]> granicus.if.org Git - postgresql/commitdiff
Correct calculations of overlap and contains operations over polygons.
authorTeodor Sigaev <teodor@sigaev.ru>
Tue, 28 Jul 2009 09:48:00 +0000 (09:48 +0000)
committerTeodor Sigaev <teodor@sigaev.ru>
Tue, 28 Jul 2009 09:48:00 +0000 (09:48 +0000)
src/backend/utils/adt/geo_ops.c
src/test/regress/expected/create_index.out
src/test/regress/expected/polygon.out
src/test/regress/sql/create_index.sql
src/test/regress/sql/polygon.sql

index 24157a53c59955f70abb2669df8f3fb57707d3da..ea7279e8c7c14d4cc8c0ff177508e15c61d016a4 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/geo_ops.c,v 1.102 2009/06/23 16:25:02 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/adt/geo_ops.c,v 1.103 2009/07/28 09:47:59 teodor Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -66,6 +66,8 @@ static bool has_interpt_sl(LSEG *lseg, LINE *line);
 static double dist_pl_internal(Point *pt, LINE *line);
 static double dist_ps_internal(Point *pt, LSEG *lseg);
 static Point *line_interpt_internal(LINE *l1, LINE *l2);
+static bool lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start);
+static Point* lseg_interpt_internal(LSEG *l1, LSEG *l2);
 
 
 /*
@@ -2352,15 +2354,9 @@ lseg_center(PG_FUNCTION_ARGS)
        PG_RETURN_POINT_P(result);
 }
 
-
-/* lseg_interpt -
- *             Find the intersection point of two segments (if any).
- */
-Datum
-lseg_interpt(PG_FUNCTION_ARGS)
+static Point*
+lseg_interpt_internal(LSEG *l1, LSEG *l2)
 {
-       LSEG       *l1 = PG_GETARG_LSEG_P(0);
-       LSEG       *l2 = PG_GETARG_LSEG_P(1);
        Point      *result;
        LINE            tmp1,
                                tmp2;
@@ -2372,7 +2368,7 @@ lseg_interpt(PG_FUNCTION_ARGS)
        line_construct_pts(&tmp2, &l2->p[0], &l2->p[1]);
        result = line_interpt_internal(&tmp1, &tmp2);
        if (!PointerIsValid(result))
-               PG_RETURN_NULL();
+               return NULL;
 
        /*
         * If the line intersection point isn't within l1 (or equivalently l2),
@@ -2380,7 +2376,10 @@ lseg_interpt(PG_FUNCTION_ARGS)
         */
        if (!on_ps_internal(result, l1) ||
                !on_ps_internal(result, l2))
-               PG_RETURN_NULL();
+       {
+               pfree(result);
+               return NULL;
+       }
 
        /*
         * If there is an intersection, then check explicitly for matching
@@ -2400,6 +2399,23 @@ lseg_interpt(PG_FUNCTION_ARGS)
                result->y = l1->p[1].y;
        }
 
+       return result;
+}
+
+/* lseg_interpt -
+ *             Find the intersection point of two segments (if any).
+ */
+Datum
+lseg_interpt(PG_FUNCTION_ARGS)
+{
+       LSEG       *l1 = PG_GETARG_LSEG_P(0);
+       LSEG       *l2 = PG_GETARG_LSEG_P(1);
+       Point      *result;
+       
+       result = lseg_interpt_internal(l1, l2);
+       if (!PointerIsValid(result))
+               PG_RETURN_NULL();
+
        PG_RETURN_POINT_P(result);
 }
 
@@ -3742,10 +3758,7 @@ poly_same(PG_FUNCTION_ARGS)
 }
 
 /*-----------------------------------------------------------------
- * Determine if polygon A overlaps polygon B by determining if
- * their bounding boxes overlap.
- *
- * XXX ought to do a more correct check!
+ * Determine if polygon A overlaps polygon B 
  *-----------------------------------------------------------------*/
 Datum
 poly_overlap(PG_FUNCTION_ARGS)
@@ -3754,7 +3767,54 @@ poly_overlap(PG_FUNCTION_ARGS)
        POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
        bool            result;
 
-       result = box_ov(&polya->boundbox, &polyb->boundbox);
+       /* Quick check by bounding box */
+       result = (polya->npts > 0 && polyb->npts > 0 && 
+                       box_ov(&polya->boundbox, &polyb->boundbox)) ? true : false;
+
+       /*
+        * Brute-force algorithm - try to find intersected edges,
+        * if so then polygons are overlapped else check is one 
+        * polygon inside other or not by testing single point 
+        * of them.
+        */
+       if (result)
+       {
+               int             ia, ib;
+               LSEG    sa, sb;
+
+               /* Init first of polya's edge with last point */
+               sa.p[0] = polya->p[polya->npts - 1];
+               result = false;
+
+               for(ia=0; ia<polya->npts && result == false; ia++)
+               {
+                       /* Second point of polya's edge is a current one */ 
+                       sa.p[1] = polya->p[ia];
+
+                       /* Init first of polyb's edge with last point */
+                       sb.p[0] = polyb->p[polyb->npts - 1];
+
+                       for(ib=0; ib<polyb->npts && result == false; ib++)
+                       {
+                               sb.p[1] = polyb->p[ib];
+                               result = lseg_intersect_internal(&sa, &sb);
+                               sb.p[0] = sb.p[1];
+                       }
+
+                       /* 
+                        * move current endpoint to the first point
+                        * of next edge
+                        */
+                       sa.p[0] = sa.p[1];
+               }
+
+               if (result==false)
+               {
+                       result = (      point_inside(polya->p, polyb->npts, polyb->p)
+                                               ||
+                                               point_inside(polyb->p, polya->npts, polya->p) );
+               }
+       }
 
        /*
         * Avoid leaking memory for toasted inputs ... needed for rtree indexes
@@ -3765,6 +3825,119 @@ poly_overlap(PG_FUNCTION_ARGS)
        PG_RETURN_BOOL(result);
 }
 
+/*
+ * Tests special kind of segment for in/out of polygon.
+ * Special kind means:
+ *  - point a should be on segment s
+ *  - segment (a,b) should not be contained by s
+ * Returns true if:
+ *  - segment (a,b) is collinear to s and (a,b) is in polygon
+ *  - segment (a,b) s not collinear to s. Note: that doesn't
+ *    mean that segment is in polygon! 
+ */ 
+
+static bool
+touched_lseg_inside_poly(Point *a, Point *b, LSEG *s, POLYGON *poly, int start)
+{
+       /* point a is on s, b is not */
+       LSEG t;
+
+       t.p[0] = *a;
+       t.p[1] = *b;
+       
+#define POINTEQ(pt1, pt2)   (FPeq((pt1)->x, (pt2)->x) && FPeq((pt1)->y, (pt2)->y))
+       if ( POINTEQ(a, s->p) )
+       {
+               if ( on_ps_internal(s->p+1, &t) )
+                       return lseg_inside_poly(b, s->p+1, poly, start);
+       }
+       else if (POINTEQ(a, s->p+1))
+       {
+               if ( on_ps_internal(s->p, &t) )
+                       return lseg_inside_poly(b, s->p, poly, start);
+       }
+       else if ( on_ps_internal(s->p, &t) )
+       {
+               return lseg_inside_poly(b, s->p, poly, start);
+       }
+       else if ( on_ps_internal(s->p+1, &t) )
+       {
+               return lseg_inside_poly(b, s->p+1, poly, start);
+       }
+
+       return true; /* may be not true, but that will check later */
+}
+
+/*
+ * Returns true if segment (a,b) is in polygon, option
+ * start is used for optimization - function checks 
+ * polygon's edges started from start
+ */
+static bool
+lseg_inside_poly(Point *a, Point *b, POLYGON *poly, int start)
+{
+       LSEG    s,
+                       t;
+       int     i;
+       bool    res = true,
+                       intersection = false;
+
+       t.p[0] = *a;
+       t.p[1] = *b;
+       s.p[0] = poly->p[( start == 0) ? (poly->npts - 1) : (start - 1)];
+
+       for(i=start; i<poly->npts && res == true; i++)
+       {
+               Point   *interpt;
+
+               s.p[1] = poly->p[i];
+
+               if ( on_ps_internal(t.p, &s) )
+               {
+                       if ( on_ps_internal(t.p+1, &s) )
+                               return true; /* t is contained by s */
+
+                       /* Y-cross */
+                       res = touched_lseg_inside_poly(t.p, t.p+1, &s, poly, i+1);
+               } 
+               else if ( on_ps_internal(t.p+1, &s) )
+               {
+                       /* Y-cross */
+                       res = touched_lseg_inside_poly(t.p+1, t.p, &s, poly, i+1);
+               }
+               else if ( (interpt = lseg_interpt_internal(&t, &s)) != NULL )
+               {
+                       /*
+                        * segments are X-crossing, go to check each subsegment
+                        */
+
+                       intersection = true;
+                       res = lseg_inside_poly(t.p, interpt, poly, i+1);
+                       if (res)
+                               res = lseg_inside_poly(t.p+1, interpt, poly, i+1);
+                       pfree(interpt);
+               }
+
+               s.p[0] = s.p[1];
+       }
+
+       if (res && !intersection)
+       {
+               Point p;
+
+               /*
+                * if X-intersection wasn't found  then check central point
+                * of tested segment. In opposite case we already check all 
+                * subsegments
+                */
+               p.x = (t.p[0].x + t.p[1].x) / 2.0; 
+               p.y = (t.p[0].y + t.p[1].y) / 2.0;
+
+               res = point_inside(&p, poly->npts, poly->p); 
+       }
+
+       return res;
+}
 
 /*-----------------------------------------------------------------
  * Determine if polygon A contains polygon B.
@@ -3775,49 +3948,30 @@ poly_contain(PG_FUNCTION_ARGS)
        POLYGON    *polya = PG_GETARG_POLYGON_P(0);
        POLYGON    *polyb = PG_GETARG_POLYGON_P(1);
        bool            result;
-       int                     i;
 
        /*
         * Quick check to see if bounding box is contained.
         */
-       if (DatumGetBool(DirectFunctionCall2(box_contain,
-                                                                                BoxPGetDatum(&polya->boundbox),
-                                                                                BoxPGetDatum(&polyb->boundbox))))
+       if (polya->npts > 0 && polyb->npts > 0 &&
+                       DatumGetBool(DirectFunctionCall2(box_contain,
+                                                                                       BoxPGetDatum(&polya->boundbox),
+                                                                                       BoxPGetDatum(&polyb->boundbox))))
        {
-               result = true;                  /* assume true for now */
-               for (i = 0; i < polyb->npts; i++)
-               {
-                       if (point_inside(&(polyb->p[i]), polya->npts, &(polya->p[0])) == 0)
-                       {
-#ifdef GEODEBUG
-                               printf("poly_contain- point (%f,%f) not in polygon\n", polyb->p[i].x, polyb->p[i].y);
-#endif
-                               result = false;
-                               break;
-                       }
-               }
-               if (result)
+               int             i;
+               LSEG    s;
+
+               s.p[0] = polyb->p[polyb->npts - 1];
+               result = true;
+
+               for(i=0; i<polyb->npts && result == true; i++)
                {
-                       for (i = 0; i < polya->npts; i++)
-                       {
-                               if (point_inside(&(polya->p[i]), polyb->npts, &(polyb->p[0])) == 1)
-                               {
-#ifdef GEODEBUG
-                                       printf("poly_contain- point (%f,%f) in polygon\n", polya->p[i].x, polya->p[i].y);
-#endif
-                                       result = false;
-                                       break;
-                               }
-                       }
+                       s.p[1] = polyb->p[i];
+                       result = lseg_inside_poly(s.p, s.p+1, polya, 0);
+                       s.p[0] = s.p[1];
                }
        }
        else
        {
-#ifdef GEODEBUG
-               printf("poly_contain- bound box ((%f,%f),(%f,%f)) not inside ((%f,%f),(%f,%f))\n",
-                          polyb->boundbox.low.x, polyb->boundbox.low.y, polyb->boundbox.high.x, polyb->boundbox.high.y,
-                          polya->boundbox.low.x, polya->boundbox.low.y, polya->boundbox.high.x, polya->boundbox.high.y);
-#endif
                result = false;
        }
 
index 7b37cbf40bc3c2f26d1bd6dc8705141768cde989..6e06be5ed534f1c5f1a5ce121a76f8cda01ffbdc 100644 (file)
@@ -53,6 +53,8 @@ CREATE INDEX gpolygonind ON polygon_tbl USING gist (f1);
 CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
 CREATE TEMP TABLE gpolygon_tbl AS
     SELECT polygon(home_base) AS f1 FROM slow_emp4000;
+INSERT INTO gpolygon_tbl VALUES ( '(1000,0,0,1000)' ); 
+INSERT INTO gpolygon_tbl VALUES ( '(0,1000,1000,1000)' ); 
 CREATE TEMP TABLE gcircle_tbl AS
     SELECT circle(home_base) AS f1 FROM slow_emp4000;
 CREATE INDEX ggpolygonind ON gpolygon_tbl USING gist (f1);
index 56b72aaa8e12b9a436a657c30bbf75913a4fea9b..7e0ae242664dbe11dd314f0e56e2b9eb83d18be3 100644 (file)
@@ -180,6 +180,59 @@ SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' @> polygon '(3.0,1.0),(3.0,3.0),(
  f
 (1 row)
 
+--     +------------------------+
+--     |    *---*               1
+--     |  + |   | 
+--     |  2 *---*
+--     +------------------------+
+--                              3
+--     endpoints '+' is ofr one polygon, '*' - for another
+--     Edges 1-2, 2-3 are not shown on picture
+SELECT '((0,4),(6,4),(1,2),(6,0),(0,0))'::polygon @> '((2,1),(2,3),(3,3),(3,1))'::polygon AS "false";
+ false 
+-------
+ f
+(1 row)
+
+--     +-----------+
+--     |    *---* /      
+--     |    |   |/ 
+--     |    |   +  
+--     |    |   |\ 
+--     |    *---* \
+--     +-----------+
+SELECT '((0,4),(6,4),(3,2),(6,0),(0,0))'::polygon @> '((2,1),(2,3),(3,3),(3,1))'::polygon AS "true";
+ true 
+------
+ t
+(1 row)
+
+--     +-----------------+
+--     |                 |
+--     |   +---*---*-----+
+--     |   |   |   |
+--     |   +---*---*-----+
+--     |                 |
+--     +-----------------+
+SELECT '((1,1),(1,4),(5,4),(5,3),(2,3),(2,2),(5,2),(5,1))'::polygon @> '((3,2),(3,3),(4,3),(4,2))'::polygon AS "false";
+ false 
+-------
+ f
+(1 row)
+
+--     +---------+
+--     |         |
+--     |    *----*
+--     |    |    |
+--     |    *----*
+--     |         |
+--     +---------+
+SELECT '((0,0),(0,3),(3,3),(3,0))'::polygon @> '((2,1),(2,2),(3,2),(3,1))'::polygon AS "true";
+ true 
+------
+ t
+(1 row)
+
 -- same 
 SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' ~= polygon '(3.0,1.0),(3.0,3.0),(1.0,0.0)' AS false;
  false 
@@ -194,3 +247,34 @@ SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' && polygon '(3.0,1.0),(3.0,3.0),(
  t
 (1 row)
 
+--     +--------------------+
+--     |    *---*              1
+--     |  + |   | 
+--     |  2 *---*
+--     +--------------------+
+--                             3
+--     Edges 1-2, 2-3 are not shown on picture
+SELECT '((0,4),(6,4),(1,2),(6,0),(0,0))'::polygon && '((2,1),(2,3),(3,3),(3,1))'::polygon AS "true";
+ true 
+------
+ t
+(1 row)
+
+--     +--+ *--*
+--     |  | |  |
+--     |  | *--*
+--     |  +----+
+--     |       |
+--     +-------+
+SELECT '((1,4),(1,1),(4,1),(4,2),(2,2),(2,4),(1,4))'::polygon && '((3,3),(4,3),(4,4),(3,4),(3,3))'::polygon AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT '((200,800),(800,800),(800,200),(200,200))' &&  '(1000,1000,0,0)'::polygon AS "true";
+ true 
+------
+ t
+(1 row)
+
index 66ab0f9f02fc01d8016d7be2c30cdd2568a3306a..9527ab7a7bff767d38f93d8bafdaa422aa13eba8 100644 (file)
@@ -78,6 +78,8 @@ CREATE INDEX gcircleind ON circle_tbl USING gist (f1);
 
 CREATE TEMP TABLE gpolygon_tbl AS
     SELECT polygon(home_base) AS f1 FROM slow_emp4000;
+INSERT INTO gpolygon_tbl VALUES ( '(1000,0,0,1000)' ); 
+INSERT INTO gpolygon_tbl VALUES ( '(0,1000,1000,1000)' ); 
 
 CREATE TEMP TABLE gcircle_tbl AS
     SELECT circle(home_base) AS f1 FROM slow_emp4000;
index 1f45de0a6d96914970e7baee1f77511a9439c8ae..743ca22f536a29133893ee923174a79c99e4c796 100644 (file)
@@ -111,9 +111,64 @@ SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' <@ polygon '(3.0,1.0),(3.0,3.0),(
 -- contains 
 SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' @> polygon '(3.0,1.0),(3.0,3.0),(1.0,0.0)' AS false;
 
+--     +------------------------+
+--     |    *---*               1
+--     |  + |   | 
+--     |  2 *---*
+--     +------------------------+
+--                              3
+--     endpoints '+' is ofr one polygon, '*' - for another
+--     Edges 1-2, 2-3 are not shown on picture
+SELECT '((0,4),(6,4),(1,2),(6,0),(0,0))'::polygon @> '((2,1),(2,3),(3,3),(3,1))'::polygon AS "false";
+
+--     +-----------+
+--     |    *---* /      
+--     |    |   |/ 
+--     |    |   +  
+--     |    |   |\ 
+--     |    *---* \
+--     +-----------+
+SELECT '((0,4),(6,4),(3,2),(6,0),(0,0))'::polygon @> '((2,1),(2,3),(3,3),(3,1))'::polygon AS "true";
+
+--     +-----------------+
+--     |                 |
+--     |   +---*---*-----+
+--     |   |   |   |
+--     |   +---*---*-----+
+--     |                 |
+--     +-----------------+
+SELECT '((1,1),(1,4),(5,4),(5,3),(2,3),(2,2),(5,2),(5,1))'::polygon @> '((3,2),(3,3),(4,3),(4,2))'::polygon AS "false";
+
+--     +---------+
+--     |         |
+--     |    *----*
+--     |    |    |
+--     |    *----*
+--     |         |
+--     +---------+
+SELECT '((0,0),(0,3),(3,3),(3,0))'::polygon @> '((2,1),(2,2),(3,2),(3,1))'::polygon AS "true";
+
 -- same 
 SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' ~= polygon '(3.0,1.0),(3.0,3.0),(1.0,0.0)' AS false;
 
 -- overlap 
 SELECT polygon '(2.0,0.0),(2.0,4.0),(0.0,0.0)' && polygon '(3.0,1.0),(3.0,3.0),(1.0,0.0)' AS true;
 
+--     +--------------------+
+--     |    *---*              1
+--     |  + |   | 
+--     |  2 *---*
+--     +--------------------+
+--                             3
+--     Edges 1-2, 2-3 are not shown on picture
+SELECT '((0,4),(6,4),(1,2),(6,0),(0,0))'::polygon && '((2,1),(2,3),(3,3),(3,1))'::polygon AS "true";
+
+--     +--+ *--*
+--     |  | |  |
+--     |  | *--*
+--     |  +----+
+--     |       |
+--     +-------+
+SELECT '((1,4),(1,1),(4,1),(4,2),(2,2),(2,4),(1,4))'::polygon && '((3,3),(4,3),(4,4),(3,4),(3,3))'::polygon AS "false";
+SELECT '((200,800),(800,800),(800,200),(200,200))' &&  '(1000,1000,0,0)'::polygon AS "true";
+