最终的成果展示
由于高德地图涉及到收费的 apikey, 所以这里不方便公开源码, 除非谁能给我一个免费的 apikey。
上一篇 《Three.js 加载 glb 模型实现散点标注》 较为详细地介绍了在 Three.js 中, 怎么加载 glb 格式的 3D 模型, 本文将会跟高德地图结合起来, 在高德地图上, 加入 Three.js 自定的图层, 并实现散点标注。
一、加载高德地图相关依赖
高德地图,详细的使用方法,可以见官方文档,
首先加载 script webapi.amap.com/maps
其次再加载 script webapi.amap.com/loca
顺序不要搞混乱,由于时间关系,这里直接通过 DOM 标签,通过 promise 按顺序引入进来
详细代码如下:
private readonly pluginsList = [
//编辑多边形
'AMap.PolyEditor',
//允许开发者自定义图层的绘制方法
'AMap.CustomLayer',
//3D 控制条,旋转、缩放
'AMap.ControlBar',
//热力图
'AMap.Heatmap',
//3D图层
'Map3D',
//叠加自定义的WebGL内容
'AMap.GLCustomLayer',
//显示和管理建筑物信息
'AMap.Buildings',
//地图尺寸管理
'AMap.Size',
//经纬度坐标点
'AMap.LngLat',
//加载3D瓦片数据
'AMap.3DTilesLayer',
//编辑多边形和折线
'AMap.PolylineEditor',
//驾车路线规划
'AMap.Driving'
];
//加载高德地图相关的js
private async loadGaoDeMapScript(){
const AMAP_KEY = '8281b6b8f40890205d2a2755b52dbfee';
return new Promise((resolve, reject) => {
if (window.AMap && window.Loca){
resolve();
}else{
//加载maps.js
const script = document.createElement('script');
script.charset = 'utf-8'
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}&callback=_mapLoaded&plugin=${this.pluginsList.join(',')}`;
document.head.appendChild(script);
script.onerror = function () {
reject(new Error('地图API文件加载失败'));
}
}
window._mapLoaded = function () {
//加载loca.js
const arr = [`https://webapi.amap.com/loca?v=2.0.0beta&key=${AMAP_KEY}`];
let count = 0
for (let i = 0; i < arr.length; i++) {
const script = document.createElement('script');
script.charset = 'utf-8';
script.src = arr[i];
document.head.appendChild(script);
script.onload = function () {
count = count + 1;
if (count >= arr.length) {
resolve();
}
}
script.onerror = function () {
reject(new Error('地图可视化API文件加载失败'));
}
}
}
})
}
二、初始化高德地图
这里相当于把高德地图当成网页背景, 所以需要初始化高德地图。
这里会涉及到比较多的参数设置, 具体可以看高德地图官方文档:
- center 地图的中心位置,一般是经纬度构成的数组
- zooms 缩放程度, 例如 [3, 22] 表示缩放程度在 3 与 22 之间
- viewMode 想要 3D 效果的话, 直接赋值 “3D”
- pitch 视角倾斜度
代码如下:
public async createMap(){
return new Promise((resolve, reject) => {
this.loadGaoDeMapScript().then(() => {
const containerId = this.config.containerId || 'container';
const gaodeMap = new AMap.Map(containerId, {
center: this.config.center,
resizeEnable: true,
zooms: [3, 22],
viewMode: this.config.viewMode,
defaultCursor: 'default',
pitch: 30,
mapStyle: this.config.mapStyle || 'amap://styles/grey',
expandZoomRange: true,
rotation: 0,
zoom: this.config.zoom,
skyColor: this.config.skyColor,
//不显示默认建筑物
showBuildingBlock: false,
//不显示默认建筑物
features: ['bg', 'road', 'point'],
//注意:这个Layer写与不写好像都是没有什么影响
layers: [
AMap.createDefaultLayer(),
new AMap.Buildings({
zooms: [10, 22],
zIndex: 2,
//修改该值会导致显示异常
//heightFactor: 1.2,
roofColor: 'rgba(171,211,234,0.9)',
wallColor: 'rgba(34,64,169,0.5)',
opacity: 0.7,
visible: this.config.showBuildingBlock
})
],
mask: null
})
this.map = gaodeMap;
resolve(gaodeMap);
})
.catch((err) => {
reject(err);
})
})
}
三、自定义 3D 图层
以上第二步,已经创建了高德地图, 获取到 map 这个对象, 现在, 需要根据 map 对象, 创建一个 3D 图层, 加入高德地图中, 下面代码 createGlCustomLayer 这个方法,将会创建 3D 图层, 并在 init 里面, 实例化 Three.js 相关的 Scene、 Camera、 Renderer , 最后在 render 里面,更新下相机状态, 使之渲染不出现异常。
//创建非独立图层
createGlCustomLayer () {
return new Promise((resolve) => {
const layer = new AMap.GLCustomLayer({
zIndex: 120,
//设置为true时才会执行init
visible: true,
init: (gl) => {
this.initThree(gl);
resolve(layer);
},
render: (gl) => {
//注意:这个方法是放在关键帧里面执行的,所以调用会非常频繁
this.updateCamera();
this.map.render();
}
})
this.map.add(layer);
})
}
//初始化three实例
initThree (gl) {
//第1步:创建scene
this.scene = new Scene();
//第2步:创建camera,注意这里并没有设置相机的位置,而是在关键帧方法里面执行updateCamera,从而设置相机位置
const { clientWidth, clientHeight } = this.container;
this.camera = new PerspectiveCamera(60, clientWidth / clientHeight, 100, 1 << 30);
//第3步:创建renderer,注意这里多加设置了渲染器的上下文gl
const renderer = new WebGLRenderer({
alpha: true,
antialias: false,
precision: 'highp',
context: gl
});
renderer.setSize(clientWidth, clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
//必须设置为false才能实现多个render的叠加
renderer.autoClear = false;
renderer.setClearAlpha(0);
this.renderer = renderer;
}
//更新相机
updateCamera () {
const { scene, renderer, camera, customCoords } = this;
if (!renderer) {
return;
}
//重新定位中心,这样才能使当前图层与Loca图层共存时显示正常
if (this.center) {
customCoords.setCenter(this.center);
}
const { near, far, fov, up, lookAt, position } = customCoords.getCameraParams();
//近平面
camera.near = near;
//远平面
camera.far = far;
//视野范围
camera.fov = fov;
camera.position.set(...position);
camera.up.set(...up);
camera.lookAt(...lookAt);
//更新相机坐标系
camera.updateProjectionMatrix();
renderer.render(scene, camera);
//这里必须执行,重新设置 three 的 gl 上下文状态
renderer.resetState();
}
//设置图层中心坐标,非常重要
updateCenter (lngLat) {
if (lngLat instanceof Array && lngLat.length === 2) {
this.customCoords.setCenter(lngLat);
}
}
同时, 页面需要监听下尺寸变化, 尺寸变化需要更新下相机的宽高比, 使之不会变形。
//尺寸监听
eventListener () {
window.addEventListener('resize', () => {
const { clientWidth, clientHeight } = this.container;
if (this.camera) {
this.camera.aspect = clientWidth / clientHeight;
}
})
}