]> granicus.if.org Git - postgis/commitdiff
branch 2.5: Geography Distance inconsistent with Intersects
authorPaul Ramsey <pramsey@cleverelephant.ca>
Tue, 13 Aug 2019 16:59:08 +0000 (16:59 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Tue, 13 Aug 2019 16:59:08 +0000 (16:59 +0000)
References #4480

git-svn-id: http://svn.osgeo.org/postgis/branches/2.5@17705 b70326c6-7e19-0410-871a-916f4a2858ee

NEWS
liblwgeom/cunit/cu_geodetic.c
liblwgeom/g_box.c
liblwgeom/liblwgeom.h.in
liblwgeom/lwgeodetic.c
liblwgeom/lwgeodetic.h
liblwgeom/lwgeodetic_tree.c
liblwgeom/lwgeodetic_tree.h
postgis/geography_measurement_trees.c

diff --git a/NEWS b/NEWS
index 59ec527d224eec0e71429b2e1e313437c4ed603f..912b8bf80d59bc28c22611f3cbbe165916037e28 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,7 @@ PostGIS 2.5.3
   - #4459, Fix ST_Subdivide crash on intermediate EMPTY (Darafei Praliaskouski)
   - #4470, ST_GeomFromGeoJSON crash on empty rings (Darafei Praliaskouski)
   - #4420, update path does not exists for address_standardizer extension (Regina Obe)
+  - #4480, Geography Distance inconsistent with Intersects (Paul Ramsey)
 
 
 PostGIS 2.5.2
index cfc3165768f3f6f0493d7c89b98560d877545100..892d90d10e9a6687bc9ead463a90f17b7a57f9e3 100644 (file)
@@ -1135,7 +1135,7 @@ static void test_lwpoly_covers_point2d(void)
        lwgeom_free(lwg);
 
        /* Great big ring */
-       lwg = lwgeom_from_wkt("POLYGON((-40.0 52.0, 102.0 -6.0, -67.0 -29.0, -40.0 52.0))", LW_PARSER_CHECK_NONE);
+       lwg = lwgeom_from_wkt("POLYGON((-40.0 52.0, -67.0 -29.0, 102.0 -6.0, -40.0 52.0))", LW_PARSER_CHECK_NONE);
        poly = (LWPOLY*)lwg;
        pt_to_test.x = 4.0;
        pt_to_test.y = 11.0;
index 33914bee44961884114348c07c163253e0a01d81..7a4703bcccc9ec4680cd8288d6bf0f2c874d02c4 100644 (file)
@@ -254,7 +254,7 @@ int gbox_init_point3d(const POINT3D *p, GBOX *gbox)
 int gbox_contains_point3d(const GBOX *gbox, const POINT3D *pt)
 {
        if ( gbox->xmin > pt->x || gbox->ymin > pt->y || gbox->zmin > pt->z ||
-               gbox->xmax < pt->x || gbox->ymax < pt->y || gbox->zmax < pt->z )
+            gbox->xmax < pt->x || gbox->ymax < pt->y || gbox->zmax < pt->z )
        {
                return LW_FALSE;
        }
index 94ae57c23027903ef4dcf5844d720194200d5c33..2b1c255c5e61dd71aaf03509c726a9127bdb83d1 100644 (file)
@@ -1829,7 +1829,7 @@ extern int ptarray_calculate_gbox_cartesian(const POINTARRAY *pa, GBOX *gbox );
 /**
 * Calculate a spherical point that falls outside the geocentric gbox
 */
-void gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside);
+int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside);
 
 /**
 * Create a new gbox with the dimensionality indicated by the flags. Caller
index 249270a34da0689a5e3fa07b51c923813015a30c..c5dfb8b429e3c382710b478bddc5db2fc5ed4e90 100644 (file)
@@ -323,53 +323,75 @@ static int gbox_check_poles(GBOX *gbox)
        lwfree(gbox_str);
 #endif
        /* Z axis */
-       if ( gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
-            gbox->ymin < 0.0 && gbox->ymax > 0.0 )
+       if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
+           gbox->ymin < 0.0 && gbox->ymax > 0.0)
        {
-               if ( (gbox->zmin + gbox->zmax) > 0.0 )
+               /* Extrema lean positive */
+               if ((gbox->zmin > 0.0) && (gbox->zmax > 0.0))
                {
                        LWDEBUG(4, "enclosed positive z axis");
                        gbox->zmax = 1.0;
                }
-               else
+               /* Extrema lean negative */
+               else if ((gbox->zmin < 0.0) && (gbox->zmax < 0.0))
                {
                        LWDEBUG(4, "enclosed negative z axis");
                        gbox->zmin = -1.0;
                }
+               /* Extrema both sides! */
+               else
+               {
+                       LWDEBUG(4, "enclosed both z axes");
+                       gbox->zmin = -1.0;
+                       gbox->zmax = 1.0;
+               }
                rv = LW_TRUE;
        }
 
        /* Y axis */
-       if ( gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
-            gbox->zmin < 0.0 && gbox->zmax > 0.0 )
+       if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
+           gbox->zmin < 0.0 && gbox->zmax > 0.0)
        {
-               if ( gbox->ymin + gbox->ymax > 0.0 )
+               if ((gbox->ymin > 0.0) && (gbox->ymax > 0.0))
                {
                        LWDEBUG(4, "enclosed positive y axis");
                        gbox->ymax = 1.0;
                }
-               else
+               else if ((gbox->ymin < 0.0) && (gbox->ymax < 0.0))
                {
                        LWDEBUG(4, "enclosed negative y axis");
+                       gbox->ymax = -1.0;
+               }
+               else
+               {
+                       LWDEBUG(4, "enclosed both y axes");
+                       gbox->ymax = 1.0;
                        gbox->ymin = -1.0;
                }
                rv = LW_TRUE;
        }
 
        /* X axis */
-       if ( gbox->ymin < 0.0 && gbox->ymax > 0.0 &&
-            gbox->zmin < 0.0 && gbox->zmax > 0.0 )
+       if (gbox->ymin < 0.0 && gbox->ymax > 0.0 &&
+           gbox->zmin < 0.0 && gbox->zmax > 0.0)
        {
-               if ( gbox->xmin + gbox->xmax > 0.0 )
+               if ((gbox->xmin > 0.0) && (gbox->xmax > 0.0))
                {
                        LWDEBUG(4, "enclosed positive x axis");
                        gbox->xmax = 1.0;
                }
-               else
+               else if ((gbox->xmin < 0.0) && (gbox->xmax < 0.0))
                {
                        LWDEBUG(4, "enclosed negative x axis");
                        gbox->xmin = -1.0;
                }
+               else
+               {
+                       LWDEBUG(4, "enclosed both x axes");
+                       gbox->xmax = 1.0;
+                       gbox->xmin = -1.0;
+               }
+
                rv = LW_TRUE;
        }
 
@@ -462,7 +484,7 @@ static void vector_difference(const POINT3D *a, const POINT3D *b, POINT3D *n)
 /**
 * Scale a vector out by a factor
 */
-static void vector_scale(POINT3D *n, double scale)
+void vector_scale(POINT3D *n, double scale)
 {
        n->x *= scale;
        n->y *= scale;
@@ -1450,28 +1472,84 @@ int edge_calculate_gbox(const POINT3D *A1, const POINT3D *A2, GBOX *gbox)
        return LW_SUCCESS;
 }
 
-void lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside)
+/*
+* When we have a globe-covering gbox but we still want an outside
+* point, we do this Very Bad Hack, which is look at the first two points
+* in the ring and then nudge a point to the left of that arc.
+* There is an assumption of convexity built in there, as well as that
+* the shape doesn't have a sharp reversal in it. It's ugly, but
+* it fixes some common cases (large selection polygons) that users
+* are generating. At some point all of geodetic needs a clean-room
+* rewrite.
+* There is also an assumption of CCW exterior ring, which is how the
+* GeoJSON spec defined geographic ring orientation.
+*/
+static int lwpoly_pt_outside_hack(const LWPOLY *poly, POINT2D *pt_outside)
+{
+       GEOGRAPHIC_POINT g1, g2, gSum;
+       POINT4D p1, p2;
+       POINT3D q1, q2, qMid, qCross, qSum;
+       POINTARRAY *pa;
+       if (lwgeom_is_empty((LWGEOM*)poly))
+               return LW_FAILURE;
+       if (poly->nrings < 1)
+               return LW_FAILURE;
+       pa = poly->rings[0];
+       if (pa->npoints < 2)
+               return LW_FAILURE;
+
+       /* First two points of ring */
+       getPoint4d_p(pa, 0, &p1);
+       getPoint4d_p(pa, 1, &p2);
+       /* Convert to XYZ unit vectors */
+       geographic_point_init(p1.x, p1.y, &g1);
+       geographic_point_init(p2.x, p2.y, &g2);
+       geog2cart(&g1, &q1);
+       geog2cart(&g2, &q2);
+       /* Mid-point of first two points */
+       vector_sum(&q1, &q2, &qMid);
+       normalize(&qMid);
+       /* Cross product of first two points (perpendicular) */
+       cross_product(&q1, &q2, &qCross);
+       normalize(&qCross);
+       /* Invert it to put it outside, and scale down */
+       vector_scale(&qCross, -0.2);
+       /* Project midpoint to the right */
+       vector_sum(&qMid, &qCross, &qSum);
+       normalize(&qSum);
+       /* Convert back to lon/lat */
+       cart2geog(&qSum, &gSum);
+       pt_outside->x = rad2deg(gSum.lon);
+       pt_outside->y = rad2deg(gSum.lat);
+       return LW_SUCCESS;
+}
+
+int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside)
 {
+       int rv;
        /* Make sure we have boxes */
        if ( poly->bbox )
        {
-               gbox_pt_outside(poly->bbox, pt_outside);
-               return;
+               rv = gbox_pt_outside(poly->bbox, pt_outside);
        }
        else
        {
                GBOX gbox;
                lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox);
-               gbox_pt_outside(&gbox, pt_outside);
-               return;
+               rv = gbox_pt_outside(&gbox, pt_outside);
        }
+
+       if (rv == LW_FALSE)
+               return lwpoly_pt_outside_hack(poly, pt_outside);
+
+       return rv;
 }
 
 /**
 * Given a unit geocentric gbox, return a lon/lat (degrees) coordinate point point that is
 * guaranteed to be outside the box (and therefore anything it contains).
 */
-void gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
+int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
 {
        double grow = M_PI / 180.0 / 60.0; /* one arc-minute */
        int i;
@@ -1538,7 +1616,7 @@ void gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
                                pt_outside->x = rad2deg(g.lon);
                                pt_outside->y = rad2deg(g.lat);
                                LWDEBUGF(4, "returning POINT(%.8g %.8g) as outside point", pt_outside->x, pt_outside->y);
-                               return;
+                               return LW_SUCCESS;
                        }
                }
 
@@ -1547,8 +1625,8 @@ void gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
        }
 
        /* This should never happen! */
-       lwerror("BOOM! Could not generate outside point!");
-       return;
+       // lwerror("BOOM! Could not generate outside point!");
+       return LW_FAILURE;
 }
 
 
@@ -2473,7 +2551,7 @@ int lwpoly_covers_point2d(const LWPOLY *poly, const POINT2D *pt_to_test)
        }
 
        /* Calculate our outside point from the gbox */
-       gbox_pt_outside(&gbox, &pt_outside);
+       lwpoly_pt_outside(poly, &pt_outside);
 
        LWDEBUGF(4, "pt_outside POINT(%.18g %.18g)", pt_outside.x, pt_outside.y);
        LWDEBUGF(4, "pt_to_test POINT(%.18g %.18g)", pt_to_test->x, pt_to_test->y);
@@ -2807,6 +2885,7 @@ int getPoint2d_p_ro(const POINTARRAY *pa, uint32_t n, POINT2D **point)
        return LW_SUCCESS;
 }
 
+
 int ptarray_calculate_gbox_geodetic(const POINTARRAY *pa, GBOX *gbox)
 {
        uint32_t i;
index f744b0fad05613770172a09bacb38a158c161584..739f073f1d7cd485c7c2d057a7ed781c384f0ded 100644 (file)
@@ -125,7 +125,7 @@ int lwpoly_covers_lwline(const LWPOLY *poly, const LWLINE *line);
 int lwline_covers_lwline(const LWLINE* lwline1, const LWLINE* lwline2);
 int lwline_covers_lwpoint(const LWLINE* lwline, const LWPOINT* lwpoint);
 int lwpoly_intersects_line(const LWPOLY* lwpoly, const POINTARRAY* line);
-void lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside);
+int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside);
 int ptarray_point_in_ring(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test);
 double ptarray_area_sphere(const POINTARRAY *pa);
 double latitude_degrees_normalize(double lat);
@@ -137,6 +137,7 @@ void point_shift(GEOGRAPHIC_POINT *p, double shift);
 double longitude_radians_normalize(double lon);
 double latitude_radians_normalize(double lat);
 void vector_sum(const POINT3D *a, const POINT3D *b, POINT3D *n);
+void vector_scale(POINT3D *a, double s);
 double vector_angle(const POINT3D* v1, const POINT3D* v2);
 void vector_rotate(const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n);
 void normalize(POINT3D *p);
index 112e0489658be0fdb9f7ad2ee591bc0394f84fb1..e0d5058a8cf54da4fe83797d5785d628bd011afa 100644 (file)
@@ -472,6 +472,19 @@ int circ_tree_get_point(const CIRC_NODE* node, POINT2D* pt)
        }
 }
 
+int circ_tree_get_point_outside(const CIRC_NODE* node, POINT2D* pt)
+{
+       POINT3D center3d;
+       GEOGRAPHIC_POINT g;
+       if (node->radius >= M_PI) return LW_FAILURE;
+       geog2cart(&(node->center), &center3d);
+       vector_scale(&center3d, -1.0);
+       cart2geog(&center3d, &g);
+       pt->x = rad2deg(g.lon);
+       pt->y = rad2deg(g.lat);
+       return LW_SUCCESS;
+}
+
 
 /**
 * Walk the tree and count intersections between the stab line and the edges.
index e9cefee0f16925ee15f1898cfc9b015242388c26..4acef980fad4152e7e1f72aa7f3c6ac5ec6654b7 100644 (file)
@@ -54,6 +54,7 @@ int circ_tree_contains_point(const CIRC_NODE* node, const POINT2D* pt, const POI
 double circ_tree_distance_tree(const CIRC_NODE* n1, const CIRC_NODE* n2, const SPHEROID *spheroid, double threshold);
 CIRC_NODE* lwgeom_calculate_circ_tree(const LWGEOM* lwgeom);
 int circ_tree_get_point(const CIRC_NODE* node, POINT2D* pt);
+int circ_tree_get_point_outside(const CIRC_NODE* node, POINT2D* pt);
 
 #endif /* _LWGEODETIC_TREE_H */
 
index a992e3b962895eeb09672819e67354ba1ab4002f..d73b2c2b0f098da88b5c45caaf5e80b30adfd9a2 100644 (file)
@@ -95,7 +95,6 @@ GetCircTreeGeomCache(FunctionCallInfo fcinfo, const GSERIALIZED *g1, const GSERI
        return (CircTreeGeomCache*)GetGeomCache(fcinfo, &CircTreeCacheMethods, g1, g2);
 }
 
-
 static int
 CircTreePIP(const CIRC_NODE* tree1, const GSERIALIZED* g1, const POINT4D* in_point)
 {
@@ -137,7 +136,10 @@ CircTreePIP(const CIRC_NODE* tree1, const GSERIALIZED* g1, const POINT4D* in_poi
                        pt2d_inside.x = in_point->x;
                        pt2d_inside.y = in_point->y;
                        /* Calculate a definitive outside point */
-                       gbox_pt_outside(&gbox1, &pt2d_outside);
+                       if (gbox_pt_outside(&gbox1, &pt2d_outside) == LW_FAILURE)
+                               if (circ_tree_get_point_outside(tree1, &pt2d_outside) == LW_FAILURE)
+                                       lwpgerror("%s: Unable to generate outside point!", __func__);
+
                        POSTGIS_DEBUGF(3, "p2d_inside=POINT(%g %g) p2d_outside=POINT(%g %g)", pt2d_inside.x, pt2d_inside.y, pt2d_outside.x, pt2d_outside.y);
                        /* Test the candidate point for strict containment */
                        POSTGIS_DEBUG(3, "calling circ_tree_contains_point for PiP test");