【问题标题】:How do I check if a longitude/latitude point is within a range of coordinates?如何检查经度/纬度点是否在坐标范围内?
【发布时间】:2012-07-15 15:44:30
【问题描述】:

我有许多构成多边形区域的经度和纬度坐标。我还有一个经度和纬度坐标来定义车辆的位置。如何检查车辆是否位于多边形区域内?

【问题讨论】:

    标签: php math geolocation coordinates latitude-longitude


    【解决方案1】:

    这本质上是球体上的Point in polygon 问题。您可以修改光线投射算法,使其使用大圆弧而不是线段。

    1. 对于构成多边形的每一对相邻坐标,在它们之间画一个大圆段。
    2. 选择一个不在多边形区域内的参考点。
    3. 绘制一个从参考点开始到车辆点结束的大圆段。计算该线段跨越多边形线段的次数。如果总次数是奇数,则车辆在多边形内。如果是偶数,则车辆在多边形之外。

    或者,如果坐标和车辆足够接近,并且不在两极或国际日期变更线附近,您可以假装地球是平的,并使用经度和纬度作为简单的 x 和 y 坐标。这样,您可以将光线投射算法与简单的线段一起使用。如果您对非欧几里得几何感到不满意,这是最好的选择,但是由于圆弧会变形,因此您会在多边形的边界周围产生一些变形。

    编辑:更多关于球体上的几何。

    一个大圆可以通过垂直于圆所在平面的向量来识别(AKA,normal vector

    class Vector{
        double x;
        double y;
        double z;
    };
    
    class GreatCircle{
        Vector normal;
    }
    

    任何不是antipodal 的两个纬度/经度坐标都共享一个大圆。要找到这个大圆,请将坐标转换为穿过地球中心的线。这两条线的cross product是坐标大圆的法向量。

    //arbitrarily defining the north pole as (0,1,0) and (0'N, 0'E) as (1,0,0)
    //lattidues should be in [-90, 90] and longitudes in [-180, 180]
    //You'll have to convert South lattitudes and East longitudes into their negative North and West counterparts.
    Vector lineFromCoordinate(Coordinate c){
        Vector ret = new Vector();
        //given:
        //tan(lat) == y/x
        //tan(long) == z/x
        //the Vector has magnitude 1, so sqrt(x^2 + y^2 + z^2) == 1
        //rearrange some symbols, solving for x first...
        ret.x = 1.0 / math.sqrt(tan(c.lattitude)^2 + tan(c.longitude)^2 + 1);
        //then for y and z
        ret.y = ret.x * tan(c.lattitude);
        ret.z = ret.x * tan(c.longitude);
        return ret;
    }
    
    Vector Vector::CrossProduct(Vector other){
        Vector ret = new Vector();
        ret.x = this.y * other.z - this.z * other.y;
        ret.y = this.z * other.x - this.x * other.z;
        ret.z = this.x * other.y - this.y * other.x;
        return ret;
    }
    
    GreatCircle circleFromCoordinates(Coordinate a, Coordinate b){
        Vector a = lineFromCoordinate(a);
        Vector b = lineFromCoordinate(b);
        GreatCircle ret = new GreatCircle();
        ret.normal = a.CrossProdct(b);
        return ret;
    }
    

    两个大圆在球体上的两个点相交。圆圈的叉积形成一个通过这些点之一的向量。该向量的对映点经过另一点。

    Vector intersection(GreatCircle a, GreatCircle b){
        return a.normal.CrossProduct(b.normal);
    }
    
    Vector antipode(Vector v){
        Vector ret = new Vector();
        ret.x = -v.x;
        ret.y = -v.y;
        ret.z = -v.z;
        return ret;
    }
    

    一个大圆段可以用穿过该段起点和终点的向量来表示。

    class GreatCircleSegment{
        Vector start;
        Vector end;
        Vector getNormal(){return start.CrossProduct(end);}
        GreatCircle getWhole(){return new GreatCircle(this.getNormal());}
    };
    
    GreatCircleSegment segmentFromCoordinates(Coordinate a, Coordinate b){
        GreatCircleSegment ret = new GreatCircleSegment();
        ret.start = lineFromCoordinate(a);
        ret.end = lineFromCoordinate(b);
        return ret;
    }
    

    您可以使用dot product 测量大圆弧段的圆弧大小或任意两个向量之间的角度。

    double Vector::DotProduct(Vector other){
        return this.x*other.x + this.y*other.y + this.z*other.z;
    }
    
    double Vector::Magnitude(){
        return math.sqrt(pow(this.x, 2) + pow(this.y, 2) + pow(this.z, 2));
    }
    
    //for any two vectors `a` and `b`, 
    //a.DotProduct(b) = a.magnitude() * b.magnitude() * cos(theta)
    //where theta is the angle between them.
    double angleBetween(Vector a, Vector b){
        return math.arccos(a.DotProduct(b) / (a.Magnitude() * b.Magnitude()));
    }
    

    您可以通过以下方式测试大圆段 a 是否与大圆 b 相交:

    • 找到向量ca的整个大圆与b的交点。
    • 找到向量d,即c的对映点。
    • 如果c介于a.starta.end之间,或者d介于a.starta.end之间,则ab相交。

     

    //returns true if Vector x lies between Vectors a and b.
    //note that this function only gives sensical results if the three vectors are coplanar.
    boolean liesBetween(Vector x, Vector a, Vector b){
        return angleBetween(a,x) + angleBetween(x,b) == angleBetween(a,b);
    }
    
    bool GreatCircleSegment::Intersects(GreatCircle b){
        Vector c = intersection(this.getWhole(), b);
        Vector d = antipode(c);
        return liesBetween(c, this.start, this.end) or liesBetween(d, this.start, this.end);
    }
    

    两个大圆段ab 相交,如果:

    • ab 的整个大圈相交
    • ba 的整个大圈相交

     

    bool GreatCircleSegment::Intersects(GreatCircleSegment b){
        return this.Intersects(b.getWhole()) and b.Intersects(this.getWhole());
    }
    

    现在您可以构建多边形并计算参考线经过它的次数。

    bool liesWithin(Array<Coordinate> polygon, Coordinate pointNotLyingInsidePolygon, Coordinate vehiclePosition){
        GreatCircleSegment referenceLine = segmentFromCoordinates(pointNotLyingInsidePolygon, vehiclePosition);
        int intersections = 0;
        //iterate through all adjacent polygon vertex pairs
        //we iterate i one farther than the size of the array, because we need to test the segment formed by the first and last coordinates in the array
        for(int i = 0; i < polygon.size + 1; i++){
            int j = (i+1) % polygon.size;
            GreatCircleSegment polygonEdge = segmentFromCoordinates(polygon[i], polygon[j]);
            if (referenceLine.Intersects(polygonEdge)){
                intersections++;
            }
        }
        return intersections % 2 == 1;
    }
    

    【讨论】:

    • 知道如何选择多边形之外的点吗?在球体上并不像在平面上那么明显。
    • @LisuBB 我认为“外部”和“内部”在球体几何的上下文中定义不明确。例如,假设您有一个沿赤道直线延伸的多边形。北半球在多边形内,还是南半球?当我最初写这个答案时,我想我认为 OP 会知道一个参考点,由于某些外部约束,该参考点肯定不在多边形内。例如,如果您正在编写汽车导航系统,您的参考点可以是the oceanic pole of inaccessibility
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-27
    • 2021-09-04
    • 1970-01-01
    • 1970-01-01
    • 2015-02-18
    • 1970-01-01
    • 2019-10-08
    相关资源
    最近更新 更多