diff --git a/src/c3nav/editor/static/editor/js/editor.js b/src/c3nav/editor/static/editor/js/editor.js index a51d2c30..4533f057 100644 --- a/src/c3nav/editor/static/editor/js/editor.js +++ b/src/c3nav/editor/static/editor/js/editor.js @@ -1870,20 +1870,16 @@ editor = { // check if layer is within the area limit for infinite extension var allowInfiniteExtension = editor._is_layer_in_extension_area(layer, latlng, mapPoint); - var snapPoint = editor._find_closest_point_on_geometry(layer, latlng, mapPoint, allowInfiniteExtension); - if (snapPoint && snapPoint.distance < editor._snap_distance) { - candidates.push(snapPoint); - } + var snapPoints = editor._find_closest_point_on_geometry(layer, latlng, mapPoint, allowInfiniteExtension); + candidates.push(...snapPoints); }); // check current editing shape with infinite extension enabled if (editor._current_editing_shape) { - var currentShapeSnap = editor._find_closest_point_on_geometry( + var currentShapeSnapPoints = editor._find_closest_point_on_geometry( editor._current_editing_shape, latlng, mapPoint, true // Always enable infinite extension for current shape ); - if (currentShapeSnap && currentShapeSnap.distance < editor._snap_distance) { - candidates.push(currentShapeSnap); - } + candidates.push(...currentShapeSnapPoints); } // find closest candidate @@ -1891,6 +1887,30 @@ editor = { candidates.sort(function(a, b) { return a.distance - b.distance; }); var best = candidates[0]; + // see if we can snap to a corner, i.e. an edge intersection + if (candidates.length >= 2 && candidates[0].isLine && candidates[1].isLine) { + console.log(candidates.slice(0,2)) + var inters = editor._intersect_infinite( + [candidates[0].edgeStart, candidates[0].edgeEnd], + [candidates[1].edgeStart, candidates[1].edgeEnd] + ) + if (inters) { + intersMap = editor.map.latLngToContainerPoint(inters) + var distance = Math.sqrt( + Math.pow(intersMap.x - mapPoint.x, 2) + + Math.pow(intersMap.y - mapPoint.y, 2) + ) + if (distance < editor._snap_distance) { + best = { + latlng: inters, + distance: distance, + referenceVertex: inters, + } + } + } + } + + // show snap indicator with edge highlighting editor._show_snap_indicator(best.latlng, best); @@ -1901,6 +1921,24 @@ editor = { } }, + _intersect_infinite: function(line1, line2) { + const [p1, p2] = line1; + const [p3, p4] = line2; + + const x1 = p1.lng, y1 = p1.lat; + const x2 = p2.lng, y2 = p2.lat; + const x3 = p3.lng, y3 = p3.lat; + const x4 = p4.lng, y4 = p4.lat; + + const denom = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4); + if (denom === 0) return null; // parallel + + const px = ((x1*y2 - y1*x2)*(x3-x4) - (x1-x2)*(x3*y4 - y3*x4)) / denom; + const py = ((x1*y2 - y1*x2)*(y3-y4) - (y1-y2)*(x3*y4 - y3*x4)) / denom; + + return {lng: px, lat: py}; + }, + _is_layer_in_extension_area: function(layer, targetLatLng, targetMapPoint) { if (!layer.getLatLngs) return false; @@ -2064,10 +2102,9 @@ editor = { }, _find_closest_point_on_geometry: function(layer, targetLatLng, targetMapPoint, allowInfiniteExtension) { - if (!layer.getLatLngs) return null; + if (!layer.getLatLngs) return []; - var closestPoint = null; - var closestDistance = Infinity; + var closestPoints = []; try { var coordinates = []; @@ -2083,16 +2120,16 @@ editor = { var centerMapPoint = editor.map.latLngToContainerPoint(center); var distance = centerMapPoint.distanceTo(targetMapPoint); if (distance < editor._snap_distance) { - return { + return [{ latlng: center, distance: distance, edgeStart: center, edgeEnd: center, isInfiniteExtension: false, isRightAngle: false - }; + }]; } - return null; + return []; } // check each edge of the geometry @@ -2102,17 +2139,16 @@ editor = { var p2 = coordinates[(i + 1) % coordinates.length]; var snapPoint = editor._find_closest_point_on_edge(p1, p2, targetLatLng, targetMapPoint, allowInfiniteExtension); - if (snapPoint && snapPoint.distance < closestDistance) { - closestDistance = snapPoint.distance; - closestPoint = snapPoint; + if (snapPoint && snapPoint.distance < editor._snap_distance) { + closestPoints.push(snapPoint); } } } catch (error) { - return null; + return []; } - return closestPoint; + return closestPoints; }, _find_closest_point_on_edge: function(p1, p2, targetLatLng, targetMapPoint, allowInfiniteExtension) { @@ -2132,6 +2168,7 @@ editor = { distance: distance, edgeStart: p1, edgeEnd: p2, + isLine: true, isInfiniteExtension: false, isRightAngle: false }; @@ -2167,6 +2204,7 @@ editor = { distance: distance, edgeStart: p1, edgeEnd: p2, + isLine: true, isInfiniteExtension: isInfiniteExtension, isRightAngle: false, t: originalT @@ -2195,9 +2233,26 @@ editor = { editor._show_90_degree_highlight(snapInfo); } else if (snapInfo && snapInfo.edgeStart && snapInfo.edgeEnd) { editor._show_edge_highlight(snapInfo); + } else if (snapInfo && snapInfo.referenceVertex) { + editor._show_intersect_highlight(snapInfo); } }, + _show_intersect_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); + }, + _show_90_degree_highlight: function(snapInfo) { var referenceVertex = snapInfo.referenceVertex; var snapPoint = snapInfo.latlng;