使用echarts-mapmaker自定义地图

242 阅读4分钟

1. 地图数据来源

echarts仓库里面就有,地图数据不会经常改变,没必要在线获取,还慢 地图数据有jsonjs格式的,jsonecharts压缩过的,js本质就是引入json只是包装了导出

2. 整形工具

最重要的部分就是利用工具处理这些数据,实现如扣去某块区域添加某块区域去除边界等功能

  1. echarts-mapmaker压缩解码地图数据、合并拆分地图数据
  2. mapshaper整形地图,主要就是来去除边界
  3. 文档,上面有例子
  4. 这个仓库也有很多新的地图数据,自己可以翻看

3实战

3.1 拆分地图区域

这里我们拆分重庆各个区县为例

const path = require('path')
const fs = require('fs')
const fsp = require('fs/promises')
const distFolder = path.resolve(__dirname, './dist')
const dest = (fileName) => {
  try {
    fs.accessSync(distFolder, fs.constants.W_OK)
  }catch(err) {
    fs.mkdirSync(distFolder)
  }
  return path.resolve(__dirname, distFolder, fileName)
}

// 引入mapmaker
const maker = require('echarts-mapmaker/src/maker')
// 引入原始地图数据
const chongqingJson = path.resolve(__dirname, './map/province/chongqing.json')
// 解码地图数据并输出
// 解码后的文件就是GeoJson 方便后续操作
const dec_chonggqing = dest('dec_chongqing.json')
maker.decompress(chongqingJson, dec_chonggqing)
// 分割区域
maker.splitAsGeojson(dec_chonggqing, distFolder)

解码后的文件 分割后的文件(名称是自动根据json里面的name生成的)

3.2 合并区域

比如我需要合并中部地区、西部地区之类的,就需要合并区域

// 只需要这一句话
// 合并第一个和第二个json
 maker.merge('./dist/九龙坡区.geojson', './dist/大渡口区.geojson')

根目录输出文件

这个方法有些问题:

  1. 无法自定义输出位置和输出文件名
  2. 每合并一次输出文件前面会加一个merge_,处理起来很麻烦 可以考虑自己写一个方法,参考源码其实不难(最好还是了解一下geojson
// 源码
function merge(geojson, geojsonToBeMerged){
  // 读取两个文件
  const data = fs.readFileSync(geojson, 'utf8');
  const data2 = fs.readFileSync(geojsonToBeMerged, 'utf8');
  var parent = JSON.parse(data);
  var child = JSON.parse(data2);
  
  // features要素,就是描述边界信息的一个数组
  child.features.forEach(function(feature){
    // 只合并不同name的区域
    parent.features = parent.features.filter((featurex)=>{
      return featurex.properties.name!==feature.properties.name;
    });
    // 直接把一个数据的边界信息放入另一个的features中
    parent.features.push(feature);
  });
  // 写入文件
  fs.writeFileSync('merged_'+path.basename(geojson), JSON.stringify(parent));
}    

逻辑很简单,主要就是提取要素然后放入另一个地图里面,下面是我简单的一个封装

function mergeToOne(targetJSON, distDir, ...childrenJSONList){
  // 读取目标json,其他json都往这个json合并
  const data = fs.readFileSync(targetJSON, 'utf8');
  const parentData = JSON.parse(data);
  // 循环需要合并的数据
  childrenJSONList.forEach(child => {
    const childJSON= fs.readFileSync(child, 'utf8')
    const childData = JSON.parse(childJSON)
    // 合并
    childData.features.forEach(function(feature){
      parentData.features = parentData.features.filter((featurex)=>{
        return featurex.properties.name!==feature.properties.name;
      });
      parentData.features.push(feature);
    });
  })

  // 写文件
  fs.writeFileSync(distDir, JSON.stringify(parentData));
}

// 使用
// 最后会输出一个`合并文件.json`,合并了三个区
mergeToOne('./dist/九龙坡区.geojson', './合并文件.json', './dist/大渡口区.geojson', './dist/沙坪坝区.geojson')

3.3 镶嵌区域

只用合并方法也可以,但是在echarts中看起来有点问题,在地图上高亮这片区域的时候看起来不是一体的。因为这样直接合并是重叠的。

所以下面给出另一种方案,结合扣去区域cut方法

// 使用 
maker.cut('a.json', '重庆', '沙坪坝区')

但是源码似乎有问题,反正我用开头给的地图数据是无法正确切除的

// 源码
var cutAHoleInFeatureAWithFB = (jsonFile, featureA, featureB) => {

  data = fs.readFileSync(jsonFile, 'utf8');

  var geojson = JSON.parse(data);
  var featurea = geojson.features.find(feature => feature.properties.name === featureA);
  var featureb = geojson.features.find(feature => feature.properties.name === featureB);
  // https://stackoverflow.com/questions/43645172/geojson-multipolygon-with-multiple-holes
// 就是这一步 似乎不正确
  featurea.geometry.coordinates.push(featureb.geometry.coordinates[0]);

  fs.writeFileSync("cut_" + jsonFile, JSON.stringify(geojson));
};

查阅资料发现,geojson挖去孔洞就是使用MultiPolygon要素, 多边形带空洞的数据格式是一个四维数组,

{
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "MultiPolygon",
        "coordinates":
    [ 
        [
            [
              // 这个就是孔洞的多边形数据
                [101.6455078125,27.68352808378776],
                [114.78515624999999,27.68352808378776],
                [114.78515624999999,35.209721645221386],
                [101.6455078125,35.209721645221386],
                [101.6455078125,27.68352808378776]
            ],
            [
                [104.2822265625,30.107117887092357],
                [108.896484375,30.107117887092357],
                [108.896484375,33.76088200086917],
                [104.2822265625,33.76088200086917],
                [104.2822265625,30.107117887092357]
            
            ]
        ]
    ]
   }
}

// 再简化
{
  "type": "MultiPolygon",
  "coordinates": [
    [
      {polygon},
      {hole},
      {hole},
      {hole}
    ]
  ]
}

源码的问题就是

 // coordinates层级错了
  featurea.geometry.coordinates.push(featureb.geometry.coordinates[0]);
// 改为
featurea.geometry.coordinates[0].push(featureb.geometry.coordinates[0]);

下面是我修改后的

var cutArea = (jsonFile, featureA, featureB) => {

  data = fs.readFileSync(jsonFile, 'utf8');

  var geojson = JSON.parse(data);
  var featurea = geojson.features.find(feature => feature.properties.name === featureA);
  var featureb = geojson.features.find(feature => feature.properties.name === featureB);
  featurea.geometry.coordinates[0].push(featureb.geometry.coordinates[0])
  fs.writeFileSync("cut_" + jsonFile, JSON.stringify(geojson));
}

所以镶嵌的过程就是:

  1. 合并两个地图
  2. 扣去镶嵌的区域
mergeToOne('./dist/d_china.json', './合并文件.json', './dist/沙坪坝区.geojson')
cutArea('合并文件.json', '重庆', '沙坪坝区')

3.4 消除内部边界

这种功能主要是为了满足ui的一些设计需求。

主要用到mapshaper,挺复杂的一个东西,有时间可以详细研究

const maker = require('echarts-mapmaker/src/maker')
const shaper = require('mapshaper')
shaper.runCommands('./dist/d_chongqing.json  -dissolve2 -o chongqing_shape_only.geojson', (err) => {
  if(!err) {
    maker.transform('./chongqing_shape_only.geojson', './chongqing_shape_only.echarts.json', '重慶市')
  }
})
  1. 主要利用mapshaperdissolve2命令
  2. 去除边界完成后echarts不能直接使用,要用mapmaker转化一次,这里和官网的文档不一样,注意使用transform方法转成echarts能识别的格式,不然就是普通的geoJSON
  3. 官网文档使用的是全局安装mapshaper然后用命令行,我这里用的是wiki上的编程方式,本质上也是命令行。

4 总结

以上就是基本的一些操作,通过结合上面的基本方法,我们可以随意组合想要的地图数据。

实际上就是对geojson的一些操作,有时间可以详细了解一下这个东西。