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 &region;
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