├── LICENSE ├── README.md ├── docs ├── CNAME └── index.htm ├── excel-geocoder-samples.xls ├── excel-geocoder-samples.xlsm └── geocoder.bas /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ted Dowd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # excel-geocoder 2 | A VBA application for geocoding and reverse geocoding in Excel. Supports both Google's free and enterprise for business geocoder (Maps API for Work). 3 | 4 | 5 | # Background 6 | GIS and geospatial data science applications will usually require geocoding of locations or reverse geocoding of latitude/longitude at some point in the analysis. While most of this analysis is frequently done in something a little more involved than Excel (python, arcGIS, ESRI, etc.), sometimes I have found that doing something quick with a dataset in Excel can be more efficient than working with a SQL DB or creating a python script. 7 | 8 | This project was influenced by a blog post by josephglover. josephglover's module on accessing the free Google geocoder was the foundation which I used to make the reverse geocoder and to add flexibility to use Google's Maps API for Work Enterprise geocoder. 9 | 10 | 11 | # Prerequisites 12 | * Enable developer tab in Excel. Instructions from MSFT can be [found here](https://msdn.microsoft.com/en-us/library/bb608625.aspx). 13 | * Within the VB IDE, add "Microsoft XML, v6.0" as a Reference. Can be found within *Tools* - *References*. 14 | 15 | 16 | # Installation 17 | * Import the .bas file into your project. 18 | * To use Google's Maps API for Work geocoder, view the code in the VB IDE and change the `gintType` constant equal to `1` and insert your Google Client ID and Google Secret Key into the `gstrClientID` and `gstrKey` constants respectively. 19 | * To use Google's API Premium Plan, change the `gintType` constant equal to `2` and insert your API key into the `gstrKey` constant. 20 | * To use Google's Free Geocoding API, change the `gintType` constant equal to `0`. 21 | * Note that as of late Summer/early Fall 2018, Google's Free Geocoding API now also requires a key. More information can be [found here](https://developers.google.com/maps/documentation/geocoding/usage-and-billing) and [here](https://developers.google.com/maps/documentation/geocoding/get-api-key). Still set your `gintType` to `0` and insert your new API key into the `gstrKey` constant and you should still be able to geocode in Excel. 22 | 23 | 24 | # Usage 25 | * `=AddressGeocode(address)` 26 | * Takes in the address of the location we want to geocode and returns the first latitude, longitude pair from the geocoder. 27 | * `=ReverseGeocode(lat,long)` 28 | * Takes in a latitude, longitude pair and returns the first formatted address from the geocoder. 29 | 30 | 31 | # TODO 32 | * Clean up code around key management as a result of Google's 2018 key management changes 33 | * Test cases 34 | * Functionality for Bing Maps, Data Science Toolkit, etc. 35 | * Fix for forcing too many requests at one time 36 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | www.excelgeocoder.com -------------------------------------------------------------------------------- /docs/index.htm: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | contact 6 | 7 | 8 | -------------------------------------------------------------------------------- /excel-geocoder-samples.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdowd/excel-geocoder/95f69d2103ef02f2c707cdcdd3f7cc85b1488a54/excel-geocoder-samples.xls -------------------------------------------------------------------------------- /excel-geocoder-samples.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdowd/excel-geocoder/95f69d2103ef02f2c707cdcdd3f7cc85b1488a54/excel-geocoder-samples.xlsm -------------------------------------------------------------------------------- /geocoder.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "geocoder" 2 | Option Explicit 3 | 4 | ' Domain and URL for Google API 5 | Public Const gstrGeocodingDomain = "https://maps.googleapis.com" 6 | Public Const gstrGeocodingURL = "/maps/api/geocode/xml?" 7 | 8 | ' set gintType = 1 to use the Enterprise Geocoder (requires clientID and key) 9 | ' set gintType = 2 to use the API Premium Plan (requires key) 10 | ' leave gintType = 0 to use the free-ish Google geocoder (now requires a key! see https://developers.google.com/maps/documentation/geocoding/get-api-key) 11 | Public Const gintType = 0 12 | 13 | ' key for Enterprise Geocoder or API Premium Plan or free-ish geocoder 14 | Public Const gstrKey = "" 15 | 16 | ' clientID for Enterprise Geocoder 17 | Public Const gstrClientID = "" 18 | 19 | ' kludge to not overdo the API calls and add a delay 20 | #If VBA7 Then 21 | Public Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 22 | #Else 23 | Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) 24 | #End If 25 | 26 | Public Function AddressGeocode(address As String) As String 27 | Dim strAddress As String 28 | Dim strQuery As String 29 | Dim strLatitude As String 30 | Dim strLongitude As String 31 | Dim strQueryBland As String 32 | 33 | strAddress = URLEncode(address) 34 | 35 | 'Assemble the query string 36 | strQuery = gstrGeocodingURL 37 | strQuery = strQuery & "address=" & strAddress 38 | If gintType = 0 Then ' free-ish Google Geocoder - now requires an API key! 39 | strQuery = strQuery & "&key=" & gstrKey 40 | ElseIf gintType = 1 Then ' Enterprise Geocoder 41 | strQuery = strQuery & "&client=" & gstrClientID 42 | strQuery = strQuery & "&signature=" & Base64_HMACSHA1(strQuery, gstrKey) 43 | ElseIf gintType = 2 Then ' API Premium Plan 44 | strQuery = strQuery & "&key=" & gstrKey 45 | End If 46 | 47 | 'define XML and HTTP components 48 | Dim googleResult As New MSXML2.DOMDocument60 49 | Dim googleService As New MSXML2.XMLHTTP60 50 | Dim oNodes As MSXML2.IXMLDOMNodeList 51 | Dim oNode As MSXML2.IXMLDOMNode 52 | 53 | Sleep (5) 54 | 55 | 'create HTTP request to query URL - make sure to have 56 | googleService.Open "GET", gstrGeocodingDomain & strQuery, False 57 | googleService.send 58 | googleResult.LoadXML (googleService.responseText) 59 | 60 | Set oNodes = googleResult.getElementsByTagName("geometry") 61 | 62 | If oNodes.Length = 1 Then 63 | For Each oNode In oNodes 64 | Debug.Print oNode.Text 65 | strLatitude = oNode.ChildNodes(0).ChildNodes(0).Text 66 | strLongitude = oNode.ChildNodes(0).ChildNodes(1).Text 67 | AddressGeocode = strLatitude & "," & strLongitude 68 | Next oNode 69 | Else 70 | AddressGeocode = "Not Found (try again, you may have done too many too fast)" 71 | End If 72 | End Function 73 | 74 | 75 | Public Function URLEncode(StringToEncode As String, Optional _ 76 | UsePlusRatherThanHexForSpace As Boolean = False) As String 77 | 78 | Dim TempAns As String 79 | Dim CurChr As Integer 80 | CurChr = 1 81 | Do Until CurChr - 1 = Len(StringToEncode) 82 | Select Case asc(Mid(StringToEncode, CurChr, 1)) 83 | Case 48 To 57, 65 To 90, 97 To 122 84 | TempAns = TempAns & Mid(StringToEncode, CurChr, 1) 85 | Case 32 86 | If UsePlusRatherThanHexForSpace = True Then 87 | TempAns = TempAns & "+" 88 | Else 89 | TempAns = TempAns & "%" & Hex(32) 90 | End If 91 | Case Else 92 | TempAns = TempAns & "%" & _ 93 | Format(Hex(asc(Mid(StringToEncode, _ 94 | CurChr, 1))), "00") 95 | End Select 96 | 97 | CurChr = CurChr + 1 98 | Loop 99 | 100 | URLEncode = TempAns 101 | End Function 102 | 103 | 104 | Public Function ReverseGeocode(lat As String, lng As String) As String 105 | Dim strAddress As String 106 | Dim strLat As String 107 | Dim strLng As String 108 | Dim strQuery As String 109 | Dim strLatitude As String 110 | Dim strLongitude As String 111 | 112 | strLat = URLEncode(lat) 113 | strLng = URLEncode(lng) 114 | 115 | 'Assemble the query string 116 | strQuery = gstrGeocodingURL 117 | strQuery = strQuery & "latlng=" & strLat & "," & strLng 118 | If gintType = 0 Then ' free-ish Google Geocoder - now requires an API key! 119 | strQuery = strQuery & "&key=" & gstrKey 120 | ElseIf gintType = 1 Then ' Enterprise Geocoder 121 | strQuery = strQuery & "&client=" & gstrClientID 122 | strQuery = strQuery & "&signature=" & Base64_HMACSHA1(strQuery, gstrKey) 123 | ElseIf gintType = 2 Then ' API Premium Plan 124 | strQuery = strQuery & "&key=" & gstrKey 125 | End If 126 | 127 | 'define XML and HTTP components 128 | Dim googleResult As New MSXML2.DOMDocument60 129 | Dim googleService As New MSXML2.XMLHTTP60 130 | Dim oNodes As MSXML2.IXMLDOMNodeList 131 | Dim oNode As MSXML2.IXMLDOMNode 132 | 133 | Sleep (5) 134 | 135 | 'create HTTP request to query URL - make sure to have 136 | googleService.Open "GET", gstrGeocodingDomain & strQuery, False 137 | googleService.send 138 | googleResult.LoadXML (googleService.responseText) 139 | 140 | Set oNodes = googleResult.getElementsByTagName("formatted_address") 141 | 142 | If oNodes.Length > 0 Then 143 | ReverseGeocode = oNodes.Item(0).Text 144 | Else 145 | ReverseGeocode = "Not Found (try again, you may have done too many too fast)" 146 | End If 147 | End Function 148 | 149 | 150 | Public Function Base64_HMACSHA1(ByVal strTextToHash As String, ByVal strSharedSecretKey As String) 151 | 152 | Dim asc As Object 153 | Dim enc As Object 154 | Dim TextToHash() As Byte 155 | Dim SharedSecretKey() As Byte 156 | Dim bytes() As Byte 157 | 158 | Set asc = CreateObject("System.Text.UTF8Encoding") 159 | Set enc = CreateObject("System.Security.Cryptography.HMACSHA1") 160 | 161 | strSharedSecretKey = Replace(Replace(strSharedSecretKey, "-", "+"), "_", "/") 162 | SharedSecretKey = Base64Decode(strSharedSecretKey) 163 | enc.Key = SharedSecretKey 164 | 165 | TextToHash = asc.Getbytes_4(strTextToHash) 166 | bytes = enc.ComputeHash_2((TextToHash)) 167 | Base64_HMACSHA1 = Replace(Replace(Base64Encode(bytes), "+", "-"), "/", "_") 168 | 169 | End Function 170 | 171 | 172 | Public Function Base64Decode(ByVal strData As String) As Byte() 173 | 174 | Dim objXML As MSXML2.DOMDocument60 175 | Dim objNode As MSXML2.IXMLDOMElement 176 | 177 | Set objXML = New MSXML2.DOMDocument60 178 | Set objNode = objXML.createElement("b64") 179 | objNode.DataType = "bin.base64" 180 | objNode.Text = strData 181 | Base64Decode = objNode.nodeTypedValue 182 | 183 | Set objNode = Nothing 184 | Set objXML = Nothing 185 | 186 | End Function 187 | 188 | 189 | Public Function Base64Encode(ByRef arrData() As Byte) As String 190 | 191 | Dim objXML As MSXML2.DOMDocument60 192 | Dim objNode As MSXML2.IXMLDOMElement 193 | 194 | Set objXML = New MSXML2.DOMDocument60 195 | Set objNode = objXML.createElement("b64") 196 | objNode.DataType = "bin.base64" 197 | objNode.nodeTypedValue = arrData 198 | Base64Encode = objNode.Text 199 | 200 | Set objNode = Nothing 201 | Set objXML = Nothing 202 | 203 | End Function 204 | --------------------------------------------------------------------------------