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) {}
})
Navigate to a search result¶
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
}
}
)
Custom Places Search¶
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.