30 #include "OCPNPlatform.h"
31 #include "shapefile_basemap.h"
32 #include "chartbase.h"
33 #include "glChartCanvas.h"
40 #define __CALL_CONVENTION
42 #define __CALL_CONVENTION
46 extern wxString gWorldShapefileLocation;
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;
68 void __CALL_CONVENTION shpscombineCallback(GLdouble coords[3],
69 GLdouble *vertex_data[4],
74 vertex =
new GLvertexshp();
75 g_vertexesshp.push_back(vertex);
77 vertex->info.x = coords[0];
78 vertex->info.y = coords[1];
80 *dataOut = vertex->data;
83 void __CALL_CONVENTION shpserrorCallback(GLenum errorCode) {
84 const GLubyte *estring;
85 estring = gluErrorString(errorCode);
89 void __CALL_CONVENTION shpsbeginCallback(GLenum type) {
92 case GL_TRIANGLE_STRIP:
97 printf(
"tess unhandled begin type: %d\n", type);
103 void __CALL_CONVENTION shpsendCallback() {}
105 void __CALL_CONVENTION shpsvertexCallback(GLvoid *arg) {
107 vertex = (GLvertexshp *)arg;
109 p.y = vertex->info.x;
110 p.x = vertex->info.y;
113 if (g_typeshp != GL_TRIANGLES) {
115 g_pvshp.push_back(g_p1shp);
116 g_pvshp.push_back(g_p2shp);
119 if (g_typeshp == GL_TRIANGLE_STRIP)
121 else if (g_posshp == 0)
126 g_pvshp.push_back(p);
131 ShapeBaseChartSet::ShapeBaseChartSet() : _loaded(false) {
134 wxPoint2DDouble ShapeBaseChartSet::GetDoublePixFromLL(
ViewPort &vp,
double lat,
136 wxPoint2DDouble p = vp.GetDoublePixFromLL(lat, lon);
137 p.m_x -= vp.rv_rect.x, p.m_y -= vp.rv_rect.y;
142 if (_basemap_map.find(Quality::crude) != _basemap_map.end()) {
143 return _basemap_map.at(Quality::crude);
145 if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
146 return _basemap_map.at(Quality::low);
148 if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
149 return _basemap_map.at(Quality::medium);
151 if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
152 return _basemap_map.at(Quality::high);
154 return _basemap_map.at(Quality::full);
158 if (_basemap_map.find(Quality::full) != _basemap_map.end()) {
159 return _basemap_map.at(Quality::full);
161 if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
162 return _basemap_map.at(Quality::high);
164 if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
165 return _basemap_map.at(Quality::medium);
167 if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
168 return _basemap_map.at(Quality::low);
170 return _basemap_map.at(Quality::crude);
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);
192 return LowestQualityBaseMap();
195 void ShapeBaseChartSet::Reset() {
197 wxString basemap_dir;
198 if (gWorldShapefileLocation.empty()) {
199 basemap_dir = g_Platform->GetSharedDataDir();
200 basemap_dir.Append(
"basemap_shp");
202 basemap_dir = gWorldShapefileLocation;
205 LoadBasemaps(basemap_dir.ToStdString());
207 void ShapeBaseChartSet::LoadBasemaps(
const std::string &dir) {
209 _basemap_map.clear();
211 wxColor land_color = wxColor(170, 175, 80);
213 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"crude_10x10"))) {
214 auto c =
ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"crude_10x10"), 300000000,
217 _basemap_map.insert(std::make_pair(Quality::crude, c));
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)));
225 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"medium"))) {
226 _basemap_map.insert(std::make_pair(
228 ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"medium"), 1000000,
231 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"high"))) {
232 _basemap_map.insert(std::make_pair(
234 ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"high"), 300000,
237 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"full"))) {
238 _basemap_map.insert(std::make_pair(
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 &&
255 _is_usable &= _reader->getGeometryType() == shp::GeometryType::Polygon;
258 for (
auto field : _reader->getFields()) {
259 if (field.getName() ==
"x") {
261 }
else if (field.getName() ==
"y") {
265 _is_tiled = (has_x && has_y);
266 if (_is_usable && _is_tiled) {
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"]))]
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()];
287 auto bbox = vp.GetBBox();
288 for (
auto &point : ring.getPoints()) {
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);
302 pnt.DrawPolygonTessellated(cnt, poly_pt, 0, 0);
308 void ShapeBaseChart::AddPointToTessList(shp::Point &point,
ViewPort &vp,
309 GLUtesselator *tobj,
bool idl) {
313 if (glChartCanvas::HasNormalizedViewPort(vp)) {
314 q = ShapeBaseChartSet::GetDoublePixFromLL(vp, point.getY(), point.getX());
316 q.m_x = point.getY(), q.m_y = point.getX();
318 GLvertexshp *vertex =
new GLvertexshp();
319 g_vertexesshp.push_back(vertex);
320 if (vp.m_projection_type != PROJECTION_POLAR) {
324 if (idl && (point.getX() == 180)) {
325 if (vp.m_projection_type == PROJECTION_MERCATOR ||
326 vp.m_projection_type == PROJECTION_EQUIRECTANGULAR) {
334 vertex->info.x = q.m_x;
335 vertex->info.y = q.m_y;
337 gluTessVertex(tobj, (GLdouble *)vertex, (GLdouble *)vertex);
341 void ShapeBaseChart::DoDrawPolygonFilledGL(
ocpnDC &pnt,
ViewPort &vp,
342 const shp::Feature &feature) {
346 vp.GetBBox().GetMinLon() <= -180 || vp.GetBBox().GetMaxLon() >= 180;
347 auto polygon =
static_cast<shp::Polygon *
>(feature.getGeometry());
348 for (
auto &ring : polygon->getRings()) {
350 GLUtesselator *tobj = gluNewTess();
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);
358 gluTessNormal(tobj, 0, 0, 1);
359 gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
361 gluTessBeginPolygon(tobj, NULL);
362 gluTessBeginContour(tobj);
363 for (
auto &point : ring.getPoints()) {
364 AddPointToTessList(point, vp, tobj, idl);
367 gluTessEndContour(tobj);
368 gluTessEndPolygon(tobj);
371 for (
auto ver : g_vertexesshp)
delete ver;
372 g_vertexesshp.clear();
374 float_2Dpt *polyv =
new float_2Dpt[g_pvshp.size()];
376 for (
auto pt : g_pvshp) {
379 size_t polycnt = g_pvshp.size();
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);
391 float *pvt =
new float[2 * (polycnt)];
392 for (
size_t i = 0; i < polycnt; i++) {
393 float_2Dpt *pc = polyv + i;
395 wxPoint2DDouble q = vp.GetDoublePixFromLL(pc->y, pc->x);
398 pvt[(i * 2) + 1] = q.m_y;
401 GLShaderProgram *shader = pcolor_tri_shader_program[pnt.m_canvasIndex];
405 colorv[0] = _color.Red() / float(256);
406 colorv[1] = _color.Green() / float(256);
407 colorv[2] = _color.Blue() / float(256);
409 shader->SetUniform4fv(
"color", colorv);
411 shader->SetAttributePointerf(
"position", pvt);
413 glDrawArrays(GL_TRIANGLES, 0, polycnt);
418 glDeleteBuffers(1, &vbo);
423 void ShapeBaseChart::DrawPolygonFilled(
ocpnDC &pnt,
ViewPort &vp) {
429 _loaded = std::async(std::launch::async, [&]() {
430 bool ret = LoadSHP();
436 if (_loaded.wait_for(std::chrono::milliseconds(0)) ==
437 std::future_status::ready) {
438 _is_usable = _loaded.get();
445 LLBBox bbox = vp.GetBBox();
447 int lat_start = floor(bbox.GetMinLat());
449 lat_start = lat_start - (pmod + (lat_start % pmod));
451 lat_start = lat_start - (lat_start % pmod);
453 int lon_start = floor(bbox.GetMinLon());
455 lon_start = lon_start - (pmod + (lon_start % pmod));
457 lon_start = lon_start - (lon_start % pmod);
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) {
466 }
else if (j >= 180) {
469 for (
auto fid : _tiles[
LatLonKey(i, lon)]) {
470 auto const &feature = _reader->getFeature(fid);
472 DoDrawPolygonFilled(pnt, vp,
475 DoDrawPolygonFilledGL(pnt, vp,
482 for (
auto const &feature : *_reader) {
484 DoDrawPolygonFilled(pnt, vp,
487 DoDrawPolygonFilledGL(pnt, vp,
494 bool ShapeBaseChart::CrossesLand(
double &lat1,
double &lon1,
double &lat2,
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);
501 auto A = std::make_pair(lat1, lon1);
502 auto B = std::make_pair(lat2, lon2);
505 for (
int i = floor(latmin); i < ceil(latmax); i++) {
506 for (
int j = floor(lonmin); j < ceil(lonmax); j++) {
510 }
else if (j >= 180) {
513 for (
auto fid : _tiles[
LatLonKey(i, lon)]) {
514 auto const &feature = _reader->getFeature(fid);
515 if (PolygonLineIntersect(feature, A, B)) {
522 for (
auto const &feature : *_reader) {
523 if (PolygonLineIntersect(feature, A, B)) {
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) {
537 double a1 = B.second - A.second;
538 double b1 = A.first - B.first;
539 double c1 = a1 * (A.first) + b1 * (A.second);
542 double a2 = D.second - C.second;
543 double b2 = C.first - D.first;
544 double c2 = a2 * (C.first) + b2 * (C.second);
546 double determinant = a1 * b2 - a2 * b1;
548 if (determinant == 0) {
553 double x = (b2 * c1 - b1 * c2) / determinant;
554 double y = (a1 * c2 - a2 * c1) / determinant;
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)) {
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()) {
574 for (
auto &point : ring.getPoints()) {
575 auto pnt = std::make_pair(point.getY(), point.getX());
579 if (LineLineIntersect(A, B, previous_point, pnt)) {
583 previous_point = pnt;
590 void ShapeBaseChartSet::RenderViewOnDC(
ocpnDC &dc,
ViewPort &vp) {
592 SelectBaseMap(vp.chart_scale).RenderViewOnDC(dc, vp);
latitude/longitude key for 1 degree cells