Rose
MapProjection.h
1 
8 #pragma once
9 
10 #include <filesystem>
11 #include "Visual.h"
12 #include "WebCache.h"
13 #include "GraphicsModel.h"
14 #include "ImageStore.h"
15 #include "Texture.h"
16 #include "TimerTick.h"
17 #include "SatelliteModel.h"
18 #include "Surface.h"
19 #include "AntiAliasedDrawing.h"
20 
21 // https://earthobservatory.nasa.gov/features/NightLights/page3.php
22 // https://visibleearth.nasa.gov/images/57752/blue-marble-land-surface-shallow-water-and-shaded-topography
23 // https://visibleearth.nasa.gov/images/57752/blue-marble-land-surface-shallow-water-and-shaded-topography/57754l
24 // https://commons.wikimedia.org/wiki/File:Large_World_Topo_Map_2.png
25 // https://commons.wikimedia.org/wiki/File:The_earth_at_night.jpg
26 // https://commons.wikimedia.org/wiki/File:The_earth_at_night_(2).jpg
27 // https://edwilliams.org/avform147.htm
28 
29 namespace rose {
30 
31 #if SDL_VERSION_ATLEAST(2, 0, 10)
32  using MapPositionType = float;
33 #else
34  using MapPositionType = int;
35 #endif
36 
37  namespace set {
38  static constexpr std::string_view ChronoMapProjection{"MapProjection"};
39  static constexpr std::string_view ChronoMapDepiction{"MapDepiction"};
40  }
41 
45  enum class MapProjectionType {
46  Mercator,
49  };
50 
54  enum class MapDepiction {
55  Terrain,
56  Countries,
57  };
58 
62  enum class MapIllumination {
63  Day,
64  Night,
65  };
66 
70  enum class MapSize {
71  Small,
72  Medium,
73  Large,
74  ExtraLarge,
75  Last
76  };
77 
83  static constexpr Size MapImageSize(MapSize mapSize) {
84  switch (mapSize) {
85  case MapSize::Small:
86  return Size{660,330};
87  case MapSize::Medium:
88  return Size{1320, 660};
89  case MapSize::Large:
90  return Size{1980, 990};
92  return Size{2640,1320};
93  default:
94  break;
95  }
96  return Size{};
97  }
98 
106  static constexpr uint32_t MapImageId(MapDepiction mapDepiction, MapSize mapSize, MapIllumination illumination) {
107  return (static_cast<uint32_t>(mapSize) << 2u) | (static_cast<uint32_t>(mapDepiction) << 1u) | static_cast<uint32_t>(illumination);
108  }
109 
117  static std::string MapFileName(MapDepiction mapDepiction, MapSize mapSize, MapIllumination illumination) {
118  std::stringstream sstrm{};
119 
120  switch (mapDepiction) {
122  sstrm << "World_Topo_";
123  break;
125  sstrm << "Countries";
126  break;
127  default:
128  break;
129  }
130 
131  switch (illumination) {
133  sstrm << "D_";
134  break;
136  sstrm << "N_";
137  break;
138  default:
139  break;
140  }
141 
142  auto size = MapImageSize(mapSize);
143  sstrm << size.w << 'x' << size.h;
144 
145  sstrm << ".png";
146  return sstrm.str();
147  }
148 
150  template<typename T>
151  static constexpr T rad2deg(T r) noexcept {
152  return 180. * (r/M_PI);
153  }
154 
156  template<typename T>
157  static constexpr T deg2rad(T d) noexcept {
158  return M_PI * (d/180.);
159  }
160 
165  struct GeoPosition {
166  double lat{0.},
167  lon{0.};
168  bool radians{false};
169  bool end{false};
170 
171  constexpr GeoPosition() = default;
172 
173  constexpr explicit GeoPosition(bool endPoint) : GeoPosition() {
174  end = endPoint;
175  }
176 
183  constexpr GeoPosition(double latitude, double longitude, bool rad = false) {
184  radians = rad;
185  if (radians) {
186  lat = std::clamp(latitude, -M_PI_2, M_PI_2);
187  lon = std::clamp(longitude, -M_PI, M_PI);
188  } else {
189  lat = std::clamp(latitude, -90., 90.);
190  lon = std::clamp(longitude, -180., 180.);
191  }
192  }
193 
198  [[nodiscard]] GeoPosition constexpr toRadians() const {
199  if (radians)
200  return GeoPosition{*this};
201  else {
202  GeoPosition g{};
203  g.radians = true;
204  g.lat = deg2rad(lat);
205  g.lon = deg2rad(lon);
206  return g;
207  }
208  }
209 
214  [[nodiscard]] GeoPosition constexpr toDegrees() const {
215  if (radians) {
216  GeoPosition g{};
217  g.radians = false;
218  g.lat = rad2deg(lat);
219  g.lon = rad2deg(lon);
220  return g;
221  } else
222  return GeoPosition{*this};
223  }
224 
230  [[nodiscard]] double distance(const GeoPosition &other) const {
231  auto r = toRadians();
232  auto o = other.toRadians();
233  return acos(sin(r.lat) * sin(o.lat) + cos(r.lat) * cos(o.lat) * cos(r.lon - o.lon));
234  }
235 
244  [[nodiscard]] GeoPosition midpoint(const GeoPosition &other, double distance, double fraction = 0.5) const {
245  auto r = toRadians();
246  auto o = other.toRadians();
247  auto A = sin((1. - fraction) * distance) / sin(distance);
248  auto B = sin(fraction * distance) / sin(distance);
249  auto x = A * cos(r.lat) * cos(r.lon) + B * cos(o.lat) * cos(o.lon);
250  auto y = A * cos(r.lat) * sin(r.lon) + B * cos(o.lat) * sin(o.lon);
251  auto z = A * sin(r.lat) + B * sin(o.lat);
252 
253  return GeoPosition{atan2(z, sqrt(x * x + y * y)), atan2(y, x), true};
254  }
255 
263  [[nodiscard]] GeoPosition midpoint(const GeoPosition &other, double fraction = 0.5) const {
264  return midpoint(other, distance(other), fraction);
265  }
266 
267  std::ostream &printOn(std::ostream &strm) const {
268  auto g = toDegrees();
269  return strm << '(' << g.lat << ',' << g.lon << ')';
270  }
271  };
272 
273  static constexpr double EquatorLatitude = 0.;
274  static constexpr double TropicLatitude = 23.4365;
275  static constexpr double ArcticCircle = 66.5635;
276  static constexpr double PrimeMeridian = 0.;
277  static constexpr std::array<GeoPosition, 21> InternationalDateLine = {
278  {
279  {90., 180.},
280  {75., 180.},
281  {67.7356, -169.25},
282  {65.0189, -169.25},
283  {52.6863, 170.05},
284  {47.8353, 180.},
285  {-0.9, 180.},
286  {-0.9, -159.65},
287  {2.9, -159.65},
288  {2.9, -161.85},
289  {5., -161.85},
290  {5., -155.95},
291  {-7.8, -150.65},
292  {-10., -150.65},
293  {-10., -156.05},
294  {-7.8, -156.05},
295  {-7.8, -178.05},
296  {-15., -172.75},
297  {-45., -172.75},
298  {-51.1815, 180.},
299  {-90., 180.},
300  }
301  };
302 
303  enum class MapOverLayImage : size_t {
304  Sun,
305  Moon,
306  Count,
307  };
308 
310  MapOverLayImage mapOverLayImage;
311  std::string_view fileName;
312  };
313 
318  class MapProjection : public Manager {
319  public:
320  enum ShortCutCode : uint32_t {
321  MercatorProjection,
322  StationMercatorProjection,
323  AzimuthalProjection,
324  TerrainMap,
325  CountryMap,
326  };
327 
328  protected:
330  std::shared_ptr<TimerTick> mTimerTick{};
331 
333  TickProtocol::slot_type mMapIlluminationTimer{};
334 
336 // std::unique_ptr<WebCache> mMapCache{};
337 
340 
342  MapProjectionType mProjection{};
343 
346 
349 
351  bool mNewSurfaces{};
352 
354  Size mMapImgSize{};
355 
356  std::array<gm::Surface,2> mMapSurface{};
357  std::array<gm::Surface,2> mAzSurface{};
358 
359  std::array<gm::Surface,2> mMercatorTemp{};
360  std::array<gm::Surface,2> mAzimuthalTemp{};
361 
362  std::array<gm::Texture,2> mMercator{};
363  std::array<gm::Texture,2> mAzimuthal{};
364 
365  std::atomic_bool mAbortFuture{};
366 
368  GeoPosition mQth{45.,-75.};
369 
371  GeoPosition mQthRad{mQth.toRadians()};
372 
374  static constexpr std::array<double, 3> GrayLineCos = {-0.105,
375  -0.208,
376  -0.309};
377  static constexpr double GrayLinePow = .80;
378 
386  static void
387  azimuthalProjection(gm::Surface &projectedSurface, const gm::Surface &mapSurface, Position<int> projected,
388  Position<int> map);
389 
401  static std::tuple<bool, double, double>
402  xyToAzLatLong(int x, int y, const Size &mapSize, const GeoPosition &location, double sinY, double cosY);
403 
414  template<size_t N>
415  static bool
416  azimuthalProjectionSet(std::atomic_bool &abort, const GeoPosition &qthRad, const Size &mapImageSize, std::array<gm::Surface,N> &projected, std::array<gm::Surface,N> &map) {
417  // Compute Azimuthal maps from the Mercator maps
418  auto sinY = sin(qthRad.lat);
419  auto cosY = cos(qthRad.lat);
420  for (int y = 0; y < mapImageSize.h; y += 1) {
421  for (int x = 0; x < mapImageSize.w; x += 1) {
422  if (abort) {
423  abort = false;
424  return false;
425  }
426 
427  auto[valid, lat, lon] = xyToAzLatLong(x, y, mapImageSize, qthRad, sinY, cosY);
428  if (valid) {
429  auto xx = std::min(mapImageSize.w - 1,
430  (int) round((double) mapImageSize.w * ((lon + M_PI) / (2 * M_PI))));
431  auto yy = std::min(mapImageSize.h - 1,
432  (int) round((double) mapImageSize.h * ((M_PI_2 - lat) / M_PI)));
433 
434  for (auto idx = 0; idx < N; ++idx)
435  azimuthalProjection(projected[idx], map[idx], Position<int>(x, y), Position<int>(xx, yy));
436  }
437  }
438  }
439  return true;
440  }
441 
451  return azimuthalProjectionSet(mAbortFuture, mQthRad, mMapImgSize, mAzSurface, mMapSurface);
452  }
453 
455  std::future<bool> mComputeAzimuthalMapsFuture;
456 
458  bool mMapProjectionsInvalid{true};
459 
461  std::future<bool> mForegroundBackgroundFuture;
462 
471  bool setForegroundBackground();
472 
481  Position<MapPositionType> geoToMap(GeoPosition geo, MapProjectionType projection, int splitPixel, Rectangle &mapRect) const;
482 
483  public:
484  MapProjection() = delete;
485 
486  explicit MapProjection(std::shared_ptr<TimerTick> timerTick, std::filesystem::path& xdgDataPath);
487 
488  ~MapProjection() override = default;
489 
490  MapProjection(const MapProjection &) = delete;
491 
492  MapProjection(MapProjection &&) = delete;
493 
494  MapProjection &operator=(const MapProjection &) = delete;
495 
496  MapProjection &operator=(MapProjection &&) = delete;
497 
498  static constexpr std::string_view id = "MapProjection";
499  std::string_view nodeId() const noexcept override {
500  return id;
501  }
502 
504  void draw(gm::Context &context, const Position<int>& containerPosition) override;
505 
507  Rectangle layout(gm::Context &context, const Rectangle &screenRect) override;
508 
514  void addedToContainer() override;
515 
519  void cacheCurrentMaps();
520 
525  static std::tuple<double, double> subSolar();
526 
527  /*
528  * @brief Accessor for Qth
529  */
530  auto getQth() const {
531  return mQth;
532  }
533 
538  bool mapProjectionsValid() const {
539  return !mMapProjectionsInvalid; // && !mForegroundBackgroundFuture.valid();
540  }
541 
546  auto getProjection() const {
547  return mProjection;
548  }
549 
557  void drawMapItem(const ImageId &mapItem, gm::Context& context, Rectangle mapRectangle, GeoPosition& geoPosition,
558  MapProjectionType projection, int splitPixel);
559 
565  int projectionSplitPixel(Size drawSize) const {
566  int splitPixel = util::roundToInt((double) drawSize.w * ((mQth.lon) / 360.));
567  if (splitPixel < 0)
568  splitPixel += drawSize.w;
569  return splitPixel;
570  }
571 
581  void drawMapLine(gm::Context &context, AntiAliasedDrawing &drawing, GeoPosition begin, Rectangle mapRectangle,
582  const std::function<GeoPosition(GeoPosition &, bool fine)> &increment) {
583  int splitPixel = 0;
584  std::function<bool(const Position<MapPositionType>& p0, const Position<MapPositionType>& p1)> gapTest;
585 
586  switch (mProjection) {
588  gapTest = [&](const Position<MapPositionType>& p0, const Position<MapPositionType>& p1) -> bool {
589  auto split = mapRectangle.w / 2 + mapRectangle.x;
590  return (p0.x < split && p1.x < split) || (p0.x > split && p1.x > split);
591  };
592  break;
594  splitPixel = projectionSplitPixel(mapRectangle.size());
596  gapTest = [&](const Position<MapPositionType>& p0, const Position<MapPositionType>& p1) -> bool {
597  return abs(p0.x - p1.x) < static_cast<MapPositionType>(mapRectangle.w) / 4 &&
598  abs(p0.y - p1.y) < static_cast<MapPositionType>(mapRectangle.h) / 4;
599  };
600  break;
601  }
602 
603  auto p0 = geoToMap(begin.toRadians(), mProjection, splitPixel, mapRectangle) + mapRectangle.position();
604  auto g0 = begin;
605  do {
606  auto g1 = increment(g0, false);
607  auto p1 = geoToMap(g1.toRadians(), mProjection, splitPixel, mapRectangle) + mapRectangle.position();
608  if (gapTest(p0, p1)) {
609  // Draw up to a plotting gap.
610  drawing.renderLine(context, p0, p1);
611  }
612  else {
613  // Switch to fine increment until the gap is encountered again.
614  for (g1 = increment(g0, true); !g1.end; g1 = increment(g1, true)) {
615  p1 = geoToMap(g1.toRadians(), mProjection, splitPixel, mapRectangle) + mapRectangle.position();
616  if (gapTest(p0, p1)) {
617  drawing.renderLine(context, p0, p1);
618  } else {
619  break;
620  }
621  p0 = p1;
622  g0 = g1;
623  }
624  }
625  p0 = p1;
626  g0 = g1;
627  } while (!g0.end);
628  }
629 
630  void drawInterpolate(gm::Context &context, AntiAliasedDrawing &drawing, Rectangle mapRect, GeoPosition &geo0,
631  GeoPosition &geo1) {
632  auto r0 = geo0.toRadians();
633  auto r1 = geo1.toRadians();
634  int splitPixel = 0;
635  std::function<bool(const Position<MapPositionType>& p0, const Position<MapPositionType>& p1)> gapTest;
636 
641  auto plotPoints = [this,&splitPixel,&mapRect,&gapTest,&drawing,&context](GeoPosition &g0, GeoPosition &g1) -> bool {
642  auto p0 = geoToMap(g0, mProjection, splitPixel, mapRect) + mapRect.position();
643  auto p1 = geoToMap(g1, mProjection, splitPixel, mapRect) + mapRect.position();
644  if (gapTest(p0, p1)) {
645  drawing.renderLine(context, p0, p1);
646  return false;
647  }
648  return true;
649  };
650 
651  switch (mProjection) {
653  gapTest = [&](const Position<MapPositionType>& p0, const Position<MapPositionType>& p1) -> bool {
654  auto split = mapRect.w / 2 + mapRect.x;
655  return (p0.x < split && p1.x < split) || (p0.x > split && p1.x > split);
656  };
657  break;
659  splitPixel = projectionSplitPixel(mapRect.size());
661  gapTest = [&](const Position<MapPositionType>& p0, const Position<MapPositionType>& p1) -> bool {
662  return abs(p0.x - p1.x) < mapRect.w / 4 &&
663  abs(p0.y - p1.y) < mapRect.h / 4;
664  };
665  break;
666  }
667 
668  for (auto r = r0.distance(r1); r > deg2rad(0.25); r = r0.distance(r1)) {
669  if (plotPoints(r0, r1)) {
670  auto midPoint = r0.midpoint(r1, r, 0.5);
671  if (plotPoints(r0, midPoint)) {
672  plotPoints(midPoint, r1);
673  r1 = midPoint;
674  } else {
675  r0 = midPoint;
676  }
677  } else {
678  break;
679  }
680  }
681  }
682 
692  template<typename Iterator>
693  void drawMapLine(gm::Context &context, AntiAliasedDrawing &drawing, Rectangle mapRect, Iterator first, Iterator last) {
694 
695  double StepSize = deg2rad(3.);
696  for (auto idx = first; idx != last - 1; ++idx) {
697  auto g1 = (idx + 1)->toRadians();
698  auto g0 = idx->toRadians();
699 
700  auto dist = g0.distance(g1);
701  auto steps = std::max(util::roundToInt(dist / StepSize), 1);
702  double f = 0.;
703  double fInc = 1. / static_cast<double>(steps);
704 
705  auto r0 = g0;
706  for (int fIdx = 0; fIdx <= steps; fIdx++) {
707  f = fInc * fIdx;
708 
709  auto r1 = g0.midpoint(g1, dist, f);
710  drawInterpolate(context, drawing, mapRect, r0, r1);
711  r0 = r1;
712  }
713  }
714  }
715 
723  void drawLongitude(gm::Context &context, AntiAliasedDrawing &drawing, double longitude, double latitudeBound,
724  Rectangle mapRect) {
725  static constexpr double fineInc = 1.;
726  static constexpr double coarseInc = 3.;
727  double begin = -abs(latitudeBound);
728  double end = abs(latitudeBound);
729  drawMapLine(context, drawing, GeoPosition{begin, longitude}, mapRect,
730  [&end](GeoPosition &g0, bool fine) -> GeoPosition {
731  auto r = g0;
732  if (fine) {
733  r.lat += fineInc;
734  } else {
735  r.lat += coarseInc;
736  }
737  if (r.lat > end) {
738  r.end = true;
739  r.lat = end;
740  }
741  return r;
742  });
743  }
744 
752  void drawLatitude(gm::Context &context, AntiAliasedDrawing &drawing, double latitude, Rectangle mapRect) {
753  static constexpr double begin = -180.;
754  static constexpr double end = 180.;
755  static constexpr double fineInc = 1.;
756  static constexpr double coarseInc = 3.;
757  drawMapLine(context, drawing, GeoPosition{latitude, begin}, mapRect,
758  [](GeoPosition &g0, bool fine) -> GeoPosition {
759  auto r = g0;
760  if (fine) {
761  r.lon += fineInc;
762  } else {
763  r.lon += coarseInc;
764  }
765  r.end = r.lon > end + coarseInc;
766  return r;
767  });
768  }
769  };
770 }
771 
772 inline std::ostream &operator<<(std::ostream &strm, rose::GeoPosition &geoPosition) {
773  return geoPosition.printOn(strm);
774 }
775 
Definition: MapProjection.h:318
Sun position in the sky.
Definition: Plan13.h:282
auto getProjection() const
Accessor for the type of projection.
Definition: MapProjection.h:546
int roundToInt(T value, T multiplier=1.)
Round a floating point value to an integer.
Definition: Math.h:34
A Widget which manages contained Widgets.
Definition: Visual.h:697
bool radians
Values are in radians when true.
Definition: MapProjection.h:168
A wrapper class for SDL_Surface pointers.
Definition: Surface.h:50
std::tuple< double, double > subSolar()
Compute the sub-solar geographic coordinates, used in plotting the solar ilumination.
Definition: MapProjection.cpp:457
bool mapProjectionsValid() const
Determine if map projections are valid.
Definition: MapProjection.h:538
constexpr GeoPosition(double latitude, double longitude, bool rad=false)
Create a geographic position.
Definition: MapProjection.h:183
bool computeAzimuthalMaps()
Compute Azimuthal map projections.
Definition: MapProjection.h:450
std::shared_ptr< Slot< Args... > > slot_type
Composed Slot type.
Definition: Signals.h:123
Standard Mercator split a the International Date Line.
double lat
Latitude value.
Definition: MapProjection.h:166
Definition: AntiAliasedDrawing.h:23
void drawInterpolate(gm::Context &context, AntiAliasedDrawing &drawing, Rectangle mapRect, GeoPosition &geo0, GeoPosition &geo1)
Definition: MapProjection.h:630
int projectionSplitPixel(Size drawSize) const
Computer the StationMercator split pixel given a map drawing size.
Definition: MapProjection.h:565
void drawLatitude(gm::Context &context, AntiAliasedDrawing &drawing, double latitude, Rectangle mapRect)
Draw a line of Latitude at latitude.
Definition: MapProjection.h:752
void drawMapLine(gm::Context &context, AntiAliasedDrawing &drawing, GeoPosition begin, Rectangle mapRectangle, const std::function< GeoPosition(GeoPosition &, bool fine)> &increment)
Draw a line on the projected map.
Definition: MapProjection.h:581
MapSize
Definition: MapProjection.h:70
MapIllumination
Definition: MapProjection.h:62
An abstraction of a geographic position.
Definition: MapProjection.h:165
std::future< bool > mComputeAzimuthalMapsFuture
The std::future result of computeAzimuthalMaps()
Definition: MapProjection.h:455
Context
Definition: GraphicsModel.h:76
Definition: SettingsNames.h:13
Position< int > position() const noexcept
Get a Position from a Rectangle.
Definition: Types.h:346
Abstraction of the graphics model.
Fetching and caching web resources.
GeoPosition midpoint(const GeoPosition &other, double distance, double fraction=0.5) const
Find a mid-point on the Great Circle between this GeoPosition and another.
Definition: MapProjection.h:244
double distance(const GeoPosition &other) const
Computer the Great Circle distance between this GeoPosition and another.
Definition: MapProjection.h:230
GeoPosition midpoint(const GeoPosition &other, double fraction=0.5) const
Find a mid-point on the Great Circle between this GeoPosition and another.
Definition: MapProjection.h:263
GeoPosition constexpr toRadians() const
Convert the position from degrees to radians.
Definition: MapProjection.h:198
Large 1980 x 990.
Azimuthal with the Station location centered on the left hemisphere.
MapProjectionType
Definition: MapProjection.h:45
A composite of a Position and a Size.
Definition: Types.h:307
Medium 1320 x 660.
MapDepiction
Definition: MapProjection.h:54
bool renderLine(gm::Context &context, Position< T > p0, Position< T > p1)
Draw an anti-aliased line.
Definition: AntiAliasedDrawing.h:90
static bool azimuthalProjectionSet(std::atomic_bool &abort, const GeoPosition &qthRad, const Size &mapImageSize, std::array< gm::Surface, N > &projected, std::array< gm::Surface, N > &map)
Compute Azimuthal projection of a set of the same sized maps.
Definition: MapProjection.h:416
double lon
Longitude value.
Definition: MapProjection.h:167
A size in integer dimensions.
Definition: Types.h:230
ToDo: There is an issue that the initial scroll interaction is lost if the click/press lands on a Wid...
Definition: CelestialOverlay.cpp:13
Mercator split so the Station location is centred.
std::future< bool > mForegroundBackgroundFuture
The std::future result of setForegroundBackground()
Definition: MapProjection.h:461
ExtraLarger 2640 x 1320.
User Interface Visual types.
GeoPosition constexpr toDegrees() const
Convert the position from radians to degrees.
Definition: MapProjection.h:214
void drawLongitude(gm::Context &context, AntiAliasedDrawing &drawing, double longitude, double latitudeBound, Rectangle mapRect)
Draw a line of Longitude at longitude.
Definition: MapProjection.h:723
Definition: MapProjection.h:309
void drawMapLine(gm::Context &context, AntiAliasedDrawing &drawing, Rectangle mapRect, Iterator first, Iterator last)
Draw a line on the projected map using points in a standard container.
Definition: MapProjection.h:693
Size size() const noexcept
Get a Size from a Rectangle.
Definition: Types.h:351
Small 660 x 330.
std::tuple< bool, double, double > xyToAzLatLong(int x, int y, const Size &mapSize, const GeoPosition &location, double siny, double cosy)
Transform a Mercator map pixel into an Azimuthal map latitude and longitude in radians.
Definition: MapProjection.cpp:403