概述
上一篇文章通过纯前端的方式实现了ArcGIS JS API和eCharts的普通二维图表绘制,因为这些图表绘制其实是跟地理坐标无关的,只需要设置图表的位置即可,所以仅仅用了纯前端的方式去实现。这篇文章通过参考【dGIS】大佬的文章,重构了EchartsLayer.js文件(因为网上所有关于EchartsLayer.js文件的代码全部是编译过的,阅读起来很不友好,并且有些EchartsLayer.js扩展文件仅仅支持到ArcGIS JS API 4.6版本,对于ArcGIS JS API 4.14这样的高版本切换还是有一些问题),使其支持最新版的ArcGIS API for JavaScript 4.14和eCharts 4版本,实现了在ArcGIS的底图上使其能够绘制二维和三维的迁徙图,我们先来看一下效果:
实现思路
迁徙图、散点图这种图表跟地理坐标关系紧密,所以仅仅通过二维普通图表绘制的方式是无法实现这类图表绘制的,所以就需要我们来扩展eCharts的相关功能,使其能够够结合最新版的ArcGIS JS API来完成地图上这类图表的绘制,eCharts官网也提供了相应的扩展插件,但这种插件并不能很好地支持我们ArcGIS JS API的高版本,所以我们在这篇文章里直接扩展了一个图层类,下面是具体的实现思路:
实现ArcGIS JS API和eCharts的结合,最最关键的是要实现两个插件库里的坐标系转换,这是重点,只要搞清楚了这一点,我们完全可以脱离地图API库的束缚,理论上可以实现eCharts跟任意地图库的结合。在此处转换坐标时我们使用了eCharts提供的registerCoordinateSystem方法,通过这个方法我们注册了一个名为"arcgis"的坐标系,里面对eCharts中的dataToPoint、pointToData等方法进行了重写,然后将这些所有内容封装为了一个EchartsLayer图层类。至于这个文件的源码,文章结尾会提供,接下来我们看一下具体的实现步骤。
实现步骤
1、本文所用的demo是基于React框架搭建的,所以我们首先基于React框架搭建一个初始化项目,然后改写src目录下的App.js这个主文件,实例化出一张二维地图,这中间用到了esri-loader插件,具体的实现过程可查看我的这篇文章【【番外】 React中使用ArcGIS JS API 4.14开发】,里面有具体的实现步骤。
2、通过上述操作实例化完一张二维地图后,我们接下来就要进行迁徙图的绘制操作了,在开始之前我们需要一些数据,首先是迁徙途中所要用到的各个行政区划的省会城市坐标,是一份JSON文件,源文件如下:
let GeoCodingData = { '海门': [121.15, 31.89], '鄂尔多斯': [109.781327, 39.608266], '招远': [120.38, 37.35], '舟山': [122.207216, 29.985295], '齐齐哈尔': [123.97, 47.33], '盐城': [120.13, 33.38], '赤峰': [118.87, 42.28], '青岛': [120.33, 36.07], '乳山': [121.52, 36.89], '金昌': [102.188043, 38.520089], '泉州': [118.58, 24.93], '莱西': [120.53, 36.86], '日照': [119.46, 35.42], '胶南': [119.97, 35.88], '南通': [121.05, 32.08], '拉萨': [91.11, 29.97], '云浮': [112.02, 22.93], '梅州': [116.1, 24.55], '文登': [122.05, 37.2], '上海': [121.48, 31.22], '攀枝花': [101.718637, 26.582347], '威海': [122.1, 37.5], '承德': [117.93, 40.97], '厦门': [118.1, 24.46], '汕尾': [115.375279, 22.786211], '潮州': [116.63, 23.68], '丹东': [124.37, 40.13], '太仓': [121.1, 31.45], '曲靖': [103.79, 25.51], '烟台': [121.39, 37.52], '福州': [119.3, 26.08], '瓦房店': [121.979603, 39.627114], '即墨': [120.45, 36.38], '抚顺': [123.97, 41.97], '玉溪': [102.52, 24.35], '张家口': [114.87, 40.82], '阳泉': [113.57, 37.85], '莱州': [119.942327, 37.177017], '湖州': [120.1, 30.86], '汕头': [116.69, 23.39], '昆山': [120.95, 31.39], '宁波': [121.56, 29.86], '湛江': [110.359377, 21.270708], '揭阳': [116.35, 23.55], '荣成': [122.41, 37.16], '连云港': [119.16, 34.59], '葫芦岛': [120.836932, 40.711052], '常熟': [120.74, 31.64], '东莞': [113.75, 23.04], '河源': [114.68, 23.73], '淮安': [119.15, 33.5], '泰州': [119.9, 32.49], '南宁': [108.33, 22.84], '营口': [122.18, 40.65], '惠州': [114.4, 23.09], '江阴': [120.26, 31.91], '蓬莱': [120.75, 37.8], '韶关': [113.62, 24.84], '嘉峪关': [98.289152, 39.77313], '广州': [113.23, 23.16], '延安': [109.47, 36.6], '太原': [112.53, 37.87], '清远': [113.01, 23.7], '中山': [113.38, 22.52], '昆明': [102.73, 25.04], '寿光': [118.73, 36.86], '盘锦': [122.070714, 41.119997], '长治': [113.08, 36.18], '深圳': [114.07, 22.62], '珠海': [113.52, 22.3], '宿迁': [118.3, 33.96], '咸阳': [108.72, 34.36], '铜川': [109.11, 35.09], '平度': [119.97, 36.77], '佛山': [113.11, 23.05], '海口': [110.35, 20.02], '江门': [113.06, 22.61], '章丘': [117.53, 36.72], '肇庆': [112.44, 23.05], '大连': [121.62, 38.92], '临汾': [111.5, 36.08], '吴江': [120.63, 31.16], '石嘴山': [106.39, 39.04], '沈阳': [123.38, 41.8], '苏州': [120.62, 31.32], '茂名': [110.88, 21.68], '嘉兴': [120.76, 30.77], '长春': [125.35, 43.88], '胶州': [120.03336, 36.264622], '银川': [106.27, 38.47], '张家港': [120.555821, 31.875428], '三门峡': [111.19, 34.76], '锦州': [121.15, 41.13], '南昌': [115.89, 28.68], '柳州': [109.4, 24.33], '三亚': [109.511909, 18.252847], '自贡': [104.778442, 29.33903], '吉林': [126.57, 43.87], '阳江': [111.95, 21.85], '泸州': [105.39, 28.91], '西宁': [101.74, 36.56], '宜宾': [104.56, 29.77], '呼和浩特': [111.65, 40.82], '成都': [104.06, 30.67], '大同': [113.3, 40.12], '镇江': [119.44, 32.2], '桂林': [110.28, 25.29], '张家界': [110.479191, 29.117096], '宜兴': [119.82, 31.36], '北海': [109.12, 21.49], '西安': [108.95, 34.27], '金坛': [119.56, 31.74], '东营': [118.49, 37.46], '牡丹江': [129.58, 44.6], '遵义': [106.9, 27.7], '绍兴': [120.58, 30.01], '扬州': [119.42, 32.39], '常州': [119.95, 31.79], '潍坊': [119.1, 36.62], '重庆': [106.54, 29.59], '台州': [121.420757, 28.656386], '南京': [118.78, 32.04], '滨州': [118.03, 37.36], '贵阳': [106.71, 26.57], '无锡': [120.29, 31.59], '本溪': [123.73, 41.3], '克拉玛依': [84.77, 45.59], '渭南': [109.5, 34.52], '马鞍山': [118.48, 31.56], '宝鸡': [107.15, 34.38], '焦作': [113.21, 35.24], '句容': [119.16, 31.95], '北京': [116.46, 39.92], '徐州': [117.2, 34.26], '衡水': [115.72, 37.72], '包头': [110, 40.58], '绵阳': [104.73, 31.48], '乌鲁木齐': [87.68, 43.77], '枣庄': [117.57, 34.86], '杭州': [120.19, 30.26], '淄博': [118.05, 36.78], '鞍山': [122.85, 41.12], '溧阳': [119.48, 31.43], '库尔勒': [86.06, 41.68], '安阳': [114.35, 36.1], '开封': [114.35, 34.79], '济南': [117, 36.65], '德阳': [104.37, 31.13], '温州': [120.65, 28.01], '九江': [115.97, 29.71], '邯郸': [114.47, 36.6], '临安': [119.72, 30.23], '兰州': [103.73, 36.03], '沧州': [116.83, 38.33], '临沂': [118.35, 35.05], '南充': [106.110698, 30.837793], '天津': [117.2, 39.13], '富阳': [119.95, 30.07], '泰安': [117.13, 36.18], '诸暨': [120.23, 29.71], '郑州': [113.65, 34.76], '哈尔滨': [126.63, 45.75], '聊城': [115.97, 36.45], '芜湖': [118.38, 31.33], '唐山': [118.02, 39.63], '平顶山': [113.29, 33.75], '邢台': [114.48, 37.05], '德州': [116.29, 37.45], '济宁': [116.59, 35.38], '荆州': [112.239741, 30.335165], '宜昌': [111.3, 30.7], '义乌': [120.06, 29.32], '丽水': [119.92, 28.45], '洛阳': [112.44, 34.7], '秦皇岛': [119.57, 39.95], '株洲': [113.16, 27.83], '石家庄': [114.48, 38.03], '莱芜': [117.67, 36.19], '常德': [111.69, 29.05], '保定': [115.48, 38.85], '湘潭': [112.91, 27.87], '金华': [119.64, 29.12], '岳阳': [113.09, 29.37], '长沙': [113, 28.21], '衢州': [118.88, 28.97], '廊坊': [116.7, 39.53], '菏泽': [115.480656, 35.23375], '合肥': [117.27, 31.86], '武汉': [114.31, 30.52], '大庆': [125.03, 46.58]}; export default GeoCodingData;
在此处因为数据量有点大,所以我将它单独提出来作为一个文件,放在项目目录的data文件夹下,然后需要在我们的组件中定义state,里面存放一些迁徙图各个中心点的数据和后期创建地图后绘制图表时所要用到的数据,如下:
state = { BJData: [ [{name:'北京'}, {name:'上海',value:95}], [{name:'北京'}, {name:'广州',value:90}], [{name:'北京'}, {name:'大连',value:80}], [{name:'北京'}, {name:'南宁',value:70}], [{name:'北京'}, {name:'南昌',value:60}], [{name:'北京'}, {name:'拉萨',value:50}], [{name:'北京'}, {name:'长春',value:40}], [{name:'北京'}, {name:'包头',value:30}], [{name:'北京'}, {name:'重庆',value:20}], [{name:'北京'}, {name:'常州',value:10}] ], SHData: [ [{name:'上海'},{name:'包头',value:95}], [{name:'上海'},{name:'昆明',value:90}], [{name:'上海'},{name:'广州',value:80}], [{name:'上海'},{name:'郑州',value:70}], [{name:'上海'},{name:'长春',value:60}], [{name:'上海'},{name:'重庆',value:50}], [{name:'上海'},{name:'长沙',value:40}], [{name:'上海'},{name:'北京',value:30}], [{name:'上海'},{name:'丹东',value:20}], [{name:'上海'},{name:'大连',value:10}] ], GZData: [ [{name:'广州'},{name:'福州',value:95}], [{name:'广州'},{name:'太原',value:90}], [{name:'广州'},{name:'长春',value:80}], [{name:'广州'},{name:'重庆',value:70}], [{name:'广州'},{name:'西安',value:60}], [{name:'广州'},{name:'成都',value:50}], [{name:'广州'},{name:'常州',value:40}], [{name:'广州'},{name:'北京',value:30}], [{name:'广州'},{name:'北海',value:20}], [{name:'广州'},{name:'海口',value:10}] ], planePath: 'path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z', //迁徙图上的移动样式,目前是默认的小飞机,后期可以自己改 color: ['#F4EF8D', '#323296', '#CB4743'], //各个迁徙中心点的航线颜色 series: [], //eCharts绘制时所需的配置信息 mapview: null, //实例化地图后存放地图视图
2、定义完上述的基础数据之后,我们接下来进行迁徙图的绘制,这中间其实就是配置一些绘制迁徙图时所要用到的eCharts图表的配置信息,我们将这些配置信息全部压到state中的series数组中去,代码如下:
_initCharts=() => { const _self = this; let placeCenter = [ ['北京', this.state.BJData], ['上海', this.state.SHData], ['广州', this.state.GZData] ]; placeCenter.map((value, key) => { _self.state.series.push({ name: value[0] + 'Top10', type: 'lines', coordinateSystem: 'arcgis', zlevel: 1, effect: { show: true, period: 6, trailLength: 0.7, color: '#fff', symbolSize: 3 }, lineStyle: { normal: { color: _self.state.color[key], width: 0, curveness: 0.2 } }, data: _self._convertData(value[1]) }, { name: value[0] + ' Top10', type: 'lines', coordinateSystem: 'arcgis', zlevel: 2, symbol: ['none', 'arrow'], symbolSize: 10, effect: { show: true, period: 6, trailLength: 0, symbol: _self.state.planePath, symbolSize: 15 }, lineStyle: { normal: { color: _self.state.color[key], width: 1, opacity: 0.6, curveness: 0.2 } }, data: _self._convertData(value[1]) }, { name: value[0] + ' Top10', type: 'effectScatter', coordinateSystem: 'arcgis', zlevel: 2, rippleEffect: { brushType: 'stroke' }, label: { normal: { show: true, position: 'left', formatter: '{b}' } }, symbolSize: function (val) { return val[2] / 8; }, itemStyle: { normal: { color: _self.state.color[key] } }, data: value[1].map(function(dataItem) { return { name: dataItem[1].name, value: GeoCodingData[dataItem[1].name].concat([dataItem[1].value]) }; }) }); }); }
上面过程中用到了_convertData方法,主要用于数据的转换,代码如下:
_convertData=(data) => { let res = []; for (var i = 0; i < data.length; i++) { var dataItem = data[i]; var fromCoord = GeoCodingData[dataItem[0].name]; var toCoord = GeoCodingData[dataItem[1].name]; if (fromCoord && toCoord) { res.push({ fromName: dataItem[0].name, toName: dataItem[1].name, coords: [fromCoord, toCoord], value: dataItem[1].value }); } } return res;}
3、经过上述的操作,我们已经完成了底图的绘制和迁徙图的配置信息实例化,接下来就进行迁徙图的绘制操作。绘制过程要监听底图实例化的when方法,等待地图实例化完成之后再进行绘制,代码如下:
view.when(function() { _self.state.mapview = view; _self._drawCharts();});
_drawCharts=() => { const _self = this; const options = { url: 'https://js.arcgis.com/4.14/dojo/dojo.js', }; loadModules([ 'http://localhost/test/EchartsLayer.min.js' ], options).then(([ echartsLayer ]) => { console.log(_self.state.mapview) //_self.state.mapview.when(function(){ let chart = new echartsLayer(_self.state.mapview); let option = { title: { text: 'ArcGIS API for Javascript4.14扩展Echarts4之模拟迁徙', subtext: 'Develop By X北辰北', left: 'center', textStyle: { color: '#fff' } }, series: _self.state.series }; chart.setChartOption(option); //}); } ).catch((err) => { console.log('图表绘制失败,' + err); }); }
4、经过以上操作后我们就完成了迁徙图的绘制,在这里要注意的是,绘制迁徙图时要调用我们扩展的EchartsLayer图层类,这个图层类我们只需要将它放到本级服务器目录下即可,或者放在demo的项目文件夹下面,然后我们引入dojo文件的时候不用上述代码里的那样引用官网的dojo文件,而是自己下载dojo文件到demo项目里,然后在引入EchartsLayer图层类之前通过dojoConfig类配置下引用路径就好了。
5、以上过程完成了二维场景下迁徙图的绘制,三维场景下的绘制其实很简单,我们只需要将视图层换成三维就可以了,代码如下:
let view = new SceneView({ container: "mapview", map: map, scale: 50000000, center: [107.246152,34.414465] });
6、以上就是迁徙图在二维和三维下的绘制过程。
总结
本文主要介绍下ArcGIS JS API高版本和eCharts 4版本的结合使用,在这篇文章里最重要的是我们实现了两个图表库中的坐标系转换工作,只要完成了这个步骤,那接下来的绘制其实跟eCharts的普通绘制是相差不大的,后期我会继续更新EchartsLayer这个图层类,迎合最新版的ArcGIS JS API,欢迎大家持续关注。
加webgis群和许老师及同行交流,可以加QQ群1137474406,微信wangkai_hh