expo-gaode-map-search 1.1.2-next.1 → 1.1.2-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/expo/modules/gaodemap/search/ExpoGaodeMapSearchModule.kt +354 -0
- package/ios/ExpoGaodeMapSearchModule.swift +415 -0
- package/package.json +15 -3
- package/src/ExpoGaodeMapSearch.types.ts +179 -0
- package/src/ExpoGaodeMapSearchModule.ts +46 -0
- package/src/index.ts +170 -0
- package/.eslintrc.js +0 -2
- package/package.json.backup +0 -49
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
package expo.modules.gaodemap.search
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.amap.api.services.core.LatLonPoint
|
|
5
|
+
import com.amap.api.services.core.PoiItem
|
|
6
|
+
import com.amap.api.services.help.Inputtips
|
|
7
|
+
import com.amap.api.services.help.InputtipsQuery
|
|
8
|
+
import com.amap.api.services.help.Tip
|
|
9
|
+
import com.amap.api.services.poisearch.PoiResult
|
|
10
|
+
import com.amap.api.services.poisearch.PoiSearch
|
|
11
|
+
import com.amap.api.services.route.RouteSearch
|
|
12
|
+
import com.amap.api.services.routepoisearch.RoutePOISearch
|
|
13
|
+
import com.amap.api.services.routepoisearch.RoutePOISearchQuery
|
|
14
|
+
import com.amap.api.services.routepoisearch.RoutePOISearchResult
|
|
15
|
+
import expo.modules.kotlin.Promise
|
|
16
|
+
import expo.modules.kotlin.modules.Module
|
|
17
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
18
|
+
|
|
19
|
+
class ExpoGaodeMapSearchModule : Module() {
|
|
20
|
+
private val context: Context
|
|
21
|
+
get() = appContext.reactContext ?: throw Exception("React context is null")
|
|
22
|
+
|
|
23
|
+
override fun definition() = ModuleDefinition {
|
|
24
|
+
Name("ExpoGaodeMapSearch")
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* POI 搜索
|
|
28
|
+
*/
|
|
29
|
+
AsyncFunction("searchPOI") { options: Map<String, Any?>, promise: Promise ->
|
|
30
|
+
try {
|
|
31
|
+
val keyword = options["keyword"] as? String
|
|
32
|
+
?: throw Exception("keyword is required")
|
|
33
|
+
|
|
34
|
+
val city = options["city"] as? String ?: ""
|
|
35
|
+
val types = options["types"] as? String ?: ""
|
|
36
|
+
val pageSize = (options["pageSize"] as? Number)?.toInt() ?: 20
|
|
37
|
+
val pageNum = (options["pageNum"] as? Number)?.toInt() ?: 1
|
|
38
|
+
|
|
39
|
+
val query = PoiSearch.Query(keyword, types, city)
|
|
40
|
+
query.pageSize = pageSize
|
|
41
|
+
query.pageNum = pageNum - 1 // 高德 SDK 从 0 开始
|
|
42
|
+
|
|
43
|
+
val poiSearch = PoiSearch(context, query)
|
|
44
|
+
|
|
45
|
+
poiSearch.setOnPoiSearchListener(object : PoiSearch.OnPoiSearchListener {
|
|
46
|
+
override fun onPoiSearched(result: PoiResult?, rCode: Int) {
|
|
47
|
+
if (rCode == 1000) {
|
|
48
|
+
promise.resolve(convertPoiResult(result))
|
|
49
|
+
} else {
|
|
50
|
+
promise.reject("SEARCH_ERROR", "Search failed with code: $rCode", null)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun onPoiItemSearched(item: PoiItem?, rCode: Int) {
|
|
55
|
+
// 不使用单个 POI 搜索
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
poiSearch.searchPOIAsyn()
|
|
60
|
+
} catch (e: Exception) {
|
|
61
|
+
promise.reject("SEARCH_ERROR", e.message, e)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 周边搜索
|
|
67
|
+
*/
|
|
68
|
+
AsyncFunction("searchNearby") { options: Map<String, Any?>, promise: Promise ->
|
|
69
|
+
try {
|
|
70
|
+
val keyword = options["keyword"] as? String
|
|
71
|
+
?: throw Exception("keyword is required")
|
|
72
|
+
|
|
73
|
+
val center = options["center"] as? Map<String, Any?>
|
|
74
|
+
?: throw Exception("center is required")
|
|
75
|
+
|
|
76
|
+
val latitude = (center["latitude"] as? Number)?.toDouble()
|
|
77
|
+
?: throw Exception("center.latitude is required")
|
|
78
|
+
val longitude = (center["longitude"] as? Number)?.toDouble()
|
|
79
|
+
?: throw Exception("center.longitude is required")
|
|
80
|
+
|
|
81
|
+
val radius = (options["radius"] as? Number)?.toInt() ?: 1000
|
|
82
|
+
val types = options["types"] as? String ?: ""
|
|
83
|
+
val pageSize = (options["pageSize"] as? Number)?.toInt() ?: 20
|
|
84
|
+
val pageNum = (options["pageNum"] as? Number)?.toInt() ?: 1
|
|
85
|
+
|
|
86
|
+
val query = PoiSearch.Query(keyword, types)
|
|
87
|
+
query.pageSize = pageSize
|
|
88
|
+
query.pageNum = pageNum - 1
|
|
89
|
+
|
|
90
|
+
val poiSearch = PoiSearch(context, query)
|
|
91
|
+
poiSearch.setBound(PoiSearch.SearchBound(
|
|
92
|
+
LatLonPoint(latitude, longitude), radius
|
|
93
|
+
))
|
|
94
|
+
|
|
95
|
+
poiSearch.setOnPoiSearchListener(object : PoiSearch.OnPoiSearchListener {
|
|
96
|
+
override fun onPoiSearched(result: PoiResult?, rCode: Int) {
|
|
97
|
+
if (rCode == 1000) {
|
|
98
|
+
promise.resolve(convertPoiResult(result))
|
|
99
|
+
} else {
|
|
100
|
+
promise.reject("SEARCH_ERROR", "Search failed with code: $rCode", null)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
override fun onPoiItemSearched(item: PoiItem?, rCode: Int) {}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
poiSearch.searchPOIAsyn()
|
|
108
|
+
} catch (e: Exception) {
|
|
109
|
+
promise.reject("SEARCH_ERROR", e.message, e)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 沿途搜索
|
|
115
|
+
*/
|
|
116
|
+
AsyncFunction("searchAlong") { options: Map<String, Any?>, promise: Promise ->
|
|
117
|
+
try {
|
|
118
|
+
val keyword = options["keyword"] as? String
|
|
119
|
+
?: throw Exception("keyword is required")
|
|
120
|
+
|
|
121
|
+
val polyline = options["polyline"] as? List<Map<String, Any?>>
|
|
122
|
+
?: throw Exception("polyline is required")
|
|
123
|
+
|
|
124
|
+
if (polyline.size < 2) {
|
|
125
|
+
throw Exception("polyline must have at least 2 points")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 转换路线点
|
|
129
|
+
val points = polyline.map { point ->
|
|
130
|
+
val lat = (point["latitude"] as? Number)?.toDouble()
|
|
131
|
+
?: throw Exception("Invalid polyline point")
|
|
132
|
+
val lng = (point["longitude"] as? Number)?.toDouble()
|
|
133
|
+
?: throw Exception("Invalid polyline point")
|
|
134
|
+
LatLonPoint(lat, lng)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
val startPoint = points.first()
|
|
138
|
+
val endPoint = points.last()
|
|
139
|
+
|
|
140
|
+
// 构造沿途搜索参数
|
|
141
|
+
val searchRange = 250 // 搜索半径(米)
|
|
142
|
+
// 使用枚举类型作为搜索类型
|
|
143
|
+
val searchType = when(keyword.lowercase()) {
|
|
144
|
+
"加油站", "加油" -> RoutePOISearch.RoutePOISearchType.TypeGasStation
|
|
145
|
+
"atm", "银行" -> RoutePOISearch.RoutePOISearchType.TypeATM
|
|
146
|
+
"汽修", "维修" -> RoutePOISearch.RoutePOISearchType.TypeMaintenanceStation
|
|
147
|
+
"厕所", "卫生间" -> RoutePOISearch.RoutePOISearchType.TypeToilet
|
|
148
|
+
else -> RoutePOISearch.RoutePOISearchType.TypeGasStation // 默认搜索加油站
|
|
149
|
+
}
|
|
150
|
+
val query = RoutePOISearchQuery(startPoint, endPoint, 1, searchType, searchRange)
|
|
151
|
+
|
|
152
|
+
val routePOISearch = RoutePOISearch(context, query)
|
|
153
|
+
|
|
154
|
+
routePOISearch.setPoiSearchListener(object : RoutePOISearch.OnRoutePOISearchListener {
|
|
155
|
+
override fun onRoutePoiSearched(result: RoutePOISearchResult?, rCode: Int) {
|
|
156
|
+
if (rCode == 1000 && result != null) {
|
|
157
|
+
promise.resolve(convertRoutePOIResult(result))
|
|
158
|
+
} else {
|
|
159
|
+
promise.reject("SEARCH_ERROR", "Route search failed with code: $rCode", null)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
routePOISearch.searchRoutePOIAsyn()
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
promise.reject("SEARCH_ERROR", e.message, e)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 多边形搜索(使用矩形范围代替)
|
|
172
|
+
*/
|
|
173
|
+
AsyncFunction("searchPolygon") { options: Map<String, Any?>, promise: Promise ->
|
|
174
|
+
try {
|
|
175
|
+
val keyword = options["keyword"] as? String
|
|
176
|
+
?: throw Exception("keyword is required")
|
|
177
|
+
|
|
178
|
+
val polygon = options["polygon"] as? List<Map<String, Any?>>
|
|
179
|
+
?: throw Exception("polygon is required")
|
|
180
|
+
|
|
181
|
+
val types = options["types"] as? String ?: ""
|
|
182
|
+
val pageSize = (options["pageSize"] as? Number)?.toInt() ?: 20
|
|
183
|
+
val pageNum = (options["pageNum"] as? Number)?.toInt() ?: 1
|
|
184
|
+
|
|
185
|
+
// 计算边界矩形
|
|
186
|
+
val points = polygon.map { point ->
|
|
187
|
+
val lat = (point["latitude"] as? Number)?.toDouble()
|
|
188
|
+
?: throw Exception("Invalid polygon point")
|
|
189
|
+
val lng = (point["longitude"] as? Number)?.toDouble()
|
|
190
|
+
?: throw Exception("Invalid polygon point")
|
|
191
|
+
LatLonPoint(lat, lng)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
val minLat = points.minOf { it.latitude }
|
|
195
|
+
val maxLat = points.maxOf { it.latitude }
|
|
196
|
+
val minLng = points.minOf { it.longitude }
|
|
197
|
+
val maxLng = points.maxOf { it.longitude }
|
|
198
|
+
|
|
199
|
+
val query = PoiSearch.Query(keyword, types)
|
|
200
|
+
query.pageSize = pageSize
|
|
201
|
+
query.pageNum = pageNum - 1
|
|
202
|
+
|
|
203
|
+
val poiSearch = PoiSearch(context, query)
|
|
204
|
+
// 使用矩形搜索代替多边形搜索
|
|
205
|
+
poiSearch.setBound(PoiSearch.SearchBound(
|
|
206
|
+
LatLonPoint(minLat, minLng),
|
|
207
|
+
LatLonPoint(maxLat, maxLng)
|
|
208
|
+
))
|
|
209
|
+
|
|
210
|
+
poiSearch.setOnPoiSearchListener(object : PoiSearch.OnPoiSearchListener {
|
|
211
|
+
override fun onPoiSearched(result: PoiResult?, rCode: Int) {
|
|
212
|
+
if (rCode == 1000) {
|
|
213
|
+
promise.resolve(convertPoiResult(result))
|
|
214
|
+
} else {
|
|
215
|
+
promise.reject("SEARCH_ERROR", "Search failed with code: $rCode", null)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
override fun onPoiItemSearched(item: PoiItem?, rCode: Int) {}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
poiSearch.searchPOIAsyn()
|
|
223
|
+
} catch (e: Exception) {
|
|
224
|
+
promise.reject("SEARCH_ERROR", e.message, e)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 输入提示
|
|
230
|
+
*/
|
|
231
|
+
AsyncFunction("getInputTips") { options: Map<String, Any?>, promise: Promise ->
|
|
232
|
+
try {
|
|
233
|
+
val keyword = options["keyword"] as? String
|
|
234
|
+
?: throw Exception("keyword is required")
|
|
235
|
+
|
|
236
|
+
val city = options["city"] as? String ?: ""
|
|
237
|
+
val types = options["types"] as? String ?: ""
|
|
238
|
+
|
|
239
|
+
val query = InputtipsQuery(keyword, city)
|
|
240
|
+
if (types.isNotEmpty()) {
|
|
241
|
+
query.cityLimit = true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
val inputtips = Inputtips(context, query)
|
|
245
|
+
|
|
246
|
+
inputtips.setInputtipsListener { tipList, rCode ->
|
|
247
|
+
if (rCode == 1000) {
|
|
248
|
+
promise.resolve(convertTipsResult(tipList))
|
|
249
|
+
} else {
|
|
250
|
+
promise.reject("TIPS_ERROR", "Input tips failed with code: $rCode", null)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
inputtips.requestInputtipsAsyn()
|
|
255
|
+
} catch (e: Exception) {
|
|
256
|
+
promise.reject("TIPS_ERROR", e.message, e)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 转换 POI 搜索结果
|
|
263
|
+
*/
|
|
264
|
+
private fun convertPoiResult(result: PoiResult?): Map<String, Any?> {
|
|
265
|
+
if (result == null) {
|
|
266
|
+
return mapOf(
|
|
267
|
+
"pois" to emptyList<Map<String, Any?>>(),
|
|
268
|
+
"total" to 0,
|
|
269
|
+
"pageNum" to 1,
|
|
270
|
+
"pageSize" to 20,
|
|
271
|
+
"pageCount" to 0
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
val pois = result.pois?.map { poi ->
|
|
276
|
+
mapOf(
|
|
277
|
+
"id" to poi.poiId,
|
|
278
|
+
"name" to poi.title,
|
|
279
|
+
"address" to poi.snippet,
|
|
280
|
+
"location" to mapOf(
|
|
281
|
+
"latitude" to poi.latLonPoint?.latitude,
|
|
282
|
+
"longitude" to poi.latLonPoint?.longitude
|
|
283
|
+
),
|
|
284
|
+
"typeCode" to poi.typeCode,
|
|
285
|
+
"typeDes" to poi.typeDes,
|
|
286
|
+
"tel" to poi.tel,
|
|
287
|
+
"distance" to poi.distance,
|
|
288
|
+
"cityName" to poi.cityName,
|
|
289
|
+
"cityCode" to poi.cityCode,
|
|
290
|
+
"provinceName" to poi.provinceName,
|
|
291
|
+
"adName" to poi.adName,
|
|
292
|
+
"adCode" to poi.adCode
|
|
293
|
+
)
|
|
294
|
+
} ?: emptyList()
|
|
295
|
+
|
|
296
|
+
return mapOf(
|
|
297
|
+
"pois" to pois,
|
|
298
|
+
"total" to result.pageCount * result.query.pageSize,
|
|
299
|
+
"pageNum" to (result.query.pageNum + 1),
|
|
300
|
+
"pageSize" to result.query.pageSize,
|
|
301
|
+
"pageCount" to result.pageCount
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 转换输入提示结果
|
|
307
|
+
*/
|
|
308
|
+
private fun convertTipsResult(tips: List<Tip>?): Map<String, Any?> {
|
|
309
|
+
val tipList = tips?.map { tip ->
|
|
310
|
+
mapOf(
|
|
311
|
+
"id" to tip.poiID,
|
|
312
|
+
"name" to tip.name,
|
|
313
|
+
"address" to tip.address,
|
|
314
|
+
"location" to tip.point?.let {
|
|
315
|
+
mapOf(
|
|
316
|
+
"latitude" to it.latitude,
|
|
317
|
+
"longitude" to it.longitude
|
|
318
|
+
)
|
|
319
|
+
},
|
|
320
|
+
"typeCode" to tip.typeCode,
|
|
321
|
+
"cityName" to tip.district,
|
|
322
|
+
"adName" to tip.district
|
|
323
|
+
)
|
|
324
|
+
} ?: emptyList()
|
|
325
|
+
|
|
326
|
+
return mapOf("tips" to tipList)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 转换沿途 POI 搜索结果
|
|
331
|
+
*/
|
|
332
|
+
private fun convertRoutePOIResult(result: RoutePOISearchResult): Map<String, Any?> {
|
|
333
|
+
val pois = result.routePois?.map { poi ->
|
|
334
|
+
mapOf(
|
|
335
|
+
"id" to poi.id,
|
|
336
|
+
"name" to poi.title,
|
|
337
|
+
"address" to "", // RoutePOIItem 没有 address 属性
|
|
338
|
+
"location" to mapOf(
|
|
339
|
+
"latitude" to poi.point?.latitude,
|
|
340
|
+
"longitude" to poi.point?.longitude
|
|
341
|
+
),
|
|
342
|
+
"distance" to poi.distance
|
|
343
|
+
)
|
|
344
|
+
} ?: emptyList()
|
|
345
|
+
|
|
346
|
+
return mapOf(
|
|
347
|
+
"pois" to pois,
|
|
348
|
+
"total" to pois.size,
|
|
349
|
+
"pageNum" to 1,
|
|
350
|
+
"pageSize" to pois.size,
|
|
351
|
+
"pageCount" to 1
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import AMapSearchKit
|
|
3
|
+
import AMapFoundationKit
|
|
4
|
+
|
|
5
|
+
public class ExpoGaodeMapSearchModule: Module {
|
|
6
|
+
private var searchAPI: AMapSearchAPI!
|
|
7
|
+
private var searchDelegate: SearchDelegate!
|
|
8
|
+
|
|
9
|
+
public func definition() -> ModuleDefinition {
|
|
10
|
+
Name("ExpoGaodeMapSearch")
|
|
11
|
+
|
|
12
|
+
// 不在 OnCreate 中初始化,改为延迟初始化
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 手动初始化搜索模块(可选)
|
|
16
|
+
* 如果 API Key 已经设置,则直接初始化
|
|
17
|
+
* 如果未设置,会尝试从 Info.plist 读取
|
|
18
|
+
*/
|
|
19
|
+
Function("initSearch") {
|
|
20
|
+
self.initSearchAPI()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* POI 搜索
|
|
25
|
+
*/
|
|
26
|
+
AsyncFunction("searchPOI") { (options: [String: Any], promise: Promise) in
|
|
27
|
+
// 延迟初始化:在首次使用时才初始化
|
|
28
|
+
self.initSearchAPI()
|
|
29
|
+
|
|
30
|
+
guard let keyword = options["keyword"] as? String else {
|
|
31
|
+
promise.reject("SEARCH_ERROR", "keyword is required")
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let city = options["city"] as? String ?? ""
|
|
36
|
+
let types = options["types"] as? String ?? ""
|
|
37
|
+
let pageSize = options["pageSize"] as? Int ?? 20
|
|
38
|
+
let pageNum = options["pageNum"] as? Int ?? 1
|
|
39
|
+
|
|
40
|
+
let request = AMapPOIKeywordsSearchRequest()
|
|
41
|
+
request.keywords = keyword
|
|
42
|
+
request.city = city
|
|
43
|
+
request.types = types
|
|
44
|
+
request.page = pageNum
|
|
45
|
+
request.offset = pageSize
|
|
46
|
+
|
|
47
|
+
self.searchDelegate.currentPromise = promise
|
|
48
|
+
self.searchAPI.aMapPOIKeywordsSearch(request)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 周边搜索
|
|
53
|
+
*/
|
|
54
|
+
AsyncFunction("searchNearby") { (options: [String: Any], promise: Promise) in
|
|
55
|
+
self.initSearchAPI()
|
|
56
|
+
|
|
57
|
+
guard let keyword = options["keyword"] as? String else {
|
|
58
|
+
promise.reject("SEARCH_ERROR", "keyword is required")
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
guard let center = options["center"] as? [String: Any],
|
|
63
|
+
let latitude = center["latitude"] as? Double,
|
|
64
|
+
let longitude = center["longitude"] as? Double else {
|
|
65
|
+
promise.reject("SEARCH_ERROR", "center is required")
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let radius = options["radius"] as? Int ?? 1000
|
|
70
|
+
let types = options["types"] as? String ?? ""
|
|
71
|
+
let pageSize = options["pageSize"] as? Int ?? 20
|
|
72
|
+
let pageNum = options["pageNum"] as? Int ?? 1
|
|
73
|
+
|
|
74
|
+
let request = AMapPOIAroundSearchRequest()
|
|
75
|
+
request.keywords = keyword
|
|
76
|
+
request.location = AMapGeoPoint.location(
|
|
77
|
+
withLatitude: CGFloat(latitude),
|
|
78
|
+
longitude: CGFloat(longitude)
|
|
79
|
+
)
|
|
80
|
+
request.radius = radius
|
|
81
|
+
request.types = types
|
|
82
|
+
request.page = pageNum
|
|
83
|
+
request.offset = pageSize
|
|
84
|
+
|
|
85
|
+
self.searchDelegate.currentPromise = promise
|
|
86
|
+
self.searchAPI.aMapPOIAroundSearch(request)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 沿途搜索
|
|
91
|
+
* iOS SDK 支持的沿途搜索类型:加油站、ATM、汽修、厕所
|
|
92
|
+
*/
|
|
93
|
+
AsyncFunction("searchAlong") { (options: [String: Any], promise: Promise) in
|
|
94
|
+
self.initSearchAPI()
|
|
95
|
+
|
|
96
|
+
guard let keyword = options["keyword"] as? String else {
|
|
97
|
+
promise.reject("SEARCH_ERROR", "keyword is required")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
guard let polyline = options["polyline"] as? [[String: Any]] else {
|
|
102
|
+
promise.reject("SEARCH_ERROR", "polyline is required")
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
guard polyline.count >= 2 else {
|
|
107
|
+
promise.reject("SEARCH_ERROR", "polyline must have at least 2 points")
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 转换路线点
|
|
112
|
+
var points: [AMapGeoPoint] = []
|
|
113
|
+
for point in polyline {
|
|
114
|
+
if let lat = point["latitude"] as? Double,
|
|
115
|
+
let lng = point["longitude"] as? Double {
|
|
116
|
+
points.append(AMapGeoPoint.location(
|
|
117
|
+
withLatitude: CGFloat(lat),
|
|
118
|
+
longitude: CGFloat(lng)
|
|
119
|
+
))
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
guard let startPoint = points.first,
|
|
124
|
+
let endPoint = points.last else {
|
|
125
|
+
promise.reject("SEARCH_ERROR", "Invalid polyline points")
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 根据关键词确定搜索类型
|
|
130
|
+
let lowercaseKeyword = keyword.lowercased()
|
|
131
|
+
var searchType: AMapRoutePOISearchType = .gasStation
|
|
132
|
+
if lowercaseKeyword.contains("加油") || lowercaseKeyword == "加油站" {
|
|
133
|
+
searchType = .gasStation
|
|
134
|
+
} else if lowercaseKeyword.contains("atm") || lowercaseKeyword.contains("银行") {
|
|
135
|
+
searchType = .ATM
|
|
136
|
+
} else if lowercaseKeyword.contains("汽修") || lowercaseKeyword.contains("维修") {
|
|
137
|
+
searchType = .maintenanceStation
|
|
138
|
+
} else if lowercaseKeyword.contains("厕所") || lowercaseKeyword.contains("卫生间") {
|
|
139
|
+
searchType = .toilet
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let request = AMapRoutePOISearchRequest()
|
|
143
|
+
request.origin = startPoint
|
|
144
|
+
request.destination = endPoint
|
|
145
|
+
request.searchType = searchType
|
|
146
|
+
request.range = 250
|
|
147
|
+
request.strategy = 0
|
|
148
|
+
|
|
149
|
+
self.searchDelegate.currentPromise = promise
|
|
150
|
+
self.searchAPI.aMapRoutePOISearch(request)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 多边形搜索
|
|
155
|
+
*/
|
|
156
|
+
AsyncFunction("searchPolygon") { (options: [String: Any], promise: Promise) in
|
|
157
|
+
self.initSearchAPI()
|
|
158
|
+
|
|
159
|
+
guard let keyword = options["keyword"] as? String else {
|
|
160
|
+
promise.reject("SEARCH_ERROR", "keyword is required")
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
guard let polygon = options["polygon"] as? [[String: Any]] else {
|
|
165
|
+
promise.reject("SEARCH_ERROR", "polygon is required")
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let types = options["types"] as? String ?? ""
|
|
170
|
+
let pageSize = options["pageSize"] as? Int ?? 20
|
|
171
|
+
let pageNum = options["pageNum"] as? Int ?? 1
|
|
172
|
+
|
|
173
|
+
var points: [AMapGeoPoint] = []
|
|
174
|
+
for point in polygon {
|
|
175
|
+
if let lat = point["latitude"] as? Double,
|
|
176
|
+
let lng = point["longitude"] as? Double {
|
|
177
|
+
points.append(AMapGeoPoint.location(
|
|
178
|
+
withLatitude: CGFloat(lat),
|
|
179
|
+
longitude: CGFloat(lng)
|
|
180
|
+
))
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let request = AMapPOIPolygonSearchRequest()
|
|
185
|
+
request.keywords = keyword
|
|
186
|
+
request.polygon = AMapGeoPolygon(points: points)
|
|
187
|
+
request.types = types
|
|
188
|
+
request.page = pageNum
|
|
189
|
+
request.offset = pageSize
|
|
190
|
+
|
|
191
|
+
self.searchDelegate.currentPromise = promise
|
|
192
|
+
self.searchAPI.aMapPOIPolygonSearch(request)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 输入提示
|
|
197
|
+
*/
|
|
198
|
+
AsyncFunction("getInputTips") { (options: [String: Any], promise: Promise) in
|
|
199
|
+
self.initSearchAPI()
|
|
200
|
+
|
|
201
|
+
guard let keyword = options["keyword"] as? String else {
|
|
202
|
+
promise.reject("TIPS_ERROR", "keyword is required")
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let city = options["city"] as? String ?? ""
|
|
207
|
+
let types = options["types"] as? String ?? ""
|
|
208
|
+
|
|
209
|
+
let request = AMapInputTipsSearchRequest()
|
|
210
|
+
request.keywords = keyword
|
|
211
|
+
request.city = city
|
|
212
|
+
request.types = types
|
|
213
|
+
|
|
214
|
+
self.searchDelegate.currentPromise = promise
|
|
215
|
+
self.searchAPI.aMapInputTipsSearch(request)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// MARK: - Private Methods
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 初始化搜索 API(延迟初始化)
|
|
223
|
+
*/
|
|
224
|
+
private func initSearchAPI() {
|
|
225
|
+
if searchAPI == nil {
|
|
226
|
+
// 确保 API Key 已设置
|
|
227
|
+
self.ensureAPIKeyIsSet()
|
|
228
|
+
|
|
229
|
+
searchDelegate = SearchDelegate()
|
|
230
|
+
searchAPI = AMapSearchAPI()
|
|
231
|
+
searchAPI.delegate = searchDelegate
|
|
232
|
+
|
|
233
|
+
let apiKey = AMapServices.shared().apiKey
|
|
234
|
+
print("[ExpoGaodeMapSearch] 搜索 API 已初始化,API Key: \(apiKey ?? "未设置")")
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 确保 API Key 已设置
|
|
240
|
+
* 优先级:已设置 > Info.plist > 提示错误
|
|
241
|
+
*/
|
|
242
|
+
private func ensureAPIKeyIsSet() {
|
|
243
|
+
// 1. 检查是否已经设置(通过 initSDK 或 AppDelegate)
|
|
244
|
+
if let existingKey = AMapServices.shared().apiKey, !existingKey.isEmpty {
|
|
245
|
+
print("[ExpoGaodeMapSearch] ✓ API Key 已设置")
|
|
246
|
+
return
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 2. 尝试从 Info.plist 读取(Config Plugin 会写入)
|
|
250
|
+
if let apiKey = Bundle.main.object(forInfoDictionaryKey: "AMapApiKey") as? String, !apiKey.isEmpty {
|
|
251
|
+
AMapServices.shared().apiKey = apiKey
|
|
252
|
+
AMapServices.shared().enableHTTPS = true
|
|
253
|
+
print("[ExpoGaodeMapSearch] ✓ 从 Info.plist 读取并设置 API Key")
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 3. 都没有,提示错误
|
|
258
|
+
print("[ExpoGaodeMapSearch] ✗ 错误: API Key 未设置!")
|
|
259
|
+
print("[ExpoGaodeMapSearch] 请通过以下任一方式设置:")
|
|
260
|
+
print("[ExpoGaodeMapSearch] 1. 在 app.json 的 plugins 中配置 iosApiKey(推荐)")
|
|
261
|
+
print("[ExpoGaodeMapSearch] 2. 调用 ExpoGaodeMap.initSDK({ iosKey: 'your-key' })")
|
|
262
|
+
print("[ExpoGaodeMapSearch] 3. 在 AppDelegate 中调用 [AMapServices sharedServices].apiKey = @\"your-key\"")
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// MARK: - Search Delegate
|
|
267
|
+
class SearchDelegate: NSObject, AMapSearchDelegate {
|
|
268
|
+
var currentPromise: Promise?
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* POI 搜索回调
|
|
272
|
+
*/
|
|
273
|
+
func onPOISearchDone(_ request: AMapPOISearchBaseRequest!, response: AMapPOISearchResponse!) {
|
|
274
|
+
guard let promise = currentPromise else { return }
|
|
275
|
+
|
|
276
|
+
if let response = response {
|
|
277
|
+
promise.resolve(convertPOISearchResponse(response))
|
|
278
|
+
} else {
|
|
279
|
+
promise.reject("SEARCH_ERROR", "Search failed")
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
currentPromise = nil
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 沿途搜索回调
|
|
287
|
+
*/
|
|
288
|
+
func onRoutePOISearchDone(_ request: AMapRoutePOISearchRequest!, response: AMapRoutePOISearchResponse!) {
|
|
289
|
+
guard let promise = currentPromise else { return }
|
|
290
|
+
|
|
291
|
+
if let response = response {
|
|
292
|
+
promise.resolve(convertRoutePOISearchResponse(response))
|
|
293
|
+
} else {
|
|
294
|
+
promise.reject("SEARCH_ERROR", "Route search failed")
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
currentPromise = nil
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 输入提示回调
|
|
302
|
+
*/
|
|
303
|
+
func onInputTipsSearchDone(_ request: AMapInputTipsSearchRequest!, response: AMapInputTipsSearchResponse!) {
|
|
304
|
+
guard let promise = currentPromise else { return }
|
|
305
|
+
|
|
306
|
+
if let response = response {
|
|
307
|
+
promise.resolve(convertInputTipsResponse(response))
|
|
308
|
+
} else {
|
|
309
|
+
promise.reject("TIPS_ERROR", "Input tips failed")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
currentPromise = nil
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 搜索失败回调
|
|
317
|
+
*/
|
|
318
|
+
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {
|
|
319
|
+
guard let promise = currentPromise else { return }
|
|
320
|
+
|
|
321
|
+
let errorMessage = error?.localizedDescription ?? "Unknown error"
|
|
322
|
+
promise.reject("SEARCH_ERROR", errorMessage)
|
|
323
|
+
currentPromise = nil
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// MARK: - 转换方法
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 转换 POI 搜索结果
|
|
330
|
+
*/
|
|
331
|
+
private func convertPOISearchResponse(_ response: AMapPOISearchResponse) -> [String: Any] {
|
|
332
|
+
let pois = response.pois?.map { poi -> [String: Any] in
|
|
333
|
+
var result: [String: Any] = [
|
|
334
|
+
"id": poi.uid ?? "",
|
|
335
|
+
"name": poi.name ?? "",
|
|
336
|
+
"address": poi.address ?? "",
|
|
337
|
+
"location": [
|
|
338
|
+
"latitude": poi.location?.latitude ?? 0,
|
|
339
|
+
"longitude": poi.location?.longitude ?? 0
|
|
340
|
+
],
|
|
341
|
+
"typeCode": poi.typecode ?? "",
|
|
342
|
+
"typeDes": poi.type ?? "",
|
|
343
|
+
"tel": poi.tel ?? "",
|
|
344
|
+
"distance": poi.distance,
|
|
345
|
+
"cityName": poi.city ?? "",
|
|
346
|
+
"provinceName": poi.province ?? "",
|
|
347
|
+
"adName": poi.district ?? "",
|
|
348
|
+
"adCode": poi.adcode ?? ""
|
|
349
|
+
]
|
|
350
|
+
return result
|
|
351
|
+
} ?? []
|
|
352
|
+
|
|
353
|
+
return [
|
|
354
|
+
"pois": pois,
|
|
355
|
+
"total": response.count,
|
|
356
|
+
"pageNum": response.pois?.first?.uid != nil ? 1 : 0,
|
|
357
|
+
"pageSize": response.pois?.count ?? 0,
|
|
358
|
+
"pageCount": (response.count + 19) / 20
|
|
359
|
+
]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 转换输入提示结果
|
|
364
|
+
*/
|
|
365
|
+
private func convertInputTipsResponse(_ response: AMapInputTipsSearchResponse) -> [String: Any] {
|
|
366
|
+
let tips = response.tips?.map { tip -> [String: Any] in
|
|
367
|
+
var result: [String: Any] = [
|
|
368
|
+
"id": tip.uid ?? "",
|
|
369
|
+
"name": tip.name ?? "",
|
|
370
|
+
"address": tip.address ?? "",
|
|
371
|
+
"typeCode": tip.typecode ?? "",
|
|
372
|
+
"cityName": tip.district ?? "",
|
|
373
|
+
"adName": tip.district ?? ""
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
if let location = tip.location {
|
|
377
|
+
result["location"] = [
|
|
378
|
+
"latitude": location.latitude,
|
|
379
|
+
"longitude": location.longitude
|
|
380
|
+
]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return result
|
|
384
|
+
} ?? []
|
|
385
|
+
|
|
386
|
+
return ["tips": tips]
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 转换沿途 POI 搜索结果
|
|
391
|
+
*/
|
|
392
|
+
private func convertRoutePOISearchResponse(_ response: AMapRoutePOISearchResponse) -> [String: Any] {
|
|
393
|
+
let pois = response.pois?.map { poi -> [String: Any] in
|
|
394
|
+
var result: [String: Any] = [
|
|
395
|
+
"id": poi.uid ?? "",
|
|
396
|
+
"name": poi.name ?? "",
|
|
397
|
+
"address": "",
|
|
398
|
+
"location": [
|
|
399
|
+
"latitude": poi.location?.latitude ?? 0,
|
|
400
|
+
"longitude": poi.location?.longitude ?? 0
|
|
401
|
+
],
|
|
402
|
+
"distance": poi.distance
|
|
403
|
+
]
|
|
404
|
+
return result
|
|
405
|
+
} ?? []
|
|
406
|
+
|
|
407
|
+
return [
|
|
408
|
+
"pois": pois,
|
|
409
|
+
"total": pois.count,
|
|
410
|
+
"pageNum": 1,
|
|
411
|
+
"pageSize": pois.count,
|
|
412
|
+
"pageCount": 1
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-gaode-map-search",
|
|
3
|
-
"version": "1.1.2-next.
|
|
3
|
+
"version": "1.1.2-next.2",
|
|
4
4
|
"description": "高德地图搜索功能模块 - POI搜索、关键词搜索、周边搜索,需先安装expo-gaode-map",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -21,8 +21,17 @@
|
|
|
21
21
|
"author": "尚博信_王强 <wangqiang03@sunboxsoft.com> (https://github.com/TomWq)",
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"homepage": "https://github.com/TomWq/expo-gaode-map#readme",
|
|
24
|
-
"
|
|
25
|
-
"
|
|
24
|
+
"files": [
|
|
25
|
+
"build/**/*",
|
|
26
|
+
"src/**/*",
|
|
27
|
+
"android/**/*",
|
|
28
|
+
"ios/**/*",
|
|
29
|
+
"expo-module.config.json",
|
|
30
|
+
"README.md",
|
|
31
|
+
"package.json"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
26
35
|
},
|
|
27
36
|
"devDependencies": {
|
|
28
37
|
"@types/react": "~19.1.0",
|
|
@@ -36,6 +45,9 @@
|
|
|
36
45
|
"react": "*",
|
|
37
46
|
"react-native": "*"
|
|
38
47
|
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"expo-gaode-map": "^2.2.4-next.0"
|
|
50
|
+
},
|
|
39
51
|
"scripts": {
|
|
40
52
|
"build": "expo-module build",
|
|
41
53
|
"clean": "expo-module clean",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 搜索类型
|
|
3
|
+
*/
|
|
4
|
+
export enum SearchType {
|
|
5
|
+
/** POI 搜索 */
|
|
6
|
+
POI = 'poi',
|
|
7
|
+
/** 周边搜索 */
|
|
8
|
+
NEARBY = 'nearby',
|
|
9
|
+
/** 沿途搜索 */
|
|
10
|
+
ALONG = 'along',
|
|
11
|
+
/** 多边形搜索 */
|
|
12
|
+
POLYGON = 'polygon',
|
|
13
|
+
/** 输入提示 */
|
|
14
|
+
INPUT_TIPS = 'inputTips',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 坐标点
|
|
19
|
+
*/
|
|
20
|
+
export interface Coordinates {
|
|
21
|
+
latitude: number;
|
|
22
|
+
longitude: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* POI 信息
|
|
27
|
+
*/
|
|
28
|
+
export interface POI {
|
|
29
|
+
/** POI ID */
|
|
30
|
+
id: string;
|
|
31
|
+
/** 名称 */
|
|
32
|
+
name: string;
|
|
33
|
+
/** 地址 */
|
|
34
|
+
address: string;
|
|
35
|
+
/** 坐标 */
|
|
36
|
+
location: Coordinates;
|
|
37
|
+
/** 类型编码 */
|
|
38
|
+
typeCode: string;
|
|
39
|
+
/** 类型描述 */
|
|
40
|
+
typeDes: string;
|
|
41
|
+
/** 电话 */
|
|
42
|
+
tel?: string;
|
|
43
|
+
/** 距离(米),仅周边搜索返回 */
|
|
44
|
+
distance?: number;
|
|
45
|
+
/** 城市名称 */
|
|
46
|
+
cityName?: string;
|
|
47
|
+
/** 城市编码 */
|
|
48
|
+
cityCode?: string;
|
|
49
|
+
/** 省份名称 */
|
|
50
|
+
provinceName?: string;
|
|
51
|
+
/** 区域名称 */
|
|
52
|
+
adName?: string;
|
|
53
|
+
/** 区域编码 */
|
|
54
|
+
adCode?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* POI 搜索选项
|
|
59
|
+
*/
|
|
60
|
+
export interface POISearchOptions {
|
|
61
|
+
/** 搜索关键词 */
|
|
62
|
+
keyword: string;
|
|
63
|
+
/** 城市名称或城市编码(可选) */
|
|
64
|
+
city?: string;
|
|
65
|
+
/** POI 类型(可选),多个类型用 | 分隔 */
|
|
66
|
+
types?: string;
|
|
67
|
+
/** 每页记录数,默认 20,最大 50 */
|
|
68
|
+
pageSize?: number;
|
|
69
|
+
/** 当前页码,从 1 开始,默认 1 */
|
|
70
|
+
pageNum?: number;
|
|
71
|
+
/** 是否按照距离排序,需要设置中心点 */
|
|
72
|
+
sortByDistance?: boolean;
|
|
73
|
+
/** 中心点坐标,用于距离排序或周边搜索 */
|
|
74
|
+
center?: Coordinates;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 周边搜索选项
|
|
79
|
+
*/
|
|
80
|
+
export interface NearbySearchOptions {
|
|
81
|
+
/** 搜索关键词 */
|
|
82
|
+
keyword: string;
|
|
83
|
+
/** 中心点坐标 */
|
|
84
|
+
center: Coordinates;
|
|
85
|
+
/** 搜索半径,单位:米,默认 1000,最大 50000 */
|
|
86
|
+
radius?: number;
|
|
87
|
+
/** POI 类型(可选),多个类型用 | 分隔 */
|
|
88
|
+
types?: string;
|
|
89
|
+
/** 每页记录数,默认 20,最大 50 */
|
|
90
|
+
pageSize?: number;
|
|
91
|
+
/** 当前页码,从 1 开始,默认 1 */
|
|
92
|
+
pageNum?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 沿途搜索选项
|
|
97
|
+
*/
|
|
98
|
+
export interface AlongSearchOptions {
|
|
99
|
+
/** 搜索关键词 */
|
|
100
|
+
keyword: string;
|
|
101
|
+
/** 路线坐标点数组 */
|
|
102
|
+
polyline: Coordinates[];
|
|
103
|
+
/** 搜索范围,单位:米,默认 500,最大 1000 */
|
|
104
|
+
range?: number;
|
|
105
|
+
/** POI 类型(可选),多个类型用 | 分隔 */
|
|
106
|
+
types?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 多边形搜索选项
|
|
111
|
+
*/
|
|
112
|
+
export interface PolygonSearchOptions {
|
|
113
|
+
/** 搜索关键词 */
|
|
114
|
+
keyword: string;
|
|
115
|
+
/** 多边形顶点坐标数组 */
|
|
116
|
+
polygon: Coordinates[];
|
|
117
|
+
/** POI 类型(可选),多个类型用 | 分隔 */
|
|
118
|
+
types?: string;
|
|
119
|
+
/** 每页记录数,默认 20,最大 50 */
|
|
120
|
+
pageSize?: number;
|
|
121
|
+
/** 当前页码,从 1 开始,默认 1 */
|
|
122
|
+
pageNum?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 输入提示选项
|
|
127
|
+
*/
|
|
128
|
+
export interface InputTipsOptions {
|
|
129
|
+
/** 关键词 */
|
|
130
|
+
keyword: string;
|
|
131
|
+
/** 城市名称或城市编码(可选) */
|
|
132
|
+
city?: string;
|
|
133
|
+
/** POI 类型(可选),多个类型用 | 分隔 */
|
|
134
|
+
types?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 输入提示结果
|
|
139
|
+
*/
|
|
140
|
+
export interface InputTip {
|
|
141
|
+
/** 提示 ID */
|
|
142
|
+
id: string;
|
|
143
|
+
/** 名称 */
|
|
144
|
+
name: string;
|
|
145
|
+
/** 地址 */
|
|
146
|
+
address: string;
|
|
147
|
+
/** 坐标(可能为空) */
|
|
148
|
+
location?: Coordinates;
|
|
149
|
+
/** 类型编码 */
|
|
150
|
+
typeCode?: string;
|
|
151
|
+
/** 城市名称 */
|
|
152
|
+
cityName?: string;
|
|
153
|
+
/** 区域名称 */
|
|
154
|
+
adName?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 搜索结果
|
|
159
|
+
*/
|
|
160
|
+
export interface SearchResult {
|
|
161
|
+
/** POI 列表 */
|
|
162
|
+
pois: POI[];
|
|
163
|
+
/** 总记录数 */
|
|
164
|
+
total: number;
|
|
165
|
+
/** 当前页码 */
|
|
166
|
+
pageNum: number;
|
|
167
|
+
/** 每页记录数 */
|
|
168
|
+
pageSize: number;
|
|
169
|
+
/** 总页数 */
|
|
170
|
+
pageCount: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 输入提示结果
|
|
175
|
+
*/
|
|
176
|
+
export interface InputTipsResult {
|
|
177
|
+
/** 提示列表 */
|
|
178
|
+
tips: InputTip[];
|
|
179
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 在加载原生搜索模块前,强制校验基础地图组件是否已安装。
|
|
6
|
+
* 支持两种“基础地图提供者”:expo-gaode-map 或 expo-gaode-map-navigation(导航内置地图)。
|
|
7
|
+
* 这样可避免导航与核心包的 SDK 冲突时无法使用搜索模块的问题。
|
|
8
|
+
*/
|
|
9
|
+
function ensureBaseInstalled() {
|
|
10
|
+
let installed = false;
|
|
11
|
+
try {
|
|
12
|
+
// 优先检测核心地图包
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
14
|
+
require('expo-gaode-map');
|
|
15
|
+
installed = true;
|
|
16
|
+
} catch (_) {
|
|
17
|
+
// 再尝试导航包(内置地图能力)
|
|
18
|
+
try {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
20
|
+
require('expo-gaode-map-navigation');
|
|
21
|
+
installed = true;
|
|
22
|
+
} catch (_) {
|
|
23
|
+
installed = false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!installed) {
|
|
28
|
+
const msg =
|
|
29
|
+
'[expo-gaode-map-search] 需要先安装基础地图组件,支持以下任一包:\n' +
|
|
30
|
+
' - expo-gaode-map(核心地图包),或\n' +
|
|
31
|
+
' - expo-gaode-map-navigation(导航包,内置地图能力)\n' +
|
|
32
|
+
'请先安装并完成原生配置后再重试。';
|
|
33
|
+
throw new Error(msg);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ensureBaseInstalled();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 高德地图搜索模块
|
|
41
|
+
*
|
|
42
|
+
* 提供 POI 搜索、周边搜索、沿途搜索、多边形搜索和输入提示功能
|
|
43
|
+
*/
|
|
44
|
+
const ExpoGaodeMapSearchModule = requireNativeModule('ExpoGaodeMapSearch');
|
|
45
|
+
|
|
46
|
+
export default ExpoGaodeMapSearchModule;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import ExpoGaodeMapSearchModule from './ExpoGaodeMapSearchModule';
|
|
2
|
+
import type {
|
|
3
|
+
SearchType,
|
|
4
|
+
Coordinates,
|
|
5
|
+
POI,
|
|
6
|
+
POISearchOptions,
|
|
7
|
+
NearbySearchOptions,
|
|
8
|
+
AlongSearchOptions,
|
|
9
|
+
PolygonSearchOptions,
|
|
10
|
+
InputTipsOptions,
|
|
11
|
+
InputTip,
|
|
12
|
+
SearchResult,
|
|
13
|
+
InputTipsResult,
|
|
14
|
+
} from './ExpoGaodeMapSearch.types';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 初始化搜索模块(可选)
|
|
18
|
+
*
|
|
19
|
+
* 如果 API Key 已通过以下方式设置,则无需调用此方法:
|
|
20
|
+
* 1. app.json 的 plugins 中配置了 iosApiKey(推荐)
|
|
21
|
+
* 2. 调用了 ExpoGaodeMap.initSDK()
|
|
22
|
+
* 3. 在 AppDelegate 中手动设置
|
|
23
|
+
*
|
|
24
|
+
* 此方法会在首次调用搜索功能时自动执行,手动调用可以提前检测配置问题。
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { initSearch } from '@expo-gaode-map/search';
|
|
29
|
+
*
|
|
30
|
+
* // 可选:提前初始化以检测问题
|
|
31
|
+
* initSearch();
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function initSearch(): void {
|
|
35
|
+
ExpoGaodeMapSearchModule.initSearch();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* POI 搜索
|
|
40
|
+
*
|
|
41
|
+
* @param options 搜索选项
|
|
42
|
+
* @returns 搜索结果
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const result = await searchPOI({
|
|
47
|
+
* keyword: '酒店',
|
|
48
|
+
* city: '北京',
|
|
49
|
+
* pageSize: 20,
|
|
50
|
+
* pageNum: 1,
|
|
51
|
+
* });
|
|
52
|
+
* console.log('找到', result.total, '个结果');
|
|
53
|
+
* result.pois.forEach(poi => {
|
|
54
|
+
* console.log(poi.name, poi.address);
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export async function searchPOI(options: POISearchOptions): Promise<SearchResult> {
|
|
59
|
+
return await ExpoGaodeMapSearchModule.searchPOI(options);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 周边搜索
|
|
64
|
+
*
|
|
65
|
+
* @param options 搜索选项
|
|
66
|
+
* @returns 搜索结果
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const result = await searchNearby({
|
|
71
|
+
* keyword: '餐厅',
|
|
72
|
+
* center: { latitude: 39.9, longitude: 116.4 },
|
|
73
|
+
* radius: 1000,
|
|
74
|
+
* });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export async function searchNearby(options: NearbySearchOptions): Promise<SearchResult> {
|
|
78
|
+
return await ExpoGaodeMapSearchModule.searchNearby(options);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 沿途搜索
|
|
83
|
+
*
|
|
84
|
+
* @param options 搜索选项
|
|
85
|
+
* @returns 搜索结果
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const result = await searchAlong({
|
|
90
|
+
* keyword: '加油站',
|
|
91
|
+
* polyline: [
|
|
92
|
+
* { latitude: 39.9, longitude: 116.4 },
|
|
93
|
+
* { latitude: 39.91, longitude: 116.41 },
|
|
94
|
+
* ],
|
|
95
|
+
* range: 500,
|
|
96
|
+
* });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export async function searchAlong(options: AlongSearchOptions): Promise<SearchResult> {
|
|
100
|
+
return await ExpoGaodeMapSearchModule.searchAlong(options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 多边形搜索
|
|
105
|
+
*
|
|
106
|
+
* @param options 搜索选项
|
|
107
|
+
* @returns 搜索结果
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const result = await searchPolygon({
|
|
112
|
+
* keyword: '学校',
|
|
113
|
+
* polygon: [
|
|
114
|
+
* { latitude: 39.9, longitude: 116.4 },
|
|
115
|
+
* { latitude: 39.91, longitude: 116.4 },
|
|
116
|
+
* { latitude: 39.91, longitude: 116.41 },
|
|
117
|
+
* ],
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export async function searchPolygon(options: PolygonSearchOptions): Promise<SearchResult> {
|
|
122
|
+
return await ExpoGaodeMapSearchModule.searchPolygon(options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 输入提示
|
|
127
|
+
*
|
|
128
|
+
* @param options 搜索选项
|
|
129
|
+
* @returns 提示结果
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const result = await getInputTips({
|
|
134
|
+
* keyword: '天安门',
|
|
135
|
+
* city: '北京',
|
|
136
|
+
* });
|
|
137
|
+
* result.tips.forEach(tip => {
|
|
138
|
+
* console.log(tip.name, tip.address);
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export async function getInputTips(options: InputTipsOptions): Promise<InputTipsResult> {
|
|
143
|
+
return await ExpoGaodeMapSearchModule.getInputTips(options);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 导出类型和枚举
|
|
147
|
+
export type {
|
|
148
|
+
Coordinates,
|
|
149
|
+
POI,
|
|
150
|
+
POISearchOptions,
|
|
151
|
+
NearbySearchOptions,
|
|
152
|
+
AlongSearchOptions,
|
|
153
|
+
PolygonSearchOptions,
|
|
154
|
+
InputTipsOptions,
|
|
155
|
+
InputTip,
|
|
156
|
+
SearchResult,
|
|
157
|
+
InputTipsResult,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export { SearchType } from './ExpoGaodeMapSearch.types';
|
|
161
|
+
|
|
162
|
+
// 默认导出
|
|
163
|
+
export default {
|
|
164
|
+
initSearch,
|
|
165
|
+
searchPOI,
|
|
166
|
+
searchNearby,
|
|
167
|
+
searchAlong,
|
|
168
|
+
searchPolygon,
|
|
169
|
+
getInputTips,
|
|
170
|
+
};
|
package/.eslintrc.js
DELETED
package/package.json.backup
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "expo-gaode-map-search",
|
|
3
|
-
"version": "1.1.1-next.0",
|
|
4
|
-
"description": "高德地图搜索功能模块 - POI搜索、关键词搜索、周边搜索,需先安装expo-gaode-map",
|
|
5
|
-
"main": "build/index.js",
|
|
6
|
-
"types": "build/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "expo-module build",
|
|
9
|
-
"clean": "expo-module clean",
|
|
10
|
-
"lint": "expo-module lint",
|
|
11
|
-
"test": "expo-module test",
|
|
12
|
-
"prepare": "expo-module prepare",
|
|
13
|
-
"prepublishOnly": "expo-module prepublishOnly",
|
|
14
|
-
"expo-module": "expo-module",
|
|
15
|
-
"postinstall": "node -e \"try{require.resolve('expo-gaode-map');process.exit(0)}catch(e1){try{require.resolve('expo-gaode-map-navigation');process.exit(0)}catch(e2){console.error('[expo-gaode-map-search] 需要安装基础地图组件:expo-gaode-map 或 expo-gaode-map-navigation 中的任意一个。\\n请执行:pnpm add expo-gaode-map 或 pnpm add expo-gaode-map-navigation');process.exit(1)}}\""
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"react-native",
|
|
19
|
-
"expo",
|
|
20
|
-
"expo-gaode-map",
|
|
21
|
-
"amap",
|
|
22
|
-
"search",
|
|
23
|
-
"poi",
|
|
24
|
-
"高德地图",
|
|
25
|
-
"搜索"
|
|
26
|
-
],
|
|
27
|
-
"repository": "https://github.com/TomWq/expo-gaode-map",
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/TomWq/expo-gaode-map/issues"
|
|
30
|
-
},
|
|
31
|
-
"author": "尚博信_王强 <wangqiang03@sunboxsoft.com> (https://github.com/TomWq)",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"homepage": "https://github.com/TomWq/expo-gaode-map#readme",
|
|
34
|
-
"dependencies": {
|
|
35
|
-
"expo-gaode-map": "^2.2.2-next.0"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/react": "~19.1.0",
|
|
39
|
-
"expo": "^54.0.18",
|
|
40
|
-
"expo-module-scripts": "^5.0.7",
|
|
41
|
-
"react-native": "0.81.5",
|
|
42
|
-
"typescript": "^5.9.3"
|
|
43
|
-
},
|
|
44
|
-
"peerDependencies": {
|
|
45
|
-
"expo": "*",
|
|
46
|
-
"react": "*",
|
|
47
|
-
"react-native": "*"
|
|
48
|
-
}
|
|
49
|
-
}
|