Skip gif animation optimization in case both frames are identical
[rails.git] / lib / gpx.rb
1 module GPX
2   class File
3     require "libxml"
4
5     include LibXML
6
7     attr_reader :possible_points
8     attr_reader :actual_points
9     attr_reader :tracksegs
10
11     def initialize(file)
12       @file = file
13     end
14
15     def points
16       return enum_for(:points) unless block_given?
17
18       @possible_points = 0
19       @actual_points = 0
20       @tracksegs = 0
21
22       @file.rewind
23
24       reader = XML::Reader.io(@file)
25
26       point = nil
27
28       while reader.read
29         if reader.node_type == XML::Reader::TYPE_ELEMENT
30           if reader.name == "trkpt"
31             point = TrkPt.new(@tracksegs, reader["lat"].to_f, reader["lon"].to_f)
32             @possible_points += 1
33           elsif reader.name == "ele" && point
34             point.altitude = reader.read_string.to_f
35           elsif reader.name == "time" && point
36             point.timestamp = Time.parse(reader.read_string)
37           end
38         elsif reader.node_type == XML::Reader::TYPE_END_ELEMENT
39           if reader.name == "trkpt" && point && point.valid?
40             point.altitude ||= 0
41             yield point
42             @actual_points += 1
43           elsif reader.name == "trkseg"
44             @tracksegs += 1
45           end
46         end
47       end
48     end
49
50     def picture(min_lat, min_lon, max_lat, max_lon, num_points)
51       nframes = 10
52       width = 250
53       height = 250
54       delay = 50
55
56       points_per_frame = num_points / nframes
57
58       proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
59
60       frames = []
61
62       (0...nframes).each do |n|
63         frames[n] = GD2::Image::IndexedColor.new(width, height)
64         black = frames[n].palette.allocate(GD2::Color[0, 0, 0])
65         white = frames[n].palette.allocate(GD2::Color[255, 255, 255])
66         grey = frames[n].palette.allocate(GD2::Color[187, 187, 187])
67
68         frames[n].draw do |pen|
69           pen.color = white
70           pen.rectangle(0, 0, width, height, true)
71         end
72
73         frames[n].draw do |pen|
74           pen.color = black
75           pen.anti_aliasing = true
76           pen.dont_blend = false
77
78           oldpx = 0.0
79           oldpy = 0.0
80
81           first = true
82
83           points.each_with_index do |p, pt|
84             px = proj.x(p.longitude)
85             py = proj.y(p.latitude)
86
87             if (pt >= (points_per_frame * n)) && (pt <= (points_per_frame * (n + 1)))
88               pen.thickness = 3
89               pen.color = black
90             else
91               pen.thickness = 1
92               pen.color = grey
93             end
94
95             pen.line(px, py, oldpx, oldpy) unless first
96             first = false
97             oldpy = py
98             oldpx = px
99           end
100         end
101       end
102
103       res = GD2::AnimatedGif.gif_anim_begin(frames[0])
104       res << GD2::AnimatedGif.gif_anim_add(frames[0], nil, delay)
105       (1...nframes).each do |n|
106         res << GD2::AnimatedGif.gif_anim_add(frames[n],
107                                              (frames[n] == frames[n - 1] ? nil : frames[n - 1]),
108                                              delay)
109       end
110       res << GD2::AnimatedGif.gif_anim_end
111
112       res
113     end
114
115     def icon(min_lat, min_lon, max_lat, max_lon)
116       width = 50
117       height = 50
118       proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
119
120       image = GD2::Image::IndexedColor.new(width, height)
121
122       black = image.palette.allocate(GD2::Color[0, 0, 0])
123       white = image.palette.allocate(GD2::Color[255, 255, 255])
124
125       image.draw do |pen|
126         pen.color = white
127         pen.rectangle(0, 0, width, height, true)
128       end
129
130       image.draw do |pen|
131         pen.color = black
132         pen.anti_aliasing = true
133         pen.dont_blend = false
134
135         oldpx = 0.0
136         oldpy = 0.0
137
138         first = true
139
140         points do |p|
141           px = proj.x(p.longitude)
142           py = proj.y(p.latitude)
143
144           pen.line(px, py, oldpx, oldpy) unless first
145
146           first = false
147           oldpy = py
148           oldpx = px
149         end
150       end
151
152       image.gif
153     end
154   end
155
156   TrkPt = Struct.new(:segment, :latitude, :longitude, :altitude, :timestamp) do
157     def valid?
158       latitude && longitude && timestamp &&
159         latitude >= -90 && latitude <= 90 &&
160         longitude >= -180 && longitude <= 180
161     end
162   end
163 end