Class: Pippa::Map

Inherits:
Object
  • Object
show all
Includes:
Magick
Defined in:
lib/pippa.rb

Overview

An image-based map class that can be overlain with dots of given area and location given by pixel coordinates, lat/lon, or zipcode (courtesy of federalgovernmentzipcodes.us).

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = 'World') ⇒ Map

Make a new map with given name. See the file maps/_info or call Pippa#map_names for all possible.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/pippa.rb', line 88

def initialize(name = 'World')

  # Set up drawing standards.
  @point_size = 1
  @fill = 'DarkRed'
  @stroke = 'gray25'
  @fill_opacity = 0.85
  @stroke_width = 1
  @anti_alias = false
  @dots = []

  # Look up global info or return if none.
  return unless @map_info = Map.info[:map][name]
  @image = Image.read("#{File.dirname(__FILE__)}/pippa/maps/#{@map_info[0]}").first
  @width, @height = @image.columns, @image.rows

  # Look up projection info, if any.
  @projection_info = Map.info[:projection][name]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Handle special cases of missing converters, writers, and flushing attribute setters.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/pippa.rb', line 266

def method_missing(sym, *args, &block) # :nodoc:

  # Handle graphic attribute setters. flushing with render first.
  if GRAPHIC_ATTRIBUTE_SETTERS.include?(sym)
    iv_name = "@#{sym.to_s[0..-2]}"
    old_val = instance_variable_get(iv_name)
    return old_val if args[0] == old_val
    render
    return instance_variable_set(iv_name, args[0])
  end

  # Handle to_??? format converters, again flushing with render.
  fmt = conversion_to_format(sym)
  if fmt
    render
    @image.format = fmt
    return @image.to_blob
  end

  # Handle write_??? file writers, again flushing with render
  fmt = writer_to_format(sym)
  if fmt
    render
    @image.format = fmt
    return @image.write(args[0])
  end

  # Punt on everything else.
  super
end

Instance Attribute Details

#fillObject (readonly)

Dot fill color



39
40
41
# File 'lib/pippa.rb', line 39

def fill
  @fill
end

#fill_opacityObject (readonly)

Dot fill opacity



45
46
47
# File 'lib/pippa.rb', line 45

def fill_opacity
  @fill_opacity
end

#heightObject (readonly)

Height of the map image in pixels



29
30
31
# File 'lib/pippa.rb', line 29

def height
  @height
end

#imageObject (readonly)

RMagick image for direct manipulation, for example drawing lines and labels



63
64
65
# File 'lib/pippa.rb', line 63

def image
  @image
end

#point_sizeObject (readonly)

Base size of dot edges in pixels; defaults to 1. Therefore a unit area is one pixel.



33
34
35
# File 'lib/pippa.rb', line 33

def point_size
  @point_size
end

#strokeObject (readonly)

Dot border stroke color name



51
52
53
# File 'lib/pippa.rb', line 51

def stroke
  @stroke
end

#stroke_widthObject (readonly)

Dot border stroke width



57
58
59
# File 'lib/pippa.rb', line 57

def stroke_width
  @stroke_width
end

#widthObject (readonly)

Width of the map image in pixels



26
27
28
# File 'lib/pippa.rb', line 26

def width
  @width
end

Class Method Details

.infoObject

Return global map and projection information from config file. See maps/_info for format. This is not generally very useful.



82
83
84
# File 'lib/pippa.rb', line 82

def self.info # :nodoc:
  @@info ||= info_from_file
end

.profileObject

Run the profiler and record results.



320
321
322
323
324
325
326
327
328
# File 'lib/pippa.rb', line 320

def self.profile
  require 'ruby-prof'
  RubyProf.start
  write_zipcode_maps
  result = RubyProf.stop
  File.open('profile.htm', 'w') do |f|
    RubyProf::GraphHtmlPrinter.new(result).print(f)
  end
end

.write_zipcode_mapsObject

Write the test map produced by zipcode_map as png and jpg files.



313
314
315
316
317
# File 'lib/pippa.rb', line 313

def self.write_zipcode_maps
  m = zipcode_map
  File.open('spec/data/zipcodes.png', 'wb') { |f| f.write(m.to_png) }
  m.write_jpg('spec/data/zipcodes.jpg')
end

.zipcode_mapObject

Make a map showing all the zip codes in the USA with dots of random size. Also a couple of additional dots.



299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/pippa.rb', line 299

def self.zipcode_map
  generator = Random.new(42) # Force same on every run for testing.
  m = Map.new('USA')
  zips.each_key.each do |zip|
    m.add_at_zip(zip, generator.rand(4) ** 2)
  end
  m.fill = 'red'
  m.fill_opacity = 1
  m.add_at_lat_lon(41, -74, 300) # West Point, NY
  m.add_at_lat_lon(38, -122, 300) # Berkeley, CA
  m
end

.zipsObject

Return a hash mapping zip codes to CSV records of zip code data. NB: The file is big, so this takes a while to return the first time called.

CSV::Row struct format (see also ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Row.html):

#<CSV::Row
  zipcode:"97475"
  zip_code_type:"PO BOX"
  city:"SPRINGFIELD"
  state:"OR"
  location_type:"PRIMARY"
  lat:44.05
  long:-123.02
  location:"NA-US-OR-SPRINGFIELD"
  decommisioned:"false"
  tax_returns_filed:nil
  estimated_population:nil
  total_wages:nil>

See federalgovernmentzipcodes.us for more information on the zipcode data.



203
204
205
# File 'lib/pippa.rb', line 203

def self.zips
  @@zips ||= zips_from_file
end

Instance Method Details

#add_at_lat_lon(lat, lon, area = 0) ⇒ Object

Add a dot on the map at given latitude and longitude with given area.

Attributes

  • lat - Dot latitude

  • lon - Dot longitude

  • area - Optional area, defaults to single pixel

Examples

Make a map and put a dot at West Point, NY.

map = Map.new('USA')
map.add_at_lat_lon(41, -74, 100)
map.write_png('map.png')


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

def add_at_lat_lon(lat, lon, area = 0)
  add_dot(*lat_lon_to_xy(lat, lon), area)
end

#add_at_zip(zip, area = 0) ⇒ Object

Add a dot on the map at given 5-digit zip code.

Attributes

  • zip - Zipcode

  • area - Optional area, defaults to single pixel

Examples

Make a map and put a dot at West Point, NY.

map = Map.new('USA')
map.add_at_zip('10996', 100)
map.write_png('map.png')


178
179
180
181
# File 'lib/pippa.rb', line 178

def add_at_zip(zip, area = 0)
  data = Map.zips[zip]
  add_at_lat_lon(data[:lat], data[:long], area) if data
end

#add_dot(x, y, area = 0) ⇒ Object

Add a dot of given area at the given pixel coordinates.

Attributes

  • x - Dot x-pixel coordinate

  • y - Dot y-pixel coordinate

  • area - Optional area, defaults to single pixel

Examples

Make a map and put a dot in the middle.

map = Map.new('USA')
map.add_dot(map.width/2, map.height/2, 100)
map.write_png('map.png')


123
124
125
# File 'lib/pippa.rb', line 123

def add_dot(x, y, area = 0)
  @dots << [x, y, area]
end

#anti_alias=(val) ⇒ Object

Render if we’re making a change and then set a flag indicating whether anti-aliasing will be performed in next render. Default is false.



68
69
70
71
72
73
# File 'lib/pippa.rb', line 68

def anti_alias=(val) # :nodoc:
  val = !!val
  return val if val == @anti_alias
  render
  @anti_alias = val
end

#anti_alias?Boolean

Return flag indicating whether anti-aliasing will be performed in next render.

Returns:

  • (Boolean)


76
77
78
# File 'lib/pippa.rb', line 76

def anti_alias? # :nodoc:
  @anti_alias
end

#lat_lon_to_xy(lat, lon) ⇒ Object

Return the pixel-xy coordinate on this map of a given latitude and longitude.

Attributes

  • lat - Given latitude

  • lon - Given longitude

Examples

Get the pixel coordinate of West Point, NY.

map = Map.new('USA')
x, y = map.lat_lon_to_xy(41, -74)


140
141
142
143
# File 'lib/pippa.rb', line 140

def lat_lon_to_xy(lat, lon)
  set_projection unless @lat_lon_to_xy
  @lat_lon_to_xy.call(lat, lon)
end

#renderObject

Force rendering of all dots added so far onto the map. Then forget them so they’re never rendered again.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/pippa.rb', line 209

def render
  return if @image.nil? || @dots.empty?
  @dots.sort! {|a, b| b[2] <=> a[2] } # by area, smallest last
  gc = new_gc
  if @anti_alias
    @dots.each do |x, y, area|
      side = @point_size * Math.sqrt(area)
      if side <= 1
        gc.point(x, y)
      else
        h = 0.5 * side
        x1 = x - h
        y1 = y - h
        gc.rectangle(x1, y1, x1 + side, y1 + side)
      end
    end
  else
    @dots.each do |x, y, area|
      side = @point_size * Math.sqrt(area)
      x, y, side = x.round, y.round, side.round
      if side <= 1
        gc.point(x, y)
      else
        h = side / 2
        x1 = x - h
        y1 = y - h
        gc.rectangle(x1, y1, x1 + side, y1 + side)
      end
    end
  end
  gc.draw(@image)
  @dots = []
end

#respond_to?(sym, include_private = false) ⇒ Boolean

Return true iff we respond to given method. Takes care of to_??? and write_???? converters and writers of graphic formats.

Returns:

  • (Boolean)


246
247
248
# File 'lib/pippa.rb', line 246

def respond_to? (sym, include_private = false)
  conversion_to_format(sym) || writer_to_format(sym) ? true : super
end