-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlatlon-v0009.c
3352 lines (3185 loc) · 131 KB
/
latlon-v0009.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*-------------*
* C prelude *
*-------------*/
#include "postgres.h"
#include "fmgr.h"
#include "libpq/pqformat.h"
#include "access/gist.h"
#include "access/stratnum.h"
#include "utils/array.h"
#include <limits.h>
#include <math.h>
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
#if INT_MAX < 2147483647
#error Expected int type to be at least 32 bit wide
#endif
/*---------------------------------*
* distance calculation on earth *
* (using WGS-84 spheroid) *
*---------------------------------*/
/* WGS-84 spheroid with following parameters:
semi-major axis a = 6378137
semi-minor axis b = a * (1 - 1/298.257223563)
estimated diameter = 2 * (2*a+b)/3
*/
#define PGL_SPHEROID_A 6378137.0 /* semi major axis */
#define PGL_SPHEROID_F (1.0/298.257223563) /* flattening */
#define PGL_SPHEROID_B (PGL_SPHEROID_A * (1.0-PGL_SPHEROID_F))
#define PGL_EPS2 ( ( PGL_SPHEROID_A * PGL_SPHEROID_A - \
PGL_SPHEROID_B * PGL_SPHEROID_B ) / \
( PGL_SPHEROID_A * PGL_SPHEROID_A ) )
#define PGL_SUBEPS2 (1.0-PGL_EPS2)
#define PGL_RADIUS ((2.0*PGL_SPHEROID_A + PGL_SPHEROID_B) / 3.0)
#define PGL_DIAMETER (2.0 * PGL_RADIUS)
#define PGL_SCALE (PGL_SPHEROID_A / PGL_DIAMETER) /* semi-major ref. */
#define PGL_MAXDIST (PGL_RADIUS * M_PI) /* maximum distance */
#define PGL_FADELIMIT (PGL_MAXDIST / 3.0) /* 1/6 circumference */
/* calculate distance between two points on earth (given in degrees) */
static inline double pgl_distance(
double lat1, double lon1, double lat2, double lon2
) {
float8 lat1cos, lat1sin, lat2cos, lat2sin, lon2cos, lon2sin;
float8 nphi1, nphi2, x1, z1, x2, y2, z2, g, s, t;
/* normalize delta longitude (lon2 > 0 && lon1 = 0) */
/* lon1 = 0 (not used anymore) */
lon2 = fabs(lon2-lon1);
/* convert to radians (first divide, then multiply) */
lat1 = (lat1 / 180.0) * M_PI;
lat2 = (lat2 / 180.0) * M_PI;
lon2 = (lon2 / 180.0) * M_PI;
/* make lat2 >= lat1 to ensure reversal-symmetry despite floating point
operations (lon2 >= lon1 is already ensured in a previous step) */
if (lat2 < lat1) { float8 swap = lat1; lat1 = lat2; lat2 = swap; }
/* calculate 3d coordinates on scaled ellipsoid which has an average diameter
of 1.0 */
lat1cos = cos(lat1); lat1sin = sin(lat1);
lat2cos = cos(lat2); lat2sin = sin(lat2);
lon2cos = cos(lon2); lon2sin = sin(lon2);
nphi1 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat1sin * lat1sin);
nphi2 = PGL_SCALE / sqrt(1 - PGL_EPS2 * lat2sin * lat2sin);
x1 = nphi1 * lat1cos;
z1 = nphi1 * PGL_SUBEPS2 * lat1sin;
x2 = nphi2 * lat2cos * lon2cos;
y2 = nphi2 * lat2cos * lon2sin;
z2 = nphi2 * PGL_SUBEPS2 * lat2sin;
/* calculate tunnel distance through scaled (diameter 1.0) ellipsoid */
g = sqrt((x2-x1)*(x2-x1) + y2*y2 + (z2-z1)*(z2-z1));
/* convert tunnel distance through scaled ellipsoid to approximated surface
distance on original ellipsoid */
if (g > 1.0) g = 1.0;
s = PGL_DIAMETER * asin(g);
/* return result only if small enough to be precise (less than 1/3 of
maximum possible distance) */
if (s <= PGL_FADELIMIT) return s;
/* calculate tunnel distance to antipodal point through scaled ellipsoid */
g = sqrt((x2+x1)*(x2+x1) + y2*y2 + (z2+z1)*(z2+z1));
/* convert tunnel distance to antipodal point through scaled ellipsoid to
approximated surface distance to antipodal point on original ellipsoid */
if (g > 1.0) g = 1.0;
t = PGL_DIAMETER * asin(g);
/* surface distance between original points can now be approximated by
substracting antipodal distance from maximum possible distance;
return result only if small enough (less than 1/3 of maximum possible
distance) */
if (t <= PGL_FADELIMIT) return PGL_MAXDIST-t;
/* otherwise crossfade direct and antipodal result to ensure monotonicity */
return (
(s * (t-PGL_FADELIMIT) + (PGL_MAXDIST-t) * (s-PGL_FADELIMIT)) /
(s + t - 2*PGL_FADELIMIT)
);
}
/* finite distance that can not be reached on earth */
#define PGL_ULTRA_DISTANCE (3 * PGL_MAXDIST)
/*--------------------------------*
* simple geographic data types *
*--------------------------------*/
/* point on earth given by latitude and longitude in degrees */
/* (type "epoint" in SQL) */
typedef struct {
double lat; /* between -90 and 90 (both inclusive) */
double lon; /* between -180 and 180 (both inclusive) */
} pgl_point;
/* box delimited by two parallels and two meridians (all in degrees) */
/* (type "ebox" in SQL) */
typedef struct {
double lat_min; /* between -90 and 90 (both inclusive) */
double lat_max; /* between -90 and 90 (both inclusive) */
double lon_min; /* between -180 and 180 (both inclusive) */
double lon_max; /* between -180 and 180 (both inclusive) */
/* if lat_min > lat_max, then box is empty */
/* if lon_min > lon_max, then 180th meridian is crossed */
} pgl_box;
/* circle on earth surface (for radial searches with fixed radius) */
/* (type "ecircle" in SQL) */
typedef struct {
pgl_point center;
double radius; /* positive (including +0 but excluding -0), or -INFINITY */
/* A negative radius (i.e. -INFINITY) denotes nothing (i.e. no point),
zero radius (0) denotes a single point,
a finite radius (0 < radius < INFINITY) denotes a filled circle, and
a radius of INFINITY is valid and means complete coverage of earth. */
} pgl_circle;
/*----------------------------------*
* geographic "cluster" data type *
*----------------------------------*/
/* A cluster is a collection of points, paths, outlines, and polygons. If two
polygons in a cluster overlap, the area covered by both polygons does not
belong to the cluster. This way, a cluster can be used to describe complex
shapes like polygons with holes. Outlines are non-filled polygons. Paths are
open by default (i.e. the last point in the list is not connected with the
first point in the list). Note that each outline or polygon in a cluster
must cover a longitude range of less than 180 degrees to avoid ambiguities.
Areas which are larger may be split into multiple polygons. */
/* maximum number of points in a cluster */
/* (limited to avoid integer overflows, e.g. when allocating memory) */
#define PGL_CLUSTER_MAXPOINTS 16777216
/* types of cluster entries */
#define PGL_ENTRY_POINT 1 /* a point */
#define PGL_ENTRY_PATH 2 /* a path from first point to last point */
#define PGL_ENTRY_OUTLINE 3 /* a non-filled polygon with given vertices */
#define PGL_ENTRY_POLYGON 4 /* a filled polygon with given vertices */
/* Entries of a cluster are described by two different structs: pgl_newentry
and pgl_entry. The first is used only during construction of a cluster, the
second is used in all other cases (e.g. when reading clusters from the
database, performing operations, etc). */
/* entry for new geographic cluster during construction of that cluster */
typedef struct {
int32_t entrytype;
int32_t npoints;
pgl_point *points; /* pointer to an array of points (pgl_point) */
} pgl_newentry;
/* entry of geographic cluster */
typedef struct {
int32_t entrytype; /* type of entry: point, path, outline, polygon */
int32_t npoints; /* number of stored points (set to 1 for point entry) */
int32_t offset; /* offset of pgl_point array from cluster base address */
/* use macro PGL_ENTRY_POINTS to obtain a pointer to the array of points */
} pgl_entry;
/* geographic cluster which is a collection of points, (open) paths, polygons,
and outlines (non-filled polygons) */
typedef struct {
char header[VARHDRSZ]; /* PostgreSQL header for variable size data types */
int32_t nentries; /* number of stored points */
pgl_circle bounding; /* bounding circle */
/* Note: bounding circle ensures alignment of pgl_cluster for points */
pgl_entry entries[FLEXIBLE_ARRAY_MEMBER]; /* var-length data */
} pgl_cluster;
/* macro to determine memory alignment of points */
/* (needed to store pgl_point array after entries in pgl_cluster) */
typedef struct { char dummy; pgl_point aligned; } pgl_point_alignment;
#define PGL_POINT_ALIGNMENT offsetof(pgl_point_alignment, aligned)
/* macro to extract a pointer to the array of points of a cluster entry */
#define PGL_ENTRY_POINTS(cluster, idx) \
((pgl_point *)(((intptr_t)cluster)+(cluster)->entries[idx].offset))
/* convert pgl_newentry array to pgl_cluster */
/* NOTE: requires pgl_finalize_cluster to be called to finalize result */
static pgl_cluster *pgl_new_cluster(int nentries, pgl_newentry *entries) {
int i; /* index of current entry */
int npoints = 0; /* number of points in whole cluster */
int entry_npoints; /* number of points in current entry */
int points_offset = PGL_POINT_ALIGNMENT * (
( offsetof(pgl_cluster, entries) +
nentries * sizeof(pgl_entry) +
PGL_POINT_ALIGNMENT - 1
) / PGL_POINT_ALIGNMENT
); /* offset of pgl_point array from base address (considering alignment) */
pgl_cluster *cluster; /* new cluster to be returned */
/* determine total number of points */
for (i=0; i<nentries; i++) npoints += entries[i].npoints;
/* allocate memory for cluster (including entries and points) */
cluster = palloc(points_offset + npoints * sizeof(pgl_point));
/* re-count total number of points to determine offset for each entry */
npoints = 0;
/* copy entries and points */
for (i=0; i<nentries; i++) {
/* determine number of points in entry */
entry_npoints = entries[i].npoints;
/* copy entry */
cluster->entries[i].entrytype = entries[i].entrytype;
cluster->entries[i].npoints = entry_npoints;
/* calculate offset (in bytes) of pgl_point array */
cluster->entries[i].offset = points_offset + npoints * sizeof(pgl_point);
/* copy points */
memcpy(
PGL_ENTRY_POINTS(cluster, i),
entries[i].points,
entry_npoints * sizeof(pgl_point)
);
/* update total number of points processed */
npoints += entry_npoints;
}
/* set number of entries in cluster */
cluster->nentries = nentries;
/* set PostgreSQL header for variable sized data */
SET_VARSIZE(cluster, points_offset + npoints * sizeof(pgl_point));
/* return newly created cluster */
return cluster;
}
/*----------------------------------------------*
* Geographic point with integer sample count *
* (needed for fair distance calculation) *
*----------------------------------------------*/
typedef struct {
pgl_point point; /* NOTE: point first to allow C cast to pgl_point */
int32 samples;
} pgl_point_sc;
/*----------------------------------------*
* C functions on geographic data types *
*----------------------------------------*/
/* round latitude or longitude to 12 digits after decimal point */
static inline double pgl_round(double val) {
return round(val * 1e12) / 1e12;
}
/* compare two points */
/* (equality when same point on earth is described, otherwise an arbitrary
linear order) */
static int pgl_point_cmp(pgl_point *point1, pgl_point *point2) {
double lon1, lon2; /* modified longitudes for special cases */
/* use latitude as first ordering criterion */
if (point1->lat < point2->lat) return -1;
if (point1->lat > point2->lat) return 1;
/* determine modified longitudes (considering special case of poles and
180th meridian which can be described as W180 or E180) */
if (point1->lat == -90 || point1->lat == 90) lon1 = 0;
else if (point1->lon == 180) lon1 = -180;
else lon1 = point1->lon;
if (point2->lat == -90 || point2->lat == 90) lon2 = 0;
else if (point2->lon == 180) lon2 = -180;
else lon2 = point2->lon;
/* use (modified) longitude as secondary ordering criterion */
if (lon1 < lon2) return -1;
if (lon1 > lon2) return 1;
/* no difference found, points are equal */
return 0;
}
/* compare two boxes */
/* (equality when same box on earth is described, otherwise an arbitrary linear
order) */
static int pgl_box_cmp(pgl_box *box1, pgl_box *box2) {
/* two empty boxes are equal, and an empty box is always considered "less
than" a non-empty box */
if (box1->lat_min> box1->lat_max && box2->lat_min<=box2->lat_max) return -1;
if (box1->lat_min> box1->lat_max && box2->lat_min> box2->lat_max) return 0;
if (box1->lat_min<=box1->lat_max && box2->lat_min> box2->lat_max) return 1;
/* use southern border as first ordering criterion */
if (box1->lat_min < box2->lat_min) return -1;
if (box1->lat_min > box2->lat_min) return 1;
/* use northern border as second ordering criterion */
if (box1->lat_max < box2->lat_max) return -1;
if (box1->lat_max > box2->lat_max) return 1;
/* use western border as third ordering criterion */
if (box1->lon_min < box2->lon_min) return -1;
if (box1->lon_min > box2->lon_min) return 1;
/* use eastern border as fourth ordering criterion */
if (box1->lon_max < box2->lon_max) return -1;
if (box1->lon_max > box2->lon_max) return 1;
/* no difference found, boxes are equal */
return 0;
}
/* compare two circles */
/* (equality when same circle on earth is described, otherwise an arbitrary
linear order) */
static int pgl_circle_cmp(pgl_circle *circle1, pgl_circle *circle2) {
/* two circles with same infinite radius (positive or negative infinity) are
considered equal independently of center point */
if (
!isfinite(circle1->radius) && !isfinite(circle2->radius) &&
circle1->radius == circle2->radius
) return 0;
/* use radius as first ordering criterion */
if (circle1->radius < circle2->radius) return -1;
if (circle1->radius > circle2->radius) return 1;
/* use center point as secondary ordering criterion */
return pgl_point_cmp(&(circle1->center), &(circle2->center));
}
/* set box to empty box*/
static void pgl_box_set_empty(pgl_box *box) {
box->lat_min = INFINITY;
box->lat_max = -INFINITY;
box->lon_min = 0;
box->lon_max = 0;
}
/* check if point is inside a box */
static bool pgl_point_in_box(pgl_point *point, pgl_box *box) {
return (
point->lat >= box->lat_min && point->lat <= box->lat_max && (
(box->lon_min > box->lon_max) ? (
/* box crosses 180th meridian */
point->lon >= box->lon_min || point->lon <= box->lon_max
) : (
/* box does not cross the 180th meridian */
point->lon >= box->lon_min && point->lon <= box->lon_max
)
)
);
}
/* check if two boxes overlap */
static bool pgl_boxes_overlap(pgl_box *box1, pgl_box *box2) {
return (
box2->lat_max >= box2->lat_min && /* ensure box2 is not empty */
( box2->lat_min >= box1->lat_min || box2->lat_max >= box1->lat_min ) &&
( box2->lat_min <= box1->lat_max || box2->lat_max <= box1->lat_max ) && (
(
/* check if one and only one box crosses the 180th meridian */
((box1->lon_min > box1->lon_max) ? 1 : 0) ^
((box2->lon_min > box2->lon_max) ? 1 : 0)
) ? (
/* exactly one box crosses the 180th meridian */
box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min ||
box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max
) : (
/* no box or both boxes cross the 180th meridian */
(
(box2->lon_min >= box1->lon_min || box2->lon_max >= box1->lon_min) &&
(box2->lon_min <= box1->lon_max || box2->lon_max <= box1->lon_max)
) ||
/* handle W180 == E180 */
( box1->lon_min == -180 && box2->lon_max == 180 ) ||
( box2->lon_min == -180 && box1->lon_max == 180 )
)
)
);
}
/* check unambiguousness of east/west orientation of cluster entries and set
bounding circle of cluster */
static bool pgl_finalize_cluster(pgl_cluster *cluster) {
int i, j; /* i: index of entry, j: index of point in entry */
int npoints; /* number of points in entry */
int total_npoints = 0; /* total number of points in cluster */
pgl_point *points; /* points in entry */
int lon_dir; /* first point of entry west (-1) or east (+1) */
double lon_break = 0; /* antipodal longitude of first point in entry */
double lon_min, lon_max; /* covered longitude range of entry */
double value; /* temporary variable */
/* reset bounding circle center to empty circle at 0/0 coordinates */
cluster->bounding.center.lat = 0;
cluster->bounding.center.lon = 0;
cluster->bounding.radius = -INFINITY;
/* if cluster is not empty */
if (cluster->nentries != 0) {
/* iterate over all cluster entries and ensure they each cover a longitude
range less than 180 degrees */
for (i=0; i<cluster->nentries; i++) {
/* get properties of entry */
npoints = cluster->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster, i);
/* get longitude of first point of entry */
value = points[0].lon;
/* initialize lon_min and lon_max with longitude of first point */
lon_min = value;
lon_max = value;
/* determine east/west orientation of first point and calculate antipodal
longitude (Note: rounding required here) */
if (value < 0) { lon_dir = -1; lon_break = pgl_round(value + 180); }
else if (value > 0) { lon_dir = 1; lon_break = pgl_round(value - 180); }
else lon_dir = 0;
/* iterate over all other points in entry */
for (j=1; j<npoints; j++) {
/* consider longitude wrap-around */
value = points[j].lon;
if (lon_dir<0 && value>lon_break) value = pgl_round(value - 360);
else if (lon_dir>0 && value<lon_break) value = pgl_round(value + 360);
/* update lon_min and lon_max */
if (value < lon_min) lon_min = value;
else if (value > lon_max) lon_max = value;
/* return false if 180 degrees or more are covered */
if (lon_max - lon_min >= 180) return false;
}
}
/* iterate over all points of all entries and calculate arbitrary center
point for bounding circle (best if center point minimizes the radius,
but some error is allowed here) */
for (i=0; i<cluster->nentries; i++) {
/* get properties of entry */
npoints = cluster->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster, i);
/* check if first entry */
if (i==0) {
/* get longitude of first point of first entry in whole cluster */
value = points[0].lon;
/* initialize lon_min and lon_max with longitude of first point of
first entry in whole cluster (used to determine if whole cluster
covers a longitude range of 180 degrees or more) */
lon_min = value;
lon_max = value;
/* determine east/west orientation of first point and calculate
antipodal longitude (Note: rounding not necessary here) */
if (value < 0) { lon_dir = -1; lon_break = value + 180; }
else if (value > 0) { lon_dir = 1; lon_break = value - 180; }
else lon_dir = 0;
}
/* iterate over all points in entry */
for (j=0; j<npoints; j++) {
/* longitude wrap-around (Note: rounding not necessary here) */
value = points[j].lon;
if (lon_dir < 0 && value > lon_break) value -= 360;
else if (lon_dir > 0 && value < lon_break) value += 360;
if (value < lon_min) lon_min = value;
else if (value > lon_max) lon_max = value;
/* set bounding circle to cover whole earth if 180 degrees or more are
covered */
if (lon_max - lon_min >= 180) {
cluster->bounding.center.lat = 0;
cluster->bounding.center.lon = 0;
cluster->bounding.radius = INFINITY;
return true;
}
/* add point to bounding circle center (for average calculation) */
cluster->bounding.center.lat += points[j].lat;
cluster->bounding.center.lon += value;
}
/* count total number of points */
total_npoints += npoints;
}
/* determine average latitude and longitude of cluster */
cluster->bounding.center.lat /= total_npoints;
cluster->bounding.center.lon /= total_npoints;
/* normalize longitude of center of cluster bounding circle */
if (cluster->bounding.center.lon < -180) {
cluster->bounding.center.lon += 360;
}
else if (cluster->bounding.center.lon > 180) {
cluster->bounding.center.lon -= 360;
}
/* round bounding circle center (useful if it is used by other functions) */
cluster->bounding.center.lat = pgl_round(cluster->bounding.center.lat);
cluster->bounding.center.lon = pgl_round(cluster->bounding.center.lon);
/* calculate radius of bounding circle */
for (i=0; i<cluster->nentries; i++) {
npoints = cluster->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster, i);
for (j=0; j<npoints; j++) {
value = pgl_distance(
cluster->bounding.center.lat, cluster->bounding.center.lon,
points[j].lat, points[j].lon
);
if (value > cluster->bounding.radius) cluster->bounding.radius = value;
}
}
}
/* return true (east/west orientation is unambiguous) */
return true;
}
/* check if point is inside cluster */
/* (if point is on perimeter, then true is returned if and only if
strict == false) */
static bool pgl_point_in_cluster(
pgl_point *point,
pgl_cluster *cluster,
bool strict
) {
int i, j, k; /* i: entry, j: point in entry, k: next point in entry */
int entrytype; /* type of entry */
int npoints; /* number of points in entry */
pgl_point *points; /* array of points in entry */
int lon_dir = 0; /* first vertex west (-1) or east (+1) */
double lon_break = 0; /* antipodal longitude of first vertex */
double lat0 = point->lat; /* latitude of point */
double lon0; /* (adjusted) longitude of point */
double lat1, lon1; /* latitude and (adjusted) longitude of vertex */
double lat2, lon2; /* latitude and (adjusted) longitude of next vertex */
double lon; /* longitude of intersection */
int counter = 0; /* counter for intersections east of point */
/* iterate over all entries */
for (i=0; i<cluster->nentries; i++) {
/* get type of entry */
entrytype = cluster->entries[i].entrytype;
/* skip all entries but polygons if perimeters are excluded */
if (strict && entrytype != PGL_ENTRY_POLYGON) continue;
/* get points of entry */
npoints = cluster->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster, i);
/* determine east/west orientation of first point of entry and calculate
antipodal longitude */
lon_break = points[0].lon;
if (lon_break < 0) { lon_dir = -1; lon_break += 180; }
else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; }
else lon_dir = 0;
/* get longitude of point */
lon0 = point->lon;
/* consider longitude wrap-around for point */
if (lon_dir < 0 && lon0 > lon_break) lon0 = pgl_round(lon0 - 360);
else if (lon_dir > 0 && lon0 < lon_break) lon0 = pgl_round(lon0 + 360);
/* iterate over all edges and vertices */
for (j=0; j<npoints; j++) {
/* return if point is on vertex of polygon */
if (pgl_point_cmp(point, &(points[j])) == 0) return !strict;
/* calculate index of next vertex */
k = (j+1) % npoints;
/* skip last edge unless entry is (closed) outline or polygon */
if (
k == 0 &&
entrytype != PGL_ENTRY_OUTLINE &&
entrytype != PGL_ENTRY_POLYGON
) continue;
/* use previously calculated values for lat1 and lon1 if possible */
if (j) {
lat1 = lat2;
lon1 = lon2;
} else {
/* otherwise get latitude and longitude values of first vertex */
lat1 = points[0].lat;
lon1 = points[0].lon;
/* and consider longitude wrap-around for first vertex */
if (lon_dir < 0 && lon1 > lon_break) lon1 = pgl_round(lon1 - 360);
else if (lon_dir > 0 && lon1 < lon_break) lon1 = pgl_round(lon1 + 360);
}
/* get latitude and longitude of next vertex */
lat2 = points[k].lat;
lon2 = points[k].lon;
/* consider longitude wrap-around for next vertex */
if (lon_dir < 0 && lon2 > lon_break) lon2 = pgl_round(lon2 - 360);
else if (lon_dir > 0 && lon2 < lon_break) lon2 = pgl_round(lon2 + 360);
/* return if point is on horizontal (west to east) edge of polygon */
if (
lat0 == lat1 && lat0 == lat2 &&
( (lon0 >= lon1 && lon0 <= lon2) || (lon0 >= lon2 && lon0 <= lon1) )
) return !strict;
/* check if edge crosses east/west line of point */
if ((lat1 < lat0 && lat2 >= lat0) || (lat2 < lat0 && lat1 >= lat0)) {
/* calculate longitude of intersection */
lon = (lon1 * (lat2-lat0) + lon2 * (lat0-lat1)) / (lat2-lat1);
/* return if intersection goes (approximately) through point */
if (pgl_round(lon) == lon0) return !strict;
/* count intersection if east of point and entry is polygon*/
if (entrytype == PGL_ENTRY_POLYGON && lon > lon0) counter++;
}
}
}
/* return true if number of intersections is odd */
return counter & 1;
}
/* check if all points of the second cluster are strictly inside the first
cluster */
static inline bool pgl_all_cluster_points_strictly_in_cluster(
pgl_cluster *outer, pgl_cluster *inner
) {
int i, j; /* i: entry, j: point in entry */
int npoints; /* number of points in entry */
pgl_point *points; /* array of points in entry */
/* iterate over all entries of "inner" cluster */
for (i=0; i<inner->nentries; i++) {
/* get properties of entry */
npoints = inner->entries[i].npoints;
points = PGL_ENTRY_POINTS(inner, i);
/* iterate over all points in entry of "inner" cluster */
for (j=0; j<npoints; j++) {
/* return false if one point of inner cluster is not in outer cluster */
if (!pgl_point_in_cluster(points+j, outer, true)) return false;
}
}
/* otherwise return true */
return true;
}
/* check if any point the second cluster is inside the first cluster */
static inline bool pgl_any_cluster_points_in_cluster(
pgl_cluster *outer, pgl_cluster *inner
) {
int i, j; /* i: entry, j: point in entry */
int npoints; /* number of points in entry */
pgl_point *points; /* array of points in entry */
/* iterate over all entries of "inner" cluster */
for (i=0; i<inner->nentries; i++) {
/* get properties of entry */
npoints = inner->entries[i].npoints;
points = PGL_ENTRY_POINTS(inner, i);
/* iterate over all points in entry of "inner" cluster */
for (j=0; j<npoints; j++) {
/* return true if one point of inner cluster is in outer cluster */
if (pgl_point_in_cluster(points+j, outer, false)) return true;
}
}
/* otherwise return false */
return false;
}
/* check if line segment strictly crosses line (not just touching) */
static inline bool pgl_lseg_crosses_line(
double seg_x1, double seg_y1, double seg_x2, double seg_y2,
double line_x1, double line_y1, double line_x2, double line_y2
) {
return (
(
(seg_x1-line_x1) * (line_y2-line_y1) -
(seg_y1-line_y1) * (line_x2-line_x1)
) * (
(seg_x2-line_x1) * (line_y2-line_y1) -
(seg_y2-line_y1) * (line_x2-line_x1)
)
) < 0;
}
/* check if paths and outlines of two clusters strictly overlap (not just
touching) */
static bool pgl_outlines_overlap(
pgl_cluster *cluster1, pgl_cluster *cluster2
) {
int i1, j1, k1; /* i: entry, j: point in entry, k: next point in entry */
int i2, j2, k2;
int entrytype1, entrytype2; /* type of entry */
int npoints1, npoints2; /* number of points in entry */
pgl_point *points1; /* array of points in entry of cluster1 */
pgl_point *points2; /* array of points in entry of cluster2 */
int lon_dir1, lon_dir2; /* first vertex west (-1) or east (+1) */
double lon_break1, lon_break2; /* antipodal longitude of first vertex */
double lat11, lon11; /* latitude and (adjusted) longitude of vertex */
double lat12, lon12; /* latitude and (adjusted) longitude of next vertex */
double lat21, lon21; /* latitude and (adjusted) longitudes for cluster2 */
double lat22, lon22;
double wrapvalue; /* temporary helper value to adjust wrap-around */
/* iterate over all entries of cluster1 */
for (i1=0; i1<cluster1->nentries; i1++) {
/* get properties of entry in cluster1 and skip points */
npoints1 = cluster1->entries[i1].npoints;
if (npoints1 < 2) continue;
entrytype1 = cluster1->entries[i1].entrytype;
points1 = PGL_ENTRY_POINTS(cluster1, i1);
/* determine east/west orientation of first point and calculate antipodal
longitude */
lon_break1 = points1[0].lon;
if (lon_break1 < 0) {
lon_dir1 = -1;
lon_break1 = pgl_round(lon_break1 + 180);
} else if (lon_break1 > 0) {
lon_dir1 = 1;
lon_break1 = pgl_round(lon_break1 - 180);
} else lon_dir1 = 0;
/* iterate over all edges and vertices in cluster1 */
for (j1=0; j1<npoints1; j1++) {
/* calculate index of next vertex */
k1 = (j1+1) % npoints1;
/* skip last edge unless entry is (closed) outline or polygon */
if (
k1 == 0 &&
entrytype1 != PGL_ENTRY_OUTLINE &&
entrytype1 != PGL_ENTRY_POLYGON
) continue;
/* use previously calculated values for lat1 and lon1 if possible */
if (j1) {
lat11 = lat12;
lon11 = lon12;
} else {
/* otherwise get latitude and longitude values of first vertex */
lat11 = points1[0].lat;
lon11 = points1[0].lon;
/* and consider longitude wrap-around for first vertex */
if (lon_dir1<0 && lon11>lon_break1) lon11 = pgl_round(lon11-360);
else if (lon_dir1>0 && lon11<lon_break1) lon11 = pgl_round(lon11+360);
}
/* get latitude and longitude of next vertex */
lat12 = points1[k1].lat;
lon12 = points1[k1].lon;
/* consider longitude wrap-around for next vertex */
if (lon_dir1<0 && lon12>lon_break1) lon12 = pgl_round(lon12-360);
else if (lon_dir1>0 && lon12<lon_break1) lon12 = pgl_round(lon12+360);
/* skip degenerated edges */
if (lat11 == lat12 && lon11 == lon12) continue;
/* iterate over all entries of cluster2 */
for (i2=0; i2<cluster2->nentries; i2++) {
/* get points and number of points of entry in cluster2 */
npoints2 = cluster2->entries[i2].npoints;
if (npoints2 < 2) continue;
entrytype2 = cluster2->entries[i2].entrytype;
points2 = PGL_ENTRY_POINTS(cluster2, i2);
/* determine east/west orientation of first point and calculate antipodal
longitude */
lon_break2 = points2[0].lon;
if (lon_break2 < 0) {
lon_dir2 = -1;
lon_break2 = pgl_round(lon_break2 + 180);
} else if (lon_break2 > 0) {
lon_dir2 = 1;
lon_break2 = pgl_round(lon_break2 - 180);
} else lon_dir2 = 0;
/* iterate over all edges and vertices in cluster2 */
for (j2=0; j2<npoints2; j2++) {
/* calculate index of next vertex */
k2 = (j2+1) % npoints2;
/* skip last edge unless entry is (closed) outline or polygon */
if (
k2 == 0 &&
entrytype2 != PGL_ENTRY_OUTLINE &&
entrytype2 != PGL_ENTRY_POLYGON
) continue;
/* use previously calculated values for lat1 and lon1 if possible */
if (j2) {
lat21 = lat22;
lon21 = lon22;
} else {
/* otherwise get latitude and longitude values of first vertex */
lat21 = points2[0].lat;
lon21 = points2[0].lon;
/* and consider longitude wrap-around for first vertex */
if (lon_dir2<0 && lon21>lon_break2) lon21 = pgl_round(lon21-360);
else if (lon_dir2>0 && lon21<lon_break2) lon21 = pgl_round(lon21+360);
}
/* get latitude and longitude of next vertex */
lat22 = points2[k2].lat;
lon22 = points2[k2].lon;
/* consider longitude wrap-around for next vertex */
if (lon_dir2<0 && lon22>lon_break2) lon22 = pgl_round(lon22-360);
else if (lon_dir2>0 && lon22<lon_break2) lon22 = pgl_round(lon22+360);
/* skip degenerated edges */
if (lat21 == lat22 && lon21 == lon22) continue;
/* perform another wrap-around where necessary */
/* TODO: improve performance of whole wrap-around mechanism */
wrapvalue = (lon21 + lon22) - (lon11 + lon12);
if (wrapvalue > 360) {
lon21 = pgl_round(lon21 - 360);
lon22 = pgl_round(lon22 - 360);
} else if (wrapvalue < -360) {
lon21 = pgl_round(lon21 + 360);
lon22 = pgl_round(lon22 + 360);
}
/* return true if segments overlap */
if (
pgl_lseg_crosses_line(
lat11, lon11, lat12, lon12,
lat21, lon21, lat22, lon22
) && pgl_lseg_crosses_line(
lat21, lon21, lat22, lon22,
lat11, lon11, lat12, lon12
)
) {
return true;
}
}
}
}
}
/* otherwise return false */
return false;
}
/* check if second cluster is completely contained in first cluster */
static bool pgl_cluster_in_cluster(pgl_cluster *outer, pgl_cluster *inner) {
if (!pgl_all_cluster_points_strictly_in_cluster(outer, inner)) return false;
if (pgl_any_cluster_points_in_cluster(inner, outer)) return false;
if (pgl_outlines_overlap(outer, inner)) return false;
return true;
}
/* check if two clusters overlap */
static bool pgl_clusters_overlap(
pgl_cluster *cluster1, pgl_cluster *cluster2
) {
if (pgl_any_cluster_points_in_cluster(cluster1, cluster2)) return true;
if (pgl_any_cluster_points_in_cluster(cluster2, cluster1)) return true;
if (pgl_outlines_overlap(cluster1, cluster2)) return true;
return false;
}
/* calculate (approximate) distance between point and cluster */
static double pgl_point_cluster_distance(pgl_point *point, pgl_cluster *cluster) {
double comp; /* square of compression of meridians */
int i, j, k; /* i: entry, j: point in entry, k: next point in entry */
int entrytype; /* type of entry */
int npoints; /* number of points in entry */
pgl_point *points; /* array of points in entry */
int lon_dir = 0; /* first vertex west (-1) or east (+1) */
double lon_break = 0; /* antipodal longitude of first vertex */
double lon_min = 0; /* minimum (adjusted) longitude of entry vertices */
double lon_max = 0; /* maximum (adjusted) longitude of entry vertices */
double lat0 = point->lat; /* latitude of point */
double lon0; /* (adjusted) longitude of point */
double lat1, lon1; /* latitude and (adjusted) longitude of vertex */
double lat2, lon2; /* latitude and (adjusted) longitude of next vertex */
double s; /* scalar for vector calculations */
double dist; /* distance calculated in one step */
double min_dist = INFINITY; /* minimum distance */
/* distance is zero if point is contained in cluster */
if (pgl_point_in_cluster(point, cluster, false)) return 0;
/* calculate approximate square compression of meridians */
comp = cos((lat0 / 180.0) * M_PI);
comp *= comp;
/* calculate exact square compression of meridians */
comp *= (
(1.0 - PGL_EPS2 * (1.0-comp)) *
(1.0 - PGL_EPS2 * (1.0-comp)) /
(PGL_SUBEPS2 * PGL_SUBEPS2)
);
/* iterate over all entries */
for (i=0; i<cluster->nentries; i++) {
/* get properties of entry */
entrytype = cluster->entries[i].entrytype;
npoints = cluster->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster, i);
/* determine east/west orientation of first point of entry and calculate
antipodal longitude */
lon_break = points[0].lon;
if (lon_break < 0) { lon_dir = -1; lon_break += 180; }
else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; }
else lon_dir = 0;
/* determine covered longitude range */
for (j=0; j<npoints; j++) {
/* get longitude of vertex */
lon1 = points[j].lon;
/* adjust longitude to fix potential wrap-around */
if (lon_dir < 0 && lon1 > lon_break) lon1 -= 360;
else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360;
/* update minimum and maximum longitude of polygon */
if (j == 0 || lon1 < lon_min) lon_min = lon1;
if (j == 0 || lon1 > lon_max) lon_max = lon1;
}
/* adjust longitude wrap-around according to full longitude range */
lon_break = (lon_max + lon_min) / 2;
if (lon_break < 0) { lon_dir = -1; lon_break += 180; }
else if (lon_break > 0) { lon_dir = 1; lon_break -= 180; }
/* get longitude of point */
lon0 = point->lon;
/* consider longitude wrap-around for point */
if (lon_dir < 0 && lon0 > lon_break) lon0 -= 360;
else if (lon_dir > 0 && lon0 < lon_break) lon0 += 360;
/* iterate over all edges and vertices */
for (j=0; j<npoints; j++) {
/* use previously calculated values for lat1 and lon1 if possible */
if (j) {
lat1 = lat2;
lon1 = lon2;
} else {
/* otherwise get latitude and longitude values of first vertex */
lat1 = points[0].lat;
lon1 = points[0].lon;
/* and consider longitude wrap-around for first vertex */
if (lon_dir < 0 && lon1 > lon_break) lon1 -= 360;
else if (lon_dir > 0 && lon1 < lon_break) lon1 += 360;
}
/* calculate distance to vertex */
dist = pgl_distance(lat0, lon0, lat1, lon1);
/* store calculated distance if smallest */
if (dist < min_dist) min_dist = dist;
/* calculate index of next vertex */
k = (j+1) % npoints;
/* skip last edge unless entry is (closed) outline or polygon */
if (
k == 0 &&
entrytype != PGL_ENTRY_OUTLINE &&
entrytype != PGL_ENTRY_POLYGON
) continue;
/* get latitude and longitude of next vertex */
lat2 = points[k].lat;
lon2 = points[k].lon;
/* consider longitude wrap-around for next vertex */
if (lon_dir < 0 && lon2 > lon_break) lon2 -= 360;
else if (lon_dir > 0 && lon2 < lon_break) lon2 += 360;
/* go to next vertex and edge if edge is degenerated */
if (lat1 == lat2 && lon1 == lon2) continue;
/* otherwise test if point can be projected onto edge of polygon */
s = (
((lat0-lat1) * (lat2-lat1) + comp * (lon0-lon1) * (lon2-lon1)) /
((lat2-lat1) * (lat2-lat1) + comp * (lon2-lon1) * (lon2-lon1))
);
/* go to next vertex and edge if point cannot be projected */
if (!(s > 0 && s < 1)) continue;
/* calculate distance from original point to projected point */
dist = pgl_distance(
lat0, lon0,
lat1 + s * (lat2-lat1),
lon1 + s * (lon2-lon1)
);
/* store calculated distance if smallest */
if (dist < min_dist) min_dist = dist;
}
}
/* return minimum distance */
return min_dist;
}
/* calculate (approximate) distance between two clusters */
static double pgl_cluster_distance(pgl_cluster *cluster1, pgl_cluster *cluster2) {
int i, j; /* i: entry, j: point in entry */
int npoints; /* number of points in entry */
pgl_point *points; /* array of points in entry */
double dist; /* distance calculated in one step */
double min_dist = INFINITY; /* minimum distance */
/* consider distance from each point in one cluster to the whole other */
for (i=0; i<cluster1->nentries; i++) {
npoints = cluster1->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster1, i);
for (j=0; j<npoints; j++) {
dist = pgl_point_cluster_distance(points+j, cluster2);
if (dist == 0) return dist;
if (dist < min_dist) min_dist = dist;
}
}
/* consider distance from each point in other cluster to the first cluster */
for (i=0; i<cluster2->nentries; i++) {
npoints = cluster2->entries[i].npoints;
points = PGL_ENTRY_POINTS(cluster2, i);
for (j=0; j<npoints; j++) {
dist = pgl_point_cluster_distance(points+j, cluster1);
if (dist == 0) return dist;
if (dist < min_dist) min_dist = dist;
}
}
return min_dist;
}
/* estimator function for distance between box and point */
/* always returns a smaller value than actually correct or zero */
static double pgl_estimate_point_box_distance(pgl_point *point, pgl_box *box) {
double dlon; /* longitude range of box (delta longitude) */
double distance; /* return value */
/* return infinity if box is empty */
if (box->lat_min > box->lat_max) return INFINITY;
/* return zero if point is inside box */
if (pgl_point_in_box(point, box)) return 0;
/* calculate delta longitude */
dlon = box->lon_max - box->lon_min;
if (dlon < 0) dlon += 360; /* 180th meridian crossed */
/* if delta longitude is greater than 150 degrees, perform safe fall-back */
if (dlon > 150) return 0;
/* calculate lower limit for distance (formula below requires dlon <= 150) */
/* TODO: provide better estimation function to improve performance */
distance = (
(1.0-PGL_SPHEROID_F) * /* safety margin due to flattening and approx. */
pgl_distance(
point->lat,
point->lon,
(box->lat_min + box->lat_max) / 2,
box->lon_min + dlon/2
)
) - pgl_distance(
box->lat_min, box->lon_min,
box->lat_max, box->lon_max
);
/* truncate negative results to zero */
if (distance <= 0) distance = 0;
/* return result */
return distance;
}
/*------------------------------------------------------------*
* Functions using numerical integration (Monte Carlo like) *
*------------------------------------------------------------*/