gpx2exif 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +39 -32
- data/README.md +1 -1
- data/Rakefile +10 -3
- data/VERSION +1 -1
- data/bin/geotag +157 -0
- data/bin/gpx2png +24 -1
- data/lib/geotagger/exif_editor.rb +93 -30
- data/lib/geotagger/geotagger.rb +13 -4
- data/lib/geotagger/track_importer.rb +61 -7
- data/lib/gpx2png/osm.rb +3 -2
- data/lib/gpx2png/osm_base.rb +30 -5
- data/lib/gpx2png/renderers/rmagick_renderer.rb +1 -1
- metadata +40 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5c57a7184d4a6c5a92e236813aaac15520bfac1d
|
4
|
+
data.tar.gz: b75af062cb4ca3cd443f0cacdb34d322f8c07b9f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6e0978ea19d9ced64f58cc8d8adece6cf8b2930fb1d798484293d6fa99fbcec5bcacfa324ef9376b72c05a62b6fe4dad46362094c2e18b493a546f6d93c0c0ea
|
7
|
+
data.tar.gz: d45af749bd7beae9b406b5dcaac6717bb161ed75806213a8a30b735c120be2c99185bb34ffeccaffe34aa9dc98949920d7825bcc922f080067bdcc3925cb289b
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,69 +1,75 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
addressable (2.3.
|
4
|
+
addressable (2.3.6)
|
5
5
|
builder (3.2.2)
|
6
|
-
chunky_png (1.
|
6
|
+
chunky_png (1.3.0)
|
7
|
+
descendants_tracker (0.0.4)
|
8
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
7
9
|
diff-lcs (1.2.5)
|
8
|
-
docile (1.1.
|
9
|
-
faraday (0.
|
10
|
-
multipart-post (
|
10
|
+
docile (1.1.3)
|
11
|
+
faraday (0.9.0)
|
12
|
+
multipart-post (>= 1.2, < 3)
|
13
|
+
geokit (1.8.4)
|
14
|
+
multi_json (>= 1.3.2)
|
11
15
|
git (1.2.6)
|
12
|
-
github_api (0.
|
13
|
-
addressable
|
14
|
-
|
16
|
+
github_api (0.11.3)
|
17
|
+
addressable (~> 2.3)
|
18
|
+
descendants_tracker (~> 0.0.1)
|
19
|
+
faraday (~> 0.8, < 0.10)
|
15
20
|
hashie (>= 1.2)
|
16
|
-
multi_json (
|
17
|
-
nokogiri (~> 1.
|
21
|
+
multi_json (>= 1.7.5, < 2.0)
|
22
|
+
nokogiri (~> 1.6.0)
|
18
23
|
oauth2
|
19
24
|
gpx_utils (0.0.1)
|
20
25
|
builder
|
21
26
|
nokogiri
|
22
|
-
hashie (2.
|
23
|
-
highline (1.6.
|
24
|
-
|
25
|
-
jeweler (1.8.8)
|
27
|
+
hashie (2.1.1)
|
28
|
+
highline (1.6.21)
|
29
|
+
jeweler (2.0.1)
|
26
30
|
builder
|
27
|
-
bundler (
|
31
|
+
bundler (>= 1.0)
|
28
32
|
git (>= 1.2.5)
|
29
|
-
github_api
|
33
|
+
github_api
|
30
34
|
highline (>= 1.6.15)
|
31
|
-
nokogiri (
|
35
|
+
nokogiri (>= 1.5.10)
|
32
36
|
rake
|
33
37
|
rdoc
|
34
38
|
json (1.8.1)
|
35
|
-
jwt (0.1.
|
39
|
+
jwt (0.1.11)
|
36
40
|
multi_json (>= 1.5)
|
37
|
-
mini_exiftool (2.
|
38
|
-
|
41
|
+
mini_exiftool (2.4.1)
|
42
|
+
mini_portile (0.5.3)
|
43
|
+
multi_json (1.9.2)
|
39
44
|
multi_xml (0.5.5)
|
40
|
-
multipart-post (
|
41
|
-
nokogiri (1.
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
jwt (~> 0.1.
|
46
|
-
multi_json (~> 1.
|
45
|
+
multipart-post (2.0.0)
|
46
|
+
nokogiri (1.6.1)
|
47
|
+
mini_portile (~> 0.5.0)
|
48
|
+
oauth2 (0.9.3)
|
49
|
+
faraday (>= 0.8, < 0.10)
|
50
|
+
jwt (~> 0.1.8)
|
51
|
+
multi_json (~> 1.3)
|
47
52
|
multi_xml (~> 0.5)
|
48
53
|
rack (~> 1.2)
|
49
54
|
rack (1.5.2)
|
50
|
-
rake (10.1
|
51
|
-
rdoc (4.
|
55
|
+
rake (10.3.1)
|
56
|
+
rdoc (4.1.1)
|
52
57
|
json (~> 1.4)
|
53
58
|
rmagick (2.13.2)
|
54
59
|
rspec (2.14.1)
|
55
60
|
rspec-core (~> 2.14.0)
|
56
61
|
rspec-expectations (~> 2.14.0)
|
57
62
|
rspec-mocks (~> 2.14.0)
|
58
|
-
rspec-core (2.14.
|
59
|
-
rspec-expectations (2.14.
|
63
|
+
rspec-core (2.14.8)
|
64
|
+
rspec-expectations (2.14.5)
|
60
65
|
diff-lcs (>= 1.1.3, < 2.0)
|
61
|
-
rspec-mocks (2.14.
|
66
|
+
rspec-mocks (2.14.6)
|
62
67
|
simplecov (0.8.2)
|
63
68
|
docile (~> 1.1.0)
|
64
69
|
multi_json
|
65
70
|
simplecov-html (~> 0.8.0)
|
66
71
|
simplecov-html (0.8.0)
|
72
|
+
thread_safe (0.3.3)
|
67
73
|
|
68
74
|
PLATFORMS
|
69
75
|
ruby
|
@@ -71,6 +77,7 @@ PLATFORMS
|
|
71
77
|
DEPENDENCIES
|
72
78
|
bundler
|
73
79
|
chunky_png
|
80
|
+
geokit
|
74
81
|
gpx_utils
|
75
82
|
jeweler
|
76
83
|
mini_exiftool
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -20,9 +20,9 @@ Jeweler::Tasks.new do |gem|
|
|
20
20
|
gem.summary = %Q{Mass geotagger using GPX files}
|
21
21
|
gem.description = %Q{Mass geotagger using GPX files.}
|
22
22
|
gem.email = "bobikx@poczta.fm"
|
23
|
-
gem.authors = ["Aleksander Kwiatkowski"]
|
23
|
+
gem.authors = ["Aleksander Kwiatkowski", "Craig Taverner"]
|
24
24
|
# dependencies defined in Gemfile
|
25
|
-
gem.executables = ['geotag_all_images', 'geotag_simulate', 'gpx2png']
|
25
|
+
gem.executables = ['geotag', 'geotag_all_images', 'geotag_simulate', 'gpx2png']
|
26
26
|
|
27
27
|
gem.files = FileList[
|
28
28
|
"[A-Z]*", "{bin,generators,lib,test}/**/*"
|
@@ -49,4 +49,11 @@ desc "Run RSpec with code coverage"
|
|
49
49
|
task :coverage do
|
50
50
|
`rake spec COVERAGE=true`
|
51
51
|
#`open coverage/index.html`
|
52
|
-
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Make temp directory for image output"
|
55
|
+
task :make_temp do
|
56
|
+
FileUtils.mkdir_p "samples/tmp"
|
57
|
+
end
|
58
|
+
|
59
|
+
task :spec => :make_temp
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.1
|
data/bin/geotag
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'gpx2exif'
|
5
|
+
require 'gpx2png/osm'
|
6
|
+
require 'gpx2png/ump'
|
7
|
+
require 'optparse'
|
8
|
+
require 'geotagger/geotagger'
|
9
|
+
|
10
|
+
options = { }
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: geotag [options] [files]"
|
13
|
+
|
14
|
+
opts.on("-d", "--[no-]debug", "Debug mode") do |v|
|
15
|
+
options[:debug] = v
|
16
|
+
end
|
17
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
|
18
|
+
options[:verbose] = v
|
19
|
+
end
|
20
|
+
opts.on("-m", "--[no-]simulate", "Run geotagging as simulation") do |v|
|
21
|
+
options[:verbose] = v
|
22
|
+
end
|
23
|
+
opts.on("-a", "--[no-]autofind", "Run geotagging using all GPX and JPG files found from current path") do |v|
|
24
|
+
options[:verbose] = v
|
25
|
+
end
|
26
|
+
opts.on("-g", "--gpx FILE", "Input GPX file") do |v|
|
27
|
+
options[:gpx] = v
|
28
|
+
end
|
29
|
+
opts.on("-T", "--start-time DATETIME", "Time of first image for images without timestamps") do |v|
|
30
|
+
options[:start_time] = v
|
31
|
+
end
|
32
|
+
opts.on("-G", "--time-gap MILLIS", "Time gap in milliseconds between images for images without timestamps") do |v|
|
33
|
+
options[:time_gap] = v.to_f
|
34
|
+
end
|
35
|
+
opts.on("-t", "--timeoffset OFFSET", "Timeoffset between GPX and EXIF") do |v|
|
36
|
+
options[:time_offset] = v.to_i
|
37
|
+
end
|
38
|
+
opts.on("-p", "--pattern FILEPAT", "Input image file pattern") do |v|
|
39
|
+
options[:pattern] = v
|
40
|
+
end
|
41
|
+
opts.on("-z", "--zoom ZOOM", "Set zoom") do |v|
|
42
|
+
options[:zoom] = v
|
43
|
+
end
|
44
|
+
opts.on("-s", "--size WIDTHxHEIGHT", "Set output map image size") do |v|
|
45
|
+
options[:size] = v
|
46
|
+
end
|
47
|
+
opts.on("-e", "--scale SCALE:XFACTOR:YFACTOR", "Scale dataset for closeup view") do |v|
|
48
|
+
s,x,y = v.split(/\:/)
|
49
|
+
options[:scale] = {
|
50
|
+
:scale => [[s.to_f,0.00001].max,1000].min,
|
51
|
+
:shift_x => x,
|
52
|
+
:shift_y => (y || x)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
opts.on("-x", "--gpx-tags TAGS", "Comma separated list of key=value tags to add to EXIF") do |v|
|
56
|
+
options[:exif_tags] ||= {}
|
57
|
+
v.split(/\,+/).each do |pair|
|
58
|
+
key,value = pair.split(/\s*\=\s*/)
|
59
|
+
options[:exif_tags][key] = value
|
60
|
+
end
|
61
|
+
puts "Setting extra EXIF tags: #{options[:exif_tags].inspect}"
|
62
|
+
end
|
63
|
+
opts.on("-o", "--output FILE", "Output map image file") do |v|
|
64
|
+
options[:output_file] = v
|
65
|
+
end
|
66
|
+
opts.on("-u", "--ump", "Use UMP tiles rather than OSM for output map background") do
|
67
|
+
options[:ump] = true
|
68
|
+
end
|
69
|
+
end.parse!
|
70
|
+
|
71
|
+
if options[:debug]
|
72
|
+
puts options.inspect
|
73
|
+
puts ARGV.inspect
|
74
|
+
end
|
75
|
+
|
76
|
+
unless options[:gpx]
|
77
|
+
puts "Input GPX file needed"
|
78
|
+
@fail = true
|
79
|
+
end
|
80
|
+
if options[:output_file]
|
81
|
+
options[:size] ||= "600x600"
|
82
|
+
options[:zoom] ||= 1.0
|
83
|
+
end
|
84
|
+
if options[:output_file].nil? && options[:pattern].nil? && ARGV.length == 0
|
85
|
+
puts "Need at least one file, or a file pattern, or an output image"
|
86
|
+
@fail = true
|
87
|
+
end
|
88
|
+
|
89
|
+
if @fail
|
90
|
+
exit 0
|
91
|
+
end
|
92
|
+
|
93
|
+
$track_importer = nil
|
94
|
+
|
95
|
+
if ARGV.length > 0 || options[:pattern] || options[:autofind]
|
96
|
+
g = Geotagger::Geotagger.new(options.merge(files: ARGV))
|
97
|
+
$track_importer = g.ti
|
98
|
+
if options[:autofind]
|
99
|
+
g.add_all_files(options[:time_offset].to_i)
|
100
|
+
end
|
101
|
+
if options[:gpx]
|
102
|
+
g.add_gpx_file(options[:gpx])
|
103
|
+
end
|
104
|
+
if options[:pattern]
|
105
|
+
g.add_pattern(options[:pattern])
|
106
|
+
end
|
107
|
+
if ARGV.length > 0
|
108
|
+
ARGV.each{|file| g.add_image(file)}
|
109
|
+
end
|
110
|
+
if options[:start_time]
|
111
|
+
g.fix_times
|
112
|
+
end
|
113
|
+
g.match_up
|
114
|
+
if options[:simulate]
|
115
|
+
g.simulate
|
116
|
+
else
|
117
|
+
g.save!
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if options[:output_file]
|
122
|
+
g = $track_importer
|
123
|
+
unless g
|
124
|
+
g = Geotagger::TrackImporter.new
|
125
|
+
g.add_file(options[:gpx])
|
126
|
+
end
|
127
|
+
|
128
|
+
if options[:ump]
|
129
|
+
e = Gpx2png::Ump.new
|
130
|
+
else
|
131
|
+
e = Gpx2png::Osm.new
|
132
|
+
end
|
133
|
+
e.coords = g.coords
|
134
|
+
g.auto_marker do |marker|
|
135
|
+
e.add_marker marker
|
136
|
+
end
|
137
|
+
|
138
|
+
if options[:scale]
|
139
|
+
e.scale_options = options[:scale]
|
140
|
+
end
|
141
|
+
|
142
|
+
if options[:size]
|
143
|
+
# constant size
|
144
|
+
options[:size] =~ /(\d+)x(\d+)/
|
145
|
+
e.fixed_size($1.to_i, $1.to_i)
|
146
|
+
else
|
147
|
+
# constant zoom
|
148
|
+
e.zoom = options[:zoom].to_i
|
149
|
+
e.renderer_options = {crop_enabled: true}
|
150
|
+
end
|
151
|
+
|
152
|
+
e.save(options[:output_file])
|
153
|
+
end
|
154
|
+
|
155
|
+
# testing
|
156
|
+
# ruby -Ilib bin/geotag -g spec/fixtures/sample.gpx -s 400x400 -o samples/tmp/cli_test.png -u
|
157
|
+
|
data/bin/gpx2png
CHANGED
@@ -40,6 +40,26 @@ if options[:zoom].nil? and options[:size].nil?
|
|
40
40
|
@fail = true
|
41
41
|
end
|
42
42
|
|
43
|
+
module GpxUtils
|
44
|
+
class TrackImporter
|
45
|
+
def self.make_label point
|
46
|
+
"#{point[:time].strftime('%H:%M:%S')}: (#{point[:lat]}, #{point[:lon]})"
|
47
|
+
end
|
48
|
+
def auto_marker
|
49
|
+
puts "Track starts: #{self.class.make_label self.coords[0]}"
|
50
|
+
puts "Track ends: #{self.class.make_label self.coords[-1]}"
|
51
|
+
|
52
|
+
(0..(self.coords.length/20)).each do |i|
|
53
|
+
index = i * 20
|
54
|
+
point = self.coords[index]
|
55
|
+
label = self.class.make_label point
|
56
|
+
yield({lat: point[:lat], lon: point[:lon], label: label})
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
43
63
|
unless @fail
|
44
64
|
g = GpxUtils::TrackImporter.new
|
45
65
|
g.add_file(options[:gpx])
|
@@ -50,6 +70,9 @@ unless @fail
|
|
50
70
|
e = Gpx2png::Osm.new
|
51
71
|
end
|
52
72
|
e.coords = g.coords
|
73
|
+
g.auto_marker do |marker|
|
74
|
+
e.add_marker marker
|
75
|
+
end
|
53
76
|
|
54
77
|
if options[:size]
|
55
78
|
# constant size
|
@@ -65,4 +88,4 @@ unless @fail
|
|
65
88
|
end
|
66
89
|
|
67
90
|
# testing
|
68
|
-
# ruby -Ilib bin/gpx2png -g spec/fixtures/sample.gpx -s 400x400 -o samples/tmp/cli_test.png -u
|
91
|
+
# ruby -Ilib bin/gpx2png -g spec/fixtures/sample.gpx -s 400x400 -o samples/tmp/cli_test.png -u
|
@@ -4,58 +4,75 @@ require 'mini_exiftool'
|
|
4
4
|
$:.unshift(File.dirname(__FILE__))
|
5
5
|
|
6
6
|
module Geotagger
|
7
|
-
class ExifEditor
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@verbose = options[:verbose]
|
13
|
-
end
|
8
|
+
# Wrapper class for path to image and the EXIF data read (and written)
|
9
|
+
# to the image.
|
10
|
+
class Image
|
14
11
|
|
15
|
-
attr_reader :
|
16
|
-
attr_accessor :global_time_offset
|
12
|
+
attr_reader :editor, :photo, :attr
|
17
13
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
def initialize(editor, path, time_offset = 0)
|
15
|
+
@editor = editor
|
16
|
+
@photo = MiniExiftool.new path
|
17
|
+
@attr = {
|
18
|
+
:path => path,
|
19
|
+
:time => (time = @photo['DateTimeOriginal']) && (time + time_offset + editor.global_time_offset)
|
22
20
|
}
|
23
|
-
@images << i
|
24
|
-
puts "Added file #{path}, time #{i[:time]}" if @verbose
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
23
|
+
def path
|
24
|
+
self[:path]
|
25
|
+
end
|
26
|
+
|
27
|
+
def time
|
28
|
+
self[:time]
|
30
29
|
end
|
31
30
|
|
32
|
-
def
|
33
|
-
|
31
|
+
def [](key)
|
32
|
+
@attr[key]
|
34
33
|
end
|
35
34
|
|
36
|
-
def
|
37
|
-
|
35
|
+
def []=(key,value)
|
36
|
+
@attr[key] = value
|
37
|
+
end
|
38
38
|
|
39
|
+
def get_photo_time(offset=0)
|
40
|
+
(time = photo['DateTimeOriginal']) && (time + offset)
|
41
|
+
end
|
42
|
+
|
43
|
+
def save!
|
39
44
|
# http://en.wikipedia.org/wiki/Geotagging#JPEG_photos
|
40
45
|
|
46
|
+
photo['ProcessingSoftware'] = 'gpx2exif'
|
47
|
+
|
41
48
|
photo['GPSVersionID'] = '2 2 0 0'
|
49
|
+
photo['DateTimeOriginal'] = self.time
|
42
50
|
|
43
|
-
photo['GPSLatitude'] = lat
|
44
|
-
photo['GPSLongitude'] = lon
|
51
|
+
photo['GPSLatitude'] = @attr[:coord][:lat]
|
52
|
+
photo['GPSLongitude'] = @attr[:coord][:lon]
|
45
53
|
|
46
|
-
lat_ref = "N"
|
47
|
-
lon_ref = "E"
|
48
|
-
lat_ref = "S" if lat < 0.0
|
49
|
-
lon_ref = "W" if lon < 0.0
|
54
|
+
lat_ref = (@attr[:coord][:lat] < 0.0) ? "S" : "N"
|
55
|
+
lon_ref = (@attr[:coord][:lon] < 0.0) ? "W" : "E"
|
50
56
|
|
51
57
|
photo['GPSLatitudeRef'] = lat_ref
|
52
58
|
photo['GPSLongitudeRef'] = lon_ref
|
53
59
|
|
54
|
-
photo['GPSAltitude'] = alt
|
55
|
-
photo
|
60
|
+
photo['GPSAltitude'] = @attr[:coord][:alt]
|
61
|
+
photo['Orientation'] = 1 # 1 means normal upright landscape mode
|
62
|
+
photo['GPSImgDirectionRef'] = 'T' # T=true north (as opposed to M=magnetic)
|
63
|
+
photo['GPSImgDirection'] = @attr[:coord][:direction] # calculated in TrackImporter
|
64
|
+
photo['GPSMapDatum'] = 'WGS-84' # We assume all GPS data is WGS-84
|
65
|
+
|
66
|
+
(editor.options[:exif_tags]||{}).each do |key,value|
|
67
|
+
photo[key] = value
|
68
|
+
end
|
69
|
+
|
70
|
+
unless photo.save
|
71
|
+
puts "Failed to save exif data to '#{path}': #{photo.errors.inspect}"
|
72
|
+
end
|
56
73
|
|
57
74
|
photo2 = MiniExiftool.new path
|
58
|
-
puts " - coord saved lat #{photo2['GPSLatitude']} lon #{photo2['GPSLongitude']}" if
|
75
|
+
puts " - coord saved lat #{photo2['GPSLatitude']} lon #{photo2['GPSLongitude']}" if editor.verbose
|
59
76
|
|
60
77
|
# exiftool -GPSMapDatum="WGS-84" -gps:GPSLatitude="34,57,57"
|
61
78
|
# -gps:GPSLatitudeRef="N" -gps:GPSLongitude="83,17,59" -gps:GPSLongitudeRef="W"
|
@@ -63,5 +80,51 @@ module Geotagger
|
|
63
80
|
# -State="North Carolina" -Country="USA" ~/Desktop/RabunBaldSummit_NC.jpg
|
64
81
|
end
|
65
82
|
|
83
|
+
def to_s
|
84
|
+
"Image[#{path}] at '#{time}'"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
class ExifEditor
|
90
|
+
|
91
|
+
attr_reader :options, :verbose
|
92
|
+
|
93
|
+
def initialize(options = {})
|
94
|
+
@options = options
|
95
|
+
@images = []
|
96
|
+
@image_map = {}
|
97
|
+
@global_time_offset = 0
|
98
|
+
@verbose = options[:verbose]
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_reader :images
|
102
|
+
attr_accessor :global_time_offset
|
103
|
+
|
104
|
+
def fix_times
|
105
|
+
start_time = options[:start_time] && DateTime.parse(options[:start_time]) || DateTime.now
|
106
|
+
puts "Start: #{start_time}"
|
107
|
+
@images.each_with_index do |image,index|
|
108
|
+
if image[:time].nil?
|
109
|
+
timestamp = start_time + ((options[:time_gap] || 1000).to_i * index) / (1000.0 * 24 * 60 * 60)
|
110
|
+
image[:time] = timestamp.to_time
|
111
|
+
end
|
112
|
+
puts "#{index}: #{image.attr.inspect}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def read_file(path, time_offset = 0)
|
117
|
+
image = Image.new(self,path,time_offset)
|
118
|
+
@images << image
|
119
|
+
@image_map[path] = image
|
120
|
+
puts "Added #{image}" if @verbose
|
121
|
+
image
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_photo_time(path)
|
125
|
+
image = @image_map[path] || read_file(path)
|
126
|
+
image.photo['DateTimeOriginal']
|
127
|
+
end
|
128
|
+
|
66
129
|
end
|
67
130
|
end
|
data/lib/geotagger/geotagger.rb
CHANGED
@@ -8,9 +8,11 @@ $:.unshift(File.dirname(__FILE__))
|
|
8
8
|
module Geotagger
|
9
9
|
class Geotagger
|
10
10
|
|
11
|
+
attr_reader :options, :ti
|
12
|
+
|
11
13
|
def initialize(options = {})
|
12
14
|
@verbose = options[:verbose]
|
13
|
-
@ee = ExifEditor.new
|
15
|
+
@ee = ExifEditor.new options
|
14
16
|
@ti = TrackImporter.new
|
15
17
|
@ti.verbose = @verbose
|
16
18
|
end
|
@@ -39,12 +41,19 @@ module Geotagger
|
|
39
41
|
@ee.read_file(path, time_offset)
|
40
42
|
end
|
41
43
|
|
44
|
+
def fix_times
|
45
|
+
@ee.fix_times
|
46
|
+
end
|
47
|
+
|
42
48
|
def match_up
|
49
|
+
@ti.determine_directions
|
43
50
|
@ee.images.each do |i|
|
44
|
-
puts "* searching for #{i
|
51
|
+
puts "* searching for #{i}" if @verbose
|
45
52
|
i[:coord] = @ti.find_by_time(i[:time])
|
46
53
|
if i[:coord].nil?
|
47
54
|
puts " - not found" if @verbose
|
55
|
+
else
|
56
|
+
@ti.add_image_marker(i)
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
@@ -55,7 +64,7 @@ module Geotagger
|
|
55
64
|
@ee.images.each do |i|
|
56
65
|
if not i[:coord].nil?
|
57
66
|
puts "! saving for #{i[:path]}" if @verbose
|
58
|
-
|
67
|
+
i.save!
|
59
68
|
end
|
60
69
|
|
61
70
|
end
|
@@ -67,4 +76,4 @@ module Geotagger
|
|
67
76
|
end
|
68
77
|
|
69
78
|
end
|
70
|
-
end
|
79
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'nokogiri'
|
3
|
+
require 'geokit'
|
3
4
|
|
4
5
|
$:.unshift(File.dirname(__FILE__))
|
5
6
|
|
@@ -17,17 +18,70 @@ module Geotagger
|
|
17
18
|
return false
|
18
19
|
end
|
19
20
|
|
21
|
+
def determine_directions(index=0)
|
22
|
+
if @coords.length > 1
|
23
|
+
previous_point = nil
|
24
|
+
@coords.each do |coord|
|
25
|
+
point = Geokit::LatLng.new(coord[:lat], coord[:lon])
|
26
|
+
if previous_point
|
27
|
+
coord[:direction] = previous_point.heading_to(point)
|
28
|
+
end
|
29
|
+
previous_point = point
|
30
|
+
end
|
31
|
+
@coords[0][:direction] = @coords[1][:direction]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
20
35
|
def find_by_time(time)
|
21
|
-
selected_coords = @coords.select
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
36
|
+
selected_coords = @coords.select do |c|
|
37
|
+
(c[:time].localtime - time.localtime).abs < THRESHOLD
|
38
|
+
end
|
39
|
+
selected_coords = selected_coords.sort do |a, b|
|
40
|
+
(a[:time].localtime - time.localtime).abs <=> (b[:time].localtime - time.localtime).abs
|
41
|
+
end
|
42
|
+
if @verbose
|
43
|
+
puts " - found #{selected_coords.size} coords within #{THRESHOLD}s from image time"
|
44
|
+
if selected_coords.size > 0
|
45
|
+
puts " - best is #{selected_coords.first[:time].localtime}, time offset #{selected_coords.first[:time].localtime - time.localtime}"
|
46
|
+
puts " - lat #{selected_coords.first[:lat]} lon #{selected_coords.first[:lon]}"
|
47
|
+
end
|
27
48
|
end
|
28
49
|
|
29
50
|
return selected_coords.first
|
30
51
|
end
|
31
52
|
|
53
|
+
def self.make_label(point, image=nil)
|
54
|
+
"#{point[:time].strftime('%H:%M:%S')}: (#{point[:lat]}, #{point[:lon]})#{image.nil? ? '' : image[:path]}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_image_marker(image)
|
58
|
+
@images ||= []
|
59
|
+
@images << image
|
60
|
+
end
|
61
|
+
|
62
|
+
def auto_marker
|
63
|
+
puts "Track starts: #{self.class.make_label self.coords[0]}"
|
64
|
+
puts "Track ends: #{self.class.make_label self.coords[-1]}"
|
65
|
+
|
66
|
+
coordset = self.coords.map do |coord|
|
67
|
+
image = @images.select {|i| i[:coord] == coord}[0]
|
68
|
+
{coord: coord, image: image}
|
69
|
+
end
|
70
|
+
|
71
|
+
prev_point = nil
|
72
|
+
coordset.each_with_index do |co,index|
|
73
|
+
coord = co[:coord]
|
74
|
+
image = co[:image]
|
75
|
+
puts "Labeling coord:#{coord} with image: #{image}" if image
|
76
|
+
point = Geokit::LatLng.new(coord[:lat], coord[:lon])
|
77
|
+
if prev_point.nil? || (distance = point.distance_from(prev_point, units: :kms) > 0.02)
|
78
|
+
label = self.class.make_label coord, image
|
79
|
+
prev_point = point
|
80
|
+
yield({lat: coord[:lat], lon: coord[:lon], label: label})
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
32
86
|
end
|
33
|
-
end
|
87
|
+
end
|
data/lib/gpx2png/osm.rb
CHANGED
@@ -32,10 +32,11 @@ module Gpx2png
|
|
32
32
|
|
33
33
|
def render
|
34
34
|
setup_renderer
|
35
|
-
initial_calculations
|
35
|
+
initial_calculations(@scale_options)
|
36
36
|
download_and_join_tiles
|
37
37
|
end
|
38
38
|
|
39
|
+
attr_accessor :scale_options
|
39
40
|
attr_accessor :renderer_options
|
40
41
|
|
41
42
|
# Get proper renderer class
|
@@ -54,4 +55,4 @@ module Gpx2png
|
|
54
55
|
end
|
55
56
|
|
56
57
|
end
|
57
|
-
end
|
58
|
+
end
|
data/lib/gpx2png/osm_base.rb
CHANGED
@@ -110,11 +110,36 @@ module Gpx2png
|
|
110
110
|
@fixed_height = _height
|
111
111
|
end
|
112
112
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
def min_coords_or(key,val)
|
114
|
+
val || @coords.collect { |c| c[key] }.min
|
115
|
+
end
|
116
|
+
|
117
|
+
def max_coords_or(key,val)
|
118
|
+
val || @coords.collect { |c| c[key] }.max
|
119
|
+
end
|
120
|
+
|
121
|
+
def initial_calculations(scale={})
|
122
|
+
scale ||= {}
|
123
|
+
puts "Initializing with scale: #{scale.inspect}"
|
124
|
+
@lat_min = min_coords_or(:lat,scale[:lat_min])
|
125
|
+
@lat_max = max_coords_or(:lat,scale[:lat_max])
|
126
|
+
@lon_min = min_coords_or(:lon,scale[:lon_min])
|
127
|
+
@lon_max = max_coords_or(:lon,scale[:lon_max])
|
128
|
+
|
129
|
+
if scale[:scale].to_f > 0.00001
|
130
|
+
puts "Scaling from: (#{lat_min},#{lon_min})-(#{lat_max},#{lon_max})"
|
131
|
+
dlat = @lat_max - @lat_min
|
132
|
+
dlon = @lon_max - @lon_min
|
133
|
+
dlat2 = dlat * scale[:scale].to_f
|
134
|
+
dlon2 = dlon * scale[:scale].to_f
|
135
|
+
shift_x = dlon2 * scale[:shift_x].to_f
|
136
|
+
shift_y = dlat2 * scale[:shift_y].to_f
|
137
|
+
@lat_max += shift_y - (dlat - dlat2)/2.0
|
138
|
+
@lat_min += shift_y + (dlat - dlat2)/2.0
|
139
|
+
@lon_max += shift_x - (dlon - dlon2)/2.0
|
140
|
+
@lon_max += shift_x + (dlon - dlon2)/2.0
|
141
|
+
puts "Scaled to: (#{lat_min},#{lon_min})-(#{lat_max},#{lon_max})"
|
142
|
+
end
|
118
143
|
|
119
144
|
# auto zoom must be here
|
120
145
|
# drawing must fit into fixed resolution
|
metadata
CHANGED
@@ -1,147 +1,146 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gpx2exif
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Aleksander Kwiatkowski
|
8
|
+
- Craig Taverner
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-04-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mini_exiftool
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
17
|
requirements:
|
19
|
-
- -
|
18
|
+
- - ">="
|
20
19
|
- !ruby/object:Gem::Version
|
21
20
|
version: '0'
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
24
|
requirements:
|
27
|
-
- -
|
25
|
+
- - ">="
|
28
26
|
- !ruby/object:Gem::Version
|
29
27
|
version: '0'
|
30
28
|
- !ruby/object:Gem::Dependency
|
31
29
|
name: rmagick
|
32
30
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
31
|
requirements:
|
35
|
-
- -
|
32
|
+
- - ">="
|
36
33
|
- !ruby/object:Gem::Version
|
37
34
|
version: '0'
|
38
35
|
type: :runtime
|
39
36
|
prerelease: false
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
38
|
requirements:
|
43
|
-
- -
|
39
|
+
- - ">="
|
44
40
|
- !ruby/object:Gem::Version
|
45
41
|
version: '0'
|
46
42
|
- !ruby/object:Gem::Dependency
|
47
43
|
name: chunky_png
|
48
44
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
45
|
requirements:
|
51
|
-
- -
|
46
|
+
- - ">="
|
52
47
|
- !ruby/object:Gem::Version
|
53
48
|
version: '0'
|
54
49
|
type: :runtime
|
55
50
|
prerelease: false
|
56
51
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
52
|
requirements:
|
59
|
-
- -
|
53
|
+
- - ">="
|
60
54
|
- !ruby/object:Gem::Version
|
61
55
|
version: '0'
|
62
56
|
- !ruby/object:Gem::Dependency
|
63
57
|
name: gpx_utils
|
64
58
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
59
|
requirements:
|
67
|
-
- -
|
60
|
+
- - ">="
|
68
61
|
- !ruby/object:Gem::Version
|
69
62
|
version: '0'
|
70
63
|
type: :runtime
|
71
64
|
prerelease: false
|
72
65
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
66
|
requirements:
|
75
|
-
- -
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: geokit
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
76
82
|
- !ruby/object:Gem::Version
|
77
83
|
version: '0'
|
78
84
|
- !ruby/object:Gem::Dependency
|
79
85
|
name: rspec
|
80
86
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
87
|
requirements:
|
83
|
-
- -
|
88
|
+
- - ">="
|
84
89
|
- !ruby/object:Gem::Version
|
85
90
|
version: '0'
|
86
91
|
type: :development
|
87
92
|
prerelease: false
|
88
93
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
94
|
requirements:
|
91
|
-
- -
|
95
|
+
- - ">="
|
92
96
|
- !ruby/object:Gem::Version
|
93
97
|
version: '0'
|
94
98
|
- !ruby/object:Gem::Dependency
|
95
99
|
name: bundler
|
96
100
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
101
|
requirements:
|
99
|
-
- -
|
102
|
+
- - ">="
|
100
103
|
- !ruby/object:Gem::Version
|
101
104
|
version: '0'
|
102
105
|
type: :development
|
103
106
|
prerelease: false
|
104
107
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
108
|
requirements:
|
107
|
-
- -
|
109
|
+
- - ">="
|
108
110
|
- !ruby/object:Gem::Version
|
109
111
|
version: '0'
|
110
112
|
- !ruby/object:Gem::Dependency
|
111
113
|
name: jeweler
|
112
114
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
115
|
requirements:
|
115
|
-
- -
|
116
|
+
- - ">="
|
116
117
|
- !ruby/object:Gem::Version
|
117
118
|
version: '0'
|
118
119
|
type: :development
|
119
120
|
prerelease: false
|
120
121
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
122
|
requirements:
|
123
|
-
- -
|
123
|
+
- - ">="
|
124
124
|
- !ruby/object:Gem::Version
|
125
125
|
version: '0'
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
127
|
name: simplecov
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
129
|
requirements:
|
131
|
-
- -
|
130
|
+
- - ">="
|
132
131
|
- !ruby/object:Gem::Version
|
133
132
|
version: '0'
|
134
133
|
type: :development
|
135
134
|
prerelease: false
|
136
135
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
136
|
requirements:
|
139
|
-
- -
|
137
|
+
- - ">="
|
140
138
|
- !ruby/object:Gem::Version
|
141
139
|
version: '0'
|
142
140
|
description: Mass geotagger using GPX files.
|
143
141
|
email: bobikx@poczta.fm
|
144
142
|
executables:
|
143
|
+
- geotag
|
145
144
|
- geotag_all_images
|
146
145
|
- geotag_simulate
|
147
146
|
- gpx2png
|
@@ -156,6 +155,7 @@ files:
|
|
156
155
|
- README.md
|
157
156
|
- Rakefile
|
158
157
|
- VERSION
|
158
|
+
- bin/geotag
|
159
159
|
- bin/geotag_all_images
|
160
160
|
- bin/geotag_simulate
|
161
161
|
- bin/gpx2png
|
@@ -178,29 +178,25 @@ files:
|
|
178
178
|
homepage: http://github.com/akwiatkowski/gpx2exif
|
179
179
|
licenses:
|
180
180
|
- LGPLv3
|
181
|
+
metadata: {}
|
181
182
|
post_install_message:
|
182
183
|
rdoc_options: []
|
183
184
|
require_paths:
|
184
185
|
- lib
|
185
186
|
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
-
none: false
|
187
187
|
requirements:
|
188
|
-
- -
|
188
|
+
- - ">="
|
189
189
|
- !ruby/object:Gem::Version
|
190
190
|
version: '0'
|
191
|
-
segments:
|
192
|
-
- 0
|
193
|
-
hash: -2766757745175557176
|
194
191
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
-
none: false
|
196
192
|
requirements:
|
197
|
-
- -
|
193
|
+
- - ">="
|
198
194
|
- !ruby/object:Gem::Version
|
199
195
|
version: '0'
|
200
196
|
requirements: []
|
201
197
|
rubyforge_project:
|
202
|
-
rubygems_version:
|
198
|
+
rubygems_version: 2.2.2
|
203
199
|
signing_key:
|
204
|
-
specification_version:
|
200
|
+
specification_version: 4
|
205
201
|
summary: Mass geotagger using GPX files
|
206
202
|
test_files: []
|