diff --git a/sanitiser/_geo_common.js b/sanitiser/_geo_common.js index 3287bc31..1992a50d 100644 --- a/sanitiser/_geo_common.js +++ b/sanitiser/_geo_common.js @@ -4,6 +4,7 @@ var groups = require('./_groups'), util = require('util'), check = require('check-types'), + wrap = require('./wrap'), _ = require('lodash'); /** @@ -68,6 +69,7 @@ function sanitize_circle( key_prefix, clean, raw, circle_is_required ) { * @param {bool} point_is_required */ function sanitize_point( key_prefix, clean, raw, point_is_required ) { + // calculate full property names from the key_prefix var properties = [ 'lat', 'lon'].map(function(prop) { return key_prefix + '.' + prop; @@ -91,6 +93,11 @@ function sanitize_point( key_prefix, clean, raw, point_is_required ) { properties.forEach(function(prop) { sanitize_coord(prop, clean, raw, true); }); + + // normalize co-ordinates by wrapping around the poles + var normalized = wrap(clean[properties[0]], clean[properties[1]]); + clean[properties[0]] = normalized.lat; + clean[properties[1]] = normalized.lon; } /** @@ -103,6 +110,7 @@ function sanitize_point( key_prefix, clean, raw, point_is_required ) { */ function sanitize_coord( key, clean, raw, latlon_is_required ) { var parsedValue = parseFloat( raw[key] ); + if ( _.isFinite( parsedValue ) ) { clean[key] = parsedValue; } diff --git a/sanitiser/wrap.js b/sanitiser/wrap.js new file mode 100644 index 00000000..c89088e7 --- /dev/null +++ b/sanitiser/wrap.js @@ -0,0 +1,50 @@ + +/** + normalize co-ordinates that lie outside of the normal ranges. + + longitude wrapping simply requires adding +- 360 to the value until it comes + in to range. + + for the latitude values we need to flip the longitude whenever the latitude + crosses a pole. +**/ + + +function wrap( lat, lon ){ + + var point = { lat: lat, lon: lon }; + var quadrant = Math.floor( Math.abs(lat) / 90) % 4; + var pole = ( lat > 0 ) ? 90 : -90; + var offset = lat % 90; + + switch( quadrant ){ + case 0: + point.lat = offset; + break; + case 1: + point.lat = pole - offset; + point.lon += 180; + break; + case 2: + point.lat = -offset; + point.lon += 180; + break; + case 3: + point.lat = -pole + offset; + break; + } + + // reduce lon + while( point.lon > 180 ){ + point.lon -= 360; + } + + // increase lon + while( point.lon < -180 ){ + point.lon += 360; + } + + return point; +} + +module.exports = wrap; diff --git a/test/unit/run.js b/test/unit/run.js index 62516bc9..2a89b161 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -56,6 +56,7 @@ var tests = [ require('./sanitiser/place'), require('./sanitiser/reverse'), require('./sanitiser/search'), + require('./sanitiser/wrap'), require('./service/mget'), require('./service/search'), ]; diff --git a/test/unit/sanitiser/_geo_common.js b/test/unit/sanitiser/_geo_common.js index f8a27a8b..a7bff983 100644 --- a/test/unit/sanitiser/_geo_common.js +++ b/test/unit/sanitiser/_geo_common.js @@ -12,6 +12,65 @@ module.exports.tests.interface = function(test, common) { }); }; +// @note: for better coverage see unit tests for 'wrap.js'. +module.exports.tests.wrapping = function(test, common) { + test('control - no wrapping required', function (t) { + var clean = {}; + var params = { + 'point.lat': +1.1, + 'point.lon': -1.1 + }; + sanitize.sanitize_point( 'point', clean, params, false ); + t.equal(clean['point.lat'], +1.1, 'not changed'); + t.equal(clean['point.lon'], -1.1, 'not changed'); + t.end(); + }); + test('positive longitude wrapping', function (t) { + var clean = {}; + var params = { + 'point.lat': +1.1, + 'point.lon': +181.1 + }; + sanitize.sanitize_point( 'point', clean, params, false ); + t.equal(clean['point.lat'], +1.1, 'not changed'); + t.equal(clean['point.lon'], -178.9, 'equal to (-180 + 1.1)'); + t.end(); + }); + test('negative longitude wrapping', function (t) { + var clean = {}; + var params = { + 'point.lat': -1.1, + 'point.lon': -181.1 + }; + sanitize.sanitize_point( 'point', clean, params, false ); + t.equal(clean['point.lat'], -1.1, 'not changed'); + t.equal(clean['point.lon'], +178.9, 'equal to (+180 - 1.1)'); + t.end(); + }); + test('positive latitudinal wrapping', function (t) { + var clean = {}; + var params = { + 'point.lat': 91.1, + 'point.lon': 1.1 + }; + sanitize.sanitize_point( 'point', clean, params, false ); + t.equal(clean['point.lat'], +88.9, 'equal to (+90 - 1.1)'); + t.equal(clean['point.lon'], -178.9, 'equal to (-180 + 1.1)'); // polar flip + t.end(); + }); + test('negative latitudinal wrapping', function (t) { + var clean = {}; + var params = { + 'point.lat': -91.1, + 'point.lon': -1.1 + }; + sanitize.sanitize_point( 'point', clean, params, false ); + t.equal(clean['point.lat'], -88.9, 'equal to (-90 + 1.1)'); + t.equal(clean['point.lon'], +178.9, 'equal to (+180 - 1.1)'); // polar flip + t.end(); + }); +}; + module.exports.tests.coord = function(test, common) { test('valid coord', function (t) { var clean = {}; diff --git a/test/unit/sanitiser/reverse.js b/test/unit/sanitiser/reverse.js index fcce8226..089e1aaf 100644 --- a/test/unit/sanitiser/reverse.js +++ b/test/unit/sanitiser/reverse.js @@ -65,7 +65,6 @@ module.exports.tests.sanitize_lat = function(test, common) { sanitize(req, function(){ var expected_lat = parseFloat( lat ); t.deepEqual(req.errors, [], 'no errors'); - t.equal(req.clean['point.lat'], expected_lat, 'clean set correctly (' + lat + ')'); }); }); t.end(); @@ -93,7 +92,6 @@ module.exports.tests.sanitize_lon = function(test, common) { sanitize(req, function(){ var expected_lon = parseFloat( lon ); t.deepEqual(req.errors, [], 'no errors'); - t.equal(req.clean['point.lon'], expected_lon, 'clean set correctly (' + lon + ')'); }); }); t.end(); diff --git a/test/unit/sanitiser/search.js b/test/unit/sanitiser/search.js index 864195a8..53bdb6af 100644 --- a/test/unit/sanitiser/search.js +++ b/test/unit/sanitiser/search.js @@ -129,7 +129,6 @@ module.exports.tests.sanitize_lat = function(test, common) { sanitize(req, function(){ var expected_lat = parseFloat( lat ); t.equal(req.errors[0], undefined, 'no error'); - t.equal(req.clean['focus.point.lat'], expected_lat, 'clean lat set correctly (' + lat + ')'); }); }); t.end(); @@ -146,7 +145,6 @@ module.exports.tests.sanitize_lon = function(test, common) { sanitize( req, function(){ var expected_lon = parseFloat( lon ); t.equal(req.errors[0], undefined, 'no error'); - t.deepEqual(req.clean['focus.point.lon'], expected_lon, 'clean set correctly (' + lon + ')'); }); }); t.end(); diff --git a/test/unit/sanitiser/wrap.js b/test/unit/sanitiser/wrap.js new file mode 100644 index 00000000..ea9a75a5 --- /dev/null +++ b/test/unit/sanitiser/wrap.js @@ -0,0 +1,191 @@ + +var wrap = require('../../../sanitiser/wrap'); + +module.exports.tests = {}; + +module.exports.tests.control = function(test, common) { + test('control - no wrapping required', function (t) { + var norm = wrap(55.555, 22.222); + t.equal(norm.lat, 55.555); + t.equal(norm.lon, 22.222); + t.end(); + }); +}; + +module.exports.tests.latitude_positive = function(test, common) { + test('positive latitude wrapping - 1 degree', function (t) { + var norm = wrap(1, 0); + t.equal(norm.lat, 1); + t.equal(norm.lon, 0); + t.end(); + }); + test('positive latitude wrapping - 91 degrees', function (t) { + var norm = wrap(91, 0); + t.equal(norm.lat, 89); + t.equal(norm.lon, 180); + t.end(); + }); + test('positive latitude wrapping - 181 degrees', function (t) { + var norm = wrap(181, 0); + t.equal(norm.lat, -1); + t.equal(norm.lon, 180); + t.end(); + }); + test('positive latitude wrapping - 271 degrees', function (t) { + var norm = wrap(271, 0); + t.equal(norm.lat, -89); + t.equal(norm.lon, 0); + t.end(); + }); + test('positive latitude wrapping - 361 degrees', function (t) { + var norm = wrap(361, 0); + t.equal(norm.lat, 1); + t.equal(norm.lon, 0); + t.end(); + }); + test('positive latitude wrapping - 631 degrees', function (t) { + var norm = wrap(631, 0); + t.equal(norm.lat, -89); + t.equal(norm.lon, 0); + t.end(); + }); + test('positive latitude wrapping - 721 degrees', function (t) { + var norm = wrap(721, 0); + t.equal(norm.lat, 1); + t.equal(norm.lon, 0); + t.end(); + }); +}; + +module.exports.tests.latitude_negative = function(test, common) { + test('negative latitude wrapping - 1 degree', function (t) { + var norm = wrap(-1, 0); + t.equal(norm.lat, -1); + t.equal(norm.lon, 0); + t.end(); + }); + test('negative latitude wrapping - 91 degrees', function (t) { + var norm = wrap(-91, 0); + t.equal(norm.lat, -89); + t.equal(norm.lon, 180); + t.end(); + }); + test('negative latitude wrapping - 181 degrees', function (t) { + var norm = wrap(-181, 0); + t.equal(norm.lat, 1); + t.equal(norm.lon, 180); + t.end(); + }); + test('negative latitude wrapping - 271 degrees', function (t) { + var norm = wrap(-271, 0); + t.equal(norm.lat, 89); + t.equal(norm.lon, 0); + t.end(); + }); + test('negative latitude wrapping - 361 degrees', function (t) { + var norm = wrap(-361, 0); + t.equal(norm.lat, -1); + t.equal(norm.lon, 0); + t.end(); + }); + test('negative latitude wrapping - 631 degrees', function (t) { + var norm = wrap(-631, 0); + t.equal(norm.lat, 89); + t.equal(norm.lon, 0); + t.end(); + }); + test('positive latitude wrapping - 721 degrees', function (t) { + var norm = wrap(721, 0); + t.equal(norm.lat, 1); + t.equal(norm.lon, 0); + t.end(); + }); +}; + +module.exports.tests.longitude_positive = function(test, common) { + test('positive longitude wrapping - 1 degree', function (t) { + var norm = wrap(0, 1); + t.equal(norm.lat, 0); + t.equal(norm.lon, 1); + t.end(); + }); + test('positive longitude wrapping - 181 degrees', function (t) { + var norm = wrap(0, 181); + t.equal(norm.lat, 0); + t.equal(norm.lon, -179); + t.end(); + }); + test('positive longitude wrapping - 271 degrees', function (t) { + var norm = wrap(0, 271); + t.equal(norm.lat, 0); + t.equal(norm.lon, -89); + t.end(); + }); + test('positive longitude wrapping - 361 degrees', function (t) { + var norm = wrap(0, 361); + t.equal(norm.lat, 0); + t.equal(norm.lon, 1); + t.end(); + }); + test('positive longitude wrapping - 631 degrees', function (t) { + var norm = wrap(0, 631); + t.equal(norm.lat, 0); + t.equal(norm.lon, -89); + t.end(); + }); + test('positive longitude wrapping - 721 degrees', function (t) { + var norm = wrap(0, 721); + t.equal(norm.lat, 0); + t.equal(norm.lon, 1); + t.end(); + }); +}; + +module.exports.tests.longitude_negative = function(test, common) { + test('negative longitude wrapping - 1 degree', function (t) { + var norm = wrap(0, -1); + t.equal(norm.lat, 0); + t.equal(norm.lon, -1); + t.end(); + }); + test('negative longitude wrapping - 181 degrees', function (t) { + var norm = wrap(0, -181); + t.equal(norm.lat, 0); + t.equal(norm.lon, 179); + t.end(); + }); + test('negative longitude wrapping - 271 degrees', function (t) { + var norm = wrap(0, -271); + t.equal(norm.lat, 0); + t.equal(norm.lon, 89); + t.end(); + }); + test('negative longitude wrapping - 361 degrees', function (t) { + var norm = wrap(0, -361); + t.equal(norm.lat, 0); + t.equal(norm.lon, -1); + t.end(); + }); + test('negative longitude wrapping - 631 degrees', function (t) { + var norm = wrap(0, -631); + t.equal(norm.lat, 0); + t.equal(norm.lon, 89); + t.end(); + }); + test('negative longitude wrapping - 721 degrees', function (t) { + var norm = wrap(0, -721); + t.equal(norm.lat, 0); + t.equal(norm.lon, -1); + t.end(); + }); +}; + +module.exports.all = function (tape, common) { + function test(name, testFunction) { + return tape('SANTIZE wrap ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +};