B4A Library [B4X] PlusCodes Library

1629507005924.png


This is an implementation of the Open Location Code, OLC, library in B4X.
The class source is an adaption of the VBA source provided on GITBUB VBA Source

I stay these days in rural province area, common postal addresses are not a given, usually the nick name is sufficient for a village.
For example, in the time of Covid and travel restrictions, the number of online orders are grown up.
Delivery of online orders by services from outside not familiar with the village run into a time problem, houses in satellite areas are difficult to find.
The PlusCodes attached to the delivery addresses are often helpful in the last month, specially to deliver in satellite areas with minimum time delay.
I implement the PlusCodes in an B4A and B4J application for support. The library decode and encode PlusCodes addresses in longitude and latitude coordinates with given accuracy.
The coordinates are WGS84 compatible and can be used with other application. BTW Google Maps support PlusCodes Addresses directly.

I implemented OLC as standard class. So before use the class must be initialized.

For example running the test procedures

B4X:
            Private PC As PlusCode
            PC.Initialize
            PC.TestOLCLibrary

I modified the code where necessary (string operation in most cases) and changed the MSGBOX calls to LOG and LOGERROR in case of B4J specially in the included test routine
I did not test the library with B4I. There are no UI elements and the source code does not include platform relevant instructions.

The following ist part of the GITHUB Documentation....

The intent Is To provide the core functions of encoding, decoding, shorten And recovery.
A full Plus Code Is a "Global Code" such As 7MJCH93V+7F having 8 characters before the “+” sign

Plus Codes include a “+” so that they are easy To recognize, both For people And computers.
The “+” sign Is added after eight characters of the Plus Code For full codes;
it’s added after the first two characters For short Plus Codes.

Description of public methods of the class PLUSCODES
For more details see the source code provided in the b4xLib file
The source code has more information in the header and includes the original VBA implementation

Encoding
=========
OLCEncode(latitude, longitude, length)
This returns the Open Location Code For the passed latitude And longitude (in decimal degrees).

If the length parameter Is Null, the standard precision length (CODE_PRECISION_NORMAL) will be used.
This provides an area that Is 1/8000 x 1/8000 degree in size, roughly 14x14 meters.
If CODE_PRECISION_EXTRA Is specified As length, the area of the code will be roughly 2x3 meters.

Decoding
=========
Two decoding methods are provided. One returns a data structure, the other returns an Array And Is more suited To use within a spreadsheet.

OLCDecode(code)
===============
This decodes the passed Open Location Code, And returns an OLCArea data structure, which has the following fields:

latLo: The latitude of the south-west corner of the code area.
lngLo: The longitude of the south-west corner of the code area.
latCenter: The latitude of the center of the code area.
lngCenter: The longitude of the center of the code area.
latHi: The latitude of the north-east corner of the code area.
lngHi: The longitude of the north-east corner of the code area.
codeLength: The number of digits in the code.

OLCDecode2Array(code)
=====================
This returns a list of the fields from the OLCArea data structure, in the following order:

latLo, lngLo, latCenter, lngCenter, latHi, lngHi, codeLength

Shortening And Recovery
=======================
The codes returned by OLCEncode are globally unique, but often locally unique Is sufficient.
For example, 796RWF8Q+WF can be shortened To WF8Q+WF, relative To Praia, Cape Verde.

This works because 796RWF8Q+WF Is the nearest match To the location.

OLCShorten(code, latitude, longitude)
=====================================
This removes As many digits from the code As possible,
so that it Is still the nearest match To the passed location.
Even If six Or more digits can be removed, we suggest only removing four so that the codes are used consistently.

OLCRecoverNearest(code, latitude, longitude)
=============================================
This uses the specified location To extend the short code And returns the nearest matching full length code.

Part of the library source is the protocol of the test run

Any improvements of library or comments are welcome
I have a weak internet connection here, so can happen I will answer delayed

Version 210821.01 is attached
 

Attachments

  • PlusCode.b4xlib
    17.2 KB · Views: 401

Theera

Well-Known Member
Licensed User
Longtime User
Is there example for test?
 

mading1309

Member
Licensed User
Longtime User
Is there example for test?
Hello Theera

It's been a while since I touched the source code. My internet connection here in Paraguay has not improved significantly (remote area). As far as I remember there is a test routine in the library.

I'll have a look tonight and give you a hint.
 

mading1309

Member
Licensed User
Longtime User
Hello Theera

It's been a while since I touched the source code. My internet connection here in Paraguay has not improved significantly (remote area). As far as I remember there is a test routine in the library.

I'll have a look tonight and give you a hint.
Hello Theera

The source code of the library (B4xlib is a ZIP file) contains a description of the library, the GITBUB source code and a test function. The test function uses the test data from the GITHUB source. I have simply taken this part of the source code from the source code I found on my notebook.

Test Function for PlusCode Library, included in the Library:
'
' This is a subroutine to test the functions of the library, using test data
' from the Github project. If you make any changes to the above code, run this
' subroutine to check that your changes have not introduced errors. If you
' identify tests that are faulty or would like to add tests, go to the
' Github project and raise an issue.
Public Sub TestOLCLibrary As String
    Private i As Int
    Private calcode As String
    Private OLCArea As OLCArea

    Private validity(17) As List
    ' Fields code,isValid,isShort,isFull
'     0 8fwc2345+G6            valid            notshort        full
'     1 8FWC2345+G6G            valid            notshort        full
'     2 8fwc2345+            valid            notshort        full
'     3 8FWCX400+            valid            notshort        full
'     4 WC2345+G6g            valid            Short            notfull
'     5 2345+G6                valid            short            notfull
'     6 45+G6                valid            short            notfull
'     7 +G6                    valid            short            notfull
'     8 G+                    notvalid        notshort        notfull
'     9 +                    notvalid        notshort        notfull
'    10 8FWC2345+G            notvalid        notshort        notfull
'    11 8FWC2_45+G6            notvalid        notshort        notfull
'    12 8FWC2η45+G6            notvalid        notshort        notfull
'    13 8FWC2345+G6+            notvalid        notshort        notfull
'    14 8FWC2300+G6            notvalid        notshort        notfull
'    15 WC2300+G6g            notvalid        notshort        notfull
'    16 WC2345+G                notvalid        notshort        notfull  
'  
    validity(0).Initialize2 (Array As String ("8fwc2345+G6", "true", "false", "true"))
    validity(1).Initialize2 (Array As String ("8FWC2345+G6G", "true", "false", "true"))
    validity(2).Initialize2 (Array As String ("8fwc2345+", "true", "false", "true"))  
    validity(3).Initialize2 (Array As String ("8FWCX400+", "true", "false", "true")) 'behind separator at least 1 sign
    validity(4).Initialize2 (Array As String ("WC2345+G6g", "true", "true", "false"))
    validity(5).Initialize2 (Array As String ("2345+G6", "true", "true", "false"))
    validity(6).Initialize2 (Array As String ("45+G6", "true", "true", "false"))
    validity(7).Initialize2 (Array As String ("+G6", "true", "true", "false"))
    validity(8).Initialize2 (Array As String ("G+", "false", "false", "false"))
    validity(9).Initialize2 (Array As String ("+", "false", "false", "false"))
    validity(10).Initialize2 (Array As String ("8FWC2345+G", "false", "false", "false"))
    validity(11).Initialize2 (Array As String ("8FWC2_45+G6", "false", "false", "false"))
    validity(12).Initialize2 (Array As String ("8FWC2η45+G6", "false", "false", "false"))
    validity(13).Initialize2 (Array As String ("8FWC2345+G6+", "false", "false", "false"))
    validity(14).Initialize2 (Array As String ("8FWC2300+G6", "false", "false", "false"))
    validity(15).Initialize2 (Array As String ("WC2300+G6g", "false", "false", "false"))
    validity(16).Initialize2 (Array As String ("WC2345+G", "false", "false", "false"))
    For i = 0 To validity.Length-1
        Log ("  ")
        Private v, s, f As Boolean
        Log ($"Code [${i}] : ${validity(i).Get(0)}"$)
       
        v = OLCIsValid(validity(i).Get(0))
        #if B4j
            Dim output As String = $"IsValid, ${IIf(validity(i).get(1)=v.As (String),"Okay","Failed")} => expected: ${validity(i).get(1)}, actual: ${v}"$
            If validity(i).get(1)=v.As (String) Then
                Log (output)      
            Else
                LogError (output)
            End If
        #else
            Log ($"IsValid, ${IIf(validity(i).get(1)=v.As (String),"Okay","Failed")} => expected: ${validity(i).get(1)}, actual: ${v}"$)
        #End If
        s = OLCIsShort(validity(i).Get(0))
        #if B4j
        Dim output As String = $"IsShort, ${IIf(validity(i).get(2)=s.As (String),"Okay","Failed")} => expected: ${validity(i).get(2)}, actual: ${s}"$
            If validity(i).get(2)=s.As (String) Then
                Log (output)      
            Else
                LogError (output)
            End If
        #else
            Log ($"IsShort, ${IIf(validity(i).get(2)=s.As (String),"Okay","Failed")} => expected: ${validity(i).get(2)}, actual: ${s}"$)
        #end if
        f = OLCIsFull(validity(i).Get(0))
        #if B4j
        Dim output As String = $"IsFull, ${IIf(validity(i).get(3)=f.As (String),"Okay","Failed")} => expected: ${validity(i).get(3)}, actual: ${f}"$
            If validity(i).get(3)=f.As (String) Then
                Log (output)      
            Else
                LogError (output)
            End If
        #else
            Log ($"IsFull, ${IIf(validity(i).get(3)=f.As (String),"Okay","Failed")} => expected: ${validity(i).get(3)}, actual: ${f}"$)
        #end if
'        If (v <> (validity(i).Get(1) = "true")) Or (s <> (validity(i).Get(2) = "true")) Or (f <> (validity(i).Get(3) = "true")) Then
'            Return False
'        End If
        Log ("  ")
    Next

    Private encodingCodes(15) As String
    ' Fields are lat,lng,latLo,lngLo,latHi,lngHi
    Private encodingCoordinates(15) As List
    encodingCodes(0) = "7fG49Q00+"
    encodingCoordinates(0).Initialize2 (Array As Double (20.375, 2.775, 20.35, 2.75, 20.4, 2.8))
    encodingCodes(1) = "7FG49QCJ+2v"
    encodingCoordinates(1).Initialize2 (Array As Double (20.3700625, 2.7821875, 20.37, 2.782125, 20.370125, 2.78225))
    encodingCodes(2) = "7FG49QCJ+2VX"
    encodingCoordinates(2).Initialize2 (Array As Double (20.3701125, 2.782234375, 20.3701, 2.78221875, 20.370125, 2.78225))
    encodingCodes(3) = "7FG49QCJ+2VXGJ"
    encodingCoordinates(3).Initialize2 (Array As Double (20.3701135, 2.78223535156, 20.370113, 2.782234375, 20.370114, 2.78223632813))
    encodingCodes(4) = "8FVC2222+22"
    encodingCoordinates(4).Initialize2 (Array As Double (47.0000625, 8.0000625, 47, 8, 47.000125, 8.000125))
    encodingCodes(5) = "4VCPPQGP+Q9"
    encodingCoordinates(5).Initialize2 (Array As Double (-41.2730625, 174.7859375, -41.273125, 174.785875, -41.273, 174.786))
    encodingCodes(6) = "62G20000+"
    encodingCoordinates(6).Initialize2 (Array As Double (0.5, -179.5, 0, -180, 1, -179))
    encodingCodes(7) = "22220000+"
    encodingCoordinates(7).Initialize2 (Array As Double (-89.5, -179.5, -90, -180, -89, -179))
    encodingCodes(8) = "7FG40000+"
    encodingCoordinates(8).Initialize2 (Array As Double (20.5, 2.5, 20, 2.0, 21.0, 3.0))
    encodingCodes(9) = "22222222+22"
    encodingCoordinates(9).Initialize2 (Array As Double (-89.9999375, -179.9999375, -90.0, -180.0, -89.999875, -179.999875))
    encodingCodes(10) = "6VGX0000+"
    encodingCoordinates(10).Initialize2 (Array As Double (0.5, 179.5, 0, 179, 1, 180))
    encodingCodes(11) = "CFX30000+"
    encodingCoordinates(11).Initialize2 (Array As Double (90, 1, 89, 1, 90, 2))
    encodingCodes(12) = "CFX30000+"
    encodingCoordinates(12).Initialize2 (Array As Double (92, 1, 89, 1, 90, 2))
    encodingCodes(13) = "62H20000+"
    encodingCoordinates(13).Initialize2 (Array As Double (1, 180, 1, -180, 2, -179))
    encodingCodes(14) = "62H30000+"
    encodingCoordinates(14).Initialize2 (Array As Double (1, 181, 1, -179, 2, -178))
    For i = 0 To encodingCodes.Length-1
        Log ("  ")
        Log ($"Encoding test [${i}] : ${encodingCodes(i)}"$)
        OLCArea = OLCDecode(encodingCodes(i))
       
        calcode = OLCEncode(encodingCoordinates(i).get(0), encodingCoordinates(i).get(1), OLCArea.CodeLength)
        Log ($"code generation expected: ${encodingCodes(i)}, actual: ${calcode};"$)
        Do Until encodingCodes(i).ToUpperCase=calcode
            calcode = OLCEncode(encodingCoordinates(i).get(0), encodingCoordinates(i).get(1), OLCArea.CodeLength)
            Log ($"code generation expected: ${encodingCodes(i)}, actual: ${calcode}"$)  
        Loop
       
        calcode = OLCEncode(OLCArea.LatCenter, OLCArea.LngCenter, OLCArea.CodeLength)
        Log ($"code recovery expected: ${encodingCodes(i)}, actual: ${calcode}"$)
        Log ($"coordinate check: "$)
        Log ($"     => given : ${OLCArea.latlo}, ${OLCArea.LngLo} ${OLCArea.LatHi}, ${OLCArea.LngHi}"$)
        Log ($"     => expected: " ${encodingCoordinates(i).get(2)}, ${encodingCoordinates(i).get(3)}, ${encodingCoordinates(i).get(4)}, ${encodingCoordinates(i).get(5)}"$)
    Next

    Private shortCodes(11) As List
    shortCodes(0).Initialize2 (Array As String ("9C3W9QCJ+2VX", "+2VX"))
    shortCodes(1).Initialize2 (Array As String ("9C3W9QCJ+2VX", "CJ+2VX"))
    shortCodes(2).Initialize2 (Array As String ("9C3W9QCJ+2VX", "CJ+2VX"))
    shortCodes(3).Initialize2 (Array As String ("9C3W9QCJ+2VX", "CJ+2VX"))
    shortCodes(4).Initialize2 (Array As String ("9C3W9QCJ+2VX", "CJ+2VX"))
    shortCodes(5).Initialize2 (Array As String ("9C3W9QCJ+2VX", "9QCJ+2VX"))
    shortCodes(6).Initialize2 (Array As String ("9C3W9QCJ+2VX", "9QCJ+2VX"))
    shortCodes(7).Initialize2 (Array As String ("9C3W9QCJ+2VX", "9QCJ+2VX"))
    shortCodes(8).Initialize2 (Array As String ("9C3W9QCJ+2VX", "9QCJ+2VX"))
    shortCodes(9).Initialize2 (Array As String ("8FJFW222+", "22+"))
    shortCodes(10).Initialize2 (Array As String ("796RXG22+", "22+"))
    Private shortCoordinates(11) As List
    shortCoordinates(0).Initialize2 (Array As Double (51.3701125, -1.217765625))
    shortCoordinates(1).Initialize2 (Array As Double (51.3708675, -1.217765625))
    shortCoordinates(2).Initialize2 (Array As Double (51.3693575, -1.217765625))
    shortCoordinates(3).Initialize2 (Array As Double (51.3701125, -1.218520625))
    shortCoordinates(4).Initialize2 (Array As Double (51.3701125, -1.217010625))
    shortCoordinates(5).Initialize2 (Array As Double (51.3852125, -1.217765625))
    shortCoordinates(6).Initialize2 (Array As Double (51.3550125, -1.217765625))
    shortCoordinates(7).Initialize2 (Array As Double (51.3701125, -1.232865625))
    shortCoordinates(8).Initialize2 (Array As Double (51.3701125, -1.202665625))
    shortCoordinates(9).Initialize2 (Array As Double (42.899, 9.012))
    shortCoordinates(10).Initialize2 (Array As Double (14.95125, -23.5001))
    For i = 0 To shortCodes.Length-1
        Log ("  ")
        calcode = OLCShorten(shortCodes(i).get(0), shortCoordinates(i).get(0), shortCoordinates(i).get(1))
        Log ($"Shorten test ${i}, expected: ${shortCodes(i).get(1)}, actual: ${calcode}"$)
        Dim str As String = shortCodes(i).Get(1)
        calcode = OLCRecoverNearest(shortCodes(i).get(1), shortCoordinates(i).get(0), shortCoordinates(i).get(1))
        Log ($"Recover test ${i}, expected: ${shortCodes(i).get(0)}, actual: ${calcode}"$)
        str = shortCodes(i).Get(0)
       
        Do Until str.Contains(calcode.SubString2(0,calcode.length-2))
            calcode = OLCRecoverNearest(shortCodes(i).get(1), shortCoordinates(i).get(0), shortCoordinates(i).get(1))
            Log ($"Recover test ${i}, expected: ${shortCodes(i).get(0)}, actual: ${calcode}"$)
        Loop
    Next

    ' North pole recovery test.
    calcode = OLCRecoverNearest("2222+22", 89.6, 0.0)
    Log ("  ")
    Log ($"North pole recovery test, expected: CFX22222+22, actual: ${calcode}"$)
    If calcode <> "CFX22222+22" Then Return False

    ' South pole recovery test.
    calcode = OLCRecoverNearest("XXXXXX+XX", -81.0, 0.0)
    Log ("  ")
    Log ($"South pole recovery test, expected: 2CXXXXXX+XX, actual: ${calcode}"$)
    If calcode <> "2CXXXXXX+XX" Then Return False

    Log ("All tests finished, check log output if passed")',"OLCLibrary Test")
    Return True
End Sub


or simply use my other test routine. The outputs are logs.


My additional Testcode:
Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

'This event will be called once, before the page becomes visible.
Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    Private PC As PlusCode
    PC.Initialize
'    PC.TestOLCLibrary
Private lat As Double = 12.293918
Private lon As Double = 122.647114
    Log (PC.OLCEncode(lat,lon,PC.CODE_PRECISION_EXTRA_))  '7Q447JVW+HR9
    Log (PC.OLCEncode(lat,lon,PC.CODE_PRECISION_EXTRA_+4))  '7Q447JVW+HR9PP
    Log (PC.OLCEncode(lat,lon,PC.CODE_PRECISION_NORMAL_))  '7Q447JVW+HR
    Log (PC.OLCencode(11.577735039107786, 122.75344180996201,PC.CODE_PRECISION_EXTRA_))
    Log (PC.OLCDecode2array("7Q34QQ8V+6H7"))
    Log (PC.OLCDecode2array("7Q34HQH3+39"))
    Log ($"lat : ${lat} lon : ${lon}"$)
    Log (PC.OLCDecode2Array("7Q447JVW+HR9"))
    Log (PC.OLCDecode("7Q447JVW+HR9"))
    Log (PC.Distance(PC.OLCDecode("7Q447JVW+HR9PP"))*1000)
    Log (PC.DistanceLoCenter(PC.OLCDecode("7Q447JVW+HR9PP"))*1000)
    Log (PC.DistancehiCenter(PC.OLCDecode("7Q447JVW+HR9PP"))*1000)
    Log (PC.Distance(PC.OLCDecode("7Q447JVW+HR9"))*1000)
    Log (PC.DistanceLoCenter(PC.OLCDecode("7Q447JVW+HR9"))*1000)
    Log (PC.DistancehiCenter(PC.OLCDecode("7Q447JVW+HR9"))*1000)
    Log (PC.Distance(PC.OLCDecode("7Q447JVW+HR"))*1000)
    Log (PC.DistanceLoCenter(PC.OLCDecode("7Q447JVW+HR"))*1000)
    Log (PC.DistancehiCenter(PC.OLCDecode("7Q447JVW+HR"))*1000)
End Sub

I hope I have been able to help you a little.
 
Top