react-native-platform-maps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/package.json +28 -0
- package/src/LeafletMapView.js +449 -0
- package/src/PlatformMapView.js +37 -0
- package/src/index.js +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# react-native-platform-maps
|
|
2
|
+
|
|
3
|
+
Cross-platform React Native maps wrapper:
|
|
4
|
+
|
|
5
|
+
- Android: Leaflet inside `react-native-webview`
|
|
6
|
+
- iOS: `react-native-maps`
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
Install the package and its peer dependencies in the consumer app:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install react-native-platform-maps
|
|
14
|
+
npx expo install react-native-maps react-native-webview
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with Yarn:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add react-native-platform-maps
|
|
21
|
+
npx expo install react-native-maps react-native-webview
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Local development in this repo
|
|
25
|
+
|
|
26
|
+
The current app can consume this package directly from `package.json` with:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"react-native-platform-maps": "file:./packages/gs-rn-maps"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then run:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
yarn install
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```jsx
|
|
45
|
+
import { MapView, Marker, Callout, PROVIDER_GOOGLE } from 'react-native-platform-maps';
|
|
46
|
+
|
|
47
|
+
<MapView
|
|
48
|
+
style={{ flex: 1 }}
|
|
49
|
+
initialRegion={{
|
|
50
|
+
latitude: 10.8231,
|
|
51
|
+
longitude: 106.6297,
|
|
52
|
+
latitudeDelta: 0.01,
|
|
53
|
+
longitudeDelta: 0.01,
|
|
54
|
+
}}
|
|
55
|
+
provider={PROVIDER_GOOGLE}
|
|
56
|
+
>
|
|
57
|
+
<Marker
|
|
58
|
+
coordinate={{
|
|
59
|
+
latitude: 10.8231,
|
|
60
|
+
longitude: 106.6297,
|
|
61
|
+
}}
|
|
62
|
+
title="Ho Chi Minh City"
|
|
63
|
+
description="Example marker"
|
|
64
|
+
/>
|
|
65
|
+
</MapView>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API
|
|
69
|
+
|
|
70
|
+
Exports:
|
|
71
|
+
|
|
72
|
+
- `MapView`
|
|
73
|
+
- `Marker`
|
|
74
|
+
- `Callout`
|
|
75
|
+
- `PROVIDER`
|
|
76
|
+
- `PROVIDER_DEFAULT`
|
|
77
|
+
- `PROVIDER_GOOGLE`
|
|
78
|
+
- `LeafletMapView`
|
|
79
|
+
|
|
80
|
+
## Android behavior
|
|
81
|
+
|
|
82
|
+
The Android implementation focuses on a compatible subset of the `react-native-maps` API:
|
|
83
|
+
|
|
84
|
+
- `initialRegion`
|
|
85
|
+
- `region`
|
|
86
|
+
- `animateToRegion(...)`
|
|
87
|
+
- `Marker` with `coordinate`, `title`, `description`, `onPress`
|
|
88
|
+
|
|
89
|
+
Rich React marker trees and fully custom callout JSX are not rendered inside the Leaflet WebView.
|
|
90
|
+
|
|
91
|
+
## Publish
|
|
92
|
+
|
|
93
|
+
Before publishing, make sure the package name is available on npm and update metadata if needed.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cd packages/gs-rn-maps
|
|
97
|
+
npm pack --dry-run
|
|
98
|
+
npm publish --access public
|
|
99
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-platform-maps",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-platform React Native maps wrapper using Leaflet on Android and react-native-maps on iOS.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"react-native": "src/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"src",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"expo",
|
|
14
|
+
"maps",
|
|
15
|
+
"leaflet",
|
|
16
|
+
"webview"
|
|
17
|
+
],
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"react": ">=18",
|
|
20
|
+
"react-native": ">=0.81",
|
|
21
|
+
"react-native-maps": ">=1.20.1",
|
|
22
|
+
"react-native-webview": ">=13.15.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"license": "UNLICENSED"
|
|
28
|
+
}
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useCallback,
|
|
6
|
+
forwardRef,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { View, StyleSheet } from 'react-native';
|
|
10
|
+
import { WebView } from 'react-native-webview';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_REGION = {
|
|
13
|
+
latitude: 10.8231,
|
|
14
|
+
longitude: 106.6297,
|
|
15
|
+
latitudeDelta: 0.01,
|
|
16
|
+
longitudeDelta: 0.01,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const hasValidCoordinate = coordinate =>
|
|
20
|
+
Number.isFinite(coordinate?.latitude) && Number.isFinite(coordinate?.longitude);
|
|
21
|
+
|
|
22
|
+
const getZoomFromDelta = latitudeDelta => {
|
|
23
|
+
if (!latitudeDelta) return 15;
|
|
24
|
+
return Math.round(Math.log2(360 / latitudeDelta));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const LeafletMapView = forwardRef((props, ref) => {
|
|
28
|
+
const {
|
|
29
|
+
style,
|
|
30
|
+
initialRegion,
|
|
31
|
+
region,
|
|
32
|
+
children,
|
|
33
|
+
onRegionChange,
|
|
34
|
+
onRegionChangeComplete,
|
|
35
|
+
userLocation,
|
|
36
|
+
} = props;
|
|
37
|
+
|
|
38
|
+
const webViewRef = useRef(null);
|
|
39
|
+
const markersRef = useRef([]);
|
|
40
|
+
const userLocationRef = useRef(null);
|
|
41
|
+
const isMapReadyRef = useRef(false);
|
|
42
|
+
const initialHtmlRegionRef = useRef(region || initialRegion || DEFAULT_REGION);
|
|
43
|
+
|
|
44
|
+
const postMessageToWebView = useCallback(message => {
|
|
45
|
+
if (!webViewRef.current) return;
|
|
46
|
+
webViewRef.current.postMessage(JSON.stringify(message));
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
const updateMarkers = useCallback(() => {
|
|
50
|
+
postMessageToWebView({
|
|
51
|
+
type: 'updateMarkers',
|
|
52
|
+
payload: markersRef.current
|
|
53
|
+
.filter(marker => hasValidCoordinate(marker.coordinate))
|
|
54
|
+
.map(marker => ({
|
|
55
|
+
id: marker.id,
|
|
56
|
+
latitude: marker.coordinate.latitude,
|
|
57
|
+
longitude: marker.coordinate.longitude,
|
|
58
|
+
title: marker.title,
|
|
59
|
+
description: marker.description,
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
}, [postMessageToWebView]);
|
|
63
|
+
|
|
64
|
+
const updateUserLocation = useCallback(coords => {
|
|
65
|
+
if (!hasValidCoordinate(coords) || !isMapReadyRef.current) return;
|
|
66
|
+
userLocationRef.current = coords;
|
|
67
|
+
postMessageToWebView({
|
|
68
|
+
type: 'updateUserLocation',
|
|
69
|
+
payload: coords,
|
|
70
|
+
});
|
|
71
|
+
}, [postMessageToWebView]);
|
|
72
|
+
|
|
73
|
+
const setRegion = useCallback((nextRegion, duration = 0) => {
|
|
74
|
+
if (!hasValidCoordinate(nextRegion) || !isMapReadyRef.current) return;
|
|
75
|
+
postMessageToWebView({
|
|
76
|
+
type: duration > 0 ? 'animateToRegion' : 'setRegion',
|
|
77
|
+
payload: {
|
|
78
|
+
latitude: nextRegion.latitude,
|
|
79
|
+
longitude: nextRegion.longitude,
|
|
80
|
+
zoom: getZoomFromDelta(nextRegion.latitudeDelta),
|
|
81
|
+
duration,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}, [postMessageToWebView]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const markers = [];
|
|
88
|
+
|
|
89
|
+
React.Children.forEach(children, child => {
|
|
90
|
+
if (child && child.type && child.type.displayName === 'LeafletMarker') {
|
|
91
|
+
markers.push({
|
|
92
|
+
id: child.key ?? String(markers.length),
|
|
93
|
+
coordinate: child.props.coordinate,
|
|
94
|
+
title: child.props.title || '',
|
|
95
|
+
description: child.props.description || '',
|
|
96
|
+
onPress: child.props.onPress,
|
|
97
|
+
children: child.props.children,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
markersRef.current = markers;
|
|
103
|
+
|
|
104
|
+
if (isMapReadyRef.current) {
|
|
105
|
+
updateMarkers();
|
|
106
|
+
}
|
|
107
|
+
}, [children, updateMarkers]);
|
|
108
|
+
|
|
109
|
+
useImperativeHandle(ref, () => ({
|
|
110
|
+
animateToRegion: (nextRegion, duration = 500) => {
|
|
111
|
+
setRegion(nextRegion, duration);
|
|
112
|
+
},
|
|
113
|
+
}));
|
|
114
|
+
|
|
115
|
+
const handleMessage = event => {
|
|
116
|
+
try {
|
|
117
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
118
|
+
|
|
119
|
+
switch (data.type) {
|
|
120
|
+
case 'markerPress': {
|
|
121
|
+
const marker = markersRef.current.find(item => item.id === data.markerId);
|
|
122
|
+
if (marker?.onPress) {
|
|
123
|
+
marker.onPress();
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'regionChange':
|
|
128
|
+
onRegionChange?.(data.region);
|
|
129
|
+
break;
|
|
130
|
+
case 'regionChangeComplete':
|
|
131
|
+
onRegionChangeComplete?.(data.region);
|
|
132
|
+
break;
|
|
133
|
+
case 'mapReady':
|
|
134
|
+
isMapReadyRef.current = true;
|
|
135
|
+
updateMarkers();
|
|
136
|
+
if (userLocationRef.current) {
|
|
137
|
+
updateUserLocation(userLocationRef.current);
|
|
138
|
+
} else if (hasValidCoordinate(userLocation)) {
|
|
139
|
+
updateUserLocation(userLocation);
|
|
140
|
+
}
|
|
141
|
+
if (region) {
|
|
142
|
+
setRegion(region);
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn('LeafletMapView: Error parsing message', error);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const htmlContent = useMemo(() => {
|
|
154
|
+
const htmlRegion = initialHtmlRegionRef.current;
|
|
155
|
+
const lat = htmlRegion?.latitude ?? DEFAULT_REGION.latitude;
|
|
156
|
+
const lng = htmlRegion?.longitude ?? DEFAULT_REGION.longitude;
|
|
157
|
+
const zoom = getZoomFromDelta(htmlRegion?.latitudeDelta ?? DEFAULT_REGION.latitudeDelta);
|
|
158
|
+
|
|
159
|
+
return `
|
|
160
|
+
<!DOCTYPE html>
|
|
161
|
+
<html>
|
|
162
|
+
<head>
|
|
163
|
+
<meta charset="utf-8">
|
|
164
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
165
|
+
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
|
166
|
+
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
|
|
167
|
+
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
|
168
|
+
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
|
|
169
|
+
<script src="https://unpkg.com/@maplibre/maplibre-gl-leaflet/leaflet-maplibre-gl.js"></script>
|
|
170
|
+
<style>
|
|
171
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
172
|
+
html, body, #map { width: 100%; height: 100%; }
|
|
173
|
+
.user-location-marker {
|
|
174
|
+
width: 20px;
|
|
175
|
+
height: 20px;
|
|
176
|
+
background: #4285F4;
|
|
177
|
+
border: 3px solid white;
|
|
178
|
+
border-radius: 50%;
|
|
179
|
+
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
|
180
|
+
}
|
|
181
|
+
.user-location-pulse {
|
|
182
|
+
width: 40px;
|
|
183
|
+
height: 40px;
|
|
184
|
+
background: rgba(66, 133, 244, 0.2);
|
|
185
|
+
border-radius: 50%;
|
|
186
|
+
position: absolute;
|
|
187
|
+
top: -10px;
|
|
188
|
+
left: -10px;
|
|
189
|
+
animation: pulse 2s infinite;
|
|
190
|
+
}
|
|
191
|
+
@keyframes pulse {
|
|
192
|
+
0% { transform: scale(0.5); opacity: 1; }
|
|
193
|
+
100% { transform: scale(1.5); opacity: 0; }
|
|
194
|
+
}
|
|
195
|
+
.custom-marker {
|
|
196
|
+
width: 32px;
|
|
197
|
+
height: 32px;
|
|
198
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
199
|
+
border: 2px solid white;
|
|
200
|
+
border-radius: 50%;
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
justify-content: center;
|
|
204
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
205
|
+
}
|
|
206
|
+
.custom-marker svg {
|
|
207
|
+
width: 18px;
|
|
208
|
+
height: 18px;
|
|
209
|
+
fill: white;
|
|
210
|
+
}
|
|
211
|
+
.marker-pin {
|
|
212
|
+
width: 3px;
|
|
213
|
+
height: 8px;
|
|
214
|
+
background: #667eea;
|
|
215
|
+
margin-top: -2px;
|
|
216
|
+
border-bottom-left-radius: 2px;
|
|
217
|
+
border-bottom-right-radius: 2px;
|
|
218
|
+
}
|
|
219
|
+
.leaflet-popup-content-wrapper {
|
|
220
|
+
border-radius: 12px;
|
|
221
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.15);
|
|
222
|
+
}
|
|
223
|
+
.popup-content {
|
|
224
|
+
padding: 4px 0;
|
|
225
|
+
}
|
|
226
|
+
.popup-title {
|
|
227
|
+
font-weight: bold;
|
|
228
|
+
font-size: 14px;
|
|
229
|
+
color: #333;
|
|
230
|
+
margin-bottom: 4px;
|
|
231
|
+
}
|
|
232
|
+
.popup-desc {
|
|
233
|
+
font-size: 12px;
|
|
234
|
+
color: #666;
|
|
235
|
+
}
|
|
236
|
+
</style>
|
|
237
|
+
</head>
|
|
238
|
+
<body>
|
|
239
|
+
<div id="map"></div>
|
|
240
|
+
<script>
|
|
241
|
+
var map = L.map('map', {
|
|
242
|
+
zoomControl: false,
|
|
243
|
+
attributionControl: true
|
|
244
|
+
}).setView([${lat}, ${lng}], ${zoom});
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
if (L.maplibreGL) {
|
|
248
|
+
L.maplibreGL({
|
|
249
|
+
style: 'https://tiles.openfreemap.org/styles/liberty',
|
|
250
|
+
}).addTo(map);
|
|
251
|
+
} else {
|
|
252
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
253
|
+
maxZoom: 19,
|
|
254
|
+
attribution: '© OpenStreetMap contributors'
|
|
255
|
+
}).addTo(map);
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
259
|
+
maxZoom: 19,
|
|
260
|
+
attribution: '© OpenStreetMap contributors'
|
|
261
|
+
}).addTo(map);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
var markers = {};
|
|
265
|
+
var userMarker = null;
|
|
266
|
+
|
|
267
|
+
var shopIcon = L.divIcon({
|
|
268
|
+
className: 'marker-wrapper',
|
|
269
|
+
html: '<div class="custom-marker"><svg viewBox="0 0 24 24"><path d="M18.36 9L18.96 12H5.04L5.64 9H18.36ZM20 4H4V6H20V4ZM20 7H4L3 12V14H4V20H14V14H18V20H20V14H21V12L20 7ZM6 18V14H12V18H6Z"/></svg></div><div class="marker-pin"></div>',
|
|
270
|
+
iconSize: [32, 42],
|
|
271
|
+
iconAnchor: [16, 42],
|
|
272
|
+
popupAnchor: [0, -42]
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
var userIcon = L.divIcon({
|
|
276
|
+
className: 'user-location-wrapper',
|
|
277
|
+
html: '<div class="user-location-pulse"></div><div class="user-location-marker"></div>',
|
|
278
|
+
iconSize: [20, 20],
|
|
279
|
+
iconAnchor: [10, 10]
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
function updateMarkers(markerData) {
|
|
283
|
+
Object.keys(markers).forEach(function(id) {
|
|
284
|
+
map.removeLayer(markers[id]);
|
|
285
|
+
});
|
|
286
|
+
markers = {};
|
|
287
|
+
|
|
288
|
+
markerData.forEach(function(markerDataItem) {
|
|
289
|
+
if (Number.isFinite(markerDataItem.latitude) && Number.isFinite(markerDataItem.longitude)) {
|
|
290
|
+
var marker = L.marker([markerDataItem.latitude, markerDataItem.longitude], { icon: shopIcon });
|
|
291
|
+
|
|
292
|
+
if (markerDataItem.title || markerDataItem.description) {
|
|
293
|
+
var popupContent = '<div class="popup-content">';
|
|
294
|
+
if (markerDataItem.title) popupContent += '<div class="popup-title">' + markerDataItem.title + '</div>';
|
|
295
|
+
if (markerDataItem.description) popupContent += '<div class="popup-desc">' + markerDataItem.description + '</div>';
|
|
296
|
+
popupContent += '</div>';
|
|
297
|
+
marker.bindPopup(popupContent);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
marker.on('click', function() {
|
|
301
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
302
|
+
type: 'markerPress',
|
|
303
|
+
markerId: markerDataItem.id
|
|
304
|
+
}));
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
marker.addTo(map);
|
|
308
|
+
markers[markerDataItem.id] = marker;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function updateUserLocation(coords) {
|
|
314
|
+
if (!coords || !Number.isFinite(coords.latitude) || !Number.isFinite(coords.longitude)) return;
|
|
315
|
+
|
|
316
|
+
var latlng = [coords.latitude, coords.longitude];
|
|
317
|
+
|
|
318
|
+
if (userMarker) {
|
|
319
|
+
userMarker.setLatLng(latlng);
|
|
320
|
+
} else {
|
|
321
|
+
userMarker = L.marker(latlng, { icon: userIcon }).addTo(map);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function setRegion(lat, lng, zoom) {
|
|
326
|
+
map.setView([lat, lng], zoom, { animate: false });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function animateToRegion(lat, lng, zoom, duration) {
|
|
330
|
+
map.flyTo([lat, lng], zoom, { duration: duration / 1000 });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
document.addEventListener('message', function(e) {
|
|
334
|
+
handleMessage(e.data);
|
|
335
|
+
});
|
|
336
|
+
window.addEventListener('message', function(e) {
|
|
337
|
+
handleMessage(e.data);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
function handleMessage(data) {
|
|
341
|
+
try {
|
|
342
|
+
var msg = JSON.parse(data);
|
|
343
|
+
switch(msg.type) {
|
|
344
|
+
case 'updateMarkers':
|
|
345
|
+
updateMarkers(msg.payload);
|
|
346
|
+
break;
|
|
347
|
+
case 'updateUserLocation':
|
|
348
|
+
updateUserLocation(msg.payload);
|
|
349
|
+
break;
|
|
350
|
+
case 'setRegion':
|
|
351
|
+
setRegion(
|
|
352
|
+
msg.payload.latitude,
|
|
353
|
+
msg.payload.longitude,
|
|
354
|
+
msg.payload.zoom
|
|
355
|
+
);
|
|
356
|
+
break;
|
|
357
|
+
case 'animateToRegion':
|
|
358
|
+
animateToRegion(
|
|
359
|
+
msg.payload.latitude,
|
|
360
|
+
msg.payload.longitude,
|
|
361
|
+
msg.payload.zoom,
|
|
362
|
+
msg.payload.duration
|
|
363
|
+
);
|
|
364
|
+
break;
|
|
365
|
+
default:
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
} catch (e) {}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
map.whenReady(function() {
|
|
372
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'mapReady' }));
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
map.on('moveend', function() {
|
|
376
|
+
var center = map.getCenter();
|
|
377
|
+
var bounds = map.getBounds();
|
|
378
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
379
|
+
type: 'regionChangeComplete',
|
|
380
|
+
region: {
|
|
381
|
+
latitude: center.lat,
|
|
382
|
+
longitude: center.lng,
|
|
383
|
+
latitudeDelta: bounds.getNorth() - bounds.getSouth(),
|
|
384
|
+
longitudeDelta: bounds.getEast() - bounds.getWest()
|
|
385
|
+
}
|
|
386
|
+
}));
|
|
387
|
+
});
|
|
388
|
+
</script>
|
|
389
|
+
</body>
|
|
390
|
+
</html>
|
|
391
|
+
`;
|
|
392
|
+
}, []);
|
|
393
|
+
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (hasValidCoordinate(userLocation)) {
|
|
396
|
+
userLocationRef.current = userLocation;
|
|
397
|
+
updateUserLocation(userLocation);
|
|
398
|
+
}
|
|
399
|
+
}, [updateUserLocation, userLocation]);
|
|
400
|
+
|
|
401
|
+
useEffect(() => {
|
|
402
|
+
if (region && isMapReadyRef.current) {
|
|
403
|
+
setRegion(region);
|
|
404
|
+
}
|
|
405
|
+
}, [region, setRegion]);
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
<View style={[styles.container, style]}>
|
|
409
|
+
<WebView
|
|
410
|
+
ref={webViewRef}
|
|
411
|
+
source={{ html: htmlContent }}
|
|
412
|
+
style={styles.webview}
|
|
413
|
+
onMessage={handleMessage}
|
|
414
|
+
javaScriptEnabled={true}
|
|
415
|
+
domStorageEnabled={true}
|
|
416
|
+
originWhitelist={['*']}
|
|
417
|
+
scrollEnabled={false}
|
|
418
|
+
bounces={false}
|
|
419
|
+
overScrollMode="never"
|
|
420
|
+
showsHorizontalScrollIndicator={false}
|
|
421
|
+
showsVerticalScrollIndicator={false}
|
|
422
|
+
cacheEnabled={true}
|
|
423
|
+
cacheMode="LOAD_CACHE_ELSE_NETWORK"
|
|
424
|
+
/>
|
|
425
|
+
</View>
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
LeafletMapView.displayName = 'LeafletMapView';
|
|
430
|
+
|
|
431
|
+
const LeafletMarker = () => null;
|
|
432
|
+
LeafletMarker.displayName = 'LeafletMarker';
|
|
433
|
+
|
|
434
|
+
const LeafletCallout = () => null;
|
|
435
|
+
LeafletCallout.displayName = 'LeafletCallout';
|
|
436
|
+
|
|
437
|
+
const styles = StyleSheet.create({
|
|
438
|
+
container: {
|
|
439
|
+
flex: 1,
|
|
440
|
+
overflow: 'hidden',
|
|
441
|
+
},
|
|
442
|
+
webview: {
|
|
443
|
+
flex: 1,
|
|
444
|
+
backgroundColor: 'transparent',
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
export default LeafletMapView;
|
|
449
|
+
export { LeafletMarker as Marker, LeafletCallout as Callout };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlatformMapView - platform-specific map component
|
|
3
|
+
* Android: uses Leaflet/OpenStreetMap via WebView
|
|
4
|
+
* iOS: uses react-native-maps
|
|
5
|
+
*/
|
|
6
|
+
import { Platform } from 'react-native';
|
|
7
|
+
|
|
8
|
+
let MapView;
|
|
9
|
+
let Marker;
|
|
10
|
+
let Callout;
|
|
11
|
+
let PROVIDER = null;
|
|
12
|
+
let PROVIDER_DEFAULT = null;
|
|
13
|
+
let PROVIDER_GOOGLE = null;
|
|
14
|
+
|
|
15
|
+
if (Platform.OS === 'android') {
|
|
16
|
+
const Leaflet = require('./LeafletMapView');
|
|
17
|
+
MapView = Leaflet.default;
|
|
18
|
+
Marker = Leaflet.Marker;
|
|
19
|
+
Callout = Leaflet.Callout;
|
|
20
|
+
} else {
|
|
21
|
+
const RNMaps = require('react-native-maps');
|
|
22
|
+
MapView = RNMaps.default;
|
|
23
|
+
Marker = RNMaps.Marker;
|
|
24
|
+
Callout = RNMaps.Callout;
|
|
25
|
+
PROVIDER = RNMaps.PROVIDER_DEFAULT;
|
|
26
|
+
PROVIDER_DEFAULT = RNMaps.PROVIDER_DEFAULT;
|
|
27
|
+
PROVIDER_GOOGLE = RNMaps.PROVIDER_GOOGLE;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
MapView as default,
|
|
32
|
+
Marker,
|
|
33
|
+
Callout,
|
|
34
|
+
PROVIDER,
|
|
35
|
+
PROVIDER_DEFAULT,
|
|
36
|
+
PROVIDER_GOOGLE,
|
|
37
|
+
};
|
package/src/index.js
ADDED