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.