summaryrefslogtreecommitdiffstats
path: root/www/geopoly.html
diff options
context:
space:
mode:
Diffstat (limited to 'www/geopoly.html')
-rw-r--r--www/geopoly.html581
1 files changed, 581 insertions, 0 deletions
diff --git a/www/geopoly.html b/www/geopoly.html
new file mode 100644
index 0000000..8d5dac9
--- /dev/null
+++ b/www/geopoly.html
@@ -0,0 +1,581 @@
+<!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;
+}
+var antiRobot = 0;
+function antiRobotGo(){
+if( antiRobot!=3 ) return;
+antiRobot = 7;
+var j = document.getElementById("mtimelink");
+if(j && j.hasAttribute("data-href")) j.href=j.getAttribute("data-href");
+}
+function antiRobotDefense(){
+document.body.onmousedown=function(){
+antiRobot |= 2;
+antiRobotGo();
+document.body.onmousedown=null;
+}
+document.body.onmousemove=function(){
+antiRobot |= 2;
+antiRobotGo();
+document.body.onmousemove=null;
+}
+setTimeout(function(){
+antiRobot |= 1;
+antiRobotGo();
+}, 100)
+antiRobotGo();
+}
+antiRobotDefense();
+</script>
+<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">&#x25ba;</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 = "&#x25bc;";
+} else {
+sub.style.display = "none";
+mk.innerHTML = "&#x25ba;";
+}
+}
+</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>. However,
+depending on configuration options and the a particular version of SQLite
+you are using, the Geopoly extension may or may not be enabled by default.
+To ensure that Geopoly is enabled for your build,
+add the <a href="compile.html#enable_geopoly">-DSQLITE_ENABLE_GEOPOLY=1</a> compile-time option.
+
+</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>&#91;&#91;0,0],&#91;1,0],&#91;0.5,1],&#91;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('&#91;&#91;0,0],&#91;1,0],&#91;0.5,1],&#91;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 '&lt;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(' &lt;text x="%d" y="%d" alignment-baseline="central" text-anchor="middle">%d&lt;/text>',x,y+6,n)
+ FROM t1;
+SELECT '&lt;/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><p align="center"><small><i>This page last modified on <a href="https://sqlite.org/docsrc/honeypot" id="mtimelink" data-href="https://sqlite.org/docsrc/finfo/pages/geopoly.in?m=7b64a47e71">2023-12-05 14:43:20</a> UTC </small></i></p>
+