chore: fix gps
This commit is contained in:
@@ -7,6 +7,7 @@ import React, {
|
||||
} from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import { geocodeAddress, calculateRoute } from "../api/tomtom";
|
||||
|
||||
const TOMTOM_API_KEY = "MERY8I7LMeYVSLKO5WuV73W9rKJpBLoB";
|
||||
|
||||
@@ -20,29 +21,25 @@ export interface TomTomMarker {
|
||||
isSelected?: boolean;
|
||||
}
|
||||
|
||||
export interface TomTomRoute {
|
||||
coordinates: { latitude: number; longitude: number }[];
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface TomTomDestination {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export interface TomTomMapRef {
|
||||
fitAllMarkers: () => void;
|
||||
fitToCoordinates: (
|
||||
coords: { latitude: number; longitude: number }[],
|
||||
) => void;
|
||||
calcRoute: (
|
||||
origin: { latitude: number; longitude: number },
|
||||
destination: { latitude: number; longitude: number },
|
||||
) => void;
|
||||
calcRouteFromAddress: (
|
||||
origin: { latitude: number; longitude: number },
|
||||
address: string,
|
||||
) => void;
|
||||
clearRoute: () => void;
|
||||
}
|
||||
|
||||
interface TomTomMapProps {
|
||||
style?: any;
|
||||
markers?: TomTomMarker[];
|
||||
route?: TomTomRoute | null;
|
||||
destination?: TomTomDestination | null;
|
||||
initialCenter?: { latitude: number; longitude: number };
|
||||
initialZoom?: number;
|
||||
onMarkerPress?: (markerId: string) => void;
|
||||
@@ -53,10 +50,8 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
{
|
||||
style,
|
||||
markers = [],
|
||||
route,
|
||||
destination,
|
||||
initialCenter,
|
||||
initialZoom = 12,
|
||||
initialZoom = 14,
|
||||
onMarkerPress,
|
||||
},
|
||||
ref,
|
||||
@@ -69,7 +64,7 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
const json = JSON.stringify(msg);
|
||||
if (isReady.current) {
|
||||
webViewRef.current?.injectJavaScript(
|
||||
`handleMessage(${json}); true;`,
|
||||
`(function(){ try { handleMessage(${json}); } catch(e) {} })(); true;`,
|
||||
);
|
||||
} else {
|
||||
pendingMessages.current.push(json);
|
||||
@@ -82,6 +77,53 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
fitAllMarkers: () => sendMessage({ type: "fitAll" }),
|
||||
fitToCoordinates: (coords) =>
|
||||
sendMessage({ type: "fitCoords", coords }),
|
||||
clearRoute: () => sendMessage({ type: "clearRoute" }),
|
||||
calcRoute: (origin, dest) => {
|
||||
calculateRoute(origin, dest).then((result) => {
|
||||
if (result) {
|
||||
sendMessage({
|
||||
type: "drawRoute",
|
||||
coordinates: result.route.coordinates,
|
||||
destination: dest,
|
||||
});
|
||||
} else {
|
||||
// Fallback: ligne droite
|
||||
sendMessage({
|
||||
type: "drawRoute",
|
||||
coordinates: [origin, dest],
|
||||
destination: dest,
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
sendMessage({
|
||||
type: "drawRoute",
|
||||
coordinates: [origin, dest],
|
||||
destination: dest,
|
||||
});
|
||||
});
|
||||
},
|
||||
calcRouteFromAddress: async (origin, address) => {
|
||||
try {
|
||||
const dest = await geocodeAddress(address);
|
||||
if (!dest) return;
|
||||
const result = await calculateRoute(origin, dest);
|
||||
if (result) {
|
||||
sendMessage({
|
||||
type: "drawRoute",
|
||||
coordinates: result.route.coordinates,
|
||||
destination: dest,
|
||||
});
|
||||
} else {
|
||||
sendMessage({
|
||||
type: "drawRoute",
|
||||
coordinates: [origin, dest],
|
||||
destination: dest,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
/* silent */
|
||||
}
|
||||
},
|
||||
}),
|
||||
[sendMessage],
|
||||
);
|
||||
@@ -90,30 +132,25 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
sendMessage({ type: "updateMarkers", markers });
|
||||
}, [markers, sendMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
sendMessage({
|
||||
type: "updateRoute",
|
||||
route: route || null,
|
||||
destination: destination || null,
|
||||
});
|
||||
}, [route, destination, sendMessage]);
|
||||
|
||||
const onMessage = useCallback(
|
||||
(event: any) => {
|
||||
try {
|
||||
const data = JSON.parse(event.nativeEvent.data);
|
||||
if (data.type === "ready") {
|
||||
isReady.current = true;
|
||||
// flush pending
|
||||
for (const msg of pendingMessages.current) {
|
||||
// Rejouer les messages en attente
|
||||
const pending = pendingMessages.current.slice();
|
||||
pendingMessages.current = [];
|
||||
for (const msg of pending) {
|
||||
webViewRef.current?.injectJavaScript(
|
||||
`handleMessage(${msg}); true;`,
|
||||
`(function(){ try { handleMessage(${msg}); } catch(e) {} })(); true;`,
|
||||
);
|
||||
}
|
||||
pendingMessages.current = [];
|
||||
// Auto fit markers after ready + flush
|
||||
// Fit sur tous les markers après un court délai
|
||||
setTimeout(() => {
|
||||
sendMessage({ type: "fitAll" });
|
||||
webViewRef.current?.injectJavaScript(
|
||||
`(function(){ try { handleMessage(${JSON.stringify({ type: "fitAll" })}); } catch(e) {} })(); true;`,
|
||||
);
|
||||
}, 500);
|
||||
} else if (data.type === "markerPress" && onMarkerPress) {
|
||||
onMarkerPress(data.id);
|
||||
@@ -134,7 +171,7 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
}
|
||||
: { latitude: 48.8566, longitude: 2.3522 });
|
||||
|
||||
const html = buildHtml(center, initialZoom);
|
||||
const html = buildHtml(center, initialZoom, TOMTOM_API_KEY);
|
||||
|
||||
return (
|
||||
<WebView
|
||||
@@ -147,6 +184,7 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
scrollEnabled={false}
|
||||
bounces={false}
|
||||
originWhitelist={["*"]}
|
||||
mixedContentMode="always"
|
||||
/>
|
||||
);
|
||||
},
|
||||
@@ -155,6 +193,7 @@ const TomTomMap = forwardRef<TomTomMapRef, TomTomMapProps>(
|
||||
function buildHtml(
|
||||
center: { latitude: number; longitude: number },
|
||||
zoom: number,
|
||||
apiKey: string,
|
||||
) {
|
||||
return `<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -163,13 +202,13 @@ function buildHtml(
|
||||
<link rel="stylesheet" href="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.25.0/maps/maps.css">
|
||||
<script src="https://api.tomtom.com/maps-sdk-for-web/cdn/6.x/6.25.0/maps/maps-web.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; }
|
||||
html, body, #map { width: 100%; height: 100%; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body, #map { width: 100%; height: 100%; overflow: hidden; }
|
||||
.marker-dot {
|
||||
width: 28px; height: 28px; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 2px solid rgba(255,255,255,0.8);
|
||||
font-size: 12px; color: white; cursor: pointer;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||
}
|
||||
.marker-dot.selected {
|
||||
@@ -178,11 +217,8 @@ function buildHtml(
|
||||
box-shadow: 0 0 12px rgba(124,58,237,0.6);
|
||||
}
|
||||
.dest-marker {
|
||||
width: 24px; height: 24px; border-radius: 50%;
|
||||
width: 32px; height: 32px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
border: 2px solid white;
|
||||
font-size: 10px; color: white;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.4);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -190,39 +226,82 @@ function buildHtml(
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
var map = tt.map({
|
||||
key: '${TOMTOM_API_KEY}',
|
||||
key: '${apiKey}',
|
||||
container: 'map',
|
||||
center: [${center.longitude}, ${center.latitude}],
|
||||
zoom: ${zoom},
|
||||
stylesVisibility: { trafficFlow: false, trafficIncidents: false }
|
||||
});
|
||||
|
||||
var markers = {};
|
||||
var routeLayer = null;
|
||||
var markerObjects = {};
|
||||
var destMarker = null;
|
||||
var ROUTE_SOURCE = 'tt-route-src';
|
||||
var ROUTE_LAYER = 'tt-route-lyr';
|
||||
|
||||
function clearMarkers() {
|
||||
Object.values(markers).forEach(function(m) { m.remove(); });
|
||||
markers = {};
|
||||
}
|
||||
// File d'attente pour les messages reçus avant que le style soit chargé
|
||||
var pendingRouteData = null;
|
||||
var styleLoaded = false;
|
||||
|
||||
function clearRoute() {
|
||||
if (routeLayer && map.getSource('route')) {
|
||||
map.removeLayer('route-line');
|
||||
map.removeSource('route');
|
||||
routeLayer = null;
|
||||
}
|
||||
try { if (map.getLayer(ROUTE_LAYER)) map.removeLayer(ROUTE_LAYER); } catch(e) {}
|
||||
try { if (map.getSource(ROUTE_SOURCE)) map.removeSource(ROUTE_SOURCE); } catch(e) {}
|
||||
if (destMarker) { destMarker.remove(); destMarker = null; }
|
||||
}
|
||||
|
||||
function drawRoute(coordinates, destination) {
|
||||
clearRoute();
|
||||
if (!destination) return;
|
||||
|
||||
// Marker destination (pin rouge)
|
||||
var pin = document.createElement('div');
|
||||
pin.className = 'dest-marker';
|
||||
pin.innerHTML = '<svg width="32" height="32" viewBox="0 0 24 24"><path fill="#ef4444" d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/><circle cx="12" cy="9" r="2.5" fill="white"/></svg>';
|
||||
destMarker = new tt.Marker({ element: pin })
|
||||
.setLngLat([destination.longitude, destination.latitude])
|
||||
.addTo(map);
|
||||
|
||||
if (!coordinates || coordinates.length < 2) {
|
||||
map.flyTo({ center: [destination.longitude, destination.latitude], zoom: 15 });
|
||||
return;
|
||||
}
|
||||
|
||||
// Convertir les coords { latitude, longitude } en [lng, lat] pour GeoJSON
|
||||
var lngLat = coordinates.map(function(c) {
|
||||
return [c.longitude, c.latitude];
|
||||
});
|
||||
|
||||
try {
|
||||
map.addSource(ROUTE_SOURCE, {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: { type: 'LineString', coordinates: lngLat }
|
||||
}
|
||||
});
|
||||
map.addLayer({
|
||||
id: ROUTE_LAYER,
|
||||
type: 'line',
|
||||
source: ROUTE_SOURCE,
|
||||
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
||||
paint: { 'line-color': '#4285F4', 'line-width': 6, 'line-opacity': 0.9 }
|
||||
});
|
||||
var bounds = new tt.LngLatBounds();
|
||||
lngLat.forEach(function(c) { bounds.extend(c); });
|
||||
map.fitBounds(bounds, { padding: 60, maxZoom: 16, duration: 800 });
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
function handleMessage(data) {
|
||||
if (data.type === 'updateMarkers') {
|
||||
clearMarkers();
|
||||
Object.values(markerObjects).forEach(function(m) { m.remove(); });
|
||||
markerObjects = {};
|
||||
(data.markers || []).forEach(function(m) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'marker-dot' + (m.isSelected ? ' selected' : '');
|
||||
el.style.backgroundColor = m.color || '#7c3aed';
|
||||
el.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="white"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
|
||||
el.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="white"><circle cx="12" cy="12" r="8"/></svg>';
|
||||
el.addEventListener('click', function() {
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'markerPress', id: m.id }));
|
||||
});
|
||||
@@ -232,47 +311,30 @@ function handleMessage(data) {
|
||||
.setLngLat([m.longitude, m.latitude])
|
||||
.setPopup(popup)
|
||||
.addTo(map);
|
||||
markers[m.id] = marker;
|
||||
markerObjects[m.id] = marker;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.type === 'updateRoute') {
|
||||
clearRoute();
|
||||
if (data.destination) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'dest-marker';
|
||||
el.style.backgroundColor = data.destination.color || '#ef4444';
|
||||
el.innerHTML = '<svg width="12" height="12" viewBox="0 0 24 24" fill="white"><path d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6z"/></svg>';
|
||||
destMarker = new tt.Marker({ element: el })
|
||||
.setLngLat([data.destination.longitude, data.destination.latitude])
|
||||
.addTo(map);
|
||||
}
|
||||
if (data.route && data.route.coordinates && data.route.coordinates.length > 1) {
|
||||
var coords = data.route.coordinates.map(function(c) { return [c.longitude, c.latitude]; });
|
||||
map.addSource('route', {
|
||||
type: 'geojson',
|
||||
data: { type: 'Feature', geometry: { type: 'LineString', coordinates: coords }, properties: {} }
|
||||
});
|
||||
map.addLayer({
|
||||
id: 'route-line',
|
||||
type: 'line',
|
||||
source: 'route',
|
||||
paint: {
|
||||
'line-color': data.route.color || '#4285F4',
|
||||
'line-width': 4,
|
||||
'line-opacity': 0.8
|
||||
}
|
||||
});
|
||||
routeLayer = true;
|
||||
if (data.type === 'drawRoute') {
|
||||
if (!styleLoaded) {
|
||||
pendingRouteData = data;
|
||||
} else {
|
||||
drawRoute(data.coordinates, data.destination);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'clearRoute') {
|
||||
pendingRouteData = null;
|
||||
clearRoute();
|
||||
}
|
||||
|
||||
if (data.type === 'fitAll') {
|
||||
var allM = Object.values(markers);
|
||||
if (allM.length > 0) {
|
||||
var all = Object.values(markerObjects);
|
||||
if (all.length > 0) {
|
||||
var bounds = new tt.LngLatBounds();
|
||||
allM.forEach(function(m) { bounds.extend(m.getLngLat()); });
|
||||
map.fitBounds(bounds, { padding: 60, maxZoom: 15 });
|
||||
all.forEach(function(m) { bounds.extend(m.getLngLat()); });
|
||||
if (destMarker) bounds.extend(destMarker.getLngLat());
|
||||
map.fitBounds(bounds, { padding: 60, maxZoom: 15, duration: 800 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,12 +342,17 @@ function handleMessage(data) {
|
||||
if (data.coords && data.coords.length > 0) {
|
||||
var bounds = new tt.LngLatBounds();
|
||||
data.coords.forEach(function(c) { bounds.extend([c.longitude, c.latitude]); });
|
||||
map.fitBounds(bounds, { padding: 80, maxZoom: 15 });
|
||||
map.fitBounds(bounds, { padding: 80, maxZoom: 15, duration: 800 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map.on('load', function() {
|
||||
styleLoaded = true;
|
||||
if (pendingRouteData) {
|
||||
drawRoute(pendingRouteData.coordinates, pendingRouteData.destination);
|
||||
pendingRouteData = null;
|
||||
}
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ready' }));
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user