Tạo bộ định vị cửa hàng ngăn xếp đầy đủ với Google Maps Platform và Google Cloud

1. Giới thiệu

Bản tóm tắt

Hãy tưởng tượng bạn có nhiều địa điểm để đưa vào bản đồ và bạn muốn người dùng có thể thấy vị trí của những địa điểm này và xác định địa điểm họ muốn ghé thăm. Các ví dụ phổ biến về vấn đề này bao gồm:

  • công cụ định vị cửa hàng trên trang web của nhà bán lẻ
  • bản đồ địa điểm bỏ phiếu cho cuộc bầu cử sắp tới
  • một danh bạ các địa điểm chuyên biệt như thùng rác tái chế pin

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn sẽ tạo một bộ định vị để lấy thông tin từ nguồn cấp dữ liệu trực tiếp của những vị trí chuyên biệt và giúp người dùng tìm thấy vị trí gần điểm bắt đầu nhất của họ. Công cụ định vị toàn ngăn này có thể xử lý số lượng địa điểm lớn hơn nhiều so với công cụ định vị cửa hàng đơn giản, vốn chỉ giới hạn ở 25 vị trí cửa hàng.

2ece59c64c06e9da.png

Kiến thức bạn sẽ học được

Lớp học lập trình này sử dụng một tập dữ liệu mở để mô phỏng siêu dữ liệu được điền sẵn về một số lượng lớn vị trí cửa hàng thực tế. Nhờ đó, bạn có thể tập trung tìm hiểu các khái niệm chính về kỹ thuật.

  • API JavaScript của Maps: hiển thị số lượng lớn vị trí trên bản đồ web được tùy chỉnh
  • GeoJSON: một định dạng lưu trữ siêu dữ liệu về vị trí
  • Tự động hoàn thành địa điểm: giúp người dùng cung cấp vị trí bắt đầu nhanh hơn và chính xác hơn
  • Go: Ngôn ngữ lập trình được sử dụng để phát triển ứng dụng phụ trợ. Phụ trợ này sẽ tương tác với cơ sở dữ liệu và gửi kết quả truy vấn trở lại giao diện người dùng trong định dạng JSON.
  • App Engine: để lưu trữ ứng dụng web

Điều kiện tiên quyết

  • Kiến thức cơ bản về HTML và JavaScript
  • Tài khoản Google

2. Bắt đầu thiết lập

Trong Bước 3 của phần sau, hãy bật API JavaScript của Maps, API địa điểmAPI ma trận khoảng cách cho lớp học lập trình này.

Bắt đầu sử dụng Nền tảng Google Maps

Nếu bạn chưa từng sử dụng Nền tảng Google Maps, hãy làm theo Hướng dẫn bắt đầu sử dụng Nền tảng Google Maps hoặc xem Danh sách phát Bắt đầu với Nền tảng Google Maps để hoàn thành các bước sau:

  1. Tạo một tài khoản thanh toán.
  2. Tạo một dự án.
  3. Bật API và SDK của nền tảng Google Maps (được liệt kê trong phần trước).
  4. Tạo khoá API.

Kích hoạt Cloud Shell

Trong lớp học lập trình này, bạn sử dụng Cloud Shell, một môi trường dòng lệnh chạy trong Google Cloud, cung cấp quyền truy cập vào các sản phẩm và tài nguyên chạy trên Google Cloud để bạn có thể lưu trữ và chạy hoàn toàn dự án của mình trên trình duyệt web.

Để kích hoạt Cloud Shell từ Cloud Console, hãy nhấp vào Kích hoạt Cloud Shell 89665d8d348105cd.png (chỉ mất vài phút để cấp phép và kết nối với môi trường).

5f504766b9b3be17.png

Thao tác này sẽ mở một màn hình shell mới ở phần dưới của trình duyệt sau khi có thể hiển thị quảng cáo xen kẽ giới thiệu.

d3bb67d514893d1f.png

Xác nhận dự án của bạn

Sau khi kết nối với Cloud Shell, bạn sẽ thấy rằng bạn đã được xác thực và dự án này đã được đặt thành mã dự án mà bạn đã chọn trong quá trình thiết lập.

$ gcloud auth list
Credentialed Accounts:
ACTIVE  ACCOUNT
  *     <myaccount>@<mydomain>.com
$ gcloud config list project
[core]
project = <YOUR_PROJECT_ID>

Nếu vì lý do nào đó mà dự án chưa được đặt, hãy chạy lệnh sau:

gcloud config set project <YOUR_PROJECT_ID>

Bật API Appengine Flex

Bạn cần bật API Appengine Flex theo cách thủ công từ Cloud Console. Thao tác này không chỉ bật API mà còn tạo Tài khoản dịch vụ môi trường linh hoạt Appengine, tài khoản đã xác thực sẽ thay mặt người dùng tương tác với các dịch vụ của Google (như cơ sở dữ liệu SQL).

3. Xin chào!

Hệ thống phụ trợ: Hello World in Go

Trong phiên bản Cloud Shell, bạn sẽ bắt đầu bằng cách tạo một ứng dụng Go App Engine Flex sẽ làm cơ sở cho phần còn lại của lớp học lập trình.

Trong thanh công cụ của Cloud Shell, hãy nhấp vào nút Mở trình chỉnh sửa để mở trình soạn thảo mã trong thẻ mới. Trình chỉnh sửa mã dựa trên web này cho phép bạn dễ dàng chỉnh sửa các tệp trong thực thể Cloud Shell.

b63f7baad67b6601.png

Tiếp theo, nhấp vào biểu tượng Mở trong cửa sổ mới để di chuyển trình chỉnh sửa và thiết bị đầu cuối sang thẻ mới.

3f6625ff8461c551.png

Trong thiết bị đầu cuối ở cuối thẻ mới, hãy tạo thư mục austin-recycling mới.

mkdir -p austin-recycling && cd $_

Tiếp theo, bạn sẽ tạo một ứng dụng Go App Engine nhỏ để đảm bảo mọi thứ hoạt động. Xin chào!

Thư mục austin-recycling cũng sẽ xuất hiện trong danh sách thư mục của Trình chỉnh sửa ở bên trái. Trong thư mục austin-recycling, hãy tạo một tệp có tên app.yaml. Đặt nội dung sau đây vào tệp app.yaml:

app.yaml

runtime: go
env: flex

manual_scaling:
  instances: 1
resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

Tệp cấu hình này định cấu hình ứng dụng App Engine của bạn để sử dụng thời gian chạy Go Flex. Để biết thông tin cơ bản về ý nghĩa của các mục cấu hình trong tệp này, hãy xem Tài liệu về môi trường tiêu chuẩn của Google App Engine Go.

Tiếp theo, hãy tạo tệp main.go cùng với tệp app.yaml:

main.go [chính_sách]

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!")
}

Bạn nên tạm dừng một chút ở đây để hiểu mã này có chức năng gì, ít nhất là ở cấp độ cao. Bạn đã xác định gói main khởi động máy chủ http lắng nghe trên cổng 8080 và đăng ký hàm xử lý cho các yêu cầu HTTP khớp với đường dẫn "/".

Hàm trình xử lý, được gọi một cách dễ dàng là handler, viết ra chuỗi văn bản "Hello, world!". Văn bản này sẽ được chuyển tiếp trở lại trình duyệt của bạn, nơi bạn có thể đọc văn bản này. Trong các bước sau này, bạn sẽ tạo trình xử lý phản hồi bằng dữ liệu GeoJSON thay vì các chuỗi được mã hóa cứng đơn giản.

Sau khi thực hiện các bước này, bạn sẽ có một trình chỉnh sửa như sau:

2084fdd5ef594ece.png

Xem thử

Để kiểm tra ứng dụng này, bạn có thể chạy máy chủ phát triển App Engine bên trong phiên bản Cloud Shell. Quay lại dòng lệnh Cloud Shell và nhập nội dung sau:

go run *.go

Bạn sẽ thấy một số dòng kết quả nhật ký cho biết rằng bạn thực sự đang chạy máy chủ phát triển trên phiên bản Cloud Shell, trong đó ứng dụng web Hello Hello đang nghe trên cổng localhost 8080. Bạn có thể mở một thẻ trình duyệt web trên ứng dụng này bằng cách nhấn nút Xem trước trên web rồi chọn mục trong trình đơn Xem trước trên cổng 8080 trên thanh công cụ Cloud Shell.

4155fc1dc717ac67.png

Khi nhấp vào mục trong trình đơn này, bạn sẽ thấy một thẻ mới trong trình duyệt web có các từ "Xin chào, world!" được phân phát từ máy chủ phát triển App Engine.

Trong bước tiếp theo, bạn sẽ thêm dữ liệu tái chế Thành phố Austin vào ứng dụng này và bắt đầu hình ảnh hóa dữ liệu đó.

4. Xem dữ liệu hiện tại

GeoJSON, tiếng Pháp

Bước trước đó có đề cập rằng bạn sẽ tạo các trình xử lý trong mã Go để hiển thị dữ liệu GeoJSON vào trình duyệt web. Nhưng GeoJSON là gì?

Trong thế giới Hệ thống thông tin địa lý (GIS), chúng ta cần phải có khả năng truyền đạt kiến thức về thực thể địa lý giữa các hệ thống máy tính. Maps rất phù hợp để con người đọc, nhưng máy tính thường thích dữ liệu của họ ở định dạng dễ hiểu hơn.

GeoJSON là một định dạng để mã hóa các cấu trúc dữ liệu địa lý, chẳng hạn như tọa độ của các địa điểm trả lại hàng ở tái chế ở Austin, Texas. GeoJSON đã được chuẩn hóa trong tiêu chuẩn Lực lượng chuyên trách kỹ thuật Internet có tên là RFC7946. GeoJSON được định nghĩa về JSON, JavaScript Object Notation. Bản thân tiêu chuẩn này được chuẩn hóa trong ECMA-404, bởi cùng một tổ chức đã chuẩn hóa JavaScript, Ecma International.

Điều quan trọng là GeoJSON là một định dạng dây được hỗ trợ rộng rãi để truyền đạt kiến thức địa lý. Lớp học lập trình này sử dụng GeoJSON theo những cách sau:

  • Sử dụng các gói Go để phân tích cú pháp dữ liệu Austin vào một cấu trúc dữ liệu nội bộ cụ thể của GIS mà bạn sẽ sử dụng để lọc dữ liệu theo yêu cầu.
  • Chuyển đổi dữ liệu theo yêu cầu để chuyển đổi giữa máy chủ web và trình duyệt web.
  • Sử dụng thư viện JavaScript để chuyển đổi phản hồi thành các điểm đánh dấu trên bản đồ.

Điều này sẽ giúp bạn tiết kiệm được một lượng lớn dữ liệu nhập vào mã, vì bạn không cần phải viết trình phân tích cú pháp và trình tạo để chuyển đổi luồng dữ liệu trên dây thành các bản trình bày trong bộ nhớ.

Truy xuất dữ liệu

Cổng dữ liệu mở của Thành phố Austin, Texas cung cấp thông tin không gian địa lý về các tài nguyên công khai được sử dụng công khai. Trong lớp học lập trình này, bạn sẽ hình ảnh hóa tập dữ liệu vị trí thả tái chế.

Bạn sẽ hình ảnh hóa dữ liệu bằng các điểm đánh dấu trên bản đồ, được hiển thị bằng Lớp dữ liệu của API JavaScript của Maps.

Hãy bắt đầu bằng cách tải dữ liệu GeoJSON từ trang web Thành phố Austin xuống ứng dụng của bạn.

  1. Trong cửa sổ dòng lệnh của Cloud Shell phiên bản, hãy tắt máy chủ bằng cách nhập [CTRL] + [C].
  2. Tạo thư mục data bên trong thư mục austin-recycling và thay đổi thành thư mục đó:
mkdir -p data && cd data

Bây giờ, hãy sử dụng curl để truy xuất các vị trí tái chế:

curl "https://data.austintexas.gov/resource/qzi7-nx8g.geojson" -o recycling-locations.geojson

Cuối cùng, hãy thay đổi sao lưu vào thư mục mẹ.

cd ..

5. Lập bản đồ vị trí

Trước tiên, hãy cập nhật tệp app.yaml để phản ánh ứng dụng mạnh mẽ hơn và quot;không chỉ là một ứng dụng Hello Hello nữa!

app.yaml

runtime: go
env: flex

handlers:
- url: /
  static_files: static/index.html
  upload: static/index.html
- url: /(.*\.(js|html|css))$
  static_files: static/\1
  upload: static/.*\.(js|html|css)$
- url: /.*
  script: auto

manual_scaling:
  instances: 1
resources:
  cpu: 1
  memory_gb: 0.5
  disk_size_gb: 10

Cấu hình app.yaml này chuyển hướng các yêu cầu cho /, /*.js, /*.css/*.html đến một nhóm tệp tĩnh. Điều này có nghĩa là thành phần HTML tĩnh của ứng dụng sẽ được phân phát trực tiếp bằng cơ sở hạ tầng phân phối tệp của App Engine, chứ không phải ứng dụng Go. Điều này giúp giảm tải máy chủ và tăng tốc độ phân phát.

Bây giờ, đã đến lúc xây dựng ứng dụng phụ trợ cho ứng dụng của bạn trong Go!

Xây dựng hệ thống phụ trợ

Bạn có thể đã nhận thấy một điều thú vị mà tệp app.yaml không làm là hiển thị tệp GeoJSON. Đó là vì GeoJSON sẽ được xử lý và gửi bởi phụ trợ Go, cho phép chúng tôi xây dựng một số tính năng ưa thích trong các bước sau. Thay đổi tệp main.go để đọc như sau:

main.go [chính_sách]

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"])
}

Phụ trợ Go đã cung cấp cho chúng tôi một tính năng có giá trị: Phiên bản Appengine đang lưu tất cả các vị trí đó vào bộ nhớ đệm ngay khi khởi động. Điều này giúp tiết kiệm thời gian vì phần phụ trợ sẽ không phải đọc tệp từ ổ đĩa trong mỗi lần làm mới từ mọi người dùng!

Xây dựng giao diện người dùng

Điều đầu tiên chúng ta cần làm là tạo một thư mục để lưu giữ tất cả tài sản tĩnh của mình. Từ thư mục mẹ của dự án, hãy tạo một thư mục static.

mkdir -p static && cd static

Chúng ta sẽ tạo 3 tệp trong thư mục này.

  • index.html sẽ chứa tất cả HTML cho ứng dụng định vị một trang của bạn.
  • style.css , như bạn mong đợi, sẽ chứa kiểu
  • app.js sẽ chịu trách nhiệm truy xuất GeoJSON, thực hiện lệnh gọi đến API Maps và đặt các điểm đánh dấu trên bản đồ tùy chỉnh của bạn.

Tạo 3 tệp này và đảm bảo đặt chúng vào static/ .

style.css

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  display: flex;
}

#map {
  height: 100%;
  flex-grow: 4;
  flex-basis: auto;
}

index.html

<html>
  <head>
    <title>Austin recycling drop-off locations</title>
    <link rel="stylesheet" type="text/css" href="style.css" />
    <script src="app.js"></script>

    <script
      defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=weekly&libraries=places&callback=initialize&solution_channel=GMP_codelabs_fullstackstorelocator_v1_a"
    ></script>
  </head>

  <body>
    <div id="map"></div>
    <!-- Autocomplete div goes here -->
  </body>
</html>

Hãy đặc biệt chú ý đến URL src trong thẻ tập lệnh của phần tử head.

  • Thay thế văn bản phần giữ chỗ &YOUR_API_KEY" bằng khóa API mà bạn đã tạo trong bước thiết lập. Bạn có thể truy cập API và amp; Dịch vụ –> Trang thông tin xác thực trong Cloud Console để truy xuất khóa API hoặc tạo khóa mới.
  • Xin lưu ý rằng URL chứa thông số callback=initialize. We\39; sẽ tạo tệp JavaScript chứa hàm callback đó. Đây là nơi ứng dụng của bạn sẽ tải vị trí từ phần phụ trợ, gửi đến vị trí API Maps và sử dụng kết quả để đánh dấu vị trí tùy chỉnh trên bản đồ, tất cả đều được hiển thị đẹp mắt về trang web của bạn.
  • Thông số libraries=places tải Thư viện địa điểm. Điều này là cần thiết cho những tính năng như tự động hoàn thành địa chỉ sẽ được thêm vào sau này.

app.js

let distanceMatrixService;
let map;
let originMarker;
let infowindow;
let circles = [];
let stores = [];
// The location of Austin, TX
const AUSTIN = { lat: 30.262129, lng: -97.7468 };

async function initialize() {
  initMap();

  // TODO: Initialize an infoWindow

  // Fetch and render stores as circles on map
  fetchAndRenderStores(AUSTIN);

  // TODO: Initialize the Autocomplete widget
}

const initMap = () => {
  // TODO: Start Distance Matrix service

  // The map, centered on Austin, TX
  map = new google.maps.Map(document.querySelector("#map"), {
    center: AUSTIN,
    zoom: 14,
    // mapId: 'YOUR_MAP_ID_HERE',
    clickableIcons: false,
    fullscreenControl: false,
    mapTypeControl: false,
    rotateControl: true,
    scaleControl: false,
    streetViewControl: true,
    zoomControl: true,
  });
};

const fetchAndRenderStores = async (center) => {
  // Fetch the stores from the data source
  stores = (await fetchStores(center)).features;

  // Create circular markers based on the stores
  circles = stores.map((store) => storeToCircle(store, map));
};

const fetchStores = async (center) => {
  const url = `/data/dropoffs`;
  const response = await fetch(url);
  return response.json();
};

const storeToCircle = (store, map) => {
  const [lng, lat] = store.geometry.coordinates;
  const circle = new google.maps.Circle({
    radius: 50,
    strokeColor: "#579d42",
    strokeOpacity: 0.8,
    strokeWeight: 5,
    center: { lat, lng },
    map,
  });

  return circle;
};

Mã này sẽ hiển thị vị trí cửa hàng trên bản đồ. Để kiểm tra nội dung chúng ta có từ trước đến nay, từ dòng lệnh trở về thư mục mẹ:

cd ..

Bây giờ, hãy chạy lại ứng dụng của bạn ở chế độ phát triển bằng:

go run *.go

Xem trước như bạn đã làm trước đó. Bạn sẽ thấy một bản đồ có các vòng tròn nhỏ màu xanh lục như thế này.

58a6680e9c8e7396.png

Bạn đang hiển thị vị trí bản đồ và chúng tôi chỉ đi được một nửa lớp học lập trình! Thật tuyệt! Bây giờ, hãy thêm một số yếu tố tương tác.

6. Hiển thị thông tin chi tiết theo yêu cầu

Trả lời sự kiện nhấp vào các điểm đánh dấu bản đồ

Hiển thị một loạt điểm đánh dấu trên bản đồ là một khởi đầu tuyệt vời, nhưng chúng tôi thực sự cần khách truy cập có thể nhấp vào một trong các điểm đánh dấu đó và xem thông tin về vị trí đó (như tên của doanh nghiệp, địa chỉ, v.v.). Tên của cửa sổ thông tin nhỏ thường bật lên khi bạn nhấp vào điểm đánh dấu Google Maps là Cửa sổ thông tin.

Tạo đối tượng infowindow. Thêm phần sau vào hàm initialize, thay thế dòng được nhận xét có nội dung là "// TODO: Initialize an info window".

app.js – khởi tạo

  // 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();

Thay thế định nghĩa hàm fetchAndRenderStores bằng phiên bản hơi khác này, thay đổi dòng cuối cùng để gọi storeToCircle bằng một đối số bổ sung, infowindow:

app.js –fetchAndRenderingStores

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));
};

Thay thế định nghĩa storeToCircle bằng phiên bản dài hơn một chút. Bây giờ, phiên bản này sẽ lấy Cửa sổ thông tin làm đối số thứ ba:

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;
};

Mã mới ở trên hiển thị infoWindow với thông tin của cửa hàng đã chọn bất cứ khi nào người dùng nhấp vào một điểm đánh dấu cửa hàng trên bản đồ.

Nếu máy chủ của bạn vẫn đang chạy, hãy dừng máy rồi khởi động lại. Làm mới trang bản đồ của bạn và thử nhấp vào một điểm đánh dấu bản đồ. Một cửa sổ thông tin nhỏ sẽ xuất hiện cùng với tên và địa chỉ của doanh nghiệp, trông giống như sau:

1af0ab72ad0eadc5.png

7. Xem vị trí bắt đầu của người dùng

Người dùng công cụ định vị cửa hàng thường muốn biết cửa hàng nào gần họ nhất hoặc một địa chỉ mà họ dự định bắt đầu hành trình của mình. Thêm thanh tìm kiếm Tự động hoàn thành địa điểm để cho phép người dùng dễ dàng nhập địa chỉ bắt đầu. Tính năng Tự động hoàn thành địa điểm cung cấp chức năng đánh dấu tương tự như cách hoạt động của tính năng Tự động hoàn thành trong các thanh tìm kiếm khác của Google, ngoại trừ dự đoán là tất cả Địa điểm trong Nền tảng Google Maps.

Tạo trường nhập dữ liệu cho người dùng

Quay lại chỉnh sửa style.css để thêm kiểu cho thanh tìm kiếm Tự động hoàn thành và bảng kết quả bên được liên kết. Trong khi cập nhật kiểu CSS, chúng tôi cũng sẽ thêm kiểu cho thanh bên trong tương lai hiển thị thông tin cửa hàng dưới dạng danh sách để đi kèm với bản đồ.

Thêm mã này vào cuối tệp.

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;
}

Cả thanh tìm kiếm của tính năng Tự động hoàn thành và bảng trượt sẽ đều bị ẩn cho đến khi cần thiết.

Chuẩn bị một div cho tiện ích Tự động hoàn thành bằng cách thay thế nhận xét trong index.html đọc "<!-- Autocomplete div goes here -->" bằng mã sau. Trong khi chỉnh sửa, chúng tôi cũng sẽ thêm div cho bảng điều khiển dạng trượt.

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>

Bây giờ, hãy xác định một hàm để thêm tiện ích Tự động hoàn thành vào bản đồ bằng cách thêm mã sau vào cuối app.js.

app.js

const initAutocompleteWidget = () => {
  // Add search bar for auto-complete
  // Build and add the search bar
  const placesAutoCompleteCardElement = document.getElementById("pac-card");
  const placesAutoCompleteInputElement = placesAutoCompleteCardElement.querySelector(
    "input"
  );
  const options = {
    types: ["address"],
    componentRestrictions: { country: "us" },
    map,
  };
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(
    placesAutoCompleteCardElement
  );
  // Make the search bar into a Places Autocomplete search bar and select
  // which detail fields should be returned about the place that
  // the user selects from the suggestions.
  const autocomplete = new google.maps.places.Autocomplete(
    placesAutoCompleteInputElement,
    options
  );
  autocomplete.setFields(["address_components", "geometry", "name"]);
  map.addListener("bounds_changed", () => {
    autocomplete.setBounds(map.getBounds());
  });

  // TODO: Respond when a user selects an address
};

Mã này hạn chế các nội dung đề xuất Tự động hoàn thành chỉ trả lại địa chỉ (vì tính năng Tự động hoàn thành địa điểm cũng có thể khớp với tên cơ sở và vị trí quản trị) và giới hạn địa chỉ được trả về chỉ những địa chỉ ở Hoa Kỳ. Việc thêm các thông số tùy chọn này sẽ giảm số lượng ký tự mà người dùng cần nhập để thu hẹp cụm từ gợi ý để hiển thị địa chỉ mà họ đang tìm kiếm.

Sau đó, hệ thống này sẽ di chuyển div Tự động hoàn thành mà bạn đã tạo vào góc trên cùng bên phải của bản đồ và chỉ định trường nào sẽ được trả về về mỗi Địa điểm trong nội dung phản hồi.

Cuối cùng, gọi hàm initAutocompleteWidget ở cuối hàm initialize, thay thế nhận xét có nội dung là "// TODO: Initialize the Autocomplete widget"

app.js – khởi chạy

 // Initialize the Places Autocomplete Widget
 initAutocompleteWidget();

Khởi động lại máy chủ bằng cách chạy lệnh sau, sau đó làm mới bản xem trước của bạn.

go run *.go

Bạn sẽ thấy tiện ích Tự động hoàn thành ở góc trên cùng bên phải của bản đồ ngay bây giờ, tiện ích này sẽ hiển thị cho bạn các địa chỉ ở Hoa Kỳ khớp với nội dung bạn nhập, thiên về khu vực hiển thị của bản đồ.

58e9bbów4bf18d1.png

Cập nhật bản đồ khi người dùng chọn địa chỉ bắt đầu

Giờ đây, bạn cần xử lý khi người dùng chọn một cụm từ gợi ý từ tiện ích Tự động hoàn thành và sử dụng vị trí đó làm cơ sở để tính khoảng cách đến các cửa hàng của bạn.

Thêm mã sau vào cuối initAutocompleteWidget trong app.js, thay thế nhận xét "// TODO: Respond when a user selects an address".

app.js – initAutoFillWidget

  // 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
  });

Mã này sẽ thêm một trình xử lý để khi người dùng nhấp vào một trong các đề xuất, bản đồ sẽ được căn cứ vào địa chỉ đã chọn và đặt nguồn gốc làm cơ sở cho các phép tính khoảng cách của bạn. Bạn triển khai các phép tính khoảng cách trong một bước sau này.

Dừng và khởi động lại máy chủ của bạn, rồi làm mới bản xem trước để quan sát lại bản đồ căn giữa bản đồ sau khi bạn nhập địa chỉ vào thanh tìm kiếm tự động hoàn thành.

8. Mở rộng quy mô bằng Cloud SQL

Cho đến nay, chúng ta đã có một công cụ định vị cửa hàng khá tốt. Tận dụng thực tế là chỉ có khoảng một trăm vị trí mà ứng dụng sẽ sử dụng, bằng cách tải chúng vào bộ nhớ trên phần phụ trợ (thay vì đọc liên tục từ tệp). Nhưng nếu bộ định vị của bạn cần hoạt động ở quy mô khác thì sao? Nếu bạn có hàng trăm vị trí nằm rải rác trên một khu vực địa lý rộng lớn (hoặc hàng nghìn vị trí trên toàn thế giới), việc không còn lưu trữ tất cả các vị trí đó trong bộ nhớ là ý tưởng hay nhất và việc tách các khu vực thành các tệp riêng lẻ sẽ gây ra các vấn đề riêng.

Đã đến lúc tải các vị trí của bạn từ một cơ sở dữ liệu. Đối với bước này, chúng ta sẽ di chuyển tất cả các vị trí trong tệp GeoJSON vào cơ sở dữ liệu Cloud SQL và cập nhật phần phụ trợ Go để lấy kết quả từ cơ sở dữ liệu đó thay vì từ bộ nhớ đệm cục bộ bất cứ khi nào có yêu cầu.

Tạo một thực thể Cloud SQL bằng Cơ sở dữ liệu PostGres

Bạn có thể tạo một phiên bản Cloud SQL thông qua Google Cloud Console, nhưng việc sử dụng tiện ích gcloud để tạo một phiên bản từ dòng lệnh sẽ dễ dàng hơn nữa. Trong Cloud Shell, hãy tạo một thực thể Cloud SQL bằng lệnh sau:

gcloud sql instances create locations \
--database-version=POSTGRES_12 \
--tier=db-custom-1-3840 --region=us-central1
  • Đối số locations là tên mà chúng tôi chọn để đặt cho phiên bản Cloud SQL này.
  • Cờ tier là một cách để chọn trong số các máy được xác định trước thuận tiện.
  • Giá trị db-custom-1-3840 cho biết phiên bản đang được tạo phải có một vCPU và khoảng 3,75 GB bộ nhớ.

Thực thể Cloud SQL sẽ được tạo và khởi tạo bằng cơ sở dữ liệu PostGresSQL, với người dùng mặc định là postgres. Mật khẩu của người dùng này là gì? Câu hỏi rất hay! Họ không có. Bạn cần thiết lập một tài khoản trước khi đăng nhập.

Đặt mật khẩu bằng lệnh sau:

gcloud sql users set-password postgres \
    --instance=locations --prompt-for-password

Sau đó, hãy nhập mật khẩu mà bạn đã chọn khi được nhắc.

Bật tiện ích PostGIS

PostGIS là một tiện ích cho PostGresSQL, giúp dễ dàng lưu trữ các loại dữ liệu không gian địa lý chuẩn. Trong những trường hợp bình thường, chúng tôi phải trải qua quá trình cài đặt đầy đủ để thêm PostGIS vào cơ sở dữ liệu của mình. Rất may là đây là một trong những tiện ích hỗ trợ Cloud Cloud của PostGresSQL.

Kết nối với thực thể cơ sở dữ liệu bằng cách đăng nhập với tư cách là người dùng postgres bằng lệnh sau trong thiết bị đầu cuối shell đám mây.

gcloud sql connect locations --user=postgres --quiet

Nhập mật khẩu bạn vừa tạo. Bây giờ, hãy thêm tiện ích PostGIS vào lời nhắc lệnh postgres=>.

CREATE EXTENSION postgis;

Nếu thành công, kết quả sẽ đọc là TẠO TIỆN ÍCH, như hiển thị bên dưới.

Ví dụ về kết quả lệnh

CREATE EXTENSION

Cuối cùng, thoát kết nối cơ sở dữ liệu bằng cách nhập lệnh thoát tại lời nhắc lệnh postgres=>.

\q

Nhập dữ liệu địa lý vào cơ sở dữ liệu

Bây giờ, chúng ta cần nhập tất cả dữ liệu vị trí đó từ các tệp GeoJSON vào cơ sở dữ liệu mới của chúng tôi.

Rất may là đây là một vấn đề đi đúng hướng và một số công cụ có thể được tìm thấy trên Internet để tự động hóa điều này cho bạn. Chúng tôi sẽ sử dụng công cụ có tên ogr2ogr để chuyển đổi giữa nhiều định dạng phổ biến để lưu trữ dữ liệu không gian địa lý. Trong số đó, có nghĩa là bạn sẽ đoán được nó là đang chuyển đổi biểu mẫu GeoJSON thành tệp kết xuất SQL. Sau đó, bạn có thể sử dụng tệp kết xuất SQL để tạo bảng & cột cho cơ sở dữ liệu và tải tệp bằng tất cả dữ liệu tồn tại trong tệp GeoJSON của bạn.

Tạo tệp kết xuất SQL

Trước tiên, hãy cài đặt ogr2ogr.

sudo apt-get install gdal-bin

Tiếp theo, hãy dùng ogr2ogr để tạo tệp kết xuất SQL. Tệp này sẽ tạo một bảng tên là austinrecycling.

ogr2ogr --config PG_USE_COPY YES -f PGDump datadump.sql \
data/recycling-locations.geojson -nln austinrecycling

Lệnh trên dựa trên việc chạy từ thư mục austin-recycling. Nếu bạn cần chạy tệp này từ một thư mục khác, hãy thay thế data bằng đường dẫn đến thư mục lưu trữ recycling-locations.geojson.

Điền sẵn các cơ sở dữ liệu của bạn vào cơ sở dữ liệu

Sau khi hoàn tất lệnh cuối cùng đó, giờ đây bạn sẽ có một tệp datadump.sql, trong cùng thư mục mà bạn đã chạy lệnh. Nếu mở bảng, bạn sẽ thấy hơn một trăm dòng SQL, tạo bảng austinrecycling và điền bảng với các vị trí.

Bây giờ, hãy mở kết nối tới cơ sở dữ liệu và chạy tập lệnh đó bằng lệnh sau.

gcloud sql connect locations --user=postgres --quiet < datadump.sql

Nếu tập lệnh chạy thành công, đây là giao diện của một vài dòng đầu ra cuối cùng:

Kết quả lệnh mẫu

ALTER TABLE
ALTER TABLE
ATLER TABLE
ALTER TABLE
COPY 103
COMMIT
WARNING: there is no transaction in progress
COMMIT

Cập nhật giao diện người dùng Go Go để sử dụng Cloud SQL

Bây giờ, chúng tôi đã có tất cả dữ liệu trong cơ sở dữ liệu của mình, đã đến lúc cập nhật mã.

Cập nhật giao diện người dùng để gửi thông tin vị trí

Hãy bắt đầu với một bản cập nhật rất nhỏ cho giao diện người dùng: Bởi vì chúng ta hiện đang viết ứng dụng này với quy mô lớn mà chúng ta không muốn mọi vị trí phân phối giao diện người dùng mỗi khi truy vấn chạy, nên chúng ta cần chuyển một số thông tin cơ bản từ giao diện người dùng đến vị trí mà người dùng quan tâm.

Mở app.js và thay thế định nghĩa hàm fetchStores bằng phiên bản này để đưa vĩ độ và kinh độ quan tâm vào URL.

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();
};

Sau khi hoàn thành bước này trong lớp học lập trình, nội dung phản hồi sẽ chỉ trả về những cửa hàng gần nhất với tọa độ bản đồ mà bạn cung cấp trong tham số center. Đối với lần tìm nạp ban đầu trong hàm initialize, mã mẫu được cung cấp trong phòng thí nghiệm này sử dụng tọa độ trung tâm cho Austin, Texas.

Vì từ nay fetchStores sẽ chỉ trả về một số vị trí cửa hàng, nên chúng tôi sẽ phải tìm nạp lại các cửa hàng mỗi khi người dùng thay đổi vị trí xuất phát.

Cập nhật hàm initAutocompleteWidget để làm mới vị trí mỗi khi bạn đặt một nguồn gốc mới. Điều này yêu cầu hai nội dung chỉnh sửa:

  1. Trong hàm init Nói và dùng, hãy tìm lệnh gọi lại cho trình nghe place_changed. Bỏ nhận xét dòng xóa các vòng kết nối hiện có để dòng đó sẽ chạy mỗi khi người dùng chọn địa chỉ từ tìm kiếm Tự động hoàn thành địa điểm.

app.js – initAutoFillWidget

  autocomplete.addListener("place_changed", async () => {
    circles.forEach((c) => c.setMap(null)); // clear existing stores
    // ...
  1. Bất cứ khi nào nguồn gốc đã chọn được thay đổi, nguồn gốc biến sẽ được cập nhật. Ở cuối hàm "place_changed" callback, hãy bỏ nhận xét dòng phía trên "// TODO: Calculate the closest stores" dòng để chuyển nguồn gốc mới này đến một lệnh gọi mới đến hàm fetchAndRenderStores.

app.js – initAutoFillWidget

    await fetchAndRenderStores(originLocation.toJSON());
    // TODO: Calculate the closest stores

Cập nhật giao diện người dùng để sử dụng CloudSQL thay vì tệp JSON phẳng

Xóa hoạt động đọc và lưu vào bộ nhớ đệm của tệp JSON cố định

Trước tiên, hãy thay đổi main.go để xóa mã tải và lưu vào bộ nhớ đệm tệp JSON JSON phẳng. Chúng ta cũng có thể loại bỏ hàm dropoffsHandler, vì chúng ta sẽ viết một hàm do Cloud SQL cung cấp trong một tệp khác.

main.go mới của bạn sẽ ngắn hơn rất nhiều.

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)
        }
}

Tạo trình xử lý mới cho Yêu cầu vị trí

Bây giờ, hãy tạo một tệp khác, locations.go, cũng trong thư mục tái chế austin. Hãy bắt đầu bằng cách triển khai lại trình xử lý cho các yêu cầu về vị trí.

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)
}

Trình xử lý thực hiện các thao tác quan trọng sau:

  • Tham số này lấy vĩ độ và kinh độ từ đối tượng yêu cầu (Bạn có nhớ rằng chúng ta đã thêm các vĩ độ và kinh độ đó vào URL không? )
  • Thao tác này sẽ kích hoạt lệnh gọi getGeoJsonFromDatabase, sẽ trả về chuỗi GeoJSON (Chúng tôi sẽ viết nội dung này sau.)
  • Phương thức này sử dụng ResponseWriter để in chuỗi GeoJSON của bạn cho phản hồi.

Tiếp theo, chúng ta sẽ tạo một nhóm kết nối để giúp mở rộng phạm vi sử dụng cơ sở dữ liệu cho người dùng đồng thời.

Tạo nhóm kết nối

Nhóm kết nối là một tập hợp các kết nối cơ sở dữ liệu đang hoạt động mà máy chủ có thể sử dụng lại để đáp ứng yêu cầu của người dùng. Loại bỏ này sẽ làm giảm rất nhiều chi phí khi số lượng người dùng đang hoạt động mở rộng quy mô vì máy chủ không phải dành thời gian tạo và hủy bỏ các kết nối cho mọi người dùng đang hoạt động. Có thể bạn đã nhận thấy trong phần trước chúng tôi đã nhập thư viện github.com/jackc/pgx/stdlib. Đây là thư viện phổ biến để làm việc với các nhóm kết nối trong Go.

Vào cuối locations.go, hãy tạo một hàm initConnectionPool (được gọi từ main.go) để khởi chạy nhóm kết nối. Để làm rõ, một số phương thức trợ giúp được sử dụng trong đoạn mã này. configureConnectionPool cung cấp một vị trí thuận tiện để điều chỉnh các chế độ cài đặt nhóm như số lượng kết nối và thời gian hoạt động trên mỗi kết nối. mustGetEnv bao gồm các lệnh gọi để lấy các biến môi trường bắt buộc, do đó, các thông báo lỗi hữu ích có thể được gửi nếu thực thể thiếu thông tin quan trọng (như IP hoặc tên của cơ sở dữ liệu để kết nối).

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
}

Truy vấn cơ sở dữ liệu vị trí, nhận lại JSON.

Bây giờ chúng ta sẽ viết một truy vấn cơ sở dữ liệu lấy tọa độ bản đồ và trả về 25 vị trí gần nhất. Không chỉ có vậy, nhờ một số chức năng cơ sở dữ liệu hiện đại, ưa thích, nó sẽ trả về dữ liệu đó dưới dạng GeoJSON. Kết quả cuối cùng là tất cả những gì mã giao diện người dùng có thể nhận thấy, không có gì thay đổi. Trước khi nó kích hoạt một yêu cầu đến một URL và nhận được một loạt GeoJSON. Bây giờ, nó sẽ kích hoạt một yêu cầu đến một URL và... lấy lại một loạt GeoJSON.

Đây là chức năng thực hiện điều kỳ diệu đó. Thêm hàm sau sau trình xử lý và mã gộp nhóm kết nối mà bạn vừa viết ở cuối locations.go.

locations.go

func getGeoJSONFromDatabase(centerLat string, centerLng string) (string, error) {

        // Obviously you can one-line this, but for testing purposes let's make it easy to modify on the fly.
        const milesRadius = 10
        const milesToMeters = 1609
        const radiusInMeters = milesRadius * milesToMeters

        const tableName = "austinrecycling"

        var queryStr = fmt.Sprintf(
                `SELECT jsonb_build_object(
                        'type',
                        'FeatureCollection',
                        'features',
                        jsonb_agg(feature)
                )
        FROM (
                        SELECT jsonb_build_object(
                                        'type',
                                        'Feature',
                                        'id',
                                        ogc_fid,
                                        'geometry',
                                        ST_AsGeoJSON(wkb_geometry)::jsonb,
                                        'properties',
                                        to_jsonb(row) - 'ogc_fid' - 'wkb_geometry'
                                ) AS feature
                        FROM (
                                        SELECT *,
                                                ST_Distance(
                                                        ST_GEOGFromWKB(wkb_geometry),
                                                        -- Los Angeles (LAX)
                                                        ST_GEOGFromWKB(st_makepoint(%v, %v))
                                                ) as distance
                                        from %v
                                        order by distance
                                        limit 25
                                ) row
                        where distance < %v
                ) features
                `, centerLng, centerLat, tableName, radiusInMeters)

        log.Println(queryStr)

        rows, err := db.Query(queryStr)

        defer rows.Close()

        rows.Next()
        queryResult := result{}
        err = rows.Scan(&queryResult.featureCollection)
        return queryResult.featureCollection, err
}

Chức năng này chủ yếu chỉ thiết lập, chia nhỏ và xử lý lỗi để kích hoạt một yêu cầu đến cơ sở dữ liệu. Hãy xem SQL thực tế, điều này đang làm rất nhiều việc thực sự thú vị ở lớp cơ sở dữ liệu, vì vậy bạn sẽ không phải lo lắng về việc triển khai bất kỳ SQL nào trong mã.

Truy vấn thô được kích hoạt sau khi chuỗi đã được phân tích cú pháp và tất cả ký tự chuỗi được chèn vào vị trí thích hợp, trông giống như sau:

được phân tích cú pháp.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

Truy vấn này có thể được xem là một truy vấn chính và một số hàm bao bọc JSON.

SELECT * ... LIMIT 25 chọn tất cả trường cho từng vị trí. Sau đó, tính năng này sử dụng hàm ST_DISTANCE (thành phần của bộ chức năng đo địa lý của PostGIS để xác định khoảng cách giữa từng vị trí trong cơ sở dữ liệu và cặp vĩ độ/dài của vị trí mà người dùng đã cung cấp trong giao diện người dùng). Hãy nhớ rằng không giống như Ma trận khoảng cách, có thể cung cấp cho bạn khoảng cách lái xe, đó là khoảng cách GeoSpatial. Để đạt được hiệu quả, chiến dịch này sử dụng khoảng cách đó để sắp xếp và trả về 25 vị trí gần nhất với vị trí do người dùng chỉ định.

**SELECT json_build_object(‘type', ‘F**đại diện#39;) bao gồm truy vấn trước đó, lấy kết quả và dùng kết quả để tạo đối tượng Tính năng GeoJSON. Thật bất ngờ, truy vấn này cũng là nơi bán kính tối đa được áp dụng "16090" là số mét tính bằng 10 dặm, giới hạn cứng do phần phụ trợ Go chỉ định. Nếu bạn tự hỏi tại sao điều kiện WHERE này không được thêm vào truy vấn bên trong (trong đó khoảng cách của mỗi vị trí được xác định), thì đó là do cách SQL thực thi phía sau hậu trường, nên trường đó có thể không được tính toán khi mệnh đề WHERE được kiểm tra. Trên thực tế, nếu bạn cố gắng di chuyển mệnh đề WHERE này đến truy vấn bên trong, thì hệ thống sẽ gửi lỗi.

**SELECT json_build_object(‘type', ‘FeatureColl**ection#39;) Truy vấn này bao gồm tất cả các hàng hiện có từ truy vấn tạo JSON trong đối tượng GeoJSON FeatureCollection.

Thêm thư viện PGX vào dự án của bạn

Chúng tôi cần thêm một phần phụ thuộc vào dự án của bạn: PostGres Trình điều khiển và amp; Bộ công cụ cho phép gộp kết nối. Cách dễ nhất để thực hiện việc này là sử dụng Mô-đun Go. Khởi tạo một mô-đun bằng lệnh này trong shell Cloud:

go mod init my_locator

Tiếp theo, chạy lệnh này để quét mã cho các phần phụ thuộc, thêm danh sách các phần phụ thuộc vào tệp mod và tải các phần phụ thuộc này xuống.

go mod tidy

Cuối cùng, hãy chạy lệnh này để lấy các phần phụ thuộc trực tiếp vào thư mục dự án của bạn để có thể dễ dàng tạo vùng chứa cho Appengine Flex.

go mod vendor

Bạn đã sẵn sàng dùng thử!

Xem thử

Được rồi, chúng tôi vừa có xong RẤT NHIỀU. Hãy xem nó hoạt động!

Để máy phát triển của bạn (có, thậm chí có cả Cloud Shell) kết nối với cơ sở dữ liệu, chúng tôi sẽ phải dùng Proxy Cloud SQL để quản lý kết nối cơ sở dữ liệu. Để thiết lập Cloud SQL Proxy:

  1. Truy cập vào đây để bật API quản trị Cloud SQL
  2. Nếu bạn sử dụng máy phát triển cục bộ, hãy cài đặt công cụ proxy SQL trên đám mây. Nếu đang sử dụng Cloud Shell, bạn có thể bỏ qua bước này. Bước này đã được cài đặt! Xin lưu ý rằng các hướng dẫn này sẽ đề cập đến tài khoản dịch vụ. Một chế độ xem đã được tạo cho bạn và chúng tôi sẽ đề cập đến việc thêm các quyền cần thiết cho tài khoản đó trong phần sau.
  3. Tạo một thẻ mới (trong Cloud Shell hoặc thiết bị đầu cuối của riêng bạn) để bắt đầu proxy.

SHAa42933bfbd497.png

  1. Truy cập vào https://console.cloud.google.com/sql/instances/locations/overview rồi di chuyển xuống để tìm trường Tên kết nối. Hãy sao chép tên đó để sử dụng trong lệnh tiếp theo.
  2. Trong thẻ đó, hãy chạy proxy Cloud SQL bằng lệnh này, thay thế CONNECTION_NAME bằng tên kết nối hiển thị trong bước trước.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432

Quay lại thẻ đầu tiên của shell Cloud và xác định các biến môi trường Go sẽ cần để kết nối với chương trình phụ trợ cơ sở dữ liệu, sau đó chạy máy chủ theo cách bạn đã làm trước đó:

Chuyển đến thư mục gốc của dự án nếu bạn chưa ở đó.

cd YOUR_PROJECT_ROOT

Tạo 5 biến môi trường sau (thay thế YOUR_PASSWORD_HERE bằng mật khẩu bạn đã tạo ở trên).

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

Chạy phiên bản cục bộ của bạn.

go run *.go

Mở cửa sổ xem trước và cửa sổ sẽ hoạt động như thể không có gì thay đổi: Bạn có thể nhập một địa chỉ bắt đầu, thu phóng xung quanh bản đồ và nhấp vào các vị trí tái chế. Nhưng giờ đây, cơ sở dữ liệu đã được hỗ trợ và đã được chuẩn bị để mở rộng quy mô!

9. Liệt kê các cửa hàng gần nhất

API Chỉ đường hoạt động giống như trải nghiệm yêu cầu chỉ đường trong ứng dụng Google Maps — nhập một nguồn gốc và một điểm đến duy nhất để nhận tuyến đường giữa hai mục tiêu. API Ma trận khoảng cách nâng cao khái niệm này để xác định những cặp ghép tối ưu giữa nhiều nguồn gốc có thể có và nhiều điểm đến có thể dựa trên thời gian di chuyển và khoảng cách. Trong trường hợp này, để giúp người dùng tìm cửa hàng gần nhất đến địa chỉ đã chọn, bạn cung cấp một nguồn gốc và một mảng vị trí cửa hàng làm điểm đến.

Thêm khoảng cách từ nguồn gốc đến mỗi cửa hàng

Ở đầu định nghĩa hàm initMap, hãy thay thế nhận xét "// TODO: Start Distance Matrix service" bằng mã sau:

app.js – initMap

distanceMatrixService = new google.maps.DistanceMatrixService();

Thêm một hàm mới vào cuối app.js có tên là calculateDistances.

app.js

async function calculateDistances(origin, stores) {
  // Retrieve the distances of each store from the origin
  // The returned list will be in the same order as the destinations list
  const response = await getDistanceMatrix({
    origins: [origin],
    destinations: stores.map((store) => {
      const [lng, lat] = store.geometry.coordinates;
      return { lat, lng };
    }),
    travelMode: google.maps.TravelMode.DRIVING,
    unitSystem: google.maps.UnitSystem.METRIC,
  });
  response.rows[0].elements.forEach((element, index) => {
    stores[index].properties.distanceText = element.distance.text;
    stores[index].properties.distanceValue = element.distance.value;
  });
}

const getDistanceMatrix = (request) => {
  return new Promise((resolve, reject) => {
    const callback = (response, status) => {
      if (status === google.maps.DistanceMatrixStatus.OK) {
        resolve(response);
      } else {
        reject(response);
      }
    };
    distanceMatrixService.getDistanceMatrix(request, callback);
  });
};

Hàm này gọi API Ma trận khoảng cách bằng cách sử dụng nguồn gốc được chuyển đến dưới dạng một nguồn gốc và các vị trí cửa hàng dưới dạng một mảng đích. Sau đó, Analytics xây dựng một mảng đối tượng lưu trữ mã cửa hàng, khoảng cách biểu thị trong một chuỗi mà con người có thể đọc được, khoảng cách tính bằng mét dưới dạng một giá trị số và sắp xếp mảng.

Cập nhật hàm initAutocompleteWidget để tính khoảng cách cửa hàng mỗi khi một nguồn gốc mới được chọn từ thanh tìm kiếm Tự động hoàn thành của địa điểm. Ở cuối hàm initAutocompleteWidget, hãy thay thế nhận xét "// TODO: Calculate the closest stores" bằng mã sau:

app.js – initAutoFillWidget

    // Use the selected address as the origin to calculate distances
    // to each of the store locations
    await calculateDistances(originLocation, stores);
    renderStoresPanel();

Hiển thị chế độ xem danh sách cửa hàng được sắp xếp theo khoảng cách

Người dùng cần xem danh sách cửa hàng được sắp xếp theo thứ tự từ gần nhất đến xa nhất. Điền thông tin vào bảng điều khiển bên cho mỗi cửa hàng bằng danh sách đã được sửa đổi bằng hàm calculateDistances để thông báo về thứ tự hiển thị của các cửa hàng.

Thêm hai hàm mới vào cuối app.js có tên là renderStoresPanel()storeToPanelRow().

app.js

function renderStoresPanel() {
  const panel = document.getElementById("panel");

  if (stores.length == 0) {
    panel.classList.remove("open");
    return;
  }

  // Clear the previous panel rows
  while (panel.lastChild) {
    panel.removeChild(panel.lastChild);
  }
  stores
    .sort((a, b) => a.properties.distanceValue - b.properties.distanceValue)
    .forEach((store) => {
      panel.appendChild(storeToPanelRow(store));
    });
  // Open the panel
  panel.classList.add("open");
  return;
}

const storeToPanelRow = (store) => {
  // Add store details with text formatting
  const rowElement = document.createElement("div");
  const nameElement = document.createElement("p");
  nameElement.classList.add("place");
  nameElement.textContent = store.properties.business_name;
  rowElement.appendChild(nameElement);
  const distanceTextElement = document.createElement("p");
  distanceTextElement.classList.add("distanceText");
  distanceTextElement.textContent = store.properties.distanceText;
  rowElement.appendChild(distanceTextElement);
  return rowElement;
};

Khởi động lại máy chủ và làm mới bản xem trước của bạn bằng cách chạy lệnh sau.

go run *.go

Cuối cùng, hãy nhập địa chỉ Austin, TX vào thanh tìm kiếm Tự động hoàn thành và nhấp vào một trong các đề xuất.

Bản đồ phải căn giữa vào địa chỉ đó và thanh bên sẽ xuất hiện liệt kê các vị trí cửa hàng theo khoảng cách từ địa chỉ đã chọn. Dưới đây là một ví dụ:

96e35794dd0e88c9.png

10. Định kiểu cho bản đồ

Một cách có tác động mạnh mẽ để làm nổi bật bản đồ của bạn một cách trực quan là thêm kiểu vào bản đồ. Với kiểu bản đồ trên đám mây, việc tùy chỉnh bản đồ của bạn được kiểm soát từ Cloud Console bằng cách sử dụng tính năng Tạo kiểu bản đồ dựa trên đám mây (bản thử nghiệm). Nếu bạn muốn tạo kiểu cho bản đồ của mình bằng tính năng không phải thử nghiệm beta, bạn có thể sử dụng tài liệu định kiểu bản đồ để giúp bạn tạo json cho việc tạo kiểu bản đồ theo phương thức lập trình. Các hướng dẫn bên dưới sẽ hướng dẫn bạn tạo kiểu bản đồ dựa trên đám mây (bản thử nghiệm).

Tạo mã bản đồ

Trước tiên, hãy mở Cloud Console rồi nhập hộp tìm kiếm rồi nhập "Map Management" . Nhấp vào kết quả có nội dung "Map Management (Google Maps)" 64036dd0ed200200.png

Bạn sẽ thấy một nút ở gần phía trên cùng (ngay bên dưới Hộp tìm kiếm) cho biết Tạo mã bản đồ mới. Nhấp vào mục đó rồi điền bất kỳ tên nào bạn muốn. Đối với Loại bản đồ, hãy nhớ chọn JavaScript và khi các tùy chọn khác hiển thị, hãy chọn Vectơ từ danh sách. Kết quả cuối cùng sẽ trông giống như hình ảnh bên dưới.

70f55a759b4c4212.png

Nhấp vào "Next" và bạn sẽ nhận được một Mã bản đồ hoàn toàn mới. Bạn có thể sao chép nội dung đó ngay bây giờ nếu muốn, nhưng đừng lo, nó sẽ dễ tìm kiếm sau này.

Tiếp theo, chúng ta sẽ tạo một kiểu để áp dụng cho bản đồ đó.

Tạo kiểu bản đồ

Nếu bạn vẫn đang ở phần Maps trong Cloud Console, hãy nhấp "Bản đồ kiểu ở cuối trình đơn điều hướng ở bên trái. Hoặc, giống như việc tạo ID bản đồ, bạn có thể tìm thấy trang phù hợp bằng cách nhập "Kiểu bản đồ" trong hộp tìm kiếm và chọn " Kiểu bản đồ (Google Maps)" từ kết quả, như trong hình bên dưới.

9284cd200f1a9223.png

Tiếp theo, hãy nhấp vào nút gần phía trên cùng có nội dung "+ Tạo kiểu bản đồ mới"

  1. Nếu bạn muốn khớp với kiểu trong bản đồ được hiển thị trong phòng thí nghiệm này, hãy nhấp vào thẻ "IMPORT JSON" và dán blob JSON bên dưới. Nếu không, nếu bạn muốn tạo kiểu của riêng mình, hãy chọn Kiểu bản đồ mà bạn muốn bắt đầu. Nhấp vào Tiếp theo.
  2. Chọn Mã bản đồ mà bạn vừa tạo để liên kết Mã bản đồ đó với kiểu này rồi nhấp lại vào Tiếp theo.
  3. Tại thời điểm này, bạn được cung cấp tùy chọn tùy chỉnh thêm kiểu của bản đồ. Nếu đây là nội dung bạn muốn khám phá, hãy nhấp vào Tùy chỉnh trong Trình chỉnh sửa kiểu và khám phá những màu sắc và tùy chọn cho đến khi bạn có kiểu bản đồ mà bạn thích. Nếu không, hãy nhấp vào Bỏ qua.
  4. Bước tiếp theo, nhập tên và nội dung mô tả về kiểu của bạn, sau đó nhấp vào Lưu và xuất bản.

Đây là một blob JSON tùy chọn để nhập trong bước đầu tiên.

[
  {
    "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"
      }
    ]
  }
]

Thêm Mã bản đồ vào mã của bạn

Bây giờ, bạn đã gặp phải khó khăn khi tạo kiểu bản đồ này, làm thế nào để bạn thực sự sử dụng kiểu bản đồ này trong bản đồ của riêng mình? Bạn cần thực hiện hai thay đổi nhỏ:

  1. Thêm mã bản đồ làm thông số url vào thẻ tập lệnh trong index.html
  2. Add Mã bản đồ dưới dạng một đối số hàm dựng khi bạn tạo bản đồ trong phương thức initMap().

Thay thế thẻ tập lệnh tải API JavaScript của Maps trong tệp HTML bằng URL trình tải bên dưới, thay thế phần giữ chỗ cho "YOUR_API_KEY" và "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>
...

Trong phương thức initMap của app.js, trong đó map hằng số được xác định, hãy bỏ dòng cho thuộc tính mapId và thay thế "YOUR_MAP_ID_HERE" bằng mã bản đồ mà bạn vừa tạo:

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',
// ...
});
...

Khởi động lại máy chủ.

go run *.go

Khi làm mới bản xem trước, bản đồ sẽ được tạo kiểu theo tùy chọn của bạn. Dưới đây là một ví dụ bằng cách sử dụng kiểu JSON ở trên.

2ece59c64c06e9da.png

11. Triển khai ở phiên bản chính thức

Nếu bạn muốn xem ứng dụng của mình chạy từ Appengine Flex (chứ không chỉ là một máy chủ web cục bộ trên máy phát triển / Cloud Shell của bạn, đây là những gì bạn đang làm) thì rất dễ dàng. Chúng ta chỉ cần thêm một vài tuỳ chọn để quyền truy cập cơ sở dữ liệu hoạt động trong môi trường thực tế. Bạn có thể xem tất cả thông tin này trên trang Tài liệu về Kết nối từ App Engine Flex đến Cloud SQL.

Thêm biến môi trường vào App.yaml

Trước tiên, tất cả các biến môi trường mà bạn đang sử dụng để thử nghiệm cục bộ cần phải được thêm vào cuối tệp app.yaml của ứng dụng.

  1. Hãy truy cập vào https://console.cloud.google.com/sql/instances/locations/overview để tìm tên kết nối của thực thể.
  2. Dán mã sau vào cuối app.yaml.
  3. Thay thế YOUR_DB_PASSWORD_HERE bằng mật khẩu bạn đã tạo cho tên người dùng postgres trước đó.
  4. Thay thế YOUR_CONNECTION_NAME_HERE bằng giá trị trong bước 1.

app.yaml

# ...
# Set environment variables
env_variables:
    DB_USER: postgres
    DB_PASS: YOUR_DB_PASSWORD_HERE
    DB_NAME: postgres
    DB_TCP_HOST: 172.17.0.1
    DB_PORT: 5432

#Enable TCP Port
# You can look up your instance connection name by going to the page for
# your instance in the Cloud Console here : https://console.cloud.google.com/sql/instances/
beta_settings:
  cloud_sql_instances: YOUR_CONNECTION_NAME_HERE=tcp:5432

Lưu ý rằng DB_TCP_HOST phải có giá trị 172.17.0.1 vì ứng dụng này kết nối qua Appengine Flex**.** Điều này là do máy chủ sẽ giao tiếp với Cloud SQL qua proxy, tương tự như cách bạn đã thực hiện.

Thêm quyền SQL Client vào tài khoản dịch vụ Appengine Flex

Truy cập vào trang IAM-Quản trị trong Cloud Console rồi tìm tài khoản dịch vụ có tên trùng khớp với định dạng service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com. Đây là tài khoản dịch vụ App Engine Flex sẽ dùng để kết nối với cơ sở dữ liệu. Nhấp vào nút Chỉnh sửa ở cuối hàng rồi thêm vai trò "Ứng dụng Cloud SQL"

b04ccc0b4022b905.png

Sao chép mã dự án vào đường dẫn Go

Để Appengine chạy mã, bạn cần phải tìm được các tệp phù hợp trong đường dẫn Go. Hãy đảm bảo bạn đang ở trong thư mục gốc của dự án.

cd YOUR_PROJECT_ROOT

Sao chép thư mục đó vào đường dẫn chuyển đến.

mkdir -p ~/gopath/src/austin-recycling
cp -r ./ ~/gopath/src/austin-recycling

Hãy đổi sang thư mục đó.

cd ~/gopath/src/austin-recycling

Triển khai ứng dụng

Sử dụng CLI gcloud để triển khai ứng dụng. Quá trình triển khai sẽ mất một thời gian.

gcloud app deploy

Sử dụng lệnh browse để nhận đường liên kết mà bạn có thể nhấp vào để xem bộ định vị cửa hàng hoàn toàn mới, đẹp mắt dành cho doanh nghiệp.

gcloud app browse

Nếu bạn đang chạy gcloud bên ngoài shell Cloud, thì việc chạy gcloud app browse sẽ mở một thẻ trình duyệt mới.

12. (Nên dùng) Dọn dẹp

Việc thực hiện lớp học lập trình này sẽ nằm trong giới hạn cấp miễn phí đối với các lệnh gọi xử lý BigQuery và API Platform, nhưng nếu bạn chỉ làm việc này trong phạm vi giáo dục và muốn không phải chịu bất kỳ khoản phí nào trong tương lai, thì cách dễ nhất để xóa các tài nguyên liên quan đến dự án này là xóa chính dự án đó.

Xóa dự án

Trong Bảng điều khiển GCP, hãy truy cập vào trang Trình quản lý tài nguyên đám mây:

Trong danh sách dự án, hãy chọn dự án mà chúng tôi đang xử lý rồi nhấp vào Xóa. Bạn sẽ được nhắc nhập mã dự án. Nhập mã đó rồi nhấp vào Tắt.

Ngoài ra, bạn có thể xóa toàn bộ dự án trực tiếp khỏi Cloud Shell bằng gcloud bằng cách chạy lệnh sau và thay thế trình giữ chỗ GOOGLE_CLOUD_PROJECT bằng mã dự án của bạn:

gcloud projects delete GOOGLE_CLOUD_PROJECT

13. Xin chúc mừng

Xin chúc mừng! Bạn đã hoàn thành thành công lớp học lập trình!

Hoặc bạn đã chuyển sang trang cuối cùng. Xin chúc mừng! Bạn đã chuyển đến trang cuối!

Trong khóa học này, bạn đã làm việc với các công nghệ sau:

Đọc thêm

Vẫn còn nhiều điều cần tìm hiểu về tất cả các công nghệ này. Dưới đây là một số đường liên kết hữu ích về các chủ đề mà chúng tôi không có thời gian học trong lớp học lập trình này, nhưng chắc chắn sẽ có ích cho bạn trong việc xây dựng một giải pháp định vị cửa hàng phù hợp với nhu cầu cụ thể của mình.