1. مقدمه
چکیده
تصور کنید مکانهای زیادی برای قرار دادن روی نقشه دارید و میخواهید کاربران بتوانند ببینند این مکانها کجا هستند و مکانهایی را که میخواهند از آن بازدید کنند، شناسایی کنند. نمونه های رایج این مورد عبارتند از:
- یک مکان یاب فروشگاه در وب سایت یک خرده فروش
- نقشه محل های رای گیری برای انتخابات آینده
- فهرستی از مکان های تخصصی مانند ظروف بازیافت باتری
چیزی که خواهی ساخت
در این نرم افزار کد، شما یک مکان یاب ایجاد می کنید که از فید داده های زنده مکان های تخصصی ترسیم می کند و به کاربر کمک می کند نزدیک ترین مکان را به نقطه شروع خود پیدا کند. این مکان یاب تمام پشته می تواند تعداد مکان های بسیار بیشتری را نسبت به مکان یاب فروشگاهی ساده که به 25 یا کمتر مکان فروشگاه محدود می شود، اداره کند.
چیزی که یاد خواهید گرفت
این آزمایشگاه کد از یک مجموعه داده باز برای شبیهسازی ابردادههای از پیش جمعشده در مورد تعداد زیادی مکان فروشگاه استفاده میکند تا بتوانید بر یادگیری مفاهیم فنی کلیدی تمرکز کنید.
- Maps JavaScript API: تعداد زیادی مکان را روی یک نقشه وب سفارشی نمایش می دهد
- GeoJSON: قالبی که ابردادههای مکانها را ذخیره میکند
- تکمیل خودکار مکان: به کاربران کمک می کند مکان های شروع را سریعتر و دقیق تر ارائه دهند
- Go: زبان برنامه نویسی مورد استفاده برای توسعه برنامه کاربردی. Backend با پایگاه داده تعامل خواهد داشت و نتایج پرس و جو را با فرمت JSON به قسمت جلویی ارسال می کند.
- App Engine: برای میزبانی برنامه وب
پیش نیازها
- دانش اولیه HTML و جاوا اسکریپت
- یک حساب کاربری گوگل
2. راه اندازی شوید
در مرحله 3 از بخش زیر، Maps JavaScript API ، Places API و Distance Matrix API را برای این Codelab فعال کنید.
با پلتفرم نقشه های گوگل شروع کنید
اگر قبلاً از Google Maps Platform استفاده نکردهاید، راهنمای Get Started with Google Maps Platform را دنبال کنید یا لیست پخش Started with Google Maps Platform را برای تکمیل مراحل زیر تماشا کنید:
- یک حساب صورتحساب ایجاد کنید.
- یک پروژه ایجاد کنید.
- APIها و SDKهای پلتفرم Google Maps را فعال کنید (در قسمت قبل فهرست شده است).
- یک کلید API ایجاد کنید.
Cloud Shell را فعال کنید
در این کد لبه از Cloud Shell استفاده میکنید، یک محیط خط فرمان که در Google Cloud اجرا میشود و دسترسی به محصولات و منابع در حال اجرا در Google Cloud را فراهم میکند تا بتوانید پروژه خود را به طور کامل از مرورگر وب خود میزبانی و اجرا کنید.
برای فعال کردن Cloud Shell از Cloud Console، روی Activate 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>
AppEngine Flex API را فعال کنید
AppEngine Flex API باید به صورت دستی از کنسول Cloud فعال شود. انجام این کار نه تنها API را فعال میکند، بلکه حساب AppEngine Flexible Environment Service را نیز ایجاد میکند، حساب تأیید شدهای که از طرف کاربر با سرویسهای Google (مانند پایگاههای داده SQL) در تعامل است.
3. سلام، جهان
Backend: Hello World in Go
در نمونه Cloud Shell خود، با ایجاد یک برنامه Go App Engine Flex شروع میکنید که بهعنوان پایهای برای بقیه بخش کدها عمل میکند.
در نوار ابزار Cloud Shell، روی دکمه Open editor کلیک کنید تا ویرایشگر کد در یک تب جدید باز شود. این ویرایشگر کد مبتنی بر وب به شما اجازه می دهد تا به راحتی فایل ها را در نمونه Cloud Shell ویرایش کنید.
سپس بر روی آیکون Open in new window کلیک کنید تا ویرایشگر و ترمینال به تب جدید منتقل شود.
در ترمینال در پایین تب جدید، یک فهرست راهنمای 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، که به راحتی handler
نامیده می شود، رشته متن "Hello, world!"
. این متن به مرورگر شما بازگردانده می شود، جایی که می توانید آن را بخوانید. در مراحل بعدی، کنترلکنندههایی خواهید ساخت که بهجای رشتههای کدگذاری سخت ساده، با دادههای GeoJSON پاسخ میدهند.
پس از انجام این مراحل، اکنون باید یک ویرایشگر به شکل زیر داشته باشید:
تستش کن
برای آزمایش این برنامه، می توانید سرور توسعه App Engine را در داخل نمونه Cloud Shell اجرا کنید. به خط فرمان Cloud Shell برگردید و عبارت زیر را تایپ کنید:
go run *.go
برخی از خطوط خروجی گزارش را مشاهده خواهید کرد که به شما نشان می دهد که واقعاً سرور توسعه را در نمونه Cloud Shell اجرا می کنید، با برنامه وب hello world در حال گوش دادن به درگاه لوکال هاست 8080. می توانید با فشار دادن دکمه Web Preview و انتخاب گزینه منوی Preview on port 8080 در نوار ابزار Cloud Shell، یک تب مرورگر وب را در این برنامه باز کنید.
با کلیک بر روی این آیتم منو، تب جدیدی در مرورگر وب شما با عبارت "Hello, World!" باز می شود. از سرور توسعه App Engine ارائه شده است.
در مرحله بعدی دادههای بازیافت شهر آستین را به این برنامه اضافه میکنید و شروع به تجسم آن میکنید.
4. داده های فعلی را دریافت کنید
GeoJSON، زبان فرانک دنیای GIS
در مرحله قبل ذکر شد که شما در کد Go خود کنترلکنندههایی ایجاد میکنید که دادههای GeoJSON را به مرورگر وب ارائه میکنند. اما GeoJSON چیست؟
در دنیای سیستم اطلاعات جغرافیایی (GIS)، ما باید بتوانیم دانش موجودات جغرافیایی را بین سیستمهای کامپیوتری به اشتراک بگذاریم. نقشهها برای خواندن انسانها عالی هستند، اما رایانهها معمولاً دادههایشان را در قالبهای هضمتر ترجیح میدهند.
GeoJSON قالبی برای رمزگذاری ساختارهای داده های جغرافیایی است، مانند مختصات مکان های رها شده بازیافت در آستین، تگزاس. GeoJSON در یک استاندارد نیروی کار مهندسی اینترنت به نام RFC7946 استاندارد شده است. GeoJSON بر اساس JSON تعریف شده است، جاوا اسکریپت Object Notation، که خود در ECMA-404 توسط همان سازمانی که جاوا اسکریپت را استاندارد کرده است، Ecma International ، استاندارد شده است.
نکته مهم این است که GeoJSON یک فرمت سیمی است که به طور گسترده برای انتقال دانش جغرافیایی پشتیبانی می شود. این کد لبه از GeoJSON به روش های زیر استفاده می کند:
- از بسته های Go برای تجزیه داده های آستین در یک ساختار داده خاص GIS داخلی استفاده کنید که از آن برای فیلتر کردن داده های درخواستی استفاده می کنید.
- داده های درخواستی را برای انتقال بین وب سرور و مرورگر وب سریال کنید.
- از کتابخانه جاوا اسکریپت برای تبدیل پاسخ به نشانگر روی نقشه استفاده کنید.
با این کار مقدار قابل توجهی در تایپ کد صرفه جویی خواهید کرد، زیرا برای تبدیل جریان داده های روی سیم به نمایش های درون حافظه، نیازی به نوشتن تجزیه کننده ها و ژنراتورها ندارید.
داده ها را بازیابی کنید
پورتال داده باز شهر آستین، تگزاس، اطلاعات مکانی را درباره منابع عمومی برای استفاده عمومی در دسترس قرار می دهد. در این آزمایشگاه کد، مجموعه دادههای مکانهای تخلیه بازیافت را تجسم خواهید کرد.
داده ها را با نشانگرهایی روی نقشه که با استفاده از لایه داده در Maps 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، برقراری تماس با Maps API و قرار دادن نشانگرها بر روی نقشه سفارشی شما خواهد بود.
این 3 فایل را ایجاد کنید، مطمئن شوید که آنها را در 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>
توجه ویژه ای به URL src
در تگ اسکریپت عنصر head
داشته باشید.
- متن مکاننما «
YOUR_API_KEY
» را با کلید API که در مرحله راهاندازی ایجاد کردید، جایگزین کنید. برای بازیابی کلید API یا ایجاد یک کلید جدید، میتوانید به صفحه APIs & Services -> Credentials در Cloud Console مراجعه کنید. - توجه داشته باشید که URL حاوی پارامتر
callback=initialize.
اکنون میخواهیم فایل جاوا اسکریپت حاوی تابع callback را ایجاد کنیم. اینجاست که برنامه شما مکانها را از پشتیبان بارگیری میکند، آنها را به Maps API میفرستد و از نتیجه برای علامتگذاری مکانهای سفارشی روی نقشه استفاده میکند که همه به زیبایی در صفحه وب شما ارائه میشوند. - پارامتر
libraries=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 Maps ظاهر میشود ، پنجره اطلاعات است.
یک شی 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. مکان شروع کاربر را دریافت کنید
کاربران مکان یاب فروشگاه معمولاً می خواهند بدانند کدام فروشگاه به آنها نزدیکتر است یا آدرسی که قصد دارند سفر خود را از آنجا شروع کنند. یک نوار جستجوی تکمیل خودکار مکان اضافه کنید تا کاربر بتواند به راحتی آدرس شروع را وارد کند. «تکمیل خودکار مکان» عملکردی شبیه به روش تکمیل خودکار در سایر نوارهای جستجوی Google ارائه میکند، به جز اینکه پیشبینیها همه مکانها در پلتفرم نقشههای Google هستند.
یک فیلد ورودی کاربر ایجاد کنید
برای افزودن یک ظاهر به نوار جستجوی تکمیل خودکار و پانل جانبی مرتبط نتایج، به edit style.css
برگردید. در حالی که ما در حال بهروزرسانی سبکهای CSS هستیم، سبکهایی را نیز برای نوار کناری آینده اضافه میکنیم که اطلاعات فروشگاه را بهعنوان فهرستی به همراه نقشه نمایش میدهد.
این کد را به انتهای فایل اضافه کنید.
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;
}
هر دو نوار جستجوی تکمیل خودکار و پانل کشویی در ابتدا تا زمانی که به آنها نیاز باشد پنهان می شوند.
یک 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 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
نشان می دهد که نمونه در حال ایجاد باید دارای یک vCPU و حدود 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 به یک فایل dump SQL است. سپس از فایل dump SQL می توان برای ایجاد جداول و ستون های خود برای پایگاه داده استفاده کرد و آن را با تمام داده هایی که در فایل های GeoJSON شما وجود داشت بارگیری کرد.
فایل dump SQL ایجاد کنید
ابتدا ogr2ogr را نصب کنید.
sudo apt-get install gdal-bin
در مرحله بعد، از ogr2ogr برای ایجاد فایل dump 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
برای استفاده از Cloud SQL، Go back end را به روز کنید
اکنون که همه این داده ها را در پایگاه داده خود داریم، وقت آن است که کد خود را به روز کنیم.
برای ارسال اطلاعات مکان، قسمت جلویی را بهروزرسانی کنید
بیایید با یک بهروزرسانی بسیار کوچک در قسمت جلو شروع کنیم: از آنجایی که ما اکنون این برنامه را برای مقیاسی مینویسیم که نمیخواهیم هر زمان که درخواست اجرا میشود، تک تک مکانها به جلویی تحویل داده شود، باید برخی از اطلاعات اولیه را از قسمت جلویی در مورد مکانی که کاربر به آن اهمیت میدهد ارسال کنیم.
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();
};
پس از تکمیل این مرحله از Codelab، پاسخ فقط نزدیک ترین فروشگاه ها به مختصات نقشه ارائه شده در پارامتر 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
ایجاد کنیم، همچنین در فهرست راهنمای بازیافت آستین. با پیاده سازی مجدد کنترل کننده برای درخواست های مکان شروع کنید.
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)
}
گرداننده وظایف مهم زیر را انجام می دهد:
- طول و عرض جغرافیایی را از شی درخواست بیرون می کشد (به یاد دارید چگونه آنها را به URL اضافه کردیم؟)
- این فراخوانی
getGeoJsonFromDatabase
را اجرا می کند که یک رشته GeoJSON را برمی گرداند (این را بعداً خواهیم نوشت.) - از
ResponseWriter
برای چاپ رشته GeoJSON در پاسخ استفاده می کند.
در مرحله بعد، ما یک استخر اتصال ایجاد می کنیم تا به مقیاس استفاده از پایگاه داده با کاربران همزمان کمک کند.
یک استخر اتصال ایجاد کنید
استخر اتصال مجموعه ای از اتصالات پایگاه داده فعال است که سرور می تواند مجدداً برای خدمات رسانی به درخواست های کاربر استفاده کند. با افزایش تعداد کاربران فعال شما، هزینه های زیادی را حذف می کند، زیرا سرور نیازی به صرف زمان برای ایجاد و از بین بردن اتصالات برای هر کاربر فعال ندارد. ممکن است در بخش قبلی متوجه شده باشید که کتابخانه github.com/jackc/pgx/stdlib.
این یک کتابخانه محبوب برای کار با استخرهای اتصال در Go است.
در انتهای locations.go
، یک تابع initConnectionPool
(که از main.go
فراخوانی می شود) ایجاد کنید که یک مخزن اتصال را مقداردهی اولیه می کند. برای وضوح، از چند روش کمکی در این قطعه استفاده شده است. configureConnectionPool
مکانی مناسب برای تنظیم تنظیمات استخر مانند تعداد اتصالات و طول عمر هر اتصال فراهم می کند. mustGetEnv
تماس ها را برای دریافت متغیرهای محیطی مورد نیاز جمع می کند، بنابراین اگر نمونه اطلاعات مهمی را از دست داده باشد (مانند IP یا نام پایگاه داده برای اتصال به آن) می توان پیام های خطای مفیدی ارسال کرد.
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
}
از پایگاه داده برای مکان ها پرس و جو کنید، در ازای آن JSON دریافت کنید.
اکنون می خواهیم یک کوئری پایگاه داده بنویسیم که مختصات نقشه را می گیرد و نزدیکترین 25 مکان را برمی گرداند. نه تنها این، بلکه به لطف برخی عملکردهای مدرن پایگاه داده فانتزی، آن داده ها را به عنوان GeoJSON برمی گرداند. نتیجه نهایی همه اینها این است که تا آنجا که کد جلویی می تواند بگوید، هیچ چیز تغییر نکرده است. قبل از اینکه درخواستی را برای یک URL ارسال کند و تعدادی GeoJSON دریافت کند. اکنون درخواستی را به یک URL ارسال می کند و ... دسته ای از GeoJSON را دریافت می کند.
در اینجا عملکرد انجام آن جادو است. تابع زیر را بعد از کنترل کننده و کد ادغام اتصال که در پایین 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
}
این تابع عمدتاً فقط راهاندازی، حذف و مدیریت خطا برای ارسال درخواست به پایگاه داده است. بیایید به 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) برای تعیین فاصله بین هر مکان در پایگاه داده و جفت lat/long مکانی که کاربر در قسمت جلویی ارائه کرده است استفاده می کند. به یاد داشته باشید که بر خلاف Distance Matrix، که می تواند مسافت رانندگی را به شما بدهد، این فواصل GeoSpatial هستند. سپس برای کارایی از آن فاصله برای مرتبسازی استفاده میکند و 25 نزدیکترین مکان را به مکان مشخص شده کاربر برمیگرداند.
** SELECT json_build_object('type', 'F
**eature') کوئری قبلی را میپیچد، نتایج را میگیرد و از آنها برای ساخت یک شی GeoJSON Feature استفاده میکند. به طور غیرمنتظره ای، این پرس و جو همچنین جایی است که حداکثر شعاع اعمال می شود "16090" تعداد متر در 10 مایل است، حد سختی که توسط Go backend مشخص شده است. اگر تعجب می کنید که چرا این عبارت WHERE به پرس و جو داخلی (که در آن فاصله هر مکان مشخص می شود) اضافه نشده است، به این دلیل است که نحوه اجرای SQL در پشت صحنه، ممکن است آن فیلد هنگام بررسی بند WHERE محاسبه نشده باشد. در واقع اگر بخواهید این عبارت WHERE را به پرس و جو داخلی منتقل کنید، یک خطا ایجاد می کند.
** SELECT json_build_object('type', 'FeatureColl
.
کتابخانه PGX را به پروژه خود اضافه کنید
ما باید یک وابستگی به پروژه شما اضافه کنیم: PostGres Driver & Toolkit ، که ادغام اتصال را فعال می کند. ساده ترین راه برای انجام این کار با Go Modules است. یک ماژول را با این دستور در پوسته ابری راه اندازی کنید:
go mod init my_locator
در مرحله بعد، این دستور را اجرا کنید تا کدها را برای وابستگی ها اسکن کنید، لیستی از وابستگی ها را به فایل مود اضافه کنید و آنها را دانلود کنید.
go mod tidy
در نهایت، این دستور را اجرا کنید تا وابستگی ها را مستقیماً به فهرست پروژه خود بکشید تا کانتینر به راحتی برای AppEngine Flex ساخته شود.
go mod vendor
خوب، شما برای آزمایش آن آماده هستید!
تستش کن
خوب، ما فقط کارهای زیادی انجام دادیم. بیایید کار آن را تماشا کنیم!
برای اینکه ماشین توسعه شما (بله، حتی پوسته ابری) به پایگاه داده متصل شود، باید از Cloud SQL Proxy برای مدیریت اتصال پایگاه داده استفاده کنیم. برای راه اندازی Cloud SQL Proxy:
- برای فعال کردن Cloud SQL Admin API به اینجا بروید
- اگر در یک ماشین توسعه محلی هستید، ابزار پروکسی ابر SQL را نصب کنید. اگر از پوسته ابری استفاده می کنید، می توانید این مرحله را رد کنید، این مرحله قبلاً نصب شده است! توجه داشته باشید که دستورالعمل ها به یک حساب خدمات اشاره می کنند. قبلاً یکی برای شما ایجاد شده است، و ما اضافه کردن مجوزهای لازم به آن حساب را در بخش زیر پوشش خواهیم داد.
- یک تب جدید (در پوسته ابری یا ترمینال خود) برای شروع پروکسی ایجاد کنید.
- از
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. نزدیک ترین فروشگاه ها را فهرست کنید
Directions API بسیار شبیه تجربه درخواست مسیرها در برنامه Google Maps عمل می کند - وارد کردن یک مبدأ و یک مقصد واحد برای دریافت مسیر بین این دو. Distance Matrix API این مفهوم را برای شناسایی جفتهای بهینه بین مبداهای متعدد ممکن و مقصدهای متعدد ممکن بر اساس زمان و مسافت سفر بیشتر میکند. در این حالت، برای کمک به کاربر برای یافتن نزدیکترین فروشگاه به آدرس انتخابشده، یک مبدا و مجموعهای از مکانهای فروشگاه را به عنوان مقصد ارائه میدهید.
فاصله از مبدا را به هر فروشگاه اضافه کنید
در ابتدای تعریف تابع 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);
});
};
این تابع، Distance Matrix API را با استفاده از مبدأ ارسال شده به آن به عنوان یک مبدا و مکانهای فروشگاه به عنوان آرایهای از مقصدها فراخوانی میکند. سپس، آرایهای از اشیاء را میسازد که شناسه فروشگاه را ذخیره میکند، فاصله بیان شده در یک رشته قابل خواندن برای انسان، فاصله بر حسب متر به عنوان یک مقدار عددی، و آرایه را مرتب میکند.
عملکرد initAutocompleteWidget
را به روز کنید تا مسافت های فروشگاه را در هر زمان که منشأ جدید از نوار جستجوی خودکار مکان انتخاب شود ، محاسبه کنید. در پایین عملکرد 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 Maps)" کلیک کنید.
دکمه ای را در نزدیکی بالا (درست زیر کادر جستجو) مشاهده خواهید کرد که می گوید شناسه نقشه جدید ایجاد کنید . روی آن کلیک کنید و هر نامی را که می خواهید پر کنید. برای نوع نقشه ، حتما JavaScript را انتخاب کنید و وقتی گزینه های بعدی نمایش داده می شود ، بردار را از لیست انتخاب کنید. نتیجه نهایی باید چیزی شبیه به تصویر زیر باشد.
روی "Next" کلیک کنید و با شناسه نقشه کاملاً جدید روبرو خواهید شد. اگر می خواهید ، اکنون می توانید آن را کپی کنید ، اما نگران نباشید ، بعداً به راحتی نگاه می کنید.
در مرحله بعد ما می خواهیم سبکی برای استفاده از آن نقشه ایجاد کنیم.
یک سبک نقشه ایجاد کنید
اگر هنوز در بخش نقشه های کنسول ابر هستید ، روی "Styles Map Styles در پایین منوی ناوبری در سمت چپ کلیک کنید. در غیر این صورت ، دقیقاً مانند ایجاد یک شناسه نقشه ، می توانید با تایپ کردن" سبک های نقشه "در کادر جستجو و انتخاب" سبک های نقشه (نقشه های گوگل) "از نتایج ، مانند تصویر زیر ، صفحه مناسب را پیدا کنید.
بعد روی دکمه نزدیک بالای بالای صفحه کلیک کنید که می گوید " + سبک نقشه جدید ایجاد کنید "
- اگر می خواهید با یک ظاهر طراحی شده در نقشه نشان داده شده در این آزمایشگاه مطابقت داشته باشید ، روی برگه " Import Json " کلیک کرده و حباب JSON را در زیر جای دهید. در غیر این صورت اگر می خواهید خود را ایجاد کنید ، سبک نقشه مورد نظر خود را انتخاب کنید. سپس روی Next کلیک کنید.
- شناسه نقشه ای را که فقط ایجاد کرده اید انتخاب کنید تا آن شناسه نقشه را با این سبک مرتبط کنید و دوباره روی Next کلیک کنید.
- در این مرحله به شما امکان سفارشی سازی بیشتر یک ظاهر طراحی شده از نقشه خود داده می شود. اگر این چیزی است که می خواهید کشف کنید ، روی Customize in Style Editor کلیک کنید و با رنگ ها و گزینه ها بازی کنید تا زمانی که سبک نقشه ای را که دوست دارید داشته باشید. در غیر این صورت روی پرش کلیک کنید.
- در مرحله بعدی ، نام و توضیحات سبک خود را وارد کنید و سپس روی ذخیره و انتشار کلیک کنید.
در اینجا یک حباب JSON اختیاری برای وارد کردن در مرحله اول وجود دارد.
[
{
"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
اضافه کنید - هنگامی که نقشه را در روش
initMap()
خود ایجاد می کنید ، شناسه نقشه را به عنوان یک آرگومان سازندهAdd
.
برچسب اسکریپت را که Maps JavaScript API را در پرونده 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 در حال اجرا ببینید (و نه فقط یک وب سایت محلی در دستگاه توسعه / پوسته ابر خود ، این همان کاری است که شما انجام داده اید) ، بسیار آسان است. ما فقط برای دسترسی به بانک اطلاعاتی به کار در محیط تولید ، باید چند مورد اضافه کنیم. این همه در صفحه اسناد در مورد اتصال از موتور Flex به Cloud SQL بیان شده است.
متغیرهای محیط را به App.yaml اضافه کنید
اول ، تمام متغیرهای محیطی که برای تست محلی استفاده می کنید باید به پایین پرونده app.yaml
برنامه خود اضافه شود.
- برای جستجوی نام اتصال نمونه ، به https://console.cloud.google.com/sql/instances/locations/overview مراجعه کنید.
- کد زیر را در انتهای
app.yaml
بچسبانید. -
YOUR_DB_PASSWORD_HERE
با رمز عبور خود که برای نام کاربریpostgres
ایجاد کرده اید زودتر جایگزین کنید. -
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 را به حساب سرویس AppEngine Flex اضافه کنید
به صفحه IAM-Admin در کنسول ابر بروید و به دنبال یک حساب کاربری باشید که نام آن با فرمت service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com
مطابقت دارد. این سرویس App App Engine Flex برای اتصال به پایگاه داده استفاده می کند. در انتهای ردیف روی دکمه ویرایش کلیک کنید و نقش " Cloud SQL Client " را اضافه کنید.
کد پروژه خود را در مسیر GO کپی کنید
برای اینکه AppEngine کد خود را اجرا کند ، باید بتواند پرونده های مربوطه را در مسیر GO پیدا کند. اطمینان حاصل کنید که در فهرست ریشه پروژه خود هستید.
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
استفاده کنید تا بتوانید روی آن را به طور کامل مستقر ، درجه سازمانی ، از نظر زیبایی شناسی فروشگاهی در Action مشاهده کنید.
gcloud app browse
اگر gcloud
در خارج از پوسته ابر اجرا می کردید ، در حال اجرا gcloud app browse
یک زبانه مرورگر جدید را باز می کند.
12. (توصیه می شود) تمیز کنید
انجام این CodeLab در محدودیت های سطح رایگان برای پردازش BigQuery و نقشه های API Platform Maps باقی خواهد ماند ، اما اگر این کار را صرفاً به عنوان یک تمرین آموزشی انجام داده اید و می خواهید از تحمل هرگونه هزینه های آینده جلوگیری کنید ، ساده ترین راه برای حذف منابع مرتبط با این پروژه ، حذف خود پروژه است.
پروژه را حذف کنید
در کنسول GCP ، به صفحه مدیر منبع Cloud بروید:
در لیست پروژه ، پروژه ای را که ما در آن کار کرده ایم انتخاب کنید و بر روی حذف کلیک کنید. از شما خواسته می شود شناسه پروژه را تایپ کنید. آن را وارد کرده و روی خاموش کردن کلیک کنید.
از طرف دیگر ، می توانید با اجرای دستور زیر ، کل پروژه را مستقیماً از Cloud Shell با gcloud
حذف کنید و جایگزین مکان نگهدارنده GOOGLE_CLOUD_PROJECT
با شناسه پروژه خود کنید:
gcloud projects delete GOOGLE_CLOUD_PROJECT
13. تبریک می گویم
تبریک می گویم! شما CodeLab را با موفقیت به پایان رسانده اید!
یا به صفحه آخر لاغر شدید. تبریک می گویم! شما به صفحه آخر لاغر شده اید!
در طول این CodeLab ، شما با فناوری های زیر کار کرده اید:
- Maps JavaScript API
- سرویس ماتریس از راه دور ، نقشه های API JavaScript (همچنین API ماتریس فاصله وجود دارد)
- کتابخانه مکان ها ، نقشه های API JavaScript (همچنین API را مکان می کند )
- محیط انعطاف پذیر موتور برنامه (GO)
- ابر SQL API
ادامه مطلب
در مورد همه این فناوری ها هنوز چیزهای زیادی برای یادگیری وجود دارد. در زیر برخی از پیوندهای مفید برای موضوعاتی که ما وقت آن را برای پوشش در این CodeLab نداشتیم ، اما مطمئناً می تواند برای شما در ساختن یک راه حل مکان یاب فروشگاه که متناسب با نیازهای خاص شما باشد ، مفید باشد.