2008 年 3 月
目標
本教學課程概略說明使用 Python 從逗號分隔值 (CSV) 資料建立 KML 的基本概念。CSV 資料是當今最常用的檔案格式之一。大多數試算表和資料庫都可以讀取及寫入 CSV 檔案。您可以在文字編輯器中編輯簡單的格式。許多程式設計語言 (例如 Python) 都有特殊的程式庫,可供讀取和寫入 CSV 檔案。因此,它是交換大量資料的極佳媒介。
雖然本教學課程中的程式碼範例是以 Python 編寫而成,但可適應大多數其他程式設計語言。本教學課程使用 KML 中使用的地理編碼地址,將地址轉換成經緯度座標。此外還使用 KML 2.2 的新 <ExtendedData>
元素,並利用新增自訂資料中說明的熱氣裁剪範本。因此,Google 地圖或其他運用 KML 的應用程式目前不支援目前產生的 KML,但該程式碼可進行調整,以便產生與 Google 地圖相容的 KML。
範例資料
在本教學課程中,請使用 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 檔案
Python 的 xml.dom.minidom
模組提供用於建立 XML 文件的強大工具,且由於 KML 是 XML,因此在本教學課程中,您將使用這類工具。您可以使用 createElement
或 createElementNS
建立元素,並用 appendChild
附加至其他元素。以下是剖析 CSV 檔案並建立 KML 檔案的步驟。
- 將 Geocoding_for_KML.py 匯入模組。
- 為 CSV 檔案建立
DictReader
。DictReader
是dicts
的集合,每一列都有一個。 - 使用 Python 的
xml.dom.minidom.Document()
建立文件。 - 使用
createElementNS.
建立根<kml>
元素 - 請將其附加到文件
中。
- 使用
createElement
建立<Document>
元素。 - 使用
appendChild
將其附加到<kml>
元素。 - 為每個資料列建立
<Placemark>
元素,然後將該元素附加到<Document>
元素。 - 針對每一個資料列,為每個資料列建立
<ExtendedData>
元素,並將其附加到您在步驟 8 建立的<Placemark>
元素。 - 建立
<Data>
元素,並將其附加至<ExtendedData>
元素。為<Data>
元素提供名稱屬性,並使用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 程式碼範例
以下示範使用 Python 2.2 從 CSV 檔案建立 KML 檔案的程式碼範例。你也可以前往這個網頁下載。
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 地球中的樣貌。
由於每個 <Placemark>
元素都沒有 <BalloonStyle><text>
和 <description>
元素,因此氣球預設為資料表樣式,然後繪製在 <Data>
元素上。
地理編碼考慮度
這已在「KML 專用的地理編碼地址」中提及,但需要重複所在位置。您的地理編碼要求將以地理編碼器的最大查詢頻率和每日 15,000 次查詢為基準 (根據您的 IP 量而定)。此外,當您查詢查詢時,進行地理編碼的速度必須更快處理,因此 620
會傳回狀態碼。(如需完整的狀態碼清單,請參閱這篇文章)。為避免傳送查詢的速度過快至地理編碼器,您可以指定每個地理編碼要求之間的延遲時間。您可以在每次收到 620
狀態時增加延遲時間,然後使用 while
迴圈,確保已成功疊代地址後再進行疊代至下一個地址。換句話說,如果您的 CSV 檔案非常大,您可能需要修改地理編碼程式碼,或者持續追蹤您建立地標的速度,如果速度太慢,則可能放慢速度。
結語
現在您可以使用 Python 從 CSV 檔案建立 KML 檔案。使用所提供的程式碼時,KML 檔案只能在「Google 地球」中運作。您可以使用 <description>
而非 <ExtendedData>
,在 Google 地圖和 Google 地球中修改該設定。此外,將這個程式碼範例轉換為任何其他支援 XML 支援的程式設計語言。
完成所有 CSV 檔案的轉換作業後,建議您查看其他 KML 文章,例如使用 PHP 和 MySQL 來建立 KML,以及 Google 開發人員指南的 ExtendedData 相關說明文章:新增自訂資料。