java 多边形触摸检测 Google Map API V2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14405593/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 16:10:31  来源:igfitidea点击:

Polygon Touch detection Google Map API V2

javaandroidcoordinatesgoogle-maps-android-api-2point-in-polygon

提问by Dwill

I'm trying to figure out how best to do this, I have a map with one Polygondrawn on it. Since it doesn't seem as though the Google Maps API V2 has a touch detection on a Polygon. I was wonder if it is possible to detect whether the touch point is inside the Polygon? If so then how, my main goal is to outline a state on a map and when the user taps that state it will show more details inside a custom view. As of now I am able to capture the MapOnClickof the map but when the user taps inside the PolygonI want the polygon.getID()set on the Toast. I am a newbie so I apologize if I am not clear enough.

我想弄清楚如何最好地做到这一点,我有一张地图,上面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);
     }    

Ok this is what I have semi-working so far

好的,这就是我到目前为止的半工作

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

}

}

The issue that I am having is the touch is true to the left of each polygon until it reaches another one. What's wrong with my algorithm that would cause this issue? Any help would be appreciated.

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

回答by Matt

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

您要解决的问题是多边形测试中的

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 Segmentindividually, and see if your Rayintersects it

您的多边形由一系列顶点组成: 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;
}

回答by user1504495

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

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

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

地图支持库文档

回答by matiash

With the release of Google Play Services 8.4.0, the Maps API has included support for adding an OnPolygonClickListenerto Polygons. Both polygons, polylinesand overlayssupport similar events.

随着Google Play Services 8.4.0发布,Maps API 支持OnPolygonClickListener向多边形添加。这两个多边形折线覆盖支持类似的活动。

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

您只需要调用GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener)来设置它,并相应地为其他侦听器 ( 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.

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

回答by whizzle

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"

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

回答by Sagar Shah

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

尽管 user1504495 在我使用它时已简短回答。但不要使用整个Map Utility Library使用此方法。

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

回答by Mixaz

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

只是为了一致性 - 当用户点击多边形(或其他叠加层)时不会调用 onMapClick,它在 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.

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

See more details here

在此处查看更多详细信息