NaviOhm

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche


Projektbeschreibung

NaviOhm ist eine Augmented Reality App, die interaktiv die Orientierung auf dem Hochschulgelände erleichtert.

Funktionalitäten

- Ortung des Standortes und Anzeige der Fakultäten der Hochschule via GPS/Netzwerkkoordinaten
- Anzeigen wichtiger Infos, z.B.: Öffnungszeiten, Computerräume, ...
- Darstellung via OpenGL

Systemübersicht

Folgende Komponenten spielen bei der Umsetzung dieser interaktiven App eine wichtige Rolle:


Hardwarekomponenten

Mobiltelefon mit:
- GPS
- Kamera
- Kompass
- Betriebssystem: Android

Softwarekomponenten

- Eclipse -> Entwicklungsumgebung
- Android SDK -> Programmierschnittstelle
- SQLite -> Datenbanksystem
- Java -> Programmiersprache
- OpenGL -> Programmierschnittstelle




Vorgehensweise


Datenbank

Datenbankmodell


Aufbau Datenbank



Die Daten der Gebäude wurde in der Tabelle GPS_Information wie folgt strukturiert:


  • locality_ID enthält den ganzzahlig fortlaufenden Primärschlüssel. Für die Zuordnung der jeweiligen GPS-Daten zu einen der Gebäude, ist der Inhalt von locality_ ID identisch, den Inhalt der gleichnamigen Spalte der Tabelle GPS_Coordinates, zuzuweisen.
  • locality entspricht der namentlichen Bezeichnung des Gebäudes und hat somit den Datentyp varchar(255).
  • openinghours gibt Auskunft über die Öffnungszeiten der Gebäude. Diese Zeiten sollen in der Applikation als Zusatzinformation zu den Gebäuden angezeigt werden. Da es sich um einen Text handelt wurde hier der Datentyp varchar(255) gewählt. Der Datentyp "Date" wäre in diesem Fall ungeeignet, da viele der Gebäude an unterschiedlichen Tagen geöffnet haben. Dies würde zu vielen Null-Werten in der Tabelle führen.
  • information enthält wichtige Gebäudeinformationen, wie beispielsweise die Raumnummer des Rechenzentrums oder Informationen über Kaffeeautomaten. Wegen des groÿen Textspeicherbedarfs wurde hier varchar(2550) gewählt.

Ergänzend wurde in der Tabelle GPS_Coordinates folgende Spalten erstellt:

  • GPS_ID ist der eindeutige Primärschlüssel der Tabelle GPS_Coordinates. Die ganzzahlige Nummerierung der Datensätze erfolgt fortlaufend und beginnt bei eins.
  • GPS_N enthält die GPS-Position der nördlichen Breite. Durch die Definierung des Datentyps in number(3,6) ist die Eingabe von bis zu sechs Nachkommastellen möglich.
  • GPS_E gibt Aussage über die Koordinaten des Punktes bezogen auf den Längengrad. Ebenfalls wurde hier der Datentyp "number(3,6)" verwendet. Damit ein Punkt eines Gebäudes nicht doppelt gespeichert wird, wurde mit der Funktion unique(GPS_N, GPS_E) die Einzigartigkeit sichergestellt.
  • mark nummeriert die GPS-Koordinaten, zugehörig zum jeweiligen Gebäude, durch. Wegen der Ganzzahligkeit wurde hier der Datentyp "integer" gewählt.
  • locality_ID dient der Zuordnung der GPS-Daten zu einem der Gebäude. Die zu jeweils einen Gebäude zugeordnete locality_ID ist durch Einzigartigkeit bestimmt.
  • is_entry kennzeichnet mit den Wert "1", wenn es sich um einen Eingang zu einem Gebäude handelt. Ist der Wert "0" gesetzt handelt es sich um eine GPS-Koordinate, die nicht mit einen Eingang übereinstimmt. Diese Zuordnung ist für die spätere Navigation von Bedeutung.



Aufbau mit einer Tabelle



Datenbankschnittstelle

Ermitteln der Gebäudekoordinaten

Die benötigten Koordinaten aller Hochschulgebäude wurden mit Hilfe eines Geoplaners im Internet („http://gpso.de/maps/“) ermittelt. Als aussagekräftige Koordinate pro Gebäude haben wir uns jeweils für den Mittelpunkt des Gebäudes entschieden.

Erstellen der Datenbanktabelle

Alle benötigten Gebäude müssen nun in einer Datenbanktabelle abgelegt werden. Alle Informationen werden vom Typ „String“ und die Koordinaten vom Typ „Number“ abgespeichert. Die Struktur wurde wie folgt angelegt:



Tabellenschema


Über eine von der Android SDK bereitgestelle Hilfsklasse, kann in die Tabelle geschrieben werden. Die dafür benötigte Methoden ist insert(). Durch diese Funktion wird ein neuer Datensatz in die Tabelle eingefügt.

Datenbankabfrage

Eine Abfrage in der Datenbank kann mit SQL-Statements umgesetzt werden. Zum Beispiel wird das Anzeigen der gesamten Tabelle wie folgt programmiert:


Datenbankabfrage


In diesem Beispiel wurde die Methode „query“ verwendet. Diese führt eine SQL- Anfrage aus, indem alle Bestandteile der Anfrage als Parameter übergeben werden. Eine weitere Möglichkeit wäre „update“. Hierbei werden nur die Attribute eines vorhandenen Datensatzes geändert. Dies werden wir vorallem dann verwenden, wenn sich z.B. die Öffnungszeiten einiger Gebäude ändern. Des Weiteren können mit „insert“ und „delete“ Datensätze angelegt bzw. gelöscht werden. Zum Beispiel wird dies benötigt, wenn neue Gebäude gebaut wurden und nachträglich in die App integriert werden sollen.

Daten abspeichern & übergeben


Alle zusammengehörige Daten, also je Gebäude die entsprechende Infos, wie Gebäudenamen, Öffnungszeiten und Koordinaten, mussten nun in eine ArrayListe abgespeichert werden. In diesem Datensatz sollen dann nur Infos & Koordinaten eines einzigen -nämlich dem gesuchtem- Gebäudes stehen.
Um die Daten in einer Liste abzuspeichern, wurde eine neue Liste namens „Building“ erstellt:

List<Building> myBuildings = new ArrayList<Building>();

Nachdem die Arrayliste erstellt wurde, muss sie nun mit den entsprechenden Infos gefüllt werden. Dies geschieht über einen Cursor. Über den Cursor werden die einzelnen Gebäude nacheinander mit den entsprechenden Informationen in eine ArrayListe gespeichert. Durch einen Cursor wird nur ein Zeiger auf den jeweiligen Ergebnisdatensatz übertragen und nicht die gesamte Tabelle, welche am Anfang erstellt wurde.
Damit der Cursor in die nächste Zeile der Tabelle springt und somit den nächsten Datensatz abspeichert, muss der Befehl „moveToNext()“ ausgeführt werden. Am Ende der Tabelle, wenn es keine weiteren Datensätze mehr gibt, muss der Cursor geschloßen werden. Dies geschieht durch den Befehl „isClosed()“.
Der Vorgang des Cursors als Iterator sieht nun wie folgt aus:

Gebäudeklasse übergeben


Visualisierung

Bestimmung des Standpunktes

Die Ortung des Benutzers funktioniert entweder über GPS oder über den Android Network Location Provider.
GPS (Global Positioning System) ist ein Satellitengestütztes System zur Ermittlung des Standortes eines Objektes oder einer Person. Die Technik ist bei Endgeräten mittlerweile weit verbreitet. Dabei ist zu beachten dass eine Ortung über GPS zwar meistens sehr genau ist, aber dafür nur im Freien funktioniert. Weitere Nachteile von GPS sind der schnelle Akkuverbrauch und unter Umständen eine verhältnismäßig langsame Rückgabe des Standortes.
Der Android Network Location Provider bezieht seine Daten über Funktürme oder über Wi-Fi-Signale. Die Vorteile hierbei sind dass eine Ortung sowohl drinnen als auch draußen funktioniert, weniger Akku verbraucht wird und eine meist schnellere Standortrückgabe möglich ist.
Um nun den Standort zu erhalten, kann man GPS und den Network Location Provider verwenden, oder nur eines von beiden.
Bei der Implementierung der Positionsermittlung wurden sowohl GPS als auch Netzwerkkoordinaten berücksichtigt. In verschiedenen Situationen liefern die einzelnen Provider jedoch unterschiedliche Genauigkeiten. So ist es in der Stadt gut möglich, dass GPS Daten durch die höheren Gebäude sehr ungenau sind, während Netzwerkkoordinaten durch die hohe Netzwerkdichte eine sehr genaue Positionsermittlung ermöglichen können. Die Genauigkeit des Providers fließt daher bei der Ermittlung des Standpunktes mit ein.

Getting-location.png

Die genaue Implementierung der Bestimmung von Geokoordinaten wird vom Android SDK vor dem Entwickler versteckt. Die Updates können durch ein einfaches Registrieren am Android Sensor Manager empfangen werden.


locationManager = (LocationManager)c.getSystemService(Context.LOCATION_SERVICE);
 
// read network and gps provider
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,0,0,this);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,0,0,this);


Klassen die sich am Location Provider registrieren müssen die onLocationChanged() Schnittstellenmethode implementieren.


@Override
public void onLocationChanged(Location location) {
// check if we already have a better location
 
	if(isBetterLocation(location,currentLocation) == true) { 
		this.currentLocation = location;
		// notify observers that the location has changed
		this.notifyObservers();
	}
}

LocationProvider.png

Bestimmung der Blickrichtung

Um die Blickrichtung des Benutzers zu ermitteln, wird der im Smartphone integrierte Kompass benötigt. Das Zugreifen auf Sensordaten des Kompasses verhält sich dabei analog zum Zugriff auf die Sensordaten des Location Providers: Der Anwender kann Komponenten am Sensor Manager registrieren und wird informiert wenn Änderungen vorliegen.


public OrientationProvider(Context ctx) {
      // Auf Sensoren zugreifen 
      sensorManager = (SensorManager) ctx.getSystemService(Context.SENSOR_SERVICE);
      // Auf Kompass-Sensor zugreifen 
      compass = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
      // Kompassupdates erhalten
      sensorManager.registerListener(this, compass,0);
}
 
public void onSensorChanged(SensorEvent event) {
      for(OrientationListener l : listeners) {
            l.onOrientationChanged(event.values);
      }
}


Transformation von Weltkoordinaten in Bildschirmkoordinaten

Das Format der vom Location Sensor erhaltenen Koordinaten liegt in Längen- und Breitengrad vor. Nach der Evaluation verschiedener Umwandlungsmethoden, wurde entschieden die Geodätischen Koordinaten in ENU Koordinaten umzuwandeln. Diese verhalten sich wie ein homogenes Koordinatensystem und lassen sich so einfach mit Standardbibliotheken (z.B. OpenGL) verarbeiten.
Das Vorgehen dabei ist zunächst eine Umwandlung der Geodätischen Koordinaten (Längen- und Breitengrad) in ECEF (Earth Centred Earth Fixed) Koordinaten, da Längen- und Breitengrad einen Punkt auf der Erdkugel beschreiben, man aber einen Punkt in einem homogenen Koordinatensystem benötigt.
Die Umwandlung geodätischer in geozentrische ECEF Koordinaten wird durch folgende Formel beschrieben:


ECEF.png




mit

ECEF N.png




Der Quellcode dazu lautet wie folgt:


public double N() {
        return WGS84_A/Math.sqrt(1-WGS84_e2*
		       Math.sin(this.getLatitude()*dtr)*
		       Math.sin(this.getLatitude()*dtr));
}
 
private double h = 1;
 
public float getX() {
	return (float) (
	       (float) (N()+h)*
		Math.cos(dtr*this.getLatitude())*
		Math.cos(dtr*this.getLongitude()));
}
 
 
public float getY() {
        return (float) (
	       (float) (N()+h)*
		Math.cos(this.getLatitude()*dtr)*
		Math.sin(this.getLongitude()*dtr));	
}
 
 
public float getZ() {
	return (float) (
	       (float) (N()*(1-WGS84_e2)+h)*
		Math.sin(this.getLatitude()*dtr));
}


Da sich bei den ECEF Koordinaten der Ursprung im Mittelpunkt befindet und man nicht so genau sagen kann wo Norden ist, müssen diese noch in ENU (East North UP) Koordinaten umgerechnet werden. Diese haben den Vorteil dass der Standpunkt des Benutzers sich im Koordinatenursprung befindet und Norden durch einen Vektor beschrieben wird.
Die Formel für die Berechnung von ENU Koordinaten wird durch eine Matrixmultiplikation beschrieben, wobei der aktuelle Standpunkt des Benutzers immer in die Berechnung miteinfließt.


ENU.png




Der Index r bezieht sich auf den aktuellen Standpunkt des Benutzers und der Index p beschreibt die Koordinaten des angezeigten Gebäudes. X, Y und Z beziehen sich auf ECEF Koordinaten und x, y, z sind die ENU Koordinaten.


Der Quellcode dazu lautet:


double[][] radarVals = {
        {-sinLong,		cosLat,		        0d},
	{-sinLat*cosLong,	-sinLat*sinLong, 	cosLat},
	{cosLat*cosLong,	cosLat*sinLong,	        sinLat}
};
 
double[][] localVals = {
	{this.getX()-ecefOrigin.getX()},
	{this.getY()-ecefOrigin.getY()},
	{this.getZ()-ecefOrigin.getZ()}
};
 
// Matrix mit Jama erstellen
Matrix radarMatrix = new Matrix(radarVals);
Matrix localVector = new Matrix(localVals);
// Matrixmultiplikation
Matrix result = radarMatrix.times(localVector);
this.E = (float) result.get(0, 0);
this.N = (float) result.get(1, 0);
this.U = (float) result.get(2, 0);


Darstellung des Sichtfeldes mit OpenGL

Für eine Realisierung mit OpenGL wurde sich aus mehreren Gründen entschieden. Zunächst macht es einem die dreidimensionale Darstellung der Koordinaten einfach eine perspektivische Darstellung der Gebäude vorzunehmen.
Weiterhin ist OpenGL mittlerweile auf jedem Android Smartphone vorhanden, so dass die Bibliothek bei der Anwendung nicht mitgeliefert werden muss.

Der Standort des Benutzers ist in ENU Koordinaten immer der Nullpunkt. Norden kann demzufolge durch den Vektor (0,1,0)T beschrieben werden. Auf diesen Vektor richtet man die OpenGL Kamera aus. Um die Abweichung von Norden zu realisieren rotiert man das Koordinatensystem. So können Koordinaten ohne Änderungen angegeben werden und OpenGL transformiert sie in passende Weltkoordinaten.


gl.glLoadIdentity();				
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glRotatef(rotation,0,1,0);
 
GLU.gluLookAt(gl,
	0,0,0, // Standpunkt ist immer 0,0,0 in ENU
	0,0,1, // in Richtung Norden schauen
	0,1,0
);


Das eigentliche Zeichen der Gebäude geschieht indem man die ENU Koordinaten der Gebäude in Abhängigkeit von dem aktuellen Standort des Benutzers berechnen lässt.


for(Building b: buildings) {
// Aktuellen Standort für ENU geben
	b.getLocation().setOrigin(this.getOrigin());
 
	x = b.getLocation().getE();
	y = b.getLocation().getU();
	z = b.getLocation().getN();
 
	gl.glPushMatrix();
	    gl.glTranslatef(x,y,z);
	    gl.glScalef(5, 5, 5);
	    // Würfel zeichnen
	    setColor(gl,i++);
	    new Cube().draw(gl);
	gl.glPopMatrix();	
}


Einbinden des Kamerabildes

Für die Einbindung des Kamerabildes gibt es von developer.android.com eine zur Verfügung gestellte Beispielklasse.
Diese enthält bereits den benötigten Funktionsumfang. Dabei war zu beachten dass die OpenGL View über das Kamerabild gelegt wird und der Hintergrund transparent gemacht wird.


Interface

Planung

Bei der Planung des Interfaces wurde besonderen Wert auf Benutzerfreundlichkeit gelegt. Dementsprechend sollte das Menü intuitiv bedienbar sein. Dies sollte durch mehrere Komponenten umgesetzt werden. Bei den Vorüberlegungen einigten wir uns darauf, dass selbsterklärende Symbole auf den Buttons abgebildet werden. Die Informationsanzeige, soll durch eine Lupe gekennzeichnet werden. Durch ein "i" wird die Gebäudesuche dargestellt und die Anzeige des Suchfeldes sollte durch ein eigenes Logo aufrufbar sein. Bei der Auswahl der Icons orientierten wir uns an bereits existierenden Programmen. Das Navigieren von einem Menüpunkt zum anderen, soll durch eine Tabnativation am oberenden Bildschirmrand umgesetzt werden. Beim Rotieren des Smartphones soll sich das Interface um 90 Grad in beide Richtungen drehen können. Weiter wurde festgelegt, dass das Suchen nach Gebäuden den Nutzer durch ein Autocompletefeld erleichtert werden soll.


Model

Model stellt die Objekte bzw. Datenmodelle dar. Bei der Android-Entwicklung kön-nen Objekte verwendet werden, die in Standard-Klassen vordefiniert sind. Diese Klassen enthalten die Objekteigenschaften bzw. Funktionen enthalten. Z.B.: Die Nutzung einer Klasse für die Tabs wird durch den folgenden Code-Teil ermöglicht. Dabei wird eine vordefinierte Klasse TabActivity in das Projekt importiert.

import android.app.TabActivity;

View

Bei View geht es um die Darstellung der benötigten Daten aus dem Model. Außer-dem ist View für die Entgegennahme der Daten und Benutzerinteraktionen zustän-dig. Z.B. Bei NaviOhm sind folgende Views erstellt.

main.xml
icon.png

Controller

Controller steuert die Verbindung zwischen dem Benutzer und System. Einerseits wird der Benutzer mit relevanter und berechtigter Datenansicht, also mit passenden Views, versorgt. Andererseits erfolgen die Datenänderungen vom Benutzer unter Berücksichtigung der Benutzerberechtigung. View und Controller hängen meist enger zusammen, da beide für eine bestimmte Art der Anwendung (Android) bestimmt sind.

Aufbau der NaviOhm in MVC-Model

Aufbau der NaviOhm in MVC-Model


Die wichtigsten Programmbausteine bei der Entwicklung der Benutzeroberfläche sind:

Activity.java: Ermöglicht das Start bzw. den Aufruf von App
Layout(.xml): Enthält Layouts für Activity.java
R.java: Automatische Generierung der Klassen, die auf Activity.java und Layout.xml basieren.
*.png: Enthält Logos und Buttons
AndroidManifest.xml: Enthält die Zugriffserlaubnisse von der gesamten App.

Ein wichtiger Vorteil der App-Entwicklung in Android ist: Es werden verschiedene Tutorials im Netz frei angeboten, die beim schnelleren Einstieg hilft.

Informationsanzeige

Damit der Nutzter dieser App auch die entsprechenden Infos bzw. Öffnungszeiten des gesuchten Gebäudes angezeigt bekommt, haben wir uns um eine Eingabemöglichkeit bemüht. Der Nutzer hat ein Eingebafeld, in welches er verschieden Gebäude- Suchbegriffe eingeben kann. Klickt er auf den Button „Info“, werden ihm die entsprechenden Informationen angezeigt.
Schritt 1: Abfrage, was in das Feld eingeben wurde. Dies geschieht über eine if-else-Abfrage, wobei der Name, welcher der Benutzer eingibt über „getString()“ geholt wird. Anschließend wird dieser Name verglichen mit vorgespeicherten Gebäude.
Abfrage, welche Infos zu welchem Gebäude angezeigt werden sollen

Über „setText(R.string.lib)“ wird dann ein Text gesetzt, welcher in der Datei string.xml abgespeichert wurde.
Das Resultat sieht im Emulator folgendermaßen aus:
Die Informationsanzeige im Emulator

Weblinks


Interner Bereich: