[geos-commits] [SCM] GEOS branch main updated. 130176433db592b88390df5de9539448db305d2c

git at osgeo.org git at osgeo.org
Thu Jun 8 11:19:27 PDT 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GEOS".

The branch, main has been updated
       via  130176433db592b88390df5de9539448db305d2c (commit)
       via  f7de298feaefb5a3afa73c12fed2ae66d6d4ef36 (commit)
      from  bf3216ecc34cd0a026af7a5f9a636400f252a456 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 130176433db592b88390df5de9539448db305d2c
Author: Paul Ramsey <pramsey at cleverelephant.ca>
Date:   Thu Jun 8 11:18:57 2023 -0700

    Add STRTree example to web documentation

diff --git a/examples/capi_strtree.c b/examples/capi_strtree.c
index f654bfb8a..716e44967 100644
--- a/examples/capi_strtree.c
+++ b/examples/capi_strtree.c
@@ -2,13 +2,17 @@
 * # GEOS C API example 3
 *
 * Build a spatial index and search it for a
-* nearest pair.
+* nearest neighbor and for a query bounds.
+*
+* cc -I/usr/local/include capi_strtree.c -o capi_strtree -L/usr/local/lib -lgeos_c
 */
 
-/* To print to stdout */
+/* System headers */
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#include <math.h>
+#include <time.h>
 
 /* Only the CAPI header is required */
 #include <geos_c.h>
@@ -32,17 +36,63 @@ geos_message_handler(const char* fmt, ...)
 }
 
 /*
-* Generate a random point in the range of
-* POINT(0..range, 0..range). Caller must
-* free.
+* An application will want to index items, which have
+* some attributes and a geometry part.
+*/
+typedef struct
+{
+    GEOSGeometry* geom;
+    size_t id;
+} item_t;
+
+/*
+* Generate a random item with a location in the range of
+* POINT(0..range, 0..range). Caller must free.
 */
-static GEOSGeometry*
-geos_random_point(double range)
+static item_t *
+random_item(double range)
 {
+    item_t* item = malloc(sizeof(item_t));
     double x = range * rand() / RAND_MAX;
     double y = range * rand() / RAND_MAX;
     /* Make a point in the point grid */
-    return GEOSGeom_createPointFromXY(x, y);
+    item->geom = GEOSGeom_createPointFromXY(x, y);
+    item->id = rand();
+    return item;
+}
+
+/*
+* Free an item and its geometry.
+*/
+void
+free_item(item_t* item)
+{
+    if (item && item->geom) GEOSGeom_destroy(item->geom);
+    if (item) free(item);
+}
+
+/*
+* Item distance callback for GEOSSTRtree_nearest_generic()
+*/
+int
+itemDistanceCallback(const void* item1, const void* item2, double* distance, void* userdata)
+{
+    item_t* obj1 = (item_t*)item1;
+    item_t* obj2 = (item_t*)item2;
+    return GEOSDistance(obj1->geom, obj2->geom, distance);
+}
+
+/*
+* Item query callback for GEOSSTRtree_query()
+*/
+void
+itemQueryCallback(void* item, void* userdata)
+{
+    double x, y;
+    item_t* i = (item_t*)item;
+    GEOSGeomGetX(i->geom, &x);
+    GEOSGeomGetY(i->geom, &y);
+    printf("Found item %10zu at (%g, %g)\n", i->id, x, y);
 }
 
 
@@ -51,65 +101,94 @@ int main()
     /* Send notice and error messages to our stdout handler */
     initGEOS(geos_message_handler, geos_message_handler);
 
-    /* How many points to add to our random field */
-    const size_t npoints = 10000;
-    /* The coordinate range of the field (0->100.0) */
+    /* How many random items to add to our index */
+    const size_t nItems = 10000;
+    /* The coordinate range of the random locations (0->100.0) */
     const double range = 100.0;
+    /* Set the seed for rand() */
+    srand(time(NULL));
 
     /*
-    * The tree doesn't take ownership of inputs just
-    * holds references, so we keep our point field
-    * handy in an array
+    * The tree doesn't take ownership of inputs, it just
+    * holds pointers, so we keep a list of allocated items
+    * handy in an array for future clean-up
     */
-    GEOSGeometry* geoms[npoints];
+    item_t* items[nItems];
     /*
     * The create parameter for the tree is not the
     * number of inputs, it is the number of entries
     * per node. 10 is a good default number to use.
     */
     GEOSSTRtree* tree = GEOSSTRtree_create(10);
-    for (size_t i = 0; i < npoints; i++) {
+    for (size_t i = 0; i < nItems; i++) {
         /* Make a random point */
-        GEOSGeometry* geom = geos_random_point(range);
+        item_t* item = random_item(range);
         /* Store away a reference so we can free it after */
-        geoms[i] = geom;
+        items[i] = item;
         /* Add an entry for it to the tree */
-        GEOSSTRtree_insert(tree, geom, geom);
+        GEOSSTRtree_insert(tree, item->geom, item);
     }
 
-    /* Random point to compare to the field */
-    GEOSGeometry* geom_random = geos_random_point(range);
-    /* Nearest point in the field to our test point */
-    const GEOSGeometry* geom_nearest = GEOSSTRtree_nearest(tree, geom_random);
-
-    /* Convert results to WKT */
+    /* Prepare to write some geometries out as text */
     GEOSWKTWriter* writer = GEOSWKTWriter_create();
     /* Trim trailing zeros off output */
     GEOSWKTWriter_setTrim(writer, 1);
     GEOSWKTWriter_setRoundingPrecision(writer, 3);
-    char* wkt_random = GEOSWKTWriter_write(writer, geom_random);
-    char* wkt_nearest = GEOSWKTWriter_write(writer, geom_nearest);
-    GEOSWKTWriter_destroy(writer);
 
-    /* Print answer */
+    /* Prepare to read some geometries in as text */
+    GEOSWKTReader* reader = GEOSWKTReader_create();
+
+    /* Random item to query the index with */
+    item_t* item_random = random_item(range);
+    /* Nearest item in the index to our random item */
+    const item_t* item_nearest = GEOSSTRtree_nearest_generic(
+        tree,                 // STRTree to query
+        item_random,          // Item to use in search
+        item_random->geom,    // Geometry to seed search
+        itemDistanceCallback, // Callback to process nearest object
+        NULL);                // Userdata to hand to the callback
+
+    /* Convert geometry to WKT */
+    char* wkt_random  = GEOSWKTWriter_write(writer, item_random->geom);
+    char* wkt_nearest = GEOSWKTWriter_write(writer, item_nearest->geom);
+
+    /* Print random query point and nearest point */
     printf(" Random Point: %s\n", wkt_random);
     printf("Nearest Point: %s\n", wkt_nearest);
 
-    /* Clean up all allocated objects */
-    /* Destroying tree does not destroy inputs */
-    GEOSSTRtree_destroy(tree);
-    GEOSGeom_destroy(geom_random);
-    /* Destroy all the points in our random field */
-    for (size_t i = 0; i < npoints; i++) {
-        GEOSGeom_destroy(geoms[i]);
-    }
-    /*
-    * Don't forget to free memory allocated by the
-    * printing functions!
-    */
+    /* Don't forget to free memory allocated for WKT! */
     GEOSFree(wkt_random);
     GEOSFree(wkt_nearest);
 
+    /* Set up a query rectangle for index query */
+    const char* wkt_bounds = "POLYGON((20 20, 22 20, 22 22, 20 22, 20 20))";
+    GEOSGeometry* geom_query = GEOSWKTReader_read(reader, wkt_bounds);
+
+    /* Find all items that touch the bounds */
+    /* For non-rectangular query geometry, this will be an over-determined set */
+    GEOSSTRtree_query(
+        tree,              // STRTree to query
+        geom_query,        // GEOSGeometry query bounds
+        itemQueryCallback, // Callback to process index entries that pass query
+        NULL);             // Userdata to hand to the callback
+
+    /* Free the query bounds geometry */
+    GEOSGeom_destroy(geom_query);
+
+    /* Free the WKT writer and reader */
+    GEOSWKTWriter_destroy(writer);
+    GEOSWKTReader_destroy(reader);
+
+    /* Freeing the tree does not free the tree inputs */
+    GEOSSTRtree_destroy(tree);
+
+    /* Free all the items in our random item list */
+    for (size_t i = 0; i < nItems; i++) {
+        free_item(items[i]);
+    }
+    /* Free our working random item */
+    free_item(item_random);
+
     /* Clean up the global context */
     finishGEOS();
 
diff --git a/web/content/usage/c_api.md b/web/content/usage/c_api.md
index e6012f878..2360a2b3e 100644
--- a/web/content/usage/c_api.md
+++ b/web/content/usage/c_api.md
@@ -351,4 +351,118 @@ For a complete example of using prepared geometry to accelerate multiple predica
 
 ### STRTree Index
 
-* `GEOSSTRtree`
+The STRTree index allows you to create, populate, and query a spatial index. Like most spatial indexes, the STRTree is based on [indexing rectangles](https://en.wikipedia.org/wiki/R-tree). For a complete example using a reader and writer, see [capi_strtree.c](https://github.com/libgeos/geos/blob/main/examples/capi_strtree.c).
+
+When you build an index, you will usually insert an "item" -- some kind of `struct` that you are interested in indexing -- and an associated bounds for that item, in the form of a `GEOSGeometry`. The geometry does not need to be rectangular, a rectangular bounding box will be automatically calculated for the geometry.
+
+Once you have built an STRTree, you have two basic ways to query:
+
+* Find the **nearest item** to a query geometry, using `GEOSSTRtree_nearest_generic()`
+* Find all the **items that intersect** with a query rectangle, using `GEOSSTRtree_query()`
+
+Build the tree by creating it, then inserting items.
+
+```c
+/*
+* An application will want to index items, which have
+* some attributes and a geometry part.
+*/
+typedef struct
+{
+    GEOSGeometry* geom;
+    size_t id;
+} item;
+
+/*
+* The tree doesn't take ownership of inputs just
+* holds references, so we keep our point field
+* handy in an array
+*/
+item* items[nItems];
+
+/*
+* The create parameter for the tree is not the
+* number of inputs, it is the number of entries
+* per node. 10 is a good default number to use.
+*/
+GEOSSTRtree* tree = GEOSSTRtree_create(10);
+
+for (size_t i = 0; i < nItems; i++) {
+    /* Make a random point */
+    item* obj = random_item(range);
+    /* Store away a reference so we can free it after */
+    items[i] = obj;
+    /* Add an entry for it to the tree */
+    GEOSSTRtree_insert(tree, obj->geom, obj);
+}
+```
+
+Note that the index does **not take ownership** of the inserted `GEOSGeometry` or the item, it just stores pointers. So remember to keep a list of the items you create in order to free them at the end of your process.
+
+Once the tree is built, you can query it.
+
+The generic **nearest-neighbor query** uses a callback to check the actual distance between the search item and the indexed item. In this way it can filter through the many candidate nearest nodes in the index to find the **actual** nearest node.
+
+```c
+/*
+* Item distance callback for GEOSSTRtree_nearest_generic()
+*/
+int
+itemDistanceCallback(const void* item1, const void* item2, double* distance, void* userdata)
+{
+    item_t* obj1 = (item_t*)item1;
+    item_t* obj2 = (item_t*)item2;
+    return GEOSDistance(obj1->geom, obj2->geom, distance);
+}
+```
+
+The query call requires the tree, the item driving the search (so it can be fed into the callback), the geometry driving the search (because the library doesn't know how to extract the geometry from the item *a priori*), the callback, and whatever extra information want sent into the callback.
+
+```c
+/* Random item to query the index with */
+item_t* item_random = random_item(range);
+
+/* Nearest item in the index to our random item */
+const item_t* item_nearest = GEOSSTRtree_nearest_generic(
+    tree,                 // STRTree to query
+    item_random,          // Item to use in search
+    item_random->geom,    // Geometry to seed search
+    itemDistanceCallback, // Callback to process nearest object
+    NULL);                // Userdata to hand to the callback
+```
+
+The **query by rectangle** function also uses a callback, which could be used to exactly test for a spatial relationship (intersects, contains, etc), for all the index nodes that meet the rough "bounds interact" filter the index applies.
+
+```c
+/*
+* Item query callback for GEOSSTRtree_query()
+*/
+void
+itemQueryCallback(void* item, void* userdata)
+{
+    double x, y;
+    item_t* i = (item_t*)item;
+    GEOSGeomGetX(i->geom, &x);
+    GEOSGeomGetY(i->geom, &y);
+    printf("Found item %10zu at (%g, %g)\n", i->id, x, y);
+}
+```
+
+This example just prints out every candidate that passes the index filter.
+
+```c
+/* Set up a query rectangle for index query */
+const char* wkt_bounds = "POLYGON((20 20, 22 20, 22 22, 20 22, 20 20))";
+GEOSGeometry* geom_query = GEOSWKTReader_read(reader, wkt_bounds);
+
+/* Find all items that touch the bounds */
+/* For non-rectangular query geometry, this will be an over-determined set */
+GEOSSTRtree_query(
+    tree,              // STRTree to query
+    geom_query,        // GEOSGeometry query bounds
+    itemQueryCallback, // Callback to process index entries that pass query
+    NULL);             // Userdata to hand to the callback
+```
+
+The query itself just uses the tree, the query bounds geometry, the callback, and optional user data. You could use the user data to pass in an array to write results out to, or a prepared geometry to use for exact tests.
+

commit f7de298feaefb5a3afa73c12fed2ae66d6d4ef36
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Jun 8 19:31:43 2023 +0200

    Fix spelling errors. (#920)
    
    * unintialized -> uninitialized

diff --git a/capi/geos_ts_c.cpp b/capi/geos_ts_c.cpp
index 64df095e5..9c42927fc 100644
--- a/capi/geos_ts_c.cpp
+++ b/capi/geos_ts_c.cpp
@@ -391,7 +391,7 @@ inline auto execute(
                                   decltype(std::declval<F>()())>::type errval,
         F&& f) -> decltype(errval) {
     if (extHandle == nullptr) {
-        throw std::runtime_error("GEOS context handle is unintialized, call initGEOS");
+        throw std::runtime_error("GEOS context handle is uninitialized, call initGEOS");
     }
 
     GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);
@@ -415,7 +415,7 @@ inline auto execute(
 template<typename F, typename std::enable_if<!std::is_void<decltype(std::declval<F>()())>::value, std::nullptr_t>::type = nullptr>
 inline auto execute(GEOSContextHandle_t extHandle, F&& f) -> decltype(f()) {
     if (extHandle == nullptr) {
-        throw std::runtime_error("context handle is unintialized, call initGEOS");
+        throw std::runtime_error("context handle is uninitialized, call initGEOS");
     }
 
     GEOSContextHandleInternal_t* handle = reinterpret_cast<GEOSContextHandleInternal_t*>(extHandle);

-----------------------------------------------------------------------

Summary of changes:
 capi/geos_ts_c.cpp         |   4 +-
 examples/capi_strtree.c    | 161 +++++++++++++++++++++++++++++++++------------
 web/content/usage/c_api.md | 116 +++++++++++++++++++++++++++++++-
 3 files changed, 237 insertions(+), 44 deletions(-)


hooks/post-receive
-- 
GEOS


More information about the geos-commits mailing list