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
- Implement the
NMAResultListener
protocol to handle the completion of the search. - Create a request using the
NMAPlaces
factory. - Invoke the request by calling
NMARequest startWithListener:
. -
NMAResultListener request:didCompleteWithData:error:
callback is triggered when the request is finished.
- Search
- Request for Details
- Perform Actions
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. |
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
.
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
ofNMALink
, eitherNMAPlaceLink
orNMADiscoveryLink
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 anNMAPlace
. TheNMAPlaceLink
contains a brief summary about a place. Details about a place are available from theNMAPlace
that theNMAPlaceLink
references. -
NMADiscoveryLink
- Represents a discovery-related API link used to retrieve additionalNMADiscoveryPage
instances. This type ofNMALink
can be a result item in an Explore or Here type of search. TheNMADiscoveryLink
references refine discovery requests resulting in more specific results. For example, theNMADiscoveryLink
may link to a discovery request to search for 'Eat & Drink', 'Going Out', 'Accommodation', and so on.
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
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];
//...
}
}
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".
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.
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.
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.