├── .github └── workflows │ ├── build-docs.yml │ ├── build.yml │ └── release.yml ├── .gitignore ├── example-app ├── components │ └── switch-with-label.tsx ├── home.tsx ├── images │ ├── flag.png │ └── point.png ├── index.tsx ├── screens │ ├── animated.tsx │ ├── circle.tsx │ ├── cluster.tsx │ ├── controls.tsx │ ├── events.tsx │ ├── gestures.tsx │ ├── heat-map.tsx │ ├── index.ts │ ├── layers.tsx │ ├── map-types.tsx │ ├── marker-dynamic.tsx │ ├── marker-icon.tsx │ ├── marker.tsx │ ├── multi-point.tsx │ ├── offline.js │ ├── polygon.tsx │ └── polyline.tsx └── types.ts ├── index.js ├── lib ├── android │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── qiuxiang │ │ └── amap3d │ │ ├── AMap3DPackage.kt │ │ ├── Utils.kt │ │ ├── map_view │ │ ├── Circle.kt │ │ ├── CircleManager.kt │ │ ├── HeatMap.kt │ │ ├── HeatMapManager.kt │ │ ├── MapView.kt │ │ ├── MapViewManager.kt │ │ ├── Marker.kt │ │ ├── MarkerManager.kt │ │ ├── MultiPoint.kt │ │ ├── MultiPointManager.kt │ │ ├── Overlay.kt │ │ ├── Polygon.kt │ │ ├── PolygonManager.kt │ │ ├── Polyline.kt │ │ └── PolylineManager.kt │ │ └── modules │ │ └── SdkModule.kt ├── ios │ ├── .swiftformat │ ├── Bridging-Header.h │ ├── MapView │ │ ├── CircleManager.m │ │ ├── CircleManager.swift │ │ ├── HeatMapManager.m │ │ ├── HeatMapManager.swift │ │ ├── MapViewManager.m │ │ ├── MapViewManager.swift │ │ ├── MarkerManager.m │ │ ├── MarkerManager.swift │ │ ├── MultiPointManager.m │ │ ├── MultiPointManager.swift │ │ ├── Overlay.swift │ │ ├── PolygonManager.m │ │ ├── PolygonManager.swift │ │ ├── PolylineManager.m │ │ └── PolylineManager.swift │ ├── Modules │ │ ├── SdkModule.m │ │ └── SdkModule.swift │ ├── Utils.swift │ └── react-native-amap3d.podspec └── src │ ├── circle.tsx │ ├── cluster │ ├── cluster-view.tsx │ └── index.tsx │ ├── component.ts │ ├── heat-map.tsx │ ├── index.ts │ ├── map-view.tsx │ ├── marker.tsx │ ├── multi-point.tsx │ ├── polygon.tsx │ ├── polyline.tsx │ ├── sdk.ts │ └── types.ts ├── license ├── package.json ├── react-native-amap3d.podspec ├── react-native.config.js ├── readme.md └── tsconfig.json /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: build-docs 2 | on: 3 | push: 4 | branches: [main] 5 | jobs: 6 | build-docs: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: write 10 | steps: 11 | - uses: actions/checkout@v3 12 | - run: npm i typescript typedoc 13 | - run: npx typedoc --out docs/api --skipErrorChecking lib/src 14 | - uses: JamesIves/github-pages-deploy-action@v4 15 | with: 16 | folder: docs 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - run: npm pack 9 | - run: npx react-native init example 10 | - run: cp index.js example/ 11 | - run: cp -r example-app example/ 12 | - run: npm i ../react-native-amap3d-0.0.0.tgz 13 | working-directory: example 14 | - run: npm i @react-native-picker/picker @react-navigation/native @react-navigation/native-stack react-native-safe-area-context react-native-screens 15 | working-directory: example 16 | - run: sed -i.backup -r 's/(enableSeparateBuildPerCPUArchitecture = )false/\1true/' build.gradle 17 | working-directory: example/android/app 18 | - run: ./gradlew assembleRelease 19 | working-directory: example/android 20 | - uses: actions/upload-artifact@v3 21 | with: 22 | name: example.apk 23 | path: example/android/app/build/outputs/apk/release/app-arm64-v8a-release.apk 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: bruceadams/get-release@v1.3.2 11 | id: release 12 | env: 13 | GITHUB_TOKEN: ${{ github.token }} 14 | - uses: actions/setup-node@v3 15 | with: 16 | registry-url: "https://registry.npmjs.org" 17 | - run: npm version ${{ steps.release.outputs.tag_name }} --no-git-tag-version 18 | - run: npm publish 19 | env: 20 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 21 | build-example: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: npm pack 26 | - run: npx react-native init example 27 | - run: cp index.js example/ 28 | - run: cp -r example-app example/ 29 | - run: npm i ../react-native-amap3d-0.0.0.tgz 30 | working-directory: example 31 | - run: npm i @react-native-picker/picker @react-navigation/native @react-navigation/native-stack react-native-safe-area-context react-native-screens 32 | working-directory: example 33 | - run: sed -i.backup -r 's/(enableSeparateBuildPerCPUArchitecture = )false/\1true/' build.gradle 34 | working-directory: example/android/app 35 | - run: ./gradlew assembleRelease 36 | working-directory: example/android 37 | - uses: svenstaro/upload-release-action@v2 38 | with: 39 | file: example/android/app/build/outputs/apk/release/app-*-release.apk 40 | file_glob: true 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | node_modules/ 4 | build/ 5 | xcuserdata/ 6 | Pods/ 7 | lib/js/ 8 | /android/ 9 | /ios/ 10 | 11 | *.iml 12 | *.xcworkspace 13 | local.properties 14 | Podfile.lock 15 | package-lock.json 16 | yarn.lock 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /example-app/components/switch-with-label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useTheme } from "@react-navigation/native"; 3 | import { StyleSheet, Switch, Text, View } from "react-native"; 4 | 5 | interface Props { 6 | onChange: (value: boolean) => void; 7 | value: boolean; 8 | label: string; 9 | vertical?: boolean; 10 | } 11 | 12 | export default ({ onChange, value, label }: Props) => { 13 | const { colors } = useTheme(); 14 | return ( 15 | 16 | 17 | 18 | {label} 19 | 20 | ); 21 | }; 22 | 23 | const style = StyleSheet.create({ 24 | control: { 25 | alignItems: "center", 26 | flex: 1, 27 | paddingTop: 8, 28 | paddingBottom: 8, 29 | }, 30 | separator: { width: 8, height: 8 }, 31 | }); 32 | -------------------------------------------------------------------------------- /example-app/home.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigation, useTheme } from "@react-navigation/native"; 2 | import * as React from "react"; 3 | import { 4 | Platform, 5 | ScrollView, 6 | StyleSheet, 7 | Text, 8 | TouchableNativeFeedback, 9 | TouchableOpacity, 10 | View, 11 | } from "react-native"; 12 | import { AMapSdk } from "react-native-amap3d"; 13 | import { Constructor } from "react-native/private/Utilities"; 14 | import screens from "./screens"; 15 | import { NavigationProps, ScreenName } from "./types"; 16 | 17 | let Touchable: Constructor = TouchableOpacity; 18 | if (Platform.OS === "android") { 19 | Touchable = TouchableNativeFeedback; 20 | } 21 | 22 | export default () => { 23 | const navigation = useNavigation(); 24 | React.useEffect(() => { 25 | AMapSdk.init( 26 | Platform.select({ 27 | android: "c52c7169e6df23490e3114330098aaac", 28 | ios: "186d3464209b74effa4d8391f441f14d", 29 | }) 30 | ); 31 | AMapSdk.getVersion().then((version) => { 32 | navigation.setOptions({ headerRight: () => v{version} }); 33 | }); 34 | }, []); 35 | return ( 36 | 37 | {Object.keys(screens).map((i) => ( 38 | 39 | ))} 40 | 41 | ); 42 | }; 43 | 44 | function Item({ name }: { name: ScreenName }) { 45 | const { colors } = useTheme(); 46 | const navigation = useNavigation(); 47 | return ( 48 | navigation.push(name)}> 49 | 50 | {name} 51 | 52 | 53 | ); 54 | } 55 | 56 | const style = StyleSheet.create({ 57 | item: { padding: 16 }, 58 | itemText: { fontSize: 16 }, 59 | }); 60 | -------------------------------------------------------------------------------- /example-app/images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuxiang/react-native-amap3d/da9ca7525b2ce62a441989fd509389cbcfb5e155/example-app/images/flag.png -------------------------------------------------------------------------------- /example-app/images/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiuxiang/react-native-amap3d/da9ca7525b2ce62a441989fd509389cbcfb5e155/example-app/images/point.png -------------------------------------------------------------------------------- /example-app/index.tsx: -------------------------------------------------------------------------------- 1 | import { DarkTheme, DefaultTheme, NavigationContainer } from "@react-navigation/native"; 2 | import { createNativeStackNavigator } from "@react-navigation/native-stack"; 3 | import * as React from "react"; 4 | import { useColorScheme } from "react-native"; 5 | import screens from "./screens"; 6 | import Home from "./home"; 7 | 8 | const Stack = createNativeStackNavigator(); 9 | 10 | export default () => { 11 | const scheme = useColorScheme(); 12 | return ( 13 | 14 | 15 | 16 | {Object.keys(screens).map((name) => ( 17 | // @ts-ignore 18 | 19 | ))} 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /example-app/screens/animated.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Dimensions, PixelRatio, StyleSheet, Text, TouchableOpacity, View } from "react-native"; 3 | import { MapView } from "react-native-amap3d"; 4 | 5 | let mapView: MapView; 6 | 7 | export default () => ( 8 | 9 | (mapView = ref)} style={style.body} /> 10 | 11 | 12 | { 14 | console.log(Dimensions.get("window").width); 15 | mapView?.moveCamera( 16 | { 17 | tilt: 45, 18 | bearing: 90, 19 | zoom: 18, 20 | target: { latitude: 39.97837, longitude: 116.31363 }, 21 | }, 22 | 1000 23 | ); 24 | }} 25 | > 26 | 中关村 27 | 28 | 29 | 30 | { 32 | mapView?.moveCamera( 33 | { 34 | tilt: 0, 35 | bearing: 0, 36 | zoom: 16, 37 | target: { latitude: 39.90864, longitude: 116.39745 }, 38 | }, 39 | 1000 40 | ); 41 | }} 42 | > 43 | 天安门 44 | 45 | 46 | 47 | 48 | ); 49 | 50 | const style = StyleSheet.create({ 51 | body: { flex: 1 }, 52 | buttons: { 53 | width: "100%", 54 | position: "absolute", 55 | flexDirection: "row", 56 | justifyContent: "center", 57 | }, 58 | button: { 59 | paddingVertical: 8, 60 | paddingHorizontal: 16, 61 | margin: 16, 62 | borderRadius: 50, 63 | backgroundColor: "rgba(255, 255, 255, 0.9)", 64 | }, 65 | text: { fontSize: 16, color: "#000" }, 66 | }); 67 | -------------------------------------------------------------------------------- /example-app/screens/circle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StyleSheet } from "react-native"; 3 | import { Circle, MapView } from "react-native-amap3d"; 4 | 5 | export default () => ( 6 | 7 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /example-app/screens/cluster.tsx: -------------------------------------------------------------------------------- 1 | import { CameraEvent } from "lib/src/map-view"; 2 | import * as React from "react"; 3 | import { Component } from "react"; 4 | import { Cluster, MapView, Marker } from "react-native-amap3d"; 5 | 6 | export default class Clustering extends Component { 7 | static navigationOptions = { title: "Marker clustering" }; 8 | status?: CameraEvent; 9 | cluster?: Cluster | null; 10 | mapView?: MapView | null; 11 | state = { markers: generateMarkers() }; 12 | 13 | render() { 14 | return ( 15 | (this.mapView = ref)} 17 | onLoad={() => this.mapView?.moveCamera({ zoom: 8 }, 100)} 18 | onCameraIdle={({ nativeEvent }) => { 19 | this.status = nativeEvent; 20 | this.cluster?.update(nativeEvent); 21 | }} 22 | onLongPress={() => this.setState({ markers: generateMarkers() })} 23 | > 24 | { 26 | this.mapView?.moveCamera( 27 | { 28 | target: position, 29 | zoom: this.status!.cameraPosition.zoom! + 1, 30 | }, 31 | 200 32 | ); 33 | }} 34 | ref={(ref) => (this.cluster = ref)} 35 | points={this.state.markers} 36 | renderMarker={(item) => ( 37 | 42 | )} 43 | /> 44 | 45 | ); 46 | } 47 | } 48 | 49 | function generateMarkers(count = 1000) { 50 | return Array(count) 51 | .fill(0) 52 | .map((_, i) => ({ 53 | position: { latitude: 39.5 + Math.random(), longitude: 116 + Math.random() }, 54 | properties: { key: `Marker${i}` }, 55 | })); 56 | } 57 | -------------------------------------------------------------------------------- /example-app/screens/controls.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@react-navigation/native"; 2 | import * as React from "react"; 3 | import { View } from "react-native"; 4 | import { MapView } from "react-native-amap3d"; 5 | import SwitchWithLabel from "../components/switch-with-label"; 6 | 7 | export default () => { 8 | const { colors } = useTheme(); 9 | const [compassEnabled, setCompassEnabled] = React.useState(false); 10 | const [scaleControlsEnabled, setScaleControlsEnabled] = React.useState(true); 11 | const [zoomControlsEnabled, setZoomControlsEnabled] = React.useState(true); 12 | const [myLocationButtonEnabled, setMyLocationButtonEnabled] = React.useState(false); 13 | const props = { 14 | compassEnabled, 15 | scaleControlsEnabled, 16 | zoomControlsEnabled, 17 | myLocationButtonEnabled, 18 | }; 19 | return ( 20 | <> 21 | 22 | 23 | 24 | 29 | 34 | 39 | 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /example-app/screens/events.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | FlatList, 4 | ListRenderItemInfo, 5 | NativeSyntheticEvent, 6 | PermissionsAndroid, 7 | Platform, 8 | StyleSheet, 9 | Text, 10 | View, 11 | } from "react-native"; 12 | import { MapView } from "react-native-amap3d"; 13 | 14 | export default class extends React.Component { 15 | state = { logs: [] }; 16 | 17 | async componentDidMount() { 18 | if (Platform.OS == "android") { 19 | console.log( 20 | await PermissionsAndroid.requestMultiple([ 21 | PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, 22 | ]) 23 | ); 24 | } 25 | } 26 | 27 | log(event: string, data: any) { 28 | console.log(data); 29 | this.setState({ 30 | logs: [ 31 | { 32 | key: Date.now().toString(), 33 | time: new Date().toLocaleString(), 34 | event, 35 | data: JSON.stringify(data, null, 2), 36 | }, 37 | ...this.state.logs, 38 | ], 39 | }); 40 | } 41 | 42 | logger(name: string) { 43 | return ({ nativeEvent }: NativeSyntheticEvent) => this.log(name, nativeEvent); 44 | } 45 | 46 | renderItem = ({ item }: ListRenderItemInfo) => ( 47 | 48 | {item.time} {item.event}: {item.data} 49 | 50 | ); 51 | 52 | render() { 53 | const events = ["onLoad", "onPress", "onPressPoi", "onLongPress", "onCameraIdle", "onLocation"]; 54 | return ( 55 | 56 | [i, this.logger(i)]))} 59 | distanceFilter={10} 60 | headingFilter={90} 61 | myLocationEnabled 62 | myLocationButtonEnabled 63 | /> 64 | 65 | 66 | ); 67 | } 68 | } 69 | 70 | const style = StyleSheet.create({ 71 | body: { flex: 1 }, 72 | logs: { elevation: 8, flex: 1 }, 73 | logText: { 74 | fontFamily: Platform.OS === "ios" ? "menlo" : "monospace", 75 | fontSize: 12, 76 | paddingLeft: 15, 77 | paddingRight: 15, 78 | paddingTop: 10, 79 | paddingBottom: 10, 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /example-app/screens/gestures.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@react-navigation/native"; 2 | import * as React from "react"; 3 | import { View } from "react-native"; 4 | import { MapView } from "react-native-amap3d"; 5 | import SwitchWithLabel from "../components/switch-with-label"; 6 | 7 | export default () => { 8 | const { colors } = useTheme(); 9 | const [zoomGesturesEnabled, setZoomGesturesEnabled] = React.useState(false); 10 | const [scrollGesturesEnabled, setScrollGesturesEnabled] = React.useState(false); 11 | const [rotateGesturesEnabled, setRotateGesturesEnabled] = React.useState(false); 12 | const [tiltGesturesEnabled, setTiltGesturesEnabled] = React.useState(false); 13 | const props = { 14 | zoomGesturesEnabled, 15 | scrollGesturesEnabled, 16 | rotateGesturesEnabled, 17 | tiltGesturesEnabled, 18 | }; 19 | return ( 20 | <> 21 | 22 | 23 | 28 | 33 | 38 | 43 | 44 | 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /example-app/screens/heat-map.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StyleSheet } from "react-native"; 3 | import { HeatMap, MapView } from "react-native-amap3d"; 4 | 5 | const coordinates = new Array(200).fill(0).map(() => ({ 6 | latitude: 39.5 + Math.random(), 7 | longitude: 116 + Math.random(), 8 | })); 9 | 10 | export default () => ( 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /example-app/screens/index.ts: -------------------------------------------------------------------------------- 1 | import Animated from "./animated"; 2 | import Circle from "./circle"; 3 | import Cluster from "./cluster"; 4 | import Controls from "./controls"; 5 | import Events from "./events"; 6 | import Gestures from "./gestures"; 7 | import HeatMap from "./heat-map"; 8 | import Layers from "./layers"; 9 | import MapTypes from "./map-types"; 10 | import Marker from "./marker"; 11 | import MarkerDynamic from "./marker-dynamic"; 12 | import MarkerIcon from "./marker-icon"; 13 | import MultiPoint from "./multi-point"; 14 | import Polygon from "./polygon"; 15 | import Polyline from "./polyline"; 16 | 17 | export default { 18 | 地图模式切换: MapTypes, 19 | "3D 建筑、路况、室内地图": Layers, 20 | "指南针、比例尺、缩放按钮、定位按钮": Controls, 21 | 手势控制: Gestures, 22 | 动画移动: Animated, 23 | 地图事件: Events, 24 | "添加标记,支持拖拽": Marker, 25 | 动态添加移除标记: MarkerDynamic, 26 | 更多自定义标记图标: MarkerIcon, 27 | 点聚合: Cluster, 28 | 绘制折线: Polyline, 29 | 绘制多边形: Polygon, 30 | 绘制圆形: Circle, 31 | 热力图: HeatMap, 32 | 海量点: MultiPoint, 33 | }; 34 | -------------------------------------------------------------------------------- /example-app/screens/layers.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@react-navigation/native"; 2 | import * as React from "react"; 3 | import { View } from "react-native"; 4 | import { MapView } from "react-native-amap3d"; 5 | import SwitchWithLabel from "../components/switch-with-label"; 6 | 7 | export default () => { 8 | const { colors } = useTheme(); 9 | const [buildingsEnabled, setBuildingsEnabled] = React.useState(true); 10 | const [trafficEnabled, setTrafficEnabled] = React.useState(false); 11 | const [indoorViewEnabled, setIndoorViewEnabled] = React.useState(false); 12 | const target = { latitude: 39.9098, longitude: 116.37296 }; 13 | const initialCameraPosition = { target, zoom: 18, tilt: 45 }; 14 | const props = { buildingsEnabled, trafficEnabled, indoorViewEnabled, initialCameraPosition }; 15 | return ( 16 | <> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /example-app/screens/map-types.tsx: -------------------------------------------------------------------------------- 1 | import { Picker } from "@react-native-picker/picker"; 2 | import * as React from "react"; 3 | import { useState } from "react"; 4 | import { StyleSheet, View } from "react-native"; 5 | import { MapType, MapView } from "react-native-amap3d"; 6 | 7 | export default () => { 8 | const [mapType, setMapType] = useState(MapType.Standard); 9 | return ( 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /example-app/screens/marker-dynamic.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { LatLng, MapView, Marker } from "react-native-amap3d"; 3 | 4 | export default () => { 5 | const [markers, setMarkers] = React.useState(Array()); 6 | React.useEffect(() => alert("点击地图添加 Marker,点击 Marker 移除"), []); 7 | console.log(markers); 8 | return ( 9 | { 11 | setMarkers([...markers, nativeEvent]); 12 | }} 13 | > 14 | {markers.map((position) => ( 15 | { 20 | markers.splice(markers.indexOf(position), 1); 21 | setMarkers([...markers]); 22 | }} 23 | /> 24 | ))} 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /example-app/screens/marker-icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useEffect, useState } from "react"; 3 | import { StyleSheet, Text } from "react-native"; 4 | import { MapView, Marker } from "react-native-amap3d"; 5 | 6 | export default () => { 7 | const [time, setTime] = useState(new Date()); 8 | 9 | useEffect(() => { 10 | const timer = setInterval(() => setTime(new Date()), 1000); 11 | return () => clearInterval(timer); 12 | }, []); 13 | 14 | return ( 15 | 16 | alert("onPress")} 19 | icon={require("../images/flag.png")} 20 | /> 21 | alert("onPress")} 23 | position={{ latitude: 39.806901, longitude: 116.297972 }} 24 | icon={{ 25 | uri: "https://reactnative.dev/img/pwa/manifest-icon-512.png", 26 | width: 64, 27 | height: 64, 28 | }} 29 | /> 30 | alert("onPress")} 33 | > 34 | {time.toLocaleString()} 35 | 36 | 37 | ); 38 | }; 39 | 40 | const style = StyleSheet.create({ 41 | customView: { 42 | color: "#fff", 43 | backgroundColor: "#009688", 44 | alignItems: "center", 45 | borderRadius: 5, 46 | padding: 5, 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /example-app/screens/marker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { MapView, Marker } from "react-native-amap3d"; 3 | 4 | export default () => ( 5 | 6 | alert("onPress")} 11 | onDragEnd={({ nativeEvent }) => 12 | alert(`onDragEnd: ${nativeEvent.latitude}, ${nativeEvent.longitude}`) 13 | } 14 | /> 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /example-app/screens/multi-point.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StyleSheet } from "react-native"; 3 | import { MapView, MultiPoint } from "react-native-amap3d"; 4 | 5 | const points = Array(1000) 6 | .fill(0) 7 | .map(() => ({ 8 | latitude: 39.5 + Math.random(), 9 | longitude: 116 + Math.random(), 10 | })); 11 | 12 | export default () => ( 13 | 14 | alert(nativeEvent.index)} 18 | /> 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /example-app/screens/offline.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react' 2 | import { Offline } from 'react-native-amap3d' 3 | 4 | // TODO: 提供完整的离线地图示例 5 | export default class IndoorExample extends Component { 6 | static navigationOptions = { 7 | title: '离线地图', 8 | } 9 | 10 | async componentDidMount() { 11 | console.log(await Offline.getProvinces()) 12 | Offline.addDownloadListener(data => console.log(data)) 13 | Offline.remove('香港特别行政区') 14 | Offline.download('香港特别行政区') 15 | } 16 | 17 | render() { 18 | return null 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example-app/screens/polygon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { MapView, Polygon } from "react-native-amap3d"; 3 | 4 | const points = [ 5 | { 6 | latitude: 39.806901, 7 | longitude: 116.397972, 8 | }, 9 | { 10 | latitude: 39.806901, 11 | longitude: 116.297972, 12 | }, 13 | { 14 | latitude: 39.906901, 15 | longitude: 116.397972, 16 | }, 17 | ]; 18 | 19 | export default () => ( 20 | 21 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /example-app/screens/polyline.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StyleSheet } from "react-native"; 3 | import { MapView, Polyline } from "react-native-amap3d"; 4 | 5 | const line1 = [ 6 | { latitude: 40.006901, longitude: 116.097972 }, 7 | { latitude: 40.006901, longitude: 116.597972 }, 8 | ]; 9 | 10 | const line2 = [ 11 | { latitude: 39.906901, longitude: 116.097972 }, 12 | { latitude: 39.906901, longitude: 116.597972 }, 13 | ]; 14 | 15 | const line3 = [ 16 | { latitude: 39.806901, longitude: 116.097972 }, 17 | { latitude: 39.806901, longitude: 116.257972 }, 18 | { latitude: 39.806901, longitude: 116.457972 }, 19 | { latitude: 39.806901, longitude: 116.597972 }, 20 | ]; 21 | 22 | export default () => ( 23 | 24 | 25 | 26 | alert("onPress")} 30 | points={line3} 31 | gradient 32 | /> 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /example-app/types.ts: -------------------------------------------------------------------------------- 1 | import { NativeStackNavigationProp, NativeStackScreenProps } from "@react-navigation/native-stack"; 2 | import screens from "./screens"; 3 | 4 | export type ScreenName = keyof typeof screens; 5 | type ParamList = { [key in ScreenName]: undefined }; 6 | export type ScreenProps = NativeStackScreenProps; 7 | export type NavigationProps = NativeStackNavigationProp; 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from "react-native"; 2 | import app from "./example-app"; 3 | 4 | AppRegistry.registerComponent("example", () => app); 5 | -------------------------------------------------------------------------------- /lib/android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | 4 | def getExt(prop, fallback) { 5 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 6 | } 7 | 8 | buildscript { 9 | ext.kotlin_version = '1.7.21' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 17 | } 18 | } 19 | 20 | android { 21 | compileSdkVersion getExt('compileSdkVersion', 33) 22 | buildToolsVersion getExt('buildToolsVersion', '33.0.0') 23 | 24 | defaultConfig { 25 | minSdkVersion getExt('minSdkVersion', 21) 26 | targetSdkVersion getExt('targetSdkVersion', 33) 27 | } 28 | } 29 | 30 | dependencies { 31 | compileOnly 'com.facebook.react:react-native:+' 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 33 | implementation 'com.amap.api:3dmap:9.6.0' 34 | } 35 | -------------------------------------------------------------------------------- /lib/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/AMap3DPackage.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | import qiuxiang.amap3d.map_view.* 8 | import qiuxiang.amap3d.modules.SdkModule 9 | 10 | class AMap3DPackage : ReactPackage { 11 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 12 | return listOf( 13 | SdkModule(reactContext), 14 | ) 15 | } 16 | 17 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 18 | return listOf( 19 | MapViewManager(), 20 | MarkerManager(), 21 | PolylineManager(), 22 | PolygonManager(), 23 | CircleManager(), 24 | HeatMapManager(), 25 | MultiPointManager() 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/Utils.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d 2 | 3 | import android.content.res.Resources 4 | import android.graphics.Bitmap 5 | import android.graphics.Point 6 | import android.location.Location 7 | import android.view.View 8 | import com.amap.api.maps.model.* 9 | import com.facebook.drawee.backends.pipeline.Fresco 10 | import com.facebook.imagepipeline.common.ResizeOptions 11 | import com.facebook.imagepipeline.request.BasePostprocessor 12 | import com.facebook.imagepipeline.request.ImageRequestBuilder 13 | import com.facebook.react.bridge.Arguments 14 | import com.facebook.react.bridge.ReadableArray 15 | import com.facebook.react.bridge.ReadableMap 16 | import com.facebook.react.bridge.WritableMap 17 | import com.facebook.react.views.imagehelper.ImageSource 18 | 19 | fun Float.toPx(): Int { 20 | return (this * Resources.getSystem().displayMetrics.density).toInt() 21 | } 22 | 23 | fun Int.toPx(): Int { 24 | return (this * Resources.getSystem().displayMetrics.density).toInt() 25 | } 26 | 27 | fun ReadableMap.toPoint(): Point { 28 | return Point(getDouble("x").toFloat().toPx(), getDouble("y").toFloat().toPx()) 29 | } 30 | 31 | fun ReadableMap.toLatLng(): LatLng { 32 | return LatLng(getDouble("latitude"), getDouble("longitude")) 33 | } 34 | 35 | fun ReadableArray.toLatLngList(): List { 36 | return (0 until size()).map { 37 | // @todo 暂时兼容 0.63 38 | @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") 39 | getMap(it)!!.toLatLng() 40 | } 41 | } 42 | 43 | fun LatLng.toJson(): WritableMap { 44 | return Arguments.createMap().apply { 45 | putDouble("latitude", latitude) 46 | putDouble("longitude", longitude) 47 | } 48 | } 49 | 50 | fun Poi.toJson(): WritableMap { 51 | return Arguments.createMap().apply { 52 | putMap("position", coordinate.toJson()) 53 | putString("id", poiId) 54 | putString("name", name) 55 | } 56 | } 57 | 58 | fun CameraPosition.toJson(): WritableMap { 59 | return Arguments.createMap().apply { 60 | putMap("target", target.toJson()) 61 | putDouble("zoom", zoom.toDouble()) 62 | putDouble("tilt", tilt.toDouble()) 63 | putDouble("bearing", bearing.toDouble()) 64 | } 65 | } 66 | 67 | fun Location.toJson(): WritableMap { 68 | return Arguments.createMap().apply { 69 | putDouble("timestamp", time.toDouble()) 70 | putMap("coords", Arguments.createMap().apply { 71 | putDouble("latitude", latitude) 72 | putDouble("longitude", longitude) 73 | putDouble("latitude", latitude) 74 | putDouble("accuracy", accuracy.toDouble()) 75 | putDouble("heading", bearing.toDouble()) 76 | putDouble("speed", speed.toDouble()) 77 | }) 78 | } 79 | } 80 | 81 | fun LatLngBounds.toJson(): WritableMap { 82 | return Arguments.createMap().apply { 83 | putMap("southwest", southwest.toJson()) 84 | putMap("northeast", northeast.toJson()) 85 | } 86 | } 87 | 88 | fun ReadableMap.getFloat(key: String): Float? { 89 | if (hasKey(key)) return getDouble(key).toFloat() 90 | return null 91 | } 92 | 93 | fun getEventTypeConstants(vararg list: String): Map { 94 | return list.associateWith { mapOf("phasedRegistrationNames" to mapOf("bubbled" to it)) } 95 | } 96 | 97 | fun View.fetchImage(source: ReadableMap, callback: (BitmapDescriptor) -> Unit) { 98 | val uri = ImageSource(context, source.getString("uri")).uri 99 | val request = ImageRequestBuilder.newBuilderWithSource(uri).let { 100 | it.postprocessor = object : BasePostprocessor() { 101 | override fun process(bitmap: Bitmap) { 102 | callback(BitmapDescriptorFactory.fromBitmap(bitmap)) 103 | } 104 | } 105 | if (source.hasKey("width") && source.hasKey("height")) { 106 | it.resizeOptions = ResizeOptions.forDimensions( 107 | source.getInt("width").toPx(), 108 | source.getInt("height").toPx() 109 | ) 110 | } 111 | it.build() 112 | } 113 | Fresco.getImagePipeline().fetchDecodedImage(request, this) 114 | } -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/Circle.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import com.amap.api.maps.AMap 6 | import com.amap.api.maps.model.Circle 7 | import com.amap.api.maps.model.CircleOptions 8 | import com.amap.api.maps.model.LatLng 9 | import com.facebook.react.views.view.ReactViewGroup 10 | 11 | class Circle(context: Context) : ReactViewGroup(context), Overlay { 12 | private var circle: Circle? = null 13 | 14 | var center: LatLng? = null 15 | set(value) { 16 | field = value 17 | circle?.center = value 18 | } 19 | 20 | var radius: Double = 0.0 21 | set(value) { 22 | field = value 23 | circle?.radius = value 24 | } 25 | 26 | var strokeWidth: Float = 1f 27 | set(value) { 28 | field = value 29 | circle?.strokeWidth = value 30 | } 31 | 32 | var strokeColor: Int = Color.BLACK 33 | set(value) { 34 | field = value 35 | circle?.strokeColor = value 36 | } 37 | 38 | var fillColor: Int = Color.BLACK 39 | set(value) { 40 | field = value 41 | circle?.fillColor = value 42 | } 43 | 44 | var zIndex: Float = 0f 45 | set(value) { 46 | field = value 47 | circle?.zIndex = value 48 | } 49 | 50 | override fun add(map: AMap) { 51 | circle = map.addCircle( 52 | CircleOptions() 53 | .center(center) 54 | .radius(radius) 55 | .strokeColor(strokeColor) 56 | .strokeWidth(strokeWidth) 57 | .fillColor(fillColor) 58 | .zIndex(zIndex) 59 | ) 60 | } 61 | 62 | override fun remove() { 63 | circle?.remove() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/CircleManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | import com.facebook.react.uimanager.SimpleViewManager 5 | import com.facebook.react.uimanager.ThemedReactContext 6 | import com.facebook.react.uimanager.annotations.ReactProp 7 | import qiuxiang.amap3d.toLatLng 8 | import qiuxiang.amap3d.toPx 9 | 10 | @Suppress("unused") 11 | internal class CircleManager : SimpleViewManager() { 12 | override fun getName(): String { 13 | return "AMapCircle" 14 | } 15 | 16 | override fun createViewInstance(reactContext: ThemedReactContext): Circle { 17 | return Circle(reactContext) 18 | } 19 | 20 | @ReactProp(name = "center") 21 | fun setCenter(circle: Circle, center: ReadableMap) { 22 | circle.center = center.toLatLng() 23 | } 24 | 25 | @ReactProp(name = "radius") 26 | fun setRadius(circle: Circle, radius: Double) { 27 | circle.radius = radius 28 | } 29 | 30 | @ReactProp(name = "fillColor", customType = "Color") 31 | fun setFillColor(circle: Circle, fillColor: Int) { 32 | circle.fillColor = fillColor 33 | } 34 | 35 | @ReactProp(name = "strokeColor", customType = "Color") 36 | fun setStrokeColor(circle: Circle, strokeColor: Int) { 37 | circle.strokeColor = strokeColor 38 | } 39 | 40 | @ReactProp(name = "strokeWidth") 41 | fun setStrokeWidth(circle: Circle, strokeWidth: Float) { 42 | circle.strokeWidth = strokeWidth.toPx().toFloat() 43 | } 44 | 45 | @ReactProp(name = "zIndex") 46 | fun setIndex(circle: Circle, zIndex: Float) { 47 | circle.zIndex = zIndex 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/HeatMap.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import com.amap.api.maps.AMap 5 | import com.amap.api.maps.model.HeatmapTileProvider 6 | import com.amap.api.maps.model.LatLng 7 | import com.amap.api.maps.model.TileOverlay 8 | import com.amap.api.maps.model.TileOverlayOptions 9 | import com.facebook.react.views.view.ReactViewGroup 10 | 11 | class HeatMap(context: Context) : ReactViewGroup(context), Overlay { 12 | private var overlay: TileOverlay? = null 13 | var data: List = emptyList() 14 | var opacity: Double = 0.6 15 | var radius: Int = 12 16 | 17 | override fun add(map: AMap) { 18 | overlay = map.addTileOverlay( 19 | TileOverlayOptions().tileProvider( 20 | HeatmapTileProvider.Builder() 21 | .data(data) 22 | .radius(radius) 23 | .transparency(opacity) 24 | .build() 25 | ) 26 | ) 27 | } 28 | 29 | override fun remove() { 30 | overlay?.remove() 31 | } 32 | } -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/HeatMapManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.facebook.react.bridge.ReadableArray 4 | import com.facebook.react.uimanager.SimpleViewManager 5 | import com.facebook.react.uimanager.ThemedReactContext 6 | import com.facebook.react.uimanager.annotations.ReactProp 7 | import qiuxiang.amap3d.toLatLngList 8 | 9 | @Suppress("unused") 10 | internal class HeatMapManager : SimpleViewManager() { 11 | override fun getName(): String { 12 | return "AMapHeatMap" 13 | } 14 | 15 | override fun createViewInstance(reactContext: ThemedReactContext): HeatMap { 16 | return HeatMap(reactContext) 17 | } 18 | 19 | @ReactProp(name = "data") 20 | fun setData(heatMap: HeatMap, data: ReadableArray) { 21 | heatMap.data = data.toLatLngList() 22 | } 23 | 24 | @ReactProp(name = "radius") 25 | fun setRadius(heatMap: HeatMap, radius: Int) { 26 | heatMap.radius = radius 27 | } 28 | 29 | @ReactProp(name = "opacity") 30 | fun setOpacity(heatMap: HeatMap, opacity: Double) { 31 | heatMap.opacity = opacity 32 | } 33 | } -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/MapView.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.View 5 | import com.amap.api.maps.AMap 6 | import com.amap.api.maps.CameraUpdateFactory 7 | import com.amap.api.maps.TextureMapView 8 | import com.amap.api.maps.model.CameraPosition 9 | import com.amap.api.maps.model.Marker 10 | import com.amap.api.maps.model.MyLocationStyle 11 | import com.facebook.react.bridge.Arguments 12 | import com.facebook.react.bridge.ReadableArray 13 | import com.facebook.react.bridge.ReadableMap 14 | import com.facebook.react.bridge.WritableMap 15 | import com.facebook.react.uimanager.ThemedReactContext 16 | import qiuxiang.amap3d.getFloat 17 | import qiuxiang.amap3d.toJson 18 | import qiuxiang.amap3d.toLatLng 19 | import qiuxiang.amap3d.toPoint 20 | 21 | @SuppressLint("ViewConstructor") 22 | class MapView(context: ThemedReactContext) : TextureMapView(context) { 23 | @Suppress("Deprecation") 24 | private val eventEmitter = 25 | context.getJSModule(com.facebook.react.uimanager.events.RCTEventEmitter::class.java) 26 | private val markerMap = HashMap() 27 | private val polylineMap = HashMap() 28 | private var initialCameraPosition: ReadableMap? = null 29 | private var locationStyle: MyLocationStyle 30 | 31 | init { 32 | super.onCreate(null) 33 | 34 | locationStyle = MyLocationStyle() 35 | locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER) 36 | map.myLocationStyle = locationStyle 37 | 38 | map.setOnMapLoadedListener { emit(id, "onLoad") } 39 | map.setOnMapClickListener { latLng -> emit(id, "onPress", latLng.toJson()) } 40 | map.setOnPOIClickListener { poi -> emit(id, "onPressPoi", poi.toJson()) } 41 | map.setOnMapLongClickListener { latLng -> emit(id, "onLongPress", latLng.toJson()) } 42 | map.setOnPolylineClickListener { polyline -> emit(polylineMap[polyline.id]?.id, "onPress") } 43 | 44 | map.setOnMarkerClickListener { marker -> 45 | markerMap[marker.id]?.let { emit(it.id, "onPress") } 46 | true 47 | } 48 | 49 | map.setOnMarkerDragListener(object : AMap.OnMarkerDragListener { 50 | override fun onMarkerDragStart(marker: Marker) { 51 | emit(markerMap[marker.id]?.id, "onDragStart") 52 | } 53 | 54 | override fun onMarkerDrag(marker: Marker) { 55 | emit(markerMap[marker.id]?.id, "onDrag") 56 | } 57 | 58 | override fun onMarkerDragEnd(marker: Marker) { 59 | emit(markerMap[marker.id]?.id, "onDragEnd", marker.position.toJson()) 60 | } 61 | }) 62 | 63 | map.setOnCameraChangeListener(object : AMap.OnCameraChangeListener { 64 | override fun onCameraChangeFinish(position: CameraPosition) { 65 | emit(id, "onCameraIdle", Arguments.createMap().apply { 66 | putMap("cameraPosition", position.toJson()) 67 | putMap("latLngBounds", map.projection.visibleRegion.latLngBounds.toJson()) 68 | }) 69 | } 70 | 71 | override fun onCameraChange(position: CameraPosition) { 72 | emit(id, "onCameraMove", Arguments.createMap().apply { 73 | putMap("cameraPosition", position.toJson()) 74 | putMap("latLngBounds", map.projection.visibleRegion.latLngBounds.toJson()) 75 | }) 76 | } 77 | }) 78 | 79 | map.setOnMultiPointClickListener { item -> 80 | item.customerId.split("_").let { 81 | emit( 82 | it[0].toInt(), 83 | "onPress", 84 | Arguments.createMap().apply { putInt("index", it[1].toInt()) }, 85 | ) 86 | } 87 | false 88 | } 89 | 90 | map.setOnMyLocationChangeListener { 91 | if (it.time > 0) { 92 | emit(id, "onLocation", it.toJson()) 93 | } 94 | } 95 | } 96 | 97 | fun emit(id: Int?, event: String, data: WritableMap = Arguments.createMap()) { 98 | @Suppress("Deprecation") 99 | id?.let { eventEmitter.receiveEvent(it, event, data) } 100 | } 101 | 102 | fun add(child: View) { 103 | if (child is Overlay) { 104 | child.add(map) 105 | if (child is qiuxiang.amap3d.map_view.Marker) { 106 | markerMap[child.marker?.id!!] = child 107 | } 108 | if (child is Polyline) { 109 | polylineMap[child.polyline?.id!!] = child 110 | } 111 | } 112 | } 113 | 114 | fun remove(child: View) { 115 | if (child is Overlay) { 116 | child.remove() 117 | if (child is qiuxiang.amap3d.map_view.Marker) { 118 | markerMap.remove(child.marker?.id) 119 | } 120 | if (child is Polyline) { 121 | polylineMap.remove(child.polyline?.id) 122 | } 123 | } 124 | } 125 | 126 | private val animateCallback = object : AMap.CancelableCallback { 127 | override fun onCancel() {} 128 | override fun onFinish() {} 129 | } 130 | 131 | fun moveCamera(args: ReadableArray?) { 132 | val current = map.cameraPosition 133 | val position = args?.getMap(0)!! 134 | val target = position.getMap("target")?.toLatLng() ?: current.target 135 | val zoom = position.getFloat("zoom") ?: current.zoom 136 | val tilt = position.getFloat("tilt") ?: current.tilt 137 | val bearing = position.getFloat("bearing") ?: current.bearing 138 | val cameraUpdate = CameraUpdateFactory.newCameraPosition( 139 | CameraPosition(target, zoom, tilt, bearing) 140 | ) 141 | map.animateCamera(cameraUpdate, args.getInt(1).toLong(), animateCallback) 142 | } 143 | 144 | fun setInitialCameraPosition(position: ReadableMap) { 145 | if (initialCameraPosition == null) { 146 | initialCameraPosition = position 147 | moveCamera(Arguments.createArray().apply { 148 | pushMap(Arguments.createMap().apply { merge(position) }) 149 | pushInt(0) 150 | }) 151 | } 152 | } 153 | 154 | fun call(args: ReadableArray?) { 155 | val id = args?.getDouble(0)!! 156 | when (args.getString(1)) { 157 | "getLatLng" -> callback( 158 | id, 159 | // @todo 暂时兼容 0.63 160 | @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") 161 | map.projection.fromScreenLocation(args.getMap(2)!!.toPoint()).toJson() 162 | ) 163 | } 164 | } 165 | 166 | private fun callback(id: Double, data: Any) { 167 | emit(this.id, "onCallback", Arguments.createMap().apply { 168 | putDouble("id", id) 169 | when (data) { 170 | is WritableMap -> putMap("data", data) 171 | } 172 | }) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/MapViewManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.view.View 4 | import com.amap.api.maps.CameraUpdateFactory 5 | import com.facebook.react.bridge.ReadableArray 6 | import com.facebook.react.bridge.ReadableMap 7 | import com.facebook.react.uimanager.ThemedReactContext 8 | import com.facebook.react.uimanager.ViewGroupManager 9 | import com.facebook.react.uimanager.annotations.ReactProp 10 | import qiuxiang.amap3d.getEventTypeConstants 11 | import qiuxiang.amap3d.toLatLng 12 | 13 | @Suppress("unused") 14 | internal class MapViewManager : ViewGroupManager() { 15 | private val commands = mapOf( 16 | "moveCamera" to { view: MapView, args: ReadableArray? -> view.moveCamera(args) }, 17 | "call" to { view: MapView, args: ReadableArray? -> view.call(args) }, 18 | ) 19 | 20 | override fun getName(): String { 21 | return "AMapView" 22 | } 23 | 24 | override fun createViewInstance(reactContext: ThemedReactContext): MapView { 25 | return MapView(reactContext) 26 | } 27 | 28 | override fun onDropViewInstance(view: MapView) { 29 | super.onDropViewInstance(view) 30 | view.onDestroy() 31 | } 32 | 33 | override fun getCommandsMap(): Map { 34 | return commands.keys.mapIndexed { index, key -> key to index }.toMap() 35 | } 36 | 37 | override fun receiveCommand(view: MapView, command: Int, args: ReadableArray?) { 38 | commands.values.toList()[command](view, args) 39 | } 40 | 41 | override fun addView(mapView: MapView, child: View, index: Int) { 42 | mapView.add(child) 43 | super.addView(mapView, child, index) 44 | } 45 | 46 | override fun removeViewAt(parent: MapView, index: Int) { 47 | parent.remove(parent.getChildAt(index)) 48 | super.removeViewAt(parent, index) 49 | } 50 | 51 | override fun getExportedCustomBubblingEventTypeConstants(): Map { 52 | return getEventTypeConstants( 53 | "onLoad", 54 | "onPress", 55 | "onPressPoi", 56 | "onLongPress", 57 | "onCameraMove", 58 | "onCameraIdle", 59 | "onLocation", 60 | "onCallback", 61 | ) 62 | } 63 | 64 | @ReactProp(name = "initialCameraPosition") 65 | fun setInitialCameraPosition(view: MapView, position: ReadableMap) { 66 | view.setInitialCameraPosition(position) 67 | } 68 | 69 | @ReactProp(name = "myLocationEnabled") 70 | fun setMyLocationEnabled(view: MapView, enabled: Boolean) { 71 | view.map.isMyLocationEnabled = enabled 72 | } 73 | 74 | @ReactProp(name = "indoorViewEnabled") 75 | fun setIndoorViewEnabled(view: MapView, enabled: Boolean) { 76 | view.map.showIndoorMap(enabled) 77 | } 78 | 79 | @ReactProp(name = "buildingsEnabled") 80 | fun setBuildingsEnabled(view: MapView, enabled: Boolean) { 81 | view.map.showBuildings(enabled) 82 | } 83 | 84 | @ReactProp(name = "compassEnabled") 85 | fun setCompassEnabled(view: MapView, show: Boolean) { 86 | view.map.uiSettings.isCompassEnabled = show 87 | } 88 | 89 | @ReactProp(name = "zoomControlsEnabled") 90 | fun setZoomControlsEnabled(view: MapView, enabled: Boolean) { 91 | view.map.uiSettings.isZoomControlsEnabled = enabled 92 | } 93 | 94 | @ReactProp(name = "scaleControlsEnabled") 95 | fun setScaleControlsEnabled(view: MapView, enabled: Boolean) { 96 | view.map.uiSettings.isScaleControlsEnabled = enabled 97 | } 98 | 99 | @ReactProp(name = "language") 100 | fun setLanguage(view: MapView, language: String) { 101 | view.map.setMapLanguage(language) 102 | } 103 | 104 | @ReactProp(name = "myLocationButtonEnabled") 105 | fun setMyLocationButtonEnabled(view: MapView, enabled: Boolean) { 106 | view.map.uiSettings.isMyLocationButtonEnabled = enabled 107 | } 108 | 109 | @ReactProp(name = "trafficEnabled") 110 | fun setTrafficEnabled(view: MapView, enabled: Boolean) { 111 | view.map.isTrafficEnabled = enabled 112 | } 113 | 114 | @ReactProp(name = "maxZoom") 115 | fun setMaxZoom(view: MapView, zoomLevel: Float) { 116 | view.map.maxZoomLevel = zoomLevel 117 | } 118 | 119 | @ReactProp(name = "minZoom") 120 | fun setMinZoom(view: MapView, zoomLevel: Float) { 121 | view.map.minZoomLevel = zoomLevel 122 | } 123 | 124 | @ReactProp(name = "mapType") 125 | fun setMapType(view: MapView, mapType: Int) { 126 | view.map.mapType = mapType + 1 127 | } 128 | 129 | @ReactProp(name = "zoomGesturesEnabled") 130 | fun setZoomGesturesEnabled(view: MapView, enabled: Boolean) { 131 | view.map.uiSettings.isZoomGesturesEnabled = enabled 132 | } 133 | 134 | @ReactProp(name = "scrollGesturesEnabled") 135 | fun setScrollGesturesEnabled(view: MapView, enabled: Boolean) { 136 | view.map.uiSettings.isScrollGesturesEnabled = enabled 137 | } 138 | 139 | @ReactProp(name = "rotateGesturesEnabled") 140 | fun setRotateGesturesEnabled(view: MapView, enabled: Boolean) { 141 | view.map.uiSettings.isRotateGesturesEnabled = enabled 142 | } 143 | 144 | @ReactProp(name = "tiltGesturesEnabled") 145 | fun setTiltGesturesEnabled(view: MapView, enabled: Boolean) { 146 | view.map.uiSettings.isTiltGesturesEnabled = enabled 147 | } 148 | 149 | @ReactProp(name = "cameraPosition") 150 | fun setCameraPosition(view: MapView, center: ReadableMap) { 151 | view.map.moveCamera(CameraUpdateFactory.changeLatLng(center.toLatLng())) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/Marker.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.os.Handler 7 | import android.os.Looper 8 | import android.view.View 9 | import com.amap.api.maps.AMap 10 | import com.amap.api.maps.model.* 11 | import com.amap.api.maps.model.Marker 12 | import com.facebook.react.bridge.ReadableMap 13 | import com.facebook.react.views.view.ReactViewGroup 14 | import qiuxiang.amap3d.fetchImage 15 | 16 | class Marker(context: Context) : ReactViewGroup(context), Overlay { 17 | private var view: View? = null 18 | private var icon: BitmapDescriptor? = null 19 | private var anchorX: Float = 0.5f 20 | private var anchorY: Float = 1f 21 | var marker: Marker? = null 22 | 23 | var position: LatLng? = null 24 | set(value) { 25 | field = value 26 | marker?.position = value 27 | } 28 | 29 | var zIndex: Float = 0.0f 30 | set(value) { 31 | field = value 32 | marker?.zIndex = value 33 | } 34 | 35 | var flat: Boolean = false 36 | set(value) { 37 | field = value 38 | marker?.isFlat = value 39 | } 40 | 41 | var opacity: Float = 1f 42 | set(value) { 43 | field = value 44 | marker?.alpha = value 45 | } 46 | 47 | var draggable: Boolean = false 48 | set(value) { 49 | field = value 50 | marker?.isDraggable = value 51 | } 52 | 53 | fun updateIcon() { 54 | view?.let { 55 | if (it.width != 0 && it.height != 0) { 56 | val bitmap = Bitmap.createBitmap(it.width, it.height, Bitmap.Config.ARGB_8888) 57 | it.draw(Canvas(bitmap)) 58 | icon = BitmapDescriptorFactory.fromBitmap(bitmap) 59 | marker?.setIcon(icon) 60 | } 61 | } 62 | } 63 | 64 | fun setAnchor(x: Double, y: Double) { 65 | anchorX = x.toFloat() 66 | anchorY = y.toFloat() 67 | marker?.setAnchor(anchorX, anchorY) 68 | } 69 | 70 | override fun addView(child: View, index: Int) { 71 | super.addView(child, index) 72 | view = child 73 | view?.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> updateIcon() } 74 | } 75 | 76 | fun setIcon(source: ReadableMap) { 77 | fetchImage(source) { 78 | icon = it 79 | Handler(Looper.getMainLooper()).post { 80 | marker?.setIcon(it) 81 | } 82 | } 83 | } 84 | 85 | override fun add(map: AMap) { 86 | marker = map.addMarker( 87 | MarkerOptions() 88 | .setFlat(flat) 89 | .icon(icon) 90 | .alpha(opacity) 91 | .draggable(draggable) 92 | .position(position) 93 | .anchor(anchorX, anchorY) 94 | .zIndex(zIndex) 95 | .infoWindowEnable(false) 96 | ) 97 | } 98 | 99 | override fun remove() { 100 | marker?.destroy() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/MarkerManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.view.View 4 | import com.facebook.react.bridge.ReadableArray 5 | import com.facebook.react.bridge.ReadableMap 6 | import com.facebook.react.uimanager.ThemedReactContext 7 | import com.facebook.react.uimanager.ViewGroupManager 8 | import com.facebook.react.uimanager.annotations.ReactProp 9 | import qiuxiang.amap3d.getEventTypeConstants 10 | import qiuxiang.amap3d.toLatLng 11 | 12 | @Suppress("unused") 13 | internal class MarkerManager : ViewGroupManager() { 14 | override fun getName(): String { 15 | return "AMapMarker" 16 | } 17 | 18 | override fun createViewInstance(reactContext: ThemedReactContext): Marker { 19 | return Marker(reactContext) 20 | } 21 | 22 | override fun addView(marker: Marker, view: View, index: Int) { 23 | super.addView(marker, view, index) 24 | } 25 | 26 | override fun getExportedCustomBubblingEventTypeConstants(): Map { 27 | return getEventTypeConstants("onPress", "onDrag", "onDragStart", "onDragEnd") 28 | } 29 | 30 | companion object { 31 | const val update = 1 32 | } 33 | 34 | override fun getCommandsMap(): Map { 35 | return mapOf("update" to update) 36 | } 37 | 38 | override fun receiveCommand(marker: Marker, commandId: Int, args: ReadableArray?) { 39 | when (commandId) { 40 | update -> marker.updateIcon() 41 | } 42 | } 43 | 44 | @ReactProp(name = "latLng") 45 | fun setLatLng(view: Marker, position: ReadableMap) { 46 | view.position = position.toLatLng() 47 | } 48 | 49 | @ReactProp(name = "flat") 50 | fun setFlat(marker: Marker, flat: Boolean) { 51 | marker.flat = flat 52 | } 53 | 54 | @ReactProp(name = "opacity") 55 | override fun setOpacity(marker: Marker, opacity: Float) { 56 | marker.opacity = opacity 57 | } 58 | 59 | @ReactProp(name = "draggable") 60 | fun setDraggable(marker: Marker, draggable: Boolean) { 61 | marker.draggable = draggable 62 | } 63 | 64 | @ReactProp(name = "zIndex") 65 | fun setIndex(marker: Marker, zIndex: Float) { 66 | marker.zIndex = zIndex 67 | } 68 | 69 | @ReactProp(name = "anchor") 70 | fun setAnchor(view: Marker, anchor: ReadableMap) { 71 | view.setAnchor(anchor.getDouble("x"), anchor.getDouble("y")) 72 | } 73 | 74 | @ReactProp(name = "icon") 75 | fun setIcon(view: Marker, icon: ReadableMap?) { 76 | icon?.let { view.setIcon(it) } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/MultiPoint.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import com.amap.api.maps.AMap 5 | import com.amap.api.maps.model.BitmapDescriptor 6 | import com.amap.api.maps.model.MultiPointItem 7 | import com.amap.api.maps.model.MultiPointOverlay 8 | import com.amap.api.maps.model.MultiPointOverlayOptions 9 | import com.facebook.react.bridge.ReadableArray 10 | import com.facebook.react.bridge.ReadableMap 11 | import com.facebook.react.views.view.ReactViewGroup 12 | import qiuxiang.amap3d.fetchImage 13 | import qiuxiang.amap3d.toLatLng 14 | 15 | class MultiPoint(context: Context) : ReactViewGroup(context), Overlay { 16 | private lateinit var map: AMap 17 | private var overlay: MultiPointOverlay? = null 18 | private var items: List = emptyList() 19 | private var icon: BitmapDescriptor? = null 20 | 21 | override fun add(map: AMap) { 22 | this.map = map 23 | addToMap() 24 | } 25 | 26 | override fun remove() { 27 | overlay?.destroy() 28 | } 29 | 30 | private fun addToMap() { 31 | if (overlay != null) return 32 | if (icon != null) { 33 | overlay = map.addMultiPointOverlay(MultiPointOverlayOptions().icon(icon)) 34 | overlay?.items = items 35 | } 36 | } 37 | 38 | fun setItems(points: ReadableArray) { 39 | items = (0 until points.size()) 40 | .map { item -> 41 | // 兼容 0.63 42 | MultiPointItem(points.getMap(item)!!.toLatLng()).apply { customerId = "${id}_$item" } 43 | } 44 | overlay?.items = items 45 | } 46 | 47 | fun setIcon(source: ReadableMap) { 48 | fetchImage(source) { 49 | icon = it 50 | addToMap() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/MultiPointManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.facebook.react.bridge.ReadableArray 4 | import com.facebook.react.bridge.ReadableMap 5 | import com.facebook.react.uimanager.SimpleViewManager 6 | import com.facebook.react.uimanager.ThemedReactContext 7 | import com.facebook.react.uimanager.annotations.ReactProp 8 | import qiuxiang.amap3d.getEventTypeConstants 9 | 10 | @Suppress("unused") 11 | internal class MultiPointManager : SimpleViewManager() { 12 | override fun getName(): String { 13 | return "AMapMultiPoint" 14 | } 15 | 16 | override fun createViewInstance(reactContext: ThemedReactContext): MultiPoint { 17 | return MultiPoint(reactContext) 18 | } 19 | 20 | override fun getExportedCustomBubblingEventTypeConstants(): Map { 21 | return getEventTypeConstants("onPress") 22 | } 23 | 24 | @ReactProp(name = "items") 25 | fun setPoints(multiPoint: MultiPoint, items: ReadableArray) { 26 | multiPoint.setItems(items) 27 | } 28 | 29 | @ReactProp(name = "icon") 30 | fun setIcon(multiPoint: MultiPoint, icon: ReadableMap?) { 31 | icon?.let { multiPoint.setIcon(it) } 32 | } 33 | } -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/Overlay.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.amap.api.maps.AMap 4 | 5 | interface Overlay { 6 | fun add(map: AMap) 7 | fun remove() 8 | } -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/Polygon.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import com.amap.api.maps.AMap 6 | import com.amap.api.maps.model.LatLng 7 | import com.amap.api.maps.model.Polygon 8 | import com.amap.api.maps.model.PolygonOptions 9 | import com.facebook.react.views.view.ReactViewGroup 10 | 11 | class Polygon(context: Context) : ReactViewGroup(context), Overlay { 12 | private var polygon: Polygon? = null 13 | 14 | var points: List = emptyList() 15 | set(value) { 16 | field = value 17 | polygon?.points = value 18 | } 19 | 20 | var strokeWidth: Float = 1f 21 | set(value) { 22 | field = value 23 | polygon?.strokeWidth = value 24 | } 25 | 26 | var strokeColor: Int = Color.BLACK 27 | set(value) { 28 | field = value 29 | polygon?.strokeColor = value 30 | } 31 | 32 | var fillColor: Int = Color.BLACK 33 | set(value) { 34 | field = value 35 | polygon?.fillColor = value 36 | } 37 | 38 | var zIndex: Float = 0f 39 | set(value) { 40 | field = value 41 | polygon?.zIndex = value 42 | } 43 | 44 | override fun add(map: AMap) { 45 | polygon = map.addPolygon( 46 | PolygonOptions() 47 | .addAll(points) 48 | .strokeColor(strokeColor) 49 | .strokeWidth(strokeWidth) 50 | .fillColor(fillColor) 51 | .zIndex(zIndex) 52 | ) 53 | } 54 | 55 | override fun remove() { 56 | polygon?.remove() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/PolygonManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.facebook.react.bridge.ReadableArray 4 | import com.facebook.react.uimanager.SimpleViewManager 5 | import com.facebook.react.uimanager.ThemedReactContext 6 | import com.facebook.react.uimanager.annotations.ReactProp 7 | import qiuxiang.amap3d.toLatLngList 8 | import qiuxiang.amap3d.toPx 9 | 10 | @Suppress("unused") 11 | internal class PolygonManager : SimpleViewManager() { 12 | override fun getName(): String { 13 | return "AMapPolygon" 14 | } 15 | 16 | override fun createViewInstance(reactContext: ThemedReactContext): Polygon { 17 | return Polygon(reactContext) 18 | } 19 | 20 | @ReactProp(name = "points") 21 | fun setPoints(polygon: Polygon, points: ReadableArray) { 22 | polygon.points = points.toLatLngList() 23 | } 24 | 25 | @ReactProp(name = "fillColor", customType = "Color") 26 | fun setFillColor(polygon: Polygon, fillColor: Int) { 27 | polygon.fillColor = fillColor 28 | } 29 | 30 | @ReactProp(name = "strokeColor", customType = "Color") 31 | fun setStrokeColor(polygon: Polygon, strokeColor: Int) { 32 | polygon.strokeColor = strokeColor 33 | } 34 | 35 | @ReactProp(name = "strokeWidth") 36 | fun setStrokeWidth(polygon: Polygon, strokeWidth: Float) { 37 | polygon.strokeWidth = strokeWidth.toPx().toFloat() 38 | } 39 | 40 | @ReactProp(name = "zIndex") 41 | fun setIndex(polygon: Polygon, zIndex: Float) { 42 | polygon.zIndex = zIndex 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/Polyline.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import com.amap.api.maps.AMap 6 | import com.amap.api.maps.model.LatLng 7 | import com.amap.api.maps.model.Polyline 8 | import com.amap.api.maps.model.PolylineOptions 9 | import com.facebook.react.views.view.ReactViewGroup 10 | 11 | class Polyline(context: Context) : ReactViewGroup(context), Overlay { 12 | var polyline: Polyline? = null 13 | var gradient: Boolean = false 14 | var colors: List = emptyList() 15 | 16 | var points: List = emptyList() 17 | set(value) { 18 | field = value 19 | polyline?.points = value 20 | } 21 | 22 | var width: Float = 1f 23 | set(value) { 24 | field = value 25 | polyline?.width = value 26 | } 27 | 28 | var color: Int = Color.BLACK 29 | set(value) { 30 | field = value 31 | polyline?.color = value 32 | } 33 | 34 | var zIndex: Float = 0f 35 | set(value) { 36 | field = value 37 | polyline?.zIndex = value 38 | } 39 | 40 | var geodesic: Boolean = false 41 | set(value) { 42 | field = value 43 | polyline?.isGeodesic = value 44 | } 45 | 46 | var dashed: Boolean = false 47 | set(value) { 48 | field = value 49 | polyline?.isDottedLine = value 50 | } 51 | 52 | override fun add(map: AMap) { 53 | polyline = map.addPolyline( 54 | PolylineOptions() 55 | .addAll(points) 56 | .color(color) 57 | .colorValues(colors) 58 | .width(width) 59 | .useGradient(gradient) 60 | .geodesic(geodesic) 61 | .setDottedLine(dashed) 62 | .zIndex(zIndex) 63 | ) 64 | } 65 | 66 | override fun remove() { 67 | polyline?.remove() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/map_view/PolylineManager.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.map_view 2 | 3 | import com.facebook.react.bridge.ReadableArray 4 | import com.facebook.react.uimanager.SimpleViewManager 5 | import com.facebook.react.uimanager.ThemedReactContext 6 | import com.facebook.react.uimanager.annotations.ReactProp 7 | import qiuxiang.amap3d.getEventTypeConstants 8 | import qiuxiang.amap3d.toLatLngList 9 | import qiuxiang.amap3d.toPx 10 | 11 | @Suppress("unused") 12 | internal class PolylineManager : SimpleViewManager() { 13 | override fun getName(): String { 14 | return "AMapPolyline" 15 | } 16 | 17 | override fun createViewInstance(context: ThemedReactContext): Polyline { 18 | return Polyline(context) 19 | } 20 | 21 | override fun getExportedCustomBubblingEventTypeConstants(): Map { 22 | return getEventTypeConstants("onPress") 23 | } 24 | 25 | @ReactProp(name = "points") 26 | fun setPoints(polyline: Polyline, points: ReadableArray) { 27 | polyline.points = points.toLatLngList() 28 | } 29 | 30 | @ReactProp(name = "colors") 31 | fun setColors(polyline: Polyline, colors: ReadableArray) { 32 | polyline.colors = (0 until colors.size()).map { colors.getInt(it) } 33 | } 34 | 35 | @ReactProp(name = "color", customType = "Color") 36 | fun setColor(polyline: Polyline, color: Int) { 37 | polyline.color = color 38 | } 39 | 40 | @ReactProp(name = "width") 41 | fun setWidth(polyline: Polyline, width: Float) { 42 | polyline.width = width.toPx().toFloat() 43 | } 44 | 45 | @ReactProp(name = "zIndex") 46 | fun setIndex(polyline: Polyline, zIndex: Float) { 47 | polyline.zIndex = zIndex 48 | } 49 | 50 | @ReactProp(name = "geodesic") 51 | fun setGeodesic(polyline: Polyline, geodesic: Boolean) { 52 | polyline.geodesic = geodesic 53 | } 54 | 55 | @ReactProp(name = "dashed") 56 | fun setDashed(polyline: Polyline, dashed: Boolean) { 57 | polyline.dashed = dashed 58 | } 59 | 60 | @ReactProp(name = "gradient") 61 | fun setGradient(polyline: Polyline, gradient: Boolean) { 62 | polyline.gradient = gradient 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/android/src/main/java/qiuxiang/amap3d/modules/SdkModule.kt: -------------------------------------------------------------------------------- 1 | package qiuxiang.amap3d.modules 2 | 3 | import com.amap.api.location.AMapLocationClient 4 | import com.amap.api.maps.MapsInitializer 5 | import com.facebook.react.bridge.Promise 6 | import com.facebook.react.bridge.ReactApplicationContext 7 | import com.facebook.react.bridge.ReactContextBaseJavaModule 8 | import com.facebook.react.bridge.ReactMethod 9 | 10 | @Suppress("unused") 11 | class SdkModule(val context: ReactApplicationContext) : ReactContextBaseJavaModule() { 12 | override fun getName(): String { 13 | return "AMapSdk" 14 | } 15 | 16 | @ReactMethod 17 | fun initSDK(apiKey: String?) { 18 | apiKey?.let { 19 | MapsInitializer.setApiKey(it) 20 | MapsInitializer.updatePrivacyAgree(context, true) 21 | MapsInitializer.updatePrivacyShow(context, true, true) 22 | AMapLocationClient.updatePrivacyAgree(context, true) 23 | AMapLocationClient.updatePrivacyShow(context, true, true) 24 | } 25 | } 26 | 27 | @ReactMethod 28 | fun getVersion(promise: Promise) { 29 | promise.resolve(MapsInitializer.getVersion()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/ios/.swiftformat: -------------------------------------------------------------------------------- 1 | --swiftversion 5.2 2 | --indent 2 3 | -------------------------------------------------------------------------------- /lib/ios/Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import 5 | -------------------------------------------------------------------------------- /lib/ios/MapView/CircleManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapCircleManager, RCTViewManager) 4 | 5 | RCT_REMAP_VIEW_PROPERTY(center, circleCenter, CLLocationCoordinate2D) 6 | RCT_EXPORT_VIEW_PROPERTY(radius, double) 7 | RCT_EXPORT_VIEW_PROPERTY(strokeWidth, double) 8 | RCT_EXPORT_VIEW_PROPERTY(strokeColor, UIColor) 9 | RCT_EXPORT_VIEW_PROPERTY(fillColor, UIColor) 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /lib/ios/MapView/CircleManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapCircleManager) 2 | class AMapCircleManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | override func view() -> UIView { Circle() } 5 | } 6 | 7 | class Circle: UIView, Overlay { 8 | var overlay = MACircle() 9 | var renderer: MACircleRenderer? 10 | 11 | @objc var radius = 0.0 { didSet { overlay.radius = radius } } 12 | @objc var strokeWidth = 1.0 { didSet { renderer?.lineWidth = strokeWidth } } 13 | @objc var strokeColor = UIColor.black { didSet { renderer?.strokeColor = strokeColor } } 14 | @objc var fillColor = UIColor.white { didSet { renderer?.fillColor = fillColor } } 15 | 16 | @objc func setCircleCenter(_ center: CLLocationCoordinate2D) { 17 | overlay.coordinate = center 18 | } 19 | 20 | func getOverlay() -> MABaseOverlay { overlay } 21 | func getRenderer() -> MAOverlayRenderer { 22 | if renderer == nil { 23 | renderer = MACircleRenderer(circle: overlay) 24 | renderer?.fillColor = fillColor 25 | renderer?.strokeColor = strokeColor 26 | renderer?.lineWidth = strokeWidth 27 | } 28 | return renderer! 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/ios/MapView/HeatMapManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapHeatMapManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(data, NSArray) 6 | RCT_EXPORT_VIEW_PROPERTY(radius, int) 7 | RCT_EXPORT_VIEW_PROPERTY(opacity, double) 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /lib/ios/MapView/HeatMapManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapHeatMapManager) 2 | class AMapHeatMapManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | override func view() -> UIView { HeatMap() } 5 | } 6 | 7 | class HeatMap: UIView, Overlay { 8 | var overlay = MAHeatMapTileOverlay() 9 | var renderer: MATileOverlayRenderer? 10 | 11 | func getOverlay() -> MABaseOverlay { overlay } 12 | func getRenderer() -> MAOverlayRenderer { 13 | if renderer == nil { 14 | renderer = MATileOverlayRenderer(tileOverlay: overlay) 15 | } 16 | return renderer! 17 | } 18 | 19 | @objc func setRadius(_ radius: Int) { overlay.radius = radius } 20 | @objc func setOpacity(_ opacity: Double) { overlay.opacity = opacity } 21 | @objc func setData(_ data: NSArray) { 22 | overlay.data = data.map { it -> MAHeatMapNode in 23 | let item = MAHeatMapNode() 24 | item.coordinate = (it as! NSDictionary).coordinate 25 | item.intensity = 1 26 | return item 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/ios/MapView/MapViewManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapViewManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(mapType, MAMapType) 6 | RCT_EXPORT_VIEW_PROPERTY(initialCameraPosition, NSDictionary) 7 | RCT_EXPORT_VIEW_PROPERTY(distanceFilter, double) 8 | RCT_EXPORT_VIEW_PROPERTY(headingFilter, double) 9 | 10 | RCT_REMAP_VIEW_PROPERTY(myLocationEnabled, showsUserLocation, BOOL) 11 | RCT_REMAP_VIEW_PROPERTY(buildingsEnabled, showsBuildings, BOOL) 12 | RCT_REMAP_VIEW_PROPERTY(trafficEnabled, showTraffic, BOOL) 13 | RCT_REMAP_VIEW_PROPERTY(indoorViewEnabled, showsIndoorMap, BOOL) 14 | RCT_REMAP_VIEW_PROPERTY(compassEnabled, showsCompass, BOOL) 15 | RCT_REMAP_VIEW_PROPERTY(scaleControlsEnabled, showsScale, BOOL) 16 | RCT_REMAP_VIEW_PROPERTY(scrollGesturesEnabled, scrollEnabled, BOOL) 17 | RCT_REMAP_VIEW_PROPERTY(zoomGesturesEnabled, zoomEnabled, BOOL) 18 | RCT_REMAP_VIEW_PROPERTY(rotateGesturesEnabled, rotateEnabled, BOOL) 19 | RCT_REMAP_VIEW_PROPERTY(tiltGesturesEnabled, rotateCameraEnabled, BOOL) 20 | RCT_REMAP_VIEW_PROPERTY(minZoom, minZoomLevel, double) 21 | RCT_REMAP_VIEW_PROPERTY(maxZoom, maxZoomLevel, double) 22 | 23 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 24 | RCT_EXPORT_VIEW_PROPERTY(onPressPoi, RCTBubblingEventBlock) 25 | RCT_EXPORT_VIEW_PROPERTY(onLongPress, RCTBubblingEventBlock) 26 | RCT_EXPORT_VIEW_PROPERTY(onCameraIdle, RCTBubblingEventBlock) 27 | RCT_EXPORT_VIEW_PROPERTY(onCameraMove, RCTBubblingEventBlock) 28 | RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock) 29 | RCT_EXPORT_VIEW_PROPERTY(onLocation, RCTBubblingEventBlock) 30 | RCT_EXPORT_VIEW_PROPERTY(onCallback, RCTBubblingEventBlock) 31 | 32 | RCT_EXTERN_METHOD(moveCamera:(nonnull NSNumber *)reactTag position:(NSDictionary *)_ duration:(int)_) 33 | RCT_EXTERN_METHOD(call:(nonnull NSNumber *)reactTag callerId:(double)_ name:(NSString *)_ args:(NSDictionary *)_) 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /lib/ios/MapView/MapViewManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapViewManager) 2 | class AMapViewManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | 5 | override func view() -> UIView { 6 | let view = MapView() 7 | view.delegate = view 8 | return view 9 | } 10 | 11 | @objc func moveCamera(_ reactTag: NSNumber, position: NSDictionary, duration: Int) { 12 | getView(reactTag: reactTag) { view in 13 | view.moveCamera(position: position, duration: duration) 14 | } 15 | } 16 | 17 | @objc func call(_ reactTag: NSNumber, callerId: Double, name: String, args: NSDictionary) { 18 | getView(reactTag: reactTag) { view in 19 | view.call(id: callerId, name: name, args: args) 20 | } 21 | } 22 | 23 | func getView(reactTag: NSNumber, callback: @escaping (MapView) -> Void) { 24 | bridge.uiManager.addUIBlock { _, viewRegistry in 25 | callback(viewRegistry![reactTag] as! MapView) 26 | } 27 | } 28 | } 29 | 30 | class MapView: MAMapView, MAMapViewDelegate { 31 | var initialized = false 32 | var overlayMap: [MABaseOverlay: Overlay] = [:] 33 | var markerMap: [MAPointAnnotation: Marker] = [:] 34 | 35 | @objc var onLoad: RCTBubblingEventBlock = { _ in } 36 | @objc var onCameraMove: RCTBubblingEventBlock = { _ in } 37 | @objc var onCameraIdle: RCTBubblingEventBlock = { _ in } 38 | @objc var onPress: RCTBubblingEventBlock = { _ in } 39 | @objc var onPressPoi: RCTBubblingEventBlock = { _ in } 40 | @objc var onLongPress: RCTBubblingEventBlock = { _ in } 41 | @objc var onLocation: RCTBubblingEventBlock = { _ in } 42 | @objc var onCallback: RCTBubblingEventBlock = { _ in } 43 | 44 | @objc func setInitialCameraPosition(_ json: NSDictionary) { 45 | if !initialized { 46 | initialized = true 47 | moveCamera(position: json) 48 | } 49 | } 50 | 51 | func moveCamera(position: NSDictionary, duration: Int = 0) { 52 | let status = MAMapStatus() 53 | status.zoomLevel = (position["zoom"] as? Double)?.cgFloat ?? zoomLevel 54 | status.cameraDegree = (position["tilt"] as? Double)?.cgFloat ?? cameraDegree 55 | status.rotationDegree = (position["bearing"] as? Double)?.cgFloat ?? rotationDegree 56 | status.centerCoordinate = (position["target"] as? NSDictionary)?.coordinate ?? centerCoordinate 57 | setMapStatus(status, animated: true, duration: Double(duration) / 1000) 58 | } 59 | 60 | func call(id: Double, name: String, args: NSDictionary) { 61 | switch name { 62 | case "getLatLng": 63 | callback(id: id, data: convert(args.point, toCoordinateFrom: self).json) 64 | default: 65 | break 66 | } 67 | } 68 | 69 | func callback(id: Double, data: [String: Any]) { 70 | onCallback(["id": id, "data": data]) 71 | } 72 | 73 | override func didAddSubview(_ subview: UIView) { 74 | if let overlay = (subview as? Overlay)?.getOverlay() { 75 | overlayMap[overlay] = subview as? Overlay 76 | add(overlay) 77 | } 78 | if let annotation = (subview as? Marker)?.annotation { 79 | markerMap[annotation] = subview as? Marker 80 | addAnnotation(annotation) 81 | } 82 | } 83 | 84 | override func removeReactSubview(_ subview: UIView!) { 85 | super.removeReactSubview(subview) 86 | if let overlay = (subview as? Overlay)?.getOverlay() { 87 | overlayMap.removeValue(forKey: overlay) 88 | remove(overlay) 89 | } 90 | if let annotation = (subview as? Marker)?.annotation { 91 | markerMap.removeValue(forKey: annotation) 92 | removeAnnotation(annotation) 93 | } 94 | } 95 | 96 | func mapView(_: MAMapView, rendererFor overlay: MAOverlay) -> MAOverlayRenderer? { 97 | if let key = overlay as? MABaseOverlay { 98 | return overlayMap[key]?.getRenderer() 99 | } 100 | return nil 101 | } 102 | 103 | func mapView(_: MAMapView!, viewFor annotation: MAAnnotation) -> MAAnnotationView? { 104 | if let key = annotation as? MAPointAnnotation { 105 | return markerMap[key]?.getView() 106 | } 107 | return nil 108 | } 109 | 110 | func mapView(_: MAMapView!, annotationView view: MAAnnotationView!, didChange newState: MAAnnotationViewDragState, fromOldState _: MAAnnotationViewDragState) { 111 | if let key = view.annotation as? MAPointAnnotation { 112 | let market = markerMap[key]! 113 | if newState == MAAnnotationViewDragState.starting { 114 | market.onDragStart(nil) 115 | } 116 | if newState == MAAnnotationViewDragState.dragging { 117 | market.onDrag(nil) 118 | } 119 | if newState == MAAnnotationViewDragState.ending { 120 | market.onDragEnd(view.annotation.coordinate.json) 121 | } 122 | } 123 | } 124 | 125 | func mapView(_: MAMapView!, didAnnotationViewTapped view: MAAnnotationView!) { 126 | if let key = view.annotation as? MAPointAnnotation { 127 | markerMap[key]?.onPress(nil) 128 | } 129 | } 130 | 131 | func mapInitComplete(_: MAMapView!) { 132 | onLoad(nil) 133 | } 134 | 135 | func mapView(_: MAMapView!, didSingleTappedAt coordinate: CLLocationCoordinate2D) { 136 | onPress(coordinate.json) 137 | } 138 | 139 | func mapView(_: MAMapView!, didTouchPois pois: [Any]!) { 140 | let poi = pois[0] as! MATouchPoi 141 | onPressPoi(["name": poi.name!, "id": poi.uid!, "position": poi.coordinate.json]) 142 | } 143 | 144 | func mapView(_: MAMapView!, didLongPressedAt coordinate: CLLocationCoordinate2D) { 145 | onLongPress(coordinate.json) 146 | } 147 | 148 | func mapViewRegionChanged(_: MAMapView!) { 149 | onCameraMove(cameraEvent) 150 | } 151 | 152 | func mapView(_: MAMapView!, regionDidChangeAnimated _: Bool) { 153 | onCameraIdle(cameraEvent) 154 | } 155 | 156 | func mapView(_: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation _: Bool) { 157 | onLocation(userLocation.json) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/ios/MapView/MarkerManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapMarkerManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(latLng, CLLocationCoordinate2D) 6 | RCT_EXPORT_VIEW_PROPERTY(centerOffset, CGPoint) 7 | RCT_EXPORT_VIEW_PROPERTY(draggable, BOOL) 8 | RCT_EXPORT_VIEW_PROPERTY(zIndex, int) 9 | RCT_EXPORT_VIEW_PROPERTY(icon, NSDictionary) 10 | 11 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 12 | RCT_EXPORT_VIEW_PROPERTY(onDragStart, RCTBubblingEventBlock) 13 | RCT_EXPORT_VIEW_PROPERTY(onDrag, RCTBubblingEventBlock) 14 | RCT_EXPORT_VIEW_PROPERTY(onDragEnd, RCTBubblingEventBlock) 15 | 16 | RCT_EXTERN_METHOD(update:(nonnull NSNumber *)reactTag) 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /lib/ios/MapView/MarkerManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapMarkerManager) 2 | class AMapMarkerManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | 5 | override func view() -> UIView { 6 | let view = Marker() 7 | view.imageLoader = bridge.module(forName: "ImageLoader") as? RCTImageLoader 8 | return view 9 | } 10 | 11 | @objc func update(_ reactTag: NSNumber) { 12 | getView(reactTag: reactTag) { view in view.update() } 13 | } 14 | 15 | func getView(reactTag: NSNumber, callback: @escaping (Marker) -> Void) { 16 | bridge.uiManager.addUIBlock { _, viewRegistry in 17 | callback(viewRegistry![reactTag] as! Marker) 18 | } 19 | } 20 | } 21 | 22 | class Marker: UIView { 23 | var imageLoader: RCTImageLoader? 24 | var view: MAAnnotationView? 25 | var annotation = MAPointAnnotation() 26 | var icon: UIImage? 27 | var iconView: UIView? 28 | var centerOffset: CGPoint? 29 | 30 | @objc var draggable = false { didSet { view?.isDraggable = draggable } } 31 | @objc var zIndex = 1 { didSet { view?.zIndex = zIndex } } 32 | 33 | @objc var onPress: RCTDirectEventBlock = { _ in } 34 | @objc var onDragStart: RCTDirectEventBlock = { _ in } 35 | @objc var onDrag: RCTDirectEventBlock = { _ in } 36 | @objc var onDragEnd: RCTDirectEventBlock = { _ in } 37 | 38 | @objc func setIcon(_ icon: NSDictionary?) { 39 | imageLoader?.loadImage(icon) { image in 40 | self.icon = image 41 | self.view?.image = image 42 | self.updateCenterOffset() 43 | } 44 | } 45 | 46 | @objc func setLatLng(_ coordinate: CLLocationCoordinate2D) { 47 | annotation.coordinate = coordinate 48 | } 49 | 50 | @objc func setCenterOffset(_ centerOffset: CGPoint) { 51 | self.centerOffset = centerOffset 52 | view?.centerOffset = centerOffset 53 | } 54 | 55 | override func didAddSubview(_ subview: UIView) { 56 | subview.layer.opacity = 0 57 | iconView = subview 58 | } 59 | 60 | /** 61 | * subview 不能直接用作 marker 的 icon,因为在实现点聚合的时候发现,subview 一定概率无法正常 layout,会堆在右上角。 62 | * 于是索性把 subview 渲染成 image,原来用 subview 带来的 offset、点击问题也都不用处理了。 63 | * 正常情况下就把 subview 的 opacity 设成 0,需要渲染的时候才设成 1,渲染然后马上设回 0。 64 | */ 65 | func update() { 66 | if centerOffset == nil, view != nil { 67 | iconView?.layer.opacity = 1 68 | let renderer = UIGraphicsImageRenderer(bounds: iconView!.bounds) 69 | view?.image = renderer.image { context in layer.render(in: context.cgContext) } 70 | iconView?.layer.opacity = 0 71 | updateCenterOffset() 72 | } 73 | } 74 | 75 | func updateCenterOffset() { 76 | if centerOffset == nil, view != nil { 77 | let size: CGSize = (view?.image.size)! 78 | view?.centerOffset = CGPoint(x: 0, y: -size.height / 2) 79 | } 80 | } 81 | 82 | func getView() -> MAAnnotationView { 83 | if view == nil { 84 | view = MAAnnotationView(annotation: annotation, reuseIdentifier: nil) 85 | if icon == nil, iconView == nil { 86 | view?.image = MAPinAnnotationView(annotation: annotation, reuseIdentifier: nil).image 87 | } 88 | view?.isDraggable = draggable 89 | view?.zIndex = zIndex 90 | if centerOffset != nil { 91 | view?.centerOffset = centerOffset! 92 | } 93 | if icon != nil { 94 | view?.image = icon 95 | updateCenterOffset() 96 | } 97 | } 98 | return view! 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/ios/MapView/MultiPointManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapMultiPointManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(items, NSArray) 6 | RCT_EXPORT_VIEW_PROPERTY(icon, NSDictionary) 7 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /lib/ios/MapView/MultiPointManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapMultiPointManager) 2 | class AMapMultiPointManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | 5 | override func view() -> UIView { 6 | let view = MultiPoint() 7 | view.imageLoader = bridge.module(forName: "ImageLoader") as? RCTImageLoader 8 | return view 9 | } 10 | } 11 | 12 | class MultiPoint: UIView, Overlay, MAMultiPointOverlayRendererDelegate { 13 | var imageLoader: RCTImageLoader? 14 | var overlay: MAMultiPointOverlay? 15 | var renderer: MAMultiPointOverlayRenderer? 16 | var icon: UIImage? 17 | 18 | @objc var onPress: RCTDirectEventBlock = { _ in } 19 | 20 | @objc func setIcon(_ icon: NSDictionary) { 21 | imageLoader?.loadImage(icon) { image in 22 | self.renderer?.icon = image 23 | } 24 | } 25 | 26 | @objc func setItems(_ items: NSArray) { 27 | overlay = MAMultiPointOverlay(multiPointItems: items.map { it -> MAMultiPointItem in 28 | let item = MAMultiPointItem() 29 | item.coordinate = (it as! NSDictionary).coordinate 30 | return item 31 | }) 32 | } 33 | 34 | func getOverlay() -> MABaseOverlay { overlay! } 35 | func getRenderer() -> MAOverlayRenderer { 36 | if renderer == nil { 37 | renderer = MAMultiPointOverlayRenderer(multiPointOverlay: overlay) 38 | renderer?.icon = icon 39 | renderer?.delegate = self 40 | } 41 | return renderer! 42 | } 43 | 44 | func multiPointOverlayRenderer(_: MAMultiPointOverlayRenderer!, didItemTapped item: MAMultiPointItem!) { 45 | onPress(["index": (overlay?.items.firstIndex(of: item))!]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/ios/MapView/Overlay.swift: -------------------------------------------------------------------------------- 1 | protocol Overlay: UIView { 2 | func getOverlay() -> MABaseOverlay 3 | func getRenderer() -> MAOverlayRenderer 4 | } 5 | -------------------------------------------------------------------------------- /lib/ios/MapView/PolygonManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapPolygonManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(points, NSArray) 6 | RCT_EXPORT_VIEW_PROPERTY(strokeWidth, double) 7 | RCT_EXPORT_VIEW_PROPERTY(strokeColor, UIColor) 8 | RCT_EXPORT_VIEW_PROPERTY(fillColor, UIColor) 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /lib/ios/MapView/PolygonManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapPolygonManager) 2 | class AMapPolygonManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | override func view() -> UIView { Polygon() } 5 | } 6 | 7 | class Polygon: UIView, Overlay { 8 | var overlay = MAPolygon() 9 | var renderer: MAPolygonRenderer? 10 | 11 | @objc var strokeWidth = 1.0 { didSet { renderer?.lineWidth = strokeWidth } } 12 | @objc var strokeColor = UIColor.black { didSet { renderer?.strokeColor = strokeColor } } 13 | @objc var fillColor = UIColor.white { didSet { renderer?.fillColor = fillColor } } 14 | 15 | @objc func setPoints(_ points: NSArray) { 16 | var coordinates = points.map { it -> CLLocationCoordinate2D in (it as! NSDictionary).coordinate } 17 | overlay.setPolygonWithCoordinates(&coordinates, count: points.count) 18 | } 19 | 20 | func getOverlay() -> MABaseOverlay { overlay } 21 | func getRenderer() -> MAOverlayRenderer { 22 | if renderer == nil { 23 | renderer = MAPolygonRenderer(polygon: overlay) 24 | renderer?.fillColor = fillColor 25 | renderer?.strokeColor = strokeColor 26 | renderer?.lineWidth = strokeWidth 27 | } 28 | return renderer! 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/ios/MapView/PolylineManager.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapPolylineManager, RCTViewManager) 4 | 5 | RCT_EXPORT_VIEW_PROPERTY(points, NSArray) 6 | RCT_EXPORT_VIEW_PROPERTY(width, double) 7 | RCT_EXPORT_VIEW_PROPERTY(color, UIColor) 8 | RCT_EXPORT_VIEW_PROPERTY(dotted, BOOL) 9 | RCT_EXPORT_VIEW_PROPERTY(gradient, BOOL) 10 | RCT_EXPORT_VIEW_PROPERTY(colors, UIColorArray) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /lib/ios/MapView/PolylineManager.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapPolylineManager) 2 | class AMapPolylineManager: RCTViewManager { 3 | override class func requiresMainQueueSetup() -> Bool { false } 4 | override func view() -> UIView { Polyline() } 5 | } 6 | 7 | class Polyline: UIView, Overlay { 8 | var overlay = MAMultiPolyline() 9 | var renderer: MAMultiColoredPolylineRenderer? 10 | 11 | @objc var width = 1.0 { didSet { renderer?.lineWidth = width } } 12 | @objc var color = UIColor.black { didSet { renderer?.strokeColor = color } } 13 | @objc var gradient = false { didSet { renderer?.isGradient = gradient } } 14 | @objc var dotted = false { didSet { setDotted() } } 15 | @objc var colors: [UIColor] = [] { didSet { 16 | renderer?.strokeColors = colors 17 | overlay.drawStyleIndexes = (0 ..< colors.count).map { it in NSNumber(value: it) } 18 | } } 19 | 20 | @objc func setPoints(_ points: NSArray) { 21 | var coordinates = points.map { it -> CLLocationCoordinate2D in (it as! NSDictionary).coordinate } 22 | overlay.setPolylineWithCoordinates(&coordinates, count: points.count) 23 | } 24 | 25 | func setDotted() { 26 | renderer?.lineDashType = dotted ? kMALineDashTypeDot : kMALineDashTypeNone 27 | } 28 | 29 | func getOverlay() -> MABaseOverlay { overlay } 30 | func getRenderer() -> MAOverlayRenderer { 31 | if renderer == nil { 32 | renderer = MAMultiColoredPolylineRenderer(multiPolyline: overlay) 33 | renderer?.strokeColor = color 34 | renderer?.lineWidth = width 35 | renderer?.isGradient = gradient 36 | renderer?.strokeColors = colors 37 | setDotted() 38 | } 39 | return renderer! 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/ios/Modules/SdkModule.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCT_EXTERN_MODULE(AMapSdk, NSObject) 4 | 5 | RCT_EXTERN_METHOD(initSDK: (NSString)apiKey) 6 | RCT_EXTERN_METHOD(getVersion: (RCTPromiseResolveBlock)resolve reject: (RCTPromiseRejectBlock)_) 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /lib/ios/Modules/SdkModule.swift: -------------------------------------------------------------------------------- 1 | @objc(AMapSdk) 2 | class AMapSdk: NSObject { 3 | @objc static func requiresMainQueueSetup() -> Bool { 4 | false 5 | } 6 | 7 | @objc func initSDK(_ apiKey: String) { 8 | AMapServices.shared().apiKey = apiKey 9 | MAMapView.updatePrivacyAgree(AMapPrivacyAgreeStatus.didAgree) 10 | MAMapView.updatePrivacyShow(AMapPrivacyShowStatus.didShow, privacyInfo: AMapPrivacyInfoStatus.didContain) 11 | } 12 | 13 | @objc func getVersion(_ resolve: RCTPromiseResolveBlock, reject _: RCTPromiseRejectBlock) { 14 | resolve("8.0.1") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/ios/Utils.swift: -------------------------------------------------------------------------------- 1 | extension NSDictionary { 2 | var coordinate: CLLocationCoordinate2D { 3 | CLLocationCoordinate2DMake(self["latitude"] as! Double, self["longitude"] as! Double) 4 | } 5 | 6 | var point: CGPoint { 7 | CGPoint(x: self["x"] as! Double, y: self["y"] as! Double) 8 | } 9 | } 10 | 11 | extension CLLocationCoordinate2D { 12 | var json: [String: Double] { 13 | ["latitude": latitude, "longitude": longitude] 14 | } 15 | } 16 | 17 | extension MAUserLocation { 18 | var json: [String: Any] { 19 | [ 20 | "coords": [ 21 | "latitude": coordinate.latitude, 22 | "longitude": coordinate.longitude, 23 | "altitude": location?.altitude ?? 0, 24 | "heading": heading?.trueHeading, 25 | "accuracy": location?.horizontalAccuracy ?? 0, 26 | "speed": location?.speed ?? 0, 27 | ], 28 | "timestamp": NSDate().timeIntervalSince1970 * 1000, 29 | ] 30 | } 31 | } 32 | 33 | extension MACoordinateRegion { 34 | var json: [String: Any] { 35 | [ 36 | "southwest": [ 37 | "latitude": center.latitude - span.latitudeDelta / 2, 38 | "longitude": center.longitude - span.longitudeDelta / 2, 39 | ], 40 | "northeast": [ 41 | "latitude": center.latitude + span.latitudeDelta / 2, 42 | "longitude": center.longitude + span.longitudeDelta / 2, 43 | ], 44 | ] 45 | } 46 | } 47 | 48 | extension MAMapStatus { 49 | var json: [String: Any] { 50 | [ 51 | "target": centerCoordinate.json, 52 | "zoom": zoomLevel, 53 | "bearing": rotationDegree, 54 | "tilt": cameraDegree, 55 | ] 56 | } 57 | } 58 | 59 | extension MAMapView { 60 | var cameraEvent: [String: Any] { 61 | [ 62 | "cameraPosition": getMapStatus().json, 63 | "latLngBounds": region.json, 64 | ] 65 | } 66 | } 67 | 68 | extension Double { 69 | var cgFloat: CGFloat { 70 | CGFloat(self) 71 | } 72 | } 73 | 74 | extension RCTConvert { 75 | @objc static func MAMapType(_ json: Any) -> MAMapType { 76 | MAMapKit.MAMapType(rawValue: json as! NSInteger)! 77 | } 78 | } 79 | 80 | extension RCTImageLoader { 81 | func loadImage(_ icon: NSDictionary?, callback: @escaping (UIImage) -> Void) { 82 | if icon == nil { 83 | return 84 | } 85 | let width = icon?["width"] as? Double ?? 0 86 | let height = icon?["height"] as? Double ?? 0 87 | loadImage( 88 | with: RCTConvert.nsurlRequest(icon), 89 | size: CGSize(width: width, height: height), 90 | scale: RCTScreenScale(), 91 | clipped: false, 92 | resizeMode: RCTResizeMode.cover, 93 | progressBlock: { _, _ in }, 94 | partialLoad: { _ in }, 95 | completionBlock: { _, image in 96 | if image != nil { 97 | DispatchQueue.main.async { 98 | callback(image!) 99 | } 100 | } 101 | } 102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/ios/react-native-amap3d.podspec: -------------------------------------------------------------------------------- 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 = "react-native-amap3d" 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 => "10.0" } 14 | s.source = { :git => "https://github.com/qiuxiang/react-native-amap3d.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = "**/*.{h,m,mm,swift}" 17 | 18 | s.dependency "React-Core" 19 | s.dependency 'AMap3DMap', "~> 9.6.0" 20 | end 21 | -------------------------------------------------------------------------------- /lib/src/circle.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeComponent } from "react-native"; 2 | import { LatLng } from "./types"; 3 | 4 | export interface CircleProps { 5 | /** 6 | * 圆点坐标 7 | */ 8 | center: LatLng; 9 | 10 | /** 11 | * 半径(米) 12 | */ 13 | radius: number; 14 | 15 | /** 16 | * 边线宽度 17 | */ 18 | strokeWidth?: number; 19 | 20 | /** 21 | * 边线颜色 22 | */ 23 | strokeColor?: string; 24 | 25 | /** 26 | * 填充颜色 27 | */ 28 | fillColor?: string; 29 | 30 | /** 31 | * 层级 32 | */ 33 | zIndex?: number; 34 | } 35 | 36 | export default requireNativeComponent("AMapCircle"); 37 | -------------------------------------------------------------------------------- /lib/src/cluster/cluster-view.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { StyleSheet, Text, View, ViewStyle } from "react-native"; 3 | import { ClusterParams } from "."; 4 | import Marker from "../marker"; 5 | 6 | interface Props { 7 | cluster: ClusterParams; 8 | style?: ViewStyle; 9 | textStyle?: ViewStyle; 10 | onPress?: (params: ClusterParams) => void; 11 | } 12 | 13 | export default class ClusterView extends React.PureComponent { 14 | onPress = () => { 15 | this.props.onPress?.call(this, this.props.cluster); 16 | }; 17 | 18 | renderClusterView = () => { 19 | const { count } = this.props.cluster; 20 | const size = 36 + Math.log2(count); 21 | const clusterStyle = { width: size, height: size, borderRadius: size / 2 }; 22 | return ( 23 | 24 | {count} 25 | 26 | ); 27 | }; 28 | 29 | render() { 30 | return ( 31 | 32 | {this.renderClusterView()} 33 | 34 | ); 35 | } 36 | } 37 | 38 | const style = StyleSheet.create({ 39 | cluster: { 40 | borderWidth: 4, 41 | borderColor: "#fff", 42 | backgroundColor: "rgba(245,83,61,0.9)", 43 | alignItems: "center", 44 | justifyContent: "center", 45 | }, 46 | text: { color: "#fff", fontWeight: "600" }, 47 | }); 48 | -------------------------------------------------------------------------------- /lib/src/cluster/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ViewStyle } from "react-native"; 3 | import Supercluster, { ClusterFeature, ClusterProperties } from "supercluster"; 4 | import { CameraEvent } from "../map-view"; 5 | import { LatLng } from "../types"; 6 | import ClusterView from "./cluster-view"; 7 | 8 | export interface ClusterParams { 9 | /** 10 | * 唯一标识 11 | */ 12 | id: number; 13 | 14 | /** 15 | * 包含的 Marker 数量 16 | */ 17 | count: number; 18 | 19 | /** 20 | * 坐标 21 | */ 22 | position: LatLng; 23 | } 24 | 25 | interface ClusterPoint { 26 | /** 27 | * 坐标 28 | */ 29 | position: LatLng; 30 | 31 | /** 32 | * 携带的数据,可以是任意类型 33 | */ 34 | properties?: any; 35 | } 36 | 37 | interface Props { 38 | /** 39 | * 聚合半径 40 | */ 41 | radius?: number; 42 | 43 | /** 44 | * 聚合点样式 45 | */ 46 | clusterStyle?: ViewStyle; 47 | 48 | /** 49 | * 聚合点文本样式 50 | */ 51 | clusterTextStyle?: ViewStyle; 52 | 53 | /** 54 | * 坐标点列表 55 | */ 56 | points: ClusterPoint[]; 57 | 58 | /** 59 | * 渲染 Marker 60 | */ 61 | renderMarker: (item: ClusterPoint) => React.ReactNode; 62 | 63 | /** 64 | * 渲染聚合点 65 | */ 66 | renderCluster?: (params: ClusterParams) => React.ComponentType; 67 | 68 | /** 69 | * 聚合点点击事件 70 | */ 71 | onPress?: (params: ClusterParams) => void; 72 | } 73 | 74 | interface State { 75 | clusters: ClusterFeature[]; 76 | } 77 | 78 | export default class Cluster extends React.PureComponent { 79 | static defaultProps = { radius: 200 }; 80 | state: State = { clusters: [] }; 81 | status?: CameraEvent; 82 | cluster?: Supercluster; 83 | 84 | componentDidMount() { 85 | this.init(); 86 | } 87 | 88 | componentDidUpdate(props: Props) { 89 | if (props.points != this.props.points) { 90 | this.init(); 91 | } 92 | } 93 | 94 | async init() { 95 | const { radius, points } = this.props; 96 | // 如果主线程占用太多计算资源,会导致 ios onLoad 事件无法触发,非常蛋疼 97 | // 暂时想到的解决办法是等一个事件循环 98 | await new Promise((resolve) => setTimeout(resolve, 0)); 99 | const options = { radius, minZoom: 3, maxZoom: 21 }; 100 | this.cluster = new Supercluster(options).load( 101 | points.map((marker) => ({ 102 | type: "Feature", 103 | geometry: { 104 | type: "Point", 105 | coordinates: [marker.position.longitude, marker.position.latitude], 106 | }, 107 | properties: marker.properties, 108 | })) 109 | ); 110 | if (this.status) { 111 | this.update(this.status); 112 | } 113 | } 114 | 115 | /** 116 | * 需要在 MapView.onCameraIdle({ nativeEvent }) 回调里调用,参数为 nativeEvent 117 | */ 118 | async update(status: CameraEvent) { 119 | this.status = status; 120 | await new Promise((resolve) => setTimeout(resolve, 0)); 121 | const { cameraPosition, latLngBounds } = status; 122 | const { southwest, northeast } = latLngBounds; 123 | const clusters = this.cluster!.getClusters( 124 | [southwest.longitude, southwest.latitude, northeast.longitude, northeast.latitude], 125 | Math.round(cameraPosition.zoom!) 126 | ); 127 | this.setState({ clusters }); 128 | } 129 | 130 | renderCluster = (cluster: ClusterParams) => { 131 | return ( 132 | 139 | ); 140 | }; 141 | 142 | render() { 143 | const { renderCluster, renderMarker } = this.props; 144 | const render = renderCluster || this.renderCluster; 145 | return this.state.clusters.map(({ geometry, properties }) => { 146 | const position = { 147 | latitude: geometry.coordinates[1], 148 | longitude: geometry.coordinates[0], 149 | }; 150 | 151 | if (properties.point_count > 0) { 152 | const { cluster_id, point_count } = properties; 153 | return render({ position, id: cluster_id, count: point_count }); 154 | } 155 | 156 | return renderMarker({ position, properties }); 157 | }); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/src/component.ts: -------------------------------------------------------------------------------- 1 | import { PureComponent } from "react"; 2 | import { findNodeHandle, UIManager } from "react-native"; 3 | 4 | export default class Component extends PureComponent { 5 | /** 6 | * 原生 View 名称,继承时必须指定 7 | */ 8 | name = ""; 9 | mounted = false; 10 | 11 | componentDidMount() { 12 | this.mounted = true; 13 | } 14 | 15 | componentWillUnmount() { 16 | this.mounted = false; 17 | } 18 | 19 | /** 20 | * 调用原生方法 21 | */ 22 | invoke(name: string, params?: any[]) { 23 | if (!this.mounted) return; 24 | 25 | const handle = findNodeHandle(this); 26 | if (handle) { 27 | const command = UIManager.getViewManagerConfig(this.name).Commands[name]; 28 | UIManager.dispatchViewManagerCommand(handle, command, params); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/heat-map.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeComponent } from "react-native"; 2 | import { LatLng } from "./types"; 3 | 4 | export interface HeatMapProps { 5 | /** 6 | * 节点坐标 7 | */ 8 | data: LatLng[]; 9 | 10 | /** 11 | * 半径(米) 12 | */ 13 | radius?: number; 14 | 15 | /** 16 | * 透明度 17 | */ 18 | opacity?: number; 19 | } 20 | 21 | export default requireNativeComponent("AMapHeatMap"); 22 | -------------------------------------------------------------------------------- /lib/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Circle } from "./circle"; 2 | export { default as Cluster } from "./cluster"; 3 | export { default as HeatMap } from "./heat-map"; 4 | export { default as MapView } from "./map-view"; 5 | export { default as Marker } from "./marker"; 6 | export { default as MultiPoint } from "./multi-point"; 7 | export { default as Polygon } from "./polygon"; 8 | export { default as Polyline } from "./polyline"; 9 | export * from "./types"; 10 | export { AMapSdk }; 11 | import * as AMapSdk from "./sdk"; 12 | -------------------------------------------------------------------------------- /lib/src/map-view.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | NativeMethods, 4 | NativeSyntheticEvent, 5 | requireNativeComponent, 6 | ViewProps, 7 | } from "react-native"; 8 | import Component from "./component"; 9 | import { CameraPosition, LatLng, LatLngBounds, MapPoi, MapType, Point } from "./types"; 10 | 11 | export interface CameraEvent { 12 | cameraPosition: CameraPosition; 13 | latLngBounds: LatLngBounds; 14 | } 15 | 16 | export interface MapViewProps extends ViewProps { 17 | /** 18 | * 地图类型 19 | */ 20 | mapType?: MapType; 21 | 22 | /** 23 | * 初始状态 24 | */ 25 | initialCameraPosition?: CameraPosition; 26 | 27 | /** 28 | * 是否显示当前定位 29 | */ 30 | myLocationEnabled?: boolean; 31 | 32 | /** 33 | * 是否显示室内地图 34 | */ 35 | indoorViewEnabled?: boolean; 36 | 37 | /** 38 | * 是否显示3D建筑 39 | */ 40 | buildingsEnabled?: boolean; 41 | 42 | /** 43 | * 是否显示标注 44 | */ 45 | labelsEnabled?: boolean; 46 | 47 | /** 48 | * 是否显示指南针 49 | */ 50 | compassEnabled?: boolean; 51 | 52 | /** 53 | * 是否显示放大缩小按钮 54 | * 55 | * @platform android 56 | */ 57 | zoomControlsEnabled?: boolean; 58 | 59 | /** 60 | * 是否显示比例尺 61 | */ 62 | scaleControlsEnabled?: boolean; 63 | 64 | /** 65 | * 是否显示定位按钮 66 | * 67 | * @platform android 68 | */ 69 | myLocationButtonEnabled?: boolean; 70 | 71 | /** 72 | * 是否显示路况 73 | */ 74 | trafficEnabled?: boolean; 75 | 76 | /** 77 | * 最大缩放级别 78 | */ 79 | maxZoom?: number; 80 | 81 | /** 82 | * 最小缩放级别 83 | */ 84 | minZoom?: number; 85 | 86 | /** 87 | * 是否启用缩放手势,用于放大缩小 88 | */ 89 | zoomGesturesEnabled?: boolean; 90 | 91 | /** 92 | * 是否启用滑动手势,用于平移 93 | */ 94 | scrollGesturesEnabled?: boolean; 95 | 96 | /** 97 | * 是否启用旋转手势,用于调整方向 98 | */ 99 | rotateGesturesEnabled?: boolean; 100 | 101 | /** 102 | * 是否启用倾斜手势,用于改变视角 103 | */ 104 | tiltGesturesEnabled?: boolean; 105 | 106 | /** 107 | * 设定定位的最小更新距离 108 | * 109 | * @platform ios 110 | */ 111 | distanceFilter?: number; 112 | 113 | /** 114 | * 设定最小更新角度,默认为 1 度 115 | * 116 | * @platform ios 117 | */ 118 | headingFilter?: number; 119 | 120 | /** 121 | * 点击事件 122 | */ 123 | onPress?: (event: NativeSyntheticEvent) => void; 124 | 125 | /** 126 | * 标注点击事件 127 | */ 128 | onPressPoi?: (event: NativeSyntheticEvent) => void; 129 | 130 | /** 131 | * 长按事件 132 | */ 133 | onLongPress?: (event: NativeSyntheticEvent) => void; 134 | 135 | /** 136 | * 地图状态改变事件,随地图状态变化不停地触发 137 | */ 138 | onCameraMove?: (event: NativeSyntheticEvent) => void; 139 | 140 | /** 141 | * 地图状态改变事件,在停止变化后触发 142 | */ 143 | onCameraIdle?: (event: NativeSyntheticEvent) => void; 144 | 145 | /** 146 | * 地图初始化完成事件 147 | */ 148 | onLoad?: (event: NativeSyntheticEvent) => void; 149 | 150 | /** 151 | * 地图定位更新事件 152 | */ 153 | onLocation?: (event: NativeSyntheticEvent) => void; 154 | } 155 | 156 | const name = "AMapView"; 157 | const NativeMapView = requireNativeComponent(name); 158 | 159 | export default class extends Component { 160 | static defaultProps = { 161 | style: { flex: 1 }, 162 | compassEnabled: true, 163 | scaleControlsEnabled: true, 164 | distanceFilter: 1, 165 | }; 166 | 167 | name = name; 168 | ref?: (React.Component & NativeMethods) | null; 169 | state = { loaded: false }; 170 | callbackMap: { [key: number]: (data: any) => void } = {}; 171 | 172 | /** 173 | * 移动视角 174 | */ 175 | moveCamera(cameraPosition: CameraPosition, duration = 0) { 176 | this.invoke("moveCamera", [cameraPosition, duration]); 177 | } 178 | 179 | /** 180 | * 点坐标转地理坐标,主要用于地图选点 181 | */ 182 | getLatLng(point: Point): Promise { 183 | return this.call("getLatLng", point); 184 | } 185 | 186 | callback = ({ nativeEvent }: NativeSyntheticEvent<{ id: number; data: any }>) => { 187 | this.callbackMap[nativeEvent.id]?.call(this, nativeEvent.data); 188 | delete this.callbackMap[nativeEvent.id]; 189 | }; 190 | 191 | call(name: string, args: any): Promise { 192 | const id = Math.random(); 193 | this.invoke("call", [id, name, args]); 194 | return new Promise((resolve) => (this.callbackMap[id] = resolve)); 195 | } 196 | 197 | componentDidMount() { 198 | super.componentDidMount(); 199 | // 无论如何也要在 1 秒后 setLoaded(true) ,防止 onLoad 事件不触发的情况下显示不正常 200 | // 目前只在 iOS 上低概率出现 201 | setTimeout(() => this.setState({ loaded: true }), 1000); 202 | } 203 | 204 | render() { 205 | let { style, onLoad } = this.props; 206 | if (!this.state.loaded) { 207 | style = [style, { width: 1, height: 1 }]; 208 | } 209 | return ( 210 | (this.ref = ref)} 213 | style={style} 214 | // @ts-ignore: 内部接口 215 | onCallback={this.callback} 216 | onPress={(event) => { 217 | if (event.nativeEvent.latitude) { 218 | this.props.onPress?.call(this, event); 219 | } 220 | }} 221 | onLoad={(event) => { 222 | // android 地图部分控件不显示的问题在重新 layout 之后会恢复正常。 223 | // 同时也能修复 ios 地图偶尔出现的 layout 异常 224 | this.setState({ loaded: true }); 225 | onLoad?.call(this, event); 226 | }} 227 | /> 228 | ); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /lib/src/marker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { 3 | ImageSourcePropType, 4 | NativeSyntheticEvent, 5 | requireNativeComponent, 6 | View, 7 | ViewStyle, 8 | } from "react-native"; 9 | // @ts-ignore 10 | import resolveAssetSource from "react-native/Libraries/Image/resolveAssetSource"; 11 | import Component from "./component"; 12 | import { LatLng, Point } from "./types"; 13 | 14 | export interface MarkerProps { 15 | /** 16 | * 坐标 17 | */ position: LatLng; 18 | 19 | /** 20 | * 图标 21 | */ 22 | icon?: ImageSourcePropType; 23 | 24 | /** 25 | * 透明度 [0, 1] 26 | * 27 | * @platform android 28 | */ 29 | opacity?: number; 30 | 31 | /** 32 | * 是否可拖拽 33 | */ 34 | draggable?: boolean; 35 | 36 | /** 37 | * 是否平贴地图 38 | * 39 | * @platform android 40 | */ 41 | flat?: boolean; 42 | 43 | /** 44 | * 层级 45 | */ 46 | zIndex?: number; 47 | 48 | /** 49 | * 覆盖物锚点比例 50 | * 51 | * @link http://a.amap.com/lbs/static/unzip/Android_Map_Doc/3D/com/amap/api/maps/model/Marker.html#setAnchor-float-float- 52 | * @platform android 53 | */ 54 | anchor?: Point; 55 | 56 | /** 57 | * 覆盖物偏移位置 58 | * 59 | * @link http://a.amap.com/lbs/static/unzip/iOS_Map_Doc/AMap_iOS_API_Doc_3D/interface_m_a_annotation_view.html#a78f23c1e6a6d92faf12a00877ac278a7 60 | * @platform ios 61 | */ 62 | centerOffset?: Point; 63 | 64 | /** 65 | * 自定义 View 66 | */ 67 | children?: React.ReactNode; 68 | 69 | /** 70 | * 点击事件 71 | */ 72 | onPress?: () => void; 73 | 74 | /** 75 | * 拖放开始事件 76 | */ 77 | onDragStart?: () => void; 78 | 79 | /** 80 | * 拖放进行事件,类似于 mousemove,在结束之前会不断调用 81 | */ 82 | onDrag?: () => void; 83 | 84 | /** 85 | * 拖放结束事件 86 | */ 87 | onDragEnd?: (event: NativeSyntheticEvent) => void; 88 | } 89 | 90 | export default class extends Component { 91 | name = name; 92 | 93 | /** 94 | * 触发自定义 view 更新 95 | * 96 | * 通常来说,不需要主动调用该方法,对于 android,如果自定义 view 存在异步更新, 97 | * 例如,包含一个引用了网络图片的 ,则需要在 view 更新后主动调用该方法触发 98 | * icon 更新。 99 | */ 100 | update = () => { 101 | setTimeout(() => this.invoke("update"), 0); 102 | }; 103 | 104 | componentDidUpdate() { 105 | if (this.props.children) { 106 | this.update(); 107 | } 108 | } 109 | 110 | render() { 111 | const props = { ...this.props }; 112 | Reflect.set(props, "latLng", props.position); 113 | // @ts-ignore android 不能用 position 作为属性,会发生冲突,也是个蛋疼的问题 114 | delete props.position; 115 | if (props.children) { 116 | props.children = ( 117 | 118 | {props.children} 119 | 120 | ); 121 | } 122 | return ; 123 | } 124 | } 125 | 126 | const name = "AMapMarker"; 127 | const style: ViewStyle = { position: "absolute", zIndex: -1 }; 128 | const NativeMarker = requireNativeComponent(name); 129 | -------------------------------------------------------------------------------- /lib/src/multi-point.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ImageSourcePropType, NativeSyntheticEvent, requireNativeComponent } from "react-native"; 3 | // @ts-ignore 4 | import resolveAssetSource from "react-native/Libraries/Image/resolveAssetSource"; 5 | import { LatLng } from "./types"; 6 | 7 | const NativeMultiPoint = requireNativeComponent("AMapMultiPoint"); 8 | 9 | export interface MultiPointProps { 10 | /** 11 | * 坐标点集合 12 | */ 13 | items?: LatLng[]; 14 | 15 | /** 16 | * 图标 17 | */ 18 | icon?: ImageSourcePropType; 19 | 20 | /** 21 | * 点击事件 22 | */ 23 | onPress?: (event: NativeSyntheticEvent<{ index: number }>) => void; 24 | } 25 | 26 | export default (props: MultiPointProps) => { 27 | return ; 28 | }; 29 | -------------------------------------------------------------------------------- /lib/src/polygon.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeComponent } from "react-native"; 2 | import { LatLng } from "./types"; 3 | 4 | export interface Polygon { 5 | /** 6 | * 节点坐标 7 | */ 8 | points: LatLng[]; 9 | 10 | /** 11 | * 边线宽度 12 | */ 13 | strokeWidth?: number; 14 | 15 | /** 16 | * 边线颜色 17 | */ 18 | strokeColor?: string; 19 | 20 | /** 21 | * 填充颜色 22 | */ 23 | fillColor?: string; 24 | 25 | /** 26 | * 层级 27 | */ 28 | zIndex?: number; 29 | } 30 | 31 | export default requireNativeComponent("AMapPolygon"); 32 | -------------------------------------------------------------------------------- /lib/src/polyline.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ColorValue, Platform, processColor, requireNativeComponent } from "react-native"; 3 | import { LatLng } from "./types"; 4 | 5 | export interface PolylineProps { 6 | /** 7 | * 节点坐标 8 | */ 9 | points: LatLng[]; 10 | 11 | /** 12 | * 线段宽度 13 | */ 14 | width?: number; 15 | 16 | /** 17 | * 线段颜色 18 | */ 19 | color?: ColorValue; 20 | 21 | /** 22 | * 层级 23 | */ 24 | zIndex?: number; 25 | 26 | /** 27 | * 多段颜色 28 | */ 29 | colors: ColorValue[]; 30 | 31 | /** 32 | * 是否使用颜色渐变 33 | */ 34 | gradient?: boolean; 35 | 36 | /** 37 | * 是否绘制大地线 38 | */ 39 | geodesic?: boolean; 40 | 41 | /** 42 | * 是否绘制虚线 43 | */ 44 | dotted?: boolean; 45 | 46 | /** 47 | * 点击事件 48 | */ 49 | onPress?: () => void; 50 | } 51 | 52 | export default class extends React.PureComponent { 53 | static defaultProps = { colors: [] }; 54 | 55 | render() { 56 | const props = { 57 | ...this.props, 58 | ...Platform.select({ android: { colors: this.props.colors.map(processColor) } }), 59 | }; 60 | // @ts-ignore 61 | return ; 62 | } 63 | } 64 | 65 | const NativePolyline = requireNativeComponent("AMapPolyline"); 66 | -------------------------------------------------------------------------------- /lib/src/sdk.ts: -------------------------------------------------------------------------------- 1 | import { NativeModules } from "react-native"; 2 | 3 | const { AMapSdk } = NativeModules; 4 | 5 | export function init(apiKey?: string) { 6 | AMapSdk.initSDK(apiKey); 7 | } 8 | 9 | export function getVersion(): Promise { 10 | return AMapSdk.getVersion(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 点坐标 3 | */ 4 | export interface Point { 5 | x: number; 6 | y: number; 7 | } 8 | 9 | /** 10 | * 地理坐标 11 | */ 12 | export interface LatLng { 13 | /** 14 | * 纬度 15 | */ 16 | latitude: number; 17 | 18 | /** 19 | * 经度 20 | */ 21 | longitude: number; 22 | } 23 | 24 | /** 25 | * 地图标注点 26 | */ 27 | export interface MapPoi { 28 | /** 29 | * 标注点 ID 30 | */ 31 | id: string; 32 | 33 | /** 34 | * 标注点名称 35 | */ 36 | name: string; 37 | 38 | /** 39 | * 标注点坐标 40 | */ 41 | position: LatLng; 42 | } 43 | 44 | /** 45 | * 矩形坐标边界 46 | */ 47 | export interface LatLngBounds { 48 | /** 49 | * 西南坐标 50 | */ 51 | southwest: LatLng; 52 | 53 | /** 54 | * 东北坐标 55 | */ 56 | northeast: LatLng; 57 | } 58 | 59 | /** 60 | * 地图状态 61 | */ 62 | export interface CameraPosition { 63 | /** 64 | * 中心坐标 65 | */ 66 | target?: LatLng; 67 | 68 | /** 69 | * 缩放级别 70 | */ 71 | zoom?: number; 72 | 73 | /** 74 | * 朝向、旋转角度 75 | */ 76 | bearing?: number; 77 | 78 | /** 79 | * 倾斜角度 80 | */ 81 | tilt?: number; 82 | } 83 | 84 | /** 85 | * 定位 86 | */ 87 | export interface Location extends LatLng { 88 | /** 89 | * 精度 90 | */ 91 | accuracy: number; 92 | 93 | /** 94 | * 朝向 95 | */ 96 | heading: number; 97 | 98 | /** 99 | * 海拔 100 | */ 101 | altitude: number; 102 | 103 | /** 104 | * 运动速度 105 | */ 106 | speed: number; 107 | } 108 | 109 | /** 110 | * 地图类型 111 | */ 112 | export enum MapType { 113 | /** 114 | * 标准地图 115 | */ 116 | Standard, 117 | 118 | /** 119 | * 卫星地图 120 | */ 121 | Satellite, 122 | 123 | /** 124 | * 夜间地图 125 | */ 126 | Night, 127 | 128 | /** 129 | * 导航地图 130 | */ 131 | Navi, 132 | 133 | /** 134 | * 公交地图 135 | */ 136 | Bus, 137 | } 138 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 7c00 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-amap3d", 3 | "version": "0.0.0", 4 | "description": "react-native 高德地图组件,支持 Android + iOS", 5 | "author": "7c00 ", 6 | "license": "MIT", 7 | "keywords": [ 8 | "react-native", 9 | "amap", 10 | "maps", 11 | "mapview" 12 | ], 13 | "main": "lib/src", 14 | "files": [ 15 | "lib/src", 16 | "lib/android/src", 17 | "lib/android/build.gradle", 18 | "lib/ios", 19 | "react-native.config.js", 20 | "react-native-amap3d.podspec" 21 | ], 22 | "homepage": "https://github.com/qiuxiang/react-native-amap3d", 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/qiuxiang/react-native-amap3d" 26 | }, 27 | "scripts": { 28 | "start": "react-native start", 29 | "android": "react-native run-android", 30 | "release-android": "react-native run-android --variant=release", 31 | "reload": "adb reverse tcp:8081 tcp:8081 && adb shell input text rr", 32 | "ios": "react-native run-ios" 33 | }, 34 | "dependencies": { 35 | "supercluster": "^7.1.4" 36 | }, 37 | "devDependencies": { 38 | "@react-native-picker/picker": "^2.4.8", 39 | "@react-navigation/native": "^6.1.3", 40 | "@react-navigation/native-stack": "^6.9.9", 41 | "@types/react-native": "^0.71.2", 42 | "@types/supercluster": "^7.1.0", 43 | "react": "18.2.0", 44 | "react-native": "^0.71.2", 45 | "react-native-safe-area-context": "^4.5.0", 46 | "react-native-screens": "^3.19.0" 47 | }, 48 | "prettier": { 49 | "printWidth": 100 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /react-native-amap3d.podspec: -------------------------------------------------------------------------------- 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 = "react-native-amap3d" 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 => "10.0" } 14 | s.source = { :git => "https://github.com/qiuxiang/react-native-amap3d.git", :tag => "#{s.version}" } 15 | 16 | s.source_files = "lib/ios/**/*.{h,m,mm,swift}" 17 | 18 | s.dependency "React-Core" 19 | s.dependency 'AMap3DMap', "~> 9.6.0" 20 | end 21 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dependency: { 3 | platforms: { 4 | ios: { project: "lib/ios/react-native-amap3d.podspec" }, 5 | android: { sourceDir: "lib/android" }, 6 | }, 7 | }, 8 | dependencies: { 9 | "react-native-amap3d": { 10 | root: __dirname, 11 | platforms: { 12 | android: { 13 | sourceDir: __dirname + "/lib/android", 14 | packageImportPath: "import qiuxiang.amap3d.AMap3DPackage;", 15 | packageInstance: "new AMap3DPackage()", 16 | }, 17 | }, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # react-native-amap3d [![][version-badge]][npm] [![](https://github.com/qiuxiang/react-native-amap3d/actions/workflows/build.yml/badge.svg)](https://github.com/qiuxiang/react-native-amap3d/actions/workflows/build.yml) 2 | 3 | **注意:该项目目前只维护,不加新功能。** 4 | 5 | react-native 高德地图组件,使用最新 3D SDK,支持 Android + iOS,受 [react-native-maps](https://github.com/airbnb/react-native-maps) 启发,提供功能丰富且易用的接口。 6 | 7 | 相关项目推荐: 8 | 9 | - [react-native-baidumap-sdk(百度地图 SDK)](https://github.com/qiuxiang/react-native-baidumap-sdk) 10 | - [react-native-amap-geolocation(高德地图定位模块)](https://github.com/qiuxiang/react-native-amap-geolocation) 11 | 12 | ## 功能 13 | 14 | - 地图模式切换(常规、卫星、导航、夜间) 15 | - 3D 建筑、路况、室内地图 16 | - 内置地图控件的显示隐藏(指南针、比例尺、定位按钮、缩放按钮) 17 | - 手势交互控制(平移、缩放、旋转、倾斜) 18 | - 中心坐标、缩放级别、倾斜度的设置,支持动画过渡 19 | - 地图事件(onPress、onLongPress、onLocation、onCameraMove、onCameraIdle 等) 20 | - 地图标记(Marker) 21 | - 折线绘制(Polyline) 22 | - 多边形绘制(Polygon) 23 | - 圆形绘制(Circle) 24 | - 热力图(HeatMap) 25 | - 海量点(MultiPoint) 26 | - 点聚合(Cluster) 27 | 28 | ## 接口文档 29 | 30 | https://qiuxiang.github.io/react-native-amap3d/api/ 31 | 32 | ## 安装 33 | 34 | ```bash 35 | npm i react-native-amap3d 36 | ``` 37 | 38 | ### 添加高德 API Key 39 | 40 | 首先你需要获取高德地图 API Key: 41 | 42 | - [Aandroid](http://lbs.amap.com/api/android-sdk/guide/create-project/get-key) 43 | - [iOS](https://lbs.amap.com/api/ios-sdk/guide/create-project/get-key) 44 | 45 | 然后你需要在显示地图前调用接口设置 API key: 46 | 47 | ```js 48 | import { AMapSdk } from "react-native-amap3d"; 49 | import { Platform } from "react-native"; 50 | 51 | AMapSdk.init( 52 | Platform.select({ 53 | android: "c52c7169e6df23490e3114330098aaac", 54 | ios: "186d3464209b74effa4d8391f441f14d", 55 | }) 56 | ); 57 | ``` 58 | 59 | ## 用法 60 | 61 | ### 显示地图 62 | 63 | ```jsx 64 | import { MapView, MapType } from "react-native-amap3d"; 65 | 66 | ; 76 | ``` 77 | 78 | 79 | 80 | ### 监听地图事件 81 | 82 | ```jsx 83 | import { MapView } from "react-native-amap3d"; 84 | 85 | console.log("onLoad")} 87 | onPress={({ nativeEvent }) => console.log(nativeEvent)} 88 | onCameraIdle={({ nativeEvent }) => console.log(nativeEvent)} 89 | />; 90 | ``` 91 | 92 | 93 | 94 | ### 添加标记 95 | 96 | 其中 `icon` 支持 [ImageSource](https://reactnative.dev/docs/image#imagesource)。 97 | 98 | 同时支持 `children` 作为标记图标。 99 | 100 | ```jsx 101 | import { MapView, Marker } from "react-native-amap3d"; 102 | 103 | 104 | alert("onPress")} 108 | /> 109 | 117 | 118 | 127 | {new Date().toLocaleString()} 128 | 129 | 130 | ; 131 | ``` 132 | 133 | 134 | 135 | ### 点聚合 136 | 137 | Marker 数量过多(尤其是使用自定义 View 的情况下)会导致性能问题,而且显示过于密集,这时候可以用点聚合改善。 138 | 139 | ```jsx 140 | import { Cluster, MapView, Marker } from "react-native-amap3d"; 141 | 142 | const markers = Array(1000) 143 | .fill(0) 144 | .map((_, i) => ({ 145 | position: { latitude: 39.5 + Math.random(), longitude: 116 + Math.random() }, 146 | properties: { key: `Marker${i}` }, 147 | })); 148 | 149 | (this.mapView = ref)} 151 | onLoad={() => this.mapView?.moveCamera({ zoom: 8 }, 100)} 152 | onCameraIdle={({ nativeEvent }) => { 153 | this.status = nativeEvent; 154 | this.cluster?.update(nativeEvent); 155 | }} 156 | > 157 | (this.cluster = ref)} 159 | points={markers} 160 | renderMarker={(item) => ( 161 | 166 | )} 167 | /> 168 | ; 169 | ``` 170 | 171 | 172 | 173 | 174 | 175 | ### 更多示例 176 | 177 | 参考 [example](https://github.com/qiuxiang/react-native-amap3d/tree/master/example-app)。 178 | 179 | ## 常见问题 180 | 181 | - 尽量使用真实设备进行测试,在模拟器可能存在一些问题(常见的是 Android 模拟器因为缺少 GPU 加速而导致闪退)。 182 | - onLocation 没有返回定位数据通常是因为 key 不正确,或没有申请 PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION 权限 183 | 184 | [npm]: https://www.npmjs.com/package/react-native-amap3d 185 | [version-badge]: https://img.shields.io/npm/v/react-native-amap3d.svg 186 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "jsx": "react-native", 5 | "esModuleInterop": true, 6 | "baseUrl": ".", 7 | "paths": { 8 | "react-native-amap3d": ["lib/src"] 9 | } 10 | } 11 | } 12 | --------------------------------------------------------------------------------