facilitySelection.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. /** Facility selection uses entity category+primary key, never monitoring point codes. */
  2. import { facilityStatusTone, entityIdentity } from './entityDetail.js'
  3. import { COORDINATE_REASON_TEXT, NO_MAP_LOCATION_TEXT } from './coordinate.js'
  4. import { filterFacilityPoints, FACILITY_DETAIL_CATEGORIES } from './facilityPoints.js'
  5. export function facilityIdentity(input) {
  6. const identity = entityIdentity(input, 'facility')
  7. return { ...identity, valid: identity.valid && identity.kind === 'facility' && FACILITY_DETAIL_CATEGORIES.includes(identity.category) }
  8. }
  9. const coordinatesOf = value => value && Number.isFinite(value.longitude) && Number.isFinite(value.latitude)
  10. ? [value.longitude, value.latitude] : null
  11. /** Resolve only current identity evidence; pipeline location is the along-line geometric midpoint of its validated polyline — never an endpoint, never invented. */
  12. /**
  13. * 管线定位点 = 沿真实折线的**长度中点**(维护者 2026-10-30 15:57:不许再取起点/终点)。
  14. * 每段按大圆近似计长(与 pipeDiameter 同一公式),半程落在哪一段就在段内线性插值;
  15. * 顶点不足或全长为零(重合点)时回那个真实顶点 —— 仍是几何证据,不是编造。
  16. */
  17. function polylineMidpoint(positions) {
  18. const points = (Array.isArray(positions) ? positions : [])
  19. .filter((point) => Array.isArray(point) && Number.isFinite(point[0]) && Number.isFinite(point[1]))
  20. if (!points.length) return null
  21. if (points.length === 1) return points[0]
  22. const rad = Math.PI / 180
  23. const segmentLength = (a, b) => {
  24. const h = Math.sin((b[1] - a[1]) * rad / 2) ** 2
  25. + Math.cos(a[1] * rad) * Math.cos(b[1] * rad) * Math.sin((b[0] - a[0]) * rad / 2) ** 2
  26. return 12742017.6 * Math.asin(Math.sqrt(Math.min(1, h)))
  27. }
  28. const lengths = []
  29. let total = 0
  30. for (let i = 1; i < points.length; i += 1) {
  31. const length = segmentLength(points[i - 1], points[i])
  32. lengths.push(length)
  33. total += length
  34. }
  35. if (!(total > 0)) return points[0]
  36. let walked = 0
  37. for (let i = 0; i < lengths.length; i += 1) {
  38. const length = lengths[i]
  39. if (walked + length >= total / 2) {
  40. const t = length > 0 ? (total / 2 - walked) / length : 0
  41. return [
  42. points[i][0] + (points[i + 1][0] - points[i][0]) * t,
  43. points[i][1] + (points[i + 1][1] - points[i][1]) * t,
  44. ]
  45. }
  46. walked += length
  47. }
  48. return points[points.length - 1]
  49. }
  50. export function resolveFacilityPoint({ identity: input, points, detail, network } = {}) {
  51. const identity = facilityIdentity(input)
  52. if (!identity.valid) return null
  53. const row = (points || []).find(item => item.id === identity.id && item.type === identity.category)
  54. const current = detail && detail.kind === 'facility' && detail.id === identity.id && detail.category === identity.category ? detail : null
  55. const raw = current?.raw || {}
  56. let source = row || {}
  57. let position = null
  58. let placement = 'pending'
  59. let reasonText = ''
  60. let locationHint = ''
  61. if (identity.category === 'PIPE_NETWORK') {
  62. const matches = (network?.pipes || []).filter(pipe => String(pipe.sourceId ?? '') === identity.id || String(pipe.networkId ?? '') === identity.id)
  63. const byCode = matches.length ? matches : (network?.pipes || []).filter(pipe => raw.code && pipe.code === raw.code)
  64. const pipe = byCode.length === 1 ? byCode[0] : null
  65. if (pipe) {
  66. source = pipe
  67. position = polylineMidpoint(pipe.positions)
  68. placement = position ? 'drawn' : 'no-location'
  69. locationHint = position ? '定位到管段几何中点(沿管线长度取中点,非起止点)' : ''
  70. } else if (network) {
  71. const blocked = (network.blocked || []).find(item => raw.code && item.code === raw.code)
  72. placement = blocked ? 'no-location' : 'pending'
  73. reasonText = blocked?.reasonText || '当前地图尚未取到该管段的可定位几何'
  74. }
  75. } else if (current && Object.prototype.hasOwnProperty.call(raw, 'coordinates')) {
  76. position = coordinatesOf(current.coordinates)
  77. placement = position ? 'drawn' : 'no-location'
  78. reasonText = position ? '' : '设施档案未登记坐标'
  79. // 工程设施不是旧四类设施点契约;新定位必须有后端明确的可渲染结论。
  80. if (position && identity.category === 'ENGINEERING_FACILITY' && raw.coordinateValidation?.renderable !== true) {
  81. position = null
  82. placement = raw.coordinateValidation?.renderable === false ? 'no-location' : 'pending'
  83. reasonText = placement === 'pending' ? '设施坐标尚未获得地图可渲染校验' : (COORDINATE_REASON_TEXT[raw.coordinateValidation.reason] || NO_MAP_LOCATION_TEXT)
  84. }
  85. } else if (row) {
  86. position = coordinatesOf(row)
  87. placement = row.placement
  88. reasonText = row.reasonText || ''
  89. }
  90. const status = raw.status ?? row?.status ?? ''
  91. return {
  92. key: 'facility-point|' + identity.category + '|' + identity.id,
  93. id: identity.id, type: identity.category, detailCategory: identity.category,
  94. code: String(raw.code || source.code || identity.id),
  95. name: current?.name || source.name || String(raw.name || identity.id),
  96. status: String(status), tone: facilityStatusTone(status),
  97. industry: source.industry || raw.industry || '',
  98. district: source.district ?? raw.district ?? null,
  99. placement, longitude: position?.[0] ?? null, latitude: position?.[1] ?? null,
  100. reasonText, locationHint,
  101. }
  102. }
  103. /** Selected ledger-only entities get one temporary marker, under the SAME filters; global legend counts stay unchanged. */
  104. export function mergeFacilitySelectionPoints(base, selected, filters = {}) {
  105. if (!selected || selected.placement !== 'drawn') return base
  106. if (!filterFacilityPoints([selected], filters.types || [], filters).length) return base
  107. const list = Array.isArray(base) ? base : []
  108. const index = list.findIndex(row => row.key === selected.key)
  109. if (index < 0) return [...list, selected]
  110. return list.map((row, i) => i === index ? selected : row)
  111. }