Polygon Touch detection Google Map API V2

后端 未结 6 2029
小蘑菇
小蘑菇 2020-11-28 11:22

I\'m trying to figure out how best to do this, I have a map with one Polygon drawn on it. Since it doesn\'t seem as though the Google Maps API V2 has a touch de

相关标签:
6条回答
  • 2020-11-28 11:59

    Just for consistency - onMapClick is not called when user taps on a polygon (or other overlay), and it's mentioned in javadoc.

    I made a workaround to intercept taps events before MapFragment handles them, and project point to map coordinates and check if the point is inside any polygon, as suggested in other answer.

    See more details here

    0 讨论(0)
  • 2020-11-28 12:01

    With the release of Google Play Services 8.4.0, the Maps API has included support for adding an OnPolygonClickListener to Polygons. Both polygons, polylines and overlays support similar events.

    You just need to call GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener) to set it up, and correspondingly for the other listeners (setOnPolylineClickListener, &c):

    map.setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() {  
        @Override  
        public void onPolygonClick(Polygon polygon) {  
            // Handle click ...  
        }  
    });  
    

    Although a bit late, it solves this use case quite nicely.

    0 讨论(0)
  • 2020-11-28 12:15

    The Google Maps Support library now has a static method that does this check for you:

    PolyUtil.containsLocation(LatLng point, List<LatLng>polygon, boolean geodesic);
    

    Although the docs don't mention it explicitly in the guide the method is there

    Maps Support Library docs

    0 讨论(0)
  • 2020-11-28 12:16

    Though user1504495 has answered in short as I have used it. But instead of using whole Map Utility Library Use this methods.

    From your activity class pass params accordingly:

    if (area.containsLocation(Touchablelatlong, listLatlong, true))
                    isMarkerINSide = true;
                else
                    isMarkerINSide = false;
    

    and put following in a Separate class :

    /**
         * 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;
        } 
    
    0 讨论(0)
  • 2020-11-28 12:17

    Here's a full working example to know if a touch happened on a polygon. Some of the answers are more complicated than they need to be. This solution uses the "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();
    }
    
    0 讨论(0)
  • 2020-11-28 12:18

    The problem you're trying to solve is the Point in Polygon test.

    To help visualize the concept of Ray Casting:

    Draw a Polygon on a piece of paper. Then, starting at any random point, draw a straight line to the right of the page. If your line intersected with your polygon an odd number of times, this means your starting point was inside the Polygon.


    So, how do you do that in code?

    Your polygon is comprised of a list of vertices: ArrayList<Geopoint> vertices. You need to look at each Line Segment individually, and see if your Ray intersects it

    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;
    }
    
    0 讨论(0)
提交回复
热议问题