将 CSV 文件转换为 KML 文件

Mano Marks,Google Geo API 团队
2008 年 3 月

目标

本教程简要介绍了如何使用 Python 基于逗号分隔值 (CSV) 数据创建 KML。CSV 数据是当今使用最广泛的文件格式之一。大多数电子表格和数据库都可以读取和写入 CSV 文件。其格式简单,可在文本编辑器中进行修改。许多编程语言(例如 Python)都有专门用于读取和写入 CSV 文件的库。因此,它非常适合用于交换大量数据。

虽然本教程中的代码示例是使用 Python 编写的,但它们可以适用于大多数其他编程语言。本教程使用对地址进行地理编码以在 KML 中使用中的代码将地址转换为经度/纬度坐标。它还使用了 KML 2.2 的新 <ExtendedData> 元素,并利用了添加自定义数据中介绍的气球模板。因此,生成的 KML 目前在 Google 地图或其他使用 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,因此您将在本教程中大量使用该模块。您可以使用 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 Addresses for Use in KML(对地址进行地理编码以在 KML 中使用)中介绍的 geocoding_for_kml.py 代码对地址进行地理编码。此方法会返回一个字符串,其中包含相应位置的经度和纬度。
  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 的屏幕截图

地理编码注意事项

虽然“对地址进行地理编码以在 KML 中使用”一文中已提及这一点,但仍有必要再次强调。您的地理编码请求将受地理编码器的最大查询速率限制,并且每天最多只能发出 15,000 次查询(基于您的 IP 地址)。此外,如果您查询地理编码器的速度过快,导致地理编码器无法处理,则地理编码器会返回状态代码 620。(如需查看状态代码的完整列表,请点击此处。) 为确保您不会过于频繁地向地理编码器发送查询,您可以在每个地理编码请求之间指定延迟时间。您可以在每次收到 620 状态时增加此延迟,并使用 while 循环来确保您已成功对某个地址进行地理编码,然后再迭代到下一个地址。这意味着,如果您的 CSV 文件非常大,您可能需要修改地理编码代码,或者跟踪您创建地标的速度,如果速度过快,则需要减慢速度。

总结

现在,您可以使用 Python 从 CSV 文件创建 KML 文件。使用提供的代码后,KML 文件将仅在 Google 地球中有效。您可以修改该代码,使其在 Google 地图和 Google 地球中都能运行,方法是将 <ExtendedData> 替换为 <description>。此外,您还可以轻松将此代码示例转换为任何提供 XML 支持的其他编程语言。

现在,您已完成将所有 CSV 文件转换为 KML 的操作,不妨查看其他 KML 文章,例如使用 PHP 和 MySQL 创建 KML 以及 Google 开发者指南中有关 ExtendedData 的文章添加自定义数据

返回页首