Skip to content

Commit 8cf8ab9

Browse files
authored
Modify encode method to resemble other implementations (#706)
* Modify encode method to resemble other implementations * remove surplus hash (pound) symbols
1 parent 894dfd2 commit 8cf8ab9

File tree

1 file changed

+86
-77
lines changed

1 file changed

+86
-77
lines changed

visualbasic/OpenLocationCode.bas

Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ Private Const LATITUDE_MAX_ As Double = 90
9191
Private Const LONGITUDE_MAX_ As Double = 180
9292

9393
' Minimum number of digits in a code.
94-
Private Const MIN_DIGIT_COUNT_ = 2;
94+
Private Const MIN_DIGIT_COUNT_ As Integer = 2
9595

9696
' Maximum number of digits in a code.
97-
Private Const MAX_DIGIT_COUNT_ = 15;
97+
Private Const MAX_DIGIT_COUNT_ As Integer = 15
9898

9999
' Maximum code length using lat/lng pair encoding. The area of such a
100100
' code is approximately 13x13 meters (at the equator), and should be suitable
@@ -107,9 +107,18 @@ Private Const GRID_COLUMNS_ As Integer = 4
107107
' Number of rows in the grid refinement method.
108108
Private Const GRID_ROWS_ As Integer = 5
109109

110+
' Number of grid digits.
111+
Private Const GRID_CODE_LENGTH_ As Integer = MAX_DIGIT_COUNT_ - PAIR_CODE_LENGTH_
112+
110113
' Size of the initial grid in degrees.
111114
Private Const GRID_SIZE_DEGREES_ As Double = 1 / 8000
112115

116+
' Degree resolution for latitude.
117+
Private Const FINAL_LAT_PRECISION_ As Long = 8000 * (GRID_ROWS_ ^ GRID_CODE_LENGTH_)
118+
119+
' Degree resolution for longitude.
120+
Private Const FINAL_LNG_PRECISION_ As Long = 8000 * (GRID_COLUMNS_ ^ GRID_CODE_LENGTH_)
121+
113122
' Minimum length of a code that can be shortened.
114123
Private Const MIN_TRIMMABLE_CODE_LEN_ As Integer = 6
115124

@@ -227,23 +236,80 @@ Public Function OLCEncode(ByVal latitude As Double, ByVal longitude As Double, O
227236
If codeLength < PAIR_CODE_LENGTH_ And codeLength \ 2 = 1 Then
228237
Err.raise vbObjectError + 513, "OLCEncodeWithLength", "Invalid code length"
229238
End If
230-
Dim lat, lng As Double
231-
Dim latCode, lngCode, gridCode As String
232-
Dim code As String
233-
' Ensure that the latitude and longitude are valid.
234-
lat = clipLatitude(latitude)
235-
lng = normalizeLongitude(longitude)
236-
' Latitude 90 needs to be adjusted to be just under, so the returned code can also be decoded.
237-
If lat = 90 Then
238-
lat = lat - computeLatitudePrecision(codeLength)
239+
' We use Doubles for the latitude and longitude, even though we will use them as integers.
240+
' The reason is that we want to use this code in Excel and LibreOffice, but the LibreOffice
241+
' Long type is only 32 bits, –2147483648 and 2147483647, which is too small.
242+
Dim lat As Double, lng As Double
243+
' i is used in loops.
244+
Dim i As Integer
245+
246+
' Convert latitude into a positive integer clipped into the range 0-(just
247+
' under 180*2.5e7). Latitude 90 needs to be adjusted to be just less, so the
248+
' returned code can also be decoded.
249+
lat = Round(latitude * FINAL_LAT_PRECISION_)
250+
lat = lat + LATITUDE_MAX_ * FINAL_LAT_PRECISION_
251+
If lat < 0 Then
252+
lat = 0
253+
ElseIf lat >= 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_ Then
254+
lat = 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_ - 1
239255
End If
240-
latCode = encodeCoordinate(lat + LATITUDE_MAX_, doubleMin(codeLength, PAIR_CODE_LENGTH_) / 2)
241-
lngCode = encodeCoordinate(lng + LONGITUDE_MAX_, doubleMin(codeLength, PAIR_CODE_LENGTH_) / 2)
256+
' Convert longitude into a positive integer and normalise it into the range 0-360*8.192e6.
257+
lng = Round(longitude * FINAL_LNG_PRECISION_)
258+
lng = lng + LONGITUDE_MAX_ * FINAL_LNG_PRECISION_
259+
If lng < 0 Then
260+
lng = doubleMod(lng, (2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_)) + 2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_
261+
ElseIf lng >= 2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_ Then
262+
lng = doubleMod(lng, (2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_))
263+
EndIf
264+
265+
' Build up the code in an array.
266+
Dim code(MAX_DIGIT_COUNT_) As String
267+
code(SEPARATOR_POSITION_) = SEPARATOR_
268+
269+
' Compute the grid part of the code if necessary.
270+
Dim latDigit As Integer
271+
Dim lngDigit As Integer
242272
If codeLength > PAIR_CODE_LENGTH_ Then
243-
gridCode = encodeGrid(lat, lng, codeLength - PAIR_CODE_LENGTH_)
273+
For i = MAX_DIGIT_COUNT_ - PAIR_CODE_LENGTH_ To 1 Step -1
274+
latDigit = CInt(doubleMod(lat, GRID_ROWS_))
275+
lngDigit = CInt(doubleMod(lng, GRID_COLUMNS_))
276+
code(SEPARATOR_POSITION_ + 2 + i) = Mid(CODE_ALPHABET_, 1 + latDigit * GRID_COLUMNS_ + lngDigit, 1)
277+
lat = Int(lat / GRID_ROWS_)
278+
lng = Int(lng / GRID_COLUMNS_)
279+
Next
280+
Else
281+
lat = Int(lat / (GRID_ROWS_ ^ GRID_CODE_LENGTH_))
282+
lng = Int(lng / (GRID_COLUMNS_ ^ GRID_CODE_LENGTH_))
244283
End If
245-
code = mergeCode(latCode, lngCode, gridCode)
246-
OLCEncode = code
284+
285+
' Add the pair after the separator.
286+
code(SEPARATOR_POSITION_ + 1) = Mid(CODE_ALPHABET_, 1 + doubleMod(lat, ENCODING_BASE_), 1)
287+
code(SEPARATOR_POSITION_ + 2) = Mid(CODE_ALPHABET_, 1 + doubleMod(lng, ENCODING_BASE_), 1)
288+
lat = Int(lat / ENCODING_BASE_)
289+
lng = Int(lng / ENCODING_BASE_)
290+
291+
' Compute the pair section of the code.
292+
For i = Int(PAIR_CODE_LENGTH_ / 2) + 1 To 0 Step -2
293+
code(i) = Mid(CODE_ALPHABET_, 1 + doubleMod(lat, ENCODING_BASE_), 1)
294+
code(i + 1) = Mid(CODE_ALPHABET_, 1 + doubleMod(lng, ENCODING_BASE_), 1)
295+
lat = Int(lat / ENCODING_BASE_)
296+
lng = Int(lng / ENCODING_BASE_)
297+
Next
298+
Dim finalCodeLen As Integer
299+
finalCodeLen = codeLength
300+
' Add padding characters if necessary.
301+
If codeLength < SEPARATOR_POSITION_ Then
302+
For i = codeLength To SEPARATOR_POSITION_ - 1
303+
code(i) = PADDING_CHARACTER_
304+
Next
305+
finalCodeLen = SEPARATOR_POSITION_
306+
EndIf
307+
' Build the final code and return it.
308+
Dim finalCode As String
309+
For i = 0 To finalCodeLen
310+
finalCode = finalCode & code(i)
311+
Next
312+
OLCEncode = finalCode
247313
End Function
248314

249315
' Decodes an Open Location Code into an array of latlo, lnglo, latcenter, lngcenter, lathi, lnghi, codelength.
@@ -427,25 +493,6 @@ Private Function mergeCode(ByVal latCode As String, ByVal lngCode As String, ByV
427493
mergeCode = code
428494
End Function
429495

430-
' Encode a coordinate into an OLC sequence.
431-
Private Function encodeCoordinate(ByVal degrees As Double, ByVal digits As Integer) As String
432-
Dim code As String
433-
Dim remaining, precision As Double
434-
Dim i As Integer
435-
code = ""
436-
remaining = degrees
437-
precision = CDbl(ENCODING_BASE_)
438-
For i = 1 To digits
439-
Dim digitValue As Double
440-
' Get the latitude digit.
441-
digitValue = Int(remaining / precision)
442-
remaining = remaining - digitValue * precision
443-
code = code + Mid(CODE_ALPHABET_, digitValue + 1, 1)
444-
precision = precision / ENCODING_BASE_
445-
Next
446-
encodeCoordinate = code
447-
End Function
448-
449496
' Decode an OLC code made up of lat/lng pairs.
450497
Private Function decodePairs(code) As OLCArea
451498
Dim lat, lng, precision As Double
@@ -478,34 +525,6 @@ Private Function decodePairs(code) As OLCArea
478525
decodePairs = codeArea
479526
End Function
480527

481-
' Encode a location using the grid refinement method into an OLC string.
482-
' The grid refinement method divides the area into a grid of 4x5, and uses a
483-
' single character to refine the area. This allows default accuracy OLC codes
484-
' to be refined with just a single character.
485-
' This algorithm is used for codes longer than 10 digits.
486-
Private Function encodeGrid(ByVal latitude As Double, ByVal longitude As Double, ByVal codeLength As Integer) As String
487-
Dim code As String
488-
Dim latPlaceValue, lngPlaceValue, lat, lng As Double
489-
Dim i, row, col As Integer
490-
code = ""
491-
latPlaceValue = CDbl(GRID_SIZE_DEGREES_)
492-
lngPlaceValue = CDbl(GRID_SIZE_DEGREES_)
493-
' Adjust latitude and longitude so they fall into positive ranges and get the offset for the required places.
494-
lat = doubleMod(latitude + LATITUDE_MAX_, latPlaceValue)
495-
lng = doubleMod(longitude + LONGITUDE_MAX_, lngPlaceValue)
496-
For i = 1 To codeLength
497-
' Work out the row and column.
498-
row = Int(lat / (latPlaceValue / GRID_ROWS_))
499-
col = Int(lng / (lngPlaceValue / GRID_COLUMNS_))
500-
latPlaceValue = latPlaceValue / GRID_ROWS_
501-
lngPlaceValue = lngPlaceValue / GRID_COLUMNS_
502-
lat = lat - row * latPlaceValue
503-
lng = lng - col * lngPlaceValue
504-
code = code + Mid(CODE_ALPHABET_, row * GRID_COLUMNS_ + col + 1, 1)
505-
Next
506-
encodeGrid = code
507-
End Function
508-
509528
' Decode the grid refinement portion of an OLC code.
510529
Private Function decodeGrid(ByVal code As String) As OLCArea
511530
Dim gridOffSet As OLCArea
@@ -542,15 +561,6 @@ Private Function doubleMax(ByVal number1 As Double, ByVal number2 As Double) As
542561
End If
543562
End Function
544563

545-
' Provide a min function.
546-
Private Function doubleMin(ByVal number1 As Double, ByVal number2 As Double) As Double
547-
If number1 < number2 Then
548-
doubleMin = number1
549-
Else
550-
doubleMin = number2
551-
End If
552-
End Function
553-
554564
' Provide an ABS function for doubles.
555565
Private Function doubleABS(ByVal number As Double) As Double
556566
If number < 0 Then
@@ -630,17 +640,17 @@ Sub TestOLCLibrary()
630640
encodingCodes(3) = "7FG49QCJ+2VXGJ"
631641
encodingCoordinates(3) = Array(20.3701135, 2.78223535156, 20.370113, 2.782234375, 20.370114, 2.78223632813)
632642
encodingCodes(4) = "8FVC2222+22"
633-
encodingCoordinates(4) = Array(47.0000625, 8.0000625, 47#, 8#, 47.000125, 8.000125)
643+
encodingCoordinates(4) = Array(47.0000625, 8.0000625, 47, 8, 47.000125, 8.000125)
634644
encodingCodes(5) = "4VCPPQGP+Q9"
635645
encodingCoordinates(5) = Array(-41.2730625, 174.7859375, -41.273125, 174.785875, -41.273, 174.786)
636646
encodingCodes(6) = "62G20000+"
637-
encodingCoordinates(6) = Array(0.5, -179.5, 0#, -180#, 1, -179)
647+
encodingCoordinates(6) = Array(0.5, -179.5, 0, -180, 1, -179)
638648
encodingCodes(7) = "22220000+"
639649
encodingCoordinates(7) = Array(-89.5, -179.5, -90, -180, -89, -179)
640650
encodingCodes(8) = "7FG40000+"
641-
encodingCoordinates(8) = Array(20.5, 2.5, 20#, 2#, 21#, 3#)
651+
encodingCoordinates(8) = Array(20.5, 2.5, 20, 2, 21, 3#)
642652
encodingCodes(9) = "22222222+22"
643-
encodingCoordinates(9) = Array(-89.9999375, -179.9999375, -90#, -180#, -89.999875, -179.999875)
653+
encodingCoordinates(9) = Array(-89.9999375, -179.9999375, -90, -180, -89.999875, -179.999875)
644654
encodingCodes(10) = "6VGX0000+"
645655
encodingCoordinates(10) = Array(0.5, 179.5, 0, 179, 1, 180)
646656
encodingCodes(11) = "CFX30000+"
@@ -720,6 +730,5 @@ Sub TestOLCLibrary()
720730
Exit Sub
721731
End If
722732

723-
724733
MsgBox ("All tests pass")
725734
End Sub

0 commit comments

Comments
 (0)