From 51fefc609216aefa818f106fe301fed02b78d348 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Tue, 18 Oct 2016 12:17:18 +0900 Subject: [PATCH 01/17] Add explicit fallback methods to the main object --- js/geocoder.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/js/geocoder.js b/js/geocoder.js index da2c3b7..8b0dc70 100644 --- a/js/geocoder.js +++ b/js/geocoder.js @@ -10,6 +10,18 @@ export default { this.apiKey = key; }, + geocodePositionFallback(position) { + if (!this.apiKey) { throw new Error("Google API key required"); } + + return GoogleApi.geocodePosition(this.apiKey, position); + }, + + geocodeAddressFallback(address) { + if (!this.apiKey) { throw new Error("Google API key required"); } + + return GoogleApi.geocodeAddress(this.apiKey, address); + }, + geocodePosition(position) { if (!position || !position.lat || !position.lng) { return Promise.reject(new Error("invalid position: {lat, lng} required")); @@ -17,7 +29,7 @@ export default { return RNGeocoder.geocodePosition(position).catch(err => { if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } - return GoogleApi.geocodePosition(this.apiKey, position); + return this.geocodePositionFallback(position); }); }, @@ -28,7 +40,7 @@ export default { return RNGeocoder.geocodeAddress(address).catch(err => { if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } - return GoogleApi.geocodeAddress(this.apiKey, address); + return this.geocodeAddressFallback(address); }); }, } From 4bda02f5f1430890521030cfe16a777b6641454e Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 15:06:51 +0900 Subject: [PATCH 02/17] Add `radius` property for iOS making it `null` on Android --- .../src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java | 2 ++ ios/RNGeocoder/RNGeocoder.m | 1 + 2 files changed, 3 insertions(+) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index 642ebfb..5f59abe 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -73,6 +73,8 @@ WritableArray transform(List
addresses) { position.putDouble("lat", address.getLatitude()); position.putDouble("lng", address.getLongitude()); result.putMap("position", position); + // There is no radius field in the `Address` object. + result.putString("radius", null); final String feature_name = address.getFeatureName(); if (feature_name != null && !feature_name.equals(address.getSubThoroughfare()) && diff --git a/ios/RNGeocoder/RNGeocoder.m b/ios/RNGeocoder/RNGeocoder.m index 5ad122b..f827395 100644 --- a/ios/RNGeocoder/RNGeocoder.m +++ b/ios/RNGeocoder/RNGeocoder.m @@ -100,6 +100,7 @@ - (NSArray *)placemarksToDictionary:(NSArray *)placemarks { @"lat": [NSNumber numberWithDouble:placemark.location.coordinate.latitude], @"lng": [NSNumber numberWithDouble:placemark.location.coordinate.longitude], }, + @"radius": [NSNumber numberWithDouble:[(CLCircularRegion *)placemark.region radius]] ?: [NSNull null], @"country": placemark.country ?: [NSNull null], @"countryCode": placemark.ISOcountryCode ?: [NSNull null], @"locality": placemark.locality ?: [NSNull null], From 4d27690f175b5cfb3be2d3bd7fb70a445c44c820 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 15:07:54 +0900 Subject: [PATCH 03/17] Change `radius` to `region` --- .../java/com/devfd/RNGeocoder/RNGeocoderModule.java | 5 +++-- ios/RNGeocoder/RNGeocoder.m | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index 5f59abe..1179713 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -73,8 +73,9 @@ WritableArray transform(List
addresses) { position.putDouble("lat", address.getLatitude()); position.putDouble("lng", address.getLongitude()); result.putMap("position", position); - // There is no radius field in the `Address` object. - result.putString("radius", null); + + // There is no region field in the `Address` object. + result.putString("region", null); final String feature_name = address.getFeatureName(); if (feature_name != null && !feature_name.equals(address.getSubThoroughfare()) && diff --git a/ios/RNGeocoder/RNGeocoder.m b/ios/RNGeocoder/RNGeocoder.m index f827395..c3fc072 100644 --- a/ios/RNGeocoder/RNGeocoder.m +++ b/ios/RNGeocoder/RNGeocoder.m @@ -81,6 +81,7 @@ - (NSArray *)placemarksToDictionary:(NSArray *)placemarks { for (int i = 0; i < placemarks.count; i++) { CLPlacemark* placemark = [placemarks objectAtIndex:i]; + CLCircularRegion* region = placemark.region; NSString* name = [NSNull null]; @@ -100,7 +101,13 @@ - (NSArray *)placemarksToDictionary:(NSArray *)placemarks { @"lat": [NSNumber numberWithDouble:placemark.location.coordinate.latitude], @"lng": [NSNumber numberWithDouble:placemark.location.coordinate.longitude], }, - @"radius": [NSNumber numberWithDouble:[(CLCircularRegion *)placemark.region radius]] ?: [NSNull null], + @"region": placemark.region ? @{ + @"center": @{ + @"lat": [NSNumber numberWithDouble:region.center.latitude], + @"lng": [NSNumber numberWithDouble:region.center.longitude], + }, + @"radius": [NSNumber numberWithDouble:region.radius], + } : [NSNull null], @"country": placemark.country ?: [NSNull null], @"countryCode": placemark.ISOcountryCode ?: [NSNull null], @"locality": placemark.locality ?: [NSNull null], @@ -110,7 +117,7 @@ - (NSArray *)placemarksToDictionary:(NSArray *)placemarks { @"postalCode": placemark.postalCode ?: [NSNull null], @"adminArea": placemark.administrativeArea ?: [NSNull null], @"subAdminArea": placemark.subAdministrativeArea ?: [NSNull null], - @"formattedAddress": [lines componentsJoinedByString:@", "] + @"formattedAddress": [lines componentsJoinedByString:@", "], }; [results addObject:result]; From 37e43e717c9466559ba0523624d660c5a7477b7f Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 16:50:01 +0900 Subject: [PATCH 04/17] Get `region` via Google API --- js/googleApi.js | 24 ++++++++++++++++++++++++ test/unit/googleApi.test.js | 6 ++++++ 2 files changed, 30 insertions(+) diff --git a/js/googleApi.js b/js/googleApi.js index 4358de7..c867eb0 100644 --- a/js/googleApi.js +++ b/js/googleApi.js @@ -1,8 +1,11 @@ +import { getCenterOfBounds, getDistance } from 'geolib'; + const googleUrl = 'https://maps.google.com/maps/api/geocode/json'; function format(raw) { const address = { position: {}, + region: null, formattedAddress: raw.formatted_address || '', feature: null, streetNumber: null, @@ -21,6 +24,27 @@ function format(raw) { lat: raw.geometry.location.lat, lng: raw.geometry.location.lng, } + + if (raw.geometry.viewport) { + const northEast = { + latitude: raw.geometry.viewport.northeast.lat, + longitude: raw.geometry.viewport.northeast.lng, + }; + const southWest = { + latitude: raw.geometry.viewport.southwest.lat, + longitude: raw.geometry.viewport.southwest.lng, + }; + const center = getCenterOfBounds([northEast, southWest]); + const radius = getDistance(center, northEast); + + address.region = { + center: { + lat: center.latitude, + lng: center.longitude, + }, + radius, + } + } } raw.address_components.forEach(component => { diff --git a/test/unit/googleApi.test.js b/test/unit/googleApi.test.js index f02e8b9..bae0cf6 100644 --- a/test/unit/googleApi.test.js +++ b/test/unit/googleApi.test.js @@ -51,6 +51,9 @@ describe('googleApi', function() { it ('for waterloo-bridge', async function() { mockFetch(require('./fixtures/waterloo-bridge.js')); let [first, ...ret] = await GoogleApi.geocodeRequest(); + expect(first.region.center.lat).to.eql(51.506349); + expect(first.region.center.lng).to.eql(-0.114699); + expect(first.region.radius).to.eql(177); expect(first.countryCode).to.eql('GB'); expect(first.feature).to.be.eql(null); expect(first.locality).to.eql('London'); @@ -61,6 +64,9 @@ describe('googleApi', function() { it ('for yosemite park', async function() { mockFetch(require('./fixtures/yosemite-park.js')); let [first, ...ret] = await GoogleApi.geocodeRequest(); + expect(first.region.center.lat).to.eql(37.865101); + expect(first.region.center.lng).to.eql(-119.538329); + expect(first.region.radius).to.eql(191); expect(first.countryCode).to.eql('US'); expect(first.feature).to.be.eql('Yosemite National Park'); expect(first.streetName).to.be.eql(null); From 1188eda4d60b52ffcfbbdf30e94ea7a2d460c321 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 16:52:26 +0900 Subject: [PATCH 05/17] Remove duplicate conditions --- js/geocoder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/geocoder.js b/js/geocoder.js index 8b0dc70..dc49bfe 100644 --- a/js/geocoder.js +++ b/js/geocoder.js @@ -28,7 +28,7 @@ export default { } return RNGeocoder.geocodePosition(position).catch(err => { - if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } + if (err.code !== 'NOT_AVAILABLE') { throw err; } return this.geocodePositionFallback(position); }); }, @@ -39,7 +39,7 @@ export default { } return RNGeocoder.geocodeAddress(address).catch(err => { - if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } + if (err.code !== 'NOT_AVAILABLE') { throw err; } return this.geocodeAddressFallback(address); }); }, From 5b4ab8d573caf238c00e1a96d8fcd747f7e7c779 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 17:03:13 +0900 Subject: [PATCH 06/17] Update README --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8421dfb..97311d1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# react-native-geocoder +# react-native-geocoder [![CircleCI](https://circleci.com/gh/devfd/react-native-geocoder/tree/master.svg?style=shield)](https://circleci.com/gh/devfd/react-native-geocoder/tree/master) @@ -114,13 +114,17 @@ both iOS and Android will return the following object: ```js { position: {lat, lng}, + region: { + center: {lat, lng}, + radius: Number, + } | null, formattedAddress: String, // the full address feature: String | null, // ex Yosemite Park, Eiffel Tower streetNumber: String | null, streetName: String | null, postalCode: String | null, locality: String | null, // city name - country: String, + country: String, countryCode: String adminArea: String | null subAdminArea: String | null, @@ -135,5 +139,4 @@ iOS does not allow sending multiple geocoding requests simultaneously, unless yo ### Android geocoding may not work on older android devices (4.1) and will not work if Google play services are not available. - - +`region` will always be `null` on Android since it doesn't support the feature. In this case, `Geocoder.geocodePositionFallback` and `Geocoder.geocodeAddressFallback` may be used to get the `region` value. From fca2043fc610649ebd5747c4be18a1a74de10753 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Wed, 19 Oct 2016 17:04:36 +0900 Subject: [PATCH 07/17] Add `geolib` as a dependency --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 913f14a..3f8bed0 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,8 @@ "sinon": "^1.17.5", "sinon-chai": "^2.8.0", "wd": "^0.4.0" + }, + "dependencies": { + "geolib": "^2.0.21" } } From 82d057bd2c76bbc232c53197401c271ee8ec8e04 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Thu, 20 Oct 2016 03:47:15 +0900 Subject: [PATCH 08/17] Calculate `radius` accurately --- js/googleApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/googleApi.js b/js/googleApi.js index c867eb0..8ff59f9 100644 --- a/js/googleApi.js +++ b/js/googleApi.js @@ -35,7 +35,7 @@ function format(raw) { longitude: raw.geometry.viewport.southwest.lng, }; const center = getCenterOfBounds([northEast, southWest]); - const radius = getDistance(center, northEast); + const radius = Math.max(getDistance(center, northEast), getDistance(center, southWest)); address.region = { center: { From 60a7299668339ac7d3829e11da872ffb3072ad47 Mon Sep 17 00:00:00 2001 From: Felipe Martim Date: Sat, 29 Oct 2016 13:04:27 -0200 Subject: [PATCH 09/17] Fallback to google API if empty list --- .../main/java/com/devfd/RNGeocoder/RNGeocoderModule.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index 642ebfb..d91f6f7 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -39,7 +39,12 @@ public void geocodeAddress(String addressName, Promise promise) { try { List
addresses = geocoder.getFromLocationName(addressName, 20); - promise.resolve(transform(addresses)); + if(addresses != null && addresses.size() > 0 { + promise.resolve(transform(addresses)); + } else { + promise.reject("NO_RESULTS", "Geocoder returned an empty list"); + } + } catch (IOException e) { From fd7ef1409b708c4c2545bd9bdeac7a8fdd29ddf9 Mon Sep 17 00:00:00 2001 From: Felipe Martim Date: Sat, 29 Oct 2016 13:11:37 -0200 Subject: [PATCH 10/17] fix parenthesis --- .../src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index d91f6f7..35ad2f9 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -39,7 +39,7 @@ public void geocodeAddress(String addressName, Promise promise) { try { List
addresses = geocoder.getFromLocationName(addressName, 20); - if(addresses != null && addresses.size() > 0 { + if(addresses != null && addresses.size() > 0) { promise.resolve(transform(addresses)); } else { promise.reject("NO_RESULTS", "Geocoder returned an empty list"); From 303f6f75202cfb18023ab0a9175af9a74d82d29e Mon Sep 17 00:00:00 2001 From: Felipe Martim Date: Sat, 29 Oct 2016 14:01:31 -0200 Subject: [PATCH 11/17] returning NOT_AVAILABLE, since it's a known issue --- .../src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index 35ad2f9..5549a05 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -42,7 +42,7 @@ public void geocodeAddress(String addressName, Promise promise) { if(addresses != null && addresses.size() > 0) { promise.resolve(transform(addresses)); } else { - promise.reject("NO_RESULTS", "Geocoder returned an empty list"); + promise.reject("NOT_AVAILABLE", "Geocoder returned an empty list"); } } From b24955b4034da9f44d90593a11cf5d3fb79a1f52 Mon Sep 17 00:00:00 2001 From: Felipe Martim Date: Sat, 29 Oct 2016 14:20:20 -0200 Subject: [PATCH 12/17] specific fallback methods --- js/geocoder.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/js/geocoder.js b/js/geocoder.js index da2c3b7..dc49bfe 100644 --- a/js/geocoder.js +++ b/js/geocoder.js @@ -10,14 +10,26 @@ export default { this.apiKey = key; }, + geocodePositionFallback(position) { + if (!this.apiKey) { throw new Error("Google API key required"); } + + return GoogleApi.geocodePosition(this.apiKey, position); + }, + + geocodeAddressFallback(address) { + if (!this.apiKey) { throw new Error("Google API key required"); } + + return GoogleApi.geocodeAddress(this.apiKey, address); + }, + geocodePosition(position) { if (!position || !position.lat || !position.lng) { return Promise.reject(new Error("invalid position: {lat, lng} required")); } return RNGeocoder.geocodePosition(position).catch(err => { - if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } - return GoogleApi.geocodePosition(this.apiKey, position); + if (err.code !== 'NOT_AVAILABLE') { throw err; } + return this.geocodePositionFallback(position); }); }, @@ -27,8 +39,8 @@ export default { } return RNGeocoder.geocodeAddress(address).catch(err => { - if (!this.apiKey || err.code !== 'NOT_AVAILABLE') { throw err; } - return GoogleApi.geocodeAddress(this.apiKey, address); + if (err.code !== 'NOT_AVAILABLE') { throw err; } + return this.geocodeAddressFallback(address); }); }, } From 13ef25e4553685fd058dd43d7e362096191217bf Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Tue, 8 Nov 2016 13:29:16 +0900 Subject: [PATCH 13/17] Add support for language-specific calls --- .../devfd/RNGeocoder/RNGeocoderModule.java | 24 +++++++++++++- ios/RNGeocoder/RNGeocoder.m | 11 +++++++ js/geocoder.js | 21 ++++++++++-- js/googleApi.js | 18 +++++++--- test/unit/geocoder.test.js | 33 +++++++++++++++++++ test/unit/googleApi.test.js | 20 +++++++++-- 6 files changed, 116 insertions(+), 11 deletions(-) diff --git a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java index ebd68b2..37741b7 100644 --- a/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java +++ b/android/src/main/java/com/devfd/RNGeocoder/RNGeocoderModule.java @@ -1,8 +1,10 @@ package com.devfd.RNGeocoder; +import android.content.Context; import android.location.Address; import android.location.Geocoder; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; @@ -15,14 +17,20 @@ import java.io.IOException; import java.util.List; +import java.util.Locale; public class RNGeocoderModule extends ReactContextBaseJavaModule { + private Context context; + private Locale locale; private Geocoder geocoder; public RNGeocoderModule(ReactApplicationContext reactContext) { super(reactContext); - geocoder = new Geocoder(reactContext.getApplicationContext()); + context = reactContext.getApplicationContext(); + + locale = context.getResources().getConfiguration().locale; + geocoder = new Geocoder(context, locale); } @Override @@ -30,6 +38,20 @@ public String getName() { return "RNGeocoder"; } + @ReactMethod + public void setLanguage(String language, Callback callback) { + Locale target = new Locale(language); + if (!context.getResources().getConfiguration().locale.equals(target) && !locale.equals(target)) { + locale = target; + geocoder = new Geocoder(context, locale); + + callback.invoke(language); + return; + } + + callback.invoke((String) null); + } + @ReactMethod public void geocodeAddress(String addressName, Promise promise) { if (!geocoder.isPresent()) { diff --git a/ios/RNGeocoder/RNGeocoder.m b/ios/RNGeocoder/RNGeocoder.m index c3fc072..5a47d16 100644 --- a/ios/RNGeocoder/RNGeocoder.m +++ b/ios/RNGeocoder/RNGeocoder.m @@ -22,6 +22,17 @@ @implementation RNGeocoder RCT_EXPORT_MODULE(); +RCT_EXPORT_METHOD(setLanguage:(NSString *)language + callback:(RCTResponseSenderBlock)callback) +{ + NSString *deviceLanguage = [[NSLocale preferredLanguages] objectAtIndex:0]; + if ([deviceLanguage isEqualToString:language]) { + return callback(@[[NSNull null]]); + } + + callback(@[language]); +} + RCT_EXPORT_METHOD(geocodePosition:(CLLocation *)location resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) diff --git a/js/geocoder.js b/js/geocoder.js index dc49bfe..388426c 100644 --- a/js/geocoder.js +++ b/js/geocoder.js @@ -1,25 +1,32 @@ -import { NativeModules } from 'react-native'; +import { NativeModules, Platform } from 'react-native'; import GoogleApi from './googleApi.js'; const { RNGeocoder } = NativeModules; export default { apiKey: null, + language: null, fallbackToGoogle(key) { this.apiKey = key; }, + setLanguage(language) { + RNGeocoder.setLanguage(language, (result) => { + this.language = result; + }); + }, + geocodePositionFallback(position) { if (!this.apiKey) { throw new Error("Google API key required"); } - return GoogleApi.geocodePosition(this.apiKey, position); + return GoogleApi.geocodePosition(this.apiKey, position, this.language); }, geocodeAddressFallback(address) { if (!this.apiKey) { throw new Error("Google API key required"); } - return GoogleApi.geocodeAddress(this.apiKey, address); + return GoogleApi.geocodeAddress(this.apiKey, address, this.language); }, geocodePosition(position) { @@ -27,6 +34,10 @@ export default { return Promise.reject(new Error("invalid position: {lat, lng} required")); } + if (this.language && (Platform.OS === 'ios')) { + return this.geocodePositionFallback(position); + } + return RNGeocoder.geocodePosition(position).catch(err => { if (err.code !== 'NOT_AVAILABLE') { throw err; } return this.geocodePositionFallback(position); @@ -38,6 +49,10 @@ export default { return Promise.reject(new Error("address is null")); } + if (this.language && (Platform.OS === 'ios')) { + return this.geocodeAddressFallback(address); + } + return RNGeocoder.geocodeAddress(address).catch(err => { if (err.code !== 'NOT_AVAILABLE') { throw err; } return this.geocodeAddressFallback(address); diff --git a/js/googleApi.js b/js/googleApi.js index 8ff59f9..d5b3072 100644 --- a/js/googleApi.js +++ b/js/googleApi.js @@ -82,20 +82,30 @@ function format(raw) { } export default { - geocodePosition(apiKey, position) { + geocodePosition(apiKey, position, language = null) { if (!apiKey || !position || !position.lat || !position.lng) { return Promise.reject(new Error("invalid apiKey / position")); } - return this.geocodeRequest(`${googleUrl}?key=${apiKey}&latlng=${position.lat},${position.lng}`); + let url = `${googleUrl}?key=${apiKey}&latlng=${position.lat},${position.lng}`; + if (language) { + url = `${url}&language=${language}`; + } + + return this.geocodeRequest(url); }, - geocodeAddress(apiKey, address) { + geocodeAddress(apiKey, address, language = null) { if (!apiKey || !address) { return Promise.reject(new Error("invalid apiKey / address")); } - return this.geocodeRequest(`${googleUrl}?key=${apiKey}&address=${encodeURI(address)}`); + let url = `${googleUrl}?key=${apiKey}&address=${encodeURI(address)}`; + if (language) { + url = `${url}&language=${language}`; + } + + return this.geocodeRequest(url); }, async geocodeRequest(url) { diff --git a/test/unit/geocoder.test.js b/test/unit/geocoder.test.js index 7759e53..497b2fb 100644 --- a/test/unit/geocoder.test.js +++ b/test/unit/geocoder.test.js @@ -11,6 +11,7 @@ describe('geocoder', function() { }; RNGeocoder = { + setLanguage: sinon.stub().callsArgWith(1, null), geocodePosition: sinon.stub().returns(Promise.resolve()), geocodeAddress: sinon.stub().returns(Promise.resolve()), }; @@ -21,6 +22,7 @@ describe('geocoder', function() { './googleApi.js': GoogleApi, 'react-native': { NativeModules: { RNGeocoder }, + Platform: { OS: 'android' }, } }).default; }); @@ -52,6 +54,16 @@ describe('geocoder', function() { expect(RNGeocoder.geocodePosition).to.have.been.calledWith(position); }); + it ('returns geocoding results with a specific language', async function() { + const position = {lat: 1.234, lng: 4.567}; + const language = 'ko'; + RNGeocoder.setLanguage = sinon.stub().callsArgWith(1, language); + Geocoder.setLanguage(language); + expect(RNGeocoder.setLanguage).to.have.been.calledWith(language); + await Geocoder.geocodePosition(position); + expect(RNGeocoder.geocodePosition).to.have.been.calledWith(position); + }); + it ('does not call google api if no apiKey', function() { const position = {lat: 1.234, lng: 4.567}; RNGeocoder.geocodePosition = sinon.stub().returns(Promise.reject()); @@ -70,6 +82,27 @@ describe('geocoder', function() { expect(ret).to.eql('google'); }); + it ('fallbacks to google api when with a specific language on iOS', async function() { + Geocoder = proxyquire + .noCallThru() + .load('../../js/geocoder.js', { + './googleApi.js': GoogleApi, + 'react-native': { + NativeModules: { RNGeocoder }, + Platform: { OS: 'ios' }, + } + }).default; + const position = {lat: 1.234, lng: 4.567}; + const language = 'ko'; + RNGeocoder.setLanguage = sinon.stub().callsArgWith(1, language); + Geocoder.setLanguage(language); + expect(RNGeocoder.setLanguage).to.have.been.calledWith(language); + Geocoder.fallbackToGoogle('myGoogleMapsAPIKey'); + const ret = await Geocoder.geocodePosition(position); + expect(GoogleApi.geocodePosition).to.have.been.calledWith('myGoogleMapsAPIKey', position); + expect(ret).to.eql('google'); + }); + it ('does not fallback to google api on error', function() { const position = {lat: 1.234, lng: 4.567}; RNGeocoder.geocodePosition = sinon.stub().returns(Promise.reject(new Error('something wrong'))); diff --git a/test/unit/googleApi.test.js b/test/unit/googleApi.test.js index bae0cf6..ee822ea 100644 --- a/test/unit/googleApi.test.js +++ b/test/unit/googleApi.test.js @@ -22,12 +22,26 @@ describe('googleApi', function() { expect(ret).to.eql('yo'); }); + it ('position with a specific language', async function() { + let ret = await GoogleApi.geocodePosition('myKey', {lat: 1.234, lng: 1.14}, 'ko'); + expect(geocodeRequest).to.have.been.calledWith( + 'https://maps.google.com/maps/api/geocode/json?key=myKey&latlng=1.234,1.14&language=ko'); + expect(ret).to.eql('yo'); + }); + it ('address', async function() { - let ret = await GoogleApi.geocodeAddress('myKey', "london"); + let ret = await GoogleApi.geocodeAddress('myKey', 'london'); expect(geocodeRequest).to.have.been.calledWith( 'https://maps.google.com/maps/api/geocode/json?key=myKey&address=london'); expect(ret).to.eql('yo'); }); + + it ('address with a specific language', async function() { + let ret = await GoogleApi.geocodeAddress('myKey', 'london', 'ko'); + expect(geocodeRequest).to.have.been.calledWith( + 'https://maps.google.com/maps/api/geocode/json?key=myKey&address=london&language=ko'); + expect(ret).to.eql('yo'); + }); }); describe('geocodeRequest', function() { @@ -39,10 +53,10 @@ describe('googleApi', function() { } it ('throws if invalid results', function() { - mockFetch({ status: "NOT_OK" }); + mockFetch({ status: 'NOT_OK' }); return GoogleApi.geocodeRequest().then( () => { throw new Error('cannot be there') }, - (err) => { expect(err.message).to.contain("NOT_OK"); } + (err) => { expect(err.message).to.contain('NOT_OK'); } ); }); From 1de38cb1785b53d43a7dbbcb944ddfc2bfdfd27d Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Tue, 8 Nov 2016 19:27:19 +0900 Subject: [PATCH 14/17] Refactor the code to ease overriding --- js/googleApi.js | 158 ++++++++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/js/googleApi.js b/js/googleApi.js index d5b3072..1ac1ad5 100644 --- a/js/googleApi.js +++ b/js/googleApi.js @@ -1,93 +1,93 @@ import { getCenterOfBounds, getDistance } from 'geolib'; -const googleUrl = 'https://maps.google.com/maps/api/geocode/json'; - -function format(raw) { - const address = { - position: {}, - region: null, - formattedAddress: raw.formatted_address || '', - feature: null, - streetNumber: null, - streetName: null, - postalCode: null, - locality: null, - country: null, - countryCode: null, - adminArea: null, - subAdminArea: null, - subLocality: null, - }; - - if (raw.geometry && raw.geometry.location) { - address.position = { - lat: raw.geometry.location.lat, - lng: raw.geometry.location.lng, - } +export default { + googleUrl: 'https://maps.google.com/maps/api/geocode/json', + + format(raw) { + const address = { + position: {}, + region: null, + formattedAddress: raw.formatted_address || '', + feature: null, + streetNumber: null, + streetName: null, + postalCode: null, + locality: null, + country: null, + countryCode: null, + adminArea: null, + subAdminArea: null, + subLocality: null, + }; + + if (raw.geometry && raw.geometry.location) { + address.position = { + lat: raw.geometry.location.lat, + lng: raw.geometry.location.lng, + } - if (raw.geometry.viewport) { - const northEast = { - latitude: raw.geometry.viewport.northeast.lat, - longitude: raw.geometry.viewport.northeast.lng, - }; - const southWest = { - latitude: raw.geometry.viewport.southwest.lat, - longitude: raw.geometry.viewport.southwest.lng, - }; - const center = getCenterOfBounds([northEast, southWest]); - const radius = Math.max(getDistance(center, northEast), getDistance(center, southWest)); - - address.region = { - center: { - lat: center.latitude, - lng: center.longitude, - }, - radius, + if (raw.geometry.viewport) { + const northEast = { + latitude: raw.geometry.viewport.northeast.lat, + longitude: raw.geometry.viewport.northeast.lng, + }; + const southWest = { + latitude: raw.geometry.viewport.southwest.lat, + longitude: raw.geometry.viewport.southwest.lng, + }; + const center = getCenterOfBounds([northEast, southWest]); + const radius = Math.max(getDistance(center, northEast), getDistance(center, southWest)); + + address.region = { + center: { + lat: center.latitude, + lng: center.longitude, + }, + radius, + } } } - } - raw.address_components.forEach(component => { - if (component.types.indexOf('route') !== -1) { - address.streetName = component.long_name; - } - else if (component.types.indexOf('street_number') !== -1) { - address.streetNumber = component.long_name; - } - else if (component.types.indexOf('country') !== -1) { - address.country = component.long_name; - address.countryCode = component.short_name; - } - else if (component.types.indexOf('locality') !== -1) { - address.locality = component.long_name; - } - else if (component.types.indexOf('postal_code') !== -1) { - address.postalCode = component.long_name; - } - else if (component.types.indexOf('administrative_area_level_1') !== -1) { - address.adminArea = component.long_name; - } - else if (component.types.indexOf('administrative_area_level_2') !== -1) { - address.subAdminArea = component.long_name; - } - else if (component.types.indexOf('sublocality') !== -1 || component.types.indexOf('sublocality_level_1') !== -1) { - address.subLocality = component.long_name; - } - else if (component.types.indexOf('point_of_interest') !== -1 || component.types.indexOf('colloquial_area') !== -1) { - address.feature = component.long_name; - } - }); + raw.address_components.forEach(component => { + if (component.types.indexOf('route') !== -1) { + address.streetName = component.long_name; + } + else if (component.types.indexOf('street_number') !== -1) { + address.streetNumber = component.long_name; + } + else if (component.types.indexOf('country') !== -1) { + address.country = component.long_name; + address.countryCode = component.short_name; + } + else if (component.types.indexOf('locality') !== -1) { + address.locality = component.long_name; + } + else if (component.types.indexOf('postal_code') !== -1) { + address.postalCode = component.long_name; + } + else if (component.types.indexOf('administrative_area_level_1') !== -1) { + address.adminArea = component.long_name; + } + else if (component.types.indexOf('administrative_area_level_2') !== -1) { + address.subAdminArea = component.long_name; + } + else if (component.types.indexOf('sublocality') !== -1 || component.types.indexOf('sublocality_level_1') !== -1) { + address.subLocality = component.long_name; + } + else if (component.types.indexOf('point_of_interest') !== -1 || component.types.indexOf('colloquial_area') !== -1) { + address.feature = component.long_name; + } + }); - return address; -} + return address; + }, -export default { geocodePosition(apiKey, position, language = null) { if (!apiKey || !position || !position.lat || !position.lng) { return Promise.reject(new Error("invalid apiKey / position")); } - let url = `${googleUrl}?key=${apiKey}&latlng=${position.lat},${position.lng}`; + let url = `${this.googleUrl}?key=${apiKey}&latlng=${position.lat},${position.lng}`; if (language) { url = `${url}&language=${language}`; } @@ -100,7 +100,7 @@ export default { return Promise.reject(new Error("invalid apiKey / address")); } - let url = `${googleUrl}?key=${apiKey}&address=${encodeURI(address)}`; + let url = `${this.googleUrl}?key=${apiKey}&address=${encodeURI(address)}`; if (language) { url = `${url}&language=${language}`; } @@ -116,6 +116,6 @@ export default { return Promise.reject(new Error(`geocoding error ${json.status}, ${json.error_message}`)); } - return json.results.map(format); + return json.results.map(this.format); } } From 3d4c475636d4410685449898a53a829869dbddc8 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Fri, 18 Nov 2016 15:48:07 +0900 Subject: [PATCH 15/17] Change the Google API endpoint to `googleapis.com` --- js/googleApi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/googleApi.js b/js/googleApi.js index 1ac1ad5..80b1563 100644 --- a/js/googleApi.js +++ b/js/googleApi.js @@ -1,7 +1,7 @@ import { getCenterOfBounds, getDistance } from 'geolib'; export default { - googleUrl: 'https://maps.google.com/maps/api/geocode/json', + googleUrl: 'https://maps.googleapis.com/maps/api/geocode/json', format(raw) { const address = { From 6f15a617970f72ac74aff65d57185a60a6e41267 Mon Sep 17 00:00:00 2001 From: "James G. Kim" Date: Mon, 21 Nov 2016 11:20:00 +0900 Subject: [PATCH 16/17] Fix the broken tests --- test/unit/googleApi.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/googleApi.test.js b/test/unit/googleApi.test.js index ee822ea..19991e0 100644 --- a/test/unit/googleApi.test.js +++ b/test/unit/googleApi.test.js @@ -18,28 +18,28 @@ describe('googleApi', function() { it ('position', async function() { let ret = await GoogleApi.geocodePosition('myKey', {lat: 1.234, lng: 1.14}); expect(geocodeRequest).to.have.been.calledWith( - 'https://maps.google.com/maps/api/geocode/json?key=myKey&latlng=1.234,1.14'); + 'https://maps.googleapis.com/maps/api/geocode/json?key=myKey&latlng=1.234,1.14'); expect(ret).to.eql('yo'); }); it ('position with a specific language', async function() { let ret = await GoogleApi.geocodePosition('myKey', {lat: 1.234, lng: 1.14}, 'ko'); expect(geocodeRequest).to.have.been.calledWith( - 'https://maps.google.com/maps/api/geocode/json?key=myKey&latlng=1.234,1.14&language=ko'); + 'https://maps.googleapis.com/maps/api/geocode/json?key=myKey&latlng=1.234,1.14&language=ko'); expect(ret).to.eql('yo'); }); it ('address', async function() { let ret = await GoogleApi.geocodeAddress('myKey', 'london'); expect(geocodeRequest).to.have.been.calledWith( - 'https://maps.google.com/maps/api/geocode/json?key=myKey&address=london'); + 'https://maps.googleapis.com/maps/api/geocode/json?key=myKey&address=london'); expect(ret).to.eql('yo'); }); it ('address with a specific language', async function() { let ret = await GoogleApi.geocodeAddress('myKey', 'london', 'ko'); expect(geocodeRequest).to.have.been.calledWith( - 'https://maps.google.com/maps/api/geocode/json?key=myKey&address=london&language=ko'); + 'https://maps.googleapis.com/maps/api/geocode/json?key=myKey&address=london&language=ko'); expect(ret).to.eql('yo'); }); }); From 26de832009c6f92b6edd0d7b712e171cf5c51529 Mon Sep 17 00:00:00 2001 From: Son Tran-Nguyen Date: Sat, 7 Jan 2017 12:30:05 -0600 Subject: [PATCH 17/17] Update headers for RN@0.40.0 React Native has made a breaking change to header paths in [0.40.0](https://github.com/facebook/react-native/releases/tag/v0.40.0). Besides fixing the headers, this adds `RN@0.40` and `React@15.4.0` as peer depedencies and bump version. --- ios/RNGeocoder/RNGeocoder.h | 4 ++-- ios/RNGeocoder/RNGeocoder.m | 2 +- package.json | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ios/RNGeocoder/RNGeocoder.h b/ios/RNGeocoder/RNGeocoder.h index 8abb862..98ce8fa 100644 --- a/ios/RNGeocoder/RNGeocoder.h +++ b/ios/RNGeocoder/RNGeocoder.h @@ -1,5 +1,5 @@ -#import "RCTBridgeModule.h" -#import "RCTConvert.h" +#import +#import #import diff --git a/ios/RNGeocoder/RNGeocoder.m b/ios/RNGeocoder/RNGeocoder.m index 5a47d16..796ac4a 100644 --- a/ios/RNGeocoder/RNGeocoder.m +++ b/ios/RNGeocoder/RNGeocoder.m @@ -2,7 +2,7 @@ #import -#import "RCTConvert.h" +#import @implementation RCTConvert (CoreLocation) diff --git a/package.json b/package.json index 3f8bed0..e7d9a14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-geocoder", - "version": "0.4.5", + "version": "0.5.0", "description": "react native geocoding and reverse geocoding", "main": "index.js", "scripts": { @@ -42,5 +42,9 @@ }, "dependencies": { "geolib": "^2.0.21" + }, + "peerDependencies": { + "react": ">=15.4.0", + "react-native": ">=0.40" } }