1. قبل البدء
من أكثر الميزات شيوعًا في المواقع الإلكترونية عرض خريطة Google تُبرز موقعًا جغرافيًا واحدًا أو أكثر لنشاط تجاري أو مؤسسة أو كيان آخر له حضور فعلي. يمكن أن تختلف طريقة تنفيذ هذه الخرائط بشكل كبير حسب المتطلبات، مثل عدد المواقع الجغرافية ومدى تكرار تغييرها.
في هذا الدرس العملي، سنستعرض أبسط حالة استخدام، وهي عدد صغير من المواقع الجغرافية التي نادرًا ما تتغيّر، مثل أداة البحث عن المتاجر لنشاط تجاري لديه سلسلة من المتاجر. في هذه الحالة، يمكنك استخدام أسلوب بسيط نسبيًا بدون أي برمجة من جهة الخادم. ولكن هذا لا يعني أنّه لا يمكنك إطلاق العنان لإبداعك، ويمكنك فعل ذلك من خلال الاستفادة من تنسيق البيانات GeoJSON لتخزين وعرض معلومات عشوائية حول كل متجر على خريطتك، بالإضافة إلى تخصيص العلامات والنمط العام للخريطة نفسها.
أخيرًا، يمكنك استخدام Cloud Shell لتطوير واستضافة أداة البحث عن المتاجر. مع أنّ استخدام هذه الأداة ليس مطلوبًا بشكل صارم، إلا أنّه يتيح لك تطوير أداة تحديد موقع المتجر من أي جهاز يعمل على متصفّح ويب وإتاحتها على الإنترنت للجميع.
المتطلبات الأساسية
- معرفة أساسية بلغتَي HTML وJavaScript
المهام التي ستنفذها
- عرض خريطة تتضمّن مجموعة من المواقع الجغرافية للمتاجر ومعلومات مخزّنة بتنسيق GeoJSON
- تخصيص العلامات والخريطة نفسها
- عرض معلومات إضافية عن المتجر عند النقر على علامته
- أضِف شريط بحث مفعَّلاً فيه ميزة "الإكمال التلقائي لأسماء الأماكن" إلى صفحة الويب.
- تحديد موقع المتجر الأقرب إلى نقطة بداية يحدّدها المستخدم
2. طريقة الإعداد
في الخطوة 3 من القسم التالي، فعِّل واجهات برمجة التطبيقات الثلاث التالية لهذا الدرس العملي:
- Maps JavaScript API
- واجهة برمجة تطبيقات الأماكن
- Distance Matrix API
بدء استخدام "منصة خرائط Google"
إذا لم يسبق لك استخدام "منصة خرائط Google"، اتّبِع دليل "البدء باستخدام منصة خرائط Google" أو شاهِد قائمة تشغيل "البدء باستخدام منصة خرائط Google" لإكمال الخطوات التالية:
- أنشئ حساب فوترة.
- إنشاء مشروع
- فعِّل واجهات برمجة التطبيقات وحِزم تطوير البرامج (SDK) في "منصة خرائط Google" (المدرَجة في القسم السابق).
- أنشئ مفتاح واجهة برمجة تطبيقات.
تفعيل Cloud Shell
في هذا الدرس العملي، ستستخدم Cloud Shell، وهي بيئة سطر أوامر تعمل في Google Cloud وتتيح الوصول إلى المنتجات والموارد التي تعمل على Google Cloud، ما يتيح لك استضافة مشروعك وتشغيله بالكامل من متصفّح الويب.
لتفعيل Cloud Shell من Cloud Console، انقر على تفعيل Cloud Shell (يجب أن تستغرق عملية توفير البيئة والاتصال بها بضع لحظات فقط).
يؤدي هذا الإجراء إلى فتح نافذة shell جديدة في الجزء السفلي من المتصفّح بعد عرض إعلان بيني تمهيدي.
بعد الاتصال بـ Cloud Shell، من المفترض أن ترى أنّه تم إثبات هويتك وأنّ المشروع تم ضبطه مسبقًا على رقم تعريف المشروع الذي اخترته أثناء عملية الإعداد.
$ gcloud auth list Credentialed Accounts: ACTIVE ACCOUNT * <myaccount>@<mydomain>.com
$ gcloud config list project [core] project = <YOUR_PROJECT_ID>
إذا لم يتم ضبط المشروع لسبب ما، شغِّل الأمر التالي:
$ gcloud config set project <YOUR_PROJECT_ID>
3- "Hello, World!" مع خريطة
بدء التطوير باستخدام خريطة
في Cloud Shell، ابدأ بإنشاء صفحة HTML ستكون أساس بقية الدرس العملي.
- في شريط أدوات Cloud Shell، انقر على تشغيل المحرّر
لفتح محرّر رموز في علامة تبويب جديدة.
يتيح لك محرّر الرموز البرمجية المستند إلى الويب تعديل الملفات في Cloud Shell بسهولة.
- أنشئ دليلاً جديدًا باسم
store-locator
لتطبيقك في محرّر الرموز البرمجية من خلال النقر على ملف > مجلد جديد.
- أدخِل اسمًا للمجلد الجديد
store-locator
.
بعد ذلك، أنشِئ صفحة ويب تتضمّن خريطة.
- أنشئ ملفًا في الدليل
store-locator
باسمindex.html
.
- ضَع المحتوى التالي في ملف
index.html
:
index.html
<html>
<head>
<title>Store Locator</title>
<style>
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- The div to hold the map -->
<div id="map"></div>
<script src="app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
</script>
</body>
</html>
هذه هي صفحة HTML التي تعرض الخريطة. يحتوي على بعض رموز CSS لضمان أن تشغل الخريطة الصفحة بأكملها بصريًا، وعلامة <div>
لاحتواء الخريطة، وزوج من العلامات <script>
. تحمّل علامة البرنامج النصي الأولى ملف JavaScript باسم app.js
، والذي يحتوي على كل رمز JavaScript. تحمّل علامة النص البرمجي الثانية مفتاح واجهة برمجة التطبيقات، وتتضمّن استخدام "مكتبة الأماكن" لوظيفة الإكمال التلقائي التي ستضيفها لاحقًا، وتحدّد اسم دالة JavaScript التي يتم تنفيذها بعد تحميل Maps JavaScript API، وهي initMap
.
- استبدِل النص
YOUR_API_KEY
في مقتطف الرمز بمفتاح واجهة برمجة التطبيقات الذي أنشأته سابقًا في هذا الدرس التطبيقي. - أخيرًا، أنشئ ملفًا آخر باسم
app.js
باستخدام الرمز التالي:
app.js
function initMap() {
// Create the map.
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
center: { lat: 52.632469, lng: -1.689423 },
});
}
هذا هو الحد الأدنى من الرموز المطلوبة لإنشاء خريطة. يمكنك تمرير مرجع إلى علامة <div>
لعرض الخريطة، وتحديد المركز ومستوى التكبير.
لاختبار هذا التطبيق، يمكنك تشغيل خادم HTTP بسيط بلغة Python في Cloud Shell.
- انتقِل إلى Cloud Shell واكتب ما يلي:
$ cd store-locator $ python3 -m http.server 8080
ستظهر لك بعض أسطر ناتج السجلّ توضّح أنّك تشغّل خادم HTTP البسيط في Cloud Shell مع تطبيق الويب الذي يستمع إلى المنفذ 8080 على المضيف المحلي.
- افتح علامة تبويب متصفّح ويب في هذا التطبيق من خلال النقر على معاينة الويب
في شريط أدوات Cloud Console واختيار المعاينة على المنفذ 8080.
يؤدي النقر على عنصر القائمة هذا إلى فتح علامة تبويب جديدة في متصفّح الويب تتضمّن محتوى HTML الذي يتم عرضه من خادم HTTP بسيط بلغة Python. إذا سارت الأمور على ما يرام، من المفترض أن تظهر لك خريطة في وسطها مدينة لندن في إنجلترا.
لإيقاف خادم HTTP البسيط، اضغط على Control+C
في Cloud Shell.
4. ملء الخريطة باستخدام GeoJSON
الآن، ألقِ نظرة على بيانات المتاجر. GeoJSON هو تنسيق بيانات يمثّل عناصر جغرافية بسيطة، مثل النقاط أو الخطوط أو المضلّعات على الخريطة. يمكن أن تحتوي الميزات أيضًا على بيانات عشوائية. هذا يجعل GeoJSON خيارًا ممتازًا لتمثيل المتاجر، التي هي في الأساس نقاط على خريطة تتضمّن بعض البيانات الإضافية، مثل اسم المتجر وساعات عمله ورقم هاتفه. الأهم من ذلك أنّ GeoJSON متوافق تمامًا مع "خرائط Google"، ما يعني أنّه يمكنك إرسال مستند GeoJSON إلى خريطة Google وسيتم عرضه على الخريطة بشكل مناسب.
- أنشئ ملفًا جديدًا باسم
stores.json
والصِق فيه الرمز التالي:
stores.json
{
"type": "FeatureCollection",
"features": [{
"geometry": {
"type": "Point",
"coordinates": [-0.1428115,
51.5125168
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Modern twists on classic pastries. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Mayfair",
"phone": "+44 20 1234 5678",
"storeid": "01"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-2.579623,
51.452251
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Come and try our award-winning cakes and pastries. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Bristol",
"phone": "+44 117 121 2121",
"storeid": "02"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [
1.273459,
52.638072
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Whatever the occasion, whether it's a birthday or a wedding, Josie's Patisserie has the perfect treat for you. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Norwich",
"phone": "+44 1603 123456",
"storeid": "03"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-1.9912838,
50.8000418
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "A gourmet patisserie that will delight your senses. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Wimborne",
"phone": "+44 1202 343434",
"storeid": "04"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-2.985933,
53.408899
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Spoil yourself or someone special with our classic pastries. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Liverpool",
"phone": "+44 151 444 4444",
"storeid": "05"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-1.689423,
52.632469
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Come and feast your eyes and tastebuds on our delicious pastries and cakes. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Tamworth",
"phone": "+44 5555 55555",
"storeid": "06"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-3.155305,
51.479756
]
},
"type": "Feature",
"properties": {
"category": "patisserie",
"hours": "10am - 6pm",
"description": "Josie's Patisserie is family-owned, and our delectable pastries, cakes, and great coffee are renowed. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Patisserie Cardiff",
"phone": "+44 29 6666 6666",
"storeid": "07"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-0.725019,
52.668891
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "Oakham's favorite spot for fresh coffee and delicious cakes. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Oakham",
"phone": "+44 7777 777777",
"storeid": "08"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-2.477653,
53.735405
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "Enjoy freshly brewed coffe, and home baked cakes in our homely cafe. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Blackburn",
"phone": "+44 8888 88888",
"storeid": "09"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-0.211363,
51.108966
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "A delicious array of pastries with many flavours, and fresh coffee in an snug cafe. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Crawley",
"phone": "+44 1010 101010",
"storeid": "10"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-0.123559,
50.832679
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "Grab a freshly brewed coffee, a decadent cake and relax in our idyllic cafe. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Brighton",
"phone": "+44 1313 131313",
"storeid": "11"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [-3.319575,
52.517827
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "Come in and unwind at this idyllic cafe with fresh coffee and home made cakes. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Newtown",
"phone": "+44 1414 141414",
"storeid": "12"
}
},
{
"geometry": {
"type": "Point",
"coordinates": [
1.158167,
52.071634
]
},
"type": "Feature",
"properties": {
"category": "cafe",
"hours": "8am - 9:30pm",
"description": "Fresh coffee and delicious cakes in an snug cafe. We're part of a larger chain of patisseries and cafes.",
"name": "Josie's Cafe Ipswich",
"phone": "+44 1717 17171",
"storeid": "13"
}
}
]
}
هذه كمية كبيرة من البيانات، ولكن بعد الاطّلاع عليها، ستلاحظ أنّها مجرّد البنية نفسها المتكرّرة لكل متجر. يتم تمثيل كل متجر كعنصر Point
GeoJSON مع إحداثياته والبيانات الإضافية الواردة ضمن المفتاح properties
. من المثير للاهتمام أنّ GeoJSON يسمح بتضمين مفاتيح ذات أسماء عشوائية ضمن المفتاح properties
. في هذا الدرس العملي، تكون هذه المفاتيح category
وhours
وdescription
وname
وphone
.
- عدِّل الآن
app.js
ليتم تحميل GeoJSON فيstores.js
على خريطتك.
app.js
function initMap() {
// Create the map.
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
center: {lat: 52.632469, lng: -1.689423},
});
// Load the stores GeoJSON onto the map.
map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});
const apiKey = 'YOUR_API_KEY';
const infoWindow = new google.maps.InfoWindow();
// Show the information for a store when its marker is clicked.
map.data.addListener('click', (event) => {
const category = event.feature.getProperty('category');
const name = event.feature.getProperty('name');
const description = event.feature.getProperty('description');
const hours = event.feature.getProperty('hours');
const phone = event.feature.getProperty('phone');
const position = event.feature.getGeometry().get();
const content = `
<h2>${name}</h2><p>${description}</p>
<p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
`;
infoWindow.setContent(content);
infoWindow.setPosition(position);
infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
infoWindow.open(map);
});
}
في مثال الرمز البرمجي، تم تحميل GeoJSON على الخريطة من خلال استدعاء loadGeoJson
وتمرير اسم ملف JSON. حدّدت أيضًا دالة يتم تشغيلها في كل مرة يتم فيها النقر على علامة. يمكن للدالة بعد ذلك الوصول إلى البيانات الإضافية الخاصة بالمتجر الذي تم النقر على علامته واستخدام المعلومات في نافذة معلومات يتم عرضها. لاختبار هذا التطبيق، يمكنك تشغيل خادم HTTP بسيط بلغة Python باستخدام الأمر نفسه كما في السابق.
- ارجع إلى Cloud Shell واكتب ما يلي:
$ python3 -m http.server 8080
- انقر على معاينة الويب
> المعاينة على المنفذ 8080 مرة أخرى، وستظهر لك خريطة مليئة بالعلامات التي يمكنك النقر عليها لعرض تفاصيل حول كل متجر، مثل المثال التالي. أحرزت تقدّمًا!
5- تخصيص الخريطة
أنت على وشك إكمال إعداد الملف الشخصي للشريك. لديك خريطة تتضمّن جميع علامات متجرك ومعلومات إضافية تظهر عند النقر عليها. ولكنّها تبدو مثل أي خريطة أخرى من خرائط Google. يا له من ملل! يمكنك إضافة لمسة شخصية باستخدام نمط خريطة مخصّص وعلامات وشعارات وصور "التجوّل الافتراضي".
إليك نسخة جديدة من app.js
مع إضافة أنماط مخصّصة:
app.js
const mapStyle = [{
'featureType': 'administrative',
'elementType': 'all',
'stylers': [{
'visibility': 'on',
},
{
'lightness': 33,
},
],
},
{
'featureType': 'landscape',
'elementType': 'all',
'stylers': [{
'color': '#f2e5d4',
}],
},
{
'featureType': 'poi.park',
'elementType': 'geometry',
'stylers': [{
'color': '#c5dac6',
}],
},
{
'featureType': 'poi.park',
'elementType': 'labels',
'stylers': [{
'visibility': 'on',
},
{
'lightness': 20,
},
],
},
{
'featureType': 'road',
'elementType': 'all',
'stylers': [{
'lightness': 20,
}],
},
{
'featureType': 'road.highway',
'elementType': 'geometry',
'stylers': [{
'color': '#c5c6c6',
}],
},
{
'featureType': 'road.arterial',
'elementType': 'geometry',
'stylers': [{
'color': '#e4d7c6',
}],
},
{
'featureType': 'road.local',
'elementType': 'geometry',
'stylers': [{
'color': '#fbfaf7',
}],
},
{
'featureType': 'water',
'elementType': 'all',
'stylers': [{
'visibility': 'on',
},
{
'color': '#acbcc9',
},
],
},
];
function initMap() {
// Create the map.
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 7,
center: {lat: 52.632469, lng: -1.689423},
styles: mapStyle,
});
// Load the stores GeoJSON onto the map.
map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});
// Define the custom marker icons, using the store's "category".
map.data.setStyle((feature) => {
return {
icon: {
url: `img/icon_${feature.getProperty('category')}.png`,
scaledSize: new google.maps.Size(64, 64),
},
};
});
const apiKey = 'YOUR_API_KEY';
const infoWindow = new google.maps.InfoWindow();
// Show the information for a store when its marker is clicked.
map.data.addListener('click', (event) => {
const category = event.feature.getProperty('category');
const name = event.feature.getProperty('name');
const description = event.feature.getProperty('description');
const hours = event.feature.getProperty('hours');
const phone = event.feature.getProperty('phone');
const position = event.feature.getGeometry().get();
const content = `
<img style="float:left; width:200px; margin-top:30px" src="img/logo_${category}.png">
<div style="margin-left:220px; margin-bottom:20px;">
<h2>${name}</h2><p>${description}</p>
<p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
<p><img src="https://maps.googleapis.com/maps/api/streetview?size=350x120&location=${position.lat()},${position.lng()}&key=${apiKey}&solution_channel=GMP_codelabs_simplestorelocator_v1_a"></p>
</div>
`;
infoWindow.setContent(content);
infoWindow.setPosition(position);
infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
infoWindow.open(map);
});
}
إليك ما أضفته:
- يحتوي المتغيّر
mapStyle
على جميع المعلومات اللازمة لتصميم الخريطة. (يمكنك أيضًا إنشاء نمطك الخاص إذا أردت ذلك). - باستخدام طريقة
map.data.setStyle
، طبّقت علامات مخصّصة، أي علامة مختلفة لكلcategory
من GeoJSON. - عدّلت المتغيّر
content
لتضمين شعار (باستخدامcategory
من GeoJSON مرة أخرى) وصورة "التجوّل الافتراضي" لموقع المتجر.
قبل نشر هذا التطبيق، عليك إكمال بضع خطوات:
- اضبط القيمة الصحيحة للمتغير
apiKey
من خلال استبدال السلسلة'YOUR_API_KEY'
فيapp.js
بمفتاح واجهة برمجة التطبيقات الخاص بك الذي حصلت عليه سابقًا (المفتاح نفسه الذي لصقته فيindex.html
، مع ترك علامات الاقتباس كما هي). - نفِّذ الأوامر التالية في Cloud Shell لتنزيل الرسومات الخاصة بالعلامة والشعار. تأكَّد من أنّك في الدليل
store-locator
. استخدِمControl+C
لإيقاف خادم HTTP البسيط إذا كان قيد التشغيل.
$ mkdir -p img; cd img $ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_cafe.png $ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/icon_patisserie.png $ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_cafe.png $ wget https://github.com/googlecodelabs/google-maps-simple-store-locator/raw/master/src/img/logo_patisserie.png
- يمكنك معاينة أداة البحث عن المتاجر المكتملة من خلال تنفيذ الأمر التالي:
$ python3 -m http.server 8080
عند إعادة تحميل المعاينة، من المفترض أن تظهر لك خريطة مشابهة لهذه الخريطة مع أنماط مخصّصة وصور علامات مخصّصة وتنسيق محسّن لنافذة المعلومات وصورة "تجوّل افتراضي" لكل موقع جغرافي:
6. الحصول على بيانات أدخلها المستخدم
يريد مستخدمو أدوات البحث عن المتاجر عادةً معرفة أقرب متجر إليهم أو عنوان المكان الذي يخطّطون لبدء رحلتهم منه. أضِف شريط بحث "الإكمال التلقائي لاسم المكان" للسماح للمستخدم بإدخال عنوان بداية بسهولة. توفّر خدمة Place Autocomplete وظيفة البحث المسبق المشابهة لطريقة عمل ميزة "الإكمال التلقائي" في أشرطة البحث الأخرى من Google، ولكنّ جميع النتائج المقترَحة هي أماكن في "منصة خرائط Google".
- ارجع إلى تعديل
index.html
لإضافة أنماط إلى شريط البحث الخاص بميزة "الإكمال التلقائي" واللوحة الجانبية المرتبطة بالنتائج. لا تنسَ استبدال مفتاح واجهة برمجة التطبيقات إذا لصقت الرمز القديم.
index.html
<html>
<head>
<title>Store Locator</title>
<style>
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
/* 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;
}
#title {
color: #fff;
background-color: #acbcc9;
font-size: 18px;
font-weight: 400;
padding: 6px 12px;
}
.hidden {
display: none;
}
/* Styling for an info pane that slides out from the left.
* Hidden by default. */
#panel {
height: 100%;
width: null;
background-color: white;
position: fixed;
z-index: 1;
overflow-x: hidden;
transition: all .2s ease-out;
}
.open {
width: 250px;
}
.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;
}
.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;
}
</style>
</head>
<body>
<!-- The div to hold the map -->
<div id="map"></div>
<script src="app.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap&solution_channel=GMP_codelabs_simplestorelocator_v1_a">
</script>
</body>
</html>
يتم إخفاء كلّ من شريط البحث "الإكمال التلقائي" واللوحة المنزلقة في البداية إلى أن تكون هناك حاجة إليهما.
- الآن، أضِف أداة الإكمال التلقائي إلى الخريطة في نهاية الدالة
initMap
فيapp.js
، قبل قوس الإغلاق المعقوف مباشرةً.
app.js
// Build and add the search bar
const card = document.createElement('div');
const titleBar = document.createElement('div');
const title = document.createElement('div');
const container = document.createElement('div');
const input = document.createElement('input');
const options = {
types: ['address'],
componentRestrictions: {country: 'gb'},
};
card.setAttribute('id', 'pac-card');
title.setAttribute('id', 'title');
title.textContent = 'Find the nearest store';
titleBar.appendChild(title);
container.setAttribute('id', 'pac-container');
input.setAttribute('id', 'pac-input');
input.setAttribute('type', 'text');
input.setAttribute('placeholder', 'Enter an address');
container.appendChild(input);
card.appendChild(titleBar);
card.appendChild(container);
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);
// 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(input, options);
autocomplete.setFields(
['address_components', 'geometry', 'name']);
يقتصر الرمز على اقتراحات "الإكمال التلقائي" لعرض العناوين فقط (لأنّ خدمة Place Autocomplete يمكن أن تتطابق مع أسماء المؤسسات والمواقع الإدارية)، كما يقتصر على عرض العناوين في المملكة المتحدة فقط. ستؤدي إضافة هذه المواصفات الاختيارية إلى تقليل عدد الأحرف التي يحتاج المستخدم إلى إدخالها لتضييق نطاق التوقعات وعرض العنوان الذي يبحث عنه. بعد ذلك، ينقل أداة الإكمال التلقائي div
التي أنشأتها إلى أعلى يسار الخريطة ويحدّد الحقول التي يجب عرضها عن كل "مكان" في الردّ.
- أعِد تشغيل الخادم وأعِد تحميل المعاينة من خلال تنفيذ الأمر التالي:
$ python3 -m http.server 8080
من المفترض أن يظهر لك الآن تطبيق مصغّر للإكمال التلقائي في أعلى يسار الخريطة، يعرض لك عناوين في المملكة المتحدة تتطابق مع ما تكتبه.
الآن، عليك التعامل مع الحالات التي يختار فيها المستخدم نتيجة بحث مقترَحة من أداة "الإكمال التلقائي" واستخدام هذا الموقع الجغرافي كأساس لاحتساب المسافات إلى متاجرك.
- أضِف الرمز التالي إلى نهاية
initMap
فيapp.js
بعد الرمز الذي ألصقته للتو.
app.js
// Set the origin point when the user selects an address
const originMarker = new google.maps.Marker({map: map});
originMarker.setVisible(false);
let originLocation = map.getCenter();
autocomplete.addListener('place_changed', async () => {
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(9);
console.log(place);
originMarker.setPosition(originLocation);
originMarker.setVisible(true);
// Use the selected address as the origin to calculate distances
// to each of the store locations
const rankedStores = await calculateDistances(map.data, originLocation);
showStoresList(map.data, rankedStores);
return;
});
يضيف الرمز البرمجي أداة معالجة الأحداث حتى عندما ينقر المستخدم على أحد الاقتراحات، تتم إعادة توسيط الخريطة على العنوان المحدّد ويتم ضبط نقطة البداية كأساس لاحتساب المسافات. يمكنك تنفيذ عمليات حساب المسافة في الخطوة التالية.
7. إدراج المتاجر الأقرب إليك
تعمل واجهة Directions API بشكل مشابه لتجربة طلب الاتجاهات في تطبيق "خرائط Google"، أي إدخال نقطة بداية واحدة ووجهة واحدة لتلقّي مسار بين النقطتين. تتوسّع واجهة برمجة التطبيقات Distance Matrix API في هذا المفهوم لتحديد أفضل عمليات الربط بين نقاط انطلاق متعددة محتملة ووجهات متعددة محتملة استنادًا إلى أوقات السفر والمسافات. في هذه الحالة، ولمساعدة المستخدم في العثور على أقرب متجر إلى العنوان الذي اختاره، عليك تقديم نقطة بداية واحدة ومجموعة من المواقع الجغرافية للمتاجر كوجهات.
- أضِف دالة جديدة إلى
app.js
باسمcalculateDistances
.
app.js
async function calculateDistances(data, origin) {
const stores = [];
const destinations = [];
// Build parallel arrays for the store IDs and destinations
data.forEach((store) => {
const storeNum = store.getProperty('storeid');
const storeLoc = store.getGeometry().get();
stores.push(storeNum);
destinations.push(storeLoc);
});
// Retrieve the distances of each store from the origin
// The returned list will be in the same order as the destinations list
const service = new google.maps.DistanceMatrixService();
const getDistanceMatrix =
(service, parameters) => new Promise((resolve, reject) => {
service.getDistanceMatrix(parameters, (response, status) => {
if (status != google.maps.DistanceMatrixStatus.OK) {
reject(response);
} else {
const distances = [];
const results = response.rows[0].elements;
for (let j = 0; j < results.length; j++) {
const element = results[j];
const distanceText = element.distance.text;
const distanceVal = element.distance.value;
const distanceObject = {
storeid: stores[j],
distanceText: distanceText,
distanceVal: distanceVal,
};
distances.push(distanceObject);
}
resolve(distances);
}
});
});
const distancesList = await getDistanceMatrix(service, {
origins: [origin],
destinations: destinations,
travelMode: 'DRIVING',
unitSystem: google.maps.UnitSystem.METRIC,
});
distancesList.sort((first, second) => {
return first.distanceVal - second.distanceVal;
});
return distancesList;
}
تطلب الدالة بيانات من Distance Matrix API باستخدام نقطة الأصل التي تم تمريرها إليها كنقطة أصل واحدة ومواقع المتاجر كمصفوفة من الوجهات. بعد ذلك، يتم إنشاء مصفوفة من العناصر التي تخزّن رقم تعريف المتجر والمسافة المعروضة في سلسلة يمكن لشخص عادي قراءتها والمسافة بالأمتار كقيمة رقمية، ويتم ترتيب المصفوفة.
يتوقّع المستخدم أن يرى قائمة بالمتاجر مرتّبة من الأقرب إلى الأبعد. املأ قائمة اللوحة الجانبية لكل متجر باستخدام القائمة التي تم إرجاعها من الدالة calculateDistances
لتحديد ترتيب عرض المتاجر.
- أضِف دالة جديدة إلى
app.js
باسمshowStoresList
.
app.js
function showStoresList(data, stores) {
if (stores.length == 0) {
console.log('empty stores');
return;
}
let panel = document.createElement('div');
// If the panel already exists, use it. Else, create it and add to the page.
if (document.getElementById('panel')) {
panel = document.getElementById('panel');
// If panel is already open, close it
if (panel.classList.contains('open')) {
panel.classList.remove('open');
}
} else {
panel.setAttribute('id', 'panel');
const body = document.body;
body.insertBefore(panel, body.childNodes[0]);
}
// Clear the previous details
while (panel.lastChild) {
panel.removeChild(panel.lastChild);
}
stores.forEach((store) => {
// Add store details with text formatting
const name = document.createElement('p');
name.classList.add('place');
const currentStore = data.getFeatureById(store.storeid);
name.textContent = currentStore.getProperty('name');
panel.appendChild(name);
const distanceText = document.createElement('p');
distanceText.classList.add('distanceText');
distanceText.textContent = store.distanceText;
panel.appendChild(distanceText);
});
// Open the panel
panel.classList.add('open');
return;
}
- أعِد تشغيل الخادم وأعِد تحميل المعاينة من خلال تنفيذ الأمر التالي.
$ python3 -m http.server 8080
- أخيرًا، أدخِل عنوانًا في المملكة المتحدة في شريط البحث "الإكمال التلقائي" وانقر على أحد الاقتراحات.
يجب أن تتوسّط الخريطة هذا العنوان وأن يظهر شريط جانبي يعرض مواقع المتاجر حسب المسافة من العنوان المحدّد. في ما يلي مثال على ذلك:
8. اختياري: استضافة صفحة الويب
حتى هذه المرحلة، لا يمكنك عرض خريطتك إلا عندما يكون خادم HTTP الخاص بلغة Python قيد التشغيل. لعرض الخريطة خارج جلسة Cloud Shell النشطة أو لمشاركة عنوان URL الخاص بالخريطة مع الآخرين، يمكنك استخدام Cloud Storage لاستضافة صفحة الويب. Cloud Storage هي خدمة ويب لتخزين الملفات على الإنترنت، وتتيح تخزين البيانات والوصول إليها على بنية Google الأساسية. تجمع الخدمة بين أداء Google Cloud وقابليته للتوسّع وبين ميزات الأمان والمشاركة المتقدّمة. توفّر هذه الخدمة أيضًا طبقة مجانية، ما يجعلها خيارًا رائعًا لاستضافة أداة بسيطة للعثور على المتاجر.
باستخدام Cloud Storage، يتم تخزين الملفات في حِزم تشبه الدلائل على جهاز الكمبيوتر. لاستضافة صفحة الويب، عليك أولاً إنشاء حزمة. عليك اختيار اسم فريد للحزمة، ربما باستخدام اسمك كجزء من اسم الحزمة.
- بعد اختيار اسم، شغِّل الأمر التالي في Cloud Shell:
$ gsutil mb gs://yourname-store-locator
gsutil هي الأداة المستخدَمة للتفاعل مع Cloud Storage. يمثّل الأمر mb
بشكل إبداعي عبارة "إنشاء حزمة". لمزيد من المعلومات عن جميع الأوامر المتاحة، بما في ذلك الأوامر التي تستخدمها، يمكنك الاطّلاع على أداة gsutil.
تكون الحِزم والملفات المستضافة على Cloud Storage خاصة بشكلٍ تلقائي. ومع ذلك، بالنسبة إلى أداة البحث عن المتاجر، يجب أن تكون جميع الملفات متاحة للجميع حتى يتمكّن أي مستخدم من الوصول إليها عبر الإنترنت. يمكنك ضبط كل ملف على "علني" بعد تحميله، ولكنّ ذلك سيكون مضنيًا. بدلاً من ذلك، يمكنك ببساطة ضبط مستوى الوصول التلقائي للحزمة التي أنشأتها، وستكتسب جميع الملفات التي تحمّلها إليها مستوى الوصول هذا.
- نفِّذ الأمر التالي، مع استبدال
yourname-store-locator
بالاسم الذي اخترته للحزمة:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
- يمكنك الآن تحميل جميع ملفاتك في الدليل الحالي (ملفات
index.html
وapp.js
فقط حاليًا) باستخدام الأمر التالي:
$ gsutil -h "Cache-Control:no-cache" cp * gs://yourname-store-locator
من المفترض أن تظهر لك الآن صفحة ويب تتضمّن خريطة على الإنترنت. سيكون عنوان URL للاطّلاع عليه هو http://storage.googleapis.com/yourname-store-locator/index.html، مع استبدال جزء yourname-store-locator باسم الحزمة الذي اخترته سابقًا.
تنظيف
أسهل طريقة لتنظيف جميع الموارد التي تم إنشاؤها في هذا المشروع هي إيقاف مشروع Google Cloud الذي أنشأته في بداية هذا البرنامج التعليمي:
- فتح صفحة الإعدادات في Cloud Console
- انقر على اختيار مشروع.
- اختَر المشروع الذي أنشأته في بداية هذا البرنامج التعليمي وانقر على فتح.
- أدخِل رقم تعريف المشروع وانقر على إيقاف.
9- تهانينا
تهانينا! لقد أكملت هذا الدرس العملي.
ما تعلّمته
- إضافة خريطة ذات تصميم مخصّص باستخدام Maps JavaScript API
- تحميل طبقة بيانات على خريطة بتنسيق GeoJSON
- استخدام Street View Static API لعرض صورة "التجوّل الافتراضي" في صفحة ويب
- استخدام "مكتبة الأماكن" لإضافة شريط بحث "الإكمال التلقائي للأماكن" إلى الصفحة
- استخدام خدمة "مصفوفة المسافات" لاحتساب مسافات متعددة من خلال طلب واحد من واجهة برمجة التطبيقات
- إدارة مشاريع تطوير الويب واختبارها في Google Cloud Platform باستخدام واجهة سطر الأوامر Cloud Shell المستندة إلى المتصفّح
- استضافة موقع إلكتروني باستخدام Cloud Storage
مزيد من المعلومات
- تعرَّف على طريقة أخرى لاستضافة خريطة الويب باستخدام Google App Engine في الدرس التطبيقي حول الترميز رسم خريطة مترو أنفاق مدينة نيويورك.
- استكشِف المزيد من الدروس التطبيقية حول الترميز في "منصة خرائط Google"، مثل خدمة إنشاء خدمة بحث عن الأنشطة التجارية القريبة.
- يُرجى مساعدتنا في إنشاء المحتوى الذي تراه الأنسب لك من خلال الإجابة عن السؤال أدناه:
ما هي جلسات الترميز الأخرى التي تريد المشاركة فيها؟
هل لم يتم إدراج الدرس العملي الذي تريده أعلاه؟ يمكنك طلب ذلك من خلال تقديم مشكلة جديدة هنا.
إذا أردت التعرّف على المزيد من التفاصيل حول الرمز البرمجي، يمكنك الاطّلاع على مستودع الرموز البرمجية المصدر على https://github.com/googlecodelabs/google-maps-simple-store-locator.