Hands On

Touring our New Geocoding and Search API

By Raymond Camden | 08 April 2020

A few months back, we released a new version of our Places (Search) API. While the older version is still supported, developers looking to add location-based search to their applications should absolutely take a look at our new Geocoding and Search APIs. In this article I'm going to give you a quick tour of some of the cooler parts of the updated APIs. Then I'm going to wrap with an example application I call - "Plan a Night" - it's going to use a bit of randomness to find places to go out near you, once, you know, we're actually allowed to go out again!

I'm going to cover three main aspects of the API covering three unique features - our autosuggest API, our discover API, and our browse API. Lets start off by talking about autosuggest. This feature let's users type in search terms in a free form manner. Our API will then attempt to find matches, being forgiving of mispellings and partial entries.

While you can (and should) check the API reference, a typical autosuggest request will pass a search term and a geograpic filter that's either a simple center point, or a "region" defined by politcal boundries, a circle around a point with a radius, or a custom box. To keep things simple, here's how you would search for "cagun" (again, I'm mispelling on purpose) results focused around my home town:

https://autosuggest.search.hereapi.com/v1/autosuggest?at=30.22,-92.02&limit=10&apikey={{apiKey}}&q=cagun

This returns an array of items. Here's one item just as an example:


 {
	"title": "Cajun Field",
	"id": "here:pds:place:8409vqec-5d50a5b72bcb4a65ad229f272ae12965",
	"resultType": "place",
	"address": {
		"label": "Cajun Field, 201 Reinhardt Dr, Lafayette, LA 70506-4252, United States",
		"countryCode": "USA",
		"countryName": "United States",
		"state": "Louisiana",
		"county": "Lafayette",
		"city": "Lafayette",
		"street": "Reinhardt Dr",
		"postalCode": "70506-4252",
		"houseNumber": "201"
	},
	"position": {
		"lat": 30.21459,
		"lng": -92.04069
	},
	"access": [
		{
			"lat": 30.21422,
			"lng": -92.04045
		}
	],
	"distance": 2077,
	"categories": [
		{
			"id": "800-8400-0000"
		},
		{
			"id": "800-8600-0180"
		}
	],
	"highlights": {
		"title": [
			{
				"start": 0,
				"end": 5
			}
		],
		"address": {
			"label": [
				{
					"start": 0,
					"end": 5
				}
			]
		}
	}
}

There's a lot to digest here but I'll point out the most crucial (in my opinion) aspects below:

  • First is the title. This is typically what you would display to the user.
  • Next is the address and position values. The address is what you would show to the user and position would be used with our maps.
  • The distance to the result in meters, you know, the unit of measurement pretty much the entire planet uses.
  • And then categories, which lets you determine the type of place the item is. Categories get pretty deep and you can find documentation for them here.

There's one more aspect of the result you may want to pay attention to - the resultType. In the above example you can see it's "place". Other values like "unit" and "locality" exist, but one in particular may be useful - "categoryQuery". When your item is of this type, it's like the API suggesting you browse within a particular category of results. In fact, this type of item will have an "href" property. Here's an example:

https://autosuggest.search.hereapi.com/v1/discover?q=restaurants&_ontology=restaurant&at=30.22%2C-92.02

You can use this URL to request items that match the category and still find results based on the location of the initial search. Do note though that the apikey value is not present and you'll have to add that manually.

Want to see it in action? Check out the full, if simple, demo below. It uses Vue.js to search as you type in a field. This particular demo is tied to my geographic location but you could easily modify it to use the browser's Geolocation API to use your current position. (Or if you're a mapping nerd like I'm turning into, you may know your latitude and longitude already. ;)

See the Pen HERE Autosuggest by Raymond Camden (@cfjedimaster) on CodePen.

Alright, now let's turn our attention to the two other aspects of this new product, Discover and Browse. These are somewhat similar APIs and which you use will depend on your exact need.

Discover is used when want the user to type in a search term to find results. This search term can be very broad and understands things like proximity (X near Y) and even chains. This allows for neat things like asking for a bank near a Subway.

Browse is used more often when you have a specific category in mind. While you can still combine this with a textual search, you would use this when you know what type of result you want, so for example, all night clubs that have "beer" in their name.

To help bring this home a bit, let's look at a few example of each. First, let's search for comic books near my location:

https://discover.search.hereapi.com/v1/discover?q=comic books&at=30.22,-92.02

As with autosuggest, the result is an array of items, here's one as an example:


{
	"title": "Acadiana Comic & Collectibles",
	"id": "here:pds:place:8409vqec-2b1784049740487cb6fba868184132d7",
	"resultType": "place",
	"address": {
		"label": "Acadiana Comic & Collectibles, 2506 Johnston St, Lafayette, LA 70503-3238, United States",
		"countryCode": "USA",
		"countryName": "United States",
		"state": "Louisiana",
		"county": "Lafayette",
		"city": "Lafayette",
		"street": "Johnston St",
		"postalCode": "70503-3238",
		"houseNumber": "2506"
	},
	"position": {
		"lat": 30.20875,
		"lng": -92.03577
	},
	"access": [
		{
			"lat": 30.20861,
			"lng": -92.0357
		}
	],
	"distance": 1965,
	"categories": [
		{
			"id": "600-6700-0087"
		},
		{
			"id": "600-6900-0103"
		}
	],
	"contacts": [
		{
			"phone": [
				{
					"value": "+13372349639"
				}
			],
			"email": [
				{
					"value": "acadianacomics@att.net"
				}
			]
		}
	],
	"openingHours": [
		{
			"text": [
				"Tue: 11:00 - 16:00",
				"Wed: 10:00 - 19:00",
				"Thu, Fri: 11:00 - 19:00",
				"Sat: 11:00 - 15:00"
			],
			"isOpen": false,
			"structured": [
				{
					"start": "T110000",
					"duration": "PT05H00M",
					"recurrence": "FREQ:DAILY;BYDAY:TU"
				},
				{
					"start": "T100000",
					"duration": "PT09H00M",
					"recurrence": "FREQ:DAILY;BYDAY:WE"
				},
				{
					"start": "T110000",
					"duration": "PT08H00M",
					"recurrence": "FREQ:DAILY;BYDAY:TH,FR"
				},
				{
					"start": "T110000",
					"duration": "PT04H00M",
					"recurrence": "FREQ:DAILY;BYDAY:SA"
				}
			]
		}
	]
}

As you can see, this is a fairly detailed response. As before I suggest checking the API docs for a full reference, but here are a few things I'd call out.

  • Title and address give you nice human-readable results to show the user.
  • Position gives you something you can render on a map.
  • Contacts provide multiple ways (if available) of rearching the business.
  • And my favorite, openingHours provides both human readable and machine parseable forms of information related to when the business can be accessed. Note there's a shorthand "isOpen" you can quickly check.

Now that we know the basics of the API, let's build a simple demo. I'm going to start off by creating a map centered on Lafayette. I zoomed in a bit though.


var platform = new H.service.Platform({
	'apikey': KEY
});

// Obtain the default map types from the platform object:
var defaultLayers = platform.createDefaultLayers();

var map = new H.Map(
	document.getElementById('mapContainer'),
	defaultLayers.vector.normal.map,
	{
		zoom: 14,
		center: { lat: 30.22, lng: -92.02 },
		pixelRatio: window.devicePixelRatio || 1
	}
);
		
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
var ui = H.ui.UI.createDefault(map, defaultLayers);

In my UI, I added a simple text field to allow user's to enter a search term. I added a button and used this handler with it:


let box = document.querySelector('#searchBox');
let btn = document.querySelector('#searchBtn');

btn.addEventListener('click', async () => {
	let resp = await fetch(`https://discover.search.hereapi.com/v1/discover?apikey=${apiKey}&q=${encodeURIComponent(box.value)}&at=30.22,-92.02&limit=10`);

	let data = await resp.json();
});

Notice I've focused my API call in the same location as the map. In order to render the results, I used a conbination of markers and info bubbles. Markers will be displayed for all the results and an info bubble is used when a user clicks on it. Our API returns a lot of information about each result and I decided to display just the title and human-readable address. There's a bit more JavaScript involved to handle all of that, but the Discover integration was rather simple. You can see the complete source code in the embed below. (Note that the embed may be a bit small. You can click to view it on CodePen and have a bit more space.

See the Pen HERE Discover demo by Raymond Camden (@cfjedimaster) on CodePen.

Alright, now let's look at the final of the three things I wanted to cover - the Browse endpoint. Unlike Discover which is a bit more free form, Browse is useful when you've got a specific category, or set of categories, you want to focus on. Browse can still take a search value to go along with the categories you want to use.

I already shared the docs for categories above, but in general there's two types of categories. There's a broad set that define certain groups at different levels of specificity. I think the docs do a great job explaining this so I'm just going to steal (sorry, "borrow") their example. Using 100 for a category represent a level one, or top level, category descriptor named "Food and Drink". You can then go deeper and use 100-1000 to represent a restaurant. But wait - there's more. You can get one more level precise and use 100-1000-0001 to represent a "Casual Dining" place.

If you like food (I do, I like food), then you're in luck. We also have an incredibly detailed food categorization feature as well. This lets you specify things like Cajun food, which is category 101-070. For Asian food, there are 22 different second level categories, like Chinese versus Korean, and then numerous options under that as well. As an example, 201-055 is Chinese Hot Pot (which if you've never had, you're missing out!).

To use the API, you pass in the category (or multiple, separated by comma) and an option search filter called name:

https://browse.search.hereapi.com/v1/browse?name=SOMETHING&at=30.22,-92.02&categories=203-026

As with Discover, you need to specify a location and you've got options for how to do that. Again, check the API docs for details. I built a demo of this API that basically just modifies the previous one. It's modified to search just for sushi (203-026) and again centered on my home town. Literally then the only change in the entire demo is here:


let resp = await fetch(`https://browse.search.hereapi.com/v1/browse?apikey=${apiKey}&name=${encodeURIComponent(box.value)}&at=30.22,-92.02&categories=203-026&limit=10`);

You can run this demo via the CodePen below, and again, if it's a bit too small, just click to view it in another tab.

See the Pen HERE Browse demo by Raymond Camden (@cfjedimaster) on CodePen.

The Great Random Night Out

So imagine you aren't locked in your house and can actually spend the night out. After being indoors for so long, you may be forgiven for being a bit unsure of what exactly to do. In order to make things easier, my demo will select a random restaurant and a random item from the "Going Out-Entertainment" category. This is done by simply asking for a lot of results and selecting one. I built this as a simple Vue.js application. Here's the HTML portion:


<div id="app" v-cloak>
  <div style="margin: auto;width: 400px;">
    <button :disabled="searching" @click="doIt">DECIDE FOR ME</button>
  </div>
  <div v-if="result" class="results">
    <strong>Congratulations!</strong> 
    You will be eating at {{ restaurant.title }} (located at {{ restaurant.address.label}}) and then having fun at {{ entertainment.title }} (located at {{ entertainment.address.label}})
  </div>
</div>
And here's the code:

Vue.config.productionTip = false;
Vue.config.devtools = false;

const KEY = 'c1LJuR0Bl2y02PefaQ2d8PvPnBKEN8KdhAOFYR_Bgmw';

const app = new Vue({
  el:'#app',
  data: {
    result:false,
    searching:false,
    restaurant:null,
    enertainment:null
  },
  methods: {
    async doIt() {
      this.result = false;
      this.searching = true;
      this.restaurant = await getRestaurant();
      this.entertainment = await getEntertainment();
      console.log(this.entertainment);
      this.searching = false;
      this.result = true;
    }
  }
})

async function getRestaurant() {
  let resp = await fetch(`https://browse.search.hereapi.com/v1/browse?apikey=${KEY}&at=30.22,-92.02&categories=100-1000-0000&limit=100`);
  let data = await resp.json();
  return data.items[getRandomInt(0, data.items.length)];
}

async function getEntertainment() {
  let resp = await fetch(`https://browse.search.hereapi.com/v1/browse?apikey=${KEY}&at=30.22,-92.02&categories=200-2000-0000&limit=100`);
  let data = await resp.json();
  return data.items[getRandomInt(0, data.items.length)];
}

function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}

The crucial bits here really are getRestaunrant and getEntertainment. Both do the exact same thing really with just a change to the desired category. Once the results are returned, a random item in the array is selected. (And notice that this could be a bit more bulletproof, it's possible that if you change your location that no restaurants or entertainment may exist. If so, don't change the code. Move.) You can play with this demo below:

See the Pen Decide For Me by Raymond Camden (@cfjedimaster) on CodePen.

I hope this little tour through our new Search API has been fun, and be sure to dig deep into the guide for more inspiration!