前言
2024.02.20
下午摸鱼时接到一个客户的数字孪生项目的需求,客户什么也没说,就要求"炫酷"和"科技感"地图,还要把他们的模型都放上去,起初我以为又是一个可视化大屏的项目,准备用高德地图应付过去,然后他们又在群里发什么要求高之类的,说什么高德、百度、Mapbox、Cesium之类框架都试过了,满足不了需求,好嘛,这下给我犯了难了,会的技术栈全都给我排除了, 手撸Three.js源码我可不干,于是就在网上晃悠,尝试找一些灵感
2024.02.24
又试了几个地图框架,还是不行,被客户和经理催了一顿,再不行他们要换人了
2024.02.28
在QQ群里闲逛,发现了群友发的一个叫 Mapmost SDK for WebGL 的地图框架,于是抱着试一试的态度做了一下,好嘛,一下就对客户味了
2024.03.04
后面我发现这个SDK蛮有意思,于是把我实现客户需求的过程记录下来,分享给大家
初始化
这个SDK看似是个商用软件,不过目前是免费试用,官网上申请一下就行了,然后按照他们的文档,填一下参数,就能初始化一个地图,和一般的地图SDK用法差不多
js
<script src ='https://delivery.mapmost.com/cdn/sdk/webgl/v3.5.0/mapmost-webgl-min.js'></script>
<script>
let mapmost = window.mapmost
/*
* 初始化地图
*/
let map = new mapmost.Map({
container: 'map', //地图容器
style:'http://192.168.126.44/mms-style/darkMap.json', //矢量底图
center: [120.74014004382997, 31.32975410974069], //地图中心点
bearing: 50.399999999999636, //方位
pitch: 78.99999999999993, //倾斜角
zoom: 19.964625761228117, //缩放
userId: '***',
})
</script>
不过,在此之前,要把底图中的矢量建筑图层隐藏掉,客户要加载真正的建筑三维模型。
代码和效果图如下:
js
const buildingLayers = [
'buildings-di',
'buildings-faguang-1',
'buildings-faguang-2',
'buildings-faguang-3',
'buildings-high',
'buildings-high-top',
'buildings-low',
'buildings-low-top',
'buildings-lowmid',
'buildings-lowmid-top',
'buildings-mid',
'buildings-mid-top',
'buildings-midhigh',
'buildings-midhigh-copy',
]
map.on('load', (e) => {
buildingLayers.forEach((layer, index) => {
let layerObj = map.getLayer(layer)
map.setLayoutProperty(layerObj.id, 'visibility', 'none');
})
})
加载建筑三维模型
这里我们准备了城市建筑的模型,格式为glb
,按照文档描述,我们添加一个id
为modelLayer
的三维图层,代码和效果图如下
js
//...
/*
* 加城市f载建筑模型
*/
let Group = null
let Layer = null
let models = ["./model/YQ.glb"].map(item => ({
type: 'glb',
url: item
}));
map.on('load',(e) => {
let modelOptions = {
id: 'modelLayer',
type: 'model',
models: models,
sky: "./sky/satara_night_no_lamps_2k.hdr",
exposure: 2.4,
center: [120.74155610348487, 31.328251735532746, 0],
callback: (group, layer) => {
Group = group
Layer = layer
}
};
map.addLayer(modelOptions);
})
添加三维效果
接下来就是客户的G点了,为三维场景添加特效:
添加建筑流光渐变效果
参考SDK的接口文档,给建筑加上流光渐变的效果
定义一个添加特效的函数addModelEffect
,然后按照文档上的参数说明来配置相关属性
js
const addModelEffect = () =>{
Layer.addModelEffect(Group, [{
type: "gradient",
startColor: "rgba(63, 177, 245,.5)",
endColor: "rgba(58, 142, 255,.8)",
opacity: 0.8,
percent: 0.5
}, {
type: "flow",
speed: 1,
color: "rgba(241, 99, 114, .4)",
opacity: 0.8,
percent: 0.05
}])
Group.addFrame(0x3FB1F5);
}
然后我们在模型加载完成后调用这个函数:
js
//...
map.on('load',(e) => {
let modelOptions = {
id: 'modelLayer',
type: 'model',
models: models,
sky: "./sky/satara_night_no_lamps_2k.hdr",
exposure: 2.4,
center: [120.74155610348487, 31.328251735532746, 0],
callback: (group, layer) => {
Group = group
Layer = layer
addModelEffect()
}
};
map.addLayer(modelOptions);
})
效果如下:
添加粒子飞线
同样,在SDK的文档上找到添加流动线的接口,定义一个addFlowLine
的函数,然后按照要求配置参数:
这里我们借助了一个生成贝塞尔曲线的函数,以及一些精通点数据。 他们的作用是为我们提供必要的模拟数据
js
import { getBSRPoints } from './bezierFunction.js'
import { flowLineData } from './flowLineData.js'
//...
const addFlowLine = () => {
//生成贝塞尔曲线测试数据
let data_trail1 = getBSRPoints(120.71541557869517, 31.316503949907542,
120.73787560336916, 31.321925190347713, 800);
let data_trail2 = getBSRPoints(120.71541557869517, 31.316503949907542,
120.72619950480242, 31.33360076088249, 1500);
let data_trail3 = getBSRPoints(120.71541557869517, 31.316503949907542,
120.69933418653403, 31.332725809914024, 900);
[data_trail1, data_trail2, data_trail3].map(data => {
Layer.addFlowLine({
type: "trail",
color: '#1ffff8',
speed: 4,
opacity: 0.9,
width: 8,
data: {
coordinate: data
}
});
})
flowLineData.map(data => {
Layer.addFlowLine({
type: "flow",
color: '#ff680d',
speed: 4,
opacity: 1,
percent: 0.08,
gradient: 0.02,
width: 5,
data: {
coordinate: data
}
});
})
}
同样,我们在模型加载完成后调用这个函数:
js
//...
map.on('load',(e) => {
let modelOptions = {
id: 'modelLayer',
type: 'model',
models: models,
sky: "./sky/satara_night_no_lamps_2k.hdr",
exposure: 2.4,
center: [120.74155610348487, 31.328251735532746, 0],
callback: (group, layer) => {
Group = group
Layer = layer
addModelEffect()
addFlowLine()
}
};
map.addLayer(modelOptions);
})
效果图如下:
添加特效球
类似的,文档上的添加特效球的接口,给场景里添加两个"能量半球"
代码和效果图如下:
js
const addSphere = () => {
let sphere = Layer.addSphere({
color: "rgb(53, 108, 222)",
radius: 3300, //半径,单位米,默认为10米
segment: 256, //构成圆的面片数
phiLength: Math.PI,
speed: 3,
opacity: 1,
center: [120.67727020663829, 31.31997024841401, 0.0]
});
let sphere2 = Layer.addSphere({
color: "rgb(219, 74, 51)",
radius: 2300, //半径,单位米,默认为10米
segment: 256, //构成圆的面片数
phiLength: Math.PI,
speed: 6,
opacity: 1,
center: [120.67727020663829, 31.31997024841401, 0.0]
});
}
//...
map.on('load',(e) => {
let modelOptions = {
id: 'modelLayer',
type: 'model',
models: models,
sky: "./sky/satara_night_no_lamps_2k.hdr",
exposure: 2.4,
center: [120.74155610348487, 31.328251735532746, 0],
callback: (group, layer) => {
Group = group
Layer = layer
addModelEffect()
addFlowLine()
addSphere()
}
};
map.addLayer(modelOptions);
})
环境效果调优
仔细看整个环境,发现天是白的,和整体环境不搭配
更改一下地图初始化时的参数,将天空设置为暗色:
js
let map = new mapmost.Map({
container: 'map', //地图容器
style: 'http://192.168.126.44/mms-style/darkMap.json', //矢量底图
center: [120.74014004382997, 31.32975410974069], //地图中心点
bearing: 60.399999999999636, //方位
pitch: 78.99999999999993, //倾斜角
zoom: 14.964625761228117, //缩放
sky: 'dark' //天空颜色
})
然后整体效果如下:
如果觉得场景本身太亮,可以降低添加模型时的曝光度:
js
let modelOptions = {
exposure: .4,
callback: (group, layer) => {
//...
}
};
这样整体环境就会偏暗一点,更有黑夜下的赛博朋克城市的味道
当然,在这我又换了一张更暗的底图:
最后,再调整一下特效球的半径和位置,行了,这就是客户喜欢的样子,哈哈哈,2小时搞定,而且不用手撸Three.js代码:
js
const addSphere = () => {
let sphere = Layer.addSphere({
color: "rgb(53, 108, 222)",
radius: 3300, //半径,单位米,默认为10米
segment: 256, //构成圆的面片数
phiLength: 180,
speed: 3,
opacity: 1,
center: [120.67943361712065, 31.306450929918768]
});
let sphere2 = Layer.addSphere({
color: "rgb(219, 74, 51)",
radius: 2300, //半径,单位米,默认为10米
segment: 256, //构成圆的面片数
phiLength: 180,
speed: 6,
opacity: 1,
center: [120.67727020663829, 31.31997024841401, 0.0]
});
}
总结
仔细看,不难发现,这个SDK集成了 Mapbox 和 Three.js 的核心功能,主打的是一个高颜值的三维地图引擎,当然除了好看之外,其他地图框架该有的功能它也具备,只是官网给人一种十几年前的感觉,有点山寨...