# 风向图
vc-windmap
组件用于加载风向图,其实质搬运自开源项目3D-Wind-Field (opens new window)。
# 示例
# 加载风向图
# 预览
maxParticles particleHeight fadeOpacity dropRate dropRateBump speedFactor lineWidth switch data
收起
<template>
<div class="viewer">
<vc-viewer scene3DOnly animation timeline :fullscreenButton="fullscreenButton"
:fullscreenElement="fullscreenElement" @ready="ready">
<vc-layer-imagery>
<vc-provider-imagery-tile-single :url="urlLayer"></vc-provider-imagery-tile-single>
</vc-layer-imagery>
<vc-windmap ref="windmap" :data="windData" :particleSystemOptions="particleSystemOptions"> </vc-windmap>
<!-- <vc-kriging-map v-if="values.length !== 0" :breaks="breaks" :values="values" :lngs="lngs" :lats="lats" :colors="colors" :clipCoords="clipCoords">
</vc-kriging-map> -->
</vc-viewer>
<div class="demo-tool">
<el-select v-model="data" placeholder="切换数据" @selected="switchData">
<el-option v-for="item in options" :key="item.value" :value="item.value"
:label="item.label">
</el-option>
</el-select>
<el-button size="small" class="md-raised md-accent" @click="toggle"
>移除数据</el-button>
<div>
<span>maxParticles</span>
<el-slider v-model="particleSystemOptions.maxParticles" :min="1" :max="65536" :interval="1"></el-slider>
<span>particleHeight</span>
<el-slider v-model="particleSystemOptions.particleHeight" :min="1" :max="10000" :interval="1"></el-slider>
<span>fadeOpacity</span>
<el-slider v-model="particleSystemOptions.fadeOpacity" :min="0.90" :max="0.999" :interval="0.001"></el-slider>
<span>dropRate</span>
<el-slider v-model="particleSystemOptions.dropRate" :min="0.0" :max="0.1" :interval="0.001"></el-slider>
<span>dropRateBump</span>
<el-slider v-model="particleSystemOptions.dropRateBump" :min="0.0" :max="0.2" :interval="0.001"></el-slider>
<span>speedFactor</span>
<el-slider v-model="particleSystemOptions.speedFactor" :min="0.5" :max="100" :interval="0.1"></el-slider>
<span>lineWidth</span>
<el-slider v-model="particleSystemOptions.lineWidth" :min="0.01" :max="16" :step="0.01"></el-slider>
<span>switch data</span>
</div>
</div>
</div>
</template>
<script>
import NetCDFReader from 'netcdfjs'
export default {
data() {
return {
showWind:true,
urlLayer: '/statics/SampleData/worldimage.jpg',
urlNetCDF: '/statics/SampleData/windData/demo.nc',
windData: {},
values: [],
lngs: [],
lats: [],
fullscreenButton: true,
fullscreenElement: null,
breaks: [0.1, 10, 25, 50, 100, 250, 500],
clipCoords:[],
// colors:['#d0c9ec','#9f8de6','#6046c5','#382190','#340cd0','#3502fb','#59b7e4','#1981b3','#0aa6f1'
// ,'#63cfda','#28cfe0'],
colors:[],
options: [
{
label: 'Global Data',
value: 1
},
{
label: 'China Data',
value: 2
}
],
data: 0,
particleSystemOptions: {
particlesTextureSize: 100,
maxParticles: 100 * 100,
particleHeight: 1.0,
fadeOpacity: 0.984,
dropRate: 0.003,
dropRateBump: 0.01,
speedFactor: 1.2,
lineWidth: 9.0
}
}
},
methods: {
toggle(){
this.showWind=!this.showWind
if(this.showWind){
this.$refs.windmap.reload()
}else{
this.$refs.windmap.unload()
}
},
LEFT_CLICK(movement){
const { Cesium, viewer } = this.cesiumInstance
var pos=viewer.scene.pickPosition(movement.position)
var position=GisEye.CoordinatesTransform.windowPositionToDegrees(movement.position,viewer)
console.log(position[0],position[1])
var label=this.product.getPickData(position[0],position[1])
viewer.entities.removeAll();
viewer.entities.add({
position:pos,
label:{
text:label,
fillColor: Cesium.Color.RED,
disableDepthTestDistance:100000000000000
}
})
},
ready(cesiumInstance) {
this.cesiumInstance = cesiumInstance
this.data = 1
this.switchData(1)
},
switchData(val) {
const { Cesium, viewer } = this.cesiumInstance
viewer.screenSpaceEventHandler.setInputAction(
this.LEFT_CLICK,
Cesium.ScreenSpaceEventType.LEFT_CLICK
)
let _this = this
if (val === 1) {
this.product = new GisEye.ProductData('https://hyyj-stream.oss-cn-beijing.aliyuncs.com/slik/data/oscar/202011211200-surface-currents-oscar-0.33.json')
this.product.getData()
.then(data => {
_this.windData = data
const {lon,lat}=data
var canvas = document.createElement('canvas')
// var canvas = document.getElementById('test')
let scale=4
canvas.width=lon.size
canvas.height=lat.size
var context = canvas.getContext("2d");
context.fillStyle = "rgba(255, 0, 0, 1)";
context.fill();
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
var imgdata = imageData.data;
// context.clearRect(0, 0, canvas.width, canvas.height)
function set(x, y, rgba){
try{
// context.fillStyle=rgba
// context.fillRect(x,y,1,1)
let i = (y * canvas.width + x) * 4;
imgdata[i ] = rgba[0];
imgdata[i + 1] = rgba[1];
imgdata[i + 2] = rgba[2];
imgdata[i + 3] = rgba[3];
}
catch(e){
console.log(e)
}
}
// layout: [r, g, b, a, r, g, b, a, ...]
debugger
var datatemp=_this.product.getGridFeatures('current')
_this.values=datatemp.values
_this.lngs=datatemp.lngs
_this.lats=datatemp.lats,
_this.breaks=datatemp.breaks
_this.clipCoords=datatemp.polygon
_this.colors=datatemp.colors
let obj=datatemp.obj
let x=0
while(x<lon.size){
for(let y=0 ;y< lat.size;y+=1){
try{
let color=[0,0,0,0]
let key=`${x}_${y}`;
let value=obj[key]
if(value) {
color=value[1]
}
// set(Number(x),Number(y),color)
set(Number(x)+1,Number(y),color)
set(Number(x),Number(y)+1,color)
set(Number(x)+1,Number(y)+1,color)
}
catch(e){
console.log(e)
}
}
x+=1
}
context.clearRect(0, 0, canvas.width, canvas.height)
context.putImageData(imageData,0,0)
var datasource=new Cesium.CustomDataSource('xx')
viewer.dataSources.add(datasource)
datasource.entities.add({
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(data.lon.min, data.lat.min, data.lon.max, data.lat.max),
material: new Cesium.ImageMaterialProperty({
image: canvas,
// color: Cesium.Color.WHITE.withAlpha(0.7)
})
}
})
})
return
this.loadNetCDF(this.urlNetCDF).then((data) => {
_this.windData = data
})
} else if (val === 2) {
Cesium.Resource.fetchJson({ url: './statics/SampleData/windData/wind.json' }).then((data) => {
data.lon.array = new Float32Array(data.lon.array.slice(0,20))
data.lat.array = new Float32Array(data.lat.array.slice(0,20))
data.lev.array = new Float32Array(data.lev.array)
data.U.array = new Float32Array(data.U.array.slice(0,400))
data.V.array = new Float32Array(data.V.array.slice(0,400))
// debugger
// data.lon.array = data.lon.array.silce(0,29);
// data.lat.array = data.lat.array.silce(0,29);
// data.U.array = data.U.array.silce(0,899);
// data.V.array = data.V.array.silce(0,899);
data.dimensions.lat = 20;
data.dimensions.lon = 20;
let _base_lon = 104.0141756630243;
let _base_lat = 30.71715735036010;
var x1 = _base_lon-0.002-1;
var x2 = _base_lon+0.002+1;
var y1 = _base_lat-0.0014-1;
var y2 = _base_lat+0.0014+1;
var lenX = 20; var stepX = (x2 - x1) / lenX;
var lenY = 20;var stepY = (y2 - y1) / lenY;
for (var i = 0;i<lenX;i++){
data.lon.array[i]= x1+i*stepX;
};
data.lon.max=x2;
data.lon.min=x1;
data.lat.max=y2;
data.lat.min=y1;
for (var i = 0;i<lenY;i++){
data.lat.array[i]= y1+i*stepY;
};
_this.windData = data
// console.log('wind.json',data)
// viewer.scene.camera.flyTo({
// destination: {
// x: -1327779.8149322902,
// y: 5320165.247685926,
// z: 3257916.830723023
// },
// orientation: {
// pitch: -0.34163087120619107,
// heading: 0.9705565449837765,
// roll: 6.283185307179579
// },
// duration: 0.5
// });
// let imgLabel = new Cesium.UrlTemplateImageryProvider({
// url:
// "http://114.116.101.122:7769/gisserver/rest/services/mapserver/tdt-image/{z}/{x}/{y}"
// });
// viewer.imageryLayers.addImageryProvider(imgLabel);
// let imgLabel2 = new Cesium.UrlTemplateImageryProvider({
// url:
// "http://114.116.101.122:7769/gisserver/rest/services/mapserver/tdt-image-label/{z}/{x}/{y}"
// });
// viewer.imageryLayers.addImageryProvider(imgLabel2);
});
}
},
async loadNetCDF(filePath) {
let _this = this
return new Promise(function(resolve) {
var request = new XMLHttpRequest()
request.open('GET', filePath)
request.responseType = 'arraybuffer'
request.onload = function() {
var arrayToMap = function(array) {
return array.reduce(function(map, object) {
map[object.name] = object
return map
}, {})
}
var NetCDF = new NetCDFReader(request.response)
let data = {}
var dimensions = arrayToMap(NetCDF.dimensions)
data.dimensions = {}
data.dimensions.lon = dimensions['lon'].size
data.dimensions.lat = dimensions['lat'].size
data.dimensions.lev = dimensions['lev'].size
var variables = arrayToMap(NetCDF.variables)
var uAttributes = arrayToMap(variables['U'].attributes)
var vAttributes = arrayToMap(variables['V'].attributes)
data.lon = {}
data.lon.array = new Float32Array(NetCDF.getDataVariable('lon').flat())
data.lon.min = Math.min(...data.lon.array)
data.lon.max = Math.max(...data.lon.array)
data.lat = {}
data.lat.array = new Float32Array(NetCDF.getDataVariable('lat').flat())
data.lat.min = Math.min(...data.lat.array)
data.lat.max = Math.max(...data.lat.array)
data.lev = {}
data.lev.array = new Float32Array(NetCDF.getDataVariable('lev').flat())
data.lev.min = Math.min(...data.lev.array)
data.lev.max = Math.max(...data.lev.array)
data.U = {}
data.U.array = new Float32Array(NetCDF.getDataVariable('U').flat())
data.U.min = uAttributes['min'].value
data.U.max = uAttributes['max'].value
data.V = {}
data.V.array = new Float32Array(NetCDF.getDataVariable('V').flat())
data.V.min = vAttributes['min'].value
data.V.max = vAttributes['max'].value
resolve(data)
}
request.send()
})
}
}
}
</script>
# 属性
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
data | Object | required 指定风向数据。 | |
particleSystemOptions | Object | optional 指定粒子参数。 |
# 事件
事件名 | 参数 | 描述 |
---|---|---|
ready | {Cesium, viewer} | 该组件渲染完毕时触发,返回 Cesium 类, viewer 实例。 |
# Vue 方法
方法名 | 参数 | 描述 |
---|---|---|
destroy | 清除风向图。 |
# 其他说明
- 实现思路和着色代码(GLSL)都来自大神
RaymanNg
的开源项目3D-Wind-Field (opens new window),感谢大神的贡献,我只是搬运到支持 vue 了。 - 风向数据是 NetCDF 文件,详细介绍后面再补充。
- 对比nullschool (opens new window)
← 矢量地图 vc-geometry-box →