30#include "OCPNPlatform.h"
31#include "shapefile_basemap.h"
33#include "glChartCanvas.h"
42#define __CALL_CONVENTION
44#define __CALL_CONVENTION
48extern wxString gWorldShapefileLocation;
65static std::list<float_2Dpt> g_pvshp;
66static std::list<GLvertexshp *> g_vertexesshp;
67static int g_typeshp, g_posshp;
68static float_2Dpt g_p1shp, g_p2shp;
70void __CALL_CONVENTION shpscombineCallback(GLdouble coords[3],
71 GLdouble *vertex_data[4],
76 vertex =
new GLvertexshp();
77 g_vertexesshp.push_back(vertex);
79 vertex->info.x = coords[0];
80 vertex->info.y = coords[1];
82 *dataOut = vertex->data;
85void __CALL_CONVENTION shpserrorCallback(GLenum errorCode) {
86 const GLubyte *estring;
87 estring = gluErrorString(errorCode);
91void __CALL_CONVENTION shpsbeginCallback(GLenum type) {
94 case GL_TRIANGLE_STRIP:
99 printf(
"tess unhandled begin type: %d\n", type);
105void __CALL_CONVENTION shpsendCallback() {}
107void __CALL_CONVENTION shpsvertexCallback(GLvoid *arg) {
109 vertex = (GLvertexshp *)arg;
111 p.y = vertex->info.x;
112 p.x = vertex->info.y;
115 if (g_typeshp != GL_TRIANGLES) {
117 g_pvshp.push_back(g_p1shp);
118 g_pvshp.push_back(g_p2shp);
121 if (g_typeshp == GL_TRIANGLE_STRIP)
123 else if (g_posshp == 0)
128 g_pvshp.push_back(p);
133ShapeBaseChartSet::ShapeBaseChartSet() : _loaded(false) {
134 land_color = wxColor(170, 175, 80);
136void ShapeBaseChartSet::SetBasemapLandColor(wxColor color) {
139wxColor ShapeBaseChartSet::GetBasemapLandColor() {
return land_color; }
141wxPoint2DDouble ShapeBaseChartSet::GetDoublePixFromLL(
ViewPort &vp,
double lat,
144 p.m_x -= vp.rv_rect.x, p.m_y -= vp.rv_rect.y;
149 if (_basemap_map.find(Quality::crude) != _basemap_map.end()) {
150 return _basemap_map.at(Quality::crude);
152 if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
153 return _basemap_map.at(Quality::low);
155 if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
156 return _basemap_map.at(Quality::medium);
158 if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
159 return _basemap_map.at(Quality::high);
161 return _basemap_map.at(Quality::full);
165 if (_basemap_map.find(Quality::full) != _basemap_map.end()) {
166 return _basemap_map.at(Quality::full);
168 if (_basemap_map.find(Quality::high) != _basemap_map.end()) {
169 return _basemap_map.at(Quality::high);
171 if (_basemap_map.find(Quality::medium) != _basemap_map.end()) {
172 return _basemap_map.at(Quality::medium);
174 if (_basemap_map.find(Quality::low) != _basemap_map.end()) {
175 return _basemap_map.at(Quality::low);
177 return _basemap_map.at(Quality::crude);
181 if (_basemap_map.find(Quality::full) != _basemap_map.end() &&
182 _basemap_map.at(Quality::full).IsUsable() &&
183 scale <= _basemap_map.at(Quality::full).MinScale()) {
184 return _basemap_map.at(Quality::full);
185 }
else if (_basemap_map.find(Quality::high) != _basemap_map.end() &&
186 _basemap_map.at(Quality::high).IsUsable() &&
187 scale <= _basemap_map.at(Quality::high).MinScale()) {
188 return _basemap_map.at(Quality::high);
189 }
else if (_basemap_map.find(Quality::medium) != _basemap_map.end() &&
190 _basemap_map.at(Quality::medium).IsUsable() &&
191 scale <= _basemap_map.at(Quality::medium).MinScale()) {
192 return _basemap_map.at(Quality::medium);
193 }
else if (_basemap_map.find(Quality::low) != _basemap_map.end() &&
194 _basemap_map.at(Quality::low).IsUsable() &&
195 scale <= _basemap_map.at(Quality::low).MinScale()) {
196 return _basemap_map.at(Quality::low);
198 return LowestQualityBaseMap();
201void ShapeBaseChartSet::Reset() {
203 wxString basemap_dir;
204 if (gWorldShapefileLocation.empty()) {
205 basemap_dir = g_Platform->GetSharedDataDir();
206 basemap_dir.Append(
"basemap_shp");
208 basemap_dir = gWorldShapefileLocation;
211 LoadBasemaps(basemap_dir.ToStdString());
213void ShapeBaseChartSet::LoadBasemaps(
const std::string &dir) {
215 _basemap_map.clear();
217 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"crude_10x10"))) {
218 auto c =
ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"crude_10x10"),
219 300000000, land_color);
221 _basemap_map.insert(std::make_pair(Quality::crude, c));
224 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"low"))) {
225 _basemap_map.insert(std::make_pair(
226 Quality::low,
ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"low"),
227 15000000, land_color)));
229 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"medium"))) {
230 _basemap_map.insert(std::make_pair(
232 ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"medium"), 1000000,
235 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"high"))) {
236 _basemap_map.insert(std::make_pair(
238 ShapeBaseChart(ShapeBaseChart::ConstructPath(dir,
"high"), 300000,
241 if (fs::exists(ShapeBaseChart::ConstructPath(dir,
"full"))) {
242 _basemap_map.insert(std::make_pair(
252bool ShapeBaseChart::LoadSHP() {
253 if (!fs::exists(_filename)) {
257 std::unique_ptr<shp::ShapefileReader> temp_reader(
258 new shp::ShapefileReader(_filename));
259 if (!temp_reader->isOpen()) {
260 MESSAGE_LOG <<
"Shapefile " << _filename <<
" is not opened";
268 auto bounds = temp_reader->getBounds();
269 _is_usable = temp_reader->getCount() > 1 && bounds.getMaxX() <= 180 &&
270 bounds.getMinX() >= -180 && bounds.getMinY() >= -90 &&
278 _is_usable &= temp_reader->getGeometryType() == shp::GeometryType::Polygon;
281 for (
auto field : temp_reader->getFields()) {
282 if (field.getName() ==
"x") {
284 }
else if (field.getName() ==
"y") {
288 _is_tiled = (has_x && has_y);
289 if (_is_usable && _is_tiled) {
291 for (
auto const &feature : *temp_reader) {
297 auto f1 = feature.getAttributes();
298 _tiles[
LatLonKey(std::any_cast<int>(feature.getAttributes()[
"y"]),
299 std::any_cast<int>(feature.getAttributes()[
"x"]))]
305 _reader = temp_reader.release();
310void ShapeBaseChart::DoDrawPolygonFilled(
ocpnDC &pnt,
ViewPort &vp,
311 const shp::Feature &feature) {
312 double old_x = -9999999.0, old_y = -9999999.0;
313 auto polygon =
static_cast<shp::Polygon *
>(feature.getGeometry());
314 pnt.SetBrush(_color);
315 for (
auto &ring : polygon->getRings()) {
316 wxPoint *poly_pt =
new wxPoint[ring.getPoints().size()];
318 auto bbox = vp.GetBBox();
319 for (
auto &point : ring.getPoints()) {
322 ShapeBaseChartSet::GetDoublePixFromLL(vp, point.getY(), point.getX());
323 if (round(q.m_x) != round(old_x) || round(q.m_y) != round(old_y)) {
324 poly_pt[cnt].x = round(q.m_x);
325 poly_pt[cnt].y = round(q.m_y);
333 pnt.DrawPolygonTessellated(cnt, poly_pt, 0, 0);
339void ShapeBaseChart::AddPointToTessList(shp::Point &point,
ViewPort &vp,
340 GLUtesselator *tobj,
bool idl) {
344 if (glChartCanvas::HasNormalizedViewPort(vp)) {
345 q = ShapeBaseChartSet::GetDoublePixFromLL(vp, point.getY(), point.getX());
347 q.m_x = point.getY(), q.m_y = point.getX();
349 GLvertexshp *vertex =
new GLvertexshp();
350 g_vertexesshp.push_back(vertex);
351 if (vp.m_projection_type != PROJECTION_POLAR) {
355 if (idl && (point.getX() == 180)) {
356 if (vp.m_projection_type == PROJECTION_MERCATOR ||
357 vp.m_projection_type == PROJECTION_EQUIRECTANGULAR) {
365 vertex->info.x = q.m_x;
366 vertex->info.y = q.m_y;
368 gluTessVertex(tobj, (GLdouble *)vertex, (GLdouble *)vertex);
372void ShapeBaseChart::DoDrawPolygonFilledGL(
ocpnDC &pnt,
ViewPort &vp,
373 const shp::Feature &feature) {
377 vp.GetBBox().GetMinLon() <= -180 || vp.GetBBox().GetMaxLon() >= 180;
378 auto polygon =
static_cast<shp::Polygon *
>(feature.getGeometry());
379 for (
auto &ring : polygon->getRings()) {
381 GLUtesselator *tobj = gluNewTess();
383 gluTessCallback(tobj, GLU_TESS_VERTEX, (_GLUfuncptr)&shpsvertexCallback);
384 gluTessCallback(tobj, GLU_TESS_BEGIN, (_GLUfuncptr)&shpsbeginCallback);
385 gluTessCallback(tobj, GLU_TESS_END, (_GLUfuncptr)&shpsendCallback);
386 gluTessCallback(tobj, GLU_TESS_COMBINE, (_GLUfuncptr)&shpscombineCallback);
387 gluTessCallback(tobj, GLU_TESS_ERROR, (_GLUfuncptr)&shpserrorCallback);
389 gluTessNormal(tobj, 0, 0, 1);
390 gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO);
392 gluTessBeginPolygon(tobj, NULL);
393 gluTessBeginContour(tobj);
394 for (
auto &point : ring.getPoints()) {
395 AddPointToTessList(point, vp, tobj, idl);
398 gluTessEndContour(tobj);
399 gluTessEndPolygon(tobj);
402 for (
auto ver : g_vertexesshp) delete ver;
403 g_vertexesshp.clear();
405 float_2Dpt *polyv =
new float_2Dpt[g_pvshp.size()];
407 for (
auto pt : g_pvshp) {
410 size_t polycnt = g_pvshp.size();
418 mat4x4_scale_aniso(mvp, m, 2.0 / (
float)vp.
pix_width,
422 float *pvt =
new float[2 * (polycnt)];
423 for (
size_t i = 0; i < polycnt; i++) {
424 float_2Dpt *pc = polyv + i;
429 pvt[(i * 2) + 1] = q.m_y;
432 GLShaderProgram *shader = pcolor_tri_shader_program[pnt.m_canvasIndex];
436 colorv[0] = _color.Red() / float(256);
437 colorv[1] = _color.Green() / float(256);
438 colorv[2] = _color.Blue() / float(256);
440 shader->SetUniform4fv(
"color", colorv);
442 shader->SetAttributePointerf(
"position", pvt);
444 glDrawArrays(GL_TRIANGLES, 0, polycnt);
449 glDeleteBuffers(1, &vbo);
454void ShapeBaseChart::DrawPolygonFilled(
ocpnDC &pnt,
ViewPort &vp) {
460 _loaded = std::async(std::launch::async, [&]() {
461 bool ret = LoadSHP();
467 if (_loaded.wait_for(std::chrono::milliseconds(0)) ==
468 std::future_status::ready) {
469 _is_usable = _loaded.get();
476 LLBBox bbox = vp.GetBBox();
478 int lat_start = floor(bbox.GetMinLat());
480 lat_start = lat_start - (pmod + (lat_start % pmod));
482 lat_start = lat_start - (lat_start % pmod);
484 int lon_start = floor(bbox.GetMinLon());
486 lon_start = lon_start - (pmod + (lon_start % pmod));
488 lon_start = lon_start - (lon_start % pmod);
491 for (
int i = lat_start; i < ceil(bbox.GetMaxLat()) + pmod; i += pmod) {
492 for (
int j = lon_start; j < ceil(bbox.GetMaxLon()) + pmod; j += pmod) {
496 }
else if (j >= 180) {
499 for (
auto fid : _tiles[
LatLonKey(i, lon)]) {
500 auto const &feature = _reader->getFeature(fid);
502 DoDrawPolygonFilled(pnt, vp,
505 DoDrawPolygonFilledGL(pnt, vp,
512 for (
auto const &feature : *_reader) {
514 DoDrawPolygonFilled(pnt, vp,
517 DoDrawPolygonFilledGL(pnt, vp,
524bool ShapeBaseChart::CrossesLand(
double &lat1,
double &lon1,
double &lat2,
526 double latmin = std::min(lat1, lat2);
527 double lonmin = std::min(lon1, lon2);
528 double latmax = std::min(lat1, lat2);
529 double lonmax = std::min(lon1, lon2);
531 auto A = std::make_pair(lat1, lon1);
532 auto B = std::make_pair(lat2, lon2);
535 for (
int i = floor(latmin); i < ceil(latmax); i++) {
536 for (
int j = floor(lonmin); j < ceil(lonmax); j++) {
540 }
else if (j >= 180) {
543 for (
auto fid : _tiles[
LatLonKey(i, lon)]) {
544 auto const &feature = _reader->getFeature(fid);
545 if (PolygonLineIntersect(feature, A, B)) {
552 for (
auto const &feature : *_reader) {
553 if (PolygonLineIntersect(feature, A, B)) {
565 if (_loaded.valid()) {
571bool ShapeBaseChart::LineLineIntersect(
const std::pair<double, double> &A,
572 const std::pair<double, double> &B,
573 const std::pair<double, double> &C,
574 const std::pair<double, double> &D) {
576 double a1 = B.second - A.second;
577 double b1 = A.first - B.first;
578 double c1 = a1 * (A.first) + b1 * (A.second);
581 double a2 = D.second - C.second;
582 double b2 = C.first - D.first;
583 double c2 = a2 * (C.first) + b2 * (C.second);
585 double determinant = a1 * b2 - a2 * b1;
587 if (determinant == 0) {
592 double x = (b2 * c1 - b1 * c2) / determinant;
593 double y = (a1 * c2 - a2 * c1) / determinant;
595 if (std::min(A.first, B.first) <= x && x <= std::max(A.first, B.first) &&
596 std::min(A.second, B.second) <= y &&
597 y <= std::max(A.second, B.second) && std::min(C.first, D.first) <= x &&
598 x <= std::max(C.first, D.first) && std::min(C.second, D.second) <= y &&
599 y <= std::max(C.second, D.second)) {
606bool ShapeBaseChart::PolygonLineIntersect(
const shp::Feature &feature,
607 const std::pair<double, double> &A,
608 const std::pair<double, double> &B) {
609 auto polygon =
static_cast<shp::Polygon *
>(feature.getGeometry());
610 std::pair<double, double> previous_point;
611 for (
auto &ring : polygon->getRings()) {
613 for (
auto &point : ring.getPoints()) {
614 auto pnt = std::make_pair(point.getY(), point.getX());
618 if (LineLineIntersect(A, B, previous_point, pnt)) {
622 previous_point = pnt;
632 chart.SetColor(land_color);
633 chart.RenderViewOnDC(dc, vp);
Wrapper class for OpenGL shader programs.
latitude/longitude key for 1 degree cells
Represents a basemap chart based on shapefile data.
void CancelLoading()
Cancel the chart loading operation.
Represents the view port for chart display in OpenCPN.
int pix_height
Height of the viewport in physical pixels.
int pix_width
Width of the viewport in physical pixels.
wxPoint2DDouble GetDoublePixFromLL(double lat, double lon)
Convert latitude and longitude on the ViewPort to physical pixel coordinates with double precision.
double chart_scale
Chart scale denominator (e.g., 50000 for a 1:50000 scale).
Device context class that can use either wxDC or OpenGL for drawing.
Enhanced logging interface on top of wx/log.h.