Skip to content

Commit

Permalink
init project
Browse files Browse the repository at this point in the history
  • Loading branch information
vensing committed Jul 29, 2020
0 parents commit 464c764
Show file tree
Hide file tree
Showing 13 changed files with 11,830 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# openlayers 气象可视化 🌈

openlayers 气象要素可视化,做出类似 [Ventusky](https://ventusky.com) 的效果。

![ol meteo](public/images/ol_meteo.png)

## 关于

本项目是基于 vue-cli3 搭建的基础项目,因此不涉及路由处理等请知悉。

## 数据

GFS grib2 数据生成灰度图。如何读取 grib2 文件并生成图片,请参考:[grib2-plot](https://github.com/vensing/grib2-plot),该项目生成彩色图,生成灰度图只需设置单通道颜色即可。


## 原理

前台读取灰度图数据;获取屏幕坐标转行为经纬度坐标,经纬度坐标经双线性插值获取对应点的气象要素值;通过值找到对应色阶区域的颜色值,canvas 绘制矩形填色。

## 如何运行

```
npm install
npm run serve
```

## 参考

- [Ventusky.com](https://ventusky.com)
- [Windy.com](https://windy.com)
- [earth](https://earth.nullschool.net)
5 changes: 5 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
11,514 changes: 11,514 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"chroma-js": "^2.1.0",
"core-js": "^3.6.5",
"ol": "^6.4.0",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-plugin-eslint": "~4.4.0",
"@vue/cli-service": "~4.4.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Binary file added public/favicon.ico
Binary file not shown.
Binary file added public/images/icon_teplota_2_m_20200728_03.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/ol_meteo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>openlayers 气象可视化</title>
</head>
<body>
<noscript>
<strong>We're sorry but web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
24 changes: 24 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<div id="app">
<Map/>
</div>
</template>

<script>
import Map from './components/Map.vue'
export default {
name: 'App',
components: {
Map
}
}
</script>

<style>
html, body {
width: 100%;
height: 100%;
margin: unset;
}
</style>
Binary file added src/assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
163 changes: 163 additions & 0 deletions src/components/Map.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<template>
<div class="ol-map" ref="olmap"></div>
</template>

<script>
import 'ol/ol.css'
import { Map, View } from 'ol'
import { transform, toLonLat } from 'ol/proj'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import ImageLayer from 'ol/layer/Image'
import ImageCanvasSource from 'ol/source/ImageCanvas'
import chroma from 'chroma-js'
const MERCATOR = 'EPSG:3857'
const WGS84 = 'EPSG:4326'
const temColors = [
'rgba( 238, 238, 238 ,0.85)', 'rgba( 255, 170, 255 ,0.85)',
'rgba( 145, 9, 145 ,0.85)', 'rgba( 36, 24, 106 ,0.85)',
'rgba( 85, 78, 177 ,0.85)', 'rgba( 62, 121, 198 ,0.85)',
'rgba( 75, 182, 152 ,0.85)', 'rgba( 89, 208, 73 ,0.85)',
'rgba( 190, 228, 61 ,0.85)', 'rgba( 235, 215, 53 ,0.85)',
'rgba( 234, 164, 62 ,0.85)', 'rgba( 229, 109, 83 ,0.85)',
'rgba( 190, 48, 102 ,0.85)', 'rgba( 107, 21, 39 ,0.85)',
'rgba( 43, 0, 1 ,1)'];
const colors = chroma.scale(temColors).domain([98, 103, 108, 113, 118, 123, 128, 133, 138, 143, 148, 153, 158, 163, 168]);
export default {
name: 'HelloWorld',
data () {
return {
map: {},
imageArray: [],
canvasLayer: null,
image: null
}
},
props: {
msg: String
},
mounted () {
this.canvasLayer = new ImageLayer({opacity: 0.7});
var map = new Map({
target: this.$refs.olmap,
layers: this.getBaseLayers().concat([this.canvasLayer]),
view: new View({
projection: MERCATOR,
center: this.transformPoint(116, 40),
zoom: 4,
maxZoom: 14,
minZoom: 4,
enableRotation: false
})
});
this.map = map;
this.images2Canvas();
this.image.src = '/images/icon_teplota_2_m_20200728_03.jpg';
},
methods: {
getBaseLayers () {
var warmlayer = new TileLayer({
source: new XYZ({
url: 'https://www.google.cn/maps/vt?lyrs=m@189&gl=cn&x={x}&y={y}&z={z}'
})
})
var graylayer = new TileLayer({
source: new XYZ({
url: 'https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetGray/MapServer/tile/{z}/{y}/{x}'
})
})
var bluelayer = new TileLayer({
source: new XYZ({
url: 'https://map.geoq.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}'
})
})
return [bluelayer, graylayer, warmlayer]
},
transformPoint (lon, lat) {
return transform([lon, lat], WGS84, MERCATOR)
},
images2Canvas () {
var self = this;
var img = new Image;
self.image = img;
img.crossOrigin = "anonymous";
img.onload = function() {
var canvasPic = document.createElement('canvas');
var ctxPic = canvasPic.getContext("2d");
canvasPic.width = this.width;
canvasPic.height = this.height;
ctxPic.drawImage(this, 0, 0);
var imageData = ctxPic.getImageData(0, 0, this.width, this.height).data;
canvasPic.style.display = 'none';
self.imageArray = new Float32Array(imageData.length/4);
Float32Array.prototype.getValue = function(lon,lat){
var a = lon;
var b = lat;
var na = Math.floor(a)<-360?na+=720:Math.floor(a);
var nb = Math.floor(b)<-180?nb+=360:Math.floor(b);
var ma = Math.ceil(a)>360?ma-=720:Math.ceil(a);
var mb = Math.ceil(b)>180?mb-=360:Math.ceil(b);
var fa = a - na;
var fb = b - nb;
var value= this[((90-nb)*2*720+(na+180)*2)] * (1 - fa) * (1 - fb) +
this[((90-nb)*2*720+(ma+180)*2)] * fa * (1 - fb) +
this[((90-mb)*2*720+(na+180)*2)] * (1 - fa) * fb +
this[((90-mb)*2*720+(ma+180)*2)] * fa * fb;
return value;
}
for (var i = 0; i < imageData.length; i+=4 ) {
self.imageArray[i/4] = imageData[i]
}
self.canvasLayer.setSource(new ImageCanvasSource({
canvasFunction : self.canvasFunction,
ratio : 1,
projection : MERCATOR,
imageSmoothing : true
}))
}
},
canvasFunction (extent, resolution, pixelRatio, size, projection) {
var width = Math.round(size[0])*pixelRatio;
var height = Math.round(size[1])*pixelRatio;
var can = document.createElement('canvas');
can.width = width;
can.height = height;
var ctx = can.getContext('2d');
var dx = Math.floor(3*pixelRatio);
var halfdx = Math.ceil(dx/2);
for (var j = 0; j <= height; j += dx) {
for (var i = 0; i <= width; i += dx ) {
var coord = new toLonLat(this.map.getCoordinateFromPixel([i/pixelRatio, j/pixelRatio]), projection);
var value = this.imageArray.getValue(coord[0],coord[1]);
ctx.fillStyle = colors(value).css();
ctx.fillRect(i-halfdx,j-halfdx,dx,dx)
}
}
return can;
}
}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.ol-map {
height: calc(100vh);
width: auto;
}
</style>
8 changes: 8 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

0 comments on commit 464c764

Please sign in to comment.