1. Giriş
Özet
Haritaya ekleyeceğiniz birçok yeriniz olduğunu ve kullanıcıların bu yerlerin nerede olduğunu ve nerede ziyaret etmek istediklerini belirleyebilmesini istediğinizi düşünün. Bunun yaygın örnekleri şunlardır:
- Bir perakendecinin web sitesindeki mağaza bulma aracı
- gelecek seçimler için yoklama yerlerinin haritası
- pil geri dönüşüm kutuları gibi özel konum dizinleri
Derlemeniz istenen nedir?
Bu codelab'de, özel konumların canlı veri feed'inden alınan 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ığın konum belirleyicisi, 25 veya daha az mağaza konumuyla sınırlı olan basit mağaza bulma aracından çok daha fazla sayıda yeri işleyebilir.
Neler öğreneceksiniz?
Bu codelab'de, çok sayıda mağaza konumu hakkındaki önceden doldurulmuş meta verileri simüle etmek için açık bir veri kümesi kullanılır. Böylece temel teknik kavramları öğrenmeye odaklanabilirsiniz.
- 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 daha doğru bir şekilde sağlamasına yardımcı olur
- Git: Uygulama arka ucunu geliştirmek için kullanılan programlama dili. Arka uç, veritabanıyla etkileşimde bulunur ve sorgu sonuçlarını kullanıcı arabirimine biçimlendirilmiş JSON olarak geri gönderir.
- App Engine: Web uygulamasını barındırmak için
Ön koşullar
- HTML ve JavaScript ile ilgili temel bilgiler
- 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 Remote Matrix API'yi etkinleştirin.
Google Haritalar Platformu'nu kullanmaya başlayın
Google Haritalar Platformu'nu daha önce kullanmadıysanız Google Haritalar Platformu'nu Kullanmaya Başlama rehberini izleyerek veya Google Haritalar Platformu oynatma listesini izleyerek aşağıdaki adımları tamamlayın:
- Faturalandırma hesabı oluşturun.
- Bir proje oluşturun.
- Google Haritalar Platformu API'lerini ve SDK'larını etkinleştirin (bir önceki bölümde listelenmiştir).
- API anahtarı oluşturun.
Cloud Shell'i etkinleştirme
Bu codelab'de, Google Cloud'da çalışan ve Google Cloud'da çalışan ürün ve kaynaklara erişim sağlayan bir komut satırı ortamı olan Cloud Shell'i kullanırsınız. Böylece, projenizi web tarayıcınızda tamamen barındırabilir ve çalıştırabilirsiniz.
Cloud Shell'i Cloud Console'dan etkinleştirmek için Cloud Shell'i Etkinleştir seçeneğini tıklayın (temel hazırlığının yapılması ve ortama bağlanması yalnızca birkaç dakika sürer).
Böylece, muhtemelen bir geçiş reklamı gösterildikten sonra tarayıcınızın alt kısmında yeni bir kabuk açılır.
Projenizi onaylayın
Cloud Shell'e bağlandıktan sonra kimliğinizin zaten doğrulandığını ve projenin, kurulum sırasında seçtiğiniz proje kimliğine ayarlanmış olduğunu görmeniz gerekir.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
Herhangi bir nedenle proje ayarlanmadıysa aşağıdaki komutu çalıştırın:
gcloud config set project <YOUR_PROJECT_ID>
AppEngine Flex API'yi Etkinleştir
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ı adına Google hizmetleriyle (SQL veritabanları gibi) etkileşimde bulunacak olan kimliği doğrulanmış hesap olan AppEngine Esnek Ortam Hizmet Hesabını da oluşturur.
3. Merhaba,
Arka Uç: Hello World in Go
Cloud Shell örneğinizde, codelab'in geri kalanı için temel görevi görecek bir Go App Engine Flex uygulaması oluşturarak başlayabilirsiniz.
Cloud Shell'in araç çubuğunda, Düzenleyiciyi aç düğmesini tıklayarak kod düzenleyiciyi yeni bir sekmede açın. Bu web tabanlı kod düzenleyici, Cloud Shell örneğindeki dosyaları kolayca düzenlemenize olanak sağlar.
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 $_
Şimdi 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 Editor (Klasör) klasör listesinde de görünecektir. austin-recycling
dizininde app.yaml
adlı bir dosya oluşturun. app.yaml
dosyasına aşağıdaki içeriği yerleştirin:
uygulama.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 bilgileri için Google App Engine Go Standard Ortam Belgelerine bakın.
Ardından, app.yaml
dosyasının yanında main.go
dosyasını oluşturun:
main.go ise
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 işe yaradığını en azından yüksek düzeyde anlamak için burada bir anı duraklatmanız faydalı olacaktır. 8080 bağlantı noktasında bir http sunucusu dinlemeye başlayan ve "/"
yolu ile eşleşen HTTP istekleri için bir işleyici işlevi kaydeden bir main
paketi tanımladınız.
handler
adı verilen işleyici işlevi, "Hello, world!"
metin dizesini yazar. Bu metin, tarayıcınıza geri aktarılacak. Böylece bu metni okuyabilirsiniz. Sonraki adımlarda, basit kodlu dizeler yerine GeoJSON verileriyle yanıt veren işleyiciler oluşturacaksınız.
Bu adımları gerçekleştirdikten sonra, aşağıdaki gibi bir düzenleyiciniz olur:
Deneyin
Bu uygulamayı test etmek için App Engine geliştirme sunucusunu Cloud Shell örneğinde çalıştırabilirsiniz. Cloud Shell komut satırına geri dönün ve aşağıdakileri yazın:
go run *.go
gerçekten günlük sunucusu'nun Cloud Shell örneğinde geliştirme sunucusunu çalıştırdığınızı gösteren bir günlük çıkış satırları görürsünüz. Hello World web uygulaması, yerel barındırıcı bağlantı noktası 8080'de dinleniyor. Bu uygulamada bir web tarayıcısı sekmesini açmak için Web Önizlemesi düğmesine basıp Cloud Shell araç çubuğundaki 8080 bağlantı noktasında önizle menü öğesini seçebilirsiniz.
Bu menü öğesini tıkladığınızda, web tarayıcınızda App Engine geliştirme sunucusu tarafından sunulan "Merhaba, dünya!" şeklinde yeni bir sekme açılır.
Bir sonraki adımda, bu uygulamaya City of Austin geri dönüşüm verilerini ekleyip görselleştirmeye başlayacaksınız.
4. Güncel verileri alma
Coğrafi Bilgi Sistemi dünyasında bir lingua franca olan GeoJSON
Önceki adımda, GoJSON verilerinizde web tarayıcısı için coğrafi veriler oluşturan işleyiciler oluşturacağınız belirtiliyordu. Peki GeoJSON nedir?
Coğrafi Bilgi Sistemi (GIS) dünyasında, bilgisayar sistemleri arasında coğrafi varlıklarla ilgili bilgileri paylaşabilmemiz gerekir. Haritalar insanlar tarafından kolayca okunabilse de bilgisayarlar verileri daha kolay anlaşılabilen biçimlerde tercih eder.
GeoJSON, Austin, Teksas'ta geri dönüşüm bölgesinin koordinatları gibi coğrafi veri yapılarını kodlamak için kullanılan bir biçimdir. GeoJSON, RFC7946 adlı bir İnternet Mühendisliği Görev Gücü standardında standartlaştırılmıştır. GeoJSON, JavaScript ve Ecma International gibi standartlaştırılmış JavaScript standardı olan ECMA-404 tarafından standart hale getirilen JSON ve JavaScript Object Notation terimleriyle tanımlanır.
Önemli olan, GeoJSON'ın coğrafi bilgileri aktarmak için yaygın olarak desteklenen bir kablo biçimidir. Bu codelab'de GeoJSON aşağıdaki şekillerde kullanılmaktadır:
- Austin verilerini, istenen verileri filtrelemek için kullanacağınız dahili bir Coğrafi Bilgi Sistemi'ne özel veri yapısında ayrıştırmak için Go paketlerini kullanın.
- Web sunucusu ile web tarayıcısı arasında geçiş için istenen verileri serileştirin.
- Yanıtı bir haritadaki işaretçilere dönüştürmek için bir JavaScript kitaplığı kullanın.
Bu şekilde kablolu veri akışını bellek içi gösterimlere dönüştürmek için ayrıştırıcılar ve oluşturma araçları yazmanız gerekmez. Bu sayede önemli ölçüde kod yazabilirsiniz.
Verileri alma
City of Austin, Texas Open Data Portal, halka açık kaynaklarla ilgili coğrafi konum bilgilerini halka açık hâle getirmektedir. Bu codelab'de, geri dönüşüm teslim etme konumları veri kümesini görselleştireceksiniz.
Verileri, Maps JavaScript API'nin Veri katmanı kullanılarak oluşturulan haritadaki işaretçilerle görselleştirebilirsiniz.
Başlamak için, City of Austin web sitesinden GeoJSON verilerini indirin.
- Cloud Shell örneğinizin komut satırı penceresine [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
Şimdi geri dönüşüm konumlarını almak için curl 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ı eşleyin
Öncelikle, app.yaml
dosyasını artık yalnızca bir merhaba dünya uygulaması değil, daha güçlü bir uygulama olarak yansıtmak için güncelleyin.
uygulama.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 Go uygulamanıza değil, doğrudan App Engine dosya sunma altyapısına göre sunulacağı anlamına gelir. Bu, sunucu yükünü azaltır ve sunum hızını artırır.
Artık Go'da uygulamanızın arka ucunu oluşturma zamanı geldi.
Arka ucu oluşturun
app.yaml
dosyanızın yapmadığı ilginç bir şey de coğrafi JSON dosyasının gösterilmesidir. Bu nedenle, GeoJSON, Go arka ucumuz tarafından işlenip gönderileceği için sonraki adımlarda bazı gösterişli özellikleri geliştirmemize olanak tanıyacaktır. main.go
dosyanızı aşağıdaki gibi okuyacak şekilde değiştirin:
main.go ise
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"])
}
Go arka ucu zaten değerli bir özellik sunuyor: AppEngine örneği başlar başlamaz bu konumların tümünü önbelleğe alıyor. Bu, arka ucun her kullanıcıda yapılan her yenilemede dosyayı diskten okumasına gerek kalmayacağı için zaman kazandırır.
Ön ucu oluşturma
Yapmanız gereken ilk şey, tüm statik öğelerimizi içerecek bir klasör oluşturmaktır. Projenizin üst klasöründen 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 bulma aracı uygulamanızın tüm HTML'sini içerir.style.css
(beklediğiniz gibi)- GeoJSON'ı almak, Maps API'ye çağrı yapmaktan ve özel haritaya işaretçiler yerleştirmekten
app.js
sorumlu olacaktır.
Bu 3 dosyayı static/
altına eklediğinizden emin olun.
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 dosyası etiketindeki src
URL'sine özellikle dikkat edin.
- Yer tutucu metni "
YOUR_API_KEY
" yerine kurulum adımında oluşturduğunuz API anahtarını kullanın. API anahtarınızı almak veya yeni bir anahtar oluşturmak için Cloud Console'da API'ler ve Hizmetler -> Kimlik Bilgileri sayfasını ziyaret edebilirsiniz. - URL'nin artık geri çağırma işlevini içeren JavaScript dosyasını oluşturacağı
callback=initialize.
parametresini içerdiğini unutmayın. Bu noktada uygulamanız, arka uçtaki konumları yükler, bunları Maps API'ye gönderir ve sonuçta web'de mükemmel şekilde oluşturulmuş özel konumları harita üzerinde işaretlemek için kullanır. libraries=places
parametresi, daha sonra eklenecek adres otomatik tamamlama gibi özellikler için gerekli olan Yer Kitaplığı'nı yükler.
uygulama.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 oluşturur. Şimdiye kadarki komutumuzu test etmek için komut satırından üst dizine dönün:
cd ..
Şimdi uygulamanızı aşağıdakileri kullanarak geliştirme modunda tekrar çalıştırın:
go run *.go
Daha önce yaptığınız gibi önizleme yapın. Buna benzer küçük yeşil daireler içeren bir harita görürsünüz.
Zaten harita konumları oluşturuyorsunuz ve codelab'in yalnızca yarısındayız. Harika. Şimdi de biraz etkileşim ekleyelim.
6. İsteğe bağlı ayrıntıları göster
Harita işaretçilerinde tıklama etkinliklerine yanıt verme
Haritada birçok işaretçi görüntülemek harika bir başlangıçtır, ancak bir ziyaretçinin bu işaretçilerden birini tıklayıp o konumla ilgili bilgileri (ör. işletmenin adı, adresi vb.) görebilmesi için gerçekten ihtiyacımız vardır. Bir Google Haritalar işaretçisini tıkladığınızda açılan küçük bilgi penceresinin adı, Bilgi Penceresi'dir.
infowindow nesnesi oluşturun. Aşağıdakileri, "// TODO: Initialize an info window
" yazan yorum satırını değiştirerek initialize
işlevine ekleyin.
app.js - başlatma
// 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ı, biraz farklı bir bağımsız değişkenle değiştirerek, son satırı storeToCircle
çağrısı yapacak şekilde yeni bir bağımsız değişkenle değiştirin: 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));
};
storeToCircle
tanımını, biraz daha uzun bir sürümle değiştirin. Bu sürüm, üçüncü bağımsız değişken olarak Bilgi Penceresini alır:
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 tıklandığında seçili mağaza bilgilerini içeren bir infoWindow
görüntüler.
Sunucunuz hâlâ çalışıyorsa sunucuyu durdurun ve yeniden başlatın. Harita sayfanızı yenileyin ve bir harita işaretleyiciyi tıklamayı deneyin. İşletmenin adı ve adresinin yer aldığı aşağıdaki gibi bir küçük bilgi penceresi açılır:
7. Kullanıcının başlangıç konumunu al
Mağaza bulma aracı kullanıcıları, genellikle hangi mağazanın kendilerine en yakın olduğunu veya yolculuklarına başlamayı planladıkları bir adresi öğrenmek ister. Kullanıcının kolayca bir başlangıç adresi girmesine olanak tanımak için Yer Otomatik Tamamlama arama çubuğu ekleyin. Yer Otomatik Tamamlama özelliği, tahminlerin tamamının Google Haritalar Platformu'ndaki Yerler olması dışında, Otomatik Tamamlama özelliğinin diğer Google arama çubuklarındaki işleyiş şekline benzer bir yazma işlevi sağlar.
Kullanıcı giriş alanı oluşturma
Otomatik tamamlama arama çubuğu ve ilişkili sonuçlar yan paneli için stil eklemek üzere style.css
düzenlemeye geri dönün. CSS stillerini güncellerken, mağazaya eşlik edecek mağaza bilgilerini liste halinde gösteren daha sonraki bir kenar çubuğu için de stil 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;
}
Gerektiğinde otomatik tamamlama arama çubuğu ve kaydırma paneli başlangıçta gizlenmiştir.
Index.html'deki "<!-- Autocomplete div goes here -->
" açıklamasını aşağıdaki kodla değiştirerek Otomatik Tamamlama widget'ı için bir div hazırlayın. Bu düzenleme yapılırken, slaytın div öğesini de ekleriz.
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, app.js
kodunun sonuna aşağıdaki kodu ekleyerek Otomatik tamamlama widget'ını haritaya eklemek için bir işlev tanımlayın.
uygulama.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 adresleri döndürecek şekilde kısıtlar (Yer Otomatik Tamamlama özelliği de işletme adları ve yönetici konumları için eşleşebilir) ve döndürülen adresleri yalnızca ABD'deki adreslerle sınırlar. İsteğe bağlı olan bu spesifikasyonları eklemek, kullanıcının aradığı adresi gösterecek şekilde tahminleri daraltmak için kullanıcının 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ıttaki her Yerle ilgili olarak hangi alanların döndürülmesi gerektiğini belirtir.
Son olarak, initialize
işlevinin sonundaki initAutocompleteWidget
işlevini çağırın. "// TODO: Initialize the Autocomplete widget
" yazan yorumu değiştirin.
app.js - ilk kullanıma hazırlama
// Initialize the Places Autocomplete Widget
initAutocompleteWidget();
Aşağıdaki komutu çalıştırarak sunucunuzu yeniden başlatın, ardından önizlemenizi yenileyin.
go run *.go
Şimdi haritanızın sağ üst köşesinde, yazdıklarınızla eşleşen ABD'deki adresleri gösteren ve otomatik olarak haritanın görünür alanına bakan bir Otomatik Tamamlama widget'ı göreceksiniz.
Kullanıcı Bir Başlangıç Adresi Seçtiğinde Haritasını Güncelleme
Şimdi, kullanıcının Otomatik tamamlama widget'ından bir tahmini seçmesi ve bunu mağazalarınıza olan mesafeleri hesaplamak için bir temel olarak kullanması gerekiyor.
Aşağıdaki kodu, app.js
dilinde initAutocompleteWidget
öğesinin sonuna ekleyerek // TODO: Respond when a user selects an address
" yorumunu değiştirin.
app.js - initAutocompleteWidget'dır
// 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 bir dinleyici ekler. Böylece, kullanıcı önerilerden birini tıkladığında harita seçilen adrese yeniden döner ve mesafe hesaplamalarınız için temel olarak kaynak gösterilir. Mesafe hesaplamalarını sonraki bir adımda uygularsınız.
Otomatik tamamlama arama çubuğuna bir adres girdikten sonra sunucunuzun yeniden ortalamasını gözlemlemek için sunucunuzu durdurup yeniden başlatın ve önizlemenizi yenileyin.
8. Cloud SQL ile ölçeklendirme
Şimdiye kadar oldukça başarılı bir mağaza bulucumuz var. Yalnızca arka planda bellekten yükleme yaparak (dosyadan tekrar tekrar okumak yerine) uygulamanın kullanacağı yaklaşık yüz yüzden fazla konumdan yararlanır. Peki, bulucunuzun farklı bir ölçekte çalışması gerekiyorsa ne olur? Büyük bir coğrafi alana (veya dünyanın dört bir yanına dağılmış) yüzlerce konumunuz varsa, bu yerlerin hepsinin bellekte olması artık en iyi fikir değil. Alt bölgeleri ayrı ayrı bölümlere ayırmak kendi sorunlarını ortaya çıkaracak.
Konumlarınızı bir veritabanından yüklemenin zamanı geldi. Bu adım için coğrafi JSON dosyanızdaki tüm konumları bir Cloud SQL veritabanına taşıyacağız ve bir istek geldiğinde söz konusu veritabanından yerel önbelleğinden sonuç almak için Go arka ucunu güncelleyeceğiz.
PostGres Veritabanı ile Cloud SQL Örneği oluşturma
Google Cloud Console üzerinden bir Cloud SQL örneği oluşturabilirsiniz ancak komut satırından örnek oluşturmak için gcloud
yardımcı programını kullanmak daha da 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ğini vermek için seçtiğimiz addır.tier
işareti, önceden tanımlanmış makineler arasından kolayca seçim yapabileceğiniz bir yöntemdir.db-custom-1-3840
değeri, oluşturulan örneğin bir Firestore'a ve yaklaşık 3,75 GB belleğe sahip olması gerektiğini belirtir.
Cloud SQL örneği, varsayılan kullanıcı postgres
olan bir PostGresSQL veritabanıyla oluşturulup başlatılır. Bu kullanıcının şifresi nedir? Güzel soru. İçerik yok. Oturum açabilmek için önce bir yapılandırma yapmanız gerekiyor.
Şifreyi aşağıdaki komutla ayarlayın:
gcloud sql users set-password postgres \ --instance=locations --prompt-for-password
İstendiğinde seçtiğiniz şifreyi girin.
PostGIS Uzantısını Etkinleştir
PostGIS, PostGresSQL için standart bir coğrafi veri türünün depolanmasını kolaylaştıran bir uzantıdır. Normal koşullarda, PostGIS'i veritabanımıza eklemek için tam yükleme işlemini gerçekleştirmemiz gerekir. Neyse ki PostGresSQL için Cloud SQL'in desteklenen uzantılarından biri.
Cloud kabuk terminalinde aşağıdaki komutla postgres
kullanıcısı olarak giriş yaparak veritabanı örneğine bağlanın.
gcloud sql connect locations --user=postgres --quiet
Oluşturduğunuz şifreyi girin. Şimdi postgres=>
komut istemine PostGIS uzantısını ekleyin.
CREATE EXTENSION postgis;
İşlem başarılı olursa çıkış, aşağıda gösterildiği gibi UZANTI OLUŞTUR'u okumalıdır.
Örnek komut çıkışı
CREATE EXTENSION
Son olarak, postgres=>
komut istemine çıkış komutu girerek veritabanı bağlantısından çıkın.
\q
Coğrafi Verileri Veritabanına Aktar
Şimdi tüm coğrafi verileri GeoJSON dosyalarından yeni veritabanımıza aktarmamız gerekiyor.
Neyse ki bu iyi seyahat eden bir sorundur ve internette bunu otomatikleştirmek için çeşitli araçlar bulabilirsiniz. Coğrafi verileri depolamak için birden yaygın kullanılan biçimler arasında dönüşüm gerçekleştiren, ogr2ogr adlı bir araç kullanacağız. Bu seçenekler arasında coğrafi JSON formunu SQL döküm dosyasına dönüştürmeyi biliyorsunuz. 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şturun
Önce ogr2ogr'u yükleyin.
sudo apt-get install gdal-bin
Ardından, SQL döküm dosyasını oluşturmak için ogr2ogr'u kullanın. Bu dosya austinrecycling
adında 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ılmasına dayalıdır. Başka bir dizinden çalıştırmanız gerekiyorsa data
öğesini, recycling-locations.geojson
dizininin depolandığı dizin yoluyla değiştirin.
Veritabanınızı geri dönüşüm konumlarıyla doldurun
Bu son komutun tamamlanmasından sonra artık komutu çalıştırdığınız dizinde datadump.sql,
dosyası olacaktır. Bunu açarsanız, bir yüz satırdan fazla SQL satırı, bir tablo oluşturma austinrecycling
ve konumlar ile doldurma işlemini görürsünüz.
Şimdi, veritabanı bağlantısını açın ve bu komut dosyasını aşağıdaki komutla çalıştırın.
gcloud sql connect locations --user=postgres --quiet < datadump.sql
Komut dosyası başarıyla çalışırsa son birkaç çıkış satırı aşağıdaki gibi görünecektir:
Örnek komut çıkışı
ALTER TABLE ALTER TABLE ATLER TABLE ALTER TABLE COPY 103 COMMIT WARNING: there is no transaction in progress COMMIT
Cloud SQL kullanmak için Go arka ucunu güncelleyin
Artık tüm bu verileri veritabanımızda bulunduğuna göre kodumuzu güncelleyebiliriz.
Konum bilgisi göndermek için kullanıcı arabirimini güncelleyin
Kullanıcı arabiriminde çok küçük bir güncellemeyle başlayalım: Artık bu uygulamayı, sorgu her çalıştırıldığında her konumun kullanıcı arabirimine teslim edilmesini istemediğimiz bir ölçekte yazdık. Bu nedenle, kullanıcının önemsediği konumla ilgili kullanıcı arabiriminden bazı temel bilgileri iletmemiz gerekiyor.
app.js
öğesini açın ve URL'ye olan 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();
};
Codelab'in bu adımı tamamlandı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çin bu laboratuvarda sağlanan örnek kod Austin, Teksas'ın merkezi koordinatlarını kullanır.
fetchStores
artık mağaza konumlarının yalnızca bir alt kümesini döndüreceği için, kullanıcı başlangıç konumunu değiştirdiğinde mağazaları tekrar getirmemiz gerekecek.
Yeni bir kaynak ayarlandığında konumları yenilemek için initAutocompleteWidget
işlevini güncelleyin. Bunun için iki düzenleme gereklidir:
- initAutocompleteWidget içinde
place_changed
işleyicisinin geri çağırmasını bulun. Mevcut daireleri temizleyen satırın açıklamasını kaldırın. Böylece, kullanıcı Yer Otomatik tamamlama aramasından bir adres seçtiğinde bu satır çalışmaya başlar.
app.js - initAutocompleteWidget
autocomplete.addListener("place_changed", async () => {
circles.forEach((c) => c.setMap(null)); // clear existing stores
// ...
- Seçili 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 yeni bir çağrıya geçirmek için "// TODO: Calculate the closest stores
" satırının üzerindeki satırın açıklamasını kaldırın.
app.js - initAutocompleteWidget
await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
Düz JSON dosyası yerine CloudSQL kullanmak için arka ucu güncelleyin
Düz dosya GeoJSON okuma ve önbelleğe alma işlemlerini kaldırın
İlk olarak, düz GeoJSON dosyasını yükleyip önbelleğe alan kodu kaldırmak için main.go
değerini değiştirin. Ayrıca Cloud SQL tarafından desteklenen bir dosya farklı bir dosyaya yazacağımız için dropoffsHandler
işlevinden de kaçınabiliriz.
Yeni main.go
metriğiniz ç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 İstekleri için yeni bir işleyici oluşturma
Şimdi, austin geri dönüşüm dizininde locations.go
adlı başka bir dosya oluşturalım. İlk olarak, konum istekleri için işleyiciyi yeniden uygulayı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 gerçekleştirir:
- İstek nesnesinden enlem ve boylamı alır (Bunları URL'ye nasıl eklediğimizi hatırlıyor musunuz? )
- Bu işlem, GeoJSON dizesi döndüren
getGeoJsonFromDatabase
çağrısını tetikler (Bunu daha sonra yazacağız.) - Bu JSON JSON dizesini yanıta yazdırmak için
ResponseWriter
kullanılır.
Şimdi de eş zamanlı kullanıcılarla veritabanı kullanımının iyi ölçeklenmesine yardımcı olmak için bir bağlantı havuzu oluşturacağız.
Bağlantı Havuzu Oluşturma
Bağlantı havuzu, sunucunun hizmet istekleri için yeniden kullanabileceği etkin veritabanı bağlantıları koleksiyonudur. Sunucunun etkin olan her kullanıcı için bağlantı oluşturup harcamak için zaman ayırması gerekmediğinden, etkin kullanıcı sayınız arttıkça çok fazla yük ortadan kaldırır. Önceki bölümde github.com/jackc/pgx/stdlib.
kitaplığını içe aktardığımızı fark etmiş olabilirsiniz. Bu, Go'daki bağlantı havuzlarıyla çalışmak için popüler bir kitaplıktır.
locations.go
öğesinin sonunda, bir bağlantı havuzunu başlatan bir initConnectionPool
işlevi (main.go
kaynağından çağrılır) oluşturun. Açıkça belirtmek gerekirse, bu snippet'te birkaç yardımcı yöntem kullanılır. configureConnectionPool
, bağlantı sayısı ve bağlantı başına kullanım ömrü gibi havuz ayarlarını değiştirebileceğiniz faydalı bir yer sağlar. mustGetEnv
, gerekli ortam değişkenlerini almak için çağrıları sarmalar. Bu nedenle, örnekte kritik bilgiler eksikse (bağlanacak veritabanının IP'si veya adı gibi) yararlı hata mesajları gönderilebilir.
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
}
Veritabanı için konumları sorgulayın, karşılığında JSON'u alın.
Şimdi, harita koordinatlarını alıp en yakın 25 konumu döndüren bir veritabanı sorgusu yazacağız. Ayrıca, bazı modern ve modern veritabanı işlevleri sayesinde bu verileri GeoJSON olarak döndürecek. Tüm bunların nihai sonucu, kullanıcı arabirimi kodunun anlayabildiği kadarıyla, hiçbir şey değişmedi. URL'ye yönelik bir talebi tetiklemeden önce çok fazla GeoJSON içeriyordu. Şimdi bir URL isteğini tetikleyiyor ve bir grup GeoJSON geri döndürüyor.
İşte bu büyüyü yerine getirme işlevi. locations.go
işlevinin alt kısmına yazdığınız işleyici ve bağlantı havuzu kodundan sonra 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ı isteğine yönelik etkinleştirme işlemi için kurulum, yıkma ve hata giderme ile ilgilidir. Veritabanı katmanında birçok ilginç işlem gerçekleştiren gerçek SQL'e bakalım. Böylece bunlardan herhangi birini koda uygulamak için endişelenmenize gerek yok.
Dize ayrıştırılıp tüm dize değişmez değerleri uygun yerlerine eklendikten sonra tetiklenen ham sorgu şöyle görünür:
ayrıştırılmış.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, tek bir 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 bir konum ile kullanıcının kullanıcı arabiriminde sağladığı konumun enlem/boylam çifti arasındaki mesafeyi belirlemek için ST_DISTANCE işlevini (PostGIS' konum ölçümü işlevleri paketinin bir parçası) kullanır. Sürüş mesafesi sağlayan Mesafe Matrisi'nin aksine, bunlar Coğrafi konum mesafeleridir. Daha sonra, verimliliği sağlamak için bu mesafeyi kullanarak kullanıcının bulunduğu konuma en yakın 25 konumu sıralar ve döndürür.
**SELECT json_build_object(‘type', ‘F
**eature') önceki sorguyu sarmalayarak sonuçları alır ve bir GeoJSON Özellik Özelliği oluşturmak için kullanır. Beklenmeyen bir düzeyde, bu sorgu maksimum yarıçapın uygulandığı &16.090&;# değerini, Go arka ucu tarafından belirtilen sabit sınır olan 10 mildeki metre sayısıdır. Bunun yerine, bu WHERE yan tümcesinin neden dahili sorguya (her bir yerin mesafesi belirlenir) eklenmediğini merak ediyorsanız bunun nedeni, SQL'in perde arkasında yürütme şeklidir. Bu nedenle, WHERE yan tümcesi incelendiği sırada bu alan hesaplanmamış olabilir. Hatta WHERE yan tümcesini iç sorguya taşımaya çalışırsanız bir hata verir.
**SELECT json_build_object(‘type', ‘FeatureColl
**ection') Bu sorgu, JSON tarafından oluşturulan sorgudan kaynaklanan tüm satırları bir GeoJSON FeatureCollection nesnesinde sarmalar.
Projenize PGX kitaplığı ekleme
Projenize tek bir bağımlılık eklememiz gerekiyor: PostGres Sürücü & Araç Seti (bağlantı havuzuna olanak tanır). Bunu yapmanın en kolay yolu Git Modülleri'ni kullanmaktır. Bulut kabuğunda şu komutla bir modül başlatın:
go mod init my_locator
Ardından, kodu bağımlılıklara göre taramak için komutu çalıştırın, mod dosyasına bağımlılık listesini ekleyin ve indirin.
go mod tidy
Son olarak, container'ları App Engine Flex için kolayca derleyebilmek üzere doğrudan proje dizininize çekmek için bu komutu çalıştırın.
go mod vendor
Tamam, test etmeye hazırsınız!
Deneyin
Tamam, ÇOK fazlasını yaptık. Şimdi bu işlevi inceleyelim.
Geliştirme makinenizin (hatta Cloud shell'in bile) veritabanına bağlanması için veritabanı bağlantısını yönetmek üzere Cloud SQL Proxy'yi kullanmamız gerekir. Cloud SQL Proxy'yi ayarlamak için:
- Cloud SQL Admin API'yi etkinleştirmek için buraya gidin
- Yerel geliştirme makinesi kullanıyorsanız bulut SQL proxy aracını yükleyin. Cloud shell'i kullanıyorsanız bu adımı atlayabilirsiniz. Yüklenmiş demektir. Talimatların bir hizmet hesabına işaret edeceğini unutmayın. Sizin için zaten bir hesap oluşturuldu. Bu bölümde, aşağıdaki bölümde bu hesaba gerekli izinleri eklemeyi ele alacağız.
- Proxy'yi başlatmak için yeni bir sekme oluşturun (bulut kabuğunda veya kendi terminalinizde).
https://console.cloud.google.com/sql/instances/locations/overview
sayfasını ziyaret edin ve aşağı kaydırarak Bağlantı adı alanını bulun. Sonraki komutta kullanmak üzere bu adı kopyalayın.- Bu sekmede, Cloud SQL proxy'sini bu komutla çalıştırarak
CONNECTION_NAME
öğesini önceki adımda gösterilen bağlantı adıyla değiştirin.
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 ve ardından sunucuyu daha önce yaptığınız gibi çalıştırın:
Henüz eklemediyseniz projenin kök dizinine gidin.
cd YOUR_PROJECT_ROOT
Aşağıdaki beş ortam değişkenini oluşturun (YOUR_PASSWORD_HERE
öğesini, yukarıda oluşturduğunuz şifreyle değiştirin).
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 pencere değişmemiş gibi çalışır: Bir başlangıç adresi girebilir, haritayı yakınlaştırabilir ve geri dönüşüm konumlarını tıklayabilirsiniz. Ancak şimdi veritabanı tarafından destekleniyor ve ölçeklenmeye hazır.
9. En yakın mağazaları listeleyin
Directions API, Google Haritalar uygulamasında yol tarifi isteme deneyimine benzer şekilde, ikisi arasında bir rota almak için tek bir başlangıç noktası ve tek bir varış noktası girer. Mesafe Matrisi API'si, seyahat süresi ve mesafelere dayalı olarak birden fazla olası kaynak ve birden fazla olası hedef arasındaki optimum eşlemeyi tanımlamak için bu kavramı daha da ileri taşır. Bu durumda, kullanıcının seçilen adrese en yakın mağazayı bulmasına yardımcı olmak için hedefler olarak bir kaynak ve mağaza konumu dizisi sağlarsınız.
Her mağazaya merkezden uzaklığı ekleyin
initMap
işlev tanımının başında, yorumu aşağıdaki kodla değiştirin: // TODO: Start Distance Matrix service
app.js - initMap
distanceMatrixService = new google.maps.DistanceMatrixService();
app.js
öğesinin sonuna calculateDistances
adlı yeni bir işlev ekleyin.
uygulama.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, tek bir kaynak olarak iletilen kaynağı ve bir dizi hedef olarak mağaza konumlarını kullanarak Mesafe Matrisi API'sini çağırır. Ardından, mağazanın kimliğini, insanların okuyabileceği bir dizeyle ifade edilen mesafeyi, sayısal değer olarak metre cinsinden mesafeyi depolayıp bir dizi dizi oluşturur ve diziyi sıralar.
Otomatik Yer Tamamlama arama çubuğundan yeni bir kaynak seçildiğinde mağaza mesafelerini hesaplamak için initAutocompleteWidget
işlevini güncelleyin. initAutocompleteWidget
işlevinin en altında, yorumu aşağıdaki kodla değiştirin "// TODO: Calculate the closest stores
":
app.js - initAutocompleteWidget'dır
// Use the selected address as the origin to calculate distances
// to each of the store locations
await calculateDistances(originLocation, stores);
renderStoresPanel();
Mağazaların mesafeye göre sıralanmış listesini görüntüleyin
Kullanıcı, en yakından en uzaka doğru sıralanan mağazaların listesini görmeyi bekler. Mağazaların görüntülenme sırasını bildirmek üzere calculateDistances
işlevi tarafından değiştirilen listeyi kullanarak her mağaza için yan panel bir liste doldurun.
app.js
öğesinin sonuna renderStoresPanel()
ve storeToPanelRow()
adlı iki yeni işlev ekleyin.
uygulama.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;
};
Sunucunuzu yeniden başlatın ve aşağıdaki komutu çalıştırarak önizlemenizi yenileyin.
go run *.go
Son olarak, Otomatik tamamlama arama çubuğuna Austin, TX adresi girin ve önerilerden birini tıklayın.
Haritada söz konusu adres merkeze gelmeli ve seçilen adresten uzaklık sırasına göre mağaza konumları listeleniyorsa bir kenar çubuğu görünmelidir. Aşağıda bir örnek verilmiştir:
10. Harita stilini belirleyin
Haritanızı görsel olarak ayırt etmenin yüksek etkili yollarından biri, haritanızın stilini belirlemektir. Bulut tabanlı harita stili sayesinde haritalarınızın özelleştirilmesi, Cloud tabanlı Harita Stili (beta) kullanılarak Cloud Console'dan kontrol edilir. Haritanızı beta olmayan bir özellikle şekillendirmek isterseniz, haritayı programatik olarak biçimlendirmek için json dosyası oluşturmanıza yardımcı olacak harita stili dokümanlarını kullanabilirsiniz. Aşağıdaki talimatlar Bulut Tabanlı Harita Stili (beta) konusunda size yol gösterir.
Harita kimliği oluşturma
Önce Cloud Console'u açın ve arama kutusuna "Harita Yönetimi" yazın. "Harita Yönetimi (Google Haritalar)" yazan sonucu tıklayın.
Üst tarafta, Arama kutusunun hemen altında Yeni Harita Kimliği Oluştur yazan bir düğme görürsünüz. Bu bölümü tıklayıp istediğiniz adı girin. Harita Türü için JavaScript'i seçtiğinizden emin olun ve başka seçenekler göründüğünde listeden Vektör'ü seçin. Sonuç, aşağıdakine benzeyecektir.
"İleri"yi tıkladığınızda yepyeni bir Harita Kimliği görürsünüz. İsterseniz şimdi kopyalayabilirsiniz, ancak daha sonra kolayca deneyebilirsiniz.
Şimdi, 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 bulunan "Harita Stilleri"ni tıklayın. Aksi takdirde, Harita Kimliği oluştururken olduğu gibi arama kutusuna "Harita Stilleri" yazıp sonuçlardaki "Harita Stilleri (Google Haritalar) seçeneğini belirleyerek doğru sayfayı bulabilirsiniz.
Üst kısma yakın bir yerde bulunan "+ Yeni Harita Stili Oluştur" düğmesini tıklayın.
- Bu laboratuvarda gösterilen haritadaki stille eşleştirme yapmak istiyorsanız "JSON'ı içe aktar" sekmesini tıklayın ve JSON blobunu aşağıya yapıştırın. Aksi takdirde, kendi haritanızı oluşturmak isterseniz, başlamak istediğiniz Harita Stilini seçin. Ardından, İleri'yi tıklayın.
- Seçtiğiniz harita kimliğini bu stille ilişkilendirmek için yeni oluşturduğunuz harita kimliğini seçin ve tekrar İleri'yi tıklayın.
- Bu aşamada, haritanızın stilini daha da özelleştirme seçeneği sunulur. Bu, keşfetmek istediğiniz bir şeyse Harita Düzenleyici'de Özelleştir'i tıklayın ve istediğiniz harita stiline ulaşana kadar renkler ve seçenekler arasında gezinin. Yoksa Atla'yı tıklayın.
- Bir 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.
[
{
"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 ekleyin
Bu harita stilini oluşturma konusunda sorun yaşadığınıza göre artık bu harita stilini kendi haritanızda nasıl KULLANIyorsunuz? İki küçük değişiklik yapmanız gerekir:
- Harita Kimliğini
index.html
ürünündeki komut dosyası etiketine URL olarak ekleyin Add
initMap()
yönteminizde haritayı oluşturduğunuzda, yapımcı bağımsız değişkeni olarak Harita Kimliği.
HTML dosyasında Haritalar JavaScript API'sini yükleyen komut dosyası etiketini, aşağıdaki yükleyici URL'siyle değiştirerek &&tt;YOUR_API_KEY
"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>
...
app.js
sabit değerinin map
tanımlandığı initMap
yönteminde, mapId
mülkü için satırın açıklamasını kaldırın ve az önce oluşturduğunuz Harita Kimliği ile değiştirin:YOUR_MAP_ID_HERE
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
Önizlemeniz yenilendikten sonra, harita tercihlerinize göre biçimlendirilmelidir. Yukarıdaki JSON stilini kullanan bir örneği burada görebilirsiniz.
11. Üretim için dağıtma
Uygulamanızın App Engine Flex'ten çalıştığını (yalnızca geliştirme makinenizde / Cloud Shell'de yaptığınız yerel bir web sunucusu değil) görmek istiyorsanız bu çok kolay bir işlemdir. Veritabanı erişiminin üretim ortamında çalışması için yalnızca birkaç şey eklememiz gerekir. Bu, App Engine Flex'ten Cloud SQL'e bağlanma hakkındaki doküman sayfasında özetlenmiştir.
App.yaml'ye 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 altına eklenmesi gerekir.
- Örnek bağlantı adını aramak için https://console.cloud.google.com/sql/instances/locations/overview adresini ziyaret edin.
- Aşağıdaki kodu
app.yaml
kodunun sonuna yapıştırın. YOUR_DB_PASSWORD_HERE
öğesini, daha öncepostgres
kullanıcı adı için oluşturduğunuz şifreyle değiştirin.YOUR_CONNECTION_NAME_HERE
değerini 1. adımdaki değerle değiştirin.
uygulama.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ığı için DB_TCP_HOST
öğesinin 172.17.0.1 değerine sahip olması gerektiğini unutmayın**.** Bunun nedeni, bir proxy aracılığıyla Cloud SQL ile olan iletişiminizin aynı olmasıdır.
App Engine Flex hizmet hesabına SQL İstemcisi izinleri ekleme
Cloud Console'da IAM Yöneticisi sayfasına gidin ve adı service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
biçimiyle eşleşen bir hizmet hesabı bulun. Bu, App Engine Flex'in veritabanına bağlanmak için kullanacağı hizmet hesabıdır. Satırın sonundaki Edit (Düzenle) düğmesini tıklayın ve Cloud SQL Client rolünü ekleyin.
Proje kodunuzu Go yoluna kopyalayın
App Engine'in kodunuzu çalıştırabilmesi için Go yolunda alakalı dosyaları bulabilmesi gerekir. Projenizin kök dizininde olduğunuzdan emin olun.
cd YOUR_PROJECT_ROOT
Dizini yol 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
KSA'yı kullanın. Dağıtım işlemi biraz zaman alır.
gcloud app deploy
Tamamen dağıtılmış, kurumsal düzeyde, estetik açıdan göz alıcı mağaza bulma aracınızı çalışırken görmek için tıklayacağınız bağlantıya ulaşmak için browse
komutunu kullanın.
gcloud app browse
gcloud
öğesini bulut kabuğunun dışında çalıştırıyorsanız gcloud app browse
çalıştırdığınızda yeni bir tarayıcı sekmesi açılır.
12. (Önerilir) Temizleme
Bu codelab'in gerçekleştirilmesi, BigQuery işleme ve Haritalar Platformu API çağrıları için ücretsiz katman sınırları içinde kalır, ancak bunu yalnızca eğitici bir alıştırma olarak yaptıysanız ve ileride herhangi bir ücret ödememek istiyorsanız bu projeyle ilişkili kaynakları silmenin en kolay yolu projeyi 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. Kodu girip Kapat'ı tıklayın.
Alternatif olarak, aşağıdaki komutu çalıştırıp GOOGLE_CLOUD_PROJECT
yer tutucusunu proje kimliğinizle değiştirerek tüm projeyi gcloud
ile doğrudan Cloud Shell'den silebilirsiniz:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. Tebrikler
Tebrikler! Codelab'i başarıyla tamamladınız.
Son sayfaya göz attınız. Tebrikler! Son sayfaya göz attınız.
Bu codelab boyunca aşağıdaki teknolojilerle çalıştınız:
- Maps JavaScript API
- Mesafe Matrisi Hizmeti, Haritalar JavaScript API'si (ayrıca Mesafe Matrisi API'si de vardır)
- Yerler Kitaplığı, Maps JavaScript API (ayrıca Places API)
- App Engine Esnek Ortamı (Git)
- Cloud SQL API'si
Daha Fazla Okuma
Bu teknolojiler hakkında öğrenilecek daha birçok şey var. Aşağıda, bu codelab'de ele almamız gereken zamanı olmayan konular için bazı faydalı bağlantılar verilmiştir. Ancak, belirli ihtiyaçlarınıza uyan bir mağaza bulma çözümü oluşturmak için kesinlikle işinize yarayabilir.