1 /***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Latitude and Longitude regions
5 * Author: Sean D'Epagnier
6 *
7 ***************************************************************************
8 * Copyright (C) 2015 by Sean D'Epagnier *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 ***************************************************************************
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <math.h>
31
32 #include "LLRegion.h"
33 #include "logger.h"
34
cross(const contour_pt & v1,const contour_pt & v2)35 static inline double cross(const contour_pt &v1, const contour_pt &v2)
36 {
37 return v1.y*v2.x - v1.x*v2.y;
38 }
39
dot(const contour_pt & v1,const contour_pt & v2)40 static inline double dot(const contour_pt &v1, const contour_pt &v2)
41 {
42 return v1.x*v2.x + v1.y*v2.y;
43 }
44
dist2(const contour_pt & v)45 static inline double dist2(const contour_pt &v)
46 {
47 return dot(v, v);
48 }
49
vector(const contour_pt & p1,const contour_pt & p2)50 static inline contour_pt vector(const contour_pt &p1, const contour_pt &p2)
51 {
52 contour_pt r = {p2.y - p1.y, p2.x - p1.x};
53 return r;
54 }
55
LLRegion(float minlat,float minlon,float maxlat,float maxlon)56 LLRegion::LLRegion( float minlat, float minlon, float maxlat, float maxlon)
57 {
58 InitBox(minlat, minlon, maxlat, maxlon);
59 }
60
LLRegion(const LLBBox & llbbox)61 LLRegion::LLRegion( const LLBBox& llbbox )
62 {
63 InitBox(llbbox.GetMinLat(), llbbox.GetMinLon(), llbbox.GetMaxLat(), llbbox.GetMaxLon());
64 }
65
LLRegion(size_t n,const float * points)66 LLRegion::LLRegion( size_t n, const float *points )
67 {
68 double *pts = new double[2*n];
69 for(size_t i=0; i<2*n; i++)
70 pts[i] = points[i];
71 InitPoints(n, pts);
72 delete [] pts;
73 }
74
LLRegion(size_t n,const double * points)75 LLRegion::LLRegion( size_t n, const double *points )
76 {
77 InitPoints(n, points);
78 }
79
80 // determine if a loop of points is counter clockwise
PointsCCW(size_t n,const double * points)81 bool LLRegion::PointsCCW( size_t n, const double *points )
82 {
83 double total = 0;
84 for(unsigned int i=0; i<2*n; i+=2) {
85 int pn = i < 2*(n-1) ? i + 2 : 0;
86 total += (points[pn+0] - points[i+0]) * (points[pn+1] + points[i+1]);
87 }
88 return total > 0;
89 }
90
Print() const91 void LLRegion::Print() const
92 {
93 for(std::list<poly_contour>::const_iterator i = contours.begin(); i != contours.end(); i++) {
94 printf("[");
95 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++)
96 printf("(%g %g) ", j->y, j->x);
97 printf("]\n");
98 }
99 }
100
plot(const char * fn) const101 void LLRegion::plot(const char*fn) const
102 {
103 char filename[100] = "/home/sean/";
104 strcat(filename, fn);
105 FILE *f = fopen(filename, "w");
106 for(std::list<poly_contour>::const_iterator i = contours.begin(); i != contours.end(); i++) {
107 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++)
108 fprintf(f, "%f %f\n", j->x, j->y);
109
110 fprintf(f, "%f %f\n", i->begin()->x, i->begin()->y);
111 fprintf(f, "\n");
112 }
113 fclose(f);
114 }
115
GetBox() const116 LLBBox LLRegion::GetBox() const
117 {
118 if(contours.empty())
119 return LLBBox(); // invalid box
120
121 if(m_box.GetValid())
122 return m_box;
123
124 // there are 3 possible longitude bounds: -180 to 180, 0 to 360, -360 to 0
125 double minlat = 90, minlon[3] = {180, 360, 0};
126 double maxlat = -90, maxlon[3] = {-180, 0, -360};
127 for(std::list<poly_contour>::const_iterator i = contours.begin(); i != contours.end(); i++) {
128 bool neg = false, pos = false;
129 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++)
130 if(j->x < 0)
131 neg = true;
132 else
133 pos = true;
134
135 double resolved[3] = {0, 0, 0};
136 if(neg && !pos)
137 resolved[1] = 360;
138 if(pos && !neg)
139 resolved[2] = -360;
140
141 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++) {
142 minlat = wxMin(minlat, j->y);
143 maxlat = wxMax(maxlat, j->y);
144
145 for(int k = 0; k<3; k++) {
146 minlon[k] = wxMin(minlon[k], j->x + resolved[k]);
147 maxlon[k] = wxMax(maxlon[k], j->x + resolved[k]);
148 }
149 }
150 }
151
152 double d[3];
153 for(int k = 0; k<3; k++) {
154 double a = maxlon[k] + minlon[k];
155 // eliminate cases where the average longitude falls outside of -180 to 180
156 if(a <= -360 || a >= 360)
157 d[k] = 360;
158 else
159 d[k] = maxlon[k] - minlon[k];
160 }
161
162 // find minimum difference (best case to use)
163 double epsilon = 1e-2; // because floating point rounding favor... d1, then d2 then d3
164 d[1] += epsilon, d[2] += 2*epsilon;
165 int mink = 0;
166 for(int k=1; k<3; k++)
167 if(d[k] < d[mink])
168 mink = k;
169
170 LLBBox &box = const_cast<LLBBox&>(m_box);
171 box.Set(minlat, minlon[mink], maxlat, maxlon[mink]);
172 return m_box;
173 }
174
ComputeState(const LLBBox & box,const contour_pt & p)175 static inline int ComputeState(const LLBBox &box, const contour_pt &p)
176 {
177 int state = 0;
178 if(p.x >= box.GetMinLon()) {
179 if(p.x > box.GetMaxLon())
180 state = 2;
181 else
182 state = 1;
183 }
184
185 if(p.y >= box.GetMinLat()) {
186 if(p.y > box.GetMaxLat())
187 state += 6;
188 else
189 state += 3;
190 }
191 return state;
192 }
193
TestPoint(contour_pt p0,contour_pt p1,double x,double y)194 inline bool TestPoint(contour_pt p0, contour_pt p1, double x, double y)
195 {
196 contour_pt p = { y, x};
197 return cross(vector(p0, p1), vector(p0, p)) < 0;
198 }
199
IntersectOut(const LLBBox & box) const200 bool LLRegion::IntersectOut(const LLBBox &box) const
201 {
202 // First do faster test of bounding boxes
203 if(GetBox().IntersectOut(box))
204 return true;
205
206 return NoIntersection(box);
207 }
208
209 // this function may produce false positives in degenerate cases
Contains(float lat,float lon) const210 bool LLRegion::Contains(float lat, float lon) const
211 {
212 if(lon == 180 && Contains(lat, -180)) return true;
213 if(lon > 180) return Contains(lat, lon-360);
214
215 int cnt = 0;
216 for(std::list<poly_contour>::const_iterator i = contours.begin(); i != contours.end(); i++) {
217 contour_pt l = *i->rbegin();
218 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++) {
219 contour_pt p = *j, a, b;
220 if(p.x < l.x)
221 a = p, b = l;
222 else
223 a = l, b = p;
224 if(lon > a.x && lon < b.x) {
225 if(lat > a.y) {
226 if(lat > b.y)
227 cnt++;
228 else
229 cnt += TestPoint(a, b, lon, lat);
230 } else if(lat > b.y)
231 cnt += TestPoint(a, b, lon, lat);
232 }
233
234 if(lat == a.y || lat == b.y)
235 return true;
236
237 if(lon == a.x || lon == b.x)
238 return true;
239 l = p;
240 }
241 }
242 return cnt&1;
243 }
244
245 struct work
246 {
workwork247 work(LLRegion &r) : region(r) {
248 tobj = gluNewTess();
249 }
250
~workwork251 ~work() {
252 gluDeleteTess(tobj);
253 for(std::list<double*>::iterator i = data.begin(); i!=data.end(); i++)
254 delete [] *i;
255 data.clear();
256 }
257
NewDatawork258 double *NewData() {
259 double *d = new double[3];
260 data.push_back(d);
261 return d;
262 }
263
PutVertexwork264 void PutVertex(const contour_pt &j) {
265 double *p = NewData();
266 p[0] = j.x, p[1] = j.y, p[2] = 0;
267 gluTessVertex(tobj, p, p);
268 }
269
270 std::list<double*> data;
271 poly_contour contour;
272 GLUtesselator *tobj;
273 LLRegion ®ion;
274 };
275
276
LLvertexCallback(GLvoid * vertex,void * user_data)277 static void /*APIENTRY*/ LLvertexCallback(GLvoid *vertex, void *user_data)
278 {
279 work *w = (work*)user_data;
280 const GLdouble *pointer = (GLdouble *)vertex;
281 contour_pt p;
282 p.x = pointer[0], p.y = pointer[1];
283 w->contour.push_back(p);
284 }
285
LLbeginCallback(GLenum which)286 static void /*APIENTRY*/ LLbeginCallback(GLenum which) {
287 }
288
LLendCallback(void * user_data)289 static void /*APIENTRY*/ LLendCallback(void *user_data)
290 {
291 work *w = (work*)user_data;
292 if(w->contour.size()) {
293 w->region.contours.push_back(w->contour);
294 w->contour.clear();
295 }
296 }
297
LLcombineCallback(GLdouble coords[3],GLdouble * vertex_data[4],GLfloat weight[4],GLdouble ** dataOut,void * user_data)298 static void /*APIENTRY*/ LLcombineCallback( GLdouble coords[3], GLdouble *vertex_data[4], GLfloat weight[4],
299 GLdouble **dataOut, void *user_data )
300 {
301 work *w = (work*)user_data;
302 GLdouble *vertex = w->NewData();
303 memcpy(vertex, coords, 3*(sizeof *coords));
304 *dataOut = vertex;
305 }
306
LLerrorCallback(GLenum errorCode)307 static void /*APIENTRY*/ LLerrorCallback(GLenum errorCode)
308 {
309 const GLubyte *estring;
310 estring = gluErrorString(errorCode);
311 LOG_INFO ("Tessellation Error: %s\n", estring);
312 //exit (0);
313 }
314
Intersect(const LLRegion & region)315 void LLRegion::Intersect(const LLRegion& region)
316 {
317 if(NoIntersection(region)) {
318 Clear();
319 return;
320 }
321
322 Put(region, GLU_TESS_WINDING_ABS_GEQ_TWO, false);
323 }
324
Union(const LLRegion & region)325 void LLRegion::Union(const LLRegion& region)
326 {
327 if(NoIntersection(region)) {
328 Combine(region);
329 return;
330 }
331
332 Put(region, GLU_TESS_WINDING_POSITIVE, false);
333 }
334
Subtract(const LLRegion & region)335 void LLRegion::Subtract(const LLRegion& region)
336 {
337 if(NoIntersection(region))
338 return;
339
340 Put(region, GLU_TESS_WINDING_POSITIVE, true);
341 }
342
Reduce(double factor)343 void LLRegion::Reduce(double factor)
344 {
345 double factor2 = factor*factor;
346
347 std::list<poly_contour>::iterator i = contours.begin();
348 while(i != contours.end()) {
349 if(i->size() < 3) {
350 printf("invalid contour");
351 continue;
352 }
353
354 // reduce segments
355 contour_pt l = *i->rbegin();
356 poly_contour::iterator j = i->begin(), k;
357 while(j != i->end()) {
358 k = j;
359 j++;
360 if(dist2(vector(*k, l)) < factor2)
361 i->erase(k);
362 else
363 l = *k;
364 }
365
366 // erase zero contours
367 if(i->size() < 3)
368 i = contours.erase(i);
369 else
370 i++;
371 }
372
373 //Optimize();
374 }
375
376 // slightly ugly, but efficient intersection algorithm
NoIntersection(const LLBBox & box) const377 bool LLRegion::NoIntersection(const LLBBox& box) const
378 {
379 return false; // there are occasional false positives we must fix first
380
381 #if 0
382 double minx = box.GetMinLon(), maxx = box.GetMaxLon(), miny = box.GetMinLat(), maxy = box.GetMaxLat();
383 if(Contains(miny, minx))
384 return false;
385
386 // test if any segment crosses the box
387 for(std::list<poly_contour>::const_iterator i = contours.begin(); i != contours.end(); i++) {
388 contour_pt l = *i->rbegin();
389 int state = ComputeState(box, l), lstate = state;
390 if(state == 4) return false;
391 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++) {
392 contour_pt p = *j;
393 int quadrant = p.x > l.x ? 1 : 0;
394 if(p.y > l.y) quadrant += 2;
395 switch(state*4 + quadrant) {
396 case 0: goto skip;
397 case 1: if(p.x >= minx) state = p.x > maxx ? 2 : 1; goto skip;
398 case 2: if(p.y >= miny) state = p.y > maxy ? 6 : 3; goto skip;
399 case 4: if(p.x < minx) state = 0; goto skip;
400 case 5: if(p.x > maxx) state = 2; goto skip;
401 case 8: if(p.x <= maxx) state = p.x < minx ? 0 : 1; goto skip;
402 case 9: goto skip;
403 case 11: if(p.y >= miny) state = p.y > maxy ? 8 : 5; goto skip;
404 case 12: if(p.y < miny) state = 0; goto skip;
405 case 14: if(p.y > maxy) state = 6; goto skip;
406 case 21: if(p.y < miny) state = 2; goto skip;
407 case 23: if(p.y > maxy) state = 8; goto skip;
408 case 24: if(p.y <= maxy) state = p.y < miny ? 0 : 3; goto skip;
409 case 26: goto skip;
410 case 27: if(p.x >= minx) state = p.x > maxx ? 8 : 7; goto skip;
411 case 30: if(p.x < minx) state = 6; goto skip;
412 case 31: if(p.x > maxx) state = 8; goto skip;
413 case 33: if(p.y <= maxy) state = p.y < miny ? 2 : 5; goto skip;
414 case 34: if(p.x <= maxx) state = p.x < minx ? 6 : 7; goto skip;
415 case 35: goto skip;
416 }
417
418 state = ComputeState(box, *j);
419 if(state == 4) return false;
420 switch(lstate) {
421 #define TEST_CASE(NO_INT, CASEA, CASEB, CASEAB, AX, AY, BX, BY) \
422 switch(state) { NO_INT break; \
423 CASEAB if(TestPoint(l, p, BX##x, BY##y)) return false; \
424 CASEA if(TestPoint(p, l, AX##x, AY##y)) return false; break; \
425 CASEB if(TestPoint(l, p, BX##x, BY##y)) return false; break; \
426 default: printf("invalid state inner %d %d\n", lstate, state); } break;
427 case 0: TEST_CASE(case 0: case 1: case 2: case 3: case 6:,
428 case 5:, case 7:, case 8:, max, min, min, max)
429 case 1: TEST_CASE(case 0: case 1: case 2:,
430 case 5: case 8:, case 3: case 6:, case 7:, max, min, min, min)
431 case 2: TEST_CASE(case 0: case 1: case 2: case 5: case 8:,
432 case 7:, case 3:, case 6:, max, max, min, min)
433 case 3: TEST_CASE(case 0: case 3: case 6:,
434 case 1: case 2:, case 7: case 8:, case 5:, min, min, min, max)
435 // case 4: return false; // should never hit
436 case 5: TEST_CASE(case 2: case 5: case 8:,
437 case 6: case 7:, case 0: case 1:, case 3:, max, max, max, min)
438 case 6: TEST_CASE(case 0: case 3: case 6: case 7: case 8:,
439 case 1:, case 5:, case 2:, min, min, max, max)
440 case 7: TEST_CASE(case 6: case 7: case 8:,
441 case 0: case 3:, case 2: case 5:, case 1:, min, max, max, max)
442 case 8: TEST_CASE(case 2: case 5: case 6: case 7: case 8:,
443 case 3:, case 1:, case 0:, min, max, max, min)
444 default: printf("invalid state\n");
445 }
446 skip:
447 lstate = state;
448 l = p;
449 }
450 }
451
452 return true;
453 #endif
454 }
455
456 // internal test to see if regions don't intersect (optimization)
NoIntersection(const LLRegion & region) const457 bool LLRegion::NoIntersection(const LLRegion& region) const
458 {
459 if(Empty() || region.Empty())
460 return true;
461
462 LLBBox box = GetBox(), rbox = region.GetBox();
463 return box.IntersectOut(rbox) || NoIntersection(rbox) || region.NoIntersection(box);
464 }
465
PutContours(work & w,const LLRegion & region,bool reverse)466 void LLRegion::PutContours(work &w, const LLRegion& region, bool reverse)
467 {
468 for(std::list<poly_contour>::const_iterator i = region.contours.begin(); i != region.contours.end(); i++) {
469 gluTessBeginContour(w.tobj);
470 if(reverse)
471 for(poly_contour::const_reverse_iterator j = i->rbegin(); j != i->rend(); j++)
472 w.PutVertex(*j);
473 else
474 for(poly_contour::const_iterator j = i->begin(); j != i->end(); j++)
475 w.PutVertex(*j);
476 gluTessEndContour(w.tobj);
477 }
478 }
479
Put(const LLRegion & region,int winding_rule,bool reverse)480 void LLRegion::Put( const LLRegion& region, int winding_rule, bool reverse)
481 {
482 work w(*this);
483
484 gluTessCallback( w.tobj, GLU_TESS_VERTEX_DATA, (_GLUfuncptr) &LLvertexCallback );
485 gluTessCallback( w.tobj, GLU_TESS_BEGIN, (_GLUfuncptr) &LLbeginCallback );
486 gluTessCallback( w.tobj, GLU_TESS_COMBINE_DATA, (_GLUfuncptr) &LLcombineCallback );
487 gluTessCallback( w.tobj, GLU_TESS_END_DATA, (_GLUfuncptr) &LLendCallback );
488 gluTessCallback( w.tobj, GLU_TESS_ERROR, (_GLUfuncptr) &LLerrorCallback );
489 gluTessProperty(w.tobj, GLU_TESS_WINDING_RULE, winding_rule);
490 gluTessProperty(w.tobj, GLU_TESS_BOUNDARY_ONLY, GL_TRUE);
491 // gluTessProperty(w.tobj, GLU_TESS_TOLERANCE, 1e-5);
492
493 gluTessNormal( w.tobj, 0, 0, 1);
494
495 gluTessBeginPolygon(w.tobj, &w);
496
497 PutContours(w, *this);
498 PutContours(w, region, reverse);
499 contours.clear();
500 gluTessEndPolygon( w.tobj );
501
502 Optimize();
503 m_box.Invalidate();
504 }
505
506 // same result as union, but only allowed if there is no intersection
Combine(const LLRegion & region)507 void LLRegion::Combine(const LLRegion& region)
508 {
509 for(std::list<poly_contour>::const_iterator i = region.contours.begin(); i != region.contours.end(); i++)
510 contours.push_back(*i);
511 m_box.Invalidate();
512 }
513
InitBox(float minlat,float minlon,float maxlat,float maxlon)514 void LLRegion::InitBox( float minlat, float minlon, float maxlat, float maxlon)
515 {
516 if(minlon < -180)
517 minlon += 360, maxlon += 360;
518
519 contour_pt p[4];
520 p[0].x = maxlon, p[0].y = minlat;
521 p[1].x = maxlon, p[1].y = maxlat;
522 p[2].x = minlon, p[2].y = maxlat;
523 p[3].x = minlon, p[3].y = minlat;
524 poly_contour c;
525 for(int i=0; i<4; i++)
526 c.push_back(p[i]);
527 contours.push_back(c);
528
529 if(minlon < -180 || maxlon > 180)
530 AdjustLongitude();
531 }
532
InitPoints(size_t n,const double * points)533 void LLRegion::InitPoints( size_t n, const double *points)
534 {
535 if(n < 3) {
536 printf("invalid point count\n");
537 return;
538 }
539
540 std::list<contour_pt> pts;
541 bool adjust = false;
542
543 bool ccw = PointsCCW(n, points);
544 for(unsigned int i=0; i<2*n; i+=2) {
545 contour_pt p;
546 p.y = points[i+0];
547 p.x = points[i+1];
548 if(p.x < -180 || p.x > 180)
549 adjust = true;
550 if(ccw)
551 pts.push_back(p);
552 else
553 pts.push_front(p);
554 }
555
556 contours.push_back(pts);
557
558 if(adjust)
559 AdjustLongitude();
560 Optimize();
561 }
562
AdjustLongitude()563 void LLRegion::AdjustLongitude()
564 {
565 // Make region bounded for longitude of +- 180
566 // areas from -360 to -180 and 180 to 360 are shifted
567 LLRegion clip(-90, -180, 90, 180), resolved = *this;
568 resolved.Subtract(clip);
569 if(!resolved.Empty()) {
570 Intersect(clip);
571 // apply longitude offset
572 for(std::list<poly_contour>::iterator i = resolved.contours.begin(); i != resolved.contours.end(); i++)
573 for(poly_contour::iterator j = i->begin(); j != i->end(); j++)
574 if(j->x > 0)
575 j->x -= 360;
576 else
577 j->x += 360;
578 Union(resolved);
579 }
580 Intersect(clip);
581 }
582
Optimize()583 void LLRegion::Optimize()
584 {
585 // merge parallel segments
586 std::list<poly_contour>::iterator i = contours.begin();
587 while(i != contours.end()) {
588 if(i->size() < 3) {
589 printf("invalid contour");
590 continue;
591 }
592
593 // Round coordinates to avoid numerical errors in region computations
594 const double eps = 6e-6; // about 1cm on earth's surface at equator
595 for(poly_contour::iterator j = i->begin(); j != i->end(); j++) {
596 //j->x -= fmod(j->x, 1e-8);
597 j->x = round(j->x/eps)*eps;
598 j->y = round(j->y/eps)*eps;
599 }
600
601 #if 0
602 // round toward 180 and -180 as this is where adjusted longitudes
603 // are split, and so zero contours can get eliminated by the next step
604 for(poly_contour::iterator j = i->begin(); j != i->end(); j++)
605 if(fabs(j->x - 180) < 2e-4) j->x = 180;
606 else if(fabs(j->x + 180) < 2e-4) j->x = -180;
607 #endif
608
609 // eliminiate parallel segments
610 poly_contour::iterator j = i->begin();
611 int s = i->size();
612 for(int c=0; c<s; c++) {
613 poly_contour::iterator l = j, k = j;
614
615 if (l == i->begin())
616 l = i->end();
617 l--;
618
619 k++;
620 if(k == i->end())
621 k = i->begin();
622
623 if(l == k)
624 break;
625 if(fabs(cross(vector(*j, *l), vector(*j, *k))) < 1e-12) {
626 i->erase(j);
627 j = l;
628 c--;
629 } else
630 j = k;
631 }
632
633 // erase zero contours
634 if(i->size() < 3)
635 i = contours.erase(i);
636 else
637 i++;
638 }
639 }
640