Konwertowanie plików CSV na KML

Mano Marks, zespół ds. interfejsów Google Geo API
Marzec 2008 r.

Cel

W tym samouczku znajdziesz podstawowe informacje o tym, jak utworzyć plik KML na podstawie danych w pliku CSV za pomocą języka Python. Dane CSV to jeden z najpopularniejszych formatów plików używanych obecnie. Większość arkuszy kalkulacyjnych i baz danych może odczytywać i zapisywać pliki CSV. Jego prosty format można edytować w edytorze tekstu. Wiele języków programowania, np. Python, ma specjalne biblioteki do odczytywania i zapisywania plików CSV. Dlatego jest to świetny sposób na wymianę dużych ilości danych.

Przykłady kodu w tym samouczku są napisane w Pythonie, ale można je dostosować do większości innych języków programowania. W tym samouczku używamy kodu z artykułu Geokodowanie adresów do użycia w KML, aby przekształcić adres w współrzędne geograficzne. Wykorzystuje też nowy element <ExtendedData> KML 2.2 i szablony dymków opisane w artykule Dodawanie danych niestandardowych. Wygenerowany plik KML nie jest obecnie obsługiwany w Mapach Google ani w innych aplikacjach korzystających z tego formatu, ale kod można dostosować tak, aby generować pliki KML zgodne z Mapami.

Przykładowe dane

W tym samouczku użyj pliku google-addresses.csv jako przykładowego pliku CSV. Ten plik zawiera wszystkie adresy, numery telefonów i numery faksów różnych biur Google w Stanach Zjednoczonych. Oto tekst pliku:

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,

Zwróć uwagę, że każdy wiersz to ciąg tekstowy oddzielony przecinkami. Każdy przecinek oddziela pole, a każdy wiersz zawiera taką samą liczbę przecinków. Pierwszy wiersz zawiera nazwy pól w odpowiedniej kolejności. Na przykład pierwszy blok tekstu w każdym wierszu to pole „Office”, drugi to „Address1” itd. Python może przekształcić to w kolekcję dicts, zwaną DictReader, która umożliwia przechodzenie przez poszczególne wiersze. Ten przykładowy kod wymaga wcześniejszej znajomości struktury danych, ale możesz dodać do niego podstawowe moduły obsługi, aby przekazywać strukturę pola dynamicznie.

Analizowanie pliku CSV

Moduł xml.dom.minidom Pythona udostępnia świetne narzędzia do tworzenia dokumentów XML, a ponieważ KML to XML, w tym samouczku będziesz z niego często korzystać. Element tworzysz za pomocą createElement lub createElementNS, a dołączasz go do innego elementu za pomocą appendChild. Oto czynności, które należy wykonać, aby przeanalizować plik CSV i utworzyć plik KML.

  1. Zaimportuj plik geocoding_for_kml.py do modułu.
  2. Utwórz DictReader dla plików CSV. DictReader to zbiór dicts, po jednym dla każdego wiersza.
  3. Utwórz dokument za pomocą funkcji xml.dom.minidom.Document() w Pythonie.
  4. Utwórz element główny <kml> za pomocą createElementNS.
  5. Dołącz go do dokumentu.
  6. Utwórz element <Document> za pomocą języka createElement.
  7. Dodaj go do elementu <kml> za pomocą appendChild.
  8. Dla każdego wiersza utwórz element <Placemark> i dołącz go do elementu <Document>.
  9. Dla każdej kolumny w każdym wierszu utwórz element <ExtendedData> i dołącz go do elementu <Placemark> utworzonego w kroku 8.
  10. Utwórz element <Data> i dołącz go do elementu <ExtendedData>. Elementowi <Data> nadaj atrybut name i przypisz mu wartość nazwy kolumny za pomocą setAttribute.
  11. Utwórz element <value> i dołącz go do elementu <Data>. Utwórz węzeł tekstowy i przypisz mu wartość kolumny za pomocą createTextNode. Dołącz węzeł tekstowy do elementu <value>.
  12. Utwórz element <Point> i dołącz go do elementu <Placemark>. Utwórz element <coordinates> i dołącz go do elementu <Point>.
  13. Wyodrębnij adres z wiersza, aby był jednym ciągiem znaków w tym formacie: Address1,Address2,City,State,Zip. Pierwszy wiersz to 1600 Amphitheater Parkway,,Mountain View,CA,94043. Nie ma nic złego w tym, że obok siebie znajdują się przecinki. Pamiętaj, że wymaga to wcześniejszej znajomości struktury pliku CSV i kolumn, które tworzą adres.
  14. Geokoduj adres za pomocą kodu geocoding_for_kml.py, który został opisany w artykule Geokodowanie adresów do użycia w KML. Zwraca ciąg znaków, który jest długością i szerokością geograficzną lokalizacji.
  15. Utwórz węzeł tekstowy i przypisz mu wartość współrzędnych z kroku 14, a następnie dołącz go do elementu <coordinates>.
  16. Zapisz dokument KML w pliku.
  17. Jeśli przekażesz listę nazw kolumn jako argumenty do skryptu, skrypt doda elementy w tej kolejności. Gdyby kolejność elementów nie miała znaczenia, moglibyśmy użyć dict.keys(), aby uzyskać list. dict.keys() nie zachowuje jednak oryginalnej kolejności z dokumentu. Aby użyć tego argumentu, przekaż listę nazw pól jako listę rozdzieloną przecinkami, np. tak:
    python csvtokml.py Office,Address1,Address2,Address3,City,State,Zip,Phone,Fax

Przykładowy kod w Pythonie

Poniżej znajdziesz przykładowy kod do tworzenia pliku KML z pliku CSV w Pythonie 2.2. Możesz też pobrać go tutaj.


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()

Utworzono przykładowy plik KML

Poniżej znajdziesz przykładowy plik KML utworzony przez ten skrypt. Zwróć uwagę, że niektóre<value> elementy zawierają tylko białe znaki. Dzieje się tak, ponieważ pole nie zawierało żadnych danych. Pełną wersję przykładową możesz pobrać tutaj.

<?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>
    ...

Zrzut ekranu

Poniżej znajdziesz zrzut ekranu pokazujący, jak ten plik KML wygląda w Google Earth. Ponieważ każdy element <Placemark> nie ma elementu <BalloonStyle><text> ani elementu <description>, dymek domyślnie przyjmuje styl tabeli, korzystając z elementów <Data>.

Zrzut ekranu pliku KML utworzonego przez ten skrypt

Wskazówki dotyczące geokodowania

Wspomnieliśmy o tym w artykule „Geokodowanie adresów do użycia w KML”, ale warto to powtórzyć. Twoje żądania geokodowania będą podlegać maksymalnej częstotliwości zapytań geokodera i 15 tys. zapytań dziennie na podstawie Twojego adresu IP. Dodatkowo, jeśli zapytanie do geokodera zostanie wysłane szybciej, niż jest on w stanie je obsłużyć, zwróci on kod stanu 620. (Pełna lista kodów stanu jest dostępna tutaj). Aby mieć pewność, że nie wysyłasz zapytań do geokodera zbyt szybko, możesz określić opóźnienie między poszczególnymi żądaniami geokodowania. Możesz zwiększać to opóźnienie za każdym razem, gdy otrzymasz stan 620, i używać pętli while, aby mieć pewność, że adres został prawidłowo geokodowany, zanim przejdziesz do następnego. Oznacza to, że jeśli plik CSV jest bardzo duży, może być konieczne zmodyfikowanie kodu geokodowania lub śledzenie szybkości tworzenia znaczników i zmniejszenie jej, jeśli jest zbyt duża.

Podsumowanie

Teraz możesz użyć Pythona do utworzenia pliku KML z pliku CSV. Dzięki podanemu kodowi plik KML będzie działać tylko w Google Earth. Możesz zmodyfikować go tak, aby działał zarówno w Mapach, jak i w Earth, używając <description> zamiast <ExtendedData>. Ten przykład kodu można też łatwo przekonwertować na dowolny inny język programowania, który obsługuje XML.

Po przekonwertowaniu wszystkich plików CSV na KML możesz zapoznać się z innymi artykułami na temat KML, np. Tworzenie plików KML za pomocą PHP i MySQL oraz artykułem z przewodnika dla deweloperów Google na temat elementu ExtendedData, Dodawanie danych niestandardowych.

Powrót do góry