本项目代码使用umi+react+高德地图amap+threejs开发的一款景区管理项目,其中模型使用svg文件并嵌入高德地图中,实现新增景区、绑定店铺、绑定点位设施等功能,使用高德地图的导航功能,可以直接导航到景区内。页面内容包含景区的创建和景区大屏展示,在大屏展示中可以对商铺和点位进行绑定,另页面中集成了coze的ai智能系统,可进行对话,绑定商铺活动或者一些可自动化生产的内容,将在视频演示中详细讲解。
下面跟着作者一步一步来实现吧~
调用
useAsyncEffect(async()=>{AMapRef.current=awaitCreateAMap()if(containerRef.current){createMap()}},[])将AMAP实例存放在AMapRef.current中,在后面的方法中都可以使用,并且在containerRef.current存在的时候绘制地图,containerRef.current是将要绘制地图的容器,id为container
constcreateMap=async()=>{letAMap=AMapRef.current//创建地图varmap=newAMap.Map("container",{resizeEnable:true,center:[119.986,30.2235],//地图中心点zoom:17.4,//地图显示的缩放级别viewMode:'3D',//开启3D视图,默认为关闭buildingAnimation:true,//楼块出现是否带动画pitch:45,rotation:45,features:['bg','building'],//只显示建筑、道路、区域//showLabel:false,//隐藏标注信息mapStyle:"amap://styles/grey",showIndoorMap:false,//rotateEnable:false,//pitchEnable:false,zIndex:9});mapRef.current=mapvarloca=new(windowasany).Loca.Container({map,zIndex:9});locaRef.current=loca……}以上配置项都是基础需要配置的,可以参照官网上面的介绍进行配置,这里需要说明一点是features和viewMode一个是过滤标注信息,项目中去掉了其他的元素,只展示了建筑和背景,而mapStyle则是官网提供的一个3d黑色主题的风格,当然,你用自己的自定义地图生成的地址也可以放在这里,下面从图中看一下具体创建出一个什么样的地图
从图中可以看到一个类似矩形的区域,那个就是本次要做的景区——闲林埠,下一步就是要将自制的景区模型加载到高德地图中
首先说明,本人不是UI,只是一个技术死宅,对于美术上的事儿一窍不通,在做的过程中,就有一种感觉,我加入的3d景区,还不如高德原本的模型好看~,勿喷
exportconstcleanBuild=(AMap:any,map:any)=>{//底图楼块扣除varbuilding=newAMap.Buildings({zIndex:10,});building.setStyle({hideWithoutStyle:false,//是否隐藏设定区域外的楼块areas:[{visible:false,//是否可见rejectTexture:false,//是否屏蔽自定义地图的纹理color1:'00000000',//楼顶颜色color2:'00000000',//楼面颜色path:[ClearBuildPoint]}]});map.add(building);}需要在cleanBuild方法中传入之前我们创建的AMAP实例和地图map的实例,那么你也可以对3d楼块自定义颜色,path传入的是围栏的经纬度,ClearBuildPoint是围栏的经纬度数据。
这样我们就去除了原有的模块,下面我们将在高德地图中加入threejs的3d世界,景区模型是使用svg文件绘制的,这样不需要3d设计师的介入,靠前端也可以生成一个3d模型,如果有3d模型设计师,那自然是更好的了。svg生成的模型肯定没有3d设计师绘制的模型好看,本文主要讲实现的过程,细节咱们不深究
exportconstcreateScene=(AMap:any,map:any,css2dRenderDom:any)=>{customCoords=map.customCoords;constcenter=map.getCenter()customCoords.setCenter([center.lng,center.lat]);returnnewPromise((resolve,reject)=>{vargllayer=newAMap.GLCustomLayer({zIndex:110,//图层的层级init:async(gl)=>{……},render:()=>{……},})map.add(gllayer);})}在init回调中可以用来创建镜头、渲染器、灯光等场景元素
renderer=newWebGLRenderer({context:gl});这里着重介绍一下context,官网是这么介绍的
而init的回调接受一个参数gl,这个参数就是高德地图提供的canvas实例,就是说我们绘制的3d场景都将在这个高德地图的canvas中去绘制,场景中还有2d元素图层,光照等内容,这里不一一赘述,开发过程中有一个疑问,一直没找答案
render的回调中,可以对相机和渲染器进行设置,将AMAP的一些参数和自有数据图层的数据赋值到threejs的元素中。包含设置相机位置,渲染器重绘等
svg文件是作者用AdobeIllustrator制作的,制作成本低,它大概长这样,根据高德地图上的景区绘制的,
这是它在高德地图中的样子
import{SVGLoader}from'three/examples/jsm/loaders/SVGLoader';constsvgLoader=newSVGLoader()exportfunctionloadSVG(url:string){returnnewPromise((res,reg)=>{svgLoader.load(url,(data:any)=>{res(data)})})}在threejs中加载svg使用SVGLoader的API,由于不是threejs内置的方法,所以要用显示引用将方法引入进来,loadSVG接受路径,并在加载后将数据使用promise的成功回调传出去,加载后得到参数类型是#形状路径(ShapePath),接下来对路径进行加工,并使用#挤压缓冲几何体(ExtrudeGeometry)挤出高度
map.on('click',async(e:any)=>{console.log(e.lnglat.lng,e.lnglat.lat);constlnglat=[e.lnglat.lng,e.lnglat.lat]map.render();})前面创建的mapRef对地图进行点击事件的绑定,e参数包含了经纬度、目标、事件类型等信息。
rallyist便是与射线交互的模型信息,从这个数组的长度便可判断出,当前点击的位置是在高德地图上,还是在模型上,并且还知道在哪个模型上,从而进行模型的绑定。
想象一下这个场景,3d设计师给了你一个飞机的模型,并规划好了行动路线(vector3向量组),而你呢,需要在飞机飞行的过程中检测飞机是否经过某个建筑(经纬度标注),从而来进行一些对于飞机或者建筑的交互。
官网对坐标转换功能,没有过多的内容,只有api的介绍,我想大概是高等机密吧,这两个坐标一个是地理坐标,一个是映射坐标,或许通过常见的投影方式如墨卡托投影、高斯-克吕格投影等进行的投影变换,亦或是其他。
我们只管用,做一个api搬运工,下面做一个实验,在map的点击事件里,获取到地图经纬度beforelnglat和模型的射线交互的世界坐标rallyList[0].point,在世界坐标通过一系列的转换后得到的经纬度坐标和点击地图获取到的经纬度进行对比,看看两者是否有区别。。。
从打印的数据看来,两者是完全一样的。
上面的代码用到了两个方法,一个是getViewCp世界坐标转屏幕坐标,另一个是coordsToLngLats屏幕坐标转经纬度,这个是高德地图提供的api。
像素坐标,确定地图上的一个像素点。
这样便将一个用react组件写的标记添加到高德地图中,顺便提一嘴,如果不是在AMAP中添加标记,而是在threejs的css3drender中添加标记也是同样的道理,需要将react组件转成html标识或者dom节点,所以个人标识不喜欢用虚拟dom的框架开发threejs,太繁琐。
前面的所有内容都是对于模型和地图的定义和交互的设置,下面将对景区的数据进行绑定解绑等功能的开发。
由于没有后端给写服务器,所以只采用前端基于浏览器的indexDB进行数据管理,在代码中封装了对于数据的增删改查功能,
项目的UI使用的antd的组件库
这里就是一个简单的像indexdb库里插入一条景区的数据,不过多赘述,起始位置是设定模型在高德地图中的位置,按理说模型路径应该是上传文件,我这里是将oss路径内置到选项中,直接选择就可以,你可以增加很多个景区,但是模型只有一个。
前面写过的map.on来监听click事件,并通过射线获取当前点击到的模型,并存储模型的经纬度,并将数据和景区的数据绑定。
//弹窗确定按钮constadd=()=>{form.validateFields().then((values)=>{if(storeState.isEdit){editStore(storeState.storeId||'',values)}else{if(props.addId){addStore({...values,lnglat,storeId:props.addId})}}})}在添加点位后还需要调用refreshStore方法刷新一下模型,将刚添加的模型进行重绘和绑定。
绑定设施点位逻辑与绑定店铺相同,只不过只需要存下经纬度和基础信息即可不需要记录模型信息。
项目中集成了coze的ai系统,并且设置了bot
从流程图中可以看到,当用户输入肯德基这个关键词,bot会执行工作流,并最终输出一条预支好的消息。
这里做了交互,点击店铺的marker,则获取店铺的信息,并向aibot进行询问,aibot识别到关键字“肯德基”则会自动回复预设好的内容,如果输入其他内容,aibot也会回答的,如下图
另注:coze的aibot将在8月15日后进行限流访问,可以在代码中替换自己的ai智慧体,高德地图的api也是限流的,超过额度就不可使用了,尽量在项目中使用自己申请的key