expo-gaode-map-navigation 1.1.5-next.2 → 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.
- package/android/src/main/cpp/CMakeLists.txt +4 -4
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ClusterNative.kt +1 -1
- package/android/src/main/java/expo/modules/gaodemap/map/utils/ColorParser.kt +1 -1
- package/android/src/main/java/expo/modules/gaodemap/map/utils/GeometryUtils.kt +1 -1
- package/build/ExpoGaodeMapNavigationModule.d.ts +2 -1
- package/build/index.d.ts +35 -33
- package/build/index.js +69 -105
- package/build/types/index.d.ts +1 -0
- package/build/types/index.js +1 -0
- package/build/types/native-module.types.d.ts +69 -0
- package/build/types/native-module.types.js +2 -0
- package/package.json +2 -1
- package/shared/cpp/ClusterEngine.cpp +110 -0
- package/shared/cpp/ClusterEngine.hpp +20 -0
- package/shared/cpp/ColorParser.cpp +135 -0
- package/shared/cpp/ColorParser.hpp +14 -0
- package/shared/cpp/GeometryEngine.cpp +574 -0
- package/shared/cpp/GeometryEngine.hpp +159 -0
- package/shared/cpp/QuadTree.cpp +92 -0
- package/shared/cpp/QuadTree.hpp +42 -0
- package/shared/cpp/README.md +55 -0
- /package/{ios/map → shared}/cpp/tests/benchmark_js.js +0 -0
- /package/{ios/map → shared}/cpp/tests/run.sh +0 -0
- /package/{ios/map → shared}/cpp/tests/test_main.cpp +0 -0
|
@@ -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
|
+
}
|