1. Giriş
Soyut
Haritaya yerleştireceğiniz birçok yer olduğunu ve kullanıcıların bu yerlerin nerede olduğunu görmesini ve hangi yeri ziyaret etmek istediklerini belirleyebilmesini istediğinizi düşünün. Buna yaygın örnekler şunlardır:
- Perakendecinin web sitesindeki mağaza bulma aracı
- Yaklaşan bir seçim için oy kullanma yerlerinin haritası
- pil geri dönüşüm kutuları gibi özel yerlerin dizini
Ne oluşturacaksınız?
Bu codelab'de, özel konumların canlı veri feed'inden yararlanan ve kullanıcının başlangıç noktasına en yakın konumu bulmasına yardımcı olan bir bulucu oluşturacaksınız. Bu tam yığınlı konum bulucu, 25 veya daha az mağaza konumuyla sınırlı olan basit mağaza konum bulucudan çok daha fazla sayıda yeri işleyebilir.
Neler öğreneceksiniz?
Bu codelab'de, temel teknik kavramları öğrenmeye odaklanabilmeniz için çok sayıda mağaza konumuyla ilgili önceden doldurulmuş meta verileri simüle etmek üzere açık bir veri kümesi kullanılır.
- Maps JavaScript API: Özelleştirilmiş bir web haritasında çok sayıda konum görüntüleme
- GeoJSON: Konumlarla ilgili meta verileri depolayan bir biçim
- Yer otomatik tamamlama: Kullanıcıların başlangıç konumlarını daha hızlı ve doğru bir şekilde sağlamasına yardımcı olun
- Go: Uygulama arka ucunu geliştirmek için kullanılan programlama dili. Arka uç, veritabanıyla etkileşime girer ve sorgu sonuçlarını biçimlendirilmiş JSON olarak ön uca geri gönderir.
- App Engine: Web uygulamasını barındırmak için
Ön koşullar
- HTML ve JavaScript hakkında temel bilgi
- Google Hesabı
2. Hazırlanın
Aşağıdaki bölümün 3. adımında bu codelab için Maps JavaScript API, Places API ve Distance Matrix API'yi etkinleştirin.
Google Haritalar Platformu'nu kullanmaya başlayın
Google Haritalar Platformu'nu daha önce kullanmadıysanız aşağıdaki adımları tamamlamak için Google Haritalar Platformu'nu Kullanmaya Başlama Kılavuzu'nu inceleyin veya Google Haritalar Platformu'nu Kullanmaya Başlama oynatma listesini izleyin:
- Faturalandırma hesabı oluşturun.
- Proje oluşturun.
- Google Haritalar Platformu API'lerini ve SDK'larını (önceki bölümde listelenmiştir) etkinleştirin.
- API anahtarı oluşturun.
Cloud Shell'i etkinleştirme
Bu codelab'de, projenizi tamamen web tarayıcınızdan barındırıp çalıştırabilmeniz için Google Cloud'da çalışan ve Google Cloud'da çalışan ürünlere ve kaynaklara erişim sağlayan bir komut satırı ortamı olan Cloud Shell'i kullanacaksınız.
Cloud Shell'i Cloud Console'dan etkinleştirmek için Cloud Shell'i etkinleştir 'i tıklayın (ortamın sağlanması ve bağlantının kurulması yalnızca birkaç saniye sürer).
Bu işlem, muhtemelen bir tanıtım geçiş reklamı gösterdikten sonra tarayıcınızın alt kısmında yeni bir kabuk açar.
Projenizi onaylama
Cloud Shell'e bağlandıktan sonra kimliğinizin doğrulandığını ve projenin, kurulum sırasında seçtiğiniz proje kimliğine ayarlandığını görürsünüz.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
Herhangi bir nedenle proje ayarlanmamışsa aşağıdaki komutu çalıştırın:
gcloud config set project <YOUR_PROJECT_ID>
App Engine Flex API'yi etkinleştirme
App Engine Flex API'nin Cloud Console'dan manuel olarak etkinleştirilmesi gerekir. Bu işlem yalnızca API'yi etkinleştirmekle kalmaz, aynı zamanda kullanıcının adına Google hizmetleriyle (ör. SQL veritabanları) etkileşimde bulunacak kimliği doğrulanmış hesap olan App Engine Esnek Ortam Hizmet Hesabı'nı da oluşturur.
3. Hello, World
Arka uç: Go'da Hello World
Cloud Shell örneğinizde, bu codelab'in geri kalanında temel olarak kullanılacak bir Go App Engine Flex uygulaması oluşturarak başlayacaksınız.
Yeni bir sekmede kod düzenleyiciyi açmak için Cloud Shell'in araç çubuğunda Open editor (Düzenleyiciyi aç) düğmesini tıklayın. Bu web tabanlı kod düzenleyici, Cloud Shell örneğindeki dosyaları kolayca düzenlemenize olanak tanır.
Ardından, düzenleyiciyi ve terminali yeni bir sekmeye taşımak için Yeni pencerede aç simgesini tıklayın.
Yeni sekmenin alt kısmındaki terminalde yeni bir austin-recycling
dizini oluşturun.
mkdir -p austin-recycling && cd $_
Ardından, her şeyin çalıştığından emin olmak için küçük bir Go App Engine uygulaması oluşturacaksınız. Merhaba Dünya!
austin-recycling
dizini, soldaki düzenleyicinin klasör listesinde de görünmelidir. austin-recycling
dizininde app.yaml
adlı bir dosya oluşturun. app.yaml
dosyasına aşağıdaki içeriği ekleyin:
app.yaml
runtime: go
env: flex
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
Bu yapılandırma dosyası, App Engine uygulamanızı Go Flex çalışma zamanını kullanacak şekilde yapılandırır. Bu dosyadaki yapılandırma öğelerinin anlamı hakkında arka plan bilgisi için Google App Engine Go Standart Ortam Belgeleri'ne bakın.
Ardından, app.yaml
dosyasının yanında bir main.go
dosyası oluşturun:
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!")
}
Bu kodun ne yaptığını anlamak için burada bir an duraklamak faydalı olacaktır (en azından genel hatlarıyla). 8080 numaralı bağlantı noktasında dinleme yapan bir HTTP sunucusu başlatan ve "/"
yoluyla eşleşen HTTP istekleri için bir işleyici işlevi kaydeden bir paket main
tanımladınız.
handler
olarak adlandırılan işleyici işlevi, "Hello, world!"
metin dizesini yazar. Bu metin, tarayıcınıza geri iletilir ve burada okuyabilirsiniz. İlerleyen adımlarda, basitçe kodlanmış dizeler yerine GeoJSON verileriyle yanıt veren işleyiciler oluşturacaksınız.
Bu adımları uyguladıktan sonra artık aşağıdaki gibi bir düzenleyiciniz olmalıdır:
Deneyin
Bu uygulamayı test etmek için Cloud Shell örneğinde App Engine geliştirme sunucusunu çalıştırabilirsiniz. Cloud Shell komut satırına dönüp aşağıdakileri yazın:
go run *.go
Geliştirme sunucusunu Cloud Shell örneğinde gerçekten çalıştırdığınızı gösteren bazı günlük çıkışı satırları görürsünüz. Bu satırlarda, localhost bağlantı noktası 8080'de dinleme yapan "Merhaba Dünya" web uygulaması yer alır. Web Önizlemesi düğmesine basıp Cloud Shell araç çubuğunda 8080 bağlantı noktasında önizle menü öğesini seçerek bu uygulamada bir web tarayıcısı sekmesi açabilirsiniz.
Bu menü öğesini tıkladığınızda, web tarayıcınızda App Engine geliştirme sunucusundan sunulan "Hello, world!" (Merhaba dünya!) kelimelerini içeren yeni bir sekme açılır.
Bir sonraki adımda, Austin şehrinin geri dönüşüm verilerini bu uygulamaya ekleyip görselleştirmeye başlayacaksınız.
4. Güncel verileri alma
Coğrafi Bilgi Sistemi dünyasının ortak dili olan GeoJSON
Önceki adımda, Go kodunuzda GeoJSON verilerini web tarayıcısında oluşturacak işleyiciler oluşturacağınız belirtilmişti. Peki GeoJSON nedir?
Coğrafi Bilgi Sistemi (GIS) dünyasında, coğrafi varlıklarla ilgili bilgileri bilgisayar sistemleri arasında iletebilmemiz gerekir. Haritalar insanlar için kolay okunur olsa da bilgisayarlar genellikle verilerini daha kolay işlenebilir biçimlerde tercih eder.
GeoJSON, Teksas'taki Austin şehrinde bulunan geri dönüşüm merkezlerinin koordinatları gibi coğrafi veri yapılarını kodlamak için kullanılan bir biçimdir. GeoJSON, Internet Engineering Task Force standardı olan RFC7946 ile standartlaştırılmıştır. GeoJSON, JavaScript'i standartlaştıran kuruluş olan Ecma International tarafından ECMA-404'te standartlaştırılan JavaScript Object Notation (JavaScript Nesne Gösterimi) JSON açısından tanımlanır.
Önemli olan, GeoJSON'un coğrafi bilgileri iletmek için yaygın olarak desteklenen bir tel biçimi olmasıdır. Bu codelab'de GeoJSON aşağıdaki şekillerde kullanılır:
- İstenen verileri filtrelemek için kullanacağınız Austin verilerini, dahili bir GIS'e özgü veri yapısına ayrıştırmak üzere Go paketlerini kullanın.
- Web sunucusu ile web tarayıcısı arasında aktarım için istenen verileri serileştirin.
- Yanıtı haritada işaretçilere dönüştürmek için bir JavaScript kitaplığı kullanın.
Bu sayede, kodda önemli ölçüde yazma işlemi yapmanız gerekmez. Bunun nedeni, ağ üzerinden gelen veri akışını bellekteki gösterimlere dönüştürmek için ayrıştırıcılar ve oluşturucular yazmanız gerekmemesidir.
Verileri alma
City of Austin, Texas Open Data Portal, kamu kaynaklarıyla ilgili coğrafi bilgileri kamu kullanımına sunar. Bu codelab'de geri dönüşüm için bırakma yerleri veri kümesini görselleştireceksiniz.
Verileri, Maps JavaScript API'nin veri katmanı kullanılarak oluşturulan haritadaki işaretçilerle görselleştirirsiniz.
Austin Şehri web sitesinden GeoJSON verilerini uygulamanıza indirerek başlayın.
- Cloud Shell örneğinizin komut satırı penceresinde [CTRL] + [C] yazarak sunucuyu kapatın.
austin-recycling
dizininin içinde birdata
dizini oluşturun ve bu dizine geçin:
mkdir -p data && cd data
Geri dönüşüm yerlerini almak için curl komutunu kullanın:
curl "https://data.austintexas.gov/resource/qzi7-nx8g.geojson" -o recycling-locations.geojson
Son olarak, üst dizine geri dönün.
cd ..
5. Konumları haritalama
Öncelikle, oluşturmak üzere olduğunuz daha sağlam, "artık sadece bir "Merhaba dünya" uygulaması değil" uygulamasını yansıtacak şekilde app.yaml
dosyasını güncelleyin.
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
Bu app.yaml
yapılandırması, /
, /*.js
, /*.css
ve /*.html
isteklerini bir dizi statik dosyaya yönlendirir. Bu, uygulamanızın statik HTML bileşeninin doğrudan App Engine dosya sunma altyapısı tarafından sunulacağı ve Go uygulamanız tarafından sunulmayacağı anlamına gelir. Bu, sunucu yükünü azaltır ve sunma hızını artırır.
Şimdi uygulamanızın arka ucunu Go ile oluşturma zamanı.
Arka ucu oluşturma
app.yaml
dosyanızın yapmadığı ilginç bir şeyin GeoJSON dosyasını kullanıma sunmak olduğunu fark etmiş olabilirsiniz. Bunun nedeni, GeoJSON'un Go arka uçumuz tarafından işlenip gönderilecek olmasıdır. Bu sayede, sonraki adımlarda bazı gelişmiş özellikler ekleyebiliriz. main.go
dosyanızı aşağıdaki gibi okuyacak şekilde değiştirin:
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"])
}
Go arka ucu bize değerli bir özellik sunuyor: App Engine örneği, başlatıldığı anda tüm bu konumları önbelleğe alıyor. Arka uç, her kullanıcının her yenilemesinde dosyayı diskten okumak zorunda kalmayacağından bu işlem zaman kazandırır.
Ön ucu oluşturma
İlk olarak, tüm statik öğelerimizi barındıracak bir klasör oluşturmamız gerekiyor. Projenizin üst klasöründe bir static
klasörü oluşturun.
mkdir -p static && cd static
Bu klasörde 3 dosya oluşturacağız.
index.html
, tek sayfalık mağaza bulucu uygulamanızın tüm HTML'sini içerir.style.css
, beklediğiniz gibi stili içerecekapp.js
, GeoJSON'u alma, Haritalar API'sine çağrı yapma ve özel haritanıza işaretçi yerleştirme işlemlerinden sorumludur.
Bu 3 dosyayı oluşturun ve static/
içine yerleştirin .
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>
head
öğesinin komut etiketi içindeki src
URL'sine özellikle dikkat edin.
- "
YOUR_API_KEY
" yer tutucu metnini, kurulum adımında oluşturduğunuz API anahtarıyla değiştirin. API anahtarınızı almak veya yeni bir anahtar oluşturmak için Cloud Console'da API'ler ve Hizmetler -> Kimlik Bilgileri sayfasına gidebilirsiniz. - URL'nin
callback=initialize.
parametresini içerdiğini unutmayın. Şimdi bu geri çağırma işlevini içeren JavaScript dosyasını oluşturacağız. Uygulamanız, arka uçtaki konumları yükleyip Haritalar API'ye gönderir ve sonucu kullanarak haritada özel konumları işaretler. Tüm bu işlemler, web sayfanızda güzel bir şekilde oluşturulur. libraries=places
parametresi, daha sonra eklenecek adres otomatik tamamlama gibi özellikler için gerekli olan Yerler Kitaplığı'nı yükler.
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;
};
Bu kod, mağaza konumlarını haritada gösterir. Şimdiye kadar yaptıklarımızı test etmek için komut satırından üst dizine dönün:
cd ..
Şimdi uygulamanızı geliştirme modunda tekrar çalıştırın:
go run *.go
Daha önce yaptığınız gibi önizleyin. Buna benzer küçük yeşil dairelerin olduğu bir harita görürsünüz.
Harita konumlarını zaten oluşturuyorsunuz ve codelab'in sadece yarısını tamamladık. Harika. Şimdi biraz etkileşim ekleyelim.
6. İsteğe bağlı olarak ayrıntıları göster
Harita işaretçilerindeki tıklama etkinliklerine yanıt verme
Haritada bir dizi işaretçi göstermek iyi bir başlangıçtır ancak ziyaretçilerin bu işaretçilerden birini tıklayıp konumla ilgili bilgileri (ör. işletmenin adı, adresi vb.) görmesi gerekir. Genellikle bir Google Haritalar işaretçisini tıkladığınızda açılan küçük bilgi penceresinin adı Bilgi Penceresi'dir.
Bir infoWindow nesnesi oluşturun. initialize
işlevine aşağıdakileri ekleyin. "// TODO: Initialize an info window
" yazan yorum satırını değiştirin.
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();
fetchAndRenderStores
işlev tanımını, son satırı storeToCircle
işlevini ek bir bağımsız değişken olan infowindow
ile çağıracak şekilde değiştiren bu biraz farklı sürümle değiştirin:
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));
};
storeToCircle
tanımını, artık üçüncü bağımsız değişken olarak bilgi penceresi alan bu biraz daha uzun sürümle değiştirin:
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;
};
Yukarıdaki yeni kod, haritadaki bir mağaza işaretçisi her tıklandığında seçilen mağazanın bilgilerini içeren bir infoWindow
gösterir.
Sunucunuz hâlâ çalışıyorsa durdurup yeniden başlatın. Harita sayfanızı yenileyip bir harita işaretçisini tıklamayı deneyin. İşletmenin adı ve adresinin yer aldığı küçük bir bilgi penceresi açılır. Bu pencere aşağıdaki gibi görünür:
7. Kullanıcının başlangıç konumunu alma
Mağaza bulucuları kullananlar genellikle kendilerine en yakın mağazayı veya yolculuklarına başlamayı planladıkları adresi öğrenmek ister. Kullanıcının başlangıç adresini kolayca girmesine olanak tanımak için otomatik yer tamamlama özelliğine sahip bir arama çubuğu ekleyin. Otomatik Yer Tamamlama, diğer Google arama çubuklarındaki otomatik tamamlama işlevine benzer bir önden yazma işlevi sunar. Ancak tahminler, Google Haritalar Platformu'ndaki tüm yerlerdir.
Kullanıcı girişi alanı oluşturma
Otomatik tamamlama arama çubuğu ve ilişkili sonuç yan paneli için stil eklemek üzere style.css
düzenleme işlemine geri dönün. CSS stillerini güncellerken, mağaza bilgilerini haritayla birlikte liste olarak gösteren gelecekteki bir kenar çubuğu için de stiller ekleyeceğiz.
Bu kodu dosyanın sonuna ekleyin.
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;
}
Hem otomatik tamamlama arama çubuğu hem de açılır panel, ihtiyaç duyulana kadar gizlidir.
index.html dosyasındaki "<!-- Autocomplete div goes here -->
yorumunu aşağıdaki kodla değiştirerek otomatik tamamlama widget'ı için bir div hazırlayın. Bu düzenlemeyi yaparken, kayarak açılan panel için div'i de ekleyeceğiz.
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>
Şimdi, aşağıdaki kodu app.js
öğesinin sonuna ekleyerek otomatik tamamlama widget'ını haritaya ekleyecek bir işlev tanımlayın.
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
};
Kod, otomatik tamamlama önerilerini yalnızca adres döndürecek şekilde kısıtlar (çünkü yer otomatik tamamlama, kuruluş adları ve idari konumlarla da eşleşebilir) ve döndürülen adresleri yalnızca ABD'dekilerle sınırlar. Bu isteğe bağlı özellikleri eklemek, kullanıcının aradığı adresi göstermek için tahminleri daraltmak üzere girmesi gereken karakter sayısını azaltır.
Ardından, oluşturduğunuz otomatik tamamlama div
öğesini haritanın sağ üst köşesine taşır ve yanıtta her yerle ilgili hangi alanların döndürülmesi gerektiğini belirtir.
Son olarak, initAutocompleteWidget
işlevini initialize
işlevinin sonunda çağırın ve "// TODO: Initialize the Autocomplete widget
" yorumunu değiştirin.
app.js - initialize
// Initialize the Places Autocomplete Widget
initAutocompleteWidget();
Aşağıdaki komutu çalıştırarak sunucunuzu yeniden başlatın ve ardından önizlemenizi yenileyin.
go run *.go
Artık haritanızın sağ üst köşesinde, yazdıklarınızla eşleşen ABD adreslerini gösteren bir otomatik tamamlama widget'ı görmelisiniz. Bu widget, haritanın görünür alanına göre önyargılıdır.
Kullanıcı bir başlangıç adresi seçtiğinde haritayı güncelleme
Şimdi, kullanıcının otomatik tamamlama widget'ından bir tahmin seçtiği durumu ele almanız ve bu konumu mağazalarınıza olan mesafeleri hesaplamak için temel olarak kullanmanız gerekir.
app.js
içindeki initAutocompleteWidget
öğesinin sonuna aşağıdaki kodu ekleyin ve "// TODO: Respond when a user selects an address
" yorumunu değiştirin.
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
});
Kod, kullanıcı önerilerden birini tıkladığında haritanın seçilen adrese göre yeniden ortalanması ve mesafelerin hesaplanmasında başlangıç noktası olarak belirlenmesi için bir dinleyici ekler. Mesafe hesaplamalarını sonraki bir adımda uygularsınız.
Sunucunuzu durdurup yeniden başlatın ve otomatik tamamlama arama çubuğuna bir adres girdikten sonra haritanın yeniden ortalandığını görmek için önizlemenizi yenileyin.
8. Cloud SQL ile ölçeklendirme
Şu ana kadar oldukça iyi bir mağaza bulucu oluşturduk. Uygulamanın yalnızca yaklaşık yüz konum kullanacağı gerçeğinden yararlanarak bu konumları arka uçta belleğe yükler (dosyadan tekrar tekrar okumak yerine). Ancak konum bulucunuzun farklı bir ölçekte çalışması gerekiyorsa ne olur? Geniş bir coğrafi alana yayılmış yüzlerce konumunuz (veya tüm dünyada binlerce konumunuz) varsa bu konumların hepsini bellekte tutmak artık en iyi seçenek değildir ve bölgeleri ayrı dosyalara bölmek kendi sorunlarını ortaya çıkarır.
Konumlarınızı bir veritabanından yüklemenin zamanı geldi. Bu adımda, GeoJSON dosyanızdaki tüm konumları bir Cloud SQL veritabanına taşıyacağız ve bir istek geldiğinde sonuçları yerel önbelleğinden değil, bu veritabanından çekecek şekilde Go arka ucunu güncelleyeceğiz.
PostgreSQL veritabanı ile Cloud SQL örneği oluşturma
Google Cloud Console'u kullanarak Cloud SQL örneği oluşturabilirsiniz ancak komut satırından oluşturmak için gcloud
yardımcı programını kullanmak daha kolaydır. Cloud Shell'de aşağıdaki komutla bir Cloud SQL örneği oluşturun:
gcloud sql instances create locations \ --database-version=POSTGRES_12 \ --tier=db-custom-1-3840 --region=us-central1
locations
bağımsız değişkeni, bu Cloud SQL örneğine vermek için seçtiğimiz addır.tier
işareti, bazı uygun önceden tanımlanmış makineler arasından seçim yapmanın bir yoludur.db-custom-1-3840
değeri, oluşturulan örneğin bir vCPU'ya ve yaklaşık 3,75 GB belleğe sahip olması gerektiğini gösterir.
Cloud SQL örneği, varsayılan kullanıcı postgres
ile bir PostgreSQL veritabanı kullanılarak oluşturulur ve başlatılır. Bu kullanıcının şifresi nedir? Bu sorunuzla önemli bir noktaya değindiniz. Bu kullanıcıların profili yok. Oturum açabilmek için önce bir tane yapılandırmanız gerekir.
Aşağıdaki komutla şifreyi ayarlayın:
gcloud sql users set-password postgres \ --instance=locations --prompt-for-password
Ardından, istendiğinde seçtiğiniz şifreyi girin.
PostGIS uzantısını etkinleştirme
PostGIS, PostGresSQL için bir uzantıdır ve standartlaştırılmış coğrafi veri türlerini depolamayı kolaylaştırır. Normal şartlar altında, PostGIS'i veritabanımıza eklemek için tam bir yükleme sürecinden geçmemiz gerekirdi. Neyse ki bu uzantı, PostgreSQL için Cloud SQL'in desteklediği uzantılardan biridir.
Cloud Shell terminalinde aşağıdaki komutu kullanarak postgres
kullanıcısı olarak giriş yapıp veritabanı örneğine bağlanın.
gcloud sql connect locations --user=postgres --quiet
Yeni oluşturduğunuz şifreyi girin. Şimdi postgres=>
komut istemine PostGIS uzantısını ekleyin.
CREATE EXTENSION postgis;
İşlem başarılı olursa çıkışta aşağıda gösterildiği gibi CREATE EXTENSION yazmalıdır.
Örnek komut çıkışı
CREATE EXTENSION
Son olarak, postgres=>
komut istemine quit komutunu girerek veritabanı bağlantısını sonlandırın.
\q
Coğrafi verileri veritabanına aktarma
Şimdi, GeoJSON dosyalarındaki tüm konum verilerini yeni veritabanımıza aktarmamız gerekiyor.
Neyse ki bu sorun yaygın olarak yaşanmaktadır ve internette bu işlemi sizin için otomatikleştirecek çeşitli araçlar bulabilirsiniz. Jeo-uzamsal verileri depolamak için kullanılan yaygın biçimler arasında dönüşüm yapan ogr2ogr adlı bir araç kullanacağız. Bu seçeneklerden biri de GeoJSON'dan SQL döküm dosyasına dönüştürmedir. SQL döküm dosyası daha sonra veritabanı için tablolarınızı ve sütunlarınızı oluşturmak ve GeoJSON dosyalarınızda bulunan tüm verilerle yüklemek için kullanılabilir.
SQL döküm dosyası oluşturma
Öncelikle ogr2ogr'yi yükleyin.
sudo apt-get install gdal-bin
Ardından, SQL döküm dosyasını oluşturmak için ogr2ogr'yi kullanın. Bu dosya, austinrecycling
adlı bir tablo oluşturur.
ogr2ogr --config PG_USE_COPY YES -f PGDump datadump.sql \ data/recycling-locations.geojson -nln austinrecycling
Yukarıdaki komut, austin-recycling
klasöründen çalıştırılmaya dayanmaktadır. Komutu başka bir dizinden çalıştırmanız gerekiyorsa data
yerine recycling-locations.geojson
öğesinin depolandığı dizinin yolunu girin.
Veritabanınızı geri dönüşüm yerleriyle doldurma
Bu son komut tamamlandığında, komutu çalıştırdığınız dizinde datadump.sql,
adlı bir dosyanız olmalıdır. Bu dosyayı açtığınızda, austinrecycling
tablosunu oluşturan ve konumlarla dolduran yüzü aşkın SQL satırı görürsünüz.
Şimdi veritabanına bağlantı açın ve aşağıdaki komutla bu komut dosyasını çalıştırın.
gcloud sql connect locations --user=postgres --quiet < datadump.sql
Komut dosyası başarıyla çalışırsa çıkışın son birkaç satırı şu şekilde görünür:
Örnek komut çıkışı
ALTER TABLE ALTER TABLE ATLER TABLE ALTER TABLE COPY 103 COMMIT WARNING: there is no transaction in progress COMMIT
Go arka ucunu Cloud SQL kullanacak şekilde güncelleme
Tüm bu veriler artık veritabanımızda olduğuna göre kodumuzu güncelleme zamanı geldi.
Konum bilgilerini göndermek için ön ucu güncelleme
Ön uçta çok küçük bir güncellemeyle başlayalım: Bu uygulamayı artık sorgu her çalıştırıldığında her konumun ön uca iletilmesini istemediğimiz bir ölçekte yazdığımız için, kullanıcının ilgilendiği konumla ilgili bazı temel bilgileri ön uçtan geçirmemiz gerekiyor.
app.js
dosyasını açın ve URL'ye ilgilenilen enlem ve boylamı eklemek için fetchStores
işlev tanımını bu sürümle değiştirin.
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();
};
Bu codelab adımını tamamladıktan sonra yanıt yalnızca center
parametresinde sağlanan harita koordinatlarına en yakın mağazaları döndürür. initialize
işlevindeki ilk getirme işlemi için bu laboratuvarda sağlanan örnek kod, Austin, Teksas'ın merkezi koordinatlarını kullanır.
fetchStores
artık yalnızca mağaza konumlarının bir alt kümesini döndüreceğinden, kullanıcı başlangıç konumunu her değiştirdiğinde mağazaları yeniden getirmemiz gerekecek.
Yeni bir kaynak ayarlandığında konumları yenilemek için initAutocompleteWidget
işlevini güncelleyin. Bu işlem için iki düzenleme yapılması gerekir:
- initAutocompleteWidget içinde
place_changed
dinleyicisinin geri çağırma işlevini bulun. Mevcut daireleri temizleyen satırın yorumunu kaldırın. Böylece kullanıcı, Yer Otomatik Tamamlama arama çubuğundan bir adres seçtiğinde bu satır çalışır.
app.js - initAutocompleteWidget
autocomplete.addListener("place_changed", async () => {
circles.forEach((c) => c.setMap(null)); // clear existing stores
// ...
- Seçilen kaynak her değiştirildiğinde originLocation değişkeni güncellenir. "
place_changed
" geri çağırmasının sonunda, bu yeni kaynağıfetchAndRenderStores
işlevine yapılan yeni bir çağrıya iletmek için "// TODO: Calculate the closest stores
" satırının üzerindeki satırın yorumunu kaldırın.
app.js - initAutocompleteWidget
await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
Arka ucu düz JSON dosyası yerine CloudSQL kullanacak şekilde güncelleyin.
Düz dosya GeoJSON okuma ve önbelleğe alma özelliğini kaldırma
Öncelikle, düz GeoJSON dosyasını yükleyen ve önbelleğe alan kodu kaldırmak için main.go
değerini değiştirin. Ayrıca, farklı bir dosyada Cloud SQL destekli bir işlev yazacağımız için dropoffsHandler
işlevini de kaldırabiliriz.
Yeni main.go
çok daha kısa olacak.
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)
}
}
Konum istekleri için yeni bir işleyici oluşturma
Şimdi de austin-recycling dizininde locations.go
adlı başka bir dosya oluşturalım. Konum istekleri için işleyiciyi yeniden uygulayarak başlayın.
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)
}
İşleyici aşağıdaki önemli görevleri yerine getirir:
- Enlem ve boylamı istek nesnesinden alır (Bunları URL'ye nasıl eklediğimizi hatırlıyor musunuz? )
getGeoJsonFromDatabase
çağrısını tetikler. Bu çağrı, GeoJSON dizesini döndürür (Bunu daha sonra yazacağız).- Bu dizeyi yanıta yazdırmak için
ResponseWriter
kullanılır.
Ardından, veritabanı kullanımının eşzamanlı kullanıcılarla iyi ölçeklenmesine yardımcı olmak için bir bağlantı havuzu oluşturacağız.
Bağlantı havuzu oluşturma
Bağlantı havuzu, sunucunun kullanıcı isteklerine hizmet vermek için yeniden kullanabileceği etkin veritabanı bağlantılarının bir koleksiyonudur. Sunucunun her etkin kullanıcı için bağlantı oluşturup yok etmesi gerekmediğinden, etkin kullanıcı sayınız arttıkça çok fazla ek yük ortadan kalkar. Önceki bölümde github.com/jackc/pgx/stdlib.
kitaplığını içe aktardığımızı fark etmiş olabilirsiniz. Bu, Go'da bağlantı havuzlarıyla çalışmak için kullanılan popüler bir kitaplıktır.
locations.go
dosyasının sonunda, bağlantı havuzunu başlatan initConnectionPool
işlevini (main.go
dosyasından çağrılır) oluşturun. Anlaşılırlık için bu snippet'te birkaç yardımcı yöntem kullanılmıştır. configureConnectionPool
, bağlantı sayısı ve bağlantı başına ömür gibi havuz ayarlarını düzenlemek için kullanışlı bir yer sağlar. mustGetEnv
, gerekli ortam değişkenlerini almak için yapılan çağrıları sarmalar. Böylece, örnekte kritik bilgiler (ör. bağlanılacak veritabanının IP'si veya adı) eksikse yararlı hata mesajları verilebilir.
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
}
Konumlar için veritabanına sorgu gönderin ve karşılığında JSON alın.
Şimdi harita koordinatlarını alan ve en yakın 25 konumu döndüren bir veritabanı sorgusu yazacağız. Bununla da kalmayıp bazı gelişmiş modern veritabanı işlevleri sayesinde bu verileri GeoJSON olarak döndürür. Tüm bunların sonucunda, ön uç kodu açısından hiçbir şey değişmez. URL'ye istek gönderip bir sürü GeoJSON almadan önce. Şimdi bir URL'ye istek gönderiyor ve bir sürü GeoJSON alıyor.
İşte bu sihirli işlevi gerçekleştirecek fonksiyon. locations.go
dosyasının en altına, az önce yazdığınız işleyici ve bağlantı havuzu oluşturma kodunun ardından aşağıdaki işlevi ekleyin.
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
}
Bu işlev, çoğunlukla veritabanına istek göndermek için kurulum, yıkım ve hata işleme işlemlerini kapsar. Veritabanı katmanında gerçekten ilginç birçok şey yapan gerçek SQL'e bakalım. Böylece, bunların hiçbirini kodda uygulamanız gerekmez.
Dize ayrıştırılıp tüm dize değişmezleri uygun yerlere eklendikten sonra gönderilen ham sorgu şu şekilde görünür:
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
Bu sorgu, birincil sorgu ve bazı JSON sarmalama işlevleri olarak görüntülenebilir.
SELECT * ... LIMIT 25
, her konum için tüm alanları seçer. Ardından, veritabanındaki her konum ile kullanıcının ön uçta sağladığı konumun enlem/boylam çifti arasındaki mesafeyi belirlemek için ST_DISTANCE işlevini (PostGIS'in coğrafya ölçümü işlevleri paketinin bir parçası) kullanır. Sürüş mesafesini verebilen Mesafe Matrisi'nin aksine, bu mesafelerin coğrafi mesafeler olduğunu unutmayın. Ardından, verimlilik için bu mesafeyi kullanarak sıralama yapar ve kullanıcının belirttiği konuma en yakın 25 konumu döndürür.
**SELECT json_build_object(‘type', ‘F
**eature') önceki sorguyu sarmalar, sonuçları alır ve bunları GeoJSON Feature nesnesi oluşturmak için kullanır. Beklenmedik bir şekilde, bu sorguda maksimum yarıçap da uygulanır. "16090", 10 milin metre cinsinden değeridir ve Go arka ucu tarafından belirtilen kesin sınırdır. Bu WHERE ifadesinin neden her konumun mesafesinin belirlendiği iç sorguya eklenmediğini merak ediyorsanız bunun nedeni, SQL'in arka planda yürütülme şekli nedeniyle WHERE ifadesi incelenirken bu alanın hesaplanmamış olabilmesidir. Hatta bu WHERE ifadesini iç sorguya taşımaya çalışırsanız hata alırsınız.
**SELECT json_build_object(‘type', ‘FeatureColl
**ection') Bu sorgu, JSON oluşturan sorgudan elde edilen tüm sonuç satırlarını GeoJSON FeatureCollection nesnesi olarak sarmalar.
PGX kitaplığını projenize ekleme
Projenize bir bağımlılık eklememiz gerekiyor: Bağlantı havuzunu etkinleştiren PostGres Driver & Toolkit. Bunu yapmanın en kolay yolu Go Modules'ü kullanmaktır. Cloud Shell'de aşağıdaki komutla bir modülü başlatın:
go mod init my_locator
Ardından, kodu bağımlılıklar için taramak, bağımlılıkların listesini mod dosyasına eklemek ve bunları indirmek için bu komutu çalıştırın.
go mod tidy
Son olarak, bağımlılıkları doğrudan proje dizininize çekmek için bu komutu çalıştırın. Böylece, App Engine Flex için kapsayıcı kolayca oluşturulabilir.
go mod vendor
Artık özelliği test etmeye hazırsınız.
Deneyin
Evet, bu bölümde çok şey öğrendik. Nasıl çalıştığını izleyelim.
Geliştirme makinenizin (evet, Cloud Shell bile) veritabanına bağlanabilmesi için veritabanı bağlantısını yönetmek üzere Cloud SQL Proxy'yi kullanmamız gerekecek. Cloud SQL Proxy'yi ayarlamak için:
- Cloud SQL Admin API'yi etkinleştirmek için buraya gidin.
- Yerel bir geliştirme makinesindeyseniz Cloud SQL proxy aracını yükleyin. Cloud Shell kullanıyorsanız bu adımı atlayabilirsiniz. Cloud Shell'de zaten yüklüdür. Talimatlarda hizmet hesabından bahsedileceğini unutmayın. Sizin için zaten bir hesap oluşturuldu. Gerekli izinleri bu hesaba ekleme konusunu bir sonraki bölümde ele alacağız.
- Proxy'yi başlatmak için yeni bir sekme oluşturun (Cloud Shell'de veya kendi terminalinizde).
https://console.cloud.google.com/sql/instances/locations/overview
adresine gidin ve Bağlantı adı alanını bulmak için aşağı kaydırın. Sonraki komutta kullanmak üzere bu adı kopyalayın.- Bu sekmede,
CONNECTION_NAME
yerine önceki adımda gösterilen bağlantı adını yazarak Cloud SQL proxy'sini şu komutla çalıştırın.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432
Cloud Shell'inizin ilk sekmesine dönün ve Go'nun veritabanı arka ucuyla iletişim kurmak için ihtiyaç duyacağı ortam değişkenlerini tanımlayın. Ardından, sunucuyu daha önce yaptığınız gibi çalıştırın:
Henüz orada değilseniz projenin kök dizinine gidin.
cd YOUR_PROJECT_ROOT
Aşağıdaki beş ortam değişkenini oluşturun (YOUR_PASSWORD_HERE
yerine yukarıda oluşturduğunuz şifreyi girin).
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
Yerel örneğinizi çalıştırın.
go run *.go
Önizleme penceresini açtığınızda hiçbir şey değişmemiş gibi çalışır: Başlangıç adresi girebilir, haritada yakınlaştırıp uzaklaştırabilir ve geri dönüşüm yerlerini tıklayabilirsiniz. Ancak artık bir veritabanı tarafından destekleniyor ve ölçeklendirmeye hazır.
9. En yakın mağazaları listele
Directions API, Google Haritalar uygulamasında yol tarifi isteme deneyimine benzer şekilde çalışır. İki nokta arasındaki rotayı almak için tek bir başlangıç ve tek bir hedef girilir. Distance Matrix API, seyahat sürelerine ve mesafelere göre birden fazla olası başlangıç noktası ile birden fazla olası varış noktası arasındaki en uygun eşleşmeleri belirlemek için bu kavramı daha da ileriye taşır. Bu durumda, kullanıcının seçilen adrese en yakın mağazayı bulmasına yardımcı olmak için bir başlangıç noktası ve hedef olarak bir dizi mağaza konumu sağlarsınız.
Her mağazaya başlangıç noktasına olan mesafeyi ekleyin.
initMap
işlev tanımının başında, "// TODO: Start Distance Matrix service
" yorumunu aşağıdaki kodla değiştirin:
app.js - initMap
distanceMatrixService = new google.maps.DistanceMatrixService();
app.js
adlı dizenin sonuna calculateDistances
adlı yeni bir işlev ekleyin.
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);
});
};
İşlev, kendisine tek bir başlangıç noktası olarak iletilen başlangıç noktasını ve mağaza konumlarını hedef dizisi olarak kullanarak Distance Matrix API'yi çağırır. Ardından, mağazanın kimliğini, kullanıcıların okuyabileceği bir dize olarak ifade edilen mesafeyi, sayısal değer olarak metre cinsinden mesafeyi depolayan bir nesne dizisi oluşturur ve diziyi sıralar.
initAutocompleteWidget
işlevini, Yer Otomatik Tamamlama arama çubuğundan yeni bir başlangıç noktası seçildiğinde mağaza mesafelerini hesaplayacak şekilde güncelleyin. initAutocompleteWidget
işlevinin en altında, "// TODO: Calculate the closest stores
" yorumunu aşağıdaki kodla değiştirin:
app.js - initAutocompleteWidget
// Use the selected address as the origin to calculate distances
// to each of the store locations
await calculateDistances(originLocation, stores);
renderStoresPanel();
Mesafeye göre sıralanmış mağazaların liste görünümünü gösterme
Kullanıcı, mağazaların en yakından en uzağa doğru sıralandığı bir liste görmeyi bekler. Mağazaların gösterilme sırasını belirlemek için calculateDistances
işleviyle değiştirilen listeyi kullanarak her mağaza için bir yan panel girişi oluşturun.
app.js
işlevinin sonuna renderStoresPanel()
ve storeToPanelRow()
adlı iki yeni işlev ekleyin.
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;
};
Aşağıdaki komutu çalıştırarak sunucunuzu yeniden başlatın ve önizlemenizi yenileyin.
go run *.go
Son olarak, otomatik tamamlama arama çubuğuna Austin, TX adresini girin ve önerilerden birini tıklayın.
Harita, bu adresin merkezinde olmalı ve seçilen adrese olan uzaklığa göre mağaza konumlarının listelendiği bir kenar çubuğu görünmelidir. Bir örnek aşağıda gösterilmiştir:
10. Haritayı stilize etme
Haritanızı görsel olarak öne çıkarmanın etkili bir yolu, haritaya stil eklemektir. Bulut tabanlı harita stiliyle, haritalarınızın özelleştirilmesi Cloud Console'dan Cloud-based Map Styling (beta) kullanılarak kontrol edilir. Haritanızı beta olmayan bir özellik ile stilize etmeyi tercih ederseniz haritayı programatik olarak stilize etmek için JSON oluşturmanıza yardımcı olacak harita stilize etme dokümanlarını kullanabilirsiniz. Aşağıdaki talimatlar, Cloud-based Map Styling (beta) konusunda size yol gösterir.
Harita kimliği oluşturma
Öncelikle Cloud Console'u açın ve arama kutusuna "Map Management" (Harita Yönetimi) yazın. "Harita Yönetimi (Google Haritalar)" yazan sonucu tıklayın.
Üst kısma yakın bir yerde (Arama kutusunun hemen altında) Yeni Harita Kimliği Oluştur yazan bir düğme görürsünüz. Bunu tıklayın ve istediğiniz adı girin. Harita Türü için JavaScript'i seçtiğinizden emin olun. Diğer seçenekler gösterildiğinde listeden Vektör'ü seçin. Sonuç aşağıdaki resme benzer şekilde görünmelidir.
"İleri"yi tıkladığınızda yepyeni bir harita kimliği oluşturulur. İsterseniz şimdi kopyalayabilirsiniz ancak daha sonra kolayca bulabileceğiniz için endişelenmenize gerek yok.
Ardından, bu haritaya uygulanacak bir stil oluşturacağız.
Harita stili oluşturma
Hâlâ Cloud Console'un Haritalar bölümündeyseniz soldaki gezinme menüsünün alt kısmında "Harita Stilleri"ni tıklayın. Aksi takdirde, harita kimliği oluşturmaya benzer şekilde, arama kutusuna "Harita Stilleri" yazıp sonuçlardan "Harita Stilleri (Google Haritalar)"ı seçerek doğru sayfayı bulabilirsiniz (aşağıdaki resimde gösterildiği gibi).
Ardından, üst kısımlarda yer alan "+ Yeni Harita Stili Oluştur" düğmesini tıklayın.
- Bu laboratuvarda gösterilen haritadaki stili kullanmak istiyorsanız "JSON'U İÇE AKTAR" sekmesini tıklayın ve aşağıdaki JSON blob'unu yapıştırın. Kendi harita stilinizi oluşturmak istiyorsanız başlamak istediğiniz harita stilini seçin. Ardından, İleri'yi tıklayın.
- Bu harita kimliğini bu stille ilişkilendirmek için az önce oluşturduğunuz harita kimliğini seçin ve tekrar Sonraki'yi tıklayın.
- Bu noktada, haritanızın stilini daha fazla özelleştirme seçeneği sunulur. Bu özelliği incelemek istiyorsanız Stil düzenleyicide özelleştir'i tıklayın ve beğendiğiniz bir harita stili elde edene kadar renkler ve seçeneklerle oynayın. Aksi takdirde Atla'yı tıklayın.
- Sonraki adımda, stilinizin adını ve açıklamasını girip Kaydet ve Yayınla'yı tıklayın.
İlk adımda içe aktarılacak isteğe bağlı bir JSON blob'u aşağıda verilmiştir.
[
{
"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"
}
]
}
]
Kodunuza harita kimliği ekleme
Bu harita stilini oluşturmak için uğraştığınıza göre, bu harita stilini kendi haritanızda nasıl KULLANABİLİRSİNİZ? İki küçük değişiklik yapmanız gerekir:
- Harita kimliğini
index.html
içindeki komut dosyası etiketine URL parametresi olarak ekleyin. Add
yönteminizde haritayı oluştururken oluşturucu bağımsız değişkeni olarak harita kimliği.initMap()
HTML dosyasında Maps JavaScript API'yi yükleyen komut dosyası etiketini aşağıdaki yükleyici URL'siyle değiştirin. "YOUR_API_KEY
" ve "YOUR_MAP_ID
" yer tutucularını değiştirin:
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>
...
Sabit map
değerinin tanımlandığı initMap
app.js
yönteminde, mapId
özelliği için yorum işaretini kaldırın ve "YOUR_MAP_ID_HERE
" değerini yeni oluşturduğunuz harita kimliğiyle değiştirin:
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',
// ...
});
...
Sunucunuzu yeniden başlatın.
go run *.go
Önizlemenizi yenilediğinizde harita, tercihleriniz doğrultusunda şekillendirilmiş olarak görünmelidir. Yukarıdaki JSON stilini kullanan bir örneği burada bulabilirsiniz.
11. Üretime dağıtma
Uygulamanızın App Engine Flex'te (geliştirme makinenizdeki / Cloud Shell'deki yerel bir web sunucusunda değil) çalıştığını görmek istiyorsanız bu işlem çok kolaydır. Veritabanı erişiminin üretim ortamında çalışması için birkaç şey eklememiz gerekiyor. Bu işlemlerin tümü, App Engine Flex'ten Cloud SQL'e bağlanma başlıklı doküman sayfasında açıklanmıştır.
App.yaml dosyasına ortam değişkenleri ekleme
Öncelikle, yerel olarak test etmek için kullandığınız tüm ortam değişkenlerinin uygulamanızın app.yaml
dosyasının en altına eklenmesi gerekir.
- Örnek bağlantı adını bulmak için https://console.cloud.google.com/sql/instances/locations/overview adresini ziyaret edin.
- Aşağıdaki kodu
app.yaml
öğesinin sonuna yapıştırın. YOUR_DB_PASSWORD_HERE
yerine, daha öncepostgres
kullanıcı adı için oluşturduğunuz şifreyi girin.YOUR_CONNECTION_NAME_HERE
yerine 1. adımdaki değeri girin.
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
Bu uygulama App Engine Flex üzerinden bağlandığından DB_TCP_HOST
değerinin 172.17.0.1 olması gerektiğini unutmayın.** Bunun nedeni, sizin yaptığınız gibi Cloud SQL ile bir proxy üzerinden iletişim kuracak olmasıdır.
App Engine Flex hizmet hesabına SQL İstemcisi izinleri ekleme
Cloud Console'da IAM-Admin sayfasına gidin ve adı service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
biçimine uyan bir hizmet hesabı arayın. Bu, App Engine Flex'in veritabanına bağlanmak için kullanacağı hizmet hesabıdır. Satırın sonundaki Düzenle düğmesini tıklayın ve "Cloud SQL İstemcisi" rolünü ekleyin.
Proje kodunuzu Go yoluna kopyalama
App Engine'in kodunuzu çalıştırması için Go yolundaki ilgili dosyaları bulabilmesi gerekir. Projenizin kök dizininde olduğunuzdan emin olun.
cd YOUR_PROJECT_ROOT
Dizini go yoluna kopyalayın.
mkdir -p ~/gopath/src/austin-recycling cp -r ./ ~/gopath/src/austin-recycling
Bu dizine geçin.
cd ~/gopath/src/austin-recycling
Uygulamanızı Dağıtma
Uygulamanızı dağıtmak için gcloud
CLI'yı kullanın. Dağıtım işlemi biraz zaman alır.
gcloud app deploy
Tam olarak dağıtılmış, kurumsal düzeyde ve estetik açıdan çarpıcı mağaza bulucunuzu çalışırken görmek için tıklayabileceğiniz bir bağlantı almak üzere browse
komutunu kullanın.
gcloud app browse
gcloud
komutunu Cloud Shell dışında çalıştırıyorsanız gcloud app browse
komutunu çalıştırdığınızda yeni bir tarayıcı sekmesi açılır.
12. (Önerilen) Temizleme
Bu codelab'i yaptığınızda BigQuery işleme ve Haritalar Platformu API çağrıları için ücretsiz katman sınırları aşılmaz. Ancak bu codelab'i yalnızca eğitici bir alıştırma olarak yaptıysanız ve gelecekte herhangi bir ücret ödemek istemiyorsanız bu projeyle ilişkili kaynakları silmenin en kolay yolu projenin kendisini silmektir.
Projeyi silme
GCP Console'da Cloud Resource Manager sayfasına gidin:
Proje listesinde, üzerinde çalıştığımız projeyi seçin ve Sil'i tıklayın. Proje kimliğini yazmanız istenir. Girip Kapat'ı tıklayın.
Alternatif olarak, aşağıdaki komutu çalıştırıp yer tutucu GOOGLE_CLOUD_PROJECT
yerine proje kimliğinizi yazarak gcloud
ile doğrudan Cloud Shell'den projenin tamamını silebilirsiniz:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. Tebrikler
Tebrikler! Codelab'i başarıyla tamamladınız.
Veya son sayfaya göz attınız. Tebrikler! Son sayfaya kadar göz attınız.
Bu codelab boyunca aşağıdaki teknolojilerle çalıştınız:
- Maps JavaScript API
- Distance Matrix Hizmeti, Maps JavaScript API (Distance Matrix API de vardır)
- Yerler Kitaplığı, Maps JavaScript API (Places API de dahil)
- App Engine Esnek Ortamı (Go)
- Cloud SQL API
Daha fazla bilgi
Bu teknolojilerin tümü hakkında öğrenilecek çok şey var. Bu codelab'de ele almaya zamanımızın yetmediği ancak özel ihtiyaçlarınıza uygun bir mağaza bulucu çözümü oluştururken kesinlikle faydalı olabilecek konularla ilgili bazı yararlı bağlantıları aşağıda bulabilirsiniz.