gpx2png 0.2.0 → 0.3.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 -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
|
-
[](https://flattr.com/submit/auto?user_id=bobik314&url=https://github.com/akwiatkowski/
|
41
|
+
[](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:
|