mongotiles
Version:
mongotiles is a tilelive backend plug-in for MongoDB GridFS
237 lines (204 loc) • 8.04 kB
text/coffeescript
###
#
# Copyright (C) 2013-2016 by Vaughn Iverson
#
# mongotiles
#
# With this you can use MongoDB GridFS as a "tilelive.js" source or sink for
# map tile / grid / tilejson data.
#
# See: https://github.com/mapbox/tilelive.js
#
# This project is free software released under the MIT/X11 license.
# See LICENSE file for more information.
#
###
path = require 'path'
fs = require 'fs'
url = require 'url'
querystring = require 'querystring'
mongodb = require 'mongodb'
LockCollection = require('gridfs-locks').LockCollection
Lock = require('gridfs-locks').Lock
protocol = 'mongotiles'
default_root = 'fs'
tile_name = (z, x, y) ->
if x? and y? and z?
name = "tile_#{z}_#{x}_#{y}"
else
name = 'tile_{z}_{x}_{y}'
grid_name = (z, x, y) ->
if x? and y? and z?
name = "grid_#{z}_#{x}_#{y}"
else
name = 'grid_{z}_{x}_{y}'
tilejson_name = "tilejson.json"
# Image type magic numbers snarfed from https://github.com/mapbox/node-tilejson
get_mime_type = (bytes) ->
if (bytes[0] is 0x89 and bytes[1] is 0x50 and bytes[2] is 0x4E and
bytes[3] is 0x47 and bytes[4] is 0x0D and bytes[5] is 0x0A and
bytes[6] is 0x1A and bytes[7] is 0x0A)
return 'image/png'
else if (bytes[0] is 0xFF and bytes[1] is 0xD8 and
bytes[bytes.length - 2] is 0xFF and bytes[bytes.length - 1] is 0xD9)
return 'image/jpeg'
else if (bytes[0] is 0x47 and bytes[1] is 0x49 and bytes[2] is 0x46 and
bytes[3] is 0x38 and (bytes[4] is 0x39 or bytes[4] is 0x37) and
bytes[5] is 0x61)
return 'image/gif'
else
console.warn "#{protocol}: Image data with unknown MIME type in putTile call to get_mime_type."
return 'application/octet-stream'
class Tilemongo
_file_exists : (fn, cb) ->
@files.findOne {filename: fn}, {_id: 1}, (err, doc) =>
return cb err, doc
_write_buffer : (fn, type, buffer, metadata, cb) ->
write_it = (_id, l) =>
gs = new mongodb.GridStore @db, _id, fn, 'w', { w : 1, root : @grid_root, content_type : type, metadata: metadata }
gs.open (err, gs) =>
return cb err if err
gs.write buffer, (err, gs) =>
return cb err if err
gs.close (err) =>
l.releaseLock() if l
cb err
@_file_exists fn, (err, doc) =>
return cb err if err
unless doc
doc = { _id: new mongodb.ObjectID() }
if @lockColl
lock = Lock(doc._id, @lockColl, {}).obtainWriteLock()
lock.on 'timed-out', () =>
cb new Error "Timed out waiting for write lock"
lock.on 'error', (err) =>
cb err
lock.on 'locked', (ld) =>
write_it doc._id, lock
else
write_it doc._id
_read_buffer : (fn, cb) ->
read_it = (_id, l) =>
gs = new mongodb.GridStore @db, _id, 'r', { root : @grid_root }
gs.open (err, gs) =>
return cb err if err
gs.read (err, buffer) =>
return cb err if err
gs.close (err) =>
l.releaseLock() if l
cb err, buffer
@_file_exists fn, (err, doc) =>
return cb err if err
unless doc
return cb null, null
if @lockColl
lock = Lock(doc._id, @lockColl, {}).obtainReadLock()
lock.on 'timed-out', () =>
cb new Error "Timed out waiting for write lock"
lock.on 'error', (err) =>
cb err
lock.on 'locked', (ld) =>
read_it doc._id, lock
else
read_it doc._id
@registerProtocols = (tilelive) ->
tilelive.protocols["#{protocol}:"] = @
@list = (filepath, callback) ->
callback new Error ".list not implemented for #{protocol}"
@findID = (filepath, id, callback) ->
callback new Error ".findID not implemented for #{protocol}"
constructor : (uri, callback) ->
@starts = 0
# uri seems to be preparsed, but make sure...
if typeof uri is 'string'
uri = url.parse uri, true
if typeof uri.query is 'string'
uri.query = querystring.parse uri.query
unless uri.protocol is "#{protocol}:"
return callback new Error "Bad uri protocol '#{uri.protocol}'. Must be #{protocol}."
tilepath_match = uri.pathname.match new RegExp "(/[^/]+)(/[^/]+)?"
unless tilepath_match
return callback new Error "Bad tile url path '#{uri.pathname}' for #{uri.protocol}."
locking = uri.query?.locking ? false
uri.query = ''
uri.search = ''
uri.hash = ''
uri.protocol = 'http:'
@source = url.format uri
@db_name = tilepath_match[1][1...]
@grid_root = tilepath_match[2]?[1...] or default_root
uri.path = @db_name
uri.pathname = uri.path
uri.protocol = 'mongodb:'
@server = url.format(uri)
console.log "Server: #{@server}"
mongodb.MongoClient.connect @server, (err, db) =>
return callback err if err
@db = db
console.log "GRID ROOT #{@grid_root}"
@db.collection "#{@grid_root}.files", { w: 1 }, (err, coll) =>
return callback err if err
@files = coll
unless locking
return callback null, @
else
@lockColl = LockCollection(@db,
root: @grid_root
timeOut: 60,
pollingInterval: 5,
lockExpiration: 30)
@lockColl.on 'ready', () =>
callback null, @
@lockColl.on 'error', (err) ->
callback err
close : (callback) ->
@db.close(callback)
getInfo : (callback) ->
@_read_buffer tilejson_name, (err, info) =>
return callback err if err
callback null, JSON.parse(info)
getTile : (z, x, y, callback) ->
tn = tile_name z, x, y
@_read_buffer tn, (err, data) =>
if err
callback err
else unless data
callback new Error('Tile does not exist')
else
callback null, data
getGrid : (z, x, y, callback) ->
gn = grid_name z, x, y
@_read_buffer gn, (err, data) =>
if err
callback err
else unless data
callback new Error('Grid does not exist')
else
callback null, JSON.parse(data)
startWriting : (callback) ->
@starts += 1
callback null
stopWriting : (callback) ->
@starts -= 1
callback null
putInfo : (info, callback) ->
unless @starts
return callback new Error "Error, writing not started."
tn = tile_name()
info.tiles = [ "#{@source}#{tn}" ]
if info.grids?
gn = grid_name()
info.grids = [ "#{@source}#{gn}" ]
@_write_buffer tilejson_name, 'application/json', JSON.stringify(info), null, callback
putTile : (z, x, y, tile, callback) ->
unless @starts
return callback new Error "Error, writing not started."
tn = tile_name z, x, y
type = get_mime_type tile
@_write_buffer tn, type, tile, { type: "tile", x:x, y:y, z:z }, callback
putGrid : (z, x, y, grid, callback) ->
unless @starts
return callback new Error "Error, writing not started."
gn = grid_name z, x, y
@_write_buffer gn, 'application/json', JSON.stringify(grid), { type: "grid", x:x, y:y, z:z }, callback
module.exports = Tilemongo