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 */
|
||||
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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue