tilecache 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +4 -0
- data/README.rdoc +85 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/lib/tile_cache/bounds.rb +44 -0
- data/lib/tile_cache/caches/disk.rb +53 -0
- data/lib/tile_cache/caches.rb +18 -0
- data/lib/tile_cache/layer.rb +122 -0
- data/lib/tile_cache/layers/map_server.rb +60 -0
- data/lib/tile_cache/layers.rb +21 -0
- data/lib/tile_cache/meta_layer.rb +79 -0
- data/lib/tile_cache/meta_tile.rb +31 -0
- data/lib/tile_cache/services/wms.rb +31 -0
- data/lib/tile_cache/services.rb +1 -0
- data/lib/tile_cache/tile.rb +28 -0
- data/lib/tile_cache.rb +112 -0
- metadata +70 -0
data/Changelog
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
Ruby-TileCache
|
2
|
+
by Pascal Ehlert, ODAdata
|
3
|
+
http://ruby-tilecache.rubyforge.org
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
ruby-tilecache is an implementation of TileCache from MetaCarta, written in pure Ruby.
|
8
|
+
It allows you to cache image tiles either rendered by MapScript or taken from another source.
|
9
|
+
Additionally, it supports meta-tiling which nearly eliminates label duplication issues
|
10
|
+
because of the tiling.
|
11
|
+
|
12
|
+
== FEATURES/PROBLEMS
|
13
|
+
|
14
|
+
* See description.
|
15
|
+
|
16
|
+
== SYNOPSIS
|
17
|
+
|
18
|
+
require 'tile_cache'
|
19
|
+
|
20
|
+
class MapController < ApplicationController
|
21
|
+
|
22
|
+
# This action handles WMS GetMap requests
|
23
|
+
def wms
|
24
|
+
wms = TileCache::Services::WMS.new(params)
|
25
|
+
map = wms.get_map
|
26
|
+
|
27
|
+
render :text => map.data, :content_type => map.layer.format
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
== REQUIREMENTS
|
32
|
+
|
33
|
+
* RMagick for meta-tiling support
|
34
|
+
* Ruby-MapScript for Mapserver layers
|
35
|
+
|
36
|
+
== INSTALL
|
37
|
+
|
38
|
+
* sudo gem install ruby-mapscript
|
39
|
+
|
40
|
+
== CONFIGURATION
|
41
|
+
|
42
|
+
If you are in a Rails environment, ruby-tilecache looks for a configuration file in config/tilecache.yml,
|
43
|
+
otherwise it searches ~/.tilecache.yml and /etc/tilecache.yml.
|
44
|
+
|
45
|
+
As the name suggests, the file format should be YAML, here is an example configuration:
|
46
|
+
|
47
|
+
cache:
|
48
|
+
type: DiskCache
|
49
|
+
root: '../tmp/mapcache'
|
50
|
+
|
51
|
+
basic:
|
52
|
+
type: MapServer
|
53
|
+
mapfile: app/map/nic.map
|
54
|
+
layers: NIC2,NIC1
|
55
|
+
maxresolution: 0.01991484375
|
56
|
+
levels: 5
|
57
|
+
extension: jpeg
|
58
|
+
metatile: true
|
59
|
+
metabuffer: 0, 0
|
60
|
+
bbox: -87.6903, 10.7075, -82.5921, 15.0259
|
61
|
+
|
62
|
+
In a Rails environment relative paths always refer to RAILS_ROOT, in other environments you should use absolute paths.
|
63
|
+
|
64
|
+
== LICENSE
|
65
|
+
|
66
|
+
Copyright (c) 2008 Pascal Ehlert, ODAdata
|
67
|
+
|
68
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
69
|
+
a copy of this software and associated documentation files (the
|
70
|
+
'Software'), to deal in the Software without restriction, including
|
71
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
72
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
73
|
+
permit persons to whom the Software is furnished to do so, subject to
|
74
|
+
the following conditions:
|
75
|
+
|
76
|
+
The above copyright notice and this permission notice shall be
|
77
|
+
included in all copies or substantial portions of the Software.
|
78
|
+
|
79
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
80
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
81
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
82
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
83
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
84
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
85
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gemspec|
|
7
|
+
gemspec.rubyforge_project = "tilecache"
|
8
|
+
gemspec.name = "tilecache"
|
9
|
+
gemspec.summary = "An implementation of TileCache from MetaCarta, written in pure Ruby"
|
10
|
+
#gemspec.description = "An implementation of TileCache from MetaCarta, written in pure Ruby"
|
11
|
+
gemspec.email = "pascal.ehlert@odadata.eu"
|
12
|
+
gemspec.homepage = "http://github.com/pehlert/ruby-tilecache"
|
13
|
+
gemspec.authors = ["Pascal Ehlert"]
|
14
|
+
gemspec.files.exclude ".gitignore"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module TileCache
|
2
|
+
class Bounds
|
3
|
+
attr_reader :minx, :miny, :maxx, :maxy
|
4
|
+
|
5
|
+
def self.parse_string(str)
|
6
|
+
new(*str.split(",").map { |s| Float(s.strip) })
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(minx = -180, miny = -90, maxx = 180, maxy = 90)
|
10
|
+
@minx = Float(minx)
|
11
|
+
@miny = Float(miny)
|
12
|
+
@maxx = Float(maxx)
|
13
|
+
@maxy = Float(maxy)
|
14
|
+
|
15
|
+
if (minx > maxx) || (miny > maxy)
|
16
|
+
raise TileCache::InvalidBounds, "Invalid Bounds: #{self}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
[@minx, @miny, @maxx, @maxy].join(", ")
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns this Bboxes resolution
|
25
|
+
def resolution(width = 256, height = 256)
|
26
|
+
return [(maxx - minx) / width,
|
27
|
+
(maxy - miny) / height].max
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the maximum resolution possible for a given tile width and height
|
31
|
+
def max_resolution(width = 256, height = 256)
|
32
|
+
b_width = (maxx - minx).to_f
|
33
|
+
b_height = (maxy - miny).to_f
|
34
|
+
|
35
|
+
if b_width >= b_height
|
36
|
+
aspect = (b_width / b_height).ceil
|
37
|
+
b_width / (width * aspect)
|
38
|
+
else
|
39
|
+
aspect = (b_height / b_width).ceil
|
40
|
+
b_height / (height * aspect)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module TileCache
|
2
|
+
module Caches
|
3
|
+
class Disk
|
4
|
+
REQUIRED_ATTRIBUTES = %w{ root }
|
5
|
+
|
6
|
+
def initialize(settings)
|
7
|
+
@root = settings[:root]
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(tile)
|
11
|
+
file = key_for_tile(tile)
|
12
|
+
|
13
|
+
if File.exist?(file)
|
14
|
+
File.open(file, File::RDONLY) { |f| tile.data = f.read }
|
15
|
+
return true
|
16
|
+
else
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def store(tile, data = nil)
|
22
|
+
tile.data = data if data
|
23
|
+
raise CacheError, "Called #store! with no data argument and no data associated with the tile." unless tile.data
|
24
|
+
|
25
|
+
# Create the full path to the cache file
|
26
|
+
file = key_for_tile(tile)
|
27
|
+
FileUtils.mkdir_p(File.dirname(file))
|
28
|
+
|
29
|
+
File.open(file, (File::WRONLY | File::CREAT)) do |f|
|
30
|
+
f.sync = true
|
31
|
+
f.write(tile.data)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
def key_for_tile(tile)
|
37
|
+
components = [
|
38
|
+
@root,
|
39
|
+
tile.layer.name,
|
40
|
+
sprintf("%02d", tile.z),
|
41
|
+
sprintf("%03d", Integer(tile.x / 1000000)),
|
42
|
+
sprintf("%03d", Integer((tile.x / 1000)) % 1000),
|
43
|
+
sprintf("%03d", Integer(tile.x) % 1000),
|
44
|
+
sprintf("%03d", Integer(tile.y / 1000000)),
|
45
|
+
sprintf("%03d", Integer((tile.y / 1000)) % 1000),
|
46
|
+
sprintf("%03d.%s", Integer(tile.y) % 1000, tile.layer.extension)
|
47
|
+
]
|
48
|
+
|
49
|
+
File.join(*components)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Preload all cache handler classes
|
2
|
+
Dir[File.dirname(__FILE__) + '/caches/*.rb'].each { |c| require c }
|
3
|
+
|
4
|
+
module TileCache
|
5
|
+
module Caches
|
6
|
+
class << self
|
7
|
+
def get_handler_class(cache_type)
|
8
|
+
class_name = cache_type.sub(/Cache$/, '')
|
9
|
+
|
10
|
+
if Caches.const_defined?(class_name)
|
11
|
+
Caches.const_get(class_name)
|
12
|
+
else
|
13
|
+
raise InvalidConfiguration, "Invalid cache type attribute: #{type}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'tile_cache/tile'
|
2
|
+
require 'tile_cache/meta_tile'
|
3
|
+
|
4
|
+
module TileCache
|
5
|
+
class Layer
|
6
|
+
DEFAULT_CONFIGURATION = {
|
7
|
+
:bbox => [-180, -90, 180, 90],
|
8
|
+
:srs => "EPSG:4326",
|
9
|
+
:description => "",
|
10
|
+
:size => [256, 256],
|
11
|
+
:levels => 20,
|
12
|
+
:extension => "png",
|
13
|
+
:metatile => false,
|
14
|
+
:metasize => [5, 5],
|
15
|
+
:metabuffer => [10, 10]
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_reader :name, :description, :layers, :bbox, :size, :units, :srs, :extension, :resolutions
|
19
|
+
|
20
|
+
def initialize(name, config)
|
21
|
+
@name = name
|
22
|
+
@description = config[:description]
|
23
|
+
@layers = config[:layers] || name
|
24
|
+
@bbox = config[:bbox].is_a?(String) ? TileCache::Bounds.parse_string(config[:bbox]) : TileCache::Bounds.new(*config[:bbox])
|
25
|
+
@size = config[:size].is_a?(String) ? config[:size].split(",").map { |s| Integer(s.strip) } : config[:size].map { |s| s.to_i }
|
26
|
+
@units = config[:units]
|
27
|
+
@srs = config[:srs]
|
28
|
+
|
29
|
+
@extension = config[:extension].downcase
|
30
|
+
@extension = 'jpeg' if @extension == 'jpg'
|
31
|
+
|
32
|
+
@resolutions = parse_resolutions(config[:resolutions], config[:minresolution], config[:maxresolution], config[:levels])
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a new tile for the given bounds
|
36
|
+
def get_tile(bbox)
|
37
|
+
coords = get_cell(bbox)
|
38
|
+
TileCache::Tile.new(self, *coords)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Fetch or render tile
|
42
|
+
def render(tile)
|
43
|
+
cache = ConfigParser.instance.cache
|
44
|
+
|
45
|
+
unless cache.get(tile)
|
46
|
+
data = render_tile(tile)
|
47
|
+
cache.store(tile, data)
|
48
|
+
end
|
49
|
+
|
50
|
+
return tile
|
51
|
+
end
|
52
|
+
|
53
|
+
def render_bbox(bbox)
|
54
|
+
case bbox
|
55
|
+
when String
|
56
|
+
bounds = Bounds.parse_string(bbox)
|
57
|
+
when Bounds
|
58
|
+
bounds = bbox
|
59
|
+
else
|
60
|
+
raise ArgumentError, "Invalid argument for bbox: #{bbox.inspect}"
|
61
|
+
end
|
62
|
+
|
63
|
+
tile = get_tile(bounds)
|
64
|
+
render(tile)
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# This returns the total number of columns and rows of the grid,
|
69
|
+
# based on the given z-indexes resolution
|
70
|
+
def grid_limits(z)
|
71
|
+
maxcols = (@bbox.maxx - @bbox.minx) / (@resolutions[z] * @size[0])
|
72
|
+
maxrows = (@bbox.maxy - @bbox.miny) / (@resolutions[z] * @size[1])
|
73
|
+
|
74
|
+
return [maxcols.to_i, maxrows.to_i]
|
75
|
+
end
|
76
|
+
|
77
|
+
def format
|
78
|
+
return "image/" + @extension
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
# Returns the z-index for the bboxes resolution
|
83
|
+
def level_for_bbox(bbox)
|
84
|
+
max_diff = bbox.resolution(*@size) / @size.max
|
85
|
+
|
86
|
+
if match = @resolutions.detect { |res| (res - bbox.resolution(*@size)).abs < max_diff }
|
87
|
+
@resolutions.index(match)
|
88
|
+
else
|
89
|
+
raise TileCache::InvalidResolution, "Can't find resolution index for #{bbox.resolution}. Available resolutions are #{@resolutions.join(', ')}."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns x, y and z coordinates for a given bbox
|
94
|
+
def get_cell(bbox)
|
95
|
+
# Get exact resolution as specified
|
96
|
+
z = level_for_bbox(bbox)
|
97
|
+
res = @resolutions[z]
|
98
|
+
|
99
|
+
x = ((bbox.minx - @bbox.minx) / (res * @size[0])).round
|
100
|
+
y = ((bbox.miny - @bbox.miny) / (res * @size[1])).round
|
101
|
+
|
102
|
+
return [x, y, z]
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
# Calculate resolutions unless given via configuration
|
107
|
+
def parse_resolutions(res_list, min_res, max_res, levels)
|
108
|
+
case res_list
|
109
|
+
when String
|
110
|
+
res_list.split(",").map { |r| Float(r.strip) }
|
111
|
+
when Array
|
112
|
+
res_list.map { |r| Float(r) }
|
113
|
+
when NilClass
|
114
|
+
max_res ||= @bbox.max_resolution(*@size)
|
115
|
+
(0..levels).map { |i| max_res.to_f / 2 ** i }
|
116
|
+
else
|
117
|
+
raise TileCache::Layers::InvalidConfiguration, "Invalid format of resolutions for layer #{@name}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'mapscript'
|
2
|
+
|
3
|
+
module TileCache
|
4
|
+
module Layers
|
5
|
+
class MapServer < TileCache::MetaLayer
|
6
|
+
include Mapscript
|
7
|
+
|
8
|
+
VALID_ATTRIBUTES = %w{ maxresolution levels extension metatile metabuffer bbox resolutions width height }
|
9
|
+
REQUIRED_ATTRIBUTES = %w{ mapfile layers }
|
10
|
+
|
11
|
+
attr_reader :mapfile
|
12
|
+
|
13
|
+
def initialize(name, config)
|
14
|
+
@mapfile = config[:mapfile]
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_tile(tile)
|
19
|
+
set_metabuffer if @metabuffer
|
20
|
+
req = build_request(tile)
|
21
|
+
|
22
|
+
msIO_installStdoutToBuffer
|
23
|
+
map.OWSDispatch(req)
|
24
|
+
msIO_stripStdoutBufferContentType
|
25
|
+
msIO_getStdoutBufferBytes
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def map
|
30
|
+
@map ||= MapObj.new(File.join(RAILS_ROOT, @mapfile))
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_metabuffer
|
34
|
+
# Don't override the mapfile settings!
|
35
|
+
begin
|
36
|
+
map.getMetaData("labelcache_map_edge_buffer")
|
37
|
+
rescue
|
38
|
+
buffer = -@metabuffer.max - 5
|
39
|
+
map.setMetaData("labelcache_map_edge_buffer", buffer.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_request(tile)
|
44
|
+
req = OWSRequest.new
|
45
|
+
|
46
|
+
req.setParameter("bbox", tile.bounds.to_s)
|
47
|
+
req.setParameter("width", tile.size[0].to_s)
|
48
|
+
req.setParameter("height", tile.size[1].to_s)
|
49
|
+
req.setParameter("srs", @srs)
|
50
|
+
req.setParameter("format", format)
|
51
|
+
req.setParameter("layers", @layers)
|
52
|
+
req.setParameter("request", "GetMap")
|
53
|
+
req.setParameter("service", "WMS")
|
54
|
+
req.setParameter("version", "1.1.1")
|
55
|
+
|
56
|
+
return req
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'tile_cache/layer'
|
2
|
+
require 'tile_cache/meta_layer'
|
3
|
+
|
4
|
+
# Preload layer classes
|
5
|
+
Dir[File.dirname(__FILE__) + '/layers/*.rb'].each { |c| require c }
|
6
|
+
|
7
|
+
module TileCache
|
8
|
+
module Layers
|
9
|
+
class << self
|
10
|
+
def get_handler_class(layer_type)
|
11
|
+
class_name = layer_type.sub(/Layer$/, '')
|
12
|
+
|
13
|
+
if Layers.const_defined?(class_name)
|
14
|
+
Layers.const_get(class_name)
|
15
|
+
else
|
16
|
+
raise InvalidConfiguration, "Invalid layer type attribute: #{type}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'RMagick'
|
3
|
+
|
4
|
+
module TileCache
|
5
|
+
class MetaLayer < TileCache::Layer
|
6
|
+
include Magick
|
7
|
+
|
8
|
+
attr_reader :metabuffer
|
9
|
+
|
10
|
+
def initialize(name, config)
|
11
|
+
super
|
12
|
+
|
13
|
+
@metatile = [true, 1, "true", "yes", "1"].include?(config[:metatile])
|
14
|
+
@metasize = config[:metasize].is_a?(String) ? config[:metasize].split(",").map { |s| Integer(s.strip) } : config[:metasize].map { |s| Integer(s) }
|
15
|
+
@metabuffer = config[:metabuffer].is_a?(String) ? config[:metabuffer].split(",").map { |s| Integer(s.strip) } : config[:metabuffer].map { |s| Integer(s) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Columns and rows for a given z-indexes resolution.
|
19
|
+
# Returns 1,1 if meta-tiling is disabled, otherwise the desired metasize or grid limits.
|
20
|
+
def meta_limits(z)
|
21
|
+
return [1, 1] unless @metatile
|
22
|
+
|
23
|
+
limits = grid_limits(z)
|
24
|
+
maxcols = [@metasize[0], limits[0] + 1].min
|
25
|
+
maxrows = [@metasize[1], limits[1] + 1].min
|
26
|
+
|
27
|
+
[maxcols, maxrows]
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_meta_tile(tile)
|
31
|
+
x = (tile.x / @metasize[0]).to_i
|
32
|
+
y = (tile.y / @metasize[1]).to_i
|
33
|
+
|
34
|
+
MetaTile.new(self, x, y, tile.z)
|
35
|
+
end
|
36
|
+
|
37
|
+
def render_meta_tile(metatile, tile)
|
38
|
+
data = render_tile(metatile)
|
39
|
+
image = Image.from_blob(data).first
|
40
|
+
|
41
|
+
(meta_cols, meta_rows) = meta_limits(metatile.z)
|
42
|
+
meta_height = meta_rows * @size[1] + 2 * @metabuffer[1]
|
43
|
+
|
44
|
+
0.upto(meta_cols-1) do |c|
|
45
|
+
0.upto(meta_rows-1) do |r|
|
46
|
+
minx = c * @size[0] + @metabuffer[0]
|
47
|
+
miny = r * @size[1] + @metabuffer[1]
|
48
|
+
|
49
|
+
subimage = image.crop(SouthWestGravity, minx, miny, @size[0], @size[1])
|
50
|
+
x = metatile.x * @metasize[0] + c
|
51
|
+
y = metatile.y * @metasize[1] + r
|
52
|
+
subtile = TileCache::Tile.new(self, x, y, metatile.z)
|
53
|
+
TileCache.cache.store(subtile, subimage.to_blob)
|
54
|
+
|
55
|
+
if x == tile.x && y == tile.y
|
56
|
+
tile.data = subimage.to_blob
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return tile.data
|
62
|
+
end
|
63
|
+
|
64
|
+
def render(tile)
|
65
|
+
if @metatile
|
66
|
+
metatile = get_meta_tile(tile)
|
67
|
+
|
68
|
+
unless TileCache.cache.get(tile)
|
69
|
+
render_meta_tile(metatile, tile)
|
70
|
+
end
|
71
|
+
|
72
|
+
return tile
|
73
|
+
else
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module TileCache
|
2
|
+
class MetaTile < TileCache::Tile
|
3
|
+
# Returns the tile's size without meta-buffers
|
4
|
+
def size_without_buffers
|
5
|
+
(meta_cols, meta_rows) = @layer.meta_limits(@z)
|
6
|
+
return [@layer.size[0] * meta_cols, @layer.size[1] * meta_rows]
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns this tile's size including buffers
|
10
|
+
def size
|
11
|
+
actual = size_without_buffers
|
12
|
+
return [actual[0] + @layer.metabuffer[0] * 2,
|
13
|
+
actual[1] + @layer.metabuffer[1] * 2]
|
14
|
+
end
|
15
|
+
|
16
|
+
def bounds
|
17
|
+
tilesize = size_without_buffers
|
18
|
+
res = @layer.resolutions[@z]
|
19
|
+
buffer = [res * @layer.metabuffer[0], res * @layer.metabuffer[1]]
|
20
|
+
meta_width = res * tilesize[0]
|
21
|
+
meta_height = res * tilesize[1]
|
22
|
+
|
23
|
+
minx = @layer.bbox.minx + @x * meta_width - buffer[0]
|
24
|
+
miny = @layer.bbox.miny + @y * meta_height - buffer[1]
|
25
|
+
maxx = minx + meta_width + 2 * buffer[0]
|
26
|
+
maxy = miny + meta_height + 2 * buffer[1]
|
27
|
+
|
28
|
+
return TileCache::Bounds.new(minx, miny, maxx, maxy)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module TileCache
|
2
|
+
module Services
|
3
|
+
class WMS
|
4
|
+
FIELDS = %{ bbox srs width height format layers styles }
|
5
|
+
|
6
|
+
def initialize(params)
|
7
|
+
@params = parse_request(params)
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_map
|
11
|
+
bbox = Bounds.parse_string(@params[:bbox])
|
12
|
+
layer = TileCache.layers[@params[:layers]]
|
13
|
+
# TODO: Move into config parser
|
14
|
+
raise LayerNotFound, "Can't find layer '#{@params[:layers]}' in configuration" unless layer
|
15
|
+
tile = layer.get_tile(bbox)
|
16
|
+
|
17
|
+
layer.render(tile)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def parse_request(params)
|
22
|
+
parsed = {}
|
23
|
+
params.each do |k, v|
|
24
|
+
key = k.downcase
|
25
|
+
parsed[key.to_sym] = v if FIELDS.include?(key)
|
26
|
+
end
|
27
|
+
parsed
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.dirname(__FILE__) + '/services/*.rb'].each { |c| require c }
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TileCache
|
2
|
+
class Tile
|
3
|
+
attr_reader :layer, :x, :y, :z, :size
|
4
|
+
attr_accessor :data
|
5
|
+
|
6
|
+
def initialize(layer, x, y, z)
|
7
|
+
@layer = layer
|
8
|
+
@x = x
|
9
|
+
@y = y
|
10
|
+
@z = z
|
11
|
+
@data = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def bounds
|
15
|
+
res = @layer.resolutions[@z]
|
16
|
+
minx = @layer.bbox.minx + (res * @x * @layer.size[0])
|
17
|
+
miny = @layer.bbox.miny + (res * @y * @layer.size[1])
|
18
|
+
maxx = @layer.bbox.minx + (res * (@x + 1) * @layer.size[0])
|
19
|
+
maxy = @layer.bbox.miny + (res * (@y + 1) * @layer.size[1])
|
20
|
+
|
21
|
+
return TileCache::Bounds.new(minx, miny, maxx, maxy)
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@layer.size
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/tile_cache.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'active_support/core_ext/hash/reverse_merge.rb'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'tile_cache/bounds'
|
8
|
+
require 'tile_cache/services'
|
9
|
+
require 'tile_cache/caches'
|
10
|
+
require 'tile_cache/layers'
|
11
|
+
|
12
|
+
module TileCache
|
13
|
+
CONFIG_PATHS = [
|
14
|
+
Pathname.new("/etc/tilecache.yml"),
|
15
|
+
Pathname.new("#{ENV['HOME']}/.tilecache.yml"),
|
16
|
+
Pathname.new("#{Dir.pwd}/config/tilecache.yml")
|
17
|
+
]
|
18
|
+
|
19
|
+
# Exception classes
|
20
|
+
class InvalidBounds < StandardError; end
|
21
|
+
class InvalidResolution < StandardError; end
|
22
|
+
class InvalidConfiguration < StandardError; end
|
23
|
+
class CacheError < StandardError; end
|
24
|
+
class LayerNotFound < StandardError; end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :cache, :layers
|
28
|
+
|
29
|
+
# Initialize TileCache framework, read out configuration and setup cache and layer classes
|
30
|
+
def initialize!
|
31
|
+
read_configuration
|
32
|
+
|
33
|
+
initialize_cache
|
34
|
+
initialize_layers_pool
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def read_configuration
|
39
|
+
# Select first existing path from hardcoded list
|
40
|
+
path = CONFIG_PATHS.reverse.find(&:exist?)
|
41
|
+
|
42
|
+
@config = YAML.load(File.read(path))
|
43
|
+
rescue => e
|
44
|
+
raise InvalidConfiguration, "Can't read configuration from #{CONFIG_PATHS.join(', ')}: #{e.message}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize_cache
|
48
|
+
config = @config.delete('cache')
|
49
|
+
raise InvalidConfiguration, "Missing cache section in your configuration file" unless config
|
50
|
+
config.symbolize_keys!
|
51
|
+
|
52
|
+
klass = Caches.get_handler_class(config.delete(:type))
|
53
|
+
validate_attributes!(klass, config)
|
54
|
+
merge_with_defaults!(klass, config)
|
55
|
+
|
56
|
+
TileCache.cache = klass.new(config)
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize_layers_pool
|
60
|
+
layers = @config.delete('layers')
|
61
|
+
raise InvalidConfiguration, "Please configure at least one layer" unless layers
|
62
|
+
|
63
|
+
TileCache.layers = {}
|
64
|
+
layers.each do |layer, settings|
|
65
|
+
settings.symbolize_keys!
|
66
|
+
|
67
|
+
klass = Layers.get_handler_class(settings.delete(:type))
|
68
|
+
validate_attributes!(klass, settings)
|
69
|
+
merge_with_defaults!(klass, settings)
|
70
|
+
|
71
|
+
TileCache.layers.store(layer, klass.new(layer, settings))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reads DEFAULT_CONFIGURATION hash from the class specified in settings[:class]
|
76
|
+
# and merges settings hash with these values. If no DEFAULT_CONFIGURATION has
|
77
|
+
# been specified, it doesn't change settings.
|
78
|
+
def merge_with_defaults!(klass, config)
|
79
|
+
defaults = klass.const_get("DEFAULT_CONFIGURATION").symbolize_keys rescue {}
|
80
|
+
config.reverse_merge!(defaults)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check for the validity of configuration attributes
|
84
|
+
# If any attributes that are not specified in the classes VALID_ATTRIBUTES
|
85
|
+
# constant appear in the configuration file or if there are attributes from REQUIRED_ATTRIBUTES missing
|
86
|
+
# this raises TileCache::InvalidConfiguration
|
87
|
+
def validate_attributes!(klass, config)
|
88
|
+
valid_attributes = klass.const_get("VALID_ATTRIBUTES") rescue []
|
89
|
+
required_attributes = klass.const_get("REQUIRED_ATTRIBUTES") rescue []
|
90
|
+
# Merge with required attributes so that we don't have to specify them twice
|
91
|
+
valid_attributes |= required_attributes
|
92
|
+
|
93
|
+
unless valid_attributes.empty?
|
94
|
+
config.except(:name).each_key do |a|
|
95
|
+
unless valid_attributes.include?(a.to_s)
|
96
|
+
raise InvalidConfiguration, "Unrecognized attribute in #{config[:name]}: #{a}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
unless required_attributes.empty?
|
102
|
+
required_attributes.each do |a|
|
103
|
+
unless config.include?(a.to_sym)
|
104
|
+
raise InvalidConfiguration, "Missing attribute in #{config[:name]}: #{a}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
TileCache.initialize!
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tilecache
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pascal Ehlert
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-26 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: pascal.ehlert@odadata.eu
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- Changelog
|
26
|
+
- README.rdoc
|
27
|
+
- Rakefile
|
28
|
+
- VERSION
|
29
|
+
- lib/tile_cache.rb
|
30
|
+
- lib/tile_cache/bounds.rb
|
31
|
+
- lib/tile_cache/caches.rb
|
32
|
+
- lib/tile_cache/caches/disk.rb
|
33
|
+
- lib/tile_cache/layer.rb
|
34
|
+
- lib/tile_cache/layers.rb
|
35
|
+
- lib/tile_cache/layers/map_server.rb
|
36
|
+
- lib/tile_cache/meta_layer.rb
|
37
|
+
- lib/tile_cache/meta_tile.rb
|
38
|
+
- lib/tile_cache/services.rb
|
39
|
+
- lib/tile_cache/services/wms.rb
|
40
|
+
- lib/tile_cache/tile.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/pehlert/ruby-tilecache
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project: tilecache
|
65
|
+
rubygems_version: 1.3.5
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: An implementation of TileCache from MetaCarta, written in pure Ruby
|
69
|
+
test_files: []
|
70
|
+
|