d3 地理投影完全指南:从 projection(point) 到 fitExtent 与自定义投影的完整实践
发布时间:2026/9/5 16:50:45来源:尧图网络
d3 地理投影完全指南从 projection(point) 到 fitExtent 与自定义投影的完整实践【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3本篇基于 d3 官方文档中的投影Projections章节系统讲解 d3-geo 中投影的完整 API 体系点投影与反投影、流式投影管线、球面/视口裁剪、缩放与旋转等几何变换、自动适配的 fit 系列方法以及用 geoProjection / geoProjectionMutator 自定义投影的进阶手法。读完你不仅能把任意 GeoJSON 正确投影渲染到 SVG/Canvas还能按地理学原理派生出自己的新投影。1. 投影是什么从球面多边形到平面多边形投影Projection把球面上的多边形几何经纬度坐标转换为平面上的多边形几何像素坐标。在 d3 中投影是连接地理数据与屏幕像素的桥梁所有投影都实现统一的接口——点变换、反向点变换与流式变换stream再由 geoPath 消费其输出生成 SVG path 数据或绘制到 Canvas。当前仓库为 d3 v7.9.0 的主包见 package.json它通过依赖d3-geo: ^3.1.1提供全部地理能力并在 src/index.js 中export * from d3-geo将d3.geoXxx系列 API 暴露给使用者。文档侧对应 docs/d3-geo/ 目录下的各篇说明。d3 内置实现了三大类标准投影各自成篇方位投影Azimuthal把球面直接投到平面上如geoAzimuthalEqualArea()方位等积、geoAzimuthalEquidistant()方位等距、geoGnomonic()正射/斜射、geoOrthographic()正视、geoStereographic()立体圆锥投影Conic把球面投到圆锥再展开有两条标准纬线参数如geoConicConformal()圆锥等角默认 parallels 为 [30°, 30°] 时退化为平顶、geoConicEqualArea()Albers 等积圆锥、geoConicEquidistant()圆锥等距、geoAlbers()与geoAlbersUsa()美国专用的复合投影阿拉斯加按 0.35× 缩小面积因此不支持 center/rotate/clipAngle/clipExtent圆柱投影Cylindrical把球面投到包裹圆柱再展开如geoEquirectangular()等距圆柱/平板投影、geoMercator()球面墨卡托默认 clipExtent 使世界投影到正方形、纬度约 ±85° 处裁剪、geoTransverseMercator()横轴墨卡托、geoEqualEarth()等积、geoNaturalEarth1()伪圆柱Neither 等积也非等角小比例尺世界图美观。需要更多投影时可以使用独立生态中的 d3-geo-projection / d3-geo-polygon 等扩展包也可以按本文第 7 节的方法实现自定义投影。2. 点投影与反投影projection(point) 与 projection.invert(point)最基础的用法是把单个经纬度点投影为像素点projection([116.4, 39.9]); // [x, y]通常单位是像素规则输入point必须是两元素数组 [longitude, latitude]单位为度返回新的数组 [x, y]通常单位是像素代表该点的投影位置若该点没有定义的投影位置例如落在投影的裁剪范围之外可能返回 null——因此在批量投影时应当做 null 判断。可逆投影还实现projection.invert(point)输入投影后的点 [x, y]像素输出 [longitude, latitude]度同样可能返回 null该方法只存在于可逆投影上——不可逆投影例如某些复合投影调用会报错使用前可以用invert in projection之类的结构判断。这两个方法是点级接口适合做坐标拾取、tooltip 定位等离散操作而真正渲染整幅地图时用的是流式接口下一节因为流式接口避免了中间表示物化开销更低。3. projection.stream流式投影管线projection.stream(stream)返回一个投影流对指定的输出stream包装后任何输入几何都会先投影、再流式写入输出流。一次典型的投影包含多级几何变换按顺序为输入几何先转换为弧度沿三轴旋转对应rotate裁剪小圆裁剪small circle或沿反经线antimeridian切割最后投影到平面伴随自适应重采样adaptive resampling、缩放scale与平移translate。流Stream本身是一组必须实现的方法详见 Streams 文档point(x, y, z)、lineStart()/lineEnd()、polygonStart()/polygonEnd()、sphere()。流是有状态的一个 point 的含义取决于它是否处于 lineStart/lineEnd 之间线line与环ring则由 polygonStart 区分。例如输入一个 GeoJSON Polygon投影流上会发生这样的方法序列stream.polygonStart(); stream.lineStart(); stream.point(0, 0); stream.point(0, 1); stream.point(1, 1); stream.point(1, 0); // 注意GeoJSON 里冗余的闭合坐标不会被 point 再传一次而是由 lineEnd 隐含 stream.lineEnd(); stream.polygonEnd();这个流接口是 d3 地理模块低层但高效的关键geoPath、geoArea、geoBounds 等都是不同的输出流实现共用同一条投影管线。4. 裁剪体系preclip、postclip、clipAngle、clipExtent裁剪分两个阶段对应投影管线中不同的位置4.1 球面裁剪 preclip / clipAngleprojection.preclip(preclip)设置或不带参数时返回投影的球面裁剪函数。preclip是一个接收投影流、返回裁剪后流的函数常用于沿反经线切割或沿小圆裁剪。projection.clipAngle(angle)是其最常用的封装指定angle度把投影的裁剪圆半径设为以投影中心为圆心、半径为angle的小圆返回投影本身链式调用指定 null切换为沿反经线切割模式而非小圆裁剪不指定返回当前裁剪角默认 null。小圆裁剪与视口裁剪clipExtent相互独立二者可同时生效。例如方位投影文档中的示例geoGnomonic().clipAngle(74 - 1e-4)见 docs/d3-geo/azimuthal.md就是用小圆把地图限制在可视范围内- 1e-4是消除浮点边界毛刺的惯用写法。d3 提供两个现成的球面裁剪函数geoClipAntimeridian把跨越反经线的线/多边形切成两段、各在一侧通常用作前裁剪pre-clippinggeoClipCircle(angle)生成以小圆为界的裁剪函数小圆半径为angle、圆心为投影中心通常用作前裁剪。4.2 平面裁剪 postclip / clipExtentprojection.postclip(postclip)设置或返回投影的笛卡尔平面裁剪函数。后裁剪发生在投影到平面之后用于把投影限制到某个范围如矩形视口。projection.clipExtent(extent)是其封装extent指定为 [[x₀, y₀], [x₁, y₁]]像素x₀ 为视口左侧、y₀ 为顶部、x₁ 为右侧、y₁ 为底部指定 null 表示不做视口裁剪不指定参数时返回当前视口裁剪范围默认 null。对应的平面裁剪函数是geoClipRectangle(x0, y0, x1, y1)生成把几何限制在矩形 [[x0, y0], [x1, y1]] 内的裁剪函数通常用作后裁剪post-clipping。圆柱投影文档中geoMercator()的默认 clipExtent 使世界投影到正方形正是用到了它见 docs/d3-geo/cylindrical.md。一个常用套路是小圆裁剪 略宽于画布的矩形裁剪叠加例如geoStereographic().clipAngle(135 - 1e-4).clipExtent([[-1, -1], [width 1, height 1]])小圆决定看多大范围矩形裁剪负责把越界的线条干净地切掉。5. 几何变换scale、translate、center、rotate、angle 与反射投影返回的像素坐标 原始投影结果经过旋转 → 缩放 → 平移之后的值。以下 API 均可链式调用且都能设值/取值两用带参数设置并返回投影不带参数返回当前值API说明默认值projection.scale(s)缩放因子与投影点间距离成线性关系但不同投影之间绝对缩放值不等价因投影而异projection.translate([tx, ty])平移偏移决定投影中心在像素坐标系中的位置[480, 250]即把 ⟨0°,0°⟩ 放在 960×500 区域中央projection.center([λ, φ])投影中心经纬度度⟨0°, 0°⟩projection.rotate([λ, φ, γ])三轴球面旋转角度单位度γroll可省略对应 yaw / pitch / roll 三轴[0, 0, 0]projection.angle(a)投影之后的平面旋转角度。注意渲染时旋转如 Canvas 的 context.rotate通常比投影时旋转更快0°projection.reflectX(r)是否对输出的 x 维取反。可用于天球/天文数据的仰视显示北朝上时赤经向东指向左falseprojection.reflectY(r)是否对输出的 y 维取反。用于把正 y 向上的空间参考系如标准地理坐标系转换到正 y 向下的 Canvas/SVG 显示坐标系falseprojection.precision(p)自适应重采样阈值像素对应 Douglas–Peucker 距离调小得到更平滑的线条但点更多√0.5 ≈ 0.70710…其中rotate是最常用也最直观的变换给方位投影传rotate([110, -40])等价于把视角转到东经 110°、南纬 40°。本仓库文档站的 WorldMap 组件 就实现了一个可交互的三轴旋转演示鼠标水平拖动时用projection.rotate([rotate[0] (x1 - x0) / width * 20, rotate[1], rotate[2]])增量修改 λ 轴并用requestAnimationFrame节流重绘见 WorldMap.vue 的rerender函数随后统一调用path(outline)、path(graticule)、path(feature)刷新三条 path。这个组件同时展示了文档中各投影示例如geoAzimuthalEqualArea().rotate([110, -40]).fitExtent([[1, 1], [width - 1, height - 1]], {type: Sphere})在真实运行环境里的形态。关于precision的取舍自适应重采样是投影把球面上的弧变成平面折线的机制——采样越密折线越逼近真实曲线。文档里几乎所有世界地图示例都带.precision(0.2)比默认值更精细绘制小尺寸缩略图时可以保持默认甚至调大以节省点数。6. 自动适配fitExtent、fitSize、fitWidth、fitHeight手动调 scale/translate 很痛苦d3 提供了一组给定一个 GeoJSON 对象自动算出合适的 scale 与 translate的便捷方法projection.fitExtent([[x0, y0], [x1, y1]], object); // 把 object 居中放进 extent projection.fitSize([width, height], object); // 等价于 fitExtent([[0, 0], [width, height]], object) projection.fitWidth(width, object); // 高度按 object 的宽高比自动确定 projection.fitHeight(height, object); // 宽度按 object 的宽高比自动确定几个重要细节计算新的 scale/translate 时忽略当前已设置的 clipExtent用于计算object包围盒的 precision 是在有效缩放 150下计算的——意味着包围盒的精度与最终 scale 无关fit 的结果稳定可复现。官方文档给出的横轴墨卡托新泽西州 State Plane配置示例值得完整保留它同时演示了 rotate fitExtent 的组合var projection d3.geoTransverseMercator() .rotate([74 30 / 60, -38 - 50 / 60]) // 中心经度西经 74°30′、纬度北纬 38°50′ .fitExtent([[20, 20], [940, 480]], nj); // 在 960×500 画布中留 20px 内边距而在本仓库的组件里可以看到更简洁的惯用形态——直接对{type: Sphere}做 fit把整个可见地球适配进画布见 docs/d3-geo/azimuthal.md 中的投影示例字符串d3.geoAzimuthalEquidistant() .rotate([110, -40]) .fitExtent([[1, 1], [width - 1, height - 1]], {type: Sphere}) .precision(0.2)fitWidth/fitHeight则适合响应式场景容器宽度已知时约束宽高度由数据宽高比推出反之亦然。7. 自定义投影Raw Projections、geoProjection 与 geoProjectionMutator7.1 Raw projection 的约定Raw projection原始投影是点变换函数是自定义投影的底层积木通常传给geoProjection或geoProjectionMutator。约定输入lambdaλ经度、phiφ纬度单位是弧度不是度输出 [x, y]通常是无量纲的单位投影坐标以原点为中心的近似单位方内接口为project(lambda, phi)若暴露project.invert(x, y)则包装后的投影也自动获得projection.invert。Raw projection 故意保持裸露一方面方便派生相关投影改公式即可另一方面它不需要自己做缩放/平移由 scale/translate/center 自动施加也不需要做球面旋转rotate 会先于投影应用。7.2 geoProjection(project)一步包装geoProjection(project)用给定的 raw projection 构造一个完整投影。最经典的例子是球面墨卡托var mercator d3.geoProjection(function(x, y) { return [x, Math.log(Math.tan(Math.PI / 4 y / 2))]; });两行核心公式就完成了墨卡托的纬度拉伸y ln(tan(π/4 φ/2))旋转、缩放、裁剪、流接口全部由包装器补齐。若该函数没有invert得到的投影也不支持反投影。7.3 geoProjectionMutator(factory)带参数的可变异投影圆锥投影有两个可配置的标准纬线这类带参数的投影要用geoProjectionMutator(factory)它接收一个raw projection 工厂函数factory 必须返回一个 raw projection返回一个mutate函数——每当参数变化时调用它即可把投影内部使用的 raw projection 重新赋值mutate 的返回值就是包装后的投影。文档给出的两段完整示例工厂 可变异投影是理解这套模式的最佳材料// y0 和 y1 代表两条标准纬线 function conicFactory(phi0, phi1) { return function conicRaw(lambda, phi) { return […, …]; // 圆锥投影的坐标公式 }; }function conicCustom() { var phi0 29.5, phi1 45.5, mutate d3.geoProjectionMutator(conicFactory), projection mutate(phi0, phi1); projection.parallels function(_) { return arguments.length ? mutate(phi0 _[0], phi1 _[1]) : [phi0, phi1]; }; return projection; }要点自定义 accessorparallels([a, b])在设值时重新调用mutate从而在不重建投影对象的前提下替换内部 raw projection——之前设置的 scale/translate/clip 等状态保留。惯例上mutate函数本身不对外暴露。d3 内置的geoConicEqualAreaRaw、geoConicConformalRaw等见 docs/d3-geo/conic.md就是这种工厂形态geoAlbers()本质上是对geoConicEqualArea的美国中心预设配置。8. 超越投影geoTransform 与 geoIdentity8.1 geoTransform(methods)geoTransform(methods)用methods对象上定义的方法实现任意变换未定义的方法走直通实现把输入原样转发给输出流。文档中的两个例子分别覆盖了点级变换与仿射矩阵// 反射 y 维等价于 projection.reflectY 的流式写法 const reflectY d3.geoTransform({ point(x, y) { this.stream.point(x, -y); } });// 仿射矩阵变换 function matrix(a, b, c, d, tx, ty) { return d3.geoTransform({ point(x, y) { this.stream.point(a * x b * y tx, c * x d * y ty); } }); }从源码结构看变换与投影共享同一套流协议geoTransform 返回的对象实现了projection.stream因此可以传给path.projection(...)直接参与渲染但它只实现了投影接口的一个子集语义上表达的是任意几何变换而非球面到平面的投影。8.2 geoIdentity()geoIdentity()是恒等变换用于对平面几何直接做缩放、平移与裁剪。它实现的投影方法包括scale、translate、fitExtent、fitSize、fitWidth、fitHeight、clipExtent、angle、reflectX、reflectY。典型用途是处理本来就在平面上的数据本地平面坐标、建筑图、遥感影像像素坐标const projection d3.geoIdentity().reflectY(true).scale(10).translate([0, height]); const path d3.geoPath(projection);9. 与 geoPath 组合从投影到渲染投影本身只产坐标/流真正把地图画出来要靠 geoPath。组合方式按渲染目标分两种const path d3.geoPath(projection); // SVG返回 path 数据字符串 const path d3.geoPath(projection, context); // Canvas绘制到 2D contextpath 支持 Point / MultiPoint / LineString / MultiLineString / Polygon / MultiPolygon / GeometryCollection / Feature / FeatureCollection以及特殊的Sphere类型无坐标渲染地球轮廓。多个 feature 可包进一个 FeatureCollection 用单条 path 渲染快或按 feature join 成多条 path利于交互与样式。本仓库 WorldMap.vue 组件是投影 path 裁剪 旋转全链路的最小真实实现值得完整阅读const outline {type: Sphere}; const graticule d3.geoGraticule10(); // render() 内部 const path d3.geoPath(projection); function update() { svg.selectAll([nameoutline]).attr(d, path(outline)); // 地球轮廓受 clipAngle 影响 svg.selectAll([namegraticule]).attr(d, path(graticule)); // 10° 经纬网格 svg.selectAll([namefeature]).attr(d, path(feature)); // 陆地 }配合模板中的四条path填充轮廓、经纬网、陆地、描边轮廓整个组件仅用投影的 rotate fitExtent precision clip 系列 API 就完成了交互式世界地图——这正是第 5、6 节 API 的落地形态。10. 实战速查需求推荐 API 组合世界地图整球可见fitExtent([[1,1],[w-1,h-1]], {type:Sphere}) 合适的投影等积/伪圆柱某区域地图自动适配fitSize([w, h], geojsonObject)fit 时忽略 clipExtent包围盒按有效 scale 150 计算只限宽或限高响应式fitWidth/fitHeight另一边按对象宽高比推出小圆窗口式地图正射、斜射clipAngle(θ - 1e-4)定范围 略宽画布的clipExtent切边跨反经线国家俄罗斯、太平洋岛国默认反经线切割clipAngle 为 null 时或自定义 preclip换个视角看地球rotate([λ, φ, γ])文档组件里有拖动增量旋转的现成写法平面坐标数据geoIdentity() scale/translate/clipExtent自定义投影geoProjection(rawFn)带参数则geoProjectionMutator(factory)点在屏幕外/裁剪区外projection(point)/invert(point)返回 null渲染前需判空最后提醒两个容易踩的坑其一raw projection 的输入输出单位是弧度而projection(point)、rotate、center等对外 API 全部是度二者不要混用其二scale 的绝对值在不同投影之间不可比换投影后应重新用 fit 系列方法适配而不是沿用旧 scale。按本文的管线理解——弧度化 → 三轴旋转 → 小圆/反经线前裁剪 → 投影 自适应重采样 → scale/translate → 平面后裁剪——绝大多数投影配置问题都能定位到具体是哪一级出了问题。参考路径docs/d3-geo/projection.md本文主体依据、docs/d3-geo/stream.md、docs/d3-geo/path.md、docs/d3-geo/azimuthal.md、docs/d3-geo/conic.md、docs/d3-geo/cylindrical.md、docs/components/WorldMap.vue、src/index.js、package.json。【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网