Polygons for reverse geocoding
Combine Additional Data Provider queries with Reverse Geocoding queries to obtain extra data about specific entityType like:
- Country
- CountrySubdivision
- CountrySecondarySubdivision
- CountryTertiarySubdivision
- Municipality
- MunicipalitySubdivision
- Neighbourhood
- PostalCodeArea
Sample use case: You want to display Country or Municipality boundaries.
Use the following code to try this in your app:
1ReverseGeocoderSearchQuery reverseGeocoderQuery =2 createReverseGeocoderQuery(latLng.getLatitude(), latLng.getLongitude())34Observable<RevGeoWithAdpResponse> revGeoWithAdpResponseObservable =5 revGeoWithAdpRequester.rawReverseGeocoding(reverseGeocoderQuery)6 .subscribeOn(getWorkerScheduler())7 .filter(revGeoWithAdpResponse -> !revGeoWithAdpResponse.getRevGeoResponse().getAddresses().isEmpty())8 .doOnNext(this::updateUiWithResponse);
1val queryBuilder = ReverseGeocoderSearchQueryBuilder(latLng.latitude, latLng.longitude)2entityType.value?.let {3 queryBuilder.withEntityType(it)4}56val adpSearchRequester = AdpSearchRequester(getApplication(), getGeometriesZoomForEntityType())7adpSearchRequester.revGeoWithAdp(queryBuilder.build(), revGeoWithAdpResult)
1revGeoWithAdpRequester.toPolygonObservable(revGeoWithAdpResponseObservable)2 .observeOn(getObserverScheduler())3 .subscribe(polygon -> {4 tomtomMap.getOverlaySettings().addOverlay(polygon);5 getProgressDisplayable().hideInProgressDialog();6 }, error -> getProgressDisplayable().hideInProgressDialog())
1FuncUtils.apply<Geometry>(feature.geometry) { input ->2 input.accept(object : GeoJsonObjectVisitorAdapter() {3 override fun visit(polygon: Polygon?) {4 polygon?.let { item ->5 displayPolygon(item)6 tomtomMap.setCurrentBounds(parsePolygonLatLngs(item))7 }8 }910 override fun visit(multiPolygon: MultiPolygon?) {11 val coordinates = ArrayList<LatLng>()12 multiPolygon?.polygons?.forEach { polygon ->13 coordinates.addAll(parsePolygonLatLngs(polygon))14 displayPolygon(polygon)15 }16 tomtomMap.setCurrentBounds(coordinates)17 }18 })19}
The class RevGeoWithAdpRequester
serves as a helper to create complex queries:
1class RevGeoWithAdpRequester {23 private final static int DEFAULT_GEOMETRIES_ZOOM = 4;4 private final PolygonAdapter polygonAdapter = new PolygonAdapter();5 private final SearchApi searchApi;67 private int geometriesZoom = DEFAULT_GEOMETRIES_ZOOM;89 RevGeoWithAdpRequester(SearchApi searchApi) {10 this.searchApi = searchApi;11 }1213 Observable<Polygon> toPolygonObservable(Observable<RevGeoWithAdpResponse> revGeoWithAdpResponseObservable) {14 return revGeoWithAdpResponseObservable15 .flatMap((Function<RevGeoWithAdpResponse, ObservableSource<Polygon>>) revGeoWithAdpResponse -> {16 AdditionalDataSearchResult adpResult =17 FuncUtils.first(revGeoWithAdpResponse.getAdpResponse().getResults()).get();1819 return createSpecificPolygonObservable(parseGeometryResult(adpResult));20 });21 }2223 @NonNull24 @VisibleForTesting25 protected GeometryResult parseGeometryResult(AdditionalDataSearchResult adpResult) {26 final AtomicReference<GeometryResult> gr = new AtomicReference<>();27 adpResult.accept(result -> gr.set(result));28 return gr.get();29 }3031 Observable<RevGeoWithAdpResponse> rawReverseGeocoding(ReverseGeocoderSearchQuery reverseGeocoderQuery) {32 return searchApi.reverseGeocoding(reverseGeocoderQuery)33 .toObservable()34 .filter(response -> !response.getAddresses().isEmpty()35 && FuncUtils.first(response.getAddresses()).get()36 .getAdditionalDataSources().getGeometryDataSource().isPresent())37 .flatMap(response -> {38 final GeometryDataSource geometryDataSource =39 FuncUtils.first(response.getAddresses()).get()40 .getAdditionalDataSources().getGeometryDataSource().get();41 return searchApi.additionalDataSearch(createAdditionalDataSearchQuery(geometryDataSource))42 .toObservable().flatMap(adpResponse ->43 Observable.just(new RevGeoWithAdpResponse(response, adpResponse)));44 })45 .filter(response -> response.isValid())46 .filter(response -> response.getRevGeoResponse().hasResults())47 .filter(response -> !response.getAdpResponse().getResults().isEmpty());48 }4950 void setGeometriesZoom(int geometriesZoom) {51 this.geometriesZoom = geometriesZoom;52 }5354 private AdditionalDataSearchQuery createAdditionalDataSearchQuery(GeometryDataSource geometryDataSource) {55 return AdditionalDataSearchQueryBuilder.create()56 .withGeometryDataSource(geometryDataSource)57 .withGeometriesZoom(geometriesZoom)58 .build();59 }6061 private Observable<Geometry> createGeometryObservable(GeometryResult result) {62 return Observable.just(result)63 .filter(geometryResult -> geometryResult.getGeometryData().isPresent())64 .map(geometryResult -> geometryResult.getGeometryData().get())65 .filter(geoJsonObject -> geoJsonObject instanceof FeatureCollection)66 .map(geoJsonObject -> ((FeatureCollection) geoJsonObject).getFeatures())67 .filter(features -> !features.isEmpty())68 .map(features -> FuncUtils.first(features).get())69 .filter(feature -> feature.getGeometry().isPresent())70 .map(feature -> feature.getGeometry().get());71 }7273 private Observable<Polygon> createPolygonsObservable(Geometry geometry) {74 return Observable.just(geometry)75 .filter(geo -> geo instanceof MultiPolygon)76 .map(geo -> (MultiPolygon) geo)77 .flatMap((Function<MultiPolygon, ObservableSource<com.tomtom.online.sdk.common.geojson.geometry.Polygon>>)78 multiPolygon -> Observable.fromIterable(multiPolygon.getPolygons()))79 .map(polygon -> getPolygonAdapter().convertToMapPolygon(polygon));80 }8182 private Observable<Polygon> createSinglePolygonObservable(Geometry geometry) {83 return Observable.just(geometry)84 .filter(geo -> geo instanceof com.tomtom.online.sdk.common.geojson.geometry.Polygon)85 .map(geo -> (com.tomtom.online.sdk.common.geojson.geometry.Polygon) geo)86 .map(polygon -> getPolygonAdapter().convertToMapPolygon(polygon));87 }8889 @VisibleForTesting90 protected PolygonAdapter getPolygonAdapter() {91 return polygonAdapter;92 }9394 private Observable<Polygon> createSpecificPolygonObservable(GeometryResult result) {95 Geometry geometry = createGeometryObservable(result).blockingFirst();96 return (geometry instanceof MultiPolygon) ?97 createPolygonsObservable(geometry) :98 createSinglePolygonObservable(geometry);99 }100}
1class AdpSearchRequester(context: Context, private val geometriesZoom: Int) : RxContext {23 private val disposable = SerialDisposable()4 private val searchApi = OnlineSearchApi.create(context, BuildConfig.SEARCH_API_KEY)56 fun fuzzyWithAdp(searchQuery: FuzzySearchQuery, resource: ResourceLiveData<AdditionalDataSearchResult>) {7 resource.value = Resource.loading(null)8 disposable.set(searchApi.search(searchQuery)9 .subscribeOn(workingScheduler)10 .filter { nonEmptySearchResults(it) }11 .map { firstSearchResultWithGeometryDataSource(it) }12 .filter { presentSearchResult(it) }13 .map { firstDataSourceForSearchResult(it) }14 .flatMap { performAdp(it) }15 .observeOn(resultScheduler)16 .subscribe(17 { response -> resource.value = Resource.success(response.results.first()) },18 { error -> resource.value = Resource.error(null, Error(error.message)) }19 ))20 }2122 private fun firstDataSourceForSearchResult(searchResult: Optional<FuzzySearchResult>) =23 searchResult.get().additionalDataSources.geometryDataSource.get()2425 private fun presentSearchResult(searchResult: Optional<FuzzySearchResult>) =26 searchResult.isPresent2728 private fun nonEmptySearchResults(searchResponse: FuzzySearchResponse) =29 searchResponse.results.isEmpty().not()3031 private fun firstSearchResultWithGeometryDataSource(searchResponse: FuzzySearchResponse): Optional<FuzzySearchResult> {32 return Optional.fromNullable(searchResponse.results.find { item ->33 item.additionalDataSources?.geometryDataSource!!.isPresent34 })35 }3637 fun revGeoWithAdp(revGeoQuery: ReverseGeocoderSearchQuery, resource: ResourceLiveData<RevGeoWithAdpResponse>) {38 val result = RevGeoWithAdpResponse()39 resource.value = Resource.loading(null)4041 disposable.set(searchApi.reverseGeocoding(revGeoQuery)42 .subscribeOn(workingScheduler)43 .filter { nonEmptyRevGeoResults(it) }44 .map { firstRevGeoResultWithGeometryDataSource(it) }45 .map {46 result.revGeoResult = it.orNull()47 firstDataSourceForRevGeoResult(it)48 }49 .filter { nonEmptyGeometryDataSource(it) }50 .map { it.get() }51 .flatMap { performAdp(it) }52 .observeOn(resultScheduler)53 .subscribe(54 { response ->55 result.adpResult = response.results56 resource.value = Resource.success(result)57 },58 { error -> resource.value = Resource.error(null, Error(error.message)) }59 )60 )61 }6263 private fun nonEmptyGeometryDataSource(dataSource: Optional<GeometryDataSource>) =64 dataSource.isPresent6566 private fun nonEmptyRevGeoResults(response: ReverseGeocoderSearchResponse) =67 response.hasResults()6869 private fun firstRevGeoResultWithGeometryDataSource(revGeoResponse: ReverseGeocoderSearchResponse): Optional<ReverseGeocoderFullAddress> =70 Optional.fromNullable(revGeoResponse.addresses.find { item ->71 item.additionalDataSources?.geometryDataSource!!.isPresent72 })7374 private fun firstDataSourceForRevGeoResult(it: Optional<ReverseGeocoderFullAddress>): Optional<GeometryDataSource> {75 it.orNull()?.let {76 return it.additionalDataSources!!.geometryDataSource!!77 } ?: run {78 return Optional.absent<GeometryDataSource>()79 }80 }8182 private fun performAdp(dataSource: GeometryDataSource): Maybe<AdditionalDataSearchResponse> {83 val adpQuery = AdditionalDataSearchQueryBuilder.create()84 .withGeometryDataSource(dataSource)85 .withGeometriesZoom(geometriesZoom)86 val request = searchApi.additionalDataSearch(adpQuery.build())87 return request.toMaybe()88 }8990 override fun getWorkingScheduler() = Schedulers.newThread()9192 override fun getResultScheduler() = AndroidSchedulers.mainThread()!!9394}
Sample views utilizing entities retrieved by combining both services:
Boundaries for a selected country | Boundaries for a selected municipality |