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