GPX to GeoJSON in MongoDB with Node.js
It’s quite hard to find information how to work with GeoJSON. And some information provided in the library documentation is quite hard to understand. So her’s a few code blocks that can help someone combine GPX, GeoJSON, MongoDB, Node.js and Mongoose.
Disclamer: I’m new to Node.js and CoffeeScript, so feel free to comment my mistakes or code improvements.
GPX to GeoJSON
We will use a Node.JS library togeojson for this.
npm install togeojson --save
npm install jsdom --save
Imagine we will upload a GPX file and convert it after to GeoJSON
togeojson = require 'togeojson'
fs = require 'fs'
jsdom = require('jsdom').jsdom
# Later on
app.post '/routes/upload', (req, res) ->
file = req.files.file
if file
console.log "Uploaded " + file.originalFilename + " to " + file.path
fs.readFile file.path, (err, data) ->
gpx = jsdom(data)
converted = togeojson.gpx(gpx)
# Send back converted
res.send converted
else
res.send 400
Storing GeoJSON in MongoDB
For connecting with our MongoDB from Node.js we will use mongoose
library
npm install mongoose --save
The GeoJSON has a definition of Feature
and FeatureCollection
(Spec). For the simple case, we will store in our DB only the first Feature
under the loc
. The Feature
itself can be a point, multi-line or a polygon. For most use-cases, working with one Feature
is enought. Storing FeatureCollection
will be covered in the separate post.
First, create a schema:
mongoose = require 'mongoose'
mongoose.connect(process.env.MONGOHQ_URL);
Schema = mongoose.Schema
routeSchema = new Schema
name: String
loc: {type: Object, index: '2dsphere'}
Now let’s add a virtual setter and getter to convert from GeoJSON to our Mongo representation and vise versa:
routeSchema.virtual("geoJSON").get ->
feature =
type: "Feature"
geometry: @loc
properties:
name: @name
resp =
type: "FeatureCollection"
features: [
feature
]
return resp
routeSchema.virtual("geoJSON").set (json) ->
feature = json.features[0]
@name = feature.properties.name
@loc = feature.geometry
Now let’s change our /upload
route to add saving:
app.post '/routes/upload', (req, res) ->
file = req.files.file
if file
fs.readFile file.path, (err, data) ->
gpx = jsdom(data)
converted = togeojson.gpx(gpx)
fs.unlinkSync file.path
route = new Route()
route.geoJSON = converted
route.save (err) ->
console.log "Error saved route", err if err
console.log "Saved route", route
res.send err if err
res.send route.geoJSON
else
res.send 400
Checking the stored data
After uploading, we can check the data with mongo
console:
mongo -u admin -p password lennon.mongohq.com:10031/app12345
> version()
2.4.9
> db.routes.find()
{ "loc" : { "coordinates" : [ [5.40300237,53.177886249], [5.276818363, 53.223566907] ], "type" : "LineString" }, "name" : "Waddenrace", "_id" : ObjectId("5320bc0cae1f27a09faed03b"), "__v" : 0 }
So it’s there. Now let’s try searching with $near:
> db.routes.find({loc: {$near : { $geometry : { type: "Point",coordinates: [5.40300237, 53.177886249]}}, $maxDistance : 500 }})
{ "loc" : { "coordinates" : [ [5.40300237,53.177886249], [5.276818363, 53.223566907] ], "type" : "LineString" }, "name" : "Waddenrace", "_id" : ObjectId("5320bc0cae1f27a09faed03b"), "__v" : 0 }
In real life, I suggest using geoNear command instead of $near
. Will cover this in next article.