Code Samples for Businesses, Schools & Developers

Click any image to view a larger version

Last Updated 21 May 2022



When working with online maps from a provider such as Google, Bing or Open Street Maps, adding boundary paths, shapes or routes as additional map layers can require a very large number of co-ordinates and significantly increase the length of the URL required to generate the map.

As a result, the number of characters in the URL may exceed the allowed limit for the web browser control based on Internet Explorer (2083 characters).
Alternatively, if using a static map image, it may exceed the character limit imposed by the map provider. This is currently just over 16,000 for Google maps.

The three example screenshots below show postcode district boundary paths and a route plan which were created as added map layers.

BoundaryPathTA2

BoundaryPathAB42

RoutePlan

For more information on adding layers to online maps , see my article Annotating Google Maps elsewhere on this website.

There are 2 main methods of handling these issues to reduce the overall URL length needed:
1.   Encoding the co-ordinates
2.   Sampling the data



1.   Encoding the co-ordinates

Google Maps use an Encoded Polyline Algorithm Format for this purpose.

This is an extract from that article.

Polyline encoding is a lossy compression algorithm that allows you to store a series of coordinates as a single string. Point coordinates are encoded using signed values. If you only have a few static points, you may also wish to use the interactive polyline encoding utility.

The encoding process converts a binary value into a series of character codes for ASCII characters using the familiar base64 encoding scheme: to ensure proper display of these characters, encoded values are summed with 63 (the ASCII character '?') before converting them into ASCII. The algorithm also checks for additional character codes for a given point by checking the least significant bit of each byte group; if this bit is set to 1, the point is not yet fully formed and additional data must follow.

Additionally, to conserve space, points only include the offset from the previous point (except of course for the first point). All points are encoded in Base64 as signed integers, as latitudes and longitudes are signed values. The encoding format within a polyline needs to represent two coordinates representing latitude and longitude to a reasonable precision. Given a maximum longitude of +/- 180 degrees to a precision of 5 decimal places (180.00000 to -180.00000), this results in the need for a 32 bit signed binary integer value.

An example dataset of three map points is shown below both as co-ordinates and as encoded values

EncodeExample The link shown above describes the steps needed to achieve this outcome.

I have converted these steps into the VBA function shown below.
In fact, several of the steps are NOT required and can safely be omitted:

Function EncPolyline(sngValue) As String

'used to calculate the EncodedPolylineAlgorithmFormat values for Google maps
'https://developers.google.com/maps/documentation/utilities/polylinealgorithm?hl=pt-BR
'https://developers.google.com/maps/documentation/utilities/polylineutility?hl=pt-br

      Dim lngE5 As Long, strBin As String
      Dim strTemp1, strTemp2, strTemp3 As String
      Dim strDec As String, strOut As String

      '==========================================
      'The steps for encoding such a signed value are specified below.
      'Take the initial signed value:
      '-179.9832104

      If IsNull(sngValue) Then Exit Function

      '1. Take the decimal value and multiply it by 1e5, rounding the result:
      '-17998321

      lngE5 = Fix(sngValue * 100000)
      'Debug.Print lngE5

      '2. Convert the decimal value to binary.
      'Note that a negative value must be calculated using its two's complement
      'by inverting the binary value and adding one to the result:
      '00000001 00010010 10100001 11110001      'start value
      '11111110 11101101 01011110 00001110       'two's complement
      '11111110 11101101 01011110 00001111      '... and add one

      strBin = DecToBin(lngE5)
      'Debug.Print strBin

      '3. Left-shift the binary value one bit:
      '11111101 11011010 10111100 00011110

      strBin = Mid(strBin, 2) & "0"

      '4. If the original decimal value is negative, invert this encoding:
      'i.e. make all 0 into 1 and vice versa
      '00000010 00100101 01000011 11100001

      If sngValue < 0 Then
            strBin = Replace(strBin, "1", "2")
            strBin = Replace(strBin, "0", "1")
            strBin = Replace(strBin, "2", "0")
      End If
     'Debug.Print strBin

     '5. Break the binary value out into 5-bit chunks (starting from the right hand side):
      '00001 00010 01010 10000 11111 00001
      'This can be bypassed

      'strTemp1 = Mid(strBin, 3, 5) & " " & Mid(strBin, 8, 5) & _
            " " & Mid(strBin, 13, 5) & " " & Mid(strBin, 18, 5) & _
            " " & Mid(strBin, 23, 5) & " " & Mid(strBin, 28, 5)

     '6. Place the 5-bit chunks into reverse order:
      '00001 11111 10000 01010 00010 00001
      'This can be bypassed

      'strTemp2 = Mid(strBin, 28, 5) & " " & Mid(strBin, 23, 5) & _
            " " & Mid(strBin, 18, 5) & " " & Mid(strBin, 13, 5) & _
            " " & Mid(strBin, 8, 5) & " " & Mid(strBin, 3, 5)

      '7. OR each value with 0x20 if another bit chunk follows:
      '100001 111111 110000 101010 100010
      'This can be bypassed

      'strTemp3 = "1" & Mid(strBin, 28, 5) & " 1" & Mid(strBin, 23, 5) & _
            " 1" & Mid(strBin, 18, 5) & " 1" & Mid(strBin, 13, 5) & _
            " 1" & Mid(strBin, 8, 5) & " 0" & Mid(strBin, 3, 5)

      '8. Convert each value to decimal:
     '33 63 48 42 34 1
      'This can be bypassed

      'strDec = Bin2Dec("1" & Mid(strBin, 28, 5)) & " " & Bin2Dec("1" & Mid(strBin, 23, 5)) & _
            " " & Bin2Dec("1" & Mid(strBin, 18, 5)) & " " & Bin2Dec("1" & Mid(strBin, 13, 5)) & _
            " " & Bin2Dec("1" & Mid(strBin, 8, 5)) & " " & Bin2Dec("0" & Mid(strBin, 3, 5))

      '9. Add 63 to each value:
      '96 126 111 105 97 64
      'This can be bypassed

      'strDec = (63 + Bin2Dec("1" & Mid(strBin, 28, 5))) & " " & (63 + Bin2Dec("1" & Mid(strBin, 23, 5))) & _
            " " & (63 + Bin2Dec("1" & Mid(strBin, 18, 5))) & " " & (63 + Bin2Dec("1" & Mid(strBin, 13, 5))) & _
            " " & (63 + Bin2Dec("1" & Mid(strBin, 8, 5))) & " " & (63 + Bin2Dec("0" & Mid(strBin, 3, 5)))

      '10. Convert each value to its ASCII equivalent:
      '`~oia@

      strOut = Chr(63 + Bin2Dec("1" & Mid(strBin, 28, 5))) & Chr(63 + Bin2Dec("1" & Mid(strBin, 23, 5))) & _
            Chr(63 + Bin2Dec("1" & Mid(strBin, 18, 5))) & Chr(63 + Bin2Dec("1" & Mid(strBin, 13, 5))) & _
            Chr(63 + Bin2Dec("1" & Mid(strBin, 8, 5))) & Chr(63 + Bin2Dec("0" & Mid(strBin, 3, 5)))

      'Debug.Print sngValue, strOut
      EncPolyline = strOut

End Function

'-------------------------------------------

Sub TESTEncode()

      Dim sngValue As Single
      sngValue = 54.3847
      Debug.Print EncPolyline(sngValue)
End Sub



The screenshot below shows the steps involved in creating encoded data for a complete set of boundary path co-ordinates:

BoundaryPathDataTable


2.   Sampling the data

Although encoding significantly reduces the overall string length, by itself this may not be sufficient to keep within the URL character limit for complex paths.

In addition, it may be necessary to sample the data.
Instead of using each set of co-ordinates, use alternate values or every third, fourth, fifth (etc) set of co-ordinates as necessary

The screenshot below shows the total length for co-ordinate paths and the encoded equivalent strings for some of the more complex postcode district paths

BoundaryPathLengthData

The longer the path, the fewer coordinates are sampled in order to make the encoded path length manageable for creating maps.
This will of course reduce the accuracy of the displayed path. However, it is normally good enough to not cause any problems

BoundaryPathYO1

In certain cases such as where the boundary path follows the coastline of a number of islands, sampling will result in a very approximate outline
However, it should still be recognisable in terms of the overall outcome.

The most extreme example in the UK is for the postcode district ZE2. This covers a group of islands called the Shetland Isles off the north coast of Scotland.
The boundary path is very complex as a result and mapping the full path would require over 40,000 characters which far exceeds the allowed character limits.

Sampling every 20th point together with encoding reduces the path length to just over 1400 characters which is well below the character limit

BoundaryPathDataZE2
The outcome is shown below - certainly not perfect but it is recognisable

BoundaryPathZE2
When constructing a path for the map layer, each co-ordinate is encoded separately.

Apart from the first set of co-ordinates, the positions of all subsequent points are encoded as locations relative to the previous point.
This further reduces the space required.

The complete set of encoded values in the boundary path are then combined as a string using the following function:

Function GetEncPath()

'gets Google encode path for drawing postcode boundary on static map

      Dim strEncPath As String
      Dim rst As DAO.Recordset

      strEncPath = ""

      'check saved data
      Set rst = CurrentDb.OpenRecordset("tblPostcodeBoundaryPath", dbOpenDynaset)

      With rst
            .MoveFirst
            Do Until .EOF
            strEncPath = strEncPath & !EncPoint
            .MoveNext
            Loop
            .Close

      End With
      GetEncPath = strEncPath
      'Debug.Print strEncPath

      Set rst = Nothing

End Function




I hope this information helps other developers trying to add boundary data as additional map layers.

Further information to follow as part of my series of articles on Annotating Google Maps



Colin Riddington           Mendip Data Systems                 Last Updated 21 May 2022



Return to Code Samples Page




Return to Top