Skip to content

Instantly share code, notes, and snippets.

@enjikaka
Created August 19, 2020 11:17
Show Gist options
  • Save enjikaka/78b92a628bad0ac28f4054b3aa714d75 to your computer and use it in GitHub Desktop.
Save enjikaka/78b92a628bad0ac28f4054b3aa714d75 to your computer and use it in GitHub Desktop.
function imageLoad(imageUrl) {
return new Promise((resolve, reject) => {
const image = new Image();
image.crossOrigin = 'anonymous';
image.onload = () => resolve(image);
image.onerror = err => reject(err);
image.src = imageUrl;
});
}
const pollenScale = {
'#EE1583': [20000, undefined],
'#F83F43': [10000, 20000],
'#EE8236': [5000, 10000],
'#E5AE3D': [2000, 5000],
'#E6DB45': [1000, 2000],
'#A2E444': [500, 1000],
'#58EE5A': [100, 500],
'#22DA27': [50, 100],
'#55A7F2': [10, 50],
'#2F85ED': [5, 10],
'#1C67CF': [1, 5],
'#DCDCDC': [0, 1]
};
const scale10 = {
'#EE1583': [10.0, undefined],
'#F83F43': [5.0, 10.0],
'#EE8236': [2.0, 5.0],
'#E5AE3D': [1.5, 2.0],
'#E6DB45': [1.0, 1.5],
'#A2E444': [0.5, 1.0],
'#58EE5A': [0.1, 0.5],
'#22DA27': [0.05, 0.1],
'#55A7F2': [0.01, 0.05],
'#2F85ED': [0.005, 0.01],
'#1C67CF': [0.001, 0.005],
'#DCDCDC': [0, 0.001]
};
const scale200 = {
'#EE1583': [200, undefined],
'#F83F43': [150, 200],
'#EE8236': [100, 150],
'#E5AE3D': [75, 100],
'#E6DB45': [50, 75],
'#A2E444': [40, 50],
'#58EE5A': [30, 40],
'#22DA27': [20, 30],
'#55A7F2': [10, 20],
'#2F85ED': [5, 10],
'#1C67CF': [2, 5],
'#DCDCDC': [0, 2]
};
const scale240 = {
'#EE1583': [240, undefined],
'#F83F43': [200, 240],
'#EE8236': [180, 200],
'#E5AE3D': [160, 180],
'#E6DB45': [140, 160],
'#A2E444': [120, 150],
'#58EE5A': [100, 120],
'#22DA27': [80, 100],
'#55A7F2': [60, 80],
'#2F85ED': [40, 60],
'#1C67CF': [20, 40],
'#DCDCDC': [0, 20]
};
const scaleColors = [
'#EE1583',
'#F83F43',
'#EE8236',
'#E5AE3D',
'#E6DB45',
'#A2E444',
'#58EE5A',
'#22DA27',
'#55A7F2',
'#2F85ED',
'#1C67CF',
'#DCDCDC'
];
const atmosphereTypeToScale = {
'Pollen-Birch': pollenScale,
'CO': {
'#EE1583': [1000, undefined],
'#F83F43': [700, 1000],
'#EE8236': [500, 700],
'#E5AE3D': [400, 500],
'#E6DB45': [350, 400],
'#A2E444': [300, 350],
'#58EE5A': [250, 300],
'#22DA27': [200, 250],
'#55A7F2': [150, 200],
'#2F85ED': [100, 150],
'#1C67CF': [50, 100],
'#DCDCDC': [0, 50]
},
'Pollen-Grass': pollenScale,
'NH3': scale10,
'NMVOC': scale200,
'NO': scale200,
'NO2': scale200,
'O3': scale240,
'PANs': scale10,
'PM10': scale200,
'PM2.5': scale200,
'SO2': scale200,
};
/**
* @typedef {'Pollen-Birch' | 'CO' | 'Pollen-Grass' | 'NH3' | 'NMVOC' | 'NO' | 'NO2' | 'O3' | 'PANs' | 'PM10' | 'PM2.5' | 'SO2'} AthmosphereType
*/
const atmosphereTypeToLayersAndStyle = {
'Pollen-Birch': {
layers: 'BIRCHPOLLEN__GROUND_OR_WATER_SURFACE',
styles: 'C_POL_BIRCH__GROUND__SHADING'
},
'CO': {
layers: 'CO__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'CO_USI__HEIGHT__SHADING'
},
'Pollen-Grass': {
layers: 'GRASSPOLLEN__GROUND_OR_WATER_SURFACE',
styles: 'C_POL_GRASS__GROUND__SHADING'
},
'NH3': {
layers: 'NH3__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'NH3_USI__HEIGHT__SHADING'
},
'NMVOC': {
layers: 'NMVOC__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'NMVOC_USI__HEIGHT__SHADING'
},
'NO': {
layers: 'NO__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'NO_USI__HEIGHT__SHADING'
},
'NO2': {
layers: 'NO2__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'NO2_USI__HEIGHT__SHADING'
},
'O3': {
layers: 'O3__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'O3_USI__HEIGHT__SHADING'
},
'PANs': {
layers: 'PANS__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'PANS_USI__HEIGHT__SHADING'
},
'PM10': {
layers: 'PM10__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'PM10_USI__HEIGHT__SHADING'
},
'PM2.5': {
layers: 'PM25__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'PM25_USI__HEIGHT__SHADING'
},
'SO2': {
layers: 'SO2__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND',
styles: 'SO2_USI__HEIGHT__SHADING'
},
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
] : null;
}
function euclideanDistance (v1, v2) {
let i;
let d = 0;
for (i = 0; i < v1.length; i++) {
d += (v1[i] - v2[i]) * (v1[i] - v2[i]);
}
return Math.sqrt(d);
}
/**
* @param {number[]} coord
* @param {AthmosphereType} type
*/
export async function get([long, lat], type) {
const url = new URL('https://geoservices.regional.atmosphere.copernicus.eu/services/CAMS50-ENSEMBLE-FORECAST-01-EUROPE-WMS?service=WMS&VERSION=1.3.0&ELEVATION=0&FORMAT=image/png&REQUEST=GetMap&CRS=EPSG:4326&BBOX=50,0,77,30&WIDTH=810&HEIGHT=495');
url.searchParams.set('TOKEN', '__jCPVM0UQqSJKhAZWgBcZtyE9y97JlOtYnGtIR4reaLs__');
const { layers, styles } = atmosphereTypeToLayersAndStyle[type];
url.searchParams.set('LAYERS', layers);
url.searchParams.set('STYLES', styles);
const dimRefTime = new Date().toJSON().split('T')[0] + 'T00:00:00Z';
const time = new Date().toJSON().split(':')[0] + ':00:00Z';
url.searchParams.set('DIM_REFERENCE_TIME', dimRefTime);
url.searchParams.set('TIME', time);
const image = await imageLoad(url.toString());
// const backgroundImage = await imageLoad('./bg.png');
// const canvas = document.querySelector('canvas');
const canvas = document.createElement('canvas');
canvas.width = 810;
canvas.height = 495;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
// context.drawImage(backgroundImage, 0, 0, 810, 495);
const x = (810 / 30) * long;
const y = (495 / 27) * (77 - lat - 2.2);
const colour = context.getImageData(x, y, 1, 1);
// context.fillStyle = 'red';
// context.fillRect(x, y, 5, 5);
const [r, g, b] = colour.data;
const noData = (r === 255 && g === 255 && b === 255);
const res = scaleColors.map(hex => ({
hex,
distance: euclideanDistance(hexToRgb(hex), [r, g, b])
})).sort((a, b) => a.distance - b.distance)[0];
return {
type,
value: noData ? [0] : atmosphereTypeToScale[type][res.hex],
unit: type.indexOf('Pollen') !== -1 ? undefined : 'µg/m3',
color: [r, g, b]
};
}
export async function getAll(coords) {
/** @type {AthmosphereType[]} */
// @ts-ignore
const keys = Object.keys(atmosphereTypeToLayersAndStyle);
const promises = keys.map(type => get(coords, type));
const results = await Promise.all(promises);
return results;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment