@react-native-kakao/map 2.2.6 → 3.2.0
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/CHANGELOG.md +27 -0
- package/README.md +5 -38
- package/RNCKakaoMap.podspec +16 -52
- package/android/build.gradle +12 -107
- package/android/consumer-rules.pro +2 -0
- package/android/src/main/AndroidManifest.xml +3 -1
- package/android/src/main/java/net/mjstudio/rnkakao/map/CameraOptions.kt +61 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapModule.kt +131 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapView.kt +1257 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapCameraCommands.kt +306 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapDimScreen.kt +431 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapGui.kt +899 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapImageLoader.kt +232 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapLabels.kt +1572 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapOptions.kt +360 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapRoutes.kt +805 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapShapes.kt +1096 -0
- package/android/src/main/java/net/mjstudio/rnkakao/map/MapUtilities.kt +495 -0
- package/expo-module.config.json +5 -0
- package/ios/CameraOptions.swift +41 -0
- package/ios/KakaoMapModule.swift +204 -0
- package/ios/KakaoMapView.swift +1018 -0
- package/ios/MapCameraCommands.swift +451 -0
- package/ios/MapDimScreen.swift +463 -0
- package/ios/MapGui.swift +1099 -0
- package/ios/MapImageLoader.swift +177 -0
- package/ios/MapLabels.swift +1676 -0
- package/ios/MapOptions.swift +374 -0
- package/ios/MapRoutes.swift +903 -0
- package/ios/MapShapes.swift +1294 -0
- package/ios/MapUnits.swift +20 -0
- package/ios/MapUtilities.swift +365 -0
- package/lib/commonjs/index.js +180 -29
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +18 -0
- package/lib/commonjs/index.web.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types-assets.js +2 -0
- package/lib/commonjs/types-assets.js.map +1 -0
- package/lib/commonjs/types-camera.js +6 -0
- package/lib/commonjs/types-camera.js.map +1 -0
- package/lib/commonjs/types-dim.js +6 -0
- package/lib/commonjs/types-dim.js.map +1 -0
- package/lib/commonjs/types-gui.js +6 -0
- package/lib/commonjs/types-gui.js.map +1 -0
- package/lib/commonjs/types-labels.js +6 -0
- package/lib/commonjs/types-labels.js.map +1 -0
- package/lib/commonjs/types-routes.js +6 -0
- package/lib/commonjs/types-routes.js.map +1 -0
- package/lib/commonjs/types-shapes.js +6 -0
- package/lib/commonjs/types-shapes.js.map +1 -0
- package/lib/commonjs/types-utilities.js +6 -0
- package/lib/commonjs/types-utilities.js.map +1 -0
- package/lib/commonjs/types.js +6 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/module/index.js +180 -22
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +13 -0
- package/lib/module/index.web.js.map +1 -0
- package/lib/module/types-assets.js +2 -0
- package/lib/module/types-assets.js.map +1 -0
- package/lib/module/types-camera.js +4 -0
- package/lib/module/types-camera.js.map +1 -0
- package/lib/module/types-dim.js +4 -0
- package/lib/module/types-dim.js.map +1 -0
- package/lib/module/types-gui.js +4 -0
- package/lib/module/types-gui.js.map +1 -0
- package/lib/module/types-labels.js +4 -0
- package/lib/module/types-labels.js.map +1 -0
- package/lib/module/types-routes.js +4 -0
- package/lib/module/types-routes.js.map +1 -0
- package/lib/module/types-shapes.js +4 -0
- package/lib/module/types-shapes.js.map +1 -0
- package/lib/module/types-utilities.js +4 -0
- package/lib/module/types-utilities.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/src/index.d.ts +59 -9
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types-assets.d.ts +9 -0
- package/lib/typescript/src/types-assets.d.ts.map +1 -0
- package/lib/typescript/src/types-camera.d.ts +123 -0
- package/lib/typescript/src/types-camera.d.ts.map +1 -0
- package/lib/typescript/src/types-dim.d.ts +54 -0
- package/lib/typescript/src/types-dim.d.ts.map +1 -0
- package/lib/typescript/src/types-gui.d.ts +189 -0
- package/lib/typescript/src/types-gui.d.ts.map +1 -0
- package/lib/typescript/src/types-labels.d.ts +246 -0
- package/lib/typescript/src/types-labels.d.ts.map +1 -0
- package/lib/typescript/src/types-routes.d.ts +162 -0
- package/lib/typescript/src/types-routes.d.ts.map +1 -0
- package/lib/typescript/src/types-shapes.d.ts +253 -0
- package/lib/typescript/src/types-shapes.d.ts.map +1 -0
- package/lib/typescript/src/types-utilities.d.ts +130 -0
- package/lib/typescript/src/types-utilities.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +222 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +24 -77
- package/src/index.tsx +322 -0
- package/src/index.web.tsx +46 -0
- package/src/types-assets.ts +8 -0
- package/src/types-camera.ts +97 -0
- package/src/types-dim.ts +54 -0
- package/src/types-gui.ts +171 -0
- package/src/types-labels.ts +207 -0
- package/src/types-routes.ts +118 -0
- package/src/types-shapes.ts +244 -0
- package/src/types-utilities.ts +115 -0
- package/src/types.ts +223 -0
- package/android/gradle.properties +0 -1
- package/android/src/main/AndroidManifestNew.xml +0 -2
- package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapModule.kt +0 -25
- package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapPackage.kt +0 -35
- package/android/src/newarch/KakaoMapSpec.kt +0 -7
- package/android/src/oldarch/KakaoMapSpec.kt +0 -14
- package/ios/Component/Map/RNCKakaoMapView.h +0 -23
- package/ios/Component/Map/RNCKakaoMapView.mm +0 -209
- package/ios/Component/Map/RNCKakaoMapViewManager.mm +0 -19
- package/ios/NativeModule/RNCKakaoMap.h +0 -7
- package/ios/NativeModule/RNCKakaoMap.mm +0 -19
- package/ios/NativeModule/RNCKakaoMapManager.swift +0 -7
- package/ios/Util/Util.h +0 -97
- package/lib/commonjs/component/KakaoMapView.js +0 -54
- package/lib/commonjs/component/KakaoMapView.js.map +0 -1
- package/lib/commonjs/spec/NativeKakaoMap.js +0 -9
- package/lib/commonjs/spec/NativeKakaoMap.js.map +0 -1
- package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js +0 -15
- package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
- package/lib/commonjs/type/type.js +0 -2
- package/lib/commonjs/type/type.js.map +0 -1
- package/lib/commonjs/util/Const.js +0 -14
- package/lib/commonjs/util/Const.js.map +0 -1
- package/lib/commonjs/util/Util.js +0 -15
- package/lib/commonjs/util/Util.js.map +0 -1
- package/lib/module/component/KakaoMapView.js +0 -48
- package/lib/module/component/KakaoMapView.js.map +0 -1
- package/lib/module/spec/NativeKakaoMap.js +0 -3
- package/lib/module/spec/NativeKakaoMap.js.map +0 -1
- package/lib/module/spec/RNCKakaoMapViewNativeComponent.js +0 -10
- package/lib/module/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
- package/lib/module/type/type.js +0 -2
- package/lib/module/type/type.js.map +0 -1
- package/lib/module/util/Const.js +0 -8
- package/lib/module/util/Const.js.map +0 -1
- package/lib/module/util/Util.js +0 -9
- package/lib/module/util/Util.js.map +0 -1
- package/lib/typescript/src/component/KakaoMapView.d.ts +0 -25
- package/lib/typescript/src/component/KakaoMapView.d.ts.map +0 -1
- package/lib/typescript/src/spec/NativeKakaoMap.d.ts +0 -7
- package/lib/typescript/src/spec/NativeKakaoMap.d.ts.map +0 -1
- package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts +0 -32
- package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts.map +0 -1
- package/lib/typescript/src/type/type.d.ts +0 -8
- package/lib/typescript/src/type/type.d.ts.map +0 -1
- package/lib/typescript/src/util/Const.d.ts +0 -8
- package/lib/typescript/src/util/Const.d.ts.map +0 -1
- package/lib/typescript/src/util/Util.d.ts +0 -3
- package/lib/typescript/src/util/Util.d.ts.map +0 -1
- package/src/component/KakaoMapView.tsx +0 -82
- package/src/index.ts +0 -43
- package/src/spec/NativeKakaoMap.ts +0 -8
- package/src/spec/RNCKakaoMapViewNativeComponent.ts +0 -38
- package/src/type/type.ts +0 -7
- package/src/util/Const.ts +0 -7
- package/src/util/Util.ts +0 -11
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
package net.mjstudio.rnkakao.map
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import com.kakao.vectormap.BuildConfig
|
|
7
|
+
import com.kakao.vectormap.CoordConverter
|
|
8
|
+
import com.kakao.vectormap.KakaoMap
|
|
9
|
+
import com.kakao.vectormap.PoiCategory
|
|
10
|
+
import com.kakao.vectormap.PoiScale
|
|
11
|
+
import com.kakao.vectormap.PoiState
|
|
12
|
+
import expo.modules.kotlin.Promise
|
|
13
|
+
import expo.modules.kotlin.exception.CodedException
|
|
14
|
+
import java.lang.ref.WeakReference
|
|
15
|
+
import kotlin.math.roundToInt
|
|
16
|
+
|
|
17
|
+
private const val UTILITY_UNSUPPORTED = "ERR_KAKAO_MAP_UNSUPPORTED_PLATFORM_FEATURE"
|
|
18
|
+
private const val UTILITY_DESTROYED = "ERR_KAKAO_MAP_VIEW_DESTROYED"
|
|
19
|
+
private const val POI_STATE_TIMEOUT = "ERR_KAKAO_MAP_POI_STATE_TIMEOUT"
|
|
20
|
+
private const val POI_STATE_TIMEOUT_MILLIS = 3_000L
|
|
21
|
+
private val fontResourceNamePattern = Regex("^[a-z][a-z0-9_]*$")
|
|
22
|
+
|
|
23
|
+
internal class MapUtilities(
|
|
24
|
+
map: KakaoMap,
|
|
25
|
+
context: Context,
|
|
26
|
+
) {
|
|
27
|
+
private var map: KakaoMap? = map
|
|
28
|
+
private val applicationContext = context.applicationContext
|
|
29
|
+
private val density = applicationContext.resources.displayMetrics.density
|
|
30
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
31
|
+
private val pendingPoiStateRequests = mutableMapOf<Any, PendingPoiStateRequest>()
|
|
32
|
+
|
|
33
|
+
fun command(command: Map<String, Any?>): Map<String, Any?> {
|
|
34
|
+
requireMap()
|
|
35
|
+
return when (command.utilityString("type")) {
|
|
36
|
+
"getCapabilities" -> {
|
|
37
|
+
capabilities()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
"getState" -> {
|
|
41
|
+
state()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
"setPoiVisible" -> {
|
|
45
|
+
requireMap().setPoiVisible(command.utilityBoolean("visible"))
|
|
46
|
+
none()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
"setPoiCategoryVisible" -> {
|
|
50
|
+
requireMap().setVisiblePoiCategory(command.utilityCategory("category"), command.utilityBoolean("visible"))
|
|
51
|
+
none()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
"setPoiCategoryMaxCount" -> {
|
|
55
|
+
requireMap().setPoiVisibleMaxCount(command.utilityCategory("category"), command.utilityInteger("count"))
|
|
56
|
+
none()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
"resetPoiCategoryMaxCount" -> {
|
|
60
|
+
requireMap().resetPoiVisibleMaxCount(command.utilityCategory("category"))
|
|
61
|
+
none()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
"setTapInterval" -> {
|
|
65
|
+
requireMap().setClickIntervalTime(command.utilityInteger("milliseconds"))
|
|
66
|
+
none()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
"setCameraAnimationEnabled" -> {
|
|
70
|
+
requireMap().setCameraAnimateEnable(command.utilityBoolean("enabled"))
|
|
71
|
+
none()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
"setEnabled" -> {
|
|
75
|
+
unsupported("The Kakao Maps Android SDK does not expose an enabled setting.")
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
"setVisible" -> {
|
|
79
|
+
requireMap().setVisible(command.utilityBoolean("visible"))
|
|
80
|
+
none()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
"setKeepLevelOnResize" -> {
|
|
84
|
+
unsupported("The Kakao Maps Android SDK does not expose keep-level-on-resize.")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
"getViewport" -> {
|
|
88
|
+
viewport()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
"setViewport" -> {
|
|
92
|
+
setViewport(command.utilityObject("viewport"))
|
|
93
|
+
none()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
"refresh" -> {
|
|
97
|
+
unsupported("The Kakao Maps Android SDK does not expose map refresh.")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
"registerFont" -> {
|
|
101
|
+
registerFont(command)
|
|
102
|
+
none()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
"getVersion" -> {
|
|
106
|
+
version(command.utilityString("component"))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
"convertCoordinate" -> {
|
|
110
|
+
convert(command)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
"clearCache" -> {
|
|
114
|
+
clearCache(command.utilityString("kind"))
|
|
115
|
+
none()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
else -> {
|
|
119
|
+
invalidUtility("Unknown map utility command.")
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
fun dispose() {
|
|
125
|
+
map = null
|
|
126
|
+
pendingPoiStateRequests.values.forEach { request ->
|
|
127
|
+
mainHandler.removeCallbacks(request.timeout)
|
|
128
|
+
request.promise.reject(UTILITY_DESTROYED, "The map view was destroyed.", null)
|
|
129
|
+
}
|
|
130
|
+
pendingPoiStateRequests.clear()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fun requestPoiState(promise: Promise) {
|
|
134
|
+
val map =
|
|
135
|
+
try {
|
|
136
|
+
requireMap()
|
|
137
|
+
} catch (error: CodedException) {
|
|
138
|
+
promise.reject(error)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
val requestId = Any()
|
|
142
|
+
val utilitiesReference = WeakReference(this)
|
|
143
|
+
val mapReference = WeakReference(map)
|
|
144
|
+
val timeout =
|
|
145
|
+
Runnable {
|
|
146
|
+
utilitiesReference.get()?.rejectPoiStateRequest(
|
|
147
|
+
requestId,
|
|
148
|
+
POI_STATE_TIMEOUT,
|
|
149
|
+
"Kakao Maps did not return POI state within $POI_STATE_TIMEOUT_MILLIS milliseconds.",
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
pendingPoiStateRequests[requestId] = PendingPoiStateRequest(promise, timeout)
|
|
153
|
+
mainHandler.postDelayed(timeout, POI_STATE_TIMEOUT_MILLIS)
|
|
154
|
+
val callbackHandler = mainHandler
|
|
155
|
+
try {
|
|
156
|
+
map.requestPoiState { callbackMap, state ->
|
|
157
|
+
val callbackMapReference = WeakReference(callbackMap)
|
|
158
|
+
callbackHandler.post {
|
|
159
|
+
utilitiesReference.get()?.resolvePoiStateRequest(requestId, mapReference.get(), callbackMapReference.get(), state)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (error: Exception) {
|
|
163
|
+
rejectPoiStateRequest(requestId, error.javaClass.simpleName, error.message ?: "POI-state request failed.", error)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private fun capabilities(): Map<String, Any?> =
|
|
168
|
+
mapOf(
|
|
169
|
+
"type" to "capabilities",
|
|
170
|
+
"poi" to
|
|
171
|
+
mapOf(
|
|
172
|
+
"visibility" to true,
|
|
173
|
+
"visibilityReadable" to true,
|
|
174
|
+
"categoryVisibility" to true,
|
|
175
|
+
"categoryMaxCount" to true,
|
|
176
|
+
"stateRequest" to true,
|
|
177
|
+
"categories" to utilityCategoryNames,
|
|
178
|
+
),
|
|
179
|
+
"settings" to
|
|
180
|
+
mapOf(
|
|
181
|
+
"tapInterval" to true,
|
|
182
|
+
"cameraAnimationEnabled" to true,
|
|
183
|
+
"cameraAnimationEnabledReadable" to false,
|
|
184
|
+
"enabled" to false,
|
|
185
|
+
"visible" to true,
|
|
186
|
+
"keepLevelOnResize" to false,
|
|
187
|
+
"refresh" to false,
|
|
188
|
+
"focusedReadable" to false,
|
|
189
|
+
),
|
|
190
|
+
"viewport" to mapOf("readable" to true, "writable" to true),
|
|
191
|
+
"fontRegistration" to true,
|
|
192
|
+
"versionComponents" to listOf("sdk", "engine"),
|
|
193
|
+
"coordinateSystems" to androidCoordinateSystems,
|
|
194
|
+
"coordinateConversions" to utilityConversions(androidCoordinateSystems),
|
|
195
|
+
"cacheKinds" to listOf("disk", "all"),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
private fun state(): Map<String, Any?> {
|
|
199
|
+
val map = requireMap()
|
|
200
|
+
return mapOf(
|
|
201
|
+
"type" to "state",
|
|
202
|
+
"tapInterval" to map.clickIntervalTime,
|
|
203
|
+
"poiVisible" to map.isPoiVisible,
|
|
204
|
+
"visible" to map.isVisible,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private fun version(component: String): Map<String, Any?> {
|
|
209
|
+
val value =
|
|
210
|
+
when (component) {
|
|
211
|
+
"sdk" -> BuildConfig.SDK_VERSION
|
|
212
|
+
"engine" -> BuildConfig.ENGINE_VERSION
|
|
213
|
+
else -> invalidUtility("Unknown map version component.")
|
|
214
|
+
}
|
|
215
|
+
if (value.isBlank()) {
|
|
216
|
+
throw CodedException(
|
|
217
|
+
"ERR_KAKAO_MAP_VERSION_UNAVAILABLE",
|
|
218
|
+
"The Kakao Maps $component version is unavailable.",
|
|
219
|
+
null,
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
return mapOf("type" to "version", "component" to component, "version" to value)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun viewport(): Map<String, Any?> {
|
|
226
|
+
val viewport = requireMap().viewport
|
|
227
|
+
return mapOf(
|
|
228
|
+
"type" to "viewport",
|
|
229
|
+
"viewport" to
|
|
230
|
+
mapOf(
|
|
231
|
+
"x" to viewport.left / density.toDouble(),
|
|
232
|
+
"y" to viewport.top / density.toDouble(),
|
|
233
|
+
"width" to viewport.width() / density.toDouble(),
|
|
234
|
+
"height" to viewport.height() / density.toDouble(),
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private fun setViewport(viewport: Map<String, Any?>) {
|
|
240
|
+
val x = viewport.utilityViewportPixel("x", density, isSize = false)
|
|
241
|
+
val y = viewport.utilityViewportPixel("y", density, isSize = false)
|
|
242
|
+
val width = viewport.utilityViewportPixel("width", density, isSize = true)
|
|
243
|
+
val height = viewport.utilityViewportPixel("height", density, isSize = true)
|
|
244
|
+
if (x.toLong() + width > Int.MAX_VALUE || y.toLong() + height > Int.MAX_VALUE) {
|
|
245
|
+
invalidUtility("Viewport edges exceed the Android pixel range.")
|
|
246
|
+
}
|
|
247
|
+
requireMap().setViewport(x, y, width, height)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private fun registerFont(command: Map<String, Any?>) {
|
|
251
|
+
val resourceName = command.utilityString("resourceName")
|
|
252
|
+
if (!fontResourceNamePattern.matches(resourceName)) {
|
|
253
|
+
invalidUtility("resourceName must be a lowercase Android font resource name.")
|
|
254
|
+
}
|
|
255
|
+
val resourceId =
|
|
256
|
+
applicationContext.resources.getIdentifier(resourceName, "font", applicationContext.packageName)
|
|
257
|
+
if (resourceId == 0) {
|
|
258
|
+
throw CodedException(
|
|
259
|
+
"ERR_KAKAO_MAP_FONT_RESOURCE_NOT_FOUND",
|
|
260
|
+
"Android font resource '$resourceName' was not found.",
|
|
261
|
+
null,
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
val name = command.utilityOptionalString("name")
|
|
265
|
+
if (name == null) requireMap().addFont(resourceId) else requireMap().addFont(name, resourceId)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private fun convert(command: Map<String, Any?>): Map<String, Any?> {
|
|
269
|
+
val source = command.utilityString("from")
|
|
270
|
+
val destination = command.utilityString("to")
|
|
271
|
+
if (source !in utilityCoordinateSystems || destination !in utilityCoordinateSystems) {
|
|
272
|
+
invalidUtility("Unknown coordinate system.")
|
|
273
|
+
}
|
|
274
|
+
if (source == "kakao" || destination == "kakao") {
|
|
275
|
+
unsupported("The Kakao Maps Android SDK does not expose Kakao-coordinate conversion.")
|
|
276
|
+
}
|
|
277
|
+
val value = command.utilityObject("coordinate").utilityCoordinate(source)
|
|
278
|
+
val converted =
|
|
279
|
+
when (source to destination) {
|
|
280
|
+
"wgs84" to "wgs84", "wtm" to "wtm", "wcong" to "wcong" -> value
|
|
281
|
+
"wgs84" to "wtm" -> CoordConverter.toWTMFromLatLng(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
282
|
+
"wgs84" to "wcong" -> CoordConverter.toWCONGFromLatLng(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
283
|
+
"wtm" to "wgs84" -> CoordConverter.toLatLngFromWTM(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
284
|
+
"wtm" to "wcong" -> CoordConverter.toWCONGFromWTM(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
285
|
+
"wcong" to "wgs84" -> CoordConverter.toLatLngFromWCONG(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
286
|
+
"wcong" to "wtm" -> CoordConverter.toWTMFromWCONG(value.first, value.second)?.coordinatePair() ?: conversionFailed()
|
|
287
|
+
else -> unsupported("The requested coordinate conversion is unavailable on Android.")
|
|
288
|
+
}
|
|
289
|
+
if (!converted.first.isFinite() || !converted.second.isFinite()) {
|
|
290
|
+
throw CodedException("ERR_KAKAO_MAP_COORDINATE_CONVERSION_FAILED", "Kakao Maps returned an invalid coordinate.", null)
|
|
291
|
+
}
|
|
292
|
+
return mapOf(
|
|
293
|
+
"type" to "coordinate",
|
|
294
|
+
"system" to destination,
|
|
295
|
+
"coordinate" to mapOf("x" to converted.first, "y" to converted.second),
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun clearCache(kind: String) {
|
|
300
|
+
val map = requireMap()
|
|
301
|
+
when (kind) {
|
|
302
|
+
"disk" -> map.clearDiskCache()
|
|
303
|
+
"all" -> map.clearAllCache()
|
|
304
|
+
"memory", "viewInfo" -> unsupported("The Kakao Maps Android SDK does not expose $kind-only cache clearing.")
|
|
305
|
+
else -> invalidUtility("Unknown map cache kind.")
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private fun resolvePoiStateRequest(
|
|
310
|
+
requestId: Any,
|
|
311
|
+
expectedMap: KakaoMap?,
|
|
312
|
+
callbackMap: KakaoMap?,
|
|
313
|
+
state: PoiState,
|
|
314
|
+
) {
|
|
315
|
+
val request = pendingPoiStateRequests.remove(requestId) ?: return
|
|
316
|
+
mainHandler.removeCallbacks(request.timeout)
|
|
317
|
+
if (map == null || expectedMap == null || map !== expectedMap || callbackMap !== expectedMap) {
|
|
318
|
+
request.promise.reject(UTILITY_DESTROYED, "The map view was destroyed.", null)
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
request.promise.resolve(
|
|
322
|
+
mapOf(
|
|
323
|
+
"type" to "poiState",
|
|
324
|
+
"state" to
|
|
325
|
+
mapOf(
|
|
326
|
+
"visible" to state.isVisible,
|
|
327
|
+
"clickable" to state.isClickable,
|
|
328
|
+
"scale" to
|
|
329
|
+
when (state.poiScale) {
|
|
330
|
+
PoiScale.SMALL -> "small"
|
|
331
|
+
PoiScale.REGULAR -> "regular"
|
|
332
|
+
PoiScale.LARGE -> "large"
|
|
333
|
+
PoiScale.XLARGE -> "xLarge"
|
|
334
|
+
},
|
|
335
|
+
"language" to state.poiLanguage,
|
|
336
|
+
),
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private fun rejectPoiStateRequest(
|
|
342
|
+
requestId: Any,
|
|
343
|
+
code: String,
|
|
344
|
+
message: String,
|
|
345
|
+
cause: Throwable? = null,
|
|
346
|
+
) {
|
|
347
|
+
val request = pendingPoiStateRequests.remove(requestId) ?: return
|
|
348
|
+
mainHandler.removeCallbacks(request.timeout)
|
|
349
|
+
request.promise.reject(code, message, cause)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private fun requireMap() = map ?: throw CodedException(UTILITY_DESTROYED, "The map view was destroyed.", null)
|
|
353
|
+
|
|
354
|
+
private data class PendingPoiStateRequest(
|
|
355
|
+
val promise: Promise,
|
|
356
|
+
val timeout: Runnable,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private val androidCoordinateSystems = listOf("wgs84", "wtm", "wcong")
|
|
361
|
+
|
|
362
|
+
private val utilityCategoryNames =
|
|
363
|
+
listOf(
|
|
364
|
+
"policyCategory",
|
|
365
|
+
"address",
|
|
366
|
+
"district",
|
|
367
|
+
"busStop",
|
|
368
|
+
"railroadPlatform",
|
|
369
|
+
"railroadEntrance",
|
|
370
|
+
"railroadLine",
|
|
371
|
+
"brand",
|
|
372
|
+
"place",
|
|
373
|
+
"climb",
|
|
374
|
+
"climbWave",
|
|
375
|
+
"road",
|
|
376
|
+
"roadNumber",
|
|
377
|
+
"roadInfo",
|
|
378
|
+
"facilities",
|
|
379
|
+
"publicFacilities",
|
|
380
|
+
"apartNumber",
|
|
381
|
+
"apartDetail",
|
|
382
|
+
"houseNumber",
|
|
383
|
+
"land",
|
|
384
|
+
"indoor",
|
|
385
|
+
"indoorPlace",
|
|
386
|
+
"detailCategory",
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
private val utilityCategories =
|
|
390
|
+
mapOf(
|
|
391
|
+
"policyCategory" to PoiCategory.POLICY_CATEGORY,
|
|
392
|
+
"address" to PoiCategory.ADDRESS,
|
|
393
|
+
"district" to PoiCategory.DISTRICT,
|
|
394
|
+
"busStop" to PoiCategory.BUS_STOP,
|
|
395
|
+
"railroadPlatform" to PoiCategory.RAILROAD_PLATFORM,
|
|
396
|
+
"railroadEntrance" to PoiCategory.RAILROAD_ENTRANCE,
|
|
397
|
+
"railroadLine" to PoiCategory.RAILROAD_LINE,
|
|
398
|
+
"brand" to PoiCategory.BRAND,
|
|
399
|
+
"place" to PoiCategory.PLACE,
|
|
400
|
+
"climb" to PoiCategory.CLIMB,
|
|
401
|
+
"climbWave" to PoiCategory.CLIMB_WAVE,
|
|
402
|
+
"road" to PoiCategory.ROAD,
|
|
403
|
+
"roadNumber" to PoiCategory.ROAD_NUMBER,
|
|
404
|
+
"roadInfo" to PoiCategory.ROAD_INFO,
|
|
405
|
+
"facilities" to PoiCategory.FACILITIES,
|
|
406
|
+
"publicFacilities" to PoiCategory.PUBLIC_FACILITIES,
|
|
407
|
+
"apartNumber" to PoiCategory.APART_NUMBER,
|
|
408
|
+
"apartDetail" to PoiCategory.APART_DETAIL,
|
|
409
|
+
"houseNumber" to PoiCategory.HOUSE_NUMBER,
|
|
410
|
+
"land" to PoiCategory.LAND,
|
|
411
|
+
"indoor" to PoiCategory.INDOOR,
|
|
412
|
+
"indoorPlace" to PoiCategory.INDOOR_PLACE,
|
|
413
|
+
"detailCategory" to PoiCategory.DETAIL_CATEGORY,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
private val utilityCoordinateSystems = setOf("wgs84", "wtm", "wcong", "kakao")
|
|
417
|
+
|
|
418
|
+
private fun utilityConversions(systems: List<String>) = systems.flatMap { source -> systems.map { destination -> "$source:$destination" } }
|
|
419
|
+
|
|
420
|
+
private fun com.kakao.vectormap.Coordinate.coordinatePair() = x to y
|
|
421
|
+
|
|
422
|
+
private fun com.kakao.vectormap.LatLng.coordinatePair() = longitude to latitude
|
|
423
|
+
|
|
424
|
+
private fun none(): Map<String, Any?> = mapOf("type" to "none")
|
|
425
|
+
|
|
426
|
+
private fun invalidUtility(message: String): Nothing = throw CodedException(INVALID_ARGUMENT, message, null)
|
|
427
|
+
|
|
428
|
+
private fun unsupported(message: String): Nothing = throw CodedException(UTILITY_UNSUPPORTED, message, null)
|
|
429
|
+
|
|
430
|
+
private fun conversionFailed(): Nothing =
|
|
431
|
+
throw CodedException("ERR_KAKAO_MAP_COORDINATE_CONVERSION_FAILED", "Kakao Maps could not convert the coordinate.", null)
|
|
432
|
+
|
|
433
|
+
private fun Map<String, Any?>.utilityString(key: String): String {
|
|
434
|
+
val value = this[key] as? String
|
|
435
|
+
if (value.isNullOrEmpty()) invalidUtility("$key must be a non-empty string.")
|
|
436
|
+
return value
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private fun Map<String, Any?>.utilityOptionalString(key: String): String? {
|
|
440
|
+
val value = this[key] ?: return null
|
|
441
|
+
if (value !is String || value.isBlank()) invalidUtility("$key must be a non-empty string when provided.")
|
|
442
|
+
return value
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
private fun Map<String, Any?>.utilityBoolean(key: String) = this[key] as? Boolean ?: invalidUtility("$key must be a boolean.")
|
|
446
|
+
|
|
447
|
+
private fun Map<String, Any?>.utilityInteger(key: String): Int {
|
|
448
|
+
val value = (this[key] as? Number)?.toDouble() ?: invalidUtility("$key must be an integer.")
|
|
449
|
+
if (!value.isFinite() || value % 1 != 0.0 || value !in 0.0..Int.MAX_VALUE.toDouble()) {
|
|
450
|
+
invalidUtility("$key must be an integer between 0 and ${Int.MAX_VALUE}.")
|
|
451
|
+
}
|
|
452
|
+
return value.toInt()
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private fun Map<String, Any?>.utilityObject(key: String): Map<String, Any?> {
|
|
456
|
+
val value = this[key] as? Map<*, *> ?: invalidUtility("$key must be an object.")
|
|
457
|
+
return value.entries.associate { (entryKey, entryValue) ->
|
|
458
|
+
val stringKey = entryKey as? String ?: invalidUtility("$key must use string keys.")
|
|
459
|
+
stringKey to entryValue
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private fun Map<String, Any?>.utilityViewportPixel(
|
|
464
|
+
key: String,
|
|
465
|
+
density: Float,
|
|
466
|
+
isSize: Boolean,
|
|
467
|
+
): Int {
|
|
468
|
+
if (!density.isFinite() || density <= 0) invalidUtility("Display density must be positive and finite.")
|
|
469
|
+
val value = (this[key] as? Number)?.toDouble() ?: invalidUtility("viewport.$key must be a number.")
|
|
470
|
+
if (!value.isFinite() || value < 0 || (isSize && value == 0.0)) {
|
|
471
|
+
invalidUtility("viewport.$key must be ${if (isSize) "positive" else "non-negative"} and finite.")
|
|
472
|
+
}
|
|
473
|
+
val pixels = value * density
|
|
474
|
+
if (pixels > Int.MAX_VALUE) invalidUtility("viewport.$key exceeds the Android pixel range.")
|
|
475
|
+
val rounded = pixels.roundToInt()
|
|
476
|
+
if (isSize && rounded == 0) invalidUtility("viewport.$key must resolve to at least one physical pixel.")
|
|
477
|
+
return rounded
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private fun Map<String, Any?>.utilityCategory(key: String): PoiCategory {
|
|
481
|
+
val name = utilityString(key)
|
|
482
|
+
return utilityCategories[name] ?: invalidUtility("Unknown POI category.")
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private fun Map<String, Any?>.utilityCoordinate(system: String): Pair<Double, Double> {
|
|
486
|
+
val x = (this["x"] as? Number)?.toDouble()
|
|
487
|
+
val y = (this["y"] as? Number)?.toDouble()
|
|
488
|
+
if (x == null || y == null || !x.isFinite() || !y.isFinite()) {
|
|
489
|
+
invalidUtility("Coordinate x and y must be finite numbers.")
|
|
490
|
+
}
|
|
491
|
+
if (system == "wgs84" && (x !in -180.0..180.0 || y !in -90.0..90.0)) {
|
|
492
|
+
invalidUtility("WGS84 x must be longitude and y must be latitude.")
|
|
493
|
+
}
|
|
494
|
+
return x to y
|
|
495
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import KakaoMapsSDK
|
|
3
|
+
|
|
4
|
+
let invalidCameraArgument = "ERR_KAKAO_MAP_INVALID_ARGUMENT"
|
|
5
|
+
let mapNotInitialized = "ERR_KAKAO_MAP_NOT_INITIALIZED"
|
|
6
|
+
|
|
7
|
+
final class KakaoMapException: Exception, @unchecked Sendable {
|
|
8
|
+
init(_ code: String, _ message: String) {
|
|
9
|
+
super.init(name: "KakaoMapError", description: message, code: code)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
struct CameraOptions: Record {
|
|
14
|
+
@Field(.required) var latitude: Double = .nan
|
|
15
|
+
@Field(.required) var longitude: Double = .nan
|
|
16
|
+
@Field(.required) var zoomLevel: Double = .nan
|
|
17
|
+
@Field var tilt: Double = 0
|
|
18
|
+
@Field var rotation: Double = 0
|
|
19
|
+
|
|
20
|
+
var values: [Double] {
|
|
21
|
+
[latitude, longitude, zoomLevel, tilt, rotation]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func update(for map: KakaoMap) throws -> CameraUpdate {
|
|
25
|
+
guard values.allSatisfy(\.isFinite), (-90 ... 90).contains(latitude),
|
|
26
|
+
(-180 ... 180).contains(longitude), zoomLevel.rounded() == zoomLevel,
|
|
27
|
+
zoomLevel >= Double(map.minLevel), zoomLevel <= Double(map.maxLevel),
|
|
28
|
+
(0 ... 90).contains(tilt)
|
|
29
|
+
else {
|
|
30
|
+
throw KakaoMapException(
|
|
31
|
+
invalidCameraArgument,
|
|
32
|
+
"Invalid camera coordinates, zoom level, tilt, or rotation."
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
return CameraUpdate.make(
|
|
36
|
+
target: MapPoint(longitude: longitude, latitude: latitude), zoomLevel: Int(zoomLevel),
|
|
37
|
+
rotation: rotation.truncatingRemainder(dividingBy: 360) * .pi / 180, tilt: tilt * .pi / 180,
|
|
38
|
+
mapView: map
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
}
|