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 coordinates 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.
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 coordinates
2. Sampling the data
1. Encoding the coordinates
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 coordinates and as encoded values
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=ptBR
'https://developers.google.com/maps/documentation/utilities/polylineutility?hl=ptbr
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. Leftshift 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 5bit 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 5bit 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 coordinates:
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 coordinates, use alternate values or every third, fourth, fifth (etc) set of coordinates as necessary
The screenshot below shows the total length for coordinate paths and the encoded equivalent strings for some of the more complex postcode district paths
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
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
The outcome is shown below  certainly not perfect but it is recognisable
When constructing a path for the map layer, each coordinate is encoded separately.
Apart from the first set of coordinates, 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
