/********************************************************************** * * PostGIS - Spatial Types for PostgreSQL * http://postgis.net * * PostGIS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * PostGIS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PostGIS. If not, see . * ********************************************************************** * * Copyright (C) 2009-2012 Paul Ramsey * **********************************************************************/ #include "liblwgeom_internal.h" #include "lwgeom_log.h" #include "lwtree.h" #include "measures.h" static inline int rect_node_is_leaf(const RECT_NODE *node) { return node->type == RECT_NODE_LEAF_TYPE; } /* * Support qsort of nodes for collection/multi types so nodes * are in "spatial adjacent" order prior to merging. */ static int rect_node_cmp(const void *pn1, const void *pn2) { GBOX b1, b2; RECT_NODE *n1 = *((RECT_NODE**)pn1); RECT_NODE *n2 = *((RECT_NODE**)pn2); uint64_t h1, h2; b1.flags = 0; b1.xmin = n1->xmin; b1.xmax = n1->xmax; b1.ymin = n1->ymin; b1.ymax = n1->ymax; b2.flags = 0; b2.xmin = n2->xmin; b2.xmax = n2->xmax; b2.ymin = n2->ymin; b2.ymax = n2->ymax; h1 = gbox_get_sortable_hash(&b1, 0); h2 = gbox_get_sortable_hash(&b2, 0); return h1 < h2 ? -1 : (h1 > h2 ? 1 : 0); } /** * Recurse from top of node tree and free all children. * does not free underlying point array. */ void rect_tree_free(RECT_NODE *node) { int i; if (!node) return; if (!rect_node_is_leaf(node)) { for (i = 0; i < node->i.num_nodes; i++) { rect_tree_free(node->i.nodes[i]); node->i.nodes[i] = NULL; } } lwfree(node); } static int rect_leaf_node_intersects(RECT_NODE_LEAF *n1, RECT_NODE_LEAF *n2) { const POINT2D *p1, *p2, *p3, *q1, *q2, *q3; DISTPTS dl; lw_dist2d_distpts_init(&dl, 1); switch (n1->seg_type) { case RECT_NODE_SEG_POINT: { p1 = getPoint2d_cp(n1->pa, n1->seg_num); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_pt(q1, p1, &dl); return dl.distance == 0.0; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); lw_dist2d_pt_seg(p1, q1, q2, &dl); return dl.distance == 0.0; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_pt_arc(p1, q1, q2, q3, &dl); return dl.distance == 0.0; default: lwerror("%s: unsupported segment type", __func__); break; } break; } case RECT_NODE_SEG_LINEAR: { p1 = getPoint2d_cp(n1->pa, n1->seg_num); p2 = getPoint2d_cp(n1->pa, n1->seg_num+1); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_seg(q1, p1, p2, &dl); return dl.distance == 0.0; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); return lw_segment_intersects(p1, p2, q1, q2) > 0; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_seg_arc(p1, p2, q1, q2, q3, &dl); return dl.distance == 0.0; default: lwerror("%s: unsupported segment type", __func__); break; } break; } case RECT_NODE_SEG_CIRCULAR: { p1 = getPoint2d_cp(n1->pa, n1->seg_num*2); p2 = getPoint2d_cp(n1->pa, n1->seg_num*2+1); p3 = getPoint2d_cp(n1->pa, n1->seg_num*2+2); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_arc(q1, p1, p2, p3, &dl); return dl.distance == 0.0; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); lw_dist2d_seg_arc(q1, q2, p1, p2, p3, &dl); return dl.distance == 0.0; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_arc_arc(p1, p2, p3, q1, q2, q3, &dl); return dl.distance == 0.0; default: lwerror("%s: unsupported segment type", __func__); break; } break; } default: return LW_FALSE; } return LW_FALSE; } /* * Returns 1 if segment is to the right of point. */ static inline int rect_leaf_node_segment_side(RECT_NODE_LEAF *node, const POINT2D *q, int *on_boundary) { const POINT2D *p1, *p2, *p3; switch (node->seg_type) { case RECT_NODE_SEG_LINEAR: { int side; p1 = getPoint2d_cp(node->pa, node->seg_num); p2 = getPoint2d_cp(node->pa, node->seg_num+1); side = lw_segment_side(p1, p2, q); /* Always note case where we're on boundary */ if (side == 0 && lw_pt_in_seg(q, p1, p2)) { *on_boundary = LW_TRUE; return 0; } /* Segment points up and point is on left */ if (p1->y < p2->y && side == -1 && q->y != p2->y) { return 1; } /* Segment points down and point is on right */ if (p1->y > p2->y && side == 1 && q->y != p2->y) { return 1; } /* Segment is horizontal, do we cross first point? */ if (p1->y == p2->y && q->x < p1->x) { return 1; } return 0; } case RECT_NODE_SEG_CIRCULAR: { int arc_side, seg_side; p1 = getPoint2d_cp(node->pa, node->seg_num*2); p2 = getPoint2d_cp(node->pa, node->seg_num*2+1); p3 = getPoint2d_cp(node->pa, node->seg_num*2+2); /* Always note case where we're on boundary */ arc_side = lw_arc_side(p1, p2, p3, q); if (arc_side == 0) { *on_boundary = LW_TRUE; return 0; } seg_side = lw_segment_side(p1, p3, q); if (seg_side == arc_side) { /* Segment points up and point is on left */ if (p1->y < p3->y && seg_side == -1 && q->y != p3->y) { return 1; } /* Segment points down and point is on right */ if (p1->y > p3->y && seg_side == 1 && q->y != p3->y) { return 1; } } else { /* Segment points up and point is on left */ if (p1->y < p3->y && seg_side == 1 && q->y != p3->y) { return 1; } /* Segment points down and point is on right */ if (p1->y > p3->y && seg_side == -1 && q->y != p3->y) { return 1; } /* Segment is horizontal, do we cross first point? */ if (p1->y == p3->y) { return 1; } } return 0; } default: { lwerror("%s: unsupported seg_type - %d", __func__, node->seg_type); return 0; } } return 0; } /* * Only pass the head of a ring. Either a LinearRing from a polygon * or a CompoundCurve from a CurvePolygon. * Takes a horizontal line through the ring, and adds up the * crossing directions. An "inside" point will be on the same side * ("both left" or "both right") of the edges the line crosses, * so it will have a non-zero return sum. An "outside" point will * be on both sides, and will have a zero return sum. */ static int rect_tree_ring_contains_point(RECT_NODE *node, const POINT2D *pt, int *on_boundary) { /* Only test nodes that straddle our stabline vertically */ /* and might be to the right horizontally */ if (node->ymin <= pt->y && pt->y <= node->ymax && pt->x <= node->xmax) { if (rect_node_is_leaf(node)) { return rect_leaf_node_segment_side(&node->l, pt, on_boundary); } else { int i, r = 0; for (i = 0; i < node->i.num_nodes; i++) { r += rect_tree_ring_contains_point(node->i.nodes[i], pt, on_boundary); } return r; } } return 0; } /* * Only pass in the head of an "area" type. Polygon or CurvePolygon. * Sums up containment of exterior (+1) and interior (-1) rings, so * that zero is uncontained, +1 is contained and negative is an error * (multiply contained by interior rings?) */ static int rect_tree_area_contains_point(RECT_NODE *node, const POINT2D *pt) { /* Can't do anything with a leaf node, makes no sense */ if (rect_node_is_leaf(node)) return 0; /* Iterate into area until we find ring heads */ if (node->i.ring_type == RECT_NODE_RING_NONE) { int i, sum = 0; for (i = 0; i < node->i.num_nodes; i++) sum += rect_tree_area_contains_point(node->i.nodes[i], pt); return sum; } /* See if the ring encloses the point */ else { int on_boundary = 0; int edge_crossing_count = rect_tree_ring_contains_point(node, pt, &on_boundary); /* Odd number of crossings => contained */ int contained = (edge_crossing_count % 2 == 1); /* External rings return positive containment, interior ones negative, */ /* so that a point-in-hole case nets out to zero (contained by both */ /* interior and exterior rings. */ if (node->i.ring_type == RECT_NODE_RING_INTERIOR) { return on_boundary ? 0 : -1 * contained; } else { return contained || on_boundary; } } } /* * Simple containment test for node/point inputs */ static int rect_node_bounds_point(RECT_NODE *node, const POINT2D *pt) { if (pt->y < node->ymin || pt->y > node->ymax || pt->x < node->xmin || pt->x > node->xmax) return LW_FALSE; else return LW_TRUE; } /* * Pass in arbitrary tree, get back true if point is contained or on boundary, * and false otherwise. */ int rect_tree_contains_point(RECT_NODE *node, const POINT2D *pt) { int i, c; /* Object cannot contain point if bounds don't */ if (!rect_node_bounds_point(node, pt)) return 0; switch (node->geom_type) { case POLYGONTYPE: case CURVEPOLYTYPE: return rect_tree_area_contains_point(node, pt) > 0; case MULTIPOLYGONTYPE: case MULTISURFACETYPE: case COLLECTIONTYPE: { for (i = 0; i < node->i.num_nodes; i++) { c = rect_tree_contains_point(node->i.nodes[i], pt); if (c) return LW_TRUE; } return LW_FALSE; } default: return LW_FALSE; } } /* * For area types, doing intersects and distance, we will * need to do a point-in-poly test first to find the full-contained * case where an intersection exists without any edges actually * intersecting. */ static int rect_tree_is_area(const RECT_NODE *node) { switch (node->geom_type) { case POLYGONTYPE: case CURVEPOLYTYPE: case MULTISURFACETYPE: return LW_TRUE; case COLLECTIONTYPE: { if (rect_node_is_leaf(node)) return LW_FALSE; else { int i; for (i = 0; i < node->i.num_nodes; i++) { if (rect_tree_is_area(node->i.nodes[i])) return LW_TRUE; } return LW_FALSE; } } default: return LW_FALSE; } } static RECT_NODE_SEG_TYPE lwgeomTypeArc[] = { RECT_NODE_SEG_UNKNOWN, // "Unknown" RECT_NODE_SEG_POINT, // "Point" RECT_NODE_SEG_LINEAR, // "LineString" RECT_NODE_SEG_LINEAR, // "Polygon" RECT_NODE_SEG_UNKNOWN, // "MultiPoint" RECT_NODE_SEG_LINEAR, // "MultiLineString" RECT_NODE_SEG_LINEAR, // "MultiPolygon" RECT_NODE_SEG_UNKNOWN, // "GeometryCollection" RECT_NODE_SEG_CIRCULAR, // "CircularString" RECT_NODE_SEG_UNKNOWN, // "CompoundCurve" RECT_NODE_SEG_UNKNOWN, // "CurvePolygon" RECT_NODE_SEG_UNKNOWN, // "MultiCurve" RECT_NODE_SEG_UNKNOWN, // "MultiSurface" RECT_NODE_SEG_LINEAR, // "PolyhedralSurface" RECT_NODE_SEG_LINEAR, // "Triangle" RECT_NODE_SEG_LINEAR // "Tin" }; /* * Create a new leaf node. */ static RECT_NODE * rect_node_leaf_new(const POINTARRAY *pa, int seg_num, int geom_type) { const POINT2D *p1, *p2, *p3; RECT_NODE *node; GBOX gbox; RECT_NODE_SEG_TYPE seg_type = lwgeomTypeArc[geom_type]; switch (seg_type) { case RECT_NODE_SEG_POINT: { p1 = getPoint2d_cp(pa, seg_num); gbox.xmin = gbox.xmax = p1->x; gbox.ymin = gbox.ymax = p1->y; break; } case RECT_NODE_SEG_LINEAR: { p1 = getPoint2d_cp(pa, seg_num); p2 = getPoint2d_cp(pa, seg_num+1); /* Zero length edge, doesn't get a node */ if ((p1->x == p2->x) && (p1->y == p2->y)) return NULL; gbox.xmin = FP_MIN(p1->x, p2->x); gbox.xmax = FP_MAX(p1->x, p2->x); gbox.ymin = FP_MIN(p1->y, p2->y); gbox.ymax = FP_MAX(p1->y, p2->y); break; } case RECT_NODE_SEG_CIRCULAR: { p1 = getPoint2d_cp(pa, 2*seg_num); p2 = getPoint2d_cp(pa, 2*seg_num+1); p3 = getPoint2d_cp(pa, 2*seg_num+2); /* Zero length edge, doesn't get a node */ if ((p1->x == p2->x) && (p2->x == p3->x) && (p1->y == p2->y) && (p2->y == p3->y)) return NULL; lw_arc_calculate_gbox_cartesian_2d(p1, p2, p3, &gbox); break; } default: { lwerror("%s: unsupported seg_type - %d", __func__, seg_type); return NULL; } } node = lwalloc(sizeof(RECT_NODE)); node->type = RECT_NODE_LEAF_TYPE; node->geom_type = geom_type; node->xmin = gbox.xmin; node->xmax = gbox.xmax; node->ymin = gbox.ymin; node->ymax = gbox.ymax; node->l.seg_num = seg_num; node->l.seg_type = seg_type; node->l.pa = pa; return node; } static void rect_node_internal_add_node(RECT_NODE *node, RECT_NODE *add) { if (rect_node_is_leaf(node)) lwerror("%s: call on leaf node", __func__); node->xmin = FP_MIN(node->xmin, add->xmin); node->xmax = FP_MAX(node->xmax, add->xmax); node->ymin = FP_MIN(node->ymin, add->ymin); node->ymax = FP_MAX(node->ymax, add->ymax); node->i.nodes[node->i.num_nodes++] = add; return; } static RECT_NODE * rect_node_internal_new(const RECT_NODE *seed) { RECT_NODE *node = lwalloc(sizeof(RECT_NODE)); node->xmin = seed->xmin; node->xmax = seed->xmax; node->ymin = seed->ymin; node->ymax = seed->ymax; node->geom_type = seed->geom_type; node->type = RECT_NODE_INTERNAL_TYPE; node->i.num_nodes = 0; node->i.ring_type = RECT_NODE_RING_NONE; node->i.sorted = 0; return node; } /* * We expect the incoming nodes to be in a spatially coherent * order. For incoming nodes derived from point arrays, * the very fact that they are * a vertex list implies a reasonable ordering: points nearby in * the list will be nearby in space. For incoming nodes from higher * level structures (collections, etc) the caller should sort the * nodes using a z-order first, so that this merge step results in a * spatially coherent structure. */ static RECT_NODE * rect_nodes_merge(RECT_NODE ** nodes, uint32_t num_nodes) { if (num_nodes < 1) { return NULL; } while (num_nodes > 1) { uint32_t i, k = 0; RECT_NODE *node = NULL; for (i = 0; i < num_nodes; i++) { if (!node) node = rect_node_internal_new(nodes[i]); rect_node_internal_add_node(node, nodes[i]); if (node->i.num_nodes == RECT_NODE_SIZE) { nodes[k++] = node; node = NULL; } } if (node) nodes[k++] = node; num_nodes = k; } return nodes[0]; } /* * Build a tree of nodes from a point array, one node per edge. */ RECT_NODE * rect_tree_from_ptarray(const POINTARRAY *pa, int geom_type) { int num_edges = 0, i = 0, j = 0; RECT_NODE_SEG_TYPE seg_type = lwgeomTypeArc[geom_type]; RECT_NODE **nodes = NULL; RECT_NODE *tree = NULL; /* No-op on empty ring/line/pt */ if ( pa->npoints < 1 ) return NULL; /* For arcs, 3 points per edge, for lines, 2 per edge */ switch(seg_type) { case RECT_NODE_SEG_POINT: return rect_node_leaf_new(pa, 0, geom_type); break; case RECT_NODE_SEG_LINEAR: num_edges = pa->npoints - 1; break; case RECT_NODE_SEG_CIRCULAR: num_edges = (pa->npoints - 1)/2; break; default: lwerror("%s: unsupported seg_type - %d", __func__, seg_type); } /* First create a flat list of nodes, one per edge. */ nodes = lwalloc(sizeof(RECT_NODE*) * num_edges); for (i = 0; i < num_edges; i++) { RECT_NODE *node = rect_node_leaf_new(pa, i, geom_type); if (node) /* Not zero length? */ nodes[j++] = node; } /* Merge the list into a tree */ tree = rect_nodes_merge(nodes, j); /* Free the old list structure, leaving the tree in place */ lwfree(nodes); /* Return top of tree */ return tree; } LWGEOM * rect_tree_to_lwgeom(const RECT_NODE *node) { LWGEOM *poly = (LWGEOM*)lwpoly_construct_envelope(0, node->xmin, node->ymin, node->xmax, node->ymax); if (rect_node_is_leaf(node)) { return poly; } else { int i; LWCOLLECTION *col = lwcollection_construct_empty(COLLECTIONTYPE, 0, 0, 0); lwcollection_add_lwgeom(col, poly); for (i = 0; i < node->i.num_nodes; i++) { lwcollection_add_lwgeom(col, rect_tree_to_lwgeom(node->i.nodes[i])); } return (LWGEOM*)col; } } char * rect_tree_to_wkt(const RECT_NODE *node) { LWGEOM *geom = rect_tree_to_lwgeom(node); char *wkt = lwgeom_to_wkt(geom, WKT_ISO, 12, 0); lwgeom_free(geom); return wkt; } void rect_tree_printf(const RECT_NODE *node, int depth) { printf("%*s----\n", depth, ""); printf("%*stype: %d\n", depth, "", node->type); printf("%*sgeom_type: %d\n", depth, "", node->geom_type); printf("%*sbox: %g %g, %g %g\n", depth, "", node->xmin, node->ymin, node->xmax, node->ymax); if (node->type == RECT_NODE_LEAF_TYPE) { printf("%*sseg_type: %d\n", depth, "", node->l.seg_type); printf("%*sseg_num: %d\n", depth, "", node->l.seg_num); } else { int i; for (i = 0; i < node->i.num_nodes; i++) { rect_tree_printf(node->i.nodes[i], depth+2); } } } static RECT_NODE * rect_tree_from_lwpoint(const LWGEOM *lwgeom) { const LWPOINT *lwpt = (const LWPOINT*)lwgeom; return rect_tree_from_ptarray(lwpt->point, lwgeom->type); } static RECT_NODE * rect_tree_from_lwline(const LWGEOM *lwgeom) { const LWLINE *lwline = (const LWLINE*)lwgeom; return rect_tree_from_ptarray(lwline->points, lwgeom->type); } static RECT_NODE * rect_tree_from_lwpoly(const LWGEOM *lwgeom) { RECT_NODE **nodes; RECT_NODE *tree; uint32_t i, j = 0; const LWPOLY *lwpoly = (const LWPOLY*)lwgeom; if (lwpoly->nrings < 1) return NULL; nodes = lwalloc(sizeof(RECT_NODE*) * lwpoly->nrings); for (i = 0; i < lwpoly->nrings; i++) { RECT_NODE *node = rect_tree_from_ptarray(lwpoly->rings[i], lwgeom->type); if (node) { node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; nodes[j++] = node; } } tree = rect_nodes_merge(nodes, j); tree->geom_type = lwgeom->type; lwfree(nodes); return tree; } static RECT_NODE * rect_tree_from_lwcurvepoly(const LWGEOM *lwgeom) { RECT_NODE **nodes; RECT_NODE *tree; uint32_t i, j = 0; const LWCURVEPOLY *lwcol = (const LWCURVEPOLY*)lwgeom; if (lwcol->nrings < 1) return NULL; nodes = lwalloc(sizeof(RECT_NODE*) * lwcol->nrings); for (i = 0; i < lwcol->nrings; i++) { RECT_NODE *node = rect_tree_from_lwgeom(lwcol->rings[i]); if (node) { /* * In the case of arc circle, it's possible for a ring to consist * of a single closed edge. That will arrive as a leaf node. We * need to wrap that node in an internal node with an appropriate * ring type so all the other code can try and make sense of it. */ if (node->type == RECT_NODE_LEAF_TYPE) { RECT_NODE *internal = rect_node_internal_new(node); rect_node_internal_add_node(internal, node); node = internal; } /* Each subcomponent is a ring */ node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; nodes[j++] = node; } } /* Put the top nodes in a z-order curve for a spatially coherent */ /* tree after node merge */ qsort(nodes, j, sizeof(RECT_NODE*), rect_node_cmp); tree = rect_nodes_merge(nodes, j); tree->geom_type = lwgeom->type; lwfree(nodes); return tree; } static RECT_NODE * rect_tree_from_lwcollection(const LWGEOM *lwgeom) { RECT_NODE **nodes; RECT_NODE *tree; uint32_t i, j = 0; const LWCOLLECTION *lwcol = (const LWCOLLECTION*)lwgeom; if (lwcol->ngeoms < 1) return NULL; /* Build one tree for each sub-geometry, then below */ /* we merge the root notes of those trees to get a single */ /* top node for the collection */ nodes = lwalloc(sizeof(RECT_NODE*) * lwcol->ngeoms); for (i = 0; i < lwcol->ngeoms; i++) { RECT_NODE *node = rect_tree_from_lwgeom(lwcol->geoms[i]); if (node) { /* Curvepolygons are collections where the sub-geometries */ /* are the rings, and will need to doint point-in-poly */ /* tests in order to do intersects and distance calculations */ /* correctly */ if (lwgeom->type == CURVEPOLYTYPE) node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; nodes[j++] = node; } } /* Sort the nodes using a z-order curve, so that merging the nodes */ /* gives a spatially coherent tree (near things are in near nodes) */ /* Note: CompoundCurve has edges already spatially organized, no */ /* sorting needed */ if (lwgeom->type != COMPOUNDTYPE) qsort(nodes, j, sizeof(RECT_NODE*), rect_node_cmp); tree = rect_nodes_merge(nodes, j); tree->geom_type = lwgeom->type; lwfree(nodes); return tree; } RECT_NODE * rect_tree_from_lwgeom(const LWGEOM *lwgeom) { switch(lwgeom->type) { case POINTTYPE: return rect_tree_from_lwpoint(lwgeom); case TRIANGLETYPE: case CIRCSTRINGTYPE: case LINETYPE: return rect_tree_from_lwline(lwgeom); case POLYGONTYPE: return rect_tree_from_lwpoly(lwgeom); case CURVEPOLYTYPE: return rect_tree_from_lwcurvepoly(lwgeom); case COMPOUNDTYPE: case MULTICURVETYPE: case MULTISURFACETYPE: case MULTIPOINTTYPE: case MULTILINETYPE: case MULTIPOLYGONTYPE: case POLYHEDRALSURFACETYPE: case TINTYPE: case COLLECTIONTYPE: return rect_tree_from_lwcollection(lwgeom); default: lwerror("%s: Unknown geometry type: %s", __func__, lwtype_name(lwgeom->type)); return NULL; } return NULL; } /* * Get an actual coordinate point from a tree to use * for point-in-polygon testing. */ static const POINT2D * rect_tree_get_point(const RECT_NODE *node) { if (!node) return NULL; if (rect_node_is_leaf(node)) return getPoint2d_cp(node->l.pa, 0); else return rect_tree_get_point(node->i.nodes[0]); } static inline int rect_node_intersects(const RECT_NODE *n1, const RECT_NODE *n2) { if (n1->xmin > n2->xmax || n2->xmin > n1->xmax || n1->ymin > n2->ymax || n2->ymin > n1->ymax) { return 0; } else { return 1; } } #if POSTGIS_DEBUG_LEVEL >= 4 static char * rect_node_to_str(const RECT_NODE *n) { char *buf = lwalloc(256); snprintf(buf, 256, "(%.9g %.9g,%.9g %.9g)", n->xmin, n->ymin, n->xmax, n->ymax); return buf; } #endif /* * Work down to leaf nodes, until we find a pair of leaf nodes * that intersect. Prune branches that do not intersect. */ static int rect_tree_intersects_tree_recursive(RECT_NODE *n1, RECT_NODE *n2) { int i, j; #if POSTGIS_DEBUG_LEVEL >= 4 char *n1_str = rect_node_to_str(n1); char *n2_str = rect_node_to_str(n2); LWDEBUGF(4,"n1 %s n2 %s", n1, n2); lwfree(n1_str); lwfree(n2_str); #endif /* There can only be an edge intersection if the rectangles overlap */ if (rect_node_intersects(n1, n2)) { LWDEBUG(4," interaction found"); /* We can only test for a true intersection if the nodes are both leaf nodes */ if (rect_node_is_leaf(n1) && rect_node_is_leaf(n2)) { LWDEBUG(4," leaf node test"); /* Check for true intersection */ return rect_leaf_node_intersects(&n1->l, &n2->l); } else if (rect_node_is_leaf(n2) && !rect_node_is_leaf(n1)) { for (i = 0; i < n1->i.num_nodes; i++) { if (rect_tree_intersects_tree_recursive(n1->i.nodes[i], n2)) return LW_TRUE; } } else if (rect_node_is_leaf(n1) && !rect_node_is_leaf(n2)) { for (i = 0; i < n2->i.num_nodes; i++) { if (rect_tree_intersects_tree_recursive(n2->i.nodes[i], n1)) return LW_TRUE; } } else { for (j = 0; j < n1->i.num_nodes; j++) { for (i = 0; i < n2->i.num_nodes; i++) { if (rect_tree_intersects_tree_recursive(n2->i.nodes[i], n1->i.nodes[j])) return LW_TRUE; } } } } return LW_FALSE; } int rect_tree_intersects_tree(RECT_NODE *n1, RECT_NODE *n2) { /* * It is possible for an area to intersect another object * without any edges intersecting, if the object is fully contained. * If that is so, then any point in the object will be contained, * so we do a quick point-in-poly test first for those cases */ if (rect_tree_is_area(n1) && rect_tree_contains_point(n1, rect_tree_get_point(n2))) { return LW_TRUE; } if (rect_tree_is_area(n2) && rect_tree_contains_point(n2, rect_tree_get_point(n1))) { return LW_TRUE; } /* * Not contained, so intersection can only happen if * edges actually intersect. */ return rect_tree_intersects_tree_recursive(n1, n2); } /* * For slightly more speed when doing distance comparisons, * where we only care if d1 < d2 and not what the actual value * of d1 or d2 is, we use the squared distance and avoid the * sqrt() */ static inline double distance_sq(double x1, double y1, double x2, double y2) { double dx = x1-x2; double dy = y1-y2; return dx*dx + dy*dy; } static inline double distance(double x1, double y1, double x2, double y2) { return sqrt(distance_sq(x1, y1, x2, y2)); } /* * The closest any two objects in two nodes can be is the smallest * distance between the nodes themselves. */ static inline double rect_node_min_distance(const RECT_NODE *n1, const RECT_NODE *n2) { int left = n1->xmin > n2->xmax; int right = n1->xmax < n2->xmin; int bottom = n1->ymin > n2->ymax; int top = n1->ymax < n2->ymin; //lwnotice("rect_node_min_distance"); if (top && left) return distance(n1->xmin, n1->ymax, n2->xmax, n2->ymin); else if (top && right) return distance(n1->xmax, n1->ymax, n2->xmin, n2->ymin); else if (bottom && left) return distance(n1->xmin, n1->ymin, n2->xmax, n2->ymax); else if (bottom && right) return distance(n1->xmax, n1->ymin, n2->xmin, n2->ymax); else if (left) return n1->xmin - n2->xmax; else if (right) return n2->xmin - n1->xmax; else if (bottom) return n1->ymin - n2->ymax; else if (top) return n2->ymin - n1->ymax; else return 0.0; return 0.0; } /* * The furthest apart the objects in two nodes can be is if they * are at opposite corners of the bbox that contains both nodes */ static inline double rect_node_max_distance(const RECT_NODE *n1, const RECT_NODE *n2) { double xmin = FP_MIN(n1->xmin, n2->xmin); double ymin = FP_MIN(n1->ymin, n2->ymin); double xmax = FP_MAX(n1->xmax, n2->xmax); double ymax = FP_MAX(n1->ymax, n2->ymax); double dx = xmax - xmin; double dy = ymax - ymin; //lwnotice("rect_node_max_distance"); return sqrt(dx*dx + dy*dy); } /* * Leaf nodes represent individual edges from the original shape. * As such, they can be either points (if original was a (multi)point) * two-point straight edges (for linear types), or * three-point circular arcs (for curvilinear types). * The distance calculations for each possible combination of primitive * edges is different, so there's a big case switch in here to match * up the right combination of inputs to the right distance calculation. */ static double rect_leaf_node_distance(const RECT_NODE_LEAF *n1, const RECT_NODE_LEAF *n2, RECT_TREE_DISTANCE_STATE *state) { const POINT2D *p1, *p2, *p3, *q1, *q2, *q3; DISTPTS dl; //lwnotice("rect_leaf_node_distance, %d<->%d", n1->seg_num, n2->seg_num); lw_dist2d_distpts_init(&dl, DIST_MIN); switch (n1->seg_type) { case RECT_NODE_SEG_POINT: { p1 = getPoint2d_cp(n1->pa, n1->seg_num); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_pt(q1, p1, &dl); break; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); lw_dist2d_pt_seg(p1, q1, q2, &dl); break; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_pt_arc(p1, q1, q2, q3, &dl); break; default: lwerror("%s: unsupported segment type", __func__); } break; } case RECT_NODE_SEG_LINEAR: { p1 = getPoint2d_cp(n1->pa, n1->seg_num); p2 = getPoint2d_cp(n1->pa, n1->seg_num+1); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_seg(q1, p1, p2, &dl); break; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); lw_dist2d_seg_seg(q1, q2, p1, p2, &dl); // lwnotice( // "%d\tLINESTRING(%g %g,%g %g)\t%d\tLINESTRING(%g %g,%g %g)\t%g\t%g\t%g", // n1->seg_num, // p1->x, p1->y, p2->x, p2->y, // n2->seg_num, // q1->x, q1->y, q2->x, q2->y, // dl.distance, state->min_dist, state->max_dist); break; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_seg_arc(p1, p2, q1, q2, q3, &dl); break; default: lwerror("%s: unsupported segment type", __func__); } break; } case RECT_NODE_SEG_CIRCULAR: { p1 = getPoint2d_cp(n1->pa, n1->seg_num*2); p2 = getPoint2d_cp(n1->pa, n1->seg_num*2+1); p3 = getPoint2d_cp(n1->pa, n1->seg_num*2+2); switch (n2->seg_type) { case RECT_NODE_SEG_POINT: q1 = getPoint2d_cp(n2->pa, n2->seg_num); lw_dist2d_pt_arc(q1, p1, p2, p3, &dl); break; case RECT_NODE_SEG_LINEAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num); q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); lw_dist2d_seg_arc(q1, q2, p1, p2, p3, &dl); break; case RECT_NODE_SEG_CIRCULAR: q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); lw_dist2d_arc_arc(p1, p2, p3, q1, q2, q3, &dl); break; default: lwerror("%s: unsupported segment type", __func__); } break; } default: lwerror("%s: unsupported segment type", __func__); } /* If this is a new global minima, save it */ if (dl.distance < state->min_dist) { state->min_dist = dl.distance; state->p1 = dl.p1; state->p2 = dl.p2; } return dl.distance; } static int rect_tree_node_sort_cmp(const void *a, const void *b) { RECT_NODE *n1 = *(RECT_NODE**)a; RECT_NODE *n2 = *(RECT_NODE**)b; if (n1->d < n2->d) return -1; else if (n1->d > n2->d) return 1; else return 0; } /* Calculate the center of a node */ static inline void rect_node_to_p2d(const RECT_NODE *n, POINT2D *pt) { pt->x = (n->xmin + n->xmax)/2; pt->y = (n->ymin + n->ymax)/2; } /* * (If necessary), sort the children of each node in * order of their distance from the enter of the other node. */ static void rect_tree_node_sort(RECT_NODE *n1, RECT_NODE *n2) { int i; RECT_NODE *n; POINT2D c1, c2, c; if (!rect_node_is_leaf(n1) && ! n1->i.sorted) { rect_node_to_p2d(n2, &c2); /* Distance of each child from center of other node */ for (i = 0; i < n1->i.num_nodes; i++) { n = n1->i.nodes[i]; rect_node_to_p2d(n, &c); n->d = distance2d_sqr_pt_pt(&c2, &c); } /* Sort the children by distance */ n1->i.sorted = 1; qsort(n1->i.nodes, n1->i.num_nodes, sizeof(RECT_NODE*), rect_tree_node_sort_cmp); } if (!rect_node_is_leaf(n2) && ! n2->i.sorted) { rect_node_to_p2d(n1, &c1); /* Distance of each child from center of other node */ for (i = 0; i < n2->i.num_nodes; i++) { n = n2->i.nodes[i]; rect_node_to_p2d(n, &c); n->d = distance2d_sqr_pt_pt(&c1, &c); } /* Sort the children by distance */ n2->i.sorted = 1; qsort(n2->i.nodes, n2->i.num_nodes, sizeof(RECT_NODE*), rect_tree_node_sort_cmp); } } static double rect_tree_distance_tree_recursive(RECT_NODE *n1, RECT_NODE *n2, RECT_TREE_DISTANCE_STATE *state) { double min, max; /* Short circuit if we've already hit the minimum */ if (state->min_dist < state->threshold || state->min_dist == 0.0) return state->min_dist; /* If your minimum is greater than anyone's maximum, you can't hold the winner */ min = rect_node_min_distance(n1, n2); if (min > state->max_dist) { //lwnotice("pruning pair %p, %p", n1, n2); LWDEBUGF(4, "pruning pair %p, %p", n1, n2); return FLT_MAX; } /* If your maximum is a new low, we'll use that as our new global tolerance */ max = rect_node_max_distance(n1, n2); if (max < state->max_dist) state->max_dist = max; /* Both leaf nodes, do a real distance calculation */ if (rect_node_is_leaf(n1) && rect_node_is_leaf(n2)) { return rect_leaf_node_distance(&n1->l, &n2->l, state); } /* Recurse into nodes */ else { int i, j; double d_min = FLT_MAX; rect_tree_node_sort(n1, n2); if (rect_node_is_leaf(n1) && !rect_node_is_leaf(n2)) { for (i = 0; i < n2->i.num_nodes; i++) { min = rect_tree_distance_tree_recursive(n1, n2->i.nodes[i], state); d_min = FP_MIN(d_min, min); } } else if (rect_node_is_leaf(n2) && !rect_node_is_leaf(n1)) { for (i = 0; i < n1->i.num_nodes; i++) { min = rect_tree_distance_tree_recursive(n1->i.nodes[i], n2, state); d_min = FP_MIN(d_min, min); } } else { for (i = 0; i < n1->i.num_nodes; i++) { for (j = 0; j < n2->i.num_nodes; j++) { min = rect_tree_distance_tree_recursive(n1->i.nodes[i], n2->i.nodes[j], state); d_min = FP_MIN(d_min, min); } } } return d_min; } } double rect_tree_distance_tree(RECT_NODE *n1, RECT_NODE *n2, double threshold) { double distance; RECT_TREE_DISTANCE_STATE state; /* * It is possible for an area to intersect another object * without any edges intersecting, if the object is fully contained. * If that is so, then any point in the object will be contained, * so we do a quick point-in-poly test first for those cases */ if (rect_tree_is_area(n1) && rect_tree_contains_point(n1, rect_tree_get_point(n2))) { return 0.0; } if (rect_tree_is_area(n2) && rect_tree_contains_point(n2, rect_tree_get_point(n1))) { return 0.0; } state.threshold = threshold; state.min_dist = FLT_MAX; state.max_dist = FLT_MAX; distance = rect_tree_distance_tree_recursive(n1, n2, &state); // *p1 = state.p1; // *p2 = state.p2; return distance; }