mars3d渲染数据的几种方式

2,559 阅读5分钟

之前写过一篇文章叫做《Cesium渲染数据的几种方式》,这回来延伸一下在mars3d中渲染数据的方式。mars3d是一个免费开源的地球三维可视化的解决方案。同样可以在上面来渲染球面矢量数据。为什么要在介绍完Cesium之后再说mars3d呢?因为mars3d作为一个开源库,是基于Cesium把项目中所要用到的一些数据体现方式做了进一步封装。它的操作虽然更加简洁,但是跟Cesium还是有共通的地方,所以我们这次大体也会从之前的layers、entity、primitive、div几个方面来解析mars3d的用法。

mars3d.jpg

如上图我们也可以把mars3d分成layers、GeoJson图层、Entity对象图层、Primitive对象图层、DIV数据图层这几个纬度来解析。每种渲染方式也都有自己细分的二级显示样式。

layers

添加图层的方式有3种:

1.在创建地球前的传参中配置layers参数。

  layers: [
    {
      name: "天地图注记",
      type: "tdt",
      layer: "img_z",
      show: true
    }
  ]

2.在创建地球后调用addLayer添加图层(直接new对应type类型的图层类)。

  const tileLayer = new mars3d.layer.XyzLayer({
    url: "//data.mars3d.cn/tile/dizhiChina/{z}/{x}/{y}.png",
    minimumLevel: 0,
    maximumLevel: 10,
    rectangle: { xmin: 69.706929, xmax: 136.560941, ymin: 15.831038, ymax: 52.558005 },
    opacity: 0.7
  })
  map.addLayer(tileLayer)

3.在创建地球后调用addLayer添加图层(用 mars3d.layer.create工厂方法创建)。

  const layerImg = mars3d.LayerUtil.create({
    type: "image",
    url: "//data.mars3d.cn//file/img/radar/201906211112.PNG",
    rectangle: { xmin: 73.16895, xmax: 134.86816, ymin: 12.2023, ymax: 54.11485 },
    alpha: 0.7
  })
  map.addLayer(layerImg)

GeoJson图层

当我们想要在球面上渲染geojson文件时,可以通过GeoJson图层的方式:

截屏2023-01-18 23.05.49.png

let graphicLayer = new mars3d.layer.GeoJsonLayer({
    id: 1987,
    name: "用地规划",
    url: "//data.mars3d.cn/file/geojson/guihua.json",
    symbol: {
      type: "polygonC",
      styleOptions: {
        opacity: 0.6,
      },
      styleField: "类型",
      styleFieldOptions: {
        一类居住用地: { color: "#FFDF7F" },
        二类居住用地: { color: "#FFFF00" },
      }
    }
  })
map.addLayer(graphicLayer)

注意可以通过styleField、styleFieldOptions来为每个图形设置自己的颜色。

Entity对象图层

在mars3d中,需要把entity加入到GraphicLayer图层实例中。此处用LabelEntity、BillboardEntity类型来举例。

LabelEntity:

  // 创建矢量数据图层
  let graphicLayer = new mars3d.layer.GraphicLayer()
  map.addLayer(graphicLayer)
  
  addDemoGraphic(graphicLayer)
  
  function addDemoGraphic(graphicLayer) {
  const graphic = new mars3d.graphic.LabelEntity({
    name: "根据视距缩放文字",
    position: new mars3d.LngLatPoint(116.340026, 30.873948, 383.31),
    style: {
      text: "中国安徽合肥",
      font_size: 20,
      color: "#00ff00",
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      scaleByDistance: new Cesium.NearFarScalar(10000, 1.0, 500000, 0.1)
    },
    attr: { remark: "示例3" }
  })
  graphicLayer.addGraphic(graphic)

截屏2023-01-18 23.28.53.png

BillboardEntity:

// 创建矢量数据图层
let graphicLayer = new mars3d.layer.GraphicLayer()
map.addLayer(graphicLayer)

addDemoGraphic1(graphicLayer)

function addDemoGraphic1(graphicLayer) {
  const graphic = new mars3d.graphic.BillboardEntity({
    position: [116.1, 31.0, 1000],
    style: {
      image: "img/marker/lace-blue.png",
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM
    },
    attr: { remark: "示例1" }
  })
  graphicLayer.addGraphic(graphic)

  // 演示个性化处理graphic
  initGraphicManager(graphic)
}

// 也可以在单个Graphic上做个性化管理及绑定操作
function initGraphicManager(graphic) {
  // 1.在graphic上绑定监听事件
  graphic.on(mars3d.EventType.click, function (event) {
     console.log("监听graphic,单击了矢量对象", event)
  })

  // 2.绑定Tooltip
  graphic.bindTooltip('我是graphic上绑定的Tooltip') //.openTooltip()

  // 3.绑定Popup
  const inthtml = `<table style="width: auto;">
            <tr>
              <th scope="col" colspan="2" style="text-align:center;font-size:15px;">我是graphic上绑定的Popup </th>
            </tr>
            <tr>
              <td>提示:</td>
              <td>这只是测试信息,可以任意html</td>
            </tr>
          </table>`
  graphic.bindPopup(inthtml).openPopup()

  // 4.绑定右键菜单
  graphic.bindContextMenu([
    {
      text: "删除对象[graphic绑定的]",
      icon: "fa fa-trash-o",
      callback: (e) => {
        const graphic = e.graphic
        if (graphic) {
          graphic.remove()
        }
      }
    }
  ])

  // 5.颜色闪烁
  if (graphic.startFlicker) {
    graphic.startFlicker({
      time: 20, // 闪烁时长(秒)
      maxAlpha: 0.5,
      color: Cesium.Color.YELLOW,
      onEnd: function () {
        // 结束后回调
      }
    })
  }
}

注意可以给entity添加绑定事件、tooltip、popup、闪烁等特效。

截屏2023-01-18 23.51.00.png

primitive

与entity的表现形式差不多,都有label、billboard等类型。不同的是要比entity支持更大的数据量。我们主要来讲解组合线的渲染:

截屏2023-01-19 00.35.30.png

上图为北京公交路线的流动渲染:

// 创建矢量数据图层
let graphicLayer = new mars3d.layer.GraphicLayer()
map.addLayer(graphicLayer)

mars3d.Util.fetchJson({ url: "//data.mars3d.cn/file/apidemo/bjgj.json" }).then(function (data) {
  const 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,
        color: new Cesium.Color(Math.random() * 0.5 + 0.5, Math.random() * 0.8 + 0.2, 0.0, 1.0),
        speed: 2 + 1.0 * Math.random()
      })
    })
    createLines(busLines)
})

function createLines(arr) {
  const arrData = []
  arr.forEach(function (item, index) {
    arrData.push({
      positions: item.positions,
      style: {
        width: 2.0,
        materialType: mars3d.MaterialType.ODLine,
        materialOptions: {
          color: item.color,
          speed: item.speed,
          startTime: Math.random()
        }
      },
      attr: { index: index }
    })
  })

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

geojson文件在程序中可以通过fetchJson的方式来加载,我们把每条线中的点收集到points数组中,再把这些points收集到线路数组busLines中,为线上的流动点定义颜色和速度。在createLines中,再为线来定义材质。通过PolylineCombine类型,合并渲染。

DIV数据图层

div的优势是可以最大限度的自定义样式:

截屏2023-01-19 00.59.06.png

// 创建DIV数据图层
let graphicLayer = new mars3d.layer.GraphicLayer()
map.addLayer(graphicLayer)

bindLayerPopup() // 在图层上绑定popup,对所有加到这个图层的矢量数据都生效
bindLayerContextMenu() // 在图层绑定右键菜单,对所有加到这个图层的矢量数据都生效

addDemoGraphic(graphicLayer)

// 一个渐变的文本面板,中间竖直连线
function addDemoGraphic(graphicLayer) {
  const graphic = new mars3d.graphic.DivGraphic({
    position: [116.510732, 31.403786, 176.4],
    style: {
      html: `<div class="marsBlueGradientPnl">
              <div>合肥火星科技有限公司</div>
          </div>`,
      offsetY: -60,
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
      distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 400000), // 按视距距离显示

      // 高亮时的样式
      highlight: {
        type: mars3d.EventType.click,
        className: "marsBlueGradientPnl-highlight"
      }
    },
    attr: { remark: "示例2" }
  })
  graphicLayer.addGraphic(graphic)
}

// 在图层绑定Popup弹窗
export function bindLayerPopup() {
  graphicLayer.bindPopup(function (event) {
    const attr = event.graphic.attr || {}
    attr["类型"] = event.graphic.type
    attr["来源"] = "我是layer上绑定的Popup"
    attr["备注"] = "我支持鼠标交互"

    return mars3d.Util.getTemplateHtml({ title: "矢量图层", template: "all", attr: attr })
  })
}

// 绑定右键菜单
export function bindLayerContextMenu() {
  graphicLayer.bindContextMenu([
    {
      text: "开始编辑对象",
      icon: "fa fa-edit",
      show: function (e) {
        const graphic = e.graphic
        if (!graphic || !graphic.hasEdit) {
          return false
        }
        return !graphic.isEditing
      },
      callback: (e) => {
        const graphic = e.graphic
        if (!graphic) {
          return false
        }
        if (graphic) {
          graphicLayer.startEditing(graphic)
        }
      }
    },
    {
      text: "停止编辑对象",
      icon: "fa fa-edit",
      show: function (e) {
        const graphic = e.graphic
        if (!graphic || !graphic.hasEdit) {
          return false
        }
        return graphic.isEditing
      },
      callback: (e) => {
        const graphic = e.graphic
        if (!graphic) {
          return false
        }
        if (graphic) {
          graphic.stopEditing()
        }
      }
    },
    {
      text: "删除对象",
      icon: "fa fa-trash-o",
      show: (event) => {
        const graphic = event.graphic
        if (!graphic || graphic.isDestroy) {
          return false
        } else {
          return true
        }
      },
      callback: (e) => {
        const graphic = e.graphic
        if (!graphic) {
          return
        }
        graphic.stopEditing()
        graphicLayer.removeGraphic(graphic)
      }
    }
  ])
}

可以注意一下统一给图层里的元素绑定popup、右键菜单的方法。

甚至可以把echarts显示在球上:

截屏2023-01-19 01.09.52.png

// 创建div图层
  let graphicLayer = new mars3d.layer.GraphicLayer()
  map.addLayer(graphicLayer)

  const arrData = [
    {
      name: "南京市",
      totalLength: 91025,
      deepUsedLength: 36909,
      deepUnUsedLength: 12551,
      unDeepUsedLength: 28251,
      unDeepUnUsedLength: 13313,
      lng: 118.735996333,
      lat: 32.089238239
    },
    {
      name: "扬州市",
      totalLength: 49649,
      deepUsedLength: 30245,
      deepUnUsedLength: 9140,
      unDeepUsedLength: 8164,
      unDeepUnUsedLength: 2101,
      lng: 119.399151815,
      lat: 32.271322643
    }
  ]
  
  showDivGraphic(arrData)
  
  function showDivGraphic(arr) {
  for (let i = 0; i < arr.length; i++) {
    const deepUnUsed = arr[i].deepUnUsedLength // 国道
    const deepUsed = arr[i].deepUsedLength // 县道
    const total = arr[i].totalLength // 中间显示
    const unDeepUnUsed = arr[i].unDeepUnUsedLength // 铁路
    const unDeepUsed = arr[i].unDeepUsedLength // 高速
    const cityName = arr[i].name // 城市名字
    const point = [arr[i].lng, arr[i].lat] // 位置

    // 白色背景
    const backGroundGraphic = new mars3d.graphic.DivGraphic({
      position: point,
      style: {
        html: '<div style="width:60px;height:60px;border-radius: 50%;background-color: #ffffff; position: relative;"></div>',
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.CENTER
      }
    })
    graphicLayer.addGraphic(backGroundGraphic)

    // div
    const graphic = new mars3d.graphic.DivGraphic({
      position: point,
      style: {
        html: '<div style="width: 100px;height:100px;"></div>',
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
        verticalOrigin: Cesium.VerticalOrigin.CENTER
      },
      pointerEvents: true
    })
    graphic.on(mars3d.EventType.add, function (e) {
      const dom = e.target.container.firstChild
      const attr = e.target.attr

      const chartChart = echarts.init(dom)

      const option = {
        tooltip: {
          trigger: "item",
          formatter: "{a} <br/>{b} : {c}km </br>占比 : {d}%",
          backgroundColor: "rgba(63, 72, 84, 0.7)",
          textStyle: {
            color: "#ffffff"
          }
        },
        title: {
          text: total + "\n Km",
          x: "center",
          y: "center",
          textStyle: {
            fontSize: 14
          }
        },
        color: ["#334b5c", "#6ab0b8", "#d48265", "#c23531"],
        series: [
          {
            name: cityName,
            type: "pie",
            radius: ["60%", "80%"],
            avoidLabelOverlap: false,
            label: {
              show: false,
              position: "center"
            },
            emphasis: {
              label: {
                show: false,
                fontSize: "40",
                fontWeight: "bold"
              }
            },
            labelLine: {
              show: false
            },
            data: [
              { value: deepUnUsed, name: "国道" },
              { value: deepUsed, name: "县道" },
              { value: unDeepUnUsed, name: "铁路" },
              { value: unDeepUsed, name: "高速" }
            ]
          }
        ]
      }

      chartChart.setOption(option)
    })
    graphicLayer.addGraphic(graphic)
  }
}