/** * 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: '
', 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); } }