diff --git a/cors/index.js b/cors/index.js
index a3dd9b3..ca12788 100644
--- a/cors/index.js
+++ b/cors/index.js
@@ -10,8 +10,6 @@ const queryString = require('querystring');
// return an express router
module.exports = (req, res) => {
- if (!req.query.u) res.status(404);
-
// add out-going headers
const headers = {};
headers['user-agent'] = '(WeatherStar 4000+, ws4000@netbymatt.com)';
diff --git a/cors/radar.js b/cors/radar.js
new file mode 100644
index 0000000..14313eb
--- /dev/null
+++ b/cors/radar.js
@@ -0,0 +1,44 @@
+// 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://radar.weather.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']);
+ // pipe to response
+ getRes.pipe(res);
+
+ }).on('error', e=>{
+ console.error(e);
+ });
+};
\ No newline at end of file
diff --git a/gulp/publish-frontend.js b/gulp/publish-frontend.js
index d63d1b1..5f9e6ce 100644
--- a/gulp/publish-frontend.js
+++ b/gulp/publish-frontend.js
@@ -34,6 +34,7 @@ const js_sources = [
'server/scripts/modules/localforecast.js',
'server/scripts/modules/extendedforecast.js',
'server/scripts/modules/almanac.js',
+ 'server/scripts/modules/radar.js',
'server/scripts/modules/navigation.js',
];
gulp.task('compress_js', () =>
diff --git a/index.js b/index.js
index 0b1faef..5d49e64 100644
--- a/index.js
+++ b/index.js
@@ -9,9 +9,11 @@ app.set('view engine', 'ejs');
// cors pass through
const corsPassThru = require('./cors');
+const radarPassThru = require('./cors/radar');
// cors pass-thru to api.weather.gov
app.get('/stations/*', corsPassThru);
+app.get('/Conus/*', radarPassThru);
const index = (req, res) => {
diff --git a/server/scripts/modules/navigation.js b/server/scripts/modules/navigation.js
index 0d87aaf..851b3e4 100644
--- a/server/scripts/modules/navigation.js
+++ b/server/scripts/modules/navigation.js
@@ -1,7 +1,7 @@
'use strict';
// navigation handles progress, next/previous and initial load messages from the parent frame
/* globals utils, _StationInfo, STATUS */
-/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac */
+/* globals CurrentWeather, LatestObservations, TravelForecast, RegionalForecast, LocalForecast, ExtendedForecast, Almanac, Radar */
document.addEventListener('DOMContentLoaded', () => {
navigation.init();
@@ -107,6 +107,7 @@ const navigation = (() => {
new LocalForecast(6, 'localForecast', weatherParameters),
new ExtendedForecast(7, 'extendedForecast', weatherParameters),
new Almanac(8, 'alamanac', weatherParameters),
+ new Radar(8, 'radar', weatherParameters),
];
} else {
// or just call for new data if the canvases already exist
diff --git a/server/scripts/modules/radar.js b/server/scripts/modules/radar.js
new file mode 100644
index 0000000..c2dbbba
--- /dev/null
+++ b/server/scripts/modules/radar.js
@@ -0,0 +1,322 @@
+// current weather conditions display
+/* globals WeatherDisplay, utils, STATUS, icons, UNITS, draw, navigation */
+
+// eslint-disable-next-line no-unused-vars
+class Radar extends WeatherDisplay {
+ constructor(navId,elemId,weatherParameters) {
+ super(navId,elemId);
+
+ // set max images
+ this.dopplerRadarImageMax = 6;
+
+ // pre-load background image (returns promise)
+ this.backgroundImage = utils.image.load('images/BackGround4_1.png');
+
+ // get the data
+ this.getData(weatherParameters);
+ }
+
+ async getData(weatherParameters) {
+ super.getData();
+
+ // ALASKA ISN'T SUPPORTED!
+ if (weatherParameters.state === 'AK') {
+ this.setStatus(STATUS.noData);
+ return;
+ }
+
+ // get the base map
+ let src = 'images/4000RadarMap2.jpg';
+ if (weatherParameters.State === 'HI') src = 'images/HawaiiRadarMap2.png';
+ this.baseMap = await utils.image.load(src);
+
+ const baseUrl = 'Conus/RadarImg/';
+
+ let radarHtml;
+ try {
+ // get a list of available radars
+ radarHtml = await $.ajax({
+ type: 'GET',
+ url: baseUrl,
+ dataType: 'text',
+ crossDomain: true,
+ });
+ } catch (e) {
+ console.error('Unable to get list of radars');
+ console.error(e);
+ this.setStatus(STATUS.error);
+ return;
+ }
+
+ // convert to an array of gif urls
+ const $list = $(radarHtml);
+ const gifs = $list.find('a[href]').map((i,elem) => elem.innerHTML).get();
+
+ // filter for selected urls
+ let filter = /^Conus_\d/;
+ if (weatherParameters.State === 'HI') filter = /hawaii_\d/;
+
+ // get the last few images
+ const urlsFull = gifs.filter(gif => gif.match(filter));
+ const urls = urlsFull.slice(-this.dopplerRadarImageMax);
+
+ // calculate offsets and sizes
+ let offsetX = 120;
+ let offsetY = 69;
+ let sourceXY;
+ let width;
+ let height;
+ if (weatherParameters.State === 'HI') {
+ width = 600;
+ height = 571;
+ sourceXY = this.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
+ } else {
+ width = 2550;
+ height = 1600;
+ offsetX *= 2;
+ offsetY *= 2;
+ sourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
+ }
+
+ // create working context for manipulation
+ const workingCanvas = document.createElement('canvas');
+ workingCanvas.width = width;
+ workingCanvas.height = height;
+ const workingContext = workingCanvas.getContext('2d');
+ workingContext.imageSmoothingEnabled = false;
+
+ // calculate radar offsets
+ let radarOffsetX = 117;
+ let radarOffsetY = 60;
+ let radarSourceXY = this.getXYFromLatitudeLongitudeDoppler(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
+ let radarSourceX = radarSourceXY.x / 2;
+ let radarSourceY = radarSourceXY.y / 2;
+
+ if (weatherParameters.State === 'HI') {
+ radarOffsetX = 120;
+ radarOffsetY = 69;
+ radarSourceXY = this.getXYFromLatitudeLongitudeHI(weatherParameters.latitude, weatherParameters.longitude, offsetX, offsetY);
+ radarSourceX = radarSourceXY.x;
+ radarSourceY = radarSourceXY.y;
+ }
+
+ // Load the most recent doppler radar images.
+ const radarCanvases = await Promise.all(urls.map(async (url) => {
+ // create destination context
+ const canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 367;
+ const context = canvas.getContext('2d');
+ context.imageSmoothingEnabled = false;
+
+ // get the image
+ const blob = await $.ajaxCORS({
+ type: 'GET',
+ url: baseUrl + url,
+ xhrFields: {
+ responseType: 'blob',
+ },
+ crossDomain: true,
+ });
+
+ // assign to an html image element
+ const imgBlob = await utils.image.load(blob);
+
+ // draw the entire image
+ if (weatherParameters.State === 'HI') {
+ workingContext.drawImage(imgBlob, 0, 0, 571, 600);
+ } else {
+ workingContext.drawImage(imgBlob, 0, 0, 2550, 1600);
+ }
+
+ // clean the image
+ this.removeDopplerRadarImageNoise(workingContext);
+
+ // get the base map
+ context.drawImage(await this.baseMap, sourceXY.x, sourceXY.y, offsetX*2, offsetY*2, 0, 0, 640, 367);
+
+ // put the radar on top
+ context.drawImage(workingCanvas, radarSourceX, radarSourceY, (radarOffsetX * 2), (radarOffsetY * 2.33), 0, 0, 640, 367);
+
+ return canvas;
+ }));
+ // set max length
+ this.timing.totalScreens = radarCanvases.length;
+
+ console.log(radarCanvases);
+ }
+
+ drawCanvas() {
+ super.drawCanvas();
+
+
+ this.finishDraw();
+ this.setStatus(STATUS.loaded);
+ }
+
+ // utility latitude/pixel conversions
+ getXYFromLatitudeLongitude (Latitude, Longitude, OffsetX, OffsetY, state) {
+ if (state === 'HI') return this.getXYFromLatitudeLongitudeHI(...arguments);
+ let y = 0;
+ let x = 0;
+ const ImgHeight = 1600;
+ const ImgWidth = 2550;
+
+ y = (50.5 - Latitude) * 55.2;
+ y -= OffsetY; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (y > (ImgHeight - (OffsetY * 2))) {
+ y = ImgHeight - (OffsetY * 2);
+ } else if (y < 0) {
+ y = 0;
+ }
+
+ x = ((-127.5 - Longitude) * 41.775) * -1;
+ x -= OffsetX; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (x > (ImgWidth - (OffsetX * 2))) {
+ x = ImgWidth - (OffsetX * 2);
+ } else if (x < 0) {
+ x = 0;
+ }
+
+ return { x, y };
+ }
+
+ getXYFromLatitudeLongitudeHI(Latitude, Longitude, OffsetX, OffsetY) {
+ let y = 0;
+ let x = 0;
+ const ImgHeight = 571;
+ const ImgWidth = 600;
+
+ y = (25 - Latitude) * 55.2;
+ y -= OffsetY; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (y > (ImgHeight - (OffsetY * 2))) {
+ y = ImgHeight - (OffsetY * 2);
+ } else if (y < 0) {
+ y = 0;
+ }
+
+ x = ((-164.5 - Longitude) * 41.775) * -1;
+ x -= OffsetX; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (x > (ImgWidth - (OffsetX * 2))) {
+ x = ImgWidth - (OffsetX * 2);
+ } else if (x < 0) {
+ x = 0;
+ }
+
+ return { x, y };
+ }
+
+ getXYFromLatitudeLongitudeDoppler (Latitude, Longitude, OffsetX, OffsetY) {
+ let y = 0;
+ let x = 0;
+ const ImgHeight = 3200;
+ const ImgWidth = 5100;
+
+ y = (51.75 - Latitude) * 55.2;
+ y -= OffsetY; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (y > (ImgHeight - (OffsetY * 2))) {
+ y = ImgHeight - (OffsetY * 2);
+ } else if (y < 0) {
+ y = 0;
+ }
+
+ x = ((-130.37 - Longitude) * 41.775) * -1;
+ x -= OffsetX; // Centers map.
+ // Do not allow the map to exceed the max/min coordinates.
+ if (x > (ImgWidth - (OffsetX * 2))) {
+ x = ImgWidth - (OffsetX * 2);
+ } else if (x < 0) {
+ x = 0;
+ }
+
+ return { x: x * 2, y: y * 2 };
+ }
+
+ removeDopplerRadarImageNoise (RadarContext) {
+ const RadarImageData = RadarContext.getImageData(0, 0, RadarContext.canvas.width, RadarContext.canvas.height);
+
+ // examine every pixel,
+ // change any old rgb to the new-rgb
+ for (let i = 0; i < RadarImageData.data.length; i += 4) {
+ // i + 0 = red
+ // i + 1 = green
+ // i + 2 = blue
+ // i + 3 = alpha (0 = transparent, 255 = opaque)
+ let [R, G, B, A] = RadarImageData.data.slice(i,i+4);
+
+ // is this pixel the old rgb?
+ if ((R === 1 && G === 159 && B === 244)
+ || (R >= 200 && G >= 200 && B >= 200)
+ || (R === 4 && G === 233 && B === 231)
+ || (R === 3 && G === 0 && B === 244)) {
+ // Transparent
+ R = 0;
+ G = 0;
+ B = 0;
+ A = 0;
+ } else if (R === 2 && G === 253 && B === 2) {
+ // Light Green 1
+ R = 49;
+ G = 210;
+ B = 22;
+ A = 255;
+ } else if (R === 1 && G === 197 && B === 1) {
+ // Light Green 2
+ R = 0;
+ G = 142;
+ B = 0;
+ A = 255;
+ } else if (R === 0 && G === 142 && B === 0) {
+ // Dark Green 1
+ R = 20;
+ G = 90;
+ B = 15;
+ A = 255;
+ } else if (R === 253 && G === 248 && B === 2) {
+ // Dark Green 2
+ R = 10;
+ G = 40;
+ B = 10;
+ A = 255;
+ } else if (R === 229 && G === 188 && B === 0) {
+ // Yellow
+ R = 196;
+ G = 179;
+ B = 70;
+ A = 255;
+ } else if (R === 253 && G === 139 && B === 0) {
+ // Orange
+ R = 190;
+ G = 72;
+ B = 19;
+ A = 255;
+ } else if (R === 212 && G === 0 && B === 0) {
+ // Red
+ R = 171;
+ G = 14;
+ B = 14;
+ A = 255;
+ } else if (R === 188 && G === 0 && B === 0) {
+ // Brown
+ R = 115;
+ G = 31;
+ B = 4;
+ A = 255;
+ }
+
+ // store new values
+ RadarImageData.data[i] = R;
+ RadarImageData.data[i + 1] = G;
+ RadarImageData.data[i + 2] = B;
+ RadarImageData.data[i + 3] = A;
+ }
+
+ // rewrite the image
+ RadarContext.putImageData(RadarImageData, 0, 0);
+ }
+}
\ No newline at end of file
diff --git a/server/scripts/twc3.js b/server/scripts/twc3.js
index cfc3ef6..bbe3afc 100644
--- a/server/scripts/twc3.js
+++ b/server/scripts/twc3.js
@@ -2882,51 +2882,9 @@ const PopulateAlmanacInfo = async (WeatherParameters) => {
-const ShowRegionalMap = async (WeatherParameters, TomorrowForecast1, TomorrowForecast2) => {
-};
-
-
-
-
-
-const GetXYFromLatitudeLongitudeDoppler = (Latitude, Longitude, OffsetX, OffsetY) => {
- let y = 0;
- let x = 0;
- const ImgHeight = 3200;
- const ImgWidth = 5100;
-
- y = (51.75 - Latitude) * 55.2;
- y -= OffsetY; // Centers map.
- // Do not allow the map to exceed the max/min coordinates.
- if (y > (ImgHeight - (OffsetY * 2))) {
- y = ImgHeight - (OffsetY * 2);
- } else if (y < 0) {
- y = 0;
- }
-
- x = ((-130.37 - Longitude) * 41.775) * -1;
- x -= OffsetX; // Centers map.
- // Do not allow the map to exceed the max/min coordinates.
- if (x > (ImgWidth - (OffsetX * 2))) {
- x = ImgWidth - (OffsetX * 2);
- } else if (x < 0) {
- x = 0;
- }
-
- return { x: x * 2, y: y * 2 };
-};
-
-
-
-
-
const ShowDopplerMap = async (WeatherParameters) => {
- // ALASKA ISN'T SUPPORTED!
- if (WeatherParameters.State === 'AK') {
- WeatherParameters.Progress.DopplerRadar = LoadStatuses.NoData;
- return;
- }
+
let OffsetY;
let OffsetX;
@@ -2939,11 +2897,6 @@ const ShowDopplerMap = async (WeatherParameters) => {
// Clear the current image.
divDopplerRadarMap.empty();
- if (_DopplerRadarInterval !== null) {
- window.clearTimeout(_DopplerRadarInterval);
- _DopplerRadarInterval = null;
- }
-
let src = 'images/4000RadarMap2.jpg';
if (WeatherParameters.State === 'HI') src = 'images/HawaiiRadarMap2.png';
const img = await utils.loadImg(src);
diff --git a/views/twc3.ejs b/views/twc3.ejs
index 758e687..66a08a2 100644
--- a/views/twc3.ejs
+++ b/views/twc3.ejs
@@ -27,6 +27,7 @@
+
<% } %>