之前写过一篇文章叫做《Cesium渲染数据的几种方式》,这回来延伸一下在mars3d中渲染数据的方式。mars3d是一个免费开源的地球三维可视化的解决方案。同样可以在上面来渲染球面矢量数据。为什么要在介绍完Cesium之后再说mars3d呢?因为mars3d作为一个开源库,是基于Cesium把项目中所要用到的一些数据体现方式做了进一步封装。它的操作虽然更加简洁,但是跟Cesium还是有共通的地方,所以我们这次大体也会从之前的layers、entity、primitive、div几个方面来解析mars3d的用法。
如上图我们也可以把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图层的方式:
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)
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、闪烁等特效。
primitive
与entity的表现形式差不多,都有label、billboard等类型。不同的是要比entity支持更大的数据量。我们主要来讲解组合线的渲染:
上图为北京公交路线的流动渲染:
// 创建矢量数据图层
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的优势是可以最大限度的自定义样式:
// 创建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显示在球上:
// 创建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)
}
}