tairu 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ tairu ... simple map tile server
2
+
3
+ To play with the library in irb: `rake console`
data/bin/tairu ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'rack'
5
+ require 'puma'
6
+ require 'puma/server'
7
+
8
+ opts = {}
9
+
10
+ OptionParser.new do |o|
11
+ o.banner = 'Usage: tairu.rb [options]'
12
+
13
+ o.on('-c', '--config CONFIG', '') do |config|
14
+ opts[:config] = File.expand_path(config)
15
+ end
16
+
17
+ o.on('-p', '--port PORT', '') do |port|
18
+ opts[:port] = port
19
+ end
20
+
21
+ o.on('-h', '--host HOST', '') do |host|
22
+ opts[:host] = host
23
+ end
24
+ end.parse!
25
+
26
+ if opts[:config]
27
+ unless defined?(Tairu)
28
+ require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'tairu')
29
+ end
30
+
31
+ Tairu::CONFIG = Tairu::Configuration.new(opts[:config])
32
+ Tairu::CACHE = Tairu::CONFIG.cache
33
+ else
34
+ puts "No valid config file specified. Use -c / --config option."
35
+ exit(1)
36
+ end
37
+
38
+ app, options = Rack::Builder.parse_file File.join(File.expand_path(File.dirname(__FILE__)), '..', 'config.ru')
39
+
40
+ server = ::Puma::Server.new(app)
41
+ host = opts[:host] || '0.0.0.0'
42
+ port = opts[:port] || 8080
43
+ server.add_tcp_listener host, port
44
+ min_threads, max_threads = 0, 16
45
+ server.min_threads = min_threads
46
+ server.max_threads = max_threads
47
+
48
+ puts "Starting server on http://#{host}:#{port}"
49
+ puts "Min Threads: #{min_threads} / Max Threads: #{max_threads}"
50
+
51
+ begin
52
+ server.run.join
53
+ rescue Interrupt
54
+ server.stop(true)
55
+ end
data/ext/mkrf_conf.rb ADDED
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,8 @@
1
+ module Tairu
2
+ module Cache
3
+ class Disk
4
+ def initialize(options = nil)
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,34 @@
1
+ module Tairu
2
+ module Cache
3
+ class Memory
4
+ def initialize(options = nil)
5
+ @tiles = {}
6
+ end
7
+
8
+ def add(layer, coord, tile, age = 300) # format?
9
+ key = Tairu::Cache::Key.new(layer, coord)
10
+ expire = Time.now + age
11
+ @tiles[key] = {tile: tile, expire: expire}
12
+ :purge_expired_tiles
13
+ end
14
+
15
+ def get(layer, coord) # format?
16
+ key = Tairu::Cache::Key.new(layer, coord)
17
+ tile = @tiles[key]
18
+
19
+ return nil if tile.nil?
20
+
21
+ if tile[:expire] < Time.now
22
+ tile[:tile]
23
+ else
24
+ @tiles.delete(key)
25
+ return nil
26
+ end
27
+ end
28
+
29
+ def purge_expired
30
+ @tiles.delete_if {|k,v| v[:expire] > Time.now}
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,13 @@
1
+ require 'cache/memory'
2
+ require 'cache/disk'
3
+
4
+ module Tairu
5
+ module Cache
6
+ TYPES = {
7
+ 'memory' => Tairu::Cache::Memory,
8
+ 'disk' => Tairu::Cache::Disk
9
+ }
10
+
11
+ Key = Struct.new(:layer, :coord, :format)
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+
3
+ module Tairu
4
+ class Configuration
5
+ attr_accessor :data, :cache
6
+
7
+ def initialize(config = nil)
8
+ raise "No config file specified." unless config
9
+ @data = YAML.load_file(config)
10
+ @cache = start_cache
11
+ end
12
+
13
+ def start_cache
14
+ cache_type = @data['cache']['type']
15
+ cache_options = @data['cache']['options']
16
+
17
+ if cache_type
18
+ cache = Tairu::Cache::TYPES[cache_type].new(cache_options)
19
+ else
20
+ cache = Tairu::Cache::Memory.new
21
+ end
22
+
23
+ cache
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Tairu
2
+ class Coordinate
3
+ attr_accessor :row, :column, :zoom
4
+
5
+ def initialize(row, column, zoom)
6
+ @row = Integer(row)
7
+ @column = Integer(column)
8
+ @zoom = Integer(zoom)
9
+ end
10
+ end
11
+ end
Binary file
@@ -0,0 +1,31 @@
1
+ require 'sinatra/base'
2
+ require File.join(File.dirname(__FILE__), '..', 'tairu')
3
+
4
+ module Tairu
5
+ class Server < Sinatra::Base
6
+ get '/:tileset/:zoom/:row/:col.grid.json' do
7
+ coord = Tairu::Coordinate.new(Integer(params[:row]), Integer(params[:col]), Integer(params[:zoom]))
8
+ tileset = Tairu::CONFIG.data['cache']['layers'][params[:tileset]]
9
+ tile = Tairu::Store::MBTiles.get_grid(params[:tileset], tileset['tileset'], coord)
10
+
11
+ callback = params.delete('callback')
12
+
13
+ if callback
14
+ content_type :js
15
+ "#{callback}(#{tile.to_json})"
16
+ else
17
+ content_type :json
18
+ tile.to_json
19
+ end
20
+ end
21
+
22
+ get '/:tileset/:zoom/:row/:col' do
23
+ coord = Tairu::Coordinate.new(Integer(params[:row]), Integer(params[:col]), Integer(params[:zoom]))
24
+ tile = Tairu.get_tile(params[:tileset], coord)
25
+
26
+ response.headers['Content-Type'] = tile.mime_type
27
+ response.headers['Content-Disposition'] = 'inline'
28
+ tile.data
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ # Integer("0x0000000a") => 10
2
+ # 10.to_s(16) => "a"
3
+ # "%x" % 10 => "a"
4
+ # "%08x" % 10 => "0000000a"
5
+ # "#%02x%02x%02x" % [255, 0, 10] => #ff000a
6
+ # /Layers/_alllayers/Lxx/Rhex/Chex.fmt => L15/R0000000a/C0000000a.jpg
7
+ # http://services.arcgisonline.com/cache_im/l3_imagery_Prime_world_2D/Layers/_alllayers/L15/R000027f9/C00002d2c.jpg
8
+ # http://proceedings.esri.com/library/userconf/devsummit07/papers/building_and_using_arcgis_server_map_caches-best_practices.pdf
9
+
10
+ module Tairu
11
+ module Store
12
+ class Esri
13
+ def self.decode_hex(hex_value)
14
+ Integer("0x#{hex_value}")
15
+ end
16
+
17
+ def self.encode_hex(int_value, length = 8)
18
+ length = "%02i" % length
19
+ "%#{length}x" % int_value
20
+ end
21
+
22
+ def self.get(tileset, coord, format = 'jpg')
23
+ data = File.read(File.join(File.dirname(__FILE__), '..', '..', 'tilesets', tileset, 'Layers', '_alllayers', "L#{coord[:zoom]}", "R#{encode_hex(coord[:row])}", "C#{encode_hex(coord[:col])}.#{format}"))
24
+ mime_type = "image/#{format}"
25
+ Tairu::Tile.new(data, mime_type)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,173 @@
1
+ require 'json'
2
+ require 'zlib'
3
+
4
+ module Tairu
5
+ module Store
6
+ class MBTiles
7
+ def initialize;end
8
+
9
+ def self.inflate(str)
10
+ zstream = Zlib::Inflate.new
11
+ buf = zstream.inflate(str)
12
+ zstream.finish
13
+ zstream.close
14
+ buf
15
+ end
16
+
17
+ def self.connection_string(layer)
18
+ loc = File.expand_path(Tairu::CONFIG.data['cache']['layers'][layer]['location'])
19
+ conn = defined?(JRUBY_VERSION) ? "jdbc:sqlite:#{loc}" : "sqlite://#{loc}"
20
+ conn
21
+ end
22
+
23
+ def self.create(layer, file, name, type, version, description, format, bounds = nil)
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.data['cache']['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)
116
+ conn = connection_string(layer)
117
+ db = Sequel.connect(File.join(conn, file))
118
+
119
+ formats = {
120
+ 'png' => 'image/png',
121
+ 'jpg' => 'image/jpg'
122
+ }
123
+
124
+ format = db[:metadata].select(:value).where(name: 'format').first
125
+ mime_type = format.nil? ? formats['png'] : formats[format[:value]]
126
+
127
+ tile_row = (2 ** coord.zoom - 1) - coord.row
128
+ tile = db[:tiles].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
129
+
130
+ tile_data = tile.first.nil? ? nil : Tairu::Tile.new(tile.first[:tile_data], mime_type)
131
+
132
+ db.disconnect
133
+
134
+ tile_data
135
+ end
136
+
137
+ def self.get_grid(layer, file, coord)
138
+ conn = connection_string(layer)
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)
143
+
144
+ grid_buf = inflate(grid.first[:grid])
145
+
146
+ utf_grid = JSON.parse(grid_buf)
147
+ utf_grid[:data] = {}
148
+
149
+ grid_data = db[:grid_data].where(zoom_level: coord.zoom, tile_column: coord.column, tile_row: tile_row)
150
+
151
+ grid_data.each do |gd|
152
+ utf_grid[:data][gd[:key_name]] = JSON.parse(gd[:key_json])
153
+ end
154
+
155
+ db.disconnect
156
+
157
+ utf_grid
158
+ end
159
+
160
+ def self.remove(layer, file, coord)
161
+ conn = connection_string(layer)
162
+ db = Sequel.connect(File.join(conn, file))
163
+ db.disconnect
164
+ end
165
+
166
+ def self.add(layer, file, coord, tile_data)
167
+ conn = connection_string(layer)
168
+ db = Sequel.connect(File.join(conn, file))
169
+ db.disconnect
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,11 @@
1
+ require 'store/mbtiles'
2
+ require 'store/esri'
3
+
4
+ module Tairu
5
+ module Store
6
+ TYPES = {
7
+ 'mbtiles' => Tairu::Store::MBTiles,
8
+ 'esri' => Tairu::Store::Esri
9
+ }
10
+ end
11
+ end
data/lib/tairu/tile.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Tairu
2
+ class Tile
3
+ attr_accessor :data, :mime_type
4
+
5
+ def initialize(data, mime_type)
6
+ @data = data
7
+ @mime_type = mime_type
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Tairu
2
+ VERSION = '0.8.0'
3
+ end
data/lib/tairu.rb ADDED
@@ -0,0 +1,36 @@
1
+ $:.push File.expand_path(File.join(File.dirname(__FILE__), 'tairu'))
2
+
3
+ require 'sequel'
4
+ require 'cache'
5
+ require 'store'
6
+ require 'coordinate'
7
+ require 'tile'
8
+ require 'configuration'
9
+
10
+ module Tairu
11
+ TILE_404 = Tairu::Tile.new(File.read(File.join(File.expand_path(File.dirname(__FILE__)), 'tairu', 'images', '404.png')), 'image/png')
12
+
13
+ def self.get_tile(name, coord, format = nil)
14
+ tileset = Tairu::CONFIG.data['cache']['layers'][name]
15
+
16
+ unless tileset.nil?
17
+ tile = Tairu::CACHE.get(tileset, coord)
18
+
19
+ if tile.nil?
20
+ provider = Tairu::Store::TYPES[tileset['provider']]
21
+ provider_tile = provider.get(name, tileset['tileset'], coord)
22
+
23
+ unless provider_tile.nil?
24
+ tile = provider_tile
25
+ Tairu::CACHE.add(tileset, coord, tile)
26
+ else
27
+ tile = TILE_404
28
+ end
29
+ end
30
+ else
31
+ tile = TILE_404
32
+ end
33
+
34
+ tile
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tairu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Scooter Wadsworth
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sinatra
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
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: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: puma
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '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: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Simple Tile Server for Ruby
79
+ email:
80
+ - scooterwadsworth@gmail.com
81
+ executables:
82
+ - tairu
83
+ extensions:
84
+ - ext/mkrf_conf.rb
85
+ extra_rdoc_files: []
86
+ files:
87
+ - README.md
88
+ - bin/tairu
89
+ - lib/tairu/cache/disk.rb
90
+ - lib/tairu/cache/memory.rb
91
+ - lib/tairu/cache.rb
92
+ - lib/tairu/configuration.rb
93
+ - lib/tairu/coordinate.rb
94
+ - lib/tairu/images/404.png
95
+ - lib/tairu/server.rb
96
+ - lib/tairu/store/esri.rb
97
+ - lib/tairu/store/mbtiles.rb
98
+ - lib/tairu/store.rb
99
+ - lib/tairu/tile.rb
100
+ - lib/tairu/version.rb
101
+ - lib/tairu.rb
102
+ - ext/mkrf_conf.rb
103
+ homepage: https://github.com/scooterw/tairu
104
+ licenses:
105
+ - MIT
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: 1.9.2
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: 1.3.6
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.24
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Simple Tile Server for Ruby
128
+ test_files: []