目录
最终效果
实现思路(三个图层)
第一次自己做的时候是按照这个思路来的,如果你也是新手,可以参考一下下面思路
1、绘制台风路线并添加动画
备注:gif中白色实线是已经发生过的台风路线,虚线是预测的台风路线
动画思路:使用定时器,定时器每次执行时,每次添加新的坐标
2、绘制台风点位并添加动画
备注:不同颜色的点位代表不同台风登记(台风路线和台风点位是两个图层)
动画思路:使用定时器,定时器每次执行时,每次添加新的坐标
3、绘制台风眼图标
备注:单独一个图层
4、台风眼图标旋转
代码实现
以下是台风数据
// 台风等级颜色
let tfColor = new Map()
tfColor.set('TD', '#30fc31')
tfColor.set('TS', '#307efa')
tfColor.set('STS', '#fffc00')
tfColor.set('TY', '#ff9c00')
tfColor.set('STY', '#fb7cff')
tfColor.set('SuperTY', '#fa3030')
// 构造实况数据(台风编号,当前时间,经度,纬度,气压)
let liveData = [
{typhoonID: '2305', datetime: '2023-07-24 02:00:00', longitude: 127.1, latitude: 15.2, pressure: 965, grade: 'TY'},
{typhoonID: '2305', datetime: '2023-07-24 05:00:00', longitude: 127, latitude: 15.4, pressure: 960, grade: 'TY'},
{typhoonID: '2305', datetime: '2023-07-25 08:00:00', longitude: 124.6, latitude: 17.6, pressure: 915, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-25 11:00:00', longitude: 124, latitude: 18, pressure: 915, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-25 14:00:00', longitude: 123.8, latitude: 18.2, pressure: 915, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-25 17:00:00', longitude: 123.3, latitude: 18.6, pressure: 915, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-25 20:00:00', longitude: 122.8, latitude: 18.8, pressure: 920, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-25 23:00:00', longitude: 122.2, latitude: 18.9, pressure: 925, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 02:00:00', longitude: 121.6, latitude: 19, pressure: 925, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 05:00:00', longitude: 121.3, latitude: 18.8, pressure: 925, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 11:00:00', longitude: 121.1, latitude: 19, pressure: 930, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 14:00:00', longitude: 121, latitude: 19.3, pressure: 930, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 17:00:00', longitude: 120.7, latitude: 19.4, pressure: 935, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 20:00:00', longitude: 120.6, latitude: 19.6, pressure: 935, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-26 23:00:00', longitude: 120.4, latitude: 19.9, pressure: 935, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-27 02:00:00', longitude: 120, latitude: 20, pressure: 935, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-27 05:00:00', longitude: 120, latitude: 20.1, pressure: 940, grade: 'STY'},
{typhoonID: '2305', datetime: '2023-07-27 08:00:00', longitude: 119.9, latitude: 20.5, pressure: 940, grade: 'STY'},
{typhoonID: '2305', datetime: '2023-07-27 11:00:00', longitude: 119.5, latitude: 20.9, pressure: 940, grade: 'STY'},
{typhoonID: '2305', datetime: '2023-07-27 14:00:00', longitude: 119.3, latitude: 21.1, pressure: 940, grade: 'STY'},
{typhoonID: '2305', datetime: '2023-07-27 17:00:00', longitude: 119.2, latitude: 21.4, pressure: 935, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-27 20:00:00', longitude: 119.2, latitude: 21.7, pressure: 930, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-27 23:00:00', longitude: 119.1, latitude: 22.2, pressure: 925, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-28 02:00:00', longitude: 119, latitude: 22.8, pressure: 925, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-28 05:00:00', longitude: 118.9, latitude: 23.4, pressure: 930, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-28 07:00:00', longitude: 118.9, latitude: 23.9, pressure: 930, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-28 10:00:00', longitude: 118.6, latitude: 24.7, pressure: 945, grade: 'STY'}
]
// 预测数据
let forecastData = [
{typhoonID: '2305', datetime: '2023-07-28 14:00:00', longitude: 118.3, latitude: 25.2, pressure: 950, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-28 20:00:00', longitude: 118, latitude: 26.5, pressure: 980, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-29 02:00:00', longitude: 117.5, latitude: 28.2, pressure: 992, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-29 08:00:00', longitude: 116.7, latitude: 30.7, pressure: 998, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-29 20:00:00', longitude: 116, latitude: 32.6, pressure: 998, grade: 'SuperTY'},
{typhoonID: '2305', datetime: '2023-07-30 08:00:00', longitude: 115.8, latitude: 34.3, pressure: 998, grade: 'SuperTY'}
]
// 先将以上数据处理为目标数据格式后全部放入lineFeatures和pointFeatures。做动画时,
// 在定时器中,将lineFeatures和pointFeatures中的数据 每次 分别加入animationLineFeature和animationPointFeature
// 线路feature(包含实况和预测)
let lineFeatures = []
// 台风点feature(包含实况和预测)
let pointFeatures = []
// 动画-台风点feature(包含实况和预测)
let animationPointFeature = []
// 动画-台风路线feature(包含实况和预测)
let animationLineFeature = []
绘制台风路线并添加动画
目标lineFeature数据格式
// 渲染台风路线
function renderLine() {
handleLineData()
// 路径geojson
let geojsonLine = {
type: 'FeatureCollection',
features: animationLineFeature
}
map.addSource('tf_live_path', {
type: 'geojson',
data: geojsonLine
})
animationLine(geojsonLine)
map.addLayer({
id: 'tf_live_path',
type: 'line',
source: 'tf_live_path',
paint: {
'line-color': '#ccc', // 线颜色
'line-width': 3, // 线宽
// 根据properties中type字段 forecastve设置为实线 其他设置为虚线
'line-dasharray': ['match', ['get', 'type'], 'forecastve', ['literal', [1, 1]], ['literal', [1, 0]]]
}
})
}
// 处理台风路线数据
function handleLineData() {
for (let i = 0; i < liveData.length; i++) {
let firstCoordinates
let secondCoordinates
let type
if (i === liveData.length - 1) {
firstCoordinates = [liveData[liveData.length - 1].longitude, liveData[liveData.length - 1].latitude]
secondCoordinates = [forecastData[0].longitude, forecastData[0].latitude]
type = 'forecastve'
} else {
firstCoordinates = [liveData[i].longitude, liveData[i].latitude]
secondCoordinates = [liveData[i + 1].longitude, liveData[i + 1].latitude]
type = 'live'
}
let feature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [firstCoordinates, secondCoordinates]
},
properties: {
type: type
}
}
lineFeatures.push(feature)
}
for (let i = 0; i < forecastData.length - 1; i++) {
let feature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [
[forecastData[i].longitude, forecastData[i].latitude],
[forecastData[i + 1].longitude, forecastData[i + 1].latitude]
]
},
properties: {
type: 'forecastve'
}
}
lineFeatures.push(feature)
}
// console.log('lineFeatures:', lineFeatures)
}
// 台风路线动画
function animationLine(geojsonLine) {
let lineIndex = 0
let lineTimer = setInterval(() => {
if (lineIndex === lineFeatures.length - 1) clearInterval(lineTimer)
animationLineFeature.push(lineFeatures[lineIndex])
if (lineIndex === liveData.length) {
// renderIcon()
// 渲染台风眼icon并旋转
rotateIcon()
}
lineIndex++
map.getSource('tf_live_path').setData(geojsonLine)
}, 200)
}
绘制台风点位并添加动画
pointFeatures目标数据结构
// 渲染台风点
function renderPoint() {
handlePointData()
let geojsonPoint = {
type: 'FeatureCollection',
features: animationPointFeature
}
map.addSource('tf_live_point', {
type: 'geojson',
data: geojsonPoint
})
animationPoint(geojsonPoint)
map.addLayer({
id: 'tf_live_point',
type: 'circle',
source: 'tf_live_point',
paint: {
'circle-color': [
'case',
['==', ['get', 'grade'], 'TD'],
tfColor.get('TD'),
['==', ['get', 'grade'], 'TS'],
'#fb7cff',
['==', ['get', 'grade'], 'STS'],
'#fb7cff',
['==', ['get', 'grade'], 'TY'],
tfColor.get('TY'),
['==', ['get', 'grade'], 'STY'],
tfColor.get('STY'),
['==', ['get', 'grade'], 'SuperTY'],
tfColor.get('SuperTY'),
'#FF0000'
]
}
})
}
// 处理台风点数据
function handlePointData() {
// 将实况点位加入pointFeatures
for (let i = 0; i < liveData.length; i++) {
let feature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [liveData[i].longitude, liveData[i].latitude]
},
properties: {
type: 'live',
grade: liveData[i].grade
}
}
pointFeatures.push(feature)
}
// 将预测点位加入pointFeatures
for (let i = 0; i < forecastData.length; i++) {
let feature = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [forecastData[i].longitude, forecastData[i].latitude]
},
properties: {
type: 'forecastve',
grade: forecastData[i].grade
}
}
pointFeatures.push(feature)
}
console.log('pointFeatures:', pointFeatures)
// 动画先加载第一个点
animationPointFeature.push({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [liveData[0].longitude, liveData[0].latitude]
},
properties: {
type: 'live',
grade: liveData[0].grade
}
})
}
// 台风点动画
function animationPoint(geojsonPoint) {
let i = 1
let timer = setInterval(() => {
if (i === pointFeatures.length - 1) clearInterval(timer)
animationPointFeature.push(pointFeatures[i])
i++
map.getSource('tf_live_point').setData(geojsonPoint)
}, 200)
}
绘制台风眼图标并旋转
具体思路我不清楚,代码就在下面了,可以直接复制
function rotateIcon() {
map.addImage('typhoon', createRotateIconByStaticDiagram('/src/assets/img/typhoon.png'))
map.addSource('rotate', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [liveData[liveData.length - 1].longitude, liveData[liveData.length - 1].latitude]
}
}
]
}
})
map.addLayer({
id: 'rotate',
type: 'symbol',
source: 'rotate',
layout: {
'icon-image': 'typhoon',
'icon-size': 0.3,
'icon-offset': [0, 0],
'icon-rotation-alignment': 'viewport',
'icon-allow-overlap': true,
'icon-ignore-placement': true
}
})
}
function createRotateIconByStaticDiagram(url) {
const size = 100
return {
width: size,
height: size,
data: new Uint8Array(size * size * 4),
// context: null,
onAdd: function () {
const canvas = document.createElement('canvas')
canvas.width = this.width
canvas.height = this.height
this.context = canvas.getContext('2d')
},
render: function () {
const context = this.context
context.clearRect(0, 0, this.width, this.height)
const img = new Image()
img.src = url
context.drawImage(img, 0, 0)
img.onload = function () {
context.clearRect(0, 0, size, size)
context.translate(50, 50)
context.rotate(Math.PI / 45)
context.translate(-50, -50)
context.drawImage(img, 0, 0)
}
this.data = context.getImageData(0, 0, this.width, this.height).data
map.triggerRepaint()
return true
}
}
}
执行下面两个函数即可
// 渲染台风路线
renderLine()
// 渲染台风点
renderPoint()