diff --git a/index.html b/index.html index 5174822..7c56755 100644 --- a/index.html +++ b/index.html @@ -1888,84 +1888,80 @@ } }); - // Helper function to flatten coordinates - function getFlattenedCoords(rawRoutes) { - let latLngs = []; - const flattenLL = (raw) => { - if (!raw) return; - if (typeof raw === 'object' && raw.lat !== undefined && !isNaN(raw.lat)) { - latLngs.push(L.latLng(raw)); - } else if (Array.isArray(raw) && raw.length === 2 && typeof raw[0] === 'number') { - latLngs.push(L.latLng(raw)); - } else if (Array.isArray(raw)) { - raw.forEach(item => flattenLL(item)); + function getFlattenedCoords(raw) { + if (!raw) return []; + // If it's a flat array of LatLngs + if (raw.length > 0 && (raw[0].lat !== undefined || (Array.isArray(raw[0]) && typeof raw[0][0] === 'number'))) { + return raw.map(ll => L.latLng(ll)); + } + // If it's nested + const flattened = []; + raw.forEach(section => { + if (Array.isArray(section)) { + section.forEach(ll => flattened.push(L.latLng(ll))); } - }; - flattenLL(rawRoutes); - return latLngs.map(ll => { - try { return L.latLng(ll); } catch (e) { return null; } - }).filter(ll => ll && !isNaN(ll.lat) && !isNaN(ll.lng)); + }); + return flattened; + } + + function getNestedCoords(raw) { + if (!raw) return []; + // Check if it's already nested + if (raw.length > 0 && Array.isArray(raw[0]) && raw[0].length > 0 && (raw[0][0].lat !== undefined || Array.isArray(raw[0][0]))) { + return raw.map(section => section.map(ll => L.latLng(ll))); + } + // If it's flat, wrap it + if (raw.length > 0) return [raw.map(ll => L.latLng(ll))]; + return []; } function renderSegmentLabels(variant, coordsInput = null, tempPt = null) { const vLabel = labelLayers[variant.id]; if (!vLabel) return; + if (!variant.visible) { vLabel.clearLayers(); return; } - // Use passed coordinates or fall back to state - let latLngs = coordsInput ? [...coordsInput] : getFlattenedCoords(variant.routes); - if (tempPt) latLngs.push(L.latLng(tempPt)); - - if (!variant.visible || latLngs.length < 2) { - vLabel.clearLayers(); - return; - } - + const nested = coordsInput ? [coordsInput] : getNestedCoords(variant.routes); const currentMarkers = vLabel.getLayers(); let markerIdx = 0; - for (let i = 0; i < latLngs.length - 1; i++) { - const p1 = latLngs[i]; - const p2 = latLngs[i + 1]; - const dist = map.distance(p1, p2); - const mid = L.latLng((p1.lat + p2.lat) / 2, (p1.lng + p2.lng) / 2); - const labelText = `${dist.toFixed(0)}m`; + nested.forEach((latLngs, sectionIdx) => { + const drawLatLngs = [...latLngs]; + // Only add temp point to the very LAST section if we are actively drawing + if (tempPt && sectionIdx === nested.length - 1) drawLatLngs.push(L.latLng(tempPt)); - if (markerIdx < currentMarkers.length) { - const m = currentMarkers[markerIdx]; - m.setLatLng(mid); - const el = m.getElement(); - let contentSpan = null; - if (el) { - contentSpan = el.querySelector('.segment-label-content'); - } + if (drawLatLngs.length < 2) return; - if (contentSpan) { - contentSpan.textContent = labelText; + for (let i = 0; i < drawLatLngs.length - 1; i++) { + const p1 = drawLatLngs[i]; + const p2 = drawLatLngs[i + 1]; + const dist = map.distance(p1, p2); + const mid = L.latLng((p1.lat + p2.lat) / 2, (p1.lng + p2.lng) / 2); + const labelText = `${dist.toFixed(0)}m`; + + if (markerIdx < currentMarkers.length) { + const m = currentMarkers[markerIdx]; + m.setLatLng(mid); + const el = m.getElement(); + if (el) { + const span = el.querySelector('.segment-label-content'); + if (span) span.textContent = labelText; + } } else { - // Backup if element not yet rendered or span missing - m.setIcon(L.divIcon({ - className: 'segment-label', - html: `${labelText}`, - iconSize: [46, 20], - iconAnchor: [23, 10] - })); + L.marker(mid, { + interactive: false, + pane: 'labelPane', + icon: L.divIcon({ + className: 'segment-label', + html: `${labelText}`, + iconSize: [46, 20], + iconAnchor: [23, 10] + }) + }).addTo(vLabel); } - } else { - L.marker(mid, { - interactive: false, - pane: 'labelPane', // Explicitly set pane - icon: L.divIcon({ - className: 'segment-label', - html: `${labelText}`, - iconSize: [46, 20], - iconAnchor: [23, 10] - }) - }).addTo(vLabel); + markerIdx++; } - markerIdx++; - } + }); - // Remove excess markers while (markerIdx < currentMarkers.length) { vLabel.removeLayer(currentMarkers[markerIdx++]); } @@ -1979,26 +1975,18 @@ const vDrill = drillingLayers[variant.id]; if (vDrill) vDrill.clearLayers(); - const latLngs = getFlattenedCoords(variant.routes); - if (latLngs.length < 2) { - variant.stats.total = 0; - variant.stats.drilling = 0; - variant.stats.muffen = 0; - return; - } + const nested = getNestedCoords(variant.routes); + variant.stats.total = 0; + variant.stats.drilling = 0; + variant.stats.muffen = 0; + variant.stats.hasTooLongDrilling = false; + variant.drillingSegments = []; + + if (nested.length === 0) return; - // Sync labels for final state renderSegmentLabels(variant); - - const line = turf.lineString(latLngs.map(ll => [ll.lng, ll.lat])); - variant.stats.total = turf.length(line, { units: 'meters' }); - const startPoint = turf.point([latLngs[0].lng, latLngs[0].lat]); - - // --- Bohrungs-Logik (Optimized Version) --- - const drillingRanges = []; - - // Only re-filter if cache is empty or data changed + // Pre-filter obstacles once if (!cachedObstacles && state.usage.features) { const keywords = ['bahn', 'gewässer', 'wasser', 'straße', 'verkehr', 'gehölz', 'baufläche', 'wald', 'forst', 'hecke', 'weg', 'pfad', 'graben', 'bach', 'fluss']; cachedObstacles = state.usage.features.filter(f => { @@ -2006,132 +1994,114 @@ return keywords.some(k => type.includes(k)); }); } - const currentObstacles = cachedObstacles || []; - if (currentObstacles.length > 0) { - const lineBbox = turf.bbox(line); + nested.forEach(latLngs => { + if (latLngs.length < 2) return; - currentObstacles.forEach(obs => { - try { - const obsBbox = turf.bbox(obs); - if (lineBbox[0] > obsBbox[2] || lineBbox[2] < obsBbox[0] || - lineBbox[1] > obsBbox[3] || lineBbox[3] < obsBbox[1]) return; + const line = turf.lineString(latLngs.map(ll => [ll.lng, ll.lat])); + const sectionLength = turf.length(line, { units: 'meters' }); + variant.stats.total += sectionLength; + + const startPoint = turf.point([latLngs[0].lng, latLngs[0].lat]); + const drillingRanges = []; - if (turf.booleanIntersects(line, obs)) { - // Robust Intersection Strategy: - // 1. Find all intersection points - const intersect = turf.lineIntersect(line, obs); - let distances = [0, variant.stats.total]; - - intersect.features.forEach(f => { - const d = turf.length(turf.lineSlice(startPoint, f, line), { units: 'meters' }); - distances.push(d); - }); - - // 2. Sort unique distances to create segments - distances = [...new Set(distances)].sort((a, b) => a - b); - - // 3. Test the midpoint of each segment - for (let i = 0; i < distances.length - 1; i++) { - const dStart = distances[i]; - const dEnd = distances[i + 1]; - const midDist = (dStart + dEnd) / 2; - - const midPt = turf.along(line, midDist / 1000, { units: 'kilometers' }); - - if (turf.booleanPointInPolygon(midPt, obs)) { - drillingRanges.push([ - Math.max(0, dStart - 20), - Math.min(variant.stats.total, dEnd + 20) - ]); + if (currentObstacles.length > 0) { + const lineBbox = turf.bbox(line); + currentObstacles.forEach(obs => { + try { + const obsBbox = turf.bbox(obs); + if (lineBbox[0] > obsBbox[2] || lineBbox[2] < obsBbox[0] || lineBbox[1] > obsBbox[3] || lineBbox[3] < obsBbox[1]) return; + if (turf.booleanIntersects(line, obs)) { + const intersect = turf.lineIntersect(line, obs); + let distances = [0, sectionLength]; + intersect.features.forEach(f => { + distances.push(turf.length(turf.lineSlice(startPoint, f, line), { units: 'meters' })); + }); + distances = [...new Set(distances)].sort((a, b) => a - b); + for (let i = 0; i < distances.length - 1; i++) { + const dStart = distances[i]; + const dEnd = distances[i + 1]; + const midPt = turf.along(line, (dStart + dEnd) / 2 / 1000, { units: 'kilometers' }); + if (turf.booleanPointInPolygon(midPt, obs)) { + drillingRanges.push([Math.max(0, dStart - 20), Math.min(sectionLength, dEnd + 20)]); + } } } - } - } catch (e) { /* ignore error */ } - }); - } - - // Merge - let mergedRanges = []; - if (drillingRanges.length > 0) { - drillingRanges.sort((a, b) => a[0] - b[0]); - let cur = drillingRanges[0]; - for (let i = 1; i < drillingRanges.length; i++) { - if (drillingRanges[i][0] <= cur[1]) cur[1] = Math.max(cur[1], drillingRanges[i][1]); - else { mergedRanges.push(cur); cur = drillingRanges[i]; } - } - mergedRanges.push(cur); - } - - variant.stats.drilling = mergedRanges.reduce((sum, r) => sum + (r[1] - r[0]), 0); - variant.stats.open = Math.max(0, variant.stats.total - variant.stats.drilling); - variant.stats.muffen = mergedRanges.length * 2; - variant.stats.hasTooLongDrilling = false; - - // Reconstruct geometry - variant.drillingSegments = mergedRanges.map(range => { - try { - const lengthM = range[1] - range[0]; - if (lengthM > 180) variant.stats.hasTooLongDrilling = true; - - const s = turf.along(line, range[0] / 1000, { units: 'kilometers' }); - const e = turf.along(line, range[1] / 1000, { units: 'kilometers' }); - const sCoord = [s.geometry.coordinates[1], s.geometry.coordinates[0]]; - const eCoord = [e.geometry.coordinates[1], e.geometry.coordinates[0]]; - return { - path: [sCoord, eCoord], - muffen: [sCoord, eCoord], - length: lengthM - }; - } catch (e) { return null; } - }).filter(s => s !== null); - - if (variant.visible) { - // Drilling & Muffen - if (vDrill) { - const drillingOpacity = variant.active ? 1 : 0.3; - variant.drillingSegments.forEach(seg => { - L.polyline(seg.path, { - color: '#000000', - weight: 8, - opacity: drillingOpacity, - dashArray: '5, 10', - interactive: false, - pane: 'drillingPane' - }).addTo(vDrill); - - // Individual drilling label (number only) - const midLat = (seg.path[0][0] + seg.path[1][0]) / 2; - const midLng = (seg.path[0][1] + seg.path[1][1]) / 2; - - L.marker([midLat, midLng], { - interactive: false, - pane: 'labelPane', - icon: L.divIcon({ - className: 'drilling-segment-label', - html: `