toggle to 90°

This commit is contained in:
Dennis Orlando 2025-08-02 10:58:33 +02:00
parent f99fcb8916
commit 2e681dffb4
2 changed files with 175 additions and 173 deletions

View file

@ -571,7 +571,7 @@ label.theme-color-label {
/* 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;
display: block;
width: 30px;
@ -595,6 +595,10 @@ label.theme-color-label {
}
}
a.snap-to-90-toggle {
background-color: yellow !important;
}
/* icons */
a.snap-toggle {
background-image: url("/static/img/snap-to-edges-icon.svg");
@ -602,6 +606,9 @@ label.theme-color-label {
a.snap-to-original-toggle {
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 */

View file

@ -1673,6 +1673,7 @@ editor = {
// Snap-to-edges functionality
_snap_enabled: false,
_snap_to_original_enabled: false,
_snap_to_90_enable: false,
_snap_distance: 30, // pixels
_extension_area_multiplier: 4, // Extension area = snap_distance * this multiplier
_snap_to_base_map: false,
@ -1687,8 +1688,7 @@ editor = {
editor._add_snap_controls();
},
_add_snap_controls: function() {
@ -1726,7 +1726,22 @@ editor = {
};
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() {
if (!editor._bounds_layer || editor._original_geometry_layer) return;
@ -1819,10 +1844,12 @@ editor = {
var mapPoint = editor.map.latLngToContainerPoint(latlng);
var candidates = [];
// check for right-angle snap to current shape vertices
var rightAngleSnap = editor._find_right_angle_snap(latlng, mapPoint);
if (rightAngleSnap) {
candidates.push(rightAngleSnap);
// ADD THIS: check for 90° axis snap
if (editor._snap_to_90_enabled) {
var ninetyDegreeSnap = editor._find_90_degree_snap(latlng, mapPoint);
if (ninetyDegreeSnap) {
candidates.push(ninetyDegreeSnap);
}
}
// find snap candidates from existing geometries with area-limited infinite extension
@ -1943,6 +1970,89 @@ editor = {
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) {
if (!layer.getLatLngs) return null;
@ -2052,107 +2162,7 @@ editor = {
t: originalT
};
},
_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) {
editor._clear_snap_indicators();
@ -2171,20 +2181,62 @@ editor = {
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);
}
},
_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) {
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 endPoint = snapInfo.edgeEnd;
var extendedStart, extendedEnd;
@ -2245,63 +2297,6 @@ editor = {
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() {
if (editor._snap_indicator) {
editor._snap_indicator.clearLayers();