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

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

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

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

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

שירותי האינטרנט של ממשקי ה-API של מפות Google משתמשים בבקשות HTTP(S) לכתובות 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 או קידוד באחוזים.

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

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

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

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

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

כל התווים לקידודי התווים שמתאימים לכתובות 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 מוגבלות ל-16,384 תווים בכל שירותי האינטרנט של הפלטפורמה של מפות Google וממשקי API סטטיים לאינטרנט. ברוב השירותים שלרוב תתקרבו למגבלת התווים הזו. אבל, לפעמים חשוב לשים לב ששירותים מסוימים כוללים מספר פרמטרים שעשויים להוביל לכתובות URL ארוכות.

שימוש מנומס בממשקי API של Google

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

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

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

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

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

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

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

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

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

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

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

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

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

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

בחירת טקסט בהבעות

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

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

    37.4217550
    -122.0846330
    The secret message
    
בחירת טקסט
XPath Expression: '/WebServiceRequest/result[2]/message/text()'
בחירה:
    The secret message
    
בחירה רגישה להקשר
XPath Expression: '/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");
    }
  }
}