14

我试图弄清楚如何最好地做到这一点,我有一张地图,Polygon上面画了一张。由于 Google Maps API V2 似乎没有在多边形上进行触摸检测。我想知道是否可以检测到触摸点是否在多边形内部?如果是这样,那么如何,我的主要目标是在地图上勾勒出一个状态,当用户点击该状态时,它将在自定义视图中显示更多详细信息。到目前为止,我能够捕获MapOnClick地图,但是当用户在里面点击时,Polygon我想要polygon.getID()Toast. 我是新手,所以如果我不够清楚,我很抱歉。

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;
}

}

我遇到的问题是每个多边形左侧的触摸都是真实的,直到它到达另一个多边形。我的算法有什么问题会导致这个问题?任何帮助,将不胜感激。

4

6 回答 6

25

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;
}
于 2013-01-18T19:12:23.427 回答
16

Google 地图支持库现在有一个静态方法可以为您执行此检查:

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

虽然文档没有在指南中明确提到它,但方法就在那里

地图支持库文档

于 2015-05-11T14:48:10.030 回答
10

随着Google Play Services 8.4.0 的发布,Maps API 支持OnPolygonClickListener向多边形添加一个。多边形折线叠加都支持类似的事件。

您只需要调用GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener)来设置它,并相应地为其他侦听器(setOnPolylineClickListener, &c):

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

虽然有点晚了,但它很好地解决了这个用例。

于 2015-12-22T15:04:24.363 回答
2

虽然 user1504495 在我使用它时已经简短地回答了。但不要使用整个地图实用程序库,而是使用此方法。

从您的活动课程中相应地传递参数:

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;
    } 
于 2015-08-13T12:37:04.447 回答
1

这是一个完整的工作示例,可以了解是否在多边形上发生了触摸。有些答案比他们需要的更复杂。此解决方案使用“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();
}
于 2015-07-09T23:09:52.163 回答
-1

只是为了保持一致性 - 当用户点击多边形(或其他叠加层)时不会调用 onMapClick,它在 javadoc 中有所提及。

我做了一个解决方法,在 MapFragment 处理它们之前拦截点击事件,并将点投影到地图坐标并检查该点是否在任何多边形内,如其他答案中所建议的那样。

在此处查看更多详细信息

于 2013-09-05T11:17:11.707 回答