tairu 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +18 -14
- data/bin/tairu +9 -5
- data/config.ru +0 -0
- data/lib/tairu.rb +20 -16
- data/lib/tairu/cache.rb +4 -2
- data/lib/tairu/cache/disk.rb +11 -12
- data/lib/tairu/cache/memory.rb +7 -7
- data/lib/tairu/cache/redis_cache.rb +45 -0
- data/lib/tairu/configuration.rb +46 -31
- data/lib/tairu/coordinate.rb +0 -0
- data/lib/tairu/server.rb +18 -5
- data/lib/tairu/store.rb +3 -1
- data/lib/tairu/store/esri.rb +22 -20
- data/lib/tairu/store/mbtiles.rb +34 -136
- data/lib/tairu/store/tms.rb +36 -0
- data/lib/tairu/tile.rb +0 -0
- data/lib/tairu/version.rb +2 -2
- metadata +81 -18
- data/ext/mkrf_conf.rb +0 -26
- data/lib/tairu/images/404.png +0 -0
data/README.md
CHANGED
|
@@ -2,12 +2,11 @@ tairu ... simple map tile server
|
|
|
2
2
|
|
|
3
3
|
In development mode (irb): `rake console`
|
|
4
4
|
|
|
5
|
-
To run: `tairu
|
|
5
|
+
To run from bin: `tairu --config /path/to/config/file`
|
|
6
6
|
|
|
7
7
|
Example config file:
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
|
|
11
10
|
name: tairu_config_example
|
|
12
11
|
cache:
|
|
13
12
|
type: memory
|
|
@@ -17,17 +16,12 @@ layers:
|
|
|
17
16
|
tileset: geography-class.mbtiles
|
|
18
17
|
location: ~/.tairu/tilesets
|
|
19
18
|
format: png
|
|
20
|
-
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
This may be read from a yaml file (see example above) using `Tairu::Configuration.config_from_file(file_name)`
|
|
26
|
-
|
|
27
|
-
or by assigning default values for layers, cache, and name (optional):
|
|
21
|
+
Configuration may be read from a yaml file (see example above) using `Tairu.config_from_file(file_name)`
|
|
22
|
+
or by passing values for layers, cache, and name (optional) into a configuration block:
|
|
28
23
|
|
|
29
24
|
```ruby
|
|
30
|
-
|
|
31
25
|
layers = {
|
|
32
26
|
'geo' => {
|
|
33
27
|
'provider' => 'mbtiles',
|
|
@@ -37,10 +31,20 @@ layers = {
|
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
|
|
40
|
-
cache =
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
cache = {
|
|
35
|
+
'type' => 'redis',
|
|
36
|
+
'options' => {
|
|
37
|
+
'host' => 'localhost',
|
|
38
|
+
'port' => '6379',
|
|
39
|
+
'db' => 0
|
|
40
|
+
}
|
|
41
|
+
}
|
|
43
42
|
|
|
44
|
-
Tairu
|
|
43
|
+
Tairu.configure do |config|
|
|
44
|
+
config.name = 'tairu_config_example'
|
|
45
|
+
config.layers = layers
|
|
46
|
+
config.cache = cache
|
|
47
|
+
end
|
|
48
|
+
```
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
NOTE: If no cache is passed in, it will default to the memory cache
|
data/bin/tairu
CHANGED
|
@@ -11,7 +11,10 @@ OptionParser.new do |o|
|
|
|
11
11
|
o.banner = 'Usage: tairu.rb [options]'
|
|
12
12
|
|
|
13
13
|
o.on('-v', '--version', '') do
|
|
14
|
-
|
|
14
|
+
unless defined?(Tairu)
|
|
15
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'tairu', 'version')
|
|
16
|
+
end
|
|
17
|
+
|
|
15
18
|
puts "Tairu v#{Tairu::VERSION}"
|
|
16
19
|
exit(0)
|
|
17
20
|
end
|
|
@@ -30,10 +33,11 @@ OptionParser.new do |o|
|
|
|
30
33
|
end.parse!
|
|
31
34
|
|
|
32
35
|
if opts[:config]
|
|
33
|
-
|
|
36
|
+
unless defined?(Tairu)
|
|
37
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'tairu')
|
|
38
|
+
end
|
|
34
39
|
|
|
35
|
-
Tairu
|
|
36
|
-
Tairu::CACHE = Tairu::CONFIG.cache
|
|
40
|
+
Tairu.config_from_file(opts[:config])
|
|
37
41
|
else
|
|
38
42
|
puts "No valid config file specified. Use -c / --config option."
|
|
39
43
|
exit(1)
|
|
@@ -45,7 +49,7 @@ server = ::Puma::Server.new(app)
|
|
|
45
49
|
host = opts[:host] || '0.0.0.0'
|
|
46
50
|
port = opts[:port] || 8080
|
|
47
51
|
server.add_tcp_listener host, port
|
|
48
|
-
min_threads, max_threads =
|
|
52
|
+
min_threads, max_threads = 4, 16
|
|
49
53
|
server.min_threads = min_threads
|
|
50
54
|
server.max_threads = max_threads
|
|
51
55
|
|
data/config.ru
CHANGED
|
File without changes
|
data/lib/tairu.rb
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
$:.push File.expand_path(File.join(File.dirname(__FILE__), 'tairu'))
|
|
2
2
|
|
|
3
|
+
require 'rubygems'
|
|
3
4
|
require 'sequel'
|
|
4
5
|
require 'cache'
|
|
5
6
|
require 'store'
|
|
6
7
|
require 'coordinate'
|
|
7
8
|
require 'tile'
|
|
8
9
|
require 'configuration'
|
|
10
|
+
require 'version'
|
|
9
11
|
|
|
10
12
|
module Tairu
|
|
11
|
-
|
|
13
|
+
extend self
|
|
14
|
+
extend Configuration
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
tileset = Tairu::CONFIG.layers[name]
|
|
16
|
+
TILE_404 = Tairu::Tile.new('', 'image/png')
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :logger
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
def get_tile(name, coord, format=nil)
|
|
22
|
+
tileset = Tairu.tilesets[name]
|
|
23
|
+
|
|
24
|
+
return TILE_404 if tileset.nil?
|
|
25
|
+
|
|
26
|
+
tile = Tairu.cache.get(name, coord)
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
if tile.nil?
|
|
29
|
+
tile = tileset.get(coord, Tairu.layers[name]['format'])
|
|
30
|
+
unless tile.nil?
|
|
31
|
+
Tairu.cache.add(name, coord, tile)
|
|
26
32
|
else
|
|
27
33
|
tile = TILE_404
|
|
28
34
|
end
|
|
29
35
|
end
|
|
30
|
-
else
|
|
31
|
-
tile = TILE_404
|
|
32
|
-
end
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
tile
|
|
38
|
+
end
|
|
35
39
|
end
|
|
36
|
-
end
|
|
40
|
+
end
|
data/lib/tairu/cache.rb
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
require 'cache/memory'
|
|
2
2
|
require 'cache/disk'
|
|
3
|
+
require 'cache/redis_cache'
|
|
3
4
|
|
|
4
5
|
module Tairu
|
|
5
6
|
module Cache
|
|
6
7
|
TYPES = {
|
|
7
8
|
'memory' => Tairu::Cache::Memory,
|
|
8
|
-
'disk' => Tairu::Cache::Disk
|
|
9
|
+
'disk' => Tairu::Cache::Disk,
|
|
10
|
+
'redis' => Tairu::Cache::RedisCache
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
Key = Struct.new(:layer, :coord, :format)
|
|
12
14
|
end
|
|
13
|
-
end
|
|
15
|
+
end
|
data/lib/tairu/cache/disk.rb
CHANGED
|
@@ -3,11 +3,10 @@ require 'fileutils'
|
|
|
3
3
|
module Tairu
|
|
4
4
|
module Cache
|
|
5
5
|
class Disk
|
|
6
|
-
def initialize(options
|
|
6
|
+
def initialize(options={})
|
|
7
7
|
raise "No path specified." unless options['path']
|
|
8
8
|
@path = options['path']
|
|
9
9
|
@expire = options['expire'] || 300
|
|
10
|
-
@tiles = {}
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def lock_write(path, data)
|
|
@@ -40,19 +39,18 @@ module Tairu
|
|
|
40
39
|
data
|
|
41
40
|
end
|
|
42
41
|
|
|
43
|
-
def add(
|
|
42
|
+
def add(name, coord, tile)
|
|
44
43
|
expire = Time.now + @expire
|
|
45
|
-
|
|
46
|
-
base_path = File.join(File.expand_path(@path), tileset, "#{coord.zoom}", "#{coord.column}")
|
|
44
|
+
base_path = File.join(File.expand_path(@path), name, "#{coord.zoom}", "#{coord.column}")
|
|
47
45
|
FileUtils.mkdir_p(base_path)
|
|
48
|
-
path = File.join(base_path, "#{coord.row}.#{
|
|
46
|
+
path = File.join(base_path, "#{coord.row}.#{Tairu.config.layers[name]['format']}")
|
|
49
47
|
lock_write(path, tile.data)
|
|
50
|
-
purge_expired(
|
|
48
|
+
purge_expired(name)
|
|
51
49
|
end
|
|
52
50
|
|
|
53
|
-
def get(
|
|
54
|
-
|
|
55
|
-
path = File.join(File.expand_path(@path),
|
|
51
|
+
def get(name, coord)
|
|
52
|
+
layer = Tairu.config.layers[name]
|
|
53
|
+
path = File.join(File.expand_path(@path), name, "#{coord.zoom}", "#{coord.column}", "#{coord.row}.#{layer['format']}")
|
|
56
54
|
data = lock_read(path)
|
|
57
55
|
|
|
58
56
|
return nil if data.nil?
|
|
@@ -60,10 +58,11 @@ module Tairu
|
|
|
60
58
|
Tairu::Tile.new(data, layer['format'])
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
def purge_expired(
|
|
64
|
-
Dir.glob(File.join(File.expand_path(@path), "**/*.#{
|
|
61
|
+
def purge_expired(name)
|
|
62
|
+
Dir.glob(File.join(File.expand_path(@path), "**/*.#{Tairu.config.layers[name]['format']}")).each do |f|
|
|
65
63
|
FileUtils.rm(f) if File.mtime(f) < (Time.now - @expire)
|
|
66
64
|
end
|
|
65
|
+
Dir[File.join(File.expand_path(@path), '**/*')].select {|d| File.directory?(d)}.select {|d| (Dir.entries(d) - %w[. ..]).empty?}.each {|d| Dir.rmdir(d)}
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
68
|
end
|
data/lib/tairu/cache/memory.rb
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
module Tairu
|
|
2
2
|
module Cache
|
|
3
3
|
class Memory
|
|
4
|
-
def initialize(options
|
|
4
|
+
def initialize(options=nil)
|
|
5
5
|
@tiles = {}
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def add(
|
|
9
|
-
key = Tairu::Cache::Key.new(
|
|
8
|
+
def add(name, coord, tile, age=300)
|
|
9
|
+
key = Tairu::Cache::Key.new(name, coord)
|
|
10
10
|
expire = Time.now + age
|
|
11
11
|
@tiles[key] = {tile: tile, expire: expire}
|
|
12
|
-
|
|
12
|
+
purge_expired
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def get(
|
|
16
|
-
key = Tairu::Cache::Key.new(
|
|
15
|
+
def get(name, coord)
|
|
16
|
+
key = Tairu::Cache::Key.new(name, coord)
|
|
17
17
|
tile = @tiles[key]
|
|
18
18
|
|
|
19
19
|
return nil if tile.nil?
|
|
@@ -31,4 +31,4 @@ module Tairu
|
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
-
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'connection_pool'
|
|
2
|
+
require 'redis'
|
|
3
|
+
require 'base64'
|
|
4
|
+
gem 'json', '1.7.7'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'multi_json'
|
|
7
|
+
|
|
8
|
+
module Tairu
|
|
9
|
+
module Cache
|
|
10
|
+
class RedisCache
|
|
11
|
+
def initialize(options={'host' => 'localhost', 'port' => 6379, 'db' => 0})
|
|
12
|
+
if options['url']
|
|
13
|
+
redis_url = options['url']
|
|
14
|
+
else
|
|
15
|
+
db = options['db'] || 0
|
|
16
|
+
redis_url = "redis://#{options['host']}:#{options['port']}/#{db}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@pool = ConnectionPool::Wrapper.new(timeout: 1, size: 4) do
|
|
20
|
+
Redis.connect(url: redis_url, driver: 'ruby')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add(name, coord, tile, age=300)
|
|
25
|
+
key = "#{name}_#{coord.zoom}_#{coord.column}_#{coord.row}"
|
|
26
|
+
expire = Time.now.to_i + age
|
|
27
|
+
tile = Base64.encode64(tile.data)
|
|
28
|
+
@pool.set(key, MultiJson.dump({tile: tile, format: Tairu.config.layers[name]['format']}))
|
|
29
|
+
@pool.expire(key, expire)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get(name, coord)
|
|
33
|
+
key = "#{name}_#{coord.zoom}_#{coord.column}_#{coord.row}"
|
|
34
|
+
value = @pool.get(key)
|
|
35
|
+
|
|
36
|
+
if value.nil?
|
|
37
|
+
return nil
|
|
38
|
+
else
|
|
39
|
+
tile_hash = MultiJson.load(value)
|
|
40
|
+
Tairu::Tile.new(Base64.decode64(tile_hash['tile']), tile_hash['format'])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/tairu/configuration.rb
CHANGED
|
@@ -1,54 +1,69 @@
|
|
|
1
1
|
require 'yaml'
|
|
2
2
|
|
|
3
3
|
module Tairu
|
|
4
|
-
|
|
5
|
-
attr_accessor :layers, :cache, :name
|
|
4
|
+
module Configuration
|
|
5
|
+
attr_accessor :layers, :cache, :name, :tilesets
|
|
6
6
|
|
|
7
|
-
def
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
def configure
|
|
8
|
+
yield self
|
|
9
|
+
configure_layers
|
|
10
|
+
configure_cache
|
|
11
|
+
configure_tilesets
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
def configure_cache
|
|
15
|
+
if self.cache
|
|
16
|
+
unless self.cache.instance_of? Tairu::Cache
|
|
17
|
+
if self.cache.instance_of? Hash
|
|
18
|
+
if self.cache['type']
|
|
19
|
+
options = self.cache['options'] || nil
|
|
20
|
+
self.cache = start_cache(self.cache['type'], options)
|
|
21
|
+
else
|
|
22
|
+
raise RuntimeError.new('No cache type sepecified')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
18
26
|
else
|
|
19
|
-
|
|
27
|
+
self.cache = start_cache
|
|
20
28
|
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def configure_layers
|
|
32
|
+
raise RuntimeError.new('At least one layer must be specified') unless self.layers
|
|
33
|
+
end
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
def configure_tilesets
|
|
36
|
+
self.tilesets = {}
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
else
|
|
27
|
-
raise 'Layers must be specified.'
|
|
38
|
+
self.layers.each do |k,v|
|
|
39
|
+
self.tilesets[k] = Tairu::Store::TYPES[v['provider'].downcase].new(k)
|
|
28
40
|
end
|
|
41
|
+
end
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
options = data['cache']['options'] ? data['cache']['options'] : {}
|
|
43
|
+
def config_from_file(file)
|
|
44
|
+
file = File.expand_path(file)
|
|
33
45
|
|
|
34
|
-
|
|
46
|
+
if File.exists?(file)
|
|
47
|
+
data = YAML.load_file(file)
|
|
48
|
+
else
|
|
49
|
+
raise 'Configuration file not found at specified location.'
|
|
35
50
|
end
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
config = Configuration.new(layers, cache, name)
|
|
52
|
+
raise RuntimeError.new('At least one layer must be specified') unless data['layers']
|
|
40
53
|
|
|
41
|
-
config
|
|
54
|
+
configure do |config|
|
|
55
|
+
config.name = data['name']
|
|
56
|
+
config.layers = data['layers']
|
|
57
|
+
config.cache = data['cache']
|
|
58
|
+
end
|
|
42
59
|
end
|
|
43
60
|
|
|
44
|
-
def
|
|
61
|
+
def start_cache(cache_type=nil, options=nil)
|
|
45
62
|
if cache_type
|
|
46
|
-
|
|
63
|
+
Tairu::Cache::TYPES[cache_type].new(options)
|
|
47
64
|
else
|
|
48
|
-
|
|
65
|
+
Tairu::Cache::Memory.new
|
|
49
66
|
end
|
|
50
|
-
|
|
51
|
-
cache
|
|
52
67
|
end
|
|
53
68
|
end
|
|
54
|
-
end
|
|
69
|
+
end
|
data/lib/tairu/coordinate.rb
CHANGED
|
File without changes
|
data/lib/tairu/server.rb
CHANGED
|
@@ -3,10 +3,23 @@ require File.join(File.dirname(__FILE__), '..', 'tairu')
|
|
|
3
3
|
|
|
4
4
|
module Tairu
|
|
5
5
|
class Server < Sinatra::Base
|
|
6
|
-
get '/:tileset
|
|
6
|
+
get '/:tileset/info' do
|
|
7
|
+
tileset = Tairu.tilesets[params[:tileset]]
|
|
8
|
+
|
|
9
|
+
status 404 if tileset.nil?
|
|
10
|
+
|
|
11
|
+
content_type :json
|
|
12
|
+
tileset.info.to_json
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
get '/:tileset/:zoom/:col/:row.grid.json' do
|
|
16
|
+
status 404 unless Tairu.layers[params[:tileset]]['provider'] == 'mbtiles'
|
|
17
|
+
|
|
18
|
+
tileset = Tairu.tilesets[params[:tileset]]
|
|
19
|
+
status 404 if tileset.nil?
|
|
20
|
+
|
|
7
21
|
coord = Tairu::Coordinate.new(Integer(params[:row]), Integer(params[:col]), Integer(params[:zoom]))
|
|
8
|
-
|
|
9
|
-
tile = Tairu::Store::MBTiles.get_grid(params[:tileset], tileset['tileset'], coord)
|
|
22
|
+
tile = tileset.get_grid(coord)
|
|
10
23
|
|
|
11
24
|
callback = params.delete('callback')
|
|
12
25
|
|
|
@@ -19,7 +32,7 @@ module Tairu
|
|
|
19
32
|
end
|
|
20
33
|
end
|
|
21
34
|
|
|
22
|
-
get '/:tileset/:zoom/:row
|
|
35
|
+
get '/:tileset/:zoom/:col/:row' do
|
|
23
36
|
coord = Tairu::Coordinate.new(Integer(params[:row]), Integer(params[:col]), Integer(params[:zoom]))
|
|
24
37
|
tile = Tairu.get_tile(params[:tileset], coord)
|
|
25
38
|
|
|
@@ -28,4 +41,4 @@ module Tairu
|
|
|
28
41
|
tile.data
|
|
29
42
|
end
|
|
30
43
|
end
|
|
31
|
-
end
|
|
44
|
+
end
|
data/lib/tairu/store.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
require 'store/mbtiles'
|
|
2
2
|
require 'store/esri'
|
|
3
|
+
require 'store/tms'
|
|
3
4
|
|
|
4
5
|
module Tairu
|
|
5
6
|
module Store
|
|
6
7
|
TYPES = {
|
|
7
8
|
'mbtiles' => Tairu::Store::MBTiles,
|
|
8
|
-
'esri' => Tairu::Store::Esri
|
|
9
|
+
'esri' => Tairu::Store::Esri,
|
|
10
|
+
'tms' => Tairu::Store::TMS
|
|
9
11
|
}
|
|
10
12
|
end
|
|
11
13
|
end
|
data/lib/tairu/store/esri.rb
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
module Tairu
|
|
2
2
|
module Store
|
|
3
3
|
class Esri
|
|
4
|
-
def
|
|
5
|
-
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def self.encode_hex(int_value, length = 8)
|
|
9
|
-
length = "%02i" % length
|
|
10
|
-
"%#{length}x" % int_value
|
|
4
|
+
def initialize(layer)
|
|
5
|
+
@tileset = File.join(File.expand_path(Tairu.layers[layer]['location']), Tairu.layers[layer]['tileset'])
|
|
11
6
|
end
|
|
12
7
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
-
path = File.join(loc, tileset, 'Layers', '_alllayers', "L#{'%02i' % coord.zoom}", "R#{encode_hex(coord.row)}", "C#{encode_hex(coord.column)}.#{format}")
|
|
8
|
+
def get(coord, format='png')
|
|
9
|
+
path = File.join(@tileset, 'Layers', '_alllayers', "L#{'%02i' % coord.zoom}", "R#{encode_hex(coord.row)}", "C#{encode_hex(coord.column)}.#{format}")
|
|
16
10
|
|
|
17
11
|
return nil unless File.exists?(path)
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
f.flock(File::LOCK_UN)
|
|
26
|
-
end
|
|
13
|
+
data = File.open(path, 'r') do |f|
|
|
14
|
+
begin
|
|
15
|
+
f.flock(File::LOCK_SH)
|
|
16
|
+
f.read
|
|
17
|
+
ensure
|
|
18
|
+
f.flock(File::LOCK_UN)
|
|
27
19
|
end
|
|
28
20
|
end
|
|
29
21
|
|
|
30
22
|
mime_type = "image/#{format}"
|
|
31
|
-
Tairu::Tile.new(data, mime_type)
|
|
23
|
+
tile = data.nil? ? nil : Tairu::Tile.new(data, mime_type)
|
|
24
|
+
tile
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def decode_hex(hex_value)
|
|
28
|
+
Integer("0x#{hex_value}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def encode_hex(int_value, length=8)
|
|
32
|
+
length = "%02i" % length
|
|
33
|
+
"%#{length}x" % int_value
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
|
-
end
|
|
37
|
+
end
|
data/lib/tairu/store/mbtiles.rb
CHANGED
|
@@ -4,170 +4,68 @@ require 'zlib'
|
|
|
4
4
|
module Tairu
|
|
5
5
|
module Store
|
|
6
6
|
class MBTiles
|
|
7
|
-
def initialize
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
buf = zstream.inflate(str)
|
|
12
|
-
zstream.finish
|
|
13
|
-
zstream.close
|
|
14
|
-
buf
|
|
7
|
+
def initialize(layer)
|
|
8
|
+
tileset = File.join(File.expand_path(Tairu.layers[layer]['location']), Tairu.layers[layer]['tileset'])
|
|
9
|
+
conn = defined?(JRUBY_VERSION) ? "jdbc:sqlite:#{tileset}" : "sqlite://#{tileset}"
|
|
10
|
+
@db = Sequel.connect(conn, max_connections: 1)
|
|
15
11
|
end
|
|
16
12
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
conn = defined?(JRUBY_VERSION) ? "jdbc:sqlite:#{loc}" : "sqlite://#{loc}"
|
|
20
|
-
conn
|
|
13
|
+
def flip_y(zoom, y)
|
|
14
|
+
(2 ** zoom - 1) - y
|
|
21
15
|
end
|
|
22
16
|
|
|
23
|
-
def
|
|
24
|
-
unless %w{png jpg}.index format
|
|
25
|
-
raise "Format {#{format}} not supported. Must be 'png' or 'jpg' per MBTiles 1.1 spec."
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
conn = connection_string(layer)
|
|
29
|
-
db = Sequel.connect(File.join(conn, file))
|
|
30
|
-
|
|
31
|
-
unless db.table_exists?(:metadata)
|
|
32
|
-
db.create_table :metadata do
|
|
33
|
-
primary_key :name
|
|
34
|
-
String :name
|
|
35
|
-
String :value
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
db.add_index :metadata, :name, unique: true
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
unless db.table_exists?(:tiles)
|
|
42
|
-
db.create_table :tiles do
|
|
43
|
-
Integer :zoom_level
|
|
44
|
-
Integer :tile_column
|
|
45
|
-
Integer :tile_row
|
|
46
|
-
Blob :tile_data
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
db.add_index :tiles, [:zoom_level, :tile_column, :tile_row], unique: true
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
md = db[:metadata]
|
|
53
|
-
|
|
54
|
-
md.insert(name: 'name', value: name)
|
|
55
|
-
md.insert(name: 'type', value: type)
|
|
56
|
-
md.insert(name: 'version', value: version)
|
|
57
|
-
md.insert(name: 'description', value: description)
|
|
58
|
-
md.insert(name: 'format', value: format)
|
|
59
|
-
md.insert(name: 'bounds', value: bounds) unless bounds.nil?
|
|
60
|
-
|
|
61
|
-
db.disconnect
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def self.exists?(layer, file)
|
|
65
|
-
conn = connection_string(layer)
|
|
66
|
-
db = Sequel.connect(File.join(conn, file))
|
|
67
|
-
|
|
68
|
-
tv = db.tables + db.views
|
|
69
|
-
|
|
70
|
-
[:metadata, :tiles].each {|s| return false unless tv.index(s)}
|
|
71
|
-
|
|
72
|
-
exists = db[:metadata].first.nil? && db[:tiles].first.nil?
|
|
73
|
-
|
|
74
|
-
db.disconnect
|
|
75
|
-
|
|
76
|
-
exists
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def self.info(layer, file)
|
|
80
|
-
file_loc = File.join(File.expand_path(Tairu::CONFIG.layers[layer]['location']), file)
|
|
81
|
-
raise "Tileset does not exist." unless exists? file_loc
|
|
82
|
-
|
|
83
|
-
conn = connection_string(layer)
|
|
84
|
-
db = Sequel.connect(File.join(conn, file))
|
|
85
|
-
|
|
86
|
-
info = {}
|
|
87
|
-
|
|
88
|
-
%w{name type version description format bounds}.each do |key|
|
|
89
|
-
value = db[:metadata].where(name: key).first
|
|
90
|
-
info[key] = value[:value] unless value.nil?
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
db.disconnect
|
|
94
|
-
|
|
95
|
-
info
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def self.list(layer, file)
|
|
99
|
-
conn = connection_string(layer)
|
|
100
|
-
db = Sequel.connect(File.join(conn, file))
|
|
101
|
-
|
|
102
|
-
tiles = db[:tiles]
|
|
103
|
-
|
|
104
|
-
list = []
|
|
105
|
-
|
|
106
|
-
tiles.each do |tile|
|
|
107
|
-
list << [((2 ** tile[:zoom_level] - 1) - tile[:tile_row]), tile[:tile_column], tile[:zoom_level]]
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
db.disconnect
|
|
111
|
-
|
|
112
|
-
list
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def self.get(layer, file, coord, format = nil)
|
|
116
|
-
conn = connection_string(layer)
|
|
117
|
-
db = Sequel.connect(File.join(conn, file))
|
|
118
|
-
|
|
17
|
+
def get(coord, format=nil)
|
|
119
18
|
formats = {
|
|
120
19
|
'png' => 'image/png',
|
|
121
20
|
'jpg' => 'image/jpg'
|
|
122
21
|
}
|
|
123
22
|
|
|
124
|
-
format = db[:metadata].select(:value).where(name: 'format').first
|
|
23
|
+
format = @db[:metadata].select(:value).where(name: 'format').first
|
|
125
24
|
mime_type = format.nil? ? formats['png'] : formats[format[:value]]
|
|
126
25
|
|
|
127
|
-
|
|
128
|
-
tile = db[:tiles].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
|
|
26
|
+
tile = @db[:tiles].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: flip_y(coord.zoom, coord.row))
|
|
129
27
|
|
|
130
28
|
tile_data = tile.first.nil? ? nil : Tairu::Tile.new(tile.first[:tile_data], mime_type)
|
|
131
|
-
|
|
132
|
-
db.disconnect
|
|
133
|
-
|
|
134
29
|
tile_data
|
|
135
30
|
end
|
|
136
31
|
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
db = Sequel.connect(File.join(conn, file))
|
|
140
|
-
|
|
141
|
-
tile_row = (2 ** coord.zoom - 1) - coord.row
|
|
142
|
-
grid = db[:grids].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
|
|
32
|
+
def get_grid(coord)
|
|
33
|
+
grid = @db[:grids].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: flip_y(coord.zoom, coord.row))
|
|
143
34
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
utf_grid = JSON.parse(grid_buf)
|
|
35
|
+
utf_grid = JSON.parse(inflate(grid.first[:grid]))
|
|
147
36
|
utf_grid[:data] = {}
|
|
148
37
|
|
|
149
|
-
grid_data = db[:grid_data].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
|
|
150
|
-
|
|
38
|
+
grid_data = @db[:grid_data].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
|
|
39
|
+
|
|
151
40
|
grid_data.each do |gd|
|
|
152
41
|
utf_grid[:data][gd[:key_name]] = JSON.parse(gd[:key_json])
|
|
153
42
|
end
|
|
154
43
|
|
|
155
|
-
db.disconnect
|
|
156
|
-
|
|
157
44
|
utf_grid
|
|
158
45
|
end
|
|
159
46
|
|
|
160
|
-
def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
47
|
+
def get_legend
|
|
48
|
+
@db[:metadata].where(name: 'legend').first
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def info
|
|
52
|
+
info = {}
|
|
53
|
+
|
|
54
|
+
%w{name type version description format bounds attribution minzoom maxzoom template legend}.each do |key|
|
|
55
|
+
value = @db[:metadata].where(name: key).first
|
|
56
|
+
info[key] = value[:value] unless value.nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
info
|
|
164
60
|
end
|
|
165
61
|
|
|
166
|
-
def
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
62
|
+
def inflate(str)
|
|
63
|
+
zstream = Zlib::Inflate.new
|
|
64
|
+
buf = zstream.inflate(str)
|
|
65
|
+
zstream.finish
|
|
66
|
+
zstream.close
|
|
67
|
+
buf
|
|
170
68
|
end
|
|
171
69
|
end
|
|
172
70
|
end
|
|
173
|
-
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Tairu
|
|
2
|
+
module Store
|
|
3
|
+
class TMS
|
|
4
|
+
def initialize(layer)
|
|
5
|
+
@tileset = File.join(File.expand_path(Tairu.layers[layer]['location']), Tairu.layers[layer]['tileset'])
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def flip_y(z, y)
|
|
9
|
+
(2 ** z - 1) - y
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get(coord, format='png')
|
|
13
|
+
z = "#{coord.zoom}"
|
|
14
|
+
x = "#{coord.column}"
|
|
15
|
+
y = flip_y(coord.zoom, coord.row)
|
|
16
|
+
|
|
17
|
+
path = File.join(@tileset, z, x, "#{y}.#{format}")
|
|
18
|
+
|
|
19
|
+
return nil unless File.exists?(path)
|
|
20
|
+
|
|
21
|
+
data = File.open(path, 'r') do |f|
|
|
22
|
+
begin
|
|
23
|
+
f.flock(File::LOCK_SH)
|
|
24
|
+
f.read
|
|
25
|
+
ensure
|
|
26
|
+
f.flock(File::LOCK_UN)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
mime_type = "image/#{format}"
|
|
31
|
+
tile = data.nil? ? nil : Tairu::Tile.new(data, mime_type)
|
|
32
|
+
tile
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/tairu/tile.rb
CHANGED
|
File without changes
|
data/lib/tairu/version.rb
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
module Tairu
|
|
2
|
-
VERSION = '0.
|
|
3
|
-
end
|
|
2
|
+
VERSION = '0.10.0'
|
|
3
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tairu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,49 +9,113 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2013-04-06 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: sequel
|
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
|
-
- -
|
|
19
|
+
- - '='
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
|
-
version:
|
|
21
|
+
version: 3.46.0
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
25
|
none: false
|
|
26
26
|
requirements:
|
|
27
|
-
- -
|
|
27
|
+
- - '='
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version:
|
|
29
|
+
version: 3.46.0
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: redis
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - '='
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: 3.0.3
|
|
38
|
+
type: :runtime
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - '='
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 3.0.3
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: connection_pool
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - '='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 1.0.0
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - '='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 1.0.0
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: multi_json
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - '='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 1.7.2
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - '='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 1.7.2
|
|
30
78
|
- !ruby/object:Gem::Dependency
|
|
31
79
|
name: sinatra
|
|
32
80
|
requirement: !ruby/object:Gem::Requirement
|
|
33
81
|
none: false
|
|
34
82
|
requirements:
|
|
35
|
-
- -
|
|
83
|
+
- - '='
|
|
36
84
|
- !ruby/object:Gem::Version
|
|
37
|
-
version:
|
|
85
|
+
version: 1.4.2
|
|
38
86
|
type: :runtime
|
|
39
87
|
prerelease: false
|
|
40
88
|
version_requirements: !ruby/object:Gem::Requirement
|
|
41
89
|
none: false
|
|
42
90
|
requirements:
|
|
43
|
-
- -
|
|
91
|
+
- - '='
|
|
44
92
|
- !ruby/object:Gem::Version
|
|
45
|
-
version:
|
|
93
|
+
version: 1.4.2
|
|
46
94
|
- !ruby/object:Gem::Dependency
|
|
47
95
|
name: puma
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - '='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: 1.6.3
|
|
102
|
+
type: :runtime
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - '='
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: 1.6.3
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rake
|
|
48
112
|
requirement: !ruby/object:Gem::Requirement
|
|
49
113
|
none: false
|
|
50
114
|
requirements:
|
|
51
115
|
- - ! '>='
|
|
52
116
|
- !ruby/object:Gem::Version
|
|
53
117
|
version: '0'
|
|
54
|
-
type: :
|
|
118
|
+
type: :development
|
|
55
119
|
prerelease: false
|
|
56
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
121
|
none: false
|
|
@@ -60,7 +124,7 @@ dependencies:
|
|
|
60
124
|
- !ruby/object:Gem::Version
|
|
61
125
|
version: '0'
|
|
62
126
|
- !ruby/object:Gem::Dependency
|
|
63
|
-
name:
|
|
127
|
+
name: rspec
|
|
64
128
|
requirement: !ruby/object:Gem::Requirement
|
|
65
129
|
none: false
|
|
66
130
|
requirements:
|
|
@@ -80,8 +144,7 @@ email:
|
|
|
80
144
|
- scooterwadsworth@gmail.com
|
|
81
145
|
executables:
|
|
82
146
|
- tairu
|
|
83
|
-
extensions:
|
|
84
|
-
- ext/mkrf_conf.rb
|
|
147
|
+
extensions: []
|
|
85
148
|
extra_rdoc_files: []
|
|
86
149
|
files:
|
|
87
150
|
- README.md
|
|
@@ -89,21 +152,21 @@ files:
|
|
|
89
152
|
- bin/tairu
|
|
90
153
|
- lib/tairu/cache/disk.rb
|
|
91
154
|
- lib/tairu/cache/memory.rb
|
|
155
|
+
- lib/tairu/cache/redis_cache.rb
|
|
92
156
|
- lib/tairu/cache.rb
|
|
93
157
|
- lib/tairu/configuration.rb
|
|
94
158
|
- lib/tairu/coordinate.rb
|
|
95
|
-
- lib/tairu/images/404.png
|
|
96
159
|
- lib/tairu/server.rb
|
|
97
160
|
- lib/tairu/store/esri.rb
|
|
98
161
|
- lib/tairu/store/mbtiles.rb
|
|
162
|
+
- lib/tairu/store/tms.rb
|
|
99
163
|
- lib/tairu/store.rb
|
|
100
164
|
- lib/tairu/tile.rb
|
|
101
165
|
- lib/tairu/version.rb
|
|
102
166
|
- lib/tairu.rb
|
|
103
|
-
- ext/mkrf_conf.rb
|
|
104
167
|
homepage: https://github.com/scooterw/tairu
|
|
105
168
|
licenses:
|
|
106
|
-
-
|
|
169
|
+
- BSD
|
|
107
170
|
post_install_message:
|
|
108
171
|
rdoc_options: []
|
|
109
172
|
require_paths:
|
|
@@ -122,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
185
|
version: 1.3.6
|
|
123
186
|
requirements: []
|
|
124
187
|
rubyforge_project:
|
|
125
|
-
rubygems_version: 1.8.
|
|
188
|
+
rubygems_version: 1.8.23
|
|
126
189
|
signing_key:
|
|
127
190
|
specification_version: 3
|
|
128
191
|
summary: Simple Tile Server for Ruby
|
data/ext/mkrf_conf.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'rubygems/command.rb'
|
|
3
|
-
require 'rubygems/dependency_installer.rb'
|
|
4
|
-
|
|
5
|
-
begin
|
|
6
|
-
Gem::Command.build_args = ARGV
|
|
7
|
-
rescue NoMethodError
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
inst = Gem::DependencyInstaller.new
|
|
11
|
-
|
|
12
|
-
begin
|
|
13
|
-
if defined?(JRUBY_VERSION)
|
|
14
|
-
inst.install 'jdbc-sqlite3'
|
|
15
|
-
else
|
|
16
|
-
inst.install 'sqlite3'
|
|
17
|
-
end
|
|
18
|
-
rescue
|
|
19
|
-
exit(1)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
p File.expand_path(__FILE__)
|
|
23
|
-
|
|
24
|
-
f = File.open(File.join(File.expand_path(File.dirname(__FILE__)), 'Rakefile'), 'w')
|
|
25
|
-
f.write("task :default\n")
|
|
26
|
-
f.close
|
data/lib/tairu/images/404.png
DELETED
|
Binary file
|