Skip to content

Commit 2d910d9

Browse files
committed
Fix Dms.wrap90(), Dms.wrap180() to work for all -ve degrees
The wrap functions used simplified forms of triangle/sawtooth wave functions which failed for some negative imputs due to JavaScript '%' being a 'rem' operator rather than a 'mod' operator; this change replaces 'x%n' with '((x%n)+n)%n' to emulate a 'mod' operator, and uses the full form of the wave functions for clarity.
1 parent 209f3c3 commit 2d910d9

File tree

3 files changed

+37
-15
lines changed

3 files changed

+37
-15
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixed
66

77
- Fix parsing of 'H' 500km squares (Scottish islands)
8+
- Fix Dms.wrap90(), Dms.wrap180() to work for all -ve degrees
89

910
## [2.2.1] - 2020-04-22
1011

dms.js

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2-
/* Geodesy representation conversion functions (c) Chris Veness 2002-2019 */
2+
/* Geodesy representation conversion functions (c) Chris Veness 2002-2020 */
33
/* MIT Licence */
44
/* www.movable-type.co.uk/scripts/latlong.html */
55
/* www.movable-type.co.uk/scripts/js/geodesy/geodesy-library.html#dms */
@@ -285,39 +285,60 @@ class Dms {
285285

286286

287287
/**
288-
* Constrain degrees to range 0..360 (e.g. for bearings); -1 => 359, 361 => 1.
288+
* Constrain degrees to range -90..+90 (for latitude); e.g. -91 => -89, 91 => 89.
289289
*
290290
* @private
291291
* @param {number} degrees
292-
* @returns degrees within range 0..360.
292+
* @returns degrees within range -90..+90.
293293
*/
294-
static wrap360(degrees) {
295-
if (0<=degrees && degrees<360) return degrees; // avoid rounding due to arithmetic ops if within range
296-
return (degrees%360+360) % 360; // sawtooth wave p:360, a:360
294+
static wrap90(degrees) {
295+
if (-90<=degrees && degrees<=90) return degrees; // avoid rounding due to arithmetic ops if within range
296+
297+
// latitude wrapping requires a triangle wave function; a general triangle wave is
298+
// f(x) = 4a/p ⋅ | (x-p/4)%p - p/2 | - a
299+
// where a = amplitude, p = period, % = modulo; however, JavaScript '%' is a remainder operator
300+
// not a modulo operator - for modulo, replace 'x%n' with '((x%n)+n)%n'
301+
const x = degrees, a = 90, p = 360;
302+
return 4*a/p * Math.abs((((x-p/4)%p)+p)%p - p/2) - a;
297303
}
298304

299305
/**
300-
* Constrain degrees to range -180..+180 (e.g. for longitude); -181 => 179, 181 => -179.
306+
* Constrain degrees to range -180..+180 (for longitude); e.g. -181 => 179, 181 => -179.
301307
*
302308
* @private
303309
* @param {number} degrees
304310
* @returns degrees within range -180..+180.
305311
*/
306312
static wrap180(degrees) {
307-
if (-180<degrees && degrees<=180) return degrees; // avoid rounding due to arithmetic ops if within range
308-
return (degrees+540)%360-180; // sawtooth wave p:180, a:±180
313+
if (-180<=degrees && degrees<=180) return degrees; // avoid rounding due to arithmetic ops if within range
314+
315+
// longitude wrapping requires a sawtooth wave function; a general sawtooth wave is
316+
// f(x) = (2ax/p - p/2) % p - a
317+
// where a = amplitude, p = period, % = modulo; however, JavaScript '%' is a remainder operator
318+
// not a modulo operator - for modulo, replace 'x%n' with '((x%n)+n)%n'
319+
const x = degrees, a = 180, p = 360;
320+
return (((2*a*x/p - p/2)%p)+p)%p - a;
309321
}
310322

311323
/**
312-
* Constrain degrees to range -90..+90 (e.g. for latitude); -91 => -89, 91 => 89.
324+
* Constrain degrees to range 0..360 (for bearings); e.g. -1 => 359, 361 => 1.
313325
*
314326
* @private
315327
* @param {number} degrees
316-
* @returns degrees within range -90..+90.
328+
* @returns degrees within range 0..360.
317329
*/
318-
static wrap90(degrees) {
319-
if (-90<=degrees && degrees<=90) return degrees; // avoid rounding due to arithmetic ops if within range
320-
return Math.abs((degrees%360 + 270)%360 - 180) - 90; // triangle wave p:360 a:±90 TODO: fix e.g. -315°
330+
static wrap360(degrees) {
331+
if (0<=degrees && degrees<360) return degrees; // avoid rounding due to arithmetic ops if within range
332+
333+
// bearing wrapping requires a sawtooth wave function with a vertical offset equal to the
334+
// amplitude and a corresponding phase shift; this changes the general sawtooth wave function from
335+
// f(x) = (2ax/p - p/2) % p - a
336+
// to
337+
// f(x) = (2ax/p) % p
338+
// where a = amplitude, p = period, % = modulo; however, JavaScript '%' is a remainder operator
339+
// not a modulo operator - for modulo, replace 'x%n' with '((x%n)+n)%n'
340+
const x = degrees, a = 180, p = 360;
341+
return (((2*a*x/p)%p)+p)%p;
321342
}
322343

323344
}

test/dms-tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ describe('dms', function() {
197197
test('-450°', () => Dms.wrap90(-450).should.equal( -90));
198198
test('-405°', () => Dms.wrap90(-405).should.equal( -45));
199199
test('-360°', () => Dms.wrap90(-360).should.equal( 0));
200-
// test('-315°', () => Dms.wrap90(-315).should.equal( 45)); TODO: fix!
200+
test('-315°', () => Dms.wrap90(-315).should.equal( 45));
201201
test('-270°', () => Dms.wrap90(-270).should.equal( 90));
202202
test('-225°', () => Dms.wrap90(-225).should.equal( 45));
203203
test('-180°', () => Dms.wrap90(-180).should.equal( 0));

0 commit comments

Comments
 (0)