Обнаружение Polygon Touch Google Map API V2

Я пытаюсь выяснить, как это сделать, у меня есть карта с одним Polygon, нарисованным на ней. Поскольку API Google Maps V2 не имеет сенсорного обнаружения на многоугольнике. Мне было интересно, можно ли определить, находится ли точка касания внутри Polygon? Если да, то как, моя главная цель состоит в том, чтобы нарисовать состояние на карте, и когда пользователь закроет это состояние, он покажет больше деталей внутри пользовательского представления. На данный момент я могу захватить 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;
}

}

Проблема, с которой я столкнулась, - это касание слева от каждого полигона, пока оно не достигнет другого. Что не так с моим алгоритмом, который может вызвать эту проблему? Любая помощь будет оценена.

Ответ 1

Проблема, которую вы пытаетесь решить, - это тест Point in Polygon.

Чтобы помочь визуализировать концепцию Ray Casting:

Нарисуйте многоугольник на листе бумаги. Затем, начиная с любой случайной точки, нарисуйте прямую линию справа от страницы. Если ваша линия пересекалась с вашим полигоном нечетным числом раз, это означает, что ваша начальная точка находилась внутри полигона.


Итак, как вы это делаете в коде?

Ваш многоугольник состоит из списка вершин: 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;
}

Ответ 2

В библиотеке поддержки Google Maps теперь есть статический метод, который делает это для вас:

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

Хотя документы не упоминают это явно в руководстве, метод есть

Документация по поддержке карт.

Ответ 3

С выпуском Google Play Services 8.4.0 API Карт включил поддержку добавления OnPolygonClickListener для многоугольников. Оба полигоны, полилинии и наложения поддерживают похожие события.

Вам просто нужно позвонить GoogleMap.setOnPolygonClickListener(OnPolygonClickListener listener), чтобы настроить его, и соответственно для других слушателей (setOnPolylineClickListener, & c)

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

Хотя немного поздно, он довольно хорошо решает этот вариант использования.

Ответ 4

Вот полный рабочий пример, чтобы узнать, произошло ли прикосновение на полигоне. Некоторые из ответов сложнее, чем они должны быть. Это решение использует "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 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();
}

Ответ 5

Хотя 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;
    } 

Ответ 6

Просто для согласованности - onMapClick не вызывается, когда пользователь нажимает на многоугольник (или другое наложение), и он упоминается в javadoc.

Я сделал обходной путь, чтобы перехватить события кранов, прежде чем MapFragment обработает их, а проект укажет на координаты карты и проверит, находится ли точка внутри любого полигона, как это предлагается в другом ответе.

Подробнее здесь