diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c1dfbf75..673c7c6fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,8 +96,10 @@ set(SUPPORT formspec.cc xmltag.cc cet.cc cet_util.cc fatal.cc rgbcolors.cc inifile.cc garmin_fs.cc units.cc gbser.cc gbfile.cc parse.cc session.cc main.cc globals.cc + src/core/nvector.cc src/core/textstream.cc src/core/usasciicodec.cc + src/core/vector3d.cc src/core/xmlstreamwriter.cc ) if(${QT_VERSION_MAJOR} EQUAL "6") @@ -187,8 +189,10 @@ set(HEADERS src/core/datetime.h src/core/file.h src/core/logging.h + src/core/nvector.h src/core/textstream.h src/core/usasciicodec.h + src/core/vector3d.h src/core/xmlstreamwriter.h src/core/xmltag.h ) diff --git a/GPSBabel.pro b/GPSBabel.pro index 0bd2a2696..3ff2771d2 100644 --- a/GPSBabel.pro +++ b/GPSBabel.pro @@ -107,8 +107,10 @@ SUPPORT = route.cc waypt.cc filter_vecs.cc util.cc vecs.cc mkshort.cc \ formspec.cc xmltag.cc cet.cc cet_util.cc fatal.cc rgbcolors.cc \ inifile.cc garmin_fs.cc units.cc gbser.cc \ gbfile.cc parse.cc session.cc main.cc globals.cc \ + src/core/nvector.cc \ src/core/textstream.cc \ src/core/usasciicodec.cc \ + src/core/vector3d.cc \ src/core/xmlstreamwriter.cc versionAtLeast(QT_VERSION, 6.0): SUPPORT += src/core/codecdevice.cc @@ -184,8 +186,10 @@ HEADERS = \ src/core/datetime.h \ src/core/file.h \ src/core/logging.h \ + src/core/nvector.h \ src/core/textstream.h \ src/core/usasciicodec.h \ + src/core/vector3d.h \ src/core/xmlstreamwriter.h \ src/core/xmltag.h diff --git a/kml.cc b/kml.cc index a548e5b85..00ad14247 100644 --- a/kml.cc +++ b/kml.cc @@ -49,11 +49,13 @@ #include "src/core/datetime.h" // for DateTime #include "src/core/file.h" // for File #include "src/core/logging.h" // for Warning, Fatal +#include "src/core/nvector.h" // for NVector #include "src/core/xmlstreamwriter.h" // for XmlStreamWriter #include "src/core/xmltag.h" // for xml_findfirst, xml_tag, fs_xml, xml_attribute, xml_findnext #include "units.h" // for fmt_setunits, fmt_speed, fmt_altitude, fmt_distance, units_aviation, units_metric, units_nautical, units_statute #include "xmlgeneric.h" // for cb_cdata, cb_end, cb_start, xg_callback, xg_string, xg_cb_type, xml_deinit, xml_ignore_tags, xml_init, xml_read, xg_tag_mapping +using gpsbabel::NVector; // Icons provided and hosted by Google. Used with permission. #define ICON_BASE "https://earth.google.com/images/kml-icons/" @@ -1237,7 +1239,7 @@ QString KmlFormat::kml_geocache_get_logs(const Waypoint* wpt) const if (logpart) { gpsbabel::DateTime t = xml_parse_time(logpart->cdata); if (t.isValid()) { - r += t.date().toString(Qt::ISODate); + r = r + " " + t.date().toString(Qt::ISODate); } } @@ -1704,15 +1706,19 @@ void KmlFormat::kml_write_AbstractView() writer->writeEndElement(); // Close gx:TimeSpan tag } -// If our BB spans the antemeridian, flip sign on one. -// This doesn't make our BB optimal, but it at least prevents us from -// zooming to the wrong hemisphere. - if (kml_bounds.min_lon * kml_bounds.max_lon < 0) { - kml_bounds.min_lon = -kml_bounds.max_lon; - } + // Uses NVectors to compute the middle of the bounding box. + // This works even if the BB spans the antimeridian. + // This prevents us from zooming to the wrong hemisphere. + // Gade, K. (2010). A Non-singular Horizontal Position Representation, The Journal of Navigation, Volume 63, Issue 03, pp 395-417, July 2010. + // 5.3.6. Horizontal geographical mean, equation 17. + NVector bb_1(kml_bounds.min_lat, kml_bounds.min_lon); + NVector bb_2(kml_bounds.min_lat, kml_bounds.max_lon); + NVector bb_3(kml_bounds.max_lat, kml_bounds.min_lon); + NVector bb_4(kml_bounds.max_lat, kml_bounds.max_lon); + NVector bb_mid = (bb_1 + bb_2 + bb_3 + bb_4).normalize(); - writer->writeTextElement(QStringLiteral("longitude"), QString::number((kml_bounds.min_lon + kml_bounds.max_lon) / 2, 'f', precision)); - writer->writeTextElement(QStringLiteral("latitude"), QString::number((kml_bounds.min_lat + kml_bounds.max_lat) / 2, 'f', precision)); + writer->writeTextElement(QStringLiteral("longitude"), QString::number(bb_mid.longitude(), 'f', precision)); + writer->writeTextElement(QStringLiteral("latitude"), QString::number(bb_mid.latitude(), 'f', precision)); // It turns out the length of the diagonal of the bounding box gives us a // reasonable guess for setting the camera altitude. diff --git a/reference/LineStyles.kml b/reference/LineStyles.kml index 77c81ca37..8d908bc50 100644 --- a/reference/LineStyles.kml +++ b/reference/LineStyles.kml @@ -4,7 +4,7 @@ GPS device -122.275670 - 37.523278 + 37.523279 5908.813619 @@ -159,7 +159,7 @@ NOTE 1 - 12 test lines - see notes with GPX sample!! four line widths for solid red two line widths for dashed red seven colors for medium width Note that line names have nothing at all to do with line style attributes! These are named for convenient cross-reference, but keep in mind that "red med" is a line label and "Red Medium" is a style label. Waypoints are generated by Topo to exactly match the first track point. Apparently only GPX and KML output from GPSBabel retain track descriptions?? + 12 test lines - see notes with GPX sample!! four line widths for solid red two line widths for dashed red seven colors for medium width Note that line names have nothing at all to do with line style attributes!These are named for convenient cross-reference, but keep in mind that "red med" is a line label and "Red Medium" is a style label. Waypoints are generated by Topo to exactly match the first track point. Apparently only GPX and KML output from GPSBabel retain track descriptions?? #waypoint -122.277670,37.522060 diff --git a/reference/bounds-test.kml b/reference/bounds-test.kml index 65298dd15..8d140e0e4 100644 --- a/reference/bounds-test.kml +++ b/reference/bounds-test.kml @@ -4,7 +4,7 @@ GPS device -117.144015 - 36.438270 + 36.438594 87257.203848 diff --git a/reference/earth-expertgps-track.kml b/reference/earth-expertgps-track.kml index 207b78b3f..ed0f0ad29 100644 --- a/reference/earth-expertgps-track.kml +++ b/reference/earth-expertgps-track.kml @@ -8,7 +8,7 @@ 2002-05-25T19:05:57Z -81.356770 - 36.257086 + 36.698053 2979727.763965 diff --git a/reference/earth-expertgps.kml b/reference/earth-expertgps.kml index fd0b5347a..87ec4654f 100644 --- a/reference/earth-expertgps.kml +++ b/reference/earth-expertgps.kml @@ -8,7 +8,7 @@ 2002-05-25T19:05:57Z -81.356770 - 36.257086 + 36.698053 2979727.763965 diff --git a/reference/earth-gc.kml b/reference/earth-gc.kml index 94d1bfde0..31a9144b8 100644 --- a/reference/earth-gc.kml +++ b/reference/earth-gc.kml @@ -8,7 +8,7 @@ 2003-06-29T07:00:00Z -79.930833 - 41.027500 + 41.235699 2109328.437865 diff --git a/reference/track/bounds-test-track.kml b/reference/track/bounds-test-track.kml index f0ec3641c..ed0b8cfa1 100644 --- a/reference/track/bounds-test-track.kml +++ b/reference/track/bounds-test-track.kml @@ -4,7 +4,7 @@ GPS device -117.144015 - 36.438270 + 36.438594 87257.203848 diff --git a/reference/track/gtrnctr_power-kml.kml b/reference/track/gtrnctr_power-kml.kml index 10f614624..6667d6337 100644 --- a/reference/track/gtrnctr_power-kml.kml +++ b/reference/track/gtrnctr_power-kml.kml @@ -8,10 +8,10 @@ 2010-05-28T02:41:44Z -122.139608 - 37.382794 + 37.382814 19819.321245 - + - + - + - + - Heart Rate + Heart Rate - Cadence + Cadence - Power + Power diff --git a/reference/track/sim.csv b/reference/track/sim.csv new file mode 100644 index 000000000..8a00c4a00 --- /dev/null +++ b/reference/track/sim.csv @@ -0,0 +1,49 @@ +utc_d,utc_t,lat,lon +2021/11/10,11:00:00 AM,66.000103228632,-179.97904 +2021/11/10,11:00:01 AM,66.0001884601077,-179.97903 +2021/11/10,11:00:02 AM,66.0000230237331,-179.97947 +2021/11/10,11:00:03 AM,66.000045323685,-179.98008 +2021/11/10,11:00:04 AM,66.0001767900345,-179.98048 +2021/11/10,11:00:05 AM,66.0001092928285,-179.98067 +2021/11/10,11:00:06 AM,66.0001668298279,-179.98197 +2021/11/10,11:00:07 AM,66.0000748518391,-179.98320 +2021/11/10,11:00:08 AM,66.0001341555094,-179.98429 +2021/11/10,11:00:09 AM,66.0001213660596,-179.98519 +2021/11/10,11:00:10 AM,66.0001967593428,-179.98558 +2021/11/10,11:00:11 AM,66.0001580625671,-179.98671 +2021/11/10,11:00:12 AM,66.0001378472268,-179.98805 +2021/11/10,11:00:13 AM,66.0001709596168,-179.98906 +2021/11/10,11:00:14 AM,66.0000502908297,-179.98970 +2021/11/10,11:00:15 AM,66.000066383696,-179.99130 +2021/11/10,11:00:16 AM,66.0000462879889,-179.99183 +2021/11/10,11:00:17 AM,66.0001312399228,-179.99340 +2021/11/10,11:00:18 AM,66.0000883525882,-179.99384 +2021/11/10,11:00:19 AM,66.0001647357197,-179.99504 +2021/11/10,11:00:20 AM,66.0001959985441,-179.99596 +2021/11/10,11:00:21 AM,66.0001581122524,-179.99737 +2021/11/10,11:00:22 AM,66.0001028337378,-179.99798 +2021/11/10,11:00:23 AM,66.000105956278,-179.99903 +2021/11/10,11:00:24 AM,66.0000574941283,-179.99956 +2021/11/10,11:00:25 AM,66.0000416369344,179.99915 +2021/11/10,11:00:26 AM,66.0001504925119,179.99831 +2021/11/10,11:00:27 AM,66.0001927654035,179.99675 +2021/11/10,11:00:28 AM,66.0001282065146,179.99621 +2021/11/10,11:00:29 AM,66.0000911889429,179.99493 +2021/11/10,11:00:30 AM,66.0001909788552,179.99362 +2021/11/10,11:00:31 AM,66.0000568057737,179.99293 +2021/11/10,11:00:32 AM,66.0000978383165,179.99211 +2021/11/10,11:00:33 AM,66.0000403599515,179.99138 +2021/11/10,11:00:34 AM,66.0001144849062,179.99045 +2021/11/10,11:00:35 AM,66.0000769781374,179.98854 +2021/11/10,11:00:36 AM,66.0000133158984,179.98783 +2021/11/10,11:00:37 AM,66.0001758676388,179.98699 +2021/11/10,11:00:38 AM,66.0000207460354,179.98606 +2021/11/10,11:00:39 AM,66.0000931231469,179.98538 +2021/11/10,11:00:40 AM,66.0001991309548,179.98429 +2021/11/10,11:00:41 AM,66.0001841979519,179.98311 +2021/11/10,11:00:42 AM,66.0001217081583,179.98251 +2021/11/10,11:00:43 AM,66.0001017396611,179.98182 +2021/11/10,11:00:44 AM,66.0001469324212,179.98183 +2021/11/10,11:00:45 AM,66.0000706021879,179.98175 +2021/11/10,11:00:46 AM,66.0000732617344,179.98178 +2021/11/10,11:00:47 AM,66.0001673249007,179.98162 diff --git a/reference/track/sim.kml b/reference/track/sim.kml new file mode 100644 index 000000000..0b286c82c --- /dev/null +++ b/reference/track/sim.kml @@ -0,0 +1,1358 @@ + + + + GPS device + + + 2021-11-10T11:00:00Z + 2021-11-10T11:00:47Z + + 179.999795 + 66.000106 + 1300.000000 + + + + + + + + normal + #track_n + + + highlight + #track_h + + + + + + + + + normal + #waypoint_n + + + highlight + #waypoint_h + + + + + Tracks + + + + +Distance 1.2 mi +Min Speed 3.1 mph +Max Speed 193.7 mph +Avg Speed 88.4 mph +Start Time2021-11-10T11:00:00Z +End Time2021-11-10T11:00:47Z +]]> + + + 2021-11-10T11:00:00Z + 2021-11-10T11:00:47Z + + + Points + + -0 + + +Longitude: -179.979040 +Latitude: 66.000103 +Heading: 360.0 +Time: 2021-11-10T11:00:00Z + +]]> + + -179.979040 + 66.000103 + 66 + + + 2021-11-10T11:00:00Z + + #track + + -179.979040,66.000103 + + + + -1 + + +Longitude: -179.979030 +Latitude: 66.000188 +Speed: 21.2 mph +Heading: 2.7 +Time: 2021-11-10T11:00:01Z + +]]> + + -179.979030 + 66.000188 + 66 + + + 2021-11-10T11:00:01Z + + #track + + -179.979030,66.000188 + + + + -2 + + +Longitude: -179.979470 +Latitude: 66.000023 +Speed: 60.7 mph +Heading: 227.2 +Time: 2021-11-10T11:00:02Z + +]]> + + -179.979470 + 66.000023 + 66 + + + 2021-11-10T11:00:02Z + + #track + + -179.979470,66.000023 + + + + -3 + + +Longitude: -179.980080 +Latitude: 66.000045 +Speed: 62.0 mph +Heading: 275.1 +Time: 2021-11-10T11:00:03Z + +]]> + + -179.980080 + 66.000045 + 66 + + + 2021-11-10T11:00:03Z + + #track + + -179.980080,66.000045 + + + + -4 + + +Longitude: -179.980480 +Latitude: 66.000177 +Speed: 52.1 mph +Heading: 308.9 +Time: 2021-11-10T11:00:04Z + +]]> + + -179.980480 + 66.000177 + 66 + + + 2021-11-10T11:00:04Z + + #track + + -179.980480,66.000177 + + + + -5 + + +Longitude: -179.980670 +Latitude: 66.000109 +Speed: 25.6 mph +Heading: 228.9 +Time: 2021-11-10T11:00:05Z + +]]> + + -179.980670 + 66.000109 + 66 + + + 2021-11-10T11:00:05Z + + #track + + -179.980670,66.000109 + + + + -6 + + +Longitude: -179.981970 +Latitude: 66.000167 +Speed: 132.4 mph +Heading: 276.2 +Time: 2021-11-10T11:00:06Z + +]]> + + -179.981970 + 66.000167 + 66 + + + 2021-11-10T11:00:06Z + + #track + + -179.981970,66.000167 + + + + -7 + + +Longitude: -179.983200 +Latitude: 66.000075 +Speed: 126.7 mph +Heading: 259.6 +Time: 2021-11-10T11:00:07Z + +]]> + + -179.983200 + 66.000075 + 66 + + + 2021-11-10T11:00:07Z + + #track + + -179.983200,66.000075 + + + + -8 + + +Longitude: -179.984290 +Latitude: 66.000134 +Speed: 111.4 mph +Heading: 277.6 +Time: 2021-11-10T11:00:08Z + +]]> + + -179.984290 + 66.000134 + 66 + + + 2021-11-10T11:00:08Z + + #track + + -179.984290,66.000134 + + + + -9 + + +Longitude: -179.985190 +Latitude: 66.000121 +Speed: 91.2 mph +Heading: 268.0 +Time: 2021-11-10T11:00:09Z + +]]> + + -179.985190 + 66.000121 + 66 + + + 2021-11-10T11:00:09Z + + #track + + -179.985190,66.000121 + + + + -10 + + +Longitude: -179.985580 +Latitude: 66.000197 +Speed: 43.7 mph +Heading: 295.4 +Time: 2021-11-10T11:00:10Z + +]]> + + -179.985580 + 66.000197 + 66 + + + 2021-11-10T11:00:10Z + + #track + + -179.985580,66.000197 + + + + -11 + + +Longitude: -179.986710 +Latitude: 66.000158 +Speed: 114.9 mph +Heading: 265.2 +Time: 2021-11-10T11:00:11Z + +]]> + + -179.986710 + 66.000158 + 66 + + + 2021-11-10T11:00:11Z + + #track + + -179.986710,66.000158 + + + + -12 + + +Longitude: -179.988050 +Latitude: 66.000138 +Speed: 135.8 mph +Heading: 267.9 +Time: 2021-11-10T11:00:12Z + +]]> + + -179.988050 + 66.000138 + 66 + + + 2021-11-10T11:00:12Z + + #track + + -179.988050,66.000138 + + + + -13 + + +Longitude: -179.989060 +Latitude: 66.000171 +Speed: 102.6 mph +Heading: 274.6 +Time: 2021-11-10T11:00:13Z + +]]> + + -179.989060 + 66.000171 + 66 + + + 2021-11-10T11:00:13Z + + #track + + -179.989060,66.000171 + + + + -14 + + +Longitude: -179.989700 +Latitude: 66.000050 +Speed: 71.4 mph +Heading: 245.1 +Time: 2021-11-10T11:00:14Z + +]]> + + -179.989700 + 66.000050 + 66 + + + 2021-11-10T11:00:14Z + + #track + + -179.989700,66.000050 + + + + -15 + + +Longitude: -179.991300 +Latitude: 66.000066 +Speed: 162.1 mph +Heading: 271.4 +Time: 2021-11-10T11:00:15Z + +]]> + + -179.991300 + 66.000066 + 66 + + + 2021-11-10T11:00:15Z + + #track + + -179.991300,66.000066 + + + + -16 + + +Longitude: -179.991830 +Latitude: 66.000046 +Speed: 53.9 mph +Heading: 264.7 +Time: 2021-11-10T11:00:16Z + +]]> + + -179.991830 + 66.000046 + 66 + + + 2021-11-10T11:00:16Z + + #track + + -179.991830,66.000046 + + + + -17 + + +Longitude: -179.993400 +Latitude: 66.000131 +Speed: 160.4 mph +Heading: 277.6 +Time: 2021-11-10T11:00:17Z + +]]> + + -179.993400 + 66.000131 + 66 + + + 2021-11-10T11:00:17Z + + #track + + -179.993400,66.000131 + + + + -18 + + +Longitude: -179.993840 +Latitude: 66.000088 +Speed: 45.8 mph +Heading: 256.5 +Time: 2021-11-10T11:00:18Z + +]]> + + -179.993840 + 66.000088 + 66 + + + 2021-11-10T11:00:18Z + + #track + + -179.993840,66.000088 + + + + -19 + + +Longitude: -179.995040 +Latitude: 66.000165 +Speed: 123.0 mph +Heading: 278.9 +Time: 2021-11-10T11:00:19Z + +]]> + + -179.995040 + 66.000165 + 66 + + + 2021-11-10T11:00:19Z + + #track + + -179.995040,66.000165 + + + + -20 + + +Longitude: -179.995960 +Latitude: 66.000196 +Speed: 93.5 mph +Heading: 274.8 +Time: 2021-11-10T11:00:20Z + +]]> + + -179.995960 + 66.000196 + 66 + + + 2021-11-10T11:00:20Z + + #track + + -179.995960,66.000196 + + + + -21 + + +Longitude: -179.997370 +Latitude: 66.000158 +Speed: 143.1 mph +Heading: 266.2 +Time: 2021-11-10T11:00:21Z + +]]> + + -179.997370 + 66.000158 + 66 + + + 2021-11-10T11:00:21Z + + #track + + -179.997370,66.000158 + + + + -22 + + +Longitude: -179.997980 +Latitude: 66.000103 +Speed: 63.3 mph +Heading: 257.4 +Time: 2021-11-10T11:00:22Z + +]]> + + -179.997980 + 66.000103 + 66 + + + 2021-11-10T11:00:22Z + + #track + + -179.997980,66.000103 + + + + -23 + + +Longitude: -179.999030 +Latitude: 66.000106 +Speed: 106.3 mph +Heading: 270.4 +Time: 2021-11-10T11:00:23Z + +]]> + + -179.999030 + 66.000106 + 66 + + + 2021-11-10T11:00:23Z + + #track + + -179.999030,66.000106 + + + + -24 + + +Longitude: -179.999560 +Latitude: 66.000057 +Speed: 55.0 mph +Heading: 257.3 +Time: 2021-11-10T11:00:24Z + +]]> + + -179.999560 + 66.000057 + 66 + + + 2021-11-10T11:00:24Z + + #track + + -179.999560,66.000057 + + + + -25 + + +Longitude: 179.999150 +Latitude: 66.000042 +Speed: 130.7 mph +Heading: 268.3 +Time: 2021-11-10T11:00:25Z + +]]> + + 179.999150 + 66.000042 + 66 + + + 2021-11-10T11:00:25Z + + #track + + 179.999150,66.000042 + + + + -26 + + +Longitude: 179.998310 +Latitude: 66.000150 +Speed: 89.3 mph +Heading: 287.7 +Time: 2021-11-10T11:00:26Z + +]]> + + 179.998310 + 66.000150 + 66 + + + 2021-11-10T11:00:26Z + + #track + + 179.998310,66.000150 + + + + -27 + + +Longitude: 179.996750 +Latitude: 66.000193 +Speed: 158.4 mph +Heading: 273.8 +Time: 2021-11-10T11:00:27Z + +]]> + + 179.996750 + 66.000193 + 66 + + + 2021-11-10T11:00:27Z + + #track + + 179.996750,66.000193 + + + + -28 + + +Longitude: 179.996210 +Latitude: 66.000128 +Speed: 57.0 mph +Heading: 253.6 +Time: 2021-11-10T11:00:28Z + +]]> + + 179.996210 + 66.000128 + 66 + + + 2021-11-10T11:00:28Z + + #track + + 179.996210,66.000128 + + + + -29 + + +Longitude: 179.994930 +Latitude: 66.000091 +Speed: 130.0 mph +Heading: 265.9 +Time: 2021-11-10T11:00:29Z + +]]> + + 179.994930 + 66.000091 + 66 + + + 2021-11-10T11:00:29Z + + #track + + 179.994930,66.000091 + + + + -30 + + +Longitude: 179.993620 +Latitude: 66.000191 +Speed: 135.0 mph +Heading: 280.6 +Time: 2021-11-10T11:00:30Z + +]]> + + 179.993620 + 66.000191 + 66 + + + 2021-11-10T11:00:30Z + + #track + + 179.993620,66.000191 + + + + -31 + + +Longitude: 179.992930 +Latitude: 66.000057 +Speed: 77.5 mph +Heading: 244.4 +Time: 2021-11-10T11:00:31Z + +]]> + + 179.992930 + 66.000057 + 66 + + + 2021-11-10T11:00:31Z + + #track + + 179.992930,66.000057 + + + + -32 + + +Longitude: 179.992110 +Latitude: 66.000098 +Speed: 83.7 mph +Heading: 277.0 +Time: 2021-11-10T11:00:32Z + +]]> + + 179.992110 + 66.000098 + 66 + + + 2021-11-10T11:00:32Z + + #track + + 179.992110,66.000098 + + + + -33 + + +Longitude: 179.991380 +Latitude: 66.000040 +Speed: 75.3 mph +Heading: 259.0 +Time: 2021-11-10T11:00:33Z + +]]> + + 179.991380 + 66.000040 + 66 + + + 2021-11-10T11:00:33Z + + #track + + 179.991380,66.000040 + + + + -34 + + +Longitude: 179.990450 +Latitude: 66.000114 +Speed: 96.0 mph +Heading: 281.1 +Time: 2021-11-10T11:00:34Z + +]]> + + 179.990450 + 66.000114 + 66 + + + 2021-11-10T11:00:34Z + + #track + + 179.990450,66.000114 + + + + -35 + + +Longitude: 179.988540 +Latitude: 66.000077 +Speed: 193.7 mph +Heading: 267.2 +Time: 2021-11-10T11:00:35Z + +]]> + + 179.988540 + 66.000077 + 66 + + + 2021-11-10T11:00:35Z + + #track + + 179.988540,66.000077 + + + + -36 + + +Longitude: 179.987830 +Latitude: 66.000013 +Speed: 73.6 mph +Heading: 257.6 +Time: 2021-11-10T11:00:36Z + +]]> + + 179.987830 + 66.000013 + 66 + + + 2021-11-10T11:00:36Z + + #track + + 179.987830,66.000013 + + + + -37 + + +Longitude: 179.986990 +Latitude: 66.000176 +Speed: 94.2 mph +Heading: 295.4 +Time: 2021-11-10T11:00:37Z + +]]> + + 179.986990 + 66.000176 + 66 + + + 2021-11-10T11:00:37Z + + #track + + 179.986990,66.000176 + + + + -38 + + +Longitude: 179.986060 +Latitude: 66.000021 +Speed: 101.8 mph +Heading: 247.7 +Time: 2021-11-10T11:00:38Z + +]]> + + 179.986060 + 66.000021 + 66 + + + 2021-11-10T11:00:38Z + + #track + + 179.986060,66.000021 + + + + -39 + + +Longitude: 179.985380 +Latitude: 66.000093 +Speed: 71.2 mph +Heading: 284.7 +Time: 2021-11-10T11:00:39Z + +]]> + + 179.985380 + 66.000093 + 66 + + + 2021-11-10T11:00:39Z + + #track + + 179.985380,66.000093 + + + + -40 + + +Longitude: 179.984290 +Latitude: 66.000199 +Speed: 113.5 mph +Heading: 283.4 +Time: 2021-11-10T11:00:40Z + +]]> + + 179.984290 + 66.000199 + 66 + + + 2021-11-10T11:00:40Z + + #track + + 179.984290,66.000199 + + + + -41 + + +Longitude: 179.983110 +Latitude: 66.000184 +Speed: 119.6 mph +Heading: 268.2 +Time: 2021-11-10T11:00:41Z + +]]> + + 179.983110 + 66.000184 + 66 + + + 2021-11-10T11:00:41Z + + #track + + 179.983110,66.000184 + + + + -42 + + +Longitude: 179.982510 +Latitude: 66.000122 +Speed: 62.7 mph +Heading: 255.6 +Time: 2021-11-10T11:00:42Z + +]]> + + 179.982510 + 66.000122 + 66 + + + 2021-11-10T11:00:42Z + + #track + + 179.982510,66.000122 + + + + -43 + + +Longitude: 179.981820 +Latitude: 66.000102 +Speed: 70.1 mph +Heading: 265.9 +Time: 2021-11-10T11:00:43Z + +]]> + + 179.981820 + 66.000102 + 66 + + + 2021-11-10T11:00:43Z + + #track + + 179.981820,66.000102 + + + + -44 + + +Longitude: 179.981830 +Latitude: 66.000147 +Speed: 11.3 mph +Heading: 5.1 +Time: 2021-11-10T11:00:44Z + +]]> + + 179.981830 + 66.000147 + 66 + + + 2021-11-10T11:00:44Z + + #track + + 179.981830,66.000147 + + + + -45 + + +Longitude: 179.981750 +Latitude: 66.000071 +Speed: 20.7 mph +Heading: 203.1 +Time: 2021-11-10T11:00:45Z + +]]> + + 179.981750 + 66.000071 + 66 + + + 2021-11-10T11:00:45Z + + #track + + 179.981750,66.000071 + + + + -46 + + +Longitude: 179.981780 +Latitude: 66.000073 +Speed: 3.1 mph +Heading: 77.7 +Time: 2021-11-10T11:00:46Z + +]]> + + 179.981780 + 66.000073 + 66 + + + 2021-11-10T11:00:46Z + + #track + + 179.981780,66.000073 + + + + -47 + + +Longitude: 179.981620 +Latitude: 66.000167 +Speed: 28.5 mph +Heading: 325.3 +Time: 2021-11-10T11:00:47Z + +]]> + + 179.981620 + 66.000167 + 66 + + + 2021-11-10T11:00:47Z + + #track + + 179.981620,66.000167 + + + + + Path + #lineStyle + + 1 + +-179.979040,66.000103 +-179.979030,66.000188 +-179.979470,66.000023 +-179.980080,66.000045 +-179.980480,66.000177 +-179.980670,66.000109 +-179.981970,66.000167 +-179.983200,66.000075 +-179.984290,66.000134 +-179.985190,66.000121 +-179.985580,66.000197 +-179.986710,66.000158 +-179.988050,66.000138 +-179.989060,66.000171 +-179.989700,66.000050 +-179.991300,66.000066 +-179.991830,66.000046 +-179.993400,66.000131 +-179.993840,66.000088 +-179.995040,66.000165 +-179.995960,66.000196 +-179.997370,66.000158 +-179.997980,66.000103 +-179.999030,66.000106 +-179.999560,66.000057 +179.999150,66.000042 +179.998310,66.000150 +179.996750,66.000193 +179.996210,66.000128 +179.994930,66.000091 +179.993620,66.000191 +179.992930,66.000057 +179.992110,66.000098 +179.991380,66.000040 +179.990450,66.000114 +179.988540,66.000077 +179.987830,66.000013 +179.986990,66.000176 +179.986060,66.000021 +179.985380,66.000093 +179.984290,66.000199 +179.983110,66.000184 +179.982510,66.000122 +179.981820,66.000102 +179.981830,66.000147 +179.981750,66.000071 +179.981780,66.000073 +179.981620,66.000167 + + + + + + + diff --git a/src/core/nvector.cc b/src/core/nvector.cc new file mode 100644 index 000000000..a35efc691 --- /dev/null +++ b/src/core/nvector.cc @@ -0,0 +1,450 @@ +/* + Copyright (C) 2018, 2021 Robert Lipe, gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ + +// https://en.wikipedia.org/wiki/N-vector +// http://www.navlab.net/Publications/A_Nonsingular_Horizontal_Position_Representation.pdf + +#include +#include +#include +#include +#include + +#include "nvector.h" +#include "vector3d.h" + +namespace gpsbabel +{ + +LatLon::LatLon(double latitude, double longitude) +{ + lat = latitude; + lon = longitude; +} + +NVector::NVector(double latitude_degrees, double longitude_degrees) +{ + // This implements equation 3. + + // The coordinate system choice matches + // A_Nonsingular_Horizontal_Position_Representation.pdf + // The E frame: + // from the earths center + // x points to North pole + // y points towards latitude 0, longitude +90 (east) + // z points to latitude 0, longitude +180, the antimeridian + double latitude_radians = latitude_degrees * kRadiansPerDegree; + double longitude_radians = longitude_degrees * kRadiansPerDegree; + x_ = sin(latitude_radians); + y_ = sin(longitude_radians)*cos(latitude_radians); + z_ = -cos(longitude_radians)*cos(latitude_radians); +} + +NVector::NVector(const Vector3D& v) +{ + x_ = v.getx(); + y_ = v.gety(); + z_ = v.getz(); +} + +std::pair PVector::toNVectorAndHeight() const +{ + // This implements equation 23. + constexpr double a = WGS84_SEMI_MAJOR_AXIS_METERS; + constexpr double a2 = a * a; + constexpr double e2 = WGS84_ECCENTRICITY_SQUARED; + constexpr double e4 = e2 * e2; + + double px2 = x_ * x_; + double py2 = y_ * y_; + double pz2 = z_ * z_; + + double q = ((1.0-e2)/(a2)) * px2; + double p = (py2 + pz2) / (a2); + double r = (p+q-e4) / 6.0; + double s = (e4*p*q) / (4.0*r*r*r); + double t = cbrt(1.0 + s + sqrt(s*(2.0+s))); + double u = r * (1.0 + t + (1.0/t)); + double v = sqrt(u*u + e4*q); + double w = e2 * ((u+v-q)/(2.0*v)); + double k = sqrt(u+v+w*w) - w; + double d = k*sqrt(py2 + pz2) / (k+e2); + double sf = 1.0 / (sqrt(d*d + px2)); + double sf2 = k / (k+e2); + + double height = ((k+e2-1.0)/k) * sqrt(d*d+px2); + double nx = sf * x_; + double ny = sf * sf2 * y_; + double nz = sf * sf2 * z_; + + return {Vector3D(nx, ny, nz), height}; +} + +double NVector::latitude() const +{ + // This implements equation 6. + double latitude_radians = atan2(x_, sqrt(y_*y_ + z_*z_)); + double latitude_degrees = latitude_radians * kDegreesPerRadian; + return latitude_degrees; +} + +double NVector::longitude() const +{ + // This implements equation 5. + double longitude_radians = atan2(y_, -z_); + double longitude_degrees = longitude_radians * kDegreesPerRadian; + return longitude_degrees; +} + +// great circle distance in radians +double NVector::distance_radians(const NVector& n_EA_E, const NVector& n_EB_E) +{ + // This implements equation 16 using arctan for numerical accuracy. + double result = atan2(crossProduct(n_EA_E, n_EB_E).norm(), + dotProduct(n_EA_E, n_EB_E)); + return result; +} + +// great circle distance in meters +double NVector::distance(const NVector& n_EA_E, const NVector& n_EB_E) +{ + double result = distance_radians(n_EA_E, n_EB_E) * MEAN_EARTH_RADIUS_METERS; + return result; +} + +double NVector::distance(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees) +{ + NVector n_EA_E(latitude_a_degrees, longitude_a_degrees); + NVector n_EB_E(latitude_b_degrees, longitude_b_degrees); + double result = distance(n_EA_E, n_EB_E); + return result; +} + +double NVector::azimuth_radians(const NVector& n_EA_E, const NVector& n_EB_E, double height_a, double height_b) +{ + PVector p_EA_E(n_EA_E, height_a); + PVector p_EB_E(n_EB_E, height_b); + Vector3D p_AB_E = p_EB_E - p_EA_E; + + // equation 9 (normalized) + Vector3D kaeast = crossProduct(Vector3D(1.0, 0.0, 0.0), n_EA_E).normalize(); + // equation 10 (normalized, n_EA_E and kaeast are perpendicular unit vectors) + Vector3D kanorth = crossProduct(n_EA_E, kaeast); + + // equation 11 + // Ren = [kanorth(normalized) kaeast(normalized) -n_EA_E]; + // and a rotation from the E frame to the N frame (North, East, Down). + // P_AB_N = R_EN' * p_AB_E + double P_AB_N_x = kanorth*p_AB_E; + double P_AB_N_y = kaeast*p_AB_E; + // double P_AB_N_z = (-n_EA_E)*p_AB_E; + + double azimuth = atan2(P_AB_N_y, P_AB_N_x); + return azimuth; +} + +double NVector::azimuth(const NVector& n_EA_E, const NVector& n_EB_E, double height_a, double height_b) +{ + double azimuth = azimuth_radians(n_EA_E, n_EB_E, height_a, height_b); + double azimuth_degrees = azimuth * kDegreesPerRadian; + return azimuth_degrees; +} + +// returns values in the range [0.0,360) +double NVector::azimuth_true_degrees(const NVector& n_EA_E, const NVector& n_EB_E, double height_a, double height_b) +{ + double result = azimuth(n_EA_E, n_EB_E, height_a, height_b); + if (result < 0.0) { + result += 360.0; + } + if (result >= 360.0) { + result = 0.0; + } + return result; +} + +double NVector::azimuth(double latitude_a_degrees, double longitude_a_degrees, + double latitude_b_degrees, double longitude_b_degrees, + double height_a, double height_b) +{ + NVector n_EA_E(latitude_a_degrees, longitude_a_degrees); + NVector n_EB_E(latitude_b_degrees, longitude_b_degrees); + return azimuth(n_EA_E, n_EB_E, height_a, height_b); +} + +// returns values in the range [0.0,360) +double NVector::azimuth_true_degrees(double latitude_a_degrees, double longitude_a_degrees, + double latitude_b_degrees, double longitude_b_degrees, + double height_a, double height_b) +{ + double result = azimuth(latitude_a_degrees, longitude_a_degrees, + latitude_b_degrees, longitude_b_degrees, + height_a, height_b); + if (result < 0.0) { + result += 360.0; + } + if (result >= 360.0) { + result = 0.0; + } + return result; +} + +#if 0 +// This interpolation is nonlinear! +NVector NVector::linepart(const NVector& n_EA_E, const NVector& n_EB_E, double fraction) +{ + Vector3D n_ER_E = (n_EA_E + (n_EB_E - n_EA_E)*fraction).normalize(); + return n_ER_E; +} +#elif 0 +NVector NVector::linepart(const NVector& n_EA_E, const NVector& n_EB_E, double fraction) +{ + // see example 8. + double sab_rad = distance_radians(n_EA_E, n_EB_E); + double sar_rad = fraction*sab_rad; + double az_rad = azimuth_radians(n_EA_E, n_EB_E); + + // equation 9 (normalized) + Vector3D kaeast = crossProduct(Vector3D(1.0, 0.0, 0.0), n_EA_E).normalize(); + // equation 10 (normalized, n_EA_E and kaeast are perpendicular unit vectors) + Vector3D kanorth = crossProduct(n_EA_E, kaeast); + + Vector3D d_EA_E = kanorth*cos(az_rad) + kaeast*sin(az_rad); + Vector3D n_ER_E = n_EA_E*cos(sar_rad) + d_EA_E*sin(sar_rad); + return n_ER_E; +} +#else +NVector NVector::linepart(const NVector& n_EA_E, const NVector& n_EB_E, double fraction) +{ + double dp_a_b = dotProduct(n_EA_E, n_EB_E); + if (dp_a_b >= (1.0-8.0*DBL_EPSILON)) { + // The points are so close we will have trouble constructing a basis. + // Since they are so close the non-linearities in direct n vector + // interpolation are negligible. + Vector3D result = (n_EA_E + (n_EB_E-n_EA_E)*fraction).normalize(); + return result; + } + if (dp_a_b <= (-1.0+8.0*DBL_EPSILON)) { + // The points are so close to be exactly opposite each other that + // there isn't a unique great circle between them. + // Unless fraction is 1.0 or 0.0 there isn't a unique answer. + Vector3D result(nan(""), nan(""), nan("")); + return result; + } + // Form an orthonormal basis with one component perpendicular to the great + // circle containing A and B, and one component being A. + // Call this the W frame. + // The columns of the rotation matrix from E to W are w1, w2 and w3. + Vector3D w1 = n_EA_E; + Vector3D w2 = crossProduct(n_EB_E, n_EA_E).normalize(); + Vector3D w3 = crossProduct(n_EA_E,w2); + // Rotate A and B to the W frame. + // Vector3D n_EA_W = Vector3D(w1*n_EA_E, w2*n_EA_E, w3*n_EA_E); + Vector3D n_EB_W = Vector3D(w1*n_EB_E, w2*n_EB_E, w3*n_EB_E); + // By construction n_EA_W.y is (1, 0, 0), + // n_EB_W.y is zero, + // and both n_EA_W and n_EB_W are unit vectors. + // The information is all in the angle between them, + // which is all in n_EB_W.z and n_EB_W.x. + double theta = atan2(n_EB_W.getz(), n_EB_W.getx()); + // We define the interpolated point as the point on the great circle + // between A and B whose great circle distance from A is the given fraction + // of the great circle distance between A and B. For a spheroid the + // distance is proportional to the angle between the vectors. + // The interpolated point is thus: + Vector3D n_EX_W = Vector3D(cos(fraction*theta), 0.0, sin(fraction*theta)); + // Translate the interpolated point back to the E frame. + // We need to invert the matrix composed of the column vectors w1, w2, w3. + // Since this matrix is orthogonal it's inverse equals it's transpose. + Vector3D wt1 = Vector3D(w1.getx(), w2.getx(), w3.getx()); + Vector3D wt2 = Vector3D(w1.gety(), w2.gety(), w3.gety()); + Vector3D wt3 = Vector3D(w1.getz(), w2.getz(), w3.getz()); + Vector3D n_EX_E = Vector3D(wt1*n_EX_W, wt2*n_EX_W, wt3*n_EX_W); + return n_EX_E; +} +#endif + +LatLon NVector::linepart(double latitude_a_degrees, double longitude_a_degrees, + double latitude_b_degrees, double longitude_b_degrees, + double fraction) +{ + NVector n_EA_E(latitude_a_degrees, longitude_a_degrees); + NVector n_EB_E(latitude_b_degrees, longitude_b_degrees); + NVector n_ER_E = linepart(n_EA_E, n_EB_E, fraction); + LatLon result(n_ER_E.latitude(), n_ER_E.longitude()); + return result; +} + +// Find the point of closest approach to Y on the great circle arc AB. +// The great circle arc AB is the shortest of the two possibilities. +#if 0 +// This implementation works and passes regression. +// However, it seems harder to understand than the next implementation. +NVector NVector::crossTrackProjection(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EY_E) +{ + // Compute the normal to the great circle defined by A and B: + Vector3D a_cross_b = crossProduct(n_EA_E, n_EB_E); + // Because a_cross_b is normal to the plane defined by n_EA_E and n_EB_E, + // any great circle defined by the point defined by a_cross_b and + // any other point, e.g. Y, + // will cross the great circle including the points A and B perpendicularly. + // Thus, if we find the intersection of the two great circles we will + // have found the projection of Y onto the great circle defined + // by A and B. + Vector3D x = a_cross_b; + // Compute the normal to the great circle defined by Y and X: + Vector3D x_cross_y = crossProduct(x, n_EY_E); + // A candidate projection from X onto the great circle defined by A and B is + // c_candidate, the other possibility is -c_candidate. + Vector3D c_candidate = crossProduct(a_cross_b, x_cross_y).normalize(); + // pick the candidate closest to Y. + Vector3D c = dotProduct(c_candidate, n_EY_E) >= 0.0 ? c_candidate : -c_candidate; + // find the midpoint between A and B. + Vector3D m = (n_EA_E + n_EB_E).normalize(); + // Now we have a point C on the great circle defined by A and B, + // and the midpoint M of this arc. + // If C is on the arc defined by A and B then the point of closest + // approach to Y is C. But if C is not on the arc, then the point + // of closest approach is either A or B, whichever is close to C. + // Note that A, B, C, M all are all on the great circle defined by A and B, so + // the dot product of any of these two unit vectors monotonically decreases + // the farther apart they are on the great circle. + Vector3D result; + if (dotProduct(n_EA_E, m) < dotProduct(c, m)) { + result = c; + } else if (dotProduct(n_EA_E, c) < dotProduct(n_EB_E, c)) { + result = n_EB_E; + } else { + result = n_EA_E; + } + return result; +} +#else +NVector NVector::crossTrackProjection(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EY_E) +{ + // Form an orthonormal basis with one component perpendicular to the great + // circle containing A and B, and one component being A. + // Call this the W frame. + // The columns of the rotation matrix from E to W are w1, w2 and w3. + Vector3D w1 = n_EA_E; + Vector3D w2 = crossProduct(n_EB_E, n_EA_E).normalize(); + Vector3D w3 = crossProduct(n_EA_E,w2); + // Rotate Y to the W frame. + Vector3D n_EY_W = Vector3D(w1*n_EY_E, w2*n_EY_E, w3*n_EY_E); + // By construction n_EA_W.y is (1, 0, 0), + // n_EB_W.y is zero, + // and both n_EA_W and n_EB_W are unit vectors. + // The projection of Y onto the great circle defined by A and B + // is just the n_EY_W with the y component set to zero. + Vector3D n_EC_W = Vector3D(n_EY_W.getx(), 0.0, n_EY_W.getz()).normalize(); + // Translate the projected point back to the E frame. + // We need to invert the matrix composed of the column vectors w1, w2, w3. + // Since this matrix is orthogonal it's inverse equals it's transpose. + Vector3D wt1 = Vector3D(w1.getx(), w2.getx(), w3.getx()); + Vector3D wt2 = Vector3D(w1.gety(), w2.gety(), w3.gety()); + Vector3D wt3 = Vector3D(w1.getz(), w2.getz(), w3.getz()); + Vector3D n_EC_E = Vector3D(wt1*n_EC_W, wt2*n_EC_W, wt3*n_EC_W); + // find the midpoint between A and B. + Vector3D n_EM_E = (n_EA_E + n_EB_E).normalize(); + // Now we have a point C on the great circle defined by A and B, + // and the midpoint M of this arc. + // If C is on the arc defined by A and B then the point of closest + // approach to Y is C. But if C is not on the arc, then the point + // of closest approach is either A or B, whichever is close to C. + // Note that A, B, C, M all are all on the great circle defined by A and B, so + // the dot product of any of these two unit vectors monotonically decreases + // the farther apart they are on the great circle. + Vector3D result; + if (dotProduct(n_EA_E, n_EM_E) < dotProduct(n_EC_E, n_EM_E)) { + result = n_EC_E; + } else if (dotProduct(n_EA_E, n_EC_E) < dotProduct(n_EB_E, n_EC_E)) { + result = n_EB_E; + } else { + result = n_EA_E; + } + return result; +} +#endif + +LatLon NVector::crossTrackProjection(double latitude_a_degrees, double longitude_a_degrees, + double latitude_b_degrees, double longitude_b_degrees, + double latitude_x_degrees, double longitude_x_degrees) +{ + NVector n_EA_E(latitude_a_degrees, longitude_a_degrees); + NVector n_EB_E(latitude_b_degrees, longitude_b_degrees); + NVector n_EX_E(latitude_x_degrees, longitude_x_degrees); + NVector n_EC_E = crossTrackProjection(n_EA_E, n_EB_E, n_EX_E); + LatLon result(NVector(n_EC_E).latitude(), NVector(n_EC_E).longitude()); + return result; +} + +// Find the minimum distance to point X when traveling from A to B. +#if 0 +// This doesn't work if we are more than 90 degrees away +double NVector::crossTrackDistance(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EX_E) +{ + Vector3D c_E = crossProduct(n_EA_E, n_EB_E).normalize(); + double result = fabs((atan2(crossProduct(c_E, n_EX_E).norm(), + dotProduct(c_E, n_EX_E)) - M_PI/2.0)) * MEAN_EARTH_RADIUS_METERS; + return result; +} +#else +double NVector::crossTrackDistance(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EX_E) +{ + NVector n_EP_E = crossTrackProjection(n_EA_E, n_EB_E, n_EX_E); + double result = distance(n_EP_E, n_EX_E); + return result; +} +#endif + +double NVector::crossTrackDistance(double latitude_a_degrees, double longitude_a_degrees, + double latitude_b_degrees, double longitude_b_degrees, + double latitude_x_degrees, double longitude_x_degrees) +{ + NVector n_EA_E(latitude_a_degrees, longitude_a_degrees); + NVector n_EB_E(latitude_b_degrees, longitude_b_degrees); + NVector n_EX_E(latitude_x_degrees, longitude_x_degrees); + double result = crossTrackDistance(n_EA_E, n_EB_E, n_EX_E); + return result; +} + +PVector::PVector(const NVector& n_EA_E, double h=0.0) +{ + // This implements equation 22. + + // a semi-major axis, b semi-minor axis, f flattening + // a/b = 1/(1-f) + // aspect ratio = b/a + constexpr double b = WGS84_SEMI_MINOR_AXIS_METERS; + constexpr double asq_over_bsq = 1.0 / (WGS84_ASPECT_RATIO * WGS84_ASPECT_RATIO); + + double denom = sqrt(n_EA_E.getx()*n_EA_E.getx() + n_EA_E.gety()*n_EA_E.gety()*asq_over_bsq + n_EA_E.getz()*n_EA_E.getz()*asq_over_bsq); + x_ = (b/denom)*n_EA_E.getx() + h*n_EA_E.getx(); + y_ = (b/denom)*asq_over_bsq*n_EA_E.gety() + h*n_EA_E.gety(); + z_ = (b/denom)*asq_over_bsq*n_EA_E.getz() + h*n_EA_E.getz(); +} + +PVector::PVector(const Vector3D& v) +{ + x_ = v.getx(); + y_ = v.gety(); + z_ = v.getz(); +} + +} // namespace gpsbabel diff --git a/src/core/nvector.h b/src/core/nvector.h new file mode 100644 index 000000000..04ce9d211 --- /dev/null +++ b/src/core/nvector.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2018, 2021 Robert Lipe, gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ + +#ifndef NVECTOR_H +#define NVECTOR_H + +#include "defs.h" +#include "vector3d.h" + +namespace gpsbabel +{ + +#define COMPARE_BEARING_TO_GRTCIRC +#ifdef COMPARE_BEARING_TO_GRTCIRC +constexpr double MEAN_EARTH_RADIUS_METERS = 6378137.0; +#else +constexpr double MEAN_EARTH_RADIUS_METERS = 6371000.0; +#endif +constexpr double WGS84_SEMI_MAJOR_AXIS_METERS = 6378137.0; // a +#ifdef COMPARE_BEARING_TO_GRTCIRC +constexpr double WGS84_FLATTENING = 0.0; +#else +constexpr double WGS84_FLATTENING = 1.0/298.257223563; // (a-b)/a +#endif +constexpr double WGS84_ASPECT_RATIO = 1.0 - WGS84_FLATTENING; // b/a +constexpr double WGS84_SEMI_MINOR_AXIS_METERS = WGS84_SEMI_MAJOR_AXIS_METERS * WGS84_ASPECT_RATIO; // b +constexpr double WGS84_ECCENTRICITY_SQUARED = 1.0 - (WGS84_ASPECT_RATIO * WGS84_ASPECT_RATIO); + +constexpr double kRadiansPerDegree = M_PI/180.0; +constexpr double kDegreesPerRadian = 1.0/kRadiansPerDegree; + +class PVector; + +class LatLon +{ +public: + LatLon(double latitude, double longitude); + + double lat; + double lon; +}; + +class NVector : public Vector3D +{ + +public: + NVector() = default; + NVector(double latitude_degrees, double longitude_degrees); + NVector(const Vector3D& v); + + [[nodiscard]] double latitude() const; + [[nodiscard]] double longitude() const; + + static double distance_radians(const NVector& n_EA_E, const NVector& n_EB_E); + static double distance(const NVector& n_EA_E, const NVector& n_EB_E); + static double distance(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees); + static double azimuth_radians(const NVector& n_EA_E, const NVector& n_EB_E, double height_a = 0.0, double height_b = 0.0); + static double azimuth(const NVector& n_EA_E, const NVector& n_EB_E, double height_a = 0.0, double height_b = 0.0); + static double azimuth(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees, double height_a = 0.0, double height_b = 0.0); + static double azimuth_true_degrees(const NVector& n_EA_E, const NVector& n_EB_E, double height_a = 0.0, double height_b = 0.0); + static double azimuth_true_degrees(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees, double height_a = 0.0, double height_b = 0.0); + static NVector linepart(const NVector& n_EA_E, const NVector& n_EB_E, double fraction); + static LatLon linepart(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees, double fraction); + static NVector crossTrackProjection(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EY_E); + static LatLon crossTrackProjection(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees, double latitude_x_degrees, double longitude_x_degrees); + static double crossTrackDistance(const NVector& n_EA_E, const NVector& n_EB_E, const NVector& n_EX_E); + static double crossTrackDistance(double latitude_a_degrees, double longitude_a_degrees, double latitude_b_degrees, double longitude_b_degrees, double latitude_x_degrees, double longitude_x_degrees); +}; + +class PVector : public Vector3D +{ +public: + PVector() = default; + PVector(const NVector& n_EA_E, double h); + PVector(const Vector3D& v); + + [[nodiscard]] std::pair toNVectorAndHeight() const; +}; + +} // namespace gpsbabel +#endif // NVECTOR_H diff --git a/src/core/vector3d.cc b/src/core/vector3d.cc new file mode 100644 index 000000000..a59b44a72 --- /dev/null +++ b/src/core/vector3d.cc @@ -0,0 +1,156 @@ +/* + Copyright (C) 2018, 2021 Robert Lipe, gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ + +// https://en.wikipedia.org/wiki/N-vector +// http://www.navlab.net/Publications/A_Nonsingular_Horizontal_Position_Representation.pdf + +#include +#include +#include "vector3d.h" + +namespace gpsbabel +{ + +Vector3D::Vector3D(double xi, double yi, double zi) +{ + x_ = xi; + y_ = yi; + z_ = zi; +} + +double Vector3D::norm() const +{ + double norm = sqrt(x_*x_ + y_*y_ + z_*z_); + return norm; +} + +Vector3D& Vector3D::normalize() +{ + *this = *this/this->norm(); + return *this; +} + +Vector3D& Vector3D::operator+=(const Vector3D& rhs) +{ + x_ += rhs.x_; + y_ += rhs.y_; + z_ += rhs.z_; + return *this; +} + +Vector3D& Vector3D::operator-=(const Vector3D& rhs) +{ + x_ -= rhs.x_; + y_ -= rhs.y_; + z_ -= rhs.z_; + return *this; +} + +Vector3D& Vector3D::operator*=(double rhs) +{ + x_ *= rhs; + y_ *= rhs; + z_ *= rhs; + return *this; +} + +Vector3D& Vector3D::operator/=(double rhs) +{ + x_ /= rhs; + y_ /= rhs; + z_ /= rhs; + return *this; +} + +Vector3D Vector3D::operator+ (const Vector3D& rhs) const +{ + Vector3D result = *this; + result += rhs; + return result; +} + +Vector3D Vector3D::operator- (const Vector3D& rhs) const +{ + Vector3D result = *this; + result -= rhs; + return result; +} + +Vector3D Vector3D::operator* (double rhs) const +{ + Vector3D result = *this; + result *= rhs; + return result; +} + +Vector3D Vector3D::operator/ (double rhs) const +{ + Vector3D result = *this; + result /= rhs; + return result; +} + +Vector3D Vector3D::operator- () const +{ + Vector3D result(-x_,-y_,-z_); + return result; +} + +double Vector3D::operator* (const Vector3D& b) const +{ + double result = x_*b.x_ + y_*b.y_ + z_*b.z_; + return result; +} + +double Vector3D::dotProduct(const Vector3D& a, const Vector3D& b) +{ + double dotproduct = a.x_*b.x_ + a.y_*b.y_ + a.z_*b.z_; + return dotproduct; +} + +Vector3D Vector3D::crossProduct(const Vector3D& b, const Vector3D& c) +{ + // a = b x c + Vector3D a; + a.x_ = b.y_*c.z_ - b.z_*c.y_; + a.y_ = b.z_*c.x_ - b.x_*c.z_; + a.z_ = b.x_*c.y_ - b.y_*c.x_; + return a; +} + +Vector3D operator* (double lhs, const Vector3D& rhs) +{ + Vector3D result = rhs*lhs; + return result; +} + +std::ostream& operator<< (std::ostream& os, const Vector3D& v) +{ + os << '(' << v.getx() << ", " << v.gety() << ", " << v.getz() << ')'; + return os; +} + +QDebug operator<< (QDebug debug, const Vector3D& v) +{ + QDebugStateSaver saver(debug); + debug.nospace() << '(' << v.getx() << ", " << v.gety() << ", " << v.getz() << ')'; + return debug; +} + +} // namespace gpsbabel diff --git a/src/core/vector3d.h b/src/core/vector3d.h new file mode 100644 index 000000000..bd9ac5474 --- /dev/null +++ b/src/core/vector3d.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2018, 2021 Robert Lipe, gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + */ + +#ifndef VECTOR3D_H +#define VECTOR3D_H + +#include + +#include + +namespace gpsbabel +{ + +class Vector3D +{ +public: + Vector3D() = default; + Vector3D(double xi, double yi, double zi); + + [[nodiscard]] double norm() const; + [[nodiscard]] double getx() const {return x_;} + [[nodiscard]] double gety() const {return y_;} + [[nodiscard]] double getz() const {return z_;} + Vector3D& normalize(); + + Vector3D& operator+=(const Vector3D& rhs); + Vector3D& operator-=(const Vector3D& rhs); + Vector3D& operator*=(double rhs); + Vector3D& operator/=(double rhs); + Vector3D operator+ (const Vector3D& rhs) const; + Vector3D operator- (const Vector3D& rhs) const; + Vector3D operator* (double rhs) const; + Vector3D operator/ (double rhs) const; + Vector3D operator- () const; + double operator* (const Vector3D& b) const; + + static Vector3D crossProduct(const Vector3D& b, const Vector3D& c); + static double dotProduct(const Vector3D& a, const Vector3D& b); + +protected: + double x_{}; + double y_{}; + double z_{}; +}; +Vector3D operator* (double lhs, const Vector3D& rhs); +std::ostream& operator<< (std::ostream& os, const Vector3D& v); +QDebug operator<<(QDebug debug, const Vector3D& v); + +} // namespace gpsbabel +#endif // VECTOR3D_H diff --git a/testo.d/kml.test b/testo.d/kml.test index 2863b86bd..2b3664e74 100644 --- a/testo.d/kml.test +++ b/testo.d/kml.test @@ -69,6 +69,10 @@ compare ${REFERENCE}/track/Placemark-Track-1~kml.gpx ${TMPDIR}/Placemark-Track-1 gpsbabel -T -i random,points=20,seed=33,nodelay -f dummy -o kml,track -F ${TMPDIR}/realtime.kml compare ${REFERENCE}/realtime.kml ${TMPDIR}/realtime.kml +# kml bounding box +gpsbabel -t -i unicsv -f ${REFERENCE}/track/sim.csv -o kml -F ${TMPDIR}/sim.kml +compare ${REFERENCE}/track/sim.kml ${TMPDIR}/sim.kml + if [ "${RUNNINGVALGRIND}" != "0" ]; then set -e if which xmllint > /dev/null;