概述
本文在以前风圈生成文章的基础上,将openlayers4+中台风路径的播放展示做了优化,并进行了封装。
说明:
1、本文示例台风数据来自温州台风网;
2、openlayers使用版本为4.6;
效果
效果实现
封装后的代码为:
var Typhoon = function(map) {
{
const that = this;
that._map = map;
that._speed = 200;
that._typhoonList = [];
that._typhoonData = {};
that._nameOverlays = {};
that._typhoonPlayFlag = {};
that._typhoonPlayIndex = {};
that._warnLines = null;
that._typhoonLayers = {};
that._forecastFeatures = {};
that._forcColorDict = {
'中国': '#ec5d72',
'中国香港': '#ec7cfe',
'中国台湾': '#ecaa65',
'日本': '#56f66e',
'美国': '#53dbfe',
'韩国': '#72a4ac',
'欧洲': '#4c54a6',
};
that.init = function() {
// 添加警戒线
that.addWarnLine();
// 添加地图事件
that.addMapEvent();
// 获取台风数据
$.get('../../data/201929.json', res => {
that._typhoonList = res;
that.showTyphoon(that._typhoonList[0]);
})
};
that.addWarnLine = function() {
const warnLineData = [{
"color": "blue",
"weight": 1,
"opacity": 0.8,
"dashArray": [0, 0],
"points": [
[
105,
0,
],
[
113,
4.5
],
[
119,
11
],
[
119,
18
],
[
127,
22
],
[
127,
34
]
]
},
{
"color": "green",
"weight": 1,
"opacity": 0.8,
"dashArray": [10, 5],
"points": [
[
105,
0
],
[
120,
0
],
[
132,
15
],
[
132,
34
]
]
}
];
const features = [];
for (var i = 0; i < warnLineData.length; i++) {
const d = warnLineData[i];
const geometry = new ol.geom.LineString(d.points);
geometry.transform('EPSG:4326', that._map.getView().getProjection());
const feature = new ol.Feature({
geometry: geometry,
attr: d
});
features.push(feature);
}
const source = new ol.source.Vector({
features: features
});
that._warnLines = new ol.layer.Vector({
source: source,
style: that.warnLineStyle,
opacity: 0.8
});
that._map.addLayer(that._warnLines);
};
// 警戒线样式设置
that.warnLineStyle = function(feat) {
const attr = feat.get('attr');
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: attr.color,
width: attr.weight,
lineDash: attr.dashArray
})
});
};
// 展示台风
that.showTyphoon = function(typhoonData) {
const tfbh = typhoonData.tfbh;
that._typhoonData[tfbh] = typhoonData;
// 1. 添加台风名称
that.addNameOverlay(typhoonData);
// 2. 创建展示图层
that.addTyphoonLayer(tfbh);
// 3.开始播放
that.playTyphoon(tfbh);
};
// 移除台风
that.removeTyphoon = function(tfbh) {
// 删除台风名称
that._map.removeOverlay(that._nameOverlays[tfbh]);
// 删除展示图层
that._map.removeLayer(that._typhoonLayers[tfbh].layer);
// 消除定时器
clearInterval(that._typhoonPlayFlag[tfbh]);
};
// 添加台风名称
that.addNameOverlay = function(typhoonData) {
const nameDom = document.createElement('div');
nameDom.setAttribute('class', 'typhoon-name');
nameDom.innerHTML = typhoonData.name;
const pointStart = typhoonData.points[0];
const position = ol.proj.fromLonLat([pointStart.longitude, pointStart.latitude]);
const nameOverlay = new ol.Overlay({
element: nameDom,
position: position,
positioning: 'center-left',
offset: [15, 0]
});
that._map.addOverlay(nameOverlay);
that._nameOverlays[typhoonData.tfbh] = nameOverlay;
};
// 根据编号添加台风图层
that.addTyphoonLayer = function(tfbh) {
const source = new ol.source.Vector({
features: []
});
const layer = new ol.layer.Vector({
source: source,
style: that.typhoonStyle
});
that._map.addLayer(layer);
that._typhoonLayers[tfbh] = {
source: source,
layer: layer
}
that._forecastFeatures[tfbh] = [];
};
// 添加地图事件
that.addMapEvent = function() {
// 鼠标移动事件
that._map.on('pointermove', function(evt) {
const pixel = evt.pixel;
const dom = that._map.getTargetElement();
if(that._map.hasFeatureAtPixel(pixel)) {
dom.style.cursor = 'pointer';
const features = that._map.getFeaturesAtPixel(pixel);
const feature = features[0];
// console.log(feature.get('attr'));
} else {
dom.style.cursor = 'default';
}
});
that._map.on('click', function(evt) {
const pixel = evt.pixel;
if(that._map.hasFeatureAtPixel(pixel)) {
const features = that._map.getFeaturesAtPixel(pixel);
const feature = features[0];
const attr = feature.get('attr');
that._typhoonPlayIndex[attr.tfbh] = attr.index;
that.showForecast(attr.tfbh, attr);
}
});
};
// 播放台风
that.playTyphoon = function(tfbh) {
let index = 0;
const typhoonData = that._typhoonData[tfbh];
that.play(index, tfbh);
that._typhoonPlayFlag[tfbh] = setInterval(function() {
index++;
if (index === typhoonData.points.length) {
clearInterval(that._typhoonPlayFlag[tfbh]);
} else {
that.play(index, tfbh);
}
}, that._speed);
};
// 播放单个点
that.play = function(index, tfbh) {
// 删除预报
that.removeForecast(tfbh);
that._typhoonPlayIndex[tfbh] = index;
const points = that._typhoonData[tfbh].points;
const point = points[index];
point.type = 'live';
point.index = index;
point.tfbh = tfbh;
if (index > 0) {
const prePoint = points[index - 1];
point.start = [prePoint.longitude, prePoint.latitude];
}
point.end = [point.longitude, point.latitude];
const coords = ol.proj.fromLonLat(point.end);
const feature = new ol.Feature({
geometry: new ol.geom.Point(coords),
attr: point
});
that._typhoonLayers[tfbh].source.addFeature(feature);
// 最后一个实况点,展示预报路径
if (index === that._typhoonData[tfbh].points.length - 1) {
that.showForecast(tfbh, point);
}
};
// 删除预报数据
that.removeForecast = function(tfbh) {
const source = that._typhoonLayers[tfbh].source;
for (var i = 0; i < that._forecastFeatures[tfbh].length; i++) {
const f = that._forecastFeatures[tfbh][i];
source.removeFeature(f);
}
that._forecastFeatures[tfbh] = [];
}
// 展示预报数据
that.showForecast = function(tfbh, livePoint) {
const source = that._typhoonLayers[tfbh].source;
// 1. 删除预报数据
that.removeForecast(tfbh);
// 2. 添加预报
const forecast = livePoint.forecast;
const features = [];
for (var i = 0; i < forecast.length; i++) {
const f = forecast[i];
for (var j = 0; j < f.points.length; j++) {
const point = f.points[j];
const prePoint = f.points[j - 1];
point.sets = f.sets;
point.type = 'forc';
point.index = j;
point.start =
j === 0 ?
[livePoint.longitude, livePoint.latitude] :
[prePoint.longitude, prePoint.latitude];
point.end = [point.longitude, point.latitude];
const coords = ol.proj.fromLonLat(point.end);
const feature = new ol.Feature({
geometry: new ol.geom.Point(coords),
attr: point
});
features.push(feature);
}
}
source.addFeatures(features);
that._forecastFeatures[tfbh] = features;
}
// 台风展示样式
that.typhoonStyle = function(feat) {
const attr = feat.get('attr');
const speed = attr.speed;
const type = attr.type;
const index = attr.index;
const color = that.getPointColor(speed);
let styles = [];
// 点的样式
const radius = type === 'live' ? 4 : 3;
const pointStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: radius,
fill: new ol.style.Fill({
color: color
}),
stroke: new ol.style.Stroke({
color: 'rgba(0, 0, 0, 0.6)',
width: 1
})
})
});
styles.push(pointStyle);
if (type === 'live' && index === that._typhoonPlayIndex[attr.tfbh]) {
const center = feat.get('geometry').getCoordinates();
const fillStyle = new ol.style.Fill({
color: 'rgba(0, 0, 0, 0.2)'
});
// 7级风圈的样式
if(attr.radius7 > 0) {
const geometry = that.getCircleGeometry(center, attr.radius7_quad);
const style = new ol.style.Style({
geometry: geometry,
stroke: new ol.style.Stroke({
color: '#00bab2',
width: 1
}),
fill: fillStyle
});
styles.push(style);
}
// 10级风圈的样式
if(attr.radius10 > 0) {
const geometry = that.getCircleGeometry(center, attr.radius10_quad);
const style = new ol.style.Style({
geometry: geometry,
stroke: new ol.style.Stroke({
color: '#ffff00',
width: 1
}),
fill: fillStyle
});
styles.push(style);
}
// 12级风圈的样式
if(attr.radius12 > 0) {
const geometry = that.getCircleGeometry(center, attr.radius12_quad);
const style = new ol.style.Style({
geometry: geometry,
stroke: new ol.style.Stroke({
color: '#da7341',
width: 1
}),
fill: fillStyle
});
styles.push(style);
}
}
// 线的样式
const start = attr.start;
const end = attr.end;
const lineColor = that.getLineColor(type, attr.sets);
const lineDash = type === 'live' ? [0] : [8];
const lineWidth = type === 'live' ? 2 : 1;
if(start && start.length > 0) {
const coords = [start, end];
const geometry = new ol.geom.LineString(coords);
geometry.transform('EPSG:4326', that._map.getView().getProjection());
const lineStyle = new ol.style.Style({
geometry: geometry,
stroke: new ol.style.Stroke({
color: lineColor,
width: lineWidth,
lineDash: lineDash
})
});
styles.push(lineStyle);
}
return styles;
};
// 获取线的颜色
that.getLineColor = function(type, sets) {
if (type === 'live') {
return 'rgba(0, 0, 0, .6)';
} else {
let color = that._forcColorDict[sets];
if (!color) color = 'rgba(0, 0, 0, .3)';
return color;
}
};
// 根据风速获取点的颜色
that.getPointColor = function(_speed) {
let _color = '';
if (_speed >= 10.8 && _speed < 17.2) {
_color = 'rgba(153, 255, 153, .9)';
} else if (_speed >= 17.2 && _speed < 24.5) {
_color = 'rgba(102, 204, 255, .9)';
} else if (_speed >= 24.5 && _speed < 32.7) {
_color = 'rgba(255, 255, 102, .9)';
} else if (_speed >= 32.7 && _speed < 41.5) {
_color = 'rgba(253, 139, 0, .9)';
} else if (_speed >= 41.5 && _speed < 50.1) {
_color = 'rgba(255, 51, 0, .9)';
} else {
_color = 'rgba(255, 0, 255, .9)';
}
return _color;
};
that.getCircleGeometry = function(center, radiusData) {
if (typeof radiusData === 'number') {
return new ol.geom.Circle(center, radiusData * 1000);
} else {
if (radiusData['ne']) {
let _coords = [];
let _angInterval = 6;
let _pointNums = 360 / (_angInterval * 4);
let quadrant = {
// 逆时针算角度
'0': 'ne',
'1': 'nw',
'2': 'sw',
'3': 'se'
};
for (let i = 0; i < 4; i++) {
let _r = parseFloat(radiusData[quadrant[i]]) * 1000; // 单位是km
if (!_r) _r = 0;
for (let j = i * _pointNums; j <= (i + 1) * _pointNums; j++) {
let _ang = _angInterval * j;
let x = center[0] + _r * Math.cos((_ang * Math.PI) / 180);
let y = center[1] + _r * Math.sin((_ang * Math.PI) / 180);
_coords.push([x, y]);
}
}
return new ol.geom.Polygon([_coords]);
} else {
return null;
}
}
}
}
}
说明:
1、本文中做了优化最多的部分是typhoonStyle
,将实况线、预报线、台风风圈等内容均通过style的方式实现;
2、在计算实况线、预报线的时候直接进行了数据层面的处理,方便后续再用。
调用
调用非常简单,传入一个map对象,并调用init()方法即可。
const typhoon = new Typhoon(map);
typhoon.init();
技术博客
CSDN:http://blog.csdn.NET/gisshixisheng
联系方式
类型 | 内容 |
---|---|
1004740957 | |
公众号 | lzugis15 |
niujp08@qq.com | |
webgis群 | 452117357 |
网友评论