expo-gaode-map 2.2.15 → 2.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/expo/modules/gaodemap/ExpoGaodeMapView.kt +40 -1
  3. package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterView.kt +427 -61
  4. package/android/src/main/java/expo/modules/gaodemap/overlays/ClusterViewModule.kt +16 -0
  5. package/android/src/main/java/expo/modules/gaodemap/overlays/HeatMapView.kt +160 -25
  6. package/android/src/main/java/expo/modules/gaodemap/overlays/HeatMapViewModule.kt +13 -1
  7. package/android/src/main/java/expo/modules/gaodemap/overlays/MultiPointView.kt +165 -13
  8. package/android/src/main/java/expo/modules/gaodemap/overlays/MultiPointViewModule.kt +9 -1
  9. package/android/src/main/java/expo/modules/gaodemap/utils/BitmapDescriptorCache.kt +20 -0
  10. package/build/components/overlays/Cluster.d.ts.map +1 -1
  11. package/build/components/overlays/Cluster.js +6 -2
  12. package/build/components/overlays/Cluster.js.map +1 -1
  13. package/build/components/overlays/HeatMap.d.ts.map +1 -1
  14. package/build/components/overlays/HeatMap.js +12 -1
  15. package/build/components/overlays/HeatMap.js.map +1 -1
  16. package/build/index.d.ts +0 -1
  17. package/build/index.d.ts.map +1 -1
  18. package/build/index.js.map +1 -1
  19. package/build/types/overlays.types.d.ts +69 -14
  20. package/build/types/overlays.types.d.ts.map +1 -1
  21. package/build/types/overlays.types.js.map +1 -1
  22. package/build/utils/ModuleLoader.js +1 -1
  23. package/build/utils/ModuleLoader.js.map +1 -1
  24. package/ios/ExpoGaodeMapView.swift +44 -0
  25. package/ios/overlays/ClusterAnnotation.swift +32 -0
  26. package/ios/overlays/ClusterView.swift +251 -45
  27. package/ios/overlays/ClusterViewModule.swift +14 -0
  28. package/ios/overlays/CoordinateQuadTree.swift +291 -0
  29. package/ios/overlays/HeatMapView.swift +119 -5
  30. package/ios/overlays/HeatMapViewModule.swift +13 -1
  31. package/ios/overlays/MultiPointView.swift +160 -2
  32. package/ios/overlays/MultiPointViewModule.swift +22 -0
  33. package/package.json +1 -1
@@ -1,16 +1,27 @@
1
1
  package expo.modules.gaodemap.overlays
2
2
 
3
3
  import android.content.Context
4
+ import android.os.Looper
5
+ import android.util.Log
4
6
  import com.amap.api.maps.AMap
5
7
  import com.amap.api.maps.model.LatLng
6
8
  import com.amap.api.maps.model.TileOverlay
7
9
  import com.amap.api.maps.model.TileOverlayOptions
8
10
  import com.amap.api.maps.model.HeatmapTileProvider
11
+ import com.amap.api.maps.CameraUpdateFactory
9
12
  import expo.modules.kotlin.AppContext
10
13
  import expo.modules.kotlin.views.ExpoView
14
+ import java.util.concurrent.ExecutorService
15
+ import java.util.concurrent.Executors
11
16
 
12
17
  class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
13
18
 
19
+ private val executor: ExecutorService = Executors.newSingleThreadExecutor()
20
+ private val applyUpdateRunnable = Runnable { applyUpdateOnMain() }
21
+ @Volatile private var updateToken: Int = 0
22
+ @Volatile private var needsRebuild: Boolean = true
23
+ private var visible: Boolean = true
24
+
14
25
  private var heatmapOverlay: TileOverlay? = null
15
26
  private var aMap: AMap? = null
16
27
  private var dataList: MutableList<LatLng> = mutableListOf()
@@ -23,7 +34,8 @@ class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context,
23
34
  @Suppress("unused")
24
35
  fun setMap(map: AMap) {
25
36
  aMap = map
26
- createOrUpdateHeatMap()
37
+ needsRebuild = true
38
+ scheduleUpdate()
27
39
  }
28
40
 
29
41
  /**
@@ -34,12 +46,12 @@ class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context,
34
46
  data.forEach { point ->
35
47
  val lat = (point["latitude"] as? Number)?.toDouble()
36
48
  val lng = (point["longitude"] as? Number)?.toDouble()
37
- // 坐标验证
38
49
  if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
39
50
  dataList.add(LatLng(lat, lng))
40
51
  }
41
52
  }
42
- createOrUpdateHeatMap()
53
+ needsRebuild = true
54
+ scheduleUpdate()
43
55
  }
44
56
 
45
57
  /**
@@ -47,7 +59,8 @@ class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context,
47
59
  */
48
60
  fun setRadius(radiusValue: Int) {
49
61
  radius = radiusValue
50
- createOrUpdateHeatMap()
62
+ needsRebuild = true
63
+ scheduleUpdate()
51
64
  }
52
65
 
53
66
  /**
@@ -55,41 +68,162 @@ class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context,
55
68
  */
56
69
  fun setOpacity(opacityValue: Double) {
57
70
  opacity = opacityValue
58
- createOrUpdateHeatMap()
71
+ applyOverlayOpacity()
59
72
  }
60
-
73
+
74
+ fun setVisible(visibleValue: Boolean) {
75
+ if (!visibleValue) {
76
+ visible = false
77
+ updateToken += 1
78
+ removeCallbacks(applyUpdateRunnable)
79
+ applyOverlayVisibility()
80
+ return
81
+ }
82
+
83
+ visible = true
84
+ applyOverlayVisibility()
85
+
86
+ if (dataList.isEmpty()) {
87
+ return
88
+ }
89
+
90
+ if (heatmapOverlay == null || needsRebuild) {
91
+ scheduleUpdate()
92
+ } else {
93
+ applyOverlayOpacity()
94
+ forceRefresh()
95
+ }
96
+ }
97
+
98
+ private fun scheduleUpdate() {
99
+ updateToken += 1
100
+ removeCallbacks(applyUpdateRunnable)
101
+ postDelayed(applyUpdateRunnable, 32)
102
+ }
103
+
104
+ private fun applyOverlayVisibility() {
105
+ if (Looper.myLooper() != Looper.getMainLooper()) {
106
+ post { applyOverlayVisibility() }
107
+ return
108
+ }
109
+ val overlay = heatmapOverlay ?: return
110
+ runCatching {
111
+ overlay.javaClass.getMethod("setVisible", Boolean::class.javaPrimitiveType)
112
+ .invoke(overlay, visible)
113
+ }.onFailure {
114
+ if (!visible) {
115
+ overlay.remove()
116
+ heatmapOverlay = null
117
+ needsRebuild = true
118
+ }
119
+ }
120
+ }
121
+
122
+ private fun applyOverlayOpacity() {
123
+ if (Looper.myLooper() != Looper.getMainLooper()) {
124
+ post { applyOverlayOpacity() }
125
+ return
126
+ }
127
+ val overlay = heatmapOverlay ?: return
128
+ val opacityValue = opacity.coerceIn(0.0, 1.0)
129
+ val transparency = (1.0 - opacityValue).toFloat()
130
+ runCatching {
131
+ overlay.javaClass.getMethod("setTransparency", Float::class.javaPrimitiveType)
132
+ .invoke(overlay, transparency)
133
+ }
134
+ }
135
+
61
136
  /**
62
- * 创建或更新热力图
137
+ * 在主线程应用更新(构建 TileProvider 在后台线程)
63
138
  */
64
- private fun createOrUpdateHeatMap() {
65
- aMap?.let { map ->
66
- if (dataList.isNotEmpty()) {
67
- // 移除旧的热力图
68
- heatmapOverlay?.remove()
69
-
70
- // 创建热力图提供者
71
- val builder = HeatmapTileProvider.Builder()
72
- builder.data(dataList)
73
- builder.radius(radius)
74
-
75
- // 创建热力图图层
76
- val heatmapTileProvider = builder.build()
77
- val tileOverlayOptions = TileOverlayOptions()
78
- .tileProvider(heatmapTileProvider)
79
-
80
- heatmapOverlay = map.addTileOverlay(tileOverlayOptions)
81
- // 注意:TileOverlay 不支持 transparency 属性,透明度通过 HeatmapTileProvider 控制
139
+ private fun applyUpdateOnMain() {
140
+ if (Looper.myLooper() != Looper.getMainLooper()) {
141
+ post { applyUpdateOnMain() }
142
+ return
143
+ }
144
+
145
+ val map = aMap ?: return
146
+ val token = updateToken
147
+ val pointsSnapshot = ArrayList(dataList)
148
+ val radiusValue = radius.coerceIn(10, 200)
149
+ val opacityValue = opacity.coerceIn(0.0, 1.0)
150
+
151
+ if (!visible) {
152
+ applyOverlayVisibility()
153
+ return
154
+ }
155
+
156
+ if (pointsSnapshot.isEmpty()) {
157
+ heatmapOverlay?.remove()
158
+ heatmapOverlay = null
159
+ return
160
+ }
161
+
162
+ if (heatmapOverlay != null && !needsRebuild) {
163
+ applyOverlayVisibility()
164
+ applyOverlayOpacity()
165
+ return
166
+ }
167
+
168
+ executor.execute {
169
+ try {
170
+ val provider = HeatmapTileProvider.Builder()
171
+ .data(pointsSnapshot)
172
+ .radius(radiusValue)
173
+ .build()
174
+
175
+ post {
176
+ if (token != updateToken) {
177
+ return@post
178
+ }
179
+ if (aMap !== map) {
180
+ return@post
181
+ }
182
+ if (!visible) {
183
+ return@post
184
+ }
185
+
186
+ heatmapOverlay?.remove()
187
+
188
+ val options = TileOverlayOptions().tileProvider(provider)
189
+
190
+ runCatching {
191
+ options.javaClass.getMethod("zIndex", Float::class.javaPrimitiveType)
192
+ .invoke(options, 1f)
193
+ }
194
+
195
+ runCatching {
196
+ options.javaClass.getMethod("transparency", Float::class.javaPrimitiveType)
197
+ .invoke(options, (1.0 - opacityValue).toFloat())
198
+ }
199
+
200
+ heatmapOverlay = map.addTileOverlay(options)
201
+ needsRebuild = false
202
+ runCatching { heatmapOverlay?.clearTileCache() }
203
+ applyOverlayVisibility()
204
+ applyOverlayOpacity()
205
+ forceRefresh()
206
+ }
207
+ } catch (t: Throwable) {
208
+ Log.e("HeatMapView", "Failed to build heatmap", t)
82
209
  }
83
210
  }
84
211
  }
85
212
 
213
+ private fun forceRefresh() {
214
+ runCatching { aMap?.moveCamera(CameraUpdateFactory.zoomBy(0f)) }
215
+ }
216
+
86
217
  /**
87
218
  * 移除热力图
88
219
  */
89
220
  fun removeHeatMap() {
221
+ updateToken += 1
222
+ removeCallbacks(applyUpdateRunnable)
90
223
  heatmapOverlay?.remove()
91
224
  heatmapOverlay = null
92
225
  dataList.clear()
226
+ needsRebuild = true
93
227
  }
94
228
 
95
229
  override fun onDetachedFromWindow() {
@@ -99,6 +233,7 @@ class HeatMapView(context: Context, appContext: AppContext) : ExpoView(context,
99
233
  if (parent == null) {
100
234
  removeHeatMap()
101
235
  aMap = null
236
+ runCatching { executor.shutdownNow() }
102
237
  }
103
238
  }
104
239
  }
@@ -14,6 +14,10 @@ class HeatMapViewModule : Module() {
14
14
  Prop<List<Map<String, Any>>>("data") { view: HeatMapView, data ->
15
15
  view.setData(data)
16
16
  }
17
+
18
+ Prop<Boolean>("visible") { view: HeatMapView, visible ->
19
+ view.setVisible(visible)
20
+ }
17
21
 
18
22
  Prop<Int>("radius") { view: HeatMapView, radius ->
19
23
  view.setRadius(radius)
@@ -22,6 +26,14 @@ class HeatMapViewModule : Module() {
22
26
  Prop<Double>("opacity") { view: HeatMapView, opacity ->
23
27
  view.setOpacity(opacity)
24
28
  }
29
+
30
+ Prop<Map<String, Any>?>("gradient") { view: HeatMapView, gradient ->
31
+ // view.setGradient(gradient) // TODO: Implement gradient on Android
32
+ }
33
+
34
+ Prop<Boolean>("allowRetinaAdapting") { view: HeatMapView, allow ->
35
+ // iOS only, ignore on Android
36
+ }
25
37
  }
26
38
  }
27
- }
39
+ }
@@ -1,24 +1,47 @@
1
1
  package expo.modules.gaodemap.overlays
2
2
 
3
3
  import android.content.Context
4
+ import android.graphics.Bitmap
5
+ import android.graphics.BitmapFactory
6
+ import android.os.Handler
7
+ import android.os.Looper
8
+ import androidx.core.graphics.scale
4
9
 
5
10
  import com.amap.api.maps.AMap
11
+ import com.amap.api.maps.model.BitmapDescriptor
6
12
  import com.amap.api.maps.model.BitmapDescriptorFactory
7
13
  import com.amap.api.maps.model.LatLng
8
14
  import com.amap.api.maps.model.MultiPointItem
9
15
  import com.amap.api.maps.model.MultiPointOverlay
10
16
  import com.amap.api.maps.model.MultiPointOverlayOptions
17
+ import expo.modules.gaodemap.companion.BitmapDescriptorCache
18
+ import expo.modules.gaodemap.companion.IconBitmapCache
11
19
  import expo.modules.kotlin.AppContext
20
+ import expo.modules.kotlin.viewevent.EventDispatcher
12
21
 
13
22
  import expo.modules.kotlin.views.ExpoView
23
+ import java.io.InputStream
24
+ import java.net.HttpURLConnection
25
+ import java.net.URL
26
+ import kotlin.concurrent.thread
14
27
 
15
28
  class MultiPointView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
16
29
 
17
-
30
+ private val onMultiPointPress by EventDispatcher()
18
31
  private var multiPointOverlay: MultiPointOverlay? = null
19
32
  private var aMap: AMap? = null
20
33
  private var points: MutableList<MultiPointItem> = mutableListOf()
21
34
 
35
+ private var pendingIconUri: String? = null
36
+ private var currentIconDescriptor: BitmapDescriptor? = null
37
+ private val mainHandler = Handler(Looper.getMainLooper())
38
+
39
+ private var anchorX: Float = 0.5f
40
+ private var anchorY: Float = 0.5f
41
+
42
+ private var iconWidth: Int? = null
43
+ private var iconHeight: Int? = null
44
+
22
45
  /**
23
46
  * 设置地图实例
24
47
  */
@@ -35,7 +58,7 @@ class MultiPointView(context: Context, appContext: AppContext) : ExpoView(contex
35
58
  pointsList.forEach { point ->
36
59
  val lat = (point["latitude"] as? Number)?.toDouble()
37
60
  val lng = (point["longitude"] as? Number)?.toDouble()
38
- val id = point["id"] as? String ?: ""
61
+ val id = point["customerId"] as? String ?: point["id"] as? String ?: ""
39
62
 
40
63
  // 坐标验证
41
64
  if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
@@ -51,18 +74,32 @@ class MultiPointView(context: Context, appContext: AppContext) : ExpoView(contex
51
74
  * 设置图标
52
75
  */
53
76
  fun setIcon(iconUri: String?) {
77
+ pendingIconUri = iconUri
78
+ if (iconUri != null) {
79
+ loadAndSetIcon(iconUri)
80
+ } else {
81
+ currentIconDescriptor = null
82
+ createOrUpdateMultiPoint()
83
+ }
84
+ }
54
85
 
55
- // 简化处理,实际需要实现图片加载
56
- createOrUpdateMultiPoint()
86
+ fun setIconWidth(width: Int?) {
87
+ iconWidth = width
88
+ pendingIconUri?.let { loadAndSetIcon(it) }
89
+ }
90
+
91
+ fun setIconHeight(height: Int?) {
92
+ iconHeight = height
93
+ pendingIconUri?.let { loadAndSetIcon(it) }
57
94
  }
58
95
 
59
96
  /**
60
97
  * 设置锚点
61
98
  */
62
99
  fun setAnchor(anchor: Map<String, Float>) {
63
- val x = anchor["x"] ?: 0.5f
64
- val y = anchor["y"] ?: 0.5f
65
- multiPointOverlay?.setAnchor(x, y)
100
+ anchorX = anchor["x"] ?: 0.5f
101
+ anchorY = anchor["y"] ?: 0.5f
102
+ multiPointOverlay?.setAnchor(anchorX, anchorY)
66
103
  }
67
104
 
68
105
  /**
@@ -76,18 +113,133 @@ class MultiPointView(context: Context, appContext: AppContext) : ExpoView(contex
76
113
 
77
114
  // 创建海量点选项
78
115
  val overlayOptions = MultiPointOverlayOptions()
79
- overlayOptions.icon(BitmapDescriptorFactory.defaultMarker())
80
- overlayOptions.anchor(0.5f, 0.5f)
116
+ // 使用加载的图标或默认图标
117
+ val icon = currentIconDescriptor ?: BitmapDescriptorFactory.defaultMarker()
118
+ overlayOptions.icon(icon)
119
+ overlayOptions.anchor(anchorX, anchorY)
81
120
 
82
121
  // 创建海量点覆盖物
83
122
  multiPointOverlay = map.addMultiPointOverlay(overlayOptions)
84
- multiPointOverlay?.items = points
85
-
86
- // 注意:MultiPointOverlay 在高德地图 Android SDK 中不直接支持点击事件
87
- // 如果需要点击事件,需要使用 Marker 或其他方式实现
123
+ multiPointOverlay?.items = points
88
124
  }
89
125
  }
90
126
  }
127
+
128
+ fun handleMultiPointClick(item: MultiPointItem): Boolean {
129
+ val index = points.indexOfFirst { it.customerId == item.customerId }
130
+ if (index != -1) {
131
+ onMultiPointPress(mapOf(
132
+ "id" to item.customerId, // 兼容旧版
133
+ "customerId" to item.customerId,
134
+ "index" to index, // 添加 index 字段
135
+ "latitude" to item.latLng.latitude,
136
+ "longitude" to item.latLng.longitude
137
+ ))
138
+ return true
139
+ }
140
+ return false
141
+ }
142
+
143
+ private fun loadAndSetIcon(iconUri: String) {
144
+ val w = iconWidth ?: 0
145
+ val h = iconHeight ?: 0
146
+ val cacheKey = "multipoint|$iconUri|$w|$h"
147
+
148
+ // 尝试从缓存获取
149
+ BitmapDescriptorCache.get(cacheKey)?.let {
150
+ currentIconDescriptor = it
151
+ createOrUpdateMultiPoint()
152
+ return
153
+ }
154
+
155
+ when {
156
+ iconUri.startsWith("http") -> {
157
+ loadImageFromUrl(iconUri) { bitmap ->
158
+ processBitmap(bitmap, cacheKey)
159
+ }
160
+ }
161
+ iconUri.startsWith("file://") -> {
162
+ val path = iconUri.substring(7)
163
+ val bitmap = BitmapFactory.decodeFile(path)
164
+ processBitmap(bitmap, cacheKey)
165
+ }
166
+ else -> {
167
+ val resId = context.resources.getIdentifier(iconUri, "drawable", context.packageName)
168
+ if (resId != 0) {
169
+ val bitmap = BitmapFactory.decodeResource(context.resources, resId)
170
+ processBitmap(bitmap, cacheKey)
171
+ } else {
172
+ currentIconDescriptor = null
173
+ mainHandler.post { createOrUpdateMultiPoint() }
174
+ }
175
+ }
176
+ }
177
+ }
178
+
179
+ private fun processBitmap(bitmap: Bitmap?, cacheKey: String) {
180
+ if (bitmap != null) {
181
+ var finalBitmap = bitmap
182
+ if (iconWidth != null || iconHeight != null) {
183
+ var w = iconWidth?.let { dpToPx(it) } ?: 0
184
+ var h = iconHeight?.let { dpToPx(it) } ?: 0
185
+
186
+ if (w > 0 && h == 0) {
187
+ h = (bitmap.height * (w.toFloat() / bitmap.width)).toInt()
188
+ } else if (h > 0 && w == 0) {
189
+ w = (bitmap.width * (h.toFloat() / bitmap.height)).toInt()
190
+ } else if (w == 0 && h == 0) {
191
+ w = bitmap.width
192
+ h = bitmap.height
193
+ }
194
+
195
+ try {
196
+ finalBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true)
197
+ } catch (e: Exception) {
198
+ e.printStackTrace()
199
+ }
200
+ }
201
+
202
+ val descriptor = BitmapDescriptorFactory.fromBitmap(finalBitmap)
203
+ BitmapDescriptorCache.putDescriptor(cacheKey, descriptor)
204
+ currentIconDescriptor = descriptor
205
+ } else {
206
+ currentIconDescriptor = null
207
+ }
208
+ mainHandler.post { createOrUpdateMultiPoint() }
209
+ }
210
+
211
+ private fun dpToPx(dp: Int): Int {
212
+ val density = context.resources.displayMetrics.density
213
+ return (dp * density).toInt()
214
+ }
215
+
216
+ private fun loadImageFromUrl(url: String, callback: (Bitmap?) -> Unit) {
217
+ thread {
218
+ var connection: HttpURLConnection? = null
219
+ var inputStream: InputStream? = null
220
+ try {
221
+ val urlConnection = URL(url)
222
+ connection = urlConnection.openConnection() as HttpURLConnection
223
+ connection.connectTimeout = 10000
224
+ connection.readTimeout = 10000
225
+ connection.doInput = true
226
+ connection.connect()
227
+
228
+ if (connection.responseCode == HttpURLConnection.HTTP_OK) {
229
+ inputStream = connection.inputStream
230
+ val bitmap = BitmapFactory.decodeStream(inputStream)
231
+ callback(bitmap)
232
+ } else {
233
+ callback(null)
234
+ }
235
+ } catch (_: Exception) {
236
+ callback(null)
237
+ } finally {
238
+ inputStream?.close()
239
+ connection?.disconnect()
240
+ }
241
+ }
242
+ }
91
243
 
92
244
  /**
93
245
  * 移除海量点
@@ -11,7 +11,7 @@ class MultiPointViewModule : Module() {
11
11
  Name("MultiPointView")
12
12
 
13
13
  View(MultiPointView::class) {
14
- Events("onPress")
14
+ Events("onMultiPointPress")
15
15
 
16
16
  Prop<List<Map<String, Any>>>("points") { view: MultiPointView, points ->
17
17
  view.setPoints(points)
@@ -21,6 +21,14 @@ class MultiPointViewModule : Module() {
21
21
  view.setIcon(icon)
22
22
  }
23
23
 
24
+ Prop<Int?>("iconWidth") { view: MultiPointView, width: Int? ->
25
+ view.setIconWidth(width)
26
+ }
27
+
28
+ Prop<Int?>("iconHeight") { view: MultiPointView, height: Int? ->
29
+ view.setIconHeight(height)
30
+ }
31
+
24
32
  Prop<Map<String, Float>>("anchor"){ view: MultiPointView, anchor ->
25
33
  view.setAnchor(anchor)
26
34
  }
@@ -0,0 +1,20 @@
1
+ package expo.modules.gaodemap.utils
2
+
3
+ import com.amap.api.maps.model.BitmapDescriptor
4
+ import java.util.concurrent.ConcurrentHashMap
5
+
6
+ object BitmapDescriptorCache {
7
+ private val cache = ConcurrentHashMap<String, BitmapDescriptor>()
8
+
9
+ fun get(key: String): BitmapDescriptor? {
10
+ return cache[key]
11
+ }
12
+
13
+ fun put(key: String, descriptor: BitmapDescriptor) {
14
+ cache[key] = descriptor
15
+ }
16
+
17
+ fun clear() {
18
+ cache.clear()
19
+ }
20
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"Cluster.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/Cluster.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,KAAK,EAAE,YAAY,qBAEnC;;AAgCD,wBAAkD"}
1
+ {"version":3,"file":"Cluster.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/Cluster.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,KAAK,EAAE,YAAY,qBAEnC;;AAqCD,wBAAkD"}
@@ -27,8 +27,12 @@ function arePropsEqual(prevProps, nextProps) {
27
27
  if (prevProps.minClusterSize !== nextProps.minClusterSize) {
28
28
  return false;
29
29
  }
30
- // 比较 onPress 回调
31
- if (prevProps.onPress !== nextProps.onPress) {
30
+ // 比较 clusterBuckets
31
+ if (prevProps.clusterBuckets !== nextProps.clusterBuckets) {
32
+ return false;
33
+ }
34
+ // 比较 onClusterPress 回调
35
+ if (prevProps.onClusterPress !== nextProps.onClusterPress) {
32
36
  return false;
33
37
  }
34
38
  // 其他属性相同,不重新渲染
@@ -1 +1 @@
1
- {"version":3,"file":"Cluster.js","sourceRoot":"","sources":["../../../src/components/overlays/Cluster.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,aAAa,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAAmB;IAClC,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,SAAuB,EAAE,SAAuB;IACrE,uBAAuB;IACvB,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,YAAY;IACZ,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oBAAoB;IACpB,IAAI,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gBAAgB;IAChB,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,WAAW;AACX,eAAe,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\nimport type { ClusterProps } from '../../types';\n\nconst NativeCluster = requireNativeViewManager('ClusterView');\n\n/**\n * 高德地图点聚合组件\n *\n * @param props 点聚合组件的属性配置\n * @returns 渲染原生点聚合组件\n */\nfunction Cluster(props: ClusterProps) {\n return <NativeCluster {...props} />;\n}\n\n/**\n * 🔑 性能优化:浅比较关键属性\n * 只检查最常变化的属性,避免深度比较开销\n */\nfunction arePropsEqual(prevProps: ClusterProps, nextProps: ClusterProps): boolean {\n // 比较 points 数组引用(最常变化)\n if (prevProps.points !== nextProps.points) {\n return false;\n }\n \n // 比较 radius\n if (prevProps.radius !== nextProps.radius) {\n return false;\n }\n \n // 比较 minClusterSize\n if (prevProps.minClusterSize !== nextProps.minClusterSize) {\n return false;\n }\n \n // 比较 onPress 回调\n if (prevProps.onPress !== nextProps.onPress) {\n return false;\n }\n \n // 其他属性相同,不重新渲染\n return true;\n}\n\n// 导出优化后的组件\nexport default React.memo(Cluster, arePropsEqual);\n"]}
1
+ {"version":3,"file":"Cluster.js","sourceRoot":"","sources":["../../../src/components/overlays/Cluster.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,aAAa,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;AAE9D;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAAmB;IAClC,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,SAAuB,EAAE,SAAuB;IACrE,uBAAuB;IACvB,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,YAAY;IACZ,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oBAAoB;IACpB,IAAI,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oBAAoB;IACpB,IAAI,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uBAAuB;IACvB,IAAI,SAAS,CAAC,cAAc,KAAK,SAAS,CAAC,cAAc,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,eAAe;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAED,WAAW;AACX,eAAe,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\nimport type { ClusterProps } from '../../types';\n\nconst NativeCluster = requireNativeViewManager('ClusterView');\n\n/**\n * 高德地图点聚合组件\n *\n * @param props 点聚合组件的属性配置\n * @returns 渲染原生点聚合组件\n */\nfunction Cluster(props: ClusterProps) {\n return <NativeCluster {...props} />;\n}\n\n/**\n * 🔑 性能优化:浅比较关键属性\n * 只检查最常变化的属性,避免深度比较开销\n */\nfunction arePropsEqual(prevProps: ClusterProps, nextProps: ClusterProps): boolean {\n // 比较 points 数组引用(最常变化)\n if (prevProps.points !== nextProps.points) {\n return false;\n }\n \n // 比较 radius\n if (prevProps.radius !== nextProps.radius) {\n return false;\n }\n \n // 比较 minClusterSize\n if (prevProps.minClusterSize !== nextProps.minClusterSize) {\n return false;\n }\n \n // 比较 clusterBuckets\n if (prevProps.clusterBuckets !== nextProps.clusterBuckets) {\n return false;\n }\n \n // 比较 onClusterPress 回调\n if (prevProps.onClusterPress !== nextProps.onClusterPress) {\n return false;\n }\n \n // 其他属性相同,不重新渲染\n return true;\n}\n\n// 导出优化后的组件\nexport default React.memo(Cluster, arePropsEqual);\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"HeatMap.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/HeatMap.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,KAAK,EAAE,YAAY,qBAEnC;;AAqBD,wBAAkD"}
1
+ {"version":3,"file":"HeatMap.d.ts","sourceRoot":"","sources":["../../../src/components/overlays/HeatMap.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD;;;;;GAKG;AACH,iBAAS,OAAO,CAAC,KAAK,EAAE,YAAY,qBASnC;;AAiCD,wBAAkD"}
@@ -1,5 +1,6 @@
1
1
  import { requireNativeViewManager } from 'expo-modules-core';
2
2
  import * as React from 'react';
3
+ import { StyleSheet } from 'react-native';
3
4
  const NativeHeatMap = requireNativeViewManager('HeatMapView');
4
5
  /**
5
6
  * 高德地图热力图组件
@@ -8,8 +9,15 @@ const NativeHeatMap = requireNativeViewManager('HeatMapView');
8
9
  * @returns 渲染高德地图原生热力图组件
9
10
  */
10
11
  function HeatMap(props) {
11
- return <NativeHeatMap {...props}/>;
12
+ return (<NativeHeatMap {...props} collapsable={false} pointerEvents="none" style={styles.hidden}/>);
12
13
  }
14
+ const styles = StyleSheet.create({
15
+ hidden: {
16
+ width: 0,
17
+ height: 0,
18
+ backgroundColor: 'transparent',
19
+ },
20
+ });
13
21
  /**
14
22
  * 🔑 性能优化:浅比较关键属性
15
23
  */
@@ -18,6 +26,9 @@ function arePropsEqual(prevProps, nextProps) {
18
26
  if (prevProps.data !== nextProps.data) {
19
27
  return false;
20
28
  }
29
+ if (prevProps.visible !== nextProps.visible) {
30
+ return false;
31
+ }
21
32
  // 比较样式属性
22
33
  if (prevProps.radius !== nextProps.radius ||
23
34
  prevProps.opacity !== nextProps.opacity) {
@@ -1 +1 @@
1
- {"version":3,"file":"HeatMap.js","sourceRoot":"","sources":["../../../src/components/overlays/HeatMap.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,aAAa,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;AAG9D;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAAmB;IAClC,OAAO,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,EAAG,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,SAAuB,EAAE,SAAuB;IACrE,qBAAqB;IACrB,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS;IACT,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QACrC,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,WAAW;AACX,eAAe,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\nimport type { HeatMapProps } from '../../types';\n\nconst NativeHeatMap = requireNativeViewManager('HeatMapView');\n\n\n/**\n * 高德地图热力图组件\n *\n * @param props - 热力图配置属性,继承自NativeHeatMap组件的属性\n * @returns 渲染高德地图原生热力图组件\n */\nfunction HeatMap(props: HeatMapProps) {\n return <NativeHeatMap {...props} />;\n}\n\n/**\n * 🔑 性能优化:浅比较关键属性\n */\nfunction arePropsEqual(prevProps: HeatMapProps, nextProps: HeatMapProps): boolean {\n // 比较 data 数组引用(最常变化)\n if (prevProps.data !== nextProps.data) {\n return false;\n }\n \n // 比较样式属性\n if (prevProps.radius !== nextProps.radius ||\n prevProps.opacity !== nextProps.opacity) {\n return false;\n }\n \n return true;\n}\n\n// 导出优化后的组件\nexport default React.memo(HeatMap, arePropsEqual);\n"]}
1
+ {"version":3,"file":"HeatMap.js","sourceRoot":"","sources":["../../../src/components/overlays/HeatMap.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,MAAM,aAAa,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;AAG9D;;;;;GAKG;AACH,SAAS,OAAO,CAAC,KAAmB;IAClC,OAAO,CACL,CAAC,aAAa,CACZ,IAAI,KAAK,CAAC,CACV,WAAW,CAAC,CAAC,KAAK,CAAC,CACnB,aAAa,CAAC,MAAM,CACpB,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EACrB,CACH,CAAC;AACJ,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE;QACN,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,eAAe,EAAE,aAAa;KAC/B;CACF,CAAC,CAAC;AAEH;;GAEG;AACH,SAAS,aAAa,CAAC,SAAuB,EAAE,SAAuB;IACrE,qBAAqB;IACrB,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,SAAS;IACT,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;QACrC,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,WAAW;AACX,eAAe,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC","sourcesContent":["import { requireNativeViewManager } from 'expo-modules-core';\nimport * as React from 'react';\nimport { StyleSheet } from 'react-native';\nimport type { HeatMapProps } from '../../types';\n\nconst NativeHeatMap = requireNativeViewManager('HeatMapView');\n\n\n/**\n * 高德地图热力图组件\n *\n * @param props - 热力图配置属性,继承自NativeHeatMap组件的属性\n * @returns 渲染高德地图原生热力图组件\n */\nfunction HeatMap(props: HeatMapProps) {\n return (\n <NativeHeatMap\n {...props}\n collapsable={false}\n pointerEvents=\"none\"\n style={styles.hidden}\n />\n );\n}\n\nconst styles = StyleSheet.create({\n hidden: {\n width: 0,\n height: 0,\n backgroundColor: 'transparent',\n },\n});\n\n/**\n * 🔑 性能优化:浅比较关键属性\n */\nfunction arePropsEqual(prevProps: HeatMapProps, nextProps: HeatMapProps): boolean {\n // 比较 data 数组引用(最常变化)\n if (prevProps.data !== nextProps.data) {\n return false;\n }\n\n if (prevProps.visible !== nextProps.visible) {\n return false;\n }\n \n // 比较样式属性\n if (prevProps.radius !== nextProps.radius ||\n prevProps.opacity !== nextProps.opacity) {\n return false;\n }\n \n return true;\n}\n\n// 导出优化后的组件\nexport default React.memo(HeatMap, arePropsEqual);\n"]}
package/build/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export * from './types';
2
2
  export { default as ExpoGaodeMapModule } from './ExpoGaodeMapModule';
3
3
  export { default as MapView } from './ExpoGaodeMapView';
4
- export type { MapViewRef } from './ExpoGaodeMapView';
5
4
  export { useMap } from './components/MapContext';
6
5
  export { MapUI } from './components/MapUI';
7
6
  export { Marker, Polyline, Polygon, Circle, HeatMap, MultiPoint, Cluster, } from './components/overlays';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,SAAS,CAAC;AAExB,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGrE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,GACR,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,QAAQ,EACR,UAAU,EACV,MAAM,GACP,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,iBAAiB,EAAE,UAAU;AAC7B,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,eAAe,EACf,cAAc,GACf,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,oBAAoB,EACpB,cAAc,GACf,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEjF,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAM/B;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,wOAGjC,CAAA;AAGF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,SAAS,CAAC;AAExB,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAGrE,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAG3C,OAAO,EACL,MAAM,EACN,QAAQ,EACR,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,OAAO,GACR,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,QAAQ,EACR,UAAU,EACV,MAAM,GACP,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,iBAAiB,EAAE,UAAU;AAC7B,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,eAAe,EACf,cAAc,GACf,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,oBAAoB,EACpB,cAAc,GACf,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEjF,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAM/B;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,wOAGjC,CAAA;AAGF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAG/D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC"}