Нахождение точек, содержащихся в пути в Android - PullRequest
9 голосов
/ 12 августа 2011

Есть ли причина, по которой они решили не добавлять метод содержимого (для Path) в Android?

Я хочу знать, какие точки у меня есть на Пути, и надеялся, что это было проще, чем здесь:

Как узнать, содержит ли заданная точка заданную точку?

Было бы лучше для меня создать ArrayList и добавить целые числа в массив? (Я проверяю точки только один раз в контрольном утверждении) Т.е. if(myPath.contains(x,y)

Пока мои варианты:

  • Использование региона
  • Использование ArrayList
  • Расширение класса
  • Ваше предложение

Я просто ищу наиболее эффективный способ, которым я должен идти по этому поводу

Ответы [ 4 ]

14 голосов
/ 12 августа 2011

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

В Java есть класс Polygon с методом contains(), который сделает все очень просто. К сожалению, класс java.awt.Polygon не поддерживается в Android. Однако я смог найти человека, который написал эквивалентный класс .

Не думаю, что вы можете получить отдельные точки, составляющие путь, из класса Android Path, поэтому вам придется хранить данные другим способом.

Класс использует алгоритм пересечения чисел, чтобы определить, находится ли точка внутри заданного списка точек.

/**
 * Minimum Polygon class for Android.
 */
public class Polygon
{
    // Polygon coodinates.
    private int[] polyY, polyX;

    // Number of sides in the polygon.
    private int polySides;

    /**
     * Default constructor.
     * @param px Polygon y coods.
     * @param py Polygon x coods.
     * @param ps Polygon sides count.
     */
    public Polygon( int[] px, int[] py, int ps )
    {
        polyX = px;
        polyY = py;
        polySides = ps;
    }

    /**
     * Checks if the Polygon contains a point.
     * @see "http://alienryderflex.com/polygon/"
     * @param x Point horizontal pos.
     * @param y Point vertical pos.
     * @return Point is in Poly flag.
     */
    public boolean contains( int x, int y )
    {
        boolean oddTransitions = false;
        for( int i = 0, j = polySides -1; i < polySides; j = i++ )
        {
            if( ( polyY[ i ] < y && polyY[ j ] >= y ) || ( polyY[ j ] < y && polyY[ i ] >= y ) )
            {
                if( polyX[ i ] + ( y - polyY[ i ] ) / ( polyY[ j ] - polyY[ i ] ) * ( polyX[ j ] - polyX[ i ] ) < x )
                {
                    oddTransitions = !oddTransitions;          
                }
            }
        }
        return oddTransitions;
    }  
}
7 голосов
/ 29 апреля 2012

Я просто хотел бы прокомментировать ответ @theisenp: код имеет целочисленные массивы, и если вы посмотрите на веб-страницу описания алгоритма, он предостерегает от использования целых чисел вместо плавающей запятой.

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

Изменив все на плавающую точку, я избавился от этой ошибки.

6 голосов
/ 20 декабря 2011

Пробовал другой ответ, но он дал ошибочный результат для моего случая. Не удосужился найти точную причину, но сделал свой прямой перевод из алгоритма: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

Теперь код выглядит так:

/**
 * Minimum Polygon class for Android.
 */
public class Polygon
{
    // Polygon coodinates.
    private int[] polyY, polyX;

    // Number of sides in the polygon.
    private int polySides;

    /**
     * Default constructor.
     * @param px Polygon y coods.
     * @param py Polygon x coods.
     * @param ps Polygon sides count.
     */
    public Polygon( int[] px, int[] py, int ps )
    {
        polyX = px;
        polyY = py;
        polySides = ps;
    }

    /**
     * Checks if the Polygon contains a point.
     * @see "http://alienryderflex.com/polygon/"
     * @param x Point horizontal pos.
     * @param y Point vertical pos.
     * @return Point is in Poly flag.
     */
    public boolean contains( int x, int y )
    {
        boolean c = false;
        int i, j = 0;
        for (i = 0, j = polySides - 1; i < polySides; j = i++) {
            if (((polyY[i] > y) != (polyY[j] > y))
                && (x < (polyX[j] - polyX[i]) * (y - polyY[i]) / (polyY[j] - polyY[i]) + polyX[i]))
            c = !c;
        }
        return c;
    }  
}
0 голосов
/ 28 ноября 2015

Для полноты я хочу сделать пару замечаний здесь:

Начиная с API 19, существует операция пересечения для путей. Вы можете создать очень маленький квадратный путь вокруг вашей контрольной точки, пересечь его с путем и посмотреть, является ли результат пустым или нет.

Вы можете преобразовать пути в регионы и выполнить операцию contains () . Однако регионы работают в целочисленных координатах, и я думаю, что они используют преобразованные (пиксельные) координаты, поэтому вам придется работать с этим. Я также подозреваю, что процесс преобразования требует больших вычислительных ресурсов.

Алгоритм пересечения ребер, который опубликовал Ганс, хорош и быстр, но вы должны быть очень осторожны в определенных угловых случаях, например, когда луч проходит непосредственно через вершину или пересекает горизонтальное ребро, или когда ошибка округления это проблема, которая всегда есть.

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * '*' * '' '' '' '' '' '' '' '' '' '' в значительной степени ''] не требует больших усилий;

Эта статья Дэна Сандея дает гибридный алгоритм, который так же точен, как число обмоток, но так же прост в вычислительном отношении, как и алгоритм приведения лучей. Это поразило меня, как это было элегантно.

Мой код

Это некоторый код, который я недавно написал на Java, который обрабатывает путь, состоящий из обоих отрезков и дуг. (Также круги, но это полные пути сами по себе, так что это своего рода вырожденный случай.)

package org.efalk.util;

/**
 * Utility: determine if a point is inside a path.
 */
public class PathUtil {
    static final double RAD = (Math.PI/180.);
    static final double DEG = (180./Math.PI);

    protected static final int LINE = 0;
    protected static final int ARC = 1;
    protected static final int CIRCLE = 2;

    /**
     * Used to cache the contents of a path for pick testing.  For a
     * line segment, x0,y0,x1,y1 are the endpoints of the line.  For
     * a circle (ellipse, actually), x0,y0,x1,y1 are the bounding box
     * of the circle (this is how Android and X11 like to represent
     * circles).  For an arc, x0,y0,x1,y1 are the bounding box, a1 is
     * the start angle (degrees CCW from the +X direction) and a1 is
     * the sweep angle (degrees CCW).
     */
    public static class PathElement {
        public int type;
        public float x0,y0,x1,y1;   // Endpoints or bounding box
        public float a0,a1;         // Arcs and circles
    }

    /**
     * Determine if the given point is inside he given path.
     */
    public static boolean inside(float x, float y, PathElement[] path) {
        // Based on algorithm by Dan Sunday, but allows for arc segments too.         
        // http://geomalgorithms.com/a03-_inclusion.html
        int wn = 0;
        // loop through all edges of the polygon
        // An upward crossing requires y0 <= y and y1 > y
        // A downward crossing requires y0 > y and y1 <= y
        for (PathElement pe : path) {
            switch (pe.type) {
              case LINE:
                if (pe.x0 < x && pe.x1 < x) // left
                    break;
                if (pe.y0 <= y) {           // start y <= P.y
                    if (pe.y1 > y) {        // an upward crossing
                        if (isLeft(pe, x, y) > 0) // P left of  edge
                            ++wn;                // have  a valid up intersect
                    }
                }
                else {                              // start y > P.y
                    if (pe.y1 <= y) {       // a downward crossing
                        if (isLeft(pe, x, y) < 0) // P right of  edge
                            --wn;                // have  a valid down intersect
                    }
                }
                break;
              case ARC:
                wn += arcCrossing(pe, x, y);
                break;
              case CIRCLE:
                // This should be the only element in the path, so test it
                // and get out.
                float rx = (pe.x1-pe.x0)/2;
                float ry = (pe.y1-pe.y0)/2;
                float xc = (pe.x1+pe.x0)/2;
                float yc = (pe.y1+pe.y0)/2;
                return (x-xc)*(x-xc)/rx*rx + (y-yc)*(y-yc)/ry*ry <= 1;
            }
        }
        return wn != 0;
    }

    /**
     * Return >0 if p is left of line p0-p1; <0 if to the right; 0 if
     * on the line.
     */
    private static float
    isLeft(float x0, float y0, float x1, float y1, float x, float y)
    {
        return (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);
    }

    private static float isLeft(PathElement pe, float x, float y) {
        return isLeft(pe.x0,pe.y0, pe.x1,pe.y1, x,y);
    }

    /**
     * Determine if an arc segment crosses the test ray up or down, or not
     * at all.
     * @return winding number increment:
     *      +1 upward crossing
     *       0 no crossing
     *      -1 downward crossing
     */
    private static int arcCrossing(PathElement pe, float x, float y) {
        // Look for trivial reject cases first.
        if (pe.x1 < x || pe.y1 < y || pe.y0 > y) return 0;

        // Find the intersection of the test ray with the arc. This consists
        // of finding the intersection(s) of the line with the ellipse that
        // contains the arc, then determining if the intersection(s)
        // are within the limits of the arc.
        // Since we're mostly concerned with whether or not there *is* an
        // intersection, we have several opportunities to punt.
        // An upward crossing requires y0 <= y and y1 > y
        // A downward crossing requires y0 > y and y1 <= y
        float rx = (pe.x1-pe.x0)/2;
        float ry = (pe.y1-pe.y0)/2;
        float xc = (pe.x1+pe.x0)/2;
        float yc = (pe.y1+pe.y0)/2;
        if (rx == 0 || ry == 0) return 0;
        if (rx < 0) rx = -rx;
        if (ry < 0) ry = -ry;
        // We start by transforming everything so the ellipse is the unit
        // circle; this simplifies the math.
        x -= xc;
        y -= yc;
        if (x > rx || y > ry || y < -ry) return 0;
        x /= rx;
        y /= ry;
        // Now find the points of intersection. This is simplified by the
        // fact that our line is horizontal. Also, by the time we get here,
        // we know there *is* an intersection.
        // The equation for the circle is x²+y² = 1. We have y, so solve
        // for x = ±sqrt(1 - y²)
        double x0 = 1 - y*y;
        if (x0 <= 0) return 0;
        x0 = Math.sqrt(x0);
        // We only care about intersections to the right of x, so
        // that's another opportunity to punt. For a CCW arc, The right
        // intersection is an upward crossing and the left intersection
        // is a downward crossing.  The reverse is true for a CW arc.
        if (x > x0) return 0;
        int wn = arcXing1(x0,y, pe.a0, pe.a1);
        if (x < -x0) wn -= arcXing1(-x0,y, pe.a0, pe.a1);
        return wn;
    }

    /**
     * Return the winding number of the point x,y on the unit circle
     * which passes through the arc segment defined by a0,a1.
     */
    private static int arcXing1(double x, float y, float a0, float a1) {
        double a = Math.atan2(y,x) * DEG;
        if (a < 0) a += 360;
        if (a1 > 0) {       // CCW
            if (a < a0) a += 360;
            return a0 + a1 > a ? 1 : 0;
        } else {            // CW
            if (a0 < a) a0 += 360;
            return a0 + a1 <= a ? -1 : 0;
        }
    }
}

Редактировать: по запросу, добавив пример кода, который использует это.

import PathUtil;
import PathUtil.PathElement;

/**
 * This class represents a single geographic area defined by a
 * circle or a list of line segments and arcs.
 */
public class Area {
    public float lat0, lon0, lat1, lon1;    // bounds
    Path path = null;
    PathElement[] pathList;

    /**
     * Return true if this point is inside the area bounds. This is
     * used to confirm touch events and may be computationally expensive.
     */
    public boolean pointInBounds(float lat, float lon) {
        if (lat < lat0 || lat > lat1 || lon < lon0 || lon > lon1)
            return false;
        return PathUtil.inside(lon, lat, pathList);
    }

    static void loadBounds() {
        int n = number_of_elements_in_input;
        path = new Path();
        pathList = new PathElement[n];
        for (Element element : elements_in_input) {
            PathElement pe = new PathElement();
            pathList[i] = pe;
            pe.type = element.type;
            switch (element.type) {
              case LINE:        // Line segment
                pe.x0 = element.x0;
                pe.y0 = element.y0;
                pe.x1 = element.x1;
                pe.y1 = element.y1;
                // Add to path, not shown here
                break;
              case ARC: // Arc segment
                pe.x0 = element.xmin;     // Bounds of arc ellipse
                pe.y0 = element.ymin;
                pe.x1 = element.xmax;
                pe.y1 = element.ymax;
                pe.a0 = a0; pe.a1 = a1;
                break;
              case CIRCLE: // Circle; hopefully the only entry here
                pe.x0 = element.xmin;    // Bounds of ellipse
                pe.y0 = element.ymin;
                pe.x1 = element.xmax;
                pe.y1 = element.ymax;
                // Add to path, not shown here
                break;
            }
        }
        path.close();
    }   
...