שיטות מומלצות לשימוש בשירותי האינטרנט של Directions API

שירותי האינטרנט של פלטפורמת מפות Google הם אוסף של ממשקי HTTP לשירותי Google שמספקים נתונים גיאוגרפיים לאפליקציות המפות שלכם.

במדריך הזה מתוארות כמה שיטות נפוצות שיעזרו לכם להגדיר את הבקשות של שירות האינטרנט ולעבד את התשובות מהשירות. במדריך למפתחים תוכלו למצוא מסמכי עזרה מלאים בנושא Directions API.

מהו שירות אינטרנט?

שירותי האינטרנט של הפלטפורמה של מפות Google הם ממשק לבקשת נתונים מ-Maps API משירותים חיצוניים ולהשתמש בנתונים באפליקציות של מפות Google. השירותים האלה מיועדים לשימוש בשילוב עם מפה, בהתאם להגבלות הרישיון שמפורטות בתנאים ובהגבלות של פלטפורמת מפות Google.

שירותי האינטרנט של Maps API משתמשים בבקשות HTTP(S) לכתובות URL ספציפיות, ומעבירים לשירותים פרמטרים של כתובות URL ו/או נתוני POST בפורמט JSON כארגומנטים. באופן כללי, השירותים האלה מחזירים נתונים בגוף התגובה כ-JSON או כ-XML לניתוח ו/או לעיבוד על ידי האפליקציה.

בקשה אופיינית ל-Directions API היא בדרך כלל בפורמט הבא:

https://maps.googleapis.com/maps/api/directions/output?parameters

כאשר output מציין את פורמט התשובה (בדרך כלל json או xml).

הערה: כל האפליקציות של Directions API מחייבות אימות. מידע נוסף על פרטי כניסה לאימות

גישה ל-SSL/TLS

צריך להשתמש ב-HTTPS בכל הבקשות לפלטפורמה של מפות Google שמשתמשות במפתחות API או מכילות נתוני משתמשים. יכול להיות שנדחה בקשות שנשלחות ב-HTTP ומכילות מידע אישי רגיש.

יצירת כתובת URL תקינה

יכול להיות שתחשובו שכתובת URL "תקינה" היא מובנת מאליו, אבל זה לא בדיוק המצב. לדוגמה, כתובת URL שמזינים בסרגל הכתובות בדפדפן עשויה להכיל תווים מיוחדים (למשל "上海+中國"). הדפדפן צריך לתרגם את התווים האלה באופן פנימי לקידוד אחר לפני ההעברה. באופן דומה, כל קוד שיוצר או מקבל קלט ב-UTF-8 עשוי להתייחס לכתובות URL עם תווים ב-UTF-8 כ'תקינות', אבל יהיה עליו גם לתרגם את התווים האלה לפני השליחה שלהם לשרת אינטרנט. התהליך הזה נקרא קידוד כתובות URL או קידוד לפי אחוזים.

תווים מיוחדים

אנחנו צריכים לתרגם תווים מיוחדים כי כל כתובות ה-URL צריכות לעמוד בתחביר שצוין במפרט של מזהה המשאב האחיד (URI). פירוש הדבר הוא שכתובות URL חייבות להכיל רק קבוצת משנה מיוחדת של תווים מסוג ASCII: הסמלים האלפאנומריים המוכרים, ותווים ספציפיים ששמורים לשימוש בתור תווים של בקרה בכתובות URL. התווים האלה מפורטים בטבלה הבאה:

סיכום של תווים חוקיים בכתובות URL
אפשר להתחיל?תוויםשימוש בכתובת URL
אלפאנומרי a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 מחרוזות טקסט, שימוש בסכימה (http), יציאה (8080) וכו'.
לא שמור - _ . ~ מחרוזות טקסט
בוצעה הזמנה ! * ' ( ) ; : @ & = + $ , / ? % # [ ] תווי בקרה ו/או מחרוזות טקסט

כשיוצרים כתובת URL תקפה, צריך לוודא שהיא מכילה רק את התווים שמופיעים בטבלה. בדרך כלל, התאמת כתובת URL לשימוש בקבוצת התווים הזו מובילה לשתי בעיות: אחת של השמטה ואחת של החלפה:

  • התווים שרוצים לטפל בהם לא נמצאים בקבוצה שלמעלה. לדוגמה, תווים בשפות זרות, כמו 上海+中國, צריך לקודד באמצעות התווים שלמעלה. לפי הסכמה פופולרית, רווחים (שאסור להשתמש בהם בכתובות URL) מיוצגים לעיתים קרובות גם באמצעות התו '+'.
  • התווים נמצאים בקבוצה שלמעלה בתור תווים שמורים, אבל צריך להשתמש בהם באופן מילולי. לדוגמה, התו ? משמש בכתובות URL כדי לציין את תחילת מחרוזת השאילתה. אם רוצים להשתמש במחרוזת '? and the Mysterions', צריך לקודד את התו '?'.

כל התווים שצריך לקודד בהתאמה לכתובות URL מקודדים באמצעות התו '%' וערך הקסדצימלי בן שני תווים שתואם לתו ה-UTF-8 שלהם. לדוגמה, 上海+中國 ב-UTF-8 יותאם לקידודי התווים שמתאימים לכתובות URL באופן הבא: %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. המחרוזת ? and the Mysterians יותאמו לקידודי התווים שמתאימים לכתובות URL באופן הבא: %3F+and+the+Mysterians או %3F%20and%20the%20Mysterians.

תווים נפוצים שצריך לקודד

דוגמאות לתוים נפוצים שצריך לקודד:

תו לא בטוח ערך מקודד
רווח %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

לפעמים קשה להמיר כתובת URL שמקבלים מהקלדה של משתמש. לדוגמה, משתמש יכול להזין כתובת כמו 'רחוב 5 ו-Main'. באופן כללי, צריך ליצור את כתובת ה-URL מהחלקים שלה, ולתייחס לכל קלט של משתמש בתור תווים מילוליים.

בנוסף, כתובות URL מוגבלות ל-16,384 תווים בכל שירותי האינטרנט של הפלטפורמה של מפות Google וממשקי ה-API הסטטיים של האינטרנט. ברוב השירותים, מגבלת התווים הזו תהיה גדולה מדי. עם זאת, חשוב לזכור שלשירותים מסוימים יש כמה פרמטרים שעשויים לגרום לכתובות URL ארוכות.

שימוש מנומס ב-Google APIs

לקוחות API שתוכננו בצורה לקויה עלולים להעמיס יותר מהנדרש על האינטרנט ועל שרתי Google. בקטע הזה מפורטות כמה שיטות מומלצות ללקוחות של ממשקי ה-API. כדאי לפעול לפי השיטות המומלצות האלה כדי למנוע חסימה של האפליקציה עקב שימוש לרעה לא מכוון ב-API.

השהיה מעריכית לפני ניסיון חוזר (exponential backoff)

במקרים נדירים, יכול להיות משהו השתבש בטיפול בבקשה. יכול להיות שתקבלו קוד תגובה HTTP‏ 4XX או 5XX, או שפשוט החיבור ל-TCP נכשל במקום כלשהו בין הלקוח שלכם לשרת של Google. לרוב כדאי לנסות שוב את הבקשה, כי ייתכן שהבקשה הבאה תצליח אם הבקשה המקורית נכשלה. עם זאת, חשוב לא לשלוח בקשות שוב ושוב לשרתים של Google. התנהגות הלולאה הזו עלולה לגרום לעומס ברשת בין הלקוח ל-Google, וכתוצאה מכך לבעיות אצל גורמים רבים.

גישה טובה יותר היא לנסות שוב עם עיכובים הולכים וגדלים בין הניסיונות. בדרך כלל, העיכוב גדל לפי גורם כפול בכל ניסיון, גישה שנקראת השהיה מעריכית לפני ניסיון חוזר.

לדוגמה, נניח שאפליקציה רוצה לשלוח את הבקשה הבאה ל-Time Zone API:

https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510&timestamp=1331161200&key=YOUR_API_KEY

בדוגמה הבאה ב-Python מוסבר איך לשלוח את הבקשה עם השהיה מעריכית לפני ניסיון חוזר (exponential backoff):

import json
import time
import urllib.error
import urllib.parse
import urllib.request

# The maps_key defined below isn't a valid Google Maps API key.
# You need to get your own API key.
# See https://developers.google.com/maps/documentation/timezone/get-api-key
API_KEY = "YOUR_KEY_HERE"
TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json"


def timezone(lat, lng, timestamp):

    # Join the parts of the URL together into one string.
    params = urllib.parse.urlencode(
        {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,}
    )
    url = f"{TIMEZONE_BASE_URL}?{params}"

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 5  # Set the maximum retry delay to 5 seconds.

    while True:
        try:
            # Get the API response.
            response = urllib.request.urlopen(url)
        except urllib.error.URLError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.load(response)

            if result["status"] == "OK":
                return result["timeZoneId"]
            elif result["status"] != "UNKNOWN_ERROR":
                # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
                # ZERO_RESULTS. There is no point retrying these requests.
                raise Exception(result["error_message"])

        if current_delay > max_delay:
            raise Exception("Too many retry attempts.")

        print("Waiting", current_delay, "seconds before retrying.")

        time.sleep(current_delay)
        current_delay *= 2  # Increase the delay each time we retry.


if __name__ == "__main__":
    tz = timezone(39.6034810, -119.6822510, 1331161200)
    print(f"Timezone: {tz}")

חשוב גם לוודא שאין קוד ניסיון חוזר גבוה יותר בשרשרת הקריאות של האפליקציה, שגורם לבקשות חוזרות ברצף מהיר.

בקשות מסונכרנות

מספר גדול של בקשות מסונכרנות לממשקי ה-API של Google יכול להיראות כמו התקפת מניעת שירות מבוזרת (DDoS) על התשתית של Google, ולכן המערכת עשויה לטפל בהן בהתאם. כדי למנוע זאת, צריך לוודא שבקשות ה-API לא מסונכרנות בין לקוחות.

לדוגמה, ניקח אפליקציה שמציגה את השעה באזור הזמן הנוכחי. סביר להניח שהאפליקציה הזו תגדיר אזעקה במערכת ההפעלה של הלקוח שתעיר אותו בתחילת הדקה כדי לעדכן את השעה המוצגת. האפליקציה לא צריכה לבצע קריאות API במסגרת העיבוד שמשויך להתראה הזו.

שליחת קריאות API בתגובה להתראה קבועה היא לא מומלצת, כי כתוצאה מכך קריאות ה-API מסונכרנות עם תחילת הדקה, גם בין מכשירים שונים, במקום להיות מפוזרות באופן שווה לאורך זמן. אפליקציה שתוכננה בצורה גרועה ותבצע את הפעולה הזו תיצור עלייה חדה בנפח התנועה בשישית מרמות התנועה הרגילות בתחילת כל דקה.

במקום זאת, אפשרות טובה אחת היא להגדיר שעון מעורר שני לשעה שנבחרה באופן אקראי. כשהתזכורת השנייה מופעלת, האפליקציה קוראת לכל ממשקי ה-API שנדרשים לה ושומרת את התוצאות. כשהאפליקציה רוצה לעדכן את התצוגה שלה בתחילת הדקה, היא משתמשת בתוצאות ששמורות מראש במקום לבצע קריאה חוזרת ל-API. הגישה הזו מאפשרת לפזר את הקריאות ל-API באופן שווה לאורך זמן. בנוסף, הקריאות ל-API לא מעכבות את העיבוד בזמן שהתצוגה מתעדכנת.

בנוסף להתחלת הדקה, יש עוד זמנים נפוצים לסנכרון שצריך להיזהר לא לטרגט, כמו תחילת השעה ותחילת כל יום בחצות.

עיבוד התשובות

בקטע הזה נסביר איך לחלץ את הערכים האלה באופן דינמי מתשובות של שירותי אינטרנט.

שירותי האינטרנט של מפות Google מספקים תשובות שקל להבין, אבל הם לא ממש ידידותיים למשתמש. כשמבצעים שאילתות, במקום להציג קבוצת נתונים, בדרך כלל רוצים לחלץ כמה ערכים ספציפיים. באופן כללי, מומלץ לנתח את התשובות משירות האינטרנט ולחלץ רק את הערכים שמעניינים אתכם.

תוכנית הניתוח שבה משתמשים תלויה בפורמט הפלט: XML או JSON. תגובות JSON, שכבר נמצאות בפורמט של אובייקטים של JavaScript, יכולות לעבור עיבוד ב-JavaScript עצמו בצד הלקוח. כדי לטפל בתגובות XML, צריך להשתמש במעבד XML ובשפת שאילתות XML כדי לטפל ברכיבים בפורמט XML. אנחנו משתמשים ב-XPath בדוגמאות הבאות, כי יש תמיכה רחבה בו בספריות לעיבוד XML.

עיבוד XML באמצעות XPath

XML הוא פורמט מידע מובנה יחסית מתקדם המשמש לחילופי נתונים. הוא לא קל כמו JSON, אבל הוא מספק תמיכה בשפות נוספות וכלים חזקים יותר. לדוגמה, הקוד לעיבוד XML ב-Java מוטמע בחבילות javax.xml.

כשמעובדים תגובות XML, צריך להשתמש בשפת שאילתות מתאימה לבחירת צמתים במסמך ה-XML, במקום להניח שהרכיבים נמצאים במיקומים מוחלטים בתוך ה-Markup של ה-XML. XPath הוא תחביר שפה לתיאורים ייחודיים של צמתים ואלמנטים במסמך XML. ביטויי XPath מאפשרים לזהות תוכן ספציפי במסמך התגובה בפורמט XML.

ביטויים של XPath

מומלץ להכיר את XPath כדי לפתח סכימה חזקה לניתוח. בקטע הזה נסביר איך רכיבים במסמך XML מנותבים באמצעות XPath, וכך תוכלו לטפל בכמה רכיבים וליצור שאילתות מורכבות.

ב-XPath נעשה שימוש בביטויים כדי לבחור רכיבים במסמך XML, באמצעות תחביר שדומה לזה שמשמש לנתיבי ספריות. הביטויים האלה מזהים רכיבים בעץ של מסמך XML, שהוא עץ היררכי שדומה לעץ של DOM. באופן כללי, ביטויים של XPath הם 'גרדיים', כלומר הם יתאימו לכל הצמתים שתואמים לקריטריונים שצוינו.

נשתמש בקוד ה-XML המופשט הבא כדי להמחיש את הדוגמאות שלנו:

<WebServiceResponse>
 <status>OK</status>
 <result>
  <type>sample</type>
  <name>Sample XML</name>
  <location>
   <lat>37.4217550</lat>
   <lng>-122.0846330</lng>
  </location>
 </result>
 <result>
  <message>The secret message</message>
 </result>
</WebServiceResponse>

בחירת צומת בביטויים

בחירות XPath בוחרות צומתים. צומת הבסיס כולל את כל המסמך. בוחרים את הצומת הזה באמצעות הביטוי המיוחד /. חשוב לזכור שהצומת ברמה הבסיסית הוא לא הצומת ברמה העליונה של מסמך ה-XML. למעשה, הוא נמצא ברמה אחת מעל הרכיב ברמה העליונה הזה וכולל אותו.

צמתים של רכיבים מייצגים את הרכיבים השונים בעץ המסמך של ה-XML. לדוגמה, רכיב <WebServiceResponse> מייצג את הרכיב ברמה העליונה שהוחזר בשירות לדוגמה שלמעלה. אפשר לבחור צמתים ספציפיים באמצעות נתיבים מוחלטים או יחסיים, שמסומנים על ידי נוכחות או היעדרות של התו / בתחילת הנתיב.

  • נתיב מוחלט: הביטוי /WebServiceResponse/result בוחר את כל הצמתים מסוג <result> שהם צאצאים של הצומת <WebServiceResponse>. (שימו לב ששני הרכיבים האלה יורדים מצומת הבסיס /).
  • נתיב יחסי מההקשר הנוכחי: הביטוי result יתאים לכל רכיבי <result> בהקשר הנוכחי. באופן כללי, אין צורך לדאוג להקשר, כי בדרך כלל מעבדים את תוצאות שירותי האינטרנט באמצעות ביטוי יחיד.

אפשר להוסיף לכל אחד מהביטויים האלה נתיב של תו כללי לחיפוש, שמסומן בקו נטוי כפול ("//"). התו הכללי לחיפוש הזה מציין שיכולים להתאים אפס רכיבים או יותר בנתיב שבין הביטויים. לדוגמה, הביטוי XPath‏ //formatted_address יתאים לכל הצמתים עם השם הזה במסמך הנוכחי. הביטוי //viewport//lat יתאים לכל הרכיבים מסוג <lat> שאפשר לעקוב אחריהם <viewport> בתור הורה.

כברירת מחדל, ביטויי XPath תואמים לכל הרכיבים. כדי להגביל את הביטוי כך שיתאים לרכיב מסוים, צריך לספק תנאי שמוקף בסוגריים מרובעים ([]). לדוגמה, ביטוי ה-XPath /GeocodeResponse/result[2] תמיד מחזיר את התוצאה השנייה.

סוג הביטוי
צומת בסיס
ביטוי XPath:  "/"
בחירה:
    <WebServiceResponse>
     <status>OK</status>
     <result>
      <type>sample</type>
      <name>Sample XML</name>
      <location>
       <lat>37.4217550</lat>
       <lng>-122.0846330</lng>
      </location>
     </result>
     <result>
      <message>The secret message</message>
     </result>
    </WebServiceResponse>
    
נתיב מוחלט
ביטוי XPath:  "/WebServiceResponse/result"
בחירה:
    <result>
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    </result>
    <result>
     <message>The secret message</message>
    </result>
    
נתיב עם תו כללי לחיפוש
ביטוי XPath:  "/WebServiceResponse//location"
בחירה:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
נתיב עם תנאי
ביטוי XPath:  "/WebServiceResponse/result[2]/message"
בחירה:
    <message>The secret message</message>
    
כל הצאצאים הישירים של result הראשון
ביטוי XPath:  "/WebServiceResponse/result[1]/*"
בחירה:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
הערך של name ב-result שהטקסט של type הוא 'sample'.
ביטוי XPath:  "/WebServiceResponse/result[type/text()='sample']/name"
בחירה:
    Sample XML
    

חשוב לציין שכאשר בוחרים רכיבים, בוחרים צמתים, ולא רק את הטקסט שבתוך האובייקטים האלה. באופן כללי, כדאי לבצע איטרציה על כל הצמתים שתואמים ולחלץ את הטקסט. אפשר גם להתאים צמתים של טקסט ישירות. מידע נוסף זמין בקטע צמתים של טקסט בהמשך.

שימו לב ש-XPath תומך גם בצמתים של מאפיינים. עם זאת, כל שירותי האינטרנט של מפות Google מספקים רכיבים ללא מאפיינים, ולכן אין צורך להתאים מאפיינים.

בחירת טקסט בביטויים

טקסט במסמך XML מצוין בביטויי XPath באמצעות אופרטור text node. האופרטור text() מציין חילוץ טקסט מהצומת שצוין. לדוגמה, ביטוי ה-XPath //formatted_address/text() יחזיר את כל הטקסט בתוך רכיבי <formatted_address>.

סוג הביטוי
כל צמתי הטקסט (כולל רווחים)
ביטוי XPath:  "//text()"
בחירה:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
בחירת טקסט
ביטוי XPath:  "/WebServiceRequest/result[2]/message/text()"
בחירה:
    The secret message
    
בחירת הקשר
ביטוי XPath:  "/WebServiceRequest/result[type/text() = 'sample']/name/text()"
בחירה:
    Sample XML
    

לחלופין, אפשר להעריך ביטוי ולהחזיר קבוצה של צמתים, ואז לבצע איטרציה על 'קבוצת הצמתים' הזו כדי לחלץ את הטקסט מכל צומת. אנחנו משתמשים בגישה הזו בדוגמה הבאה.

מידע נוסף על XPath זמין במפרט XPath של W3C.

הערכה של XPath ב-Java

ל-Java יש תמיכה רחבה בניתוח XML ובשימוש בביטויי XPath בחבילה javax.xml.xpath.*. לכן, בקוד לדוגמה בקטע הזה נעשה שימוש ב-Java כדי להמחיש איך לטפל ב-XML ולנתח נתונים מתשובות של שירותי XML.

כדי להשתמש ב-XPath בקוד Java, קודם צריך ליצור מופע של XPathFactory ולקרוא ל-newXPath() במפעל הזה כדי ליצור אובייקט XPath . לאחר מכן, אפשר לעבד את הביטויים של ה-XML ו-XPath שהועברו באמצעות השיטה evaluate().

כשמבצעים הערכה של ביטויי XPath, חשוב לבצע איטרציה על כל 'קבוצות הצמתים' האפשריות שעשויות להוחזר. מאחר שהתוצאות האלה מוחזרות כצמתים של DOM בקוד Java, צריך לתעד ערכים מרובים כאלה באובייקט NodeList ולבצע איטרציה על האובייקט הזה כדי לחלץ טקסט או ערכים מהצמתים האלה.

הקוד הבא מדגים איך יוצרים אובייקט XPath, מקצים לו XML וביטוי XPath ומעריכים את הביטוי כדי להדפיס את התוכן הרלוונטי.

import org.xml.sax.InputSource;
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.io.*;

public class SimpleParser {

  public static void main(String[] args) throws IOException {

	XPathFactory factory = XPathFactory.newInstance();

    XPath xpath = factory.newXPath();

    try {
      System.out.print("Web Service Parser 1.0\n");

      // In practice, you'd retrieve your XML via an HTTP request.
      // Here we simply access an existing file.
      File xmlFile = new File("XML_FILE");

      // The xpath evaluator requires the XML be in the format of an InputSource
	  InputSource inputXml = new InputSource(new FileInputStream(xmlFile));

      // Because the evaluator may return multiple entries, we specify that the expression
      // return a NODESET and place the result in a NodeList.
      NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET);

      // We can then iterate over the NodeList and extract the content via getTextContent().
      // NOTE: this will only return text for element nodes at the returned context.
      for (int i = 0, n = nodes.getLength(); i < n; i++) {
        String nodeString = nodes.item(i).getTextContent();
        System.out.print(nodeString);
        System.out.print("\n");
      }
    } catch (XPathExpressionException ex) {
	  System.out.print("XPath Error");
    } catch (FileNotFoundException ex) {
      System.out.print("File Error");
    }
  }
}