「数据可视化 D3系列」入门第十三章:地图可视化全面指南


一、地理数据基础

GeoJSON/TopoJSON格式解析

GeoJSON 是一种用于编码各种地理数据结构的格式,基于JavaScript对象表示法(JSON)。它支持以下几何类型:

  • Point (点)
  • LineString (线)
  • Polygon (面)
  • MultiPoint (多点)
  • MultiLineString (多线)
  • MultiPolygon (多面)
  • GeometryCollection (几何集合)
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {"name": "北京市"},
      "geometry": {
        "type": "Polygon",
        "coordinates": [[[116.2,39.9],[116.3,39.8],...]]
      }
    }
  ]
}

TopoJSON 是GeoJSON的扩展,通过共享弧段来减少冗余,显著减小文件大小:

{
  "type": "Topology",
  "objects": {
    "provinces": {
      "type": "GeometryCollection",
      "geometries": [...]
    }
  },
  "arcs": [[[0,0],[1,1],[2,0]],...
}

地理投影原理与应用

地理投影是将三维地球表面转换为二维平面的数学方法。D3提供了多种投影:

1. 等距投影: 保持距离准确

  • d3.geoAzimuthalEqualArea()
  • d3.geoEquirectangular()

2. 等角投影: 保持角度和形状

  • d3.geoMercator() (墨卡托投影,Web地图常用)
  • d3.geoConicConformal()

3. 折衷投影: 平衡各种变形

  • d3.geoAlbers() (美国常用)
  • d3.geoNaturalEarth1()

二、核心API详解

d3.geoPath() 地理路径生成器

将 GeoJSON/TopoJSON 转换为SVG路径字符串的核心工具:

const projection = d3.geoMercator()
  .fitSize([width, height], geojson);

const path = d3.geoPath()
  .projection(projection);

svg.selectAll("path")
  .data(geojson.features)
  .enter()
  .append("path")
  .attr("d", path)
  .attr("fill", "#ccc")
  .attr("stroke", "#fff");

d3.geoProjection() 投影系统

自定义投影配置示例:

const projection = d3.geoAlbers()
  .center([105, 36])  // 中心点[经度,纬度]
  .rotate([-20, 0])   // 旋转角度
  .parallels([20, 40]) // 标准纬线(圆锥投影专用)
  .scale(1000)        // 缩放比例
  .translate([width/2, height/2]); // 平移至画布中心

d3.zoom() 地图缩放行为

实现地图平移和缩放交互:

const zoom = d3.zoom()
  .scaleExtent([1, 8])  // 缩放范围限制
  .on("zoom", (event) => {
    g.attr("transform", event.transform);
  });

svg.call(zoom);

三、高级地图可视化技术

分级统计图(Choropleth)实现

根据数据值对区域着色:

// 创建颜色比例尺
const colorScale = d3.scaleThreshold()
  .domain([0, 10, 50, 100, 500, 1000])
  .range(d3.schemeBlues[7]);

// 绑定数据并着色
svg.selectAll(".region")
  .data(geojson.features)
  .enter()
  .append("path")
  .attr("class", "region")
  .attr("d", path)
  .attr("fill", d => {
    const value = dataMap.get(d.properties.name);
    return value ? colorScale(value) : "#eee";
  })
  .append("title")
  .text(d => `${d.properties.name}: ${dataMap.get(d.properties.name) || '无数据'}`);

气泡地图叠加

在地图上叠加圆形标记表示数据:

// 计算气泡位置
const bubbles = svg.append("g")
  .selectAll("circle")
  .data(cityData)
  .enter()
  .append("circle")
  .attr("cx", d => projection([d.lng, d.lat])[0])
  .attr("cy", d => projection([d.lng, d.lat])[1])
  .attr("r", d => Math.sqrt(d.population) * 0.02)
  .attr("fill", "steelblue")
  .attr("opacity", 0.7);

地图交互与钻取

实现省-市二级钻取效果:

// 省级地图点击事件
provincePaths.on("click", function(event, d) {
  // 加载该省的市级数据
  loadCityData(d.properties.code).then(cityGeoJSON => {
    // 更新投影中心点
    projection.fitSize([width, height], cityGeoJSON);
    
    // 重绘路径
    paths.datum(cityGeoJSON)
      .attr("d", path);
    
    // 添加返回按钮
    addBackButton();
  });
});

四、性能优化策略

大数据量地图渲染

1. 简化几何图形:

const simplified = topojson.simplify(
  topojson.quantize(topojsonObject, 1e4),
  0.1
);

2. Canvas渲染替代SVG:

const canvas = d3.select("#map").append("canvas");
const context = canvas.node().getContext("2d");
const path = d3.geoPath().context(context);

3. Web Workers处理复杂计算:

const worker = new Worker("geoWorker.js");
worker.postMessage({type: "simplify", data: largeGeoJSON});

拓扑简化技术

使用TopoJSON的量化与简化:

const topojson = require("topojson-client");

// 原始数据转换
const topo = topojson.topology({collection: geojson});

// 量化减少坐标精度
const quantized = topojson.quantize(topo, 1e4);

// 简化拓扑结构
const simplified = topojson.simplify(quantized, 0.1);

动态加载策略

1. 按需加载区域数据:

async function loadProvinceData(code) {
  const response = await fetch(`/geodata/${code}.json`);
  return await response.json();
}

2. 多级细节(LOD)技术:

function updateMap(zoomLevel) {
  const resolution = zoomLevel > 5 ? "high" : "low";
  loadGeoData(resolution).then(updatePaths);
}

3. 数据分块渲染:

function renderChunk(start, end) {
  requestAnimationFrame(() => {
    for (let i = start; i < end; i++) {
      renderFeature(features[i]);
    }
  });
}

五、实战案例:中国省级地图可视化

首先我们需要的是 中国地图的json数据:戳这里

👇 完整代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>中国地图</title>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>

<body>
  <svg width="900" height="700"></svg>
</body>

<script>
  //获取svg
  var svg = d3.select('svg');
  var width = svg.attr('width');
  var height = svg.attr('height');

  //创建区域分组
  var g = svg.append('g').attr('transform', 'translate(0,0)');

  //创建一个地图投影
  var mercator = d3.geoMercator()
    .center([107, 31])//设置投影的中心点 经纬度
    .scale(550)//设置缩放因子
    .translate([width / 2, height / 2]);//设置平移偏移量

  //创建一个地理路径生成器
  var geoPath = d3.geoPath()
    .projection(mercator)//设置地理路径生成器的投影方式

  //获取中国地图的json文件
  //利用node.js 在本地起一个http-server
  d3.json('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json').then(function (data) {//D3 v5版本d3.json()现在将返回一个你可以在.then()方法中处理的Promise
    console.log(data);//features

    //新建一个颜色比例尺
    var scaleColor = d3.scaleOrdinal()
      .domain(d3.range(data.features.length))
      .range(d3.schemeCategory10);

    //绘制区域
    g.append('g')
      .selectAll('path')
      .data(data.features)
      .enter()
      .append('path')
      .attr('stroke', 'gray')
      .attr('strok-widht', 1)
      .attr('d', function (d, i) {
        return geoPath(d);
      })
      .attr('fill', function (d, i) {
        return scaleColor(i);
      })

    //绘制文字
    g.append('g')
      .selectAll('text')
      .data(data.features)
      .enter()
      .append('text')
      .attr('font-size', 12)
      .attr('text-anchor', 'middle')
      .attr('x', function (d, i) {
        var position = mercator(d.properties.centroid || [0, 0]);
        return position[0];
      })
      .attr('y', function (d, i) {
        var position = mercator(d.properties.centroid || [0, 0]);
        return position[1];
      })
      .attr('dy', function (d, i) {
        //这里为什么这么写呢,因为澳门和香港重合了,挤到一起了。
        if (d.properties.name === '澳门特别行政区') {
          return 10;
        }
      })
      .text(function (d, i) {
        return d.properties.name;
      });
  });
</script>
</html>

👇 效果实现

在这里插入图片描述


小结

核心实现步骤

1. 数据准备:

  • 获取地理数据(GeoJSON/TopoJSON格式)

  • 准备要可视化的业务数据

2. 地图初始化:

  • 设置SVG容器和尺寸

  • 选择合适的投影方式

  • 创建路径生成器

3. 地图绘制:

  • 将地理数据绑定到SVG路径元素

  • 根据业务数据设置颜色填充

4. 交互增强:

  • 添加鼠标悬停效果

  • 实现缩放和平移功能

  • 添加工具提示显示详细信息

5. 辅助元素:

  • 创建图例解释颜色编码

  • 添加标题和说明文字

关键技术与API

  • d3.geoPath(): 将GeoJSON转换为SVG路径的核心方法

  • d3.geoProjection(): 定义如何将三维地理坐标映射到二维平面

  • d3.zoom(): 实现地图的交互式缩放和平移

  • 比例尺系统: 将数据值映射到视觉属性(如颜色)

  • 数据绑定与更新模式: D3特有的数据驱动DOM操作方式

性能优化要点

1. 数据层面:

  • 使用TopoJSON替代GeoJSON减小文件体积

  • 简化几何图形减少点数

  • 按需加载不同级别的细节数据

2. 渲染层面:

  • 对于大数据量考虑使用Canvas替代SVG

  • 实现渐进式渲染避免界面卡顿

  • 使用Web Workers处理复杂计算

3. 交互层面:

  • 合理设置缩放范围限制

  • 添加过渡动画提升用户体验

  • 实现延迟加载和缓存机制

扩展方向

1. 功能增强:

  • 添加地图钻取功能(国家→省→市)

  • 实现时间轴动态变化

  • 结合其他图表类型(如气泡图)

2. 可视化类型扩展:

  • 创建热力地图

  • 实现流向地图

  • 添加3D地形效果

3. 应用场景:

  • 疫情数据可视化

  • 人口分布分析

  • 经济指标区域对比

  • 物流运输路径展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八了个戒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值