Status: 收集信息中
Last Updated: 2022-01-20

背景

harp.glHERE 公司开源的一款基于 Three.js 搭建的地图渲染引擎。

因为最近在从事地图可视化方向,并且团队在使用和建设基于 Three.js 的技术栈,因此便有了一定的想法阅读它的源码,看能否得到一些思路。

初印象

harp.gl 是在 2018 年底,开始在 Github 上进行开发活动的。到目前也有差不多三年了。参见其 Github Contributors.
image.png
通过浏览它的基本示例的使用代码,不难发现它具有明显的 Three.js、Mapbox GL JS 的影子,甚至,在一些比较小的接口上,还有一些 Deck.gl 的影子。看来它在设计的时候,有较多地参考到这几个先于它出现的渲染引擎,前者是它基于的 3D 渲染引擎,后面二者是两个在地图领域被广泛使用的渲染引擎。

  1. const canvas = document.getElementById('map');
  2. const mapView = new harp.MapView({
  3. canvas,
  4. theme: "https://unpkg.com/@here/harp-map-theme@latest/resources/berlin_tilezen_night_reduced.json"
  5. });
  6. // center the camera to New York
  7. mapView.lookAt({
  8. target: new harp.GeoCoordinates(40.70398928, -74.01319808),
  9. zoomLevel: 17,
  10. tilt: 40,
  11. });
  12. const mapControls = new harp.MapControls(mapView);
  13. const ui = new harp.MapControlsUI(mapControls);
  14. canvas.parentElement.appendChild(ui.domElement);
  15. mapView.resize(window.innerWidth, window.innerHeight);
  16. window.onresize = () => mapView.resize(window.innerWidth, window.innerHeight);
  17. const vectorTileDataSource = new harp.VectorTileDataSource({
  18. authenticationCode: 'YOUR-TOKEN HERE',
  19. });
  20. mapView.addDataSource(vectorTileDataSource);

其尝试走的商业模式,很 Mapbox GL JS 非常相似,目前处于 软件完全开源(Apache License 2.0),但是对于使用 HERE 的数据源,需要收费。不知道它未来会不会走类似 Mapbox GL JS 的路线,一旦软件使用的人多了,后期转变为了收费软件。但从现阶段看,其应用的广泛程度以及项目质量(不管是代码质量、文档、生态),还远不足以支撑它进入收费软件行列。

核心模块

MapView

整个引擎的入口模块为 MapView,其上挂载着 Three.js 的 RendererSceneCamera,以及 harp.gl 自身定义的一众模块的 References,是一个最高层的集成模块。

其主要概念、行为的集成情况,如图所示:
MapView 集成图

关于其设计/代码风格的一些看法:

  • 对于 THREE.Renderer 的构造参数,不是完整透传,而是选择性地传递了部分属性。
  • Sub Renderers 其实只是一些 THREE.Object3D 构造器,不适合被称为 Renderer。实质上整个 MapView 只存在一个 THREE.Renderer 实例负责对所有 Objects 进行渲染。
  • 大部分变量的命名做的比较好,能够让人很容易理解意图。
  • 代码中存在大量的双向 References,模块间的依赖关系错综复杂,健康性和可维护性堪忧。

Tile 系列模块

Three.js 扩展模块

基于 Three.js 的模块进行扩展的模块

MapAnchor

  • 对 THREE.Object3D 进行继承式扩展。而其实际扩展的功能,是一些地图领域的概念、拾取功能,以及样式。个人认为这是一种有损可组合性的扩展方式。
  • 另外,因为扩展的字段都是optional的,所以从接口上说,它能够兼容 THREE.Object3D。

接口:

type MapAnchor<T extends THREE.Object3D = THREE.Object3D> = T & {
    /**
     * The position of this [[MapAnchor]] in {@link @here/harp-geoutils#GeoCoordinates}.
     * @deprecated Use [[anchor]] instead.
     */
    geoPosition?: GeoCoordinates;

    /**
     * The anchor of this Object3D in {@link @here/harp-geoutils#GeoCoordinates}
     * or world coordinates.
     */
    anchor?: GeoCoordLike | Vector3Like;

    /**
     * Flag defining if the object may be picked.
     *
     * @note By default all objects are pickable even if this flag is undefined.
     */
    pickable?: boolean;

    /**
     * The styleSet that owns this map object.
     *
     * @remarks
     * This property is used together with [[Theme.priorities]] to compute the render
     * order of this map object.
     */
    styleSet?: string;

    /**
     * The category of this style.
     *
     * @remarks
     * This property is used together with [[Theme.priorities]] to compute the render
     * order of this map object.
     */
    category?: string;

    /**
     * Whether to draw the anchor on top of labels.
     * @defaultValue false
     */
    overlay?: boolean;
};

兼容 Object3D 的用途:

window.addEventListener("click", (evt) => {
   //Create the three.js cube
   const geometry = new THREE.BoxGeometry(100, 100, 100);
   const material = new THREE.MeshStandardMaterial({ color: 0x00ff00fe });
   const cube = new THREE.Mesh(geometry, material);
   cube.renderOrder = 100000;

   //Get the position of the click
   const geoPosition = mapView.getGeoCoordinatesAt(evt.pageX, evt.pageY);
   cube.anchor = geoPosition;

   // 此处的接口定义是 MapAnchor。
   // 但因为 MapAnchor 在接口上兼容 THREE.Object3D,所以可添加 Object3D 实例。
   //Add object to the map
   mapView.mapAnchors.add(cube);
   mapView.update();
});

工作机制

坐标参考系

herp.js 接口中的 GeoJSON,其坐标参考系使用的是 经纬度,并且坐标值顺序上,与通常的[Longitude, Latitude] 不同,它用的是 [Latitude, Longitude]。

Note: GeoJSON uses the format [Longitude, Latitude] for the coordinate system, while harp.gl uses [Latitude, Longitude]. Be careful not to get these confused!


与 GeoJSON 相关的关键模块

  • GeoJsonDataProvider
    • m_tiler 在处理 input
      • this.m_tiler.registerIndex(this.name, this.input)
    • ITile
    • GeoJsonTiler
      • updateIndex
        • Mapbox 开发的 geojson-vt lib
          • 功能:将 「GeoJSON」 转换成「MVT 的 JSON形式」。
          • 用途:
            • 从前端提高渲染大数据量时的性能;
            • 后端用于生成 MVT。
          • 局限性:GeoJSON-VT only operates on zoom levels up to 24.

渲染流程

MapView.render
MapView.render 流程图

瓦片管理

基于 MapView.render的流程分析,可初步判断,对于瓦片管理的关键函数,应该在于 VisibleTileSet.updateRenderListTileObjectRenderer.render
VisibleTileSet.updateRenderList 流程图

TileObjectRenderer.render 流程图

任务调度

(Hold on. )
MapViewTaskScheduler 做了什么?

分包机制

  • @here/harp-mapview :绝大多数核心模块。
  • @here/harp-datasource-protocol:这是个什么模块?datasource 的公共处理机制?
  • @here/harp-vectortile-datasource:vector tile data source 相关模块。其实,内部也包含了 geojson data source 相关模块。
  • @here/harp-xxx-datasource:各种 data source 模块。
  • @here/harp-material:基于 Three.js Materail 进行扩展的自定义 Materials.
  • @here/harp-utils :工具模块, 比如:UriResolver

应用分析

harp.gl 是否适合作为我们团队的地图可视化的基础架构?
harp.gl 中是否有某些模块,可以直接被我们用于填补 Three.js 到地图应用场景间的缺口?

TODO

  • 搞清楚其地图领域关键问题的工作机制,以及模块的通用性
  • 坐标参考系
  • 数据源
  • GeoJSON
  • MVT
  • 瓦片化
  • 可以通过 各个包的 README/官方文档 来了解其中模块的职责。