426 lines
13 KiB
JavaScript
426 lines
13 KiB
JavaScript
/**
|
|
* 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: `<div class="vertex-point${isFirstVertex ? ' first' : ''}"></div>`,
|
|
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;
|
|
}
|