us_geo 1.0.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 +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
|