Mongoimport von CSV Dateien mit X/Y Koordinaten

Die Daten von Geonames können in einem wunderbaren CSV-Format länderweise heruntergeladen werden. Der Import der Daten in mongo ist wirklich einfach. Zuerst benötigen wir eine Header-Datei, in der die Spalten definiert sind (siehe dazu das entsprechende readme).

cat header.txt
id	name	asciiname	alternatenames	latitude	longitude	featureclass	featurecode	countrycode	cc2	admin1	admin2	admin3	admin4	population	elevation	dem	timezone	modification

Die Felder müssen natürlich mit Tabulatoren getrennt sein, wie in der von mir heruntergeladenen AT.txt Datei mit den Geonames Einträgen für Österreich. Anschließend den Header und die Daten zusammen kopieren und mit mongoimport in die Datenbank einspielen.

cat header.txt AT.txt >> AT_header.txt
mongoimport --db geonames --collection snowhow -type tsv --file AT_header.txt --headerline
mongo geonames
db.snowhow.find({ "name": "Sattelspitze" })
{ "_id" : ObjectId("502d20427529fd3eebcaf58d"), "id" : 6939577, "name" : "Sattelspitze", "asciiname" : "Sattelspitze", "alternatenames" : "", "latitude" : 47.33778, "longitude" : 9.77, "featureclass" : "T", "featurecode" : "PK", "countrycode" : "AT", "cc2" : "", "admin1" : 8, "admin2" : "", "admin3" : "", "admin4" : "", "population" : 0, "elevation" : 1562, "dem" : 1541, "timezone" : "Europe/Vienna", "modification" : "2009-03-19" }

Extrem cool. Aber leider ist so kein 2D-Index auf die Felder longitude und latitude möglich. Ein bisschen Googlen bestätigte die Vermutung, dass mongoimport keine Möglichkeit bietet, Geopositionen so zu erkennen. Als schnellste Möglichkeit für kleine Datensätze exportierte ich die Collection im JSON Format und korrigierte die Positionen mit sed. Ein mögliches Suchen-Ersetzen Kommando mit sed dazu lautet wie folgt:

sed -e 's/"latitude" : ([0-9.]+), "longitude" : ([0-9.]+),/loc: { "lon" : 2 , "lat" : 1 },/'
  < at.json > at_loc.json

Jetzt wird die collection gelöscht und die neue, konvertierte JSON-Datei in die Datenbank eingespielt:

mongo geonames
db.snowhow.remove()
exit
mongoimport --db geonames --collection snowhow --file at_loc.json

Abschließend noch den Geospatial-Index erzeugen, dann können räumliche Abfragen durchgeführt werden:

db.snowhow.ensureIndex( { loc: "2d" } );
db.snowhow.find( { loc : { $nearSphere : [9.77, 47.33778] } }, {name: true, loc: true}).limit(5)
{ "_id" : ObjectId("502d20427529fd3eebcaf58d"), "name" : "Sattelspitze", "loc" : { "lon" : 9.77, "lat" : 47.33778 } }
{ "_id" : ObjectId("502d20417529fd3eebcacf19"), "name" : "Alpkopf", "loc" : { "lon" : 9.76667, "lat" : 47.33306 } }
{ "_id" : ObjectId("502d20427529fd3eebcaf578"), "name" : "Salzbödenkopf", "loc" : { "lon" : 9.79361, "lat" : 47.33056 } }
{ "_id" : ObjectId("502d20417529fd3eebcac667"), "name" : "Ebnit", "loc" : { "lon" : 9.75, "lat" : 47.35 } }
{ "_id" : ObjectId("502d20427529fd3eebcaf55a"), "name" : "Hörnle", "loc" : { "lon" : 9.74056, "lat" : 47.31944 } }

Update

Zugegeben, wesentlich elaganter ist der hier (Update 2017: Link zu laulima.com funktioniert leider nicht mehr) vorgeschlagene Weg: Nach dem importieren der Daten wird die Struktur direkt in mongo umgebaut:

db.snowhow.find().forEach(function(data) {
  db.snowhow.update({_id:data._id},{$set:{loc:{lng:parseFloat(data.longitude),lat:parseFloat(data.latitude)}}});
})

Update 2012 / 2013

Und hier die Variante um nach dem Import mit mongoimport GeoJSON Punkte anzulegen:

db.geonames.find().forEach(function(data) {
  db.geonames.update( { _id: data._id }, {
    $set: { loc: { type: "Point",
        coordinates: [parseFloat(data.longitude), parseFloat(data.latitude)] } }
  })
})
csv  GIS  mongo  sed 

See also