Skip to content

Search

This section explains how to implement online, offline, coordinate, flat or custom places geocoding and search (or combination of any of these) into your Android application.

Autocomplete, Geocoding and SearchPlaces

Applications developed with the Sygic Maps SDK can use geocoding to transform textual query into geographical information. With the SearchManager you can perform three basic actions : Geocoding, ReverseGeocoding and Autocomplete.

Note

If you use the online services, you might want to define to send a request once every 300ms or so, otherwise some requests might end up with an error TOO_MANY_REQUESTS.

Search types

The search engine offers few types of search engines which you can use separately or combined together

  • Coordinate Search - searching coordinates (search input may look like "48.1457, 17.1269")
  • Offline Maps Search - searching within offline installed maps
  • Online Maps Search - searching via online Search service API
  • Flat Search - searching among previously imported flat data
  • Custom Places Search - searching among previously imported custom places via CustomPlacesManager

Each type of search engine could be created via SearchManager and allows you to create search sessions

A Search session is used to actual search itself and has a limited lifetime which follows this lifecycle graph

1) create the search session
2) do [0..N] autocomplete requests 
3) then either of:
    - do nothing
    - geocode with location ID
    - geocode with query
    - do [0..N] search places requests with continuation token
4) close the session

That means, you are not able to use autocomplete operation again after performing at least one of the operations from step 3.

To create, for example, offline maps search engine, you may use this code:

val searchManager = SearchManagerProvider.getInstance().get()
var offlineMapSearch: Search? = null
searchManager.createOfflineMapSearch(object: CreateSearchCallback<OfflineMapSearch> {
    override fun onSuccess(search: OfflineMapSearch) {
        offlineMapSearch = search
        ...
        // create sessions and search
    }

    override fun onError(error: CreateSearchCallback.Error) {
        // search engine not available
    }
})

Please, bear in mind that the offline maps search engine consumes more resources, so try to reuse single instance for creating search sessions. This example shows how it is possible to combine the coordinate search and the offline map search together into one search engine:

val searchManager = SearchManagerProvider.getInstance().get()
var offlineMapSearch: Search? = null
var coordinateSearch: Search? = null
var customPlacesSearch: Search? = null
...
// obtain both search engines as in the previous example
...

var compositeSearch: Search? = null
searchManager.createCompositeSearch(
    SearchManager.CompositeSearchType.PARALLEL,
    listOf(offlineMapSearch, coordinateSearch, customPlacesSearch),
    object: CreateSearchCallback<CompositeSearch> {
        override fun onSuccess(search: CompositeSearch) {
            compositeSearch = search
            ...
            // create sessions and search within both search engines
        }

        override fun onError(error: CreateSearchCallback.Error) {
            // search engine not available
        }
})

After finding the desired result in autocomplete, you can take its locationID and call Geocode() with it to get more precise information about the location. If you do not need to use autocomplete and have the full string that you want to search for, just use Geocode straight away.

When searching for places, you can create a new placeRequest and use your position, the categories that you want to search for and several other arguments.

For example, offline autocomplete search session with geocode operation may look like this:

val offlineSearch: OfflineMapSearch
...
// obtain search instance via SearchManager
...
val position = GeoCoordinates(51.510175, -0.122445)
val searchRequest = SearchRequest("London Eye", position)
val session = offlineSearch.createSession()

session.autocomplete(searchRequest, object : AutocompleteResultListener {
    override fun onAutocomplete(autocompleteResult: List<AutocompleteResult>) {
        // handle results
    }

    override fun onAutocompleteError(status: ResultStatus) {}
})
session.geocode(searchRequest, object : GeocodingResultsListener {
    override fun onGeocodingResults(geocodingResults: List<GeocodingResult>) {
        // handle results
    }

    override fun onGeocodingResultsError(status: ResultStatus) {}
})

...

// close session when you are done with handling results
session.close()
````

Or online place search session:

=== "Kotlin"

````kotlin
val onlineSearch: OnlineMapSearch
...
// obtain search instance via SearchManager
...
val categories = listOf(PlaceCategories.TouristInformationOffice, PlaceCategories.Museum)
val placeRequest = PlaceRequest(position, categories, /*radius*/ 1000)
val session = onlineSearch.createSession()

session.searchPlaces(placeRequest, object : PlacesListener {
    override fun onPlacesLoaded(places: List<Place>, continuationToken: String?) {
        // handle results
    }

    override fun onPlacesError(status: ResultStatus) {}
})

...

// close session when you are done with handling results
session.close()

Reverse geocoding

Reverse geocoding is the opposite process to geocoding, meaning you query by geographical location and receive textual address of the location. You can define a filter if you do not want to take walkways into account or pass it an emptySet.

ReverseGeocoderProvider.getInstance(object : CoreInitCallback<ReverseGeocoder> {
    override fun onInstance(reverseGeocoder: ReverseGeocoder) {
        reverseGeocoder.reverseGeocode(position, emptySet(), object : ReverseGeocoder.ReverseGeocodingResultListener {
            override fun onReverseGeocodingResult(result: List<ReverseGeocodingResult>) {
                // do not forget to filter out the walkways if you do not want them
            }

            override fun onReverseGeocodingResultError(code: ReverseGeocoder.ErrorCode) {}
        })
    }

    override fun onError(error: CoreInitException) {}
})

During the implementation you will probably stumble upon a task of navigating to a search result. Let's see a simple example of how this would be achieved. Please bear in mind that in this example we do it in a simple callback succession and it's probably not what would be desired to exist in a complete application.

val offlineSearch: OfflineMapSearch
...
// obtain search instance via SearchManager
...
val searchRequest = SearchRequest("champs-elysees 121", GeoCoordinates(48.2879167, 17.2636084))
val searchSession = offlineSearch.createSession()
val router = RouterProvider.getInstance().get()

searchSession.autocomplete(searchRequest, object : AutocompleteResultListener {
  override fun onAutocomplete(autocompleteResult: List<AutocompleteResult>) {
    // here you can show the list of the results to the user
    // the results contain info such as distance to the result, its type, or which parts of the text were matched
    // let's suppose that the user chooses the first result (usually the most relevant one)
    // now let's take the locationID and use it for calling the geocode() function
    searchSession.geocode(GeocodeLocationRequest(autocompleteResult[0].locationId), object : GeocodingResultListener {
        override fun onGeocodingResult(geocodingResult: GeocodingResult) {
            // this is where you get detailed information about the result
            // you also get the GeoCoordinates of the object
            val routePlan = RoutePlan() // create a route plan
            routePlan.setStart(your-last-known-location) // set the start point
            routePlan.setDestination(geocodingResult.location) // set the destination point - using the GeocodingResult
            router.computeRoute(routePlan, object: Router.RouteComputeListener{}) // compute the route. more in the Routing section
        }

        override fun onGeocodingResultError(status: ResultStatus) {
          // handle the errors here
        }
    })
  }

  override fun onAutocompleteError(status: ResultStatus) {
    // handle the errors here
  }
})

Get the time at a location

One of the two prerequisites need to be met in order to get the time at a location, otherwise the operation will finish with error:

  • The coordinate must be inside of already downloaded maps
  • Online maps need to be active

You can use ReverseGeocoder to retrieve the local time given the position and UTC time.

 ReverseGeocoderProvider.getInstance().get().getLocalTimeAtLocation(
    GeoCoordinates(12.345,67.8910), // the location from which you want to get the local time
    System.currentTimeMillis() / 1000L, // in seconds!
    object: ReverseGeocoder.TimeAtLocationResultListener {
        override fun onError(errorCode: ReverseGeocoder.ErrorCode) {
        }

        override fun onSuccess(unixTimestamp: Long) {
          // result
        }
    }
)

If you are using the Online mode where the places are not downloaded to the device, you can still search for the custom places that are stored on the server. The places are automatically included in the results when calling the OnlineMapSearch's searchPlaces() or autocomplete() or geocode() function. You can read about them in the previous chapters. function.

To search for Custom Places that are stored locally, you can create the CustomPlacesSearch class which you can use directly for searching or add it to a composite/parallel search.

Warning

However, please note that the downloaded places need to be indexed after the installation and that this can take a considerable amount of time depending on the number of imported places but also the device's performance.

The Custom places search will be unavailable until the indexing is finished. To check whether the indexing is finished, you can add a SearchIndexingListener:

val searchIndexingListener = object: CustomPlacesSearchIndexingListener{
    override fun onError(
        dataset: String,
        errorCode: CustomPlacesSearchIndexingListener.ErrorCode,
        message: String
    ) {
        println("dataset $dataset | indexing error $errorCode, message : $message")
    }

    override fun onStarted(dataset: String) {
        println("indexing of dataset $dataset started")
    }

    override fun onSuccess(dataset: String) {
        println("indexing of dataset $dataset finished")
    }
}

CustomPlacesManagerProvider.getInstance().get().addSearchIndexingListener(searchIndexingListener)

Note

The callbacks are sent for batches of indexing jobs and not for each country / JSON separately. That means that if two or more imports/installs finish at the same time and the indexing of the first one takes longer, the callback will be sent only after the indexing of the last one finishes.