pgLatLon

changeset 0:3b70e93cc07d v0.1

Version 0.1 (initial commit)
author jbe
date Sun Aug 21 17:43:48 2016 +0200 (2016-08-21)
parents
children 2393084ef356
files GNUmakefile LICENSE Makefile README.html README.mkd create_test_db.data.sql create_test_db.schema.sql create_test_db.sh latlon--0.1.sql latlon-v0001.c latlon.control make-doc.sh
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/GNUmakefile	Sun Aug 21 17:43:48 2016 +0200
     1.3 @@ -0,0 +1,7 @@
     1.4 +EXTENSION = latlon
     1.5 +DATA = latlon--0.1.sql
     1.6 +MODULES = latlon-v0001
     1.7 +
     1.8 +PG_CONFIG = pg_config
     1.9 +PGXS := $(shell $(PG_CONFIG) --pgxs)
    1.10 +include $(PGXS)
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/LICENSE	Sun Aug 21 17:43:48 2016 +0200
     2.3 @@ -0,0 +1,19 @@
     2.4 +Copyright (c) 2016 Public Software Group e. V., Berlin, Germany
     2.5 +
     2.6 +Permission is hereby granted, free of charge, to any person obtaining a
     2.7 +copy of this software and associated documentation files (the "Software"),
     2.8 +to deal in the Software without restriction, including without limitation
     2.9 +the rights to use, copy, modify, merge, publish, distribute, sublicense,
    2.10 +and/or sell copies of the Software, and to permit persons to whom the
    2.11 +Software is furnished to do so, subject to the following conditions:
    2.12 +
    2.13 +The above copyright notice and this permission notice shall be included in
    2.14 +all copies or substantial portions of the Software.
    2.15 +
    2.16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    2.17 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    2.18 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    2.19 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    2.20 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    2.21 +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    2.22 +DEALINGS IN THE SOFTWARE.
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/Makefile	Sun Aug 21 17:43:48 2016 +0200
     3.3 @@ -0,0 +1,8 @@
     3.4 +all::
     3.5 +	gmake all
     3.6 +clean::
     3.7 +	gmake clean
     3.8 +install::
     3.9 +	gmake install
    3.10 +uninstall::
    3.11 +	gmake uninstall
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/README.html	Sun Aug 21 17:43:48 2016 +0200
     4.3 @@ -0,0 +1,450 @@
     4.4 +<html><head><title>pgLatLon v0.1 documentation</title></head><body>
     4.5 +<h1>pgLatLon v0.1 documentation</h1>
     4.6 +
     4.7 +<p>pgLatLon is a spatial database extension for the PostgreSQL object-relational
     4.8 +database management system providing geographic data types and spatial indexing
     4.9 +for the WGS-84 spheroid.</p>
    4.10 +
    4.11 +<p>While many other spatial databases still use imprecise bounding boxes for many
    4.12 +operations, pgLatLon supports more precise geometric calculations for all
    4.13 +implemented operators. Efficient indexing of geometric objects is provided
    4.14 +using fractal indices. Optimizations on bit level (including logarithmic
    4.15 +compression) allow for a highly memory-efficient non-overlapping index suitable
    4.16 +for huge datasets.</p>
    4.17 +
    4.18 +<p>Unlike competing spatial extensions for PostgreSQL, pgLatLon is available under
    4.19 +the permissive MIT/X11 license to avoid problems with viral licenses like the
    4.20 +GPLv2/v3.</p>
    4.21 +
    4.22 +<h2>Installation</h2>
    4.23 +
    4.24 +<h3>Automatic installation</h3>
    4.25 +
    4.26 +<p>Prerequisites:</p>
    4.27 +
    4.28 +<ul>
    4.29 +<li>Ensure that the <code>pg_config</code> binary is in your path (shipped with PostgreSQL).</li>
    4.30 +<li>Ensure that GNU Make is available (either as <code>make</code> or <code>gmake</code>).</li>
    4.31 +</ul>
    4.32 +
    4.33 +<p>Then simply type:</p>
    4.34 +
    4.35 +<pre><code>make install
    4.36 +</code></pre>
    4.37 +
    4.38 +<h3>Manual installation</h3>
    4.39 +
    4.40 +<p>It is also possible to compile and install the extension without GNU Make as
    4.41 +follows:</p>
    4.42 +
    4.43 +<pre><code>cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0001.so latlon-v0001.c
    4.44 +cp latlon-v0001.so `pg_config --pkglibdir`
    4.45 +cp latlon.control `pg_config --sharedir`/extension/
    4.46 +cp latlon--0.1.sql `pg_config --sharedir`/extension/
    4.47 +</code></pre>
    4.48 +
    4.49 +<h3>Loading the extension</h3>
    4.50 +
    4.51 +<p>After installation, you can create a database and load the extension as
    4.52 +follows:</p>
    4.53 +
    4.54 +<pre><code>% createdb test_database
    4.55 +% psql test_database
    4.56 +psql (9.5.4)
    4.57 +Type "help" for help.
    4.58 +
    4.59 +test_database=# CREATE EXTENSION latlon;
    4.60 +</code></pre>
    4.61 +
    4.62 +<h2>Reference</h2>
    4.63 +
    4.64 +<h3>1. Types</h3>
    4.65 +
    4.66 +<p>pgLatLon provides four geographic types: <code>epoint</code>, <code>ebox</code>, <code>ecircle</code>, and
    4.67 +<code>ecluster</code>.</p>
    4.68 +
    4.69 +<h4><code>epoint</code></h4>
    4.70 +
    4.71 +<p>A point on the earth spheroid (WGS-84).</p>
    4.72 +
    4.73 +<p>The text input format is <code>'[N|S]&lt;float&gt; [E|W]&lt;float&gt;'</code>, where each float is in
    4.74 +degrees. Note the required white space between the latitude and longitude
    4.75 +components.  Each floating point number may have a sign, in which case <code>N</code>/<code>S</code>
    4.76 +or <code>E</code>/<code>W</code> are switched respectively (e.g. <code>E-5</code> is the same as <code>W5</code>).</p>
    4.77 +
    4.78 +<p>An <code>epoint</code> may also be created from two floating point numbers by calling
    4.79 +<code>epoint(latitude, longitude)</code>, where positive latitudes are used for the
    4.80 +northern hemisphere, negative latitudes are used for the southern hemisphere,
    4.81 +positive longitudes indicate positions east of the prime meridian, and negative
    4.82 +longitudes indicate positions west of the prime meridian.</p>
    4.83 +
    4.84 +<p>Latitudes exceeding -90 or +90 degrees are truncated to -90 or +90
    4.85 +respectively, in which case a warning will be issued. Longitudes exceeding -180
    4.86 +or +180 degrees will be converted to values between -180 and +180 (both
    4.87 +inclusive) by adding or substracting a multiple of 360 degrees, in which case a
    4.88 +notice will be issued.</p>
    4.89 +
    4.90 +<p>If the latitude is -90 or +90 (south pole or north pole), a longitude value is
    4.91 +still stored in the datum, and if a point is on the prime meridian or the
    4.92 +180th meridian, the east/west bit is also stored in the datum. In case of the
    4.93 +prime meridian, this is done by storing a floating point value of -0 for
    4.94 +0 degrees west and a value of +0 for 0 degrees east. In case of the
    4.95 +180th meridian, this is done by storing -180 or +180 respectively. The equality
    4.96 +operator, however, returns true when the same points on earth are described,
    4.97 +i.e. the longitude is ignored for the poles, and 180 degrees west is considered
    4.98 +to be equal to 180 degrees east.</p>
    4.99 +
   4.100 +<h4><code>ebox</code></h4>
   4.101 +
   4.102 +<p>An area on earth demarcated by a southern and northern latitude, and a western
   4.103 +and eastern longitude (all given in WGS-84).</p>
   4.104 +
   4.105 +<p>The text input format is
   4.106 +<code>'{N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt;'</code>, where each float is in
   4.107 +degrees. The ordering of the four white-space separated blocks is not
   4.108 +significant. To include the 180th meridian, one longitude boundary must be
   4.109 +equal to or exceed <code>W180</code> or <code>E180</code>, e.g. <code>'N10 N20 E170 E190'</code>.</p>
   4.110 +
   4.111 +<p>A special value is the empty area, denoted by the text represenation <code>'empty'</code>.
   4.112 +Such an <code>ebox</code> does not contain any point.</p>
   4.113 +
   4.114 +<p>An <code>ebox</code> may also be created from four floating point numbers by calling
   4.115 +<code>ebox(min_latitude, max_latitude, min_longitude, max_longitude)</code>, where
   4.116 +positive values are used for north and east, and negative values are used for
   4.117 +south and west. If <code>min_latitude</code> is strictly greater than <code>max_latitude</code>, an
   4.118 +empty <code>ebox</code> is created. If <code>min_longitude</code> is greater than <code>max_longitude</code> and
   4.119 +if both longitudes are between -180 and +180 degrees, then the area oriented in
   4.120 +such way that the 180th meridian is included.</p>
   4.121 +
   4.122 +<p>If the longitude span is less than 120 degrees, an <code>ebox</code> may be alternatively
   4.123 +created from two <code>epoints</code> in the following way: <code>ebox(epoint(lat1, lon1),
   4.124 +epoint(lat2, lon2))</code>. In this case <code>lat1</code> and <code>lat2</code> as well as <code>lon1</code> and
   4.125 +<code>lon2</code> can be swapped without any impact.</p>
   4.126 +
   4.127 +<h4><code>ecircle</code></h4>
   4.128 +
   4.129 +<p>An area containing all points not farther away from a given center point
   4.130 +(WGS-84) than a given radius.</p>
   4.131 +
   4.132 +<p>The text input format is <code>'{N|S}&lt;float&gt; {E|W}&lt;float&gt; &lt;float&gt;'</code>, where the first
   4.133 +two floats denote the center point in degrees and the third float denotes the
   4.134 +radius in meters. A radius equal to minus infinity denotes an empty circle
   4.135 +which contains no point at all (despite having a center), while a radius equal
   4.136 +to zero denotes a circle that includes a single point.</p>
   4.137 +
   4.138 +<p>An <code>ecircle</code> may also be created by calling <code>ecircle(epoint(...), radius)</code> or
   4.139 +from three floating point numbers by calling <code>ecircle(latitude, longitude,
   4.140 +radius)</code>.</p>
   4.141 +
   4.142 +<h4><code>ecluster</code></h4>
   4.143 +
   4.144 +<p>A collection of points, paths, polygons, and outlines on the WGS-84 spheroid.
   4.145 +Each path, polygon, or outline must cover a longitude range of less than
   4.146 +180 degrees to avoid ambiguities.</p>
   4.147 +
   4.148 +<p>The text input format is a white-space separated list of the following items:</p>
   4.149 +
   4.150 +<ul>
   4.151 +<li><code>point   ({N|S}&lt;float&gt; {E|W}&lt;float&gt;)</code></li>
   4.152 +<li><code>path    ({N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt; ...)</code></li>
   4.153 +<li><code>outline ({N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt; ...)</code></li>
   4.154 +<li><code>polygon ({N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt; {N|S}&lt;float&gt; {E|W}&lt;float&gt; ...)</code></li>
   4.155 +</ul>
   4.156 +
   4.157 +<p>Paths are open by default (i.e. there is no connection from the last point in
   4.158 +the list to the first point in the list). Outlines and polygons, in contrast,
   4.159 +are automatically closed (i.e. there is a line segment from the last point in
   4.160 +the list to the first point in the list) which means the first point should not
   4.161 +be repeated as last point in the list. Polygons are filled, outlines are not.</p>
   4.162 +
   4.163 +<h3>2. Indices</h3>
   4.164 +
   4.165 +<p>Two kinds of indices are supported: B-tree and GiST indices.</p>
   4.166 +
   4.167 +<h4>B-tree indices</h4>
   4.168 +
   4.169 +<p>A B-tree index can be used for simple equality searches and is supported by the
   4.170 +<code>epoint</code>, <code>ebox</code>, and <code>ecircle</code> data types. B-tree indices can not be used for
   4.171 +geographic searches.</p>
   4.172 +
   4.173 +<h4>GiST indices</h4>
   4.174 +
   4.175 +<p>For geographic searches, GiST indices must be used. The <code>epoint</code>, <code>ecircle</code>,
   4.176 +and <code>ecluster</code> data types support GiST indexing. A GiST index for geographic
   4.177 +searches can be created as follows:</p>
   4.178 +
   4.179 +<pre><code>CREATE TABLE tbl (
   4.180 +        id              serial4         PRIMARY KEY,
   4.181 +        loc             epoint          NOT NULL );
   4.182 +
   4.183 +CREATE INDEX name_of_index ON tbl USING gist (loc);
   4.184 +</code></pre>
   4.185 +
   4.186 +<p>GiST indices also support nearest neighbor searches when using the distance
   4.187 +operator (<code>&lt;-&gt;</code>) in the ORDER BY clause.</p>
   4.188 +
   4.189 +<h4>Indices on other data types (e.g. GeoJSON)</h4>
   4.190 +
   4.191 +<p>Note that further types can be indexed by using an index on an expression with
   4.192 +a conversion function. One conversion function provided by pgLatLon is the
   4.193 +<code>GeoJSON_to_ecluster(float8, float8, text)</code> function:</p>
   4.194 +
   4.195 +<pre><code>CREATE TABLE tbl (
   4.196 +        id              serial4         PRIMARY KEY,
   4.197 +        loc             jsonb           NOT NULL );
   4.198 +
   4.199 +CREATE INDEX name_of_index ON tbl USING gist((GeoJSON_to_ecluster("loc")));
   4.200 +</code></pre>
   4.201 +
   4.202 +<p>When using the conversion function in an expression, the index will be used
   4.203 +automatically:</p>
   4.204 +
   4.205 +<pre><code>SELECT * FROM tbl WHERE GeoJSON_to_ecluster("loc") &amp;&amp; 'N50 E10 10000'::ecircle;
   4.206 +</code></pre>
   4.207 +
   4.208 +<h3>3. Operators</h3>
   4.209 +
   4.210 +<h4>Equality operator <code>=</code></h4>
   4.211 +
   4.212 +<p>Tests if two geographic objects are equal.</p>
   4.213 +
   4.214 +<p>The longitude is ignored for the poles, and 180 degrees west is considered to
   4.215 +be equal to 180 degrees east.</p>
   4.216 +
   4.217 +<p>For boxes and circles, two empty objects are considered equal. (Note that a
   4.218 +circle is not empty if the radius is zero but only if it is negative infinity,
   4.219 +i.e. smaller than zero.) Two circles with a positive infinite radius are also
   4.220 +considered equal.</p>
   4.221 +
   4.222 +<p>Implemented for:</p>
   4.223 +
   4.224 +<ul>
   4.225 +<li><code>epoint = epoint</code></li>
   4.226 +<li><code>ebox = ebox</code></li>
   4.227 +<li><code>ecircle = ecircle</code></li>
   4.228 +</ul>
   4.229 +
   4.230 +<p>The negation is the inequality operator (<code>&lt;&gt;</code> or <code>!=</code>).</p>
   4.231 +
   4.232 +<h4>Linear ordering operators <code>&lt;&lt;&lt;</code>, <code>&lt;&lt;&lt;=</code>, <code>&gt;&gt;&gt;=</code>, <code>&gt;&gt;&gt;</code></h4>
   4.233 +
   4.234 +<p>These operators create an arbitrary (but well-defined) linear ordering of
   4.235 +geographic objects, which is used internally for B-tree indexing and merge
   4.236 +joins. These operators will usually not be used by an application programmer.</p>
   4.237 +
   4.238 +<h4>Overlap operator <code>&amp;&amp;</code></h4>
   4.239 +
   4.240 +<p>Tests if two geographic objects have at least one point in common. Currently
   4.241 +implemented for:</p>
   4.242 +
   4.243 +<ul>
   4.244 +<li><code>epoint &amp;&amp; ebox</code></li>
   4.245 +<li><code>epoint &amp;&amp; ecircle</code></li>
   4.246 +<li><code>epoint &amp;&amp; ecluster</code></li>
   4.247 +<li><code>ebox &amp;&amp; ebox</code></li>
   4.248 +<li><code>ecircle &amp;&amp; ecircle</code></li>
   4.249 +<li><code>ecircle &amp;&amp; ecluster</code></li>
   4.250 +</ul>
   4.251 +
   4.252 +<p>The <code>&amp;&amp;</code> operator is commutative, i.e. <code>a &amp;&amp; b</code> is the same as <code>b &amp;&amp; a</code>. Each
   4.253 +commutation is supported as well.</p>
   4.254 +
   4.255 +<h4>Distance operator <code>&lt;-&gt;</code></h4>
   4.256 +
   4.257 +<p>Calculates the shortest distance between two geographic objects in meters (zero
   4.258 +if the objects are overlapping). Currently implemented for:</p>
   4.259 +
   4.260 +<ul>
   4.261 +<li><code>epoint &lt;-&gt; epoint</code></li>
   4.262 +<li><code>epoint &lt;-&gt; ecircle</code></li>
   4.263 +<li><code>epoint &lt;-&gt; ecluster</code></li>
   4.264 +<li><code>ecircle &lt;-&gt; ecircle</code></li>
   4.265 +<li><code>ecircle &lt;-&gt; ecluster</code></li>
   4.266 +</ul>
   4.267 +
   4.268 +<p>The <code>&lt;-&gt;</code> operator is commutative, i.e. <code>a &lt;-&gt; b</code> is the same as <code>b &lt;-&gt; a</code>.
   4.269 +Each commutation is supported as well.</p>
   4.270 +
   4.271 +<p>For short distances, the result is very accurate (i.e. respects the dimensions
   4.272 +of the WGS-84 spheroid). For longer distances in the order of magnitude of
   4.273 +earth's radius or greater, the value is only approximate (but the error is
   4.274 +still less than 0.2% as long as no polygons with very long edges are involved).</p>
   4.275 +
   4.276 +<p>The functions <code>distance(epoint, epoint)</code> and <code>distance(ecluster, epoint)</code> can
   4.277 +be used as an alias for this operator.</p>
   4.278 +
   4.279 +<p>Note: In case of radial searches with a fixed radius, this operator should
   4.280 +not be used. Instead, an <code>ecircle</code> should be created and used in combination
   4.281 +with the overlap operator (<code>&amp;&amp;</code>). Alternatively, the functions
   4.282 +<code>distance_within(epoint, epoint, float8)</code> or <code>distance_within(ecluster, epoint,
   4.283 +float8)</code> can be used for fixed-radius searches.</p>
   4.284 +
   4.285 +<h3>4. Functions</h3>
   4.286 +
   4.287 +<h4><code>center(circle)</code></h4>
   4.288 +
   4.289 +<p>Returns the center of an <code>ecircle</code> as an <code>epoint</code>.</p>
   4.290 +
   4.291 +<h4><code>distance(epoint, epoint)</code></h4>
   4.292 +
   4.293 +<p>Calculates the distance between two <code>epoint</code> datums in meters. This function is
   4.294 +an alias for the distance operator <code>&lt;-&gt;</code>.</p>
   4.295 +
   4.296 +<p>Note: In case of radial searches with a fixed radius, this function should not be
   4.297 +used. Use <code>distance_within(epoint, epoint, float8)</code> instead.</p>
   4.298 +
   4.299 +<h4><code>distance(ecluster, epoint)</code></h4>
   4.300 +
   4.301 +<p>Calculates the distance from an <code>ecluster</code> to an <code>epoint</code> in meters. This
   4.302 +function is an alias for the distance operator <code>&lt;-&gt;</code>.</p>
   4.303 +
   4.304 +<p>Note: In case of radial searches with a fixed radius, this function should not be
   4.305 +used. Use <code>distance_within(epoint, epoint, float8)</code> instead.</p>
   4.306 +
   4.307 +<h4><code>distance_within(</code>variable <code>epoint,</code> fixed <code>epoint,</code> radius <code>float8)</code></h4>
   4.308 +
   4.309 +<p>Checks if the distance between two <code>epoint</code> datums is not greater than a given
   4.310 +value (search radius).</p>
   4.311 +
   4.312 +<p>Note: In case of radial searches with a fixed radius, the first argument must
   4.313 +be used for the table column, while the second argument must be used for the
   4.314 +search center. Otherwise an existing index cannot be used.</p>
   4.315 +
   4.316 +<h4><code>distance_within(</code>variable <code>ecluster,</code> fixed <code>epoint,</code> radius <code>float8)</code></h4>
   4.317 +
   4.318 +<p>Checks if the distance from an <code>ecluster</code> to an <code>epoint</code> is not greater than a
   4.319 +given value (search radius).</p>
   4.320 +
   4.321 +<h4><code>ebox(</code>latmin <code>float8,</code> latmax <code>float8,</code> lonmin <code>float8,</code> lonmax <code>float8)</code></h4>
   4.322 +
   4.323 +<p>Creates a new <code>ebox</code> with the given boundaries.
   4.324 +See "1. Types", subsection <code>ebox</code> for details.</p>
   4.325 +
   4.326 +<h4><code>ebox(epoint, epoint)</code></h4>
   4.327 +
   4.328 +<p>Creates a new <code>ebox</code>. This function may only be used if the longitude
   4.329 +difference is less than or equal to 120 degrees.
   4.330 +See "1. Types", subsection <code>ebox</code> for details.</p>
   4.331 +
   4.332 +<h4><code>ecircle(epoint, float8)</code></h4>
   4.333 +
   4.334 +<p>Creates an <code>ecircle</code> with the given center point and radius.</p>
   4.335 +
   4.336 +<h4><code>ecircle(</code>latitude <code>float8,</code> longitude <code>float8,</code> radius <code>float8)</code></h4>
   4.337 +
   4.338 +<p>Creates an <code>ecircle</code> with the given center point and radius.</p>
   4.339 +
   4.340 +<h4><code>ecluster_concat(ecluster, ecluster)</code></h4>
   4.341 +
   4.342 +<p>Combines two clusters to form a new <code>ecluster</code> by uniting all entries of both
   4.343 +clusters. Note that two overlapping areas of polygons annihilate each other
   4.344 +(which may be used to create polygons with holes).</p>
   4.345 +
   4.346 +<h4><code>ecluster_concat(ecluster[])</code></h4>
   4.347 +
   4.348 +<p>Creates a new <code>ecluster</code> that unites all entries of all clusters in the passed
   4.349 +array. Note that two overlapping areas of polygons annihilate each other (which
   4.350 +may be used to create polygons with holes).</p>
   4.351 +
   4.352 +<h4><code>ecluster_create_multipoint(epoint[])</code></h4>
   4.353 +
   4.354 +<p>Creates a new <code>ecluster</code> which contains multiple points.</p>
   4.355 +
   4.356 +<h4><code>ecluster_create_outline(epoint[])</code></h4>
   4.357 +
   4.358 +<p>Creates a new <code>ecluster</code> that is an outline given by the passed points.</p>
   4.359 +
   4.360 +<h4><code>ecluster_create_path(epoint[])</code></h4>
   4.361 +
   4.362 +<p>Creates a new <code>ecluster</code> that is a path given by the passed points.</p>
   4.363 +
   4.364 +<h4><code>ecluster_create_polygon(epoint[])</code></h4>
   4.365 +
   4.366 +<p>Creates a new <code>ecluster</code> that is a polygon given by the passed points.</p>
   4.367 +
   4.368 +<h4><code>ecluster_extract_outlines(ecluster)</code></h4>
   4.369 +
   4.370 +<p>Set-returning function that returns the outlines of an <code>ecluster</code> as <code>epoint[]</code>
   4.371 +rows.</p>
   4.372 +
   4.373 +<h4><code>ecluster_extract_paths(ecluster)</code></h4>
   4.374 +
   4.375 +<p>Set-returning function that returns the paths of an <code>ecluster</code> as <code>epoint[]</code>
   4.376 +rows.</p>
   4.377 +
   4.378 +<h4><code>ecluster_extract_points(ecluster)</code></h4>
   4.379 +
   4.380 +<p>Set-returning function that returns the points of an <code>ecluster</code> as <code>epoint</code>
   4.381 +rows.</p>
   4.382 +
   4.383 +<h4><code>ecluster_extract_polygons(ecluster)</code></h4>
   4.384 +
   4.385 +<p>Set-returning function that returns the polygons of an <code>ecluster</code> as <code>epoint[]</code>
   4.386 +rows.</p>
   4.387 +
   4.388 +<h4><code>empty_ebox</code>()</h4>
   4.389 +
   4.390 +<p>Returns the empty <code>ebox</code>.
   4.391 +See "1. Types", subsection <code>ebox</code> for details.</p>
   4.392 +
   4.393 +<h4><code>epoint(</code>latitude <code>float8,</code> longitude <code>float8)</code></h4>
   4.394 +
   4.395 +<p>Returns an <code>epoint</code> with the given latitude and longitude.</p>
   4.396 +
   4.397 +<h4><code>epoint_latlon(</code>latitude <code>float8,</code> longitude <code>float8)</code></h4>
   4.398 +
   4.399 +<p>Alias for <code>epoint(float8, float8)</code>.</p>
   4.400 +
   4.401 +<h4><code>epoint_lonlat(</code>longitude <code>float8,</code> latitude <code>float8)</code></h4>
   4.402 +
   4.403 +<p>Same as <code>epoint(float8, float8)</code> but with arguments reversed.</p>
   4.404 +
   4.405 +<h4><code>GeoJSON_to_epoint(jsonb, text)</code></h4>
   4.406 +
   4.407 +<p>Maps a GeoJSON object of type "Point" or "Feature" (which contains a
   4.408 +"Point") to an <code>epoint</code> datum. For any other JSON objects, NULL is returned.</p>
   4.409 +
   4.410 +<p>The second parameter (which defaults to <code>epoint_lonlat</code>) may be set to a name
   4.411 +of a conversion function that transforms two coordinates (two <code>float8</code>
   4.412 +parameters) to an <code>epoint</code>.</p>
   4.413 +
   4.414 +<h4><code>GeoJSON_to_ecluster(jsonb, text)</code></h4>
   4.415 +
   4.416 +<p>Maps a (valid) GeoJSON object to an <code>ecluster</code>. Note that this function
   4.417 +does not check whether the JSONB object is a valid GeoJSON object.</p>
   4.418 +
   4.419 +<p>The second parameter (which defaults to <code>epoint_lonlat</code>) may be set to a name
   4.420 +of a conversion function that transforms two coordinates (two <code>float8</code>
   4.421 +parameters) to an <code>epoint</code>.</p>
   4.422 +
   4.423 +<h4><code>max_latitude(ebox)</code></h4>
   4.424 +
   4.425 +<p>Returns the northern boundary of a given <code>ebox</code> in degrees between -90 and +90.</p>
   4.426 +
   4.427 +<h4><code>max_longitude(ebox)</code></h4>
   4.428 +
   4.429 +<p>Returns the eastern boundary of a given <code>ebox</code> in degrees between -180 and +180
   4.430 +(both inclusive).</p>
   4.431 +
   4.432 +<h4><code>min_latitude(ebox)</code></h4>
   4.433 +
   4.434 +<p>Returns the southern boundary of a given <code>ebox</code> in degrees between -90 and +90.</p>
   4.435 +
   4.436 +<h4><code>min_longitude(ebox)</code></h4>
   4.437 +
   4.438 +<p>Returns the western boundary of a given <code>ebox</code> in degrees between -180 and +180
   4.439 +(both inclusive).</p>
   4.440 +
   4.441 +<h4><code>latitude(epoint)</code></h4>
   4.442 +
   4.443 +<p>Returns the latitude value of an <code>epoint</code> in degrees between -90 and +90.</p>
   4.444 +
   4.445 +<h4><code>longitude(epoint)</code></h4>
   4.446 +
   4.447 +<p>Returns the longitude value of an <code>epoint</code> in degrees between -180 and +180
   4.448 +(both inclusive).</p>
   4.449 +
   4.450 +<h4><code>radius(ecircle)</code></h4>
   4.451 +
   4.452 +<p>Returns the radius of an <code>ecircle</code> in meters.</p>
   4.453 +</body></html>
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/README.mkd	Sun Aug 21 17:43:48 2016 +0200
     5.3 @@ -0,0 +1,438 @@
     5.4 +pgLatLon v0.1 documentation
     5.5 +===========================
     5.6 +
     5.7 +pgLatLon is a spatial database extension for the PostgreSQL object-relational
     5.8 +database management system providing geographic data types and spatial indexing
     5.9 +for the WGS-84 spheroid.
    5.10 +
    5.11 +While many other spatial databases still use imprecise bounding boxes for many
    5.12 +operations, pgLatLon supports more precise geometric calculations for all
    5.13 +implemented operators. Efficient indexing of geometric objects is provided
    5.14 +using fractal indices. Optimizations on bit level (including logarithmic
    5.15 +compression) allow for a highly memory-efficient non-overlapping index suitable
    5.16 +for huge datasets.
    5.17 +
    5.18 +Unlike competing spatial extensions for PostgreSQL, pgLatLon is available under
    5.19 +the permissive MIT/X11 license to avoid problems with viral licenses like the
    5.20 +GPLv2/v3.
    5.21 +
    5.22 +
    5.23 +Installation
    5.24 +------------
    5.25 +
    5.26 +### Automatic installation
    5.27 +
    5.28 +Prerequisites:
    5.29 +
    5.30 +* Ensure that the `pg_config` binary is in your path (shipped with PostgreSQL).
    5.31 +* Ensure that GNU Make is available (either as `make` or `gmake`).
    5.32 +
    5.33 +Then simply type:
    5.34 +
    5.35 +    make install
    5.36 +
    5.37 +### Manual installation    
    5.38 +
    5.39 +It is also possible to compile and install the extension without GNU Make as
    5.40 +follows:
    5.41 +
    5.42 +    cc -Wall -O2 -fPIC -shared -I `pg_config --includedir-server` -o latlon-v0001.so latlon-v0001.c
    5.43 +    cp latlon-v0001.so `pg_config --pkglibdir`
    5.44 +    cp latlon.control `pg_config --sharedir`/extension/
    5.45 +    cp latlon--0.1.sql `pg_config --sharedir`/extension/
    5.46 +
    5.47 +### Loading the extension
    5.48 +
    5.49 +After installation, you can create a database and load the extension as
    5.50 +follows:
    5.51 +
    5.52 +    % createdb test_database
    5.53 +    % psql test_database
    5.54 +    psql (9.5.4)
    5.55 +    Type "help" for help.
    5.56 +
    5.57 +    test_database=# CREATE EXTENSION latlon;
    5.58 +
    5.59 +
    5.60 +Reference
    5.61 +---------
    5.62 +
    5.63 +### 1. Types
    5.64 +
    5.65 +pgLatLon provides four geographic types: `epoint`, `ebox`, `ecircle`, and
    5.66 +`ecluster`.
    5.67 +
    5.68 +#### `epoint`
    5.69 +
    5.70 +A point on the earth spheroid (WGS-84).
    5.71 +
    5.72 +The text input format is `'[N|S]<float> [E|W]<float>'`, where each float is in
    5.73 +degrees. Note the required white space between the latitude and longitude
    5.74 +components.  Each floating point number may have a sign, in which case `N`/`S`
    5.75 +or `E`/`W` are switched respectively (e.g. `E-5` is the same as `W5`).
    5.76 +
    5.77 +An `epoint` may also be created from two floating point numbers by calling
    5.78 +`epoint(latitude, longitude)`, where positive latitudes are used for the
    5.79 +northern hemisphere, negative latitudes are used for the southern hemisphere,
    5.80 +positive longitudes indicate positions east of the prime meridian, and negative
    5.81 +longitudes indicate positions west of the prime meridian.
    5.82 +
    5.83 +Latitudes exceeding -90 or +90 degrees are truncated to -90 or +90
    5.84 +respectively, in which case a warning will be issued. Longitudes exceeding -180
    5.85 +or +180 degrees will be converted to values between -180 and +180 (both
    5.86 +inclusive) by adding or substracting a multiple of 360 degrees, in which case a
    5.87 +notice will be issued.
    5.88 +
    5.89 +If the latitude is -90 or +90 (south pole or north pole), a longitude value is
    5.90 +still stored in the datum, and if a point is on the prime meridian or the
    5.91 +180th meridian, the east/west bit is also stored in the datum. In case of the
    5.92 +prime meridian, this is done by storing a floating point value of -0 for
    5.93 +0 degrees west and a value of +0 for 0 degrees east. In case of the
    5.94 +180th meridian, this is done by storing -180 or +180 respectively. The equality
    5.95 +operator, however, returns true when the same points on earth are described,
    5.96 +i.e. the longitude is ignored for the poles, and 180 degrees west is considered
    5.97 +to be equal to 180 degrees east.
    5.98 +
    5.99 +#### `ebox`
   5.100 +
   5.101 +An area on earth demarcated by a southern and northern latitude, and a western
   5.102 +and eastern longitude (all given in WGS-84).
   5.103 +
   5.104 +The text input format is
   5.105 +`'{N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float>'`, where each float is in
   5.106 +degrees. The ordering of the four white-space separated blocks is not
   5.107 +significant. To include the 180th meridian, one longitude boundary must be
   5.108 +equal to or exceed `W180` or `E180`, e.g. `'N10 N20 E170 E190'`.
   5.109 +
   5.110 +A special value is the empty area, denoted by the text represenation `'empty'`.
   5.111 +Such an `ebox` does not contain any point.
   5.112 +
   5.113 +An `ebox` may also be created from four floating point numbers by calling
   5.114 +`ebox(min_latitude, max_latitude, min_longitude, max_longitude)`, where
   5.115 +positive values are used for north and east, and negative values are used for
   5.116 +south and west. If `min_latitude` is strictly greater than `max_latitude`, an
   5.117 +empty `ebox` is created. If `min_longitude` is greater than `max_longitude` and
   5.118 +if both longitudes are between -180 and +180 degrees, then the area oriented in
   5.119 +such way that the 180th meridian is included.
   5.120 +
   5.121 +If the longitude span is less than 120 degrees, an `ebox` may be alternatively
   5.122 +created from two `epoints` in the following way: `ebox(epoint(lat1, lon1),
   5.123 +epoint(lat2, lon2))`. In this case `lat1` and `lat2` as well as `lon1` and
   5.124 +`lon2` can be swapped without any impact.
   5.125 +
   5.126 +#### `ecircle`
   5.127 +
   5.128 +An area containing all points not farther away from a given center point
   5.129 +(WGS-84) than a given radius.
   5.130 +
   5.131 +The text input format is `'{N|S}<float> {E|W}<float> <float>'`, where the first
   5.132 +two floats denote the center point in degrees and the third float denotes the
   5.133 +radius in meters. A radius equal to minus infinity denotes an empty circle
   5.134 +which contains no point at all (despite having a center), while a radius equal
   5.135 +to zero denotes a circle that includes a single point.
   5.136 +
   5.137 +An `ecircle` may also be created by calling `ecircle(epoint(...), radius)` or
   5.138 +from three floating point numbers by calling `ecircle(latitude, longitude,
   5.139 +radius)`.
   5.140 +
   5.141 +#### `ecluster`
   5.142 +
   5.143 +A collection of points, paths, polygons, and outlines on the WGS-84 spheroid.
   5.144 +Each path, polygon, or outline must cover a longitude range of less than
   5.145 +180 degrees to avoid ambiguities.
   5.146 +
   5.147 +The text input format is a white-space separated list of the following items:
   5.148 +
   5.149 +* `point   ({N|S}<float> {E|W}<float>)`
   5.150 +* `path    ({N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float> ...)`
   5.151 +* `outline ({N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float> ...)`
   5.152 +* `polygon ({N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float> {N|S}<float> {E|W}<float> ...)`
   5.153 +
   5.154 +Paths are open by default (i.e. there is no connection from the last point in
   5.155 +the list to the first point in the list). Outlines and polygons, in contrast,
   5.156 +are automatically closed (i.e. there is a line segment from the last point in
   5.157 +the list to the first point in the list) which means the first point should not
   5.158 +be repeated as last point in the list. Polygons are filled, outlines are not.
   5.159 +
   5.160 +### 2. Indices
   5.161 +
   5.162 +Two kinds of indices are supported: B-tree and GiST indices.
   5.163 +
   5.164 +#### B-tree indices
   5.165 +
   5.166 +A B-tree index can be used for simple equality searches and is supported by the
   5.167 +`epoint`, `ebox`, and `ecircle` data types. B-tree indices can not be used for
   5.168 +geographic searches.
   5.169 +
   5.170 +#### GiST indices
   5.171 +
   5.172 +For geographic searches, GiST indices must be used. The `epoint`, `ecircle`,
   5.173 +and `ecluster` data types support GiST indexing. A GiST index for geographic
   5.174 +searches can be created as follows:
   5.175 +
   5.176 +    CREATE TABLE tbl (
   5.177 +            id              serial4         PRIMARY KEY,
   5.178 +            loc             epoint          NOT NULL );
   5.179 +
   5.180 +    CREATE INDEX name_of_index ON tbl USING gist (loc);
   5.181 +
   5.182 +GiST indices also support nearest neighbor searches when using the distance
   5.183 +operator (`<->`) in the ORDER BY clause.
   5.184 +
   5.185 +#### Indices on other data types (e.g. GeoJSON)
   5.186 +
   5.187 +Note that further types can be indexed by using an index on an expression with
   5.188 +a conversion function. One conversion function provided by pgLatLon is the
   5.189 +`GeoJSON_to_ecluster(float8, float8, text)` function:
   5.190 +
   5.191 +    CREATE TABLE tbl (
   5.192 +            id              serial4         PRIMARY KEY,
   5.193 +            loc             jsonb           NOT NULL );
   5.194 +
   5.195 +    CREATE INDEX name_of_index ON tbl USING gist((GeoJSON_to_ecluster("loc")));
   5.196 +
   5.197 +When using the conversion function in an expression, the index will be used
   5.198 +automatically:
   5.199 +
   5.200 +    SELECT * FROM tbl WHERE GeoJSON_to_ecluster("loc") && 'N50 E10 10000'::ecircle;
   5.201 +
   5.202 +### 3. Operators
   5.203 +
   5.204 +#### Equality operator `=`
   5.205 +
   5.206 +Tests if two geographic objects are equal.
   5.207 +
   5.208 +The longitude is ignored for the poles, and 180 degrees west is considered to
   5.209 +be equal to 180 degrees east.
   5.210 +
   5.211 +For boxes and circles, two empty objects are considered equal. (Note that a
   5.212 +circle is not empty if the radius is zero but only if it is negative infinity,
   5.213 +i.e. smaller than zero.) Two circles with a positive infinite radius are also
   5.214 +considered equal.
   5.215 +
   5.216 +Implemented for:
   5.217 +
   5.218 +* `epoint = epoint`
   5.219 +* `ebox = ebox`
   5.220 +* `ecircle = ecircle`
   5.221 +
   5.222 +The negation is the inequality operator (`<>` or `!=`).
   5.223 +
   5.224 +#### Linear ordering operators `<<<`, `<<<=`, `>>>=`, `>>>`
   5.225 +
   5.226 +These operators create an arbitrary (but well-defined) linear ordering of
   5.227 +geographic objects, which is used internally for B-tree indexing and merge
   5.228 +joins. These operators will usually not be used by an application programmer.
   5.229 +
   5.230 +#### Overlap operator `&&`
   5.231 +
   5.232 +Tests if two geographic objects have at least one point in common. Currently
   5.233 +implemented for:
   5.234 +
   5.235 +* `epoint && ebox`
   5.236 +* `epoint && ecircle`
   5.237 +* `epoint && ecluster`
   5.238 +* `ebox && ebox`
   5.239 +* `ecircle && ecircle`
   5.240 +* `ecircle && ecluster`
   5.241 +
   5.242 +The `&&` operator is commutative, i.e. `a && b` is the same as `b && a`. Each
   5.243 +commutation is supported as well.
   5.244 +
   5.245 +#### Distance operator `<->`
   5.246 +
   5.247 +Calculates the shortest distance between two geographic objects in meters (zero
   5.248 +if the objects are overlapping). Currently implemented for:
   5.249 +
   5.250 +* `epoint <-> epoint`
   5.251 +* `epoint <-> ecircle`
   5.252 +* `epoint <-> ecluster`
   5.253 +* `ecircle <-> ecircle`
   5.254 +* `ecircle <-> ecluster`
   5.255 +
   5.256 +The `<->` operator is commutative, i.e. `a <-> b` is the same as `b <-> a`.
   5.257 +Each commutation is supported as well.
   5.258 +
   5.259 +For short distances, the result is very accurate (i.e. respects the dimensions
   5.260 +of the WGS-84 spheroid). For longer distances in the order of magnitude of
   5.261 +earth's radius or greater, the value is only approximate (but the error is
   5.262 +still less than 0.2% as long as no polygons with very long edges are involved).
   5.263 +
   5.264 +The functions `distance(epoint, epoint)` and `distance(ecluster, epoint)` can
   5.265 +be used as an alias for this operator.
   5.266 +
   5.267 +Note: In case of radial searches with a fixed radius, this operator should
   5.268 +not be used. Instead, an `ecircle` should be created and used in combination
   5.269 +with the overlap operator (`&&`). Alternatively, the functions
   5.270 +`distance_within(epoint, epoint, float8)` or `distance_within(ecluster, epoint,
   5.271 +float8)` can be used for fixed-radius searches.
   5.272 +
   5.273 +### 4. Functions
   5.274 +
   5.275 +#### `center(circle)`
   5.276 +
   5.277 +Returns the center of an `ecircle` as an `epoint`.
   5.278 +
   5.279 +#### `distance(epoint, epoint)`
   5.280 +
   5.281 +Calculates the distance between two `epoint` datums in meters. This function is
   5.282 +an alias for the distance operator `<->`.
   5.283 +
   5.284 +Note: In case of radial searches with a fixed radius, this function should not be
   5.285 +used. Use `distance_within(epoint, epoint, float8)` instead.
   5.286 +
   5.287 +#### `distance(ecluster, epoint)`
   5.288 +
   5.289 +Calculates the distance from an `ecluster` to an `epoint` in meters. This
   5.290 +function is an alias for the distance operator `<->`.
   5.291 +
   5.292 +Note: In case of radial searches with a fixed radius, this function should not be
   5.293 +used. Use `distance_within(epoint, epoint, float8)` instead.
   5.294 +
   5.295 +#### `distance_within(`variable `epoint,` fixed `epoint,` radius `float8)`
   5.296 +
   5.297 +Checks if the distance between two `epoint` datums is not greater than a given
   5.298 +value (search radius).
   5.299 +
   5.300 +Note: In case of radial searches with a fixed radius, the first argument must
   5.301 +be used for the table column, while the second argument must be used for the
   5.302 +search center. Otherwise an existing index cannot be used.
   5.303 +
   5.304 +#### `distance_within(`variable `ecluster,` fixed `epoint,` radius `float8)`
   5.305 +
   5.306 +Checks if the distance from an `ecluster` to an `epoint` is not greater than a
   5.307 +given value (search radius).
   5.308 +
   5.309 +#### `ebox(`latmin `float8,` latmax `float8,` lonmin `float8,` lonmax `float8)`
   5.310 +
   5.311 +Creates a new `ebox` with the given boundaries.
   5.312 +See "1. Types", subsection `ebox` for details.
   5.313 +
   5.314 +#### `ebox(epoint, epoint)`
   5.315 +
   5.316 +Creates a new `ebox`. This function may only be used if the longitude
   5.317 +difference is less than or equal to 120 degrees.
   5.318 +See "1. Types", subsection `ebox` for details.
   5.319 +
   5.320 +#### `ecircle(epoint, float8)`
   5.321 +
   5.322 +Creates an `ecircle` with the given center point and radius.
   5.323 +
   5.324 +#### `ecircle(`latitude `float8,` longitude `float8,` radius `float8)`
   5.325 +
   5.326 +Creates an `ecircle` with the given center point and radius.
   5.327 +
   5.328 +#### `ecluster_concat(ecluster, ecluster)`
   5.329 +
   5.330 +Combines two clusters to form a new `ecluster` by uniting all entries of both
   5.331 +clusters. Note that two overlapping areas of polygons annihilate each other
   5.332 +(which may be used to create polygons with holes).
   5.333 +
   5.334 +#### `ecluster_concat(ecluster[])`
   5.335 +
   5.336 +Creates a new `ecluster` that unites all entries of all clusters in the passed
   5.337 +array. Note that two overlapping areas of polygons annihilate each other (which
   5.338 +may be used to create polygons with holes).
   5.339 +
   5.340 +#### `ecluster_create_multipoint(epoint[])`
   5.341 +
   5.342 +Creates a new `ecluster` which contains multiple points.
   5.343 +
   5.344 +#### `ecluster_create_outline(epoint[])`
   5.345 +
   5.346 +Creates a new `ecluster` that is an outline given by the passed points.
   5.347 +
   5.348 +#### `ecluster_create_path(epoint[])`
   5.349 +
   5.350 +Creates a new `ecluster` that is a path given by the passed points.
   5.351 +
   5.352 +#### `ecluster_create_polygon(epoint[])`
   5.353 +
   5.354 +Creates a new `ecluster` that is a polygon given by the passed points.
   5.355 +
   5.356 +#### `ecluster_extract_outlines(ecluster)`
   5.357 +
   5.358 +Set-returning function that returns the outlines of an `ecluster` as `epoint[]`
   5.359 +rows.
   5.360 +
   5.361 +#### `ecluster_extract_paths(ecluster)`
   5.362 +
   5.363 +Set-returning function that returns the paths of an `ecluster` as `epoint[]`
   5.364 +rows.
   5.365 +
   5.366 +#### `ecluster_extract_points(ecluster)`
   5.367 +
   5.368 +Set-returning function that returns the points of an `ecluster` as `epoint`
   5.369 +rows.
   5.370 +
   5.371 +#### `ecluster_extract_polygons(ecluster)`
   5.372 +
   5.373 +Set-returning function that returns the polygons of an `ecluster` as `epoint[]`
   5.374 +rows.
   5.375 +
   5.376 +#### `empty_ebox`()
   5.377 +
   5.378 +Returns the empty `ebox`.
   5.379 +See "1. Types", subsection `ebox` for details.
   5.380 +
   5.381 +#### `epoint(`latitude `float8,` longitude `float8)`
   5.382 +
   5.383 +Returns an `epoint` with the given latitude and longitude.
   5.384 +
   5.385 +#### `epoint_latlon(`latitude `float8,` longitude `float8)`
   5.386 +
   5.387 +Alias for `epoint(float8, float8)`.
   5.388 +
   5.389 +#### `epoint_lonlat(`longitude `float8,` latitude `float8)`
   5.390 +
   5.391 +Same as `epoint(float8, float8)` but with arguments reversed.
   5.392 +
   5.393 +#### `GeoJSON_to_epoint(jsonb, text)`
   5.394 +
   5.395 +Maps a GeoJSON object of type "Point" or "Feature" (which contains a
   5.396 +"Point") to an `epoint` datum. For any other JSON objects, NULL is returned.
   5.397 +
   5.398 +The second parameter (which defaults to `epoint_lonlat`) may be set to a name
   5.399 +of a conversion function that transforms two coordinates (two `float8`
   5.400 +parameters) to an `epoint`.
   5.401 +
   5.402 +#### `GeoJSON_to_ecluster(jsonb, text)`
   5.403 +
   5.404 +Maps a (valid) GeoJSON object to an `ecluster`. Note that this function
   5.405 +does not check whether the JSONB object is a valid GeoJSON object.
   5.406 +
   5.407 +The second parameter (which defaults to `epoint_lonlat`) may be set to a name
   5.408 +of a conversion function that transforms two coordinates (two `float8`
   5.409 +parameters) to an `epoint`.
   5.410 +
   5.411 +#### `max_latitude(ebox)`
   5.412 +
   5.413 +Returns the northern boundary of a given `ebox` in degrees between -90 and +90.
   5.414 +
   5.415 +#### `max_longitude(ebox)`
   5.416 +
   5.417 +Returns the eastern boundary of a given `ebox` in degrees between -180 and +180
   5.418 +(both inclusive).
   5.419 +
   5.420 +#### `min_latitude(ebox)`
   5.421 +
   5.422 +Returns the southern boundary of a given `ebox` in degrees between -90 and +90.
   5.423 +
   5.424 +#### `min_longitude(ebox)`
   5.425 +
   5.426 +Returns the western boundary of a given `ebox` in degrees between -180 and +180
   5.427 +(both inclusive).
   5.428 +
   5.429 +#### `latitude(epoint)`
   5.430 +
   5.431 +Returns the latitude value of an `epoint` in degrees between -90 and +90.
   5.432 +
   5.433 +#### `longitude(epoint)`
   5.434 +
   5.435 +Returns the longitude value of an `epoint` in degrees between -180 and +180
   5.436 +(both inclusive).
   5.437 +
   5.438 +#### `radius(ecircle)`
   5.439 +
   5.440 +Returns the radius of an `ecircle` in meters.
   5.441 +
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/create_test_db.data.sql	Sun Aug 21 17:43:48 2016 +0200
     6.3 @@ -0,0 +1,6 @@
     6.4 +
     6.5 +INSERT INTO "test" ("location", "area") SELECT
     6.6 +  epoint((asin(2*random()-1) / pi()) * 180, (2*random()-1) * 180),
     6.7 +  ecircle((asin(2*random()-1) / pi()) * 180, (2*random()-1) * 180, -ln(1-random()) * 1000)
     6.8 +  FROM generate_series(1, 10000);
     6.9 +
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/create_test_db.schema.sql	Sun Aug 21 17:43:48 2016 +0200
     7.3 @@ -0,0 +1,11 @@
     7.4 +
     7.5 +CREATE EXTENSION latlon;
     7.6 +
     7.7 +CREATE TABLE "test" (
     7.8 +        "id"            SERIAL4         PRIMARY KEY,
     7.9 +        "location"      EPOINT          NOT NULL,
    7.10 +        "area"          ECIRCLE         NOT NULL );
    7.11 +
    7.12 +CREATE INDEX "test_location_key" ON "test" USING gist ("location");
    7.13 +CREATE INDEX "test_area_key"     ON "test" USING gist ("area");
    7.14 +
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/create_test_db.sh	Sun Aug 21 17:43:48 2016 +0200
     8.3 @@ -0,0 +1,8 @@
     8.4 +#!/bin/sh
     8.5 +dropdb latlon_test --if-exists
     8.6 +createdb latlon_test || exit 1
     8.7 +psql -v ON_ERROR_STOP=1 -f create_test_db.schema.sql latlon_test || exit 1
     8.8 +for i in 1 2 3 4 5 6 7 8 9 10
     8.9 +do
    8.10 +psql -v ON_ERROR_STOP=1 -f create_test_db.data.sql latlon_test || exit 1
    8.11 +done
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/latlon--0.1.sql	Sun Aug 21 17:43:48 2016 +0200
     9.3 @@ -0,0 +1,1205 @@
     9.4 +
     9.5 +----------------------------------------
     9.6 +-- forward declarations (shell types) --
     9.7 +----------------------------------------
     9.8 +
     9.9 +CREATE TYPE epoint;
    9.10 +CREATE TYPE ebox;
    9.11 +CREATE TYPE ecircle;
    9.12 +CREATE TYPE ecluster;
    9.13 +
    9.14 +
    9.15 +------------------------------------------------------------
    9.16 +-- dummy input/output functions for dummy index key types --
    9.17 +------------------------------------------------------------
    9.18 +
    9.19 +CREATE FUNCTION ekey_point_in_dummy(cstring)
    9.20 +  RETURNS ekey_point
    9.21 +  LANGUAGE C IMMUTABLE STRICT
    9.22 +  AS '$libdir/latlon-v0001', 'pgl_notimpl';
    9.23 +
    9.24 +CREATE FUNCTION ekey_point_out_dummy(ekey_point)
    9.25 +  RETURNS cstring
    9.26 +  LANGUAGE C IMMUTABLE STRICT
    9.27 +  AS '$libdir/latlon-v0001', 'pgl_notimpl';
    9.28 +
    9.29 +CREATE FUNCTION ekey_area_in_dummy(cstring)
    9.30 +  RETURNS ekey_area
    9.31 +  LANGUAGE C IMMUTABLE STRICT
    9.32 +  AS '$libdir/latlon-v0001', 'pgl_notimpl';
    9.33 +
    9.34 +CREATE FUNCTION ekey_area_out_dummy(ekey_area)
    9.35 +  RETURNS cstring
    9.36 +  LANGUAGE C IMMUTABLE STRICT
    9.37 +  AS '$libdir/latlon-v0001', 'pgl_notimpl';
    9.38 +
    9.39 +
    9.40 +--------------------------
    9.41 +-- text input functions --
    9.42 +--------------------------
    9.43 +
    9.44 +CREATE FUNCTION epoint_in(cstring)
    9.45 +  RETURNS epoint
    9.46 +  LANGUAGE C IMMUTABLE STRICT
    9.47 +  AS '$libdir/latlon-v0001', 'pgl_epoint_in';
    9.48 +
    9.49 +CREATE FUNCTION ebox_in(cstring)
    9.50 +  RETURNS ebox
    9.51 +  LANGUAGE C IMMUTABLE STRICT
    9.52 +  AS '$libdir/latlon-v0001', 'pgl_ebox_in';
    9.53 +
    9.54 +CREATE FUNCTION ecircle_in(cstring)
    9.55 +  RETURNS ecircle
    9.56 +  LANGUAGE C IMMUTABLE STRICT
    9.57 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_in';
    9.58 +
    9.59 +CREATE FUNCTION ecluster_in(cstring)
    9.60 +  RETURNS ecluster
    9.61 +  LANGUAGE C IMMUTABLE STRICT
    9.62 +  AS '$libdir/latlon-v0001', 'pgl_ecluster_in';
    9.63 +
    9.64 +
    9.65 +---------------------------
    9.66 +-- text output functions --
    9.67 +---------------------------
    9.68 +
    9.69 +CREATE FUNCTION epoint_out(epoint)
    9.70 +  RETURNS cstring
    9.71 +  LANGUAGE C IMMUTABLE STRICT
    9.72 +  AS '$libdir/latlon-v0001', 'pgl_epoint_out';
    9.73 +
    9.74 +CREATE FUNCTION ebox_out(ebox)
    9.75 +  RETURNS cstring
    9.76 +  LANGUAGE C IMMUTABLE STRICT
    9.77 +  AS '$libdir/latlon-v0001', 'pgl_ebox_out';
    9.78 +
    9.79 +CREATE FUNCTION ecircle_out(ecircle)
    9.80 +  RETURNS cstring
    9.81 +  LANGUAGE C IMMUTABLE STRICT
    9.82 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_out';
    9.83 +
    9.84 +CREATE FUNCTION ecluster_out(ecluster)
    9.85 +  RETURNS cstring
    9.86 +  LANGUAGE C IMMUTABLE STRICT
    9.87 +  AS '$libdir/latlon-v0001', 'pgl_ecluster_out';
    9.88 +
    9.89 +
    9.90 +--------------------------
    9.91 +-- binary I/O functions --
    9.92 +--------------------------
    9.93 +
    9.94 +CREATE FUNCTION epoint_recv(internal)
    9.95 +  RETURNS epoint
    9.96 +  LANGUAGE C IMMUTABLE STRICT
    9.97 +  AS '$libdir/latlon-v0001', 'pgl_epoint_recv';
    9.98 +
    9.99 +CREATE FUNCTION ebox_recv(internal)
   9.100 +  RETURNS ebox
   9.101 +  LANGUAGE C IMMUTABLE STRICT
   9.102 +  AS '$libdir/latlon-v0001', 'pgl_ebox_recv';
   9.103 +
   9.104 +CREATE FUNCTION ecircle_recv(internal)
   9.105 +  RETURNS ecircle
   9.106 +  LANGUAGE C IMMUTABLE STRICT
   9.107 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_recv';
   9.108 +
   9.109 +CREATE FUNCTION epoint_send(epoint)
   9.110 +  RETURNS bytea
   9.111 +  LANGUAGE C IMMUTABLE STRICT
   9.112 +  AS '$libdir/latlon-v0001', 'pgl_epoint_send';
   9.113 +
   9.114 +CREATE FUNCTION ebox_send(ebox)
   9.115 +  RETURNS bytea
   9.116 +  LANGUAGE C IMMUTABLE STRICT
   9.117 +  AS '$libdir/latlon-v0001', 'pgl_ebox_send';
   9.118 +
   9.119 +CREATE FUNCTION ecircle_send(ecircle)
   9.120 +  RETURNS bytea
   9.121 +  LANGUAGE C IMMUTABLE STRICT
   9.122 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_send';
   9.123 +
   9.124 +
   9.125 +-----------------------------------------------
   9.126 +-- type definitions of dummy index key types --
   9.127 +-----------------------------------------------
   9.128 +
   9.129 +CREATE TYPE ekey_point (
   9.130 +  internallength = 8,
   9.131 +  input = ekey_point_in_dummy,
   9.132 +  output = ekey_point_out_dummy,
   9.133 +  alignment = char );
   9.134 +
   9.135 +CREATE TYPE ekey_area (
   9.136 +  internallength = 9,
   9.137 +  input = ekey_area_in_dummy,
   9.138 +  output = ekey_area_out_dummy,
   9.139 +  alignment = char );
   9.140 +
   9.141 +
   9.142 +------------------------------------------
   9.143 +-- definitions of geographic data types --
   9.144 +------------------------------------------
   9.145 +
   9.146 +CREATE TYPE epoint (
   9.147 +  internallength = 16,
   9.148 +  input = epoint_in,
   9.149 +  output = epoint_out,
   9.150 +  receive = epoint_recv,
   9.151 +  send = epoint_send,
   9.152 +  alignment = double );
   9.153 +
   9.154 +CREATE TYPE ebox (
   9.155 +  internallength = 32,
   9.156 +  input = ebox_in,
   9.157 +  output = ebox_out,
   9.158 +  receive = ebox_recv,
   9.159 +  send = ebox_send,
   9.160 +  alignment = double );
   9.161 +
   9.162 +CREATE TYPE ecircle (
   9.163 +  internallength = 24,
   9.164 +  input = ecircle_in,
   9.165 +  output = ecircle_out,
   9.166 +  receive = ecircle_recv,
   9.167 +  send = ecircle_send,
   9.168 +  alignment = double );
   9.169 +
   9.170 +CREATE TYPE ecluster (
   9.171 +  internallength = VARIABLE,
   9.172 +  input = ecluster_in,
   9.173 +  output = ecluster_out,
   9.174 +  alignment = double,
   9.175 +  storage = external );
   9.176 +
   9.177 +
   9.178 +--------------------
   9.179 +-- B-tree support --
   9.180 +--------------------
   9.181 +
   9.182 +-- begin of B-tree support for epoint
   9.183 +
   9.184 +CREATE FUNCTION epoint_btree_lt(epoint, epoint)
   9.185 +  RETURNS boolean
   9.186 +  LANGUAGE C IMMUTABLE STRICT
   9.187 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_lt';
   9.188 +
   9.189 +CREATE FUNCTION epoint_btree_le(epoint, epoint)
   9.190 +  RETURNS boolean
   9.191 +  LANGUAGE C IMMUTABLE STRICT
   9.192 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_le';
   9.193 +
   9.194 +CREATE FUNCTION epoint_btree_eq(epoint, epoint)
   9.195 +  RETURNS boolean
   9.196 +  LANGUAGE C IMMUTABLE STRICT
   9.197 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_eq';
   9.198 +
   9.199 +CREATE FUNCTION epoint_btree_ne(epoint, epoint)
   9.200 +  RETURNS boolean
   9.201 +  LANGUAGE C IMMUTABLE STRICT
   9.202 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_ne';
   9.203 +
   9.204 +CREATE FUNCTION epoint_btree_ge(epoint, epoint)
   9.205 +  RETURNS boolean
   9.206 +  LANGUAGE C IMMUTABLE STRICT
   9.207 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_ge';
   9.208 +
   9.209 +CREATE FUNCTION epoint_btree_gt(epoint, epoint)
   9.210 +  RETURNS boolean
   9.211 +  LANGUAGE C IMMUTABLE STRICT
   9.212 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_gt';
   9.213 +
   9.214 +CREATE OPERATOR <<< (
   9.215 +  leftarg = epoint,
   9.216 +  rightarg = epoint,
   9.217 +  procedure = epoint_btree_lt,
   9.218 +  commutator = >>>,
   9.219 +  negator = >>>=,
   9.220 +  restrict = scalarltsel,
   9.221 +  join = scalarltjoinsel
   9.222 +);
   9.223 +
   9.224 +CREATE OPERATOR <<<= (
   9.225 +  leftarg = epoint,
   9.226 +  rightarg = epoint,
   9.227 +  procedure = epoint_btree_le,
   9.228 +  commutator = >>>=,
   9.229 +  negator = >>>,
   9.230 +  restrict = scalarltsel,
   9.231 +  join = scalarltjoinsel
   9.232 +);
   9.233 +
   9.234 +CREATE OPERATOR = (
   9.235 +  leftarg = epoint,
   9.236 +  rightarg = epoint,
   9.237 +  procedure = epoint_btree_eq,
   9.238 +  commutator = =,
   9.239 +  negator = <>,
   9.240 +  restrict = eqsel,
   9.241 +  join = eqjoinsel,
   9.242 +  merges
   9.243 +);
   9.244 +
   9.245 +CREATE OPERATOR <> (
   9.246 +  leftarg = epoint,
   9.247 +  rightarg = epoint,
   9.248 +  procedure = epoint_btree_eq,
   9.249 +  commutator = <>,
   9.250 +  negator = =,
   9.251 +  restrict = neqsel,
   9.252 +  join = neqjoinsel
   9.253 +);
   9.254 +
   9.255 +CREATE OPERATOR >>>= (
   9.256 +  leftarg = epoint,
   9.257 +  rightarg = epoint,
   9.258 +  procedure = epoint_btree_ge,
   9.259 +  commutator = <<<=,
   9.260 +  negator = <<<,
   9.261 +  restrict = scalargtsel,
   9.262 +  join = scalargtjoinsel
   9.263 +);
   9.264 +
   9.265 +CREATE OPERATOR >>> (
   9.266 +  leftarg = epoint,
   9.267 +  rightarg = epoint,
   9.268 +  procedure = epoint_btree_gt,
   9.269 +  commutator = <<<,
   9.270 +  negator = <<<=,
   9.271 +  restrict = scalargtsel,
   9.272 +  join = scalargtjoinsel
   9.273 +);
   9.274 +
   9.275 +CREATE FUNCTION epoint_btree_cmp(epoint, epoint)
   9.276 +  RETURNS int4
   9.277 +  LANGUAGE C IMMUTABLE STRICT
   9.278 +  AS '$libdir/latlon-v0001', 'pgl_btree_epoint_cmp';
   9.279 +
   9.280 +CREATE OPERATOR CLASS epoint_btree_ops
   9.281 +  DEFAULT FOR TYPE epoint USING btree AS
   9.282 +  OPERATOR 1 <<< ,
   9.283 +  OPERATOR 2 <<<= ,
   9.284 +  OPERATOR 3 = ,
   9.285 +  OPERATOR 4 >>>= ,
   9.286 +  OPERATOR 5 >>> ,
   9.287 +  FUNCTION 1 epoint_btree_cmp(epoint, epoint);
   9.288 +
   9.289 +-- end of B-tree support for epoint
   9.290 +
   9.291 +-- begin of B-tree support for ebox
   9.292 +
   9.293 +CREATE FUNCTION ebox_btree_lt(ebox, ebox)
   9.294 +  RETURNS boolean
   9.295 +  LANGUAGE C IMMUTABLE STRICT
   9.296 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_lt';
   9.297 +
   9.298 +CREATE FUNCTION ebox_btree_le(ebox, ebox)
   9.299 +  RETURNS boolean
   9.300 +  LANGUAGE C IMMUTABLE STRICT
   9.301 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_le';
   9.302 +
   9.303 +CREATE FUNCTION ebox_btree_eq(ebox, ebox)
   9.304 +  RETURNS boolean
   9.305 +  LANGUAGE C IMMUTABLE STRICT
   9.306 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_eq';
   9.307 +
   9.308 +CREATE FUNCTION ebox_btree_ne(ebox, ebox)
   9.309 +  RETURNS boolean
   9.310 +  LANGUAGE C IMMUTABLE STRICT
   9.311 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_ne';
   9.312 +
   9.313 +CREATE FUNCTION ebox_btree_ge(ebox, ebox)
   9.314 +  RETURNS boolean
   9.315 +  LANGUAGE C IMMUTABLE STRICT
   9.316 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_ge';
   9.317 +
   9.318 +CREATE FUNCTION ebox_btree_gt(ebox, ebox)
   9.319 +  RETURNS boolean
   9.320 +  LANGUAGE C IMMUTABLE STRICT
   9.321 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_gt';
   9.322 +
   9.323 +CREATE OPERATOR <<< (
   9.324 +  leftarg = ebox,
   9.325 +  rightarg = ebox,
   9.326 +  procedure = ebox_btree_lt,
   9.327 +  commutator = >>>,
   9.328 +  negator = >>>=,
   9.329 +  restrict = scalarltsel,
   9.330 +  join = scalarltjoinsel
   9.331 +);
   9.332 +
   9.333 +CREATE OPERATOR <<<= (
   9.334 +  leftarg = ebox,
   9.335 +  rightarg = ebox,
   9.336 +  procedure = ebox_btree_le,
   9.337 +  commutator = >>>=,
   9.338 +  negator = >>>,
   9.339 +  restrict = scalarltsel,
   9.340 +  join = scalarltjoinsel
   9.341 +);
   9.342 +
   9.343 +CREATE OPERATOR = (
   9.344 +  leftarg = ebox,
   9.345 +  rightarg = ebox,
   9.346 +  procedure = ebox_btree_eq,
   9.347 +  commutator = =,
   9.348 +  negator = <>,
   9.349 +  restrict = eqsel,
   9.350 +  join = eqjoinsel,
   9.351 +  merges
   9.352 +);
   9.353 +
   9.354 +CREATE OPERATOR <> (
   9.355 +  leftarg = ebox,
   9.356 +  rightarg = ebox,
   9.357 +  procedure = ebox_btree_eq,
   9.358 +  commutator = <>,
   9.359 +  negator = =,
   9.360 +  restrict = neqsel,
   9.361 +  join = neqjoinsel
   9.362 +);
   9.363 +
   9.364 +CREATE OPERATOR >>>= (
   9.365 +  leftarg = ebox,
   9.366 +  rightarg = ebox,
   9.367 +  procedure = ebox_btree_ge,
   9.368 +  commutator = <<<=,
   9.369 +  negator = <<<,
   9.370 +  restrict = scalargtsel,
   9.371 +  join = scalargtjoinsel
   9.372 +);
   9.373 +
   9.374 +CREATE OPERATOR >>> (
   9.375 +  leftarg = ebox,
   9.376 +  rightarg = ebox,
   9.377 +  procedure = ebox_btree_gt,
   9.378 +  commutator = <<<,
   9.379 +  negator = <<<=,
   9.380 +  restrict = scalargtsel,
   9.381 +  join = scalargtjoinsel
   9.382 +);
   9.383 +
   9.384 +CREATE FUNCTION ebox_btree_cmp(ebox, ebox)
   9.385 +  RETURNS int4
   9.386 +  LANGUAGE C IMMUTABLE STRICT
   9.387 +  AS '$libdir/latlon-v0001', 'pgl_btree_ebox_cmp';
   9.388 +
   9.389 +CREATE OPERATOR CLASS ebox_btree_ops
   9.390 +  DEFAULT FOR TYPE ebox USING btree AS
   9.391 +  OPERATOR 1 <<< ,
   9.392 +  OPERATOR 2 <<<= ,
   9.393 +  OPERATOR 3 = ,
   9.394 +  OPERATOR 4 >>>= ,
   9.395 +  OPERATOR 5 >>> ,
   9.396 +  FUNCTION 1 ebox_btree_cmp(ebox, ebox);
   9.397 +
   9.398 +-- end of B-tree support for ebox
   9.399 +
   9.400 +-- begin of B-tree support for ecircle
   9.401 +
   9.402 +CREATE FUNCTION ecircle_btree_lt(ecircle, ecircle)
   9.403 +  RETURNS boolean
   9.404 +  LANGUAGE C IMMUTABLE STRICT
   9.405 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_lt';
   9.406 +
   9.407 +CREATE FUNCTION ecircle_btree_le(ecircle, ecircle)
   9.408 +  RETURNS boolean
   9.409 +  LANGUAGE C IMMUTABLE STRICT
   9.410 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_le';
   9.411 +
   9.412 +CREATE FUNCTION ecircle_btree_eq(ecircle, ecircle)
   9.413 +  RETURNS boolean
   9.414 +  LANGUAGE C IMMUTABLE STRICT
   9.415 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_eq';
   9.416 +
   9.417 +CREATE FUNCTION ecircle_btree_ne(ecircle, ecircle)
   9.418 +  RETURNS boolean
   9.419 +  LANGUAGE C IMMUTABLE STRICT
   9.420 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_ne';
   9.421 +
   9.422 +CREATE FUNCTION ecircle_btree_ge(ecircle, ecircle)
   9.423 +  RETURNS boolean
   9.424 +  LANGUAGE C IMMUTABLE STRICT
   9.425 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_ge';
   9.426 +
   9.427 +CREATE FUNCTION ecircle_btree_gt(ecircle, ecircle)
   9.428 +  RETURNS boolean
   9.429 +  LANGUAGE C IMMUTABLE STRICT
   9.430 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_gt';
   9.431 +
   9.432 +CREATE OPERATOR <<< (
   9.433 +  leftarg = ecircle,
   9.434 +  rightarg = ecircle,
   9.435 +  procedure = ecircle_btree_lt,
   9.436 +  commutator = >>>,
   9.437 +  negator = >>>=,
   9.438 +  restrict = scalarltsel,
   9.439 +  join = scalarltjoinsel
   9.440 +);
   9.441 +
   9.442 +CREATE OPERATOR <<<= (
   9.443 +  leftarg = ecircle,
   9.444 +  rightarg = ecircle,
   9.445 +  procedure = ecircle_btree_le,
   9.446 +  commutator = >>>=,
   9.447 +  negator = >>>,
   9.448 +  restrict = scalarltsel,
   9.449 +  join = scalarltjoinsel
   9.450 +);
   9.451 +
   9.452 +CREATE OPERATOR = (
   9.453 +  leftarg = ecircle,
   9.454 +  rightarg = ecircle,
   9.455 +  procedure = ecircle_btree_eq,
   9.456 +  commutator = =,
   9.457 +  negator = <>,
   9.458 +  restrict = eqsel,
   9.459 +  join = eqjoinsel,
   9.460 +  merges
   9.461 +);
   9.462 +
   9.463 +CREATE OPERATOR <> (
   9.464 +  leftarg = ecircle,
   9.465 +  rightarg = ecircle,
   9.466 +  procedure = ecircle_btree_eq,
   9.467 +  commutator = <>,
   9.468 +  negator = =,
   9.469 +  restrict = neqsel,
   9.470 +  join = neqjoinsel
   9.471 +);
   9.472 +
   9.473 +CREATE OPERATOR >>>= (
   9.474 +  leftarg = ecircle,
   9.475 +  rightarg = ecircle,
   9.476 +  procedure = ecircle_btree_ge,
   9.477 +  commutator = <<<=,
   9.478 +  negator = <<<,
   9.479 +  restrict = scalargtsel,
   9.480 +  join = scalargtjoinsel
   9.481 +);
   9.482 +
   9.483 +CREATE OPERATOR >>> (
   9.484 +  leftarg = ecircle,
   9.485 +  rightarg = ecircle,
   9.486 +  procedure = ecircle_btree_gt,
   9.487 +  commutator = <<<,
   9.488 +  negator = <<<=,
   9.489 +  restrict = scalargtsel,
   9.490 +  join = scalargtjoinsel
   9.491 +);
   9.492 +
   9.493 +CREATE FUNCTION ecircle_btree_cmp(ecircle, ecircle)
   9.494 +  RETURNS int4
   9.495 +  LANGUAGE C IMMUTABLE STRICT
   9.496 +  AS '$libdir/latlon-v0001', 'pgl_btree_ecircle_cmp';
   9.497 +
   9.498 +CREATE OPERATOR CLASS ecircle_btree_ops
   9.499 +  DEFAULT FOR TYPE ecircle USING btree AS
   9.500 +  OPERATOR 1 <<< ,
   9.501 +  OPERATOR 2 <<<= ,
   9.502 +  OPERATOR 3 = ,
   9.503 +  OPERATOR 4 >>>= ,
   9.504 +  OPERATOR 5 >>> ,
   9.505 +  FUNCTION 1 ecircle_btree_cmp(ecircle, ecircle);
   9.506 +
   9.507 +-- end of B-tree support for ecircle
   9.508 +
   9.509 +
   9.510 +----------------
   9.511 +-- type casts --
   9.512 +----------------
   9.513 +
   9.514 +CREATE FUNCTION cast_epoint_to_ebox(epoint)
   9.515 +  RETURNS ebox
   9.516 +  LANGUAGE C IMMUTABLE STRICT
   9.517 +  AS '$libdir/latlon-v0001', 'pgl_epoint_to_ebox';
   9.518 +
   9.519 +CREATE CAST (epoint AS ebox) WITH FUNCTION cast_epoint_to_ebox(epoint);
   9.520 +
   9.521 +CREATE FUNCTION cast_epoint_to_ecircle(epoint)
   9.522 +  RETURNS ecircle
   9.523 +  LANGUAGE C IMMUTABLE STRICT
   9.524 +  AS '$libdir/latlon-v0001', 'pgl_epoint_to_ecircle';
   9.525 +
   9.526 +CREATE CAST (epoint AS ecircle) WITH FUNCTION cast_epoint_to_ecircle(epoint);
   9.527 +
   9.528 +CREATE FUNCTION cast_epoint_to_ecluster(epoint)
   9.529 +  RETURNS ecluster
   9.530 +  LANGUAGE C IMMUTABLE STRICT
   9.531 +  AS '$libdir/latlon-v0001', 'pgl_epoint_to_ecluster';
   9.532 +
   9.533 +CREATE CAST (epoint AS ecluster) WITH FUNCTION cast_epoint_to_ecluster(epoint);
   9.534 +
   9.535 +CREATE FUNCTION cast_ebox_to_ecluster(ebox)
   9.536 +  RETURNS ecluster
   9.537 +  LANGUAGE C IMMUTABLE STRICT
   9.538 +  AS '$libdir/latlon-v0001', 'pgl_ebox_to_ecluster';
   9.539 +
   9.540 +CREATE CAST (ebox AS ecluster) WITH FUNCTION cast_ebox_to_ecluster(ebox);
   9.541 +
   9.542 +
   9.543 +---------------------------
   9.544 +-- constructor functions --
   9.545 +---------------------------
   9.546 +
   9.547 +CREATE FUNCTION epoint(float8, float8)
   9.548 +  RETURNS epoint
   9.549 +  LANGUAGE C IMMUTABLE STRICT
   9.550 +  AS '$libdir/latlon-v0001', 'pgl_create_epoint';
   9.551 +
   9.552 +CREATE FUNCTION epoint_latlon(float8, float8)
   9.553 +  RETURNS epoint
   9.554 +  LANGUAGE SQL IMMUTABLE STRICT AS $$
   9.555 +    SELECT epoint($1, $2)
   9.556 +  $$;
   9.557 +
   9.558 +CREATE FUNCTION epoint_lonlat(float8, float8)
   9.559 +  RETURNS epoint
   9.560 +  LANGUAGE SQL IMMUTABLE STRICT AS $$
   9.561 +    SELECT epoint($2, $1)
   9.562 +  $$;
   9.563 +
   9.564 +CREATE FUNCTION empty_ebox()
   9.565 +  RETURNS ebox
   9.566 +  LANGUAGE C IMMUTABLE STRICT
   9.567 +  AS '$libdir/latlon-v0001', 'pgl_create_empty_ebox';
   9.568 +
   9.569 +CREATE FUNCTION ebox(float8, float8, float8, float8)
   9.570 +  RETURNS ebox
   9.571 +  LANGUAGE C IMMUTABLE STRICT
   9.572 +  AS '$libdir/latlon-v0001', 'pgl_create_ebox';
   9.573 +
   9.574 +CREATE FUNCTION ebox(epoint, epoint)
   9.575 +  RETURNS ebox
   9.576 +  LANGUAGE C IMMUTABLE STRICT
   9.577 +  AS '$libdir/latlon-v0001', 'pgl_create_ebox_from_epoints';
   9.578 +
   9.579 +CREATE FUNCTION ecircle(float8, float8, float8)
   9.580 +  RETURNS ecircle
   9.581 +  LANGUAGE C IMMUTABLE STRICT
   9.582 +  AS '$libdir/latlon-v0001', 'pgl_create_ecircle';
   9.583 +
   9.584 +CREATE FUNCTION ecircle(epoint, float8)
   9.585 +  RETURNS ecircle
   9.586 +  LANGUAGE C IMMUTABLE STRICT
   9.587 +  AS '$libdir/latlon-v0001', 'pgl_create_ecircle_from_epoint';
   9.588 +
   9.589 +CREATE FUNCTION ecluster_concat(ecluster[])
   9.590 +  RETURNS ecluster
   9.591 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.592 +    SELECT array_to_string($1, ' ')::ecluster
   9.593 +  $$;
   9.594 +
   9.595 +CREATE FUNCTION ecluster_concat(ecluster, ecluster)
   9.596 +  RETURNS ecluster
   9.597 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.598 +    SELECT ($1::text || ' ' || $2::text)::ecluster
   9.599 +  $$;
   9.600 +
   9.601 +CREATE FUNCTION ecluster_create_multipoint(epoint[])
   9.602 +  RETURNS ecluster
   9.603 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.604 +    SELECT
   9.605 +      array_to_string(array_agg('point (' || unnest || ')'), ' ')::ecluster
   9.606 +    FROM unnest($1)
   9.607 +  $$;
   9.608 +
   9.609 +CREATE FUNCTION ecluster_create_path(epoint[])
   9.610 +  RETURNS ecluster
   9.611 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.612 +    SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE
   9.613 +      ('path (' || array_to_string($1, ' ') || ')')::ecluster
   9.614 +    END
   9.615 +    FROM array_to_string($1, ' ') AS "str"
   9.616 +  $$;
   9.617 +
   9.618 +CREATE FUNCTION ecluster_create_outline(epoint[])
   9.619 +  RETURNS ecluster
   9.620 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.621 +    SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE
   9.622 +      ('outline (' || array_to_string($1, ' ') || ')')::ecluster
   9.623 +    END
   9.624 +    FROM array_to_string($1, ' ') AS "str"
   9.625 +  $$;
   9.626 +
   9.627 +CREATE FUNCTION ecluster_create_polygon(epoint[])
   9.628 +  RETURNS ecluster
   9.629 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.630 +    SELECT CASE WHEN "str" = '' THEN 'empty'::ecluster ELSE
   9.631 +      ('polygon (' || array_to_string($1, ' ') || ')')::ecluster
   9.632 +    END
   9.633 +    FROM array_to_string($1, ' ') AS "str"
   9.634 +  $$;
   9.635 +
   9.636 +
   9.637 +----------------------
   9.638 +-- getter functions --
   9.639 +----------------------
   9.640 +
   9.641 +CREATE FUNCTION latitude(epoint)
   9.642 +  RETURNS float8
   9.643 +  LANGUAGE C IMMUTABLE STRICT
   9.644 +  AS '$libdir/latlon-v0001', 'pgl_epoint_lat';
   9.645 +
   9.646 +CREATE FUNCTION longitude(epoint)
   9.647 +  RETURNS float8
   9.648 +  LANGUAGE C IMMUTABLE STRICT
   9.649 +  AS '$libdir/latlon-v0001', 'pgl_epoint_lon';
   9.650 +
   9.651 +CREATE FUNCTION min_latitude(ebox)
   9.652 +  RETURNS float8
   9.653 +  LANGUAGE C IMMUTABLE STRICT
   9.654 +  AS '$libdir/latlon-v0001', 'pgl_ebox_lat_min';
   9.655 +
   9.656 +CREATE FUNCTION max_latitude(ebox)
   9.657 +  RETURNS float8
   9.658 +  LANGUAGE C IMMUTABLE STRICT
   9.659 +  AS '$libdir/latlon-v0001', 'pgl_ebox_lat_max';
   9.660 +
   9.661 +CREATE FUNCTION min_longitude(ebox)
   9.662 +  RETURNS float8
   9.663 +  LANGUAGE C IMMUTABLE STRICT
   9.664 +  AS '$libdir/latlon-v0001', 'pgl_ebox_lon_min';
   9.665 +
   9.666 +CREATE FUNCTION max_longitude(ebox)
   9.667 +  RETURNS float8
   9.668 +  LANGUAGE C IMMUTABLE STRICT
   9.669 +  AS '$libdir/latlon-v0001', 'pgl_ebox_lon_max';
   9.670 +
   9.671 +CREATE FUNCTION center(ecircle)
   9.672 +  RETURNS epoint
   9.673 +  LANGUAGE C IMMUTABLE STRICT
   9.674 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_center';
   9.675 +
   9.676 +CREATE FUNCTION radius(ecircle)
   9.677 +  RETURNS float8
   9.678 +  LANGUAGE C IMMUTABLE STRICT
   9.679 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_radius';
   9.680 +
   9.681 +CREATE FUNCTION ecluster_extract_points(ecluster)
   9.682 +  RETURNS SETOF epoint
   9.683 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.684 +    SELECT "match"[2]::epoint
   9.685 +    FROM regexp_matches($1::text, e'(^| )point \\(([^)]+)\\)', 'g') AS "match"
   9.686 +  $$;
   9.687 +
   9.688 +CREATE FUNCTION ecluster_extract_paths(ecluster)
   9.689 +  RETURNS SETOF epoint[]
   9.690 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.691 +    SELECT (
   9.692 +      SELECT array_agg("m2"[1]::epoint)
   9.693 +      FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
   9.694 +    )
   9.695 +    FROM regexp_matches($1::text, e'(^| )path \\(([^)]+)\\)', 'g') AS "m1"
   9.696 +  $$;
   9.697 +
   9.698 +CREATE FUNCTION ecluster_extract_outlines(ecluster)
   9.699 +  RETURNS SETOF epoint[]
   9.700 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.701 +    SELECT (
   9.702 +      SELECT array_agg("m2"[1]::epoint)
   9.703 +      FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
   9.704 +    )
   9.705 +    FROM regexp_matches($1::text, e'(^| )outline \\(([^)]+)\\)', 'g') AS "m1"
   9.706 +  $$;
   9.707 +
   9.708 +CREATE FUNCTION ecluster_extract_polygons(ecluster)
   9.709 +  RETURNS SETOF epoint[]
   9.710 +  LANGUAGE sql IMMUTABLE STRICT AS $$
   9.711 +    SELECT (
   9.712 +      SELECT array_agg("m2"[1]::epoint)
   9.713 +      FROM regexp_matches("m1"[2], e'[^ ]+ [^ ]+', 'g') AS "m2"
   9.714 +    )
   9.715 +    FROM regexp_matches($1::text, e'(^| )polygon \\(([^)]+)\\)', 'g') AS "m1"
   9.716 +  $$;
   9.717 +
   9.718 +
   9.719 +---------------
   9.720 +-- operators --
   9.721 +---------------
   9.722 +
   9.723 +CREATE FUNCTION epoint_ebox_overlap_proc(epoint, ebox)
   9.724 +  RETURNS boolean
   9.725 +  LANGUAGE C IMMUTABLE STRICT
   9.726 +  AS '$libdir/latlon-v0001', 'pgl_epoint_ebox_overlap';
   9.727 +
   9.728 +CREATE FUNCTION epoint_ecircle_overlap_proc(epoint, ecircle)
   9.729 +  RETURNS boolean
   9.730 +  LANGUAGE C IMMUTABLE STRICT
   9.731 +  AS '$libdir/latlon-v0001', 'pgl_epoint_ecircle_overlap';
   9.732 +
   9.733 +CREATE FUNCTION epoint_ecluster_overlap_proc(epoint, ecluster)
   9.734 +  RETURNS boolean
   9.735 +  LANGUAGE C IMMUTABLE STRICT
   9.736 +  AS '$libdir/latlon-v0001', 'pgl_epoint_ecluster_overlap';
   9.737 +
   9.738 +CREATE FUNCTION ebox_overlap_proc(ebox, ebox)
   9.739 +  RETURNS boolean
   9.740 +  LANGUAGE C IMMUTABLE STRICT
   9.741 +  AS '$libdir/latlon-v0001', 'pgl_ebox_overlap';
   9.742 +
   9.743 +CREATE FUNCTION ecircle_overlap_proc(ecircle, ecircle)
   9.744 +  RETURNS boolean
   9.745 +  LANGUAGE C IMMUTABLE STRICT
   9.746 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_overlap';
   9.747 +
   9.748 +CREATE FUNCTION ecircle_ecluster_overlap_proc(ecircle, ecluster)
   9.749 +  RETURNS boolean
   9.750 +  LANGUAGE C IMMUTABLE STRICT
   9.751 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_ecluster_overlap';
   9.752 +
   9.753 +CREATE FUNCTION epoint_distance_proc(epoint, epoint)
   9.754 +  RETURNS float8
   9.755 +  LANGUAGE C IMMUTABLE STRICT
   9.756 +  AS '$libdir/latlon-v0001', 'pgl_epoint_distance';
   9.757 +
   9.758 +CREATE FUNCTION epoint_ecircle_distance_proc(epoint, ecircle)
   9.759 +  RETURNS float8
   9.760 +  LANGUAGE C IMMUTABLE STRICT
   9.761 +  AS '$libdir/latlon-v0001', 'pgl_epoint_ecircle_distance';
   9.762 +
   9.763 +CREATE FUNCTION epoint_ecluster_distance_proc(epoint, ecluster)
   9.764 +  RETURNS float8
   9.765 +  LANGUAGE C IMMUTABLE STRICT
   9.766 +  AS '$libdir/latlon-v0001', 'pgl_epoint_ecluster_distance';
   9.767 +
   9.768 +CREATE FUNCTION ecircle_distance_proc(ecircle, ecircle)
   9.769 +  RETURNS float8
   9.770 +  LANGUAGE C IMMUTABLE STRICT
   9.771 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_distance';
   9.772 +
   9.773 +CREATE FUNCTION ecircle_ecluster_distance_proc(ecircle, ecluster)
   9.774 +  RETURNS float8
   9.775 +  LANGUAGE C IMMUTABLE STRICT
   9.776 +  AS '$libdir/latlon-v0001', 'pgl_ecircle_ecluster_distance';
   9.777 +
   9.778 +CREATE OPERATOR && (
   9.779 +  leftarg = epoint,
   9.780 +  rightarg = ebox,
   9.781 +  procedure = epoint_ebox_overlap_proc,
   9.782 +  commutator = &&,
   9.783 +  restrict = areasel,
   9.784 +  join = areajoinsel
   9.785 +);
   9.786 +
   9.787 +CREATE FUNCTION epoint_ebox_overlap_commutator(ebox, epoint)
   9.788 +  RETURNS boolean
   9.789 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1';
   9.790 +
   9.791 +CREATE OPERATOR && (
   9.792 +  leftarg = ebox,
   9.793 +  rightarg = epoint,
   9.794 +  procedure = epoint_ebox_overlap_commutator,
   9.795 +  commutator = &&,
   9.796 +  restrict = areasel,
   9.797 +  join = areajoinsel
   9.798 +);
   9.799 +
   9.800 +CREATE OPERATOR && (
   9.801 +  leftarg = epoint,
   9.802 +  rightarg = ecircle,
   9.803 +  procedure = epoint_ecircle_overlap_proc,
   9.804 +  commutator = &&,
   9.805 +  restrict = areasel,
   9.806 +  join = areajoinsel
   9.807 +);
   9.808 +
   9.809 +CREATE FUNCTION epoint_ecircle_overlap_commutator(ecircle, epoint)
   9.810 +  RETURNS boolean
   9.811 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1';
   9.812 +
   9.813 +CREATE OPERATOR && (
   9.814 +  leftarg = ecircle,
   9.815 +  rightarg = epoint,
   9.816 +  procedure = epoint_ecircle_overlap_commutator,
   9.817 +  commutator = &&,
   9.818 +  restrict = areasel,
   9.819 +  join = areajoinsel
   9.820 +);
   9.821 +
   9.822 +CREATE OPERATOR && (
   9.823 +  leftarg = epoint,
   9.824 +  rightarg = ecluster,
   9.825 +  procedure = epoint_ecluster_overlap_proc,
   9.826 +  commutator = &&,
   9.827 +  restrict = areasel,
   9.828 +  join = areajoinsel
   9.829 +);
   9.830 +
   9.831 +CREATE FUNCTION epoint_ecluster_overlap_commutator(ecluster, epoint)
   9.832 +  RETURNS boolean
   9.833 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1';
   9.834 +
   9.835 +CREATE OPERATOR && (
   9.836 +  leftarg = ecluster,
   9.837 +  rightarg = epoint,
   9.838 +  procedure = epoint_ecluster_overlap_commutator,
   9.839 +  commutator = &&,
   9.840 +  restrict = areasel,
   9.841 +  join = areajoinsel
   9.842 +);
   9.843 +
   9.844 +CREATE OPERATOR && (
   9.845 +  leftarg = ebox,
   9.846 +  rightarg = ebox,
   9.847 +  procedure = ebox_overlap_proc,
   9.848 +  commutator = &&,
   9.849 +  restrict = areasel,
   9.850 +  join = areajoinsel
   9.851 +);
   9.852 +
   9.853 +CREATE OPERATOR && (
   9.854 +  leftarg = ecircle,
   9.855 +  rightarg = ecircle,
   9.856 +  procedure = ecircle_overlap_proc,
   9.857 +  commutator = &&,
   9.858 +  restrict = areasel,
   9.859 +  join = areajoinsel
   9.860 +);
   9.861 +
   9.862 +CREATE OPERATOR && (
   9.863 +  leftarg = ecircle,
   9.864 +  rightarg = ecluster,
   9.865 +  procedure = ecircle_ecluster_overlap_proc,
   9.866 +  commutator = &&,
   9.867 +  restrict = areasel,
   9.868 +  join = areajoinsel
   9.869 +);
   9.870 +
   9.871 +CREATE FUNCTION ecircle_ecluster_overlap_commutator(ecluster, ecircle)
   9.872 +  RETURNS boolean
   9.873 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 && $1';
   9.874 +
   9.875 +CREATE OPERATOR && (
   9.876 +  leftarg = ecluster,
   9.877 +  rightarg = ecircle,
   9.878 +  procedure = ecircle_ecluster_overlap_commutator,
   9.879 +  commutator = &&,
   9.880 +  restrict = areasel,
   9.881 +  join = areajoinsel
   9.882 +);
   9.883 +
   9.884 +CREATE OPERATOR <-> (
   9.885 +  leftarg = epoint,
   9.886 +  rightarg = epoint,
   9.887 +  procedure = epoint_distance_proc,
   9.888 +  commutator = <->
   9.889 +);
   9.890 +
   9.891 +CREATE OPERATOR <-> (
   9.892 +  leftarg = epoint,
   9.893 +  rightarg = ecircle,
   9.894 +  procedure = epoint_ecircle_distance_proc,
   9.895 +  commutator = <->
   9.896 +);
   9.897 +
   9.898 +CREATE FUNCTION epoint_ecircle_distance_commutator(ecircle, epoint)
   9.899 +  RETURNS float8
   9.900 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1';
   9.901 +
   9.902 +CREATE OPERATOR <-> (
   9.903 +  leftarg = ecircle,
   9.904 +  rightarg = epoint,
   9.905 +  procedure = epoint_ecircle_distance_commutator,
   9.906 +  commutator = <->
   9.907 +);
   9.908 +
   9.909 +CREATE OPERATOR <-> (
   9.910 +  leftarg = epoint,
   9.911 +  rightarg = ecluster,
   9.912 +  procedure = epoint_ecluster_distance_proc,
   9.913 +  commutator = <->
   9.914 +);
   9.915 +
   9.916 +CREATE FUNCTION epoint_ecluster_distance_commutator(ecluster, epoint)
   9.917 +  RETURNS float8
   9.918 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1';
   9.919 +
   9.920 +CREATE OPERATOR <-> (
   9.921 +  leftarg = ecluster,
   9.922 +  rightarg = epoint,
   9.923 +  procedure = epoint_ecluster_distance_commutator,
   9.924 +  commutator = <->
   9.925 +);
   9.926 +
   9.927 +CREATE OPERATOR <-> (
   9.928 +  leftarg = ecircle,
   9.929 +  rightarg = ecircle,
   9.930 +  procedure = ecircle_distance_proc,
   9.931 +  commutator = <->
   9.932 +);
   9.933 +
   9.934 +CREATE OPERATOR <-> (
   9.935 +  leftarg = ecircle,
   9.936 +  rightarg = ecluster,
   9.937 +  procedure = ecircle_ecluster_distance_proc,
   9.938 +  commutator = <->
   9.939 +);
   9.940 +
   9.941 +CREATE FUNCTION ecircle_ecluster_distance_commutator(ecluster, ecircle)
   9.942 +  RETURNS float8
   9.943 +  LANGUAGE sql IMMUTABLE AS 'SELECT $2 <-> $1';
   9.944 +
   9.945 +CREATE OPERATOR <-> (
   9.946 +  leftarg = ecluster,
   9.947 +  rightarg = ecircle,
   9.948 +  procedure = ecircle_ecluster_distance_commutator,
   9.949 +  commutator = <->
   9.950 +);
   9.951 +
   9.952 +
   9.953 +----------------
   9.954 +-- GiST index --
   9.955 +----------------
   9.956 +
   9.957 +CREATE FUNCTION pgl_gist_consistent(internal, internal, smallint, oid, internal)
   9.958 +  RETURNS boolean
   9.959 +  LANGUAGE C STRICT
   9.960 +  AS '$libdir/latlon-v0001', 'pgl_gist_consistent';
   9.961 +
   9.962 +CREATE FUNCTION pgl_gist_union(internal, internal)
   9.963 +  RETURNS internal
   9.964 +  LANGUAGE C STRICT
   9.965 +  AS '$libdir/latlon-v0001', 'pgl_gist_union';
   9.966 +
   9.967 +CREATE FUNCTION pgl_gist_compress_epoint(internal)
   9.968 +  RETURNS internal
   9.969 +  LANGUAGE C STRICT
   9.970 +  AS '$libdir/latlon-v0001', 'pgl_gist_compress_epoint';
   9.971 +
   9.972 +CREATE FUNCTION pgl_gist_compress_ecircle(internal)
   9.973 +  RETURNS internal
   9.974 +  LANGUAGE C STRICT
   9.975 +  AS '$libdir/latlon-v0001', 'pgl_gist_compress_ecircle';
   9.976 +
   9.977 +CREATE FUNCTION pgl_gist_compress_ecluster(internal)
   9.978 +  RETURNS internal
   9.979 +  LANGUAGE C STRICT
   9.980 +  AS '$libdir/latlon-v0001', 'pgl_gist_compress_ecluster';
   9.981 +
   9.982 +CREATE FUNCTION pgl_gist_decompress(internal)
   9.983 +  RETURNS internal
   9.984 +  LANGUAGE C STRICT
   9.985 +  AS '$libdir/latlon-v0001', 'pgl_gist_decompress';
   9.986 +
   9.987 +CREATE FUNCTION pgl_gist_penalty(internal, internal, internal)
   9.988 +  RETURNS internal
   9.989 +  LANGUAGE C STRICT
   9.990 +  AS '$libdir/latlon-v0001', 'pgl_gist_penalty';
   9.991 +
   9.992 +CREATE FUNCTION pgl_gist_picksplit(internal, internal)
   9.993 +  RETURNS internal
   9.994 +  LANGUAGE C STRICT
   9.995 +  AS '$libdir/latlon-v0001', 'pgl_gist_picksplit';
   9.996 +
   9.997 +CREATE FUNCTION pgl_gist_same(internal, internal, internal)
   9.998 +  RETURNS internal
   9.999 +  LANGUAGE C STRICT
  9.1000 +  AS '$libdir/latlon-v0001', 'pgl_gist_same';
  9.1001 +
  9.1002 +CREATE FUNCTION pgl_gist_distance(internal, internal, smallint, oid)
  9.1003 +  RETURNS internal
  9.1004 +  LANGUAGE C STRICT
  9.1005 +  AS '$libdir/latlon-v0001', 'pgl_gist_distance';
  9.1006 +
  9.1007 +CREATE OPERATOR CLASS epoint_ops
  9.1008 +  DEFAULT FOR TYPE epoint USING gist AS
  9.1009 +  OPERATOR 11 = ,
  9.1010 +  OPERATOR 22 && (epoint, ebox),
  9.1011 +  OPERATOR 23 && (epoint, ecircle),
  9.1012 +  OPERATOR 24 && (epoint, ecluster),
  9.1013 +  OPERATOR 31 <-> (epoint, epoint) FOR ORDER BY float_ops,
  9.1014 +  OPERATOR 33 <-> (epoint, ecircle) FOR ORDER BY float_ops,
  9.1015 +  OPERATOR 34 <-> (epoint, ecluster) FOR ORDER BY float_ops,
  9.1016 +  FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal),
  9.1017 +  FUNCTION 2 pgl_gist_union(internal, internal),
  9.1018 +  FUNCTION 3 pgl_gist_compress_epoint(internal),
  9.1019 +  FUNCTION 4 pgl_gist_decompress(internal),
  9.1020 +  FUNCTION 5 pgl_gist_penalty(internal, internal, internal),
  9.1021 +  FUNCTION 6 pgl_gist_picksplit(internal, internal),
  9.1022 +  FUNCTION 7 pgl_gist_same(internal, internal, internal),
  9.1023 +  FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid),
  9.1024 +  STORAGE ekey_point;
  9.1025 +
  9.1026 +CREATE OPERATOR CLASS ecircle_ops
  9.1027 +  DEFAULT FOR TYPE ecircle USING gist AS
  9.1028 +  OPERATOR 13 = ,
  9.1029 +  OPERATOR 21 && (ecircle, epoint),
  9.1030 +  OPERATOR 23 && (ecircle, ecircle),
  9.1031 +  OPERATOR 24 && (ecircle, ecluster),
  9.1032 +  OPERATOR 31 <-> (ecircle, epoint) FOR ORDER BY float_ops,
  9.1033 +  OPERATOR 33 <-> (ecircle, ecircle) FOR ORDER BY float_ops,
  9.1034 +  OPERATOR 34 <-> (ecircle, ecluster) FOR ORDER BY float_ops,
  9.1035 +  FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal),
  9.1036 +  FUNCTION 2 pgl_gist_union(internal, internal),
  9.1037 +  FUNCTION 3 pgl_gist_compress_ecircle(internal),
  9.1038 +  FUNCTION 4 pgl_gist_decompress(internal),
  9.1039 +  FUNCTION 5 pgl_gist_penalty(internal, internal, internal),
  9.1040 +  FUNCTION 6 pgl_gist_picksplit(internal, internal),
  9.1041 +  FUNCTION 7 pgl_gist_same(internal, internal, internal),
  9.1042 +  FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid),
  9.1043 +  STORAGE ekey_area;
  9.1044 +
  9.1045 +CREATE OPERATOR CLASS ecluster_ops
  9.1046 +  DEFAULT FOR TYPE ecluster USING gist AS
  9.1047 +  OPERATOR 21 && (ecluster, epoint),
  9.1048 +  FUNCTION 1 pgl_gist_consistent(internal, internal, smallint, oid, internal),
  9.1049 +  FUNCTION 2 pgl_gist_union(internal, internal),
  9.1050 +  FUNCTION 3 pgl_gist_compress_ecluster(internal),
  9.1051 +  FUNCTION 4 pgl_gist_decompress(internal),
  9.1052 +  FUNCTION 5 pgl_gist_penalty(internal, internal, internal),
  9.1053 +  FUNCTION 6 pgl_gist_picksplit(internal, internal),
  9.1054 +  FUNCTION 7 pgl_gist_same(internal, internal, internal),
  9.1055 +  FUNCTION 8 pgl_gist_distance(internal, internal, smallint, oid),
  9.1056 +  STORAGE ekey_area;
  9.1057 +
  9.1058 +
  9.1059 +---------------------
  9.1060 +-- alias functions --
  9.1061 +---------------------
  9.1062 +
  9.1063 +CREATE FUNCTION distance(epoint, epoint)
  9.1064 +  RETURNS float8
  9.1065 +  LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2';
  9.1066 +
  9.1067 +CREATE FUNCTION distance(ecluster, epoint)
  9.1068 +  RETURNS float8
  9.1069 +  LANGUAGE sql IMMUTABLE AS 'SELECT $1 <-> $2';
  9.1070 +
  9.1071 +CREATE FUNCTION distance_within(epoint, epoint, float8)
  9.1072 +  RETURNS boolean
  9.1073 +  LANGUAGE sql IMMUTABLE AS 'SELECT $1 && ecircle($2, $3)';
  9.1074 +
  9.1075 +CREATE FUNCTION distance_within(ecluster, epoint, float8)
  9.1076 +  RETURNS boolean
  9.1077 +  LANGUAGE sql IMMUTABLE AS 'SELECT $1 && ecircle($2, $3)';
  9.1078 +
  9.1079 +
  9.1080 +--------------------------------
  9.1081 +-- other data storage formats --
  9.1082 +--------------------------------
  9.1083 +
  9.1084 +CREATE FUNCTION coords_to_epoint(float8, float8, text = 'epoint_lonlat')
  9.1085 +  RETURNS epoint
  9.1086 +  LANGUAGE plpgsql IMMUTABLE STRICT AS $$
  9.1087 +    DECLARE
  9.1088 +      "result" epoint;
  9.1089 +    BEGIN
  9.1090 +      IF $3 = 'epoint_lonlat' THEN
  9.1091 +        -- avoid dynamic command execution for better performance
  9.1092 +        RETURN epoint($2, $1);
  9.1093 +      END IF;
  9.1094 +      IF $3 = 'epoint' OR $3 = 'epoint_latlon' THEN
  9.1095 +        -- avoid dynamic command execution for better performance
  9.1096 +        RETURN epoint($1, $2);
  9.1097 +      END IF;
  9.1098 +      EXECUTE 'SELECT ' || $3 || '($1, $2)' INTO STRICT "result" USING $1, $2;
  9.1099 +      RETURN "result";
  9.1100 +    END;
  9.1101 +  $$;
  9.1102 +
  9.1103 +CREATE FUNCTION GeoJSON_to_epoint(jsonb, text = 'epoint_lonlat')
  9.1104 +  RETURNS epoint
  9.1105 +  LANGUAGE sql IMMUTABLE STRICT AS $$
  9.1106 +    SELECT CASE
  9.1107 +    WHEN $1->>'type' = 'Point' THEN
  9.1108 +      coords_to_epoint(
  9.1109 +        ($1->'coordinates'->>1)::float8,
  9.1110 +        ($1->'coordinates'->>0)::float8,
  9.1111 +        $2
  9.1112 +      )
  9.1113 +    WHEN $1->>'type' = 'Feature' THEN
  9.1114 +      GeoJSON_to_epoint($1->'geometry', $2)
  9.1115 +    ELSE
  9.1116 +      NULL
  9.1117 +    END
  9.1118 +  $$;
  9.1119 +
  9.1120 +CREATE FUNCTION GeoJSON_to_ecluster(jsonb, text = 'epoint_lonlat')
  9.1121 +  RETURNS ecluster
  9.1122 +  LANGUAGE sql IMMUTABLE STRICT AS $$
  9.1123 +    SELECT CASE $1->>'type'
  9.1124 +    WHEN 'Point' THEN
  9.1125 +      coords_to_epoint(
  9.1126 +        ($1->'coordinates'->>1)::float8,
  9.1127 +        ($1->'coordinates'->>0)::float8,
  9.1128 +        $2
  9.1129 +      )::ecluster
  9.1130 +    WHEN 'MultiPoint' THEN
  9.1131 +      ( SELECT ecluster_create_multipoint(array_agg(
  9.1132 +          coords_to_epoint(
  9.1133 +            ("coord"->>1)::float8,
  9.1134 +            ("coord"->>0)::float8,
  9.1135 +            $2
  9.1136 +          )
  9.1137 +        ))
  9.1138 +        FROM jsonb_array_elements($1->'coordinates') AS "coord"
  9.1139 +      )
  9.1140 +    WHEN 'LineString' THEN
  9.1141 +      ( SELECT ecluster_create_path(array_agg(
  9.1142 +          coords_to_epoint(
  9.1143 +            ("coord"->>1)::float8,
  9.1144 +            ("coord"->>0)::float8,
  9.1145 +            $2
  9.1146 +          )
  9.1147 +        ))
  9.1148 +        FROM jsonb_array_elements($1->'coordinates') AS "coord"
  9.1149 +      )
  9.1150 +    WHEN 'MultiLineString' THEN
  9.1151 +      ( SELECT ecluster_concat(array_agg(
  9.1152 +          ( SELECT ecluster_create_path(array_agg(
  9.1153 +              coords_to_epoint(
  9.1154 +                ("coord"->>1)::float8,
  9.1155 +                ("coord"->>0)::float8,
  9.1156 +                $2
  9.1157 +              )
  9.1158 +            ))
  9.1159 +            FROM jsonb_array_elements("coord_array") AS "coord"
  9.1160 +          )
  9.1161 +        ))
  9.1162 +        FROM jsonb_array_elements($1->'coordinates') AS "coord_array"
  9.1163 +      )
  9.1164 +    WHEN 'Polygon' THEN
  9.1165 +      ( SELECT ecluster_concat(array_agg(
  9.1166 +          ( SELECT ecluster_create_polygon(array_agg(
  9.1167 +              coords_to_epoint(
  9.1168 +                ("coord"->>1)::float8,
  9.1169 +                ("coord"->>0)::float8,
  9.1170 +                $2
  9.1171 +              )
  9.1172 +            ))
  9.1173 +            FROM jsonb_array_elements("coord_array") AS "coord"
  9.1174 +          )
  9.1175 +        ))
  9.1176 +        FROM jsonb_array_elements($1->'coordinates') AS "coord_array"
  9.1177 +      )
  9.1178 +    WHEN 'MultiPolygon' THEN
  9.1179 +      ( SELECT ecluster_concat(array_agg(
  9.1180 +          ( SELECT ecluster_concat(array_agg(
  9.1181 +              ( SELECT ecluster_create_polygon(array_agg(
  9.1182 +                  coords_to_epoint(
  9.1183 +                    ("coord"->>1)::float8,
  9.1184 +                    ("coord"->>0)::float8,
  9.1185 +                    $2
  9.1186 +                  )
  9.1187 +                ))
  9.1188 +                FROM jsonb_array_elements("coord_array") AS "coord"
  9.1189 +              )
  9.1190 +            ))
  9.1191 +            FROM jsonb_array_elements("coord_array_array") AS "coord_array"
  9.1192 +          )
  9.1193 +        ))
  9.1194 +        FROM jsonb_array_elements($1->'coordinates') AS "coord_array_array"
  9.1195 +      )
  9.1196 +    WHEN 'Feature' THEN
  9.1197 +      GeoJSON_to_ecluster($1->'geometry', $2)
  9.1198 +    WHEN 'FeatureCollection' THEN
  9.1199 +      ( SELECT ecluster_concat(array_agg(
  9.1200 +          GeoJSON_to_ecluster("feature", $2)
  9.1201 +        ))
  9.1202 +        FROM jsonb_array_elements($1->'features') AS "feature"
  9.1203 +      )
  9.1204 +    ELSE
  9.1205 +      NULL
  9.1206 +    END
  9.1207 +  $$;
  9.1208 +
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/latlon-v0001.c	Sun Aug 21 17:43:48 2016 +0200
    10.3 @@ -0,0 +1,2710 @@
    10.4 +
    10.5 +/*-------------*
    10.6 + *  C prelude  *
    10.7 + *-------------*/
    10.8 +
    10.9 +#include "postgres.h"
   10.10 +#include "fmgr.h"
   10.11 +#include "libpq/pqformat.h"
   10.12 +#include "access/gist.h"
   10.13 +#include "access/stratnum.h"
   10.14 +#include "utils/array.h"
   10.15 +#include <math.h>
   10.16 +
   10.17 +#ifdef PG_MODULE_MAGIC
   10.18 +PG_MODULE_MAGIC;
   10.19 +#endif
   10.20 +
   10.21 +#if INT_MAX < 2147483647
   10.22 +#error Expected int type to be at least 32 bit wide
   10.23 +#endif
   10.24 +
   10.25 +
   10.26 +/*---------------------------------*
   10.27 + *  distance calculation on earth  *
   10.28 + *  (using WGS-84 spheroid)        *
   10.29 + *---------------------------------*/
   10.30 +
   10.31 +/*  WGS-84 spheroid with following parameters:
   10.32 +    semi-major axis  a = 6378137
   10.33 +    semi-minor axis  b = a * (1 - 1/298.257223563)
   10.34 +    estimated diameter = 2 * (2*a+b)/3
   10.35 +*/
   10.36 +#define PGL_SPHEROID_A 6378137.0            /* semi major axis */
   10.37 +#define PGL_SPHEROID_F (1.0/298.257223563)  /* flattening */
   10.38 +#define PGL_SPHEROID_B (PGL_SPHEROID_A * (1.0-PGL_SPHEROID_F))
   10.39 +#define PGL_EPS2       ( ( PGL_SPHEROID_A * PGL_SPHEROID_A - \
   10.40 +                           PGL_SPHEROID_B * PGL_SPHEROID_B ) / \
   10.41 +                         ( PGL_SPHEROID_A * PGL_SPHEROID_A ) )
   10.42 +#define PGL_SUBEPS2    (1.0-PGL_EPS2)
   10.43 +#define PGL_DIAMETER   ((4.0*PGL_SPHEROID_A + 2.0*PGL_SPHEROID_B) / 3.0)
   10.44 +#define PGL_SCALE      (PGL_SPHEROID_A / PGL_DIAMETER)  /* semi-major ref. */
   10.45 +#define PGL_FADELIMIT  (PGL_DIAMETER * M_PI / 6.0)      /* 1/6 circumference */
   10.46 +#define PGL_MAXDIST    (PGL_DIAMETER * M_PI / 2.0)      /* maximum distance */
   10.47 +
   10.48 +/* calculate distance between two points on earth (given in degrees) */
   10.49 +static inline double pgl_distance(
   10.50 +  double lat1, double lon1, double lat2, double lon2
   10.51 +) {
   10.52 +  float8 lat1cos, lat1sin, lat2cos, lat2sin, lon2cos, lon2sin;
   10.53 +  float8 nphi1, nphi2, x1, z1, x2, y2, z2, g, s, t;
   10.54 +  /* normalize delta longitude (lon2 > 0 && lon1 = 0) */
   10.55 +  /* lon1 = 0 (not used anymore) */
   10.56 +  lon2 = fabs(lon2-lon1);
   10.57 +  /* convert to radians (first divide, then multiply) */
   10.58 +  lat1 = (lat1 / 180.0) * M_PI;
   10.59 +  lat2 = (lat2 / 180.0) * M_PI;
   10.60 +  lon2 = (lon2 / 180.0) * M_PI;
   10.61 +  /* make lat2 >= lat1 to ensure reversal-symmetry despite floating point
   10.62 +     operations (lon2 >= lon1 is already ensured in a previous step) */
   10.63 +  if (lat2 < lat1) { float8 swap = lat1; lat1 = lat2; lat2 = swap; }
   10.64 +  /* calculate 3d coordinates on scaled ellipsoid which has an average diameter
   10.65 +     of 1.0 */
   10.66 +  lat1cos = cos(lat1); lat1sin = sin(lat1);
   10.67 +  lat2cos = cos(lat2); lat2sin = sin(lat2);
   10.68 +  lon2cos = cos(lon2); lon2sin = sin(lon2);
   10.69 +  nphi1 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat1sin * lat1sin);
   10.70 +  nphi2 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat2sin * lat2sin);
   10.71 +  x1 = nphi1 * lat1cos;
   10.72 +  z1 = nphi1 * PGL_SUBEPS2 * lat1sin;
   10.73 +  x2 = nphi2 * lat2cos * lon2cos;
   10.74 +  y2 = nphi2 * lat2cos * lon2sin;
   10.75 +  z2 = nphi2 * PGL_SUBEPS2 * lat2sin;
   10.76 +  /* calculate tunnel distance through scaled (diameter 1.0) ellipsoid */
   10.77 +  g = sqrt((x2-x1)*(x2-x1) + y2*y2 + (z2-z1)*(z2-z1));
   10.78 +  /* convert tunnel distance through scaled ellipsoid to approximated surface
   10.79 +     distance on original ellipsoid */
   10.80 +  if (g > 1.0) g = 1.0;
   10.81 +  s = PGL_DIAMETER * asin(g);
   10.82 +  /* return result only if small enough to be precise (less than 1/3 of
   10.83 +     maximum possible distance) */
   10.84 +  if (s <= PGL_FADELIMIT) return s;
   10.85 +  /* determine antipodal point of second point (i.e. mirror second point) */
   10.86 +  lat2 = -lat2; lon2 = lon2 - M_PI;
   10.87 +  lat2cos = cos(lat2); lat2sin = sin(lat2);
   10.88 +  lon2cos = cos(lon2); lon2sin = sin(lon2);
   10.89 +  /* calculate 3d coordinates of antipodal point on scaled ellipsoid */
   10.90 +  nphi2 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat2sin * lat2sin);
   10.91 +  x2 = nphi2 * lat2cos * lon2cos;
   10.92 +  y2 = nphi2 * lat2cos * lon2sin;
   10.93 +  z2 = nphi2 * PGL_SUBEPS2 * lat2sin;
   10.94 +  /* calculate tunnel distance to antipodal point through scaled ellipsoid */
   10.95 +  g = sqrt((x2-x1)*(x2-x1) + y2*y2 + (z2-z1)*(z2-z1));
   10.96 +  /* convert tunnel distance to antipodal point through scaled ellipsoid to
   10.97 +     approximated surface distance to antipodal point on original ellipsoid */
   10.98 +  if (g > 1.0) g = 1.0;
   10.99 +  t = PGL_DIAMETER * asin(g);
  10.100 +  /* surface distance between original points can now be approximated by
  10.101 +     substracting antipodal distance from maximum possible distance;
  10.102 +     return result only if small enough (less than 1/3 of maximum possible
  10.103 +     distance) */
  10.104 +  if (t <= PGL_FADELIMIT) return PGL_MAXDIST-t;
  10.105 +  /* otherwise crossfade direct and antipodal result to ensure monotonicity */
  10.106 +  return (
  10.107 +    (s * (t-PGL_FADELIMIT) + (PGL_MAXDIST-t) * (s-PGL_FADELIMIT)) /
  10.108 +    (s + t - 2*PGL_FADELIMIT)
  10.109 +  );
  10.110 +}
  10.111 +
  10.112 +/* finite distance that can not be reached on earth */
  10.113 +#define PGL_ULTRA_DISTANCE (3 * PGL_MAXDIST)
  10.114 +
  10.115 +
  10.116 +/*--------------------------------*
  10.117 + *  simple geographic data types  *
  10.118 + *--------------------------------*/
  10.119 +
  10.120 +/* point on earth given by latitude and longitude in degrees */
  10.121 +/* (type "epoint" in SQL) */
  10.122 +typedef struct {
  10.123 +  double lat;  /* between  -90 and  90 (both inclusive) */
  10.124 +  double lon;  /* between -180 and 180 (both inclusive) */
  10.125 +} pgl_point;
  10.126 +
  10.127 +/* box delimited by two parallels and two meridians (all in degrees) */
  10.128 +/* (type "ebox" in SQL) */
  10.129 +typedef struct {
  10.130 +  double lat_min;  /* between  -90 and  90 (both inclusive) */
  10.131 +  double lat_max;  /* between  -90 and  90 (both inclusive) */
  10.132 +  double lon_min;  /* between -180 and 180 (both inclusive) */
  10.133 +  double lon_max;  /* between -180 and 180 (both inclusive) */
  10.134 +  /* if lat_min > lat_max, then box is empty */
  10.135 +  /* if lon_min > lon_max, then 180th meridian is crossed */
  10.136 +} pgl_box;
  10.137 +
  10.138 +/* circle on earth surface (for radial searches with fixed radius) */
  10.139 +/* (type "ecircle" in SQL) */
  10.140 +typedef struct {
  10.141 +  pgl_point center;
  10.142 +  double radius; /* positive (including +0 but excluding -0), or -INFINITY */
  10.143 +  /* A negative radius (i.e. -INFINITY) denotes nothing (i.e. no point),
  10.144 +     zero radius (0) denotes a single point,
  10.145 +     a finite radius (0 < radius < INFINITY) denotes a filled circle, and
  10.146 +     a radius of INFINITY is valid and means complete coverage of earth. */
  10.147 +} pgl_circle;
  10.148 +
  10.149 +
  10.150 +/*----------------------------------*
  10.151 + *  geographic "cluster" data type  *
  10.152 + *----------------------------------*/
  10.153 +
  10.154 +/* A cluster is a collection of points, paths, outlines, and polygons. If two
  10.155 +   polygons in a cluster overlap, the area covered by both polygons does not
  10.156 +   belong to the cluster. This way, a cluster can be used to describe complex
  10.157 +   shapes like polygons with holes. Outlines are non-filled polygons. Paths are
  10.158 +   open by default (i.e. the last point in the list is not connected with the
  10.159 +   first point in the list). Note that each outline or polygon in a cluster
  10.160 +   must cover a longitude range of less than 180 degrees to avoid ambiguities.
  10.161 +   Areas which are larger may be split into multiple polygons. */
  10.162 +
  10.163 +/* maximum number of points in a cluster */
  10.164 +/* (limited to avoid integer overflows, e.g. when allocating memory) */
  10.165 +#define PGL_CLUSTER_MAXPOINTS 16777216
  10.166 +
  10.167 +/* types of cluster entries */
  10.168 +#define PGL_ENTRY_POINT   1  /* a point */
  10.169 +#define PGL_ENTRY_PATH    2  /* a path from first point to last point */
  10.170 +#define PGL_ENTRY_OUTLINE 3  /* a non-filled polygon with given vertices */
  10.171 +#define PGL_ENTRY_POLYGON 4  /* a filled polygon with given vertices */
  10.172 +
  10.173 +/* Entries of a cluster are described by two different structs: pgl_newentry
  10.174 +   and pgl_entry. The first is used only during construction of a cluster, the
  10.175 +   second is used in all other cases (e.g. when reading clusters from the
  10.176 +   database, performing operations, etc). */
  10.177 +
  10.178 +/* entry for new geographic cluster during construction of that cluster */
  10.179 +typedef struct {
  10.180 +  int32_t entrytype;
  10.181 +  int32_t npoints;
  10.182 +  pgl_point *points;  /* pointer to an array of points (pgl_point) */
  10.183 +} pgl_newentry;
  10.184 +
  10.185 +/* entry of geographic cluster */
  10.186 +typedef struct {
  10.187 +  int32_t entrytype;  /* type of entry: point, path, outline, polygon */
  10.188 +  int32_t npoints;    /* number of stored points (set to 1 for point entry) */
  10.189 +  int32_t offset;     /* offset of pgl_point array from cluster base address */
  10.190 +  /* use macro PGL_ENTRY_POINTS to obtain a pointer to the array of points */
  10.191 +} pgl_entry;
  10.192 +
  10.193 +/* geographic cluster which is a collection of points, (open) paths, polygons,
  10.194 +   and outlines (non-filled polygons) */
  10.195 +typedef struct {
  10.196 +  char header[VARHDRSZ];  /* PostgreSQL header for variable size data types */
  10.197 +  int32_t nentries;       /* number of stored points */
  10.198 +  pgl_circle bounding;    /* bounding circle */
  10.199 +  /* Note: bounding circle ensures alignment of pgl_cluster for points */
  10.200 +  pgl_entry entries[FLEXIBLE_ARRAY_MEMBER];  /* var-length data */
  10.201 +} pgl_cluster;
  10.202 +
  10.203 +/* macro to determine memory alignment of points */
  10.204 +/* (needed to store pgl_point array after entries in pgl_cluster) */
  10.205 +typedef struct { char dummy; pgl_point aligned; } pgl_point_alignment;
  10.206 +#define PGL_POINT_ALIGNMENT offsetof(pgl_point_alignment, aligned)
  10.207 +
  10.208 +/* macro to extract a pointer to the array of points of a cluster entry */
  10.209 +#define PGL_ENTRY_POINTS(cluster, idx) \
  10.210 +  ((pgl_point *)(((intptr_t)cluster)+(cluster)->entries[idx].offset))
  10.211 +
  10.212 +/* convert pgl_newentry array to pgl_cluster */
  10.213 +static pgl_cluster *pgl_new_cluster(int nentries, pgl_newentry *entries) {
  10.214 +  int i;              /* index of current entry */
  10.215 +  int npoints = 0;    /* number of points in whole cluster */
  10.216 +  int entry_npoints;  /* number of points in current entry */
  10.217 +  int points_offset = PGL_POINT_ALIGNMENT * (
  10.218 +    ( offsetof(pgl_cluster, entries) +
  10.219 +      nentries * sizeof(pgl_entry) +
  10.220 +      PGL_POINT_ALIGNMENT - 1
  10.221 +    ) / PGL_POINT_ALIGNMENT
  10.222 +  );  /* offset of pgl_point array from base address (considering alignment) */
  10.223 +  pgl_cluster *cluster;  /* new cluster to be returned */
  10.224 +  /* determine total number of points */
  10.225 +  for (i=0; i<nentries; i++) npoints += entries[i].npoints;
  10.226 +  /* allocate memory for cluster (including entries and points) */
  10.227 +  cluster = palloc(points_offset + npoints * sizeof(pgl_point));
  10.228 +  /* re-count total number of points to determine offset for each entry */
  10.229 +  npoints = 0;
  10.230 +  /* copy entries and points */
  10.231 +  for (i=0; i<nentries; i++) {
  10.232 +    /* determine number of points in entry */
  10.233 +    entry_npoints = entries[i].npoints;
  10.234 +    /* copy entry */
  10.235 +    cluster->entries[i].entrytype = entries[i].entrytype;
  10.236 +    cluster->entries[i].npoints = entry_npoints;
  10.237 +    /* calculate offset (in bytes) of pgl_point array */
  10.238 +    cluster->entries[i].offset = points_offset + npoints * sizeof(pgl_point);
  10.239 +    /* copy points */
  10.240 +    memcpy(
  10.241 +      PGL_ENTRY_POINTS(cluster, i),
  10.242 +      entries[i].points,
  10.243 +      entry_npoints * sizeof(pgl_point)
  10.244 +    );
  10.245 +    /* update total number of points processed */
  10.246 +    npoints += entry_npoints;
  10.247 +  }
  10.248 +  /* set number of entries in cluster */
  10.249 +  cluster->nentries = nentries;
  10.250 +  /* set PostgreSQL header for variable sized data */
  10.251 +  SET_VARSIZE(cluster, points_offset + npoints * sizeof(pgl_point));
  10.252 +  /* return newly created cluster */
  10.253 +  return cluster;
  10.254 +}
  10.255 +
  10.256 +
  10.257 +/*----------------------------------------*
  10.258 + *  C functions on geographic data types  *
  10.259 + *----------------------------------------*/
  10.260 +
  10.261 +/* round latitude or longitude to 12 digits after decimal point */
  10.262 +static inline double pgl_round(double val) {
  10.263 +  return round(val * 1e12) / 1e12;
  10.264 +}
  10.265 +
  10.266 +/* compare two points */
  10.267 +/* (equality when same point on earth is described, otherwise an arbitrary
  10.268 +   linear order) */
  10.269 +static int pgl_point_cmp(pgl_point *point1, pgl_point *point2) {
  10.270 +  double lon1, lon2;  /* modified longitudes for special cases */
  10.271 +  /* use latitude as first ordering criterion */
  10.272 +  if (point1->lat < point2->lat) return -1;
  10.273 +  if (point1->lat > point2->lat) return 1;
  10.274 +  /* determine modified longitudes (considering special case of poles and
  10.275 +     180th meridian which can be described as W180 or E180) */
  10.276 +  if (point1->lat == -90 || point1->lat == 90) lon1 = 0;
  10.277 +  else if (point1->lon == 180) lon1 = -180;
  10.278 +  else lon1 = point1->lon;
  10.279 +  if (point2->lat == -90 || point2->lat == 90) lon2 = 0;
  10.280 +  else if (point2->lon == 180) lon2 = -180;
  10.281 +  else lon2 = point2->lon;
  10.282 +  /* use (modified) longitude as secondary ordering criterion */
  10.283 +  if (lon1 < lon2) return -1;
  10.284 +  if (lon1 > lon2) return 1;
  10.285 +  /* no difference found, points are equal */
  10.286 +  return 0;
  10.287 +}
  10.288 +
  10.289 +/* compare two boxes */
  10.290 +/* (equality when same box on earth is described, otherwise an arbitrary linear
  10.291 +   order) */
  10.292 +static int pgl_box_cmp(pgl_box *box1, pgl_box *box2) {
  10.293 +  /* two empty boxes are equal, and an empty box is always considered "less
  10.294 +     than" a non-empty box */
  10.295 +  if (box1->lat_min> box1->lat_max && box2->lat_min<=box2->lat_max) return -1;
  10.296 +  if (box1->lat_min> box1->lat_max && box2->lat_min> box2->lat_max) return 0;
  10.297 +  if (box1->lat_min<=box1->lat_max && box2->lat_min> box2->lat_max) return 1;
  10.298 +  /* use southern border as first ordering criterion */
  10.299 +  if (box1->lat_min < box2->lat_min) return -1;
  10.300 +  if (box1->lat_min > box2->lat_min) return 1;
  10.301 +  /* use northern border as second ordering criterion */
  10.302 +  if (box1->lat_max < box2->lat_max) return -1;
  10.303 +  if (box1->lat_max > box2->lat_max) return 1;
  10.304 +  /* use western border as third ordering criterion */
  10.305 +  if (box1->lon_min < box2->lon_min) return -1;
  10.306 +  if (box1->lon_min > box2->lon_min) return 1;
  10.307 +  /* use eastern border as fourth ordering criterion */
  10.308 +  if (box1->lon_max < box2->lon_max) return -1;
  10.309 +  if (box1->lon_max > box2->lon_max) return 1;
  10.310 +  /* no difference found, boxes are equal */
  10.311 +  return 0;
  10.312 +}
  10.313 +
  10.314 +/* compare two circles */
  10.315 +/* (equality when same circle on earth is described, otherwise an arbitrary
  10.316 +   linear order) */
  10.317 +static int pgl_circle_cmp(pgl_circle *circle1, pgl_circle *circle2) {
  10.318 +  /* two circles with same infinite radius (positive or negative infinity) are
  10.319 +     considered equal independently of center point */
  10.320 +  if (
  10.321 +    !isfinite(circle1->radius) && !isfinite(circle2->radius) &&
  10.322 +    circle1->radius == circle2->radius
  10.323 +  ) return 0;
  10.324 +  /* use radius as first ordering criterion */
  10.325 +  if (circle1->radius < circle2->radius) return -1;
  10.326 +  if (circle1->radius > circle2->radius) return 1;
  10.327 +  /* use center point as secondary ordering criterion */
  10.328 +  return pgl_point_cmp(&(circle1->center), &(circle2->center));
  10.329 +}
  10.330 +
  10.331 +/* set box to empty box*/
  10.332 +static void pgl_box_set_empty(pgl_box *box) {
  10.333 +  box->lat_min = INFINITY;
  10.334 +  box->lat_max = -INFINITY;
  10.335 +  box->lon_min = 0;
  10.336 +  box->lon_max = 0;
  10.337 +}
  10.338 +
  10.339 +/* check if point is inside a box */
  10.340 +static bool pgl_point_in_box(pgl_point *point, pgl_box *box) {
  10.341 +  return (
  10.342 +    point->lat >= box->lat_min && point->lat <= box->lat_max && (
  10.343 +      (box->lon_min > box->lon_max) ? (
  10.344 +        /* box crosses 180th meridian */
  10.345 +        point->lon >= box->lon_min || point->lon <= box->lon_max
  10.346 +      ) : (
  10.347 +        /* box does not cross the 180th meridian */
  10.348 +        point->lon >= box->lon_min && point->lon <= box->lon_max
  10.349 +      )
  10.350 +    )
  10.351 +  );
  10.352 +}
  10.353 +
  10.354 +/* check if two boxes overlap */
  10.355 +static bool pgl_boxes_overlap(pgl_box *box1, pgl_box *box2) {
  10.356 +  return (
  10.357 +    box2->lat_max >= box2->lat_min &&  /* ensure box2 is not empty */
  10.358 +    ( box2->lat_min >= box1->lat_min || box2->lat_max >= box1->lat_min ) &&
  10.359 +    ( box2->lat_min <= box1->lat_max || box2->lat_max <= box1->lat_max ) && (
  10.360 +      (
  10.361 +        /* check if one and only one box crosses the 180th meridian */
  10.362 +        ((box1->lon_min > box1->lon_max) ? 1 : 0) ^
  10.363 +        ((box2->lon_min > box2->lon_max) ? 1 : 0)
  10.364 +      ) ? (
  10.365 +        /* exactly one box crosses the 180th meridian */
  10.366 +        box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min ||
  10.367 +        box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max
  10.368 +      ) : (
  10.369 +        /* no box or both boxes cross the 180th meridian */
  10.370 +        (
  10.371 +          (box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min) &&
  10.372 +          (box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max)
  10.373 +        ) ||
  10.374 +        /* handle W180 == E180 */
  10.375 +        ( box1->lon_min == -180 && box2->lon_max == 180 ) ||
  10.376 +        ( box2->lon_min == -180 && box1->lon_max == 180 )
  10.377 +      )
  10.378 +    )
  10.379 +  );
  10.380 +}
  10.381 +
  10.382 +/* check unambiguousness of east/west orientation of cluster entries and set
  10.383 +   bounding circle of cluster */
  10.384 +static bool pgl_finalize_cluster(pgl_cluster *cluster) {
  10.385 +  int i, j;                 /* i: index of entry, j: index of point in entry */
  10.386 +  int npoints;              /* number of points in entry */
  10.387 +  int total_npoints = 0;    /* total number of points in cluster */
  10.388 +  pgl_point *points;        /* points in entry */
  10.389 +  int lon_dir;              /* first point of entry west (-1) or east (+1) */
  10.390 +  double lon_break = 0;     /* antipodal longitude of first point in entry */
  10.391 +  double lon_min, lon_max;  /* covered longitude range of entry */
  10.392 +  double value;             /* temporary variable */
  10.393 +  /* reset bounding circle center to empty circle at 0/0 coordinates */
  10.394 +  cluster->bounding.center.lat = 0;
  10.395 +  cluster->bounding.center.lon = 0;
  10.396 +  cluster->bounding.radius = -INFINITY;
  10.397 +  /* if cluster is not empty */
  10.398 +  if (cluster->nentries != 0) {
  10.399 +    /* iterate over all cluster entries and ensure they each cover a longitude
  10.400 +       range less than 180 degrees */
  10.401 +    for (i=0; i<cluster->nentries; i++) {
  10.402 +      /* get properties of entry */
  10.403 +      npoints = cluster->entries[i].npoints;
  10.404 +      points = PGL_ENTRY_POINTS(cluster, i);
  10.405 +      /* get longitude of first point of entry */
  10.406 +      value = points[0].lon;
  10.407 +      /* initialize lon_min and lon_max with longitude of first point */
  10.408 +      lon_min = value;
  10.409 +      lon_max = value;
  10.410 +      /* determine east/west orientation of first point and calculate antipodal
  10.411 +         longitude (Note: rounding required here) */
  10.412 +      if      (value < 0) { lon_dir = -1; lon_break = pgl_round(value + 180); }
  10.413 +      else if (value > 0) { lon_dir =  1; lon_break = pgl_round(value - 180); }
  10.414 +      else lon_dir = 0;
  10.415 +      /* iterate over all other points in entry */
  10.416 +      for (j=1; j<npoints; j++) {
  10.417 +        /* consider longitude wrap-around */
  10.418 +        value = points[j].lon;
  10.419 +        if      (lon_dir<0 && value>lon_break) value = pgl_round(value - 360);
  10.420 +        else if (lon_dir>0 && value<lon_break) value = pgl_round(value + 360);
  10.421 +        /* update lon_min and lon_max */
  10.422 +        if      (value < lon_min) lon_min = value;
  10.423 +        else if (value > lon_max) lon_max = value;
  10.424 +        /* return false if 180 degrees or more are covered */
  10.425 +        if (lon_max - lon_min >= 180) return false;
  10.426 +      }
  10.427 +    }
  10.428 +    /* iterate over all points of all entries and calculate arbitrary center
  10.429 +       point for bounding circle (best if center point minimizes the radius,
  10.430 +       but some error is allowed here) */
  10.431 +    for (i=0; i<cluster->nentries; i++) {
  10.432 +      /* get properties of entry */
  10.433 +      npoints = cluster->entries[i].npoints;
  10.434 +      points = PGL_ENTRY_POINTS(cluster, i);
  10.435 +      /* check if first entry */
  10.436 +      if (i==0) {
  10.437 +        /* get longitude of first point of first entry in whole cluster */
  10.438 +        value = points[0].lon;
  10.439 +        /* initialize lon_min and lon_max with longitude of first point of
  10.440 +           first entry in whole cluster (used to determine if whole cluster
  10.441 +           covers a longitude range of 180 degrees or more) */
  10.442 +        lon_min = value;
  10.443 +        lon_max = value;
  10.444 +        /* determine east/west orientation of first point and calculate
  10.445 +           antipodal longitude (Note: rounding not necessary here) */
  10.446 +        if      (value < 0) { lon_dir = -1; lon_break = value + 180; }
  10.447 +        else if (value > 0) { lon_dir =  1; lon_break = value - 180; }
  10.448 +        else lon_dir = 0;
  10.449 +      }
  10.450 +      /* iterate over all points in entry */
  10.451 +      for (j=0; j<npoints; j++) {
  10.452 +        /* longitude wrap-around (Note: rounding not necessary here) */
  10.453 +        value = points[j].lon;
  10.454 +        if      (lon_dir < 0 && value > lon_break) value -= 360;
  10.455 +        else if (lon_dir > 0 && value < lon_break) value += 360;
  10.456 +        if      (value < lon_min) lon_min = value;
  10.457 +        else if (value > lon_max) lon_max = value;
  10.458 +        /* set bounding circle to cover whole earth if more than 180 degrees
  10.459 +           are covered */
  10.460 +        if (lon_max - lon_min >= 180) {
  10.461 +          cluster->bounding.center.lat = 0;
  10.462 +          cluster->bounding.center.lon = 0;
  10.463 +          cluster->bounding.radius = INFINITY;
  10.464 +          return true;
  10.465 +        }
  10.466 +        /* add point to bounding circle center (for average calculation) */
  10.467 +        cluster->bounding.center.lat += points[j].lat;
  10.468 +        cluster->bounding.center.lon += value;
  10.469 +      }
  10.470 +      /* count total number of points */
  10.471 +      total_npoints += npoints;
  10.472 +    }
  10.473 +    /* determine average latitude and longitude of cluster */
  10.474 +    cluster->bounding.center.lat /= total_npoints;
  10.475 +    cluster->bounding.center.lon /= total_npoints;
  10.476 +    /* normalize longitude of center of cluster bounding circle */
  10.477 +    if (cluster->bounding.center.lon < -180) {
  10.478 +      cluster->bounding.center.lon += 360;
  10.479 +    }
  10.480 +    else if (cluster->bounding.center.lon > 180) {
  10.481 +      cluster->bounding.center.lon -= 360;
  10.482 +    }
  10.483 +    /* round bounding circle center (useful if it is used by other functions) */
  10.484 +    cluster->bounding.center.lat = pgl_round(cluster->bounding.center.lat);
  10.485 +    cluster->bounding.center.lon = pgl_round(cluster->bounding.center.lon);
  10.486 +    /* calculate radius of bounding circle */
  10.487 +    for (i=0; i<cluster->nentries; i++) {
  10.488 +      npoints = cluster->entries[i].npoints;
  10.489 +      points = PGL_ENTRY_POINTS(cluster, i);
  10.490 +      for (j=0; j<npoints; j++) {
  10.491 +        value = pgl_distance(
  10.492 +          cluster->bounding.center.lat, cluster->bounding.center.lon,
  10.493 +          points[j].lat, points[j].lon
  10.494 +        );
  10.495 +        if (value > cluster->bounding.radius) cluster->bounding.radius = value;
  10.496 +      }
  10.497 +    }
  10.498 +  }
  10.499 +  /* return true (east/west orientation is unambiguous) */
  10.500 +  return true;
  10.501 +}
  10.502 +
  10.503 +/* check if point is inside cluster */
  10.504 +static bool pgl_point_in_cluster(pgl_point *point, pgl_cluster *cluster) {
  10.505 +  int i, j, k;  /* i: entry, j: point in entry, k: next point in entry */
  10.506 +  int entrytype;         /* type of entry */
  10.507 +  int npoints;           /* number of points in entry */
  10.508 +  pgl_point *points;     /* array of points in entry */
  10.509 +  int lon_dir = 0;       /* first vertex west (-1) or east (+1) */
  10.510 +  double lon_break = 0;  /* antipodal longitude of first vertex */
  10.511 +  double lat0 = point->lat;  /* latitude of point */
  10.512 +  double lon0;           /* (adjusted) longitude of point */
  10.513 +  double lat1, lon1;     /* latitude and (adjusted) longitude of vertex */
  10.514 +  double lat2, lon2;     /* latitude and (adjusted) longitude of next vertex */
  10.515 +  double lon;            /* longitude of intersection */
  10.516 +  int counter = 0;       /* counter for intersections east of point */
  10.517 +  /* points outside bounding circle are always assumed to be non-overlapping */
  10.518 +  /* (necessary for consistent table and index scans) */
  10.519 +  if (
  10.520 +    pgl_distance(
  10.521 +      point->lat, point->lon,
  10.522 +      cluster->bounding.center.lat, cluster->bounding.center.lon
  10.523 +    ) > cluster->bounding.radius
  10.524 +  ) return false;
  10.525 +  /* iterate over all entries */
  10.526 +  for (i=0; i<cluster->nentries; i++) {
  10.527 +    /* get properties of entry */
  10.528 +    entrytype = cluster->entries[i].entrytype;
  10.529 +    npoints = cluster->entries[i].npoints;
  10.530 +    points = PGL_ENTRY_POINTS(cluster, i);
  10.531 +    /* determine east/west orientation of first point of entry and calculate
  10.532 +       antipodal longitude */
  10.533 +    lon_break = points[0].lon;
  10.534 +    if      (lon_break < 0) { lon_dir = -1; lon_break += 180; }
  10.535 +    else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; }
  10.536 +    else lon_dir = 0;
  10.537 +    /* get longitude of point */
  10.538 +    lon0 = point->lon;
  10.539 +    /* consider longitude wrap-around for point */
  10.540 +    if      (lon_dir < 0 && lon0 > lon_break) lon0 = pgl_round(lon0 - 360);
  10.541 +    else if (lon_dir > 0 && lon0 < lon_break) lon0 = pgl_round(lon0 + 360);
  10.542 +    /* iterate over all edges and vertices */
  10.543 +    for (j=0; j<npoints; j++) {
  10.544 +      /* return true if point is on vertex of polygon */
  10.545 +      if (pgl_point_cmp(point, &(points[j])) == 0) return true;
  10.546 +      /* calculate index of next vertex */
  10.547 +      k = (j+1) % npoints;
  10.548 +      /* skip last edge unless entry is (closed) outline or polygon */
  10.549 +      if (
  10.550 +        k == 0 &&
  10.551 +        entrytype != PGL_ENTRY_OUTLINE &&
  10.552 +        entrytype != PGL_ENTRY_POLYGON
  10.553 +      ) continue;
  10.554 +      /* get latitude and longitude values of edge */
  10.555 +      lat1 = points[j].lat;
  10.556 +      lat2 = points[k].lat;
  10.557 +      lon1 = points[j].lon;
  10.558 +      lon2 = points[k].lon;
  10.559 +      /* consider longitude wrap-around for edge */
  10.560 +      if      (lon_dir < 0 && lon1 > lon_break) lon1 = pgl_round(lon1 - 360);
  10.561 +      else if (lon_dir > 0 && lon1 < lon_break) lon1 = pgl_round(lon1 + 360);
  10.562 +      if      (lon_dir < 0 && lon2 > lon_break) lon2 = pgl_round(lon2 - 360);
  10.563 +      else if (lon_dir > 0 && lon2 < lon_break) lon2 = pgl_round(lon2 + 360);
  10.564 +      /* return true if point is on horizontal (west to east) edge of polygon */
  10.565 +      if (
  10.566 +        lat0 == lat1 && lat0 == lat2 &&
  10.567 +        ( (lon0 >= lon1 && lon0 <= lon2) || (lon0 >= lon2 && lon0 <= lon1) )
  10.568 +      ) return true;
  10.569 +      /* check if edge crosses east/west line of point */
  10.570 +      if ((lat1 < lat0 && lat2 >= lat0) || (lat2 < lat0 && lat1 >= lat0)) {
  10.571 +        /* calculate longitude of intersection */
  10.572 +        lon = (lon1 * (lat2-lat0) + lon2 * (lat0-lat1)) / (lat2-lat1);
  10.573 +        /* return true if intersection goes (approximately) through point */
  10.574 +        if (pgl_round(lon) == lon0) return true;
  10.575 +        /* count intersection if east of point and entry is polygon*/
  10.576 +        if (entrytype == PGL_ENTRY_POLYGON && lon > lon0) counter++;
  10.577 +      }
  10.578 +    }
  10.579 +  }
  10.580 +  /* return true if number of intersections is odd */
  10.581 +  return counter & 1;
  10.582 +}
  10.583 +
  10.584 +/* calculate (approximate) distance between point and cluster */
  10.585 +static double pgl_point_cluster_distance(pgl_point *point, pgl_cluster *cluster) {
  10.586 +  int i, j, k;  /* i: entry, j: point in entry, k: next point in entry */
  10.587 +  int entrytype;         /* type of entry */
  10.588 +  int npoints;           /* number of points in entry */
  10.589 +  pgl_point *points;     /* array of points in entry */
  10.590 +  int lon_dir = 0;       /* first vertex west (-1) or east (+1) */
  10.591 +  double lon_break = 0;  /* antipodal longitude of first vertex */
  10.592 +  double lon_min = 0;    /* minimum (adjusted) longitude of entry vertices */
  10.593 +  double lon_max = 0;    /* maximum (adjusted) longitude of entry vertices */
  10.594 +  double lat0 = point->lat;  /* latitude of point */
  10.595 +  double lon0;           /* (adjusted) longitude of point */
  10.596 +  double lat1, lon1;     /* latitude and (adjusted) longitude of vertex */
  10.597 +  double lat2, lon2;     /* latitude and (adjusted) longitude of next vertex */
  10.598 +  double s;              /* scalar for vector calculations */
  10.599 +  double dist;           /* distance calculated in one step */
  10.600 +  double min_dist = INFINITY;   /* minimum distance */
  10.601 +  /* distance is zero if point is contained in cluster */
  10.602 +  if (pgl_point_in_cluster(point, cluster)) return 0;
  10.603 +  /* iterate over all entries */
  10.604 +  for (i=0; i<cluster->nentries; i++) {
  10.605 +    /* get properties of entry */
  10.606 +    entrytype = cluster->entries[i].entrytype;
  10.607 +    npoints = cluster->entries[i].npoints;
  10.608 +    points = PGL_ENTRY_POINTS(cluster, i);
  10.609 +    /* determine east/west orientation of first point of entry and calculate
  10.610 +       antipodal longitude */
  10.611 +    lon_break = points[0].lon;
  10.612 +    if      (lon_break < 0) { lon_dir = -1; lon_break += 180; }
  10.613 +    else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; }
  10.614 +    else lon_dir = 0;
  10.615 +    /* determine covered longitude range */
  10.616 +    for (j=0; j<npoints; j++) {
  10.617 +      /* get longitude of vertex */
  10.618 +      lon1 = points[j].lon;
  10.619 +      /* adjust longitude to fix potential wrap-around */
  10.620 +      if      (lon_dir < 0 && lon1 > lon_break) lon1 -= 360;
  10.621 +      else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360;
  10.622 +      /* update minimum and maximum longitude of polygon */
  10.623 +      if (j == 0 || lon1 < lon_min) lon_min = lon1;
  10.624 +      if (j == 0 || lon1 > lon_max) lon_max = lon1;
  10.625 +    }
  10.626 +    /* adjust longitude wrap-around according to full longitude range */
  10.627 +    lon_break = (lon_max + lon_min) / 2;
  10.628 +    if      (lon_break < 0) { lon_dir = -1; lon_break += 180; }
  10.629 +    else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; }
  10.630 +    /* get longitude of point */
  10.631 +    lon0 = point->lon;
  10.632 +    /* consider longitude wrap-around for point */
  10.633 +    if      (lon_dir < 0 && lon0 > lon_break) lon0 -= 360;
  10.634 +    else if (lon_dir > 0 && lon0 < lon_break) lon0 += 360;
  10.635 +    /* iterate over all edges and vertices */
  10.636 +    for (j=0; j<npoints; j++) {
  10.637 +      /* get latitude and longitude values of current point */
  10.638 +      lat1 = points[j].lat;
  10.639 +      lon1 = points[j].lon;
  10.640 +      /* consider longitude wrap-around for current point */
  10.641 +      if      (lon_dir < 0 && lon1 > lon_break) lon1 -= 360;
  10.642 +      else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360;
  10.643 +      /* calculate distance to vertex */
  10.644 +      dist = pgl_distance(lat0, lon0, lat1, lon1);
  10.645 +      /* store calculated distance if smallest */
  10.646 +      if (dist < min_dist) min_dist = dist;
  10.647 +      /* calculate index of next vertex */
  10.648 +      k = (j+1) % npoints;
  10.649 +      /* skip last edge unless entry is (closed) outline or polygon */
  10.650 +      if (
  10.651 +        k == 0 &&
  10.652 +        entrytype != PGL_ENTRY_OUTLINE &&
  10.653 +        entrytype != PGL_ENTRY_POLYGON
  10.654 +      ) continue;
  10.655 +      /* get latitude and longitude values of next point */
  10.656 +      lat2 = points[k].lat;
  10.657 +      lon2 = points[k].lon;
  10.658 +      /* consider longitude wrap-around for next point */
  10.659 +      if      (lon_dir < 0 && lon2 > lon_break) lon2 -= 360;
  10.660 +      else if (lon_dir > 0 && lon2 < lon_break) lon2 += 360;
  10.661 +      /* go to next vertex and edge if edge is degenerated */
  10.662 +      if (lat1 == lat2 && lon1 == lon2) continue;
  10.663 +      /* otherwise test if point can be projected onto edge of polygon */
  10.664 +      s = (
  10.665 +        ((lat0-lat1) * (lat2-lat1) + (lon0-lon1) * (lon2-lon1)) /
  10.666 +        ((lat2-lat1) * (lat2-lat1) + (lon2-lon1) * (lon2-lon1))
  10.667 +      );
  10.668 +      /* go to next vertex and edge if point cannot be projected */
  10.669 +      if (!(s > 0 && s < 1)) continue;
  10.670 +      /* calculate distance from original point to projected point */
  10.671 +      dist = pgl_distance(
  10.672 +        lat0, lon0,
  10.673 +        lat1 + s * (lat2-lat1),
  10.674 +        lon1 + s * (lon2-lon1)
  10.675 +      );
  10.676 +      /* store calculated distance if smallest */
  10.677 +      if (dist < min_dist) min_dist = dist;
  10.678 +    }
  10.679 +  }
  10.680 +  /* return minimum distance */
  10.681 +  return min_dist;
  10.682 +}
  10.683 +
  10.684 +/* estimator function for distance between box and point */
  10.685 +/* allowed to return smaller values than actually correct */
  10.686 +static double pgl_estimate_point_box_distance(pgl_point *point, pgl_box *box) {
  10.687 +  double dlon;  /* longitude range of box (delta longitude) */
  10.688 +  double h;     /* half of distance along meridian */
  10.689 +  double d;     /* distance between both southern or both northern points */
  10.690 +  double cur_dist;  /* calculated distance */
  10.691 +  double min_dist;  /* minimum distance calculated */
  10.692 +  /* return infinity if bounding box is empty */
  10.693 +  if (box->lat_min > box->lat_max) return INFINITY;
  10.694 +  /* return zero if point is inside bounding box */
  10.695 +  if (pgl_point_in_box(point, box)) return 0;
  10.696 +  /* calculate delta longitude */
  10.697 +  dlon = box->lon_max - box->lon_min;
  10.698 +  if (dlon < 0) dlon += 360;  /* 180th meridian crossed */
  10.699 +  /* if delta longitude is greater than 180 degrees, perform safe fall-back */
  10.700 +  if (dlon > 180) return 0;
  10.701 +  /* calculate half of distance along meridian */
  10.702 +  h = pgl_distance(box->lat_min, 0, box->lat_max, 0) / 2;
  10.703 +  /* calculate full distance between southern points */
  10.704 +  d = pgl_distance(box->lat_min, 0, box->lat_min, dlon);
  10.705 +  /* calculate maximum of full distance and half distance */
  10.706 +  if (h > d) d = h;
  10.707 +  /* calculate distance from point to first southern vertex and substract
  10.708 +     maximum error */
  10.709 +  min_dist = pgl_distance(
  10.710 +    point->lat, point->lon, box->lat_min, box->lon_min
  10.711 +  ) - d;
  10.712 +  /* return zero if estimated distance is smaller than zero */
  10.713 +  if (min_dist <= 0) return 0;
  10.714 +  /* repeat procedure with second southern vertex */
  10.715 +  cur_dist = pgl_distance(
  10.716 +    point->lat, point->lon, box->lat_min, box->lon_max
  10.717 +  ) - d;
  10.718 +  if (cur_dist <= 0) return 0;
  10.719 +  if (cur_dist < min_dist) min_dist = cur_dist;
  10.720 +  /* calculate full distance between northern points */
  10.721 +  d = pgl_distance(box->lat_max, 0, box->lat_max, dlon);
  10.722 +  /* calculate maximum of full distance and half distance */
  10.723 +  if (h > d) d = h;
  10.724 +  /* repeat procedure with northern vertices */
  10.725 +  cur_dist = pgl_distance(
  10.726 +    point->lat, point->lon, box->lat_max, box->lon_max
  10.727 +  ) - d;
  10.728 +  if (cur_dist <= 0) return 0;
  10.729 +  if (cur_dist < min_dist) min_dist = cur_dist;
  10.730 +  cur_dist = pgl_distance(
  10.731 +    point->lat, point->lon, box->lat_max, box->lon_min
  10.732 +  ) - d;
  10.733 +  if (cur_dist <= 0) return 0;
  10.734 +  if (cur_dist < min_dist) min_dist = cur_dist;
  10.735 +  /* return smallest value (unless already returned zero) */
  10.736 +  return min_dist;
  10.737 +}
  10.738 +
  10.739 +
  10.740 +/*----------------------------*
  10.741 + *  fractal geographic index  *
  10.742 + *----------------------------*/
  10.743 +
  10.744 +/* number of bytes used for geographic (center) position in keys */
  10.745 +#define PGL_KEY_LATLON_BYTELEN 7
  10.746 +
  10.747 +/* maximum reference value for logarithmic size of geographic objects */
  10.748 +#define PGL_AREAKEY_REFOBJSIZE (PGL_DIAMETER/3.0)  /* can be tweaked */
  10.749 +
  10.750 +/* safety margin to avoid floating point errors in distance estimation */
  10.751 +#define PGL_FPE_SAFETY (1.0+1e-14)  /* slightly greater than 1.0 */
  10.752 +
  10.753 +/* pointer to index key (either pgl_pointkey or pgl_areakey) */
  10.754 +typedef unsigned char *pgl_keyptr;
  10.755 +
  10.756 +/* index key for points (objects with zero area) on the spheroid */
  10.757 +/* bit  0..55: interspersed bits of latitude and longitude,
  10.758 +   bit 56..57: always zero,
  10.759 +   bit 58..63: node depth in hypothetic (full) tree from 0 to 56 (incl.) */
  10.760 +typedef unsigned char pgl_pointkey[PGL_KEY_LATLON_BYTELEN+1];
  10.761 +
  10.762 +/* index key for geographic objects on spheroid with area greater than zero */
  10.763 +/* bit  0..55: interspersed bits of latitude and longitude of center point,
  10.764 +   bit     56: always set to 1,
  10.765 +   bit 57..63: node depth in hypothetic (full) tree from 0 to (2*56)+1 (incl.),
  10.766 +   bit 64..71: logarithmic object size from 0 to 56+1 = 57 (incl.), but set to
  10.767 +               PGL_KEY_OBJSIZE_EMPTY (with interspersed bits = 0 and node depth
  10.768 +               = 113) for empty objects, and set to PGL_KEY_OBJSIZE_UNIVERSAL
  10.769 +               (with interspersed bits = 0 and node depth = 0) for keys which
  10.770 +               cover both empty and non-empty objects */
  10.771 +
  10.772 +typedef unsigned char pgl_areakey[PGL_KEY_LATLON_BYTELEN+2];
  10.773 +
  10.774 +/* helper macros for reading/writing index keys */
  10.775 +#define PGL_KEY_NODEDEPTH_OFFSET  PGL_KEY_LATLON_BYTELEN
  10.776 +#define PGL_KEY_OBJSIZE_OFFSET    (PGL_KEY_NODEDEPTH_OFFSET+1)
  10.777 +#define PGL_POINTKEY_MAXDEPTH     (PGL_KEY_LATLON_BYTELEN*8)
  10.778 +#define PGL_AREAKEY_MAXDEPTH      (2*PGL_POINTKEY_MAXDEPTH+1)
  10.779 +#define PGL_AREAKEY_MAXOBJSIZE    (PGL_POINTKEY_MAXDEPTH+1)
  10.780 +#define PGL_AREAKEY_TYPEMASK      0x80
  10.781 +#define PGL_KEY_LATLONBIT(key, n) ((key)[(n)/8] & (0x80 >> ((n)%8)))
  10.782 +#define PGL_KEY_LATLONBIT_DIFF(key1, key2, n) \
  10.783 +                                  ( PGL_KEY_LATLONBIT(key1, n) ^ \
  10.784 +                                    PGL_KEY_LATLONBIT(key2, n) )
  10.785 +#define PGL_KEY_IS_AREAKEY(key)   ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \
  10.786 +                                    PGL_AREAKEY_TYPEMASK)
  10.787 +#define PGL_KEY_NODEDEPTH(key)    ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \
  10.788 +                                    (PGL_AREAKEY_TYPEMASK-1))
  10.789 +#define PGL_KEY_OBJSIZE(key)      ((key)[PGL_KEY_OBJSIZE_OFFSET])
  10.790 +#define PGL_KEY_OBJSIZE_EMPTY     126
  10.791 +#define PGL_KEY_OBJSIZE_UNIVERSAL 127
  10.792 +#define PGL_KEY_IS_EMPTY(key)     ( PGL_KEY_IS_AREAKEY(key) && \
  10.793 +                                    (key)[PGL_KEY_OBJSIZE_OFFSET] == \
  10.794 +                                    PGL_KEY_OBJSIZE_EMPTY )
  10.795 +#define PGL_KEY_IS_UNIVERSAL(key) ( PGL_KEY_IS_AREAKEY(key) && \
  10.796 +                                    (key)[PGL_KEY_OBJSIZE_OFFSET] == \
  10.797 +                                    PGL_KEY_OBJSIZE_UNIVERSAL )
  10.798 +
  10.799 +/* set area key to match empty objects only */
  10.800 +static void pgl_key_set_empty(pgl_keyptr key) {
  10.801 +  memset(key, 0, sizeof(pgl_areakey));
  10.802 +  /* Note: setting node depth to maximum is required for picksplit function */
  10.803 +  key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH;
  10.804 +  key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_EMPTY;
  10.805 +}
  10.806 +
  10.807 +/* set area key to match any object (including empty objects) */
  10.808 +static void pgl_key_set_universal(pgl_keyptr key) {
  10.809 +  memset(key, 0, sizeof(pgl_areakey));
  10.810 +  key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK;
  10.811 +  key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_UNIVERSAL;
  10.812 +}
  10.813 +
  10.814 +/* convert a point on earth into a max-depth key to be used in index */
  10.815 +static void pgl_point_to_key(pgl_point *point, pgl_keyptr key) {
  10.816 +  double lat = point->lat;
  10.817 +  double lon = point->lon;
  10.818 +  int i;
  10.819 +  /* clear latitude and longitude bits */
  10.820 +  memset(key, 0, PGL_KEY_LATLON_BYTELEN);
  10.821 +  /* set node depth to maximum and type bit to zero */
  10.822 +  key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_POINTKEY_MAXDEPTH;
  10.823 +  /* iterate over all latitude/longitude bit pairs */
  10.824 +  for (i=0; i<PGL_POINTKEY_MAXDEPTH/2; i++) {
  10.825 +    /* determine latitude bit */
  10.826 +    if (lat >= 0) {
  10.827 +      key[i/4] |= 0x80 >> (2*(i%4));
  10.828 +      lat *= 2; lat -= 90;
  10.829 +    } else {
  10.830 +      lat *= 2; lat += 90;
  10.831 +    }
  10.832 +    /* determine longitude bit */
  10.833 +    if (lon >= 0) {
  10.834 +      key[i/4] |= 0x80 >> (2*(i%4)+1);
  10.835 +      lon *= 2; lon -= 180;
  10.836 +    } else {
  10.837 +      lon *= 2; lon += 180;
  10.838 +    }
  10.839 +  }
  10.840 +}
  10.841 +
  10.842 +/* convert a circle on earth into a max-depth key to be used in an index */
  10.843 +static void pgl_circle_to_key(pgl_circle *circle, pgl_keyptr key) {
  10.844 +  /* handle special case of empty circle */
  10.845 +  if (circle->radius < 0) {
  10.846 +    pgl_key_set_empty(key);
  10.847 +    return;
  10.848 +  }
  10.849 +  /* perform same action as for point keys */
  10.850 +  pgl_point_to_key(&(circle->center), key);
  10.851 +  /* but overwrite type and node depth to fit area index key */
  10.852 +  key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH;
  10.853 +  /* check if radius is greater than (or equal to) reference size */
  10.854 +  /* (treat equal values as greater values for numerical safety) */
  10.855 +  if (circle->radius >= PGL_AREAKEY_REFOBJSIZE) {
  10.856 +    /* if yes, set logarithmic size to zero */
  10.857 +    key[PGL_KEY_OBJSIZE_OFFSET] = 0;
  10.858 +  } else {
  10.859 +    /* otherwise, determine logarithmic size iteratively */
  10.860 +    /* (one step is equivalent to a factor of sqrt(2)) */
  10.861 +    double reference = PGL_AREAKEY_REFOBJSIZE / M_SQRT2;
  10.862 +    int objsize = 1;
  10.863 +    while (objsize < PGL_AREAKEY_MAXOBJSIZE) {
  10.864 +      /* stop when radius is greater than (or equal to) adjusted reference */
  10.865 +      /* (treat equal values as greater values for numerical safety) */
  10.866 +      if (circle->radius >= reference) break;
  10.867 +      reference /= M_SQRT2;
  10.868 +      objsize++;
  10.869 +    }
  10.870 +    /* set logarithmic size to determined value */
  10.871 +    key[PGL_KEY_OBJSIZE_OFFSET] = objsize;
  10.872 +  }
  10.873 +}
  10.874 +
  10.875 +/* check if one key is subkey of another key or vice versa */
  10.876 +static bool pgl_keys_overlap(pgl_keyptr key1, pgl_keyptr key2) {
  10.877 +  int i;  /* key bit offset (includes both lat/lon and log. obj. size bits) */
  10.878 +  /* determine smallest depth */
  10.879 +  int depth1 = PGL_KEY_NODEDEPTH(key1);
  10.880 +  int depth2 = PGL_KEY_NODEDEPTH(key2);
  10.881 +  int depth = (depth1 < depth2) ? depth1 : depth2;
  10.882 +  /* check if keys are area keys (assuming that both keys have same type) */
  10.883 +  if (PGL_KEY_IS_AREAKEY(key1)) {
  10.884 +    int j = 0;  /* bit offset for logarithmic object size bits */
  10.885 +    int k = 0;  /* bit offset for latitude and longitude */
  10.886 +    /* fetch logarithmic object size information */
  10.887 +    int objsize1 = PGL_KEY_OBJSIZE(key1);
  10.888 +    int objsize2 = PGL_KEY_OBJSIZE(key2);
  10.889 +    /* handle special cases for empty objects (universal and empty keys) */
  10.890 +    if (
  10.891 +      objsize1 == PGL_KEY_OBJSIZE_UNIVERSAL ||
  10.892 +      objsize2 == PGL_KEY_OBJSIZE_UNIVERSAL
  10.893 +    ) return true;
  10.894 +    if (
  10.895 +      objsize1 == PGL_KEY_OBJSIZE_EMPTY ||
  10.896 +      objsize2 == PGL_KEY_OBJSIZE_EMPTY
  10.897 +    ) return objsize1 == objsize2;
  10.898 +    /* iterate through key bits */
  10.899 +    for (i=0; i<depth; i++) {
  10.900 +      /* every second bit is a bit describing the object size */
  10.901 +      if (i%2 == 0) {
  10.902 +        /* check if object size bit is different in both keys (objsize1 and
  10.903 +           objsize2 describe the minimum index when object size bit is set) */
  10.904 +        if (
  10.905 +          (objsize1 <= j && objsize2 > j) ||
  10.906 +          (objsize2 <= j && objsize1 > j)
  10.907 +        ) {
  10.908 +          /* bit differs, therefore keys are in separate branches */
  10.909 +          return false;
  10.910 +        }
  10.911 +        /* increase bit counter for object size bits */
  10.912 +        j++;
  10.913 +      }
  10.914 +      /* all other bits describe latitude and longitude */
  10.915 +      else {
  10.916 +        /* check if bit differs in both keys */
  10.917 +        if (PGL_KEY_LATLONBIT_DIFF(key1, key2, k)) {
  10.918 +          /* bit differs, therefore keys are in separate branches */
  10.919 +          return false;
  10.920 +        }
  10.921 +        /* increase bit counter for latitude/longitude bits */
  10.922 +        k++;
  10.923 +      }
  10.924 +    }
  10.925 +  }
  10.926 +  /* if not, keys are point keys */
  10.927 +  else {
  10.928 +    /* iterate through key bits */
  10.929 +    for (i=0; i<depth; i++) {
  10.930 +      /* check if bit differs in both keys */
  10.931 +      if (PGL_KEY_LATLONBIT_DIFF(key1, key2, i)) {
  10.932 +        /* bit differs, therefore keys are in separate branches */
  10.933 +        return false;
  10.934 +      }
  10.935 +    }
  10.936 +  }
  10.937 +  /* return true because keys are in the same branch */
  10.938 +  return true;
  10.939 +}
  10.940 +
  10.941 +/* combine two keys into new key which covers both original keys */
  10.942 +/* (result stored in first argument) */
  10.943 +static void pgl_unite_keys(pgl_keyptr dst, pgl_keyptr src) {
  10.944 +  int i;  /* key bit offset (includes both lat/lon and log. obj. size bits) */
  10.945 +  /* determine smallest depth */
  10.946 +  int depth1 = PGL_KEY_NODEDEPTH(dst);
  10.947 +  int depth2 = PGL_KEY_NODEDEPTH(src);
  10.948 +  int depth = (depth1 < depth2) ? depth1 : depth2;
  10.949 +  /* check if keys are area keys (assuming that both keys have same type) */
  10.950 +  if (PGL_KEY_IS_AREAKEY(dst)) {
  10.951 +    pgl_areakey dstbuf = { 0, };  /* destination buffer (cleared) */
  10.952 +    int j = 0;  /* bit offset for logarithmic object size bits */
  10.953 +    int k = 0;  /* bit offset for latitude and longitude */
  10.954 +    /* fetch logarithmic object size information */
  10.955 +    int objsize1 = PGL_KEY_OBJSIZE(dst);
  10.956 +    int objsize2 = PGL_KEY_OBJSIZE(src);
  10.957 +    /* handle special cases for empty objects (universal and empty keys) */
  10.958 +    if (
  10.959 +      objsize1 > PGL_AREAKEY_MAXOBJSIZE ||
  10.960 +      objsize2 > PGL_AREAKEY_MAXOBJSIZE
  10.961 +    ) {
  10.962 +      if (
  10.963 +        objsize1 == PGL_KEY_OBJSIZE_EMPTY &&
  10.964 +        objsize2 == PGL_KEY_OBJSIZE_EMPTY
  10.965 +      ) pgl_key_set_empty(dst);
  10.966 +      else pgl_key_set_universal(dst);
  10.967 +      return;
  10.968 +    }
  10.969 +    /* iterate through key bits */
  10.970 +    for (i=0; i<depth; i++) {
  10.971 +      /* every second bit is a bit describing the object size */
  10.972 +      if (i%2 == 0) {
  10.973 +        /* increase bit counter for object size bits first */
  10.974 +        /* (handy when setting objsize variable) */
  10.975 +        j++;
  10.976 +        /* check if object size bit is set in neither key */
  10.977 +        if (objsize1 >= j && objsize2 >= j) {
  10.978 +          /* set objsize in destination buffer to indicate that size bit is
  10.979 +             unset in destination buffer at the current bit position */
  10.980 +          dstbuf[PGL_KEY_OBJSIZE_OFFSET] = j;
  10.981 +        }
  10.982 +        /* break if object size bit is set in one key only */
  10.983 +        else if (objsize1 >= j || objsize2 >= j) break;
  10.984 +      }
  10.985 +      /* all other bits describe latitude and longitude */
  10.986 +      else {
  10.987 +        /* break if bit differs in both keys */
  10.988 +        if (PGL_KEY_LATLONBIT(dst, k)) {
  10.989 +          if (!PGL_KEY_LATLONBIT(src, k)) break;
  10.990 +          /* but set bit in destination buffer if bit is set in both keys */
  10.991 +          dstbuf[k/8] |= 0x80 >> (k%8);
  10.992 +        } else if (PGL_KEY_LATLONBIT(src, k)) break;
  10.993 +        /* increase bit counter for latitude/longitude bits */
  10.994 +        k++;
  10.995 +      }
  10.996 +    }
  10.997 +    /* set common node depth and type bit (type bit = 1) */
  10.998 +    dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | i;
  10.999 +    /* copy contents of destination buffer to first key */
 10.1000 +    memcpy(dst, dstbuf, sizeof(pgl_areakey));
 10.1001 +  }
 10.1002 +  /* if not, keys are point keys */
 10.1003 +  else {
 10.1004 +    pgl_pointkey dstbuf = { 0, };  /* destination buffer (cleared) */
 10.1005 +    /* iterate through key bits */
 10.1006 +    for (i=0; i<depth; i++) {
 10.1007 +      /* break if bit differs in both keys */
 10.1008 +      if (PGL_KEY_LATLONBIT(dst, i)) {
 10.1009 +        if (!PGL_KEY_LATLONBIT(src, i)) break;
 10.1010 +        /* but set bit in destination buffer if bit is set in both keys */
 10.1011 +        dstbuf[i/8] |= 0x80 >> (i%8);
 10.1012 +      } else if (PGL_KEY_LATLONBIT(src, i)) break;
 10.1013 +    }
 10.1014 +    /* set common node depth (type bit = 0) */
 10.1015 +    dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = i;
 10.1016 +    /* copy contents of destination buffer to first key */
 10.1017 +    memcpy(dst, dstbuf, sizeof(pgl_pointkey));
 10.1018 +  }
 10.1019 +}
 10.1020 +
 10.1021 +/* determine center(!) boundaries and radius estimation of index key */
 10.1022 +static double pgl_key_to_box(pgl_keyptr key, pgl_box *box) {
 10.1023 +  int i;
 10.1024 +  /* determine node depth */
 10.1025 +  int depth = PGL_KEY_NODEDEPTH(key);
 10.1026 +  /* center point of possible result */
 10.1027 +  double lat = 0;
 10.1028 +  double lon = 0;
 10.1029 +  /* maximum distance of real center point from key center */
 10.1030 +  double dlat = 90;
 10.1031 +  double dlon = 180;
 10.1032 +  /* maximum radius of contained objects */
 10.1033 +  double radius = 0;  /* always return zero for point index keys */
 10.1034 +  /* check if key is area key */
 10.1035 +  if (PGL_KEY_IS_AREAKEY(key)) {
 10.1036 +    /* get logarithmic object size */
 10.1037 +    int objsize = PGL_KEY_OBJSIZE(key);
 10.1038 +    /* handle special cases for empty objects (universal and empty keys) */
 10.1039 +    if (objsize == PGL_KEY_OBJSIZE_EMPTY) {
 10.1040 +      pgl_box_set_empty(box);
 10.1041 +      return 0;
 10.1042 +    } else if (objsize == PGL_KEY_OBJSIZE_UNIVERSAL) {
 10.1043 +      box->lat_min = -90;
 10.1044 +      box->lat_max =  90;
 10.1045 +      box->lon_min = -180;
 10.1046 +      box->lon_max =  180;
 10.1047 +      return 0;  /* any value >= 0 would do */
 10.1048 +    }
 10.1049 +    /* calculate maximum possible radius of objects covered by the given key */
 10.1050 +    if (objsize == 0) radius = INFINITY;
 10.1051 +    else {
 10.1052 +      radius = PGL_AREAKEY_REFOBJSIZE;
 10.1053 +      while (--objsize) radius /= M_SQRT2;
 10.1054 +    }
 10.1055 +    /* iterate over latitude and longitude bits in key */
 10.1056 +    /* (every second bit is a latitude or longitude bit) */
 10.1057 +    for (i=0; i<depth/2; i++) {
 10.1058 +      /* check if latitude bit */
 10.1059 +      if (i%2 == 0) {
 10.1060 +        /* cut latitude dimension in half */
 10.1061 +        dlat /= 2;
 10.1062 +        /* increase center latitude if bit is 1, otherwise decrease */
 10.1063 +        if (PGL_KEY_LATLONBIT(key, i)) lat += dlat;
 10.1064 +        else lat -= dlat;
 10.1065 +      }
 10.1066 +      /* otherwise longitude bit */
 10.1067 +      else {
 10.1068 +        /* cut longitude dimension in half */
 10.1069 +        dlon /= 2;
 10.1070 +        /* increase center longitude if bit is 1, otherwise decrease */
 10.1071 +        if (PGL_KEY_LATLONBIT(key, i)) lon += dlon;
 10.1072 +        else lon -= dlon;
 10.1073 +      }
 10.1074 +    }
 10.1075 +  }
 10.1076 +  /* if not, keys are point keys */
 10.1077 +  else {
 10.1078 +    /* iterate over all bits in key */
 10.1079 +    for (i=0; i<depth; i++) {
 10.1080 +      /* check if latitude bit */
 10.1081 +      if (i%2 == 0) {
 10.1082 +        /* cut latitude dimension in half */
 10.1083 +        dlat /= 2;
 10.1084 +        /* increase center latitude if bit is 1, otherwise decrease */
 10.1085 +        if (PGL_KEY_LATLONBIT(key, i)) lat += dlat;
 10.1086 +        else lat -= dlat;
 10.1087 +      }
 10.1088 +      /* otherwise longitude bit */
 10.1089 +      else {
 10.1090 +        /* cut longitude dimension in half */
 10.1091 +        dlon /= 2;
 10.1092 +        /* increase center longitude if bit is 1, otherwise decrease */
 10.1093 +        if (PGL_KEY_LATLONBIT(key, i)) lon += dlon;
 10.1094 +        else lon -= dlon;
 10.1095 +      }
 10.1096 +    }
 10.1097 +  }
 10.1098 +  /* calculate boundaries from center point and remaining dlat and dlon */
 10.1099 +  /* (return values through pointer to box) */
 10.1100 +  box->lat_min = lat - dlat;
 10.1101 +  box->lat_max = lat + dlat;
 10.1102 +  box->lon_min = lon - dlon;
 10.1103 +  box->lon_max = lon + dlon;
 10.1104 +  /* return radius (as a function return value) */
 10.1105 +  return radius;
 10.1106 +}
 10.1107 +
 10.1108 +/* estimator function for distance between point and index key */
 10.1109 +/* allowed to return smaller values than actually correct */
 10.1110 +static double pgl_estimate_key_distance(pgl_keyptr key, pgl_point *point) {
 10.1111 +  pgl_box box;  /* center(!) bounding box of area index key */
 10.1112 +  /* calculate center(!) bounding box and maximum radius of objects covered
 10.1113 +     by area index key (radius is zero for point index keys) */
 10.1114 +  double distance = pgl_key_to_box(key, &box);
 10.1115 +  /* calculate estimated distance between bounding box of center point of
 10.1116 +     indexed object and point passed as second argument, then substract maximum
 10.1117 +     radius of objects covered by index key */
 10.1118 +  /* (use PGL_FPE_SAFETY factor to cope with minor floating point errors) */
 10.1119 +  distance = (
 10.1120 +    pgl_estimate_point_box_distance(point, &box) / PGL_FPE_SAFETY -
 10.1121 +    distance * PGL_FPE_SAFETY
 10.1122 +  );
 10.1123 +  /* truncate negative results to zero */
 10.1124 +  if (distance <= 0) distance = 0;
 10.1125 +  /* return result */
 10.1126 +  return distance;
 10.1127 +}
 10.1128 +
 10.1129 +
 10.1130 +/*---------------------------------*
 10.1131 + *  helper functions for text I/O  *
 10.1132 + *---------------------------------*/
 10.1133 +
 10.1134 +#define PGL_NUMBUFLEN 64  /* buffer size for number to string conversion */
 10.1135 +
 10.1136 +/* convert floating point number to string (round-trip safe) */
 10.1137 +static void pgl_print_float(char *buf, double flt) {
 10.1138 +  /* check if number is integral */
 10.1139 +  if (trunc(flt) == flt) {
 10.1140 +    /* for integral floats use maximum precision */
 10.1141 +    snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt);
 10.1142 +  } else {
 10.1143 +    /* otherwise check if 15, 16, or 17 digits needed (round-trip safety) */
 10.1144 +    snprintf(buf, PGL_NUMBUFLEN, "%.15g", flt);
 10.1145 +    if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.16g", flt);
 10.1146 +    if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt);
 10.1147 +  }
 10.1148 +}
 10.1149 +
 10.1150 +/* convert latitude floating point number (in degrees) to string */
 10.1151 +static void pgl_print_lat(char *buf, double lat) {
 10.1152 +  if (signbit(lat)) {
 10.1153 +    /* treat negative latitudes (including -0) as south */
 10.1154 +    snprintf(buf, PGL_NUMBUFLEN, "S%015.12f", -lat);
 10.1155 +  } else {
 10.1156 +    /* treat positive latitudes (including +0) as north */
 10.1157 +    snprintf(buf, PGL_NUMBUFLEN, "N%015.12f", lat);
 10.1158 +  }
 10.1159 +}
 10.1160 +
 10.1161 +/* convert longitude floating point number (in degrees) to string */
 10.1162 +static void pgl_print_lon(char *buf, double lon) {
 10.1163 +  if (signbit(lon)) {
 10.1164 +    /* treat negative longitudes (including -0) as west */
 10.1165 +    snprintf(buf, PGL_NUMBUFLEN, "W%016.12f", -lon);
 10.1166 +  } else {
 10.1167 +    /* treat positive longitudes (including +0) as east */
 10.1168 +    snprintf(buf, PGL_NUMBUFLEN, "E%016.12f", lon);
 10.1169 +  }
 10.1170 +}
 10.1171 +
 10.1172 +/* bit masks used as return value of pgl_scan() function */
 10.1173 +#define PGL_SCAN_NONE 0      /* no value has been parsed */
 10.1174 +#define PGL_SCAN_LAT (1<<0)  /* latitude has been parsed */
 10.1175 +#define PGL_SCAN_LON (1<<1)  /* longitude has been parsed */
 10.1176 +#define PGL_SCAN_LATLON (PGL_SCAN_LAT | PGL_SCAN_LON)  /* bitwise OR of both */
 10.1177 +
 10.1178 +/* parse a coordinate (can be latitude or longitude) */
 10.1179 +static int pgl_scan(char **str, double *lat, double *lon) {
 10.1180 +  double val;
 10.1181 +  int len;
 10.1182 +  if (
 10.1183 +    sscanf(*str, " N %lf %n", &val, &len) ||
 10.1184 +    sscanf(*str, " n %lf %n", &val, &len)
 10.1185 +  ) {
 10.1186 +    *str += len; *lat = val; return PGL_SCAN_LAT;
 10.1187 +  }
 10.1188 +  if (
 10.1189 +    sscanf(*str, " S %lf %n", &val, &len) ||
 10.1190 +    sscanf(*str, " s %lf %n", &val, &len)
 10.1191 +  ) {
 10.1192 +    *str += len; *lat = -val; return PGL_SCAN_LAT;
 10.1193 +  }
 10.1194 +  if (
 10.1195 +    sscanf(*str, " E %lf %n", &val, &len) ||
 10.1196 +    sscanf(*str, " e %lf %n", &val, &len)
 10.1197 +  ) {
 10.1198 +    *str += len; *lon = val; return PGL_SCAN_LON;
 10.1199 +  }
 10.1200 +  if (
 10.1201 +    sscanf(*str, " W %lf %n", &val, &len) ||
 10.1202 +    sscanf(*str, " w %lf %n", &val, &len)
 10.1203 +  ) {
 10.1204 +    *str += len; *lon = -val; return PGL_SCAN_LON;
 10.1205 +  }
 10.1206 +  return PGL_SCAN_NONE;
 10.1207 +}
 10.1208 +
 10.1209 +
 10.1210 +/*-----------------*
 10.1211 + *  SQL functions  *
 10.1212 + *-----------------*/
 10.1213 +
 10.1214 +/* Note: These function names use "epoint", "ebox", etc. notation here instead
 10.1215 +   of "point", "box", etc. in order to distinguish them from any previously
 10.1216 +   defined functions. */
 10.1217 +
 10.1218 +/* function needed for dummy types and/or not implemented features */
 10.1219 +PG_FUNCTION_INFO_V1(pgl_notimpl);
 10.1220 +Datum pgl_notimpl(PG_FUNCTION_ARGS) {
 10.1221 +  ereport(ERROR, (errmsg("not implemented by pgLatLon")));
 10.1222 +}
 10.1223 +
 10.1224 +/* set point to latitude and longitude (including checks) */
 10.1225 +static void pgl_epoint_set_latlon(pgl_point *point, double lat, double lon) {
 10.1226 +  /* reject infinite or NaN values */
 10.1227 +  if (!isfinite(lat) || !isfinite(lon)) {
 10.1228 +    ereport(ERROR, (
 10.1229 +      errcode(ERRCODE_DATA_EXCEPTION),
 10.1230 +      errmsg("epoint requires finite coordinates")
 10.1231 +    ));
 10.1232 +  }
 10.1233 +  /* check latitude bounds */
 10.1234 +  if (lat < -90) {
 10.1235 +    ereport(WARNING, (errmsg("latitude exceeds south pole")));
 10.1236 +    lat = -90;
 10.1237 +  } else if (lat > 90) {
 10.1238 +    ereport(WARNING, (errmsg("latitude exceeds north pole")));
 10.1239 +    lat = 90;
 10.1240 +  }
 10.1241 +  /* check longitude bounds */
 10.1242 +  if (lon < -180) {
 10.1243 +    ereport(NOTICE, (errmsg("longitude west of 180th meridian normalized")));
 10.1244 +    lon += 360 - trunc(lon / 360) * 360;
 10.1245 +  } else if (lon > 180) {
 10.1246 +    ereport(NOTICE, (errmsg("longitude east of 180th meridian normalized")));
 10.1247 +    lon -= 360 + trunc(lon / 360) * 360;
 10.1248 +  }
 10.1249 +  /* store rounded latitude/longitude values for round-trip safety */
 10.1250 +  point->lat = pgl_round(lat);
 10.1251 +  point->lon = pgl_round(lon);
 10.1252 +}
 10.1253 +
 10.1254 +/* create point ("epoint" in SQL) from latitude and longitude */
 10.1255 +PG_FUNCTION_INFO_V1(pgl_create_epoint);
 10.1256 +Datum pgl_create_epoint(PG_FUNCTION_ARGS) {
 10.1257 +  pgl_point *point = (pgl_point *)palloc(sizeof(pgl_point));
 10.1258 +  pgl_epoint_set_latlon(point, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1));
 10.1259 +  PG_RETURN_POINTER(point);
 10.1260 +}
 10.1261 +
 10.1262 +/* parse point ("epoint" in SQL) */
 10.1263 +/* format: '[NS]<float> [EW]<float>' */
 10.1264 +PG_FUNCTION_INFO_V1(pgl_epoint_in);
 10.1265 +Datum pgl_epoint_in(PG_FUNCTION_ARGS) {
 10.1266 +  char *str = PG_GETARG_CSTRING(0);  /* input string */
 10.1267 +  char *strptr = str;  /* current position within string */
 10.1268 +  int done = 0;        /* bit mask storing if latitude or longitude was read */
 10.1269 +  double lat, lon;     /* parsed values as double precision floats */
 10.1270 +  pgl_point *point;    /* return value (to be palloc'ed) */
 10.1271 +  /* parse two floats (each latitude or longitude) separated by white-space */
 10.1272 +  done |= pgl_scan(&strptr, &lat, &lon);
 10.1273 +  if (strptr != str && isspace(strptr[-1])) {
 10.1274 +    done |= pgl_scan(&strptr, &lat, &lon);
 10.1275 +  }
 10.1276 +  /* require end of string, and latitude and longitude parsed successfully */
 10.1277 +  if (strptr[0] || done != PGL_SCAN_LATLON) {
 10.1278 +    ereport(ERROR, (
 10.1279 +      errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1280 +      errmsg("invalid input syntax for type epoint: \"%s\"", str)
 10.1281 +    ));
 10.1282 +  }
 10.1283 +  /* allocate memory for result */
 10.1284 +  point = (pgl_point *)palloc(sizeof(pgl_point));
 10.1285 +  /* set latitude and longitude (and perform checks) */
 10.1286 +  pgl_epoint_set_latlon(point, lat, lon);
 10.1287 +  /* return result */
 10.1288 +  PG_RETURN_POINTER(point);
 10.1289 +}
 10.1290 +
 10.1291 +/* create box ("ebox" in SQL) that is empty */
 10.1292 +PG_FUNCTION_INFO_V1(pgl_create_empty_ebox);
 10.1293 +Datum pgl_create_empty_ebox(PG_FUNCTION_ARGS) {
 10.1294 +  pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1295 +  pgl_box_set_empty(box);
 10.1296 +  PG_RETURN_POINTER(box);
 10.1297 +}
 10.1298 +
 10.1299 +/* set box to given boundaries (including checks) */
 10.1300 +static void pgl_ebox_set_boundaries(
 10.1301 +  pgl_box *box,
 10.1302 +  double lat_min, double lat_max, double lon_min, double lon_max
 10.1303 +) {
 10.1304 +  /* if minimum latitude is greater than maximum latitude, return empty box */
 10.1305 +  if (lat_min > lat_max) {
 10.1306 +    pgl_box_set_empty(box);
 10.1307 +    return;
 10.1308 +  }
 10.1309 +  /* otherwise reject infinite or NaN values */
 10.1310 +  if (
 10.1311 +    !isfinite(lat_min) || !isfinite(lat_max) ||
 10.1312 +    !isfinite(lon_min) || !isfinite(lon_max)
 10.1313 +  ) {
 10.1314 +    ereport(ERROR, (
 10.1315 +      errcode(ERRCODE_DATA_EXCEPTION),
 10.1316 +      errmsg("ebox requires finite coordinates")
 10.1317 +    ));
 10.1318 +  }
 10.1319 +  /* check latitude bounds */
 10.1320 +  if (lat_max < -90) {
 10.1321 +    ereport(WARNING, (errmsg("northern latitude exceeds south pole")));
 10.1322 +    lat_max = -90;
 10.1323 +  } else if (lat_max > 90) {
 10.1324 +    ereport(WARNING, (errmsg("northern latitude exceeds north pole")));
 10.1325 +    lat_max = 90;
 10.1326 +  }
 10.1327 +  if (lat_min < -90) {
 10.1328 +    ereport(WARNING, (errmsg("southern latitude exceeds south pole")));
 10.1329 +    lat_min = -90;
 10.1330 +  } else if (lat_min > 90) {
 10.1331 +    ereport(WARNING, (errmsg("southern latitude exceeds north pole")));
 10.1332 +    lat_min = 90;
 10.1333 +  }
 10.1334 +  /* check if all longitudes are included */
 10.1335 +  if (lon_max - lon_min >= 360) {
 10.1336 +    if (lon_max - lon_min > 360) ereport(WARNING, (
 10.1337 +      errmsg("longitude coverage greater than 360 degrees")
 10.1338 +    ));
 10.1339 +    lon_min = -180;
 10.1340 +    lon_max = 180;
 10.1341 +  } else {
 10.1342 +    /* normalize longitude bounds */
 10.1343 +    if      (lon_min < -180) lon_min += 360 - trunc(lon_min / 360) * 360;
 10.1344 +    else if (lon_min >  180) lon_min -= 360 + trunc(lon_min / 360) * 360;
 10.1345 +    if      (lon_max < -180) lon_max += 360 - trunc(lon_max / 360) * 360;
 10.1346 +    else if (lon_max >  180) lon_max -= 360 + trunc(lon_max / 360) * 360;
 10.1347 +  }
 10.1348 +  /* store rounded latitude/longitude values for round-trip safety */
 10.1349 +  box->lat_min = pgl_round(lat_min);
 10.1350 +  box->lat_max = pgl_round(lat_max);
 10.1351 +  box->lon_min = pgl_round(lon_min);
 10.1352 +  box->lon_max = pgl_round(lon_max);
 10.1353 +  /* ensure that rounding does not change orientation */
 10.1354 +  if (lon_min > lon_max && box->lon_min == box->lon_max) {
 10.1355 +    box->lon_min = -180;
 10.1356 +    box->lon_max = 180;
 10.1357 +  }
 10.1358 +}
 10.1359 +
 10.1360 +/* create box ("ebox" in SQL) from min/max latitude and min/max longitude */
 10.1361 +PG_FUNCTION_INFO_V1(pgl_create_ebox);
 10.1362 +Datum pgl_create_ebox(PG_FUNCTION_ARGS) {
 10.1363 +  pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1364 +  pgl_ebox_set_boundaries(
 10.1365 +    box,
 10.1366 +    PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1),
 10.1367 +    PG_GETARG_FLOAT8(2), PG_GETARG_FLOAT8(3)
 10.1368 +  );
 10.1369 +  PG_RETURN_POINTER(box);
 10.1370 +}
 10.1371 +
 10.1372 +/* create box ("ebox" in SQL) from two points ("epoint"s) */
 10.1373 +/* (can not be used to cover a longitude range of more than 120 degrees) */
 10.1374 +PG_FUNCTION_INFO_V1(pgl_create_ebox_from_epoints);
 10.1375 +Datum pgl_create_ebox_from_epoints(PG_FUNCTION_ARGS) {
 10.1376 +  pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0);
 10.1377 +  pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1);
 10.1378 +  pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1379 +  double lat_min, lat_max, lon_min, lon_max;
 10.1380 +  double dlon;  /* longitude range (delta longitude) */
 10.1381 +  /* order latitude and longitude boundaries */
 10.1382 +  if (point2->lat < point1->lat) {
 10.1383 +    lat_min = point2->lat;
 10.1384 +    lat_max = point1->lat;
 10.1385 +  } else {
 10.1386 +    lat_min = point1->lat;
 10.1387 +    lat_max = point2->lat;
 10.1388 +  }
 10.1389 +  if (point2->lon < point1->lon) {
 10.1390 +    lon_min = point2->lon;
 10.1391 +    lon_max = point1->lon;
 10.1392 +  } else {
 10.1393 +    lon_min = point1->lon;
 10.1394 +    lon_max = point2->lon;
 10.1395 +  }
 10.1396 +  /* calculate longitude range (round to avoid floating point errors) */
 10.1397 +  dlon = pgl_round(lon_max - lon_min);
 10.1398 +  /* determine east-west direction */
 10.1399 +  if (dlon >= 240) {
 10.1400 +    /* assume that 180th meridian is crossed and swap min/max longitude */
 10.1401 +    double swap = lon_min; lon_min = lon_max; lon_max = swap;
 10.1402 +  } else if (dlon > 120) {
 10.1403 +    /* unclear orientation since delta longitude > 120 */
 10.1404 +    ereport(ERROR, (
 10.1405 +      errcode(ERRCODE_DATA_EXCEPTION),
 10.1406 +      errmsg("can not determine east/west orientation for ebox")
 10.1407 +    ));
 10.1408 +  }
 10.1409 +  /* use boundaries to setup box (and perform checks) */
 10.1410 +  pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max);
 10.1411 +  /* return result */
 10.1412 +  PG_RETURN_POINTER(box);
 10.1413 +}
 10.1414 +
 10.1415 +/* parse box ("ebox" in SQL) */
 10.1416 +/* format: '[NS]<float> [EW]<float> [NS]<float> [EW]<float>'
 10.1417 +       or: '[NS]<float> [NS]<float> [EW]<float> [EW]<float>' */
 10.1418 +PG_FUNCTION_INFO_V1(pgl_ebox_in);
 10.1419 +Datum pgl_ebox_in(PG_FUNCTION_ARGS) {
 10.1420 +  char *str = PG_GETARG_CSTRING(0);  /* input string */
 10.1421 +  char *str_lower;     /* lower case version of input string */
 10.1422 +  char *strptr;        /* current position within string */
 10.1423 +  int valid;           /* number of valid chars */
 10.1424 +  int done;            /* specifies if latitude or longitude was read */
 10.1425 +  double val;          /* temporary variable */
 10.1426 +  int lat_count = 0;   /* count of latitude values parsed */
 10.1427 +  int lon_count = 0;   /* count of longitufde values parsed */
 10.1428 +  double lat_min, lat_max, lon_min, lon_max;  /* see pgl_box struct */
 10.1429 +  pgl_box *box;        /* return value (to be palloc'ed) */
 10.1430 +  /* lowercase input */
 10.1431 +  str_lower = psprintf("%s", str);
 10.1432 +  for (strptr=str_lower; *strptr; strptr++) {
 10.1433 +    if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A';
 10.1434 +  }
 10.1435 +  /* reset reading position to start of (lowercase) string */
 10.1436 +  strptr = str_lower;
 10.1437 +  /* check if empty box */
 10.1438 +  valid = 0;
 10.1439 +  sscanf(strptr, " empty %n", &valid);
 10.1440 +  if (valid && strptr[valid] == 0) {
 10.1441 +    /* allocate and return empty box */
 10.1442 +    box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1443 +    pgl_box_set_empty(box);
 10.1444 +    PG_RETURN_POINTER(box);
 10.1445 +  }
 10.1446 +  /* demand four blocks separated by whitespace */
 10.1447 +  valid = 0;
 10.1448 +  sscanf(strptr, " %*s %*s %*s %*s %n", &valid);
 10.1449 +  /* if four blocks separated by whitespace exist, parse those blocks */
 10.1450 +  if (strptr[valid] == 0) while (strptr[0]) {
 10.1451 +    /* parse either latitude or longitude (whichever found in input string) */
 10.1452 +    done = pgl_scan(&strptr, &val, &val);
 10.1453 +    /* store latitude or longitude in lat_min, lat_max, lon_min, or lon_max */
 10.1454 +    if (done == PGL_SCAN_LAT) {
 10.1455 +      if (!lat_count) lat_min = val; else lat_max = val;
 10.1456 +      lat_count++;
 10.1457 +    } else if (done == PGL_SCAN_LON) {
 10.1458 +      if (!lon_count) lon_min = val; else lon_max = val;
 10.1459 +      lon_count++;
 10.1460 +    } else {
 10.1461 +      break;
 10.1462 +    }
 10.1463 +  }
 10.1464 +  /* require end of string, and two latitude and two longitude values */
 10.1465 +  if (strptr[0] || lat_count != 2 || lon_count != 2) {
 10.1466 +    ereport(ERROR, (
 10.1467 +      errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1468 +      errmsg("invalid input syntax for type ebox: \"%s\"", str)
 10.1469 +    ));
 10.1470 +  }
 10.1471 +  /* free lower case string */
 10.1472 +  pfree(str_lower);
 10.1473 +  /* order boundaries (maximum greater than minimum) */
 10.1474 +  if (lat_min > lat_max) { val = lat_min; lat_min = lat_max; lat_max = val; }
 10.1475 +  if (lon_min > lon_max) { val = lon_min; lon_min = lon_max; lon_max = val; }
 10.1476 +  /* allocate memory for result */
 10.1477 +  box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1478 +  /* set boundaries (and perform checks) */
 10.1479 +  pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max);
 10.1480 +  /* return result */
 10.1481 +  PG_RETURN_POINTER(box);
 10.1482 +}
 10.1483 +
 10.1484 +/* set circle to given latitude, longitude, and radius (including checks) */
 10.1485 +static void pgl_ecircle_set_latlon_radius(
 10.1486 +  pgl_circle *circle, double lat, double lon, double radius
 10.1487 +) {
 10.1488 +  /* set center point (including checks) */
 10.1489 +  pgl_epoint_set_latlon(&(circle->center), lat, lon);
 10.1490 +  /* handle non-positive radius */
 10.1491 +  if (isnan(radius)) {
 10.1492 +    ereport(ERROR, (
 10.1493 +      errcode(ERRCODE_DATA_EXCEPTION),
 10.1494 +      errmsg("invalid radius for ecircle")
 10.1495 +    ));
 10.1496 +  }
 10.1497 +  if (radius == 0) radius = 0;  /* avoids -0 */
 10.1498 +  else if (radius < 0) {
 10.1499 +    if (isfinite(radius)) {
 10.1500 +      ereport(NOTICE, (errmsg("negative radius converted to minus infinity")));
 10.1501 +    }
 10.1502 +    radius = -INFINITY;
 10.1503 +  }
 10.1504 +  /* store radius (round-trip safety is ensured by pgl_print_float) */
 10.1505 +  circle->radius = radius;
 10.1506 +}
 10.1507 +
 10.1508 +/* create circle ("ecircle" in SQL) from latitude, longitude, and radius */
 10.1509 +PG_FUNCTION_INFO_V1(pgl_create_ecircle);
 10.1510 +Datum pgl_create_ecircle(PG_FUNCTION_ARGS) {
 10.1511 +  pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle));
 10.1512 +  pgl_ecircle_set_latlon_radius(
 10.1513 +    circle, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1), PG_GETARG_FLOAT8(2)
 10.1514 +  );
 10.1515 +  PG_RETURN_POINTER(circle);
 10.1516 +}
 10.1517 +
 10.1518 +/* create circle ("ecircle" in SQL) from point ("epoint"), and radius */
 10.1519 +PG_FUNCTION_INFO_V1(pgl_create_ecircle_from_epoint);
 10.1520 +Datum pgl_create_ecircle_from_epoint(PG_FUNCTION_ARGS) {
 10.1521 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1522 +  double radius = PG_GETARG_FLOAT8(1);
 10.1523 +  pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle));
 10.1524 +  /* set latitude, longitude, radius (and perform checks) */
 10.1525 +  pgl_ecircle_set_latlon_radius(circle, point->lat, point->lon, radius);
 10.1526 +  /* return result */
 10.1527 +  PG_RETURN_POINTER(circle);
 10.1528 +}
 10.1529 +
 10.1530 +/* parse circle ("ecircle" in SQL) */
 10.1531 +/* format: '[NS]<float> [EW]<float> <float>' */
 10.1532 +PG_FUNCTION_INFO_V1(pgl_ecircle_in);
 10.1533 +Datum pgl_ecircle_in(PG_FUNCTION_ARGS) {
 10.1534 +  char *str = PG_GETARG_CSTRING(0);  /* input string */
 10.1535 +  char *strptr = str;       /* current position within string */
 10.1536 +  double lat, lon, radius;  /* parsed values as double precision flaots */
 10.1537 +  int valid = 0;            /* number of valid chars */
 10.1538 +  int done = 0;             /* stores if latitude and/or longitude was read */
 10.1539 +  pgl_circle *circle;       /* return value (to be palloc'ed) */
 10.1540 +  /* demand three blocks separated by whitespace */
 10.1541 +  sscanf(strptr, " %*s %*s %*s %n", &valid);
 10.1542 +  /* if three blocks separated by whitespace exist, parse those blocks */
 10.1543 +  if (strptr[valid] == 0) {
 10.1544 +    /* parse latitude and longitude */
 10.1545 +    done |= pgl_scan(&strptr, &lat, &lon);
 10.1546 +    done |= pgl_scan(&strptr, &lat, &lon);
 10.1547 +    /* parse radius (while incrementing strptr by number of bytes parsed) */
 10.1548 +    valid = 0;
 10.1549 +    if (sscanf(strptr, " %lf %n", &radius, &valid) == 1) strptr += valid;
 10.1550 +  }
 10.1551 +  /* require end of string and both latitude and longitude being parsed */
 10.1552 +  if (strptr[0] || done != PGL_SCAN_LATLON) {
 10.1553 +    ereport(ERROR, (
 10.1554 +      errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1555 +      errmsg("invalid input syntax for type ecircle: \"%s\"", str)
 10.1556 +    ));
 10.1557 +  }
 10.1558 +  /* allocate memory for result */
 10.1559 +  circle = (pgl_circle *)palloc(sizeof(pgl_circle));
 10.1560 +  /* set latitude, longitude, radius (and perform checks) */
 10.1561 +  pgl_ecircle_set_latlon_radius(circle, lat, lon, radius);
 10.1562 +  /* return result */
 10.1563 +  PG_RETURN_POINTER(circle);
 10.1564 +}
 10.1565 +
 10.1566 +/* parse cluster ("ecluster" in SQL) */
 10.1567 +PG_FUNCTION_INFO_V1(pgl_ecluster_in);
 10.1568 +Datum pgl_ecluster_in(PG_FUNCTION_ARGS) {
 10.1569 +  int i;
 10.1570 +  char *str = PG_GETARG_CSTRING(0);  /* input string */
 10.1571 +  char *str_lower;         /* lower case version of input string */
 10.1572 +  char *strptr;            /* pointer to current reading position of input */
 10.1573 +  int npoints_total = 0;   /* total number of points in cluster */
 10.1574 +  int nentries = 0;        /* total number of entries */
 10.1575 +  pgl_newentry *entries;   /* array of pgl_newentry to create pgl_cluster */
 10.1576 +  int entries_buflen = 4;  /* maximum number of elements in entries array */
 10.1577 +  int valid;               /* number of valid chars processed */
 10.1578 +  double lat, lon;         /* latitude and longitude of parsed point */
 10.1579 +  int entrytype;           /* current entry type */
 10.1580 +  int npoints;             /* number of points in current entry */
 10.1581 +  pgl_point *points;       /* array of pgl_point for pgl_newentry */
 10.1582 +  int points_buflen;       /* maximum number of elements in points array */
 10.1583 +  int done;                /* return value of pgl_scan function */
 10.1584 +  pgl_cluster *cluster;    /* created cluster */
 10.1585 +  /* lowercase input */
 10.1586 +  str_lower = psprintf("%s", str);
 10.1587 +  for (strptr=str_lower; *strptr; strptr++) {
 10.1588 +    if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A';
 10.1589 +  }
 10.1590 +  /* reset reading position to start of (lowercase) string */
 10.1591 +  strptr = str_lower;
 10.1592 +  /* allocate initial buffer for entries */
 10.1593 +  entries = palloc(entries_buflen * sizeof(pgl_newentry));
 10.1594 +  /* parse until end of string */
 10.1595 +  while (strptr[0]) {
 10.1596 +    /* require previous white-space or closing parenthesis before next token */
 10.1597 +    if (strptr != str_lower && !isspace(strptr[-1]) && strptr[-1] != ')') {
 10.1598 +      goto pgl_ecluster_in_error;
 10.1599 +    }
 10.1600 +    /* ignore token "empty" */
 10.1601 +    valid = 0; sscanf(strptr, " empty %n", &valid);
 10.1602 +    if (valid) { strptr += valid; continue; }
 10.1603 +    /* test for "point" token */
 10.1604 +    valid = 0; sscanf(strptr, " point ( %n", &valid);
 10.1605 +    if (valid) {
 10.1606 +      strptr += valid;
 10.1607 +      entrytype = PGL_ENTRY_POINT;
 10.1608 +      goto pgl_ecluster_in_type_ok;
 10.1609 +    }
 10.1610 +    /* test for "path" token */
 10.1611 +    valid = 0; sscanf(strptr, " path ( %n", &valid);
 10.1612 +    if (valid) {
 10.1613 +      strptr += valid;
 10.1614 +      entrytype = PGL_ENTRY_PATH;
 10.1615 +      goto pgl_ecluster_in_type_ok;
 10.1616 +    }
 10.1617 +    /* test for "outline" token */
 10.1618 +    valid = 0; sscanf(strptr, " outline ( %n", &valid);
 10.1619 +    if (valid) {
 10.1620 +      strptr += valid;
 10.1621 +      entrytype = PGL_ENTRY_OUTLINE;
 10.1622 +      goto pgl_ecluster_in_type_ok;
 10.1623 +    }
 10.1624 +    /* test for "polygon" token */
 10.1625 +    valid = 0; sscanf(strptr, " polygon ( %n", &valid);
 10.1626 +    if (valid) {
 10.1627 +      strptr += valid;
 10.1628 +      entrytype = PGL_ENTRY_POLYGON;
 10.1629 +      goto pgl_ecluster_in_type_ok;
 10.1630 +    }
 10.1631 +    /* error if no valid token found */
 10.1632 +    goto pgl_ecluster_in_error;
 10.1633 +    pgl_ecluster_in_type_ok:
 10.1634 +    /* check if pgl_newentry array needs to grow */
 10.1635 +    if (nentries == entries_buflen) {
 10.1636 +      pgl_newentry *newbuf;
 10.1637 +      entries_buflen *= 2;
 10.1638 +      newbuf = palloc(entries_buflen * sizeof(pgl_newentry));
 10.1639 +      memcpy(newbuf, entries, nentries * sizeof(pgl_newentry));
 10.1640 +      pfree(entries);
 10.1641 +      entries = newbuf;
 10.1642 +    }
 10.1643 +    /* reset number of points for current entry */
 10.1644 +    npoints = 0;
 10.1645 +    /* allocate array for points */
 10.1646 +    points_buflen = 4;
 10.1647 +    points = palloc(points_buflen * sizeof(pgl_point));
 10.1648 +    /* parse until closing parenthesis */
 10.1649 +    while (strptr[0] != ')') {
 10.1650 +      /* error on unexpected end of string */
 10.1651 +      if (strptr[0] == 0) goto pgl_ecluster_in_error;
 10.1652 +      /* mark neither latitude nor longitude as read */
 10.1653 +      done = PGL_SCAN_NONE;
 10.1654 +      /* require white-space before second, third, etc. point */
 10.1655 +      if (npoints != 0 && !isspace(strptr[-1])) goto pgl_ecluster_in_error;
 10.1656 +      /* scan latitude (or longitude) */
 10.1657 +      done |= pgl_scan(&strptr, &lat, &lon);
 10.1658 +      /* require white-space before second coordinate */
 10.1659 +      if (strptr != str && !isspace(strptr[-1])) goto pgl_ecluster_in_error;
 10.1660 +      /* scan longitude (or latitude) */
 10.1661 +      done |= pgl_scan(&strptr, &lat, &lon);
 10.1662 +      /* error unless both latitude and longitude were parsed */
 10.1663 +      if (done != PGL_SCAN_LATLON) goto pgl_ecluster_in_error;
 10.1664 +      /* throw error if number of points is too high */
 10.1665 +      if (npoints_total == PGL_CLUSTER_MAXPOINTS) {
 10.1666 +        ereport(ERROR, (
 10.1667 +          errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1668 +          errmsg(
 10.1669 +            "too many points for ecluster entry (maximum %i)",
 10.1670 +            PGL_CLUSTER_MAXPOINTS
 10.1671 +          )
 10.1672 +        ));
 10.1673 +      }
 10.1674 +      /* check if pgl_point array needs to grow */
 10.1675 +      if (npoints == points_buflen) {
 10.1676 +        pgl_point *newbuf;
 10.1677 +        points_buflen *= 2;
 10.1678 +        newbuf = palloc(points_buflen * sizeof(pgl_point));
 10.1679 +        memcpy(newbuf, points, npoints * sizeof(pgl_point));
 10.1680 +        pfree(points);
 10.1681 +        points = newbuf;
 10.1682 +      }
 10.1683 +      /* append point to pgl_point array (includes checks) */
 10.1684 +      pgl_epoint_set_latlon(&(points[npoints++]), lat, lon);
 10.1685 +      /* increase total number of points */
 10.1686 +      npoints_total++;
 10.1687 +    }
 10.1688 +    /* error if entry has no points */
 10.1689 +    if (!npoints) goto pgl_ecluster_in_error;
 10.1690 +    /* entries with one point are automatically of type "point" */
 10.1691 +    if (npoints == 1) entrytype = PGL_ENTRY_POINT;
 10.1692 +    /* if entries have more than one point */
 10.1693 +    else {
 10.1694 +      /* throw error if entry type is "point" */
 10.1695 +      if (entrytype == PGL_ENTRY_POINT) {
 10.1696 +        ereport(ERROR, (
 10.1697 +          errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1698 +          errmsg("invalid input syntax for type ecluster (point entry with more than one point)")
 10.1699 +        ));
 10.1700 +      }
 10.1701 +      /* coerce outlines and polygons with more than 2 points to be a path */
 10.1702 +      if (npoints == 2) entrytype = PGL_ENTRY_PATH;
 10.1703 +    }
 10.1704 +    /* append entry to pgl_newentry array */
 10.1705 +    entries[nentries].entrytype = entrytype;
 10.1706 +    entries[nentries].npoints = npoints;
 10.1707 +    entries[nentries].points = points;
 10.1708 +    nentries++;
 10.1709 +    /* consume closing parenthesis */
 10.1710 +    strptr++;
 10.1711 +    /* consume white-space */
 10.1712 +    while (isspace(strptr[0])) strptr++;
 10.1713 +  }
 10.1714 +  /* free lower case string */
 10.1715 +  pfree(str_lower);
 10.1716 +  /* create cluster from pgl_newentry array */
 10.1717 +  cluster = pgl_new_cluster(nentries, entries);
 10.1718 +  /* free pgl_newentry array */
 10.1719 +  for (i=0; i<nentries; i++) pfree(entries[i].points);
 10.1720 +  pfree(entries);
 10.1721 +  /* set bounding circle of cluster and check east/west orientation */
 10.1722 +  if (!pgl_finalize_cluster(cluster)) {
 10.1723 +    ereport(ERROR, (
 10.1724 +      errcode(ERRCODE_DATA_EXCEPTION),
 10.1725 +      errmsg("can not determine east/west orientation for ecluster"),
 10.1726 +      errhint("Ensure that each entry has a longitude span of less than 180 degrees.")
 10.1727 +    ));
 10.1728 +  }
 10.1729 +  /* return cluster */
 10.1730 +  PG_RETURN_POINTER(cluster);
 10.1731 +  /* code to throw error */
 10.1732 +  pgl_ecluster_in_error:
 10.1733 +  ereport(ERROR, (
 10.1734 +    errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
 10.1735 +    errmsg("invalid input syntax for type ecluster: \"%s\"", str)
 10.1736 +  ));
 10.1737 +}
 10.1738 +
 10.1739 +/* convert point ("epoint") to string representation */
 10.1740 +PG_FUNCTION_INFO_V1(pgl_epoint_out);
 10.1741 +Datum pgl_epoint_out(PG_FUNCTION_ARGS) {
 10.1742 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1743 +  char latstr[PGL_NUMBUFLEN];
 10.1744 +  char lonstr[PGL_NUMBUFLEN];
 10.1745 +  pgl_print_lat(latstr, point->lat);
 10.1746 +  pgl_print_lon(lonstr, point->lon);
 10.1747 +  PG_RETURN_CSTRING(psprintf("%s %s", latstr, lonstr));
 10.1748 +}
 10.1749 +
 10.1750 +/* convert box ("ebox") to string representation */
 10.1751 +PG_FUNCTION_INFO_V1(pgl_ebox_out);
 10.1752 +Datum pgl_ebox_out(PG_FUNCTION_ARGS) {
 10.1753 +  pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0);
 10.1754 +  double lon_min = box->lon_min;
 10.1755 +  double lon_max = box->lon_max;
 10.1756 +  char lat_min_str[PGL_NUMBUFLEN];
 10.1757 +  char lat_max_str[PGL_NUMBUFLEN];
 10.1758 +  char lon_min_str[PGL_NUMBUFLEN];
 10.1759 +  char lon_max_str[PGL_NUMBUFLEN];
 10.1760 +  /* return string "empty" if box is set to be empty */
 10.1761 +  if (box->lat_min > box->lat_max) PG_RETURN_CSTRING("empty");
 10.1762 +  /* use boundaries exceeding W180 or E180 if 180th meridian is enclosed */
 10.1763 +  /* (required since pgl_box_in orders the longitude boundaries) */
 10.1764 +  if (lon_min > lon_max) {
 10.1765 +    if (lon_min + lon_max >= 0) lon_min -= 360;
 10.1766 +    else lon_max += 360;
 10.1767 +  }
 10.1768 +  /* format and return result */
 10.1769 +  pgl_print_lat(lat_min_str, box->lat_min);
 10.1770 +  pgl_print_lat(lat_max_str, box->lat_max);
 10.1771 +  pgl_print_lon(lon_min_str, lon_min);
 10.1772 +  pgl_print_lon(lon_max_str, lon_max);
 10.1773 +  PG_RETURN_CSTRING(psprintf(
 10.1774 +    "%s %s %s %s",
 10.1775 +    lat_min_str, lon_min_str, lat_max_str, lon_max_str
 10.1776 +  ));
 10.1777 +}
 10.1778 +
 10.1779 +/* convert circle ("ecircle") to string representation */
 10.1780 +PG_FUNCTION_INFO_V1(pgl_ecircle_out);
 10.1781 +Datum pgl_ecircle_out(PG_FUNCTION_ARGS) {
 10.1782 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0);
 10.1783 +  char latstr[PGL_NUMBUFLEN];
 10.1784 +  char lonstr[PGL_NUMBUFLEN];
 10.1785 +  char radstr[PGL_NUMBUFLEN];
 10.1786 +  pgl_print_lat(latstr, circle->center.lat);
 10.1787 +  pgl_print_lon(lonstr, circle->center.lon);
 10.1788 +  pgl_print_float(radstr, circle->radius);
 10.1789 +  PG_RETURN_CSTRING(psprintf("%s %s %s", latstr, lonstr, radstr));
 10.1790 +}
 10.1791 +
 10.1792 +/* convert cluster ("ecluster") to string representation */
 10.1793 +PG_FUNCTION_INFO_V1(pgl_ecluster_out);
 10.1794 +Datum pgl_ecluster_out(PG_FUNCTION_ARGS) {
 10.1795 +  pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
 10.1796 +  char latstr[PGL_NUMBUFLEN];  /* string buffer for latitude */
 10.1797 +  char lonstr[PGL_NUMBUFLEN];  /* string buffer for longitude */
 10.1798 +  char ***strings;     /* array of array of strings */
 10.1799 +  char *string;        /* string of current token */
 10.1800 +  char *res, *resptr;  /* result and pointer to current write position */
 10.1801 +  size_t reslen = 1;   /* length of result (init with 1 for terminator) */
 10.1802 +  int npoints;         /* number of points of current entry */
 10.1803 +  int i, j;            /* i: entry, j: point in entry */
 10.1804 +  /* handle empty clusters */
 10.1805 +  if (cluster->nentries == 0) {
 10.1806 +    /* free detoasted cluster (if copy) */
 10.1807 +    PG_FREE_IF_COPY(cluster, 0);
 10.1808 +    /* return static result */
 10.1809 +    PG_RETURN_CSTRING("empty");
 10.1810 +  }
 10.1811 +  /* allocate array of array of strings */
 10.1812 +  strings = palloc(cluster->nentries * sizeof(char **));
 10.1813 +  /* iterate over all entries in cluster */
 10.1814 +  for (i=0; i<cluster->nentries; i++) {
 10.1815 +    /* get number of points in entry */
 10.1816 +    npoints = cluster->entries[i].npoints;
 10.1817 +    /* allocate array of strings (one string for each point plus two extra) */
 10.1818 +    strings[i] = palloc((2 + npoints) * sizeof(char *));
 10.1819 +    /* determine opening string */
 10.1820 +    switch (cluster->entries[i].entrytype) {
 10.1821 +      case PGL_ENTRY_POINT:   string = (i==0)?"point ("  :" point (";   break;
 10.1822 +      case PGL_ENTRY_PATH:    string = (i==0)?"path ("   :" path (";    break;
 10.1823 +      case PGL_ENTRY_OUTLINE: string = (i==0)?"outline (":" outline ("; break;
 10.1824 +      case PGL_ENTRY_POLYGON: string = (i==0)?"polygon (":" polygon ("; break;
 10.1825 +      default:                string = (i==0)?"unknown"  :" unknown";
 10.1826 +    }
 10.1827 +    /* use opening string as first string in array */
 10.1828 +    strings[i][0] = string;
 10.1829 +    /* update result length (for allocating result string later) */
 10.1830 +    reslen += strlen(string);
 10.1831 +    /* iterate over all points */
 10.1832 +    for (j=0; j<npoints; j++) {
 10.1833 +      /* create string representation of point */
 10.1834 +      pgl_print_lat(latstr, PGL_ENTRY_POINTS(cluster, i)[j].lat);
 10.1835 +      pgl_print_lon(lonstr, PGL_ENTRY_POINTS(cluster, i)[j].lon);
 10.1836 +      string = psprintf((j == 0) ? "%s %s" : " %s %s", latstr, lonstr);
 10.1837 +      /* copy string pointer to string array */
 10.1838 +      strings[i][j+1] = string;
 10.1839 +      /* update result length (for allocating result string later) */
 10.1840 +      reslen += strlen(string);
 10.1841 +    }
 10.1842 +    /* use closing parenthesis as last string in array */
 10.1843 +    strings[i][npoints+1] = ")";
 10.1844 +    /* update result length (for allocating result string later) */
 10.1845 +    reslen++;
 10.1846 +  }
 10.1847 +  /* allocate result string */
 10.1848 +  res = palloc(reslen);
 10.1849 +  /* set write pointer to begin of result string */
 10.1850 +  resptr = res;
 10.1851 +  /* copy strings into result string */
 10.1852 +  for (i=0; i<cluster->nentries; i++) {
 10.1853 +    npoints = cluster->entries[i].npoints;
 10.1854 +    for (j=0; j<npoints+2; j++) {
 10.1855 +      string = strings[i][j];
 10.1856 +      strcpy(resptr, string);
 10.1857 +      resptr += strlen(string);
 10.1858 +      /* free strings allocated by psprintf */
 10.1859 +      if (j != 0 && j != npoints+1) pfree(string);
 10.1860 +    }
 10.1861 +    /* free array of strings */
 10.1862 +    pfree(strings[i]);
 10.1863 +  }
 10.1864 +  /* free array of array of strings */
 10.1865 +  pfree(strings);
 10.1866 +  /* free detoasted cluster (if copy) */
 10.1867 +  PG_FREE_IF_COPY(cluster, 0);
 10.1868 +  /* return result */
 10.1869 +  PG_RETURN_CSTRING(res);
 10.1870 +}
 10.1871 +
 10.1872 +/* binary input function for point ("epoint") */
 10.1873 +PG_FUNCTION_INFO_V1(pgl_epoint_recv);
 10.1874 +Datum pgl_epoint_recv(PG_FUNCTION_ARGS) {
 10.1875 +  StringInfo buf = (StringInfo)PG_GETARG_POINTER(0);
 10.1876 +  pgl_point *point = (pgl_point *)palloc(sizeof(pgl_point));
 10.1877 +  point->lat = pq_getmsgfloat8(buf);
 10.1878 +  point->lon = pq_getmsgfloat8(buf);
 10.1879 +  PG_RETURN_POINTER(point);
 10.1880 +}
 10.1881 +
 10.1882 +/* binary input function for box ("ebox") */
 10.1883 +PG_FUNCTION_INFO_V1(pgl_ebox_recv);
 10.1884 +Datum pgl_ebox_recv(PG_FUNCTION_ARGS) {
 10.1885 +  StringInfo buf = (StringInfo)PG_GETARG_POINTER(0);
 10.1886 +  pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box));
 10.1887 +  box->lat_min = pq_getmsgfloat8(buf);
 10.1888 +  box->lat_max = pq_getmsgfloat8(buf);
 10.1889 +  box->lon_min = pq_getmsgfloat8(buf);
 10.1890 +  box->lon_max = pq_getmsgfloat8(buf);
 10.1891 +  PG_RETURN_POINTER(box);
 10.1892 +}
 10.1893 +
 10.1894 +/* binary input function for circle ("ecircle") */
 10.1895 +PG_FUNCTION_INFO_V1(pgl_ecircle_recv);
 10.1896 +Datum pgl_ecircle_recv(PG_FUNCTION_ARGS) {
 10.1897 +  StringInfo buf = (StringInfo)PG_GETARG_POINTER(0);
 10.1898 +  pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle));
 10.1899 +  circle->center.lat = pq_getmsgfloat8(buf);
 10.1900 +  circle->center.lon = pq_getmsgfloat8(buf);
 10.1901 +  circle->radius = pq_getmsgfloat8(buf);
 10.1902 +  PG_RETURN_POINTER(circle);
 10.1903 +}
 10.1904 +
 10.1905 +/* TODO: binary receive function for cluster */
 10.1906 +
 10.1907 +/* binary output function for point ("epoint") */
 10.1908 +PG_FUNCTION_INFO_V1(pgl_epoint_send);
 10.1909 +Datum pgl_epoint_send(PG_FUNCTION_ARGS) {
 10.1910 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1911 +  StringInfoData buf;
 10.1912 +  pq_begintypsend(&buf);
 10.1913 +  pq_sendfloat8(&buf, point->lat);
 10.1914 +  pq_sendfloat8(&buf, point->lon);
 10.1915 +  PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 10.1916 +}
 10.1917 +
 10.1918 +/* binary output function for box ("ebox") */
 10.1919 +PG_FUNCTION_INFO_V1(pgl_ebox_send);
 10.1920 +Datum pgl_ebox_send(PG_FUNCTION_ARGS) {
 10.1921 +  pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0);
 10.1922 +  StringInfoData buf;
 10.1923 +  pq_begintypsend(&buf);
 10.1924 +  pq_sendfloat8(&buf, box->lat_min);
 10.1925 +  pq_sendfloat8(&buf, box->lat_max);
 10.1926 +  pq_sendfloat8(&buf, box->lon_min);
 10.1927 +  pq_sendfloat8(&buf, box->lon_max);
 10.1928 +  PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 10.1929 +}
 10.1930 +
 10.1931 +/* binary output function for circle ("ecircle") */
 10.1932 +PG_FUNCTION_INFO_V1(pgl_ecircle_send);
 10.1933 +Datum pgl_ecircle_send(PG_FUNCTION_ARGS) {
 10.1934 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0);
 10.1935 +  StringInfoData buf;
 10.1936 +  pq_begintypsend(&buf);
 10.1937 +  pq_sendfloat8(&buf, circle->center.lat);
 10.1938 +  pq_sendfloat8(&buf, circle->center.lon);
 10.1939 +  pq_sendfloat8(&buf, circle->radius);
 10.1940 +  PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
 10.1941 +}
 10.1942 +
 10.1943 +/* TODO: binary send functions for cluster */
 10.1944 +
 10.1945 +/* cast point ("epoint") to box ("ebox") */
 10.1946 +PG_FUNCTION_INFO_V1(pgl_epoint_to_ebox);
 10.1947 +Datum pgl_epoint_to_ebox(PG_FUNCTION_ARGS) {
 10.1948 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1949 +  pgl_box *box = palloc(sizeof(pgl_box));
 10.1950 +  box->lat_min = point->lat;
 10.1951 +  box->lat_max = point->lat;
 10.1952 +  box->lon_min = point->lon;
 10.1953 +  box->lon_max = point->lon;
 10.1954 +  PG_RETURN_POINTER(box);
 10.1955 +}
 10.1956 +
 10.1957 +/* cast point ("epoint") to circle ("ecircle") */
 10.1958 +PG_FUNCTION_INFO_V1(pgl_epoint_to_ecircle);
 10.1959 +Datum pgl_epoint_to_ecircle(PG_FUNCTION_ARGS) {
 10.1960 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1961 +  pgl_circle *circle = palloc(sizeof(pgl_box));
 10.1962 +  circle->center = *point;
 10.1963 +  circle->radius = 0;
 10.1964 +  PG_RETURN_POINTER(circle);
 10.1965 +}
 10.1966 +
 10.1967 +/* cast point ("epoint") to cluster ("ecluster") */
 10.1968 +PG_FUNCTION_INFO_V1(pgl_epoint_to_ecluster);
 10.1969 +Datum pgl_epoint_to_ecluster(PG_FUNCTION_ARGS) {
 10.1970 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.1971 +  pgl_newentry entry;
 10.1972 +  entry.entrytype = PGL_ENTRY_POINT;
 10.1973 +  entry.npoints = 1;
 10.1974 +  entry.points = point;
 10.1975 +  PG_RETURN_POINTER(pgl_new_cluster(1, &entry));
 10.1976 +}
 10.1977 +
 10.1978 +/* cast box ("ebox") to cluster ("ecluster") */
 10.1979 +#define pgl_ebox_to_ecluster_macro(i, a, b) \
 10.1980 +  entries[i].entrytype = PGL_ENTRY_POLYGON; \
 10.1981 +  entries[i].npoints = 4; \
 10.1982 +  entries[i].points = points[i]; \
 10.1983 +  points[i][0].lat = box->lat_min; \
 10.1984 +  points[i][0].lon = (a); \
 10.1985 +  points[i][1].lat = box->lat_min; \
 10.1986 +  points[i][1].lon = (b); \
 10.1987 +  points[i][2].lat = box->lat_max; \
 10.1988 +  points[i][2].lon = (b); \
 10.1989 +  points[i][3].lat = box->lat_max; \
 10.1990 +  points[i][3].lon = (a);
 10.1991 +PG_FUNCTION_INFO_V1(pgl_ebox_to_ecluster);
 10.1992 +Datum pgl_ebox_to_ecluster(PG_FUNCTION_ARGS) {
 10.1993 +  pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0);
 10.1994 +  double lon, dlon;
 10.1995 +  int nentries;
 10.1996 +  pgl_newentry entries[3];
 10.1997 +  pgl_point points[3][4];
 10.1998 +  if (box->lat_min > box->lat_max) {
 10.1999 +    nentries = 0;
 10.2000 +  } else if (box->lon_min > box->lon_max) {
 10.2001 +    if (box->lon_min < 0) {
 10.2002 +      lon = pgl_round((box->lon_min + 180) / 2.0);
 10.2003 +      nentries = 3;
 10.2004 +      pgl_ebox_to_ecluster_macro(0, box->lon_min, lon);
 10.2005 +      pgl_ebox_to_ecluster_macro(1, lon, 180);
 10.2006 +      pgl_ebox_to_ecluster_macro(2, -180, box->lon_max);
 10.2007 +    } else if (box->lon_max > 0) {
 10.2008 +      lon = pgl_round((box->lon_max - 180) / 2.0);
 10.2009 +      nentries = 3;
 10.2010 +      pgl_ebox_to_ecluster_macro(0, box->lon_min, 180);
 10.2011 +      pgl_ebox_to_ecluster_macro(1, -180, lon);
 10.2012 +      pgl_ebox_to_ecluster_macro(2, lon, box->lon_max);
 10.2013 +    } else {
 10.2014 +      nentries = 2;
 10.2015 +      pgl_ebox_to_ecluster_macro(0, box->lon_min, 180);
 10.2016 +      pgl_ebox_to_ecluster_macro(1, -180, box->lon_max);
 10.2017 +    }
 10.2018 +  } else {
 10.2019 +    dlon = pgl_round(box->lon_max - box->lon_min);
 10.2020 +    if (dlon < 180) {
 10.2021 +      nentries = 1;
 10.2022 +      pgl_ebox_to_ecluster_macro(0, box->lon_min, box->lon_max);
 10.2023 +    } else {
 10.2024 +      lon = pgl_round((box->lon_min + box->lon_max) / 2.0);
 10.2025 +      if (
 10.2026 +        pgl_round(lon - box->lon_min) < 180 &&
 10.2027 +        pgl_round(box->lon_max - lon) < 180
 10.2028 +      ) {
 10.2029 +        nentries = 2;
 10.2030 +        pgl_ebox_to_ecluster_macro(0, box->lon_min, lon);
 10.2031 +        pgl_ebox_to_ecluster_macro(1, lon, box->lon_max);
 10.2032 +      } else {
 10.2033 +        nentries = 3;
 10.2034 +        pgl_ebox_to_ecluster_macro(0, box->lon_min, -60);
 10.2035 +        pgl_ebox_to_ecluster_macro(1, -60, 60);
 10.2036 +        pgl_ebox_to_ecluster_macro(2, 60, box->lon_max);
 10.2037 +      }
 10.2038 +    }
 10.2039 +  }
 10.2040 +  PG_RETURN_POINTER(pgl_new_cluster(nentries, entries));
 10.2041 +}
 10.2042 +
 10.2043 +/* extract latitude from point ("epoint") */
 10.2044 +PG_FUNCTION_INFO_V1(pgl_epoint_lat);
 10.2045 +Datum pgl_epoint_lat(PG_FUNCTION_ARGS) {
 10.2046 +  PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lat);
 10.2047 +}
 10.2048 +
 10.2049 +/* extract longitude from point ("epoint") */
 10.2050 +PG_FUNCTION_INFO_V1(pgl_epoint_lon);
 10.2051 +Datum pgl_epoint_lon(PG_FUNCTION_ARGS) {
 10.2052 +  PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lon);
 10.2053 +}
 10.2054 +
 10.2055 +/* extract minimum latitude from box ("ebox") */
 10.2056 +PG_FUNCTION_INFO_V1(pgl_ebox_lat_min);
 10.2057 +Datum pgl_ebox_lat_min(PG_FUNCTION_ARGS) {
 10.2058 +  PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_min);
 10.2059 +}
 10.2060 +
 10.2061 +/* extract maximum latitude from box ("ebox") */
 10.2062 +PG_FUNCTION_INFO_V1(pgl_ebox_lat_max);
 10.2063 +Datum pgl_ebox_lat_max(PG_FUNCTION_ARGS) {
 10.2064 +  PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_max);
 10.2065 +}
 10.2066 +
 10.2067 +/* extract minimum longitude from box ("ebox") */
 10.2068 +PG_FUNCTION_INFO_V1(pgl_ebox_lon_min);
 10.2069 +Datum pgl_ebox_lon_min(PG_FUNCTION_ARGS) {
 10.2070 +  PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_min);
 10.2071 +}
 10.2072 +
 10.2073 +/* extract maximum longitude from box ("ebox") */
 10.2074 +PG_FUNCTION_INFO_V1(pgl_ebox_lon_max);
 10.2075 +Datum pgl_ebox_lon_max(PG_FUNCTION_ARGS) {
 10.2076 +  PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_max);
 10.2077 +}
 10.2078 +
 10.2079 +/* extract center point from circle ("ecircle") */
 10.2080 +PG_FUNCTION_INFO_V1(pgl_ecircle_center);
 10.2081 +Datum pgl_ecircle_center(PG_FUNCTION_ARGS) {
 10.2082 +  PG_RETURN_POINTER(&(((pgl_circle *)PG_GETARG_POINTER(0))->center));
 10.2083 +}
 10.2084 +
 10.2085 +/* extract radius from circle ("ecircle") */
 10.2086 +PG_FUNCTION_INFO_V1(pgl_ecircle_radius);
 10.2087 +Datum pgl_ecircle_radius(PG_FUNCTION_ARGS) {
 10.2088 +  PG_RETURN_FLOAT8(((pgl_circle *)PG_GETARG_POINTER(0))->radius);
 10.2089 +}
 10.2090 +
 10.2091 +/* check if point is inside box (overlap operator "&&") in SQL */
 10.2092 +PG_FUNCTION_INFO_V1(pgl_epoint_ebox_overlap);
 10.2093 +Datum pgl_epoint_ebox_overlap(PG_FUNCTION_ARGS) {
 10.2094 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.2095 +  pgl_box *box = (pgl_box *)PG_GETARG_POINTER(1);
 10.2096 +  PG_RETURN_BOOL(pgl_point_in_box(point, box));
 10.2097 +}
 10.2098 +
 10.2099 +/* check if point is inside circle (overlap operator "&&") in SQL */
 10.2100 +PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_overlap);
 10.2101 +Datum pgl_epoint_ecircle_overlap(PG_FUNCTION_ARGS) {
 10.2102 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.2103 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2104 +  PG_RETURN_BOOL(
 10.2105 +    pgl_distance(
 10.2106 +      point->lat, point->lon,
 10.2107 +      circle->center.lat, circle->center.lon
 10.2108 +    ) <= circle->radius
 10.2109 +  );
 10.2110 +}
 10.2111 +
 10.2112 +/* check if point is inside cluster (overlap operator "&&") in SQL */
 10.2113 +PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_overlap);
 10.2114 +Datum pgl_epoint_ecluster_overlap(PG_FUNCTION_ARGS) {
 10.2115 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.2116 +  pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2117 +  bool retval = pgl_point_in_cluster(point, cluster);
 10.2118 +  PG_FREE_IF_COPY(cluster, 1);
 10.2119 +  PG_RETURN_BOOL(retval);
 10.2120 +}
 10.2121 +
 10.2122 +/* check if two boxes overlap (overlap operator "&&") in SQL */
 10.2123 +PG_FUNCTION_INFO_V1(pgl_ebox_overlap);
 10.2124 +Datum pgl_ebox_overlap(PG_FUNCTION_ARGS) {
 10.2125 +  pgl_box *box1 = (pgl_box *)PG_GETARG_POINTER(0);
 10.2126 +  pgl_box *box2 = (pgl_box *)PG_GETARG_POINTER(1);
 10.2127 +  PG_RETURN_BOOL(pgl_boxes_overlap(box1, box2));
 10.2128 +}
 10.2129 +
 10.2130 +/* check if two circles overlap (overlap operator "&&") in SQL */
 10.2131 +PG_FUNCTION_INFO_V1(pgl_ecircle_overlap);
 10.2132 +Datum pgl_ecircle_overlap(PG_FUNCTION_ARGS) {
 10.2133 +  pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0);
 10.2134 +  pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2135 +  PG_RETURN_BOOL(
 10.2136 +    pgl_distance(
 10.2137 +      circle1->center.lat, circle1->center.lon,
 10.2138 +      circle2->center.lat, circle2->center.lon
 10.2139 +    ) <= circle1->radius + circle2->radius
 10.2140 +  );
 10.2141 +}
 10.2142 +
 10.2143 +/* check if circle and cluster overlap (overlap operator "&&") in SQL */
 10.2144 +PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_overlap);
 10.2145 +Datum pgl_ecircle_ecluster_overlap(PG_FUNCTION_ARGS) {
 10.2146 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0);
 10.2147 +  pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2148 +  bool retval = (
 10.2149 +    pgl_point_cluster_distance(&(circle->center), cluster) <= circle->radius
 10.2150 +  );
 10.2151 +  PG_FREE_IF_COPY(cluster, 1);
 10.2152 +  PG_RETURN_BOOL(retval);
 10.2153 +}
 10.2154 +
 10.2155 +/* calculate distance between two points ("<->" operator) in SQL */
 10.2156 +PG_FUNCTION_INFO_V1(pgl_epoint_distance);
 10.2157 +Datum pgl_epoint_distance(PG_FUNCTION_ARGS) {
 10.2158 +  pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0);
 10.2159 +  pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1);
 10.2160 +  PG_RETURN_FLOAT8(pgl_distance(
 10.2161 +    point1->lat, point1->lon, point2->lat, point2->lon
 10.2162 +  ));
 10.2163 +}
 10.2164 +
 10.2165 +/* calculate point to circle distance ("<->" operator) in SQL */
 10.2166 +PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_distance);
 10.2167 +Datum pgl_epoint_ecircle_distance(PG_FUNCTION_ARGS) {
 10.2168 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.2169 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2170 +  double distance = pgl_distance(
 10.2171 +    point->lat, point->lon, circle->center.lat, circle->center.lon
 10.2172 +  ) - circle->radius;
 10.2173 +  PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance);
 10.2174 +}
 10.2175 +
 10.2176 +/* calculate point to cluster distance ("<->" operator) in SQL */
 10.2177 +PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_distance);
 10.2178 +Datum pgl_epoint_ecluster_distance(PG_FUNCTION_ARGS) {
 10.2179 +  pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0);
 10.2180 +  pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2181 +  double distance = pgl_point_cluster_distance(point, cluster);
 10.2182 +  PG_FREE_IF_COPY(cluster, 1);
 10.2183 +  PG_RETURN_FLOAT8(distance);
 10.2184 +}
 10.2185 +
 10.2186 +/* calculate distance between two circles ("<->" operator) in SQL */
 10.2187 +PG_FUNCTION_INFO_V1(pgl_ecircle_distance);
 10.2188 +Datum pgl_ecircle_distance(PG_FUNCTION_ARGS) {
 10.2189 +  pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0);
 10.2190 +  pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2191 +  double distance = pgl_distance(
 10.2192 +    circle1->center.lat, circle1->center.lon,
 10.2193 +    circle2->center.lat, circle2->center.lon
 10.2194 +  ) - (circle1->radius + circle2->radius);
 10.2195 +  PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance);
 10.2196 +}
 10.2197 +
 10.2198 +/* calculate circle to cluster distance ("<->" operator) in SQL */
 10.2199 +PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_distance);
 10.2200 +Datum pgl_ecircle_ecluster_distance(PG_FUNCTION_ARGS) {
 10.2201 +  pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0);
 10.2202 +  pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2203 +  double distance = (
 10.2204 +    pgl_point_cluster_distance(&(circle->center), cluster) - circle->radius
 10.2205 +  );
 10.2206 +  PG_FREE_IF_COPY(cluster, 1);
 10.2207 +  PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance);
 10.2208 +}
 10.2209 +
 10.2210 +
 10.2211 +/*-----------------------------------------------------------*
 10.2212 + *  B-tree comparison operators and index support functions  *
 10.2213 + *-----------------------------------------------------------*/
 10.2214 +
 10.2215 +/* macro for a B-tree operator (without detoasting) */
 10.2216 +#define PGL_BTREE_OPER(func, type, cmpfunc, oper) \
 10.2217 +  PG_FUNCTION_INFO_V1(func); \
 10.2218 +  Datum func(PG_FUNCTION_ARGS) { \
 10.2219 +    type *a = (type *)PG_GETARG_POINTER(0); \
 10.2220 +    type *b = (type *)PG_GETARG_POINTER(1); \
 10.2221 +    PG_RETURN_BOOL(cmpfunc(a, b) oper 0); \
 10.2222 +  }
 10.2223 +
 10.2224 +/* macro for a B-tree comparison function (without detoasting) */
 10.2225 +#define PGL_BTREE_CMP(func, type, cmpfunc) \
 10.2226 +  PG_FUNCTION_INFO_V1(func); \
 10.2227 +  Datum func(PG_FUNCTION_ARGS) { \
 10.2228 +    type *a = (type *)PG_GETARG_POINTER(0); \
 10.2229 +    type *b = (type *)PG_GETARG_POINTER(1); \
 10.2230 +    PG_RETURN_INT32(cmpfunc(a, b)); \
 10.2231 +  }
 10.2232 +
 10.2233 +/* macro for a B-tree operator (with detoasting) */
 10.2234 +#define PGL_BTREE_OPER_DETOAST(func, type, cmpfunc, oper) \
 10.2235 +  PG_FUNCTION_INFO_V1(func); \
 10.2236 +  Datum func(PG_FUNCTION_ARGS) { \
 10.2237 +    bool res; \
 10.2238 +    type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \
 10.2239 +    type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \
 10.2240 +    res = cmpfunc(a, b) oper 0; \
 10.2241 +    PG_FREE_IF_COPY(a, 0); \
 10.2242 +    PG_FREE_IF_COPY(b, 1); \
 10.2243 +    PG_RETURN_BOOL(res); \
 10.2244 +  }
 10.2245 +
 10.2246 +/* macro for a B-tree comparison function (with detoasting) */
 10.2247 +#define PGL_BTREE_CMP_DETOAST(func, type, cmpfunc) \
 10.2248 +  PG_FUNCTION_INFO_V1(func); \
 10.2249 +  Datum func(PG_FUNCTION_ARGS) { \
 10.2250 +    int32_t res; \
 10.2251 +    type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \
 10.2252 +    type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \
 10.2253 +    res = cmpfunc(a, b); \
 10.2254 +    PG_FREE_IF_COPY(a, 0); \
 10.2255 +    PG_FREE_IF_COPY(b, 1); \
 10.2256 +    PG_RETURN_INT32(res); \
 10.2257 +  }
 10.2258 +
 10.2259 +/* B-tree operators and comparison function for point */
 10.2260 +PGL_BTREE_OPER(pgl_btree_epoint_lt, pgl_point, pgl_point_cmp, <)
 10.2261 +PGL_BTREE_OPER(pgl_btree_epoint_le, pgl_point, pgl_point_cmp, <=)
 10.2262 +PGL_BTREE_OPER(pgl_btree_epoint_eq, pgl_point, pgl_point_cmp, ==)
 10.2263 +PGL_BTREE_OPER(pgl_btree_epoint_ne, pgl_point, pgl_point_cmp, !=)
 10.2264 +PGL_BTREE_OPER(pgl_btree_epoint_ge, pgl_point, pgl_point_cmp, >=)
 10.2265 +PGL_BTREE_OPER(pgl_btree_epoint_gt, pgl_point, pgl_point_cmp, >)
 10.2266 +PGL_BTREE_CMP(pgl_btree_epoint_cmp, pgl_point, pgl_point_cmp)
 10.2267 +
 10.2268 +/* B-tree operators and comparison function for box */
 10.2269 +PGL_BTREE_OPER(pgl_btree_ebox_lt, pgl_box, pgl_box_cmp, <)
 10.2270 +PGL_BTREE_OPER(pgl_btree_ebox_le, pgl_box, pgl_box_cmp, <=)
 10.2271 +PGL_BTREE_OPER(pgl_btree_ebox_eq, pgl_box, pgl_box_cmp, ==)
 10.2272 +PGL_BTREE_OPER(pgl_btree_ebox_ne, pgl_box, pgl_box_cmp, !=)
 10.2273 +PGL_BTREE_OPER(pgl_btree_ebox_ge, pgl_box, pgl_box_cmp, >=)
 10.2274 +PGL_BTREE_OPER(pgl_btree_ebox_gt, pgl_box, pgl_box_cmp, >)
 10.2275 +PGL_BTREE_CMP(pgl_btree_ebox_cmp, pgl_box, pgl_box_cmp)
 10.2276 +
 10.2277 +/* B-tree operators and comparison function for circle */
 10.2278 +PGL_BTREE_OPER(pgl_btree_ecircle_lt, pgl_circle, pgl_circle_cmp, <)
 10.2279 +PGL_BTREE_OPER(pgl_btree_ecircle_le, pgl_circle, pgl_circle_cmp, <=)
 10.2280 +PGL_BTREE_OPER(pgl_btree_ecircle_eq, pgl_circle, pgl_circle_cmp, ==)
 10.2281 +PGL_BTREE_OPER(pgl_btree_ecircle_ne, pgl_circle, pgl_circle_cmp, !=)
 10.2282 +PGL_BTREE_OPER(pgl_btree_ecircle_ge, pgl_circle, pgl_circle_cmp, >=)
 10.2283 +PGL_BTREE_OPER(pgl_btree_ecircle_gt, pgl_circle, pgl_circle_cmp, >)
 10.2284 +PGL_BTREE_CMP(pgl_btree_ecircle_cmp, pgl_circle, pgl_circle_cmp)
 10.2285 +
 10.2286 +
 10.2287 +/*--------------------------------*
 10.2288 + *  GiST index support functions  *
 10.2289 + *--------------------------------*/
 10.2290 +
 10.2291 +/* GiST "consistent" support function */
 10.2292 +PG_FUNCTION_INFO_V1(pgl_gist_consistent);
 10.2293 +Datum pgl_gist_consistent(PG_FUNCTION_ARGS) {
 10.2294 +  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 10.2295 +  pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key);
 10.2296 +  StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2);
 10.2297 +  bool *recheck = (bool *)PG_GETARG_POINTER(4);
 10.2298 +  /* demand recheck because index and query methods are lossy */
 10.2299 +  *recheck = true;
 10.2300 +  /* strategy number 11: equality of two points */
 10.2301 +  if (strategy == 11) {
 10.2302 +    /* query datum is another point */
 10.2303 +    pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1);
 10.2304 +    /* convert other point to key */
 10.2305 +    pgl_pointkey querykey;
 10.2306 +    pgl_point_to_key(query, querykey);
 10.2307 +    /* return true if both keys overlap */
 10.2308 +    PG_RETURN_BOOL(pgl_keys_overlap(key, querykey));
 10.2309 +  }
 10.2310 +  /* strategy number 13: equality of two circles */
 10.2311 +  if (strategy == 13) {
 10.2312 +    /* query datum is another circle */
 10.2313 +    pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2314 +    /* convert other circle to key */
 10.2315 +    pgl_areakey querykey;
 10.2316 +    pgl_circle_to_key(query, querykey);
 10.2317 +    /* return true if both keys overlap */
 10.2318 +    PG_RETURN_BOOL(pgl_keys_overlap(key, querykey));
 10.2319 +  }
 10.2320 +  /* for all remaining strategies, keys on empty objects produce no match */
 10.2321 +  /* (check necessary because query radius may be infinite) */
 10.2322 +  if (PGL_KEY_IS_EMPTY(key)) PG_RETURN_BOOL(false);
 10.2323 +  /* strategy number 21: overlapping with point */
 10.2324 +  if (strategy == 21) {
 10.2325 +    /* query datum is a point */
 10.2326 +    pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1);
 10.2327 +    /* return true if estimated distance (allowed to be smaller than real
 10.2328 +       distance) between index key and point is zero */
 10.2329 +    PG_RETURN_BOOL(pgl_estimate_key_distance(key, query) == 0);
 10.2330 +  }
 10.2331 +  /* strategy number 22: (point) overlapping with box */
 10.2332 +  if (strategy == 22) {
 10.2333 +    /* query datum is a box */
 10.2334 +    pgl_box *query = (pgl_box *)PG_GETARG_POINTER(1);
 10.2335 +    /* determine bounding box of indexed key */
 10.2336 +    pgl_box keybox;
 10.2337 +    pgl_key_to_box(key, &keybox);
 10.2338 +    /* return true if query box overlaps with bounding box of indexed key */
 10.2339 +    PG_RETURN_BOOL(pgl_boxes_overlap(query, &keybox));
 10.2340 +  }
 10.2341 +  /* strategy number 23: overlapping with circle */
 10.2342 +  if (strategy == 23) {
 10.2343 +    /* query datum is a circle */
 10.2344 +    pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2345 +    /* return true if estimated distance (allowed to be smaller than real
 10.2346 +       distance) between index key and circle center is smaller than radius */
 10.2347 +    PG_RETURN_BOOL(
 10.2348 +      pgl_estimate_key_distance(key, &(query->center)) <= query->radius
 10.2349 +    );
 10.2350 +  }
 10.2351 +  /* strategy number 24: overlapping with cluster */
 10.2352 +  if (strategy == 24) {
 10.2353 +    bool retval;  /* return value */
 10.2354 +    /* query datum is a cluster */
 10.2355 +    pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2356 +    /* return true if estimated distance (allowed to be smaller than real
 10.2357 +       distance) between index key and circle center is smaller than radius */
 10.2358 +    retval = (
 10.2359 +      pgl_estimate_key_distance(key, &(query->bounding.center)) <=
 10.2360 +      query->bounding.radius
 10.2361 +    );
 10.2362 +    PG_FREE_IF_COPY(query, 1);  /* free detoasted cluster (if copy) */
 10.2363 +    PG_RETURN_BOOL(retval);
 10.2364 +  }
 10.2365 +  /* throw error for any unknown strategy number */
 10.2366 +  elog(ERROR, "unrecognized strategy number: %d", strategy);
 10.2367 +}
 10.2368 +
 10.2369 +/* GiST "union" support function */
 10.2370 +PG_FUNCTION_INFO_V1(pgl_gist_union);
 10.2371 +Datum pgl_gist_union(PG_FUNCTION_ARGS) {
 10.2372 +  GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0);
 10.2373 +  pgl_keyptr out;  /* return value (to be palloc'ed) */
 10.2374 +  int i;
 10.2375 +  /* determine key size */
 10.2376 +  size_t keysize = PGL_KEY_IS_AREAKEY(
 10.2377 +    (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key)
 10.2378 +  ) ? sizeof (pgl_areakey) : sizeof(pgl_pointkey);
 10.2379 +  /* begin with first key as result */
 10.2380 +  out = palloc(keysize);
 10.2381 +  memcpy(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key), keysize);
 10.2382 +  /* unite current result with second, third, etc. key */
 10.2383 +  for (i=1; i<entryvec->n; i++) {
 10.2384 +    pgl_unite_keys(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key));
 10.2385 +  }
 10.2386 +  /* return result */
 10.2387 +  PG_RETURN_POINTER(out);
 10.2388 +}
 10.2389 +
 10.2390 +/* GiST "compress" support function for indicis on points */
 10.2391 +PG_FUNCTION_INFO_V1(pgl_gist_compress_epoint);
 10.2392 +Datum pgl_gist_compress_epoint(PG_FUNCTION_ARGS) {
 10.2393 +  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 10.2394 +  GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */
 10.2395 +  /* only transform new leaves */
 10.2396 +  if (entry->leafkey) {
 10.2397 +    /* get point to be transformed */
 10.2398 +    pgl_point *point = (pgl_point *)DatumGetPointer(entry->key);
 10.2399 +    /* allocate memory for key */
 10.2400 +    pgl_keyptr key = palloc(sizeof(pgl_pointkey));
 10.2401 +    /* transform point to key */
 10.2402 +    pgl_point_to_key(point, key);
 10.2403 +    /* create new GISTENTRY structure as return value */
 10.2404 +    retval = palloc(sizeof(GISTENTRY));
 10.2405 +    gistentryinit(
 10.2406 +      *retval, PointerGetDatum(key),
 10.2407 +      entry->rel, entry->page, entry->offset, FALSE
 10.2408 +    );
 10.2409 +  } else {
 10.2410 +    /* inner nodes have already been transformed */
 10.2411 +    retval = entry;
 10.2412 +  }
 10.2413 +  /* return pointer to old or new GISTENTRY structure */
 10.2414 +  PG_RETURN_POINTER(retval);
 10.2415 +}
 10.2416 +
 10.2417 +/* GiST "compress" support function for indicis on circles */
 10.2418 +PG_FUNCTION_INFO_V1(pgl_gist_compress_ecircle);
 10.2419 +Datum pgl_gist_compress_ecircle(PG_FUNCTION_ARGS) {
 10.2420 +  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 10.2421 +  GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */
 10.2422 +  /* only transform new leaves */
 10.2423 +  if (entry->leafkey) {
 10.2424 +    /* get circle to be transformed */
 10.2425 +    pgl_circle *circle = (pgl_circle *)DatumGetPointer(entry->key);
 10.2426 +    /* allocate memory for key */
 10.2427 +    pgl_keyptr key = palloc(sizeof(pgl_areakey));
 10.2428 +    /* transform circle to key */
 10.2429 +    pgl_circle_to_key(circle, key);
 10.2430 +    /* create new GISTENTRY structure as return value */
 10.2431 +    retval = palloc(sizeof(GISTENTRY));
 10.2432 +    gistentryinit(
 10.2433 +      *retval, PointerGetDatum(key),
 10.2434 +      entry->rel, entry->page, entry->offset, FALSE
 10.2435 +    );
 10.2436 +  } else {
 10.2437 +    /* inner nodes have already been transformed */
 10.2438 +    retval = entry;
 10.2439 +  }
 10.2440 +  /* return pointer to old or new GISTENTRY structure */
 10.2441 +  PG_RETURN_POINTER(retval);
 10.2442 +}
 10.2443 +
 10.2444 +/* GiST "compress" support function for indices on clusters */
 10.2445 +PG_FUNCTION_INFO_V1(pgl_gist_compress_ecluster);
 10.2446 +Datum pgl_gist_compress_ecluster(PG_FUNCTION_ARGS) {
 10.2447 +  GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
 10.2448 +  GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */
 10.2449 +  /* only transform new leaves */
 10.2450 +  if (entry->leafkey) {
 10.2451 +    /* get cluster to be transformed (detoasting necessary!) */
 10.2452 +    pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(entry->key);
 10.2453 +    /* allocate memory for key */
 10.2454 +    pgl_keyptr key = palloc(sizeof(pgl_areakey));
 10.2455 +    /* transform cluster to key */
 10.2456 +    pgl_circle_to_key(&(cluster->bounding), key);
 10.2457 +    /* create new GISTENTRY structure as return value */
 10.2458 +    retval = palloc(sizeof(GISTENTRY));
 10.2459 +    gistentryinit(
 10.2460 +      *retval, PointerGetDatum(key),
 10.2461 +      entry->rel, entry->page, entry->offset, FALSE
 10.2462 +    );
 10.2463 +    /* free detoasted datum */
 10.2464 +    if ((void *)cluster != (void *)DatumGetPointer(entry->key)) pfree(cluster);
 10.2465 +  } else {
 10.2466 +    /* inner nodes have already been transformed */
 10.2467 +    retval = entry;
 10.2468 +  }
 10.2469 +  /* return pointer to old or new GISTENTRY structure */
 10.2470 +  PG_RETURN_POINTER(retval);
 10.2471 +}
 10.2472 +
 10.2473 +/* GiST "decompress" support function for indices */
 10.2474 +PG_FUNCTION_INFO_V1(pgl_gist_decompress);
 10.2475 +Datum pgl_gist_decompress(PG_FUNCTION_ARGS) {
 10.2476 +  /* return passed pointer without transformation */
 10.2477 +  PG_RETURN_POINTER(PG_GETARG_POINTER(0));
 10.2478 +}
 10.2479 +
 10.2480 +/* GiST "penalty" support function */
 10.2481 +PG_FUNCTION_INFO_V1(pgl_gist_penalty);
 10.2482 +Datum pgl_gist_penalty(PG_FUNCTION_ARGS) {
 10.2483 +  GISTENTRY *origentry = (GISTENTRY *)PG_GETARG_POINTER(0);
 10.2484 +  GISTENTRY *newentry = (GISTENTRY *)PG_GETARG_POINTER(1);
 10.2485 +  float *penalty = (float *)PG_GETARG_POINTER(2);
 10.2486 +  /* get original key and key to insert */
 10.2487 +  pgl_keyptr orig = (pgl_keyptr)DatumGetPointer(origentry->key);
 10.2488 +  pgl_keyptr new = (pgl_keyptr)DatumGetPointer(newentry->key);
 10.2489 +  /* copy original key */
 10.2490 +  union { pgl_pointkey pointkey; pgl_areakey areakey; } union_key;
 10.2491 +  if (PGL_KEY_IS_AREAKEY(orig)) {
 10.2492 +    memcpy(union_key.areakey, orig, sizeof(union_key.areakey));
 10.2493 +  } else {
 10.2494 +    memcpy(union_key.pointkey, orig, sizeof(union_key.pointkey));
 10.2495 +  }
 10.2496 +  /* calculate union of both keys */
 10.2497 +  pgl_unite_keys((pgl_keyptr)&union_key, new);
 10.2498 +  /* penalty equal to reduction of key length (logarithm of added area) */
 10.2499 +  /* (return value by setting referenced value and returning pointer) */
 10.2500 +  *penalty = (
 10.2501 +    PGL_KEY_NODEDEPTH(orig) - PGL_KEY_NODEDEPTH((pgl_keyptr)&union_key)
 10.2502 +  );
 10.2503 +  PG_RETURN_POINTER(penalty);
 10.2504 +}
 10.2505 +
 10.2506 +/* GiST "picksplit" support function */
 10.2507 +PG_FUNCTION_INFO_V1(pgl_gist_picksplit);
 10.2508 +Datum pgl_gist_picksplit(PG_FUNCTION_ARGS) {
 10.2509 +  GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0);
 10.2510 +  GIST_SPLITVEC *v = (GIST_SPLITVEC *)PG_GETARG_POINTER(1);
 10.2511 +  OffsetNumber i;  /* between FirstOffsetNumber and entryvec->n (inclusive) */
 10.2512 +  union {
 10.2513 +    pgl_pointkey pointkey;
 10.2514 +    pgl_areakey areakey;
 10.2515 +  } union_all;  /* union of all keys (to be calculated from scratch)
 10.2516 +                   (later cut in half) */
 10.2517 +  int is_areakey = PGL_KEY_IS_AREAKEY(
 10.2518 +    (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key)
 10.2519 +  );
 10.2520 +  int keysize = is_areakey ? sizeof(pgl_areakey) : sizeof(pgl_pointkey);
 10.2521 +  pgl_keyptr unionL = palloc(keysize);  /* union of keys that go left */
 10.2522 +  pgl_keyptr unionR = palloc(keysize);  /* union of keys that go right */
 10.2523 +  pgl_keyptr key;  /* current key to be processed */
 10.2524 +  /* allocate memory for array of left and right keys, set counts to zero */
 10.2525 +  v->spl_left = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber));
 10.2526 +  v->spl_nleft = 0;
 10.2527 +  v->spl_right = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber));
 10.2528 +  v->spl_nright = 0;
 10.2529 +  /* calculate union of all keys from scratch */
 10.2530 +  memcpy(
 10.2531 +    (pgl_keyptr)&union_all,
 10.2532 +    (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key),
 10.2533 +    keysize
 10.2534 +  );
 10.2535 +  for (i=FirstOffsetNumber+1; i<entryvec->n; i=OffsetNumberNext(i)) {
 10.2536 +    pgl_unite_keys(
 10.2537 +      (pgl_keyptr)&union_all,
 10.2538 +      (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key)
 10.2539 +    );
 10.2540 +  }
 10.2541 +  /* check if trivial split is necessary due to exhausted key length */
 10.2542 +  /* (Note: keys for empty objects must have node depth set to maximum) */
 10.2543 +  if (PGL_KEY_NODEDEPTH((pgl_keyptr)&union_all) == (
 10.2544 +    is_areakey ? PGL_AREAKEY_MAXDEPTH : PGL_POINTKEY_MAXDEPTH
 10.2545 +  )) {
 10.2546 +    /* half of all keys go left */
 10.2547 +    for (
 10.2548 +      i=FirstOffsetNumber;
 10.2549 +      i<FirstOffsetNumber+(entryvec->n - FirstOffsetNumber)/2;
 10.2550 +      i=OffsetNumberNext(i)
 10.2551 +    ) {
 10.2552 +      /* pointer to current key */
 10.2553 +      key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key);
 10.2554 +      /* update unionL */
 10.2555 +      /* check if key is first key that goes left */
 10.2556 +      if (!v->spl_nleft) {
 10.2557 +        /* first key that goes left is just copied to unionL */
 10.2558 +        memcpy(unionL, key, keysize);
 10.2559 +      } else {
 10.2560 +        /* unite current value and next key */
 10.2561 +        pgl_unite_keys(unionL, key);
 10.2562 +      }
 10.2563 +      /* append offset number to list of keys that go left */
 10.2564 +      v->spl_left[v->spl_nleft++] = i;
 10.2565 +    }
 10.2566 +    /* other half goes right */
 10.2567 +    for (
 10.2568 +      i=FirstOffsetNumber+(entryvec->n - FirstOffsetNumber)/2;
 10.2569 +      i<entryvec->n;
 10.2570 +      i=OffsetNumberNext(i)
 10.2571 +    ) {
 10.2572 +      /* pointer to current key */
 10.2573 +      key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key);
 10.2574 +      /* update unionR */
 10.2575 +      /* check if key is first key that goes right */
 10.2576 +      if (!v->spl_nright) {
 10.2577 +        /* first key that goes right is just copied to unionR */
 10.2578 +        memcpy(unionR, key, keysize);
 10.2579 +      } else {
 10.2580 +        /* unite current value and next key */
 10.2581 +        pgl_unite_keys(unionR, key);
 10.2582 +      }
 10.2583 +      /* append offset number to list of keys that go right */
 10.2584 +      v->spl_right[v->spl_nright++] = i;
 10.2585 +    }
 10.2586 +  }
 10.2587 +  /* otherwise, a non-trivial split is possible */
 10.2588 +  else {
 10.2589 +    /* cut covered area in half */
 10.2590 +    /* (union_all then refers to area of keys that go left) */
 10.2591 +    /* check if union of all keys covers empty and non-empty objects */
 10.2592 +    if (PGL_KEY_IS_UNIVERSAL((pgl_keyptr)&union_all)) {
 10.2593 +      /* if yes, split into empty and non-empty objects */
 10.2594 +      pgl_key_set_empty((pgl_keyptr)&union_all);
 10.2595 +    } else {
 10.2596 +      /* otherwise split by next bit */
 10.2597 +      ((pgl_keyptr)&union_all)[PGL_KEY_NODEDEPTH_OFFSET]++;
 10.2598 +      /* NOTE: type bit conserved */
 10.2599 +    }
 10.2600 +    /* determine for each key if it goes left or right */
 10.2601 +    for (i=FirstOffsetNumber; i<entryvec->n; i=OffsetNumberNext(i)) {
 10.2602 +      /* pointer to current key */
 10.2603 +      key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key);
 10.2604 +      /* keys within one half of the area go left */
 10.2605 +      if (pgl_keys_overlap((pgl_keyptr)&union_all, key)) {
 10.2606 +        /* update unionL */
 10.2607 +        /* check if key is first key that goes left */
 10.2608 +        if (!v->spl_nleft) {
 10.2609 +          /* first key that goes left is just copied to unionL */
 10.2610 +          memcpy(unionL, key, keysize);
 10.2611 +        } else {
 10.2612 +          /* unite current value of unionL and processed key */
 10.2613 +          pgl_unite_keys(unionL, key);
 10.2614 +        }
 10.2615 +        /* append offset number to list of keys that go left */
 10.2616 +        v->spl_left[v->spl_nleft++] = i;
 10.2617 +      }
 10.2618 +      /* the other keys go right */
 10.2619 +      else {
 10.2620 +        /* update unionR */
 10.2621 +        /* check if key is first key that goes right */
 10.2622 +        if (!v->spl_nright) {
 10.2623 +          /* first key that goes right is just copied to unionR */
 10.2624 +          memcpy(unionR, key, keysize);
 10.2625 +        } else {
 10.2626 +          /* unite current value of unionR and processed key */
 10.2627 +          pgl_unite_keys(unionR, key);
 10.2628 +        }
 10.2629 +        /* append offset number to list of keys that go right */
 10.2630 +        v->spl_right[v->spl_nright++] = i;
 10.2631 +      }
 10.2632 +    }
 10.2633 +  }
 10.2634 +  /* store unions in return value */
 10.2635 +  v->spl_ldatum = PointerGetDatum(unionL);
 10.2636 +  v->spl_rdatum = PointerGetDatum(unionR);
 10.2637 +  /* return all results */
 10.2638 +  PG_RETURN_POINTER(v);
 10.2639 +}
 10.2640 +
 10.2641 +/* GiST "same"/"equal" support function */
 10.2642 +PG_FUNCTION_INFO_V1(pgl_gist_same);
 10.2643 +Datum pgl_gist_same(PG_FUNCTION_ARGS) {
 10.2644 +  pgl_keyptr key1 = (pgl_keyptr)PG_GETARG_POINTER(0);
 10.2645 +  pgl_keyptr key2 = (pgl_keyptr)PG_GETARG_POINTER(1);
 10.2646 +  bool *boolptr = (bool *)PG_GETARG_POINTER(2);
 10.2647 +  /* two keys are equal if they are binary equal */
 10.2648 +  /* (return result by setting referenced boolean and returning pointer) */
 10.2649 +  *boolptr = !memcmp(
 10.2650 +    key1,
 10.2651 +    key2,
 10.2652 +    PGL_KEY_IS_AREAKEY(key1) ? sizeof(pgl_areakey) : sizeof(pgl_pointkey)
 10.2653 +  );
 10.2654 +  PG_RETURN_POINTER(boolptr);
 10.2655 +}
 10.2656 +
 10.2657 +/* GiST "distance" support function */
 10.2658 +PG_FUNCTION_INFO_V1(pgl_gist_distance);
 10.2659 +Datum pgl_gist_distance(PG_FUNCTION_ARGS) {
 10.2660 +  GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0);
 10.2661 +  pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key);
 10.2662 +  StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2);
 10.2663 +  bool *recheck = (bool *)PG_GETARG_POINTER(4);
 10.2664 +  double distance;  /* return value */
 10.2665 +  /* demand recheck because distance is just an estimation */
 10.2666 +  /* (real distance may be bigger) */
 10.2667 +  *recheck = true;
 10.2668 +  /* strategy number 31: distance to point */
 10.2669 +  if (strategy == 31) {
 10.2670 +    /* query datum is a point */
 10.2671 +    pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1);
 10.2672 +    /* use pgl_estimate_pointkey_distance() function to compute result */
 10.2673 +    distance = pgl_estimate_key_distance(key, query);
 10.2674 +    /* avoid infinity (reserved!) */
 10.2675 +    if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE;
 10.2676 +    /* return result */
 10.2677 +    PG_RETURN_FLOAT8(distance);
 10.2678 +  }
 10.2679 +  /* strategy number 33: distance to circle */
 10.2680 +  if (strategy == 33) {
 10.2681 +    /* query datum is a circle */
 10.2682 +    pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1);
 10.2683 +    /* estimate distance to circle center and substract circle radius */
 10.2684 +    distance = (
 10.2685 +      pgl_estimate_key_distance(key, &(query->center)) - query->radius
 10.2686 +    );
 10.2687 +    /* convert non-positive values to zero and avoid infinity (reserved!) */
 10.2688 +    if (distance <= 0) distance = 0;
 10.2689 +    else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE;
 10.2690 +    /* return result */
 10.2691 +    PG_RETURN_FLOAT8(distance);
 10.2692 +  }
 10.2693 +  /* strategy number 34: distance to cluster */
 10.2694 +  if (strategy == 34) {
 10.2695 +    /* query datum is a cluster */
 10.2696 +    pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1));
 10.2697 +    /* estimate distance to bounding center and substract bounding radius */
 10.2698 +    distance = (
 10.2699 +      pgl_estimate_key_distance(key, &(query->bounding.center)) -
 10.2700 +      query->bounding.radius
 10.2701 +    );
 10.2702 +    /* convert non-positive values to zero and avoid infinity (reserved!) */
 10.2703 +    if (distance <= 0) distance = 0;
 10.2704 +    else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE;
 10.2705 +    /* free detoasted cluster (if copy) */
 10.2706 +    PG_FREE_IF_COPY(query, 1);
 10.2707 +    /* return result */
 10.2708 +    PG_RETURN_FLOAT8(distance);
 10.2709 +  }
 10.2710 +  /* throw error for any unknown strategy number */
 10.2711 +  elog(ERROR, "unrecognized strategy number: %d", strategy);
 10.2712 +}
 10.2713 +
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/latlon.control	Sun Aug 21 17:43:48 2016 +0200
    11.3 @@ -0,0 +1,4 @@
    11.4 +# geographical extension
    11.5 +comment = 'geospatial support'
    11.6 +default_version = '0.1'
    11.7 +relocatable = true
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/make-doc.sh	Sun Aug 21 17:43:48 2016 +0200
    12.3 @@ -0,0 +1,8 @@
    12.4 +#!/bin/sh
    12.5 +#
    12.6 +# This command can be used to update the README.html file after changing the
    12.7 +# README.mkd file.
    12.8 +
    12.9 +echo "<html><head><title>"`grep '[^ \t\r\n][^ \t\r\n]*' README.mkd | head -n 1`"</title></head><body>" > README.html
   12.10 +markdown2 README.mkd >> README.html
   12.11 +echo "</body></html>" >> README.html

Impressum / About Us