技术架构
vue3+高德地图2.0+threejs
代码步骤
这里我们就用合肥市为主要的地区,将其他地区扣除,首先使用高德的webapi的DistrictSearch功能,使用该功能之前记得检查一下初始化的时候是否添加到plugins中,然后搜索合肥市的行政数据。
district.search(targetArea.name, (status, result) => {if (status === "complete") {//设置中心点map.setCenter([result.districtList[0].center.lng, result.districtList[0].center.lat], true);//整个世界let outer = [new AMap.LngLat(-360, 90, true), new AMap.LngLat(-360, -90, true), new AMap.LngLat(360, -90, true), new AMap.LngLat(360, 90, true)];//合肥市边界信息let holes = result.districtList[0].boundaries;let pathArray = [outer];// 绘制行政区划pathArray.push.apply(pathArray, holes);// 在合肥市内描边let polygon1 = new AMap.Polygon({path: [outer],strokeColor: "#00eeff",strokeWeight: 5,fillOpacity: 0,});//添加至地图polygon.setPath(pathArray);map.add(polygon);}});
这里就已经有了整个城市的轮廓。

我们的需求是自定义背景,首先我们需要获取到除了合肥市以外的地区,这里我们使用threejs,版本是0.157.0,大家这里可以安装和我一样的版本保证不会出错,版本太高会有问题,控制台 npm i three@0.157.0,在代码中import * as THREE from "three"导入,这里我们单独将threejs业务代码分出去。
------继续上面的代码----------//先获取地图画布的大小const innerHeight = mapRef.value.clientHeight;const innerWidth = mapRef.value.clientWidth;let options = {pathArray,center: [result.districtList[0].center.lng, result.districtList[0].center.lat],size: {innerWidth,innerHeight,},};//这里传入数据,单独写一个js文件来处理threejs业务 ,instace是AMapcreateThreeLayer(instance, map, options);
收集到我们需要的数据之后单独建一个js文件,先安装一个插件叫turf,这个插件非常善于处理地图上的数学、多边型,先 npm i @turf/turf,再导入import * as turf from "@turf/turf,下面开始写逻辑。
import * as THREE from "three";import * as turf from "@turf/turf";let glLayer;let camera, renderer, scene, raycaster;let customCoords = null;/*** @description 创建ThreeLayer*/export function createThreeLayer(instance, map, options) {const { polygon, center, size } = options;customCoords = map.customCoords;// 地图中心转换为墨卡托const centerCoord = customCoords.lngLatToCoord(center);// polygon是传入的多边形坐标,是一个非遮罩区域,outer外边框是整个地图的边界const outer = polygon[0];const inner = polygon[1];// inner的bboxconst turfPoints = inner.map((item) => [item.lng, item.lat]);const innerBbox = turf.bbox(turf.polygon([turfPoints]));// 最小正方形const squared = turf.square(innerBbox);// 转为4个点const squaredPoints = [[squared[0], squared[1]],[squared[0], squared[3]],[squared[2], squared[3]],[squared[2], squared[1]],];// 多边形坐标转换为世界中的坐标const innerCoords = inner.map((item) => {return {...item,coord: customCoords.lngLatToCoord([item.lng, item.lat]),};});const outerCoords = outer.map((item) => {return {...item,coord: customCoords.lngLatToCoord([item.lng, item.lat]),};});//世界边界点const squaredPointsCoords = squaredPoints.map((item) => customCoords.lngLatToCoord(item));//——--------------------——------------继续写代码的位置-------------------------}
这里将我们所有使用的点转换好了之后就可以创建自定义图层了,接下来开始绘制,这里我们准备两张图片,一张贴图,一张法线贴图(让图片凹凸部分感光,让画面更有质感),所有步骤的解释都在注释里面。
// 背景贴图const bgTexture = new THREE.TextureLoader().load("static/three/bg.png");// 法线背景贴图const normalTexture = new THREE.TextureLoader().load("static/three/bg_normal.png");if (!glLayer) {glLayer = new instance.GLCustomLayer({zIndex: 10,init: (gl) => {camera = new THREE.PerspectiveCamera(45, size.innerWidth / size.innerHeight, 100, 1 << 30);renderer = new THREE.WebGLRenderer({context: gl, // 地图的 gl 上下文});renderer.autoClear = false;scene = new THREE.Scene();// 环境光照和平行光let aLight = new THREE.AmbientLight(0xffffff, 2);let dLight = new THREE.DirectionalLight(0xffffff);dLight.intensity = 6;dLight.position.set(centerCoord[0], -100000, 50000);dLight.target.position.set(centerCoord[0], centerCoord[1], 0);dLight.target.updateMatrixWorld();scene.add(dLight);scene.add(aLight);// 辅助// const helper = new THREE.DirectionalLightHelper(dLight, 1);// scene.add(helper);// 辅助坐标轴// const axesHelper = new THREE.AxesHelper(100000);// scene.add(axesHelper);// 创建遮罩区域,遮罩区域 = outer - innerconst maskShape = new THREE.Shape();// 创建outermaskShape.moveTo(outerCoords[0].coord[0], outerCoords[0].coord[1]);outerCoords.forEach((item) => {maskShape.lineTo(item.coord[0], item.coord[1]);});// 创建innermaskShape.holes.push(new THREE.Path());maskShape.holes[0].moveTo(innerCoords[0].coord[0], innerCoords[0].coord[1]);innerCoords.forEach((item) => {maskShape.holes[0].lineTo(item.coord[0], item.coord[1]);});// 信息添加给多边形const maskGeometry = new THREE.ShapeGeometry(maskShape);// 调整纹理坐标 这里是调整贴图偏移和大小bgTexture.wrapS = THREE.RepeatWrapping;bgTexture.wrapT = THREE.RepeatWrapping;bgTexture.repeat.set(120, 120);bgTexture.offset.set(0.877, 0.275);//这里是调整法线贴图的normalTexture.wrapS = THREE.RepeatWrapping;normalTexture.wrapT = THREE.RepeatWrapping;normalTexture.repeat.set(120, 120);normalTexture.offset.set(0.877, 0.275);// 调整 UVmaskGeometry.computeBoundingBox();const bbox = maskGeometry.boundingBox;let size1 = new THREE.Vector2();bbox.getSize(size1);const uvAttribute = maskGeometry.attributes.uv;for (let i = 0; i < uvAttribute.count; i++) {const uv = new THREE.Vector2().fromBufferAttribute(uvAttribute, i);uv.x = (uv.x - bbox.min.x) / size1.x; // uv坐标映射uv.y = (uv.y - bbox.min.y) / size1.y;uvAttribute.setXY(i, uv.x, uv.y);}uvAttribute.needsUpdate = true;const maskMaterial = new THREE.MeshStandardMaterial({map: bgTexture,side: THREE.DoubleSide,transparent: true,normalMap: normalTexture,roughness: 0.5,metalness: 0.5,});const maskMesh = new THREE.Mesh(maskGeometry, maskMaterial);scene.add(maskMesh);},render: () => {let { 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);renderer.resetState();},});map.add(glLayer);const animate = () => {map.render();requestAnimationFrame(animate);};animate();function onWindowResize() {camera.aspect = size.innerWidth / size.innerHeight;camera.updateProjectionMatrix();renderer.setSize(size.innerWidth, size.innerHeight);}window.addEventListener("resize", onWindowResize);}
下面放出效果图。

因为加上了法线贴图所以他会像这样。


加入高德地图各种特效之后是这样的效果。

素材放在下面了,需要的朋友可以自取哦~



高德开放平台第二期实战案例,三等奖作品
作者:左文韬
仅代表作者个人观点



















