A short while ago, I received a mysterious package in the mail from my friends at Garmin. Inside I found a shiny new fēnix 5 Multisport GPS Watch. I was thrilled, especially because I was just about to head off for a short 5-day hiking trip to Madeira. Perfect timing!
In the end the trip was just OK, but the fēnix 5 turned out to be amazing. It’s got everything you want in an outdoor adventure watch (GPS! Epic battery life! Readable screen! Waterproof! Every possible activity! Every imaginable statistic!) and then some. Seriously, the device is so ridiculously capable that I’m still finding cool new features on a regular basis. The watch has now become my constant companion, even when navigating the urban trails of Berlin on my way to work.
Rain or shine, the fēnix 5 is the perfect hiking companion.
One of the reasons the trip turned out to be just OK was that some serious fog rolled into the mountains on the third day. It was so impenetrable that hiking was out of the question. My fiancée and I had to entertain ourselves otherwise and so we headed into town, looking for interesting things to see. As it turns out, Madeira is a solid destination for hiking, but not necessarily gifted with great museums - unless of course you enjoy pictures of Cristiano Ronaldo licking his collection of golden shoes.
This hiccup got me thinking though. If the fēnix is a great hiking companion, maybe it could also be a great sightseeing companion? After all, it’s also smartwatch and I have access to the world’s greatest location technology platform. As I was pondering this existential question, I looked up and there he was - CR7 himself, in wax form, smiling down on me. I took this as a sign and immediately went to work on building a sightseeing app using the Connect IQ SDK and the HERE Location APIs.
Well, not immediately. First the fog went away, and we enjoyed some more hiking. Also, when I returned to the office in Berlin there were 762 new emails waiting for me. But, I went to work immediately after marking those as read.
CR7 approves. But not officially, because we don’t have that kind of sponsorship money.
The app I ended up building works like this. First, it finds your current location using GPS. Then, it searches for nearby points of interest which are relevant to a touristically inclined person and displays them in a list. Said person can scroll through the list and select the entry they are most interested in. The app will then provide routing instructions toward the selected point of interest. The user can follow these instructions, walk to the sight and see it.
To get a better idea of what this looks like in action, let’s have a look at the flow of the user interface.
User interface flow of our sightseeing app.
Here are some pictures of the app in action in Berlin. I used it to walk to the East Side Gallery, a piece of the former Berlin Wall which has been turned into an art project. It’s also become one of the world’s most Instagrammed places and, consequently, a bit of a tourist trap. 2 out of 5, would not Instagram again.
Using the app to get to the East Side Gallery.
Before we can start coding, a brief note. I will be assuming that you are at least somewhat familiar with building Connect IQ applications. If you’ve never created a Connect IQ app before, you should head over to the Garmin Developer Portal, follow the Getting Started section and have a look at the rest of the Programmer’s Guide. (Seriously, do it! Monkey C is like the language you didn’t know you already knew. I’ve had a lot of fun making this app.)
In this blog post we will mostly be discussing how to integrate the HERE Location APIs into your Connect IQ applications. When setting up your application, please make sure to give it permissions for communications (to allow our API requests to go through) and positioning (to enable access to the GPS model).
Alright then, time to get going! To build our app, we need to make use of two location APIs, the HERE Places API and the HERE Routing API. Let’s take a closer look at them.
The HERE Places API lets us search for and get information about points of interest (POIs), such as gas stations, restaurants, train stations, museums, etc. This is a powerful API with many capabilities, but the relevant features for our app are:
- searching for POIs around our location
- filtering these POIs by a category for relevant tourist sights
- providing information about the distance of these POIs from our location
The HERE Routing API lets us find routes between two or more waypoints, using a variety of transport modes, such as car or public transit. Once again, this is a powerful API with many features, but for us the relevant ones are:
- finding a route between our location and a selected POI
- providing a walkable route using pedestrian mode (as a tourist we are likely on foot)
- providing human-readable text instructions to display on the watch
To sum this up, we will be using the HERE Places API to find nearby sights and display them to the user. When the user has selected a sight, we will use the HERE Routing API to get routing instructions and display these to the user as well.
- url (the API endpoint)
- parameters (the parameters we want to pass to the API)
- options (determines how Connect IQ handles the request and its response)
- responseCallback (the method that will handle the response)
To determine the correct values, we need to take a closer look at one of our APIs. Let’s start with the HERE Places API.
The HERE Places API offers multiple endpoints for browsing, searching by strings, autosuggest, and much more. The correct choice for our use case is the “discover/explore” endpoint, which “retrieves a list of relevant places nearby a given position”.
The following parameters are relevant for our use case:
- app_id (your credentials)
- app_code (your credentials)
- at (our position as a decimal latitude/longitude coordinate, e.g., “52.5308,13.384”)
- cat (the list of categories we want to filter by, e.g., “sights-museums”)
- size (how many places we want in our results, e.g., “15”)
To get our current location, we need to make use of the GPS feature in our Garmin device. The Connect IQ SDK provides the Toybox.Position module for this. First, we need to request location events and provide a callback function using the Position.enableLocationEvents() function. One place to start and stop requesting location events are the onStart() and onStop() functions of our app.
When the GPS has found a position, the callback function then receives a Position.Info object which contains all the information provided by the positioning system. This includes everything from accuracy, heading and speed to a Location object, which has the latitude and longitude we are looking for.
The Places API provides a variety of categories we can use to filter our search results. There’s even a dedicated API endpoint to get a list of locally relevant categories. However, for our use case we can simply use one of the permanent categories and set the cat parameter to “sights-museums”.
Finally, we need to tell the API how many different points-of-interest we would like to receive in the response. As the results are sorted by popularity and we don’t want the user to scroll through a large list of possible sights, we can set the size parameter to “10” for now.
So let’s make a call to the API using makeWebRequest() with our endpoint and parameters.
The response from the HERE Places API will be provided in JSON, so we need to tell the Connect IQ SDK to treat it as such, using the appropriate Communication.REQUEST_CONTENT_TYPE option. We also provide “onReceive” as a response handler function. Let’s look at how this function works.
The response handler receives a Communications. HTTP_RESPONSE_TYPE code and the data contained in the API response body. Let’s have a look at an example of the JSON that the API returns.
As we can see the response contains a list of items. These are the points of interest we are looking for. The title and distance elements are essential information for the user. The category object is also of interest, in particular its title element which helps with displaying the category. We also need to store the position information, because this will end up as the destination waypoint parameter for our Routing API call later on.
Note that we don’t have to manually parse the JSON in Connect IQ as the SDK automatically transforms the response into a Monkey C Dictionary object.
Now that we have an items object (an array that contains the information we’re looking for), we can extract that information and display it when the user scrolls through the list of places.
So far so good! We have now understood how to make REST API calls using the Connect IQ SDK and how the HERE Places API works. We’ve put these two together, so let’s run our code and see what happens.
Unfortunately, it doesn’t work. Instead of a list of places, we are getting an error: 402. According to the ConnectIQ documentation this is a “NETWORK_RESPONSE_TOO_LARGE” error. What does this mean exactly?
As it turns out, memory can be a very limited resource when developing for wearables and so the maximum size of a makeWebRequest() response ConnectIQ accepts is 16KB. Whenever we are calling an API from a Connect IQ device, we must therefore ensure that the response remains under that limit.
With the HERE Places API, we have two options for reducing our response size. First, we can simply reduce the “size” parameter. Experience has shown that setting it to a value less than 8 is sufficient to get responses under 16K in size. For our purposes this will likely be sufficient as we don’t want to present the user with a very long list.
However, the HERE Places API also supports pagination, which allows us to be more sophisticated.
If you take another look at the JSON above, you will see that beside the list of items, the response also has a next element which contains the full API call required to get the next page of results. For example, if you have requested five nearby places of the category “sights-museums”, then using the request in next will give you another response with five additional nearby places of that category. This response will also contain a next element, so you can continue the process. In addition, it will contain a previous element, which allows you to go back as well.
To request the next or previous page in Connect IQ, you can simply make another web request, set the url parameter to the content of the next or previous element and leave the parameters empty.
Whichever approach you choose to reduce the size of the request, remember that you can never completely predict it, so make sure to handle 402 errors appropriately.
Now that we have the Places API working, we can take a look at the Routing API.
The HERE Routing API only has one endpoint but provides a large number of possible parameters. Fortunately, requesting a basic route requires just three parameters plus your credentials. Let’s look at these parameters now.
- app_id (your credentials)
- app_code (your credentials)
- waypoint1 (your start waypoint, once again in decimal lat/long format)
- waypoint 2 (your destination waypoint, once again decimal lat/long format)
- mode (determines whether the route is calculated for driving, biking, walking, etc.)
We’ve already discussed how to get your credentials and are familiar with latitude/longitude parameters. This leaves us with the mode parameter. The Routing API is quite powerful and supports a number of routing modes. You can route using a car, a bike, public transit and trucks. Most importantly, you can also get routes on foot, which is what we want to do when sightseeing. It’s not exactly trail running, but at least we want to meet our step and activity goals for the day, right? We therefore set the parameter to “shortest;pedestrian” or “fastest;pedestrian”, depending on our preference.
Using makeWebRequest(), our code will look like the following.
Once again, the HERE Routing API returns a JSON response. Looking at it, we can see that it contains a list of routes. (Multiple routes are possible because we could request a number of alternative routes. As we have not done this, there is only one route.)
This route contains a list of legs. Legs partition a route between multiple waypoints. Since we have only chosen two waypoints, there is only one leg.
The leg contains a list of maneuvers. Maneuvers describe the action that needs to be performed to progress along the route. This sounds like the information we are after! In fact, the instruction attribute in the maneuver has a verbal description of the action the user should perform, such as “Turn right on Stralauer Allee. Go for 473m.”
Neat! Once again, the Connect IQ SDK wraps up this JSON response in a Dictionary object for us. We can parse this object and display the routing instructions to the user.
There is an issue, however, and we are already familiar with it. What if the routing response is larger than 16KB? Fortunately, there are some strategies to reduce the size of the routing response.
As you will have noticed there is a lot of additional information available in the response, even though we’re really only interested in the data in each maneuver. Using attribute switches, we can in fact tell the HERE Routing API to only show us the specific data we are interested in. Using the routeAttributes parameter we can tell the API to return only the leg information inside of a route and nothing else.
Even when using attribute switches, there is no guarantee a response will always be small enough. The size of a HERE Routing API response is even less predictable than that a HERE Places API response. A route to a distant target might just be one instruction (“Follow the road for 400km.”) and a route through many small twisting alleys might be complex, even if the destination is close by. So once again, always make sure you properly handle a possible 402 error, not just in the code, but also in the user interface.
Finally, you may have noticed that the instructions are in HTML format. You may want them to be, if you’re planning to use the markup information in your user interface; but, you can also turn this off by setting the instructionsFormat parameter to “text”.
Our code for the request now looks like this.
And that’s it! Well, sort of. Obviously, the code snippets in this blog post are somewhat simplified and there’s a lot more to building a great sightseeing app than what we’ve discussed. Fortunately, you can always have a look at the complete app on GitHub.
Speaking of seeing, why not come and see the HERE team at the 2018 Garmin CIQ Developer Summit! Among other things, I will be conducting a workshop on how to use location technology and the HERE Location APIs in your Garmin ConnectIQ applications. Be sure to follow us on Twitter (@heredev) for updates and more information.