Skip to content
目录

ThreeLayer

ThreeLayermaptalks.three插件里

WARNING

maptalks.three作者是社区人员,不是专职维护人员 ,所以其没有那么多时间天天盯着three插件。

@maptalks/gl-layersmaptalks.three备注
诞生时间最近较早一开始maptalks的3d功能主要有three插件提供
维护官方团队社区人员
性质正规军野生的
面向的用户所有用户喜欢折腾和捣鼓的用户
用户素质要求没有要求对three比较熟悉的二次开发能力的用户
侧重点对maptalks核心库的扩展业务里那些花里胡哨的
功能矢量切片,倾斜摄影,地形,3d模型,空间分析等业务里那些花里胡哨的
上手难度简单,创建图层,传数据配置样式即可中等,需要用户懂three,是么材质啊等

安装

sh
npm i maptalks

npm i maptalks.three

或者

html
<script
    type="text/javascript"
    src="https://maptalks.com/api/maptalks.min.js"
></script>
<script
    type="text/javascript"
    src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"
></script>

<!-- <script
    type="text/javascript"
    src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.min.js"
></script> -->

使用方式

js
import { ThreeLayer } from "maptalks.three";

const layer = new ThreeLayer(id, options);

WARNING

如果你使用umd包,在页面里用script标签引入,maptalks.three里的所有变量将自动挂载到maptalks命名空间

html
<script
    type="text/javascript"
    src="https://maptalks.com/api/maptalks.min.js"
></script>
<script
    type="text/javascript"
    src="https://unpkg.com/maptalks.three@latest/dist/maptalks.three.js"
></script>
<script>
    console.log(maptalks);
    console.log(maptalks.BaseObject);
    const layer = new maptalks.ThreeLayer(id, options);
</script>

定位

maptalks.three主要价值是对 maptalks gl生态的补充, 所以当你的业务的需求能用 maptalks-gl 解决时请使用maptalks-gl ,只有当业务里需要自定义图形等时我们在引入 maptalks.three

  • 常规的建筑物,行政区,道路,点的图形的功能需求maptalks-gl都是可以完成的
  • three插件定位为拥有一定二次开发能力的用户的,其不像 deck.gl那样, 用户配置下参数和样式就可以了,three插件要求你对three比较熟悉
  • 当且仅当gl插件不能完成业务需求时在使用 maptalks.three 例如下面这些效果,这些图形都具有强的自定义,且gl插件没有这些功能

关于维护

maptalks.three作者是社区人员,不是专职维护人员 ,所以其没有那么多时间天天盯着three插件。

维护原则:

  • 核心bug会第一时间fix
  • 不重要的功能和性能等全看作者心情和时间了,不会第一时间去处理的
  • three插件里的功能已经基本稳定,不会做大的功能添加
  • 专注于bug修复和性能优化,或者开放一些自定义功能的接口等

所以能用maptalks-gl搞定的功能请使用它

使用方式

three插件里主要提供了两个类:

  • ThreeLayer 图层
  • BaseObject 图形的基类

ThreeLayer

ThreeLayer是three插件里图形管理的图层,所有的图形都应该有其管理

  • 坐标转换,经纬度转webgl坐标coordinateToVector3
  • 米转webgl长度distanceToVector3
  • 海拔米转webgl长度altitudeToVector3
  • 默认图形的构造 toBar,toExtrudePolygon,toModel
  • 图形的添加和删除addMesh,removeMesh
  • 其他等
js
//coordinates to webgl vector3
const vector3 = threeLayer.coordinateToVector3([120, 31]);
//meter to webgl vector3
const vector3 = threeLayer.distanceToVector3(100, 100);
//altitude to webgl vector3
const vector3 = threeLayer.altitudeToVector3(100, 100);

其提供了默认图形构造的代理方法:

  • toExtrudeLine
  • toExtrudePolygon
  • toBar
  • toModel
  • toExtrudePolygons
  • toPath
  • toFatLine
  • toLine
  • toTerrain
  • toHeatMap
  • 其他等

BaseObject

BaseObject 是three插件里图形的基类,其设计时就尽可能的使其和maptalks的Geometry类似,和Geometry的使用方式基本相同, 比如图形构造,事件监听,气泡绑定等

three插件里提供了常见的功能:

  • ExtrudePolygon polygon拔高
  • ExtrudeLine 线路拔高
  • Bar 立体柱子
  • Path 路径
  • FatLine 像素线

TIP

这些图形都是基于BaseObject的,都是BaseObject的子类

js
const bar = threeLayer.toBar(coordinate, options, material);
const line = threeLayer.toExtrudeLine(polyline, options, material);
const bar = three.toBar(coordinate, options, material);

常用方法

  • _createMesh(geometry,material) 创建内部的three object3d 对象为Mesh
  • _createGroup() 创建内部的three object3d 对象为Group
  • getObject3d() 获取 内部three原生的object3d对象
  • _initOptions(options) 初始化配置信息
  • isVisible()
  • show()
  • hide()
  • setAltitude(altitude)
  • setId(id)
  • getId()

自定义图形

TIP

自定义图形需要你对three比较熟悉,如果你不熟悉three请先熟悉它

从threeLayer的各种图形代理方法,你应该发现自定义图形的规律了

  • 基于BaseObject
  • 自定义的图形设计如下:
js
//default values
const OPTIONS = {
    radius: 100,
    altitude: 0,
};

/**
 * custom Circle component
 *
 * you can customize your own components
 * */

class Circle extends maptalks.BaseObject {
    constructor(coordinate, options, material, layer) {
        options = maptalks.Util.extend({}, OPTIONS, options, {
            layer,
            coordinate,
        });
        super();
        //Initialize internal configuration
        // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
        this._initOptions(options);
        const { altitude, radius } = options;
        //generate geometry
        const r = layer.distanceToVector3(radius, radius).x;
        const geometry = new THREE.CircleBufferGeometry(r, 50);

        //Initialize internal object3d
        // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140
        this._createMesh(geometry, material);

        //set object3d position
        const z = layer.altitudeToVector3(altitude, altitude).x;
        const position = layer.coordinateToVector3(coordinate, z);
        this.getObject3d().position.copy(position);
        // this.getObject3d().rotation.x = -Math.PI;
    }
}
  • 构造器的参数规律:
参数1参数2参数3参数4
坐标或者Geometry配置选项材质threeLayer

提示

针对大数量的,three插件里基本都提供了复数形式的图形,比如

  • ExtrudePolygons
  • ExtrudeLines
  • Bars

所以当数据量大或者你感觉渲染比较卡时请使用这些方法

three插件里还提供了worker的支持,当图形开启asynchronous选项是时会自动的在worker里去构造图形,以此来解决主线程卡顿 问题

js
const polygons = threeLayer.toExtrudePolygons(
    polygons,
    { asynchronous: true },
    material,
);

polygons.on("workerload", (e) => {
    //do some things
});

WARNING

图形开启worker时,图形构造是异步的,所有如果需要获取图形的一些图形信息,需要在workerload事件里

使用建议

ThreeLayer不要去大量的new,不要频繁的的去add和remove,一个地图上一般只有一个ThreeLayer,因为ThreeLayer里要创建 three的渲染器,相机,场景等对象,这些对象创建还是比较吃资源的,即一个ThreeLayer永远的常驻与地图上,应该通过不断地通过 添加和删除图形来操作

如果你喜欢批量的操作图形,比如图形的批量添加和删除等,你可以:

  • 把ThreeLayer看成ThreeScene
  • 自己模拟一个虚拟的图层或者组,参考three里的Group来实现图形的批量添加和删除等

这里只是简单的贴个示意代码

js
class VirtuallyGroup {
    constructor(layer) {
        this.layer = layer;
        this.meshes = [];
    }

    addMesh(meshes) {
        this.meshes = this.meshes.concat(meshes);
        this.layer.addMesh(meshes);
        return this;
    }

    removeMesh(meshes) {
        //do some things
    }

    clear() {
        //do some things
    }

    remove() {
        //do some things
    }
}

const roadGroup = new VirtuallyGroup(threeLayer);
roadGroup.addMesh(roads);
roadGroup.removeMesh(roads);

常见问题

初始化TheeLayer图层报错

TheeLayer的初始化是异步的,所以需要再threelayer的prepareToDraw方法里进行业务操作:

  • 添加图形
  • 场景获取
  • 相机获取等
js
threeLayer.prepareToDraw = function (gl, scene, camera) {
    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
    camera.add(new THREE.SpotLight(0xffffff, 0.6, 0, Math.PI));
    //do some things
};

如果希望自己的业务代码只被执行一次,你可以:

js
threeLayer.prepareToDraw = function (gl, scene, camera) {
    if (this._inited) {
        return;
    }
    if (!this._inited) {
        this._inited = true;
    }
    scene.add(new THREE.AmbientLight(0xffffff, 0.6));
    camera.add(new THREE.SpotLight(0xffffff, 0.6, 0, Math.PI));
    //do some things
};

升级three后程序就不正常了

这个是因为three版本迭代时兼容性非常差导致的,three版本升级时会做很多破坏性的更新🤧

WARNING

使用three时切记不要随随便便的升级版本,最好集中性的升级,比如半年或者一年升级一个版本,而不是每次three发版本了都去升级, 目前看three每次版本升级都会做出不少的破坏性更新

原型继承 class的问题

这种都是你用了three的插件,但是插件比较老,而你用了高版本的three导致的,新版本的three默认打包是 ES6了,但是老的插件继承 时还是用了原型的方法,而ES规范类原型是不能继承class的,所有在你使用时请保持three和其插件版本的一致性

操作ThreeLayer的相机,地图的视角并么有是预期的效果

ThreeLayer 在地图上只是一个图层,地图上可能有多个图层的,所以ThreeLayer里的相机不做开放的,如果想操作相机应该从地图的视角入口 ,而不是想着用一个图层来操作地图,ThreeLayer里的相机只是用来同步地图里的相机而已

TIP

  • 相机是个全局性的功能,有地图对象控制
  • 所有的图层都是有地图驱动的,不存在用图层来控制地图的可能性,因为地图上有多个图层,如果图层可以控制地图,那么地图会发生紊乱 现象
  • 图层应该只控制自己内部的事情,比如图层里的图形能否被点击,图层的显示隐藏,图层里的数据清空,图层开启动画,控制图层内部图形的动画等

three的各种后处理不生效或者不是期望的效果

three插件里的webglcontext默认是有 GroupGLLayer提供的,整个webglcontext是有GroupGLLayer控制的,如果使用three的后处理会 对webglcontext做状态的改变,可能会和GroupGLLayer里对webglcontext的操作产生冲突的,发生一些未知的问题

GroupGLLayer doc

GroupGLLayer里默认提供一些后处理功能了,如果你需要后处理效果应该从GroupGLLayer入手,比如:bloom,shadow,ssao等

模型拾取性能比较差

three插件里关于模型(gltf,obj,fbx等)的拾取还是用的raycaster,所有当模型体积大了和复杂了会有性能问题, 你可以使用three bvh插件来加速 ,我试了下性能提升还是非常不错的

three-mesh-bvh

添加到图层上的图形需要手动动一下地图才渲染出来

threelayer内部还是采用three的那一套渲染机制,需要开启图层的动画渲染函数,实时渲染才行

  • 自己手动控制动画函数
js
function animation() {
    // layer animation support Skipping frames
    threeLayer._needsUpdate = !threeLayer._needsUpdate;
    if (threeLayer._needsUpdate) {
        threeLayer.redraw();
    }
    stats.update();
    requestAnimationFrame(animation);
}
  • 或者开启图层的动画函数选项
js
var threeLayer = new maptalks.ThreeLayer("t", {
    forceRenderOnMoving: true,
    forceRenderOnRotating: true,
    identifyCountOnEvent: 1,
    animation: true,
});

animation表示开启动画函数的意思

动画函数表示图层实时渲染,如果你不喜欢这样怕性能浪费,可以自己手动控制渲染时机,在需要重新渲染时调用 图层的 redraw方法即可

js
threeLayer.redraw();

原生的three的Object3D直接加入到 threeLayer会自动bloom

threeLayer里的图形分成两种:

  • 基于BaseObject的
  • three原生的Object3D

three原生的Object3D加入到threeLayer里,threeLayer也会渲染,但是将脱离threeLayer的全局管理:

  • 事件控制
  • bloom控制

建议基于BaseObject构造图形,即使是three原生的Object3D也要用BaseObject包裹下

相关示例 buildings-outline

js
//default values
var OPTIONS = {
    altitude: 0,
};

//https://zhuanlan.zhihu.com/p/199353080
class XXX extends maptalks.BaseObject {
    constructor(mesh, options, material, layer) {
        options = maptalks.Util.extend({}, OPTIONS, options, { layer });
        super();
        //Initialize internal configuration
        // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L135
        this._initOptions(options);

        this._createGroup();
        this.getObject3d().add(mesh);
        //Initialize internal object3d
        // https://github.com/maptalks/maptalks.three/blob/1e45f5238f500225ada1deb09b8bab18c1b52cf2/src/BaseObject.js#L140

        //set object3d position
        this.getObject3d().position.copy(mesh.getObject3d().position);
    }
}

如果是模型的话,threeLayer自带了个方法

js
baseObjectModel = threeLayer.toModel(model, { coordinate: map.getCenter() });

用来包裹模型

相关示例

This document is generated by mdpress