إنشاء محدِّد مواقع لحزم الحزمة باستخدام "منصة خرائط Google" وGoogle Cloud

1. مقدمة

تجريدي

تخيّل أن لديك العديد من الأماكن لوضعها على خريطة وتريد أن يتمكن المستخدمون من رؤية أماكن هذه الأماكن وتحديد الأماكن التي يريدون زيارتها. ومن الأمثلة الشائعة على ذلك:

  • محدِّد مواقع المتاجر على موقع إلكتروني لبائع تجزئة
  • خريطة مواقع الاقتراع لانتخابات قادمة
  • دليل المواقع الجغرافية المتخصصة، مثل أوعية إعادة تدوير البطاريات

العناصر التي سيتم إنشاؤها

في هذا الدرس التطبيقي حول الترميز، ستنشئ محدِّد موقعًا يستند إلى خلاصة بيانات مباشرة للمواقع الجغرافية المتخصّصة ويساعد المستخدم في العثور على الموقع الجغرافي الأقرب إلى نقطة البداية. يمكن أن يتعرّف محدِّد موقع الحزمة بالكامل على عدد أكبر من الأماكن مقارنةً بمحدِّد مواقع المتاجر البسيط، الذي يقتصر على 25 موقعًا جغرافيًا أو أقل للمتجر.

2ece59c64c06e9da.png

ما ستتعرَّف عليه

يستخدم هذا الدرس التطبيقي حول الترميز مجموعة بيانات مفتوحة لمحاكاة البيانات الوصفية المعبأة تلقائيًا حول عدد كبير من المواقع الجغرافية للمتاجر حتى تتمكّن من التركيز على تعلّم المفاهيم الفنية الرئيسية.

  • واجهة برمجة تطبيقات JavaScript للخرائط: تعرض عددًا كبيرًا من المواقع الجغرافية على خريطة ويب مخصصة
  • GeoJSON: تنسيق لتخزين البيانات الوصفية حول المواقع
  • الإكمال التلقائي للأماكن: يمكنك مساعدة المستخدمين على توفير مواقع جغرافية للبدء بشكلٍ أسرع وأكثر دقة
  • Go: لغة البرمجة المستخدمة لتطوير خلفية التطبيق. ستتفاعل الخلفية مع قاعدة البيانات وستُرسل نتائج طلبات البحث إلى الواجهة الأمامية بتنسيق JSON المنسَّق.
  • App Engine: لاستضافة تطبيق الويب

المتطلّبات الأساسية

  • معرفة أساسية بـ HTML وJavaScript
  • حساب على Google

2. الإعداد

في الخطوة 3 من القسم التالي، فعِّل واجهة برمجة تطبيقات JavaScript للخرائط وواجهة برمجة تطبيقات الأماكن وواجهة برمجة تطبيقات مصفوفة المسافة لهذا الدرس التطبيقي حول الترميز.

بدء استخدام "منصة خرائط Google"

إذا لم يسبق لك استخدام "منصة خرائط Google"، يمكنك اتّباع دليل بدء استخدام "منصة خرائط Google" أو مشاهدة "البدء في استخدام قائمة تشغيل منصة خرائط Google" لإكمال الخطوات التالية:

  1. أنشئ حساب فوترة.
  2. أنشئ مشروعًا.
  3. فعِّل واجهات برمجة تطبيقات ومنصة SDK لمنصة "خرائط Google" (المُدرَجة في القسم السابق).
  4. أنشئ مفتاح واجهة برمجة تطبيقات.

تفعيل Cloud Shell

في هذا الدرس التطبيقي حول الترميز، تستخدم Cloud Shell، وهو عبارة عن بيئة سطر أوامر تعمل في Google Cloud وتوفّر إمكانية الوصول إلى المنتجات والموارد التي يتم تشغيلها على Google Cloud، حتى تتمكّن من استضافة مشروعك وتشغيله بالكامل من متصفّح الويب.

لتفعيل Cloud Shell من Cloud Console، انقر على تفعيل Cloud Shell 89665d8d348105cd.png (من المفترض أن تستغرق إدارة الحسابات والاتصال بالبيئة بضع لحظات فقط).

5f504766b9b3be17.png

يؤدي ذلك إلى فتح واجهة جديدة في الجزء السفلي من المتصفح بعد عرض إعلان بيني تمهيدي.

d3bb67d514893d1f.png

تأكيد مشروعك

بعد الربط بخدمة 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 Console. عند تفعيل ذلك، لن يتم تفعيل واجهة برمجة التطبيقات فحسب، بل سيتم أيضًا إنشاء حساب خدمة بيئة AppEngine المرنة وهو الحساب الذي تمت مصادقته والذي سيتفاعل مع خدمات Google (مثل قواعد بيانات SQL) نيابة عن المستخدم.

3- مرحبًا بالعالم

الخلفية: Hello World in Go

في مثيل Cloud Shell، ستبدأ من خلال إنشاء تطبيق Go App Engine Flex الذي يعمل كأساس لبقية الدرس التطبيقي حول الترميز.

في شريط أدوات Cloud Shell، انقر على الزر Open Editor (فتح المحرِّر) لفتح محرِّر رموز في علامة تبويب جديدة. يتيح لك محرِّر الرموز المستند إلى الويب تعديل الملفات بسهولة في مثيل Cloud Shell.

b63f7baad67b6601.png

بعد ذلك، انقر على رمز الفتح في نافذة جديدة لنقل المحرّر والمحطة إلى علامة تبويب جديدة.

3f6625ff8461c551.png

في الدليل أسفل علامة التبويب الجديدة، أنشئ دليل 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.

بعد ذلك، أنشِئ ملف 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 بدلاً من سلاسل بسيطة ذات ترميز ثابت.

بعد تنفيذ هذه الخطوات، يجب أن يكون لديك الآن محرِّر يبدو كما يلي:

2084fdd5ef594ece.png

تجربة الميزة

لاختبار هذا التطبيق، يمكنك تشغيل خادم تطوير App Engine داخل مثيل Cloud Shell. ارجع إلى سطر أوامر Cloud Shell، واكتب ما يلي:

go run *.go

ستظهر لك بعض أسطر مخرجات السجل التي تُظهر أنك تعمل فعلاً على خادم التطوير على مثيل Cloud Shell، مع الاستماع إلى تطبيق الويب العالمي الترحيبي على منفذ localhost رقم 8080. يمكنك فتح علامة تبويب متصفِّح الويب على هذا التطبيق من خلال الضغط على زر معاينة الويب واختيار عنصر القائمة معاينة على المنفذ 8080 في شريط أدوات Cloud Shell.

4155fc1dc717ac67.png

عند النقر على عنصر القائمة هذا، سيتم فتح علامة تبويب جديدة في متصفّح الويب تتضمّن الكلمات "مرحبًا، عالمي!&quot؛ يتم عرضها من خادم تطوير التطبيقات App Engine.

في الخطوة التالية، ستضيف بيانات إعادة تدوير مدينة أوستن إلى هذا التطبيق وابدأ في تمثيلها.

4. الحصول على البيانات الحالية

GeoJSON، اللغة اليابانية في عالم نظم المعلومات الجغرافية

ذكرت الخطوة السابقة أنك ستحتاج إلى معالجات في رمز Go تعرض بيانات GeoJSON على متصفح الويب. ما هو GeoJSON؟

في عالم نظام المعلومات الجغرافية (GIS)، نحتاج إلى القدرة على إيصال المعرفة حول الكيانات الجغرافية بين أنظمة الكمبيوتر. تشكّل "خرائط Google" وسيلة رائعة ليقرأها المستخدمون، ولكنّ أجهزة الكمبيوتر تفضّل عادةً بياناتها بتنسيقات يسهل فهمها.

GeoJSON هو تنسيق لترميز بنى البيانات الجغرافية، مثل إحداثيات المواقع الجغرافية لإعادة التدوير في أوستن، تكساس. تم توحيد GeoJSON في معيار Internet Engineering Task Force (مجموعة مهام هندسة الإنترنت) يُسمى RFC7946. يتم تحديد GeoJSON بواسطة JSON، JavaScript Object Notation، والتي تم توحيدها في ECMA-404، من خلال المؤسسة نفسها التي قامت بتوحيد JavaScript، Ecma International.

المهم هو أنّ GeoJSON هو تنسيق سلكي شائع الاستخدام لتعريف المعرفة الجغرافية. يستخدم هذا الدرس التطبيقي حول الترميز GeoJSON بالطرق التالية:

  • يمكنك استخدام حزم Go لتحليل بيانات أوستن في بنية بيانات داخلية تستند إلى GIS وستستخدمها لفلترة البيانات المطلوبة.
  • تسلسل البيانات المطلوبة للنقل العام بين خادم الويب ومتصفح الويب.
  • استخدِم مكتبة JavaScript لتحويل الاستجابة إلى علامات محدّدة على خريطة.

سيوفّر لك ذلك قدرًا كبيرًا من الكتابة بالترميز، نظرًا لأنك لن تحتاج إلى كتابة محلّلات ومولدات بيانات لتحويل مصدر البيانات على الأسلاك إلى تمثيلات في الذاكرة.

استرداد البيانات

إن بوابة البيانات في مدينة أوستن، في ولاية تكساس تجعل المعلومات الجغرافية المكانية حول الموارد العامة متاحة للاستخدام العام. في هذا الدرس التطبيقي حول الترميز، يمكنك الاطّلاع على تمثيل بصري لمجموعة بيانات مواقع إعادة التدوير.

ستتمكّن من عرض البيانات باستخدام محدّدات الموقع على الخريطة، والتي يتم عرضها باستخدام طبقة البيانات في واجهة برمجة تطبيقات JavaScript للخرائط.

يمكنك البدء بتنزيل بيانات GeoJSON من موقع مدينة أوستن الإلكتروني إلى تطبيقك.

  1. في نافذة سطر الأوامر الخاصة بـ Cloud Shell&#39، يمكنك إغلاق الخادم من خلال كتابة [CTRL] + [C].
  2. أنشِئ دليل 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 ليعكس التطبيق الأقوياء، والذي ليس فقط تطبيقًا للترحيب بالعالم؛

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.

إنشاء الواجهة الخلفية

لاحظت على الأرجح أنّ ملف GeoJSON هو ملف واحد (app.yaml) غير لا يثير اهتمامه. ذلك's لأنّ GeoJSON ستتم معالجته وإرساله إلينا في الخلفية من Go، ما يتيح لنا إضافة بعض الميزات الرائعة في الخطوات اللاحقة. يمكنك تغيير ملف main.go للقراءة كما يلي:

main.go

package main

import (
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        "os"
        "path/filepath"
)

var GeoJSON = make(map[string][]byte)

// cacheGeoJSON loads files under data into `GeoJSON`.
func cacheGeoJSON() {
        filenames, err := filepath.Glob("data/*")
        if err != nil {
                log.Fatal(err)
        }

        for _, f := range filenames {
                name := filepath.Base(f)
                dat, err := ioutil.ReadFile(f)
                if err != nil {
                        log.Fatal(err)
                }
                GeoJSON[name] = dat
        }
}

func main() {
        // Cache the JSON so it doesn't have to be reloaded every time a request is made.
        cacheGeoJSON()


        // Request for data should be handled by Go.  Everything else should be directed
        // to the folder of static files.
        http.HandleFunc("/data/dropoffs", dropoffsHandler)
        http.Handle("/", http.FileServer(http.Dir("./static/")))

        // Open up a port for the webserver.
        port := os.Getenv("PORT")
        if port == "" {
                port = "8080"
        }
        log.Printf("Listening on port %s", port)

        if err := http.ListenAndServe(":"+port, nil); err != nil {
                log.Fatal(err)
        }
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
        // Writes Hello, World! to the user's web browser via `w`
        fmt.Fprint(w, "Hello, world!")
}

func dropoffsHandler(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-type", "application/json")
        w.Write(GeoJSON["recycling-locations.geojson"])
}

تقدّم لنا الآن خلفية Go ميزة قيّمة: تعمل مثيل AppEngine على تخزين ذاكرة التخزين المؤقت لجميع هذه المواقع فور بدء تشغيلها. يؤدي هذا إلى توفير الوقت لأنّ الخلفية لن تضطر إلى قراءة الملف من القرص عند كل إعادة تحميل لكل مستخدم.

إنشاء الواجهة الأمامية

أول ما نحتاج إليه هو إنشاء مجلد للاحتفاظ بجميع مواد العرض الثابتة. من المجلد الرئيسي لمشروعك، أنشئ مجلد static.

mkdir -p static && cd static

سننشئ 3 ملفات في هذا المجلد.

  • سيحتوي index.html على جميع رموز HTML لتطبيق محدِّد مواقع المتاجر المؤلَّف من صفحة واحدة.
  • style.css، كما تتوقع، سيحتوي على التصميم
  • ستكون app.js مسؤولة عن استرداد GeoJSON وإجراء مكالمات إلى واجهة برمجة تطبيقات الخرائط ووضع العلامات على خريطتك المخصصة.

يمكنك إنشاء هذه الملفات الثلاثة، مع الحرص على وضعها في 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&quot، بمفتاح واجهة برمجة التطبيقات الذي أنشأته أثناء خطوة الإعداد. يمكنك الانتقال إلى صفحة واجهات برمجة التطبيقات &amp؛ الخدمات -> بيانات الاعتماد في Cloud Console لاسترداد مفتاح واجهة برمجة التطبيقات أو إنشاء مفتاح جديد.
  • ملاحظة: يحتوي عنوان URL على المَعلمة callback=initialize. We'وسنعمل الآن على إنشاء ملف JavaScript يحتوي على دالة رد الاتصال هذه. هذا هو المكان الذي سيحمّل فيه تطبيقك المواقع من الخلفية، ويرسلها إلى 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

يمكنك معاينته كما فعلت من قبل. من المفترض أن ترى خريطة بها دوائر خضراء صغيرة مثل هذه.

58a6680e9c8e7396.png

أنت تعرض مواقع الخرائط حاليًا، وأنت في منتصف الدرس التطبيقي فقط. مدهش. لنضيف الآن بعض التفاعل.

6- عرض تفاصيل عند الطلب

الاستجابة لأحداث النقر على علامات الخريطة

يُعد عرض مجموعة من العلامات على الخريطة بدايةً رائعة، ولكننا نحتاج إلى أن يكون بإمكان الزائر النقر على إحدى هذه العلامات والاطّلاع على معلومات متعلقة بهذا الموقع الجغرافي (مثل اسم النشاط التجاري والعنوان وما إلى ذلك). اسم نافذة المعلومات الصغيرة التي تظهر عادةً عند النقر على علامة "خرائط Google" هي نافذة المعلومات.

إنشاء كائن 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 مع معلومات المتجر المختارة عندما يتم النقر على علامة متجر على الخريطة.

إذا كان الخادم لا يزال قيد التشغيل، يُرجى إيقافه وإعادة تشغيله. يمكنك إعادة تحميل صفحة الخريطة ومحاولة النقر على محدّد الخريطة. يجب أن تظهر نافذة معلومات صغيرة تعرض اسم النشاط التجاري وعنوانه وبتبدو كما يلي:

1af0ab72ad0eadc5.png

7- الحصول على موقع بداية المستخدم

عادةً ما يريد مستخدمو محدِّدي مواقع المتاجر معرفة المتجر الأقرب إليهم أو العنوان الذي يخططون لبدء رحلتهم. أضف شريط بحث خدمة الإكمال التلقائي للأماكن للسماح للمستخدم بإدخال عنوان بداية بسهولة. توفر ميزة الإكمال التلقائي للأماكن وظيفة مشابهة للكتابة بالطريقة نفسها التي تعمل بها ميزة الإكمال التلقائي في أشرطة بحث Google الأخرى، باستثناء التوقعات التي تمثل جميع الأماكن في منصة خرائط Google.

إنشاء حقل إدخال المستخدم

يمكنك الرجوع لتعديل 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 -->&quot؛ بالرمز التالي. أثناء إجراء هذا التعديل، سنضيف أيضًا 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

من المفترض أن تظهر لك أداة الإكمال التلقائي في أعلى الجانب الأيسر من الخريطة الآن، والتي تعرض لك عناوين الولايات المتحدة التي تتطابق مع ما تكتبه، والمتحاز نحو المنطقة المرئية من الخريطة.

58e9bbbcc4bf18d1.png

تعديل الخريطة عندما يختار المستخدم عنوان بداية

والآن، عليك التعامل مع الوقت الذي يحدد فيه المستخدم توقعًا من أداة الإكمال التلقائي واستخدام هذا الموقع كأساس لحساب المسافات إلى متاجرك.

إضافة الرمز التالي إلى نهاية initAutocompleteWidget في app.js، مع استبدال التعليق &;// TODO: Respond when a user selects an address".

app.js - initcompleteWidget

  // 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 إلى أنّ النسخة الافتراضية التي يتم إنشاؤها يجب أن تحتوي على وحدة vCPU واحدة و3.75 غيغابايت تقريبًا من الذاكرة.

سيتم إنشاء مثيل Cloud SQL وإعداده باستخدام قاعدة بيانات PostGresSQL، مع المستخدم التلقائي postgres. ما هي كلمة مرور هذا المستخدم؟ نشكرك على طرح هذا السؤال. وليس لديهم واحدة. يجب إعداد حساب حتى تتمكّن من تسجيل الدخول.

اضبط كلمة المرور باستخدام الأمر التالي:

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

أدخِل بعد ذلك كلمة المرور التي اخترتها عندما يُطلب منك ذلك.

تفعيل إضافة PostGIS

PostGIS هي إضافة إلى PostGresSQL وتسهّل تخزين الأنواع الموحّدة من البيانات الجغرافية المكانية. في ظل الظروف العادية، علينا تنفيذ عملية تثبيت كاملة لإضافة PostGIS إلى قاعدة بياناتنا. ولحسن الحظ، هي إحدى الإضافات المتوافقة مع Cloud SQL&s#39s’s for PostGresSQL.

اتصِل بمثيل قاعدة البيانات من خلال تسجيل الدخول باسم المستخدم postgres باستخدام الأمر التالي في الوحدة الطرفية Shell Shell.

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

أدخِل كلمة المرور التي أنشأتها للتو. والآن، أضِف إضافة PostGIS في موجه الأوامر postgres=>.

CREATE EXTENSION postgis;

في حالة نجاح هذه العملية، يجب أن تكون المخرجات هي "إنشاء إضافة"، كما هو موضح أدناه.

مثال على الناتج عن الأمر

CREATE EXTENSION

وأخيرًا، عليك إنهاء اتصال قاعدة البيانات عن طريق إدخال أمر الخروج في موجه الأوامر 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

تعديل نهاية الرجوع لاستخدام Cloud SQL

الآن بعد أن وضعنا كل هذه البيانات في قاعدة بياناتنا، حان وقت تعديل الرمز الخاص بنا.

تعديل الواجهة الأمامية لإرسال معلومات الموقع الجغرافي

لنبدأ بتحديث صغير جدًا للواجهة الأمامية: لأننا نكتب الآن هذا التطبيق على نطاق لا نريد أن يتم تسليم كل موقع جغرافي واحد إلى الواجهة الأمامية في كل مرة يتم فيها تشغيل طلب البحث، نحتاج إلى تمرير بعض المعلومات الأساسية من الواجهة الأمامية حول الموقع الذي يهتم به المستخدم.

افتح app.js واستبدل تعريف الدالة fetchStores بهذا الإصدار لتضمين خط العرض وخط الطول محل الاهتمام في عنوان URL.

app.js - fetchStores

const fetchStores = async (center) => {
  const url = `/data/dropoffs?centerLat=${center.lat}&centerLng=${center.lng}`;
  const response = await fetch(url);
  return response.json();
};

بعد إكمال هذه الخطوة من الدرس التطبيقي، ستعرض الاستجابة فقط المتاجر الأقرب إلى إحداثيات الخريطة المتوفّرة في المعلّمة center. بالنسبة إلى عملية الجلب الأولية في الدالة initialize، يستخدم نموذج الرمز المقدَّم في هذا الدرس التطبيقي الإحداثيات المركزية لأوستن، تكساس.

وبما أنّ fetchStores سيعرض الآن مجموعة فرعية من المواقع الجغرافية للمتاجر فقط، سنحتاج إلى إعادة جلب المتاجر كلما غيّر المستخدم الموقع الجغرافي للبدء.

عليك تحديث الدالة initAutocompleteWidget لإعادة تحميل المواقع الجغرافية عندما يتم تحديد مصدر جديد. ويتطلب ذلك تعديلَين:

  1. ضمن initcompleteWidget، ابحث عن معاودة الاتصال للمستمع place_changed. ألغِ تعليق السطر الذي يمحو الدوائر الحالية، بحيث سيتم تشغيل هذا السطر الآن في كل مرة يحدد فيها المستخدم عنوانًا من البحث عن الإكمال التلقائي للأماكن.

app.js - initcompleteWidget

  autocomplete.addListener("place_changed", async () => {
    circles.forEach((c) => c.setMap(null)); // clear existing stores
    // ...
  1. وكلما تم تغيير المصدر المحدّد، يتم تعديل نقطة الانطلاق بالمتغيّر. في نهاية المعلّمة place_changed&،;&&;;معاودة الاتصال, إلغاء التعليق على السطر أعلى الرمز &// TODO: Calculate the closest stores;&;;; لتمرير هذا الأصل الجديد إلى استدعاء جديد إلى الدالة fetchAndRenderStores.

app.js - initcompleteWidget

    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) الفعلية التي تنفّذ الكثير من الأمور المثيرة للاهتمام حقًا في طبقة قاعدة البيانات، لذا لا داع للقلق بشأن تنفيذ أي منها في رمز.

يظهر طلب البحث الأوّلي الذي يتم تنشيطه، بعد تحليل السلسلة وجميع الأحرف الحرفية للسلسلة التي تم إدراجها في أماكنها المناسبة، على النحو التالي:

تحليل 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's من دوال قياس الموقع الجغرافي) لتحديد المسافة بين كل موقع في قاعدة البيانات وزوج خطوط الطول/العرض للموقع الذي قدمه المستخدم في الواجهة الأمامية. تذكّر أنه على عكس مصفوفة المسافة التي يمكن أن تمنحك مسافة قيادة، فإن هذه هي مسافات جغرافية مكانية. وبالنسبة إلى الكفاءة، فإنها تستخدم تلك المسافة لترتيب أكثر من 25 موقعًا جغرافيًا وعرضها إلى الموقع الجغرافي المحدد للمستخدم.

يعمل **SELECT json_build_object(‘type', ‘F**eature') على ملء طلب البحث السابق، مع أخذ النتائج واستخدامها لإنشاء كائن ميزة GeoJSON. بشكل غير متوقع، هذا الطلب هو أيضًا المكان الذي يتم فيه تطبيق الحد الأقصى للنطاق الجغرافي &quot، 16090&quot؛ هو عدد الأمتار في 10 أميال، وهو الحد الثابت الذي وضعته خلفية Go. إذا كنت تتساءل عن سبب عدم إضافة هذه الفقرة WHERE إلى طلب البحث الداخلي (حيث يتم تحديد المسافة لكل موقع جغرافي) بدلاً من ذلك، فإنها بسبب طريقة تنفيذ SQL خلف الكواليس، قد لا يتم حساب هذا الحقل عند فحص البند WHERE. في حال حاولت نقل عبارة WHERE إلى طلب البحث الداخلي، سيؤدي ذلك إلى ظهور خطأ.

**SELECT json_build_object(‘type', ‘FeatureColl**ection') يغطي طلب البحث هذا جميع الصفوف الناتجة من طلب بحث إنشاء JSON في كائن GeoJSON FeatureCollection.

إضافة مكتبة PGX إلى مشروعك

نحتاج إلى إضافة تبعية واحدة إلى مشروعك: PostGres Driver &amp؛ مجموعة أدوات، والتي تتيح تجميع الاتصال. وأسهل طريقة لإجراء ذلك هي استخدام وحدات Go. أعدّ وحدة باستخدام هذا الأمر في Cloud Shell:

go mod init my_locator

بعد ذلك، شغِّل هذا الأمر لفحص الرمز بحثًا عن الارتباطات، وإضافة قائمة اعتماديات إلى ملف mod، وتنزيلها.

go mod tidy

وأخيرًا، شغِّل هذا الأمر لسحب التبعيات مباشرةً إلى دليل المشروع حتى يمكن إنشاء الحاوية بسهولة لـ AppEngine Flex.

go mod vendor

حسنًا، أنت مستعد لتجربة هذه الميزة.

تجربة الميزة

حَسَنًا، عَمَلْنَا حَالًا كَثِيرًا. لنبدأ العمل.

كي يتسنى لجهاز التطوير (نعم، حتى Cloud Shell) الاتصال بقاعدة البيانات، سنضطر إلى استخدام Cloud SQL Proxy لإدارة اتصال قاعدة البيانات. لإعداد خادم وكيل SQL SQL:

  1. يُرجى الانتقال إلى هنا لتفعيل Cloud Identity Admin API
  2. إذا كنت تستخدم جهاز تطوير محلي، يمكنك تثبيت أداة الخادم الوكيل SQL. إذا كنت تستخدم Cloud Shell، يمكنك تخطي هذه الخطوة، إنها مثبّتة حاليًا. لاحظ أن التعليمات ستشير إلى حساب الخدمة. تم إنشاء حساب لك من قبل، وسنتناول إضافة الأذونات اللازمة إلى هذا الحساب في القسم التالي.
  3. أنشِئ علامة تبويب جديدة (في Cloud Shell أو الوحدة الطرفية الخاصة بك) لبدء الخادم الوكيل.

bcca42933bfbd497.png

  1. انتقِل إلى https://console.cloud.google.com/sql/instances/locations/overview وانتقِل للأسفل للعثور على حقل اسم الاتصال. انسخ هذا الاسم لاستخدامه في الأمر التالي.
  2. في علامة التبويب هذه، شغِّل الخادم الوكيل Cloud SQL بهذا الأمر، مع استبدال CONNECTION_NAME باسم الاتصال الموضّح في الخطوة السابقة.
cloud_sql_proxy -instances=CONNECTION_NAME=tcp:5432

ارجِع إلى علامة التبويب الأولى من Cloud Shell وحدِّد متغيّرات البيئة التي ستحتاج إليها 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- قائمة المتاجر الأقرب

تعمل واجهة برمجة تطبيقات الاتجاهات إلى حد كبير تجربة طلب الاتجاهات في تطبيق "خرائط Google"، مثل إدخال نقطة انطلاق واحدة ووجهة واحدة للحصول على مسار بينهما. تعتمد واجهة برمجة تطبيقات مصفوفة Matrix أكثر على هذا المفهوم لتحديد عمليات الإقران المثالية بين العديد من الأصول المحتملة ووجهات محتملة متعددة استنادًا إلى أوقات السفر والمسافات. في هذه الحالة، لمساعدة المستخدم في العثور على أقرب متجر إلى العنوان المحدّد، يمكنك تقديم مصدر واحد ومجموعة من مواقع المتاجر كوجهات.

إضافة المسافة من نقطة الانطلاق إلى كل متجر

في بداية تعريف الدالة initMap، استبدِل التعليق &quot؛// TODO: Start Distance Matrix service&quot؛ بالرمز التالي:

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

تستدعي الدالة واجهة برمجة التطبيقات لمصفوفة المسافة باستخدام المصدر الذي تم تمريره إليها على أنها أصل واحد ومواقع المتجر كمصفوفة من الوجهات. وبعد ذلك، تُنشئ مجموعة من العناصر لتخزين رقم تعريف المتجر، والمسافة التي يتم التعبير عنها في سلسلة يمكن للمستخدمين قراءتها، والمسافة بالأمتار كقيمة رقمية، ويرتّب المصفوفة.

يمكنك تعديل دالة initAutocompleteWidget لحساب المسافات في المتاجر عند اختيار مصدر جديد من شريط البحث في الإكمال التلقائي للأماكن. في أسفل دالة initAutocompleteWidget، استبدِل التعليق &quot؛// TODO: Calculate the closest stores&quot؛ بالرمز التالي:

app.js - initcompleteWidget

    // 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

وأخيرًا، أدخِل عنوان أوستن، تكساس في شريط البحث في الإكمال التلقائي، ثم انقر على أحد الاقتراحات.

يجب أن تتوسط الخريطة هذا العنوان، ويجب أن يظهر شريط جانبي يسرد مواقع المتاجر بترتيب المسافة من العنوان المحدد. في ما يلي صورة للمثال:

96e35794dd0e88c9.png

10- اختيار نمط الخريطة

تتمثّل إحدى الطرق العالية التأثير لتمييز الخريطة عن غيرها بصريًا في إضافة تصميم إليها. باستخدام نمط الخريطة المستنِد إلى السحابة الإلكترونية، يتم التحكّم في تخصيص خرائطك من Cloud Console باستخدام نمط الخرائط المستنِد إلى السحابة الإلكترونية (إصدار تجريبي). إذا كنت تفضّل تصميم الخريطة باستخدام ميزة غير تجريبية، يمكنك استخدام مستندات تصميم الخريطة لمساعدتك في إنشاء ملف JSON لتصميم الخريطة آليًا. ترشدك التعليمات التالية خلال نمط الخريطة المستنِد إلى السحابة الإلكترونية (إصدار تجريبي).

إنشاء رقم تعريف للخريطة

أولاً، افتح Cloud Console وفي مربع البحث، واكتب "إدارة الخرائط&;;؛ انقر على النتيجة التي تعرض "إدارة الخرائط (خرائط Google)&quot. 64036dd0ed200200.png

سترى زرًا بالقرب من أعلى الصفحة (أسفل مربّع البحث مباشرةً) يحمل عبارة إنشاء رقم تعريف خريطة جديد. انقر على ذلك، واملأ أي اسم تريده. بالنسبة إلى نوع الخريطة، تأكد من تحديد JavaScript، وعند ظهور المزيد من الخيارات، اختَر المتّجه من القائمة. يجب أن تبدو النتيجة النهائية على النحو التالي للصورة أدناه.

70f55a759b4c4212.png

انقر على "&القبو"&التالية، وستحصل على رقم تعريف جديد للخريطة. يمكنك نسخه الآن إذا أردت، ولكن لا داعي للقلق، يمكنك البحث عنه لاحقًا.

بعد ذلك، سننشئ نمطًا لتطبيقه على تلك الخريطة.

إنشاء نمط خريطة

إذا كنت لا تزال في قسم "خرائط Google" في Cloud Console، انقر على &quot،أنماط الخرائط" في أسفل قائمة التنقُّل على يمين الصفحة. وبخلاف ذلك، تمامًا كما هو الحال مع إنشاء رقم تعريف للخريطة، يمكنك العثور على الصفحة الصحيحة عن طريق كتابة "خريطة الأنماط والأنماط&quot في مربّع البحث واختيار &خريطة الأنماط (خرائط Google) من النتائج، كما في الصورة أدناه.

9284cd200f1a9223.png

انقر على الزر التالي بالقرب من الجزء العلوي الذي يحتوي على عبارة &;+ إنشاء نمط خريطة جديد;

  1. إذا كنت ترغب في مطابقة النمط في الخريطة المعروضة في هذا الدرس التطبيقي، انقر على علامة التبويب &;استيراد ملف JSON" ثم الصق كائن JSON الثنائي الكبير أدناه. بخلاف ذلك إذا كنت تريد إنشاء نمط خاص بك، اختَر نمط الخريطة الذي تريد البدء به. ثم انقر على التالي.
  2. اختَر رقم تعريف الخريطة الذي أنشأته للتو لربط رقم تعريف الخريطة بهذا النمط، ثم انقر على التالي مرة أخرى.
  3. في هذه المرحلة، سيتوفر لك خيار تخصيص نمط خريطتك بصورةٍ أكبر. إذا كان هذا هو الأمر الذي تريد استكشافه، انقر على تخصيص في محرر الأنماط وتجوّل باستخدام الألوان &amp؛; إلى أن يتوفر لديك نمط خريطة يعجبك. بخلاف ذلك، انقر على التخطّي.
  4. في الخطوة التالية، أدخل اسم ووصف نمطك، ثم انقر على حفظ ونشر.

في ما يلي كائن 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"
      }
    ]
  }
]

إضافة رقم تعريف الخريطة إلى رمزك

الآن بعد أن واجهت مشكلة في إنشاء نمط الخريطة هذا، كيف يمكنك فعليًا استخدام نمط الخريطة هذا في خريطتك؟ عليك إجراء تغييرَين بسيطَين:

  1. إضافة رقم تعريف الخريطة كمعلّمة عنوان URL إلى علامة النص البرمجي في index.html
  2. Add رقم تعريف الخريطة كوسيطة دالة إنشاء عند إنشاء الخريطة في طريقة initMap().

استبدِل علامة النص البرمجي التي تُحمِّل واجهة برمجة تطبيقات JavaScript لـ "خرائط Google" في ملف 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 أعلاه.

2ece59c64c06e9da.png

11- النشر لمرحلة الإنتاج

إذا كنت تريد مشاهدة تطبيقك قيد التشغيل من AppEngine Flex (وليس فقط خادم ويب محلي على جهاز التطوير / Cloud Shell، وهو ما فعلته بنفسك)، فهذا أمر سهل للغاية. علينا إضافة بعض العناصر لكي يعمل الوصول إلى قاعدة البيانات في بيئة الإنتاج. تم توضيح ذلك في صفحة المستندات حول الاتصال من App Engine Flex إلى Cloud SQL.

إضافة متغيرات البيئة إلى App.yaml

أولاً، يجب إضافة جميع متغيرات البيئة التي كنت تستخدمها لاختبار محليًا إلى أسفل ملف app.yaml الخاص بتطبيقك.

  1. انتقِل إلى https://console.cloud.google.com/sql/instances/locations/overview للبحث عن اسم اتصال المثيل.
  2. ألصِق الرمز التالي في نهاية app.yaml.
  3. استبدل YOUR_DB_PASSWORD_HERE بكلمة المرور التي أنشأتها لاسم المستخدم postgres في وقت سابق.
  4. استبدِل 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**.** ويرجع ذلك إلى أنه سيتواصل مع Cloud SQL من خلال خادم وكيل، بالطريقة نفسها التي كنت تستخدمها.

إضافة أذونات برنامج SQL إلى حساب خدمة AppEngine Flex

انتقل إلى صفحة مشرف الهوية وإمكانية الوصول (IAM) في Cloud Console وابحث عن حساب خدمة يتطابق اسمه مع التنسيق service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com. هذا هو حساب الخدمة App Engine Flex الذي سيستخدم للاتصال بقاعدة البيانات. انقر على الزر تعديل في نهاية الصف وأضِف الدور &quot؛عميل Cloud SQL.

b04ccc0b4022b905.png

نسخ رمز المشروع إلى مسار 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 لتفعيل تطبيقك. سيستغرق نشر التطبيق بعض الوقت.

gcloud app deploy

استخدِم الأمر browse للحصول على رابط يمكنك النقر عليه للاطّلاع على كيفية عمل محدِّد مواقع المتاجر الذي يضم تطبيقات الأنشطة على مستوى المؤسسة والرائعة من الناحية الجمالية.

gcloud app browse

في حال تشغيل gcloud خارج هيكل السحابة الإلكترونية، سيؤدي تشغيل gcloud app browse إلى فتح علامة تبويب جديدة في المتصفّح.

12- (مستحسن) التنظيف

سيظل تنفيذ هذا الدرس التطبيقي داخل حدود الفئة المجانية لعمليات معالجة BigQuery وطلبات واجهة برمجة التطبيقات في النظام الأساسي للخرائط، ولكن إذا أجريت ذلك فقط في شكل تمرين تعليمي وتريد تجنب تكبد أي رسوم مستقبلية، فإن أسهل طريقة لحذف الموارد المرتبطة بهذا المشروع هي حذف المشروع نفسه.

حذف المشروع

في وحدة تحكم Google Cloud Platform، انتقِل إلى صفحة Cloud Resource Manager:

في قائمة المشاريع، اختَر المشروع الذي كنا نعمل عليه وانقر على حذف. سيُطلب منك كتابة رقم تعريف المشروع. أدخِله وانقر على إيقاف التشغيل.

بدلاً من ذلك، يمكنك حذف المشروع بأكمله مباشرةً من Cloud Shell على gcloud عن طريق تشغيل الأمر التالي واستبدال العنصر النائب GOOGLE_CLOUD_PROJECT برقم تعريف المشروع:

gcloud projects delete GOOGLE_CLOUD_PROJECT

13- تهانينا

تهانينا. لقد أكملت الدرس التطبيقي حول الترميز بنجاح.

أو انتقل إلى الصفحة الأخيرة. تهانينا. لقد انتقلت إلى الصفحة الأخيرة!

على مدار هذا الدرس التطبيقي حول الترميز، عملت على التقنيات التالية:

قراءات إضافية

ولا يزال هناك الكثير من المعلومات لمعرفة المزيد عن جميع هذه التقنيات. في ما يلي بعض الروابط المفيدة للمواضيع التي لم يكن لدينا الوقت الكافي تناولها في هذا الدرس التطبيقي حول الترميز، ولكنها قد تكون مفيدة لك بالتأكيد في إنشاء حل لمحدِّد مواقع المتاجر يناسب احتياجاتك المحددة.