From 378de8b4e40e5f5d37c5781149125f3234037050 Mon Sep 17 00:00:00 2001 From: Martin Donnelly Date: Mon, 3 Feb 2020 14:45:22 +0000 Subject: [PATCH] Added weather stuff and start of server tidyup --- .eslintrc.json | 55 +++++++++++++ .gitignore | 152 +++++++++++++++++++++++++++++++++++ .ignore | 152 +++++++++++++++++++++++++++++++++++ newapp.js | 79 ++++++++++-------- package.json | 30 ++++--- routes/time.js | 3 + routes/trains.js | 0 server/lib/geocode.js | 96 ++++++++++++++++++++++ server/lib/weather.js | 118 +++++++++++++++++++++++++++ server/reducers/opencage.js | 62 +++++++++++++++ server/reducers/weather.js | 154 ++++++++++++++++++++++++++++++++++++ server/routes/weather.js | 97 +++++++++++++++++++++++ 12 files changed, 952 insertions(+), 46 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .ignore create mode 100644 routes/trains.js create mode 100644 server/lib/geocode.js create mode 100644 server/lib/weather.js create mode 100644 server/reducers/opencage.js create mode 100644 server/reducers/weather.js create mode 100644 server/routes/weather.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..7042266 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,55 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": false + } + }, + "env": { + "browser": true, + "node": true, + "es6": true + }, + "rules": { + "arrow-spacing": "error", + "block-scoped-var": "error", + "block-spacing": "error", + "brace-style": ["error", "stroustrup", {}], + "camelcase": "error", + "comma-dangle": ["error", "never"], + "comma-spacing": ["error", { "before": false, "after": true }], + "comma-style": [1, "last"], + "consistent-this": [1, "_this"], + "curly": [1, "multi"], + "eol-last": 1, + "eqeqeq": 1, + "func-names": 1, + "indent": ["error", 2, { "SwitchCase": 1 }], + "lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }], + "max-len": [1, 120, 2], // 2 spaces per tab, max 80 chars per line + "new-cap": 1, + "newline-before-return": "error", + "no-array-constructor": 1, + "no-inner-declarations": [1, "both"], + "no-mixed-spaces-and-tabs": 1, + "no-multi-spaces": 2, + "no-new-object": 1, + "no-shadow-restricted-names": 1, + "object-curly-spacing": ["error", "always"], + "padded-blocks": ["error", { "blocks": "never", "switches": "always" }], + "prefer-const": "error", + "prefer-template": "error", + "one-var": 0, + "quote-props": ["error", "always"], + "quotes": [1, "single"], + "radix": 1, + "semi": [1, "always"], + "space-before-blocks": [1, "always"], + "space-infix-ops": 1, + "vars-on-top": 1, + "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }], + "spaced-comment": ["error", "always", { "markers": ["/"] }] + } + +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b35207a --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml +/src/bundle.js +/src/bundle.js.map +/src/react/bundle.js +/src/backbone/bundle.js +/src/react/bundle.js.map +/src/es2016/bundle.js +/src/es2016/bundle.js.map +/src/backbone/bundle.js.map diff --git a/.ignore b/.ignore new file mode 100644 index 0000000..b35207a --- /dev/null +++ b/.ignore @@ -0,0 +1,152 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml +/src/bundle.js +/src/bundle.js.map +/src/react/bundle.js +/src/backbone/bundle.js +/src/react/bundle.js.map +/src/es2016/bundle.js +/src/es2016/bundle.js.map +/src/backbone/bundle.js.map diff --git a/newapp.js b/newapp.js index 09d711f..b61bac7 100644 --- a/newapp.js +++ b/newapp.js @@ -1,47 +1,56 @@ -var express = require('express'), path = require('path'), http = require('http'), +require('dotenv').config(); +const express = require('express'); +const path = require('path'); +const http = require('http'); +const favicon = require('serve-favicon'); +const logger = require('morgan'); +const cookieParser = require('cookie-parser'); +const bodyParser = require('body-parser'); +const routes = require('./routes/index'); +const users = require('./routes/users'); +const timeroute = require('./routes/time'); +const btcroute = require('./routes/btc'); +const temproute = require('./routes/temp'); +const weightroute = require('./routes/weight'); +const weatherRoute = require('./server/routes/weather'); - favicon = require('serve-favicon'), - logger = require('morgan'), - cookieParser = require('cookie-parser'), - bodyParser = require('body-parser'), +const app = express(); +GLOBAL.lastcheck = { 'btc': 0, 'fx': 0 }; - routes = require('./routes/index'), - users = require('./routes/users'), +app.set('port', process.env.PORT || 8030); +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'ejs'); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ 'extended': true })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); - timeroute = require('./routes/time'), - btcroute = require('./routes/btc'), - temproute = require('./routes/temp'), - weightroute = require('./routes/weight') -//train = require('lib/train') -/* ,submit = require('./routes/mongo/submit') */ - ; -var app = express(); -GLOBAL.lastcheck = {"btc": 0, "fx": 0}; +app.use('/', routes); +// app.use('/users', users); +app.use('/time', timeroute); +app.use('/btc', btcroute); +app.use('/temp', temproute); +app.use('/weight', weightroute); +app.use('/weather', weatherRoute); - app.set('port', process.env.PORT || 8030); - app.set('views', path.join(__dirname, 'views')); - app.set('view engine', 'ejs'); - app.use(logger('dev')); - app.use(bodyParser.json()); - app.use(bodyParser.urlencoded({ extended: true })); - app.use(cookieParser()); - app.use(express.static(path.join(__dirname, 'public'))); - - app.use('/', routes); -//app.use('/users', users); - - app.use('/time', timeroute); - app.use('/btc', btcroute); - app.use('/temp',temproute); - app.use('/weight',weightroute); +// Handle 404 +app.use((req, res) => { + res.status(404).send('404: Page not Found'); +}); +// Handle 500 +app.use((error, req, res, next) => { + console.log(error); + res.status(500).send('500: Internal Server Error'); +}); /** * create the server */ -app.listen(app.get('port'), function () { - console.log('APIServer listening on ' + app.get('port')); -}); \ No newline at end of file +app.listen(app.get('port'), function() { + console.log(`APIServer listening on ${ app.get('port')}`); +}); diff --git a/package.json b/package.json index ea684fb..8e99efc 100644 --- a/package.json +++ b/package.json @@ -6,19 +6,27 @@ "start": "node ./bin/www" }, "dependencies": { - "body-parser": "~1.13.2", - "cookie-parser": "~1.3.5", - "debug": "~2.2.0", - "ejs": "~2.3.3", - "events": "^1.1.0", - "express": "~4.13.1", - "morgan": "~1.6.1", - "serve-favicon": "~2.3.0", + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.4", + "debug": "^4.1.1", + "dotenv": "^8.2.0", + "ejs": "^2.7.1", + "eslint": "^6.8.0", + "events": "^3.0.0", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "fecha": "^4.0.0", + "lodash": "^4.17.15", + "morgan": "^1.9.1", + "node-geocoder": "^3.25.0", + "openweather-apis": "^4.1.0", + "request-json": "^0.6.4", + "serve-favicon": "^2.5.0", "websocket": "^1.0.22", - "ws": "^1.0.1" + "ws": "^7.1.2" }, "devDependencies": { - "log4js": "^0.6.31", - "sqlite3": "~3.1.1" + "log4js": "^5.2.1", + "sqlite3": "^4.1.0" } } diff --git a/routes/time.js b/routes/time.js index d2215c3..4322b1c 100644 --- a/routes/time.js +++ b/routes/time.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); +var debug = require('debug')('silvrapi:time'); /* GET users listing. */ router.get('/', function(req, res, next) { @@ -20,6 +21,8 @@ router.get('/', function(req, res, next) { var t = {"zulu":now, "utc":now.toUTCString(),"date":date, "time":time}; + debug(t); + res.writeHead(200, {"ContentType": "application/json"}); //res.send(JSON.stringify(t)); res.end(JSON.stringify(t)); diff --git a/routes/trains.js b/routes/trains.js new file mode 100644 index 0000000..e69de29 diff --git a/server/lib/geocode.js b/server/lib/geocode.js new file mode 100644 index 0000000..b009ff5 --- /dev/null +++ b/server/lib/geocode.js @@ -0,0 +1,96 @@ +const NodeGeocoder = require('node-geocoder'); +const logger = require('log4js').getLogger('GeoCode'); + +const { reduceOpencage } = require('../reducers/opencage'); +logger.level = 'debug'; + +const options = { + 'provider': 'opencage', + + // Optional depending on the providers + 'httpAdapter': 'https', // Default + 'apiKey': '893ab539eca84b5ca7a54cb03ef23443', // for Mapquest, OpenCage, Google Premier + 'formatter': null // 'gpx', 'string', ... +}; + +const geocoder = NodeGeocoder(options); + +function doGetGeocode(ll) { + return new Promise((resolve, reject) => { + const [lat, lon ] = ll.split(','); + + const latlong = { lat, lon }; + + logger.debug(latlong); + + geocoder.reverse(latlong) + .then(function(res) { + if (res.hasOwnProperty('raw')) { + const result = reduceOpencage(res.raw); + + return resolve(result[0]); + } + else + return resolve(res[0]); + }) + .catch(function(err) { + logger.error(err); + + return reject(err); + }); + }); +} + +module.exports = { doGetGeocode }; + +/* + +opencage + +{ + "latitude": 51.508751, + "longitude": -0.067457, + "country": "United Kingdom", + "city": "London", + "state": "England", + "zipcode": "SE15", + "streetName": "Vaughan Way", + "countryCode": "gb", + "suburb": "St.George in the East", + "extra": { + "flag": "🇬🇧", + "confidence": 9, + "confidenceKM": 0.5, + "map": "https://www.openstreetmap.org/?mlat=51.50875&mlon=-0.06746#map=17/51.50875/-0.06746" + } +} + +google + +[ + { + "administrativeLevels": { + "level1long": "England", + "level1short": "England", + "level2long": "Northamptonshire", + "level2short": "Northamptonshire" + }, + "city": "Northampton", + "country": "United Kingdom", + "countryCode": "GB", + "extra": { + "confidence": 0.7, + "establishment": "Daventy depot", + "googlePlaceId": "ChIJI8H0WFUVd0gRIIFzNwDQAuM", + "neighborhood": "Kilsby", + "premise": null, + "subpremise": null + }, + "formattedAddress": "Daventy depot, Kilsby, Northampton NN6 7GY, UK", + "latitude": 52.3546726, + "longitude": -1.1741823, + "provider": "google", + "zipcode": "NN6 7GY" + } +] + */ diff --git a/server/lib/weather.js b/server/lib/weather.js new file mode 100644 index 0000000..eb94123 --- /dev/null +++ b/server/lib/weather.js @@ -0,0 +1,118 @@ +const Client = require('request-json'); + +const logger = require('log4js').getLogger('Weather'); +const weather = require('openweather-apis'); +const { reduceWeather } = require('../reducers/weather'); +const request = require('request'); + +logger.level = 'debug'; +logger.label = 'Weather'; + +const openWeatherApiKey = process.env.openweatherAPI || ''; + +const darkskyApiKey = process.env.darkskyApiKey || ''; + +const dsURL = `https://api.darksky.net/forecast/${ darkskyApiKey }/`; +const DSclient = Client.createClient(`https://api.darksky.net/forecast/${ darkskyApiKey }/`); + +weather.setAPPID(openWeatherApiKey); +weather.setLang('en'); +// weather.setCity('Glasgow City'); + +function doGetOpenWeather(ll) { + const [lat, long ] = ll.split(','); + + return new Promise((resolve, reject) => { + weather.setCoordinate(lat, long); + weather.getWeatherForecast( function(err, wData) { + if (err) + return reject(err); + else + return resolve(wData); + }); + }); +} + +function doGetOpenWeatherForecast(ll) { + const [lat, long ] = ll.split(','); + + return new Promise((resolve, reject) => { + weather.setCoordinate(lat, long); + weather.getWeatherForecastForDays(5, function(err, wData) { + if (err) + return reject(err); + else + return resolve(wData); + }); + }); +} + +function doGetDarkSkyWeather(ll) { + const query = `${ll}?units=uk2&exclude=daily,flags,minutely,hourly`; + logger.debug(`https://api.darksky.net/forecast/${ darkskyApiKey }/${query}`); + + return new Promise((resolve, reject) => { + DSclient.get(query, function(err, res, body) { + if (err || !body || !body.currently) + return reject(err); + + return resolve(body); + }); + }); +} + +function doGetFullForcast(ll) { + const query = `${ll}?units=uk2&exclude=flags,minutely`; + logger.debug('doGetFullForcast'); + logger.debug(query); + return new Promise((resolve, reject) => { + DSclient.get(query, function(err, res, body) { + if (err || !body || !body.currently) + return reject(err); + + const output = reduceWeather(body); + + return resolve(output); + }); + }); +} + +function doGetFullForcastV2(ll) { + const query = `${ll}?units=uk2&exclude=flags,minutely`; + logger.debug('doGetFullForcastV2'); + const url = `${dsURL}${query}`; + + logger.debug(url); + + return new Promise(function(resolve, reject) { + request.get({ 'url':url }, function(err, resp, body) { + if (err) { + logger.error(err); + + return reject(err); + } + // Logger.error(err); + // return reject(err); + // Throw err; + + console.log; + const output = reduceWeather(body); + output.fullBody = JSON.parse(body); + output.timestamp = new Date().getTime(); + + console.log(output); + + return resolve(output); + }, function(error, response, body) { + console.log(response); + if (response.statusCode !== 200) { + logger.error(response.statusCode); + logger.error(body); + + return reject(error); + } + }); + }); +} + +module.exports = { doGetOpenWeather, doGetOpenWeatherForecast, doGetDarkSkyWeather, doGetFullForcast, doGetFullForcastV2 }; diff --git a/server/reducers/opencage.js b/server/reducers/opencage.js new file mode 100644 index 0000000..d72d8d2 --- /dev/null +++ b/server/reducers/opencage.js @@ -0,0 +1,62 @@ +const logger = require('log4js').getLogger('GeoCode 🔧'); + +const { get, isEmpty, has, uniq } = require('lodash'); + +logger.level = 'debug'; + +var ConfidenceInKM = { + '10': 0.25, + '9': 0.5, + '8': 1, + '7': 5, + '6': 7.5, + '5': 10, + '4': 15, + '3': 20, + '2': 25, + '1': Number.POSITIVE_INFINITY, + '0': Number.NaN +}; + +function formatResult (result) { + var confidence = result.confidence || 0; + + return { + 'latitude': result.geometry.lat, + 'longitude': result.geometry.lng, + 'country': result.components.country, + 'city': result.components.city, + 'town': result.components.town, + 'state': result.components.state, + 'zipcode': result.components.postcode, + 'streetName': result.components.road, + 'streetNumber': result.components.house_number, + 'countryCode': result.components.country_code, + 'county': result.components.county, + 'suburb': result.components.suburb, + 'neighbourhood' : result.components.neighbourhood, + 'village' : result.components.village, + 'formatted' : result.formatted, + 'target' : result.components.village || result.components.town || result.components.city, + 'extra': { + 'flag' : result.annotations.flag, + 'confidence': confidence, + 'confidenceKM': ConfidenceInKM[result.confidence] || Number.NaN, + 'map' : result.annotations.OSM.url + + } + }; +} + +function reduceOpencage(result) { + logger.debug(JSON.stringify(result)); + const results = []; + + if (result && result.results instanceof Array) + for (let i = 0; i < result.results.length; i++) + results.push(formatResult(result.results[i])); + + return results; +} + +module.exports = { reduceOpencage }; diff --git a/server/reducers/weather.js b/server/reducers/weather.js new file mode 100644 index 0000000..4cd82a2 --- /dev/null +++ b/server/reducers/weather.js @@ -0,0 +1,154 @@ +const logger = require('log4js').getLogger('Weather 🔧'); +const fecha = require('fecha'); + +const { get } = require('lodash'); + +logger.level = 'debug'; + +function getTodaysForcast(hourlyData) { + const data = get(hourlyData, 'data', []); + + const output = []; + const now = new Date().getTime(); + const nowToHour = now - (now % 3600000); + const tomorrow = nowToHour + (60 * 1000 * 60 * 24); + + for (const item of data) { + const time = item.time * 1000; + if (!(time < nowToHour || time > tomorrow)) { + // logger.debug(item); + const newItem = { 'time': fecha.format(time, 'h A'), 'icon': item.icon, 'temp': item.temperature, 'precip': item.precipProbability }; + output.push(newItem); + } + } + + return output; +} + +function getDailyForcast(dailyData) { + const data = get(dailyData, 'data', []); + + const output = []; + + const h24 = (60 * 1000 * 60 * 24); + const now = new Date().getTime(); + + const startODBase = new Date(); + startODBase.setHours(0, 0, 0, 0); + + const startOD = startODBase.getTime(); + const endOD = startOD + h24 - 1; + + logger.debug('startOD', fecha.format(new Date(startOD), 'default')); + logger.debug('endOD', fecha.format(new Date(endOD), 'default')); + + for (const item of data) { + const time = item.time * 1000; + if (!(time < endOD)) { + // logger.debug(item); + const newItem = { + 'time': fecha.format(time, 'dddd'), + 'icon': item.icon, + 'tempHigh': item.temperatureHigh, + 'tempLow': item.temperatureLow, + 'precip': item.precipProbability, + 'precipType': item.precipType + }; + output.push(newItem); + } + } + + return output; +} + +function toCompass(degrees) { + return ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'][Math.round(degrees / 11.25 / 2)]; +} + +function moonCalc(moonPhase) { + let output = ''; + if (moonPhase === 0.0) + output = '🌕 New moon'; + else if (moonPhase >= 0.1 && moonPhase < 0.25) + output = '🌔 waxing crescent'; + else if (moonPhase === 0.25) + output = '🌓 First Quarter'; + else if (moonPhase > 0.25 && moonPhase < 0.5) + output = '🌒 waxing gibbous'; + else if (moonPhase === 0.5) + output = '🌑 Full moon'; + else if (moonPhase > 0.5 && moonPhase < 0.75) + output = '🌘 Waning gibbous'; + else if (moonPhase === 0.5) + output = '🌗 Third quarter'; + else if (moonPhase > 0.75) + output = '🌖 Waning crescent'; + + return output; + // ['🌑', '🌘', '🌗', '🌖', '🌕', '🌔', '🌓', '🌒'] + // a value of 0 corresponds to a new moon, 0.25 to a first quarter moon, 0.5 to a full moon, and 0.75 to a last quarter moon. (The ranges + // in between these represent waxing crescent, waxing gibbous, waning gibbous, and waning crescent moons, respectively.) +} +function getDetails(dailyData) { + const data = get(dailyData, 'data', []); + + const today = data[0]; + + const output = {}; + + output.summary = dailyData.summary; + output.icon = dailyData.icon; + output.humidity = today.humidity; + output.visibility = today.visibility; + output.uvIndex = today.uvIndex; + output.sunriseTime = fecha.format(today.sunriseTime * 1000, 'shortTime'); + output.sunsetTime = fecha.format(today.sunsetTime * 1000, 'shortTime'); + output.moonphase = moonCalc(today.moonPhase); + output.moonPhaseVal = today.moonPhase; + output.windSpeed = today.windSpeed; + output.pressure = today.pressure; + + return output; +} + +function reduceWeather(body = '') { + if (body === '') return {}; + const obj = {}; + const { currently, daily, hourly } = body; + const today = daily.data[0]; + + const outCurrent = {}; + + outCurrent.icon = get(currently, 'icon'); + outCurrent.temperature = get(currently, 'temperature'); + outCurrent.summary = get(currently, 'summary'); + outCurrent.precip = get(currently, 'precipProbability'); + outCurrent.precipType = get(currently, 'precipType'); + outCurrent.tempMax = get(today, 'temperatureMax'); + outCurrent.tempMin = get(today, 'temperatureMin'); + outCurrent.windBearing = get(today, 'windBearing'); + outCurrent.windBearingRead = toCompass(get(today, 'windBearing')); + + const forcastToday = getTodaysForcast(hourly); + const dailyForecast = getDailyForcast(daily); + const details = getDetails(daily); + + obj.currently = outCurrent; + obj.forcastToday = forcastToday; + obj.dailyForecast = dailyForecast; + obj.details = details; + obj.time = get(currently, 'time'); + + return obj; +} + +module.exports = { reduceWeather }; + +// +/* +moonPhase optional, only on daily +The fractional part of the lunation number during the given day: a value of 0 corresponds to a new moon, 0.25 to a first quarter +moon, 0.5 to a full moon, and 0.75 to a last quarter moon. (The ranges in between these represent waxing crescent, waxing gibbous, +waning gibbous, and waning crescent moons, respectively.) + + */ diff --git a/server/routes/weather.js b/server/routes/weather.js new file mode 100644 index 0000000..6683473 --- /dev/null +++ b/server/routes/weather.js @@ -0,0 +1,97 @@ +var router = require('express-promise-router')(); + +const weather = require('../lib/weather'); + +const logger = require('log4js').getLogger('simpleWeather'); +logger.level = 'debug'; + +/* + + OpenWeather: + /ow/forecast + /ow/fullforecast + + DarkSkies + /ds/forecast + /ds/fullforecast + + + */ + +router.get('/ow/forecast', async (req, res, next) => { + if (req.query.hasOwnProperty('ll')) { + const ll = req.query.ll; + + const weatherData = await weather.doGetOpenWeather(ll); + + res.send(weatherData); + } + + else { + // throw new Error('Weather: LL missing'); + logger.warn('FS: LL missing'); + res.status(500).send('LL Missing'); + } + + +}); + + +router.get('/ow/fullforecast', async (req, res, next) => { + if (req.query.hasOwnProperty('ll')) { + const ll = req.query.ll; + + const weatherData = await weather.doGetOpenWeatherForecast(ll); + + res.send(weatherData); + } + + else { + // throw new Error('Weather: LL missing'); + logger.warn('FS: LL missing'); + res.status(500).send('LL Missing'); + } + + +}); + + +router.get('/ds/forecast', async (req, res, next) => { + if (req.query.hasOwnProperty('ll')) { + const ll = req.query.ll; + + const weatherData = await weather.doGetDarkSkyWeather(ll); + + res.send(weatherData); + } + + else { + // throw new Error('Weather: LL missing'); + logger.warn('FS: LL missing'); + res.status(500).send('LL Missing'); + } + + +}); + + +router.get('/ds/fullforecast', async (req, res, next) => { + if (req.query.hasOwnProperty('ll')) { + const ll = req.query.ll; + + const weatherData = await weather.doGetFullForcast(ll); + + res.send(weatherData); + } + + else { + // throw new Error('Weather: LL missing'); + logger.warn('FS: LL missing'); + res.status(500).send('LL Missing'); + } + + +}); + + +module.exports = router;