gpx2png 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -2
- data/Gemfile.lock +2 -0
- data/README.md +11 -71
- data/VERSION +1 -1
- data/bin/gpx2png +20 -9
- data/lib/gpx2png.rb +4 -0
- data/lib/gpx2png/base.rb +108 -12
- data/lib/gpx2png/calculations/base_class_methods.rb +15 -0
- data/lib/gpx2png/calculations/base_instance_methods.rb +32 -0
- data/lib/gpx2png/calculations/osm_class_methods.rb +99 -0
- data/lib/gpx2png/calculations/osm_instance_methods.rb +197 -0
- data/lib/gpx2png/landscape.rb +19 -0
- data/lib/gpx2png/layer.rb +36 -0
- data/lib/gpx2png/opencycle.rb +19 -0
- data/lib/gpx2png/osm.rb +0 -2
- data/lib/gpx2png/osm_base.rb +7 -281
- data/lib/gpx2png/outdoors.rb +19 -0
- data/lib/gpx2png/renderers/chunky_png_renderer.rb +1 -1
- data/lib/gpx2png/renderers/rmagick_renderer.rb +0 -4
- data/lib/gpx2png/transport.rb +19 -0
- data/lib/gpx2png/ump.rb +0 -2
- metadata +28 -3
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,75 +1,11 @@
|
|
1
|
-
|
1
|
+
gpx2png
|
2
2
|
=======
|
3
3
|
|
4
|
-
Geotagging
|
5
|
-
----------
|
6
|
-
|
7
|
-
Geotag your photos using stored GPX files. At this moment it supports only Garmin eTrex devices.
|
8
|
-
|
9
|
-
|
10
|
-
Disclaimer
|
11
|
-
----------
|
12
|
-
|
13
|
-
This gem add one executable which overwrite JPG files. BACKUP IS NEEDED!
|
14
|
-
|
15
|
-
|
16
|
-
How to use it
|
17
|
-
-------------
|
18
|
-
|
19
|
-
1. gem install gpx2exif
|
20
|
-
|
21
|
-
2. Go to path where you have JPG/JPEG photos (case insensitive) and GPX files.
|
22
|
-
|
23
|
-
3. From 0.0.1 there is 'simulation command'. Type 'geotag_simulate' and enter.
|
24
|
-
|
25
|
-
4. WARNING! it will overwrite all your photos so MAKE A BACKUP!
|
26
|
-
|
27
|
-
5. Type 'geotag_all_images' and press enter key.
|
28
|
-
|
29
|
-
6. See a nice piece of output and now you have geotagged photos :)
|
30
|
-
|
31
|
-
|
32
|
-
If something is not working send me e-mail and I'll fix it.
|
33
|
-
|
34
|
-
|
35
|
-
Create waypoint files
|
36
|
-
---------------------
|
37
|
-
|
38
|
-
You can prepare your own list of waypoints and then store into eTrex using GPX file. At this moment there is
|
39
|
-
only possible to convert data from YAML file to GPX. It is also possible to integrate with other (web)apps.
|
40
|
-
|
41
|
-
How to use it
|
42
|
-
-------------
|
43
|
-
|
44
|
-
1. Check samples/sample_yaml_pois.yml as a template.
|
45
|
-
|
46
|
-
2. Modify it, add yours POIs.
|
47
|
-
|
48
|
-
3. Run command
|
49
|
-
|
50
|
-
generate_garmin_waypoints -y input_file.yml > output.gpx
|
51
|
-
|
52
|
-
4. You can check inter-POI distances using
|
53
|
-
|
54
|
-
generate_garmin_waypoints -y input_file.yml -C
|
55
|
-
|
56
|
-
Distance conflict does not mean something is wrong. POIs can be close to each other so it
|
57
|
-
is a good idea to have your brain turned on ;)
|
58
|
-
|
59
|
-
5. You can change inter-POI distances using 'latlon something' distance for distance checking
|
60
|
-
explained line before.
|
61
|
-
|
62
|
-
generate_garmin_waypoints -y samples/sample_yaml_pois.yml -C -t 1
|
63
|
-
|
64
|
-
6. You can specify output file if you don't like using >> 'file.gpx'.
|
65
|
-
|
66
|
-
generate_garmin_waypoints -y samples/sample_yaml_pois.yml -o file.gpx
|
67
|
-
|
68
|
-
|
69
4
|
Render track with OpenStreetMap
|
70
5
|
---------------------
|
71
6
|
|
72
|
-
You can "convert" your tracks to images using this command.
|
7
|
+
You can "convert" your tracks to map images using this command.
|
8
|
+
|
73
9
|
|
74
10
|
How to use it
|
75
11
|
-------------
|
@@ -92,13 +28,17 @@ How to use it
|
|
92
28
|
|
93
29
|
gpx2png -g spec/fixtures/sample.gpx -z 11 -o map.png
|
94
30
|
|
95
|
-
4.
|
31
|
+
4. You can change map provider:
|
32
|
+
|
33
|
+
* OpenStreetMap - defualt
|
34
|
+
* UMP - add -u to use [UMP tiles](http://ump.waw.pl/)
|
35
|
+
* Cycle - add -u to use [UMP tiles](http://ump.waw.pl/)
|
96
36
|
|
97
37
|
|
98
|
-
Contributing to
|
38
|
+
Contributing to gpx2png
|
99
39
|
-------------------------------
|
100
40
|
|
101
|
-
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bobik314&url=https://github.com/akwiatkowski/
|
41
|
+
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bobik314&url=https://github.com/akwiatkowski/gpx2png&title=gpx2png&language=en_GB&tags=github&category=software)
|
102
42
|
|
103
43
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
104
44
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
@@ -112,6 +52,6 @@ Contributing to gpx2xif
|
|
112
52
|
Copyright
|
113
53
|
---------
|
114
54
|
|
115
|
-
Copyright (c) 2012 Aleksander Kwiatkowski. See LICENSE.txt for
|
55
|
+
Copyright (c) 2012-2014 Aleksander Kwiatkowski. See LICENSE.txt for
|
116
56
|
further details.
|
117
57
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/bin/gpx2png
CHANGED
@@ -6,7 +6,7 @@ require 'gpx2png/osm'
|
|
6
6
|
require 'gpx2png/ump'
|
7
7
|
require 'optparse'
|
8
8
|
|
9
|
-
options = {
|
9
|
+
options = {}
|
10
10
|
OptionParser.new do |opts|
|
11
11
|
opts.banner = "Usage: gpx2png [options]"
|
12
12
|
|
@@ -22,8 +22,8 @@ OptionParser.new do |opts|
|
|
22
22
|
opts.on("-o", "--output FILE", "Output image file") do |v|
|
23
23
|
options[:output_file] = v
|
24
24
|
end
|
25
|
-
opts.on("-
|
26
|
-
options[:
|
25
|
+
opts.on("-k", "--klass CLASS", "Use different map provider (osm, ump, cycle, transport, outdoors, landscape)") do |v|
|
26
|
+
options[:klass] = v
|
27
27
|
end
|
28
28
|
end.parse!
|
29
29
|
|
@@ -40,15 +40,26 @@ if options[:zoom].nil? and options[:size].nil?
|
|
40
40
|
@fail = true
|
41
41
|
end
|
42
42
|
|
43
|
+
klass = case options[:klass].to_s
|
44
|
+
when "ump" then
|
45
|
+
Gpx2png::Ump
|
46
|
+
when "cycle" then
|
47
|
+
Gpx2png::OpenCycle
|
48
|
+
when "transport" then
|
49
|
+
Gpx2png::Transport
|
50
|
+
when "outdoors" then
|
51
|
+
Gpx2png::Outdoors
|
52
|
+
when "landscape" then
|
53
|
+
Gpx2png::Landscape
|
54
|
+
else
|
55
|
+
Gpx2png::Osm
|
56
|
+
end
|
57
|
+
|
43
58
|
unless @fail
|
44
59
|
g = GpxUtils::TrackImporter.new
|
45
60
|
g.add_file(options[:gpx])
|
46
61
|
|
47
|
-
|
48
|
-
e = Gpx2png::Ump.new
|
49
|
-
else
|
50
|
-
e = Gpx2png::Osm.new
|
51
|
-
end
|
62
|
+
e = klass.new
|
52
63
|
e.coords = g.coords
|
53
64
|
|
54
65
|
if options[:size]
|
@@ -58,7 +69,7 @@ unless @fail
|
|
58
69
|
else
|
59
70
|
# constant zoom
|
60
71
|
e.zoom = options[:zoom].to_i
|
61
|
-
e.renderer_options = {crop_enabled: true}
|
72
|
+
e.renderer_options = { crop_enabled: true }
|
62
73
|
end
|
63
74
|
|
64
75
|
e.save(options[:output_file])
|
data/lib/gpx2png.rb
CHANGED
data/lib/gpx2png/base.rb
CHANGED
@@ -1,29 +1,125 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
1
|
+
require 'colorize'
|
2
|
+
require 'logger'
|
3
|
+
require 'gpx2png/layer'
|
4
|
+
require 'gpx2png/calculations/base_class_methods'
|
5
|
+
require 'gpx2png/calculations/base_instance_methods'
|
4
6
|
|
5
7
|
module Gpx2png
|
6
8
|
class Base
|
9
|
+
extend Calculations::BaseClassMethods
|
10
|
+
include Calculations::BaseInstanceMethods
|
7
11
|
|
8
12
|
def initialize
|
9
|
-
@
|
13
|
+
@layers = Array.new
|
14
|
+
_layer = Layer.new
|
15
|
+
_layer.parent = self
|
16
|
+
@layers << _layer
|
17
|
+
@single_layer = true
|
18
|
+
|
10
19
|
@zoom = 9
|
11
|
-
|
20
|
+
|
21
|
+
self.class.logger.debug("Init, default zoom #{@zoom.to_s.green}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_layer(_layer_options)
|
25
|
+
if @single_layer
|
26
|
+
self.class.logger.debug("Created #{"first".to_s.red} layer")
|
27
|
+
# turn off single layer version
|
28
|
+
@single_layer = false
|
29
|
+
# set layer options for first layer and return it
|
30
|
+
_layer = @layers.first
|
31
|
+
_layer.options = _layer_options
|
32
|
+
return _layer
|
33
|
+
else
|
34
|
+
# create new layer with options, add to list and return
|
35
|
+
_layer = Layer.new
|
36
|
+
_layer.parent = self
|
37
|
+
_layer.options = _layer_options
|
38
|
+
@layers << _layer
|
39
|
+
self.class.logger.debug("Created layer, count: #{@layers.size.to_s.red}")
|
40
|
+
return _layer
|
41
|
+
end
|
12
42
|
end
|
13
43
|
|
14
44
|
def add(lat, lon)
|
15
|
-
@
|
45
|
+
if @single_layer
|
46
|
+
# add coord to first layer
|
47
|
+
_layer = @layers.first
|
48
|
+
_layer.add(lat, lon)
|
49
|
+
else
|
50
|
+
# I'm afraid Dave I can't do that
|
51
|
+
self.class.logger.fatal("After you've added first layer you can use coords only from layers")
|
52
|
+
raise StandardError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def coords=(_coords)
|
57
|
+
if @single_layer
|
58
|
+
# add coord to first layer
|
59
|
+
_layer = @layers.first
|
60
|
+
_layer.coords = _coords
|
61
|
+
else
|
62
|
+
# I'm afraid Dave I can't do that
|
63
|
+
self.class.fatal("After you've added first layer you can use coords only from layers")
|
64
|
+
raise StandardError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def color(_color)
|
69
|
+
if @single_layer
|
70
|
+
# add coord to first layer
|
71
|
+
_layer = @layers.first
|
72
|
+
_layer.color = _color
|
73
|
+
else
|
74
|
+
# I'm afraid Dave I can't do that
|
75
|
+
self.class.fatal("After you've added first layer you have to use layers")
|
76
|
+
raise StandardError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create image with fixed size
|
81
|
+
def fixed_size(_width, _height)
|
82
|
+
@fixed_width = _width
|
83
|
+
@fixed_height = _height
|
84
|
+
self.class.logger.debug("Map image fixed dimension set #{@fixed_width.to_s.red} x #{@fixed_height.to_s.red}")
|
85
|
+
true
|
16
86
|
end
|
17
87
|
|
18
|
-
|
88
|
+
def zoom=(_zoom)
|
89
|
+
self.class.logger.debug("Map zoom set #{_zoom.to_s.red}")
|
90
|
+
@zoom = _zoom
|
91
|
+
end
|
92
|
+
|
93
|
+
def zoom
|
94
|
+
@zoom
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_accessor :layers
|
98
|
+
|
99
|
+
# Simulate download of tiles
|
100
|
+
attr_accessor :simulate_download
|
101
|
+
|
102
|
+
def self.simulate_download=(b)
|
103
|
+
logger.debug("Simulate tiles download for class #{self.to_s.blue}") if b
|
104
|
+
@@simulate_download = b
|
105
|
+
end
|
106
|
+
|
107
|
+
def simulate_download?
|
108
|
+
return true if true == self.simulate_download or (defined? @@simulate_download and true == @@simulate_download)
|
109
|
+
end
|
110
|
+
|
111
|
+
def destroy
|
112
|
+
@r.destroy
|
113
|
+
self.class.logger.debug "Image destroyed"
|
114
|
+
end
|
19
115
|
|
20
|
-
|
21
|
-
|
22
|
-
return rad * 180.0 / Math::PI
|
116
|
+
def self.logger=(_logger)
|
117
|
+
@@logger = _logger
|
23
118
|
end
|
24
119
|
|
25
|
-
def self.
|
26
|
-
|
120
|
+
def self.logger
|
121
|
+
@@logger = Logger.new(STDOUT) unless defined? @@logger
|
122
|
+
return @@logger
|
27
123
|
end
|
28
124
|
|
29
125
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Gpx2png
|
2
|
+
module Calculations
|
3
|
+
module BaseInstanceMethods
|
4
|
+
|
5
|
+
def calculate_minmax_latlon
|
6
|
+
@layers.each do |l|
|
7
|
+
enlarge_border_coords(l)
|
8
|
+
end
|
9
|
+
# when no coords specified
|
10
|
+
@lat_min ||= -0.01
|
11
|
+
@lat_max ||= 0.01
|
12
|
+
@lon_min ||= -0.01
|
13
|
+
@lon_max ||= 0.01
|
14
|
+
end
|
15
|
+
|
16
|
+
def enlarge_border_coords(layer)
|
17
|
+
_lat_min = layer.coords.collect { |c| c[:lat] }.min
|
18
|
+
_lat_max = layer.coords.collect { |c| c[:lat] }.max
|
19
|
+
_lon_min = layer.coords.collect { |c| c[:lon] }.min
|
20
|
+
_lon_max = layer.coords.collect { |c| c[:lon] }.max
|
21
|
+
|
22
|
+
@lat_min = _lat_min if @lat_min.nil? or _lat_min < @lat_min
|
23
|
+
@lat_max = _lat_max if @lat_max.nil? or _lat_max > @lat_max
|
24
|
+
@lon_min = _lon_min if @lon_min.nil? or _lon_min < @lon_min
|
25
|
+
@lon_max = _lon_max if @lon_max.nil? or _lon_max > @lon_max
|
26
|
+
|
27
|
+
self.class.logger.debug("Border coords #{@lat_min.to_s.green},#{@lon_min.to_s.green} - #{@lat_max.to_s.green},#{@lon_max.to_s.green}")
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Gpx2png
|
2
|
+
module Calculations
|
3
|
+
module OsmClassMethods
|
4
|
+
|
5
|
+
TILE_WIDTH = 256
|
6
|
+
TILE_HEIGHT = 256
|
7
|
+
|
8
|
+
# http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#X_and_Y
|
9
|
+
# Convert latlon deg to OSM tile coords
|
10
|
+
def convert(zoom, coord)
|
11
|
+
lat_deg, lon_deg = coord
|
12
|
+
lat_rad = deg2rad(lat_deg)
|
13
|
+
x = (((lon_deg + 180) / 360) * (2 ** zoom)).floor
|
14
|
+
y = ((1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math::PI) /2 * (2 ** zoom)).floor
|
15
|
+
|
16
|
+
logger.debug "Converted #{lat_deg.to_s.green},#{lat_deg.to_s.green} to [#{x.to_s.red},#{y.to_s.red}]"
|
17
|
+
|
18
|
+
return [x, y]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Convert OSM tile coords to latlon deg in top-left corner
|
22
|
+
def reverse_convert(zoom, coord)
|
23
|
+
x, y = coord
|
24
|
+
n = 2 ** zoom
|
25
|
+
lon_deg = x.to_f / n.to_f * 360.0 - 180.0
|
26
|
+
lat_deg = rad2deg(Math.atan(Math.sinh(Math::PI * (1.to_f - 2.to_f * y.to_f / n.to_f))))
|
27
|
+
|
28
|
+
logger.debug "Reverse converted [#{x.to_s.red},#{y.to_s.red}] to #{lat_deg.to_s.green},#{lat_deg.to_s.green}"
|
29
|
+
|
30
|
+
return [lat_deg, lon_deg]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Lazy calc proper zoom for drawing
|
34
|
+
def calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height)
|
35
|
+
# because I'm lazy! :] and math is not my best side
|
36
|
+
|
37
|
+
last_zoom = 2
|
38
|
+
(5..18).each do |zoom|
|
39
|
+
# calculate drawing tile size and pixel size
|
40
|
+
tile_min = point_on_absolute_image(zoom, [lat_min, lon_min])
|
41
|
+
tile_max = point_on_absolute_image(zoom, [lat_max, lon_max])
|
42
|
+
current_pixel_x_distance = tile_max[0] - tile_min[0]
|
43
|
+
current_pixel_y_distance = tile_min[1] - tile_max[1]
|
44
|
+
if current_pixel_x_distance > width or current_pixel_y_distance > height
|
45
|
+
logger.debug "Calculated best zoom #{last_zoom.to_s.red}"
|
46
|
+
return last_zoom
|
47
|
+
end
|
48
|
+
last_zoom = zoom
|
49
|
+
end
|
50
|
+
return 18
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convert latlon deg coords to image point (x,y) and OSM tile coord
|
54
|
+
# return where you should put point on tile
|
55
|
+
def point_on_image(zoom, geo_coord)
|
56
|
+
osm_tile_coord = convert(zoom, geo_coord)
|
57
|
+
top_left_corner = reverse_convert(zoom, osm_tile_coord)
|
58
|
+
bottom_right_corner = reverse_convert(zoom, [
|
59
|
+
osm_tile_coord[0] + 1, osm_tile_coord[1] + 1
|
60
|
+
])
|
61
|
+
|
62
|
+
# some line math: y = ax + b
|
63
|
+
|
64
|
+
x_geo = geo_coord[1]
|
65
|
+
# offset
|
66
|
+
x_offset = x_geo - top_left_corner[1]
|
67
|
+
# scale
|
68
|
+
x_distance = (bottom_right_corner[1] - top_left_corner[1])
|
69
|
+
x = (TILE_WIDTH.to_f * (x_offset / x_distance)).round
|
70
|
+
|
71
|
+
y_geo = geo_coord[0]
|
72
|
+
# offset
|
73
|
+
y_offset = y_geo - top_left_corner[0]
|
74
|
+
# scale
|
75
|
+
y_distance = (bottom_right_corner[0] - top_left_corner[0])
|
76
|
+
y = (TILE_HEIGHT.to_f * (y_offset / y_distance)).round
|
77
|
+
|
78
|
+
logger.debug("Point on image: zoom #{zoom.to_s.green}, coord #{geo_coord[0].to_s.green},#{geo_coord[1].to_s.green} => [#{x.to_s.red}, #{y.to_s.red}]")
|
79
|
+
|
80
|
+
return { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Useful for calculating distance on output image
|
84
|
+
# It is not position on output image because we don't know tile coords
|
85
|
+
# For upper-left tile
|
86
|
+
def point_on_absolute_image(zoom, geo_coord)
|
87
|
+
_p = point_on_image(zoom, geo_coord)
|
88
|
+
_x = _p[:osm_title_coord][0] * TILE_WIDTH + _p[:pixel_offset][0]
|
89
|
+
_y = _p[:osm_title_coord][1] * TILE_WIDTH + _p[:pixel_offset][1]
|
90
|
+
|
91
|
+
logger.debug("Point on abs. image: zoom #{zoom.to_s.green}, coord #{geo_coord[0].to_s.green},#{geo_coord[1].to_s.green} => [#{_x.to_s.red}, #{_y.to_s.red}]")
|
92
|
+
|
93
|
+
return [_x, _y]
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'gpx2png/calculations/osm_class_methods'
|
2
|
+
|
3
|
+
module Gpx2png
|
4
|
+
module Calculations
|
5
|
+
module OsmInstanceMethods
|
6
|
+
|
7
|
+
TILE_WIDTH = OsmClassMethods::TILE_WIDTH
|
8
|
+
TILE_HEIGHT = OsmClassMethods::TILE_WIDTH
|
9
|
+
|
10
|
+
attr_reader :lat_min, :lat_max, :lon_min, :lon_max
|
11
|
+
attr_reader :tile_x_distance, :tile_y_distance
|
12
|
+
# points for cropping
|
13
|
+
attr_reader :bitmap_point_x_max, :bitmap_point_x_min, :bitmap_point_y_max, :bitmap_point_y_min
|
14
|
+
|
15
|
+
def initial_calculations
|
16
|
+
calculate_minmax_latlon
|
17
|
+
|
18
|
+
# auto zoom must be here
|
19
|
+
# drawing must fit into fixed resolution
|
20
|
+
# map must be bigger than fixed resolution
|
21
|
+
if @fixed_width and @fixed_height
|
22
|
+
@new_zoom = self.class.calc_zoom(
|
23
|
+
@lat_min, @lat_max,
|
24
|
+
@lon_min, @lon_max,
|
25
|
+
@fixed_width, @fixed_height
|
26
|
+
)
|
27
|
+
self.class.logger.debug "Calculated new zoom #{@new_zoom.to_s.red} (was #{@zoom.to_s.red})"
|
28
|
+
@zoom = @new_zoom
|
29
|
+
end
|
30
|
+
|
31
|
+
@border_tiles = [
|
32
|
+
self.class.convert(@zoom, [@lat_min, @lon_min]),
|
33
|
+
self.class.convert(@zoom, [@lat_max, @lon_max])
|
34
|
+
]
|
35
|
+
|
36
|
+
@tile_x_range = (@border_tiles[0][0])..(@border_tiles[1][0])
|
37
|
+
@tile_y_range = (@border_tiles[1][1])..(@border_tiles[0][1])
|
38
|
+
|
39
|
+
# enlarging ranges to fill up map area
|
40
|
+
# both sizes are enlarged
|
41
|
+
if @fixed_width and @fixed_height
|
42
|
+
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
|
43
|
+
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
|
44
|
+
self.class.logger.debug "Expanding #{"X".to_s.blue} tiles from both sides #{x_axis_expand_count.to_s.green}"
|
45
|
+
self.class.logger.debug "Expanding #{"Y".to_s.blue} tiles from both sides #{y_axis_expand_count.to_s.green}"
|
46
|
+
@tile_x_range = ((@tile_x_range.min - x_axis_expand_count)..(@tile_x_range.max + x_axis_expand_count))
|
47
|
+
@tile_y_range = ((@tile_y_range.min - y_axis_expand_count)..(@tile_y_range.max + y_axis_expand_count))
|
48
|
+
end
|
49
|
+
|
50
|
+
# new/full image size
|
51
|
+
@full_image_x = (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH
|
52
|
+
@full_image_y = (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT
|
53
|
+
@r.x = @full_image_x
|
54
|
+
@r.y = @full_image_y
|
55
|
+
|
56
|
+
if @fixed_width and @fixed_height
|
57
|
+
calculate_for_crop_with_auto_zoom
|
58
|
+
else
|
59
|
+
calculate_for_crop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Calculate zoom level
|
64
|
+
def auto_zoom_for(x = 0, y = 0)
|
65
|
+
# TODO
|
66
|
+
end
|
67
|
+
|
68
|
+
# Calculate some numbers for cropping operation
|
69
|
+
def calculate_for_crop
|
70
|
+
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
71
|
+
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
72
|
+
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
73
|
+
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
74
|
+
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
75
|
+
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
76
|
+
|
77
|
+
@r.set_crop(@bitmap_point_x_min, @bitmap_point_x_max, @bitmap_point_y_min, @bitmap_point_y_max)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Calculate some numbers for cropping operation with autozoom
|
81
|
+
def calculate_for_crop_with_auto_zoom
|
82
|
+
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
83
|
+
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
84
|
+
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
85
|
+
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
86
|
+
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
87
|
+
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
88
|
+
|
89
|
+
bitmap_x_center = (@bitmap_point_x_min + @bitmap_point_x_max) / 2
|
90
|
+
bitmap_y_center = (@bitmap_point_y_min + @bitmap_point_y_max) / 2
|
91
|
+
|
92
|
+
@r.set_crop_fixed(bitmap_x_center, bitmap_y_center, @fixed_width, @fixed_height)
|
93
|
+
end
|
94
|
+
|
95
|
+
def expand_map
|
96
|
+
# TODO expand min and max ranges
|
97
|
+
end
|
98
|
+
|
99
|
+
# Do everything
|
100
|
+
def download_and_join_tiles
|
101
|
+
self.class.logger.info "Output image dimension #{@full_image_x.to_s.red} x #{@full_image_y.to_s.red}"
|
102
|
+
@r.new_image
|
103
|
+
|
104
|
+
# {:x, :y, :blob}
|
105
|
+
@images = Array.new
|
106
|
+
|
107
|
+
|
108
|
+
@tile_x_range.each do |x|
|
109
|
+
@tile_y_range.each do |y|
|
110
|
+
url = self.class.url(@zoom, [x, y])
|
111
|
+
|
112
|
+
# blob time
|
113
|
+
unless simulate_download?
|
114
|
+
uri = URI.parse(url)
|
115
|
+
response = Net::HTTP.get_response(uri)
|
116
|
+
blob = response.body
|
117
|
+
else
|
118
|
+
blob = @r.blank_tile(TILE_WIDTH, TILE_HEIGHT, x+y)
|
119
|
+
end
|
120
|
+
|
121
|
+
@r.add_tile(
|
122
|
+
blob,
|
123
|
+
(x - @tile_x_range.min) * TILE_WIDTH,
|
124
|
+
(y - @tile_y_range.min) * TILE_HEIGHT
|
125
|
+
)
|
126
|
+
|
127
|
+
@images << {
|
128
|
+
url: url,
|
129
|
+
x: x,
|
130
|
+
y: y
|
131
|
+
}
|
132
|
+
|
133
|
+
self.class.logger.info "processed #{(x - @tile_x_range.min).to_s.red} x #{(y - @tile_y_range.min).to_s.red} (max #{(@tile_x_range.max - @tile_x_range.min).to_s.yellow} x #{(@tile_y_range.max - @tile_y_range.min).to_s.yellow})"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# sweet, image is joined
|
138
|
+
|
139
|
+
# min/max points used for cropping
|
140
|
+
@bitmap_point_x_max = (@full_image_x / 2).round
|
141
|
+
@bitmap_point_x_min = (@full_image_x / 2).round
|
142
|
+
@bitmap_point_y_max = (@full_image_y / 2).round
|
143
|
+
@bitmap_point_y_min = (@full_image_y / 2).round
|
144
|
+
|
145
|
+
# add all coords to the map
|
146
|
+
_coords_count = 0
|
147
|
+
@layers.each do |layer|
|
148
|
+
_coords = layer.coords
|
149
|
+
_coords_count += _coords.size
|
150
|
+
(1..._coords.size).each do |i|
|
151
|
+
|
152
|
+
lat_from = _coords[i-1][:lat]
|
153
|
+
lon_from = _coords[i-1][:lon]
|
154
|
+
|
155
|
+
lat_to = _coords[i][:lat]
|
156
|
+
lon_to = _coords[i][:lon]
|
157
|
+
|
158
|
+
point_from = self.class.point_on_image(@zoom, [lat_from, lon_from])
|
159
|
+
point_to = self.class.point_on_image(@zoom, [lat_to, lon_to])
|
160
|
+
# { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
161
|
+
|
162
|
+
# first point
|
163
|
+
bitmap_xa = (point_from[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_from[:pixel_offset][0]
|
164
|
+
bitmap_ya = (point_from[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_from[:pixel_offset][1]
|
165
|
+
bitmap_xb = (point_to[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_to[:pixel_offset][0]
|
166
|
+
bitmap_yb = (point_to[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_to[:pixel_offset][1]
|
167
|
+
|
168
|
+
@r.line(
|
169
|
+
bitmap_xa, bitmap_ya,
|
170
|
+
bitmap_xb, bitmap_yb
|
171
|
+
)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
self.class.logger.info "Layers - #{@layers.size.to_s.red} with #{_coords_count.to_s.yellow} coords have been added to image"
|
175
|
+
|
176
|
+
# add points
|
177
|
+
@markers.each do |point|
|
178
|
+
lat = point[:lat]
|
179
|
+
lon = point[:lon]
|
180
|
+
|
181
|
+
p = self.class.point_on_image(@zoom, [lat, lon])
|
182
|
+
bitmap_x = (p[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + p[:pixel_offset][0]
|
183
|
+
bitmap_y = (p[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + p[:pixel_offset][1]
|
184
|
+
|
185
|
+
point[:x] = bitmap_x
|
186
|
+
point[:y] = bitmap_y
|
187
|
+
|
188
|
+
@r.markers << point
|
189
|
+
end
|
190
|
+
self.class.logger.info "Markers - #{@markers.size.to_s.red} have been added to image"
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/osm'
|
3
|
+
|
4
|
+
module Gpx2png
|
5
|
+
class Landscape < Osm
|
6
|
+
|
7
|
+
# Convert OSM/UMP tile coords to url
|
8
|
+
def self.url(zoom, coord, server = '3.')
|
9
|
+
x, y = coord
|
10
|
+
url = "http://tile.thunderforest.com/landscape/#{zoom}/#{x}/#{y}.png"
|
11
|
+
return url
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.licence_string
|
15
|
+
"All maps copyright Thunderforest.com and OpenStreetMap contributors"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Gpx2png
|
2
|
+
class Layer
|
3
|
+
def initialize
|
4
|
+
@coords = Array.new
|
5
|
+
@options = Hash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :options, :coords, :parent
|
9
|
+
|
10
|
+
def coords=(_coords)
|
11
|
+
logger.debug("Set #{_coords.size.to_s.red} for layer #{self.index.to_s.green}")
|
12
|
+
@coords = _coords
|
13
|
+
end
|
14
|
+
|
15
|
+
def coords
|
16
|
+
@coords
|
17
|
+
end
|
18
|
+
|
19
|
+
# Number of this layer
|
20
|
+
def index
|
21
|
+
if @parent
|
22
|
+
return parent.layers.index(self)
|
23
|
+
end
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def logger
|
28
|
+
@parent.class.logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(lat, lon)
|
32
|
+
logger.debug("Added coord #{lat.to_s.red},#{lon.to_s.red} for layer #{self.index.to_s.green}, count #{(@coords.size + 1).to_s.blue}")
|
33
|
+
@coords << { lat: lat, lon: lon }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/osm'
|
3
|
+
|
4
|
+
module Gpx2png
|
5
|
+
class OpenCycle < Osm
|
6
|
+
|
7
|
+
# Convert OSM/UMP tile coords to url
|
8
|
+
def self.url(zoom, coord, server = '3.')
|
9
|
+
x, y = coord
|
10
|
+
url = "http://tile.thunderforest.com/cycle/#{zoom}/#{x}/#{y}.png"
|
11
|
+
return url
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.licence_string
|
15
|
+
"All maps copyright Thunderforest.com and OpenStreetMap contributors"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/gpx2png/osm.rb
CHANGED
data/lib/gpx2png/osm_base.rb
CHANGED
@@ -1,36 +1,16 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'gpx2png/base'
|
3
2
|
require 'net/http'
|
4
3
|
require "uri"
|
5
|
-
|
6
|
-
|
4
|
+
require 'gpx2png/calculations/osm_class_methods'
|
5
|
+
require 'gpx2png/calculations/osm_instance_methods'
|
7
6
|
|
8
7
|
module Gpx2png
|
9
8
|
class OsmBase < Base
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# if true it will not download tiles
|
14
|
-
attr_accessor :simulate_download
|
9
|
+
extend Calculations::OsmClassMethods
|
10
|
+
include Calculations::OsmInstanceMethods
|
15
11
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
|
20
|
-
def simulate_download?
|
21
|
-
return true if true == self.simulate_download or (defined? @@simulate_download and true == @@simulate_download)
|
22
|
-
end
|
23
|
-
|
24
|
-
# http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#X_and_Y
|
25
|
-
# Convert latlon deg to OSM tile coords
|
26
|
-
def self.convert(zoom, coord)
|
27
|
-
lat_deg, lon_deg = coord
|
28
|
-
lat_rad = deg2rad(lat_deg)
|
29
|
-
x = (((lon_deg + 180) / 360) * (2 ** zoom)).floor
|
30
|
-
y = ((1 - Math.log(Math.tan(lat_rad) + 1 / Math.cos(lat_rad)) / Math::PI) /2 * (2 ** zoom)).floor
|
31
|
-
|
32
|
-
return [x, y]
|
33
|
-
end
|
12
|
+
TILE_WIDTH = Calculations::OsmClassMethods::TILE_WIDTH
|
13
|
+
TILE_HEIGHT = Calculations::OsmClassMethods::TILE_HEIGHT
|
34
14
|
|
35
15
|
# Convert latlon deg to OSM tile url
|
36
16
|
# TODO add algorithm to choose from diff. servers
|
@@ -46,265 +26,11 @@ module Gpx2png
|
|
46
26
|
return url
|
47
27
|
end
|
48
28
|
|
49
|
-
# Convert OSM tile coords to latlon deg in top-left corner
|
50
|
-
def self.reverse_convert(zoom, coord)
|
51
|
-
x, y = coord
|
52
|
-
n = 2 ** zoom
|
53
|
-
lon_deg = x.to_f / n.to_f * 360.0 - 180.0
|
54
|
-
lat_deg = rad2deg(Math.atan(Math.sinh(Math::PI * (1.to_f - 2.to_f * y.to_f / n.to_f))))
|
55
|
-
return [lat_deg, lon_deg]
|
56
|
-
end
|
57
|
-
|
58
|
-
# Lazy calc proper zoom for drawing
|
59
|
-
def self.calc_zoom(lat_min, lat_max, lon_min, lon_max, width, height)
|
60
|
-
# because I'm lazy! :] and math is not my best side
|
61
|
-
|
62
|
-
last_zoom = 2
|
63
|
-
(5..18).each do |zoom|
|
64
|
-
# calculate drawing tile size and pixel size
|
65
|
-
tile_min = point_on_absolute_image(zoom, [lat_min, lon_min])
|
66
|
-
tile_max = point_on_absolute_image(zoom, [lat_max, lon_max])
|
67
|
-
current_pixel_x_distance = tile_max[0] - tile_min[0]
|
68
|
-
current_pixel_y_distance = tile_min[1] - tile_max[1]
|
69
|
-
if current_pixel_x_distance > width or current_pixel_y_distance > height
|
70
|
-
return last_zoom
|
71
|
-
end
|
72
|
-
last_zoom = zoom
|
73
|
-
end
|
74
|
-
return 18
|
75
|
-
end
|
76
|
-
|
77
|
-
# Convert latlon deg coords to image point (x,y) and OSM tile coord
|
78
|
-
# return where you should put point on tile
|
79
|
-
def self.point_on_image(zoom, geo_coord)
|
80
|
-
osm_tile_coord = convert(zoom, geo_coord)
|
81
|
-
top_left_corner = reverse_convert(zoom, osm_tile_coord)
|
82
|
-
bottom_right_corner = reverse_convert(zoom, [
|
83
|
-
osm_tile_coord[0] + 1, osm_tile_coord[1] + 1
|
84
|
-
])
|
85
|
-
|
86
|
-
# some line math: y = ax + b
|
87
|
-
|
88
|
-
x_geo = geo_coord[1]
|
89
|
-
# offset
|
90
|
-
x_offset = x_geo - top_left_corner[1]
|
91
|
-
# scale
|
92
|
-
x_distance = (bottom_right_corner[1] - top_left_corner[1])
|
93
|
-
x = (TILE_WIDTH.to_f * (x_offset / x_distance)).round
|
94
|
-
|
95
|
-
y_geo = geo_coord[0]
|
96
|
-
# offset
|
97
|
-
y_offset = y_geo - top_left_corner[0]
|
98
|
-
# scale
|
99
|
-
y_distance = (bottom_right_corner[0] - top_left_corner[0])
|
100
|
-
y = (TILE_HEIGHT.to_f * (y_offset / y_distance)).round
|
101
|
-
|
102
|
-
return { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
103
|
-
end
|
104
|
-
|
105
|
-
# Useful for calculating distance on output image
|
106
|
-
# It is not position on output image because we don't know tile coords
|
107
|
-
# For upper-left tile
|
108
|
-
def self.point_on_absolute_image(zoom, geo_coord)
|
109
|
-
_p = point_on_image(zoom, geo_coord)
|
110
|
-
_x = _p[:osm_title_coord][0] * TILE_WIDTH + _p[:pixel_offset][0]
|
111
|
-
_y = _p[:osm_title_coord][1] * TILE_WIDTH + _p[:pixel_offset][1]
|
112
|
-
return [_x, _y]
|
113
|
-
end
|
114
|
-
|
115
|
-
# Create image with fixed size
|
116
|
-
def fixed_size(_width, _height)
|
117
|
-
@fixed_width = _width
|
118
|
-
@fixed_height = _height
|
119
|
-
end
|
120
|
-
|
121
|
-
def initial_calculations
|
122
|
-
@lat_min = @coords.collect { |c| c[:lat] }.min
|
123
|
-
@lat_max = @coords.collect { |c| c[:lat] }.max
|
124
|
-
@lon_min = @coords.collect { |c| c[:lon] }.min
|
125
|
-
@lon_max = @coords.collect { |c| c[:lon] }.max
|
126
|
-
|
127
|
-
# auto zoom must be here
|
128
|
-
# drawing must fit into fixed resolution
|
129
|
-
# map must be bigger than fixed resolution
|
130
|
-
if @fixed_width and @fixed_height
|
131
|
-
@new_zoom = self.class.calc_zoom(
|
132
|
-
@lat_min, @lat_max,
|
133
|
-
@lon_min, @lon_max,
|
134
|
-
@fixed_width, @fixed_height
|
135
|
-
)
|
136
|
-
puts "Calculated new zoom #{@new_zoom} (was #{@zoom})" if @verbose
|
137
|
-
@zoom = @new_zoom
|
138
|
-
end
|
139
|
-
|
140
|
-
@border_tiles = [
|
141
|
-
self.class.convert(@zoom, [@lat_min, @lon_min]),
|
142
|
-
self.class.convert(@zoom, [@lat_max, @lon_max])
|
143
|
-
]
|
144
|
-
|
145
|
-
@tile_x_range = (@border_tiles[0][0])..(@border_tiles[1][0])
|
146
|
-
@tile_y_range = (@border_tiles[1][1])..(@border_tiles[0][1])
|
147
|
-
|
148
|
-
# enlarging ranges to fill up map area
|
149
|
-
# both sizes are enlarged
|
150
|
-
# = ( ( (preferred size - real size) / tile width ) / 2 ).ceil
|
151
|
-
if @fixed_width and @fixed_height
|
152
|
-
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
|
153
|
-
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
|
154
|
-
puts "Expanding X tiles from both sides #{x_axis_expand_count}" if @verbose
|
155
|
-
puts "Expanding Y tiles from both sides #{y_axis_expand_count}" if @verbose
|
156
|
-
@tile_x_range = ((@tile_x_range.min - x_axis_expand_count)..(@tile_x_range.max + x_axis_expand_count))
|
157
|
-
@tile_y_range = ((@tile_y_range.min - y_axis_expand_count)..(@tile_y_range.max + y_axis_expand_count))
|
158
|
-
end
|
159
|
-
|
160
|
-
# new/full image size
|
161
|
-
@full_image_x = (1 + @tile_x_range.max - @tile_x_range.min) * TILE_WIDTH
|
162
|
-
@full_image_y = (1 + @tile_y_range.max - @tile_y_range.min) * TILE_HEIGHT
|
163
|
-
@r.x = @full_image_x
|
164
|
-
@r.y = @full_image_y
|
165
|
-
|
166
|
-
if @fixed_width and @fixed_height
|
167
|
-
calculate_for_crop_with_auto_zoom
|
168
|
-
else
|
169
|
-
calculate_for_crop
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Calculate zoom level
|
174
|
-
def auto_zoom_for(x = 0, y = 0)
|
175
|
-
# TODO
|
176
|
-
end
|
177
|
-
|
178
|
-
attr_reader :lat_min, :lat_max, :lon_min, :lon_max
|
179
|
-
attr_reader :tile_x_distance, :tile_y_distance
|
180
|
-
# points for cropping
|
181
|
-
attr_reader :bitmap_point_x_max, :bitmap_point_x_min, :bitmap_point_y_max, :bitmap_point_y_min
|
182
|
-
|
183
|
-
# Do everything
|
184
|
-
def download_and_join_tiles
|
185
|
-
puts "Output image dimension #{@full_image_x}x#{@full_image_y}" if @verbose
|
186
|
-
@r.new_image
|
187
|
-
|
188
|
-
# {:x, :y, :blob}
|
189
|
-
@images = Array.new
|
190
|
-
|
191
|
-
|
192
|
-
@tile_x_range.each do |x|
|
193
|
-
@tile_y_range.each do |y|
|
194
|
-
url = self.class.url(@zoom, [x, y])
|
195
|
-
|
196
|
-
# blob time
|
197
|
-
unless simulate_download?
|
198
|
-
uri = URI.parse(url)
|
199
|
-
response = Net::HTTP.get_response(uri)
|
200
|
-
blob = response.body
|
201
|
-
else
|
202
|
-
blob = @r.blank_tile(TILE_WIDTH, TILE_HEIGHT, x+y)
|
203
|
-
end
|
204
|
-
|
205
|
-
@r.add_tile(
|
206
|
-
blob,
|
207
|
-
(x - @tile_x_range.min) * TILE_WIDTH,
|
208
|
-
(y - @tile_y_range.min) * TILE_HEIGHT
|
209
|
-
)
|
210
|
-
|
211
|
-
@images << {
|
212
|
-
url: url,
|
213
|
-
x: x,
|
214
|
-
y: y
|
215
|
-
}
|
216
|
-
|
217
|
-
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
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# sweet, image is joined
|
222
|
-
|
223
|
-
# min/max points used for cropping
|
224
|
-
@bitmap_point_x_max = (@full_image_x / 2).round
|
225
|
-
@bitmap_point_x_min = (@full_image_x / 2).round
|
226
|
-
@bitmap_point_y_max = (@full_image_y / 2).round
|
227
|
-
@bitmap_point_y_min = (@full_image_y / 2).round
|
228
|
-
|
229
|
-
# add some coords to the map
|
230
|
-
(1...@coords.size).each do |i|
|
231
|
-
lat_from = @coords[i-1][:lat]
|
232
|
-
lon_from = @coords[i-1][:lon]
|
233
|
-
|
234
|
-
lat_to = @coords[i][:lat]
|
235
|
-
lon_to = @coords[i][:lon]
|
236
|
-
|
237
|
-
point_from = self.class.point_on_image(@zoom, [lat_from, lon_from])
|
238
|
-
point_to = self.class.point_on_image(@zoom, [lat_to, lon_to])
|
239
|
-
# { osm_title_coord: osm_tile_coord, pixel_offset: [x, y] }
|
240
|
-
|
241
|
-
# first point
|
242
|
-
bitmap_xa = (point_from[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_from[:pixel_offset][0]
|
243
|
-
bitmap_ya = (point_from[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_from[:pixel_offset][1]
|
244
|
-
bitmap_xb = (point_to[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_to[:pixel_offset][0]
|
245
|
-
bitmap_yb = (point_to[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_to[:pixel_offset][1]
|
246
|
-
|
247
|
-
@r.line(
|
248
|
-
bitmap_xa, bitmap_ya,
|
249
|
-
bitmap_xb, bitmap_yb
|
250
|
-
)
|
251
|
-
end
|
252
|
-
|
253
|
-
# add points
|
254
|
-
@markers.each do |point|
|
255
|
-
lat = point[:lat]
|
256
|
-
lon = point[:lon]
|
257
|
-
|
258
|
-
p = self.class.point_on_image(@zoom, [lat, lon])
|
259
|
-
bitmap_x = (p[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + p[:pixel_offset][0]
|
260
|
-
bitmap_y = (p[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + p[:pixel_offset][1]
|
261
|
-
|
262
|
-
point[:x] = bitmap_x
|
263
|
-
point[:y] = bitmap_y
|
264
|
-
|
265
|
-
@r.markers << point
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
# Calculate some numbers for cropping operation
|
270
|
-
def calculate_for_crop
|
271
|
-
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
272
|
-
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
273
|
-
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
274
|
-
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
275
|
-
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
276
|
-
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
277
|
-
|
278
|
-
@r.set_crop(@bitmap_point_x_min, @bitmap_point_x_max, @bitmap_point_y_min, @bitmap_point_y_max)
|
279
|
-
end
|
280
|
-
|
281
|
-
# Calculate some numbers for cropping operation with autozoom
|
282
|
-
def calculate_for_crop_with_auto_zoom
|
283
|
-
point_min = self.class.point_on_image(@zoom, [@lat_min, @lon_min])
|
284
|
-
point_max = self.class.point_on_image(@zoom, [@lat_max, @lon_max])
|
285
|
-
@bitmap_point_x_min = (point_min[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_min[:pixel_offset][0]
|
286
|
-
@bitmap_point_x_max = (point_max[:osm_title_coord][0] - @tile_x_range.min) * TILE_WIDTH + point_max[:pixel_offset][0]
|
287
|
-
@bitmap_point_y_max = (point_min[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_min[:pixel_offset][1]
|
288
|
-
@bitmap_point_y_min = (point_max[:osm_title_coord][1] - @tile_y_range.min) * TILE_HEIGHT + point_max[:pixel_offset][1]
|
289
|
-
|
290
|
-
bitmap_x_center = (@bitmap_point_x_min + @bitmap_point_x_max) / 2
|
291
|
-
bitmap_y_center = (@bitmap_point_y_min + @bitmap_point_y_max) / 2
|
292
|
-
|
293
|
-
@r.set_crop_fixed(bitmap_x_center, bitmap_y_center, @fixed_width, @fixed_height)
|
294
|
-
end
|
295
|
-
|
296
|
-
def expand_map
|
297
|
-
# TODO expand min and max ranges
|
298
|
-
end
|
299
|
-
|
300
29
|
def self.licence_string
|
301
30
|
"Map data OpenStreetMap (CC-by-SA 2.0)"
|
302
31
|
end
|
303
32
|
|
304
|
-
|
305
|
-
@r.destroy
|
306
|
-
puts "Image destroyed" if @verbose
|
307
|
-
end
|
33
|
+
|
308
34
|
|
309
35
|
end
|
310
36
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/osm'
|
3
|
+
|
4
|
+
module Gpx2png
|
5
|
+
class Outdoors < Osm
|
6
|
+
|
7
|
+
# Convert OSM/UMP tile coords to url
|
8
|
+
def self.url(zoom, coord, server = '3.')
|
9
|
+
x, y = coord
|
10
|
+
url = "http://tile.thunderforest.com/outdoors/#{zoom}/#{x}/#{y}.png"
|
11
|
+
return url
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.licence_string
|
15
|
+
"All maps copyright Thunderforest.com and OpenStreetMap contributors"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -2,8 +2,6 @@ require 'rubygems'
|
|
2
2
|
require 'RMagick'
|
3
3
|
require 'gpx2png/assets/sample_marker'
|
4
4
|
|
5
|
-
$:.unshift(File.dirname(__FILE__))
|
6
|
-
|
7
5
|
module Gpx2png
|
8
6
|
class RmagickRenderer
|
9
7
|
def initialize(_options = {})
|
@@ -77,8 +75,6 @@ module Gpx2png
|
|
77
75
|
|
78
76
|
# Setup crop image using CSS padding style data
|
79
77
|
def set_crop(x_min, x_max, y_min, y_max)
|
80
|
-
# puts @x, @y, @crop_margin, x_min, x_max, y_min, y_max
|
81
|
-
|
82
78
|
@crop_t = y_min - @crop_margin
|
83
79
|
@crop_r = (@x - x_max) - @crop_margin
|
84
80
|
@crop_b = (@y - y_max) - @crop_margin
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'gpx2png/osm'
|
3
|
+
|
4
|
+
module Gpx2png
|
5
|
+
class Transport < Osm
|
6
|
+
|
7
|
+
# Convert OSM/UMP tile coords to url
|
8
|
+
def self.url(zoom, coord, server = '3.')
|
9
|
+
x, y = coord
|
10
|
+
url = "http://tile.thunderforest.com/transport/#{zoom}/#{x}/#{y}.png"
|
11
|
+
return url
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.licence_string
|
15
|
+
"All maps copyright Thunderforest.com and OpenStreetMap contributors"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
data/lib/gpx2png/ump.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gpx2png
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-12-
|
12
|
+
date: 2013-12-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mini_exiftool
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: colorize
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
62
78
|
- !ruby/object:Gem::Dependency
|
63
79
|
name: rspec
|
64
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,10 +158,19 @@ files:
|
|
142
158
|
- lib/gpx2png.rb
|
143
159
|
- lib/gpx2png/assets/sample_marker.rb
|
144
160
|
- lib/gpx2png/base.rb
|
161
|
+
- lib/gpx2png/calculations/base_class_methods.rb
|
162
|
+
- lib/gpx2png/calculations/base_instance_methods.rb
|
163
|
+
- lib/gpx2png/calculations/osm_class_methods.rb
|
164
|
+
- lib/gpx2png/calculations/osm_instance_methods.rb
|
165
|
+
- lib/gpx2png/landscape.rb
|
166
|
+
- lib/gpx2png/layer.rb
|
167
|
+
- lib/gpx2png/opencycle.rb
|
145
168
|
- lib/gpx2png/osm.rb
|
146
169
|
- lib/gpx2png/osm_base.rb
|
170
|
+
- lib/gpx2png/outdoors.rb
|
147
171
|
- lib/gpx2png/renderers/chunky_png_renderer.rb
|
148
172
|
- lib/gpx2png/renderers/rmagick_renderer.rb
|
173
|
+
- lib/gpx2png/transport.rb
|
149
174
|
- lib/gpx2png/ump.rb
|
150
175
|
homepage: http://github.com/akwiatkowski/gpx2png
|
151
176
|
licenses:
|
@@ -162,7 +187,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
162
187
|
version: '0'
|
163
188
|
segments:
|
164
189
|
- 0
|
165
|
-
hash:
|
190
|
+
hash: 828698515099526590
|
166
191
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
192
|
none: false
|
168
193
|
requirements:
|