react-native-cn-maps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CnMaps.podspec +21 -0
- package/LICENSE +20 -0
- package/README.md +89 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +7 -0
- package/android/src/main/java/com/cnmaps/MapView.kt +368 -0
- package/android/src/main/java/com/cnmaps/MapViewManager.kt +112 -0
- package/android/src/main/java/com/cnmaps/MapsPackage.kt +17 -0
- package/ios/RNMapsMapView.h +14 -0
- package/ios/RNMapsMapView.mm +291 -0
- package/lib/module/MapMarker.js +8 -0
- package/lib/module/MapMarker.js.map +1 -0
- package/lib/module/MapView.js +111 -0
- package/lib/module/MapView.js.map +1 -0
- package/lib/module/MapViewNativeComponent.ts +79 -0
- package/lib/module/coordinate.js +72 -0
- package/lib/module/coordinate.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/MapMarker.d.ts +8 -0
- package/lib/typescript/src/MapMarker.d.ts.map +1 -0
- package/lib/typescript/src/MapView.d.ts +145 -0
- package/lib/typescript/src/MapView.d.ts.map +1 -0
- package/lib/typescript/src/MapViewNativeComponent.d.ts +55 -0
- package/lib/typescript/src/MapViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/coordinate.d.ts +8 -0
- package/lib/typescript/src/coordinate.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/lib/typescript/src/types.d.ts +50 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +182 -0
- package/src/MapMarker.tsx +14 -0
- package/src/MapView.tsx +201 -0
- package/src/MapViewNativeComponent.ts +79 -0
- package/src/coordinate.ts +123 -0
- package/src/index.tsx +15 -0
- package/src/types.ts +59 -0
package/CnMaps.podspec
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "CnMaps"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/popsiclelmlm/react-native-cn-maps.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
s.dependency "AMap3DMap"
|
|
21
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 popsiclelmlm
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# react-native-cn-maps
|
|
2
|
+
|
|
3
|
+
React Native Map components for China providers, with a `react-native-maps`-compatible API shape and a Fabric-only native implementation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install react-native-cn-maps
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This package currently targets React Native New Architecture/Fabric only. The first provider is AMap/Gaode (`provider="amap"`) on Android and iOS.
|
|
12
|
+
|
|
13
|
+
## Native Setup
|
|
14
|
+
|
|
15
|
+
### Android
|
|
16
|
+
|
|
17
|
+
Add your AMap Android key in the host app manifest:
|
|
18
|
+
|
|
19
|
+
```xml
|
|
20
|
+
<application>
|
|
21
|
+
<meta-data
|
|
22
|
+
android:name="com.amap.api.v2.apikey"
|
|
23
|
+
android:value="YOUR_AMAP_ANDROID_KEY" />
|
|
24
|
+
</application>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Android SDK version can be overridden from the host Gradle project:
|
|
28
|
+
|
|
29
|
+
```gradle
|
|
30
|
+
ext.amapSdkVersion = "latest.integration"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### iOS
|
|
34
|
+
|
|
35
|
+
Set the AMap iOS key before the React Native surface starts:
|
|
36
|
+
|
|
37
|
+
```swift
|
|
38
|
+
import AMapFoundationKit
|
|
39
|
+
|
|
40
|
+
AMapServices.shared().apiKey = "YOUR_AMAP_IOS_KEY"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The podspec depends on `AMap3DMap`, which pulls in `AMapFoundation`.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import MapView, { Marker } from 'react-native-cn-maps';
|
|
49
|
+
|
|
50
|
+
<MapView
|
|
51
|
+
provider="amap"
|
|
52
|
+
coordinateSystem="gcj02"
|
|
53
|
+
initialRegion={{
|
|
54
|
+
latitude: 31.2304,
|
|
55
|
+
longitude: 121.4737,
|
|
56
|
+
latitudeDelta: 0.05,
|
|
57
|
+
longitudeDelta: 0.05,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<Marker
|
|
61
|
+
coordinate={{ latitude: 31.2304, longitude: 121.4737 }}
|
|
62
|
+
title="Shanghai"
|
|
63
|
+
/>
|
|
64
|
+
</MapView>;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## MVP Scope
|
|
68
|
+
|
|
69
|
+
- `MapView`
|
|
70
|
+
- `Marker`
|
|
71
|
+
- `initialRegion`
|
|
72
|
+
- controlled `region`
|
|
73
|
+
- `onRegionChange`
|
|
74
|
+
- `onRegionChangeComplete`
|
|
75
|
+
- marker `onPress`
|
|
76
|
+
- `animateToRegion`
|
|
77
|
+
- `coordinateSystem="gcj02" | "wgs84"`
|
|
78
|
+
|
|
79
|
+
`Polyline`, `Polygon`, `Circle`, custom marker views, callouts, and extra providers are intentionally left for later milestones.
|
|
80
|
+
|
|
81
|
+
## Contributing
|
|
82
|
+
|
|
83
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
84
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
85
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.CnMaps = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return CnMaps[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.cnmaps"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
implementation "com.amap.api:3dmap-location-search:${rootProject.ext.has('amapSdkVersion') ? rootProject.ext.get('amapSdkVersion') : 'latest.integration'}"
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
3
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
4
|
+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
5
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
7
|
+
</manifest>
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
package com.cnmaps
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import android.view.MotionEvent
|
|
5
|
+
import android.widget.FrameLayout
|
|
6
|
+
import com.amap.api.maps.AMap
|
|
7
|
+
import com.amap.api.maps.CameraUpdate
|
|
8
|
+
import com.amap.api.maps.CameraUpdateFactory
|
|
9
|
+
import com.amap.api.maps.MapView as AMapView
|
|
10
|
+
import com.amap.api.maps.model.BitmapDescriptorFactory
|
|
11
|
+
import com.amap.api.maps.model.CameraPosition
|
|
12
|
+
import com.amap.api.maps.model.LatLng
|
|
13
|
+
import com.amap.api.maps.model.LatLngBounds
|
|
14
|
+
import com.amap.api.maps.model.Marker
|
|
15
|
+
import com.amap.api.maps.model.MarkerOptions
|
|
16
|
+
import com.facebook.react.bridge.Arguments
|
|
17
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
18
|
+
import com.facebook.react.bridge.ReadableArray
|
|
19
|
+
import com.facebook.react.bridge.ReadableMap
|
|
20
|
+
import com.facebook.react.bridge.WritableMap
|
|
21
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
22
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
23
|
+
import com.facebook.react.uimanager.events.Event
|
|
24
|
+
import kotlin.math.abs
|
|
25
|
+
import kotlin.math.ln
|
|
26
|
+
import kotlin.math.max
|
|
27
|
+
import kotlin.math.min
|
|
28
|
+
|
|
29
|
+
data class MapRegion(
|
|
30
|
+
val latitude: Double,
|
|
31
|
+
val longitude: Double,
|
|
32
|
+
val latitudeDelta: Double,
|
|
33
|
+
val longitudeDelta: Double
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
class MapView(private val reactContext: ThemedReactContext) :
|
|
37
|
+
FrameLayout(reactContext),
|
|
38
|
+
LifecycleEventListener {
|
|
39
|
+
private val mapView = AMapView(reactContext)
|
|
40
|
+
private val aMap: AMap
|
|
41
|
+
private val markerByIdentifier = LinkedHashMap<String, Marker>()
|
|
42
|
+
private var pendingInitialRegion: MapRegion? = null
|
|
43
|
+
private var didApplyInitialRegion = false
|
|
44
|
+
private var didDestroy = false
|
|
45
|
+
private var isGesture = false
|
|
46
|
+
|
|
47
|
+
init {
|
|
48
|
+
addView(mapView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
49
|
+
mapView.onCreate(null)
|
|
50
|
+
aMap = mapView.map
|
|
51
|
+
reactContext.addLifecycleEventListener(this)
|
|
52
|
+
configureMap()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fun setInitialRegion(region: MapRegion?) {
|
|
56
|
+
if (region == null || didApplyInitialRegion) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pendingInitialRegion = region
|
|
61
|
+
applyPendingInitialRegion()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fun setRegion(region: MapRegion?) {
|
|
65
|
+
if (region != null) {
|
|
66
|
+
moveToRegion(region, animated = false)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun animateToRegion(region: MapRegion, duration: Int) {
|
|
71
|
+
moveToRegion(region, animated = true, duration = duration)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fun setMarkers(markers: ReadableArray?) {
|
|
75
|
+
markerByIdentifier.values.forEach { it.remove() }
|
|
76
|
+
markerByIdentifier.clear()
|
|
77
|
+
|
|
78
|
+
if (markers == null) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (index in 0 until markers.size()) {
|
|
83
|
+
val markerMap = markers.getMap(index) ?: continue
|
|
84
|
+
val identifier = markerMap.getOptionalString("identifier") ?: index.toString()
|
|
85
|
+
val latitude = markerMap.getOptionalDouble("latitude") ?: continue
|
|
86
|
+
val longitude = markerMap.getOptionalDouble("longitude") ?: continue
|
|
87
|
+
|
|
88
|
+
val options = MarkerOptions()
|
|
89
|
+
.position(LatLng(latitude, longitude))
|
|
90
|
+
.draggable(markerMap.getOptionalBoolean("draggable") ?: false)
|
|
91
|
+
|
|
92
|
+
markerMap.getOptionalString("title")?.let { options.title(it) }
|
|
93
|
+
markerMap.getOptionalString("description")?.let { options.snippet(it) }
|
|
94
|
+
markerMap.getOptionalString("pinColor")?.let { pinColor ->
|
|
95
|
+
markerHue(pinColor)?.let { hue ->
|
|
96
|
+
options.icon(BitmapDescriptorFactory.defaultMarker(hue))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
val marker = aMap.addMarker(options)
|
|
101
|
+
marker.`object` = identifier
|
|
102
|
+
markerByIdentifier[identifier] = marker
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fun setShowsUserLocation(value: Boolean) {
|
|
107
|
+
aMap.isMyLocationEnabled = value
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fun setZoomEnabled(value: Boolean) {
|
|
111
|
+
aMap.uiSettings.isZoomGesturesEnabled = value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fun setScrollEnabled(value: Boolean) {
|
|
115
|
+
aMap.uiSettings.isScrollGesturesEnabled = value
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fun setRotateEnabled(value: Boolean) {
|
|
119
|
+
aMap.uiSettings.isRotateGesturesEnabled = value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fun setPitchEnabled(value: Boolean) {
|
|
123
|
+
aMap.uiSettings.isTiltGesturesEnabled = value
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fun destroy() {
|
|
127
|
+
if (didDestroy) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
didDestroy = true
|
|
132
|
+
reactContext.removeLifecycleEventListener(this)
|
|
133
|
+
markerByIdentifier.values.forEach { it.remove() }
|
|
134
|
+
markerByIdentifier.clear()
|
|
135
|
+
mapView.onDestroy()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
override fun onHostResume() {
|
|
139
|
+
if (!didDestroy) {
|
|
140
|
+
mapView.onResume()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override fun onHostPause() {
|
|
145
|
+
if (!didDestroy) {
|
|
146
|
+
mapView.onPause()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
override fun onHostDestroy() {
|
|
151
|
+
destroy()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
|
|
155
|
+
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
156
|
+
applyPendingInitialRegion()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private fun configureMap() {
|
|
160
|
+
aMap.uiSettings.isZoomControlsEnabled = false
|
|
161
|
+
|
|
162
|
+
aMap.setOnMapTouchListener { event ->
|
|
163
|
+
if (
|
|
164
|
+
event?.actionMasked == MotionEvent.ACTION_DOWN ||
|
|
165
|
+
event?.actionMasked == MotionEvent.ACTION_MOVE
|
|
166
|
+
) {
|
|
167
|
+
isGesture = true
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
aMap.setOnCameraChangeListener(
|
|
172
|
+
object : AMap.OnCameraChangeListener {
|
|
173
|
+
override fun onCameraChange(position: CameraPosition?) {
|
|
174
|
+
sendRegionEvent("topRegionChange", isGesture)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override fun onCameraChangeFinish(position: CameraPosition?) {
|
|
178
|
+
sendRegionEvent("topRegionChangeComplete", isGesture)
|
|
179
|
+
isGesture = false
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
aMap.setOnMarkerClickListener { marker ->
|
|
185
|
+
val identifier = marker.`object` as? String ?: return@setOnMarkerClickListener false
|
|
186
|
+
sendMarkerPressEvent(identifier, marker.position)
|
|
187
|
+
false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private fun applyPendingInitialRegion() {
|
|
192
|
+
val region = pendingInitialRegion ?: return
|
|
193
|
+
|
|
194
|
+
if (width <= 0 || height <= 0) {
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
didApplyInitialRegion = true
|
|
199
|
+
pendingInitialRegion = null
|
|
200
|
+
moveToRegion(region, animated = false)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private fun moveToRegion(region: MapRegion, animated: Boolean, duration: Int = 0) {
|
|
204
|
+
val applyCamera = Runnable {
|
|
205
|
+
val boundsUpdate = CameraUpdateFactory.newLatLngBounds(region.toBounds(), 0)
|
|
206
|
+
runCatching {
|
|
207
|
+
applyCameraUpdate(boundsUpdate, animated, duration)
|
|
208
|
+
}.onFailure {
|
|
209
|
+
val fallbackUpdate = CameraUpdateFactory.newLatLngZoom(
|
|
210
|
+
LatLng(region.latitude, region.longitude),
|
|
211
|
+
region.toApproximateZoom()
|
|
212
|
+
)
|
|
213
|
+
applyCameraUpdate(fallbackUpdate, animated, duration)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (width <= 0 || height <= 0) {
|
|
218
|
+
post(applyCamera)
|
|
219
|
+
} else {
|
|
220
|
+
applyCamera.run()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private fun applyCameraUpdate(update: CameraUpdate, animated: Boolean, duration: Int) {
|
|
225
|
+
if (animated) {
|
|
226
|
+
aMap.animateCamera(update, duration.toLong(), null)
|
|
227
|
+
} else {
|
|
228
|
+
aMap.moveCamera(update)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private fun sendRegionEvent(eventName: String, gesture: Boolean) {
|
|
233
|
+
if (id == NO_ID) {
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
currentRegion()?.let { region ->
|
|
238
|
+
UIManagerHelper.getEventDispatcher(reactContext)
|
|
239
|
+
?.dispatchEvent(RegionEvent(UIManagerHelper.getSurfaceId(this), id, eventName, region, gesture))
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun sendMarkerPressEvent(identifier: String, coordinate: LatLng) {
|
|
244
|
+
if (id == NO_ID) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
UIManagerHelper.getEventDispatcher(reactContext)
|
|
249
|
+
?.dispatchEvent(
|
|
250
|
+
MarkerPressEvent(
|
|
251
|
+
UIManagerHelper.getSurfaceId(this),
|
|
252
|
+
id,
|
|
253
|
+
identifier,
|
|
254
|
+
coordinate.latitude,
|
|
255
|
+
coordinate.longitude
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private fun currentRegion(): MapRegion? {
|
|
261
|
+
val bounds = runCatching {
|
|
262
|
+
aMap.projection.visibleRegion.latLngBounds
|
|
263
|
+
}.getOrNull() ?: return null
|
|
264
|
+
val target = aMap.cameraPosition.target ?: return null
|
|
265
|
+
val latitudeDelta = abs(bounds.northeast.latitude - bounds.southwest.latitude)
|
|
266
|
+
val longitudeDelta = abs(bounds.northeast.longitude - bounds.southwest.longitude)
|
|
267
|
+
|
|
268
|
+
return MapRegion(
|
|
269
|
+
latitude = target.latitude,
|
|
270
|
+
longitude = target.longitude,
|
|
271
|
+
latitudeDelta = latitudeDelta,
|
|
272
|
+
longitudeDelta = longitudeDelta
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private fun MapRegion.toBounds(): LatLngBounds {
|
|
277
|
+
val halfLat = max(latitudeDelta, MIN_DELTA) / 2.0
|
|
278
|
+
val halfLon = max(longitudeDelta, MIN_DELTA) / 2.0
|
|
279
|
+
val southwest = LatLng(
|
|
280
|
+
max(-90.0, latitude - halfLat),
|
|
281
|
+
max(-180.0, longitude - halfLon)
|
|
282
|
+
)
|
|
283
|
+
val northeast = LatLng(
|
|
284
|
+
min(90.0, latitude + halfLat),
|
|
285
|
+
min(180.0, longitude + halfLon)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
return LatLngBounds(southwest, northeast)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun MapRegion.toApproximateZoom(): Float {
|
|
292
|
+
val delta = max(max(latitudeDelta, longitudeDelta), MIN_DELTA)
|
|
293
|
+
val zoom = ln(360.0 / delta) / ln(2.0)
|
|
294
|
+
return zoom.toFloat().coerceIn(3f, 20f)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private fun markerHue(color: String): Float? {
|
|
298
|
+
return runCatching {
|
|
299
|
+
val hsv = FloatArray(3)
|
|
300
|
+
Color.colorToHSV(Color.parseColor(color), hsv)
|
|
301
|
+
hsv[0]
|
|
302
|
+
}.getOrNull()
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private fun ReadableMap.getOptionalString(key: String): String? {
|
|
306
|
+
return if (hasKey(key) && !isNull(key)) getString(key) else null
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private fun ReadableMap.getOptionalDouble(key: String): Double? {
|
|
310
|
+
return if (hasKey(key) && !isNull(key)) getDouble(key) else null
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private fun ReadableMap.getOptionalBoolean(key: String): Boolean? {
|
|
314
|
+
return if (hasKey(key) && !isNull(key)) getBoolean(key) else null
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private class RegionEvent(
|
|
318
|
+
surfaceId: Int,
|
|
319
|
+
viewId: Int,
|
|
320
|
+
private val rnEventName: String,
|
|
321
|
+
private val region: MapRegion,
|
|
322
|
+
private val isGesture: Boolean
|
|
323
|
+
) : Event<RegionEvent>(surfaceId, viewId) {
|
|
324
|
+
override fun getEventName(): String = rnEventName
|
|
325
|
+
|
|
326
|
+
override fun getEventData(): WritableMap =
|
|
327
|
+
Arguments.createMap().apply {
|
|
328
|
+
putMap(
|
|
329
|
+
"region",
|
|
330
|
+
Arguments.createMap().apply {
|
|
331
|
+
putDouble("latitude", region.latitude)
|
|
332
|
+
putDouble("longitude", region.longitude)
|
|
333
|
+
putDouble("latitudeDelta", region.latitudeDelta)
|
|
334
|
+
putDouble("longitudeDelta", region.longitudeDelta)
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
putBoolean("isGesture", isGesture)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private class MarkerPressEvent(
|
|
342
|
+
surfaceId: Int,
|
|
343
|
+
viewId: Int,
|
|
344
|
+
private val identifier: String,
|
|
345
|
+
private val latitude: Double,
|
|
346
|
+
private val longitude: Double
|
|
347
|
+
) : Event<MarkerPressEvent>(surfaceId, viewId) {
|
|
348
|
+
override fun getEventName(): String = "topMarkerPress"
|
|
349
|
+
|
|
350
|
+
override fun canCoalesce(): Boolean = false
|
|
351
|
+
|
|
352
|
+
override fun getEventData(): WritableMap =
|
|
353
|
+
Arguments.createMap().apply {
|
|
354
|
+
putString("identifier", identifier)
|
|
355
|
+
putMap(
|
|
356
|
+
"coordinate",
|
|
357
|
+
Arguments.createMap().apply {
|
|
358
|
+
putDouble("latitude", latitude)
|
|
359
|
+
putDouble("longitude", longitude)
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private companion object {
|
|
366
|
+
private const val MIN_DELTA = 0.000001
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.cnmaps
|
|
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.SimpleViewManager
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
9
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
10
|
+
import com.facebook.react.viewmanagers.RNMapsMapViewManagerDelegate
|
|
11
|
+
import com.facebook.react.viewmanagers.RNMapsMapViewManagerInterface
|
|
12
|
+
|
|
13
|
+
@ReactModule(name = MapViewManager.REACT_CLASS)
|
|
14
|
+
class MapViewManager : SimpleViewManager<MapView>(),
|
|
15
|
+
RNMapsMapViewManagerInterface<MapView> {
|
|
16
|
+
private val delegate: ViewManagerDelegate<MapView> = RNMapsMapViewManagerDelegate(this)
|
|
17
|
+
|
|
18
|
+
override fun getDelegate(): ViewManagerDelegate<MapView> = delegate
|
|
19
|
+
|
|
20
|
+
override fun getName(): String = REACT_CLASS
|
|
21
|
+
|
|
22
|
+
public override fun createViewInstance(context: ThemedReactContext): MapView {
|
|
23
|
+
return MapView(context)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun onDropViewInstance(view: MapView) {
|
|
27
|
+
view.destroy()
|
|
28
|
+
super.onDropViewInstance(view)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@ReactProp(name = "provider")
|
|
32
|
+
override fun setProvider(view: MapView, value: String?) {
|
|
33
|
+
// Fabric schema reserves future providers; the current native implementation is AMap only.
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@ReactProp(name = "coordinateSystem")
|
|
37
|
+
override fun setCoordinateSystem(view: MapView, value: String?) {
|
|
38
|
+
// JS normalizes coordinates before they cross the native boundary.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@ReactProp(name = "initialRegion")
|
|
42
|
+
override fun setInitialRegion(view: MapView, value: ReadableMap?) {
|
|
43
|
+
view.setInitialRegion(value?.toMapRegion())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@ReactProp(name = "region")
|
|
47
|
+
override fun setRegion(view: MapView, value: ReadableMap?) {
|
|
48
|
+
view.setRegion(value?.toMapRegion())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@ReactProp(name = "markers")
|
|
52
|
+
override fun setMarkers(view: MapView, value: ReadableArray?) {
|
|
53
|
+
view.setMarkers(value)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@ReactProp(name = "showsUserLocation")
|
|
57
|
+
override fun setShowsUserLocation(view: MapView, value: Boolean) {
|
|
58
|
+
view.setShowsUserLocation(value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@ReactProp(name = "zoomEnabled")
|
|
62
|
+
override fun setZoomEnabled(view: MapView, value: Boolean) {
|
|
63
|
+
view.setZoomEnabled(value)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@ReactProp(name = "scrollEnabled")
|
|
67
|
+
override fun setScrollEnabled(view: MapView, value: Boolean) {
|
|
68
|
+
view.setScrollEnabled(value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@ReactProp(name = "rotateEnabled")
|
|
72
|
+
override fun setRotateEnabled(view: MapView, value: Boolean) {
|
|
73
|
+
view.setRotateEnabled(value)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@ReactProp(name = "pitchEnabled")
|
|
77
|
+
override fun setPitchEnabled(view: MapView, value: Boolean) {
|
|
78
|
+
view.setPitchEnabled(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override fun animateToRegion(
|
|
82
|
+
view: MapView,
|
|
83
|
+
latitude: Double,
|
|
84
|
+
longitude: Double,
|
|
85
|
+
latitudeDelta: Double,
|
|
86
|
+
longitudeDelta: Double,
|
|
87
|
+
duration: Int
|
|
88
|
+
) {
|
|
89
|
+
view.animateToRegion(
|
|
90
|
+
MapRegion(
|
|
91
|
+
latitude = latitude,
|
|
92
|
+
longitude = longitude,
|
|
93
|
+
latitudeDelta = latitudeDelta,
|
|
94
|
+
longitudeDelta = longitudeDelta
|
|
95
|
+
),
|
|
96
|
+
duration
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private fun ReadableMap.toMapRegion(): MapRegion {
|
|
101
|
+
return MapRegion(
|
|
102
|
+
latitude = getDouble("latitude"),
|
|
103
|
+
longitude = getDouble("longitude"),
|
|
104
|
+
latitudeDelta = getDouble("latitudeDelta"),
|
|
105
|
+
longitudeDelta = getDouble("longitudeDelta")
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
companion object {
|
|
110
|
+
const val REACT_CLASS = "RNMapsMapView"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.cnmaps
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager
|
|
8
|
+
|
|
9
|
+
class MapsPackage : BaseReactPackage() {
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
11
|
+
return listOf(MapViewManager())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
15
|
+
|
|
16
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { emptyMap() }
|
|
17
|
+
}
|