tilecache 0.0.3
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/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
|
+
|