❝
在打造数字孪生项目时,我们经常会遇到客户提出的一个特别需求 —— 制作科技感十足的行政区划地图效果。
以往,这种视觉效果的实现可能需要美术人员介入。 但是,您知道吗?其实我们前端开发人员也能轻松应对,而且我们的方法更灵活、更易复用。
本文将带领一起探索如何使用前端,打造一款酷炫的科技风三维地图。
功能解析
咱一起来探索一下如何实现科技风三维地图这个功能吧~~
数据准备层面:
-
需要准备市级界线、区县级界线、区县界名称geojson
-
数据下载:使用bigMap、水经维图等软件下载 -> 下载市级界线、区县级界线shp数据;
-
数据转换:使用qgis、ArcMap等gis软件转换 ->
- 将数据转换为投影坐标系【数据转换参考推文: 包学会!轻松掌握坐标系转换技巧!】;
- 将转换的shp格式数据导出为geojson格式数据;
- 使用qgis等gis软件提取区县级行政边界的中心点坐标以及对应的名称,并将其数据进行导出;
-
前端实现层面:
前端实现科技风地图这里我拆解成下面几个步骤进行实现:
- 样式调整。
❝
专题资源链接(包含数据、代码)下载: ttps://pan.baidu.com/s/1V9mX2rrByeQ4M7ZAJIaDfA?pwd=rckt
该代码为html。需要将lib/aircity的SDK替换成本地Cloud SDK,同时,由于需要读取 geojson 文件,需启动相应的 Web 服务以确保文件访问。
- 替换SDK步骤:Cloud SDK获取方式:将SDK文件夹的ac.min.js进行复制;
- 将代码/行政区划/lib/aircity的ac.min.js替换成SDK的ac.min.js。
- 在编辑器中,通过启动 Web 服务进行访问html,有两种方式:一种是使用右键的 “Open with Live Server” 选项,另一种是点击编辑器底部的 “Go Live” 按钮。
实现步骤
总体分析
打造一个炫酷且充满科技感的地图效果,需要挑选对应色彩主题。在蓝色、绿色、紫色这三种科技风格的代表色中,以蓝色为例作为地图的主色调,同时以浅蓝色和银色作为辅助色彩,打造一个充满科技感和现代感的视觉体验。
这里我从样式调整、行政区划、立体、科技风效果一一进行讲解让大家查看到对应的变化。
(ps: 每小节讲解功能部分的核心代码,非最终整合代码;最终完整代码可参考百度网盘链接)
样式调整
为什么会有样式调整的这一个步骤呢?
在初始状态下,大气层散射的存在会对行政区划颜色效果造成色差,因此建议开启黑暗模式,关闭大气层与云层以减少视觉干扰;由于,图层亮度比较高,在后续的展示可能过于突出,故需对图层亮度进行调整,以便更好地突出行政区划面地展示效果。
我们需要做的操作:
-
使用
fdapi.weather.setDarkMode(true),开启黑暗模式; -
将图层的亮度调低。
- 遍历图层树,后续我们通过图层名进行拾取到对应的图层id,使用此方法可读性较高,后续也易于维护;
- 使用
fdapi.tileLayer.setStyle()对图层进行设置亮度。
// 图层树对象
const infoTreeObj = {}
const setStyles = async () => {
//样式调整:a.打开黑暗模式 b.调整地形影像的亮度
// 开启黑暗模式
await fdapi.weather.setDarkMode(true)
// 获取图层树
const { infotree } = await fdapi.infoTree.get()
// 遍历图层树,根据图层名获取图层id
infotree.forEach(item => {
infoTreeObj[item.name] = item.iD
})
// 图层亮度调整
await fdapi.tileLayer.setStyle(
infoTreeObj['大地形影像'],
null,
null,
null,
0.4
)
}
行政区划
在一般情况下,绘制市行政区划图时,常采用区县数据进行绘制面,并用线来区分不同区域,使用点来标识区域所属。接下来,我们将共同探讨如何实现标准的行政区划效果。
我们需要做的操作:
- 需要提前获取准备的数据,用于后续线与面的添加。在html中需要以fetch获取数据,在Vue框架中可以使用axios获取数据。
- 此数据在项目初始化的时候进行获取。
function initScene() {
fetch(getCountyDataPath)
.then(res => res.json())
.then(jsonData => {
// 处理JSON数据
getCountyData = JSON.parse(JSON.stringify(jsonData))
})
.catch(err => {
console.error('Error:', err)
})
new DigitalTwinPlayer(HostConfig.Player, {
domId: 'player',
apiOptions: {
onReady: async () => {
fdapi.reset(1 | 2 | 4)
}
}
})
2. 使用Polygon将读取区县数据进行绘制面。面的样式的选择可以参考推文:前端三维可视化线面样式选择大揭秘!!!
行政区划面
- 全局需要创建一个图层对象,便于后续对图层进行统一管理;
// 图层ID对象
const LayerIdObj = {
polyline: [],
polygon3d: [],
polygon: []
}
- 以科技风为主题,以蓝色为主色调,这里面的样式
style采用的是0单色样式
// 创建数组存放polygon id
const polygonList = []
// 遍历县级数据
getCountyData.features.forEach((item, index) => {
const coordinates = item.geometry.coordinates[0][0]
const city_polygon_id = 'city_polygon_' + index
LayerIdObj.polygon.push(city_polygon_id)
const city_polygon = {
id: city_polygon_id,
coordinates: coordinates.map(coord => [coord[0], coord[1]]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
color: [5 / 255, 50 / 255, 74 / 255, 0.8], //多边形的填充颜色
intensity: 20, //亮度
style: 0, //单色 请参照API开发文档选取枚举
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
priority: 1 //显示优先级 值越大显示越靠上
}
polygonList.push(city_polygon)
})
// 进行添加polygon
await fdapi.polygon.add(polygonList)
3. 使用Polyline线来区分每个区县区域,同样,线的样式选择参考推文:前端三维可视化线面样式选择大揭秘!!!
行政区划线面
- 遍历区县数据,每个区域线唯一id值,将其推送
push至数组中,以存储Polyline的ID; - 在科技风格的基础上,线颜色以银色作为辅助色彩,为后续营造一个充满科技感的视觉效果;
- 适当降低Polyline的流速
flowRate,以优化显示效果; - 采用的线条样式
style为静态的样式4-正常线条; - 使用
fdapi.polyline.add()一次性添加所有Polyline,以提高效率和统一性。
// 创建数组存放polyline id
const polylineList = []
getCountyData.features.forEach((item, index) => {
// 获取坐标
const coordinates = item.geometry.coordinates[0][0]
const county_border_id = 'county_border_' + index
LayerIdObj.polyline.push(county_border_id)
const county_border = {
id: county_border_id, //折线唯一标识id
coordinates: coordinates.map(coord => [coord[0], coord[1]]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
color: [1, 1, 1, 1], //折线颜色
thickness: 150, //折线宽度
intensity: 10, //亮度
flowRate: 0.1, //流速
shape: 0, //折线类型 0:直线, 1:曲线
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
style: 4, //折线样式 参考样式枚举:PolylineStyle
tiling: 0.5 //材质贴图平铺比例
}
polylineList.push(county_border)
})
await fdapi.polyline.add(polylineList)
4. 凸显整个行政区划效果,给行政区划外轮廓添加线。
- 外轮廓Polyline 的线宽
thickness应大于内轮廓 Polyline(区县Polyline),突出外轮廓。
// 创建边框线
const border_coordinates =
getCityData.features[0].geometry.coordinates[0][0]
const city_border = {
id: 'city_border', //折线唯一标识id
coordinates: border_coordinates.map(coord => [coord[0], coord[1]]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
color: [1, 1, 1, 1], //折线颜色
thickness: 300, //折线宽度
intensity: 10, //亮度
flowRate: 0.1, //流速
shape: 0, //折线类型 0:直线, 1:曲线
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
style: 4, //折线样式 参考样式枚举:PolylineStyle
tiling: 0.5 //材质贴图平铺比例
}
await fdapi.polyline.add(city_border)
- 通过添加光流样式(style:3)的 Polyline 进一步强化外轮廓效果。
const city_border_dynamic = {
id: 'city_border_dynamic', //折线唯一标识id
coordinates: border_coordinates.map(coord => [coord[0], coord[1]]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
color: [1, 1, 1, 1], //折线颜色
thickness: 600, //折线宽度
intensity: 10, //亮度
flowRate: 0.3, //流速
shape: 0, //折线类型 0:直线, 1:曲线
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
style: 3, //折线样式 参考样式枚举:PolylineStyle
tiling: 2 //材质贴图平铺比例
}
await fdapi.polyline.add(city_border_dynamic)
5. 添加完线面,那我们就需要加上我们的区县名称来标识我们区县区域所属。点的样式添加可参考推文:震惊!三维可视化前端竟能让二维标签玩出 7 种效果欸!
- 需要获取区域名称geojson数据,同样是在初始化时进行获取。
// 获取行政县界名称
const CountyLabelPath = './lib/data/4549苏州县界_名称标注.geojson'
let CountyLabelData = '' // 行政区县界名称数据存储
function initScene() {
fetch(CountyLabelPath)
.then(res => res.json())
.then(jsonData => {
// 处理JSON数据
getCountyLabelData = JSON.parse(JSON.stringify(jsonData))
})
.catch(err => {
console.error('Error:', err)
})
}
- 遍历区域名称数据,每个区域名称需要唯一id值,将其推送
push至数组中,以存储Marker - 名称通过遍历数据进行获取,并将其填入到属性
text中; - 这里字的展示,用的普通文字点位;
- 文字可视距离的远裁距离设置大些,拉远可以查看到点标注。
// 创建数组存放marker id
const markerList = []
// 遍历点数据
getCountyLabelData.features.forEach((item, index) => {
// 获取坐标
const coordinates = item.geometry.coordinates
// 获取名称
const name = item.properties.Name
let label = {
id: 'county_label_' + index,
groupId: 'county_label',
coordinate: [...coordinates], //坐标位置
coordinateType: 0, //默认0是投影坐标系,也可以设置为经纬度空间坐标系值为1
range: [1, 100000000], //可视范围
viewHeightRange: [1, 100000000], // 可见高度范围
text: name, //显示的文字
useTextAnimation: true, //关闭文字展开动画效果 打开会影响效率
textRange: [1, 100000000], //文本可视范围[近裁距离, 远裁距离]
textBackgroundColor: [0, 0, 0, 0], //文本背景颜色
fontSize: 16, //字体大小
fontOutlineSize: 2, //字体轮廓线大小
fontColor: Color.White, //字体颜色
fontOutlineColor: Color.Black, //字体轮廓线颜色
displayMode: 2 //智能显示模式 开发过程中请根据业务需求判断使用四种显示模式
}
markerList.push(label)
})
// 添加行政名称
await fdapi.marker.add(markerList)
立体效果
实现完基础行政区划,接下来我们需要进行升级啦~~~不再是只有美术可以实现立体效果,咱前端同样也可以实现了~
这一部分我们讲解一下如何实现行政区划立体效果。
当前的行政区划紧贴地形呈现平面效果,我们要让它立体呈现出来。 需要做的操作:
- 将行政区划进行抬升,给与高度
height;
- 设置一个全局变量
height,给之前创建的点线面的coordinates的z值进行赋值height -> 参考:coordinates: coordinates.map(coord => [coord[0], coord[1], height])。
// 高度
const height = 10000
2. 给行政区划添加侧面,呈现立体效果。
- 使用Polygon3d进行添加侧面,需关闭Polygon3d的顶面和侧面显示。
- Polygon3d可采用样式2:渐变静态进行呈现。
- 渐变侧面颜色和行政区划线颜色一致。
- 要实现Polygon3d侧面朝下的效果,需将height设置为负值。
// 创建边框数组存放polygon3d id
const WallList = []
getCountyData.features.forEach((item, index) => {
const coordinates = item.geometry.coordinates[0][0]
const county_wall_id = 'county_wall_' + index
LayerIdObj.polygon3d.push(county_wall_id)
let county_wall = {
id: county_wall_id,
coordinates: coordinates.map(coord => [coord[0], coord[1], height]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
color: '#ffffff', //颜色值
height: -height * 0.5, //3D多边形的高度
intensity: 1.0, //亮度
style: 2, //3DPolygon的样式 请参照API开发文档选取枚举
tillingX: 0, //可选,仅当3DPolygon的样式支持贴图显示,贴图横向平铺
tillingY: 0, //可选,仅当3DPolygon的样式支持贴图显示,贴图纵向平铺
generateTop: false, //是否生成顶面
generateSide: true, //是否生成侧面
generateBottom: false //是否生成底面
}
WallList.push(county_wall)
})
fdapi.polygon3d.add(WallList)
科技风
成功实现立体效果后,接下来的步骤是采用蓝色作为主色调,对整个行政区划进行点缀,以此塑造科技风格的视觉效果。
- 为突出行政区划的科技风效果,采用Polygon3d,添加蓝色色调的行政区划侧面,以增强视觉效果的立体感和科技感。
- 关闭 Polygon3d 的顶面和底面显示功能。
- 采用Polygon3d动态渐变样式,突出科技感效果。
- Polygon3d 的高度值需设为负值,以实现侧面朝下的效果。
const coordinates = getCityData.features[0].geometry.coordinates[0][0]
let city_wall = {
id: 'city_wall',
coordinates: coordinates.map(coord => [coord[0], coord[1], height]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
color: '#215cca', //颜色值
height: -height * 1.5, //3D多边形的高度
intensity: 1.0, //亮度
style: 4, //3DPolygon的样式 请参照API开发文档选取枚举
tillingX: 0, //可选,仅当3DPolygon的样式支持贴图显示,贴图横向平铺
tillingY: 0, //可选,仅当3DPolygon的样式支持贴图显示,贴图纵向平铺
generateTop: false, //是否生成顶面
generateSide: true, //是否生成侧面
generateBottom: false //是否生成底面
}
fdapi.polygon3d.add(city_wall)
2. 通过自定义样式的 Polygon 添加顶面,以提升科技风格效果。自定义Polygon样式设置参考推文Polygon3d自定义样式模块:前端三维可视化线面样式选择大揭秘!!!
- 需要使用到区县geojson数据。
- 采用平面混合_2自定义样式。
- 根据实际情况调整自定义样式Polygon的参数,特别是UV相关参数,以调整纹理密度和速度,达到更真实的效果。
- 自定义材质颜色设置为蓝色色调。
let polygonList = []
getCountyData.features.forEach((item, index) => {
// 获取坐标
const coordinates = item.geometry.coordinates[0][0]
const city_polygon_dynamic_id = 'city_polygon_dynamic' + index
LayerIdObj.polygon.push(city_polygon_dynamic_id)
const city_polygon_dynamic = {
id: city_polygon_dynamic_id,
coordinates: coordinates.map(coord => [coord[0], coord[1], height]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
intensity: 1, //亮度
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
priority: 1, //显示优先级 值越大显示越靠上
material: '/JC_CustomAssets/PolygonLibrary/Exhibition/平面混合_2', //自定义材质路径 设置后style相关参数会失效
scalarParameters: [
{ name: '亮条U速度', value: 0.1 },
{ name: '亮条V速度', value: 0.1 },
{ name: '亮条UV', value: 0.25 },
{ name: '亮条亮度', value: 10 },
{ name: '亮条旋转', value: 0.33 },
{ name: '亮度', value: 2 },
{ name: 'UV缩放', value: 0.001 },
{ name: '不透明度', value: 0.01 }
], //材质数值类型参数
vectorParameters: [
{ name: '颜色', value: [19 / 255, 204 / 255, 255 / 255] }
] //材质数组类型参数
}
polygonList.push(city_polygon_dynamic)
})
fdapi.polygon.add(polygonList)
3. 添加自定义面之后,由于最开始有个行政区划面并且高度一致,就会出现闪面。需要将底下面的高度-1,解决闪面现象。
const polygonList = []
// 遍历县级数据
getCountyData.features.forEach((item, index) => {
const coordinates = item.geometry.coordinates[0][0]
const city_polygon_id = 'city_polygon_' + index
LayerIdObj.polygon.push(city_polygon_id)
const city_polygon = {
id: city_polygon_id,
coordinates: coordinates.map(coord => [coord[0], coord[1],height-1]),
coordinateType: 0, //坐标系类型,取值范围:0为Projection类型,1为WGS84类型,2为火星坐标系(GCJ02),3为百度坐标系(BD09),默认值:0
range: [1, 10000000], //可视范围:[近裁距离, 远裁距离],取值范围: [任意负值, 任意正值]
color: [5 / 255, 50 / 255, 74 / 255, 0.8], //多边形的填充颜色
intensity: 20, //亮度
style: 0, //单色 请参照API开发文档选取枚举
depthTest: false, //是否做深度检测 开启后会被地形高度遮挡
priority: 1 //显示优先级 值越大显示越靠上
}
polygonList.push(city_polygon)
})
// 进行添加polygon
await fdapi.polygon.add(polygonList)
总结
本教程带着大家从零起步,逐步掌握前端实现科技风地图效果。通过学习相应的流程和操作方法,会发现前端实现科技风地图并非难事。希望本文能给大家带来帮助。鼓励大家动手实践起来~~也欢迎大家一起探讨实现效果的创新以及突破!!!
❝
专题资源链接(包含数据、代码)下载: ttps://pan.baidu.com/s/1V9mX2rrByeQ4M7ZAJIaDfA?pwd=rckt
这里准备了两份html,内容是一致的。一个是按照推文流程去实现的,大家可以根据流程去了解每一个步骤;另一个将相同的数据请求集中处理,便于未来的维护和优化工作。