背景
之前系统中有关地理信息的图表是直接使用国家/省市名称, 通过柱状图来展示地区数据的区别. 在重构的时候想优化一下这块的展示, 直接使用中国地图/世界地图来展示数据的分布, 我们使用的图表组件是highcharts
, 看了一下虽然自带map
组件, 但是有个比较严重的问题, 中国地图不标准, 所以我们需要自己生成底图数据, 然后自己渲染.
又研究了一下其他的组件, 发现antv
也支持地图绘制, 而且对于自定义地图支持得比较完善, 功能和性能也都比highcharts
好. 在支付宝看疫情分布时候发现了和这里demo
一模一样效果的地图(感觉就是直接使用这个组件生成的), 支持的地图数据是geojson
也比较常见, 所以最终使用antv
实现地图渲染.
地图渲染
首先按官方文档, 实现地图组件渲染, 然后获取数据后将数据按图层加载到组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| if (!antvL7Scene.current) { antvL7Scene.current = new Scene( currentShowItem.type === 'china-map' ? { id: chartRef.current, map: new Mapbox({ pitch: 0, style: 'blank', center: [107.042225, 37.66565], zoom: 2.5, }), } : { id: chartRef.current, map: new Mapbox({ pitch: 0, style: 'blank', center: [0, 40], zoom: 0.5, }), }, ); }
|
生成图层数据, 颜色是根据数据量大小由浅入深的, 这里注意下需要对Sea
就是海的部分的事件不要处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| const genAntvL7Options = ( result: ESResponse, indicators: DashboardItemChart['indicators'], dimensions: DashboardItemChart['dimensions'], type: typeof CONSTANT.DASHBOARD_CHART_TYPE_CONFIG[number]['type'], antvL7MapData: any, colorOptions: string[], ): { baseLayer: ILayer; lineLayer: ILayer; tipsPopup: IPopup; dataSource: Record<string, number | string>[] } => { const firstDimension = dimensions[0];
const indicator = indicators[0];
const aggsMapData: Record<string, number> = {}; const dataSource: Record<string, number | string>[] = [];
let maxNum = 0; let minNum = 0; _.map(antvL7MapData.features, (feature) => { const areaValue = aggsMapData[feature.properties.name]; if (areaValue) { _.set(feature.properties, 'value', areaValue); maxNum = maxNum > areaValue ? maxNum : areaValue; minNum = minNum < areaValue ? minNum : areaValue; } else if (feature.properties.name) { _.set(feature.properties, 'value', 0); } }); maxNum = maxNum + 1; const stepNum = (maxNum - minNum) / (colorOptions.length - 1);
const tipsPopup = new Popup({ offsets: [0, 0], closeButton: false, closeOnClick: false, }); const baseLayer = new PolygonLayer({}) .scale('value', { type: 'quantile', }) .source(antvL7MapData) .color('value', (value) => { if (value === 0) { return colorOptions[0]; } for (let i = 0; i < colorOptions.length - 1; i++) { if (value >= minNum + stepNum * i && value < minNum + stepNum * (i + 1)) { return colorOptions[i + 1]; } } }) .shape('fill') .style({ opacity: 1, }); baseLayer.on('mousemove', (e) => { if (e.feature.properties.name === 'Sea') { return; } tipsPopup.setLnglat(e.lngLat).setHTML(`<span>${e.feature.properties.name}: ${e.feature.properties.value} (${indicator})</span>`); tipsPopup.open(); }); baseLayer.on('mouseout', (e) => { tipsPopup.close(); });
const lineLayer = new LineLayer({ zIndex: 2, }) .source(antvL7MapData) .color('#000') .size(0.3) .style({ opacity: 1, });
return { baseLayer, lineLayer, tipsPopup, dataSource }; };
|
将生成的图层添加到地图中
1 2 3 4 5 6 7 8
| if (antvL7Scene.current.getLayers().length > 0) { antvL7Scene.current.removeAllLayer(); } const layerResult = await parseAntvL7Layer(item.dataCache.chartOptions); antvL7Scene.current.addLayer(layerResult.baseLayer); antvL7Scene.current.addLayer(layerResult.lineLayer); antvL7Scene.current.addPopup(layerResult.tipsPopup); item.dataCache.dataSource = layerResult.dataSource;
|
数据处理
其实这里才是最重要的, 上面代码中的antvL7MapData
就是这一步得到的. antv
支持的底图数据是geojson
格式, 也提供了获取方式, 可以在http://datav.aliyun.com/tools/atlas/index.html
获取到高德地图开放的地图数据. 这里下载到的数据细致到了市, 目前的需求是只需要省的数据, 所以将数据处理一下即可, 然后在highcharts
能下载到完整的全球地图数据, 再将里面的china
部分替换成我们的数据即可. 将这两份数据存为两份本地json
, 在代码加载的时候动态引入, 可以减少代码体积(虽然写完之后看起来简单, 但是寻找资料和构造这份数据的过程才是最耗时的).
1 2 3 4 5 6
| const parse = { parseAntvL7Layer: async (result) => { const antvL7MapData = _.cloneDeep((await import('../../../../static/map/china.geo.json')).default); return genAntvL7Options(result, indicators, dimensions, 'china-map', antvL7MapData, ANTV_MAP_COLORS_CHINA); }, };
|