pippa 0.1.0
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 +15 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +706 -0
- data/README.md +70 -0
- data/Rakefile +9 -0
- data/lib/pippa.rb +446 -0
- data/lib/pippa/maps/Africa.png +0 -0
- data/lib/pippa/maps/Australia.png +0 -0
- data/lib/pippa/maps/Belgium.png +0 -0
- data/lib/pippa/maps/Canada.png +0 -0
- data/lib/pippa/maps/Caribbean.png +0 -0
- data/lib/pippa/maps/CentralAmerica.png +0 -0
- data/lib/pippa/maps/China.png +0 -0
- data/lib/pippa/maps/Europe.png +0 -0
- data/lib/pippa/maps/France.png +0 -0
- data/lib/pippa/maps/Germany.png +0 -0
- data/lib/pippa/maps/Hawaii.png +0 -0
- data/lib/pippa/maps/India.png +0 -0
- data/lib/pippa/maps/Italy.png +0 -0
- data/lib/pippa/maps/Japan.png +0 -0
- data/lib/pippa/maps/Korea.png +0 -0
- data/lib/pippa/maps/MalaysiaIndonesia.png +0 -0
- data/lib/pippa/maps/MiddleEast.png +0 -0
- data/lib/pippa/maps/NOSEFI.png +0 -0
- data/lib/pippa/maps/Netherlands.png +0 -0
- data/lib/pippa/maps/NewZealand.png +0 -0
- data/lib/pippa/maps/Philippines.png +0 -0
- data/lib/pippa/maps/SouthAmerica.png +0 -0
- data/lib/pippa/maps/UK.png +0 -0
- data/lib/pippa/maps/USA100.png +0 -0
- data/lib/pippa/maps/USA200.png +0 -0
- data/lib/pippa/maps/USA50-new.png +0 -0
- data/lib/pippa/maps/World100.png +0 -0
- data/lib/pippa/maps/World50-new.png +0 -0
- data/lib/pippa/maps/_info +34 -0
- data/lib/pippa/maps/_zipcodes.csv +42523 -0
- data/lib/pippa/version.rb +4 -0
- data/pippa.gemspec +27 -0
- data/spec/data/zipcodes.jpg +0 -0
- data/spec/data/zipcodes.png +0 -0
- data/spec/lib/map_spec.rb +43 -0
- data/spec/lib/pippa_spec.rb +13 -0
- data/spec/spec_helper.rb +31 -0
- metadata +162 -0
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Pippa
|
2
|
+
|
3
|
+
Pippa - a Ruby gem for producing simple map graphics overlain with
|
4
|
+
geocoded dots of given area. Dot coordinates are in screen pixels,
|
5
|
+
latitude/longitude, or US zipcode.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'pippa'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle install
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install pippa
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
require 'pippa'
|
24
|
+
|
25
|
+
# Get available map names.
|
26
|
+
puts Pippa.map_names
|
27
|
+
|
28
|
+
# Make a new, clean map.
|
29
|
+
map = Pippa::Map.new('USA') # or 29 other maps (default == 'World')
|
30
|
+
|
31
|
+
# Change default dark red fill to dark green.
|
32
|
+
# Changes cause dots entered so far to be rendered to graphic.
|
33
|
+
# Several other parameters also control dot appearance.
|
34
|
+
map.fill = 'DarkGreen'
|
35
|
+
|
36
|
+
# Change default to enable ImageMagick anti-aliasing by refraining
|
37
|
+
# from snapping dot coordinates to nearest pixel.
|
38
|
+
map.anti_alias = true
|
39
|
+
|
40
|
+
# Add a dot in the middle of the map using pixel coordinates.
|
41
|
+
map.add_dot(map.width/2, map.height/2, 100)
|
42
|
+
|
43
|
+
# Add a single green pixel dot at West Point, NY.
|
44
|
+
# Between calls to render, dots are drawn biggest first, so
|
45
|
+
# overlaps are generally okay.
|
46
|
+
map.add_at_lat_lon(41.5, -74.1)
|
47
|
+
|
48
|
+
# Flush buffered dots to the map.
|
49
|
+
map.render
|
50
|
+
|
51
|
+
# Add a dot with an area of 86 at a given zip code in Pennsylvania.
|
52
|
+
# This will be drawn on top of all previous dots regardless of
|
53
|
+
# size due to render above.
|
54
|
+
map.add_at_zip('18088', 86)
|
55
|
+
|
56
|
+
# Make a blob of the map e.g. suitable for Rails send_data.
|
57
|
+
# Any RMagick blob format will work in lieu of 'png'
|
58
|
+
blob = map.to_png
|
59
|
+
|
60
|
+
# Write the map directly to a file using RMagick write.
|
61
|
+
# Any RMagick writable format will work.
|
62
|
+
map.write_jpg('mymap.jpg')
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
1. Fork it
|
67
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
68
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
69
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
70
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/pippa.rb
ADDED
@@ -0,0 +1,446 @@
|
|
1
|
+
# Pippa - a Ruby gem for producing simple map graphics overlain with
|
2
|
+
# geocoded dots of given area. The geocoding is by lat/lon or US zipcode.
|
3
|
+
#
|
4
|
+
# Author:: Gene Ressler (mailto:gene.ressler@gmail.com)
|
5
|
+
# Copyright:: Copyright (c) 2013 Gene Ressler
|
6
|
+
# License:: See LICENSE.TXT
|
7
|
+
#
|
8
|
+
require 'pippa/version'
|
9
|
+
require 'RMagick'
|
10
|
+
require 'csv'
|
11
|
+
|
12
|
+
module Pippa
|
13
|
+
|
14
|
+
# Return a list of the valid map names.
|
15
|
+
def self.map_names
|
16
|
+
Map.info[:map].keys
|
17
|
+
end
|
18
|
+
|
19
|
+
# An image-based map class that can be overlain with dots
|
20
|
+
# of given area and location given by pixel coordinates, lat/lon,
|
21
|
+
# or zipcode (courtesy of http://federalgovernmentzipcodes.us).
|
22
|
+
class Map
|
23
|
+
include Magick
|
24
|
+
|
25
|
+
# Width of the map image in pixels
|
26
|
+
attr_reader :width
|
27
|
+
|
28
|
+
# Height of the map image in pixels
|
29
|
+
attr_reader :height
|
30
|
+
|
31
|
+
# Base size of dot edges in pixels; defaults to 1.
|
32
|
+
# Therefore a unit area is one pixel.
|
33
|
+
attr_reader :point_size
|
34
|
+
|
35
|
+
##
|
36
|
+
# :attr_writer: point_size
|
37
|
+
|
38
|
+
# Dot fill color
|
39
|
+
attr_reader :fill
|
40
|
+
|
41
|
+
##
|
42
|
+
# :attr_writer: fill
|
43
|
+
|
44
|
+
# Dot fill opacity
|
45
|
+
attr_reader :fill_opacity
|
46
|
+
|
47
|
+
##
|
48
|
+
# :attr_writer: fill_opacity
|
49
|
+
|
50
|
+
# Dot border stroke color name
|
51
|
+
attr_reader :stroke
|
52
|
+
|
53
|
+
##
|
54
|
+
# :attr_writer: stroke
|
55
|
+
|
56
|
+
# Dot border stroke width
|
57
|
+
attr_reader :stroke_width
|
58
|
+
|
59
|
+
##
|
60
|
+
# :attr_writer: stroke_width
|
61
|
+
|
62
|
+
# RMagick image for direct manipulation, for example drawing lines and labels
|
63
|
+
attr_reader :image
|
64
|
+
|
65
|
+
# Render if we're making a change and then set a flag indicating
|
66
|
+
# whether anti-aliasing will be performed in next render.
|
67
|
+
# Default is false.
|
68
|
+
def anti_alias=(val) # :nodoc:
|
69
|
+
val = !!val
|
70
|
+
return val if val == @anti_alias
|
71
|
+
render
|
72
|
+
@anti_alias = val
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return flag indicating whether anti-aliasing will be performed in next render.
|
76
|
+
def anti_alias? # :nodoc:
|
77
|
+
@anti_alias
|
78
|
+
end
|
79
|
+
|
80
|
+
# Return global map and projection information from config file.
|
81
|
+
# See +maps/_info+ for format. This is not generally very useful.
|
82
|
+
def self.info # :nodoc:
|
83
|
+
@@info ||= info_from_file
|
84
|
+
end
|
85
|
+
|
86
|
+
# Make a new map with given name.
|
87
|
+
# See the file +maps/_info+ or call Pippa#map_names for all possible.
|
88
|
+
def initialize(name = 'World')
|
89
|
+
|
90
|
+
# Set up drawing standards.
|
91
|
+
@point_size = 1
|
92
|
+
@fill = 'DarkRed'
|
93
|
+
@stroke = 'gray25'
|
94
|
+
@fill_opacity = 0.85
|
95
|
+
@stroke_width = 1
|
96
|
+
@anti_alias = false
|
97
|
+
@dots = []
|
98
|
+
|
99
|
+
# Look up global info or return if none.
|
100
|
+
return unless @map_info = Map.info[:map][name]
|
101
|
+
@image = Image.read("#{File.dirname(__FILE__)}/pippa/maps/#{@map_info[0]}").first
|
102
|
+
@width, @height = @image.columns, @image.rows
|
103
|
+
|
104
|
+
# Look up projection info, if any.
|
105
|
+
@projection_info = Map.info[:projection][name]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a dot of given area at the given pixel coordinates.
|
109
|
+
#
|
110
|
+
# ==== Attributes
|
111
|
+
#
|
112
|
+
# * +x+ - Dot x-pixel coordinate
|
113
|
+
# * +y+ - Dot y-pixel coordinate
|
114
|
+
# * +area+ - Optional area, defaults to single pixel
|
115
|
+
#
|
116
|
+
# ==== Examples
|
117
|
+
#
|
118
|
+
# Make a map and put a dot in the middle.
|
119
|
+
#
|
120
|
+
# map = Map.new('USA')
|
121
|
+
# map.add_dot(map.width/2, map.height/2, 100)
|
122
|
+
# map.write_png('map.png')
|
123
|
+
def add_dot(x, y, area = 0)
|
124
|
+
@dots << [x, y, area]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Return the pixel-xy coordinate on this map of a given latitude and longitude.
|
128
|
+
#
|
129
|
+
# ==== Attributes
|
130
|
+
#
|
131
|
+
# * +lat+ - Given latitude
|
132
|
+
# * +lon+ - Given longitude
|
133
|
+
#
|
134
|
+
# ==== Examples
|
135
|
+
#
|
136
|
+
# Get the pixel coordinate of West Point, NY.
|
137
|
+
#
|
138
|
+
# map = Map.new('USA')
|
139
|
+
# x, y = map.lat_lon_to_xy(41, -74)
|
140
|
+
def lat_lon_to_xy(lat, lon)
|
141
|
+
set_projection unless @lat_lon_to_xy
|
142
|
+
@lat_lon_to_xy.call(lat, lon)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Add a dot on the map at given latitude and longitude with given area.
|
146
|
+
#
|
147
|
+
# ==== Attributes
|
148
|
+
#
|
149
|
+
# * +lat+ - Dot latitude
|
150
|
+
# * +lon+ - Dot longitude
|
151
|
+
# * +area+ - Optional area, defaults to single pixel
|
152
|
+
#
|
153
|
+
# ==== Examples
|
154
|
+
#
|
155
|
+
# Make a map and put a dot at West Point, NY.
|
156
|
+
#
|
157
|
+
# map = Map.new('USA')
|
158
|
+
# map.add_at_lat_lon(41, -74, 100)
|
159
|
+
# map.write_png('map.png')
|
160
|
+
def add_at_lat_lon(lat, lon, area = 0)
|
161
|
+
add_dot(*lat_lon_to_xy(lat, lon), area)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Add a dot on the map at given 5-digit zip code.
|
165
|
+
#
|
166
|
+
# ==== Attributes
|
167
|
+
#
|
168
|
+
# * +zip+ - Zipcode
|
169
|
+
# * +area+ - Optional area, defaults to single pixel
|
170
|
+
#
|
171
|
+
# ==== Examples
|
172
|
+
#
|
173
|
+
# Make a map and put a dot at West Point, NY.
|
174
|
+
#
|
175
|
+
# map = Map.new('USA')
|
176
|
+
# map.add_at_zip('10996', 100)
|
177
|
+
# map.write_png('map.png')
|
178
|
+
def add_at_zip(zip, area = 0)
|
179
|
+
data = Map.zips[zip]
|
180
|
+
add_at_lat_lon(data[:lat], data[:long], area) if data
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return a hash mapping zip codes to CSV records of zip code data.
|
184
|
+
# NB: The file is big, so this takes a while to return the first time called.
|
185
|
+
#
|
186
|
+
# +CSV::Row+ struct format (see also http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Row.html):
|
187
|
+
#
|
188
|
+
# #<CSV::Row
|
189
|
+
# zipcode:"97475"
|
190
|
+
# zip_code_type:"PO BOX"
|
191
|
+
# city:"SPRINGFIELD"
|
192
|
+
# state:"OR"
|
193
|
+
# location_type:"PRIMARY"
|
194
|
+
# lat:44.05
|
195
|
+
# long:-123.02
|
196
|
+
# location:"NA-US-OR-SPRINGFIELD"
|
197
|
+
# decommisioned:"false"
|
198
|
+
# tax_returns_filed:nil
|
199
|
+
# estimated_population:nil
|
200
|
+
# total_wages:nil>
|
201
|
+
#
|
202
|
+
# See http://federalgovernmentzipcodes.us for more information on the zipcode data.
|
203
|
+
def self.zips
|
204
|
+
@@zips ||= zips_from_file
|
205
|
+
end
|
206
|
+
|
207
|
+
# Force rendering of all dots added so far onto the map.
|
208
|
+
# Then forget them so they're never rendered again.
|
209
|
+
def render
|
210
|
+
return if @image.nil? || @dots.empty?
|
211
|
+
@dots.sort! {|a, b| b[2] <=> a[2] } # by area, smallest last
|
212
|
+
gc = new_gc
|
213
|
+
if @anti_alias
|
214
|
+
@dots.each do |x, y, area|
|
215
|
+
side = @point_size * Math.sqrt(area)
|
216
|
+
if side <= 1
|
217
|
+
gc.point(x, y)
|
218
|
+
else
|
219
|
+
h = 0.5 * side
|
220
|
+
x1 = x - h
|
221
|
+
y1 = y - h
|
222
|
+
gc.rectangle(x1, y1, x1 + side, y1 + side)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
else
|
226
|
+
@dots.each do |x, y, area|
|
227
|
+
side = @point_size * Math.sqrt(area)
|
228
|
+
x, y, side = x.round, y.round, side.round
|
229
|
+
if side <= 1
|
230
|
+
gc.point(x, y)
|
231
|
+
else
|
232
|
+
h = side / 2
|
233
|
+
x1 = x - h
|
234
|
+
y1 = y - h
|
235
|
+
gc.rectangle(x1, y1, x1 + side, y1 + side)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
gc.draw(@image)
|
240
|
+
@dots = []
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
# Return true iff we respond to given method. Takes care of to_???
|
245
|
+
# and write_???? converters and writers of graphic formats.
|
246
|
+
def respond_to? (sym, include_private = false)
|
247
|
+
conversion_to_format(sym) || writer_to_format(sym) ? true : super
|
248
|
+
end
|
249
|
+
|
250
|
+
##
|
251
|
+
# :method: write_xxx
|
252
|
+
# Write map as graphic file in Magick format xxx.
|
253
|
+
# File suffix is *not* added automatically.
|
254
|
+
# Get a full list of formats with this:
|
255
|
+
# Magick.formats.each {|k,v| puts k if v.include?('w') }
|
256
|
+
# :call-seq:
|
257
|
+
# write_xxx(filename)
|
258
|
+
|
259
|
+
##
|
260
|
+
# :method: to_xxx
|
261
|
+
# Return map as a blob with Magick format +xxx+.
|
262
|
+
# Get a full list of formats with this:
|
263
|
+
# Magick.formats.each {|k,v| puts k if v.include?('*') }
|
264
|
+
|
265
|
+
# Handle special cases of missing converters, writers, and flushing attribute setters.
|
266
|
+
def method_missing(sym, *args, &block) # :nodoc:
|
267
|
+
|
268
|
+
# Handle graphic attribute setters. flushing with render first.
|
269
|
+
if GRAPHIC_ATTRIBUTE_SETTERS.include?(sym)
|
270
|
+
iv_name = "@#{sym.to_s[0..-2]}"
|
271
|
+
old_val = instance_variable_get(iv_name)
|
272
|
+
return old_val if args[0] == old_val
|
273
|
+
render
|
274
|
+
return instance_variable_set(iv_name, args[0])
|
275
|
+
end
|
276
|
+
|
277
|
+
# Handle to_??? format converters, again flushing with render.
|
278
|
+
fmt = conversion_to_format(sym)
|
279
|
+
if fmt
|
280
|
+
render
|
281
|
+
@image.format = fmt
|
282
|
+
return @image.to_blob
|
283
|
+
end
|
284
|
+
|
285
|
+
# Handle write_??? file writers, again flushing with render
|
286
|
+
fmt = writer_to_format(sym)
|
287
|
+
if fmt
|
288
|
+
render
|
289
|
+
@image.format = fmt
|
290
|
+
return @image.write(args[0])
|
291
|
+
end
|
292
|
+
|
293
|
+
# Punt on everything else.
|
294
|
+
super
|
295
|
+
end
|
296
|
+
|
297
|
+
# Make a map showing all the zip codes in the USA with
|
298
|
+
# dots of random size. Also a couple of additional dots.
|
299
|
+
def self.zipcode_map
|
300
|
+
generator = Random.new(42) # Force same on every run for testing.
|
301
|
+
m = Map.new('USA')
|
302
|
+
zips.each_key.each do |zip|
|
303
|
+
m.add_at_zip(zip, generator.rand(4) ** 2)
|
304
|
+
end
|
305
|
+
m.fill = 'red'
|
306
|
+
m.fill_opacity = 1
|
307
|
+
m.add_at_lat_lon(41, -74, 300) # West Point, NY
|
308
|
+
m.add_at_lat_lon(38, -122, 300) # Berkeley, CA
|
309
|
+
m
|
310
|
+
end
|
311
|
+
|
312
|
+
# Write the test map produced by +zipcode_map+ as png and jpg files.
|
313
|
+
def self.write_zipcode_maps
|
314
|
+
m = zipcode_map
|
315
|
+
File.open('spec/data/zipcodes.png', 'wb') { |f| f.write(m.to_png) }
|
316
|
+
m.write_jpg('spec/data/zipcodes.jpg')
|
317
|
+
end
|
318
|
+
|
319
|
+
# Run the profiler and record results.
|
320
|
+
def self.profile
|
321
|
+
require 'ruby-prof'
|
322
|
+
RubyProf.start
|
323
|
+
write_zipcode_maps
|
324
|
+
result = RubyProf.stop
|
325
|
+
File.open('profile.htm', 'w') do |f|
|
326
|
+
RubyProf::GraphHtmlPrinter.new(result).print(f)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
private
|
331
|
+
|
332
|
+
#:nodoc:
|
333
|
+
GRAPHIC_ATTRIBUTE_SETTERS = [:point_size=, :fill=, :stroke=, :fill_opacity=, :stroke_width=]
|
334
|
+
|
335
|
+
# Build a new graphics context for rendering.
|
336
|
+
def new_gc
|
337
|
+
gc = Magick::Draw.new
|
338
|
+
gc.fill(@fill)
|
339
|
+
gc.stroke(@stroke)
|
340
|
+
gc.fill_opacity(@fill_opacity)
|
341
|
+
gc.stroke_width(@stroke_width)
|
342
|
+
gc
|
343
|
+
end
|
344
|
+
|
345
|
+
# Set the projection from the configuration projection information.
|
346
|
+
def set_projection
|
347
|
+
if @projection_info
|
348
|
+
case @projection_info[0]
|
349
|
+
when 'ALBER'
|
350
|
+
r = Float(@projection_info[1])
|
351
|
+
false_easting = Float(@projection_info[6])
|
352
|
+
false_northing = Float(@projection_info[7])
|
353
|
+
phi_1, phi_2, phi_0, lmd_0 = @projection_info[2..5].map {|s| Float(s) * Math::PI / 180.0 };
|
354
|
+
n = 0.5 * (Math.sin(phi_1) + Math.sin(phi_2))
|
355
|
+
c = Math.cos(phi_1) ** 2 + 2.0 * n * Math.sin(phi_1)
|
356
|
+
@lat_lon_to_xy = lambda do |lat, lon|
|
357
|
+
phi = lat * Math::PI / 180.0
|
358
|
+
lmd = lon * Math::PI / 180.0
|
359
|
+
p = r * Math.sqrt(c - 2.0 * n * Math.sin(phi)) / n
|
360
|
+
p_0 = r * Math.sqrt(c - 2.0 * n * Math.sin(phi_0)) / n
|
361
|
+
theta = n * (lmd - lmd_0)
|
362
|
+
x = false_easting + p * Math.sin(theta)
|
363
|
+
y = false_northing - (p_0 - p * Math.cos(theta))
|
364
|
+
[x, y]
|
365
|
+
end
|
366
|
+
else
|
367
|
+
fail "Unknown projection #{@projection_info[0]}"
|
368
|
+
end
|
369
|
+
else
|
370
|
+
top_lat, top_lon, bot_lat, bot_lon = @map_info[1..4].map {|s| Float(s) }
|
371
|
+
lat_scale = @height / (top_lat - bot_lat)
|
372
|
+
lon_scale = @width / (bot_lon - top_lon)
|
373
|
+
@lat_lon_to_xy = lambda do |lat, lon|
|
374
|
+
[(lon - top_lon) * lon_scale, (top_lat - lat) * lat_scale]
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# For given string +prefix+ and a symbol like +:<prefix>_png+ or +:<prefix>_jpg+,
|
380
|
+
# return 'PNG' or 'JPG' so long as the part of the symbol after the underscore
|
381
|
+
# is a valid Magick image format with required function. Otherwise return +nil+.
|
382
|
+
def method_to_format(prefix, sym, function)
|
383
|
+
return nil unless sym.to_s =~ /^#{prefix}_(.*)$/
|
384
|
+
format_name = $1.upcase
|
385
|
+
return nil unless format = Magick.formats[format_name]
|
386
|
+
format.include?(function) && format_name
|
387
|
+
end
|
388
|
+
|
389
|
+
# Translate to_xxx to XXX if XXX is a valid Magick image format with blob function.
|
390
|
+
def conversion_to_format(sym)
|
391
|
+
method_to_format('to', sym, '*')
|
392
|
+
end
|
393
|
+
|
394
|
+
# Translate write_xxx to XXX if XXX is a valid Magick image format with write function.
|
395
|
+
def writer_to_format(sym)
|
396
|
+
method_to_format('write', sym, 'w')
|
397
|
+
end
|
398
|
+
|
399
|
+
# Format:
|
400
|
+
# MAP World World100.png 90 -170 -90 190
|
401
|
+
# PROJECTION USA50 ALBER 704.0 30.8 45.5 21.86 -99.9 232 388
|
402
|
+
def self.info_from_file
|
403
|
+
File.open("#{File.dirname(__FILE__)}/pippa/maps/_info", 'r') do |f|
|
404
|
+
data = {}
|
405
|
+
while (line = f.gets)
|
406
|
+
tag, name, *vec = line.split
|
407
|
+
tag = tag.downcase.to_sym
|
408
|
+
data[tag] ||= {}
|
409
|
+
data[tag][name] = vec
|
410
|
+
end
|
411
|
+
data
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# Read CSV file of zipcode data. Much more than we need.
|
416
|
+
# TODO: Develop quicker-loading version of the data file.
|
417
|
+
# Format:
|
418
|
+
# "Zipcode","ZipCodeType","City","State","LocationType","Lat","Long",
|
419
|
+
# "Location","Decommisioned","TaxReturnsFiled","EstimatedPopulation","TotalWages"
|
420
|
+
def self.zips_from_file
|
421
|
+
CSV::HeaderConverters[:underscore_symbol] = lambda do |s|
|
422
|
+
t = s.gsub(/::/, '/')
|
423
|
+
t.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
424
|
+
t.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
425
|
+
t.tr!("-", "_")
|
426
|
+
t.downcase!
|
427
|
+
t.to_sym
|
428
|
+
end
|
429
|
+
CSV::Converters[:custom] = lambda do |s, info|
|
430
|
+
begin
|
431
|
+
[:lat, :long].include?(info.header) ? Float(s) : s
|
432
|
+
rescue
|
433
|
+
s
|
434
|
+
end
|
435
|
+
end
|
436
|
+
zips = {}
|
437
|
+
CSV.foreach("#{File.dirname(__FILE__)}/pippa/maps/_zipcodes.csv",
|
438
|
+
:headers => :first_row,
|
439
|
+
:header_converters => :underscore_symbol,
|
440
|
+
:converters => :custom) do |row|
|
441
|
+
zips[row[:zipcode]] = row if row[:lat] && row[:long]
|
442
|
+
end
|
443
|
+
zips
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|