get outlook data

This commit is contained in:
Matt Walsh 2020-09-23 14:43:49 -05:00
parent 37eb88a90d
commit 24855fd959
4 changed files with 175 additions and 4 deletions

45
cors/outlook.js Normal file
View file

@ -0,0 +1,45 @@
// pass through api requests
// http(s) modules
const https = require('https');
// url parsing
const queryString = require('querystring');
// return an express router
module.exports = (req, res) => {
// add out-going headers
const headers = {};
headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)';
headers['accept'] = req.headers.accept;
// get query paramaters if the exist
const queryParams = Object.keys(req.query).reduce((acc, key) => {
// skip the paramater 'u'
if (key === 'u') return acc;
// add the paramter to the resulting object
acc[key] = req.query[key];
return acc;
},{});
let query = queryString.encode(queryParams);
if (query.length > 0) query = '?' + query;
// get the page
https.get('https://www.cpc.ncep.noaa.gov/' + req.path + query, {
headers,
}, getRes => {
// pull some info
const {statusCode} = getRes;
// pass the status code through
res.status(statusCode);
// set headers
res.header('content-type', getRes.headers['content-type']);
res.header('last-modified', getRes.headers['last-modified']);
// pipe to response
getRes.pipe(res);
}).on('error', e=>{
console.error(e);
});
};

View file

@ -10,10 +10,12 @@ app.set('view engine', 'ejs');
// cors pass through
const corsPassThru = require('./cors');
const radarPassThru = require('./cors/radar');
const outlookPassThru = require('./cors/outlook');
// cors pass-thru to api.weather.gov
app.get('/stations/*', corsPassThru);
app.get('/Conus/*', radarPassThru);
app.get('/products/*', outlookPassThru);
// version
const version = require('./version');

View file

@ -26,11 +26,10 @@ class Almanac extends WeatherDisplay {
// get images for outlook
const imagePromises = [
utils.image.load('https://www.cpc.ncep.noaa.gov/products/predictions/30day/off14_temp.gif'),
utils.image.load('https://www.cpc.ncep.noaa.gov/products/predictions/30day/off14_prcp.gif'),
utils.image.load('products/predictions/30day/off14_temp.gif'),
utils.image.load('products/predictions/30day/off14_prcp.gif'),
];
// get sun/moon data
const {sun, moon} = this.calcSunMoonData(weatherParameters);
@ -38,7 +37,7 @@ class Almanac extends WeatherDisplay {
const [outlookTemp, outlookPrecip] = await Promise.all(imagePromises);
console.log(outlookTemp,outlookPrecip);
const outlook = 1;
const outlook = this.parseOutlooks(weatherParameters.latitude, weatherParameters.longitude, outlookTemp, outlookPrecip);
// store the data
this.data = {
@ -125,6 +124,114 @@ class Almanac extends WeatherDisplay {
return {phase: phaseName, date: moonDate};
}
// use the color of the pixel to determine the outlook
parseOutlooks(lat, lon, temp, precip) {
const {DateTime} = luxon;
const month = DateTime.local().toLocaleString({month: 'long'});
// draw the images on the canvases
const tempContext = utils.image.drawLocalCanvas(temp);
const precipContext = utils.image.drawLocalCanvas(precip);
// get the color from each canvas
const tempColor = this.getOutlookColor(lat, lon, tempContext);
const precipColor = this.getOutlookColor(lat, lon, precipContext);
return {
month,
temperature: this.getOutlookTemperatureIndicator(tempColor),
precipitation: this.getOutlookPrecipitationIndicator(precipColor),
};
}
getOutlookColor (lat, lon, context) {
let x = 0;
let y = 0;
// The height is in the range of latitude 75'N (top) - 15'N (bottom)
y = ((75 - lat) / 53) * 707;
if (lat < 48.83) {
y -= Math.abs(48.83 - lat) * 2.9;
}
if (lon < -100.46) {
y -= Math.abs(-100.46 - lon) * 1.7;
} else {
y -= Math.abs(-100.46 - lon) * 1.7;
}
// The width is in the range of the longitude ???
x = ((-155 - lon) / -110) * 719; // -155 - -40
if (lon < -100.46) {
x -= Math.abs(-100.46 - lon) * 1;
if (lat > 40) {
x += Math.abs(40 - lat) * 4;
} else {
x -= Math.abs(40 - lat) * 4;
}
} else {
x += Math.abs(-100.46 - lon) * 2;
if (lat < 36 && lon > -90) {
x += Math.abs(36 - lat) * 8;
} else {
x -= Math.abs(36 - lat) * 6;
}
}
// The further left and right from lat 45 and lon -97 the y increases
x = Math.round(x);
y = Math.round(y);
// Determine if there is any "non-white" colors around the area.
// Search a 16x16 region.
for (let colorX = x - 8; colorX <= x + 8; colorX++) {
for (let colorY = y - 8; colorY <= y + 8; colorY++) {
const pixelColor = this.getPixelColor(context, colorX, colorY);
if ((pixelColor.r !== 0 && pixelColor.g !== 0 && pixelColor.b !== 0) ||
(pixelColor.r !== 255 && pixelColor.g !== 255 && pixelColor.b !== 255)) {
return pixelColor;
}
}
}
return false;
}
// get rgb values of a pixel
getPixelColor (context, x, y) {
const pixelData = context.getImageData(x, y, 1, 1).data;
return {
r: pixelData[0],
g: pixelData[1],
b: pixelData[2],
};
}
// get temperature outlook from color
getOutlookTemperatureIndicator(pixelColor) {
if (pixelColor.b > pixelColor.r) {
return 'B';
} else if (pixelColor.r > pixelColor.b) {
return 'A';
} else {
return 'N';
}
}
// get precipitation outlook from color
getOutlookPrecipitationIndicator (pixelColor) {
if (pixelColor.g > pixelColor.r) {
return 'A';
} else if (pixelColor.r > pixelColor.g) {
return 'B';
} else {
return 'N';
}
}
async drawCanvas() {
super.drawCanvas();
const info = this.data;

View file

@ -58,6 +58,22 @@ const utils = (() => {
return true;
};
// draw an image on a local canvas and return the context
const drawLocalCanvas = (img) => {
// create a canvas
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
// get the context
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = false;
// draw the image
context.drawImage(img, 0,0);
return context;
};
// *********************************** unit conversions ***********************
Math.round2 = (value, decimals) => Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
@ -374,6 +390,7 @@ const utils = (() => {
load: loadImg,
superGifAsync,
preload,
drawLocalCanvas,
},
weather: {
getPoint,