Add Leaflet fallback for map rendering and implement group visibility toggling
This commit is contained in:
@@ -4,6 +4,38 @@ import { check, PERMISSIONS, request, requestMultiple, RESULTS } from 'react-nat
|
|||||||
import MapView, { Marker, UrlTile, Region } from 'react-native-maps';
|
import MapView, { Marker, UrlTile, Region } from 'react-native-maps';
|
||||||
import Geolocation from '@react-native-community/geolocation';
|
import Geolocation from '@react-native-community/geolocation';
|
||||||
|
|
||||||
|
// Generate a small Leaflet page showing markers (used as Android fallback)
|
||||||
|
const makeLeafletHtml = (centerLat: number, centerLng: number, pointsJson: string) => `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<style>html,body,#map{height:100%;margin:0;padding:0}#map{width:100%;height:100%}.legend{position: absolute; top:8px; right:8px; background:#fff;padding:6px;border-radius:6px;font-family:sans-serif;font-size:12px;box-shadow:0 1px 4px rgba(0,0,0,0.2)}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
const center = [${centerLat}, ${centerLng}];
|
||||||
|
const points = ${pointsJson};
|
||||||
|
const map = L.map('map',{zoomControl:true,attributionControl:false}).setView(center,13);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {maxZoom: 19}).addTo(map);
|
||||||
|
points.forEach(p => {
|
||||||
|
L.circleMarker([p.latitude, p.longitude], { radius: 7, color: p.color, fillColor: p.color, fillOpacity: 0.9 }).addTo(map).bindPopup(p.title || '');
|
||||||
|
});
|
||||||
|
var legend = L.control({position: 'topright'});
|
||||||
|
legend.onAdd = function (map) {
|
||||||
|
var div = L.DomUtil.create('div', 'legend');
|
||||||
|
div.innerHTML = '<b>Groups</b><br/>' + (Array.from(new Set(points.map(p => p.group))).map(g => '<div style="display:flex;align-items:center;margin-top:6px"><span style="width:10px;height:10px;background:'+points.find(x=>x.group===g).color+';display:inline-block;margin-right:6px;border-radius:2px"></span>'+g+'</div>').join(''));
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
legend.addTo(map);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
type Props = { onClose?: () => void };
|
type Props = { onClose?: () => void };
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component<any, { error: any }> {
|
class ErrorBoundary extends React.Component<any, { error: any }> {
|
||||||
@@ -41,6 +73,21 @@ export default function Map({ onClose }: Props) {
|
|||||||
const [mapAttempt, setMapAttempt] = useState(false);
|
const [mapAttempt, setMapAttempt] = useState(false);
|
||||||
const [mapError, setMapError] = useState<any>(null);
|
const [mapError, setMapError] = useState<any>(null);
|
||||||
|
|
||||||
|
// --- Sample points and group visibility state ---
|
||||||
|
const SAMPLE_POINTS = [
|
||||||
|
{ id: 'p1', latitude: 22.3039, longitude: 70.8022, group: 'Restaurants', title: 'Restaurant A' },
|
||||||
|
{ id: 'p2', latitude: 22.3090, longitude: 70.8070, group: 'Restaurants', title: 'Restaurant B' },
|
||||||
|
{ id: 'p3', latitude: 22.2980, longitude: 70.8000, group: 'Parks', title: 'Park A' },
|
||||||
|
{ id: 'p4', latitude: 22.3140, longitude: 70.7950, group: 'Hospitals', title: 'Hospital A' },
|
||||||
|
{ id: 'p5', latitude: 22.3100, longitude: 70.8100, group: 'Parks', title: 'Park B' },
|
||||||
|
];
|
||||||
|
const [points] = useState(SAMPLE_POINTS);
|
||||||
|
const groupList = Array.from(new Set(points.map(p => p.group)));
|
||||||
|
const colorPalette = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6'];
|
||||||
|
const getGroupColor = (group: string) => colorPalette[groupList.indexOf(group) % colorPalette.length];
|
||||||
|
const [groupsVisible, setGroupsVisible] = useState<Record<string, boolean>>(() => Object.fromEntries(groupList.map(g => [g, true])));
|
||||||
|
const toggleGroup = (g: string) => setGroupsVisible(prev => ({ ...prev, [g]: !prev[g] }));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -170,18 +217,7 @@ export default function Map({ onClose }: Props) {
|
|||||||
if (permissionStatus === 'granted') getCurrentLocation();
|
if (permissionStatus === 'granted') getCurrentLocation();
|
||||||
else retryPermission();
|
else retryPermission();
|
||||||
};
|
};
|
||||||
return (
|
|
||||||
<MapView
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
initialRegion={{
|
|
||||||
latitude: 22.3039,
|
|
||||||
longitude: 70.8022,
|
|
||||||
latitudeDelta: 0.05,
|
|
||||||
longitudeDelta: 0.05,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
)
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
||||||
@@ -216,20 +252,36 @@ export default function Map({ onClose }: Props) {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
) : mapAttempt ? (
|
) : mapAttempt ? (
|
||||||
<ErrorBoundary onError={(e: any) => { setMapError(e); console.error('Map render error:', e); }}>
|
<View style={{ flex: 1 }}>
|
||||||
<MapView style={styles.map} initialRegion={region as Region} showsUserLocation={true} showsMyLocationButton={true}>
|
<View style={styles.legendContainer} pointerEvents="box-none">
|
||||||
<UrlTile
|
{groupList.map(g => (
|
||||||
urlTemplate="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
<TouchableOpacity key={g} style={[styles.legendItem, !groupsVisible[g] && styles.legendItemDisabled]} onPress={() => toggleGroup(g)}>
|
||||||
maximumZ={19}
|
<View style={[styles.legendDot, { backgroundColor: getGroupColor(g) }]} />
|
||||||
flipY={false}
|
<Text style={styles.legendLabel}>{g}</Text>
|
||||||
tileSize={256}
|
</TouchableOpacity>
|
||||||
/>
|
))}
|
||||||
<Marker coordinate={{ latitude: region.latitude, longitude: region.longitude }} title="You are here" />
|
</View>
|
||||||
</MapView>
|
|
||||||
<TouchableOpacity style={styles.osmAttribution} onPress={() => Linking.openURL('https://www.openstreetmap.org/copyright')}>
|
<ErrorBoundary onError={(e: any) => { setMapError(e); console.error('Map render error:', e); }}>
|
||||||
<Text style={styles.osmAttributionText}>© OpenStreetMap</Text>
|
|
||||||
</TouchableOpacity>
|
<MapView style={styles.map} initialRegion={region as Region} showsUserLocation={true} showsMyLocationButton={true}>
|
||||||
</ErrorBoundary>
|
<UrlTile
|
||||||
|
urlTemplate="https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
maximumZ={19}
|
||||||
|
flipY={false}
|
||||||
|
tileSize={256}
|
||||||
|
/>
|
||||||
|
{points.filter(p => groupsVisible[p.group]).map(p => (
|
||||||
|
<Marker key={p.id} coordinate={{ latitude: p.latitude, longitude: p.longitude }} title={p.title} pinColor={getGroupColor(p.group)} />
|
||||||
|
))}
|
||||||
|
</MapView>
|
||||||
|
|
||||||
|
|
||||||
|
<TouchableOpacity style={styles.osmAttribution} onPress={() => Linking.openURL('https://www.openstreetmap.org/copyright')}>
|
||||||
|
<Text style={styles.osmAttributionText}>© OpenStreetMap</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.center}>
|
<View style={styles.center}>
|
||||||
<Text style={{ marginBottom: 12 }}>Map is ready. Tap "Try Map" to open it.</Text>
|
<Text style={{ marginBottom: 12 }}>Map is ready. Tap "Try Map" to open it.</Text>
|
||||||
@@ -257,4 +309,9 @@ const styles = StyleSheet.create({
|
|||||||
actionText: { color: '#fff', fontWeight: '600' },
|
actionText: { color: '#fff', fontWeight: '600' },
|
||||||
osmAttribution: { position: 'absolute', bottom: 16, right: 8, backgroundColor: 'rgba(255,255,255,0.9)', padding: 6, borderRadius: 6 },
|
osmAttribution: { position: 'absolute', bottom: 16, right: 8, backgroundColor: 'rgba(255,255,255,0.9)', padding: 6, borderRadius: 6 },
|
||||||
osmAttributionText: { fontSize: 10, color: '#333' },
|
osmAttributionText: { fontSize: 10, color: '#333' },
|
||||||
|
legendContainer: { position: 'absolute', top: 12, right: 12, zIndex: 20, flexDirection: 'column', backgroundColor: 'rgba(255,255,255,0.95)', padding: 8, borderRadius: 8 },
|
||||||
|
legendItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: 6, paddingHorizontal: 8, borderRadius: 6, marginBottom: 6 },
|
||||||
|
legendItemDisabled: { opacity: 0.4 },
|
||||||
|
legendDot: { width: 12, height: 12, borderRadius: 3, marginRight: 8 },
|
||||||
|
legendLabel: { fontSize: 12, color: '#111' },
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user