使用D3绘制地图同时解决地图上色 fitExtent失效的问题

使用D3绘制地图

先看一下效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENi85nYQ-1678532273754)(null)]

首先,D3绘制地图的数据geoJson可以从阿里云数据可视化平台 获取:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyYrL5p3-1678532273772)(null)]

然后,地图常用的投影方式中,我选择使用墨卡托投影,这也是百度,高德地图常用的投影方式,

我们需要从数据平台获取一份GeoJson格式的数据,json数据可以直接当成 Object 对象来导入使用,也可以使用 d3.json 获取远端的Json数据.本次演示采用某市数据为例

  1. 引入D3

    原生直接通过 <script src="https://d3js.org/d3.v5.js"></script>

    或者: npm install d3

2.为地图绘制创建一个容器

<body>
    <svg class="map" style="display: inline-block;"> </svg>
</body>
  1. SVG地图容器的初始化工作:

    	const svgW = 1000
        const svgH = 1000
        const colors = ['white', 'blue', 'red'];
        const svg = d3.select(".map")
        svg.attr('viewBox', `0 0 1000 1000`)
        svg.attr("width", svgW).attr("height", svgH)
            .attr('xmlns', 'http://www.w3.org/2000/svg')
    

    如果想要实现响应式网页设计,设置 viewBox 事半功倍

  2. 创建G分组,设定投影参数

    const g = svg.append('g').attr("class", 'path-wrap').attr("width", svgW).attr("height", svgH)
    const projection = d3.geoMercator()
            // .fitSize([svgW, svgH], mapJson)
            // 使用某市的中心经纬度
            .center([116.418757, 39.917544])  //链式写法,.center([longitude, latitude])设置地图中心(真实维度) // 
            .scale([18000])   //.scale([value])设置地图缩放
        // .translate([svgW / 2, svgH / 2]) //.translate([x,y])设置偏移
    

    这里要注意一点的是,center需要传入经纬度参数而不是画布参数,这个数据我们可以下载更高一级的地图,比如江苏省的地图,里面会有南京的center经纬度. 使用 center scale 这种方法属于手动调整地图来合适的展示地图,不过,官方也给了可以自动布局缩放地图适应svg尺寸的工具函数 – fitExtent ,你可以传入一个二位数组定义你画布的左上角和右下角位置

    譬如我们可以这样写:

    const projection = d3.geoMercator()
            .fitExtent([[0, 0], [svgW, svgH]], mapJson)
    

    或者使用简写:

    const projection = d3.geoMercator()
            .fitSize([svgW, svgH], mapJson)
    

    fitSize 将第一个坐标数据默认设置为 0,0

    第二个参数需要一个完整的 geoJson 对象,注意不是 geo.features 这样之后我们就不需要 centerscale 了, 也不能在 fitExtent 之后调用他们,会报错的.

  3. 生成路径数据 绘制地图

    const pathGenerator = d3.geoPath()
        .projection(projection)
    

    将我们配置好的投影参数传给 geoPathprojection 方法, 可以获取到 path 标签的 d 属性: MDN_path

    然后我们进行数据上的绑定, d3.data() 使用 geoJson.features 而非整个 geoJson 一定要注意! 地图颜色方面 就随意使用一个d3 现有的颜色方案填充了,有其他需求可以自行了解配置, 具体教程可以参考: https://github.com/d3/d3-scale-chromatic,

    const mapDraw = g.selectAll("path")
            .data(mapJson.features) //数据绑定
        mapDraw.join("path")
            .attr("class", "continent-path")
            .attr("d", pathGenerator) //绘制path
            .attr("stroke-width", 1.60347)
            .attr("stroke", "#000")
            .style("fill", (d, i) => d3.schemeSet2[i % 3])
       
    

    然后我们可以给我们的地图标注上地理名称,我们配置好的 projection 工具可以支持传入一组地理坐标返回一组二维坐标用于Svg元素的定位, 我们可以使用 geoJson中的 centroid (质心) 作为我们的定位基准,转换之后 传给 transform 属性就行了

     mapDraw.join('text').attr("class", "continent-text")
            .attr('transform', (d) => {
                return `translate(${projection(d.properties.centroid)})`
            })
            .text((d) => {
                return d.properties.name
            })
    

如果你的地图只有一个小点点,可能你设置错了 center 数据, 注意经纬度别传反了

有个坑你可能会踩一下:

可能第五步你会发现地图无法被渲染出来,可能遇到了下面的问题:

如果你和我一样从阿里云数据平台下载的地图数据,你可能会和我遇到相同的问题,那就是使用 centerscale 可以正常的显示地图, 使用fitExtent 之后地图就是一个小点或者根本不显示了, 这就是我遇到的第一个坑:

由于D3使用 ellipsoidal math 进行地图的投影计算,而不是像其他工具将 geoJson 的数据使用笛卡尔坐标系处理, 同时D3 使用右手法则绘制地图,与现有的geoJson 规范相反, 这将导致 绘制区域可能也会被 “取反”, 如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n7CXTm8A-1678532273824)(null)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dN29lk9Z-1678532273807)(null)]

我不是相关专业人士,需要更详细解释可以移步 => https://github.com/d3/d3-geo/pull/79#issuecomment-280935242

解决该问题也很简单,我们可以使用 turf 工具 – 一个可以在浏览器与Node中使用的地理空间数据分析处理工具 来进行转换,

我们需要使用 其中的 rewind 函数 来对我们的数据进行’倒带’, 顾名思义 就是磁带中的倒带操作,对geo数据进行反向缠绕操作,\

按需安装自己需要的工具:

npm i @turf/rewind

在D3 绘制前处理好 geoJson 数据:

 mapJson.features = mapJson.features.map(function (feature) {
     return turf.rewind(feature, { reverse: true });
 })

接下来使用fitExtent来绘制地图了,下面我放一下手动设定参数与使用 fitExtent 调整的效果对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nKumrjim-1678532273788)(null)]

手动调整缩放与居中问题 确实很难找到合适的大小与偏移值.还是自动调更香啊

下面是完整代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./resources/map.js"></script>
    <script src="https://d3js.org/d3.v5.js"></script>
    <script src='https://unpkg.com/@turf/turf@6/turf.min.js'></script>
</head>
<style>
    .continent-path {}

    .continent-path:hover {
        fill: aquamarine;
    }
</style>

<body>
    <svg class="map" style="display: inline-block;"> </svg>
</body>

<script>

    const svgW = 1000
    const svgH = 1000
    const colors = ['white', 'blue', 'red'];
    const svg = d3.select(".map")
    svg.attr('viewBox', `0 0 1000 1000`)
    svg.attr("width", svgW).attr("height", svgH)
        .attr('xmlns', 'http://www.w3.org/2000/svg')
        // .attr("stroke-width", 1.60347)
        // .attr("stroke", "#000")

    mapJson.features = mapJson.features.map(function (feature) {
        return turf.rewind(feature, { reverse: true });
    })
    const g = svg.append('g').attr("class", 'path-wrap').attr("width", svgW).attr("height", svgH)
    const projection = d3.geoMercator()
     // .fitExtent([[0, 0], [svgW, svgH]], mapJson) // 使用fitExtent 下面的参数就不要使用了,反之 依然
    // 使用某市的中心经纬度
    .center([116.418757, 39.917544])  //链式写法,.center([longitude, latitude])设置地图中心(真实维度) 
    .scale([18000])   //.scale([value])设置地图缩放
    .translate([svgW / 2, svgH / 2]) //.translate([x,y])设置偏移

    const pathGenerator = d3.geoPath()
        .projection(projection)
    // console.log('pathGenerator: ', pathGenerator);
    const mapDraw = g.selectAll("path")
        .data(mapJson.features) //数据绑定
    mapDraw.join("path")
        .attr("class", "continent-path")
        .attr("d", pathGenerator) //绘制path
        .attr("stroke-width", 1.60347)
        .attr("stroke", "#000")
        .style("fill", (d, i) => d3.schemeSet2[i % 3])
    mapDraw.join('text').attr("class", "continent-text")
        .attr('transform', (d) => {
            return `translate(${projection(d.properties.centroid)})`
        })
        .text((d) => {
            return d.properties.name
        })

</script>

</html>

全部文件在这里 => https://github.com/3biubiu/D3ToDrawMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值