介绍
此项目使用vite为基础架构,内部实现均以typescript开发,可替换为自己的业务逻辑,并迁移到
react
,vue
,umi
等其他框架。
通过调用高德地图的API和threejs的开发,实现了一个小鸭子(可替换为自己的模型)沿着规划路线行走,并使镜头跟随小鸭子前进的功能。
该功能主要常见于外卖平台的骑手、滴滴司机、以及情侣软件的实时共享位置。
实现思路
1.加载地图
使用AMapLoader.load
加载地图,从高德开放平台控制台申请一个属于自己的key。
import AMapLoader from '@amap/amap-jsapi-loader';...
const AMap = await AMapLoader.load({
"key": "您自己申请的KEY", // 申请好的Web端开发者Key,首次调用 load 时必填
"version": "2.0",
"plugins": ["AMap.Walking", "AMap.Driving"], // 需要使用的的插件列表,如比例尺'AMap.Scale'等
"Loca": {
version: '2.0.0'
}
})
使用new AMap.Map
实例化地图,并设置mapStyle
为"amap://styles/grey"
,也可以在官网上自己设计属于自己的风格,主要讲的不是这部分所以大概交代一下就过去了,实例化Map后返回一个map实例,后续的操作都需要用到。
添加GLCustomLayer图层
new AMap.GLCustomLayer({
zIndex: 100,
init:()=>{},
render: ()=>{}
})
threejs的加载和创建需要在init
属性的方法里操作,render主要是用来渲染一些镜头信息和 WebGLRenderer
的重绘。
在init方法中创建一个THREEJS的透视相机。
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 100, 1 << 30);
2.镜头信息的获取
前文实例化Map后获取一个map的实例,其中提供了customCoords
数据转换的工具,可以从这里获取到镜头信息,后续转化经纬度到3D世界坐标时候也会用到,转换工具需要提前获取到,方便后续的工作。
var { near, far, fov, up, lookAt, position } = customCoords.getCameraParams();
转换工具提供一个getCameraParams
方法,其中包含相机位置等其他属性。
fov — 摄像机视锥体垂直视野角度
near — 摄像机视锥体近端面
far — 摄像机视锥体远端面
其中大部分属性都和threejs的透视相机属性相同,在render方法中更新相机,这样做的作用就是在后续做巡航功能时会实时更新相机位置。
camera.near = near;
camera.far = far;
camera.fov = fov;
camera.position.set(...position);
camera.up.set(...up);
camera.lookAt(...lookAt);
camera.updateProjectionMatrix();
3.初始化loca
可视化图层需要用到Loca
容器,需要在地图外绘制的图层,需要在可视化图层上绘制。
创建可视化作品前,首先要创建一个 Loca 容器,这个容器可以帮您加载高德地图作为底图,或者帮您关联已有的高德地图作为底图。
在使用地图的时,您可以使用任何一个 JS API 已有的功能,Loca 容器不会影响原有地图的任何功能和特性。这里创建的 Loca 容器您可以理解为是可视化图层的管理器。
注意:创建地图时候 Loca 版本要和map的版本一致,否则会报错。
var loca = new (window as any).Loca.Container({
map,
zIndex: 9
});
将创建好的AMap.GLCustomLayer
添加到map图层。
const customLayer = await createGLCustomLayer(AMap, customCoords)
map.add(customLayer);
createGLCustomLayer
方法就是之前定义的初始化AMap.GLCustomLayer
方法。
返回一个GLCustomLayer
实例,这样就可以在地图内插入可视化内容。
加载模型
回到new AMap.GLCustomLayer
提供的init属性中,创建一个3d场景并把模型加载到场景中。
renderer = new THREE.WebGLRenderer({
context: gl, // 地图的 gl 上下文
});
// 自动清空画布这里必须设置为 false,否则地图底图将无法显示
renderer.autoClear = false;
scene = new THREE.Scene();
加载模型方法跟threejs相同,使用gltfloder
api,加载方法返回一个promise,再使用。
// 初始化模型
function initGltf(): Promise<THREE.Object3D> {
return new Promise((resolve, reject) => {
var loader = new GLTFLoader();
loader.load('https://a.amap.com/jsapi_demos/static/gltf/Duck.gltf', (gltf: any) => {
let object = gltf.scene;
resolve(object)
});
})
}
模型添加到场景。
const { x, y, z } = setRotation(new THREE.Vector3(90, 90, 0))
object.scale.set(15, 15, 15);
group.add(object)
group.add(new THREE.AxesHelper(100))
scene.add(group)
object.name = 'duck'
我在模型上添加了一个AxesHelper
辅助线,官网上表示蓝色代表z轴,但是放在地图中发生了坐标方向不一致的问题,threejs的向上方向是y轴,地图中z是向上方向,这一点可能要注意一下了。
用于简单模拟3个坐标轴的对象。
红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴。
4.旋转模型
const { x, y, z } = setRotation(new THREE.Vector3(90, -90, 0))
group.rotation.set(x, y, z)
获取旋转角度的方法。
export function setRotation(rotation: THREE.Vector3) {
var x = Math.PI / 180 * (rotation.x || 0);
var y = Math.PI / 180 * (rotation.y || 0);
var z = Math.PI / 180 * (rotation.z || 0);
return new THREE.Vector3(x, y, z)
}
5.计算轨迹
使用viewControl
现在模型已经加载好,接下来就是要获取轨迹数据,镜头跟踪在Loca中有相应的apiviewControl
,使用这个api调用addTrackAnimate
方法,提供对应参数即可。
loca.viewControl.addTrackAnimate({
path: pathArr, // 镜头轨迹,二维数组,支持海拔
duration: 120000, // 时长
timing: [[0, 0.3], [1, 0.7]], // 速率控制器
rotationSpeed: 1800, // 每秒旋转多少度
}, function () {
console.log('完成',);
});
pathArr
是一个轨迹数组。
const pathArr = [[116.310348, 39.89702], [116.310541, 39.884855], [116.320963, 39.889154], [116.322894, 39.889608], [116.325542, 39.889822], [116.328074, 39.889761], [116.349104, 39.889429], [116.348517, 39.89747], [116.355205, 39.898413], [116.35656, 39.90021], [116.355802, 39.93225]]
为了方便查看,我们在使用Loca提供的绘制引导线功能将这几个关键点连接的引导线画一下。
// 导航线
var polyline = new AMap.Polyline({
path: pathArr, // 设置线覆盖物路径
showDir: true,
strokeColor: '#3366bb', // 线颜色
strokeWeight: 10, // 线宽
zIndex: 1
});
map.add(polyline)
以上工作做完后,需要调用一下loca.animate.start();
方法,否则可视化图层不会更新,相应数据也获取不到,现在画面变成这样。
除了以上这种方法去实现镜头的移动,还可以通过插入坐标的方式去实现,也是传统threejs中使用的方法,就是利用tweenjs
的动画,运动过程中改变map.setCenter
,实现跟踪,这部分代码在changeObject
方法中。
6.镜头跟踪
移动模型
利用requestAnimationFrame
函数写一个循环渲染的方法,在调用的同时获取镜头中心坐标,通过customCoords
转换工具将经纬度转为3D世界的坐标,并将该坐标赋值给object模型,再通过map提供的getRotation
方法,获取地图的旋转角度,并将该角度赋值给object模型的y轴,使模型沿着y轴旋转,至于旋转的速度,在前面定义addTrackAnimate
时的rotationSpeed
属性定义的。
const render = () => {
requestAnimationFrame(() => {
render()
})
if (object) {
const center = map.getCenter()
var position = customCoords.lngLatsToCoords([
[center.lng, center.lat]
])[0];
const v3 = new THREE.Vector3(position[1], 0, position[0])
object.position.copy(v3)
const rotation = map.getRotation()
object.rotation.y = rotation * Math.PI / 180
}
map.render();
TWEEN && TWEEN.update()
}
以上文章内容有一些关于threejs的基础知识,可以先提前了解一下,否则有很多好玩有趣的效果实现不出来。
7.其他
关于飞线,只是作为装饰,显得画面不那么呆板,在官网上也有案例,简单贴一个代码吧。
// 飞线
var geo = new (window as any).Loca.GeoJSONSource({
url: 'https://a.amap.com/Loca/static/loca-v2/demos/mock_data/bj_bus.json',
});
var layer = new (window as any).Loca.PulseLineLayer({
// loca,
zIndex: 10,
opacity: 1,
visible: true,
zooms: [1, 30],
});
var headColors = ['#EFBB51', '#7F3CFF', '#4CC19B', '#0B5D74', '#E06AC4', '#223F9B', '#F15C1A', '#7A0FA6'];
layer.setSource(geo);
layer.setStyle({
altitude: 0,
lineWidth: 2,
// 脉冲头颜色
headColor: (_, feature) => {
return headColors[feature.properties.type - 1];
},
// 脉冲尾颜色
trailColor: 'rgba(128, 128, 128, 0.5)',
// 脉冲长度,0.25 表示一段脉冲占整条路的 1/4
interval: 0.25,
// 脉冲线的速度,几秒钟跑完整段路,可以通过计算距离动态改变时间
duration: 5000,
});
// 飞线结束
loca.add(layer);
three.js的版权声明及许可证:
The MIT License
Copyright © 2010-2024 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
tween.js的版权声明及许可证:
The MIT License
Copyright (c) 2010-2012 Tween.js authors.
Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
相关源码和模型的下载链接地址
https://www.aspiringcode.com/content?id=17086628146313&uid=a8012550ef80420fa58669250592734c
本文由高德开放平台用户—孙华鹏提供
仅代表作者个人观点