Geocoding für Adressen über Internet

Programmierung unter AOO/LO (StarBasic, Python, Java, ...)

Moderator: Moderatoren

Mobbi
**
Beiträge: 23
Registriert: Do, 11.12.2008 21:52

Geocoding für Adressen über Internet

Beitrag von Mobbi »

Hallo Leute,

Ich versuche für eine Adressdatenbank aus den Angaben Ort, Straße und Hausnummer, die Geokoordinaten (Breiten- und Längengrad) zu ermitteln.
Diese Daten werden benötigt, um für eine Suche im Umfeld (z.B. 50 km) alle Adressen zu berechnen und in einer Karte anzuzeigen.
Die Berechnung selbst und Anzeige auf der Karte bekomme ich hin.

Mein Problem ist die Ermittlung der Geoloordinaten.
Diese lassen sich aus dem Internet abfragen. Ich habe da eine Lösung unter Excel (*schauder*) gefunden, das war von Jörg Napp aus Weingarten,
und funktioniere über eine Yahoo-API. Die Antwort liegt im XMS-Format vor.
Der Quellcode läuft prima unter Excel und mit einer Genauigkeiten bis zur Hausnummer - aber leider nur unter Excel.
Nun wollte ich das ganze in OOO umsetzen, scheitere aber schon bei der Variablendefinition.
Dim Request As XMLHTTP
Dim Response As DOMDocument
Dim Section As IXMLDOMNode
Dim Node As IXMLDOMNode
Die versteht OOO nicht und ich habe leider keinen Plan wie ich die Definieren kann

Nachfolgend der EXCEL-Basic-Code

Code: Alles auswählen

Private Function Geocode(Address As String) As Collection
    ' Bei Yahoo eine ID beantragen
    Const APPID = "XXXXXXXXX"

    Const YahooURL = "http://local.yahooapis.com/MapsService/V1/geocode"

    Dim url As String
    Dim Request As XMLHTTP
    Dim Response As DOMDocument
    Dim Section As IXMLDOMNode
    Dim Node As IXMLDOMNode

    ' Adresse basteln
    url = YahooURL & "?appid=" & APPID & "&location=" & Address

    ' Abfrage der Adresse
    Set Request = New XMLHTTP

    Request.Open "get", url, False
    Request.send

    ' Antwort verarbeiten
    Set Response = New DOMDocument
    Response.loadXML Request.responseText

    ' XML-Antwort analysieren
    Set Section = Response.selectSingleNode("//ResultSet/Result")

    Set Geocode = New Collection
    For Each Node In Section.childNodes
        Geocode.Add Node.Text, Node.nodeName
    Next

    Geocode.Add Section.Attributes.getNamedItem("precision").Text, "Precision"

    ' Ressourcen freigeben
    Set Section = Nothing
    Set Response = Nothing
    Set Request = Nothing
End Function

Public Function Latitude(Address As String) As Double
    Dim coords As Collection
    Set coords = Geocode(Address)

    Latitude = Val(coords("Latitude"))
End Function

' Ermittelt den Längengrad einer Adresse
Public Function Longitude(Address As String) As Double
    Dim coords As Collection
    Set coords = Geocode(Address)
    Longitude = Val(coords("Longitude"))
End Function

' Ermittelt die Straße einer Adresse
Public Function Street(Address As String) As String
    Dim coords As Collection
    Set coords = Geocode(Address)
    Street = coords("Address")
End Function

' Ermittelt die Stadt einer Adresse
Public Function City(Address As String) As String
    Dim coords As Collection
    Set coords = Geocode(Address)
    City = coords("City")
End Function

' Ermittelt die Präzision des Geocodings einer Adresse
Public Function Precision(Address As String) As String
    Dim coords As Collection
    Set coords = Geocode(Address)
    Precision = coords("Precision")
End Function
Quelle http://joergnapp.de/geocoding_mit_excel/
Ausführbare Excel-Datei http://joergnapp.de/wp-content/uploads/ ... coding.xls

Hat jemand ne Idee wie ich das umssetzen kann?
Grüße
Mobbi
DPunch
*******
Beiträge: 1112
Registriert: Mo, 02.11.2009 16:16
Wohnort: Marburg

Re: Geocoding für Adressen über Internet

Beitrag von DPunch »

Aloha

Schau Dir Folgendes mal an:

OOo Wiki: XML and Filter

Die Abfrage und Analyse von XML-Antworten funktioniert damit.
Karolus
********
Beiträge: 7532
Registriert: Mo, 02.01.2006 19:48

Re: Geocoding für Adressen über Internet

Beitrag von Karolus »

Hallo
Die oben verlinkte Exceldatei funktioniert in OOo auch nach diversen Anpassungen im Code nicht.

Aber:Hier gibts ein paar Beispiele aus Calc heraus Python-funktionen zu nutzen.
Im Basic-Module benötigst du zusätzlich :

Code: Alles auswählen

Function geoinfo( addresse )
   sURL = URL_Main & "geoinfo" & URL_Args
   oMSP = getMasterScriptProvider()
   oScript = oMSP.getScript(sURL)
   x = oScript.invoke(Array( addresse  ),Array(),Array())
   geoinfo = x
end Function
im Pythonscript ist es leider nicht unbedingt plattformunabhängig:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import re ,urllib

wrapper = uno.createUnoStruct('com.sun.star.script.ArrayWrapper')
wrapper.IsZeroIndex = False

geourl ="""http://local.yahooapis.com/MapsService/V1/geocode?appid=XXXXXXXXX&location={0:}""".format
pat = re.compile(r'''(?:Result precision="|Latitude>|Longitude>|Address>|City>)(.+?)(?:">|</Latitude|</Longitude|</Address|</City)''' )

def geoinfo( adresse ):
    request = urllib.urlopen(geourl( adresse ))
    xml = request.read().decode('utf8')
    request.close()
    relist = pat.findall(xml)
    
    ## ! für die Rückgabe in mehrere Zellen (als Arrayformel ) die nächsten 3 Zeilen entkommentieren ! ##
    
    #relist = tuple((elem.encode('utf8'),) for elem in relist)
    #wrapper.Array = relist
    #return wrapper
    
    ### ! und die folgende Zeile auskommentieren ! ###
    return '\n'.join( relist ) 
Die obere Funktion ist plattformunabhängig liefert aber mit der OOoPython version stillschweigend kein Ergebnis. :?

Die Funktion unten funktioniert hier unter Linux auch direkt aus Calc, ist aber wahrscheinlich nicht plattformunabhängig weil sie hässlicherweise die xml-daten nicht mit python-eigenen Modulen holt, sondern das Kommandozeilentool "w3m" benutzt.
Auf den meisten "Linux"en dürfte das aber entweder schon dabei sein oder einfachst zu installieren.
Wenn ich Google glauben darf gibts auch für Windows "w3m"

Code: Alles auswählen

# -*- coding: utf-8 -*-
import re
from subprocess import Popen, PIPE

wrapper = uno.createUnoStruct('com.sun.star.script.ArrayWrapper')
wrapper.IsZeroIndex = False

geourl ="""http://local.yahooapis.com/MapsService/V1/geocode?appid=XXXXXXXXX&location={0:}""".format
pat = re.compile(r'''(?:Result precision="|Latitude>|Longitude>|Address>|City>)(.+?)(?:">|</Latitude|</Longitude|</Address|</City)''' )

def geoinfo( adresse ):
    request = Popen(["w3m" ,"-dump", geourl( "%s" % adresse.encode('utf8') )],stdout=PIPE).communicate()[0]
    xml = request.decode('utf8')
    relist = pat.findall(xml)
    relist = [elem.encode('utf8') for elem in relist]
    #relist = [(elem,) for elem in relist]
    #wrapper.Array = tuple(relist)
    return '\n'.join( relist )#wrapper
Ps.
Wenn du eigentlich eine Datenbank aufbauen willst aus vielen ( > 100 < 10000 Einzeladressen ),
würde ich :
1. die Einzeladressen einfach zeilenweise in eine Textdatei schreiben.
2. das obere Pythonscript anpassen, sodass es die Textdatei zeilenweise an die Funktion "geoinfo" übergibt und die Rückgabe ebenso in eine neue Ausgabedatei schreibt.

Die Ausgabedatei kannst du dann als "Datenbank" in Calc einbinden.

Gruß Karo
LO7.4.7.2 debian 12(bookworm) auf Raspberry5 8GB (ARM64)
LO25.2.3.2 flatpak debian 12(bookworm) auf Raspberry5 8GB (ARM64)
Mobbi
**
Beiträge: 23
Registriert: Do, 11.12.2008 21:52

Re: Geocoding für Adressen über Internet

Beitrag von Mobbi »

Hallo DPunch Karolus,

ich habe nicht wirklich viel verstanden.
Kenne mich bisher weder mit XML noch Phyton aus.
Ich Arbeite übrigens unter Windows.

Ich hatte mir das als Funktion wie eine DB-Abfrage vorgestellt. Übergebe Suchstring - erhalte Antwortstring mit Länge Breite usw.

Den Code 1 von Karolus habe ich etwas umgebaut damit der Abfragestring steht und den zum Test an die Shell übergeben.
Neuer Code nachfolgend.

Code: Alles auswählen

Function geoinfo( addresse )
   Dim sURL As String
   Const APPID = "paPZiSvV34E4hH0pWShd407HbT94qIX21npmHyPm.c7vcozqCzVVGiq048wC9BZA"
   Const YahooURL = "http://local.yahooapis.com/MapsService/V1/geocode"
   
   sURL = YahooURL & "?appid=" & APPID & "&location=" & Addresse
   'Test der URL -------------
       set extShell = CreateObject("Shell.Application")
       extShell.Open sURL
   'Test der URL Ende -------
   oMSP = getMasterScriptProvider()
   oScript = oMSP.getScript(sURL)
   x = oScript.invoke(Array( addresse  ),Array(),Array())
   geoinfo = x
End Function
Die Shell öffnet den Standartbrowser der dann das XML-Ergebniss enthält.
Der Abfrage selbst ist also schon mal richtig.
Wo muss ich den Phytoncode von Karolus hinpacken. Bei "oScript = oMSP.getScript(sURL)" steigt der Code aus.

Habe mit folgenden Adressen getestet:
Berlin unter den Linden
Koeln
http://local.yahooapis.com/MapsService/ ... n%20Linden


Grüße
Mobbi
DPunch
*******
Beiträge: 1112
Registriert: Mo, 02.11.2009 16:16
Wohnort: Marburg

Re: Geocoding für Adressen über Internet

Beitrag von DPunch »

Aloha

Wie gesagt kannst Du das Ganze auch komplett mit OOo Basic umsetzen. Sowohl über die Verwendung des SAX-Parsers, als auch per Hand.
Die Antwortzeit lässt allerdings etwas zu wünschen übrig in meinen Augen, möglicherweise ist da der Weg über die Einbindung von Python-Code schneller, keine Ahnung.

Hier mal der Code zu meinem "Workaround", Du kannst ja mal schauen, ob Dir das schnell genug ist - bzw. Karo könnte bei Gelegenheit ja mal vergleichen, ob das Ganze per Python schneller ist.

Code: Alles auswählen

Sub Main
	MsgBox GetGeoData("berlin unter den Linden")
End Sub

Function GetGeoData(sSearch as String) as String
	If Len(sSearch) = 0 Then Exit Function
	URL = "http://local.yahooapis.com/MapsService/V1/geocode?appid=paPZiSvV34E4hH0pWShd407HbT94qIX21npmHyPm.c7vcozqCzVVGiq048wC9BZA--&location="	
	URL = URL & sSearch
	oSimpleFileAccess = createUnoService( "com.sun.star.ucb.SimpleFileAccess" )
	On Error GoTo ErrorResponse
	oInputStream = oSimpleFileAccess.openFileRead(URL)
	oTextStream = createUnoService("com.sun.star.io.TextInputStream")
	oTextStream.InputStream = oInputStream
	aDelimiters = Array(ASC(">"),ASC("<"))
	sLastString = ""
	Do While NOT oTextStream.isEOF
		sThisString = oTextStream.readString(aDelimiters,True)
		Select Case sLastString
			Case "Longitude":
				sLongitude = sThisString
			Case "Latitude":
				sLatitude = sThisString
			Case "State":
				sState = sThisString
			Case "Address":
				sAddress = sThisString
			Case "City":
				sCity = sThisString
		End Select
		sLastString = sThisString
	Loop
	GetGeoData = "Ergebnis für """ & sSearch & """" & Chr(13) & Chr(13) & "Stadt: " & sCity & Chr(13) & _
				 "Adresse: " & sAddress & Chr(13) & "Land: " & sState & Chr(13) & "Breitengrad: " & _
				 sLatitude & Chr(13) & "Längengrad: " & sLongitude
	oInputStream.closeInput()
	Exit Function
	ErrorResponse:
	MsgBox "Kein Ergebnis gefunden", 48, "Ergebnis"
End Function
Karolus
********
Beiträge: 7532
Registriert: Mo, 02.01.2006 19:48

Re: Geocoding für Adressen über Internet

Beitrag von Karolus »

Hallo
@Dpunch
Wie schafftst du es eigentlich aus der eher bis zur Unzugänglichkeit verschachtelten OOo-Api das notwendige zusammenzuklauben ?
:shock:
Ich verzweifle da schon dabei den richtigen Service zu finden.
In Python suche ich mal ein Weile in der Docu finde "urllib" und ab da ist fast selbsterklärend.
Wie dem auch sei, meine Lösung via BasicBridge → Python → w3m und wieder zurück ist hier ca. 4 mal schneller :D als deine pure Basiclösung. (ca 0,4 versus 1,6 sec)

@Mobbi
Installier dir doch mal w3m, und übernehm den entsprechenden Code unverändert.

Ps. Die ellenlange kryptische APPID brauchts eigentlich nicht XXXXXXXXX tuts auch.

Gruß Karo
LO7.4.7.2 debian 12(bookworm) auf Raspberry5 8GB (ARM64)
LO25.2.3.2 flatpak debian 12(bookworm) auf Raspberry5 8GB (ARM64)
DPunch
*******
Beiträge: 1112
Registriert: Mo, 02.11.2009 16:16
Wohnort: Marburg

Re: Geocoding für Adressen über Internet

Beitrag von DPunch »

Aloha
Karolus hat geschrieben:Wie schafftst du es eigentlich aus der eher bis zur Unzugänglichkeit verschachtelten OOo-Api das notwendige zusammenzuklauben ?
Ich verzweifle da schon dabei den richtigen Service zu finden.
Zusammenklauben tue ich mir das nur relativ selten allein aus der API, das geht dann eher den Weg xRay -> API -> OOo Wiki.
Aber Du hast Recht, von allen Sprachen und APIs, die mir bisher je untergekommen sind, sind die Dokumentation / API von OpenOffice (Basic) das mit deutlichstem Abstand Schlechteste, was mir je untergekommen ist... und selbst wenn man sich mal einigermaßen zurecht gefunden hat, hat mat man immer noch mit der API zu kämpfen...
allein schon beim vorliegenden Fall:

Code: Alles auswählen

aDelimiters = Array(ASC(">"),ASC("<"))
(...)
oTextStream.readString(aDelimiters,True)
API: XTextInputStream: readString
Erster Parameter:
sequence< char > Delimiters
Bis ich herausgefunden hab, dass mit "sequence< char >" tatsächlich der Unicode-Wert gemeint ist, bin ich fast durchgedreht :?
Wie dem auch sei, meine Lösung via BasicBridge → Python → w3m und wieder zurück ist hier ca. 4 mal schneller :D als deine pure Basiclösung. (ca 0,4 versus 1,6 sec)
Das hatte ich schon vermutet, der Aufbau der Streams dauert schon bei lokalen Dateien sehr lange, eine XML-Response auf diese Weise auszuwerten konnte nicht effektiv sein.
Karolus
********
Beiträge: 7532
Registriert: Mo, 02.01.2006 19:48

Re: Geocoding für Adressen über Internet

Beitrag von Karolus »

Hallo

Ich konnte jetzt den Fehler einkreisen, der dazu geführt hat das die "Python-only"-lösung aus OOo heraus nicht funktioniert, es liegt an der importierten "urllib.py" aus der Python-lib. (z.B. /opt/libreoffice3/basis3.3/program/python-core-2.6.1/lib/urllib.py )***
Nach Austausch derselben durch die gleichnamige Datei einer normalen Python-bibliothek funktioniert das reibungslos.

***ist aber nicht nur in der aktuellen libreoffice so sondern auch in allen anderen *office-versionen.

Gruß Karo
LO7.4.7.2 debian 12(bookworm) auf Raspberry5 8GB (ARM64)
LO25.2.3.2 flatpak debian 12(bookworm) auf Raspberry5 8GB (ARM64)
Mobbi
**
Beiträge: 23
Registriert: Do, 11.12.2008 21:52

Re: Geocoding für Adressen über Internet

Beitrag von Mobbi »

Hallo

Danke erst mal für die Hilfe. Die reine OOO version über den SAX-PArser habe ich schon mal am laufen. Geht prima.
Dies werde ich wohl auch im Büro in mein Projekt einarbeiten, da ich hierfür auf dem Server nichts installieren darf.
Dass diese Version langsamer läuft macht nichts, da ich die einmal ermittelten Daten bei den Adress- und Kundendaten ablege und
nur beim Neuanlegen bzw. Ändern von Lieferadressen oder Kontakten das Geocoding durchführe.
Zeitaufwendig wird es nur den aktuellen Adressbestand zu ergänzen. Da läuft das Makro dann etwa 2 Stunden oder 2 x 1 Stunde über Mittag. :-))

@ Karolus: Die Lösung über Phyton klingt allerdings viel eleganter. Hier beisse ich mir aber die Zähne aus. Mit W3m meinst du schon http://www.w3m.org/ ! Oder?
Das hab ich auch schon installiert aber wie gehts dann weiter? Wohin mit Deinem Programmcode?

Gruß Mobbi
Karolus
********
Beiträge: 7532
Registriert: Mo, 02.01.2006 19:48

Re: Geocoding für Adressen über Internet

Beitrag von Karolus »

Hallo
Ich schrieb ja bereits daß der Fehler in der urllib.py steckt die mit OOo mitgeliefert wird.
Tauscht du diese Datei gegen eine aus einer Standard Python-distribution, benötigst du "w3m" nicht mehr.
Im Angehängten zip-archiv hab ich dir alles notwendige zusammengepackt, verschiebe die Datei "urllib.py" daraus in den Programmordner von OOo und dann ins Unterverzeichnis "/basis3.3/program/python-core-2.6.1/lib/".
Den Ordner "python" aus de Zip-Datei verschiebst du in das Unterverzeichnis "Scripts" deiner OOo Anwendungs-daten.

Dan Code für den Basicteil nochmal als Quelltext:

Code: Alles auswählen

Global g_MasterScriptProvider
REM Specify location of Python script, providing cell functions:

Const URL_Main = "vnd.sun.star.script:geo.py$"
Const URL_Args = "?language=Python&location=user"

'######____Schnittstelle zum Pythonscript___####

Function getMasterScriptProvider()
   if NOT isObject(g_MasterScriptProvider) then
      oMasterScriptProviderFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory")
      g_MasterScriptProvider = oMasterScriptProviderFactory.createScriptProvider("")
   endif
   getMasterScriptProvider = g_MasterScriptProvider
End Function

'####################__Schnittstelle_ende__###### 
Function geoinfo( data )
   sURL = URL_Main & "geoinfo" & URL_Args
   oMSP = getMasterScriptProvider()
   oScript = oMSP.getScript(sURL)
   x = oScript.invoke(Array( data  ),Array(),Array())
   geoinfo = x
end Function
urllib.zip
(16.05 KiB) 105-mal heruntergeladen
Gruß Karo
LO7.4.7.2 debian 12(bookworm) auf Raspberry5 8GB (ARM64)
LO25.2.3.2 flatpak debian 12(bookworm) auf Raspberry5 8GB (ARM64)
Karolus
********
Beiträge: 7532
Registriert: Mo, 02.01.2006 19:48

Re: Geocoding für Adressen über Internet

Beitrag von Karolus »

Hallo
Der in diesem Thread aufgedeckte Fehler in der OOo-pythonlibrary ist im gerade veröffentlichen RC1 von Libreoffice berichtigt worden.
http://www.documentfoundation.org/download/

Gruß Karo
LO7.4.7.2 debian 12(bookworm) auf Raspberry5 8GB (ARM64)
LO25.2.3.2 flatpak debian 12(bookworm) auf Raspberry5 8GB (ARM64)
MerlinTransporte
Beiträge: 2
Registriert: Di, 20.09.2011 14:13

Re: Geocoding für Adressen über Internet

Beitrag von MerlinTransporte »

Doesn't work anymore!
I implemented this function into Libreoffice Calc. It worked fine for almost one year, but today it stopped working.

Looks like Yahoo abandoned the maps API:
http://developer.yahoo.com/maps/

Does anyone have an alternative method for getting the latitude and longitude of a specific adress into ooo calc?
Maybe using OviMaps? Or Google Maps?
DPunch
*******
Beiträge: 1112
Registriert: Mo, 02.11.2009 16:16
Wohnort: Marburg

Re: Geocoding für Adressen über Internet

Beitrag von DPunch »

Aloha

Try Google Maps, uses an URL like "http://maps.google.com/maps/api/geocode/xml?address=WhatYouSearchFor&sensor=false" so it shouldnt be all that hard to adapt to your current code (also supports JSON instead of XML).

Otherwise have a look at the attached file for how to use it with SAX-Parser in OOo Basic.
Dateianhänge
GeoData.ods
(9.42 KiB) 177-mal heruntergeladen
MerlinTransporte
Beiträge: 2
Registriert: Di, 20.09.2011 14:13

Re: Geocoding für Adressen über Internet

Beitrag von MerlinTransporte »

Hallo!

Vielen Dank für die prompte Antwort und den Vorschlag!
Ich hatte schon eine Lösung gefunden: Google bietet eine ähnliche Funktion wie yahoo. Ich verwende den Link

http://maps.google.com/maps/geo?output= ... abcdefg&q=

gefolgt von der Straßenadresse, dann bekommt man wie bei yahoo eine XML-Datei zurück.

Für alle, die es interessiert, anbei der Libreoffice-Code, der die Koordinaten in sCoordinates ausspuckt.
(Vorsicht mit sLatitude und sLongitude: das sind nur extrem rudimentär ausgewertete Substrings, die auf unsere Situation zugeschnitten sind (Längengrad hat bei uns immer eine Stelle vor dem Komma, Breitengrad immer zwei).)

Vielleicht hilft's ja jemandem.
Gruß,
Dirk

Code: Alles auswählen

Function GetGeoData(sSearch as String) as String
   If Len(sSearch) = 0 Then Exit Function
   URL = "http://maps.google.com/maps/geo?output=xml&sensor=false&key=abcdefg&q="  
   URL = URL & sSearch
   oSimpleFileAccess = createUnoService( "com.sun.star.ucb.SimpleFileAccess" )
   On Error GoTo ErrorResponse
   oInputStream = oSimpleFileAccess.openFileRead(URL)
   oTextStream = createUnoService("com.sun.star.io.TextInputStream")
   oTextStream.InputStream = oInputStream
   aDelimiters = Array(ASC(">"),ASC("<"))
   sLastString = ""
   Do While NOT oTextStream.isEOF
      sThisString = oTextStream.readString(aDelimiters,True)
      Select Case sLastString
         Case "coordinates":
            sCoordinates = sThisString
         Case "PostalCodeNumber":
            sPLZ = sThisString
         Case "CountryName":
            sState = sThisString
         Case "ThoroughfareName":
            sAddress = sThisString
         Case "DependentLocalityName":
            sCity = sThisString
      End Select
      sLastString = sThisString
   Loop
   sLongitude = Left(sCoordinates,7)
   sLatitude = Mid(sCoordinates,InStr(sCoordinates,",")+1,8)
   if sAddress = "" then
   GetGeoData = "ACHTUNG!!!ACHTUNG!!! " & sPLZ & " " & sCity & Chr(13) & _
             "Straße: " & sAddress & Chr(13) & " Koordinaten: " & _
             sCoordinates & Chr(13) & " Längengrad: " & sLongitude & " Breitengrad: " &sLatitude
   else
      GetGeoData = sPLZ & " " & sCity & Chr(13) & _
             "Straße: " & sAddress & Chr(13) & " Koordinaten: " & _
             sCoordinates & Chr(13) & " Längengrad: " & sLongitude & " Breitengrad: " &sLatitude
   end if      
   oInputStream.closeInput()
   Exit Function
   ErrorResponse:
   GetGeoData = "Kein Ergebnis gefunden!!!"
End Function

Moderation,4: CODE tags gesetzt
Antworten