將 CSV 檔案轉換為 KML

Google 地理位置 API 團隊 Mano Marks
2008 年 3 月

目標

本教學課程將說明如何使用 Python,從半形逗號分隔值 (CSV) 資料建立 KML 的基本概念。CSV 資料是現今最常見的檔案格式之一。大多數試算表和資料庫都能讀取及寫入 CSV 檔案。簡單的格式可使用文字編輯器編輯。許多程式設計語言 (例如 Python) 都有專用的程式庫,可讀取及寫入 CSV 檔案。因此非常適合用於交換大量資料。

本教學課程的程式碼範例是以 Python 撰寫,但可改用大多數其他程式設計語言。本教學課程使用「Geocoding Addresses for Use in 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 可以將這些文字區塊轉換為稱為 DictReaderdicts 集合,讓您逐步處理每一列。這個程式碼範例的前提是您事先瞭解資料結構,但您可以加入一些基本處理常式,動態傳遞欄位結構。

剖析 CSV 檔案

Python 的 xml.dom.minidom 模組提供建立 XML 文件的絕佳工具,由於 KML 是 XML,您會在本次教學課程中大量使用這個模組。您可以使用 createElementcreateElementNS 建立元素,並使用 appendChild 附加至另一個元素。以下是剖析 CSV 檔案及建立 KML 檔案的步驟。

  1. 將 geocoding_for_kml.py 匯入模組。
  2. 為 CSV 檔案建立 DictReaderDictReaderdicts 的集合,每個資料列都有一個 dicts
  3. 使用 Python 的 xml.dom.minidom.Document() 建立文件。
  4. 使用 createElementNS. 建立根 <kml> 元素
  5. 將其附加至文件
  6. 使用 createElement 建立 <Document> 元素。
  7. 使用 appendChild 將其附加至 <kml> 元素。
  8. 針對每個資料列,建立 <Placemark> 元素,並附加至 <Document> 元素。
  9. 針對每個資料列中的每個資料欄,建立 <ExtendedData> 元素,然後附加至您在步驟 8 中建立的 <Placemark> 元素。
  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 程式碼範例

以下是使用 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 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 地球中的樣子。 由於每個 <Placemark> 元素都沒有 <BalloonStyle><text><description> 元素,氣球會預設為表格樣式,並使用 <Data> 元素。

這個指令碼建立的 KML 螢幕截圖

地理編碼注意事項

「Geocoding Addresses for Use in KML」一文已提及這點,但仍值得再次強調。根據您的 IP 位址,地理編碼要求會受到地理編碼器最高查詢率和每日 15,000 次查詢的限制。此外,如果查詢速度過快,地理編碼器無法處理,就會傳回 620 狀態碼。(如要查看狀態碼完整清單,請按這裡)。為確保您不會太快將查詢傳送至地理編碼器,您可以指定每次地理編碼要求之間的延遲時間。每次收到 620 狀態時,您都可以增加這項延遲,並使用 while 迴圈,確保已成功將地址地理編碼,再疊代至下一個地址。也就是說,如果 CSV 檔案很大,您可能必須修改地理編碼,或追蹤建立地標的速度,如果速度太快,請放慢速度。

結論

現在您可以使用 Python,從 CSV 檔案建立 KML 檔案。使用提供的程式碼,KML 檔案只能在 Google 地球中運作。您可以修改這個函式,使其同時適用於 Google 地圖和地球,方法是使用 <description> 取代 <ExtendedData>。此外,您也可以輕鬆將這個程式碼範例轉換為任何支援 XML 的其他程式設計語言。

現在您已完成將所有 CSV 檔案轉換為 KML,不妨查看其他 KML 文章,例如「使用 PHP 和 MySQL 建立 KML」,以及 Google 開發人員指南中「新增自訂資料」一文,瞭解 ExtendedData。

返回頁首