1. Pengantar
Abstrak
Bayangkan Anda memiliki banyak tempat untuk diletakkan di peta dan Anda ingin pengguna dapat melihat lokasi tempat tersebut dan mengidentifikasi tempat yang ingin mereka kunjungi. Contoh umumnya mencakup:
- pencari toko di situs retailer
- peta lokasi pemungutan suara untuk pemilu mendatang
- direktori lokasi khusus seperti tempat daur ulang baterai
Yang akan Anda buat
Di codelab ini, Anda akan membuat pencari lokasi (locator) yang didapat dari feed data langsung yang berisi lokasi khusus dan membantu pengguna menemukan lokasi paling dekat dengan titik awal mereka. Pencari lokasi full-stack dapat menangani jumlah tempat yang lebih besar dibandingkan pencari toko sederhana, yang dibatasi hingga maksimal 25 lokasi toko.
Yang akan Anda pelajari
Codelab ini menggunakan set data terbuka untuk menyimulasikan metadata tentang lokasi toko dalam jumlah besar yang diisi otomatis, sehingga Anda dapat fokus mempelajari konsep teknis utama.
- Maps JavaScript API: menampilkan lokasi dalam jumlah besar pada peta web yang disesuaikan
- GeoJSON: format yang menyimpan metadata tentang lokasi
- Place Autocomplete: membantu pengguna memberikan lokasi awal dengan lebih cepat dan akurat
- Go: Bahasa pemrograman yang digunakan untuk mengembangkan back-end aplikasi. Backend akan berinteraksi dengan database dan mengirimkan hasil kueri kembali ke front-end dalam JSON yang diformat.
- App Engine: untuk menghosting aplikasi web
Prasyarat
- Pengetahuan dasar tentang HTML dan JavaScript
- Akun Google
2. Memulai persiapan
Pada Langkah 3 di bagian berikut, aktifkan Maps JavaScript API, Places API, dan Distance Matrix API untuk codelab ini.
Memulai Google Maps Platform
Jika Anda belum pernah menggunakan Google Maps Platform, ikuti panduan Memulai Google Maps Platform atau tonton playlist Memulai Google Maps Platform untuk menyelesaikan langkah-langkah berikut:
- Membuat akun penagihan.
- Membuat project.
- Mengaktifkan API dan SDK Google Maps Platform (tercantum di bagian sebelumnya).
- Membuat kunci API.
Mengaktifkan Cloud Shell
Di codelab ini, Anda menggunakan Cloud Shell, lingkungan command line yang berjalan di Google Cloud yang menyediakan akses ke produk dan resource yang berjalan di Google Cloud, sehingga Anda dapat menghosting dan menjalankan project sepenuhnya dari browser web.
Untuk mengaktifkan Cloud Shell dari Cloud Console, klik Aktifkan Cloud Shell (hanya perlu waktu beberapa saat untuk penyediaan dan koneksi ke lingkungan).
Tindakan ini akan membuka shell baru di bagian bawah browser setelah kemungkinan menampilkan interstisial pengantar.
Mengonfirmasi project Anda
Setelah terhubung ke Cloud Shell, Anda akan melihat bahwa Anda telah diautentikasi dan project tersebut telah ditetapkan ke project ID yang Anda pilih selama penyiapan.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
Jika project belum ditetapkan karena beberapa alasan, jalankan perintah berikut:
gcloud config set project <YOUR_PROJECT_ID>
Mengaktifkan App Engine Flex API
App Engine Flex API harus diaktifkan secara manual dari Cloud Console. Tindakan ini tidak hanya akan mengaktifkan API, namun juga membuat Akun Layanan Lingkungan Fleksibel App Engine, akun yang diautentikasi yang akan berinteraksi dengan layanan Google (seperti database SQL) atas nama pengguna.
3. Halo, Dunia
Backend: Halo Dunia dalam Go
Pada instance Cloud Shell, Anda akan memulai dengan membuat aplikasi Fleksibel App Engine Go yang akan berfungsi sebagai dasar untuk codelab lainnya.
Di toolbar Cloud Shell, klik tombol Open editor untuk membuka editor kode di tab baru. Editor kode berbasis web ini memungkinkan Anda mengedit file dengan mudah di instance Cloud Shell.
Selanjutnya, klik ikon Buka di jendela baru untuk memindahkan editor dan terminal ke tab baru.
Pada terminal di bagian bawah tab baru, buat direktori austin-recycling
baru.
mkdir -p austin-recycling && cd $_
Selanjutnya, Anda akan membuat aplikasi Go App Engine kecil untuk memastikan semuanya berfungsi. Halo Dunia!
Direktori austin-recycling
juga akan muncul dalam daftar folder Editor di sebelah kiri. Di direktori austin-recycling
, buat file dengan nama app.yaml
. Masukkan konten berikut dalam file app.yaml
:
app.yaml
runtime: go
env: flex
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
File konfigurasi ini mengonfigurasi aplikasi App Engine Anda untuk menggunakan runtime Fleksibel Go. Untuk informasi latar belakang tentang arti item konfigurasi dalam file ini, lihat Dokumentasi Lingkungan Standar Google App Engine Go.
Selanjutnya, buat file main.go
di samping file app.yaml
:
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!")
}
Sebaiknya, kita berhenti sebentar di sini untuk memahami fungsi kode ini, setidaknya di tingkat tinggi. Anda telah menetapkan paket main
yang memulai server http yang memproses port 8080, dan mendaftarkan fungsi pengendali untuk permintaan HTTP yang cocok dengan jalur "/"
.
Fungsi pengendali, yang disebut handler
, menulis string teks "Hello, world!"
. Teks ini akan direlai kembali ke browser Anda, tempat Anda dapat membacanya. Di langkah selanjutnya, Anda akan membuat pengendali yang merespons dengan data GeoJSON, dan bukan string hard-code sederhana.
Setelah melakukan langkah-langkah ini, sekarang Anda akan memiliki editor yang terlihat seperti ini:
Melakukan pengujian
Untuk menguji aplikasi ini, Anda dapat menjalankan server pengembangan App Engine di dalam instance Cloud Shell. Kembali ke command line Cloud Shell, lalu ketik:
go run *.go
Anda akan melihat beberapa baris log output yang menunjukkan bahwa Anda benar-benar menjalankan server pengembangan pada instance Cloud Shell, dengan aplikasi web halo dunia yang memproses port localhost 8080. Anda dapat membuka tab browser web di aplikasi ini dengan menekan tombol Web Preview dan memilih item menu Preview on port 8080 di toolbar Cloud Shell.
Mengklik item menu ini akan membuka tab baru di browser web Anda dengan kata "Hello, world!" yang ditayangkan dari server pengembangan App Engine.
Pada langkah berikutnya, Anda akan menambahkan data daur ulang Kota Austin ke aplikasi ini, dan mulai memvisualisasikannya.
4. Mendapatkan data saat ini
GeoJSON, lingua franca di dunia GIS
Di langkah sebelumnya disebutkan bahwa Anda akan membuat pengendali di kode Go yang merender data GeoJSON ke browser web. Namun, apa yang dimaksud dengan GeoJSON?
Dalam dunia Sistem Informasi Geografis (GIS), kita harus dapat menyampaikan pengetahuan tentang entitas geografis di antara sistem komputer. Peta sangat mudah dibaca oleh manusia, namun komputer biasanya lebih memilih datanya dalam format yang lebih mudah dicerna.
GeoJSON adalah format untuk mengenkode struktur data geografis, seperti koordinat lokasi pembuangan sampah daur ulang di Austin, Texas. GeoJSON telah distandardisasi dalam standar Internet Engineering Task Force yang disebut RFC7946. GeoJSON didefinisikan dalam konteks JSON, JavaScript Object Notation, yang telah distandardisasi dalam ECMA-404, oleh organisasi yang sama yang menetapkan standar JavaScript, yaitu Ecma International.
Yang penting adalah GeoJSON merupakan format kawat yang didukung secara luas untuk menyampaikan pengetahuan geografis. Codelab ini menggunakan GeoJSON dalam cara berikut:
- Menggunakan paket Go untuk mengurai data Austin ke dalam struktur data khusus GIS internal yang akan Anda gunakan untuk memfilter data yang diminta.
- Melakukan serialisasi data yang diminta untuk transit antara server web dan browser web.
- Menggunakan library JavaScript untuk mengonversi respons menjadi penanda pada peta.
Ini akan mengurangi secara signifikan jumlah pengetikan kode, karena Anda tidak perlu menulis parser dan generator untuk mengonversi aliran data pada kawat menjadi representasi dalam memori.
Mengambil data
Portal Data Terbuka Kota Austin, Texas menyediakan informasi geospasial tentang resource publik yang tersedia untuk digunakan oleh masyarakat. Di codelab ini, Anda akan memvisualisasikan set data lokasi pembuangan sampah daur ulang.
Anda akan memvisualisasikan data dengan penanda pada peta, yang dirender menggunakan Lapisan data Maps JavaScript API.
Mulailah dengan mendownload data GeoJSON dari situs Kota Austin di aplikasi Anda.
- Di jendela command line instance Cloud Shell, matikan server dengan mengetik [CTRL] + [C].
- Buat direktori
data
di dalam direktoriaustin-recycling
, lalu ubah ke direktori tersebut:
mkdir -p data && cd data
Sekarang gunakan curl untuk mengambil lokasi daur ulang:
curl "https://data.austintexas.gov/resource/qzi7-nx8g.geojson" -o recycling-locations.geojson
Terakhir, ubah kembali ke direktori induk.
cd ..
5. Memetakan lokasi
Pertama, update file app.yaml
untuk mencerminkan aplikasi lebih kokoh yang akan Anda buat, dan "bukan sekadar aplikasi halo dunia yang biasa".
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
Konfigurasi app.yaml
ini mengarahkan permintaan untuk /
, /*.js
, /*.css
, dan /*.html
ke kumpulan file statis. Ini berarti bahwa komponen HTML statis aplikasi Anda akan ditayangkan langsung oleh infrastruktur penayangan file App Engine, dan bukan aplikasi Go. Ini akan mengurangi beban server dan meningkatkan kecepatan penayangan.
Sekarang saatnya membuat backend aplikasi Anda di Go.
Membuat backend
Anda mungkin telah memperhatikan bahwa satu hal menarik yang tidak dilakukan file app.yaml
adalah mengekspos file GeoJSON. Hal itu karena GeoJSON akan diproses dan dikirim oleh backend Go, sehingga kita dapat membuat beberapa fitur menarik pada langkah-langkah berikutnya. Ubah file main.go
Anda agar terbaca sebagai berikut:
main.go
package main
import (
"fmt"
"io/ioutil"
"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 := ioutil.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"])
}
Backend Go sudah memberi kita fitur yang berharga: Instance AppEngine menyimpan di cache semua lokasi tersebut segera setelah dimulai. Ini akan menghemat waktu karena backend tidak perlu membaca file dari disk setiap kali pengguna melakukan refresh.
Membuat frontend
Hal pertama yang perlu dilakukan adalah membuat folder untuk menyimpan semua aset statis kita. Dari folder induk project Anda, buat folder static
.
mkdir -p static && cd static
Kita akan membuat 3 file dalam folder ini.
index.html
akan berisi semua HTML untuk aplikasi pencari toko satu halaman Anda.style.css
akan berisi gaya visual, seperti yang Anda harapkanapp.js
akan bertanggung jawab untuk mengambil GeoJSON, melakukan panggilan ke Maps API, dan menempatkan penanda di peta kustom Anda.
Buat 3 file ini, dan pastikan untuk meletakkan semuanya di static/
.
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>
Berikan perhatian khusus pada URL src
dalam tag skrip di elemen head
.
- Ganti teks placeholder "
YOUR_API_KEY
" dengan kunci API yang Anda buat selama langkah penyiapan. Anda dapat membuka halaman APIs & Services -> Credentials di Cloud Console untuk mengambil kunci API atau membuat kunci API yang baru. - Perhatikan bahwa URL berisi parameter
callback=initialize.
Sekarang kita akan membuat file JavaScript yang berisi fungsi callback tersebut. Ini adalah tempat aplikasi Anda akan memuat lokasi dari backend, mengirimkannya ke Maps API, dan menggunakan hasilnya untuk menandai lokasi kustom di peta, semua dirender dengan sempurna ke halaman web Anda. - Parameter
libraries=places
memuat Places Library, yang diperlukan untuk fitur seperti pelengkapan otomatis alamat yang akan ditambahkan nanti.
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;
};
Kode ini merender lokasi toko pada peta. Untuk menguji apa yang sudah kita miliki sejauh ini, dari command line, kembali ke direktori induk:
cd ..
Sekarang, jalankan aplikasi Anda dalam mode pengembangan lagi menggunakan:
go run *.go
Lihat pratinjaunya seperti yang Anda lakukan sebelumnya. Anda akan melihat peta dengan lingkaran hijau kecil seperti ini.
Anda sudah merender lokasi peta, dan kita baru setengah jalan menyelesaikan codelab. Luar biasa. Sekarang, mari kita tambahkan beberapa interaktivitas.
6. Menampilkan detail sesuai permintaan
Merespons peristiwa klik pada penanda peta
Menampilkan sekumpulan penanda pada peta adalah awal yang bagus, namun kita benar-benar menginginkan pengunjung dapat mengklik salah satu penanda dan melihat informasi tentang lokasi tersebut (seperti nama bisnis, alamat, dsb). Nama jendela informasi kecil yang biasanya pop-up saat Anda mengklik penanda Google Maps adalah Jendela Info.
Buat objek infoWindow. Tambahkan hal berikut ke fungsi initialize
, menggantikan baris komentar yang bertuliskan "// 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();
Ganti definisi fungsi fetchAndRenderStores
dengan versi yang sedikit berbeda ini, yang mengubah baris terakhir untuk memanggil storeToCircle
dengan argumen tambahan, infowindow
:
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));
};
Ganti definisi storeToCircle
dengan versi yang sedikit lebih panjang ini, yang sekarang menggunakan Jendela Info sebagai argumen ketiga:
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;
};
Kode baru di atas menampilkan infoWindow
dengan informasi toko yang dipilih setiap kali penanda toko pada peta diklik.
Jika server Anda masih berjalan, hentikan dan mulai ulang. Muat ulang halaman peta Anda dan coba klik penanda peta. Jendela info kecil akan pop-up dengan nama dan alamat bisnis, yang terlihat seperti ini:
7. Mendapatkan lokasi awal pengguna
Pengguna pencari toko biasanya ingin mengetahui toko mana yang paling dekat dengan posisi mereka atau alamat tempat mereka berencana memulai perjalanan. Tambahkan kotak penelusuran Place Autocomplete agar pengguna dapat memasukkan alamat titik awal dengan mudah. Place Autocomplete menyediakan fungsi prediksi sebelum mengetik yang mirip dengan cara kerja Autocomplete di kotak penelusuran Google lainnya, dengan pengecualian prediksinya adalah semua Tempat di Google Maps Platform.
Membuat kolom input pengguna
Kembali untuk mengedit style.css
guna menambahkan gaya visual untuk kotak penelusuran Autocomplete dan panel samping hasil yang terkait. Saat memperbarui gaya CSS, kita juga akan menambahkan gaya untuk sidebar berikutnya yang menampilkan informasi toko sebagai daftar untuk melengkapi peta.
Tambahkan kode ini ke bagian akhir file.
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;
}
Kotak penelusuran Autocomplete dan panel slideout awalnya tersembunyi hingga diperlukan.
Siapkan div untuk widget Autocomplete dengan mengganti komentar di index.html yang bertuliskan "<!-- Autocomplete div goes here -->
" dengan kode berikut. Saat melakukan pengeditan ini, kita juga akan menambahkan div untuk panel slideout.
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>
Sekarang, tentukan fungsi untuk menambahkan widget Autocomplete ke peta dengan menambahkan kode berikut ke akhir app.js
.
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
};
Kode ini membatasi saran Autocomplete untuk hanya menampilkan alamat (karena Place Autocomplete dapat mencocokkan nama bangunan dan lokasi administratif) dan membatasi alamat yang ditampilkan hanya kepada orang yang berada di AS. Menambahkan spesifikasi opsional ini akan mengurangi jumlah karakter yang harus dimasukkan pengguna untuk mempersempit prediksi agar dapat menampilkan alamat yang mereka cari.
Kemudian, kode ini memindahkan div
Autocomplete yang telah Anda buat ke sudut kanan atas peta dan menentukan kolom mana yang harus ditampilkan tentang setiap Tempat dalam respons.
Terakhir, panggil fungsi initAutocompleteWidget
di akhir fungsi initialize
, menggantikan komentar yang bertuliskan "// TODO: Initialize the Autocomplete widget
".
app.js - initialize
// Initialize the Places Autocomplete Widget
initAutocompleteWidget();
Mulai ulang server Anda dengan menjalankan perintah berikut, lalu muat ulang pratinjau.
go run *.go
Anda akan melihat widget Autocomplete di sudut kanan atas peta, yang menunjukkan alamat di AS yang cocok dengan teks yang Anda ketik, yang dicondongkan ke area yang terlihat di peta.
Mengupdate Peta Saat Pengguna Memilih Alamat Awal
Sekarang, Anda perlu mengatur kapan pengguna memilih prediksi dari widget Autocomplete dan menggunakan lokasi tersebut sebagai dasar untuk menghitung jarak ke toko Anda.
Tambahkan kode berikut ke akhir initAutocompleteWidget
di app.js
, menggantikan komentar "// 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
});
Kode ini menambahkan pemroses sehingga saat pengguna mengklik salah satu saran, peta akan kembali ke pusat di alamat yang dipilih dan menetapkan tempat asal sebagai dasar penghitungan jarak Anda. Anda menerapkan penghitungan jarak pada langkah berikutnya.
Hentikan dan mulai ulang server Anda, lalu muat ulang pratinjau untuk mengamati pemusatan kembali peta setelah Anda memasukkan alamat ke dalam kotak penelusuran pelengkapan otomatis.
8. Menskalakan dengan Cloud SQL
Sejauh ini, kita memiliki pencari toko yang cukup andal. Aplikasi ini memanfaatkan fakta bahwa hanya ada sekitar seratus lokasi yang akan digunakan aplikasi, dengan memuatnya ke memori di backend (bukannya membaca dari file berulang kali.). Namun, bagaimana jika pencari lokasi harus beroperasi pada skala yang berbeda? Jika Anda memiliki ratusan lokasi yang tersebar di sekeliling area geografis yang luas (atau ribuan lokasi di seluruh dunia), menyimpan semua lokasi tersebut dalam memori bukan lagi ide terbaik, dan membagi zona menjadi file individual akan menimbulkan masalah tersendiri.
Kini saatnya untuk memuat lokasi Anda dari database. Untuk langkah ini, kita akan memigrasikan semua lokasi di file GeoJSON ke dalam database Cloud SQL, dan mengupdate backend Go untuk mengambil hasil dari database tersebut, bukan dari cache lokalnya setiap kali permintaan masuk.
Membuat Instance Cloud SQL dengan Database PostGres
Anda dapat membuat instance Cloud SQL melalui Google Cloud Console, tetapi akan lebih mudah menggunakan utilitas gcloud
untuk membuat instance dari command line. Di Cloud Shell, buat instance Cloud SQL dengan perintah berikut:
gcloud sql instances create locations \ --database-version=POSTGRES_12 \ --tier=db-custom-1-3840 --region=us-central1
- Argumen
locations
adalah nama yang kami pilih untuk diberikan pada instance Cloud SQL ini. - Tanda
tier
adalah cara untuk memilih dari beberapa komputer yang telah ditentukan dengan mudah. - Nilai
db-custom-1-3840
menunjukkan instance yang dibuat harus memiliki satu vCPU dan memori sekitar 3,75 GB.
Instance Cloud SQL akan dibuat dan diinisialisasi dengan database PostGresSQL, dengan pengguna default postgres
. Apa sandi pengguna ini? Pertanyaan bagus. Pengguna belum memiliki sandi. Anda perlu mengonfigurasikannya sebelum login.
Tetapkan sandi dengan perintah berikut:
gcloud sql users set-password postgres \ --instance=locations --prompt-for-password
Lalu masukkan sandi yang Anda pilih saat diminta untuk melakukannya.
Mengaktifkan Ekstensi PostGIS
PostGIS adalah ekstensi untuk PostGresSQL yang memudahkan penyimpanan jenis data geospasial standar. Dalam keadaan normal, kita harus melalui seluruh proses penginstalan untuk menambahkan PostGIS ke database. Untungnya, ini adalah salah satu ekstensi yang didukung Cloud SQL untuk PostGresSQL.
Hubungkan ke instance database dengan login sebagai pengguna postgres
lewat perintah berikut di terminal Cloud Shell.
gcloud sql connect locations --user=postgres --quiet
Masukkan sandi yang baru saja Anda buat. Sekarang, tambahkan ekstensi PostGIS pada command prompt postgres=>
.
CREATE EXTENSION postgis;
Jika berhasil, output akan menampilkan CREATE EXTENSION, seperti yang ditunjukkan di bawah.
Contoh output perintah
CREATE EXTENSION
Terakhir, keluar dari koneksi database dengan memasukkan perintah keluar pada command prompt postgres=>
.
\q
Mengimpor Data Geografi ke dalam Database
Sekarang, kita perlu mengimpor semua data lokasi tersebut dari file GeoJSON ke dalam database baru.
Untungnya, ini adalah masalah yang umum dan ada beberapa alat yang dapat ditemukan di internet untuk mengotomatiskannya untuk Anda. Kita akan menggunakan alat yang disebut ogr2ogr yang melakukan konversi di antara beberapa format umum untuk menyimpan data geospasial. Di antara opsi tersebut adalah mengonversi dari GeoJSON ke file dump SQL, persis seperti dugaan Anda. Selanjutnya, file dump SQL dapat digunakan untuk membuat tabel & kolom untuk database tersebut, dan memuatnya dengan semua data yang ada di file GeoJSON Anda.
Membuat file dump SQL
Pertama, instal ogr2ogr.
sudo apt-get install gdal-bin
Lalu, gunakan ogr2ogr untuk membuat file dump SQL. File ini akan membuat tabel yang disebut austinrecycling
.
ogr2ogr --config PG_USE_COPY YES -f PGDump datadump.sql \ data/recycling-locations.geojson -nln austinrecycling
Perintah di atas didasarkan pada pengoperasian dari folder austin-recycling
. Jika Anda perlu menjalankannya dari direktori lain, ganti data
dengan jalur ke direktori tempat recycling-locations.geojson
disimpan.
Mengisi database Anda dengan lokasi daur ulang
Setelah menyelesaikan perintah terakhir tersebut, sekarang Anda memiliki sebuah file, datadump.sql,
di direktori yang sama tempat Anda menjalankan perintah. Jika dibuka, Anda akan melihat seratus lebih baris SQL, yang membuat tabel austinrecycling
dan mengisinya dengan lokasi.
Sekarang, buka koneksi ke database dan jalankan skrip tersebut dengan perintah berikut.
gcloud sql connect locations --user=postgres --quiet < datadump.sql
Jika skrip berhasil dijalankan, berikut adalah bentuk tampilan beberapa baris terakhir output:
Contoh output perintah
ALTER TABLE ALTER TABLE ATLER TABLE ALTER TABLE COPY 103 COMMIT WARNING: there is no transaction in progress COMMIT
Mengupdate backend Go untuk menggunakan Cloud SQL
Setelah kita memiliki semua data ini di database, kini saatnya mengupdate kode.
Mengupdate frontend untuk mengirim informasi lokasi
Mari kita mulai dengan satu update kecil pada front-end: Karena sekarang kita menulis aplikasi ini untuk skala terbatas agar setiap lokasi tidak dikirim ke front-end setiap kali kueri dijalankan, maka kita perlu meneruskan beberapa informasi dasar dari front-end tentang lokasi yang diprioritaskan oleh pengguna.
Buka app.js
dan ganti definisi fungsi fetchStores
dengan versi ini untuk menyertakan lintang dan bujur yang diinginkan dalam URL.
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();
};
Setelah menyelesaikan langkah ini dalam codelab, respons hanya akan menampilkan toko yang terdekat dengan koordinat peta yang diberikan dalam parameter center
. Untuk pengambilan awal dalam fungsi initialize
, kode contoh yang diberikan di lab ini menggunakan koordinat pusat untuk Austin, Texas.
Karena sekarang fetchStores
hanya akan menampilkan subkumpulan lokasi toko, kita perlu mengambil ulang toko setiap kali pengguna mengubah lokasi awalnya.
Update fungsi initAutocompleteWidget
untuk memuat ulang lokasi setiap kali titik asal baru ditetapkan. Ini akan membutuhkan dua hasil edit:
- Dalam initAutocompleteWidget, temukan callback untuk pemroses
place_changed
. Batalkan komentar pada baris yang menghapus lingkaran yang ada, sehingga baris tersebut akan berjalan setiap kali pengguna memilih alamat dari kotak penelusuran Place Autocomplete.
app.js - initAutocompleteWidget
autocomplete.addListener("place_changed", async () => {
circles.forEach((c) => c.setMap(null)); // clear existing stores
// ...
- Setiap kali titik awal yang dipilih berubah, variabel originLocation akan diupdate. Di akhir callback "
place_changed
", hapus komentar pada baris di atas baris "// TODO: Calculate the closest stores
" untuk meneruskan titik asal baru ini untuk panggilan baru ke fungsifetchAndRenderStores
.
app.js - initAutocompleteWidget
await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
Mengupdate backend untuk menggunakan CloudSQL, bukan file JSON datar
Menghapus pembacaan dan penyimpanan file GeoJSON datar dalam cache
Pertama, ubah main.go
untuk menghapus kode yang memuat dan menyimpan file GeoJSON datar dalam cache. Kita juga dapat menghapus fungsi dropoffsHandler
, karena kita akan menulis fungsi yang didukung Cloud SQL dalam file yang berbeda.
main.go
baru Anda akan menjadi jauh lebih singkat.
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)
}
}
Membuat pengendali baru untuk Permintaan Lokasi
Sekarang, mari kita buat file lain, locations.go
, juga di direktori austin-recycling. Mulai dengan mengimplementasikan kembali pengendali untuk permintaan lokasi.
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)
}
Pengendali akan melakukan tugas penting berikut:
- Mengambil lintang dan bujur dari objek permintaan (Ingat cara kita menambahkannya ke URL? )
- Mengaktifkan panggilan
getGeoJsonFromDatabase
, yang menampilkan string GeoJSON (Kita akan menulisnya nanti.) - Menggunakan
ResponseWriter
untuk mencetak string GeoJSON tersebut ke respons.
Selanjutnya, kita akan membuat kumpulan koneksi untuk membantu penskalaan penggunaan database secara baik dengan pengguna simultan.
Membuat Kumpulan Koneksi
Kumpulan koneksi adalah kumpulan berisi koneksi database aktif yang dapat digunakan kembali oleh server untuk menangani permintaan pengguna. Ini akan menghapus banyak pengeluaran seiring dengan penyesuaian jumlah pengguna Anda, karena server tidak perlu menghabiskan waktu untuk membuat dan menghancurkan koneksi untuk setiap pengguna aktif. Anda mungkin telah melihat di bagian sebelumnya, kami mengimpor library github.com/jackc/pgx/stdlib.
Ini adalah library populer untuk digunakan bersama kumpulan koneksi di Go.
Di akhir locations.go
, buat fungsi initConnectionPool
(dipanggil dari main.go
) yang menginisialisasi kumpulan koneksi. Untuk lebih jelasnya, beberapa metode bantuan digunakan dalam cuplikan ini. configureConnectionPool
menyediakan tempat praktis untuk menyesuaikan setelan kumpulan seperti jumlah koneksi dan masa pakai per koneksi. mustGetEnv
menggabungkan panggilan untuk mendapatkan variabel lingkungan yang diperlukan, sehingga pesan error yang berguna dapat diberikan jika instance tidak memiliki informasi penting (seperti IP atau nama database yang akan dihubungkan).
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
}
Mengirimkan kueri database untuk lokasi, untuk mendapatkan JSON.
Sekarang, kita akan menulis kueri database yang mengambil koordinat peta dan menampilkan 25 lokasi terdekat. Tidak hanya itu, namun berkat beberapa fungsi database modern yang spesial, data tersebut akan ditampilkan sebagai GeoJSON. Hasil akhir dari semua ini adalah bahwa sejauh yang diketahui kode front-end, tidak ada yang berubah. Sebelumnya, fungsi mengaktifkan permintaan ke URL dan mendapatkan sekumpulan GeoJSON. Sekarang, fungsi mengaktifkan permintaan ke URL dan... mendapatkan sekumpulan GeoJSON.
Ini adalah fungsi yang luar biasa. Tambahkan fungsi berikut setelah pengendali dan kode penggabungan koneksi yang baru saja Anda tulis di bagian bawah locations.go
.
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
}
Fungsi ini umumnya merupakan penyiapan, pemutusan koneksi, dan penanganan error untuk mengaktifkan permintaan ke database. Mari kita lihat SQL yang sebenarnya, yang melakukan banyak hal yang sangat menarik di lapisan database, sehingga Anda tidak perlu lagi mengkhawatirkan penerapan salah satunya dalam kode.
Kueri mentah yang diaktifkan, setelah string diuraikan dan semua literal string yang disisipkan di tempat yang benar, terlihat seperti ini:
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
Kueri ini dapat dilihat sebagai satu kueri utama dan beberapa fungsi penggabungan JSON.
SELECT * ... LIMIT 25
memilih semua kolom untuk setiap lokasi. Kueri ini kemudian menggunakan fungsi ST_DISTANCE (bagian dari rangkaian PostGIS pada fungsi pengukuran geografi) untuk menentukan jarak antara setiap lokasi dalam database dan pasangan lintang/bujur lokasi yang diberikan oleh pengguna di front-end. Ingat bahwa tidak seperti Distance Matrix, yang bisa memberikan jarak tempuh, ini adalah jarak GeoSpatial. Agar efisien, kueri kemudian menggunakan jarak tersebut untuk mengurutkan dan menampilkan 25 lokasi terdekat dengan lokasi pengguna yang ditentukan.
**SELECT json_build_object(‘type', ‘F
**eature') menggabungkan kueri sebelumnya, mengambil hasilnya, dan menggunakannya untuk membuat objek Feature GeoJSON. Tanpa diduga, kueri ini juga merupakan tempat radius maksimum diterapkan. Angka "16090" adalah jumlah meter yang sama dengan 10 mil, yaitu batas mutlak yang ditentukan oleh backend Go. Jika Anda ingin tahu mengapa klausa WHERE ini tidak ditambahkan ke kueri dalam (tempat jarak setiap lokasi akan ditentukan), hal itu karena cara SQL mengeksekusi di balik layar, kolom tersebut mungkin belum dihitung saat klausa WHERE diperiksa. Faktanya, jika Anda mencoba memindahkan klausa WHERE ini ke kueri dalam, error akan muncul.
**SELECT json_build_object(‘type', ‘FeatureColl
**ection') Kueri ini menggabungkan semua baris yang dihasilkan dari kueri yang menghasilkan JSON dalam objek FeatureCollection GeoJSON.
Menambahkan library PGX ke project Anda
Kita perlu menambahkan satu dependensi ke project Anda: Driver & Toolkit PostGres, yang mengaktifkan penggabungan koneksi. Cara termudah untuk melakukannya adalah dengan Modul Go. Lakukan inisialisasi modul dengan perintah ini dalam Cloud Shell:
go mod init my_locator
Selanjutnya, jalankan perintah ini untuk memindai dependensi di kode, tambahkan daftar dependensi ke file mod, lalu download.
go mod tidy
Terakhir, jalankan perintah ini untuk menarik dependensi secara langsung ke direktori project Anda sehingga container dapat dibuat dengan mudah untuk AppEngine Flex.
go mod vendor
Oke, Anda siap mengujinya sekarang.
Melakukan pengujian
Oke, kita baru saja menyelesaikan BANYAK hal. Mari kita lihat cara kerjanya.
Agar mesin pengembangan Anda (ya, bahkan Cloud Shell) dapat terhubung ke database, kita harus menggunakan Proxy Cloud SQL untuk mengelola koneksi database. Untuk menyiapkan Proxy Cloud SQL:
- Buka di sini untuk mengaktifkan Cloud SQL Admin API
- Jika Anda menggunakan mesin pengembangan lokal, instal alat proxy Cloud SQL. Jika menggunakan Cloud Shell, Anda dapat melewati langkah ini karena sudah terinstal. Perhatikan bahwa petunjuk akan merujuk ke akun layanan. Satu akun telah dibuat untuk Anda, dan kita akan membahas cara menambahkan izin yang diperlukan ke akun tersebut di bagian berikutnya.
- Buat tab baru (di Cloud Shell atau terminal Anda sendiri) untuk memulai proxy.
- Buka
https://console.cloud.google.com/sql/instances/locations/overview
dan scroll ke bawah untuk menemukan kolom Connection name. Salin nama tersebut untuk digunakan di perintah berikutnya. - Di tab tersebut, jalankan proxy Cloud SQL dengan perintah ini, dengan mengganti
CONNECTION_NAME
dengan nama koneksi yang ditampilkan di langkah sebelumnya.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432
Kembali ke tab pertama di Cloud Shell dan tentukan variabel lingkungan yang akan diperlukan Go untuk berkomunikasi dengan backend database, lalu jalankan server dengan cara yang sama seperti sebelumnya:
Buka direktori utama project jika Anda belum membukanya.
cd YOUR_PROJECT_ROOT
Buat lima variabel lingkungan berikut (ganti YOUR_PASSWORD_HERE
dengan sandi yang Anda buat di atas).
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
Jalankan instance lokal Anda.
go run *.go
Buka jendela pratinjau, dan instance seharusnya beroperasi seperti tidak ada yang berubah: Anda dapat memasukkan alamat awal, melakukan zoom di sekitar peta, dan mengklik lokasi daur ulang. Namun sekarang, instance didukung oleh database, dan siap untuk penskalaan.
9. Membuat daftar toko terdekat
Cara kerja Directions API mirip seperti pengalaman meminta rute di aplikasi Google Maps—memasukkan satu tempat asal dan satu tujuan untuk mendapatkan rute di antara kedua lokasi tersebut. Distance Matrix API menggunakan konsep ini lebih lanjut untuk mengidentifikasi pasangan optimal antara beberapa kemungkinan tempat asal dan beberapa kemungkinan tujuan berdasarkan waktu dan jarak perjalanan. Dalam hal ini, untuk membantu pengguna menemukan toko terdekat dengan alamat yang dipilih, Anda memberikan satu tempat asal dan array lokasi toko sebagai tujuan.
Menambahkan jarak dari tempat asal ke setiap toko
Di awal definisi fungsi initMap
, ganti komentar "// TODO: Start Distance Matrix service
" dengan kode berikut:
app.js - initMap
distanceMatrixService = new google.maps.DistanceMatrixService();
Tambahkan fungsi baru ke akhir app.js
yang disebut calculateDistances
.
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);
});
};
Fungsi ini memanggil Distance Matrix API menggunakan tempat asal yang dikirimkan sebagai satu tempat asal dan lokasi toko sebagai array tujuan. Kemudian, fungsi ini membuat array objek yang menyimpan ID toko, jarak yang dinyatakan dalam string yang dapat dibaca manusia, jarak dalam meter sebagai nilai numerik, dan mengurutkan array.
Update fungsi initAutocompleteWidget
untuk menghitung jarak toko setiap kali titik asal baru dipilih dari kotak penelusuran Place Autocomplete. Di bagian bawah fungsi initAutocompleteWidget
, ganti komentar "// TODO: Calculate the closest stores
" dengan kode berikut:
app.js - initAutocompleteWidget
// Use the selected address as the origin to calculate distances
// to each of the store locations
await calculateDistances(originLocation, stores);
renderStoresPanel();
Menampilkan tampilan daftar toko yang diurutkan menurut jarak
Pengguna mengharapkan untuk melihat daftar toko yang diurutkan dari posisi yang terdekat hingga yang terjauh. Isi listingan panel samping untuk setiap toko menggunakan daftar yang diubah dari fungsi calculateDistances
untuk menginformasikan urutan tampilan toko.
Tambahkan dua fungsi baru ke akhir app.js
yang disebut renderStoresPanel()
dan storeToPanelRow()
.
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;
};
Mulai ulang server dan muat ulang pratinjau Anda dengan menjalankan perintah berikut.
go run *.go
Terakhir, masukkan alamat Austin, TX ke kotak penelusuran Autocomplete lalu klik salah satu saran.
Peta akan menempatkan alamat tersebut di tengah dan sidebar akan muncul yang mencantumkan lokasi toko dalam urutan jarak dari alamat yang dipilih. Salah satu contohnya digambarkan sebagai berikut:
10. Menentukan gaya peta
Salah satu cara yang memberikan dampak besar dalam membedakan peta Anda secara visual adalah dengan menambahkan gaya visual ke peta. Dengan gaya visual peta berbasis cloud, penyesuaian peta Anda dikontrol dari Cloud Console menggunakan Gaya Visual Peta Berbasis Cloud (beta). Jika Anda lebih suka menata gaya peta dengan fitur non-beta, Anda dapat menggunakan dokumentasi penataan gaya visual peta untuk membantu Anda membuat JSON untuk menata gaya visual peta secara terprogram. Petunjuk di bawah memandu Anda dalam Penataan Gaya Visual Peta Berbasis Cloud (beta).
Membuat ID Peta
Pertama, buka Cloud Console dan di kotak penelusuran, lalu ketik "Map Management" . Klik hasil yang bertuliskan "Map Management (Google Maps)".
Anda akan melihat tombol di dekat bagian atas (sisi kanan di bawah kotak Penelusuran) yang bertuliskan Create New Map ID. Klik, dan isi nama apa pun yang Anda inginkan. Untuk Map Type, pastikan memilih JavaScript, lalu saat opsi lebih lanjut muncul, pilih Vector dari daftar. Hasil akhirnya akan terlihat seperti gambar di bawah.
Klik "Next" dan Anda akan diberikan ID Peta baru. Anda dapat menyalinnya sekarang jika mau, namun jangan khawatir, Anda dapat mencarinya dengan mudah nanti.
Selanjutnya, kita akan membuat gaya untuk diterapkan pada peta tersebut.
Membuat Gaya Peta
Jika Anda masih berada di bagian Maps pada Cloud Console, klik "Map Styles" di bagian bawah menu navigasi di sebelah kiri. Atau, sama seperti membuat ID Peta, Anda dapat menemukan halaman yang tepat dengan mengetikkan "Map Styles" di kotak penelusuran dan memilih "Map Styles (Google Maps)" dari hasilnya, seperti pada gambar di bawah.
Selanjutnya, klik tombol di dekat bagian atas yang bertuliskan "+ Create New Map Style"
- Jika Anda ingin mencocokkan gaya visual pada peta yang ditampilkan di lab ini, klik tab "IMPORT JSON" dan tempelkan blob JSON di bawah. Atau, jika Anda ingin membuatnya sendiri, pilih Map Style yang Anda inginkan. Kemudian, klik Next.
- Pilih ID Peta yang baru saja Anda buat untuk mengaitkan ID Peta tersebut dengan gaya ini, lalu klik Next lagi.
- Pada titik ini, Anda diberikan opsi untuk menyesuaikan gaya visual peta lebih lanjut. Jika ini adalah sesuatu yang ingin Anda pelajari, klik Customize in Style Editor dan cobalah berbagai warna & opsi sampai Anda memiliki gaya peta yang Anda sukai. Jika tidak, klik Skip.
- Pada langkah berikutnya, masukkan nama dan deskripsi gaya Anda, lalu klik Save And Publish.
Berikut adalah blob JSON opsional yang akan diimpor pada langkah pertama.
[
{
"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"
}
]
}
]
Menambahkan ID Peta ke kode Anda
Setelah bersusah payah membuat gaya peta ini, bagaimana sebenarnya cara MENGGUNAKAN gaya peta ini di peta Anda sendiri? Anda harus membuat dua perubahan kecil:
- Tambahkan ID Peta sebagai parameter URL ke tag skrip di
index.html
Add
ID Peta sebagai argumen konstruktor saat Anda membuat peta dalam metodeinitMap()
.
Ganti tag skrip yang memuat Maps JavaScript API dalam file HTML dengan URL loader di bawah ini, menggantikan placeholder untuk "YOUR_API_KEY
" dan "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>
...
Pada metode initMap
di app.js
tempat konstanta map
ditetapkan, batalkan komentar pada baris untuk properti mapId
dan ganti "YOUR_MAP_ID_HERE
" dengan ID Peta yang baru Anda buat:
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',
// ...
});
...
Mulai ulang server Anda.
go run *.go
Setelah memuat ulang pratinjau, peta akan terlihat sesuai dengan preferensi gaya Anda. Berikut ini contoh penggunaan gaya visual JSON di atas.
11. Men-deploy ke produksi
Jika ingin melihat aplikasi Anda berjalan dari App Engine Flex (dan bukan hanya server web lokal di mesin pengembangan/Cloud Shell, yang selama ini sudah Anda lakukan), caranya sangat mudah. Kita hanya perlu menambahkan beberapa hal agar database dapat diakses di lingkungan produksi. Ini semua diuraikan di halaman dokumentasi tentang Menghubungkan dari App Engine Flex ke Cloud SQL.
Menambahkan Variabel Lingkungan ke App.yaml
Pertama, semua variabel lingkungan yang Anda gunakan untuk menguji secara lokal harus ditambahkan ke bagian bawah file app.yaml
aplikasi Anda.
- Kunjungi https://console.cloud.google.com/sql/in State/locations/overview untuk mencari nama koneksi instance.
- Tempel kode berikut di akhir
app.yaml
. - Ganti
YOUR_DB_PASSWORD_HERE
dengan sandi yang Anda buat untuk nama penggunapostgres
sebelumnya. - Ganti
YOUR_CONNECTION_NAME_HERE
dengan nilai dari langkah 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
Perhatikan bahwa DB_TCP_HOST
harus memiliki nilai 172.17.0.1 karena aplikasi ini terhubung melalui App Engine Flex**.** Hal ini karena aplikasi akan berkomunikasi dengan Cloud SQL melalui proxy, mirip dengan cara Anda sebelumnya.
Menambahkan izin SQL Client ke akun layanan App Engine Flex
Buka halaman IAM-Admin di Cloud Console dan cari akun layanan yang namanya cocok dengan format service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
. Ini adalah akun layanan yang akan digunakan App Engine Flex untuk terhubung ke database. Klik tombol Edit di akhir baris dan tambahkan peran "Cloud SQL Client".
Menyalin kode project Anda ke jalur Go
Agar App Engine dapat menjalankan kode Anda, App Engine harus dapat menemukan file yang relevan di jalur Go. Pastikan Anda berada di direktori utama project.
cd YOUR_PROJECT_ROOT
Salin direktori ke jalur Go.
mkdir -p ~/gopath/src/austin-recycling cp -r ./ ~/gopath/src/austin-recycling
Ubah ke direktori tersebut.
cd ~/gopath/src/austin-recycling
Men-deploy Aplikasi Anda
Gunakan alat gcloud untuk men-deploy aplikasi Anda. Perlu waktu beberapa saat untuk men-deploy-nya.
gcloud app deploy
Gunakan perintah browse
untuk mendapatkan link yang dapat diklik untuk melihat cara kerja pencari toko tingkat perusahaan yang secara estetika terlihat bagus dan sepenuhnya di-deploy.
gcloud app browse
Jika Anda menjalankan gcloud
di luar Cloud Shell, maka menjalankan gcloud app browse
akan membuka tab browser yang baru.
12. (Direkomendasikan) Pembersihan
Menjalankan codelab ini tidak akan melampaui batas paket gratis untuk pemrosesan BigQuery dan panggilan Maps Platform API, tetapi jika Anda melakukan ini hanya sebagai latihan edukasi dan ingin menghindari tagihan di masa mendatang, cara termudah untuk menghapus resource yang terkait dengan project ini adalah dengan menghapus project itu sendiri.
Menghapus Project
Di GCP Console, buka halaman Cloud Resource Manager:
Dalam daftar project, pilih project yang sedang kita kerjakan lalu klik Delete. Anda akan diminta untuk mengetikkan ID project. Masukkan dan klik Shut Down.
Atau, Anda dapat menghapus seluruh project langsung dari Cloud Shell menggunakan gcloud
dengan menjalankan perintah berikut dan mengganti placeholder GOOGLE_CLOUD_PROJECT
dengan ID project Anda:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. Selamat
Selamat. Anda berhasil menyelesaikan codelab.
Atau Anda membaca sekilas hingga halaman terakhir. Selamat. Anda sudah membaca sekilas hingga halaman terakhir.
Selama codelab ini, Anda telah menggunakan teknologi berikut:
- Maps JavaScript API
- Distance Matrix Service, Maps JavaScript API (ada juga Distance Matrix API)
- Places Library, Maps JavaScript API (juga Places API)
- Lingkungan Fleksibel App Engine (Go)
- Cloud SQL API
Bacaan Lebih Lanjut
Masih banyak yang harus dipelajari tentang semua teknologi ini. Di bawah ini adalah beberapa link berguna untuk topik yang tidak kita bahas dalam codelab ini, tetapi tentu saja dapat berguna bagi Anda dalam membangun solusi pencari toko yang sesuai dengan kebutuhan spesifik Anda.