555 lines
24 KiB
HTML
555 lines
24 KiB
HTML
<!DOCTYPE html>
|
|
<html><head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
<link href="sqlite.css" rel="stylesheet">
|
|
<title>The Geopoly Interface To The SQLite R*Tree Module</title>
|
|
<!-- path= -->
|
|
</head>
|
|
<body>
|
|
<div class=nosearch>
|
|
<a href="index.html">
|
|
<img class="logo" src="images/sqlite370_banner.gif" alt="SQLite" border="0">
|
|
</a>
|
|
<div><!-- IE hack to prevent disappearing logo --></div>
|
|
<div class="tagline desktoponly">
|
|
Small. Fast. Reliable.<br>Choose any three.
|
|
</div>
|
|
<div class="menu mainmenu">
|
|
<ul>
|
|
<li><a href="index.html">Home</a>
|
|
<li class='mobileonly'><a href="javascript:void(0)" onclick='toggle_div("submenu")'>Menu</a>
|
|
<li class='wideonly'><a href='about.html'>About</a>
|
|
<li class='desktoponly'><a href="docs.html">Documentation</a>
|
|
<li class='desktoponly'><a href="download.html">Download</a>
|
|
<li class='wideonly'><a href='copyright.html'>License</a>
|
|
<li class='desktoponly'><a href="support.html">Support</a>
|
|
<li class='desktoponly'><a href="prosupport.html">Purchase</a>
|
|
<li class='search' id='search_menubutton'>
|
|
<a href="javascript:void(0)" onclick='toggle_search()'>Search</a>
|
|
</ul>
|
|
</div>
|
|
<div class="menu submenu" id="submenu">
|
|
<ul>
|
|
<li><a href='about.html'>About</a>
|
|
<li><a href='docs.html'>Documentation</a>
|
|
<li><a href='download.html'>Download</a>
|
|
<li><a href='support.html'>Support</a>
|
|
<li><a href='prosupport.html'>Purchase</a>
|
|
</ul>
|
|
</div>
|
|
<div class="searchmenu" id="searchmenu">
|
|
<form method="GET" action="search">
|
|
<select name="s" id="searchtype">
|
|
<option value="d">Search Documentation</option>
|
|
<option value="c">Search Changelog</option>
|
|
</select>
|
|
<input type="text" name="q" id="searchbox" value="">
|
|
<input type="submit" value="Go">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggle_div(nm) {
|
|
var w = document.getElementById(nm);
|
|
if( w.style.display=="block" ){
|
|
w.style.display = "none";
|
|
}else{
|
|
w.style.display = "block";
|
|
}
|
|
}
|
|
function toggle_search() {
|
|
var w = document.getElementById("searchmenu");
|
|
if( w.style.display=="block" ){
|
|
w.style.display = "none";
|
|
} else {
|
|
w.style.display = "block";
|
|
setTimeout(function(){
|
|
document.getElementById("searchbox").focus()
|
|
}, 30);
|
|
}
|
|
}
|
|
function div_off(nm){document.getElementById(nm).style.display="none";}
|
|
window.onbeforeunload = function(e){div_off("submenu");}
|
|
/* Disable the Search feature if we are not operating from CGI, since */
|
|
/* Search is accomplished using CGI and will not work without it. */
|
|
if( !location.origin || !location.origin.match || !location.origin.match(/http/) ){
|
|
document.getElementById("search_menubutton").style.display = "none";
|
|
}
|
|
/* Used by the Hide/Show button beside syntax diagrams, to toggle the */
|
|
function hideorshow(btn,obj){
|
|
var x = document.getElementById(obj);
|
|
var b = document.getElementById(btn);
|
|
if( x.style.display!='none' ){
|
|
x.style.display = 'none';
|
|
b.innerHTML='show';
|
|
}else{
|
|
x.style.display = '';
|
|
b.innerHTML='hide';
|
|
}
|
|
return false;
|
|
}
|
|
</script>
|
|
</div>
|
|
<div class=fancy>
|
|
<div class=nosearch>
|
|
<div class="fancy_title">
|
|
The Geopoly Interface To The SQLite R*Tree Module
|
|
</div>
|
|
<div class="fancy_toc">
|
|
<a onclick="toggle_toc()">
|
|
<span class="fancy_toc_mark" id="toc_mk">►</span>
|
|
Table Of Contents
|
|
</a>
|
|
<div id="toc_sub"><div class="fancy-toc1"><a href="#overview">1. Overview</a></div>
|
|
<div class="fancy-toc2"><a href="#geojson">1.1. GeoJSON</a></div>
|
|
<div class="fancy-toc2"><a href="#binary_storage_format">1.2. Binary storage format</a></div>
|
|
<div class="fancy-toc1"><a href="#using_the_geopoly_extension">2. Using The Geopoly Extension</a></div>
|
|
<div class="fancy-toc2"><a href="#queries">2.1. Queries</a></div>
|
|
<div class="fancy-toc1"><a href="#special_functions">3. Special Functions</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_overlap_p1_p2_function">3.1. The geopoly_overlap(P1,P2) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_within_p1_p2_function">3.2. The geopoly_within(P1,P2) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_area_p_function">3.3. The geopoly_area(P) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_blob_p_function">3.4. The geopoly_blob(P) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_json_p_function">3.5. The geopoly_json(P) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_svg_p_function">3.6. The geopoly_svg(P,...) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_bbox_p_and_geopoly_group_bbox_p_functions">3.7. The geopoly_bbox(P) and geopoly_group_bbox(P) Functions</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_contains_point_p_x_y_function">3.8. The geopoly_contains_point(P,X,Y) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_xform_p_a_b_c_d_e_f_function">3.9. The geopoly_xform(P,A,B,C,D,E,F) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_regular_x_y_r_n_function">3.10. The geopoly_regular(X,Y,R,N) Function</a></div>
|
|
<div class="fancy-toc2"><a href="#the_geopoly_ccw_j_function">3.11. The geopoly_ccw(J) Function</a></div>
|
|
<div class="fancy-toc1"><a href="#implementation_details">4. Implementation Details</a></div>
|
|
<div class="fancy-toc2"><a href="#binary_encoding_of_polygons">4.1. Binary Encoding of Polygons</a></div>
|
|
<div class="fancy-toc2"><a href="#shadow_tables">4.2. Shadow Tables</a></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
function toggle_toc(){
|
|
var sub = document.getElementById("toc_sub")
|
|
var mk = document.getElementById("toc_mk")
|
|
if( sub.style.display!="block" ){
|
|
sub.style.display = "block";
|
|
mk.innerHTML = "▼";
|
|
} else {
|
|
sub.style.display = "none";
|
|
mk.innerHTML = "►";
|
|
}
|
|
}
|
|
</script>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h1 id="overview"><span>1. </span>Overview</h1>
|
|
|
|
<p>
|
|
The Geopoly module is an alternative interface to the <a href="rtree.html">R-Tree extension</a> that uses
|
|
the <a href="http://geojson.org">GeoJSON</a> notation
|
|
(<a href="https://tools.ietf.org/html/rfc7946">RFC-7946</a>) to describe two-dimensional
|
|
polygons. Geopoly includes functions for detecting when one polygon is
|
|
contained within or overlaps with another, for computing the
|
|
area enclosed by a polygon, for doing linear transformations of polygons,
|
|
for rendering polygons as
|
|
<a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">SVG</a>, and other
|
|
similar operations.
|
|
|
|
</p><p>
|
|
The source code for Geopoly is included in the <a href="amalgamation.html">amalgamation</a> but is not
|
|
included in the library unless the <a href="compile.html#enable_geopoly">-DSQLITE_ENABLE_GEOPOLY</a> compile-time option
|
|
is used.
|
|
|
|
</p><p>
|
|
Geopoly operates on "simple" polygons - that is, polygons for which
|
|
the boundary does not intersect itself. Geopoly thus extends the capabilities
|
|
of the <a href="rtree.html">R-Tree extension</a> which can only deal with rectangular areas.
|
|
On the other hand, the <a href="rtree.html">R-Tree extension</a> is
|
|
able to handle between 1 and 5 coordinate dimensions, whereas Geopoly is restricted
|
|
to 2-dimensional shapes only.
|
|
|
|
</p><p>
|
|
Each polygon in the Geopoly module can be associated with an arbitrary
|
|
number of auxiliary data fields.
|
|
|
|
</p><h2 id="geojson"><span>1.1. </span>GeoJSON</h2>
|
|
|
|
<p>The <a href="https://tools.ietf.org/html/rfc7946">GeoJSON standard</a> is syntax for
|
|
exchanging geospatial information using JSON. GeoJSON is a rich standard
|
|
that can describe nearly any kind of geospatial content.
|
|
|
|
</p><p>The Geopoly module only understands
|
|
a small subset of GeoJSON, but a critical subset.
|
|
In particular, GeoJSON understands
|
|
the JSON array of vertexes that describes a simple polygon.
|
|
|
|
</p><p>A polygon is defined by its vertexes.
|
|
Each vertex is a JSON array of two numeric values which are the
|
|
X and Y coordinates of the vertex.
|
|
A polygon is a JSON array of at least four of these vertexes,
|
|
and hence is an array of arrays.
|
|
The first and last vertex in the array must be the same.
|
|
The polygon follows the right-hand rule: When tracing a line from
|
|
one vertex to the next, the area to the right of the line is outside
|
|
of the polygon and the area to the left is inside the polygon.
|
|
In other words, the net rotation of the vertexes is counter-clockwise.
|
|
|
|
</p><p>
|
|
For example, the following JSON describes an isosceles triangle, sitting
|
|
on the X axis and with an area of 0.5:
|
|
|
|
</p><div class="codeblock"><pre>[[0,0],[1,0],[0.5,1],[0,0]]
|
|
</pre></div>
|
|
|
|
<p>
|
|
A triangle has three vertexes, but the GeoJSON description of the triangle
|
|
has 4 vertexes because the first and last vertex are duplicates.
|
|
|
|
</p><h2 id="binary_storage_format"><span>1.2. </span>Binary storage format</h2>
|
|
|
|
<p>
|
|
Internally, Geopoly stores polygons in a binary format - an SQL BLOB.
|
|
Details of the binary format are given below.
|
|
All of the Geopoly interfaces are able to accept polygons in either the
|
|
GeoJSON format or in the binary format.
|
|
|
|
</p><h1 id="using_the_geopoly_extension"><span>2. </span>Using The Geopoly Extension</h1>
|
|
|
|
<p>
|
|
A geopoly table is created as follows:
|
|
|
|
</p><div class="codeblock"><pre>CREATE VIRTUAL TABLE newtab USING geopoly(a,b,c);
|
|
</pre></div>
|
|
|
|
<p>
|
|
The statement above creates a new geopoly table named "newtab".
|
|
Every geopoly table contains a built-in integer "rowid" column
|
|
and a "_shape" column that contains
|
|
the polygon associated with that row of the table.
|
|
The example above also defines three auxiliary data columns
|
|
named "a", "b", and "c" that can store whatever additional
|
|
information the application needs to associate
|
|
with each polygon. If there is no need to store auxiliary
|
|
information, the list of auxiliary columns can be omitted.
|
|
|
|
</p><p>
|
|
Store new polygons in the table using ordinary INSERT statements:
|
|
|
|
</p><div class="codeblock"><pre>INSERT INTO newtab(_shape) VALUES('[[0,0],[1,0],[0.5,1],[0,0]]');
|
|
</pre></div>
|
|
|
|
<p>
|
|
UPDATE and DELETE statements work similarly.
|
|
|
|
</p><h2 id="queries"><span>2.1. </span>Queries</h2>
|
|
|
|
<p>
|
|
To query the geopoly table using an indexed geospatial search,
|
|
use one of the functions geopoly_overlap()
|
|
or geopoly_within() as a boolean function in the WHERE clause,
|
|
with the "_shape" column as the first argument to the function.
|
|
For example:
|
|
|
|
</p><div class="codeblock"><pre>SELECT * FROM newtab WHERE geopoly_overlap(_shape, $query_polygon);
|
|
</pre></div>
|
|
|
|
<p>
|
|
The previous example will return every row for which the _shape
|
|
overlaps the polygon in the $query_polygon parameter. The
|
|
geopoly_within() function works similarly, but only returns rows for
|
|
which the _shape is completely contained within $query_polygon.
|
|
|
|
</p><p>
|
|
Queries (and also DELETE and UPDATE statements) in which the WHERE
|
|
clause contains a bare geopoly_overlap() or geopoly_within() function
|
|
make use of the underlying R*Tree data structures for a fast lookup that
|
|
only has to examine a subset of the rows in the table. The number of
|
|
rows examines depends, of course, on the size of the $query_polygon.
|
|
Large $query_polygons will normally need to look at more rows than small
|
|
ones.
|
|
|
|
</p><p>
|
|
Queries against the rowid of a geopoly table are also very quick, even
|
|
for tables with a vast number of rows.
|
|
However, none of the auxiliary data columns are indexes, and so queries
|
|
against the auxiliary data columns will involve a full table scan.
|
|
|
|
</p><h1 id="special_functions"><span>3. </span>Special Functions</h1>
|
|
|
|
<p>
|
|
The geopoly module defines several new SQL functions that are useful for
|
|
dealing with polygons. All polygon arguments to these functions can be
|
|
either the GeoJSON format or the internal binary format.
|
|
|
|
<a name="goverlap"></a>
|
|
|
|
</p><h2 id="the_geopoly_overlap_p1_p2_function"><span>3.1. </span>The geopoly_overlap(P1,P2) Function</h2>
|
|
|
|
<p>
|
|
If P1 and P2 are both polygons, then the geopoly_overlap(P1,P2) function returns
|
|
a non-zero integer if there is any overlap between P1 and P2, or it returns
|
|
zero if P1 and P2 completely disjoint.
|
|
If either P1 or P2 is not a polygon, this routine returns NULL.
|
|
|
|
</p><p>
|
|
The geopoly_overlap(P1,P2) function is special in that the geopoly virtual
|
|
table knows how to use R*Tree indexes to optimize queries in which the
|
|
WHERE clause uses geopoly_overlap() as a boolean function. Only the
|
|
geopoly_overlap(P1,P2) and geopoly_within(P1,P2) functions have this
|
|
capability.
|
|
|
|
<a name="gwithin"></a>
|
|
|
|
</p><h2 id="the_geopoly_within_p1_p2_function"><span>3.2. </span>The geopoly_within(P1,P2) Function</h2>
|
|
|
|
<p>
|
|
If P1 and P2 are both polygons, then the geopoly_within(P1,P2) function returns
|
|
a non-zero integer if P1 is completely contained within P2, or it returns zero
|
|
if any part of P1 is outside of P2. If P1 and P2 are the same polygon, this routine
|
|
returns non-zero.
|
|
If either P1 or P2 is not a polygon, this routine returns NULL.
|
|
|
|
</p><p>
|
|
The geopoly_within(P1,P2) function is special in that the geopoly virtual
|
|
table knows how to use R*Tree indexes to optimize queries in which the
|
|
WHERE clause uses geopoly_within() as a boolean function. Only the
|
|
geopoly_within(P1,P2) and geopoly_overlap(P1,P2) functions have this
|
|
capability.
|
|
|
|
<a name="garea"></a>
|
|
|
|
</p><h2 id="the_geopoly_area_p_function"><span>3.3. </span>The geopoly_area(P) Function</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_area(P) returns the area enclosed by
|
|
that polygon. If P is not a polygon, geopoly_area(P) returns NULL.
|
|
|
|
<a name="gblob"></a>
|
|
|
|
</p><h2 id="the_geopoly_blob_p_function"><span>3.4. </span>The geopoly_blob(P) Function</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_blob(P) returns the binary encoding
|
|
of that polygon as a BLOB.
|
|
If P is not a polygon, geopoly_blob(P) returns NULL.
|
|
|
|
<a name="gjson"></a>
|
|
|
|
</p><h2 id="the_geopoly_json_p_function"><span>3.5. </span>The geopoly_json(P) Function</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_json(P) returns the GeoJSON representation
|
|
of that polygon as a TEXT string.
|
|
If P is not a polygon, geopoly_json(P) returns NULL.
|
|
|
|
<a name="gsvg"></a>
|
|
|
|
</p><h2 id="the_geopoly_svg_p_function"><span>3.6. </span>The geopoly_svg(P,...) Function</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_svg(P,...) returns a text string which is a
|
|
<a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics">Scalable Vector Graphics (SVG)</a>
|
|
representation of that polygon. If there is more one argument, then second
|
|
and subsequent arguments are added as attributes to each SVG glyph. For example:
|
|
|
|
</p><div class="codeblock"><pre>SELECT geopoly_svg($polygon,'class="poly"','style="fill:blue;"');
|
|
</pre></div>
|
|
|
|
<p>
|
|
If P is not a polygon, geopoly_svg(P,...) returns NULL.
|
|
|
|
</p><p>
|
|
Note that geopoly uses a traditional right-handed cartesian coordinate system
|
|
with the origin at the lower left, whereas SVG uses a left-handed coordinate
|
|
system with the origin at the upper left. The geopoly_svg() routine makes no
|
|
attempt to transform the coordinate system, so the displayed images are shown
|
|
in mirror image and rotated. If that is undesirable, the geopoly_xform() routine
|
|
can be used to transform the output from cartesian to SVG coordinates prior to
|
|
passing the polygons into geopoly_svg().
|
|
|
|
<a name="gbbox"></a>
|
|
|
|
</p><h2 id="the_geopoly_bbox_p_and_geopoly_group_bbox_p_functions"><span>3.7. </span>The geopoly_bbox(P) and geopoly_group_bbox(P) Functions</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_bbox(P) returns a new polygon that is
|
|
the smallest (axis-aligned) rectangle completely enclosing P.
|
|
If P is not a polygon, geopoly_bbox(P) returns NULL.
|
|
|
|
</p><p>
|
|
The geopoly_group_bbox(P) function is an aggregate version of geopoly_bbox(P).
|
|
The geopoly_group_bbox(P) function returns the smallest rectangle that will
|
|
enclose all P values seen during aggregation.
|
|
|
|
<a name="gpoint"></a>
|
|
|
|
</p><h2 id="the_geopoly_contains_point_p_x_y_function"><span>3.8. </span>The geopoly_contains_point(P,X,Y) Function</h2>
|
|
|
|
<p>
|
|
If P is a polygon, then geopoly_contains_point(P,X,Y) returns a
|
|
non-zero integer if and only
|
|
if the coordinate X,Y is inside or on the boundary of the polygon P.
|
|
If P is not a polygon, geopoly_contains_point(P,X,Y) returns NULL.
|
|
|
|
<a name="xform"></a>
|
|
|
|
</p><h2 id="the_geopoly_xform_p_a_b_c_d_e_f_function"><span>3.9. </span>The geopoly_xform(P,A,B,C,D,E,F) Function</h2>
|
|
|
|
<p>
|
|
The geopoly_xform(P,A,B,C,D,E,F) function returns a new polygon that is an
|
|
affine transformation of the polygon P and where the transformation
|
|
is defined by values A,B,C,D,E,F. If P is not a valid polygon, this
|
|
routine returns NULL.
|
|
|
|
</p><p>
|
|
The transformation converts each vertex of the polygon according to the
|
|
following formula:
|
|
|
|
</p><div class="codeblock"><pre>x1 = A*x0 + B*y0 + E
|
|
y1 = C*x0 + D*y0 + F
|
|
</pre></div>
|
|
|
|
<p>
|
|
So, for example, to move a polygon by some amount DX, DY without changing
|
|
its shape, use:
|
|
|
|
</p><div class="codeblock"><pre>geopoly_xform($polygon, 1, 0, 0, 1, $DX, $DY)
|
|
</pre></div>
|
|
|
|
<p>
|
|
To rotate a polygon by R radians around the point 0, 0:
|
|
|
|
</p><div class="codeblock"><pre>geopoly_xform($polygon, cos($R), sin($R), -sin($R), cos($R), 0, 0)
|
|
</pre></div>
|
|
|
|
<p>
|
|
Note that a transformation that flips the polygon might cause the
|
|
order of vertexes to be reversed. In other words, the transformation
|
|
might cause the vertexes to circulate in clockwise order instead of
|
|
counter-clockwise. This can be corrected by sending the result
|
|
through the <a href="geopoly.html#ccw">geopoly_ccw()</a> function after transformation.
|
|
|
|
|
|
<a name="regpoly"></a>
|
|
|
|
</p><h2 id="the_geopoly_regular_x_y_r_n_function"><span>3.10. </span>The geopoly_regular(X,Y,R,N) Function</h2>
|
|
|
|
<p>
|
|
The geopoly_regular(X,Y,R,N) function returns a convex, simple, regular,
|
|
equilateral, equiangular polygon with N sides, centered at X,Y, and with
|
|
a circumradius of R. Or, if R is negative or if N is less than 3, the
|
|
function returns NULL. The N value is capped at 1000 so that the routine
|
|
will never render a polygon with more than 1000 sides even if the N value
|
|
is larger than 1000.
|
|
|
|
</p><p>
|
|
As an example, the following graphic:
|
|
|
|
</p><blockquote>
|
|
<svg width="600" height="300" style="border:1px solid black">
|
|
<polyline points="140.003,100 80.0019,134.644 80.0019,65.3565 140.003,100" style="fill:none;stroke:red;stroke-width:2"></polyline> <text x="100" y="106" alignment-baseline="central" text-anchor="middle">3</text>
|
|
<polyline points="240.003,100 200,140.003 159.997,100 200,59.9973 240.003,100" style="fill:none;stroke:orange;stroke-width:2"></polyline> <text x="200" y="106" alignment-baseline="central" text-anchor="middle">4</text>
|
|
<polyline points="340.003,100 312.358,138.042 267.637,123.511 267.637,76.4893 312.358,61.9583 340.003,100" style="fill:none;stroke:green;stroke-width:2"></polyline> <text x="300" y="106" alignment-baseline="central" text-anchor="middle">5</text>
|
|
<polyline points="440.003,100 419.998,134.644 380.002,134.644 359.997,100 380.002,65.3565 419.998,65.3565 440.003,100" style="fill:none;stroke:blue;stroke-width:2"></polyline> <text x="400" y="106" alignment-baseline="central" text-anchor="middle">6</text>
|
|
<polyline points="540.003,100 524.94,131.276 491.101,138.995 463.959,117.353 463.959,82.6471 491.101,61.005 524.94,68.7243 540.003,100" style="fill:none;stroke:purple;stroke-width:2"></polyline> <text x="500" y="106" alignment-baseline="central" text-anchor="middle">7</text>
|
|
<polyline points="140.003,200 128.286,228.286 100,240.003 71.7143,228.286 59.9973,200 71.7143,171.714 100,159.997 128.286,171.714 140.003,200" style="fill:none;stroke:red;stroke-width:2"></polyline> <text x="100" y="206" alignment-baseline="central" text-anchor="middle">8</text>
|
|
<polyline points="240.003,200 232.363,223.511 212.358,238.042 187.642,238.042 167.637,223.511 159.997,200 167.637,176.489 187.642,161.958 212.358,161.958 232.363,176.489 240.003,200" style="fill:none;stroke:orange;stroke-width:2"></polyline> <text x="200" y="206" alignment-baseline="central" text-anchor="middle">10</text>
|
|
<polyline points="340.003,200 334.644,219.998 319.998,234.644 300,240.003 280.002,234.644 265.356,219.998 259.997,200 265.356,180.002 280.002,165.356 300,159.997 319.998,165.356 334.644,180.002 340.003,200" style="fill:none;stroke:green;stroke-width:2"></polyline> <text x="300" y="206" alignment-baseline="central" text-anchor="middle">12</text>
|
|
<polyline points="440.003,200 436.956,215.305 428.286,228.286 415.305,236.956 400,240.003 384.695,236.956 371.714,228.286 363.044,215.305 359.997,200 363.044,184.695 371.714,171.714 384.695,163.044 400,159.997 415.305,163.044 428.286,171.714 436.956,184.695 440.003,200" style="fill:none;stroke:blue;stroke-width:2"></polyline> <text x="400" y="206" alignment-baseline="central" text-anchor="middle">16</text>
|
|
<polyline points="540.003,200 538.042,212.358 532.363,223.511 523.511,232.363 512.358,238.042 500,240.003 487.642,238.042 476.489,232.363 467.637,223.511 461.958,212.358 459.997,200 461.958,187.642 467.637,176.489 476.489,167.637 487.642,161.958 500,159.997 512.358,161.958 523.511,167.637 532.363,176.489 538.042,187.642 540.003,200" style="fill:none;stroke:purple;stroke-width:2"></polyline> <text x="500" y="206" alignment-baseline="central" text-anchor="middle">20</text>
|
|
</svg>
|
|
</blockquote>
|
|
|
|
<p>Was generated by this script:
|
|
|
|
</p><div class="codeblock"><pre>SELECT '<svg width="600" height="300">';
|
|
WITH t1(x,y,n,color) AS (VALUES
|
|
(100,100,3,'red'),
|
|
(200,100,4,'orange'),
|
|
(300,100,5,'green'),
|
|
(400,100,6,'blue'),
|
|
(500,100,7,'purple'),
|
|
(100,200,8,'red'),
|
|
(200,200,10,'orange'),
|
|
(300,200,12,'green'),
|
|
(400,200,16,'blue'),
|
|
(500,200,20,'purple')
|
|
)
|
|
SELECT
|
|
geopoly_svg(geopoly_regular(x,y,40,n),
|
|
printf('style="fill:none;stroke:%s;stroke-width:2"',color))
|
|
|| printf(' <text x="%d" y="%d" alignment-baseline="central" text-anchor="middle">%d</text>',x,y+6,n)
|
|
FROM t1;
|
|
SELECT '</svg>';
|
|
</pre></div>
|
|
|
|
<a name="ccw"></a>
|
|
|
|
<h2 id="the_geopoly_ccw_j_function"><span>3.11. </span>The geopoly_ccw(J) Function</h2>
|
|
|
|
<p>The geopoly_ccw(J) function returns the polygon J with counter-clockwise (CCW) rotation.
|
|
|
|
</p><p>
|
|
<a href="https://tools.ietf.org/html/rfc7946">RFC-7946</a> requires that polygons use CCW rotation.
|
|
But the spec also observes that many legacy GeoJSON files do not following the spec and
|
|
contain polygons with clockwise (CW) rotation. The geopoly_ccw() function is useful for
|
|
applications that are reading legacy GeoJSON scripts. If the input to geopoly_ccw() is
|
|
a correctly-formatted polygon, then no changes are made. However, if the circulation of
|
|
the input polygon is backwards, then geopoly_ccw() reverses the circulation order so that
|
|
it conforms to the spec and so that it will work correctly with the Geopoly module.
|
|
|
|
|
|
|
|
</p><h1 id="implementation_details"><span>4. </span>Implementation Details</h1>
|
|
|
|
<p>The geopoly module is an extension to the <a href="rtree.html">R-Tree extension</a>. Geopoly
|
|
uses the same underlying logic and shadow tables as the <a href="rtree.html">R-Tree extension</a>.
|
|
Geopoly merely presents a different interface, and provides some extra logic
|
|
to compute polygon decoding, overlap, and containment.
|
|
|
|
</p><h2 id="binary_encoding_of_polygons"><span>4.1. </span>Binary Encoding of Polygons</h2>
|
|
|
|
<p>
|
|
Geopoly stores all polygons internally using a binary format. A binary
|
|
polygon consists of a 4-byte header following by an array of coordinate
|
|
pairs in which each dimension of each coordinate is a 32-bit floating point
|
|
number.
|
|
|
|
</p><p>
|
|
The first byte of the header is a flag byte. The least significant bit
|
|
of the flag byte determines whether the coordinate pairs that follow the
|
|
header are stored big-endian or little-endian. A value of 0 for the least
|
|
significant bit means big-endian and a value of 1 means little endian.
|
|
Other bits of the first byte in the header are reserved for future expansion.
|
|
|
|
</p><p>
|
|
The next three bytes in the header record the number of vertexes in the polygon
|
|
as a big-endian integer. Thus there is an upper bound of about 16 million
|
|
vertexes per polygon.
|
|
|
|
</p><p>
|
|
Following the header is the array of coordinate pairs. Each coordinate is
|
|
a 32-bit floating point number. The use of 32-bit floating point values for
|
|
coordinates means that any point on the earth's surface can be mapped with
|
|
a resolution of approximately 2.5 meters. Higher resolutions are of course
|
|
possible if the map is restricted to a single continent or country.
|
|
Note that the resolution of coordinates in the geopoly module is similar
|
|
in magnitude to daily movement of points on the earth's surface due to
|
|
tidal forces.
|
|
|
|
</p><p>
|
|
The list of coordinates in the binary format contains no redundancy.
|
|
The last coordinate is not a repeat of the first as it is with GeoJSON.
|
|
Hence, there is always one fewer coordinate pair in the binary representation of
|
|
a polygon compared to the GeoJSON representation.
|
|
|
|
</p><h2 id="shadow_tables"><span>4.2. </span>Shadow Tables</h2>
|
|
|
|
<p>
|
|
The geopoly module is built on top of the <a href="rtree.html">R-Tree extension</a> and uses the
|
|
same underlying shadow tables and algorithms. For indexing purposes, each
|
|
polygon is represented in the shadow tables as a rectangular bounding box.
|
|
The underlying R-Tree implementation uses bounding boxes to limit the search
|
|
space. Then the geoploy_overlap() and/or geopoly_within() routines further
|
|
refine the search to the exact answer.
|
|
</p>
|