zipcoder 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +129 -48
- data/lib/ext/string.rb +10 -2
- data/lib/zipcoder/cacher/base.rb +190 -0
- data/lib/zipcoder/version.rb +1 -1
- data/lib/zipcoder.rb +103 -133
- data/spec/zipcoder_spec.rb +116 -26
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a3ed8a72bc6297d47835fa432a6ef7329afa105
|
4
|
+
data.tar.gz: dcd374d44e39bc8a7c11991a3e7e7d6ebd232c03
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fe034ee31e6ee82c61d4b4aba9b0a2a61635b81e236ceafb2100e858d27e2e842e2e8c43a88db5d6234cc05b18c1b57f3bdd6c6e31e9256d1fb55f3eb3bdadf
|
7
|
+
data.tar.gz: ba288abb61c112e26c09e7788792b30123634bb69af4d93cdf0007e67c4eacd2a46f8e77bcef2c946ab283636985dcb3caa95f4a54e088d5d8684051d4bdf359
|
data/README.md
CHANGED
@@ -7,6 +7,13 @@ Gem for performing zip code lookup operations
|
|
7
7
|
|
8
8
|
## Revision History
|
9
9
|
|
10
|
+
- v0.4.0:
|
11
|
+
- abstracted "cacher" object to later support "redis" and "memcacher"
|
12
|
+
- API Change!! - changed name of "cities" to "state_cities"
|
13
|
+
- added "names_only" option to "state_cities"
|
14
|
+
- added "states" method that returns list of states
|
15
|
+
- added "zip_cities" method that returns the cities associated with a
|
16
|
+
list of zip codes
|
10
17
|
- v0.3.0:
|
11
18
|
- API Change!! - intitialization method change from "load_data" to "load_cache"
|
12
19
|
- added city and state caches
|
@@ -58,104 +65,178 @@ reduce this later.
|
|
58
65
|
The library overrides the String (and in some instances Integer) class to
|
59
66
|
provide the following methods
|
60
67
|
|
61
|
-
####
|
68
|
+
#### Method: Zipcoder.zip_info(zip, **args)
|
62
69
|
|
63
|
-
|
70
|
+
Returns the info for the "zip"
|
71
|
+
|
72
|
+
**variations:**
|
73
|
+
|
74
|
+
- ```Zipcoder.zip_info(78748, **args)```
|
75
|
+
- ```Zipcoder.zip_info("78748", **args)```
|
76
|
+
- ```"78748".zip_info(**args)```
|
77
|
+
- ```78748.zip_info(**args)```
|
78
|
+
|
79
|
+
**parameters:**
|
80
|
+
|
81
|
+
- zip [String, Integer] - a string or integer representing a single zip code
|
82
|
+
- **return** [Hash] - zip object
|
83
|
+
|
84
|
+
**arguments:**
|
85
|
+
|
86
|
+
- keys [Array] - array of keys to include (filters out the others)
|
87
|
+
|
88
|
+
**notes:**
|
89
|
+
|
90
|
+
- none
|
91
|
+
|
92
|
+
**examples:**
|
64
93
|
|
65
94
|
``` ruby
|
66
95
|
require 'zipcoder'
|
67
96
|
|
97
|
+
# Looks up a zip code by string
|
68
98
|
puts "78748".zip_info
|
69
99
|
# > {:zip=>"78748", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
100
|
+
|
101
|
+
# Looks up a zip code by integer
|
102
|
+
puts 78748.zip_info
|
103
|
+
# > {:zip=>"78748", :city=>"Austin", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
104
|
+
|
70
105
|
```
|
71
106
|
|
72
|
-
|
107
|
+
#### Method: Zipcoder.zip_cities(zip_string, **args)
|
73
108
|
|
74
|
-
|
75
|
-
require 'zipcoder'
|
109
|
+
Returns the cities that are covered by the "zip_string"
|
76
110
|
|
77
|
-
|
78
|
-
# > {:zip=>"78748", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
79
|
-
```
|
111
|
+
**variations:**
|
80
112
|
|
81
|
-
|
113
|
+
- ```Zipcoder.zip_cities("78701-78799,78613", **args)```
|
114
|
+
- ```"78701-78799,78613".zip_cities(**args)```
|
82
115
|
|
83
|
-
|
84
|
-
as shown below
|
116
|
+
**parameters:**
|
85
117
|
|
86
|
-
|
87
|
-
|
118
|
+
- zip_string [String] - a string containing comma delimited list of
|
119
|
+
zip codes and zip code ranges (ex. "78701-78750, 78613")
|
120
|
+
- **return** [Array] - array of zip objects or names (if "names_only"
|
121
|
+
is specified)
|
88
122
|
|
89
|
-
|
90
|
-
# > {:city=>"AUSTIN", :state=>"TX"}
|
91
|
-
```
|
123
|
+
**arguments:**
|
92
124
|
|
93
|
-
|
125
|
+
- keys [Array] - array of keys to include (filters out the others)
|
126
|
+
- names_only [Bool] - set to "true" if you only want the city names returned
|
127
|
+
- max [Integer] - maximum number of cities to return
|
128
|
+
|
129
|
+
**notes:**
|
94
130
|
|
95
|
-
|
131
|
+
- none
|
132
|
+
|
133
|
+
**examples:**
|
96
134
|
|
97
135
|
``` ruby
|
98
136
|
require 'zipcoder'
|
99
137
|
|
100
|
-
|
101
|
-
|
138
|
+
# Returns the cities for a zip_code
|
139
|
+
puts "78701-78750,78613".zip_cities
|
140
|
+
# > [{:zip=>"78748", :city=>"Austin", :state=>"TX", :lat=>30.26, :long=>-97.74}, ...
|
141
|
+
|
142
|
+
# Returns just the name of the cities
|
143
|
+
puts "78701-78750,78613".zip_cities names_only: true
|
144
|
+
# > ["Austin", "Cedar Park"]
|
102
145
|
```
|
103
146
|
|
104
|
-
|
147
|
+
#### Method: Zipcoder.city_info(city_state, **args)
|
148
|
+
|
149
|
+
Returns the zip object for a city
|
150
|
+
|
151
|
+
**variations:**
|
152
|
+
|
153
|
+
- ```Zipcoder.city_info("Atlanta, GA", **args)```
|
154
|
+
- ```"zip_string".zip_cities(**args)```
|
155
|
+
|
156
|
+
**parameters:**
|
157
|
+
|
158
|
+
- city_state [String] - a string "city, state"
|
159
|
+
- **return** [Hash] - zip object
|
160
|
+
|
161
|
+
**arguments:**
|
162
|
+
|
163
|
+
- keys [Array] - array of keys to include (filters out the others)
|
164
|
+
|
165
|
+
**notes:**
|
105
166
|
|
106
167
|
- the "zip", "lat", "long" are the combined values from all of the
|
107
168
|
individual zip codes
|
108
169
|
- the library will normalize the key by removing all of the whitespace
|
109
|
-
and capitalizing the letters. So for example, "
|
110
|
-
"
|
111
|
-
- the library will cache the normalized cities to improve performance
|
112
|
-
on subsequent calls
|
170
|
+
and capitalizing the letters. So for example, " Los Angeles , CA " becomes
|
171
|
+
"LOS ANGELES,CA"
|
113
172
|
|
114
|
-
|
115
|
-
|
116
|
-
You can filter the keys that are returned by including the "keys" argument
|
117
|
-
as shown below
|
173
|
+
**examples:**
|
118
174
|
|
119
175
|
``` ruby
|
120
176
|
require 'zipcoder'
|
121
177
|
|
122
|
-
puts "Austin, TX".city_info
|
123
|
-
# > {:zip=>"78701-78799"}
|
178
|
+
puts "Austin, TX".city_info
|
179
|
+
# > {:zip=>"78701-78799", :city=>"AUSTIN", :state=>"TX", :lat=>30.26, :long=>-97.74}
|
124
180
|
```
|
125
181
|
|
126
|
-
####
|
182
|
+
#### Method: Zipcoder.state_cities(state, **args)
|
127
183
|
|
128
184
|
This will return the cities in a state
|
129
185
|
|
186
|
+
**variations:**
|
187
|
+
|
188
|
+
- ```Zipcoder.state_cities("GA", **args)```
|
189
|
+
- ```"GA".state_cities(**args)```
|
190
|
+
|
191
|
+
**parameters:**
|
192
|
+
|
193
|
+
- state [String] - the 2 letter state abbreviation
|
194
|
+
- **return** [Array] - list of zip objects (or city names if "names_only")
|
195
|
+
is set
|
196
|
+
|
197
|
+
**arguments:**
|
198
|
+
|
199
|
+
- keys [Array] - array of keys to include (filters out the others)
|
200
|
+
- names_only [Bool] - set to "true" if you only want the city names returned
|
201
|
+
|
202
|
+
**examples:**
|
203
|
+
|
130
204
|
``` ruby
|
131
205
|
require 'zipcoder'
|
132
206
|
|
133
|
-
|
134
|
-
|
207
|
+
# Returns Objects
|
208
|
+
puts "TX".state_cities
|
209
|
+
# > [{:city=>"Abbott", :state=>"TX" ...
|
210
|
+
|
211
|
+
# Returns List
|
212
|
+
puts "TX".state_cities names_only: true
|
213
|
+
# > ["Abbott", ...
|
135
214
|
```
|
136
215
|
|
137
|
-
|
216
|
+
#### Method: Zipcoder.states
|
138
217
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
218
|
+
This will return the states in the US
|
219
|
+
|
220
|
+
**variations:**
|
221
|
+
|
222
|
+
- ```Zipcoder.states```
|
223
|
+
|
224
|
+
**parameters:**
|
146
225
|
|
147
|
-
|
226
|
+
- none
|
148
227
|
|
149
|
-
|
150
|
-
|
228
|
+
**arguments:**
|
229
|
+
|
230
|
+
- none
|
231
|
+
|
232
|
+
**examples:**
|
151
233
|
|
152
234
|
``` ruby
|
153
235
|
require 'zipcoder'
|
154
236
|
|
155
|
-
puts
|
156
|
-
# >
|
237
|
+
puts Zipcoder.states
|
238
|
+
# > ["AK", "AL", ...
|
157
239
|
```
|
158
|
-
|
159
240
|
### Updating Data
|
160
241
|
|
161
242
|
The library is using the free public zip code data base located
|
data/lib/ext/string.rb
CHANGED
@@ -6,16 +6,24 @@ class String
|
|
6
6
|
Zipcoder.zip_info self, **kwargs
|
7
7
|
end
|
8
8
|
|
9
|
+
def zip_cities(**kwargs)
|
10
|
+
Zipcoder.zip_cities self, **kwargs
|
11
|
+
end
|
12
|
+
|
9
13
|
def city_info(**kwargs)
|
10
14
|
Zipcoder.city_info self, **kwargs
|
11
15
|
end
|
12
16
|
|
13
|
-
def
|
14
|
-
Zipcoder.
|
17
|
+
def state_cities(**kwargs)
|
18
|
+
Zipcoder.state_cities self, **kwargs
|
15
19
|
end
|
16
20
|
|
17
21
|
def to_zip
|
18
22
|
self
|
19
23
|
end
|
20
24
|
|
25
|
+
def capitalize_all
|
26
|
+
self.split(' ').map {|w| w.capitalize }.join(' ')
|
27
|
+
end
|
28
|
+
|
21
29
|
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Cacher
|
2
|
+
# The cacher base class places all of the objects in memory. The
|
3
|
+
# abstraction will later allow us to override for MemCacher and
|
4
|
+
# Redis implementations
|
5
|
+
class Base
|
6
|
+
|
7
|
+
#region Override These
|
8
|
+
def _init_cache(**kwargs)
|
9
|
+
@cache = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def _empty_cache
|
13
|
+
@cache.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def _write_cache(key, value)
|
17
|
+
@cache[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def _read_cache(key)
|
21
|
+
@cache[key]
|
22
|
+
end
|
23
|
+
|
24
|
+
def _iterate_keys(**kwargs, &block)
|
25
|
+
return if block == nil
|
26
|
+
|
27
|
+
start_with = kwargs[:start_with]
|
28
|
+
|
29
|
+
@cache.keys.each do |key|
|
30
|
+
if start_with == nil or key.start_with?(start_with)
|
31
|
+
block.call(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#endregion
|
37
|
+
|
38
|
+
def initialize(**kwargs)
|
39
|
+
self._init_cache **kwargs
|
40
|
+
end
|
41
|
+
|
42
|
+
def load
|
43
|
+
this_dir = File.expand_path(File.dirname(__FILE__))
|
44
|
+
|
45
|
+
# Load zip cache from file
|
46
|
+
zip_data = File.join(this_dir, '..', '..', 'data', 'zip_data.yml')
|
47
|
+
zip_codes = YAML.load(File.open(zip_data))
|
48
|
+
|
49
|
+
# Initialize
|
50
|
+
_empty_cache
|
51
|
+
city_states = {}
|
52
|
+
state_lookup = {}
|
53
|
+
|
54
|
+
# Add the zip codes to the cache
|
55
|
+
zip_codes.each do |zip, info|
|
56
|
+
city = info[:city].capitalize_all
|
57
|
+
info[:city] = city
|
58
|
+
state = info[:state]
|
59
|
+
|
60
|
+
# Iterate through the zip codes and add them to the zip cache
|
61
|
+
_write_cache _zip_cache(zip), info
|
62
|
+
|
63
|
+
# Create the city lookups
|
64
|
+
city_state = "#{city.upcase},#{state.upcase}"
|
65
|
+
infos = city_states[city_state] || []
|
66
|
+
infos << info
|
67
|
+
city_states[city_state] = infos
|
68
|
+
end
|
69
|
+
|
70
|
+
# Normalize each city and populate the state cache
|
71
|
+
city_states.each do |city_state, infos|
|
72
|
+
city = infos[0][:city]
|
73
|
+
state = infos[0][:state]
|
74
|
+
|
75
|
+
# Populate the City Cache
|
76
|
+
normalized = _normalize_city(infos)
|
77
|
+
_write_cache _city_cache(city_state), normalized
|
78
|
+
|
79
|
+
# Populate the State Cache
|
80
|
+
cities = state_lookup[state] || []
|
81
|
+
cities << city
|
82
|
+
state_lookup[state] = cities
|
83
|
+
end
|
84
|
+
|
85
|
+
# Set the cities cache
|
86
|
+
state_lookup.each do |state, cities|
|
87
|
+
_write_cache _state_cache(state), cities.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the states cache
|
91
|
+
self._write_cache _states, state_lookup.keys.sort
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_zip_cache(zip)
|
95
|
+
_read_cache _zip_cache(zip)
|
96
|
+
end
|
97
|
+
|
98
|
+
def read_city_cache(city_state)
|
99
|
+
_read_cache _city_cache(city_state)
|
100
|
+
end
|
101
|
+
|
102
|
+
def read_state_cache(state)
|
103
|
+
_read_cache _state_cache(state)
|
104
|
+
end
|
105
|
+
|
106
|
+
def read_states
|
107
|
+
_read_cache _states
|
108
|
+
end
|
109
|
+
|
110
|
+
def iterate_zips(&block)
|
111
|
+
return if block == nil
|
112
|
+
_iterate_keys(start_with: "zipcoder:zip") do |key|
|
113
|
+
info = _read_cache(key)
|
114
|
+
block.call(info) if block != nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def iterate_cities(&block)
|
119
|
+
return if block == nil
|
120
|
+
_iterate_keys(start_with: "zipcoder:city") do |key|
|
121
|
+
info = _read_cache(key)
|
122
|
+
block.call(info) if block != nil
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def _zip_cache(zip)
|
129
|
+
"zipcoder:zip:#{zip}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def _city_cache(city_state)
|
133
|
+
"zipcoder:city:#{city_state}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def _state_cache(state)
|
137
|
+
"zipcoder:state:#{state}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def _states
|
141
|
+
"zipcoder:states"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Normalizes the values
|
145
|
+
def _normalize_city(infos)
|
146
|
+
# Values
|
147
|
+
zip_min = 100000
|
148
|
+
zip_max = 0
|
149
|
+
lat_min = 200
|
150
|
+
lat_max = -200
|
151
|
+
long_min = 200
|
152
|
+
long_max = -200
|
153
|
+
|
154
|
+
# Iterate through the info and get min/max of zip/lat/long
|
155
|
+
infos.each do |info|
|
156
|
+
zip = info[:zip].to_i
|
157
|
+
zip_min = zip if zip < zip_min
|
158
|
+
zip_max = zip if zip > zip_max
|
159
|
+
lat_min = info[:lat] if info[:lat] < lat_min
|
160
|
+
lat_max = info[:lat] if info[:lat] > lat_max
|
161
|
+
long_min = info[:long] if info[:long] < long_min
|
162
|
+
long_max = info[:long] if info[:long] > long_max
|
163
|
+
end
|
164
|
+
|
165
|
+
# Create the normalized value
|
166
|
+
if infos.count == 0
|
167
|
+
normalized = nil
|
168
|
+
elsif infos.count == 1
|
169
|
+
normalized = {
|
170
|
+
city: infos[0][:city],
|
171
|
+
state: infos[0][:state],
|
172
|
+
zip: infos[0][:zip],
|
173
|
+
lat: infos[0][:lat],
|
174
|
+
long: infos[0][:long],
|
175
|
+
}
|
176
|
+
else
|
177
|
+
normalized = {
|
178
|
+
city: infos[0][:city],
|
179
|
+
state: infos[0][:state],
|
180
|
+
zip: "#{zip_min.to_zip}-#{zip_max.to_zip}",
|
181
|
+
lat: ((lat_min+lat_max)/2).round(4),
|
182
|
+
long: ((long_min+long_max)/2).round(4)
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
normalized
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
end
|
data/lib/zipcoder/version.rb
CHANGED
data/lib/zipcoder.rb
CHANGED
@@ -1,151 +1,121 @@
|
|
1
1
|
require "zipcoder/version"
|
2
|
+
require "zipcoder/cacher/base"
|
2
3
|
require "ext/string"
|
3
4
|
require "ext/integer"
|
4
5
|
require "yaml"
|
5
6
|
|
6
7
|
module Zipcoder
|
7
8
|
|
8
|
-
|
9
|
-
@@zip_cache = nil
|
10
|
-
def self.zip_cache
|
11
|
-
if @@zip_cache == nil
|
12
|
-
self.load_cache
|
13
|
-
end
|
14
|
-
@@zip_cache
|
15
|
-
end
|
16
|
-
|
17
|
-
@@city_cache = {}
|
18
|
-
def self.city_cache
|
19
|
-
@@city_cache
|
9
|
+
class ZipcoderError < Exception
|
20
10
|
end
|
21
11
|
|
22
|
-
@@
|
23
|
-
def self.
|
24
|
-
@@
|
12
|
+
@@cacher = nil
|
13
|
+
def self.cacher
|
14
|
+
if @@cacher == nil
|
15
|
+
self.load_cache
|
16
|
+
end
|
17
|
+
@@cacher
|
25
18
|
end
|
26
19
|
|
27
20
|
# Loads the data into memory
|
28
|
-
def self.load_cache
|
29
|
-
|
30
|
-
|
31
|
-
# Load zip cache from file
|
32
|
-
zip_data = File.join(this_dir, 'data', 'zip_data.yml')
|
33
|
-
@@zip_cache = YAML.load(File.open(zip_data))
|
34
|
-
|
35
|
-
# Iterate through zip codes to populate city and state data
|
36
|
-
city_states = {}
|
37
|
-
self.zip_cache.values.each do |info|
|
38
|
-
city = info[:city]
|
39
|
-
state = info[:state]
|
40
|
-
|
41
|
-
# Create the city lookups
|
42
|
-
city_state = "#{city},#{state}"
|
43
|
-
infos = city_states[city_state] || []
|
44
|
-
infos << info
|
45
|
-
city_states[city_state] = infos
|
46
|
-
end
|
47
|
-
|
48
|
-
# Normalize each city and populate the state cache
|
49
|
-
city_states.each do |city_state, infos|
|
50
|
-
state = infos[0][:state]
|
51
|
-
|
52
|
-
# Populate the City Cache
|
53
|
-
normalized = self.city_info(city_state, infos)
|
54
|
-
self.city_cache[city_state] = normalized
|
55
|
-
|
56
|
-
# Populate the State Cache
|
57
|
-
cities = self.state_cache[state] || []
|
58
|
-
cities << normalized
|
59
|
-
self.state_cache[state] = cities
|
60
|
-
end
|
61
|
-
|
62
|
-
# Sort the city arrays
|
63
|
-
self.state_cache.keys.each do |state|
|
64
|
-
infos = self.state_cache[state]
|
65
|
-
new_infos = infos.sort_by { |hsh| hsh[:city] }
|
66
|
-
self.state_cache[state] = new_infos
|
67
|
-
end
|
21
|
+
def self.load_cache(cacher=nil)
|
22
|
+
@@cacher = cacher || Cacher::Base.new
|
23
|
+
self.cacher.load
|
68
24
|
end
|
69
25
|
|
70
26
|
# Looks up zip code information
|
71
|
-
def self.zip_info(zip=nil, **kwargs
|
27
|
+
def self.zip_info(zip=nil, **kwargs)
|
72
28
|
|
73
29
|
# If zip is not nil, then we are returning a single value
|
74
30
|
if zip != nil
|
75
31
|
# Get the info
|
76
|
-
info = self.
|
77
|
-
|
78
|
-
# Inform callback that we have a match
|
79
|
-
block.call(info) if block != nil
|
32
|
+
info = self.cacher.read_zip_cache(zip.to_zip)
|
80
33
|
|
81
34
|
# Filter to the included keys
|
82
35
|
self._filter_hash_args info, kwargs[:keys]
|
83
36
|
else
|
84
37
|
# If zip is nil, then we are returning an array of values
|
85
|
-
|
86
38
|
city_filter = kwargs[:city] != nil ? kwargs[:city].upcase : nil
|
87
39
|
state_filter = kwargs[:state] != nil ? kwargs[:state].upcase : nil
|
88
40
|
|
89
41
|
# Iterate through and only add the ones that match the filters
|
90
42
|
infos = []
|
91
|
-
self.
|
92
|
-
if (city_filter == nil or info[:city] == city_filter) and
|
93
|
-
(state_filter == nil or info[:state] == state_filter)
|
43
|
+
self.cacher.iterate_zips do |info|
|
44
|
+
if (city_filter == nil or info[:city].upcase == city_filter) and
|
45
|
+
(state_filter == nil or info[:state].upcase == state_filter)
|
94
46
|
infos << self._filter_hash_args(info, kwargs[:keys])
|
95
|
-
|
96
|
-
# Inform callback that we have a match
|
97
|
-
block.call(info) if block != nil
|
98
47
|
end
|
99
|
-
|
48
|
+
end
|
100
49
|
|
101
50
|
infos
|
102
51
|
end
|
103
52
|
end
|
104
53
|
|
105
|
-
#
|
106
|
-
def self.
|
107
|
-
|
108
|
-
|
109
|
-
|
54
|
+
# Returns the cities that contain the zip codes
|
55
|
+
def self.zip_cities(zip_string, **kwargs)
|
56
|
+
max = kwargs[:max]
|
57
|
+
|
58
|
+
cities = {}
|
59
|
+
self._parse_zip_string(zip_string).each do |zip|
|
60
|
+
info = zip.zip_info
|
61
|
+
if info == nil
|
62
|
+
next
|
63
|
+
end
|
64
|
+
|
65
|
+
cities["#{info[:city]}, #{info[:state]}"] = true
|
110
66
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
if cached_value != nil
|
115
|
-
return self._filter_hash_args cached_value, kwargs[:keys]
|
67
|
+
if max != nil and cities.keys.count >= max
|
68
|
+
break
|
69
|
+
end
|
116
70
|
end
|
117
71
|
|
118
|
-
|
119
|
-
components = city_state.split(",")
|
120
|
-
city = components[0].strip.upcase
|
121
|
-
state = components[1].strip.upcase
|
72
|
+
cities = cities.keys.uniq.sort
|
122
73
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
74
|
+
if kwargs[:names_only]
|
75
|
+
zips = cities.map{ |x| x.split(",")[0].strip }
|
76
|
+
else
|
77
|
+
zips = []
|
78
|
+
cities.each do |key|
|
79
|
+
zips << key.city_info(keys: kwargs[:keys])
|
80
|
+
end
|
129
81
|
end
|
82
|
+
zips
|
83
|
+
end
|
130
84
|
|
131
|
-
|
132
|
-
|
85
|
+
# Looks up city information
|
86
|
+
def self.city_info(city_state, **kwargs)
|
87
|
+
# Get the city from the cache
|
88
|
+
cache_key = self._cache_key(city_state)
|
89
|
+
cached_value = self.cacher.read_city_cache(cache_key)
|
133
90
|
|
134
|
-
#
|
135
|
-
self._filter_hash_args
|
91
|
+
# Return it
|
92
|
+
self._filter_hash_args cached_value, kwargs[:keys]
|
136
93
|
end
|
137
94
|
|
138
95
|
# Returns the cities in a state
|
139
|
-
def self.
|
96
|
+
def self.state_cities(state, **kwargs)
|
140
97
|
state = state.strip.upcase
|
141
98
|
|
99
|
+
names_only = kwargs[:names_only]
|
100
|
+
keys = kwargs[:keys]
|
101
|
+
|
142
102
|
# Filter the returned cities
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
103
|
+
cities = self.cacher.read_state_cache(state)
|
104
|
+
if names_only
|
105
|
+
cities
|
106
|
+
else
|
107
|
+
infos = []
|
108
|
+
self.cacher.read_state_cache(state).each { |city|
|
109
|
+
infos << self.city_info("#{city}, #{state}", keys: keys)
|
110
|
+
}
|
147
111
|
|
148
|
-
|
112
|
+
infos
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the states
|
117
|
+
def self.states
|
118
|
+
self.cacher.read_states
|
149
119
|
end
|
150
120
|
|
151
121
|
# Filters arguments in return hash
|
@@ -160,45 +130,45 @@ module Zipcoder
|
|
160
130
|
hash
|
161
131
|
end
|
162
132
|
|
163
|
-
#
|
164
|
-
def self.
|
165
|
-
|
166
|
-
|
167
|
-
zip_max = 0
|
168
|
-
lat_min = 200
|
169
|
-
lat_max = 0
|
170
|
-
long_min = 200
|
171
|
-
long_max = 0
|
172
|
-
|
173
|
-
# Iterate through the info and get min/max of zip/lat/long
|
174
|
-
infos.each do |info|
|
175
|
-
zip = info[:zip].to_i
|
176
|
-
zip_min = zip if zip < zip_min
|
177
|
-
zip_max = zip if zip > zip_max
|
178
|
-
lat_min = info[:lat] if info[:lat] < lat_min
|
179
|
-
lat_max = info[:lat] if info[:lat] > lat_max
|
180
|
-
long_min = info[:long] if info[:long] < long_min
|
181
|
-
long_max = info[:long] if info[:long] > long_max
|
133
|
+
# Returns a cache key
|
134
|
+
def self._cache_key(city_state)
|
135
|
+
unless city_state.include? ','
|
136
|
+
raise ZipcoderError, "city/state must include ','"
|
182
137
|
end
|
183
138
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
139
|
+
components = city_state.split(',')
|
140
|
+
city = components[0].strip.upcase
|
141
|
+
state = components[1].strip.upcase
|
142
|
+
|
143
|
+
"#{city},#{state}"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Parses a zip code string and returns all of the zip codes as
|
147
|
+
# an array
|
148
|
+
def self._parse_zip_string(zip_string)
|
149
|
+
zips = []
|
150
|
+
|
151
|
+
zip_string.split(",").each do |zip_component|
|
152
|
+
if zip_component.include? "-"
|
153
|
+
z = zip_component.split("-")
|
154
|
+
(z[0].strip.to_i..z[1].strip.to_i).each do |zip|
|
155
|
+
zips << self._check_zip(zip.to_zip)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
zips << self._check_zip(zip_component.strip)
|
159
|
+
end
|
199
160
|
end
|
200
161
|
|
201
|
-
|
162
|
+
zips.sort.uniq
|
163
|
+
end
|
164
|
+
|
165
|
+
# Check the zip codes
|
166
|
+
def self._check_zip(zip)
|
167
|
+
if zip.length != 5
|
168
|
+
raise ZipcoderError, "zip code #{zip} is not 5 characters"
|
169
|
+
end
|
170
|
+
zip
|
202
171
|
end
|
203
172
|
|
173
|
+
|
204
174
|
end
|
data/spec/zipcoder_spec.rb
CHANGED
@@ -6,10 +6,19 @@ describe Zipcoder do
|
|
6
6
|
expect(Zipcoder::VERSION).not_to be nil
|
7
7
|
end
|
8
8
|
|
9
|
+
describe "#states" do
|
10
|
+
it "returns the states" do
|
11
|
+
states = described_class.states
|
12
|
+
expect(states.count).to eq(54)
|
13
|
+
expect(states[0]).to eq("AK")
|
14
|
+
expect(states[1]).to eq("AL")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
9
18
|
describe "#zip_info" do
|
10
19
|
it "returns the info for a particular zip_code" do
|
11
20
|
zip_info = described_class.zip_info "78748"
|
12
|
-
expect(zip_info[:city]).to eq("
|
21
|
+
expect(zip_info[:city]).to eq("Austin")
|
13
22
|
expect(zip_info[:state]).to eq("TX")
|
14
23
|
expect(zip_info[:zip]).to eq("78748")
|
15
24
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -18,7 +27,7 @@ describe Zipcoder do
|
|
18
27
|
|
19
28
|
it "returns the info for a particular zip_code" do
|
20
29
|
zip_info = described_class.zip_info 78748
|
21
|
-
expect(zip_info[:city]).to eq("
|
30
|
+
expect(zip_info[:city]).to eq("Austin")
|
22
31
|
expect(zip_info[:state]).to eq("TX")
|
23
32
|
expect(zip_info[:zip]).to eq("78748")
|
24
33
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -27,13 +36,13 @@ describe Zipcoder do
|
|
27
36
|
|
28
37
|
it "zero pads the zip code when using an integer" do
|
29
38
|
zip_info = described_class.zip_info 705
|
30
|
-
expect(zip_info[:city]).to eq("
|
39
|
+
expect(zip_info[:city]).to eq("Aibonito")
|
31
40
|
end
|
32
41
|
|
33
42
|
describe "keys filter" do
|
34
43
|
it "only returns specified keys" do
|
35
44
|
zip_info = described_class.zip_info "78748", keys: [:city, :state]
|
36
|
-
expect(zip_info[:city]).to eq("
|
45
|
+
expect(zip_info[:city]).to eq("Austin")
|
37
46
|
expect(zip_info[:state]).to eq("TX")
|
38
47
|
expect(zip_info[:zip]).to be_nil
|
39
48
|
expect(zip_info[:lat]).to be_nil
|
@@ -52,10 +61,15 @@ describe Zipcoder do
|
|
52
61
|
expect(zip_infos.count).to eq(1745)
|
53
62
|
end
|
54
63
|
|
55
|
-
it "returns zip codes that match a particular city
|
64
|
+
it "returns zip codes that match a particular city and state" do
|
56
65
|
zip_infos = described_class.zip_info city: "Austin", state: "TX"
|
57
66
|
expect(zip_infos.count).to eq(47)
|
58
67
|
end
|
68
|
+
|
69
|
+
it "returns zip codes that match a particular city with spaces and state" do
|
70
|
+
zip_infos = described_class.zip_info city: "San Antonio", state: "TX"
|
71
|
+
expect(zip_infos.count).to eq(65)
|
72
|
+
end
|
59
73
|
end
|
60
74
|
|
61
75
|
end
|
@@ -64,16 +78,25 @@ describe Zipcoder do
|
|
64
78
|
it "raises exception if no ','" do
|
65
79
|
expect {
|
66
80
|
described_class.city_info "Austin TX"
|
67
|
-
}.to raise_error(
|
81
|
+
}.to raise_error(Zipcoder::ZipcoderError)
|
68
82
|
end
|
69
83
|
|
70
84
|
it "returns the normalized city/state value" do
|
71
85
|
city_info = described_class.city_info "Austin, TX"
|
72
|
-
expect(city_info[:city]).to eq("
|
86
|
+
expect(city_info[:city]).to eq("Austin")
|
73
87
|
expect(city_info[:state]).to eq("TX")
|
74
88
|
expect(city_info[:zip]).to eq("78701-78799")
|
75
89
|
expect(city_info[:lat]).to eq(30.315)
|
76
|
-
expect(city_info[:long]).to eq(-
|
90
|
+
expect(city_info[:long]).to eq(-97.71)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "returns the normalized city/state value with space" do
|
94
|
+
city_info = described_class.city_info " San Antonio , TX"
|
95
|
+
expect(city_info[:city]).to eq("San Antonio")
|
96
|
+
expect(city_info[:state]).to eq("TX")
|
97
|
+
expect(city_info[:zip]).to eq("78201-78285")
|
98
|
+
expect(city_info[:lat]).to eq(29.435)
|
99
|
+
expect(city_info[:long]).to eq(-98.495)
|
77
100
|
end
|
78
101
|
|
79
102
|
it "returns the normalized city/state filtered" do
|
@@ -82,7 +105,7 @@ describe Zipcoder do
|
|
82
105
|
expect(city_info[:state]).to be_nil
|
83
106
|
expect(city_info[:zip]).to eq("78701-78799")
|
84
107
|
expect(city_info[:lat]).to eq(30.315)
|
85
|
-
expect(city_info[:long]).to eq(-
|
108
|
+
expect(city_info[:long]).to eq(-97.71)
|
86
109
|
end
|
87
110
|
|
88
111
|
it 'returns nil on mismatch' do
|
@@ -92,36 +115,103 @@ describe Zipcoder do
|
|
92
115
|
|
93
116
|
end
|
94
117
|
|
95
|
-
describe "#
|
118
|
+
describe "#state_cities" do
|
96
119
|
it "returns the cities for a state" do
|
97
|
-
cities = described_class.
|
120
|
+
cities = described_class.state_cities "TX"
|
98
121
|
expect(cities.count).to eq(1170)
|
99
122
|
|
100
123
|
city_info = cities[0]
|
101
|
-
expect(city_info[:city]).to eq("
|
124
|
+
expect(city_info[:city]).to eq("Abbott")
|
102
125
|
expect(city_info[:state]).to eq("TX")
|
103
126
|
expect(city_info[:zip]).to eq("76621")
|
104
127
|
expect(city_info[:lat]).to eq(31.88)
|
105
128
|
expect(city_info[:long]).to eq(-97.07)
|
106
129
|
end
|
107
130
|
it "returns the cities for a state filtered" do
|
108
|
-
cities = described_class.
|
131
|
+
cities = described_class.state_cities "TX", keys: [:zip, :city]
|
109
132
|
expect(cities.count).to eq(1170)
|
110
133
|
|
111
134
|
city_info = cities[0]
|
112
|
-
expect(city_info[:city]).to eq("
|
135
|
+
expect(city_info[:city]).to eq("Abbott")
|
113
136
|
expect(city_info[:state]).to be_nil
|
114
137
|
expect(city_info[:zip]).to eq("76621")
|
115
138
|
expect(city_info[:lat]).to be_nil
|
116
139
|
expect(city_info[:long]).to be_nil
|
117
140
|
end
|
141
|
+
it "returns the names of the cities" do
|
142
|
+
cities = described_class.state_cities "TX", names_only: true
|
143
|
+
expect(cities.count).to eq(1170)
|
144
|
+
|
145
|
+
expect(cities[0]).to eq("Abbott")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "#zip_cities" do
|
150
|
+
it "returns a city for one zip code" do
|
151
|
+
zip_cities = "78748".zip_cities
|
152
|
+
expect(zip_cities.count).to eq(1)
|
153
|
+
|
154
|
+
zip_info = zip_cities[0]
|
155
|
+
expect(zip_info[:city]).to eq("Austin")
|
156
|
+
expect(zip_info[:state]).to eq("TX")
|
157
|
+
expect(zip_info[:zip]).to eq("78701-78799")
|
158
|
+
expect(zip_info[:lat]).to eq(30.315)
|
159
|
+
expect(zip_info[:long]).to eq(-97.71)
|
160
|
+
end
|
161
|
+
|
162
|
+
it "returns multiple cities" do
|
163
|
+
zip_cities = "78702-78750,78613".zip_cities
|
164
|
+
expect(zip_cities.count).to eq(2)
|
165
|
+
|
166
|
+
zip_info = zip_cities[0]
|
167
|
+
expect(zip_info[:city]).to eq("Austin")
|
168
|
+
expect(zip_info[:state]).to eq("TX")
|
169
|
+
expect(zip_info[:zip]).to eq("78701-78799")
|
170
|
+
expect(zip_info[:lat]).to eq(30.315)
|
171
|
+
expect(zip_info[:long]).to eq(-97.71)
|
172
|
+
|
173
|
+
zip_info = zip_cities[1]
|
174
|
+
expect(zip_info[:city]).to eq("Cedar Park")
|
175
|
+
expect(zip_info[:state]).to eq("TX")
|
176
|
+
expect(zip_info[:zip]).to eq("78613")
|
177
|
+
expect(zip_info[:lat]).to eq(30.51)
|
178
|
+
expect(zip_info[:long]).to eq(-97.83)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "breaks on max" do
|
182
|
+
zip_cities = "13601,11223,78748,78613".zip_cities max: 2
|
183
|
+
expect(zip_cities.count).to eq(2)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "returns just names of cities sorted" do
|
187
|
+
zip_cities = "13601,78613,78702-78750".zip_cities names_only: true
|
188
|
+
expect(zip_cities).to eq(["Austin", "Cedar Park", "Watertown"])
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe "#_parse_zip_string" do
|
193
|
+
it "parses the zip_string" do
|
194
|
+
[
|
195
|
+
["78703, 78701", ["78701", "78703"]],
|
196
|
+
["78701-78703, 78702", ["78701", "78702", "78703"]],
|
197
|
+
].each do |t|
|
198
|
+
zips = described_class._parse_zip_string(t[0])
|
199
|
+
expect(zips).to eq(t[1])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it "raises an error for invalid zip code" do
|
204
|
+
expect {
|
205
|
+
described_class._parse_zip_string "100"
|
206
|
+
}.to raise_error(Zipcoder::ZipcoderError)
|
207
|
+
end
|
118
208
|
end
|
119
209
|
|
120
210
|
describe("String") do
|
121
211
|
describe "zip_info" do
|
122
212
|
it "returns the info for a particular zip_code" do
|
123
213
|
zip_info = "78748".zip_info
|
124
|
-
expect(zip_info[:city]).to eq("
|
214
|
+
expect(zip_info[:city]).to eq("Austin")
|
125
215
|
expect(zip_info[:state]).to eq("TX")
|
126
216
|
expect(zip_info[:zip]).to eq("78748")
|
127
217
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -130,7 +220,7 @@ describe Zipcoder do
|
|
130
220
|
|
131
221
|
it "only returns specified keys" do
|
132
222
|
zip_info = "78748".zip_info keys: [:city, :state]
|
133
|
-
expect(zip_info[:city]).to eq("
|
223
|
+
expect(zip_info[:city]).to eq("Austin")
|
134
224
|
expect(zip_info[:state]).to eq("TX")
|
135
225
|
expect(zip_info[:zip]).to be_nil
|
136
226
|
expect(zip_info[:lat]).to be_nil
|
@@ -141,15 +231,15 @@ describe Zipcoder do
|
|
141
231
|
describe "city_info" do
|
142
232
|
it "returns the info for a particular city" do
|
143
233
|
city_info = "Austin, TX".city_info
|
144
|
-
expect(city_info[:city]).to eq("
|
234
|
+
expect(city_info[:city]).to eq("Austin")
|
145
235
|
expect(city_info[:state]).to eq("TX")
|
146
236
|
expect(city_info[:zip]).to eq("78701-78799")
|
147
237
|
expect(city_info[:lat]).to eq(30.315)
|
148
|
-
expect(city_info[:long]).to eq(-
|
238
|
+
expect(city_info[:long]).to eq(-97.71)
|
149
239
|
end
|
150
240
|
it "only returns specified keys" do
|
151
241
|
city_info = "Austin, TX".city_info keys: [:zip, :city]
|
152
|
-
expect(city_info[:city]).to eq("
|
242
|
+
expect(city_info[:city]).to eq("Austin")
|
153
243
|
expect(city_info[:state]).to be_nil
|
154
244
|
expect(city_info[:zip]).to eq("78701-78799")
|
155
245
|
expect(city_info[:lat]).to be_nil
|
@@ -157,24 +247,24 @@ describe Zipcoder do
|
|
157
247
|
end
|
158
248
|
end
|
159
249
|
|
160
|
-
describe "
|
250
|
+
describe "state_cities" do
|
161
251
|
it "returns the cities for a state" do
|
162
|
-
cities = "TX".
|
252
|
+
cities = "TX".state_cities
|
163
253
|
expect(cities.count).to eq(1170)
|
164
254
|
|
165
255
|
city_info = cities[0]
|
166
|
-
expect(city_info[:city]).to eq("
|
256
|
+
expect(city_info[:city]).to eq("Abbott")
|
167
257
|
expect(city_info[:state]).to eq("TX")
|
168
258
|
expect(city_info[:zip]).to eq("76621")
|
169
259
|
expect(city_info[:lat]).to eq(31.88)
|
170
260
|
expect(city_info[:long]).to eq(-97.07)
|
171
261
|
end
|
172
262
|
it "returns the cities for a state filtered" do
|
173
|
-
cities = "TX".
|
263
|
+
cities = "TX".state_cities keys: [:zip, :city]
|
174
264
|
expect(cities.count).to eq(1170)
|
175
265
|
|
176
266
|
city_info = cities[0]
|
177
|
-
expect(city_info[:city]).to eq("
|
267
|
+
expect(city_info[:city]).to eq("Abbott")
|
178
268
|
expect(city_info[:state]).to be_nil
|
179
269
|
expect(city_info[:zip]).to eq("76621")
|
180
270
|
expect(city_info[:lat]).to be_nil
|
@@ -186,7 +276,7 @@ describe Zipcoder do
|
|
186
276
|
describe("Integer") do
|
187
277
|
it "returns the info for a particular zip_code" do
|
188
278
|
zip_info = 78748.zip_info
|
189
|
-
expect(zip_info[:city]).to eq("
|
279
|
+
expect(zip_info[:city]).to eq("Austin")
|
190
280
|
expect(zip_info[:state]).to eq("TX")
|
191
281
|
expect(zip_info[:zip]).to eq("78748")
|
192
282
|
expect(zip_info[:lat]).to eq(30.26)
|
@@ -195,7 +285,7 @@ describe Zipcoder do
|
|
195
285
|
|
196
286
|
it "zero pads the zip code when using an integer" do
|
197
287
|
zip_info = 705.zip_info
|
198
|
-
expect(zip_info[:city]).to eq("
|
288
|
+
expect(zip_info[:city]).to eq("Aibonito")
|
199
289
|
end
|
200
290
|
end
|
201
291
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zipcoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Chapman
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/ext/integer.rb
|
105
105
|
- lib/ext/string.rb
|
106
106
|
- lib/zipcoder.rb
|
107
|
+
- lib/zipcoder/cacher/base.rb
|
107
108
|
- lib/zipcoder/version.rb
|
108
109
|
- spec/spec_helper.rb
|
109
110
|
- spec/zipcoder_spec.rb
|