react-native-cesium 1.0.6 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +64 -11
  2. package/ReactNativeCesium.podspec +1 -1
  3. package/android/CMakeLists.txt +5 -3
  4. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +197 -227
  5. package/android/src/main/cpp/CesiumBridgeAndroid.h +48 -49
  6. package/android/src/main/cpp/cpp-adapter.cpp +2 -2
  7. package/android/src/main/cpp/vulkan/VulkanBackend.cpp +1010 -569
  8. package/android/src/main/cpp/vulkan/VulkanBackend.h +121 -12
  9. package/android/src/main/cpp/vulkan/shaders/sky.frag +20 -10
  10. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +28 -0
  11. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +295 -58
  12. package/cpp/engine/CameraSmoother.cpp +99 -0
  13. package/cpp/engine/CameraSmoother.hpp +29 -0
  14. package/cpp/engine/CameraTargetState.cpp +49 -0
  15. package/cpp/engine/CameraTargetState.hpp +121 -0
  16. package/cpp/engine/CesiumEngine.cpp +206 -60
  17. package/cpp/engine/CesiumEngine.hpp +77 -10
  18. package/cpp/engine/EngineTunables.hpp +79 -0
  19. package/cpp/engine/GeoidConverter.cpp +36 -0
  20. package/cpp/engine/GeoidConverter.hpp +10 -0
  21. package/cpp/engine/GlobeCamera.cpp +7 -2
  22. package/cpp/engine/MetricsAggregator.cpp +71 -0
  23. package/cpp/engine/MetricsAggregator.hpp +54 -0
  24. package/cpp/engine/TileLifecycleManager.cpp +0 -12
  25. package/cpp/engine/TileLifecycleManager.hpp +0 -3
  26. package/cpp/third_party/egm96/EGM96.c +289 -0
  27. package/cpp/third_party/egm96/EGM96.h +45 -0
  28. package/cpp/third_party/egm96/EGM96_data.h +65372 -0
  29. package/ios/CesiumBridge.h +24 -5
  30. package/ios/CesiumBridge.mm +149 -271
  31. package/ios/HybridCesiumView.swift +197 -40
  32. package/ios/metal/MetalBackend.mm +72 -23
  33. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +26 -0
  34. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  35. package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +5 -1
  36. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +109 -1
  37. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +24 -0
  38. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +48 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +25 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +32 -2
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +72 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +19 -0
  43. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +30 -0
  44. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +85 -1
  45. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +60 -0
  46. package/nitrogen/generated/ios/swift/CesiumMetrics.swift +7 -2
  47. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +12 -0
  48. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +288 -0
  49. package/nitrogen/generated/shared/c++/CesiumMetrics.hpp +5 -1
  50. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +24 -0
  51. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +25 -1
  52. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +132 -0
  53. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +13 -1
  54. package/nitrogen/generated/shared/json/CesiumViewConfig.json +12 -0
  55. package/package.json +4 -4
  56. package/scripts/cesium/clone.mjs +1 -1
  57. package/src/specs/cesium-view.nitro.ts +29 -0
  58. package/cpp/engine/DeletionQueue.cpp +0 -28
  59. package/cpp/engine/DeletionQueue.hpp +0 -28
@@ -0,0 +1,79 @@
1
+ #pragma once
2
+
3
+ #include <cstddef>
4
+ #include <cstdint>
5
+
6
+ namespace reactnativecesium {
7
+
8
+ // Centralised default values that previously lived as anonymous magic numbers
9
+ // scattered across the engine and platform backends. Keep this header tiny and
10
+ // dependency-free so it is safe to include from any translation unit.
11
+ namespace tunables {
12
+
13
+ // CPU pipeline stages allowed to be in flight at once on the GPU. Both Metal
14
+ // (triple-buffered MTLBuffer slots) and Vulkan (in-flight fences / command
15
+ // buffers) use this constant as their core depth, so changing it here
16
+ // propagates to every backend.
17
+ inline constexpr int kMaxFramesInFlight = 3;
18
+
19
+ // Upper bound on simultaneously live raster overlay textures held by the
20
+ // Vulkan descriptor pool. Cesium can cache many hundreds of tiles at high
21
+ // zoom; 2048 gives substantial headroom without exhausting GPU descriptor
22
+ // state.
23
+ inline constexpr int kMaxRasterTextures = 2048;
24
+
25
+ // Cesium Native tileset cache (decoded geometry / textures kept in RAM).
26
+ inline constexpr int64_t kDefaultMaxCachedBytes = 256LL * 1024LL * 1024LL;
27
+
28
+ // SqliteCache row budget — number of cached HTTP responses Cesium keeps on
29
+ // disk between sessions. 4k rows is a reasonable phone default; tablets can
30
+ // happily go to 16k.
31
+ inline constexpr int32_t kDefaultSqliteCacheMaxRows = 4096;
32
+
33
+ // FrameResult initial reservations (CPU-side merged geometry buffer) used to
34
+ // avoid reallocations during the first few frames of a session. The buffers
35
+ // are then re-used (only re-grown if visible geometry exceeds capacity).
36
+ inline constexpr std::size_t kFrameResultPositionFloatReserve = 512u * 1024u;
37
+ inline constexpr std::size_t kFrameResultIndexUint32Reserve = 3u * 1024u * 1024u;
38
+
39
+ // Tileset selection knobs (mirrored to TilesetOptions defaults).
40
+ inline constexpr double kDefaultMaximumScreenSpaceError = 32.0;
41
+ inline constexpr int32_t kDefaultMaximumSimultaneousTileLoads = 12;
42
+ inline constexpr int32_t kDefaultLoadingDescendantLimit = 20;
43
+
44
+ // Camera smoothing constants — shared between platform bridges so jank /
45
+ // motion feel is identical on iOS and Android.
46
+ inline constexpr double kSmoothAltitude = 25.0;
47
+ inline constexpr double kSmoothHeading = 30.0;
48
+ inline constexpr double kSmoothPitch = 50.0;
49
+ inline constexpr double kSmoothRoll = 50.0;
50
+ inline constexpr double kSmoothViewCorr = 50.0;
51
+ inline constexpr double kEpsLatLon = 1e-7;
52
+ inline constexpr double kEpsAltitudeMeters = 0.1;
53
+ inline constexpr double kEpsAngleDeg = 0.05;
54
+ inline constexpr double kEpsQuatDot = 1e-8;
55
+
56
+ // Lat/lon are smoothed adaptively: τ = clamp(α · ewmaInterval, τmin, τmax),
57
+ // then per frame we apply 1 - exp(-dt/τ). α<1 means the camera converges
58
+ // before the next sample lands. With ~60 Hz gestures the EWMA interval is
59
+ // ~16 ms ⇒ τ at the floor (5 ms) ⇒ ≤1-frame trail (indistinguishable from
60
+ // the previous snap). With a 1 Hz GPS feed τ ≈ 450 ms ⇒ ~95 % converged
61
+ // before the next sample arrives, removing the per-second teleport.
62
+ inline constexpr double kSmoothPositionAlpha = 0.45;
63
+ inline constexpr double kSmoothPositionTauMin = 0.005;
64
+ inline constexpr double kSmoothPositionTauMax = 0.6;
65
+ // Genuine teleports (e.g. setCamera(NewYork) → setCamera(Tokyo)) skip the
66
+ // smoother — easing across the planet over 600 ms looks worse than a snap.
67
+ inline constexpr double kPosSnapThroughDeg = 0.5;
68
+ // EWMA mixing factor for the inter-arrival interval of setCamera calls.
69
+ inline constexpr double kEwmaIntervalAlpha = 0.3;
70
+
71
+ // Metrics throttle: emit telemetry every N rendered frames.
72
+ inline constexpr int kMetricsEmitEveryFrames = 20;
73
+
74
+ // Idle-probe interval (seconds): when the engine is idle, periodically tick
75
+ // to catch late tile completions without redundant full draws.
76
+ inline constexpr double kIdleProbeSeconds = 0.25;
77
+
78
+ } // namespace tunables
79
+ } // namespace reactnativecesium
@@ -0,0 +1,36 @@
1
+ #include "GeoidConverter.hpp"
2
+
3
+ #include "../third_party/egm96/EGM96.h"
4
+
5
+ #include <cmath>
6
+
7
+ namespace reactnativecesium {
8
+
9
+ namespace {
10
+
11
+ double normalizeLongitudeDegrees(double longitudeDeg) {
12
+ if (!std::isfinite(longitudeDeg)) return 0.0;
13
+ double wrapped = std::fmod(longitudeDeg + 180.0, 360.0);
14
+ if (wrapped < 0.0) wrapped += 360.0;
15
+ return wrapped - 180.0;
16
+ }
17
+
18
+ } // namespace
19
+
20
+ double mslToEllipsoidMeters(double latitudeDeg,
21
+ double longitudeDeg,
22
+ double mslMeters) {
23
+ if (!std::isfinite(latitudeDeg) || !std::isfinite(longitudeDeg) ||
24
+ !std::isfinite(mslMeters)) {
25
+ return mslMeters;
26
+ }
27
+
28
+ const double normalizedLon = normalizeLongitudeDegrees(longitudeDeg);
29
+ const double geoidOffset =
30
+ egm96_compute_altitude_offset(latitudeDeg, normalizedLon);
31
+
32
+ if (!std::isfinite(geoidOffset)) return mslMeters;
33
+ return mslMeters + geoidOffset;
34
+ }
35
+
36
+ } // namespace reactnativecesium
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+
3
+ namespace reactnativecesium {
4
+
5
+ /// Converts orthometric height (MSL) to ellipsoidal height (HAE, WGS84).
6
+ double mslToEllipsoidMeters(double latitudeDeg,
7
+ double longitudeDeg,
8
+ double mslMeters);
9
+
10
+ } // namespace reactnativecesium
@@ -1,4 +1,5 @@
1
1
  #include "GlobeCamera.hpp"
2
+ #include "GeoidConverter.hpp"
2
3
 
3
4
  #include <CesiumGeospatial/GlobeTransforms.h>
4
5
 
@@ -43,9 +44,11 @@ void GlobeCamera::recompute() const {
43
44
  dirty_ = false;
44
45
 
45
46
  const auto& ellipsoid = CesiumGeospatial::Ellipsoid::WGS84;
47
+ const double ellipsoidAltitude =
48
+ mslToEllipsoidMeters(params_.latitude, params_.longitude, params_.altitude);
46
49
  CesiumGeospatial::Cartographic carto(glm::radians(params_.longitude),
47
50
  glm::radians(params_.latitude),
48
- params_.altitude);
51
+ ellipsoidAltitude);
49
52
  ecefPosition_ = ellipsoid.cartographicToCartesian(carto);
50
53
 
51
54
  glm::dmat4 enuToECEF =
@@ -117,10 +120,12 @@ void GlobeCamera::computeHeadingPitchToward(double targetLatDeg,
117
120
  recompute();
118
121
 
119
122
  const auto& ellipsoid = CesiumGeospatial::Ellipsoid::WGS84;
123
+ const double targetEllipsoidAlt =
124
+ mslToEllipsoidMeters(targetLatDeg, targetLonDeg, targetAltMeters);
120
125
  CesiumGeospatial::Cartographic targetCarto(
121
126
  glm::radians(targetLonDeg),
122
127
  glm::radians(targetLatDeg),
123
- targetAltMeters);
128
+ targetEllipsoidAlt);
124
129
  glm::dvec3 targetEcef = ellipsoid.cartographicToCartesian(targetCarto);
125
130
  glm::dvec3 dirEcef = glm::normalize(targetEcef - ecefPosition_);
126
131
 
@@ -0,0 +1,71 @@
1
+ #include "MetricsAggregator.hpp"
2
+
3
+ #include "EngineTunables.hpp"
4
+
5
+ #include <regex>
6
+
7
+ namespace reactnativecesium {
8
+
9
+ std::string MetricsAggregator::stripHtmlToPlain(const std::string& html) {
10
+ if (html.empty()) return "";
11
+ // The regexes are static so we pay the (very real) cost of compilation only
12
+ // once per process. std::regex matches behaviour previously implemented twice
13
+ // (NSRegularExpression on iOS, std::regex on Android) — both bridges now hit
14
+ // this path so we cannot drift.
15
+ static const std::regex tagRx("<[^>]+>");
16
+ static const std::regex wsRx("\\s+");
17
+ std::string plain = std::regex_replace(html, tagRx, " ");
18
+ plain = std::regex_replace(plain, wsRx, " ");
19
+ size_t start = plain.find_first_not_of(" \t\n\r");
20
+ size_t end = plain.find_last_not_of(" \t\n\r");
21
+ if (start == std::string::npos) return "";
22
+ return plain.substr(start, end - start + 1);
23
+ }
24
+
25
+ std::optional<MetricsSnapshot>
26
+ MetricsAggregator::tick(double dt, const FrameResult& frame, bool tlsConfigured) {
27
+ const double instFps = (dt > 1e-6) ? (1.0 / dt) : 0.0;
28
+ fpsEma_ = (fpsEma_ <= 1e-6) ? instFps : (fpsEma_ * 0.85 + instFps * 0.15);
29
+
30
+ if (++metricsTick_ < tunables::kMetricsEmitEveryFrames) {
31
+ return std::nullopt;
32
+ }
33
+ metricsTick_ = 0;
34
+
35
+ latest_.fps = fpsEma_;
36
+ latest_.tilesRendered = frame.tilesRendered;
37
+ latest_.tilesLoading = frame.tilesLoading;
38
+ latest_.tilesVisited = frame.tilesVisited;
39
+ latest_.ionTokenConfigured = frame.ionTokenConfigured;
40
+ latest_.tilesetReady = frame.tilesetActive;
41
+ latest_.tlsConfigured = tlsConfigured;
42
+
43
+ if (!frame.creditHtmlLines.empty()) {
44
+ std::string joined;
45
+ joined.reserve(256);
46
+ for (const auto& html : frame.creditHtmlLines) {
47
+ if (!joined.empty()) joined += "|";
48
+ joined += html;
49
+ }
50
+ if (joined != lastCreditHtmlJoined_) {
51
+ lastCreditHtmlJoined_ = std::move(joined);
52
+ std::string credits;
53
+ credits.reserve(256);
54
+ for (const auto& html : frame.creditHtmlLines) {
55
+ if (html.find("Error: Invalid Credit") != std::string::npos) continue;
56
+ std::string chunk = stripHtmlToPlain(html);
57
+ if (chunk.empty()) continue;
58
+ if (!credits.empty()) credits += " \xC2\xB7 "; // " · "
59
+ credits += chunk;
60
+ }
61
+ latest_.creditsPlainText = std::move(credits);
62
+ }
63
+ } else {
64
+ lastCreditHtmlJoined_.clear();
65
+ latest_.creditsPlainText.clear();
66
+ }
67
+
68
+ return latest_;
69
+ }
70
+
71
+ } // namespace reactnativecesium
@@ -0,0 +1,54 @@
1
+ #pragma once
2
+
3
+ #include "../renderer/RenderTypes.hpp"
4
+
5
+ #include <optional>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ namespace reactnativecesium {
10
+
11
+ // Per-frame snapshot the platform shells expose to JS via Nitro / JNI.
12
+ struct MetricsSnapshot {
13
+ double fps;
14
+ int tilesRendered;
15
+ int tilesLoading;
16
+ int tilesVisited;
17
+ bool ionTokenConfigured;
18
+ bool tilesetReady;
19
+ bool tlsConfigured;
20
+ std::string creditsPlainText;
21
+ };
22
+
23
+ // Aggregates per-frame metrics on the render thread, throttling emission
24
+ // (every kMetricsEmitEveryFrames frames) so we do not spam the JS bridge.
25
+ //
26
+ // Usage: call `tick(dt, frameResult, tlsConfigured)` once per rendered frame;
27
+ // take the optional return value as the "publish to JS" signal. The internal
28
+ // FPS EMA persists between ticks.
29
+ class MetricsAggregator {
30
+ public:
31
+ MetricsAggregator() = default;
32
+
33
+ // Returns std::nullopt on most frames; returns the latest values once the
34
+ // throttle counter wraps. Idempotent w.r.t. credit text — only re-strips
35
+ // HTML when the joined HTML lines actually change frame-to-frame.
36
+ std::optional<MetricsSnapshot>
37
+ tick(double dt, const FrameResult& frame, bool tlsConfigured);
38
+
39
+ // Returns the most recently *computed* snapshot (fps EMA + counters); used
40
+ // by the platform read-back accessors. Cheap.
41
+ const MetricsSnapshot& latest() const { return latest_; }
42
+
43
+ // Strips HTML tags + collapses whitespace + trims. Pure utility, exposed so
44
+ // platform shells that prefer to render their own credit lines can reuse it.
45
+ static std::string stripHtmlToPlain(const std::string& html);
46
+
47
+ private:
48
+ double fpsEma_ = 0.0;
49
+ int metricsTick_ = 0;
50
+ std::string lastCreditHtmlJoined_;
51
+ MetricsSnapshot latest_{};
52
+ };
53
+
54
+ } // namespace reactnativecesium
@@ -1,8 +1,5 @@
1
1
  #include "TileLifecycleManager.hpp"
2
2
 
3
- #include <Cesium3DTilesSelection/TileContent.h>
4
- #include <Cesium3DTilesSelection/TileSelectionState.h>
5
-
6
3
  namespace reactnativecesium {
7
4
 
8
5
  void TileLifecycleManager::stampTileUsed(TileGPUResources* resources) {
@@ -18,13 +15,4 @@ bool TileLifecycleManager::shouldDeferFree(
18
15
  return resources->lastUsedFrame >= frameNumber_;
19
16
  }
20
17
 
21
- bool TileLifecycleManager::isRefinementLocked(
22
- const Cesium3DTilesSelection::Tile& /*tile*/) const {
23
- // Cesium Native guarantees that free() is not called on tiles that are
24
- // currently needed for rendering (including refined parent tiles whose
25
- // children are loading). Therefore, explicit refinement locking is not
26
- // required here.
27
- return false;
28
- }
29
-
30
18
  } // namespace reactnativecesium
@@ -2,8 +2,6 @@
2
2
 
3
3
  #include "../renderer/RenderTypes.hpp"
4
4
 
5
- #include <Cesium3DTilesSelection/Tile.h>
6
-
7
5
  #include <cstdint>
8
6
 
9
7
  namespace reactnativecesium {
@@ -16,7 +14,6 @@ public:
16
14
  void stampTileUsed(TileGPUResources* resources);
17
15
 
18
16
  bool shouldDeferFree(const TileGPUResources* resources) const;
19
- bool isRefinementLocked(const Cesium3DTilesSelection::Tile& tile) const;
20
17
 
21
18
  private:
22
19
  uint64_t frameNumber_ = 0;
@@ -0,0 +1,289 @@
1
+ /*
2
+ * Copyright (c) 2006 D.Ineiev <ineiev@yahoo.co.uk>
3
+ * Copyright (c) 2020 Emeric Grange <emeric.grange@gmail.com>
4
+ *
5
+ * This software is provided 'as-is', without any express or implied warranty.
6
+ * In no event will the authors be held liable for any damages arising from
7
+ * the use of this software.
8
+ *
9
+ * Permission is granted to anyone to use this software for any purpose,
10
+ * including commercial applications, and to alter it and redistribute it
11
+ * freely, subject to the following restrictions:
12
+ *
13
+ * 1. The origin of this software must not be misrepresented; you must not
14
+ * claim that you wrote the original software. If you use this software
15
+ * in a product, an acknowledgment in the product documentation would be
16
+ * appreciated but is not required.
17
+ * 2. Altered source versions must be plainly marked as such, and must not be
18
+ * misrepresented as being the original software.
19
+ * 3. This notice may not be removed or altered from any source distribution.
20
+ */
21
+
22
+ /*
23
+ * This program is designed for the calculation of a geoid undulation at a point
24
+ * whose latitude and longitude is specified.
25
+ *
26
+ * This program is designed to be used with the constants of EGM96 and those of
27
+ * the WGS84(g873) system. The undulation will refer to the WGS84 ellipsoid.
28
+ *
29
+ * It's designed to use the potential coefficient model EGM96 and a set of
30
+ * spherical harmonic coefficients of a correction term.
31
+ * The correction term is composed of several different components, the primary
32
+ * one being the conversion of a height anomaly to a geoid undulation.
33
+ * The principles of this procedure were initially described in the paper:
34
+ * - use of potential coefficient models for geoid undulation determination using
35
+ * a spherical harmonic representation of the height anomaly/geoid undulation
36
+ * difference by R.H. Rapp, Journal of Geodesy, 1996.
37
+ *
38
+ * This program is a modification of the program described in the following report:
39
+ * - a fortran program for the computation of gravimetric quantities from high
40
+ * degree spherical harmonic expansions, Richard H. Rapp, report 334, Department
41
+ * of Geodetic Science and Surveying, the Ohio State University, Columbus, 1982
42
+ */
43
+
44
+ #include "EGM96.h"
45
+ #include "EGM96_data.h"
46
+
47
+ #include <math.h>
48
+ #include <stdlib.h>
49
+ #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__)
50
+ #include <threads.h>
51
+ #else
52
+ #ifndef thread_local
53
+ #define thread_local _Thread_local
54
+ #endif
55
+ #endif
56
+
57
+ /* ************************************************************************** */
58
+
59
+ #define _coeffs (65341) //!< Size of correction and harmonic coefficients arrays (361*181)
60
+ #define _nmax (360) //!< Maximum degree and orders of harmonic coefficients.
61
+ #define _361 (361)
62
+
63
+ /* ************************************************************************** */
64
+
65
+ double hundu(const double p[_coeffs+1],
66
+ const double sinml[_361+1], const double cosml[_361+1],
67
+ const double gr, const double re)
68
+ {
69
+ // WGS84 gravitational constant in m³/s² (mass of Earth’s atmosphere included)
70
+ const double GM = 0.3986004418e15;
71
+ // WGS84 datum surface equatorial radius
72
+ const double ae = 6378137.0;
73
+
74
+ const double ar = ae / re;
75
+ double arn = ar;
76
+ double ac = 0;
77
+ double a = 0;
78
+
79
+ unsigned k = 3;
80
+ for (unsigned n = 2; n <= _nmax; n++)
81
+ {
82
+ arn *= ar;
83
+ k++;
84
+ double sumc = p[k] * egm96_data[k][0];
85
+ double sum = p[k] * egm96_data[k][2];
86
+
87
+ for (unsigned m = 1; m <= n; m++)
88
+ {
89
+ k++;
90
+ double tempc = (egm96_data[k][0] * cosml[m]) + (egm96_data[k][1] * sinml[m]);
91
+ double temp = (egm96_data[k][2] * cosml[m]) + (egm96_data[k][3] * sinml[m]);
92
+ sumc += p[k] * tempc;
93
+ sum += p[k] * temp;
94
+ }
95
+ ac += sumc;
96
+ a += sum * arn;
97
+ }
98
+ ac += egm96_data[1][0] + (p[2] * egm96_data[2][0]) + (p[3] * ((egm96_data[3][0] * cosml[1]) + (egm96_data[3][1] * sinml[1])));
99
+
100
+ // Add haco = ac/100 to convert height anomaly on the ellipsoid to the undulation
101
+ // Add -0.53m to make undulation refer to the WGS84 ellipsoid
102
+
103
+ return ((a * GM) / (gr * re)) + (ac / 100.0) - 0.53;
104
+ }
105
+
106
+ void dscml(const double rlon, double sinml[_361+1], double cosml[_361+1])
107
+ {
108
+ const double a = sin(rlon);
109
+ const double b = cos(rlon);
110
+
111
+ sinml[1] = a;
112
+ cosml[1] = b;
113
+ sinml[2] = (2 * b * a);
114
+ cosml[2] = (2 * b * b) - 1;
115
+
116
+ for (unsigned m = 3; m <= _nmax; m++)
117
+ {
118
+ sinml[m] = (2 * b * sinml[m-1]) - sinml[m-2];
119
+ cosml[m] = (2 * b * cosml[m-1]) - cosml[m-2];
120
+ }
121
+ }
122
+
123
+ /*!
124
+ * \param m: order.
125
+ * \param theta: Colatitude (in radians).
126
+ * \param rleg: Normalized legendre function.
127
+ *
128
+ * This subroutine computes all normalized legendre function in 'rleg'.
129
+ * The dimensions of array 'rleg' must be at least equal to nmax+1.
130
+ * All calculations are in double precision.
131
+ *
132
+ * Original programmer: Oscar L. Colombo, Dept. of Geodetic Science the Ohio State University, August 1980.
133
+ * ineiev: I removed the derivatives, for they are never computed here.
134
+ */
135
+ void legfdn(const unsigned m, const double theta, double rleg[_361+1])
136
+ {
137
+ static thread_local double drts[1301], dirt[1301];
138
+ static thread_local int ir = 0;
139
+
140
+ const unsigned nmax1 = _nmax + 1;
141
+ const unsigned nmax2p = (2 * _nmax) + 1;
142
+ const unsigned m1 = m + 1;
143
+ const unsigned m2 = m + 2;
144
+ const unsigned m3 = m + 3;
145
+
146
+ unsigned n, n1, n2;
147
+
148
+ if (ir == 0)
149
+ {
150
+ ir = 1;
151
+ for (n = 1; n <= nmax2p; n++)
152
+ {
153
+ drts[n] = sqrt(n);
154
+ dirt[n] = 1 / drts[n];
155
+ }
156
+ }
157
+
158
+ const double cothet = cos(theta);
159
+ const double sithet = sin(theta);
160
+
161
+ // compute the legendre functions
162
+ double rlnn[_361+1];
163
+ rlnn[1] = 1;
164
+ rlnn[2] = sithet * drts[3];
165
+ for (n1 = 3; n1 <= m1; n1++)
166
+ {
167
+ n = n1 - 1;
168
+ n2 = 2 * n;
169
+ rlnn[n1] = drts[n2 + 1] * dirt[n2] * sithet * rlnn[n];
170
+ }
171
+
172
+ switch (m)
173
+ {
174
+ case 1:
175
+ rleg[2] = rlnn[2];
176
+ rleg[3] = drts[5] * cothet * rleg[2];
177
+ break;
178
+ case 0:
179
+ rleg[1] = 1;
180
+ rleg[2] = cothet * drts[3];
181
+ break;
182
+ }
183
+ rleg[m1] = rlnn[m1];
184
+
185
+ if (m2 <= nmax1)
186
+ {
187
+ rleg[m2] = drts[(2 * m1) + 1] * cothet * rleg[m1];
188
+ if (m3 <= nmax1)
189
+ {
190
+ for (n1 = m3; n1 <= nmax1; n1++)
191
+ {
192
+ n = n1 - 1;
193
+ if ((!m && n < 2) || (m == 1 && n < 3)) continue;
194
+ n2 = 2 * n;
195
+ rleg[n1] = drts[n2+1] * dirt[n+m] * dirt[n-m] * ((drts[n2-1] * cothet * rleg[n1-1]) - (drts[n+m-1] * drts[n-m-1] * dirt[n2-3] * rleg[n1-2]));
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ /*!
202
+ * \param lat: Latitude (in radians).
203
+ * \param lon: Longitude (in radians).
204
+ * \param re: Geocentric radius.
205
+ * \param rlat: Geocentric latitude.
206
+ * \param gr: Normal gravity (m/sec²).
207
+ *
208
+ * This subroutine computes geocentric distance to the point, the geocentric
209
+ * latitude, and an approximate value of normal gravity at the point based the
210
+ * constants of the WGS84(g873) system are used.
211
+ */
212
+ void radgra(const double lat, const double lon, double *rlat, double *gr, double *re)
213
+ {
214
+ const double a = 6378137.0;
215
+ const double e2 = 0.00669437999013;
216
+ const double geqt = 9.7803253359;
217
+ const double k = 0.00193185265246;
218
+ const double t1 = sin(lat) * sin(lat);
219
+ const double n = a / sqrt(1.0 - (e2 * t1));
220
+ const double t2 = n * cos(lat);
221
+ const double x = t2 * cos(lon);
222
+ const double y = t2 * sin(lon);
223
+ const double z = (n * (1 - e2)) * sin(lat);
224
+
225
+ *re = sqrt((x * x) + (y * y) + (z * z)); // compute the geocentric radius
226
+ *rlat = atan(z / sqrt((x * x) + (y * y))); // compute the geocentric latitude
227
+ *gr = geqt * (1 + (k * t1)) / sqrt(1 - (e2 * t1)); // compute normal gravity (m/sec²)
228
+ }
229
+
230
+ /*!
231
+ * \brief Compute the geoid undulation from the EGM96 potential coefficient model, for a given latitude and longitude.
232
+ * \param lat: Latitude (in radians).
233
+ * \param lon: Longitude (in radians).
234
+ * \return The geoid undulation / altitude offset (in meters).
235
+ */
236
+ double undulation(const double lat, const double lon)
237
+ {
238
+ // Allocate large arrays on heap instead of stack to avoid stack overflow
239
+ // on threads with limited stack size (e.g., iOS DispatchQueue threads)
240
+ double* p = (double*)malloc((_coeffs+1) * sizeof(double));
241
+ double* sinml = (double*)malloc((_361+1) * sizeof(double));
242
+ double* cosml = (double*)malloc((_361+1) * sizeof(double));
243
+ double* rleg = (double*)malloc((_361+1) * sizeof(double));
244
+
245
+ if (!p || !sinml || !cosml || !rleg) {
246
+ free(p);
247
+ free(sinml);
248
+ free(cosml);
249
+ free(rleg);
250
+ return 0.0;
251
+ }
252
+
253
+ double rlat, gr, re;
254
+
255
+ // compute the geocentric latitude, geocentric radius, normal gravity
256
+ radgra(lat, lon, &rlat, &gr, &re);
257
+ rlat = (M_PI / 2) - rlat;
258
+
259
+ const unsigned nmax1 = _nmax + 1;
260
+ for (unsigned j = 1; j <= nmax1; j++)
261
+ {
262
+ unsigned m = j - 1;
263
+ legfdn(m, rlat, rleg);
264
+ for (unsigned i = j ; i <= nmax1; i++)
265
+ {
266
+ p[(((i - 1) * i) / 2) + m + 1] = rleg[i];
267
+ }
268
+ }
269
+ dscml(lon, sinml, cosml);
270
+
271
+ double result = hundu(p, sinml, cosml, gr, re);
272
+
273
+ free(p);
274
+ free(sinml);
275
+ free(cosml);
276
+ free(rleg);
277
+
278
+ return result;
279
+ }
280
+
281
+ /* ************************************************************************** */
282
+
283
+ double egm96_compute_altitude_offset(const double latitude, const double longitude)
284
+ {
285
+ const double rad = (180.0 / M_PI);
286
+ return undulation(latitude / rad, longitude / rad);
287
+ }
288
+
289
+ /* ************************************************************************** */
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright (c) 2006 D.Ineiev <ineiev@yahoo.co.uk>
3
+ * Copyright (c) 2020 Emeric Grange <emeric.grange@gmail.com>
4
+ *
5
+ * This software is provided 'as-is', without any express or implied warranty.
6
+ * In no event will the authors be held liable for any damages arising from
7
+ * the use of this software.
8
+ *
9
+ * Permission is granted to anyone to use this software for any purpose,
10
+ * including commercial applications, and to alter it and redistribute it
11
+ * freely, subject to the following restrictions:
12
+ *
13
+ * 1. The origin of this software must not be misrepresented; you must not
14
+ * claim that you wrote the original software. If you use this software
15
+ * in a product, an acknowledgment in the product documentation would be
16
+ * appreciated but is not required.
17
+ * 2. Altered source versions must be plainly marked as such, and must not be
18
+ * misrepresented as being the original software.
19
+ * 3. This notice may not be removed or altered from any source distribution.
20
+ */
21
+
22
+ #ifndef EGM96_H
23
+ #define EGM96_H
24
+
25
+ #ifdef __cplusplus
26
+ extern "C" {
27
+ #endif
28
+
29
+ /* ************************************************************************** */
30
+
31
+ /*!
32
+ * \brief Compute the geoid undulation from the EGM96 potential coefficient model, for a given latitude and longitude.
33
+ * \param latitude: Latitude (in degrees).
34
+ * \param longitude: Longitude (in degrees).
35
+ * \return The geoid undulation / altitude offset (in meters).
36
+ */
37
+ double egm96_compute_altitude_offset(double latitude, double longitude);
38
+
39
+ /* ************************************************************************** */
40
+
41
+ #ifdef __cplusplus
42
+ }
43
+ #endif
44
+
45
+ #endif // EGM96_H