1. Введение
Абстрактный
Представьте, что вам нужно отметить на карте множество мест, и вы хотите, чтобы пользователи могли видеть их местоположение и определять, какие из них они хотят посетить. Вот распространённые примеры:
- локатор магазинов на сайте розничного продавца
- карта избирательных участков для предстоящих выборов
- справочник специализированных мест, таких как пункты приема батареек
Что вы построите
В этой лабораторной работе вы создадите локатор, который использует данные из потока данных о специализированных локациях в режиме реального времени и помогает пользователю найти ближайшее к его начальной точке место. Этот полнофункциональный локатор может обрабатывать гораздо большее количество локаций, чем простой локатор магазинов, который ограничен 25 или менее.
Чему вы научитесь
В этой лабораторной работе используется открытый набор данных для моделирования предварительно заполненных метаданных о большом количестве местоположений магазинов, чтобы вы могли сосредоточиться на изучении ключевых технических концепций.
- API JavaScript Карт: отображение большого количества местоположений на настраиваемой веб-карте
- GeoJSON: формат, в котором хранятся метаданные о местоположениях
- Автозаполнение мест: помогите пользователям быстрее и точнее указывать начальные местоположения.
- Go: язык программирования, используемый для разработки бэкенда приложения. Бэкенд будет взаимодействовать с базой данных и отправлять результаты запросов обратно на фронтенд в формате JSON.
- App Engine: для размещения веб-приложения
Предпосылки
- Базовые знания HTML и JavaScript
- Аккаунт Google
2. Настройте
На шаге 3 следующего раздела включите Maps JavaScript API , Places API и Distance Matrix API для этой кодовой лаборатории.
Начните работу с платформой Google Карт
Если вы ранее не использовали платформу Google Карт, следуйте руководству «Начало работы с платформой Google Карт» или посмотрите плейлист «Начало работы с платформой Google Карт», чтобы выполнить следующие шаги:
- Создайте платежный аккаунт.
- Создать проект.
- Включите API и SDK платформы Google Карт (перечислены в предыдущем разделе).
- Сгенерируйте ключ API.
Активировать Cloud Shell
В этой лабораторной работе вы будете использовать Cloud Shell — среду командной строки, работающую в Google Cloud, которая обеспечивает доступ к продуктам и ресурсам, работающим в Google Cloud, чтобы вы могли размещать и запускать свой проект полностью из веб-браузера.
Чтобы активировать Cloud Shell из Cloud Console, нажмите «Активировать Cloud Shell». (подготовка и подключение к среде займет всего несколько минут).
Это откроет новую оболочку в нижней части браузера после возможного показа вступительного объявления.
Подтвердите свой проект
После подключения к Cloud Shell вы увидите, что вы уже аутентифицированы и что проекту уже присвоен идентификатор проекта, выбранный вами во время настройки.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
Если по какой-то причине проект не установлен, выполните следующую команду:
gcloud config set project <YOUR_PROJECT_ID>
Включить API AppEngine Flex
API AppEngine Flex необходимо включить вручную в консоли Cloud. Это не только включит API, но и создаст учётную запись службы AppEngine Flexible Environment — аутентифицированную учётную запись, которая будет взаимодействовать со службами Google (например, с базами данных SQL) от имени пользователя.
3. Привет, мир
Бэкэнд: Hello World на Go
В своем экземпляре Cloud Shell вы начнете с создания приложения Go App Engine Flex, которое послужит основой для остальной части кодовой лаборатории.
На панели инструментов Cloud Shell нажмите кнопку «Открыть редактор» , чтобы открыть редактор кода в новой вкладке. Этот веб-редактор кода позволяет легко редактировать файлы в экземпляре Cloud Shell.
Затем нажмите значок « Открыть в новом окне», чтобы переместить редактор и терминал на новую вкладку.
В терминале в нижней части новой вкладки создайте новый каталог austin-recycling
.
mkdir -p austin-recycling && cd $_
Далее вам предстоит создать небольшое приложение Go App Engine, чтобы убедиться, что всё работает. Привет, мир!
Каталог austin-recycling
также должен появиться в списке папок редактора слева. В каталоге austin-recycling
создайте файл app.yaml
. Вставьте в app.yaml
следующее содержимое:
app.yaml
runtime: go
env: flex
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
Этот файл конфигурации настраивает ваше приложение App Engine для использования среды выполнения Go Flex. Дополнительную информацию о значении элементов конфигурации в этом файле см. в документации по стандартной среде Google App Engine Go .
Затем создайте файл main.go
рядом с файлом app.yaml
:
main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
http.HandleFunc("/", handle)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func handle(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, "Hello world!")
}
Здесь стоит остановиться на мгновение, чтобы понять, что делает этот код, по крайней мере, на общем уровне. Вы определили пакет main
, который запускает HTTP-сервер, прослушивающий порт 8080, и регистрирует функцию-обработчик для HTTP-запросов, соответствующих пути "/"
.
Функция-обработчик, удобно названная handler
, выводит текстовую строку "Hello, world!"
. Этот текст будет передан обратно в ваш браузер, где вы сможете его прочитать. В дальнейшем вы создадите обработчики, которые будут отвечать данными GeoJSON вместо простых жёстко заданных строк.
После выполнения этих шагов у вас должен получиться редактор, который выглядит примерно так:
Проверьте это
Чтобы протестировать это приложение, запустите сервер разработки App Engine внутри экземпляра Cloud Shell. Вернитесь в командную строку Cloud Shell и введите следующее:
go run *.go
Вы увидите несколько строк журнала, показывающих, что вы действительно запускаете сервер разработки на экземпляре Cloud Shell, а веб-приложение Hello World прослушивает порт 8080 локального хоста. Вы можете открыть вкладку веб-браузера в этом приложении, нажав кнопку « Предварительный просмотр в Интернете» и выбрав пункт меню «Предварительный просмотр на порту 8080» на панели инструментов Cloud Shell.
При нажатии на этот пункт меню в вашем веб-браузере откроется новая вкладка со словами «Hello, world!», предоставленными сервером разработки App Engine.
На следующем этапе вы добавите данные по переработке отходов города Остин в это приложение и начнете их визуализировать.
4. Получите актуальные данные
GeoJSON — язык общения в мире ГИС
В предыдущем шаге упоминалось, что вы создадите обработчики в коде Go, которые будут отображать данные GeoJSON в веб-браузере. Но что такое GeoJSON?
В мире географических информационных систем (ГИС) нам необходимо иметь возможность передавать знания о географических объектах между компьютерными системами. Карты удобны для чтения человеком, но компьютеры обычно предпочитают данные в более удобных для восприятия форматах.
GeoJSON — это формат для кодирования географических структур данных, таких как координаты пунктов приёма вторсырья в Остине, штат Техас. GeoJSON стандартизирован в стандарте RFC7946 , разработанном Инженерной группой Интернета . GeoJSON определён в терминах JSON (JavaScript Object Notation), который, в свою очередь, был стандартизирован в ECMA-404 той же организацией, которая стандартизировала JavaScript, Ecma International .
Важно то, что GeoJSON — это широко поддерживаемый формат передачи географических знаний. В этой лабораторной работе GeoJSON используется следующим образом:
- Используйте пакеты Go для преобразования данных Остина во внутреннюю структуру данных ГИС, которую вы будете использовать для фильтрации запрашиваемых данных.
- Сериализовать запрошенные данные для передачи между веб-сервером и веб-браузером.
- Используйте библиотеку JavaScript для преобразования ответа в маркеры на карте.
Это позволит вам существенно сэкономить время на вводе кода, поскольку вам не придется писать анализаторы и генераторы для преобразования потока данных, передаваемых по сети, в представления в памяти.
Получить данные
Портал открытых данных города Остин, штат Техас, предоставляет геопространственную информацию об общественных ресурсах для общественного пользования. В этой лабораторной работе вы визуализируете набор данных о пунктах приёма вторсырья .
Вы визуализируете данные с помощью маркеров на карте, отображаемых с помощью слоя данных JavaScript API Карт.
Начните с загрузки данных GeoJSON с веб-сайта города Остин в свое приложение.
- В окне командной строки вашего экземпляра Cloud Shell завершите работу сервера, набрав [CTRL] + [C].
- Создайте каталог
data
внутри каталогаaustin-recycling
и перейдите в этот каталог:
mkdir -p data && cd data
Теперь используйте curl для получения мест переработки:
curl "https://data.austintexas.gov/resource/qzi7-nx8g.geojson" -o recycling-locations.geojson
Наконец, вернитесь в родительский каталог.
cd ..
5. Составьте карту местоположений
Во-первых, обновите файл app.yaml
, чтобы он отражал более надежное приложение, которое вы собираетесь создать — уже не просто «Hello World».
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
Эта конфигурация app.yaml
направляет запросы к /
, /*.js
, /*.css
и /*.html
к набору статических файлов. Это означает, что статический HTML-компонент вашего приложения будет обслуживаться непосредственно инфраструктурой обработки файлов App Engine, а не вашим приложением Go. Это снижает нагрузку на сервер и увеличивает скорость обслуживания.
Теперь пришло время создать бэкэнд вашего приложения на Go!
Постройте бэкэнд
Вы, возможно, заметили интересную особенность вашего файла app.yaml
: он не предоставляет доступ к файлу GeoJSON. Это связано с тем, что GeoJSON будет обрабатываться и отправляться нашим бэкендом Go, что позволит нам реализовать некоторые интересные функции на последующих этапах. Измените файл main.go
следующим образом:
main.go
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
)
var GeoJSON = make(map[string][]byte)
// cacheGeoJSON loads files under data into `GeoJSON`.
func cacheGeoJSON() {
filenames, err := filepath.Glob("data/*")
if err != nil {
log.Fatal(err)
}
for _, f := range filenames {
name := filepath.Base(f)
dat, err := os.ReadFile(f)
if err != nil {
log.Fatal(err)
}
GeoJSON[name] = dat
}
}
func main() {
// Cache the JSON so it doesn't have to be reloaded every time a request is made.
cacheGeoJSON()
// Request for data should be handled by Go. Everything else should be directed
// to the folder of static files.
http.HandleFunc("/data/dropoffs", dropoffsHandler)
http.Handle("/", http.FileServer(http.Dir("./static/")))
// Open up a port for the webserver.
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Listening on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
// Writes Hello, World! to the user's web browser via `w`
fmt.Fprint(w, "Hello, world!")
}
func dropoffsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-type", "application/json")
w.Write(GeoJSON["recycling-locations.geojson"])
}
Бэкенд Go уже предоставляет нам ценную функцию: экземпляр AppEngine кэширует все эти расположения сразу после запуска. Это экономит время, поскольку бэкенду не придётся считывать файл с диска при каждом обновлении от каждого пользователя!
Постройте переднюю часть
Первое, что нам нужно сделать, — это создать папку для всех наших статических ресурсов. В родительской папке вашего проекта создайте папку static
.
mkdir -p static && cd static
Мы создадим в этой папке 3 файла.
-
index.html
будет содержать весь HTML-код вашего одностраничного приложения для поиска магазинов. -
style.css
, как и ожидалось, будет содержать стили -
app.js
будет отвечать за извлечение GeoJSON, выполнение вызовов API Карт и размещение маркеров на вашей пользовательской карте.
Создайте эти 3 файла, убедившись, что они помещены в static/
.
стиль.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>
Обратите особое внимание на URL-адрес src
в теге script элемента head
.
- Замените текст-заполнитель «
YOUR_API_KEY
» на ключ API, сгенерированный вами на этапе настройки. Чтобы получить ключ API или сгенерировать новый, перейдите на страницу «API и сервисы» -> «Учётные данные» в Cloud Console. - Обратите внимание, что URL содержит параметр
callback=initialize.
Сейчас мы создадим файл JavaScript, содержащий эту функцию обратного вызова. Именно здесь ваше приложение будет загружать местоположения из бэкенда, отправлять их в API Карт и использовать результат для отметки пользовательских местоположений на карте, что будет красиво отображаться на вашей веб-странице. - Параметр
libraries=places
загружает библиотеку Places, которая необходима для таких функций, как автозаполнение адресов, которые будут добавлены позже.
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;
};
Этот код отображает расположение магазинов на карте. Чтобы проверить, что у нас есть на данный момент, вернитесь в родительский каталог из командной строки:
cd ..
Теперь снова запустите приложение в режиме разработки, используя:
go run *.go
Просмотрите его, как и раньше. Вы должны увидеть карту с маленькими зелёными кружками, как на этой картинке.
Вы уже визуализируете местоположения на карте, а мы только на полпути к выполнению кодовой работы! Потрясающе. Теперь добавим немного интерактивности.
6. Показывать детали по запросу
Реагировать на нажатия на маркеры карты
Отображение нескольких маркеров на карте — отличное начало, но нам действительно нужно, чтобы посетитель мог нажать на один из них и увидеть информацию о данном месте (например, название компании, адрес и т. д.). Небольшое информационное окно, которое обычно появляется при нажатии на маркер Google Карт, называется Info Window .
Создайте объект infoWindow. Добавьте следующий код в функцию initialize
, заменив закомментированную строку « // TODO: Initialize an info window
».
app.js - инициализация
// 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
этой немного другой версией, которая изменяет последнюю строку для вызова storeToCircle
с дополнительным аргументом 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
этой немного более длинной версией, которая теперь принимает информационное окно в качестве третьего аргумента:
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;
};
Новый код выше отображает infoWindow
с информацией о выбранном магазине при каждом щелчке по маркеру магазина на карте.
Если ваш сервер всё ещё работает, остановите его и перезапустите. Обновите страницу карты и попробуйте нажать на маркер. Должно появиться небольшое информационное окно с названием и адресом компании, которое будет выглядеть примерно так:
7. Получите начальное местоположение пользователя.
Пользователи локаторов магазинов обычно хотят знать, какой магазин находится ближе всего к ним или с какого адреса они планируют начать свой маршрут. Добавьте строку поиска Place Autocomplete, чтобы пользователь мог легко ввести начальный адрес. Place Autocomplete предоставляет функцию опережающего ввода, аналогичную работе автозаполнения в других строках поиска Google, за исключением того, что все подсказки — это места из платформы Google Карт.
Создайте поле ввода пользователя
Вернитесь к редактированию style.css
, чтобы добавить стили для строки поиска с автозаполнением и соответствующей боковой панели с результатами. Пока мы обновляем стили CSS, мы также добавим стили для будущей боковой панели, которая будет отображать информацию о магазинах в виде списка, сопровождающего карту.
Добавьте этот код в конец файла.
стиль.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;
}
Панель поиска автозаполнения и выдвижная панель изначально скрыты до тех пор, пока они не понадобятся.
Подготовьте блок div для виджета автозаполнения, заменив комментарий в index.html, который гласит "<!-- Autocomplete div goes here -->
» на следующий код. При внесении этого изменения мы также добавим блок div для выдвижной панели.
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>
Теперь определите функцию для добавления виджета автозаполнения на карту, добавив следующий код в конец 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
};
Код ограничивает подсказки автозаполнения только адресами (поскольку автозаполнение мест также может сопоставлять названия организаций и административные местоположения) и ограничивает возвращаемые адреса только адресами в США. Добавление этих необязательных спецификаций сократит количество символов, которые пользователю необходимо ввести, чтобы сузить область поиска и получить нужный адрес.
Затем он перемещает созданный вами div
автозаполнения в правый верхний угол карты и указывает, какие поля должны быть возвращены для каждого места в ответе.
Наконец, вызовите функцию initAutocompleteWidget
в конце функции initialize
, заменив комментарий, который гласит « // TODO: Initialize the Autocomplete widget
».
app.js - инициализация
// Initialize the Places Autocomplete Widget
initAutocompleteWidget();
Перезагрузите сервер, выполнив следующую команду, затем обновите предварительный просмотр.
go run *.go
Теперь в правом верхнем углу карты должен появиться виджет автозаполнения, который будет показывать адреса США, соответствующие введенному вами тексту, с приоритетом на видимую область карты.
Обновлять карту, когда пользователь выбирает начальный адрес
Теперь вам нужно обработать момент, когда пользователь выбирает прогноз из виджета автозаполнения, и использовать это местоположение в качестве основы для расчета расстояний до ваших магазинов.
Добавьте следующий код в конец initAutocompleteWidget
в app.js
, заменив комментарий « // TODO: Respond when a user selects an address
».
app.js - initAutocompleteWidget
// Respond when a user selects an address
// Set the origin point when the user selects an address
originMarker = new google.maps.Marker({ map: map });
originMarker.setVisible(false);
let originLocation = map.getCenter();
autocomplete.addListener("place_changed", async () => {
// circles.forEach((c) => c.setMap(null)); // clear existing stores
originMarker.setVisible(false);
originLocation = map.getCenter();
const place = autocomplete.getPlace();
if (!place.geometry) {
// User entered the name of a Place that was not suggested and
// pressed the Enter key, or the Place Details request failed.
window.alert("No address available for input: '" + place.name + "'");
return;
}
// Recenter the map to the selected address
originLocation = place.geometry.location;
map.setCenter(originLocation);
map.setZoom(15);
originMarker.setPosition(originLocation);
originMarker.setVisible(true);
// await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
});
Код добавляет прослушиватель, чтобы при нажатии пользователем на одну из подсказок карта центрировалась на выбранном адресе и устанавливала исходную точку в качестве основы для расчёта расстояния. Вы реализуете расчёт расстояния на следующем этапе.
Остановите и перезапустите сервер, а затем обновите предварительный просмотр, чтобы увидеть, как карта центрируется после ввода адреса в строку поиска автозаполнения.
8. Масштабируйтесь с помощью Cloud SQL
На данный момент у нас есть довольно неплохой локатор магазинов. Он использует тот факт, что приложение будет использовать всего около сотни локаций, загружая их в память на бэкенде (вместо многократного чтения из файла). Но что, если вашему локатору нужно работать в другом масштабе? Если у вас сотни локаций, разбросанных по большой географической области (или тысячи по всему миру), хранить все эти локации в памяти уже не лучшая идея, а разбиение зон на отдельные файлы создаст свои проблемы.
Пришло время загрузить данные о местоположениях из базы данных. На этом этапе мы перенесём все местоположения из файла GeoJSON в базу данных Cloud SQL и обновим бэкенд Go, чтобы он извлекал результаты из этой базы данных, а не из локального кэша при каждом запросе.
Создайте экземпляр Cloud SQL с базой данных PostGres
Экземпляр Cloud SQL можно создать через Google Cloud Console, но ещё проще использовать утилиту gcloud
для создания экземпляра из командной строки. В Cloud Shell создайте экземпляр Cloud SQL с помощью следующей команды:
gcloud sql instances create locations \ --database-version=POSTGRES_12 \ --tier=db-custom-1-3840 --region=us-central1
- Аргумент
locations
— это имя, которое мы решили дать данному экземпляру Cloud SQL. - Флаг
tier
— это способ выбора среди некоторых удобных предопределенных машин . - Значение
db-custom-1-3840
указывает, что создаваемый экземпляр должен иметь один виртуальный ЦП и около 3,75 ГБ памяти.
Экземпляр Cloud SQL будет создан и инициализирован с базой данных PostGresSQL с пользователем по умолчанию postgres
. Какой пароль у этого пользователя? Отличный вопрос! У него его нет. Вам необходимо настроить его перед входом в систему.
Установите пароль с помощью следующей команды:
gcloud sql users set-password postgres \ --instance=locations --prompt-for-password
Затем введите выбранный вами пароль, когда будет предложено это сделать.
Включить расширение PostGIS
PostGIS — это расширение для PostGresSQL, которое упрощает хранение стандартизированных типов геопространственных данных. В обычных условиях для добавления PostGIS в нашу базу данных пришлось бы пройти полную процедуру установки. К счастью, это одно из поддерживаемых Cloud SQL расширений для PostGresSQL .
Подключитесь к экземпляру базы данных, войдя в систему как пользователь postgres
выполнив следующую команду в терминале облачной оболочки.
gcloud sql connect locations --user=postgres --quiet
Введите только что созданный пароль. Теперь добавьте расширение PostGIS в командной строке postgres=>
.
CREATE EXTENSION postgis;
В случае успеха вывод должен выглядеть как CREATE EXTENSION, как показано ниже.
Пример вывода команды
CREATE EXTENSION
Наконец, завершите соединение с базой данных, введя команду quit в командной строке postgres=>
.
\q
Импорт географических данных в базу данных
Теперь нам нужно импортировать все данные о местоположении из файлов GeoJSON в нашу новую базу данных.
К счастью, это распространённая задача, и в интернете можно найти несколько инструментов для её автоматизации. Мы будем использовать инструмент ogr2ogr , который конвертирует данные между несколькими распространёнными форматами хранения геопространственных данных. Среди этих возможностей, как вы уже догадались, есть преобразование GeoJSON в файл дампа SQL. Этот файл дампа SQL затем можно использовать для создания таблиц и столбцов базы данных и загрузки в неё всех данных из ваших файлов GeoJSON.
Создать файл дампа SQL
Сначала установите ogr2ogr.
sudo apt-get install gdal-bin
Затем с помощью ogr2ogr создайте файл дампа SQL. Этот файл создаст таблицу с именем austinrecycling
.
ogr2ogr --config PG_USE_COPY YES -f PGDump datadump.sql \ data/recycling-locations.geojson -nln austinrecycling
Приведённая выше команда выполняется из папки austin-recycling
. Если вам нужно запустить её из другого каталога, замените data
на путь к каталогу, где хранится recycling-locations.geojson
.
Заполните базу данных местами переработки
После выполнения последней команды у вас должен появиться файл datadump.sql,
в том же каталоге, где вы её запустили. Открыв его, вы увидите чуть больше сотни строк SQL-кода, создающего таблицу austinrecycling
и заполняющего её местоположениями.
Теперь откройте соединение с базой данных и запустите этот скрипт с помощью следующей команды.
gcloud sql connect locations --user=postgres --quiet < datadump.sql
Если скрипт отработает успешно, последние несколько строк вывода будут выглядеть так:
Пример вывода команды
ALTER TABLE ALTER TABLE ATLER TABLE ALTER TABLE COPY 103 COMMIT WARNING: there is no transaction in progress COMMIT
Обновите бэкэнд Go для использования Cloud SQL
Теперь, когда все эти данные есть в нашей базе данных, пришло время обновить наш код.
Обновите интерфейс для отправки информации о местоположении
Давайте начнем с одного очень небольшого обновления интерфейса: поскольку мы сейчас пишем это приложение для масштаба, где мы не хотим, чтобы каждое отдельное местоположение доставлялось на интерфейс каждый раз при выполнении запроса, нам нужно передать некоторую базовую информацию из интерфейса о местоположении, которое интересует пользователя.
Откройте app.js
и замените определение функции fetchStores
этой версией, чтобы включить интересующую вас широту и долготу в URL.
app.js - fetchStores
const fetchStores = async (center) => {
const url = `/data/dropoffs?centerLat=${center.lat}¢erLng=${center.lng}`;
const response = await fetch(url);
return response.json();
};
После завершения этого этапа практической работы ответ будет возвращать только магазины, наиболее близкие к координатам на карте, указанным в параметре center
. Для первоначальной выборки в функции initialize
пример кода, представленный в этой практической работе, использует координаты центра Остина, штат Техас.
Поскольку fetchStores
теперь будет возвращать только подмножество местоположений магазинов, нам придется повторно извлекать магазины каждый раз, когда пользователь меняет свое начальное местоположение.
Обновите функцию initAutocompleteWidget
, чтобы обновлять местоположения при каждой установке нового источника. Для этого потребуется внести два изменения:
- В initAutocompleteWidget найдите обратный вызов для прослушивателя
place_changed
. Раскомментируйте строку, очищающую существующие круги, чтобы она запускалась каждый раз, когда пользователь выбирает адрес в поле поиска автозаполнения мест.
app.js - initAutocompleteWidget
autocomplete.addListener("place_changed", async () => {
circles.forEach((c) => c.setMap(null)); // clear existing stores
// ...
- При каждом изменении выбранного источника обновляется переменная originLocation. В конце обратного вызова «
place_changed
» раскомментируйте строку над строкой «// TODO: Calculate the closest stores
», чтобы передать новый источник новому вызову функцииfetchAndRenderStores
.
app.js - initAutocompleteWidget
await fetchAndRenderStores(originLocation.toJSON());
// TODO: Calculate the closest stores
Обновите бэкэнд, чтобы использовать CloudSQL вместо простого JSON-файла.
Удалить чтение и кэширование плоского файла GeoJSON
Сначала измените main.go
, удалив код, загружающий и кэширующий плоский GeoJSON-файл. Также можно избавиться от функции dropoffsHandler
, так как мы будем писать функцию на базе Cloud SQL в другом файле.
Ваш новый main.go
будет намного короче.
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)
}
}
Создать новый обработчик для запросов местоположения
Теперь создадим ещё один файл, locations.go
, также в каталоге austin-recycling. Начнём с повторной реализации обработчика запросов местоположения.
location.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)
}
Обработчик выполняет следующие важные задачи:
- Он извлекает широту и долготу из объекта запроса (помните, как мы добавили их в URL?)
- Он запускает вызов
getGeoJsonFromDatabase
, который возвращает строку GeoJSON (мы напишем это позже). - Он использует
ResponseWriter
для вывода строки GeoJSON в ответ.
Далее мы создадим пул соединений, который поможет масштабировать использование базы данных при одновременном использовании пользователей.
Создать пул соединений
Пул соединений — это набор активных соединений с базой данных, которые сервер может повторно использовать для обслуживания пользовательских запросов. Это значительно снижает накладные расходы по мере увеличения числа активных пользователей, поскольку серверу не приходится тратить время на создание и удаление соединений для каждого активного пользователя. Возможно, вы заметили, что в предыдущем разделе мы импортировали библиотеку github.com/jackc/pgx/stdlib.
Это популярная библиотека для работы с пулами соединений в Go.
В конце файла locations.go
создайте функцию initConnectionPool
(вызываемую из main.go
), которая инициализирует пул подключений. Для ясности в этом фрагменте кода используется несколько вспомогательных методов. configureConnectionPool
предоставляет удобное место для настройки параметров пула, таких как количество подключений и время жизни каждого подключения. mustGetEnv
оборачивает вызовы для получения необходимых переменных окружения, что позволяет выдавать полезные сообщения об ошибках, если экземпляру не хватает важной информации (например, IP-адреса или имени базы данных для подключения).
location.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
}
Запросите базу данных о местоположении и получите в ответ JSON.
Теперь мы напишем запрос к базе данных, который принимает координаты карты и возвращает 25 ближайших точек. Более того, благодаря современным функциям баз данных, данные будут возвращены в формате GeoJSON. В итоге, насколько может судить front-end-код, ничего не изменилось. Раньше он отправлял запрос по URL и получал кучу GeoJSON. Теперь он отправляет запрос по URL и... получает кучу GeoJSON.
Вот функция, которая творит это волшебство. Добавьте следующую функцию после кода обработчика и пула подключений, который вы только что написали в конце файла locations.go
.
location.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
}
Эта функция в основном отвечает за настройку, демонтаж и обработку ошибок при отправке запроса к базе данных. Давайте рассмотрим сам SQL-код, который выполняет множество интересных функций на уровне базы данных, так что вам не придётся беспокоиться о реализации этих функций в коде.
Необработанный запрос, который запускается после анализа строки и вставки всех строковых литералов в соответствующие места, выглядит следующим образом:
parsed.sql
SELECT jsonb_build_object(
'type',
'FeatureCollection',
'features',
jsonb_agg(feature)
)
FROM (
SELECT jsonb_build_object(
'type',
'Feature',
'id',
ogc_fid,
'geometry',
ST_AsGeoJSON(wkb_geometry)::jsonb,
'properties',
to_jsonb(row) - 'ogc_fid' - 'wkb_geometry'
) AS feature
FROM (
SELECT *,
ST_Distance(
ST_GEOGFromWKB(wkb_geometry),
-- Los Angeles (LAX)
ST_GEOGFromWKB(st_makepoint(-97.7624043, 30.523725))
) as distance
from austinrecycling
order by distance
limit 25
) row
where distance < 16090
) features
Этот запрос можно рассматривать как один основной запрос и несколько функций-оберток JSON.
SELECT * ... LIMIT 25
выбирает все поля для каждого местоположения. Затем функция ST_DISTANCE (входит в набор функций PostGIS для измерения географических данных ) определяет расстояние между каждым местоположением в базе данных и парой координат (широта/долгота) местоположения, указанной пользователем во внешнем интерфейсе. Помните, что в отличие от Distance Matrix, которая может показывать расстояние, которое можно проехать на машине, это геопространственные расстояния. Для эффективности функция использует это расстояние для сортировки и возвращает 25 ближайших к указанному пользователем местоположению местоположений.
** SELECT json_build_object('type', 'F
**eature') оборачивает предыдущий запрос, беря результаты и используя их для построения объекта GeoJSON Feature . Неожиданно, в этом запросе также применяется максимальный радиус: «16090» — это количество метров в 10 милях, жёсткое ограничение, заданное бэкендом Go. Если вам интересно, почему предложение WHERE не было добавлено во внутренний запрос (где определяется расстояние до каждого местоположения), то это связано с тем, что из-за особенностей выполнения SQL это поле могло не быть рассчитано при проверке предложения WHERE. Более того, если вы попытаетесь переместить это предложение WHERE во внутренний запрос, возникнет ошибка.
** SELECT json_build_object('type', 'FeatureColl
**ection') Этот запрос оборачивает все полученные строки из запроса, генерирующего JSON, в объект GeoJSON FeatureCollection .
Добавьте библиотеку PGX в свой проект
Нам нужно добавить в ваш проект одну зависимость: PostGres Driver & Toolkit , которая обеспечивает пул соединений. Проще всего это сделать с помощью Go Modules . Инициализируйте модуль с помощью этой команды в облачной оболочке:
go mod init my_locator
Затем выполните эту команду для сканирования кода на наличие зависимостей, добавьте список зависимостей в файл мода и загрузите их.
go mod tidy
Наконец, выполните эту команду, чтобы загрузить зависимости непосредственно в каталог вашего проекта, чтобы можно было легко собрать контейнер для AppEngine Flex.
go mod vendor
Хорошо, вы готовы протестировать!
Проверьте это
Ладно, мы только что сделали МНОГОЕ. Посмотрим, как это работает!
Чтобы ваш компьютер для разработки (да, даже Cloud Shell) мог подключиться к базе данных, нам потребуется использовать Cloud SQL Proxy для управления подключением к базе данных. Чтобы настроить Cloud SQL Proxy:
- Перейдите сюда, чтобы включить Cloud SQL Admin API.
- Если вы используете локальный компьютер для разработки, установите инструмент Cloud SQL Proxy. Если вы используете Cloud Shell, этот шаг можно пропустить — он уже установлен! Обратите внимание, что инструкции будут относиться к учётной записи службы. Она уже создана для вас, и мы рассмотрим добавление необходимых прав для неё в следующем разделе.
- Создайте новую вкладку (в Cloud Shell или собственном терминале) для запуска прокси.
- Перейдите по ссылке
https://console.cloud.google.com/sql/instances/locations/overview
и прокрутите страницу вниз, чтобы найти поле «Имя подключения» . Скопируйте это имя для использования в следующей команде. - На этой вкладке запустите прокси-сервер Cloud SQL с помощью этой команды, заменив
CONNECTION_NAME
на имя подключения, показанное на предыдущем шаге.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432
Вернитесь на первую вкладку вашей облачной оболочки и определите переменные среды, которые понадобятся Go для взаимодействия с серверной частью базы данных, а затем запустите сервер так же, как вы это делали раньше:
Перейдите в корневой каталог проекта, если вы еще там не находитесь.
cd YOUR_PROJECT_ROOT
Создайте следующие пять переменных среды (замените YOUR_PASSWORD_HERE
на пароль, созданный вами выше).
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
Запустите локальный экземпляр.
go run *.go
Откройте окно предварительного просмотра, и всё должно работать так, как будто ничего не изменилось: вы можете ввести начальный адрес, масштабировать карту и нажимать на пункты приема вторсырья. Но теперь оно подкреплено базой данных и готово к масштабированию!
9. Перечислите ближайшие магазины
API Directions работает во многом подобно запросу маршрутов в приложении Google Карты: ввод одного пункта отправления и одного пункта назначения позволяет получить маршрут между ними. API Distance Matrix развивает эту концепцию, находя оптимальные пары между несколькими возможными пунктами отправления и пунктами назначения с учётом времени в пути и расстояния. В данном случае, чтобы помочь пользователю найти ближайший магазин к выбранному адресу, вы указываете один пункт отправления и массив адресов магазинов в качестве пунктов назначения.
Добавьте расстояние от точки отправления до каждого магазина.
В начале определения функции initMap
замените комментарий « // TODO: Start Distance Matrix service
» со следующим кодом:
app.js - initmap
distanceMatrixService = new google.maps.DistanceMatrixService();
Добавьте новую функцию в конце app.js
, называемого 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);
});
};
Функция вызывает API матрицы расстояния, используя исходное происхождение, переданное ему в качестве единого происхождения, а местоположения хранилища в качестве массива направлений. Затем он строит массив объектов, хранящих идентификатор магазина, расстояние, выраженное в человеческой строке, расстояние в метрах в качестве численного значения и сортирует массив.
Обновите функцию initAutocompleteWidget
, чтобы вычислить расстояния в магазине всякий раз, когда выбирается новое происхождение из автозаполнения Place. Внизу функции initAutocompleteWidget
, замените комментарий « // TODO: Calculate the closest stores
» с помощью следующего кода:
app.js - initautocompletewidget
// Use the selected address as the origin to calculate distances
// to each of the store locations
await calculateDistances(originLocation, stores);
renderStoresPanel();
Отобразить список магазинов, отсортированных по расстоянию
Пользователь рассчитывает увидеть список магазинов, заказанных от ближайшего до самого дальнего. Заполните список боковых панелей для каждого хранилища, используя список, который был изменен функцией calculateDistances
, чтобы информировать порядок отображения магазинов.
Добавьте две новые функции в конце app.js
, называемого 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;
};
Перезапустите свой сервер и обновите свой предварительный просмотр, выполнив следующую команду.
go run *.go
Наконец, введите адрес Austin, TX в строку поиска автозаполнения и нажмите на одно из предложений.
Карта должна сосредоточиться на этом адресе, и должна появиться боковая панель, в которой перечислены места магазина в порядке расстояния от выбранного адреса. Один пример изображен следующим образом:
10. Стиль карты
Один высокопоставленный способ выделения карты визуально-добавить в нее стиль. С помощью облачного стиля карты настройка ваших карт управляется из облачной консоли с использованием облачной карты стиля карты (бета). Если вы предпочитаете создать свою карту с помощью не-бета-функции, вы можете использовать документацию по стилю карты , чтобы помочь вам генерировать JSON для программного стиля карты. Инструкции ниже направляют вас через облачную стиль карты (бета).
Создать идентификатор карты
Во -первых, откройте облачную консоль и в поле поиска и введите «Управление картой». Нажмите на результат, в котором говорится «Управление картой (карты Google)».
Вы увидите кнопку возле верхней части (прямо под окном поиска) с написанием «Создание нового идентификатора карты» . Нажмите на это и заполните любое имя, которое вы хотите. Для типа карты обязательно выберите JavaScript , и, когда появятся дополнительные параметры, выберите Vector в списке. Конечный результат должен выглядеть примерно как изображение ниже.
Нажмите «Далее», и вы будете украшены совершенно новым идентификатором карты. Вы можете скопировать его сейчас, если хотите, но не волнуйтесь, это легко посмотреть позже.
Далее мы собираемся создать стиль, чтобы применить к этой карте.
Создать стиль карты
Если вы все еще находитесь в разделе «Карты облачной консоли», нажмите «Стили карты в нижней части меню навигации слева. В противном случае, как и в создании идентификатора карты, вы можете найти правильную страницу, набрав« стили карты »в поле поиска и выбрав« Стили карты (карты Google) »из результатов, как на рисунке ниже.
Затем нажмите на кнопку рядом с верхней частью с надписью « + Создайте новый стиль карты »
- Если вы хотите соответствовать стилю на карте, показанной в этой лаборатории, нажмите вкладку « Импорт JSON » и вставьте BLOB JSON ниже. В противном случае, если вы хотите создать свой собственный, выберите стиль карты, с которой вы хотите начать. Затем нажмите Далее .
- Выберите только что созданный идентификатор карты, чтобы связать этот идентификатор карты с этим стилем, и нажмите снова .
- На этом этапе вам предоставлена возможность дальнейшей настройки стиля вашей карты. Если это то, что вы хотите изучить, нажмите «Настроить в редакторе стилей» и поиграйте с цветами и опциями, пока не получите стиль карты, который вам понравится. В противном случае нажмите Skip .
- На следующем шаге введите имя и описание вашего стиля, а затем нажмите «Сохранить и публиковать» .
Вот необязательная Blob для импорта на первом шаге.
[
{
"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"
}
]
}
]
Добавьте идентификатор карты в свой код
Теперь, когда вы пережили проблему создания этого стиля карты, как вы на самом деле используете этот стиль карты в своей собственной карте? Вам нужно внести два небольших изменения:
- Добавьте идентификатор карты в качестве параметра URL в тег скрипта в
index.html
-
Add
идентификатор карты в качестве аргумента конструктора при создании карты в методеinitMap()
.
Замените тег скрипта, который загружает API Maps JavaScript в файле HTML на URL -адрес загрузчика ниже, заменив заполнители для " 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>
...
В методе initMap
от app.js
, где определяется постоянная map
, расстроен строку для свойства mapId
и замените « 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',
// ...
});
...
Перезагрузите ваш сервер.
go run *.go
Обновляя свой предварительный просмотр, карта должна выглядеть в стиле в соответствии с вашими предпочтениями. Вот пример, использующий стиль JSON выше.
11. Развертывание на производство
Если вы хотите, чтобы ваше приложение работало от Appengine Flex (и не просто локального веб -сервера на вашем машине разработки / облачной оболочке, что вы делаете), это очень легко. Нам просто нужно добавить пару вещей для доступа к базе данных к работе в производственной среде. Все это изложено на странице документации при подключении от App Engine Flex к Cloud SQL .
Добавить переменные среды в app.yaml
Во -первых, все те переменные среды, которые вы использовали для тестирования локально, необходимо добавить в нижнюю часть файла app.yaml
вашего приложения.
- Посетите https://console.cloud.google.com/sql/instance/locations/overview , чтобы найти имя соединения экземпляра.
- Вставьте следующий код в конце
app.yaml
. - Замените
YOUR_DB_PASSWORD_HERE
созданным вами паролем дляpostgres
username ранее. - Замените
YOUR_CONNECTION_NAME_HERE
со значением с шага 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
Обратите внимание, что DB_TCP_HOST
должен иметь значение 172.17.0.1 , поскольку это приложение подключается через Appengine Flex **. ** Это связано с тем, что оно будет общаться с облачным SQL через прокси, аналогичный тому, как вы были.
Добавить разрешения клиента SQL в учетную запись Service Appengine Flex
Перейдите на страницу IAM-ADMIN в облачной консоли и ищите учетную запись службы, чье имя соответствует Format service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
. Это приложение Service Account Engine Flex будет использовать для подключения к базе данных. Нажмите кнопку «Редактировать» в конце строки и добавьте роль « Cloud SQL Client ».
Скопируйте код проекта на путь Go
Чтобы Appengine запустил ваш код, он должен иметь возможность найти соответствующие файлы в пути GO. Убедитесь, что вы находитесь в вашем проекте Root Directory.
cd YOUR_PROJECT_ROOT
Скопируйте каталог на путь GO.
mkdir -p ~/gopath/src/austin-recycling cp -r ./ ~/gopath/src/austin-recycling
Изменить в этот каталог.
cd ~/gopath/src/austin-recycling
Развернуть ваше приложение
Используйте gcloud
CLI, чтобы развернуть ваше приложение. Это займет некоторое время, чтобы развернуть.
gcloud app deploy
Используйте команду browse
, чтобы получить ссылку, которую вы можете нажать, чтобы увидеть, как ваш полностью развернутый, эстетически потрясающий локатор магазина в корпоративном классе в действии.
gcloud app browse
Если бы вы запустили gcloud
за пределами облачной оболочки, то запуск gcloud app browse
откроет новую вкладку браузера.
12. (рекомендуется) очистить
Выполнение этого CodeLab останется в пределах бесплатных пределов уровня для обработки BigQuery и отображает вызовы API платформы, но если вы выполняете это исключительно в качестве образовательного упражнения и хотите избежать каких -либо будущих обвинений, самый простой способ удалить ресурсы, связанные с этим проектом, - это удалить сам проект.
Удалить проект
В консоли GCP перейдите на страницу Manager Cloud Resource Manager :
В списке проектов выберите проект, над которым мы работали, и нажмите «Удалить» . Вам будет предложено ввести идентификатор проекта. Введите его и нажмите, выключите.
В качестве альтернативы, вы можете удалить весь проект непосредственно из Cloud Shell с gcloud
, запустив следующую команду и заменив заполнителя GOOGLE_CLOUD_PROJECT
на идентификатор проекта:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. Поздравляю
Поздравляю! Вы успешно завершили CodeLab !
Или вы проезжали на последнюю страницу. Поздравляю! Вы сняли на последней странице !
В течение этого коделаба вы работали со следующими технологиями:
- Карты JavaScript API
- Сервис матрицы расстояний, карты JavaScript API (есть также API матрицы расстояний )
- Библиотека мест, карты JavaScript API (также помещает API )
- App Engine Гибкая среда (GO)
- Cloud SQL API
Дополнительное чтение
Есть еще много, чтобы узнать обо всех этих технологиях. Ниже приведены некоторые полезные ссылки на темы, которые у нас не было времени, чтобы охватить в этом коделабе, но, безусловно, могут быть полезны для создания решения для локатора магазина, которое соответствует вашим конкретным потребностям.