/** * 单个台风对象 * @class Typhoon */ class Typhoon { //= ========= 构造方法 ========== constructor(options, map) { this.options = options this.map = map // 创建台风相关矢量图层 this.typhoonLayer = new mars3d.layer.GraphicLayer() map.addLayer(this.typhoonLayer) this.clickPtLayer = new mars3d.layer.GraphicLayer() map.addLayer(this.clickPtLayer) this.showTyphoonToMap(options.path) } //= ========= 对外属性 ========== // 显示隐藏 get show() { return this.options.show } set show(val) { this.options.show = val this.typhoonLayer.show = val this.clickPtLayer.show = val } getPointById(id) { return this.typhoonLayer.getGraphicById(id) } flyTo(options) { this.typhoonLayer.flyTo(options) } getPointTooltip(event, isYB) { const item = event.graphic?.attr if (!item) { return } let ybHtml = "" if (isYB) { ybHtml = `
预报机构: 中央气象台
` } let fqHtml = '| 风圈半径 | 东北 | 东南 | 西南 | 西北 |
|---|---|---|---|---|
| 七级 | ${item.circle7.radius1} | ${item.circle7.radius2} | ${item.circle7.radius3} | ${item.circle7.radius4} (KM) |
| 十级 | ${item.circle10.radius1} | ${item.circle10.radius2} | ${item.circle10.radius3} | ${item.circle10.radius4} (KM) |
| 十二级 | ${item.circle12.radius1} | ${item.circle12.radius2} | ${item.circle12.radius3} | ${item.circle12.radius4} (KM) |
${this.options.typnumber} ${this.options.name_cn}
过去时间: ${item.time_str}
中心位置: ${item.lat}N/${item.lon}E
最大风速: ${item.centerSpeed}米/秒
中心气压: ${item.strength}百帕
移动方向: ${item.moveTo_str}
移动速度: ${item.windSpeed}公里/小时
`,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER
}
})
this.typhoonLayer.addGraphic(gifGraphic)
this.gifGraphic = gifGraphic
// 显示最后所在点的对应风圈等。
this.showPointFQ(endItem)
}
// 点击事件,点击出现台风icon
showPointFQ(item, availability) {
if (!availability) {
this.clickPtLayer.clear()
}
const position = [item.lon, item.lat]
// 台风实时位置gif点更新
if (this.gifGraphic) {
this.gifGraphic.position = position
}
// 绘制7级风圈面
if (item.circle7) {
let points7 = []
points7 = points7.concat(getPoints(position, item.circle7.radius1, 0)) // 东北方向
points7 = points7.concat(getPoints(position, item.circle7.radius2, 90)) // 东南
points7 = points7.concat(getPoints(position, item.circle7.radius3, 180)) // 西南
points7 = points7.concat(getPoints(position, item.circle7.radius4, 270)) // 西北
const graphic = new mars3d.graphic.PolygonEntity({
positions: points7,
availability: availability,
style: {
setHeight: 900,
color: "#eed139",
opacity: 0.3,
outline: true,
outlineWidth: 2,
outlineColor: "#eed139"
}
})
this.clickPtLayer.addGraphic(graphic)
}
// 绘制10级风圈面
if (item.circle10) {
let points10 = []
points10 = points10.concat(getPoints(position, item.circle10.radius1, 0)) // 东北方向
points10 = points10.concat(getPoints(position, item.circle10.radius2, 90))
points10 = points10.concat(getPoints(position, item.circle10.radius3, 180))
points10 = points10.concat(getPoints(position, item.circle10.radius4, 270))
const tenGraphic = new mars3d.graphic.PolygonEntity({
positions: points10,
availability: availability,
style: {
setHeight: 800,
color: "#fe9c45",
opacity: 0.3,
outline: true,
outlineWidth: 2,
outlineColor: "#fe9c45",
arcType: Cesium.ArcType.GEODESIC
}
})
this.clickPtLayer.addGraphic(tenGraphic)
}
// 绘制12级风圈面
if (item.circle12) {
let points12 = []
points12 = points12.concat(getPoints(position, item.circle12.radius1, 0)) // 东北方向
points12 = points12.concat(getPoints(position, item.circle12.radius2, 90))
points12 = points12.concat(getPoints(position, item.circle12.radius3, 180))
points12 = points12.concat(getPoints(position, item.circle12.radius4, 270))
const tenGraphic = new mars3d.graphic.PolygonEntity({
positions: points12,
availability: availability,
style: {
setHeight: 700,
color: "#ffff00",
opacity: 0.3,
outline: true,
outlineWidth: 2,
outlineColor: "#ffff00",
arcType: Cesium.ArcType.GEODESIC
}
})
this.clickPtLayer.addGraphic(tenGraphic)
}
// 台风预测路径绘制
if (item.forecast) {
const linePoint = [position]
item.forecast.forEach((element) => {
const forecastPt = [element.lon, element.lat]
linePoint.push(forecastPt)
// 在图层上绘画出所有的点
const pointEntity = new mars3d.graphic.PointEntity({
position: forecastPt,
availability: availability,
style: {
pixelSize: 6,
color: element.color, // 不同typlevel显示不同的颜色
opacity: 0.8
},
attr: item
})
this.clickPtLayer.addGraphic(pointEntity)
pointEntity.bindTooltip(
(event) => {
return this.getPointTooltip(event, true)
},
{
template: "",
anchor: [260, -20]
}
)
// 预测路线
const graphicLine = new mars3d.graphic.PolylineEntity({
positions: linePoint,
availability: availability,
style: {
material: mars3d.MaterialUtil.createMaterialProperty(mars3d.MaterialType.PolylineDash, {
dashLength: 20.0,
color: "red"
})
}
})
this.clickPtLayer.addGraphic(graphicLine)
})
}
}
// 释放
destroy() {
this.show = false
if (this.typhoonLayer) {
this.typhoonLayer.destroy()
delete this.typhoonLayer
}
if (this.clickPtLayer) {
this.clickPtLayer.destroy()
delete this.clickPtLayer
}
}
}
/**
* 动态播放 单个台风对象
* @class PlayTyphoon
*/
class PlayTyphoon extends Typhoon {
//= ========= 对外属性 ==========
// 显示隐藏
get isStart() {
return this._isStart
}
set isStart(val) {
this._isStart = val
}
// 绘制一个台风对应的对象。
showTyphoonToMap(arr) {
if (arr.length < 1) {
return
}
const firstItem = arr[0]
const endItem = arr[arr.length - 1]
this.startTime = Cesium.JulianDate.fromDate(firstItem.time) // 开始时间
this.stopTime = Cesium.JulianDate.fromDate(endItem.time) // 结束时间
let lastType = arr[0].level
let property = new Cesium.SampledPositionProperty()
property.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD
for (let i = 0, len = arr.length; i < len; i++) {
const item = arr[i]
const point = [item.lon, item.lat]
const position = Cesium.Cartesian3.fromDegrees(item.lon, item.lat) // 经度、纬度坐标转化
const pointTime = Cesium.JulianDate.fromDate(item.time) // 将时间转化成需要的格式
property.addSample(pointTime, position)
// 绘制点
const pointEntity = new mars3d.graphic.PointEntity({
id: item.id,
position: point,
availability: new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: pointTime,
stop: this.stopTime
})
]),
style: {
pixelSize: 6,
color: item.color
},
attr: item
})
pointEntity.bindTooltip(
(event) => {
return this.getPointTooltip(event)
},
{
template: "",
anchor: [260, -20]
}
)
this.typhoonLayer.addGraphic(pointEntity)
if (lastType !== item.level || i === len - 1) {
// 绘制线
const graphicLine = new mars3d.graphic.PathEntity({
position: property,
style: {
leadTime: 0,
color: getColor(lastType)
}
})
this.typhoonLayer.addGraphic(graphicLine)
lastType = item.level
property = new Cesium.SampledPositionProperty() // 控制动画播放的对象
property.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD
property.addSample(pointTime, position)
}
// 显示每个点的风圈和预测路线
let lastTime
if (i === len - 1) {
lastTime = this.lastTime
} else {
lastTime = Cesium.JulianDate.fromDate(arr[i + 1].time)
}
const availability = new Cesium.TimeIntervalCollection([
new Cesium.TimeInterval({
start: pointTime,
stop: lastTime
})
])
this.showPointFQ(item, availability)
}
// [起点]绘制台风起点名字
this.addNameGraphic(firstItem)
}
// 开始播放
start() {
this._isStart = true
this.map.clock.startTime = this.startTime.clone()
this.map.clock.stopTime = this.stopTime.clone()
this.map.clock.currentTime = this.startTime.clone()
this.map.clock.clockRange = Cesium.ClockRange.CLAMPED // 到达时间点后终止
this.map.clock.multiplier = 16000
if (this.map.controls.timeline) {
this.map.controls.timeline.zoomTo(this.startTime, this.stopTime)
}
this.show = true
this.map.clock.shouldAnimate = true
}
// 停止播放
stop() {
this._isStart = false
this.show = false
const now = Cesium.JulianDate.fromDate(new Date())
this.map.clock.startTime = now.clone()
this.map.clock.stopTime = Cesium.JulianDate.addDays(now, 1.0, new Cesium.JulianDate())
this.map.clock.currentTime = now.clone()
this.map.clock.clockRange = Cesium.ClockRange.UNBOUNDED
this.map.clock.multiplier = 1
}
}
// 不同等级的台风对应不同的颜色
function getColor(level) {
switch (level) {
case "TD": // 热带低压
return "rgb(238,209,57)"
case "TS": // 热带风暴
return "rgb(0,0,255)"
case "STS": // 强热带风暴
return "rgb(15,128,0)"
case "TY": // 台风
return "rgb(254,156,69)"
case "STY": // 强台风
return "rgb(254,0,254)"
case "SuperTY": // 超强台风
return "rgb(254,0,0)"
default:
}
}
// 根据经纬度 直径 以及方向计算方法
function getPoints(center, cradius, startAngle) {
const points = []
const radius = cradius / 100
const pointNum = 90
const endAngle = startAngle + 90
let sin, cos, x, y, angle
for (let i = 0; i <= pointNum; i++) {
angle = startAngle + ((endAngle - startAngle) * i) / pointNum
sin = Math.sin((angle * Math.PI) / 180)
cos = Math.cos((angle * Math.PI) / 180)
x = center[0] + radius * sin
y = center[1] + radius * cos
points.push([x, y])
}
return points
}