Skip to content

Commit 308cd92

Browse files
Merge pull request #54 from DavidWiesner/intent-service-geocoding
fix issue #47 move Geocoding in an IntentService to not block the main thread
2 parents 1b5cacf + 19ebb50 commit 308cd92

File tree

6 files changed

+316
-203
lines changed

6 files changed

+316
-203
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 3.3.0 (2020-03-25)
2+
3+
- merge PR #54 "move Geocoding in an IntentService to not block the main thread" (closes #47, thank you @DavidWiesner)
4+
5+
## ** BREAKING CHANGES **
6+
- remove dependency [Swift support plugin](https://github.com/akofman/cordova-plugin-add-swift-support); min Cordova iOS Version is now >5.0.0
7+
8+
19
# 3.2.2 (2019-04-14)
210

311
- update cordova-plugin-add-swift-support to 2.0.2 (closes #45, thank you @DavidWiesner)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This plugin is also available for **[Ionic Native](https://ionicframework.com/do
1313
```
1414
cordova plugin add cordova-plugin-nativegeocoder
1515
```
16-
The iOS part is written in Swift and the [Swift support plugin](https://github.com/akofman/cordova-plugin-add-swift-support) is configured as a dependency.
16+
**For iOS Cordova iOS version >5.0.0 is required.**
1717

1818
## Configuration
1919
You can also configure the following variable to customize the iOS location plist entry

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cordova-plugin-nativegeocoder",
3-
"version": "3.2.2",
3+
"version": "3.3.0",
44
"description": "Cordova plugin for native forward and reverse geocoding",
55
"cordova": {
66
"id": "cordova-plugin-nativegeocoder",

plugin.xml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<plugin id="cordova-plugin-nativegeocoder" version="3.2.2" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
2+
<plugin id="cordova-plugin-nativegeocoder" version="3.3.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
33
<name>NativeGeocoder</name>
44
<description>Cordova plugin for native forward and reverse geocoding</description>
55
<license>MIT</license>
@@ -22,7 +22,16 @@
2222
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
2323
</config-file>
2424

25-
<source-file src="src/android/NativeGeocoder.java" target-dir="src/cordova/plugin/nativegeocoder" />
25+
<config-file target="AndroidManifest.xml" parent="application">
26+
<service
27+
android:name="cordova.plugin.nativegeocoder.GeocodingIntentService"
28+
android:exported="false"
29+
android:enabled="true"
30+
/>
31+
</config-file>
32+
33+
<source-file src="src/android/NativeGeocoder.java" target-dir="src/cordova/plugin/nativegeocoder" />
34+
<source-file src="src/android/GeocodingIntentService.java" target-dir="src/cordova/plugin/nativegeocoder" />
2635
</platform>
2736

2837
<!-- ios -->
@@ -42,8 +51,5 @@
4251
<config-file target="*-Info.plist" parent="NSLocationWhenInUseUsageDescription">
4352
<string>$LOCATION_WHEN_IN_USE_DESCRIPTION</string>
4453
</config-file>
45-
46-
<dependency id="cordova-plugin-add-swift-support" version="2.0.2"/>
4754
</platform>
48-
4955
</plugin>
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
package cordova.plugin.nativegeocoder;
2+
3+
import android.app.IntentService;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.location.Address;
7+
import android.location.Geocoder;
8+
import android.location.Location;
9+
import android.net.ConnectivityManager;
10+
import android.net.NetworkInfo;
11+
import android.os.Build;
12+
import android.os.Bundle;
13+
import android.os.ResultReceiver;
14+
15+
import org.json.JSONArray;
16+
import org.json.JSONException;
17+
import org.json.JSONObject;
18+
19+
import java.io.IOException;
20+
import java.util.List;
21+
import java.util.Locale;
22+
23+
24+
class NativeGeocoderOptions {
25+
boolean useLocale = true;
26+
String defaultLocale = null;
27+
int maxResults = 1;
28+
}
29+
30+
public class GeocodingIntentService extends IntentService {
31+
public static final String OPTIONS_DATA_EXTRA = "NATIVE_GEOCODER_OPTIONS_DATA_EXTRA";
32+
public static final String LOCATION_DATA_EXTRA = "NATIVE_GEOCODER_LOCATION_DATA_EXTRA";
33+
public static final String ADDRESS_STRING_DATA_EXTRA = "NATIVE_GEOCODER_ADDRESS_STRING_DATA_EXTRA";
34+
public static final String RECEIVER = "NATIVE_GEOCODER_RECEIVER";
35+
public static final int FAILURE_RESULT = 1;
36+
public static final int SUCCESS_RESULT = 0;
37+
public static final String RESULT_DATA_KEY = "NATIVE_GEOCODER_RECEIVER_RESULT_KEY";
38+
39+
public GeocodingIntentService() {
40+
super("GeocodingIntentService");
41+
}
42+
43+
@Override
44+
protected void onHandleIntent(Intent intent) {
45+
Location location = intent.getParcelableExtra(LOCATION_DATA_EXTRA);
46+
String options = intent.getStringExtra(OPTIONS_DATA_EXTRA);
47+
String addressString = intent.getStringExtra(ADDRESS_STRING_DATA_EXTRA);
48+
ResultReceiver mReceiver = intent.getParcelableExtra(RECEIVER);
49+
50+
NativeGeocoderOptions geocoderOptions = getNativeGeocoderOptions(options);
51+
52+
try {
53+
if (location != null) {
54+
reverseGeocode(mReceiver, location, geocoderOptions);
55+
} else {
56+
forwardGeocode(mReceiver, addressString, geocoderOptions);
57+
}
58+
} catch (Exception e) {
59+
String errorMsg = e.getMessage();
60+
if (e.getMessage().equals("grpc failed") && !isNetworkAvailable()) {
61+
errorMsg = "No Internet Access";
62+
}
63+
deliverResultToReceiver(mReceiver, FAILURE_RESULT, "Geocoder:getFromLocationName Error: " + errorMsg);
64+
}
65+
}
66+
67+
/**
68+
* Reverse geocode a given location to find location address
69+
* @param mReceiver ResultReceiver
70+
* @param location Location
71+
* @param geocoderOptions NativeGeocoderOptions
72+
*/
73+
private void reverseGeocode(ResultReceiver mReceiver, Location location, NativeGeocoderOptions geocoderOptions) throws IOException {
74+
Geocoder geocoder = createGeocoderWithOptions(geocoderOptions);
75+
List<Address> addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(),
76+
geocoderOptions.maxResults);
77+
if (addresses.size() > 0) {
78+
JSONArray jsonAddresses = this.addressesToJSONArray(addresses, geocoderOptions.maxResults, false);
79+
deliverResultToReceiver(mReceiver, SUCCESS_RESULT, jsonAddresses.toString());
80+
} else {
81+
deliverResultToReceiver(mReceiver, FAILURE_RESULT, "Cannot get an address.");
82+
}
83+
}
84+
85+
/**
86+
* Forward geocode a given address to find coordinates
87+
* @param mReceiver ResultReceiver
88+
* @param addressString String
89+
* @param geocoderOptions NativeGeocoderOptions
90+
*/
91+
private void forwardGeocode(ResultReceiver mReceiver, String addressString, NativeGeocoderOptions geocoderOptions) throws IOException {
92+
Geocoder geocoder = createGeocoderWithOptions(geocoderOptions);
93+
List<Address> addresses = geocoder.getFromLocationName(addressString, geocoderOptions.maxResults);
94+
if (addresses.size() > 0) {
95+
JSONArray jsonAddresses = this.addressesToJSONArray(addresses, geocoderOptions.maxResults, true);
96+
if (jsonAddresses.length() > 0) {
97+
deliverResultToReceiver(mReceiver, SUCCESS_RESULT, jsonAddresses.toString());
98+
} else {
99+
deliverResultToReceiver(mReceiver, FAILURE_RESULT, "Cannot get latitude and/or longitude.");
100+
}
101+
} else {
102+
deliverResultToReceiver(mReceiver, FAILURE_RESULT, "Cannot find a location.");
103+
}
104+
}
105+
106+
/**
107+
* Send the decoding result back to the receiver
108+
* @param mReceiver ResultReceiver
109+
* @param resultCode int
110+
* @param message String
111+
*/
112+
private void deliverResultToReceiver(ResultReceiver mReceiver, int resultCode, String message) {
113+
Bundle bundle = new Bundle();
114+
bundle.putString(RESULT_DATA_KEY, message);
115+
mReceiver.send(resultCode, bundle);
116+
}
117+
118+
/**
119+
* Convert a list of addresses to a JSONArray
120+
* @param addresses List<Address>
121+
* @param maxResults int
122+
* @param skipEmptyLocationResults boolean
123+
* @return JSONArray
124+
*/
125+
private JSONArray addressesToJSONArray(List<Address> addresses, int maxResults, boolean skipEmptyLocationResults) {
126+
JSONArray resultArray = new JSONArray();
127+
int maxResultObjects = Math.min(addresses.size(), maxResults);
128+
129+
for (int i = 0; i < maxResultObjects; i++) {
130+
Address address = addresses.get(i);
131+
132+
try {
133+
String latitude = String.valueOf(address.getLatitude());
134+
String longitude = String.valueOf(address.getLongitude());
135+
136+
boolean hasLocation = !latitude.isEmpty() && !longitude.isEmpty();
137+
if (hasLocation || !skipEmptyLocationResults) {
138+
// https://developer.android.com/reference/android/location/Address.html
139+
JSONObject placemark = new JSONObject();
140+
placemark.put("latitude", latitude.isEmpty() ? "" : latitude);
141+
placemark.put("longitude", longitude.isEmpty() ? "" : longitude);
142+
placemark.put("countryCode", address.getCountryCode() != null ? address.getCountryCode() : "");
143+
placemark.put("countryName", address.getCountryName() != null ? address.getCountryName() : "");
144+
placemark.put("postalCode", address.getPostalCode() != null ? address.getPostalCode() : "");
145+
placemark.put("administrativeArea", address.getAdminArea() != null ? address.getAdminArea() : "");
146+
placemark.put("subAdministrativeArea", address.getSubAdminArea() != null ? address.getSubAdminArea() : "");
147+
placemark.put("locality", address.getLocality() != null ? address.getLocality() : "");
148+
placemark.put("subLocality", address.getSubLocality() != null ? address.getSubLocality() : "");
149+
placemark.put("thoroughfare", address.getThoroughfare() != null ? address.getThoroughfare() : "");
150+
placemark.put("subThoroughfare", address.getSubThoroughfare() != null ? address.getSubThoroughfare() : "");
151+
placemark.put("areasOfInterest", address.getFeatureName() != null ? new JSONArray(new String[]{address.getFeatureName()}) : new JSONArray());
152+
153+
resultArray.put(placemark);
154+
}
155+
} catch (JSONException | RuntimeException e) {
156+
e.printStackTrace();
157+
}
158+
}
159+
return resultArray;
160+
}
161+
162+
/**
163+
* Get a valid NativeGeocoderOptions object
164+
*
165+
* @param stringOptions String
166+
* @return NativeGeocoderOptions
167+
*/
168+
private NativeGeocoderOptions getNativeGeocoderOptions(String stringOptions) {
169+
NativeGeocoderOptions geocoderOptions = new NativeGeocoderOptions();
170+
171+
if (stringOptions != null) {
172+
try {
173+
JSONObject options = new JSONObject(stringOptions);
174+
geocoderOptions.useLocale = !options.has("useLocale") || options.getBoolean("useLocale");
175+
if (options.has("defaultLocale")) {
176+
geocoderOptions.defaultLocale = options.getString("defaultLocale");
177+
} else {
178+
geocoderOptions.defaultLocale = null;
179+
}
180+
if (options.has("maxResults")) {
181+
geocoderOptions.maxResults = options.getInt("maxResults");
182+
183+
if (geocoderOptions.maxResults > 0) {
184+
int MAX_RESULTS_COUNT = 5;
185+
geocoderOptions.maxResults = Math.min(geocoderOptions.maxResults, MAX_RESULTS_COUNT);
186+
} else {
187+
geocoderOptions.maxResults = 1;
188+
}
189+
190+
} else {
191+
geocoderOptions.maxResults = 1;
192+
}
193+
} catch (JSONException e) {
194+
e.printStackTrace();
195+
}
196+
}
197+
198+
return geocoderOptions;
199+
}
200+
201+
/**
202+
* Create a Geocoder with NativeGeocoderOptions
203+
*
204+
* @param geocoderOptions NativeGeocoderOptions
205+
* @return Geocoder
206+
*/
207+
private Geocoder createGeocoderWithOptions(NativeGeocoderOptions geocoderOptions) {
208+
if (geocoderOptions.defaultLocale != null && !geocoderOptions.defaultLocale.isEmpty()) {
209+
Locale locale;
210+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
211+
locale = Locale.forLanguageTag(geocoderOptions.defaultLocale);
212+
} else {
213+
String[] parts = geocoderOptions.defaultLocale.split("[-_]", -1);
214+
if (parts.length == 1)
215+
locale = new Locale(parts[0]);
216+
else if (parts.length == 2 || (parts.length == 3 && parts[2].startsWith("#")))
217+
locale = new Locale(parts[0], parts[1]);
218+
else
219+
locale = new Locale(parts[0], parts[1], parts[2]);
220+
}
221+
return new Geocoder(this, locale);
222+
} else {
223+
if (geocoderOptions.useLocale) {
224+
return new Geocoder(this, Locale.getDefault());
225+
} else {
226+
return new Geocoder(this, Locale.ENGLISH);
227+
}
228+
}
229+
}
230+
231+
/**
232+
* Get network connection
233+
*
234+
* @return boolean
235+
*/
236+
private boolean isNetworkAvailable() {
237+
ConnectivityManager connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
238+
NetworkInfo activeNetworkInfo = null;
239+
if (connectivityManager != null) {
240+
activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
241+
}
242+
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
243+
}
244+
245+
}

0 commit comments

Comments
 (0)