Geolocation_Grundlagen

ehemalige Veranstaltungen ia3.netz + ia3.data sowie ia4.Netz im Studiengang Interaktive Medien

(c) 2020 Hochschule Augsburg - Fakultät für Informatik - Prof.Dr.Nik Klever

Grundlagen GeoLocation - ortsbezogene Informationen

Koordinatensystem

Ortsbezogene Daten basieren auf dem Geografischen Koordinatensystem, welches einem Koordinatensystem auf einer Kugel mit den Breitengraden $\varphi$ und den Längengraden $\lambda$:

GPS - Global Positioning System

Mit der Einführung des GPS-Systems ab 1995 und insbesondere seit der Freigabe für die öffentliche Nutzung im Jahr 2000 hat sowohl die Navigierung als auch die Datenhaltung standortbestimmter Informationen exponentiell zugenommen. In welchem Kfz ist heute kein Navi enthalten ? Und wer benutzt heutzutage sein Smartphone nicht, um Restaurants, Museen, Events, billigste Tankstellen, Bankautomaten, etc., etc. im Umkreis seines aktuellen Standorts zu finden ?

Das GPS System bzw. jedes globale Satellitennavigationssystem beruht auf der Bestimmung der Entfernung eines Satelliten zu einem entsprechenden GPS-Empfänger über die Signallaufzeit eines von diesem Satelliten ausgesendeten Signals. Wenn die Entfernung zu mindestens 3 Satelliten bekannt ist, kann der Schnittpunkt dreier Kugelflächen berechnet werden. Mindestens ein weiterer Satellit ist notwendig, um die Uhrzeit im GPS-Empfänger zu synchronisieren, sodass letztendlich damit die aktuelle Position (d.h. die Koordinaten) auf der Erdoberfläche bestimmt werden kann.

Kartenmaterial

Die Koordinaten bestimmen zwar die aktuelle Position auf der Erdoberfläche, aber ohne eine dazugehörige Karte sind diese Koordinaten in der Regel nutzlos. Basis aller heutigen Kartendarstellungen ist die sogenannte Mercator-Projektion, welches die Kugelform der Erde in eine winkelgetreue flächige Darstellung projeziert.

Mercator-Projektion

Basis allen Kartenmaterials bis ins letzte Drittel des vergangenen Jahrhunderts war die Arbeit und Aufzeichnung der Vermessungsämter, die im staatlichen Auftrag die Aktualisierung aller geobasierten Daten u.a. auch mit regelmäßigen Luftaufnahmen durchführen. Alle Karten (Strassenkarten, Ortskarten, Wanderkarten, etc.) bauen in der Regel auf Daten der Vermessungsämter auf. Erst mit dem Aufkommen globaler und kommerziell verfügbarer Satellitendaten beschäftigen sich auch andere Organisationen mit der Erstellung von Karten.

JavaScript Karten Bibliotheken (Map Libraries)

Google APIs

Andere APIs

OpenStreetMap hat keine eigene API, aber es gibt andere JavaScript Karten Bibliotheken, die hierfür verwendet werden können. Die bekanntesten sind

Leaflet.js hat eine kompaktere und einfachere API, wohingegen OpenLayers eine umfangreichere API besitzt.

Kurze Fragen

  1. Nennen Sie die zwei wichtigsten Daten mit denen ein Ort auf der Erde beschrieben werden kann ?
  2. Welches weitere Datum ist für eine Ortsbeschreibung sinnvoll ?

Wegaufzeichnung

In einer Wegaufzeichnung (Tracking) liegen eine Anzahl von Orts-Daten entlang eines Weges in einer Datei wegdaten.csv im CSV Format mit Längengrad, Breitengrad und Höhe pro Zeile vor, d.h. pro Zeile ist ein Ortsdatum mit Längengrad;Breitengrad;Höhe\n aufgezeichnet.

Kurze Fragen

  1. Wiederholung: Welche drei Strukturen gibt es um in einem Programm Daten zu speichern ?
  2. Welche Strukturen kann man für die Daten aus der obigen Wegaufzeichnung verwenden ?

Einlesen der Daten

In [1]:
f = open('wegdaten.csv','r')
gesamterInhalt = f.read()
f.close()

Ansehen in welcher Struktur die Daten vorliegen

In [2]:
zeilen = gesamterInhalt.split('\n')
print("erste Zeile: "+zeilen[0])
print("zweite Zeile: "+zeilen[1])

# Löschen der letzten Zeile, wenn diese leer ist
if not zeilen[-1]: del zeilen[-1] 
    
print("letzte Zeile: "+zeilen[-1])

# Anzahl der Wegdaten
print(len(zeilen)-1)
erste Zeile: lon;lat;alt
zweite Zeile: 10.905733108520192;48.358350992201352;545
letzte Zeile: 10.906054973601979;48.358254432676816;536
60

Überlegung mit welcher Struktur die Daten am besten verarbeitet werden können

Kurze Fragen

  1. Wie kann ein einziges Wegdatum in einer Variablen welchen Typs gespeichert werden ?
  2. Diskutieren sie insbesondere drei unterschiedliche Möglichkeiten und wählen die Struktur aus, mit der sie am besten mit einem einzigen Wegdatum umgehen können !
  3. Wie können alle Wegdaten in einer Variablen welchen Typs gespeichert werden ?

Welche drei unterschiedlichen Strukturen sind für ein einziges Wegdatum möglich ?

1. Liste

In [3]:
# Überschrift in der ersten Zeile
ueberschrift = zeilen[0].split(';')

# erstes Wegdatum in der zweiten Zeile
einErstesWegDatum = zeilen[1].split(';')
for i in einErstesWegDatum:
    print(i)
10.905733108520192
48.358350992201352
545

Kurze Fragen

  1. Welchen Typ haben die drei Listenelemente ?
  2. Ist dieser Typ sinnvoll ?
In [4]:
for i in einErstesWegDatum:
    print(i)
    print(type(i))
    x=float(i)
    print(type(x))
10.905733108520192
<class 'str'>
<class 'float'>
48.358350992201352
<class 'str'>
<class 'float'>
545
<class 'str'>
<class 'float'>

2. Dictionary

In [5]:
wegDatumAlsDictionary = dict(lon=einErstesWegDatum[0],lat=einErstesWegDatum[1],alt=einErstesWegDatum[2])
print(wegDatumAlsDictionary)
{'lon': '10.905733108520192', 'lat': '48.358350992201352', 'alt': '545'}

Dictionary - 2. Form

In [6]:
wegDatumAlsDictionary = {'lon':einErstesWegDatum[0],'lat':einErstesWegDatum[1],'alt':einErstesWegDatum[2]}
print(wegDatumAlsDictionary)
{'lon': '10.905733108520192', 'lat': '48.358350992201352', 'alt': '545'}

Dictionary - 3. Form

In [7]:
wegDatumAlsDictionary = dict((('lon',einErstesWegDatum[0]),('lat',einErstesWegDatum[1]),('alt',einErstesWegDatum[2])))
print(wegDatumAlsDictionary)
{'lon': '10.905733108520192', 'lat': '48.358350992201352', 'alt': '545'}

Ausnutzen der Spaltendefinition in der ersten Zeile der CSV-Datei

In [8]:
spaltendefinition = zeilen[0].split(';')
print(spaltendefinition)

wegDatumAlsDictionary = dict(((spaltendefinition[0],einErstesWegDatum[0]),
                              (spaltendefinition[1],einErstesWegDatum[1]),
                              (spaltendefinition[2],einErstesWegDatum[2])))
print(wegDatumAlsDictionary)
['lon', 'lat', 'alt']
{'lon': '10.905733108520192', 'lat': '48.358350992201352', 'alt': '545'}

Verwenden der Listcomprehension

siehe Python Tutorial

In [9]:
spaltendefinition = zeilen[0].split(';')
print(spaltendefinition)

listcomprehension = [(spaltendefinition[x], einErstesWegDatum[x]) for x in range(len(spaltendefinition))]
print(listcomprehension)

wegdatum = dict(listcomprehension)
print(wegdatum)
['lon', 'lat', 'alt']
[('lon', '10.905733108520192'), ('lat', '48.358350992201352'), ('alt', '545')]
{'lon': '10.905733108520192', 'lat': '48.358350992201352', 'alt': '545'}

Umwandlung in einen "sinnvollen" Typ

In [10]:
spaltendefinition = zeilen[0].split(';')
print(spaltendefinition)

listcomprehension = [(spaltendefinition[x], float(einErstesWegDatum[x])) for x in range(len(spaltendefinition))]
print(listcomprehension)

wegdatum = dict(listcomprehension)
print(wegdatum)
['lon', 'lat', 'alt']
[('lon', 10.905733108520192), ('lat', 48.35835099220135), ('alt', 545.0)]
{'lon': 10.905733108520192, 'lat': 48.35835099220135, 'alt': 545.0}

3. Objekt einer Klasse

Kurze Fragen

  1. Welche Instanzvariablen sind für eine Klasse Ort sinnvoll ?
  2. Welche Parameter sind für den Konstruktor __init__ sinnvoll ?
  3. Welche beiden Methoden sind sinnvoll ?
In [11]:
class Ort(object):
    def __init__(self,lon,lat,alt=0.0):
        self.lon = lon
        self.lat = lat
        self.alt = alt
    def getKoord(self):
        return (self.lon,self.lat,self.alt)
    def setKoord(self,lon,lat,alt=0.0):
        self.lon = lon
        self.lat = lat
        self.alt = alt

spaltendefinition = zeilen[0].split(';')
print(spaltendefinition)

wegdatum = Ort(float(einErstesWegDatum[0]),float(einErstesWegDatum[1]),float(einErstesWegDatum[2]))
print(wegdatum.getKoord())
['lon', 'lat', 'alt']
(10.905733108520192, 48.35835099220135, 545.0)

Objekt einer Klasse - 2. Form - einschliesslich der Spaltendefinition und getKoord als Dictionary

In [12]:
class Ort(object):
    def __init__(self,kw):
        self.lat = kw['lat']
        self.lon = kw['lon']
        if 'alt' in kw: 
            self.alt = kw['alt']
        else:
            self.alt = 0.0
    def getKoord(self):
        return dict(lon=self.lon,lat=self.lat,alt=self.alt)
    def setKoord(self,lon,lat,alt):
        self.lon = lon
        self.lat = lat
        self.alt = alt

spaltendefinition = zeilen[0].split(';')
print(spaltendefinition)

listcomprehension = [(spaltendefinition[x], float(einErstesWegDatum[x])) for x in range(len(spaltendefinition))]
dictionary = dict(listcomprehension)
wegdatum = Ort(dictionary)
print(wegdatum.getKoord())

# bzw. in einer Zeile
wegdatum = Ort(dict([(spaltendefinition[x], float(einErstesWegDatum[x])) for x in range(len(spaltendefinition))]))
print(wegdatum.getKoord())
['lon', 'lat', 'alt']
{'lon': 10.905733108520192, 'lat': 48.35835099220135, 'alt': 545.0}
{'lon': 10.905733108520192, 'lat': 48.35835099220135, 'alt': 545.0}

Zusammenfassung - Einlesen und Speichern aller Wegdaten Objekte in einer Liste

Schleife über alle Zeilen - Speichern in einer Liste von Dictionaries

In [13]:
f = open('wegdaten.csv','r')
gesamterInhalt = f.read()
f.close()

zeilen = gesamterInhalt.split('\n')[:-1]
spaltendefinition = zeilen[0].split(';')

alleWegDaten = []

for zeile in zeilen[1:]:
    spalten = zeile.split(';')
    listcomprehension = [(spaltendefinition[x], float(spalten[x])) for x in range(len(spalten))]
    wegdatum = dict(listcomprehension)
    alleWegDaten.append(wegdatum)

Schleife über alle Zeilen - Speichern in einer Liste von Klasseninstanzen

In [14]:
class Ort(object):
    def __init__(self,kw):
        self.lat = kw['lat']
        self.lon = kw['lon']
        if 'alt' in kw: 
            self.alt = kw['alt']
        else:
            self.alt = 0.0
    def getKoord(self):
        return dict(lon=self.lon,lat=self.lat,alt=self.alt)
    def setKoord(self,lon,lat,alt):
        self.lon = lon
        self.lat = lat
        self.alt = alt

f = open('wegdaten.csv','r')
gesamterInhalt = f.read()
f.close()

zeilen = gesamterInhalt.split('\n')[:-1]
spaltendefinition = zeilen[0].split(';')

alleWegDaten = []

for zeile in zeilen[1:]:
    spalten = zeile.split(';')
    wegdatum = Ort(dict((spaltendefinition[x], float(spalten[x])) for x in range(len(spalten))))
    alleWegDaten.append(wegdatum)

Google Static Map

siehe Google Static Maps API Developer Guide

Holen sie sich auf der dortigen Seite einen entsprechenden API-Schlüssel

Grundlagen

Ausgehend von der URL http(s)://maps.googleapis.com/maps/api/staticmap? können an diese URL diverse Parameter angehängt werden.

Notwendig ist immer der Parameter

  • size=640x640

sinnvoll ist der Parameter

  • maptype=satellite

und Wegpunkte können mit dem Parameter

  • markers=markerstyles|markerLocation1|markerLocation2|...

gesetzt werden.

In [24]:
GOOGLEAPIKEY = "............" # setzen sie hier ihren API-Schlüssel ein
In [25]:
url = "http://maps.googleapis.com/maps/api/staticmap?size=640x640&maptype=satellite&key={}".format(GOOGLEAPIKEY) 

url += "&markers=color:blue"

for wegdatum in alleWegDaten:
    url += "|{:.6f},{:.6f}".format(wegdatum.lat,wegdatum.lon)
    #
    # statt der obigen Zeile würde ebenfalls gehen
    #
    # url += "|{lat:.6f},{lon:.6f}".format(**wegdatum.getKoord())
    
print(url)
http://maps.googleapis.com/maps/api/staticmap?size=640x640&maptype=satellite&key=............&markers=color:blue|48.358351,10.905733|48.358437,10.905325|48.358705,10.905261|48.358598,10.904875|48.358426,10.904553|48.358372,10.904145|48.358458,10.903759|48.358705,10.903566|48.358705,10.903158|48.358855,10.902793|48.359038,10.902472|48.359252,10.902214|48.359510,10.902064|48.359756,10.901892|48.359853,10.901506|48.359832,10.901098|48.359735,10.900712|48.359692,10.900304|48.359756,10.899897|48.360035,10.900004|48.360304,10.900068|48.360583,10.900111|48.360722,10.899746|48.360744,10.899339|48.360829,10.898931|48.361065,10.898695|48.361334,10.898631|48.361623,10.898652|48.361881,10.898845|48.362042,10.899189|48.362117,10.899596|48.362160,10.900004|48.362106,10.900412|48.362117,10.900819|48.362042,10.901227|48.361956,10.901613|48.361913,10.902021|48.361859,10.902429|48.361816,10.902836|48.361537,10.902922|48.361280,10.903051|48.361216,10.903459|48.361173,10.903866|48.361140,10.904274|48.361055,10.904660|48.360904,10.905004|48.360701,10.905304|48.360432,10.905411|48.360164,10.905476|48.359907,10.905604|48.359724,10.905905|48.359531,10.906205|48.359252,10.906227|48.359123,10.906591|48.359284,10.906935|48.359048,10.907149|48.358802,10.906956|48.358587,10.906699|48.358437,10.906355|48.358254,10.906055

Ausgabe als IPython Image

In [17]:
from IPython.display import Image
Image(url=url)
Out[17]:

Erweiterung der Anwendung

Erweiterung der obigen Anwendung in der Art, dass alle Punkte, die in einem bestimmten Umkreis um den folgenden Mittelpunkt (longitude, latitude) und Radius (radius - Dimension in m) liegen, mit andersfarbigen Markern versehen werden.

longitude = 10.906
latitude = 48.358867
radius = 100

1. Markieren des Mittelpunktes des Kreises

In [18]:
mittelpunkt = Ort(dict(lat=48.358867,lon=10.906,alt=0))

url = "http://maps.googleapis.com/maps/api/staticmap?size=640x640&maptype=satellite&key={}".format(GOOGLEAPIKEY) 

url += "&markers=color:green|{:.6f},{:.6f}".format(mittelpunkt.lat,mittelpunkt.lon)
url += "&markers=color:blue"

for wegdatum in alleWegDaten:
    url += "|{:.6f},{:.6f}".format(wegdatum.lat,wegdatum.lon)

Image(url=url)
Out[18]:

Kurze Fragen

  1. Wie kann man überprüfen, ob bestimmte Punkte innerhalb oder außerhalb eines Kreises um einen Mittelpunkt liegen ?

2. Erweiterung der Klasse Ort um die Funktion 'abstand' zur Berechnung des annähernden Abstands zweier Geopunkte

siehe Erich Seifert's Java-Python Vergleich

Zur Herleitung der Formel des Abstands auf einer Kugel siehe den Wikipedia-Beitrag zu Orthodrome

In [19]:
import math
class Ort(object):
    def __init__(self,kw):
        self.lat = kw['lat']
        self.lon = kw['lon']
        if 'alt' in kw:
            self.alt = kw['alt']
        else:
            self.alt = 0.0
    def getKoord(self):
        return dict(lon=self.lon,lat=self.lat,alt=self.alt)
    def setKoord(self,lon,lat,alt):
        self.lon = lon
        self.lat = lat
        self.alt = alt
    def abstand(self,zweiterort,typ="Kugel"):
        if typ == "Flaeche": # Berechnung des Abstands auf einer Fläche
            return math.sqrt((self.lon - zweiterort.lon)**2+(self.lat - zweiterort.lat)**2)*111
        else: # Berechnung des Abstands auf einer Kugel
            RadiusErde = 6371000 # mittlerer Radius der Erde in m
            lat1 = math.radians(self.lat)
            lat2 = math.radians(zweiterort.lat)
            lon1 = math.radians(self.lon)
            lon2 = math.radians(zweiterort.lon)
            return math.acos(math.sin(lat1) * math.sin(lat2) + \
                   math.cos(lat1) * math.cos(lat2) * math.cos(lon2-lon1)) * RadiusErde
        
f = open('wegdaten.csv','r')
gesamterInhalt = f.read()
f.close()

zeilen = gesamterInhalt.split('\n')[:-1]
spaltendefinition = zeilen[0].split(';')

alleWegDaten = []

for zeile in zeilen[1:]:
    spalten = zeile.split(';')
    wegdatum = Ort(dict((spaltendefinition[x], float(spalten[x])) for x in range(len(spalten))))
    alleWegDaten.append(wegdatum)

3. Markieren der aller Wegpunkte innerhalb des Kreises

In [20]:
mittelpunkt = Ort(dict(lat=48.358867,lon=10.906,alt=0))
radius = 100

url = "http://maps.googleapis.com/maps/api/staticmap?size=640x640&maptype=satellite&key={}".format(GOOGLEAPIKEY) 

url += "&markers=color:green|{:.6f},{:.6f}".format(mittelpunkt.lat,mittelpunkt.lon)
url += "&markers=color:blue"

for wegdatum in alleWegDaten:
    if  wegdatum.abstand(mittelpunkt) > radius:
        url += "|{:.6f},{:.6f}".format(wegdatum.lat,wegdatum.lon)

4. Hinzufügen der roten Marker für alle Punkte innerhalb des Kreises

In [21]:
url += "&markers=color:red"

for wegdatum in alleWegDaten:
    if wegdatum.abstand(mittelpunkt) < radius:
        url += "|{:.6f},{:.6f}".format(wegdatum.lat,wegdatum.lon)

Image(url=url)
Out[21]:

GPX - GPS Exchange Format

GPX ist ein einfaches XML basiertes Dateiformat zum Abspeichern und Austauschen von Geodaten. Es wurde 2002 von der Fa. TopoGrafix entwickelt und als XML Schema auf deren Webseite Documentation in der Version 1.1 freigegeben.

Eine gute Einführung zu GPX zusammen mit einem einfachen Beispiel findet sich auf der Wikipedia Webseite GPS Exchange Format.

Erstellen eines SAX Parsers

Entsprechend den Beispielen im Skript XML Programmieren Teil 1), wird im folgenden ein SAX-Parser erstellt der den Inhalt einer GPX-Datei verwendet und wie im vorangegangenen Beispiel alle Wegdaten in einer Liste von Objekten des Typs Ort abspeichert.

Die obige Wegaufzeichnung (Tracking) liegt ebenfalls in einer Datei wegdaten.gpx im GPX Format vor. Sie erkennen an der Datei, dass Längengrad und Breitengrad als Attribute des GPX-Tags trkpt und die Höhe als Inhalt des Elements ele enthalten ist. Diese Daten gilt es als Instanzen der Klasse Ort wie im Beispiel der CSV-Datei in einer entsprechende Liste zu speichern.

In [22]:
from xml.sax import make_parser, parseString, handler, SAXException, SAXParseException

class SAXGPXParser(handler.ContentHandler, Ort):
    
    def __init__(self):
        self.alleWegDaten = []
        self.aktuellerName = ''
    
    
    def startElement(self, name, attrs):
        self.aktuellerName = name
        if name == 'trkpt':
            self.wegdatum = {}  
            for attr in attrs.getNames():
                self.wegdatum[attr] = float(attrs.getValue(attr))           
        
    def characters(self, content):
        if self.aktuellerName == 'ele':
            self.wegdatum['alt'] = float(content)
            self.alleWegDaten.append(Ort(self.wegdatum)) 
        
    def endElement(self, name):
        self.aktuellerName = ''

f = open('wegdaten.gpx','r')
gesamterInhalt = f.read()
f.close()
        
instanz = SAXGPXParser()
parseString(gesamterInhalt,instanz)
print(instanz.alleWegDaten[0].getKoord())
{'lon': 10.905861854552906, 'lat': 48.358286619184994, 'alt': 545.0}

KML - Keyhole Markup Language

KML ist im Gegensatz zu GPX ein sehr komplexes XML basiertes Dateiformat zum Abspeichern und Austauschen von Geodaten. Die Fa. Keyhole wurde 2004 von Google übernommen und wird seitdem auch von Google weiterentwickelt. 2008 wurde die Version 2.2 - für die ein gutes Google Developers Tutorial exisitiert - vom Open Geospatial Consortium als Standard anerkannt und übernommen.

Die obige Wegaufzeichnung (Tracking) liegt ebenfalls in einer Datei wegdaten.kml im KML Format vor.

Praktikumsaufgabe 1

Erstellen einer Klasse "WegDaten"

Erweitern sie die Geolocation Anwendung in der Art, dass die gesamte bisherige Anwendung mit einer Klasse class WegDaten dargestellt wird, die folgenden drei Methoden besitzt und in der die einzelnen Wegpunkte als Instanzen der Klasse Ort verwendet werden:

  • __init__(self,filename_oder_url)
  • parseCSV(self)
  • writeGoogleStaticMapsUrl(self,lon,lat,alt,radius)

Praktikumsaufgabe 2

Integration des SAX Parsers

Integrieren sie den SAX Parser SAXGPXParser aus dem Skript in die obige Anwendung mittels einer Methode parseGPX(self) der Klasse WegDaten

Praktikumsaufgabe 3

Einbau der Klasse WegDaten in einen Flask Webserver

Es macht Sinn die Klassen Ort, SAXGPXParser und WegDaten in einer Datei GeoLocation.py zusammenzufügen.

Die Initialisierung der Instanz von WegDaten, der Aufruf der Instanzmethoden parseCSV bzw. parseGPX und writeGoogleStaticMapsUrl geschieht analog dem bisherigen Beispiel.

Der Konstruktor von WegDaten muss entsprechend dem Hochladen von Dateien angepasst werden.

Hinweis:

Bereits in den Aufgaben im Skript Flask Grundlagen wurde die eigentliche API für das Hochladen von Dateien auf der Werkzeug Webseite angesprochen. Auch in diesem Fall muss die hochgeladene Datei nicht mittels der Funktion save auf dem Filesystem gespeichert, sondern nur der Inhalt für die Berechnung verwendet werden. Dies geschieht mit der Funktion stream, die wie eine geöffnete Datei angesehen werden kann.

Praktikumsaufgabe 4

Erweiterung der bisherigen Anwendung um die Ausgabe mittels Google Maps

Erstellen sie eine Methode createGoogleMapsObject, indem sie das Flask Plugin Flask-GoogleMaps dazu verwenden. Installieren Sie dieses Plugin über

pip install flask-googlemaps

Sehen sie sich hierzu insbesondere die Beispiele auf der Github-Seite von Flask-GoogleMaps an - diese sind nur wenig anzupassen.