多边形触摸检测Google Map API V2
我试图找出如何最好地做到这一点,我有一个地图上绘制一个Polygon
。 因为Google Maps API V2似乎没有在Polygon上进行触摸检测。 我想知道是否可以检测触点是否在Polygon内? 如果是这样,那么我的主要目标是在地图上勾画一个状态,当用户点击该状态时,它将在自定义视图中显示更多细节。 到目前为止,我能够捕获地图的MapOnClick
,但是当用户在Polygon
内部点击时,我想要在Toast
上设置polygon.getID()
。 我是一个新手,所以如果我不够清楚,我很抱歉。
googleMap.setOnMapClickListener(new OnMapClickListener() { public void onMapClick(LatLng point) { boolean checkPoly = true; Toast.makeText(MainActivity.this,"The Location is outside of the Area", Toast.LENGTH_LONG).show(); } }); } } catch (Exception e) { Log.e("APP","Failed", e); }
好的,这是我迄今为止的半工作
private boolean rayCastIntersect(LatLng tap, LatLng vertA, LatLng vertB) { double aY = vertA.latitude; double bY = vertB.latitude; double aX = vertA.longitude; double bX = vertB.longitude; double pY = tap.latitude; double pX = tap.longitude; if (aY > bY) { aX = vertB.longitude; aY = vertB.latitude; bX = vertA.longitude; bX = vertA.latitude; } System.out.println("aY: "+aY+" aX : "+aX); System.out.println("bY: "+bY+" bX : "+bX); if (pX < 0) pX += 360; if (aX < 0) aX += 360; if (bX < 0) bX += 360; if (pY == aY || pY == bY) pY += 0.00000001; if ((pY > bY || pY < aY) || (pX > Math.max(aX, bX))) return false; if (pX < Math.min(aX, bX)) return true; // } double m = (aX != bX) ? ((bY - aY) / (bX - aX)) : aX; double bee = (aX != pX) ? ((pY - aY) / (pX - aX)) : aX; double x = (pY - bee) / m; return x > pX; }
}
我遇到的问题是每个多边形左边的触摸是真实的,直到到达另一个多边形。 我的algorithm有什么问题会导致这个问题? 任何帮助,将不胜感激。
你正在试图解决的问题是点多边形testing。
为了帮助观察光线投射的概念:
在一张纸上绘制一个多边形。 然后,从任意点开始,在页面的右侧画一条直线。 如果你的线与你的多边形相交奇数次,这意味着你的起点是在多边形内。
那么,你如何在代码中做到这一点?
您的多边形由顶点列表组成: ArrayList<Geopoint> vertices
。 您需要分别查看每个Line Segment
,然后查看您的Ray
是否与其相交
private boolean isPointInPolygon(Geopoint tap, ArrayList<Geopoint> vertices) { int intersectCount = 0; for(int j=0; j<vertices.size()-1; j++) { if( rayCastIntersect(tap, vertices.get(j), vertices.get(j+1)) ) { intersectCount++; } } return (intersectCount%2) == 1); // odd = inside, even = outside; } private boolean rayCastIntersect(Geopoint tap, Geopoint vertA, Geopoint vertB) { double aY = vertA.getLatitude(); double bY = vertB.getLatitude(); double aX = vertA.getLongitude(); double bX = vertB.getLongitude(); double pY = tap.getLatitude(); double pX = tap.getLongitude(); if ( (aY>pY && bY>pY) || (aY<pY && bY<pY) || (aX<pX && bX<pX) ) { return false; // a and b can't both be above or below pt.y, and a or b must be east of pt.x } double m = (aY-bY) / (aX-bX); // Rise over run double bee = (-aX) * m + aY; // y = mx + b double x = (pY - bee) / m; // algebra is neat! return x > pX; }
Google地图支持库现在有一个静态方法,可以检查您的情况:
PolyUtil.containsLocation(LatLng point, List<LatLng>polygon, boolean geodesic);
尽pipe文档没有在指南中明确提及,但方法在那里
地图支持库文档
随着Google Play服务8.4.0的发布 ,Maps API已经包含了将OnPolygonClickListener
添加到多边形的支持。 多边形 ,多段线和叠加都支持类似的事件。
您只需要调用GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener)
来设置它,并相应地为其他监听器( setOnPolylineClickListener
,&c) setOnPolylineClickListener
:
map.setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() { @Override public void onPolygonClick(Polygon polygon) { // Handle click ... } });
虽然有点晚了,但它很好地解决了这个用例。
这是一个完整的工作示例,以了解是否在多边形上发生了触摸。 一些答案比他们需要更复杂。 这个解决scheme使用“android-maps-utils”
// compile 'com.google.maps.android:android-maps-utils:0.3.4' private ArrayList<Polygon> polygonList = new ArrayList<>(); private void addMyPolygons() { PolygonOptions options = new PolygonOptions(); // TODO: make your polygon's however you want Polygon polygon = googleMap.addPolygon(options); polygonList.add(polygon); } @Override public void onMapClick(LatLng point) { boolean contains = false; for (Polygon p : polygonList) { contains = PolyUtil.containsLocation(point, p.getPoints(), false); if (contains) break; } Toast.makeText(getActivity(), "Click in polygon? " + contains, Toast.LENGTH_SHORT).show(); } @Override protected void onMapReady(View view, Bundle savedInstanceState) { googleMap.setOnMapClickListener(this); addMyPolygons(); }
虽然user1504495已经回答了,因为我已经使用它了。 但是,而不是使用整个地图实用程序库使用此方法。
从你的活动class传递相应的参数:
if (area.containsLocation(Touchablelatlong, listLatlong, true)) isMarkerINSide = true; else isMarkerINSide = false;
并把下面的一个单独的课:
/** * Computes whether the given point lies inside the specified polygon. * The polygon is always cosidered closed, regardless of whether the last point equals * the first or not. * Inside is defined as not containing the South Pole -- the South Pole is always outside. * The polygon is formed of great circle segments if geodesic is true, and of rhumb * (loxodromic) segments otherwise. */ public static boolean containsLocation(LatLng point, List<LatLng> polygon, boolean geodesic) { final int size = polygon.size(); if (size == 0) { return false; } double lat3 = toRadians(point.latitude); double lng3 = toRadians(point.longitude); LatLng prev = polygon.get(size - 1); double lat1 = toRadians(prev.latitude); double lng1 = toRadians(prev.longitude); int nIntersect = 0; for (LatLng point2 : polygon) { double dLng3 = wrap(lng3 - lng1, -PI, PI); // Special case: point equal to vertex is inside. if (lat3 == lat1 && dLng3 == 0) { return true; } double lat2 = toRadians(point2.latitude); double lng2 = toRadians(point2.longitude); // Offset longitudes by -lng1. if (intersects(lat1, lat2, wrap(lng2 - lng1, -PI, PI), lat3, dLng3, geodesic)) { ++nIntersect; } lat1 = lat2; lng1 = lng2; } return (nIntersect & 1) != 0; } /** * Wraps the given value into the inclusive-exclusive interval between min and max. * @param n The value to wrap. * @param min The minimum. * @param max The maximum. */ static double wrap(double n, double min, double max) { return (n >= min && n < max) ? n : (mod(n - min, max - min) + min); } /** * Returns the non-negative remainder of x / m. * @param x The operand. * @param m The modulus. */ static double mod(double x, double m) { return ((x % m) + m) % m; } /** * Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment * (lat1, lng1) to (lat2, lng2). * Longitudes are offset by -lng1; the implicit lng1 becomes 0. */ private static boolean intersects(double lat1, double lat2, double lng2, double lat3, double lng3, boolean geodesic) { // Both ends on the same side of lng3. if ((lng3 >= 0 && lng3 >= lng2) || (lng3 < 0 && lng3 < lng2)) { return false; } // Point is South Pole. if (lat3 <= -PI/2) { return false; } // Any segment end is a pole. if (lat1 <= -PI/2 || lat2 <= -PI/2 || lat1 >= PI/2 || lat2 >= PI/2) { return false; } if (lng2 <= -PI) { return false; } double linearLat = (lat1 * (lng2 - lng3) + lat2 * lng3) / lng2; // Northern hemisphere and point under lat-lng line. if (lat1 >= 0 && lat2 >= 0 && lat3 < linearLat) { return false; } // Southern hemisphere and point above lat-lng line. if (lat1 <= 0 && lat2 <= 0 && lat3 >= linearLat) { return true; } // North Pole. if (lat3 >= PI/2) { return true; } // Compare lat3 with latitude on the GC/Rhumb segment corresponding to lng3. // Compare through a strictly-increasing function (tan() or mercator()) as convenient. return geodesic ? tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) : mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3); } /** * Returns tan(latitude-at-lng3) on the great circle (lat1, lng1) to (lat2, lng2). lng1==0. * See http://williams.best.vwh.net/avform.htm . */ private static double tanLatGC(double lat1, double lat2, double lng2, double lng3) { return (tan(lat1) * sin(lng2 - lng3) + tan(lat2) * sin(lng3)) / sin(lng2); } /** * Returns mercator Y corresponding to latitude. * See http://en.wikipedia.org/wiki/Mercator_projection . */ static double mercator(double lat) { return log(tan(lat * 0.5 + PI/4)); } /** * Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0. */ private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) { return (mercator(lat1) * (lng2 - lng3) + mercator(lat2) * lng3) / lng2; }
只是为了保持一致性 – 当用户点击多边形(或其他覆盖层)时,onMapClick不会被调用,并且在javadoc中提到。
在MapFragment处理它之前,我做了一个解决方法来拦截点击事件,并且将点投影到地图坐标,并检查点是否在任何多边形内,如其他答案中的build议。
在这里看到更多细节