]> granicus.if.org Git - postgis/commitdiff
#3399, ST_GeneratePoints
authorPaul Ramsey <pramsey@cleverelephant.ca>
Thu, 17 Dec 2015 18:12:23 +0000 (18:12 +0000)
committerPaul Ramsey <pramsey@cleverelephant.ca>
Thu, 17 Dec 2015 18:12:23 +0000 (18:12 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@14492 b70326c6-7e19-0410-871a-916f4a2858ee

NEWS
doc/reference_processing.xml
liblwgeom/cunit/cu_algorithm.c
liblwgeom/liblwgeom.h.in
liblwgeom/lwgeom_geos.c
postgis/lwgeom_geos.c
postgis/postgis.sql.in

diff --git a/NEWS b/NEWS
index 86eabebdd070659908a57387f7073997fd82ba0e..1d187d7be803381fc6309a12deedd25f1b511dda 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ PostGIS 2.3.0
   - TopoGeom_addElement, TopoGeom_remElement (Sandro Santilli)
   - populate_topology_layer (Sandro Santilli)
   - #2259 ST_Voronoi (Dan Baston)
+  - #3339 ST_GeneratePoints (Paul Ramsey)
 
 PostGIS 2.2.0
 2015/10/07
index 10245346734a8079824f6f00f144473eef053767..edd581aa18ae4383c08e1128293587a5640b9272 100644 (file)
@@ -3653,4 +3653,43 @@ GEOMETRYCOLLECTION(LINESTRING(6 6,7 7))
                </para>
          </refsection>
        </refentry>
+       
+       <refentry id="ST_GeneratePoints">
+         <refnamediv>
+               <refname>ST_GeneratePoints</refname>
+
+               <refpurpose>Converts a polygon or multi-polygon into a multi-point composed of randomly location points within the original areas.</refpurpose>
+         </refnamediv>
+
+         <refsynopsisdiv>
+               <funcsynopsis>
+                 <funcprototype>
+                       <funcdef>geometry <function>ST_GeneratePoints</function></funcdef>
+                       <paramdef>
+                               <parameter>g</parameter>
+                               <type>geometry</type> 
+                       </paramdef>
+                       <paramdef>
+                               <parameter>npoints</parameter>
+                               <type>numeric</type> 
+                       </paramdef>
+                 </funcprototype>
+
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+
+               <para>
+                       ST_GeneratePoints generates pseudo-random points until the requested number are
+                       found within the input area.
+               </para> 
+                       
+               <para>Availability: 2.3.0</para>
+         </refsection>
+
+       </refentry>
+       
+       
 </sect1>
index f3510ea2e9db0f72c9ace6b535fc02b67faa0ab0..9313460fc0ced75be65edc3874355ecfe61c9855 100644 (file)
@@ -982,6 +982,47 @@ static void test_lwgeom_simplify(void)
                lwfree(ewkt);
 }
 
+
+
+static void test_point_density(void)
+{
+       LWGEOM *geom;
+       LWMPOINT *mpt;
+       // char *ewkt;
+
+       /* POLYGON */
+       geom = lwgeom_from_wkt("POLYGON((1 0,0 1,1 2,2 1,1 0))", LW_PARSER_CHECK_NONE);
+       mpt = lwgeom_to_points(geom, 100);
+       CU_ASSERT_EQUAL(mpt->ngeoms,100);
+       // ewkt = lwgeom_to_ewkt((LWGEOM*)mpt);
+       // printf("%s\n", ewkt);
+       // lwfree(ewkt);
+       lwmpoint_free(mpt);
+
+       mpt = lwgeom_to_points(geom, 1);
+       CU_ASSERT_EQUAL(mpt->ngeoms,1);
+       lwmpoint_free(mpt);
+
+       mpt = lwgeom_to_points(geom, 0);
+       CU_ASSERT_EQUAL(mpt, NULL);
+
+       lwgeom_free(geom);
+
+       /* MULTIPOLYGON */
+       geom = lwgeom_from_wkt("MULTIPOLYGON(((10 0,0 10,10 20,20 10,10 0)),((0 0,5 0,5 5,0 5,0 0)))", LW_PARSER_CHECK_NONE);
+
+       mpt = lwgeom_to_points(geom, 1000);
+       CU_ASSERT_EQUAL(mpt->ngeoms,1000);
+       lwmpoint_free(mpt);
+
+       mpt = lwgeom_to_points(geom, 1);
+       CU_ASSERT_EQUAL(mpt->ngeoms,1);
+       lwmpoint_free(mpt);
+
+       lwgeom_free(geom);
+}
+
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -1007,4 +1048,5 @@ void algorithms_suite_setup(void)
        PG_ADD_TEST(suite,test_isclosed);
        PG_ADD_TEST(suite,test_lwgeom_simplify);
        PG_ADD_TEST(suite,test_lw_arc_center);
+       PG_ADD_TEST(suite,test_point_density);
 }
index 22167ca420c90b6f150d10c743b9002d777f8ade..f2c7081c8985800d9a67fa87a364718dbf76c57d 100644 (file)
@@ -1440,6 +1440,13 @@ extern LWLINE *lwline_segmentize2d(LWLINE *line, double dist);
 extern LWPOLY *lwpoly_segmentize2d(LWPOLY *line, double dist);
 extern LWCOLLECTION *lwcollection_segmentize2d(LWCOLLECTION *coll, double dist);
 
+/*
+ * Point density functions
+ */ 
+extern LWMPOINT *lwpoly_to_points(const LWPOLY *poly, int npoints);
+extern LWMPOINT *lwmpoly_to_points(const LWMPOLY *mpoly, int npoints);
+extern LWMPOINT *lwgeom_to_points(const LWGEOM *lwgeom, int npoints);
+
 /**
 * Calculate the GeoHash (http://geohash.org) string for a geometry. Caller must free.
 */
index b5042a3c931e712813daba7befc15248d6796483..4946f08d78ae8c2d23775258b2899b0c64a2fcce 100644 (file)
@@ -29,6 +29,7 @@
 #include "lwgeom_log.h"
 
 #include <stdlib.h>
+#include <time.h>
 
 LWTIN *lwtin_from_geos(const GEOSGeometry *geom, int want3d);
 
@@ -1546,6 +1547,265 @@ lwgeom_offsetcurve(const LWLINE *lwline, double size, int quadsegs, int joinStyl
        return lwgeom_result;
 }
 
+
+static void shuffle(void *array, size_t n, size_t size) {
+    char tmp[size];
+    char *arr = array;
+    size_t stride = size;
+
+    if (n > 1) {
+        size_t i;
+        for (i = 0; i < n - 1; ++i) {
+            size_t rnd = (size_t) rand();
+            size_t j = i + rnd / (RAND_MAX / (n - i) + 1);
+
+            memcpy(tmp, arr + j * stride, size);
+            memcpy(arr + j * stride, arr + i * stride, size);
+            memcpy(arr + i * stride, tmp, size);
+        }
+    }
+}
+
+LWMPOINT*
+lwpoly_to_points(const LWPOLY *lwpoly, int npoints)
+{
+       double area, bbox_area, bbox_width, bbox_height;
+       GBOX bbox;
+       const LWGEOM *lwgeom = (LWGEOM*)lwpoly;
+       int sample_npoints, sample_sqrt, sample_width, sample_height;
+       double sample_cell_size;
+       int i, j;
+       int iterations = 0;
+       int npoints_generated = 0;
+       int npoints_tested = 0;
+       GEOSGeometry *g;
+       const GEOSPreparedGeometry *gprep;
+       GEOSGeometry *gpt;
+       GEOSCoordSequence *gseq;
+       LWMPOINT *mpt;
+       int srid = lwgeom_get_srid(lwgeom);
+       int done = 0;
+       int *cells;
+
+       if (lwgeom_get_type(lwgeom) != POLYGONTYPE)
+       {
+               lwerror("%s: only polygons supported", __func__);
+               return NULL;
+       }
+       
+       if (npoints == 0 || lwgeom_is_empty(lwgeom))
+       {
+               return NULL;
+               // return lwmpoint_construct_empty(lwgeom_get_srid(poly), lwgeom_has_z(poly), lwgeom_has_m(poly));
+       }
+       
+       if (!lwpoly->bbox) 
+       {
+               lwgeom_calculate_gbox(lwgeom, &bbox);
+       }
+       else
+       {
+               bbox = *(lwpoly->bbox);
+       }
+       area = lwpoly_area(lwpoly);
+       bbox_width = bbox.xmax - bbox.xmin;
+       bbox_height = bbox.ymax - bbox.ymin;
+       bbox_area = bbox_width * bbox_height;
+
+       if (area == 0.0 || bbox_area == 0.0)
+       {
+               lwerror("%s: zero area input polygon, TBD", __func__);
+               return NULL;
+       }
+       
+       /* Gross up our test set a bit to increase odds of getting */
+       /* coverage in one pass */
+       sample_npoints = npoints * bbox_area / area;
+       
+       /* We're going to generate points using a sample grid */
+       /* as described http://lin-ear-th-inking.blogspot.ca/2010/05/more-random-points-in-jts.html */
+       /* to try and get a more uniform "random" set of points */
+       /* So we have to figure out how to stick a grid into our box */
+       sample_sqrt = lround(sqrt(sample_npoints));
+       if (sample_sqrt == 0)
+               sample_sqrt = 1;
+       
+       /* Calculate the grids we're going to randomize within */
+       if (bbox_width > bbox_height)
+       {
+               sample_width = sample_sqrt;
+               sample_height = ceil((double)sample_npoints / (double)sample_width);
+               sample_cell_size = bbox_width / sample_width;
+       }
+       else
+       {
+               sample_height = sample_sqrt;
+               sample_width = ceil((double)sample_npoints / (double)sample_height);
+               sample_cell_size = bbox_height / sample_height;
+       }
+       
+       /* Prepare the polygon for fast true/false testing */
+       initGEOS(lwnotice, lwgeom_geos_error);
+       g = (GEOSGeometry *)LWGEOM2GEOS(lwgeom, 0);
+       if (!g)
+       {
+               lwerror("%s: Geometry could not be converted to GEOS: %s", __func__, lwgeom_geos_errmsg);
+               return NULL;
+       }
+       gprep = GEOSPrepare(g);
+       
+       /* Get an empty multi-point ready to return */
+       mpt = lwmpoint_construct_empty(srid, 0, 0);
+       
+       /* Init random number generator */
+       srand(time(NULL));
+
+       /* Now we fill in an array of cells, and then shuffle that array, */
+       /* so we can visit the cells in random order to avoid visual ugliness */
+       /* caused by visiting them sequentially */
+       cells = lwalloc(2*sizeof(int)*sample_height*sample_width);
+       for (i = 0; i < sample_width; i++)
+       {
+               for (j = 0; j < sample_height; j++)
+               {
+                       cells[2*(i*sample_height+j)] = i;
+                       cells[2*(i*sample_height+j)+1] = j;
+               }
+       }
+       shuffle(cells, sample_height*sample_width, 2*sizeof(int));
+       
+       /* Start testing points */
+       while (npoints_generated < npoints)
+       {
+               iterations++;
+               for (i = 0; i < sample_width*sample_height; i++)
+               {
+                       int contains = 0;
+                       double y = bbox.ymin + cells[2*i] * sample_cell_size;
+                       double x = bbox.xmin + cells[2*i+1] * sample_cell_size;
+                       x += rand() * sample_cell_size / RAND_MAX;
+                       y += rand() * sample_cell_size / RAND_MAX;
+                       if (x >= bbox.xmax || y >= bbox.ymax)
+                               continue;
+
+                       gseq = GEOSCoordSeq_create(1, 2);
+                       GEOSCoordSeq_setX(gseq, 0, x);
+                       GEOSCoordSeq_setY(gseq, 0, y);
+                       gpt = GEOSGeom_createPoint(gseq);
+
+                       contains = GEOSPreparedIntersects(gprep, gpt);
+                       
+               GEOSGeom_destroy(gpt);
+
+                       if (contains == 2)
+                       {
+                       GEOSPreparedGeom_destroy(gprep);
+                       GEOSGeom_destroy(g);
+                       lwerror("%s: GEOS exception on PreparedContains: %s", __func__, lwgeom_geos_errmsg);
+                       return NULL;
+                       }
+                       if (contains)
+                       {
+                               npoints_generated++;
+                               mpt = lwmpoint_add_lwpoint(mpt, lwpoint_make2d(srid, x, y));
+                               if (npoints_generated == npoints)
+                               {
+                                       done = 1;
+                                       break;
+                               }
+                       }
+                       
+                       /* Short-circuit check for ctrl-c occasionally */
+                       npoints_tested++;
+                       if (npoints_tested % 10000 == 0)
+                       {
+                               LW_ON_INTERRUPT(GEOSPreparedGeom_destroy(gprep); GEOSGeom_destroy(g); return NULL);
+                       }
+                       
+                       if (done) break;
+               }
+               if (done || iterations > 100) break;
+       }
+       
+       GEOSPreparedGeom_destroy(gprep);
+       GEOSGeom_destroy(g);
+       lwfree(cells);
+       
+       return mpt;
+}
+
+
+/*
+* Allocate points to sub-geometries by area, then call lwgeom_poly_to_points
+* and bundle up final result in a single multipoint.
+*/
+LWMPOINT*
+lwmpoly_to_points(const LWMPOLY *lwmpoly, int npoints)
+{
+       const LWGEOM *lwgeom = (LWGEOM*)lwmpoly;
+       double area;
+       int i;
+       LWMPOINT *mpt = NULL;
+       
+       if (lwgeom_get_type(lwgeom) != MULTIPOLYGONTYPE)
+       {
+               lwerror("%s: only multipolygons supported", __func__);
+               return NULL;            
+       }
+       if (npoints == 0 || lwgeom_is_empty(lwgeom))
+       {
+               return NULL;
+       }
+       
+       area = lwgeom_area(lwgeom);
+       
+       for (i = 0; i < lwmpoly->ngeoms; i++)
+       {
+               double sub_area = lwpoly_area(lwmpoly->geoms[i]);
+               int sub_npoints = lround(npoints * sub_area / area);
+               if(sub_npoints > 0)
+               {
+                       LWMPOINT *sub_mpt = lwpoly_to_points(lwmpoly->geoms[i], sub_npoints);
+                       if (!mpt) 
+                       {
+                               mpt = sub_mpt;
+                       }
+                       else
+                       {
+                               int j;
+                               for (j = 0; j < sub_mpt->ngeoms; j++)
+                               {
+                                       mpt = lwmpoint_add_lwpoint(mpt, sub_mpt->geoms[j]);
+                               }
+                               /* Just free the shell, leave the underlying lwpoints alone, as they
+                                  are now owed by the returning multipoint */
+                               lwgeom_release((LWGEOM*)sub_mpt);
+                       }
+               }
+       }
+       
+       return mpt;
+}
+
+
+LWMPOINT*
+lwgeom_to_points(const LWGEOM *lwgeom, int npoints)
+{
+       switch(lwgeom_get_type(lwgeom))
+       {
+               case MULTIPOLYGONTYPE:
+                       return lwmpoly_to_points((LWMPOLY*)lwgeom, npoints);
+               case POLYGONTYPE:
+                       return lwpoly_to_points((LWPOLY*)lwgeom, npoints);
+               default:
+                       lwerror("%s: unsupported geometry type '%s'", __func__, lwtype_name(lwgeom_get_type(lwgeom)));
+                       return NULL;    
+       }
+}
+
+
+
+
 LWTIN *lwtin_from_geos(const GEOSGeometry *geom, int want3d) {
        int type = GEOSGeomTypeId(geom);
        int hasZ;
index 3f1999c7ca80af86dab67e487d059acbabcacbc4..d4d7bea52f6c72249b34507c6d6429eab99dbef1 100644 (file)
@@ -33,6 +33,7 @@
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/numeric.h"
 
 #if POSTGIS_PGSQL_VERSION >= 93
 #include "access/htup_details.h"
@@ -917,6 +918,47 @@ Datum buffer(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(result);
 }
 
+
+
+/*
+* Generate a field of random points within the area of a 
+* polygon or multipolygon. Throws an error for other geometry
+* types.
+*/
+Datum ST_GeneratePoints(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_GeneratePoints);
+Datum ST_GeneratePoints(PG_FUNCTION_ARGS)
+{
+       GSERIALIZED     *gser_input;
+       GSERIALIZED *gser_result;
+       LWGEOM *lwgeom_input;
+       LWGEOM *lwgeom_result;
+       int32 npoints;
+
+       gser_input = PG_GETARG_GSERIALIZED_P(0);
+       npoints = DatumGetInt32(DirectFunctionCall1(numeric_int4, PG_GETARG_DATUM(1)));
+       
+       /* Smartasses get nothing back */
+       if (npoints < 0)
+               PG_RETURN_NULL();
+       
+       /* Types get checked in the code, we'll keep things small out there */
+       lwgeom_input = lwgeom_from_gserialized(gser_input);
+       lwgeom_result = (LWGEOM*)lwgeom_to_points(lwgeom_input, npoints);
+       lwgeom_free(lwgeom_input);
+       PG_FREE_IF_COPY(gser_input, 0);
+       
+       /* Return null as null */
+       if (!lwgeom_result)
+               PG_RETURN_NULL();
+
+       /* Serialize and return */
+       gser_result = gserialized_from_lwgeom(lwgeom_result, 0);
+       lwgeom_free(lwgeom_result);
+       PG_RETURN_POINTER(gser_result);
+}
+
+
 /*
 * Compute at offset curve to a line
 */
index 58a7ef20e97d6ba09cb5fa8b09ab2c1cebd21b63..b470c6d21d8085de39afeceb17f9bb8856f89e3d 100644 (file)
@@ -3197,6 +3197,13 @@ CREATE OR REPLACE FUNCTION ST_OffsetCurve(line geometry, distance float8, params
        LANGUAGE 'c' IMMUTABLE STRICT
        COST 100;
 
+-- Availability: 2.3.0
+CREATE OR REPLACE FUNCTION ST_GeneratePoints(area geometry, npoints numeric)
+       RETURNS geometry
+       AS 'MODULE_PATHNAME','ST_GeneratePoints'
+       LANGUAGE 'c' IMMUTABLE STRICT
+       COST 400;
+
 -- PostGIS equivalent function: convexhull(geometry)
 CREATE OR REPLACE FUNCTION ST_ConvexHull(geometry)
        RETURNS geometry