המרת קובצי CSV לפורמט KML

Mano Marks, צוות Google Geo APIs
מרץ 2008

מטרה

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

אמנם דוגמאות הקוד במדריך הזה נמצאות ב-Python, אבל אפשר להתאים אותן לרוב שפות התכנות האחרות. המדריך הזה משתמש בקוד מכתובות קידוד גיאוגרפיות לשימוש ב-KML כדי להפוך כתובת לקואורדינטות של קווי אורך/רוחב. הוא גם משתמש ברכיב החדש <ExtendedData> של KML 2.2, והוא מנצל את תכונות ההבלטה של הבלונים המתוארים בהוספת נתונים מותאמים אישית. כתוצאה מכך, ה-KML שנוצר אינו נתמך כרגע במפות Google או באפליקציות אחרות צורכות KML, אבל אפשר להתאים את הקוד כדי לייצר KML תואם למפות.

נתונים לדוגמה

במדריך הזה משתמשים בקובץ google-addresses.csv כקובץ לדוגמה לדוגמה. הקובץ הזה כולל את כל הכתובות, מספרי הטלפון ומספרי הפקס של משרדי Google השונים בארה"ב. זהו הטקסט של הקובץ:

Office,Address1,Address2,Address3,City,State,Zip,Phone,Fax
Headquarters,1600 Amphitheatre Parkway,,,Mountain View,CA,94043,650-253-0000,650-253-0001
New York Sales & Engineering Office,76 Ninth Avenue,,,New York,NY,10011,212-565-0000,212-565-0001
Ann Arbor Sales Office,201 South Division Street,,,Ann Arbor,MI,48104,734-332-6500,734-332-6501
Atlanta Sales & Engineering Office,10 10th Street NE,,,Atlanta,GA,30309,404-487-9000,404-487-9001
Boulder Sales & Engineering Office,2590 Pearl St.,,,Boulder,CO,80302,303-245-0086,303-535-5592
Cambridge Sales & Engineering Office,5 Cambridge Center,,,Cambridge,MA,02142,617-682-3635,617-249-0199
Chicago Sales & Engineering Office,20 West Kinzie St.,,,Chicago,IL,60610,312-840-4100,312-840-4101
Coppell Sales Office,701 Canyon Drive,,,Coppell,TX,75019,214-451-4000,214-451-4001
Detroit Sales Office,114 Willits Street,,,Birmingham,MI,48009,248-351-6220,248-351-6227
Irvine Sales & Engineering Office,19540 Jamboree Road,,,Irvine,CA,92612,949-794-1600,949-794-1601
Pittsburgh Engineering Office,4720 Forbes Avenue,,,Pittsburgh,PA,15213,,
Santa Monica Sales & Engineering Office,604 Arizona Avenue,,,Santa Monica,CA,90401,310-460-4000,310-309-6840
Seattle Engineering Office,720 4th Avenue,,,Kirkland,WA,98033,425-739-5600,425-739-5601
Seattle Sales Office,501 N. 34th Street,,,Seattle,WA,98103,206-876-1500,206-876-1501
Washington D.C. Public Policy Office,1001 Pennsylvania Avenue NW,,,Washington,DC,20004,202-742-6520,

שימו לב שהשורה הראשונה היא סדרה של מחרוזות טקסט שמופרדות בפסיקים. כל פסיק חופף שדה. כל שורה כוללת אותו מספר של פסיקים. השורה הראשונה מכילה את שמות השדות לפי הסדר. לדוגמה, בלוק הטקסט הראשון בכל שורה הוא השדה 'Office', החלק השני "Address1" וכו'. Python יכול להפוך אותו לאוסף של dicts, שנקרא DictReader, שמאפשר לכם לעבור בין השורות. דגימת הקוד מסתמכת על הידיעה מראש של מבנה הנתונים, אבל אפשר להוסיף כמה גורמי handler בסיסיים כדי להעביר את מבנה השדה באופן דינמי.

ניתוח קובץ ה-CSV

המודול xml.dom.minidom של Python מספק כלים מעולים ליצירת מסמכי XML, ומכיוון ש-KML הוא XML, תוכלו להיעזר בו כמעט במדריך הזה. יוצרים רכיב עם createElement או createElementNS ומצרפים אותו לרכיב אחר עם appendChild. אלו השלבים לניתוח קובץ ה-CSV וליצירת קובץ KML.

  1. מייבאים את הפונקציה Geocoding_for_KML.py למודול שלכם.
  2. אתם יכולים ליצור קובץ DictReader עבור קובצי ה-CSV. DictReader הוא אוסף של dicts, אחד בכל שורה.
  3. יוצרים את המסמך באמצעות xml.dom.minidom.Document() של Python.
  4. יצירת רכיב הבסיס <kml> באמצעות createElementNS.
  5. להוסיף אותו למסמך.
  6. יצירת רכיב <Document> באמצעות createElement.
  7. הוסיפו אותו לרכיב <kml> באמצעות appendChild.
  8. בכל שורה, יוצרים רכיב <Placemark> ומצרפים אותו לרכיב <Document>.
  9. בכל עמודה בכל שורה, יוצרים אלמנט <ExtendedData> ומצרפים אותו לרכיב <Placemark> שיצרתם בשלב 8.
  10. יוצרים רכיב <Data> ומצרפים אותו לרכיב <ExtendedData>. צריך לתת לרכיב <Data> מאפיין של שם ולהקצות לו את הערך של שם העמודה באמצעות setAttribute.
  11. יוצרים רכיב <value> ומצרפים אותו לרכיב <Data>. יוצרים צומת טקסט ומקצים לו את הערך של העמודה, createTextNode. הוסיפו את צומת הטקסט לרכיב <value>.
  12. יוצרים רכיב <Point> ומצרפים אותו לרכיב <Placemark>. יוצרים רכיב <coordinates> ומצרפים אותו לרכיב <Point>.
  13. יש לחלץ את הכתובת מהשורה כך שהיא תהיה מחרוזת יחידה בפורמט הבא: כתובת1,כתובת2,עיר,מדינה,מיקוד. כלומר, השורה הראשונה היא 1600 Amphitheater Parkway,,Mountain View,CA,94043. אם יש פסיקים זה לצד זה, זה בסדר. שימו לב: לשם כך צריך ידע מוקדם על המבנה של קובץ ה-CSV, ואילו עמודות מהוות את הכתובת.
  14. כדאי לקודד את הכתובת באמצעות הקוד geocoding_for_KML.py כפי שמוסבר בכתובות הקידוד הגיאוגרפי לשימוש ב-KML. כך מתקבלת מחרוזת שהיא קו האורך וקו הרוחב של המיקום.
  15. צריך ליצור צומת טקסט ולהקצות לו את הערך של הקואורדינטות בשלב 14, ואז לצרף אותו לרכיב <coordinates>.
  16. כותבים את מסמך ה-KML בקובץ.
  17. אם מעבירים רשימה של שמות עמודות כארגומנטים לסקריפט, הסקריפט יוסיף רכיבים בסדר הזה. אם לא שינינו את סדר הרכיבים, נוכל להשתמש ב-dict.keys() כדי לייצר list. עם זאת, dict.keys() לא שומר את ההזמנה המקורית מהמסמך. כדי להשתמש בארגומנט הזה, צריך לעבור ברשימת שמות השדות כרשימה מופרדת בפסיקים:
    python csvtokml.py Office,Address1,Address2,Address3,City,State,Zip,Phone,Fax

קוד Python לדוגמה

הקוד לדוגמה ליצירת קובץ KML מקובץ CSV באמצעות Python 2.2 מוצג בהמשך. אפשר גם להוריד אותו כאן.


import geocoding_for_kml
import csv
import xml.dom.minidom
import sys


def extractAddress(row):
  # This extracts an address from a row and returns it as a string. This requires knowing
  # ahead of time what the columns are that hold the address information.
  return '%s,%s,%s,%s,%s' % (row['Address1'], row['Address2'], row['City'], row['State'], row['Zip'])

def createPlacemark(kmlDoc, row, order):
  # This creates a  element for a row of data.
  # A row is a dict.
  placemarkElement = kmlDoc.createElement('Placemark')
  extElement = kmlDoc.createElement('ExtendedData')
  placemarkElement.appendChild(extElement)
  
  # Loop through the columns and create a  element for every field that has a value.
  for key in order:
    if row[key]:
      dataElement = kmlDoc.createElement('Data')
      dataElement.setAttribute('name', key)
      valueElement = kmlDoc.createElement('value')
      dataElement.appendChild(valueElement)
      valueText = kmlDoc.createTextNode(row[key])
      valueElement.appendChild(valueText)
      extElement.appendChild(dataElement)
  
  pointElement = kmlDoc.createElement('Point')
  placemarkElement.appendChild(pointElement)
  coordinates = geocoding_for_kml.geocode(extractAddress(row))
  coorElement = kmlDoc.createElement('coordinates')
  coorElement.appendChild(kmlDoc.createTextNode(coordinates))
  pointElement.appendChild(coorElement)
  return placemarkElement

def createKML(csvReader, fileName, order):
  # This constructs the KML document from the CSV file.
  kmlDoc = xml.dom.minidom.Document()
  
  kmlElement = kmlDoc.createElementNS('http://earth.google.com/kml/2.2', 'kml')
  kmlElement.setAttribute('xmlns','http://earth.google.com/kml/2.2')
  kmlElement = kmlDoc.appendChild(kmlElement)
  documentElement = kmlDoc.createElement('Document')
  documentElement = kmlElement.appendChild(documentElement)

  # Skip the header line.
  csvReader.next()
  
  for row in csvReader:
    placemarkElement = createPlacemark(kmlDoc, row, order)
    documentElement.appendChild(placemarkElement)
  kmlFile = open(fileName, 'w')
  kmlFile.write(kmlDoc.toprettyxml('  ', newl = '\n', encoding = 'utf-8'))

def main():
  # This reader opens up 'google-addresses.csv', which should be replaced with your own.
  # It creates a KML file called 'google.kml'.
  
  # If an argument was passed to the script, it splits the argument on a comma
  # and uses the resulting list to specify an order for when columns get added.
  # Otherwise, it defaults to the order used in the sample.
  
  if len(sys.argv) >1: order = sys.argv[1].split(',')
  else: order = ['Office','Address1','Address2','Address3','City','State','Zip','Phone','Fax']
  csvreader = csv.DictReader(open('google-addresses.csv'),order)
  kml = createKML(csvreader, 'google-addresses.kml', order)
if __name__ == '__main__':
  main()

דוגמה ל-KML שנוצר

דוגמה של ה-KML שסקריפט זה יוצר מוצגת למטה. שימו לב שבחלק מהרכיבים<value> יש רק רווח לבן. הסיבה לכך היא שהשדה לא הכיל נתונים כלשהם. אפשר גם להוריד את הדוגמה המלאה כאן.

<?xml version="1.0" encoding="utf-8"?>
<kml xmlns="http://earth.google.com/kml/2.2">
  <Document>
    <Placemark>
      <ExtendedData>
        <Data name="Office">
          <value>
            Headquarters
          </value>
        </Data>
        <Data name="Address1">
          <value>
            1600 Amphitheater Parkway
          </value>
        </Data>
        <Data name="City">
          <value>
            Mountain View
          </value>
        </Data>
        <Data name="State">
          <value>
            CA
          </value>
        </Data>
        <Data name="Zip">
          <value>
            94043
          </value>
        </Data>
        <Data name="Phone">
          <value>
            650-253-0000
          </value>
        </Data>
        <Data name="Fax">
          <value>
            650-253-0001
          </value>
        </Data>
      </ExtendedData>
      <Point>
        <coordinates>
          -122.081783,37.423111
        </coordinates>
      </Point>
    </Placemark>
    ...

צילום מסך

בהמשך תוכל לקבל צילום מסך שמציג איך קובץ ה-KML נראה ב-Google Earth. מכיוון שכל אלמנט<Placemark> לא מכיל רכיב <BalloonStyle><text> ואין אלמנט <description>, ברירת המחדל של הבלון מתבססת על סגנון טבלה, והוא מסתמך על הרכיבים של <Data>.

צילום מסך של KML שנוצר על ידי הסקריפט הזה

התעניינות בקידוד גיאוגרפי

הדבר הוזכר ב"כתובות גיאוגרפיות לשימוש ב-KML", אך הוא חוזר על עצמו. בקשות הקידוד הגיאוגרפי שלכם יהיו כפופות לקצב השאילתות המקסימלי של המקודד ול-15,000 שאילתות ליום לפי כתובת ה-IP שלכם. בנוסף, קוד הסטטוס 620 יוחזר על ידי המקודד על ידי שליחת השאילתה מהר יותר מכפי שהוא ניתן לטיפול. (הרשימה המלאה של קודי הסטטוס מופיעה כאן). כדי לוודא שאתם לא שולחים שאילתות מהר מדי למקודד, אפשר לציין עיכוב בין כל בקשה לקידוד גיאוגרפי. תוכלו להגדיל את ההשהיה הזו בכל פעם שאתם מקבלים סטטוס 620, ולהשתמש בלולאת while כדי לוודא שאתם יוצרים בהצלחה קידוד גיאוגרפי של כתובת, לפני שתחזרו לכתובת הבאה. כלומר, אם קובץ ה-CSV שלכם גדול מאוד, יכול להיות שתצטרכו לשנות את הקוד של הקוד הגיאוגרפי, או לעקוב אחרי המהירות שבה אתם יוצרים סמנים ולהאט את קצב ההורדה.

סיכום

עכשיו אפשר להשתמש ב-Python כדי ליצור קובץ KML מקובץ CSV. קובץ ה-KML יפעל באמצעות הקוד שסופק רק ב-Google Earth. אפשר לשנות אותה כך שתפעל במפות וגם ב-Earth באמצעות <description> במקום <ExtendedData>. בנוסף, קל להמיר את דוגמת הקוד הזו לשפות תכנות אחרות שמספקות תמיכה ב-XML.

עכשיו, לאחר שסיימתם להמיר את כל קובצי ה-CSV ל-KML, כדאי לכם לעיין במאמרים אחרים בפורמט KML, כמו שימוש ב-PHP ו-MySQL כדי ליצור KML, ובמאמר במדריך למפתחים של Google בנושא ExtendedData, הוספת נתונים מותאמים אישית.

חזרה למעלה