1. Einführung
Zusammenfassung
Stellen Sie sich vor, Sie haben viele Orte, die Sie auf einer Karte darstellen möchten. Nutzer sollen sehen können, wo sich diese Orte befinden, und den Ort auswählen können, den sie besuchen möchten. Häufige Beispiele:
- eine Filialsuche auf der Website eines Einzelhändlers
- eine Karte mit Wahllokalen für eine anstehende Wahl
- ein Verzeichnis von Spezialstandorten wie Behältern für das Recycling von Batterien
Aufgaben
In diesem Codelab erstellen Sie eine Suchfunktion, die auf einem Live-Datenfeed mit speziellen Standorten basiert und dem Nutzer hilft, den Standort zu finden, der seinem Ausgangspunkt am nächsten ist. Dieser Locator kann viel mehr Orte verarbeiten als der einfache Store Locator, der auf maximal 25 Geschäftsstandorte beschränkt ist.
Lerninhalte
In diesem Codelab wird ein offenes Dataset verwendet, um vorab ausgefüllte Metadaten zu einer großen Anzahl von Geschäftsstandorten zu simulieren. So können Sie sich auf die wichtigsten technischen Konzepte konzentrieren.
- Maps JavaScript API: Eine große Anzahl von Standorten auf einer benutzerdefinierten Webkarte anzeigen
- GeoJSON: Ein Format zum Speichern von Metadaten zu Standorten
- Place Autocomplete: Nutzern helfen, Startorte schneller und genauer anzugeben
- Go: Die Programmiersprache, die zum Entwickeln des Anwendungs-Back-Ends verwendet wird. Das Backend interagiert mit der Datenbank und sendet Abfrageergebnisse in formatiertem JSON zurück an das Frontend.
- App Engine: zum Hosten der Webanwendung
Vorbereitung
- Grundkenntnisse in HTML und JavaScript
- Ein Google-Konto
2. Einrichten
Aktivieren Sie in Schritt 3 des folgenden Abschnitts die Maps JavaScript API, die Places API und die Distance Matrix API für dieses Codelab.
Einstieg in die Google Maps Platform
Wenn Sie die Google Maps Platform noch nicht verwendet haben, folgen Sie der Anleitung für die ersten Schritte mit der Google Maps Platform oder sehen Sie sich die Playlist „Erste Schritte mit der Google Maps Platform“ an, um die folgenden Schritte auszuführen:
- Erstellen Sie ein Rechnungskonto.
- Projekt erstellen
- Aktivieren Sie die APIs und SDKs der Google Maps Platform, die im vorherigen Abschnitt aufgeführt sind.
- Generieren Sie einen API-Schlüssel.
Cloud Shell aktivieren
In diesem Codelab verwenden Sie Cloud Shell, eine Befehlszeilenumgebung, die in Google Cloud ausgeführt wird und Zugriff auf Produkte und Ressourcen bietet, die in Google Cloud ausgeführt werden. So können Sie Ihr Projekt vollständig über Ihren Webbrowser hosten und ausführen.
Klicken Sie zum Aktivieren von Cloud Shell in der Cloud Console auf Cloud Shell aktivieren . Es dauert nur wenige Augenblicke, bis die Umgebung bereitgestellt und eine Verbindung hergestellt wird.
Dadurch wird im unteren Bereich Ihres Browsers eine neue Shell geöffnet. Möglicherweise wird vorher ein Einführungs-Interstitial angezeigt.
Projekt bestätigen
Sobald Sie mit Cloud Shell verbunden sind, sollten Sie sehen, dass Sie bereits authentifiziert sind und das Projekt bereits auf die Projekt-ID eingestellt ist, die Sie bei der Einrichtung ausgewählt haben.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
Wenn das Projekt aus irgendeinem Grund nicht festgelegt ist, führen Sie den folgenden Befehl aus:
gcloud config set project <YOUR_PROJECT_ID>
App Engine Flex API aktivieren
Die App Engine Flex API muss manuell über die Cloud Console aktiviert werden. Dadurch wird nicht nur die API aktiviert, sondern auch das Dienstkonto für die flexible App Engine-Umgebung erstellt. Dieses authentifizierte Konto interagiert im Namen des Nutzers mit Google-Diensten wie SQL-Datenbanken.
3. Hello World
Backend: Hello World in Go
In Ihrer Cloud Shell-Instanz erstellen Sie zuerst eine Go App Engine Flex-Anwendung, die als Grundlage für den Rest des Codelabs dient.
Klicken Sie in der Symbolleiste von Cloud Shell auf die Schaltfläche Editor öffnen, um einen Code-Editor auf einem neuen Tab zu öffnen. Mit diesem webbasierten Code-Editor können Sie Dateien in der Cloud Shell-Instanz ganz einfach bearbeiten.
Klicken Sie als Nächstes auf das Symbol In neuem Fenster öffnen, um den Editor und das Terminal in einen neuen Tab zu verschieben.
Erstellen Sie im Terminal unten auf dem neuen Tab ein neues Verzeichnis austin-recycling
.
mkdir -p austin-recycling && cd $_
Als Nächstes erstellen Sie eine kleine Go-App Engine-App, um zu prüfen, ob alles funktioniert. Hallo Welt!
Das Verzeichnis austin-recycling
sollte auch links in der Ordnerliste des Editors angezeigt werden. Erstellen Sie im Verzeichnis austin-recycling
eine Datei mit dem Namen app.yaml
. Fügen Sie der Datei app.yaml
den folgenden Inhalt hinzu:
app.yaml
runtime: go
env: flex
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
Mit dieser Konfigurationsdatei wird Ihre App Engine-Anwendung für die Verwendung der flexiblen Go-Laufzeit konfiguriert. Hintergrundinformationen zur Bedeutung der Konfigurationselemente in dieser Datei finden Sie in der Dokumentation zur Google App Engine-Standardumgebung für Go.
Erstellen Sie als Nächstes eine main.go
-Datei neben der app.yaml
-Datei:
main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
http.HandleFunc("/", handle)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, "Hello world!")
}
Es lohnt sich, an dieser Stelle kurz innezuhalten, um zumindest auf einer hohen Ebene zu verstehen, was dieser Code bewirkt. Sie haben ein Paket main
definiert, das einen HTTP-Server startet, der Port 8080 überwacht, und eine Handler-Funktion für HTTP-Anfragen registriert, die dem Pfad "/"
entsprechen.
Die Handler-Funktion, die praktischerweise handler
heißt, gibt den Textstring "Hello, world!"
aus. Dieser Text wird an Ihren Browser zurückgegeben, wo Sie ihn lesen können. In den nächsten Schritten erstellen Sie Handler, die mit GeoJSON-Daten anstelle von einfachen hartcodierten Strings antworten.
Nachdem Sie diese Schritte ausgeführt haben, sollte der Editor so aussehen:
Ausprobieren
Um diese Anwendung zu testen, können Sie den App Engine-Entwicklungsserver in der Cloud Shell-Instanz ausführen. Kehren Sie zur Cloud Shell-Befehlszeile zurück und geben Sie Folgendes ein:
go run *.go
Sie sehen einige Zeilen mit Logausgabe, die zeigen, dass Sie den Entwicklungsserver tatsächlich auf der Cloud Shell-Instanz ausführen und die Hello World-Webanwendung auf dem Localhost-Port 8080 auf Anfragen wartet. Sie können einen Webbrowser-Tab für diese App öffnen, indem Sie in der Cloud Shell-Symbolleiste auf die Schaltfläche Webvorschau klicken und das Menüelement Vorschau auf Port 8080 auswählen.
Wenn Sie auf dieses Menüelement klicken, wird in Ihrem Webbrowser ein neuer Tab geöffnet, auf dem die Meldung „Hello, world!“ vom App Engine-Entwicklungsserver angezeigt wird.
Im nächsten Schritt fügen Sie dieser App die Recyclingdaten der Stadt Austin hinzu und beginnen mit der Visualisierung.
4. Aktuelle Daten abrufen
GeoJSON, die Lingua Franca der GIS-Welt
Im vorherigen Schritt wurde erwähnt, dass Sie Handler in Ihrem Go-Code erstellen, die GeoJSON-Daten im Webbrowser rendern. Was ist GeoJSON?
In der Welt der geografischen Informationssysteme (GIS) müssen wir in der Lage sein, Wissen über geografische Einheiten zwischen Computersystemen zu kommunizieren. Karten sind für Menschen gut lesbar, Computer bevorzugen jedoch Daten in leichter verdaulichen Formaten.
GeoJSON ist ein Format zum Codieren geografischer Datenstrukturen, z. B. der Koordinaten von Recycling-Sammelstellen in Austin, Texas. GeoJSON wurde in einem Internet Engineering Task Force-Standard namens RFC7946 standardisiert. GeoJSON wird in Bezug auf JSON (JavaScript Object Notation) definiert, das selbst von derselben Organisation, die JavaScript standardisiert hat, Ecma International, in ECMA-404 standardisiert wurde.
Wichtig ist, dass GeoJSON ein weit verbreitetes Wire-Format für die Übermittlung geografischer Informationen ist. In diesem Codelab wird GeoJSON auf folgende Weise verwendet:
- Verwenden Sie Go-Pakete, um die Austin-Daten in eine interne GIS-spezifische Datenstruktur zu parsen, mit der Sie angeforderte Daten filtern.
- Die angeforderten Daten für die Übertragung zwischen dem Webserver und dem Webbrowser serialisieren.
- Verwenden Sie eine JavaScript-Bibliothek, um die Antwort in Markierungen auf einer Karte umzuwandeln.
Dadurch sparen Sie sich viel Tipparbeit, da Sie keine Parser und Generatoren schreiben müssen, um den Datastream in In-Memory-Darstellungen zu konvertieren.
Daten abrufen
Das Open Data Portal der Stadt Austin, Texas stellt raumbezogene Informationen zu öffentlichen Ressourcen zur öffentlichen Nutzung zur Verfügung. In diesem Codelab visualisieren Sie den Datensatz Recycling-Abgabestellen.
Die Daten werden mit Markierungen auf der Karte visualisiert, die mit der Datenebene der Maps JavaScript API gerendert werden.
Laden Sie zuerst die GeoJSON-Daten von der Website der Stadt Austin in Ihre App herunter.
- Schließen Sie den Server in der Befehlszeile Ihrer Cloud Shell-Instanz, indem Sie [STRG] + [C] eingeben.
- Erstellen Sie im Verzeichnis
austin-recycling
ein Verzeichnis mit dem Namendata
und wechseln Sie zu diesem Verzeichnis:
mkdir -p data && cd data
Verwenden Sie nun „curl“, um die Recyclingstandorte abzurufen:
curl "https://data.austintexas.gov/resource/qzi7-nx8g.geojson" -o recycling-locations.geojson
Wechseln Sie abschließend wieder in das übergeordnete Verzeichnis.
cd ..
5. Standorte auf der Karte darstellen
Aktualisieren Sie zuerst die Datei app.yaml
, um die robustere Anwendung widerzuspiegeln, die Sie erstellen möchten.
app.yaml
runtime: go
env: flex
handlers:
- url: /
static_files: static/index.html
upload: static/index.html
- url: /(.*\.(js|html|css))$
static_files: static/\1
upload: static/.*\.(js|html|css)$
- url: /.*
script: auto
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
Diese app.yaml
-Konfiguration leitet Anfragen für /
, /*.js
, /*.css
und /*.html
an eine Reihe statischer Dateien weiter. Das bedeutet, dass die statische HTML-Komponente Ihrer App direkt von der App Engine-Infrastruktur für die Bereitstellung von Dateien und nicht von Ihrer Go-App bereitgestellt wird. Dadurch wird die Serverlast reduziert und die Bereitstellungsgeschwindigkeit erhöht.
Jetzt ist es an der Zeit, das Backend Ihrer Anwendung in Go zu erstellen.
Backend erstellen
Ihnen ist vielleicht aufgefallen, dass Ihre app.yaml
-Datei die GeoJSON-Datei nicht verfügbar macht. Das liegt daran, dass das GeoJSON von unserem Go-Backend verarbeitet und gesendet wird. So können wir in späteren Schritten einige ausgefallene Funktionen einbauen. Ändern Sie die Datei main.go
so:
main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
)
var GeoJSON = make(map[string][]byte)
// cacheGeoJSON loads files under data into `GeoJSON`.
func cacheGeoJSON() {
filenames, err := filepath.Glob("data/*")
if err != nil {
log.Fatal(err)
}
for _, f := range filenames {
name := filepath.Base(f)
dat, err := os.ReadFile(f)
if err != nil {
log.Fatal(err)
}
GeoJSON[name] = dat
}
}
func main() {
// Cache the JSON so it doesn't have to be reloaded every time a request is made.
cacheGeoJSON()
// Request for data should be handled by Go. Everything else should be directed
// to the folder of static files.
http.HandleFunc("/data/dropoffs", dropoffsHandler)
http.Handle("/", http.FileServer(http.Dir("./static/")))
// Open up a port for the webserver.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
// Writes Hello, World! to the user's web browser via `w`
fmt.Fprint(w, "Hello, world!")
}
func dropoffsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/json")
w.Write(GeoJSON["recycling-locations.geojson"])
}
Das Go-Backend bietet uns bereits eine wertvolle Funktion: Die App Engine-Instanz speichert alle diese Standorte im Cache, sobald sie gestartet wird. Das spart Zeit, da das Backend die Datei nicht bei jeder Aktualisierung für jeden Nutzer von der Festplatte lesen muss.
Frontend erstellen
Als Erstes müssen wir einen Ordner erstellen, in dem alle statischen Assets gespeichert werden. Erstellen Sie im übergeordneten Ordner Ihres Projekts einen Ordner mit dem Namen static
.
mkdir -p static && cd static
Wir erstellen in diesem Ordner drei Dateien.
index.html
enthält den gesamten HTML-Code für Ihre einseitige Store Locator-App.style.css
enthält wie erwartet das Styling.app.js
ist für das Abrufen des GeoJSON, das Senden von Anfragen an die Maps API und das Platzieren von Markierungen auf Ihrer benutzerdefinierten Karte verantwortlich.
Erstellen Sie diese drei Dateien und legen Sie sie in static/
ab .
style.css
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
body {
display: flex;
}
#map {
height: 100%;
flex-grow: 4;
flex-basis: auto;
}
index.html
<html>
<head>
<title>Austin recycling drop-off locations</title>
<link rel="stylesheet" type="text/css" href="style.css" />
<script src="app.js"></script>
<script
defer
src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=weekly&libraries=places&callback=initialize&solution_channel=GMP_codelabs_fullstackstorelocator_v1_a"
></script>
</head>
<body>
<div id="map"></div>
<!-- Autocomplete div goes here -->
</body>
</html>
Achten Sie besonders auf die src
-URL im Script-Tag des head
-Elements.
- Ersetzen Sie den Platzhaltertext „
YOUR_API_KEY
“ durch den API-Schlüssel, den Sie während der Einrichtung generiert haben. Sie können Ihren API-Schlüssel in der Cloud Console auf der Seite APIs & Dienste > Anmeldedaten abrufen oder einen neuen generieren. - Die URL enthält den Parameter
callback=initialize.
. Wir erstellen jetzt die JavaScript-Datei, die diese Callback-Funktion enthält. Hier lädt Ihre App die Standorte aus dem Backend, sendet sie an die Maps API und verwendet das Ergebnis, um benutzerdefinierte Standorte auf der Karte zu markieren. Die Karte wird dann auf Ihrer Webseite gerendert. - Mit dem Parameter
libraries=places
wird die Places Library geladen, die für Funktionen wie die automatische Vervollständigung von Adressen erforderlich ist, die später hinzugefügt werden.
app.js
let distanceMatrixService;
let map;
let originMarker;
let infowindow;
let circles = [];
let stores = [];
// The location of Austin, TX
const AUSTIN = { lat: 30.262129, lng: -97.7468 };
async function initialize() {
initMap();
// TODO: Initialize an infoWindow
// Fetch and render stores as circles on map
fetchAndRenderStores(AUSTIN);
// TODO: Initialize the Autocomplete widget
}
const initMap = () => {
// TODO: Start Distance Matrix service
// The map, centered on Austin, TX
map = new google.maps.Map(document.querySelector("#map"), {
center: AUSTIN,
zoom: 14,
// mapId: 'YOUR_MAP_ID_HERE',
clickableIcons: false,
fullscreenControl: false,
mapTypeControl: false,
rotateControl: true,
scaleControl: false,
streetViewControl: true,
zoomControl: true,
});
};
const fetchAndRenderStores = async (center) => {
// Fetch the stores from the data source
stores = (await fetchStores(center)).features;
// Create circular markers based on the stores
circles = stores.map((store) => storeToCircle(store, map));
};
const fetchStores = async (center) => {
const url = `/data/dropoffs`;
const response = await fetch(url);
return response.json();
};
const storeToCircle = (store, map) => {
const [lng, lat] = store.geometry.coordinates;
const circle = new google.maps.Circle({
radius: 50,
strokeColor: "#579d42",
strokeOpacity: 0.8,
strokeWeight: 5,
center: { lat, lng },
map,
});
return circle;
};
Mit diesem Code werden Geschäftsstandorte auf einer Karte gerendert. Um zu testen, was wir bisher haben, kehren Sie über die Befehlszeile zum übergeordneten Verzeichnis zurück:
cd ..
Führen Sie Ihre App nun noch einmal im Entwicklermodus aus:
go run *.go
Sehen Sie sich die Vorschau wie zuvor an. Sie sollten eine Karte mit kleinen grünen Kreisen wie dieser sehen.
Sie rendern bereits Kartenstandorte und wir sind erst auf halbem Weg durch das Codelab. Unglaublich. Jetzt fügen wir einige interaktive Elemente hinzu.
6. Details auf Anfrage anzeigen
Auf Klickereignisse auf Kartenmarkierungen reagieren
Das Anzeigen einer Reihe von Markierungen auf der Karte ist ein guter Anfang, aber Besucher müssen auf eine dieser Markierungen klicken können, um Informationen zu diesem Standort zu sehen, z. B. den Namen des Unternehmens und die Adresse. Das kleine Informationsfenster, das normalerweise angezeigt wird, wenn Sie auf eine Google Maps-Markierung klicken, heißt Infofenster.
Erstellen Sie ein infoWindow-Objekt. Fügen Sie der Funktion initialize
Folgendes hinzu und ersetzen Sie die kommentierte Zeile mit dem Text „// TODO: Initialize an info window
“.
app.js – initialize
// Add an info window that pops up when user clicks on an individual
// location. Content of info window is entirely up to us.
infowindow = new google.maps.InfoWindow();
Ersetzen Sie die Funktionsdefinition fetchAndRenderStores
durch diese leicht abweichende Version, in der die letzte Zeile so geändert wurde, dass storeToCircle
mit einem zusätzlichen Argument, infowindow
, aufgerufen wird:
app.js – fetchAndRenderStores
const fetchAndRenderStores = async (center) => {
// Fetch the stores from the data source
stores = (await fetchStores(center)).features;
// Create circular markers based on the stores
circles = stores.map((store) => storeToCircle(store, map, infowindow));
};
Ersetzen Sie die storeToCircle
-Definition durch diese etwas längere Version, die jetzt ein Infofenster als drittes Argument akzeptiert:
app.js – storeToCircle
const storeToCircle = (store, map, infowindow) => {
const [lng, lat] = store.geometry.coordinates;
const circle = new google.maps.Circle({
radius: 50,
strokeColor: "#579d42",
strokeOpacity: 0.8,
strokeWeight: 5,
center: { lat, lng },
map,
});
circle.addListener("click", () => {
infowindow.setContent(`${store.properties.business_name}<br />
${store.properties.address_address}<br />
Austin, TX ${store.properties.zip_code}`);
infowindow.setPosition({ lat, lng });
infowindow.setOptions({ pixelOffset: new google.maps.Size(0, -30) });
infowindow.open(map);
});
return circle;
};
Der neue Code oben zeigt ein infoWindow
mit den Informationen des ausgewählten Geschäfts an, wenn auf der Karte auf eine Geschäftsmarkierung geklickt wird.
Wenn Ihr Server noch ausgeführt wird, beenden Sie ihn und starten Sie ihn neu. Aktualisieren Sie die Kartenseite und klicken Sie auf eine Kartenmarkierung. Es sollte ein kleines Infofenster mit dem Namen und der Adresse des Unternehmens angezeigt werden, etwa so:
7. Startort des Nutzers abrufen
Nutzer von Standortfindern möchten in der Regel wissen, welches Geschäft sich am nächsten zu ihnen oder zu einer Adresse befindet, an der sie ihre Reise beginnen möchten. Fügen Sie eine Place Autocomplete-Suchleiste hinzu, damit der Nutzer ganz einfach eine Startadresse eingeben kann. Place Autocomplete bietet eine Funktion zur automatischen Vervollständigung, die ähnlich wie die automatische Vervollständigung in anderen Google-Suchleisten funktioniert. Die Vorschläge sind jedoch alle Orte in der Google Maps Platform.
Eingabefeld für Nutzer erstellen
Kehren Sie zurück zu style.css
, um die Formatierung für die Autocomplete-Suchleiste und die zugehörige Seitenleiste mit Ergebnissen hinzuzufügen. Während wir die CSS-Stile aktualisieren, fügen wir auch Stile für eine zukünftige Seitenleiste hinzu, in der Geschäftsinformationen als Liste neben der Karte angezeigt werden.
Fügen Sie diesen Code am Ende der Datei ein.
style.css
#panel {
height: 100%;
flex-basis: 0;
flex-grow: 0;
overflow: auto;
transition: all 0.2s ease-out;
}
#panel.open {
flex-basis: auto;
}
#panel .place {
font-family: "open sans", arial, sans-serif;
font-size: 1.2em;
font-weight: 500;
margin-block-end: 0px;
padding-left: 18px;
padding-right: 18px;
}
#panel .distanceText {
color: silver;
font-family: "open sans", arial, sans-serif;
font-size: 1em;
font-weight: 400;
margin-block-start: 0.25em;
padding-left: 18px;
padding-right: 18px;
}
/* Styling for Autocomplete search bar */
#pac-card {
background-color: #fff;
border-radius: 2px 0 0 2px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
box-sizing: border-box;
font-family: Roboto;
margin: 10px 10px 0 0;
-moz-box-sizing: border-box;
outline: none;
}
#pac-container {
padding-top: 12px;
padding-bottom: 12px;
margin-right: 12px;
}
#pac-input {
background-color: #fff;
font-family: Roboto;
font-size: 15px;
font-weight: 300;
margin-left: 12px;
padding: 0 11px 0 13px;
text-overflow: ellipsis;
width: 400px;
}
#pac-input:focus {
border-color: #4d90fe;
}
#pac-title {
color: #fff;
background-color: #acbcc9;
font-size: 18px;
font-weight: 400;
padding: 6px 12px;
}
.hidden {
display: none;
}
Sowohl die Suchleiste für die automatische Vervollständigung als auch das Einblendfeld sind anfangs ausgeblendet, bis sie benötigt werden.
Bereiten Sie ein div-Element für das Autocomplete-Widget vor, indem Sie den Kommentar in index.html, der "<!-- Autocomplete div goes here -->
lautet, durch den folgenden Code ersetzen. Bei dieser Änderung fügen wir auch das DIV für das Einblendfeld hinzu.
index.html
<div id="panel" class="closed"></div>
<div class="hidden">
<div id="pac-card">
<div id="pac-title">Find the nearest location</div>
<div id="pac-container">
<input
id="pac-input"
type="text"
placeholder="Enter an address"
class="pac-target-input"
autocomplete="off"
/>
</div>
</div>
</div>
Definieren Sie nun eine Funktion, um der Karte das Autocomplete-Widget hinzuzufügen. Fügen Sie dazu den folgenden Code am Ende von app.js
ein.
app.js
const initAutocompleteWidget = () => {
// Add search bar for auto-complete
// Build and add the search bar
const placesAutoCompleteCardElement = document.getElementById("pac-card");
const placesAutoCompleteInputElement = placesAutoCompleteCardElement.querySelector(
"input"
);
const options = {
types: ["address"],
componentRestrictions: { country: "us" },
map,
};
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(
placesAutoCompleteCardElement
);
// Make the search bar into a Places Autocomplete search bar and select
// which detail fields should be returned about the place that
// the user selects from the suggestions.
const autocomplete = new google.maps.places.Autocomplete(
placesAutoCompleteInputElement,
options
);
autocomplete.setFields(["address_components", "geometry", "name"]);
map.addListener("bounds_changed", () => {
autocomplete.setBounds(map.getBounds());
});
// TODO: Respond when a user selects an address
};
Der Code schränkt die Autocomplete-Vorschläge so ein, dass nur Adressen zurückgegeben werden (da Place Autocomplete auch mit Unternehmensnamen und administrativen Standorten übereinstimmen kann). Außerdem werden nur Adressen in den USA zurückgegeben. Wenn Sie diese optionalen Angaben hinzufügen, muss der Nutzer weniger Zeichen eingeben, um die Vorhersagen einzugrenzen und die gesuchte Adresse zu finden.
Anschließend wird das von Ihnen erstellte Autocomplete div
in die rechte obere Ecke der Karte verschoben und angegeben, welche Felder in der Antwort für jeden Ort zurückgegeben werden sollen.
Rufen Sie schließlich die Funktion initAutocompleteWidget
am Ende der Funktion initialize
auf und ersetzen Sie den Kommentar // TODO: Initialize the Autocomplete widget
.
app.js – initialize
// Initialize the Places Autocomplete Widget
initAutocompleteWidget();
Starten Sie den Server mit dem folgenden Befehl neu und aktualisieren Sie dann die Vorschau.
go run *.go
In der oberen rechten Ecke der Karte sollte jetzt ein Autocomplete-Widget zu sehen sein, in dem US-Adressen angezeigt werden, die mit dem übereinstimmen, was Sie eingeben. Die Vorschläge sind auf den sichtbaren Bereich der Karte ausgerichtet.
Karte aktualisieren, wenn ein Nutzer eine Startadresse auswählt
Als Nächstes müssen Sie festlegen, was passieren soll, wenn der Nutzer eine Vorhersage aus dem Widget für die automatische Vervollständigung auswählt. Verwenden Sie diesen Standort dann als Grundlage für die Berechnung der Entfernungen zu Ihren Geschäften.
Fügen Sie den folgenden Code am Ende von initAutocompleteWidget
in app.js
ein und ersetzen Sie den Kommentar „// TODO: Respond when a user selects an address
“.
app.js - initAutocompleteWidget
// Respond when a user selects an address
// Set the origin point when the user selects an address
originMarker = new google.maps.Marker({ map: map });
originMarker.setVisible(false);
let originLocation = map.getCenter();
autocomplete.addListener("place_changed", async () => {
// circles.forEach((c) => c.setMap(null)); // clear existing stores
originMarker.setVisible(false);
originLocation = map.getCenter();
const place = autocomplete.getPlace();
if (!place.geometry) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
window.alert("No address available for input: '" + place.name + "'");
return;
}
// Recenter the map to the selected address
originLocation = place.geometry.location;
map.setCenter(originLocation);
map.setZoom(15);
originMarker.setPosition(originLocation);
originMarker.setVisible(true);
// await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
});
Durch den Code wird ein Listener hinzugefügt, sodass die Karte neu zentriert wird, wenn der Nutzer auf einen der Vorschläge klickt. Der Ursprung wird dann als Grundlage für Ihre Entfernungsberechnungen festgelegt. Sie implementieren die Entfernungsberechnungen in einem späteren Schritt.
Beenden Sie den Server und starten Sie ihn neu. Aktualisieren Sie dann die Vorschau, um zu sehen, wie die Karte neu zentriert wird, nachdem Sie eine Adresse in die Autovervollständigungs-Suchleiste eingegeben haben.
8. Mit Cloud SQL skalieren
Bisher haben wir eine ziemlich gute Händlersuche. Dabei wird die Tatsache genutzt, dass die App nur etwa hundert Standorte verwendet. Diese werden im Backend in den Arbeitsspeicher geladen, anstatt wiederholt aus der Datei gelesen zu werden. Was aber, wenn Ihr Locator auf einer anderen Skala arbeiten muss? Wenn Sie Hunderte von Standorten haben, die über ein großes geografisches Gebiet verteilt sind (oder Tausende auf der ganzen Welt), ist es nicht mehr sinnvoll, alle diese Standorte im Arbeitsspeicher zu behalten. Das Aufteilen von Zonen in einzelne Dateien führt zu eigenen Problemen.
Es ist an der Zeit, Ihre Standorte aus einer Datenbank zu laden. In diesem Schritt migrieren wir alle Standorte in Ihrer GeoJSON-Datei in eine Cloud SQL-Datenbank und aktualisieren das Go-Backend so, dass Ergebnisse bei jeder eingehenden Anfrage aus dieser Datenbank und nicht aus dem lokalen Cache abgerufen werden.
Cloud SQL-Instanz mit PostgreSQL-Datenbank erstellen
Sie können eine Cloud SQL-Instanz über die Google Cloud Console erstellen. Noch einfacher ist es jedoch, das gcloud
-Tool zu verwenden, um eine Instanz über die Befehlszeile zu erstellen. Erstellen Sie in Cloud Shell mit dem folgenden Befehl eine Cloud SQL-Instanz:
gcloud sql instances create locations \ --database-version=POSTGRES_12 \ --tier=db-custom-1-3840 --region=us-central1
- Das Argument
locations
ist der Name, den wir dieser Cloud SQL-Instanz geben. - Mit dem Flag
tier
können Sie aus einigen praktischen vordefinierten Maschinen auswählen. - Der Wert
db-custom-1-3840
gibt an, dass die erstellte Instanz eine vCPU und etwa 3,75 GB Arbeitsspeicher haben soll.
Die Cloud SQL-Instanz wird erstellt und mit einer PostgreSQL-Datenbank mit dem Standardnutzer postgres
initialisiert. Wie lautet das Passwort dieses Nutzers? Gute Frage. Sie haben keine. Sie müssen eine konfigurieren, bevor Sie sich anmelden können.
Legen Sie das Passwort mit dem folgenden Befehl fest:
gcloud sql users set-password postgres \ --instance=locations --prompt-for-password
Geben Sie dann das von Ihnen gewählte Passwort ein, wenn Sie dazu aufgefordert werden.
PostGIS-Erweiterung aktivieren
PostGIS ist eine Erweiterung für PostgreSQL, mit der sich standardisierte Typen von raumbezogenen Daten einfacher speichern lassen. Unter normalen Umständen müssten wir einen vollständigen Installationsprozess durchlaufen, um PostGIS zu unserer Datenbank hinzuzufügen. Glücklicherweise ist es eine der von Cloud SQL unterstützten Erweiterungen für PostgreSQL.
Stellen Sie eine Verbindung zur Datenbankinstanz her, indem Sie sich mit dem folgenden Befehl im Cloud Shell-Terminal als Nutzer postgres
anmelden.
gcloud sql connect locations --user=postgres --quiet
Geben Sie das gerade erstellte Passwort ein. Fügen Sie jetzt die PostGIS-Erweiterung an der Eingabeaufforderung postgres=>
hinzu.
CREATE EXTENSION postgis;
Bei Erfolg sollte in der Ausgabe CREATE EXTENSION angezeigt werden, wie unten dargestellt.
Beispiel für die Befehlsausgabe
CREATE EXTENSION
Trennen Sie die Datenbankverbindung, indem Sie an der Eingabeaufforderung postgres=>
den Befehl „quit“ eingeben.
\q
Geografische Daten in Datenbank importieren
Jetzt müssen wir alle Standortdaten aus den GeoJSON-Dateien in unsere neue Datenbank importieren.
Glücklicherweise ist das ein bekanntes Problem und im Internet finden Sie mehrere Tools, die diesen Vorgang für Sie automatisieren können. Wir verwenden das Tool ogr2ogr, mit dem sich Daten zwischen mehreren gängigen Formaten zum Speichern raumbezogener Daten konvertieren lassen. Eine dieser Optionen ist, wie Sie vielleicht schon vermutet haben, die Konvertierung von GeoJSON in eine SQL-Dumpdatei. Die SQL-Dumpdatei kann dann verwendet werden, um die Tabellen und Spalten für die Datenbank zu erstellen und sie mit allen Daten zu laden, die in Ihren GeoJSON-Dateien vorhanden waren.
SQL-Dumpdatei erstellen
Installieren Sie zuerst ogr2ogr.
sudo apt-get install gdal-bin
Verwenden Sie als Nächstes ogr2ogr, um die SQL-Dumpdatei zu erstellen. In dieser Datei wird eine Tabelle mit dem Namen austinrecycling
erstellt.
ogr2ogr --config PG_USE_COPY YES -f PGDump datadump.sql \ data/recycling-locations.geojson -nln austinrecycling
Der obige Befehl basiert auf der Ausführung aus dem Ordner austin-recycling
. Wenn Sie das Skript aus einem anderen Verzeichnis ausführen müssen, ersetzen Sie data
durch den Pfad zum Verzeichnis, in dem recycling-locations.geojson
gespeichert ist.
Datenbank mit Recyclingstandorten füllen
Nachdem Sie den letzten Befehl ausgeführt haben, sollte sich im selben Verzeichnis, in dem Sie den Befehl ausgeführt haben, eine Datei namens datadump.sql,
befinden. Wenn Sie die Datei öffnen, sehen Sie etwas mehr als hundert Zeilen SQL-Code, mit denen eine Tabelle austinrecycling
erstellt und mit Standorten gefüllt wird.
Stellen Sie nun eine Verbindung zur Datenbank her und führen Sie das Skript mit dem folgenden Befehl aus.
gcloud sql connect locations --user=postgres --quiet < datadump.sql
Wenn das Skript erfolgreich ausgeführt wird, sehen die letzten Zeilen der Ausgabe so aus:
Beispiel für die Befehlsausgabe
ALTER TABLE ALTER TABLE ATLER TABLE ALTER TABLE COPY 103 COMMIT WARNING: there is no transaction in progress COMMIT
Go-Back-End für die Verwendung von Cloud SQL aktualisieren
Nachdem wir alle diese Daten in unserer Datenbank haben, ist es an der Zeit, unseren Code zu aktualisieren.
Frontend aktualisieren, um Standortinformationen zu senden
Beginnen wir mit einer sehr kleinen Änderung am Frontend: Da wir diese App jetzt für einen Maßstab schreiben, bei dem wir nicht jeden einzelnen Standort jedes Mal an das Frontend übermitteln möchten, wenn die Anfrage ausgeführt wird, müssen wir einige grundlegende Informationen vom Frontend über den Standort übergeben, der für den Nutzer relevant ist.
Öffnen Sie app.js
und ersetzen Sie die Funktionsdefinition fetchStores
durch diese Version, um den Breiten- und Längengrad in die URL aufzunehmen.
app.js – fetchStores
const fetchStores = async (center) => {
const url = `/data/dropoffs?centerLat=${center.lat}¢erLng=${center.lng}`;
const response = await fetch(url);
return response.json();
};
Nach Abschluss dieses Schritts des Codelabs werden in der Antwort nur die Geschäfte zurückgegeben, die sich am nächsten an den im Parameter center
angegebenen Kartenkoordinaten befinden. Für den ersten Abruf in der Funktion initialize
verwendet der in diesem Lab bereitgestellte Beispielcode die zentralen Koordinaten für Austin, Texas.
Da fetchStores
jetzt nur noch eine Teilmenge der Geschäftsstandorte zurückgibt, müssen wir die Geschäfte neu abrufen, wenn der Nutzer seinen Startort ändert.
Aktualisieren Sie die Funktion initAutocompleteWidget
, damit die Standorte immer dann aktualisiert werden, wenn ein neuer Ursprung festgelegt wird. Dazu sind zwei Änderungen erforderlich:
- Suchen Sie in „initAutocompleteWidget“ nach dem Callback für den
place_changed
-Listener. Entfernen Sie die Auskommentierung der Zeile, mit der vorhandene Kreise gelöscht werden. Diese Zeile wird nun jedes Mal ausgeführt, wenn der Nutzer eine Adresse aus der Place Autocomplete-Suchleiste auswählt.
app.js – initAutocompleteWidget
autocomplete.addListener("place_changed", async () => {
circles.forEach((c) => c.setMap(null)); // clear existing stores
// ...
- Wenn der ausgewählte Ursprung geändert wird, wird die Variable „originLocation“ aktualisiert. Entfernen Sie am Ende des „
place_changed
“-Callbacks die Auskommentierung der Zeile über der Zeile „// TODO: Calculate the closest stores
“, um diesen neuen Ursprung an einen neuen Aufruf der FunktionfetchAndRenderStores
zu übergeben.
app.js – initAutocompleteWidget
await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
Back-End so aktualisieren, dass CloudSQL anstelle einer einfachen JSON-Datei verwendet wird
Entfernen des Lesens und Caching von GeoJSON-Dateien
Ändern Sie zuerst main.go
, um den Code zu entfernen, mit dem die flache GeoJSON-Datei geladen und im Cache gespeichert wird. Wir können auch die dropoffsHandler
-Funktion entfernen, da wir eine Funktion, die auf Cloud SQL basiert, in einer anderen Datei schreiben.
Ihr neues main.go
wird viel kürzer sein.
main.go
package main
import (
"log"
"net/http"
"os"
)
func main() {
initConnectionPool()
// Request for data should be handled by Go. Everything else should be directed
// to the folder of static files.
http.HandleFunc("/data/dropoffs", dropoffsHandler)
http.Handle("/", http.FileServer(http.Dir("./static/")))
// Open up a port for the webserver.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
Neuen Handler für Standortanfragen erstellen
Erstellen wir nun eine weitere Datei, locations.go
, ebenfalls im Verzeichnis „austin-recycling“. Beginnen Sie damit, den Handler für Standortanfragen neu zu implementieren.
locations.go
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
"os"
_ "github.com/jackc/pgx/stdlib"
)
// queryBasic demonstrates issuing a query and reading results.
func dropoffsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/json")
centerLat := r.FormValue("centerLat")
centerLng := r.FormValue("centerLng")
geoJSON, err := getGeoJSONFromDatabase(centerLat, centerLng)
if err != nil {
str := fmt.Sprintf("Couldn't encode results: %s", err)
http.Error(w, str, 500)
return
}
fmt.Fprintf(w, geoJSON)
}
Der Handler führt die folgenden wichtigen Aufgaben aus:
- Breiten- und Längengrad werden aus dem Anfrageobjekt abgerufen. (Erinnern Sie sich, wie wir diese der URL hinzugefügt haben? )
- Dadurch wird der
getGeoJsonFromDatabase
-Aufruf ausgelöst, der einen GeoJSON-String zurückgibt (diesen schreiben wir später). - Mit
ResponseWriter
wird dieser GeoJSON-String in die Antwort ausgegeben.
Als Nächstes erstellen wir einen Verbindungspool, damit die Datenbanknutzung mit der Anzahl der gleichzeitigen Nutzer skaliert werden kann.
Verbindungspool erstellen
Ein Verbindungspool ist eine Sammlung aktiver Datenbankverbindungen, die der Server zur Bearbeitung von Nutzeranfragen wiederverwenden kann. Das spart viel Aufwand, wenn die Anzahl der aktiven Nutzer steigt, da der Server nicht für jeden aktiven Nutzer Verbindungen erstellen und schließen muss. Im vorherigen Abschnitt haben wir die Bibliothek github.com/jackc/pgx/stdlib.
importiert. Das ist eine beliebte Bibliothek für die Arbeit mit Verbindungspools in Go.
Erstellen Sie am Ende von locations.go
eine Funktion initConnectionPool
(aufgerufen von main.go
), die einen Verbindungspool initialisiert. Zur Verdeutlichung werden in diesem Snippet einige Hilfsmethoden verwendet. configureConnectionPool
bietet eine praktische Möglichkeit, Pooleinstellungen wie die Anzahl der Verbindungen und die Lebensdauer pro Verbindung anzupassen. mustGetEnv
umschließt Aufrufe zum Abrufen erforderlicher Umgebungsvariablen, sodass hilfreiche Fehlermeldungen ausgegeben werden können, wenn der Instanz wichtige Informationen fehlen (z. B. die IP-Adresse oder der Name der Datenbank, mit der eine Verbindung hergestellt werden soll).
locations.go
// The connection pool
var db *sql.DB
// Each struct instance contains a single row from the query result.
type result struct {
featureCollection string
}
func initConnectionPool() {
// If the optional DB_TCP_HOST environment variable is set, it contains
// the IP address and port number of a TCP connection pool to be created,
// such as "127.0.0.1:5432". If DB_TCP_HOST is not set, a Unix socket
// connection pool will be created instead.
if os.Getenv("DB_TCP_HOST") != "" {
var (
dbUser = mustGetenv("DB_USER")
dbPwd = mustGetenv("DB_PASS")
dbTCPHost = mustGetenv("DB_TCP_HOST")
dbPort = mustGetenv("DB_PORT")
dbName = mustGetenv("DB_NAME")
)
var dbURI string
dbURI = fmt.Sprintf("host=%s user=%s password=%s port=%s database=%s", dbTCPHost, dbUser, dbPwd, dbPort, dbName)
// dbPool is the pool of database connections.
dbPool, err := sql.Open("pgx", dbURI)
if err != nil {
dbPool = nil
log.Fatalf("sql.Open: %v", err)
}
configureConnectionPool(dbPool)
if err != nil {
log.Fatalf("initConnectionPool: unable to connect: %s", err)
}
db = dbPool
}
}
// configureConnectionPool sets database connection pool properties.
// For more information, see https://golang.org/pkg/database/sql
func configureConnectionPool(dbPool *sql.DB) {
// Set maximum number of connections in idle connection pool.
dbPool.SetMaxIdleConns(5)
// Set maximum number of open connections to the database.
dbPool.SetMaxOpenConns(7)
// Set Maximum time (in seconds) that a connection can remain open.
dbPool.SetConnMaxLifetime(1800)
}
// mustGetEnv is a helper function for getting environment variables.
// Displays a warning if the environment variable is not set.
func mustGetenv(k string) string {
v := os.Getenv(k)
if v == "" {
log.Fatalf("Warning: %s environment variable not set.\n", k)
}
return v
}
Standorte in der Datenbank abfragen und JSON als Antwort erhalten
Als Nächstes schreiben wir eine Datenbankabfrage, die Kartenkoordinaten verwendet und die 25 nächstgelegenen Orte zurückgibt. Dank einiger moderner Datenbankfunktionen werden die Daten auch als GeoJSON zurückgegeben. Das Endergebnis ist, dass sich aus Sicht des Frontend-Codes nichts geändert hat. Bevor eine Anfrage an eine URL gesendet und eine Menge GeoJSON empfangen wurde. Jetzt wird eine Anfrage an eine URL gesendet und es wird eine Menge GeoJSON zurückgegeben.
Hier ist die Funktion, die das ermöglicht. Fügen Sie die folgende Funktion nach dem Handler- und Verbindungspooling-Code ein, den Sie gerade unten in locations.go
geschrieben haben.
locations.go
func getGeoJSONFromDatabase(centerLat string, centerLng string) (string, error) {
// Obviously you can one-line this, but for testing purposes let's make it easy to modify on the fly.
const milesRadius = 10
const milesToMeters = 1609
const radiusInMeters = milesRadius * milesToMeters
const tableName = "austinrecycling"
var queryStr = fmt.Sprintf(
`SELECT jsonb_build_object(
'type',
'FeatureCollection',
'features',
jsonb_agg(feature)
)
FROM (
SELECT jsonb_build_object(
'type',
'Feature',
'id',
ogc_fid,
'geometry',
ST_AsGeoJSON(wkb_geometry)::jsonb,
'properties',
to_jsonb(row) - 'ogc_fid' - 'wkb_geometry'
) AS feature
FROM (
SELECT *,
ST_Distance(
ST_GEOGFromWKB(wkb_geometry),
-- Los Angeles (LAX)
ST_GEOGFromWKB(st_makepoint(%v, %v))
) as distance
from %v
order by distance
limit 25
) row
where distance < %v
) features
`, centerLng, centerLat, tableName, radiusInMeters)
log.Println(queryStr)
rows, err := db.Query(queryStr)
defer rows.Close()
rows.Next()
queryResult := result{}
err = rows.Scan(&queryResult.featureCollection)
return queryResult.featureCollection, err
}
Diese Funktion dient hauptsächlich zum Einrichten, Beenden und zur Fehlerbehandlung beim Senden einer Anfrage an die Datenbank. Sehen wir uns den tatsächlichen SQL-Code an. Er führt viele interessante Aktionen auf der Datenbankebene aus, sodass Sie sich nicht um die Implementierung im Code kümmern müssen.
Die Rohabfrage, die ausgeführt wird, nachdem der String geparst und alle Stringliterale an den richtigen Stellen eingefügt wurden, sieht so aus:
parsed.sql
SELECT jsonb_build_object(
'type',
'FeatureCollection',
'features',
jsonb_agg(feature)
)
FROM (
SELECT jsonb_build_object(
'type',
'Feature',
'id',
ogc_fid,
'geometry',
ST_AsGeoJSON(wkb_geometry)::jsonb,
'properties',
to_jsonb(row) - 'ogc_fid' - 'wkb_geometry'
) AS feature
FROM (
SELECT *,
ST_Distance(
ST_GEOGFromWKB(wkb_geometry),
-- Los Angeles (LAX)
ST_GEOGFromWKB(st_makepoint(-97.7624043, 30.523725))
) as distance
from austinrecycling
order by distance
limit 25
) row
where distance < 16090
) features
Diese Abfrage kann als eine primäre Abfrage und einige JSON-Wrapping-Funktionen betrachtet werden.
Mit SELECT * ... LIMIT 25
werden alle Felder für jeden Standort ausgewählt. Anschließend wird die Funktion ST_DISTANCE (Teil der PostGIS-Funktionen zur geografischen Messung) verwendet, um die Entfernung zwischen jedem Standort in der Datenbank und dem vom Nutzer im Frontend angegebenen Breiten-/Längengradpaar zu ermitteln. Im Gegensatz zur Distance Matrix API, die Ihnen die Fahrstrecke liefern kann, handelt es sich hier um geografische Distanzen. Aus Effizienzgründen wird diese Entfernung dann verwendet, um die 25 Standorte zu sortieren, die dem vom Nutzer angegebenen Standort am nächsten sind.
**SELECT json_build_object(‘type', ‘F
**eature') umschließt die vorherige Abfrage, nimmt die Ergebnisse entgegen und verwendet sie, um ein GeoJSON-Funktionsobjekt zu erstellen. Unerwarteterweise wird bei dieser Abfrage auch der maximale Radius angewendet. „16090“ ist die Anzahl der Meter in 10 Meilen, dem vom Go-Backend angegebenen harten Limit. Die WHERE-Klausel wurde nicht der inneren Abfrage hinzugefügt, in der die Entfernung jedes Standorts bestimmt wird, weil das Feld möglicherweise noch nicht berechnet wurde, als die WHERE-Klausel geprüft wurde. Wenn Sie versuchen, diese WHERE-Klausel in die innere Abfrage zu verschieben, wird ein Fehler ausgegeben.
**SELECT json_build_object(‘type', ‘FeatureColl
**ection'): Mit dieser Abfrage werden alle Ergebniszeilen aus der JSON-generierenden Abfrage in ein GeoJSON-FeatureCollection-Objekt eingeschlossen.
PGX-Bibliothek zu Ihrem Projekt hinzufügen
Wir müssen Ihrem Projekt eine Abhängigkeit hinzufügen: das PostGres Driver & Toolkit, das Connection Pooling ermöglicht. Am einfachsten geht das mit Go-Modulen. Initialisieren Sie ein Modul mit diesem Befehl in Cloud Shell:
go mod init my_locator
Führen Sie als Nächstes diesen Befehl aus, um den Code nach Abhängigkeiten zu durchsuchen, eine Liste der Abhängigkeiten zur Mod-Datei hinzuzufügen und sie herunterzuladen.
go mod tidy
Führen Sie schließlich diesen Befehl aus, um Abhängigkeiten direkt in Ihr Projektverzeichnis zu ziehen, damit der Container problemlos für App Engine Flex erstellt werden kann.
go mod vendor
Jetzt können Sie es ausprobieren.
Ausprobieren
Okay, wir haben gerade VIEL geschafft. Sehen wir uns an, wie das funktioniert.
Damit Ihr Entwicklungscomputer (ja, auch Cloud Shell) eine Verbindung zur Datenbank herstellen kann, müssen wir den Cloud SQL-Proxy verwenden, um die Datenbankverbindung zu verwalten. So richten Sie den Cloud SQL-Proxy ein:
- Cloud SQL Admin API aktivieren
- Wenn Sie einen lokalen Entwicklungscomputer verwenden, installieren Sie das Cloud SQL-Proxy-Tool. Wenn Sie Cloud Shell verwenden, können Sie diesen Schritt überspringen, da das Tool bereits installiert ist. Beachten Sie, dass sich die Anleitung auf ein Dienstkonto bezieht. Eines wurde bereits für Sie erstellt. Im nächsten Abschnitt erfahren Sie, wie Sie diesem Konto die erforderlichen Berechtigungen hinzufügen.
- Erstellen Sie einen neuen Tab (in Cloud Shell oder in Ihrem eigenen Terminal), um den Proxy zu starten.
- Rufen Sie
https://console.cloud.google.com/sql/instances/locations/overview
auf und scrollen Sie nach unten zum Feld Verbindungsname. Kopieren Sie diesen Namen, um ihn im nächsten Befehl zu verwenden. - Führen Sie auf diesem Tab den Cloud SQL-Proxy mit diesem Befehl aus. Ersetzen Sie dabei
CONNECTION_NAME
durch den im vorherigen Schritt angezeigten Verbindungsnamen.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432
Kehren Sie zum ersten Tab Ihrer Cloud Shell zurück und definieren Sie die Umgebungsvariablen, die Go für die Kommunikation mit dem Datenbank-Backend benötigt. Führen Sie dann den Server auf dieselbe Weise wie zuvor aus:
Wechseln Sie zum Stammverzeichnis des Projekts, falls Sie sich noch nicht dort befinden.
cd YOUR_PROJECT_ROOT
Erstellen Sie die folgenden fünf Umgebungsvariablen (ersetzen Sie YOUR_PASSWORD_HERE
durch das oben erstellte Passwort).
export DB_USER=postgres export DB_PASS=YOUR_PASSWORD_HERE export DB_TCP_HOST=127.0.0.1 # Proxy export DB_PORT=5432 #Default for PostGres export DB_NAME=postgres
Führen Sie Ihre lokale Instanz aus.
go run *.go
Öffnen Sie das Vorschaufenster. Es sollte sich so verhalten, als hätte sich nichts geändert: Sie können eine Startadresse eingeben, auf der Karte zoomen und auf Recyclingstandorte klicken. Jetzt wird sie aber von einer Datenbank unterstützt und ist für die Skalierung vorbereitet.
9. Nächstgelegene Geschäfte auflisten
Die Directions API funktioniert ähnlich wie das Anfordern einer Wegbeschreibung in der Google Maps App: Sie geben einen einzelnen Start- und Zielort ein, um eine Route zwischen den beiden Orten zu erhalten. Die Distance Matrix API geht noch einen Schritt weiter und ermittelt die optimalen Kombinationen zwischen mehreren möglichen Start- und Zielorten anhand von Reisezeiten und Entfernungen. In diesem Fall geben Sie einen Ursprung und ein Array von Geschäftsstandorten als Ziele an, damit der Nutzer das nächstgelegene Geschäft zur ausgewählten Adresse findet.
Fügen Sie jedem Geschäft die Entfernung vom Ursprung hinzu.
Ersetzen Sie am Anfang der initMap
-Funktionsdefinition den Kommentar „// TODO: Start Distance Matrix service
“ durch den folgenden Code:
app.js - initMap
distanceMatrixService = new google.maps.DistanceMatrixService();
Fügen Sie am Ende von app.js
eine neue Funktion mit dem Namen calculateDistances
hinzu.
app.js
async function calculateDistances(origin, stores) {
// Retrieve the distances of each store from the origin
// The returned list will be in the same order as the destinations list
const response = await getDistanceMatrix({
origins: [origin],
destinations: stores.map((store) => {
const [lng, lat] = store.geometry.coordinates;
return { lat, lng };
}),
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
});
response.rows[0].elements.forEach((element, index) => {
stores[index].properties.distanceText = element.distance.text;
stores[index].properties.distanceValue = element.distance.value;
});
}
const getDistanceMatrix = (request) => {
return new Promise((resolve, reject) => {
const callback = (response, status) => {
if (status === google.maps.DistanceMatrixStatus.OK) {
resolve(response);
} else {
reject(response);
}
};
distanceMatrixService.getDistanceMatrix(request, callback);
});
};
Die Funktion ruft die Distance Matrix API mit dem übergebenen Ausgangspunkt als einzelnen Ausgangspunkt und den Filialstandorten als Array von Zielorten auf. Anschließend wird ein Array von Objekten erstellt, in dem die ID des Geschäfts, die Entfernung als menschenlesbarer String und die Entfernung in Metern als numerischer Wert gespeichert werden. Das Array wird dann sortiert.
Aktualisieren Sie die initAutocompleteWidget
-Funktion, um die Entfernungen zu den Geschäften zu berechnen, wenn in der Suchleiste für die automatische Vervollständigung von Orten ein neuer Ausgangspunkt ausgewählt wird. Ersetzen Sie unten in der Funktion initAutocompleteWidget
den Kommentar „// TODO: Calculate the closest stores
“ durch den folgenden Code:
app.js - initAutocompleteWidget
// Use the selected address as the origin to calculate distances
// to each of the store locations
await calculateDistances(originLocation, stores);
renderStoresPanel();
Eine Listenansicht von Geschäften anzeigen, die nach Entfernung sortiert sind
Der Nutzer erwartet eine Liste der Geschäfte, die nach Entfernung sortiert ist. Erstellen Sie für jede Filiale einen Eintrag in der Seitenleiste. Verwenden Sie dazu die Liste, die von der Funktion calculateDistances
geändert wurde, um die Reihenfolge der Filialen festzulegen.
Fügen Sie am Ende von app.js
zwei neue Funktionen mit den Namen renderStoresPanel()
und storeToPanelRow()
hinzu.
app.js
function renderStoresPanel() {
const panel = document.getElementById("panel");
if (stores.length == 0) {
panel.classList.remove("open");
return;
}
// Clear the previous panel rows
while (panel.lastChild) {
panel.removeChild(panel.lastChild);
}
stores
.sort((a, b) => a.properties.distanceValue - b.properties.distanceValue)
.forEach((store) => {
panel.appendChild(storeToPanelRow(store));
});
// Open the panel
panel.classList.add("open");
return;
}
const storeToPanelRow = (store) => {
// Add store details with text formatting
const rowElement = document.createElement("div");
const nameElement = document.createElement("p");
nameElement.classList.add("place");
nameElement.textContent = store.properties.business_name;
rowElement.appendChild(nameElement);
const distanceTextElement = document.createElement("p");
distanceTextElement.classList.add("distanceText");
distanceTextElement.textContent = store.properties.distanceText;
rowElement.appendChild(distanceTextElement);
return rowElement;
};
Starten Sie den Server neu und aktualisieren Sie die Vorschau mit dem folgenden Befehl.
go run *.go
Geben Sie schließlich eine Adresse in Austin, Texas, in die Suchleiste für die automatische Vervollständigung ein und klicken Sie auf einen der Vorschläge.
Die Karte sollte auf diese Adresse zentriert werden und in der Seitenleiste sollten die Geschäftsstandorte nach Entfernung von der ausgewählten Adresse aufgelistet werden. Ein Beispiel ist unten zu sehen:
10. Karte gestalten
Eine wirkungsvolle Möglichkeit, Ihre Karte visuell hervorzuheben, besteht darin, ihr ein Styling zu geben. Mit ihm wird die Anpassung Ihrer Karten über die Cloud Console mithilfe von cloudbasierten Kartenstilen (Beta) gesteuert. Wenn Sie Ihre Karte lieber mit einer Funktion gestalten möchten, die sich nicht in der Betaphase befindet, können Sie die Dokumentation zum Kartenstil verwenden, um JSON für die programmatische Gestaltung der Karte zu generieren. Die Anleitung unten führt Sie durch das cloudbasierte Gestalten von Karteninhalten (Beta).
Karten-ID erstellen
Öffnen Sie zuerst die Cloud Console und geben Sie in das Suchfeld „Kartenverwaltung“ ein. Klicken Sie auf das Ergebnis Kartenverwaltung (Google Maps).
Oben auf der Seite, direkt unter dem Suchfeld, sehen Sie die Schaltfläche Neue Karten-ID erstellen. Klicken Sie darauf und geben Sie den gewünschten Namen ein. Wählen Sie als Kartentyp JavaScript aus. Wenn weitere Optionen angezeigt werden, wählen Sie Vektor aus der Liste aus. Das Endergebnis sollte in etwa so aussehen wie auf dem Bild unten.
Klicken Sie auf „Weiter“, um eine neue Karten-ID zu erhalten. Sie können sie jetzt kopieren, wenn Sie möchten. Sie lässt sich aber auch später ganz einfach aufrufen.
Als Nächstes erstellen wir einen Stil, der auf diese Karte angewendet werden soll.
Kartenstil erstellen
Wenn Sie sich noch im Bereich „Maps“ der Cloud Console befinden, klicken Sie links im Navigationsmenü unten auf „Kartenstile“. Andernfalls können Sie wie beim Erstellen einer Karten-ID die richtige Seite aufrufen, indem Sie „Kartenstile“ in das Suchfeld eingeben und in den Ergebnissen „Kartenstile (Google Maps)“ auswählen, wie im Bild unten.
Klicken Sie dann oben auf die Schaltfläche + Neuen Kartenstil erstellen.
- Wenn Sie den Stil der Karte in diesem Lab übernehmen möchten, klicken Sie auf den Tab JSON IMPORTIEREN und fügen Sie den folgenden JSON-Blob ein. Wenn Sie einen eigenen Stil erstellen möchten, wählen Sie den Kartenstil aus, mit dem Sie beginnen möchten. Klicken Sie anschließend auf Weiter.
- Wählen Sie die gerade erstellte Karten-ID aus, um sie mit diesem Stil zu verknüpfen, und klicken Sie noch einmal auf Weiter.
- An dieser Stelle haben Sie die Möglichkeit, den Stil Ihrer Karte weiter anzupassen. Wenn Sie das ausprobieren möchten, klicken Sie auf Im Stileditor anpassen und experimentieren Sie mit den Farben und Optionen, bis Sie einen Kartenstil gefunden haben, der Ihnen gefällt. Klicken Sie andernfalls auf Überspringen.
- Geben Sie im nächsten Schritt den Namen und die Beschreibung des Stils ein und klicken Sie auf Speichern und veröffentlichen.
Hier ist ein optionaler JSON-Blob, der im ersten Schritt importiert werden kann.
[
{
"elementType": "geometry",
"stylers": [
{
"color": "#d6d2c4"
}
]
},
{
"elementType": "labels.icon",
"stylers": [
{
"visibility": "off"
}
]
},
{
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#616161"
}
]
},
{
"elementType": "labels.text.stroke",
"stylers": [
{
"color": "#f5f5f5"
}
]
},
{
"featureType": "administrative.land_parcel",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#bdbdbd"
}
]
},
{
"featureType": "landscape.man_made",
"elementType": "geometry.fill",
"stylers": [
{
"color": "#c0baa5"
},
{
"visibility": "on"
}
]
},
{
"featureType": "landscape.man_made",
"elementType": "geometry.stroke",
"stylers": [
{
"color": "#9cadb7"
},
{
"visibility": "on"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#757575"
}
]
},
{
"featureType": "poi.park",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
},
{
"featureType": "road",
"elementType": "geometry",
"stylers": [
{
"color": "#ffffff"
}
]
},
{
"featureType": "road.arterial",
"elementType": "geometry",
"stylers": [
{
"weight": 1
}
]
},
{
"featureType": "road.arterial",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#757575"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry",
"stylers": [
{
"color": "#bf5700"
}
]
},
{
"featureType": "road.highway",
"elementType": "geometry.stroke",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "road.highway",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#616161"
}
]
},
{
"featureType": "road.local",
"elementType": "geometry",
"stylers": [
{
"weight": 0.5
}
]
},
{
"featureType": "road.local",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
},
{
"featureType": "transit.line",
"elementType": "geometry",
"stylers": [
{
"color": "#e5e5e5"
}
]
},
{
"featureType": "transit.station",
"elementType": "geometry",
"stylers": [
{
"color": "#eeeeee"
}
]
},
{
"featureType": "water",
"elementType": "geometry",
"stylers": [
{
"color": "#333f48"
}
]
},
{
"featureType": "water",
"elementType": "labels.text.fill",
"stylers": [
{
"color": "#9e9e9e"
}
]
}
]
Karten-ID in Code einfügen
Nachdem Sie sich die Mühe gemacht haben, diesen Kartenstil zu erstellen, stellt sich die Frage, wie Sie ihn in Ihrer eigenen Karte verwenden können. Sie müssen zwei kleine Änderungen vornehmen:
- Fügen Sie die Karten-ID als URL-Parameter zum Skript-Tag in
index.html
hinzu. Add
die Karten-ID als Konstruktorargument, wenn Sie die Karte in IhrerinitMap()
-Methode erstellen.
Ersetzen Sie das Script-Tag, mit dem die Maps JavaScript API in der HTML-Datei geladen wird, durch die folgende Loader-URL und ersetzen Sie die Platzhalter für „YOUR_API_KEY
“ und „YOUR_MAP_ID
“:
index.html
...
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=weekly&libraries=places&callback=initialize&map_ids=YOUR_MAP_ID&solution_channel=GMP_codelabs_fullstackstorelocator_v1_a">
</script>
...
Entfernen Sie in der Methode initMap
von app.js
, in der die Konstante map
definiert ist, die Auskommentierung der Zeile für die Eigenschaft mapId
und ersetzen Sie „YOUR_MAP_ID_HERE
“ durch die gerade erstellte Karten-ID:
app.js – initMap
...
// The map, centered on Austin, TX
const map = new google.maps.Map(document.querySelector('#map'), {
center: austin,
zoom: 14,
mapId: 'YOUR_MAP_ID_HERE',
// ...
});
...
Starten Sie den Server neu.
go run *.go
Nach dem Aktualisieren der Vorschau sollte die Karte entsprechend Ihren Einstellungen dargestellt werden. Hier sehen Sie ein Beispiel für die Verwendung des oben genannten JSON-Stils.
11. Für die Produktion bereitstellen
Wenn Sie Ihre App in App Engine Flex ausführen möchten (und nicht nur auf einem lokalen Webserver auf Ihrem Entwicklungscomputer oder in Cloud Shell, wie Sie es bisher getan haben), ist das ganz einfach. Wir müssen nur noch ein paar Dinge hinzufügen, damit der Datenbankzugriff in der Produktionsumgebung funktioniert. Dies ist alles auf der Dokumentationsseite Verbindung von der flexiblen App Engine-Umgebung zu Cloud SQL herstellen beschrieben.
Umgebungsvariablen zur Datei „app.yaml“ hinzufügen
Zuerst müssen alle Umgebungsvariablen, die Sie für lokale Tests verwendet haben, am Ende der app.yaml
-Datei Ihrer Anwendung hinzugefügt werden.
- Rufen Sie https://console.cloud.google.com/sql/instances/locations/overview auf, um den Namen der Instanzverbindung zu ermitteln.
- Fügen Sie den folgenden Code am Ende von
app.yaml
ein. - Ersetzen Sie
YOUR_DB_PASSWORD_HERE
durch das Passwort, das Sie zuvor für den Nutzernamenpostgres
erstellt haben. - Ersetzen Sie
YOUR_CONNECTION_NAME_HERE
durch den Wert aus Schritt 1.
app.yaml
# ...
# Set environment variables
env_variables:
DB_USER: postgres
DB_PASS: YOUR_DB_PASSWORD_HERE
DB_NAME: postgres
DB_TCP_HOST: 172.17.0.1
DB_PORT: 5432
#Enable TCP Port
# You can look up your instance connection name by going to the page for
# your instance in the Cloud Console here : https://console.cloud.google.com/sql/instances/
beta_settings:
cloud_sql_instances: YOUR_CONNECTION_NAME_HERE=tcp:5432
Der DB_TCP_HOST
sollte den Wert 172.17.0.1 haben, da diese App über App Engine Flex**.** verbunden ist. Das liegt daran, dass die Kommunikation mit Cloud SQL über einen Proxy erfolgt, ähnlich wie bei Ihnen.
SQL-Clientberechtigungen dem App Engine Flexible-Dienstkonto hinzufügen
Rufen Sie in der Cloud Console die IAM-Admin-Seite auf und suchen Sie nach einem Dienstkonto, dessen Name dem Format service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
entspricht. Dies ist das Dienstkonto, das von App Engine Flex für die Verbindung zur Datenbank verwendet wird. Klicken Sie am Ende der Zeile auf die Schaltfläche Bearbeiten und fügen Sie die Rolle Cloud SQL-Client hinzu.
Projektcode in den Go-Pfad kopieren
Damit App Engine Ihren Code ausführen kann, müssen relevante Dateien im Go-Pfad gefunden werden können. Achten Sie darauf, dass Sie sich im Stammverzeichnis Ihres Projekts befinden.
cd YOUR_PROJECT_ROOT
Kopieren Sie das Verzeichnis in den Go-Pfad.
mkdir -p ~/gopath/src/austin-recycling cp -r ./ ~/gopath/src/austin-recycling
Wechseln Sie in dieses Verzeichnis.
cd ~/gopath/src/austin-recycling
App bereitstellen
Stellen Sie Ihre App mit dem gcloud
-CLI bereit. Die Bereitstellung kann einige Zeit in Anspruch nehmen.
gcloud app deploy
Mit dem Befehl browse
erhalten Sie einen Link, über den Sie sich Ihren vollständig bereitgestellten, professionellen und ästhetisch ansprechenden Store Locator in Aktion ansehen können.
gcloud app browse
Wenn Sie gcloud
außerhalb der Cloud Shell ausgeführt haben, wird durch die Ausführung von gcloud app browse
ein neuer Browsertab geöffnet.
12. (Empfohlen) Bereinigen
Wenn Sie dieses Codelab durchführen, bleiben Sie innerhalb der Kontingentlimits für die BigQuery-Verarbeitung und die Maps Platform API-Aufrufe. Wenn Sie es jedoch nur zu Lernzwecken durchgeführt haben und zukünftige Gebühren vermeiden möchten, ist es am einfachsten, das Projekt selbst zu löschen, um die mit diesem Projekt verknüpften Ressourcen zu löschen.
Projekt löschen
Rufen Sie in der GCP Console die Seite Cloud Resource Manager auf:
Wählen Sie in der Projektliste das Projekt aus, in dem wir gearbeitet haben, und klicken Sie auf Löschen. Sie werden aufgefordert, die Projekt-ID einzugeben. Geben Sie sie ein und klicken Sie auf Herunterfahren.
Alternativ können Sie das gesamte Projekt direkt über Cloud Shell mit gcloud
löschen. Führen Sie dazu den folgenden Befehl aus und ersetzen Sie den Platzhalter GOOGLE_CLOUD_PROJECT
durch Ihre Projekt-ID:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. Glückwunsch
Glückwunsch! Sie haben das Codelab erfolgreich abgeschlossen.
Oder Sie haben die letzte Seite überflogen. Glückwunsch! Sie haben die letzte Seite erreicht.
In diesem Codelab haben Sie mit den folgenden Technologien gearbeitet:
- Maps JavaScript API
- „Distance Matrix“-Dienst, Maps JavaScript API (es gibt auch die Distance Matrix API)
- Places Library, Maps JavaScript API (auch Places API)
- Flexible App Engine-Umgebung (Go)
- Cloud SQL API
Weiterführende Literatur
Es gibt noch viel über diese Technologien zu lernen. Unten finden Sie einige hilfreiche Links zu Themen, die wir in diesem Codelab nicht behandeln konnten, die aber sicherlich nützlich sein können, wenn Sie eine Lösung für die Standortsuche entwickeln, die Ihren spezifischen Anforderungen entspricht.