vue中使用高德地图自定义掩膜背景结合threejs

发布于:2025-02-16 ⋅ 阅读:(31) ⋅ 点赞:(0)

技术架构

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的bbox  const 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 - inner        const maskShape = new THREE.Shape();
        // 创建outer        maskShape.moveTo(outerCoords[0].coord[0], outerCoords[0].coord[1]);        outerCoords.forEach((item) => {          maskShape.lineTo(item.coord[0], item.coord[1]);        });
        // 创建inner        maskShape.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);
        // 调整 UV        maskGeometry.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);  }

下面放出效果图。

图片

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

图片

图片

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

图片

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

图片

图片

图片

高德开放平台第二期实战案例,三等奖作品

作者:左文韬

仅代表作者个人观点