| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
- /** 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
- }
|