北京市公交三维可视化——持续更新中(2024/6/12)

8,280 阅读8分钟

声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。

🍟更新日志

  • 2023年10月23日 增加地图和公交车的联动
  • 2023年10月24日 添加详细的文字说明
  • 2023年11月6日 增加左侧栏目
  • 2023年11月8日 地图增加建筑白膜和公交OD线位置纠偏
  • 2024年1月26日(29日) 增加开场动画
  • 2024年2月5日 地图增加增加北京水系
  • 2024年6月12日 增加公交实时进度样式,优化性能和增加开屏窗口动画,增加选中公交线地图公交线动画效果

🚦制作不易,请点赞 关注 收藏😁

🍗前言

首先看一下我们要完成的效果图(效果图来自thingjs),大概我们要按照这个来完成。这里我们选择的是mars3d开发数字孪生,Mars3D三维可视化平台 是火星科技研发的一款基于 WebGL 技术实现的三维客户端开发平台,基于Cesium优化提升与B/S架构设计,支持多行业扩展的轻量级高效能GIS开发平台,能够免安装、无插件地在浏览器中高效运行,并可快速接入与使用多种GIS数据和三维模型,呈现三维空间的可视化,完成平台在不同行业的灵活应用。只要你是web前端就可以开发,门槛更低 image.png

🍖开发语言

🍝头部组件(HeadTitle)

image.png

🍛效果图

image.png

🍤地图

首先加载一个地图

   basemaps: [
      {
        name: "电子地图",
        icon: "img/basemaps/google_vec.png",
        type: "xyz",
        url:
          "https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}",
        show: true,
      },
    ],

效果图的地图是黑色的,需要使用瓦片颜色滤镜GaodeLayer,地图是灰黑色,显的更加科技感

  tileLayer = new mars3d.layer.GaodeLayer({
    layer: "vec",
    invertColor: true,
    filterColor: "#485468",
    brightness: 0.6,
    contrast: 1.8,
    gamma: 0.3,
    hue: 1,
    saturation: 0,
  });
  map.addLayer(tileLayer);

🍱效果

image.png

增加开场动画(2024/1/26更新)

// 开场动画
const startAnimation = () => {
  map.flyHome({ duration: 0 });
  map.openFlyAnimation({
    callback: function () {
      // 动画结束执行加载模型
      initBuilding();
    },
  });
};

🍣北京公交线(OD线)

使用PolylineCombine图层,这里调节的一个是线的宽度和增加一个线条需要的背景图片,其他复制粘贴就行

//  北京公交线(OD线)
const busLine = () => {
  // 创建矢量数据图层
  graphicLayer = new mars3d.layer.GraphicLayer();
  map.addLayer(graphicLayer);
  let data = bjgjJson;
  let busLines = [];
  data.forEach(function (busLine, idx) {
    let prevPt;
    const points = [];
    for (let i = 0; i < busLine.length; i += 2) {
      let pt = [busLine[i], busLine[i + 1]];
      if (i > 0) {
        pt = [prevPt[0] + pt[0], prevPt[1] + pt[1]];
      }
      prevPt = pt;

      const longitude = pt[0] / 1e4;
      const latitude = pt[1] / 1e4;
      const cart = Cesium.Cartesian3.fromDegrees(longitude, latitude, 100.0);
      points.push(cart);
    }
    busLines.push({
      positions: points,
      style: {
        width: 0.3,
        materialType: mars3d.MaterialType.LineFlow,
        image: getAssetsFile("lightFlow_strip07.png"),
         speed: 2 + 1.0 * Math.random(),
      },
    });
  });

  // 多个线对象的合并渲染。
  const graphic = new mars3d.graphic.PolylineCombine({
    instances: busLines,
  });
  graphicLayer.addGraphic(graphic);
};

GIF 2023-9-20 星期三 8-45-09.gif

公交线纠偏(2023/11/8更新)

从上面的gif里可以看到线和地图的道路是存在偏移的,这个由于坐标系不统一造成的

let point = mars3d.PointTrans.gcj2wgs([longitude, latitude]);
//new Cesium.Cartographic(longitude, latitude, height),高度这里调成0
const cart = Cesium.Cartesian3.fromDegrees(point[0], point[1], 0.0);

这样调整之后线就完全在道路上了 image.png

🍥北京公交汽车路线和停车点

const actualBus = () => {
  //处理数据
  busRoute.map(item=>{
    //绘制公交路线
    let positions=item.polyline.split(';')
    let lineData={
      positions,
      style: {
        width: 4,
        color: item.linecolor,
      },
    }
    const BusLine = new mars3d.graphic.PolylinePrimitive(lineData);
    graphicLayer.addGraphic(BusLine);
    //绘制公交停车点
    item.busstops.map(item=>{
      const BusPoints = new mars3d.graphic.PointPrimitive({
        position:item.location,
        style:{
          color: "#fff",
          pixelSize: 5,
          label: {
            text: item.name,
            font_size: 12,
            color: "#19d5ff",
            pixelOffsetY: -20,
            background:true,
            backgroundPadding:8,
            distanceDisplayCondition: true,
            distanceDisplayCondition_far: 5000,
            distanceDisplayCondition_near: 0
          }
        }
      })
      graphicLayer.addGraphic(BusPoints);
    })
  })
};

首先说一下我这个公交线的数据来源高德地图

其中busstops是停车点位,name是公交路线名称,polyline是公交线的经纬度 image.png

image.png 使用PolylinePrimitive图层画公交线,PointPrimitive图层画停车点,会发现点位是偏移的,这时候使用mars3d.PointTrans.gcj2wgs对坐标纠偏

🍙公交车行驶效果

这个参照了无人机的飞行轨迹的案例 看一下需要的数据结构

image.png

其中x是经度,y是纬度,z是海拔,time是经过该点的时间 首先获取一下当前的时间在toISOString一下 就得到了出发点的一组数据,紧接着对剩下的数据循环,通过Cesium.Cartesian3.distance计算两个点位的距离,在除以速度得到这个路段需要花费的时间,对前面的时间累加就得到给点经过时间

const getSampledPositionProperty = (points: any) => {
  let speed = 20;
  let time = 0;
  let startTime = new Date();
  // 公交车的行驶路径
  let busPath = [
    {
      id: 1,
      x: points[0].split(",")[0],
      y: points[0].split(",")[1],
      z: 0,
      time: startTime.toISOString(),
    },
  ];
  // 公交车的发车时间
  for (let i in points) {
    if (i < points.length - 1) {
      let pointFirst = points[i].split(",");
      let pointSecond = points[Number(i) + 1].split(",");
      let positionLeft = Cesium.Cartesian3.fromDegrees(pointFirst[0], pointFirst[1]);
      let positionRight = Cesium.Cartesian3.fromDegrees(pointSecond[0], pointSecond[1]);
      let distance = Cesium.Cartesian3.distance(positionLeft, positionRight);
      time += (distance / 1000 / speed) * 3600;
      let currentTime = parseInt(startTime.getTime() + time);
      let BusMoment = new Date(currentTime).toISOString();
      let vote = {
        id: i + 2,
        x: pointSecond[0],
        y: pointSecond[1],
        z: 0,
        time: BusMoment,
      };
      busPath.push(vote);
    }
  }
  const property = new Cesium.SampledPositionProperty();
  property.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD;

  let start;
  let stop;
  for (let i = 0, len = busPath.length; i < len; i++) {
    const item = busPath[i];
    const lng = Number(item.x); // 经度
    const lat = Number(item.y); // 纬度
    const height = item.z; // 高度
    const time = item.time; // 时间

    let position = null;
    if (lng && lat) {
      position = Cesium.Cartesian3.fromDegrees(lng, lat, height);
    }
    let juliaDate = null;
    if (time) {
      juliaDate = Cesium.JulianDate.fromIso8601(time);
    }
    if (position && juliaDate) {
      property.addSample(juliaDate, position);
    }

    if (i === 0) {
      start = juliaDate;
    } else if (i === len - 1) {
      stop = juliaDate;
    }
  }

  // 设置时钟属性
  map.clock.startTime = start.clone();
  map.clock.stopTime = stop.clone();
  map.clock.currentTime = start.clone();
  map.clock.clockRange = Cesium.ClockRange.LOOP_STOP;
  map.clock.multiplier = 1;

  if (map.controls.timeline) {
    map.controls.timeline.zoomTo(start, stop);
  }

  // 创建path对象
  let busPosition = new mars3d.graphic.PathEntity({
    position: property,
    orientation: new Cesium.VelocityOrientationProperty(property),
    style: {
      width: 0,//去除轨迹线
    },
    model: {
      url: "/model/bus.gltf",
      fill: true,
      color: "#619e21",
      scale: 1,
      minimumPixelSize: 40,
    },
  });
  graphicLayer.addGraphic(busPosition);
};

GIF 2023-10-19 星期四 14-43-23.gif

🍚给公交车增加车牌号和载客量

    label: {
      text: busRoute[0].licence + "(" + busRoute[0].passengers + "人)",
      font_size: 12,
      background: true,
      backgroundColor: "#f7bd42",
      outline: true,
      outlineColor: "#fff",
      hasPixelOffset: true,
      pixelOffsetY: -20,
      distanceDisplayCondition: true,
    },

image.png

🍜给公交线增加起点和终点

对第一个点和最后一个增加带图片的标记点

   if (index === 0) {
        const startingPoint = new mars3d.graphic.BillboardPrimitive({
          position: positions,
          style: {
            image: getAssetsFile("origin.png"),
            scale: 0.7,
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          },
        });
        graphicLayer.addGraphic(startingPoint);
      } else if (index === monomer.busstops.length - 1) {
        const endPoint = new mars3d.graphic.BillboardPrimitive({
          position: positions,
          style: {
            image: getAssetsFile("end.png"),
            scale: 0.7,
            horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          },
        });
        graphicLayer.addGraphic(endPoint);
      }

image.png

左侧栏目

新建一个CompositeIndex组件 做成的效果是

image.png

地图增加建筑模型和河流

shp文件处理

北京的建筑我放到这个文件夹下面了

image.png 我们要做的是把shp文件转成一份 3dTiles数据集,可以使用cesiumlab

下载

image.png 大家自行注册一下,登陆成功后是这个样子

image.png 我们选择通用模型切片,在输入文件中选择+shp,添加北京建筑.shp,下面列表会出现一行数据,点击右侧设置, 会出现一个设置的弹窗,在空间参考里选择从文件导入,下面截图的文件即可,造型参数,建筑高度选择成高度字段,点击确认即可,在数据存储选择输出路径点击提交就行,数据量大处理时间较长,请耐心等待

image.png 具体操作流程

GIF 2023-11-7 星期二 13-41-28.gif

添加加载北京市建筑物代码

// 加载北京市建筑物
const initBuilding = () => {
  buildingLayer = new mars3d.layer.TilesetLayer({
    name: "北京市建筑物",
    url: "/model/building/tileset.json",
  });
  map.addLayer(buildingLayer);
};

效果图

image.png

增加河流

公交实时进度

坐公交的时候,一般都会看一下公交还有多久到站,避免错过,下面是车来了APP,我们也做一个差不多的,能够跟地图的公交位置进行同步。

2d2b21bf92eabfc88e89cd4bf2e34062.gif

获取公交实时位置

使用的是笑园实时公交API

image.png 获取一下北京市城市ID

import { onMounted } from "vue";
import { getBus } from "@/api";
// 获取城市列表
const getCityLists = async () => {
  const res = await getBus({ optype: "city", uname: "2105856317@qq.com" });
  console.log("城市列表", res);
};
onMounted(() => {
  getCityLists();
});

image.png 获取公交路线

 const getBusLine = async () => {
   const res = await getBus({
     optype: "luxian",
     uname: "2105856317@qq.com",
     cityid: "73",
     keywords: "101",
     keySecret: md5("2105856317@qq.com你的key值luxian"),
   });
   console.log("获取公交路线", res);
 };

获取公交实时位置(2024/06/12更新)

 const getBusPositon = async () => {
   const res = await getBus({
     optype: "rtbus",
     uname: "2105856317@qq.com",
     cityid: "73",
     bus_linestrid: "MTEwMTAwMDU1NjYz",
     bus_staname: "101",
     keySecret: md5("2105856317@qq.com你的key值rtbus"),
   });
 };

image.png 这里着重看一下buses字段

		"buses": [{
			"lating": "39.9226288809075",
			"longing": "116.3773169199641",
			"distance": 368,
			"dis_stat": 17
		}, {
			"lating": "39.9224136605734",
			"longing": "116.39070257589405",
			"distance": 143,
			"dis_stat": 15
		}, {
			"lating": "39.924424558757735",
			"longing": "116.41777588814439",
			"distance": 391,
			"dis_stat": 12
		}, {
			"lating": "39.919724",
			"longing": "116.460417",
			"distance": 0,
			"dis_stat": 4
		}]

定时请求这个接口,当distance等于0时,车在第dis_stat站,当distance不等于0时应该为第dis_stat-1站到dis_stat站的途中,这里简化了,就当做distance不等于0时,车在第dis_stat-1站,这样我们就轻松获取了101路公交车实时到达的公交站点,接下来做个样式。

公交实时进度样式

代码文件为:BusProgress.vue image.png 模拟数据实现公交实时进度

const getBusPositon = () => {
  intervalId = setInterval(function () {
    ++pageData.time;
    console.log(pageData.time);
    const res = station(pageData.time);
    console.log(res);
    for (let i in res) {
      if (Number(res[i].distance) === 0) {
        pageData.busIndex[i] = res[i].dis_stat;
      } else {
        pageData.busIndex[i] = res[i].dis_stat - 1;
      }
    }
  }, 1000);
};

GIF 2024-6-12 星期三 16-34-33.gif 增加选中公交线地图公交线动画效果

    let lineData = {
      positions,
      style: {
        width: 4,
        materialType: mars3d.MaterialType.LineFlow,
        materialOptions: {
          color: Cesium.Color.CHARTREUSE,
          image: getAssetsFile("line-color-yellow.png"),
          speed: 15,
        },
      },
    };
    const BusLine = new mars3d.graphic.PolylinePrimitive(lineData);

GIF 2024-6-12 星期三 16-58-13.gif

🍲项目链接

项目链接