toggle to 90°
This commit is contained in:
parent
f99fcb8916
commit
2e681dffb4
2 changed files with 175 additions and 173 deletions
|
@ -571,7 +571,7 @@ label.theme-color-label {
|
||||||
|
|
||||||
|
|
||||||
/* watchout for leaflet.css trying to override a:hover with a different height/width */
|
/* watchout for leaflet.css trying to override a:hover with a different height/width */
|
||||||
a.snap-toggle, a.snap-to-original-toggle {
|
a.snap-toggle, a.snap-to-original-toggle, a.snap-to-90-toggle {
|
||||||
background-size: 30px 30px;
|
background-size: 30px 30px;
|
||||||
display: block;
|
display: block;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
@ -595,6 +595,10 @@ label.theme-color-label {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.snap-to-90-toggle {
|
||||||
|
background-color: yellow !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* icons */
|
/* icons */
|
||||||
a.snap-toggle {
|
a.snap-toggle {
|
||||||
background-image: url("/static/img/snap-to-edges-icon.svg");
|
background-image: url("/static/img/snap-to-edges-icon.svg");
|
||||||
|
@ -602,6 +606,9 @@ label.theme-color-label {
|
||||||
a.snap-to-original-toggle {
|
a.snap-to-original-toggle {
|
||||||
background-image: url("/static/img/snap-to-original-icon.svg");
|
background-image: url("/static/img/snap-to-original-icon.svg");
|
||||||
}
|
}
|
||||||
|
a.snap-to-90-toggle {
|
||||||
|
background-image: url("/static/img/snap-to-90-icon.svg");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Snap indicator styles */
|
/* Snap indicator styles */
|
||||||
|
|
|
@ -1673,6 +1673,7 @@ editor = {
|
||||||
// Snap-to-edges functionality
|
// Snap-to-edges functionality
|
||||||
_snap_enabled: false,
|
_snap_enabled: false,
|
||||||
_snap_to_original_enabled: false,
|
_snap_to_original_enabled: false,
|
||||||
|
_snap_to_90_enable: false,
|
||||||
_snap_distance: 30, // pixels
|
_snap_distance: 30, // pixels
|
||||||
_extension_area_multiplier: 4, // Extension area = snap_distance * this multiplier
|
_extension_area_multiplier: 4, // Extension area = snap_distance * this multiplier
|
||||||
_snap_to_base_map: false,
|
_snap_to_base_map: false,
|
||||||
|
@ -1689,7 +1690,6 @@ editor = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_add_snap_controls: function() {
|
_add_snap_controls: function() {
|
||||||
|
|
||||||
// add snap to edge toggle
|
// add snap to edge toggle
|
||||||
|
@ -1726,7 +1726,22 @@ editor = {
|
||||||
};
|
};
|
||||||
snapToOriginalControl.addTo(editor.map);
|
snapToOriginalControl.addTo(editor.map);
|
||||||
|
|
||||||
|
// add snap to 90° toggle
|
||||||
|
var snapTo90Control = L.control({position: 'topleft'});
|
||||||
|
snapTo90Control.onAdd = function() {
|
||||||
|
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-snap');
|
||||||
|
container.innerHTML = '<a href="#" title="[UNSTABLE] Toggle Snap to 90°" class="snap-to-90-toggle ' +
|
||||||
|
(editor._snap_to_90_enabled ? 'active' : '') + '"></a>';
|
||||||
|
|
||||||
|
L.DomEvent.on(container.querySelector('.snap-to-90-toggle'), 'click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
editor._toggle_snap_to_90();
|
||||||
|
});
|
||||||
|
|
||||||
|
L.DomEvent.disableClickPropagation(container);
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
snapTo90Control.addTo(editor.map);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -1755,6 +1770,16 @@ editor = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_toggle_snap_to_90: function() {
|
||||||
|
editor._snap_to_90_enabled = !editor._snap_to_90_enabled;
|
||||||
|
var toggle = document.querySelector('.snap-to-90-toggle');
|
||||||
|
if (toggle) {
|
||||||
|
toggle.classList.toggle('active', editor._snap_to_90_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor._clear_snap_indicators();
|
||||||
|
},
|
||||||
|
|
||||||
_show_original_geometry: function() {
|
_show_original_geometry: function() {
|
||||||
if (!editor._bounds_layer || editor._original_geometry_layer) return;
|
if (!editor._bounds_layer || editor._original_geometry_layer) return;
|
||||||
|
|
||||||
|
@ -1819,10 +1844,12 @@ editor = {
|
||||||
var mapPoint = editor.map.latLngToContainerPoint(latlng);
|
var mapPoint = editor.map.latLngToContainerPoint(latlng);
|
||||||
var candidates = [];
|
var candidates = [];
|
||||||
|
|
||||||
// check for right-angle snap to current shape vertices
|
// ADD THIS: check for 90° axis snap
|
||||||
var rightAngleSnap = editor._find_right_angle_snap(latlng, mapPoint);
|
if (editor._snap_to_90_enabled) {
|
||||||
if (rightAngleSnap) {
|
var ninetyDegreeSnap = editor._find_90_degree_snap(latlng, mapPoint);
|
||||||
candidates.push(rightAngleSnap);
|
if (ninetyDegreeSnap) {
|
||||||
|
candidates.push(ninetyDegreeSnap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// find snap candidates from existing geometries with area-limited infinite extension
|
// find snap candidates from existing geometries with area-limited infinite extension
|
||||||
|
@ -1943,6 +1970,89 @@ editor = {
|
||||||
return distance <= radius;
|
return distance <= radius;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_find_90_degree_snap: function(targetLatLng, targetMapPoint) {
|
||||||
|
if (!editor._geometries_layer) return null;
|
||||||
|
|
||||||
|
var bestSnap = null;
|
||||||
|
var closestDistance = Infinity;
|
||||||
|
|
||||||
|
// Check all geometry vertices for 90° alignment
|
||||||
|
editor._geometries_layer.eachLayer(function(layer) {
|
||||||
|
if (layer === editor._editing_layer) return; // don't snap to self
|
||||||
|
if (!layer.getLatLngs) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var coordinates = [];
|
||||||
|
if (layer instanceof L.Polygon || layer instanceof L.Polyline) {
|
||||||
|
coordinates = layer.getLatLngs();
|
||||||
|
if (coordinates[0] && Array.isArray(coordinates[0])) {
|
||||||
|
coordinates = coordinates[0];
|
||||||
|
}
|
||||||
|
} else if (layer instanceof L.Circle || layer instanceof L.CircleMarker) {
|
||||||
|
coordinates = [layer.getLatLng()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each vertex for 90° alignment
|
||||||
|
for (var i = 0; i < coordinates.length; i++) {
|
||||||
|
var vertex = coordinates[i];
|
||||||
|
var vertexMapPoint = editor.map.latLngToContainerPoint(vertex);
|
||||||
|
|
||||||
|
// Calculate horizontal and vertical snap points
|
||||||
|
var horizontalSnap = {
|
||||||
|
x: targetMapPoint.x,
|
||||||
|
y: vertexMapPoint.y
|
||||||
|
};
|
||||||
|
var verticalSnap = {
|
||||||
|
x: vertexMapPoint.x,
|
||||||
|
y: targetMapPoint.y
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check horizontal alignment
|
||||||
|
var horizontalDistance = Math.abs(targetMapPoint.y - vertexMapPoint.y);
|
||||||
|
if (horizontalDistance < editor._snap_distance) {
|
||||||
|
var horizontalLatLng = editor.map.containerPointToLatLng(horizontalSnap);
|
||||||
|
var totalDistance = targetMapPoint.distanceTo(horizontalSnap);
|
||||||
|
|
||||||
|
if (totalDistance < closestDistance && totalDistance < editor._snap_distance) {
|
||||||
|
closestDistance = totalDistance;
|
||||||
|
bestSnap = {
|
||||||
|
latlng: horizontalLatLng,
|
||||||
|
distance: totalDistance,
|
||||||
|
snapType: 'horizontal',
|
||||||
|
referenceVertex: vertex,
|
||||||
|
isRightAngle: false,
|
||||||
|
is90Degree: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check vertical alignment
|
||||||
|
var verticalDistance = Math.abs(targetMapPoint.x - vertexMapPoint.x);
|
||||||
|
if (verticalDistance < editor._snap_distance) {
|
||||||
|
var verticalLatLng = editor.map.containerPointToLatLng(verticalSnap);
|
||||||
|
var totalDistance = targetMapPoint.distanceTo(verticalSnap);
|
||||||
|
|
||||||
|
if (totalDistance < closestDistance && totalDistance < editor._snap_distance) {
|
||||||
|
closestDistance = totalDistance;
|
||||||
|
bestSnap = {
|
||||||
|
latlng: verticalLatLng,
|
||||||
|
distance: totalDistance,
|
||||||
|
snapType: 'vertical',
|
||||||
|
referenceVertex: vertex,
|
||||||
|
isRightAngle: false,
|
||||||
|
is90Degree: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Skip problematic layers
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return bestSnap;
|
||||||
|
},
|
||||||
|
|
||||||
_find_closest_point_on_geometry: function(layer, targetLatLng, targetMapPoint, allowInfiniteExtension) {
|
_find_closest_point_on_geometry: function(layer, targetLatLng, targetMapPoint, allowInfiniteExtension) {
|
||||||
if (!layer.getLatLngs) return null;
|
if (!layer.getLatLngs) return null;
|
||||||
|
|
||||||
|
@ -2053,106 +2163,6 @@ editor = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
_find_right_angle_snap: function(targetLatLng, targetMapPoint) {
|
|
||||||
if (!editor._current_editing_shape) return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
var coordinates = [];
|
|
||||||
|
|
||||||
if (editor._current_editing_shape.getLatLngs) {
|
|
||||||
coordinates = editor._current_editing_shape.getLatLngs();
|
|
||||||
if (coordinates[0] && Array.isArray(coordinates[0])) {
|
|
||||||
coordinates = coordinates[0]; // Handle polygon with holes
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (coordinates.length < 2) return null;
|
|
||||||
|
|
||||||
var bestRightAngleSnap = null;
|
|
||||||
var closestDistance = Infinity;
|
|
||||||
|
|
||||||
// Check each vertex for potential right-angle formation
|
|
||||||
for (var i = 0; i < coordinates.length; i++) {
|
|
||||||
var vertex = coordinates[i];
|
|
||||||
var vertexMap = editor.map.latLngToContainerPoint(vertex);
|
|
||||||
|
|
||||||
var distanceToVertex = vertexMap.distanceTo(targetMapPoint);
|
|
||||||
if (distanceToVertex > editor._snap_distance * 2) continue; // Larger radius for right-angle detection
|
|
||||||
|
|
||||||
var prevVertex = coordinates[(i - 1 + coordinates.length) % coordinates.length];
|
|
||||||
var nextVertex = coordinates[(i + 1) % coordinates.length];
|
|
||||||
|
|
||||||
var rightAngleSnap1 = editor._calculate_right_angle_snap(vertex, prevVertex, targetLatLng, targetMapPoint);
|
|
||||||
if (rightAngleSnap1 && rightAngleSnap1.distance < closestDistance) {
|
|
||||||
closestDistance = rightAngleSnap1.distance;
|
|
||||||
bestRightAngleSnap = rightAngleSnap1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rightAngleSnap2 = editor._calculate_right_angle_snap(vertex, nextVertex, targetLatLng, targetMapPoint);
|
|
||||||
if (rightAngleSnap2 && rightAngleSnap2.distance < closestDistance) {
|
|
||||||
closestDistance = rightAngleSnap2.distance;
|
|
||||||
bestRightAngleSnap = rightAngleSnap2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestRightAngleSnap;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_calculate_right_angle_snap: function(vertex, adjacentVertex, targetLatLng, targetMapPoint) {
|
|
||||||
var vertexMap = editor.map.latLngToContainerPoint(vertex);
|
|
||||||
var adjacentMap = editor.map.latLngToContainerPoint(adjacentVertex);
|
|
||||||
|
|
||||||
var edgeDx = adjacentMap.x - vertexMap.x;
|
|
||||||
var edgeDy = adjacentMap.y - vertexMap.y;
|
|
||||||
var edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy);
|
|
||||||
|
|
||||||
if (edgeLength === 0) return null;
|
|
||||||
|
|
||||||
var perpDx = -edgeDy / edgeLength;
|
|
||||||
var perpDy = edgeDx / edgeLength;
|
|
||||||
|
|
||||||
var targetDx = targetMapPoint.x - vertexMap.x;
|
|
||||||
var targetDy = targetMapPoint.y - vertexMap.y;
|
|
||||||
var targetLength = Math.sqrt(targetDx * targetDx + targetDy * targetDy);
|
|
||||||
|
|
||||||
if (targetLength === 0) return null;
|
|
||||||
|
|
||||||
var projectionLength = targetDx * perpDx + targetDy * perpDy;
|
|
||||||
|
|
||||||
var rightAngleMapPoint = {
|
|
||||||
x: vertexMap.x + projectionLength * perpDx,
|
|
||||||
y: vertexMap.y + projectionLength * perpDy
|
|
||||||
};
|
|
||||||
|
|
||||||
var distance = Math.sqrt(
|
|
||||||
Math.pow(rightAngleMapPoint.x - targetMapPoint.x, 2) +
|
|
||||||
Math.pow(rightAngleMapPoint.y - targetMapPoint.y, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (distance < editor._snap_distance && Math.abs(projectionLength) > 10) { // minimum 10 pixels away from vertex
|
|
||||||
var rightAngleLatLng = editor.map.containerPointToLatLng(rightAngleMapPoint);
|
|
||||||
|
|
||||||
return {
|
|
||||||
latlng: rightAngleLatLng,
|
|
||||||
distance: distance,
|
|
||||||
edgeStart: vertex,
|
|
||||||
edgeEnd: rightAngleLatLng,
|
|
||||||
isInfiniteExtension: false,
|
|
||||||
isRightAngle: true,
|
|
||||||
rightAngleVertex: vertex,
|
|
||||||
adjacentVertex: adjacentVertex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
_show_snap_indicator: function(latlng, snapInfo) {
|
_show_snap_indicator: function(latlng, snapInfo) {
|
||||||
editor._clear_snap_indicators();
|
editor._clear_snap_indicators();
|
||||||
|
|
||||||
|
@ -2171,20 +2181,62 @@ editor = {
|
||||||
|
|
||||||
editor._snap_indicator.addLayer(indicator);
|
editor._snap_indicator.addLayer(indicator);
|
||||||
|
|
||||||
if (snapInfo && snapInfo.edgeStart && snapInfo.edgeEnd) {
|
if (snapInfo && snapInfo.is90Degree) {
|
||||||
|
editor._show_90_degree_highlight(snapInfo);
|
||||||
|
} else if (snapInfo && snapInfo.edgeStart && snapInfo.edgeEnd) {
|
||||||
editor._show_edge_highlight(snapInfo);
|
editor._show_edge_highlight(snapInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_show_90_degree_highlight: function(snapInfo) {
|
||||||
|
var referenceVertex = snapInfo.referenceVertex;
|
||||||
|
var snapPoint = snapInfo.latlng;
|
||||||
|
|
||||||
|
// Draw line from reference vertex to snap point
|
||||||
|
var guideLine = L.polyline([referenceVertex, snapPoint], {
|
||||||
|
color: '#00aaff',
|
||||||
|
weight: 2,
|
||||||
|
opacity: 0.8,
|
||||||
|
dashArray: '4, 4',
|
||||||
|
className: '90-degree-guide'
|
||||||
|
});
|
||||||
|
editor._snap_indicator.addLayer(guideLine);
|
||||||
|
|
||||||
|
// Highlight the reference vertex
|
||||||
|
var vertexHighlight = L.circle(referenceVertex, {
|
||||||
|
radius: 0.05,
|
||||||
|
color: '#00aaff',
|
||||||
|
weight: 2,
|
||||||
|
opacity: 0.8,
|
||||||
|
fillOpacity: 0.3,
|
||||||
|
className: '90-degree-vertex'
|
||||||
|
});
|
||||||
|
editor._snap_indicator.addLayer(vertexHighlight);
|
||||||
|
|
||||||
|
// Add axis indicator
|
||||||
|
var referenceMap = editor.map.latLngToContainerPoint(referenceVertex);
|
||||||
|
var snapMap = editor.map.latLngToContainerPoint(snapPoint);
|
||||||
|
|
||||||
|
var axisText = snapInfo.snapType === 'horizontal' ? '─' : '│';
|
||||||
|
var midPoint = editor.map.containerPointToLatLng({
|
||||||
|
x: (referenceMap.x + snapMap.x) / 2,
|
||||||
|
y: (referenceMap.y + snapMap.y) / 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a small text indicator (you might need to style this with CSS)
|
||||||
|
var textMarker = L.marker(midPoint, {
|
||||||
|
icon: L.divIcon({
|
||||||
|
html: '<div style="color: #00aaff; font-weight: bold; font-size: 16px;">' + axisText + '</div>',
|
||||||
|
className: '90-degree-axis-indicator',
|
||||||
|
iconSize: [20, 20],
|
||||||
|
iconAnchor: [10, 10]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
editor._snap_indicator.addLayer(textMarker);
|
||||||
|
},
|
||||||
|
|
||||||
_show_edge_highlight: function(snapInfo) {
|
_show_edge_highlight: function(snapInfo) {
|
||||||
if (!snapInfo.edgeStart || !snapInfo.edgeEnd) return;
|
if (!snapInfo.edgeStart || !snapInfo.edgeEnd) return;
|
||||||
|
|
||||||
// handle right-angle visualization
|
|
||||||
if (snapInfo.isRightAngle) {
|
|
||||||
editor._show_right_angle_highlight(snapInfo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var startPoint = snapInfo.edgeStart;
|
var startPoint = snapInfo.edgeStart;
|
||||||
var endPoint = snapInfo.edgeEnd;
|
var endPoint = snapInfo.edgeEnd;
|
||||||
var extendedStart, extendedEnd;
|
var extendedStart, extendedEnd;
|
||||||
|
@ -2245,63 +2297,6 @@ editor = {
|
||||||
editor._snap_indicator.addLayer(originalEdge);
|
editor._snap_indicator.addLayer(originalEdge);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_show_right_angle_highlight: function(snapInfo) {
|
|
||||||
var vertex = snapInfo.rightAngleVertex;
|
|
||||||
var adjacentVertex = snapInfo.adjacentVertex;
|
|
||||||
var rightAnglePoint = snapInfo.latlng;
|
|
||||||
|
|
||||||
var referenceEdge = L.polyline([vertex, adjacentVertex], {
|
|
||||||
color: '#4488ff',
|
|
||||||
weight: 2,
|
|
||||||
opacity: 0.6,
|
|
||||||
className: 'right-angle-reference'
|
|
||||||
});
|
|
||||||
editor._snap_indicator.addLayer(referenceEdge);
|
|
||||||
|
|
||||||
var rightAngleLine = L.polyline([vertex, rightAnglePoint], {
|
|
||||||
color: '#ff4488',
|
|
||||||
weight: 3,
|
|
||||||
opacity: 0.8,
|
|
||||||
dashArray: '6, 3',
|
|
||||||
className: 'right-angle-line'
|
|
||||||
});
|
|
||||||
editor._snap_indicator.addLayer(rightAngleLine);
|
|
||||||
|
|
||||||
var vertexMap = editor.map.latLngToContainerPoint(vertex);
|
|
||||||
var adjacentMap = editor.map.latLngToContainerPoint(adjacentVertex);
|
|
||||||
var rightAngleMap = editor.map.latLngToContainerPoint(rightAnglePoint);
|
|
||||||
|
|
||||||
var size = 15; // Square size in pixels
|
|
||||||
var dx1 = adjacentMap.x - vertexMap.x;
|
|
||||||
var dy1 = adjacentMap.y - vertexMap.y;
|
|
||||||
var len1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
||||||
|
|
||||||
var dx2 = rightAngleMap.x - vertexMap.x;
|
|
||||||
var dy2 = rightAngleMap.y - vertexMap.y;
|
|
||||||
var len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
||||||
|
|
||||||
if (len1 > 0 && len2 > 0) {
|
|
||||||
dx1 = (dx1 / len1) * size;
|
|
||||||
dy1 = (dy1 / len1) * size;
|
|
||||||
dx2 = (dx2 / len2) * size;
|
|
||||||
dy2 = (dy2 / len2) * size;
|
|
||||||
|
|
||||||
var corner1 = editor.map.containerPointToLatLng({x: vertexMap.x + dx1, y: vertexMap.y + dy1});
|
|
||||||
var corner2 = editor.map.containerPointToLatLng({x: vertexMap.x + dx1 + dx2, y: vertexMap.y + dy1 + dy2});
|
|
||||||
var corner3 = editor.map.containerPointToLatLng({x: vertexMap.x + dx2, y: vertexMap.y + dy2});
|
|
||||||
|
|
||||||
var rightAngleSquare = L.polyline([vertex, corner1, corner2, corner3, vertex], {
|
|
||||||
color: '#ff4488',
|
|
||||||
weight: 2,
|
|
||||||
opacity: 0.7,
|
|
||||||
fill: false,
|
|
||||||
className: 'right-angle-square'
|
|
||||||
});
|
|
||||||
editor._snap_indicator.addLayer(rightAngleSquare);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_clear_snap_indicators: function() {
|
_clear_snap_indicators: function() {
|
||||||
if (editor._snap_indicator) {
|
if (editor._snap_indicator) {
|
||||||
editor._snap_indicator.clearLayers();
|
editor._snap_indicator.clearLayers();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue