Update: (Android/iOS) Improve accuracy of touch detection on geodesic polyline
MODIFY
README.md
README.md
});
```
- - Add: (Android/iOS/Browser) `TileOverlayOptions.getTile` can return **base64 encoded image(png,gif,jpeg)**.
- Add: (Android/iOS/Browser) `BaseClass.onThrottled()/addThrottledEventListener()/hasEventListener()` are added.
+ ```js
+ var marker = map.addMarker({ ... });
+ marker.onThrottled('position_changed', function(latLng) {
+ console.log(latLng);
+ }, 1000);
+ ```
+
+ - Add: (Android/iOS/Browser) `TileOverlayOptions.getTile` can return **base64 encoded image(png,gif,jpeg)**.
- Fix: (Android) Can not load icon image file for Marker after external link opened.
- Fix: (Browser) `MapOptions.styles` does not work.
- Update: (Android) Set `transparent` backgroundColor at `onResume()` because some other plugins change background color.
+ - Update: (Android/iOS) Improve accuracy of touch detection on geodesic polyline.
- **v2.4.6**
- Fix: (iOS) Only `src/ios/check_sdk_version.js` error.

MODIFY
plugin.xml
plugin.xml
<?xml version='1.0' encoding='utf-8'?>
-<plugin id="cordova-plugin-googlemaps" version="2.5.0-beta-20181003-1130" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+<plugin id="cordova-plugin-googlemaps" version="2.5.0-beta-20181003-1630" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
<name>cordova-plugin-googlemaps</name>
<js-module name="Promise" src="www/Promise.js" />
<asset src="www/promise-7.0.4.min.js.map" target="promise-7.0.4.min.js.map" />

MODIFY
src/android/plugin/google/maps/PluginMap.java
src/android/plugin/google/maps/PluginMap.java
@@ -53,9 +53,11 @@ import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PointOfInterest;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.Polyline;
+import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.gms.maps.model.VisibleRegion;
import org.apache.cordova.CallbackContext;
@@ -69,6 +71,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -2471,9 +2474,9 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
*
* @param points
* @param point
- * @return
+ * @return LatLng on the line
*/
- private boolean isPointOnTheLine(List<LatLng> points, LatLng point) {
+ private LatLng isPointOnTheLine(List<LatLng> points, LatLng point) {
double Sx, Sy;
Point p0, p1, touchPoint;
touchPoint = projection.toScreenLocation(point);
@@ -2484,11 +2487,11 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
Sx = ((double)touchPoint.x - (double)p0.x) / ((double)p1.x - (double)p0.x);
Sy = ((double)touchPoint.y - (double)p0.y) / ((double)p1.y - (double)p0.y);
if (Math.abs(Sx - Sy) < 0.05 && Sx < 1 && Sx > 0) {
- return true;
+ return points.get(i);
}
p0 = p1;
}
- return false;
+ return null;
}
/**
@@ -2498,15 +2501,38 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
* @param points
* @param point
* @param threshold
- * @return boolean
+ * @return LatLng on the line
*/
- private boolean isPointOnTheGeodesicLine(List<LatLng> points, LatLng point, double threshold) {
+ private LatLng isPointOnTheGeodesicLine(List<LatLng> points, final LatLng point, double threshold) {
+
double trueDistance, testDistance1, testDistance2;
- Point p0, p1, touchPoint;
- //touchPoint = new Point();
- //touchPoint.x = (int) (point.latitude * 100000);
- //touchPoint.y = (int) (point.longitude * 100000);
+ Point p0, p1;
+ int fingerSize = (int)(20 * density); // assume finger size is 20px
+ // clicked point(latlng) -> pixels
+ Point touchPoint = projection.toScreenLocation(point);
+ LatLngBounds possibleBounds = new LatLngBounds(point, point);
+ Point nePoint = new Point(touchPoint.x - fingerSize, touchPoint.y - fingerSize);
+ Point swPoint = new Point(touchPoint.x + fingerSize, touchPoint.y + fingerSize);
+
+ possibleBounds = possibleBounds.including(projection.fromScreenLocation(nePoint));
+ possibleBounds = possibleBounds.including(projection.fromScreenLocation(swPoint));
+
+ //--------------------------
+ // debug: draw rectangle
+ //--------------------------
+// PolylineOptions polylineOptions = new PolylineOptions();
+// polylineOptions.add(possibleBounds.northeast);
+// polylineOptions.add(new LatLng(possibleBounds.northeast.latitude, possibleBounds.southwest.longitude));
+// polylineOptions.add(possibleBounds.southwest);
+// polylineOptions.add(new LatLng(possibleBounds.southwest.latitude, possibleBounds.northeast.longitude));
+// polylineOptions.add(possibleBounds.northeast);
+// map.addPolyline(polylineOptions);
+
+ //----------------------------------------------------------------
+ // Detect the clicked-point is closer to the line or not
+ //----------------------------------------------------------------
+ LatLng start = null, finish = null;
for (int i = 0; i < points.size() - 1; i++) {
p0 = new Point();
p0.x = (int) (points.get(i).latitude * 100000);
@@ -2519,11 +2545,130 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
testDistance2 = this.calculateDistance(point, points.get(i + 1));
// the distance is exactly same if the point is on the straight line
if (Math.abs(trueDistance - (testDistance1 + testDistance2)) < threshold) {
- return true;
+ if (i == 0) {
+ start = points.get(0);
+ finish = points.get(1);
+ } else if (i == points.size() - 1) {
+ start = points.get(i - 1);
+ finish = points.get(i);
+ } else {
+ start = points.get(i);
+ finish = points.get(i + 1);
+ }
+ break;
}
}
- return false;
+ if (start == null) {
+ return null;
+ }
+
+ if (start.longitude > finish.longitude) {
+ LatLng tmp = start;
+ start = finish;
+ finish = tmp;
+ }
+
+ //--------------------------
+ // debug: draw rectangle
+ //--------------------------
+// LatLngBounds _targetBounds = new LatLngBounds(start, finish);
+// PolylineOptions polylineOptions2 = new PolylineOptions();
+// polylineOptions2.add(_targetBounds.northeast);
+// polylineOptions2.add(new LatLng(_targetBounds.northeast.latitude, _targetBounds.southwest.longitude));
+// polylineOptions2.add(_targetBounds.southwest);
+// polylineOptions2.add(new LatLng(_targetBounds.southwest.latitude, _targetBounds.northeast.longitude));
+// polylineOptions2.add(_targetBounds.northeast);
+// map.addPolyline(polylineOptions2);
+
+
+ //----------------------------------------------------------------
+ // Calculate waypoints from start to finish on geodesic line
+ // @ref http://jamesmccaffrey.wordpress.com/2011/04/17/drawing-a-geodesic-line-for-bing-maps-ajax/
+ //----------------------------------------------------------------
+
+ // convert to radians
+ double lat1 = start.latitude * (Math.PI / 180.0);
+ double lng1 = start.longitude * (Math.PI / 180.0);
+ double lat2 = finish.latitude * (Math.PI / 180.0);
+ double lng2 = finish.longitude * (Math.PI / 180.0);
+
+ double d = 2 * Math.asin(Math.sqrt(Math.pow((Math.sin((lat1 - lat2) / 2)), 2) +
+ Math.cos(lat1) * Math.cos(lat2) * Math.pow((Math.sin((lng1 - lng2) / 2)), 2)));
+ List<LatLng> wayPoints = new ArrayList<LatLng>();
+ double f = 0.00000000f; // fraction of the curve
+ double finc = 0.01000000f; // fraction increment
+
+ while (f <= 1.00000000f) {
+ double A = Math.sin((1.0 - f) * d) / Math.sin(d);
+ double B = Math.sin(f * d) / Math.sin(d);
+
+ double x = A * Math.cos(lat1) * Math.cos(lng1) + B * Math.cos(lat2) * Math.cos(lng2);
+ double y = A * Math.cos(lat1) * Math.sin(lng1) + B * Math.cos(lat2) * Math.sin(lng2);
+ double z = A * Math.sin(lat1) + B * Math.sin(lat2);
+ double lat = Math.atan2(z, Math.sqrt((x*x) + (y*y)));
+ double lng = Math.atan2(y, x);
+
+ LatLng wp = new LatLng(lat / (Math.PI / 180.0), lng / ( Math.PI / 180.0));
+ if (possibleBounds.contains(wp)) {
+ wayPoints.add(wp);
+ //map.addMarker(new MarkerOptions().position(wp));
+ }
+
+ f += finc;
+ } // while
+
+ // break into waypoints with negative longitudes and those with positive longitudes
+ List<LatLng> negLons = new ArrayList<LatLng>(); // lat-lons where the lon part is negative
+ List<LatLng> posLons = new ArrayList<LatLng>();
+ List<LatLng> connect = new ArrayList<LatLng>();
+
+ for (int i = 0; i < wayPoints.size(); ++i) {
+ if (wayPoints.get(i).longitude <= 0.0f)
+ negLons.add(wayPoints.get(i));
+ else
+ posLons.add(wayPoints.get(i));
+ }
+
+ // we may have to connect over 0.0 longitude
+ for (int i = 0; i < wayPoints.size() - 1; ++i) {
+ if (wayPoints.get(i).longitude <= 0.0f && wayPoints.get(i+1).longitude >= 0.0f ||
+ wayPoints.get(i).longitude >= 0.0f && wayPoints.get(i+1).longitude <= 0.0f) {
+ if (Math.abs(wayPoints.get(i).longitude) + Math.abs(wayPoints.get(i+1).longitude) < 100.0f) {
+ connect.add(wayPoints.get(i));
+ connect.add(wayPoints.get(i+1));
+ }
+ }
+ }
+
+ ArrayList<LatLng> inspectPoints = new ArrayList<LatLng>();
+ if (negLons.size() >= 2) {
+ inspectPoints.addAll(negLons);
+ }
+ if (posLons.size() >= 2) {
+ inspectPoints.addAll(posLons);
+ }
+ if (connect.size() >= 2) {
+ inspectPoints.addAll(connect);
+ }
+
+ if (inspectPoints.size() == 0) {
+ return null;
+ }
+
+
+ double minDistance = 999999999;
+ double distance;
+ LatLng mostClosePoint = null;
+
+ for (int i = 0; i < inspectPoints.size(); i++) {
+ distance = this.calculateDistance(point, inspectPoints.get(i));
+ if (distance < minDistance) {
+ minDistance = distance;
+ mostClosePoint = inspectPoints.get(i);
+ }
+ }
+ return mostClosePoint;
}
/**
@@ -2780,7 +2925,8 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
* @param point
*/
public void onMapClick(final LatLng point) {
- cordova.getThreadPool().submit(new Runnable() {
+
+ cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
@@ -2840,10 +2986,10 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
//e.printStackTrace();
}
+
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
-
//Log.d(TAG, "---> onMapClick : " + activeMarker);
if (activeMarker != null) {
//Log.d(TAG, "---> activeMarker.getTag() : " + activeMarker.getTag());
@@ -2861,18 +3007,17 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
}
activeMarker = null;
}
- String key;
Map.Entry<String, Object> entry;
Set<Map.Entry<String, Object>> entrySet = boundsHitList.entrySet();
Iterator<Map.Entry<String, Object>> iterator = entrySet.iterator();
-
List<LatLng> points ;
Point origin = new Point();
Point hitArea = new Point();
hitArea.x = 1;
hitArea.y = 1;
+ LatLng touchPoint = null;
//double threshold = calculateDistance(
// projection.fromScreenLocation(origin),
// projection.fromScreenLocation(hitArea));
@@ -2881,12 +3026,12 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
float maxZIndex = -1;
Object hitOverlay = null;
Object overlay;
+ String key;
while(iterator.hasNext()) {
entry = iterator.next();
key = entry.getKey();
overlay = entry.getValue();
-
if (key.startsWith("polyline")) {
Polyline polyline = (Polyline)overlay;
@@ -2906,13 +3051,15 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
double threshold = calculateDistance(
projection.fromScreenLocation(origin),
projection.fromScreenLocation(hitArea));
- if (isPointOnTheGeodesicLine(points, point, threshold)) {
+ touchPoint = isPointOnTheGeodesicLine(points, point, threshold);
+ if (touchPoint != null) {
hitOverlay = polyline;
maxZIndex = zIndex;
continue;
}
} else {
- if (isPointOnTheLine(points, point)) {
+ touchPoint = isPointOnTheLine(points, point);
+ if (touchPoint != null) {
hitOverlay = polyline;
maxZIndex = zIndex;
continue;
@@ -2930,6 +3077,7 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
continue;
}
if (isPolygonContains(polygon.getPoints(), point)) {
+ touchPoint = point;
hitOverlay = polygon;
maxZIndex = zIndex;
continue;
@@ -2947,6 +3095,7 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
continue;
}
if (isCircleContains(circle, point)) {
+ touchPoint = point;
hitOverlay = circle;
maxZIndex = zIndex;
continue;
@@ -2962,6 +3111,7 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
continue;
}
if (isGroundOverlayContains(groundOverlay, point)) {
+ touchPoint = point;
hitOverlay = groundOverlay;
maxZIndex = zIndex;
//continue;
@@ -2969,15 +3119,19 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
}
}
- //Log.d("PluginMap", "---> hitOverlay = " + hitOverlay);
- if (hitOverlay instanceof Polygon) {
- onPolygonClick((Polygon)hitOverlay, point);
- } else if (hitOverlay instanceof Polyline) {
- onPolylineClick((Polyline)hitOverlay, point);
- } else if (hitOverlay instanceof Circle) {
- onCircleClick((Circle)hitOverlay, point);
- } else if (hitOverlay instanceof GroundOverlay) {
- onGroundOverlayClick((GroundOverlay)hitOverlay, point);
+
+ final Object finalHitOverlay = hitOverlay;
+ final LatLng finalTouchPoint = touchPoint;
+
+ //Log.d("PluginMap", "---> hitOverlay = " + finalHitOverlay);
+ if (finalHitOverlay instanceof Polygon) {
+ onPolygonClick((Polygon)finalHitOverlay, finalTouchPoint);
+ } else if (finalHitOverlay instanceof Polyline) {
+ onPolylineClick((Polyline)finalHitOverlay, finalTouchPoint);
+ } else if (finalHitOverlay instanceof Circle) {
+ onCircleClick((Circle)finalHitOverlay, finalTouchPoint);
+ } else if (finalHitOverlay != null) {
+ onGroundOverlayClick((GroundOverlay)finalHitOverlay, finalTouchPoint);
} else {
// Only emit click event if no overlays are hit
onMapEvent("map_click", point);
@@ -2986,7 +3140,6 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
});
}
});
-
}
public void onRequestPermissionResult(int requestCode, String[] permissions,

MODIFY
src/android/plugin/google/maps/PluginPolyline.java
src/android/plugin/google/maps/PluginPolyline.java
package plugin.google.maps;
import android.graphics.Color;
+import android.util.Log;
+import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
@@ -101,105 +104,6 @@ public class PluginPolyline extends MyPlugin implements MyPluginInterface {
});
}
- /**
- * Draw geodesic line
- * @ref http://jamesmccaffrey.wordpress.com/2011/04/17/drawing-a-geodesic-line-for-bing-maps-ajax/
- * @ref http://spphire9.wordpress.com/2014/02/11/%E4%BA%8C%E6%AC%A1%E3%83%99%E3%82%B8%E3%82%A7%E6%9B%B2%E7%B7%9A%E3%81%A8%E7%B7%9A%E5%88%86%E3%81%AE%E5%BD%93%E3%81%9F%E3%82%8A%E5%88%A4%E5%AE%9A/
- * @ref http://my-clip-devdiary.blogspot.com/2014/01/html5canvas.html
- * @param args
- * @param callbackContext
- * @throws JSONException
- */
- @SuppressWarnings("unused")
- public void createPolyline2(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
- final PolylineOptions polylineOptions = new PolylineOptions();
- int color;
-
- JSONObject opts = args.getJSONObject(1);
- if (opts.has("points")) {
- JSONArray points = opts.getJSONArray("points");
- List<LatLng> path = PluginUtil.JSONArray2LatLngList(points);
-
- for (int k = 0; k < path.size() - 1; k++) {
- LatLng start = path.get(k);
- LatLng finish = path.get(k + 1);
-
- if (start.longitude > finish.longitude) {
- start = finish;
- finish = path.get(k);
- }
-
- // convert to radians
- double lat1 = start.latitude * (Math.PI / 180.0);
- double lng1 = start.longitude * (Math.PI / 180.0);
- double lat2 = finish.latitude * (Math.PI / 180.0);
- double lng2 = finish.longitude * (Math.PI / 180.0);
-
- double d = 2 * Math.asin(Math.sqrt(Math.pow((Math.sin((lat1 - lat2) / 2)), 2) +
- Math.cos(lat1) * Math.cos(lat2) * Math.pow((Math.sin((lng1 - lng2) / 2)), 2)));
- List<LatLng> wayPoints = new ArrayList<LatLng>();
- double f = 0.00000000f; // fraction of the curve
- double finc = 0.01000000f; // fraction increment
-
- while (f <= 1.00000000f) {
- double A = Math.sin((1.0 - f) * d) / Math.sin(d);
- double B = Math.sin(f * d) / Math.sin(d);
-
- double x = A * Math.cos(lat1) * Math.cos(lng1) + B * Math.cos(lat2) * Math.cos(lng2);
- double y = A * Math.cos(lat1) * Math.sin(lng1) + B * Math.cos(lat2) * Math.sin(lng2);
- double z = A * Math.sin(lat1) + B * Math.sin(lat2);
- double lat = Math.atan2(z, Math.sqrt((x*x) + (y*y)));
- double lng = Math.atan2(y, x);
-
- LatLng wp = new LatLng(lat / (Math.PI / 180.0), lng / ( Math.PI / 180.0));
- wayPoints.add(wp);
-
- f += finc;
- } // while
-
- // break into waypoints with negative longitudes and those with positive longitudes
- List<LatLng> negLons = new ArrayList<LatLng>(); // lat-lons where the lon part is negative
- List<LatLng> posLons = new ArrayList<LatLng>();
- List<LatLng> connect = new ArrayList<LatLng>();
-
- for (int i = 0; i < wayPoints.size(); ++i) {
- if (wayPoints.get(i).longitude <= 0.0f)
- negLons.add(wayPoints.get(i));
- else
- posLons.add(wayPoints.get(i));
- }
-
- // we may have to connect over 0.0 longitude
- for (int i = 0; i < wayPoints.size() - 1; ++i) {
- if (wayPoints.get(i).longitude <= 0.0f && wayPoints.get(i+1).longitude >= 0.0f ||
- wayPoints.get(i).longitude >= 0.0f && wayPoints.get(i+1).longitude <= 0.0f) {
- if (Math.abs(wayPoints.get(i).longitude) + Math.abs(wayPoints.get(i+1).longitude) < 100.0f) {
- connect.add(wayPoints.get(i));
- connect.add(wayPoints.get(i+1));
- }
- }
- }
-
- PolylineOptions options = new PolylineOptions();
- options.color(Color.RED);
- options.width(4);
- if (negLons.size() >= 2) {
- options.addAll(negLons);
- }
-
- if (posLons.size() >= 2) {
- options.addAll(posLons);
- }
-
- if (connect.size() >= 2) {
- options.addAll(connect);
- }
- map.addPolyline(options);
- }
- }
-
- callbackContext.success();
- }
/**
* set color

MODIFY
src/ios/GoogleMaps/PluginMapViewController.m
src/ios/GoogleMaps/PluginMapViewController.m
//
maxZIndex = -1;
+ CLLocationCoordinate2D touchPoint;
for (i = 0; i < [boundsHitList count]; i++) {
key = [boundsHitList objectAtIndex:i];
//plugin = [boundsPluginList objectAtIndex:i];
geodesic = (NSNumber *)[properties objectForKey:@"geodesic"];
path = (GMSPath *)[properties objectForKey:@"mutablePath"];
if ([geodesic boolValue] == YES) {
- if ([PluginUtil isPointOnTheGeodesicLine:path coordinate:coordinate threshold:threshold]) {
-
+ touchPoint = [PluginUtil isPointOnTheGeodesicLine:path coordinate:coordinate threshold:threshold projection:self.map.projection];
+ if (CLLocationCoordinate2DIsValid(touchPoint)) {
maxZIndex = zIndex;
hitKey = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
continue;
}
} else {
- if ([PluginUtil isPointOnTheLine:path coordinate:coordinate projection:self.map.projection]) {
+ touchPoint = [PluginUtil isPointOnTheLine:path coordinate:coordinate projection:self.map.projection];
+ if (CLLocationCoordinate2DIsValid(touchPoint)) {
maxZIndex = zIndex;
hitKey = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
continue;
if ([key hasPrefix:@"polygon_"]) {
path = (GMSPath *)[properties objectForKey:@"mutablePath"];
if ([PluginUtil isPolygonContains:path coordinate:coordinate projection:self.map.projection]) {
+ touchPoint = coordinate;
maxZIndex = zIndex;
hitKey = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
continue;
key = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
GMSCircle *circle = (GMSCircle *)[self.objects objectForKey:key];
if ([PluginUtil isCircleContains:circle coordinate:coordinate]) {
+ touchPoint = coordinate;
maxZIndex = zIndex;
hitKey = key;
continue;
key = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
GMSGroundOverlay *groundOverlay = (GMSGroundOverlay *)[self.objects objectForKey:key];
if ([groundOverlay.bounds containsCoordinate:coordinate]) {
+ touchPoint = coordinate;
maxZIndex = zIndex;
hitKey = key;
continue;
if (hitKey != nil) {
NSArray *tmp = [hitKey componentsSeparatedByString:@"_"];
NSString *eventName = [NSString stringWithFormat:@"%@_click", [tmp objectAtIndex:0]];
- [self triggerOverlayEvent:eventName overlayId:hitKey coordinate:coordinate];
+ [self triggerOverlayEvent:eventName overlayId:hitKey coordinate:touchPoint];
} else {
[self triggerMapEvent:@"map_click" coordinate:coordinate];
}

MODIFY
src/ios/GoogleMaps/PluginUtil.h
src/ios/GoogleMaps/PluginUtil.h
@@ -75,8 +75,8 @@ typedef void (^TIFAnimationGroupCompletionBlock)();
@interface PluginUtil : NSObject
+ (BOOL)isPolygonContains:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate projection:(GMSProjection *)projection;
-+ (BOOL)isPointOnTheLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate projection:(GMSProjection *)projection;
-+ (BOOL)isPointOnTheGeodesicLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate threshold:(double)threshold;
++ (CLLocationCoordinate2D)isPointOnTheLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate projection:(GMSProjection *)projection;
++ (CLLocationCoordinate2D)isPointOnTheGeodesicLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate threshold:(double)threshold projection:(GMSProjection *)projection;
+ (BOOL)isCircleContains:(GMSCircle *)circle coordinate:(CLLocationCoordinate2D)point;
+ (BOOL)isInDebugMode;
+ (GMSMutablePath *)getMutablePathFromCircle:(CLLocationCoordinate2D)center radius:(double)radius;

MODIFY
src/ios/GoogleMaps/PluginUtil.m
src/ios/GoogleMaps/PluginUtil.m
@@ -198,7 +198,7 @@ static char CAAnimationGroupBlockKey;
return (wn != 0);
}
-+ (BOOL)isPointOnTheLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate projection:(GMSProjection *)projection {
++ (CLLocationCoordinate2D)isPointOnTheLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)coordinate projection:(GMSProjection *)projection {
//-------------------------------------------------------------------
// Intersection for non-geodesic line
// http://movingahead.seesaa.net/article/299962216.html
@@ -214,13 +214,21 @@ static char CAAnimationGroupBlockKey;
Sx = (touchPoint.x - p0.x) / (p1.x - p0.x);
Sy = (touchPoint.y - p0.y) / (p1.y - p0.y);
if (fabs(Sx - Sy) < 0.05 && Sx < 1 && Sy > 0) {
- return YES;
+ return [path coordinateAtIndex:i];
}
}
- return NO;
+ return kCLLocationCoordinate2DInvalid;
}
-+ (BOOL)isPointOnTheGeodesicLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)point threshold:(double)threshold {
++ (CLLocationCoordinate2D)isPointOnTheGeodesicLine:(GMSPath *)path coordinate:(CLLocationCoordinate2D)point threshold:(double)threshold projection:(GMSProjection *)projection {
+
+ int fingerSize = 40; // assume finger size is 20px
+ CGPoint touchPoint = [projection pointForCoordinate:CLLocationCoordinate2DMake(point.latitude, point.longitude)];
+ GMSCoordinateBounds *possibleBounds = [[GMSCoordinateBounds alloc] init];
+ possibleBounds = [possibleBounds includingCoordinate:[projection coordinateForPoint:CGPointMake(touchPoint.x - fingerSize, touchPoint.y - fingerSize)]];
+ possibleBounds = [possibleBounds includingCoordinate:[projection coordinateForPoint:CGPointMake(touchPoint.x + fingerSize, touchPoint.y + fingerSize)]];
+
+
//-------------------------------------------------------------------
// Intersection for geodesic line
// http://my-clip-devdiary.blogspot.com/2014/01/html5canvas.html
@@ -230,7 +238,9 @@ static char CAAnimationGroupBlockKey;
NSValue *value;
//CGPoint touchPoint = CGPointMake(point.latitude * 100000, point.longitude * 100000);
CLLocationCoordinate2D position1, position2;
+ CLLocationCoordinate2D start, finish;
NSMutableArray *points = [[NSMutableArray alloc] init];
+ BOOL firstTest = NO;
for (int i = 0; i < [path count]; i++) {
position1 = [path coordinateAtIndex:i];
@@ -252,11 +262,106 @@ static char CAAnimationGroupBlockKey;
testDistance2 = GMSGeometryDistance(point, position2);
// the distance is exactly same if the point is on the straight line
if (fabs(trueDistance - (testDistance1 + testDistance2)) < threshold) {
- return YES;
+ start = position1;
+ finish = position2;
+ firstTest = YES;
+ break;
}
}
- return NO;
+
+ if (firstTest == NO) {
+ return kCLLocationCoordinate2DInvalid;
}
+
+ //----------------------------------------------------------------
+ // Calculate waypoints from start to finish on geodesic line
+ // @ref http://jamesmccaffrey.wordpress.com/2011/04/17/drawing-a-geodesic-line-for-bing-maps-ajax/
+ //----------------------------------------------------------------
+
+ // convert to radians
+ double lat1 = start.latitude * (M_PI / 180.0);
+ double lng1 = start.longitude * (M_PI / 180.0);
+ double lat2 = finish.latitude * (M_PI / 180.0);
+ double lng2 = finish.longitude * (M_PI / 180.0);
+
+ double d = 2 * asin(sqrt(pow((sin((lat1 - lat2) / 2)), 2) +
+ cos(lat1) * cos(lat2) * pow((sin((lng1 - lng2) / 2)), 2)));
+ GMSMutablePath *wayPoints = [GMSMutablePath path];
+ double f = 0.00000000f; // fraction of the curve
+ double finc = 0.01000000f; // fraction increment
+
+ while (f <= 1.00000000f) {
+ double A = sin((1.0 - f) * d) / sin(d);
+ double B = sin(f * d) / sin(d);
+
+ double x = A * cos(lat1) * cos(lng1) + B * cos(lat2) * cos(lng2);
+ double y = A * cos(lat1) * sin(lng1) + B * cos(lat2) * sin(lng2);
+ double z = A * sin(lat1) + B * sin(lat2);
+ double lat = atan2(z, sqrt((x*x) + (y*y)));
+ double lng = atan2(y, x);
+
+ CLLocationCoordinate2D wp = CLLocationCoordinate2DMake(lat / (M_PI / 180.0), lng / ( M_PI / 180.0));
+ if ([possibleBounds containsCoordinate:wp]) {
+ [wayPoints addCoordinate:wp];
+ }
+
+ f += finc;
+ } // while
+
+ // break into waypoints with negative longitudes and those with positive longitudes
+ GMSMutablePath *negLons = [GMSMutablePath path]; // lat-lons where the lon part is negative
+ GMSMutablePath *posLons = [GMSMutablePath path];
+ GMSMutablePath *connect = [GMSMutablePath path];
+
+ for (int i = 0; i < [wayPoints count]; ++i) {
+ if ([wayPoints coordinateAtIndex:i].longitude <= 0.0f)
+ [negLons addCoordinate:[wayPoints coordinateAtIndex:i]];
+ else
+ [posLons addCoordinate:[wayPoints coordinateAtIndex:i]];
+ }
+
+ // we may have to connect over 0.0 longitude
+ for (int i = 0; i < [wayPoints count] - 1; ++i) {
+ if (([wayPoints coordinateAtIndex:i].longitude <= 0.0f && [wayPoints coordinateAtIndex:(i + 1)].longitude >= 0.0f) ||
+ ([wayPoints coordinateAtIndex:i].longitude >= 0.0f && [wayPoints coordinateAtIndex:(i + 1)].longitude <= 0.0f)) {
+ if ((fabs([wayPoints coordinateAtIndex:i].longitude) + fabs([wayPoints coordinateAtIndex:(i + 1)].longitude)) < 100.0f) {
+ [connect addCoordinate:[wayPoints coordinateAtIndex:i]];
+ [connect addCoordinate:[wayPoints coordinateAtIndex:(i + 1)]];
+ }
+ }
+ }
+
+ GMSMutablePath *inspectPoints = [GMSMutablePath path];
+ if ([negLons count] > 2) {
+ for (int i = 0; i < [negLons count]; i++) {
+ [inspectPoints addCoordinate:[negLons coordinateAtIndex:i]];
+ }
+ }
+ if ([posLons count] > 2) {
+ for (int i = 0; i < [posLons count]; i++) {
+ [inspectPoints addCoordinate:[posLons coordinateAtIndex:i]];
+ }
+ }
+ if ([connect count] > 2) {
+ for (int i = 0; i < [connect count]; i++) {
+ [inspectPoints addCoordinate:[connect coordinateAtIndex:i]];
+ }
+ }
+
+ double minDistance = 999999999;
+ double distance;
+ CLLocationCoordinate2D mostClosePoint;
+
+ for (int i = 0; i < [inspectPoints count]; i++) {
+ distance = GMSGeometryDistance([inspectPoints coordinateAtIndex:i], point);
+ if (distance < minDistance) {
+ minDistance = distance;
+ mostClosePoint = [inspectPoints coordinateAtIndex:i];
+ }
+ }
+ return mostClosePoint;
+}
+
+ (BOOL) isInDebugMode
{
#ifndef __OPTIMIZE__ // Debug Mode

Update: (Android/iOS) Improve accuracy of touch detection on geodesic polyline