mapcache 0.1.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/MIT-LICENCE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2010 Valentine Bichkovsky
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,31 @@
1
+ = mapcache
2
+
3
+ == Description
4
+
5
+ Interactive GoogleMaps browser, caches all downloaded tiles on disk.
6
+
7
+ == Install
8
+
9
+ gem install mapcache
10
+
11
+ == Features
12
+
13
+ * download map tiles as you browse the map
14
+ * show tiles already on disk for (zoom + 1)..(zoom + 8)
15
+ * export downloaded tiles in mgmaps[http://www.mgmaps.com] format
16
+
17
+ == Usage
18
+
19
+ * drag map to pan
20
+ * mouse wheel - zoom in/out
21
+ * m - toggle coverage
22
+ * [] - coverage zoom in/out
23
+ * e - export tiles to mgmaps
24
+
25
+ == Files and directories
26
+
27
+ config.yml - your settings are saved in this file
28
+
29
+ <current dir>/maps - this is where map tiles are stored
30
+
31
+ <current dir>/export - this is where export goes to
data/TODO ADDED
@@ -0,0 +1,21 @@
1
+ - менять курсор в зависимости от действий пользователя, использовать "песочные часы" для длительных операций;
2
+
3
+ --------------------------
4
+
5
+ - возможность выделять области на карте и сохранять эти выделения. Область выделяется кистью в форме квадрата с привязкой к координатной сетке плиток. Размер квадрата можно менять (увеличивать и уменьшать в 2 раза, примерно как кисть в Фотошопе);
6
+
7
+ - экспорт для выделенной области;
8
+
9
+ - массовое скачивание плиток для выделенной области (возможно, с использованием прокси или с генерацией файла для другой программы скачивания);
10
+
11
+ - отображение информации - сколько плиток в очереди на скачивание, сколько выделено, сколько потребуется скачать для текущего выделения, прогресс во время скачивания, сколько всего плиток есть для конкретного зума и т.п.
12
+
13
+ --------------------------
14
+
15
+ - поддержка различных карт, не только простых GoogleMaps;
16
+
17
+ - хранение файлов плиток в одном большом файле (FUSE, tar, zip, ...);
18
+
19
+ - запоминане отсутствующих плиток (404), чтобы не пытаться их каждый раз тянуть из сети. Можно отображать на карте другим цветом;
20
+
21
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/mapcache ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'wx'
3
+
4
+ Dir[File.dirname(__FILE__) + '/../lib/*.rb'].each do |src|
5
+ require src
6
+ end
7
+
8
+ APP_VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION')
9
+
10
+ Wx::App.run { MapFrame.new }
@@ -0,0 +1,45 @@
1
+ require 'net/http'
2
+ require 'thread'
3
+
4
+ class DownloadManager
5
+
6
+ @queue = Queue.new
7
+ @thread = Thread.new do
8
+ loop do
9
+ sleep if @queue.empty?
10
+ new_thread_from_queue
11
+ end
12
+ end
13
+
14
+ def self.enqueue(tile)
15
+ @queue << tile
16
+ @thread.run
17
+ end
18
+
19
+ def self.observer=(observer)
20
+ @observer = observer
21
+ end
22
+
23
+ private
24
+
25
+ def self.new_thread_from_queue
26
+ Thread.new do
27
+ tile = @queue.pop
28
+ dir = File.dirname(tile.path)
29
+ FileUtils.mkdir_p dir if !File.exist?(dir)
30
+ download(tile)
31
+ tile.load_image
32
+ @observer.tile_loaded(tile)
33
+ end
34
+ end
35
+
36
+ def self.download(tile)
37
+ uri = URI.parse(tile.url)
38
+ req = Net::HTTP::Get.new(uri.path, {"User-Agent" => "mapcache"})
39
+ res = Net::HTTP.new(uri.host).start {|http| http.request(req)}
40
+ if res.code == "200"
41
+ File.open(tile.path, 'wb') {|f| f.write(res.body)}
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,37 @@
1
+ class ExportDialog < Wx::Dialog
2
+
3
+ def tiles_per_file
4
+ @tpf_choice.string_selection.to_i
5
+ end
6
+
7
+ def hash_size
8
+ @hs_choice.string_selection.to_i
9
+ end
10
+
11
+ def initialize(parent)
12
+ super(parent, :title => 'Export')
13
+ grid = Wx::FlexGridSizer.new(2, 2, 5, 5)
14
+
15
+ label = Wx::StaticText.new(self, :label => "Tiles per file")
16
+ @tpf_choice = Wx::Choice.new(self, :choices => %w(1 8 16 32 64 256))
17
+ grid.add(label, 0, Wx::ALIGN_CENTER_VERTICAL|Wx::ALL, 5)
18
+ grid.add(@tpf_choice, 0, Wx::GROW|Wx::ALL, 5)
19
+
20
+ label = Wx::StaticText.new(self, :label => "Hash size")
21
+ @hs_choice = Wx::Choice.new(self, :choices => %w(1 5 11 23 47 97))
22
+ grid.add(label, 0, Wx::ALIGN_CENTER_VERTICAL|Wx::ALL, 5)
23
+ grid.add(@hs_choice, 0, Wx::GROW|Wx::ALL, 5)
24
+
25
+ sizer = Wx::BoxSizer.new(Wx::VERTICAL)
26
+ sizer.add(grid, 0, Wx::ALL, 5)
27
+
28
+ button_sizer = create_button_sizer(Wx::OK|Wx::CANCEL)
29
+ sizer.add(button_sizer, 0, Wx::ALIGN_CENTER_VERTICAL|Wx::ALL, 5)
30
+
31
+ self.sizer = sizer
32
+ sizer.fit(self)
33
+
34
+ evt_choice(@tpf_choice) {|event| @hs_choice.string_selection = '1' if tiles_per_file != 1}
35
+ evt_choice(@hs_choice) {|event| @tpf_choice.string_selection = '1' if hash_size != 1}
36
+ end
37
+ end
data/lib/map_frame.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'yaml'
2
+
3
+ class MapFrame < Wx::Frame
4
+
5
+ DEFAULT_CONFIG = {'width' => 512, 'height' => 512, 'left' => 0, 'top' => 0,
6
+ 'map' => {'left_col' => 288, 'top_row' => 155, 'offset_x' => 0, 'offset_y' => 0,
7
+ 'zoom' => 9, 'show_cov' => true, 'cov_zoom' => 10} }
8
+
9
+ def initialize
10
+ config = load_config
11
+ super(nil, :title => "mapcache",
12
+ :pos => [config['left'], config['top']],
13
+ :size => [config['width'], config['height']] )
14
+
15
+ @status = Wx::StatusBar.new(self)
16
+ @status.fields_count = 3
17
+ @status.set_status_text("press F1 for help", 2)
18
+ self.status_bar = @status
19
+
20
+ box = Wx::HBoxSizer.new
21
+ @panel = MapPanel.new(self, config['map'])
22
+ box.add(@panel, 1, Wx::EXPAND)
23
+ self.sizer = box
24
+
25
+ evt_close do |event|
26
+ save_config
27
+ destroy
28
+ end
29
+
30
+ show
31
+ end
32
+
33
+ def set_zoom(value)
34
+ @status.set_status_text("zoom: #{value}", 0)
35
+ end
36
+
37
+ def set_coverage_zoom(value)
38
+ text = value ? "coverage: #{value}" : ""
39
+ @status.set_status_text(text, 1)
40
+ end
41
+
42
+ private
43
+
44
+ def load_config
45
+ File.exist?('config.yml') ? YAML::load_file('config.yml') : DEFAULT_CONFIG
46
+ end
47
+
48
+ def save_config
49
+ config = {
50
+ 'left' => self.position.x,
51
+ 'top' => self.position.y,
52
+ 'width' => self.size.width,
53
+ 'height' => self.size.height,
54
+ 'map' => @panel.config
55
+ }
56
+ open('config.yml', 'w') { |f| YAML.dump(config, f) }
57
+ end
58
+
59
+ end
data/lib/map_panel.rb ADDED
@@ -0,0 +1,140 @@
1
+ class MapPanel < Wx::Panel
2
+
3
+ def initialize(parent, config)
4
+ super(parent, :style => Wx::NO_BORDER)
5
+ @pan = false
6
+ DownloadManager.observer = self
7
+ @mgr = MatrixManager.new(self.size.width, self.size.height, config)
8
+
9
+ evt_paint {|event| draw_map}
10
+
11
+ evt_size {|event| resize_map(event.size.width, event.size.height) }
12
+
13
+ evt_char do |event|
14
+ case event.key_code
15
+ when ?m : toggle_coverage
16
+ when ?[ : coverage_zoom_out
17
+ when ?] : coverage_zoom_in
18
+ when ?e : export_dialog
19
+ when Wx::K_F1: about_box
20
+ end
21
+ end
22
+
23
+ evt_left_down do |event|
24
+ pan_start(event.x, event.y)
25
+ event.skip
26
+ end
27
+
28
+ evt_left_up {|event| pan_end}
29
+
30
+ evt_motion {|event| pan(event.x, event.y) }
31
+
32
+ evt_mousewheel do |event|
33
+ if event.wheel_rotation > 0
34
+ zoom_in(event.x, event.y)
35
+ else
36
+ zoom_out(event.x, event.y)
37
+ end
38
+ end
39
+
40
+ parent.set_zoom(@mgr.zoom)
41
+ parent.set_coverage_zoom(@mgr.cov_value)
42
+
43
+ set_focus
44
+ end
45
+
46
+ def config
47
+ @mgr.config
48
+ end
49
+
50
+ def tile_loaded(tile)
51
+ self.refresh
52
+ end
53
+
54
+ private
55
+
56
+ def draw_map
57
+ paint {|dc| @mgr.draw(dc) }
58
+ end
59
+
60
+ def toggle_coverage
61
+ draw_with_hourglass { @mgr.toggle_coverage }
62
+ parent.set_coverage_zoom(@mgr.cov_value)
63
+ end
64
+
65
+ def zoom_in(x, y)
66
+ draw_with_hourglass { @mgr.zoom_in(x, y) }
67
+ parent.set_zoom(@mgr.zoom)
68
+ parent.set_coverage_zoom(@mgr.cov_value)
69
+ end
70
+
71
+ def zoom_out(x, y)
72
+ draw_with_hourglass { @mgr.zoom_out(x, y) }
73
+ parent.set_zoom(@mgr.zoom)
74
+ parent.set_coverage_zoom(@mgr.cov_value)
75
+ end
76
+
77
+ def coverage_zoom_in
78
+ draw_with_hourglass { @mgr.coverage_zoom_in }
79
+ parent.set_coverage_zoom(@mgr.cov_value)
80
+ end
81
+
82
+ def coverage_zoom_out
83
+ draw_with_hourglass { @mgr.coverage_zoom_out }
84
+ parent.set_coverage_zoom(@mgr.cov_value)
85
+ end
86
+
87
+ def export_dialog
88
+ d = ExportDialog.new(parent)
89
+ if d.show_modal == Wx::ID_OK
90
+ MGMapsExport.export(d.tiles_per_file, d.hash_size)
91
+ end
92
+ end
93
+
94
+ def resize_map(width, height)
95
+ @mgr.resize(width, height)
96
+ end
97
+
98
+ def pan_start(x, y)
99
+ @pan = true
100
+ @start_x = x
101
+ @start_y = y
102
+ end
103
+
104
+ def pan_end
105
+ @pan = false
106
+ end
107
+
108
+ def pan(x, y)
109
+ if @pan
110
+ @mgr.pan(x - @start_x, y - @start_y)
111
+ @start_x = x
112
+ @start_y = y
113
+ draw_map
114
+ end
115
+ end
116
+
117
+ def draw_with_hourglass
118
+ self.cursor = Wx::HOURGLASS_CURSOR
119
+ yield
120
+ draw_map
121
+ self.cursor = Wx::NULL_CURSOR
122
+ end
123
+
124
+ def about_box
125
+ box = Wx::AboutDialogInfo.new
126
+ box.name = "mapcache"
127
+ box.version = APP_VERSION
128
+ box.description = "
129
+ click and drag to pan
130
+ mouse wheel - zoom in/out
131
+ m - toggle coverage
132
+ [] - coverage zoom in/out
133
+ e - export to mgmaps
134
+ for more info visit http://wiki.github.com/vbichkovsky/mapcache/"
135
+ box.add_developer 'Valentine Bichkovsky'
136
+ box.web_site = "http://wiki.github.com/vbichkovsky/mapcache/"
137
+ Wx::about_box(box)
138
+ end
139
+
140
+ end
@@ -0,0 +1,189 @@
1
+ TILE_WIDTH = 256
2
+ MAX_ZOOM = 19
3
+ MAX_ZOOM_DIFF = 8
4
+
5
+ class MatrixManager
6
+ attr_reader :top_row, :left_col, :offset_x, :offset_y, :zoom, :show_cov, :cov_zoom
7
+
8
+ def initialize(width, height, config)
9
+ @show_cov = config['show_cov']
10
+ @cov_zoom = config['cov_zoom']
11
+ @zoom = config['zoom']
12
+ @left_col = config['left_col']
13
+ @top_row = config['top_row']
14
+ @offset_x = config['offset_x']
15
+ @offset_y = config['offset_y']
16
+ @width = width
17
+ @height = height
18
+ create_matrix
19
+ end
20
+
21
+ def config
22
+ result = {}
23
+ %w(left_col top_row offset_x offset_y zoom show_cov cov_zoom).each do |attr|
24
+ result[attr] = self.send(attr)
25
+ end
26
+ result
27
+ end
28
+
29
+ def zoom_in(x, y)
30
+ if @zoom < MAX_ZOOM
31
+ @zoom += 1
32
+ @cov_zoom = zoom + 1 if cov_zoom - zoom < 1
33
+ @offset_x, @left_col = calc_start_and_offset_zoom_in(@left_col, x, @offset_x, @width)
34
+ @offset_y, @top_row = calc_start_and_offset_zoom_in(@top_row, y, @offset_y, @height)
35
+ create_matrix
36
+ end
37
+ end
38
+
39
+ def zoom_out(x, y)
40
+ if @zoom > 0
41
+ @zoom -= 1
42
+ @cov_zoom = zoom + MAX_ZOOM_DIFF if cov_zoom - zoom > MAX_ZOOM_DIFF
43
+ @offset_x, @left_col = calc_start_and_offset_zoom_out(@left_col, x, @offset_x, @width)
44
+ @offset_y, @top_row = calc_start_and_offset_zoom_out(@top_row, y, @offset_y, @height)
45
+ create_matrix
46
+ end
47
+ end
48
+
49
+ def draw(dc)
50
+ @matrix.each do |col, row, tile|
51
+ tile.draw(dc, TILE_WIDTH * (col - 1) + @offset_x, TILE_WIDTH * (row - 1) + @offset_y)
52
+ end
53
+ end
54
+
55
+ def toggle_coverage
56
+ @show_cov = !show_cov
57
+ recreate_coverage
58
+ end
59
+
60
+ def coverage_zoom_in
61
+ if cov_zoom - zoom < MAX_ZOOM_DIFF
62
+ @cov_zoom += 1
63
+ recreate_coverage
64
+ end
65
+ end
66
+
67
+ def coverage_zoom_out
68
+ if cov_zoom - zoom > 1
69
+ @cov_zoom -= 1
70
+ recreate_coverage
71
+ end
72
+ end
73
+
74
+ def pan(dx, dy)
75
+ @offset_x += dx
76
+ @offset_y += dy
77
+
78
+ if @offset_x < 0
79
+ @offset_x = TILE_WIDTH + @offset_x
80
+ @left_col += 1
81
+ @matrix.shift_left(get_tiles(:column, :last) )
82
+ elsif @offset_x >= TILE_WIDTH
83
+ @offset_x -= TILE_WIDTH
84
+ @left_col -= 1
85
+ @matrix.shift_right(get_tiles(:column, :first) )
86
+ end
87
+
88
+ if @offset_y < 0
89
+ @offset_y = TILE_WIDTH + @offset_y
90
+ @top_row += 1
91
+ @matrix.shift_up(get_tiles(:row, :last) )
92
+ elsif @offset_y >= TILE_WIDTH
93
+ @offset_y -= TILE_WIDTH
94
+ @top_row -= 1
95
+ @matrix.shift_down(get_tiles(:row, :first) )
96
+ end
97
+ end
98
+
99
+ def resize(viewport_width, viewport_height)
100
+ @width = viewport_width
101
+ @height = viewport_height
102
+ new_matrix_width = (viewport_width.to_f / TILE_WIDTH).ceil + 2
103
+ new_matrix_height = (viewport_height.to_f / TILE_WIDTH).ceil + 2
104
+ if new_matrix_width < @matrix.width || new_matrix_height < @matrix.height
105
+ @matrix.reduce(new_matrix_width, new_matrix_height)
106
+ else
107
+ if new_matrix_width > @matrix.width
108
+ (@left_col + @matrix.width..@left_col + new_matrix_width - 1).each do |col|
109
+ column = []
110
+ (@top_row..@top_row + @matrix.height - 1).each do |row|
111
+ column << get_tile(col, row, @zoom)
112
+ end
113
+ @matrix.add_column(column)
114
+ end
115
+ end
116
+ if new_matrix_height > @matrix.height
117
+ (@top_row + @matrix.height..@top_row + new_matrix_height - 1).each do |r|
118
+ row = []
119
+ (@left_col..@left_col + new_matrix_width - 1).each do |col|
120
+ row << get_tile(col, r, @zoom)
121
+ end
122
+ @matrix.add_row(row)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def cov_value
129
+ show_cov && cov_zoom
130
+ end
131
+
132
+ private
133
+
134
+ def calc_start_and_offset_zoom_in(start_idx, cursor_pos, offset, viewport_size)
135
+ middle_tile_idx = start_idx * 2 + 2 + (cursor_pos - offset).to_i / (TILE_WIDTH / 2)
136
+ from_edge = 2 * ( (cursor_pos - offset).to_i % (TILE_WIDTH / 2) )
137
+ calc_offset_and_first_idx(middle_tile_idx, viewport_size, from_edge)
138
+ end
139
+
140
+ def calc_start_and_offset_zoom_out(start_idx, cursor_pos, offset, viewport_size)
141
+ cursor_tile_idx = start_idx + (cursor_pos - offset).to_i / TILE_WIDTH + 1
142
+ middle_tile_idx = cursor_tile_idx / 2
143
+ from_edge = ( (cursor_pos - offset).to_i % TILE_WIDTH +
144
+ (cursor_tile_idx.even? ? 0 : TILE_WIDTH) ) / 2
145
+ calc_offset_and_first_idx(middle_tile_idx, viewport_size, from_edge)
146
+ end
147
+
148
+ def calc_offset_and_first_idx(middle_idx, viewport_size, from_tile_edge)
149
+ [ (viewport_size / 2 - from_tile_edge) % TILE_WIDTH,
150
+ middle_idx - ( (viewport_size / 2 - from_tile_edge) / TILE_WIDTH ) - 1 ]
151
+ end
152
+
153
+ def create_matrix
154
+ @matrix = TileMatrix.new
155
+ @matrix[0,0] = get_tile(@left_col, @top_row, @zoom)
156
+ resize(@width, @height)
157
+ end
158
+
159
+ def get_tiles(what, which)
160
+ size = (what == :row ? @matrix.width - 1 : @matrix.height - 1)
161
+ col = @left_col
162
+ row = @top_row
163
+ if which == :last
164
+ what == :column ? col += @matrix.width - 1 : row += @matrix.height - 1
165
+ end
166
+ (0..size).map do |index|
167
+ get_tile(col + (what == :row ? index : 0),
168
+ row + (what == :column ? index : 0), @zoom)
169
+ end
170
+ end
171
+
172
+ def get_tile(col, row, zoom)
173
+ Tile.new(wraparound(col, zoom), wraparound(row, zoom), zoom, cov_value)
174
+ end
175
+
176
+ def wraparound(value, zoom)
177
+ max = 2 ** zoom
178
+ value = max - (value.abs % max) if value < 0
179
+ value = value % max if value >= max
180
+ value
181
+ end
182
+
183
+ def recreate_coverage
184
+ @matrix.each do |col, row, tile|
185
+ tile.create_coverage(show_cov && cov_zoom)
186
+ end
187
+ end
188
+
189
+ end
@@ -0,0 +1,146 @@
1
+ require 'pathname'
2
+
3
+ class MGMapsExport
4
+
5
+ MAPS_ROOT = Pathname.new("maps")
6
+ EXPORT_ROOT = Pathname.new("export")
7
+
8
+ def self.export(tiles_per_file, hash_size)
9
+ if EXPORT_ROOT.exist?
10
+ error_dlg('Cannot export: export directory already exists!')
11
+ return
12
+ end
13
+
14
+ files = Dir['maps/*/*.mgm']
15
+
16
+ catch :cancelled do
17
+ if tiles_per_file > 1
18
+ export_as_mtpf(files, tiles_per_file)
19
+ elsif hash_size > 1
20
+ export_as_hashed(files, hash_size)
21
+ else
22
+ export_as_copy(files)
23
+ end
24
+
25
+ create_conf_file(tiles_per_file, hash_size)
26
+ success_msg
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def self.error_dlg(msg)
33
+ Wx::MessageDialog.new(Wx::THE_APP.top_window, msg, 'Export',
34
+ Wx::OK|Wx::ICON_ERROR).show_modal
35
+ end
36
+
37
+ def self.success_msg
38
+ Wx::MessageDialog.new(Wx::THE_APP.top_window, 'Export completed', 'Export',
39
+ Wx::OK|Wx::ICON_INFORMATION).show_modal
40
+ end
41
+
42
+ def self.confirm(msg)
43
+ Wx::MessageDialog.new(Wx::THE_APP.top_window, msg, 'Export',
44
+ Wx::YES_NO|Wx::ICON_QUESTION).show_modal == Wx::ID_YES
45
+ end
46
+
47
+ def self.create_conf_file(tiles_per_file, hash_size)
48
+ File.open('export/cache.conf', 'w') do |file|
49
+ file.puts "version=3"
50
+ file.puts "tiles_per_file=#{tiles_per_file}"
51
+ file.puts "hash_size=#{hash_size}"
52
+ end
53
+ end
54
+
55
+ def self.export_each_file(files)
56
+ progress = Wx::ProgressDialog.new('Export', 'Processing files...',
57
+ files.size, Wx::THE_APP.top_window,
58
+ Wx::PD_AUTO_HIDE | Wx::PD_APP_MODAL |
59
+ Wx::PD_CAN_ABORT | Wx::PD_REMAINING_TIME)
60
+ files_done = 0
61
+ files.each do |file|
62
+ yield file
63
+ files_done += 1
64
+ if !progress.update(files_done)
65
+ if confirm('Cancel export?')
66
+ progress.destroy
67
+ throw :cancelled
68
+ else
69
+ progress.resume
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.export_as_copy(files)
76
+ export_each_file(files) do |file|
77
+ src = Pathname.new(file)
78
+ dest = EXPORT_ROOT + src.relative_path_from(MAPS_ROOT)
79
+ FileUtils.mkdir_p dest.dirname
80
+ FileUtils.cp file, dest
81
+ end
82
+ end
83
+
84
+ def self.export_as_hashed(files, hash_size)
85
+ export_each_file(files) do |file|
86
+ src = Pathname.new(file)
87
+ x, y = src.basename('.mgm').to_s.split('_').map{|n| n.to_i}
88
+ subdir = Pathname.new( ((x * 256 + y) % hash_size).to_s )
89
+ zoomdir = src.relative_path_from(MAPS_ROOT).dirname
90
+ dest = EXPORT_ROOT + zoomdir + subdir + src.basename
91
+ FileUtils.mkdir_p dest.dirname
92
+ FileUtils.cp file, dest
93
+ end
94
+ end
95
+
96
+ def self.export_as_mtpf(files, tiles_per_file)
97
+ tpf_x, tpf_y = tpf_xy(tiles_per_file)
98
+
99
+ export_each_file(files) do |file|
100
+ src = Pathname.new(file)
101
+ x, y = src.basename('.mgm').to_s.split('_').map{|n| n.to_i}
102
+ dest_x = x / tpf_x
103
+ dest_y = y / tpf_y
104
+ rel_x = x % tpf_x
105
+ rel_y = y % tpf_y
106
+
107
+ zoomdir = src.relative_path_from(MAPS_ROOT).dirname
108
+ dest = EXPORT_ROOT + zoomdir + Pathname.new("#{dest_x}_#{dest_y}.mgm")
109
+ blank_mtpf_file(dest, tiles_per_file) if !File.exists?(dest)
110
+ add_tile_to_file(src, dest, rel_x, rel_y)
111
+ end
112
+ end
113
+
114
+ def self.tpf_xy(tpf)
115
+ log2 = (Math.log(tpf) / Math.log(2)).to_i
116
+ if log2.even?
117
+ tpf_x = Math.sqrt(tpf).to_i
118
+ tpf_y = tpf_x
119
+ else
120
+ tpf_x = Math.sqrt(tpf * 2).to_i
121
+ tpf_y = tpf_x / 2
122
+ end
123
+ [tpf_x, tpf_y]
124
+ end
125
+
126
+ def self.blank_mtpf_file(dest, tiles_per_file)
127
+ FileUtils.mkdir_p dest.dirname if !dest.dirname.exist?
128
+ File.open(dest, 'w') { |f| f.write 0.chr * (2 + 6 * tiles_per_file) }
129
+ end
130
+
131
+ def self.add_tile_to_file(src, dest, rel_x, rel_y)
132
+ File.open(dest, 'r+') do |file|
133
+ count = file.read(2).unpack('n').first + 1
134
+ file.seek(0, IO::SEEK_END)
135
+ file.write(IO.read(src))
136
+ pos = file.pos
137
+ file.rewind
138
+ file.write [count].pack('n')
139
+ file.pos = 2 + 6 * (count - 1)
140
+ file.write rel_x.chr
141
+ file.write rel_y.chr
142
+ file.write [pos].pack('N')
143
+ end
144
+ end
145
+
146
+ end
data/lib/tile.rb ADDED
@@ -0,0 +1,82 @@
1
+ require 'fileutils'
2
+
3
+ class Tile
4
+ attr_reader :col, :row, :zoom
5
+
6
+ def initialize(col, row, zoom, cov_zoom)
7
+ @col = col
8
+ @row = row
9
+ @zoom = zoom
10
+ if File.exist?(path)
11
+ load_image
12
+ else
13
+ DownloadManager.enqueue(self)
14
+ end
15
+ create_coverage(cov_zoom)
16
+ end
17
+
18
+ def create_coverage(cov_zoom)
19
+ if !cov_zoom
20
+ @mask = nil
21
+ else
22
+ @mask = Wx::Bitmap.new(TILE_WIDTH, TILE_WIDTH)
23
+ @mask.draw do |dc|
24
+ dc.pen = Wx::TRANSPARENT_PEN
25
+ dc.brush = Wx::WHITE_BRUSH
26
+ dc.draw_rectangle(0, 0, TILE_WIDTH, TILE_WIDTH)
27
+ draw_subtiles(dc, cov_zoom)
28
+ end
29
+ end
30
+ end
31
+
32
+ def load_image
33
+ image = Wx::Image.new(path, Wx::BITMAP_TYPE_PNG)
34
+ @bitmap = Wx::Bitmap.from_image(image)
35
+ end
36
+
37
+ def draw(dc, x, y)
38
+ dc.pen = Wx::TRANSPARENT_PEN
39
+ dc.logical_function = Wx::COPY
40
+ if @bitmap
41
+ dc.draw_bitmap(@bitmap, x, y, false)
42
+ else
43
+ dc.brush = Wx::GREY_BRUSH
44
+ dc.draw_rectangle(x, y, TILE_WIDTH, TILE_WIDTH)
45
+ end
46
+ if @mask
47
+ dc.logical_function = Wx::AND
48
+ dc.draw_bitmap(@mask, x, y, false)
49
+ end
50
+ end
51
+
52
+ def url
53
+ server = (@col + 2 * @row) % 4
54
+ galileo = "Galileo"[0, (3 * @col + @row) % 8]
55
+ "http://mt#{server}.google.com/vt/lyrs=m@115&hl=en&x=#{@col}&y=#{@row}&z=#{@zoom}&s=#{galileo}"
56
+ end
57
+
58
+ def path
59
+ "maps/GoogleMap_#{zoom}/#{col}_#{row}.mgm"
60
+ end
61
+
62
+ private
63
+
64
+ def draw_subtiles(dc, cov_zoom)
65
+ dc.brush = Wx::GREEN_BRUSH
66
+ diff = cov_zoom - zoom
67
+ scale = 2 ** diff
68
+ Dir["maps/GoogleMap_#{cov_zoom}/*.mgm"].each do |file|
69
+ cov_col, cov_row = File.basename(file, '.mgm').split('_')
70
+ cov_col = cov_col.to_i
71
+ cov_row = cov_row.to_i
72
+ if ((cov_col / scale) == col) && ((cov_row / scale) == row)
73
+ subcol = cov_col % scale
74
+ subrow = cov_row % scale
75
+ dc.draw_rectangle(subcol * TILE_WIDTH / scale,
76
+ subrow * TILE_WIDTH / scale,
77
+ TILE_WIDTH / scale, TILE_WIDTH / scale)
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,73 @@
1
+ class TileMatrix
2
+
3
+ def initialize
4
+ @columns = []
5
+ end
6
+
7
+ def [](col, row)
8
+ @columns[col][row]
9
+ end
10
+
11
+ def[]=(col, row, value)
12
+ @columns[col] ||= []
13
+ @columns[col][row] = value
14
+ end
15
+
16
+ def width
17
+ @columns.size
18
+ end
19
+
20
+ def height
21
+ @columns[0].size
22
+ end
23
+
24
+ def each
25
+ @columns.each_with_index do |row, col_idx|
26
+ row.each_with_index do |value, row_idx|
27
+ yield col_idx, row_idx, value
28
+ end
29
+ end
30
+ end
31
+
32
+ def shift_left(new_column)
33
+ @columns.shift
34
+ @columns.push(new_column)
35
+ end
36
+
37
+ def shift_right(new_column)
38
+ @columns.delete_at(@columns.length - 1)
39
+ @columns.unshift(new_column)
40
+ end
41
+
42
+ def shift_up(new_row)
43
+ @columns.each_with_index do |c, i|
44
+ c.shift
45
+ c.push(new_row[i])
46
+ end
47
+ end
48
+
49
+ def shift_down(new_row)
50
+ @columns.each_with_index do |c, i|
51
+ c.delete_at(c.length - 1)
52
+ c.unshift(new_row[i])
53
+ end
54
+ end
55
+
56
+ def reduce(width, height)
57
+ @columns.slice!(width..@columns.length - 1)
58
+ @columns.each do |c|
59
+ c.slice!(height..c.length - 1)
60
+ end
61
+ end
62
+
63
+ def add_column(new_column)
64
+ @columns << new_column
65
+ end
66
+
67
+ def add_row(new_row)
68
+ @columns.each_with_index do |c, i|
69
+ c << new_row[i]
70
+ end
71
+ end
72
+
73
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mapcache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Valentine Bichkovsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-04 00:00:00 +02:00
13
+ default_executable: mapcache
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: wxruby
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: valentin@livas.lv
37
+ executables:
38
+ - mapcache
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - MIT-LICENCE
45
+ - README.rdoc
46
+ - TODO
47
+ - VERSION
48
+ - bin/mapcache
49
+ - lib/download_manager.rb
50
+ - lib/export_dialog.rb
51
+ - lib/map_frame.rb
52
+ - lib/map_panel.rb
53
+ - lib/matrix_manager.rb
54
+ - lib/mgmaps_export.rb
55
+ - lib/tile.rb
56
+ - lib/tile_matrix.rb
57
+ has_rdoc: true
58
+ homepage: http://wiki.github.com/vbichkovsky/mapcache/
59
+ licenses: []
60
+
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --charset=UTF-8
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.5
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Google Maps tile browser-downloader and manager
85
+ test_files: []
86
+