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

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

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

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

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

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

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

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

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

הערה: כל האפליקציות של Geocoding 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 שמקבלים מקלט של משתמש. לדוגמה, משתמש יכול להזין כתובת כמו "5th&Main St."‎ באופן כללי, צריך לבנות את כתובת ה-URL מהחלקים שלה, ולהתייחס לכל קלט של משתמש כתווים מילוליים.

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

שימוש הוגן ב-Google APIs

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

ניהול שגיאות וניסיונות חוזרים

למידע על קודי התגובה UNKNOWN_ERROR או OVER_QUERY_LIMIT מ-Geocoding API, קראו על ניהול שגיאות וניסיונות חוזרים.

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

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

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

לדוגמה, נניח שרוצים לשלוח את הבקשה הבאה ל-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 לתחילת הדקה, אפילו בין מכשירים שונים, במקום לפזר אותן באופן שווה לאורך זמן. אפליקציה שתוכננה בצורה לא טובה תייצר עלייה חדה בתנועה, פי 60 מהרמות הרגילות, בתחילת כל דקה.

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

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

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

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

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

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

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

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

כשמעבדים תגובות XML, צריך להשתמש בשפת שאילתות מתאימה כדי לבחור צמתים בתוך מסמך ה-XML, במקום להניח שהרכיבים נמצאים במיקומים מוחלטים בתוך תגי העיצוב של ה-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> בהקשר הנוכחי. באופן כללי, לא צריך לדאוג לגבי ההקשר, כי בדרך כלל מעבדים את התוצאות של שירות האינטרנט באמצעות ביטוי אחד.

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

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

סוג הביטוי
צומת שורש
ביטוי 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()" מציין חילוץ של טקסט מהצומת שצוין. לדוגמה, הביטוי "//formatted_address/text()" ב-XPath יחזיר את כל הטקסט בתוך רכיבי <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() ב-factory הזה כדי ליצור אובייקט 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");
    }
  }
}