Adjust to final animated GIF interface
[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       image = GD2::AnimatedGif.new
104       frames.each do |frame|
105         image.add(frame, :delay => delay)
106       end
107       image.end
108
109       output = StringIO.new
110       image.export(output)
111       output.read
112     end
113
114     def icon(min_lat, min_lon, max_lat, max_lon)
115       width = 50
116       height = 50
117       proj = OSM::Mercator.new(min_lat, min_lon, max_lat, max_lon, width, height)
118
119       image = GD2::Image::IndexedColor.new(width, height)
120
121       black = image.palette.allocate(GD2::Color[0, 0, 0])
122       white = image.palette.allocate(GD2::Color[255, 255, 255])
123
124       image.draw do |pen|
125         pen.color = white
126         pen.rectangle(0, 0, width, height, true)
127       end
128
129       image.draw do |pen|
130         pen.color = black
131         pen.anti_aliasing = true
132         pen.dont_blend = false
133
134         oldpx = 0.0
135         oldpy = 0.0
136
137         first = true
138
139         points do |p|
140           px = proj.x(p.longitude)
141           py = proj.y(p.latitude)
142
143           pen.line(px, py, oldpx, oldpy) unless first
144
145           first = false
146           oldpy = py
147           oldpx = px
148         end
149       end
150
151       image.gif
152     end
153   end
154
155   TrkPt = Struct.new(:segment, :latitude, :longitude, :altitude, :timestamp) do
156     def valid?
157       latitude && longitude && timestamp &&
158         latitude >= -90 && latitude <= 90 &&
159         longitude >= -180 && longitude <= 180
160     end
161   end
162 end