534 字
1 分钟
基于 Web Canvas 与 IndexedDB 的市政道路设施平面图编辑器
市政道路改造、地下管线协调和临时交通组织经常需要在一张平面图上维护道路中心线、路口、施工区与交通设施。这篇文章记录一个浏览器平面图编辑器的核心实现。
为了让现场网络不稳定时仍能继续编辑,方案采用 HTML5 Canvas 负责高频绘制,使用 IndexedDB 保存本地草稿和版本快照。
一、为什么选择 Canvas?
当画布包含大量道路节点、管线交点、标志牌和施工区域时,使用大量 DOM 节点会带来明显的布局与重绘开销。Canvas 更适合这类连续缩放、平移和拖拽场景:
- 集中绘制:全部图元由统一渲染循环管理,避免频繁触发布局计算。
- 坐标一致:屏幕坐标可以稳定映射到工程坐标系。
- 数据解耦:业务实体与绘制样式分离,便于独立调整数据模型和校验规则。
二、编辑器核心模型
先把道路节点、连接关系和校验规则拆成独立模型:
export type RoadNodeType = 'SIGNAL' | 'JUNCTION' | 'WORK_ZONE';
export interface RoadNode { id: string; name: string; type: RoadNodeType; x: number; y: number; chainage: number;}
export interface RoadLink { id: string; fromNodeId: string; toNodeId: string; laneClearance: number;}
export interface SafetyRules { minimumClearance: number;}
export class MunicipalMapEditor { private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; private nodes: RoadNode[] = []; private links: RoadLink[] = []; private selectedNode: RoadNode | null = null; private scale = 1; private offsetX = 0; private offsetY = 0;
constructor(canvas: HTMLCanvasElement, private rules: SafetyRules) { this.canvas = canvas; this.ctx = canvas.getContext('2d')!; this.bindEvents(); }
private bindEvents() { this.canvas.addEventListener('pointerdown', (event) => { const point = this.screenToWorld(event.clientX, event.clientY); this.selectedNode = this.findNodeAt(point.x, point.y); });
this.canvas.addEventListener('pointermove', (event) => { if (!this.selectedNode) return; const point = this.screenToWorld(event.clientX, event.clientY); this.selectedNode.x = point.x; this.selectedNode.y = point.y; this.render(); });
this.canvas.addEventListener('pointerup', async () => { this.selectedNode = null; await this.saveLocalDraft(); }); }
private screenToWorld(screenX: number, screenY: number) { const rect = this.canvas.getBoundingClientRect(); return { x: (screenX - rect.left - this.offsetX) / this.scale, y: (screenY - rect.top - this.offsetY) / this.scale, }; }
private findNodeAt(x: number, y: number): RoadNode | null { const radius = 15; return this.nodes.find((node) => Math.hypot(node.x - x, node.y - y) < radius) ?? null; }
public render() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.save(); this.ctx.translate(this.offsetX, this.offsetY); this.ctx.scale(this.scale, this.scale);
for (const link of this.links) { const from = this.nodes.find((node) => node.id === link.fromNodeId); const to = this.nodes.find((node) => node.id === link.toNodeId); if (!from || !to) continue;
this.ctx.beginPath(); this.ctx.moveTo(from.x, from.y); this.ctx.lineTo(to.x, to.y); this.ctx.strokeStyle = link.laneClearance < this.rules.minimumClearance ? '#ff4d4d' : '#4dabf7'; this.ctx.stroke(); }
for (const node of this.nodes) { this.ctx.beginPath(); this.ctx.arc(node.x, node.y, 8, 0, Math.PI * 2); this.ctx.fillStyle = node.type === 'WORK_ZONE' ? '#e67700' : '#37b24d'; this.ctx.fill(); this.ctx.fillText(node.name, node.x + 12, node.y + 4); }
this.ctx.restore(); }
private async saveLocalDraft() { await localDraftDB.saveDraft('municipal_map_draft', { nodes: this.nodes, links: this.links, updatedAt: Date.now(), }); }}净距阈值不应硬编码,而应由当前项目采用的规范和审批配置提供,这样可以减少业务耦合,也方便按场景切换规则。
三、IndexedDB 本地草稿
export class LocalDraftDB { private db: IDBDatabase | null = null;
async open() { this.db = await new Promise<IDBDatabase>((resolve, reject) => { const request = indexedDB.open('MunicipalMapDemo', 1); request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains('drafts')) { db.createObjectStore('drafts', { keyPath: 'key' }); } }; request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }
async saveDraft(key: string, data: unknown) { if (!this.db) throw new Error('Database is not ready'); const transaction = this.db.transaction('drafts', 'readwrite'); transaction.objectStore('drafts').put({ key, data }); }}
export const localDraftDB = new LocalDraftDB();await localDraftDB.open();生产环境还应补充数据版本号、异常恢复、容量限制、主动清理和草稿加密策略。
四、实现要点
- 将道路、施工区和交通设施抽象为通用节点,降低绘制层与业务字段的耦合。
- 将净距、桩号格式和告警规则全部配置化,由受控数据源注入。
- 本地草稿仅存储编辑数据,敏感图纸应加密并设置生命周期。
- 导出文件和截图按用途保留必要图层,避免把无关业务字段一起带出。
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
基于 Web Canvas 与 IndexedDB 的市政道路设施平面图编辑器
https://blog.luozili.work/posts/municipal-road-canvas-indexeddb-editor/ 部分信息可能已经过时
相关文章 智能推荐
1
市政道路占道施工的多维时空冲突检测设计
技术分享 将日期、时间、道路区段与交通资源组合成多维模型,实现市政道路占道施工的冲突检测。
2
桌面端混合应用发布实践:Vite 相对路径如何解决 Electron 在 file 协议下的黑白屏
技术分享 排查 Vite 单页应用在 Electron 的 file 协议下出现黑白屏的原因,并给出相对路径与受限自定义协议两种修复方案。
3
市政道路 RTK 数据导入:断链映射与缓和曲线重建
RTK 与工程几何 解析市政道路测量 Excel/CSV 数据,建立连续桩号映射、重建缓和曲线,并对输入数据执行闭合差审计。
4
端侧轻量化大模型应用:基于多模块可选编译机制的离线语音识别构建工程设计
技术分享 使用 Gradle 依赖探测、动态源集与反射注册,在同一 Android 代码库中输出云端轻量版和端侧语音识别版。
5
物联网边缘计算的“空间感知”:无需敏感权限常开的传感器后台分析服务实践
技术分享 以 Android 前台服务整合加速度计、陀螺仪和接近传感器,构建设备姿态与在场状态判断,并控制功耗和权限边界。





