freealberta/map/app/public/js/cut-drawing.js

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);
}
}