Complete Excel Address Data with Yahoo Placemaker Maps API


Geocode Excel Data with Yahoo Placemaker get it now

If you haven't read about Yahoo rules on doing this, please do.

Since we have done this in Google Maps, and in Bing Maps, I thought it would be worthwhile to see how it's done in Yahoo Placemaker (Yahoo maps is deprecated and has been superseded by OVI maps .. which I guess I might try next) .  


GeoCoding with Yahoo Placemaker - vs - Google Maps -vs- Bing Maps

I have already looked at geocoding with Bing Maps -vs- Google Maps and Google Maps is a clear winner. Yahoo Placemaker is pretty good at first glance even though it  claims not to be a geocoder.  Note that I am just using the basic REST URI query in all these examples - since the whole point to access directly from Excel. Comparing capabilities under Javascript would be a whole different ballgame.
  •  Yahoo require you to get a Key to use Placemaker which they call and Application ID. In this downloadable example, I am using my key. If you use this code for anything, please get your own and follow the Yahoo rules on usage. Google do not require you to get a key, but limit your usage to a small number of queries daily.
  • Yahoo was as good as or better than Google in dealing with ambiguity. 
  • The Yahoo response is clean and does not contain a bunch of unnecessary waffle.
  • The Yahoo response is even simpler than the Bing one, and easier to deal with and decode. It is so similar to Bing that I was able to use the same modules to deal with both, so implementing Placeholder was about a 30 minute job, mainly to find out what the field names were. 
  • Yahoo provides less detail than both Google and Bing, but most of what you would need is there. It does not provide a 'cleaned up, full address' field though, which the other 2 do. On the other hand, this can easily be constructed from other fields.
  • Its confusing that Yahoo Placeholder is not actually Yahoo Maps. Yahoo Maps is actually OVI maps, which I haven't tried .. yet. 

The implementation

Essentially this is exactly the same kind of implementation as for the Google Version, so please take a look at that first and we can concentrate on the differences. 

The parameter sheet

The jSon response from Yahoo is a different structure to Google, but similar to Bing. It can easily be accommodated in the same structure as we used for the Google version. Here it is with a couple of extra columns specific to Yahoo contrasted against the Bing Section of our parameter sheet. Note that Bing does not provide a country code like US, DE etc - seems like a big omission actually - Google (although complicated to find), and  Yahoo both do.  


Note that the response from both Bing and Yahoo are simpler, so we can use the 'fullkey' parameter to identify the field within that response. 


The Yahoo Placemaker Key

As mentioned you need a developer app ID to use Yahoo. In the downloadable example I am using my key. You need to get your own by registering at the Yahoo developer center. Once you have it you can enter it in the parameter sheet, replacing the one that is in the parameter sheet.


The Response

An example call is generated as below

http://where.yahooapis.com/geocode?location=1600%20Amphitheatre%20 Pkwy%2C%20Mountain%20View%2C%20CA%2094043%2C%20USA &flags=J&appid=xxxxxxxxxx

and this the response - a lot briefer and more to the point than Bing or Google.

{"ResultSet":{"version":"1.0","Error":0,"ErrorMessage":"No error","Locale":"us_US","Quality":87, "Found":1,"Results": [{"quality":87, "latitude":"37.423232", "longitude":"-122.085569","offsetlat":"37.423109", "offsetlon":"-122.085598", "radius":500,"name":"", "line1":"1600 Amphitheatre Pky", "line2":"Mountain View, CA  94043-1351","line3":"", "line4":"United States", "house":"1600","street":"Amphitheatre Pky","xstreet":"","unittype":"", "unit":"", "postal":"94043-1351","neighborhood": "", "city":"Mountain View", "county":"Santa Clara County","state": "California", "country":"United States","countrycode":"US","statecode":"CA", "countycode":"","uzip":"94043", "hash":"92F5774D0FC9C1AA", "woeid":12797130,"woetype":11}]}}

This response gets decoded into a cJobject and this data is used in populating the sheet.



The code

This is very similar to Complete Excel Address Data with Bing Maps API, and calls many of the same modules which I won't bother repeating here.  We are doing the same thing - namely geoCoding and filling in named fields such as country etc as specified, and according to the rules in a parameter sheet.  This is the yahooMappingExample module in the downloadable GoogleMapping.xlsm file.


Option Explicit
' NOTE: before using this please read the Yahoo terms and conditions
' http://info.yahoo.com/legal/us/yahoo/api/api-2140.html
' http://info.yahoo.com/legal/us/yahoo/applicationplatform/applicationplatform-3959.html

' this example is purely for educational purposes and is a step in creating a yahoo map
' YOU NEED TO GET YOUR OWN Yahoo KEY TO BUILD AN APPLICATION WITH THIS CODE
Const cYahooApiUrl = "http://where.yahooapis.com/geocode?location="

Public Sub yahooMappingExample()
    Dim dSets As cDataSets, dr As cDataRow, sInput As String
    Dim jo As cJobject, cb As cBrowser, sReq As String, sWire As String
    iDebug = 1

    Set dSets = dSetsSetup
    If dSets Is Nothing Then Exit Sub
    ' now we have the data file and know that it contains an ID column and an address column
    ' now geocode the addresses - one by one (the final version will do it as a batch)
    Set cb = New cBrowser
    With dSets
        For Each dr In .DataSet(cMaster).Rows
            sInput = rxReplace("nonprintable", _
                dr.Cell(.DataSet(cParamFields).Cell _
                (cFieldAddress, cFieldValue).toString).toString, " ")
            sReq = cYahooApiUrl & URLEncode(sInput) & _
             "&flags=J&appid=" & _
            dSets.DataSet(cYahooParameters).Cell("Key", "Value").toString
            ' get mapping result
            sWire = cb.httpGET(sReq)
            Set jo = New cJobject
            With jo.deSerialize(sWire)
                If .isValid And Not .ChildExists("ResultSet") Is Nothing Then
                ' now we have a cjobject of every field in the response
                    With .Child("ResultSet")
                        If .Child("Error").Value = 0 Then
                            ' all is good go and find necessary fields
                            If Not .Child("Results").hasChildren Then
                                MsgBox ("No results for " & ":url" & sReq)
                            Else
                                fullkeySuitableJob .Child("Results"), _
                                    dr, "yahoo component", "yahoo special", _
                                       Range("debug!a2")
                            End If
                        Else
                            MsgBox ("Unable to geomap - status " & .Child("ErrorMessage").toString & ":url" & sReq)
                        End If
                    End With
                Else
                    MsgBox ("Badly formed jSon response received to " & sReq)
                End If
            End With
            
            Set jo = Nothing
        Next dr
    End With
    Set dSets = Nothing
    Set cb = Nothing
End Sub



In fact since this was so close to the Complete Excel Address Data with Bing Maps API example, I generalized a few more procedures that could be used by both. These can be found in the Publics module in the downloadable GoogleMapping.xlsm file.

Option Explicit
Public Const cFieldID = "ID"
Public Const cFieldAddress = "Address"
Public Const cFieldValue = "Column Name"
Public Const cParamSheet = "Parameters"
Public Const cParamFields = "Fields"
Public Const cParamRules = "Column Name"
Public Const cTransactions = "Transactions"
Public Const cMaster = "Master"
Public Const cCopyFields = "Clone from Master"
Public Const cJoin = "Join"
Public Const cTab = "Tabs"
Public Const cName = "Name"
Public Const cSheet = "Worksheet"
Public Const cMeasure = "Measures"
Public Const cBingParameters = "Bing"
Public Const cYahooParameters = "Yahoo"
Public iDebug As Long
Public Function dSetsSetup() As cDataSets

    Dim dSets As cDataSets

    ' populate the customer master
    Set dSets = New cDataSets
    With dSets
        .create
        ' get the names of all the sheets
        .init wholeSheet(cParamSheet), , cName, True, cName
        ' create the parameter page deserialization data set
        .init wholeSheet(cParamSheet), , cParamRules, True, cParamRules
        .init wholeSheet(.DataSet(cName).Cell(cMaster, cSheet).toString), , cMaster, , , True

        ' create the parameter page data set
        .init wholeSheet(cParamSheet), , cParamFields, True, cParamFields
        .init wholeSheet(cParamSheet), , cBingParameters, True, cBingParameters
        .init wholeSheet(cParamSheet), , cYahooParameters, True, cYahooParameters
        With .DataSet(cParamFields)
        ' check that the required fields are present in the input data
          If Not dSets.DataSet(cMaster).HeadingRow.Validate(True, _
                .Cell(cFieldID, cFieldValue).toString, _
                .Cell(cFieldAddress, cFieldValue).toString) Then
            Exit Function
          End If
        End With
    End With
    Set dSetsSetup = dSets
End Function
Public Sub fullkeySuitableJob(job As cJobject, dr As cDataRow, _
                                spComponent As String, spSpecial As String, _
                                   Optional rDebug As Range = Nothing)
                                   
    'given a row, find the most appropriate object to populate it with
    Dim dc As cCell, pc As cCell, jo As cJobject, sName As String

    For Each dc In dr.Columns
        ' is this an interesting column ?
        sName = dr.Parent.Headings(dc.Column).toString
        With dr.Parent.Parent.DataSet(cParamRules)
            Set pc = .Cell(sName, 1)
            If Not pc Is Nothing Then
                ' it is a cell that needs filling in
                Set jo = fullkeyMappingFind(job, _
                    LCase(.Cell(sName, spComponent).toString), _
                    LCase(.Cell(sName, spSpecial).toString), _
                    rDebug)
                With dc
                    If jo Is Nothing Then
                        .Value = Empty
                    Else
                        .Value = jo.Value
                    End If
                    .Commit
                End With
            End If
        End With
    Next dc
    
End Sub

Public Function fullkeyMappingFind(job As cJobject, _
            sComponent As String, sSpecial As String, _
            Optional rDebug As Range = Nothing) As cJobject
                    
    ' given a column name, what's the best fit in the structure response from geocoding
    Dim sValue As String, jo As cJobject, jResult As cJobject, sKey As String
    Dim st As String
    
    ' these are the parameters we will work with
    sKey = LCase(job.fullKey)
    sValue = LCase(job.toString)

    ' if you need a list this will show all seen api response values
    If Not rDebug Is Nothing Then
        rDebug.Offset(iDebug, 0).Value = sKey
        rDebug.Offset(iDebug, 1).Value = job.Value
        iDebug = iDebug + 1
    End If

    If sSpecial = "fullkey" Then
        ' fullkey needs no further matching
        If sKey = sComponent Then
            Set fullkeyMappingFind = job
            Exit Function
        End If
    Else
        MsgBox ("Only full key implemented for this provider")
    End If

    ' recurse for children
    If job.hasChildren Then
        For Each jo In job.Children
            Set jResult = fullkeyMappingFind(jo, sComponent, sSpecial, rDebug)
            If Not jResult Is Nothing Then
                Set fullkeyMappingFind = jResult
                Exit Function
            End If
        Next jo
    End If

End Function


Comments