起因
项目最开始实现的地图显示的是最常见的一个通用的世界地图,如图
这种世界地图依据本初子午线绘制,中国在靠右的位置,而客户想要的效果是把中国移到中间的位置来
上图使用了echarts绘制地图,同时使用了d3.geoEqualEarth的投影功能(本人对d3不熟)
既然有需求,那咱们就得做
上面的地图是使用echarts来绘制的,查看了echarts的文档发现无法实现这样的功能
作为一个只会使用echarts加载geojson的不算giser的giser,完全不知道怎样才能使中国移动到中间来
尝试的过程
最开始的想法是直接去网上找到现成的中国在中心位置的地图来绘制,但是找了很久都没有找到效果比较好的这种地图,而且地图上是有打点的,当使用中国在中心的这种地图之后,坐标对不上的问题又该怎么解决,没有思路
经过好几天的查找,后来在csdn上找到类似的文章 ArcGIS中央经线的修改-CSDN博客 如何实现一个以中国为中心的世界地图
然后我知道了这种地图变换的一个更加准确的称呼,修改地图中央经线
之后便开始研究学习了ArcGIS这个软件,并花费了68元得到激活码,安装了软件来尝试处理问题
安装软件之后,根据网上找到的世界地图shp文件,导入到ArcGIS软件中显示出来
根据上面修改中央经线的操作,确实在ArcGIS上得到了中国居中的地图显示,并且知道了这个操作称之为投影,省略操作ArcGIS软件过程中遇到的种种问题
最终,我又回到了原点,如果ArcGIS导出了这样的一幅地图的GeoJSON,但是地图坐标完全变了,后台返回的点位信息是携带坐标的,两个坐标系对不上,是无法打点的,这个问题无法处理,只能放弃ArcGIS软件来实现的方法
现在,总结一下从ArcGIS这里学到的知识:
- 是有办法直接上根据通用地图的GeoJSON转换的到以中国为中心的地图的,这个操作叫修改地图中央经线
- 修改地图中央经线需要用到投影操作
基于上述已知知识的第二点,与原地图绘制的代码结合起来,我想到一个问题:d3能不能实现这个功能?
初见成果
于是我便在网上查找d3修改地图中央经线的示例
代码层面一无所获
但是思想层面,我察觉到了一些关联
D3 GEO应用专题(一):绘制旋转的3D地球-CSDN博客
查看了不少这样的地图示例之后,我了解到d3的投影是可以旋转的
然后我便开始了尝试
经过多次尝试(对d3不熟,传参不正确导致api调用没有效果)之后,发现d3的旋转 rotate 方法就是改变地图中央经线的方法 d3.geoEqualEarth().rotate([-150, 0])
然后我尝试直接用d3来实现地图,来替换echarts实现地图,但过程极为艰难,不是这里显示不出来,就是那里显示不正常,省略中间的种种尝试之后,最终还是决定用d3结合echarts来实现这个功能
如图,会发现d3旋转投影后echarts绘制的地图显示不正常
与之前的地图比起来,上面投影旋转过的地图,首先背景不见了,其次有些地区,比如格陵兰岛横跨东西半球了
修复问题
修复之前,建议先了解一下GeoJSON的规范 【第三章 数据格式】geojson格式详解 - 知乎 (zhihu.com),重点要了解geometry的组成,有些什么type,每种type的定义和数据结构,这对修复显示问题有很大的帮助
经过两个地图的比较以及输出投影后的坐标来分析,可以知道投影过后的坐标变化非常大
以背景地图为例,背景地图的范围大概是 [-180, -90]到[180, 90](取对角坐标)的矩形(投影后变成带椭圆的形状),现在经过旋转后的投影,这个矩形的左右两边的坐标也就是经度快重合到一块了,所以显示出来是一条线,就看不到背景颜色了
- 修复背景显示问题
从地图上可以观察到,经纬度线的显示是没什么问题的,那么要修复背景的坐标需要解析参考经纬度线的坐标,通过输出投影前与投影后的经纬度线的坐标对比可以得出
通过比较得到投影后的经度范围(因为只在水平方向旋转,只要关注经度就行)为[0.4988453483708213, 953.1078059229408],反查到投影前的经度范围为[-32.400000000000006, -30]
那么我们只要把背景生成的算法改一下,原算法如下:
// 绘制地图背景
// 输出为GeoJSON中的 coordinates部分,type为Polygon
function generateEarthPolygon() {
const coordinates = [[-180, -90]]
for (let index = -90; index <= 90; index++) {
coordinates.push([180, index])
}
for (let index = 90; index >= -90; index--) {
coordinates.push([-180, index])
}
return [coordinates]
}
修改后
// 绘制地图背景
// 输出为GeoJSON中的 coordinates部分,type为Polygon
function generateEarthPolygon() {
const coordinates = [[-180, -90]]
for (let index = -90; index <= 90; index++) {
coordinates.push([-32.400000000000006, index])
}
for (let index = 90; index >= -90; index--) {
coordinates.push([-30, index])
}
return [coordinates]
}
地图背景显示正常了
- 修复经纬度线显示
可以看到地图上有一根经纬度比较显眼,那是因为经过投影转换后,有两根线挨在一起了,方法就像修复背景一样,将投影后的坐标打印出来,根据投影后坐标分析出是哪两根线的问题,修改即可,相同的解决方法,详细过程不再赘述
- 修复顶部的格陵兰岛显示问题
可以看到旋转投影后格陵兰岛发生了极大的变形,横跨了东西半球
这是因为格陵兰岛转换前是一个完整的图形,然后转换后,这个完整图形里面有的坐标还在西半球,但是有的坐标在东半球去了,然后依旧是按照原来的顺序来连线绘制,就从西半球一直连线连接到东半球了。
从实际上来说,因为这里旋转的角度不够,所以格陵兰岛没有全部转换到东半球去,那么渲染出来的效果应该是西半球剩余一部分,东半球有另一部分,这样显示才正确。
解决方法依旧是输出投影后的坐标,分析投影坐标
通过删除geojson来渲染发现,格陵兰岛只有索引为13的这堆坐标有问题
分析投影后的坐标,得到最大最小经度如图所示,浏览一遍坐标数据后,写出如下算法:
这样,我们把西半球的坐标和东半球的坐标分别提取出来,把原来的一个图形,现在变成绘制两个图形,渲染效果如下图
- 修复南极洲的显示
可以看到南极洲,显示也是错乱的,那南极洲的修复方法也是和上面的格陵兰岛是一样的,只是南极洲的图形更复杂,坐标也更多,过程是类似,这里也不再赘述。
部分修复代码
生成背景coordinates的代码
function generateEarthPolygonRotate150() {
const coordinates = [[-180, -90]]
for (let index = -90; index <= 90; index++) {
coordinates.push([-32.400000000000006, index])
}
for (let index = 90; index >= -90; index--) {
coordinates.push([-30, index])
}
return [coordinates]
}
修复格陵兰岛的代码
export default function fixGlld(geoJson) {
return {
...geoJson,
features: geoJson.features.map(obj => {
if (obj.properties.name !== '格兰陵岛') {
return obj
}
return {
...obj,
geometry: {
...obj.geometry,
coordinates: obj.geometry.coordinates.map((polygon, polygonIndex) => {
if (polygonIndex !== 13) {
return polygon
}
const [firstLineString] = polygon
return [
[
...firstLineString.slice(0, 2),
...firstLineString.slice(4, 137),
...firstLineString.slice(-1)
],
[...firstLineString.slice(2, 4), ...firstLineString.slice(137, -1)],
...polygon.slice(1)
]
})
}
}
})
}
}
总结
- 依赖性
整个处理过程与修复的代码,和使用的geojson是强相关的,也就是说如果换一个坐标或者点位数量不一致的其他的世界地图geojson,那么上面的修复代码将需要重新分析重新编写。
- 修复geojson的两个方向
一种是为了减少项目里面加载的资源的方向,我们可以将修复的代码编写成动态的根据原geojson生成新的geojson,比如 fixNjz(worldGeoJsonZh) 这样的方法,方法接收geojson,返回新的geojson。
好处是:这样我们不用修改原来的geojson的内容,我们也不需要多添加一份geojson-rotate-150.json类似的文件,假设原项目其他地方需要用到原geojson,那么上述的做法不会对项目其他地方造成影响
坏处是:会轻微的占用一些性能,因为每次加载的时候都得计算
另一种是根据原来的geojson生成一份新的geojson文件
好处是:不用费心写那些修复代码,每次加载地图没有多余的计算
坏处是:得在某些地方写好注释,告诉别人这份geojson是怎么得来的,只在某些特殊情况下能展示(d3.geoEqualEarth().rotate([-150, 0])),其他情况下,展示可能不太正常
-
学到的知识
- 地图的这种旋转称之为修改中央经线的位置
- 修改中央经线是使用投影的方式来处理
- d3的geo模块中的rotate方法可以修改中央经线
- js编写的地理分析库(Turf.js | Advanced Geospatial Analysis (turfjs.org))- 这是在解决上面问题的过程中发现的,虽然本次没有用到,但是好东西收藏了(就相当于我会了)
-
最后
解决本次问题的过程中,没有什么复杂的,有技术难度的操作,全是体力活
分享完毕