طلب البحث عن بيانات الموقع الجغرافي وعرضها بشكل مرئي في BigQuery باستخدام "منصة خرائط Google" (JavaScript)

1. نظرة عامة

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

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

ما ستنشئه

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

أهداف الدورة التعليمية

  • كيفية طلب البحث عن مجموعات بيانات المواقع الجغرافية التي تبلغ سعتها بيتابايت في ثوانٍ باستخدام BigQuery، وذلك باستخدام طلبات بحث SQL والدوال المعرَّفة من قِبل المستخدم وBigQuery API
  • كيفية استخدام منصة خرائط Google لإضافة خريطة Google إلى صفحة ويب والسماح للمستخدمين برسم أشكال عليها
  • كيفية عرض طلبات البحث بشكل مرئي مقابل مجموعات البيانات الكبيرة على "خريطة Google"، كما هو موضّح في صورة المثال أدناه التي تعرض كثافة مواقع التوقف لسيارات الأجرة في عام 2016 من الرحلات التي بدأت من المربع المحيط بمبنى "إمباير ستيت"

Screen Shot 2017-05-09 at 11.01.12 AM.png

المتطلبات

  • معرفة أساسية بلغات HTML وCSS وJavaScript وSQL و"أدوات مطوّري البرامج في Chrome"
  • متصفّح ويب حديث، مثل أحدث إصدارات Chrome أو Firefox أو Safari أو Edge
  • محرّر نصوص أو بيئة تطوير متكاملة (IDE) من اختيارك

التكنولوجيا

BigQuery

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

منصة خرائط Google

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

تتيح لك طبقة الرسم في Google Maps Platform Javascript API رسم أشكال على الخريطة. يمكن تحويل هذه البيانات إلى إدخال لتنفيذ طلبات بحث في جداول BigQuery التي تتضمّن قيم خطوط الطول والعرض المخزّنة في الأعمدة.

لبدء الاستخدام، تحتاج إلى مشروع Google Cloud Platform تم تفعيل BigQuery وواجهات برمجة التطبيقات في "خرائط Google" فيه.

2. الاستعداد

حساب Google

إذا لم يكن لديك حساب على Google (Gmail أو Google Apps)، عليك إنشاء حساب.

إنشاء مشروع

سجِّل الدخول إلى "وحدة تحكّم Google Cloud Platform" (console.cloud.google.com) وأنشِئ مشروعًا جديدًا. في أعلى الشاشة، توجد قائمة منسدلة باسم "المشروع":

f2a353c3301dc649.png

بعد النقر على القائمة المنسدلة الخاصة بهذا المشروع، ستظهر لك قائمة تتيح لك إنشاء مشروع جديد:

56a42dfa7ac27a35.png

في المربّع الذي يظهر فيه "أدخِل اسمًا جديدًا لمشروعك"، أدخِل اسمًا لمشروعك الجديد، مثل "BigQuery Codelab":

Codelab - create project (1).png

سيتم إنشاء معرّف مشروع لك. معرّف المشروع هو اسم فريد في جميع مشاريع Google Cloud. تذكَّر رقم تعريف مشروعك لأنّك ستستخدمه لاحقًا. سبق أن تم استخدام الاسم أعلاه ولن يكون متاحًا لك. أدرِج رقم تعريف مشروعك في أي مكان يظهر فيه YOUR_PROJECT_ID في هذا الدرس العملي.

تفعيل الفوترة

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

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

تقدّم Google فترة تجريبية مجانية لمدة 12 شهرًا بقيمة تصل إلى 300 دولار أمريكي لاستخدام Google Cloud Platform، ويمكنك الاستفادة منها في هذا الدرس التطبيقي. يمكنك الاطّلاع على مزيد من التفاصيل على الرابط https://cloud.google.com/free/.

المشاريع الجديدة

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

إذا لم يكن لديك حساب فوترة، عليك إنشاء حساب وتفعيل الفوترة لمشروعك قبل أن تتمكّن من استخدام العديد من ميزات Google Cloud Platform. لإنشاء حساب فوترة جديد وتفعيل الفوترة لمشروعك، اتّبِع التعليمات الواردة في إنشاء حساب فوترة جديد.

المشاريع الحالية

إذا كان لديك مشروع أوقفت الفوترة فيه مؤقتًا، يمكنك إعادة تفعيلها باتّباع الخطوات التالية:

  1. انتقِل إلى وحدة تحكّم Cloud Platform.
  2. من قائمة المشاريع، اختَر المشروع الذي تريد إعادة تفعيل الفوترة له.
  3. افتح القائمة الجانبية اليمنى في وحدة التحكّم وانقر على الفوترة الفوترة. سيُطلب منك اختيار حساب فوترة.
  4. انقر على ضبط الحساب.

إنشاء حساب فوترة جديد

لإنشاء حساب فوترة جديد، اتّبِع الخطوات التالية:

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

بشكلٍ تلقائي، يكون المستخدم الذي ينشئ حساب الفوترة هو مشرف الفوترة للحساب.

للحصول على معلومات حول إثبات ملكية الحسابات المصرفية وإضافة طرق دفع احتياطية، اطّلِع على مقالة إضافة طريقة دفع أو إزالتها أو تعديلها.

تفعيل BigQuery API

لتفعيل BigQuery API في مشروعك، انتقِل إلى صفحة BigQuery API في Marketplace في وحدة التحكّم وانقر على الزر الأزرق "تفعيل".

3- إنشاء طلب بحث عن بيانات الموقع الجغرافي في BigQuery

هناك ثلاث طرق لإجراء طلبات بحث في بيانات الموقع الجغرافي المخزّنة كقيم لخطوط الطول والعرض في BigQuery.

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

للبدء، استخدِم محرّر طلبات البحث في قسم Big Query ضمن وحدة تحكّم Google Cloud Platform لتنفيذ طلبات البحث التالية على بيانات سيارات الأجرة في نيويورك.

الإصدار العادي من لغة الاستعلامات البنيوية (SQL) مقابل الإصدار القديم

يتوافق BigQuery مع إصدارَين من لغة الاستعلامات البنيوية (SQL): لغة الاستعلامات البنيوية (SQL) القديمة و لغة الاستعلامات البنيوية (SQL) العادية. الأخير هو معيار ANSI لعام 2011. لأغراض هذا البرنامج التعليمي، سنستخدم لغة SQL القياسية لأنّها تتوافق مع المعايير بشكل أفضل.

إذا أردت تنفيذ طلبات بحث بلغة SQL القديمة في محرِّر BigQuery، يمكنك إجراء ذلك باتّباع الخطوات التالية:

  1. انقر على زر "المزيد"
  2. اختَر "إعدادات طلب البحث" من القائمة المنسدلة
  3. ضمن "إصدار لغة الاستعلامات البنيوية (SQL)"، انقر على زر الاختيار "قديم"
  4. انقر على الزر "حفظ"

طلبات البحث المستطيلة

من السهل جدًا إنشاء طلبات بحث مستطيلة في BigQuery. ما عليك سوى إضافة عبارة WHERE التي تحصر النتائج المعروضة في تلك التي تتضمّن مواقع جغرافية تتراوح بين الحدّ الأدنى والحدّ الأقصى لقيمتَي خط العرض وخط الطول.

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

SELECT 
ROUND(AVG(tip_amount),2) as avg_tip, 
ROUND(AVG(fare_amount),2) as avg_fare, 
ROUND(AVG(trip_distance),2) as avg_distance, 
ROUND(AVG(tip_proportion),2) as avg_tip_pc, 
ROUND(AVG(fare_per_mile),2) as avg_fare_mile FROM

(SELECT 

pickup_latitude, pickup_longitude, tip_amount, fare_amount, trip_distance, (tip_amount / fare_amount)*100.0 as tip_proportion, fare_amount / trip_distance as fare_per_mile

FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`

WHERE trip_distance > 0.01 AND fare_amount <100 AND payment_type = "1" AND fare_amount > 0
)

--Manhattan
WHERE pickup_latitude < 40.7679 AND pickup_latitude > 40.7000 AND pickup_longitude < -73.97 and pickup_longitude > -74.01

--JFK
--WHERE pickup_latitude < 40.654626 AND pickup_latitude > 40.639547 AND pickup_longitude < -73.771497 and pickup_longitude > -73.793755

توضّح نتائج طلبَي البحث أنّ هناك اختلافات كبيرة في متوسط مسافة الرحلة والأجرة والإكرامية لعمليات الاستلام في الموقعَين الجغرافيَّين.

مانهاتن

avg_tip

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

2.52

12.03

9.97

22.39

5.97

JFK

avg_tip

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

9.22

48.49

41.19

22.48

4.36

طلبات البحث حسب النطاق الجغرافي

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

في ما يلي مثال على عبارة SQL في BigQuery لطلب بحث عن دائرة مركزها 40.73943, -73.99585 ونصف قطرها 0.1 كيلومتر.

تستخدِم هذه الدالة القيمة الثابتة 111.045 كيلومتر لتقريب المسافة التي تمثّلها درجة واحدة.

يستند هذا إلى مثال تم العثور عليه في http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/:

SELECT pickup_latitude, pickup_longitude, 
    (111.045 * DEGREES( 
      ACOS( 
        COS( RADIANS(40.73943) ) * 
        COS( RADIANS( pickup_latitude ) ) * 
        COS( 
          RADIANS( -73.99585 ) - 
          RADIANS( pickup_longitude ) 
        ) + 
        SIN( RADIANS(40.73943) ) * 
        SIN( RADIANS( pickup_latitude ) ) 
      ) 
     ) 
    ) AS distance FROM `project.dataset.tableName` 
    HAVING distance < 0.1 

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

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

#standardSQL
CREATE TEMPORARY FUNCTION Degrees(radians FLOAT64) RETURNS FLOAT64 AS
(
  (radians*180)/(22/7)
);

CREATE TEMPORARY FUNCTION Radians(degrees FLOAT64) AS (
  (degrees*(22/7))/180
);

CREATE TEMPORARY FUNCTION DistanceKm(lat FLOAT64, lon FLOAT64, lat1 FLOAT64, lon1 FLOAT64) AS (
     Degrees( 
      ACOS( 
        COS( Radians(lat1) ) * 
        COS( Radians(lat) ) *  
        COS( Radians(lon1 ) -  
        Radians( lon ) ) +  
        SIN( Radians(lat1) ) *  
        SIN( Radians( lat ) ) 
        ) 
    ) * 111.045
);

SELECT 

ROUND(AVG(tip_amount),2) as avg_tip,
ROUND(AVG(fare_amount),2) as avg_fare,
ROUND(AVG(trip_distance),2) as avg_distance,
ROUND(AVG(tip_proportion), 2) as avg_tip_pc,
ROUND(AVG(fare_per_mile),2) as avg_fare_mile

FROM

-- EMPIRE STATE BLDG 40.748459, -73.985731
-- BRONX 40.895597, -73.856085

(SELECT pickup_latitude, pickup_longitude, tip_amount, fare_amount, trip_distance, tip_amount/fare_amount*100 as tip_proportion, fare_amount / trip_distance as fare_per_mile, DistanceKm(pickup_latitude, pickup_longitude, 40.748459, -73.985731)


FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2015`

WHERE 
  DistanceKm(pickup_latitude, pickup_longitude, 40.748459, -73.985731) < 0.1
  AND fare_amount > 0 and trip_distance > 0
  )
WHERE fare_amount < 100

في ما يلي نتائج طلب البحث. يمكنك ملاحظة اختلافات كبيرة في متوسط الإكرامية والأجرة ومسافة الرحلة والحجم النسبي للإكرامية مقارنةً بالأجرة ومتوسط الأجرة لكل ميل تمّت قيادته.

مبنى إمباير ستيت:

avg_tip

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

1.17

11.08

45.28

10.53

6.42

ذا برونكس

avg_tip

avg_fare

avg_distance

avg_tip_pc

avg_fare_mile

0.52

17.63

4.75

4.74

10.9

طلبات البحث عن المضلّعات

لا يتيح SQL تنفيذ طلبات بحث باستخدام أشكال عشوائية غير المستطيلات والدوائر. لا يتضمّن BigQuery أي نوع بيانات هندسية أو فهرس مكاني، لذا لتنفيذ طلبات البحث باستخدام أشكال مضلّعية، عليك اتّباع نهج مختلف عن طلبات البحث البسيطة بلغة SQL. أحد الأساليب هو تحديد دالة هندسية في JavaScript وتنفيذها كدالة معرَّفة من قِبل المستخدم (UDF) في BigQuery.

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

خوارزمية "النقطة داخل المضلّع"

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

function pointInPoly(nvert, vertx, verty, testx, testy){
  var i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
                (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
      c = !c;
  }
  return c;
}

نقل المحتوى إلى JavaScript

تبدو نسخة JavaScript من هذه الخوارزمية على النحو التالي:

/* This function includes a port of C code to calculate point in polygon
* see http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for license
*/

function pointInPoly(polygon, point){
    // Convert a JSON poly into two arrays and a vertex count.
    let vertx = [],
        verty = [],
        nvert = 0,
        testx = point[0],
        testy = point[1];
    for (let coord of polygon){
      vertx[nvert] = coord[0];
      verty[nvert] = coord[1];
      nvert ++;
    }

        
    // The rest of this function is the ported implementation.
    for (let i = 0, let j = nvert - 1; i < nvert; j = i++) {
      if ( ((verty[i] > testy) != (verty[j] > testy)) &&
         (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) )
        c = !c;
    }
    return c;
}

عند استخدام "لغة الاستعلامات البنيوية" (SQL) العادية في BigQuery، لا يتطلّب أسلوب الدالة المعرَّفة من قِبل المستخدم سوى عبارة واحدة، ولكن يجب تعريف الدالة المعرَّفة من قِبل المستخدم كدالة مؤقتة في العبارة. إليك مثالاً. الصِق عبارة SQL أدناه في نافذة "محرّر الاستعلامات".

CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64)
RETURNS BOOL LANGUAGE js AS """
  let polygon=[[-73.98925602436066,40.743249676056955],[-73.98836016654968,40.74280666503313],[-73.98915946483612,40.741676770346295],[-73.98967981338501,40.74191656974406]];

  let vertx = [],
    verty = [],
    nvert = 0,
    testx = longitude,
    testy = latitude,
    c = false,
    j = nvert - 1;

  for (let coord of polygon){
    vertx[nvert] = coord[0];
    verty[nvert] = coord[1];
    nvert ++;
  }

  // The rest of this function is the ported implementation.
  for (let i = 0; i < nvert; j = i++) {
    if ( ((verty[i] > testy) != (verty[j] > testy)) &&
 (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]) ) {
      c = !c;
    }
  }

  return c;
""";

SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime
FROM `bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016`
WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE
AND (pickup_datetime BETWEEN CAST("2016-01-01 00:00:01" AS DATETIME) AND CAST("2016-02-28 23:59:59" AS DATETIME))
LIMIT 1000

تهانينا!

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

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

4. العمل باستخدام Google Maps APIs

بعد تنفيذ بعض طلبات البحث المكانية البسيطة، تتمثّل الخطوة التالية في عرض الناتج بشكل مرئي للاطّلاع على الأنماط. لإجراء ذلك، عليك تفعيل Maps API، وإنشاء صفحة ويب ترسل طلبات بحث من خريطة إلى BigQuery، ثم رسم النتائج على الخريطة.

تفعيل Maps JavaScript API

في هذا الدرس العملي، عليك تفعيل Maps JavaScript API من Google Maps Platform في مشروعك. لإجراء ذلك، الرجاء القيام بما يلي:

  1. في وحدة تحكّم Google Cloud Platform، انتقِل إلى Marketplace.
  2. في Marketplace، ابحث عن Maps JavaScript API
  3. انقر على المربّع الخاص بـ Maps JavaScript API في نتائج البحث
  4. انقر على الزر "تفعيل"

إنشاء مفتاح واجهة برمجة تطبيقات

لإرسال طلبات إلى Google Maps Platform، عليك إنشاء مفتاح واجهة برمجة تطبيقات وإرساله مع جميع الطلبات. لإنشاء مفتاح واجهة برمجة تطبيقات، اتّبِع الخطوات التالية:

  1. في "وحدة تحكّم Google Cloud Platform"، انقر على قائمة الهامبرغر لفتح شريط التنقّل الأيمن
  2. اختَر "واجهات برمجة التطبيقات والخدمات" > "بيانات الاعتماد"
  3. انقر على الزر "إنشاء بيانات اعتماد" (Create Credential)، ثم اختَر "مفتاح واجهة برمجة التطبيقات" (API Key).
  4. نسخ مفتاح واجهة برمجة التطبيقات الجديد

تنزيل الرمز البرمجي وإعداد خادم ويب

انقر على الزر التالي لتنزيل كل الرموز البرمجية لهذا الدرس العملي:

فكّ ضغط ملف ZIP الذي تم تنزيله. سيؤدي ذلك إلى فك حزمة مجلد جذر (bigquery)، يحتوي على مجلد واحد لكل خطوة من خطوات هذا الدرس البرمجي، بالإضافة إلى جميع الموارد التي ستحتاج إليها.

تحتوي مجلدات stepN على الحالة النهائية المطلوبة لكل خطوة من خطوات هذا الدرس البرمجي. وهي متوفّرة للرجوع إليها. سننفّذ جميع أعمال الترميز في الدليل المسمّى work.

إعداد خادم ويب محلي

يمكنك استخدام خادم الويب الخاص بك، ولكن تم تصميم هذا الدرس العملي ليعمل بشكل جيد مع "خادم الويب في Chrome". إذا لم يكن هذا التطبيق مثبَّتًا بعد، يمكنك تثبيته من "سوق Chrome الإلكتروني".

بعد تثبيت التطبيق، افتحه. يمكنك إجراء ذلك في Chrome باتّباع الخطوات التالية:

  1. افتح Chrome
  2. في شريط العناوين في أعلى الصفحة، اكتب chrome://apps
  3. اضغط على Enter‏
  4. في النافذة التي تفتح، انقر على رمز "خادم الويب". يمكنك أيضًا النقر بزر الماوس الأيمن على أحد التطبيقات لفتحه في علامة تبويب عادية أو مثبّتة أو في وضع ملء الشاشة أو في نافذة جديدة a3ed00e79b8bfee7.png ستظهر لك مربّع الحوار التالي الذي يتيح لك ضبط خادم الويب المحلي: 81b6151c3f60c948.png
  5. انقر على "اختيار مجلد" (CHOOSE FOLDER) واختَر الموقع الجغرافي الذي نزّلت فيه ملفات نموذج الدرس العملي.
  6. في قسم "الخيارات"، ضَع علامة في المربّع بجانب "عرض index.html تلقائيًا": 17f4913500faa86f.png
  7. حرِّك زر التبديل المسمى "خادم الويب: تم التشغيل" (Web Server: STARTED) إلى اليسار ثم إلى اليمين لإيقاف خادم الويب ثم إعادة تشغيله.

a5d554d0d4a91851.png

5- جارٍ تحميل الخريطة وأدوات الرسم

إنشاء صفحة خريطة أساسية

ابدأ بصفحة HTML بسيطة تحمّل "خريطة Google" باستخدام Maps JavaScript API وبعض أسطر JavaScript. يُعدّ الرمز من نموذج الخريطة البسيط في "منصة خرائط Google" مكانًا رائعًا للبدء. تمت إعادة إنتاجه هنا لتتمكّن من نسخه ولصقه في محرّر النصوص أو بيئة التطوير المتكاملة التي تختارها، أو يمكنك العثور عليه من خلال فتح index.html من مستودع الرموز الذي نزّلته.

  1. انسخ index.html إلى المجلد work في نسختك المحلية من المستودع
  2. انسخ المجلد img/ إلى المجلد work/ في نسختك المحلية من مستودع الرموز.
  3. افتح ملف العمل/index.html في محرّر النصوص أو بيئة التطوير المتكاملة (IDE)
  4. استبدِل YOUR_API_KEY بمفتاح واجهة برمجة التطبيقات الذي أنشأته سابقًا
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"
    async defer></script>
  1. في المتصفّح، افتح localhost:<port>/work، حيث port هو رقم المنفذ المحدّد في إعدادات خادم الويب المحلي. المنفذ التلقائي هو 8887. من المفترض أن تظهر لك خرائطك الأولى.

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

تغيير الموقع الجغرافي ومستوى التكبير أو التصغير التلقائيَين

يظهر الرمز الذي يضبط الموقع الجغرافي ومستوى التكبير والتصغير في السطرين 27 و28 من ملف index.html، وهو يتمركز حاليًا في سيدني، أستراليا:

<script>
      let map;
      function initMap() {
        map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: -34.397, lng: 150.644},
          zoom: 8
        });
      }
</script>

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

لإجراء ذلك، عدِّل مجموعة الرموز البرمجية أعلاه إلى ما يلي لتوسيط الخريطة على مبنى Empire State وتعديل مستوى التكبير إلى 14:

<script>
      let map;
      function initMap() {
        map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 40.7484405, lng: -73.9878531},
          zoom: 14
        });
      }
</script>

بعد ذلك، أعِد تحميل الخريطة في المتصفّح للاطّلاع على النتائج.

تحميل مكتبات الرسومات والتصوّر

لإضافة إمكانات الرسم إلى خريطتك، عليك تغيير النص البرمجي الذي يحمّل Maps JavaScript API من خلال إضافة مَعلمة اختيارية تطلب من "منصة خرائط Google" تفعيل مكتبة الرسم.

يستخدم هذا الدرس التعليمي أيضًا HeatmapLayer، لذا عليك أيضًا تعديل النص البرمجي لطلب مكتبة العرض المرئي. لإجراء ذلك، أضِف المَعلمة libraries، وحدِّد المكتبتَين visualization وdrawing كقيم مفصولة بفواصل، مثل libraries=visualization,drawing.

يجب أن يظهر على النحو التالي:

<script src='http://maps.googleapis.com/maps/api/js?libraries=visualization,drawing&callback=initMap&key=YOUR_API_KEY' async defer></script>

إضافة DrawingManager

لاستخدام الأشكال التي يرسمها المستخدم كمدخل لطلب بحث، أضِف DrawingManager إلى خريطتك مع تفعيل الأدوات Circle وRectangle وPolygon.

من المستحسن وضع كل DrawingManager رمز الإعداد في دالة جديدة، لذا في نسختك من index.html، اتّبِع الخطوات التالية:

  1. أضِف دالة باسم setUpDrawingTools() مع الرمز التالي لإنشاء DrawingManager وضبط السمة map الخاصة بها للإشارة إلى عنصر الخريطة في الصفحة.

تضبط الخيارات التي تم تمريرها إلى google.maps.drawing.DrawingManager(options) نوع الرسم التلقائي للأشكال وخيارات العرض للأشكال المرسومة. عند اختيار مناطق من الخريطة لإرسالها كطلبات بحث، يجب أن تكون الأشكال شفافة تمامًا. لمزيد من المعلومات حول الخيارات المتاحة، يُرجى الاطّلاع على خيارات DrawingManager.

function setUpDrawingTools() {
  // Initialize drawing manager
  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.CIRCLE,
    drawingControl: true,
    drawingControlOptions: {
      position: google.maps.ControlPosition.TOP_LEFT,
      drawingModes: [
        google.maps.drawing.OverlayType.CIRCLE,
        google.maps.drawing.OverlayType.POLYGON,
        google.maps.drawing.OverlayType.RECTANGLE
      ]
    },
    circleOptions: {
      fillOpacity: 0
    },
    polygonOptions: {
      fillOpacity: 0
    },
    rectangleOptions: {
      fillOpacity: 0
    }
  });
  drawingManager.setMap(map);
}
  1. استدعِ الدالة setUpDrawingTools() في الدالة initMap() بعد إنشاء عنصر الخريطة
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
    zoom: 12
  });

  setUpDrawingTools();
}
  1. أعِد تحميل ملف index.html وتأكَّد من ظهور أدوات الرسم. تأكَّد أيضًا من إمكانية استخدامها لرسم دوائر ومستطيلات وأشكال مضلّعة.

يمكنك النقر والسحب لرسم دوائر ومستطيلات، ولكن يجب رسم المضلعات من خلال النقر على كل رأس، ثم النقر المزدوج لإنهاء الشكل.

التعامل مع أحداث الرسم

تحتاج إلى بعض الرموز البرمجية للتعامل مع الأحداث التي يتم تنشيطها عندما ينتهي المستخدم من رسم شكل، تمامًا كما تحتاج إلى إحداثيات الأشكال المرسومة لإنشاء طلبات بحث SQL.

سنضيف الرمز البرمجي لذلك في خطوة لاحقة، ولكن سنضيف الآن ثلاثة معالجات أحداث فارغة للتعامل مع الأحداث rectanglecomplete وcirclecomplete وpolygoncomplete. ليس على المعالِجات تنفيذ أي رمز برمجي في هذه المرحلة.

أضِف ما يلي إلى أسفل دالة setUpDrawingTools():

drawingManager.addListener('rectanglecomplete', rectangle => {
    // We will add code here in a later step.
});
drawingManager.addListener('circlecomplete', circle => {
  // We will add code here in a later step.
});

drawingManager.addListener('polygoncomplete', polygon => {
  // We will add code here in a later step.
});

يمكنك العثور على مثال عملي لهذا الرمز في نسختك المحلية من المستودع، في المجلد step2: step2/map.html.

6. استخدام BigQuery Client API

ستساعدك Google BigQuery Client API في تجنُّب كتابة الكثير من الرموز النموذجية اللازمة لإنشاء الطلبات وتحليل الردود والتعامل مع المصادقة. يستخدم هذا الدرس التطبيقي حول الترميز BigQuery API من خلال Google APIs Client Library for JavaScript لأنّنا سنطوّر تطبيقًا يستند إلى المتصفّح.

بعد ذلك، ستضيف رمزًا لتحميل واجهة برمجة التطبيقات هذه في صفحة ويب واستخدامها للتفاعل مع BigQuery.

إضافة Google Client API لـ JavaScript

ستستخدم Google Client API for Javascript لتنفيذ طلبات بحث في BigQuery. في نسختك من index.html (في مجلد work)، حمِّل واجهة برمجة التطبيقات باستخدام علامة <script> على النحو التالي. ضَع العلامة أسفل علامة <script> مباشرةً التي تحمّل واجهة برمجة التطبيقات Maps API:

<script src='https://apis.google.com/js/client.js'></script>

بعد تحميل Google Client API، امنح المستخدم الإذن بالوصول إلى البيانات في BigQuery. يمكنك استخدام بروتوكول OAuth 2.0 لإجراء ذلك. عليك أولاً إعداد بعض بيانات الاعتماد في مشروعك على Google Cloud Console.

إنشاء بيانات اعتماد OAuth 2.0

  1. في Google Cloud Console، من قائمة التنقّل، اختَر واجهات برمجة التطبيقات والخدمات > بيانات الاعتماد.

قبل إعداد بيانات الاعتماد، عليك إضافة بعض الإعدادات لشاشة "منح الإذن" التي سيراها المستخدم النهائي لتطبيقك عندما يسمح لتطبيقك بالوصول إلى بيانات BigQuery نيابةً عنه.

لإجراء ذلك، انقر على علامة التبويب شاشة موافقة OAuth. 2. عليك إضافة واجهة برمجة تطبيقات BigQuery إلى نطاقات هذا الرمز المميّز. انقر على الزر إضافة نطاق في قسم "نطاقات واجهات Google API". 3- من القائمة، ضَع علامة في المربّع بجانب إدخال Big Query API الذي يتضمّن النطاق ../auth/bigquery. 4. انقر على إضافة. 5- أدخِل اسمًا في حقل "اسم التطبيق". 6. انقر على حفظ لحفظ إعداداتك. 7. بعد ذلك، عليك إنشاء معرّف عميل OAuth. لإجراء ذلك، انقر على إنشاء بيانات اعتماد:

4d18a965fc760e39.png

  1. في القائمة المنسدلة، انقر على معرِّف عميل OAuth. 1f8b36a1c27c75f0.png
  2. ضمن "نوع التطبيق"، اختَر تطبيق الويب.
  3. في حقل "اسم التطبيق" (Application Name)، اكتب اسمًا لمشروعك. على سبيل المثال، "BigQuery وخرائط Google".
  4. ضمن القيود، في حقل "مصادر JavaScript المسموح بها"، أدخِل عنوان URL الخاص بالمضيف المحلي، بما في ذلك أرقام المنافذ. على سبيل المثال: http://localhost:8887
  1. انقر على الزر إنشاء.

ستظهر نافذة منبثقة تعرض معرّف العميل وسر العميل. تحتاج إلى معرّف العميل لإجراء المصادقة على BigQuery. انسخها والصقها في work/index.html كمتغيّر JavaScript شامل جديد باسم clientId.

let clientId = 'YOUR_CLIENT_ID';

7. التفويض والإعداد

يجب أن تسمح صفحة الويب للمستخدم بالوصول إلى BigQuery قبل بدء تشغيل الخريطة. في هذا المثال، نستخدم بروتوكول OAuth 2.0 كما هو موضّح في قسم التفويض في مستندات JavaScript Client API. عليك استخدام معرّف عميل OAuth ومعرّف مشروعك لإرسال طلبات البحث.

عند تحميل Google Client API في صفحة الويب، عليك اتّباع الخطوات التالية:

  • منح الإذن للمستخدم
  • إذا كان لديك إذن، حمِّل BigQuery API.
  • تحميل الخريطة وإعدادها

راجِع step3/map.html للاطّلاع على مثال حول شكل صفحة HTML النهائية.

منح الإذن للمستخدم

يجب أن يمنح المستخدم النهائي للتطبيق الإذن للتطبيق بالوصول إلى البيانات في BigQuery نيابةً عنه. تتولّى Google Client API for JavaScript معالجة منطق OAuth لتنفيذ ذلك.

في تطبيق حقيقي، لديك العديد من الخيارات بشأن كيفية دمج خطوة التفويض.

على سبيل المثال، يمكنك استدعاء authorize() من عنصر واجهة مستخدم، مثل زر، أو عند تحميل الصفحة. لقد اخترنا هنا تفويض المستخدم بعد تحميل Google Client API for JavaScript، وذلك باستخدام دالة ردّ اتصال في طريقة gapi.load().

اكتب بعض الرموز البرمجية مباشرةً بعد العلامة <script> التي تحمّل Google Client API for Javascript لتحميل كلّ من مكتبة البرامج ووحدة المصادقة حتى نتمكّن من مصادقة المستخدم على الفور.

<script src='https://apis.google.com/js/client.js'></script>
<script type='text/javascript'>
  gapi.load('client:auth', authorize);
</script>

عند منح الإذن، حمِّل BigQuery API.

بعد منح المستخدم الإذن، حمِّل BigQuery API.

أولاً، استدعِ الدالة gapi.auth.authorize() باستخدام المتغيّر clientId الذي أضفته في الخطوة السابقة. تعامَل مع الردّ في دالة ردّ اتصال باسم handleAuthResult.

تتحكّم المَعلمة immediate في ما إذا كان سيظهر للمستخدم إشعار منبثق. اضبطها على true لإخفاء النافذة المنبثقة الخاصة بالمصادقة إذا كان المستخدم قد تمّت مصادقته من قبل.

أضِف دالة إلى صفحتك باسم handleAuthResult(). يجب أن تتضمّن الدالة المَعلمة authresult التي تتيح لك التحكّم في سير المنطق استنادًا إلى ما إذا تم تفويض المستخدم بنجاح أم لا.

أضِف أيضًا دالة باسم loadApi لتحميل BigQuery API في حال تم منح المستخدم الإذن بنجاح.

أضِف منطقًا في الدالة handleAuthResult() لاستدعاء loadApi() إذا كان هناك كائن authResult تم تمريره إلى الدالة، وإذا كانت السمة error للكائن تتضمّن القيمة false.

أضِف رمزًا إلى الدالة loadApi() لتحميل BigQuery API باستخدام الطريقة gapi.client.load().

let clientId = 'your-client-id-here';
let scopes = 'https://www.googleapis.com/auth/bigquery';

// Check if the user is authorized.
function authorize(event) {
  gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
  return false;
}

// If authorized, load BigQuery API
function handleAuthResult(authResult) {
  if (authResult && !authResult.error) {
    loadApi();
    return;
  }
  console.error('Not authorized.')  
}

// Load BigQuery client API
function loadApi(){
  gapi.client.load('bigquery', 'v2');
}

تحميل الخريطة

الخطوة الأخيرة هي إعداد الخريطة. عليك تغيير ترتيب منطق التصفية قليلاً لإجراء ذلك. يتم حاليًا إعدادها عند تحميل JavaScript لواجهة برمجة التطبيقات Maps API.

يمكنك إجراء ذلك من خلال استدعاء الدالة initMap() من الطريقة then() بعد الطريقة load() في الكائن gapi.client.

// Load BigQuery client API
function loadApi(){
  gapi.client.load('bigquery', 'v2').then(
   () => initMap()
  );
}

8. مفاهيم BigQuery API

عادةً ما يتم تنفيذ طلبات BigQuery API في غضون ثوانٍ، ولكن قد لا يتم عرض ردّ على الفور. تحتاج إلى بعض المنطق لاستطلاع BigQuery لمعرفة حالة المهام التي تستغرق وقتًا طويلاً، ولا يمكنك جلب النتائج إلا عند اكتمال المهمة.

يمكنك العثور على الرمز الكامل لهذه الخطوة في step4/map.html.

إرسال طلب

أضِف دالة JavaScript إلى work/index.html لإرسال طلب بحث باستخدام واجهة برمجة التطبيقات، وبعض المتغيّرات لتخزين قيم مجموعة بيانات BigQuery والمشروع الذي يحتوي على الجدول المطلوب البحث فيه، ومعرّف المشروع الذي ستتم فوترته مقابل أي رسوم.

let datasetId = 'your_dataset_id';
let billingProjectId = 'your_project_id';
let publicProjectId = 'bigquery-public-data';

function sendQuery(queryString){
  let request = gapi.client.bigquery.jobs.query({
      'query': queryString,
      'timeoutMs': 30000,
      'datasetId': datasetId,
      'projectId': billingProjectId,
      'useLegacySql':false
  });
  request.execute(response => {
      //code to handle the query response goes here.
  });
}

التحقّق من حالة مهمة

تعرض الدالة checkJobStatus أدناه كيفية التحقّق من حالة مهمة بشكل دوري، وذلك باستخدام طريقة واجهة برمجة التطبيقات get وjobId التي تم عرضها في طلب البحث الأصلي. في ما يلي مثال يتم تنفيذه كل 500 ملي ثانية إلى أن تكتمل المهمة.

let jobCheckTimer;

function checkJobStatus(jobId){
  let request = gapi.client.bigquery.jobs.get({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response =>{
    if (response.status.errorResult){
      // Handle any errors.
      console.log(response.status.error);
      return;
    }

    if (response.status.state == 'DONE'){
      // Get the results.
      clearTimeout(jobCheckTimer);
      getQueryResults(jobId);
      return;
    }
    // Not finished, check again in a moment.
    jobCheckTimer = setTimeout(checkJobStatus, 500, [jobId]);    
  });
}

عدِّل الطريقة sendQuery لاستدعاء الطريقة checkJobStatus() كدالة ردّ في استدعاء request.execute(). مرِّر معرّف الوظيفة إلى checkJobStatus. يتم عرض ذلك من خلال عنصر الاستجابة كـ jobReference.jobId.

function sendQuery(queryString){
  let request = gapi.client.bigquery.jobs.query({
      'query': queryString,
      'timeoutMs': 30000,
      'datasetId': datasetId,
      'projectId': billingProjectId,
      'useLegacySql':false
  });
  request.execute(response => checkJobStatus(response.jobReference.jobId));
}

الحصول على نتائج طلب بحث

للحصول على نتائج طلب بحث بعد الانتهاء من تنفيذه، استخدِم طلب البيانات من واجهة برمجة التطبيقات jobs.getQueryResults. أضِف دالة إلى صفحتك باسم getQueryResults() تقبل مَعلمة jobId:

function getQueryResults(jobId){
  let request = gapi.client.bigquery.jobs.getQueryResults({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response => {
    // Do something with the results.
  })
}

9- إنشاء طلبات بحث عن بيانات الموقع الجغرافي باستخدام BigQuery API

هناك ثلاث طرق لاستخدام SQL لتنفيذ طلبات مكانية على البيانات في BigQuery:

تتوفّر أمثلة على طلبات البحث عن المربّعات المحيطة ونصف القطر في قسم "الدوال الرياضية" ضمن مرجع لغة الاستعلامات البنيوية (SQL) القديمة في BigQuery، ضِمن "أمثلة متقدّمة".

بالنسبة إلى طلبات البحث عن المربّعات المحيطة ونصف القطر، يمكنك استدعاء طريقة query في BigQuery API. أنشئ عبارة SQL لكل طلب بحث ومرِّرها إلى الدالة sendQuery التي أنشأتها في الخطوة السابقة.

يمكنك العثور على مثال عملي للرمز الخاص بهذه الخطوة في step4/map.html.

طلبات البحث المستطيلة

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

لاستخدام شكل رسمه المستخدم، غيِّر الرمز في index.html للتعامل مع حدث الرسم الذي يتم تشغيله عند اكتمال رسم مستطيل. في هذا المثال، يستخدم الرمز getBounds() على عنصر المستطيل للحصول على عنصر يمثّل مدى المستطيل في إحداثيات الخريطة، ويمرّره إلى دالة تُسمى rectangleQuery:

drawingManager.addListener('rectanglecomplete', rectangle => rectangleQuery(rectangle.getBounds()));

تحتاج الدالة rectangleQuery إلى استخدام الإحداثيات في أعلى اليمين (الشمال الشرقي) وأسفل اليسار (الجنوب الغربي) لإنشاء مقارنة أكبر من/أصغر من مع كل صف في جدول BigQuery. إليك مثال على طلب بحث في جدول يحتوي على أعمدة باسم 'pickup_latitude' و'pickup_longitude' تخزّن قيم الموقع الجغرافي.

تحديد جدول BigQuery

لإجراء طلب بحث في جدول باستخدام BigQuery API، عليك تقديم اسم الجدول في شكل مؤهَّل بالكامل في طلب بحث SQL. التنسيق في الإصدار العادي من لغة SQL هو project.dataset.tablename. في الإصدار القديم من لغة الاستعلامات البنيوية (SQL)، تكون القيمة project.dataset.tablename.

تتوفّر العديد من جداول رحلات سيارات الأجرة في مدينة نيويورك. للاطّلاع عليها، انتقِل إلى وحدة تحكّم BigQuery على الويب ووسِّع عنصر القائمة "مجموعات البيانات العامة". ابحث عن مجموعة البيانات المسماة new_york ووسِّعها للاطّلاع على الجداول. اختَر جدول رحلات سيارات الأجرة الصفراء: bigquery-public-data.new_york_taxi_trips.tlc_yellow_trips_2016).

تحديد رقم تعريف المشروع

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

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

أضِف متغيّرات JavaScript عمومية باسم billingProjectId, publicProjectId, datasetId وtableName إلى نسختك من index.html.

ابدأ بتحديد قيم المتغيرات 'publicProjectId' و'datasetId' و'tableName' باستخدام التفاصيل من مشروع "مجموعات بيانات BigQuery العامة". ابدأ billingProjectId باستخدام رقم تعريف المشروع الخاص بك (الذي أنشأته في قسم "الإعداد" في وقت سابق من هذا الدرس العملي).

let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york_taxi_trips';
let tableName = 'tlc_yellow_trips_2016';

أضِف الآن دالتَين إلى الرمز البرمجي لإنشاء SQL وإرسال طلب البحث إلى BigQuery باستخدام الدالة sendQuery التي أنشأتها في الخطوة السابقة.

يجب أن تحمل الدالة الأولى الاسم rectangleSQL() ويجب أن تقبل وسيطتَين، وهما زوج من عناصر google.Maps.LatLng التي تمثّل زوايا المستطيل في إحداثيات الخريطة.

يجب أن تُسمّى الدالة الثانية rectangleQuery(). يؤدي ذلك إلى تمرير نص طلب البحث إلى الدالة sendQuery.

let billingProjectId = 'YOUR_PROJECT_ID';
let publicProjectId = 'bigquery-public-data';
let datasetId = 'new_york';
let tableName = 'tlc_yellow_trips_2016';

function rectangleQuery(latLngBounds){
  let queryString = rectangleSQL(latLngBounds.getNorthEast(), latLngBounds.getSouthWest());
  sendQuery(queryString);
}

function rectangleSQL(ne, sw){
  let queryString = 'SELECT pickup_latitude, pickup_longitude '
  queryString +=  'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '`'
  queryString += ' WHERE pickup_latitude > ' + sw.lat();
  queryString += ' AND pickup_latitude < ' + ne.lat();
  queryString += ' AND pickup_longitude > ' + sw.lng();
  queryString += ' AND pickup_longitude < ' + ne.lng();
  return queryString;
}

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

10. عرض الردّ بشكل مرئي

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

يتوفّر الرمز الكامل لهذه الخطوة في step5/map.html.

لإبقاء حجم البيانات المنقولة إلى صفحة الويب ضمن حجم معقول لهذا الدرس التطبيقي، عدِّل الدالة rectangleSQL() لإضافة عبارة تحدّ من الاستجابة إلى 10, 000 صف. في المثال أدناه، يتم تحديد ذلك في متغير عام يُسمى recordLimit، حتى تتمكّن جميع دوال طلب البحث من استخدام القيمة نفسها.

let recordLimit = 10000;
function rectangleSQL(ne, sw){
  var queryString = 'SELECT pickup_latitude, pickup_longitude '
  queryString +=  'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '`'
  queryString += ' WHERE pickup_latitude > ' + sw.lat();
  queryString += ' AND pickup_latitude < ' + ne.lat();
  queryString += ' AND pickup_longitude > ' + sw.lng();
  queryString += ' AND pickup_longitude < ' + ne.lng();
  queryString += ' LIMIT ' + recordLimit;
  return queryString;
}

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

في الدالة getQueryResults، مرِّر الصفيف response.result.rows إلى دالة JavaScript جديدة باسم doHeatMap() ستنشئ خريطة حرارية.

سيحتوي كل صف على سمة باسم f وهي عبارة عن مصفوفة من الأعمدة. سيتضمّن كل عمود السمة v التي تحتوي على القيمة.

يجب أن يتكرّر الرمز البرمجي خلال الأعمدة في كل صف لاستخراج القيم.

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

لا تنسَ استدعاء setMap() في طبقة خريطة الكثافة بعد أن تكون قد خصّصت مصفوفة المواضع لها. سيؤدي ذلك إلى إظهارها على الخريطة.

وفي ما يلي مثال لذلك:

function getQueryResults(jobId){
  let request = gapi.client.bigquery.jobs.getQueryResults({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response => doHeatMap(response.result.rows))
}

let heatmap;

function doHeatMap(rows){
  let heatmapData = [];
  if (heatmap != null){
    heatmap.setMap(null);
  }
  for (let i = 0; i < rows.length; i++) {
      let f = rows[i].f;
      let coords = { lat: parseFloat(f[0].v), lng: parseFloat(f[1].v) };
      let latLng = new google.maps.LatLng(coords);
      heatmapData.push(latLng);
  }
  heatmap = new google.maps.visualization.HeatmapLayer({
      data: heatmapData
  });
  heatmap.setMap(map);
}

في هذه المرحلة، يجب أن تتمكّن من:

  • فتح الصفحة والمصادقة على BigQuery
  • ارسم مستطيلاً في مكان ما في مدينة نيويورك
  • اطّلِع على نتائج طلب البحث المعروضة كخريطة تمثيل لوني.

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

7b1face0e7c71c78.png

11. الاستعلام حسب النطاق الجغرافي حول نقطة

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

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

يتضمّن مستودع الرموز مثالاً عمليًا للرمز البرمجي الخاص بهذه الخطوة في step6/map.html.

drawingManager.addListener('circlecomplete', circle => circleQuery(circle));

في نسختك من index.html، أضِف دالتَين فارغتَين جديدتَين: circleQuery() وhaversineSQL().

بعد ذلك، أضِف معالج أحداث circlecomplete يمرّر المركز ونصف القطر إلى دالة جديدة باسم circleQuery().

ستستدعي الدالة circleQuery() الدالة haversineSQL() لإنشاء SQL للاستعلام، ثم سترسل الاستعلام عن طريق استدعاء الدالة sendQuery() وفقًا لنموذج الرمز التالي.

function circleQuery(circle){
  let queryString = haversineSQL(circle.getCenter(), circle.radius);
  sendQuery(queryString);
}

// Calculate a circular area on the surface of a sphere based on a center and radius.
function haversineSQL(center, radius){
  let queryString;
  let centerLat = center.lat();
  let centerLng = center.lng();
  let kmPerDegree = 111.045;

  queryString = 'CREATE TEMPORARY FUNCTION Degrees(radians FLOAT64) RETURNS FLOAT64 LANGUAGE js AS ';
  queryString += '""" ';
  queryString += 'return (radians*180)/(22/7);';
  queryString += '"""; ';

  queryString += 'CREATE TEMPORARY FUNCTION Radians(degrees FLOAT64) RETURNS FLOAT64 LANGUAGE js AS';
  queryString += '""" ';
  queryString += 'return (degrees*(22/7))/180;';
  queryString += '"""; ';

  queryString += 'SELECT pickup_latitude, pickup_longitude '
  queryString += 'FROM `' + publicProjectId +'.' + datasetId + '.' + tableName + '` ';
  queryString += 'WHERE '
  queryString += '(' + kmPerDegree + ' * DEGREES( ACOS( COS( RADIANS('
  queryString += centerLat;
  queryString += ') ) * COS( RADIANS( pickup_latitude ) ) * COS( RADIANS( ' + centerLng + ' ) - RADIANS('
  queryString += ' pickup_longitude ';
  queryString += ') ) + SIN( RADIANS('
  queryString += centerLat;
  queryString += ') ) * SIN( RADIANS( pickup_latitude ) ) ) ) ) ';

  queryString += ' < ' + radius/1000;
  queryString += ' LIMIT ' + recordLimit;
  return queryString;
}

جرّب الآن

أضِف الرمز أعلاه وجرِّب أداة "الدائرة" لاختيار منطقة من الخريطة. يجب أن تبدو النتيجة على النحو التالي:

845418166b7cc7a3.png

12. الاستعلام عن أشكال عشوائية

ملخّص: لا يتيح SQL تنفيذ طلبات بحث باستخدام أشكال عشوائية غير المستطيلات والدوائر. لا يتضمّن BigQuery أي نوع بيانات هندسية أصلي، لذا لتنفيذ طلبات البحث باستخدام أشكال مضلّعية، عليك اتّباع طريقة مختلفة عن طلبات SQL المباشرة.

إحدى ميزات BigQuery الفعّالة جدًا التي يمكن استخدامها لهذا الغرض هي "الدوال المعرَّفة من قِبل المستخدم" (UDF). تنفّذ الدوال المعرَّفة من قِبل المستخدم رمز JavaScript داخل استعلام SQL.

يمكنك العثور على الرمز البرمجي الخاص بهذه الخطوة في step7/map.html.

الدوال المعرَّفة من قِبل المستخدم في BigQuery API

يختلف أسلوب BigQuery API الخاص بوظائف UDF قليلاً عن وحدة تحكّم الويب: عليك استدعاء jobs.insert method.

بالنسبة إلى استعلامات Standard SQL من خلال واجهة برمجة التطبيقات، لا يلزم سوى عبارة SQL واحدة لاستخدام دالة من إنشاء المستخدم. يجب ضبط قيمة useLegacySql على false. يعرض مثال JavaScript أدناه دالة تنشئ كائن طلب وترسله لإدراج مهمة جديدة، وفي هذه الحالة طلب بحث باستخدام دالة من إنشاء المستخدم.

يمكنك الاطّلاع على مثال عملي لهذه الطريقة في step7/map.html.

function polygonQuery(polygon) {
  let request = gapi.client.bigquery.jobs.insert({
    'projectId' : billingProjectId,
      'resource' : {
        'configuration':
          {
            'query':
            {
              'query': polygonSql(polygon),
              'useLegacySql': false
            }
          }
      }
  });
  request.execute(response => checkJobStatus(response.jobReference.jobId));
}

يتم إنشاء طلب بحث SQL على النحو التالي:

function polygonSql(poly){
  let queryString = 'CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64) ';
  queryString += 'RETURNS BOOL LANGUAGE js AS """ ';
  queryString += 'var polygon=' + JSON.stringify(poly) + ';';
  queryString += 'var vertx = [];';
  queryString += 'var verty = [];';
  queryString += 'var nvert = 0;';
  queryString += 'var testx = longitude;';
  queryString += 'var testy = latitude;';
  queryString += 'for(coord in polygon){';
  queryString += '  vertx[nvert] = polygon[coord][0];';
  queryString += '  verty[nvert] = polygon[coord][1];';
  queryString += '  nvert ++;';
  queryString += '}';
  queryString += 'var i, j, c = 0;';
  queryString += 'for (i = 0, j = nvert-1; i < nvert; j = i++) {';
  queryString += '  if ( ((verty[i]>testy) != (verty[j]>testy)) &&(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ){';
  queryString += '    c = !c;';
  queryString += '  }';
  queryString += '}';
  queryString += 'return c;';
  queryString += '"""; ';
  queryString += 'SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime ';
  queryString += 'FROM `' + publicProjectId + '.' + datasetId + '.' + tableName + '` ';
  queryString += 'WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE ';
  queryString += 'LIMIT ' + recordLimit;
  return queryString;
}

هناك أمران يحدثان هنا. أولاً، ينشئ الرمز عبارة CREATE TEMPORARY FUNCTION التي تغلف رمز JavaScript لتحديد ما إذا كانت نقطة معيّنة تقع داخل مضلّع. يتم إدراج إحداثيات المضلّع باستخدام طلب الطريقة JSON.stringify(poly) لتحويل مصفوفة JavaScript من أزواج إحداثيات x وy إلى سلسلة. يتم تمرير عنصر المضلّع كوسيطة إلى الدالة التي تنشئ SQL.

ثانيًا، ينشئ الرمز عبارة SQL SELECT الرئيسية. يتم استدعاء الدالة المعرَّفة من قِبل المستخدم في تعبير WHERE في هذا المثال.

إجراء عملية التكامل مع Maps API

لاستخدام هذه الميزة مع مكتبة الرسومات في Maps API، علينا حفظ المضلّع الذي رسمه المستخدم وتمريره إلى جزء الدالة المعرَّفة من قِبل المستخدم في طلب بحث SQL.

أولاً، علينا التعامل مع حدث الرسم polygoncomplete للحصول على إحداثيات الشكل كمصفوفة من أزواج خطوط الطول والعرض:

drawingManager.addListener('polygoncomplete', polygon => {
  let path = polygon.getPaths().getAt(0);
  let queryPolygon = path.map(element => {
    return [element.lng(), element.lat()];
  });
  polygonQuery(queryPolygon);
});

يمكن للدالة polygonQuery بعد ذلك إنشاء دوال JavaScript الخاصة بالدالة المعرَّفة من قِبل المستخدم كسلسلة، بالإضافة إلى عبارة SQL التي ستستدعي دالة UDF.

يمكنك الاطّلاع على step7/map.html للحصول على مثال عملي على ذلك.

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

في ما يلي مثال على نتيجة طلب بحث عن عمليات استلام الركاب من بيانات سيارات الأجرة الصفراء التابعة للجنة سيارات الأجرة والليموزين في نيويورك لعام 2016 في BigQuery باستخدام مضلّع حر، مع عرض البيانات المحدّدة كخريطة حرارية.

Screen Shot 2017-05-09 at 10.00.48 AM.png

13. الخطوات التالية

في ما يلي بعض الاقتراحات حول طرق توسيع نطاق هذا الدرس العملي لاستكشاف جوانب أخرى من البيانات. يمكنك العثور على مثال عملي لهذه الأفكار في step8/map.html في مستودع الرموز البرمجية.

عمليات التوقّف عن استخدام الخريطة

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

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

غيِّر رمز عبارة SQL في polygonSql() لطلب هذه الأعمدة بالإضافة إلى موقع الاستلام.

function polygonSql(poly){
  let queryString = 'CREATE TEMPORARY FUNCTION pointInPolygon(latitude FLOAT64, longitude FLOAT64) ';
  queryString += 'RETURNS BOOL LANGUAGE js AS """ ';
  queryString += 'var polygon=' + JSON.stringify(poly) + ';';
  queryString += 'var vertx = [];';
  queryString += 'var verty = [];';
  queryString += 'var nvert = 0;';
  queryString += 'var testx = longitude;';
  queryString += 'var testy = latitude;';
  queryString += 'for(coord in polygon){';
  queryString += '  vertx[nvert] = polygon[coord][0];';
  queryString += '  verty[nvert] = polygon[coord][1];';
  queryString += '  nvert ++;';
  queryString += '}';
  queryString += 'var i, j, c = 0;';
  queryString += 'for (i = 0, j = nvert-1; i < nvert; j = i++) {';
  queryString += '  if ( ((verty[i]>testy) != (verty[j]>testy)) &&(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) ){';
  queryString += '    c = !c;';
  queryString += '  }';
  queryString += '}';
  queryString += 'return c;';
  queryString += '"""; ';

  queryString += 'SELECT pickup_latitude, pickup_longitude, dropoff_latitude, dropoff_longitude, pickup_datetime ';
  queryString += 'FROM `' + publicProjectId + '.' + datasetId + '.' + tableName + '` ';
  queryString += 'WHERE pointInPolygon(pickup_latitude, pickup_longitude) = TRUE ';
  queryString += 'LIMIT ' + recordLimit;
  return queryString;
}

يمكن أن تستخدم الدالة doHeatMap بعد ذلك قيم الانخفاض بدلاً من ذلك. يحتوي عنصر النتيجة على مخطط يمكن فحصه للعثور على موقع هذه الأعمدة في الصفيف. في هذه الحالة، سيكونان في موضعَي الفهرس 2 و3. يمكن قراءة هذه الفهارس من متغيّر لجعل الرمز البرمجي أسهل في الإدارة. يتم ضبط maxIntensity لخريطة الحرارة لعرض كثافة 20 عملية توقّف لكل بكسل كحد أقصى.

أضِف بعض المتغيّرات للسماح لك بتغيير الأعمدة التي تستخدمها لبيانات الخريطة الحرارية.

// Show query results as a Heatmap.
function doHeatMap(rows){
  let latCol = 2;
  let lngCol = 3;
  let heatmapData = [];
  if (heatmap!=null){
    heatmap.setMap(null);
  }
  for (let i = 0; i < rows.length; i++) {
      let f = rows[i].f;
      let coords = { lat: parseFloat(f[latCol].v), lng: parseFloat(f[lngCol].v) };
      let latLng = new google.maps.LatLng(coords);
      heatmapData.push(latLng);
  }
  heatmap = new google.maps.visualization.HeatmapLayer({
      data: heatmapData,
      maxIntensity: 20
  });
  heatmap.setMap(map);
}

في ما يلي خريطة حرارية توضّح توزيع عمليات التوصيل من جميع نقاط الاستلام القريبة من مبنى Empire State في عام 2016. يمكنك الاطّلاع على تجمّعات كبيرة (البقع الحمراء) لوجهات في وسط المدينة، لا سيما حول "تايمز سكوير"، فضلاً عن "الجادة الخامسة" بين "الشارع 23" و"الشارع 14". وتشمل المواقع الأخرى ذات الكثافة العالية التي لا تظهر في مستوى التكبير هذا مطارَي "لاغوارديا" و"جون إف كينيدي" و"مركز التجارة العالمي" و"باتري بارك".

Screen Shot 2017-05-09 at 10.40.01 AM.png

تصميم الخريطة الأساسية

عند إنشاء خريطة Google باستخدام Maps JavaScript API، يمكنك ضبط نمط الخريطة باستخدام عنصر JSON. بالنسبة إلى تصورات البيانات، قد يكون من المفيد كتم ألوان الخريطة. يمكنك إنشاء أنماط الخرائط وتجربتها باستخدام "معالج الأنماط" في Google Maps API على mapstyle.withgoogle.com.

يمكنك ضبط نمط الخريطة عند تهيئة عنصر خريطة، أو في أي وقت لاحق. في ما يلي كيفية إضافة نمط مخصّص في الدالة initMap():

function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
        center: {lat: 40.744593, lng: -73.990370}, // Manhattan, New York.
  zoom: 12,
  styles: [
    {
        "elementType": "geometry",
          "stylers": [
            {
              "color": "#f5f5f5"
            }
          ]
        },
        {
          "elementType": "labels.icon",
            "stylers": [
              {
                "visibility": "on"
              }
            ]
        },
        {
          "featureType": "water",
            "elementType": "labels.text.fill",
              "stylers": [
                {
                  "color": "#9e9e9e"
                }
              ]
        }
      ]
    });
  setUpDrawingTools();
}

يعرض نمط العيّنة أدناه خريطة بتدرّج الرمادي مع تصنيفات لنقاط الاهتمام.

[
  {
    "elementType": "geometry",
    "stylers": [
      {
        "color": "#f5f5f5"
      }
    ]
  },
  {
    "elementType": "labels.icon",
    "stylers": [
      {
        "visibility": "on"
      }
    ]
  },
  {
    "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": "poi",
    "elementType": "geometry",
    "stylers": [
      {
        "color": "#eeeeee"
      }
    ]
  },
  {
    "featureType": "poi",
    "elementType": "labels.text.fill",
    "stylers": [
      {
        "color": "#757575"
      }
    ]
  },
  {
    "featureType": "poi.park",
    "elementType": "geometry",
    "stylers": [
      {
        "color": "#e5e5e5"
      }
    ]
  },
  {
    "featureType": "poi.park",
    "elementType": "labels.text.fill",
    "stylers": [
      {
        "color": "#9e9e9e"
      }
    ]
  },
  {
    "featureType": "road",
    "elementType": "geometry",
    "stylers": [
      {
        "color": "#ffffff"
      }
    ]
  },
  {
    "featureType": "road.arterial",
    "elementType": "labels.text.fill",
    "stylers": [
      {
        "color": "#757575"
      }
    ]
  },
  {
    "featureType": "road.highway",
    "elementType": "geometry",
    "stylers": [
      {
        "color": "#dadada"
      }
    ]
  },
  {
    "featureType": "road.highway",
    "elementType": "labels.text.fill",
    "stylers": [
      {
        "color": "#616161"
      }
    ]
  },
  {
    "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": "#c9c9c9"
      }
    ]
  },
  {
    "featureType": "water",
    "elementType": "labels.text.fill",
    "stylers": [
      {
        "color": "#9e9e9e"
      }
    ]
  }
]

تقديم ملاحظات للمستخدم

على الرغم من أنّ BigQuery يعرض عادةً ردًا في غضون ثوانٍ، من المفيد أحيانًا أن نُظهر للمستخدم أنّه يتم تنفيذ إجراء ما أثناء تشغيل طلب البحث.

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

تشمل المعلومات التي يمكنك عرضها مدة طلب البحث وكمية البيانات التي تم إرجاعها وكمية البيانات التي تمت معالجتها.

أضِف بعض رموز HTML بعد الخريطة <div> لإنشاء لوحة في الصفحة تعرض عدد الصفوف التي يعرضها طلب البحث والوقت الذي استغرقه طلب البحث وكمية البيانات التي تمت معالجتها.

<div id="menu">
    <div id="stats">
        <h3>Statistics:</h3>
        <table>
            <tr>
                <td>Total Locations:</td><td id="rowCount"> - </td>
            </tr>
            <tr>
                <td>Query Execution:</td><td id="duration"> - </td>
            </tr>
            <tr>
                <td>Data Processed:</td><td id="bytes"> - </td>
            </tr>
        </table>
    </div>
</div>

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

#menu {
  position: absolute; 
  background: rgba(255, 255, 255, 0.8); 
  z-index: 1000; 
  top: 50px; 
  left: 10px; 
  padding: 15px;
}
#menu h1 {
  margin: 0 0 10px 0;
  font-size: 1.75em;
}
#menu div {
  margin: 5px 0px;
}

يمكن إضافة الرسم المتحرّك إلى الصفحة ولكن إخفاؤه إلى أن يصبح مطلوبًا، ويمكن استخدام بعض رموز JavaScript وCSS لإظهاره عند تشغيل مهمة BigQuery.

أضِف بعض رموز HTML لعرض رسم متحرك. يحتوي المجلد img في مستودع الرموز على ملف صورة باسم loader.gif.

<img id="spinner" src="img/loader.gif">

أضِف بعض CSS لتحديد موضع الصورة وإخفائها تلقائيًا إلى أن تحتاج إليها.

#spinner {
  position: absolute; 
  top: 50%; 
  left: 50%; 
  margin-left: -32px; 
  margin-top: -32px; 
  opacity: 0; 
  z-index: -1000;
}

أخيرًا، أضِف بعض JavaScript لتعديل لوحة الحالة وإظهار الرسم أو إخفائه عند تنفيذ طلب بحث. يمكنك استخدام الكائن response لتعديل اللوحة استنادًا إلى المعلومات المتاحة.

عند التحقّق من وظيفة حالية، هناك السمة response.statistics التي يمكنك استخدامها. عند اكتمال المهمة، يمكنك الوصول إلى السمتَين response.totalRows وresponse.totalBytesProcessed. من المفيد للمستخدم تحويل الملّي ثانية إلى ثوانٍ والبايت إلى غيغابايت لعرضها كما هو موضّح في نموذج الرمز البرمجي أدناه.

function updateStatus(response){
  if(response.statistics){
    let durationMs = response.statistics.endTime - response.statistics.startTime;
    let durationS = durationMs/1000;
    let suffix = (durationS ==1) ? '':'s';
    let durationTd = document.getElementById("duration");
    durationTd.innerHTML = durationS + ' second' + suffix;
  }
  if(response.totalRows){
    let rowsTd = document.getElementById("rowCount");
    rowsTd.innerHTML = response.totalRows;
  }
  if(response.totalBytesProcessed){
    let bytesTd = document.getElementById("bytes");
    bytesTd.innerHTML = (response.totalBytesProcessed/1073741824) + ' GB';
  }
}

استدعِ هذه الطريقة عندما يكون هناك ردّ على طلب checkJobStatus() وعندما يتم جلب نتائج طلب البحث. على سبيل المثال:

// Poll a job to see if it has finished executing.
function checkJobStatus(jobId){
  let request = gapi.client.bigquery.jobs.get({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response => {
    //Show progress to the user
    updateStatus(response);

    if (response.status.errorResult){
      // Handle any errors.
      console.log(response.status.error);
      return;
    }
    if (response.status.state == 'DONE'){
      // Get the results.
      clearTimeout(jobCheckTimer);
      getQueryResults(jobId);
      return;
    }
    // Not finished, check again in a moment.
    jobCheckTimer = setTimeout(checkJobStatus, 500, [jobId]); 
  });
}

// When a BigQuery job has completed, fetch the results.
function getQueryResults(jobId){
  let request = gapi.client.bigquery.jobs.getQueryResults({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response => {
    doHeatMap(response.result.rows);
    updateStatus(response);
  })
}

لتبديل الرسم المتحرّك، أضِف دالة للتحكّم في إمكانية ظهوره. ستؤدي هذه الدالة إلى تبديل مستوى عتامة أي عنصر HTML DOM يتم تمريره إليها.

function fadeToggle(obj){
    if(obj.style.opacity==1){
        obj.style.opacity = 0;
        setTimeout(() => {obj.style.zIndex = -1000;}, 1000);
    } else {
        obj.style.zIndex = 1000;
        obj.style.opacity = 1;
    }
}

أخيرًا، استدعِ هذه الطريقة قبل معالجة طلب بحث، وبعد أن تعود نتيجة طلب البحث من BigQuery.

يستدعي هذا الرمز الدالة fadeToggle عندما ينتهي المستخدم من رسم مستطيل.

drawingManager.addListener('rectanglecomplete', rectangle => {
  //show an animation to indicate that something is happening.
  fadeToggle(document.getElementById('spinner'));
  rectangleQuery(rectangle.getBounds());
});

بعد تلقّي ردّ الاستعلام، اتّصِل بالدالة fadeToggle() مرة أخرى لإخفاء الرسم المتحرّك.

// When a BigQuery job has completed, fetch the results.
function getQueryResults(jobId){
  let request = gapi.client.bigquery.jobs.getQueryResults({
    'projectId': billingProjectId,
    'jobId': jobId
  });
  request.execute(response => {
    doHeatMap(response.result.rows);
    //hide the animation.
    fadeToggle(document.getElementById('spinner'));
    updateStatus(response);
  })
}

يجب أن تبدو الصفحة على النحو التالي.

Screen Shot 2017-05-10 at 2.32.19 PM.png

يمكنك الاطّلاع على المثال الكامل في step8/map.html.

14. ملاحظات مهمة

عدد كبير جدًا من العلامات

إذا كنت تعمل مع جداول كبيرة جدًا، قد يعرض طلب البحث عددًا كبيرًا جدًا من الصفوف بحيث لا يمكن عرضها بكفاءة على الخريطة. يمكنك حصر النتائج بإضافة عبارة WHERE أو بيان LIMIT.

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

تحسين طلبات البحث

ستفحص خدمة BigQuery الجدول بأكمله مع كل طلب بحث. لتحسين استخدام حصة BigQuery، اختَر الأعمدة التي تحتاج إليها فقط في طلب البحث.

ستكون طلبات البحث أسرع إذا خزّنت خطوط الطول والعرض كأرقام عشرية بدلاً من سلاسل.

تصدير النتائج المثيرة للاهتمام

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

يُرجى مراعاة بنود خدمة Google Maps Platform. لمزيد من التفاصيل حول أسعار "منصة خرائط Google"، يُرجى الاطّلاع على المستندات على الإنترنت.

الاستفادة من المزيد من البيانات

تتوفّر في BigQuery عدة مجموعات بيانات عامة تتضمّن أعمدة خطوط الطول والعرض، مثل مجموعات بيانات سيارات الأجرة في نيويورك من 2009 إلى 2016 وبيانات رحلات Uber وLyft في نيويورك ومجموعة بيانات GDELT.

15. تهانينا!

نأمل أن يساعدك ذلك في البدء بسرعة في استخدام بعض طلبات البحث الجغرافية في جداول BigQuery لتتمكّن من رصد الأنماط وعرضها على "خريطة Google". نتمنّى لك تجربة ربط ناجحة.

ما هي الخطوات التالية؟

إذا أردت الاطّلاع على مزيد من المعلومات حول "منصة خرائط Google" أو BigQuery، يمكنك مراجعة الاقتراحات التالية.

يمكنك الاطّلاع على مقالة ما هو BigQuery لمعرفة المزيد عن خدمة مستودع البيانات من Google التي تعمل بدون خادم وتتّسم بإمكانية التوسّع بشكل كبير.

اطّلِع على دليل كيفية إنشاء تطبيق بسيط باستخدام BigQuery API.

راجِع دليل المطوّر الخاص بمكتبة الرسومات لمزيد من التفاصيل حول تفعيل تفاعل المستخدمين لرسم أشكال على "خريطة Google".

يمكنك الاطّلاع على طرق أخرى لعرض البيانات بشكل مرئي على خريطة Google.

راجِع دليل البدء لواجهة برمجة التطبيقات الخاصة بعميل JavaScript للتعرّف على المفاهيم الأساسية لاستخدام Client API للوصول إلى واجهات Google API الأخرى.