summaryrefslogtreecommitdiffstats
path: root/src/TileSource.coffee
blob: 5cae1eea93a5a3d9c20e9a963f0c8da3469a2bdb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
###
  termap - Terminal Map Viewer
  by Michael Strassburger <codepoet@cpan.org>

  Source for VectorTiles - supports
  * remote TileServer
  * local MBTiles and VectorTiles
###

Promise = require 'bluebird'
userhome = require 'userhome'
fetch = require 'node-fetch'
fs = require 'fs'

Tile = require './Tile'
config = require './config'

# https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
# To maximize mapscii's compatibility, MBTiles support must be manually added via
# $> npm install -g mbtiles
MBTiles = try
  require 'mbtiles'
catch
  null

module.exports = class TileSource
  cache: {}
  cached: []
  modes:
    MBTiles: 1
    VectorTile: 2
    HTTP: 3

  mode: null
  mbtiles: null
  styler: null

  init: (@source) ->
    if @source.startsWith "http"
      @_initPersistence() if config.persistDownloadedTiles

      @mode = @modes.HTTP

    else if @source.endsWith ".mbtiles"
      unless MBTiles
        throw new Error "MBTiles support must be installed with following command: 'npm install -g mbtiles'"

      @mode = @modes.MBTiles
      @loadMBtils source

    else
      throw new Error "source type isn't supported yet"

  loadMBtils: (source) ->
    new Promise (resolve, reject) =>
      new MBTiles source, (err, @mbtiles) =>
        if err then reject err
        else resolve()

  useStyler: (@styler) ->

  getTile: (z, x, y) ->
    unless @mode
      throw new Error "no TileSource defined"

    z = Math.max 0, Math.floor z

    if cached = @cache[[z,x,y].join("-")]
      return Promise.resolve cached

    if @cached.length > 4
      for tile in @cached.splice 0, Math.abs(4-@cached.length)
        delete @cache[tile]

    switch @mode
      when @modes.MBTiles then @_getMBTile z, x, y
      when @modes.HTTP then @_getHTTP z, x, y

  _getHTTP: (z, x, y) ->
    promise =
      if config.persistDownloadedTiles and tile = @_getPersited z, x, y
        Promise.resolve tile
      else
        fetch @source+[z,x,y].join("/")+".pbf"
        .then (res) => res.buffer()
        .then (buffer) =>
          @_persistTile z, x, y, buffer if config.persistDownloadedTiles
          buffer

    promise
    .then (buffer) =>
      @_createTile z, x, y, buffer

  _getMBTile: (z, x, y) ->
    new Promise (resolve, reject) =>
      @mbtiles.getTile z, x, y, (err, buffer) =>
        return reject err if err
        resolve @_createTile z, x, y, buffer

  _createTile: (z, x, y, buffer) ->
    name = [z,x,y].join("-")
    @cached.push name

    tile = @cache[name] = new Tile @styler
    tile.load buffer

  _initPersistence: ->
    try
      @_createFolder userhome ".mapscii"
      @_createFolder userhome ".mapscii", "cache"
    catch error
      config.persistDownloadedTiles = false
      return

  _persistTile: (z, x, y, buffer) ->
    zoom = z.toString()
    @_createFolder userhome ".mapscii", "cache", zoom
    fs.writeFile userhome(".mapscii", "cache", zoom, "#{x}-#{y}.pbf"), buffer, -> null

  _getPersited: (z, x, y) ->
    try
      fs.readFileSync userhome ".mapscii", "cache", z.toString(), "#{x}-#{y}.pbf"
    catch error
      false

  _createFolder: (path) ->
    try
      fs.mkdirSync path
      true
    catch e
      e.code is "EEXIST"