gpx2exif 0.0.4 → 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/Gemfile +1 -1
- data/Gemfile.lock +3 -8
- data/README.md +28 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/bin/generate_garmin_waypoints +1 -1
- data/bin/geotag_all_images +2 -2
- data/bin/geotag_simulate +2 -2
- data/bin/gpx2png +57 -0
- data/lib/geotagger.rb +9 -0
- data/lib/{gpx2exif → geotagger}/exif_editor.rb +6 -6
- data/lib/{gpx2exif/geo_manager.rb → geotagger/geotagger.rb} +16 -10
- data/lib/geotagger/track_importer.rb +33 -0
- data/lib/gpx2exif.rb +3 -7
- data/lib/gpx2png/base.rb +30 -0
- data/lib/gpx2png/gpx2png.rb +210 -0
- data/lib/gpx2png/osm.rb +51 -0
- data/lib/gpx2png/osm_base.rb +280 -0
- data/lib/gpx2png/renderers/chunky_png_renderer.rb +61 -0
- data/lib/gpx2png/renderers/rmagick_renderer.rb +134 -0
- data/lib/gpx_utils.rb +9 -0
- data/lib/gpx_utils/track_importer.rb +63 -0
- data/lib/{garmin_utils/waypoint_list_generator.rb → gpx_utils/waypoints_exporter.rb} +2 -2
- data/lib/{garmin_utils/gpx_waypoint_parser.rb → gpx_utils/waypoints_importer.rb} +3 -3
- data/lib/mini_exiftool/mini_exiftool.rb +450 -0
- metadata +35 -23
- data/lib/garmin_utils.rb +0 -7
- data/lib/gpx2exif/gpx_parser.rb +0 -71
data/lib/gpx2png/osm.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/osm_base'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
module Gpx2png
|
7
|
+
class Osm < OsmBase
|
8
|
+
|
9
|
+
DEFAULT_RENDERER = :rmagick
|
10
|
+
attr_accessor :renderer
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
super
|
14
|
+
@renderer ||= DEFAULT_RENDERER
|
15
|
+
@r = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def save(filename)
|
19
|
+
render
|
20
|
+
@r.save(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_png
|
24
|
+
render
|
25
|
+
@r.to_png
|
26
|
+
end
|
27
|
+
|
28
|
+
def render
|
29
|
+
setup_renderer
|
30
|
+
initial_calculations
|
31
|
+
download_and_join_tiles
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :renderer_options
|
35
|
+
|
36
|
+
# Get proper renderer class
|
37
|
+
def setup_renderer
|
38
|
+
case @renderer
|
39
|
+
when :chunky_png
|
40
|
+
require 'gpx2png/renderers/chunky_png_renderer'
|
41
|
+
@r = ChunkyPngRenderer.new(@renderer_options)
|
42
|
+
when :rmagick
|
43
|
+
require 'gpx2png/renderers/rmagick_renderer'
|
44
|
+
@r = RmagickRenderer.new(@renderer_options)
|
45
|
+
else
|
46
|
+
raise ArgumentError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/base'
|
3
|
+
require 'net/http'
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
$:.unshift(File.dirname(__FILE__))
|
7
|
+
|
8
|
+
module Gpx2png
|
9
|
+
class OsmBase < Base
|
10
|
+
TILE_WIDTH = 256
|
11
|
+
TILE_HEIGHT = 256
|
12
|
+
|
13
|
+
# if true it will not download tiles
|
14
|
+
attr_accessor :simulate_download
|
15
|
+
|
16
|
+
# http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#X_and_Y
|
17
|
+
# Convert latlon deg to OSM tile coords
|
18
|
+
def self.convert(zoom, coord)
|
19
|
+
lat_deg, lon_deg = coord
|
20
|
+
lat_rad = deg2rad(lat_deg)
|
21
|
+
x = (((lon_deg + 180) / 360) * (2 ** zoom)).floor
|
22
|
+
y = ((1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math::PI) /2 * (2 ** zoom)).floor
|
23
|
+
|
24
|
+
return [x, y]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convert latlon deg to OSM tile url
|
28
|
+
# TODO add algorithm to choose from diff. servers
|
29
|
+
def self.url_convert(zoom, coord, server = 'b.')
|
30
|
+
x, y = convert(zoom, coord)
|
31
|
+
url(zoom, [x, y], server)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Convert OSM tile coords to url
|
35
|
+
def self.url(zoom, coord, server = 'b.')
|
36
|
+
x, y = coord
|
37
|
+
url = "http://#{server}tile.openstreetmap.org\/#{zoom}\/#{x}\/#{y}.png"
|
38
|
+
return url
|
39
|
+
end
|
40
|
+
|
41
|
+
# Convert OSM tile coords to latlon deg in top-left corner
|
42
|
+
def self.reverse_convert(zoom, coord)
|
43
|
+
x, y = coord
|
44
|
+
n = 2 ** zoom
|
45
|
+
lon_deg = x.to_f / n.to_f * 360.0 - 180.0
|
46
|
+
lat_deg = rad2deg(Math.atan(Math.sinh(Math::PI * (1.to_f - 2.to_f * y.to_f / n.to_f))))
|
47
|
+
return [lat_deg, lon_deg]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Lazy calc proper zoom for drawing
|
51
|
+
def self.calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height)
|
52
|
+
# because I'm lazy! :] and math is not my best side
|
53
|
+
|
54
|
+
last_zoom = 2
|
55
|
+
(5..18).each do |zoom|
|
56
|
+
# calculate drawing tile size and pixel size
|
57
|
+
tile_min = point_on_absolute_image(zoom, [lat_min, lon_min])
|
58
|
+
tile_max = point_on_absolute_image(zoom, [lat_max, lon_max])
|
59
|
+
current_pixel_x_distance = tile_max[0] - tile_min[0]
|
60
|
+
current_pixel_y_distance = tile_min[1] - tile_max[1]
|
61
|
+
if current_pixel_x_distance > width or current_pixel_y_distance > height
|
62
|
+
return last_zoom
|
63
|
+
end
|
64
|
+
last_zoom = zoom
|
65
|
+
end
|
66
|
+
return 18
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convert latlon deg coords to image point (x,y) and OSM tile coord
|
70
|
+
# return where you should put point on tile
|
71
|
+
def self.point_on_image(zoom, geo_coord)
|
72
|
+
osm_tile_coord = convert(zoom, geo_coord)
|
73
|
+
top_left_corner = reverse_convert(zoom, osm_tile_coord)
|
74
|
+
bottom_right_corner = reverse_convert(zoom, [
|
75
|
+
osm_tile_coord[0] + 1, osm_tile_coord[1] + 1
|
76
|
+
])
|
77
|
+
|
78
|
+
# some line math: y = ax + b
|
79
|
+
|
80
|
+
x_geo = geo_coord[1]
|
81
|
+
# offset
|
82
|
+
x_offset = x_geo - top_left_corner[1]
|
83
|
+
# scale
|
84
|
+
x_distance = (bottom_right_corner[1] - top_left_corner[1])
|
85
|
+
x = (TILE_WIDTH.to_f * (x_offset / x_distance)).round
|
86
|
+
|
87
|
+
y_geo = geo_coord[0]
|
88
|
+
# offset
|
89
|
+
y_offset = y_geo - top_left_corner[0]
|
90
|
+
# scale
|
91
|
+
y_distance = (bottom_right_corner[0] - top_left_corner[0])
|
92
|
+
y = (TILE_HEIGHT.to_f * (y_offset / y_distance)).round
|
93
|
+
|
94
|
+
return { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Useful for calculating distance on output image
|
98
|
+
# It is not position on output image because we don't know tile coords
|
99
|
+
# For upper-left tile
|
100
|
+
def self.point_on_absolute_image(zoom, geo_coord)
|
101
|
+
_p = point_on_image(zoom, geo_coord)
|
102
|
+
_x = _p[:osm_title_coord][0] * TILE_WIDTH + _p[:pixel_offset][0]
|
103
|
+
_y = _p[:osm_title_coord][1] * TILE_WIDTH + _p[:pixel_offset][1]
|
104
|
+
return [_x, _y]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create image with fixed size
|
108
|
+
def fixed_size(_width, _height)
|
109
|
+
@fixed_width = _width
|
110
|
+
@fixed_height = _height
|
111
|
+
end
|
112
|
+
|
113
|
+
def initial_calculations
|
114
|
+
@lat_min = @coords.collect { |c| c[:lat] }.min
|
115
|
+
@lat_max = @coords.collect { |c| c[:lat] }.max
|
116
|
+
@lon_min = @coords.collect { |c| c[:lon] }.min
|
117
|
+
@lon_max = @coords.collect { |c| c[:lon] }.max
|
118
|
+
|
119
|
+
# auto zoom must be here
|
120
|
+
# drawing must fit into fixed resolution
|
121
|
+
# map must be bigger than fixed resolution
|
122
|
+
if @fixed_width and @fixed_height
|
123
|
+
@new_zoom = self.class.calc_zoom(
|
124
|
+
@lat_min, @lat_max,
|
125
|
+
@lon_min, @lon_max,
|
126
|
+
@fixed_width, @fixed_height
|
127
|
+
)
|
128
|
+
puts "Calculated new zoom #{@new_zoom} (was #{@zoom})" if @verbose
|
129
|
+
@zoom = @new_zoom
|
130
|
+
end
|
131
|
+
|
132
|
+
@border_tiles = [
|
133
|
+
self.class.convert(@zoom, [@lat_min, @lon_min]),
|
134
|
+
self.class.convert(@zoom, [@lat_max, @lon_max])
|
135
|
+
]
|
136
|
+
|
137
|
+
@tile_x_range = (@border_tiles[0][0])..(@border_tiles[1][0])
|
138
|
+
@tile_y_range = (@border_tiles[1][1])..(@border_tiles[0][1])
|
139
|
+
|
140
|
+
# enlarging ranges to fill up map area
|
141
|
+
# both sizes are enlarged
|
142
|
+
# = ( ( (preferred size - real size) / tile width ) / 2 ).ceil
|
143
|
+
if @fixed_width and @fixed_height
|
144
|
+
x_axis_expand_count = ((@fixed_width - (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH).to_f / (TILE_WIDTH.to_f * 2.0)).ceil
|
145
|
+
y_axis_expand_count = ((@fixed_height - (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT).to_f / (TILE_HEIGHT.to_f * 2.0)).ceil
|
146
|
+
puts "Expanding X tiles from both sides #{x_axis_expand_count}" if @verbose
|
147
|
+
puts "Expanding Y tiles from both sides #{y_axis_expand_count}" if @verbose
|
148
|
+
@tile_x_range = ((@tile_x_range.min - x_axis_expand_count)..(@tile_x_range.max + x_axis_expand_count))
|
149
|
+
@tile_y_range = ((@tile_y_range.min - y_axis_expand_count)..(@tile_y_range.max + y_axis_expand_count))
|
150
|
+
end
|
151
|
+
|
152
|
+
# new/full image size
|
153
|
+
@full_image_x = (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH
|
154
|
+
@full_image_y = (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT
|
155
|
+
@r.x = @full_image_x
|
156
|
+
@r.y = @full_image_y
|
157
|
+
|
158
|
+
if @fixed_width and @fixed_height
|
159
|
+
calculate_for_crop_with_auto_zoom
|
160
|
+
else
|
161
|
+
calculate_for_crop
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Calculate zoom level
|
166
|
+
def auto_zoom_for(x = 0, y = 0)
|
167
|
+
# TODO
|
168
|
+
end
|
169
|
+
|
170
|
+
attr_reader :lat_min, :lat_max, :lon_min, :lon_max
|
171
|
+
attr_reader :tile_x_distance, :tile_y_distance
|
172
|
+
# points for cropping
|
173
|
+
attr_reader :bitmap_point_x_max, :bitmap_point_x_min, :bitmap_point_y_max, :bitmap_point_y_min
|
174
|
+
|
175
|
+
# Do everything
|
176
|
+
def download_and_join_tiles
|
177
|
+
puts "Output image dimension #{@full_image_x}x#{@full_image_y}" if @verbose
|
178
|
+
@r.new_image
|
179
|
+
|
180
|
+
# {:x, :y, :blob}
|
181
|
+
@images = Array.new
|
182
|
+
|
183
|
+
|
184
|
+
@tile_x_range.each do |x|
|
185
|
+
@tile_y_range.each do |y|
|
186
|
+
url = self.class.url(@zoom, [x, y])
|
187
|
+
|
188
|
+
# blob time
|
189
|
+
unless @simulate_download
|
190
|
+
uri = URI.parse(url)
|
191
|
+
response = Net::HTTP.get_response(uri)
|
192
|
+
blob = response.body
|
193
|
+
else
|
194
|
+
blob = @r.blank_tile(TILE_WIDTH, TILE_HEIGHT, x+y)
|
195
|
+
end
|
196
|
+
|
197
|
+
@r.add_tile(
|
198
|
+
blob,
|
199
|
+
(x - @tile_x_range.min) * TILE_WIDTH,
|
200
|
+
(y - @tile_y_range.min) * TILE_HEIGHT
|
201
|
+
)
|
202
|
+
|
203
|
+
@images << {
|
204
|
+
url: url,
|
205
|
+
x: x,
|
206
|
+
y: y
|
207
|
+
}
|
208
|
+
|
209
|
+
puts "processed #{x - @tile_x_range.min}x#{y - @tile_y_range.min} (max #{@tile_x_range.max - @tile_x_range.min}x#{@tile_y_range.max - @tile_y_range.min})" if @verbose
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# sweet, image is joined
|
214
|
+
|
215
|
+
# min/max points used for cropping
|
216
|
+
@bitmap_point_x_max = (@full_image_x / 2).round
|
217
|
+
@bitmap_point_x_min = (@full_image_x / 2).round
|
218
|
+
@bitmap_point_y_max = (@full_image_y / 2).round
|
219
|
+
@bitmap_point_y_min = (@full_image_y / 2).round
|
220
|
+
|
221
|
+
# add some coords to the map
|
222
|
+
(1...@coords.size).each do |i|
|
223
|
+
lat_from = @coords[i-1][:lat]
|
224
|
+
lon_from = @coords[i-1][:lon]
|
225
|
+
|
226
|
+
lat_to = @coords[i][:lat]
|
227
|
+
lon_to = @coords[i][:lon]
|
228
|
+
|
229
|
+
point_from = self.class.point_on_image(@zoom, [lat_from, lon_from])
|
230
|
+
point_to = self.class.point_on_image(@zoom, [lat_to, lon_to])
|
231
|
+
# { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
232
|
+
|
233
|
+
# first point
|
234
|
+
bitmap_xa = (point_from[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_from[:pixel_offset][0]
|
235
|
+
bitmap_ya = (point_from[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_from[:pixel_offset][1]
|
236
|
+
bitmap_xb = (point_to[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_to[:pixel_offset][0]
|
237
|
+
bitmap_yb = (point_to[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_to[:pixel_offset][1]
|
238
|
+
|
239
|
+
@r.line(
|
240
|
+
bitmap_xa, bitmap_ya,
|
241
|
+
bitmap_xb, bitmap_yb
|
242
|
+
)
|
243
|
+
end
|
244
|
+
|
245
|
+
#calculate_for_crop
|
246
|
+
end
|
247
|
+
|
248
|
+
# Calculate some numbers for cropping operation
|
249
|
+
def calculate_for_crop
|
250
|
+
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
251
|
+
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
252
|
+
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
253
|
+
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
254
|
+
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
255
|
+
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
256
|
+
|
257
|
+
@r.set_crop(@bitmap_point_x_min, @bitmap_point_x_max, @bitmap_point_y_min, @bitmap_point_y_max)
|
258
|
+
end
|
259
|
+
|
260
|
+
# Calculate some numbers for cropping operation with autozoom
|
261
|
+
def calculate_for_crop_with_auto_zoom
|
262
|
+
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
263
|
+
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
264
|
+
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
265
|
+
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
266
|
+
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
267
|
+
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
268
|
+
|
269
|
+
bitmap_x_center = (@bitmap_point_x_min + @bitmap_point_x_max) / 2
|
270
|
+
bitmap_y_center = (@bitmap_point_y_min + @bitmap_point_y_max) / 2
|
271
|
+
|
272
|
+
@r.set_crop_fixed(bitmap_x_center, bitmap_y_center, @fixed_width, @fixed_height)
|
273
|
+
end
|
274
|
+
|
275
|
+
def expand_map
|
276
|
+
# TODO expand min and max ranges
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'chunky_png'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
# This renderer has some/a lot of features missing
|
7
|
+
module Gpx2png
|
8
|
+
class ChunkyPngRenderer
|
9
|
+
def initialize(_options = {})
|
10
|
+
@options = _options || {}
|
11
|
+
@_color = @options[:color] || '#FF0000'
|
12
|
+
@color = ChunkyPNG::Color.from_hex(@_color)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :x, :y
|
16
|
+
|
17
|
+
# Create new (full) image
|
18
|
+
def new_image
|
19
|
+
@image = ChunkyPNG::Image.new(
|
20
|
+
@x,
|
21
|
+
@y,
|
22
|
+
ChunkyPNG::Color::WHITE
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add one tile to full image
|
27
|
+
def add_tile(blob, x_offset, y_offset)
|
28
|
+
tile_image = ChunkyPNG::Image.from_blob(blob)
|
29
|
+
@image.compose!(
|
30
|
+
tile_image,
|
31
|
+
x_offset,
|
32
|
+
y_offset
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def line(xa, ya, xb, yb)
|
37
|
+
@image.line(
|
38
|
+
xa, ya,
|
39
|
+
xb, yb,
|
40
|
+
@color
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_crop(x_min, x_max, y_min, y_max)
|
45
|
+
# TODO
|
46
|
+
end
|
47
|
+
|
48
|
+
def crop
|
49
|
+
# TODO
|
50
|
+
end
|
51
|
+
|
52
|
+
def save(filename)
|
53
|
+
@image.save(filename)
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_png
|
57
|
+
# TODO
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'RMagick'
|
3
|
+
|
4
|
+
$:.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
module Gpx2png
|
7
|
+
class RmagickRenderer
|
8
|
+
def initialize(_options = { })
|
9
|
+
@options = _options || { }
|
10
|
+
@color = @options[:color] || '#FF0000'
|
11
|
+
@width = @options[:width] || 3
|
12
|
+
@aa = @options[:aa] == true
|
13
|
+
@opacity = @options[:opacity] || 1.0
|
14
|
+
@licence_string = "Map data OpenStreetMap (CC-by-SA 2.0)"
|
15
|
+
@crop_margin = @options[:crop_margin] || 50
|
16
|
+
@crop_enabled = @options[:crop_enabled] == true
|
17
|
+
|
18
|
+
@line = Magick::Draw.new
|
19
|
+
@line.stroke_antialias(@aa)
|
20
|
+
@line.stroke(@color)
|
21
|
+
@line.stroke_opacity(@opacity)
|
22
|
+
@line.stroke_width(@width)
|
23
|
+
|
24
|
+
@licence_text = Magick::Draw.new
|
25
|
+
@licence_text.text_antialias(@aa)
|
26
|
+
@licence_text.font_family('helvetica')
|
27
|
+
@licence_text.font_style(Magick::NormalStyle)
|
28
|
+
@licence_text.text_align(Magick::RightAlign)
|
29
|
+
@licence_text.pointsize(10)
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :x, :y
|
33
|
+
|
34
|
+
# Create new (full) image
|
35
|
+
def new_image
|
36
|
+
@image = Magick::Image.new(
|
37
|
+
@x,
|
38
|
+
@y
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add one tile to full image
|
43
|
+
def add_tile(blob, x_offset, y_offset)
|
44
|
+
tile_image = Magick::Image.from_blob(blob)[0]
|
45
|
+
@image = @image.composite(
|
46
|
+
tile_image,
|
47
|
+
x_offset,
|
48
|
+
y_offset,
|
49
|
+
Magick::OverCompositeOp
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def line(xa, ya, xb, yb)
|
54
|
+
@line.line(
|
55
|
+
xa, ya,
|
56
|
+
xb, yb
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Setup crop image using CSS padding style data
|
61
|
+
def set_crop(x_min, x_max, y_min, y_max)
|
62
|
+
# puts @x, @y, @crop_margin, x_min, x_max, y_min, y_max
|
63
|
+
|
64
|
+
@crop_t = y_min - @crop_margin
|
65
|
+
@crop_r = (@x - x_max) - @crop_margin
|
66
|
+
@crop_b = (@y - y_max) - @crop_margin
|
67
|
+
@crop_l = x_min - @crop_margin
|
68
|
+
|
69
|
+
@crop_t = 0 if @crop_t < 0
|
70
|
+
@crop_r = 0 if @crop_r < 0
|
71
|
+
@crop_b = 0 if @crop_b < 0
|
72
|
+
@crop_l = 0 if @crop_l < 0
|
73
|
+
end
|
74
|
+
|
75
|
+
# Setup crop for autozoom/fixed size
|
76
|
+
def set_crop_fixed(x_center, y_center, width, height)
|
77
|
+
@crop_margin = 0
|
78
|
+
@crop_enabled = true
|
79
|
+
|
80
|
+
set_crop(
|
81
|
+
x_center - (width / 2),
|
82
|
+
x_center + (width / 2),
|
83
|
+
y_center - (height / 2),
|
84
|
+
y_center + (height / 2)
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Setup crop image using CSS padding style data
|
89
|
+
def crop!
|
90
|
+
return unless @crop_enabled
|
91
|
+
|
92
|
+
@new_x = @x - @crop_r.to_i - @crop_l.to_i
|
93
|
+
@new_y = @y - @crop_b.to_i - @crop_t.to_i
|
94
|
+
@image = @image.crop(@crop_l.to_i, @crop_t.to_i, @new_x, @new_y, true)
|
95
|
+
# changing image size
|
96
|
+
@x = @new_x
|
97
|
+
@y = @new_y
|
98
|
+
end
|
99
|
+
|
100
|
+
def render
|
101
|
+
@line.draw(@image)
|
102
|
+
# crop after drawing lines, before drawing "legend"
|
103
|
+
crop!
|
104
|
+
# "static" elements
|
105
|
+
@licence_text.text(@x - 10, @y - 10, @licence_string)
|
106
|
+
@licence_text.draw(@image)
|
107
|
+
end
|
108
|
+
|
109
|
+
def save(filename)
|
110
|
+
render
|
111
|
+
@image.write(filename)
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_png
|
115
|
+
render
|
116
|
+
@image.format = 'PNG'
|
117
|
+
@image.to_blob
|
118
|
+
end
|
119
|
+
|
120
|
+
def blank_tile(width, height, index = 0)
|
121
|
+
_image = Magick::Image.new(
|
122
|
+
width,
|
123
|
+
height
|
124
|
+
) do |i|
|
125
|
+
_color = "#dddddd"
|
126
|
+
_color = "#eeeeee" if index % 2 == 0
|
127
|
+
i.background_color = _color
|
128
|
+
end
|
129
|
+
_image.format = 'PNG'
|
130
|
+
_image.to_blob
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|