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>
 |