高德地图+Three.js实现飞线、运动边界和炫酷标牌
点击关注公众号,“技术干货”及时达!
地图经常使用标牌来标注地点位置,是时候来点炫酷的效果了!用高德地+Three.js,带你实现飞线、运动边界和炫酷标牌。
1.高德地图+Three.js可以参考官方示例高德地图-自定义图层-GLCustomLayer 结合 THREE
初始化高德3D地图this.map=newAMap.Map(this.container,{
//缩放范围
zooms:[2,20],
zoom:4.5,
//倾斜角度
pitch:0,
//隐藏标签
showLabel:false,
//3D地图模式
viewMode:'3D',
//初始化地图中心点
center:this.center,
//暗色风格
mapStyle:'amap://styles/dark'
自定义高德地图经纬度转px工具,即墨卡托投影坐标转换。//数据转换工具
this.customCoords=this.map.customCoords;
//设置坐标转换中心
this.customCoords.setCenter(this.center);
高德地图添加WebGL自定义图层,初始化Three.js配置//创建GL图层
vargllayer=newAMap.GLCustomLayer({
//zIndex:10,
//初始化的操作,创建图层过程中执行一次。
init:(gl)={
//初始化Three相机
this.camera=newTHREE.PerspectiveCamera(
60,
this.container.offsetWidth/this.container.offsetHeight,
1,
130
//初始化Three渲染器
this.renderer=newTHREE.WebGLRenderer({
context:gl//地图的gl上下文
this.renderer.setPixelRatio(window.devicePixelRatio);
//自动清空画布这里必须设置为false,否则地图底图将无法显示
this.renderer.autoClear=false;
//初始化场景
this.scene=newTHREE.Scene();
this.createChart();
},
render:()={
//这里必须执行!!重新设置 three 的 gl 上下文状态。
this.renderer.resetState();
//设置坐标转换中心
this.customCoords.setCenter(this.center);
var{near,far,fov,up,lookAt,position}=this.customCoords.getCameraParams();
//这里的顺序不能颠倒,否则可能会出现绘制卡顿的效果。
this.camera.near=near;
this.camera.far=
this.camera.fov=
this.camera.position.set(...position);
this.camera.up.set(...up);
this.camera.lookAt(...lookAt);
this.camera.updateProjectionMatrix();
//渲染器渲染场景
this.renderer.render(this.scene,this.camera);
//这里必须执行!!重新设置 three 的 gl 上下文状态。
this.renderer.resetState();
}
this.map.add(gllayer);
2.绘制运动边界行政区边界可以通过地理小工具获取。
高德地图折线polyline经常使用,但是让折线运动起来,你可能很少其实只需要添加几行代码就能实现。
实现逻辑:添加边界折线,然后截取折线的一段,让它跟着时间往后移动,即开始索引递增,截取相同折线,即可让折线运动起来。
//绘制中国大陆运动边界
createChinaLine(){
returnnewPromise((resolve)={
fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000.json')
.then((res)=res.json())
.then((res)={
letpath=res.features[0].geometry.coordinates[0][0];
//截取10%的线段
letlen=Math.floor(path.length*0.1);
//边界折线
letpolyline=newAMap.Polyline({
path:path,
strokeWeight:4,
strokeColor:'white',
lineJoin:'round',
strokeOpacity:1
this.map.add(polyline);
//利用Tween创建动画
newTWEEN.Tween({start:0})
.to({start:path.length},3000)
//无限循环动画
.repeat(Infinity)
.onUpdate((obj)={
if(obj.start+lenpath.length){
polyline.setPath(path.slice(obj.start,obj.start+len));
}else{
constc=path.length-obj.start;
//头尾相接时截取尾部+头部各一段
polyline.setPath(
[].concat(path.slice(obj.start,path.length),path.slice(0,len-c))
}
})
.start();
resolve();
}
//Tween动画
animateAction(){
if(TWEEN.getAll().length){
TWEEN.update();
}
}
运动边界实现简单,但效果一级棒!并且2D和3D地图都能用,优秀!
3.绘制升起山峰在对应的坐标点上升起一座尖尖的山峰,这里使用Plane平面实现
顶点着色器precisionmediumpfloat;
uniformfloatuTime;
uniformfloatuHeight;
varyingfloat
floatPI=acos(-1.0);
vec2center=vec2(0.5);
voidmain(void){
//离中线的距离
floatd=length(uv-center)*2.0;
//沿中心点往外减少
vD=pow(1.0-d,3.0);
//山峰高度,随着uTime变化
floath=vD*uHeight*uTime;
vec3pos=vec3(position.x*0.5,position.y*0.5,
gl_Position=projectionMatrix*modelViewMatrix*vec4(pos,1.0);
}
片元着色器precisionmediumpfloat;
uniformvec3uColor;
varyingfloat
voidmain(void){
if(vD0.01)//透明度太小则不渲染颜色
discard;
else//透明度随着距离中心点的变化
gl_FragColor=vec4(uColor,vD*2.0);
}
添加shaderMaterial的平面constmaterial=newTHREE.ShaderMaterial({
uniforms:{
//随时间变化
uTime:{value:0},
//高度
uHeight:{value:this.size},
//颜色
uColor:{value:newTHREE.Color(data.color)}
},
//开启透明度
transparent:true,
vertexShader:``,
fragmentShader:``
this.material=material;
//平面形状,方便复用
if(!this.ageometry){
//平面的面数一定要足够才能形成山峰
constgeometry=newTHREE.PlaneGeometry(this.size,this.size,500,500);
this.ageometry=geometry;
}
constplane=newTHREE.Mesh(this.ageometry,material);
//转换经纬度作为px
constd=this.customCoords.lngLatToCoord(data.pos);
plane.position.set(d[0],d[1],0);
this.scene.add(plane);
「注意:」
平面的面数一定要足够才能形成山峰平面的大小和高度要足够大,在中国地图上才能看到,这里的this.size=500000,500k。在小的地图范围内,大小也要对应缩小。高德地图坐标与three.js的坐标有些许不同,经纬度lng,lat对应xy,而高度对应z坐标。添加山峰升起动画addAnimate(start,end,time,update){
returnnewPromise((resolve)={
consttween=newTWEEN.Tween(start)
.to(end,time)
.onUpdate(update)
.easing(TWEEN.Easing.Quadratic.In)
.onComplete(()={
resolve(tween);
})
.start();
}
consttw=awaitthis.addAnimate({time:0},{time:1},1000,(obj)={
this.material.uniforms.uTime.value=obj.time;
//播放完删除动画
TWEEN.remove(tw);
4.绘制浮动四棱锥//转换经纬度坐标
constd=this.customCoords.lngLatToCoord(data.pos);
constr=this.size*0.1;
//四棱锥图形,方便复用
if(!this.cgeometry){
constgeometry=newTHREE.ConeGeometry(r,r*2,4,1);
this.cgeometry=geometry;
}
constmaterial=newTHREE.MeshLambertMaterial({color:newTHREE.Color(data.color)
//创建四棱锥网格
constcone=newTHREE.Mesh(this.cgeometry,material);
//旋转90度,让四棱锥倒立
cone.rotateX(-Math.PI*0.5);
//设置位置
cone.position.set(d[0],d[1],this.size*1.1);
this.scene.add(cone);
//收集四棱锥
this.cones.push({obj:cone,step:this.speed
设置垂直高度坐标,让四棱锥上下浮动:遇到最大或最小高度时改变速度speed方向animateAction(){
if(this.cones.length){
this.cones.forEach((c)={
//高低浮动
if(c.obj.position.z=this.maxHeight){//最大高度
c.step=-this.speed;
}elseif(c.obj.position.z=this.minHeight){//最小高度
c.step=this.speed;
}
c.obj.position.z+=c.step;
}
}
5.绘制文本标牌原本尝试用Marker的自定义内容content来实现html标签的,但文档上说Marker高度属性height只有在2.1版本生效,但目前只有2.0版本,暂时无法使用高度属性
通过咨询高德地图官方,官方推荐用Loca数据可视化里面的ZMarkerLayer
以下代码来源于高德地图-Loca API标牌点-某片区房价信息
vartriangleZMarker=newLoca.ZMarkerLayer({
loca:loca,
zIndex:119,
depth:false,
triangleZMarker.setSource(geo);//设置数据集
triangleZMarker.setStyle({
content:(i,feat)={//html自定义内容
return(
'divstyle="width:120px;height:120px;background:url(https://a.amap.com/Loca/static/loca-v2/demos/images/triangle_'
+(feat.properties.price60000?'blue':'yellow')
+'.png);"/div'
},
unit:'meter',
rotation:0,
alwaysFront:true,
size:[60,60],
altitude:15,//高度
triangleZMarker.addAnimate({//动画
key:'altitude',
value:[0,1],
random:true,
transform:1000,
delay:2000,
yoyo:true,
repeat:999999,
ZMarker添加整一个图层Layer生成一批Marker,官方的示例很漂亮。因为我想控制一个个的Marker,故改用Three.js的CSS2DRender来实现。
在GLCustomLayer的init中添加初始化CSS2DRenderletlabelRenderer=newCSS2DRenderer();
labelRenderer.setSize(container.offsetWidth,container.offsetHeight);
labelRenderer.domElement.style.position='absolute';
labelRenderer.domElement.style.top='0px';
//不妨碍界面上点击冲突
labelRenderer.domElement.style.pointerEvents='none';
this.container.appendChild(labelRenderer.domElement);
this.labelRenderer=labelRenderer;
在在GLCustomLayer的init中添加CSS2DRender渲染this.labelRenderer.render(this.scene,this.camera);
添加Label标牌addLabel(dom,pos){
//label的dom可以触发事件
dom.style.pointerEvents='auto';
constlabel=newCSS2DObject(dom);
label.position.set(...pos);
this.scene.add(label);
returnlabel;
}
addALabel(data){
constdiv=document.createElement('div');
div.innerHTML=`divclass="tip-box"style="background:${data.bg};--base-color:${data.color}"span class="circle"/spanspan class="text"${data.name}/span/div`;
//坐标转换
constd=this.customCoords.lngLatToCoord(data.pos);
constlabel=this.addLabel(div,[d[0],d[1],this.size*1.5]);
}
标牌动画和样式直接用css//变大弹出
@keyframesbig{
0%{
transform:scale(0);
}
100%{
transform:scale(1);
}
}
//闪烁
@keyframesflash{
0%{
opacity:0.3;
}
100%{
opacity:1;
}
}
.tip-box{
--base-color:dodgerblue;
border:solid1pxvar(--base-color);
background-color:rgba(30,144,255,0.3);
color:white;
white-space:nowrap;
padding-left:8px;
padding-right:16px;
height:32px;
animation:big1sease-in;
border-radius:16px;
display:flex;
align-items:center;
box-shadow:008pxvar(--base-color);
}
.tip-box.circle{
background-color:var(--base-color);
height:16px;
width:16px;
border-radius:50%;
animation:flash0.5sease-inalternateinfinite;
}
.tip-box.text{
margin:08px;
}
「注意」:DOM对象上如果有动画css请在外部包裹一层,避免css样式冲突,导致动画失效。
6.绘制飞线飞线由管道形状绘制,通过着色器来改成头大尾小的形状。
顶点着色器floatPI=acos(-1.0);
varyingfloat
varyingfloat
uniformfloatuTime;
uniformfloatuSize;
uniformfloatuLen;
voidmain(void){
//取模循环
floatd=mod(uv.x-uTime,1.0);
//截取uLen长度
vS=smoothstep(0.0,uLen,
//不在范围内不渲染
if(vS0.01||duLen)
return;
//头大尾小的飞线坐标点
vec3pos=position+normal*sin(PI*0.5*(vS-0.6))*uSize;
gl_Position=projectionMatrix*modelViewMatrix*vec4(pos,1.0);
}
片元着色器varyingfloat
uniformvec3uColor;
voidmain(void){
//透明度随着飞线头尾变小
gl_FragColor=vec4(uColor,
}
添加管道飞线,用贝塞尔曲线形成弯曲addLine(posList,color){
//经纬度坐标点统一转换
constd=this.customCoords.lngLatsToCoords(posList);
//贝塞尔曲线形成弯曲
constcurve=newTHREE.QuadraticBezierCurve3(
newTHREE.Vector3(d[0][0],d[0][1],0),
//取中间点
newTHREE.Vector3((d[0][0]+d[1][0])*0.5,(d[0][1]+d[1][1])*0.5,this.size),
newTHREE.Vector3(d[1][0],d[1][1],0)
constgeometry=newTHREE.TubeGeometry(curve,32,10000,8,false);
constmaterial=newTHREE.ShaderMaterial({
uniforms:{
//随时间变化
uTime:{value:0.0},
//飞线长度
uLen:{value:0.6},
//飞线宽度
uSize:{value:10000},
//飞线颜色
uColor:{value:newTHREE.Color(color)}
},
//开启透明度
transparent:true,
vertexShader:``,
fragmentShader:``
constline=newTHREE.Mesh(geometry,material);
this.scene.add(line);
}
添加飞线动画,让飞线动起来newTWEEN.Tween({time:0})
.to({time:1.0},1000)
//重复动画
.repeat(Infinity)
.onUpdate((obj)={
material.uniforms.uTime.value=obj.time;
})
.start();
7.动画连续起来最终效果:地图绘制运动边界,镜头放大倾斜,对应地点长出尖尖的山峰,然后弹出一个上下浮动四棱锥和文本标牌,随即生出一条飞线,镜头跟随,跳到下一个景点,再次弹出四棱锥和文本标牌,重复,走过所有地点后,定格到最终视角。
//运动边界线
awaitthis.createChinaLine();
//绘制山峰
awaitthis.createA(this.tags[0]);
//视角变化
this.map.setPitch(68,false,3000);
this.map.setRotation(24,false,3000);
awaitthis.sleep(2000);
{
//升起山峰
consttw=awaitthis.addAnimate({time:0},{time:1},1000,(obj)={
this.material.uniforms.uTime.value=obj.time;
TWEEN.remove(tw);
//添加标牌
this.addALabel(this.tags[0]);
}
awaitthis.sleep(2000);
//绘制飞线
this.addLine([this.tags[0].pos,this.tags[1].pos],this.tags[0].color);
for(leti=1;ithis.tags.length;i++){
constdata=this.tags[i];
//视角跟随到新地点
this.map.setCenter(data.pos,false,1000);
this.map.setZoom(6,false,1000);
awaitthis.sleep(1000);
//绘制山峰
awaitthis.createA(data);
//升起山峰
consttw=awaitthis.addAnimate({time:0},{time:1},1000,(obj)={
this.material.uniforms.uTime.value=obj.time;
TWEEN.remove(tw);
this.addALabel(data);
awaitthis.sleep(1000);
//添加飞线
if(ithis.tags.length-1){
this.addLine([data.pos,this.tags[i+1].pos],data.color);
}else{
//最终视角
this.map.setPitch(73.2,false,3000);
this.map.setZoom(5,false,1000);
this.map.setRotation(58.7,false,3000);
this.map.setCenter([101.6,35.6],false,1000);
}
}
高德地图3D地图视角设置真的很方便,还自带动画功能,效果真的很棒!
setZoom设置缩放大小setPitch设置倾斜角度setCenter设置地图中心经纬度setRotation设置旋转角度8.GitHub地址https://github.com/xiaolidan00/my-earth
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线