expo-gaode-map 2.2.32 → 2.2.33
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/README.md +2 -3
- package/android/src/main/cpp/cluster_jni.cpp +56 -0
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapModule.kt +45 -6
- package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapOfflineModule.kt +1 -1
- package/android/src/main/java/expo/modules/gaodemap/modules/SDKInitializer.kt +23 -17
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerBitmapRenderer.kt +30 -16
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerView.kt +36 -31
- package/android/src/main/java/expo/modules/gaodemap/overlays/MarkerViewModule.kt +6 -6
- package/android/src/main/java/expo/modules/gaodemap/utils/GeometryUtils.kt +103 -0
- package/build/ExpoGaodeMapModule.d.ts +9 -259
- package/build/ExpoGaodeMapModule.d.ts.map +1 -1
- package/build/ExpoGaodeMapModule.js +19 -910
- package/build/ExpoGaodeMapModule.js.map +1 -1
- package/build/ExpoGaodeMapView.d.ts +3 -4
- package/build/ExpoGaodeMapView.d.ts.map +1 -1
- package/build/ExpoGaodeMapView.js +28 -25
- package/build/ExpoGaodeMapView.js.map +1 -1
- package/build/components/overlays/Circle.d.ts.map +1 -1
- package/build/components/overlays/Circle.js +1 -30
- package/build/components/overlays/Circle.js.map +1 -1
- package/build/components/overlays/Cluster.d.ts.map +1 -1
- package/build/components/overlays/Cluster.js +1 -42
- package/build/components/overlays/Cluster.js.map +1 -1
- package/build/components/overlays/HeatMap.d.ts.map +1 -1
- package/build/components/overlays/HeatMap.js +1 -20
- package/build/components/overlays/HeatMap.js.map +1 -1
- package/build/components/overlays/Marker.d.ts.map +1 -1
- package/build/components/overlays/Marker.js +14 -80
- package/build/components/overlays/Marker.js.map +1 -1
- package/build/components/overlays/Polygon.d.ts.map +1 -1
- package/build/components/overlays/Polygon.js +1 -25
- package/build/components/overlays/Polygon.js.map +1 -1
- package/build/components/overlays/Polyline.d.ts.map +1 -1
- package/build/components/overlays/Polyline.js +1 -31
- package/build/components/overlays/Polyline.js.map +1 -1
- package/build/index.d.ts +6 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -2
- package/build/index.js.map +1 -1
- package/build/module/geometry.d.ts +183 -0
- package/build/module/geometry.d.ts.map +1 -0
- package/build/module/geometry.js +374 -0
- package/build/module/geometry.js.map +1 -0
- package/build/module/location.d.ts +49 -0
- package/build/module/location.d.ts.map +1 -0
- package/build/module/location.js +257 -0
- package/build/module/location.js.map +1 -0
- package/build/module/nativeModule.d.ts +5 -0
- package/build/module/nativeModule.d.ts.map +1 -0
- package/build/module/nativeModule.js +34 -0
- package/build/module/nativeModule.js.map +1 -0
- package/build/module/normalizers.d.ts +9 -0
- package/build/module/normalizers.d.ts.map +1 -0
- package/build/module/normalizers.js +109 -0
- package/build/module/normalizers.js.map +1 -0
- package/build/module/privacy.d.ts +20 -0
- package/build/module/privacy.d.ts.map +1 -0
- package/build/module/privacy.js +59 -0
- package/build/module/privacy.js.map +1 -0
- package/build/module/sdk.d.ts +20 -0
- package/build/module/sdk.d.ts.map +1 -0
- package/build/module/sdk.js +82 -0
- package/build/module/sdk.js.map +1 -0
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js.map +1 -1
- package/build/types/native-module.types.d.ts +12 -16
- package/build/types/native-module.types.d.ts.map +1 -1
- package/build/types/native-module.types.js.map +1 -1
- package/build/types/overlays.types.d.ts +0 -16
- package/build/types/overlays.types.d.ts.map +1 -1
- package/build/types/overlays.types.js.map +1 -1
- package/build/types/route-playback.types.d.ts +3 -0
- package/build/types/route-playback.types.d.ts.map +1 -1
- package/build/types/route-playback.types.js.map +1 -1
- package/build/utils/RouteUtils.d.ts +1 -1
- package/build/utils/RouteUtils.d.ts.map +1 -1
- package/build/utils/RouteUtils.js +35 -1
- package/build/utils/RouteUtils.js.map +1 -1
- package/ios/ExpoGaodeMapModule.swift +38 -6
- package/ios/ExpoGaodeMapView.swift +10 -3
- package/ios/GaodeMapPrivacyManager.swift +26 -18
- package/ios/modules/LocationManager.swift +1 -1
- package/ios/overlays/MarkerView.swift +36 -25
- package/ios/overlays/MarkerViewModule.swift +4 -4
- package/ios/utils/ClusterNative.h +8 -0
- package/ios/utils/ClusterNative.mm +27 -0
- package/package.json +3 -1
- package/scripts/check-expo-modules.js +68 -0
- package/shared/cpp/GeometryEngine.cpp +112 -0
- package/shared/cpp/GeometryEngine.hpp +21 -0
- package/shared/cpp/tests/test_main.cpp +15 -0
package/README.md
CHANGED
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
- ✅ 默认实现了优化地图加载,减少内存占用
|
|
33
33
|
- ✅ 几何运算(距离/面积、点在圆/多边形、质心/边界、路径长度/抽稀、GeoHash、瓦片/像素坐标转换、最近点、热力网格等,由 C++ 实现)
|
|
34
34
|
- ✅ 丰富的使用案例
|
|
35
|
-
- ✅ 提供AI编程助手,帮助开发者快速集成和使用(https://TomWq.github.io/expo-gaode-map/guide/ai-skills.html)
|
|
36
35
|
- ✅ 更多内容和功能请查看 [完整文档](https://TomWq.github.io/expo-gaode-map/)
|
|
37
36
|
|
|
38
37
|
|
|
@@ -48,9 +47,9 @@
|
|
|
48
47
|
> - 如果你的项目使用 **Expo SDK 54 及以上**,请安装 默认的 版本。
|
|
49
48
|
> - 如果你的项目使用 **Expo SDK 53 及以下**(如 50, 51, 52, 53),请使用 **V1** 版本(Tag: `v1`)。
|
|
50
49
|
> ```bash
|
|
51
|
-
> npm install expo-gaode-map@v1
|
|
50
|
+
> npm install expo-gaode-map@v1.2.3
|
|
52
51
|
> ```
|
|
53
|
-
> **说明**:V1
|
|
52
|
+
> **说明**:V1 版本不支持世界地图,只是为了兼容佬项目使用,请尽快升级你的 expo 版本到 54 及以上,使用最新的 V2 版本。
|
|
54
53
|
|
|
55
54
|
### 方案一:仅使用地图和定位(核心包)
|
|
56
55
|
|
|
@@ -606,6 +606,62 @@ Java_expo_modules_gaodemap_utils_GeometryUtils_nativeCalculatePathBounds(
|
|
|
606
606
|
#endif
|
|
607
607
|
}
|
|
608
608
|
|
|
609
|
+
extern "C" JNIEXPORT jdouble JNICALL
|
|
610
|
+
Java_expo_modules_gaodemap_utils_GeometryUtils_nativeCalculateFitZoom(
|
|
611
|
+
JNIEnv* env,
|
|
612
|
+
jclass,
|
|
613
|
+
jdoubleArray latitudes,
|
|
614
|
+
jdoubleArray longitudes,
|
|
615
|
+
jdouble viewportWidthPx,
|
|
616
|
+
jdouble viewportHeightPx,
|
|
617
|
+
jdouble paddingPx,
|
|
618
|
+
jint minZoom,
|
|
619
|
+
jint maxZoom
|
|
620
|
+
) {
|
|
621
|
+
#if GAODE_HAVE_JNI
|
|
622
|
+
if (!latitudes || !longitudes) {
|
|
623
|
+
return static_cast<jdouble>(minZoom);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const jsize countLat = env->GetArrayLength(latitudes);
|
|
627
|
+
const jsize countLon = env->GetArrayLength(longitudes);
|
|
628
|
+
if (countLat == 0 || countLat != countLon) {
|
|
629
|
+
return static_cast<jdouble>(minZoom);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
jdouble* latValues = env->GetDoubleArrayElements(latitudes, nullptr);
|
|
633
|
+
jdouble* lonValues = env->GetDoubleArrayElements(longitudes, nullptr);
|
|
634
|
+
|
|
635
|
+
std::vector<gaodemap::GeoPoint> points;
|
|
636
|
+
points.reserve(static_cast<size_t>(countLat));
|
|
637
|
+
for (jsize i = 0; i < countLat; ++i) {
|
|
638
|
+
points.push_back({latValues[i], lonValues[i]});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
env->ReleaseDoubleArrayElements(latitudes, latValues, JNI_ABORT);
|
|
642
|
+
env->ReleaseDoubleArrayElements(longitudes, lonValues, JNI_ABORT);
|
|
643
|
+
|
|
644
|
+
return static_cast<jdouble>(gaodemap::calculateFitZoomForPoints(
|
|
645
|
+
points,
|
|
646
|
+
static_cast<double>(viewportWidthPx),
|
|
647
|
+
static_cast<double>(viewportHeightPx),
|
|
648
|
+
static_cast<double>(paddingPx),
|
|
649
|
+
static_cast<int>(minZoom),
|
|
650
|
+
static_cast<int>(maxZoom)
|
|
651
|
+
));
|
|
652
|
+
#else
|
|
653
|
+
(void)env;
|
|
654
|
+
(void)latitudes;
|
|
655
|
+
(void)longitudes;
|
|
656
|
+
(void)viewportWidthPx;
|
|
657
|
+
(void)viewportHeightPx;
|
|
658
|
+
(void)paddingPx;
|
|
659
|
+
(void)minZoom;
|
|
660
|
+
(void)maxZoom;
|
|
661
|
+
return 3.0;
|
|
662
|
+
#endif
|
|
663
|
+
}
|
|
664
|
+
|
|
609
665
|
extern "C" JNIEXPORT jdoubleArray JNICALL
|
|
610
666
|
Java_expo_modules_gaodemap_utils_GeometryUtils_nativeCalculateCentroid(
|
|
611
667
|
JNIEnv* env,
|
|
@@ -51,7 +51,7 @@ class ExpoGaodeMapModule : Module() {
|
|
|
51
51
|
} else if (!SDKInitializer.isPrivacyReady()) {
|
|
52
52
|
throw expo.modules.kotlin.exception.CodedException(
|
|
53
53
|
"PRIVACY_NOT_AGREED",
|
|
54
|
-
"隐私协议未完成确认,请先调用
|
|
54
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig",
|
|
55
55
|
null
|
|
56
56
|
)
|
|
57
57
|
} else {
|
|
@@ -69,12 +69,20 @@ class ExpoGaodeMapModule : Module() {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
Function("
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
Function("setPrivacyConfig") { config: Map<String, Any?> ->
|
|
73
|
+
val hasShow = config["hasShow"] as? Boolean ?: false
|
|
74
|
+
val hasContainsPrivacy = config["hasContainsPrivacy"] as? Boolean ?: hasShow
|
|
75
|
+
val hasAgree = config["hasAgree"] as? Boolean ?: false
|
|
76
|
+
val privacyVersion = config["privacyVersion"] as? String
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
SDKInitializer.setPrivacyConfig(
|
|
79
|
+
appContext.reactContext!!,
|
|
80
|
+
hasShow,
|
|
81
|
+
hasContainsPrivacy,
|
|
82
|
+
hasAgree,
|
|
83
|
+
privacyVersion,
|
|
84
|
+
config.containsKey("privacyVersion")
|
|
85
|
+
)
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
Function("setPrivacyVersion") { version: String ->
|
|
@@ -187,6 +195,37 @@ class ExpoGaodeMapModule : Module() {
|
|
|
187
195
|
}
|
|
188
196
|
}
|
|
189
197
|
|
|
198
|
+
/**
|
|
199
|
+
* 根据多个坐标点计算可同时可见的推荐缩放级别
|
|
200
|
+
* @param points 坐标点(至少 1 个)
|
|
201
|
+
* @param viewportWidthPx 视口宽度(像素)
|
|
202
|
+
* @param viewportHeightPx 视口高度(像素)
|
|
203
|
+
* @param paddingPx 内边距(像素)
|
|
204
|
+
* @param minZoom 最小缩放
|
|
205
|
+
* @param maxZoom 最大缩放
|
|
206
|
+
*/
|
|
207
|
+
Function("calculateFitZoom") {
|
|
208
|
+
points: List<Any>?,
|
|
209
|
+
viewportWidthPx: Double?,
|
|
210
|
+
viewportHeightPx: Double?,
|
|
211
|
+
paddingPx: Double?,
|
|
212
|
+
minZoom: Int?,
|
|
213
|
+
maxZoom: Int? ->
|
|
214
|
+
val normalized = LatLngParser.parseLatLngList(points)
|
|
215
|
+
val safeMinZoom = minZoom ?: 3
|
|
216
|
+
val safeMaxZoom = maxZoom ?: 20
|
|
217
|
+
if (normalized.isEmpty()) return@Function safeMinZoom.toDouble()
|
|
218
|
+
|
|
219
|
+
GeometryUtils.calculateFitZoom(
|
|
220
|
+
normalized,
|
|
221
|
+
viewportWidthPx ?: 390.0,
|
|
222
|
+
viewportHeightPx ?: 844.0,
|
|
223
|
+
paddingPx ?: 48.0,
|
|
224
|
+
safeMinZoom,
|
|
225
|
+
safeMaxZoom
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
190
229
|
/**
|
|
191
230
|
* 计算多边形面积
|
|
192
231
|
* @param points 多边形顶点坐标数组,支持嵌套数组(多边形空洞)
|
|
@@ -56,22 +56,6 @@ object SDKInitializer {
|
|
|
56
56
|
applyPrivacyState(appContext)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
fun setPrivacyShow(context: Context, hasShow: Boolean, hasContainsPrivacy: Boolean) {
|
|
60
|
-
privacyShown = hasShow
|
|
61
|
-
privacyContains = hasContainsPrivacy
|
|
62
|
-
val appContext = resolveContext(context)
|
|
63
|
-
persistState(appContext)
|
|
64
|
-
applyPrivacyState(appContext)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
fun setPrivacyAgree(context: Context, hasAgree: Boolean) {
|
|
68
|
-
privacyAgreed = hasAgree
|
|
69
|
-
agreedPrivacyVersion = if (hasAgree) privacyVersion else null
|
|
70
|
-
val appContext = resolveContext(context)
|
|
71
|
-
persistState(appContext)
|
|
72
|
-
applyPrivacyState(appContext)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
59
|
fun setPrivacyVersion(context: Context, version: String) {
|
|
76
60
|
val sanitizedVersion = version.trim().takeIf { it.isNotEmpty() }
|
|
77
61
|
privacyVersion = sanitizedVersion
|
|
@@ -89,6 +73,28 @@ object SDKInitializer {
|
|
|
89
73
|
applyPrivacyState(appContext)
|
|
90
74
|
}
|
|
91
75
|
|
|
76
|
+
fun setPrivacyConfig(
|
|
77
|
+
context: Context,
|
|
78
|
+
hasShow: Boolean,
|
|
79
|
+
hasContainsPrivacy: Boolean,
|
|
80
|
+
hasAgree: Boolean,
|
|
81
|
+
version: String?,
|
|
82
|
+
shouldUpdateVersion: Boolean
|
|
83
|
+
) {
|
|
84
|
+
val appContext = resolveContext(context)
|
|
85
|
+
|
|
86
|
+
if (shouldUpdateVersion) {
|
|
87
|
+
privacyVersion = version?.trim()?.takeIf { it.isNotEmpty() }
|
|
88
|
+
}
|
|
89
|
+
privacyShown = hasShow
|
|
90
|
+
privacyContains = hasContainsPrivacy
|
|
91
|
+
privacyAgreed = hasAgree
|
|
92
|
+
agreedPrivacyVersion = if (hasAgree) privacyVersion else null
|
|
93
|
+
|
|
94
|
+
persistState(appContext)
|
|
95
|
+
applyPrivacyState(appContext)
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
fun resetPrivacyConsent(context: Context) {
|
|
93
99
|
val appContext = resolveContext(context)
|
|
94
100
|
clearConsentPersistedState(appContext, keepCurrentVersion = false)
|
|
@@ -137,7 +143,7 @@ object SDKInitializer {
|
|
|
137
143
|
if (!isPrivacyReady()) {
|
|
138
144
|
throw expo.modules.kotlin.exception.CodedException(
|
|
139
145
|
"PRIVACY_NOT_AGREED",
|
|
140
|
-
"隐私协议未完成确认,请先调用
|
|
146
|
+
"隐私协议未完成确认,请先调用 setPrivacyConfig",
|
|
141
147
|
null
|
|
142
148
|
)
|
|
143
149
|
}
|
|
@@ -51,8 +51,8 @@ internal object MarkerBitmapRenderer {
|
|
|
51
51
|
|
|
52
52
|
fun resolveSnapshot(
|
|
53
53
|
container: ViewGroup,
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
contentWidth: Int,
|
|
55
|
+
contentHeight: Int,
|
|
56
56
|
cacheKey: String?,
|
|
57
57
|
): MarkerBitmapSnapshot? {
|
|
58
58
|
if (container.childCount == 0) {
|
|
@@ -76,17 +76,19 @@ internal object MarkerBitmapRenderer {
|
|
|
76
76
|
?: child.measuredHeight
|
|
77
77
|
?: 0
|
|
78
78
|
|
|
79
|
-
val finalWidth = if (measuredWidth > 0) measuredWidth else
|
|
80
|
-
val finalHeight = if (measuredHeight > 0) measuredHeight else
|
|
79
|
+
val finalWidth = if (measuredWidth > 0) measuredWidth else contentWidth
|
|
80
|
+
val finalHeight = if (measuredHeight > 0) measuredHeight else contentHeight
|
|
81
81
|
|
|
82
82
|
if (finalWidth <= 0 || finalHeight <= 0) {
|
|
83
83
|
return null
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
// 业务层已经显式传入稳定 cacheKey 时,直接信任它。
|
|
87
|
+
// 否则点击、重排或 drawable 实例抖动仍会让 key 变化,导致 children marker 反复重建。
|
|
88
|
+
val keyPart = cacheKey?.takeIf { it.isNotBlank() } ?: computeViewFingerprint(container)
|
|
87
89
|
|
|
88
90
|
return MarkerBitmapSnapshot(
|
|
89
|
-
keyPart =
|
|
91
|
+
keyPart = keyPart,
|
|
90
92
|
width = finalWidth,
|
|
91
93
|
height = finalHeight,
|
|
92
94
|
)
|
|
@@ -95,8 +97,8 @@ internal object MarkerBitmapRenderer {
|
|
|
95
97
|
fun createBitmap(
|
|
96
98
|
container: ViewGroup,
|
|
97
99
|
snapshot: MarkerBitmapSnapshot,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
contentWidth: Int,
|
|
101
|
+
contentHeight: Int,
|
|
100
102
|
mainHandler: Handler,
|
|
101
103
|
): Bitmap? {
|
|
102
104
|
IconBitmapCache.get(snapshot.fullCacheKey)?.let { return it }
|
|
@@ -107,8 +109,8 @@ internal object MarkerBitmapRenderer {
|
|
|
107
109
|
container = container,
|
|
108
110
|
finalWidth = snapshot.width,
|
|
109
111
|
finalHeight = snapshot.height,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
contentWidth = contentWidth,
|
|
113
|
+
contentHeight = contentHeight,
|
|
112
114
|
)
|
|
113
115
|
} else {
|
|
114
116
|
val latch = CountDownLatch(1)
|
|
@@ -120,8 +122,8 @@ internal object MarkerBitmapRenderer {
|
|
|
120
122
|
container = container,
|
|
121
123
|
finalWidth = snapshot.width,
|
|
122
124
|
finalHeight = snapshot.height,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
contentWidth = contentWidth,
|
|
126
|
+
contentHeight = contentHeight,
|
|
125
127
|
)
|
|
126
128
|
} finally {
|
|
127
129
|
latch.countDown()
|
|
@@ -227,7 +229,19 @@ internal object MarkerBitmapRenderer {
|
|
|
227
229
|
} else {
|
|
228
230
|
val drawable = v.drawable
|
|
229
231
|
if (drawable != null) {
|
|
230
|
-
|
|
232
|
+
val constantState = drawable.constantState
|
|
233
|
+
if (constantState != null) {
|
|
234
|
+
sb.append("[drawableState=").append(constantState.hashCode()).append("]")
|
|
235
|
+
} else {
|
|
236
|
+
sb
|
|
237
|
+
.append("[drawable=")
|
|
238
|
+
.append(drawable.javaClass.simpleName)
|
|
239
|
+
.append(':')
|
|
240
|
+
.append(drawable.intrinsicWidth)
|
|
241
|
+
.append('x')
|
|
242
|
+
.append(drawable.intrinsicHeight)
|
|
243
|
+
.append("]")
|
|
244
|
+
}
|
|
231
245
|
}
|
|
232
246
|
}
|
|
233
247
|
}
|
|
@@ -248,8 +262,8 @@ internal object MarkerBitmapRenderer {
|
|
|
248
262
|
container: ViewGroup,
|
|
249
263
|
finalWidth: Int,
|
|
250
264
|
finalHeight: Int,
|
|
251
|
-
|
|
252
|
-
|
|
265
|
+
contentWidth: Int,
|
|
266
|
+
contentHeight: Int,
|
|
253
267
|
): Bitmap? {
|
|
254
268
|
try {
|
|
255
269
|
val childView = container.getChildAt(0) ?: return null
|
|
@@ -271,7 +285,7 @@ internal object MarkerBitmapRenderer {
|
|
|
271
285
|
val canvas = Canvas(bitmap)
|
|
272
286
|
childView.draw(canvas)
|
|
273
287
|
|
|
274
|
-
val shouldTrimTransparentPadding =
|
|
288
|
+
val shouldTrimTransparentPadding = contentWidth <= 0 && contentHeight <= 0
|
|
275
289
|
return if (shouldTrimTransparentPadding) trimTransparentPadding(bitmap) else bitmap
|
|
276
290
|
} catch (_: Exception) {
|
|
277
291
|
return null
|
|
@@ -82,16 +82,16 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
82
82
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
83
83
|
val selfParams = this.layoutParams
|
|
84
84
|
if (selfParams == null || selfParams !is LayoutParams) {
|
|
85
|
-
val width = if (
|
|
86
|
-
|
|
85
|
+
val width = if (contentWidth > 0) {
|
|
86
|
+
contentWidth
|
|
87
87
|
} else if (selfParams != null && selfParams.width > 0) {
|
|
88
88
|
selfParams.width
|
|
89
89
|
} else {
|
|
90
90
|
LayoutParams.WRAP_CONTENT
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
val height = if (
|
|
94
|
-
|
|
93
|
+
val height = if (contentHeight > 0) {
|
|
94
|
+
contentHeight
|
|
95
95
|
} else if (selfParams != null && selfParams.height > 0) {
|
|
96
96
|
selfParams.height
|
|
97
97
|
} else {
|
|
@@ -123,12 +123,12 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
123
123
|
val fallbackHeightSize = resolveExplicitMeasureSize(parentHeightSize, false)
|
|
124
124
|
|
|
125
125
|
val contentWidthSpec = when {
|
|
126
|
-
|
|
126
|
+
contentWidth > 0 -> MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY)
|
|
127
127
|
fallbackWidthSize > 0 -> MeasureSpec.makeMeasureSpec(fallbackWidthSize, MeasureSpec.AT_MOST)
|
|
128
128
|
else -> MeasureSpec.makeMeasureSpec(1, MeasureSpec.EXACTLY)
|
|
129
129
|
}
|
|
130
130
|
val contentHeightSpec = when {
|
|
131
|
-
|
|
131
|
+
contentHeight > 0 -> MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY)
|
|
132
132
|
fallbackHeightSize > 0 -> MeasureSpec.makeMeasureSpec(fallbackHeightSize, MeasureSpec.AT_MOST)
|
|
133
133
|
else -> MeasureSpec.makeMeasureSpec(1, MeasureSpec.EXACTLY)
|
|
134
134
|
}
|
|
@@ -158,13 +158,13 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
158
158
|
measuredContentHeight = max(measuredContentHeight, childBounds?.height() ?: child.measuredHeight)
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
val desiredWidth = if (
|
|
162
|
-
|
|
161
|
+
val desiredWidth = if (contentWidth > 0) {
|
|
162
|
+
contentWidth
|
|
163
163
|
} else {
|
|
164
164
|
measuredContentWidth + paddingLeft + paddingRight
|
|
165
165
|
}
|
|
166
|
-
val desiredHeight = if (
|
|
167
|
-
|
|
166
|
+
val desiredHeight = if (contentHeight > 0) {
|
|
167
|
+
contentHeight
|
|
168
168
|
} else {
|
|
169
169
|
measuredContentHeight + paddingTop + paddingBottom
|
|
170
170
|
}
|
|
@@ -218,8 +218,8 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
218
218
|
private var pendingLongitude: Double? = null // 临时存储经度
|
|
219
219
|
private var iconWidth: Int = 0 // 用于自定义图标的宽度
|
|
220
220
|
private var iconHeight: Int = 0 // 用于自定义图标的高度
|
|
221
|
-
private var
|
|
222
|
-
private var
|
|
221
|
+
private var contentWidth: Int = 0 // 用于自定义视图(children)的宽度
|
|
222
|
+
private var contentHeight: Int = 0 // 用于自定义视图(children)的高度
|
|
223
223
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
224
224
|
private var isRemoving = false // 标记是否正在被移除
|
|
225
225
|
private var pendingMarkerIconUpdate: Runnable? = null
|
|
@@ -479,7 +479,7 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
479
479
|
return
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
-
invalidateAppliedCustomMarkerCaches()
|
|
482
|
+
invalidateAppliedCustomMarkerCaches(clearGlobalCache = true)
|
|
483
483
|
cacheKey = key
|
|
484
484
|
updateMarkerIcon()
|
|
485
485
|
}
|
|
@@ -738,32 +738,32 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
738
738
|
}
|
|
739
739
|
|
|
740
740
|
/**
|
|
741
|
-
*
|
|
741
|
+
* 设置内容宽度(用于 children 属性)
|
|
742
742
|
* 注意:React Native 传入的是 DP 值,需要转换为 PX
|
|
743
743
|
*/
|
|
744
|
-
fun
|
|
744
|
+
fun setContentWidth(width: Int) {
|
|
745
745
|
val density = context.resources.displayMetrics.density
|
|
746
746
|
val resolvedWidth = (width * density).toInt()
|
|
747
|
-
if (
|
|
747
|
+
if (contentWidth == resolvedWidth) {
|
|
748
748
|
return
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
-
|
|
751
|
+
contentWidth = resolvedWidth
|
|
752
752
|
markCustomMarkerContentDirty()
|
|
753
753
|
}
|
|
754
754
|
|
|
755
755
|
/**
|
|
756
|
-
*
|
|
756
|
+
* 设置内容高度(用于 children 属性)
|
|
757
757
|
* 注意:React Native 传入的是 DP 值,需要转换为 PX
|
|
758
758
|
*/
|
|
759
|
-
fun
|
|
759
|
+
fun setContentHeight(height: Int) {
|
|
760
760
|
val density = context.resources.displayMetrics.density
|
|
761
761
|
val resolvedHeight = (height * density).toInt()
|
|
762
|
-
if (
|
|
762
|
+
if (contentHeight == resolvedHeight) {
|
|
763
763
|
return
|
|
764
764
|
}
|
|
765
765
|
|
|
766
|
-
|
|
766
|
+
contentHeight = resolvedHeight
|
|
767
767
|
markCustomMarkerContentDirty()
|
|
768
768
|
}
|
|
769
769
|
|
|
@@ -904,8 +904,8 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
904
904
|
return MarkerBitmapRenderer.createBitmap(
|
|
905
905
|
container = this,
|
|
906
906
|
snapshot = resolvedSnapshot,
|
|
907
|
-
|
|
908
|
-
|
|
907
|
+
contentWidth = contentWidth,
|
|
908
|
+
contentHeight = contentHeight,
|
|
909
909
|
mainHandler = mainHandler,
|
|
910
910
|
)
|
|
911
911
|
}
|
|
@@ -997,10 +997,12 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
997
997
|
}
|
|
998
998
|
}
|
|
999
999
|
|
|
1000
|
-
private fun invalidateAppliedCustomMarkerCaches() {
|
|
1000
|
+
private fun invalidateAppliedCustomMarkerCaches(clearGlobalCache: Boolean = false) {
|
|
1001
1001
|
val key = lastAppliedCustomMarkerKey ?: return
|
|
1002
|
-
|
|
1003
|
-
|
|
1002
|
+
if (clearGlobalCache) {
|
|
1003
|
+
BitmapDescriptorCache.remove(key)
|
|
1004
|
+
IconBitmapCache.remove(key)
|
|
1005
|
+
}
|
|
1004
1006
|
lastAppliedCustomMarkerKey = null
|
|
1005
1007
|
}
|
|
1006
1008
|
|
|
@@ -1009,7 +1011,10 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1009
1011
|
return
|
|
1010
1012
|
}
|
|
1011
1013
|
|
|
1012
|
-
|
|
1014
|
+
// children(尤其是 Image)异步加载完成后,需要强制淘汰当前 marker 已应用的位图缓存,
|
|
1015
|
+
// 否则同一个 cacheKey 会一直命中“占位态”的旧 bitmap,表现为灰块/蒙层不更新。
|
|
1016
|
+
// 这里仅清理当前 marker 已应用的 key,不会影响其它 marker。
|
|
1017
|
+
invalidateAppliedCustomMarkerCaches(clearGlobalCache = true)
|
|
1013
1018
|
if (marker != null) {
|
|
1014
1019
|
scheduleMarkerIconUpdate(delayMs)
|
|
1015
1020
|
}
|
|
@@ -1048,8 +1053,8 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1048
1053
|
private fun resolveMarkerBitmapSnapshot(): MarkerBitmapSnapshot? =
|
|
1049
1054
|
MarkerBitmapRenderer.resolveSnapshot(
|
|
1050
1055
|
container = this,
|
|
1051
|
-
|
|
1052
|
-
|
|
1056
|
+
contentWidth = contentWidth,
|
|
1057
|
+
contentHeight = contentHeight,
|
|
1053
1058
|
cacheKey = cacheKey,
|
|
1054
1059
|
)
|
|
1055
1060
|
|
|
@@ -1059,13 +1064,13 @@ class MarkerView(context: Context, appContext: AppContext) : ExpoView(context, a
|
|
|
1059
1064
|
val childCountBefore = childCount
|
|
1060
1065
|
|
|
1061
1066
|
val sourceWidth = when {
|
|
1062
|
-
|
|
1067
|
+
contentWidth > 0 -> contentWidth
|
|
1063
1068
|
params?.width != null && params.width > 0 -> params.width
|
|
1064
1069
|
else -> LayoutParams.WRAP_CONTENT
|
|
1065
1070
|
}
|
|
1066
1071
|
|
|
1067
1072
|
val sourceHeight = when {
|
|
1068
|
-
|
|
1073
|
+
contentHeight > 0 -> contentHeight
|
|
1069
1074
|
params?.height != null && params.height > 0 -> params.height
|
|
1070
1075
|
else -> LayoutParams.WRAP_CONTENT
|
|
1071
1076
|
}
|
|
@@ -69,13 +69,13 @@ class MarkerViewModule : Module() {
|
|
|
69
69
|
Prop<Int>("iconHeight") { view, height ->
|
|
70
70
|
view.setIconHeight(height)
|
|
71
71
|
}
|
|
72
|
-
//
|
|
73
|
-
Prop<Int>("
|
|
74
|
-
view.
|
|
72
|
+
// 内容宽度
|
|
73
|
+
Prop<Int>("contentWidth") { view, width ->
|
|
74
|
+
view.setContentWidth(width)
|
|
75
75
|
}
|
|
76
|
-
//
|
|
77
|
-
Prop<Int>("
|
|
78
|
-
view.
|
|
76
|
+
// 内容高度
|
|
77
|
+
Prop<Int>("contentHeight") { view, height ->
|
|
78
|
+
view.setContentHeight(height)
|
|
79
79
|
}
|
|
80
80
|
// 缓存key
|
|
81
81
|
Prop<String?>("cacheKey") { view, key ->
|
|
@@ -83,6 +83,16 @@ object GeometryUtils {
|
|
|
83
83
|
longitudes: DoubleArray
|
|
84
84
|
): DoubleArray?
|
|
85
85
|
|
|
86
|
+
private external fun nativeCalculateFitZoom(
|
|
87
|
+
latitudes: DoubleArray,
|
|
88
|
+
longitudes: DoubleArray,
|
|
89
|
+
viewportWidthPx: Double,
|
|
90
|
+
viewportHeightPx: Double,
|
|
91
|
+
paddingPx: Double,
|
|
92
|
+
minZoom: Int,
|
|
93
|
+
maxZoom: Int
|
|
94
|
+
): Double
|
|
95
|
+
|
|
86
96
|
private external fun nativeEncodeGeoHash(
|
|
87
97
|
lat: Double,
|
|
88
98
|
lon: Double,
|
|
@@ -401,6 +411,99 @@ object GeometryUtils {
|
|
|
401
411
|
}
|
|
402
412
|
}
|
|
403
413
|
|
|
414
|
+
private fun mercatorX01(lon: Double): Double {
|
|
415
|
+
var wrapped = (lon + 180.0) % 360.0
|
|
416
|
+
if (wrapped < 0.0) wrapped += 360.0
|
|
417
|
+
return wrapped / 360.0
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private fun mercatorY01(lat: Double): Double {
|
|
421
|
+
val clamped = lat.coerceIn(-85.05112878, 85.05112878)
|
|
422
|
+
val rad = Math.toRadians(clamped)
|
|
423
|
+
val y = (1.0 - asinh(tan(rad)) / Math.PI) * 0.5
|
|
424
|
+
return y.coerceIn(0.0, 1.0)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private fun wrappedSpan01(values: List<Double>): Double {
|
|
428
|
+
if (values.size <= 1) return 0.0
|
|
429
|
+
val sorted = values.sorted()
|
|
430
|
+
var maxGap = 0.0
|
|
431
|
+
for (i in 0 until sorted.size - 1) {
|
|
432
|
+
maxGap = max(maxGap, sorted[i + 1] - sorted[i])
|
|
433
|
+
}
|
|
434
|
+
maxGap = max(maxGap, sorted.first() + 1.0 - sorted.last())
|
|
435
|
+
return (1.0 - maxGap).coerceAtLeast(0.0)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private fun fallbackCalculateFitZoom(
|
|
439
|
+
points: List<LatLng>,
|
|
440
|
+
viewportWidthPx: Double,
|
|
441
|
+
viewportHeightPx: Double,
|
|
442
|
+
paddingPx: Double,
|
|
443
|
+
minZoom: Int,
|
|
444
|
+
maxZoom: Int
|
|
445
|
+
): Double {
|
|
446
|
+
if (points.isEmpty()) return minZoom.toDouble()
|
|
447
|
+
if (points.size == 1) return maxZoom.toDouble()
|
|
448
|
+
|
|
449
|
+
val safeMinZoom = min(minZoom, maxZoom)
|
|
450
|
+
val safeMaxZoom = max(minZoom, maxZoom)
|
|
451
|
+
val safeWidth = if (viewportWidthPx > 1.0) viewportWidthPx else 390.0
|
|
452
|
+
val safeHeight = if (viewportHeightPx > 1.0) viewportHeightPx else 844.0
|
|
453
|
+
val safePadding = max(0.0, paddingPx)
|
|
454
|
+
val availableWidth = max(1.0, safeWidth - safePadding * 2.0)
|
|
455
|
+
val availableHeight = max(1.0, safeHeight - safePadding * 2.0)
|
|
456
|
+
|
|
457
|
+
val xs = points.map { mercatorX01(it.longitude) }
|
|
458
|
+
val ys = points.map { mercatorY01(it.latitude) }
|
|
459
|
+
val spanX = wrappedSpan01(xs)
|
|
460
|
+
val spanY = (ys.maxOrNull() ?: 0.0) - (ys.minOrNull() ?: 0.0)
|
|
461
|
+
val tileSize = 256.0
|
|
462
|
+
|
|
463
|
+
val zoomX = if (spanX <= 1e-12) safeMaxZoom.toDouble() else log2(availableWidth / (tileSize * spanX))
|
|
464
|
+
val zoomY = if (spanY <= 1e-12) safeMaxZoom.toDouble() else log2(availableHeight / (tileSize * spanY))
|
|
465
|
+
val fitZoom = min(zoomX, zoomY)
|
|
466
|
+
if (!fitZoom.isFinite()) return safeMinZoom.toDouble()
|
|
467
|
+
return fitZoom.coerceIn(safeMinZoom.toDouble(), safeMaxZoom.toDouble())
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
fun calculateFitZoom(
|
|
471
|
+
points: List<LatLng>,
|
|
472
|
+
viewportWidthPx: Double,
|
|
473
|
+
viewportHeightPx: Double,
|
|
474
|
+
paddingPx: Double,
|
|
475
|
+
minZoom: Int,
|
|
476
|
+
maxZoom: Int
|
|
477
|
+
): Double {
|
|
478
|
+
if (points.isEmpty()) return minZoom.toDouble()
|
|
479
|
+
return try {
|
|
480
|
+
val latitudes = DoubleArray(points.size)
|
|
481
|
+
val longitudes = DoubleArray(points.size)
|
|
482
|
+
for (i in points.indices) {
|
|
483
|
+
latitudes[i] = points[i].latitude
|
|
484
|
+
longitudes[i] = points[i].longitude
|
|
485
|
+
}
|
|
486
|
+
nativeCalculateFitZoom(
|
|
487
|
+
latitudes,
|
|
488
|
+
longitudes,
|
|
489
|
+
viewportWidthPx,
|
|
490
|
+
viewportHeightPx,
|
|
491
|
+
paddingPx,
|
|
492
|
+
minZoom,
|
|
493
|
+
maxZoom
|
|
494
|
+
)
|
|
495
|
+
} catch (_: Throwable) {
|
|
496
|
+
fallbackCalculateFitZoom(
|
|
497
|
+
points,
|
|
498
|
+
viewportWidthPx,
|
|
499
|
+
viewportHeightPx,
|
|
500
|
+
paddingPx,
|
|
501
|
+
minZoom,
|
|
502
|
+
maxZoom
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
404
507
|
fun encodeGeoHash(point: LatLng, precision: Int): String {
|
|
405
508
|
return try {
|
|
406
509
|
nativeEncodeGeoHash(point.latitude, point.longitude, precision)
|