إنشاء محدِّد مواقع بسيط للمتجر باستخدام "منصة خرائط Google" (JavaScript)

1. قبل البدء

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

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

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

489628918395c3d0.png

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

  • معرفة أساسية بـ HTML وJavaScript

المهام التي ستنفِّذها

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

2. الإعداد

في الخطوة 3 من القسم التالي، فعِّل واجهات برمجة التطبيقات الثلاث التالية لهذا الدرس التطبيقي:

  • Maps JavaScript API
  • واجهة برمجة تطبيقات الأماكن
  • Distance Matrix API

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

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

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

تفعيل Cloud Shell

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

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

5f504766b9b3be17.png

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

d3bb67d514893d1f.png

بعد الربط بخدمة Cloud Shell، من المفترض أن تظهر لك معلومات المصادقة التي سبق لك إعدادها وأنّ المشروع قد سبق وتم إعداده على رقم تعريف المشروع الذي اختَرته أثناء الإعداد.

$ gcloud auth list
Credentialed Accounts:
ACTIVE  ACCOUNT
  *     <myaccount>@<mydomain>.com
$ gcloud config list project
[core]
project = <YOUR_PROJECT_ID>

إذا لم يتم إعداد المشروع لسبب ما، شغّل الأمر التالي:

$ gcloud config set project <YOUR_PROJECT_ID>

3- &اقتباس،مرحبًا، بالعالم!&quot؛ مع خريطة

البدء في تطوير التطبيقات باستخدام خريطة

في Cloud Shell، تبدأ بإنشاء صفحة HTML تعمل كأساس لبقية الدرس التطبيقي حول الترميز.

  1. في شريط أدوات Cloud Shell، انقر على Launch Editor (محرِّر المستندات) 996514928389de40.png لفتح محرِّر رموز في علامة تبويب جديدة.

ويسمح لك محرّر الرموز المستند إلى الويب بتعديل الملفات بسهولة في Cloud Shell.

لقطة شاشة يوم 19-04-2017 في الساعة 10.22.48 صباحًا

  1. أنشئ دليل store-locator جديدًا لتطبيقك في محرِّر الرموز من خلال النقر على ملف > مجلد جديد.

مجلد جديد

  1. أطلق اسمًا على المجلد الجديد store-locator.

بعد ذلك، يمكنك إنشاء صفحة ويب تحتوي على خريطة.

  1. أنشئ ملفًا في دليل store-locator بالاسم index.html.

3c257603da5ab524.png

  1. ضع المحتوى التالي في ملف 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 التي يتم تشغيلها بعد تحميل واجهة برمجة تطبيقات JavaScript للخرائط، أي initMap.

  1. استبدِل النص YOUR_API_KEY في مقتطف الرمز بمفتاح واجهة برمجة التطبيقات الذي أنشأته سابقًا في هذا الدرس التطبيقي حول الترميز.
  2. أخيرًا، أنشئ ملفًا آخر باسم 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> لتثبيت الخريطة وتحديد المركز والتكبير أو التصغير.

لاختبار هذا التطبيق، يمكنك تشغيل خادم Python HTTP البسيط في Cloud Shell.

  1. انتقل إلى Cloud Shell واكتب ما يلي:
$ cd store-locator
$ python3 -m http.server 8080

ستظهر لك بعض أسطر مخرجات السجل التي تُظهر لك أنك تشغّل خادم HTTP البسيط في Cloud Shell مع تطبيق التطبيق على الويب الذي يستمع إلى منفذ localhost 8080.

  1. افتَح علامة تبويب متصفِّح الويب على هذا التطبيق من خلال النقر على معاينة الويب 95e419ae763a1d48.pngفي شريط أدوات Cloud Console واختيار معاينة على المنفذ 8080.

47b06e5169eb5add.png

bdab1f021a3b91d5.png

يؤدي النقر على عنصر القائمة هذا إلى فتح علامة تبويب جديدة في متصفح الويب تتضمن محتوى HTML يتم عرضه من خادم Python HTTP البسيط. إذا سارت الأمور بشكل جيد، ستظهر لك خريطة تتمركز في لندن، إنجلترا.

لإيقاف خادم HTTP البسيط، اضغط على Control+C في Cloud Shell.

4. ملء الخريطة باستخدام GeoJSON

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

  1. أنشئ ملفًا جديدًا باسم 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"
            }
        }
    ]
}

وهذا يشمل الكثير من البيانات، ولكن بعد الاطلاع عليه، سترى ببساطة البنية نفسها مكررة لكل متجر. يتم تمثيل كل متجر بصفته GeoJSON Point مع إحداثياته والبيانات الإضافية المضمنة ضمن المفتاح properties. وتجدر الإشارة إلى أنّ GeoJSON يسمح بتضمين المفاتيح ذات الأسماء العشوائية تحت مفتاح properties. في هذا الدرس التطبيقي حول الترميز، هذه المفاتيح هي category وhours وdescription وname وphone.

  1. يمكنك الآن تعديل 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. ويمكنك أيضًا تحديد دالة لتشغيلها في كل مرة يتم فيها النقر على علامة. يمكن للدالة بعد ذلك الوصول إلى البيانات الإضافية للمتجر الذي تم النقر على محدِّد الموقع فيه واستخدام المعلومات المتوفّرة في نافذة المعلومات المعروضة. لاختبار هذا التطبيق، يمكنك تشغيل خادم Python HTTP البسيط باستخدام الأمر نفسه كما في السابق.

  1. ارجع إلى Cloud Shell واكتب ما يلي:
$ python3 -m http.server 8080
  1. انقر على معاينة الويب 95e419ae763a1d48.png > معاينة على المنفذ 8080 مرة أخرى ويُفترض أن ترى خريطة مليئة بالعلامات يمكنك النقر عليها لعرض تفاصيل حول كل متجر، كما هو موضح في المثال التالي. مستوى التقدّم

c4507f7d3ea18439.png

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) وصورة "التجوّل الافتراضي" لموقع المتجر.

قبل نشر هذا، عليك إكمال خطوتين:

  1. حدِّد القيمة الصحيحة للمتغيّر apiKey من خلال استبدال السلسلة 'YOUR_API_KEY' في app.js بمفتاح واجهة برمجة التطبيقات الخاص بك السابق (القيمة نفسها التي لصقتها في index.html، مع الإبقاء على علامات الاقتباس كما هي).
  2. شغِّل الأوامر التالية في 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
  1. عايِن محدِّد مواقع المتاجر المنتهية من خلال تشغيل الأمر التالي:
$ python3 -m http.server 8080

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

3d8d13da126021dd.png

6- الحصول على إدخال المستخدم

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

  1. يمكنك الرجوع لتعديل 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>

يتم إخفاء كل من شريط بحث الإكمال التلقائي ولوحة السحب في البداية حتى يتم الحاجة إليها.

  1. والآن، أضِف أداة الإكمال التلقائي إلى الخريطة في نهاية دالة 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']);

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

  1. أعِد تشغيل الخادم وأعِد تحميل المعاينة من خلال تنفيذ الأمر التالي:
$ python3 -m http.server 8080

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

5163f34a03910187.png

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

  1. أضف الرمز التالي إلى نهاية 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- قائمة المتاجر الأقرب

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

  1. إضافة دالة جديدة إلى 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;
}

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

يتوقع المستخدم رؤية قائمة بالمتاجر التي تم طلبها من الأقرب إلى الأبعد. عليك ملء بطاقة بيانات جانبية لكل متجر باستخدام القائمة التي تم عرضها من دالة calculateDistances لإبلاغ ترتيب العرض للمتاجر.

  1. إضافة دالة جديدة إلى 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;
}
  1. أعِد تشغيل الخادم وأعِد تحميل المعاينة من خلال تنفيذ الأمر التالي.
$ python3 -m http.server 8080
  1. وأخيرًا، أدخِل عنوان المملكة المتحدة في شريط البحث في الإكمال التلقائي، ثم انقر على أحد الاقتراحات.

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

489628918395c3d0.png

8- اختياري: استضافة صفحة الويب الخاصة بك

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

باستخدام Cloud Storage، يتم تخزين الملفات في حِزم، وهي تشبه الأدلة على الكمبيوتر. لاستضافة صفحة الويب، عليك أولاً إنشاء حزمة. يجب اختيار اسم فريد للمجموعة، ربما باستخدام اسمك كجزء من اسم الحزمة.

  1. بعد تحديد اسم، شغِّل الأمر التالي في Cloud Shell:
$ gsutil mb gs://yourname-store-locator

gsutil هي الأداة للتفاعل مع Cloud Storage. يشير الأمر mb بشكل مبتكر إلى "make bucket." للحصول على مزيد من المعلومات حول جميع الأوامر المتاحة، بما في ذلك الأوامر التي تستخدمها، يُرجى الاطّلاع على أداة gsutil.

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

  1. شغِّل الأمر التالي، مع استبدال yourname-store-locator بالاسم الذي اخترته للحزمة:
$ gsutil defacl ch -u AllUsers:R gs://yourname-store-locator
  1. يمكنك الآن تحميل كل ملفاتك في الدليل الحالي (ملفات 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 Project الذي أنشأته في بداية هذا البرنامج التعليمي:

  • افتَح صفحة الإعدادات في Cloud Console.
  • انقر على اختيار مشروع.
  • اختَر المشروع الذي أنشأته في بداية هذا البرنامج التعليمي ثم انقر على Open (فتح).
  • أدخِل رقم تعريف المشروع وانقر على إيقاف التشغيل.

9- تهانينا

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

ما تعلّمته

مزيد من المعلومات

ما هي الدروس التطبيقية الأخرى حول الترميز التي تريد الاطّلاع عليها؟

التمثيل البصري للبيانات على الخرائط مزيد من المعلومات عن تخصيص نمط خرائطي إنشاء التفاعلات الثلاثية الأبعاد في الخرائط

هل الدرس التطبيقي حول الترميز الذي تريده غير مدرج أعلاه؟ طلب حلول للمشكلة الجديدة هنا

إذا كنت ترغب في التعمّق في الرمز، يمكنك إلقاء نظرة على مستودع رموز المصدر على https://github.com/googlecodelabs/google-maps-Simple-store-locator.