0% found this document useful (0 votes)
2 views9 pages

Nouveau Document Texte

This document is a React component that integrates Mapbox GL and Mapbox Draw to create an interactive map application. It allows users to draw polygons, polylines, and markers, as well as add labels to the map. The component manages various states for drawing modes, markers, and labels, and includes functionality for dragging a toolbar and clearing markers and labels.

Uploaded by

Hydra Project
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views9 pages

Nouveau Document Texte

This document is a React component that integrates Mapbox GL and Mapbox Draw to create an interactive map application. It allows users to draw polygons, polylines, and markers, as well as add labels to the map. The component manages various states for drawing modes, markers, and labels, and includes functionality for dragging a toolbar and clearing markers and labels.

Uploaded by

Hydra Project
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 9

'use client';

import * as React from 'react';


import mapboxgl from 'mapbox-gl';
import MapboxDraw from '@mapbox/mapbox-gl-draw';
import 'mapbox-gl/dist/mapbox-gl.css';
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
import '@material-design-icons/font/index.css';

const MAPBOX_TOKEN =
'pk.eyJ1IjoiaHlkcm9tb2JpbGUiLCJhIjoiY2x0YWt4MzRhMGtuejJrbWxwM3ZyZm02aiJ9.DIvAxkGuhR
EiJe_OgThF2g';
const DRAW_MODES = {
polygon: 'draw_polygon',
polyline: 'draw_line_string',
marker: 'draw_point',
label: 'draw_label' // mode fictif pour le label
};

export default function App() {


const mapRef = React.useRef<mapboxgl.Map | null>(null);
const drawRef = React.useRef<MapboxDraw | null>(null);
const [mode, setMode] = React.useState<keyof typeof DRAW_MODES | null>(null);
const [isMarkerMode, setIsMarkerMode] = React.useState(false);
const [isLabelMode, setIsLabelMode] = React.useState(false);
const [markers, setMarkers] = React.useState<mapboxgl.Marker[]>([]);
const [labels, setLabels] = React.useState<mapboxgl.Marker[]>([]);
const [selectedMarker, setSelectedMarker] = React.useState<mapboxgl.Marker |
null>(null);
const [selectedLabel, setSelectedLabel] = React.useState<mapboxgl.Marker |
null>(null);
const [position, setPosition] = React.useState({ x: 20, y: 20 });
const [isDragging, setIsDragging] = React.useState(false);
const dragStartPos = React.useRef({ x: 0, y: 0 });
const [labelInput, setLabelInput] = React.useState<{lng: number, lat: number, x:
number, y: number} | null>(null);
const [labelInputValue, setLabelInputValue] = React.useState('');
const mapContainerRef = React.useRef<HTMLDivElement>(null);

const POLYGON_COLOR = '#2196F3';


const POLYLINE_COLOR = '#4CAF50';
const MARKER_COLOR = '#FF9800';

const layerIds = [
'gl-draw-polygon-fill-inactive',
'gl-draw-polygon-fill-active',
'gl-draw-polygon-stroke-inactive',
'gl-draw-polygon-stroke-active',
'gl-draw-line-inactive',
'gl-draw-line-active',
'gl-draw-point-inactive',
'gl-draw-point-active'
];

React.useEffect(() => {
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/satellite-streets-v12',
accessToken: MAPBOX_TOKEN,
center: [0, 20],
zoom: 1.5,
projection: 'globe'
});
mapRef.current = map;
map.addControl(new mapboxgl.NavigationControl(), 'top-left');

const draw = new MapboxDraw({


displayControlsDefault: false,
styles: [
{
id: 'gl-draw-polygon-fill',
type: 'fill',
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
paint: { 'fill-color': POLYGON_COLOR, 'fill-opacity': 0.3 }
},
{
id: 'gl-draw-polygon-stroke',
type: 'line',
filter: ['all', ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
paint: { 'line-color': POLYGON_COLOR, 'line-width': 4 }
},
{
id: 'gl-draw-line',
type: 'line',
filter: ['all', ['==', '$type', 'LineString'], ['!=', 'mode', 'static']],
paint: { 'line-color': POLYLINE_COLOR, 'line-width': 4 }
},
{
id: 'gl-draw-point',
type: 'circle',
filter: ['all', ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
paint: {
'circle-radius': 8,
'circle-color': MARKER_COLOR,
'circle-stroke-color': '#fff',
'circle-stroke-width': 2,
'circle-pitch-alignment': 'map'
}
},
{
id: 'gl-draw-polygon-and-line-vertex',
type: 'circle',
filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point']],
paint: {
'circle-radius': 4,
'circle-color': '#fff',
'circle-stroke-color': POLYGON_COLOR,
'circle-stroke-width': 1
}
},
{
id: 'custom-vertex-always',
type: 'circle',
filter: ['all', ['==', 'meta', 'vertex'], ['!=', 'mode', 'static']],
paint: {
'circle-radius': 5,
'circle-color': '#2196F3',
'circle-stroke-color': '#fff',
'circle-stroke-width': 2
}
}
]
});
map.addControl(draw, 'top-left');
drawRef.current = draw;

map.on('load', () => {
layerIds.forEach(id => {
if (map.getLayer(id)) {
const prop = id.includes('fill') ? 'fill-color' : id.includes('line') ?
'line-color' : 'circle-color';
map.setPaintProperty(
id,
prop,
['case', ['has', 'color'], ['get', 'color'], POLYGON_COLOR]
);
}
});
});
}, []);

// Ajout du marker bleu sans recréer la carte


React.useEffect(() => {
const map = mapRef.current;
if (!map) return;
const handleClick = (e: mapboxgl.MapMouseEvent) => {
if (isMarkerMode) {
const { lng, lat } = e.lngLat;
const marker = new mapboxgl.Marker({ color: selectedMarker ? '#2196F3' :
'#2196F3' })
.setLngLat([lng, lat])
.addTo(map);
marker.getElement().style.cursor = 'pointer';
marker.getElement().addEventListener('click', (ev) => {
ev.stopPropagation();
setSelectedMarker(marker);
});
marker.getElement().addEventListener('contextmenu', (ev) => {
ev.preventDefault();
setMarkers((prev) => prev.filter(m => m !== marker));
marker.remove();
if (selectedMarker === marker) setSelectedMarker(null);
});
setMarkers((prev) => [...prev, marker]);
setSelectedMarker(marker);
}
};
map.on('click', handleClick);
return () => {
map.off('click', handleClick);
};
}, [isMarkerMode, selectedMarker]);

// Met à jour la couleur du marker sélectionné


React.useEffect(() => {
markers.forEach(marker => {
// marker.getElement().style.boxShadow = marker === selectedMarker ? '0 0 0
4px #1976d2' : '';
marker.getElement().style.zIndex = marker === selectedMarker ? '100' : '1';
});
}, [selectedMarker, markers]);

// Effacer tous les markers


const clearAllMarkers = () => {
markers.forEach(marker => marker.remove());
setMarkers([]);
setSelectedMarker(null);
};

// Ajout du label texte sans recréer la carte


React.useEffect(() => {
const map = mapRef.current;
if (!map) return;
const handleLabelClick = (e: mapboxgl.MapMouseEvent) => {
if (isLabelMode && mapContainerRef.current) {
const { lng, lat } = e.lngLat;
const point = map.project([lng, lat]);
setLabelInput({ lng, lat, x: point.x, y: point.y });
setLabelInputValue('');
}
};
map.on('click', handleLabelClick);
return () => {
map.off('click', handleLabelClick);
};
}, [isLabelMode]);

// Fonction pour valider le label


const handleLabelInputSubmit = () => {
if (labelInput && labelInputValue.trim() !== '' && mapRef.current) {
const el = document.createElement('div');
el.style.background = 'rgba(33,150,243,0.9)';
el.style.color = '#fff';
el.style.padding = '2px 8px';
el.style.borderRadius = '6px';
el.style.fontSize = '15px';
el.style.fontWeight = 'bold';
el.style.whiteSpace = 'nowrap';
el.style.boxShadow = '0 2px 6px rgba(0,0,0,0.15)';
el.innerText = labelInputValue;
const marker = new mapboxgl.Marker({ element: el, draggable: false })
.setLngLat([labelInput.lng, labelInput.lat])
.addTo(mapRef.current);
// Sélection et suppression
el.style.cursor = 'pointer';
el.addEventListener('click', (ev) => {
ev.stopPropagation();
setSelectedLabel(marker);
});
el.addEventListener('contextmenu', (ev) => {
ev.preventDefault();
setLabels(prev => prev.filter(m => m !== marker));
marker.remove();
if (selectedLabel === marker) setSelectedLabel(null);
});
setLabels(prev => [...prev, marker]);
setSelectedLabel(marker);
}
setLabelInput(null);
setLabelInputValue('');
};

// Met à jour le style du label sélectionné


React.useEffect(() => {
labels.forEach(marker => {
const el = marker.getElement();
el.style.outline = marker === selectedLabel ? '2px solid #fff' : '';
el.style.background = marker === selectedLabel ? '#1976d2' :
'rgba(33,150,243,0.9)';
el.style.zIndex = marker === selectedLabel ? '100' : '1';
});
}, [selectedLabel, labels]);

// Effacer tous les labels


const clearAllLabels = () => {
labels.forEach(marker => marker.remove());
setLabels([]);
setSelectedLabel(null);
};

React.useEffect(() => {
if (mode === 'marker') {
setIsMarkerMode(true);
setIsLabelMode(false);
} else if (mode === 'label') {
setIsLabelMode(true);
setIsMarkerMode(false);
} else {
setIsMarkerMode(false);
setIsLabelMode(false);
}
if (!drawRef.current) return;
drawRef.current.changeMode(mode && mode !== 'marker' && mode !== 'label' ?
DRAW_MODES[mode] : 'simple_select');
}, [mode]);

const clearAll = () => drawRef.current?.deleteAll();

const handleMouseDown = (e: React.MouseEvent) => {


if (e.target instanceof HTMLElement && e.target.closest('.toolbar-handle')) {
setIsDragging(true);
dragStartPos.current = {
x: e.clientX - position.x,
y: e.clientY - position.y
};
}
};

const handleMouseMove = (e: MouseEvent) => {


if (isDragging) {
const newX = e.clientX - dragStartPos.current.x;
const newY = e.clientY - dragStartPos.current.y;
const maxX = window.innerWidth - 100;
const maxY = window.innerHeight - 100;
setPosition({
x: Math.max(0, Math.min(newX, maxX)),
y: Math.max(0, Math.min(newY, maxY))
});
}
};

const handleMouseUp = () => {


setIsDragging(false);
};

React.useEffect(() => {
if (isDragging) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging]);

return (
<div ref={mapContainerRef} style={{ position: 'relative', width: '100vw',
height: '100vh' }}>
<div
style={{
position: 'absolute',
left: position.x,
top: position.y,
zIndex: 10,
background: 'rgba(255, 255, 255, 0.95)',
padding: '12px 8px',
borderRadius: 16,
display: 'flex',
flexDirection: 'column',
gap: 8,
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
backdropFilter: 'blur(8px)',
cursor: isDragging ? 'grabbing' : 'default',
userSelect: 'none'
}}
onMouseDown={handleMouseDown}
>
<div
className="toolbar-handle"
style={{
padding: '4px',
marginBottom: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'grab'
}}
>
<span className="material-icons" style={{ color: '#666', fontSize: '20px'
}}>drag_indicator</span>
</div>
{Object.keys(DRAW_MODES).map(key => (
<button
key={key}
onClick={() => setMode(key as keyof typeof DRAW_MODES)}
style={{
padding: '8px',
borderRadius: 12,
border: (mode === key || (key === 'marker' && isMarkerMode) || (key
=== 'label' && isLabelMode)) ? `2px solid ${POLYGON_COLOR}` : '1px solid
rgba(0,0,0,0.1)',
background: (mode === key || (key === 'marker' && isMarkerMode) ||
(key === 'label' && isLabelMode)) ? POLYGON_COLOR : 'rgba(255,255,255,0.9)',
color: (mode === key || (key === 'marker' && isMarkerMode) || (key
=== 'label' && isLabelMode)) ? '#fff' : '#424242',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '44px',
height: '44px',
}}
title={key === 'marker' ? 'Marker' : key === 'label' ? 'Label' :
key.charAt(0).toUpperCase() + key.slice(1)}
>
{key === 'polygon' && <span className="material-icons">draw</span>}
{key === 'polyline' && <span
className="material-icons">timeline</span>}
{key === 'marker' && <span className="material-icons">flag</span>}
{key === 'label' && <span
style={{fontWeight:'bold',fontSize:18}}>ABC</span>}
</button>
))}
<div style={{ height: '1px', background: 'rgba(0,0,0,0.1)', margin: '4px 0'
}} />
<button
onClick={() => setMode(null)}
style={{
padding: '8px',
borderRadius: 12,
border: !mode ? `2px solid ${POLYGON_COLOR}` : '1px solid
rgba(0,0,0,0.1)',
background: !mode ? POLYGON_COLOR : 'rgba(255,255,255,0.9)',
color: !mode ? '#fff' : '#424242',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '44px',
height: '44px',
}}
title="Sélection"
>
<span className="material-icons">select_all</span>
</button>
<button
onClick={clearAllMarkers}
style={{
padding: '8px',
borderRadius: 12,
border: '1px solid #1976d2',
background: '#1976d2',
color: '#fff',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '44px',
height: '44px',
}}
title="Effacer tous les markers"
>
<span className="material-icons">location_off</span>
</button>
<button
onClick={clearAllLabels}
style={{
padding: '8px',
borderRadius: 12,
border: '1px solid #4CAF50',
background: '#4CAF50',
color: '#fff',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '44px',
height: '44px',
}}
title="Effacer tous les labels"
>
<span className="material-icons">text_fields</span>
</button>
</div>
{labelInput && (
<input
autoFocus
style={{
position: 'absolute',
left: labelInput.x,
top: labelInput.y,
transform: 'translate(-50%, -100%)',
zIndex: 1000,
fontSize: 15,
fontWeight: 'bold',
borderRadius: 6,
border: '1px solid #2196F3',
padding: '2px 8px',
background: '#fff',
color: '#222',
boxShadow: '0 2px 6px rgba(0,0,0,0.15)'
}}
value={labelInputValue}
onChange={e => setLabelInputValue(e.target.value)}
onBlur={handleLabelInputSubmit}
onKeyDown={e => {
if (e.key === 'Enter') handleLabelInputSubmit();
if (e.key === 'Escape') { setLabelInput(null);
setLabelInputValue(''); }
}}
placeholder="Entrer le texte du label..."
/>
)}
<div id="map" style={{ width: '100%', height: '100%' }} />
</div>
);
}

You might also like