地图与飞线

2016-04-21

使用 d3.js 绘制地图和飞线动效。涉及内容:GeoJSON、地图投影、贝塞尔曲线、中间帧动画、蒙板等

一、绘制地图

以绘制海南省为例:

地图与飞线_第1张图片
海南省.png

1. 拿到原始数据,比如构成海南省边界的一系列经纬点。

2. 转成适合 d3 识别的格式,如 GeoJSON

  • GeoJSON 是一种专门用于描述地理数据且基于 JSON 的公开标准。
  • 如海南省边界 GeoJSON 格式如下:
地图与飞线_第2张图片
海南省数据.png

3. 将经纬度地理坐标(三维)转换成平面直角坐标(二维),即 “地图投影”。

为什么称之为 “投影”?

以圆柱投影为例,假想球中心有一处光源,球体的影子印在圆柱上,再把圆柱展开。

地图与飞线_第3张图片
圆柱投影.png

图片来源自 wikipedia

d3 中内置了多种地球投影函数,如 d3.geo.mercator()等,调用非常方便。

4. 生成 SVG 路径

调用 d3 中的路径生成器 —— d3.geo.path() ,生成 SVG 路径。

// mercator 投影法,即正轴等角圆柱投影
var projection = d3.geo.mercator()

// 调用路径生成器,加入投影函数,生成路径。
d3.geo.path().projection(projection);

最后将生成的 SVG 路径数值,放入d属性中,即可渲染出海南省


二、绘制飞线

1. 找到城市

原始数据

[{
    "from": {
        "name":"拉萨",
        "coordinate": [116.4551,40.2539]
    },
    "to": {
        "name":"北京",
        "coordinate": [91.1865,30.1465]
    }
}]

三维转二维坐标

与绘制地图时相似,使用 projection() ,把经纬度转为直角坐标。

2. 绘制路径

二次贝塞尔曲线

// 起始点为(50,50),控制点在(50,100),结束点为(100,100)

地图与飞线_第4张图片
飞线.png

起始点拉萨坐标,结束点北京坐标,控制点由计算得出,如下:

地图与飞线_第5张图片
求控制点坐标.png

三、飞线动画

使用 attrTween(),插入中间帧函数,不断变更 中的 d 属性,呈现出线条在“一点点绘制出来”的效果。

地图与飞线_第6张图片
飞线动画.png
// 过渡动画
flyline.transition()

    // 动画时长
    .duration(1800)

    // 为属性 d ,设置中间帧过渡
    .attrTween('d', function(d){

        var l = $path.getTotalLength();

        return function(t){

            var p = $path.getPointAtLength(t * l)

            return '最终返回的值'
        }
    });

说明:

  1. $path 变量为完整的飞线路径,即最终效果的飞线;
  2. getTotalLength() 得出该 的总长度;
  3. 此时的 t,即是中间帧的时刻。值范围为[0, 1],总数量大概会有 100 帧左右(为何是 100 帧左右,而不是个确切的数?暂没搞懂..)
  4. getPointAtLength() 传入路径上距离,返回该点的 x,y 坐标

新的控制点如何确定?

通过起始点和原控制点,求出新的控制点

地图与飞线_第7张图片
新的控制点.gif
新的控制点计算公式.jpg

图片来源 cnblogs

取 p01 为新的控制点。

通过新控制点和终点(变量),起始点不变,动态一次次绘制飞线。

function valueTween(d){

    var $path = d3.select(this.parentNode).select('.line-basic');

    // 基路径
    var coord = $path.attr('d').replace(/(M|Q)/g, '').match(/((\d|\.)+)/g);

    var x1 = +coord[0], y1 = +coord[1], // 起点
        x2 = +coord[2], y2 = +coord[3], // 控制点
        x3 = +coord[4], y3 = +coord[5]; // 终点

    var l = $path.node().getTotalLength();

    return function(t){


        // 新的终点
        var p = $path.node().getPointAtLength(t * l);

        // 新的控制点
        var x = (1-t) * x1 + t * x2;
        var y = (1-t) * y1 + t * y2;

        return 'M'+x1+','+y1+' Q'+x+','+y+' '+p.x+','+p.y;
    }
}

四、飞线样式

1. 使用 svg 蒙板,渲染飞线“头粗尾巴细”的效果

(1) 添加圆形蒙板

地图与飞线_第8张图片
蒙板.png
  • 圆心 cy,cx 为飞线终点;
  • 设置的半径即为可视区域;
  • 蒙板动态跟随飞线变化。

(2) svg 中 标签


    
          
    

(3) 为蒙板添加径向渐变,使得飞线有“头部深,尾部浅至透明”的效果


    
    

2. 为飞线添加一个亮色的头部

地图与飞线_第9张图片
飞线头部亮色.png

3. 优化

地图与飞线_第10张图片
蒙板半径-before.png

原因是蒙板半径没有自适应。当半径为一个固定数值时,将导致长度小于此值的飞线没掉了尾部渐变效果。如下图,白色圆圈为蒙板范围:

地图与飞线_第11张图片
mask-radius.png

优化:使蒙板半径随着两点(起点与终点)的距离而变化

地图与飞线_第12张图片
mask-radius2.png

五、总结

整个流程如下:

  1. 加载地图数据,绘制出地图;
  2. 轮询飞线数据,保存在数据中心;
  3. 飞线池 FlylinePond 初始化 生成飞线实体;
  4. 启动飞线数据运输带 - 不断绘制(只要数据池中有数据)
  5. draw() -已知起点和终点,二次贝塞尔曲线
    • 绘制飞线基本路线
    • 飞线动画,不断改变 d 属性; attrTween
    • 飞线头部
    • 蒙板
    • 结束圆圈
    • 终点文字

效果图:

效果图.gif

六、资料:

  1. eChart(百度)-地图-模拟迁徙
  2. 贝塞尔曲线原理
  3. D3.js 入门系列 — 地图的制作
  4. Marker animation along SVG path element with D3.js
  5. D3.js 官网 Tutorials
  6. wikipedia: 麥卡托投影法

你可能感兴趣的:(地图与飞线)