us_geo 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +75 -0
- data/README.md +154 -0
- data/Rakefile +18 -0
- data/db/migrate/20190221054200_create_regions.rb +16 -0
- data/db/migrate/20190221054300_create_divisions.rb +17 -0
- data/db/migrate/20190221054400_create_states.rb +20 -0
- data/db/migrate/20190221054490_create_designated_market_areas.rb +16 -0
- data/db/migrate/20190221054500_create_combined_statistical_areas.rb +20 -0
- data/db/migrate/20190221054600_create_core_based_statistical_areas.rb +24 -0
- data/db/migrate/20190221054650_create_metropolitan_divisions.rb +21 -0
- data/db/migrate/20190221054700_create_counties.rb +34 -0
- data/db/migrate/20190221054800_create_zctas.rb +23 -0
- data/db/migrate/20190221054900_create_zcta_counties.rb +22 -0
- data/db/migrate/20190221055000_create_urban_areas.rb +25 -0
- data/db/migrate/20190221055100_create_urban_area_counties.rb +22 -0
- data/db/migrate/20190221055200_create_zcta_urban_areas.rb +22 -0
- data/db/migrate/20190221060000_create_places.rb +28 -0
- data/db/migrate/20190221061000_create_place_counties.rb +18 -0
- data/db/migrate/20190221062000_create_zcta_places.rb +22 -0
- data/db/migrate/20190221063000_create_county_subdivisions.rb +25 -0
- data/lib/tasks/us_geo/us_geo.rake +43 -0
- data/lib/us_geo/base_record.rb +104 -0
- data/lib/us_geo/combined_statistical_area.rb +40 -0
- data/lib/us_geo/core_based_statistical_area.rb +57 -0
- data/lib/us_geo/county.rb +89 -0
- data/lib/us_geo/county_subdivision.rb +46 -0
- data/lib/us_geo/demographics.rb +25 -0
- data/lib/us_geo/designated_market_area.rb +30 -0
- data/lib/us_geo/division.rb +29 -0
- data/lib/us_geo/engine.rb +6 -0
- data/lib/us_geo/metropolitan_area.rb +18 -0
- data/lib/us_geo/metropolitan_division.rb +42 -0
- data/lib/us_geo/micropolitan_area.rb +18 -0
- data/lib/us_geo/place.rb +61 -0
- data/lib/us_geo/place_county.rb +28 -0
- data/lib/us_geo/region.rb +28 -0
- data/lib/us_geo/state.rb +56 -0
- data/lib/us_geo/urban_area.rb +66 -0
- data/lib/us_geo/urban_area_county.rb +68 -0
- data/lib/us_geo/urban_cluster.rb +18 -0
- data/lib/us_geo/urbanized_area.rb +18 -0
- data/lib/us_geo/version.rb +5 -0
- data/lib/us_geo/zcta.rb +63 -0
- data/lib/us_geo/zcta_county.rb +68 -0
- data/lib/us_geo/zcta_place.rb +68 -0
- data/lib/us_geo/zcta_urban_area.rb +68 -0
- data/lib/us_geo.rb +53 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/us_geo/base_record_spec.rb +67 -0
- data/spec/us_geo/combined_statistical_area_spec.rb +33 -0
- data/spec/us_geo/core_based_statistical_area_spec.rb +56 -0
- data/spec/us_geo/county_spec.rb +130 -0
- data/spec/us_geo/county_subdivision_spec.rb +37 -0
- data/spec/us_geo/demographics_spec.rb +19 -0
- data/spec/us_geo/designated_market_area_spec.rb +29 -0
- data/spec/us_geo/division_spec.rb +37 -0
- data/spec/us_geo/metropolitan_division_spec.rb +41 -0
- data/spec/us_geo/place_county_spec.rb +39 -0
- data/spec/us_geo/place_spec.rb +71 -0
- data/spec/us_geo/region_spec.rb +36 -0
- data/spec/us_geo/state_spec.rb +70 -0
- data/spec/us_geo/urban_area_county_spec.rb +82 -0
- data/spec/us_geo/urban_area_spec.rb +98 -0
- data/spec/us_geo/zcta_county_spec.rb +82 -0
- data/spec/us_geo/zcta_place_spec.rb +82 -0
- data/spec/us_geo/zcta_spec.rb +99 -0
- data/spec/us_geo/zcta_urban_area_spec.rb +82 -0
- metadata +229 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :us_geo do
|
4
|
+
namespace :import do
|
5
|
+
klasses = {
|
6
|
+
regions: USGeo::Region,
|
7
|
+
divisions: USGeo::Division,
|
8
|
+
states: USGeo::State,
|
9
|
+
designated_market_areas: USGeo::DesignatedMarketArea,
|
10
|
+
combined_statistical_areas: USGeo::CombinedStatisticalArea,
|
11
|
+
core_based_statistical_areas: USGeo::CoreBasedStatisticalArea,
|
12
|
+
metropolitan_divisions: USGeo::MetropolitanDivision,
|
13
|
+
counties: USGeo::County,
|
14
|
+
county_subdivisions: USGeo::CountySubdivision,
|
15
|
+
urban_areas: USGeo::UrbanArea,
|
16
|
+
places: USGeo::Place,
|
17
|
+
zctas: USGeo::Zcta,
|
18
|
+
zcta_counties: USGeo::ZctaCounty,
|
19
|
+
zcta_urban_areas: USGeo::ZctaUrbanArea,
|
20
|
+
zcta_places: USGeo::ZctaPlace,
|
21
|
+
urban_area_counties: USGeo::UrbanAreaCounty,
|
22
|
+
place_counties: USGeo::PlaceCounty
|
23
|
+
}
|
24
|
+
klasses.each do |name, klass|
|
25
|
+
desc "Import data for #{klass}"
|
26
|
+
task name => :environment do
|
27
|
+
t = Time.now
|
28
|
+
klass.load!
|
29
|
+
puts "Loaded #{klass.count} rows into #{klass.table_name} in #{(Time.now - t).round(1)}s"
|
30
|
+
klass.removed.find_each do |record|
|
31
|
+
puts(" WARNING: #{klass}.#{record.id} status changed to removed")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Import data for all USGeo models"
|
36
|
+
task all: :environment do
|
37
|
+
klasses.each_key do |name|
|
38
|
+
Rake::Task["us_geo:import:#{name}"].invoke
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "csv"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module USGeo
|
7
|
+
|
8
|
+
class LoadError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Base class that all models inherit from.
|
12
|
+
class BaseRecord < ::ActiveRecord::Base
|
13
|
+
|
14
|
+
self.abstract_class = true
|
15
|
+
self.table_name_prefix = "us_geo_"
|
16
|
+
|
17
|
+
STATUS_IMPORTED = 1
|
18
|
+
STATUS_REMOVED = -1
|
19
|
+
STATUS_MANUAL = 0
|
20
|
+
|
21
|
+
validates :status, inclusion: [STATUS_IMPORTED, STATUS_REMOVED, STATUS_MANUAL]
|
22
|
+
|
23
|
+
scope :imported, -> { where(status: STATUS_IMPORTED) }
|
24
|
+
scope :removed, -> { where(status: STATUS_REMOVED) }
|
25
|
+
scope :manual, -> { where(status: STATUS_MANUAL) }
|
26
|
+
scope :not_removed, -> { where(status: [STATUS_IMPORTED, STATUS_MANUAL]) }
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def load!(location = nil, gzipped: true)
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# Insert or update a record given the unique criteria for finding it.
|
36
|
+
def load_record!(criteria, &block)
|
37
|
+
record = find_or_initialize_by(criteria)
|
38
|
+
record.status = STATUS_IMPORTED
|
39
|
+
record.updated_at = Time.now
|
40
|
+
yield(record)
|
41
|
+
record.save!
|
42
|
+
end
|
43
|
+
|
44
|
+
# Mark the status of any records not updated in the block as being no longer imported.
|
45
|
+
def import!(&block)
|
46
|
+
start_time = Time.at(Time.now.to_i.floor)
|
47
|
+
yield
|
48
|
+
raise LoadError.new("No data found") unless where("updated_at >= ?", start_time).exists?
|
49
|
+
where("updated_at < ?", start_time).imported.update_all(status: STATUS_REMOVED)
|
50
|
+
end
|
51
|
+
|
52
|
+
def data_uri(path)
|
53
|
+
path = path.to_s if path
|
54
|
+
if path.start_with?("/") || path.include?(":")
|
55
|
+
path
|
56
|
+
elsif USGeo.base_data_uri.include?(":")
|
57
|
+
"#{USGeo.base_data_uri}/#{path}"
|
58
|
+
else
|
59
|
+
File.join(USGeo.base_data_uri, path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_data_file(location, &block)
|
64
|
+
file = nil
|
65
|
+
if location.include?(":")
|
66
|
+
file = URI.parse(location).open(read_timeout: 5, open_timeout: 5)
|
67
|
+
else
|
68
|
+
file = File.open(location)
|
69
|
+
end
|
70
|
+
begin
|
71
|
+
rows = []
|
72
|
+
CSV.new(file, headers: true).each do |row|
|
73
|
+
rows << row
|
74
|
+
if rows.size >= 50
|
75
|
+
transaction { rows.each(&block) }
|
76
|
+
rows.clear
|
77
|
+
end
|
78
|
+
end
|
79
|
+
transaction { rows.each(&block) } unless rows.empty?
|
80
|
+
ensure
|
81
|
+
file.close if file && !file.closed?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Convert square meters to square miles
|
86
|
+
def area_meters_to_miles(square_meters)
|
87
|
+
(square_meters.to_f / (1609.34 ** 2)).round(6)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def imported?
|
92
|
+
status == STATUS_IMPORTED
|
93
|
+
end
|
94
|
+
|
95
|
+
def removed?
|
96
|
+
status == STATUS_REMOVED
|
97
|
+
end
|
98
|
+
|
99
|
+
def manual?
|
100
|
+
status == STATUS_MANUAL
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Combined statistical area (CSA) of multiple metropolitan areas with weak regional
|
6
|
+
# and economic connectoins between them.
|
7
|
+
class CombinedStatisticalArea < BaseRecord
|
8
|
+
|
9
|
+
include Demographics
|
10
|
+
|
11
|
+
self.primary_key = "geoid"
|
12
|
+
|
13
|
+
has_many :core_based_statistical_areas, foreign_key: :csa_geoid, inverse_of: :combined_statistical_area
|
14
|
+
|
15
|
+
validates :geoid, length: {is: 3}
|
16
|
+
validates :name, length: {maximum: 60}
|
17
|
+
validates :land_area, numericality: true, presence: true
|
18
|
+
validates :water_area, numericality: true, presence: true
|
19
|
+
validates :population, numericality: {only_integer: true}, presence: true
|
20
|
+
validates :housing_units, numericality: {only_integer: true}, presence: true
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def load!(uri = nil)
|
24
|
+
location = data_uri(uri || "combined_statistical_areas.csv")
|
25
|
+
import! do
|
26
|
+
load_data_file(location) do |row|
|
27
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
28
|
+
record.name = row["Name"]
|
29
|
+
record.population = row["Population"]
|
30
|
+
record.housing_units = row["Housing Units"]
|
31
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
32
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Core based statistical area composed of one or more counties anchored by an urban center.
|
6
|
+
# Includes both metropolitan (population > 50,000) and micropolitan (population > 10,000
|
7
|
+
# but < 50,000) areas.
|
8
|
+
class CoreBasedStatisticalArea < BaseRecord
|
9
|
+
|
10
|
+
include Demographics
|
11
|
+
|
12
|
+
self.primary_key = "geoid"
|
13
|
+
self.store_full_sti_class = false
|
14
|
+
|
15
|
+
has_many :counties, foreign_key: :cbsa_geoid, inverse_of: :core_based_statistical_area
|
16
|
+
has_many :metropolitan_divisions, foreign_key: :cbsa_geoid, inverse_of: :core_based_statistical_area
|
17
|
+
belongs_to :combined_statistical_area, foreign_key: :csa_geoid, optional: true, inverse_of: :core_based_statistical_areas
|
18
|
+
|
19
|
+
validates :geoid, length: {is: 5}
|
20
|
+
validates :name, length: {maximum: 60}
|
21
|
+
validates :land_area, numericality: true, presence: true
|
22
|
+
validates :water_area, numericality: true, presence: true
|
23
|
+
validates :population, numericality: {only_integer: true}, presence: true
|
24
|
+
validates :housing_units, numericality: {only_integer: true}, presence: true
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def load!(uri = nil)
|
28
|
+
location = data_uri(uri || "core_based_statistical_areas.csv")
|
29
|
+
|
30
|
+
import! do
|
31
|
+
load_data_file(location) do |row|
|
32
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
33
|
+
record.type = (row["Population"].to_i >= 50_000 ? "MetropolitanArea" : "MicropolitanArea")
|
34
|
+
record.name = row["Name"]
|
35
|
+
record.csa_geoid = row["CSA"]
|
36
|
+
record.population = row["Population"]
|
37
|
+
record.housing_units = row["Housing Units"]
|
38
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
39
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
40
|
+
record.lat = row["Latitude"]
|
41
|
+
record.lng = row["Longitude"]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def metropolitan?
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
def micropolitan?
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# County or county equivalent. Counties are composed of zero or more ZCTA's and may
|
6
|
+
# belong to a CBSA. The county's significance withing the CBSA is indicated by the
|
7
|
+
# central flag which indicates if it is a central or outlying county.
|
8
|
+
class County < BaseRecord
|
9
|
+
|
10
|
+
include Demographics
|
11
|
+
|
12
|
+
self.primary_key = "geoid"
|
13
|
+
|
14
|
+
belongs_to :designated_market_area, foreign_key: :dma_code, optional: true, inverse_of: :counties
|
15
|
+
belongs_to :core_based_statistical_area, foreign_key: :cbsa_geoid, optional: true, inverse_of: :counties
|
16
|
+
belongs_to :metropolitan_division, foreign_key: :metropolitan_division_geoid, optional: true, inverse_of: :counties
|
17
|
+
belongs_to :state, foreign_key: :state_code, inverse_of: :counties
|
18
|
+
|
19
|
+
has_many :subdivisions, foreign_key: :county_geoid, inverse_of: :county, class_name: "USGeo::CountySubdivision"
|
20
|
+
|
21
|
+
has_many :zcta_counties, foreign_key: :county_geoid, inverse_of: :county, dependent: :destroy
|
22
|
+
has_many :zctas, through: :zcta_counties
|
23
|
+
|
24
|
+
has_many :urban_area_counties, foreign_key: :county_geoid, inverse_of: :county, dependent: :destroy
|
25
|
+
has_many :urban_areas, through: :urban_area_counties
|
26
|
+
|
27
|
+
has_many :place_counties, foreign_key: :county_geoid, inverse_of: :county, dependent: :destroy
|
28
|
+
has_many :places, through: :place_counties
|
29
|
+
|
30
|
+
validates :geoid, length: {is: 5}
|
31
|
+
validates :name, length: {maximum: 60}
|
32
|
+
validates :short_name, length: {maximum: 30}
|
33
|
+
validates :state_code, length: {is: 2}
|
34
|
+
validates :fips_class_code, length: {is: 2}
|
35
|
+
validates :metropolitan_division_geoid, length: {is: 5}, allow_nil: true
|
36
|
+
validates :cbsa_geoid, length: {is: 5}, allow_nil: true
|
37
|
+
validates :dma_code, length: {is: 3}, allow_nil: true
|
38
|
+
validates :land_area, numericality: true, allow_nil: true
|
39
|
+
validates :water_area, numericality: true, allow_nil: true
|
40
|
+
validates :population, numericality: {only_integer: true}, allow_nil: true
|
41
|
+
validates :housing_units, numericality: {only_integer: true}, allow_nil: true
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def load!(uri = nil)
|
45
|
+
location = data_uri(uri || "counties.csv")
|
46
|
+
|
47
|
+
import! do
|
48
|
+
load_data_file(location) do |row|
|
49
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
50
|
+
record.gnis_id = row["GNIS ID"]
|
51
|
+
record.name = row["Name"]
|
52
|
+
record.short_name = row["Short Name"]
|
53
|
+
record.state_code = row["State"]
|
54
|
+
record.cbsa_geoid = row["CBSA"]
|
55
|
+
record.dma_code = row["DMA"]
|
56
|
+
record.time_zone_name = row["Time Zone"]
|
57
|
+
record.fips_class_code = row["FIPS Class"]
|
58
|
+
record.central = (row["Central"] == "T")
|
59
|
+
record.population = row["Population"]
|
60
|
+
record.housing_units = row["Housing Units"]
|
61
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
62
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
63
|
+
record.lat = row["Latitude"]
|
64
|
+
record.lng = row["Longitude"]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def state_fips
|
72
|
+
geoid[0, 2]
|
73
|
+
end
|
74
|
+
|
75
|
+
def county_fips
|
76
|
+
geoid[2, 3]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return the CBSA only if it is a metropolitan area.
|
80
|
+
def metropolitan_area
|
81
|
+
core_based_statistical_area if core_based_statistical_area && core_based_statistical_area.metropolitan?
|
82
|
+
end
|
83
|
+
|
84
|
+
def time_zone
|
85
|
+
ActiveSupport::TimeZone[time_zone_name] if time_zone_name
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# County subdivision.
|
6
|
+
class CountySubdivision < BaseRecord
|
7
|
+
|
8
|
+
include Demographics
|
9
|
+
|
10
|
+
self.primary_key = "geoid"
|
11
|
+
|
12
|
+
belongs_to :county, foreign_key: :county_geoid, inverse_of: :subdivisions
|
13
|
+
|
14
|
+
validates :geoid, length: {is: 10}
|
15
|
+
validates :name, length: {maximum: 60}
|
16
|
+
validates :fips_class_code, length: {is: 2}
|
17
|
+
validates :land_area, numericality: true, allow_nil: true
|
18
|
+
validates :water_area, numericality: true, allow_nil: true
|
19
|
+
validates :population, numericality: {only_integer: true}, allow_nil: true
|
20
|
+
validates :housing_units, numericality: {only_integer: true}, allow_nil: true
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def load!(uri = nil)
|
24
|
+
location = data_uri(uri || "county_subdivisions.csv")
|
25
|
+
|
26
|
+
import! do
|
27
|
+
load_data_file(location) do |row|
|
28
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
29
|
+
record.gnis_id = row["GNIS ID"]
|
30
|
+
record.county_geoid = row["County GEOID"]
|
31
|
+
record.name = row["Name"]
|
32
|
+
record.fips_class_code = row["FIPS Class"]
|
33
|
+
record.population = row["Population"]
|
34
|
+
record.housing_units = row["Housing Units"]
|
35
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
36
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
37
|
+
record.lat = row["Latitude"]
|
38
|
+
record.lng = row["Longitude"]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# This module is mixed into all models. Note that the area given for land and water
|
6
|
+
# is in square miles.
|
7
|
+
module Demographics
|
8
|
+
|
9
|
+
# Population per square mile.
|
10
|
+
def population_density
|
11
|
+
population.to_f / land_area if population && land_area
|
12
|
+
end
|
13
|
+
|
14
|
+
# Total area of both land an water in square miles
|
15
|
+
def total_area
|
16
|
+
land_area.to_f + water_area.to_f if land_area
|
17
|
+
end
|
18
|
+
|
19
|
+
# The fraction of the area that is composed of land instead of water.
|
20
|
+
def percent_land
|
21
|
+
land_area / total_area if land_area
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Media market (DMA) as defined by the Nielsen Company.
|
6
|
+
class DesignatedMarketArea < BaseRecord
|
7
|
+
|
8
|
+
self.primary_key = "code"
|
9
|
+
|
10
|
+
has_many :counties, foreign_key: :dma_code, inverse_of: :designated_market_area
|
11
|
+
|
12
|
+
validates :code, length: {is: 3}
|
13
|
+
validates :name, length: {maximum: 60}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def load!(uri = nil)
|
17
|
+
location = data_uri(uri || "dmas.csv")
|
18
|
+
|
19
|
+
import! do
|
20
|
+
load_data_file(location) do |row|
|
21
|
+
load_record!(code: row["Code"]) do |record|
|
22
|
+
record.name = row["Name"]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# U.S. regional division composed of states.
|
6
|
+
class Division < BaseRecord
|
7
|
+
|
8
|
+
belongs_to :region, inverse_of: :divisions
|
9
|
+
has_many :states, inverse_of: :division
|
10
|
+
|
11
|
+
validates :name, length: {maximum: 30}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def load!(uri = nil)
|
15
|
+
location = data_uri(uri || "divisions.csv")
|
16
|
+
|
17
|
+
import! do
|
18
|
+
load_data_file(location) do |row|
|
19
|
+
load_record!(id: row["Division ID"]) do |record|
|
20
|
+
record.name = row["Division Name"]
|
21
|
+
record.region_id = row["Region ID"]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Core based statistical area with a population greater then 50,000.
|
6
|
+
class MetropolitanArea < CoreBasedStatisticalArea
|
7
|
+
|
8
|
+
def metropolitan?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def micropolitan?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Division of very large metropolitian areas into groups of approximately 2.5 million people.
|
6
|
+
class MetropolitanDivision < BaseRecord
|
7
|
+
|
8
|
+
include Demographics
|
9
|
+
|
10
|
+
self.primary_key = "geoid"
|
11
|
+
|
12
|
+
has_many :counties, foreign_key: :metropolitan_division_geoid, inverse_of: :metropolitan_division
|
13
|
+
belongs_to :core_based_statistical_area, foreign_key: :cbsa_geoid, optional: true, inverse_of: :metropolitan_divisions
|
14
|
+
|
15
|
+
validates :geoid, length: {is: 5}
|
16
|
+
validates :name, length: {maximum: 60}
|
17
|
+
validates :land_area, numericality: true, presence: true
|
18
|
+
validates :water_area, numericality: true, presence: true
|
19
|
+
validates :population, numericality: {only_integer: true}, presence: true
|
20
|
+
validates :housing_units, numericality: {only_integer: true}, presence: true
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def load!(uri = nil)
|
24
|
+
location = data_uri(uri || "metropolitan_divisions.csv")
|
25
|
+
|
26
|
+
import! do
|
27
|
+
load_data_file(location) do |row|
|
28
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
29
|
+
record.name = row["Name"]
|
30
|
+
record.cbsa_geoid = row["CBSA"]
|
31
|
+
record.population = row["Population"]
|
32
|
+
record.housing_units = row["Housing Units"]
|
33
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
34
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Core based statistical area with a population greater then 10,000 but less than 50,000.
|
6
|
+
class MicropolitanArea < CoreBasedStatisticalArea
|
7
|
+
|
8
|
+
def metropolitan?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def micropolitan?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/us_geo/place.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Division of very large metropolitian areas into groups of approximately 2.5 million people.
|
6
|
+
class Place < BaseRecord
|
7
|
+
|
8
|
+
include Demographics
|
9
|
+
|
10
|
+
self.primary_key = "geoid"
|
11
|
+
|
12
|
+
has_many :zcta_places, foreign_key: :place_geoid, inverse_of: :place, dependent: :destroy
|
13
|
+
has_many :zctas, through: :zcta_places
|
14
|
+
|
15
|
+
has_many :place_counties, foreign_key: :place_geoid, inverse_of: :place, dependent: :destroy
|
16
|
+
has_many :counties, through: :place_counties
|
17
|
+
|
18
|
+
belongs_to :primary_county, foreign_key: :primary_county_geoid, class_name: "USGeo::County"
|
19
|
+
belongs_to :urban_area, foreign_key: :urban_area_geoid, optional: true, class_name: "USGeo::UrbanArea"
|
20
|
+
belongs_to :state, foreign_key: :state_code, inverse_of: :places
|
21
|
+
|
22
|
+
validates :geoid, length: {is: 7}
|
23
|
+
validates :state_code, length: {is: 2}
|
24
|
+
validates :primary_county_geoid, length: {is: 5}
|
25
|
+
validates :urban_area_geoid, length: {is: 5}, allow_nil: true
|
26
|
+
validates :name, length: {maximum: 60}
|
27
|
+
validates :short_name, length: {maximum: 60}
|
28
|
+
validates :fips_class_code, length: {is: 2}
|
29
|
+
validates :land_area, numericality: true, allow_nil: true
|
30
|
+
validates :water_area, numericality: true, allow_nil: true
|
31
|
+
validates :population, numericality: {only_integer: true}, allow_nil: true
|
32
|
+
validates :housing_units, numericality: {only_integer: true}, allow_nil: true
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def load!(uri = nil)
|
36
|
+
location = data_uri(uri || "places.csv")
|
37
|
+
|
38
|
+
import! do
|
39
|
+
load_data_file(location) do |row|
|
40
|
+
load_record!(geoid: row["GEOID"]) do |record|
|
41
|
+
record.gnis_id = row["GNIS ID"]
|
42
|
+
record.name = row["Name"]
|
43
|
+
record.short_name = row["Short Name"]
|
44
|
+
record.state_code = row["State"]
|
45
|
+
record.primary_county_geoid = row["County GEOID"]
|
46
|
+
record.urban_area_geoid = row["Urban Area GEOID"]
|
47
|
+
record.fips_class_code = row["FIPS Class"]
|
48
|
+
record.population = row["Population"]
|
49
|
+
record.housing_units = row["Housing Units"]
|
50
|
+
record.land_area = area_meters_to_miles(row["Land Area"])
|
51
|
+
record.water_area = area_meters_to_miles(row["Water Area"])
|
52
|
+
record.lat = row["Latitude"]
|
53
|
+
record.lng = row["Longitude"]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# Mapping of urban areas to counties they overlap with.
|
6
|
+
class PlaceCounty < BaseRecord
|
7
|
+
|
8
|
+
belongs_to :county, foreign_key: :county_geoid, inverse_of: :place_counties
|
9
|
+
belongs_to :place, foreign_key: :place_geoid, inverse_of: :place_counties
|
10
|
+
|
11
|
+
validates :county_geoid, length: {is: 5}
|
12
|
+
validates :place_geoid, length: {is: 7}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def load!(uri = nil)
|
16
|
+
location = data_uri(uri || "place_counties.csv")
|
17
|
+
|
18
|
+
import! do
|
19
|
+
load_data_file(location) do |row|
|
20
|
+
load_record!(place_geoid: row["Place GEOID"], county_geoid: row["County GEOID"]) do |record|
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module USGeo
|
4
|
+
|
5
|
+
# U.S. region.
|
6
|
+
class Region < BaseRecord
|
7
|
+
|
8
|
+
has_many :divisions, inverse_of: :region
|
9
|
+
has_many :states, inverse_of: :region
|
10
|
+
|
11
|
+
validates :name, length: {maximum: 30}
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def load!(uri = nil)
|
15
|
+
location = data_uri(uri || "divisions.csv")
|
16
|
+
|
17
|
+
import! do
|
18
|
+
load_data_file(location) do |row|
|
19
|
+
load_record!(id: row["Region ID"]) do |record|
|
20
|
+
record.name = row["Region Name"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|