/** * Cut Drawing Module * Handles polygon drawing functionality for cut creation */ class CutDrawing { constructor(map) { this.map = map; this.vertices = []; this.markers = []; this.polyline = null; this.polygon = null; // Add polygon preview this.finalPolygon = null; // Final polygon after finishing this.isDrawing = false; this.onFinishCallback = null; this.onCancelCallback = null; this.currentColor = '#3388ff'; this.currentOpacity = 0.3; } startDrawing(onFinish, onCancel) { if (this.isDrawing) { this.cancelDrawing(); } this.isDrawing = true; this.onFinishCallback = onFinish; this.onCancelCallback = onCancel; this.vertices = []; this.markers = []; this.map.getContainer().style.cursor = 'crosshair'; this.map.on('click', this.onMapClick.bind(this)); this.map.doubleClickZoom.disable(); console.log('Cut drawing started - click to add points'); } onMapClick(e) { if (!this.isDrawing) return; // Check if clicking on first vertex to close polygon if (this.vertices.length >= 3) { const firstVertex = this.vertices[0]; const clickPoint = this.map.latLngToContainerPoint(e.latlng); const firstPoint = this.map.latLngToContainerPoint(firstVertex); const distance = Math.sqrt( Math.pow(clickPoint.x - firstPoint.x, 2) + Math.pow(clickPoint.y - firstPoint.y, 2) ); // If clicked within 15 pixels of first vertex, close the polygon if (distance < 15) { console.log('Closing polygon by clicking first vertex'); this.finishDrawing(); return; } } // Add vertex marker with special styling for first vertex const isFirstVertex = this.vertices.length === 0; const marker = L.marker(e.latlng, { icon: L.divIcon({ className: 'cut-vertex-marker' + (isFirstVertex ? ' first-vertex' : ''), html: `
`, iconSize: [12, 12], iconAnchor: [6, 6] }), draggable: false }).addTo(this.map); // Add tooltip to first vertex after 3 points if (isFirstVertex) { marker.bindTooltip('Click to close polygon', { permanent: false, direction: 'top', offset: [0, -10] }); } this.vertices.push(e.latlng); this.markers.push(marker); this.updatePolyline(); // Show tooltip on first vertex when we have enough points if (this.vertices.length === 3 && this.markers[0]) { this.markers[0].openTooltip(); } // Call update callback if available if (this.onUpdate) { this.onUpdate(); } console.log(`Added vertex ${this.vertices.length} at`, e.latlng); } updatePolyline() { // Use stored color and opacity values const color = this.currentColor; const opacity = this.currentOpacity; if (this.polyline) { this.map.removeLayer(this.polyline); } if (this.polygon) { this.map.removeLayer(this.polygon); this.polygon = null; } if (this.vertices.length > 1) { // Show polyline for incomplete polygon this.polyline = L.polyline(this.vertices, { color: color, weight: 2, dashArray: '5, 5', opacity: 1.0 // Keep polyline stroke visible }).addTo(this.map); // Show preview polygon if we have 3+ vertices if (this.vertices.length >= 3) { this.polygon = L.polygon(this.vertices, { color: color, fillColor: color, fillOpacity: opacity, weight: 2, opacity: 0.8, dashArray: '5, 5' }).addTo(this.map); // Add cut-polygon class and force inline style if (this.polygon._path) { this.polygon._path.classList.add('cut-polygon'); // Use setProperty with important flag for stronger override this.polygon._path.style.setProperty('fill-opacity', opacity, 'important'); this.polygon._path.style.setProperty('fill', color, 'important'); console.log(`Created/updated polygon with opacity: ${opacity}`); } console.log('Created/updated polygon with opacity:', opacity); } } } finishDrawing() { console.log('finishDrawing() called'); console.log('isDrawing:', this.isDrawing); console.log('vertices count:', this.vertices.length); if (!this.isDrawing) { console.log('Not in drawing mode, returning null'); return null; } if (this.vertices.length < 3) { alert('A cut must have at least 3 points. Click more points or cancel drawing.'); return null; } // Store vertices before cleanup const finalVertices = [...this.vertices]; // Use stored color and opacity values const color = this.currentColor; const opacity = this.currentOpacity; // Create polygon from vertices const polygon = L.polygon(finalVertices, { color: color, fillColor: color, fillOpacity: opacity, weight: 2, opacity: 1.0 // Keep stroke visible }); // Get GeoJSON and bounds const geojson = polygon.toGeoJSON().geometry; // Get just the geometry part const bounds = polygon.getBounds(); // Clean up drawing elements but keep the final polygon this.cleanupDrawingElements(); // Add the final polygon to the map this.finalPolygon = polygon.addTo(this.map); // Call finish callback with the data console.log('Calling finish callbacks...'); const callbackData = { geojson: JSON.stringify(geojson), bounds: JSON.stringify([ [bounds.getSouth(), bounds.getWest()], [bounds.getNorth(), bounds.getEast()] ]), vertexCount: finalVertices.length, polygon: this.finalPolygon }; if (this.onFinishCallback) { console.log('Calling onFinishCallback'); this.onFinishCallback(callbackData); } else { console.log('No onFinishCallback set'); } // Also call the onFinish callback if set if (this.onFinish) { console.log('Calling onFinish'); this.onFinish(callbackData); } else { console.log('No onFinish callback set'); } console.log('Cut drawing finished with', finalVertices.length, 'vertices'); return geojson; } cancelDrawing() { if (!this.isDrawing) return; console.log('Cut drawing cancelled'); this.cleanup(); if (this.onCancelCallback) { this.onCancelCallback(); } // Also call the onCancel callback if set if (this.onCancel) { this.onCancel(); } } undoLastVertex() { if (!this.isDrawing || this.vertices.length === 0) return; this.vertices.pop(); const lastMarker = this.markers.pop(); if (lastMarker) { this.map.removeLayer(lastMarker); } this.updatePolyline(); // Call update callback if available if (this.onUpdate) { this.onUpdate(); } console.log('Removed last vertex, remaining:', this.vertices.length); } clearVertices() { if (!this.isDrawing) return; this.markers.forEach(marker => { this.map.removeLayer(marker); }); if (this.polyline) { this.map.removeLayer(this.polyline); this.polyline = null; } if (this.polygon) { this.map.removeLayer(this.polygon); this.polygon = null; } this.vertices = []; this.markers = []; // Call update callback if available if (this.onUpdate) { this.onUpdate(); } console.log('Cleared all vertices'); } cleanupDrawingElements() { // Remove drawing elements but preserve final polygon this.markers.forEach(marker => { this.map.removeLayer(marker); }); if (this.polyline) { this.map.removeLayer(this.polyline); } if (this.polygon) { this.map.removeLayer(this.polygon); } this.map.getContainer().style.cursor = ''; this.map.off('click', this.onMapClick); this.map.doubleClickZoom.enable(); this.isDrawing = false; this.vertices = []; this.markers = []; this.polyline = null; this.polygon = null; this.onFinishCallback = null; this.onCancelCallback = null; } cleanup() { this.markers.forEach(marker => { this.map.removeLayer(marker); }); if (this.polyline) { this.map.removeLayer(this.polyline); } if (this.polygon) { this.map.removeLayer(this.polygon); } if (this.finalPolygon) { this.map.removeLayer(this.finalPolygon); } this.map.getContainer().style.cursor = ''; this.map.off('click', this.onMapClick); this.map.doubleClickZoom.enable(); this.isDrawing = false; this.vertices = []; this.markers = []; this.polyline = null; this.polygon = null; this.finalPolygon = null; this.onFinishCallback = null; this.onCancelCallback = null; } getState() { return { isDrawing: this.isDrawing, vertexCount: this.vertices.length, canFinish: this.vertices.length >= 3 }; } // Add method to update current drawing style updateDrawingStyle(color, opacity) { this.currentColor = color; this.currentOpacity = opacity; console.log(`CutDrawing.updateDrawingStyle called with color: ${color}, opacity: ${opacity}`); // Update polyline color if (this.polyline) { this.polyline.setStyle({ color: color }); } // Update polygon if it exists if (this.polygon) { this.polygon.setStyle({ color: color, fillColor: color, fillOpacity: opacity, opacity: 0.8 // Border opacity }); // Force inline style update and browser reflow if (this.polygon._path) { // Use setProperty with important flag for stronger override this.polygon._path.style.setProperty('fill-opacity', opacity, 'important'); this.polygon._path.style.setProperty('fill', color, 'important'); // Force multiple reflows to ensure update void this.polygon._path.offsetHeight; void this.polygon._path.offsetWidth; // Force repaint by temporarily changing a property const oldDisplay = this.polygon._path.style.display; this.polygon._path.style.display = 'none'; void this.polygon._path.offsetHeight; this.polygon._path.style.display = oldDisplay; } // Also try Leaflet's internal redraw if (this.polygon._updatePath) { this.polygon._updatePath(); } console.log(`Updated active drawing polygon with opacity: ${opacity}`); } else { // If no polygon exists but we have vertices, force a complete redraw console.log('No polygon exists, forcing complete redraw'); this.forceRedraw(); } } clearPreview() { // Clear any preview polygons but not the final polygon if (this.polygon) { this.map.removeLayer(this.polygon); this.polygon = null; } if (this.polyline && this.isDrawing) { this.map.removeLayer(this.polyline); this.polyline = null; } } // Force a complete redraw with current style settings forceRedraw() { if (this.vertices.length > 1) { console.log('Forcing complete redraw with vertices:', this.vertices.length); this.updatePolyline(); } } } // Export the class if using modules, otherwise it's global if (typeof module !== 'undefined' && module.exports) { module.exports = CutDrawing; } else { window.CutDrawing = CutDrawing; }