Who wants ice cream?! — In this series of blog posts we are going to develop a small web application called "Gelary"; using HERE maps and services. Gelary aims to disrupt the ice-cream market by enabling ice-cream producers to deliver their sweet goods directly to their customers, wherever they are.
What will we learn in this tutorial?
In the previous post we did a fair bit of refactoring, if you haven't read it yet, you might want to take a look at it first before going on.
In order to make our application actually useful, we will have to provide our trusty delivery drivers an easy and convenient way to locate an ice-cream shop along their way to the customer. Once the driver has selected a route, we will use the Places API to search for ice-cream parlours in the vicinity of the route and display them on the map. We will further enable the driver then to select a shop and add it as waypoint to the selected route. Sound good? — Let's dive in and get those drivers on the road!
Onwards and upwards! At the core of our new feature you‘ll find yet another awesome data API called "Places";. The Places API allows us to request Points of Interest (POI) using a search query string and formatted location information. Here's an example from the developer documentation:
Looks easy enough, right? Indeed it is! — Let's start by wrapping this functionality in our own class. First we create as usual a new file called places.js in our scripts folder and include a reference in index.html somewhere before app.js:
Then we move on to defining our class. Here's our constructor:
So far so simple. First we assign a bunch of instance variables including a callback called onClickPlace, grab an instance of H.places.Search and create an array which will eventually hold our search results.
Let's continue by defining our instance methods:
The first obvious candidate is the method used to search for places by providing a query object — most likely the most used instance method on this class. This method in turn refers to a second method called getPlaces which again handles the actual request and iterates over the result. Let's take a look under the hood:
The structure of this method looks a bit familiar, doesn't it? Essentially we're defining simply the two required callbacks for our request and submit it to the API. In the case of a successful query we iterate over the response set and formate the coordinates of each POI for ease of use before passing the result on to the function defined as onSuccessCallback parameter. Nice!
In addition to our search method we add a way to clear the results and remove markers added to the map.
Finally we need a way to actually create the markers representing the result of a successful query and render them on the map. To ensure a clean slate, let's also call clearSearch before doing so.
As we are planning on enabling the user to select a shop via it's marker in order to add it to the route by clicking it, this is also the place where we bind the onClickPlace callback to the tap event handler for each marker.
That's it! Our HEREPlaces class is all done — you can check it out in it's entirety on GitHub.
So far so good but we are not yet triggering any searches anyhow. Let's change that!
In the constructor for our HEREMap class we're also instantiatiating our shiny new class HEREPlaces class: this.places = new HEREPlaces(this.map, this.platform, this.onChangeViaPoint.bind(this));
As you can see we are referencing a new method called onChangeViaPoint as callback. Let's define that next:
Here we grab the position of the provided reroutePoint and pass it onto our draw method as third parameter. So far, so mysterious. Let's take a look at the updated this.drawRoute method to understand what's going on here:
We have extended the signature of our method to accept an additional parameter — reroutePoint. That's going to be our waypoint for the selected ice-cream parlour. Obviously this point won't always be available so we have to check for it's existence and update the route options conditionally. The Route API won't accept the alternatives option if more than two waypoints are defined.
Once the route is drawn we clear the places search to reset it.
If you take a close look at our constructor you'll notice that we have updated also the signature of our HERERouter initialiser to accept a third parameter:
Looks like we'll still have to define another method before heading over there, though — onChangeSelectedRoute:
Aha! This new method is called when the route selection changes and is passed the newly selected route as parameter. Using the getBounds() and getCenter() methods we extract the center point for the route and use it to search for ice-cream parlours close by. searchForIcecreamShop sounds pretty promising but looks like we're still one method short:
At last! This is the place where we're passing our query onto our very own HEREPlaces class — looking for some sweet delight!
Remember that we had changed the signature of our HERERouter constructor? Seems like we're not quite done yet. Let's now head over there and update that class as well to suit our new requirements.
As we already know, the constructor now accepts a third parameter called onRouteChange. The passed function is assigned to an instance variable:
The place to trigger this callback is clearly the onRouteSelection method:
Once a route has been selected we'll trigger the callback. Oh, and while we're at it we'll also adjust the view bounds of the map to suit the selected route.
The time has come to try what we have created. Refresh your browser and select a route. If everything works as it should you should see a whole bunch of ice-cream cones along the selected route. Click one and watch your route adjust to include the selected location. Awesome!
You'll notice that we're now using ice-cream cone icons for both your location and the ice-cream parlours. That's very confusing. Let's start the next post with a few visual tweaks, shall we?
Wow, it's been a wild ride but the hard work has paid off. Our application is actually starting to be useful.
In the next post we'll bring Traffic data into our app for an even better routing solution. Stay tuned!