fix regional forecast and observations
This commit is contained in:
parent
703d64f6b2
commit
94fafc247a
File diff suppressed because it is too large
Load diff
17236
datagenerators/output/stations.json
Normal file
17236
datagenerators/output/stations.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,24 +4,35 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const https = require('./https');
|
const https = require('./https');
|
||||||
|
const states = require('./stations-states');
|
||||||
|
const chunk = require('./chunk');
|
||||||
|
|
||||||
// immediately invoked function so we can access async/await
|
// immediately invoked function so we can access async/await
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
// load the list of states
|
// chunk the list of states
|
||||||
const states = ['AK', 'NC', 'VA', 'TX', 'GA', 'PR'];
|
const chunkStates = chunk(states, 5);
|
||||||
// const states = require('./stations-states.js');
|
|
||||||
|
|
||||||
|
// store output
|
||||||
const output = {};
|
const output = {};
|
||||||
|
|
||||||
|
// process all chunks
|
||||||
|
for (let i = 0; i < chunkStates.length; i += 1) {
|
||||||
|
const stateChunk = chunkStates[i];
|
||||||
// loop through states
|
// loop through states
|
||||||
await Promise.all(states.map(async (state) => {
|
|
||||||
|
stateChunk.forEach(async (state) => {
|
||||||
try {
|
try {
|
||||||
|
let stations;
|
||||||
|
let next = `https://api.weather.gov/stations?state=${state}`;
|
||||||
|
do {
|
||||||
// get list and parse the JSON
|
// get list and parse the JSON
|
||||||
const stationsRaw = await https(`https://api.weather.gov/stations?state=${state}`);
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const stationsAll = JSON.parse(stationsRaw).features;
|
const stationsRaw = await https(next);
|
||||||
|
stations = JSON.parse(stationsRaw);
|
||||||
// filter stations for 4 letter identifiers
|
// filter stations for 4 letter identifiers
|
||||||
const stations = stationsAll.filter((station) => station.properties.stationIdentifier.match(/^[A-Z]{4}$/));
|
const stationsFiltered = stations.features.filter((station) => station.properties.stationIdentifier.match(/^[A-Z]{4}$/));
|
||||||
// add each resulting station to the output
|
// add each resulting station to the output
|
||||||
stations.forEach((station) => {
|
stationsFiltered.forEach((station) => {
|
||||||
const id = station.properties.stationIdentifier;
|
const id = station.properties.stationIdentifier;
|
||||||
if (output[id]) {
|
if (output[id]) {
|
||||||
console.log(`Duplicate station: ${state}-${id}`);
|
console.log(`Duplicate station: ${state}-${id}`);
|
||||||
|
@ -35,12 +46,19 @@ const start = async () => {
|
||||||
lon: station.geometry.coordinates[0],
|
lon: station.geometry.coordinates[0],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
next = stations?.pagination?.next;
|
||||||
|
// write the output
|
||||||
|
fs.writeFileSync(path.join(__dirname, 'output/stations.json'), JSON.stringify(output, null, 2));
|
||||||
|
}
|
||||||
|
while (next && stations.features.length > 0);
|
||||||
console.log(`Complete: ${state}`);
|
console.log(`Complete: ${state}`);
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Unable to get state: ${state}`);
|
console.error(`Unable to get state: ${state}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// write the output
|
// write the output
|
||||||
fs.writeFileSync(path.join(__dirname, 'output/stations.js'), JSON.stringify(output, null, 2));
|
fs.writeFileSync(path.join(__dirname, 'output/stations.js'), JSON.stringify(output, null, 2));
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ const buildForecast = (forecast, city, cityXY) => ({
|
||||||
const getRegionalObservation = async (point, city) => {
|
const getRegionalObservation = async (point, city) => {
|
||||||
try {
|
try {
|
||||||
// get stations
|
// get stations
|
||||||
const stations = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/stations`);
|
const stations = await json(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/stations`);
|
||||||
|
|
||||||
// get the first station
|
// get the first station
|
||||||
const station = stations.features[0].id;
|
const station = stations.features[0].id;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { DateTime } from '../vendor/auto/luxon.mjs';
|
||||||
import WeatherDisplay from './weatherdisplay.mjs';
|
import WeatherDisplay from './weatherdisplay.mjs';
|
||||||
import { registerDisplay } from './navigation.mjs';
|
import { registerDisplay } from './navigation.mjs';
|
||||||
import * as utils from './regionalforecast-utils.mjs';
|
import * as utils from './regionalforecast-utils.mjs';
|
||||||
|
import { getPoint } from './utils/weather.mjs';
|
||||||
|
|
||||||
class RegionalForecast extends WeatherDisplay {
|
class RegionalForecast extends WeatherDisplay {
|
||||||
constructor(navId, elemId) {
|
constructor(navId, elemId) {
|
||||||
|
@ -73,12 +74,13 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
// get regional forecasts and observations (the two are intertwined due to the design of api.weather.gov)
|
||||||
const regionalDataAll = await Promise.all(regionalCities.map(async (city) => {
|
const regionalDataAll = await Promise.all(regionalCities.map(async (city) => {
|
||||||
try {
|
try {
|
||||||
if (!city.point) throw new Error('No pre-loaded point');
|
const point = city?.point ?? (await getAndFormatPoint(city.lat, city.lon));
|
||||||
|
if (!point) throw new Error('No pre-loaded point');
|
||||||
|
|
||||||
// start off the observation task
|
// start off the observation task
|
||||||
const observationPromise = utils.getRegionalObservation(city.point, city);
|
const observationPromise = utils.getRegionalObservation(point, city);
|
||||||
|
|
||||||
const forecast = await json(`https://api.weather.gov/gridpoints/${city.point.wfo}/${city.point.x},${city.point.y}/forecast`);
|
const forecast = await json(`https://api.weather.gov/gridpoints/${point.wfo}/${point.x},${point.y}/forecast`);
|
||||||
|
|
||||||
// get XY on map for city
|
// get XY on map for city
|
||||||
const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
const cityXY = utils.getXYForCity(city, minMaxLatLon.maxLat, minMaxLatLon.minLon, weatherParameters.state);
|
||||||
|
@ -192,5 +194,14 @@ class RegionalForecast extends WeatherDisplay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAndFormatPoint = async (lat, lon) => {
|
||||||
|
const point = await getPoint(lat, lon);
|
||||||
|
return {
|
||||||
|
x: point.properties.gridX,
|
||||||
|
y: point.properties.gridY,
|
||||||
|
wfo: point.properties.gridId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// register display
|
// register display
|
||||||
registerDisplay(new RegionalForecast(5, 'regional-forecast'));
|
registerDisplay(new RegionalForecast(5, 'regional-forecast'));
|
||||||
|
|
196
server/scripts/vendor/jeoquery.js
vendored
196
server/scripts/vendor/jeoquery.js
vendored
|
@ -1,196 +0,0 @@
|
||||||
/*
|
|
||||||
* jeoQuery v0.5.1
|
|
||||||
*
|
|
||||||
* Copyright 2012-2038, Thomas Haukland
|
|
||||||
* MIT license.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var jeoquery = (function ($) {
|
|
||||||
var my = {};
|
|
||||||
|
|
||||||
my.defaultData = {
|
|
||||||
userName: 'vbguyny',
|
|
||||||
lang: 'en'
|
|
||||||
};
|
|
||||||
my.defaultCountryCode = 'US';
|
|
||||||
my.defaultLanguage = 'en';
|
|
||||||
my.geoNamesApiServer = 'api.geonames.org';
|
|
||||||
my.geoNamesProtocol = 'http';
|
|
||||||
|
|
||||||
my.featureClass = {
|
|
||||||
AdministrativeBoundary: 'A',
|
|
||||||
Hydrographic: 'H',
|
|
||||||
Area: 'L',
|
|
||||||
PopulatedPlace: 'P',
|
|
||||||
RoadRailroad: 'R',
|
|
||||||
Spot: 'S',
|
|
||||||
Hypsographic: 'T',
|
|
||||||
Undersea: 'U',
|
|
||||||
Vegetation: 'V'
|
|
||||||
};
|
|
||||||
|
|
||||||
my.getGeoNames = function(method, data, callback, errorcallback) {
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
if (!method || !methods[method]) {
|
|
||||||
throw 'Invalid geonames method "' + method + '".';
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: my.geoNamesProtocol + '://' + my.geoNamesApiServer + '/' + method + 'JSON',
|
|
||||||
dataType: 'jsonp',
|
|
||||||
data: $.extend({}, my.defaultData, data),
|
|
||||||
// GeoNames expects "traditional" param serializing
|
|
||||||
traditional: true,
|
|
||||||
success: function(data) {
|
|
||||||
deferred.resolve(data);
|
|
||||||
if (!!callback) callback(data);
|
|
||||||
},
|
|
||||||
error: function (xhr, textStatus) {
|
|
||||||
deferred.reject(xhr, textStatus);
|
|
||||||
//alert('Ooops, geonames server returned: ' + textStatus);
|
|
||||||
if (!!errorcallback) errorcallback(textStatus);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
function formatDate(date) {
|
|
||||||
var dateQs = '';
|
|
||||||
if (date) {
|
|
||||||
dateQs = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
|
|
||||||
}
|
|
||||||
return dateQs;
|
|
||||||
}
|
|
||||||
|
|
||||||
var methods = {
|
|
||||||
astergdem: {params: ['lat', 'lng'] },
|
|
||||||
children: {params: ['geonameId', 'maxRows'] },
|
|
||||||
cities: {params: ['north', 'south', 'east', 'west', 'lang'] },
|
|
||||||
countryCode: {params: ['lat', 'lng', 'type', 'lang', 'radius'] },
|
|
||||||
countryInfo: {params: ['country', 'lang'] },
|
|
||||||
countrySubdivision: {params: ['lat', 'lng', 'level', 'lang', 'radius'] },
|
|
||||||
earthquakes: {params: ['north', 'south', 'east', 'west', 'date', 'maxRows', 'minMagnitude'] },
|
|
||||||
findNearby: {params: ['lat', 'lng', 'featureClass', 'featureCode', 'radius', 'style', 'maxRows'] },
|
|
||||||
findNearbyPlacename: {params: ['lat', 'lng', 'radius', 'style'] },
|
|
||||||
findNearbyPostalCodes: {params: ['lat', 'lng', 'radius', 'style', 'maxRows', 'country', 'localCountry', 'postalCode'] },
|
|
||||||
findNearbyStreets: {params: ['lat', 'lng', 'radius', 'maxRows'] },
|
|
||||||
findNearbyStreetsOSM: {params: ['lat', 'lng'] },
|
|
||||||
findNearbyWeather: {params: ['lat', 'lng'] },
|
|
||||||
findNearbyWikipedia: {params: ['lat', 'lng', 'radius', 'maxRows', 'country', 'postalCode'] },
|
|
||||||
findNearestAddress: {params: ['lat', 'lng'] },
|
|
||||||
findNearestIntersection: {params: ['lat', 'lng'] },
|
|
||||||
findNearestIntersectionOSM: {params: ['lat', 'lng', 'radius', 'maxRows'] },
|
|
||||||
findNearbyPOIsOSM: {params: ['lat', 'lng'] },
|
|
||||||
get: {params: ['geonameId', 'lang', 'style'] },
|
|
||||||
gtopo30: {params: ['lat', 'lng'] },
|
|
||||||
hierarchy: {params: ['geonameId'] },
|
|
||||||
neighbourhood: {params: ['lat', 'lng'] },
|
|
||||||
neighbours: {params: ['geonameId', 'country'] },
|
|
||||||
ocean: {params: ['lat', 'lng', 'radius'] },
|
|
||||||
postalCodeCountryInfo: {params: [] },
|
|
||||||
postalCodeLookup: {params: ['postalcode', 'country', 'maxRows', 'charset'] },
|
|
||||||
postalCodeSearch: {params: ['postalcode', 'postalcode_startsWith', 'placename_startsWith', 'country', 'countryBias', 'maxRows', 'style', 'operator', 'charset', 'isReduced'] },
|
|
||||||
search: {params: [ 'q', 'name', 'name_equals', 'name_startsWith', 'maxRows', 'startRow', 'country', 'countryBias', 'continentCode', 'adminCode1', 'adminCode2', 'adminCode3', 'featureClass', 'featureCode', 'lang', 'type', 'style', 'isNameRequired', 'tag', 'operator', 'charset', 'fuzzy'] },
|
|
||||||
siblings: {params: ['geonameId'] },
|
|
||||||
srtm3: {params: ['lat', 'lng'] },
|
|
||||||
timezone: {params: ['lat', 'lng', 'radius', 'date'] },
|
|
||||||
weather: {params: ['north', 'south', 'east', 'west', 'maxRows'] },
|
|
||||||
weatherIcao: {params: ['ICAO'] },
|
|
||||||
wikipediaBoundingBox: {params: ['north', 'south', 'east', 'west', 'lang', 'maxRows'] },
|
|
||||||
wikipediaSearch: {params: ['q', 'title', 'lang', 'maxRows'] }
|
|
||||||
};
|
|
||||||
|
|
||||||
return my;
|
|
||||||
}(jQuery));
|
|
||||||
|
|
||||||
(function ($) {
|
|
||||||
$.fn.jeoCountrySelect = function (options) {
|
|
||||||
var el = this;
|
|
||||||
$.when(jeoquery.getGeoNames('countryInfo'))
|
|
||||||
.then(function (data) {
|
|
||||||
var sortedNames = data.geonames;
|
|
||||||
if (data.geonames.sort) {
|
|
||||||
sortedNames = data.geonames.sort(function (a, b) {
|
|
||||||
return a.countryName.localeCompare(b.countryName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Insert blank choice
|
|
||||||
sortedNames.unshift({countryCode:'', countryName:''});
|
|
||||||
var html = $.map(sortedNames, function(c) {
|
|
||||||
return '<option value="' + c.countryCode + '">' + c.countryName + '</option>';
|
|
||||||
});
|
|
||||||
el.html(html);
|
|
||||||
if (options && options.callback) options.callback(sortedNames);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.jeoPostalCodeLookup = function (options) {
|
|
||||||
this.on("change", function () {
|
|
||||||
var code = $(this).val();
|
|
||||||
var country = options.country || jeoquery.defaultCountryCode;
|
|
||||||
if (options.countryInput) {
|
|
||||||
country = options.countryInput.val() || jeoquery.defaultCountry;
|
|
||||||
}
|
|
||||||
if (code) {
|
|
||||||
jeoquery.getGeoNames('postalCodeLookup', {postalcode: code, country: country}, function (data) {
|
|
||||||
if (data && data.postalcodes && data.postalcodes.length > 0) {
|
|
||||||
if (options) {
|
|
||||||
if (options.target) {
|
|
||||||
options.target.val(data.postalcodes[0].placeName);
|
|
||||||
}
|
|
||||||
if (options.callback) {
|
|
||||||
options.callback(data.postalcodes[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.jeoCityAutoComplete = function (options) {
|
|
||||||
this.autocomplete({
|
|
||||||
source: function (request, response) {
|
|
||||||
jeoquery.getGeoNames('search', {
|
|
||||||
featureClass: jeoquery.featureClass.PopulatedPlace,
|
|
||||||
style: ((options && options.style) ? options.style : "medium"),
|
|
||||||
maxRows: 12,
|
|
||||||
name_startsWith: request.term
|
|
||||||
}, function (data) {
|
|
||||||
response(function() {
|
|
||||||
data.geonames = $.map(data.geonames, function (item) {
|
|
||||||
var displayName = item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName;
|
|
||||||
if (options && options.displayNameFunc) {
|
|
||||||
displayName = options.displayNameFunc(item);
|
|
||||||
if (displayName === null)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
label: displayName,
|
|
||||||
value: displayName,
|
|
||||||
details: item
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (options && options.preProcessResults) {
|
|
||||||
options.preProcessResults(data.geonames);
|
|
||||||
}
|
|
||||||
return data.geonames;
|
|
||||||
}());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minLength: 2,
|
|
||||||
select: function( event, ui ) {
|
|
||||||
if (ui && ui.item && options && options.callback) {
|
|
||||||
options.callback(ui.item.details);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
open: function () {
|
|
||||||
$(this).removeClass("ui-corner-all").addClass("ui-corner-top");
|
|
||||||
},
|
|
||||||
close: function () {
|
|
||||||
$(this).removeClass("ui-corner-top").addClass("ui-corner-all");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
Loading…
Reference in a new issue