☰
Canvas+geojson.io实现交互式地图:从坐标投影到自由绘制
2026/10/14 18:01:51 网站建设 项目流程

简介:一款面向Web前端开发者的地图可视化示例资源,基于H5 Canvas与ECharts构建,支持结合geojson.io工具自由编辑GeoJSON数据,帮助解决从地理数据到交互式地图的完整落地问题。RAR压缩包约1.1MB,目前已有1630人学习下载。资源演示了Canvas基础绘图API的应用,包括fillRect、strokeRect、beginPath、moveTo、lineTo等图形绘制方法,并完成地理坐标点转换、边界线条绘制与区域填充,同时集成ECharts地图系列,实现缩放、拖拽、点击省份下钻等交互效果。通过调整zoomOnMouseWheel、Roam等配置项可控制地图层级,也可在监听事件中动态加载下级城市GeoJSON数据,形成逐级展开的地图浏览体验。此外,还展示了将业务统计数据与地理区域关联的方法,利用颜色深浅、图形大小等视觉元素直观呈现指标差异,适用于地理数据分析与业务监控场景,也可作为课程设计或实战项目的参考。

1. 拿到地图需求先别忙引 SDK,Canvas 加 geojson.io 这条路能走得更顺

拿到一个要在页面上放地图的需求,第一反应通常是去引现成的地图 SDK。但当你需要叠加一块自定义地块、让用户自己在图上描边界、再把成果导回业务系统,地图 SDK 的事件模型和图层封装反而会跟业务打架。H5 Canvas 实现地图,结合 geojson.io 可自由绘制地图,是一条更贴近业务的落地路线:用 geojson.io 组织地理数据,用 Canvas 负责渲染和交互,画出来的成果还能再回到 geojson.io 二次校对。

这个方案适合谁:前端要维护一张内部地图,数据量不大但交互要求具体,比如厂房分区、地块边界、管网草图,不希望为了一个点选功能去改现成地图的配置文件。相比引入第三方地图,Canvas 的地图能完全控制样式、性能和交互,也更容易和现有的自绘工具无缝对接。

本文不打算讲大而全的地图引擎,只讲从 geojson.io 到 Canvas 的普通工程落地。我会按顺序拆开坐标投影、平移缩放、自由绘制、常见坑和进阶优化,每一段都有可以直接照抄的代码和参数说明。

2. 地理坐标到 Canvas 像素:先想清楚三件事,再写第一行代码

Canvas 画地图的原理一句话就能说清:把 GeoJSON 里记录的经纬度坐标,换算成屏幕上的像素坐标,然后像画普通平面图一样填充和描边。难的地方不在绘图 API,而在数据结构和坐标换算。这一章先解决“坐标系怎么搭”,再用最小代码跑通第一个多边形。

2.1 在 geojson.io 上造一份测试数据:FeatureCollection 最少需要哪些字段

打开 geojson.io,在页面上画一个多边形,另存为 GeoJSON 文件后,你会得到类似下面的结构。以后你从 geojson.io 导出的数据,绝大多数也是这样:

{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "name": "地块A", "id": 1 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 123.456, 45.678 ], [ 123.458, 45.678 ], [ 123.458, 45.681 ], [ 123.456, 45.681 ], [ 123.456, 45.678 ] ] ] } } ] }

注意这里的坐标顺序是[经度, 纬度],不是[纬度, 经度]。这是新手写 Canvas 地图最容易翻车的地方,后面会专门讲。coordinates是一个三维数组:最外层是“环”,环里是“点”。对 Polygon 来说,coordinates[0]是外环,后面的元素是内洞,如果你在 geojson.io 里画的是带镂空的复杂地块,就会看到多个环。

还有人会忽略properties字段。它不参与渲染,但参与业务判断:地块名称、颜色、可否编辑、点击事件要回传的数据,都应该放到这里。如果数据是从 geojson.io 手工描出来的,顺手把名称写进去,省得前端再根据文件名猜含义。

2.2 投影选型:小区地块图不需要墨卡托,局部线性投影最省事

经纬度是球面坐标,Canvas 是平面像素坐标,中间必须有一次投影。网上很多教程一上来就让你实现 Web 墨卡托,但如果你只是画一个小园区、一块工地、一条巡检路线,完全不需要。墨卡托公式里x = lng * cos(lat),在局部范围内,cos(lat)几乎不变,再加上一个固定比例系数即可。

我遇到这类需求,一律先计算数据的最小外接矩形,再做线性映射。这样的好处有三个:第一,代码短,不用维护正经投影库;第二,对用户来说,看一个园区地块图不需要懂投影;第三,反算经纬度也很简单,点击、绘制都能直接对应回地理坐标。

function calcBounds(featureCollection) { let minLng = Infinity; let minLat = Infinity; let maxLng = -Infinity; let maxLat = -Infinity; featureCollection.features.forEach(f => { const coords = f.geometry.coordinates; coords.forEach(ring => { ring.forEach(([lng, lat]) => { if (lng < minLng) minLng = lng; if (lat < minLat) minLat = lat; if (lng > maxLng) maxLng = lng; if (lat > maxLat) maxLat = lat; }); }); }); return { minLng, minLat, maxLng, maxLat }; }

这个函数遍历所有 Polygon 的环,把经纬度的极值收出来。你没看错,先[lng, lat]解构再比较,顺序从一开始就定死。如果你的数据里还有 LineString 和 Point,记得把它们的coordinates也用同样的方式展开,否则会漏掉边界,导致图形画到画布外面。

2.3 两个核心函数:geoToScreen 和 screenToGeo 要成对出现

只做“经纬度转屏幕”是单向的,做交互的时候还需要“屏幕坐标转经纬度”。拖拽、缩放、点选、画新地块,全靠反投影。这两组函数要放在一起维护,参数一致,逻辑互逆。

function buildTransform(bounds, width, height, padding = 40) { const geoW = bounds.maxLng - bounds.minLng; const geoH = bounds.maxLat - bounds.minLat; const canvasW = width - padding * 2; const canvasH = height - padding * 2; const scale = Math.min(canvasW / geoW, canvasH / geoH); const offsetX = (width - geoW * scale) / 2; const offsetY = (height - geoH * scale) / 2; return { geoToScreen(lng, lat) { return { x: (lng - bounds.minLng) * scale + offsetX, y: (bounds.maxLat - lat) * scale + offsetY }; }, screenToGeo(x, y) { return { lng: (x - offsetX) / scale + bounds.minLng, lat: bounds.maxLat - (y - offsetY) / scale }; } }; }

这里有几个参数要解释:padding是画布四周留白,避免地块贴边,默认 40 像素;scale是每经度/纬度对应的像素数,因为地理数据长宽比例和画布不一定一致,所以同时限制宽和高,取较小值。geoToScreen里 y 的计算用了bounds.maxLat - lat,因为 Canvas 的 y 轴向下,而纬度是向上增长的,不减就会上下颠倒。

screenToGeo是它严格的反函数。后面做鼠标点选、手绘多边形,都要借助它把屏幕坐标还原成经纬度。这一对函数是整个 Canvas 地图的基础设施,建议单独放在一个模块里,不要散到各个事件函数里写。

2.4 最小渲染代码:把第一个 Polygon 画到 Canvas 上

有了 transform,画一个 Polygon 就是三个步骤:beginPath、moveTo/lineTo、fill/stroke。

function drawGeoJSON(ctx, features, transform) { features.forEach(feature => { const geometry = feature.geometry; if (geometry.type === 'Polygon') { geometry.coordinates.forEach(ring => { ctx.beginPath(); ring.forEach(([lng, lat], index) => { const p = transform.geoToScreen(lng, lat); if (index === 0) { ctx.moveTo(p.x, p.y); } else { ctx.lineTo(p.x, p.y); } }); ctx.closePath(); ctx.fillStyle = 'rgba(64, 128, 255, 0.25)'; ctx.strokeStyle = '#2f6fed'; ctx.lineWidth = 2; ctx.fill('evenodd'); ctx.stroke(); }); } }); }

注意ctx.fill('evenodd')这句。GeoJSON 的多边形带内洞时,Canvas 默认的 nonzero 填充规则容易把洞填成实心,用evenodd能直接支持环内挖空。如果你的地块有镂空结构,这个参数一定要保留。

加载数据的入口也一并给你:

fetch('./map.geojson') .then(res => res.json()) .then(fc => { const bounds = calcBounds(fc); const transform = buildTransform(bounds, canvas.width, canvas.height); drawGeoJSON(ctx, fc.features, transform); });

这段代码的问题是“一次性铺满画布”,还没有缩放和平移。下一章要解决的就是这个问题。

3. 平移和缩放:让 Canvas 地图从静态图变成可操作地图

静态画一个多边形不叫地图,得能拖、能滚轮缩放、能点击选中。Canvas 没有 DOM 元素的事件模型,所以这些交互全部要自己实现。核心思路不复杂:地图是一个由“状态”驱动的渲染函数,任何交互只改状态,然后清空画布重新画。

3.1 为什么不能像普通 DOM 那样给图形绑事件

很多第一次做 Canvas 地图的人会问:能不能给某个地块加一个 click 监听器?答案是不能。Canvas 画完的图形只是一堆像素,浏览器不知道这些像素属于哪个 Polygon。所以事件绑在 Canvas 本身上,再用屏幕坐标反算回经纬度,去数据列表里做命中检测。

这套机制下,地图状态至少要包含两项:当前视野中心经纬度和一个缩放比例。代码里的state是一切交互的源头:

const state = { centerLng: 123.457, centerLat: 45.679, scale: 1000 };

scale表示每一经度/纬度对应多少个像素,数值越大,地图被放得越大。下面所有鼠标事件都围绕这三个变量展开。

3.2 拖拽平移:通过像素位移反算中心点变化

先实现拖拽。mousedown 记录按下位置,mousemove 计算位移,把位移换算成经纬度变化,更新state后重绘。

let dragging = false; let last = null; canvas.addEventListener('mousedown', e => { dragging = true; last = { x: e.clientX, y: e.clientY }; canvas.style.cursor = 'grabbing'; }); window.addEventListener('mousemove', e => { if (!dragging) return; const dx = e.clientX - last.x; const dy = e.clientY - last.y; state.centerLng -= dx / state.scale; state.centerLat += dy / state.scale; last = { x: e.clientX, y: e.clientY }; render(); }); window.addEventListener('mouseup', () => { dragging = false; canvas.style.cursor = 'grab'; });

为什么 centerLng 是减,centerLat 是加?向右拖拽时,地图内容要跟着向右移动,视野中心其实在向左移动,所以经度减。向下拖拽时,视觉上地块往下走,视野中心在往北走,所以纬度加。刚开始容易反,记住“内容动、中心反着动”就行。

render 函数里用 state 计算当前视野对应的 bounds,然后调用转换函数。常见的做法是在 render 里临时生成一个 transform,而不是复用之前那个固定 bounds 的 transform。

3.3 滚轮缩放:以鼠标位置为中心,缩放时不能让注视点漂走

滚轮缩放的第一个坑是页面跟着滚。必须给 wheel 事件加preventDefault,并且监听器的passive必须设为 false,否则浏览器会忽略你的阻止操作。

第二个坑是缩放中心。用户把鼠标放在某个地块上滚轮,期望的是这个地块保持不动,向四周放大。实现方法是:缩放前记录鼠标位置的经纬度,缩放后让这个经纬度还停留在鼠标位置。

canvas.addEventListener('wheel', e => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; const before = screenToGeo(mx, my); const factor = e.deltaY > 0 ? 0.9 : 1.1; state.scale *= factor; const after = screenToGeo(mx, my); state.centerLng += before.lng - after.lng; state.centerLat += before.lat - after.lat; render(); }, { passive: false });

这里screenToGeo依赖当前的 state,需要你在转化函数里动态计算。缩放因子没有写死成 2 倍,是因为 1.1 倍左右的手感在连续滚轮下比较顺,不会一帧跳得太夸张。deltaY方向在不同系统下可能相反,如果发现滚轮方向和预期相反,把 0.9 和 1.1 互换即可。

3.4 点选地块:屏幕坐标反投影后做射线法命中检测

点选是 Canvas 地图最常见的交互。步骤是:click 事件拿到屏幕坐标,screenToGeo变成经纬度,遍历 features 判断这个点是否在 Polygon 内部。

function hitTest(geoPoint, features) { for (const feature of features) { const geometry = feature.geometry; if (geometry.type !== 'Polygon') continue; if (pointInPolygon(geoPoint, geometry.coordinates[0])) { return feature; } } return null; } function pointInPolygon(point, ring) { let inside = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { const [lng1, lat1] = ring[i]; const [lng2, lat2] = ring[j]; if ((lat1 > point.lat) !== (lat2 > point.lat) && point.lng < (lng2 - lng1) * (point.lat - lat1) / (lat2 - lat1) + lng1) { inside = !inside; } } return inside; }

这是标准的射线法,代码很短,但对绝大多数业务地图来说够用。点选命中后,可以把那块地的 fillStyle 改成高亮色再重绘,比如透明度从 0.25 提到 0.5。高亮逻辑不要写进 drawGeoJSON 里,可以让 drawGeoJSON 接受一个selectedId参数,在绘制每个 feature 前比较 properties.id。

这一章做完,一张 Canvas 地图已经能拖、能缩放、能点选。接下来进入标题里的核心环节:自由绘制。

4. 自由绘制:从 Canvas 画笔到 geojson.io 的可编辑闭环

标题里最值钱的两个字是“自由绘制”。它应该实现两个方向:一个方向是用户直接在页面 Canvas 上用鼠标画地块,画完导出成 GeoJSON;另一个方向是把 geojson.io 上画好的 GeoJSON 拿进项目,作为底图或编辑对象。这条路打通后,非技术人员也能维护地图数据。

4.1 先明确 geojson.io 在流程里扮演什么角色

geojson.io 是一个在线 GeoJSON 编辑器,你可以在上面加载地图、手绘点线面、实时查看属性,然后导出标准 GeoJSON 文件。它在整个流程里解决的是“数据录入和校对”问题:前端 Canvas 画完后导出的数据,粘贴回 geojson.io,可以快速检查边界是否闭合、经纬度是否越界、属性字段是否完整。

反过来,如果业务人员习惯先人工规划地块轮廓,也可以先在 geojson.io 里画一份初稿,导出文件给前端。前端 Canvas 只负责渲染和精修,不用从零实现一个完整的地理数据编辑器。两边都是标准 GeoJSON,格式一致,不存在兼容成本。

4.2 在 Canvas 上点击采点:手绘 Polygon 的交互状态机

在 Canvas 上自由绘制多边形,交互通常是这样的:单击添加顶点,移动鼠标预览当前折线,双击闭合。把顶点存成经纬度数组,这样导出时不需要二次转换。

let drawingPoints = []; let isDrawing = false; canvas.addEventListener('mousedown', e => { if (e.button !== 0) return; const rect = canvas.getBoundingClientRect(); const geo = screenToGeo(e.clientX - rect.left, e.clientY - rect.top); if (!isDrawing) { isDrawing = true; drawingPoints = []; document.body.style.cursor = 'crosshair'; } drawingPoints.push([geo.lng, geo.lat]); render(); }); canvas.addEventListener('dblclick', e => { e.preventDefault(); if (!isDrawing) return; if (drawingPoints.length >= 3) { const closed = [...drawingPoints, drawingPoints[0]]; const feature = { type: 'Feature', properties: { name: '临时地块' }, geometry: { type: 'Polygon', coordinates: [closed] } }; features.push(feature); } isDrawing = false; drawingPoints = []; render(); });

注意双击会先触发两次 mousedown,导致双击闭合时多出两个点。我通常会在 render 里把正在画的辅助图形单独渲染成虚线,并且双击后立刻把drawingPoints置空,这样多出的点也不会进入正式数据。如果你在业务里需要更精确的闭合,可以改成键盘快捷键闭合,比如按 Enter 完成绘制,按 Esc 取消,能彻底避开双击误触。

4.3 顶点拖拽编辑:让画错的边界有后悔药

只要允许用户自由画,就必然要允许用户改。顶点编辑是自由绘制里最实用的功能,原理和点选类似,只是检测目标从 Polygon 内部变成距离鼠标最近的顶点。

function findNearestVertex(x, y, features, maxDist = 8) { let best = null; let bestDist = maxDist; features.forEach(feature => { feature.geometry.coordinates.forEach(ring => { ring.forEach(point => { const p = geoToScreen(point[0], point[1]); const dist = Math.hypot(p.x - x, p.y - y); if (dist < bestDist) { bestDist = dist; best = { feature, ring, point }; } }); }); }); return best; }

这个函数的逻辑是遍历所有坐标点,找出离鼠标位置 8 像素以内的最近点,返回它所属的 feature 、所在环和这个点本身。拖拽时更新 point 数组里的经纬度即可。

function onDragVertex(e) { const rect = canvas.getBoundingClientRect(); const geo = screenToGeo(e.clientX - rect.left, e.clientY - rect.top); hit.point[0] = geo.lng; hit.point[1] = geo.lat; render(); }

这里最容易被忽略的是“拖拽顶点后 Polygon 可能不再闭合”。你在 4.2 节画完时手动把首尾坐标设成同一个值,但拖拽只改其中一个点,首尾就分开了。所以每拖拽一次,都应该把 ring 的最后一个点同步成第一个点的值,或者重新闭合一次。否则导出的 GeoJSON 会被 geojson.io 判定为非法 Polygon。

4.4 导出标准 GeoJSON 并回到 geojson.io 验证

绘制和编辑完成后,最终要生成能交给后端的 GeoJSON。导出函数很简单,但一定要过滤掉前端临时字段:

function exportFeatures(features) { const result = { type: 'FeatureCollection', features: features.map(f => ({ type: 'Feature', properties: { ...f.properties }, geometry: { type: f.geometry.type, coordinates: f.geometry.coordinates } })) }; return JSON.stringify(result, null, 2); }

用 Blob 下载文件,文件名建议带日期,方便回溯:

function downloadGeoJSON(features) { const blob = new Blob([exportFeatures(features)], { type: 'application/geo+json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `map_${new Date().toISOString().slice(0, 10)}.geojson`; a.click(); URL.revokeObjectURL(url); }

导出后不要直接丢给后端,先粘贴回 geojson.io 检查一遍。我会按固定清单验证:FeatureCollection 顶层字段齐全;每个 Polygon 的首尾坐标一致;经纬度范围合法;内环没有消失;properties 里没有残留不可序列化的对象。这一步虽然手动,但能帮你挡掉大量因为绘制意外产生的脏数据。

5. Canvas 地图常见问题排查与避坑:坐标序、高分屏与滚轮三座大山

这块内容我做了很多次才能用一句话概括:Canvas 地图百分之八十的 bug 不在绘图代码,而在坐标转换和设备像素比。下面几条都是高频场景,按“现象、原因、解决”的顺序写,方便你直接在代码里排查。

5.1 图形上下颠倒或左右镜像,像照镜子一样

现象:从 geojson.io 导出的同一份数据,在别人地图里显示正常,在你的 Canvas 里却是倒过来的。

原因:在geoToScreen里把纬度直接赋值给 y,忘了 y 轴方向处理。Canvas 下边缘是屏幕下方,而纬度高对应地理上方,不做反转就会出现镜像。

解决:把 y 计算公式改成(bounds.maxLat - lat) * scale + offsetY,让高纬度映射到画布上方。左右镜像同理,检查一下是不是把 lng 和 lat 的顺序填反了。

5.2 多边形填充区域奇怪,出现撕裂或莫名镂空

现象:一个矩形地块画出来却是交叉线条,或者中间出现一块空洞。

原因:GeoJSON 的 Polygon 坐标是完整的首尾闭合环,如果你的 ring 数组没有闭合,Canvas 的 closePath 虽然会自动连回起点,但某些 fill 规则下会生成自交区域,导致填充异常。另一个原因是 fill 默认规则是 nonzero,遇到多环 Polygon 时洞的判定和 GeoJSON 规范不一致。

解决:绘制时统一用ctx.fill('evenodd');数据结构上保证 ring 数组第一个点和最后一个点完全相同。导出前也做一个闭合校验,自动把最后一点覆盖为第一点。

5.3 高分屏上 Canvas 模糊,地图边缘发虚

现象:地图在普通屏幕上清晰,在 Retina 屏上线条发虚,文字也不锐利,像被拉伸过。

原因:Canvas 的绘图缓冲区尺寸没有跟随设备像素比。CSS 里写 100% 宽度,Canvas 默认 buffer 只有 CSS 像素大小,高分屏会用多个物理像素显示一个绘图像素,自然模糊。

解决:用 devicePixelRatio 放大缓冲区,同时用 context.scale 补偿:

function setupCanvas(canvas) { const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); return ctx; }

注意事件坐标仍然按 CSS 像素算,不要拿 buffer 坐标区处理鼠标位置。否则点击位置会和绘制位置差一截,这在高分屏上特别明显。

5.4 滚轮缩放时页面跟着滚动,或者缩放方向反了

现象:在地图上滚轮,原地不动的是 Canvas,页面却上下滚动,或者滚轮放大变成了缩小。

原因:没有在 wheel 监听器里调用preventDefault。另外浏览器对 wheel 监听器的默认 passive 行为在不同版本上有差异,早期 Chrome 会忽略 passive 事件里的 preventDefault。

解决:把监听器的第三个参数写成{ passive: false },在回调第一行执行e.preventDefault()。缩放方向问题只需调整 deltaY 判断符号,不要盲目改 factor;先在代码里打印e.deltaY的值,确认在系统里到底是正数还是负数。

5.5 数据量增大后拖拽明显卡顿,每一帧都像掉水里

现象:地图要素从几百个涨到几千个以后,拖拽和缩放掉帧,CPU 占用高。

原因:render 函数里每次重绘都把所有坐标从经纬度转成屏幕坐标,再全部走一遍路径生成。这个转换和路径构建本身是计算密集操作,在普通笔记本上几千个 Polygon 就会卡。

解决:分两步走,先做坐标预处理,把经纬度坐标投影到以地图中心为原点的“世界像素坐标”,拖拽缩放时只对世界坐标做整体偏移,不重新投影;再做视口裁剪,render 前先判断每个 feature 的最小外接矩形是否和当前视野相交,不相交的直接跳过。这两项优化通常能把帧率拉回 60fps。

6. 进阶技巧:把 Canvas 地图从最小可用带到业务量级的几个习惯

当前面的代码能跑通,下一步要解决的是“长期维护”和“规模”两个问题。我个人的进阶顺序不是去换一个重型地图库,而是保持 Canvas 这套自绘方案,然后把渲染和数据处理打磨成可复用的模块。

第一个习惯:把投影函数和渲染函数彻底分离。GeoJSON 数据和 Canvas 像素中间永远隔着一个 transform 对象,transform 只负责 map 到画布,drawGeoJSON 只负责绘图,不关心坐标是怎么来的。后期如果要支持墨卡托投影,只需要换一个 transform 实现,绘图代码一行都不用改。这个边界画清楚,项目才能长期演进。

第二个习惯:预处理坐标到世界坐标系。加载数据后,立刻用某个固定缩放级别把经纬度算成世界像素坐标,存进 feature 的缓存字段。交互时移动的是视野起点,绘制时给 canvas 设置一个整体 transform,省去每条路径反复调 geoToScreen。数据量到万级,这个优化效果立竿见影。

第三个习惯:用 Path2D 缓存复杂 Polygon。Canvas 2D 的 Path2D 对象可以预先构建路径,重绘时不重复执行 moveTo、lineTo 序列。我在一个几千个地块的内部项目里这样做过,第一次渲染会慢一点,但后续每次 render 都只做 fill 和 stroke,拖拽帧率明显提升。

const pathCache = new Map(); function getPath(feature) { if (pathCache.has(feature.properties.id)) { return pathCache.get(feature.properties.id); } const path = new Path2D(); feature.geometry.coordinates.forEach(ring => { path.moveTo(ring[0][0], ring[0][1]); ring.forEach(([lng, lat]) => path.lineTo(lng, lat)); path.closePath(); }); pathCache.set(feature.properties.id, path); return path; }

但这个缓存只对状态不变的地块有效。如果用户拖拽顶点改了几何坐标,一定要同步更新 pathCache,否则画面上会一直渲染旧路径。我习惯在每次拖拽结束后删除对应地块的缓存条目,而不是原地修改,避免索引错乱。

第四个习惯:数据验证前置。Canvas 地图再流畅,喂进来的 GeoJSON 是脏的也白搭。现在拿到数据后,我不会直接渲染,而是先写一个验证脚本跑一遍,检查坐标顺序、Polygon 闭合、经纬度范围、FeatureCollection 结构。这一步可以在 node 环境里跑,也可以直接粘贴回 geojson.io 检查,但一定要在开发早期做一次,不要等问题在下游爆出来。

最后说个我踩过不少次的教训:Canvas 地图项目里,需求永远比技术细节变得快。今天要求的可能是“能拖能点”,下个月就会变成“要手绘多边形、要顶点编辑、要导出 geojson”。如果一开始就把坐标转换和交互状态分开,后面每次新增功能都只是加一种状态和一种渲染分支,而不是重构整个模块。希望这篇笔记能帮你把 H5 Canvas 地图这条路走得更稳,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询