Google Maps geocoding API in OpenOffice Calc VBA - google-maps

For my project, I need to geocode a set of locations for which I would like to know the GPS coordinates.
The amount of locations it too big to to it by hand, but not too much so that I will not have problems with Google's limitations of the use of the Geocoding API.
The most convenient way to do this for me would be to use OpenOffice Calc.
I found a VBA code that does just what I need:
Function GetGeoData(sSearch as String) as String
If Len(sSearch) = 0 Then Exit Function 'we dont need empty cells <img draggable="false" class="emoji" alt="😉" src="http://s.w.org/images/core/emoji/72x72/1f609.png">
URL = "http://maps.googleapis.com/maps/api/geocode/xml?sensor=true&address=" 'we will use the google maps api
URL = URL & sSearch 'create the searchstring
oSimpleFileAccess = createUnoService( "com.sun.star.ucb.SimpleFileAccess" ) 'this is the Sefvice in getting the data from the web
On Error GoTo ErrorResponse
oInputStream = oSimpleFileAccess.openFileRead(URL) 'use the URL
oTextStream = createUnoService("com.sun.star.io.TextInputStream") 'get the data from the web
oTextStream.InputStream = oInputStream 'this is the data
aDelimiters = Array(ASC(">"),ASC("<")) 'as the stream is segmented with ">" and "<"
sLastString = ""
Do While NOT oTextStream.isEOF 'go through the google output
sThisString = oTextStream.readString(aDelimiters,True)
Select Case sLastString 'now search for the entries
Case "lat": 'latitudes
sLat = sThisString
Case "lng": 'longitude
sLon = sThisString
End Select
sLastString = sThisString
Loop
GetGeoData = " Longitude: " & sLon & " Latitude: " &sLat 'this is our output in the new cell
oInputStream.closeInput()
Exit Function
ErrorResponse:
GetGeoData = "no values found!!!"
End Function
However, while it is fine for exact addresses, there is a problem when it comes to settlements that Google knows as polygons. In this case, the code only keeps the last set of coordinates it found in the xml info, but this corresponds to the north-east corner of the polygon. I would be happy to have the center of the polygon, which corresponds to the first set of coordinates in the xml document generated by Google maps.
Can anyone explain to me how I can select a specific node in the xml
file based on this code?
Another solution would be to keep only the first set of coordinates.

At first: Never take XML as text string only. XML has a meaningful data structure which needs to be parsed. Fortunately the Openoffice API provides a XML parser already. com.sun.star.xml.dom.DocumentBuilder https://www.openoffice.org/api/docs/common/ref/com/sun/star/xml/dom/DocumentBuilder.html
To your question: Each result has a geometry with a location. The lat, lng in that location will be either the approximate lat, lng or the geometric center. The other lat, lng are viewport or bounds ones.
Example Berlin, Germany:
<geometry>
<location>
<lat>52.5200066</lat>
<lng>13.4049540</lng>
</location>
<location_type>APPROXIMATE</location_type>
<viewport>
<southwest>
<lat>52.3396296</lat>
<lng>13.0891553</lng>
</southwest>
<northeast>
<lat>52.6754542</lat>
<lng>13.7611176</lng>
</northeast>
</viewport>
<bounds>
<southwest>
<lat>52.3396296</lat>
<lng>13.0891553</lng>
</southwest>
<northeast>
<lat>52.6754542</lat>
<lng>13.7611176</lng>
</northeast>
</bounds>
</geometry>
So only the lat, lng from the location are needed.
But there are other issues too. What if there are more than one results? Berlin, for example, is not only the capital of Germany.
So my example function returns all results:
Function GetGeoData(sSearch as String) as String
sResult = ""
if len(sSearch) > 0 and sSearch <> "0" then
sURI = "http://maps.googleapis.com/maps/api/geocode/xml?sensor=true&address="
sURI = sURI & sSearch
oDocumentBuilder = createUnoService("com.sun.star.xml.dom.DocumentBuilder")
oDOMDocument = oDocumentBuilder.parseURI(sURI)
oResults = oDOMDocument.getElementsByTagName("result")
for i = 0 to oResults.length -1
oResult = oResults.item(i)
oformattedAddress = oResult.getElementsByTagName("formatted_address").item(0)
sformattedAddress = oformattedAddress.getFirstChild().nodeValue
oGeometry = oResult.getElementsByTagName("geometry").item(0)
oLocation = oGeometry.getElementsByTagName("location").item(0)
oLat = oLocation.getElementsByTagName("lat").item(0)
sLat = oLat.getFirstChild().nodeValue
oLng = oLocation.getElementsByTagName("lng").item(0)
sLng = oLng.getFirstChild().nodeValue
if i = 0 then
sResult = sResult & sformattedAddress & ": Lat:" & sLat & " Lng:" & sLng
else
sResult = sResult & "; " & sformattedAddress & ": Lat:" & sLat & " Lng:" & sLng
end if
next
end if
GetGeoData = sResult
End Function

It sounds like you are looking for something more powerful than just matching "<" and ">" in tags. This often happens when working with XML, and there are many specialized libraries to accomplish this task.
Parsing XML can be accomplished in OpenOffice Basic using the com.sun.star.xml.sax.Parser interface. See https://wiki.openoffice.org/wiki/XML_and_Filter for details.
Alternatively, many languages have XML parsing libraries. Java and Python have XML parsing libraries and can also work with OpenOffice. The library I personally use most with OpenOffice is xml.dom.minidom.

Related

When appending to a List of JSON Objects in python, why is it duplicating only the "2nd layer" objects?

Here is my code.
def generateNewDevices(numofdevices):
global simulated_devices
for x in range(numofdevices):
new_device = reference_device.copy()
new_device["accel"]["accx"] = random.randint(-32768, 32767)
new_device["accel"]["accy"] = random.randint(-32768, 32767)
new_device["accel"]["accz"] = random.randint(-32768, 32767)
new_device["location"]["gpsla"] = random.uniform(MINLAT, MAXLAT)
new_device["location"]["gpslo"] = random.uniform(MINLON, MAXLON)
new_device["temp"] = random.randint(-127, 127)
new_device["status"] = random.randint(0, 1)
str1 = new_device["deviceName"]
str1 = str1[:-3]
str2 = str(x).zfill(3)
str1 += str2
new_device["deviceName"] = str1
node_red_send(URL, new_device)
print(new_device)
simulated_devices.append(new_device)
generateNewDevices(3)
for x in range(len(simulated_devices)):
print(simulated_devices[x])
Why when printing through the list of values at the end, does the list show the "new device" data for appended JSON objects "1 layer deep" (temp, status and name) but duplicate the data for "2 layers deep" (accx, accy, gpsla)?
The .copy()s are in there because I was having issues with python append duplicating all the values at first. Is this some variation of the same issue? I even tried .copy()ing right before appending to the list. (I come from a c/c++ background so I do not fully understand why python does some of its things)
Any help appreciated.
Kr, apuri123.
I doubt anyone will end up here when searching for an answer, but in case you do, you are looking for "deepcopy":
import copy
original = {} #object with as many objects within objects as you want
myCopy = copy.deepcopy(original)
Google "python deepcopy" and you should be able to find what you are looking for.

How do I get output to the webpage while still inside a cfscript?

Sorry for the longer post, I'm trying to be specific. I'm a bit of a newb at cold fusion and lucee, so forgive me if I have missed something fundamental here. I'm just trying to do a quick POC, but can't get it working. What I am trying to do is make a page call, write to a web page, sleep for a while. Kind of a heartbeat. What I can't get to happen is the write to the web page...until all sleep(s) have happened and the page cfm file completes processing. I've looked extensively for the past couple of days, and have tried numerous items, but to no avail. From my index.cfm lucee page, I have a link to launch a new tab and call my cfm file.
<a href="./pinger2.cfm" target="_blank"><img class="ploverride" src="/assets/img/Ping.png" alt="Ping Test" width="125" height="75">
No problem here, a new tab opens and pinger2.cfm starts processing.
What I'm hoping for is the table heading to almost immediately print to the page, then make the first call out, print the results to the page, sleep, make the next call out, print to the page...but it no workey. Anyone have a clue? The code in the pinger2.cfm file is:
<cfscript>
public struct function pinger( required string url, required string verb, required numeric timeout, struct body )
{
var result = {
success = false,
errorMessage = ""
};
var httpService = new http();
httpService.setMethod( arguments.verb );
httpService.setUrl( arguments.url );
httpService.setTimeout( arguments.timeout );
if( arguments.verb == "post" || arguments.verb == "put" )
{
httpService.addParam(type="body", value=SerializeJSON(arguments.body));
}
try {
callStart = getTickCount();
var resultObject = httpService.send().getPrefix();
callEnd = getTickCount();
callLength = (callEnd-callStart)/1000;
if( isDefined("resultObject.status_code") && resultObject.status_code == 200 )
{
result.success = true;
logMessage = "Pinger took " & toString( callLength ) & " seconds.";
outLine = "<tr><td>" & resultObject.charset & "</td><td>" & resultObject.http_version & "</td><td>" & resultObject.mimetype & "</td><td>" & resultObject.status_code & "</td><td>" & resultObject.status_text & "</td><td>" & resultObject.statuscode & "</td><td>" & logMessage & "</td></tr>";
writeOutput( outLine );
getPageContext().getOut().flush();
return result;
}
else
{
throw("Status Code returned " & resultObject.status_code);
}
}
catch(Any e) {
// something went wrong with the request
writeDump( e );
abort;
}
}
outLine = "<table><tr><th>charset</th> <th>http_version</th> <th>mimetype</th> <th>status_code</th> <th>status_text</th> <th>statuscode</th> <th>time</th> </tr>";
writeOutput( outLine );
getPageContext().getOut().flush();
intCounter = 0;
while(intCounter LT 2)
{
theResponse = pinger(
url = "https://www.google.com",
verb = "GET",
timeout = 5
);
intCounter = intCounter + 1;
getPageContext().getOut().flush();
sleep(2000);
}
outLine = "</table>";
writeOutput( outLine );
</cfscript>
NOTE: I'm sure there are other "less than best" practices in there, but I'm just trying to do this quick and dirty.
I thought the getPageContext().getOut().flush(); would do the trick, but no bueno.
EDIT: If it matters, I'm using CF version 10,0,0,0 and Lucee version 4.5.2.018.
I do something similar to generate ETags by hand (using Lucee 4.5). I stick a simple
GetPageContext().getOut().getString()
in the onRequestEnd function in Application.cfc. This returns a string of HTML just like it's sent to the browser.
You could store that in the appropriate scope (APPLICATION, SESSION, etc) and use it later, or whatever you need. Obviously, all processing needs to be completed, but it shouldn't require any flushes. In fact, flushing may or may not alter its behavior.

LibreOffice Basic Macro command converting Calc cellRange to RTF/HTML

My goal is to fill a LibreOffice calc sheet, and silently send a cell range by email when the user clicks the send-off button (and once more to confirm).
So there is three part to this.
A push button with a request to confirm. (Easy and done.)
Select Cell Range and turn it into rich text format (Haven't yet found)
Send rich text email from within the sheet. (Will tackle the "silent" part later)
I tried copying the range to the clipboard with unoService but it seemed over-complicated and full of errors.
Here's what I have:
''''Send by e-mail enriched text
Sub Main
Dim Doc, Sheet, Range, Rtf, Exec as Object
End Sub
'Confirm it
Sub SendTableApproval
If MsgBox ("Ready to email?", MB_YESNO + MB_DEFBUTTON2) = IDYES Then
CopyTable()
End If
End Sub
'Copy it
Sub CopyTable
Doc = ThisComponent
View = Doc.CurrentController
Frame = View.Frame
Sheet = Doc.Sheets.getByIndex(0)
Range = Sheet.getCellrangeByName("a1:f45")
Exec = createUnoService("com.sun.star.frame.DispatchHelper")
View.Select(Range)
Cells = View.getTransferable()
Exec.executeDispatch(Frame, ".uno:Deselect", "", 0, array())
'SimpleMailTo(Cells)
End Sub
'Mail it
Sub SimpleMailTo(body)
Dim launcher as object
Dim eAddress, eSubject, eBody, aHTMLanchor as string
launcher = CreateUnoService("com.sun.star.system.SystemShellExecute")
eAddress = "tu#domo.eg"
eSubject = "Cotidie agenda futuendane"
eBody = body
aHTMLanchor = "mailto:" & eAddress & "?subject=" & eSubject & "&&body=" & eBody
launcher.execute(aHTMLanchor, "", 0)
End Sub
I still do not know after three days of research over methods, properties, uno.
My question is, simply put, How can I convert a transferable content to HTML/RTF?
Simply copying and pasting into an email produces the result you are asking for. The code on the LibreOffice side should look like this.
dispatcher.executeDispatch(document, ".uno:Copy", "", 0, Array())
It sounds like you already tried this, but something didn't work. Perhaps you could elaborate on what went wrong.
Another approach would be to write the spreadsheet to a temporary HTML or XHTML file. Then parse the temporary file to grab the part needed for the email.
AFAIK there is no such command to turn a cell range into rich text format with UNO. To do it that way, you would need to loop through each text range of each cell, read its formatting properties and then generate the HTML yourself.
EDIT:
Good idea about XTransferable. The following Java code adapted from the DevGuide gets an HTML string and then prints it. I believe this would be a good solution for your needs.
public void displayHTMLFromClipboard()
{
try
{
Object oClipboard = xMCF.createInstanceWithContext(
"com.sun.star.datatransfer.clipboard.SystemClipboard", xContext);
XClipboard xClipboard = (XClipboard)
UnoRuntime.queryInterface(XClipboard.class, oClipboard);
XTransferable xTransferable = xClipboard.getContents();
DataFlavor[] aDflvArr = xTransferable.getTransferDataFlavors();
System.out.println("Available clipboard formats:");
DataFlavor aChosenFlv = null;
for (int i=0;i<aDflvArr.length;i++)
{
System.out.println(
"MimeType: " + aDflvArr[i].MimeType +
" HumanPresentableName: " + aDflvArr[i].HumanPresentableName );
if (aDflvArr[i].MimeType.equals("text/html"))
{
aChosenFlv = aDflvArr[i];
}
}
System.out.println("");
try
{
if (aChosenFlv != null)
{
System.out.println("HTML text on the clipboard...");
Object aData = xTransferable.getTransferData(aChosenFlv);
String s = new String((byte[])aData, Charset.forName("ISO-8859-1"));
System.out.println(s);
}
}
catch (UnsupportedFlavorException exc)
{
exc.printStackTrace();
}
}
catch(com.sun.star.uno.Exception exc)
{
exc.printStackTrace();
}
}
If you plan to use Basic, it might be a good idea to do some research into the proper way to convert bytes. The code I have below seems to work but is probably unreliable and unsafe, and will not work for many languages. A few of my initial attempts crashed before this finally worked.
Sub DisplayClipboardData
oClipboard = createUnoService("com.sun.star.datatransfer.clipboard.SystemClipboard")
xTransferable = oClipboard.getContents()
aDflvArr = xTransferable.getTransferDataFlavors()
For i = LBound(aDflvArr) To UBound(aDflvArr)
If aDflvArr(i).MimeType = "text/html" Then
Dim aData() As Byte
aData = xTransferable.getTransferData(aDflvArr(i))
Dim s As String
For j = LBound(aData) to UBound(aData)
s = s & Chr(aData(j)) 'XXX: Probably a bad way to do this!
Next j
Print(s)
End If
Next
End Sub
One more suggestion: Python might be a better language choice here. In many ways, using Python with LibreOffice is easier than Java. And unlike Basic, Python is powerful enough to comfortably handle byte strings.

Reading HTML page using Libreoffice Basic

I'm new to LibreOffice Basic. I'm trying to write a macro in LibreOffice Calc that will read the name of a noble House of Westeros from a cell (e.g. Stark), and output the Words of that House by looking it up on the relevant page on A Wiki of Ice and Fire. It should work like this:
Here is the pseudocode:
Read HouseName from column A
Open HtmlFile at "http://www.awoiaf.westeros.org/index.php/House_" & HouseName
Iterate through HtmlFile to find line which begins "<table class="infobox infobox-body"" // Finds the info box for the page.
Read Each Row in the table until Row begins Words
Read the contents of the next <td> tag, and return this as a string.
My problem is with the second line, I don't know how to read a HTML file. How should I do this in LibreOffice Basic?
There are two mainly issues with this.
1. Performance
Your UDF will need get the HTTP resource in every cell, in which it is stored.
2. HTML
Unfortunately there is no HTML parser in OpenOffice or LibreOffice. There is only a XML parser. Thats why we cannot parse HTML directly with the UDF.
This will work, but slow and not very universal:
Public Function FETCHHOUSE(sHouse as String) as String
sURL = "http://awoiaf.westeros.org/index.php/House_" & sHouse
oSimpleFileAccess = createUNOService ("com.sun.star.ucb.SimpleFileAccess")
oInpDataStream = createUNOService ("com.sun.star.io.TextInputStream")
on error goto falseHouseName
oInpDataStream.setInputStream(oSimpleFileAccess.openFileRead(sUrl))
on error goto 0
dim delimiters() as long
sContent = oInpDataStream.readString(delimiters(), false)
lStartPos = instr(1, sContent, "<table class=" & chr(34) & "infobox infobox-body" )
if lStartPos = 0 then
FETCHHOUSE = "no infobox on page"
exit function
end if
lEndPos = instr(lStartPos, sContent, "</table>")
sTable = mid(sContent, lStartPos, lEndPos-lStartPos + 8)
lStartPos = instr(1, sTable, "Words" )
if lStartPos = 0 then
FETCHHOUSE = "no Words on page"
exit function
end if
lEndPos = instr(lStartPos, sTable, "</tr>")
sRow = mid(sTable, lStartPos, lEndPos-lStartPos + 5)
oTextSearch = CreateUnoService("com.sun.star.util.TextSearch")
oOptions = CreateUnoStruct("com.sun.star.util.SearchOptions")
oOptions.algorithmType = com.sun.star.util.SearchAlgorithms.REGEXP
oOptions.searchString = "<td[^<]*>"
oTextSearch.setOptions(oOptions)
oFound = oTextSearch.searchForward(sRow, 0, Len(sRow))
If oFound.subRegExpressions = 0 then
FETCHHOUSE = "Words header but no Words content on page"
exit function
end if
lStartPos = oFound.endOffset(0) + 1
lEndPos = instr(lStartPos, sRow, "</td>")
sWords = mid(sRow, lStartPos, lEndPos-lStartPos)
FETCHHOUSE = sWords
exit function
falseHouseName:
FETCHHOUSE = "House name does not exist"
End Function
The better way would be, if you could get the needed informations from a Web API that would offered from the Wiki. You know the people behind the Wiki? If so, then you could place this there as a suggestion.
Greetings
Axel

How do you get a unique id for a layer or generate one in Arcmap?

Is there a way in arcobjects to get a unique id for a layer? If you do a search by layer name there could be possible duplicates.
If there isn't a property is there a way to generate an id?
I tried using the GetHash() but that didn't stay consistent.
There is an ArcObjects Interface present for setting or getting an Id for a layer.
You should look at ILayerDescriptor:ID,
http://resources.esri.com/help/9.3/ArcGISDesktop/ArcObjects/esriCarto/ILayerDescriptor_ID.htm
Here is a VBA Snippet which shows how it can be used:
Public Sub layerInfo()
Dim app As IApplication '
Set app = Application
Dim mxDoc As IMxDocument
Set mxDoc = app.Document
Dim myMap As IMap
Set myMap = mxDoc.ActiveView
Dim mapServer As IMxdServer
Set mapServer = New MxdServer
'''Point to your .mxd...
mapServer.Start ("D:\Test.mxd")
Dim myArray As IArray
Set myArray = mapServer.LayerDescriptors(myMap.Name)
MsgBox myArray.Count
Dim x As ILayerDescriptor
Dim intX As Integer
intX = 0
For intX = 0 To myArray.Count - 1
Set x = myArray.Element(intX)
MsgBox x.ID
MsgBox x.Name
Next
End Sub
It isn't pretty, but in the past I've appended a guid in the layer description. Something like this:
<LAYER guid='a9843c88-3caa-4953-ad96-ca9990b410e9' revision='1' />
I've got a DLL floating around that would slam these xml frags into each layer of an MXD (with enough cr/lf in front to scroll the xml fragment out of the layer description in ArcMap Layer Prop dialog) .
There's a help file in the 7z file (documentation is sparse because I'm doing other things):
http://code.google.com/p/umbriel/downloads/list
I like the idea of using a GUID. This can then be stored in the ModelName property which is a tool for developers of custom objects to use to guarantee the names of objects independent of the true name or alias name.
There are more details and sample code at http://geographika.co.uk/?p=58
Easy. A side effect of using COM and because how the vtables are laid out, is that you can use the memory address of the layer itself as your unique identifier. Inside the implementation of many ESRI GeoDatabase and Carto code itself, this trick is being used all over the place.