maptalks+three实战项目 智慧城市项目

发布时间 2023-04-26 20:59:03作者: 爱吃土豆丝嗯

@

概述

如有不明白的可以加QQ:2354528292;wx: aichitudousien
更多教学视频请访问:https://space.bilibili.com/236087412

源码获取: https://item.taobao.com/item.htm?spm=a21dvs.23580594.0.0.3c3a645eIMTaft&ft=t&id=714967503431

经过上一次使用three.js做了个demo,这次使用maptalks+three+vue来继续开发一个demo玩玩
先看视频效果:

[video(video-bvzzjqdd-1615007935063)(type-csdn)(url-https://live.csdn.net/v/embed/156451)(image-https://vedu.csdnimg.cn/92e455d21899461ba223cc19b3530db5/snapshots/47b9fea390504faa91d59a2836f73dde-00002.jpg)(title-maptalks+three 智慧城市)]

搭建开发环境

使用的开发框架是vue-cli3.0, webgl使用three.js,地图使用maptalks,开发工具为vscode
搭建完成后的目录为
在这里插入图片描述

搭建maptalks+three场景

和上一个项目初始化场景一样,先创建一个ZThree的类,然后写入一个初始化方法initMapTalks

initMapTalks(option) {
    let config = {
      center: [113.31915199756622, 23.109087176037534],
      zoom: 17,
      pitch: 70,
      bearing: 180,

      centerCross: true,
      doubleClickZoom: false,
      baseLayer: new maptalks.TileLayer('tile', {
        urlTemplate: 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png',
        subdomains: ['a', 'b', 'c', 'd']
      })
    };
    option = Object.assign(config, option);
    let map = new maptalks.Map(this.id, option);

    let threeLayer = new ThreeLayer('t', {
      forceRenderOnMoving: true,
      forceRenderOnRotating: true
    });

    threeLayer.addTo(map);

    ThreeLayer.prototype.coordinateToXYZ = function (coordinate, height = 0) {
      let z = this.distanceToVector3(height, height).x;
      let v = this.coordinateToVector3(coordinate, z)
      return [v.x, v.y, v.z];
    }

    return threeLayer;
  }
  • 实例化类
app = new ZThree("screen");

创建灯光

灯光代码和上次项目的代码一样,在博客中的智慧城市第一篇文章中查找

app.initLight();

此时我们看到的是已经成功创建好了地图
在这里插入图片描述

加载建筑模型

  1. 创建加载模型方法 loadBuilding
export function loadBuilding(threeLayer) {
  let features = buildingGeoJosn.features;

  let polygons = features.map(f => {
    let height = Number(f.properties.height);
    let polygon = maptalks.GeoJSON.toGeometry(f);
    polygon.setProperties({
      height,
    });
    return polygon;
  });

  let material = new THREE.ShaderMaterial({
    uniforms: bulidingShader.uniforms,
    vertexShader: bulidingShader.vs,
    fragmentShader: bulidingShader.fs,
    side: THREE.DoubleSide,
    transparent: true,
  })

  let mesh = threeLayer.toExtrudePolygons(polygons, {
    interactive: false
  }, material);

  let meshs = [];

  let bufferGeometry = mesh.getObject3d().geometry;
  let geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);

  let {
    vertices,
    faces,
    faceVertexUvs
  } = geometry;
  for (let i = 0, len = faces.length; i < len; i++) {
    let {
      a,
      b,
      c
    } = faces[i];
    let p1 = vertices[a],
      p2 = vertices[b],
      p3 = vertices[c];
    //top face
    if (p1.z > 0 && p2.z > 0 && p3.z > 0) {
      let uvs = faceVertexUvs[0][i];
      for (let j = 0, len1 = uvs.length; j < len1; j++) {
        uvs[j].x = 0;
        uvs[j].y = 0;
      }
    }
  }
  mesh.getObject3d().geometry = new THREE.BufferGeometry().fromGeometry(geometry);
  bufferGeometry.dispose();
  geometry.dispose();
  meshs.push(mesh);

  threeLayer.addMesh(meshs);
}
  1. 在场景中调用此方法数据
loadBuilding(threeLayer);

数据可从此地址获得 https://www.openstreetmap.org/
此时我们可以看到的效果是,用的数据量小的做开发,后期数据自己替换即可
在这里插入图片描述
建筑贴图:
在这里插入图片描述

加载流光道路

流光道路使用MeshLineMaterial来实现,因为three.js的线条材质是没有宽度的,所以此处我们需要导入一个新的库THREE.MeshLine,当然可以使用管道tube来实现也是可以的,使用管道来实现请查看博客中的流光特效文章

export function loaderRoad(threeLayer, SpriteLine) {
  let texture = new THREE.TextureLoader().load('texture/road.png');
  texture.anisotropy = 16;
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  let camera = threeLayer.getCamera();

  let material = new MeshLineMaterial({
    map: texture,
    useMap: true,
    lineWidth: 13,
    sizeAttenuation: false,
    transparent: true,
    near: camera.near,
    far: camera.far
  });

  let features = roadGeoJosn.features;

  let meshs = []

  features.forEach(item => {
    if (item.geometry.type === 'LineString' && item.properties?.highway === 'primary') {
      let line = maptalks.GeoJSON.toGeometry(item);
      let mesh = new SpriteLine(line, {
        altitude: 0
      }, material, threeLayer);
      meshs.push(mesh);
    }
  })

  threeLayer.addMesh(meshs);
}

数据来源和建筑的是一个地址,添加好后在场景中引入即可,流光动画只要偏移贴图的uv就好了
此时的效果
在这里插入图片描述
在这里插入图片描述
道路流光的贴图
在这里插入图片描述

加载水系

水系使用Ocean来制作,具体的可以在github上搜索Ocean来看案例

export function loaderWater(threeLayer) {
  let polygons = maptalks.GeoJSON.toGeometry(waterGeoJosn);
  let oceans = polygons.map(p => {
    let ocean = new Ocean(p, {
      waterNormals: 'texture/waternormals.jpg'
    }, threeLayer)
    return ocean;
  });
  threeLayer.addMesh(oceans);
}

水系的贴图
在这里插入图片描述
此时我们看到的效果是
在这里插入图片描述

加载大厦模型

模型在网上随便找了一个,勉强能用
加载模型代码

export async function loaderModel(app, threeLayer, position) {
  let model = await app.loaderModel(
    "model/",
    'building'
  );
  let material = new THREE.ShaderMaterial({
    uniforms: modelShader.uniforms,
    vertexShader: modelShader.vs,
    fragmentShader: modelShader.fs,
    side: THREE.DoubleSide,
    transparent: true,
  })
  model.traverse(obj => {
    obj.material = material;
    obj.scale.set(1.5, 1.5, 1.5);
  })
  model.position.set(...position);
  model.rotateX(Math.PI / 2)
  threeLayer.addMesh(model);
  return model;
}

模型下载地址:https://download.csdn.net/download/qq_39503511/15611779
加载成功后看到的效果:
在这里插入图片描述

加载飞行线条

飞行线条也和上期代码中的开发方式一样的,通过三个点生成贝塞尔曲线,在添加贴图偏移就好了

export function loaderCurve(app, threeLayer, geojson = curve) {
  let points = geojson.features.map(item => {
    let coordinates = item.geometry.coordinates;
    return [new THREE.Vector3(...threeLayer.coordinateToXYZ(coordinates)), new THREE.Vector3(...threeLayer.coordinateToXYZ([-73.98565649986267, 40.74843959459197], 258))]
  })
  let meshs = []

  points.forEach((point, index) => {
    let c = point[0].clone().add(point[1].clone()).divideScalar(2);
    c.z += 4
    let curve = new THREE.QuadraticBezierCurve3(point[0], c, point[1])
    let points = curve.getPoints(80)
    let line = new THREE.CatmullRomCurve3(points)
    let tube = app.loaderTube(
      line,
      index % 2 === 0 ? tubeRedMaterial : tubeGreenMaterial
    );
    meshs.push(tube);
  })
  threeLayer.addMesh(meshs);
}

此时我们看到的效果是
在这里插入图片描述

创建圆锥体

export function loaderCone(threeLayer, geojson = cone) {
  let points = geojson.features.map(item => {
    let coordinates = item.geometry.coordinates;
    return threeLayer.coordinateToXYZ(coordinates, 100)
  })
  let geometry = new THREE.ConeGeometry(0.2, 0.4, 4);

  let meshs = []
  points.forEach(item => {
    let material = new THREE.MeshBasicMaterial({
      color: `rgb${rgb()}`,
      transparent: true,
      opacity: 8,
      side: THREE.DoubleSide,
      depthWrite: false
    });
    let cone = new THREE.Mesh(geometry, material);
    cone.rotateX(-Math.PI / 2);
    cone.position.set(...item);
    meshs.push(cone);
  });
  threeLayer.addMesh(meshs);
  return meshs;
}

rgb函数是一个获取随机rbg颜色函数

export function rgb() { //rgb颜色随机
  let r = Math.floor(Math.random() * 256);
  let g = Math.floor(Math.random() * 256);
  let b = Math.floor(Math.random() * 256);
  let rgb = '(' + r + ',' + g + ',' + b + ')';
  return rgb;
}

此时我们看到场景中的效果是
在这里插入图片描述

创建文本

最后再来创建一些基础的文本,使用精灵来创建

export function loaderText(app, threeLayer, geojson = text) {
  geojson.features.forEach(item => {
    let coordinates = item.geometry.coordinates;
    let position = threeLayer.coordinateToXYZ(coordinates, 200);
    let name = item.properties.name;
    let element = `
                    <div class="sprite-canvas">
                        <span class="sprite-layer">${name}</span>
                    </div>`;
    app.addHtmlCanvas({
      parent: app.scene,
      position,
      element
    })
  })

}

addHtmlCanvas方法在上期已经贴出代码,通过html来生成canvas,然后在map使用就好
此时的效果:
在这里插入图片描述
最后我们给整个场景添加一个高光的后期处理就ok了

export function loaderBloom(threeLayer) {
  const params = {
    exposure: 1,
    bloomStrength: 1.5,
    bloomThreshold: 0,
    bloomRadius: 0,
    debug: false
  };
  const renderer = threeLayer.getThreeRenderer();
  const size = threeLayer.getMap().getSize();
  threeLayer.composer = new EffectComposer(renderer);
  threeLayer.composer.setSize(size.width, size.height);

  const scene = threeLayer.getScene(),
    camera = threeLayer.getCamera();
  threeLayer.renderPass = new RenderPass(scene, camera);

  threeLayer.composer.addPass(threeLayer.renderPass);

  const bloomPass = threeLayer.bloomPass = new UnrealBloomPass(new THREE.Vector2(size.width, size.height));
  bloomPass.renderToScreen = true;
  bloomPass.threshold = params.bloomThreshold;
  bloomPass.strength = params.bloomStrength;
  bloomPass.radius = params.bloomRadius;
  threeLayer.composer.addPass(bloomPass);
  threeLayer.bloomEnable = true;

  threeLayer.getRenderer().renderScene = function () {
    const layer = this.layer;
    layer._callbackBaseObjectAnimation();
    this._syncCamera();
    const renderer = this.context,
      camera = this.camera,
      scene = this.scene;
    if (
      layer.bloomEnable &&
      layer.composer &&
      layer.composer.passes.length > 1
    ) {
      layer.composer.render(0);
      renderer.clearDepth();
      camera.layers.set(0);
      renderer.render(scene, camera);
    }
    this.completeRender();
  };
}

在这里插入图片描述
ok, 此时我们的整个场景就比之前亮了,我们在替换一下数据就成视频中的效果了
在这里插入图片描述