dms polygon operators
Back to Geometric functions and polygon operators
The dms_ operators are the polygon set operations that GeoDMS implements itself, as a snap rounding sweep, next to the four geometry libraries (geos, boost geometry, boost polygon and cgal). They differ from those in the one respect that matters when the data is not perfect: they accept polygons that are not valid. Rings may cross themselves, touch themselves, overlap each other, touch each other or fail to close, and an optional grid size merges vertices and slivers that are too close to be meant apart.
Since GeoDMS 20.20.0.
the operators
Element by element, on two polygon arrays:
- dms_intersect - element-wise intersection
- dms_union - element-wise union
- dms_difference - element-wise difference (A minus B)
- dms_xor - element-wise symmetric difference
Each takes two polygon data items of the same Value type and Values unit, over the same Domain unit or with one of them a Parameter, and each takes an optional third argument, the grid size.
Cleaning, dissolving and splitting:
- dms_polygon - read each element under the even-odd rule and write it back as a valid polygon
- dms_union_polygon - dissolve all polygons into one, optionally grouped by a partition attribute
- dms_split_polygon - split multi-polygons into their single polygons, each a separate domain entry
- dms_split_union_polygon - dissolve, then split into single polygons
Overlay and connectivity:
- dms_overlay_polygon - all pairs of two polygon sets, or within one, with the intersection geometry of each pair
- dms_polygon_connectivity - the same pairs, as relations only
Growing and shrinking:
- dms_minkowski_sum - grow each geometry by a kernel: the kernel as a polygon argument, or one of six named shapes at a given size
- dms_minkowski_difference - shrink each geometry by a kernel; the eroding counterpart
All six polygon value types are supported throughout: Spoint, Ipoint, Wpoint, Upoint, Fpoint and Dpoint. That is wider than any of the libraries, where geos is dpoint-only and boost polygon integer-only.
what an operand may look like: the even-odd rule
The Polygon value of one element is a sequence of points, with the rings of a multi polygon strung together by zero-width corridors. The dms_ operators read that sequence as one closed walk: consecutive points are line segments, and the last point is connected back to the first. Which part of the plane is then inside is decided by the even-odd rule:
a point lies inside the polygon when a ray from that point to infinity crosses the boundary an odd number of times.
Three consequences a modeller can use:
- Ring orientation does not matter on input. A shell given counter-clockwise, a hole given clockwise, or a feature whose rings are only partly flipped, are all read the same way. This is what has_correct_winding and fix_winding_order exist for on the other families.
- A self-intersecting ring has a defined meaning. A ring in the shape of a bow tie is two triangles, since the middle is crossed twice. The usual definition of a valid polygon, the one the geometry libraries implement, leaves such a ring without an interior, which is why they refuse it or repair it first.
- An unclosed ring is closed and a corridor walked twice cancels, because a segment traversed in both directions does not change the crossing parity.
The result is always a valid polygon: clockwise shells and counter-clockwise holes, the order that Area is positive for.
the grid, and the tolerance
Every vertex of the input and every computed intersection is snapped to a grid, and that grid is the tolerance. Two vertices in the same cell become one vertex, and a sliver thinner than a cell collapses and is dropped: polygon in, polygon out, so a collapsed line or point is not part of the result.
| form | the grid | use |
|---|---|---|
| two arguments, integer coordinates | the integer grid itself | no vertex is moved and every vertex of the result is an integer point |
| two arguments, float coordinates | a power of two, the coarser of the last-place unit of the largest coordinate and the extent divided by 2^36: the extent of the element pair for the binary operators, and for a dissolve the declared range of the Values unit, or the extent of all the data when the unit declares none | far below the precision of geographic data, so the answer is the exact one |
| three arguments | the given grid size in coordinate units | the tolerance at which near-coincident vertices merge and slivers disappear |
The grid size of the three-argument form is a Parameter, one number for the whole attribute, so that two neighbours snap a shared boundary the same way. It must be positive, a whole number for integer coordinates, and coarse enough for the operands: at most 2^36 cells in either direction over their joint extent, which a finer grid is refused for by name.
A dissolve, dms_union_polygon or dms_split_union_polygon, derives its grid once for the whole call rather than per element pair: from the declared range of the values unit when it has one, and from the extent of all the elements otherwise. Every element and every intermediate then lands on one lattice, so the result depends neither on the order of the elements nor on how the attribute is tiled. A values unit ranged over the whole of the Netherlands in metres gives a cell of about 8 micrometres.
what the result looks like
- Rings carry no vertex that lies on the straight line between its neighbours, so a shared edge that disappears in a union takes its collinear corners with it.
- Shells and holes start at their lexicographically first vertex and are written in that order, so the result does not depend on the order of the operands or on where the input rings started:
dms_union(A, B)anddms_union(B, A)are the same sequence, anddms_xor(A, A)is empty. - An element whose result is empty is an empty sequence, not a zero-area remnant; an element with an undefined operand is undefined.
which family to use
| valid input required | coordinates | fault tolerance | |
|---|---|---|---|
| dms_ | no | all six point types | the even-odd rule, plus a grid size as tolerance |
| geos_ | yes, repaired with MakeValid when needed | dpoint | a repair pass before the operation |
| bg_ | yes | all six point types | a repair pass through GEOS |
| bp_ | yes | integer only, below 2^25 | none |
| cgal_ | yes | all six point types | exact arithmetic, slow |
For data that is already valid, the geos_ operators remain the first choice; they are faster and carry the full OGC operation set. Reach for dms_ when the source is not clean, or when a tolerance is what the model needs.
diagnostics
At minor trace level each call reports how much snapping it did, for instance:
dms_union: 1240 sequences; 87 crossings snapped to the grid, 0 extra noding rounds, 0 undefined results
The crossing count is how many intersections were moved onto the grid, the noding rounds are the passes in which that snapping created a new incidence that had to be resolved, and the last number counts the elements that came out undefined.
what is not in this family
The infix operators *, +, - and ^ keep their boost polygon binding for integer coordinates (bp_intersect and its family) and their geos binding for float coordinates (geos_intersect and its family). To get the fault-tolerant reading, call the operator by name.
There are no dms_ buffer or simplify operators; use the geos ones, which need valid input, or clean first with dms_polygon.
examples
- Cleaning invalid polygons - cleaning a source, reading a self-intersecting ring, closing slivers with a grid size, cutting a hole
- The configurations that pin these operators ship with GeoDMS in
examples\testcases:oper_dms_overlay.dms(the areas of the four operations, the algebraic identities, agreement with thebp_andgeos_results),oper_dms_overlay_types.dms(all six point types),oper_dms_overlay_topology.dms(the multi polygon layout: several shells, a hole, an island in a lake, rings that touch)oper_dms_overlay_neg1.dmsto_neg5.dms(the four grid refusals and the values unit check),oper_dms_union_counts.dms(the dissolve: overlap is union, a ring wound the wrong way adds, partitions, the grid from a ranged and from an unranged dpoint unit),oper_dms_union_steep.dms(a snapped steep edge, against the pairwise result) andoper_dms_crossings.dms(the crossing sweep on its degenerate cases: several edges through one crossing, crossings on lattice points, vertical edges, collinear edges, a self-crossing ring).
see also
- polygon operators - every polygon operator, by family
- Geos, boost polygon, cgal - the library families
- Mul (polygon intersection), Add (union), Sub (difference) - the infix operators