Class: Geoptima::Trace

Inherits:
Object
  • Object
show all
Defined in:
lib/geoptima/data.rb

Direct Known Subclasses

MergedTrace

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dataset, options = {}) ⇒ Trace

Returns a new instance of Trace.



45
46
47
48
49
50
51
52
53
# File 'lib/geoptima/data.rb', line 45

def initialize(dataset, options={})
  @dataset = dataset
  @trace_type = options[:type]
  @data_id = nil
  @data_id_hashset = {}
  @name = options[:name] || dataset.name
  @events = []
  initialize_tags
end

Instance Attribute Details

#boundsObject (readonly)

Returns the value of attribute bounds.



44
45
46
# File 'lib/geoptima/data.rb', line 44

def bounds
  @bounds
end

#data_idObject (readonly)

Returns the value of attribute data_id.



43
44
45
# File 'lib/geoptima/data.rb', line 43

def data_id
  @data_id
end

#data_id_hashsetObject (readonly)

Returns the value of attribute data_id_hashset.



43
44
45
# File 'lib/geoptima/data.rb', line 43

def data_id_hashset
  @data_id_hashset
end

#datasetObject (readonly)

Returns the value of attribute dataset.



43
44
45
# File 'lib/geoptima/data.rb', line 43

def dataset
  @dataset
end

#eventsObject (readonly)

Returns the value of attribute events.



44
45
46
# File 'lib/geoptima/data.rb', line 44

def events
  @events
end

#nameObject (readonly)

Returns the value of attribute name.



43
44
45
# File 'lib/geoptima/data.rb', line 43

def name
  @name
end

#paddingObject

Returns the value of attribute padding.



44
45
46
# File 'lib/geoptima/data.rb', line 44

def padding
  @padding
end

#scaleObject

Returns the value of attribute scale.



44
45
46
# File 'lib/geoptima/data.rb', line 44

def scale
  @scale
end

#totalsObject (readonly)

Returns the value of attribute totals.



44
45
46
# File 'lib/geoptima/data.rb', line 44

def totals
  @totals
end

#tracenameObject (readonly)

Returns the value of attribute tracename.



43
44
45
# File 'lib/geoptima/data.rb', line 43

def tracename
  @tracename
end

Instance Method Details

#<<(e) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/geoptima/data.rb', line 71

def <<(e)
  if gpx_id = e.gpx_id
    @tracename ||= "#{name}-#{e.time}"
    @data_id ||= e.gpx_id
    @data_id_hashset[e.gpx_id] ||= 0
    @data_id_hashset[e.gpx_id] += 1
    check_bounds(e)
    check_totals(e)
    @events << e unless(co_located(e,@events[-1]))
  elsif $debug
    puts "Ignoring event with nil GPX id: #{e.inspect}"
  end
end

#as_csvObject



281
282
283
284
285
286
287
# File 'lib/geoptima/data.rb', line 281

def as_csv
  traces.map do |trace|
    trace.events.map do |e|
      [e.time_key,e.latitude,e.longitude].join("\t")
    end.join("\n")
  end.join("\n")
end

#as_gpxObject



211
212
213
214
215
216
217
218
219
220
221
# File 'lib/geoptima/data.rb', line 211

def as_gpx
  return unless(length>0)
  case @trace_type
  when /way/
    as_gpx_ways
  when /route/
    as_gpx_route
  else
    as_gpx_track
  end
end

#as_gpx_routeObject



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/geoptima/data.rb', line 239

def as_gpx_route
  gpx = %{<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
  <metadata>
#{bounds_as_gpx}
  </metadata>
  <rte>
<name>#{tracename}</name>
} +
  traces.map do |trace|
    trace.events.map do |e|
      event_as_gpx(e,ei)
    end.join("\n    ")
  end.join("\n    ") +
"""
  </rte>
</gpx>
"""
end

#as_gpx_trackObject



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/geoptima/data.rb', line 258

def as_gpx_track
  ei = 0
  gpx = %{<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
  <metadata>
#{bounds_as_gpx}
  </metadata>
  <trk>
<name>#{tracename}</name>
<trkseg>
  } +
  traces.map do |trace|
    trace.events.map do |e|
      ei += 1
      event_as_gpx(e,ei)
    end.join("\n      ")
  end.join("\n    </trkseg>\n    <trkseg>\n      ") +
  """
</trkseg>
  </trk>
</gpx>
"""
end

#as_gpx_waysObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/geoptima/data.rb', line 222

def as_gpx_ways
  gpx = %{<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="geoptima.rb - Craig Taverner">
  <metadata>
#{bounds_as_gpx}
  </metadata>
  <name>#{tracename}</name>
  } +
  traces.map do |trace|
    trace.events.map do |e|
      event_as_gpx(e)
    end.join("\n  ")
  end.join("\n  ") +
  """
</gpx>
"""
end

#averageObject



166
167
168
# File 'lib/geoptima/data.rb', line 166

def average
  [totals[0] / totals[2], totals[1] / totals[2]]
end

#bottomObject



163
164
165
# File 'lib/geoptima/data.rb', line 163

def bottom
  @bottom ||= @bounds[:minlat].to_f
end

#bounds_as_gpxObject



205
206
207
# File 'lib/geoptima/data.rb', line 205

def bounds_as_gpx
  "<bounds " + bounds.keys.map{|k| "#{k}=\"#{bounds[k]}\""}.join(' ') + "/>"
end

#check_bounds(e) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/geoptima/data.rb', line 99

def check_bounds(e)
  @bounds ||= {}
  check_bounds_min :minlat, e.latitude
  check_bounds_min :minlon, e.longitude
  check_bounds_max :maxlat, e.latitude
  check_bounds_max :maxlon, e.longitude
  @width = nil
  @height = nil
end

#check_bounds_max(key, value) ⇒ Object



118
119
120
# File 'lib/geoptima/data.rb', line 118

def check_bounds_max(key,value)
  @bounds[key] = value if(!value.nil? &&(@bounds[key].nil? || @bounds[key] < value))
end

#check_bounds_min(key, value) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'lib/geoptima/data.rb', line 108

def check_bounds_min(key,value)
  if value.is_a? String
    raise "Passed a string value: #{value.inspect}"
  end
  begin
    @bounds[key] = value if(!value.nil? &&(@bounds[key].nil? || @bounds[key] > value))
  rescue
     raise "Failed to set bounds using current:#{@bounds.inspect}, value=#{value.inspect}"
  end
end

#check_totals(e) ⇒ Object



93
94
95
96
97
98
# File 'lib/geoptima/data.rb', line 93

def check_totals(e)
  @totals ||= [0.0,0.0,0]
  @totals[0] += e.latitude
  @totals[1] += e.longitude
  @totals[2] += 1
end

#check_trace_bounds(t) ⇒ Object



121
122
123
124
125
126
127
128
129
# File 'lib/geoptima/data.rb', line 121

def check_trace_bounds(t)
  @bounds ||= {}
  check_bounds_min :minlat, t.bounds[:minlat]
  check_bounds_min :minlon, t.bounds[:minlon]
  check_bounds_max :maxlat, t.bounds[:maxlat]
  check_bounds_max :maxlon, t.bounds[:maxlon]
  @width = nil
  @height = nil
end

#co_located(event, other) ⇒ Object



84
85
86
# File 'lib/geoptima/data.rb', line 84

def co_located(event,other)
  event && other && event.latitude == other.latitude && event.longitude == other.longitude
end

#color(index = 1) ⇒ Object



312
313
314
# File 'lib/geoptima/data.rb', line 312

def color(index=1)
  self.colors[index%(self.colors.length)]
end

#colorsObject



309
310
311
# File 'lib/geoptima/data.rb', line 309

def colors
  @colors ||= PNG::Color.constants.reject{|c| (c.is_a?(PNG::Color)) || c.to_s =~ /max/i || c.to_s =~ /background/i}.map{|c| PNG::Color.const_get c}.reject{|c| c == PNG::Color::Background || c == PNG::Color::Black || c == PNG::Color::White}
end

#data_idsObject



67
68
69
70
# File 'lib/geoptima/data.rb', line 67

def data_ids
  puts "About to sort data ids: #{@data_id_hashset.keys.join(',')}" if($debug)
  @data_id_hashset.keys.compact.sort
end

#eachObject



130
131
132
# File 'lib/geoptima/data.rb', line 130

def each
  events.each{|e| yield e}
end

#event_as_gpx(e, elevation = 0.0) ⇒ Object



208
209
210
# File 'lib/geoptima/data.rb', line 208

def event_as_gpx(e,elevation=0.0)
  "<#{@ptag} lat=\"#{e.latitude}\" lon=\"#{e.longitude}\"><ele>#{elevation}</ele><time>#{e.time}</time><name>#{e.name}</name><desc>#{e.description}</desc></#{@ptag}>"
end

#fix_options(options = {}) ⇒ Object



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/geoptima/data.rb', line 288

def fix_options(options={})
  options.keys.each do |key|
    val = options[key]
    if val.to_s =~ /false/i
      val = false
    elsif val != true && val.to_i.to_s == val
      val = val.to_i
    end
    options[key] = val
  end
  puts "Fixed options: #{options.inspect}"
  options['show_line'].nil? && (options['show_line']=true)
  if @trace_type =~ /ways/
    options['show_line'] = false
    options['point_size'] = (options['point_size'].to_i + 1) * 2
    puts "Adjusted line/point settings for ways: show_line:#{options['show_line']}, point_size:#{options['point_size']}"
  end
end

#heightObject



157
158
159
# File 'lib/geoptima/data.rb', line 157

def height
  @height ||= @bounds[:maxlat].to_f - @bounds[:minlat].to_f
end

#initialize_tagsObject



54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/geoptima/data.rb', line 54

def initialize_tags
  case @trace_type
  when /ways/
    @ptag = 'wpt'
  when /route/
    @ttag = 'rte'
    @ptag = 'rtept'
  else
    @ttag = 'trk'
    @stag = 'trkseg'
    @ptag = 'trkpt'
  end
end

#leftObject



160
161
162
# File 'lib/geoptima/data.rb', line 160

def left
  @left ||= @bounds[:minlon].to_f
end

#lengthObject



87
88
89
# File 'lib/geoptima/data.rb', line 87

def length
  events.length
end

#remove_outliersObject



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/geoptima/data.rb', line 169

def remove_outliers
  if @bounds && (width > 0.1 || height > 0.1)
    distances = []
    total_distance = 0.0
    max_distance = 0.0
    center = Point.new(*average)
    self.each do |e|
      distance = e.distance_from(center)
      distances << [e,distance]
      total_distance += distance
      max_distance = distance if(max_distance < distance)
    end
    average_distance = total_distance / distances.length
    threshold = max_distance / 3
    if threshold > average_distance * 3
      puts "Average distance #{average_distance} much less than max distance #{max_distance}, trimming all beyond #{threshold}"
      @bounds = {}
      distances.each do |d|
        check_bounds(d[0]) if(d[1] < threshold)
      end
    else
      puts "Average distance #{average_distance} not much less than max distance #{max_distance}, not trimming outliers"
    end
  end
end

#scale_event(e) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/geoptima/data.rb', line 194

def scale_event(e)
  p = [e.longitude.to_f, e.latitude.to_f]
  if scale
    p[0] = padding[0] + ((p[0] - left) * (scale[0].to_f - 1) / (width)).to_i
    p[1] = padding[1] + ((p[1] - bottom) * (scale[1].to_f - 1) / (height)).to_i
  end
  p
end

#sizeObject



140
141
142
# File 'lib/geoptima/data.rb', line 140

def size
  @size = [scale[0]+2*padding[0],scale[1]+2*padding[1]]
end

#to_png(filename, options = {}) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/geoptima/data.rb', line 315

def to_png(filename, options={})
  return unless(length>0)
  fix_options options
  puts "Exporting with options: #{options.inspect}"
  self.scale = options['scale']
  self.padding = options['padding']
  remove_outliers
  ['scale','padding','size','bounds','width','height'].each do |a|
    puts "\t#{a}: #{self.send(a).inspect}"
  end
  begin
    canvas = PNG::Canvas.new size[0],size[1]
  rescue
    puts "Unable to initialize PNG:Canvas - is 'png' gem installed? #{$!}"
    return
  end

  data_idm = data_ids.inject({}){|a,v| a[v]=a.length;a}
  puts "Created map of data ids from available set: #{data_ids.inspect}"
  if data_ids.length > 1
    data_ids.each do |did|
      puts "\t#{color(data_idm[did])}\t#{data_id_hashset[did]}\t#{did}"
    end
  end
  traces.each_with_index do |trace,index|
    line_color = PNG::Color.from "0x00006688"
    point_color = color(index)
    if options['point_color'] && !(options['point_color'] =~ /auto/i)
      pc = options['point_color'].gsub(/^\#/,'').gsub(/^0x/,'').upcase
      pc = "0x#{pc}FFFFFFFF"[0...10]
      begin
        point_color = PNG::Color.from pc
      rescue
        puts "Failed to interpret color #{pc}, use format 0x00000000: #{$!}"
      end
    else
      point_color = color(index)
      puts "Got point color #{point_color} from trace index #{index}" if($debug)
    end

    prev = nil
    trace.events.each do |e|
      p = scale_event(e)
      if data_idm.length > 1
        point_color = color(data_idm[e.gpx_id])
        puts "Got point color #{point_color} from data ID '#{e.gpx_id}' index #{data_idm[e.gpx_id]}" if($debug)
      end
      begin
        # draw an anti-aliased line
        if options['show_line'] && prev && prev != p
          canvas.line prev[0], prev[1], p[0], p[1], line_color
        end

        if options['points']
          # Set a point to a color
          n = options['point_size'].to_i / 2
          r = (-n..n)
          r.each do |x|
            r.each do |y|
              canvas[p[0]+x, p[1]-y] = point_color
            end
          end
        end
      rescue
        puts "Error in writing PNG: #{$!}"
      end

      prev = p
    end
  end

  png = PNG.new canvas
  png.save filename
end

#to_sObject



90
91
92
# File 'lib/geoptima/data.rb', line 90

def to_s
  tracename || name
end

#too_far(other) ⇒ Object



202
203
204
# File 'lib/geoptima/data.rb', line 202

def too_far(other)
  events && events[-1] && (events[-1].days_from(other) > 0.5 || events[-1].distance_from(other) > 0.002)
end

#tracesObject



306
307
308
# File 'lib/geoptima/data.rb', line 306

def traces
  [self]
end

#widthObject



154
155
156
# File 'lib/geoptima/data.rb', line 154

def width
  @width ||= @bounds[:maxlon].to_f - @bounds[:minlon].to_f
end