Fix Echarts 中国地图

A way to Fix Echarts China map

Posted by jeneser on September 2, 2017

Fix Echarts China map

Echarts中国地图组件对某些地理位置的渲染无法满足项目运营需求,其中:钓鱼岛、赤尾屿、台湾周边岛屿、东沙群岛以及南海诸岛均有缺失,需手动fix Echarts源码来解决。

Fix前后效果对比: Alt text

实现原理

通过修改echarts部分源码,可以实现我们的定制化需求。

我们的首要问题是,echarts对某些岛屿的绘制无法满足我们的需要。既然如此,我们就可以定位相关源码,手动Fix。

通过阅读查找echarts源码,可以发现,对geo的处理主要在echarts/lib/geo文件夹下。 其中Geo.js文件包含了Geo构造函数,我们可以从中发现官方对钓鱼岛做了fix,但尽管如此还是无法满足我们的需求,不过我们可以参考此方案来根据需要进行fix。

阅读echarts/lib/geo/fix/diaoyuIsland.js文件。可以发现该文件包含了一个points变量和一个默认导出的方法。

var points = [];

function _default(geo) {
	...
	geo.regions[i].geometries.push({
      type: 'polygon',
      exterior: points[0]
    });
    ...
}

module.exports = _default;

其中points变量保存了以经纬度坐标为多边形的点围成的该岛屿的GeoJSON数据。

默认导出的方法对points做了特定区域的“挂载”。在echarts/lib/geo/fix/文件夹中我们可以看到两个比较特殊的文件,diaoyuIsland.jsnanhai.js。两个文件中包含了两种可以fix渲染区域的方式,其中diaoyuIsland.js中是以挂载pointsgeo.regions中某个特定区域的方式。nanhai.js则是新建了一个region对象并合并到了geo.regions所表示的区域数组中。

这里geo.regions所表示的数据中保存了地图中的特定区域的描述对象,即一块独立的特定区域。对应到中国地图则是表示了诸如,北京,上海,广州,台湾等行政区划。其中每一个特定区域大致由如下方式创建:

new Region(name, zrUtil.map(points, function (exterior) {
  return {
    type: 'polygon',
    exterior: exterior
  };
}), geoCoord)

其中name表示该区域名称,例如:南海诸岛,台湾等。points是一个多维数组,保存了该区域轮廓的经纬度坐标,其表示方式遵循GeoJSON格式。

需要留意的一点是,由于南海诸岛的特殊性,对南海诸岛的表示,并没有按现实生活中实际的经纬度坐标描述。而是以[126, 25]为左上顶点进行的扩展绘制。

绘制特定区域

Echarts的地理数据结构遵循GeoJSON标准。GeoJSON是一种用于编码各种地理数据结构的格式,其标准为RFC 7946了解更多

如上文所述,我们只需要提供相关区域的points即可,即通过GeoJSON的格式来表示地图上某些缺失的点。

我们可以使用http://geojson.io快速画出某些特定区域,并拿到相应的GeoJSON。如下图所示,确定钓鱼岛真实经纬度,绕该经纬度附近简单绘制钓鱼岛轮廓,从而拿到对应GeoJSONAlt text

修改目标源码

Echarts的绝大部分内部模块一般情况下是不建议外部引用的。允许被引用的模块声明在echarts/echarts.all.js文件中。我们可以从echarts/lib/*echarts/src/*中按需引用所需模块。关于两个命名空间的异同可以参考官方文档。在这里我们统一使用echarts/lib/*

map模块的入口为echarts/lib/component/geo.js,我们顺着map模块的引用关系进一步找到需要修改的地方。

geo.js导入了GeoModelgeoCreatorGeoView三个构建geo实例的构造函数和方法。转到echarts/lib/coord/geo文件夹下,可以看到,geoCreator文件是用来实例化Geo构造函数的。我们的重点就在Geo.js文件,该文件中的代码是geo的构造函数,可以在依赖模块中发现官方是有提供有关于钓鱼岛和南海诸岛的fix的。

官方提供的fix是按照钓鱼岛或南海诸岛的实际经纬度描述的。由于整个地图的比例关系,Echarts所在DOM容器的尺寸,以及对图表的缩放(zoom)处理,导致我们在大多数情况下看到的敏感区域都是存在争议的。

Fix钓鱼岛及台湾附近岛屿:

打开echarts/src/coord/geo/fix/diaoyuIsland.js文件,使用http://geojson.io快速拿到钓鱼岛的GeoJSON数据,使用该GeoJSON文件中coordinates替换diaoyuIsland.js中的points。为了方便,在这里我们直接将钓鱼岛及其附近岛屿,赤尾屿及其附近岛屿,台湾海峡诸岛屿,台湾右侧诸岛屿直接挂到台湾区域,即

if (geo.regions[i].name === '台湾') {}

由于我们添加的是多个区域点,在原有单个geo实体(geometries)合并的基础上,修改为遍历合并我们新增或fix的经纬度points

for (var j = 0, jLen = points.length; j < jLen; j++) {
    geo.regions[i].geometries.push({
        type: 'polygon',
        exterior: points[j]
    });
}

具体代码请参考相关文件。

Tip:为了强调某些敏感区域,我们可以适当扩大该区域轮廓,即以该区域实际经纬度为中心扩大该区域轮廓,以适应运营需求。

在项目中使用

Webpack resolve方案

一般情况下,我们不建议直接修改node_modules中的源码,或者将node_modules上传至git仓库。如果你在项目中使用webpack作为打包工具,可通过添加resolve alias的方式来更改webpack打包时的模块引用路径,进而可以以一种可扩展的方式更改依赖源码。

resolve: {
    alias: {
        'echarts/lib/component/geo.js': resolve('static/fixChinaMap/lib/component/geo.js')
    }
}

我们将echarts的geo.js模块指向了新建的自定义模块static/fixChinaMap/lib/component/geo.js,其内容为node_modules中所对应的echarts源码的拷贝,由于该模块存在一些模块的相对引用,我们需要更改这些模块的引用路径,将相对路径替换为echarts模块下绝对路径。

# before
require('../geo/GeoView');
# after
require('echarts/lib/component/geo/GeoView');

需要修改其源码的可手动指向当前项目的自定义路径,将相关依赖模块路径纠正完毕。从map模块入口到最终需要fix的模块的引用关系如下:

└── echarts/lib/component/geo.js
    ├── echarts/src/coord/geo/geoCreator.js
    │   ├── echarts/src/coord/geo/Geo.js
    │   │   ├── echarts/src/coord/geo/fix/**

确定了模块依赖之后,我们可以按照上文中所述的原理及实现方式fix有问题的区域。我们还可以新建相关区域对象,添加到echarts/src/coord/geo/Geo.js中的geoFixFuncs数组中,实现任意区域的hack。同样,我们也可以通过new Region以类似南海诸岛的方式创建新的特性区域,来拓展现有map。

具体源码请参考相关文件。

自定义构建方案

我们可以在修改以上相关源码之后,自定义构建只包含echarts map相关组件的图表组件,以便在项目中直接引用。 Echarts官方提供了自定义构建Echarts的方案。

使用echarts/build/build.js脚本自定义构建。我们需要到node_modules中直接修改Echarts源码(其源码修改过程同以上原理),然后运行echarts提供的打包脚本进行自定义打包构建。另外直接使用构建工具(如rollupwebpackbrowserify)自己构建,道理也是相同的。本方案最终的产出将是定制化的、可以直接在浏览器端项目中使用的图表组件。

我们以echarts/build/build.js脚本为示例演示如下:

如上文中所介绍的原理及方案,修改node_modules中echarts相关源码,fix完成后。在当前项目目录新建echarts.custom.js文件,在该文件中引用相关echarts依赖,

export * from 'echarts/src/echarts';
import 'echarts/src/chart/map';
import 'echarts/src/component/geo';

保存,运行echarts/build/build.js脚本

node node_modules/echarts/build/build.js --min -i echarts.custom.js -o lib/echarts.custom.min.js --lang en

即可在同层目录得到自定义构建的map图表组件。

至此,主要fix逻辑已及实现方案已介绍完毕。可查看本文提供的示例了解更过细节。

注:本文Echart版本^4.1.0,其他相近版本同理。

相关链接

GeoJSON

自定义构建ECharts

webpack resolve

http://geojson.io

最后更新2018-9-17 wangyazhe(wangyazhe@baidu.com)