Skip to content

Commit 33e1e93

Browse files
POC: Draw segments and polygon on map
1 parent df083e2 commit 33e1e93

File tree

10 files changed

+978
-2
lines changed

10 files changed

+978
-2
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
import maplibregl from 'maplibre-gl';
3+
import 'maplibre-gl/dist/maplibre-gl.css';
4+
import { mapStyles } from 'carte-facile';
5+
import { DrawManager } from '../customElements/draw-manager';
6+
import { OsrmRouter } from '../customElements/osrm-router';
7+
import '../styles/components/draw-map.css';
8+
9+
export default class extends Controller {
10+
static targets = ['container', 'polygonBtn', 'roadLineBtn', 'validateBtn'];
11+
static values = {
12+
centerJson: String,
13+
zoom: Number,
14+
submitUrl: String,
15+
};
16+
17+
#map = null;
18+
#draw = null;
19+
#currentMode = null;
20+
#router = null;
21+
22+
connect() {
23+
this.initializeMap();
24+
}
25+
26+
disconnect() {
27+
this.#map?.remove();
28+
this.#map = null;
29+
}
30+
31+
get centerCoords() {
32+
return JSON.parse(this.centerJsonValue || '[2.725, 47.16]');
33+
}
34+
35+
initializeMap() {
36+
if (!this.hasContainerTarget) {
37+
return;
38+
}
39+
40+
try {
41+
const centerCoords = this.centerCoords;
42+
const zoomLevel = this.zoomValue || 5;
43+
44+
this.#map = new maplibregl.Map({
45+
container: this.containerTarget,
46+
style: mapStyles.desaturated,
47+
center: centerCoords,
48+
zoom: zoomLevel,
49+
minZoom: 4.33,
50+
maxZoom: 18,
51+
hash: 'mapZoomAndPosition',
52+
});
53+
54+
this.#map.on('load', () => {
55+
this.addNavigationControl();
56+
this.initializeDrawControl();
57+
this.setupDrawing();
58+
});
59+
60+
this.#map.on('error', (e) => {
61+
console.error('Erreur MapLibre:', e);
62+
});
63+
} catch (error) {
64+
console.error('Erreur lors de l\'initialisation de la carte:', error);
65+
}
66+
}
67+
68+
addNavigationControl() {
69+
this.#map.addControl(new maplibregl.NavigationControl(), 'top-left');
70+
}
71+
72+
initializeDrawControl() {
73+
this.#draw = new DrawManager(this.#map);
74+
this.#router = new OsrmRouter();
75+
}
76+
77+
updateButtonClasses() {
78+
this.polygonBtnTarget?.classList.toggle('active', this.#currentMode === 'polygon');
79+
this.roadLineBtnTarget?.classList.toggle('active', this.#currentMode === 'road-line');
80+
}
81+
82+
togglePolygon() {
83+
this.#currentMode = this.#currentMode === 'polygon' ? null : 'polygon';
84+
this.updateButtonClasses();
85+
}
86+
87+
toggleRoadLine() {
88+
this.#currentMode = this.#currentMode === 'road-line' ? null : 'road-line';
89+
this.updateButtonClasses();
90+
}
91+
92+
setupDrawing() {
93+
let isDrawingPolygon = false;
94+
let isDrawingRoadLine = false;
95+
let isRoutingInProgress = false;
96+
97+
this.#map.on('mousedown', (e) => {
98+
if (!this.#currentMode) {
99+
return;
100+
}
101+
102+
if (this.#currentMode === 'polygon') {
103+
if (!isDrawingPolygon) {
104+
this.#draw.setMode('polygon');
105+
isDrawingPolygon = true;
106+
this.#map.dragPan.disable();
107+
}
108+
this.#draw.addCoordinate(e.lngLat);
109+
this.#draw.updatePreview();
110+
this.#map.getCanvas().style.cursor = 'crosshair';
111+
} else if (this.#currentMode === 'road-line') {
112+
if (isRoutingInProgress) return;
113+
114+
if (!isDrawingRoadLine) {
115+
this.#draw.setMode('road-line');
116+
isDrawingRoadLine = true;
117+
}
118+
119+
const newPoint = [e.lngLat.lng, e.lngLat.lat];
120+
const previousWaypoints = this.#draw.waypoints;
121+
122+
if (previousWaypoints.length === 0) {
123+
this.#draw.addWaypoint(e.lngLat);
124+
this.#draw.updateRoutedPreview();
125+
this.#map.getCanvas().style.cursor = 'crosshair';
126+
} else {
127+
const lastWaypoint = previousWaypoints[previousWaypoints.length - 1];
128+
isRoutingInProgress = true;
129+
this.#map.getCanvas().style.cursor = 'wait';
130+
131+
this.#router.getSegment(lastWaypoint, newPoint)
132+
.then((segmentCoords) => {
133+
this.#draw.addWaypoint(e.lngLat);
134+
this.#draw.addRoutedSegment(segmentCoords);
135+
this.#draw.updateRoutedPreview();
136+
})
137+
.catch((err) => {
138+
console.error('Routing error:', err);
139+
this.#draw.addWaypoint(e.lngLat);
140+
this.#draw.addRoutedSegment([lastWaypoint, newPoint]);
141+
this.#draw.updateRoutedPreview();
142+
})
143+
.finally(() => {
144+
isRoutingInProgress = false;
145+
this.#map.getCanvas().style.cursor = 'crosshair';
146+
});
147+
}
148+
}
149+
});
150+
151+
this.#map.on('dblclick', (e) => {
152+
if (isDrawingPolygon && this.#currentMode === 'polygon') {
153+
isDrawingPolygon = false;
154+
this.#map.dragPan.enable();
155+
this.#draw.finishDrawing();
156+
this.#draw.clearPreview();
157+
this.dispatch('drawUpdated', { detail: { data: this.#draw.getAll() } });
158+
this.#map.getCanvas().style.cursor = '';
159+
e.preventDefault();
160+
}
161+
162+
if (isDrawingRoadLine && this.#currentMode === 'road-line') {
163+
isDrawingRoadLine = false;
164+
this.#draw.finishRoutedLine();
165+
this.#draw.clearPreview();
166+
this.dispatch('drawUpdated', { detail: { data: this.#draw.getAll() } });
167+
this.#map.getCanvas().style.cursor = '';
168+
e.preventDefault();
169+
}
170+
});
171+
172+
document.addEventListener('keydown', (e) => {
173+
if (e.key === 'Escape') {
174+
if (isDrawingPolygon) {
175+
isDrawingPolygon = false;
176+
this.clearPolygonPreview();
177+
}
178+
if (isDrawingRoadLine) {
179+
isDrawingRoadLine = false;
180+
this.#draw.waypoints = [];
181+
this.#draw.routedSegments = [];
182+
this.#draw.clearPreview();
183+
this.#map.getCanvas().style.cursor = '';
184+
}
185+
}
186+
});
187+
}
188+
189+
getDrawnData() {
190+
return this.#draw?.getAll() || { type: 'FeatureCollection', features: [] };
191+
}
192+
193+
setDrawnData(data) {
194+
if (this.#draw && data && data.features) {
195+
this.#draw.setData(data);
196+
}
197+
}
198+
199+
clearPolygonPreview() {
200+
this.#map.dragPan.enable();
201+
this.#draw.currentCoordinates = [];
202+
this.#draw.clearPreview();
203+
this.#map.getCanvas().style.cursor = '';
204+
}
205+
206+
clearAll() {
207+
if (this.#draw) {
208+
this.#draw.deleteAll();
209+
this.clearPolygonPreview();
210+
this.#currentMode = null;
211+
this.updateButtonClasses();
212+
}
213+
}
214+
215+
async submitGeoJSON() {
216+
const geoJsonData = this.getDrawnData();
217+
218+
if (!geoJsonData.features || geoJsonData.features.length === 0) {
219+
alert('Veuillez tracer un polygone ou une ligne avant de valider.');
220+
221+
return;
222+
}
223+
224+
try {
225+
this.hasValidateBtnTarget && (this.validateBtnTarget.disabled = true);
226+
227+
const response = await fetch(this.submitUrlValue, {
228+
method: 'POST',
229+
headers: {
230+
'Content-Type': 'application/json',
231+
'X-Requested-With': 'XMLHttpRequest',
232+
},
233+
body: JSON.stringify(geoJsonData),
234+
});
235+
236+
if (!response.ok) {
237+
throw new Error(`Erreur serveur: ${response.statusText}`);
238+
}
239+
240+
const result = await response.json();
241+
242+
alert('GeoJSON envoyé avec succès !');
243+
this.dispatch('submitSuccess', { detail: result });
244+
245+
this.clearAll();
246+
} catch (error) {
247+
console.error('Erreur lors de l\'envoi du GeoJSON:', error);
248+
alert(`Erreur: ${error.message}`);
249+
this.dispatch('submitError', { detail: { error: error.message } });
250+
}
251+
}
252+
}

0 commit comments

Comments
 (0)