|
324 | 324 | (codeLength < PAIR_CODE_LENGTH_ && codeLength % 2 == 1)) { |
325 | 325 | throw new Error('IllegalArgumentException: Invalid Open Location Code length'); |
326 | 326 | } |
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. |
330 | 327 |
|
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) { |
333 | 349 | var latVal = roundAwayFromZero(latitude * FINAL_LAT_PRECISION_); |
334 | 350 | latVal += LATITUDE_MAX_ * FINAL_LAT_PRECISION_; |
335 | 351 | if (latVal < 0) { |
336 | 352 | latVal = 0; |
337 | 353 | } else if (latVal >= 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_) { |
338 | 354 | latVal = 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_ - 1; |
339 | 355 | } |
340 | | - // Convert longitude into a positive integer and normalise it into the range 0-360*8.192e6. |
341 | 356 | var lngVal = roundAwayFromZero(longitude * FINAL_LNG_PRECISION_); |
342 | 357 | lngVal += LONGITUDE_MAX_ * FINAL_LNG_PRECISION_; |
343 | 358 | if (lngVal < 0) { |
|
347 | 362 | } else if (lngVal >= 2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_) { |
348 | 363 | lngVal = lngVal % (2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_); |
349 | 364 | } |
| 365 | + return [latVal, lngVal]; |
| 366 | + }; |
350 | 367 |
|
| 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) { |
351 | 381 | // Javascript strings are immutable and it doesn't have a native |
352 | 382 | // StringBuilder, so we'll use an array. |
353 | 383 | const code = new Array(MAX_DIGIT_COUNT_ + 1); |
|
356 | 386 | // Compute the grid part of the code if necessary. |
357 | 387 | if (codeLength > PAIR_CODE_LENGTH_) { |
358 | 388 | 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_; |
361 | 391 | var ndx = latDigit * GRID_COLUMNS_ + lngDigit; |
362 | 392 | code[SEPARATOR_POSITION_ + 2 + i] = CODE_ALPHABET_.charAt(ndx); |
363 | 393 | // 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_); |
366 | 396 | } |
367 | 397 | } 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_)); |
370 | 400 | } |
371 | 401 |
|
372 | 402 | // 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_); |
377 | 407 |
|
378 | 408 | // Compute the pair section of the code. |
379 | 409 | 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_); |
384 | 414 | } |
385 | 415 |
|
386 | 416 | // If we don't need to pad the code, return the requested section. |
|
0 commit comments