Skip to content

Commit 3cf9f80

Browse files
authored
js/closure: use floor for conversion, add integer tests (#731)
* js/closure: use floor for conversion, add integer tests * missing args * test logging, use correct fields * switch to floor * correct csv fields * lint fixes
1 parent f72e2a7 commit 3cf9f80

File tree

2 files changed

+91
-34
lines changed

2 files changed

+91
-34
lines changed

js/closure/openlocationcode.js

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -362,17 +362,6 @@ exports.isFull = isFull;
362362
specified.
363363
*/
364364
function encode(latitude, longitude, codeLength) {
365-
if (typeof codeLength == 'undefined') {
366-
codeLength = PAIR_CODE_LENGTH;
367-
}
368-
if (
369-
codeLength < MIN_CODE_LEN ||
370-
(codeLength < PAIR_CODE_LENGTH && codeLength % 2 == 1)
371-
) {
372-
throw new Error('IllegalArgumentException: Invalid Plus Code length');
373-
}
374-
codeLength = Math.min(codeLength, MAX_CODE_LEN);
375-
376365
const locationIntegers = _locationToIntegers(latitude, longitude);
377366

378367
return _encodeIntegers(locationIntegers[0], locationIntegers[1], codeLength);
@@ -395,14 +384,14 @@ exports.encode = encode;
395384
* @return {Array<number>} A tuple of the latitude integer and longitude integer.
396385
*/
397386
function _locationToIntegers(latitude, longitude) {
398-
var latVal = roundAwayFromZero(latitude * FINAL_LAT_PRECISION);
387+
var latVal = Math.floor(latitude * FINAL_LAT_PRECISION);
399388
latVal += LATITUDE_MAX * FINAL_LAT_PRECISION;
400389
if (latVal < 0) {
401390
latVal = 0;
402391
} else if (latVal >= 2 * LATITUDE_MAX * FINAL_LAT_PRECISION) {
403392
latVal = 2 * LATITUDE_MAX * FINAL_LAT_PRECISION - 1;
404393
}
405-
var lngVal = roundAwayFromZero(longitude * FINAL_LNG_PRECISION);
394+
var lngVal = Math.floor(longitude * FINAL_LNG_PRECISION);
406395
lngVal += LONGITUDE_MAX * FINAL_LNG_PRECISION;
407396
if (lngVal < 0) {
408397
lngVal =
@@ -428,6 +417,17 @@ exports._locationToIntegers = _locationToIntegers;
428417
specified.
429418
*/
430419
function _encodeIntegers(latInt, lngInt, codeLength) {
420+
if (typeof codeLength == 'undefined') {
421+
codeLength = PAIR_CODE_LENGTH;
422+
}
423+
if (
424+
codeLength < MIN_CODE_LEN ||
425+
(codeLength < PAIR_CODE_LENGTH && codeLength % 2 == 1)
426+
) {
427+
throw new Error('IllegalArgumentException: Invalid Plus Code length');
428+
}
429+
codeLength = Math.min(codeLength, MAX_CODE_LEN);
430+
431431
// Javascript strings are immutable and it doesn't have a native
432432
// StringBuilder, so we'll use an array.
433433
const code = new Array(MAX_CODE_LEN + 1);
@@ -694,20 +694,6 @@ function shorten(code, latitude, longitude) {
694694
}
695695
exports.shorten = shorten;
696696

697-
/**
698-
* Round numbers like C does. This implements rounding away from zero (see
699-
* https://en.wikipedia.org/wiki/Rounding).
700-
*
701-
* @param {number} num A number to round.
702-
* @return {number} The rounded value usn
703-
*/
704-
function roundAwayFromZero(num) {
705-
if (num >= 0) {
706-
return Math.round(num);
707-
}
708-
return -1 * Math.round(Math.abs(num));
709-
}
710-
711697
/**
712698
Clip a latitude into the range -90 to 90.
713699
@param {number} latitude A latitude in signed decimal degrees.

js/closure/openlocationcode_test.js

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,92 @@ testSuite({
6767
asyncTestCase.waitForAsync('Waiting for xhr to respond');
6868
xhrIo_.send(DECODING_TEST_FILE, 'GET');
6969
},
70-
testEncode: function() {
70+
testEncodeDegrees: function() {
7171
const xhrIo_ = new XhrIo();
7272
xhrIo_.listenOnce(EventType.COMPLETE, () => {
73+
// Allow a 5% error rate encoding from degree coordinates (because of floating
74+
// point precision).
75+
const allowedErrorRate = 0.05;
76+
var errors = 0;
7377
const lines = xhrIo_.getResponseText().match(/^[^#].+/gm);
7478
for (var i = 0; i < lines.length; i++) {
7579
const fields = lines[i].split(',');
76-
const lat = parseFloat(fields[0]);
77-
const lng = parseFloat(fields[1]);
78-
const length = parseInt(fields[2], 10);
79-
const code = fields[3];
80+
const latDegrees = parseFloat(fields[0]);
81+
const lngDegrees = parseFloat(fields[1]);
82+
const length = parseInt(fields[4], 10);
83+
const code = fields[5];
8084

81-
const gotCode = OpenLocationCode.encode(lat, lng, length);
85+
const got = OpenLocationCode.encode(latDegrees, lngDegrees, length);
8286
// Did we get the same code?
83-
assertEquals('testEncode ' + 1, code, gotCode);
87+
if (code != got) {
88+
console.warn(
89+
'ENCODING DIFFERENCE: Expected code ' + code +', got ' + got
90+
);
91+
errors++;
92+
}
93+
asyncTestCase.continueTesting();
94+
}
95+
console.info('testEncodeDegrees error rate is ' + (errors / lines.length));
96+
assertTrue(
97+
'testEncodeDegrees: too many errors ' + errors / lines.length,
98+
(errors / lines.length) < allowedErrorRate
99+
);
100+
});
101+
asyncTestCase.waitForAsync('Waiting for xhr to respond');
102+
xhrIo_.send(ENCODING_TEST_FILE, 'GET');
103+
},
104+
testLocationToIntegers: function() {
105+
const xhrIo_ = new XhrIo();
106+
xhrIo_.listenOnce(EventType.COMPLETE, () => {
107+
const lines = xhrIo_.getResponseText().match(/^[^#].+/gm);
108+
for (var i = 0; i < lines.length; i++) {
109+
const fields = lines[i].split(',');
110+
const latDegrees = parseFloat(fields[0]);
111+
const lngDegrees = parseFloat(fields[1]);
112+
const latIntegers = parseInt(fields[2], 10);
113+
const lngIntegers = parseInt(fields[3], 10);
84114

115+
const got = OpenLocationCode._locationToIntegers(
116+
latDegrees,
117+
lngDegrees
118+
);
119+
// Due to floating point precision limitations, we may get values 1 less
120+
// than expected.
121+
assertTrue(
122+
'testLocationToIntegers: expected latitude ' + latIntegers + ', got ' + got[0],
123+
got[0] == latIntegers || got[0] == latIntegers - 1
124+
);
125+
assertTrue(
126+
'testLocationToIntegers: expected longitude ' + lngIntegers + ', got ' + got[1],
127+
got[1] == lngIntegers || got[1] == lngIntegers - 1
128+
);
129+
asyncTestCase.continueTesting();
130+
}
131+
});
132+
asyncTestCase.waitForAsync('Waiting for xhr to respond');
133+
xhrIo_.send(ENCODING_TEST_FILE, 'GET');
134+
},
135+
testEncodeIntegers: function() {
136+
const xhrIo_ = new XhrIo();
137+
xhrIo_.listenOnce(EventType.COMPLETE, () => {
138+
const lines = xhrIo_.getResponseText().match(/^[^#].+/gm);
139+
for (var i = 0; i < lines.length; i++) {
140+
const fields = lines[i].split(',');
141+
const latIntegers = parseInt(fields[2], 10);
142+
const lngIntegers = parseInt(fields[3], 10);
143+
const length = parseInt(fields[4], 10);
144+
const code = fields[5];
145+
146+
const got = OpenLocationCode._encodeIntegers(
147+
latIntegers,
148+
lngIntegers,
149+
length
150+
);
151+
// Did we get the same code?
152+
assertEquals(
153+
'testEncodeIntegers: expected code ' + code + ', got ' + got,
154+
code, got
155+
);
85156
asyncTestCase.continueTesting();
86157
}
87158
});

0 commit comments

Comments
 (0)