מרץ 2008
מטרה
במדריך הזה מוסבר איך ליצור KML מנתוני ערכים מופרדים בפסיקים (CSV) באמצעות Python. נתוני CSV הם אחד מפורמטי הקבצים הנפוצים ביותר בשימוש כיום. ברוב הגיליונות האלקטרוניים ומסדי הנתונים אפשר לקרוא ולכתוב קובצי CSV. הפורמט הפשוט שלו מאפשר עריכה בכלי לעריכת טקסט. בשפות תכנות רבות, כמו Python, יש ספריות מיוחדות לקריאה ולכתיבה של קובצי CSV. לכן, הוא מתאים מאוד להעברת כמויות גדולות של נתונים.
דוגמאות הקוד במדריך הזה הן ב-Python, אבל אפשר להתאים אותן לרוב שפות התכנות האחרות. במדריך הזה נעשה שימוש בקוד מתוך קידוד גאוגרפי של כתובות לשימוש ב-KML כדי להמיר כתובת לקואורדינטות של קו אורך וקו רוחב. הוא גם משתמש ברכיב <ExtendedData>
החדש של KML 2.2, ונהנה מתבניות הבלונים שמפורטות במאמר הוספת נתונים בהתאמה אישית. לכן, קובץ ה-KML שנוצר לא נתמך כרגע במפות Google או באפליקציות אחרות שצורכות KML, אבל אפשר להתאים את הקוד כדי ליצור קובץ KML שתואם למפות Google.
נתונים לדוגמה
במדריך הזה, משתמשים בקובץ google-addresses.csv כקובץ 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
שמאפשר לכם
לעבור על כל שורה. בדוגמת הקוד הזו, אתם צריכים לדעת מראש את מבנה הנתונים, אבל אפשר להוסיף כמה פונקציות בסיסיות לטיפול בנתונים כדי להעביר את מבנה השדה באופן דינמי.
ניתוח קובץ ה-CSV
מודול xml.dom.minidom
של Python מספק כלים מצוינים ליצירת מסמכי XML, ומכיוון ש-KML הוא XML, תשתמשו בו הרבה במדריך הזה. יוצרים רכיב עם createElement
או createElementNS
, ומוסיפים אותו לרכיב אחר עם appendChild
.
אלה השלבים לניתוח קובץ ה-CSV וליצירת קובץ KML.
- מייבאים את geocoding_for_kml.py למודול.
- יוצרים
DictReader
לקובצי ה-CSV. DictReader
הוא אוסף שלdicts
, אחד לכל שורה. - יוצרים את המסמך באמצעות
xml.dom.minidom.Document()
של Python. - יוצרים את רכיב הבסיס
<kml>
באמצעותcreateElementNS.
- מצרפים אותו למסמך
.
- יוצרים רכיב
<Document>
באמצעותcreateElement
. - מוסיפים אותו לרכיב
<kml>
element באמצעותappendChild
. - לכל שורה, יוצרים רכיב
<Placemark>
ומצרפים אותו לרכיב<Document>
. - לכל עמודה בכל שורה, יוצרים רכיב
<ExtendedData>
ומצרפים אותו לרכיב<Placemark>
שנוצר בשלב 8. - יוצרים רכיב
<Data>
ומצרפים אותו לרכיב<ExtendedData>
נותנים לאלמנט<Data>
מאפיין name, ומקצים לו את הערך של שם העמודה באמצעותsetAttribute
. - יוצרים רכיב
<value>
ומצרפים אותו לרכיב<Data>
. יוצרים צומת טקסט ומקצים לו את הערך של העמודה באמצעותcreateTextNode
. מצרפים את צומת הטקסט לרכיב<value>
. - יוצרים רכיב
<Point>
ומצרפים אותו לרכיב<Placemark>
. יוצרים רכיב<coordinates>
ומצרפים אותו לרכיב<Point>
. - תחלץ את הכתובת מהשורה כך שהיא תהיה מחרוזת אחת בפורמט הזה: Address1,Address2,City,State,Zip. לכן השורה הראשונה תהיה
1600 Amphitheater Parkway,,Mountain View,CA,94043
. זה בסדר אם יש פסיקים צמודים. הערה: כדי לעשות את זה, צריך ידע מוקדם במבנה של קובץ ה-CSV ובאילו עמודות מופיעה הכתובת. - מקודדים את הכתובת באמצעות הקוד geocoding_for_kml.py שמוסבר במאמר קידוד גאוגרפי של כתובות לשימוש ב-KML. הפונקציה מחזירה מחרוזת עם קו האורך וקו הרוחב של המיקום.
- יוצרים צומת טקסט ומקצים לו את הערך של הקואורדינטות משלב 14, ואז מוסיפים אותו לרכיב
<coordinates>
. - כותבים את מסמך ה-KML לקובץ.
- אם מעבירים רשימה של שמות עמודות כארגומנטים לסקריפט, הסקריפט יוסיף את הרכיבים לפי הסדר הזה. אם לא היה לנו חשוב הסדר של הרכיבים, יכולנו להשתמש ב-
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 aelement 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>
,<Placemark>
הבלון מוגדר כברירת מחדל לסגנון טבלה, על סמך רכיבי <Data>
.

שיקולים לגבי המרת כתובות לקואורדינטות (geocoding)
הנושא הזה הוזכר במאמר 'גיאו-קידוד של כתובות לשימוש ב-KML', אבל חשוב לחזור עליו. בקשות הגיאו-קידוד שלכם יהיו כפופות למגבלת קצב השאילתות המקסימלי של הגיאו-קודר ול-15,000 שאילתות ביום על סמך כתובת ה-IP שלכם. בנוסף, אם תשלחו שאילתה לקידוד הגאוגרפי מהר יותר ממה שהוא יכול לטפל בה, הוא יחזיר קוד סטטוס 620
. (רשימה מלאה של קודי סטטוס זמינה כאן).
כדי לוודא שלא תשלחו שאילתות מהר מדי לגיאוקודר, תוכלו לציין השהיה בין כל בקשת גיאוקוד. אפשר להגדיל את העיכוב הזה בכל פעם שמקבלים סטטוס 620
, ולהשתמש בלולאה while
כדי לוודא שבוצע גיאו-קידוד של כתובת לפני שממשיכים לכתובת הבאה. המשמעות היא שאם קובץ ה-CSV שלכם גדול מאוד, יכול להיות שתצטרכו לשנות את קוד הגיאו-קידוד, או לעקוב אחרי המהירות שבה אתם יוצרים את סמני המקום ולהאט אותה אם אתם יוצרים אותם מהר מדי.
סיכום
עכשיו אפשר להשתמש ב-Python כדי ליצור קובץ KML מקובץ CSV. באמצעות הקוד שסופק, קובץ ה-KML יפעל רק ב-Google Earth. אפשר לשנות את הקוד כך שיפעל גם במפות Google וגם ב-Google Earth באמצעות <description>
במקום <ExtendedData>
.
קל גם להמיר את הקוד לדוגמה הזה לכל שפת תכנות אחרת שתומכת ב-XML.
אחרי שמסיימים להמיר את כל קובצי ה-CSV ל-KML, אפשר לעיין במאמרים נוספים בנושא KML, כמו שימוש ב-PHP וב-MySQL ליצירת KML ובמאמר במדריך למפתחים של Google בנושא ExtendedData, הוספת נתונים בהתאמה אישית.