SDK for iOS Developer's Guide

Search and Discovery

SDK for iOS includes a Places API which provides functionality to search, discover, and obtain more information about places in the real world.

HERE Places helps to determine whether a business meets your needs through reviews and photos from real people. In addition to basic information such as opening hours and contact details, HERE Places can also include editorials from popular guides to help identify the best places for you to visit.

Note that offline search is supported when data connection is unavailable if the data required to perform the search has been previously downloaded.

Steps for Performing a Search

  1. Implement the NMAResultListener protocol to handle the completion of the search.
  2. Create a request using the NMAPlaces factory.
  3. Invoke the request by calling NMARequest startWithListener:.
  4. NMAResultListener request:didCompleteWithData:error: callback is triggered when the request is finished.
Note: Applications that use the Places API must honor the following prescribed workflow:
  1. Search
  2. Request for Details
  3. Perform Actions
Do not preload results that are linked from a response in order to improve performance as doing so violates HERE guidelines. For more information about usage restrictions consult the API Implementation Check List section in the Places RESTful API documentation.

Discovery Requests

HERE Places API supports the following discovery requests. Requests are created through factory methods in NMAPlaces.

Request NMAPlaces method Purpose
Search createSearchRequestWithLocation: query: Finds places that match user-provided search terms.
Explore createExploreRequestWithLocation:searchArea:filters: Finds interesting places nearby, or in the map viewport, sorted by popularity. Use this type of request if you are trying to answer the question "What are the interesting places near here?" The results may be optionally restricted to a given set of categories, which acts as a filter in terms of what places get returned.
Here createHereRequestWithLocation:filters: Helps users identify places at the given location by finding places of interest near a given point, sorted by distance. Use this type of request if you are trying to answer the question "What is near this location?" or "Where am I?" You can use this endpoint to implement features like "check-in" (by identifying places at the user's current position) or "tap to get more information about this place of interest".
Note: Normally, the closest known places are returned with the Here Discovery request but if the uncertainty in the given position is high, then some nearer places are excluded from the result in favor of more popular places in the area of uncertainty.
Around createAroundRequestWithLocation:searchArea:filters: Allows users to request places near a given point based on a location precision parameter. The places around that point are returned in order of proximity. This type of request is intended for applications that employ features such as augmented reality where places around the user's location are displayed on a device. It is intended to provide places that are likely to be visible to the user as well as important places that are farther away. The Around request is considered experimental, and its behavior and functionality are still evolving. Check future documentation for updates to this feature.
The following code example demonstrates how to perform a search discovery request. You need to implement the NMAResultListener protocol by implementing request:didCompleteWithData:error callback method, and also initialize the request by calling request startWithListener::

// Sample Search request listener
@interface NMASearchTest : NSObject<NMAResultListener> {
  NMADiscoveryPage* _result;
}
@end
@implementation NMASearchTest

// NMAResultListener protocol callback implementation
- (void)request:(NMARequest*)request
    didCompleteWithData:(id)data
    error:(NSError*)error
{
  if ( ( [request isKindOfClass:[NMADiscoveryRequest class]]) &&
    ( error.code == NMARequestErrorNone ) )
  {
    // Process result NMADiscoveryPage objects
    _result = (NMADiscoveryPage*) data;
  }
  else
  {
    // Handle error
    ...
  }
}
- (void) startSearch
{
  // Create a request to search for restaurants in Vancouver
  NMAGeoCoordinates* vancouver =
  [[NMAGeoCoordinates alloc] initWithLatitude:49.2849
          longitude:-123.1252];

  NMADiscoveryRequest* request =
  [[NMAPlaces sharedPlaces] createSearchRequestWithLocation:vancouver
          query:@"restaurant"];

  // optionally, you can set a bounding box to limit the results within it.
  NMAGeoCoordinates *boundingTopLeftCoords = [[NMAGeoCoordinates alloc] initWithLatitude:49.277484 longitude:-123.133693];
  NMAGeoCoordinates *boundingBottomRightCoords = [[NMAGeoCoordinates alloc] initWithLatitude:49.257209 longitude:-123.11275];
  NMAGeoBoundingBox *bounding = [[NMAGeoBoundingBox alloc] initWithTopLeft:boundingTopLeftCoords bottomRight:boundingBottomRightCoords];

  request.viewport = bounding;

  // limit number of items in each result page to 10
  request.collectionSize = 10;

  NSError* error = [request startWithListener:self];
  if (error.code != NMARequestErrorNone)
  {
    // Handle request error
    ...
  }
}
@end

To ensure that your application gets the best search results, you can set a location context to your search request by setting a bounding box to viewport property. In the previous example you can also replace the NMAGeoBoundingBox with the viewport from NMAMapView.

The result of a search or explore discovery request is an NMADiscoveryPage. The NMADiscoveryPage represents a paginated collection of items from which the following can be retrieved:
  • Next page request - an NMADiscoveryRequest used to retrieve additional pages of search items
  • Items for the current page - an NSArray of NMALink, either NMAPlaceLink or NMADiscoveryLink

If NMADiscoveryPage.nextPageRequest is nil, no additional results are available.

The following is an example:


...
@interface NMANextPageTest : NSObject<NMAResultListener> {
  NMADiscoveryPage* _page;  // valid NMADiscoveryPage instance
}
@end
@implementation NMANextPageTest
- (void)onNextPageAction
{
  NSError* error = [_page.nextPageRequest startWithListener:self];
  if ( error.code == NMARequestErrorNone )
  {
    // More data is available
  }
}

// NMAResultListener protocol callback implementation
- (void)request:(NMARequest*)request
    didCompleteWithData:(id)data
    error:(NSError*)error
{
  if ( ( [request isKindOfClass:[NMADiscoveryRequest class]] ) &&
    ( error.code == NMARequestErrorNone ) )
  {
    // Process NMADiscoveryPage objects
  }
  else
  {
    // Handle error
    ...
  }
}
...
@end
NMADiscoveryPage discoveryResults property contains an array of NMALink objects. The items are actually a collection of NMALink subclasses:
  • NMAPlaceLink - Represents discovery information about an NMAPlace. The NMAPlaceLink contains a brief summary about a place. Details about a place are available from the NMAPlace that the NMAPlaceLink references.
  • NMADiscoveryLink - Represents a discovery-related API link used to retrieve additional NMADiscoveryPage instances. This type of NMALink can be a result item in an Explore or Here type of search. The NMADiscoveryLink references refine discovery requests resulting in more specific results. For example, the NMADiscoveryLink may link to a discovery request to search for 'Eat & Drink', 'Going Out', 'Accommodation', and so on.
It is recommended that each type of NMADiscoveryPage be checked before it is used. In the following example it is shown how an NMAPlace is retrieved through an NMAPlaceLink:

@interface NMASearchTest : NSObject<NMAResultListener> {
  NMADiscoveryPage* _result;
}
@end
@implementation NMASearchTest
// Retrieve the place details when the user selects a displayed PlaceLink.
- (void)onPlaceLinkSelected:(NMAPlaceLink*)placeLink
{
  NSError* error = [[placeLink detailsRequest] startWithListener:self];
  if ( error.code == NMARequestErrorNone )
  {
    // More data will available.
    ...
  }
}

// NMAResultListener protocol callback implementation
- (void)request:(NMARequest*)request
    didCompleteWithData:(id)data
    error:(NSError*)error
{
  if ( ( [request isKindOfClass:[NMADiscoveryRequest class]]) &&
     ( error.code == NMARequestErrorNone ) )
  {
    _result = (NMADiscoveryPage*) data;
    NSArray* discoveryResult = _result.discoveryResults;

    for ( NMALink* link in discoveryResult )
    {
      if ( link isKindOfClass:[NMADiscoveryLink class] )
      {
        NMADiscoveryLink* discoveryLink = (NMADiscoveryLink*) link;

        // NMADiscoveryLink can also be presented to the user.
        // When a NMADiscoveryLink is selected, another search request should be
        // performed to retrieve results for a specific category.
        ...
      }
      else if ( link isKindOfClass:[NMAPlaceLink class] )
      {
        NMAPlaceLink* placeLink = (NMAPlaceLink*) link;

        // NMAPlaceLink should be presented to the user, so the link can be
        // selected in order to retrieve additional details about a place
        // of interest.
        ...
      }
    }
  }
  else  if ( ( [request isKindOfClass:[NMAPlaceRequest class]]) &&
         ( error.code == NMARequestErrorNone ) )
  {
    NMAPlace* place = (NMAPlace*)data;
    // Access to additional details about a place of interest.
  }
  else
  {
    // Handle error
    ...
  }
}
@end

Search Example on GitHub

You can find an example that demonstrates this feature at https://github.com/heremaps/ (Obj-C) and https://github.com/heremaps/ (Swift).

The NMAPlace Class

The NMAPlace class represents a detailed set of data about a physical place acting as a container for various attributes, collections of media about a place, and key-value pairs of related places. An NMAPlace object can belong to a specific NMACategory and has attributes such as:
  • A unique identifier (ID)
  • A name
  • An NMAPlaceLocation object representing the physical location of the place. NMAPlaceLocation also contains a street address and a list of the geocoordinate positions to access this place
  • An array of NMACategory objects that link to the categories assigned to the place
  • An NMALink object containing a link to the origin of supplied information, typically a website of the supplier
  • An NSString representing a URL to an icon (optional) In an offline search your application should provide the place icon.
  • Optional information such as related places, user ratings, reviews, and other editorial media

Category Filters

A place of interest can be associated with categories such as museum, restaurant, and coffee shop. While creating an Explore or Here discovery request, you can choose to provide category filters to get a more specific set of results. For example, you may want to search for sushi restaurants near Vancouver city hall.

To get a list of categories, call topLevelCategories method in NMAPlaces. From this list of categories you can then retrieve one or more levels of sub-categories. For example, "Bars/Pubs" under the "Restaurant" category. Once you have the categories, you can then create an NMACategoryFilter object and call addCategoryFilterFromUniqueId method. Note that each NMACategoryFilter object can represent multiple categories.


NSArray* categories = [[NMAPlaces sharedPlaces] topLevelCategories];

for (id category in categories)
{
  if (category.uniqueId == "restaurant" )
  {
    NMACategory* restCategory = category;
    NMAGeoCoordinates* vancouver = [[NMAGeoCoordinates alloc]
                    initWithLatitude:49.2849
                    longitude:-123.1252];

    NMACategoryFilter *categoryFilter = [NMACategoryFilter new];
    [categoryFilter addCategoryFilterFromUniqueId:restCategory.uniqueId];

    NMADiscoveryRequest* request = [ [NMAPlaces sharedPlaces]
                createHereRequestWithLocation:vancouver
                filters:categoryFilter];
    //...
  }
}
Note: The category list is retrieved and saved to cache automatically when the user creates an instance of NMAPlaces. However, if the user changes the locale or destroys NMAPlaces without a previously cached list and restarts the device in offline mode, then the cache may not automatically update when the device becomes online again.

To remedy this scenario, call refreshTopLevelCategoriesWithCompletion method to manually update the category list. You can verify that the cateogry list has been updated if topLevelCategories method does not return nil.

Text AutoSuggestion Requests

HERE Places Search API also supports text autosuggestion requests. This type of request is used for retrieveing a list of instant results (NMAAutoSuggestTypePlace) and refined search links (NMAAutoSuggestTypeSearch) that are related to a specified location context and a partial search term. For example, if you make a request with the String "rest" in Berlin, the results then contain search terms such as "Restaurant", "Rest area", and "Restorf, Höhbeck, Germany".

Note: Text AutoSuggestion is currently offered as a beta feature. APIs may change without notice. Offline requests are not supported.

To use text autosuggestions, implement a listener to handle a list of NMAAutoSuggest and call createAutoSuggestionRequestWithLocation:partialTerm: as follows:

// Sample Search request listener
@interface NMATextAutoSuggestionSearchTest : NSObject<NMAResultListener> {

}
@end
@implementation NMATextAutoSuggestionSearchTest

// NMAResultListener protocol callback implementation
- (void)request:(NMARequest*)request
  didCompleteWithData:(id)data
      error:(NSError*)error
{
  if (([request isKindOfClass:[NMAAutoSuggestionRequest class]]) &&
     (error.code == NMARequestErrorNone))
  {
    // Results are held in an array of NMAAutoSuggest objects
    // You can then check the subclass type using the NMAAutoSuggest.type property
    NSArray* textAutoSuggestionResult = (NSArray*) data;
  }
  else
  {
    // Handle error
    ...
  }
}
- (void)startSearch
{
  NMAGeoCoordinates *vancouver =
      [[NMAGeoCoordinates alloc] initWithLatitude:49.2849
            longitude:-123.1252];

  // Use following filter to show results only in the Canada
  NMAAddressFilter *filter = [[NMAAddressFilter alloc] init];
  filter.countryCode = @"CAN";
  NMAAutoSuggestionRequest *request =
    [[NMAPlaces sharedPlaces] createAutoSuggestionRequestWithLocation:vancouver
      partialTerm:@"rest" filter:filter];

  // limit number of items in each result page to 10
  request.collectionSize = 10;

  NSError *error = [request startWithListener:self];
  if (error.code != NMARequestErrorNone)
  {
    // Handle request error
    ...
  }
}
@end

You can retrieve the results of NMAAutoSuggestionRequest by first checking NMAAutoSuggest object type as shown in the following example. If the object is NMAAutoSuggestTypeSearch, it contains additional paginated results through its NMADiscoveryRequest object. If the object is NMAAutoSuggestTypePlace, you can request for more details through its NMAPlaceRequest.

+(BOOL)checkAutoSuggestResults:(NSArray*)array
{
  for (id item in array) {
    NMAAutoSuggestType type = ((NMAAutoSuggest*)item).type;
    NSString *typeString;
    switch (type){
      caseNMAAutoSuggestTypePlace:
      {
        typeString = @"Place";
      }
        break;
      caseNMAAutoSuggestTypeSearch:
      {
        typeString = @"Search";
      }
        break;
    }

    NSLog(@"Type = %@", typeString);

    NMAAutoSuggest* suggestItem = (NMAAutoSuggest*)item;

    //Retrieve information such as suggestItem.title

    if (type == NMAAutoSuggestTypePlace) {

      NMAAutoSuggestPlace* suggestPlace = (NMAAutoSuggestPlace*)item;

      //Retrieve information such as suggestPlace.vicinityDescription

      NMAPlaceRequest* detailsRequest = suggestPlace.placeDetailsRequest;

      // Get NMAPlaceResult by calling detailsRequest startWithListener:
      // ...

    } else if (type == NMAAutoSuggestTypeSearch) {

      NMAAutoSuggestSearch* suggestSearch = (NMAAutoSuggestPlace*)item;

      //Retrieve information such as suggestSearch.position

      NMADiscoveryPage* discoveryPage;
      NMADiscoveryRequest* discoveryRequest = suggestSearch.suggestedSearchRequest;

      // Get discoveryPage by calling [discoveryRequest startWithListener:]
      // ...
    }
  }
  return YES;
}
      

External References

A place of interest can contain a reference to a foreign system outside of HERE SDK. For example, an NMAPlace representing a restaurant may also contain an external reference to an entry in a restaurant review service. Each external reference in NMAPlace is tied to a reference source, and each reference can contain one or multiple identifiers.

The following external reference sources are supported in NMADiscoveryRequest and NMAPlaceRequest
  • NMAPlacesSourcePVID - Source for HERE Core Maps POI data
  • "yelp" - Source for Yelp IDs

An external reference is returned in the form of one or multiple NSString identifiers in NMAPlace, NMAPlaceLink, or NMAPlaceLocation. To request for a reference, you need to add a source to the NMARequest (such as a discovery request) and then retrieve the reference from the results using the same source name. For example,


// Create a request to search for restaurants in Vancouver
NMAGeoCoordinates* vancouver =
[[NMAGeoCoordinates alloc] initWithLatitude:49.2849
        longitude:-123.1252];
NMADiscoveryRequest* request =
[[NMAPlaces sharedPlaces] createSearchRequestWithLocation:vancouver
        query:@"restaurant"];

// We also want to retrieve the Yelp ID external reference
[request addSource:@"yelp"];

request.collectionSize = 10;
NSError* error = [request startWithListener:self];

After the request execution is complete, you can retrieve the results by using referenceIdentifiersForSource: method as shown in the following:


...
if ( resultLink isKindOfClass:[NMAPlaceLink class] )
{
  NMAPlaceLink* placeLink = (NMAPlaceLink*) resultLink;

  NSArray* yelpIds =
    [placeLink referenceIdentifiersForSource:@"yelp"];

  for(NSString* id in yelpIds) {
    NSLog(id); // retrieved Yelp ID
  }
}
...

Additional external reference sources are supported through details requests as shown in the next example. For example, you can use detailsRequest property on NMAPlaceLink to retrieve reference IDs by executing the details request.

Note: You can retrieve the following external references through a details request. Sources marked with [*] cannot be used with createLookupRequestWithReferenceIdentifier:inSource: method.
  • NMAPlacesSourcePVID - Source for HERE Core POI
NMAPlaceRequest* placeDetailsRequest = receivedPlaceLink.detailsRequest;
[placeDetailsRequest addSource:NMAPlacesSourcePVID];
[placeDetailsRequest addSource:@"yelp"];
[placeDetailsRequest addSource:@"tripadvisor"];

[placeDetailsRequest startWithBlock:^(NMARequest *request, id data, NSError *error) {
  if (!error && data) {
    NMAPlace *place = (NMAPlace*)data;

    NSArray *pvids = [place referenceIdentifiersForSource:NMAPlacesSourcePVID];
    NSArray *yelpIds = [place referenceIdentifiersForSource:@"yelp”];
    NSArray *tripadvisorIds = [place referenceIdentifiersForSource:@"tripadvisor”];
  }
}];

You can also use an external PVID reference in the reverse scenario to retrieve a particular NMAPlace by using createLookupRequestWithReferenceIdentifier:inSource: method. For example:


NMAPlaceRequest* request = [ [NMAPlaces sharedPlaces]
  createLookupRequestWithReferenceIdentifier:@"1126226306"
  inSource:NMAPlacesSourcePVID ];

Hybrid Places Search

To support offline search, HERE SDK is preloaded with a database of Places which contains a smaller subset of Places as compared to the online search. When internet connectivity is not available and a place search is performed with the default connectivity mode, only basic Places information is returned for the entries in this database (without rich data such as ratings and reviews). However, once internet connectivity is reestablished, HERE SDK then retrieves online Places results again.