1. Project: Car Park Finder
1.1. Overview
Car Park Finder is a desktop command-line application for all car owners seeking a hassle-free way to find
Housing & Development Board (HDB) car parks in Singapore.
Our application allows you to view vital information such as parking lots availability for each car park, so that you can plan ahead of your trip.
If you use the computer frequently and commute by driving, why not try out Car Park Finder. No installation is required and getting started is as simple as typing out a text message.
Main Features of Car Park Finder:
-
Find by location helps to narrows down the car parks near your destination
-
Filter through the list of car parks with the use of flags to get the preferred choice of car park
-
Receive notifications on how many parking lots are still available for a car park
-
Use of autocomplete to simplify overcomplicated commands by prompting correct format
1.2. Summary of contributions
This section is to provide a summary of my contributions to the project.
My main task for Car Park Finder was to query data from the database and update the information of all the HDB car parks.
This allows users of the application to:
-
Find car parks based on their postal code, address or car park number
-
Filter car parks using information like night parking as flags etc
-
View and click on car parks in the Google Maps found inside Car Park Finder
-
Major enhancement: Query data to provide real-time updates
-
What it does: This enhancement is broken down into two commands, namely
query
andnotify
. Basically both commands retrieve data from the database and either add or update the car parks in Car Park Finder. Users will then be able to find and filter the car parks base on their preferences and pick one that is most suitable for them. -
Justification: This feature is part of the core requirements for the application to work properly. Without it, users would not be able to view all the HDB car parks in Singapore easily or receive notifications about them.
-
Highlights: This enhancement requires an in-depth analysis of design alternatives as it is written from scratch. It serves as a basis for other features and without it, there would not be any car park(s) data. This affects features to be added in future, since a change in car park information could require changing the whole system. The implementation too was challenging as it required threading to ensure a fast and efficient load time while querying the database.
-
Credit: Gson library for converting JSON data from data.gov.sg database to Java Objects for use in the project.
-
-
Minor enhancements:
-
Added marker clustering for the Google Maps to limit the number of markers on the map. It also zooms in onto the location when clicked on.
-
Credit: This enhancement uses Javascript and was based off the tutorial here provided by Google.
-
-
Updated the status bar to show the total number of car park(s) in Car Park Finder. I also added a check for List and Clear commands to either show all the car park(s) or display a "no car parks(s) available" message instead of displaying zero.
-
-
Code contributed: Click here to view my code on the CS2103T Project Code Dashboard.
-
Other contributions:
-
Project management:
-
Managed releases
v1.1
-v1.4
(7 releases) on GitHub -
Handled deliverables and deadlines for the entire team
-
-
Enhancements to existing features:
-
Documentation:
-
Community:
-
Tools:
-
2. User Guide Contributions
For my main contributions to the User Guide, they consist of the sections documenting Query and Notify commands. They showcase my ability to write documentation targeting end-users in a clear and concise manner.
2.1. Querying data: query
On your first time running Car Park Finder, you will see nothing on your screen. Do not panic! By type query
into
the Command Box, it will start to fill the application with all the available HDB car parks.
Format | Abbreviation |
---|---|
query |
q / qu / que / quer |
-
If you wish to update all the car parks at a later date, just type this command again.
-
Remember that Car Park Finder does not automatically execute this command at the start.
2.1.1. Example: Let’s get some car parks!
It might be your first time using Car Park Finder, or you decided to clear out all the car parks. Whatever the case, its time to get some car parks into the application.
Loading… please wait… |
Step 1. Type query
into the Command Box. The message above should appear in the Message Box as confirmation that you
typed in correctly. Take note that you cannot type anything else once you press Enter.
Step 2. Wait for Car Park Finder to finish loading. It will only take awhile, so why not just sit back and relax?
2099 car parks updated |
Step 3. Once it has finish loading, the message above should appear. Please refer to Figure 1 to check if you are successful in getting the car parks.
2.1.2. Example: Query error(s)
Encountered a query error? Take a look below to see what went wrong.
Unable to retrieve car parks from data.gov.sg |
This error occurs when there is a connection problem to data.gov.sg. Please check your internet connection and try again.
2.2. Enabling Notification : notify
|
After selecting a car park, you can choose to receive notifications on how many parking lots are still available. It sends an update periodically, so once it is enabled you do not need to type the command again into the Command Box.
Format | Abbreviation | Example(s) |
---|---|---|
notify TIME_SECONDS |
n / no / not / noti / notif |
notify 0 |
-
If you did not select a car park beforehand, Car Park Finder would not know which car park needs to be updated.
-
You can set within a range of 10 to 600 seconds (10 minutes). Decimal values are not allowed.
-
Typing
query
orclear
will disable the notification. -
As
notify
is based on the index ofselect
, you can select another car park to receive notification while it is enabled. -
This means if you type
find hougang
and the original car park is gone but the index is still valid, it will update that car park instead.
2.2.1. Example: Receive notification every 10 seconds
This example assumes you have already decided on a car park. For more information on how you can choose a suitable car park in Car Park Finder, please click here. |
It is time to head to your destination. Before you do so, why not check if the car park is full?
Step 1. Select your car park from the list. In this example, we will choose the 5th car park as shown in Figure 2.
Notification enabled for car park AM18 |
Step 2. Type notify 10
into the Command Box. The message above should appear in the Message Box
as confirmation that you typed in correctly.
Car park AM18 has 159 lot(s) available |
Car park AM18 has 167 lot(s) available |
Car park AM18 has 152 lot(s) available |
Step 3. Now that the notification is enabled, you do not have to do anything else. It will inform you whether there is a change in parking lots availability through visual updates like these messages above and Figure 3.
2.2.2. Example: Turn off notification
Now that you have gotten tired of the notification, it is time to disable it.
Notification disabled |
The message above will be displayed when you type notify 0
into the Command Box. You can also
exit Car Park Finder to turn off notifications.
Notification already disabled |
If you are unsure whether you have already disabled the notification, the message above will be shown if you have done so.
2.2.3. Example: Notify error(s)
Encountered a notify error? Take a look below to see what went wrong.
Invalid command format! |
This error occurs when you type the command wrongly in the Command Box. Either try again or just copy the command here.
Remember that notify 0
is how you disable the notification.
Cannot notify without selecting a car park first |
This error occurs when you did not select a car park and tried to enable notification. Please click here for more information.
Unable to retrieve car park information from data.gov.sg |
This error occurs when the data is not available from data.gov.sg. Therefore, it is not possible to enable notification for that particular car park.
Unable to retrieve car parks from data.gov.sg |
This error occurs when there is a connection problem to data.gov.sg. Please check your internet connection and try again.
3. Developer Guide Contributions
For my main contributions to the Developer Guide, they consist of the sections documenting Query, Notify and Weather commands. They showcase my ability to write technical documentation and the technical depth of my contributions to the project in a clear and concise manner.
3.1. Query feature
The query feature updates the information of every car park using the latest information provided by data.gov.sg database.
3.1.1. Overview
The mechanism does an API call to the website data.gov.sg to obtain car park information in JSON
format.
An external library Gson
is used to parse the data in GsonUtil
. The data is stored internally as a CarparkJson
object.
Some notable methods that GsonUtil
implement are:
-
GsonUtil#getCarparkData()
— Get the basic car park information from the API. -
GsonUtil#getCarparkAvailability()
— Get the total number of parking lots as well as the availability of the parking lots from another API. -
GsonUtil#fetchCarparkInfo()
— Return a list of car parks with populated data. -
GsonUtil#loadCarparkPostalCode
— Return a list of postal code information, with hashed coordinate data.
Only GsonUtil#fetchCarparkInfo()
is used in QueryCommand
, inside QueryCommand#readCarpark()
method.
A local copy of the data is saved at the end. Users only need to execute this command if they want to get the most recent information from the database. |
3.1.2. Example
Given below is an example usage scenario of how the query mechanism behaves at each step.
Step 1. The user launches the application, where the initial state of Car Park Finder is not up-to-date with the latest data published by data.gov.sg.
Step 2. The user executes the query
command to fetch the latest data. The query
command calls
GsonUtil#fetchCarparkInfo()
which in turn runs GsonUtil#getCarparkData()
and GsonUtil#getCarparkAvailability()
.
Step 3. The user waits for data to be updated. GsonUtil#getCarparkData()
establishes a connection with the API
to read JSON
data containing basic car park information.
Step 4. The JSON
data is parsed using Gson
library and stored inside CarparkJson
. A HashSet
is used to consolidate
all the car parks and prevent duplicate entries.
Step 5. Once GsonUtil#getCarparkData()
is done getting all the basic car park information,
GsonUtil#getCarparkAvailability()
retrieves additional details of the parking lot. The process is similar to how GsonUtil#getCarparkData()
retrieves data from the API.
Step 6. GsonUtil#getCarparkAvailability()
appends the additional the parking lot details using CarparkJson#addOn()
The loading of postal code, |
Step 7. Next, a final check is done to see if there is any car park with no parking lot data. The value '0' is added if there is no data.
Step 8. Finally an ArrayList<ArrayList<String>>
is returned from GsonUtil#fetchCarparkInfo()
to update the car park finder state.
The line of text at the bottom of the application then will show that the application is updated.
If |
Please refer to the Figure 4 below for how the query
operation works.
For more details on the internal workings of Model , please refer to Undo/Redo.
|
3.1.3. Design Considerations
To find out why certain designs were chosen for the query feature, please read the following section for the explanation and reason behind such choices.
Aspect: How query
executes
-
Alternative 1 : Wait for data to be queried sequentially.
Pros
No side effects as everything is done sequentially. If an error occurred, it is easy to trace the source.
Cons
The application hangs while data is being queried due to long processing time.
-
Alternative 2 (current choice) : Data is queried using a separate thread.
Pros
Application can provide feedback to the user as the data is being fetched in the background.
Cons
Reading the car park list while querying might cause unintended side effects if not handled properly.
Aspect: Data structure to support query
command
-
Alternative 1 (current choice): Use
ArrayList<ArrayList<String>>
to store car park information.Pros
Easy to maintain and iterate through an array list of array lists to get a specific car park.
Cons
Using an
ArrayList<ArrayList<String>>
can be confusing and unintuitive. Accessing elements is also not that efficient. -
Alternative 2 : Use a
HashMap<String,Carpark>
to store data.Pros
Much more efficient in accessing elements by using a key and better code readability.
Cons
HashMap
does not provide an ordered collection. Since order of insertion is not known, the output for the list of car parks might be different every time.
3.2. Notify feature
The notify feature updates the lot availability of the car park selected by the user. It also displays a notification message periodically to inform the user.
3.2.1. Overview
The notify mechanism is facilitated by NotifyCommand
and NotifyCommandParser
. It enables notifications at a given
interval and disables notification when it detects that the interval is set to '0'.
The NotifyCommandParser
implements Parser
with the following operation:
-
NotifyCommandParser#parse()
— This checks the validity of the argument on whether it is non-negative integer and between 10 to 600, including 0. It throws aParseException
when then user input does not conform the expected format.
When notification is enabled:
-
NotifyCommand
callsScheduledExecutorService#scheduleAtFixedRate()
to startNotifyTimeTask#run()
and repeat the execution at a fixed interval. -
At every interval,
NotifyTimeTask
will callGsonUtil#getSelectedCarparkInfo()
to get the lot details from the API and update the specific car park usingCarpark#setLots()
. -
It will also create an event called
NewResultAvailableEvent()
to update the user on how many parking lots are available by displaying a message.
Take a look before at the code snippet below for more details on how NotifyTimeTask
works.
@Override
public void run() {
try {
//...
// Get the data from the database
List<String> updateData = new ArrayList<>(GsonUtil.getSelectedCarparkInfo(
selectedNumber.toString()));
// Update the specific car park
model.getCarparkFinder().getCarparkList().parallelStream()
.filter(carpark -> carpark.getCarparkNumber()
.equals(selectedNumber))
.findFirst().ifPresent(carpark -> carpark.setLots(
new LotsAvailable(updateData.get(1)), new TotalLots(updateData.get(2))));
// This event updates the UI for the CarparkListPanel to account for any changes
// in lotsAvailable variable
EventsCenter.getInstance().post(new NotifyCarparkRequestEvent());
model.commitCarparkFinder();
// Check if notification is enabled
if (CarparkListPanel.getTimeInterval() > 0) {
// show notification...
}
}
//...
}
3.2.2. Example
Given below is an example usage scenario of how the notify
mechanism behaves at each step.
To enable it, |
Step 1. The user executes select 10
to select the 10th car park in the list.
Step 2. The user then executes notify 10
, indicating the interval to be 10 seconds.
Step 3. The NotifyCommandParser#parse()
gets called to parse the arguments supplied. It checks if the interval
is valid non-negative integer, in the range of 10 to 600 and including 0.
Step 4. A new instance of NotifyCommand
is created by NotifyCommandParser
, and the argument then gets stored as
int targetTime
in NotifyCommand
.
Step 5. Inside NotifyCommand
, ScheduledExecutorService#scheduleAtFixedRate()
starts NotifyTimeTask#run()
with
targetTime
as one of the parameters passed in. This sets NotifyTimeTask
to run and repeat every targetTime
.
Step 6. When NotifyTimeTask#run()
is active, GsonUtil#getSelectedCarparkInfo()
gets called and fetches the data
from the database. It returns the specific car park to be updated.
Step 7. To find which car park to be updated in the list, parallelStream()
is used to filter through. Once it is found,
Carpark#setLots()
gets called to update the TotalLots
and LotsAvailable
variables.
Step 8. To show that the list of car parks is updated, NotifyCarparkRequestEvent()
and NewResultAvailableEvent()
are
created to update the UI and display a message respectively.
Step 9. Every 10 seconds, the user will receive a notification on the number of available lots left for the 10th car park.
Step 10. The whole process will repeat itself starting from Step 6, until ScheduledExecutorService#shutdownNow()
gets
called by notify 0
which disables notification.
Turning notification off goes through the same process as above, but instead of running
|
Figure 5 below summarises what happens when a user executes the notify
command:
notify
command3.2.3. Design Considerations
To find out why certain designs were chosen for the notify feature, please read the following section for the explanation and reason behind such choices.
Aspect: How notify
executes
-
Alternative 1 : Set an interval for data to be queried sequentially.
Pros
No side effects as everything is done sequentially. If an error occurred, it is easy to trace the source.
Cons
Performance issues as the application might stall, despite only updating one car park.
-
Alternative 2 (current choice): Data is queried using a separate thread every interval.
Pros
Application can provide feedback to user as the data is being fetched in the background.
Cons
Reading the car park list while updating on a separate thread might cause unintended side effects if not handled properly.
Aspect: Class to run NotifyTimeTask
-
Alternative 1 : Use
Timer
class to run and scheduleNotifyTimeTask
.Pros
Using
Timer
class allows fornotify
to run in the background thread and handles the repeated calls as well.Cons
If the user were to change the system clock, it might affect how
Timer
class works. This is because the class relies on the system time to calculate when to run the task again. -
Alternative 2 (current choice): Use
ExecutorService
class to run and scheduleNotifyTimeTask
.Pros
ScheduledThreadPoolExecutor
supports the use of multiple threads, whereasTimer
only has one. Also,ScheduledThreadPoolExecutor
is not affected by the system clock.Cons
Unable to handle more complex operations if the application continues to add more features. For example, handling updates to multiple car parks with different intervals.