gpx2exif 0.2.0 → 0.3.1
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.
- 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: []
|