zip-codes-pl 0.1.1
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/LICENSE.txt +27 -0
- data/README.md +204 -0
- data/README.pl.md +179 -0
- data/data/zip-codes-pl.manifest.json +10 -0
- data/data/zip-codes-pl.tsv +72900 -0
- data/lib/zip-codes-pl.rb +3 -0
- data/lib/zip_codes/pl/builder.rb +113 -0
- data/lib/zip_codes/pl/city.rb +66 -0
- data/lib/zip_codes/pl/configuration.rb +46 -0
- data/lib/zip_codes/pl/errors.rb +11 -0
- data/lib/zip_codes/pl/manifest.rb +39 -0
- data/lib/zip_codes/pl/normalize.rb +30 -0
- data/lib/zip_codes/pl/railtie.rb +11 -0
- data/lib/zip_codes/pl/reader.rb +116 -0
- data/lib/zip_codes/pl/record.rb +50 -0
- data/lib/zip_codes/pl/sources/geonames.rb +127 -0
- data/lib/zip_codes/pl/sources/poczta_polska/client.rb +91 -0
- data/lib/zip_codes/pl/sources/poczta_polska.rb +115 -0
- data/lib/zip_codes/pl/tasks/zip_codes_pl.rake +64 -0
- data/lib/zip_codes/pl/version.rb +7 -0
- data/lib/zip_codes/pl/voivodeship.rb +48 -0
- data/lib/zip_codes/pl.rb +61 -0
- metadata +86 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
module ZipCodes
|
|
7
|
+
module PL
|
|
8
|
+
module Sources
|
|
9
|
+
class PocztaPolska
|
|
10
|
+
class Client
|
|
11
|
+
MAX_ATTEMPTS = 5
|
|
12
|
+
MAX_RETRY_DELAY = 30.0
|
|
13
|
+
|
|
14
|
+
def initialize(
|
|
15
|
+
config:,
|
|
16
|
+
sleeper: ->(seconds) { sleep(seconds) },
|
|
17
|
+
clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) },
|
|
18
|
+
transport: nil
|
|
19
|
+
)
|
|
20
|
+
@request_interval = config.poczta_request_interval
|
|
21
|
+
@sleeper = sleeper
|
|
22
|
+
@clock = clock
|
|
23
|
+
@transport = transport || ->(uri, params) { post(uri, params, config) }
|
|
24
|
+
@last_request_at = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retries rate limiting a bounded number of times. An unbounded loop here
|
|
28
|
+
# would turn sustained throttling into a refresh that never finishes and
|
|
29
|
+
# never says why.
|
|
30
|
+
def post_form(uri, params)
|
|
31
|
+
MAX_ATTEMPTS.times do
|
|
32
|
+
throttle
|
|
33
|
+
response = transport.call(uri, params)
|
|
34
|
+
@last_request_at = clock.call
|
|
35
|
+
|
|
36
|
+
return response if response.is_a?(Net::HTTPSuccess)
|
|
37
|
+
|
|
38
|
+
raise DownloadError, failure_message(response, uri) unless response.is_a?(Net::HTTPTooManyRequests)
|
|
39
|
+
|
|
40
|
+
sleeper.call(retry_after(response))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
raise DownloadError, "Poczta Polska is rate limiting - gave up after #{MAX_ATTEMPTS} attempts for #{uri}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_reader :clock, :request_interval, :sleeper, :transport
|
|
49
|
+
|
|
50
|
+
def throttle
|
|
51
|
+
return if @last_request_at.nil?
|
|
52
|
+
|
|
53
|
+
remaining = request_interval - (clock.call - @last_request_at)
|
|
54
|
+
sleeper.call(remaining) if remaining.positive?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Clamped at both ends: a hostile or broken header must not make the
|
|
58
|
+
# build sleep backwards, which raises, nor park it for an hour.
|
|
59
|
+
def retry_after(response)
|
|
60
|
+
value = response["retry-after"]
|
|
61
|
+
seconds = Float(value, exception: false) || (Time.httpdate(value.to_s) - Time.now)
|
|
62
|
+
seconds.clamp(0.0, MAX_RETRY_DELAY)
|
|
63
|
+
rescue ArgumentError
|
|
64
|
+
[request_interval, 1.0].max
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A moved endpoint is reported rather than followed: this is a POST to a
|
|
68
|
+
# form, and replaying it against an unknown location is not a safe guess.
|
|
69
|
+
def failure_message(response, uri)
|
|
70
|
+
message = "Poczta Polska answered #{response.code} for #{uri}"
|
|
71
|
+
location = response["location"] if response.is_a?(Net::HTTPRedirection)
|
|
72
|
+
location ? "#{message} (redirected to #{location})" : message
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def post(uri, params, config)
|
|
76
|
+
request = Net::HTTP::Post.new(uri)
|
|
77
|
+
request["user-agent"] = config.user_agent
|
|
78
|
+
request.set_form_data(params)
|
|
79
|
+
|
|
80
|
+
Net::HTTP.start(
|
|
81
|
+
uri.host, uri.port,
|
|
82
|
+
use_ssl: uri.scheme == "https",
|
|
83
|
+
open_timeout: config.open_timeout,
|
|
84
|
+
read_timeout: config.read_timeout
|
|
85
|
+
) { |http| http.request(request) }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "uri"
|
|
5
|
+
require_relative "poczta_polska/client"
|
|
6
|
+
|
|
7
|
+
module ZipCodes
|
|
8
|
+
module PL
|
|
9
|
+
module Sources
|
|
10
|
+
class PocztaPolska
|
|
11
|
+
DISTRICTS_URL = "https://www.poczta-polska.pl/wp-content/themes/pp/inc/pna-form/pna-search-district.php"
|
|
12
|
+
COMMUNES_URL = "https://www.poczta-polska.pl/wp-content/themes/pp/inc/pna-form/pna-search-commune.php"
|
|
13
|
+
ATTRIBUTION = "county and commune names: Poczta Polska (https://www.poczta-polska.pl)"
|
|
14
|
+
DEFAULT_REQUEST_INTERVAL = 0.25
|
|
15
|
+
LEGACY_COMMUNES = { "320304" => "Ostrowice" }.freeze
|
|
16
|
+
Names = Data.define(:counties, :communes)
|
|
17
|
+
|
|
18
|
+
def initialize(config: ZipCodes::PL.config, client: nil, voivodeships: Voivodeship.all)
|
|
19
|
+
@client = client || Client.new(config: config)
|
|
20
|
+
@voivodeships = voivodeships
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch
|
|
24
|
+
counties, districts = fetch_counties
|
|
25
|
+
communes = fetch_communes(districts)
|
|
26
|
+
|
|
27
|
+
Names.new(counties: counties.freeze, communes: communes.freeze)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :client, :voivodeships
|
|
33
|
+
|
|
34
|
+
def fetch_counties
|
|
35
|
+
counties = {}
|
|
36
|
+
districts = []
|
|
37
|
+
|
|
38
|
+
voivodeships.each do |voivodeship|
|
|
39
|
+
entries(DISTRICTS_URL, province: voivodeship.teryt_code).each do |entry|
|
|
40
|
+
code, name = entry.values_at("value", "name")
|
|
41
|
+
validate_code!(code, 4, voivodeship.teryt_code)
|
|
42
|
+
add!(counties, code, name)
|
|
43
|
+
districts << [voivodeship.teryt_code, code]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
[counties, districts]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def fetch_communes(districts)
|
|
51
|
+
communes = LEGACY_COMMUNES.dup
|
|
52
|
+
districts.each do |province, district|
|
|
53
|
+
entries(COMMUNES_URL, province: province, district: district).each do |entry|
|
|
54
|
+
terc, name = entry.values_at("value", "name")
|
|
55
|
+
validate_commune_code!(terc, district)
|
|
56
|
+
add!(communes, terc[0, 6], normalize_commune_name(name))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
communes
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def entries(url, params)
|
|
64
|
+
response = client.post_form(URI(url), params)
|
|
65
|
+
# A success with no body is still a success to Net::HTTP, and JSON.parse
|
|
66
|
+
# raises TypeError rather than ParserError on nil.
|
|
67
|
+
payload = JSON.parse(response.body.to_s)
|
|
68
|
+
unless payload.is_a?(Array) && payload.all? { |entry| valid_entry?(entry) }
|
|
69
|
+
raise DownloadError, "malformed Poczta Polska response from #{url}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
payload
|
|
73
|
+
rescue JSON::ParserError
|
|
74
|
+
raise DownloadError, "malformed Poczta Polska JSON from #{url}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Strings are demanded rather than coerced, so a number where a code should
|
|
78
|
+
# be is refused here instead of reaching String methods further down and
|
|
79
|
+
# surfacing as NoMethodError instead of a controlled failure.
|
|
80
|
+
def valid_entry?(entry)
|
|
81
|
+
entry.is_a?(Hash) &&
|
|
82
|
+
entry["name"].is_a?(String) && !entry["name"].empty? &&
|
|
83
|
+
entry["value"].is_a?(String) && !entry["value"].empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def validate_code!(code, length, prefix)
|
|
87
|
+
return if code.match?(/\A\d{#{length}}\z/) && code.start_with?(prefix)
|
|
88
|
+
|
|
89
|
+
raise DownloadError, "malformed Poczta Polska TERYT code: #{code.inspect}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_commune_code!(code, district)
|
|
93
|
+
return if code.match?(/\A\d{6,7}\z/) && code.start_with?(district)
|
|
94
|
+
|
|
95
|
+
raise DownloadError, "malformed Poczta Polska TERYT code: #{code.inspect}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add!(names, code, name)
|
|
99
|
+
normalized = name.strip
|
|
100
|
+
previous = names[code]
|
|
101
|
+
if previous && previous != normalized
|
|
102
|
+
raise DownloadError,
|
|
103
|
+
"conflicting Poczta Polska names for TERYT #{code}: #{previous.inspect} and #{normalized.inspect}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
names[code] = normalized
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def normalize_commune_name(name)
|
|
110
|
+
name.sub(/ \((?:miejska|wiejska|miejsko-wiejska)\)\z/, "")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :zip_codes do
|
|
4
|
+
namespace :pl do
|
|
5
|
+
desc "Download and refresh the postal code dataset (rake zip_codes:pl:update[directory])"
|
|
6
|
+
task :update, [:output_dir] do |_task, args|
|
|
7
|
+
require "zip_codes/pl"
|
|
8
|
+
|
|
9
|
+
result = ZipCodes::PL.update(output_dir: args[:output_dir])
|
|
10
|
+
manifest = result.manifest
|
|
11
|
+
|
|
12
|
+
if result.up_to_date?
|
|
13
|
+
puts "Source unchanged - the dataset is already current: #{result.data_path}"
|
|
14
|
+
else
|
|
15
|
+
puts "Wrote #{manifest.row_count} rows to #{result.data_path}"
|
|
16
|
+
end
|
|
17
|
+
puts "Source: #{manifest.attribution}" if manifest
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Check the bundled dataset against its manifest and the record columns"
|
|
21
|
+
task :verify, [:output_dir] do |_task, args|
|
|
22
|
+
require "zip_codes/pl"
|
|
23
|
+
|
|
24
|
+
ZipCodes::PL.config.output_dir = args[:output_dir] if args[:output_dir]
|
|
25
|
+
path = ZipCodes::PL.config.readable_data_path
|
|
26
|
+
manifest = ZipCodes::PL.manifest
|
|
27
|
+
abort("No dataset at #{path}") unless File.exist?(path)
|
|
28
|
+
abort("No manifest next to #{path}") if manifest.nil?
|
|
29
|
+
|
|
30
|
+
header = File.open(path, &:gets).to_s.chomp.split("\t")
|
|
31
|
+
expected = ZipCodes::PL::Record::COLUMNS.map(&:to_s)
|
|
32
|
+
abort("Header is #{header.inspect}, expected #{expected.inspect}") unless header == expected
|
|
33
|
+
|
|
34
|
+
rows = ZipCodes::PL.reader.count
|
|
35
|
+
abort("Manifest claims #{manifest.row_count} rows, the file holds #{rows}") unless rows == manifest.row_count
|
|
36
|
+
|
|
37
|
+
known = ZipCodes::PL::Voivodeship.all.to_h { |voivodeship| [voivodeship.teryt_code, true] }
|
|
38
|
+
unknown = ZipCodes::PL.reader.reject { |record| known.key?(record.voivodeship_teryt) }
|
|
39
|
+
abort("Unknown voivodeship codes: #{unknown.first(5).map(&:voivodeship_teryt).uniq.inspect}") if unknown.any?
|
|
40
|
+
|
|
41
|
+
puts "Dataset consistent: #{rows} rows, #{known.size} voivodeships, built #{manifest.built_at}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "Show the state of the built dataset"
|
|
45
|
+
task :info, [:output_dir] do |_task, args|
|
|
46
|
+
require "zip_codes/pl"
|
|
47
|
+
|
|
48
|
+
ZipCodes::PL.config.output_dir = args[:output_dir] if args[:output_dir]
|
|
49
|
+
manifest = ZipCodes::PL.manifest
|
|
50
|
+
|
|
51
|
+
if manifest.nil?
|
|
52
|
+
puts "No dataset in #{ZipCodes::PL.config.output_dir} - run rake zip_codes:pl:update"
|
|
53
|
+
next
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
puts "File: #{ZipCodes::PL.config.data_path}"
|
|
57
|
+
puts "Rows: #{manifest.row_count}"
|
|
58
|
+
puts "Built: #{manifest.built_at}"
|
|
59
|
+
puts "Source: #{manifest.source_url}"
|
|
60
|
+
puts "Modified: #{manifest.last_modified}"
|
|
61
|
+
puts "Attribution: #{manifest.attribution}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ZipCodes
|
|
4
|
+
module PL
|
|
5
|
+
# The sixteen voivodeships, pairing the GeoNames admin1 code that appears in
|
|
6
|
+
# the source export with the official TERYT code and the Polish name.
|
|
7
|
+
#
|
|
8
|
+
# GeoNames ships English and inconsistent labels for every one of them
|
|
9
|
+
# ("Lower Silesia", "Warmia-Masuria", "Łódź Voivodeship"), so the name used by
|
|
10
|
+
# this gem is defined here rather than read from the download. The
|
|
11
|
+
# geonames_code => teryt_code pairing is verified against the source data by
|
|
12
|
+
# the test suite, because it is the join key everything else hangs off.
|
|
13
|
+
class Voivodeship < Data.define(:teryt_code, :geonames_code, :name, :slug)
|
|
14
|
+
ALL = [
|
|
15
|
+
new(teryt_code: "02", geonames_code: "72", name: "dolnośląskie", slug: "dolnoslaskie"),
|
|
16
|
+
new(teryt_code: "04", geonames_code: "73", name: "kujawsko-pomorskie", slug: "kujawsko-pomorskie"),
|
|
17
|
+
new(teryt_code: "06", geonames_code: "75", name: "lubelskie", slug: "lubelskie"),
|
|
18
|
+
new(teryt_code: "08", geonames_code: "76", name: "lubuskie", slug: "lubuskie"),
|
|
19
|
+
new(teryt_code: "10", geonames_code: "74", name: "łódzkie", slug: "lodzkie"),
|
|
20
|
+
new(teryt_code: "12", geonames_code: "77", name: "małopolskie", slug: "malopolskie"),
|
|
21
|
+
new(teryt_code: "14", geonames_code: "78", name: "mazowieckie", slug: "mazowieckie"),
|
|
22
|
+
new(teryt_code: "16", geonames_code: "79", name: "opolskie", slug: "opolskie"),
|
|
23
|
+
new(teryt_code: "18", geonames_code: "80", name: "podkarpackie", slug: "podkarpackie"),
|
|
24
|
+
new(teryt_code: "20", geonames_code: "81", name: "podlaskie", slug: "podlaskie"),
|
|
25
|
+
new(teryt_code: "22", geonames_code: "82", name: "pomorskie", slug: "pomorskie"),
|
|
26
|
+
new(teryt_code: "24", geonames_code: "83", name: "śląskie", slug: "slaskie"),
|
|
27
|
+
new(teryt_code: "26", geonames_code: "84", name: "świętokrzyskie", slug: "swietokrzyskie"),
|
|
28
|
+
new(teryt_code: "28", geonames_code: "85", name: "warmińsko-mazurskie", slug: "warminsko-mazurskie"),
|
|
29
|
+
new(teryt_code: "30", geonames_code: "86", name: "wielkopolskie", slug: "wielkopolskie"),
|
|
30
|
+
new(teryt_code: "32", geonames_code: "87", name: "zachodniopomorskie", slug: "zachodniopomorskie")
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
BY_GEONAMES_CODE = ALL.to_h { |voivodeship| [voivodeship.geonames_code, voivodeship] }.freeze
|
|
34
|
+
BY_TERYT_CODE = ALL.to_h { |voivodeship| [voivodeship.teryt_code, voivodeship] }.freeze
|
|
35
|
+
BY_LOOKUP_NAME = ALL.flat_map { |v| [[v.name, v], [v.slug, v]] }.to_h.freeze
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
def all = ALL
|
|
39
|
+
|
|
40
|
+
def find_by_geonames_code(code) = BY_GEONAMES_CODE[code.to_s]
|
|
41
|
+
|
|
42
|
+
def find_by_teryt_code(code) = BY_TERYT_CODE[code.to_s]
|
|
43
|
+
|
|
44
|
+
def find_by_name(name) = BY_LOOKUP_NAME[name.to_s.strip.downcase]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/zip_codes/pl.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "pl/errors"
|
|
4
|
+
require_relative "pl/version"
|
|
5
|
+
require_relative "pl/normalize"
|
|
6
|
+
require_relative "pl/voivodeship"
|
|
7
|
+
require_relative "pl/record"
|
|
8
|
+
require_relative "pl/city"
|
|
9
|
+
require_relative "pl/sources/geonames"
|
|
10
|
+
require_relative "pl/sources/poczta_polska"
|
|
11
|
+
require_relative "pl/configuration"
|
|
12
|
+
require_relative "pl/manifest"
|
|
13
|
+
require_relative "pl/builder"
|
|
14
|
+
require_relative "pl/reader"
|
|
15
|
+
require_relative "pl/railtie" if defined?(Rails::Railtie)
|
|
16
|
+
|
|
17
|
+
module ZipCodes
|
|
18
|
+
module PL
|
|
19
|
+
class << self
|
|
20
|
+
def config
|
|
21
|
+
@config ||= Configuration.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def configure
|
|
25
|
+
yield config
|
|
26
|
+
reset!
|
|
27
|
+
config
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Drops the loaded dataset so the next read picks up a freshly built file.
|
|
31
|
+
def reset!
|
|
32
|
+
@reader = nil
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Streams the file. Holds nothing, costs about 15 ms per call.
|
|
37
|
+
def reader
|
|
38
|
+
@reader ||= Reader.new(config.readable_data_path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update(output_dir: nil)
|
|
42
|
+
config.output_dir = output_dir if output_dir
|
|
43
|
+
Builder.new(config: config).call.tap { reset! }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def manifest = Manifest.read(config.readable_manifest_path)
|
|
47
|
+
|
|
48
|
+
def find_by_postal_code(code) = reader.find_by_postal_code(code)
|
|
49
|
+
|
|
50
|
+
def find_by_city(name, voivodeship: nil) = reader.find_by_city(name, voivodeship: voivodeship)
|
|
51
|
+
|
|
52
|
+
def search_by_city(fragment, voivodeship: nil) = reader.search_by_city(fragment, voivodeship: voivodeship)
|
|
53
|
+
|
|
54
|
+
def cities = reader.cities
|
|
55
|
+
|
|
56
|
+
def each_record(&) = reader.each(&)
|
|
57
|
+
|
|
58
|
+
def voivodeships = Voivodeship.all
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: zip-codes-pl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Łukasz Kerl
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rubyzip
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.3'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.3'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4'
|
|
32
|
+
description: Builds and refreshes a local dataset of Polish postal codes (PNA), including
|
|
33
|
+
locality, voivodeship, county and commune TERYT codes, and coordinates. PNA and
|
|
34
|
+
coordinates come from GeoNames (CC BY 4.0), while administrative names come from
|
|
35
|
+
Poczta Polska.
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- LICENSE.txt
|
|
41
|
+
- README.md
|
|
42
|
+
- README.pl.md
|
|
43
|
+
- data/zip-codes-pl.manifest.json
|
|
44
|
+
- data/zip-codes-pl.tsv
|
|
45
|
+
- lib/zip-codes-pl.rb
|
|
46
|
+
- lib/zip_codes/pl.rb
|
|
47
|
+
- lib/zip_codes/pl/builder.rb
|
|
48
|
+
- lib/zip_codes/pl/city.rb
|
|
49
|
+
- lib/zip_codes/pl/configuration.rb
|
|
50
|
+
- lib/zip_codes/pl/errors.rb
|
|
51
|
+
- lib/zip_codes/pl/manifest.rb
|
|
52
|
+
- lib/zip_codes/pl/normalize.rb
|
|
53
|
+
- lib/zip_codes/pl/railtie.rb
|
|
54
|
+
- lib/zip_codes/pl/reader.rb
|
|
55
|
+
- lib/zip_codes/pl/record.rb
|
|
56
|
+
- lib/zip_codes/pl/sources/geonames.rb
|
|
57
|
+
- lib/zip_codes/pl/sources/poczta_polska.rb
|
|
58
|
+
- lib/zip_codes/pl/sources/poczta_polska/client.rb
|
|
59
|
+
- lib/zip_codes/pl/tasks/zip_codes_pl.rake
|
|
60
|
+
- lib/zip_codes/pl/version.rb
|
|
61
|
+
- lib/zip_codes/pl/voivodeship.rb
|
|
62
|
+
homepage: https://github.com/carlos-1410/zip-codes-pl
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
homepage_uri: https://github.com/carlos-1410/zip-codes-pl
|
|
67
|
+
source_code_uri: https://github.com/carlos-1410/zip-codes-pl
|
|
68
|
+
rubygems_mfa_required: 'true'
|
|
69
|
+
rdoc_options: []
|
|
70
|
+
require_paths:
|
|
71
|
+
- lib
|
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 3.2.0
|
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
requirements: []
|
|
83
|
+
rubygems_version: 3.6.9
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Polish postal codes, localities, voivodeships, and coordinates
|
|
86
|
+
test_files: []
|