Android SDK: Arbeiten mit Google Maps - Anzeigen von Sehenswürdigkeiten
German (Deutsch) translation by Tatsiana Bochkareva (you can also view the original English article)
Mit Google Maps in Ihren Android-Apps können Sie Benutzern Lokalisierungsfunktionen wie geografische Informationen bereitstellen. Während dieser Serie haben wir eine Android-App erstellt, in der die Google Maps Android API v2 mit der Google Places API kombiniert wird. Bisher haben wir eine Karte angezeigt, auf der der Nutzer seinen aktuellen Standort sehen kann, und wir haben eine Google Places-Abfrage gesendet, um Daten zu nahe gelegenen Sehenswürdigkeiten zurückzugeben. Dies erforderte das Einrichten des API-Zugriffs für beide Dienste. Im letzten Teil der Serie werden wir die JSON-Daten von Google Places analysieren und sie verwenden, um dem Benutzer nahe gelegene Sehenswürdigkeiten anzuzeigen. Wir werden die App auch dazu bringen, die Markierungen zu aktualisieren, wenn sich der Benutzerstandort ändert.
Dies ist der letzte von vier Teilen einer Lernserie zur Verwendung von Google Maps und Google Places in Android-Apps:
- Arbeiten mit Google Maps - App-Setup
- Arbeiten mit Google Maps - Karten-Setup
- Arbeiten mit Google Maps - Integration von den Orten
- Arbeiten mit Google Maps - Anzeigen von Orten in der Nähe



1. Verarbeiten Sie die Ortsdaten
Schritt 1
Für dieses Lernprogramm müssen Sie Ihrer Aktivitätsklasse die folgenden Importanweisungen hinzufügen:
1 |
|
2 |
import org.json.JSONArray; |
3 |
import org.json.JSONException; |
4 |
import org.json.JSONObject; |
5 |
import android.util.Log; |
Im letzten Lernprogramm haben wir eine innere AsyncTask-Klasse erstellt, um das Abrufen der Daten von Google Places im Hintergrund zu handhaben. Wir haben die Methode doInBackground hinzugefügt, um die Daten anzufordern und abzurufen. Jetzt können wir die onPostExecute-Methode implementieren, um die von doInBackground zurückgegebene JSON-Zeichenfolge in Ihrer AsyncTask-Klasse nach der doInBackground-Methode zu analysieren:
1 |
|
2 |
protected void onPostExecute(String result) { |
3 |
//parse place data returned from Google Places
|
4 |
}
|
Schritt 2
Zurück im zweiten Teil dieser Serie haben wir ein Marker-Objekt erstellt, um den zuletzt aufgezeichneten Ort des Benutzers auf der Karte anzuzeigen. Wir werden auch Marker verwenden, um die nahe gelegenen Sehenswürdigkeiten anzuzeigen. Wir werden ein Array verwenden, um diese Marker zu speichern. Fügen Sie oben in Ihrer Aktivitätsklassendeklaration die folgende Instanzvariable hinzu:
1 |
|
2 |
private Marker[] placeMarkers; |
Standardmäßig gibt die Google Places-API maximal 20 Stellen zurück. Definieren Sie dies also auch als Konstante:
1 |
|
2 |
private final int MAX_PLACES = 20; |
Wenn wir die Marker für jede Stelle erstellen, verwenden wir MarkerOptions-Objekte, um die Markerdetails zu konfigurieren. Erstellen Sie eine weitere Array-Instanzvariable für diese:
1 |
|
2 |
private MarkerOptions[] places; |
Lassen Sie uns nun das Array instanziieren. Erstellen Sie in Ihrer Activity onCreate-Methode nach der Zeile, in der wir den Kartentyp festgelegt haben, ein Array mit der maximal erforderlichen Größe:
1 |
|
2 |
placeMarkers = new Marker[MAX_PLACES]; |
Wenden wir uns nun der von uns erstellten onPostExecute-Methode zu. Durchlaufen Sie zunächst das Marker-Array und entfernen Sie alle vorhandenen Marker. Diese Methode wird mehrmals ausgeführt, wenn der Benutzer den Speicherort ändert:
1 |
|
2 |
if(placeMarkers!=null){ |
3 |
for(int pm=0; pm<placeMarkers.length; pm++){ |
4 |
if(placeMarkers[pm]!=null) |
5 |
placeMarkers[pm].remove(); |
6 |
}
|
7 |
}
|
Wenn der App-Code zum ersten Mal ausgeführt wird, werden neue Marker erstellt. Wenn der Benutzer jedoch den Speicherort ändert, werden diese Methoden erneut ausgeführt, um die angezeigten Orte zu aktualisieren. Aus diesem Grund müssen wir zunächst alle vorhandenen Markierungen aus der Karte entfernen, um die Erstellung eines neuen Stapels vorzubereiten.
Schritt 3
Wir werden Java JSON-Ressourcen verwenden, um die abgerufenen Ortsdaten zu verarbeiten. Da diese Klassen bestimmte Ausnahmen auslösen, müssen wir in diesem Abschnitt eine Ebene der Fehlerbehandlung einbauen. Fügen Sie zunächst try and catch-Blöcke hinzu:
1 |
|
2 |
try { |
3 |
//parse JSON
|
4 |
}
|
5 |
catch (Exception e) { |
6 |
e.printStackTrace(); |
7 |
}
|
Erstellen Sie im try-Block ein neues JSONObject und übergeben Sie es an die von doInBackground zurückgegebene JSON-Ergebniszeichenfolge:
1 |
|
2 |
JSONObject resultObject = new JSONObject(result); |
Wenn Sie sich die Seite Place Search in der Dokumentation zur Google Places-API ansehen, sehen Sie ein Beispiel dafür, was die Abfrage tatsächlich in JSON zurückgibt. Sie werden sehen, dass die Orte in einem Array mit dem Namen "results" enthalten sind. Lassen Sie uns zuerst dieses Array aus dem zurückgegebenen JSON-Objekt abrufen:
1 |
|
2 |
JSONArray placesArray = resultObject.getJSONArray("results"); |
Sie sollten sich auf das Beispiel-JSON-Ergebnis beziehen, wenn wir jeden Abschnitt dieses Prozesses abschließen. Lassen Sie die Seite in einem Browser geöffnet, während Sie den Rest des Lernprogramms abschließen. Als nächstes instanziieren wir das von uns erstellte MarkerOptions-Array mit der Länge des zurückgegebenen "results" -Arrays:
1 |
|
2 |
places = new MarkerOptions[placesArray.length()]; |
Dies sollte uns ein MarkerOptions-Objekt für jeden zurückgegebenen Ort geben. Fügen Sie eine Schleife hinzu, um das Array von Orten zu durchlaufen:
1 |
|
2 |
//loop through places
|
3 |
for (int p=0; p<placesArray.length(); p++) { |
4 |
//parse each place
|
5 |
}
|
Schritt 4
Jetzt können wir die Daten für jeden zurückgegebenen Ort analysieren. Innerhalb der for-Schleife erstellen wir Details, die an das MarkerOptions-Objekt für den aktuellen Ort übergeben werden. Dies umfasst Breiten- und Längengrade, Ortsnamen, Typ und Umgebung, die einen Auszug aus den Adressdaten für den Ort darstellen. Wir werden alle diese Daten aus dem Google Places JSON abrufen und sie über das MarkerOptions-Objekt an den Marker für den Ort übergeben. Wenn einer der Werte im zurückgegebenen JSON-Feed fehlt, wird im Falle von Ausnahmen einfach kein Marker für diesen Ort angezeigt. Um dies zu verfolgen, fügen Sie ein boolesches Flag hinzu:
1 |
|
2 |
boolean missingValue=false; |
Fügen Sie nun lokale Variablen für jeden Aspekt des Ortes hinzu, den wir abrufen und an den Marker übergeben möchten:
1 |
|
2 |
LatLng placeLL=null; |
3 |
String placeName=""; |
4 |
String vicinity=""; |
5 |
int currIcon = otherIcon; |
Wir erstellen und initialisieren ein LatLng-Objekt für den Breiten- und Längengrad, Zeichenfolgen für den Ortsnamen und die Umgebung und setzen das Symbol zunächst so, dass es das von uns erstellte Standardsymbol verwendet. Jetzt brauchen wir einen weiteren try-Block, damit wir feststellen können, ob tatsächlich Werte fehlen:
1 |
|
2 |
try{ |
3 |
//attempt to retrieve place data values
|
4 |
}
|
5 |
catch(JSONException jse){ |
6 |
missingValue=true; |
7 |
jse.printStackTrace(); |
8 |
}
|
Wir setzen das Flag für fehlende Werte auf true, um es später zu überprüfen. Innerhalb dieses try-Blocks können wir nun versuchen, die erforderlichen Werte aus den Ortsdaten abzurufen. Initialisieren Sie zunächst das boolesche Flag auf false, vorausgesetzt, es fehlen keine Werte, bis wir etwas anderes feststellen:
1 |
|
2 |
missingValue=false; |
Holen Sie sich nun das aktuelle Objekt aus dem Place-Array:
1 |
|
2 |
JSONObject placeObject = placesArray.getJSONObject(p); |
Wenn Sie auf die Beispieldaten für die Ortssuche zurückblicken, werden Sie feststellen, dass jeder Ortsabschnitt einen Abschnitt "Geometrie" enthält, der wiederum einen Abschnitt "Ort" enthält. Hier befinden sich die Breiten- und Längengrade für den Ort. Rufen Sie sie jetzt ab:
1 |
|
2 |
JSONObject loc = placeObject.getJSONObject("geometry").getJSONObject("location"); |
Versuchen Sie, die Breiten- und Längengrade daraus zu lesen, und beziehen Sie sich dabei auf die Werte "lat" und "lng" im JSON:
1 |
|
2 |
placeLL = new LatLng( |
3 |
Double.valueOf(loc.getString("lat")), |
4 |
Double.valueOf(loc.getString("lng"))); |
Als nächstes erhalten Sie das Array "types", das Sie im JSON-Beispiel sehen können:
1 |
|
2 |
JSONArray types = placeObject.getJSONArray("types"); |
Tipp: Wir wissen, dass dies ein Array ist, wie es im JSON-Feed angezeigt wird, der von den Zeichen "[" und "]" umgeben ist. Wir behandeln alle anderen verschachtelten Abschnitte als JSON-Objekte und nicht als Arrays.
Durchlaufen Sie das Typarray:
1 |
|
2 |
for(int t=0; t<types.length(); t++){ |
3 |
//what type is it
|
4 |
}
|
Holen Sie sich die Typzeichenfolge:
1 |
|
2 |
String thisType=types.get(t).toString(); |
Wir werden bestimmte Symbole für bestimmte Ortstypen (Lebensmittel, Bar und Geschäft) verwenden. Fügen Sie daher eine Bedingung hinzu:
1 |
|
2 |
if(thisType.contains("food")){ |
3 |
currIcon = foodIcon; |
4 |
break; |
5 |
}
|
6 |
else if(thisType.contains("bar")){ |
7 |
currIcon = drinkIcon; |
8 |
break; |
9 |
}
|
10 |
else if(thisType.contains("store")){ |
11 |
currIcon = shopIcon; |
12 |
break; |
13 |
}
|
Die Typliste für einen Ort kann tatsächlich mehr als einen dieser Orte enthalten, aber der Einfachheit halber verwenden wir einfach den ersten, auf den wir stoßen. Wenn die Liste der Typen für einen Ort keine dieser Typen enthält, wird das Standardsymbol angezeigt. Denken Sie daran, dass wir diese Typen beim letzten Mal in der URL-Abfragezeichenfolge "Ortssuche" angegeben haben:
1 |
|
2 |
food|bar|store|museum|art_gallery |
Das bedeutet, dass die einzigen Ortstypen, die das Standardsymbol verwenden, Museen oder Kunstgalerien sind, da dies die einzigen anderen Typen sind, nach denen wir gefragt haben.
Rufen Sie nach der Schleife durch das Typarray die Umgebungsdaten ab:
1 |
|
2 |
vicinity = placeObject.getString("vicinity"); |
Rufen Sie abschließend den Ortsnamen ab:
1 |
|
2 |
placeName = placeObject.getString("name"); |
Schritt 5
Überprüfen Sie nach dem catch-Block, in dem Sie das Flag missingValue auf true gesetzt haben, diesen Wert und setzen Sie das Objekt "markerOptions" auf "null", damit wir nicht versuchen, Markerobjekte mit fehlenden Daten zu instanziieren:
1 |
|
2 |
if(missingValue) places[p]=null; |
Andernfalls können wir an dieser Position im Array ein MarkerOptions-Objekt erstellen:
1 |
|
2 |
else
|
3 |
places[p]=new MarkerOptions() |
4 |
.position(placeLL) |
5 |
.title(placeName) |
6 |
.icon(BitmapDescriptorFactory.fromResource(currIcon)) |
7 |
.snippet(vicinity); |
Schritt 6
Führen Sie nun am Ende von onPostExecute nach dem äußeren try, Blöcke catch, eine Schleife durch das Array von MarkerOptions, instanziieren Sie jeweils einen Marker, fügen Sie ihn der Karte hinzu und speichern Sie einen Verweis darauf in dem von uns erstellten Array:
1 |
|
2 |
if(places!=null && placeMarkers!=null){ |
3 |
for(int p=0; p<places.length && p<placeMarkers.length; p++){ |
4 |
//will be null if a value was missing
|
5 |
if(places[p]!=null) |
6 |
placeMarkers[p]=theMap.addMarker(places[p]); |
7 |
}
|
8 |
}
|
Durch das Speichern eines Verweises auf den Marker können wir ihn leicht entfernen, wenn die Orte aktualisiert werden, wie wir es zu Beginn der onPostExecute-Methode implementiert haben. Beachten Sie, dass wir jedes Mal, wenn diese Schleife wiederholt wird, zwei bedingte Tests einschließen, falls die Ortssuche nicht die vollen 20 Stellen zurückgibt. Wir prüfen auch, ob die MarkerOptions null sind, was darauf hinweist, dass ein Wert fehlte.
Schritt 7
Schließlich können wir unsere AsyncTask-Klasse instanziieren und ausführen. Starten Sie in Ihrer updatePlaces-Methode nach dem vorhandenen Code, in dem wir die Suchabfragezeichenfolge erstellt haben, diese Hintergrundverarbeitung, um die Ortsdaten mit dieser Zeichenfolge abzurufen:
1 |
|
2 |
new GetPlaces().execute(placesSearchStr); |
Sie können Ihre App jetzt ausführen, um sie in Aktion zu sehen. Es sollte Ihren zuletzt aufgezeichneten Ort zusammen mit nahe gelegenen Sehenswürdigkeiten anzeigen. Die Farben, die Sie auf den Markern sehen, hängen von den zurückgegebenen Stellen ab. Hier ist die App, die einen Benutzerstandort im Stadtzentrum von Glasgow, Großbritannien, anzeigt:



Es ist vielleicht nicht überraschend, dass viele der in Glasgow aufgeführten Orte Bars sind.
Wenn der Benutzer auf einen Marker tippt, werden der Ortsname und die Snippet-Informationen angezeigt:



2. Aktualisieren Sie mit Änderungen des Benutzerstandorts
Schritt 1
Die aktuelle App wird beim Start einmal ausgeführt. Lassen Sie uns die Funktionen einbauen, die für die Aktualisierung erforderlich sind, um Änderungen am Benutzerstandort widerzuspiegeln, und gleichzeitig die Markierungen in der Nähe aktualisieren.
Ändern Sie die Eröffnungszeile der Aktivitätsklassendeklaration, damit sie die LocationListener-Schnittstelle implementiert, damit wir Änderungen am Benutzerstandort erkennen können:
1 |
|
2 |
public class MyMapActivity extends Activity implements LocationListener { |
Ein Location Listener kann auf verschiedene Änderungen reagieren, von denen jede eine dedizierte Methode verwendet. Implementieren Sie in der Aktivitätsklasse die folgenden Methoden:
1 |
|
2 |
@Override
|
3 |
public void onLocationChanged(Location location) { |
4 |
Log.v("MyMapActivity", "location changed"); |
5 |
updatePlaces(); |
6 |
}
|
7 |
@Override
|
8 |
public void onProviderDisabled(String provider){ |
9 |
Log.v("MyMapActivity", "provider disabled"); |
10 |
}
|
11 |
@Override
|
12 |
public void onProviderEnabled(String provider) { |
13 |
Log.v("MyMapActivity", "provider enabled"); |
14 |
}
|
15 |
@Override
|
16 |
public void onStatusChanged(String provider, int status, Bundle extras) { |
17 |
Log.v("MyMapActivity", "status changed"); |
18 |
}
|
Das einzige, an dem wir wirklich interessiert sind, ist das erste, das anzeigt, dass sich der Standort geändert hat. In diesem Fall rufen wir die updatePlaces-Methode erneut auf. Ansonsten schreiben wir einfach eine Log-Nachricht aus.
Fügen Sie am Ende der updatePlaces-Methode eine Anforderung für die App hinzu, um Standortaktualisierungen zu erhalten:
1 |
|
2 |
locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this); |
Wir verwenden den Standortmanager, den wir zuvor in der Serie erstellt haben, und fordern Aktualisierungen über den Netzwerkanbieter mit einer Verzögerung von 30 Sekunden (angegeben in Millisekunden) mit einer minimalen Standortänderung von 100 Metern und der Aktivitätsklasse selbst an, um die Aktualisierungen zu erhalten. Sie können natürlich einige der Parameter an Ihre eigenen Bedürfnisse anpassen.
Tipp: Obwohl die requestLocationUpdates-Methode eine Mindestzeit und einen Mindestabstand für Aktualisierungen angibt, kann sie in der Realität dazu führen, dass die onLocationChanged-Methode viel häufiger ausgeführt wird, was schwerwiegende Auswirkungen auf die Leistung hat. In allen Apps, die Sie für Benutzer freigeben möchten, sollten Sie daher die Häufigkeit begrenzen, mit der Ihr Code auf diese Standortaktualisierungen reagiert. Die alternative Methode requestSingleUpdate, die zeitgesteuert verwendet wird, ist möglicherweise eine Überlegung wert.
Schritt 2
Zu guter Letzt müssen wir uns darum kümmern, was passiert, wenn die App angehalten und fortgesetzt wird. Überschreiben Sie die beiden Methoden wie folgt:
1 |
|
2 |
@Override
|
3 |
protected void onResume() { |
4 |
super.onResume(); |
5 |
if(theMap!=null){ |
6 |
locMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 30000, 100, this); |
7 |
}
|
8 |
}
|
9 |
|
10 |
@Override
|
11 |
protected void onPause() { |
12 |
super.onPause(); |
13 |
if(theMap!=null){ |
14 |
locMan.removeUpdates(this); |
15 |
}
|
16 |
}
|
Wir suchen nach dem GoogleMap-Objekt, bevor wir eine Verarbeitung wie in onCreate versuchen. Wenn die App angehalten wird, können keine Standortaktualisierungen angefordert werden. Wenn die App fortgesetzt wird, fordern wir die Updates erneut an.
Tipp: Wir haben den LocationManager.NETWORK_PROVIDER in dieser Serie einige Male verwendet. Wenn Sie die Lokalisierungsfunktionen in Ihren Apps untersuchen, sehen Sie sich die alternative Methode getBestProvider an, mit der Sie Kriterien für Android festlegen können, um einen Anbieter basierend auf Faktoren wie Genauigkeit und Geschwindigkeit auszuwählen.
Bevor wir fertig sind
Damit ist die App so ziemlich fertig! Es gibt jedoch viele Aspekte der Google Maps Android API v2, die wir noch nicht einmal angesprochen haben. Sobald Sie Ihre App ausgeführt haben, können Sie mit Funktionen wie Drehen und Kippen experimentieren. Der aktualisierte Kartendienst zeigt an bestimmten Stellen Innen- und 3D-Karten an. Das folgende Bild zeigt die 3D-Einrichtung mit der App, wenn sich der Benutzerstandort in Venedig, Italien, befand:



Hier ist der Kartentyp auf normal eingestellt - hier ist eine andere Ansicht von Venedig mit dem eingestellten hybriden Kartentyp:



Abschluss
In dieser Tutorial-Reihe haben wir den Prozess der Integration von Google Maps- und Google Places-APIs in eine einzige Android-App durchgearbeitet. Wir haben den Zugriff auf API-Schlüssel übernommen und die Entwicklungsumgebung, den Arbeitsbereich und die Anwendung für die Nutzung von Google Play Services eingerichtet. Wir haben Standortdaten verwendet, den Standort des Benutzers zusammen mit nahe gelegenen Sehenswürdigkeiten angezeigt und die Daten mit benutzerdefinierten UI-Elementen angezeigt. Obwohl das, was wir in dieser Serie behandelt haben, ziemlich umfangreich ist, ist es wirklich nur der Anfang, wenn es darum geht, Lokalisierungsfunktionen in Android-Apps zu integrieren. Mit der Veröffentlichung von Version 2 der Maps-API werden Android-Apps solche Funktionen auf die nächste Stufe heben.



