@react-native-kakao/map 2.2.6 → 3.1.0-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.
Files changed (165) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -38
  3. package/RNCKakaoMap.podspec +16 -52
  4. package/android/build.gradle +12 -107
  5. package/android/consumer-rules.pro +2 -0
  6. package/android/src/main/AndroidManifest.xml +3 -1
  7. package/android/src/main/java/net/mjstudio/rnkakao/map/CameraOptions.kt +61 -0
  8. package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapModule.kt +131 -0
  9. package/android/src/main/java/net/mjstudio/rnkakao/map/KakaoMapView.kt +1257 -0
  10. package/android/src/main/java/net/mjstudio/rnkakao/map/MapCameraCommands.kt +306 -0
  11. package/android/src/main/java/net/mjstudio/rnkakao/map/MapDimScreen.kt +431 -0
  12. package/android/src/main/java/net/mjstudio/rnkakao/map/MapGui.kt +899 -0
  13. package/android/src/main/java/net/mjstudio/rnkakao/map/MapImageLoader.kt +232 -0
  14. package/android/src/main/java/net/mjstudio/rnkakao/map/MapLabels.kt +1572 -0
  15. package/android/src/main/java/net/mjstudio/rnkakao/map/MapOptions.kt +360 -0
  16. package/android/src/main/java/net/mjstudio/rnkakao/map/MapRoutes.kt +805 -0
  17. package/android/src/main/java/net/mjstudio/rnkakao/map/MapShapes.kt +1096 -0
  18. package/android/src/main/java/net/mjstudio/rnkakao/map/MapUtilities.kt +495 -0
  19. package/expo-module.config.json +5 -0
  20. package/ios/CameraOptions.swift +41 -0
  21. package/ios/KakaoMapModule.swift +204 -0
  22. package/ios/KakaoMapView.swift +1018 -0
  23. package/ios/MapCameraCommands.swift +451 -0
  24. package/ios/MapDimScreen.swift +463 -0
  25. package/ios/MapGui.swift +1099 -0
  26. package/ios/MapImageLoader.swift +177 -0
  27. package/ios/MapLabels.swift +1676 -0
  28. package/ios/MapOptions.swift +374 -0
  29. package/ios/MapRoutes.swift +903 -0
  30. package/ios/MapShapes.swift +1294 -0
  31. package/ios/MapUnits.swift +20 -0
  32. package/ios/MapUtilities.swift +365 -0
  33. package/lib/commonjs/index.js +180 -29
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/index.web.js +18 -0
  36. package/lib/commonjs/index.web.js.map +1 -0
  37. package/lib/commonjs/package.json +1 -0
  38. package/lib/commonjs/types-assets.js +2 -0
  39. package/lib/commonjs/types-assets.js.map +1 -0
  40. package/lib/commonjs/types-camera.js +6 -0
  41. package/lib/commonjs/types-camera.js.map +1 -0
  42. package/lib/commonjs/types-dim.js +6 -0
  43. package/lib/commonjs/types-dim.js.map +1 -0
  44. package/lib/commonjs/types-gui.js +6 -0
  45. package/lib/commonjs/types-gui.js.map +1 -0
  46. package/lib/commonjs/types-labels.js +6 -0
  47. package/lib/commonjs/types-labels.js.map +1 -0
  48. package/lib/commonjs/types-routes.js +6 -0
  49. package/lib/commonjs/types-routes.js.map +1 -0
  50. package/lib/commonjs/types-shapes.js +6 -0
  51. package/lib/commonjs/types-shapes.js.map +1 -0
  52. package/lib/commonjs/types-utilities.js +6 -0
  53. package/lib/commonjs/types-utilities.js.map +1 -0
  54. package/lib/commonjs/types.js +6 -0
  55. package/lib/commonjs/types.js.map +1 -0
  56. package/lib/module/index.js +180 -22
  57. package/lib/module/index.js.map +1 -1
  58. package/lib/module/index.web.js +13 -0
  59. package/lib/module/index.web.js.map +1 -0
  60. package/lib/module/types-assets.js +2 -0
  61. package/lib/module/types-assets.js.map +1 -0
  62. package/lib/module/types-camera.js +4 -0
  63. package/lib/module/types-camera.js.map +1 -0
  64. package/lib/module/types-dim.js +4 -0
  65. package/lib/module/types-dim.js.map +1 -0
  66. package/lib/module/types-gui.js +4 -0
  67. package/lib/module/types-gui.js.map +1 -0
  68. package/lib/module/types-labels.js +4 -0
  69. package/lib/module/types-labels.js.map +1 -0
  70. package/lib/module/types-routes.js +4 -0
  71. package/lib/module/types-routes.js.map +1 -0
  72. package/lib/module/types-shapes.js +4 -0
  73. package/lib/module/types-shapes.js.map +1 -0
  74. package/lib/module/types-utilities.js +4 -0
  75. package/lib/module/types-utilities.js.map +1 -0
  76. package/lib/module/types.js +4 -0
  77. package/lib/module/types.js.map +1 -0
  78. package/lib/typescript/src/index.d.ts +59 -9
  79. package/lib/typescript/src/index.d.ts.map +1 -1
  80. package/lib/typescript/src/types-assets.d.ts +9 -0
  81. package/lib/typescript/src/types-assets.d.ts.map +1 -0
  82. package/lib/typescript/src/types-camera.d.ts +123 -0
  83. package/lib/typescript/src/types-camera.d.ts.map +1 -0
  84. package/lib/typescript/src/types-dim.d.ts +54 -0
  85. package/lib/typescript/src/types-dim.d.ts.map +1 -0
  86. package/lib/typescript/src/types-gui.d.ts +189 -0
  87. package/lib/typescript/src/types-gui.d.ts.map +1 -0
  88. package/lib/typescript/src/types-labels.d.ts +246 -0
  89. package/lib/typescript/src/types-labels.d.ts.map +1 -0
  90. package/lib/typescript/src/types-routes.d.ts +162 -0
  91. package/lib/typescript/src/types-routes.d.ts.map +1 -0
  92. package/lib/typescript/src/types-shapes.d.ts +253 -0
  93. package/lib/typescript/src/types-shapes.d.ts.map +1 -0
  94. package/lib/typescript/src/types-utilities.d.ts +130 -0
  95. package/lib/typescript/src/types-utilities.d.ts.map +1 -0
  96. package/lib/typescript/src/types.d.ts +222 -0
  97. package/lib/typescript/src/types.d.ts.map +1 -0
  98. package/package.json +24 -77
  99. package/src/index.tsx +322 -0
  100. package/src/index.web.tsx +46 -0
  101. package/src/types-assets.ts +8 -0
  102. package/src/types-camera.ts +97 -0
  103. package/src/types-dim.ts +54 -0
  104. package/src/types-gui.ts +171 -0
  105. package/src/types-labels.ts +207 -0
  106. package/src/types-routes.ts +118 -0
  107. package/src/types-shapes.ts +244 -0
  108. package/src/types-utilities.ts +115 -0
  109. package/src/types.ts +223 -0
  110. package/android/gradle.properties +0 -1
  111. package/android/src/main/AndroidManifestNew.xml +0 -2
  112. package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapModule.kt +0 -25
  113. package/android/src/main/java/net/mjstudio/rnkakao/map/RNCKakaoMapPackage.kt +0 -35
  114. package/android/src/newarch/KakaoMapSpec.kt +0 -7
  115. package/android/src/oldarch/KakaoMapSpec.kt +0 -14
  116. package/ios/Component/Map/RNCKakaoMapView.h +0 -23
  117. package/ios/Component/Map/RNCKakaoMapView.mm +0 -209
  118. package/ios/Component/Map/RNCKakaoMapViewManager.mm +0 -19
  119. package/ios/NativeModule/RNCKakaoMap.h +0 -7
  120. package/ios/NativeModule/RNCKakaoMap.mm +0 -19
  121. package/ios/NativeModule/RNCKakaoMapManager.swift +0 -7
  122. package/ios/Util/Util.h +0 -97
  123. package/lib/commonjs/component/KakaoMapView.js +0 -54
  124. package/lib/commonjs/component/KakaoMapView.js.map +0 -1
  125. package/lib/commonjs/spec/NativeKakaoMap.js +0 -9
  126. package/lib/commonjs/spec/NativeKakaoMap.js.map +0 -1
  127. package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js +0 -15
  128. package/lib/commonjs/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
  129. package/lib/commonjs/type/type.js +0 -2
  130. package/lib/commonjs/type/type.js.map +0 -1
  131. package/lib/commonjs/util/Const.js +0 -14
  132. package/lib/commonjs/util/Const.js.map +0 -1
  133. package/lib/commonjs/util/Util.js +0 -15
  134. package/lib/commonjs/util/Util.js.map +0 -1
  135. package/lib/module/component/KakaoMapView.js +0 -48
  136. package/lib/module/component/KakaoMapView.js.map +0 -1
  137. package/lib/module/spec/NativeKakaoMap.js +0 -3
  138. package/lib/module/spec/NativeKakaoMap.js.map +0 -1
  139. package/lib/module/spec/RNCKakaoMapViewNativeComponent.js +0 -10
  140. package/lib/module/spec/RNCKakaoMapViewNativeComponent.js.map +0 -1
  141. package/lib/module/type/type.js +0 -2
  142. package/lib/module/type/type.js.map +0 -1
  143. package/lib/module/util/Const.js +0 -8
  144. package/lib/module/util/Const.js.map +0 -1
  145. package/lib/module/util/Util.js +0 -9
  146. package/lib/module/util/Util.js.map +0 -1
  147. package/lib/typescript/src/component/KakaoMapView.d.ts +0 -25
  148. package/lib/typescript/src/component/KakaoMapView.d.ts.map +0 -1
  149. package/lib/typescript/src/spec/NativeKakaoMap.d.ts +0 -7
  150. package/lib/typescript/src/spec/NativeKakaoMap.d.ts.map +0 -1
  151. package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts +0 -32
  152. package/lib/typescript/src/spec/RNCKakaoMapViewNativeComponent.d.ts.map +0 -1
  153. package/lib/typescript/src/type/type.d.ts +0 -8
  154. package/lib/typescript/src/type/type.d.ts.map +0 -1
  155. package/lib/typescript/src/util/Const.d.ts +0 -8
  156. package/lib/typescript/src/util/Const.d.ts.map +0 -1
  157. package/lib/typescript/src/util/Util.d.ts +0 -3
  158. package/lib/typescript/src/util/Util.d.ts.map +0 -1
  159. package/src/component/KakaoMapView.tsx +0 -82
  160. package/src/index.ts +0 -43
  161. package/src/spec/NativeKakaoMap.ts +0 -8
  162. package/src/spec/RNCKakaoMapViewNativeComponent.ts +0 -38
  163. package/src/type/type.ts +0 -7
  164. package/src/util/Const.ts +0 -7
  165. 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,5 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": { "modules": ["KakaoMapModule"], "podspecPath": "RNCKakaoMap.podspec" },
4
+ "android": { "modules": ["net.mjstudio.rnkakao.map.KakaoMapModule"] }
5
+ }
@@ -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
+ }