1
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import maplibregl from 'maplibre-gl';
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
interface MapComponentProps {
|
||||
onMapLoad: (map: maplibregl.Map) => void;
|
||||
show3D?: boolean;
|
||||
showPOIs?: boolean;
|
||||
}
|
||||
|
||||
const MapComponent: React.FC<MapComponentProps> = ({
|
||||
onMapLoad,
|
||||
show3D = false,
|
||||
showPOIs = true
|
||||
}) => {
|
||||
const mapContainer = useRef<HTMLDivElement>(null);
|
||||
const map = useRef<maplibregl.Map | null>(null);
|
||||
|
||||
// Sync toggles with map layers
|
||||
useEffect(() => {
|
||||
if (!map.current) return;
|
||||
|
||||
// Toggle 3D Buildings
|
||||
if (map.current.getLayer('building-3d')) {
|
||||
map.current.setLayoutProperty('building-3d', 'visibility', show3D ? 'visible' : 'none');
|
||||
}
|
||||
|
||||
// Toggle POI Layers
|
||||
const poiLayers = ['poi-circles', 'place-labels'];
|
||||
poiLayers.forEach(layerId => {
|
||||
if (map.current!.getLayer(layerId)) {
|
||||
map.current!.setLayoutProperty(layerId, 'visibility', showPOIs ? 'visible' : 'none');
|
||||
}
|
||||
});
|
||||
}, [show3D, showPOIs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (map.current) return;
|
||||
if (!mapContainer.current) return;
|
||||
|
||||
// Load RTL Text Plugin for correct Arabic rendering
|
||||
if (maplibregl.getRTLTextPluginStatus() === 'unavailable') {
|
||||
maplibregl.setRTLTextPlugin(
|
||||
'/rtl-plugin.js', // Localized to bypass ORB
|
||||
true // Lazy load
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
map.current = new maplibregl.Map({
|
||||
container: mapContainer.current,
|
||||
style: {
|
||||
version: 8,
|
||||
glyphs: 'https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf',
|
||||
sprite: 'https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite',
|
||||
sources: {
|
||||
'local-osm-polygons': {
|
||||
type: 'vector',
|
||||
tiles: ['http://localhost:3001/planet_osm_polygon/{z}/{x}/{y}'],
|
||||
maxzoom: 14
|
||||
},
|
||||
'local-osm-lines': {
|
||||
type: 'vector',
|
||||
tiles: ['http://localhost:3001/planet_osm_line/{z}/{x}/{y}'],
|
||||
maxzoom: 14
|
||||
},
|
||||
'local-osm-points': {
|
||||
type: 'vector',
|
||||
tiles: ['http://localhost:3001/planet_osm_point/{z}/{x}/{y}'],
|
||||
maxzoom: 14
|
||||
}
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: 'background',
|
||||
type: 'background',
|
||||
paint: { 'background-color': '#f2efea' }
|
||||
},
|
||||
{
|
||||
id: 'water-layer',
|
||||
type: 'fill',
|
||||
source: 'local-osm-polygons',
|
||||
'source-layer': 'planet_osm_polygon',
|
||||
filter: ['in', 'natural', 'water', 'lake', 'riverbank'],
|
||||
paint: { 'fill-color': '#aadaff' }
|
||||
},
|
||||
{
|
||||
id: 'park-layer',
|
||||
type: 'fill',
|
||||
source: 'local-osm-polygons',
|
||||
'source-layer': 'planet_osm_polygon',
|
||||
filter: ['in', 'leisure', 'park', 'garden', 'nature_reserve'],
|
||||
paint: { 'fill-color': '#c8facc' }
|
||||
},
|
||||
// Building 3D - User toggleable
|
||||
{
|
||||
id: 'building-3d',
|
||||
type: 'fill-extrusion',
|
||||
source: 'local-osm-polygons',
|
||||
'source-layer': 'planet_osm_polygon',
|
||||
minzoom: 15,
|
||||
filter: ['has', 'building'],
|
||||
layout: {
|
||||
visibility: show3D ? 'visible' : 'none'
|
||||
},
|
||||
paint: {
|
||||
'fill-extrusion-color': '#d9d0c9',
|
||||
'fill-extrusion-height': 40,
|
||||
'fill-extrusion-base': 0,
|
||||
'fill-extrusion-opacity': 0.8
|
||||
}
|
||||
},
|
||||
// ROAD BORDERS (Casing)
|
||||
{
|
||||
id: 'road-border-high',
|
||||
type: 'line',
|
||||
source: 'local-osm-lines',
|
||||
'source-layer': 'planet_osm_line',
|
||||
filter: ['in', 'highway', 'primary', 'secondary', 'tertiary', 'motorway'],
|
||||
paint: {
|
||||
'line-color': '#ccc',
|
||||
'line-width': [
|
||||
'interpolate', ['exponential', 1.5], ['zoom'],
|
||||
12, 3,
|
||||
16, 12
|
||||
] as any
|
||||
}
|
||||
},
|
||||
// ROAD CORES
|
||||
{
|
||||
id: 'road-layer-low',
|
||||
type: 'line',
|
||||
source: 'local-osm-lines',
|
||||
'source-layer': 'planet_osm_line',
|
||||
filter: ['in', 'highway', 'residential', 'service', 'unclassified'],
|
||||
paint: { 'line-color': '#ffffff', 'line-width': 1.5 }
|
||||
},
|
||||
{
|
||||
id: 'road-layer-high',
|
||||
type: 'line',
|
||||
source: 'local-osm-lines',
|
||||
'source-layer': 'planet_osm_line',
|
||||
filter: ['in', 'highway', 'primary', 'secondary', 'tertiary'],
|
||||
paint: {
|
||||
'line-color': '#fff',
|
||||
'line-width': [
|
||||
'interpolate', ['exponential', 1.5], ['zoom'],
|
||||
12, 1.5,
|
||||
16, 8
|
||||
] as any
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'road-layer-motorway',
|
||||
type: 'line',
|
||||
source: 'local-osm-lines',
|
||||
'source-layer': 'planet_osm_line',
|
||||
filter: ['==', 'highway', 'motorway'],
|
||||
paint: {
|
||||
'line-color': '#f7d499',
|
||||
'line-width': [
|
||||
'interpolate', ['exponential', 1.5], ['zoom'],
|
||||
12, 2.5,
|
||||
16, 10
|
||||
] as any
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'road-labels',
|
||||
type: 'symbol',
|
||||
source: 'local-osm-lines',
|
||||
'source-layer': 'planet_osm_line',
|
||||
minzoom: 15,
|
||||
layout: {
|
||||
'text-field': '{name}',
|
||||
'text-font': ['Noto Sans Regular'],
|
||||
'text-size': 11,
|
||||
'symbol-placement': 'line',
|
||||
'text-letter-spacing': 0.05,
|
||||
'text-padding': 15,
|
||||
'text-allow-overlap': false, // Avoid "black cloud"
|
||||
'text-ignore-placement': false
|
||||
},
|
||||
paint: {
|
||||
'text-color': '#444',
|
||||
'text-halo-color': '#fff',
|
||||
'text-halo-width': 1.5
|
||||
}
|
||||
},
|
||||
// ROUTE LAYER - Placeholder for navigation results
|
||||
{
|
||||
id: 'route-line',
|
||||
type: 'line',
|
||||
source: 'local-osm-lines', // Use a source that exists and can be updated
|
||||
'source-layer': 'planet_osm_line',
|
||||
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
||||
paint: { 'line-color': '#3b82f6', 'line-width': 6, 'line-opacity': 0.8 },
|
||||
filter: ['==', 'id', -1] // Initially hide
|
||||
},
|
||||
// POI ICONS - Professional categorization
|
||||
{
|
||||
id: 'poi-icons',
|
||||
type: 'symbol',
|
||||
source: 'local-osm-points',
|
||||
'source-layer': 'planet_osm_point',
|
||||
minzoom: 13,
|
||||
layout: {
|
||||
'visibility': showPOIs ? 'visible' : 'none',
|
||||
'icon-image': [
|
||||
'coalesce',
|
||||
[
|
||||
'match',
|
||||
['get', 'shop'],
|
||||
'supermarket', 'grocery-15',
|
||||
'convenience', 'grocery-15',
|
||||
'pharmacy', 'pharmacy-15',
|
||||
'clothes', 'clothing_store-15',
|
||||
''
|
||||
],
|
||||
[
|
||||
'match',
|
||||
['get', 'amenity'],
|
||||
'restaurant', 'restaurant-15',
|
||||
'cafe', 'cafe-15',
|
||||
'bank', 'bank-15',
|
||||
'pharmacy', 'pharmacy-15',
|
||||
'grave_yard', 'cemetery-15',
|
||||
'school', 'school-15',
|
||||
'university', 'college-15',
|
||||
'place_of_worship', 'religious-christian-15', // Fallback for general
|
||||
'hospital', 'hospital-15',
|
||||
'fuel', 'fuel-15',
|
||||
''
|
||||
],
|
||||
[
|
||||
'match',
|
||||
['get', 'historic'],
|
||||
'monument', 'monument-15',
|
||||
'ruins', 'attraction-15',
|
||||
'archaeological_site', 'attraction-15',
|
||||
''
|
||||
],
|
||||
[
|
||||
'match',
|
||||
['get', 'tourism'],
|
||||
'museum', 'museum-15',
|
||||
'hotel', 'hotel-15',
|
||||
'attraction', 'attraction-15',
|
||||
''
|
||||
],
|
||||
[
|
||||
'match',
|
||||
['get', 'landuse'],
|
||||
'cemetery', 'cemetery-15',
|
||||
''
|
||||
],
|
||||
'marker-15' // Ultimate fallback
|
||||
],
|
||||
'icon-size': 1.1,
|
||||
'icon-padding': 2,
|
||||
'icon-allow-overlap': false
|
||||
},
|
||||
paint: {
|
||||
'icon-opacity': 1,
|
||||
'icon-halo-color': '#ffffff',
|
||||
'icon-halo-width': 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'place-labels',
|
||||
type: 'symbol',
|
||||
source: 'local-osm-points',
|
||||
'source-layer': 'planet_osm_point',
|
||||
minzoom: 14, // Only show text at higher zoom to avoid clutter
|
||||
layout: {
|
||||
'text-field': '{name}',
|
||||
'text-font': ['Noto Sans Regular'],
|
||||
'text-size': 12,
|
||||
'text-offset': [0, 1.5],
|
||||
'text-anchor': 'top',
|
||||
'text-padding': 5,
|
||||
'text-allow-overlap': false,
|
||||
'visibility': showPOIs ? 'visible' : 'none'
|
||||
},
|
||||
paint: {
|
||||
'text-color': '#111',
|
||||
'text-halo-color': '#fff',
|
||||
'text-halo-width': 1.5
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
center: [35.9106, 31.9539],
|
||||
zoom: 12,
|
||||
});
|
||||
|
||||
map.current.on('load', () => {
|
||||
console.log("MapComponent: Map Loaded Successfully");
|
||||
onMapLoad(map.current!);
|
||||
});
|
||||
|
||||
map.current.on('error', (e) => {
|
||||
console.error("MapComponent: Map Error:", e);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error("MapComponent: Fatal Initialization Error:", err);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (map.current) {
|
||||
map.current.remove();
|
||||
map.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={mapContainer} style={{ width: '100%', height: '100%' }} />;
|
||||
};
|
||||
|
||||
export default MapComponent;
|
||||
Reference in New Issue
Block a user