Google মানচিত্র প্ল্যাটফর্ম এবং Google ক্লাউড সহ একটি সম্পূর্ণ স্ট্যাক স্টোর লোকেটার তৈরি করুন৷

1। পরিচিতি

বিমূর্ত

কল্পনা করুন যে আপনার কাছে একটি মানচিত্রে রাখার জন্য অনেক জায়গা আছে এবং আপনি চান যে ব্যবহারকারীরা এই জায়গাগুলি কোথায় তা দেখতে সক্ষম হন এবং তারা কোন জায়গাটি দেখতে চান তা চিহ্নিত করতে পারেন৷ এর সাধারণ উদাহরণগুলির মধ্যে রয়েছে:

  • একটি খুচরা বিক্রেতার ওয়েবসাইটে একটি দোকান লোকেটার
  • একটি আসন্ন নির্বাচনের জন্য পোলিং অবস্থানের মানচিত্র
  • ব্যাটারি রিসাইক্লিং রিসেপ্ট্যাকলের মতো বিশেষ অবস্থানের একটি ডিরেক্টরি

আপনি কি নির্মাণ করবেন

এই কোডল্যাবে, আপনি একটি লোকেটার তৈরি করবেন যা বিশেষায়িত অবস্থানের একটি লাইভ ডেটা ফিড থেকে আঁকে এবং ব্যবহারকারীকে তাদের সূচনা পয়েন্টের কাছাকাছি অবস্থান খুঁজে পেতে সহায়তা করে। এই পূর্ণ-স্ট্যাক লোকেটারটি সাধারণ স্টোর লোকেটারের চেয়ে অনেক বড় সংখ্যক স্থান পরিচালনা করতে পারে, যা 25 বা তার কম স্টোর অবস্থানের মধ্যে সীমাবদ্ধ।

2ece59c64c06e9da.png

আপনি কি শিখবেন

এই কোডল্যাবটি প্রচুর সংখ্যক স্টোর অবস্থান সম্পর্কে প্রাক-জনসংখ্যাযুক্ত মেটাডেটা অনুকরণ করতে একটি খোলা ডেটা সেট ব্যবহার করে যাতে আপনি মূল প্রযুক্তিগত ধারণাগুলি শেখার উপর ফোকাস করতে পারেন।

  • মানচিত্র জাভাস্ক্রিপ্ট API: একটি কাস্টমাইজ করা ওয়েব মানচিত্রে প্রচুর সংখ্যক অবস্থান প্রদর্শন করুন
  • GeoJSON: একটি বিন্যাস যা অবস্থান সম্পর্কে মেটাডেটা সংরক্ষণ করে
  • স্থান স্বয়ংসম্পূর্ণ: ব্যবহারকারীদের দ্রুত এবং আরও সঠিকভাবে শুরুর অবস্থানগুলি প্রদান করতে সহায়তা করুন৷
  • যান: ব্যাক-এন্ড অ্যাপ্লিকেশন বিকাশ করতে ব্যবহৃত প্রোগ্রামিং ভাষা। ব্যাকএন্ড ডাটাবেসের সাথে ইন্টারঅ্যাক্ট করবে এবং ফরম্যাট JSON-এ ফ্রন্ট-এন্ডে কোয়েরির ফলাফল পাঠাবে।
  • অ্যাপ ইঞ্জিন: ওয়েব অ্যাপ হোস্ট করার জন্য

পূর্বশর্ত

  • HTML এবং JavaScript এর প্রাথমিক জ্ঞান
  • একটি Google অ্যাকাউন্ট

2. সেট আপ করুন

নিম্নলিখিত বিভাগের ধাপ 3-এ, এই কোডল্যাবের জন্য Maps JavaScript API , Places API , এবং Distance Matrix API সক্ষম করুন৷

Google Maps প্ল্যাটফর্ম দিয়ে শুরু করুন

আপনি যদি আগে Google মানচিত্র প্ল্যাটফর্ম ব্যবহার না করে থাকেন তবে Google মানচিত্র প্ল্যাটফর্মের সাথে শুরু করুন নির্দেশিকা অনুসরণ করুন বা নিম্নলিখিত পদক্ষেপগুলি সম্পূর্ণ করতে Google মানচিত্র প্ল্যাটফর্ম প্লেলিস্টের সাথে শুরু করুন:

  1. একটি বিলিং অ্যাকাউন্ট তৈরি করুন।
  2. একটি প্রকল্প তৈরি করুন।
  3. Google মানচিত্র প্ল্যাটফর্ম API এবং SDK সক্ষম করুন (আগের বিভাগে তালিকাভুক্ত)৷
  4. একটি API কী তৈরি করুন।

ক্লাউড শেল সক্রিয় করুন

এই কোডল্যাবে আপনি ক্লাউড শেল ব্যবহার করেন, Google ক্লাউডে চলমান একটি কমান্ড-লাইন পরিবেশ যা Google ক্লাউডে চলমান পণ্য এবং সংস্থানগুলিতে অ্যাক্সেস প্রদান করে, যাতে আপনি আপনার ওয়েব ব্রাউজার থেকে সম্পূর্ণরূপে আপনার প্রকল্প হোস্ট এবং চালাতে পারেন৷

ক্লাউড কনসোল থেকে ক্লাউড শেল সক্রিয় করতে, ক্লাউড শেল সক্রিয় করুন ক্লিক করুন 89665d8d348105cd.png (পরিবেশের সাথে সংযোগ স্থাপন এবং সংযোগের জন্য এটি শুধুমাত্র কয়েক মুহূর্ত নিতে হবে)।

5f504766b9b3be17.png

এটি সম্ভবত একটি প্রাথমিক ইন্টারস্টিশিয়াল দেখানোর পরে আপনার ব্রাউজারের নীচের অংশে একটি নতুন শেল খোলে৷

d3bb67d514893d1f.png

আপনার প্রকল্প নিশ্চিত করুন

একবার ক্লাউড শেলের সাথে সংযুক্ত হয়ে গেলে, আপনি দেখতে পাবেন যে আপনি ইতিমধ্যেই প্রমাণীকরণ করেছেন এবং সেটআপের সময় আপনি যে প্রজেক্ট আইডিটি বেছে নিয়েছেন সেটি ইতিমধ্যেই সেট করা আছে।

$ 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 কে ক্লাউড কনসোল থেকে ম্যানুয়ালি সক্ষম করতে হবে। এটি করা শুধুমাত্র API-কে সক্ষম করবে না বরং AppEngine নমনীয় পরিবেশ পরিষেবা অ্যাকাউন্ট তৈরি করবে, একটি প্রমাণীকৃত অ্যাকাউন্ট যা ব্যবহারকারীর পক্ষে Google পরিষেবাগুলির (যেমন SQL ডেটাবেস) সাথে ইন্টারঅ্যাক্ট করবে৷

3. হ্যালো, বিশ্ব

ব্যাকএন্ড: হ্যালো ওয়ার্ল্ড ইন গো

আপনার ক্লাউড শেল ইনস্ট্যান্সে, আপনি একটি গো অ্যাপ ইঞ্জিন ফ্লেক্স অ্যাপ তৈরি করে শুরু করবেন যা কোডল্যাবের বাকি অংশের ভিত্তি হিসেবে কাজ করবে।

ক্লাউড শেলের টুলবারে, একটি নতুন ট্যাবে একটি কোড এডিটর খুলতে ওপেন এডিটর বোতামে ক্লিক করুন। এই ওয়েব ভিত্তিক কোড এডিটর আপনাকে ক্লাউড শেল ইনস্ট্যান্সে সহজেই ফাইল সম্পাদনা করতে দেয়।

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

এই কনফিগারেশন ফাইলটি Go Flex রানটাইম ব্যবহার করার জন্য আপনার অ্যাপ ইঞ্জিন অ্যাপটিকে কনফিগার করে। এই ফাইলের কনফিগারেশন আইটেমগুলির অর্থ সম্পর্কে ব্যাকগ্রাউন্ড তথ্যের জন্য, Google অ্যাপ ইঞ্জিন গো স্ট্যান্ডার্ড এনভায়রনমেন্ট ডকুমেন্টেশন দেখুন।

এরপর, app.yaml ফাইলের পাশাপাশি একটি main.go ফাইল তৈরি করুন:

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 সংজ্ঞায়িত করেছেন যা পোর্ট 8080-এ একটি HTTP সার্ভার শোনার শুরু করে এবং "/" পাথের সাথে মিলে যাওয়া HTTP অনুরোধগুলির জন্য একটি হ্যান্ডলার ফাংশন নিবন্ধন করে।

handler ফাংশন, যাকে হাতের নাগালে বলা হয়, টেক্সট স্ট্রিং লিখে দেয় "Hello, world!" . এই পাঠ্যটি আপনার ব্রাউজারে রিলে করা হবে, যেখানে আপনি এটি পড়তে সক্ষম হবেন৷ ভবিষ্যতের ধাপে আপনি এমন হ্যান্ডলার তৈরি করবেন যেগুলি সাধারণ হার্ড কোডেড স্ট্রিংয়ের পরিবর্তে জিওজেএসএন ডেটার সাথে সাড়া দেয়।

এই পদক্ষেপগুলি সম্পাদন করার পরে, আপনার এখন এমন একটি সম্পাদক থাকা উচিত যা দেখতে এইরকম:

2084fdd5ef594ece.png

এটা পরীক্ষা করে দেখুন

এই অ্যাপ্লিকেশনটি পরীক্ষা করতে, আপনি ক্লাউড শেল ইন্সট্যান্সের মধ্যে অ্যাপ ইঞ্জিন ডেভেলপমেন্ট সার্ভার চালাতে পারেন। ক্লাউড শেল কমান্ড লাইনে ফিরে যান এবং নিম্নলিখিতটি টাইপ করুন:

go run *.go

আপনি লগ আউটপুটের কিছু লাইন দেখতে পাবেন যা আপনাকে দেখায় যে আপনি সত্যিই ক্লাউড শেল ইনস্ট্যান্সে ডেভেলপমেন্ট সার্ভার চালাচ্ছেন, হ্যালো ওয়ার্ল্ড ওয়েব অ্যাপ লোকালহোস্ট পোর্ট 8080-এ শুনছে। আপনি ওয়েব টিপে এই অ্যাপে একটি ওয়েব ব্রাউজার ট্যাব খুলতে পারেন। পূর্বরূপ বোতাম এবং ক্লাউড শেল টুলবারে পোর্ট 8080 মেনু আইটেমের পূর্বরূপ নির্বাচন করা।

4155fc1dc717ac67.png

এই মেনু আইটেমটিতে ক্লিক করলে আপনার ওয়েব ব্রাউজারে একটি নতুন ট্যাব খুলবে যেখানে "হ্যালো, বিশ্ব!" অ্যাপ ইঞ্জিন ডেভেলপমেন্ট সার্ভার থেকে পরিবেশিত।

পরবর্তী ধাপে আপনি এই অ্যাপে সিটি অফ অস্টিন রিসাইক্লিং ডেটা যোগ করবেন এবং এটিকে কল্পনা করা শুরু করবেন।

4. বর্তমান তথ্য পান

GeoJSON, GIS বিশ্বের ভাষা ফ্রাঙ্কা

পূর্ববর্তী ধাপে উল্লেখ করা হয়েছে যে আপনি আপনার Go কোডে হ্যান্ডলার তৈরি করবেন যা ওয়েব ব্রাউজারে GeoJSON ডেটা রেন্ডার করবে। কিন্তু GeoJSON কি?

জিওগ্রাফিক ইনফরমেশন সিস্টেম (জিআইএস) বিশ্বে, আমাদের কম্পিউটার সিস্টেমের মধ্যে ভৌগলিক সত্তা সম্পর্কে জ্ঞান যোগাযোগ করতে সক্ষম হতে হবে। মানচিত্র মানুষের পড়ার জন্য দুর্দান্ত, তবে কম্পিউটারগুলি সাধারণত তাদের ডেটা আরও সহজে হজম করা ফর্ম্যাটে পছন্দ করে।

GeoJSON হল ভৌগলিক ডেটা স্ট্রাকচার এনকোড করার একটি ফর্ম্যাট, যেমন অস্টিন, টেক্সাসে ড্রপ-অফ অবস্থানগুলির পুনর্ব্যবহারযোগ্য স্থানাঙ্কগুলি৷ GeoJSON কে RFC7946 নামে একটি ইন্টারনেট ইঞ্জিনিয়ারিং টাস্ক ফোর্স স্ট্যান্ডার্ডে প্রমিত করা হয়েছে। GeoJSON-কে JSON , JavaScript অবজেক্ট নোটেশনের পরিপ্রেক্ষিতে সংজ্ঞায়িত করা হয়েছে, যেটি নিজেই ECMA-404- এ প্রমিত ছিল, একই সংস্থা যেটি JavaScript, Ecma ইন্টারন্যাশনালকে প্রমিত করেছে।

গুরুত্বপূর্ণ বিষয় হল GeoJSON হল ভৌগলিক জ্ঞান যোগাযোগের জন্য একটি ব্যাপকভাবে সমর্থিত তারের বিন্যাস। এই কোডল্যাব নিম্নলিখিত উপায়ে GeoJSON ব্যবহার করে:

  • অস্টিন ডেটাকে একটি অভ্যন্তরীণ GIS নির্দিষ্ট ডেটা কাঠামোতে পার্স করতে Go প্যাকেজগুলি ব্যবহার করুন যা আপনি অনুরোধ করা ডেটা ফিল্টার করতে ব্যবহার করবেন৷
  • ওয়েব সার্ভার এবং ওয়েব ব্রাউজারের মধ্যে ট্রানজিটের জন্য অনুরোধ করা ডেটা সিরিয়ালাইজ করুন।
  • ম্যাপে প্রতিক্রিয়াটিকে মার্কারগুলিতে রূপান্তর করতে একটি জাভাস্ক্রিপ্ট লাইব্রেরি ব্যবহার করুন৷

এটি আপনাকে কোডে টাইপ করার উল্লেখযোগ্য পরিমাণ সংরক্ষণ করবে, কারণ অন-দ্য-ওয়্যার ডেটাস্ট্রিমকে ইন-মেমরি উপস্থাপনায় রূপান্তর করতে আপনাকে পার্সার এবং জেনারেটর লিখতে হবে না।

ডেটা পুনরুদ্ধার করুন

অস্টিন সিটি, টেক্সাস ওপেন ডেটা পোর্টাল জনসাধারণের ব্যবহারের জন্য জনসাধারণের সম্পদ সম্পর্কে ভূ-স্থানিক তথ্য উপলব্ধ করে। এই কোডল্যাবে, আপনি রিসাইক্লিং ড্রপ-অফ অবস্থানের ডেটা সেট কল্পনা করবেন।

ম্যাপ জাভাস্ক্রিপ্ট API-এর ডেটা লেয়ার ব্যবহার করে রেন্ডার করা ম্যাপে মার্কার দিয়ে ডেটা ভিজ্যুয়ালাইজ করবেন।

আপনার অ্যাপে সিটি অফ অস্টিন ওয়েবসাইট থেকে GeoJSON ডেটা ডাউনলোড করে শুরু করুন।

  1. আপনার ক্লাউড শেল ইনস্ট্যান্সের কমান্ড লাইন উইন্ডোতে, [CTRL] + [C] লিখে সার্ভারটি বন্ধ করুন।
  2. austin-recycling ডিরেক্টরির ভিতরে একটি data ডিরেক্টরি তৈরি করুন এবং সেই ডিরেক্টরিতে পরিবর্তন করুন:
mkdir -p data && cd data

এখন পুনর্ব্যবহারযোগ্য অবস্থানগুলি পুনরুদ্ধার করতে কার্ল ব্যবহার করুন:

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 এর জন্য অনুরোধগুলিকে স্ট্যাটিক ফাইলের সেটে নির্দেশ করে। এর মানে হল যে আপনার অ্যাপের স্ট্যাটিক এইচটিএমএল উপাদান সরাসরি অ্যাপ ইঞ্জিন ফাইল পরিবেশন পরিকাঠামো দ্বারা পরিবেশিত হবে, আপনার Go অ্যাপে নয়। এটি সার্ভারের লোড হ্রাস করে এবং পরিবেশন গতি বাড়ায়।

এখন গো-তে আপনার অ্যাপ্লিকেশনের ব্যাকএন্ড তৈরি করার সময়!

পিছনের প্রান্তটি তৈরি করুন

আপনি হয়তো লক্ষ্য করেছেন, একটি আকর্ষণীয় জিনিস আপনার app.yaml ফাইলটি করে না তা হল GeoJSON ফাইলটি প্রকাশ করা। এর কারণ হল GeoJSON প্রক্রিয়া করা হবে এবং আমাদের গো ব্যাকএন্ড দ্বারা পাঠানো হবে, আমাদের পরবর্তী ধাপে কিছু অভিনব বৈশিষ্ট্য তৈরি করতে দেয়। নিচের মত পড়তে আপনার 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"])
}

ইতিমধ্যেই গো ব্যাকএন্ড আমাদের একটি মূল্যবান বৈশিষ্ট্য দিচ্ছে: অ্যাপইঞ্জিন ইনস্ট্যান্স শুরু হওয়ার সাথে সাথে সেই সমস্ত অবস্থানগুলিকে ক্যাশ করছে। এটি সময় সাশ্রয় করে কারণ ব্যাকএন্ডকে প্রত্যেক ব্যবহারকারীর কাছ থেকে প্রতি রিফ্রেশে ডিস্ক থেকে ফাইলটি পড়তে হবে না!

সামনের প্রান্তটি তৈরি করুন

আমাদের যা করতে হবে তা হল আমাদের সমস্ত স্ট্যাটিক সম্পদগুলিকে ধরে রাখার জন্য একটি ফোল্ডার তৈরি করা। আপনার প্রকল্পের মূল ফোল্ডার থেকে, একটি 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>

head এলিমেন্টের স্ক্রিপ্ট ট্যাগে src URL-এ বিশেষ মনোযোগ দিন।

  • প্লেসহোল্ডার টেক্সট " YOUR_API_KEY " প্রতিস্থাপন করুন সেটআপ ধাপের সময় আপনার তৈরি করা API কী দিয়ে। আপনি আপনার API কী পুনরুদ্ধার করতে বা একটি নতুন তৈরি করতে ক্লাউড কনসোলে API এবং পরিষেবা -> শংসাপত্র পৃষ্ঠাতে যেতে পারেন৷
  • মনে রাখবেন যে URL-এ callback=initialize. আমরা এখন সেই কলব্যাক ফাংশন ধারণকারী জাভাস্ক্রিপ্ট ফাইল তৈরি করতে যাচ্ছি। এখানেই আপনার অ্যাপটি ব্যাকএন্ড থেকে অবস্থানগুলি লোড করবে, সেগুলিকে মানচিত্র 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 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 ফাংশন সংজ্ঞা প্রতিস্থাপন করুন, যা একটি অতিরিক্ত যুক্তি, infowindow সহ storeToCircle কল করার চূড়ান্ত লাইন পরিবর্তন করে:

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 --> " নিচের কোড দিয়ে। এই সম্পাদনা করার সময়, আমরা স্লাইড আউট প্যানেলের জন্য ডিভ যোগ করব।

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 মানচিত্রের উপরের-ডান কোণায় নিয়ে যায় এবং প্রতিক্রিয়াতে প্রতিটি স্থান সম্পর্কে কোন ক্ষেত্রগুলি ফেরত দেওয়া উচিত তা নির্দিষ্ট করে।

অবশেষে, initialize ফাংশনের শেষে initAutocompleteWidget ফাংশনটিকে কল করুন, " // TODO: Initialize the Autocomplete widget " লেখা মন্তব্যটি প্রতিস্থাপন করুন।

app.js - আরম্ভ করুন

 // Initialize the Places Autocomplete Widget
 initAutocompleteWidget();

নিম্নলিখিত কমান্ড চালিয়ে আপনার সার্ভার পুনরায় চালু করুন, তারপর আপনার পূর্বরূপ রিফ্রেশ করুন।

go run *.go

আপনি এখন আপনার মানচিত্রের উপরের-ডানদিকে একটি স্বয়ংসম্পূর্ণ উইজেট দেখতে পাবেন, যা আপনাকে দেখায় যে আপনি যা টাইপ করেন তার সাথে মিলে যাওয়া মার্কিন ঠিকানাগুলি, মানচিত্রের দৃশ্যমান এলাকার দিকে পক্ষপাতদুষ্ট।

58e9bbbcc4bf18d1.png

ব্যবহারকারী একটি প্রারম্ভিক ঠিকানা নির্বাচন করলে মানচিত্রটি আপডেট করুন

এখন, ব্যবহারকারী যখন স্বয়ংসম্পূর্ণ উইজেট থেকে একটি ভবিষ্যদ্বাণী নির্বাচন করে তখন আপনাকে পরিচালনা করতে হবে এবং আপনার স্টোরের দূরত্ব গণনা করার জন্য সেই অবস্থানটিকে ভিত্তি হিসাবে ব্যবহার করতে হবে।

initAutocompleteWidgetapp.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. ক্লাউড এসকিউএল দিয়ে স্কেল করুন

এখনও অবধি, আমাদের কাছে একটি দুর্দান্ত স্টোর লোকেটার রয়েছে। এটি ব্যাকএন্ডে মেমরিতে লোড করে (ফাইল থেকে বারবার পড়ার পরিবর্তে) অ্যাপটি ব্যবহার করবে এমন প্রায় একশটি অবস্থানের সুবিধা নেয়। কিন্তু যদি আপনার লোকেটারকে ভিন্ন স্কেলে কাজ করতে হয়? আপনার যদি একটি বৃহৎ ভৌগলিক এলাকা (অথবা সারা বিশ্বে হাজার হাজার) চারপাশে ছড়িয়ে ছিটিয়ে থাকা শতাধিক অবস্থান থাকে, তবে সেই সমস্ত অবস্থানগুলিকে মেমরিতে রাখা আর সেরা ধারণা নয়, এবং পৃথক ফাইলগুলিতে জোনগুলি ভেঙে দেওয়া তার নিজস্ব সমস্যাগুলি প্রবর্তন করতে চলেছে।

এটি একটি ডাটাবেস থেকে আপনার অবস্থান লোড করার সময়. এই পদক্ষেপের জন্য আমরা আপনার জিওজেএসএন ফাইলের সমস্ত অবস্থানগুলিকে একটি ক্লাউড এসকিউএল ডাটাবেসে স্থানান্তর করতে যাচ্ছি, এবং যখনই কোনও অনুরোধ আসে তখনই সেই ডাটাবেসের পরিবর্তে স্থানীয় ক্যাশে থেকে ফলাফলগুলি টেনে আনতে Go ব্যাকএন্ড আপডেট করব৷

পোস্টগ্রেস ডাটাবেসের সাথে একটি ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করুন

আপনি Google ক্লাউড কনসোলের মাধ্যমে একটি ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করতে পারেন, তবে কমান্ড লাইন থেকে একটি তৈরি করতে gcloud ইউটিলিটি ব্যবহার করা আরও সহজ। ক্লাউড শেলে, নিম্নলিখিত কমান্ডের সাহায্যে একটি ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করুন:

gcloud sql instances create locations \
--database-version=POSTGRES_12 \
--tier=db-custom-1-3840 --region=us-central1
  • ক্লাউড এসকিউএল-এর এই উদাহরণ দিতে আমরা যে নামটি বেছে নিয়েছি তা হল আর্গুমেন্ট locations
  • tier পতাকা হল কিছু সুবিধাজনকভাবে পূর্ব-সংজ্ঞায়িত মেশিন থেকে নির্বাচন করার একটি উপায়।
  • মান db-custom-1-3840 নির্দেশ করে যে উদাহরণ তৈরি করা হচ্ছে তার একটি vCPU এবং প্রায় 3.75GB মেমরি থাকা উচিত।

ক্লাউড এসকিউএল ইনস্ট্যান্স তৈরি করা হবে এবং একটি PostGresSQL ডাটাবেস দিয়ে শুরু করা হবে, ডিফল্ট ব্যবহারকারী postgres সহ। এই ব্যবহারকারীর পাসওয়ার্ড কি? দারুণ প্রশ্ন! তাদের একটা নেই। আপনি লগ ইন করার আগে আপনাকে একটি কনফিগার করতে হবে৷

নিম্নলিখিত কমান্ড দিয়ে পাসওয়ার্ড সেট করুন:

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

তারপর আপনার নির্বাচিত পাসওয়ার্ড লিখুন যখন এটি করতে বলা হবে।

পোস্টজিআইএস এক্সটেনশন সক্ষম করুন

PostGIS হল PostGresSQL-এর জন্য একটি এক্সটেনশন যা মানসম্মত ধরনের ভূ-স্থানিক ডেটা সংরক্ষণ করা সহজ করে তোলে। সাধারণ পরিস্থিতিতে আমাদের ডাটাবেসে PostGIS যোগ করার জন্য আমাদের একটি সম্পূর্ণ ইনস্টলেশন প্রক্রিয়ার মধ্য দিয়ে যেতে হবে। সৌভাগ্যবশত, এটি PostGresSQL-এর জন্য ক্লাউড এসকিউএল-এর সমর্থিত এক্সটেনশনগুলির মধ্যে একটি

ক্লাউড শেল টার্মিনালে নিম্নলিখিত কমান্ডের সাহায্যে ব্যবহারকারী postgres করার সাথে সাথে লগ ইন করে ডাটাবেস ইনস্ট্যান্সের সাথে সংযোগ করুন।

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

আপনার তৈরি পাসওয়ার্ড লিখুন. এখন postgres=> কমান্ড প্রম্পটে PostGIS এক্সটেনশন যোগ করুন।

CREATE EXTENSION postgis;

সফল হলে, আউটপুটটি ক্রিয়েট এক্সটেনশন পড়তে হবে, যেমনটি নীচে দেখানো হয়েছে।

কমান্ড আউটপুট উদাহরণ

CREATE EXTENSION

অবশেষে, postgres=> কমান্ড প্রম্পটে quit কমান্ড প্রবেশ করে ডাটাবেস সংযোগ প্রস্থান করুন।

\q

ডাটাবেসে ভৌগলিক ডেটা আমদানি করুন

এখন আমাদের নতুন ডাটাবেসে GeoJSON ফাইলগুলি থেকে সেই সমস্ত অবস্থানের ডেটা আমদানি করতে হবে।

সৌভাগ্যবশত, এটি একটি ভাল ভ্রমণের সমস্যা এবং আপনার জন্য এটি স্বয়ংক্রিয় করার জন্য ইন্টারনেটে বেশ কয়েকটি সরঞ্জাম পাওয়া যেতে পারে। আমরা ogr2ogr নামক একটি টুল ব্যবহার করতে যাচ্ছি যা ভূ-স্থানিক ডেটা সংরক্ষণের জন্য একাধিক সাধারণ ফর্ম্যাটের মধ্যে রূপান্তরিত করে। এই বিকল্পগুলির মধ্যে, হ্যাঁ, আপনি এটি অনুমান করেছেন, একটি SQL ডাম্প ফাইলে GeoJSON রূপান্তর করা। SQL ডাম্প ফাইলটি ডাটাবেসের জন্য আপনার টেবিল এবং কলাম তৈরি করতে ব্যবহার করা যেতে পারে এবং আপনার জিওজেএসএন ফাইলে বিদ্যমান সমস্ত ডেটা দিয়ে এটি লোড করতে পারে।

এসকিউএল ডাম্প ফাইল তৈরি করুন

প্রথমে ogr2ogr ইন্সটল করুন।

sudo apt-get install gdal-bin

এরপর, SQL ডাম্প ফাইল তৈরি করতে ogr2ogr ব্যবহার করুন। এই ফাইলটি austinrecycling নামে একটি টেবিল তৈরি করবে।

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

উপরের কমান্ডটি austin-recycling ফোল্ডার থেকে চালানোর উপর ভিত্তি করে। আপনার যদি অন্য ডিরেক্টরি থেকে এটি চালানোর প্রয়োজন হয়, যেখানে recycling-locations.geojson সংরক্ষণ করা হয় সেই ডিরেক্টরির পাথ দিয়ে data প্রতিস্থাপন করুন।

পুনর্ব্যবহারযোগ্য অবস্থানের সাথে আপনার ডাটাবেস পপুলেট করুন

সেই শেষ কমান্ডটি সম্পন্ন করার পরে, আপনার এখন একটি ফাইল থাকা উচিত, datadump.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

ক্লাউড এসকিউএল ব্যবহার করতে গো ব্যাক এন্ড আপডেট করুন

এখন যেহেতু আমাদের ডাটাবেসে এই সমস্ত ডেটা রয়েছে, এটি আমাদের কোড আপডেট করার সময়।

অবস্থানের তথ্য পাঠাতে ফ্রন্ট এন্ড আপডেট করুন

ফ্রন্ট-এন্ডে একটি খুব ছোট আপডেট দিয়ে শুরু করা যাক: কারণ আমরা এখন এই অ্যাপটি এমন একটি স্কেলের জন্য লিখছি যেখানে আমরা চাই না যে প্রতিবার ক্যোয়ারী চালানোর সময় প্রত্যেকটি অবস্থান ফ্রন্ট-এন্ডে পৌঁছে দেওয়া হোক, আমাদের প্রয়োজন ব্যবহারকারী যে অবস্থান সম্পর্কে যত্নশীল সে সম্পর্কে সামনের প্রান্ত থেকে কিছু প্রাথমিক তথ্য পাস করুন।

URL-এ আগ্রহের অক্ষাংশ এবং দ্রাঘিমাংশ অন্তর্ভুক্ত করতে fetchStores app.js সংজ্ঞা প্রতিস্থাপন করুন।

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. initAutocompleteWidget-এর মধ্যে, place_changed শ্রোতার জন্য কলব্যাক খুঁজুন। যে লাইনটি বিদ্যমান চেনাশোনাগুলিকে সাফ করে তা আন-মন্তব্য করুন, যাতে প্রতিটি ব্যবহারকারী প্লেস স্বয়ংসম্পূর্ণ অনুসন্ধান abr থেকে একটি ঠিকানা নির্বাচন করার সময় সেই লাইনটি এখন চলবে৷

app.js - initAutocompleteWidget

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

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

একটি ফ্ল্যাট JSON ফাইলের পরিবর্তে CloudSQL ব্যবহার করতে পিছনের প্রান্তটি আপডেট করুন৷

ফ্ল্যাট-ফাইল GeoJSON রিডিং এবং ক্যাশিং সরান

প্রথমে, ফ্ল্যাট GeoJSON ফাইলটি লোড এবং ক্যাশে করা কোডটি সরাতে main.go পরিবর্তন করুন। আমরা dropoffsHandler ফাংশন থেকেও মুক্তি পেতে পারি, কারণ আমরা ক্লাউড এসকিউএল দ্বারা চালিত একটি ভিন্ন ফাইলে লিখব।

আপনার নতুন 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 স্ট্রিং প্রদান করে (আমরা এটি পরে লিখব।)
  • এটি প্রতিক্রিয়ায় সেই GeoJSON স্ট্রিংটি মুদ্রণ করতে ResponseWriter ব্যবহার করে।

পরবর্তীতে আমরা একযোগে ব্যবহারকারীদের সাথে ডাটাবেসের ব্যবহারকে ভালোভাবে মাপতে সাহায্য করার জন্য একটি সংযোগ পুল তৈরি করতে যাচ্ছি।

একটি সংযোগ পুল তৈরি করুন

একটি সংযোগ পুল হল সক্রিয় ডাটাবেস সংযোগের একটি সংগ্রহ যা সার্ভার ব্যবহারকারীর অনুরোধের জন্য পুনরায় ব্যবহার করতে পারে। আপনার সক্রিয় ব্যবহারকারীর সংখ্যা বাড়ার সাথে সাথে এটি অনেকগুলি ওভারহেড সরিয়ে দেয়, যেহেতু সার্ভারকে প্রতিটি সক্রিয় ব্যবহারকারীর জন্য সংযোগ তৈরি এবং ধ্বংস করতে সময় ব্যয় করতে হবে না। আপনি আগের বিভাগে লক্ষ্য করেছেন যে আমরা লাইব্রেরি github.com/jackc/pgx/stdlib. গো-তে সংযোগ পুলের সাথে কাজ করার জন্য এটি একটি জনপ্রিয় লাইব্রেরি।

locations.go এর শেষে, একটি ফাংশন initConnectionPool ( main.go থেকে বলা হয়) তৈরি করুন যা একটি সংযোগ পুল শুরু করে। স্পষ্টতার জন্য, এই স্নিপেটে কয়েকটি সহায়ক পদ্ধতি ব্যবহার করা হয়েছে। configureConnectionPool সংযোগের সংখ্যা এবং প্রতি সংযোগের জীবনকালের মতো পুল সেটিংস সামঞ্জস্য করার জন্য একটি সহজ স্থান প্রদান করে। mustGetEnv প্রয়োজনীয় এনভায়রনমেন্ট ভেরিয়েবল পেতে কলগুলিকে র‍্যাপ করে, তাই ইন্সট্যান্সে গুরুত্বপূর্ণ তথ্য (যেমন আইপি বা ডাটাবেসের সাথে সংযোগ করার জন্য নাম) অনুপস্থিত থাকলে দরকারী ত্রুটির বার্তাগুলি নিক্ষেপ করা যেতে পারে।

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টি অবস্থান প্রদান করে। শুধু তাই নয়, কিছু অভিনব আধুনিক ডাটাবেস কার্যকারিতার জন্য ধন্যবাদ এটি সেই ডেটাটিকে জিওজেএসএন হিসাবে ফিরিয়ে দেবে। এই সবের শেষ ফলাফল হল যে যতদূর ফ্রন্ট-এন্ড কোড বলতে পারে, কিছুই পরিবর্তন হয়নি। এটি একটি URL-এ একটি অনুরোধ বন্ধ করার আগে এবং জিওজেএসএন-এর একটি গুচ্ছ পেয়েছিল৷ এখন এটি একটি URL-এ একটি অনুরোধ বন্ধ করে দেয় এবং... জিওজেএসএন-এর একটি গুচ্ছ ফিরে পায়৷

এখানে যে জাদু সঞ্চালন ফাংশন. হ্যান্ডলার এবং সংযোগ পুলিং কোডের পরে নিম্নলিখিত ফাংশনটি যুক্ত করুন আপনি এইমাত্র 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
}

এই ফাংশনটি বেশিরভাগই শুধু সেটআপ, টিয়ারডাউন, এবং ডাটাবেসের একটি অনুরোধ ফায়ার করার জন্য ত্রুটি পরিচালনা করে। আসুন প্রকৃত এসকিউএল-এর দিকে তাকাই, যা ডাটাবেস স্তরে সত্যিই অনেক আকর্ষণীয় জিনিস করছে, তাই আপনাকে কোডে সেগুলির কোনওটি বাস্তবায়নের বিষয়ে চিন্তা করতে হবে না।

স্ট্রিং পার্স করা হয়ে গেলে এবং সমস্ত স্ট্রিং লিটারেল তাদের সঠিক জায়গায় ঢোকানো হয়ে গেলে যে কাঁচা ক্যোয়ারীটি বন্ধ হয়ে যায়, তা এইরকম দেখায়:

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 ফাংশন (পোস্টজিআইএস-এর ভূগোল পরিমাপ ফাংশনের স্যুটের অংশ) ব্যবহার করে ডাটাবেসের প্রতিটি অবস্থান এবং ব্যবহারকারীর সামনের প্রান্তে প্রদত্ত অবস্থানের ল্যাট/লং জোড়ার মধ্যে দূরত্ব নির্ধারণ করতে। মনে রাখবেন যে দূরত্ব ম্যাট্রিক্সের বিপরীতে, যা আপনাকে ড্রাইভিং দূরত্ব দিতে পারে, এইগুলি হল জিওস্পেশিয়াল দূরত্ব। দক্ষতার জন্য এটি তারপর সেই দূরত্বটি সাজানোর জন্য ব্যবহার করে এবং 25টি নিকটতম অবস্থানগুলিকে ব্যবহারকারীর নির্দিষ্ট স্থানে ফিরিয়ে দেয়।

** SELECT json_build_object('type', 'F **eature') পূর্ববর্তী ক্যোয়ারী মোড়ানো, ফলাফল গ্রহণ করে এবং একটি GeoJSON বৈশিষ্ট্য অবজেক্ট তৈরি করতে ব্যবহার করে। অপ্রত্যাশিতভাবে, এই ক্যোয়ারীটিও যেখানে সর্বোচ্চ ব্যাসার্ধ প্রয়োগ করা হয় "16090" হল 10 মাইলে মিটারের সংখ্যা, গো ব্যাকএন্ড দ্বারা নির্দিষ্ট করা কঠিন সীমা৷ আপনি যদি ভাবছেন কেন এই WHERE ক্লজটি অভ্যন্তরীণ ক্যোয়ারীতে (যেখানে প্রতিটি অবস্থানের দূরত্ব নির্ধারণ করা হয়) যোগ করা হয়নি, তবে এর কারণ হল পর্দার পিছনে SQL যেভাবে কার্যকর করে, সেই ক্ষেত্রটি গণনা করা হয়নি যখন WHERE ক্লজ পরীক্ষা করা হয়েছিল। আসলে আপনি যদি এই WHERE ক্লজটিকে ভিতরের ক্যোয়ারীতে সরানোর চেষ্টা করেন তবে এটি একটি ত্রুটি নিক্ষেপ করবে।

** SELECT json_build_object('type', 'FeatureColl ** ection') SELECT করুন

আপনার প্রকল্পে PGX লাইব্রেরি যোগ করুন

আমাদের আপনার প্রকল্পে একটি নির্ভরতা যোগ করতে হবে: পোস্টগ্রেস ড্রাইভার এবং টুলকিট , যা সংযোগ পুলিং সক্ষম করে। এটি করার সবচেয়ে সহজ উপায় হল Go মডিউল দিয়ে। ক্লাউড শেলে এই কমান্ড দিয়ে একটি মডিউল শুরু করুন:

go mod init my_locator

এরপরে, নির্ভরতার জন্য কোড স্ক্যান করতে এই কমান্ডটি চালান, মোড ফাইলে নির্ভরতাগুলির একটি তালিকা যোগ করুন এবং সেগুলি ডাউনলোড করুন।

go mod tidy

অবশেষে, আপনার প্রজেক্ট ডিরেক্টরিতে সরাসরি নির্ভরতা টানতে এই কমান্ডটি চালান যাতে AppEngine Flex-এর জন্য ধারকটি সহজেই তৈরি করা যায়।

go mod vendor

ঠিক আছে, আপনি এটি পরীক্ষা করার জন্য প্রস্তুত!

এটা পরীক্ষা করে দেখুন

ঠিক আছে, আমরা অনেক কাজ করেছি। এর কাজ দেখা যাক!

আপনার ডেভেলপমেন্ট মেশিন (হ্যাঁ, এমনকি ক্লাউড শেল) ডাটাবেসের সাথে সংযোগ করার জন্য, ডাটাবেস সংযোগ পরিচালনা করতে আমাদের ক্লাউড এসকিউএল প্রক্সি ব্যবহার করতে হবে। ক্লাউড এসকিউএল প্রক্সি সেট আপ করতে:

  1. Cloud SQL Admin API সক্ষম করতে এখানে যান
  2. আপনি যদি স্থানীয় ডেভেলপমেন্ট মেশিনে থাকেন, তাহলে ক্লাউড এসকিউএল প্রক্সি টুল ইনস্টল করুন। আপনি যদি ক্লাউড শেল ব্যবহার করেন তবে আপনি এই ধাপটি এড়িয়ে যেতে পারেন, এটি ইতিমধ্যেই ইনস্টল করা আছে! নোট করুন যে নির্দেশাবলী একটি পরিষেবা অ্যাকাউন্ট উল্লেখ করবে। আপনার জন্য ইতিমধ্যেই একটি তৈরি করা হয়েছে, এবং আমরা নিম্নলিখিত বিভাগে সেই অ্যাকাউন্টে প্রয়োজনীয় অনুমতিগুলি যুক্ত করব৷
  3. প্রক্সি শুরু করতে একটি নতুন ট্যাব তৈরি করুন (ক্লাউড শেল বা আপনার নিজস্ব টার্মিনালে)।

bcca42933bfbd497.png

  1. https://console.cloud.google.com/sql/instances/locations/overview এ যান এবং সংযোগ নামের ক্ষেত্রটি খুঁজতে নিচে স্ক্রোল করুন। পরবর্তী কমান্ডে ব্যবহার করার জন্য সেই নামটি অনুলিপি করুন।
  2. সেই ট্যাবে, পূর্ববর্তী ধাপে দেখানো সংযোগের নাম দিয়ে CONNECTION_NAME প্রতিস্থাপন করে এই কমান্ডের সাহায্যে Cloud SQL প্রক্সি চালান।
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. নিকটতম দোকানের তালিকা করুন

দিকনির্দেশ এপিআই অনেকটা Google মানচিত্র অ্যাপে দিকনির্দেশের অনুরোধ করার অভিজ্ঞতার মতো কাজ করে—দুটির মধ্যে একটি রুট পেতে একটি একক উত্স এবং একটি গন্তব্যে প্রবেশ করা। দূরত্ব ম্যাট্রিক্স এপিআই ভ্রমণের সময় এবং দূরত্বের উপর ভিত্তি করে একাধিক সম্ভাব্য উত্স এবং একাধিক সম্ভাব্য গন্তব্যগুলির মধ্যে সর্বোত্তম জোড়া সনাক্ত করার জন্য এই ধারণাটিকে আরও এগিয়ে নিয়ে যায়। এই ক্ষেত্রে, ব্যবহারকারীকে নির্বাচিত ঠিকানার নিকটতম দোকান খুঁজে পেতে সাহায্য করার জন্য, আপনি গন্তব্য হিসাবে একটি উত্স এবং দোকান অবস্থানগুলির একটি অ্যারে প্রদান করেন৷

প্রতিটি দোকান থেকে দূরত্ব যোগ করুন

At the beginning of the initMap function definition, replace the comment " // TODO: Start Distance Matrix service " with the following code:

app.js - initMap

distanceMatrixService = new google.maps.DistanceMatrixService();

Add a new function to the end of app.js called 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);
  });
};

The function calls the Distance Matrix API using the origin passed to it as a single origin and the store locations as an array of destinations. Then, it builds an array of objects storing the store's ID, distance expressed in a human-readable string, distance in meters as a numerical value, and sorts the array.

Update the initAutocompleteWidget function to calculate the store distances whenever a new origin is selected from the Place Autocomplete search bar. At the bottom of the initAutocompleteWidget function, replace the comment " // TODO: Calculate the closest stores " with the following code:

app.js - initAutocompleteWidget

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

Display a list view of stores sorted by distance

The user expects to see a list of the stores ordered from nearest to farthest. Populate a side-panel listing for each store using the list that was modified by the calculateDistances function to inform the display order of the stores.

Add a two new functions to the end of app.js called renderStoresPanel() and 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;
};

Restart your server and refresh your preview by running the following command.

go run *.go

Finally, enter an Austin, TX address into the Autocomplete search bar and click on one of the suggestions.

The map should center on that address and a sidebar should appear listing the store locations in order of distance from the selected address. One example is pictured as follows:

96e35794dd0e88c9.png

10. Style the map

One high-impact way to set your map apart visually is to add styling to it. With cloud-based map styling, the customization of your maps is controlled from the Cloud Console using Cloud-based Map Styling (beta). If you'd rather style your map with a non-beta feature, you can use the map styling documentation to help you generate json for programmatically styling the map. The instructions below guide you through Cloud-based Map Styling (beta).

Create a Map ID

First, open up Cloud Console and in the search box, and type in "Map Management" . Click the result that says "Map Management (Google Maps)". 64036dd0ed200200.png

You'll see a button near the top (right under the Search box) that says Create New Map ID . Click that, and fill in whatever name you want. For Map Type, be sure to select JavaScript , and when further options show up, select Vector from the list. The end result should look something like the image below.

70f55a759b4c4212.png

Click "Next" and you'll be graced with a brand new Map ID. You can copy it now if you want, but don't worry, it's easy to look up later.

Next we're going to create a style to apply to that map.

Create a Map Style

If you're still in the Maps section of the Cloud Console, click "Map Styles at the bottom of the navigation menu on the left. Otherwise, just like creating a Map ID, you can find the right page by typing "Map Styles" in the search box and selecting " Map Styles (Google Maps)" from the results, like in the picture below.

9284cd200f1a9223.png

Next click on the button near the top that says " + Create New Map Style "

  1. If you want to match the styling in the map shown in this lab, click the " IMPORT JSON " tab and paste the JSON blob below. Otherwise if you want to create your own, select the Map Style you want to start with. Then click Next .
  2. Select the Map ID you just created to associate that Map ID with this style, and click Next again.
  3. At this point you're given the option of further customizing the styling of your map. If this is something you want to explore, click Customize in Style Editor and play around with the colors & options until you have a map style you like. Otherwise click Skip .
  4. On the next step, enter your style's name and description, and then click Save And Publish .

Here is an optional JSON blob to import in the first step.

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

Add Map ID to your code

Now that you've gone through the trouble of creating this map style, how do you actually USE this map style in your own map? You need to make two small changes:

  1. Add the Map ID as a url parameter to the script tag in index.html
  2. Add the Map ID as a constructor argument when you create the map in your initMap() method.

Replace the script tag that loads the Maps JavaScript API in the HTML file with the loader URL below, replacing the placeholders for " YOUR_API_KEY " and " 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>
...

In the initMap method of app.js where the constant map is defined, uncomment the line for the mapId property and replace " YOUR_MAP_ID_HERE " with the Map ID you just created:

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

Restart your server.

go run *.go

Upon refreshing your preview, the map should look styled according to your preferences. Here is an example using the JSON styling above.

2ece59c64c06e9da.png

11. Deploy to production

If you want to see your app running from AppEngine Flex (and not just a local webserver on your development machine / Cloud Shell, which is what you've been doing), it's very easy. We just need to add a couple things in order for database access to work in the production environment. This is all outlined in the documentation page on Connecting from App Engine Flex to Cloud SQL .

Add Environment Variables to App.yaml

First, all those environment variables you were using to test locally need to be added to the bottom of your application's app.yaml file.

  1. Visit https://console.cloud.google.com/sql/instances/locations/overview to look up the instance connection name.
  2. Paste the following code at the end of app.yaml .
  3. Replace YOUR_DB_PASSWORD_HERE with the password you created for the postgres username earlier.
  4. Replace YOUR_CONNECTION_NAME_HERE with the value from step 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

Note that the DB_TCP_HOST should have the value 172.17.0.1 since this app connects via AppEngine Flex**.** This is because it will be communicating with Cloud SQL via a proxy, similar to the way you were.

Add SQL Client permissions to the AppEngine Flex service account

Go to the IAM-Admin page in Cloud Console and look for a service account whose name matches the format service-PROJECT_NUMBER@gae-api-prod.google.com.iam.gserviceaccount.com . This is the service account App Engine Flex will use to connect to the database. Click the Edit button at the end of the row and add the role " Cloud SQL Client ".

b04ccc0b4022b905.png

Copy your project code to the Go path

In order for AppEngine to run your code, it needs to be able to find relevant files in the Go path. Make sure you are in your project root directory.

cd YOUR_PROJECT_ROOT

Copy the directory to the go path.

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

Change into that directory.

cd ~/gopath/src/austin-recycling

Deploy Your App

Use the gcloud CLI to deploy your app. It will take some time to deploy.

gcloud app deploy

Use the browse command to get a link that you can click on to see your fully deployed, enterprise-grade, aesthetically stunning store locator in action.

gcloud app browse

If you were running gcloud outside the cloud shell, then running gcloud app browse would open a new browser tab.

12. (Recommended) Clean up

Performing this codelab will stay within free tier limits for BigQuery processing and Maps Platform API calls, but if you performed this solely as an educational exercise and want to avoid incurring any future charges, the easiest way to delete the resources associated with this project is to delete the project itself.

Delete the Project

In the GCP Console, go to the Cloud Resource Manager page:

In the project list, select the project we've been working in and click Delete . You'll be prompted to type in the project ID. Enter it and click Shut Down.

Alternatively, you can delete the entire project directly from Cloud Shell with gcloud by running the following command and replacing the placeholder GOOGLE_CLOUD_PROJECT with your project ID:

gcloud projects delete GOOGLE_CLOUD_PROJECT

13. Congratulations

অভিনন্দন! You have successfully completed the codelab !

Or you skimmed to the last page. অভিনন্দন! You have skimmed to the last page !

Over the course of this codelab, you have worked with the following technologies:

Further Reading

There's still lots to learn about all of these technologies. Below are some helpful links for topics we didn't have time to cover in this codelab, but could certainly be useful to you in building out a store locator solution that fits your specific needs.