高德地图API + three.js + Vue3基础使用与使用 + 标记不显示避坑
three.js小白的学习之路。
最近闲来无事,突然想起来之前好像项目有需求说是要将模型放在地图上。加上在浏览别的大佬写的博客时,也找到了一些大佬写的相关文章。基本上都是使用的高德地图开放平台的JS API。我也随之开启了自己的学习之路。
先简单学习了一些在高德地图上加入点标记、图层叠加、样式修改的API,不得不佩服人家确实厉害,操作简单,灵活度高,而且API从1.x升级到2.0之后,对性能优化的非常明显。
但是单纯的学习过程有些枯燥,就简单浏览了一遍,等用到的时候再来翻一遍。
然后目标直指自定义图层中的与three.js相结合的API的使用。
基础搭建
首先我使用的是Vite+Vue3+TS的框架,于是我安装了这几个插件:
yarn add @amap/amap-jsapi-loader
yarn add @amap/amap-jsapi-types
前一个是适用于Vue的插件形式,就和我们平常使用第三方包一样。但是这个第三方包没有提供任何有关type的信息,导致编程过程中没有提示。于是就安装了第二个插件,但是啊但是,这个插件包含的也只有一部分类型信息,很多与three.js相关的类型信息是没有的,难受。
接下来新建一个vue文件,构建基础框架:
<template><div id="container"></div>
</template><script lang="ts" setup>
import AMapLoader from "@amap/amap-jsapi-loader";
import "@amap/amap-jsapi-types";onMounted(() => {window._AMapSecurityConfig = {securityJsCode: "安全密钥",};AMapLoader.load({key: "申请的key值", // 申请好的Web端开发者Key,首次调用 load 时必填version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15}).then(() => {map = new AMap.Map("container", {center: [116.54, 39.79],zooms: [2, 20],zoom: 14,viewMode: "3D",pitch: 50, // 俯仰角度,默认0,范围0-83,0相当于正交视图});}).catch((e) => {console.log(e);});});
</script><style scoped>
#container {width: 100vw;height: 100vh;
}
</style>
key和安全密钥需要自己去官网申请,跟着官网步骤走即可。
container是容器,和three.js类似。
AMapLoader是核心,在then的回调中,假如你是用的是js,没有引用@amap/amap-jsapi-types,那么then的回调函数里面将返回一个AMap参数供你使用。
AMapLoader.load().then((AMap) => {// …………
})
其实打开插件也可以看到,AMap是一个命名空间,将所有的API都放在了这个命名空间里面。
这样,打开网页就可以看到如下的画面了:
与three.js结合
高德地图官方提供了专门与three.js结合使用的API,是一个单独的自定义层级结构,GLCustomLayer类。
1.构造函数
const glLayer = new AMap.GLCustomLayer(opts: GlCustomLayerOptions)
2.参数opts
属性 | 描述 |
---|---|
opts.init 类型:Function | 初始化的时候,开发者可以在这个函数参数里面获取 gl 上下文,进行一些初始化的操作。 |
opts.render 类型:Function | 绘制函数,初始化完成时候,开发者需要给该图层设定render方法, 该方法需要实现图层的绘制,API会在合适的时机自动调用该方法 |
opts.zooms 类型:[Number, Number] default [2,20] | 图层缩放等级范围,默认 [2, 20] |
opts.opacity 类型:Number default 1 | 图层透明度,默认为 1 |
opts.visible 类型:Boolean default true | 图层是否可见,默认为 true |
opts.zIndex 类型:Number default 120 | 图层的层级,默认为 120 |
3.成员函数
getMap():返回GLCustomLayer所属地图的实例;返回值:Map| null
getzIndex():获取GLCustomLayer叠加顺序;返回值:number
setzIndex(index):设置GLCustomLayer叠加顺序;参数:number,叠加值;返回值:void
getOpacity():获取GLCustomLayer透明度;返回值:number
setOpacity(opacity):设置GLCustomLayer透明度;参数:number,透明度;返回值:void
getZooms():获取GLCustomLayer显示层级范围;返回值:number
setZooms(zooms):设置GLCustomLayer显示层级范围;参数:Vector,默认[3, 20];返回值:number
show():显示GLCustomLayer;返回值:void
hide():隐藏GLCustomLayer;返回值:void
示例代码
<template><div id="container"></div>
</template><script lang="ts" setup>
import * as Three from "three";
import AMapLoader from "@amap/amap-jsapi-loader";
import "@amap/amap-jsapi-types";let map: AMap.Map;
let camera: Three.PerspectiveCamera,scene: Three.Scene,renderer: Three.WebGLRenderer;
const meshArr: Three.Mesh[] = [];let customCoords;
let data;const init = (gl: any) => {// 这里我们的地图模式是 3D,所以创建一个透视相机,相机的参数初始化可以随意设置,因为在 render 函数中,每一帧都需要同步相机参数,因此这里变得不那么重要。// 如果你需要 2D 地图(viewMode: '2D'),那么你需要创建一个正交相机camera = new Three.PerspectiveCamera(60,window.innerWidth / window.innerHeight,10,1000000);renderer = new Three.WebGLRenderer({context: gl, // 传递canvas的gl上下文});// 自动清空画布这里必须设置为 false,否则地图底图将无法显示renderer.autoClear = false;scene = new Three.Scene();{// 环境光照和平行光const aLight = new Three.AmbientLight(0xffffff, 0.3);const dLight = new Three.DirectionalLight(0xffffff, 1);dLight.position.set(1000, -100, 900);scene.add(dLight);scene.add(aLight);}const texture = new Three.TextureLoader().load("https://a.amap.com/jsapi_demos/static/demo-center-v2/three.jpeg");texture.minFilter = Three.LinearFilter;const mat = new Three.MeshPhongMaterial({color: 0xfff0f0,depthTest: true,transparent: true,map: texture,});const geo = new Three.BoxGeometry(500, 500, 500);for (let i = 0; i < data.length; i++) {const pos = data[i];const mesh = new Three.Mesh(geo, mat);mesh.position.set(pos[0], pos[1], 500);meshArr.push(mesh);scene.add(mesh);}
};const render = () => {// 这里必须执行!!重新设置 three 的 gl 上下文状态。renderer.resetState();// 重新设置图层的渲染中心点,将模型等物体的渲染中心点重置, 否则和 LOCA 可视化等多个图层能力使用的时候会出现物体位置偏移的问题customCoords.setCenter([116.52, 39.79]);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();
};onMounted(() => {window._AMapSecurityConfig = {securityJsCode: "你的安全密钥",};AMapLoader.load({key: "你的key", // 申请好的Web端开发者Key,首次调用 load 时必填version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15}).then(() => {map = new AMap.Map("container", {center: [116.54, 39.79],zooms: [2, 20],zoom: 14,viewMode: "3D",pitch: 50, // 俯仰角度,默认0,范围0-83,0相当于正交视图});// 数据转换工具customCoords = map.customCoords;// 数据使用转换工具进行转换,这个操作必须要提前执行(在获取镜头参数 函数之前执行),否则将会获得一个错误信息。data = customCoords.lngLatsToCoords([[116.52, 39.79],[116.54, 39.79],[116.56, 39.79],]);// 创建GL图层const glLayer = new AMap.GLCustomLayer({zIndex: 10,init,render,});map.add(glLayer);}).catch((e) => {console.log(e);});
});
</script><style scoped>
#container {width: 100vw;height: 100vh;
}
</style>
运行结果:
三个Box已经跃然纸上。
AMap.Map.customCoords
这里在多一嘴,说一下用到的一个方法,AMap.Map.customCoords
我在jsap-types的插件里面是没有找到这个的。其实这个是高德地图 JS API 2.0 中的一个工具,用于在 3D 地图场景下进行坐标转换。它主要用于将经纬度坐标转换为 3D 场景中的世界坐标,以便在 Three.js 等 3D 渲染引擎中使用。
说白了即使将地图坐标转换成three.js中的世界坐标的。
这里面提供了几个方法:
-
坐标转换:通过
customCoords.lngLatsToCoords()
方法,可以将经纬度数组转换为 3D 场景中的坐标数组。 -
设置中心点:
customCoords.setCenter()
方法设置 3D 场景的中心点,用于确定 3D 模型的参考原点(世界坐标原点),通常在每次的render函数调用中执行一遍。 -
获取相机参数:
customCoords.getCameraParams()
方法可以获取当前地图视角的相机参数(如视野范围、近平面、远平面等),这些参数可以用于同步 Three.js 的相机状态。
避坑
1.加载失败,报错WebGL 1 is not supported since r163。
这个意思很明显,就是three.js的r163版本之后,不在支持WebGL 1了。此时再仔细看了一眼官网,发现其用的是r142版本,和官网严格对其,即可解决。
2.TS提示AMap.GLCustomLayer不存在
运行没问题,写代码时提示不存在,于是我去翻了翻types插件,发现其实是有的,只不过没有导出而已,自己手动更改一下就能解决
=====>>>>
但是,上面提到的AMap.Map.customCoords,我是真的没有找到,可能没有这types插件里面定义,因为官网也说了,只是定义了大部分,这个估计是个漏网之鱼。
3.中心点设置
在代码中可以看到,map的center设置的是三个Box中第二个的位置,此时添加一个AxesHelper,可以看到世界坐标的原点其实是第一个Box的位置。
因此地图的中心点与glLayer图层内3D模型世界坐标的中心点并不是一回事,是分开来单独设置的。three.js 的中心点就是通过customCoords.setCenter()
设置的。
4.标记点不显示的问题
这里面并没有添加标记点,我是在一开始学的时候遇到了,这里一并记录。
我按照官网的教程,添加了一个标记点:
const marker = new AMap.Marker({position: new AMap.LngLat(116.39, 39.9),title: "北京",offset: new AMap.Pixel(-10, -10),icon: "//vdata.amap.com/icons/b18/1/2.png", // 添加 icon 图标 URL});map.add(marker);
结果:
标记点理应是一个红星放在地图上,结果却没有显示。我查看了一下DOM结构,发现最外的盒子大小是0:
于是我修改了一下css:
// 保证marker能正常显示
:deep(.amap-markers) {width: 100%;height: 100%;
}
好了:
但是这样显然有点不合理,这么大一个工程怎么可能会有这种错误。我又向上翻了一翻,结果发现了一个关键的css,那就是overflow:hidden。
因为我这个工程是用来写three.js的,而默认情况下,three.js老爱出现滚动条,于是我在App.vue的根节点添加了overflow:hidden,所以导致后续的子节点全部自动继承,只需要将其注释掉,那么不添加上述的css字段也是ok的。
// APP.vue
// overflow: hidden;
// overflow-x: hidden;// index.vue
// // 保证marker能正常显示
// :deep(.amap-markers) {
// width: 100%;
// height: 100%;
// }
这个纯属是我个人问题导致,记录一下自己的愚蠢。