With assistance from outside sources I have this code that takes latitude and longitude and extracts zipcodes. Here is the code:
Public Function ReverseGeoCode(myInput1 As String, myInput2 As String) As String
'You will need to reference Microsoft XML, v6.0 object library
Dim XMLDoc As New DOMDocument60
Dim XMLNODE As IXMLDOMNode
Dim I As Long
Dim lat, lng, myAddress, myZipcode, reportZipcode As String
Dim splitAddress, splitZipcode As Variant
lat = myInput1
lng = myInput2
XMLDoc.Load "http://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & lng & " &sensor=false"
Do Until XMLDoc.readyState = 4
DoEvents
Loop
If Len(XMLDoc.Text) = 0 Then
Call MsgBox("No Data!")
Exit Function
End If
Set XMLNode = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
For i= 0 To XMLNode.ChildNodes.Length - 1
myAddress = XMLNode.ChildNodes(i).Text
Next i
splitAddress = Split(myAddress, ",")
myZipcode = splitAddress(2)
myZipcode = Trim(myZipcode)
splitZipcode = Split(myZipcode, " ")
reportZipcode = splitZipcode(1)
reportZipcode = Trim(reportZipcode)
ReverseGeoCode = reportZipcode
End Function
So the code works, and I know it might not be the cleanest. But the issue is when I call it in the Excel sheet using "=ReverseGeoCode(Cell1, Cell2)". Sometimes it works fine, other times it produces the return "#VALUE!" and I am not entirely sure why. I attached an image below to show you an example of the error. Does anyone have an idea of why this error is producing?
General observations:
So to pick up on what I wrote in the comments, here is an outline.
You don't want to use an User Defined Function. This will keep on making repeated calls. You definitely risk hitting a call limit to the API without an API key, and possibly with; it is inefficient and it is not necessary. Instead, write a sub which you call once and which loops all the required cells in the sheet and issues the API calls and returns the zip codes. An API key is a method of authentication used with many API calls. You shouldn't share it by the way.
These repeated calls, possibly hitting a limit and the fact that UDFs are frequently calculated maybe the source of your woes.
With efficiency in mind, first remove duplicates from the sheet to avoid calls that are not required. Switch of Screen-Updating and anything else e.g. CalculationMode to manual whilst performing.
From what I have read you require an API key once you have hit a daily limit. Not sure what the API limit is for free version or without API key.
Outline code (XML request with some psuedo code):
Option Explicit
Public Sub ListZipCodes()
Dim lat As Double, longitude As Double
Const APIKEY As String = "yourAPIkey"
Application.ScreenUpdating = False '<==Speed up code when actually working with sheet
'Code to remove duplicates
'Code to loop sheet and call function on each input set of values
'Example call. These would be picked up from cells
lat = 40.714224
longitude = -73.961452
Debug.Print GetZipCode(lat, longitude, APIKEY)
Application.ScreenUpdating = True
End Sub
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String
URL = "https://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Dim XMLDoc As New DOMDocument60, XMLNODE As IXMLDOMNode
XMLDoc.Load .responseBody
If Len(XMLDoc.Text) = 0 Then GetZipCode = "No data"
Set XMLNODE = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
GetZipCode = Split(Trim$(Split(XMLNODE.Text, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
Requesting JSON rather than XML response:
The reason calling as JSON was falling over was that the response needed to be decoded. Here is the function re-written to handle a JSON response.
This requires the download of JSONConverter, which you then import and add a reference to Microsoft Scripting Runtime via VBE > Tools > References.
The example below was run with
latitude: 42.9865913391113,
longitude: -100.137954711914
VBA:
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String, json As Object
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String, formattedAddress As String
URL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode))
formattedAddress = json("results").item(1)("formatted_address")
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
With a JSON request the initial object you get back is a dictionary, as denoted by the opening "{" in the decoded response:
i.e. Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode)) returns a dictionary object
The data of interest, in the dictionary, has the key "results", as you may observe from the above.
This can be accessed with json("results"), which returns a collection of dictionaries. This being denoted by the following "[", for collection, and subsequently by the start of the first dictionary within the collection, indicated again by "{".
I can grab the first dictionary in the collection by index with:
json("results").item(1)
An inspection of the keys in this dictionary shows that one of the keys is what we are after i.e."formatted_address".
It's associated value is a primitive datatype; in this case a string. This means we can directly access it using the key (a further object is not returned).
formattedAddress = json("results").item(1)("formatted_address")
Now that we have the address string, we can parse it as we did before:
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End note:
You can use Postman, amongst other tools, to test API calls, and in this case inspect the JSON response. Indeed, to see what kind of response you are getting full stop.
Help:
It is very quick and easy to set up a project, generate an API key and get started. Maybe 10 minutes to read through and perform.
Instructions on setting up a project and getting an API key
Enabling the API
Understanding how to make API calls to the Geocoding API
I'm trying to fetch data from a currency api. I'm fairly new to VBA and very new to JSON, but I can't figure out how to pass a request through the HTTP.... I can fetch any data I want that does not require a request though to pin it down.
The API is here: https://www.bitfinex.com/pages/api
I'm trying to pass a timestamp(time) request. See instructions ( pasted form their website just below )
Trades
GET /trades/:symbol
Get a list of the most recent trades for the given symbol.
Request
timestamp (time): Optional. Only show trades at or after this timestamp.
limit_trades (int): Optional. Limit the number of trades returned. Must be >= 1. Default is 50.
Response
An array of dictionaries:
tid (integer)
timestamp (time)
price (price)
amount (decimal)
exchange (string)
type (string) "sell" or "buy" (can be "" if undetermined)
My code so far
Private Function get_price() As String
Dim xml_a As Object
Set xml_a = CreateObject("MSXML2.XMLHTTP")
With xml_a
.Open "Get", "https://api.bitfinex.com/v1/trades/BTCUSD", False
'' <<< something should be written here right? I just don't know what... >>>''
.send
get_price = .responseText
End With
Set xml_a = Nothing
End Function
Sub tester()
Dim JSON_return As Object
Set JSON_return = JSON.parse(get_price())
Debug.Print JSON_return.Item("last_price")
End Sub
Set domSession = CreateObject("Notes.NotesSession")
Dim domDatabase
Set domDatabase = domSession.GetDatabase("server Name", "xyz.nsf")
Set domView = domDatabase.GetView("All Projects")
Up to here it's ok. But I try lot but unable to read the document values.
I am looking for the vbscript code to read the document data.
Add this:
Dim i%
Dim s$
Dim doc As Object
For i% = 1 to domView.EntryCount
Set doc = domView.GetNthDocument(i%)
s$ = doc.GetItemValue("itemname")(0)
' Do whatever you want with the string value which is now stored in s$
Next
This code assumes the items are text type. You could use simply Dim s to get any item type (number, date).
To get column values from the view you can use doc.ColumnValues(2). This returns the value of 3rd column (0 is first).
Not sure how to do this, here is my code
Dim oWeb As New System.Net.WebClient()
oWeb.Headers.Add("Content-Type", "application/x-www-form-urlencoded")
Dim bytArguments As Byte() = System.Text.Encoding.ASCII.GetBytes("username=username&password=password")
Dim bytRetData As Byte() = oWeb.UploadData("https://www.website.com", "POST", bytArguments)
MsgBox(oWeb.ResponseHeaders.GetValues(2))
Im attempting to get the cookies saved then use it for another post data i want to do, but I keep getting logged off in like 1 second.
The error is
Argument 'Prompt' cannot be converted to type 'String'.
for the message box
The problem is that the GetValues returns an array of strings, which the MessageBox has trouble displaying.
When I change the line to
MsgBox(String.Join(",", oWeb.ResponseHeaders.GetValues(2)))
It works, so you need to decide how you wish to concatenate the string array that is being returned.
I need to store the first byte of data read from the network stream as a string, so I can call it back later.
prinf(" While 1
Dim tcpListener As New TcpListener(IPAddress.Any, 80) ' Listen to port given
Console.WriteLine("Waiting for connection...")
tcpListener.Start()
'Accept the pending client connection and return 'a TcpClient initialized for communication.
Dim tcpClient As TcpClient = tcpListener.AcceptTcpClient()
Console.WriteLine("Connection accepted.")
' Get the stream
Dim networkStream As NetworkStream = tcpClient.GetStream()
' Read the stream into a byte array
Dim bytes(tcpClient.ReceiveBufferSize) As Byte
networkStream.Read(bytes, 0, CInt(tcpClient.ReceiveBufferSize))
' Return the data received from the client to the console.
Dim clientdata As String = Encoding.ASCII.GetString(bytes)
Console.WriteLine(("Client Sent: " + clientdata))
' Return the data received from the client to the console.
Dim responseString As String = "Hello"
'Dim chat_name As String = "Name"
Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(responseString)
networkStream.Write(sendBytes, 0, sendBytes.Length)
Console.WriteLine(("Response: " + responseString))
tcpClient.Close() 'Close TcpListener and TcpClient
tcpListener.Stop()
End While");
Thats my server ^ everything works fine, but I need the 1st piece of data read to be stored, such as if I get "Name" it should be stored in an array
Thanks
You'll need to define exactly what you mean by "1st piece of data" - is this data delimited in some form (like HTTP headers - key/value pairs are delimited by carriage-return line-feed)? Length-prefixed (like HTTP bodies when the Content-Length header is specified)? You almost certainly don't just want the first byte.
If you were hoping to just send the name and then send something else, without any indication of the fact that they're different bits of data, you're going to be disappointed. Streams are just sequences of bytes - there's nothing (built-in) to say "read what the client sent in their first API call".
This should work:
Dim strFirstByte as string = vbNullString
While 1
' ... Your code ...
Dim bytes(tcpClient.ReceiveBufferSize) As Byte
networkStream.Read(bytes, 0, CInt(tcpClient.ReceiveBufferSize))
If strFirstByte = vbNullString Then strFirstByte = bytes(0).ToString("X2")
' ... The rest of your code ...
End While