expo-gaode-map-navigation 1.1.5-next.3 → 1.1.5-next.4

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.
@@ -1,11 +1,11 @@
1
1
  cmake_minimum_required(VERSION 3.22.1)
2
2
 
3
- project(gaodecluster)
3
+ project(gaodecluster_nav)
4
4
 
5
5
  set(CMAKE_CXX_STANDARD 17)
6
6
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
7
7
 
8
- add_library(gaodecluster SHARED
8
+ add_library(gaodecluster_nav SHARED
9
9
  cluster_jni.cpp
10
10
  ../../../../shared/cpp/ClusterEngine.cpp
11
11
  ../../../../shared/cpp/QuadTree.cpp
@@ -13,12 +13,12 @@ add_library(gaodecluster SHARED
13
13
  ../../../../shared/cpp/ColorParser.cpp
14
14
  )
15
15
 
16
- target_include_directories(gaodecluster PRIVATE
16
+ target_include_directories(gaodecluster_nav PRIVATE
17
17
  ../../../../shared/cpp
18
18
  )
19
19
 
20
20
  find_library(log-lib log)
21
21
 
22
- target_link_libraries(gaodecluster
22
+ target_link_libraries(gaodecluster_nav
23
23
  ${log-lib}
24
24
  )
@@ -2,7 +2,7 @@ package expo.modules.gaodemap.map.utils
2
2
 
3
3
  object ClusterNative {
4
4
  init {
5
- System.loadLibrary("gaodecluster")
5
+ System.loadLibrary("gaodecluster_nav")
6
6
  }
7
7
 
8
8
  external fun clusterPoints(
@@ -6,7 +6,7 @@ import androidx.core.graphics.toColorInt
6
6
  object ColorParser {
7
7
  init {
8
8
  try {
9
- System.loadLibrary("gaodecluster")
9
+ System.loadLibrary("gaodecluster_nav")
10
10
  } catch (_: Throwable) {
11
11
  // Ignore if already loaded
12
12
  }
@@ -12,7 +12,7 @@ import kotlin.math.*
12
12
  object GeometryUtils {
13
13
 
14
14
  init {
15
- System.loadLibrary("gaodecluster")
15
+ System.loadLibrary("gaodecluster_nav")
16
16
  }
17
17
 
18
18
  private external fun nativeIsPointInCircle(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-gaode-map-navigation",
3
- "version": "1.1.5-next.3",
3
+ "version": "1.1.5-next.4",
4
4
  "description": "高德地图导航功能模块 - 路径规划、导航引导,独立版本包含完整地图功能",
5
5
  "author": "<TomWq> (https://github.com/TomWq)",
6
6
  "repository": "https://github.com/TomWq/expo-gaode-map",
@@ -36,6 +36,7 @@
36
36
  "build",
37
37
  "android",
38
38
  "ios",
39
+ "shared",
39
40
  "app.plugin.js",
40
41
  "expo-module.config.json",
41
42
  "plugin/build"
@@ -0,0 +1,110 @@
1
+ #include "ClusterEngine.hpp"
2
+ #include "QuadTree.hpp"
3
+ #include <cmath>
4
+ #include <algorithm>
5
+
6
+ namespace gaodemap {
7
+
8
+ // Removed duplicate toRadians definition, using inline or shared utility in future
9
+ // For now, let's keep it static but rename to avoid conflict in unity build
10
+ static inline double cluster_toRadians(double degrees) {
11
+ return degrees * 0.017453292519943295;
12
+ }
13
+
14
+ static double haversineMeters(const ClusterPoint& a, const ClusterPoint& b) {
15
+ const double lat1 = cluster_toRadians(a.lat);
16
+ const double lat2 = cluster_toRadians(b.lat);
17
+ const double dLat = lat2 - lat1;
18
+ const double dLon = cluster_toRadians(b.lon - a.lon);
19
+
20
+ const double sinHalfLat = std::sin(dLat * 0.5);
21
+ const double sinHalfLon = std::sin(dLon * 0.5);
22
+
23
+ const double h = sinHalfLat * sinHalfLat + std::cos(lat1) * std::cos(lat2) * sinHalfLon * sinHalfLon;
24
+ const double c = 2.0 * std::atan2(std::sqrt(h), std::sqrt(1.0 - h));
25
+
26
+ return 6371000.0 * c;
27
+ }
28
+
29
+ std::vector<ClusterOutput> clusterPoints(const std::vector<ClusterPoint>& points, double radiusMeters) {
30
+ std::vector<ClusterOutput> clusters;
31
+
32
+ if (points.empty() || radiusMeters <= 0.0) {
33
+ return clusters;
34
+ }
35
+
36
+ // 1. Build QuadTree
37
+ // Determine bounds
38
+ double minLat = 90.0, maxLat = -90.0, minLon = 180.0, maxLon = -180.0;
39
+ int maxIndex = -1;
40
+
41
+ for (const auto& p : points) {
42
+ if (p.lat < minLat) minLat = p.lat;
43
+ if (p.lat > maxLat) maxLat = p.lat;
44
+ if (p.lon < minLon) minLon = p.lon;
45
+ if (p.lon > maxLon) maxLon = p.lon;
46
+ if (p.index > maxIndex) maxIndex = p.index;
47
+ }
48
+
49
+ // Add some buffer
50
+ BoundingBox worldBounds{minLat - 1.0, minLon - 1.0, maxLat + 1.0, maxLon + 1.0};
51
+ QuadTree tree(worldBounds);
52
+
53
+ for (const auto& p : points) {
54
+ tree.insert(p);
55
+ }
56
+
57
+ // 2. Cluster
58
+ // Use maxIndex to size the visited array correctly.
59
+ // If points is empty, maxIndex is -1, but we checked empty above.
60
+ // Ensure vector size is at least maxIndex + 1
61
+ std::vector<bool> globalVisited((maxIndex >= 0 ? maxIndex + 1 : 0), false);
62
+
63
+ // Approximate degrees for radius (very rough, improves performance)
64
+ // 1 degree lat ~= 111km.
65
+ double latDegree = radiusMeters / 111000.0;
66
+
67
+ for (const auto& p : points) {
68
+ if (p.index < 0 || p.index >= globalVisited.size()) continue; // Safety check
69
+ if (globalVisited[p.index]) continue;
70
+
71
+ ClusterOutput cluster;
72
+ cluster.centerIndex = p.index;
73
+ cluster.indices.push_back(p.index);
74
+ globalVisited[p.index] = true;
75
+
76
+ // Query QuadTree
77
+ // Adjust lonDegree based on current latitude
78
+ // cos can be 0 at poles, handle it
79
+ double cosLat = std::cos(cluster_toRadians(p.lat));
80
+ double lonDegree;
81
+ if (std::abs(cosLat) < 0.00001) {
82
+ lonDegree = 360.0; // At poles, everything is close in longitude
83
+ } else {
84
+ lonDegree = radiusMeters / (111000.0 * std::abs(cosLat));
85
+ }
86
+
87
+ // Bounding box for query
88
+ BoundingBox range{p.lat - latDegree, p.lon - lonDegree, p.lat + latDegree, p.lon + lonDegree};
89
+
90
+ std::vector<ClusterPoint> neighbors;
91
+ tree.query(range, neighbors);
92
+
93
+ for (const auto& neighbor : neighbors) {
94
+ if (neighbor.index < 0 || neighbor.index >= globalVisited.size()) continue;
95
+ if (globalVisited[neighbor.index]) continue;
96
+
97
+ // Precise check
98
+ if (haversineMeters(p, neighbor) <= radiusMeters) {
99
+ cluster.indices.push_back(neighbor.index);
100
+ globalVisited[neighbor.index] = true;
101
+ }
102
+ }
103
+
104
+ clusters.push_back(std::move(cluster));
105
+ }
106
+
107
+ return clusters;
108
+ }
109
+
110
+ }
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+
5
+ namespace gaodemap {
6
+
7
+ struct ClusterPoint {
8
+ double lat;
9
+ double lon;
10
+ int index;
11
+ };
12
+
13
+ struct ClusterOutput {
14
+ int centerIndex;
15
+ std::vector<int> indices;
16
+ };
17
+
18
+ std::vector<ClusterOutput> clusterPoints(const std::vector<ClusterPoint>& points, double radiusMeters);
19
+
20
+ }
@@ -0,0 +1,135 @@
1
+ #include "ColorParser.hpp"
2
+ #include <algorithm>
3
+ #include <sstream>
4
+ #include <vector>
5
+ #include <map>
6
+ #include <cmath>
7
+
8
+ namespace gaodemap {
9
+
10
+ static uint32_t argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) {
11
+ return (static_cast<uint32_t>(a) << 24) |
12
+ (static_cast<uint32_t>(r) << 16) |
13
+ (static_cast<uint32_t>(g) << 8) |
14
+ static_cast<uint32_t>(b);
15
+ }
16
+
17
+ static uint32_t parseHex(const std::string& hexStr) {
18
+ std::string cleanHex = hexStr;
19
+ if (cleanHex.empty()) return 0;
20
+ if (cleanHex[0] == '#') {
21
+ cleanHex = cleanHex.substr(1);
22
+ }
23
+
24
+ uint32_t val = 0;
25
+ try {
26
+ val = static_cast<uint32_t>(std::stoul(cleanHex, nullptr, 16));
27
+ } catch (...) {
28
+ return 0;
29
+ }
30
+
31
+ if (cleanHex.length() == 6) {
32
+ // RRGGBB -> 0xFFRRGGBB
33
+ return 0xFF000000 | val;
34
+ } else if (cleanHex.length() == 8) {
35
+ // AARRGGBB -> AARRGGBB (Android style)
36
+ // Wait, web usually uses #RRGGBBAA.
37
+ // Android Color.parseColor("#RRGGBB") or "#AARRGGBB".
38
+ // Let's assume Android style #AARRGGBB for consistency with existing Kotlin code.
39
+ return val;
40
+ } else if (cleanHex.length() == 3) {
41
+ // RGB -> RRGGBB
42
+ uint32_t r = (val >> 8) & 0xF;
43
+ uint32_t g = (val >> 4) & 0xF;
44
+ uint32_t b = val & 0xF;
45
+ return 0xFF000000 | (r << 20) | (r << 16) | (g << 12) | (g << 8) | (b << 4) | b;
46
+ } else if (cleanHex.length() == 4) {
47
+ // ARGB -> AARRGGBB
48
+ uint32_t a = (val >> 12) & 0xF;
49
+ uint32_t r = (val >> 8) & 0xF;
50
+ uint32_t g = (val >> 4) & 0xF;
51
+ uint32_t b = val & 0xF;
52
+ return (a << 28) | (a << 24) | (r << 20) | (r << 16) | (g << 12) | (g << 8) | (b << 4) | b;
53
+ }
54
+
55
+ return 0;
56
+ }
57
+
58
+ static uint32_t parseRgba(const std::string& str) {
59
+ // rgba(r, g, b, a) or rgb(r, g, b)
60
+ bool hasAlpha = str.find("rgba") == 0;
61
+ size_t start = str.find('(');
62
+ size_t end = str.find(')');
63
+ if (start == std::string::npos || end == std::string::npos) return 0;
64
+
65
+ std::string content = str.substr(start + 1, end - start - 1);
66
+ std::stringstream ss(content);
67
+ std::string segment;
68
+ std::vector<std::string> parts;
69
+
70
+ while (std::getline(ss, segment, ',')) {
71
+ parts.push_back(segment);
72
+ }
73
+
74
+ if (parts.size() < 3) return 0;
75
+
76
+ try {
77
+ int r = std::stoi(parts[0]);
78
+ int g = std::stoi(parts[1]);
79
+ int b = std::stoi(parts[2]);
80
+ int a = 255;
81
+
82
+ if (hasAlpha && parts.size() >= 4) {
83
+ float alphaFloat = std::stof(parts[3]);
84
+ a = static_cast<int>(alphaFloat * 255.0f);
85
+ }
86
+
87
+ return argb(
88
+ static_cast<uint8_t>(std::max(0, std::min(255, a))),
89
+ static_cast<uint8_t>(std::max(0, std::min(255, r))),
90
+ static_cast<uint8_t>(std::max(0, std::min(255, g))),
91
+ static_cast<uint8_t>(std::max(0, std::min(255, b)))
92
+ );
93
+ } catch (...) {
94
+ return 0;
95
+ }
96
+ }
97
+
98
+ static const std::map<std::string, uint32_t> NAMED_COLORS = {
99
+ {"red", 0xFFFF0000},
100
+ {"blue", 0xFF0000FF},
101
+ {"green", 0xFF00FF00},
102
+ {"yellow", 0xFFFFFF00},
103
+ {"black", 0xFF000000},
104
+ {"white", 0xFFFFFFFF},
105
+ {"gray", 0xFF888888},
106
+ {"grey", 0xFF888888},
107
+ {"cyan", 0xFF00FFFF},
108
+ {"magenta", 0xFFFF00FF},
109
+ {"transparent", 0x00000000}
110
+ };
111
+
112
+ uint32_t parseColor(const std::string& colorString) {
113
+ std::string str = colorString;
114
+ // Remove whitespace
115
+ str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());
116
+ // Lowercase for named colors check
117
+ std::string lowerStr = str;
118
+ std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower);
119
+
120
+ if (NAMED_COLORS.count(lowerStr)) {
121
+ return NAMED_COLORS.at(lowerStr);
122
+ }
123
+
124
+ if (lowerStr.find("rgb") == 0) {
125
+ return parseRgba(str); // pass original str with potential spaces if needed, but we removed them
126
+ }
127
+
128
+ if (lowerStr.find("#") == 0 || std::all_of(lowerStr.begin(), lowerStr.end(), ::isxdigit)) {
129
+ return parseHex(str);
130
+ }
131
+
132
+ return 0; // Default black -> Default failure (0)
133
+ }
134
+
135
+ }
@@ -0,0 +1,14 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <cstdint>
5
+
6
+ namespace gaodemap {
7
+
8
+ // Returns color as ARGB integer (0xAARRGGBB)
9
+ // Returns 0 (transparent/black) if parsing fails, but 0 is also valid (transparent).
10
+ // Maybe return a bool success? Or default to Black (0xFF000000) or Transparent (0x00000000).
11
+ // Android defaults to Black in existing code.
12
+ uint32_t parseColor(const std::string& colorString);
13
+
14
+ }
@@ -0,0 +1,574 @@
1
+ #include "GeometryEngine.hpp"
2
+
3
+ #include <cmath>
4
+ #include <map>
5
+ #include <algorithm>
6
+
7
+ namespace gaodemap {
8
+
9
+ static constexpr double kEarthRadiusMeters = 6371000.0;
10
+ static constexpr double kPi = 3.14159265358979323846;
11
+ static constexpr double kDegreesToRadians = kPi / 180.0;
12
+ static constexpr double kRadiansToDegrees = 180.0 / kPi;
13
+
14
+ static inline double geo_toRadians(double degrees) {
15
+ return degrees * kDegreesToRadians;
16
+ }
17
+
18
+ static inline double geo_toDegrees(double radians) {
19
+ return radians * kRadiansToDegrees;
20
+ }
21
+
22
+ double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
23
+ const double radLat1 = geo_toRadians(lat1);
24
+ const double radLat2 = geo_toRadians(lat2);
25
+ const double dLat = radLat2 - radLat1;
26
+ const double dLon = geo_toRadians(lon2 - lon1);
27
+
28
+ const double sinHalfLat = std::sin(dLat * 0.5);
29
+ const double sinHalfLon = std::sin(dLon * 0.5);
30
+ const double h = sinHalfLat * sinHalfLat + std::cos(radLat1) * std::cos(radLat2) * sinHalfLon * sinHalfLon;
31
+ const double c = 2.0 * std::atan2(std::sqrt(h), std::sqrt(1.0 - h));
32
+
33
+ return kEarthRadiusMeters * c;
34
+ }
35
+
36
+ static double haversineMeters(double lat1, double lon1, double lat2, double lon2) {
37
+ return calculateDistance(lat1, lon1, lat2, lon2);
38
+ }
39
+
40
+ // 计算方位角 (Bearing)
41
+ static double calculateBearing(double lat1, double lon1, double lat2, double lon2) {
42
+ double phi1 = geo_toRadians(lat1);
43
+ double phi2 = geo_toRadians(lat2);
44
+ double lam1 = geo_toRadians(lon1);
45
+ double lam2 = geo_toRadians(lon2);
46
+
47
+ double y = std::sin(lam2 - lam1) * std::cos(phi2);
48
+ double x = std::cos(phi1) * std::sin(phi2) -
49
+ std::sin(phi1) * std::cos(phi2) * std::cos(lam2 - lam1);
50
+ double theta = std::atan2(y, x);
51
+
52
+ return std::fmod(geo_toDegrees(theta) + 360.0, 360.0);
53
+ }
54
+
55
+ bool isPointInCircle(double pointLat, double pointLon, double centerLat, double centerLon, double radiusMeters) {
56
+ if (radiusMeters <= 0.0) {
57
+ return false;
58
+ }
59
+ const double distance = haversineMeters(pointLat, pointLon, centerLat, centerLon);
60
+ return distance <= radiusMeters;
61
+ }
62
+
63
+ bool isPointInPolygon(double pointLat, double pointLon, const std::vector<GeoPoint>& polygon) {
64
+ const size_t n = polygon.size();
65
+ if (n < 3) {
66
+ return false;
67
+ }
68
+
69
+ bool inside = false;
70
+ size_t j = n - 1;
71
+ for (size_t i = 0; i < n; ++i) {
72
+ const double xi = polygon[i].lat;
73
+ const double yi = polygon[i].lon;
74
+ const double xj = polygon[j].lat;
75
+ const double yj = polygon[j].lon;
76
+
77
+ const double intersect = ((yi > pointLon) != (yj > pointLon)) &&
78
+ (pointLat < (xj - xi) * (pointLon - yi) / (yj - yi) + xi);
79
+ if (intersect) {
80
+ inside = !inside;
81
+ }
82
+ j = i;
83
+ }
84
+
85
+ return inside;
86
+ }
87
+
88
+ double calculatePolygonArea(const std::vector<GeoPoint>& polygon) {
89
+ const size_t n = polygon.size();
90
+ if (n < 3) {
91
+ return 0.0;
92
+ }
93
+
94
+ double total = 0.0;
95
+ for (size_t i = 0; i < n; ++i) {
96
+ const GeoPoint& p1 = polygon[i];
97
+ const GeoPoint& p2 = polygon[(i + 1) % n];
98
+
99
+ const double lat1 = geo_toRadians(p1.lat);
100
+ const double lat2 = geo_toRadians(p2.lat);
101
+ const double lon1 = geo_toRadians(p1.lon);
102
+ const double lon2 = geo_toRadians(p2.lon);
103
+
104
+ total += (lon2 - lon1) * (2.0 + std::sin(lat1) + std::sin(lat2));
105
+ }
106
+
107
+ const double area = std::abs(total) * (kEarthRadiusMeters * kEarthRadiusMeters) * 0.5;
108
+ return area;
109
+ }
110
+
111
+ double calculateRectangleArea(double swLat, double swLon, double neLat, double neLon) {
112
+ std::vector<GeoPoint> rectangle;
113
+ rectangle.reserve(4);
114
+ rectangle.push_back({swLat, swLon});
115
+ rectangle.push_back({swLat, neLon});
116
+ rectangle.push_back({neLat, neLon});
117
+ rectangle.push_back({neLat, swLon});
118
+ return calculatePolygonArea(rectangle);
119
+ }
120
+
121
+ // 简单的 2D 点结构,用于本地坐标投影计算
122
+ struct Point2D {
123
+ double x;
124
+ double y;
125
+ size_t index; // 原始索引
126
+ };
127
+
128
+ // 计算点到线段的垂直距离的平方
129
+ static double getSqSegDist(const Point2D& p, const Point2D& p1, const Point2D& p2) {
130
+ double x = p1.x;
131
+ double y = p1.y;
132
+ double dx = p2.x - x;
133
+ double dy = p2.y - y;
134
+
135
+ if (dx != 0 || dy != 0) {
136
+ double t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
137
+ if (t > 1) {
138
+ x = p2.x;
139
+ y = p2.y;
140
+ } else if (t > 0) {
141
+ x += dx * t;
142
+ y += dy * t;
143
+ }
144
+ }
145
+
146
+ dx = p.x - x;
147
+ dy = p.y - y;
148
+
149
+ return dx * dx + dy * dy;
150
+ }
151
+
152
+ // RDP 递归函数
153
+ static void simplifyDPStep(const std::vector<Point2D>& points, size_t first, size_t last, double sqTolerance, std::vector<size_t>& simplified) {
154
+ double maxSqDist = sqTolerance;
155
+ size_t index = 0;
156
+
157
+ for (size_t i = first + 1; i < last; i++) {
158
+ double sqDist = getSqSegDist(points[i], points[first], points[last]);
159
+ if (sqDist > maxSqDist) {
160
+ index = i;
161
+ maxSqDist = sqDist;
162
+ }
163
+ }
164
+
165
+ if (maxSqDist > sqTolerance) {
166
+ if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
167
+ simplified.push_back(index);
168
+ if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
169
+ }
170
+ }
171
+
172
+ std::vector<GeoPoint> simplifyPolyline(const std::vector<GeoPoint>& points, double toleranceMeters) {
173
+ if (points.size() <= 2) {
174
+ return points;
175
+ }
176
+
177
+ // 1. 投影到平面坐标 (Equirectangular Projection approximation)
178
+ // 以第一个点为原点
179
+ double refLat = points[0].lat;
180
+ double refLon = points[0].lon;
181
+ double refLatRad = geo_toRadians(refLat);
182
+ double metersPerDegreeLat = 111319.9;
183
+ double metersPerDegreeLon = 111319.9 * std::cos(refLatRad);
184
+
185
+ std::vector<Point2D> projectedPoints;
186
+ projectedPoints.reserve(points.size());
187
+ for (size_t i = 0; i < points.size(); ++i) {
188
+ double x = (points[i].lon - refLon) * metersPerDegreeLon;
189
+ double y = (points[i].lat - refLat) * metersPerDegreeLat;
190
+ projectedPoints.push_back({x, y, i});
191
+ }
192
+
193
+ // 2. 运行 RDP 算法
194
+ double sqTolerance = toleranceMeters * toleranceMeters;
195
+ std::vector<size_t> simplifiedIndices;
196
+ simplifiedIndices.push_back(0); // 总是保留第一个点
197
+
198
+ simplifyDPStep(projectedPoints, 0, projectedPoints.size() - 1, sqTolerance, simplifiedIndices);
199
+
200
+ simplifiedIndices.push_back(projectedPoints.size() - 1); // 总是保留最后一个点
201
+
202
+ // 3. 构建结果
203
+ std::vector<GeoPoint> result;
204
+ result.reserve(simplifiedIndices.size());
205
+
206
+ for (size_t idx : simplifiedIndices) {
207
+ result.push_back(points[idx]);
208
+ }
209
+
210
+ return result;
211
+ }
212
+
213
+ double calculatePathLength(const std::vector<GeoPoint>& points) {
214
+ if (points.size() < 2) return 0.0;
215
+
216
+ double total = 0.0;
217
+ for (size_t i = 0; i < points.size() - 1; ++i) {
218
+ total += calculateDistance(points[i].lat, points[i].lon, points[i+1].lat, points[i+1].lon);
219
+ }
220
+ return total;
221
+ }
222
+
223
+ bool getPointAtDistance(const std::vector<GeoPoint>& points, double distanceMeters, double* outLat, double* outLon, double* outAngle) {
224
+ if (points.size() < 2 || distanceMeters < 0) return false;
225
+
226
+ if (distanceMeters == 0) {
227
+ *outLat = points[0].lat;
228
+ *outLon = points[0].lon;
229
+ *outAngle = calculateBearing(points[0].lat, points[0].lon, points[1].lat, points[1].lon);
230
+ return true;
231
+ }
232
+
233
+ double covered = 0.0;
234
+ for (size_t i = 0; i < points.size() - 1; ++i) {
235
+ double d = calculateDistance(points[i].lat, points[i].lon, points[i+1].lat, points[i+1].lon);
236
+ if (covered + d >= distanceMeters) {
237
+ double remaining = distanceMeters - covered;
238
+ double fraction = remaining / d;
239
+
240
+ // 简单的线性插值 (Linear Interpolation)
241
+ // 对于小距离段,这比球形插值快得多且误差可忽略
242
+ *outLat = points[i].lat + (points[i+1].lat - points[i].lat) * fraction;
243
+ *outLon = points[i].lon + (points[i+1].lon - points[i].lon) * fraction;
244
+ *outAngle = calculateBearing(points[i].lat, points[i].lon, points[i+1].lat, points[i+1].lon);
245
+ return true;
246
+ }
247
+ covered += d;
248
+ }
249
+
250
+ // 如果超出总长度,返回最后一个点
251
+ const GeoPoint& last = points.back();
252
+ const GeoPoint& prev = points[points.size() - 2];
253
+ *outLat = last.lat;
254
+ *outLon = last.lon;
255
+ *outAngle = calculateBearing(prev.lat, prev.lon, last.lat, last.lon);
256
+ return true;
257
+ }
258
+
259
+ // Helper: Square of Euclidean distance
260
+ static double distSq(double x1, double y1, double x2, double y2) {
261
+ double dx = x1 - x2;
262
+ double dy = y1 - y2;
263
+ return dx * dx + dy * dy;
264
+ }
265
+
266
+ NearestPointResult getNearestPointOnPath(const std::vector<GeoPoint>& path, const GeoPoint& target) {
267
+ NearestPointResult result = {0.0, 0.0, 0, std::numeric_limits<double>::max()};
268
+
269
+ if (path.empty()) {
270
+ return result;
271
+ }
272
+
273
+ if (path.size() == 1) {
274
+ result.latitude = path[0].lat;
275
+ result.longitude = path[0].lon;
276
+ result.index = 0;
277
+ result.distanceMeters = calculateDistance(target.lat, target.lon, path[0].lat, path[0].lon);
278
+ return result;
279
+ }
280
+
281
+ double minDistance = std::numeric_limits<double>::max();
282
+
283
+ for (size_t i = 0; i < path.size() - 1; ++i) {
284
+ double ax = path[i].lat;
285
+ double ay = path[i].lon;
286
+ double bx = path[i+1].lat;
287
+ double by = path[i+1].lon;
288
+
289
+ // Project target (px, py) onto segment AB
290
+ // Note: This treats lat/lon as cartesian for projection, which is an approximation
291
+ // but generally sufficient for "snapping" to a path.
292
+ // For distance calculation, we use Haversine.
293
+
294
+ double px = target.lat;
295
+ double py = target.lon;
296
+
297
+ double l2 = distSq(ax, ay, bx, by);
298
+ double t = 0.0;
299
+ if (l2 > 0) {
300
+ t = ((px - ax) * (bx - ax) + (py - ay) * (by - ay)) / l2;
301
+ if (t < 0) t = 0;
302
+ else if (t > 1) t = 1;
303
+ }
304
+
305
+ double projLat = ax + t * (bx - ax);
306
+ double projLon = ay + t * (by - ay);
307
+
308
+ double dist = calculateDistance(target.lat, target.lon, projLat, projLon);
309
+
310
+ if (dist < minDistance) {
311
+ minDistance = dist;
312
+ result.latitude = projLat;
313
+ result.longitude = projLon;
314
+ result.index = static_cast<int>(i);
315
+ result.distanceMeters = dist;
316
+ }
317
+ }
318
+
319
+ return result;
320
+ }
321
+
322
+ GeoPoint calculateCentroid(const std::vector<GeoPoint>& polygon) {
323
+ if (polygon.empty()) {
324
+ return {0.0, 0.0};
325
+ }
326
+
327
+ // 简单多边形质心公式
328
+ // Cx = (1/6A) * Σ (xi + xi+1) * (xi * yi+1 - xi+1 * yi)
329
+ // Cy = (1/6A) * Σ (yi + yi+1) * (xi * yi+1 - xi+1 * yi)
330
+
331
+ double signedArea = 0.0;
332
+ double cx = 0.0;
333
+ double cy = 0.0;
334
+
335
+ size_t n = polygon.size();
336
+ // 确保多边形闭合
337
+ bool closed = (polygon[0].lat == polygon[n-1].lat && polygon[0].lon == polygon[n-1].lon);
338
+ size_t limit = closed ? n - 1 : n;
339
+
340
+ for (size_t i = 0; i < limit; ++i) {
341
+ double x0 = polygon[i].lat;
342
+ double y0 = polygon[i].lon;
343
+ double x1 = polygon[(i + 1) % n].lat;
344
+ double y1 = polygon[(i + 1) % n].lon;
345
+
346
+ double a = x0 * y1 - x1 * y0;
347
+ signedArea += a;
348
+ cx += (x0 + x1) * a;
349
+ cy += (y0 + y1) * a;
350
+ }
351
+
352
+ if (std::abs(signedArea) < 1e-9) {
353
+ // 退化为计算所有点的平均值
354
+ double sumLat = 0.0;
355
+ double sumLon = 0.0;
356
+ for(const auto& p : polygon) {
357
+ sumLat += p.lat;
358
+ sumLon += p.lon;
359
+ }
360
+ return {sumLat / n, sumLon / n};
361
+ }
362
+
363
+ signedArea *= 0.5;
364
+ cx /= (6.0 * signedArea);
365
+ cy /= (6.0 * signedArea);
366
+
367
+ return {cx, cy};
368
+ }
369
+
370
+ std::string encodeGeoHash(double lat, double lon, int precision) {
371
+ if (precision < 1) precision = 1;
372
+ if (precision > 12) precision = 12;
373
+
374
+ static const char BASE32[] = "0123456789bcdefghjkmnpqrstuvwxyz";
375
+ std::string hash = "";
376
+
377
+ double minLat = -90.0, maxLat = 90.0;
378
+ double minLon = -180.0, maxLon = 180.0;
379
+
380
+ int bit = 0;
381
+ int ch = 0;
382
+ bool isEven = true;
383
+
384
+ while (hash.length() < precision) {
385
+ if (isEven) {
386
+ double mid = (minLon + maxLon) / 2.0;
387
+ if (lon > mid) {
388
+ ch |= (1 << (4 - bit));
389
+ minLon = mid;
390
+ } else {
391
+ maxLon = mid;
392
+ }
393
+ } else {
394
+ double mid = (minLat + maxLat) / 2.0;
395
+ if (lat > mid) {
396
+ ch |= (1 << (4 - bit));
397
+ minLat = mid;
398
+ } else {
399
+ maxLat = mid;
400
+ }
401
+ }
402
+
403
+ isEven = !isEven;
404
+
405
+ if (bit < 4) {
406
+ bit++;
407
+ } else {
408
+ hash += BASE32[ch];
409
+ bit = 0;
410
+ ch = 0;
411
+ }
412
+ }
413
+
414
+ return hash;
415
+ }
416
+
417
+ std::vector<GeoPoint> parsePolyline(const std::string& polylineStr) {
418
+ std::vector<GeoPoint> points;
419
+ if (polylineStr.empty()) {
420
+ return points;
421
+ }
422
+
423
+ size_t start = 0;
424
+ size_t end = polylineStr.find(';');
425
+
426
+ while (true) {
427
+ std::string segment = polylineStr.substr(start, end - start);
428
+ if (!segment.empty()) {
429
+ size_t comma = segment.find(',');
430
+ if (comma != std::string::npos) {
431
+ try {
432
+ double lon = std::stod(segment.substr(0, comma));
433
+ double lat = std::stod(segment.substr(comma + 1));
434
+ points.push_back({lat, lon});
435
+ } catch (...) {
436
+ // 忽略无效的坐标对
437
+ }
438
+ }
439
+ }
440
+
441
+ if (end == std::string::npos) {
442
+ break;
443
+ }
444
+
445
+ start = end + 1;
446
+ end = polylineStr.find(';', start);
447
+ }
448
+
449
+ return points;
450
+ }
451
+
452
+ PathBounds calculatePathBounds(const std::vector<GeoPoint>& points) {
453
+ PathBounds bounds = { -90.0, 90.0, -180.0, 180.0, 0.0, 0.0 };
454
+
455
+ if (points.empty()) {
456
+ return bounds;
457
+ }
458
+
459
+ double minLat = 90.0;
460
+ double maxLat = -90.0;
461
+ double minLon = 180.0;
462
+ double maxLon = -180.0;
463
+
464
+ for (const auto& p : points) {
465
+ if (p.lat < minLat) minLat = p.lat;
466
+ if (p.lat > maxLat) maxLat = p.lat;
467
+ if (p.lon < minLon) minLon = p.lon;
468
+ if (p.lon > maxLon) maxLon = p.lon;
469
+ }
470
+
471
+ bounds.north = maxLat;
472
+ bounds.south = minLat;
473
+ bounds.east = maxLon;
474
+ bounds.west = minLon;
475
+ bounds.centerLat = (maxLat + minLat) / 2.0;
476
+ bounds.centerLon = (maxLon + minLon) / 2.0;
477
+
478
+ return bounds;
479
+ }
480
+
481
+ // --- 瓦片与坐标转换 ---
482
+
483
+ TileResult latLngToTile(double lat, double lon, int zoom) {
484
+ double n = std::pow(2.0, zoom);
485
+ int x = static_cast<int>((lon + 180.0) / 360.0 * n);
486
+ double latRad = geo_toRadians(lat);
487
+ int y = static_cast<int>((1.0 - std::asinh(std::tan(latRad)) / kPi) / 2.0 * n);
488
+ return {x, y, zoom};
489
+ }
490
+
491
+ GeoPoint tileToLatLng(int x, int y, int zoom) {
492
+ double n = std::pow(2.0, zoom);
493
+ double lon = static_cast<double>(x) / n * 360.0 - 180.0;
494
+ double latRad = std::atan(std::sinh(kPi * (1.0 - 2.0 * static_cast<double>(y) / n)));
495
+ double lat = latRad * kRadiansToDegrees;
496
+ return {lat, lon};
497
+ }
498
+
499
+ PixelResult latLngToPixel(double lat, double lon, int zoom) {
500
+ double n = std::pow(2.0, zoom) * 256.0; // 假设瓦片大小为 256x256
501
+ double x = (lon + 180.0) / 360.0 * n;
502
+ double latRad = geo_toRadians(lat);
503
+ double y = (1.0 - std::asinh(std::tan(latRad)) / kPi) / 2.0 * n;
504
+ return {x, y};
505
+ }
506
+
507
+ GeoPoint pixelToLatLng(double x, double y, int zoom) {
508
+ double n = std::pow(2.0, zoom) * 256.0;
509
+ double lon = x / n * 360.0 - 180.0;
510
+ double latRad = std::atan(std::sinh(kPi * (1.0 - 2.0 * y / n)));
511
+ double lat = latRad * kRadiansToDegrees;
512
+ return {lat, lon};
513
+ }
514
+
515
+ // --- 批量地理围栏与热力图 ---
516
+
517
+ int findPointInPolygons(double pointLat, double pointLon, const std::vector<std::vector<GeoPoint>>& polygons) {
518
+ for (size_t i = 0; i < polygons.size(); ++i) {
519
+ if (isPointInPolygon(pointLat, pointLon, polygons[i])) {
520
+ return static_cast<int>(i);
521
+ }
522
+ }
523
+ return -1;
524
+ }
525
+
526
+ std::vector<HeatmapGridCell> generateHeatmapGrid(const std::vector<HeatmapPoint>& points, double gridSizeMeters) {
527
+ if (points.empty()) return {};
528
+
529
+ // 1. 计算范围
530
+ double minLat = 90.0, maxLat = -90.0, minLon = 180.0, maxLon = -180.0;
531
+ for (const auto& p : points) {
532
+ if (p.lat < minLat) minLat = p.lat;
533
+ if (p.lat > maxLat) maxLat = p.lat;
534
+ if (p.lon < minLon) minLon = p.lon;
535
+ if (p.lon > maxLon) maxLon = p.lon;
536
+ }
537
+
538
+ // 2. 将米转换为大概的经纬度步长
539
+ // (这是一个近似值,但在热力图分桶中通常足够)
540
+ double latDegreeDist = 111320.0; // 每纬度度数大约 111km
541
+ double lonDegreeDist = 111320.0 * std::cos(geo_toRadians((minLat + maxLat) / 2.0));
542
+
543
+ double latStep = gridSizeMeters / latDegreeDist;
544
+ double lonStep = gridSizeMeters / lonDegreeDist;
545
+
546
+ if (latStep <= 0 || lonStep <= 0) return {};
547
+
548
+ // 3. 将点分配到网格中
549
+ std::map<std::pair<int, int>, double> grid;
550
+ for (const auto& p : points) {
551
+ int latIdx = static_cast<int>((p.lat - minLat) / latStep);
552
+ int lonIdx = static_cast<int>((p.lon - minLon) / lonStep);
553
+ grid[{latIdx, lonIdx}] += p.weight;
554
+ }
555
+
556
+ // 4. 创建结果单元格
557
+ std::vector<HeatmapGridCell> cells;
558
+ cells.reserve(grid.size());
559
+ for (const auto& entry : grid) {
560
+ int latIdx = entry.first.first;
561
+ int lonIdx = entry.first.second;
562
+ double intensity = entry.second;
563
+
564
+ cells.push_back({
565
+ minLat + (latIdx + 0.5) * latStep,
566
+ minLon + (lonIdx + 0.5) * lonStep,
567
+ intensity
568
+ });
569
+ }
570
+
571
+ return cells;
572
+ }
573
+
574
+ } // namespace gaodemap
@@ -0,0 +1,159 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+ #include <string>
5
+
6
+ namespace gaodemap {
7
+
8
+ struct GeoPoint {
9
+ double lat;
10
+ double lon;
11
+ };
12
+
13
+ double calculateDistance(double lat1, double lon1, double lat2, double lon2);
14
+ bool isPointInCircle(double pointLat, double pointLon, double centerLat, double centerLon, double radiusMeters);
15
+ bool isPointInPolygon(double pointLat, double pointLon, const std::vector<GeoPoint>& polygon);
16
+ double calculatePolygonArea(const std::vector<GeoPoint>& polygon);
17
+ double calculateRectangleArea(double swLat, double swLon, double neLat, double neLon);
18
+
19
+ /**
20
+ * Ramer-Douglas-Peucker 轨迹抽稀算法
21
+ * @param points 原始轨迹点
22
+ * @param toleranceMeters 允许的误差(米),值越大点越少
23
+ * @return 简化后的轨迹点
24
+ */
25
+ std::vector<GeoPoint> simplifyPolyline(const std::vector<GeoPoint>& points, double toleranceMeters);
26
+
27
+ /**
28
+ * 计算路径总长度(米)
29
+ */
30
+ double calculatePathLength(const std::vector<GeoPoint>& points);
31
+
32
+ /**
33
+ * 获取路径上指定距离的点和方向
34
+ * @param points 路径点
35
+ * @param distanceMeters 距离起点的米数
36
+ * @param outLat 输出纬度
37
+ * @param outLon 输出经度
38
+ * @param outAngle 输出方向角度 (0-360)
39
+ * @return 是否成功找到点
40
+ */
41
+ bool getPointAtDistance(const std::vector<GeoPoint>& points, double distanceMeters, double* outLat, double* outLon, double* outAngle);
42
+
43
+ // Result structure for nearest point calculation
44
+ struct NearestPointResult {
45
+ double latitude;
46
+ double longitude;
47
+ int index; // Index of the point in the path that starts the segment (or the point itself)
48
+ double distanceMeters;
49
+ };
50
+
51
+ // Find the nearest point on the path to a target point
52
+ // Returns the nearest point on the polyline segments
53
+ NearestPointResult getNearestPointOnPath(const std::vector<GeoPoint>& path, const GeoPoint& target);
54
+
55
+ /**
56
+ * 计算多边形的质心
57
+ * @param polygon 多边形点集
58
+ * @return 质心坐标
59
+ */
60
+ GeoPoint calculateCentroid(const std::vector<GeoPoint>& polygon);
61
+
62
+ /**
63
+ * GeoHash 编码
64
+ * @param lat 纬度
65
+ * @param lon 经度
66
+ * @param precision 精度 (1-12)
67
+ * @return GeoHash 字符串
68
+ */
69
+ std::string encodeGeoHash(double lat, double lon, int precision);
70
+
71
+ /**
72
+ * 解析高德地图 API 返回的 Polyline 字符串
73
+ * 格式: "lng,lat;lng,lat;..."
74
+ * @param polylineStr 高德原始 polyline 字符串
75
+ * @return 解析后的点集
76
+ */
77
+ std::vector<GeoPoint> parsePolyline(const std::string& polylineStr);
78
+
79
+ struct PathBounds {
80
+ double north;
81
+ double south;
82
+ double east;
83
+ double west;
84
+ double centerLat;
85
+ double centerLon;
86
+ };
87
+
88
+ /**
89
+ * 计算路径的边界和中心点
90
+ * @param points 路径点
91
+ * @return 边界信息
92
+ */
93
+ PathBounds calculatePathBounds(const std::vector<GeoPoint>& points);
94
+
95
+ // --- 瓦片与坐标转换 ---
96
+
97
+ struct TileResult {
98
+ int x;
99
+ int y;
100
+ int z;
101
+ };
102
+
103
+ struct PixelResult {
104
+ double x;
105
+ double y;
106
+ };
107
+
108
+ /**
109
+ * 经纬度转瓦片坐标
110
+ */
111
+ TileResult latLngToTile(double lat, double lon, int zoom);
112
+
113
+ /**
114
+ * 瓦片坐标转经纬度
115
+ */
116
+ GeoPoint tileToLatLng(int x, int y, int zoom);
117
+
118
+ /**
119
+ * 经纬度转像素坐标
120
+ */
121
+ PixelResult latLngToPixel(double lat, double lon, int zoom);
122
+
123
+ /**
124
+ * 像素坐标转经纬度
125
+ */
126
+ GeoPoint pixelToLatLng(double x, double y, int zoom);
127
+
128
+ // --- 批量地理围栏与热力图 ---
129
+
130
+ /**
131
+ * 批量判断点是否在多个多边形内 (优化版)
132
+ * @param pointLat 点纬度
133
+ * @param pointLon 点经度
134
+ * @param polygons 多个多边形的集合
135
+ * @return 所在的第一个多边形的索引,若不在任何多边形内返回 -1
136
+ */
137
+ int findPointInPolygons(double pointLat, double pointLon, const std::vector<std::vector<GeoPoint>>& polygons);
138
+
139
+ struct HeatmapPoint {
140
+ double lat;
141
+ double lon;
142
+ double weight;
143
+ };
144
+
145
+ struct HeatmapGridCell {
146
+ double lat;
147
+ double lon;
148
+ double intensity;
149
+ };
150
+
151
+ /**
152
+ * 生成热力图网格数据
153
+ * @param points 原始点集
154
+ * @param gridSizeMeters 网格大小(米)
155
+ * @return 网格中心点及其强度
156
+ */
157
+ std::vector<HeatmapGridCell> generateHeatmapGrid(const std::vector<HeatmapPoint>& points, double gridSizeMeters);
158
+
159
+ } // namespace gaodemap
@@ -0,0 +1,92 @@
1
+ #include "QuadTree.hpp"
2
+
3
+ namespace gaodemap {
4
+
5
+ bool BoundingBox::contains(double lat, double lon) const {
6
+ return lat >= minLat && lat <= maxLat && lon >= minLon && lon <= maxLon;
7
+ }
8
+
9
+ bool BoundingBox::intersects(const BoundingBox& other) const {
10
+ return !(other.minLat > maxLat || other.maxLat < minLat ||
11
+ other.minLon > maxLon || other.maxLon < minLon);
12
+ }
13
+
14
+ QuadTree::QuadTree(const BoundingBox& bounds, int capacity)
15
+ : bounds(bounds), capacity(capacity) {}
16
+
17
+ bool QuadTree::insert(const ClusterPoint& point) {
18
+ if (!bounds.contains(point.lat, point.lon)) {
19
+ return false;
20
+ }
21
+
22
+ if (points.size() < capacity && !subdivided) {
23
+ points.push_back(point);
24
+ return true;
25
+ }
26
+
27
+ if (!subdivided) {
28
+ subdivide();
29
+ }
30
+
31
+ if (northWest->insert(point)) return true;
32
+ if (northEast->insert(point)) return true;
33
+ if (southWest->insert(point)) return true;
34
+ if (southEast->insert(point)) return true;
35
+
36
+ // Point might be exactly on the boundary of the QuadTree but not accepted by children?
37
+ // In our logic, children cover the full range of the parent.
38
+ // However, floating point precision might be tricky.
39
+ // As a fallback, if we are subdivided but children didn't take it (rare),
40
+ // we can keep it here or force insert.
41
+ // Given the subdivision logic, it should cover all space.
42
+ return false;
43
+ }
44
+
45
+ void QuadTree::subdivide() {
46
+ double midLat = (bounds.minLat + bounds.maxLat) / 2.0;
47
+ double midLon = (bounds.minLon + bounds.maxLon) / 2.0;
48
+
49
+ northWest = std::make_unique<QuadTree>(BoundingBox{midLat, bounds.minLon, bounds.maxLat, midLon}, capacity);
50
+ northEast = std::make_unique<QuadTree>(BoundingBox{midLat, midLon, bounds.maxLat, bounds.maxLon}, capacity);
51
+ southWest = std::make_unique<QuadTree>(BoundingBox{bounds.minLat, bounds.minLon, midLat, midLon}, capacity);
52
+ southEast = std::make_unique<QuadTree>(BoundingBox{bounds.minLat, midLon, midLat, bounds.maxLon}, capacity);
53
+
54
+ subdivided = true;
55
+
56
+ // Redistribute existing points
57
+ for (const auto& p : points) {
58
+ northWest->insert(p) || northEast->insert(p) ||
59
+ southWest->insert(p) || southEast->insert(p);
60
+ }
61
+ points.clear();
62
+ }
63
+
64
+ void QuadTree::query(const BoundingBox& range, std::vector<ClusterPoint>& found) const {
65
+ if (!bounds.intersects(range)) {
66
+ return;
67
+ }
68
+
69
+ for (const auto& p : points) {
70
+ if (range.contains(p.lat, p.lon)) {
71
+ found.push_back(p);
72
+ }
73
+ }
74
+
75
+ if (subdivided) {
76
+ northWest->query(range, found);
77
+ northEast->query(range, found);
78
+ southWest->query(range, found);
79
+ southEast->query(range, found);
80
+ }
81
+ }
82
+
83
+ void QuadTree::clear() {
84
+ points.clear();
85
+ northWest.reset();
86
+ northEast.reset();
87
+ southWest.reset();
88
+ southEast.reset();
89
+ subdivided = false;
90
+ }
91
+
92
+ }
@@ -0,0 +1,42 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+ #include <memory>
5
+ #include "ClusterEngine.hpp"
6
+
7
+ namespace gaodemap {
8
+
9
+ struct BoundingBox {
10
+ double minLat;
11
+ double minLon;
12
+ double maxLat;
13
+ double maxLon;
14
+
15
+ bool contains(double lat, double lon) const;
16
+ bool intersects(const BoundingBox& other) const;
17
+ };
18
+
19
+ class QuadTree {
20
+ public:
21
+ QuadTree(const BoundingBox& bounds, int capacity = 20);
22
+ ~QuadTree() = default;
23
+
24
+ bool insert(const ClusterPoint& point);
25
+ void query(const BoundingBox& range, std::vector<ClusterPoint>& found) const;
26
+ void clear();
27
+
28
+ private:
29
+ BoundingBox bounds;
30
+ int capacity;
31
+ std::vector<ClusterPoint> points;
32
+ bool subdivided = false;
33
+
34
+ std::unique_ptr<QuadTree> northWest;
35
+ std::unique_ptr<QuadTree> northEast;
36
+ std::unique_ptr<QuadTree> southWest;
37
+ std::unique_ptr<QuadTree> southEast;
38
+
39
+ void subdivide();
40
+ };
41
+
42
+ }
@@ -0,0 +1,55 @@
1
+ # C++ 共享核心库 (Shared Core)
2
+
3
+ 该目录包含了 `expo-gaode-map` 跨平台核心逻辑的 C++ 实现。这些代码被 Android 和 iOS 平台共享,以确保计算逻辑的一致性并提升高性能计算场景(如点聚合、几何计算)的处理速度。
4
+
5
+ ## 模块说明
6
+
7
+ ### 1. GeometryEngine (几何引擎)
8
+ [GeometryEngine.hpp](file:///Users/wangqiang/Desktop/expo-gaode-map/packages/core/shared/cpp/GeometryEngine.hpp)
9
+ 提供地理空间相关的数学计算:
10
+ - **距离计算**: 基于 Haversine 公式计算经纬度点之间的球面距离。
11
+ - **点位判断**: 判断点是否在多边形 (Point-in-Polygon) 或圆形内。
12
+ - **面积计算**: 计算多边形或矩形的地理面积。
13
+ - **轨迹处理**:
14
+ - **抽稀算法**: 实现 Ramer-Douglas-Peucker 算法,用于简化复杂的折线轨迹。
15
+ - **路径长度**: 计算折线段的总长度。
16
+ - **路径插值**: 获取路径上指定距离的点坐标及其切线方向。
17
+ - **GeoHash**: 支持经纬度到 GeoHash 字符串的编码。
18
+ - **质心计算**: 计算多边形的几何质心。
19
+
20
+ ### 2. ClusterEngine (点聚合引擎)
21
+ [ClusterEngine.hpp](file:///Users/wangqiang/Desktop/expo-gaode-map/packages/core/shared/cpp/ClusterEngine.hpp)
22
+ 负责大规模地图标记点的聚合计算:
23
+ - 使用 **QuadTree** 进行空间索引优化。
24
+ - 支持基于半径的聚合逻辑。
25
+ - 高性能处理,适用于数千甚至数万个点的实时聚合。
26
+
27
+ ### 3. QuadTree (四叉树)
28
+ [QuadTree.hpp](file:///Users/wangqiang/Desktop/expo-gaode-map/packages/core/shared/cpp/QuadTree.hpp)
29
+ 为地理坐标提供高效的空间索引结构:
30
+ - **空间分割**: 自动将空间划分为四个象限。
31
+ - **范围查询**: 快速检索指定矩形区域内的所有点位。
32
+ - 被 `ClusterEngine` 用于加速近邻点搜索。
33
+
34
+ ### 4. ColorParser (颜色解析器)
35
+ [ColorParser.hpp](file:///Users/wangqiang/Desktop/expo-gaode-map/packages/core/shared/cpp/ColorParser.hpp)
36
+ 跨平台的颜色字符串解析工具:
37
+ - 支持 **Hex** 格式 (如 `#RRGGBB`, `#AARRGGBB`, `#RGB`)。
38
+ - 支持 **RGB/RGBA** 函数格式 (如 `rgba(255, 0, 0, 0.5)`)。
39
+ - 支持 **命名颜色** (如 `red`, `blue`, `transparent`)。
40
+ - 统一输出为 `0xAARRGGBB` 格式的 32 位整数。
41
+
42
+ ## 测试
43
+
44
+ 测试用例位于 `tests/` 目录。
45
+
46
+ ### 运行测试
47
+ 在 macOS/Linux 环境下,可以通过以下命令运行 C++ 单元测试:
48
+
49
+ ```bash
50
+ cd tests
51
+ chmod +x run.sh
52
+ ./run.sh
53
+ ```
54
+
55
+ 该脚本会使用 `clang++` 编译源代码并运行生成的测试二进制文件,验证各核心模块的逻辑准确性。
File without changes
File without changes
File without changes