@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.
- package/CHANGELOG.md +14 -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,1572 @@
|
|
|
1
|
+
package net.mjstudio.rnkakao.map
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.view.Gravity
|
|
6
|
+
import com.kakao.vectormap.KakaoMap
|
|
7
|
+
import com.kakao.vectormap.LatLng
|
|
8
|
+
import com.kakao.vectormap.animation.Interpolation
|
|
9
|
+
import com.kakao.vectormap.label.Badge
|
|
10
|
+
import com.kakao.vectormap.label.CompetitionType
|
|
11
|
+
import com.kakao.vectormap.label.CompetitionUnit
|
|
12
|
+
import com.kakao.vectormap.label.Label
|
|
13
|
+
import com.kakao.vectormap.label.LabelLayer
|
|
14
|
+
import com.kakao.vectormap.label.LabelLayerOptions
|
|
15
|
+
import com.kakao.vectormap.label.LabelOptions
|
|
16
|
+
import com.kakao.vectormap.label.LabelStyle
|
|
17
|
+
import com.kakao.vectormap.label.LabelStyles
|
|
18
|
+
import com.kakao.vectormap.label.LabelTextBuilder
|
|
19
|
+
import com.kakao.vectormap.label.LabelTextStyle
|
|
20
|
+
import com.kakao.vectormap.label.LabelTransition
|
|
21
|
+
import com.kakao.vectormap.label.LodLabel
|
|
22
|
+
import com.kakao.vectormap.label.LodLabelLayer
|
|
23
|
+
import com.kakao.vectormap.label.OrderingType
|
|
24
|
+
import com.kakao.vectormap.label.PathOptions
|
|
25
|
+
import com.kakao.vectormap.label.PolylineLabel
|
|
26
|
+
import com.kakao.vectormap.label.PolylineLabelOptions
|
|
27
|
+
import com.kakao.vectormap.label.PolylineLabelStyle
|
|
28
|
+
import com.kakao.vectormap.label.PolylineLabelStyles
|
|
29
|
+
import com.kakao.vectormap.label.TrackingManager
|
|
30
|
+
import com.kakao.vectormap.label.TransformMethod
|
|
31
|
+
import com.kakao.vectormap.label.Transition
|
|
32
|
+
import com.kakao.vectormap.label.animation.AlphaAnimation
|
|
33
|
+
import com.kakao.vectormap.label.animation.AlphaAnimations
|
|
34
|
+
import com.kakao.vectormap.label.animation.DropAnimation
|
|
35
|
+
import com.kakao.vectormap.label.animation.ScaleAlphaAnimation
|
|
36
|
+
import com.kakao.vectormap.label.animation.ScaleAlphaAnimations
|
|
37
|
+
import com.kakao.vectormap.label.animation.ScaleAnimation
|
|
38
|
+
import com.kakao.vectormap.label.animation.ScaleAnimations
|
|
39
|
+
import com.kakao.vectormap.label.animation.TransformAnimation
|
|
40
|
+
import com.kakao.vectormap.label.animation.TransformAnimations
|
|
41
|
+
import com.kakao.vectormap.shape.Polygon
|
|
42
|
+
import expo.modules.kotlin.exception.CodedException
|
|
43
|
+
import expo.modules.kotlin.records.Field
|
|
44
|
+
import expo.modules.kotlin.records.Record
|
|
45
|
+
import expo.modules.kotlin.records.Required
|
|
46
|
+
import java.util.UUID
|
|
47
|
+
import kotlin.math.PI
|
|
48
|
+
import kotlin.math.roundToInt
|
|
49
|
+
|
|
50
|
+
internal const val LABEL_INVALID_ARGUMENT = "ERR_KAKAO_MAP_LABEL_INVALID_ARGUMENT"
|
|
51
|
+
internal const val LABEL_NOT_FOUND = "ERR_KAKAO_MAP_LABEL_NOT_FOUND"
|
|
52
|
+
internal const val LABEL_UNSUPPORTED = "ERR_KAKAO_MAP_LABEL_UNSUPPORTED"
|
|
53
|
+
internal const val LABEL_APPLY_CANCELLED = "ERR_KAKAO_MAP_LABEL_APPLY_CANCELLED"
|
|
54
|
+
private const val DEFAULT_LABEL_LAYER_Z_ORDER = 1000
|
|
55
|
+
private const val MIN_CHARACTER_SPACE_PX = -2
|
|
56
|
+
|
|
57
|
+
class MapLabelPoint : Record {
|
|
58
|
+
@Field @Required
|
|
59
|
+
var latitude = Double.NaN
|
|
60
|
+
|
|
61
|
+
@Field @Required
|
|
62
|
+
var longitude = Double.NaN
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class MapLabelVector : Record {
|
|
66
|
+
@Field @Required
|
|
67
|
+
var x = Double.NaN
|
|
68
|
+
|
|
69
|
+
@Field @Required
|
|
70
|
+
var y = Double.NaN
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class MapLabelImage : Record {
|
|
74
|
+
@Field @Required
|
|
75
|
+
var uri = ""
|
|
76
|
+
|
|
77
|
+
@Field var width: Double? = null
|
|
78
|
+
|
|
79
|
+
@Field var height: Double? = null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class MapLabelTextStyleRecord : Record {
|
|
83
|
+
@Field var fontFamily: String? = null
|
|
84
|
+
|
|
85
|
+
@Field @Required
|
|
86
|
+
var fontSize = Double.NaN
|
|
87
|
+
|
|
88
|
+
@Field @Required
|
|
89
|
+
var color = ""
|
|
90
|
+
|
|
91
|
+
@Field var strokeColor: String? = null
|
|
92
|
+
|
|
93
|
+
@Field var strokeWidth = 0.0
|
|
94
|
+
|
|
95
|
+
@Field var characterSpacing = 0.0
|
|
96
|
+
|
|
97
|
+
@Field var lineSpacing = 1.0
|
|
98
|
+
|
|
99
|
+
@Field var aspectRatio = 1.0
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class MapLabelTransitionRecord : Record {
|
|
103
|
+
@Field @Required
|
|
104
|
+
var entrance = "none"
|
|
105
|
+
|
|
106
|
+
@Field @Required
|
|
107
|
+
var exit = "none"
|
|
108
|
+
|
|
109
|
+
@Field var enableEntrance = true
|
|
110
|
+
|
|
111
|
+
@Field var enableExit = true
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class MapLabelBadgeStyleRecord : Record {
|
|
115
|
+
@Field @Required
|
|
116
|
+
var id = ""
|
|
117
|
+
|
|
118
|
+
@Field @Required
|
|
119
|
+
var image = MapLabelImage()
|
|
120
|
+
|
|
121
|
+
@Field var offset: MapLabelVector? = null
|
|
122
|
+
|
|
123
|
+
@Field var zOrder = 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
class MapLabelPerLevelStyleRecord : Record {
|
|
127
|
+
@Field @Required
|
|
128
|
+
var level = 0
|
|
129
|
+
|
|
130
|
+
@Field var image: MapLabelImage? = null
|
|
131
|
+
|
|
132
|
+
@Field var anchor: MapLabelVector? = null
|
|
133
|
+
|
|
134
|
+
@Field var padding = 0.0
|
|
135
|
+
|
|
136
|
+
@Field var iconTransition: MapLabelTransitionRecord? = null
|
|
137
|
+
|
|
138
|
+
@Field var textTransition: MapLabelTransitionRecord? = null
|
|
139
|
+
|
|
140
|
+
@Field var textLayout = "center"
|
|
141
|
+
|
|
142
|
+
@Field var textLines: List<MapLabelTextStyleRecord> = emptyList()
|
|
143
|
+
|
|
144
|
+
@Field var badges: List<MapLabelBadgeStyleRecord> = emptyList()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
class MapLabelStyleRecord : Record {
|
|
148
|
+
@Field @Required
|
|
149
|
+
var id = ""
|
|
150
|
+
|
|
151
|
+
@Field @Required
|
|
152
|
+
var levels: List<MapLabelPerLevelStyleRecord> = emptyList()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class MapPathTextLevelStyleRecord : Record {
|
|
156
|
+
@Field @Required
|
|
157
|
+
var level = 0
|
|
158
|
+
|
|
159
|
+
@Field @Required
|
|
160
|
+
var text = MapLabelTextStyleRecord()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class MapPathTextStyleRecord : Record {
|
|
164
|
+
@Field @Required
|
|
165
|
+
var id = ""
|
|
166
|
+
|
|
167
|
+
@Field @Required
|
|
168
|
+
var levels: List<MapPathTextLevelStyleRecord> = emptyList()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
open class MapLabelLayerRecord : Record {
|
|
172
|
+
@Field @Required
|
|
173
|
+
var id = ""
|
|
174
|
+
|
|
175
|
+
@Field var visible = true
|
|
176
|
+
|
|
177
|
+
@Field var clickable = true
|
|
178
|
+
|
|
179
|
+
@Field var zOrder = DEFAULT_LABEL_LAYER_Z_ORDER
|
|
180
|
+
|
|
181
|
+
@Field var competition = "none"
|
|
182
|
+
|
|
183
|
+
@Field var competitionUnit = "iconAndText"
|
|
184
|
+
|
|
185
|
+
@Field var ordering = "rank"
|
|
186
|
+
|
|
187
|
+
@Field var competitionExceptions: List<String> = emptyList()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
class MapLodLabelLayerRecord : MapLabelLayerRecord() {
|
|
191
|
+
@Field var radius = 20.0
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class MapLabelTextRecord : Record {
|
|
195
|
+
@Field @Required
|
|
196
|
+
var value = ""
|
|
197
|
+
|
|
198
|
+
@Field var styleIndex = 0
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class MapLabelBadgeRecord : Record {
|
|
202
|
+
@Field @Required
|
|
203
|
+
var id = ""
|
|
204
|
+
|
|
205
|
+
@Field @Required
|
|
206
|
+
var image = MapLabelImage()
|
|
207
|
+
|
|
208
|
+
@Field var offset: MapLabelVector? = null
|
|
209
|
+
|
|
210
|
+
@Field var zOrder = 0
|
|
211
|
+
|
|
212
|
+
@Field var visible = true
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
open class MapLabelRecord : Record {
|
|
216
|
+
@Field @Required
|
|
217
|
+
var id = ""
|
|
218
|
+
|
|
219
|
+
@Field @Required
|
|
220
|
+
var layerId = ""
|
|
221
|
+
|
|
222
|
+
@Field @Required
|
|
223
|
+
var styleId = ""
|
|
224
|
+
|
|
225
|
+
@Field @Required
|
|
226
|
+
var coordinate = MapLabelPoint()
|
|
227
|
+
|
|
228
|
+
@Field var text: List<MapLabelTextRecord> = emptyList()
|
|
229
|
+
|
|
230
|
+
@Field var rank = 0.0
|
|
231
|
+
|
|
232
|
+
@Field var clickable = false
|
|
233
|
+
|
|
234
|
+
@Field var visible = true
|
|
235
|
+
|
|
236
|
+
@Field var transform = "default"
|
|
237
|
+
|
|
238
|
+
@Field var pixelOffset: MapLabelVector? = null
|
|
239
|
+
|
|
240
|
+
@Field var orientation = 0.0
|
|
241
|
+
|
|
242
|
+
@Field var scale: MapLabelVector? = null
|
|
243
|
+
|
|
244
|
+
@Field var badges: List<MapLabelBadgeRecord> = emptyList()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
class MapLodLabelRecord : MapLabelRecord()
|
|
248
|
+
|
|
249
|
+
class MapPathLabelRecord : Record {
|
|
250
|
+
@Field @Required
|
|
251
|
+
var id = ""
|
|
252
|
+
|
|
253
|
+
@Field @Required
|
|
254
|
+
var layerId = ""
|
|
255
|
+
|
|
256
|
+
@Field @Required
|
|
257
|
+
var styleId = ""
|
|
258
|
+
|
|
259
|
+
@Field @Required
|
|
260
|
+
var points: List<MapLabelPoint> = emptyList()
|
|
261
|
+
|
|
262
|
+
@Field @Required
|
|
263
|
+
var text = ""
|
|
264
|
+
|
|
265
|
+
@Field var rank = 0.0
|
|
266
|
+
|
|
267
|
+
@Field var visible = true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class MapLabelAnimationKeyframeRecord : Record {
|
|
271
|
+
@Field @Required
|
|
272
|
+
var duration = 0.0
|
|
273
|
+
|
|
274
|
+
@Field var interpolation = "linear"
|
|
275
|
+
|
|
276
|
+
@Field var alpha: Double? = null
|
|
277
|
+
|
|
278
|
+
@Field var scale: MapLabelVector? = null
|
|
279
|
+
|
|
280
|
+
@Field var translation: MapLabelVector? = null
|
|
281
|
+
|
|
282
|
+
@Field var rotation: Double? = null
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
class MapLabelAnimationRecord : Record {
|
|
286
|
+
@Field @Required
|
|
287
|
+
var id = ""
|
|
288
|
+
|
|
289
|
+
@Field @Required
|
|
290
|
+
var labelIds: List<String> = emptyList()
|
|
291
|
+
|
|
292
|
+
@Field @Required
|
|
293
|
+
var effect = ""
|
|
294
|
+
|
|
295
|
+
@Field @Required
|
|
296
|
+
var keyframes: List<MapLabelAnimationKeyframeRecord> = emptyList()
|
|
297
|
+
|
|
298
|
+
@Field var repeatCount = 1
|
|
299
|
+
|
|
300
|
+
@Field var hideAtStop = false
|
|
301
|
+
|
|
302
|
+
@Field var removeAtStop = false
|
|
303
|
+
|
|
304
|
+
@Field var resetToInitialState = false
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
class MapLabelSceneRecord : Record {
|
|
308
|
+
@Field var styles: List<MapLabelStyleRecord> = emptyList()
|
|
309
|
+
|
|
310
|
+
@Field var pathTextStyles: List<MapPathTextStyleRecord> = emptyList()
|
|
311
|
+
|
|
312
|
+
@Field var layers: List<MapLabelLayerRecord> = emptyList()
|
|
313
|
+
|
|
314
|
+
@Field var lodLayers: List<MapLodLabelLayerRecord> = emptyList()
|
|
315
|
+
|
|
316
|
+
@Field var labels: List<MapLabelRecord> = emptyList()
|
|
317
|
+
|
|
318
|
+
@Field var lodLabels: List<MapLodLabelRecord> = emptyList()
|
|
319
|
+
|
|
320
|
+
@Field var pathLabels: List<MapPathLabelRecord> = emptyList()
|
|
321
|
+
|
|
322
|
+
@Field var animations: List<MapLabelAnimationRecord> = emptyList()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
class MapLabelCommandRecord : Record {
|
|
326
|
+
@Field @Required
|
|
327
|
+
var type = ""
|
|
328
|
+
|
|
329
|
+
@Field var id: String? = null
|
|
330
|
+
|
|
331
|
+
@Field var sourceId: String? = null
|
|
332
|
+
|
|
333
|
+
@Field var targetId: String? = null
|
|
334
|
+
|
|
335
|
+
@Field var badgeId: String? = null
|
|
336
|
+
|
|
337
|
+
@Field var style = false
|
|
338
|
+
|
|
339
|
+
@Field var visible: Boolean? = null
|
|
340
|
+
|
|
341
|
+
@Field var enabled: Boolean? = null
|
|
342
|
+
|
|
343
|
+
@Field var coordinate: MapLabelPoint? = null
|
|
344
|
+
|
|
345
|
+
@Field var points: List<MapLabelPoint> = emptyList()
|
|
346
|
+
|
|
347
|
+
@Field var duration: Double? = null
|
|
348
|
+
|
|
349
|
+
@Field var cornerRadius = 0.0
|
|
350
|
+
|
|
351
|
+
@Field var jumpThreshold = 0.0
|
|
352
|
+
|
|
353
|
+
@Field var rotate = false
|
|
354
|
+
|
|
355
|
+
@Field var autoMove = false
|
|
356
|
+
|
|
357
|
+
@Field var baseRotation = 0.0
|
|
358
|
+
|
|
359
|
+
@Field var rotation: Double? = null
|
|
360
|
+
|
|
361
|
+
@Field var scale: MapLabelVector? = null
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
internal class MapLabels(
|
|
365
|
+
private val map: KakaoMap,
|
|
366
|
+
private val imageLoader: MapImageLoader,
|
|
367
|
+
private val emitClick: (Map<String, Any>) -> Unit,
|
|
368
|
+
private val emitAnimationStopped: (Map<String, Any>) -> Unit,
|
|
369
|
+
private val resolvePolygon: (String) -> Polygon?,
|
|
370
|
+
private val density: Float,
|
|
371
|
+
) {
|
|
372
|
+
private data class ActiveAnimationRun(
|
|
373
|
+
val nativeId: String,
|
|
374
|
+
val epoch: Long,
|
|
375
|
+
val definition: MapLabelAnimationRecord,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
init {
|
|
379
|
+
if (!density.isFinite() || density <= 0) invalid("Display density must be positive.")
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private val manager = map.labelManager ?: invalid("The label manager is unavailable before map readiness.")
|
|
383
|
+
private val nativeNamespace = UUID.randomUUID().toString().replace("-", "")
|
|
384
|
+
private val trackingManager: TrackingManager =
|
|
385
|
+
map.trackingManager ?: invalid("The tracking manager is unavailable before map readiness.")
|
|
386
|
+
private val labels = mutableMapOf<String, Label>()
|
|
387
|
+
private val lodLabels = mutableMapOf<String, LodLabel>()
|
|
388
|
+
private val pathLabels = mutableMapOf<String, PolylineLabel>()
|
|
389
|
+
private val dynamicBadges = mutableMapOf<String, MutableMap<String, Badge>>()
|
|
390
|
+
private val ownedLabelLayers = mutableMapOf<String, LabelLayer>()
|
|
391
|
+
private val ownedLodLabelLayers = mutableMapOf<String, LodLabelLayer>()
|
|
392
|
+
private val publicLayerIdsByNative = mutableMapOf<String, String>()
|
|
393
|
+
private val nativeStyleIdsByDefinition = mutableMapOf<String, String>()
|
|
394
|
+
private var nextNativeObjectId = 0L
|
|
395
|
+
private val animationDefinitions = mutableMapOf<String, MapLabelAnimationRecord>()
|
|
396
|
+
private val activeAnimationRuns = mutableMapOf<String, ActiveAnimationRun>()
|
|
397
|
+
private var nextAnimationEpoch = 0L
|
|
398
|
+
private var generation = 0
|
|
399
|
+
private val imageRequests = mutableListOf<MapImageLoadToken>()
|
|
400
|
+
private var pendingApplyCompletion: ((Result<Unit>) -> Unit)? = null
|
|
401
|
+
private var isDisposed = false
|
|
402
|
+
private var isTracking = false
|
|
403
|
+
|
|
404
|
+
fun apply(
|
|
405
|
+
scene: MapLabelSceneRecord,
|
|
406
|
+
completion: (Result<Unit>) -> Unit,
|
|
407
|
+
) {
|
|
408
|
+
requireActive()
|
|
409
|
+
validateScene(scene, density)
|
|
410
|
+
cancelPendingApply("A newer label scene replaced this apply operation.")
|
|
411
|
+
pendingApplyCompletion = completion
|
|
412
|
+
val currentGeneration = ++generation
|
|
413
|
+
cancelImageRequests()
|
|
414
|
+
loadImages(scene.imageUris(), currentGeneration) { result ->
|
|
415
|
+
if (isDisposed || currentGeneration != generation) return@loadImages
|
|
416
|
+
result.fold(
|
|
417
|
+
onSuccess = { images -> finishApply(runCatching { rebuild(scene, images) }) },
|
|
418
|
+
onFailure = { finishApply(Result.failure(it)) },
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
fun command(command: MapLabelCommandRecord) {
|
|
424
|
+
requireActive()
|
|
425
|
+
val duration = (command.duration ?: 0.0).duration()
|
|
426
|
+
when (command.type) {
|
|
427
|
+
"show" -> {
|
|
428
|
+
show(command.requiredId(), command.visible.required(), command.autoMove, duration)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
"move" -> {
|
|
432
|
+
label(command.requiredId()).moveTo(command.coordinate.required().latLng(), duration)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
"movePath" -> {
|
|
436
|
+
command.duration.required()
|
|
437
|
+
if (command.points.size < 2) invalid("movePath requires at least two points.")
|
|
438
|
+
if (command.cornerRadius < 0 || command.jumpThreshold < 0) {
|
|
439
|
+
invalid("cornerRadius and jumpThreshold must be non-negative.")
|
|
440
|
+
}
|
|
441
|
+
val path =
|
|
442
|
+
PathOptions
|
|
443
|
+
.fromPath(command.points.map(MapLabelPoint::latLng))
|
|
444
|
+
.setDuration(duration)
|
|
445
|
+
.setCornerRadius(command.cornerRadius.finiteFloat("cornerRadius"))
|
|
446
|
+
.setJumpThreshold(command.jumpThreshold.finiteFloat("jumpThreshold"))
|
|
447
|
+
.setBaseRadian(Math.toRadians(command.baseRotation.finiteFloat("baseRotation").toDouble()).toFloat())
|
|
448
|
+
label(command.requiredId()).moveOnPath(path, command.rotate)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
"rotate" -> {
|
|
452
|
+
label(
|
|
453
|
+
command.requiredId(),
|
|
454
|
+
).rotateTo(
|
|
455
|
+
Math
|
|
456
|
+
.toRadians(
|
|
457
|
+
command.rotation
|
|
458
|
+
.required()
|
|
459
|
+
.finiteFloat("rotation")
|
|
460
|
+
.toDouble(),
|
|
461
|
+
).toFloat(),
|
|
462
|
+
duration,
|
|
463
|
+
)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
"scale" -> {
|
|
467
|
+
command.scale.required().also {
|
|
468
|
+
it.validateScale()
|
|
469
|
+
label(command.requiredId()).scaleTo(it.x.finiteFloat("scale.x"), it.y.finiteFloat("scale.y"), duration)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
"sharePosition" -> {
|
|
474
|
+
share(command, transform = false)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
"shareTransform" -> {
|
|
478
|
+
share(command, transform = true)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
"shareShapeTransform" -> {
|
|
482
|
+
val isEnabled = command.enabled.required()
|
|
483
|
+
val source = label(command.sourceId ?: invalid("sourceId is required."))
|
|
484
|
+
val targetId = command.targetId ?: invalid("targetId is required.")
|
|
485
|
+
val target = resolvePolygon(targetId) ?: notFound("polygon", targetId)
|
|
486
|
+
if (isEnabled) source.addShareTransform(target) else source.removeShareTransform(target)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
"setBadgeVisibility" -> {
|
|
490
|
+
setBadgeVisibility(command)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
"startAnimation" -> {
|
|
494
|
+
startAnimation(command.requiredId())
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
"stopAnimation" -> {
|
|
498
|
+
val id = command.requiredId()
|
|
499
|
+
if (id !in animationDefinitions) notFound("animation", id)
|
|
500
|
+
activeAnimationRuns[id]?.let { finishAnimationRun(id, it, finished = false, stopNative = true) }
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
"startTracking" -> {
|
|
504
|
+
trackingManager.startTracking(label(command.requiredId()))
|
|
505
|
+
trackingManager.setTrackingRotation(command.rotate)
|
|
506
|
+
isTracking = true
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
"stopTracking" -> {
|
|
510
|
+
trackingManager.stopTracking()
|
|
511
|
+
isTracking = false
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
else -> {
|
|
515
|
+
invalid("Unknown label command: ${command.type}")
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private fun startAnimation(id: String) {
|
|
521
|
+
var definition = animationDefinitions[id] ?: notFound("animation", id)
|
|
522
|
+
activeAnimationRuns[id]?.let { run ->
|
|
523
|
+
finishAnimationRun(id, run, finished = false, stopNative = true)
|
|
524
|
+
definition = animationDefinitions[id] ?: notFound("animation", id)
|
|
525
|
+
}
|
|
526
|
+
val targets = definition.labelIds.map(::label)
|
|
527
|
+
val epoch = ++nextAnimationEpoch
|
|
528
|
+
val nativeId = makeNativeId("animation")
|
|
529
|
+
val animator =
|
|
530
|
+
manager.addAnimator(definition.nativeAnimation(density, nativeId))
|
|
531
|
+
?: invalid("Could not create animation $id.")
|
|
532
|
+
animator.addLabels(*targets.toTypedArray())
|
|
533
|
+
val run = ActiveAnimationRun(nativeId, epoch, definition)
|
|
534
|
+
activeAnimationRuns[id] = run
|
|
535
|
+
animator.setOnAnimationStopListener {
|
|
536
|
+
val active = activeAnimationRuns[id]
|
|
537
|
+
if (!isDisposed && active?.nativeId == nativeId && active.epoch == epoch) {
|
|
538
|
+
finishAnimationRun(id, active, finished = true, stopNative = false)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
animator.start()
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private fun finishAnimationRun(
|
|
545
|
+
id: String,
|
|
546
|
+
run: ActiveAnimationRun,
|
|
547
|
+
finished: Boolean,
|
|
548
|
+
stopNative: Boolean,
|
|
549
|
+
) {
|
|
550
|
+
val active = activeAnimationRuns[id]
|
|
551
|
+
if (active?.nativeId != run.nativeId || active.epoch != run.epoch) return
|
|
552
|
+
activeAnimationRuns.remove(id)
|
|
553
|
+
if (stopNative) manager.getAnimator(run.nativeId)?.stop()
|
|
554
|
+
manager.removeAnimator(run.nativeId)
|
|
555
|
+
if (run.definition.removeAtStop) removeAnimationTargets(run.definition.labelIds)
|
|
556
|
+
emitAnimationStopped(mapOf("id" to id, "finished" to finished))
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private fun clearAnimationRuns() {
|
|
560
|
+
val runs = activeAnimationRuns.values.toList()
|
|
561
|
+
activeAnimationRuns.clear()
|
|
562
|
+
runs.forEach { run ->
|
|
563
|
+
manager.getAnimator(run.nativeId)?.stop()
|
|
564
|
+
manager.removeAnimator(run.nativeId)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private fun removeAnimationTargets(ids: List<String>) {
|
|
569
|
+
val removedIds = ids.toMutableSet()
|
|
570
|
+
if (removedIds.isEmpty()) return
|
|
571
|
+
val canceledRuns = mutableListOf<Pair<String, ActiveAnimationRun>>()
|
|
572
|
+
while (true) {
|
|
573
|
+
val invalidDefinitionIds =
|
|
574
|
+
animationDefinitions
|
|
575
|
+
.filterValues { definition -> definition.labelIds.any(removedIds::contains) }
|
|
576
|
+
.keys
|
|
577
|
+
.toList()
|
|
578
|
+
if (invalidDefinitionIds.isEmpty()) break
|
|
579
|
+
var didAddRemovedId = false
|
|
580
|
+
invalidDefinitionIds.forEach { invalidId ->
|
|
581
|
+
animationDefinitions.remove(invalidId)
|
|
582
|
+
activeAnimationRuns.remove(invalidId)?.let { run ->
|
|
583
|
+
canceledRuns += invalidId to run
|
|
584
|
+
if (run.definition.removeAtStop) {
|
|
585
|
+
run.definition.labelIds.forEach { targetId ->
|
|
586
|
+
if (removedIds.add(targetId)) didAddRemovedId = true
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (!didAddRemovedId) break
|
|
592
|
+
}
|
|
593
|
+
canceledRuns.forEach { (canceledId, run) ->
|
|
594
|
+
manager.getAnimator(run.nativeId)?.stop()
|
|
595
|
+
manager.removeAnimator(run.nativeId)
|
|
596
|
+
emitAnimationStopped(mapOf("id" to canceledId, "finished" to false))
|
|
597
|
+
}
|
|
598
|
+
removedIds.forEach {
|
|
599
|
+
labels.remove(it)
|
|
600
|
+
dynamicBadges.remove(it)
|
|
601
|
+
}
|
|
602
|
+
trackingManager.stopTracking()
|
|
603
|
+
isTracking = false
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fun capabilities() =
|
|
607
|
+
mapOf(
|
|
608
|
+
"pathText" to "polylineLabel",
|
|
609
|
+
"pathTextRank" to false,
|
|
610
|
+
"pathTextStyleAddition" to true,
|
|
611
|
+
"pathTextStyleDefinitionUpdate" to true,
|
|
612
|
+
"pathTextStyleRegistrationRemoval" to true,
|
|
613
|
+
"lodPositionMutation" to false,
|
|
614
|
+
"lodAutoMove" to false,
|
|
615
|
+
"autoMoveDuration" to true,
|
|
616
|
+
"labelScaleCommand" to true,
|
|
617
|
+
"sharedShapeTransform" to true,
|
|
618
|
+
"sharedShapeTarget" to "polygon",
|
|
619
|
+
"competitionExceptions" to true,
|
|
620
|
+
"supportedTransforms" to
|
|
621
|
+
listOf("none", "absoluteRotation", "default", "absoluteRotationKeepUpright", "absoluteRotationDecal", "decal"),
|
|
622
|
+
"keyframeRepeatCount" to false,
|
|
623
|
+
"trackingStateSource" to "managed",
|
|
624
|
+
"remoteImages" to true,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
fun state(): Map<String, Any> {
|
|
628
|
+
requireActive()
|
|
629
|
+
val liveLabels =
|
|
630
|
+
labels.mapNotNull { (id, cached) ->
|
|
631
|
+
manager.getLayer(cached.layerId)?.getLabel(id)?.let {
|
|
632
|
+
mapOf(
|
|
633
|
+
"id" to id,
|
|
634
|
+
"kind" to "label",
|
|
635
|
+
"visible" to it.isShow,
|
|
636
|
+
"coordinate" to mapOf("latitude" to it.position.latitude, "longitude" to it.position.longitude),
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
val liveLodLabels =
|
|
641
|
+
lodLabels.mapNotNull { (id, cached) ->
|
|
642
|
+
manager.getLodLayer(cached.layerId)?.getLabel(id)?.let {
|
|
643
|
+
mapOf(
|
|
644
|
+
"id" to id,
|
|
645
|
+
"kind" to "lodLabel",
|
|
646
|
+
"visible" to it.isShow,
|
|
647
|
+
"coordinate" to mapOf("latitude" to it.position.latitude, "longitude" to it.position.longitude),
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
val livePathLabels =
|
|
652
|
+
pathLabels.mapNotNull { (id, cached) ->
|
|
653
|
+
manager.getLayer(cached.layerId)?.getPolylineLabel(id)?.let {
|
|
654
|
+
mapOf("id" to id, "kind" to "pathLabel", "visible" to it.isShow)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
labels.keys.retainAll(liveLabels.mapTo(mutableSetOf()) { it["id"].toString() })
|
|
658
|
+
lodLabels.keys.retainAll(liveLodLabels.mapTo(mutableSetOf()) { it["id"].toString() })
|
|
659
|
+
pathLabels.keys.retainAll(livePathLabels.mapTo(mutableSetOf()) { it["id"].toString() })
|
|
660
|
+
dynamicBadges.keys.retainAll(labels.keys + lodLabels.keys)
|
|
661
|
+
return mapOf(
|
|
662
|
+
"labels" to (liveLabels + liveLodLabels + livePathLabels).sortedBy { it["id"].toString() },
|
|
663
|
+
"animationIds" to animationDefinitions.keys.sorted(),
|
|
664
|
+
"tracking" to mapOf("active" to isTracking, "source" to "managed"),
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
fun handleLabelClick(label: Label) {
|
|
669
|
+
if (isDisposed || labels[label.labelId] !== label) return
|
|
670
|
+
if (manager.getLayer(label.layerId)?.getLabel(label.labelId) !== label) return
|
|
671
|
+
val publicLayerId = publicLayerIdsByNative[label.layerId] ?: return
|
|
672
|
+
emitClick(label.clickPayload("label", publicLayerId))
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
fun handleLodLabelClick(label: LodLabel) {
|
|
676
|
+
if (isDisposed || lodLabels[label.labelId] !== label) return
|
|
677
|
+
if (manager.getLodLayer(label.layerId)?.getLabel(label.labelId) !== label) return
|
|
678
|
+
val publicLayerId = publicLayerIdsByNative[label.layerId] ?: return
|
|
679
|
+
emitClick(label.clickPayload("lodLabel", publicLayerId))
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
fun dispose() {
|
|
683
|
+
if (isDisposed) return
|
|
684
|
+
isDisposed = true
|
|
685
|
+
++generation
|
|
686
|
+
clearAnimationRuns()
|
|
687
|
+
cancelImageRequests()
|
|
688
|
+
cancelPendingApply("The map view was destroyed.")
|
|
689
|
+
trackingManager.stopTracking()
|
|
690
|
+
clearOwnedLayers()
|
|
691
|
+
labels.clear()
|
|
692
|
+
lodLabels.clear()
|
|
693
|
+
pathLabels.clear()
|
|
694
|
+
dynamicBadges.clear()
|
|
695
|
+
animationDefinitions.clear()
|
|
696
|
+
nativeStyleIdsByDefinition.clear()
|
|
697
|
+
isTracking = false
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
private fun rebuild(
|
|
701
|
+
scene: MapLabelSceneRecord,
|
|
702
|
+
images: Map<String, Bitmap>,
|
|
703
|
+
) {
|
|
704
|
+
val nativeLayerIds =
|
|
705
|
+
(scene.layers.map { it.id to makeNativeId("layer") } + scene.lodLayers.map { it.id to makeNativeId("lod_layer") }).toMap()
|
|
706
|
+
val nativeStyles =
|
|
707
|
+
scene.styles.associate { style ->
|
|
708
|
+
val nativeId = nativeStyleId("poi_style", style.id, style.fingerprint())
|
|
709
|
+
style.id to style.native(images, nativeId, density)
|
|
710
|
+
}
|
|
711
|
+
val pathStyles =
|
|
712
|
+
scene.pathTextStyles.associate { style ->
|
|
713
|
+
val nativeId = nativeStyleId("path_style", style.id, style.fingerprint())
|
|
714
|
+
style.id to style.native(nativeId, density)
|
|
715
|
+
}
|
|
716
|
+
val layerOptions =
|
|
717
|
+
scene.layers.associate { layer ->
|
|
718
|
+
layer.id to layer.options(nativeLayerIds.getValue(layer.id), nativeLayerIds)
|
|
719
|
+
}
|
|
720
|
+
val lodLayerOptions =
|
|
721
|
+
scene.lodLayers.associate { layer ->
|
|
722
|
+
layer.id to layer.options(nativeLayerIds.getValue(layer.id), nativeLayerIds, density)
|
|
723
|
+
}
|
|
724
|
+
clearAnimationRuns()
|
|
725
|
+
trackingManager.stopTracking()
|
|
726
|
+
clearOwnedLayers()
|
|
727
|
+
labels.clear()
|
|
728
|
+
lodLabels.clear()
|
|
729
|
+
pathLabels.clear()
|
|
730
|
+
dynamicBadges.clear()
|
|
731
|
+
animationDefinitions.clear()
|
|
732
|
+
isTracking = false
|
|
733
|
+
|
|
734
|
+
val styles =
|
|
735
|
+
nativeStyles.mapValues { manager.addLabelStyles(it.value) }
|
|
736
|
+
val layers =
|
|
737
|
+
layerOptions.mapValues { (id, options) ->
|
|
738
|
+
val layer = manager.addLayer(options) ?: invalid("Could not create label layer $id.")
|
|
739
|
+
ownedLabelLayers[id] = layer
|
|
740
|
+
publicLayerIdsByNative[layer.layerId] = id
|
|
741
|
+
layer
|
|
742
|
+
}
|
|
743
|
+
val lodLayers =
|
|
744
|
+
lodLayerOptions.mapValues { (id, options) ->
|
|
745
|
+
val layer = manager.addLodLayer(options) ?: invalid("Could not create LOD label layer $id.")
|
|
746
|
+
ownedLodLabelLayers[id] = layer
|
|
747
|
+
publicLayerIdsByNative[layer.layerId] = id
|
|
748
|
+
layer
|
|
749
|
+
}
|
|
750
|
+
scene.labels.forEach { item ->
|
|
751
|
+
val layer = layers[item.layerId] ?: notFound("layer", item.layerId)
|
|
752
|
+
val native =
|
|
753
|
+
layer.addLabel(item.options(styles[item.styleId] ?: notFound("style", item.styleId)))
|
|
754
|
+
?: invalid("Could not create label ${item.id}.")
|
|
755
|
+
native.changePixelOffset(
|
|
756
|
+
item.pixelOffset?.x?.dp("pixelOffset.x", density) ?: 0f,
|
|
757
|
+
item.pixelOffset?.y?.dp("pixelOffset.y", density) ?: 0f,
|
|
758
|
+
false,
|
|
759
|
+
)
|
|
760
|
+
native.rotateTo(Math.toRadians(item.orientation).toFloat())
|
|
761
|
+
item.scale?.let { native.scaleTo(it.x.finiteFloat("scale.x"), it.y.finiteFloat("scale.y")) }
|
|
762
|
+
item.badges.forEach { badge ->
|
|
763
|
+
native.addBadge(badge.native(images, density)).firstOrNull()?.let {
|
|
764
|
+
dynamicBadges.getOrPut(item.id) { mutableMapOf() }[badge.id] = it
|
|
765
|
+
if (!badge.visible) it.hide()
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
labels[item.id] = native
|
|
769
|
+
}
|
|
770
|
+
scene.lodLabels.forEach { item ->
|
|
771
|
+
val layer = lodLayers[item.layerId] ?: notFound("LOD layer", item.layerId)
|
|
772
|
+
val native =
|
|
773
|
+
layer.addLodLabel(item.options(styles[item.styleId] ?: notFound("style", item.styleId)))
|
|
774
|
+
?: invalid("Could not create LOD label ${item.id}.")
|
|
775
|
+
item.badges.forEach { badge ->
|
|
776
|
+
native.addBadge(badge.native(images, density)).firstOrNull()?.let {
|
|
777
|
+
dynamicBadges.getOrPut(item.id) { mutableMapOf() }[badge.id] = it
|
|
778
|
+
if (!badge.visible) it.hide()
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
lodLabels[item.id] = native
|
|
782
|
+
}
|
|
783
|
+
scene.pathLabels.forEach { item ->
|
|
784
|
+
val layer = layers[item.layerId] ?: notFound("layer", item.layerId)
|
|
785
|
+
val style = pathStyles[item.styleId] ?: notFound("path text style", item.styleId)
|
|
786
|
+
val native =
|
|
787
|
+
layer.addPolylineLabel(
|
|
788
|
+
PolylineLabelOptions.from(item.id, item.text, item.points.map(MapLabelPoint::latLng)).setStyles(style).setVisible(item.visible),
|
|
789
|
+
) ?: invalid("Could not create path label ${item.id}.")
|
|
790
|
+
pathLabels[item.id] = native
|
|
791
|
+
}
|
|
792
|
+
scene.animations.forEach { animation -> addAnimation(animation) }
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private fun addAnimation(animation: MapLabelAnimationRecord) {
|
|
796
|
+
animationDefinitions[animation.id] = animation
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private fun clearOwnedLayers() {
|
|
800
|
+
ownedLabelLayers.values.toList().forEach(manager::remove)
|
|
801
|
+
ownedLodLabelLayers.values.toList().forEach(manager::remove)
|
|
802
|
+
ownedLabelLayers.clear()
|
|
803
|
+
ownedLodLabelLayers.clear()
|
|
804
|
+
publicLayerIdsByNative.clear()
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private fun makeNativeId(kind: String): String = "__rnk_${nativeNamespace}_${kind}_${++nextNativeObjectId}"
|
|
808
|
+
|
|
809
|
+
private fun nativeStyleId(
|
|
810
|
+
kind: String,
|
|
811
|
+
publicId: String,
|
|
812
|
+
fingerprint: String,
|
|
813
|
+
) = nativeStyleIdsByDefinition.getOrPut("$kind\u0000$publicId\u0000$fingerprint") { makeNativeId(kind) }
|
|
814
|
+
|
|
815
|
+
private fun share(
|
|
816
|
+
command: MapLabelCommandRecord,
|
|
817
|
+
transform: Boolean,
|
|
818
|
+
) {
|
|
819
|
+
val isEnabled = command.enabled.required()
|
|
820
|
+
val source = label(command.sourceId ?: invalid("sourceId is required."))
|
|
821
|
+
val target = label(command.targetId ?: invalid("targetId is required."))
|
|
822
|
+
if (transform) {
|
|
823
|
+
if (isEnabled) source.addShareTransform(target) else source.removeShareTransform(target)
|
|
824
|
+
} else {
|
|
825
|
+
if (isEnabled) source.addSharePosition(target) else source.removeSharePosition(target)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
private fun setBadgeVisibility(command: MapLabelCommandRecord) {
|
|
830
|
+
val isVisible = command.visible.required()
|
|
831
|
+
val id = command.requiredId()
|
|
832
|
+
val badgeId = command.badgeId ?: invalid("badgeId is required.")
|
|
833
|
+
if (command.style) {
|
|
834
|
+
labels[id]?.let {
|
|
835
|
+
if (isVisible) it.showStyleBadge(badgeId) else it.hideStyleBadge(badgeId)
|
|
836
|
+
return
|
|
837
|
+
}
|
|
838
|
+
lodLabels[id]?.let {
|
|
839
|
+
if (isVisible) it.showStyleBadge(badgeId) else it.hideStyleBadge(badgeId)
|
|
840
|
+
return
|
|
841
|
+
}
|
|
842
|
+
notFound("label", id)
|
|
843
|
+
return
|
|
844
|
+
}
|
|
845
|
+
val badge = dynamicBadges[id]?.get(badgeId) ?: notFound("badge", badgeId)
|
|
846
|
+
if (isVisible) badge.show() else badge.hide()
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
private fun show(
|
|
850
|
+
id: String,
|
|
851
|
+
visible: Boolean,
|
|
852
|
+
autoMove: Boolean,
|
|
853
|
+
duration: Int,
|
|
854
|
+
) {
|
|
855
|
+
labels[id]?.let {
|
|
856
|
+
if (visible) {
|
|
857
|
+
if (autoMove) it.show(true, duration) else it.show()
|
|
858
|
+
} else {
|
|
859
|
+
it.hide()
|
|
860
|
+
}
|
|
861
|
+
return
|
|
862
|
+
}
|
|
863
|
+
lodLabels[id]?.let {
|
|
864
|
+
if (autoMove) throw CodedException(LABEL_UNSUPPORTED, "Android LOD labels do not expose auto-move.", null)
|
|
865
|
+
if (visible) it.show() else it.hide()
|
|
866
|
+
return
|
|
867
|
+
}
|
|
868
|
+
pathLabels[id]?.let {
|
|
869
|
+
if (autoMove) throw CodedException(LABEL_UNSUPPORTED, "Path labels do not expose auto-move.", null)
|
|
870
|
+
if (visible) it.show() else it.hide()
|
|
871
|
+
return
|
|
872
|
+
}
|
|
873
|
+
notFound("label", id)
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private fun label(id: String) =
|
|
877
|
+
labels[id] ?: if (lodLabels.containsKey(id)) {
|
|
878
|
+
throw CodedException(LABEL_UNSUPPORTED, "LOD label $id cannot be moved, transformed, animated, or tracked.", null)
|
|
879
|
+
} else {
|
|
880
|
+
notFound("label", id)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private fun requireActive() {
|
|
884
|
+
if (isDisposed) throw CodedException("ERR_KAKAO_MAP_VIEW_DESTROYED", "The map view was destroyed.", null)
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private fun loadImages(
|
|
888
|
+
uris: Set<String>,
|
|
889
|
+
currentGeneration: Int,
|
|
890
|
+
completion: (Result<Map<String, Bitmap>>) -> Unit,
|
|
891
|
+
) {
|
|
892
|
+
if (uris.isEmpty()) {
|
|
893
|
+
completion(Result.success(emptyMap()))
|
|
894
|
+
return
|
|
895
|
+
}
|
|
896
|
+
val images = mutableMapOf<String, Bitmap>()
|
|
897
|
+
var remaining = uris.size
|
|
898
|
+
var didFinish = false
|
|
899
|
+
uris.forEach { uri ->
|
|
900
|
+
imageRequests +=
|
|
901
|
+
imageLoader.load(uri) { result ->
|
|
902
|
+
if (isDisposed || currentGeneration != generation || didFinish) return@load
|
|
903
|
+
result.fold(
|
|
904
|
+
onSuccess = { image ->
|
|
905
|
+
images[uri] = image
|
|
906
|
+
remaining -= 1
|
|
907
|
+
if (remaining == 0) {
|
|
908
|
+
didFinish = true
|
|
909
|
+
imageRequests.clear()
|
|
910
|
+
completion(Result.success(images))
|
|
911
|
+
}
|
|
912
|
+
},
|
|
913
|
+
onFailure = { error ->
|
|
914
|
+
didFinish = true
|
|
915
|
+
cancelImageRequests()
|
|
916
|
+
completion(Result.failure(error))
|
|
917
|
+
},
|
|
918
|
+
)
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
private fun cancelImageRequests() {
|
|
924
|
+
imageRequests.forEach(MapImageLoadToken::cancel)
|
|
925
|
+
imageRequests.clear()
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
private fun finishApply(result: Result<Unit>) {
|
|
929
|
+
val completion = pendingApplyCompletion ?: return
|
|
930
|
+
pendingApplyCompletion = null
|
|
931
|
+
completion(result)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
private fun cancelPendingApply(message: String) {
|
|
935
|
+
finishApply(Result.failure(CodedException(LABEL_APPLY_CANCELLED, message, null)))
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
private fun validateScene(
|
|
940
|
+
scene: MapLabelSceneRecord,
|
|
941
|
+
density: Float,
|
|
942
|
+
) {
|
|
943
|
+
unique("style", scene.styles.map { it.id })
|
|
944
|
+
unique("path text style", scene.pathTextStyles.map { it.id })
|
|
945
|
+
unique("layer", scene.layers.map { it.id } + scene.lodLayers.map { it.id })
|
|
946
|
+
unique("label", scene.labels.map { it.id } + scene.lodLabels.map { it.id } + scene.pathLabels.map { it.id })
|
|
947
|
+
unique("animation", scene.animations.map { it.id })
|
|
948
|
+
val styleById = scene.styles.associateBy { it.id }
|
|
949
|
+
val layerIds = scene.layers.mapTo(mutableSetOf()) { it.id }
|
|
950
|
+
val lodLayerIds = scene.lodLayers.mapTo(mutableSetOf()) { it.id }
|
|
951
|
+
val regularLabelIds = scene.labels.mapTo(mutableSetOf()) { it.id }
|
|
952
|
+
(
|
|
953
|
+
scene.styles.map { it.id } + scene.pathTextStyles.map { it.id } + layerIds + lodLayerIds +
|
|
954
|
+
regularLabelIds + scene.lodLabels.map { it.id } + scene.pathLabels.map { it.id } + scene.animations.map { it.id }
|
|
955
|
+
).forEach { if (it.isBlank()) invalid("IDs must not be empty.") }
|
|
956
|
+
scene.styles.forEach { style ->
|
|
957
|
+
if (style.levels.isEmpty()) invalid("Style ${style.id} must contain at least one level.")
|
|
958
|
+
unique("style level", style.levels.map { it.level.toString() })
|
|
959
|
+
style.levels.forEach { level ->
|
|
960
|
+
if (level.padding < 0) invalid("Style padding must be non-negative.")
|
|
961
|
+
unique("style badge", level.badges.map { it.id })
|
|
962
|
+
level.image?.validate()
|
|
963
|
+
level.badges.forEach { it.image.validate() }
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
scene.pathTextStyles.forEach { style ->
|
|
967
|
+
if (style.levels.isEmpty()) invalid("Path style ${style.id} must contain at least one level.")
|
|
968
|
+
unique("path style level", style.levels.map { it.level.toString() })
|
|
969
|
+
}
|
|
970
|
+
scene.layers.forEach { layer ->
|
|
971
|
+
layer.competitionExceptions.forEach { if (it !in layerIds && it !in lodLayerIds) notFound("competition layer", it) }
|
|
972
|
+
}
|
|
973
|
+
scene.lodLayers.forEach { layer ->
|
|
974
|
+
if (layer.radius < 0) invalid("LOD radius must be non-negative.")
|
|
975
|
+
layer.competitionExceptions.forEach { if (it !in layerIds && it !in lodLayerIds) notFound("competition layer", it) }
|
|
976
|
+
}
|
|
977
|
+
(scene.labels + scene.lodLabels).forEach { item ->
|
|
978
|
+
item.coordinate.validate()
|
|
979
|
+
val style = styleById[item.styleId] ?: notFound("style", item.styleId)
|
|
980
|
+
val expectedLayerIds = if (item is MapLodLabelRecord) lodLayerIds else layerIds
|
|
981
|
+
if (item.layerId !in expectedLayerIds) notFound("layer", item.layerId)
|
|
982
|
+
item.text.forEach { text ->
|
|
983
|
+
if (text.styleIndex < 0 || style.levels.any { text.styleIndex >= it.textLines.size }) {
|
|
984
|
+
invalid("Label ${item.id} has a text style index that is unavailable at one or more levels.")
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
item.rank.integerLong("rank")
|
|
988
|
+
transform(item.transform)
|
|
989
|
+
item.pixelOffset?.validateVector("pixelOffset")
|
|
990
|
+
item.orientation.finiteFloat("orientation")
|
|
991
|
+
item.scale?.validateScale()
|
|
992
|
+
unique("label badge", item.badges.map { it.id })
|
|
993
|
+
item.badges.forEach {
|
|
994
|
+
it.image.validate(density)
|
|
995
|
+
it.offset?.validateVector("badge.offset")
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
scene.pathLabels.forEach { item ->
|
|
999
|
+
if (item.points.size < 2) invalid("Path label ${item.id} requires at least two points.")
|
|
1000
|
+
item.points.forEach(MapLabelPoint::validate)
|
|
1001
|
+
if (item.layerId !in layerIds) notFound("layer", item.layerId)
|
|
1002
|
+
if (scene.pathTextStyles.none { it.id == item.styleId }) notFound("path text style", item.styleId)
|
|
1003
|
+
if (item.rank != 0.0) {
|
|
1004
|
+
throw CodedException(LABEL_UNSUPPORTED, "Android Kakao Maps SDK cannot set a polyline label rank.", null)
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
scene.animations.forEach { animation ->
|
|
1008
|
+
if (animation.labelIds.isEmpty() || animation.keyframes.isEmpty()) invalid("Animation ${animation.id} cannot be empty.")
|
|
1009
|
+
unique("animation label", animation.labelIds)
|
|
1010
|
+
animation.labelIds.forEach { if (it !in regularLabelIds) notFound("animatable label", it) }
|
|
1011
|
+
if (animation.repeatCount < 0) invalid("Animation repeatCount must be non-negative.")
|
|
1012
|
+
if (animation.effect != "drop" && animation.repeatCount != 1) {
|
|
1013
|
+
throw CodedException(LABEL_UNSUPPORTED, "Keyframe label animations do not expose repeatCount on Android.", null)
|
|
1014
|
+
}
|
|
1015
|
+
if (animation.effect !in setOf("drop", "alpha", "scale", "scaleAlpha", "transform")) {
|
|
1016
|
+
invalid("Unknown label animation effect: ${animation.effect}")
|
|
1017
|
+
}
|
|
1018
|
+
if (animation.effect == "drop" && animation.keyframes.size != 1) invalid("Drop animation requires exactly one keyframe.")
|
|
1019
|
+
animation.keyframes.forEach { frame ->
|
|
1020
|
+
frame.duration.duration()
|
|
1021
|
+
interpolation(frame.interpolation)
|
|
1022
|
+
frame.alpha?.unitFloat("alpha")
|
|
1023
|
+
frame.scale?.validateScale()
|
|
1024
|
+
frame.translation?.validateVector("translation")
|
|
1025
|
+
frame.rotation?.finiteFloat("rotation")
|
|
1026
|
+
if (animation.effect in setOf("scale", "scaleAlpha") && frame.scale == null) {
|
|
1027
|
+
invalid("${animation.effect} animation requires scale in every keyframe.")
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
private fun MapLabelImage.validate(density: Float = 1f) {
|
|
1034
|
+
if (uri.isBlank()) invalid("Image URI must not be empty.")
|
|
1035
|
+
width?.positiveSize("image.width", density)
|
|
1036
|
+
height?.positiveSize("image.height", density)
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
private fun MapLabelVector.validateScale() {
|
|
1040
|
+
if (x.finiteFloat("scale.x") < 0 || y.finiteFloat("scale.y") < 0) invalid("Scale must be non-negative.")
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
private fun MapLabelVector.validateVector(name: String) {
|
|
1044
|
+
x.finiteFloat("$name.x")
|
|
1045
|
+
y.finiteFloat("$name.y")
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
private fun MapLabelSceneRecord.imageUris() =
|
|
1049
|
+
buildSet {
|
|
1050
|
+
styles.flatMap { it.levels }.forEach { level ->
|
|
1051
|
+
level.image?.let { add(it.uri) }
|
|
1052
|
+
level.badges.forEach { add(it.image.uri) }
|
|
1053
|
+
}
|
|
1054
|
+
(labels + lodLabels).flatMap { it.badges }.forEach { add(it.image.uri) }
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private fun MapLabelStyleRecord.fingerprint() =
|
|
1058
|
+
levels.sortedBy { it.level }.joinToString(";") { level ->
|
|
1059
|
+
listOf(
|
|
1060
|
+
level.level.toString(),
|
|
1061
|
+
level.image.fingerprint(),
|
|
1062
|
+
level.anchor.fingerprint(),
|
|
1063
|
+
level.padding.toBits().toString(),
|
|
1064
|
+
level.iconTransition.fingerprint(),
|
|
1065
|
+
level.textTransition.fingerprint(),
|
|
1066
|
+
level.textLayout.fingerprint(),
|
|
1067
|
+
level.textLines.joinToString(",", transform = MapLabelTextStyleRecord::fingerprint),
|
|
1068
|
+
level.badges.joinToString(",", transform = MapLabelBadgeStyleRecord::fingerprint),
|
|
1069
|
+
).joinToString("|")
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
private fun MapPathTextStyleRecord.fingerprint() = levels.sortedBy { it.level }.joinToString(";") { "${it.level}|${it.text.fingerprint()}" }
|
|
1073
|
+
|
|
1074
|
+
private fun MapLabelTextStyleRecord.fingerprint() =
|
|
1075
|
+
listOf(
|
|
1076
|
+
fontFamily.fingerprint(),
|
|
1077
|
+
fontSize.toBits().toString(),
|
|
1078
|
+
color.fingerprint(),
|
|
1079
|
+
strokeColor.fingerprint(),
|
|
1080
|
+
strokeWidth.toBits().toString(),
|
|
1081
|
+
characterSpacing.toBits().toString(),
|
|
1082
|
+
lineSpacing.toBits().toString(),
|
|
1083
|
+
aspectRatio.toBits().toString(),
|
|
1084
|
+
).joinToString("|")
|
|
1085
|
+
|
|
1086
|
+
private fun MapLabelBadgeStyleRecord.fingerprint() =
|
|
1087
|
+
listOf(id.fingerprint(), image.fingerprint(), offset.fingerprint(), zOrder.toString()).joinToString("|")
|
|
1088
|
+
|
|
1089
|
+
private fun MapLabelImage?.fingerprint() =
|
|
1090
|
+
this?.let { listOf(uri.fingerprint(), width?.toBits(), height?.toBits()).joinToString("|") } ?: "-"
|
|
1091
|
+
|
|
1092
|
+
private fun MapLabelVector?.fingerprint() = this?.let { "${x.toBits()}|${y.toBits()}" } ?: "-"
|
|
1093
|
+
|
|
1094
|
+
private fun MapLabelTransitionRecord?.fingerprint() =
|
|
1095
|
+
this?.let { "${entrance.fingerprint()}|${exit.fingerprint()}|$enableEntrance|$enableExit" } ?: "-"
|
|
1096
|
+
|
|
1097
|
+
private fun String?.fingerprint() = this?.let { "${it.length}:$it" } ?: "-"
|
|
1098
|
+
|
|
1099
|
+
private fun MapLabelStyleRecord.native(
|
|
1100
|
+
images: Map<String, Bitmap>,
|
|
1101
|
+
nativeId: String,
|
|
1102
|
+
density: Float,
|
|
1103
|
+
) = LabelStyles.from(
|
|
1104
|
+
nativeId,
|
|
1105
|
+
levels.sortedBy { it.level }.map { level ->
|
|
1106
|
+
val textStyles = level.textLines.map { it.native(density) }.toTypedArray()
|
|
1107
|
+
val image = level.image?.bitmap(images, density)
|
|
1108
|
+
val style =
|
|
1109
|
+
when {
|
|
1110
|
+
image != null -> LabelStyle.from(image).setTextStyles(*textStyles)
|
|
1111
|
+
textStyles.isNotEmpty() -> LabelStyle.from(*textStyles)
|
|
1112
|
+
else -> invalid("Style $id level ${level.level} needs an image or text style.")
|
|
1113
|
+
}
|
|
1114
|
+
style
|
|
1115
|
+
.setZoomLevel(level.level)
|
|
1116
|
+
.setPadding(level.padding.dp("padding", density))
|
|
1117
|
+
.setApplyDpScale(false)
|
|
1118
|
+
level.anchor?.let { style.setAnchorPoint(it.x.finiteFloat("anchor.x"), it.y.finiteFloat("anchor.y")) }
|
|
1119
|
+
level.iconTransition?.let { style.setIconTransition(it.native()) }
|
|
1120
|
+
level.textTransition?.let { style.setTextTransition(it.native()) }
|
|
1121
|
+
style.setTextGravity(textGravity(level.textLayout))
|
|
1122
|
+
if (level.badges.isNotEmpty()) style.setBadges(*level.badges.map { it.native(images, density) }.toTypedArray())
|
|
1123
|
+
style
|
|
1124
|
+
},
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
private fun MapPathTextStyleRecord.native(
|
|
1128
|
+
nativeId: String,
|
|
1129
|
+
density: Float,
|
|
1130
|
+
) = PolylineLabelStyles.from(
|
|
1131
|
+
nativeId,
|
|
1132
|
+
levels.sortedBy { it.level }.map { level ->
|
|
1133
|
+
val text = level.text.native(density)
|
|
1134
|
+
PolylineLabelStyle
|
|
1135
|
+
.from(text.size, text.color, text.stroke, text.strokeColor)
|
|
1136
|
+
.setZoomLevel(level.level)
|
|
1137
|
+
.setApplyDpScale(false)
|
|
1138
|
+
},
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
private fun MapLabelTextStyleRecord.native(density: Float): LabelTextStyle {
|
|
1142
|
+
val nativeFontSize = fontSize.positiveDpInt("fontSize", density)
|
|
1143
|
+
val nativeLineSpace = lineSpacing.finiteFloat("lineSpacing")
|
|
1144
|
+
if (nativeLineSpace < 1) invalid("lineSpacing must be at least 1.")
|
|
1145
|
+
val nativeCharacterSpace = characterSpacing.dpInt("characterSpacing", density)
|
|
1146
|
+
if (nativeCharacterSpace < MIN_CHARACTER_SPACE_PX) {
|
|
1147
|
+
invalid("characterSpacing must resolve to at least $MIN_CHARACTER_SPACE_PX native pixels on Android.")
|
|
1148
|
+
}
|
|
1149
|
+
val result =
|
|
1150
|
+
LabelTextStyle.from(
|
|
1151
|
+
nativeFontSize,
|
|
1152
|
+
parseColor(color),
|
|
1153
|
+
strokeWidth.nonnegativeDpInt("strokeWidth", density),
|
|
1154
|
+
parseColor(strokeColor ?: "#00000000"),
|
|
1155
|
+
)
|
|
1156
|
+
fontFamily?.let(result::setFont)
|
|
1157
|
+
result.setCharacterSpace(nativeCharacterSpace)
|
|
1158
|
+
result.setLineSpace(nativeLineSpace)
|
|
1159
|
+
val ratio = aspectRatio.finiteFloat("aspectRatio")
|
|
1160
|
+
if (ratio <= 0) invalid("aspectRatio must be positive.")
|
|
1161
|
+
result.setAspectRatio(ratio)
|
|
1162
|
+
return result
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
private fun MapLabelTransitionRecord.native() =
|
|
1166
|
+
LabelTransition.from(transition(entrance), transition(exit)).enableTransitionWhenChange(enableEntrance, enableExit)
|
|
1167
|
+
|
|
1168
|
+
private fun MapLabelLayerRecord.options(
|
|
1169
|
+
nativeId: String,
|
|
1170
|
+
nativeLayerIds: Map<String, String>,
|
|
1171
|
+
) = LabelLayerOptions
|
|
1172
|
+
.from(nativeId)
|
|
1173
|
+
.setCompetitionType(competitionType(competition))
|
|
1174
|
+
.setCompetitionUnit(competitionUnit(competitionUnit))
|
|
1175
|
+
.setOrderingType(ordering(ordering))
|
|
1176
|
+
.setZOrder(zOrder)
|
|
1177
|
+
.setVisible(visible)
|
|
1178
|
+
.setClickable(clickable)
|
|
1179
|
+
.addCompetitionExceptions(*competitionExceptions.map(nativeLayerIds::getValue).toTypedArray())
|
|
1180
|
+
|
|
1181
|
+
private fun MapLodLabelLayerRecord.options(
|
|
1182
|
+
nativeId: String,
|
|
1183
|
+
nativeLayerIds: Map<String, String>,
|
|
1184
|
+
density: Float,
|
|
1185
|
+
) = (this as MapLabelLayerRecord).options(nativeId, nativeLayerIds).setLodRadius(radius.dp("radius", density))
|
|
1186
|
+
|
|
1187
|
+
private fun MapLabelRecord.options(style: LabelStyles) =
|
|
1188
|
+
LabelOptions
|
|
1189
|
+
.from(id, coordinate.latLng())
|
|
1190
|
+
.setStyles(style)
|
|
1191
|
+
.setTexts(LabelTextBuilder().apply { text.forEach { addTextLine(it.value, it.styleIndex) } })
|
|
1192
|
+
.setRank(rank.integerLong("rank"))
|
|
1193
|
+
.setClickable(clickable)
|
|
1194
|
+
.setVisible(visible)
|
|
1195
|
+
.setTransform(transform(transform))
|
|
1196
|
+
|
|
1197
|
+
private fun MapLabelBadgeStyleRecord.native(
|
|
1198
|
+
images: Map<String, Bitmap>,
|
|
1199
|
+
density: Float,
|
|
1200
|
+
) = com.kakao.vectormap.label.BadgeOptions
|
|
1201
|
+
.from(id, image.bitmap(images, density))
|
|
1202
|
+
.setOffset(offset?.x?.dp("badge.offset.x", density) ?: 0f, offset?.y?.dp("badge.offset.y", density) ?: 0f)
|
|
1203
|
+
.setZOrder(zOrder)
|
|
1204
|
+
|
|
1205
|
+
private fun MapLabelBadgeRecord.native(
|
|
1206
|
+
images: Map<String, Bitmap>,
|
|
1207
|
+
density: Float,
|
|
1208
|
+
) = com.kakao.vectormap.label.BadgeOptions
|
|
1209
|
+
.from(id, image.bitmap(images, density))
|
|
1210
|
+
.setOffset(offset?.x?.dp("badge.offset.x", density) ?: 0f, offset?.y?.dp("badge.offset.y", density) ?: 0f)
|
|
1211
|
+
.setZOrder(zOrder)
|
|
1212
|
+
|
|
1213
|
+
private fun MapLabelImage.bitmap(
|
|
1214
|
+
images: Map<String, Bitmap>,
|
|
1215
|
+
scale: Float = 1f,
|
|
1216
|
+
): Bitmap {
|
|
1217
|
+
val source = images[uri] ?: invalid("Image was not loaded: $uri")
|
|
1218
|
+
if (width == null && height == null) {
|
|
1219
|
+
return source.copy(Bitmap.Config.ARGB_8888, false).also { it.density = Bitmap.DENSITY_NONE }
|
|
1220
|
+
}
|
|
1221
|
+
val targetWidth = width?.positiveSize("image.width", scale)
|
|
1222
|
+
val targetHeight = height?.positiveSize("image.height", scale)
|
|
1223
|
+
val ratio = source.width.toDouble() / source.height.toDouble()
|
|
1224
|
+
val resolvedWidth = targetWidth ?: ((targetHeight ?: invalid("Image dimensions are unavailable.")) * ratio).roundToInt().coerceAtLeast(1)
|
|
1225
|
+
val resolvedHeight = targetHeight ?: (resolvedWidth / ratio).roundToInt().coerceAtLeast(1)
|
|
1226
|
+
val resized = Bitmap.createScaledBitmap(source, resolvedWidth, resolvedHeight, true)
|
|
1227
|
+
return (if (resized === source) source.copy(Bitmap.Config.ARGB_8888, false) else resized)
|
|
1228
|
+
.also { it.density = Bitmap.DENSITY_NONE }
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
private fun Double.positiveSize(
|
|
1232
|
+
name: String,
|
|
1233
|
+
scale: Float = 1f,
|
|
1234
|
+
): Int {
|
|
1235
|
+
val scaled = this * scale
|
|
1236
|
+
if (!scaled.isFinite() || scaled <= 0 || scaled > Int.MAX_VALUE) invalid("$name must be positive.")
|
|
1237
|
+
return scaled.roundToInt().coerceAtLeast(1)
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private fun textGravity(value: String) =
|
|
1241
|
+
when (value) {
|
|
1242
|
+
"center" -> Gravity.CENTER
|
|
1243
|
+
"top" -> Gravity.TOP
|
|
1244
|
+
"bottom" -> Gravity.BOTTOM
|
|
1245
|
+
"left" -> Gravity.LEFT
|
|
1246
|
+
"right" -> Gravity.RIGHT
|
|
1247
|
+
else -> invalid("Unknown label text layout: $value")
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
private fun MapLabelAnimationRecord.nativeAnimation(
|
|
1251
|
+
density: Float,
|
|
1252
|
+
nativeId: String,
|
|
1253
|
+
): com.kakao.vectormap.animation.Animation =
|
|
1254
|
+
when (effect) {
|
|
1255
|
+
"drop" -> {
|
|
1256
|
+
DropAnimation
|
|
1257
|
+
.from(nativeId)
|
|
1258
|
+
.setPixelHeight(
|
|
1259
|
+
keyframes
|
|
1260
|
+
.firstOrNull()
|
|
1261
|
+
?.translation
|
|
1262
|
+
?.y
|
|
1263
|
+
?.dp("translation.y", density) ?: 0f,
|
|
1264
|
+
).setDuration(keyframes.firstOrNull()?.duration?.duration() ?: 0)
|
|
1265
|
+
.setRepeatCount(repeatCount)
|
|
1266
|
+
.setHideShapeAtStop(hideAtStop)
|
|
1267
|
+
.setRemoveLabelAtStop(removeAtStop)
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
"alpha" -> {
|
|
1271
|
+
AlphaAnimations
|
|
1272
|
+
.from(
|
|
1273
|
+
nativeId,
|
|
1274
|
+
*keyframes
|
|
1275
|
+
.map {
|
|
1276
|
+
AlphaAnimation
|
|
1277
|
+
.from(
|
|
1278
|
+
(it.alpha ?: 1.0).unitFloat("alpha"),
|
|
1279
|
+
).setDuration(it.duration.duration())
|
|
1280
|
+
.setInterpolation(interpolation(it.interpolation))
|
|
1281
|
+
}.toTypedArray(),
|
|
1282
|
+
).setHideLabelAtStop(hideAtStop)
|
|
1283
|
+
.setRemoveLabelAtStop(removeAtStop)
|
|
1284
|
+
.setResetToInitialState(resetToInitialState)
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
"scale" -> {
|
|
1288
|
+
ScaleAnimations
|
|
1289
|
+
.from(
|
|
1290
|
+
nativeId,
|
|
1291
|
+
*keyframes
|
|
1292
|
+
.map { frame ->
|
|
1293
|
+
frame.scale.required().let {
|
|
1294
|
+
ScaleAnimation
|
|
1295
|
+
.from(
|
|
1296
|
+
it.x.finiteFloat("scale.x"),
|
|
1297
|
+
it.y.finiteFloat("scale.y"),
|
|
1298
|
+
).setDuration(frame.duration.duration())
|
|
1299
|
+
.setInterpolation(interpolation(frame.interpolation))
|
|
1300
|
+
}
|
|
1301
|
+
}.toTypedArray(),
|
|
1302
|
+
).setHideLabelAtStop(hideAtStop)
|
|
1303
|
+
.setRemoveLabelAtStop(removeAtStop)
|
|
1304
|
+
.setResetToInitialState(resetToInitialState)
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
"scaleAlpha" -> {
|
|
1308
|
+
ScaleAlphaAnimations
|
|
1309
|
+
.from(
|
|
1310
|
+
nativeId,
|
|
1311
|
+
*keyframes
|
|
1312
|
+
.map { frame ->
|
|
1313
|
+
frame.scale.required().let {
|
|
1314
|
+
ScaleAlphaAnimation
|
|
1315
|
+
.from(
|
|
1316
|
+
it.x.finiteFloat("scale.x"),
|
|
1317
|
+
it.y.finiteFloat("scale.y"),
|
|
1318
|
+
(
|
|
1319
|
+
frame.alpha
|
|
1320
|
+
?: 1.0
|
|
1321
|
+
).unitFloat("alpha"),
|
|
1322
|
+
).setDuration(frame.duration.duration())
|
|
1323
|
+
.setInterpolation(interpolation(frame.interpolation))
|
|
1324
|
+
}
|
|
1325
|
+
}.toTypedArray(),
|
|
1326
|
+
).setHideLabelAtStop(hideAtStop)
|
|
1327
|
+
.setRemoveLabelAtStop(removeAtStop)
|
|
1328
|
+
.setResetToInitialState(resetToInitialState)
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
"transform" -> {
|
|
1332
|
+
TransformAnimations
|
|
1333
|
+
.from(
|
|
1334
|
+
nativeId,
|
|
1335
|
+
*keyframes
|
|
1336
|
+
.map { frame ->
|
|
1337
|
+
TransformAnimation
|
|
1338
|
+
.from()
|
|
1339
|
+
.setDuration(frame.duration.duration())
|
|
1340
|
+
.setInterpolation(interpolation(frame.interpolation))
|
|
1341
|
+
.setAlpha((frame.alpha ?: 1.0).unitFloat("alpha"))
|
|
1342
|
+
.setScale(
|
|
1343
|
+
frame.scale?.let { android.graphics.PointF(it.x.finiteFloat("scale.x"), it.y.finiteFloat("scale.y")) }
|
|
1344
|
+
?: android.graphics.PointF(1f, 1f),
|
|
1345
|
+
).setPixelTranslation(
|
|
1346
|
+
frame.translation?.let { android.graphics.PointF(it.x.dp("translation.x", density), it.y.dp("translation.y", density)) }
|
|
1347
|
+
?: android.graphics.PointF(),
|
|
1348
|
+
).setRotation(Math.toRadians(frame.rotation ?: 0.0).toFloat())
|
|
1349
|
+
}.toTypedArray(),
|
|
1350
|
+
).setHideLabelAtStop(hideAtStop)
|
|
1351
|
+
.setRemoveLabelAtStop(removeAtStop)
|
|
1352
|
+
.setResetToInitialState(resetToInitialState)
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
else -> {
|
|
1356
|
+
invalid("Unknown label animation effect: $effect")
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private fun Label.clickPayload(
|
|
1361
|
+
kind: String,
|
|
1362
|
+
publicLayerId: String,
|
|
1363
|
+
) = mapOf(
|
|
1364
|
+
"id" to labelId,
|
|
1365
|
+
"layerId" to publicLayerId,
|
|
1366
|
+
"kind" to kind,
|
|
1367
|
+
"coordinate" to mapOf("latitude" to position.latitude, "longitude" to position.longitude),
|
|
1368
|
+
)
|
|
1369
|
+
|
|
1370
|
+
private fun LodLabel.clickPayload(
|
|
1371
|
+
kind: String,
|
|
1372
|
+
publicLayerId: String,
|
|
1373
|
+
) = mapOf(
|
|
1374
|
+
"id" to labelId,
|
|
1375
|
+
"layerId" to publicLayerId,
|
|
1376
|
+
"kind" to kind,
|
|
1377
|
+
"coordinate" to mapOf("latitude" to position.latitude, "longitude" to position.longitude),
|
|
1378
|
+
)
|
|
1379
|
+
|
|
1380
|
+
private fun MapLabelPoint.latLng(): LatLng {
|
|
1381
|
+
validate()
|
|
1382
|
+
return LatLng.from(latitude, longitude)
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private fun MapLabelPoint.validate() {
|
|
1386
|
+
if (!latitude.isFinite() || !longitude.isFinite() || latitude !in -90.0..90.0 ||
|
|
1387
|
+
longitude !in -180.0..180.0
|
|
1388
|
+
) {
|
|
1389
|
+
invalid("Invalid coordinate.")
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
private fun Double.duration(): Int {
|
|
1394
|
+
if (!isFinite() || this < 0 || this > Int.MAX_VALUE ||
|
|
1395
|
+
this % 1.0 != 0.0
|
|
1396
|
+
) {
|
|
1397
|
+
invalid("Duration must be a non-negative 32-bit integer.")
|
|
1398
|
+
}
|
|
1399
|
+
return toInt()
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
private fun Double.finiteFloat(name: String): Float {
|
|
1403
|
+
if (!isFinite() || this > Float.MAX_VALUE ||
|
|
1404
|
+
this < -Float.MAX_VALUE
|
|
1405
|
+
) {
|
|
1406
|
+
invalid("$name must be finite.")
|
|
1407
|
+
}
|
|
1408
|
+
return toFloat()
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
private fun Double.dp(
|
|
1412
|
+
name: String,
|
|
1413
|
+
density: Float,
|
|
1414
|
+
): Float {
|
|
1415
|
+
if (!density.isFinite() || density <= 0) invalid("Display density must be positive.")
|
|
1416
|
+
val value = this * density
|
|
1417
|
+
return value.finiteFloat(name)
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
private fun Double.dpInt(
|
|
1421
|
+
name: String,
|
|
1422
|
+
density: Float,
|
|
1423
|
+
): Int {
|
|
1424
|
+
if (!isFinite()) invalid("$name must be finite.")
|
|
1425
|
+
if (!density.isFinite() || density <= 0) invalid("Display density must be positive.")
|
|
1426
|
+
val value = this * density
|
|
1427
|
+
if (!value.isFinite() || value > Int.MAX_VALUE || value < Int.MIN_VALUE) {
|
|
1428
|
+
invalid("$name is outside the supported range.")
|
|
1429
|
+
}
|
|
1430
|
+
return value.roundToInt()
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
private fun Double.nonnegativeDpInt(
|
|
1434
|
+
name: String,
|
|
1435
|
+
density: Float,
|
|
1436
|
+
): Int {
|
|
1437
|
+
if (this < 0) invalid("$name must be non-negative.")
|
|
1438
|
+
return dpInt(name, density).also { if (it < 0) invalid("$name must be non-negative.") }
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
private fun Double.positiveDpInt(
|
|
1442
|
+
name: String,
|
|
1443
|
+
density: Float,
|
|
1444
|
+
): Int {
|
|
1445
|
+
if (this <= 0) invalid("$name must be positive.")
|
|
1446
|
+
return dpInt(name, density).also { if (it <= 0) invalid("$name must be positive.") }
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
private fun Double.unitFloat(name: String): Float {
|
|
1450
|
+
val value = finiteFloat(name)
|
|
1451
|
+
if (value !in 0f..1f) invalid("$name must be between 0 and 1.")
|
|
1452
|
+
return value
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
private fun Double.integerLong(name: String): Long {
|
|
1456
|
+
if (!isFinite() || this % 1.0 != 0.0 || this > Long.MAX_VALUE || this < Long.MIN_VALUE) {
|
|
1457
|
+
invalid("$name must be an integer.")
|
|
1458
|
+
}
|
|
1459
|
+
return toLong()
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
private fun Double.integerInt(name: String): Int {
|
|
1463
|
+
val value = integerLong(name)
|
|
1464
|
+
if (value > Int.MAX_VALUE || value < Int.MIN_VALUE) invalid("$name is outside the supported range.")
|
|
1465
|
+
return value.toInt()
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
private fun Double.nonnegativeInt(name: String): Int {
|
|
1469
|
+
val value = integerInt(name)
|
|
1470
|
+
if (value < 0) invalid("$name must be non-negative.")
|
|
1471
|
+
return value
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
private fun Double.positiveInt(name: String): Int {
|
|
1475
|
+
val value = integerInt(name)
|
|
1476
|
+
if (value <= 0) invalid("$name must be positive.")
|
|
1477
|
+
return value
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
private fun parseColor(value: String): Int {
|
|
1481
|
+
val hex = value.removePrefix("#")
|
|
1482
|
+
if (hex.length != 6 && hex.length != 8) invalid("Invalid color: $value")
|
|
1483
|
+
val number = hex.toLongOrNull(16) ?: invalid("Invalid color: $value")
|
|
1484
|
+
val hasAlpha = hex.length == 8
|
|
1485
|
+
val red = ((number shr (if (hasAlpha) 24 else 16)) and 0xFF).toInt()
|
|
1486
|
+
val green = ((number shr (if (hasAlpha) 16 else 8)) and 0xFF).toInt()
|
|
1487
|
+
val blue = ((number shr (if (hasAlpha) 8 else 0)) and 0xFF).toInt()
|
|
1488
|
+
val alpha = if (hasAlpha) (number and 0xFF).toInt() else 0xFF
|
|
1489
|
+
return Color.argb(alpha, red, green, blue)
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
private fun transition(value: String) =
|
|
1493
|
+
when (value) {
|
|
1494
|
+
"none" -> Transition.None
|
|
1495
|
+
"alpha" -> Transition.Alpha
|
|
1496
|
+
"scale" -> Transition.Scale
|
|
1497
|
+
else -> invalid("Unknown transition: $value")
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
private fun competitionType(value: String) =
|
|
1501
|
+
when (value) {
|
|
1502
|
+
"none" -> CompetitionType.None
|
|
1503
|
+
"all" -> CompetitionType.All
|
|
1504
|
+
"upper" -> CompetitionType.Upper
|
|
1505
|
+
"upperLower" -> CompetitionType.UpperLower
|
|
1506
|
+
"upperSame" -> CompetitionType.UpperSame
|
|
1507
|
+
"same" -> CompetitionType.Same
|
|
1508
|
+
"sameLower" -> CompetitionType.SameLower
|
|
1509
|
+
"lower" -> CompetitionType.Lower
|
|
1510
|
+
else -> invalid("Unknown competition: $value")
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
private fun competitionUnit(value: String) =
|
|
1514
|
+
when (value) {
|
|
1515
|
+
"iconAndText" -> CompetitionUnit.IconAndText
|
|
1516
|
+
"iconFirst" -> CompetitionUnit.IconFirst
|
|
1517
|
+
else -> invalid("Unknown competition unit: $value")
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
private fun ordering(value: String) =
|
|
1521
|
+
when (value) {
|
|
1522
|
+
"rank" -> OrderingType.Rank
|
|
1523
|
+
"leftBottom" -> OrderingType.LeftBottom
|
|
1524
|
+
else -> invalid("Unknown ordering: $value")
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
private fun transform(value: String) =
|
|
1528
|
+
when (value) {
|
|
1529
|
+
"none" -> TransformMethod.None
|
|
1530
|
+
"absoluteRotation" -> TransformMethod.AbsoluteRotation
|
|
1531
|
+
"default" -> TransformMethod.Default
|
|
1532
|
+
"absoluteRotationKeepUpright" -> TransformMethod.AbsoluteRotation_KeepUpright
|
|
1533
|
+
"absoluteRotationDecal" -> TransformMethod.AbsoluteRotation_Decal
|
|
1534
|
+
"decal" -> TransformMethod.Decal
|
|
1535
|
+
else -> invalid("Unknown transform: $value")
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
private fun interpolation(value: String) =
|
|
1539
|
+
when (value) {
|
|
1540
|
+
"linear" -> Interpolation.Linear
|
|
1541
|
+
"cubicIn" -> Interpolation.CubicIn
|
|
1542
|
+
"cubicOut" -> Interpolation.CubicOut
|
|
1543
|
+
"cubicInOut" -> Interpolation.CubicInOut
|
|
1544
|
+
else -> invalid("Unknown interpolation: $value")
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
private fun unique(
|
|
1548
|
+
kind: String,
|
|
1549
|
+
values: List<String>,
|
|
1550
|
+
) {
|
|
1551
|
+
val duplicate =
|
|
1552
|
+
values
|
|
1553
|
+
.groupingBy { it }
|
|
1554
|
+
.eachCount()
|
|
1555
|
+
.entries
|
|
1556
|
+
.firstOrNull {
|
|
1557
|
+
it.value >
|
|
1558
|
+
1
|
|
1559
|
+
}?.key
|
|
1560
|
+
; if (duplicate != null) invalid("Duplicate $kind id: $duplicate")
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
private fun MapLabelCommandRecord.requiredId() = id ?: invalid("id is required.")
|
|
1564
|
+
|
|
1565
|
+
private fun <T : Any> T?.required(): T = this ?: invalid("Required value is missing.")
|
|
1566
|
+
|
|
1567
|
+
private fun invalid(message: String): Nothing = throw CodedException(LABEL_INVALID_ARGUMENT, message, null)
|
|
1568
|
+
|
|
1569
|
+
private fun notFound(
|
|
1570
|
+
kind: String,
|
|
1571
|
+
id: String,
|
|
1572
|
+
): Nothing = throw CodedException(LABEL_NOT_FOUND, "$kind not found: $id", null)
|