tairu 0.8.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 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: []