OpenCPN Partial API docs
Loading...
Searching...
No Matches
magnetic_plot_map.cpp
Go to the documentation of this file.
1/**************************************************************************
2 * Copyright (C) 2013 by Sean D'Epagnier *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, see <https://www.gnu.org/licenses/>. *
16 **************************************************************************/
17
24#include <wx/wxprec.h>
25
26#ifndef WX_PRECOMP
27#include <wx/wx.h>
28#endif
29
30#include <wx/progdlg.h>
31
32#include "ocpn_plugin.h"
33#include "pi_ocpndc.h"
34
35#ifndef __ANDROID__
36#include <GL/gl.h>
37#include <GL/glu.h>
38#else
39#include "qopengl.h" // this gives us the qt runtime gles2.h
40#include "GL/gl_private.h"
41#endif
42
43#include "GeomagnetismHeader.h"
44#include "magnetic_plot_map.h"
45
46// static const long long lNaN = 0xfff8000000000000;
47// #define qNan (*(double *)&lNaN)
48
49double square(double x) { return x * x; }
50
51/* initialize cache to contain data */
52void ParamCache::Initialize(double step) {
53 if (m_step != step) {
54 m_step = step;
55 delete[] values;
56 int size = static_cast<int>(360 / step);
57 values = new double[size];
58 }
59 m_lat = 100; /* invalidate data */
60}
61
62/* attempt a cache read returning a hit or miss */
63bool ParamCache::Read(double lat, double lon, double &value) {
64 if (lat != m_lat) return false;
65 lon += 180;
66 if (lon > 360) lon -= 360;
67 if (lon < 0 || lon >= 360) return false;
68 double div = lon / m_step;
69 if (div != floor(div)) return false;
70
71 value = values[(int)div];
72 return true;
73}
74
75/* set the accuracy of the map */
76void MagneticPlotMap::ConfigureAccuracy(int step, int poleaccuracy) {
77 /* keeping m_Step powers of 2 */
78 switch (step) {
79 case 1:
80 m_Step = .0625;
81 break;
82 case 2:
83 m_Step = .125;
84 break;
85 case 3:
86 m_Step = .25;
87 break;
88 case 4:
89 m_Step = .5;
90 break;
91 case 5:
92 m_Step = 1;
93 break;
94 case 6:
95 m_Step = 2;
96 break;
97 case 7:
98 m_Step = 4;
99 break;
100 default:
101 m_Step = 8;
102 break;
103 }
104
105 /* keeping m_PoleAccuracy logarithmic */
106 switch (poleaccuracy) {
107 case 1:
108 m_PoleAccuracy = 5e-1;
109 break;
110 case 2:
111 m_PoleAccuracy = 1e-1;
112 break;
113 case 3:
114 m_PoleAccuracy = 1e-2;
115 break;
116 case 4:
117 m_PoleAccuracy = 1e-3;
118 break;
119 default:
120 m_PoleAccuracy = 1e-4;
121 break;
122 }
123}
124
125/* compute the graphed parameter for one lat/lon location */
126double MagneticPlotMap::CalcParameter(double lat, double lon) {
127 MAGtype_CoordSpherical CoordSpherical;
128 MAGtype_CoordGeodetic CoordGeodetic;
129 MAGtype_GeoMagneticElements GeoMagneticElements;
130
131 CoordGeodetic.lambda = lon;
132 CoordGeodetic.phi = lat;
133 CoordGeodetic.HeightAboveEllipsoid = 0;
134 CoordGeodetic.HeightAboveGeoid = 0;
135 CoordGeodetic.UseGeoid = 0;
136
137 /* Convert from geodeitic to Spherical Equations: 17-18, WMM Technical report
138 */
139 MAG_GeodeticToSpherical(*Ellip, CoordGeodetic, &CoordSpherical);
140
141 /* Computes the geoMagnetic field elements and their time change */
142 MAG_Geomag(*Ellip, CoordSpherical, CoordGeodetic, TimedMagneticModel,
143 &GeoMagneticElements);
144 MAG_CalculateGridVariation(CoordGeodetic, &GeoMagneticElements);
145
146 double ret = 0;
147 switch (m_type) {
148 case DECLINATION_PLOT:
149 ret = GeoMagneticElements.Decl >= 180 ? GeoMagneticElements.Decl - 360
150 : GeoMagneticElements.Decl;
151 break;
152 case INCLINATION_PLOT:
153 ret = GeoMagneticElements.Incl;
154 break;
155 case FIELD_STRENGTH_PLOT:
156 ret = GeoMagneticElements.F;
157 break;
158 }
159
160 return ret;
161}
162
163/* build up cache for all longitudes */
164void MagneticPlotMap::BuildParamCache(ParamCache &cache, double lat) {
165 int i = 0;
166 for (double lon = -180; lon < 180; lon += m_Step, i++)
167 cache.values[i] = CalcParameter(lat, lon);
168 cache.m_lat = lat;
169}
170
171/* a possible speedup would be to cache the last 4-10 values
172 calculated as well as the two main cache banks to speed
173 up the recursion in PlotRegion */
174double MagneticPlotMap::CachedCalcParameter(double lat, double lon) {
175 double value;
176 if (!m_Cache[0].Read(lat, lon, value) && !m_Cache[1].Read(lat, lon, value))
177 value = CalcParameter(lat, lon);
178 return value;
179}
180
181/* given a line segment (x1, y1) (x2, y2), find the point (rx, ry) along it
182 which crosses a contour. if lat is true, x1 and x2 are latitudes, other
183 wise longitudes. lonval is the complement to this which is for both x1 and
184 x2 to allow computing new y values along te segment. rx is set to nan if
185 there is no intersection. True is returned if success, otherwise false
186 to signify that we need to dig deeper to get a decent map. */
187bool MagneticPlotMap::Interpolate(double x1, double x2, double y1, double y2,
188 bool lat, double lonval, double &rx,
189 double &ry) {
190 if (fabs(x1 - x2) < m_PoleAccuracy) { /* to avoid recursing too far. make this
191 value smaller to get more accuracy
192 especially near the magnetic poles */
193 rx = NAN; /* set as no intersections */
194 return true;
195 }
196
197 /* this really only happens between geographic and magnetic pole, but to
198 * correct it... */
199 if (m_type == DECLINATION_PLOT) {
200 if (y1 - y2 > 180) y2 += 360;
201 if (y2 - y1 > 180) y1 += 360;
202 }
203
204 y1 /= m_Spacing;
205 y2 /= m_Spacing;
206
207 double fy1 = floor(y1), fy2 = floor(y2);
208 if (fy1 == fy2) {
209 rx = NAN; /* no intersections occured */
210 return true;
211 }
212
213 if (fabs(fy1 - fy2) >
214 1) /* stepped over too many lines, trigger more recursion */
215 return false;
216
217 /* make y2 larger */
218 if (y1 > y2) {
219 double t = y2;
220 y2 = y1;
221 y1 = t;
222 t = x2;
223 x2 = x1;
224 x1 = t;
225 }
226
227 ry = floor(y2); /* don't need this in loop? */
228 for (int i = 0;; i++) {
229 // x1=m*y1+b x2=m*y2+b
230 // (x1-b)/y1=(x2-b)/y2 x1/y1-x2/y2=b*(1/y1-1/y2)
231 // b = (x1/y1-x2/y2)/(1/y1-1/y2)
232 // b = [(x1/y1-x2/y2)* (y1*y2)]/[(1/y1-1/y2) *(y1*y2)]
233 // b = (x1*y2-x2*y1)/(y2-y1)
234
235 rx = (x1 * (y2 - ry) - x2 * (y1 - ry)) / (y2 - y1);
236
237 if (fabs(x1 - x2) < m_PoleAccuracy) /* to avoid recursing too far close */
238 return true;
239
240 double p;
241 if (lat)
242 p = CalcParameter(rx, lonval);
243 else
244 p = CalcParameter(lonval, rx);
245
246 if (std::isnan(p)) /* is this actually correct? */
247 return true;
248
249 if (m_type == DECLINATION_PLOT &&
250 p - ry * m_Spacing < -180) /* way off, try other way around */
251 p += 360;
252
253 p /= m_Spacing;
254
255 double err = p - ry;
256
257 /* this valuee of 1e-3 could be reduces to increase accuracy, this value
258 * seems ok */
259 if (fabs(err) < 1e-3 || p == y1 || p == y2) /* close enough */
260 return true;
261
262 if (err < 0) {
263 if (p < y1) /* undershot.. this case should not hit */
264 return false;
265 x1 = rx;
266 y1 = p;
267 } else {
268 if (p > y2) /* overshot.. this case should not hit */
269 return false;
270 x2 = rx;
271 y2 = p;
272 }
273 }
274}
275
276/* once we have a final line segment, store it in the database */
277void AddLineSeg(std::list<PlotLineSeg *> &region, double lat1, double lon1,
278 double lat2, double lon2, double contour1, double contour2) {
279 if (contour1 != contour2) /* this should not be possible */
280 return;
281
282 auto *seg = new PlotLineSeg(lat1, lon1, lat2, lon2, contour1);
283 region.push_back(seg);
284}
285
286/* we generate contour maps by sampling the value at various
287 latitude and longitude positions:
288
289 lon1 lon3 lon2
290 lat1 1-----*-----2
291 | |
292 lat3 * * lat4
293 | |
294 lat2 3-----*-----4
295 lon4
296
297*/
298void MagneticPlotMap::PlotRegion(std::list<PlotLineSeg *> &region, double lat1,
299 double lon1, double lat2, double lon2) {
300 double p1 = CachedCalcParameter(lat1, lon1);
301 double p2 = CachedCalcParameter(lat1, lon2);
302 double p3 = CachedCalcParameter(lat2, lon1);
303 double p4 = CachedCalcParameter(lat2, lon2);
304
305 if (std::isnan(p1) || std::isnan(p2) || std::isnan(p3) || std::isnan(p4))
306 return;
307
308 double ry1, ry2, ry3, ry4 = 0.0;
309 double lon3, lon4, lat3, lat4 = 0.0;
310 /* horizontal interpolate to determine intermediate longitudes as well
311 as the contours they are on. */
312 if (!Interpolate(lon1, lon2, p1, p2, false, lat1, lon3, ry1) ||
313 !Interpolate(lon1, lon2, p3, p4, false, lat2, lon4, ry2)) {
314 lon3 = (lon1 + lon2) / 2;
315 PlotRegion(region, lat1, lon1, lat2, lon3);
316 PlotRegion(region, lat1, lon3, lat2, lon2);
317 return;
318 }
319
320 /* vertical interpolate */
321 if (!Interpolate(lat1, lat2, p1, p3, true, lon1, lat3, ry3) ||
322 !Interpolate(lat1, lat2, p2, p4, true, lon2, lat4, ry4)) {
323 lat3 = (lat1 + lat2) / 2;
324 PlotRegion(region, lat1, lon1, lat3, lon2);
325 PlotRegion(region, lat3, lon1, lat2, lon2);
326 return;
327 }
328
329 /* un-normalize contours */
330 ry1 *= m_Spacing, ry2 *= m_Spacing, ry3 *= m_Spacing, ry4 *= m_Spacing;
331
332 /* determine which interpolations need line segments */
333 switch (((std::isnan(lat4) * 2 + std::isnan(lat3)) * 2 + std::isnan(lon4)) *
334 2 +
335 std::isnan(lon3)) {
336 case 0: /* all 4 sides? need to recurse to get better resolution */
337 lon3 = (lon1 + lon2) / 2;
338 lat3 = (lat1 + lat2) / 2;
339 PlotRegion(region, lat1, lon1, lat3, lon3);
340 PlotRegion(region, lat1, lon3, lat3, lon2);
341 PlotRegion(region, lat3, lon1, lat2, lon3);
342 PlotRegion(region, lat3, lon3, lat2, lon2);
343 break;
344 case 1:
345 case 2:
346 case 4:
347 case 8:
348 case 7:
349 case 11:
350 case 13:
351 case 14:
352 break; /* impossible! */
353 case 3: /* horizontal */
354 AddLineSeg(region, lat3, lon1, lat4, lon2, ry3, ry4);
355 break;
356 case 5: /* diagonal */
357 AddLineSeg(region, lat2, lon4, lat4, lon2, ry2, ry4);
358 break;
359 case 6: /* diagonal */
360 AddLineSeg(region, lat1, lon3, lat4, lon2, ry1, ry4);
361 break;
362 case 9: /* diagonal */
363 AddLineSeg(region, lat3, lon1, lat2, lon4, ry2, ry3);
364 break;
365 case 10: /* diagonal */
366 AddLineSeg(region, lat3, lon1, lat1, lon3, ry1, ry3);
367 break;
368 case 12: /* vertical */
369 AddLineSeg(region, lat1, lon3, lat2, lon4, ry1, ry2);
370 break;
371 case 15: /* no intersections */
372 break;
373 }
374}
375
376/* rebuild the map at a given date */
377bool MagneticPlotMap::Recompute(wxDateTime date) {
378 if (!m_bEnabled) return true;
379
380 UserDate.Year = date.GetYear();
381 UserDate.Month = date.GetMonth();
382 UserDate.Day = date.GetDay();
383
384 char err[255];
385 MAG_DateToYear(&UserDate, err);
386
387 /* Time adjust the coefficients, Equation 19, WMM Technical report */
388 MAG_TimelyModifyMagneticModel(UserDate, MagneticModel, TimedMagneticModel);
389
390 /* clear out old data */
391 ClearMap();
392
393 wxGenericProgressDialog progressdialog(
394 _("Building Magnetic Map"),
395 m_type == DECLINATION_PLOT ? _("Variation")
396 : m_type == INCLINATION_PLOT ? _("Inclination")
397 : _("Field Strength"),
398 180, nullptr,
399 wxPD_SMOOTH | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME | wxPD_CAN_ABORT);
400
401 int cachepage = 0;
402 m_Cache[0].Initialize(m_Step);
403 m_Cache[1].Initialize(m_Step);
404
405 BuildParamCache(m_Cache[cachepage], -MAX_LAT);
406
407 for (double lat = -MAX_LAT; lat + m_Step <= MAX_LAT; lat += m_Step) {
408 if (!progressdialog.Update(lat + 90)) {
409 return false;
410 }
411
412 cachepage = !cachepage;
413 BuildParamCache(m_Cache[cachepage], lat + m_Step);
414
415 int latind = floor((lat + MAX_LAT) / ZONE_SIZE);
416 if (latind > LATITUDE_ZONES - 1) latind = LATITUDE_ZONES - 1;
417 for (double lon = -180; lon + m_Step <= 180; lon += m_Step) {
418 int lonind = floor((lon + 180) / ZONE_SIZE);
419 PlotRegion(m_map[latind][lonind], lat, lon, lat + m_Step, lon + m_Step);
420 }
421 }
422
423 return true;
424}
425
426/* draw a line segment in opengl from lat/lon and viewport */
427void DrawLineSeg(pi_ocpnDC *dc, PlugIn_ViewPort &VP, double lat1, double lon1,
428 double lat2, double lon2) {
429 /* avoid lines which cross over the view port the long way */
430 if (lon1 + 180 < VP.clon && lon2 + 180 > VP.clon) return;
431 if (lon1 + 180 > VP.clon && lon2 + 180 < VP.clon) return;
432 if (lon1 - 180 < VP.clon && lon2 - 180 > VP.clon) return;
433 if (lon1 - 180 > VP.clon && lon2 - 180 < VP.clon) return;
434
435 wxPoint r1, r2;
436 GetCanvasPixLL(&VP, &r1, lat1, lon1);
437 GetCanvasPixLL(&VP, &r2, lat2, lon2);
438
439 dc->DrawLine(r1.x, r1.y, r2.x, r2.y);
440#if 0
441 if(dc)
442 dc->DrawLine(r1.x, r1.y, r2.x, r2.y);
443 else {
444 glBegin(GL_LINES);
445 glVertex2i(r1.x, r1.y);
446 glVertex2i(r2.x, r2.y);
447 glEnd();
448 }
449#endif
450}
451
452/* reset the map and clear all the data so it can be reused */
453void MagneticPlotMap::ClearMap() {
454 for (int latind = 0; latind < LATITUDE_ZONES; latind++)
455 for (int lonind = 0; lonind < LONGITUDE_ZONES; lonind++)
456 m_map[latind][lonind].clear();
457}
458
459/* draw text of the value of a contour at a given location */
460void MagneticPlotMap::DrawContour(pi_ocpnDC *dc, PlugIn_ViewPort &VP,
461 double contour, double lat, double lon) {
462 wxPoint r;
463
464 GetCanvasPixLL(&VP, &r, lat, lon);
465
466 double dist_squared = square(r.x - lastx) + square(r.y - lasty);
467 /* avoid printing numbers on top of each other */
468 if (dist_squared < 40000) return;
469
470 lastx = r.x;
471 lasty = r.y;
472
473 wxString msg;
474 msg.Printf("%.0f", contour);
475
476 int w, h;
477 dc->GetTextExtent(msg, &w, &h);
478 dc->DrawText(msg, r.x - w / 2, r.y - h / 2);
479#if 0
480 if(dc) {
481 int w, h;
482 dc->GetTextExtent( msg, &w, &h);
483 dc->DrawText(msg, r.x - w/2, r.y - h/2);
484 } else {
485 glEnable( GL_BLEND );
486 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
487
488 int w, h;
489 m_TexFont.GetTextExtent( msg, &w, &h);
490
491 glEnable(GL_TEXTURE_2D);
492 m_TexFont.RenderString(msg, r.x - w/2, r.y - h/2);
493 glDisable(GL_TEXTURE_2D);
494
495 glDisable(GL_BLEND);
496 }
497#endif
498}
499
500/* plot to dc, or opengl is dc is NULL */
501void MagneticPlotMap::Plot(pi_ocpnDC *dc, PlugIn_ViewPort *vp, wxColour color) {
502 if (!m_bEnabled) return;
503
504 wxFont font(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_ITALIC,
505 wxFONTWEIGHT_NORMAL);
506
507 dc->SetPen(wxPen(color, 3));
508 dc->SetTextForeground(color);
509 dc->SetFont(font);
510#if 0
511 if(dc) {
512 dc->SetPen(wxPen(color, 3));
513 dc->SetTextForeground(color);
514 dc->SetFont( font );
515 } else {
516 glLineWidth(3.0);
517 glColor4ub(color.Red(), color.Green(), color.Blue(), color.Alpha());
518 m_TexFont.Build( font );
519 }
520#endif
521 int startlatind = floor((vp->lat_min + MAX_LAT) / ZONE_SIZE);
522 if (startlatind < 0) startlatind = 0;
523
524 int endlatind = floor((vp->lat_max + MAX_LAT) / ZONE_SIZE);
525 if (endlatind > LATITUDE_ZONES - 1) endlatind = LATITUDE_ZONES - 1;
526
527 double lon_min = vp->lon_min; /* expected +- 360 convert to +- 180 */
528 if (lon_min < -180)
529 lon_min += 360;
530 else if (lon_min >= 180)
531 lon_min -= 360;
532 int startlonind = floor((lon_min + 180) / ZONE_SIZE);
533 if (startlonind < 0) startlonind = LONGITUDE_ZONES - 1;
534 if (startlonind > LONGITUDE_ZONES - 1) startlonind = 0;
535
536 double lon_max = vp->lon_max; /* expected +- 360 convert to +- 180 */
537 if (lon_max < -180)
538 lon_max += 360;
539 else if (lon_max >= 180)
540 lon_max -= 360;
541 int endlonind = floor((lon_max + 180) / ZONE_SIZE);
542 if (endlonind < 0) endlonind = LONGITUDE_ZONES - 1;
543 if (endlonind > LONGITUDE_ZONES - 1) endlonind = 0;
544
545 for (int latind = startlatind; latind <= endlatind; latind++)
546 for (int lonind = startlonind;; lonind++) {
547 if (lonind > LONGITUDE_ZONES - 1) lonind = 0;
548 for (auto it = m_map[latind][lonind].begin();
549 it != m_map[latind][lonind].end(); it++) {
550 DrawLineSeg(dc, *vp, (*it)->lat1, (*it)->lon1, (*it)->lat2,
551 (*it)->lon2);
552 wxString msg;
553 DrawContour(dc, *vp, (*it)->contour, ((*it)->lat1 + (*it)->lat2) / 2,
554 ((*it)->lon1 + (*it)->lon2) / 2);
555 }
556 if (lonind == endlonind) break;
557 }
558}
Geomagnetism Library subroutines.
Contains view parameters and status information for a chart display viewport.
double lon_max
Maximum longitude of the viewport.
double clon
Center longitude of the viewport in decimal degrees.
double lat_max
Maximum latitude of the viewport.
double lon_min
Minimum longitude of the viewport.
double lat_min
Minimum latitude of the viewport.
NavmsgFilter Read(const std::string &name)
Read filter with given name from disk.
WMM plugin magnetic plotmap utility.
PlugIn Object Definition/API.
void GetCanvasPixLL(PlugIn_ViewPort *vp, wxPoint *pp, double lat, double lon)
Converts lat/lon to canvas physical pixel coordinates.
Graphics abstraction layer on top of wxDC or OpenGL.