08月31, 2022

画地图

可视化项目中,经常会遇到绘制地图的需求。除了地图自身的寻路、标注等需求外,可视化地图应用还会将业务数据在地理信息上进行拆分,并通过某种方式在地图上进行呈现,从而揭示出业务数据与地理数据的相关性。

像上图所示,即为2011年香港18区的人口密度图。

这篇文章里,我们就来讨论下,一个符合常识的在线地图的绘制方案。在正式开始之前,我们先明确一点,由于地图绘制是一项严肃且异常严谨的工作。因此,规范使用地图,一点都不能错!。本文旨在揭示在线地图的技术原理,如在产品中使用,需使用合规地图,并应取得政府认可的审图号并在产品中进行展示。

徒手画

这种方案,我们的可定制化性最高。我们可以解析地理数据做包括样式、交互的深度定制。

以中国地图为例。为了勾划地图,我们需要首先下载中国地图的数据。可以从这里下载最新的数据。

数据格式是GeoJSON。你可以用任意文本编辑器打开这个文件。我们看到,这个文件,首先是一个JSON文件,它符合JSON数据格式的一般要求。同时,针对地理数据,GeoJSON做了一定的扩充,并形成了GeoJSON标准:RFC7946

我们可以简单的整理一下:

  1. GeoJSON总是由一个单独的对象组成。这个对象(指的是下面的GeoJSON对象)表示几何、特征或者特征集合。 GeoJSON对象可能有任何数目成员(名/值对)。
  2. GeoJSON对象必须有一个名字为"type"的成员。这个成员的值是由GeoJSON对象的类型所确定的字符串。
  3. type成员的值必须是下面之一:"Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", "GeometryCollection", "Feature", 或者 "FeatureCollection"。
  4. GeoJSON对象可能有一个可选的"crs"成员,它的值必须是一个坐标参考系统的对象。如果缺省,目前的坐标系以WCG84坐标为主,也可以按照数据和绘图协商解决。
  5. GeoJSON对象可能有一个"bbox"成员,它的值必须是边界框数组。bbox成员的值必须是2*n数组,n是所包含几何对象的维数,并且所有坐标轴的最低值后面跟着最高者值。bbox的坐标轴的顺序遵循几何坐标轴的顺序。除此之外,bbox的坐标参考系统假设匹配它所在GeoJSON对象的坐标参考系统。
{ 
    "type": "Feature",
    "bbox": [-180.0, -90.0, 180.0, 90.0],
    "geometry": {
    "type": "Polygon",
    "coordinates": [[
         [-180.0, 10.0], [20.0, 90.0], [180.0, -5.0], [-30.0, -90.0]
      ]]
    }
  ...
  }

掌握了这些标准,我们可以借助一些库,来实现地图的绘制。

比如,通过d3.js,我们可以使用SVG作为渲染引擎进行地图绘制。这里是一个示意

这里面说几个关键点:

  1. 我们采用的是最新的d3.js的7.6.1版本。这个版本对API做了一些重构,和网络上的一些积存代码有不兼容的地方。
  2. 由于GeoJSON的coordinates指示的是经纬度坐标。我们d3-geo的geoMercator方法是建立从球面坐标(经纬度)到平面坐标x轴y轴的投影。
  3. d3
     .scaleOrdinal()
     .range(
         d3.quantize(
             d3.interpolateWarm, data.features.length
         )
     )
    
    为取颜色比例尺方法。早期的代码d3.schemeCategory20v5版本被废弃d3.quantize表示均匀分布的插值样本。
  4. 这里实现了自定义内阴影效果,利用的是svg的filter。这里svg的filter是非常强大的功能,大家可以从这里查看svg的滤镜。

大家主要看这块实现内阴影的部分,大家可以从中掌握filters互相叠加的思路和语法。

    <filter id="inset-shadow">
        <!-- 投影偏移 -->
        <feOffset dx="0" dy="0"/>
        <!-- 投影模糊 -->
        <feGaussianBlur stdDeviation="28.84" result="offset-blur"/>
        <!-- 反转投影使其变成内投影 -->
        <feComposite operator="out" in="SourceGraphic" in2="offset-blur" result="inverse"/>
        <!-- 内投影附加黑色 -->
        <feFlood flood-color="#000000" flood-opacity=".5" result="color"/>
        <feComposite operator="in" in="color" in2="inverse" result="shadow"/>
        <!-- 把内投影显示在图像上 -->
        <feComposite operator="over" in="shadow" in2="SourceGraphic"/> 
    </filter>

气氛已经烘托到这里了,不如我们直接上个3d版本的地图,该程序思路详见这篇文章

除了GeoJSON之外,还有一种简化的TopoJSON,它是简化的GeoJSON版本,使用这个版本的json,可以使得传输数据减少。

上图解释了TopoJSON的简化方案。当然,有了两种格式的任一种,都可以进行互转。这里是一个在线地址。

GeoJSON除了从线上地址下载,对于特定区域的GeoJSON,也可借助前面提到的阿里云在线工具生成或ArcGIS的相关套件自行发布。

借助可视化库

上面的方案可以满足高度定制化的需求。对于常规的地图操作,我们可以借助可视化类库完成。如HighCharts、echarts以及Antv L7等等都可以完成常规的可视化地图描画以及常见的地图效果。

我们以echarts为例。一方面,我们仍需要下载GeoJSON代码,另一方面,我们需要对echarts做一些配置项处理。

这里是我们给出的一个示例。作为开源图形库,echarts给出了相当多的配置项。你可以参阅这里进行配置。

基于地理坐标的数据,均可在series字段中配置,并显示在对应的地理坐标中。

这边只指出一个问题,默认的,在调用echarts.registerMap时候,echarts会把南海诸岛用小窗的形式显示,如果想实现按原地理位置的显示,可以参考这里。核心的思路就是用两个地图,一个负责画主体,一个负责画南海诸岛和九段线,画九段线的注册时候,不要使用“china”,绘制之后进行叠放。

而AntV L7 plot默认是直接在下方显示九段线的,如果你想实现在echarts的默认效果,可以参考这里

SaaS服务

除了上述这些方案,还有一种是借助SaaS服务。国内的有高德、百度、腾讯、天地图、51world等等,国外有MapBox、Cesium、ArcGIS等等。它们提供了各具特色的服务。比如MapBox提供了完整的瓦片地图和地图渲染服务、Cesium则强在三维地图渲染等方面、ArcGIS提供了一整套工具链路和数据托管以及跨语言跨平台的接口方案。国内的SaaS服务商则在地图合规性上更加完善。使用国内SaaS服务商提供的服务,默认会提供审图号显示,而天地图的数据对于政府机构的地图使用则更为贴心。51world则是借鉴云游戏的思路,直接是在服务端用3D引擎渲染好视频流,通过JS接口下发到浏览器,能够规避渲染客户端机的配置导致的卡顿。

使用这些服务,往往需要从其官方网站注册用户,以获取运行时token。服务商的服务端一般会根据token进行权限认证和服务配额的控制。

一般来讲,在其官方网站上会自带一个测试token,这个token不稳定,且配额较小,有的还会验证请求来源。开发测试时候可以使用,正式上线还是建议使用正式的token。

这里是高德地图的一个地图渲染示例。

趣谈

地图绘制是一个比较有门槛且严谨的事情。这里在整理资料时候也发现一些之前没注意到的信息。比如我国地图黑龙江处于内蒙古境内的飞地:加格达奇。开始的时候以为地图画错了,真正了解下来,才在飞地的知识版图上又增加了一块。

参考资料

  1. https://zhuanlan.zhihu.com/p/350866070
  2. https://mp.weixin.qq.com/s/VkyAkbmqaarZ3_G7lurpVQ
  3. https://baike.baidu.com/item/GeoJson/12011566?fr=aladdin
  4. https://oschina.net/translate/geojson-spec#bounding-boxes

本文链接:https://www.leon82.com/post/map.html

-- EOF --

Comments