@caoguo/maplibre-pipeline 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3656CGXE.js → chunk-33TITD2I.js} +31 -3
- package/dist/chunk-33TITD2I.js.map +1 -0
- package/dist/health/index.cjs +30 -0
- package/dist/health/index.cjs.map +1 -1
- package/dist/health/index.d.cts +29 -1
- package/dist/health/index.d.ts +29 -1
- package/dist/health/index.js +1 -1
- package/dist/index.cjs +30 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3656CGXE.js.map +0 -1
- package/dist/chunk-BWQKY35F.js +0 -259
- package/dist/chunk-BWQKY35F.js.map +0 -1
- package/dist/chunk-GCDA7KJM.js +0 -289
- package/dist/chunk-GCDA7KJM.js.map +0 -1
- package/dist/chunk-HCT6NSNS.js +0 -532
- package/dist/chunk-HCT6NSNS.js.map +0 -1
- package/dist/chunk-QVY4WJLM.js +0 -256
- package/dist/chunk-QVY4WJLM.js.map +0 -1
- package/dist/chunk-YBLESEN4.js +0 -133
- package/dist/chunk-YBLESEN4.js.map +0 -1
- package/dist/chunk-YS2ICFVK.js +0 -101
- package/dist/chunk-YS2ICFVK.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -5,6 +5,6 @@ export { PipelineTopology, PipelineTopologyOptions } from './topology/index.cjs'
|
|
|
5
5
|
export { B as BurstResult, a as BurstSimulateOptions, b as BurstSimulateResult, c as BurstSimulator, d as BurstSimulatorOptions, I as ImpactArea, V as ValvePlan, e as convexHull, s as simulateBurst, u as userSeverity } from './burstClass-CGMjcAYy.cjs';
|
|
6
6
|
export { CandidateValveSet, MapLayerData, PlannerOptions, evaluateValvePlan, impactAreaSquareMeters, impactCenter, listCandidateValves, nearestNodeOnPipe, toMapLayerData } from './burst/index.cjs';
|
|
7
7
|
export { ConcentrationContour, DemGrid, DispersionCoefficients, FloodParams, FloodResult, GasLeakParams, GasLeakResult, LeakagePlume, LeakagePlumeOptions, PasquillGiffordParams, StabilityClass, classifyFromMeteo, classifyStability, dispersionCoefficients, gaussianPlume, simulateFlood } from './leakage/index.cjs';
|
|
8
|
-
export { DEFAULT_WEIGHTS, HealthResult, HeatmapCell, PipeHealthInput, PipeHealthPoint, PipeHealthScore, PipelineHealth, PipelineHealthOptions, RiskHeatmapOptions, aggregateHeatmap, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './health/index.cjs';
|
|
8
|
+
export { DEFAULT_WEIGHTS, HealthCard, HealthResult, HeatmapCell, PipeHealthInput, PipeHealthPoint, PipeHealthScore, PipelineHealth, PipelineHealthOptions, RiskHeatmapOptions, aggregateHeatmap, buildHealthCard, buildHealthScoreSeries, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './health/index.cjs';
|
|
9
9
|
export { PipelineNlp, PipelineNlpFilters, PipelineNlpIntent, PipelineNlpOptions, PipelineNlpResult, parsePipelineQuery } from './nlpg/index.cjs';
|
|
10
10
|
import '@caoguo/maplibre';
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export { PipelineTopology, PipelineTopologyOptions } from './topology/index.js';
|
|
|
5
5
|
export { B as BurstResult, a as BurstSimulateOptions, b as BurstSimulateResult, c as BurstSimulator, d as BurstSimulatorOptions, I as ImpactArea, V as ValvePlan, e as convexHull, s as simulateBurst, u as userSeverity } from './burstClass-Cf2p71LE.js';
|
|
6
6
|
export { CandidateValveSet, MapLayerData, PlannerOptions, evaluateValvePlan, impactAreaSquareMeters, impactCenter, listCandidateValves, nearestNodeOnPipe, toMapLayerData } from './burst/index.js';
|
|
7
7
|
export { ConcentrationContour, DemGrid, DispersionCoefficients, FloodParams, FloodResult, GasLeakParams, GasLeakResult, LeakagePlume, LeakagePlumeOptions, PasquillGiffordParams, StabilityClass, classifyFromMeteo, classifyStability, dispersionCoefficients, gaussianPlume, simulateFlood } from './leakage/index.js';
|
|
8
|
-
export { DEFAULT_WEIGHTS, HealthResult, HeatmapCell, PipeHealthInput, PipeHealthPoint, PipeHealthScore, PipelineHealth, PipelineHealthOptions, RiskHeatmapOptions, aggregateHeatmap, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './health/index.js';
|
|
8
|
+
export { DEFAULT_WEIGHTS, HealthCard, HealthResult, HeatmapCell, PipeHealthInput, PipeHealthPoint, PipeHealthScore, PipelineHealth, PipelineHealthOptions, RiskHeatmapOptions, aggregateHeatmap, buildHealthCard, buildHealthScoreSeries, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './health/index.js';
|
|
9
9
|
export { PipelineNlp, PipelineNlpFilters, PipelineNlpIntent, PipelineNlpOptions, PipelineNlpResult, parsePipelineQuery } from './nlpg/index.js';
|
|
10
10
|
import '@caoguo/maplibre';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { detectCycles, dfs } from './chunk-CDEYJ67B.js';
|
|
|
4
4
|
export { BurstSimulator, convexHull, evaluateValvePlan, impactAreaSquareMeters, impactCenter, listCandidateValves, nearestNodeOnPipe, simulateBurst, toMapLayerData, userSeverity } from './chunk-2O4IXAXO.js';
|
|
5
5
|
export { bfs, buildAdjacency, dijkstra, edgesOf, findConnectedComponents, findPaths, findUpstreamNode, haversine, highlightConnectivity, multiSourceBfs, neighborIds, pipeLengthFromGeometry, reconstructPath } from './chunk-XCP7XVNO.js';
|
|
6
6
|
export { LeakagePlume, classifyFromMeteo, classifyStability, dispersionCoefficients, gaussianPlume, simulateFlood } from './chunk-VSTG2ZUY.js';
|
|
7
|
-
export { DEFAULT_WEIGHTS, PipelineHealth, aggregateHeatmap, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './chunk-
|
|
7
|
+
export { DEFAULT_WEIGHTS, PipelineHealth, aggregateHeatmap, buildHealthCard, buildHealthScoreSeries, heatmapToGeoJSON, prioritizeMaintenance, scorePipeHealth, scorePipes } from './chunk-33TITD2I.js';
|
|
8
8
|
export { PipelineTopology } from './chunk-ASITLG37.js';
|
|
9
9
|
export { HEALTH_LEVEL_COLORS, HEALTH_LEVEL_LABELS, INDUSTRY_PRIMARY, NODE_KIND_COLORS, NODE_KIND_ICONS, PIPELINE_TYPE_COLORS, PIPELINE_TYPE_ICONS, PIPELINE_TYPE_LABELS, PIPE_MATERIAL_COLORS, PIPE_STATUS_COLORS, PIPE_STATUS_META, healthLevel, paintNodeByKind, paintPipeBy, paintPipeByDiameter, paintPipeByHealth, paintPipeByMaterial, paintPipeByStatus, paintPipeByType, paintPipeWidthByDiameter, pipelineTypeColor } from './chunk-4HP7Q7OL.js';
|
|
10
10
|
export { PipelineNlp, parsePipelineQuery } from './chunk-7LPD52KL.js';
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/health/healthScorer.ts","../src/health/riskHeatmap.ts","../src/health/PipelineHealth.ts"],"names":[],"mappings":"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","file":"chunk-3656CGXE.js","sourcesContent":["/**\n * 管线健康度评分(healthScorer)\n *\n * 6 维加权评分(PRD §4.4.2):\n * health_score = age (0.25) + material (0.20) + soil (0.15) +\n * history (0.20) + pressure (0.10) + protection (0.10)\n *\n * 每维 0-100,最终加权求和。\n * 评分模型可解释:每个维度的评分依据清晰可见。\n */\n\nimport type { PipeMaterial, PipeStatus } from '../types';\n\nexport interface PipeHealthInput {\n /** 安装日期 ISO */\n installDate?: string;\n /** 材质 */\n material?: PipeMaterial;\n /** 当前故障次数 */\n failureCount?: number;\n /** 当前压力(MPa) */\n pressure?: number;\n /** 最高允许压力(MPa) */\n ratedPressure?: number;\n /** 是否阴极保护 */\n hasCathodicProtection?: boolean;\n /** 土壤腐蚀指数 0-1(无数据时 0.5) */\n soilCorrosion?: number;\n /** 状态(damaged / under_repair 直接影响评分) */\n status?: PipeStatus;\n}\n\nexport interface PipeHealthScore {\n /** 总分 0-100 */\n score: number;\n /** 等级 */\n level: 'excellent' | 'good' | 'fair' | 'poor' | 'critical';\n /** 6 维细分 */\n dimensions: {\n age: { score: number; weight: number; reason: string };\n material: { score: number; weight: number; reason: string };\n soil: { score: number; weight: number; reason: string };\n history: { score: number; weight: number; reason: string };\n pressure: { score: number; weight: number; reason: string };\n protection: { score: number; weight: number; reason: string };\n };\n}\n\n/** 默认权重 */\nexport const DEFAULT_WEIGHTS = {\n age: 0.25,\n material: 0.2,\n soil: 0.15,\n history: 0.2,\n pressure: 0.1,\n protection: 0.1,\n};\n\nconst MATERIAL_RISK: Record<PipeMaterial, number> = {\n cast_iron: 0.85, // 铸铁 = 高风险(旧管网)\n ductile_iron: 0.2, // 球墨铸铁\n steel: 0.4,\n pe: 0.1,\n pvc: 0.15,\n concrete: 0.3,\n hdpe: 0.05,\n copper: 0.1,\n unknown: 0.5,\n};\n\n/**\n * 计算单根管线的健康度\n */\nexport function scorePipeHealth(input: PipeHealthInput, weights = DEFAULT_WEIGHTS): PipeHealthScore {\n const dims = {\n age: scoreAge(input, weights.age),\n material: scoreMaterial(input, weights.material),\n soil: scoreSoil(input, weights.soil),\n history: scoreHistory(input, weights.history),\n pressure: scorePressure(input, weights.pressure),\n protection: scoreProtection(input, weights.protection),\n };\n\n const total =\n dims.age.score * dims.age.weight +\n dims.material.score * dims.material.weight +\n dims.soil.score * dims.soil.weight +\n dims.history.score * dims.history.weight +\n dims.pressure.score * dims.pressure.weight +\n dims.protection.score * dims.protection.weight;\n\n // 损坏或维修中额外惩罚\n let final = total;\n if (input.status === 'damaged') final *= 0.5;\n else if (input.status === 'under_repair') final *= 0.7;\n else if (input.status === 'abandoned') final = 0;\n\n return {\n score: Math.round(final),\n level: toLevel(final),\n dimensions: dims,\n };\n}\n\nfunction toLevel(score: number): PipeHealthScore['level'] {\n if (score >= 80) return 'excellent';\n if (score >= 60) return 'good';\n if (score >= 40) return 'fair';\n if (score >= 20) return 'poor';\n return 'critical';\n}\n\nfunction scoreAge(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['age'] {\n if (!input.installDate) {\n return { score: 50, weight, reason: '未知安装日期,按中等计' };\n }\n const ageYears = (Date.now() - new Date(input.installDate).getTime()) / (365.25 * 24 * 3600 * 1000);\n if (ageYears < 0) return { score: 100, weight, reason: '未来日期,视为优秀' };\n if (ageYears < 5) return { score: 100, weight, reason: '新建管线 (<5年)' };\n if (ageYears < 15) return { score: 85, weight, reason: '较新 (5-15年)' };\n if (ageYears < 25) return { score: 65, weight, reason: '中龄 (15-25年)' };\n if (ageYears < 40) return { score: 40, weight, reason: '老龄 (25-40年)' };\n if (ageYears < 60) return { score: 20, weight, reason: '超期 (>40年)' };\n return { score: 5, weight, reason: '超期 (>60年)' };\n}\n\nfunction scoreMaterial(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['material'] {\n if (!input.material) {\n return { score: 50, weight, reason: '未知材质,按中等计' };\n }\n const risk = MATERIAL_RISK[input.material] ?? 0.5;\n const score = Math.round((1 - risk) * 100);\n const labels: Record<string, string> = {\n cast_iron: '铸铁(高风险)',\n ductile_iron: '球墨铸铁',\n steel: '钢',\n pe: 'PE(低风险)',\n pvc: 'PVC',\n concrete: '混凝土',\n hdpe: 'HDPE(最佳)',\n copper: '铜',\n unknown: '未知',\n };\n return {\n score,\n weight,\n reason: labels[input.material] ?? input.material,\n };\n}\n\nfunction scoreSoil(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['soil'] {\n const c = input.soilCorrosion ?? 0.5;\n const score = Math.round((1 - c) * 100);\n return {\n score,\n weight,\n reason: c === 0.5 && !input.soilCorrosion ? '无土壤数据,按平均计' : `土壤腐蚀指数 ${c.toFixed(2)}`,\n };\n}\n\nfunction scoreHistory(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['history'] {\n const fail = input.failureCount ?? 0;\n if (fail === 0) return { score: 100, weight, reason: '历史无故障' };\n if (fail === 1) return { score: 75, weight, reason: '历史 1 次故障' };\n if (fail === 2) return { score: 50, weight, reason: '历史 2 次故障' };\n if (fail === 3) return { score: 25, weight, reason: '历史 3 次故障' };\n return { score: 0, weight, reason: `历史 ≥${fail} 次故障` };\n}\n\nfunction scorePressure(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['pressure'] {\n if (!input.pressure) {\n return { score: 75, weight, reason: '无压力数据,按中等偏好计' };\n }\n if (!input.ratedPressure) {\n return { score: 75, weight, reason: '仅知当前压力' };\n }\n const ratio = input.pressure / input.ratedPressure;\n if (ratio < 0.5) return { score: 60, weight, reason: '压力偏低 (使用率 <50%)' };\n if (ratio < 0.8) return { score: 90, weight, reason: '压力正常 (使用率 50-80%)' };\n if (ratio < 1.0) return { score: 75, weight, reason: '压力偏高 (使用率 80-100%)' };\n if (ratio < 1.2) return { score: 30, weight, reason: '压力超限 (100-120%)' };\n return { score: 0, weight, reason: '压力严重超限 (>120%)' };\n}\n\nfunction scoreProtection(input: PipeHealthInput, weight: number): PipeHealthScore['dimensions']['protection'] {\n if (input.hasCathodicProtection === true) {\n return { score: 90, weight, reason: '有阴极保护' };\n }\n if (input.hasCathodicProtection === false) {\n return { score: 30, weight, reason: '无阴极保护' };\n }\n return { score: 50, weight, reason: '阴保状态未知' };\n}\n\n/**\n * 批量评分(按需遍历)\n */\nexport function scorePipes(inputs: PipeHealthInput[], weights = DEFAULT_WEIGHTS): PipeHealthScore[] {\n return inputs.map((i) => scorePipeHealth(i, weights));\n}\n","/**\n * 风险热力图(riskHeatmap)\n *\n * 把 scorePipeHealth 输出按地理网格聚合,输出每个网格的平均分/最高风险,\n * 用于地图热力图(heat-map layer)。\n *\n * 输入:管线数组 [{ lng, lat, healthScore }]\n * 输出:GeoJSON FeatureCollection<Point>(含 healthScore 属性)\n */\n\nexport interface HeatmapCell {\n lng: number;\n lat: number;\n healthScore: number;\n /** 网格内管段数 */\n pipeCount: number;\n}\n\nexport interface PipeHealthPoint {\n lng: number;\n lat: number;\n healthScore: number;\n}\n\nexport interface RiskHeatmapOptions {\n /** 网格尺寸(m),默认 500 */\n cellSize?: number;\n /** 聚合方式 */\n aggregation?: 'average' | 'min' | 'max';\n}\n\nexport function aggregateHeatmap(\n points: PipeHealthPoint[],\n options: RiskHeatmapOptions = {}\n): HeatmapCell[] {\n const cell = options.cellSize ?? 500;\n const agg = options.aggregation ?? 'average';\n\n // 经纬度 → 米近似(以第一个点为原点)\n if (points.length === 0) return [];\n\n const refLat = points[0].lat;\n const refLng = points[0].lng;\n const mPerDegLat = 110_540;\n const mPerDegLng = 111_320 * Math.cos((refLat * Math.PI) / 180);\n\n const grid = new Map<string, { acc: number; count: number; minS: number; maxS: number; lng: number; lat: number }>();\n for (const p of points) {\n const x = (p.lng - refLng) * mPerDegLng;\n const y = (p.lat - refLat) * mPerDegLat;\n const cx = Math.floor(x / cell);\n const cy = Math.floor(y / cell);\n const key = `${cx}:${cy}`;\n const cur = grid.get(key);\n if (!cur) {\n grid.set(key, {\n acc: p.healthScore,\n count: 1,\n minS: p.healthScore,\n maxS: p.healthScore,\n lng: p.lng,\n lat: p.lat,\n });\n } else {\n cur.acc += p.healthScore;\n cur.count += 1;\n if (p.healthScore < cur.minS) cur.minS = p.healthScore;\n if (p.healthScore > cur.maxS) cur.maxS = p.healthScore;\n cur.lng = (cur.lng * (cur.count - 1) + p.lng) / cur.count;\n cur.lat = (cur.lat * (cur.count - 1) + p.lat) / cur.count;\n }\n }\n\n const cells: HeatmapCell[] = [];\n for (const v of grid.values()) {\n let score: number;\n if (agg === 'min') score = v.minS;\n else if (agg === 'max') score = v.maxS;\n else score = v.acc / v.count;\n cells.push({\n lng: v.lng,\n lat: v.lat,\n healthScore: Math.round(score),\n pipeCount: v.count,\n });\n }\n return cells;\n}\n\nexport function heatmapToGeoJSON(cells: HeatmapCell[]): GeoJSON.FeatureCollection<GeoJSON.Point> {\n return {\n type: 'FeatureCollection',\n features: cells.map((c) => ({\n type: 'Feature',\n geometry: { type: 'Point', coordinates: [c.lng, c.lat] },\n properties: {\n healthScore: c.healthScore,\n pipeCount: c.pipeCount,\n },\n })),\n };\n}\n\n/** 优先维护建议列表(风险最高的 Top N 管线) */\nexport function prioritizeMaintenance(\n pipes: Array<{ id: string; healthScore: number; lng: number; lat: number; label?: string }>,\n topN = 10\n) {\n return [...pipes].sort((a, b) => a.healthScore - b.healthScore).slice(0, topN);\n}\n","/**\n * PipelineHealth 组件类(地图集成层)\n */\n\nimport type { Map as CaoguoMap } from '@caoguo/maplibre';\nimport { scorePipeHealth, type PipeHealthInput, type PipeHealthScore, DEFAULT_WEIGHTS } from './healthScorer';\nimport { aggregateHeatmap, heatmapToGeoJSON, type HeatmapCell, prioritizeMaintenance } from './riskHeatmap';\nimport type { PipelineTopologyDataset } from '../types';\n\nexport interface PipelineHealthOptions {\n map: CaoguoMap;\n dataset: PipelineTopologyDataset;\n cellSize?: number;\n layerPrefix?: string;\n}\n\nexport interface HealthResult {\n /** 全部管段评分 */\n scores: Array<{ pipeId: string; score: PipeHealthScore }>;\n /** 热力图聚合格 */\n heatmap: HeatmapCell[];\n /** 优先维护建议 */\n maintenance: Array<{ id: string; healthScore: number; lng: number; lat: number; label?: string }>;\n /** 计算耗时 */\n durationMs: number;\n}\n\ntype Listener = (r: HealthResult) => void;\n\n/**\n * 管线健康评估组件\n */\nexport class PipelineHealth {\n private map: CaoguoMap;\n private dataset: PipelineTopologyDataset;\n private cellSize: number;\n private layerPrefix: string;\n private listeners = new Set<Listener>();\n private lastResult: HealthResult | null = null;\n private layerIds: string[] = [];\n\n constructor(options: PipelineHealthOptions) {\n this.map = options.map;\n this.dataset = options.dataset;\n this.cellSize = options.cellSize ?? 500;\n this.layerPrefix = options.layerPrefix ?? 'cg-health';\n }\n\n /** 评估全网管线健康度 */\n evaluate(weights = DEFAULT_WEIGHTS): HealthResult {\n const t0 = (typeof performance !== 'undefined' ? performance.now() : Date.now());\n const nodeById = new Map(this.dataset.nodes.map((n) => [n.id, n] as const));\n\n const scores = this.dataset.pipes.map((p) => {\n const input: PipeHealthInput = {\n installDate: p.properties?.installDate,\n material: p.properties?.material,\n failureCount: p.properties?.failureCount ?? 0,\n pressure: p.properties?.pressure,\n ratedPressure: p.properties?.ratedPressure,\n hasCathodicProtection: p.properties?.hasCathodicProtection ?? false,\n status: p.properties?.status,\n };\n return { pipeId: p.id, score: scorePipeHealth(input, weights) };\n });\n\n const points = scores.flatMap((s, i) => {\n const pipe = this.dataset.pipes[i];\n const from = nodeById.get(pipe.fromNode);\n const to = nodeById.get(pipe.toNode);\n if (!from || !to) return [];\n return [\n {\n lng: (from.lng + to.lng) / 2,\n lat: (from.lat + to.lat) / 2,\n healthScore: s.score.score,\n },\n ];\n });\n\n const heatmap = aggregateHeatmap(points, { cellSize: this.cellSize });\n const maintenance = prioritizeMaintenance(\n scores.flatMap((s, i) => {\n const pipe = this.dataset.pipes[i];\n const from = nodeById.get(pipe.fromNode);\n const to = nodeById.get(pipe.toNode);\n if (!from || !to) return [];\n return [\n {\n id: pipe.id,\n healthScore: s.score.score,\n lng: (from.lng + to.lng) / 2,\n lat: (from.lat + to.lat) / 2,\n label: pipe.id,\n },\n ];\n })\n );\n\n const durationMs =\n (typeof performance !== 'undefined' ? performance.now() : Date.now()) - t0;\n\n const result: HealthResult = { scores, heatmap, maintenance, durationMs };\n this.lastResult = result;\n this.renderHeatmap(result);\n for (const l of this.listeners) l(result);\n return result;\n }\n\n /** 清空图层 */\n clear(): void {\n for (const id of this.layerIds) {\n this.map.removeLayer(id);\n }\n this.layerIds = [];\n }\n\n /** 销毁 */\n destroy(): void {\n this.clear();\n this.listeners.clear();\n this.lastResult = null;\n }\n\n /** 订阅结果 */\n onResult(fn: Listener): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n /** 取最后一次结果 */\n getLastResult(): HealthResult | null {\n return this.lastResult;\n }\n\n private renderHeatmap(result: HealthResult): void {\n this.clear();\n const mlMap = (this.map as unknown as {\n instance: {\n addSource: (id: string, source: unknown) => void;\n addLayer: (layer: unknown) => void;\n getSource: (id: string) => unknown;\n };\n }).instance;\n const data = heatmapToGeoJSON(result.heatmap);\n const sourceId = `${this.layerPrefix}-heat-src`;\n const layerId = `${this.layerPrefix}-heat-point`;\n if (!mlMap.getSource(sourceId)) mlMap.addSource(sourceId, data);\n try {\n mlMap.addLayer({\n id: layerId,\n type: 'heatmap',\n source: sourceId,\n paint: {\n 'heatmap-weight': [\n 'interpolate',\n ['linear'],\n ['get', 'healthScore'],\n 0, 1,\n 50, 0.5,\n 100, 0,\n ] as unknown,\n 'heatmap-intensity': 1,\n 'heatmap-color': [\n 'interpolate',\n ['linear'],\n ['heatmap-density'],\n 0, 'rgba(34,197,94,0)',\n 0.2, 'rgba(34,197,94,0.5)',\n 0.5, 'rgba(245,158,11,0.7)',\n 0.8, 'rgba(239,68,68,0.8)',\n 1, 'rgba(127,29,29,0.9)',\n ] as unknown,\n 'heatmap-radius': 25,\n 'heatmap-opacity': 0.7,\n },\n });\n this.layerIds.push(layerId);\n } catch {\n // ignore\n }\n }\n}\n"]}
|
package/dist/chunk-BWQKY35F.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { paintPipeBy } from './chunk-QVY4WJLM.js';
|
|
2
|
-
import { upsertSource } from '@caoguo/maplibre';
|
|
3
|
-
|
|
4
|
-
var PipelineTopology = class {
|
|
5
|
-
map;
|
|
6
|
-
dataset;
|
|
7
|
-
colorBy;
|
|
8
|
-
layerPrefix;
|
|
9
|
-
pipelineTypes;
|
|
10
|
-
layerIds = [];
|
|
11
|
-
nodeListeners = /* @__PURE__ */ new Set();
|
|
12
|
-
pipeListeners = /* @__PURE__ */ new Set();
|
|
13
|
-
drillListeners = /* @__PURE__ */ new Set();
|
|
14
|
-
/** 当前钻取区域(null = 全量) */
|
|
15
|
-
currentRegion = null;
|
|
16
|
-
/** 当前分层过滤器 */
|
|
17
|
-
layerFilter = null;
|
|
18
|
-
/** click 事件是否已绑定 */
|
|
19
|
-
clickBound = false;
|
|
20
|
-
constructor(options) {
|
|
21
|
-
this.map = options.map;
|
|
22
|
-
this.dataset = options.dataset;
|
|
23
|
-
this.colorBy = options.colorBy ?? "type";
|
|
24
|
-
this.pipelineTypes = options.pipelineTypes;
|
|
25
|
-
this.layerPrefix = options.layerPrefix ?? "cg-topo";
|
|
26
|
-
}
|
|
27
|
-
/** 渲染管段+节点到地图 */
|
|
28
|
-
render() {
|
|
29
|
-
this.clear();
|
|
30
|
-
const mlMap = this.map.instance;
|
|
31
|
-
const prefix = this.layerPrefix;
|
|
32
|
-
const pipeGeoJSON = {
|
|
33
|
-
type: "FeatureCollection",
|
|
34
|
-
features: this.dataset.pipes.filter((p) => this.isPipeVisible(p)).flatMap((p) => {
|
|
35
|
-
const from = this.dataset.nodes.find((n) => n.id === p.fromNode);
|
|
36
|
-
const to = this.dataset.nodes.find((n) => n.id === p.toNode);
|
|
37
|
-
if (!from || !to) return [];
|
|
38
|
-
const coords = p.geometry && p.geometry.length >= 2 ? p.geometry : [
|
|
39
|
-
[from.lng, from.lat],
|
|
40
|
-
[to.lng, to.lat]
|
|
41
|
-
];
|
|
42
|
-
return [
|
|
43
|
-
{
|
|
44
|
-
type: "Feature",
|
|
45
|
-
geometry: { type: "LineString", coordinates: coords },
|
|
46
|
-
properties: {
|
|
47
|
-
pipeId: p.id,
|
|
48
|
-
pipelineType: p.pipelineType ?? "",
|
|
49
|
-
diameter: p.properties?.diameter ?? 0,
|
|
50
|
-
status: p.properties?.status ?? "unknown",
|
|
51
|
-
material: p.properties?.material ?? "unknown"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
})
|
|
56
|
-
};
|
|
57
|
-
const nodeGeoJSON = {
|
|
58
|
-
type: "FeatureCollection",
|
|
59
|
-
features: this.dataset.nodes.filter((n) => this.isNodeVisible(n)).map((n) => ({
|
|
60
|
-
type: "Feature",
|
|
61
|
-
geometry: { type: "Point", coordinates: [n.lng, n.lat] },
|
|
62
|
-
properties: { nodeId: n.id, kind: n.kind, pipelineType: n.pipelineType ?? "" }
|
|
63
|
-
}))
|
|
64
|
-
};
|
|
65
|
-
upsertSource(mlMap, `${prefix}-pipes-src`, pipeGeoJSON);
|
|
66
|
-
upsertSource(mlMap, `${prefix}-nodes-src`, nodeGeoJSON);
|
|
67
|
-
mlMap.addLayer({
|
|
68
|
-
id: `${prefix}-pipes-line`,
|
|
69
|
-
type: "line",
|
|
70
|
-
source: `${prefix}-pipes-src`,
|
|
71
|
-
paint: {
|
|
72
|
-
"line-color": paintPipeBy(this.colorBy, { types: this.pipelineTypes }),
|
|
73
|
-
"line-width": 2,
|
|
74
|
-
"line-opacity": 0.85
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
mlMap.addLayer({
|
|
78
|
-
id: `${prefix}-nodes-pt`,
|
|
79
|
-
type: "circle",
|
|
80
|
-
source: `${prefix}-nodes-src`,
|
|
81
|
-
paint: {
|
|
82
|
-
"circle-radius": 4,
|
|
83
|
-
"circle-color": "#f59e0b",
|
|
84
|
-
"circle-stroke-width": 1.5,
|
|
85
|
-
"circle-stroke-color": "#ffffff"
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
this.layerIds.push(`${prefix}-pipes-line`, `${prefix}-nodes-pt`);
|
|
89
|
-
this.bindClickEvents(mlMap, prefix);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 绑定点击事件(设备卡片:点击节点/管段触发订阅回调)。
|
|
93
|
-
* 只绑定一次(render 重建图层后事件仍指向固定 layerId)。
|
|
94
|
-
*/
|
|
95
|
-
bindClickEvents(mlMap, prefix) {
|
|
96
|
-
if (this.clickBound) return;
|
|
97
|
-
this.clickBound = true;
|
|
98
|
-
const nodeById = new Map(this.dataset.nodes.map((n) => [n.id, n]));
|
|
99
|
-
const pipeById = new Map(this.dataset.pipes.map((p) => [p.id, p]));
|
|
100
|
-
mlMap.on("click", `${prefix}-pipes-line`, (e) => {
|
|
101
|
-
const features = e.features ?? [];
|
|
102
|
-
const f = features[0];
|
|
103
|
-
const pipe = f?.properties?.pipeId ? pipeById.get(f.properties.pipeId) : void 0;
|
|
104
|
-
if (pipe) {
|
|
105
|
-
for (const l of this.pipeListeners) l({ pipe });
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
mlMap.on("click", `${prefix}-nodes-pt`, (e) => {
|
|
109
|
-
const features = e.features ?? [];
|
|
110
|
-
const f = features[0];
|
|
111
|
-
const node = f?.properties?.nodeId ? nodeById.get(f.properties.nodeId) : void 0;
|
|
112
|
-
if (node) {
|
|
113
|
-
for (const l of this.nodeListeners) l({ node });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
/** 切换着色模式 */
|
|
118
|
-
setColorBy(mode) {
|
|
119
|
-
this.colorBy = mode;
|
|
120
|
-
const mlMap = this.map.instance;
|
|
121
|
-
if (mlMap.setPaintProperty) {
|
|
122
|
-
try {
|
|
123
|
-
mlMap.setPaintProperty(
|
|
124
|
-
`${this.layerPrefix}-pipes-line`,
|
|
125
|
-
"line-color",
|
|
126
|
-
paintPipeBy(mode, { types: this.pipelineTypes })
|
|
127
|
-
);
|
|
128
|
-
} catch {
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/** 清空所有图层 */
|
|
133
|
-
clear() {
|
|
134
|
-
for (const id of this.layerIds) {
|
|
135
|
-
this.map.removeLayer(id);
|
|
136
|
-
}
|
|
137
|
-
this.layerIds = [];
|
|
138
|
-
}
|
|
139
|
-
/** 销毁组件 */
|
|
140
|
-
destroy() {
|
|
141
|
-
this.clear();
|
|
142
|
-
this.nodeListeners.clear();
|
|
143
|
-
this.pipeListeners.clear();
|
|
144
|
-
this.drillListeners.clear();
|
|
145
|
-
}
|
|
146
|
-
/** 订阅节点点击事件 */
|
|
147
|
-
onNodeSelect(fn) {
|
|
148
|
-
this.nodeListeners.add(fn);
|
|
149
|
-
return () => this.nodeListeners.delete(fn);
|
|
150
|
-
}
|
|
151
|
-
/** 订阅管段点击事件 */
|
|
152
|
-
onPipeSelect(fn) {
|
|
153
|
-
this.pipeListeners.add(fn);
|
|
154
|
-
return () => this.pipeListeners.delete(fn);
|
|
155
|
-
}
|
|
156
|
-
/** 订阅钻取事件 */
|
|
157
|
-
onDrillDown(fn) {
|
|
158
|
-
this.drillListeners.add(fn);
|
|
159
|
-
return () => this.drillListeners.delete(fn);
|
|
160
|
-
}
|
|
161
|
-
/** 管段是否可见(管线类型 + 区域钻取 + 分层过滤) */
|
|
162
|
-
isPipeVisible(p) {
|
|
163
|
-
if (this.pipelineTypes?.length && !this.pipelineTypes.includes(p.pipelineType)) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
if (this.currentRegion && p.region !== this.currentRegion) return false;
|
|
167
|
-
const f = this.layerFilter;
|
|
168
|
-
if (f) {
|
|
169
|
-
const props = p.properties ?? {};
|
|
170
|
-
if (f.minDiameter !== void 0 && (props.diameter ?? 0) < f.minDiameter) return false;
|
|
171
|
-
if (f.maxDiameter !== void 0 && (props.diameter ?? Infinity) > f.maxDiameter) return false;
|
|
172
|
-
if (f.material && props.material !== f.material) return false;
|
|
173
|
-
if (f.status && props.status !== f.status) return false;
|
|
174
|
-
if (f.minAgeYears !== void 0 && props.installDate) {
|
|
175
|
-
if (this.ageInYears(props.installDate) < f.minAgeYears) return false;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
/** 节点是否可见(管线类型 + 区域钻取) */
|
|
181
|
-
isNodeVisible(n) {
|
|
182
|
-
if (this.pipelineTypes?.length && !this.pipelineTypes.includes(n.pipelineType)) {
|
|
183
|
-
return false;
|
|
184
|
-
}
|
|
185
|
-
if (this.currentRegion && n.region !== this.currentRegion) return false;
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
/** 计算管段使用年限(年) */
|
|
189
|
-
ageInYears(installDate) {
|
|
190
|
-
const d = new Date(installDate).getTime();
|
|
191
|
-
if (Number.isNaN(d)) return 0;
|
|
192
|
-
return (Date.now() - d) / (365.25 * 24 * 3600 * 1e3);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* 搜索定位(按编号/地址/区域/类型)。
|
|
196
|
-
* 返回匹配的节点与管段,供上层 flyTo 定位。
|
|
197
|
-
*/
|
|
198
|
-
search(query) {
|
|
199
|
-
const q = query.trim().toLowerCase();
|
|
200
|
-
if (!q) return { nodes: [], pipes: [] };
|
|
201
|
-
const nodes = this.dataset.nodes.filter(
|
|
202
|
-
(n) => n.id.toLowerCase().includes(q) || (n.properties?.code ?? "").toLowerCase().includes(q) || (n.region ?? "").toLowerCase().includes(q) || n.kind.toLowerCase().includes(q)
|
|
203
|
-
);
|
|
204
|
-
const pipes = this.dataset.pipes.filter(
|
|
205
|
-
(p) => p.id.toLowerCase().includes(q) || (p.region ?? "").toLowerCase().includes(q) || (p.properties?.material ?? "").toLowerCase().includes(q)
|
|
206
|
-
);
|
|
207
|
-
return { nodes, pipes };
|
|
208
|
-
}
|
|
209
|
-
/** 层级钻取:下钻到指定区域(过滤渲染 + 触发事件) */
|
|
210
|
-
drillDown(region) {
|
|
211
|
-
const from = this.currentRegion;
|
|
212
|
-
this.currentRegion = region;
|
|
213
|
-
this.render();
|
|
214
|
-
for (const l of this.drillListeners) l({ from, to: region });
|
|
215
|
-
}
|
|
216
|
-
/** 层级钻取:返回上一级(清空区域过滤) */
|
|
217
|
-
drillUp() {
|
|
218
|
-
const from = this.currentRegion;
|
|
219
|
-
this.currentRegion = null;
|
|
220
|
-
this.render();
|
|
221
|
-
for (const l of this.drillListeners) l({ from, to: "" });
|
|
222
|
-
}
|
|
223
|
-
/** 分层控制:按管径/材质/状态/年代过滤 */
|
|
224
|
-
setLayerFilter(filter) {
|
|
225
|
-
this.layerFilter = filter;
|
|
226
|
-
this.render();
|
|
227
|
-
}
|
|
228
|
-
/** 清空分层过滤器 */
|
|
229
|
-
clearLayerFilter() {
|
|
230
|
-
this.layerFilter = null;
|
|
231
|
-
this.render();
|
|
232
|
-
}
|
|
233
|
-
/** 高亮连通路径(基于节点 ID) */
|
|
234
|
-
highlightConnectivity(centerId) {
|
|
235
|
-
const adj = /* @__PURE__ */ new Map();
|
|
236
|
-
for (const p of this.dataset.pipes) {
|
|
237
|
-
if (!adj.has(p.fromNode)) adj.set(p.fromNode, /* @__PURE__ */ new Set());
|
|
238
|
-
if (!adj.has(p.toNode)) adj.set(p.toNode, /* @__PURE__ */ new Set());
|
|
239
|
-
adj.get(p.fromNode).add(p.toNode);
|
|
240
|
-
adj.get(p.toNode).add(p.fromNode);
|
|
241
|
-
}
|
|
242
|
-
const visited = /* @__PURE__ */ new Set();
|
|
243
|
-
const stack = [centerId];
|
|
244
|
-
while (stack.length > 0) {
|
|
245
|
-
const cur = stack.pop();
|
|
246
|
-
if (visited.has(cur)) continue;
|
|
247
|
-
visited.add(cur);
|
|
248
|
-
const neighbors = adj.get(cur) ?? /* @__PURE__ */ new Set();
|
|
249
|
-
for (const n of neighbors) {
|
|
250
|
-
if (!visited.has(n)) stack.push(n);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return visited;
|
|
254
|
-
}
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
export { PipelineTopology };
|
|
258
|
-
//# sourceMappingURL=chunk-BWQKY35F.js.map
|
|
259
|
-
//# sourceMappingURL=chunk-BWQKY35F.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/topology/PipelineTopology.ts"],"names":[],"mappings":"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","file":"chunk-BWQKY35F.js","sourcesContent":["/**\n * PipelineTopology 类(Phase 1 管网拓扑编辑器)\n *\n * 完整实现:\n * - 拓扑渲染(管段/节点按类型着色)\n * - 层级钻取(区域→街道→小区→楼栋,drillDown / drillUp)\n * - 设备卡片(点击节点/管段触发 onNodeSelect / onPipeSelect)\n * - 连通性高亮(选中管线后高亮上下游,highlightConnectivity)\n * - 搜索定位(按编号/地址/区域,search)\n * - 分层控制(按管径/材质/年代/状态,setLayerFilter)\n */\n\nimport type { Map as CaoguoMap } from '@caoguo/maplibre';\nimport { upsertSource } from '@caoguo/maplibre';\nimport type {\n PipelineTopologyDataset,\n ColorByMode,\n PipelineType,\n PipelineNode,\n PipelinePipe,\n NodeSelectEvent,\n PipeSelectEvent,\n DrillDownEvent,\n} from '../types';\nimport { paintPipeBy } from '../style/paintRules';\n\nexport interface PipelineTopologyOptions {\n map: CaoguoMap;\n dataset: PipelineTopologyDataset;\n /** 默认着色模式 */\n colorBy?: ColorByMode;\n /** 仅显示指定管线类型(空 = 全显示) */\n pipelineTypes?: PipelineType[];\n /** 层 ID 前缀 */\n layerPrefix?: string;\n}\n\ntype NodeListener = (e: NodeSelectEvent) => void;\ntype PipeListener = (e: PipeSelectEvent) => void;\ntype DrillListener = (e: DrillDownEvent) => void;\n\n/** 分层控制过滤器(按管径/材质/状态/年代) */\nexport interface LayerFilter {\n /** 最小管径(mm) */\n minDiameter?: number;\n /** 最大管径(mm) */\n maxDiameter?: number;\n /** 管材 */\n material?: string;\n /** 状态 */\n status?: string;\n /** 最小使用年限 */\n minAgeYears?: number;\n}\n\n/** 搜索结果 */\nexport interface SearchResult {\n /** 匹配节点 */\n nodes: PipelineNode[];\n /** 匹配管段 */\n pipes: PipelinePipe[];\n}\n\n/**\n * PipelineTopology 组件(Phase 1 MVP 完整实现)\n *\n * 实现进度:\n * - [x] 渲染数据接入\n * - [x] 层级钻取(drillDown / drillUp)\n * - [x] 设备卡片(点击绑定 onNodeSelect / onPipeSelect)\n * - [x] 连通性高亮(highlightConnectivity)\n * - [x] 搜索定位(search)\n * - [x] 分层控制(setLayerFilter / clearLayerFilter)\n */\nexport class PipelineTopology {\n private map: CaoguoMap;\n private dataset: PipelineTopologyDataset;\n private colorBy: ColorByMode;\n private layerPrefix: string;\n private pipelineTypes?: PipelineType[];\n private layerIds: string[] = [];\n private nodeListeners = new Set<NodeListener>();\n private pipeListeners = new Set<PipeListener>();\n private drillListeners = new Set<DrillListener>();\n /** 当前钻取区域(null = 全量) */\n private currentRegion: string | null = null;\n /** 当前分层过滤器 */\n private layerFilter: LayerFilter | null = null;\n /** click 事件是否已绑定 */\n private clickBound = false;\n\n constructor(options: PipelineTopologyOptions) {\n this.map = options.map;\n this.dataset = options.dataset;\n this.colorBy = options.colorBy ?? 'type';\n this.pipelineTypes = options.pipelineTypes;\n this.layerPrefix = options.layerPrefix ?? 'cg-topo';\n }\n\n /** 渲染管段+节点到地图 */\n render(): void {\n this.clear();\n const mlMap = (this.map as unknown as {\n instance: {\n addSource: (id: string, source: unknown) => void;\n addLayer: (layer: unknown) => void;\n getSource: (id: string) => unknown;\n on: (event: string, layerId: string, cb: (e: unknown) => void) => void;\n };\n }).instance;\n const prefix = this.layerPrefix;\n\n const pipeGeoJSON: GeoJSON.FeatureCollection<GeoJSON.LineString> = {\n type: 'FeatureCollection',\n features: this.dataset.pipes\n .filter((p) => this.isPipeVisible(p))\n .flatMap((p) => {\n const from = this.dataset.nodes.find((n) => n.id === p.fromNode);\n const to = this.dataset.nodes.find((n) => n.id === p.toNode);\n if (!from || !to) return [];\n const coords: [number, number][] =\n p.geometry && p.geometry.length >= 2\n ? (p.geometry as [number, number][])\n : [\n [from.lng, from.lat],\n [to.lng, to.lat],\n ];\n return [\n {\n type: 'Feature' as const,\n geometry: { type: 'LineString' as const, coordinates: coords },\n properties: {\n pipeId: p.id,\n pipelineType: p.pipelineType ?? '',\n diameter: p.properties?.diameter ?? 0,\n status: p.properties?.status ?? 'unknown',\n material: p.properties?.material ?? 'unknown',\n },\n },\n ];\n }),\n };\n\n const nodeGeoJSON: GeoJSON.FeatureCollection<GeoJSON.Point> = {\n type: 'FeatureCollection',\n features: this.dataset.nodes\n .filter((n) => this.isNodeVisible(n))\n .map((n) => ({\n type: 'Feature' as const,\n geometry: { type: 'Point' as const, coordinates: [n.lng, n.lat] },\n properties: { nodeId: n.id, kind: n.kind, pipelineType: n.pipelineType ?? '' },\n })),\n };\n\n // 幂等:层级/着色切换重渲染时 source 可能已存在,先判断避免抛 \"already exists\"\n upsertSource(mlMap, `${prefix}-pipes-src`, pipeGeoJSON);\n upsertSource(mlMap, `${prefix}-nodes-src`, nodeGeoJSON);\n mlMap.addLayer({\n id: `${prefix}-pipes-line`,\n type: 'line',\n source: `${prefix}-pipes-src`,\n paint: {\n 'line-color': paintPipeBy(this.colorBy, { types: this.pipelineTypes }) as never,\n 'line-width': 2,\n 'line-opacity': 0.85,\n },\n });\n mlMap.addLayer({\n id: `${prefix}-nodes-pt`,\n type: 'circle',\n source: `${prefix}-nodes-src`,\n paint: {\n 'circle-radius': 4,\n 'circle-color': '#f59e0b',\n 'circle-stroke-width': 1.5,\n 'circle-stroke-color': '#ffffff',\n },\n });\n this.layerIds.push(`${prefix}-pipes-line`, `${prefix}-nodes-pt`);\n this.bindClickEvents(mlMap, prefix);\n }\n\n /**\n * 绑定点击事件(设备卡片:点击节点/管段触发订阅回调)。\n * 只绑定一次(render 重建图层后事件仍指向固定 layerId)。\n */\n private bindClickEvents(\n mlMap: {\n on: (event: string, layerId: string, cb: (e: unknown) => void) => void;\n },\n prefix: string\n ): void {\n if (this.clickBound) return;\n this.clickBound = true;\n\n const nodeById = new Map(this.dataset.nodes.map((n) => [n.id, n] as const));\n const pipeById = new Map(this.dataset.pipes.map((p) => [p.id, p] as const));\n\n mlMap.on('click', `${prefix}-pipes-line`, (e) => {\n const features = (e as { features?: Array<{ properties?: { pipeId?: string } }> }).features ?? [];\n const f = features[0];\n const pipe = f?.properties?.pipeId ? pipeById.get(f.properties.pipeId) : undefined;\n if (pipe) {\n for (const l of this.pipeListeners) l({ pipe });\n }\n });\n\n mlMap.on('click', `${prefix}-nodes-pt`, (e) => {\n const features = (e as { features?: Array<{ properties?: { nodeId?: string } }> }).features ?? [];\n const f = features[0];\n const node = f?.properties?.nodeId ? nodeById.get(f.properties.nodeId) : undefined;\n if (node) {\n for (const l of this.nodeListeners) l({ node });\n }\n });\n }\n\n /** 切换着色模式 */\n setColorBy(mode: ColorByMode): void {\n this.colorBy = mode;\n const mlMap = (this.map as unknown as {\n instance: { setPaintProperty: (id: string, prop: string, value: unknown) => void };\n }).instance;\n if (mlMap.setPaintProperty) {\n try {\n mlMap.setPaintProperty(\n `${this.layerPrefix}-pipes-line`,\n 'line-color',\n paintPipeBy(mode, { types: this.pipelineTypes }) as never\n );\n } catch {\n // ignore\n }\n }\n }\n\n /** 清空所有图层 */\n clear(): void {\n for (const id of this.layerIds) {\n this.map.removeLayer(id);\n }\n this.layerIds = [];\n }\n\n /** 销毁组件 */\n destroy(): void {\n this.clear();\n this.nodeListeners.clear();\n this.pipeListeners.clear();\n this.drillListeners.clear();\n }\n\n /** 订阅节点点击事件 */\n onNodeSelect(fn: NodeListener): () => void {\n this.nodeListeners.add(fn);\n return () => this.nodeListeners.delete(fn);\n }\n\n /** 订阅管段点击事件 */\n onPipeSelect(fn: PipeListener): () => void {\n this.pipeListeners.add(fn);\n return () => this.pipeListeners.delete(fn);\n }\n\n /** 订阅钻取事件 */\n onDrillDown(fn: DrillListener): () => void {\n this.drillListeners.add(fn);\n return () => this.drillListeners.delete(fn);\n }\n\n /** 管段是否可见(管线类型 + 区域钻取 + 分层过滤) */\n private isPipeVisible(p: PipelinePipe): boolean {\n if (this.pipelineTypes?.length && !this.pipelineTypes.includes(p.pipelineType as PipelineType)) {\n return false;\n }\n if (this.currentRegion && p.region !== this.currentRegion) return false;\n const f = this.layerFilter;\n if (f) {\n const props = p.properties ?? {};\n if (f.minDiameter !== undefined && (props.diameter ?? 0) < f.minDiameter) return false;\n if (f.maxDiameter !== undefined && (props.diameter ?? Infinity) > f.maxDiameter) return false;\n if (f.material && props.material !== f.material) return false;\n if (f.status && props.status !== f.status) return false;\n if (f.minAgeYears !== undefined && props.installDate) {\n if (this.ageInYears(props.installDate) < f.minAgeYears) return false;\n }\n }\n return true;\n }\n\n /** 节点是否可见(管线类型 + 区域钻取) */\n private isNodeVisible(n: PipelineNode): boolean {\n if (this.pipelineTypes?.length && !this.pipelineTypes.includes(n.pipelineType as PipelineType)) {\n return false;\n }\n if (this.currentRegion && n.region !== this.currentRegion) return false;\n return true;\n }\n\n /** 计算管段使用年限(年) */\n private ageInYears(installDate: string): number {\n const d = new Date(installDate).getTime();\n if (Number.isNaN(d)) return 0;\n return (Date.now() - d) / (365.25 * 24 * 3600 * 1000);\n }\n\n /**\n * 搜索定位(按编号/地址/区域/类型)。\n * 返回匹配的节点与管段,供上层 flyTo 定位。\n */\n search(query: string): SearchResult {\n const q = query.trim().toLowerCase();\n if (!q) return { nodes: [], pipes: [] };\n const nodes = this.dataset.nodes.filter(\n (n) =>\n n.id.toLowerCase().includes(q) ||\n (n.properties?.code ?? '').toLowerCase().includes(q) ||\n (n.region ?? '').toLowerCase().includes(q) ||\n n.kind.toLowerCase().includes(q)\n );\n const pipes = this.dataset.pipes.filter(\n (p) =>\n p.id.toLowerCase().includes(q) ||\n (p.region ?? '').toLowerCase().includes(q) ||\n (p.properties?.material ?? '').toLowerCase().includes(q)\n );\n return { nodes, pipes };\n }\n\n /** 层级钻取:下钻到指定区域(过滤渲染 + 触发事件) */\n drillDown(region: string): void {\n const from = this.currentRegion;\n this.currentRegion = region;\n this.render();\n for (const l of this.drillListeners) l({ from, to: region });\n }\n\n /** 层级钻取:返回上一级(清空区域过滤) */\n drillUp(): void {\n const from = this.currentRegion;\n this.currentRegion = null;\n this.render();\n for (const l of this.drillListeners) l({ from, to: '' });\n }\n\n /** 分层控制:按管径/材质/状态/年代过滤 */\n setLayerFilter(filter: LayerFilter): void {\n this.layerFilter = filter;\n this.render();\n }\n\n /** 清空分层过滤器 */\n clearLayerFilter(): void {\n this.layerFilter = null;\n this.render();\n }\n\n /** 高亮连通路径(基于节点 ID) */\n highlightConnectivity(centerId: string): Set<string> {\n const adj = new Map<string, Set<string>>();\n for (const p of this.dataset.pipes) {\n if (!adj.has(p.fromNode)) adj.set(p.fromNode, new Set());\n if (!adj.has(p.toNode)) adj.set(p.toNode, new Set());\n adj.get(p.fromNode)!.add(p.toNode);\n adj.get(p.toNode)!.add(p.fromNode);\n }\n\n const visited = new Set<string>();\n const stack = [centerId];\n while (stack.length > 0) {\n const cur = stack.pop()!;\n if (visited.has(cur)) continue;\n visited.add(cur);\n const neighbors = adj.get(cur) ?? new Set();\n for (const n of neighbors) {\n if (!visited.has(n)) stack.push(n);\n }\n }\n return visited;\n }\n}\n"]}
|