| rev | line source | 
| jbe@0 | 1 | 
| jbe@0 | 2 /*-------------* | 
| jbe@0 | 3  *  C prelude  * | 
| jbe@0 | 4  *-------------*/ | 
| jbe@0 | 5 | 
| jbe@0 | 6 #include "postgres.h" | 
| jbe@0 | 7 #include "fmgr.h" | 
| jbe@0 | 8 #include "libpq/pqformat.h" | 
| jbe@0 | 9 #include "access/gist.h" | 
| jbe@0 | 10 #include "access/stratnum.h" | 
| jbe@0 | 11 #include "utils/array.h" | 
| jbe@51 | 12 #include <limits.h> | 
| jbe@0 | 13 #include <math.h> | 
| jbe@0 | 14 | 
| jbe@0 | 15 #ifdef PG_MODULE_MAGIC | 
| jbe@0 | 16 PG_MODULE_MAGIC; | 
| jbe@0 | 17 #endif | 
| jbe@0 | 18 | 
| jbe@0 | 19 #if INT_MAX < 2147483647 | 
| jbe@0 | 20 #error Expected int type to be at least 32 bit wide | 
| jbe@0 | 21 #endif | 
| jbe@0 | 22 | 
| jbe@0 | 23 | 
| jbe@0 | 24 /*---------------------------------* | 
| jbe@0 | 25  *  distance calculation on earth  * | 
| jbe@0 | 26  *  (using WGS-84 spheroid)        * | 
| jbe@0 | 27  *---------------------------------*/ | 
| jbe@0 | 28 | 
| jbe@0 | 29 /*  WGS-84 spheroid with following parameters: | 
| jbe@0 | 30     semi-major axis  a = 6378137 | 
| jbe@0 | 31     semi-minor axis  b = a * (1 - 1/298.257223563) | 
| jbe@0 | 32     estimated diameter = 2 * (2*a+b)/3 | 
| jbe@0 | 33 */ | 
| jbe@0 | 34 #define PGL_SPHEROID_A 6378137.0            /* semi major axis */ | 
| jbe@0 | 35 #define PGL_SPHEROID_F (1.0/298.257223563)  /* flattening */ | 
| jbe@0 | 36 #define PGL_SPHEROID_B (PGL_SPHEROID_A * (1.0-PGL_SPHEROID_F)) | 
| jbe@0 | 37 #define PGL_EPS2       ( ( PGL_SPHEROID_A * PGL_SPHEROID_A - \ | 
| jbe@0 | 38                            PGL_SPHEROID_B * PGL_SPHEROID_B ) / \ | 
| jbe@0 | 39                          ( PGL_SPHEROID_A * PGL_SPHEROID_A ) ) | 
| jbe@0 | 40 #define PGL_SUBEPS2    (1.0-PGL_EPS2) | 
| jbe@42 | 41 #define PGL_RADIUS     ((2.0*PGL_SPHEROID_A + PGL_SPHEROID_B) / 3.0) | 
| jbe@42 | 42 #define PGL_DIAMETER   (2.0 * PGL_RADIUS) | 
| jbe@0 | 43 #define PGL_SCALE      (PGL_SPHEROID_A / PGL_DIAMETER)  /* semi-major ref. */ | 
| jbe@42 | 44 #define PGL_MAXDIST    (PGL_RADIUS * M_PI)              /* maximum distance */ | 
| jbe@42 | 45 #define PGL_FADELIMIT  (PGL_MAXDIST / 3.0)              /* 1/6 circumference */ | 
| jbe@0 | 46 | 
| jbe@0 | 47 /* calculate distance between two points on earth (given in degrees) */ | 
| jbe@0 | 48 static inline double pgl_distance( | 
| jbe@0 | 49   double lat1, double lon1, double lat2, double lon2 | 
| jbe@0 | 50 ) { | 
| jbe@0 | 51   float8 lat1cos, lat1sin, lat2cos, lat2sin, lon2cos, lon2sin; | 
| jbe@0 | 52   float8 nphi1, nphi2, x1, z1, x2, y2, z2, g, s, t; | 
| jbe@0 | 53   /* normalize delta longitude (lon2 > 0 && lon1 = 0) */ | 
| jbe@0 | 54   /* lon1 = 0 (not used anymore) */ | 
| jbe@0 | 55   lon2 = fabs(lon2-lon1); | 
| jbe@0 | 56   /* convert to radians (first divide, then multiply) */ | 
| jbe@0 | 57   lat1 = (lat1 / 180.0) * M_PI; | 
| jbe@0 | 58   lat2 = (lat2 / 180.0) * M_PI; | 
| jbe@0 | 59   lon2 = (lon2 / 180.0) * M_PI; | 
| jbe@0 | 60   /* make lat2 >= lat1 to ensure reversal-symmetry despite floating point | 
| jbe@0 | 61      operations (lon2 >= lon1 is already ensured in a previous step) */ | 
| jbe@0 | 62   if (lat2 < lat1) { float8 swap = lat1; lat1 = lat2; lat2 = swap; } | 
| jbe@0 | 63   /* calculate 3d coordinates on scaled ellipsoid which has an average diameter | 
| jbe@0 | 64      of 1.0 */ | 
| jbe@0 | 65   lat1cos = cos(lat1); lat1sin = sin(lat1); | 
| jbe@0 | 66   lat2cos = cos(lat2); lat2sin = sin(lat2); | 
| jbe@0 | 67   lon2cos = cos(lon2); lon2sin = sin(lon2); | 
| jbe@0 | 68   nphi1 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat1sin * lat1sin); | 
| jbe@0 | 69   nphi2 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat2sin * lat2sin); | 
| jbe@0 | 70   x1 = nphi1 * lat1cos; | 
| jbe@0 | 71   z1 = nphi1 * PGL_SUBEPS2 * lat1sin; | 
| jbe@0 | 72   x2 = nphi2 * lat2cos * lon2cos; | 
| jbe@0 | 73   y2 = nphi2 * lat2cos * lon2sin; | 
| jbe@0 | 74   z2 = nphi2 * PGL_SUBEPS2 * lat2sin; | 
| jbe@0 | 75   /* calculate tunnel distance through scaled (diameter 1.0) ellipsoid */ | 
| jbe@0 | 76   g = sqrt((x2-x1)*(x2-x1) + y2*y2 + (z2-z1)*(z2-z1)); | 
| jbe@0 | 77   /* convert tunnel distance through scaled ellipsoid to approximated surface | 
| jbe@0 | 78      distance on original ellipsoid */ | 
| jbe@0 | 79   if (g > 1.0) g = 1.0; | 
| jbe@0 | 80   s = PGL_DIAMETER * asin(g); | 
| jbe@0 | 81   /* return result only if small enough to be precise (less than 1/3 of | 
| jbe@0 | 82      maximum possible distance) */ | 
| jbe@0 | 83   if (s <= PGL_FADELIMIT) return s; | 
| jbe@0 | 84   /* calculate tunnel distance to antipodal point through scaled ellipsoid */ | 
| jbe@3 | 85   g = sqrt((x2+x1)*(x2+x1) + y2*y2 + (z2+z1)*(z2+z1)); | 
| jbe@0 | 86   /* convert tunnel distance to antipodal point through scaled ellipsoid to | 
| jbe@0 | 87      approximated surface distance to antipodal point on original ellipsoid */ | 
| jbe@0 | 88   if (g > 1.0) g = 1.0; | 
| jbe@0 | 89   t = PGL_DIAMETER * asin(g); | 
| jbe@0 | 90   /* surface distance between original points can now be approximated by | 
| jbe@0 | 91      substracting antipodal distance from maximum possible distance; | 
| jbe@0 | 92      return result only if small enough (less than 1/3 of maximum possible | 
| jbe@0 | 93      distance) */ | 
| jbe@0 | 94   if (t <= PGL_FADELIMIT) return PGL_MAXDIST-t; | 
| jbe@0 | 95   /* otherwise crossfade direct and antipodal result to ensure monotonicity */ | 
| jbe@0 | 96   return ( | 
| jbe@0 | 97     (s * (t-PGL_FADELIMIT) + (PGL_MAXDIST-t) * (s-PGL_FADELIMIT)) / | 
| jbe@0 | 98     (s + t - 2*PGL_FADELIMIT) | 
| jbe@0 | 99   ); | 
| jbe@0 | 100 } | 
| jbe@0 | 101 | 
| jbe@0 | 102 /* finite distance that can not be reached on earth */ | 
| jbe@0 | 103 #define PGL_ULTRA_DISTANCE (3 * PGL_MAXDIST) | 
| jbe@0 | 104 | 
| jbe@0 | 105 | 
| jbe@0 | 106 /*--------------------------------* | 
| jbe@0 | 107  *  simple geographic data types  * | 
| jbe@0 | 108  *--------------------------------*/ | 
| jbe@0 | 109 | 
| jbe@0 | 110 /* point on earth given by latitude and longitude in degrees */ | 
| jbe@0 | 111 /* (type "epoint" in SQL) */ | 
| jbe@0 | 112 typedef struct { | 
| jbe@0 | 113   double lat;  /* between  -90 and  90 (both inclusive) */ | 
| jbe@0 | 114   double lon;  /* between -180 and 180 (both inclusive) */ | 
| jbe@0 | 115 } pgl_point; | 
| jbe@0 | 116 | 
| jbe@0 | 117 /* box delimited by two parallels and two meridians (all in degrees) */ | 
| jbe@0 | 118 /* (type "ebox" in SQL) */ | 
| jbe@0 | 119 typedef struct { | 
| jbe@0 | 120   double lat_min;  /* between  -90 and  90 (both inclusive) */ | 
| jbe@0 | 121   double lat_max;  /* between  -90 and  90 (both inclusive) */ | 
| jbe@0 | 122   double lon_min;  /* between -180 and 180 (both inclusive) */ | 
| jbe@0 | 123   double lon_max;  /* between -180 and 180 (both inclusive) */ | 
| jbe@0 | 124   /* if lat_min > lat_max, then box is empty */ | 
| jbe@0 | 125   /* if lon_min > lon_max, then 180th meridian is crossed */ | 
| jbe@0 | 126 } pgl_box; | 
| jbe@0 | 127 | 
| jbe@0 | 128 /* circle on earth surface (for radial searches with fixed radius) */ | 
| jbe@0 | 129 /* (type "ecircle" in SQL) */ | 
| jbe@0 | 130 typedef struct { | 
| jbe@0 | 131   pgl_point center; | 
| jbe@0 | 132   double radius; /* positive (including +0 but excluding -0), or -INFINITY */ | 
| jbe@0 | 133   /* A negative radius (i.e. -INFINITY) denotes nothing (i.e. no point), | 
| jbe@0 | 134      zero radius (0) denotes a single point, | 
| jbe@0 | 135      a finite radius (0 < radius < INFINITY) denotes a filled circle, and | 
| jbe@0 | 136      a radius of INFINITY is valid and means complete coverage of earth. */ | 
| jbe@0 | 137 } pgl_circle; | 
| jbe@0 | 138 | 
| jbe@0 | 139 | 
| jbe@0 | 140 /*----------------------------------* | 
| jbe@0 | 141  *  geographic "cluster" data type  * | 
| jbe@0 | 142  *----------------------------------*/ | 
| jbe@0 | 143 | 
| jbe@0 | 144 /* A cluster is a collection of points, paths, outlines, and polygons. If two | 
| jbe@0 | 145    polygons in a cluster overlap, the area covered by both polygons does not | 
| jbe@0 | 146    belong to the cluster. This way, a cluster can be used to describe complex | 
| jbe@0 | 147    shapes like polygons with holes. Outlines are non-filled polygons. Paths are | 
| jbe@0 | 148    open by default (i.e. the last point in the list is not connected with the | 
| jbe@0 | 149    first point in the list). Note that each outline or polygon in a cluster | 
| jbe@0 | 150    must cover a longitude range of less than 180 degrees to avoid ambiguities. | 
| jbe@0 | 151    Areas which are larger may be split into multiple polygons. */ | 
| jbe@0 | 152 | 
| jbe@0 | 153 /* maximum number of points in a cluster */ | 
| jbe@0 | 154 /* (limited to avoid integer overflows, e.g. when allocating memory) */ | 
| jbe@0 | 155 #define PGL_CLUSTER_MAXPOINTS 16777216 | 
| jbe@0 | 156 | 
| jbe@0 | 157 /* types of cluster entries */ | 
| jbe@0 | 158 #define PGL_ENTRY_POINT   1  /* a point */ | 
| jbe@0 | 159 #define PGL_ENTRY_PATH    2  /* a path from first point to last point */ | 
| jbe@0 | 160 #define PGL_ENTRY_OUTLINE 3  /* a non-filled polygon with given vertices */ | 
| jbe@0 | 161 #define PGL_ENTRY_POLYGON 4  /* a filled polygon with given vertices */ | 
| jbe@0 | 162 | 
| jbe@0 | 163 /* Entries of a cluster are described by two different structs: pgl_newentry | 
| jbe@0 | 164    and pgl_entry. The first is used only during construction of a cluster, the | 
| jbe@0 | 165    second is used in all other cases (e.g. when reading clusters from the | 
| jbe@0 | 166    database, performing operations, etc). */ | 
| jbe@0 | 167 | 
| jbe@0 | 168 /* entry for new geographic cluster during construction of that cluster */ | 
| jbe@0 | 169 typedef struct { | 
| jbe@0 | 170   int32_t entrytype; | 
| jbe@0 | 171   int32_t npoints; | 
| jbe@0 | 172   pgl_point *points;  /* pointer to an array of points (pgl_point) */ | 
| jbe@0 | 173 } pgl_newentry; | 
| jbe@0 | 174 | 
| jbe@0 | 175 /* entry of geographic cluster */ | 
| jbe@0 | 176 typedef struct { | 
| jbe@0 | 177   int32_t entrytype;  /* type of entry: point, path, outline, polygon */ | 
| jbe@0 | 178   int32_t npoints;    /* number of stored points (set to 1 for point entry) */ | 
| jbe@0 | 179   int32_t offset;     /* offset of pgl_point array from cluster base address */ | 
| jbe@0 | 180   /* use macro PGL_ENTRY_POINTS to obtain a pointer to the array of points */ | 
| jbe@0 | 181 } pgl_entry; | 
| jbe@0 | 182 | 
| jbe@0 | 183 /* geographic cluster which is a collection of points, (open) paths, polygons, | 
| jbe@0 | 184    and outlines (non-filled polygons) */ | 
| jbe@0 | 185 typedef struct { | 
| jbe@0 | 186   char header[VARHDRSZ];  /* PostgreSQL header for variable size data types */ | 
| jbe@0 | 187   int32_t nentries;       /* number of stored points */ | 
| jbe@0 | 188   pgl_circle bounding;    /* bounding circle */ | 
| jbe@0 | 189   /* Note: bounding circle ensures alignment of pgl_cluster for points */ | 
| jbe@0 | 190   pgl_entry entries[FLEXIBLE_ARRAY_MEMBER];  /* var-length data */ | 
| jbe@0 | 191 } pgl_cluster; | 
| jbe@0 | 192 | 
| jbe@0 | 193 /* macro to determine memory alignment of points */ | 
| jbe@0 | 194 /* (needed to store pgl_point array after entries in pgl_cluster) */ | 
| jbe@0 | 195 typedef struct { char dummy; pgl_point aligned; } pgl_point_alignment; | 
| jbe@0 | 196 #define PGL_POINT_ALIGNMENT offsetof(pgl_point_alignment, aligned) | 
| jbe@0 | 197 | 
| jbe@0 | 198 /* macro to extract a pointer to the array of points of a cluster entry */ | 
| jbe@0 | 199 #define PGL_ENTRY_POINTS(cluster, idx) \ | 
| jbe@0 | 200   ((pgl_point *)(((intptr_t)cluster)+(cluster)->entries[idx].offset)) | 
| jbe@0 | 201 | 
| jbe@0 | 202 /* convert pgl_newentry array to pgl_cluster */ | 
| jbe@42 | 203 /* NOTE: requires pgl_finalize_cluster to be called to finalize result */ | 
| jbe@0 | 204 static pgl_cluster *pgl_new_cluster(int nentries, pgl_newentry *entries) { | 
| jbe@0 | 205   int i;              /* index of current entry */ | 
| jbe@0 | 206   int npoints = 0;    /* number of points in whole cluster */ | 
| jbe@0 | 207   int entry_npoints;  /* number of points in current entry */ | 
| jbe@0 | 208   int points_offset = PGL_POINT_ALIGNMENT * ( | 
| jbe@0 | 209     ( offsetof(pgl_cluster, entries) + | 
| jbe@0 | 210       nentries * sizeof(pgl_entry) + | 
| jbe@0 | 211       PGL_POINT_ALIGNMENT - 1 | 
| jbe@0 | 212     ) / PGL_POINT_ALIGNMENT | 
| jbe@0 | 213   );  /* offset of pgl_point array from base address (considering alignment) */ | 
| jbe@0 | 214   pgl_cluster *cluster;  /* new cluster to be returned */ | 
| jbe@0 | 215   /* determine total number of points */ | 
| jbe@0 | 216   for (i=0; i<nentries; i++) npoints += entries[i].npoints; | 
| jbe@0 | 217   /* allocate memory for cluster (including entries and points) */ | 
| jbe@0 | 218   cluster = palloc(points_offset + npoints * sizeof(pgl_point)); | 
| jbe@0 | 219   /* re-count total number of points to determine offset for each entry */ | 
| jbe@0 | 220   npoints = 0; | 
| jbe@0 | 221   /* copy entries and points */ | 
| jbe@0 | 222   for (i=0; i<nentries; i++) { | 
| jbe@0 | 223     /* determine number of points in entry */ | 
| jbe@0 | 224     entry_npoints = entries[i].npoints; | 
| jbe@0 | 225     /* copy entry */ | 
| jbe@0 | 226     cluster->entries[i].entrytype = entries[i].entrytype; | 
| jbe@0 | 227     cluster->entries[i].npoints = entry_npoints; | 
| jbe@0 | 228     /* calculate offset (in bytes) of pgl_point array */ | 
| jbe@0 | 229     cluster->entries[i].offset = points_offset + npoints * sizeof(pgl_point); | 
| jbe@0 | 230     /* copy points */ | 
| jbe@0 | 231     memcpy( | 
| jbe@0 | 232       PGL_ENTRY_POINTS(cluster, i), | 
| jbe@0 | 233       entries[i].points, | 
| jbe@0 | 234       entry_npoints * sizeof(pgl_point) | 
| jbe@0 | 235     ); | 
| jbe@0 | 236     /* update total number of points processed */ | 
| jbe@0 | 237     npoints += entry_npoints; | 
| jbe@0 | 238   } | 
| jbe@0 | 239   /* set number of entries in cluster */ | 
| jbe@0 | 240   cluster->nentries = nentries; | 
| jbe@0 | 241   /* set PostgreSQL header for variable sized data */ | 
| jbe@0 | 242   SET_VARSIZE(cluster, points_offset + npoints * sizeof(pgl_point)); | 
| jbe@0 | 243   /* return newly created cluster */ | 
| jbe@0 | 244   return cluster; | 
| jbe@0 | 245 } | 
| jbe@0 | 246 | 
| jbe@0 | 247 | 
| jbe@46 | 248 /*----------------------------------------------* | 
| jbe@46 | 249  *  Geographic point with integer sample count  * | 
| jbe@46 | 250  *  (needed for fair distance calculation)      * | 
| jbe@46 | 251  *----------------------------------------------*/ | 
| jbe@46 | 252 | 
| jbe@46 | 253 typedef struct { | 
| jbe@46 | 254   pgl_point point;  /* NOTE: point first to allow C cast to pgl_point */ | 
| jbe@46 | 255   int32 samples; | 
| jbe@46 | 256 } pgl_point_sc; | 
| jbe@46 | 257 | 
| jbe@46 | 258 | 
| jbe@0 | 259 /*----------------------------------------* | 
| jbe@0 | 260  *  C functions on geographic data types  * | 
| jbe@0 | 261  *----------------------------------------*/ | 
| jbe@0 | 262 | 
| jbe@0 | 263 /* round latitude or longitude to 12 digits after decimal point */ | 
| jbe@0 | 264 static inline double pgl_round(double val) { | 
| jbe@0 | 265   return round(val * 1e12) / 1e12; | 
| jbe@0 | 266 } | 
| jbe@0 | 267 | 
| jbe@0 | 268 /* compare two points */ | 
| jbe@0 | 269 /* (equality when same point on earth is described, otherwise an arbitrary | 
| jbe@0 | 270    linear order) */ | 
| jbe@0 | 271 static int pgl_point_cmp(pgl_point *point1, pgl_point *point2) { | 
| jbe@0 | 272   double lon1, lon2;  /* modified longitudes for special cases */ | 
| jbe@0 | 273   /* use latitude as first ordering criterion */ | 
| jbe@0 | 274   if (point1->lat < point2->lat) return -1; | 
| jbe@0 | 275   if (point1->lat > point2->lat) return 1; | 
| jbe@0 | 276   /* determine modified longitudes (considering special case of poles and | 
| jbe@0 | 277      180th meridian which can be described as W180 or E180) */ | 
| jbe@0 | 278   if (point1->lat == -90 || point1->lat == 90) lon1 = 0; | 
| jbe@0 | 279   else if (point1->lon == 180) lon1 = -180; | 
| jbe@0 | 280   else lon1 = point1->lon; | 
| jbe@0 | 281   if (point2->lat == -90 || point2->lat == 90) lon2 = 0; | 
| jbe@0 | 282   else if (point2->lon == 180) lon2 = -180; | 
| jbe@0 | 283   else lon2 = point2->lon; | 
| jbe@0 | 284   /* use (modified) longitude as secondary ordering criterion */ | 
| jbe@0 | 285   if (lon1 < lon2) return -1; | 
| jbe@0 | 286   if (lon1 > lon2) return 1; | 
| jbe@0 | 287   /* no difference found, points are equal */ | 
| jbe@0 | 288   return 0; | 
| jbe@0 | 289 } | 
| jbe@0 | 290 | 
| jbe@0 | 291 /* compare two boxes */ | 
| jbe@0 | 292 /* (equality when same box on earth is described, otherwise an arbitrary linear | 
| jbe@0 | 293    order) */ | 
| jbe@0 | 294 static int pgl_box_cmp(pgl_box *box1, pgl_box *box2) { | 
| jbe@0 | 295   /* two empty boxes are equal, and an empty box is always considered "less | 
| jbe@0 | 296      than" a non-empty box */ | 
| jbe@0 | 297   if (box1->lat_min> box1->lat_max && box2->lat_min<=box2->lat_max) return -1; | 
| jbe@0 | 298   if (box1->lat_min> box1->lat_max && box2->lat_min> box2->lat_max) return 0; | 
| jbe@0 | 299   if (box1->lat_min<=box1->lat_max && box2->lat_min> box2->lat_max) return 1; | 
| jbe@0 | 300   /* use southern border as first ordering criterion */ | 
| jbe@0 | 301   if (box1->lat_min < box2->lat_min) return -1; | 
| jbe@0 | 302   if (box1->lat_min > box2->lat_min) return 1; | 
| jbe@0 | 303   /* use northern border as second ordering criterion */ | 
| jbe@0 | 304   if (box1->lat_max < box2->lat_max) return -1; | 
| jbe@0 | 305   if (box1->lat_max > box2->lat_max) return 1; | 
| jbe@0 | 306   /* use western border as third ordering criterion */ | 
| jbe@0 | 307   if (box1->lon_min < box2->lon_min) return -1; | 
| jbe@0 | 308   if (box1->lon_min > box2->lon_min) return 1; | 
| jbe@0 | 309   /* use eastern border as fourth ordering criterion */ | 
| jbe@0 | 310   if (box1->lon_max < box2->lon_max) return -1; | 
| jbe@0 | 311   if (box1->lon_max > box2->lon_max) return 1; | 
| jbe@0 | 312   /* no difference found, boxes are equal */ | 
| jbe@0 | 313   return 0; | 
| jbe@0 | 314 } | 
| jbe@0 | 315 | 
| jbe@0 | 316 /* compare two circles */ | 
| jbe@0 | 317 /* (equality when same circle on earth is described, otherwise an arbitrary | 
| jbe@0 | 318    linear order) */ | 
| jbe@0 | 319 static int pgl_circle_cmp(pgl_circle *circle1, pgl_circle *circle2) { | 
| jbe@0 | 320   /* two circles with same infinite radius (positive or negative infinity) are | 
| jbe@0 | 321      considered equal independently of center point */ | 
| jbe@0 | 322   if ( | 
| jbe@0 | 323     !isfinite(circle1->radius) && !isfinite(circle2->radius) && | 
| jbe@0 | 324     circle1->radius == circle2->radius | 
| jbe@0 | 325   ) return 0; | 
| jbe@0 | 326   /* use radius as first ordering criterion */ | 
| jbe@0 | 327   if (circle1->radius < circle2->radius) return -1; | 
| jbe@0 | 328   if (circle1->radius > circle2->radius) return 1; | 
| jbe@0 | 329   /* use center point as secondary ordering criterion */ | 
| jbe@0 | 330   return pgl_point_cmp(&(circle1->center), &(circle2->center)); | 
| jbe@0 | 331 } | 
| jbe@0 | 332 | 
| jbe@0 | 333 /* set box to empty box*/ | 
| jbe@0 | 334 static void pgl_box_set_empty(pgl_box *box) { | 
| jbe@0 | 335   box->lat_min = INFINITY; | 
| jbe@0 | 336   box->lat_max = -INFINITY; | 
| jbe@0 | 337   box->lon_min = 0; | 
| jbe@0 | 338   box->lon_max = 0; | 
| jbe@0 | 339 } | 
| jbe@0 | 340 | 
| jbe@0 | 341 /* check if point is inside a box */ | 
| jbe@0 | 342 static bool pgl_point_in_box(pgl_point *point, pgl_box *box) { | 
| jbe@0 | 343   return ( | 
| jbe@0 | 344     point->lat >= box->lat_min && point->lat <= box->lat_max && ( | 
| jbe@0 | 345       (box->lon_min > box->lon_max) ? ( | 
| jbe@0 | 346         /* box crosses 180th meridian */ | 
| jbe@0 | 347         point->lon >= box->lon_min || point->lon <= box->lon_max | 
| jbe@0 | 348       ) : ( | 
| jbe@0 | 349         /* box does not cross the 180th meridian */ | 
| jbe@0 | 350         point->lon >= box->lon_min && point->lon <= box->lon_max | 
| jbe@0 | 351       ) | 
| jbe@0 | 352     ) | 
| jbe@0 | 353   ); | 
| jbe@0 | 354 } | 
| jbe@0 | 355 | 
| jbe@0 | 356 /* check if two boxes overlap */ | 
| jbe@0 | 357 static bool pgl_boxes_overlap(pgl_box *box1, pgl_box *box2) { | 
| jbe@0 | 358   return ( | 
| jbe@0 | 359     box2->lat_max >= box2->lat_min &&  /* ensure box2 is not empty */ | 
| jbe@0 | 360     ( box2->lat_min >= box1->lat_min || box2->lat_max >= box1->lat_min ) && | 
| jbe@0 | 361     ( box2->lat_min <= box1->lat_max || box2->lat_max <= box1->lat_max ) && ( | 
| jbe@0 | 362       ( | 
| jbe@0 | 363         /* check if one and only one box crosses the 180th meridian */ | 
| jbe@0 | 364         ((box1->lon_min > box1->lon_max) ? 1 : 0) ^ | 
| jbe@0 | 365         ((box2->lon_min > box2->lon_max) ? 1 : 0) | 
| jbe@0 | 366       ) ? ( | 
| jbe@0 | 367         /* exactly one box crosses the 180th meridian */ | 
| jbe@0 | 368         box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min || | 
| jbe@0 | 369         box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max | 
| jbe@0 | 370       ) : ( | 
| jbe@0 | 371         /* no box or both boxes cross the 180th meridian */ | 
| jbe@0 | 372         ( | 
| jbe@0 | 373           (box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min) && | 
| jbe@0 | 374           (box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max) | 
| jbe@0 | 375         ) || | 
| jbe@0 | 376         /* handle W180 == E180 */ | 
| jbe@0 | 377         ( box1->lon_min == -180 && box2->lon_max == 180 ) || | 
| jbe@0 | 378         ( box2->lon_min == -180 && box1->lon_max == 180 ) | 
| jbe@0 | 379       ) | 
| jbe@0 | 380     ) | 
| jbe@0 | 381   ); | 
| jbe@0 | 382 } | 
| jbe@0 | 383 | 
| jbe@0 | 384 /* check unambiguousness of east/west orientation of cluster entries and set | 
| jbe@0 | 385    bounding circle of cluster */ | 
| jbe@0 | 386 static bool pgl_finalize_cluster(pgl_cluster *cluster) { | 
| jbe@0 | 387   int i, j;                 /* i: index of entry, j: index of point in entry */ | 
| jbe@0 | 388   int npoints;              /* number of points in entry */ | 
| jbe@0 | 389   int total_npoints = 0;    /* total number of points in cluster */ | 
| jbe@0 | 390   pgl_point *points;        /* points in entry */ | 
| jbe@0 | 391   int lon_dir;              /* first point of entry west (-1) or east (+1) */ | 
| jbe@0 | 392   double lon_break = 0;     /* antipodal longitude of first point in entry */ | 
| jbe@0 | 393   double lon_min, lon_max;  /* covered longitude range of entry */ | 
| jbe@0 | 394   double value;             /* temporary variable */ | 
| jbe@0 | 395   /* reset bounding circle center to empty circle at 0/0 coordinates */ | 
| jbe@0 | 396   cluster->bounding.center.lat = 0; | 
| jbe@0 | 397   cluster->bounding.center.lon = 0; | 
| jbe@0 | 398   cluster->bounding.radius = -INFINITY; | 
| jbe@0 | 399   /* if cluster is not empty */ | 
| jbe@0 | 400   if (cluster->nentries != 0) { | 
| jbe@0 | 401     /* iterate over all cluster entries and ensure they each cover a longitude | 
| jbe@0 | 402        range less than 180 degrees */ | 
| jbe@0 | 403     for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 404       /* get properties of entry */ | 
| jbe@0 | 405       npoints = cluster->entries[i].npoints; | 
| jbe@0 | 406       points = PGL_ENTRY_POINTS(cluster, i); | 
| jbe@0 | 407       /* get longitude of first point of entry */ | 
| jbe@0 | 408       value = points[0].lon; | 
| jbe@0 | 409       /* initialize lon_min and lon_max with longitude of first point */ | 
| jbe@0 | 410       lon_min = value; | 
| jbe@0 | 411       lon_max = value; | 
| jbe@0 | 412       /* determine east/west orientation of first point and calculate antipodal | 
| jbe@0 | 413          longitude (Note: rounding required here) */ | 
| jbe@0 | 414       if      (value < 0) { lon_dir = -1; lon_break = pgl_round(value + 180); } | 
| jbe@0 | 415       else if (value > 0) { lon_dir =  1; lon_break = pgl_round(value - 180); } | 
| jbe@0 | 416       else lon_dir = 0; | 
| jbe@0 | 417       /* iterate over all other points in entry */ | 
| jbe@0 | 418       for (j=1; j<npoints; j++) { | 
| jbe@0 | 419         /* consider longitude wrap-around */ | 
| jbe@0 | 420         value = points[j].lon; | 
| jbe@0 | 421         if      (lon_dir<0 && value>lon_break) value = pgl_round(value - 360); | 
| jbe@0 | 422         else if (lon_dir>0 && value<lon_break) value = pgl_round(value + 360); | 
| jbe@0 | 423         /* update lon_min and lon_max */ | 
| jbe@0 | 424         if      (value < lon_min) lon_min = value; | 
| jbe@0 | 425         else if (value > lon_max) lon_max = value; | 
| jbe@0 | 426         /* return false if 180 degrees or more are covered */ | 
| jbe@0 | 427         if (lon_max - lon_min >= 180) return false; | 
| jbe@0 | 428       } | 
| jbe@0 | 429     } | 
| jbe@0 | 430     /* iterate over all points of all entries and calculate arbitrary center | 
| jbe@0 | 431        point for bounding circle (best if center point minimizes the radius, | 
| jbe@0 | 432        but some error is allowed here) */ | 
| jbe@0 | 433     for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 434       /* get properties of entry */ | 
| jbe@0 | 435       npoints = cluster->entries[i].npoints; | 
| jbe@0 | 436       points = PGL_ENTRY_POINTS(cluster, i); | 
| jbe@0 | 437       /* check if first entry */ | 
| jbe@0 | 438       if (i==0) { | 
| jbe@0 | 439         /* get longitude of first point of first entry in whole cluster */ | 
| jbe@0 | 440         value = points[0].lon; | 
| jbe@0 | 441         /* initialize lon_min and lon_max with longitude of first point of | 
| jbe@0 | 442            first entry in whole cluster (used to determine if whole cluster | 
| jbe@0 | 443            covers a longitude range of 180 degrees or more) */ | 
| jbe@0 | 444         lon_min = value; | 
| jbe@0 | 445         lon_max = value; | 
| jbe@0 | 446         /* determine east/west orientation of first point and calculate | 
| jbe@0 | 447            antipodal longitude (Note: rounding not necessary here) */ | 
| jbe@0 | 448         if      (value < 0) { lon_dir = -1; lon_break = value + 180; } | 
| jbe@0 | 449         else if (value > 0) { lon_dir =  1; lon_break = value - 180; } | 
| jbe@0 | 450         else lon_dir = 0; | 
| jbe@0 | 451       } | 
| jbe@0 | 452       /* iterate over all points in entry */ | 
| jbe@0 | 453       for (j=0; j<npoints; j++) { | 
| jbe@0 | 454         /* longitude wrap-around (Note: rounding not necessary here) */ | 
| jbe@0 | 455         value = points[j].lon; | 
| jbe@0 | 456         if      (lon_dir < 0 && value > lon_break) value -= 360; | 
| jbe@0 | 457         else if (lon_dir > 0 && value < lon_break) value += 360; | 
| jbe@0 | 458         if      (value < lon_min) lon_min = value; | 
| jbe@0 | 459         else if (value > lon_max) lon_max = value; | 
| jbe@46 | 460         /* set bounding circle to cover whole earth if 180 degrees or more are | 
| jbe@46 | 461            covered */ | 
| jbe@0 | 462         if (lon_max - lon_min >= 180) { | 
| jbe@0 | 463           cluster->bounding.center.lat = 0; | 
| jbe@0 | 464           cluster->bounding.center.lon = 0; | 
| jbe@0 | 465           cluster->bounding.radius = INFINITY; | 
| jbe@0 | 466           return true; | 
| jbe@0 | 467         } | 
| jbe@0 | 468         /* add point to bounding circle center (for average calculation) */ | 
| jbe@0 | 469         cluster->bounding.center.lat += points[j].lat; | 
| jbe@0 | 470         cluster->bounding.center.lon += value; | 
| jbe@0 | 471       } | 
| jbe@0 | 472       /* count total number of points */ | 
| jbe@0 | 473       total_npoints += npoints; | 
| jbe@0 | 474     } | 
| jbe@0 | 475     /* determine average latitude and longitude of cluster */ | 
| jbe@0 | 476     cluster->bounding.center.lat /= total_npoints; | 
| jbe@0 | 477     cluster->bounding.center.lon /= total_npoints; | 
| jbe@0 | 478     /* normalize longitude of center of cluster bounding circle */ | 
| jbe@0 | 479     if (cluster->bounding.center.lon < -180) { | 
| jbe@0 | 480       cluster->bounding.center.lon += 360; | 
| jbe@0 | 481     } | 
| jbe@0 | 482     else if (cluster->bounding.center.lon > 180) { | 
| jbe@0 | 483       cluster->bounding.center.lon -= 360; | 
| jbe@0 | 484     } | 
| jbe@0 | 485     /* round bounding circle center (useful if it is used by other functions) */ | 
| jbe@0 | 486     cluster->bounding.center.lat = pgl_round(cluster->bounding.center.lat); | 
| jbe@0 | 487     cluster->bounding.center.lon = pgl_round(cluster->bounding.center.lon); | 
| jbe@0 | 488     /* calculate radius of bounding circle */ | 
| jbe@0 | 489     for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 490       npoints = cluster->entries[i].npoints; | 
| jbe@0 | 491       points = PGL_ENTRY_POINTS(cluster, i); | 
| jbe@0 | 492       for (j=0; j<npoints; j++) { | 
| jbe@0 | 493         value = pgl_distance( | 
| jbe@0 | 494           cluster->bounding.center.lat, cluster->bounding.center.lon, | 
| jbe@0 | 495           points[j].lat, points[j].lon | 
| jbe@0 | 496         ); | 
| jbe@0 | 497         if (value > cluster->bounding.radius) cluster->bounding.radius = value; | 
| jbe@0 | 498       } | 
| jbe@0 | 499     } | 
| jbe@0 | 500   } | 
| jbe@0 | 501   /* return true (east/west orientation is unambiguous) */ | 
| jbe@0 | 502   return true; | 
| jbe@0 | 503 } | 
| jbe@0 | 504 | 
| jbe@0 | 505 /* check if point is inside cluster */ | 
| jbe@20 | 506 /* (if point is on perimeter, then true is returned if and only if | 
| jbe@20 | 507    strict == false) */ | 
| jbe@20 | 508 static bool pgl_point_in_cluster( | 
| jbe@20 | 509   pgl_point *point, | 
| jbe@20 | 510   pgl_cluster *cluster, | 
| jbe@20 | 511   bool strict | 
| jbe@20 | 512 ) { | 
| jbe@0 | 513   int i, j, k;  /* i: entry, j: point in entry, k: next point in entry */ | 
| jbe@0 | 514   int entrytype;         /* type of entry */ | 
| jbe@0 | 515   int npoints;           /* number of points in entry */ | 
| jbe@0 | 516   pgl_point *points;     /* array of points in entry */ | 
| jbe@0 | 517   int lon_dir = 0;       /* first vertex west (-1) or east (+1) */ | 
| jbe@0 | 518   double lon_break = 0;  /* antipodal longitude of first vertex */ | 
| jbe@0 | 519   double lat0 = point->lat;  /* latitude of point */ | 
| jbe@0 | 520   double lon0;           /* (adjusted) longitude of point */ | 
| jbe@0 | 521   double lat1, lon1;     /* latitude and (adjusted) longitude of vertex */ | 
| jbe@0 | 522   double lat2, lon2;     /* latitude and (adjusted) longitude of next vertex */ | 
| jbe@0 | 523   double lon;            /* longitude of intersection */ | 
| jbe@0 | 524   int counter = 0;       /* counter for intersections east of point */ | 
| jbe@0 | 525   /* iterate over all entries */ | 
| jbe@0 | 526   for (i=0; i<cluster->nentries; i++) { | 
| jbe@20 | 527     /* get type of entry */ | 
| jbe@0 | 528     entrytype = cluster->entries[i].entrytype; | 
| jbe@20 | 529     /* skip all entries but polygons if perimeters are excluded */ | 
| jbe@20 | 530     if (strict && entrytype != PGL_ENTRY_POLYGON) continue; | 
| jbe@20 | 531     /* get points of entry */ | 
| jbe@0 | 532     npoints = cluster->entries[i].npoints; | 
| jbe@0 | 533     points = PGL_ENTRY_POINTS(cluster, i); | 
| jbe@0 | 534     /* determine east/west orientation of first point of entry and calculate | 
| jbe@0 | 535        antipodal longitude */ | 
| jbe@0 | 536     lon_break = points[0].lon; | 
| jbe@0 | 537     if      (lon_break < 0) { lon_dir = -1; lon_break += 180; } | 
| jbe@0 | 538     else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; } | 
| jbe@0 | 539     else lon_dir = 0; | 
| jbe@0 | 540     /* get longitude of point */ | 
| jbe@0 | 541     lon0 = point->lon; | 
| jbe@0 | 542     /* consider longitude wrap-around for point */ | 
| jbe@0 | 543     if      (lon_dir < 0 && lon0 > lon_break) lon0 = pgl_round(lon0 - 360); | 
| jbe@0 | 544     else if (lon_dir > 0 && lon0 < lon_break) lon0 = pgl_round(lon0 + 360); | 
| jbe@0 | 545     /* iterate over all edges and vertices */ | 
| jbe@0 | 546     for (j=0; j<npoints; j++) { | 
| jbe@20 | 547       /* return if point is on vertex of polygon */ | 
| jbe@20 | 548       if (pgl_point_cmp(point, &(points[j])) == 0) return !strict; | 
| jbe@0 | 549       /* calculate index of next vertex */ | 
| jbe@0 | 550       k = (j+1) % npoints; | 
| jbe@0 | 551       /* skip last edge unless entry is (closed) outline or polygon */ | 
| jbe@0 | 552       if ( | 
| jbe@0 | 553         k == 0 && | 
| jbe@0 | 554         entrytype != PGL_ENTRY_OUTLINE && | 
| jbe@0 | 555         entrytype != PGL_ENTRY_POLYGON | 
| jbe@0 | 556       ) continue; | 
| jbe@16 | 557       /* use previously calculated values for lat1 and lon1 if possible */ | 
| jbe@16 | 558       if (j) { | 
| jbe@16 | 559         lat1 = lat2; | 
| jbe@16 | 560         lon1 = lon2; | 
| jbe@16 | 561       } else { | 
| jbe@16 | 562         /* otherwise get latitude and longitude values of first vertex */ | 
| jbe@16 | 563         lat1 = points[0].lat; | 
| jbe@16 | 564         lon1 = points[0].lon; | 
| jbe@16 | 565         /* and consider longitude wrap-around for first vertex */ | 
| jbe@16 | 566         if      (lon_dir < 0 && lon1 > lon_break) lon1 = pgl_round(lon1 - 360); | 
| jbe@16 | 567         else if (lon_dir > 0 && lon1 < lon_break) lon1 = pgl_round(lon1 + 360); | 
| jbe@16 | 568       } | 
| jbe@16 | 569       /* get latitude and longitude of next vertex */ | 
| jbe@0 | 570       lat2 = points[k].lat; | 
| jbe@0 | 571       lon2 = points[k].lon; | 
| jbe@16 | 572       /* consider longitude wrap-around for next vertex */ | 
| jbe@0 | 573       if      (lon_dir < 0 && lon2 > lon_break) lon2 = pgl_round(lon2 - 360); | 
| jbe@0 | 574       else if (lon_dir > 0 && lon2 < lon_break) lon2 = pgl_round(lon2 + 360); | 
| jbe@20 | 575       /* return if point is on horizontal (west to east) edge of polygon */ | 
| jbe@0 | 576       if ( | 
| jbe@0 | 577         lat0 == lat1 && lat0 == lat2 && | 
| jbe@0 | 578         ( (lon0 >= lon1 && lon0 <= lon2) || (lon0 >= lon2 && lon0 <= lon1) ) | 
| jbe@20 | 579       ) return !strict; | 
| jbe@0 | 580       /* check if edge crosses east/west line of point */ | 
| jbe@0 | 581       if ((lat1 < lat0 && lat2 >= lat0) || (lat2 < lat0 && lat1 >= lat0)) { | 
| jbe@0 | 582         /* calculate longitude of intersection */ | 
| jbe@0 | 583         lon = (lon1 * (lat2-lat0) + lon2 * (lat0-lat1)) / (lat2-lat1); | 
| jbe@20 | 584         /* return if intersection goes (approximately) through point */ | 
| jbe@20 | 585         if (pgl_round(lon) == lon0) return !strict; | 
| jbe@0 | 586         /* count intersection if east of point and entry is polygon*/ | 
| jbe@0 | 587         if (entrytype == PGL_ENTRY_POLYGON && lon > lon0) counter++; | 
| jbe@0 | 588       } | 
| jbe@0 | 589     } | 
| jbe@0 | 590   } | 
| jbe@0 | 591   /* return true if number of intersections is odd */ | 
| jbe@0 | 592   return counter & 1; | 
| jbe@0 | 593 } | 
| jbe@0 | 594 | 
| jbe@20 | 595 /* check if all points of the second cluster are strictly inside the first | 
| jbe@20 | 596    cluster */ | 
| jbe@20 | 597 static inline bool pgl_all_cluster_points_strictly_in_cluster( | 
| jbe@16 | 598   pgl_cluster *outer, pgl_cluster *inner | 
| jbe@16 | 599 ) { | 
| jbe@16 | 600   int i, j;           /* i: entry, j: point in entry */ | 
| jbe@16 | 601   int npoints;        /* number of points in entry */ | 
| jbe@16 | 602   pgl_point *points;  /* array of points in entry */ | 
| jbe@16 | 603   /* iterate over all entries of "inner" cluster */ | 
| jbe@16 | 604   for (i=0; i<inner->nentries; i++) { | 
| jbe@16 | 605     /* get properties of entry */ | 
| jbe@16 | 606     npoints = inner->entries[i].npoints; | 
| jbe@16 | 607     points = PGL_ENTRY_POINTS(inner, i); | 
| jbe@16 | 608     /* iterate over all points in entry of "inner" cluster */ | 
| jbe@16 | 609     for (j=0; j<npoints; j++) { | 
| jbe@16 | 610       /* return false if one point of inner cluster is not in outer cluster */ | 
| jbe@20 | 611       if (!pgl_point_in_cluster(points+j, outer, true)) return false; | 
| jbe@16 | 612     } | 
| jbe@16 | 613   } | 
| jbe@16 | 614   /* otherwise return true */ | 
| jbe@16 | 615   return true; | 
| jbe@16 | 616 } | 
| jbe@16 | 617 | 
| jbe@16 | 618 /* check if any point the second cluster is inside the first cluster */ | 
| jbe@16 | 619 static inline bool pgl_any_cluster_points_in_cluster( | 
| jbe@16 | 620   pgl_cluster *outer, pgl_cluster *inner | 
| jbe@16 | 621 ) { | 
| jbe@16 | 622   int i, j;           /* i: entry, j: point in entry */ | 
| jbe@16 | 623   int npoints;        /* number of points in entry */ | 
| jbe@16 | 624   pgl_point *points;  /* array of points in entry */ | 
| jbe@16 | 625   /* iterate over all entries of "inner" cluster */ | 
| jbe@16 | 626   for (i=0; i<inner->nentries; i++) { | 
| jbe@16 | 627     /* get properties of entry */ | 
| jbe@16 | 628     npoints = inner->entries[i].npoints; | 
| jbe@16 | 629     points = PGL_ENTRY_POINTS(inner, i); | 
| jbe@16 | 630     /* iterate over all points in entry of "inner" cluster */ | 
| jbe@16 | 631     for (j=0; j<npoints; j++) { | 
| jbe@16 | 632       /* return true if one point of inner cluster is in outer cluster */ | 
| jbe@20 | 633       if (pgl_point_in_cluster(points+j, outer, false)) return true; | 
| jbe@16 | 634     } | 
| jbe@16 | 635   } | 
| jbe@16 | 636   /* otherwise return false */ | 
| jbe@16 | 637   return false; | 
| jbe@16 | 638 } | 
| jbe@16 | 639 | 
| jbe@20 | 640 /* check if line segment strictly crosses line (not just touching) */ | 
| jbe@20 | 641 static inline bool pgl_lseg_crosses_line( | 
| jbe@16 | 642   double seg_x1,  double seg_y1,  double seg_x2,  double seg_y2, | 
| jbe@20 | 643   double line_x1, double line_y1, double line_x2, double line_y2 | 
| jbe@16 | 644 ) { | 
| jbe@20 | 645   return ( | 
| jbe@20 | 646     ( | 
| jbe@20 | 647       (seg_x1-line_x1) * (line_y2-line_y1) - | 
| jbe@20 | 648       (seg_y1-line_y1) * (line_x2-line_x1) | 
| jbe@20 | 649     ) * ( | 
| jbe@20 | 650       (seg_x2-line_x1) * (line_y2-line_y1) - | 
| jbe@20 | 651       (seg_y2-line_y1) * (line_x2-line_x1) | 
| jbe@20 | 652     ) | 
| jbe@20 | 653   ) < 0; | 
| jbe@16 | 654 } | 
| jbe@16 | 655 | 
| jbe@20 | 656 /* check if paths and outlines of two clusters strictly overlap (not just | 
| jbe@20 | 657    touching) */ | 
| jbe@16 | 658 static bool pgl_outlines_overlap( | 
| jbe@20 | 659   pgl_cluster *cluster1, pgl_cluster *cluster2 | 
| jbe@16 | 660 ) { | 
| jbe@16 | 661   int i1, j1, k1;  /* i: entry, j: point in entry, k: next point in entry */ | 
| jbe@16 | 662   int i2, j2, k2; | 
| jbe@16 | 663   int entrytype1, entrytype2;     /* type of entry */ | 
| jbe@16 | 664   int npoints1, npoints2;         /* number of points in entry */ | 
| jbe@16 | 665   pgl_point *points1;             /* array of points in entry of cluster1 */ | 
| jbe@16 | 666   pgl_point *points2;             /* array of points in entry of cluster2 */ | 
| jbe@16 | 667   int lon_dir1, lon_dir2;         /* first vertex west (-1) or east (+1) */ | 
| jbe@16 | 668   double lon_break1, lon_break2;  /* antipodal longitude of first vertex */ | 
| jbe@16 | 669   double lat11, lon11;  /* latitude and (adjusted) longitude of vertex */ | 
| jbe@16 | 670   double lat12, lon12;  /* latitude and (adjusted) longitude of next vertex */ | 
| jbe@16 | 671   double lat21, lon21;  /* latitude and (adjusted) longitudes for cluster2 */ | 
| jbe@16 | 672   double lat22, lon22; | 
| jbe@16 | 673   double wrapvalue;     /* temporary helper value to adjust wrap-around */ | 
| jbe@16 | 674   /* iterate over all entries of cluster1 */ | 
| jbe@16 | 675   for (i1=0; i1<cluster1->nentries; i1++) { | 
| jbe@16 | 676     /* get properties of entry in cluster1 and skip points */ | 
| jbe@16 | 677     npoints1 = cluster1->entries[i1].npoints; | 
| jbe@16 | 678     if (npoints1 < 2) continue; | 
| jbe@16 | 679     entrytype1 = cluster1->entries[i1].entrytype; | 
| jbe@16 | 680     points1 = PGL_ENTRY_POINTS(cluster1, i1); | 
| jbe@16 | 681     /* determine east/west orientation of first point and calculate antipodal | 
| jbe@16 | 682        longitude */ | 
| jbe@16 | 683     lon_break1 = points1[0].lon; | 
| jbe@16 | 684     if (lon_break1 < 0) { | 
| jbe@16 | 685       lon_dir1   = -1; | 
| jbe@16 | 686       lon_break1 = pgl_round(lon_break1 + 180); | 
| jbe@16 | 687     } else if (lon_break1 > 0) { | 
| jbe@16 | 688       lon_dir1   = 1; | 
| jbe@16 | 689       lon_break1 = pgl_round(lon_break1 - 180); | 
| jbe@16 | 690     } else lon_dir1 = 0; | 
| jbe@16 | 691     /* iterate over all edges and vertices in cluster1 */ | 
| jbe@16 | 692     for (j1=0; j1<npoints1; j1++) { | 
| jbe@16 | 693       /* calculate index of next vertex */ | 
| jbe@16 | 694       k1 = (j1+1) % npoints1; | 
| jbe@16 | 695       /* skip last edge unless entry is (closed) outline or polygon */ | 
| jbe@16 | 696       if ( | 
| jbe@16 | 697         k1 == 0 && | 
| jbe@16 | 698         entrytype1 != PGL_ENTRY_OUTLINE && | 
| jbe@16 | 699         entrytype1 != PGL_ENTRY_POLYGON | 
| jbe@16 | 700       ) continue; | 
| jbe@16 | 701       /* use previously calculated values for lat1 and lon1 if possible */ | 
| jbe@16 | 702       if (j1) { | 
| jbe@16 | 703         lat11 = lat12; | 
| jbe@16 | 704         lon11 = lon12; | 
| jbe@16 | 705       } else { | 
| jbe@16 | 706         /* otherwise get latitude and longitude values of first vertex */ | 
| jbe@16 | 707         lat11 = points1[0].lat; | 
| jbe@16 | 708         lon11 = points1[0].lon; | 
| jbe@16 | 709         /* and consider longitude wrap-around for first vertex */ | 
| jbe@16 | 710         if      (lon_dir1<0 && lon11>lon_break1) lon11 = pgl_round(lon11-360); | 
| jbe@16 | 711         else if (lon_dir1>0 && lon11<lon_break1) lon11 = pgl_round(lon11+360); | 
| jbe@16 | 712       } | 
| jbe@16 | 713       /* get latitude and longitude of next vertex */ | 
| jbe@16 | 714       lat12 = points1[k1].lat; | 
| jbe@16 | 715       lon12 = points1[k1].lon; | 
| jbe@16 | 716       /* consider longitude wrap-around for next vertex */ | 
| jbe@16 | 717       if      (lon_dir1<0 && lon12>lon_break1) lon12 = pgl_round(lon12-360); | 
| jbe@16 | 718       else if (lon_dir1>0 && lon12<lon_break1) lon12 = pgl_round(lon12+360); | 
| jbe@16 | 719       /* skip degenerated edges */ | 
| jbe@16 | 720       if (lat11 == lat12 && lon11 == lon12) continue; | 
| jbe@16 | 721       /* iterate over all entries of cluster2 */ | 
| jbe@16 | 722       for (i2=0; i2<cluster2->nentries; i2++) { | 
| jbe@16 | 723         /* get points and number of points of entry in cluster2 */ | 
| jbe@16 | 724         npoints2 = cluster2->entries[i2].npoints; | 
| jbe@16 | 725         if (npoints2 < 2) continue; | 
| jbe@16 | 726         entrytype2 = cluster2->entries[i2].entrytype; | 
| jbe@16 | 727         points2 = PGL_ENTRY_POINTS(cluster2, i2); | 
| jbe@16 | 728         /* determine east/west orientation of first point and calculate antipodal | 
| jbe@16 | 729            longitude */ | 
| jbe@16 | 730         lon_break2 = points2[0].lon; | 
| jbe@16 | 731         if (lon_break2 < 0) { | 
| jbe@16 | 732           lon_dir2   = -1; | 
| jbe@16 | 733           lon_break2 = pgl_round(lon_break2 + 180); | 
| jbe@16 | 734         } else if (lon_break2 > 0) { | 
| jbe@16 | 735           lon_dir2   = 1; | 
| jbe@16 | 736           lon_break2 = pgl_round(lon_break2 - 180); | 
| jbe@16 | 737         } else lon_dir2 = 0; | 
| jbe@16 | 738         /* iterate over all edges and vertices in cluster2 */ | 
| jbe@16 | 739         for (j2=0; j2<npoints2; j2++) { | 
| jbe@16 | 740           /* calculate index of next vertex */ | 
| jbe@16 | 741           k2 = (j2+1) % npoints2; | 
| jbe@16 | 742           /* skip last edge unless entry is (closed) outline or polygon */ | 
| jbe@16 | 743           if ( | 
| jbe@16 | 744             k2 == 0 && | 
| jbe@16 | 745             entrytype2 != PGL_ENTRY_OUTLINE && | 
| jbe@16 | 746             entrytype2 != PGL_ENTRY_POLYGON | 
| jbe@16 | 747           ) continue; | 
| jbe@16 | 748           /* use previously calculated values for lat1 and lon1 if possible */ | 
| jbe@16 | 749           if (j2) { | 
| jbe@16 | 750             lat21 = lat22; | 
| jbe@16 | 751             lon21 = lon22; | 
| jbe@16 | 752           } else { | 
| jbe@16 | 753             /* otherwise get latitude and longitude values of first vertex */ | 
| jbe@16 | 754             lat21 = points2[0].lat; | 
| jbe@16 | 755             lon21 = points2[0].lon; | 
| jbe@16 | 756             /* and consider longitude wrap-around for first vertex */ | 
| jbe@16 | 757             if      (lon_dir2<0 && lon21>lon_break2) lon21 = pgl_round(lon21-360); | 
| jbe@16 | 758             else if (lon_dir2>0 && lon21<lon_break2) lon21 = pgl_round(lon21+360); | 
| jbe@16 | 759           } | 
| jbe@16 | 760           /* get latitude and longitude of next vertex */ | 
| jbe@16 | 761           lat22 = points2[k2].lat; | 
| jbe@16 | 762           lon22 = points2[k2].lon; | 
| jbe@16 | 763           /* consider longitude wrap-around for next vertex */ | 
| jbe@16 | 764           if      (lon_dir2<0 && lon22>lon_break2) lon22 = pgl_round(lon22-360); | 
| jbe@16 | 765           else if (lon_dir2>0 && lon22<lon_break2) lon22 = pgl_round(lon22+360); | 
| jbe@16 | 766           /* skip degenerated edges */ | 
| jbe@16 | 767           if (lat21 == lat22 && lon21 == lon22) continue; | 
| jbe@16 | 768           /* perform another wrap-around where necessary */ | 
| jbe@16 | 769           /* TODO: improve performance of whole wrap-around mechanism */ | 
| jbe@16 | 770           wrapvalue = (lon21 + lon22) - (lon11 + lon12); | 
| jbe@16 | 771           if (wrapvalue > 360) { | 
| jbe@16 | 772             lon21 = pgl_round(lon21 - 360); | 
| jbe@16 | 773             lon22 = pgl_round(lon22 - 360); | 
| jbe@16 | 774           } else if (wrapvalue < -360) { | 
| jbe@16 | 775             lon21 = pgl_round(lon21 + 360); | 
| jbe@16 | 776             lon22 = pgl_round(lon22 + 360); | 
| jbe@16 | 777           } | 
| jbe@16 | 778           /* return true if segments overlap */ | 
| jbe@16 | 779           if ( | 
| jbe@16 | 780             pgl_lseg_crosses_line( | 
| jbe@16 | 781               lat11, lon11, lat12, lon12, | 
| jbe@20 | 782               lat21, lon21, lat22, lon22 | 
| jbe@16 | 783             ) && pgl_lseg_crosses_line( | 
| jbe@16 | 784               lat21, lon21, lat22, lon22, | 
| jbe@20 | 785               lat11, lon11, lat12, lon12 | 
| jbe@16 | 786             ) | 
| jbe@16 | 787           ) { | 
| jbe@16 | 788             return true; | 
| jbe@16 | 789           } | 
| jbe@16 | 790         } | 
| jbe@16 | 791       } | 
| jbe@16 | 792     } | 
| jbe@16 | 793   } | 
| jbe@16 | 794   /* otherwise return false */ | 
| jbe@16 | 795   return false; | 
| jbe@16 | 796 } | 
| jbe@16 | 797 | 
| jbe@16 | 798 /* check if second cluster is completely contained in first cluster */ | 
| jbe@16 | 799 static bool pgl_cluster_in_cluster(pgl_cluster *outer, pgl_cluster *inner) { | 
| jbe@20 | 800   if (!pgl_all_cluster_points_strictly_in_cluster(outer, inner)) return false; | 
| jbe@20 | 801   if (pgl_any_cluster_points_in_cluster(inner, outer)) return false; | 
| jbe@20 | 802   if (pgl_outlines_overlap(outer, inner)) return false; | 
| jbe@16 | 803   return true; | 
| jbe@16 | 804 } | 
| jbe@16 | 805 | 
| jbe@16 | 806 /* check if two clusters overlap */ | 
| jbe@16 | 807 static bool pgl_clusters_overlap( | 
| jbe@16 | 808   pgl_cluster *cluster1, pgl_cluster *cluster2 | 
| jbe@16 | 809 ) { | 
| jbe@16 | 810   if (pgl_any_cluster_points_in_cluster(cluster1, cluster2)) return true; | 
| jbe@16 | 811   if (pgl_any_cluster_points_in_cluster(cluster2, cluster1)) return true; | 
| jbe@20 | 812   if (pgl_outlines_overlap(cluster1, cluster2)) return true; | 
| jbe@16 | 813   return false; | 
| jbe@16 | 814 } | 
| jbe@16 | 815 | 
| jbe@0 | 816 /* calculate (approximate) distance between point and cluster */ | 
| jbe@0 | 817 static double pgl_point_cluster_distance(pgl_point *point, pgl_cluster *cluster) { | 
| jbe@24 | 818   double comp;           /* square of compression of meridians */ | 
| jbe@0 | 819   int i, j, k;  /* i: entry, j: point in entry, k: next point in entry */ | 
| jbe@0 | 820   int entrytype;         /* type of entry */ | 
| jbe@0 | 821   int npoints;           /* number of points in entry */ | 
| jbe@0 | 822   pgl_point *points;     /* array of points in entry */ | 
| jbe@0 | 823   int lon_dir = 0;       /* first vertex west (-1) or east (+1) */ | 
| jbe@0 | 824   double lon_break = 0;  /* antipodal longitude of first vertex */ | 
| jbe@0 | 825   double lon_min = 0;    /* minimum (adjusted) longitude of entry vertices */ | 
| jbe@0 | 826   double lon_max = 0;    /* maximum (adjusted) longitude of entry vertices */ | 
| jbe@0 | 827   double lat0 = point->lat;  /* latitude of point */ | 
| jbe@0 | 828   double lon0;           /* (adjusted) longitude of point */ | 
| jbe@0 | 829   double lat1, lon1;     /* latitude and (adjusted) longitude of vertex */ | 
| jbe@0 | 830   double lat2, lon2;     /* latitude and (adjusted) longitude of next vertex */ | 
| jbe@0 | 831   double s;              /* scalar for vector calculations */ | 
| jbe@0 | 832   double dist;           /* distance calculated in one step */ | 
| jbe@0 | 833   double min_dist = INFINITY;   /* minimum distance */ | 
| jbe@0 | 834   /* distance is zero if point is contained in cluster */ | 
| jbe@20 | 835   if (pgl_point_in_cluster(point, cluster, false)) return 0; | 
| jbe@30 | 836   /* calculate approximate square compression of meridians */ | 
| jbe@24 | 837   comp = cos((lat0 / 180.0) * M_PI); | 
| jbe@24 | 838   comp *= comp; | 
| jbe@30 | 839   /* calculate exact square compression of meridians */ | 
| jbe@30 | 840   comp *= ( | 
| jbe@30 | 841     (1.0 - PGL_EPS2 * (1.0-comp)) * | 
| jbe@30 | 842     (1.0 - PGL_EPS2 * (1.0-comp)) / | 
| jbe@30 | 843     (PGL_SUBEPS2 * PGL_SUBEPS2) | 
| jbe@30 | 844   ); | 
| jbe@0 | 845   /* iterate over all entries */ | 
| jbe@0 | 846   for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 847     /* get properties of entry */ | 
| jbe@0 | 848     entrytype = cluster->entries[i].entrytype; | 
| jbe@0 | 849     npoints = cluster->entries[i].npoints; | 
| jbe@0 | 850     points = PGL_ENTRY_POINTS(cluster, i); | 
| jbe@0 | 851     /* determine east/west orientation of first point of entry and calculate | 
| jbe@0 | 852        antipodal longitude */ | 
| jbe@0 | 853     lon_break = points[0].lon; | 
| jbe@0 | 854     if      (lon_break < 0) { lon_dir = -1; lon_break += 180; } | 
| jbe@0 | 855     else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; } | 
| jbe@0 | 856     else lon_dir = 0; | 
| jbe@0 | 857     /* determine covered longitude range */ | 
| jbe@0 | 858     for (j=0; j<npoints; j++) { | 
| jbe@0 | 859       /* get longitude of vertex */ | 
| jbe@0 | 860       lon1 = points[j].lon; | 
| jbe@0 | 861       /* adjust longitude to fix potential wrap-around */ | 
| jbe@0 | 862       if      (lon_dir < 0 && lon1 > lon_break) lon1 -= 360; | 
| jbe@0 | 863       else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360; | 
| jbe@0 | 864       /* update minimum and maximum longitude of polygon */ | 
| jbe@0 | 865       if (j == 0 || lon1 < lon_min) lon_min = lon1; | 
| jbe@0 | 866       if (j == 0 || lon1 > lon_max) lon_max = lon1; | 
| jbe@0 | 867     } | 
| jbe@0 | 868     /* adjust longitude wrap-around according to full longitude range */ | 
| jbe@0 | 869     lon_break = (lon_max + lon_min) / 2; | 
| jbe@0 | 870     if      (lon_break < 0) { lon_dir = -1; lon_break += 180; } | 
| jbe@0 | 871     else if (lon_break > 0) { lon_dir =  1; lon_break -= 180; } | 
| jbe@0 | 872     /* get longitude of point */ | 
| jbe@0 | 873     lon0 = point->lon; | 
| jbe@0 | 874     /* consider longitude wrap-around for point */ | 
| jbe@0 | 875     if      (lon_dir < 0 && lon0 > lon_break) lon0 -= 360; | 
| jbe@0 | 876     else if (lon_dir > 0 && lon0 < lon_break) lon0 += 360; | 
| jbe@0 | 877     /* iterate over all edges and vertices */ | 
| jbe@0 | 878     for (j=0; j<npoints; j++) { | 
| jbe@16 | 879       /* use previously calculated values for lat1 and lon1 if possible */ | 
| jbe@16 | 880       if (j) { | 
| jbe@16 | 881         lat1 = lat2; | 
| jbe@16 | 882         lon1 = lon2; | 
| jbe@16 | 883       } else { | 
| jbe@16 | 884         /* otherwise get latitude and longitude values of first vertex */ | 
| jbe@16 | 885         lat1 = points[0].lat; | 
| jbe@16 | 886         lon1 = points[0].lon; | 
| jbe@16 | 887         /* and consider longitude wrap-around for first vertex */ | 
| jbe@16 | 888         if      (lon_dir < 0 && lon1 > lon_break) lon1 -= 360; | 
| jbe@16 | 889         else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360; | 
| jbe@16 | 890       } | 
| jbe@0 | 891       /* calculate distance to vertex */ | 
| jbe@0 | 892       dist = pgl_distance(lat0, lon0, lat1, lon1); | 
| jbe@0 | 893       /* store calculated distance if smallest */ | 
| jbe@0 | 894       if (dist < min_dist) min_dist = dist; | 
| jbe@0 | 895       /* calculate index of next vertex */ | 
| jbe@0 | 896       k = (j+1) % npoints; | 
| jbe@0 | 897       /* skip last edge unless entry is (closed) outline or polygon */ | 
| jbe@0 | 898       if ( | 
| jbe@0 | 899         k == 0 && | 
| jbe@0 | 900         entrytype != PGL_ENTRY_OUTLINE && | 
| jbe@0 | 901         entrytype != PGL_ENTRY_POLYGON | 
| jbe@0 | 902       ) continue; | 
| jbe@16 | 903       /* get latitude and longitude of next vertex */ | 
| jbe@0 | 904       lat2 = points[k].lat; | 
| jbe@0 | 905       lon2 = points[k].lon; | 
| jbe@16 | 906       /* consider longitude wrap-around for next vertex */ | 
| jbe@0 | 907       if      (lon_dir < 0 && lon2 > lon_break) lon2 -= 360; | 
| jbe@0 | 908       else if (lon_dir > 0 && lon2 < lon_break) lon2 += 360; | 
| jbe@0 | 909       /* go to next vertex and edge if edge is degenerated */ | 
| jbe@0 | 910       if (lat1 == lat2 && lon1 == lon2) continue; | 
| jbe@0 | 911       /* otherwise test if point can be projected onto edge of polygon */ | 
| jbe@0 | 912       s = ( | 
| jbe@24 | 913         ((lat0-lat1) * (lat2-lat1) + comp * (lon0-lon1) * (lon2-lon1)) / | 
| jbe@24 | 914         ((lat2-lat1) * (lat2-lat1) + comp * (lon2-lon1) * (lon2-lon1)) | 
| jbe@0 | 915       ); | 
| jbe@0 | 916       /* go to next vertex and edge if point cannot be projected */ | 
| jbe@0 | 917       if (!(s > 0 && s < 1)) continue; | 
| jbe@0 | 918       /* calculate distance from original point to projected point */ | 
| jbe@0 | 919       dist = pgl_distance( | 
| jbe@0 | 920         lat0, lon0, | 
| jbe@0 | 921         lat1 + s * (lat2-lat1), | 
| jbe@0 | 922         lon1 + s * (lon2-lon1) | 
| jbe@0 | 923       ); | 
| jbe@0 | 924       /* store calculated distance if smallest */ | 
| jbe@0 | 925       if (dist < min_dist) min_dist = dist; | 
| jbe@0 | 926     } | 
| jbe@0 | 927   } | 
| jbe@0 | 928   /* return minimum distance */ | 
| jbe@0 | 929   return min_dist; | 
| jbe@0 | 930 } | 
| jbe@0 | 931 | 
| jbe@16 | 932 /* calculate (approximate) distance between two clusters */ | 
| jbe@16 | 933 static double pgl_cluster_distance(pgl_cluster *cluster1, pgl_cluster *cluster2) { | 
| jbe@16 | 934   int i, j;                    /* i: entry, j: point in entry */ | 
| jbe@16 | 935   int npoints;                 /* number of points in entry */ | 
| jbe@16 | 936   pgl_point *points;           /* array of points in entry */ | 
| jbe@16 | 937   double dist;                 /* distance calculated in one step */ | 
| jbe@16 | 938   double min_dist = INFINITY;  /* minimum distance */ | 
| jbe@16 | 939   /* consider distance from each point in one cluster to the whole other */ | 
| jbe@16 | 940   for (i=0; i<cluster1->nentries; i++) { | 
| jbe@16 | 941     npoints = cluster1->entries[i].npoints; | 
| jbe@16 | 942     points = PGL_ENTRY_POINTS(cluster1, i); | 
| jbe@16 | 943     for (j=0; j<npoints; j++) { | 
| jbe@16 | 944       dist = pgl_point_cluster_distance(points+j, cluster2); | 
| jbe@16 | 945       if (dist == 0) return dist; | 
| jbe@16 | 946       if (dist < min_dist) min_dist = dist; | 
| jbe@16 | 947     } | 
| jbe@16 | 948   } | 
| jbe@16 | 949   /* consider distance from each point in other cluster to the first cluster */ | 
| jbe@16 | 950   for (i=0; i<cluster2->nentries; i++) { | 
| jbe@16 | 951     npoints = cluster2->entries[i].npoints; | 
| jbe@16 | 952     points = PGL_ENTRY_POINTS(cluster2, i); | 
| jbe@16 | 953     for (j=0; j<npoints; j++) { | 
| jbe@16 | 954       dist = pgl_point_cluster_distance(points+j, cluster1); | 
| jbe@16 | 955       if (dist == 0) return dist; | 
| jbe@16 | 956       if (dist < min_dist) min_dist = dist; | 
| jbe@16 | 957     } | 
| jbe@16 | 958   } | 
| jbe@16 | 959   return min_dist; | 
| jbe@16 | 960 } | 
| jbe@16 | 961 | 
| jbe@0 | 962 /* estimator function for distance between box and point */ | 
| jbe@16 | 963 /* always returns a smaller value than actually correct or zero */ | 
| jbe@0 | 964 static double pgl_estimate_point_box_distance(pgl_point *point, pgl_box *box) { | 
| jbe@16 | 965   double dlon;      /* longitude range of box (delta longitude) */ | 
| jbe@16 | 966   double distance;  /* return value */ | 
| jbe@16 | 967   /* return infinity if box is empty */ | 
| jbe@0 | 968   if (box->lat_min > box->lat_max) return INFINITY; | 
| jbe@16 | 969   /* return zero if point is inside box */ | 
| jbe@0 | 970   if (pgl_point_in_box(point, box)) return 0; | 
| jbe@0 | 971   /* calculate delta longitude */ | 
| jbe@0 | 972   dlon = box->lon_max - box->lon_min; | 
| jbe@0 | 973   if (dlon < 0) dlon += 360;  /* 180th meridian crossed */ | 
| jbe@16 | 974   /* if delta longitude is greater than 150 degrees, perform safe fall-back */ | 
| jbe@16 | 975   if (dlon > 150) return 0; | 
| jbe@16 | 976   /* calculate lower limit for distance (formula below requires dlon <= 150) */ | 
| jbe@16 | 977   /* TODO: provide better estimation function to improve performance */ | 
| jbe@16 | 978   distance = ( | 
| jbe@46 | 979     (1.0-PGL_SPHEROID_F) *  /* safety margin due to flattening and approx. */ | 
| jbe@46 | 980     pgl_distance( | 
| jbe@16 | 981       point->lat, | 
| jbe@16 | 982       point->lon, | 
| jbe@16 | 983       (box->lat_min + box->lat_max) / 2, | 
| jbe@16 | 984       box->lon_min + dlon/2 | 
| jbe@16 | 985     ) | 
| jbe@46 | 986   ) - pgl_distance( | 
| jbe@46 | 987     box->lat_min, box->lon_min, | 
| jbe@46 | 988     box->lat_max, box->lon_max | 
| jbe@16 | 989   ); | 
| jbe@16 | 990   /* truncate negative results to zero */ | 
| jbe@16 | 991   if (distance <= 0) distance = 0; | 
| jbe@16 | 992   /* return result */ | 
| jbe@16 | 993   return distance; | 
| jbe@0 | 994 } | 
| jbe@0 | 995 | 
| jbe@0 | 996 | 
| jbe@45 | 997 /*------------------------------------------------------------* | 
| jbe@45 | 998  *  Functions using numerical integration (Monte Carlo like)  * | 
| jbe@45 | 999  *------------------------------------------------------------*/ | 
| jbe@42 | 1000 | 
| jbe@42 | 1001 /* half of (spherical) earth's surface area */ | 
| jbe@42 | 1002 #define PGL_HALF_SURFACE (PGL_RADIUS * PGL_DIAMETER * M_PI) | 
| jbe@42 | 1003 | 
| jbe@42 | 1004 /* golden angle in radians */ | 
| jbe@42 | 1005 #define PGL_GOLDEN_ANGLE (M_PI * (sqrt(5) - 1.0)) | 
| jbe@42 | 1006 | 
| jbe@42 | 1007 /* create a list of sample points covering a bounding circle | 
| jbe@42 | 1008    and return covered area */ | 
| jbe@42 | 1009 static double pgl_sample_points( | 
| jbe@42 | 1010   pgl_point *center,  /* center of bounding circle */ | 
| jbe@42 | 1011   double radius,      /* radius of bounding circle */ | 
| jbe@46 | 1012   int samples,        /* number of sample points (MUST be positive!) */ | 
| jbe@42 | 1013   pgl_point *result   /* pointer to result array */ | 
| jbe@42 | 1014 ) { | 
| jbe@42 | 1015   double double_share = 2.0;  /* double of covered share of earth's surface */ | 
| jbe@42 | 1016   double double_share_div_samples;  /* double_share divided by sample count */ | 
| jbe@42 | 1017   int i; | 
| jbe@42 | 1018   double t;  /* parameter of spiral laid on (spherical) earth's surface */ | 
| jbe@42 | 1019   double x, y, z;  /* normalized coordinates of point on non-rotated spiral */ | 
| jbe@42 | 1020   double sin_phi;  /* sine of sph. coordinate of point of non-rotated spiral */ | 
| jbe@42 | 1021   double lambda;   /* other sph. coordinate of point of non-rotated spiral */ | 
| jbe@42 | 1022   double rot = (0.5 - center->lat / 180.0) * M_PI;  /* needed rot. (in rad) */ | 
| jbe@42 | 1023   double cos_rot = cos(rot);  /* cosine of rotation by latitude */ | 
| jbe@42 | 1024   double sin_rot = sin(rot);  /* sine of rotation by latitude */ | 
| jbe@42 | 1025   double x_rot, z_rot;  /* normalized coordinates of point on rotated spiral */ | 
| jbe@42 | 1026   double center_lon = center->lon;  /* second rotation in degree */ | 
| jbe@42 | 1027   /* add safety margin to bounding circle because of spherical approximation */ | 
| jbe@42 | 1028   radius *= PGL_SPHEROID_A / PGL_RADIUS; | 
| jbe@42 | 1029   /* if whole earth is covered, use initialized value, otherwise calculate | 
| jbe@42 | 1030      share of covered area (multiplied by 2) */ | 
| jbe@42 | 1031   if (radius < PGL_MAXDIST) double_share = 1.0 - cos(radius / PGL_RADIUS); | 
| jbe@42 | 1032   /* divide double_share by sample count for later calculations */ | 
| jbe@42 | 1033   double_share_div_samples = double_share / samples; | 
| jbe@42 | 1034   /* generate sample points */ | 
| jbe@42 | 1035   for (i=0; i<samples; i++) { | 
| jbe@42 | 1036     /* use an offset of 1/2 to avoid too dense clustering at spiral center */ | 
| jbe@42 | 1037     t = 0.5 + i; | 
| jbe@42 | 1038     /* calculate normalized coordinates of point on non-rotated spiral */ | 
| jbe@42 | 1039     z = 1.0 - double_share_div_samples * t; | 
| jbe@42 | 1040     sin_phi = sqrt(1.0 - z*z); | 
| jbe@42 | 1041     lambda = t * PGL_GOLDEN_ANGLE; | 
| jbe@42 | 1042     x = sin_phi * cos(lambda); | 
| jbe@42 | 1043     y = sin_phi * sin(lambda); | 
| jbe@42 | 1044     /* rotate spiral by latitude value of bounding circle */ | 
| jbe@42 | 1045     x_rot = cos_rot * x + sin_rot * z; | 
| jbe@42 | 1046     z_rot = cos_rot * z - sin_rot * x; | 
| jbe@42 | 1047     /* set resulting sample point in result array */ | 
| jbe@42 | 1048     /* (while performing second rotation by bounding circle longitude) */ | 
| jbe@42 | 1049     result[i].lat = 180.0 * (atan(z_rot / fabs(x_rot)) / M_PI); | 
| jbe@42 | 1050     result[i].lon = center_lon + 180.0 * (atan2(y, x_rot) / M_PI); | 
| jbe@42 | 1051   } | 
| jbe@42 | 1052   /* return covered area */ | 
| jbe@42 | 1053   return PGL_HALF_SURFACE * double_share; | 
| jbe@42 | 1054 } | 
| jbe@42 | 1055 | 
| jbe@42 | 1056 /* fair distance between point and cluster (see README file for explanation) */ | 
| jbe@46 | 1057 /* NOTE: sample count passed as third argument MUST be positive */ | 
| jbe@42 | 1058 static double pgl_fair_distance( | 
| jbe@42 | 1059   pgl_point *point, pgl_cluster *cluster, int samples | 
| jbe@42 | 1060 ) { | 
| jbe@42 | 1061   double distance;       /* shortest distance from point to cluster */ | 
| jbe@46 | 1062   pgl_point *points;     /* sample points for numerical integration */ | 
| jbe@42 | 1063   double area;           /* area covered by sample points */ | 
| jbe@42 | 1064   int i; | 
| jbe@46 | 1065   int inner = 0;         /* number of sample points within cluster */ | 
| jbe@46 | 1066   int outer = 0;         /* number of sample points outside cluster but | 
| jbe@46 | 1067                             within cluster enlarged by distance */ | 
| jbe@46 | 1068   double result; | 
| jbe@42 | 1069   /* calculate shortest distance from point to cluster */ | 
| jbe@42 | 1070   distance = pgl_point_cluster_distance(point, cluster); | 
| jbe@42 | 1071   /* if cluster consists of a single point or has no bounding circle with | 
| jbe@42 | 1072       positive radius, simply return distance */ | 
| jbe@42 | 1073   if ( | 
| jbe@42 | 1074     (cluster->nentries==1 && cluster->entries[0].entrytype==PGL_ENTRY_POINT) || | 
| jbe@42 | 1075     !(cluster->bounding.radius > 0) | 
| jbe@42 | 1076   ) return distance; | 
| jbe@42 | 1077   /* if cluster consists of two points which are twice as far apart, return | 
| jbe@42 | 1078      distance between point and cluster multiplied by square root of two */ | 
| jbe@42 | 1079   if ( | 
| jbe@42 | 1080     cluster->nentries == 2 && | 
| jbe@42 | 1081     cluster->entries[0].entrytype == PGL_ENTRY_POINT && | 
| jbe@42 | 1082     cluster->entries[1].entrytype == PGL_ENTRY_POINT && | 
| jbe@42 | 1083     pgl_distance( | 
| jbe@42 | 1084       PGL_ENTRY_POINTS(cluster, 0)[0].lat, | 
| jbe@42 | 1085       PGL_ENTRY_POINTS(cluster, 0)[0].lon, | 
| jbe@42 | 1086       PGL_ENTRY_POINTS(cluster, 1)[0].lat, | 
| jbe@42 | 1087       PGL_ENTRY_POINTS(cluster, 1)[0].lon | 
| jbe@42 | 1088     ) >= 2.0 * distance | 
| jbe@42 | 1089   ) { | 
| jbe@42 | 1090     return distance * M_SQRT2; | 
| jbe@42 | 1091   } | 
| jbe@46 | 1092   /* otherwise create sample points for numerical integration and determine | 
| jbe@46 | 1093      area covered by sample points */ | 
| jbe@42 | 1094   points = palloc(samples * sizeof(pgl_point)); | 
| jbe@42 | 1095   area = pgl_sample_points( | 
| jbe@42 | 1096     &cluster->bounding.center, | 
| jbe@42 | 1097     cluster->bounding.radius + distance,  /* pad bounding circle by distance */ | 
| jbe@42 | 1098     samples, | 
| jbe@42 | 1099     points | 
| jbe@42 | 1100   ); | 
| jbe@46 | 1101   /* perform numerical integration */ | 
| jbe@42 | 1102   if (distance > 0) { | 
| jbe@46 | 1103     /* point (that was passed as argument) is outside cluster */ | 
| jbe@42 | 1104     for (i=0; i<samples; i++) { | 
| jbe@46 | 1105       /* count sample points within cluster */ | 
| jbe@46 | 1106       if (pgl_point_in_cluster(points+i, cluster, true)) inner++; | 
| jbe@46 | 1107       /* count sample points outside of cluster but within cluster enlarged by | 
| jbe@46 | 1108          distance between point (that was passed as argument) and cluster */ | 
| jbe@42 | 1109       else if ( | 
| jbe@42 | 1110         pgl_point_cluster_distance(points+i, cluster) < distance | 
| jbe@46 | 1111       ) outer++; | 
| jbe@42 | 1112     } | 
| jbe@42 | 1113   } else { | 
| jbe@46 | 1114     /* if point is within cluster, just count sample points within cluster */ | 
| jbe@42 | 1115     for (i=0; i<samples; i++) { | 
| jbe@46 | 1116       if (pgl_point_in_cluster(points+i, cluster, true)) inner++; | 
| jbe@42 | 1117     } | 
| jbe@42 | 1118   } | 
| jbe@46 | 1119   /* release memory for sample points needed for numerical integration */ | 
| jbe@42 | 1120   pfree(points); | 
| jbe@46 | 1121   /* if enlargement was less than doubling the area, then combine inner and | 
| jbe@46 | 1122      outer sample point counts with different weighting */ | 
| jbe@46 | 1123   /* (ensures fairness in such a way that the integral of the squared result | 
| jbe@46 | 1124      over all possible point parameters is independent of the cluster) */ | 
| jbe@46 | 1125   if (outer < inner) result = (2*inner + 4*outer) / 3.0; | 
| jbe@46 | 1126   /* otherwise weigh inner and outer points the same */ | 
| jbe@46 | 1127   else result = inner + outer; | 
| jbe@46 | 1128   /* convert area into distance (i.e. radius of a circle with the same area) */ | 
| jbe@46 | 1129   result = sqrt(area * (result / samples) / M_PI); | 
| jbe@42 | 1130   /* return result only if it is greater than the distance between point and | 
| jbe@42 | 1131      cluster to avoid unexpected results because of errors due to limited | 
| jbe@42 | 1132      precision */ | 
| jbe@42 | 1133   if (result > distance) return result; | 
| jbe@42 | 1134   /* otherwise return distance between point and cluster */ | 
| jbe@42 | 1135   else return distance; | 
| jbe@42 | 1136 } | 
| jbe@42 | 1137 | 
| jbe@42 | 1138 | 
| jbe@16 | 1139 /*-------------------------------------------------* | 
| jbe@16 | 1140  *  geographic index based on space-filling curve  * | 
| jbe@16 | 1141  *-------------------------------------------------*/ | 
| jbe@0 | 1142 | 
| jbe@0 | 1143 /* number of bytes used for geographic (center) position in keys */ | 
| jbe@0 | 1144 #define PGL_KEY_LATLON_BYTELEN 7 | 
| jbe@0 | 1145 | 
| jbe@0 | 1146 /* maximum reference value for logarithmic size of geographic objects */ | 
| jbe@0 | 1147 #define PGL_AREAKEY_REFOBJSIZE (PGL_DIAMETER/3.0)  /* can be tweaked */ | 
| jbe@0 | 1148 | 
| jbe@0 | 1149 /* pointer to index key (either pgl_pointkey or pgl_areakey) */ | 
| jbe@0 | 1150 typedef unsigned char *pgl_keyptr; | 
| jbe@0 | 1151 | 
| jbe@0 | 1152 /* index key for points (objects with zero area) on the spheroid */ | 
| jbe@0 | 1153 /* bit  0..55: interspersed bits of latitude and longitude, | 
| jbe@0 | 1154    bit 56..57: always zero, | 
| jbe@0 | 1155    bit 58..63: node depth in hypothetic (full) tree from 0 to 56 (incl.) */ | 
| jbe@0 | 1156 typedef unsigned char pgl_pointkey[PGL_KEY_LATLON_BYTELEN+1]; | 
| jbe@0 | 1157 | 
| jbe@0 | 1158 /* index key for geographic objects on spheroid with area greater than zero */ | 
| jbe@0 | 1159 /* bit  0..55: interspersed bits of latitude and longitude of center point, | 
| jbe@0 | 1160    bit     56: always set to 1, | 
| jbe@0 | 1161    bit 57..63: node depth in hypothetic (full) tree from 0 to (2*56)+1 (incl.), | 
| jbe@0 | 1162    bit 64..71: logarithmic object size from 0 to 56+1 = 57 (incl.), but set to | 
| jbe@0 | 1163                PGL_KEY_OBJSIZE_EMPTY (with interspersed bits = 0 and node depth | 
| jbe@0 | 1164                = 113) for empty objects, and set to PGL_KEY_OBJSIZE_UNIVERSAL | 
| jbe@0 | 1165                (with interspersed bits = 0 and node depth = 0) for keys which | 
| jbe@0 | 1166                cover both empty and non-empty objects */ | 
| jbe@0 | 1167 | 
| jbe@0 | 1168 typedef unsigned char pgl_areakey[PGL_KEY_LATLON_BYTELEN+2]; | 
| jbe@0 | 1169 | 
| jbe@0 | 1170 /* helper macros for reading/writing index keys */ | 
| jbe@0 | 1171 #define PGL_KEY_NODEDEPTH_OFFSET  PGL_KEY_LATLON_BYTELEN | 
| jbe@0 | 1172 #define PGL_KEY_OBJSIZE_OFFSET    (PGL_KEY_NODEDEPTH_OFFSET+1) | 
| jbe@0 | 1173 #define PGL_POINTKEY_MAXDEPTH     (PGL_KEY_LATLON_BYTELEN*8) | 
| jbe@0 | 1174 #define PGL_AREAKEY_MAXDEPTH      (2*PGL_POINTKEY_MAXDEPTH+1) | 
| jbe@0 | 1175 #define PGL_AREAKEY_MAXOBJSIZE    (PGL_POINTKEY_MAXDEPTH+1) | 
| jbe@0 | 1176 #define PGL_AREAKEY_TYPEMASK      0x80 | 
| jbe@0 | 1177 #define PGL_KEY_LATLONBIT(key, n) ((key)[(n)/8] & (0x80 >> ((n)%8))) | 
| jbe@0 | 1178 #define PGL_KEY_LATLONBIT_DIFF(key1, key2, n) \ | 
| jbe@0 | 1179                                   ( PGL_KEY_LATLONBIT(key1, n) ^ \ | 
| jbe@0 | 1180                                     PGL_KEY_LATLONBIT(key2, n) ) | 
| jbe@0 | 1181 #define PGL_KEY_IS_AREAKEY(key)   ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \ | 
| jbe@0 | 1182                                     PGL_AREAKEY_TYPEMASK) | 
| jbe@0 | 1183 #define PGL_KEY_NODEDEPTH(key)    ((key)[PGL_KEY_NODEDEPTH_OFFSET] & \ | 
| jbe@0 | 1184                                     (PGL_AREAKEY_TYPEMASK-1)) | 
| jbe@0 | 1185 #define PGL_KEY_OBJSIZE(key)      ((key)[PGL_KEY_OBJSIZE_OFFSET]) | 
| jbe@0 | 1186 #define PGL_KEY_OBJSIZE_EMPTY     126 | 
| jbe@0 | 1187 #define PGL_KEY_OBJSIZE_UNIVERSAL 127 | 
| jbe@0 | 1188 #define PGL_KEY_IS_EMPTY(key)     ( PGL_KEY_IS_AREAKEY(key) && \ | 
| jbe@0 | 1189                                     (key)[PGL_KEY_OBJSIZE_OFFSET] == \ | 
| jbe@0 | 1190                                     PGL_KEY_OBJSIZE_EMPTY ) | 
| jbe@0 | 1191 #define PGL_KEY_IS_UNIVERSAL(key) ( PGL_KEY_IS_AREAKEY(key) && \ | 
| jbe@0 | 1192                                     (key)[PGL_KEY_OBJSIZE_OFFSET] == \ | 
| jbe@0 | 1193                                     PGL_KEY_OBJSIZE_UNIVERSAL ) | 
| jbe@0 | 1194 | 
| jbe@0 | 1195 /* set area key to match empty objects only */ | 
| jbe@0 | 1196 static void pgl_key_set_empty(pgl_keyptr key) { | 
| jbe@0 | 1197   memset(key, 0, sizeof(pgl_areakey)); | 
| jbe@0 | 1198   /* Note: setting node depth to maximum is required for picksplit function */ | 
| jbe@0 | 1199   key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH; | 
| jbe@0 | 1200   key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_EMPTY; | 
| jbe@0 | 1201 } | 
| jbe@0 | 1202 | 
| jbe@0 | 1203 /* set area key to match any object (including empty objects) */ | 
| jbe@0 | 1204 static void pgl_key_set_universal(pgl_keyptr key) { | 
| jbe@0 | 1205   memset(key, 0, sizeof(pgl_areakey)); | 
| jbe@0 | 1206   key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK; | 
| jbe@0 | 1207   key[PGL_KEY_OBJSIZE_OFFSET] = PGL_KEY_OBJSIZE_UNIVERSAL; | 
| jbe@0 | 1208 } | 
| jbe@0 | 1209 | 
| jbe@0 | 1210 /* convert a point on earth into a max-depth key to be used in index */ | 
| jbe@0 | 1211 static void pgl_point_to_key(pgl_point *point, pgl_keyptr key) { | 
| jbe@0 | 1212   double lat = point->lat; | 
| jbe@0 | 1213   double lon = point->lon; | 
| jbe@0 | 1214   int i; | 
| jbe@0 | 1215   /* clear latitude and longitude bits */ | 
| jbe@0 | 1216   memset(key, 0, PGL_KEY_LATLON_BYTELEN); | 
| jbe@0 | 1217   /* set node depth to maximum and type bit to zero */ | 
| jbe@0 | 1218   key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_POINTKEY_MAXDEPTH; | 
| jbe@0 | 1219   /* iterate over all latitude/longitude bit pairs */ | 
| jbe@0 | 1220   for (i=0; i<PGL_POINTKEY_MAXDEPTH/2; i++) { | 
| jbe@0 | 1221     /* determine latitude bit */ | 
| jbe@0 | 1222     if (lat >= 0) { | 
| jbe@0 | 1223       key[i/4] |= 0x80 >> (2*(i%4)); | 
| jbe@0 | 1224       lat *= 2; lat -= 90; | 
| jbe@0 | 1225     } else { | 
| jbe@0 | 1226       lat *= 2; lat += 90; | 
| jbe@0 | 1227     } | 
| jbe@0 | 1228     /* determine longitude bit */ | 
| jbe@0 | 1229     if (lon >= 0) { | 
| jbe@0 | 1230       key[i/4] |= 0x80 >> (2*(i%4)+1); | 
| jbe@0 | 1231       lon *= 2; lon -= 180; | 
| jbe@0 | 1232     } else { | 
| jbe@0 | 1233       lon *= 2; lon += 180; | 
| jbe@0 | 1234     } | 
| jbe@0 | 1235   } | 
| jbe@0 | 1236 } | 
| jbe@0 | 1237 | 
| jbe@0 | 1238 /* convert a circle on earth into a max-depth key to be used in an index */ | 
| jbe@0 | 1239 static void pgl_circle_to_key(pgl_circle *circle, pgl_keyptr key) { | 
| jbe@0 | 1240   /* handle special case of empty circle */ | 
| jbe@0 | 1241   if (circle->radius < 0) { | 
| jbe@0 | 1242     pgl_key_set_empty(key); | 
| jbe@0 | 1243     return; | 
| jbe@0 | 1244   } | 
| jbe@0 | 1245   /* perform same action as for point keys */ | 
| jbe@0 | 1246   pgl_point_to_key(&(circle->center), key); | 
| jbe@0 | 1247   /* but overwrite type and node depth to fit area index key */ | 
| jbe@0 | 1248   key[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | PGL_AREAKEY_MAXDEPTH; | 
| jbe@0 | 1249   /* check if radius is greater than (or equal to) reference size */ | 
| jbe@0 | 1250   /* (treat equal values as greater values for numerical safety) */ | 
| jbe@0 | 1251   if (circle->radius >= PGL_AREAKEY_REFOBJSIZE) { | 
| jbe@0 | 1252     /* if yes, set logarithmic size to zero */ | 
| jbe@0 | 1253     key[PGL_KEY_OBJSIZE_OFFSET] = 0; | 
| jbe@0 | 1254   } else { | 
| jbe@0 | 1255     /* otherwise, determine logarithmic size iteratively */ | 
| jbe@0 | 1256     /* (one step is equivalent to a factor of sqrt(2)) */ | 
| jbe@0 | 1257     double reference = PGL_AREAKEY_REFOBJSIZE / M_SQRT2; | 
| jbe@0 | 1258     int objsize = 1; | 
| jbe@0 | 1259     while (objsize < PGL_AREAKEY_MAXOBJSIZE) { | 
| jbe@0 | 1260       /* stop when radius is greater than (or equal to) adjusted reference */ | 
| jbe@0 | 1261       /* (treat equal values as greater values for numerical safety) */ | 
| jbe@0 | 1262       if (circle->radius >= reference) break; | 
| jbe@0 | 1263       reference /= M_SQRT2; | 
| jbe@0 | 1264       objsize++; | 
| jbe@0 | 1265     } | 
| jbe@0 | 1266     /* set logarithmic size to determined value */ | 
| jbe@0 | 1267     key[PGL_KEY_OBJSIZE_OFFSET] = objsize; | 
| jbe@0 | 1268   } | 
| jbe@0 | 1269 } | 
| jbe@0 | 1270 | 
| jbe@0 | 1271 /* check if one key is subkey of another key or vice versa */ | 
| jbe@0 | 1272 static bool pgl_keys_overlap(pgl_keyptr key1, pgl_keyptr key2) { | 
| jbe@0 | 1273   int i;  /* key bit offset (includes both lat/lon and log. obj. size bits) */ | 
| jbe@0 | 1274   /* determine smallest depth */ | 
| jbe@0 | 1275   int depth1 = PGL_KEY_NODEDEPTH(key1); | 
| jbe@0 | 1276   int depth2 = PGL_KEY_NODEDEPTH(key2); | 
| jbe@0 | 1277   int depth = (depth1 < depth2) ? depth1 : depth2; | 
| jbe@0 | 1278   /* check if keys are area keys (assuming that both keys have same type) */ | 
| jbe@0 | 1279   if (PGL_KEY_IS_AREAKEY(key1)) { | 
| jbe@0 | 1280     int j = 0;  /* bit offset for logarithmic object size bits */ | 
| jbe@0 | 1281     int k = 0;  /* bit offset for latitude and longitude */ | 
| jbe@0 | 1282     /* fetch logarithmic object size information */ | 
| jbe@0 | 1283     int objsize1 = PGL_KEY_OBJSIZE(key1); | 
| jbe@0 | 1284     int objsize2 = PGL_KEY_OBJSIZE(key2); | 
| jbe@0 | 1285     /* handle special cases for empty objects (universal and empty keys) */ | 
| jbe@0 | 1286     if ( | 
| jbe@0 | 1287       objsize1 == PGL_KEY_OBJSIZE_UNIVERSAL || | 
| jbe@0 | 1288       objsize2 == PGL_KEY_OBJSIZE_UNIVERSAL | 
| jbe@0 | 1289     ) return true; | 
| jbe@0 | 1290     if ( | 
| jbe@0 | 1291       objsize1 == PGL_KEY_OBJSIZE_EMPTY || | 
| jbe@0 | 1292       objsize2 == PGL_KEY_OBJSIZE_EMPTY | 
| jbe@0 | 1293     ) return objsize1 == objsize2; | 
| jbe@0 | 1294     /* iterate through key bits */ | 
| jbe@0 | 1295     for (i=0; i<depth; i++) { | 
| jbe@0 | 1296       /* every second bit is a bit describing the object size */ | 
| jbe@0 | 1297       if (i%2 == 0) { | 
| jbe@0 | 1298         /* check if object size bit is different in both keys (objsize1 and | 
| jbe@0 | 1299            objsize2 describe the minimum index when object size bit is set) */ | 
| jbe@0 | 1300         if ( | 
| jbe@0 | 1301           (objsize1 <= j && objsize2 > j) || | 
| jbe@0 | 1302           (objsize2 <= j && objsize1 > j) | 
| jbe@0 | 1303         ) { | 
| jbe@0 | 1304           /* bit differs, therefore keys are in separate branches */ | 
| jbe@0 | 1305           return false; | 
| jbe@0 | 1306         } | 
| jbe@0 | 1307         /* increase bit counter for object size bits */ | 
| jbe@0 | 1308         j++; | 
| jbe@0 | 1309       } | 
| jbe@0 | 1310       /* all other bits describe latitude and longitude */ | 
| jbe@0 | 1311       else { | 
| jbe@0 | 1312         /* check if bit differs in both keys */ | 
| jbe@0 | 1313         if (PGL_KEY_LATLONBIT_DIFF(key1, key2, k)) { | 
| jbe@0 | 1314           /* bit differs, therefore keys are in separate branches */ | 
| jbe@0 | 1315           return false; | 
| jbe@0 | 1316         } | 
| jbe@0 | 1317         /* increase bit counter for latitude/longitude bits */ | 
| jbe@0 | 1318         k++; | 
| jbe@0 | 1319       } | 
| jbe@0 | 1320     } | 
| jbe@0 | 1321   } | 
| jbe@0 | 1322   /* if not, keys are point keys */ | 
| jbe@0 | 1323   else { | 
| jbe@0 | 1324     /* iterate through key bits */ | 
| jbe@0 | 1325     for (i=0; i<depth; i++) { | 
| jbe@0 | 1326       /* check if bit differs in both keys */ | 
| jbe@0 | 1327       if (PGL_KEY_LATLONBIT_DIFF(key1, key2, i)) { | 
| jbe@0 | 1328         /* bit differs, therefore keys are in separate branches */ | 
| jbe@0 | 1329         return false; | 
| jbe@0 | 1330       } | 
| jbe@0 | 1331     } | 
| jbe@0 | 1332   } | 
| jbe@0 | 1333   /* return true because keys are in the same branch */ | 
| jbe@0 | 1334   return true; | 
| jbe@0 | 1335 } | 
| jbe@0 | 1336 | 
| jbe@0 | 1337 /* combine two keys into new key which covers both original keys */ | 
| jbe@0 | 1338 /* (result stored in first argument) */ | 
| jbe@0 | 1339 static void pgl_unite_keys(pgl_keyptr dst, pgl_keyptr src) { | 
| jbe@0 | 1340   int i;  /* key bit offset (includes both lat/lon and log. obj. size bits) */ | 
| jbe@0 | 1341   /* determine smallest depth */ | 
| jbe@0 | 1342   int depth1 = PGL_KEY_NODEDEPTH(dst); | 
| jbe@0 | 1343   int depth2 = PGL_KEY_NODEDEPTH(src); | 
| jbe@0 | 1344   int depth = (depth1 < depth2) ? depth1 : depth2; | 
| jbe@0 | 1345   /* check if keys are area keys (assuming that both keys have same type) */ | 
| jbe@0 | 1346   if (PGL_KEY_IS_AREAKEY(dst)) { | 
| jbe@0 | 1347     pgl_areakey dstbuf = { 0, };  /* destination buffer (cleared) */ | 
| jbe@0 | 1348     int j = 0;  /* bit offset for logarithmic object size bits */ | 
| jbe@0 | 1349     int k = 0;  /* bit offset for latitude and longitude */ | 
| jbe@0 | 1350     /* fetch logarithmic object size information */ | 
| jbe@0 | 1351     int objsize1 = PGL_KEY_OBJSIZE(dst); | 
| jbe@0 | 1352     int objsize2 = PGL_KEY_OBJSIZE(src); | 
| jbe@0 | 1353     /* handle special cases for empty objects (universal and empty keys) */ | 
| jbe@0 | 1354     if ( | 
| jbe@0 | 1355       objsize1 > PGL_AREAKEY_MAXOBJSIZE || | 
| jbe@0 | 1356       objsize2 > PGL_AREAKEY_MAXOBJSIZE | 
| jbe@0 | 1357     ) { | 
| jbe@0 | 1358       if ( | 
| jbe@0 | 1359         objsize1 == PGL_KEY_OBJSIZE_EMPTY && | 
| jbe@0 | 1360         objsize2 == PGL_KEY_OBJSIZE_EMPTY | 
| jbe@0 | 1361       ) pgl_key_set_empty(dst); | 
| jbe@0 | 1362       else pgl_key_set_universal(dst); | 
| jbe@0 | 1363       return; | 
| jbe@0 | 1364     } | 
| jbe@0 | 1365     /* iterate through key bits */ | 
| jbe@0 | 1366     for (i=0; i<depth; i++) { | 
| jbe@0 | 1367       /* every second bit is a bit describing the object size */ | 
| jbe@0 | 1368       if (i%2 == 0) { | 
| jbe@0 | 1369         /* increase bit counter for object size bits first */ | 
| jbe@0 | 1370         /* (handy when setting objsize variable) */ | 
| jbe@0 | 1371         j++; | 
| jbe@0 | 1372         /* check if object size bit is set in neither key */ | 
| jbe@0 | 1373         if (objsize1 >= j && objsize2 >= j) { | 
| jbe@0 | 1374           /* set objsize in destination buffer to indicate that size bit is | 
| jbe@0 | 1375              unset in destination buffer at the current bit position */ | 
| jbe@0 | 1376           dstbuf[PGL_KEY_OBJSIZE_OFFSET] = j; | 
| jbe@0 | 1377         } | 
| jbe@0 | 1378         /* break if object size bit is set in one key only */ | 
| jbe@0 | 1379         else if (objsize1 >= j || objsize2 >= j) break; | 
| jbe@0 | 1380       } | 
| jbe@0 | 1381       /* all other bits describe latitude and longitude */ | 
| jbe@0 | 1382       else { | 
| jbe@0 | 1383         /* break if bit differs in both keys */ | 
| jbe@0 | 1384         if (PGL_KEY_LATLONBIT(dst, k)) { | 
| jbe@0 | 1385           if (!PGL_KEY_LATLONBIT(src, k)) break; | 
| jbe@0 | 1386           /* but set bit in destination buffer if bit is set in both keys */ | 
| jbe@0 | 1387           dstbuf[k/8] |= 0x80 >> (k%8); | 
| jbe@0 | 1388         } else if (PGL_KEY_LATLONBIT(src, k)) break; | 
| jbe@0 | 1389         /* increase bit counter for latitude/longitude bits */ | 
| jbe@0 | 1390         k++; | 
| jbe@0 | 1391       } | 
| jbe@0 | 1392     } | 
| jbe@0 | 1393     /* set common node depth and type bit (type bit = 1) */ | 
| jbe@0 | 1394     dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = PGL_AREAKEY_TYPEMASK | i; | 
| jbe@0 | 1395     /* copy contents of destination buffer to first key */ | 
| jbe@0 | 1396     memcpy(dst, dstbuf, sizeof(pgl_areakey)); | 
| jbe@0 | 1397   } | 
| jbe@0 | 1398   /* if not, keys are point keys */ | 
| jbe@0 | 1399   else { | 
| jbe@0 | 1400     pgl_pointkey dstbuf = { 0, };  /* destination buffer (cleared) */ | 
| jbe@0 | 1401     /* iterate through key bits */ | 
| jbe@0 | 1402     for (i=0; i<depth; i++) { | 
| jbe@0 | 1403       /* break if bit differs in both keys */ | 
| jbe@0 | 1404       if (PGL_KEY_LATLONBIT(dst, i)) { | 
| jbe@0 | 1405         if (!PGL_KEY_LATLONBIT(src, i)) break; | 
| jbe@0 | 1406         /* but set bit in destination buffer if bit is set in both keys */ | 
| jbe@0 | 1407         dstbuf[i/8] |= 0x80 >> (i%8); | 
| jbe@0 | 1408       } else if (PGL_KEY_LATLONBIT(src, i)) break; | 
| jbe@0 | 1409     } | 
| jbe@0 | 1410     /* set common node depth (type bit = 0) */ | 
| jbe@0 | 1411     dstbuf[PGL_KEY_NODEDEPTH_OFFSET] = i; | 
| jbe@0 | 1412     /* copy contents of destination buffer to first key */ | 
| jbe@0 | 1413     memcpy(dst, dstbuf, sizeof(pgl_pointkey)); | 
| jbe@0 | 1414   } | 
| jbe@0 | 1415 } | 
| jbe@0 | 1416 | 
| jbe@0 | 1417 /* determine center(!) boundaries and radius estimation of index key */ | 
| jbe@0 | 1418 static double pgl_key_to_box(pgl_keyptr key, pgl_box *box) { | 
| jbe@0 | 1419   int i; | 
| jbe@0 | 1420   /* determine node depth */ | 
| jbe@0 | 1421   int depth = PGL_KEY_NODEDEPTH(key); | 
| jbe@0 | 1422   /* center point of possible result */ | 
| jbe@0 | 1423   double lat = 0; | 
| jbe@0 | 1424   double lon = 0; | 
| jbe@0 | 1425   /* maximum distance of real center point from key center */ | 
| jbe@0 | 1426   double dlat = 90; | 
| jbe@0 | 1427   double dlon = 180; | 
| jbe@0 | 1428   /* maximum radius of contained objects */ | 
| jbe@0 | 1429   double radius = 0;  /* always return zero for point index keys */ | 
| jbe@0 | 1430   /* check if key is area key */ | 
| jbe@0 | 1431   if (PGL_KEY_IS_AREAKEY(key)) { | 
| jbe@0 | 1432     /* get logarithmic object size */ | 
| jbe@0 | 1433     int objsize = PGL_KEY_OBJSIZE(key); | 
| jbe@0 | 1434     /* handle special cases for empty objects (universal and empty keys) */ | 
| jbe@0 | 1435     if (objsize == PGL_KEY_OBJSIZE_EMPTY) { | 
| jbe@0 | 1436       pgl_box_set_empty(box); | 
| jbe@0 | 1437       return 0; | 
| jbe@0 | 1438     } else if (objsize == PGL_KEY_OBJSIZE_UNIVERSAL) { | 
| jbe@0 | 1439       box->lat_min = -90; | 
| jbe@0 | 1440       box->lat_max =  90; | 
| jbe@0 | 1441       box->lon_min = -180; | 
| jbe@0 | 1442       box->lon_max =  180; | 
| jbe@0 | 1443       return 0;  /* any value >= 0 would do */ | 
| jbe@0 | 1444     } | 
| jbe@0 | 1445     /* calculate maximum possible radius of objects covered by the given key */ | 
| jbe@0 | 1446     if (objsize == 0) radius = INFINITY; | 
| jbe@0 | 1447     else { | 
| jbe@0 | 1448       radius = PGL_AREAKEY_REFOBJSIZE; | 
| jbe@0 | 1449       while (--objsize) radius /= M_SQRT2; | 
| jbe@0 | 1450     } | 
| jbe@0 | 1451     /* iterate over latitude and longitude bits in key */ | 
| jbe@0 | 1452     /* (every second bit is a latitude or longitude bit) */ | 
| jbe@0 | 1453     for (i=0; i<depth/2; i++) { | 
| jbe@0 | 1454       /* check if latitude bit */ | 
| jbe@0 | 1455       if (i%2 == 0) { | 
| jbe@0 | 1456         /* cut latitude dimension in half */ | 
| jbe@0 | 1457         dlat /= 2; | 
| jbe@0 | 1458         /* increase center latitude if bit is 1, otherwise decrease */ | 
| jbe@0 | 1459         if (PGL_KEY_LATLONBIT(key, i)) lat += dlat; | 
| jbe@0 | 1460         else lat -= dlat; | 
| jbe@0 | 1461       } | 
| jbe@0 | 1462       /* otherwise longitude bit */ | 
| jbe@0 | 1463       else { | 
| jbe@0 | 1464         /* cut longitude dimension in half */ | 
| jbe@0 | 1465         dlon /= 2; | 
| jbe@0 | 1466         /* increase center longitude if bit is 1, otherwise decrease */ | 
| jbe@0 | 1467         if (PGL_KEY_LATLONBIT(key, i)) lon += dlon; | 
| jbe@0 | 1468         else lon -= dlon; | 
| jbe@0 | 1469       } | 
| jbe@0 | 1470     } | 
| jbe@0 | 1471   } | 
| jbe@0 | 1472   /* if not, keys are point keys */ | 
| jbe@0 | 1473   else { | 
| jbe@0 | 1474     /* iterate over all bits in key */ | 
| jbe@0 | 1475     for (i=0; i<depth; i++) { | 
| jbe@0 | 1476       /* check if latitude bit */ | 
| jbe@0 | 1477       if (i%2 == 0) { | 
| jbe@0 | 1478         /* cut latitude dimension in half */ | 
| jbe@0 | 1479         dlat /= 2; | 
| jbe@0 | 1480         /* increase center latitude if bit is 1, otherwise decrease */ | 
| jbe@0 | 1481         if (PGL_KEY_LATLONBIT(key, i)) lat += dlat; | 
| jbe@0 | 1482         else lat -= dlat; | 
| jbe@0 | 1483       } | 
| jbe@0 | 1484       /* otherwise longitude bit */ | 
| jbe@0 | 1485       else { | 
| jbe@0 | 1486         /* cut longitude dimension in half */ | 
| jbe@0 | 1487         dlon /= 2; | 
| jbe@0 | 1488         /* increase center longitude if bit is 1, otherwise decrease */ | 
| jbe@0 | 1489         if (PGL_KEY_LATLONBIT(key, i)) lon += dlon; | 
| jbe@0 | 1490         else lon -= dlon; | 
| jbe@0 | 1491       } | 
| jbe@0 | 1492     } | 
| jbe@0 | 1493   } | 
| jbe@0 | 1494   /* calculate boundaries from center point and remaining dlat and dlon */ | 
| jbe@0 | 1495   /* (return values through pointer to box) */ | 
| jbe@0 | 1496   box->lat_min = lat - dlat; | 
| jbe@0 | 1497   box->lat_max = lat + dlat; | 
| jbe@0 | 1498   box->lon_min = lon - dlon; | 
| jbe@0 | 1499   box->lon_max = lon + dlon; | 
| jbe@0 | 1500   /* return radius (as a function return value) */ | 
| jbe@0 | 1501   return radius; | 
| jbe@0 | 1502 } | 
| jbe@0 | 1503 | 
| jbe@0 | 1504 /* estimator function for distance between point and index key */ | 
| jbe@16 | 1505 /* always returns a smaller value than actually correct or zero */ | 
| jbe@0 | 1506 static double pgl_estimate_key_distance(pgl_keyptr key, pgl_point *point) { | 
| jbe@0 | 1507   pgl_box box;  /* center(!) bounding box of area index key */ | 
| jbe@0 | 1508   /* calculate center(!) bounding box and maximum radius of objects covered | 
| jbe@0 | 1509      by area index key (radius is zero for point index keys) */ | 
| jbe@0 | 1510   double distance = pgl_key_to_box(key, &box); | 
| jbe@0 | 1511   /* calculate estimated distance between bounding box of center point of | 
| jbe@0 | 1512      indexed object and point passed as second argument, then substract maximum | 
| jbe@0 | 1513      radius of objects covered by index key */ | 
| jbe@16 | 1514   distance = pgl_estimate_point_box_distance(point, &box) - distance; | 
| jbe@0 | 1515   /* truncate negative results to zero */ | 
| jbe@0 | 1516   if (distance <= 0) distance = 0; | 
| jbe@0 | 1517   /* return result */ | 
| jbe@0 | 1518   return distance; | 
| jbe@0 | 1519 } | 
| jbe@0 | 1520 | 
| jbe@0 | 1521 | 
| jbe@0 | 1522 /*---------------------------------* | 
| jbe@0 | 1523  *  helper functions for text I/O  * | 
| jbe@0 | 1524  *---------------------------------*/ | 
| jbe@0 | 1525 | 
| jbe@0 | 1526 #define PGL_NUMBUFLEN 64  /* buffer size for number to string conversion */ | 
| jbe@0 | 1527 | 
| jbe@0 | 1528 /* convert floating point number to string (round-trip safe) */ | 
| jbe@0 | 1529 static void pgl_print_float(char *buf, double flt) { | 
| jbe@0 | 1530   /* check if number is integral */ | 
| jbe@0 | 1531   if (trunc(flt) == flt) { | 
| jbe@0 | 1532     /* for integral floats use maximum precision */ | 
| jbe@0 | 1533     snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt); | 
| jbe@0 | 1534   } else { | 
| jbe@0 | 1535     /* otherwise check if 15, 16, or 17 digits needed (round-trip safety) */ | 
| jbe@0 | 1536     snprintf(buf, PGL_NUMBUFLEN, "%.15g", flt); | 
| jbe@0 | 1537     if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.16g", flt); | 
| jbe@0 | 1538     if (strtod(buf, NULL) != flt) snprintf(buf, PGL_NUMBUFLEN, "%.17g", flt); | 
| jbe@0 | 1539   } | 
| jbe@0 | 1540 } | 
| jbe@0 | 1541 | 
| jbe@0 | 1542 /* convert latitude floating point number (in degrees) to string */ | 
| jbe@0 | 1543 static void pgl_print_lat(char *buf, double lat) { | 
| jbe@0 | 1544   if (signbit(lat)) { | 
| jbe@0 | 1545     /* treat negative latitudes (including -0) as south */ | 
| jbe@0 | 1546     snprintf(buf, PGL_NUMBUFLEN, "S%015.12f", -lat); | 
| jbe@0 | 1547   } else { | 
| jbe@0 | 1548     /* treat positive latitudes (including +0) as north */ | 
| jbe@0 | 1549     snprintf(buf, PGL_NUMBUFLEN, "N%015.12f", lat); | 
| jbe@0 | 1550   } | 
| jbe@0 | 1551 } | 
| jbe@0 | 1552 | 
| jbe@0 | 1553 /* convert longitude floating point number (in degrees) to string */ | 
| jbe@0 | 1554 static void pgl_print_lon(char *buf, double lon) { | 
| jbe@0 | 1555   if (signbit(lon)) { | 
| jbe@0 | 1556     /* treat negative longitudes (including -0) as west */ | 
| jbe@0 | 1557     snprintf(buf, PGL_NUMBUFLEN, "W%016.12f", -lon); | 
| jbe@0 | 1558   } else { | 
| jbe@0 | 1559     /* treat positive longitudes (including +0) as east */ | 
| jbe@0 | 1560     snprintf(buf, PGL_NUMBUFLEN, "E%016.12f", lon); | 
| jbe@0 | 1561   } | 
| jbe@0 | 1562 } | 
| jbe@0 | 1563 | 
| jbe@0 | 1564 /* bit masks used as return value of pgl_scan() function */ | 
| jbe@0 | 1565 #define PGL_SCAN_NONE 0      /* no value has been parsed */ | 
| jbe@0 | 1566 #define PGL_SCAN_LAT (1<<0)  /* latitude has been parsed */ | 
| jbe@0 | 1567 #define PGL_SCAN_LON (1<<1)  /* longitude has been parsed */ | 
| jbe@0 | 1568 #define PGL_SCAN_LATLON (PGL_SCAN_LAT | PGL_SCAN_LON)  /* bitwise OR of both */ | 
| jbe@0 | 1569 | 
| jbe@0 | 1570 /* parse a coordinate (can be latitude or longitude) */ | 
| jbe@0 | 1571 static int pgl_scan(char **str, double *lat, double *lon) { | 
| jbe@0 | 1572   double val; | 
| jbe@0 | 1573   int len; | 
| jbe@0 | 1574   if ( | 
| jbe@0 | 1575     sscanf(*str, " N %lf %n", &val, &len) || | 
| jbe@0 | 1576     sscanf(*str, " n %lf %n", &val, &len) | 
| jbe@0 | 1577   ) { | 
| jbe@0 | 1578     *str += len; *lat = val; return PGL_SCAN_LAT; | 
| jbe@0 | 1579   } | 
| jbe@0 | 1580   if ( | 
| jbe@0 | 1581     sscanf(*str, " S %lf %n", &val, &len) || | 
| jbe@0 | 1582     sscanf(*str, " s %lf %n", &val, &len) | 
| jbe@0 | 1583   ) { | 
| jbe@0 | 1584     *str += len; *lat = -val; return PGL_SCAN_LAT; | 
| jbe@0 | 1585   } | 
| jbe@0 | 1586   if ( | 
| jbe@0 | 1587     sscanf(*str, " E %lf %n", &val, &len) || | 
| jbe@0 | 1588     sscanf(*str, " e %lf %n", &val, &len) | 
| jbe@0 | 1589   ) { | 
| jbe@0 | 1590     *str += len; *lon = val; return PGL_SCAN_LON; | 
| jbe@0 | 1591   } | 
| jbe@0 | 1592   if ( | 
| jbe@0 | 1593     sscanf(*str, " W %lf %n", &val, &len) || | 
| jbe@0 | 1594     sscanf(*str, " w %lf %n", &val, &len) | 
| jbe@0 | 1595   ) { | 
| jbe@0 | 1596     *str += len; *lon = -val; return PGL_SCAN_LON; | 
| jbe@0 | 1597   } | 
| jbe@0 | 1598   return PGL_SCAN_NONE; | 
| jbe@0 | 1599 } | 
| jbe@0 | 1600 | 
| jbe@0 | 1601 | 
| jbe@0 | 1602 /*-----------------* | 
| jbe@0 | 1603  *  SQL functions  * | 
| jbe@0 | 1604  *-----------------*/ | 
| jbe@0 | 1605 | 
| jbe@0 | 1606 /* Note: These function names use "epoint", "ebox", etc. notation here instead | 
| jbe@0 | 1607    of "point", "box", etc. in order to distinguish them from any previously | 
| jbe@0 | 1608    defined functions. */ | 
| jbe@0 | 1609 | 
| jbe@0 | 1610 /* function needed for dummy types and/or not implemented features */ | 
| jbe@0 | 1611 PG_FUNCTION_INFO_V1(pgl_notimpl); | 
| jbe@0 | 1612 Datum pgl_notimpl(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1613   ereport(ERROR, (errmsg("not implemented by pgLatLon"))); | 
| jbe@0 | 1614 } | 
| jbe@0 | 1615 | 
| jbe@0 | 1616 /* set point to latitude and longitude (including checks) */ | 
| jbe@0 | 1617 static void pgl_epoint_set_latlon(pgl_point *point, double lat, double lon) { | 
| jbe@0 | 1618   /* reject infinite or NaN values */ | 
| jbe@0 | 1619   if (!isfinite(lat) || !isfinite(lon)) { | 
| jbe@0 | 1620     ereport(ERROR, ( | 
| jbe@0 | 1621       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@0 | 1622       errmsg("epoint requires finite coordinates") | 
| jbe@0 | 1623     )); | 
| jbe@0 | 1624   } | 
| jbe@0 | 1625   /* check latitude bounds */ | 
| jbe@0 | 1626   if (lat < -90) { | 
| jbe@0 | 1627     ereport(WARNING, (errmsg("latitude exceeds south pole"))); | 
| jbe@0 | 1628     lat = -90; | 
| jbe@0 | 1629   } else if (lat > 90) { | 
| jbe@0 | 1630     ereport(WARNING, (errmsg("latitude exceeds north pole"))); | 
| jbe@0 | 1631     lat = 90; | 
| jbe@0 | 1632   } | 
| jbe@0 | 1633   /* check longitude bounds */ | 
| jbe@0 | 1634   if (lon < -180) { | 
| jbe@0 | 1635     ereport(NOTICE, (errmsg("longitude west of 180th meridian normalized"))); | 
| jbe@0 | 1636     lon += 360 - trunc(lon / 360) * 360; | 
| jbe@0 | 1637   } else if (lon > 180) { | 
| jbe@0 | 1638     ereport(NOTICE, (errmsg("longitude east of 180th meridian normalized"))); | 
| jbe@0 | 1639     lon -= 360 + trunc(lon / 360) * 360; | 
| jbe@0 | 1640   } | 
| jbe@0 | 1641   /* store rounded latitude/longitude values for round-trip safety */ | 
| jbe@0 | 1642   point->lat = pgl_round(lat); | 
| jbe@0 | 1643   point->lon = pgl_round(lon); | 
| jbe@0 | 1644 } | 
| jbe@0 | 1645 | 
| jbe@0 | 1646 /* create point ("epoint" in SQL) from latitude and longitude */ | 
| jbe@0 | 1647 PG_FUNCTION_INFO_V1(pgl_create_epoint); | 
| jbe@0 | 1648 Datum pgl_create_epoint(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1649   pgl_point *point = (pgl_point *)palloc(sizeof(pgl_point)); | 
| jbe@0 | 1650   pgl_epoint_set_latlon(point, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1)); | 
| jbe@0 | 1651   PG_RETURN_POINTER(point); | 
| jbe@0 | 1652 } | 
| jbe@0 | 1653 | 
| jbe@0 | 1654 /* parse point ("epoint" in SQL) */ | 
| jbe@0 | 1655 /* format: '[NS]<float> [EW]<float>' */ | 
| jbe@0 | 1656 PG_FUNCTION_INFO_V1(pgl_epoint_in); | 
| jbe@0 | 1657 Datum pgl_epoint_in(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1658   char *str = PG_GETARG_CSTRING(0);  /* input string */ | 
| jbe@0 | 1659   char *strptr = str;  /* current position within string */ | 
| jbe@0 | 1660   int done = 0;        /* bit mask storing if latitude or longitude was read */ | 
| jbe@0 | 1661   double lat, lon;     /* parsed values as double precision floats */ | 
| jbe@0 | 1662   pgl_point *point;    /* return value (to be palloc'ed) */ | 
| jbe@0 | 1663   /* parse two floats (each latitude or longitude) separated by white-space */ | 
| jbe@0 | 1664   done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 1665   if (strptr != str && isspace(strptr[-1])) { | 
| jbe@0 | 1666     done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 1667   } | 
| jbe@0 | 1668   /* require end of string, and latitude and longitude parsed successfully */ | 
| jbe@0 | 1669   if (strptr[0] || done != PGL_SCAN_LATLON) { | 
| jbe@0 | 1670     ereport(ERROR, ( | 
| jbe@0 | 1671       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 1672       errmsg("invalid input syntax for type epoint: \"%s\"", str) | 
| jbe@0 | 1673     )); | 
| jbe@0 | 1674   } | 
| jbe@0 | 1675   /* allocate memory for result */ | 
| jbe@0 | 1676   point = (pgl_point *)palloc(sizeof(pgl_point)); | 
| jbe@0 | 1677   /* set latitude and longitude (and perform checks) */ | 
| jbe@0 | 1678   pgl_epoint_set_latlon(point, lat, lon); | 
| jbe@0 | 1679   /* return result */ | 
| jbe@0 | 1680   PG_RETURN_POINTER(point); | 
| jbe@0 | 1681 } | 
| jbe@0 | 1682 | 
| jbe@46 | 1683 /* set sample count for numerical integration (including checks) */ | 
| jbe@46 | 1684 static void pgl_epoint_set_sample_count(pgl_point_sc *search, int32 samples) { | 
| jbe@46 | 1685   /* require minimum of 6 samples */ | 
| jbe@46 | 1686   if (samples < 6) { | 
| jbe@46 | 1687     ereport(ERROR, ( | 
| jbe@46 | 1688       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@46 | 1689       errmsg("too few sample points for numerical integration (minimum 6)") | 
| jbe@46 | 1690     )); | 
| jbe@46 | 1691   } | 
| jbe@46 | 1692   /* limit sample count to avoid integer overflows on memory allocation */ | 
| jbe@46 | 1693   if (samples > PGL_CLUSTER_MAXPOINTS) { | 
| jbe@46 | 1694     ereport(ERROR, ( | 
| jbe@46 | 1695       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@46 | 1696       errmsg( | 
| jbe@46 | 1697         "too many sample points for numerical integration (maximum %i)", | 
| jbe@46 | 1698         PGL_CLUSTER_MAXPOINTS | 
| jbe@46 | 1699       ) | 
| jbe@46 | 1700     )); | 
| jbe@46 | 1701   } | 
| jbe@46 | 1702   search->samples = samples; | 
| jbe@46 | 1703 } | 
| jbe@46 | 1704 | 
| jbe@46 | 1705 /* create point with sample count for fair distance calculation | 
| jbe@46 | 1706    ("epoint_with_sample_count" in SQL) from epoint and integer */ | 
| jbe@46 | 1707 PG_FUNCTION_INFO_V1(pgl_create_epoint_with_sample_count); | 
| jbe@46 | 1708 Datum pgl_create_epoint_with_sample_count(PG_FUNCTION_ARGS) { | 
| jbe@46 | 1709   pgl_point_sc *search = (pgl_point_sc *)palloc(sizeof(pgl_point_sc)); | 
| jbe@46 | 1710   search->point = *(pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@46 | 1711   pgl_epoint_set_sample_count(search, PG_GETARG_INT32(1)); | 
| jbe@46 | 1712   PG_RETURN_POINTER(search); | 
| jbe@46 | 1713 } | 
| jbe@46 | 1714 | 
| jbe@46 | 1715 /* parse point with sample count ("epoint_with_sample_count" in SQL) */ | 
| jbe@46 | 1716 /* format: '[NS]<float> [EW]<float> <integer>' */ | 
| jbe@46 | 1717 PG_FUNCTION_INFO_V1(pgl_epoint_with_sample_count_in); | 
| jbe@46 | 1718 Datum pgl_epoint_with_sample_count_in(PG_FUNCTION_ARGS) { | 
| jbe@46 | 1719   char *str = PG_GETARG_CSTRING(0);  /* input string */ | 
| jbe@46 | 1720   char *strptr = str;    /* current position within string */ | 
| jbe@46 | 1721   double lat, lon;       /* parsed values for latitude and longitude */ | 
| jbe@46 | 1722   int samples;           /* parsed value for sample count */ | 
| jbe@46 | 1723   int valid = 0;         /* number of valid chars */ | 
| jbe@46 | 1724   int done = 0;          /* stores if latitude and/or longitude was read */ | 
| jbe@46 | 1725   pgl_point_sc *search;  /* return value (to be palloc'ed) */ | 
| jbe@46 | 1726   /* demand three blocks separated by whitespace */ | 
| jbe@46 | 1727   sscanf(strptr, " %*s %*s %*s %n", &valid); | 
| jbe@46 | 1728   /* if three blocks separated by whitespace exist, parse those blocks */ | 
| jbe@46 | 1729   if (strptr[valid] == 0) { | 
| jbe@46 | 1730     /* parse latitude and longitude */ | 
| jbe@46 | 1731     done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@46 | 1732     done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@46 | 1733     /* parse sample count (while incr. strptr by number of bytes parsed) */ | 
| jbe@46 | 1734     valid = 0; | 
| jbe@46 | 1735     if (sscanf(strptr, " %d %n", &samples, &valid) == 1) strptr += valid; | 
| jbe@46 | 1736   } | 
| jbe@46 | 1737   /* require end of string and both latitude and longitude being parsed */ | 
| jbe@46 | 1738   if (strptr[0] || done != PGL_SCAN_LATLON) { | 
| jbe@46 | 1739     ereport(ERROR, ( | 
| jbe@46 | 1740       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@46 | 1741       errmsg("invalid input syntax for type ecircle: \"%s\"", str) | 
| jbe@46 | 1742     )); | 
| jbe@46 | 1743   } | 
| jbe@46 | 1744   /* allocate memory for result */ | 
| jbe@46 | 1745   search = (pgl_point_sc *)palloc(sizeof(pgl_point_sc)); | 
| jbe@46 | 1746   /* set latitude, longitude, and sample count (while performing checks) */ | 
| jbe@46 | 1747   pgl_epoint_set_latlon(&search->point, lat, lon); | 
| jbe@46 | 1748   pgl_epoint_set_sample_count(search, samples); | 
| jbe@46 | 1749   /* return result */ | 
| jbe@46 | 1750   PG_RETURN_POINTER(search); | 
| jbe@46 | 1751 } | 
| jbe@46 | 1752 | 
| jbe@0 | 1753 /* create box ("ebox" in SQL) that is empty */ | 
| jbe@0 | 1754 PG_FUNCTION_INFO_V1(pgl_create_empty_ebox); | 
| jbe@0 | 1755 Datum pgl_create_empty_ebox(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1756   pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 1757   pgl_box_set_empty(box); | 
| jbe@0 | 1758   PG_RETURN_POINTER(box); | 
| jbe@0 | 1759 } | 
| jbe@0 | 1760 | 
| jbe@0 | 1761 /* set box to given boundaries (including checks) */ | 
| jbe@0 | 1762 static void pgl_ebox_set_boundaries( | 
| jbe@0 | 1763   pgl_box *box, | 
| jbe@0 | 1764   double lat_min, double lat_max, double lon_min, double lon_max | 
| jbe@0 | 1765 ) { | 
| jbe@0 | 1766   /* if minimum latitude is greater than maximum latitude, return empty box */ | 
| jbe@0 | 1767   if (lat_min > lat_max) { | 
| jbe@0 | 1768     pgl_box_set_empty(box); | 
| jbe@0 | 1769     return; | 
| jbe@0 | 1770   } | 
| jbe@0 | 1771   /* otherwise reject infinite or NaN values */ | 
| jbe@0 | 1772   if ( | 
| jbe@0 | 1773     !isfinite(lat_min) || !isfinite(lat_max) || | 
| jbe@0 | 1774     !isfinite(lon_min) || !isfinite(lon_max) | 
| jbe@0 | 1775   ) { | 
| jbe@0 | 1776     ereport(ERROR, ( | 
| jbe@0 | 1777       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@0 | 1778       errmsg("ebox requires finite coordinates") | 
| jbe@0 | 1779     )); | 
| jbe@0 | 1780   } | 
| jbe@0 | 1781   /* check latitude bounds */ | 
| jbe@0 | 1782   if (lat_max < -90) { | 
| jbe@0 | 1783     ereport(WARNING, (errmsg("northern latitude exceeds south pole"))); | 
| jbe@0 | 1784     lat_max = -90; | 
| jbe@0 | 1785   } else if (lat_max > 90) { | 
| jbe@0 | 1786     ereport(WARNING, (errmsg("northern latitude exceeds north pole"))); | 
| jbe@0 | 1787     lat_max = 90; | 
| jbe@0 | 1788   } | 
| jbe@0 | 1789   if (lat_min < -90) { | 
| jbe@0 | 1790     ereport(WARNING, (errmsg("southern latitude exceeds south pole"))); | 
| jbe@0 | 1791     lat_min = -90; | 
| jbe@0 | 1792   } else if (lat_min > 90) { | 
| jbe@0 | 1793     ereport(WARNING, (errmsg("southern latitude exceeds north pole"))); | 
| jbe@0 | 1794     lat_min = 90; | 
| jbe@0 | 1795   } | 
| jbe@0 | 1796   /* check if all longitudes are included */ | 
| jbe@0 | 1797   if (lon_max - lon_min >= 360) { | 
| jbe@0 | 1798     if (lon_max - lon_min > 360) ereport(WARNING, ( | 
| jbe@0 | 1799       errmsg("longitude coverage greater than 360 degrees") | 
| jbe@0 | 1800     )); | 
| jbe@0 | 1801     lon_min = -180; | 
| jbe@0 | 1802     lon_max = 180; | 
| jbe@0 | 1803   } else { | 
| jbe@0 | 1804     /* normalize longitude bounds */ | 
| jbe@0 | 1805     if      (lon_min < -180) lon_min += 360 - trunc(lon_min / 360) * 360; | 
| jbe@0 | 1806     else if (lon_min >  180) lon_min -= 360 + trunc(lon_min / 360) * 360; | 
| jbe@0 | 1807     if      (lon_max < -180) lon_max += 360 - trunc(lon_max / 360) * 360; | 
| jbe@0 | 1808     else if (lon_max >  180) lon_max -= 360 + trunc(lon_max / 360) * 360; | 
| jbe@0 | 1809   } | 
| jbe@0 | 1810   /* store rounded latitude/longitude values for round-trip safety */ | 
| jbe@0 | 1811   box->lat_min = pgl_round(lat_min); | 
| jbe@0 | 1812   box->lat_max = pgl_round(lat_max); | 
| jbe@0 | 1813   box->lon_min = pgl_round(lon_min); | 
| jbe@0 | 1814   box->lon_max = pgl_round(lon_max); | 
| jbe@0 | 1815   /* ensure that rounding does not change orientation */ | 
| jbe@0 | 1816   if (lon_min > lon_max && box->lon_min == box->lon_max) { | 
| jbe@0 | 1817     box->lon_min = -180; | 
| jbe@0 | 1818     box->lon_max = 180; | 
| jbe@0 | 1819   } | 
| jbe@0 | 1820 } | 
| jbe@0 | 1821 | 
| jbe@0 | 1822 /* create box ("ebox" in SQL) from min/max latitude and min/max longitude */ | 
| jbe@0 | 1823 PG_FUNCTION_INFO_V1(pgl_create_ebox); | 
| jbe@0 | 1824 Datum pgl_create_ebox(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1825   pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 1826   pgl_ebox_set_boundaries( | 
| jbe@0 | 1827     box, | 
| jbe@0 | 1828     PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1), | 
| jbe@0 | 1829     PG_GETARG_FLOAT8(2), PG_GETARG_FLOAT8(3) | 
| jbe@0 | 1830   ); | 
| jbe@0 | 1831   PG_RETURN_POINTER(box); | 
| jbe@0 | 1832 } | 
| jbe@0 | 1833 | 
| jbe@0 | 1834 /* create box ("ebox" in SQL) from two points ("epoint"s) */ | 
| jbe@0 | 1835 /* (can not be used to cover a longitude range of more than 120 degrees) */ | 
| jbe@0 | 1836 PG_FUNCTION_INFO_V1(pgl_create_ebox_from_epoints); | 
| jbe@0 | 1837 Datum pgl_create_ebox_from_epoints(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1838   pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 1839   pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1); | 
| jbe@0 | 1840   pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 1841   double lat_min, lat_max, lon_min, lon_max; | 
| jbe@0 | 1842   double dlon;  /* longitude range (delta longitude) */ | 
| jbe@0 | 1843   /* order latitude and longitude boundaries */ | 
| jbe@0 | 1844   if (point2->lat < point1->lat) { | 
| jbe@0 | 1845     lat_min = point2->lat; | 
| jbe@0 | 1846     lat_max = point1->lat; | 
| jbe@0 | 1847   } else { | 
| jbe@0 | 1848     lat_min = point1->lat; | 
| jbe@0 | 1849     lat_max = point2->lat; | 
| jbe@0 | 1850   } | 
| jbe@0 | 1851   if (point2->lon < point1->lon) { | 
| jbe@0 | 1852     lon_min = point2->lon; | 
| jbe@0 | 1853     lon_max = point1->lon; | 
| jbe@0 | 1854   } else { | 
| jbe@0 | 1855     lon_min = point1->lon; | 
| jbe@0 | 1856     lon_max = point2->lon; | 
| jbe@0 | 1857   } | 
| jbe@0 | 1858   /* calculate longitude range (round to avoid floating point errors) */ | 
| jbe@0 | 1859   dlon = pgl_round(lon_max - lon_min); | 
| jbe@0 | 1860   /* determine east-west direction */ | 
| jbe@0 | 1861   if (dlon >= 240) { | 
| jbe@0 | 1862     /* assume that 180th meridian is crossed and swap min/max longitude */ | 
| jbe@0 | 1863     double swap = lon_min; lon_min = lon_max; lon_max = swap; | 
| jbe@0 | 1864   } else if (dlon > 120) { | 
| jbe@0 | 1865     /* unclear orientation since delta longitude > 120 */ | 
| jbe@0 | 1866     ereport(ERROR, ( | 
| jbe@0 | 1867       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@0 | 1868       errmsg("can not determine east/west orientation for ebox") | 
| jbe@0 | 1869     )); | 
| jbe@0 | 1870   } | 
| jbe@0 | 1871   /* use boundaries to setup box (and perform checks) */ | 
| jbe@0 | 1872   pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max); | 
| jbe@0 | 1873   /* return result */ | 
| jbe@0 | 1874   PG_RETURN_POINTER(box); | 
| jbe@0 | 1875 } | 
| jbe@0 | 1876 | 
| jbe@0 | 1877 /* parse box ("ebox" in SQL) */ | 
| jbe@0 | 1878 /* format: '[NS]<float> [EW]<float> [NS]<float> [EW]<float>' | 
| jbe@0 | 1879        or: '[NS]<float> [NS]<float> [EW]<float> [EW]<float>' */ | 
| jbe@0 | 1880 PG_FUNCTION_INFO_V1(pgl_ebox_in); | 
| jbe@0 | 1881 Datum pgl_ebox_in(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1882   char *str = PG_GETARG_CSTRING(0);  /* input string */ | 
| jbe@0 | 1883   char *str_lower;     /* lower case version of input string */ | 
| jbe@0 | 1884   char *strptr;        /* current position within string */ | 
| jbe@0 | 1885   int valid;           /* number of valid chars */ | 
| jbe@0 | 1886   int done;            /* specifies if latitude or longitude was read */ | 
| jbe@0 | 1887   double val;          /* temporary variable */ | 
| jbe@0 | 1888   int lat_count = 0;   /* count of latitude values parsed */ | 
| jbe@0 | 1889   int lon_count = 0;   /* count of longitufde values parsed */ | 
| jbe@0 | 1890   double lat_min, lat_max, lon_min, lon_max;  /* see pgl_box struct */ | 
| jbe@0 | 1891   pgl_box *box;        /* return value (to be palloc'ed) */ | 
| jbe@0 | 1892   /* lowercase input */ | 
| jbe@0 | 1893   str_lower = psprintf("%s", str); | 
| jbe@0 | 1894   for (strptr=str_lower; *strptr; strptr++) { | 
| jbe@0 | 1895     if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A'; | 
| jbe@0 | 1896   } | 
| jbe@0 | 1897   /* reset reading position to start of (lowercase) string */ | 
| jbe@0 | 1898   strptr = str_lower; | 
| jbe@0 | 1899   /* check if empty box */ | 
| jbe@0 | 1900   valid = 0; | 
| jbe@0 | 1901   sscanf(strptr, " empty %n", &valid); | 
| jbe@0 | 1902   if (valid && strptr[valid] == 0) { | 
| jbe@0 | 1903     /* allocate and return empty box */ | 
| jbe@0 | 1904     box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 1905     pgl_box_set_empty(box); | 
| jbe@0 | 1906     PG_RETURN_POINTER(box); | 
| jbe@0 | 1907   } | 
| jbe@0 | 1908   /* demand four blocks separated by whitespace */ | 
| jbe@0 | 1909   valid = 0; | 
| jbe@0 | 1910   sscanf(strptr, " %*s %*s %*s %*s %n", &valid); | 
| jbe@0 | 1911   /* if four blocks separated by whitespace exist, parse those blocks */ | 
| jbe@0 | 1912   if (strptr[valid] == 0) while (strptr[0]) { | 
| jbe@0 | 1913     /* parse either latitude or longitude (whichever found in input string) */ | 
| jbe@0 | 1914     done = pgl_scan(&strptr, &val, &val); | 
| jbe@0 | 1915     /* store latitude or longitude in lat_min, lat_max, lon_min, or lon_max */ | 
| jbe@0 | 1916     if (done == PGL_SCAN_LAT) { | 
| jbe@0 | 1917       if (!lat_count) lat_min = val; else lat_max = val; | 
| jbe@0 | 1918       lat_count++; | 
| jbe@0 | 1919     } else if (done == PGL_SCAN_LON) { | 
| jbe@0 | 1920       if (!lon_count) lon_min = val; else lon_max = val; | 
| jbe@0 | 1921       lon_count++; | 
| jbe@0 | 1922     } else { | 
| jbe@0 | 1923       break; | 
| jbe@0 | 1924     } | 
| jbe@0 | 1925   } | 
| jbe@0 | 1926   /* require end of string, and two latitude and two longitude values */ | 
| jbe@0 | 1927   if (strptr[0] || lat_count != 2 || lon_count != 2) { | 
| jbe@0 | 1928     ereport(ERROR, ( | 
| jbe@0 | 1929       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 1930       errmsg("invalid input syntax for type ebox: \"%s\"", str) | 
| jbe@0 | 1931     )); | 
| jbe@0 | 1932   } | 
| jbe@0 | 1933   /* free lower case string */ | 
| jbe@0 | 1934   pfree(str_lower); | 
| jbe@0 | 1935   /* order boundaries (maximum greater than minimum) */ | 
| jbe@0 | 1936   if (lat_min > lat_max) { val = lat_min; lat_min = lat_max; lat_max = val; } | 
| jbe@0 | 1937   if (lon_min > lon_max) { val = lon_min; lon_min = lon_max; lon_max = val; } | 
| jbe@0 | 1938   /* allocate memory for result */ | 
| jbe@0 | 1939   box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 1940   /* set boundaries (and perform checks) */ | 
| jbe@0 | 1941   pgl_ebox_set_boundaries(box, lat_min, lat_max, lon_min, lon_max); | 
| jbe@0 | 1942   /* return result */ | 
| jbe@0 | 1943   PG_RETURN_POINTER(box); | 
| jbe@0 | 1944 } | 
| jbe@0 | 1945 | 
| jbe@0 | 1946 /* set circle to given latitude, longitude, and radius (including checks) */ | 
| jbe@0 | 1947 static void pgl_ecircle_set_latlon_radius( | 
| jbe@0 | 1948   pgl_circle *circle, double lat, double lon, double radius | 
| jbe@0 | 1949 ) { | 
| jbe@0 | 1950   /* set center point (including checks) */ | 
| jbe@0 | 1951   pgl_epoint_set_latlon(&(circle->center), lat, lon); | 
| jbe@0 | 1952   /* handle non-positive radius */ | 
| jbe@0 | 1953   if (isnan(radius)) { | 
| jbe@0 | 1954     ereport(ERROR, ( | 
| jbe@0 | 1955       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@0 | 1956       errmsg("invalid radius for ecircle") | 
| jbe@0 | 1957     )); | 
| jbe@0 | 1958   } | 
| jbe@0 | 1959   if (radius == 0) radius = 0;  /* avoids -0 */ | 
| jbe@0 | 1960   else if (radius < 0) { | 
| jbe@0 | 1961     if (isfinite(radius)) { | 
| jbe@0 | 1962       ereport(NOTICE, (errmsg("negative radius converted to minus infinity"))); | 
| jbe@0 | 1963     } | 
| jbe@0 | 1964     radius = -INFINITY; | 
| jbe@0 | 1965   } | 
| jbe@0 | 1966   /* store radius (round-trip safety is ensured by pgl_print_float) */ | 
| jbe@0 | 1967   circle->radius = radius; | 
| jbe@0 | 1968 } | 
| jbe@0 | 1969 | 
| jbe@0 | 1970 /* create circle ("ecircle" in SQL) from latitude, longitude, and radius */ | 
| jbe@0 | 1971 PG_FUNCTION_INFO_V1(pgl_create_ecircle); | 
| jbe@0 | 1972 Datum pgl_create_ecircle(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1973   pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); | 
| jbe@0 | 1974   pgl_ecircle_set_latlon_radius( | 
| jbe@0 | 1975     circle, PG_GETARG_FLOAT8(0), PG_GETARG_FLOAT8(1), PG_GETARG_FLOAT8(2) | 
| jbe@0 | 1976   ); | 
| jbe@0 | 1977   PG_RETURN_POINTER(circle); | 
| jbe@0 | 1978 } | 
| jbe@0 | 1979 | 
| jbe@0 | 1980 /* create circle ("ecircle" in SQL) from point ("epoint"), and radius */ | 
| jbe@0 | 1981 PG_FUNCTION_INFO_V1(pgl_create_ecircle_from_epoint); | 
| jbe@0 | 1982 Datum pgl_create_ecircle_from_epoint(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1983   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 1984   double radius = PG_GETARG_FLOAT8(1); | 
| jbe@0 | 1985   pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); | 
| jbe@0 | 1986   /* set latitude, longitude, radius (and perform checks) */ | 
| jbe@0 | 1987   pgl_ecircle_set_latlon_radius(circle, point->lat, point->lon, radius); | 
| jbe@0 | 1988   /* return result */ | 
| jbe@0 | 1989   PG_RETURN_POINTER(circle); | 
| jbe@0 | 1990 } | 
| jbe@0 | 1991 | 
| jbe@0 | 1992 /* parse circle ("ecircle" in SQL) */ | 
| jbe@0 | 1993 /* format: '[NS]<float> [EW]<float> <float>' */ | 
| jbe@0 | 1994 PG_FUNCTION_INFO_V1(pgl_ecircle_in); | 
| jbe@0 | 1995 Datum pgl_ecircle_in(PG_FUNCTION_ARGS) { | 
| jbe@0 | 1996   char *str = PG_GETARG_CSTRING(0);  /* input string */ | 
| jbe@0 | 1997   char *strptr = str;       /* current position within string */ | 
| jbe@46 | 1998   double lat, lon, radius;  /* parsed values as double precision floats */ | 
| jbe@0 | 1999   int valid = 0;            /* number of valid chars */ | 
| jbe@0 | 2000   int done = 0;             /* stores if latitude and/or longitude was read */ | 
| jbe@0 | 2001   pgl_circle *circle;       /* return value (to be palloc'ed) */ | 
| jbe@0 | 2002   /* demand three blocks separated by whitespace */ | 
| jbe@0 | 2003   sscanf(strptr, " %*s %*s %*s %n", &valid); | 
| jbe@0 | 2004   /* if three blocks separated by whitespace exist, parse those blocks */ | 
| jbe@0 | 2005   if (strptr[valid] == 0) { | 
| jbe@0 | 2006     /* parse latitude and longitude */ | 
| jbe@0 | 2007     done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 2008     done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 2009     /* parse radius (while incrementing strptr by number of bytes parsed) */ | 
| jbe@0 | 2010     valid = 0; | 
| jbe@0 | 2011     if (sscanf(strptr, " %lf %n", &radius, &valid) == 1) strptr += valid; | 
| jbe@0 | 2012   } | 
| jbe@0 | 2013   /* require end of string and both latitude and longitude being parsed */ | 
| jbe@0 | 2014   if (strptr[0] || done != PGL_SCAN_LATLON) { | 
| jbe@0 | 2015     ereport(ERROR, ( | 
| jbe@0 | 2016       errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 2017       errmsg("invalid input syntax for type ecircle: \"%s\"", str) | 
| jbe@0 | 2018     )); | 
| jbe@0 | 2019   } | 
| jbe@0 | 2020   /* allocate memory for result */ | 
| jbe@0 | 2021   circle = (pgl_circle *)palloc(sizeof(pgl_circle)); | 
| jbe@0 | 2022   /* set latitude, longitude, radius (and perform checks) */ | 
| jbe@0 | 2023   pgl_ecircle_set_latlon_radius(circle, lat, lon, radius); | 
| jbe@0 | 2024   /* return result */ | 
| jbe@0 | 2025   PG_RETURN_POINTER(circle); | 
| jbe@0 | 2026 } | 
| jbe@0 | 2027 | 
| jbe@0 | 2028 /* parse cluster ("ecluster" in SQL) */ | 
| jbe@0 | 2029 PG_FUNCTION_INFO_V1(pgl_ecluster_in); | 
| jbe@0 | 2030 Datum pgl_ecluster_in(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2031   int i; | 
| jbe@0 | 2032   char *str = PG_GETARG_CSTRING(0);  /* input string */ | 
| jbe@0 | 2033   char *str_lower;         /* lower case version of input string */ | 
| jbe@0 | 2034   char *strptr;            /* pointer to current reading position of input */ | 
| jbe@0 | 2035   int npoints_total = 0;   /* total number of points in cluster */ | 
| jbe@0 | 2036   int nentries = 0;        /* total number of entries */ | 
| jbe@0 | 2037   pgl_newentry *entries;   /* array of pgl_newentry to create pgl_cluster */ | 
| jbe@0 | 2038   int entries_buflen = 4;  /* maximum number of elements in entries array */ | 
| jbe@0 | 2039   int valid;               /* number of valid chars processed */ | 
| jbe@0 | 2040   double lat, lon;         /* latitude and longitude of parsed point */ | 
| jbe@0 | 2041   int entrytype;           /* current entry type */ | 
| jbe@0 | 2042   int npoints;             /* number of points in current entry */ | 
| jbe@0 | 2043   pgl_point *points;       /* array of pgl_point for pgl_newentry */ | 
| jbe@0 | 2044   int points_buflen;       /* maximum number of elements in points array */ | 
| jbe@0 | 2045   int done;                /* return value of pgl_scan function */ | 
| jbe@0 | 2046   pgl_cluster *cluster;    /* created cluster */ | 
| jbe@0 | 2047   /* lowercase input */ | 
| jbe@0 | 2048   str_lower = psprintf("%s", str); | 
| jbe@0 | 2049   for (strptr=str_lower; *strptr; strptr++) { | 
| jbe@0 | 2050     if (*strptr >= 'A' && *strptr <= 'Z') *strptr += 'a' - 'A'; | 
| jbe@0 | 2051   } | 
| jbe@0 | 2052   /* reset reading position to start of (lowercase) string */ | 
| jbe@0 | 2053   strptr = str_lower; | 
| jbe@0 | 2054   /* allocate initial buffer for entries */ | 
| jbe@0 | 2055   entries = palloc(entries_buflen * sizeof(pgl_newentry)); | 
| jbe@0 | 2056   /* parse until end of string */ | 
| jbe@0 | 2057   while (strptr[0]) { | 
| jbe@0 | 2058     /* require previous white-space or closing parenthesis before next token */ | 
| jbe@0 | 2059     if (strptr != str_lower && !isspace(strptr[-1]) && strptr[-1] != ')') { | 
| jbe@0 | 2060       goto pgl_ecluster_in_error; | 
| jbe@0 | 2061     } | 
| jbe@0 | 2062     /* ignore token "empty" */ | 
| jbe@0 | 2063     valid = 0; sscanf(strptr, " empty %n", &valid); | 
| jbe@0 | 2064     if (valid) { strptr += valid; continue; } | 
| jbe@0 | 2065     /* test for "point" token */ | 
| jbe@0 | 2066     valid = 0; sscanf(strptr, " point ( %n", &valid); | 
| jbe@0 | 2067     if (valid) { | 
| jbe@0 | 2068       strptr += valid; | 
| jbe@0 | 2069       entrytype = PGL_ENTRY_POINT; | 
| jbe@0 | 2070       goto pgl_ecluster_in_type_ok; | 
| jbe@0 | 2071     } | 
| jbe@0 | 2072     /* test for "path" token */ | 
| jbe@0 | 2073     valid = 0; sscanf(strptr, " path ( %n", &valid); | 
| jbe@0 | 2074     if (valid) { | 
| jbe@0 | 2075       strptr += valid; | 
| jbe@0 | 2076       entrytype = PGL_ENTRY_PATH; | 
| jbe@0 | 2077       goto pgl_ecluster_in_type_ok; | 
| jbe@0 | 2078     } | 
| jbe@0 | 2079     /* test for "outline" token */ | 
| jbe@0 | 2080     valid = 0; sscanf(strptr, " outline ( %n", &valid); | 
| jbe@0 | 2081     if (valid) { | 
| jbe@0 | 2082       strptr += valid; | 
| jbe@0 | 2083       entrytype = PGL_ENTRY_OUTLINE; | 
| jbe@0 | 2084       goto pgl_ecluster_in_type_ok; | 
| jbe@0 | 2085     } | 
| jbe@0 | 2086     /* test for "polygon" token */ | 
| jbe@0 | 2087     valid = 0; sscanf(strptr, " polygon ( %n", &valid); | 
| jbe@0 | 2088     if (valid) { | 
| jbe@0 | 2089       strptr += valid; | 
| jbe@0 | 2090       entrytype = PGL_ENTRY_POLYGON; | 
| jbe@0 | 2091       goto pgl_ecluster_in_type_ok; | 
| jbe@0 | 2092     } | 
| jbe@0 | 2093     /* error if no valid token found */ | 
| jbe@0 | 2094     goto pgl_ecluster_in_error; | 
| jbe@0 | 2095     pgl_ecluster_in_type_ok: | 
| jbe@0 | 2096     /* check if pgl_newentry array needs to grow */ | 
| jbe@0 | 2097     if (nentries == entries_buflen) { | 
| jbe@0 | 2098       pgl_newentry *newbuf; | 
| jbe@0 | 2099       entries_buflen *= 2; | 
| jbe@0 | 2100       newbuf = palloc(entries_buflen * sizeof(pgl_newentry)); | 
| jbe@0 | 2101       memcpy(newbuf, entries, nentries * sizeof(pgl_newentry)); | 
| jbe@0 | 2102       pfree(entries); | 
| jbe@0 | 2103       entries = newbuf; | 
| jbe@0 | 2104     } | 
| jbe@0 | 2105     /* reset number of points for current entry */ | 
| jbe@0 | 2106     npoints = 0; | 
| jbe@0 | 2107     /* allocate array for points */ | 
| jbe@0 | 2108     points_buflen = 4; | 
| jbe@0 | 2109     points = palloc(points_buflen * sizeof(pgl_point)); | 
| jbe@0 | 2110     /* parse until closing parenthesis */ | 
| jbe@0 | 2111     while (strptr[0] != ')') { | 
| jbe@0 | 2112       /* error on unexpected end of string */ | 
| jbe@0 | 2113       if (strptr[0] == 0) goto pgl_ecluster_in_error; | 
| jbe@0 | 2114       /* mark neither latitude nor longitude as read */ | 
| jbe@0 | 2115       done = PGL_SCAN_NONE; | 
| jbe@0 | 2116       /* require white-space before second, third, etc. point */ | 
| jbe@0 | 2117       if (npoints != 0 && !isspace(strptr[-1])) goto pgl_ecluster_in_error; | 
| jbe@0 | 2118       /* scan latitude (or longitude) */ | 
| jbe@0 | 2119       done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 2120       /* require white-space before second coordinate */ | 
| jbe@0 | 2121       if (strptr != str && !isspace(strptr[-1])) goto pgl_ecluster_in_error; | 
| jbe@0 | 2122       /* scan longitude (or latitude) */ | 
| jbe@0 | 2123       done |= pgl_scan(&strptr, &lat, &lon); | 
| jbe@0 | 2124       /* error unless both latitude and longitude were parsed */ | 
| jbe@0 | 2125       if (done != PGL_SCAN_LATLON) goto pgl_ecluster_in_error; | 
| jbe@0 | 2126       /* throw error if number of points is too high */ | 
| jbe@0 | 2127       if (npoints_total == PGL_CLUSTER_MAXPOINTS) { | 
| jbe@0 | 2128         ereport(ERROR, ( | 
| jbe@0 | 2129           errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 2130           errmsg( | 
| jbe@0 | 2131             "too many points for ecluster entry (maximum %i)", | 
| jbe@0 | 2132             PGL_CLUSTER_MAXPOINTS | 
| jbe@0 | 2133           ) | 
| jbe@0 | 2134         )); | 
| jbe@0 | 2135       } | 
| jbe@0 | 2136       /* check if pgl_point array needs to grow */ | 
| jbe@0 | 2137       if (npoints == points_buflen) { | 
| jbe@0 | 2138         pgl_point *newbuf; | 
| jbe@0 | 2139         points_buflen *= 2; | 
| jbe@0 | 2140         newbuf = palloc(points_buflen * sizeof(pgl_point)); | 
| jbe@0 | 2141         memcpy(newbuf, points, npoints * sizeof(pgl_point)); | 
| jbe@0 | 2142         pfree(points); | 
| jbe@0 | 2143         points = newbuf; | 
| jbe@0 | 2144       } | 
| jbe@0 | 2145       /* append point to pgl_point array (includes checks) */ | 
| jbe@0 | 2146       pgl_epoint_set_latlon(&(points[npoints++]), lat, lon); | 
| jbe@0 | 2147       /* increase total number of points */ | 
| jbe@0 | 2148       npoints_total++; | 
| jbe@0 | 2149     } | 
| jbe@0 | 2150     /* error if entry has no points */ | 
| jbe@0 | 2151     if (!npoints) goto pgl_ecluster_in_error; | 
| jbe@0 | 2152     /* entries with one point are automatically of type "point" */ | 
| jbe@0 | 2153     if (npoints == 1) entrytype = PGL_ENTRY_POINT; | 
| jbe@0 | 2154     /* if entries have more than one point */ | 
| jbe@0 | 2155     else { | 
| jbe@0 | 2156       /* throw error if entry type is "point" */ | 
| jbe@0 | 2157       if (entrytype == PGL_ENTRY_POINT) { | 
| jbe@0 | 2158         ereport(ERROR, ( | 
| jbe@0 | 2159           errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 2160           errmsg("invalid input syntax for type ecluster (point entry with more than one point)") | 
| jbe@0 | 2161         )); | 
| jbe@0 | 2162       } | 
| jbe@0 | 2163       /* coerce outlines and polygons with more than 2 points to be a path */ | 
| jbe@0 | 2164       if (npoints == 2) entrytype = PGL_ENTRY_PATH; | 
| jbe@0 | 2165     } | 
| jbe@0 | 2166     /* append entry to pgl_newentry array */ | 
| jbe@0 | 2167     entries[nentries].entrytype = entrytype; | 
| jbe@0 | 2168     entries[nentries].npoints = npoints; | 
| jbe@0 | 2169     entries[nentries].points = points; | 
| jbe@0 | 2170     nentries++; | 
| jbe@0 | 2171     /* consume closing parenthesis */ | 
| jbe@0 | 2172     strptr++; | 
| jbe@0 | 2173     /* consume white-space */ | 
| jbe@0 | 2174     while (isspace(strptr[0])) strptr++; | 
| jbe@0 | 2175   } | 
| jbe@0 | 2176   /* free lower case string */ | 
| jbe@0 | 2177   pfree(str_lower); | 
| jbe@0 | 2178   /* create cluster from pgl_newentry array */ | 
| jbe@0 | 2179   cluster = pgl_new_cluster(nentries, entries); | 
| jbe@0 | 2180   /* free pgl_newentry array */ | 
| jbe@0 | 2181   for (i=0; i<nentries; i++) pfree(entries[i].points); | 
| jbe@0 | 2182   pfree(entries); | 
| jbe@0 | 2183   /* set bounding circle of cluster and check east/west orientation */ | 
| jbe@0 | 2184   if (!pgl_finalize_cluster(cluster)) { | 
| jbe@0 | 2185     ereport(ERROR, ( | 
| jbe@0 | 2186       errcode(ERRCODE_DATA_EXCEPTION), | 
| jbe@0 | 2187       errmsg("can not determine east/west orientation for ecluster"), | 
| jbe@0 | 2188       errhint("Ensure that each entry has a longitude span of less than 180 degrees.") | 
| jbe@0 | 2189     )); | 
| jbe@0 | 2190   } | 
| jbe@0 | 2191   /* return cluster */ | 
| jbe@0 | 2192   PG_RETURN_POINTER(cluster); | 
| jbe@0 | 2193   /* code to throw error */ | 
| jbe@0 | 2194   pgl_ecluster_in_error: | 
| jbe@0 | 2195   ereport(ERROR, ( | 
| jbe@0 | 2196     errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), | 
| jbe@0 | 2197     errmsg("invalid input syntax for type ecluster: \"%s\"", str) | 
| jbe@0 | 2198   )); | 
| jbe@0 | 2199 } | 
| jbe@0 | 2200 | 
| jbe@0 | 2201 /* convert point ("epoint") to string representation */ | 
| jbe@0 | 2202 PG_FUNCTION_INFO_V1(pgl_epoint_out); | 
| jbe@0 | 2203 Datum pgl_epoint_out(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2204   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2205   char latstr[PGL_NUMBUFLEN]; | 
| jbe@0 | 2206   char lonstr[PGL_NUMBUFLEN]; | 
| jbe@0 | 2207   pgl_print_lat(latstr, point->lat); | 
| jbe@0 | 2208   pgl_print_lon(lonstr, point->lon); | 
| jbe@0 | 2209   PG_RETURN_CSTRING(psprintf("%s %s", latstr, lonstr)); | 
| jbe@0 | 2210 } | 
| jbe@0 | 2211 | 
| jbe@46 | 2212 /* convert point with sample count ("epoint_with_sample_count") to str. rep. */ | 
| jbe@46 | 2213 PG_FUNCTION_INFO_V1(pgl_epoint_with_sample_count_out); | 
| jbe@46 | 2214 Datum pgl_epoint_with_sample_count_out(PG_FUNCTION_ARGS) { | 
| jbe@46 | 2215   pgl_point_sc *search = (pgl_point_sc *)PG_GETARG_POINTER(0); | 
| jbe@46 | 2216   char latstr[PGL_NUMBUFLEN]; | 
| jbe@46 | 2217   char lonstr[PGL_NUMBUFLEN]; | 
| jbe@46 | 2218   pgl_print_lat(latstr, search->point.lat); | 
| jbe@46 | 2219   pgl_print_lon(lonstr, search->point.lon); | 
| jbe@46 | 2220   PG_RETURN_CSTRING( | 
| jbe@46 | 2221     psprintf("%s %s %i", latstr, lonstr, (int)search->samples) | 
| jbe@46 | 2222   ); | 
| jbe@46 | 2223 } | 
| jbe@46 | 2224 | 
| jbe@0 | 2225 /* convert box ("ebox") to string representation */ | 
| jbe@0 | 2226 PG_FUNCTION_INFO_V1(pgl_ebox_out); | 
| jbe@0 | 2227 Datum pgl_ebox_out(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2228   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2229   double lon_min = box->lon_min; | 
| jbe@0 | 2230   double lon_max = box->lon_max; | 
| jbe@0 | 2231   char lat_min_str[PGL_NUMBUFLEN]; | 
| jbe@0 | 2232   char lat_max_str[PGL_NUMBUFLEN]; | 
| jbe@0 | 2233   char lon_min_str[PGL_NUMBUFLEN]; | 
| jbe@0 | 2234   char lon_max_str[PGL_NUMBUFLEN]; | 
| jbe@0 | 2235   /* return string "empty" if box is set to be empty */ | 
| jbe@0 | 2236   if (box->lat_min > box->lat_max) PG_RETURN_CSTRING("empty"); | 
| jbe@0 | 2237   /* use boundaries exceeding W180 or E180 if 180th meridian is enclosed */ | 
| jbe@0 | 2238   /* (required since pgl_box_in orders the longitude boundaries) */ | 
| jbe@0 | 2239   if (lon_min > lon_max) { | 
| jbe@0 | 2240     if (lon_min + lon_max >= 0) lon_min -= 360; | 
| jbe@0 | 2241     else lon_max += 360; | 
| jbe@0 | 2242   } | 
| jbe@0 | 2243   /* format and return result */ | 
| jbe@0 | 2244   pgl_print_lat(lat_min_str, box->lat_min); | 
| jbe@0 | 2245   pgl_print_lat(lat_max_str, box->lat_max); | 
| jbe@0 | 2246   pgl_print_lon(lon_min_str, lon_min); | 
| jbe@0 | 2247   pgl_print_lon(lon_max_str, lon_max); | 
| jbe@0 | 2248   PG_RETURN_CSTRING(psprintf( | 
| jbe@0 | 2249     "%s %s %s %s", | 
| jbe@0 | 2250     lat_min_str, lon_min_str, lat_max_str, lon_max_str | 
| jbe@0 | 2251   )); | 
| jbe@0 | 2252 } | 
| jbe@0 | 2253 | 
| jbe@0 | 2254 /* convert circle ("ecircle") to string representation */ | 
| jbe@0 | 2255 PG_FUNCTION_INFO_V1(pgl_ecircle_out); | 
| jbe@0 | 2256 Datum pgl_ecircle_out(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2257   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2258   char latstr[PGL_NUMBUFLEN]; | 
| jbe@0 | 2259   char lonstr[PGL_NUMBUFLEN]; | 
| jbe@0 | 2260   char radstr[PGL_NUMBUFLEN]; | 
| jbe@0 | 2261   pgl_print_lat(latstr, circle->center.lat); | 
| jbe@0 | 2262   pgl_print_lon(lonstr, circle->center.lon); | 
| jbe@0 | 2263   pgl_print_float(radstr, circle->radius); | 
| jbe@0 | 2264   PG_RETURN_CSTRING(psprintf("%s %s %s", latstr, lonstr, radstr)); | 
| jbe@0 | 2265 } | 
| jbe@0 | 2266 | 
| jbe@0 | 2267 /* convert cluster ("ecluster") to string representation */ | 
| jbe@0 | 2268 PG_FUNCTION_INFO_V1(pgl_ecluster_out); | 
| jbe@0 | 2269 Datum pgl_ecluster_out(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2270   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@0 | 2271   char latstr[PGL_NUMBUFLEN];  /* string buffer for latitude */ | 
| jbe@0 | 2272   char lonstr[PGL_NUMBUFLEN];  /* string buffer for longitude */ | 
| jbe@0 | 2273   char ***strings;     /* array of array of strings */ | 
| jbe@0 | 2274   char *string;        /* string of current token */ | 
| jbe@0 | 2275   char *res, *resptr;  /* result and pointer to current write position */ | 
| jbe@0 | 2276   size_t reslen = 1;   /* length of result (init with 1 for terminator) */ | 
| jbe@0 | 2277   int npoints;         /* number of points of current entry */ | 
| jbe@0 | 2278   int i, j;            /* i: entry, j: point in entry */ | 
| jbe@0 | 2279   /* handle empty clusters */ | 
| jbe@0 | 2280   if (cluster->nentries == 0) { | 
| jbe@0 | 2281     /* free detoasted cluster (if copy) */ | 
| jbe@0 | 2282     PG_FREE_IF_COPY(cluster, 0); | 
| jbe@0 | 2283     /* return static result */ | 
| jbe@0 | 2284     PG_RETURN_CSTRING("empty"); | 
| jbe@0 | 2285   } | 
| jbe@0 | 2286   /* allocate array of array of strings */ | 
| jbe@0 | 2287   strings = palloc(cluster->nentries * sizeof(char **)); | 
| jbe@0 | 2288   /* iterate over all entries in cluster */ | 
| jbe@0 | 2289   for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 2290     /* get number of points in entry */ | 
| jbe@0 | 2291     npoints = cluster->entries[i].npoints; | 
| jbe@0 | 2292     /* allocate array of strings (one string for each point plus two extra) */ | 
| jbe@0 | 2293     strings[i] = palloc((2 + npoints) * sizeof(char *)); | 
| jbe@0 | 2294     /* determine opening string */ | 
| jbe@0 | 2295     switch (cluster->entries[i].entrytype) { | 
| jbe@0 | 2296       case PGL_ENTRY_POINT:   string = (i==0)?"point ("  :" point (";   break; | 
| jbe@0 | 2297       case PGL_ENTRY_PATH:    string = (i==0)?"path ("   :" path (";    break; | 
| jbe@0 | 2298       case PGL_ENTRY_OUTLINE: string = (i==0)?"outline (":" outline ("; break; | 
| jbe@0 | 2299       case PGL_ENTRY_POLYGON: string = (i==0)?"polygon (":" polygon ("; break; | 
| jbe@0 | 2300       default:                string = (i==0)?"unknown"  :" unknown"; | 
| jbe@0 | 2301     } | 
| jbe@0 | 2302     /* use opening string as first string in array */ | 
| jbe@0 | 2303     strings[i][0] = string; | 
| jbe@0 | 2304     /* update result length (for allocating result string later) */ | 
| jbe@0 | 2305     reslen += strlen(string); | 
| jbe@0 | 2306     /* iterate over all points */ | 
| jbe@0 | 2307     for (j=0; j<npoints; j++) { | 
| jbe@0 | 2308       /* create string representation of point */ | 
| jbe@0 | 2309       pgl_print_lat(latstr, PGL_ENTRY_POINTS(cluster, i)[j].lat); | 
| jbe@0 | 2310       pgl_print_lon(lonstr, PGL_ENTRY_POINTS(cluster, i)[j].lon); | 
| jbe@0 | 2311       string = psprintf((j == 0) ? "%s %s" : " %s %s", latstr, lonstr); | 
| jbe@0 | 2312       /* copy string pointer to string array */ | 
| jbe@0 | 2313       strings[i][j+1] = string; | 
| jbe@0 | 2314       /* update result length (for allocating result string later) */ | 
| jbe@0 | 2315       reslen += strlen(string); | 
| jbe@0 | 2316     } | 
| jbe@0 | 2317     /* use closing parenthesis as last string in array */ | 
| jbe@0 | 2318     strings[i][npoints+1] = ")"; | 
| jbe@0 | 2319     /* update result length (for allocating result string later) */ | 
| jbe@0 | 2320     reslen++; | 
| jbe@0 | 2321   } | 
| jbe@0 | 2322   /* allocate result string */ | 
| jbe@0 | 2323   res = palloc(reslen); | 
| jbe@0 | 2324   /* set write pointer to begin of result string */ | 
| jbe@0 | 2325   resptr = res; | 
| jbe@0 | 2326   /* copy strings into result string */ | 
| jbe@0 | 2327   for (i=0; i<cluster->nentries; i++) { | 
| jbe@0 | 2328     npoints = cluster->entries[i].npoints; | 
| jbe@0 | 2329     for (j=0; j<npoints+2; j++) { | 
| jbe@0 | 2330       string = strings[i][j]; | 
| jbe@0 | 2331       strcpy(resptr, string); | 
| jbe@0 | 2332       resptr += strlen(string); | 
| jbe@0 | 2333       /* free strings allocated by psprintf */ | 
| jbe@0 | 2334       if (j != 0 && j != npoints+1) pfree(string); | 
| jbe@0 | 2335     } | 
| jbe@0 | 2336     /* free array of strings */ | 
| jbe@0 | 2337     pfree(strings[i]); | 
| jbe@0 | 2338   } | 
| jbe@0 | 2339   /* free array of array of strings */ | 
| jbe@0 | 2340   pfree(strings); | 
| jbe@0 | 2341   /* free detoasted cluster (if copy) */ | 
| jbe@0 | 2342   PG_FREE_IF_COPY(cluster, 0); | 
| jbe@0 | 2343   /* return result */ | 
| jbe@0 | 2344   PG_RETURN_CSTRING(res); | 
| jbe@0 | 2345 } | 
| jbe@0 | 2346 | 
| jbe@0 | 2347 /* binary input function for point ("epoint") */ | 
| jbe@0 | 2348 PG_FUNCTION_INFO_V1(pgl_epoint_recv); | 
| jbe@0 | 2349 Datum pgl_epoint_recv(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2350   StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); | 
| jbe@0 | 2351   pgl_point *point = (pgl_point *)palloc(sizeof(pgl_point)); | 
| jbe@0 | 2352   point->lat = pq_getmsgfloat8(buf); | 
| jbe@0 | 2353   point->lon = pq_getmsgfloat8(buf); | 
| jbe@0 | 2354   PG_RETURN_POINTER(point); | 
| jbe@0 | 2355 } | 
| jbe@0 | 2356 | 
| jbe@0 | 2357 /* binary input function for box ("ebox") */ | 
| jbe@0 | 2358 PG_FUNCTION_INFO_V1(pgl_ebox_recv); | 
| jbe@0 | 2359 Datum pgl_ebox_recv(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2360   StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); | 
| jbe@0 | 2361   pgl_box *box = (pgl_box *)palloc(sizeof(pgl_box)); | 
| jbe@0 | 2362   box->lat_min = pq_getmsgfloat8(buf); | 
| jbe@0 | 2363   box->lat_max = pq_getmsgfloat8(buf); | 
| jbe@0 | 2364   box->lon_min = pq_getmsgfloat8(buf); | 
| jbe@0 | 2365   box->lon_max = pq_getmsgfloat8(buf); | 
| jbe@0 | 2366   PG_RETURN_POINTER(box); | 
| jbe@0 | 2367 } | 
| jbe@0 | 2368 | 
| jbe@0 | 2369 /* binary input function for circle ("ecircle") */ | 
| jbe@0 | 2370 PG_FUNCTION_INFO_V1(pgl_ecircle_recv); | 
| jbe@0 | 2371 Datum pgl_ecircle_recv(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2372   StringInfo buf = (StringInfo)PG_GETARG_POINTER(0); | 
| jbe@0 | 2373   pgl_circle *circle = (pgl_circle *)palloc(sizeof(pgl_circle)); | 
| jbe@0 | 2374   circle->center.lat = pq_getmsgfloat8(buf); | 
| jbe@0 | 2375   circle->center.lon = pq_getmsgfloat8(buf); | 
| jbe@0 | 2376   circle->radius = pq_getmsgfloat8(buf); | 
| jbe@0 | 2377   PG_RETURN_POINTER(circle); | 
| jbe@0 | 2378 } | 
| jbe@0 | 2379 | 
| jbe@0 | 2380 /* TODO: binary receive function for cluster */ | 
| jbe@0 | 2381 | 
| jbe@0 | 2382 /* binary output function for point ("epoint") */ | 
| jbe@0 | 2383 PG_FUNCTION_INFO_V1(pgl_epoint_send); | 
| jbe@0 | 2384 Datum pgl_epoint_send(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2385   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2386   StringInfoData buf; | 
| jbe@0 | 2387   pq_begintypsend(&buf); | 
| jbe@0 | 2388   pq_sendfloat8(&buf, point->lat); | 
| jbe@0 | 2389   pq_sendfloat8(&buf, point->lon); | 
| jbe@0 | 2390   PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); | 
| jbe@0 | 2391 } | 
| jbe@0 | 2392 | 
| jbe@0 | 2393 /* binary output function for box ("ebox") */ | 
| jbe@0 | 2394 PG_FUNCTION_INFO_V1(pgl_ebox_send); | 
| jbe@0 | 2395 Datum pgl_ebox_send(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2396   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2397   StringInfoData buf; | 
| jbe@0 | 2398   pq_begintypsend(&buf); | 
| jbe@0 | 2399   pq_sendfloat8(&buf, box->lat_min); | 
| jbe@0 | 2400   pq_sendfloat8(&buf, box->lat_max); | 
| jbe@0 | 2401   pq_sendfloat8(&buf, box->lon_min); | 
| jbe@0 | 2402   pq_sendfloat8(&buf, box->lon_max); | 
| jbe@0 | 2403   PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); | 
| jbe@0 | 2404 } | 
| jbe@0 | 2405 | 
| jbe@0 | 2406 /* binary output function for circle ("ecircle") */ | 
| jbe@0 | 2407 PG_FUNCTION_INFO_V1(pgl_ecircle_send); | 
| jbe@0 | 2408 Datum pgl_ecircle_send(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2409   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2410   StringInfoData buf; | 
| jbe@0 | 2411   pq_begintypsend(&buf); | 
| jbe@0 | 2412   pq_sendfloat8(&buf, circle->center.lat); | 
| jbe@0 | 2413   pq_sendfloat8(&buf, circle->center.lon); | 
| jbe@0 | 2414   pq_sendfloat8(&buf, circle->radius); | 
| jbe@0 | 2415   PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); | 
| jbe@0 | 2416 } | 
| jbe@0 | 2417 | 
| jbe@0 | 2418 /* TODO: binary send functions for cluster */ | 
| jbe@0 | 2419 | 
| jbe@0 | 2420 /* cast point ("epoint") to box ("ebox") */ | 
| jbe@0 | 2421 PG_FUNCTION_INFO_V1(pgl_epoint_to_ebox); | 
| jbe@0 | 2422 Datum pgl_epoint_to_ebox(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2423   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2424   pgl_box *box = palloc(sizeof(pgl_box)); | 
| jbe@0 | 2425   box->lat_min = point->lat; | 
| jbe@0 | 2426   box->lat_max = point->lat; | 
| jbe@0 | 2427   box->lon_min = point->lon; | 
| jbe@0 | 2428   box->lon_max = point->lon; | 
| jbe@0 | 2429   PG_RETURN_POINTER(box); | 
| jbe@0 | 2430 } | 
| jbe@0 | 2431 | 
| jbe@0 | 2432 /* cast point ("epoint") to circle ("ecircle") */ | 
| jbe@0 | 2433 PG_FUNCTION_INFO_V1(pgl_epoint_to_ecircle); | 
| jbe@0 | 2434 Datum pgl_epoint_to_ecircle(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2435   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2436   pgl_circle *circle = palloc(sizeof(pgl_box)); | 
| jbe@0 | 2437   circle->center = *point; | 
| jbe@0 | 2438   circle->radius = 0; | 
| jbe@0 | 2439   PG_RETURN_POINTER(circle); | 
| jbe@0 | 2440 } | 
| jbe@0 | 2441 | 
| jbe@0 | 2442 /* cast point ("epoint") to cluster ("ecluster") */ | 
| jbe@0 | 2443 PG_FUNCTION_INFO_V1(pgl_epoint_to_ecluster); | 
| jbe@0 | 2444 Datum pgl_epoint_to_ecluster(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2445   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2446   pgl_newentry entry; | 
| jbe@42 | 2447   pgl_cluster *cluster; | 
| jbe@0 | 2448   entry.entrytype = PGL_ENTRY_POINT; | 
| jbe@0 | 2449   entry.npoints = 1; | 
| jbe@0 | 2450   entry.points = point; | 
| jbe@42 | 2451   cluster = pgl_new_cluster(1, &entry); | 
| jbe@42 | 2452   pgl_finalize_cluster(cluster);  /* NOTE: should not fail */ | 
| jbe@42 | 2453   PG_RETURN_POINTER(cluster); | 
| jbe@0 | 2454 } | 
| jbe@0 | 2455 | 
| jbe@0 | 2456 /* cast box ("ebox") to cluster ("ecluster") */ | 
| jbe@0 | 2457 #define pgl_ebox_to_ecluster_macro(i, a, b) \ | 
| jbe@0 | 2458   entries[i].entrytype = PGL_ENTRY_POLYGON; \ | 
| jbe@0 | 2459   entries[i].npoints = 4; \ | 
| jbe@0 | 2460   entries[i].points = points[i]; \ | 
| jbe@0 | 2461   points[i][0].lat = box->lat_min; \ | 
| jbe@0 | 2462   points[i][0].lon = (a); \ | 
| jbe@0 | 2463   points[i][1].lat = box->lat_min; \ | 
| jbe@0 | 2464   points[i][1].lon = (b); \ | 
| jbe@0 | 2465   points[i][2].lat = box->lat_max; \ | 
| jbe@0 | 2466   points[i][2].lon = (b); \ | 
| jbe@0 | 2467   points[i][3].lat = box->lat_max; \ | 
| jbe@0 | 2468   points[i][3].lon = (a); | 
| jbe@0 | 2469 PG_FUNCTION_INFO_V1(pgl_ebox_to_ecluster); | 
| jbe@0 | 2470 Datum pgl_ebox_to_ecluster(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2471   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2472   double lon, dlon; | 
| jbe@0 | 2473   int nentries; | 
| jbe@0 | 2474   pgl_newentry entries[3]; | 
| jbe@0 | 2475   pgl_point points[3][4]; | 
| jbe@42 | 2476   pgl_cluster *cluster; | 
| jbe@0 | 2477   if (box->lat_min > box->lat_max) { | 
| jbe@0 | 2478     nentries = 0; | 
| jbe@0 | 2479   } else if (box->lon_min > box->lon_max) { | 
| jbe@0 | 2480     if (box->lon_min < 0) { | 
| jbe@0 | 2481       lon = pgl_round((box->lon_min + 180) / 2.0); | 
| jbe@0 | 2482       nentries = 3; | 
| jbe@0 | 2483       pgl_ebox_to_ecluster_macro(0, box->lon_min, lon); | 
| jbe@0 | 2484       pgl_ebox_to_ecluster_macro(1, lon, 180); | 
| jbe@0 | 2485       pgl_ebox_to_ecluster_macro(2, -180, box->lon_max); | 
| jbe@0 | 2486     } else if (box->lon_max > 0) { | 
| jbe@0 | 2487       lon = pgl_round((box->lon_max - 180) / 2.0); | 
| jbe@0 | 2488       nentries = 3; | 
| jbe@0 | 2489       pgl_ebox_to_ecluster_macro(0, box->lon_min, 180); | 
| jbe@0 | 2490       pgl_ebox_to_ecluster_macro(1, -180, lon); | 
| jbe@0 | 2491       pgl_ebox_to_ecluster_macro(2, lon, box->lon_max); | 
| jbe@0 | 2492     } else { | 
| jbe@0 | 2493       nentries = 2; | 
| jbe@0 | 2494       pgl_ebox_to_ecluster_macro(0, box->lon_min, 180); | 
| jbe@0 | 2495       pgl_ebox_to_ecluster_macro(1, -180, box->lon_max); | 
| jbe@0 | 2496     } | 
| jbe@0 | 2497   } else { | 
| jbe@0 | 2498     dlon = pgl_round(box->lon_max - box->lon_min); | 
| jbe@0 | 2499     if (dlon < 180) { | 
| jbe@0 | 2500       nentries = 1; | 
| jbe@0 | 2501       pgl_ebox_to_ecluster_macro(0, box->lon_min, box->lon_max); | 
| jbe@0 | 2502     } else { | 
| jbe@0 | 2503       lon = pgl_round((box->lon_min + box->lon_max) / 2.0); | 
| jbe@0 | 2504       if ( | 
| jbe@0 | 2505         pgl_round(lon - box->lon_min) < 180 && | 
| jbe@0 | 2506         pgl_round(box->lon_max - lon) < 180 | 
| jbe@0 | 2507       ) { | 
| jbe@0 | 2508         nentries = 2; | 
| jbe@0 | 2509         pgl_ebox_to_ecluster_macro(0, box->lon_min, lon); | 
| jbe@0 | 2510         pgl_ebox_to_ecluster_macro(1, lon, box->lon_max); | 
| jbe@0 | 2511       } else { | 
| jbe@0 | 2512         nentries = 3; | 
| jbe@0 | 2513         pgl_ebox_to_ecluster_macro(0, box->lon_min, -60); | 
| jbe@0 | 2514         pgl_ebox_to_ecluster_macro(1, -60, 60); | 
| jbe@0 | 2515         pgl_ebox_to_ecluster_macro(2, 60, box->lon_max); | 
| jbe@0 | 2516       } | 
| jbe@0 | 2517     } | 
| jbe@0 | 2518   } | 
| jbe@42 | 2519   cluster = pgl_new_cluster(nentries, entries); | 
| jbe@42 | 2520   pgl_finalize_cluster(cluster);  /* NOTE: should not fail */ | 
| jbe@42 | 2521   PG_RETURN_POINTER(cluster); | 
| jbe@0 | 2522 } | 
| jbe@0 | 2523 | 
| jbe@0 | 2524 /* extract latitude from point ("epoint") */ | 
| jbe@0 | 2525 PG_FUNCTION_INFO_V1(pgl_epoint_lat); | 
| jbe@0 | 2526 Datum pgl_epoint_lat(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2527   PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lat); | 
| jbe@0 | 2528 } | 
| jbe@0 | 2529 | 
| jbe@0 | 2530 /* extract longitude from point ("epoint") */ | 
| jbe@0 | 2531 PG_FUNCTION_INFO_V1(pgl_epoint_lon); | 
| jbe@0 | 2532 Datum pgl_epoint_lon(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2533   PG_RETURN_FLOAT8(((pgl_point *)PG_GETARG_POINTER(0))->lon); | 
| jbe@0 | 2534 } | 
| jbe@0 | 2535 | 
| jbe@0 | 2536 /* extract minimum latitude from box ("ebox") */ | 
| jbe@0 | 2537 PG_FUNCTION_INFO_V1(pgl_ebox_lat_min); | 
| jbe@0 | 2538 Datum pgl_ebox_lat_min(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2539   PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_min); | 
| jbe@0 | 2540 } | 
| jbe@0 | 2541 | 
| jbe@0 | 2542 /* extract maximum latitude from box ("ebox") */ | 
| jbe@0 | 2543 PG_FUNCTION_INFO_V1(pgl_ebox_lat_max); | 
| jbe@0 | 2544 Datum pgl_ebox_lat_max(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2545   PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lat_max); | 
| jbe@0 | 2546 } | 
| jbe@0 | 2547 | 
| jbe@0 | 2548 /* extract minimum longitude from box ("ebox") */ | 
| jbe@0 | 2549 PG_FUNCTION_INFO_V1(pgl_ebox_lon_min); | 
| jbe@0 | 2550 Datum pgl_ebox_lon_min(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2551   PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_min); | 
| jbe@0 | 2552 } | 
| jbe@0 | 2553 | 
| jbe@0 | 2554 /* extract maximum longitude from box ("ebox") */ | 
| jbe@0 | 2555 PG_FUNCTION_INFO_V1(pgl_ebox_lon_max); | 
| jbe@0 | 2556 Datum pgl_ebox_lon_max(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2557   PG_RETURN_FLOAT8(((pgl_box *)PG_GETARG_POINTER(0))->lon_max); | 
| jbe@0 | 2558 } | 
| jbe@0 | 2559 | 
| jbe@0 | 2560 /* extract center point from circle ("ecircle") */ | 
| jbe@0 | 2561 PG_FUNCTION_INFO_V1(pgl_ecircle_center); | 
| jbe@0 | 2562 Datum pgl_ecircle_center(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2563   PG_RETURN_POINTER(&(((pgl_circle *)PG_GETARG_POINTER(0))->center)); | 
| jbe@0 | 2564 } | 
| jbe@0 | 2565 | 
| jbe@0 | 2566 /* extract radius from circle ("ecircle") */ | 
| jbe@0 | 2567 PG_FUNCTION_INFO_V1(pgl_ecircle_radius); | 
| jbe@0 | 2568 Datum pgl_ecircle_radius(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2569   PG_RETURN_FLOAT8(((pgl_circle *)PG_GETARG_POINTER(0))->radius); | 
| jbe@0 | 2570 } | 
| jbe@0 | 2571 | 
| jbe@0 | 2572 /* check if point is inside box (overlap operator "&&") in SQL */ | 
| jbe@0 | 2573 PG_FUNCTION_INFO_V1(pgl_epoint_ebox_overlap); | 
| jbe@0 | 2574 Datum pgl_epoint_ebox_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2575   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2576   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2577   PG_RETURN_BOOL(pgl_point_in_box(point, box)); | 
| jbe@0 | 2578 } | 
| jbe@0 | 2579 | 
| jbe@0 | 2580 /* check if point is inside circle (overlap operator "&&") in SQL */ | 
| jbe@0 | 2581 PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_overlap); | 
| jbe@0 | 2582 Datum pgl_epoint_ecircle_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2583   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2584   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2585   PG_RETURN_BOOL( | 
| jbe@0 | 2586     pgl_distance( | 
| jbe@0 | 2587       point->lat, point->lon, | 
| jbe@0 | 2588       circle->center.lat, circle->center.lon | 
| jbe@0 | 2589     ) <= circle->radius | 
| jbe@0 | 2590   ); | 
| jbe@0 | 2591 } | 
| jbe@0 | 2592 | 
| jbe@0 | 2593 /* check if point is inside cluster (overlap operator "&&") in SQL */ | 
| jbe@0 | 2594 PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_overlap); | 
| jbe@0 | 2595 Datum pgl_epoint_ecluster_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2596   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2597   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@16 | 2598   bool retval; | 
| jbe@46 | 2599   /* points outside bounding circle are always assumed to be non-overlapping */ | 
| jbe@16 | 2600   if ( | 
| jbe@16 | 2601     pgl_distance( | 
| jbe@16 | 2602       point->lat, point->lon, | 
| jbe@16 | 2603       cluster->bounding.center.lat, cluster->bounding.center.lon | 
| jbe@16 | 2604     ) > cluster->bounding.radius | 
| jbe@16 | 2605   ) retval = false; | 
| jbe@20 | 2606   else retval = pgl_point_in_cluster(point, cluster, false); | 
| jbe@0 | 2607   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@0 | 2608   PG_RETURN_BOOL(retval); | 
| jbe@0 | 2609 } | 
| jbe@0 | 2610 | 
| jbe@10 | 2611 /* check if point may be inside cluster (lossy overl. operator "&&+") in SQL */ | 
| jbe@10 | 2612 PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_may_overlap); | 
| jbe@10 | 2613 Datum pgl_epoint_ecluster_may_overlap(PG_FUNCTION_ARGS) { | 
| jbe@10 | 2614   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@10 | 2615   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@10 | 2616   bool retval = pgl_distance( | 
| jbe@10 | 2617     point->lat, point->lon, | 
| jbe@10 | 2618     cluster->bounding.center.lat, cluster->bounding.center.lon | 
| jbe@10 | 2619   ) <= cluster->bounding.radius; | 
| jbe@10 | 2620   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@10 | 2621   PG_RETURN_BOOL(retval); | 
| jbe@10 | 2622 } | 
| jbe@10 | 2623 | 
| jbe@0 | 2624 /* check if two boxes overlap (overlap operator "&&") in SQL */ | 
| jbe@0 | 2625 PG_FUNCTION_INFO_V1(pgl_ebox_overlap); | 
| jbe@0 | 2626 Datum pgl_ebox_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2627   pgl_box *box1 = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2628   pgl_box *box2 = (pgl_box *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2629   PG_RETURN_BOOL(pgl_boxes_overlap(box1, box2)); | 
| jbe@0 | 2630 } | 
| jbe@0 | 2631 | 
| jbe@10 | 2632 /* check if box and circle may overlap (lossy overl. operator "&&+") in SQL */ | 
| jbe@10 | 2633 PG_FUNCTION_INFO_V1(pgl_ebox_ecircle_may_overlap); | 
| jbe@10 | 2634 Datum pgl_ebox_ecircle_may_overlap(PG_FUNCTION_ARGS) { | 
| jbe@10 | 2635   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@10 | 2636   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@10 | 2637   PG_RETURN_BOOL( | 
| jbe@10 | 2638     pgl_estimate_point_box_distance(&circle->center, box) <= circle->radius | 
| jbe@10 | 2639   ); | 
| jbe@10 | 2640 } | 
| jbe@10 | 2641 | 
| jbe@10 | 2642 /* check if box and cluster may overlap (lossy overl. operator "&&+") in SQL */ | 
| jbe@10 | 2643 PG_FUNCTION_INFO_V1(pgl_ebox_ecluster_may_overlap); | 
| jbe@10 | 2644 Datum pgl_ebox_ecluster_may_overlap(PG_FUNCTION_ARGS) { | 
| jbe@10 | 2645   pgl_box *box = (pgl_box *)PG_GETARG_POINTER(0); | 
| jbe@10 | 2646   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@10 | 2647   bool retval = pgl_estimate_point_box_distance( | 
| jbe@10 | 2648     &cluster->bounding.center, | 
| jbe@10 | 2649     box | 
| jbe@10 | 2650   ) <= cluster->bounding.radius; | 
| jbe@10 | 2651   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@10 | 2652   PG_RETURN_BOOL(retval); | 
| jbe@10 | 2653 } | 
| jbe@10 | 2654 | 
| jbe@0 | 2655 /* check if two circles overlap (overlap operator "&&") in SQL */ | 
| jbe@0 | 2656 PG_FUNCTION_INFO_V1(pgl_ecircle_overlap); | 
| jbe@0 | 2657 Datum pgl_ecircle_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2658   pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2659   pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2660   PG_RETURN_BOOL( | 
| jbe@0 | 2661     pgl_distance( | 
| jbe@0 | 2662       circle1->center.lat, circle1->center.lon, | 
| jbe@0 | 2663       circle2->center.lat, circle2->center.lon | 
| jbe@0 | 2664     ) <= circle1->radius + circle2->radius | 
| jbe@0 | 2665   ); | 
| jbe@0 | 2666 } | 
| jbe@0 | 2667 | 
| jbe@0 | 2668 /* check if circle and cluster overlap (overlap operator "&&") in SQL */ | 
| jbe@0 | 2669 PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_overlap); | 
| jbe@0 | 2670 Datum pgl_ecircle_ecluster_overlap(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2671   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2672   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@46 | 2673   bool retval; | 
| jbe@46 | 2674   /* points outside bounding circle (with margin for flattening) are always | 
| jbe@46 | 2675      assumed to be non-overlapping */ | 
| jbe@46 | 2676   if ( | 
| jbe@46 | 2677     (1.0-PGL_SPHEROID_F) *  /* margin for flattening and approximation */ | 
| jbe@46 | 2678     pgl_distance( | 
| jbe@46 | 2679       circle->center.lat, circle->center.lon, | 
| jbe@46 | 2680       cluster->bounding.center.lat, cluster->bounding.center.lon | 
| jbe@46 | 2681     ) > circle->radius + cluster->bounding.radius | 
| jbe@46 | 2682   ) retval = false; | 
| jbe@46 | 2683   else retval = pgl_point_cluster_distance(&(circle->center), cluster) <= circle->radius; | 
| jbe@0 | 2684   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@0 | 2685   PG_RETURN_BOOL(retval); | 
| jbe@0 | 2686 } | 
| jbe@0 | 2687 | 
| jbe@17 | 2688 /* check if circle and cluster may overlap (l. ov. operator "&&+") in SQL */ | 
| jbe@10 | 2689 PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_may_overlap); | 
| jbe@10 | 2690 Datum pgl_ecircle_ecluster_may_overlap(PG_FUNCTION_ARGS) { | 
| jbe@10 | 2691   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@10 | 2692   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@46 | 2693   bool retval = ( | 
| jbe@46 | 2694     (1.0-PGL_SPHEROID_F) *  /* margin for flattening and approximation */ | 
| jbe@46 | 2695     pgl_distance( | 
| jbe@46 | 2696       circle->center.lat, circle->center.lon, | 
| jbe@46 | 2697       cluster->bounding.center.lat, cluster->bounding.center.lon | 
| jbe@46 | 2698     ) | 
| jbe@10 | 2699   ) <= circle->radius + cluster->bounding.radius; | 
| jbe@10 | 2700   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@10 | 2701   PG_RETURN_BOOL(retval); | 
| jbe@10 | 2702 } | 
| jbe@10 | 2703 | 
| jbe@16 | 2704 /* check if two clusters overlap (overlap operator "&&") in SQL */ | 
| jbe@16 | 2705 PG_FUNCTION_INFO_V1(pgl_ecluster_overlap); | 
| jbe@16 | 2706 Datum pgl_ecluster_overlap(PG_FUNCTION_ARGS) { | 
| jbe@16 | 2707   pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@16 | 2708   pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@16 | 2709   bool retval; | 
| jbe@46 | 2710   /* clusters with non-touching bounding circles (with margin for flattening) | 
| jbe@46 | 2711      are always assumed to be non-overlapping */ | 
| jbe@16 | 2712   if ( | 
| jbe@46 | 2713     (1.0-PGL_SPHEROID_F) *  /* margin for flattening and approximation */ | 
| jbe@16 | 2714     pgl_distance( | 
| jbe@16 | 2715       cluster1->bounding.center.lat, cluster1->bounding.center.lon, | 
| jbe@16 | 2716       cluster2->bounding.center.lat, cluster2->bounding.center.lon | 
| jbe@16 | 2717     ) > cluster1->bounding.radius + cluster2->bounding.radius | 
| jbe@16 | 2718   ) retval = false; | 
| jbe@16 | 2719   else retval = pgl_clusters_overlap(cluster1, cluster2); | 
| jbe@16 | 2720   PG_FREE_IF_COPY(cluster1, 0); | 
| jbe@16 | 2721   PG_FREE_IF_COPY(cluster2, 1); | 
| jbe@16 | 2722   PG_RETURN_BOOL(retval); | 
| jbe@16 | 2723 } | 
| jbe@16 | 2724 | 
| jbe@10 | 2725 /* check if two clusters may overlap (lossy overlap operator "&&+") in SQL */ | 
| jbe@10 | 2726 PG_FUNCTION_INFO_V1(pgl_ecluster_may_overlap); | 
| jbe@10 | 2727 Datum pgl_ecluster_may_overlap(PG_FUNCTION_ARGS) { | 
| jbe@10 | 2728   pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@10 | 2729   pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@46 | 2730   bool retval = ( | 
| jbe@46 | 2731     (1.0-PGL_SPHEROID_F) *  /* margin for flattening and approximation */ | 
| jbe@46 | 2732     pgl_distance( | 
| jbe@46 | 2733       cluster1->bounding.center.lat, cluster1->bounding.center.lon, | 
| jbe@46 | 2734       cluster2->bounding.center.lat, cluster2->bounding.center.lon | 
| jbe@46 | 2735     ) | 
| jbe@10 | 2736   ) <= cluster1->bounding.radius + cluster2->bounding.radius; | 
| jbe@10 | 2737   PG_FREE_IF_COPY(cluster1, 0); | 
| jbe@10 | 2738   PG_FREE_IF_COPY(cluster2, 1); | 
| jbe@10 | 2739   PG_RETURN_BOOL(retval); | 
| jbe@10 | 2740 } | 
| jbe@10 | 2741 | 
| jbe@16 | 2742 /* check if second cluster is in first cluster (cont. operator "@>) in SQL */ | 
| jbe@16 | 2743 PG_FUNCTION_INFO_V1(pgl_ecluster_contains); | 
| jbe@16 | 2744 Datum pgl_ecluster_contains(PG_FUNCTION_ARGS) { | 
| jbe@16 | 2745   pgl_cluster *outer = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@16 | 2746   pgl_cluster *inner = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@16 | 2747   bool retval; | 
| jbe@16 | 2748   /* clusters with non-touching bounding circles are always assumed to be | 
| jbe@46 | 2749      non-overlapping */ | 
| jbe@16 | 2750   if ( | 
| jbe@46 | 2751     (1.0-PGL_SPHEROID_F) *  /* margin for flattening and approximation */ | 
| jbe@16 | 2752     pgl_distance( | 
| jbe@16 | 2753       outer->bounding.center.lat, outer->bounding.center.lon, | 
| jbe@16 | 2754       inner->bounding.center.lat, inner->bounding.center.lon | 
| jbe@16 | 2755     ) > outer->bounding.radius + inner->bounding.radius | 
| jbe@16 | 2756   ) retval = false; | 
| jbe@16 | 2757   else retval = pgl_cluster_in_cluster(outer, inner); | 
| jbe@16 | 2758   PG_FREE_IF_COPY(outer, 0); | 
| jbe@16 | 2759   PG_FREE_IF_COPY(inner, 1); | 
| jbe@16 | 2760   PG_RETURN_BOOL(retval); | 
| jbe@16 | 2761 } | 
| jbe@16 | 2762 | 
| jbe@0 | 2763 /* calculate distance between two points ("<->" operator) in SQL */ | 
| jbe@0 | 2764 PG_FUNCTION_INFO_V1(pgl_epoint_distance); | 
| jbe@0 | 2765 Datum pgl_epoint_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2766   pgl_point *point1 = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2767   pgl_point *point2 = (pgl_point *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2768   PG_RETURN_FLOAT8(pgl_distance( | 
| jbe@0 | 2769     point1->lat, point1->lon, point2->lat, point2->lon | 
| jbe@0 | 2770   )); | 
| jbe@0 | 2771 } | 
| jbe@0 | 2772 | 
| jbe@0 | 2773 /* calculate point to circle distance ("<->" operator) in SQL */ | 
| jbe@0 | 2774 PG_FUNCTION_INFO_V1(pgl_epoint_ecircle_distance); | 
| jbe@0 | 2775 Datum pgl_epoint_ecircle_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2776   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2777   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2778   double distance = pgl_distance( | 
| jbe@0 | 2779     point->lat, point->lon, circle->center.lat, circle->center.lon | 
| jbe@0 | 2780   ) - circle->radius; | 
| jbe@0 | 2781   PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); | 
| jbe@0 | 2782 } | 
| jbe@0 | 2783 | 
| jbe@0 | 2784 /* calculate point to cluster distance ("<->" operator) in SQL */ | 
| jbe@0 | 2785 PG_FUNCTION_INFO_V1(pgl_epoint_ecluster_distance); | 
| jbe@0 | 2786 Datum pgl_epoint_ecluster_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2787   pgl_point *point = (pgl_point *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2788   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@0 | 2789   double distance = pgl_point_cluster_distance(point, cluster); | 
| jbe@0 | 2790   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@0 | 2791   PG_RETURN_FLOAT8(distance); | 
| jbe@0 | 2792 } | 
| jbe@0 | 2793 | 
| jbe@0 | 2794 /* calculate distance between two circles ("<->" operator) in SQL */ | 
| jbe@0 | 2795 PG_FUNCTION_INFO_V1(pgl_ecircle_distance); | 
| jbe@0 | 2796 Datum pgl_ecircle_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2797   pgl_circle *circle1 = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2798   pgl_circle *circle2 = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2799   double distance = pgl_distance( | 
| jbe@0 | 2800     circle1->center.lat, circle1->center.lon, | 
| jbe@0 | 2801     circle2->center.lat, circle2->center.lon | 
| jbe@0 | 2802   ) - (circle1->radius + circle2->radius); | 
| jbe@0 | 2803   PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); | 
| jbe@0 | 2804 } | 
| jbe@0 | 2805 | 
| jbe@0 | 2806 /* calculate circle to cluster distance ("<->" operator) in SQL */ | 
| jbe@0 | 2807 PG_FUNCTION_INFO_V1(pgl_ecircle_ecluster_distance); | 
| jbe@0 | 2808 Datum pgl_ecircle_ecluster_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2809   pgl_circle *circle = (pgl_circle *)PG_GETARG_POINTER(0); | 
| jbe@0 | 2810   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@0 | 2811   double distance = ( | 
| jbe@0 | 2812     pgl_point_cluster_distance(&(circle->center), cluster) - circle->radius | 
| jbe@0 | 2813   ); | 
| jbe@0 | 2814   PG_FREE_IF_COPY(cluster, 1); | 
| jbe@0 | 2815   PG_RETURN_FLOAT8((distance <= 0) ? 0 : distance); | 
| jbe@0 | 2816 } | 
| jbe@0 | 2817 | 
| jbe@16 | 2818 /* calculate distance between two clusters ("<->" operator) in SQL */ | 
| jbe@16 | 2819 PG_FUNCTION_INFO_V1(pgl_ecluster_distance); | 
| jbe@16 | 2820 Datum pgl_ecluster_distance(PG_FUNCTION_ARGS) { | 
| jbe@16 | 2821   pgl_cluster *cluster1 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@16 | 2822   pgl_cluster *cluster2 = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@16 | 2823   double retval = pgl_cluster_distance(cluster1, cluster2); | 
| jbe@16 | 2824   PG_FREE_IF_COPY(cluster1, 0); | 
| jbe@16 | 2825   PG_FREE_IF_COPY(cluster2, 1); | 
| jbe@16 | 2826   PG_RETURN_FLOAT8(retval); | 
| jbe@16 | 2827 } | 
| jbe@16 | 2828 | 
| jbe@46 | 2829 /* calculate fair distance (see README) between cluster and point with | 
| jbe@46 | 2830    precision denoted by sample count ("<=> operator) in SQL */ | 
| jbe@46 | 2831 PG_FUNCTION_INFO_V1(pgl_ecluster_epoint_sc_fair_distance); | 
| jbe@46 | 2832 Datum pgl_ecluster_epoint_sc_fair_distance(PG_FUNCTION_ARGS) { | 
| jbe@42 | 2833   pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); | 
| jbe@46 | 2834   pgl_point_sc *search = (pgl_point_sc *)PG_GETARG_POINTER(1); | 
| jbe@46 | 2835   double retval = pgl_fair_distance( | 
| jbe@46 | 2836     &search->point, cluster, search->samples | 
| jbe@46 | 2837   ); | 
| jbe@42 | 2838   PG_FREE_IF_COPY(cluster, 0); | 
| jbe@42 | 2839   PG_RETURN_FLOAT8(retval); | 
| jbe@42 | 2840 } | 
| jbe@42 | 2841 | 
| jbe@0 | 2842 | 
| jbe@0 | 2843 /*-----------------------------------------------------------* | 
| jbe@0 | 2844  *  B-tree comparison operators and index support functions  * | 
| jbe@0 | 2845  *-----------------------------------------------------------*/ | 
| jbe@0 | 2846 | 
| jbe@0 | 2847 /* macro for a B-tree operator (without detoasting) */ | 
| jbe@0 | 2848 #define PGL_BTREE_OPER(func, type, cmpfunc, oper) \ | 
| jbe@0 | 2849   PG_FUNCTION_INFO_V1(func); \ | 
| jbe@0 | 2850   Datum func(PG_FUNCTION_ARGS) { \ | 
| jbe@0 | 2851     type *a = (type *)PG_GETARG_POINTER(0); \ | 
| jbe@0 | 2852     type *b = (type *)PG_GETARG_POINTER(1); \ | 
| jbe@0 | 2853     PG_RETURN_BOOL(cmpfunc(a, b) oper 0); \ | 
| jbe@0 | 2854   } | 
| jbe@0 | 2855 | 
| jbe@0 | 2856 /* macro for a B-tree comparison function (without detoasting) */ | 
| jbe@0 | 2857 #define PGL_BTREE_CMP(func, type, cmpfunc) \ | 
| jbe@0 | 2858   PG_FUNCTION_INFO_V1(func); \ | 
| jbe@0 | 2859   Datum func(PG_FUNCTION_ARGS) { \ | 
| jbe@0 | 2860     type *a = (type *)PG_GETARG_POINTER(0); \ | 
| jbe@0 | 2861     type *b = (type *)PG_GETARG_POINTER(1); \ | 
| jbe@0 | 2862     PG_RETURN_INT32(cmpfunc(a, b)); \ | 
| jbe@0 | 2863   } | 
| jbe@0 | 2864 | 
| jbe@0 | 2865 /* macro for a B-tree operator (with detoasting) */ | 
| jbe@0 | 2866 #define PGL_BTREE_OPER_DETOAST(func, type, cmpfunc, oper) \ | 
| jbe@0 | 2867   PG_FUNCTION_INFO_V1(func); \ | 
| jbe@0 | 2868   Datum func(PG_FUNCTION_ARGS) { \ | 
| jbe@0 | 2869     bool res; \ | 
| jbe@0 | 2870     type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \ | 
| jbe@0 | 2871     type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \ | 
| jbe@0 | 2872     res = cmpfunc(a, b) oper 0; \ | 
| jbe@0 | 2873     PG_FREE_IF_COPY(a, 0); \ | 
| jbe@0 | 2874     PG_FREE_IF_COPY(b, 1); \ | 
| jbe@0 | 2875     PG_RETURN_BOOL(res); \ | 
| jbe@0 | 2876   } | 
| jbe@0 | 2877 | 
| jbe@0 | 2878 /* macro for a B-tree comparison function (with detoasting) */ | 
| jbe@0 | 2879 #define PGL_BTREE_CMP_DETOAST(func, type, cmpfunc) \ | 
| jbe@0 | 2880   PG_FUNCTION_INFO_V1(func); \ | 
| jbe@0 | 2881   Datum func(PG_FUNCTION_ARGS) { \ | 
| jbe@0 | 2882     int32_t res; \ | 
| jbe@0 | 2883     type *a = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0)); \ | 
| jbe@0 | 2884     type *b = (type *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); \ | 
| jbe@0 | 2885     res = cmpfunc(a, b); \ | 
| jbe@0 | 2886     PG_FREE_IF_COPY(a, 0); \ | 
| jbe@0 | 2887     PG_FREE_IF_COPY(b, 1); \ | 
| jbe@0 | 2888     PG_RETURN_INT32(res); \ | 
| jbe@0 | 2889   } | 
| jbe@0 | 2890 | 
| jbe@0 | 2891 /* B-tree operators and comparison function for point */ | 
| jbe@0 | 2892 PGL_BTREE_OPER(pgl_btree_epoint_lt, pgl_point, pgl_point_cmp, <) | 
| jbe@0 | 2893 PGL_BTREE_OPER(pgl_btree_epoint_le, pgl_point, pgl_point_cmp, <=) | 
| jbe@0 | 2894 PGL_BTREE_OPER(pgl_btree_epoint_eq, pgl_point, pgl_point_cmp, ==) | 
| jbe@0 | 2895 PGL_BTREE_OPER(pgl_btree_epoint_ne, pgl_point, pgl_point_cmp, !=) | 
| jbe@0 | 2896 PGL_BTREE_OPER(pgl_btree_epoint_ge, pgl_point, pgl_point_cmp, >=) | 
| jbe@0 | 2897 PGL_BTREE_OPER(pgl_btree_epoint_gt, pgl_point, pgl_point_cmp, >) | 
| jbe@0 | 2898 PGL_BTREE_CMP(pgl_btree_epoint_cmp, pgl_point, pgl_point_cmp) | 
| jbe@0 | 2899 | 
| jbe@0 | 2900 /* B-tree operators and comparison function for box */ | 
| jbe@0 | 2901 PGL_BTREE_OPER(pgl_btree_ebox_lt, pgl_box, pgl_box_cmp, <) | 
| jbe@0 | 2902 PGL_BTREE_OPER(pgl_btree_ebox_le, pgl_box, pgl_box_cmp, <=) | 
| jbe@0 | 2903 PGL_BTREE_OPER(pgl_btree_ebox_eq, pgl_box, pgl_box_cmp, ==) | 
| jbe@0 | 2904 PGL_BTREE_OPER(pgl_btree_ebox_ne, pgl_box, pgl_box_cmp, !=) | 
| jbe@0 | 2905 PGL_BTREE_OPER(pgl_btree_ebox_ge, pgl_box, pgl_box_cmp, >=) | 
| jbe@0 | 2906 PGL_BTREE_OPER(pgl_btree_ebox_gt, pgl_box, pgl_box_cmp, >) | 
| jbe@0 | 2907 PGL_BTREE_CMP(pgl_btree_ebox_cmp, pgl_box, pgl_box_cmp) | 
| jbe@0 | 2908 | 
| jbe@0 | 2909 /* B-tree operators and comparison function for circle */ | 
| jbe@0 | 2910 PGL_BTREE_OPER(pgl_btree_ecircle_lt, pgl_circle, pgl_circle_cmp, <) | 
| jbe@0 | 2911 PGL_BTREE_OPER(pgl_btree_ecircle_le, pgl_circle, pgl_circle_cmp, <=) | 
| jbe@0 | 2912 PGL_BTREE_OPER(pgl_btree_ecircle_eq, pgl_circle, pgl_circle_cmp, ==) | 
| jbe@0 | 2913 PGL_BTREE_OPER(pgl_btree_ecircle_ne, pgl_circle, pgl_circle_cmp, !=) | 
| jbe@0 | 2914 PGL_BTREE_OPER(pgl_btree_ecircle_ge, pgl_circle, pgl_circle_cmp, >=) | 
| jbe@0 | 2915 PGL_BTREE_OPER(pgl_btree_ecircle_gt, pgl_circle, pgl_circle_cmp, >) | 
| jbe@0 | 2916 PGL_BTREE_CMP(pgl_btree_ecircle_cmp, pgl_circle, pgl_circle_cmp) | 
| jbe@0 | 2917 | 
| jbe@0 | 2918 | 
| jbe@0 | 2919 /*--------------------------------* | 
| jbe@0 | 2920  *  GiST index support functions  * | 
| jbe@0 | 2921  *--------------------------------*/ | 
| jbe@0 | 2922 | 
| jbe@0 | 2923 /* GiST "consistent" support function */ | 
| jbe@0 | 2924 PG_FUNCTION_INFO_V1(pgl_gist_consistent); | 
| jbe@0 | 2925 Datum pgl_gist_consistent(PG_FUNCTION_ARGS) { | 
| jbe@0 | 2926   GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); | 
| jbe@0 | 2927   pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key); | 
| jbe@0 | 2928   StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); | 
| jbe@0 | 2929   bool *recheck = (bool *)PG_GETARG_POINTER(4); | 
| jbe@0 | 2930   /* demand recheck because index and query methods are lossy */ | 
| jbe@0 | 2931   *recheck = true; | 
| jbe@10 | 2932   /* strategy number aliases for different operators using the same strategy */ | 
| jbe@10 | 2933   strategy %= 100; | 
| jbe@0 | 2934   /* strategy number 11: equality of two points */ | 
| jbe@0 | 2935   if (strategy == 11) { | 
| jbe@0 | 2936     /* query datum is another point */ | 
| jbe@0 | 2937     pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2938     /* convert other point to key */ | 
| jbe@0 | 2939     pgl_pointkey querykey; | 
| jbe@0 | 2940     pgl_point_to_key(query, querykey); | 
| jbe@0 | 2941     /* return true if both keys overlap */ | 
| jbe@0 | 2942     PG_RETURN_BOOL(pgl_keys_overlap(key, querykey)); | 
| jbe@0 | 2943   } | 
| jbe@0 | 2944   /* strategy number 13: equality of two circles */ | 
| jbe@0 | 2945   if (strategy == 13) { | 
| jbe@0 | 2946     /* query datum is another circle */ | 
| jbe@0 | 2947     pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2948     /* convert other circle to key */ | 
| jbe@0 | 2949     pgl_areakey querykey; | 
| jbe@0 | 2950     pgl_circle_to_key(query, querykey); | 
| jbe@0 | 2951     /* return true if both keys overlap */ | 
| jbe@0 | 2952     PG_RETURN_BOOL(pgl_keys_overlap(key, querykey)); | 
| jbe@0 | 2953   } | 
| jbe@0 | 2954   /* for all remaining strategies, keys on empty objects produce no match */ | 
| jbe@0 | 2955   /* (check necessary because query radius may be infinite) */ | 
| jbe@0 | 2956   if (PGL_KEY_IS_EMPTY(key)) PG_RETURN_BOOL(false); | 
| jbe@0 | 2957   /* strategy number 21: overlapping with point */ | 
| jbe@0 | 2958   if (strategy == 21) { | 
| jbe@0 | 2959     /* query datum is a point */ | 
| jbe@0 | 2960     pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2961     /* return true if estimated distance (allowed to be smaller than real | 
| jbe@0 | 2962        distance) between index key and point is zero */ | 
| jbe@0 | 2963     PG_RETURN_BOOL(pgl_estimate_key_distance(key, query) == 0); | 
| jbe@0 | 2964   } | 
| jbe@0 | 2965   /* strategy number 22: (point) overlapping with box */ | 
| jbe@0 | 2966   if (strategy == 22) { | 
| jbe@0 | 2967     /* query datum is a box */ | 
| jbe@0 | 2968     pgl_box *query = (pgl_box *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2969     /* determine bounding box of indexed key */ | 
| jbe@0 | 2970     pgl_box keybox; | 
| jbe@0 | 2971     pgl_key_to_box(key, &keybox); | 
| jbe@0 | 2972     /* return true if query box overlaps with bounding box of indexed key */ | 
| jbe@0 | 2973     PG_RETURN_BOOL(pgl_boxes_overlap(query, &keybox)); | 
| jbe@0 | 2974   } | 
| jbe@0 | 2975   /* strategy number 23: overlapping with circle */ | 
| jbe@0 | 2976   if (strategy == 23) { | 
| jbe@0 | 2977     /* query datum is a circle */ | 
| jbe@0 | 2978     pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 2979     /* return true if estimated distance (allowed to be smaller than real | 
| jbe@0 | 2980        distance) between index key and circle center is smaller than radius */ | 
| jbe@0 | 2981     PG_RETURN_BOOL( | 
| jbe@46 | 2982       (1.0-PGL_SPHEROID_F) *  /* safety margin for lossy operator */ | 
| jbe@46 | 2983       pgl_estimate_key_distance(key, &(query->center)) | 
| jbe@46 | 2984       <= query->radius | 
| jbe@0 | 2985     ); | 
| jbe@0 | 2986   } | 
| jbe@0 | 2987   /* strategy number 24: overlapping with cluster */ | 
| jbe@0 | 2988   if (strategy == 24) { | 
| jbe@0 | 2989     bool retval;  /* return value */ | 
| jbe@0 | 2990     /* query datum is a cluster */ | 
| jbe@0 | 2991     pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@0 | 2992     /* return true if estimated distance (allowed to be smaller than real | 
| jbe@0 | 2993        distance) between index key and circle center is smaller than radius */ | 
| jbe@0 | 2994     retval = ( | 
| jbe@46 | 2995       (1.0-PGL_SPHEROID_F) *  /* safety margin for lossy operator */ | 
| jbe@46 | 2996       pgl_estimate_key_distance(key, &(query->bounding.center)) | 
| jbe@46 | 2997       <= query->bounding.radius | 
| jbe@0 | 2998     ); | 
| jbe@0 | 2999     PG_FREE_IF_COPY(query, 1);  /* free detoasted cluster (if copy) */ | 
| jbe@0 | 3000     PG_RETURN_BOOL(retval); | 
| jbe@0 | 3001   } | 
| jbe@0 | 3002   /* throw error for any unknown strategy number */ | 
| jbe@0 | 3003   elog(ERROR, "unrecognized strategy number: %d", strategy); | 
| jbe@0 | 3004 } | 
| jbe@0 | 3005 | 
| jbe@0 | 3006 /* GiST "union" support function */ | 
| jbe@0 | 3007 PG_FUNCTION_INFO_V1(pgl_gist_union); | 
| jbe@0 | 3008 Datum pgl_gist_union(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3009   GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); | 
| jbe@0 | 3010   pgl_keyptr out;  /* return value (to be palloc'ed) */ | 
| jbe@0 | 3011   int i; | 
| jbe@0 | 3012   /* determine key size */ | 
| jbe@0 | 3013   size_t keysize = PGL_KEY_IS_AREAKEY( | 
| jbe@0 | 3014     (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key) | 
| jbe@0 | 3015   ) ? sizeof (pgl_areakey) : sizeof(pgl_pointkey); | 
| jbe@0 | 3016   /* begin with first key as result */ | 
| jbe@0 | 3017   out = palloc(keysize); | 
| jbe@0 | 3018   memcpy(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[0].key), keysize); | 
| jbe@0 | 3019   /* unite current result with second, third, etc. key */ | 
| jbe@0 | 3020   for (i=1; i<entryvec->n; i++) { | 
| jbe@0 | 3021     pgl_unite_keys(out, (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key)); | 
| jbe@0 | 3022   } | 
| jbe@0 | 3023   /* return result */ | 
| jbe@0 | 3024   PG_RETURN_POINTER(out); | 
| jbe@0 | 3025 } | 
| jbe@0 | 3026 | 
| jbe@0 | 3027 /* GiST "compress" support function for indicis on points */ | 
| jbe@0 | 3028 PG_FUNCTION_INFO_V1(pgl_gist_compress_epoint); | 
| jbe@0 | 3029 Datum pgl_gist_compress_epoint(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3030   GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); | 
| jbe@0 | 3031   GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */ | 
| jbe@0 | 3032   /* only transform new leaves */ | 
| jbe@0 | 3033   if (entry->leafkey) { | 
| jbe@0 | 3034     /* get point to be transformed */ | 
| jbe@0 | 3035     pgl_point *point = (pgl_point *)DatumGetPointer(entry->key); | 
| jbe@0 | 3036     /* allocate memory for key */ | 
| jbe@0 | 3037     pgl_keyptr key = palloc(sizeof(pgl_pointkey)); | 
| jbe@0 | 3038     /* transform point to key */ | 
| jbe@0 | 3039     pgl_point_to_key(point, key); | 
| jbe@0 | 3040     /* create new GISTENTRY structure as return value */ | 
| jbe@0 | 3041     retval = palloc(sizeof(GISTENTRY)); | 
| jbe@0 | 3042     gistentryinit( | 
| jbe@0 | 3043       *retval, PointerGetDatum(key), | 
| jbe@60 | 3044       entry->rel, entry->page, entry->offset, false | 
| jbe@0 | 3045     ); | 
| jbe@0 | 3046   } else { | 
| jbe@0 | 3047     /* inner nodes have already been transformed */ | 
| jbe@0 | 3048     retval = entry; | 
| jbe@0 | 3049   } | 
| jbe@0 | 3050   /* return pointer to old or new GISTENTRY structure */ | 
| jbe@0 | 3051   PG_RETURN_POINTER(retval); | 
| jbe@0 | 3052 } | 
| jbe@0 | 3053 | 
| jbe@0 | 3054 /* GiST "compress" support function for indicis on circles */ | 
| jbe@0 | 3055 PG_FUNCTION_INFO_V1(pgl_gist_compress_ecircle); | 
| jbe@0 | 3056 Datum pgl_gist_compress_ecircle(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3057   GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); | 
| jbe@0 | 3058   GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */ | 
| jbe@0 | 3059   /* only transform new leaves */ | 
| jbe@0 | 3060   if (entry->leafkey) { | 
| jbe@0 | 3061     /* get circle to be transformed */ | 
| jbe@0 | 3062     pgl_circle *circle = (pgl_circle *)DatumGetPointer(entry->key); | 
| jbe@0 | 3063     /* allocate memory for key */ | 
| jbe@0 | 3064     pgl_keyptr key = palloc(sizeof(pgl_areakey)); | 
| jbe@0 | 3065     /* transform circle to key */ | 
| jbe@0 | 3066     pgl_circle_to_key(circle, key); | 
| jbe@0 | 3067     /* create new GISTENTRY structure as return value */ | 
| jbe@0 | 3068     retval = palloc(sizeof(GISTENTRY)); | 
| jbe@0 | 3069     gistentryinit( | 
| jbe@0 | 3070       *retval, PointerGetDatum(key), | 
| jbe@60 | 3071       entry->rel, entry->page, entry->offset, false | 
| jbe@0 | 3072     ); | 
| jbe@0 | 3073   } else { | 
| jbe@0 | 3074     /* inner nodes have already been transformed */ | 
| jbe@0 | 3075     retval = entry; | 
| jbe@0 | 3076   } | 
| jbe@0 | 3077   /* return pointer to old or new GISTENTRY structure */ | 
| jbe@0 | 3078   PG_RETURN_POINTER(retval); | 
| jbe@0 | 3079 } | 
| jbe@0 | 3080 | 
| jbe@0 | 3081 /* GiST "compress" support function for indices on clusters */ | 
| jbe@0 | 3082 PG_FUNCTION_INFO_V1(pgl_gist_compress_ecluster); | 
| jbe@0 | 3083 Datum pgl_gist_compress_ecluster(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3084   GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); | 
| jbe@0 | 3085   GISTENTRY *retval;  /* return value (to be palloc'ed unless set to entry) */ | 
| jbe@0 | 3086   /* only transform new leaves */ | 
| jbe@0 | 3087   if (entry->leafkey) { | 
| jbe@0 | 3088     /* get cluster to be transformed (detoasting necessary!) */ | 
| jbe@0 | 3089     pgl_cluster *cluster = (pgl_cluster *)PG_DETOAST_DATUM(entry->key); | 
| jbe@0 | 3090     /* allocate memory for key */ | 
| jbe@0 | 3091     pgl_keyptr key = palloc(sizeof(pgl_areakey)); | 
| jbe@0 | 3092     /* transform cluster to key */ | 
| jbe@0 | 3093     pgl_circle_to_key(&(cluster->bounding), key); | 
| jbe@0 | 3094     /* create new GISTENTRY structure as return value */ | 
| jbe@0 | 3095     retval = palloc(sizeof(GISTENTRY)); | 
| jbe@0 | 3096     gistentryinit( | 
| jbe@0 | 3097       *retval, PointerGetDatum(key), | 
| jbe@60 | 3098       entry->rel, entry->page, entry->offset, false | 
| jbe@0 | 3099     ); | 
| jbe@0 | 3100     /* free detoasted datum */ | 
| jbe@0 | 3101     if ((void *)cluster != (void *)DatumGetPointer(entry->key)) pfree(cluster); | 
| jbe@0 | 3102   } else { | 
| jbe@0 | 3103     /* inner nodes have already been transformed */ | 
| jbe@0 | 3104     retval = entry; | 
| jbe@0 | 3105   } | 
| jbe@0 | 3106   /* return pointer to old or new GISTENTRY structure */ | 
| jbe@0 | 3107   PG_RETURN_POINTER(retval); | 
| jbe@0 | 3108 } | 
| jbe@0 | 3109 | 
| jbe@0 | 3110 /* GiST "decompress" support function for indices */ | 
| jbe@0 | 3111 PG_FUNCTION_INFO_V1(pgl_gist_decompress); | 
| jbe@0 | 3112 Datum pgl_gist_decompress(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3113   /* return passed pointer without transformation */ | 
| jbe@0 | 3114   PG_RETURN_POINTER(PG_GETARG_POINTER(0)); | 
| jbe@0 | 3115 } | 
| jbe@0 | 3116 | 
| jbe@0 | 3117 /* GiST "penalty" support function */ | 
| jbe@0 | 3118 PG_FUNCTION_INFO_V1(pgl_gist_penalty); | 
| jbe@0 | 3119 Datum pgl_gist_penalty(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3120   GISTENTRY *origentry = (GISTENTRY *)PG_GETARG_POINTER(0); | 
| jbe@0 | 3121   GISTENTRY *newentry = (GISTENTRY *)PG_GETARG_POINTER(1); | 
| jbe@0 | 3122   float *penalty = (float *)PG_GETARG_POINTER(2); | 
| jbe@0 | 3123   /* get original key and key to insert */ | 
| jbe@0 | 3124   pgl_keyptr orig = (pgl_keyptr)DatumGetPointer(origentry->key); | 
| jbe@0 | 3125   pgl_keyptr new = (pgl_keyptr)DatumGetPointer(newentry->key); | 
| jbe@0 | 3126   /* copy original key */ | 
| jbe@0 | 3127   union { pgl_pointkey pointkey; pgl_areakey areakey; } union_key; | 
| jbe@0 | 3128   if (PGL_KEY_IS_AREAKEY(orig)) { | 
| jbe@0 | 3129     memcpy(union_key.areakey, orig, sizeof(union_key.areakey)); | 
| jbe@0 | 3130   } else { | 
| jbe@0 | 3131     memcpy(union_key.pointkey, orig, sizeof(union_key.pointkey)); | 
| jbe@0 | 3132   } | 
| jbe@0 | 3133   /* calculate union of both keys */ | 
| jbe@0 | 3134   pgl_unite_keys((pgl_keyptr)&union_key, new); | 
| jbe@0 | 3135   /* penalty equal to reduction of key length (logarithm of added area) */ | 
| jbe@0 | 3136   /* (return value by setting referenced value and returning pointer) */ | 
| jbe@0 | 3137   *penalty = ( | 
| jbe@0 | 3138     PGL_KEY_NODEDEPTH(orig) - PGL_KEY_NODEDEPTH((pgl_keyptr)&union_key) | 
| jbe@0 | 3139   ); | 
| jbe@0 | 3140   PG_RETURN_POINTER(penalty); | 
| jbe@0 | 3141 } | 
| jbe@0 | 3142 | 
| jbe@0 | 3143 /* GiST "picksplit" support function */ | 
| jbe@0 | 3144 PG_FUNCTION_INFO_V1(pgl_gist_picksplit); | 
| jbe@0 | 3145 Datum pgl_gist_picksplit(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3146   GistEntryVector *entryvec = (GistEntryVector *)PG_GETARG_POINTER(0); | 
| jbe@0 | 3147   GIST_SPLITVEC *v = (GIST_SPLITVEC *)PG_GETARG_POINTER(1); | 
| jbe@57 | 3148   OffsetNumber i;  /* between FirstOffsetNumber and entryvec->n (exclusive) */ | 
| jbe@0 | 3149   union { | 
| jbe@0 | 3150     pgl_pointkey pointkey; | 
| jbe@0 | 3151     pgl_areakey areakey; | 
| jbe@0 | 3152   } union_all;  /* union of all keys (to be calculated from scratch) | 
| jbe@0 | 3153                    (later cut in half) */ | 
| jbe@0 | 3154   int is_areakey = PGL_KEY_IS_AREAKEY( | 
| jbe@0 | 3155     (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key) | 
| jbe@0 | 3156   ); | 
| jbe@0 | 3157   int keysize = is_areakey ? sizeof(pgl_areakey) : sizeof(pgl_pointkey); | 
| jbe@0 | 3158   pgl_keyptr unionL = palloc(keysize);  /* union of keys that go left */ | 
| jbe@0 | 3159   pgl_keyptr unionR = palloc(keysize);  /* union of keys that go right */ | 
| jbe@0 | 3160   pgl_keyptr key;  /* current key to be processed */ | 
| jbe@0 | 3161   /* allocate memory for array of left and right keys, set counts to zero */ | 
| jbe@0 | 3162   v->spl_left = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber)); | 
| jbe@0 | 3163   v->spl_nleft = 0; | 
| jbe@0 | 3164   v->spl_right = (OffsetNumber *)palloc(entryvec->n * sizeof(OffsetNumber)); | 
| jbe@0 | 3165   v->spl_nright = 0; | 
| jbe@0 | 3166   /* calculate union of all keys from scratch */ | 
| jbe@0 | 3167   memcpy( | 
| jbe@0 | 3168     (pgl_keyptr)&union_all, | 
| jbe@0 | 3169     (pgl_keyptr)DatumGetPointer(entryvec->vector[FirstOffsetNumber].key), | 
| jbe@0 | 3170     keysize | 
| jbe@0 | 3171   ); | 
| jbe@0 | 3172   for (i=FirstOffsetNumber+1; i<entryvec->n; i=OffsetNumberNext(i)) { | 
| jbe@0 | 3173     pgl_unite_keys( | 
| jbe@0 | 3174       (pgl_keyptr)&union_all, | 
| jbe@0 | 3175       (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key) | 
| jbe@0 | 3176     ); | 
| jbe@0 | 3177   } | 
| jbe@0 | 3178   /* check if trivial split is necessary due to exhausted key length */ | 
| jbe@0 | 3179   /* (Note: keys for empty objects must have node depth set to maximum) */ | 
| jbe@0 | 3180   if (PGL_KEY_NODEDEPTH((pgl_keyptr)&union_all) == ( | 
| jbe@0 | 3181     is_areakey ? PGL_AREAKEY_MAXDEPTH : PGL_POINTKEY_MAXDEPTH | 
| jbe@0 | 3182   )) { | 
| jbe@0 | 3183     /* half of all keys go left */ | 
| jbe@0 | 3184     for ( | 
| jbe@0 | 3185       i=FirstOffsetNumber; | 
| jbe@0 | 3186       i<FirstOffsetNumber+(entryvec->n - FirstOffsetNumber)/2; | 
| jbe@0 | 3187       i=OffsetNumberNext(i) | 
| jbe@0 | 3188     ) { | 
| jbe@0 | 3189       /* pointer to current key */ | 
| jbe@0 | 3190       key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); | 
| jbe@0 | 3191       /* update unionL */ | 
| jbe@0 | 3192       /* check if key is first key that goes left */ | 
| jbe@0 | 3193       if (!v->spl_nleft) { | 
| jbe@0 | 3194         /* first key that goes left is just copied to unionL */ | 
| jbe@0 | 3195         memcpy(unionL, key, keysize); | 
| jbe@0 | 3196       } else { | 
| jbe@0 | 3197         /* unite current value and next key */ | 
| jbe@0 | 3198         pgl_unite_keys(unionL, key); | 
| jbe@0 | 3199       } | 
| jbe@0 | 3200       /* append offset number to list of keys that go left */ | 
| jbe@0 | 3201       v->spl_left[v->spl_nleft++] = i; | 
| jbe@0 | 3202     } | 
| jbe@0 | 3203     /* other half goes right */ | 
| jbe@0 | 3204     for ( | 
| jbe@0 | 3205       i=FirstOffsetNumber+(entryvec->n - FirstOffsetNumber)/2; | 
| jbe@0 | 3206       i<entryvec->n; | 
| jbe@0 | 3207       i=OffsetNumberNext(i) | 
| jbe@0 | 3208     ) { | 
| jbe@0 | 3209       /* pointer to current key */ | 
| jbe@0 | 3210       key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); | 
| jbe@0 | 3211       /* update unionR */ | 
| jbe@0 | 3212       /* check if key is first key that goes right */ | 
| jbe@0 | 3213       if (!v->spl_nright) { | 
| jbe@0 | 3214         /* first key that goes right is just copied to unionR */ | 
| jbe@0 | 3215         memcpy(unionR, key, keysize); | 
| jbe@0 | 3216       } else { | 
| jbe@0 | 3217         /* unite current value and next key */ | 
| jbe@0 | 3218         pgl_unite_keys(unionR, key); | 
| jbe@0 | 3219       } | 
| jbe@0 | 3220       /* append offset number to list of keys that go right */ | 
| jbe@0 | 3221       v->spl_right[v->spl_nright++] = i; | 
| jbe@0 | 3222     } | 
| jbe@0 | 3223   } | 
| jbe@0 | 3224   /* otherwise, a non-trivial split is possible */ | 
| jbe@0 | 3225   else { | 
| jbe@0 | 3226     /* cut covered area in half */ | 
| jbe@0 | 3227     /* (union_all then refers to area of keys that go left) */ | 
| jbe@0 | 3228     /* check if union of all keys covers empty and non-empty objects */ | 
| jbe@0 | 3229     if (PGL_KEY_IS_UNIVERSAL((pgl_keyptr)&union_all)) { | 
| jbe@0 | 3230       /* if yes, split into empty and non-empty objects */ | 
| jbe@0 | 3231       pgl_key_set_empty((pgl_keyptr)&union_all); | 
| jbe@0 | 3232     } else { | 
| jbe@0 | 3233       /* otherwise split by next bit */ | 
| jbe@0 | 3234       ((pgl_keyptr)&union_all)[PGL_KEY_NODEDEPTH_OFFSET]++; | 
| jbe@0 | 3235       /* NOTE: type bit conserved */ | 
| jbe@0 | 3236     } | 
| jbe@0 | 3237     /* determine for each key if it goes left or right */ | 
| jbe@0 | 3238     for (i=FirstOffsetNumber; i<entryvec->n; i=OffsetNumberNext(i)) { | 
| jbe@0 | 3239       /* pointer to current key */ | 
| jbe@0 | 3240       key = (pgl_keyptr)DatumGetPointer(entryvec->vector[i].key); | 
| jbe@0 | 3241       /* keys within one half of the area go left */ | 
| jbe@0 | 3242       if (pgl_keys_overlap((pgl_keyptr)&union_all, key)) { | 
| jbe@0 | 3243         /* update unionL */ | 
| jbe@0 | 3244         /* check if key is first key that goes left */ | 
| jbe@0 | 3245         if (!v->spl_nleft) { | 
| jbe@0 | 3246           /* first key that goes left is just copied to unionL */ | 
| jbe@0 | 3247           memcpy(unionL, key, keysize); | 
| jbe@0 | 3248         } else { | 
| jbe@0 | 3249           /* unite current value of unionL and processed key */ | 
| jbe@0 | 3250           pgl_unite_keys(unionL, key); | 
| jbe@0 | 3251         } | 
| jbe@0 | 3252         /* append offset number to list of keys that go left */ | 
| jbe@0 | 3253         v->spl_left[v->spl_nleft++] = i; | 
| jbe@0 | 3254       } | 
| jbe@0 | 3255       /* the other keys go right */ | 
| jbe@0 | 3256       else { | 
| jbe@0 | 3257         /* update unionR */ | 
| jbe@0 | 3258         /* check if key is first key that goes right */ | 
| jbe@0 | 3259         if (!v->spl_nright) { | 
| jbe@0 | 3260           /* first key that goes right is just copied to unionR */ | 
| jbe@0 | 3261           memcpy(unionR, key, keysize); | 
| jbe@0 | 3262         } else { | 
| jbe@0 | 3263           /* unite current value of unionR and processed key */ | 
| jbe@0 | 3264           pgl_unite_keys(unionR, key); | 
| jbe@0 | 3265         } | 
| jbe@0 | 3266         /* append offset number to list of keys that go right */ | 
| jbe@0 | 3267         v->spl_right[v->spl_nright++] = i; | 
| jbe@0 | 3268       } | 
| jbe@0 | 3269     } | 
| jbe@0 | 3270   } | 
| jbe@0 | 3271   /* store unions in return value */ | 
| jbe@0 | 3272   v->spl_ldatum = PointerGetDatum(unionL); | 
| jbe@0 | 3273   v->spl_rdatum = PointerGetDatum(unionR); | 
| jbe@0 | 3274   /* return all results */ | 
| jbe@0 | 3275   PG_RETURN_POINTER(v); | 
| jbe@0 | 3276 } | 
| jbe@0 | 3277 | 
| jbe@0 | 3278 /* GiST "same"/"equal" support function */ | 
| jbe@0 | 3279 PG_FUNCTION_INFO_V1(pgl_gist_same); | 
| jbe@0 | 3280 Datum pgl_gist_same(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3281   pgl_keyptr key1 = (pgl_keyptr)PG_GETARG_POINTER(0); | 
| jbe@0 | 3282   pgl_keyptr key2 = (pgl_keyptr)PG_GETARG_POINTER(1); | 
| jbe@0 | 3283   bool *boolptr = (bool *)PG_GETARG_POINTER(2); | 
| jbe@0 | 3284   /* two keys are equal if they are binary equal */ | 
| jbe@0 | 3285   /* (return result by setting referenced boolean and returning pointer) */ | 
| jbe@0 | 3286   *boolptr = !memcmp( | 
| jbe@0 | 3287     key1, | 
| jbe@0 | 3288     key2, | 
| jbe@0 | 3289     PGL_KEY_IS_AREAKEY(key1) ? sizeof(pgl_areakey) : sizeof(pgl_pointkey) | 
| jbe@0 | 3290   ); | 
| jbe@0 | 3291   PG_RETURN_POINTER(boolptr); | 
| jbe@0 | 3292 } | 
| jbe@0 | 3293 | 
| jbe@0 | 3294 /* GiST "distance" support function */ | 
| jbe@0 | 3295 PG_FUNCTION_INFO_V1(pgl_gist_distance); | 
| jbe@0 | 3296 Datum pgl_gist_distance(PG_FUNCTION_ARGS) { | 
| jbe@0 | 3297   GISTENTRY *entry = (GISTENTRY *)PG_GETARG_POINTER(0); | 
| jbe@0 | 3298   pgl_keyptr key = (pgl_keyptr)DatumGetPointer(entry->key); | 
| jbe@0 | 3299   StrategyNumber strategy = (StrategyNumber)PG_GETARG_UINT16(2); | 
| jbe@0 | 3300   bool *recheck = (bool *)PG_GETARG_POINTER(4); | 
| jbe@0 | 3301   double distance;  /* return value */ | 
| jbe@0 | 3302   /* demand recheck because distance is just an estimation */ | 
| jbe@0 | 3303   /* (real distance may be bigger) */ | 
| jbe@0 | 3304   *recheck = true; | 
| jbe@10 | 3305   /* strategy number aliases for different operators using the same strategy */ | 
| jbe@10 | 3306   strategy %= 100; | 
| jbe@0 | 3307   /* strategy number 31: distance to point */ | 
| jbe@0 | 3308   if (strategy == 31) { | 
| jbe@0 | 3309     /* query datum is a point */ | 
| jbe@0 | 3310     pgl_point *query = (pgl_point *)PG_GETARG_POINTER(1); | 
| jbe@0 | 3311     /* use pgl_estimate_pointkey_distance() function to compute result */ | 
| jbe@0 | 3312     distance = pgl_estimate_key_distance(key, query); | 
| jbe@0 | 3313     /* avoid infinity (reserved!) */ | 
| jbe@0 | 3314     if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; | 
| jbe@0 | 3315     /* return result */ | 
| jbe@0 | 3316     PG_RETURN_FLOAT8(distance); | 
| jbe@0 | 3317   } | 
| jbe@0 | 3318   /* strategy number 33: distance to circle */ | 
| jbe@0 | 3319   if (strategy == 33) { | 
| jbe@0 | 3320     /* query datum is a circle */ | 
| jbe@0 | 3321     pgl_circle *query = (pgl_circle *)PG_GETARG_POINTER(1); | 
| jbe@0 | 3322     /* estimate distance to circle center and substract circle radius */ | 
| jbe@0 | 3323     distance = ( | 
| jbe@0 | 3324       pgl_estimate_key_distance(key, &(query->center)) - query->radius | 
| jbe@0 | 3325     ); | 
| jbe@0 | 3326     /* convert non-positive values to zero and avoid infinity (reserved!) */ | 
| jbe@0 | 3327     if (distance <= 0) distance = 0; | 
| jbe@0 | 3328     else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; | 
| jbe@0 | 3329     /* return result */ | 
| jbe@0 | 3330     PG_RETURN_FLOAT8(distance); | 
| jbe@0 | 3331   } | 
| jbe@0 | 3332   /* strategy number 34: distance to cluster */ | 
| jbe@0 | 3333   if (strategy == 34) { | 
| jbe@0 | 3334     /* query datum is a cluster */ | 
| jbe@0 | 3335     pgl_cluster *query = (pgl_cluster *)PG_DETOAST_DATUM(PG_GETARG_DATUM(1)); | 
| jbe@0 | 3336     /* estimate distance to bounding center and substract bounding radius */ | 
| jbe@0 | 3337     distance = ( | 
| jbe@0 | 3338       pgl_estimate_key_distance(key, &(query->bounding.center)) - | 
| jbe@0 | 3339       query->bounding.radius | 
| jbe@0 | 3340     ); | 
| jbe@0 | 3341     /* convert non-positive values to zero and avoid infinity (reserved!) */ | 
| jbe@0 | 3342     if (distance <= 0) distance = 0; | 
| jbe@0 | 3343     else if (!isfinite(distance)) distance = PGL_ULTRA_DISTANCE; | 
| jbe@0 | 3344     /* free detoasted cluster (if copy) */ | 
| jbe@0 | 3345     PG_FREE_IF_COPY(query, 1); | 
| jbe@0 | 3346     /* return result */ | 
| jbe@0 | 3347     PG_RETURN_FLOAT8(distance); | 
| jbe@0 | 3348   } | 
| jbe@0 | 3349   /* throw error for any unknown strategy number */ | 
| jbe@0 | 3350   elog(ERROR, "unrecognized strategy number: %d", strategy); | 
| jbe@0 | 3351 } | 
| jbe@0 | 3352 |