Module: Psgc
- Defined in:
- lib/psgc.rb,
lib/psgc/cli.rb,
lib/generators/psgc/seed_generator.rb
Defined Under Namespace
Modules: Generators
Classes: CLI, Error
Constant Summary
collapse
- VERSION =
"0.2.0"
Class Method Summary
collapse
-
.barangays ⇒ Object
-
.cities_municipalities ⇒ Object
-
.collection_for(level) ⇒ Object
-
.data_dir ⇒ Object
-
.export_csv(level: :regions, include_headers: true) ⇒ Object
-
.export_geojson(level: :regions) ⇒ String
Note: geometry is null because PSGC data has no geographic coordinates.
-
.export_yaml(level: :regions) ⇒ Object
-
.find(collection, code: nil, name: nil, **attrs) ⇒ Hash?
Finds first match in collection using AND semantics.
-
.find_barangay(code: nil, name: nil, city_municipality_code: nil) ⇒ Object
-
.find_city_municipality(code: nil, name: nil, province_code: nil) ⇒ Object
-
.find_province(code: nil, name: nil, region_code: nil) ⇒ Object
-
.find_region(code: nil, name: nil) ⇒ Object
-
.hierarchy(code) ⇒ Hash{Symbol => Hash, nil}
Traverses PSGC hierarchy for a given code.
-
.load_data(type) ⇒ Object
-
.normalize_level(level) ⇒ Symbol
Normalized level (:cities -> :cities_municipalities).
-
.provinces ⇒ Object
-
.regions ⇒ Object
-
.search(query, levels: nil, limit: nil) ⇒ Hash{Symbol => Array}
Hash with requested level keys and matching records.
-
.stats ⇒ Hash{Symbol => Integer}
-
.valid?(code) ⇒ Boolean
True if code exists in any level.
Class Method Details
.barangays ⇒ Object
28
29
30
|
# File 'lib/psgc.rb', line 28
def self.barangays
@barangays || @data_mutex.synchronize { @barangays ||= load_data("barangays") }
end
|
.cities_municipalities ⇒ Object
24
25
26
|
# File 'lib/psgc.rb', line 24
def self.cities_municipalities
@cities_municipalities || @data_mutex.synchronize { @cities_municipalities ||= load_data("cities_municipalities") }
end
|
.collection_for(level) ⇒ Object
195
196
197
198
199
200
201
202
203
204
205
|
# File 'lib/psgc.rb', line 195
def self.collection_for(level)
normalized = normalize_level(level)
case normalized
when :regions then regions
when :provinces then provinces
when :cities_municipalities then cities_municipalities
when :barangays then barangays
else raise ArgumentError, "unknown level: #{level}. Valid: :regions, :provinces, :cities, :cities_municipalities, :barangays"
end
end
|
.data_dir ⇒ Object
10
11
12
|
# File 'lib/psgc.rb', line 10
def self.data_dir
File.expand_path("../../data", __FILE__)
end
|
.export_csv(level: :regions, include_headers: true) ⇒ Object
207
208
209
210
211
212
213
214
215
216
217
218
219
|
# File 'lib/psgc.rb', line 207
def self.export_csv(level: :regions, include_headers: true)
require "csv"
collection = collection_for(level)
= collection.first.keys.map(&:to_s)
CSV.generate do |csv|
csv << if
collection.each do |item|
csv << .map { |h| item[h.to_sym] }
end
end
end
|
.export_geojson(level: :regions) ⇒ String
Note: geometry is null because PSGC data has no geographic coordinates.
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
# File 'lib/psgc.rb', line 230
def self.export_geojson(level: :regions)
collection = collection_for(level)
features = collection.map do |item|
props = item.dup
props.delete(:code)
{
type: "Feature",
id: item[:code],
geometry: nil,
properties: props
}
end
{
type: "FeatureCollection",
features: features
}.to_json
end
|
.export_yaml(level: :regions) ⇒ Object
221
222
223
224
225
226
|
# File 'lib/psgc.rb', line 221
def self.export_yaml(level: :regions)
require "yaml"
collection = collection_for(level)
{ normalize_level(level) => collection }.to_yaml
end
|
.find(collection, code: nil, name: nil, **attrs) ⇒ Hash?
Finds first match in collection using AND semantics. All non-nil criteria must match for a result. Name matching is case-insensitive substring. Code attributes (*_code) use bidirectional prefix matching:
"v.start_with?(item[k]) || item[k].start_with?(v)"
e.g., region_code "14" matches stored "1400000000" and vice versa.
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
# File 'lib/psgc.rb', line 71
def self.find(collection, code: nil, name: nil, **attrs)
collection.each do |item|
matches = true
matches &&= item[:code] == code if code
matches &&= item[:name].to_s.downcase.include?(name.to_s.downcase) if name
attrs.each do |k, v|
next unless v
if k.to_s.end_with?("_code")
matches &&= v.to_s.start_with?(item[k].to_s) || item[k].to_s.start_with?(v.to_s)
else
matches &&= item[k] == v
end
end
return item if matches
end
nil
end
|
.find_barangay(code: nil, name: nil, city_municipality_code: nil) ⇒ Object
54
55
56
57
|
# File 'lib/psgc.rb', line 54
def self.find_barangay(code: nil, name: nil, city_municipality_code: nil)
return nil unless code || name || city_municipality_code
find(barangays, code: code, name: name, city_municipality_code: city_municipality_code)
end
|
.find_city_municipality(code: nil, name: nil, province_code: nil) ⇒ Object
49
50
51
52
|
# File 'lib/psgc.rb', line 49
def self.find_city_municipality(code: nil, name: nil, province_code: nil)
return nil unless code || name || province_code
find(cities_municipalities, code: code, name: name, province_code: province_code)
end
|
.find_province(code: nil, name: nil, region_code: nil) ⇒ Object
44
45
46
47
|
# File 'lib/psgc.rb', line 44
def self.find_province(code: nil, name: nil, region_code: nil)
return nil unless code || name || region_code
find(provinces, code: code, name: name, region_code: region_code)
end
|
.find_region(code: nil, name: nil) ⇒ Object
39
40
41
42
|
# File 'lib/psgc.rb', line 39
def self.find_region(code: nil, name: nil)
return nil unless code || name
find(regions, code: code, name: name)
end
|
.hierarchy(code) ⇒ Hash{Symbol => Hash, nil}
Traverses PSGC hierarchy for a given code. Uses prefix matching because parent codes are prefixes of child codes (e.g., city code is prefix of barangay code).
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
# File 'lib/psgc.rb', line 95
def self.hierarchy(code)
return nil unless code.to_s.match?(/^\d{10}$/)
code = code.to_s
result = { code: code }
if code.end_with?("000000")
result[:region] = find_region(code: code)
elsif code.end_with?("0000")
result[:province] = find_province(code: code)
result[:city_municipality] = find_city_municipality(code: code) unless result[:province]
elsif code.end_with?("00")
result[:city_municipality] = find_city_municipality(code: code)
else
result[:barangay] = find_barangay(code: code)
end
if result[:barangay]
city = cities_municipalities.find { |c| c[:code].start_with?(result[:barangay][:city_municipality_code]) }
result[:city_municipality] = city
if city
province = provinces.find { |p| p[:code].start_with?(city[:province_code]) }
result[:province] = province
end
elsif result[:city_municipality]
province = provinces.find { |p| p[:code].start_with?(result[:city_municipality][:province_code]) }
result[:province] = province
end
if result[:province]
region = regions.find { |r| r[:code].start_with?(result[:province][:region_code]) }
result[:region] = region
end
result[:region] ||= regions.find { |r| r[:code].start_with?(code[0, 2]) }
result
end
|
.load_data(type) ⇒ Object
32
33
34
35
36
37
|
# File 'lib/psgc.rb', line 32
def self.load_data(type)
file_path = File.join(data_dir, "#{type}.json")
return [] unless File.exist?(file_path)
JSON.parse(File.read(file_path), symbolize_names: true)
end
|
.normalize_level(level) ⇒ Symbol
Returns normalized level (:cities -> :cities_municipalities).
188
189
190
191
192
193
|
# File 'lib/psgc.rb', line 188
def self.normalize_level(level)
case level
when :cities then :cities_municipalities
else level
end
end
|
.provinces ⇒ Object
20
21
22
|
# File 'lib/psgc.rb', line 20
def self.provinces
@provinces || @data_mutex.synchronize { @provinces ||= load_data("provinces") }
end
|
.regions ⇒ Object
16
17
18
|
# File 'lib/psgc.rb', line 16
def self.regions
@regions || @data_mutex.synchronize { @regions ||= load_data("regions") }
end
|
.search(query, levels: nil, limit: nil) ⇒ Hash{Symbol => Array}
Returns hash with requested level keys and matching records.
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
# File 'lib/psgc.rb', line 138
def self.search(query, levels: nil, limit: nil)
return {} unless query && !query.to_s.strip.empty?
query_down = query.to_s.downcase
levels ||= [:regions, :provinces, :cities_municipalities, :barangays]
result = {}
levels.each do |level|
collection = collection_for(level)
matches = collection.select { |item| item[:name].to_s.downcase.include?(query_down) }
matches = matches.first(limit) if limit
result[level] = matches
end
result
end
|
.stats ⇒ Hash{Symbol => Integer}
177
178
179
180
181
182
183
184
|
# File 'lib/psgc.rb', line 177
def self.stats
{
regions: regions.length,
provinces: provinces.length,
cities_municipalities: cities_municipalities.length,
barangays: barangays.length
}
end
|
.valid?(code) ⇒ Boolean
Returns true if code exists in any level.
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# File 'lib/psgc.rb', line 158
def self.valid?(code)
return false unless code && code.to_s.match?(/^\d{10}$/)
code_str = code.to_s
if code_str.end_with?("000000")
regions.any? { |r| r[:code] == code_str }
elsif code_str.end_with?("0000")
provinces.any? { |p| p[:code] == code_str } ||
cities_municipalities.any? { |c| c[:code] == code_str }
elsif code_str.end_with?("00")
cities_municipalities.any? { |c| c[:code] == code_str } ||
barangays.any? { |b| b[:code] == code_str }
else
barangays.any? { |b| b[:code] == code_str }
end
end
|