/** Rendering-only estimates. Exact same-industry endpoints are a schematic adjacency, not surveyed topology. */ const INDUSTRIES = new Set(['GAS', 'WATER_SUPPLY', 'DRAINAGE']) const positive = (value) => Number.isFinite(value) && value > 0 function pathLength(positions) { let length = 0 const rad = Math.PI / 180 for (let i = 1; i < positions.length; i += 1) { const [lon1,lat1] = positions[i-1], [lon2,lat2] = positions[i] const h = Math.sin((lat2-lat1)*rad/2)**2 + Math.cos(lat1*rad)*Math.cos(lat2*rad)*Math.sin((lon2-lon1)*rad/2)**2 length += 12742017.6 * Math.asin(Math.sqrt(Math.min(1,h))) } return length } /** Preserve reported values; fill only missing chains bounded by TWO distinct known pipes. */ export function resolvePipeDiameters(pipes) { const resolved = new Map() const nodes = new Map(), edges = [] for (const pipe of pipes) { if (positive(pipe.diameter)) resolved.set(pipe, { diameterMm:pipe.diameter, source:'reported', anchors:[] }) if (!INDUSTRIES.has(pipe.industry) || !pipe.positions?.length) continue const key = (p) => JSON.stringify([pipe.industry,p[0],p[1]]) const ends = [key(pipe.positions[0]),key(pipe.positions.at(-1))] const length = pathLength(pipe.positions) const edge = { pipe, ends, length } if (ends[0] !== ends[1] && positive(length)) edges.push(edge) // Closed/degenerate lines still occupy their endpoints and must block ambiguous propagation. for (const end of ends) { if (!nodes.has(end)) nodes.set(end,[]) nodes.get(end).push(edge) } } const visited = new Set() for (const seed of edges) { if (positive(seed.pipe.diameter) || visited.has(seed)) continue const seen = new Set([seed]) const walk = (node) => { let current = seed const missing = [] while (nodes.get(node)?.length === 2) { const next = nodes.get(node).find(edge => edge !== current) if (positive(next.pipe.diameter)) return { missing, anchor:next } if (seen.has(next)) break seen.add(next) missing.push(next) node = next.ends[0] === node ? next.ends[1] : next.ends[0] current = next } return { missing, anchor:null } } const left = walk(seed.ends[0]), right = walk(seed.ends[1]) const chain = [...left.missing.reverse(),seed,...right.missing] for (const edge of chain) visited.add(edge) if (!left.anchor || !right.anchor || left.anchor === right.anchor) continue const total = chain.reduce((sum,edge) => sum+edge.length,0) const a = left.anchor.pipe, b = right.anchor.pipe const anchors = [a,b].map(pipe => ({code:pipe.code,diameterMm:pipe.diameter})).sort((x,y)=>x.code.localeCompare(y.code)) let distance = 0 for (const edge of chain) { const fraction = (distance+edge.length/2)/total resolved.set(edge.pipe, { diameterMm:a.diameter+(b.diameter-a.diameter)*fraction, source:'interpolated', anchors }) distance += edge.length } } return resolved }