Skip to content

Commit 2bd6c06

Browse files
committed
expose js integer encoding and conversion functions
1 parent adf9172 commit 2bd6c06

1 file changed

Lines changed: 50 additions & 20 deletions

File tree

js/src/openlocationcode.js

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -324,20 +324,35 @@
324324
(codeLength < PAIR_CODE_LENGTH_ && codeLength % 2 == 1)) {
325325
throw new Error('IllegalArgumentException: Invalid Open Location Code length');
326326
}
327-
// This approach converts each value to an integer after multiplying it by the final precision.
328-
// This allows us to use only integer operations, so avoiding any accumulation of floating
329-
// point representation errors.
330327

331-
// Convert latitude into a positive integer clipped into the range 0-(just under 180*2.5e7).
332-
// Latitude 90 needs to be adjusted to be just less, so the returned code can also be decoded.
328+
const locationIntegers = locationToIntegers(latitude, longitude);
329+
330+
return encodeIntegers(locationIntegers[0], locationIntegers[1], codeLength);
331+
};
332+
333+
/**
334+
* Convert a latitude, longitude location into integer values.
335+
*
336+
* This function is only exposed for testing.
337+
*
338+
* Latitude is converted into a positive integer clipped into the range
339+
* 0 <= X < 180*2.5e7. (Latitude 90 needs to be adjusted to be slightly lower,
340+
* so that the returned code can also be decoded.
341+
* Longitude is converted into a positive integer and normalised into the range
342+
* 0 <= X < 360*8.192e6.
343+
344+
* @param {number} latitude
345+
* @param {number} longitude
346+
* @return {Array<number>} A tuple of the latitude integer and longitude integer.
347+
*/
348+
var locationToIntegers = function(latitude, longitude) {
333349
var latVal = roundAwayFromZero(latitude * FINAL_LAT_PRECISION_);
334350
latVal += LATITUDE_MAX_ * FINAL_LAT_PRECISION_;
335351
if (latVal < 0) {
336352
latVal = 0;
337353
} else if (latVal >= 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_) {
338354
latVal = 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_ - 1;
339355
}
340-
// Convert longitude into a positive integer and normalise it into the range 0-360*8.192e6.
341356
var lngVal = roundAwayFromZero(longitude * FINAL_LNG_PRECISION_);
342357
lngVal += LONGITUDE_MAX_ * FINAL_LNG_PRECISION_;
343358
if (lngVal < 0) {
@@ -347,7 +362,22 @@
347362
} else if (lngVal >= 2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_) {
348363
lngVal = lngVal % (2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_);
349364
}
365+
return [latVal, lngVal];
366+
};
350367

368+
/**
369+
* Encode a location that uses integer values into an Open Location Code.
370+
*
371+
* This is a testing function, and should not be called directly.
372+
*
373+
* @param {number} latInt An integer latitude.
374+
* @param {number} lngInt An integer longitude.
375+
* @param {number=} codeLength The number of significant digits in the output
376+
* code, not including any separator characters.
377+
* @return {string} A code of the specified length or the default length if not
378+
* specified.
379+
*/
380+
var encodeIntegers = function(latInt, lngInt, codeLength) {
351381
// Javascript strings are immutable and it doesn't have a native
352382
// StringBuilder, so we'll use an array.
353383
const code = new Array(MAX_DIGIT_COUNT_ + 1);
@@ -356,31 +386,31 @@
356386
// Compute the grid part of the code if necessary.
357387
if (codeLength > PAIR_CODE_LENGTH_) {
358388
for (var i = MAX_DIGIT_COUNT_ - PAIR_CODE_LENGTH_; i >= 1; i--) {
359-
var latDigit = latVal % GRID_ROWS_;
360-
var lngDigit = lngVal % GRID_COLUMNS_;
389+
var latDigit = latInt % GRID_ROWS_;
390+
var lngDigit = lngInt % GRID_COLUMNS_;
361391
var ndx = latDigit * GRID_COLUMNS_ + lngDigit;
362392
code[SEPARATOR_POSITION_ + 2 + i] = CODE_ALPHABET_.charAt(ndx);
363393
// Note! Integer division.
364-
latVal = Math.floor(latVal / GRID_ROWS_);
365-
lngVal = Math.floor(lngVal / GRID_COLUMNS_);
394+
latInt = Math.floor(latInt / GRID_ROWS_);
395+
lngInt = Math.floor(lngInt / GRID_COLUMNS_);
366396
}
367397
} else {
368-
latVal = Math.floor(latVal / Math.pow(GRID_ROWS_, GRID_CODE_LENGTH_));
369-
lngVal = Math.floor(lngVal / Math.pow(GRID_COLUMNS_, GRID_CODE_LENGTH_));
398+
latInt = Math.floor(latInt / Math.pow(GRID_ROWS_, GRID_CODE_LENGTH_));
399+
lngInt = Math.floor(lngInt / Math.pow(GRID_COLUMNS_, GRID_CODE_LENGTH_));
370400
}
371401

372402
// Add the pair after the separator.
373-
code[SEPARATOR_POSITION_ + 1] = CODE_ALPHABET_.charAt(latVal % ENCODING_BASE_);
374-
code[SEPARATOR_POSITION_ + 2] = CODE_ALPHABET_.charAt(lngVal % ENCODING_BASE_);
375-
latVal = Math.floor(latVal / ENCODING_BASE_);
376-
lngVal = Math.floor(lngVal / ENCODING_BASE_);
403+
code[SEPARATOR_POSITION_ + 1] = CODE_ALPHABET_.charAt(latInt % ENCODING_BASE_);
404+
code[SEPARATOR_POSITION_ + 2] = CODE_ALPHABET_.charAt(lngInt % ENCODING_BASE_);
405+
latInt = Math.floor(latInt / ENCODING_BASE_);
406+
lngInt = Math.floor(lngInt / ENCODING_BASE_);
377407

378408
// Compute the pair section of the code.
379409
for (var i = PAIR_CODE_LENGTH_ / 2 + 1; i >= 0; i -= 2) {
380-
code[i] = CODE_ALPHABET_.charAt(latVal % ENCODING_BASE_);
381-
code[i + 1] = CODE_ALPHABET_.charAt(lngVal % ENCODING_BASE_);
382-
latVal = Math.floor(latVal / ENCODING_BASE_);
383-
lngVal = Math.floor(lngVal / ENCODING_BASE_);
410+
code[i] = CODE_ALPHABET_.charAt(latInt % ENCODING_BASE_);
411+
code[i + 1] = CODE_ALPHABET_.charAt(lngInt % ENCODING_BASE_);
412+
latInt = Math.floor(latInt / ENCODING_BASE_);
413+
lngInt = Math.floor(lngInt / ENCODING_BASE_);
384414
}
385415

386416
// If we don't need to pad the code, return the requested section.

0 commit comments

Comments
 (0)