@exterio/react-native-yamap-lite 1.0.1
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/LICENSE +20 -0
- package/README.md +510 -0
- package/YamapLite.podspec +21 -0
- package/android/build.gradle +101 -0
- package/android/gradle.properties +14 -0
- package/android/settings.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/yamaplite/ClusteredYamapLiteViewManager.kt +181 -0
- package/android/src/main/java/com/yamaplite/Components/ClusteredYamapLiteView.kt +169 -0
- package/android/src/main/java/com/yamaplite/Components/YamapLiteCircleView.kt +145 -0
- package/android/src/main/java/com/yamaplite/Components/YamapLiteMarkerView.kt +214 -0
- package/android/src/main/java/com/yamaplite/Components/YamapLitePolygon.kt +149 -0
- package/android/src/main/java/com/yamaplite/Components/YamapLitePolyline.kt +171 -0
- package/android/src/main/java/com/yamaplite/Components/YamapLiteView.kt +678 -0
- package/android/src/main/java/com/yamaplite/Events/CameraPositionChangeEvent.kt +38 -0
- package/android/src/main/java/com/yamaplite/Events/MapLoadEvent.kt +34 -0
- package/android/src/main/java/com/yamaplite/Events/PressEvent.kt +24 -0
- package/android/src/main/java/com/yamaplite/Utils/ImageCache.kt +45 -0
- package/android/src/main/java/com/yamaplite/Utils/PointParser.kt +39 -0
- package/android/src/main/java/com/yamaplite/Utils/ResolveImageHelper.kt +122 -0
- package/android/src/main/java/com/yamaplite/Utils/YamapUtils.kt +169 -0
- package/android/src/main/java/com/yamaplite/YamapLiteCircleViewManager.kt +67 -0
- package/android/src/main/java/com/yamaplite/YamapLiteMarkerViewManager.kt +82 -0
- package/android/src/main/java/com/yamaplite/YamapLitePackage.kt +41 -0
- package/android/src/main/java/com/yamaplite/YamapLitePolygonViewManager.kt +61 -0
- package/android/src/main/java/com/yamaplite/YamapLitePolylineViewManager.kt +75 -0
- package/android/src/main/java/com/yamaplite/YamapLiteViewManager.kt +176 -0
- package/ios/ClusteredYamapLiteView.h +15 -0
- package/ios/ClusteredYamapLiteView.mm +325 -0
- package/ios/ClusteredYamapLiteViewManager.mm +17 -0
- package/ios/Components/Circle.swift +108 -0
- package/ios/Components/ClusteredYamap.swift +190 -0
- package/ios/Components/Marker.swift +187 -0
- package/ios/Components/Polygon.swift +108 -0
- package/ios/Components/Polyline.swift +125 -0
- package/ios/Components/Yamap.swift +585 -0
- package/ios/Listeners/CameraListener.swift +54 -0
- package/ios/Listeners/ClusterListener.swift +38 -0
- package/ios/Listeners/MapInputListener.swift +35 -0
- package/ios/Listeners/MapLoadListener.swift +35 -0
- package/ios/Listeners/MapObjectTap.swift +12 -0
- package/ios/Listeners/UserLocationObjectListener.swift +29 -0
- package/ios/Models/YamapModels.swift +77 -0
- package/ios/Utils/ImageCache.swift +20 -0
- package/ios/Utils/PointsEqual.h +45 -0
- package/ios/Utils/ResolveImageHelper.swift +142 -0
- package/ios/YamapLiteCircleView.h +15 -0
- package/ios/YamapLiteCircleView.mm +102 -0
- package/ios/YamapLiteMarkerView.h +15 -0
- package/ios/YamapLiteMarkerView.mm +102 -0
- package/ios/YamapLitePolygonView.h +15 -0
- package/ios/YamapLitePolygonView.mm +120 -0
- package/ios/YamapLitePolylineView.h +15 -0
- package/ios/YamapLitePolylineView.mm +115 -0
- package/ios/YamapLiteView.h +15 -0
- package/ios/YamapLiteView.mm +361 -0
- package/ios/YamapLiteViewManager.mm +17 -0
- package/ios/YamapUtils.h +5 -0
- package/ios/YamapUtils.mm +205 -0
- package/lib/module/@types/index.js +8 -0
- package/lib/module/@types/index.js.map +1 -0
- package/lib/module/ClusteredYamapLiteViewNativeComponent.ts +98 -0
- package/lib/module/Components/Circle.js +20 -0
- package/lib/module/Components/Circle.js.map +1 -0
- package/lib/module/Components/ClusteredYamap.js +133 -0
- package/lib/module/Components/ClusteredYamap.js.map +1 -0
- package/lib/module/Components/Marker.js +40 -0
- package/lib/module/Components/Marker.js.map +1 -0
- package/lib/module/Components/Polygon.js +25 -0
- package/lib/module/Components/Polygon.js.map +1 -0
- package/lib/module/Components/Polyline.js +25 -0
- package/lib/module/Components/Polyline.js.map +1 -0
- package/lib/module/Components/Yamap.js +124 -0
- package/lib/module/Components/Yamap.js.map +1 -0
- package/lib/module/Components/index.js +9 -0
- package/lib/module/Components/index.js.map +1 -0
- package/lib/module/NativeYamapUtils.js +5 -0
- package/lib/module/NativeYamapUtils.js.map +1 -0
- package/lib/module/Utils/YamapUtils.js +14 -0
- package/lib/module/Utils/YamapUtils.js.map +1 -0
- package/lib/module/YamapCircleViewNativeComponent.ts +28 -0
- package/lib/module/YamapLiteViewNativeComponent.ts +93 -0
- package/lib/module/YamapMarkerViewNativeComponent.ts +32 -0
- package/lib/module/YamapPolygonViewNativeComponent.ts +28 -0
- package/lib/module/YamapPolylineViewNativeComponent.ts +31 -0
- package/lib/module/index.js +7 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/@types/index.d.ts +257 -0
- package/lib/typescript/src/@types/index.d.ts.map +1 -0
- package/lib/typescript/src/ClusteredYamapLiteViewNativeComponent.d.ts +82 -0
- package/lib/typescript/src/ClusteredYamapLiteViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/Components/Circle.d.ts +4 -0
- package/lib/typescript/src/Components/Circle.d.ts.map +1 -0
- package/lib/typescript/src/Components/ClusteredYamap.d.ts +3 -0
- package/lib/typescript/src/Components/ClusteredYamap.d.ts.map +1 -0
- package/lib/typescript/src/Components/Marker.d.ts +3 -0
- package/lib/typescript/src/Components/Marker.d.ts.map +1 -0
- package/lib/typescript/src/Components/Polygon.d.ts +4 -0
- package/lib/typescript/src/Components/Polygon.d.ts.map +1 -0
- package/lib/typescript/src/Components/Polyline.d.ts +4 -0
- package/lib/typescript/src/Components/Polyline.d.ts.map +1 -0
- package/lib/typescript/src/Components/Yamap.d.ts +3 -0
- package/lib/typescript/src/Components/Yamap.d.ts.map +1 -0
- package/lib/typescript/src/Components/index.d.ts +7 -0
- package/lib/typescript/src/Components/index.d.ts.map +1 -0
- package/lib/typescript/src/NativeYamapUtils.d.ts +30 -0
- package/lib/typescript/src/NativeYamapUtils.d.ts.map +1 -0
- package/lib/typescript/src/Utils/YamapUtils.d.ts +27 -0
- package/lib/typescript/src/Utils/YamapUtils.d.ts.map +1 -0
- package/lib/typescript/src/YamapCircleViewNativeComponent.d.ts +20 -0
- package/lib/typescript/src/YamapCircleViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/YamapLiteViewNativeComponent.d.ts +80 -0
- package/lib/typescript/src/YamapLiteViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/YamapMarkerViewNativeComponent.d.ts +25 -0
- package/lib/typescript/src/YamapMarkerViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/YamapPolygonViewNativeComponent.d.ts +20 -0
- package/lib/typescript/src/YamapPolygonViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/YamapPolylineViewNativeComponent.d.ts +23 -0
- package/lib/typescript/src/YamapPolylineViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +189 -0
- package/src/@types/index.ts +314 -0
- package/src/ClusteredYamapLiteViewNativeComponent.ts +98 -0
- package/src/Components/Circle.tsx +22 -0
- package/src/Components/ClusteredYamap.tsx +194 -0
- package/src/Components/Marker.tsx +50 -0
- package/src/Components/Polygon.tsx +27 -0
- package/src/Components/Polyline.tsx +27 -0
- package/src/Components/Yamap.tsx +176 -0
- package/src/Components/index.ts +6 -0
- package/src/NativeYamapUtils.ts +47 -0
- package/src/Utils/YamapUtils.ts +63 -0
- package/src/YamapCircleViewNativeComponent.ts +28 -0
- package/src/YamapLiteViewNativeComponent.ts +93 -0
- package/src/YamapMarkerViewNativeComponent.ts +32 -0
- package/src/YamapPolygonViewNativeComponent.ts +28 -0
- package/src/YamapPolylineViewNativeComponent.ts +31 -0
- package/src/index.tsx +5 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
package com.yamaplite
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableArray
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
|
+
import com.facebook.react.uimanager.ViewGroupManager
|
|
8
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
9
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
10
|
+
import com.facebook.react.viewmanagers.ClusteredYamapLiteViewManagerInterface
|
|
11
|
+
import com.facebook.react.viewmanagers.ClusteredYamapLiteViewManagerDelegate
|
|
12
|
+
import android.view.View
|
|
13
|
+
import android.graphics.Color
|
|
14
|
+
import com.yamaplite.components.ClusteredYamapLiteView
|
|
15
|
+
import com.yamaplite.utils.PointParser
|
|
16
|
+
|
|
17
|
+
class ClusteredYamapLiteViewManager : ViewGroupManager<ClusteredYamapLiteView>(), ClusteredYamapLiteViewManagerInterface<ClusteredYamapLiteView> {
|
|
18
|
+
private val mDelegate: ViewManagerDelegate<ClusteredYamapLiteView>
|
|
19
|
+
|
|
20
|
+
init {
|
|
21
|
+
mDelegate = ClusteredYamapLiteViewManagerDelegate(this)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun getDelegate(): ViewManagerDelegate<ClusteredYamapLiteView> = mDelegate
|
|
25
|
+
|
|
26
|
+
override fun createViewInstance(context: ThemedReactContext): ClusteredYamapLiteView {
|
|
27
|
+
return ClusteredYamapLiteView(context)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun onDropViewInstance(view: ClusteredYamapLiteView) {
|
|
31
|
+
view.onDropViewInstance()
|
|
32
|
+
super.onDropViewInstance(view)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun addView(parent: ClusteredYamapLiteView, child: View, index: Int) {
|
|
36
|
+
parent.addChild(child, index)
|
|
37
|
+
super.addView(parent, child, index)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun getChildAt(parent: ClusteredYamapLiteView, index: Int): View {
|
|
41
|
+
return parent.getReactChildAt(index) ?: throw IndexOutOfBoundsException("Index $index is out of bounds for child count ${parent.getReactChildCount()}")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun getChildCount(parent: ClusteredYamapLiteView): Int {
|
|
45
|
+
return parent.getReactChildCount()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
override fun removeViewAt(parent: ClusteredYamapLiteView, index: Int) {
|
|
49
|
+
val childCount = parent.getReactChildCount()
|
|
50
|
+
if (index >= 0 && index < childCount) {
|
|
51
|
+
parent.removeChild(index)
|
|
52
|
+
super.removeViewAt(parent, index)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun getName() = "ClusteredYamapLiteView"
|
|
57
|
+
|
|
58
|
+
@ReactProp(name = "userLocationIcon")
|
|
59
|
+
override fun setUserLocationIcon(view: ClusteredYamapLiteView, value: String?) {
|
|
60
|
+
view.setUserLocationIcon(value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@ReactProp(name = "userLocationIconScale")
|
|
64
|
+
override fun setUserLocationIconScale(view: ClusteredYamapLiteView, value: Float) {
|
|
65
|
+
view.setUserLocationIconScale(value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@ReactProp(name = "showUserPosition")
|
|
69
|
+
override fun setShowUserPosition(view: ClusteredYamapLiteView, value: Boolean) {
|
|
70
|
+
view.setShowUserPosition(value)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@ReactProp(name = "nightMode")
|
|
74
|
+
override fun setNightMode(view: ClusteredYamapLiteView, value: Boolean) {
|
|
75
|
+
view.setNightMode(value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@ReactProp(name = "mapStyle")
|
|
79
|
+
override fun setMapStyle(view: ClusteredYamapLiteView, value: String?) {
|
|
80
|
+
view.setMapStyle(value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@ReactProp(name = "scrollGesturesEnabled")
|
|
84
|
+
override fun setScrollGesturesEnabled(view: ClusteredYamapLiteView, value: Boolean) {
|
|
85
|
+
view.setScrollGesturesEnabled(value)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@ReactProp(name = "zoomGesturesEnabled")
|
|
89
|
+
override fun setZoomGesturesEnabled(view: ClusteredYamapLiteView, value: Boolean) {
|
|
90
|
+
view.setZoomGesturesEnabled(value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@ReactProp(name = "tiltGesturesEnabled")
|
|
94
|
+
override fun setTiltGesturesEnabled(view: ClusteredYamapLiteView, value: Boolean) {
|
|
95
|
+
view.setTiltGesturesEnabled(value)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@ReactProp(name = "rotateGesturesEnabled")
|
|
99
|
+
override fun setRotateGesturesEnabled(view: ClusteredYamapLiteView, value: Boolean) {
|
|
100
|
+
view.setRotateGesturesEnabled(value)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@ReactProp(name = "fastTapEnabled")
|
|
104
|
+
override fun setFastTapEnabled(view: ClusteredYamapLiteView, value: Boolean) {
|
|
105
|
+
view.setFastTapEnabled(value)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@ReactProp(name = "initialRegion")
|
|
109
|
+
override fun setInitialRegion(view: ClusteredYamapLiteView, value: ReadableMap?) {
|
|
110
|
+
if (value != null) {
|
|
111
|
+
val region = mutableMapOf<String, Any>()
|
|
112
|
+
if (value.hasKey("lat")) region["lat"] = value.getDouble("lat")
|
|
113
|
+
if (value.hasKey("lon")) region["lon"] = value.getDouble("lon")
|
|
114
|
+
if (value.hasKey("zoom")) region["zoom"] = value.getDouble("zoom")
|
|
115
|
+
if (value.hasKey("azimuth")) region["azimuth"] = value.getDouble("azimuth")
|
|
116
|
+
if (value.hasKey("tilt")) region["tilt"] = value.getDouble("tilt")
|
|
117
|
+
view.setInitialRegion(region)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@ReactProp(name = "maxFps")
|
|
122
|
+
override fun setMaxFps(view: ClusteredYamapLiteView, value: Float) {
|
|
123
|
+
view.setMaxFps(value)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@ReactProp(name = "mapType")
|
|
127
|
+
override fun setMapType(view: ClusteredYamapLiteView, value: String?) {
|
|
128
|
+
view.setMapType(value)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@ReactProp(name = "logoPosition")
|
|
132
|
+
override fun setLogoPosition(view: ClusteredYamapLiteView, value: ReadableMap?) {
|
|
133
|
+
if (value != null) {
|
|
134
|
+
val position = mutableMapOf<String, Any>()
|
|
135
|
+
if (value.hasKey("vertical")) position["vertical"] = value.getString("vertical") ?: "bottom"
|
|
136
|
+
if (value.hasKey("horizontal")) position["horizontal"] = value.getString("horizontal") ?: "left"
|
|
137
|
+
view.setLogoPosition(position)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@ReactProp(name = "logoPadding")
|
|
142
|
+
override fun setLogoPadding(view: ClusteredYamapLiteView, value: ReadableMap?) {
|
|
143
|
+
if (value != null) {
|
|
144
|
+
val horizontalPadding =
|
|
145
|
+
if ((value.hasKey("horizontal") && !value.isNull("horizontal"))) value.getInt("horizontal") else 0
|
|
146
|
+
val verticalPadding =
|
|
147
|
+
if ((value.hasKey("vertical") && !value.isNull("vertical"))) value.getInt("vertical") else 0
|
|
148
|
+
view.setLogoPadding(horizontalPadding, verticalPadding)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@ReactProp(name = "userLocationAccuracyFillColor")
|
|
153
|
+
override fun setUserLocationAccuracyFillColor(view: ClusteredYamapLiteView, value: String?) {
|
|
154
|
+
view.setUserLocationAccuracyFillColor(value)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@ReactProp(name = "userLocationAccuracyStrokeColor")
|
|
158
|
+
override fun setUserLocationAccuracyStrokeColor(view: ClusteredYamapLiteView, value: String?) {
|
|
159
|
+
view.setUserLocationAccuracyStrokeColor(value)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@ReactProp(name = "userLocationAccuracyStrokeWidth")
|
|
163
|
+
override fun setUserLocationAccuracyStrokeWidth(view: ClusteredYamapLiteView, value: Float) {
|
|
164
|
+
view.setUserLocationAccuracyStrokeWidth(value)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@ReactProp(name = "clusteredMarkers")
|
|
168
|
+
override fun setClusteredMarkers(view: ClusteredYamapLiteView, points: ReadableArray?) {
|
|
169
|
+
view.setClusteredMarkers(PointParser.parsePoints(points))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@ReactProp(name = "clusterColor")
|
|
173
|
+
override fun setClusterColor(view: ClusteredYamapLiteView, value: String?) {
|
|
174
|
+
view.setClusterColor(Color.parseColor(value ?: "#000000"))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@ReactProp(name = "followUser")
|
|
178
|
+
override fun setFollowUser(view: ClusteredYamapLiteView, value: Boolean) {
|
|
179
|
+
view.setFollowUser(value)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
package com.yamaplite.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.Paint
|
|
8
|
+
import android.view.View
|
|
9
|
+
import com.yandex.mapkit.geometry.Point
|
|
10
|
+
import com.yandex.mapkit.map.Cluster
|
|
11
|
+
import com.yandex.mapkit.map.ClusterListener
|
|
12
|
+
import com.yandex.mapkit.map.ClusterTapListener
|
|
13
|
+
import com.yandex.mapkit.map.ClusterizedPlacemarkCollection
|
|
14
|
+
import com.yandex.mapkit.map.IconStyle
|
|
15
|
+
import com.yandex.mapkit.map.PlacemarkMapObject
|
|
16
|
+
import com.yandex.runtime.image.ImageProvider
|
|
17
|
+
import kotlin.math.abs
|
|
18
|
+
import kotlin.math.sqrt
|
|
19
|
+
|
|
20
|
+
class ClusteredYamapLiteView(context: Context) :
|
|
21
|
+
YamapLiteView(context), ClusterListener, ClusterTapListener {
|
|
22
|
+
private var clusterCollection: ClusterizedPlacemarkCollection
|
|
23
|
+
private var clusterColor = Color.RED
|
|
24
|
+
private val placemarksMap: HashMap<String?, PlacemarkMapObject?> =
|
|
25
|
+
HashMap<String?, PlacemarkMapObject?>()
|
|
26
|
+
private var pointsList = ArrayList<Point>()
|
|
27
|
+
|
|
28
|
+
init {
|
|
29
|
+
clusterCollection = mapView.mapWindow.map.mapObjects.addClusterizedPlacemarkCollection(this)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun setClusteredMarkers(points: List<Point>) {
|
|
33
|
+
pointsList = ArrayList(points)
|
|
34
|
+
rebuildClusterPlacemarks()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun setClusterColor(color: Int) {
|
|
38
|
+
clusterColor = color
|
|
39
|
+
rebuildClusterPlacemarks()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private fun rebuildClusterPlacemarks() {
|
|
43
|
+
clusterCollection.clear()
|
|
44
|
+
placemarksMap.clear()
|
|
45
|
+
val placemarks = clusterCollection.addPlacemarks(pointsList, TextImageProvider(""), IconStyle())
|
|
46
|
+
val childCount = super.getReactChildCount()
|
|
47
|
+
for (i in placemarks.indices) {
|
|
48
|
+
val placemark = placemarks[i]
|
|
49
|
+
val geometry = placemark.geometry
|
|
50
|
+
placemarksMap[geometryKey(geometry.latitude, geometry.longitude)] = placemark
|
|
51
|
+
if (i < childCount) {
|
|
52
|
+
val child = super.getReactChildAt(i)
|
|
53
|
+
if (child is YamapLiteMarkerView) {
|
|
54
|
+
child.setMarkerMapObject(placemark)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
clusterCollection.clusterPlacemarks(50.0, 12)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private fun geometryKey(lat: Double, lon: Double): String = "$lat,$lon"
|
|
62
|
+
|
|
63
|
+
fun addChild(child: View, index: Int) {
|
|
64
|
+
if (child is YamapLiteMarkerView) {
|
|
65
|
+
addReactChildToList(child, index)
|
|
66
|
+
val placemark = placemarksMap[geometryKey(child.latitude, child.longitude)]
|
|
67
|
+
if (placemark != null) {
|
|
68
|
+
child.setMarkerMapObject(placemark)
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
super.addReactChild(child, index)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fun removeChild(index: Int) {
|
|
76
|
+
if (index < 0 || index >= super.getReactChildCount()) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
val child = super.getReactChildAt(index)
|
|
80
|
+
if (child is YamapLiteMarkerView) {
|
|
81
|
+
val clusterPlacemark = placemarksMap.remove(geometryKey(child.latitude, child.longitude))
|
|
82
|
+
if (clusterPlacemark != null && clusterPlacemark.isValid) {
|
|
83
|
+
clusterCollection.remove(clusterPlacemark)
|
|
84
|
+
}
|
|
85
|
+
removeReactChildFromListAt(index)
|
|
86
|
+
} else {
|
|
87
|
+
super.removeReactChildAt(index)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override fun onClusterAdded(cluster: Cluster) {
|
|
92
|
+
cluster.appearance.setIcon(TextImageProvider(cluster.size.toString()))
|
|
93
|
+
cluster.addClusterTapListener(this)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onClusterTap(cluster: Cluster): Boolean {
|
|
97
|
+
val points = ArrayList<Point?>()
|
|
98
|
+
for (placemark in cluster.placemarks) {
|
|
99
|
+
points.add(placemark.geometry)
|
|
100
|
+
}
|
|
101
|
+
super.fitMarkers(points)
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private inner class TextImageProvider(private val text: String) : ImageProvider() {
|
|
106
|
+
override fun getId(): String {
|
|
107
|
+
return "text_$text"
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override fun getImage(): Bitmap {
|
|
111
|
+
// Scale sizes by display density to match iOS (which uses points that scale automatically)
|
|
112
|
+
val density = this@ClusteredYamapLiteView.context.resources.displayMetrics.density
|
|
113
|
+
val scaledFontSize = Companion.FONT_SIZE * density
|
|
114
|
+
val scaledMarginSize = Companion.MARGIN_SIZE * density
|
|
115
|
+
val scaledStrokeSize = Companion.STROKE_SIZE * density
|
|
116
|
+
|
|
117
|
+
val textPaint = Paint()
|
|
118
|
+
textPaint.textSize = scaledFontSize
|
|
119
|
+
textPaint.textAlign = Paint.Align.CENTER
|
|
120
|
+
textPaint.style = Paint.Style.FILL
|
|
121
|
+
textPaint.isAntiAlias = true
|
|
122
|
+
|
|
123
|
+
val widthF = textPaint.measureText(text)
|
|
124
|
+
val textMetrics = textPaint.fontMetrics
|
|
125
|
+
val heightF = (abs(textMetrics.bottom.toDouble()) + abs(textMetrics.top.toDouble())).toFloat()
|
|
126
|
+
val textRadius = sqrt((widthF * widthF + heightF * heightF).toDouble()).toFloat() / 2
|
|
127
|
+
val internalRadius = textRadius + scaledMarginSize
|
|
128
|
+
val externalRadius = internalRadius + scaledStrokeSize
|
|
129
|
+
|
|
130
|
+
val width = (2 * externalRadius + 0.5).toInt()
|
|
131
|
+
|
|
132
|
+
val bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888)
|
|
133
|
+
val canvas = Canvas(bitmap)
|
|
134
|
+
|
|
135
|
+
val backgroundPaint = Paint()
|
|
136
|
+
backgroundPaint.isAntiAlias = true
|
|
137
|
+
backgroundPaint.color = clusterColor
|
|
138
|
+
canvas.drawCircle(
|
|
139
|
+
(width / 2).toFloat(),
|
|
140
|
+
(width / 2).toFloat(),
|
|
141
|
+
externalRadius,
|
|
142
|
+
backgroundPaint
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
backgroundPaint.color = Color.WHITE
|
|
146
|
+
canvas.drawCircle(
|
|
147
|
+
(width / 2).toFloat(),
|
|
148
|
+
(width / 2).toFloat(),
|
|
149
|
+
internalRadius,
|
|
150
|
+
backgroundPaint
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
canvas.drawText(
|
|
154
|
+
text,
|
|
155
|
+
(width / 2).toFloat(),
|
|
156
|
+
width / 2 - (textMetrics.ascent + textMetrics.descent) / 2,
|
|
157
|
+
textPaint
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return bitmap
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
companion object {
|
|
165
|
+
private const val FONT_SIZE = 45f
|
|
166
|
+
private const val MARGIN_SIZE = 9f
|
|
167
|
+
private const val STROKE_SIZE = 9f
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
package com.yamaplite.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import com.facebook.react.bridge.ReactContext
|
|
7
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
8
|
+
import com.yamaplite.events.PressEvent
|
|
9
|
+
import com.yandex.mapkit.geometry.Circle
|
|
10
|
+
import com.yandex.mapkit.geometry.Point
|
|
11
|
+
import com.yandex.mapkit.map.CircleMapObject
|
|
12
|
+
import com.yandex.mapkit.map.MapObject
|
|
13
|
+
import com.yandex.mapkit.map.MapObjectTapListener
|
|
14
|
+
import com.yandex.mapkit.mapview.MapView
|
|
15
|
+
|
|
16
|
+
class YamapCircle(context: Context?) : ViewGroup(context), MapObjectTapListener {
|
|
17
|
+
var circle: Circle
|
|
18
|
+
|
|
19
|
+
var rnMapObject: MapObject? = null
|
|
20
|
+
private var mapView: MapView? = null
|
|
21
|
+
private var handled = true
|
|
22
|
+
private var fillColor = Color.BLACK
|
|
23
|
+
private var strokeColor = Color.BLACK
|
|
24
|
+
private var zInd = 1
|
|
25
|
+
private var strokeWidth = 1f
|
|
26
|
+
private var center = Point(0.0, 0.0)
|
|
27
|
+
private var radius = 0f
|
|
28
|
+
|
|
29
|
+
init {
|
|
30
|
+
circle = Circle(center, radius)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
|
|
34
|
+
|
|
35
|
+
// PROPS
|
|
36
|
+
fun setCenter(point: Point) {
|
|
37
|
+
center = point
|
|
38
|
+
updateGeometry()
|
|
39
|
+
if (rnMapObject == null && mapView != null && radius > 0) {
|
|
40
|
+
addToMap(mapView!!)
|
|
41
|
+
} else {
|
|
42
|
+
updateCircle()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun setRadius(_radius: Float) {
|
|
47
|
+
radius = _radius
|
|
48
|
+
updateGeometry()
|
|
49
|
+
if (rnMapObject == null &&
|
|
50
|
+
mapView != null &&
|
|
51
|
+
radius > 0 &&
|
|
52
|
+
(center.latitude != 0.0 || center.longitude != 0.0)
|
|
53
|
+
) {
|
|
54
|
+
addToMap(mapView!!)
|
|
55
|
+
} else {
|
|
56
|
+
updateCircle()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun updateGeometry() {
|
|
61
|
+
circle = Circle(center, radius)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun setZInd(_zInd: Int) {
|
|
65
|
+
zInd = _zInd
|
|
66
|
+
updateCircle()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun setHandled(_handled: Boolean) {
|
|
70
|
+
handled = _handled
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun setStrokeColor(_color: Int) {
|
|
74
|
+
strokeColor = _color
|
|
75
|
+
updateCircle()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fun setFillColor(_color: Int) {
|
|
79
|
+
fillColor = _color
|
|
80
|
+
updateCircle()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun setStrokeWidth(width: Float) {
|
|
84
|
+
strokeWidth = width
|
|
85
|
+
updateCircle()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun updateCircle() {
|
|
89
|
+
if (rnMapObject != null) {
|
|
90
|
+
(rnMapObject as CircleMapObject).geometry = circle
|
|
91
|
+
(rnMapObject as CircleMapObject).strokeWidth = strokeWidth
|
|
92
|
+
(rnMapObject as CircleMapObject).strokeColor = strokeColor
|
|
93
|
+
(rnMapObject as CircleMapObject).fillColor = fillColor
|
|
94
|
+
(rnMapObject as CircleMapObject).zIndex = zInd.toFloat()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun setCircleMapObject(obj: MapObject?) {
|
|
99
|
+
rnMapObject = obj as CircleMapObject?
|
|
100
|
+
rnMapObject!!.addTapListener(this)
|
|
101
|
+
updateCircle()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fun addToMap(mapView: MapView) {
|
|
105
|
+
this.mapView = mapView
|
|
106
|
+
updateGeometry()
|
|
107
|
+
if (!isGeometryValid()) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
val circleObject = mapView.getMapWindow().map.mapObjects.addCircle(circle)
|
|
111
|
+
rnMapObject = circleObject
|
|
112
|
+
circleObject.addTapListener(this)
|
|
113
|
+
updateCircle()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private fun isGeometryValid(): Boolean {
|
|
117
|
+
return radius > 0f && center.latitude.isFinite() && center.longitude.isFinite()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
fun removeFromMap(mapView: MapView) {
|
|
121
|
+
rnMapObject?.let {
|
|
122
|
+
if (it.isValid) {
|
|
123
|
+
mapView.getMapWindow().map.mapObjects.remove(it)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
rnMapObject = null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override fun onMapObjectTap(mapObject: MapObject, point: Point): Boolean {
|
|
130
|
+
val reactContext = context as? ReactContext
|
|
131
|
+
if (reactContext == null) {
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
val viewId = getId()
|
|
136
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
137
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, viewId)
|
|
138
|
+
|
|
139
|
+
if (eventDispatcher != null) {
|
|
140
|
+
val event = PressEvent(surfaceId, viewId, "onCirclePress", point)
|
|
141
|
+
eventDispatcher.dispatchEvent(event)
|
|
142
|
+
}
|
|
143
|
+
return handled
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
package com.yamaplite.components
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.PointF
|
|
7
|
+
import android.view.View
|
|
8
|
+
import com.facebook.react.bridge.ReactContext
|
|
9
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
10
|
+
import com.yamaplite.events.PressEvent
|
|
11
|
+
import com.yamaplite.utils.ResolveImageHelper
|
|
12
|
+
import com.yandex.mapkit.geometry.Point
|
|
13
|
+
import com.yandex.mapkit.map.IconStyle
|
|
14
|
+
import com.yandex.mapkit.map.MapObject
|
|
15
|
+
import com.yandex.mapkit.map.MapObjectTapListener
|
|
16
|
+
import com.yandex.mapkit.map.PlacemarkMapObject
|
|
17
|
+
import com.yandex.mapkit.map.RotationType
|
|
18
|
+
import com.yandex.mapkit.mapview.MapView
|
|
19
|
+
import com.yandex.runtime.image.ImageProvider
|
|
20
|
+
import kotlinx.coroutines.CoroutineScope
|
|
21
|
+
import kotlinx.coroutines.Dispatchers
|
|
22
|
+
import kotlinx.coroutines.cancel
|
|
23
|
+
import kotlinx.coroutines.launch
|
|
24
|
+
|
|
25
|
+
class YamapLiteMarkerView(context: Context) : View(context), MapObjectTapListener {
|
|
26
|
+
var latitude: Double = 0.0
|
|
27
|
+
var longitude: Double = 0.0
|
|
28
|
+
private var iconSource: String? = null
|
|
29
|
+
private var iconBitmap: Bitmap? = null
|
|
30
|
+
var markerScale: Double = 1.0
|
|
31
|
+
var zInd: Float = 0f
|
|
32
|
+
var isVisibleFlag: Boolean = true
|
|
33
|
+
var anchorX: Double = 0.5
|
|
34
|
+
var anchorY: Double = 0.5
|
|
35
|
+
private var _rotated: Boolean = false
|
|
36
|
+
private var _handled: Boolean = false
|
|
37
|
+
private var _size: Int = 25
|
|
38
|
+
private var _childView: View? = null
|
|
39
|
+
|
|
40
|
+
var placemark: PlacemarkMapObject? = null
|
|
41
|
+
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
|
42
|
+
|
|
43
|
+
private val childLayoutListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
|
44
|
+
applyStyle()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fun setMarkerMapObject(obj: PlacemarkMapObject?) {
|
|
48
|
+
placemark = obj
|
|
49
|
+
placemark?.userData = this
|
|
50
|
+
placemark?.addTapListener(this)
|
|
51
|
+
placemark?.isVisible = false
|
|
52
|
+
applyStyle()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun addToMap(mapView: MapView) {
|
|
56
|
+
if (!latitude.isFinite() || !longitude.isFinite()) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
placemark?.let {
|
|
60
|
+
if (it.isValid) {
|
|
61
|
+
mapView.mapWindow.map.mapObjects.remove(it)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
val point = Point(latitude, longitude)
|
|
65
|
+
placemark = mapView.mapWindow.map.mapObjects.addPlacemark(point)
|
|
66
|
+
placemark?.userData = this
|
|
67
|
+
placemark?.addTapListener(this)
|
|
68
|
+
applyStyle()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fun onDrop() {
|
|
72
|
+
_childView?.removeOnLayoutChangeListener(childLayoutListener)
|
|
73
|
+
_childView = null
|
|
74
|
+
coroutineScope.cancel()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fun removeFromMap(mapView: MapView) {
|
|
78
|
+
placemark?.let {
|
|
79
|
+
if (it.isValid) {
|
|
80
|
+
mapView.mapWindow.map.mapObjects.remove(it)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
placemark = null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fun setPoint(lat: Double, lon: Double) {
|
|
87
|
+
latitude = lat
|
|
88
|
+
longitude = lon
|
|
89
|
+
placemark?.geometry = Point(lat, lon)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fun setIconSource(name: String?) {
|
|
93
|
+
iconSource = name
|
|
94
|
+
applyStyle()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun setScale(value: Double) {
|
|
98
|
+
markerScale = value
|
|
99
|
+
applyStyle()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fun setZInd(value: Int) {
|
|
103
|
+
zInd = value.toFloat()
|
|
104
|
+
applyStyle()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fun setVisible(value: Boolean) {
|
|
108
|
+
isVisibleFlag = value
|
|
109
|
+
placemark?.isVisible = value
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fun setAnchor(x: Double, y: Double) {
|
|
113
|
+
anchorX = x
|
|
114
|
+
anchorY = y
|
|
115
|
+
applyStyle()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fun setRotated(value: Boolean) {
|
|
119
|
+
_rotated = value
|
|
120
|
+
applyStyle()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fun setHandled(value: Boolean) {
|
|
124
|
+
_handled = value
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fun getRotated(): Boolean = _rotated
|
|
128
|
+
|
|
129
|
+
fun getHandled(): Boolean = _handled
|
|
130
|
+
|
|
131
|
+
fun setSize(value: Int) {
|
|
132
|
+
_size = value
|
|
133
|
+
applyStyle()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fun setChildView(view: View?) {
|
|
137
|
+
if (view == null) {
|
|
138
|
+
_childView?.removeOnLayoutChangeListener(childLayoutListener)
|
|
139
|
+
_childView = null
|
|
140
|
+
applyStyle()
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
_childView = view
|
|
144
|
+
_childView!!.addOnLayoutChangeListener(childLayoutListener)
|
|
145
|
+
if (_childView!!.width > 0 && _childView!!.height > 0) {
|
|
146
|
+
applyStyle()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun applyStyle() {
|
|
151
|
+
if (placemark != null && placemark!!.isValid) {
|
|
152
|
+
(placemark as PlacemarkMapObject).isVisible = false
|
|
153
|
+
val iconStyle = IconStyle()
|
|
154
|
+
iconStyle.setScale(markerScale.toFloat())
|
|
155
|
+
iconStyle.setRotationType(if (_rotated) RotationType.ROTATE else RotationType.NO_ROTATION)
|
|
156
|
+
iconStyle.setVisible(isVisibleFlag)
|
|
157
|
+
iconStyle.setAnchor(PointF(anchorX.toFloat(), anchorY.toFloat()))
|
|
158
|
+
(placemark as PlacemarkMapObject).geometry = Point(latitude, longitude)
|
|
159
|
+
(placemark as PlacemarkMapObject).zIndex = zInd.toFloat()
|
|
160
|
+
(placemark as PlacemarkMapObject).setIconStyle(iconStyle)
|
|
161
|
+
|
|
162
|
+
if (_childView != null && _childView!!.width > 0 && _childView!!.height > 0) {
|
|
163
|
+
try {
|
|
164
|
+
val b =
|
|
165
|
+
Bitmap.createBitmap(
|
|
166
|
+
_childView!!.width,
|
|
167
|
+
_childView!!.height,
|
|
168
|
+
Bitmap.Config.ARGB_8888
|
|
169
|
+
)
|
|
170
|
+
val c = Canvas(b)
|
|
171
|
+
_childView!!.draw(c)
|
|
172
|
+
val resizedBitmap = ResolveImageHelper.getInstance().resizeBitmap(context, b, _size)
|
|
173
|
+
(placemark as PlacemarkMapObject).setIcon(ImageProvider.fromBitmap(resizedBitmap))
|
|
174
|
+
(placemark as PlacemarkMapObject).setIconStyle(iconStyle)
|
|
175
|
+
(placemark as PlacemarkMapObject).isVisible = isVisibleFlag
|
|
176
|
+
} catch (e: Exception) {
|
|
177
|
+
e.printStackTrace()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (_childView == null && iconSource?.isNotEmpty() == true) {
|
|
181
|
+
val currentIconStyle = iconStyle
|
|
182
|
+
coroutineScope.launch {
|
|
183
|
+
val icon = ResolveImageHelper.getInstance().resolveImage(context, iconSource!!, _size)
|
|
184
|
+
icon?.let {
|
|
185
|
+
placemark?.let { pm ->
|
|
186
|
+
if (pm.isValid) {
|
|
187
|
+
pm.setIcon(it)
|
|
188
|
+
currentIconStyle.let { pm.setIconStyle(it) }
|
|
189
|
+
pm.isVisible = isVisibleFlag
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
override fun onMapObjectTap(p0: MapObject, p1: Point): Boolean {
|
|
199
|
+
val reactContext = context as? ReactContext
|
|
200
|
+
if (reactContext == null) {
|
|
201
|
+
return _handled
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
val viewId = getId()
|
|
205
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
206
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, viewId)
|
|
207
|
+
|
|
208
|
+
if (eventDispatcher != null) {
|
|
209
|
+
val event = PressEvent(surfaceId, viewId, "onMarkerPress", p1)
|
|
210
|
+
eventDispatcher.dispatchEvent(event)
|
|
211
|
+
}
|
|
212
|
+
return _handled
|
|
213
|
+
}
|
|
214
|
+
}
|