Socket
Socket
Sign inDemoInstall

yr.no-forecast

Package Overview
Dependencies
74
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.1 to 2.0.0

16

CHANGELOG.md

@@ -5,4 +5,16 @@ # CHANGELOG

## 2.0.0 - 24/05/17
* Increase performance by approximately 10x due to use of `pixl-xml` and
improved algorithm for getting weather nodes.
* Simplify codebase.
* Update JSON output format to be closer to the original XML content. This gives
users of this module more flexibility and information but is a breaking change.
* Add `getValidTimes` function.
* Fix security `request` and `qs` module vulnerabilities by updating to `yr.no-interface@1.0.1`.
# 1.0.0 26/04/2017
## 1.0.1 - 28/04/2017
* Mitigate security vulnerabilities.
* Use new yr.no-interface@1.0.0 internally.
## 1.0.0 - 27/04/2017
* Change to Promise based interface.

@@ -17,3 +29,3 @@ * Make the module a factory function.

# < 1.0.0
## < 1.0.0
* Undocumented. Sorry.

331

index.js

@@ -7,6 +7,33 @@ 'use strict';

const VError = require('verror');
const filter = require('lodash.filter');
const each = require('lodash.foreach');
const Promise = require('bluebird');
/**
* "simple" nodes are those with very basic detail. 1 to 4 of these follow a
* node with more details
* @param {Object} node
* @return {Boolean}
*/
function isSimpleNode (node) {
return node.location.symbol !== undefined;
}
/**
* Check if a node has a min and max temp range
* @param {Object} node
* @return {Boolean}
*/
function hasTemperatureRange (node) {
return node.location.minTemperature && node.location.maxTemperature;
}
/**
* Convert a momentjs date object into an ISO string compatible with our XML
* @param {Date} date
* @return {String}
*/
function dateToForecastISO (date) {
return date.utc().format('YYYY-MM-DDTHH:mm:ss[Z]');
}
module.exports = (config) => {

@@ -65,5 +92,8 @@ // Make a default config, but extend it with the passed config

this.xml = xml;
this.basic = [];
this.detail = [];
// Map containing weather info for given utc times
this.times = {
// e.g '2017-04-29T01:00:00Z': { DATA HERE }
};
log('building LocationForecast object by parsing xml to JSON');

@@ -92,7 +122,27 @@

each(this.json.weatherdata.product.time, function (time) {
if (time.location.symbol) {
self.basic.push(time);
each(this.json.weatherdata.product.time, function (node) {
const simple = isSimpleNode(node);
const temps = hasTemperatureRange(node);
if (!simple) {
self.times[node.to] = node;
} else {
self.detail.push(time);
// node is a small/simple node with format
// <time datatype="forecast" from="2017-04-28T22:00:00Z" to="2017-04-28T23:00:00Z">
// <location altitude="17" latitude="34.0522" longitude="118.2437">
// <precipitation unit="mm" value="0.0"/>
// <symbol id="Sun" number="1"/>
// </location>
// </time>
const parent = self.times[node.to];
parent.icon = node.location.symbol.id;
parent.rain = node.location.precipitation.value + ' ' + node.location.precipitation.unit;
/* istanbul ignore else */
if (temps) {
parent.minTemperature = node.location.minTemperature;
parent.maxTemperature = node.location.maxTemperature;
}
}

@@ -104,3 +154,4 @@ });

/**
* Return JSON of weather
* Returns the JSON representation of the parsed XML`
* @return {Object}
*/

@@ -113,3 +164,4 @@ getJson: function() {

/**
* Return XML of weather
* Return the XML string that the met.no api returned
* @return {String}
*/

@@ -121,2 +173,6 @@ getXml: function() {

/**
* Returns the earliest ISO timestring available in the weather data
* @return {String}
*/
getFirstDateInPayload: function () {

@@ -128,2 +184,20 @@ return this.json.weatherdata.product.time[0].from;

/**
* Returns the latest ISO timestring available in the weather data
* @return {String}
*/
getLastDateInPayload: function () {
return this.json.weatherdata.product.time[this.json.weatherdata.product.time.length - 1].from;
},
/**
* Returns an array of all times that we have weather data for
* @return {Array<String>}
*/
getValidTimestamps: function () {
return Object.keys(this.times);
},
/**
* Get five day weather.

@@ -133,12 +207,23 @@ * @param {Function} callback

getFiveDaySummary: function() {
log('getting five day summary');
const startDate = moment.utc(this.getFirstDateInPayload());
const baseDate = startDate.clone().set('hour', 12).startOf('hour');
let firstDate = baseDate.clone();
var firstDate = moment.utc(this.getFirstDateInPayload()).hours(12);
log(`five day summary is using ${baseDate.toString()} as a starting point`);
/* istanbul ignore else */
if (firstDate.isBefore(startDate)) {
// first date is unique since we may not have data back to midday so instead we
// go with the earliest available
firstDate = startDate.clone();
}
log(`getting five day summary starting with ${firstDate.toISOString()}`);
return Promise.all([
this.getForecastForTime(firstDate),
this.getForecastForTime(firstDate.clone().add('days', 1)),
this.getForecastForTime(firstDate.clone().add('days', 2)),
this.getForecastForTime(firstDate.clone().add('days', 3)),
this.getForecastForTime(firstDate.clone().add('days', 4))
this.getForecastForTime(baseDate.clone().add(1, 'days')),
this.getForecastForTime(baseDate.clone().add(2, 'days')),
this.getForecastForTime(baseDate.clone().add(3, 'days')),
this.getForecastForTime(baseDate.clone().add(4, 'days'))
])

@@ -153,2 +238,16 @@ .then(function (results) {

/**
* Verifies if the pased timestamp is a within range for the weather data
* @param {String|Number|Date} time
* @return {Boolean}
*/
isInRange: function (time) {
return moment.utc(time)
.isBetween(
moment(this.getFirstDateInPayload()),
moment(this.getLastDateInPayload())
);
},
/**
* Returns a forecast for a given time.

@@ -158,5 +257,3 @@ * @param {String|Date} time

*/
getForecastForTime: function(time) {
var self = this;
getForecastForTime: function (time) {
time = moment.utc(time);

@@ -170,179 +267,59 @@

log('getting forecast for time %s', time);
if (time.minute() > 30) {
time.add('hours', 1).startOf('hour');
} else {
time.startOf('hour');
}
return Promise.resolve()
.then(function () {
return buildDetail(
self.getDetailForTime(time),
self.getBasicForTime(time)
);
});
},
log('getForecastForTime', dateToForecastISO(time));
/**
* Get basic items for a time.
* Find nearest hour on same day, or no result.
* @param {String|Object}
* @param {Function}
*/
getBasicForTime: function (date) {
date = moment.utc(date);
let data = this.times[dateToForecastISO(time)] || null;
log('getBasicForTime %s', date);
/* istanbul ignore else */
if (!data && this.isInRange(time)) {
data = this.fallbackSelector(time);
}
// Used to find closest time to one provided
var maxDifference = Infinity;
var res = null;
var items = getItemsForDay(this.basic, date);
var len = items.length;
var i = 0;
while (i < len) {
var to = moment.utc(items[i].to);
var from = moment.utc(items[i].from);
// Check the date falls in range
if ((from.isSame(date) || from.isBefore(date)) && (to.isSame(date) || to.isAfter(date))) {
var diff = Math.abs(to.diff(from));
if (diff < maxDifference) {
maxDifference = diff;
res = items[i];
}
}
i++;
/* istanbul ignore else */
if (data) {
data = Object.assign({}, data, data.location);
delete data.location;
}
return res || fallbackSelector(date, this.basic);
return Promise.resolve(data);
},
/**
* Get detailed items for a time.
* Find nearest hour on same day, or no result.
* @param {String|Object}
* @param {Function}
*/
getDetailForTime: function (date) {
date = moment.utc(date);
log('getDetailForTime %s', date);
fallbackSelector: function (date) {
log('using fallbackSelector for date', date);
// Used to find closest time to one provided
var maxDifference = Infinity;
var res = null;
var itemsForDay = getItemsForDay(this.detail, date);
var len = itemsForDay.length;
var i = 0;
const datetimes = Object.keys(this.times);
while (i < len) {
var diff = Math.abs(moment.utc(itemsForDay[i].to).diff(date));
if (diff < maxDifference) {
maxDifference = diff;
res = itemsForDay[i];
}
let closest = null;
let curnode, curTo;
let len = datetimes.length - 1;
i++;
}
while (len) {
curnode = this.times[datetimes[len]];
curTo = moment(curnode.to);
return res || fallbackSelector(date, this.detail);
}
};
/**
* Build the detailed info for forecast object.
* @param {Object} detail
* @param {Object} obj
*/
function buildDetail(detail, basic) {
// <location altitude="48" latitude="59.3758" longitude="10.7814">
// <temperature id="TTT" unit="celcius" value="6.3"/>
// <windDirection id="dd" deg="223.7" name="SW"/>
// <windSpeed id="ff" mps="4.2" beaufort="3" name="Lett bris"/>
// <humidity value="87.1" unit="percent"/>
// <pressure id="pr" unit="hPa" value="1010.5"/>
// <cloudiness id="NN" percent="0.0"/>
// <fog id="FOG" percent="0.0"/>
// <lowClouds id="LOW" percent="0.0"/>
// <mediumClouds id="MEDIUM" percent="0.0"/>
// <highClouds id="HIGH" percent="0.0"/>
// <dewpointTemperature id="TD" unit="celcius" value="4.2"/>
// </location>
var obj = {
icon: basic.location.symbol.id,
to: basic.to,
from: basic.from,
rain: basic.location.precipitation.value + ' ' + basic.location.precipitation.unit
};
// Corresponds to XML 'location' element
var location = detail.location;
var cur = null;
for (var key in location) {
cur = location[key];
// Based on field type build the response
// Type 1: Has "value" and "unit", combine for result
// Type 2: Has only "percent"
// Type 3: Has multiple properties where the name is the value
if (cur.hasOwnProperty('value')) {
obj[key] = cur.value + ' ' + cur.unit;
} else if (cur.hasOwnProperty('percent')) {
obj[key] = cur.percent + '%';
} else if (typeof cur === 'object') {
obj[key] = {};
for (var nestedKey in cur) {
if (nestedKey !== 'id') {
obj[key][nestedKey] = cur[nestedKey];
if (date.isSame(curTo, 'day')) {
if (!closest) {
closest = curnode;
} else {
/* istanbul ignore else */
if (Math.abs(date.diff(curTo)) < Math.abs(date.diff(moment(closest.to)))) {
closest = curnode;
}
}
} else if (closest) {
// we found a node, and no more nodes exist for the day we need...BAIL
break;
}
}
}
return obj;
}
/**
* Used when no matching element is found for a time of a day.
* @param {Date} time
* @param {Array} collection
*/
function fallbackSelector(time, collection) {
time = moment.utc(time);
var isBefore = false;
var len = collection.length;
var i = 0;
while (i < len) {
if (time.isBefore(moment(collection[i].to))) {
isBefore = true;
i = len;
len--;
}
i++;
return closest;
}
return isBefore ? collection[0] : collection[collection.length - 1];
}
/**
* Provides detailed forecasts for a given date.
* @param {Array} collection
* @param {String|Object} date
*/
function getItemsForDay(collection, date) {
date = moment.utc(date);
return filter(collection, function (item) {
return (
moment.utc(item.from).isSame(date, 'day') ||
moment.utc(item.to).isSame(date, 'day')
);
});
}
};
{
"name": "yr.no-forecast",
"version": "1.0.1",
"version": "2.0.0",
"description": "retrieve a weather forecast for a given time and location from met.no",
"main": "./index.js",
"scripts": {
"test": "npm run lint && npm run unit && npm run coverage",
"test": "date && npm run lint && npm run unit && npm run coverage",
"lint": "npm run eslint && npm run linelint",

@@ -14,3 +14,4 @@ "eslint": "eslint index.js test/*.js",

"coverage": "NODE_PATH=. nyc mocha test/ && nyc report --reporter=lcov",
"example": "node example/dublin-weather.js"
"example": "node example/dublin-weather.js",
"benchmark": "node benchmark/index.js"
},

@@ -24,2 +25,3 @@ "files": [

"lodash.filter": "~4.6.0",
"lodash.find": "~4.6.0",
"lodash.foreach": "~4.5.0",

@@ -29,3 +31,3 @@ "moment": "~2.18.1",

"verror": "~1.9.0",
"yr.no-interface": "~1.0.0"
"yr.no-interface": "~1.0.1"
},

@@ -55,2 +57,3 @@ "license": "MIT",

"devDependencies": {
"benchmark": "~2.1.4",
"chai": "~3.5.0",

@@ -57,0 +60,0 @@ "chai-truthy": "~1.0.0",

@@ -77,6 +77,6 @@ yr.no-forecast

### LocationForecast.getXml()
Returns the XML string that the locationforecast API returned.
Returns the raw XML string that the `locationforecast` API returned.
### LocationForecast.getJson()
Returns the JSON representation of a locationforecast response.
Returns the JSON representation of the entire `locationforecast` response.

@@ -87,29 +87,82 @@ ### LocationForecast.getFirstDateInPayload()

### LocationForecast.getValidTimes()
Returns an Array of ISO timestamps that represent points in time that we have
weather data for.
## Weather JSON Format
Format is somewhat inspired by that of the
[forecast.io](https://developer.forecast.io/) service.
Some fields will be undefined depending on the weather conditions. Always
verify the field you need exists by using `data.hasOwnProperty('fog')`
or similar techniques.
verify the field you need exists, e.g use `data.hasOwnProperty('fog')` or
similar techniques.
```js
```json
{
icon: 'PARTLYCLOUD',
to: '2013-11-15T18:00:00Z',
from: '2013-11-15T12:00:00Z',
rain: '0.0 mm',
temperature: '9.7 celcius',
windDirection: { deg: '220.2', name: 'SW' },
windSpeed: { mps: '2.7', beaufort: '2', name: 'Svak vind' },
humidity: '27.9 percent',
pressure: '1021.0 hPa',
cloudiness: '0.0%',
fog: '0.0%',
lowClouds: '0.0%',
mediumClouds: '0.0%',
highClouds: '0.0%',
dewpointTemperature: '-8.3 celcius'
"datatype": "forecast",
"from": "2017-04-18T03:00:00Z",
"to": "2017-04-18T03:00:00Z",
"icon": "PartlyCloud",
"rain": "0.0 mm",
"altitude": "0",
"latitude": "59.8940",
"longitude": "10.6450",
"temperature": {
"id": "TTT",
"unit": "celsius",
"value": "-0.9"
},
"windDirection": {
"id": "dd",
"deg": "14.6",
"name": "N"
},
"windSpeed": {
"id": "ff",
"mps": "1.5",
"beaufort": "1",
"name": "Flau vind"
},
"windGust": {
"id": "ff_gust",
"mps": "2.4"
},
"humidity": {
"value": "78.3",
"unit": "percent"
},
"pressure": {
"id": "pr",
"unit": "hPa",
"value": "1030.1"
},
"cloudiness": {
"id": "NN",
"percent": "15.4"
},
"fog": {
"id": "FOG",
"percent": "0.0"
},
"lowClouds": {
"id": "LOW",
"percent": "15.4"
},
"mediumClouds": {
"id": "MEDIUM",
"percent": "0.8"
},
"highClouds": {
"id": "HIGH",
"percent": "0.0"
},
"dewpointTemperature": {
"id": "TD",
"unit": "celsius",
"value": "-4.5"
}
}
```
## CHANGELOG
Can be found [at this link](https://github.com/evanshortiss/yr.no-forecast/blob/master/CHANGELOG.md ).
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc