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
|
@@ -48,12 +48,19 @@ public class ExpoGaodeMapModule: Module {
|
|
|
48
48
|
_ = self.trySetupApiKeyFromPlist()
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
Function("
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
Function("setPrivacyConfig") { (config: [String: Any]) in
|
|
52
|
+
let hasShow = config["hasShow"] as? Bool ?? false
|
|
53
|
+
let hasContainsPrivacy = config["hasContainsPrivacy"] as? Bool ?? hasShow
|
|
54
|
+
let hasAgree = config["hasAgree"] as? Bool ?? false
|
|
55
|
+
let privacyVersion = config["privacyVersion"] as? String
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
GaodeMapPrivacyManager.setPrivacyConfig(
|
|
58
|
+
hasShow: hasShow,
|
|
59
|
+
hasContainsPrivacy: hasContainsPrivacy,
|
|
60
|
+
hasAgree: hasAgree,
|
|
61
|
+
privacyVersion: privacyVersion,
|
|
62
|
+
updatesPrivacyVersion: config.keys.contains("privacyVersion")
|
|
63
|
+
)
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
Function("setPrivacyVersion") { (version: String) in
|
|
@@ -76,7 +83,7 @@ public class ExpoGaodeMapModule: Module {
|
|
|
76
83
|
*/
|
|
77
84
|
Function("initSDK") { (config: [String: String]) in
|
|
78
85
|
guard GaodeMapPrivacyManager.isReady else {
|
|
79
|
-
throw Exception(name: "PRIVACY_NOT_AGREED", description: "隐私协议未完成确认,请先调用
|
|
86
|
+
throw Exception(name: "PRIVACY_NOT_AGREED", description: "隐私协议未完成确认,请先调用 setPrivacyConfig")
|
|
80
87
|
}
|
|
81
88
|
GaodeMapPrivacyManager.applyPrivacyState()
|
|
82
89
|
|
|
@@ -273,6 +280,31 @@ public class ExpoGaodeMapModule: Module {
|
|
|
273
280
|
}
|
|
274
281
|
return ClusterNative.calculateDistance(lat1: coord1.latitude, lon1: coord1.longitude, lat2: coord2.latitude, lon2: coord2.longitude)
|
|
275
282
|
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 根据多个坐标点计算可同时可见的推荐缩放级别
|
|
286
|
+
*/
|
|
287
|
+
Function("calculateFitZoom") {
|
|
288
|
+
(points: [Any]?, viewportWidthPx: Double?, viewportHeightPx: Double?, paddingPx: Double?, minZoom: Int?, maxZoom: Int?) -> Double in
|
|
289
|
+
let coords = LatLngParser.parseLatLngList(points)
|
|
290
|
+
let safeMinZoom = minZoom ?? 3
|
|
291
|
+
let safeMaxZoom = maxZoom ?? 20
|
|
292
|
+
if coords.isEmpty {
|
|
293
|
+
return Double(safeMinZoom)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let lats = coords.map { NSNumber(value: $0.latitude) }
|
|
297
|
+
let lons = coords.map { NSNumber(value: $0.longitude) }
|
|
298
|
+
return ClusterNative.calculateFitZoom(
|
|
299
|
+
latitudes: lats,
|
|
300
|
+
longitudes: lons,
|
|
301
|
+
viewportWidthPx: viewportWidthPx ?? 390.0,
|
|
302
|
+
viewportHeightPx: viewportHeightPx ?? 844.0,
|
|
303
|
+
paddingPx: paddingPx ?? 48.0,
|
|
304
|
+
minZoom: Int32(safeMinZoom),
|
|
305
|
+
maxZoom: Int32(safeMaxZoom)
|
|
306
|
+
)
|
|
307
|
+
}
|
|
276
308
|
|
|
277
309
|
/**
|
|
278
310
|
* 计算多边形面积
|
|
@@ -1349,17 +1349,24 @@ extension ExpoGaodeMapView {
|
|
|
1349
1349
|
isHandlingAnnotationSelect = true
|
|
1350
1350
|
|
|
1351
1351
|
// 🔑 统一从 overlayViews 查找 MarkerView(新旧架构统一)
|
|
1352
|
-
for
|
|
1353
|
-
if let markerView =
|
|
1352
|
+
for overlayView in overlayViews {
|
|
1353
|
+
if let markerView = overlayView as? MarkerView {
|
|
1354
1354
|
if markerView.annotation === annotation {
|
|
1355
1355
|
let eventData: [String: Any] = [
|
|
1356
1356
|
"latitude": annotation.coordinate.latitude,
|
|
1357
1357
|
"longitude": annotation.coordinate.longitude
|
|
1358
1358
|
]
|
|
1359
1359
|
markerView.onMarkerPress(eventData)
|
|
1360
|
+
// iOS 对“已选中 annotation”再次点击通常不会重复触发 didSelect。
|
|
1361
|
+
// 对自定义 children marker(无系统 callout)这里主动取消选中,
|
|
1362
|
+
// 以保证同一 marker 可以连续触发点击(例如关闭 sheet 后再次点击)。
|
|
1363
|
+
if !markerView.subviews.isEmpty {
|
|
1364
|
+
mapView.deselectAnnotation(annotation, animated: false)
|
|
1365
|
+
isHandlingAnnotationSelect = false
|
|
1366
|
+
}
|
|
1360
1367
|
return
|
|
1361
1368
|
}
|
|
1362
|
-
} else if let clusterView =
|
|
1369
|
+
} else if let clusterView = overlayView as? ClusterView {
|
|
1363
1370
|
if clusterView.containsAnnotation(annotation) {
|
|
1364
1371
|
clusterView.handleAnnotationTap(annotation)
|
|
1365
1372
|
return
|
|
@@ -46,24 +46,6 @@ enum GaodeMapPrivacyManager {
|
|
|
46
46
|
applyPrivacyState()
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
static func setPrivacyShow(_ show: Bool, hasContainsPrivacy: Bool) {
|
|
50
|
-
let previousStatus = status()
|
|
51
|
-
hasShow = show
|
|
52
|
-
self.hasContainsPrivacy = hasContainsPrivacy
|
|
53
|
-
persistState()
|
|
54
|
-
applyPrivacyState()
|
|
55
|
-
notifyIfNeeded(previousStatus: previousStatus)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static func setPrivacyAgree(_ agree: Bool) {
|
|
59
|
-
let previousStatus = status()
|
|
60
|
-
hasAgree = agree
|
|
61
|
-
agreedPrivacyVersion = agree ? privacyVersion : nil
|
|
62
|
-
persistState()
|
|
63
|
-
applyPrivacyState()
|
|
64
|
-
notifyIfNeeded(previousStatus: previousStatus)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
49
|
static func setPrivacyVersion(_ version: String) {
|
|
68
50
|
let previousStatus = status()
|
|
69
51
|
privacyVersion = version.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
@@ -83,6 +65,32 @@ enum GaodeMapPrivacyManager {
|
|
|
83
65
|
notifyIfNeeded(previousStatus: previousStatus)
|
|
84
66
|
}
|
|
85
67
|
|
|
68
|
+
static func setPrivacyConfig(
|
|
69
|
+
hasShow: Bool,
|
|
70
|
+
hasContainsPrivacy: Bool,
|
|
71
|
+
hasAgree: Bool,
|
|
72
|
+
privacyVersion newPrivacyVersion: String?,
|
|
73
|
+
updatesPrivacyVersion: Bool
|
|
74
|
+
) {
|
|
75
|
+
let previousStatus = status()
|
|
76
|
+
|
|
77
|
+
if updatesPrivacyVersion {
|
|
78
|
+
privacyVersion = newPrivacyVersion?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
79
|
+
if privacyVersion?.isEmpty == true {
|
|
80
|
+
privacyVersion = nil
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
self.hasShow = hasShow
|
|
85
|
+
self.hasContainsPrivacy = hasContainsPrivacy
|
|
86
|
+
self.hasAgree = hasAgree
|
|
87
|
+
agreedPrivacyVersion = hasAgree ? privacyVersion : nil
|
|
88
|
+
|
|
89
|
+
persistState()
|
|
90
|
+
applyPrivacyState()
|
|
91
|
+
notifyIfNeeded(previousStatus: previousStatus)
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
static func resetPrivacyConsent() {
|
|
87
95
|
let previousStatus = status()
|
|
88
96
|
clearConsentPersistedState(keepCurrentVersion: false)
|
|
@@ -238,7 +238,7 @@ class LocationManager: NSObject, AMapLocationManagerDelegate {
|
|
|
238
238
|
*/
|
|
239
239
|
func coordinateConvert(_ coordinate: [String: Double], type: Int, promise: Promise) {
|
|
240
240
|
guard GaodeMapPrivacyManager.isReady else {
|
|
241
|
-
promise.reject("PRIVACY_NOT_AGREED", "隐私协议未完成确认,请先调用
|
|
241
|
+
promise.reject("PRIVACY_NOT_AGREED", "隐私协议未完成确认,请先调用 setPrivacyConfig")
|
|
242
242
|
return
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -36,10 +36,10 @@ class MarkerView: ExpoView {
|
|
|
36
36
|
var iconWidth: Double = 40
|
|
37
37
|
/// 图标高度(用于自定义图标 icon 属性)
|
|
38
38
|
var iconHeight: Double = 40
|
|
39
|
-
///
|
|
40
|
-
var
|
|
41
|
-
///
|
|
42
|
-
var
|
|
39
|
+
/// 内容宽度(用于 children 属性)
|
|
40
|
+
var contentWidth: Double = 0
|
|
41
|
+
/// 内容高度(用于 children 属性)
|
|
42
|
+
var contentHeight: Double = 0
|
|
43
43
|
/// 中心偏移
|
|
44
44
|
var centerOffset: [String: Double]?
|
|
45
45
|
/// 是否显示动画
|
|
@@ -230,7 +230,7 @@ class MarkerView: ExpoView {
|
|
|
230
230
|
|
|
231
231
|
// 1. 如果有 children,使用自定义视图
|
|
232
232
|
if self.subviews.count > 0 {
|
|
233
|
-
let size =
|
|
233
|
+
let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
|
|
234
234
|
let key = childrenCacheKey(for: size)
|
|
235
235
|
if let cached = IconBitmapCache.shared.image(forKey: key) {
|
|
236
236
|
annotationView?.image = cached
|
|
@@ -243,6 +243,7 @@ class MarkerView: ExpoView {
|
|
|
243
243
|
// 异步渲染并设置
|
|
244
244
|
DispatchQueue.main.async { [weak self, weak annotationView] in
|
|
245
245
|
guard let self = self, let annotationView = annotationView else { return }
|
|
246
|
+
guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
|
|
246
247
|
if let generated = self.createImageFromSubviews() {
|
|
247
248
|
annotationView.image = generated
|
|
248
249
|
self.applyCenterOffset(to: annotationView, defaultOffset: .zero)
|
|
@@ -265,6 +266,7 @@ class MarkerView: ExpoView {
|
|
|
265
266
|
// 异步加载图标
|
|
266
267
|
loadIcon(iconUri: iconUri) { [weak self, weak annotationView] image in
|
|
267
268
|
guard let self = self, let image = image, let annotationView = annotationView else { return }
|
|
269
|
+
guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
|
|
268
270
|
let size = CGSize(width: self.iconWidth, height: self.iconHeight)
|
|
269
271
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
|
270
272
|
image.draw(in: CGRect(origin: .zero, size: size))
|
|
@@ -305,8 +307,9 @@ class MarkerView: ExpoView {
|
|
|
305
307
|
|
|
306
308
|
// 🔑 如果有 children,使用自定义视图
|
|
307
309
|
if self.subviews.count > 0 {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
+
let reuseToken = cacheKey?.replacingOccurrences(of: "|", with: "_")
|
|
311
|
+
?? String(ObjectIdentifier(self).hashValue)
|
|
312
|
+
let reuseId = "custom_marker_children_\(reuseToken)" + (growAnimation ? "_grow" : "")
|
|
310
313
|
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
|
|
311
314
|
if annotationView == nil {
|
|
312
315
|
if growAnimation {
|
|
@@ -326,7 +329,7 @@ class MarkerView: ExpoView {
|
|
|
326
329
|
self.annotationView = annotationView
|
|
327
330
|
|
|
328
331
|
// 生成 cacheKey 或 fallback 到 identifier
|
|
329
|
-
let size =
|
|
332
|
+
let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 40))
|
|
330
333
|
let key = childrenCacheKey(for: size)
|
|
331
334
|
|
|
332
335
|
// 1) 如果缓存命中,直接同步返回图像(fast path)
|
|
@@ -347,6 +350,7 @@ class MarkerView: ExpoView {
|
|
|
347
350
|
// 🔑 修复:延长延迟时间,给 React Native Image 更多加载时间
|
|
348
351
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self, weak annotationView] in
|
|
349
352
|
guard let self = self, let annotationView = annotationView else { return }
|
|
353
|
+
guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
|
|
350
354
|
// 再次检查缓存(避免重复渲染)
|
|
351
355
|
if let cached = IconBitmapCache.shared.image(forKey: key) {
|
|
352
356
|
annotationView.image = cached
|
|
@@ -410,12 +414,12 @@ class MarkerView: ExpoView {
|
|
|
410
414
|
UIGraphicsEndImageContext()
|
|
411
415
|
|
|
412
416
|
DispatchQueue.main.async {
|
|
417
|
+
guard let annotationView else { return }
|
|
418
|
+
guard self.isAnnotationView(annotationView, boundTo: annotation) else { return }
|
|
413
419
|
if let img = resizedImage {
|
|
414
420
|
IconBitmapCache.shared.setImage(img, forKey: key)
|
|
415
|
-
annotationView
|
|
416
|
-
|
|
417
|
-
self.applyCenterOffset(to: annotationView, defaultOffset: CGPoint(x: 0, y: -img.size.height / 2))
|
|
418
|
-
}
|
|
421
|
+
annotationView.image = img
|
|
422
|
+
self.applyCenterOffset(to: annotationView, defaultOffset: CGPoint(x: 0, y: -img.size.height / 2))
|
|
419
423
|
}
|
|
420
424
|
}
|
|
421
425
|
}
|
|
@@ -496,7 +500,7 @@ class MarkerView: ExpoView {
|
|
|
496
500
|
* 将子视图转换为图片
|
|
497
501
|
*/
|
|
498
502
|
private func createImageFromSubviews() -> UIImage? {
|
|
499
|
-
let size =
|
|
503
|
+
let size = resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
|
|
500
504
|
let key = childrenCacheKey(for: size)
|
|
501
505
|
|
|
502
506
|
if let cachedImage = IconBitmapCache.shared.image(forKey: key) {
|
|
@@ -580,14 +584,21 @@ class MarkerView: ExpoView {
|
|
|
580
584
|
return .zero
|
|
581
585
|
}
|
|
582
586
|
|
|
583
|
-
private func
|
|
587
|
+
private func isAnnotationView(_ annotationView: MAAnnotationView, boundTo annotation: MAAnnotation) -> Bool {
|
|
588
|
+
guard let current = annotationView.annotation else {
|
|
589
|
+
return false
|
|
590
|
+
}
|
|
591
|
+
return (current as AnyObject) === (annotation as AnyObject)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private func resolvedContentSubviewSize(defaultSize: CGSize) -> CGSize {
|
|
584
595
|
guard let firstSubview = subviews.first else {
|
|
585
596
|
return defaultSize
|
|
586
597
|
}
|
|
587
598
|
|
|
588
|
-
if
|
|
589
|
-
let width =
|
|
590
|
-
let height =
|
|
599
|
+
if contentWidth > 0 || contentHeight > 0 {
|
|
600
|
+
let width = contentWidth > 0 ? CGFloat(contentWidth) : defaultSize.width
|
|
601
|
+
let height = contentHeight > 0 ? CGFloat(contentHeight) : defaultSize.height
|
|
591
602
|
return CGSize(width: width, height: height)
|
|
592
603
|
}
|
|
593
604
|
|
|
@@ -842,8 +853,8 @@ class MarkerView: ExpoView {
|
|
|
842
853
|
|
|
843
854
|
private func invalidateCurrentChildrenCache() {
|
|
844
855
|
let sizes = [
|
|
845
|
-
|
|
846
|
-
|
|
856
|
+
resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 40)),
|
|
857
|
+
resolvedContentSubviewSize(defaultSize: CGSize(width: 200, height: 60))
|
|
847
858
|
]
|
|
848
859
|
|
|
849
860
|
for size in sizes {
|
|
@@ -1000,17 +1011,17 @@ class MarkerView: ExpoView {
|
|
|
1000
1011
|
}
|
|
1001
1012
|
}
|
|
1002
1013
|
|
|
1003
|
-
func
|
|
1004
|
-
guard
|
|
1005
|
-
|
|
1014
|
+
func setContentWidth(_ width: Double) {
|
|
1015
|
+
guard contentWidth != width else { return }
|
|
1016
|
+
contentWidth = width
|
|
1006
1017
|
if !subviews.isEmpty {
|
|
1007
1018
|
refreshAnnotationAppearance(invalidateChildrenCache: true)
|
|
1008
1019
|
}
|
|
1009
1020
|
}
|
|
1010
1021
|
|
|
1011
|
-
func
|
|
1012
|
-
guard
|
|
1013
|
-
|
|
1022
|
+
func setContentHeight(_ height: Double) {
|
|
1023
|
+
guard contentHeight != height else { return }
|
|
1024
|
+
contentHeight = height
|
|
1014
1025
|
if !subviews.isEmpty {
|
|
1015
1026
|
refreshAnnotationAppearance(invalidateChildrenCache: true)
|
|
1016
1027
|
}
|
|
@@ -41,12 +41,12 @@ public class MarkerViewModule: Module {
|
|
|
41
41
|
view.setIconHeight(height)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
Prop("
|
|
45
|
-
view.
|
|
44
|
+
Prop("contentWidth") { (view: MarkerView, width: Double) in
|
|
45
|
+
view.setContentWidth(width)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
Prop("
|
|
49
|
-
view.
|
|
48
|
+
Prop("contentHeight") { (view: MarkerView, height: Double) in
|
|
49
|
+
view.setContentHeight(height)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
Prop("centerOffset") { (view: MarkerView, offset: [String: Double]) in
|
|
@@ -55,6 +55,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
55
55
|
+ (NSDictionary * _Nullable)calculatePathBoundsWithLatitudes:(NSArray<NSNumber *> *)latitudes
|
|
56
56
|
longitudes:(NSArray<NSNumber *> *)longitudes NS_SWIFT_NAME(calculatePathBounds(latitudes:longitudes:));
|
|
57
57
|
|
|
58
|
+
+ (double)calculateFitZoomWithLatitudes:(NSArray<NSNumber *> *)latitudes
|
|
59
|
+
longitudes:(NSArray<NSNumber *> *)longitudes
|
|
60
|
+
viewportWidthPx:(double)viewportWidthPx
|
|
61
|
+
viewportHeightPx:(double)viewportHeightPx
|
|
62
|
+
paddingPx:(double)paddingPx
|
|
63
|
+
minZoom:(int)minZoom
|
|
64
|
+
maxZoom:(int)maxZoom NS_SWIFT_NAME(calculateFitZoom(latitudes:longitudes:viewportWidthPx:viewportHeightPx:paddingPx:minZoom:maxZoom:));
|
|
65
|
+
|
|
58
66
|
+ (NSString *)encodeGeoHashWithLat:(double)lat
|
|
59
67
|
lon:(double)lon
|
|
60
68
|
precision:(int)precision NS_SWIFT_NAME(encodeGeoHash(lat:lon:precision:));
|
|
@@ -242,6 +242,33 @@
|
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
+ (double)calculateFitZoomWithLatitudes:(NSArray<NSNumber *> *)latitudes
|
|
246
|
+
longitudes:(NSArray<NSNumber *> *)longitudes
|
|
247
|
+
viewportWidthPx:(double)viewportWidthPx
|
|
248
|
+
viewportHeightPx:(double)viewportHeightPx
|
|
249
|
+
paddingPx:(double)paddingPx
|
|
250
|
+
minZoom:(int)minZoom
|
|
251
|
+
maxZoom:(int)maxZoom {
|
|
252
|
+
if (latitudes.count == 0 || latitudes.count != longitudes.count) {
|
|
253
|
+
return (double)minZoom;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
std::vector<gaodemap::GeoPoint> points;
|
|
257
|
+
points.reserve(latitudes.count);
|
|
258
|
+
for (NSUInteger i = 0; i < latitudes.count; i++) {
|
|
259
|
+
points.push_back({[latitudes[i] doubleValue], [longitudes[i] doubleValue]});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return gaodemap::calculateFitZoomForPoints(
|
|
263
|
+
points,
|
|
264
|
+
viewportWidthPx,
|
|
265
|
+
viewportHeightPx,
|
|
266
|
+
paddingPx,
|
|
267
|
+
minZoom,
|
|
268
|
+
maxZoom
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
245
272
|
+ (NSString *)encodeGeoHashWithLat:(double)lat
|
|
246
273
|
lon:(double)lon
|
|
247
274
|
precision:(int)precision {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-gaode-map",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.33",
|
|
4
4
|
"description": "A full-featured React Native AMap (Gaode Map) library for Expo, including map display, location, overlays, offline maps, and geometry utilities.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"android",
|
|
10
10
|
"ios",
|
|
11
11
|
"shared",
|
|
12
|
+
"scripts",
|
|
12
13
|
"app.plugin.js",
|
|
13
14
|
"expo-module.config.json",
|
|
14
15
|
"plugin/build"
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
"lint": "expo-module lint",
|
|
21
22
|
"test": "expo-module test",
|
|
22
23
|
"prepare": "expo-module prepare && yarn build:plugin",
|
|
24
|
+
"postinstall": "node scripts/check-expo-modules.js",
|
|
23
25
|
"prepublishOnly": "echo 'Skipping proofread check' && exit 0",
|
|
24
26
|
"expo-module": "expo-module",
|
|
25
27
|
"open:ios": "xed example/ios",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
function resolveProjectRoot() {
|
|
7
|
+
return process.env.INIT_CWD || process.env.npm_config_local_prefix || process.cwd();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function hasPackage(pkgName, projectRoot) {
|
|
11
|
+
try {
|
|
12
|
+
require.resolve(`${pkgName}/package.json`, { paths: [projectRoot] });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function printWarning(lines) {
|
|
20
|
+
const prefix = '[expo-gaode-map]';
|
|
21
|
+
console.warn(`${prefix} WARNING: Expo Modules infrastructure was not detected in this React Native project.`);
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
console.warn(`${prefix} ${line}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function main() {
|
|
28
|
+
const projectRoot = resolveProjectRoot();
|
|
29
|
+
|
|
30
|
+
// Skip non-React Native consumers.
|
|
31
|
+
if (!hasPackage('react-native', projectRoot)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const hasExpo = hasPackage('expo', projectRoot);
|
|
36
|
+
const hasExpoModulesCore = hasPackage('expo-modules-core', projectRoot);
|
|
37
|
+
const podfilePath = path.join(projectRoot, 'ios', 'Podfile');
|
|
38
|
+
const hasPodfile = fs.existsSync(podfilePath);
|
|
39
|
+
const hasUseExpoModules = hasPodfile
|
|
40
|
+
? /\buse_expo_modules!\b/m.test(fs.readFileSync(podfilePath, 'utf8'))
|
|
41
|
+
: true;
|
|
42
|
+
|
|
43
|
+
if (hasExpo && hasExpoModulesCore && hasUseExpoModules) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const details = [];
|
|
48
|
+
if (!hasExpo) {
|
|
49
|
+
details.push('Missing dependency: expo');
|
|
50
|
+
}
|
|
51
|
+
if (!hasExpoModulesCore) {
|
|
52
|
+
details.push('Missing dependency: expo-modules-core');
|
|
53
|
+
}
|
|
54
|
+
if (!hasUseExpoModules) {
|
|
55
|
+
details.push('iOS Podfile does not contain use_expo_modules!');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
printWarning([
|
|
59
|
+
...details,
|
|
60
|
+
'This package requires Expo Modules infrastructure even in bare React Native apps.',
|
|
61
|
+
'Suggested fix:',
|
|
62
|
+
' npx install-expo-modules@latest',
|
|
63
|
+
hasPodfile ? ' (then) cd ios && pod install' : null,
|
|
64
|
+
'Docs: https://docs.expo.dev/bare/installing-expo-modules/',
|
|
65
|
+
].filter(Boolean));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#include <cmath>
|
|
4
4
|
#include <map>
|
|
5
5
|
#include <algorithm>
|
|
6
|
+
#include <limits>
|
|
6
7
|
|
|
7
8
|
namespace gaodemap {
|
|
8
9
|
|
|
@@ -19,6 +20,54 @@ static inline double geo_toDegrees(double radians) {
|
|
|
19
20
|
return radians * kRadiansToDegrees;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
static inline double clampMercatorLatitude(double lat) {
|
|
24
|
+
static constexpr double kMaxMercatorLatitude = 85.05112878;
|
|
25
|
+
if (lat > kMaxMercatorLatitude) return kMaxMercatorLatitude;
|
|
26
|
+
if (lat < -kMaxMercatorLatitude) return -kMaxMercatorLatitude;
|
|
27
|
+
return lat;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static inline double mercatorX01(double lon) {
|
|
31
|
+
double wrapped = std::fmod(lon + 180.0, 360.0);
|
|
32
|
+
if (wrapped < 0.0) {
|
|
33
|
+
wrapped += 360.0;
|
|
34
|
+
}
|
|
35
|
+
return wrapped / 360.0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static inline double mercatorY01(double lat) {
|
|
39
|
+
const double clampedLat = clampMercatorLatitude(lat);
|
|
40
|
+
const double latRad = geo_toRadians(clampedLat);
|
|
41
|
+
const double y = (1.0 - std::asinh(std::tan(latRad)) / kPi) * 0.5;
|
|
42
|
+
if (y < 0.0) return 0.0;
|
|
43
|
+
if (y > 1.0) return 1.0;
|
|
44
|
+
return y;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static double wrappedSpan01(std::vector<double> xs) {
|
|
48
|
+
if (xs.size() <= 1) {
|
|
49
|
+
return 0.0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
std::sort(xs.begin(), xs.end());
|
|
53
|
+
double maxGap = 0.0;
|
|
54
|
+
|
|
55
|
+
for (size_t i = 0; i + 1 < xs.size(); ++i) {
|
|
56
|
+
const double gap = xs[i + 1] - xs[i];
|
|
57
|
+
if (gap > maxGap) {
|
|
58
|
+
maxGap = gap;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const double endGap = xs.front() + 1.0 - xs.back();
|
|
63
|
+
if (endGap > maxGap) {
|
|
64
|
+
maxGap = endGap;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const double span = 1.0 - maxGap;
|
|
68
|
+
return span < 0.0 ? 0.0 : span;
|
|
69
|
+
}
|
|
70
|
+
|
|
22
71
|
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
|
23
72
|
const double radLat1 = geo_toRadians(lat1);
|
|
24
73
|
const double radLat2 = geo_toRadians(lat2);
|
|
@@ -512,6 +561,69 @@ GeoPoint pixelToLatLng(double x, double y, int zoom) {
|
|
|
512
561
|
return {lat, lon};
|
|
513
562
|
}
|
|
514
563
|
|
|
564
|
+
double calculateFitZoomForPoints(
|
|
565
|
+
const std::vector<GeoPoint>& points,
|
|
566
|
+
double viewportWidthPx,
|
|
567
|
+
double viewportHeightPx,
|
|
568
|
+
double paddingPx,
|
|
569
|
+
int minZoom,
|
|
570
|
+
int maxZoom
|
|
571
|
+
) {
|
|
572
|
+
if (minZoom > maxZoom) {
|
|
573
|
+
std::swap(minZoom, maxZoom);
|
|
574
|
+
}
|
|
575
|
+
if (points.empty()) {
|
|
576
|
+
return static_cast<double>(minZoom);
|
|
577
|
+
}
|
|
578
|
+
if (points.size() == 1) {
|
|
579
|
+
return static_cast<double>(maxZoom);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const double safeViewportWidth = viewportWidthPx > 1.0 ? viewportWidthPx : 390.0;
|
|
583
|
+
const double safeViewportHeight = viewportHeightPx > 1.0 ? viewportHeightPx : 844.0;
|
|
584
|
+
const double safePadding = std::max(0.0, paddingPx);
|
|
585
|
+
const double availableWidth = std::max(1.0, safeViewportWidth - safePadding * 2.0);
|
|
586
|
+
const double availableHeight = std::max(1.0, safeViewportHeight - safePadding * 2.0);
|
|
587
|
+
|
|
588
|
+
std::vector<double> projectedXs;
|
|
589
|
+
projectedXs.reserve(points.size());
|
|
590
|
+
|
|
591
|
+
double minY = std::numeric_limits<double>::max();
|
|
592
|
+
double maxY = -std::numeric_limits<double>::max();
|
|
593
|
+
|
|
594
|
+
for (const auto& p : points) {
|
|
595
|
+
projectedXs.push_back(mercatorX01(p.lon));
|
|
596
|
+
const double y = mercatorY01(p.lat);
|
|
597
|
+
if (y < minY) minY = y;
|
|
598
|
+
if (y > maxY) maxY = y;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const double spanX = wrappedSpan01(projectedXs);
|
|
602
|
+
const double spanY = std::max(0.0, maxY - minY);
|
|
603
|
+
static constexpr double kTileSize = 256.0;
|
|
604
|
+
static constexpr double kSpanEpsilon = 1e-12;
|
|
605
|
+
|
|
606
|
+
const double zoomX = spanX <= kSpanEpsilon
|
|
607
|
+
? static_cast<double>(maxZoom)
|
|
608
|
+
: std::log2(availableWidth / (kTileSize * spanX));
|
|
609
|
+
const double zoomY = spanY <= kSpanEpsilon
|
|
610
|
+
? static_cast<double>(maxZoom)
|
|
611
|
+
: std::log2(availableHeight / (kTileSize * spanY));
|
|
612
|
+
|
|
613
|
+
double fitZoom = std::min(zoomX, zoomY);
|
|
614
|
+
if (!std::isfinite(fitZoom)) {
|
|
615
|
+
fitZoom = static_cast<double>(minZoom);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (fitZoom < static_cast<double>(minZoom)) {
|
|
619
|
+
fitZoom = static_cast<double>(minZoom);
|
|
620
|
+
} else if (fitZoom > static_cast<double>(maxZoom)) {
|
|
621
|
+
fitZoom = static_cast<double>(maxZoom);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return fitZoom;
|
|
625
|
+
}
|
|
626
|
+
|
|
515
627
|
// --- 批量地理围栏与热力图 ---
|
|
516
628
|
|
|
517
629
|
int findPointInPolygons(double pointLat, double pointLon, const std::vector<std::vector<GeoPoint>>& polygons) {
|
|
@@ -125,6 +125,27 @@ PixelResult latLngToPixel(double lat, double lon, int zoom);
|
|
|
125
125
|
*/
|
|
126
126
|
GeoPoint pixelToLatLng(double x, double y, int zoom);
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* 根据一组坐标点和视口尺寸计算“可同时看到所有点”的推荐缩放级别。
|
|
130
|
+
* 使用 Web Mercator 投影,在跨经线场景下会自动取更小经度跨度。
|
|
131
|
+
*
|
|
132
|
+
* @param points 坐标点集合,至少 1 个
|
|
133
|
+
* @param viewportWidthPx 视口宽度(像素)
|
|
134
|
+
* @param viewportHeightPx 视口高度(像素)
|
|
135
|
+
* @param paddingPx 四周预留的内边距(像素)
|
|
136
|
+
* @param minZoom 最小缩放级别
|
|
137
|
+
* @param maxZoom 最大缩放级别
|
|
138
|
+
* @return 推荐 zoom(已在 [minZoom, maxZoom] 范围内)
|
|
139
|
+
*/
|
|
140
|
+
double calculateFitZoomForPoints(
|
|
141
|
+
const std::vector<GeoPoint>& points,
|
|
142
|
+
double viewportWidthPx,
|
|
143
|
+
double viewportHeightPx,
|
|
144
|
+
double paddingPx,
|
|
145
|
+
int minZoom,
|
|
146
|
+
int maxZoom
|
|
147
|
+
);
|
|
148
|
+
|
|
128
149
|
// --- 批量地理围栏与热力图 ---
|
|
129
150
|
|
|
130
151
|
/**
|
|
@@ -167,6 +167,21 @@ void testGeometryEngineExtended() {
|
|
|
167
167
|
// Test trailing semicolon
|
|
168
168
|
assert(parsePolyline("116.4074,39.9042;").size() == 1);
|
|
169
169
|
|
|
170
|
+
// 10. calculateFitZoomForPoints
|
|
171
|
+
std::vector<GeoPoint> nearby = {
|
|
172
|
+
{39.9042, 116.4074}, // Beijing
|
|
173
|
+
{39.9142, 116.4174}
|
|
174
|
+
};
|
|
175
|
+
std::vector<GeoPoint> farAway = {
|
|
176
|
+
{39.9042, 116.4074}, // Beijing
|
|
177
|
+
{31.2304, 121.4737} // Shanghai
|
|
178
|
+
};
|
|
179
|
+
const double nearZoom = calculateFitZoomForPoints(nearby, 390, 844, 48, 3, 20);
|
|
180
|
+
const double farZoom = calculateFitZoomForPoints(farAway, 390, 844, 48, 3, 20);
|
|
181
|
+
assert(nearZoom > farZoom);
|
|
182
|
+
assert(nearZoom <= 20.0 && nearZoom >= 3.0);
|
|
183
|
+
assert(farZoom <= 20.0 && farZoom >= 3.0);
|
|
184
|
+
|
|
170
185
|
std::cout << "PASSED" << std::endl;
|
|
171
186
|
}
|
|
172
187
|
|