OpenCPN Partial API docs
shapefile_basemap.cpp
1 /******************************************************************************
2  *
3  * Project: OpenCPN
4  * Purpose: Shapefile basemap
5  *
6  ***************************************************************************
7  * Copyright (C) 2012-2023 by David S. Register *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the *
21  * Free Software Foundation, Inc., *
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
23  ***************************************************************************
24  *
25  *
26  */
27 
28 // Include OCPNPlatform.h before shapefile_basemap.h to prevent obscure syntax
29 // error when compiling with VS2022
30 #include "OCPNPlatform.h"
31 #include "shapefile_basemap.h"
32 #include "chartbase.h"
33 #include "glChartCanvas.h"
34 
35 #ifdef ocpnUSE_GL
36 #include "shaders.h"
37 #endif
38 
39 #ifdef __WXMSW__
40 #define __CALL_CONVENTION //__stdcall
41 #else
42 #define __CALL_CONVENTION
43 #endif
44 
45 extern OCPNPlatform* g_Platform;
46 extern wxString gWorldShapefileLocation;
47 
48 #ifdef ocpnUSE_GL
49 
50 typedef union {
51  GLdouble data[6];
52  struct sGLvertex {
53  GLdouble x;
54  GLdouble y;
55  GLdouble z;
56  GLdouble r;
57  GLdouble g;
58  GLdouble b;
59  } info;
60 } GLvertexshp;
61 #include <list>
62 
63 static std::list<float_2Dpt> g_pvshp;
64 static std::list<GLvertexshp *> g_vertexesshp;
65 static int g_typeshp, g_posshp;
66 static float_2Dpt g_p1shp, g_p2shp;
67 
68 void __CALL_CONVENTION shpscombineCallback(GLdouble coords[3],
69  GLdouble *vertex_data[4],
70  GLfloat weight[4],
71  GLdouble **dataOut) {
72  GLvertexshp *vertex;
73 
74  vertex = new GLvertexshp();
75  g_vertexesshp.push_back(vertex);
76 
77  vertex->info.x = coords[0];
78  vertex->info.y = coords[1];
79 
80  *dataOut = vertex->data;
81 }
82 
83 void __CALL_CONVENTION shpserrorCallback(GLenum errorCode) {
84  const GLubyte *estring;
85  estring = gluErrorString(errorCode);
86  // wxLogMessage( _T("OpenGL Tessellation Error: %s"), estring );
87 }
88 
89 void __CALL_CONVENTION shpsbeginCallback(GLenum type) {
90  switch (type) {
91  case GL_TRIANGLES:
92  case GL_TRIANGLE_STRIP:
93  case GL_TRIANGLE_FAN:
94  g_typeshp = type;
95  break;
96  default:
97  printf("tess unhandled begin type: %d\n", type);
98  }
99 
100  g_posshp = 0;
101 }
102 
103 void __CALL_CONVENTION shpsendCallback() {}
104 
105 void __CALL_CONVENTION shpsvertexCallback(GLvoid *arg) {
106  GLvertexshp *vertex;
107  vertex = (GLvertexshp *)arg;
108  float_2Dpt p;
109  p.y = vertex->info.x;
110  p.x = vertex->info.y;
111 
112  // convert strips and fans into triangles
113  if (g_typeshp != GL_TRIANGLES) {
114  if (g_posshp > 2) {
115  g_pvshp.push_back(g_p1shp);
116  g_pvshp.push_back(g_p2shp);
117  }
118 
119  if (g_typeshp == GL_TRIANGLE_STRIP)
120  g_p1shp = g_p2shp;
121  else if (g_posshp == 0)
122  g_p1shp = p;
123  g_p2shp = p;
124  }
125 
126  g_pvshp.push_back(p);
127  g_posshp++;
128 }
129 #endif
130 
131 ShapeBaseChartSet::ShapeBaseChartSet() : _loaded(false) {
132 }
133 
134 wxPoint2DDouble ShapeBaseChartSet::GetDoublePixFromLL(ViewPort &vp, double lat,
135  double lon) {
136  wxPoint2DDouble p = vp.GetDoublePixFromLL(lat, lon);
137  p.m_x -= vp.rv_rect.x, p.m_y -= vp.rv_rect.y;
138  return p;
139 }
140 
141 ShapeBaseChart &ShapeBaseChartSet::LowestQualityBaseMap() {
142  if (_basemap_map.find(Quality::crude) != _basemap_map.end()) {
143  return _basemap_map.at(Quality::crude);
144  }
145  if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
146  return _basemap_map.at(Quality::low);
147  }
148  if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
149  return _basemap_map.at(Quality::medium);
150  }
151  if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
152  return _basemap_map.at(Quality::high);
153  }
154  return _basemap_map.at(Quality::full);
155 }
156 
157 ShapeBaseChart &ShapeBaseChartSet::HighestQualityBaseMap() {
158  if (_basemap_map.find(Quality::full) != _basemap_map.end()) {
159  return _basemap_map.at(Quality::full);
160  }
161  if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
162  return _basemap_map.at(Quality::high);
163  }
164  if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
165  return _basemap_map.at(Quality::medium);
166  }
167  if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
168  return _basemap_map.at(Quality::low);
169  }
170  return _basemap_map.at(Quality::crude);
171 }
172 
173 ShapeBaseChart &ShapeBaseChartSet::SelectBaseMap(const size_t &scale) {
174 
175  if (_basemap_map.find(Quality::full) != _basemap_map.end() &&
176  _basemap_map.at(Quality::full).IsUsable() &&
177  scale <= _basemap_map.at(Quality::full).MinScale()) {
178  return _basemap_map.at(Quality::full);
179  } else if (_basemap_map.find(Quality::high) != _basemap_map.end() &&
180  _basemap_map.at(Quality::high).IsUsable() &&
181  scale <= _basemap_map.at(Quality::high).MinScale()) {
182  return _basemap_map.at(Quality::high);
183  } else if (_basemap_map.find(Quality::medium) != _basemap_map.end() &&
184  _basemap_map.at(Quality::medium).IsUsable() &&
185  scale <= _basemap_map.at(Quality::medium).MinScale()) {
186  return _basemap_map.at(Quality::medium);
187  } else if (_basemap_map.find(Quality::low) != _basemap_map.end() &&
188  _basemap_map.at(Quality::low).IsUsable() &&
189  scale <= _basemap_map.at(Quality::low).MinScale()) {
190  return _basemap_map.at(Quality::low);
191  }
192  return LowestQualityBaseMap();
193 }
194 
195 void ShapeBaseChartSet::Reset() {
196  // Establish the Shapefile basemap location
197  wxString basemap_dir;
198  if (gWorldShapefileLocation.empty()) {
199  basemap_dir = g_Platform->GetSharedDataDir();
200  basemap_dir.Append("basemap_shp");
201  } else {
202  basemap_dir = gWorldShapefileLocation;
203  }
204 
205  LoadBasemaps(basemap_dir.ToStdString());
206 }
207 void ShapeBaseChartSet::LoadBasemaps(const std::string &dir) {
208  _loaded = false;
209  _basemap_map.clear();
210 
211  wxColor land_color = wxColor(170, 175, 80);
212 
213  if (fs::exists(ShapeBaseChart::ConstructPath(dir, "crude_10x10"))) {
214  auto c = ShapeBaseChart(ShapeBaseChart::ConstructPath(dir, "crude_10x10"), 300000000,
215  land_color);
216  c._dmod= 10;
217  _basemap_map.insert(std::make_pair(Quality::crude, c));
218  }
219 
220  if (fs::exists(ShapeBaseChart::ConstructPath(dir, "low"))) {
221  _basemap_map.insert(std::make_pair(
222  Quality::low, ShapeBaseChart(ShapeBaseChart::ConstructPath(dir, "low"),
223  15000000, land_color)));
224  }
225  if (fs::exists(ShapeBaseChart::ConstructPath(dir, "medium"))) {
226  _basemap_map.insert(std::make_pair(
227  Quality::medium,
228  ShapeBaseChart(ShapeBaseChart::ConstructPath(dir, "medium"), 1000000,
229  land_color)));
230  }
231  if (fs::exists(ShapeBaseChart::ConstructPath(dir, "high"))) {
232  _basemap_map.insert(std::make_pair(
233  Quality::high,
234  ShapeBaseChart(ShapeBaseChart::ConstructPath(dir, "high"), 300000,
235  land_color)));
236  }
237  if (fs::exists(ShapeBaseChart::ConstructPath(dir, "full"))) {
238  _basemap_map.insert(std::make_pair(
239  Quality::full,
240  ShapeBaseChart(ShapeBaseChart::ConstructPath(dir, "full"), 50000,
241  land_color)));
242  }
243  _loaded = true;
244  //if(_basemap_map.size())
245  //LowestQualityBaseMap().LoadSHP();
246 }
247 
248 bool ShapeBaseChart::LoadSHP() {
249  _reader = new shp::ShapefileReader(_filename);
250  auto bounds = _reader->getBounds();
251  _is_usable = _reader->getCount() > 1 && bounds.getMaxX() <= 180 &&
252  bounds.getMinX() >= -180 && bounds.getMinY() >= -90 &&
253  bounds.getMaxY() <=
254  90; // TODO - Do we care whether the planet is covered?
255  _is_usable &= _reader->getGeometryType() == shp::GeometryType::Polygon;
256  bool has_x = false;
257  bool has_y = false;
258  for (auto field : _reader->getFields()) {
259  if (field.getName() == "x") {
260  has_x = true;
261  } else if (field.getName() == "y") {
262  has_y = true;
263  }
264  }
265  _is_tiled = (has_x && has_y);
266  if (_is_usable && _is_tiled) {
267  size_t feat{0};
268  for (auto const &feature : *_reader) {
269  auto f1 = feature.getAttributes();
270  _tiles[LatLonKey(std::any_cast<int>(feature.getAttributes()["y"]),
271  std::any_cast<int>(feature.getAttributes()["x"]))]
272  .push_back(feat);
273  feat++;
274  }
275  }
276  return _is_usable;
277 }
278 
279 void ShapeBaseChart::DoDrawPolygonFilled(ocpnDC &pnt, ViewPort &vp,
280  const shp::Feature &feature) {
281  double old_x = -9999999.0, old_y = -9999999.0;
282  auto polygon = static_cast<shp::Polygon *>(feature.getGeometry());
283  pnt.SetBrush(_color);
284  for (auto &ring : polygon->getRings()) {
285  wxPoint *poly_pt = new wxPoint[ring.getPoints().size()];
286  size_t cnt{0};
287  auto bbox = vp.GetBBox();
288  for (auto &point : ring.getPoints()) {
289  // if (bbox.ContainsMarge(point.getY(), point.getX(), 0.05)) {
290  wxPoint2DDouble q =
291  ShapeBaseChartSet::GetDoublePixFromLL(vp, point.getY(), point.getX());
292  if (round(q.m_x) != round(old_x) || round(q.m_y) != round(old_y)) {
293  poly_pt[cnt].x = round(q.m_x);
294  poly_pt[cnt].y = round(q.m_y);
295  cnt++;
296  }
297  old_x = q.m_x;
298  old_y = q.m_y;
299  //}
300  }
301  if (cnt > 1) {
302  pnt.DrawPolygonTessellated(cnt, poly_pt, 0, 0);
303  }
304  delete[] poly_pt;
305  }
306 }
307 
308 void ShapeBaseChart::AddPointToTessList(shp::Point &point, ViewPort &vp,
309  GLUtesselator *tobj, bool idl) {
310 #ifdef ocpnUSE_GL
311 
312  wxPoint2DDouble q;
313  if (glChartCanvas::HasNormalizedViewPort(vp)) {
314  q = ShapeBaseChartSet::GetDoublePixFromLL(vp, point.getY(), point.getX());
315  } else { // tesselation directly from lat/lon
316  q.m_x = point.getY(), q.m_y = point.getX();
317  }
318  GLvertexshp *vertex = new GLvertexshp();
319  g_vertexesshp.push_back(vertex);
320  if (vp.m_projection_type != PROJECTION_POLAR) {
321  // need to correctly pick +180 or -180 longitude for projections
322  // that have a discontiguous date line
323 
324  if (idl && (point.getX() == 180)) {
325  if (vp.m_projection_type == PROJECTION_MERCATOR ||
326  vp.m_projection_type == PROJECTION_EQUIRECTANGULAR) {
327  // q.m_x -= 40058986 * 4096.0; // 360 degrees in normalized
328  // viewport
329  } else {
330  q.m_x -= 360; // lat/lon coordinates
331  }
332  }
333  }
334  vertex->info.x = q.m_x;
335  vertex->info.y = q.m_y;
336 
337  gluTessVertex(tobj, (GLdouble *)vertex, (GLdouble *)vertex);
338 #endif
339 }
340 
341 void ShapeBaseChart::DoDrawPolygonFilledGL(ocpnDC &pnt, ViewPort &vp,
342  const shp::Feature &feature) {
343 #ifdef ocpnUSE_GL
344 
345  bool idl =
346  vp.GetBBox().GetMinLon() <= -180 || vp.GetBBox().GetMaxLon() >= 180;
347  auto polygon = static_cast<shp::Polygon *>(feature.getGeometry());
348  for (auto &ring : polygon->getRings()) {
349  size_t cnt{0};
350  GLUtesselator *tobj = gluNewTess();
351 
352  gluTessCallback(tobj, GLU_TESS_VERTEX, (_GLUfuncptr)&shpsvertexCallback);
353  gluTessCallback(tobj, GLU_TESS_BEGIN, (_GLUfuncptr)&shpsbeginCallback);
354  gluTessCallback(tobj, GLU_TESS_END, (_GLUfuncptr)&shpsendCallback);
355  gluTessCallback(tobj, GLU_TESS_COMBINE, (_GLUfuncptr)&shpscombineCallback);
356  gluTessCallback(tobj, GLU_TESS_ERROR, (_GLUfuncptr)&shpserrorCallback);
357 
358  gluTessNormal(tobj, 0, 0, 1);
359  gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
360 
361  gluTessBeginPolygon(tobj, NULL);
362  gluTessBeginContour(tobj);
363  for (auto &point : ring.getPoints()) {
364  AddPointToTessList(point, vp, tobj, idl);
365  cnt++;
366  }
367  gluTessEndContour(tobj);
368  gluTessEndPolygon(tobj);
369  gluDeleteTess(tobj);
370 
371  for (auto ver : g_vertexesshp) delete ver;
372  g_vertexesshp.clear();
373  }
374  float_2Dpt *polyv = new float_2Dpt[g_pvshp.size()];
375  int cnt = 0;
376  for (auto pt : g_pvshp) {
377  polyv[cnt++] = pt;
378  }
379  size_t polycnt = g_pvshp.size();
380  g_pvshp.clear();
381 
382  GLuint vbo = 0;
383 
384  // Build the shader viewport transform matrix
385  mat4x4 m, mvp;
386  mat4x4_identity(m);
387  mat4x4_scale_aniso(mvp, m, 2.0 / (float)vp.pix_width,
388  2.0 / (float)vp.pix_height, 1.0);
389  mat4x4_translate_in_place(mvp, -vp.pix_width / 2, vp.pix_height / 2, 0);
390 
391  float *pvt = new float[2 * (polycnt)];
392  for (size_t i = 0; i < polycnt; i++) {
393  float_2Dpt *pc = polyv + i;
394  // wxPoint2DDouble q(pc->y, pc->x);// = vp.GetDoublePixFromLL(pc->y, pc->x);
395  wxPoint2DDouble q = vp.GetDoublePixFromLL(pc->y, pc->x);
396 
397  pvt[i * 2] = q.m_x;
398  pvt[(i * 2) + 1] = q.m_y;
399  }
400 
401  GLShaderProgram *shader = pcolor_tri_shader_program[pnt.m_canvasIndex];
402  shader->Bind();
403 
404  float colorv[4];
405  colorv[0] = _color.Red() / float(256);
406  colorv[1] = _color.Green() / float(256);
407  colorv[2] = _color.Blue() / float(256);
408  colorv[3] = 1.0;
409  shader->SetUniform4fv("color", colorv);
410 
411  shader->SetAttributePointerf("position", pvt);
412 
413  glDrawArrays(GL_TRIANGLES, 0, polycnt);
414 
415  delete[] pvt;
416  delete[] polyv;
417 
418  glDeleteBuffers(1, &vbo);
419  shader->UnBind();
420 #endif
421 }
422 
423 void ShapeBaseChart::DrawPolygonFilled(ocpnDC &pnt, ViewPort &vp) {
424  if (!_is_usable) {
425  return;
426  }
427  if (!_reader) {
428  _loading = true;
429  _loaded = std::async(std::launch::async, [&]() {
430  bool ret = LoadSHP();
431  _loading = false;
432  return ret;
433  });
434  }
435  if (_loading) {
436  if (_loaded.wait_for(std::chrono::milliseconds(0)) ==
437  std::future_status::ready) {
438  _is_usable = _loaded.get();
439  } else {
440  return; // not yet loaded
441  }
442  }
443 
444  int pmod = _dmod;
445  LLBBox bbox = vp.GetBBox();
446 
447  int lat_start = floor(bbox.GetMinLat());
448  if (lat_start < 0)
449  lat_start = lat_start - (pmod + (lat_start % pmod));
450  else
451  lat_start = lat_start - (lat_start % pmod);
452 
453  int lon_start = floor(bbox.GetMinLon());
454  if (lon_start < 0)
455  lon_start = lon_start - (pmod + (lon_start % pmod));
456  else
457  lon_start = lon_start - (lon_start % pmod);
458 
459 
460  if (_is_tiled) {
461  for (int i = lat_start; i < ceil(bbox.GetMaxLat()) + pmod; i+=pmod) {
462  for (int j = lon_start; j < ceil(bbox.GetMaxLon()) + pmod; j+=pmod) {
463  int lon{j};
464  if (j < -180) {
465  lon = j + 360;
466  } else if (j >= 180) {
467  lon = j - 360;
468  }
469  for (auto fid : _tiles[LatLonKey(i, lon)]) {
470  auto const &feature = _reader->getFeature(fid);
471  if (pnt.GetDC()) {
472  DoDrawPolygonFilled(pnt, vp,
473  feature); // Parallelize using std::async?
474  } else {
475  DoDrawPolygonFilledGL(pnt, vp,
476  feature); // Parallelize using std::async?
477  }
478  }
479  }
480  }
481  } else {
482  for (auto const &feature : *_reader) {
483  if (pnt.GetDC()) {
484  DoDrawPolygonFilled(pnt, vp,
485  feature); // Parallelize using std::async?
486  } else {
487  DoDrawPolygonFilledGL(pnt, vp,
488  feature); // Parallelize using std::async?
489  }
490  }
491  }
492 }
493 
494 bool ShapeBaseChart::CrossesLand(double &lat1, double &lon1, double &lat2,
495  double &lon2) {
496  double latmin = std::min(lat1, lat2);
497  double lonmin = std::min(lon1, lon2);
498  double latmax = std::min(lat1, lat2);
499  double lonmax = std::min(lon1, lon2);
500 
501  auto A = std::make_pair(lat1, lon1);
502  auto B = std::make_pair(lat2, lon2);
503 
504  if (_is_tiled) {
505  for (int i = floor(latmin); i < ceil(latmax); i++) {
506  for (int j = floor(lonmin); j < ceil(lonmax); j++) {
507  int lon{j};
508  if (j < -180) {
509  lon = j + 360;
510  } else if (j >= 180) {
511  lon = j - 360;
512  }
513  for (auto fid : _tiles[LatLonKey(i, lon)]) {
514  auto const &feature = _reader->getFeature(fid);
515  if (PolygonLineIntersect(feature, A, B)) {
516  return true;
517  }
518  }
519  }
520  }
521  } else {
522  for (auto const &feature : *_reader) {
523  if (PolygonLineIntersect(feature, A, B)) {
524  return true;
525  }
526  }
527  }
528 
529  return false;
530 }
531 
532 bool ShapeBaseChart::LineLineIntersect(const std::pair<double, double> &A,
533  const std::pair<double, double> &B,
534  const std::pair<double, double> &C,
535  const std::pair<double, double> &D) {
536  // Line AB represented as a1x + b1y = c1
537  double a1 = B.second - A.second;
538  double b1 = A.first - B.first;
539  double c1 = a1 * (A.first) + b1 * (A.second);
540 
541  // Line CD represented as a2x + b2y = c2
542  double a2 = D.second - C.second;
543  double b2 = C.first - D.first;
544  double c2 = a2 * (C.first) + b2 * (C.second);
545 
546  double determinant = a1 * b2 - a2 * b1;
547 
548  if (determinant == 0) {
549  // The lines are parallel
550  return false;
551  } else {
552  // The lines intersect at x,y
553  double x = (b2 * c1 - b1 * c2) / determinant;
554  double y = (a1 * c2 - a2 * c1) / determinant;
555  // x,y must be on both the segments we are checking
556  if (std::min(A.first, B.first) <= x && x <= std::max(A.first, B.first) &&
557  std::min(A.second, B.second) <= y &&
558  y <= std::max(A.second, B.second) && std::min(C.first, D.first) <= x &&
559  x <= std::max(C.first, D.first) && std::min(C.second, D.second) <= y &&
560  y <= std::max(C.second, D.second)) {
561  return true;
562  }
563  }
564  return false;
565 }
566 
567 bool ShapeBaseChart::PolygonLineIntersect(const shp::Feature &feature,
568  const std::pair<double, double> &A,
569  const std::pair<double, double> &B) {
570  auto polygon = static_cast<shp::Polygon *>(feature.getGeometry());
571  std::pair<double, double> previous_point;
572  for (auto &ring : polygon->getRings()) {
573  size_t cnt{0};
574  for (auto &point : ring.getPoints()) {
575  auto pnt = std::make_pair(point.getY(), point.getX());
576  if (cnt > 0) {
577  // TODO: Is it faster to first check if we are in the boundong box of
578  // the line?
579  if (LineLineIntersect(A, B, previous_point, pnt)) {
580  return true;
581  }
582  }
583  previous_point = pnt;
584  cnt++;
585  }
586  }
587  return false;
588 }
589 
590 void ShapeBaseChartSet::RenderViewOnDC(ocpnDC &dc, ViewPort &vp) {
591  if (IsUsable()) {
592  SelectBaseMap(vp.chart_scale).RenderViewOnDC(dc, vp);
593  }
594 }
latitude/longitude key for 1 degree cells
Definition: ocpndc.h:58
Definition: Quilt.cpp:867