vega-geo 地理数据变换实战指南:从投影、GeoJSON 到密度等值线完整解析
2026/9/23 16:33:21 网站建设 项目流程

vega-geo 地理数据变换实战指南:从投影、GeoJSON 到密度等值线完整解析

【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega

导读

vega-geo是 Vega 可视化语法(vega 项目)中负责地理数据处理的核心包,为 Vega 数据流提供一组地理变换(transform),涵盖制图投影(projection)、GeoJSON 数据加工、经纬度投影为像素坐标、经纬网(graticule)生成,以及基于二维核密度估计(KDE)的等值线(isocontour)与热力图(heatmap)可视化。阅读完本文,你将掌握vega-geo全部 8 个公开变换与 1 个内部变换的用途、参数、实现原理,并能用真实仓库中的示例规格(如 contour-plot.vg.json、world-map.vg.json)快速搭建地图与密度可视化。

vega-geo 包概览:为 Vega 数据流提供的地理变换集合

vega-geo(packages/vega-geo/README.md)的定位是 "Geographic data transforms for Vega dataflows"。它不直接渲染图形,而是作为 Vega 数据流(dataflow)中的变换算子,把地理数据加工成下游 mark 可以直接消费的形式(如 SVG path 字符串、shape 实例、GeoJSON 几何、Canvas 位图等)。

从包的入口文件 packages/vega-geo/index.js 可以看到它导出 10 个变换:

变换类型一句话用途
contour公开变换(已废弃)基于核密度估计生成等值线,功能已被kde2d+isocontour取代
geojson公开变换把经纬度点或 GeoJSON 要素合并为单个 FeatureCollection
geopath公开变换把 GeoJSON 要素映射为 SVG path 字符串
geopoint公开变换把 (longitude, latitude) 投影为 (x, y) 像素坐标
geoshape公开变换为数据项附加一个 shape 绘制实例(延迟到渲染期执行投影)
graticule公开变换生成经纬网 GeoJSON 参考网格
heatmap公开变换把栅格数据渲染为 Canvas 位图
isocontour公开变换基于栅格数据生成等值线 GeoJSON
kde2d公开变换对点数据执行二维核密度估计,输出栅格矩阵
projection内部变换维护制图投影实例,供其他地理变换引用

其中projection在 README 中被明确标注为internal transform,即普通用户不会直接在数据流中书写它,而是通过 Vega 规格顶层的projections声明来配置(详见下文)。

从 packages/vega-geo/package.json 可以看到该包的依赖关系,这直接反映了它的实现基石:

  • d3-geo:提供各类投影构造器、geoPathgeoGraticule
  • d3-array/d3-color:分别用于极值计算与热力图像素颜色解析;
  • vega-dataflow:提供Transform基类与脉冲(pulse)机制;
  • vega-projection:提供投影注册表与getProjectionPath
  • vega-canvas:用于热力图离屏 Canvas 位图创建;
  • vega-statistics:提供密度估计相关统计工具。

Projection 内部变换:一切地图变换的地基

投影类型注册表

Projection变换(packages/vega-geo/src/Projection.js)负责"维护一个制图投影实例"。它的核心逻辑是:根据type参数从vega-projection包的注册表中取到对应构造器,创建投影,并把规格中出现的投影属性逐一设置到该实例上。

投影注册表位于 packages/vega-projection/src/projections.js,Vega 内置的投影类型全部来自d3-geo,外加d3-geo-projectionmollweide

albersalbersUsaazimuthalEqualAreaazimuthalEquidistantconicConformalconicEqualAreaconicEquidistantequalEarthequirectangulargnomonicidentitymercatormollweidenaturalEarth1orthographicstereographictransverseMercator

从源码看,类型名是大小写不敏感的((type || 'mercator').toLowerCase()),默认为mercator;若传入未注册的类型,会抛出Unrecognized projection type错误。每个创建的投影都会被附上type属性,并预绑定一个geoPath().projection(p)的路径生成器p.path——这正是GeoPath/GeoShape变换通过getProjectionPath(proj)(见 packages/vega-projection/src/projections.js)直接取用的对象。

投影属性:直接透传 d3-geo

projectionProperties数组(packages/vega-projection/src/projections.js)定义了 Vega 透传给投影实例的标准属性,覆盖 d3-geo 常规属性与 d3-geo-projection 扩展属性:

  • 常规:clipAngleclipExtentscaletranslatecenterrotateparallelsprecisionreflectXreflectY
  • 扩展:coefficientdistancefractionlobesparallelradiusratiospacingtilt

这些属性在 docs/docs/projections.md 中有完整说明,关键几点:

  • scale:默认值因投影而异,且不同投影之间的 scale 数值不可直接比较;
  • translate:默认[480, 250],把 (0°, 0°) 置于 960×500 区域中心;
  • rotate:两或三个元素[λ, φ, γ],依次对应 yaw、pitch、roll;
  • pointRadius:绘制 GeoJSONPoint/MultiPoint时的默认点半径(像素),默认 4.5;
  • precision:自适应重采样的 Douglas–Peucker 阈值,默认 √0.5 ≈ 0.70710;
  • fit/extent/size:让投影自动适配给定 GeoJSON 数据(见下节)。

fit 自动适配与 collectGeoJSON

Projection变换还支持fit参数——给定 GeoJSON 数据后自动计算translatescale,使数据正好落入指定的extentsize像素区域(packages/vega-geo/src/Projection.js 中的fit()函数:extent存在则用fitExtent,否则用fitSize)。

fit的输入会被collectGeoJSON()归一化:接受单个 Feature/FeatureCollection,也接受数组(数组中的FeatureCollection会被展开,裸几何对象会被包装成Feature),最终合并为一个FeatureCollection。这一能力有测试直接覆盖——packages/vega-geo/test/projection-test.js 中验证了orthographic投影在size: [500, 500]fit传入经纬网与Sphere时,scale()变为 250、translate落在 (250, 250),并验证了fit输入包含null数据时依然稳健。

fitgeojson变换的搭配是常见用法:先用geojson变换把散落的经纬度点合并成 GeoJSON,再通过信号把结果喂给投影的fit,实现"让地图自动缩放到数据范围"。

GeoJSON 变换:把点与要素合并为 FeatureCollection

geojson变换(packages/vega-geo/src/GeoJSON.js)把"经纬度点数组"或"既有 GeoJSON 要素"整合进一个FeatureCollection,输出作为该变换的 value(可通过signal绑定为信号值)。它最典型的用途就是给投影的fit参数供数。

参数(定义见 docs/docs/transforms/geojson.md):

属性类型说明
fieldsField[]分别包含经度、纬度值的两个字段
geojsonField包含 GeoJSON Feature 对象的字段;当fieldsgeojson均未指定时,默认使用恒等函数(把输入数据对象本身当作 GeoJSON Feature)

从源码实现(packages/vega-geo/src/GeoJSON.js)可以看到处理细节:若提供fields,遍历数据时会对每个点做数值校验((x = +x) === x排除 NaN/字符串),把有效的[lon, lat]点收集进points,最后追加一个MultiPoint要素到特征数组;若提供geojson字段,则把每个 tuple 取出的对象作为独立要素压入数组。最终输出{type: 'FeatureCollection', features: ...}

官方用法示例(docs/docs/transforms/geojson.md):把经纬度字段合并并通过signal绑定:

{ "type": "geojson", "fields": ["longitude", "latitude"], "signal": "geodata" }

随后在投影声明中引用:

"projections": [ { "name": "proj", "type": "mercator", "fit": {"signal": "geodata"} } ]

GeoPath 与 GeoShape:两种 GeoJSON 渲染策略

GeoPath:直接产出 SVG path 字符串

geopath变换(packages/vega-geo/src/GeoPath.js)把 GeoJSON 要素映射为SVG path 字符串,适用于pathmark。它依赖 d3-geo 的geoPath完成投影与路径生成。

参数(docs/docs/transforms/geopath.md):

属性类型默认说明
projectionString投影名;不指定则不对 GeoJSON 做投影
fieldField恒等存放 GeoJSON 数据的字段;不指定则把整个数据对象当作 GeoJSON
pointRadiusNumber|Expr绘制Point/MultiPoint时的默认半径(像素),支持表达式按要素属性动态设定
asString"path"输出字段名

用法:

{ "type": "geopath", "projection": "projection" }

源码实现要点:变换会缓存this.value中的 path 生成器;当参数变化(_.modified())时重建路径并reflow全部数据;否则仅处理新增/修改的 tuple。pointRadius在遍历前后会被临时设置并恢复(initPath),保证多轮处理互不干扰。输出写入t[as],并声明modifies元数据。

GeoShape:延迟到渲染期的 shape 实例

geoshape变换(packages/vega-geo/src/GeoShape.js)与geopath功能类似,但不立即生成 SVG 字符串,而是为每个数据项附加一个"形状生成器"(shape instance),渲染时再发出绘制命令。由于投影被延迟到渲染阶段,在 Canvas 渲染的动态地图场景下通常性能更好。它专用于shapemark。

参数(docs/docs/transforms/geoshape.md)与geopath几乎一致,默认输出字段为"shape"

属性类型默认说明
projectionString投影名;不指定则不做投影
fieldField"datum"存放 GeoJSON 数据的字段
pointRadiusNumber|ExprPoint/MultiPoint默认半径
asString"shape"输出字段名

源码中,shapeGenerator()(packages/vega-geo/src/GeoShape.js)返回的 shape 函数还带context方法,把渲染上下文透传给底层geoPath,这正是 Canvas 直接绘制的基础。其metadata同时声明了modifiesnomod

如何选择

按官方文档(docs/docs/transforms/geopath.md、docs/docs/transforms/geoshape.md)的说明:需要立即拿到 SVG path 字符串(如静态导出、SVG 渲染)用geopath;Canvas 动态地图优先geoshape。world-map.vg.json 就是一个典型:世界地图与国家边界都使用geoshape+shapemark,经纬网同样如此。

GeoPoint 变换:经纬度点到像素坐标

geopoint变换(packages/vega-geo/src/GeoPoint.js)把 (longitude, latitude) 对投影为 (x, y) 像素坐标,适合把"散点式地理位置"放到 symbol 等 mark 上。

参数(docs/docs/transforms/geopoint.md):

属性类型必填默认说明
projectionString投影名
fieldsField[]依次为经度、纬度字段
asString[]["x", "y"]输出字段名

用法:

{ "type": "geopoint", "projection": "myprojection", "fields": ["lon", "lat"] }

实现要点(packages/vega-geo/src/GeoPoint.js):调用proj([lon(t), lat(t)]),若投影返回结果则写入t[x]/t[y],否则置为undefined——这一分支保证了超出投影范围(如裁剪圈外)的点不会产生无效坐标。geopoint.vg.json(docs/docs/transforms/geopoint.vg.json)展示了它与albersUsa投影配合,把美国州府经纬度(lon/lat字段)投影为x/y并绘制 symbol 的完整流程。

Graticule 变换:生成经纬网参考网格

graticule变换(packages/vega-geo/src/Graticule.js)生成地图经纬网:一组均匀的经线(meridians)与纬线(parallels),用于展示投影变形。它内部直接使用 d3-geo 的geoGraticule()生成器,输出一个包含单个 GeoJSON 对象的新数据流,可交给geopath/geoshape绘制。

默认网格:±80° 纬度之间每 10° 一条经纬线,极地区域经线每 90° 一条。

参数(docs/docs/transforms/graticule.md):

属性类型默认说明
extentMajorArray[]主要经纬网的边界(两元素坐标数组)
extentMinorArray[]次要经纬网的边界
extentArray[]同时设置 major 与 minor 边界
stepMajorNumber[][90, 360]主要经纬网步长(度)
stepMinorNumber[][10, 10]次要经纬网步长(度)
stepNumber[]同时设置 major 与 minor 步长
precisionNumber2.5经纬网精度(度)

用法:

{"type": "graticule", "stepMinor": [15, 15]}

源码实现(packages/vega-geo/src/Graticule.js)非常直白:遍历参数对象,凡是对应geoGraticule生成器上存在同名函数(如extentstepprecision)就调用设置;然后执行gen()得到新的 GeoJSON,用replace/ingest更新数据流中的唯一数据对象。

密度可视化管线:KDE2D → Isocontour / Heatmap

从 Vega 5.8 起,vega-geo提供了一套"点数据 → 密度栅格 → 等值线/热力图"的完整管线,官方文档(docs/docs/transforms/kde2d.md、docs/docs/transforms/isocontour.md、docs/docs/transforms/heatmap.md)统一用 contour-plot 示例 演示。这套管线尤其适合海量 2D 点数据的可缩放表达。

KDE2D:二维核密度估计

kde2d变换(packages/vega-geo/src/KDE2D.js)对输入点数据执行二维核密度估计,输出一个或多个栅格矩阵({width, height, values, scale, translate, ...}),供isocontour/heatmap消费。

参数:

属性类型必填默认说明
sizeNumber[]密度估计的空间范围[width, height](像素)
xFieldx 坐标字段
yFieldy 坐标字段
weightField数据点权重;不指定则每个点权重为 1
groupbyField[]分组字段;不指定则全部数据作为一组
cellSizeNumber4空间近似粒度:默认 4 意味着宽高各缩减 2 倍;设为 1 则输出栅格与size完全一致
bandwidthNumber[]自动KDE 核带宽(像素);两元素数组分别指定 x/y 带宽,单元素同时作用于两轴;小于 0 或未指定时自动确定
countsBooleanfalsefalse输出概率估计,true输出平滑计数
asString"grid"输出栅格字段名

源码要点(packages/vega-geo/src/KDE2D.js):partition()groupby字段把数据切成若干组(组内附带dims记录分组键值,输出时回填到 tuple 上);每组独立执行density2D()估计。当输入未变化且参数未修改时,直接StopPropagation短路,避免无谓重算。

Isocontour:从栅格到等值线 GeoJSON

isocontour变换(packages/vega-geo/src/Isocontour.js)以一个或多个数值栅格为输入,生成等值线(isoline)GeoJSON 几何流,可用geoshape/geopath绘制。每条等值线是常数值的等值面边界。

参数(docs/docs/transforms/isocontour.md):

属性类型默认说明
fieldField恒等存放栅格数据的字段;不指定则数据对象本身视为栅格
thresholdsNumber[]显式等值线阈值数组;一旦指定,levels/nice/resolve/zero均被忽略
levelsNumber期望的等间距等值线数量(有thresholds时忽略)
niceBooleanfalse是否把阈值对齐到"友好"整数值(可能使实际条数偏离levels
resolveString"independent"多栅格阈值求解方式:'independent'每个栅格单独计算;'shared'所有栅格共用一套阈值
zeroBooleantrue阈值是否包含 0 作为基线
smoothBooleantrue是否用线性插值平滑等值线多边形(密度估计场景下忽略)
scaleNumber|Number[]输出坐标缩放(可传[sx, sy]),用于匹配坐标空间
translateNumber[]输出坐标平移[dx, dy]
asString"contour"输出字段名(设为null则直接输出几何对象)

源码实现(packages/vega-geo/src/Isocontour.js)值得展开:每个输入栅格通过contour.size([w, h])(grid.values, thresholds)生成 GeoJSON 等值线;随后transformPaths()依据栅格自带的scale/translate(或显式参数)对坐标做仿射变换——x' = (x - x1) * sx + tx,并在sx * sy < 0(坐标翻转)时反转环的缠绕方向以维持几何正确性;最后用rederive()把源数据属性复制到输出 tuple,保证下游可以按分组字段着色。resolve: 'shared'时,阈值基于所有栅格最大值的全局统计计算。

Heatmap:把栅格渲染为 Canvas 位图

heatmap变换(packages/vega-geo/src/Heatmap.js)把输入数值栅格渲染为 Canvas 位图(bitmap),输出可用imagemark 绘制。位图尺寸与栅格一致,实际显示尺寸由 image mark 的 width/height 决定。

参数(docs/docs/transforms/heatmap.md):

属性类型默认说明
fieldField恒等存放栅格数据的字段
colorColor|Expr"#888"像素颜色;表达式可读取$x$y$value$max字段
opacityNumber|Expr$value / $max像素不透明度;表达式同样可读取四个$字段
resolveString"independent"$max求解方式:每个栅格独立计算或全局共享一个最大值
asString"image"输出 Canvas 位图的字段名

源码实现(packages/vega-geo/src/Heatmap.js)很巧妙:为每个像素构造一个代理数据对象({$x, $y, $value, $max}),颜色/不透明度函数若不依赖单个像素(通过accessorFields检测是否引用$x/$y/$value/$max),则只计算一次并用constant()缓存,从而大幅优化逐像素开销;最终通过vega-canvas创建离屏画布、逐像素写入 RGBA(pix[k+3] = ~~(255 * opacity(obj)))。

官方示例一:密度热力图(docs/docs/transforms/heatmap.md):

{ "type": "kde2d", "x": "x_value", "y": "y_value", "size": [{"signal": "width"}, {"signal": "height"}], "as": "grid" }, { "type": "heatmap", "field": "grid", "color": "steelblue", "opacity": {"expr": "datum.$value / datum.$max"} }

官方示例二:数学函数热力图(不依赖输入栅格,直接用表达式逐像素填色):

{ "signals": [ {"name": "scale", "value": 0.05} ], "scales": [ { "name": "color", "type": "linear", "domain": [-1, 1], "range": {"scheme": "spectral"} } ], "data": [ { "name": "heatmap", "values": [{"width": 150, "height": 100}], "transform": [ { "type": "heatmap", "color": { "expr": "scale('color', sin(scale * (datum.$x + datum.$y)) * sin(scale * (datum.$x - datum.$y)))" }, "opacity": 1 } ] } ] }

Contour:已被取代的旧变换

contour变换(packages/vega-geo/src/Contour.js)在 README 中即被标注为deprecated(自 Vega 5.8 起,未来主版本可能移除)。它同时提供了 d3-contour 中contoursdensityContour的能力:要么直接对values数组(width×height 数值网格)算等值线,要么对输入点数据做核密度估计后生成等值线。

官方推荐迁移方案:改用更灵活的kde2d+isocontour(或kde2d+heatmap)组合。参数表(docs/docs/transforms/contour.md)如下,仅供理解遗留规格:

属性类型必填说明
sizeNumber[]等值线计算区域[width, height];提供values时为输入数据尺寸,做密度估计时为输出视图像素尺寸
valuesNumber[]直接给定数值网格;不指定则对输入点数据做密度估计
x/yField密度估计的像素坐标字段
weightField数据点权重字段
cellSizeNumber密度计算单元大小
bandwidthNumberKDE 带宽
smoothBooleantrue是否线性插值平滑
thresholdsNumber[]显式阈值数组(指定后忽略count/nice
countNumber期望等值线条数
niceBooleanfalse阈值是否对齐友好数值

从源码可见(packages/vega-geo/src/Contour.js),它实际上是"借用"了KDE2D.jsparamsIsocontour.jstransform坐标修正逻辑,内部结构完全可以被新管线替代。

端到端实战:contour-plot 示例拆解

仓库自带的 docs/examples/contour-plot.vg.json 把上述密度管线串成了一个完整可运行的 Vega 规格,数据来自 docs/data/cars.json。整个流程分四步:

  1. 数据过滤:剔除HorsepowerMiles_per_Gallon为空的行;
  2. 密度估计kde2dOrigin(产地)分组,把经scale('x', ...)/scale('y', ...)换算后的像素坐标作为 x/y,size取视图宽高信号,bandwidthcounts由交互控件驱动;
  3. 等值线isocontour读取grid字段,resolve跟随信号在independent/shared间切换,生成 3 层等值线;
  4. 渲染:三层 mark 各司其职——symbol画散点,imagemark 叠加heatmap生成的密度底色(颜色取自Origin的序数色标),pathmark 用geopath绘制等值线轮廓。

对应的标记/变换组合验证了本文所述的全部要点:heatmap输出喂给imageisocontour输出喂给geopath,而geopath在这里未指定投影field: "datum.contour"的等值线坐标已是像素空间,无需再投影)。

地图场景则可参考 docs/examples/world-map.vg.json:通过信号绑定投影typescalerotatecentertranslate,配合graticule变换生成的经纬网与geoshape绘制的国家边界,实现了可交互切换投影类型的世界地图——这也与 packages/vega-geo/test/projection-test.js 中验证的投影适配逻辑互相印证。

源码地图与扩展指引

如果你要在 Vega 数据流中二次开发或调试,以下文件值得优先阅读:

  • 变换入口与导出:packages/vega-geo/index.js;
  • 每个变换的Definition都声明了类型元数据(typemetadataparams),这是 Vega 解析器校验规格参数的依据,分布在 packages/vega-geo/src/ 各文件;
  • 工具函数:packages/vega-geo/src/util/contours.js、packages/vega-geo/src/util/density2D.js、packages/vega-geo/src/util/quantize.js;
  • GeoJSON 类型常量:packages/vega-geo/src/constants.js;
  • 投影注册与属性透传:packages/vega-projection/src/projections.js;
  • 测试用例:packages/vega-geo/test/projection-test.js 与 packages/vega-geo/test/geojson-test.js。

扩展自定义投影也很简单:vega-projection提供了projection(name, constructor)注册接口(见 packages/vega-projection/src/projections.js),注册后即可在规格的projections中直接引用;vega-projection-extended包则一次性注册 d3-geo-projection 的全部扩展投影。更完整的投影类型与属性清单,见 docs/docs/projections.md。

小结

vega-geo用一套小而精的变换集合覆盖了 Vega 地理可视化的全部基础能力:projection维护投影实例并支持fit自动适配;geojsonfit聚合数据;geopath/geoshape/geopoint分别面向 SVG 路径、Canvas shape 与散点三种渲染需求;graticule提供地图参考网格;kde2disocontourheatmap组成现代的密度等值线与热力图管线,而旧有的contour已进入废弃状态。理解这些变换的参数语义与底层实现(大多直接构建在 d3-geo 之上),你就能在 Vega 规格中组合出从世界地图到密度等高线的各类地理可视化。

【免费下载链接】vegaA visualization grammar.项目地址: https://gitcode.com/gh_mirrors/ve/vega

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询