Google Haritalar Platformu ve Google Cloud ile tam kapsamlı bir mağaza bulma aracı oluşturun

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.

2ece59c64c06e9da.png

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:

  1. Faturalandırma hesabı oluşturun.
  2. Bir proje oluşturun.
  3. Google Haritalar Platformu API'lerini ve SDK'larını etkinleştirin (bir önceki bölümde listelenmiştir).
  4. 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 89665d8d348105cd.png seçeneğini tıklayın (temel hazırlığının yapılması ve ortama bağlanması yalnızca birkaç dakika sürer).

5f504766b9b3be17.png

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.

d3bb67d514893d1f.png

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.

b63f7baad67b6601.png

Ardından, düzenleyiciyi ve terminali yeni bir sekmeye taşımak için Yeni pencerede aç simgesini tıklayın.

3f6625ff8461c551.png

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:

2084fdd5ef594ece.png

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.

4155fc1dc717ac67.png

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.

  1. Cloud Shell örneğinizin komut satırı penceresine [CTRL] + [C] yazarak sunucuyu kapatın.
  2. austin-recycling dizininin içinde bir data 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.

58a6680e9c8e7396.png

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:

1af0ab72ad0eadc5.png

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 -->&quot 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.

58e9bbbcc4bf18d1.png

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&quot 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}&centerLng=${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:

  1. 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
    // ...
  1. 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:

  1. Cloud SQL Admin API'yi etkinleştirmek için buraya gidin
  2. 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.
  3. Proxy'yi başlatmak için yeni bir sekme oluşturun (bulut kabuğunda veya kendi terminalinizde).

bcca42933bfbd497.png

  1. 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.
  2. 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:

96e35794dd0e88c9.png

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.64036gg0ed200200.png

Ü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.

70f55a759b4c4212.png

"İ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.

9284cd200f1a9223.png

Üst kısma yakın bir yerde bulunan "+ Yeni Harita Stili Oluştur" düğmesini tıklayın.

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. Harita Kimliğini index.html ürünündeki komut dosyası etiketine URL olarak ekleyin
  2. 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.

2ece59c64c06e9da.png

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.

  1. Örnek bağlantı adını aramak için https://console.cloud.google.com/sql/instances/locations/overview adresini ziyaret edin.
  2. Aşağıdaki kodu app.yaml kodunun sonuna yapıştırın.
  3. YOUR_DB_PASSWORD_HERE öğesini, daha önce postgres kullanıcı adı için oluşturduğunuz şifreyle değiştirin.
  4. 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.

b04ccc0b4022b905.png

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:

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.