337 lines
9.3 KiB
JavaScript
337 lines
9.3 KiB
JavaScript
/**
|
|
* Cut Drawing Module
|
|
* Handles polygon drawing functionality for creating map cuts
|
|
*/
|
|
|
|
export class CutDrawing {
|
|
constructor(map, options = {}) {
|
|
this.map = map;
|
|
this.vertices = [];
|
|
this.markers = [];
|
|
this.polyline = null;
|
|
this.previewPolygon = null; // Add preview polygon
|
|
this.isDrawing = false;
|
|
this.onComplete = options.onComplete || null;
|
|
}
|
|
|
|
/**
|
|
* Start drawing mode
|
|
*/
|
|
startDrawing(onFinish, onCancel) {
|
|
if (this.isDrawing) {
|
|
this.cancelDrawing();
|
|
}
|
|
|
|
this.isDrawing = true;
|
|
this.onFinishCallback = onFinish;
|
|
this.onCancelCallback = onCancel;
|
|
this.vertices = [];
|
|
this.markers = [];
|
|
|
|
// Change cursor and add click listener
|
|
this.map.getContainer().style.cursor = 'crosshair';
|
|
this.map.on('click', this.onMapClick.bind(this));
|
|
|
|
// Disable double-click zoom while drawing
|
|
this.map.doubleClickZoom.disable();
|
|
|
|
console.log('Cut drawing started - click to add points');
|
|
}
|
|
|
|
/**
|
|
* Handle map clicks to add vertices
|
|
*/
|
|
onMapClick(e) {
|
|
if (!this.isDrawing) return;
|
|
|
|
// Add vertex marker
|
|
const marker = L.marker(e.latlng, {
|
|
icon: L.divIcon({
|
|
className: 'cut-vertex-marker',
|
|
html: '<div class="vertex-point"></div>',
|
|
iconSize: [12, 12],
|
|
iconAnchor: [6, 6]
|
|
}),
|
|
draggable: false
|
|
}).addTo(this.map);
|
|
|
|
this.vertices.push(e.latlng);
|
|
this.markers.push(marker);
|
|
|
|
// Update the polyline
|
|
this.updatePolyline();
|
|
|
|
// Call update callback if available
|
|
if (this.onUpdate) {
|
|
this.onUpdate();
|
|
}
|
|
|
|
console.log(`Added vertex ${this.vertices.length} at`, e.latlng);
|
|
}
|
|
|
|
/**
|
|
* Update the polyline connecting vertices
|
|
*/
|
|
updatePolyline() {
|
|
// Remove existing polyline
|
|
if (this.polyline) {
|
|
this.map.removeLayer(this.polyline);
|
|
}
|
|
|
|
if (this.vertices.length > 1) {
|
|
// Create polyline connecting all vertices
|
|
this.polyline = L.polyline(this.vertices, {
|
|
color: '#3388ff',
|
|
weight: 2,
|
|
dashArray: '5, 5',
|
|
opacity: 0.8
|
|
}).addTo(this.map);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finish drawing and create polygon
|
|
*/
|
|
finishDrawing() {
|
|
if (this.vertices.length < 3) {
|
|
alert('A cut must have at least 3 points');
|
|
return;
|
|
}
|
|
|
|
// Create the polygon
|
|
const latlngs = this.vertices.map(v => v.getLatLng());
|
|
|
|
// Close the polygon
|
|
latlngs.push(latlngs[0]);
|
|
|
|
// Generate GeoJSON
|
|
const geojson = {
|
|
type: 'Polygon',
|
|
coordinates: [latlngs.map(ll => [ll.lng, ll.lat])]
|
|
};
|
|
|
|
// Calculate bounds
|
|
const bounds = {
|
|
north: Math.max(...latlngs.map(ll => ll.lat)),
|
|
south: Math.min(...latlngs.map(ll => ll.lat)),
|
|
east: Math.max(...latlngs.map(ll => ll.lng)),
|
|
west: Math.min(...latlngs.map(ll => ll.lng))
|
|
};
|
|
|
|
console.log('Cut drawing finished with', this.vertices.length, 'vertices');
|
|
|
|
// Show preview before clearing drawing
|
|
const color = document.getElementById('cut-color')?.value || '#3388ff';
|
|
const opacity = parseFloat(document.getElementById('cut-opacity')?.value) || 0.3;
|
|
this.showPreview(geojson, color, opacity);
|
|
|
|
// Clean up drawing elements
|
|
this.clearDrawing();
|
|
|
|
// Call completion callback with the data
|
|
if (this.onComplete && typeof this.onComplete === 'function') {
|
|
console.log('Calling completion callback with geojson and bounds');
|
|
this.onComplete(geojson, bounds);
|
|
} else {
|
|
console.error('No completion callback defined');
|
|
}
|
|
|
|
// Reset state
|
|
this.isDrawing = false;
|
|
this.updateToolbar();
|
|
}
|
|
|
|
/**
|
|
* Cancel drawing
|
|
*/
|
|
cancelDrawing() {
|
|
if (!this.isDrawing) return;
|
|
|
|
console.log('Cut drawing cancelled');
|
|
this.cleanup();
|
|
|
|
if (this.onCancelCallback) {
|
|
this.onCancelCallback();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the last added vertex
|
|
*/
|
|
undoLastVertex() {
|
|
if (!this.isDrawing || this.vertices.length === 0) return;
|
|
|
|
// Remove last vertex and marker
|
|
this.vertices.pop();
|
|
const lastMarker = this.markers.pop();
|
|
if (lastMarker) {
|
|
this.map.removeLayer(lastMarker);
|
|
}
|
|
|
|
// Update polyline
|
|
this.updatePolyline();
|
|
|
|
// Call update callback if available
|
|
if (this.onUpdate) {
|
|
this.onUpdate();
|
|
}
|
|
|
|
console.log('Removed last vertex, remaining:', this.vertices.length);
|
|
}
|
|
|
|
/**
|
|
* Clear all vertices and start over
|
|
*/
|
|
clearVertices() {
|
|
if (!this.isDrawing) return;
|
|
|
|
// Remove all markers
|
|
this.markers.forEach(marker => {
|
|
this.map.removeLayer(marker);
|
|
});
|
|
|
|
// Remove polyline
|
|
if (this.polyline) {
|
|
this.map.removeLayer(this.polyline);
|
|
this.polyline = null;
|
|
}
|
|
|
|
// Reset arrays
|
|
this.vertices = [];
|
|
this.markers = [];
|
|
|
|
// Call update callback if available
|
|
if (this.onUpdate) {
|
|
this.onUpdate();
|
|
}
|
|
|
|
console.log('Cleared all vertices');
|
|
}
|
|
|
|
/**
|
|
* Cleanup drawing state
|
|
*/
|
|
cleanup() {
|
|
// Remove all markers
|
|
this.markers.forEach(marker => {
|
|
this.map.removeLayer(marker);
|
|
});
|
|
|
|
// Remove polyline
|
|
if (this.polyline) {
|
|
this.map.removeLayer(this.polyline);
|
|
}
|
|
|
|
// Reset cursor
|
|
this.map.getContainer().style.cursor = '';
|
|
|
|
// Remove event listeners
|
|
this.map.off('click', this.onMapClick);
|
|
|
|
// Re-enable double-click zoom
|
|
this.map.doubleClickZoom.enable();
|
|
|
|
// Reset state
|
|
this.isDrawing = false;
|
|
this.vertices = [];
|
|
this.markers = [];
|
|
this.polyline = null;
|
|
this.onFinishCallback = null;
|
|
this.onCancelCallback = null;
|
|
}
|
|
|
|
/**
|
|
* Get current drawing state
|
|
*/
|
|
getState() {
|
|
return {
|
|
isDrawing: this.isDrawing,
|
|
vertexCount: this.vertices.length,
|
|
canFinish: this.vertices.length >= 3
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Preview polygon without finishing
|
|
*/
|
|
showPreview(geojson, color = '#3388ff', opacity = 0.3) {
|
|
this.clearPreview();
|
|
|
|
if (!geojson) return;
|
|
|
|
try {
|
|
const coordinates = geojson.coordinates[0];
|
|
const latlngs = coordinates.map(coord => L.latLng(coord[1], coord[0]));
|
|
|
|
this.previewPolygon = L.polygon(latlngs, {
|
|
color: color,
|
|
weight: 2,
|
|
opacity: 0.8,
|
|
fillColor: color,
|
|
fillOpacity: opacity,
|
|
className: 'cut-preview-polygon'
|
|
}).addTo(this.map);
|
|
|
|
// Add CSS class for opacity control
|
|
const pathElement = this.previewPolygon.getElement();
|
|
if (pathElement) {
|
|
pathElement.classList.add('cut-polygon');
|
|
console.log('Added cut-polygon class to preview polygon');
|
|
}
|
|
|
|
console.log('Preview polygon shown with opacity:', opacity);
|
|
} catch (error) {
|
|
console.error('Error showing preview polygon:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update preview polygon style without recreating it
|
|
*/
|
|
updatePreview(color = '#3388ff', opacity = 0.3) {
|
|
if (this.previewPolygon) {
|
|
this.previewPolygon.setStyle({
|
|
color: color,
|
|
weight: 2,
|
|
opacity: 0.8,
|
|
fillColor: color,
|
|
fillOpacity: opacity
|
|
});
|
|
|
|
// Ensure CSS class is still present
|
|
const pathElement = this.previewPolygon.getElement();
|
|
if (pathElement) {
|
|
pathElement.classList.add('cut-polygon');
|
|
}
|
|
|
|
console.log('Preview polygon style updated with opacity:', opacity);
|
|
}
|
|
}
|
|
|
|
clearPreview() {
|
|
if (this.previewPolygon) {
|
|
this.map.removeLayer(this.previewPolygon);
|
|
this.previewPolygon = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update drawing style (called from admin cuts manager)
|
|
*/
|
|
updateDrawingStyle(color = '#3388ff', opacity = 0.3) {
|
|
// Update the polyline connecting vertices if it exists
|
|
if (this.polyline) {
|
|
this.polyline.setStyle({
|
|
color: color,
|
|
weight: 2,
|
|
opacity: 0.8
|
|
});
|
|
}
|
|
|
|
// Update preview polygon if it exists
|
|
this.updatePreview(color, opacity);
|
|
|
|
console.log('Cut drawing style updated with color:', color, 'opacity:', opacity);
|
|
}
|
|
}
|