swissmatch-location 0.0.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.
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ module SwissMatch
6
+
7
+ # Represents a swiss canton.
8
+ class Canton
9
+
10
+ # @return [String]
11
+ # The two letter abbreviation of the cantons name as used on license plates.
12
+ attr_reader :license_tag
13
+
14
+ # @param [String] license_tag
15
+ # The two letter abbreviation of the cantons name as used on license plates.
16
+ # @param [String] name
17
+ # The official name of the canton in the local language.
18
+ # @param [String] name_de
19
+ # The official name of the canton in german.
20
+ # @param [String] name_fr
21
+ # The official name of the canton in french.
22
+ # @param [String] name_it
23
+ # The official name of the canton in italian.
24
+ # @param [String] name_rt
25
+ # The official name of the canton in rhaeto-romanic.
26
+ def initialize(license_tag, name, name_de, name_fr, name_it, name_rt)
27
+ @license_tag = license_tag
28
+ @name = name
29
+ @names = {
30
+ :de => name_de,
31
+ :fr => name_fr,
32
+ :it => name_it,
33
+ :rt => name_rt,
34
+ }
35
+ end
36
+
37
+ # The name of the canton. If no language is passed, the local language is used.
38
+ #
39
+ # @param [Symbol, nil] language
40
+ # One of nil, :de, :fr, :it or :rt
41
+ def name(language=nil)
42
+ language ? @names[language] : @name
43
+ end
44
+
45
+ # @return [Array<String>]
46
+ # The name of this zip code in all languages (only unique names)
47
+ def names
48
+ @names.values.uniq
49
+ end
50
+
51
+ # @return [Hash]
52
+ # All properties of the canton as a hash.
53
+ def to_hash
54
+ {
55
+ :name => @name,
56
+ :name_de => @names[:de],
57
+ :name_fr => @names[:fr],
58
+ :name_it => @names[:it],
59
+ :name_rt => @names[:rt],
60
+ :license_tag => @license_tag
61
+ }
62
+ end
63
+
64
+ alias to_s name
65
+
66
+ # @private
67
+ # @see Object#hash
68
+ def hash
69
+ [self.class, @license_tag].hash
70
+ end
71
+
72
+ # @private
73
+ # @see Object#eql?
74
+ def eql?(other)
75
+ self.class == other.class && @license_tag == other.license_tag
76
+ end
77
+
78
+ # @return [String]
79
+ # @see Object#inspect
80
+ def inspect
81
+ sprintf "\#<%s:%014x %s>", self.class, object_id, self
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'swissmatch/canton'
6
+
7
+
8
+
9
+ module SwissMatch
10
+
11
+ # Represents a collection of swiss cantons and provides a query interface.
12
+ class Cantons
13
+ include Enumerable
14
+
15
+ # @param [Array<SwissMatch::Canton>] cantons
16
+ # The SwissMatch::Canton objects this SwissMatch::Cantons should contain
17
+ def initialize(cantons)
18
+ @cantons = cantons
19
+ @by_license_tag = {}
20
+ @by_name = {}
21
+
22
+ cantons.each do |canton|
23
+ @by_license_tag[canton.license_tag] = canton
24
+ canton.names.each do |name|
25
+ @by_name[name] = canton
26
+ end
27
+ end
28
+ end
29
+
30
+ # Calls the block once for every SwissMatch::Canton in this SwissMatch::Cantons
31
+ # instance, passing that canton as a parameter.
32
+ # The order is the same as the instance was constructed.
33
+ #
34
+ # @yield [canton]
35
+ # @yieldparam [SwissMatch::Canton] canton
36
+ #
37
+ # @return [self] Returns self
38
+ def each(&block)
39
+ @cantons.each(&block)
40
+ self
41
+ end
42
+
43
+ # Calls the block once for every SwissMatch::Canton in this SwissMatch::Cantons
44
+ # instance, passing that canton as a parameter.
45
+ # The order is the reverse of what the instance was constructed.
46
+ #
47
+ # @yield [canton]
48
+ # @yieldparam [SwissMatch::Canton] canton
49
+ #
50
+ # @return [self] Returns self
51
+ def reverse_each(&block)
52
+ @cantons.reverse_each(&block)
53
+ self
54
+ end
55
+
56
+ # @return [SwissMatch::Cantons]
57
+ # A SwissMatch::Cantons collection with all SwissMatch::Canton objects for which the block
58
+ # returned true (or a trueish value)
59
+ def select(*args, &block)
60
+ Cantons.new(@cantons.select(*args, &block))
61
+ end
62
+
63
+ # @return [SwissMatch::Cantons]
64
+ # A SwissMatch::Cantons collection with all SwissMatch::Canton objects for which the block
65
+ # returned false (or a falseish value)
66
+ def reject(*args, &block)
67
+ Cantons.new(@cantons.reject(*args, &block))
68
+ end
69
+
70
+ # @see Enumerable#sort
71
+ #
72
+ # @return [SwissMatch::Cantons]
73
+ # A SwissMatch::Cantons collection sorted by the block
74
+ def sort(*args, &block)
75
+ Cantons.new(@cantons.sort(*args, &block))
76
+ end
77
+
78
+ # @see Enumerable#sort_by
79
+ #
80
+ # @return [SwissMatch::Cantons]
81
+ # A SwissMatch::Cantons collection sorted by the block
82
+ def sort_by(*args, &block)
83
+ Cantons.new(@cantons.sort_by(*args, &block))
84
+ end
85
+
86
+ # @return [SwissMatch::Canton]
87
+ # The canton with the given license tag or name (in any language)
88
+ def [](key)
89
+ @by_license_tag[key] || @by_name[key]
90
+ end
91
+
92
+ # @return [SwissMatch::Canton]
93
+ # The canton with the given license tag.
94
+ def by_license_tag(tag)
95
+ @by_license_tag[tag]
96
+ end
97
+
98
+ # @return [SwissMatch::Canton]
99
+ # The canton with the given name (any language)
100
+ def by_name(name)
101
+ @by_name[name]
102
+ end
103
+
104
+ # @return [Integer] The number of SwissMatch::Canton objects in this collection.
105
+ def size
106
+ @cantons.size
107
+ end
108
+
109
+ # @return [Array<SwissMatch::Canton>]
110
+ # An Array with all SwissMatch::Canton objects in this SwissMatch::Cantons.
111
+ def to_a
112
+ @cantons.dup
113
+ end
114
+
115
+ # @private
116
+ # @see Object#inspect
117
+ def inspect
118
+ sprintf "\#<%s:%x size: %d>", self.class, object_id>>1, size
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ module SwissMatch
6
+
7
+ # Represents a collection of SwissMatch::Community objects and provides a query
8
+ # interface.
9
+ class Communities
10
+ include Enumerable
11
+
12
+ # @param [Array<SwissMatch::Community>] communities
13
+ # The SwissMatch::Community objects this SwissMatch::Communities should contain
14
+ def initialize(communities)
15
+ @communities = communities
16
+ reset!
17
+ end
18
+
19
+ # @private
20
+ # Reinitialize all caching instance variables
21
+ def reset!
22
+ @by_community_number = Hash[@communities.map { |c| [c.community_number, c] }]
23
+ @by_name = {}
24
+ @communities.each do |community|
25
+ @by_name[community.name.to_s] = community
26
+ end
27
+
28
+ unless @communities.size == @by_name.size
29
+ raise "ImplementationError: The author assumed communities to have a unique name, which doesn't seem to be the case anymore"
30
+ end
31
+
32
+ self
33
+ end
34
+
35
+ # Calls the block once for every SwissMatch::Community in this SwissMatch::Communities
36
+ # instance, passing that community as a parameter.
37
+ # The order is the same as the instance was constructed.
38
+ #
39
+ # @yield [community]
40
+ # @yieldparam [SwissMatch::Community] community
41
+ #
42
+ # @return [self] Returns self
43
+ def each(&block)
44
+ @communities.each(&block)
45
+ self
46
+ end
47
+
48
+ # Calls the block once for every SwissMatch::Community in this SwissMatch::Communities
49
+ # instance, passing that community as a parameter.
50
+ # The order is the reverse of what the instance was constructed.
51
+ #
52
+ # @yield [community]
53
+ # @yieldparam [SwissMatch::Community] community
54
+ #
55
+ # @return [self] Returns self
56
+ def reverse_each(&block)
57
+ @communities.reverse_each(&block)
58
+ self
59
+ end
60
+
61
+ # @return [SwissMatch::Communities]
62
+ # A SwissMatch::Communities collection with all SwissMatch::Community objects for which the block
63
+ # returned true (or a trueish value)
64
+ def select(*args, &block)
65
+ Communities.new(@communities.select(*args, &block))
66
+ end
67
+
68
+ # @return [SwissMatch::Communities]
69
+ # A SwissMatch::Communities collection with all SwissMatch::Community objects for which the block
70
+ # returned false (or a falseish value)
71
+ def reject(*args, &block)
72
+ Communities.new(@communities.reject(*args, &block))
73
+ end
74
+
75
+ # @see Enumerable#sort
76
+ #
77
+ # @return [SwissMatch::Communities]
78
+ # A SwissMatch::Communities collection sorted by the block
79
+ def sort(*args, &block)
80
+ Communities.new(@communities.sort(*args, &block))
81
+ end
82
+
83
+ # @see Enumerable#sort_by
84
+ #
85
+ # @return [SwissMatch::Communities]
86
+ # A SwissMatch::Communities collection sorted by the block
87
+ def sort_by(*args, &block)
88
+ Communities.new(@communities.sort_by(*args, &block))
89
+ end
90
+
91
+ # @return [SwissMatch::Community]
92
+ # The community with the given name or community number.
93
+ def [](key)
94
+ @by_name[key] || @by_community_number[key.to_i]
95
+ end
96
+
97
+ # @return [SwissMatch::Community]
98
+ # The community with the given community number (also known as BFSNR).
99
+ def by_community_number(number)
100
+ @by_community_number[number]
101
+ end
102
+
103
+ # @return [SwissMatch::Community]
104
+ # The community with the given name.
105
+ def by_name(name)
106
+ @by_name[name]
107
+ end
108
+
109
+ # @return [Integer] The number of SwissMatch::Community objects in this collection.
110
+ def size
111
+ @communities.size
112
+ end
113
+
114
+ # @return [Array<SwissMatch::Community>]
115
+ # An Array with all SwissMatch::Community objects in this SwissMatch::Communities.
116
+ def to_a
117
+ @communities.dup
118
+ end
119
+
120
+ # @private
121
+ # @see Object#inspect
122
+ def inspect
123
+ sprintf "\#<%s:%x size: %d>", self.class, object_id>>1, size
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ module SwissMatch
6
+
7
+ # Represents a swiss community.
8
+ # Swiss communities are identified by their community number (BFSNR).
9
+ class Community
10
+
11
+ # @return [Integer]
12
+ # A unique, never recycled identification number.
13
+ # Also known as BFSNR.
14
+ attr_reader :community_number
15
+
16
+ # @return [String]
17
+ # The official name of the community.
18
+ attr_reader :name
19
+
20
+ # @return [SwissMatch::Canton]
21
+ # The canton this community belongs to.
22
+ attr_reader :canton
23
+
24
+ # @return [SwissMatch::Community]
25
+ # The community this community is considered to be an agglomeration of.
26
+ # Note that a main community will reference itself.
27
+ attr_reader :agglomeration
28
+
29
+ # @param [Integer] community_number
30
+ # The identification number of the community, also known as BFSNR.
31
+ # @param [String] name
32
+ # The official name of the community
33
+ # @param [SwissMatch::Canton] canton
34
+ # The canton this community belongs to
35
+ # @param [SwissMatch::Community] agglomeration
36
+ # The community this community is considered to be an agglomeration of.
37
+ # Note that a main community will reference itself.
38
+ def initialize(community_number, name, canton, agglomeration)
39
+ @community_number = community_number
40
+ @name = name
41
+ @canton = canton
42
+ @agglomeration = agglomeration == :self ? self : agglomeration
43
+ end
44
+
45
+ alias to_s name
46
+
47
+ # @param [Boolean] retain_references
48
+ # If set to false, :agglomeration will be set to the community_number and
49
+ # :canton to the canton's license_tag.
50
+ #
51
+ # @return [Hash]
52
+ # All properties of the community as a hash.
53
+ def to_hash(retain_references=false)
54
+ if retain_references then
55
+ canton = @canton
56
+ agglomeration = @agglomeration
57
+ else
58
+ canton = @canton && @canton.license_tag
59
+ agglomeration = @agglomeration && @agglomeration.community_number
60
+ end
61
+
62
+ {
63
+ :community_number => @community_number,
64
+ :name => @name,
65
+ :canton => canton,
66
+ :agglomeration => agglomeration,
67
+ }
68
+ end
69
+
70
+ # @private
71
+ # @see Object#hash
72
+ def hash
73
+ [self.class, @community_number].hash
74
+ end
75
+
76
+ # @private
77
+ # @see Object#eql?
78
+ def eql?(other)
79
+ self.class == other.class && @community_number == other.community_number
80
+ end
81
+
82
+ # @return [String]
83
+ # @see Object#inspect
84
+ def inspect
85
+ sprintf "\#<%s:%014x %s, BFSNR %d>", self.class, object_id, self, @community_number
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,333 @@
1
+ # encoding: utf-8
2
+
3
+
4
+
5
+ require 'swissmatch/loaderror'
6
+
7
+
8
+
9
+ module SwissMatch
10
+
11
+ # Deals with retrieving and updating the files provided by the swiss postal service,
12
+ # and loading the data from them.
13
+ #
14
+ # @todo
15
+ # The current handling of the urls is not clean. I don't know yet how the urls will
16
+ # change over iterations.
17
+ class DataFiles
18
+
19
+ # Used to generate the regular expressions used to parse the data files.
20
+ # Generates a regular expression, that matches +size+ tab separated fields,
21
+ # delimited by \r\n.
22
+ # @private
23
+ def self.generate_expression(size)
24
+ /^#{Array.new(size) { '([^\t]*)' }.join('\t')}\r\n/
25
+ end
26
+
27
+ # Regular expressions used to parse the different files.
28
+ # @private
29
+ Expressions = {
30
+ :community => generate_expression(4),
31
+ :zip_2 => generate_expression(6),
32
+ :zip_1 => generate_expression(13),
33
+ }
34
+
35
+ # @private
36
+ # The URL of the plz_p1 file
37
+ URLZip1 = "https://match.post.ch/download?file=10001&tid=11&rol=0"
38
+
39
+ # @private
40
+ # The URL of the plz_p2 file
41
+ URLZip2 = "https://match.post.ch/download?file=10002&tid=14&rol=0"
42
+
43
+ # @private
44
+ # The URL of the plz_c file
45
+ URLCommunity = "https://match.post.ch/download?file=10003&tid=13&rol=0"
46
+
47
+ # @private
48
+ # An array of all urls
49
+ URLAll = [URLZip1, URLZip2, URLCommunity]
50
+
51
+ # The data of all cantons
52
+ # @private
53
+ CantonData = [
54
+ ["AG", "Aargau", "Aargau", "Argovie", "Argovia", "Argovia"],
55
+ ["AI", "Appenzell Innerrhoden", "Appenzell Innerrhoden", "Appenzell Rhodes-Intérieures", "Appenzello Interno", "Appenzell Dadens"],
56
+ ["AR", "Appenzell Ausserrhoden", "Appenzell Ausserrhoden", "Appenzell Rhodes-Extérieures", "Appenzello Esterno", "Appenzell Dadora"],
57
+ ["BE", "Bern", "Bern", "Berne", "Berna", "Berna"],
58
+ ["BL", "Basel-Landschaft", "Basel-Landschaft", "Bâle-Campagne", "Basilea Campagna", "Basilea-Champagna"],
59
+ ["BS", "Basel-Stadt", "Basel-Stadt", "Bâle-Ville", "Basilea Città", "Basilea-Citad"],
60
+ ["FR", "Freiburg", "Fribourg", "Fribourg", "Friburgo", "Friburg"],
61
+ ["GE", "Genève", "Genf", "Genève", "Ginevra", "Genevra"],
62
+ ["GL", "Glarus", "Glarus", "Glaris", "Glarona", "Glaruna"],
63
+ ["GR", "Graubünden", "Graubünden", "Grisons", "Grigioni", "Grischun"],
64
+ ["JU", "Jura", "Jura", "Jura", "Giura", "Giura"],
65
+ ["LU", "Luzern", "Luzern", "Lucerne", "Lucerna", "Lucerna"],
66
+ ["NE", "Neuchâtel", "Neuenburg", "Neuchâtel", "Neuchâtel", "Neuchâtel"],
67
+ ["NW", "Nidwalden", "Nidwalden", "Nidwald", "Nidvaldo", "Sutsilvania"],
68
+ ["OW", "Obwalden", "Obwalden", "Obwald", "Obvaldo", "Sursilvania"],
69
+ ["SG", "St. Gallen", "St. Gallen", "Saint-Gall", "San Gallo", "Son Gagl"],
70
+ ["SH", "Schaffhausen", "Schaffhausen", "Schaffhouse", "Sciaffusa", "Schaffusa"],
71
+ ["SO", "Solothurn", "Solothurn", "Soleure", "Soletta", "Soloturn"],
72
+ ["SZ", "Schwyz", "Schwyz", "Schwytz", "Svitto", "Sviz"],
73
+ ["TG", "Thurgau", "Thurgau", "Thurgovie", "Turgovia", "Turgovia"],
74
+ ["TI", "Ticino", "Tessin", "Tessin", "Ticino", "Tessin"],
75
+ ["UR", "Uri", "Uri", "Uri", "Uri", "Uri"],
76
+ ["VD", "Vaud", "Waadt", "Vaud", "Vaud", "Vad"],
77
+ ["VS", "Valais", "Wallis", "Valais", "Vallese", "Vallais"],
78
+ ["ZG", "Zug", "Zug", "Zoug", "Zugo", "Zug"],
79
+ ["ZH", "Zürich", "Zürich", "Zurich", "Zurigo", "Turitg"],
80
+ ["FL", "Fürstentum Liechtenstein", "Fürstentum Liechtenstein", "Liechtenstein", "Liechtenstein", "Liechtenstein"],
81
+ ["DE", "Deutschland", "Deutschland", "Allemagne", "Germania", "Germania"],
82
+ ["IT", "Italien", "Italien", "Italie", "Italia", "Italia"],
83
+ ]
84
+
85
+ # The directory in which the post mat[ch] files reside
86
+ attr_accessor :data_directory
87
+
88
+ # @return [SwissMatch::Cantons] The loaded swiss cantons
89
+ attr_reader :cantons
90
+
91
+ # @return [SwissMatch::Communities] The loaded swiss communities
92
+ attr_reader :communities
93
+
94
+ # @return [SwissMatch::ZipCodes] The loaded swiss zip codes
95
+ attr_reader :zip_codes
96
+
97
+ # @return [Array<LoadError>] Errors that occurred while loading the data
98
+ attr_reader :errors
99
+
100
+ # @param [nil, String] data_directory
101
+ # The directory in which the post mat[ch] files reside
102
+ def initialize(data_directory=nil)
103
+ reset_errors!
104
+ if data_directory then
105
+ @data_directory = data_directory
106
+ elsif ENV['SWISSMATCH_DATA'] then
107
+ @data_directory = ENV['SWISSMATCH_DATA']
108
+ else
109
+ data_directory = File.expand_path('../../../data/swissmatch', __FILE__)
110
+ data_directory = Gem.datadir 'swissmatch' if defined?(Gem) && !File.directory?(data_directory)
111
+ @data_directory = data_directory
112
+ end
113
+ end
114
+
115
+ # Resets the list of errors that were encountered during load
116
+ # @return [self]
117
+ def reset_errors!
118
+ @errors = []
119
+ self
120
+ end
121
+
122
+ # Load new files
123
+ #
124
+ # @return [Array<String>]
125
+ # An array with the absolute file paths of the extracted files.
126
+ def load_updates
127
+ URLAll.flat_map { |url|
128
+ http_get_zip_file(url, @data_directory)
129
+ }
130
+ end
131
+
132
+ # Performs an HTTP-GET for the given url, extracts it as a zipped file into the
133
+ # destination directory.
134
+ #
135
+ # @return [Array<String>]
136
+ # An array with the absolute file paths of the extracted files.
137
+ def http_get_zip_file(url, destination)
138
+ require 'open-uri'
139
+ require 'swissmatch/zip' # patched zip/zip
140
+ require 'fileutils'
141
+
142
+ files = []
143
+
144
+ open(url) do |zip_buffer|
145
+ Zip::ZipFile.open(zip_buffer) do |zip_file|
146
+ zip_file.each do |f|
147
+ target_path = File.join(destination, f.name)
148
+ FileUtils.mkdir_p(File.dirname(target_path))
149
+ zip_file.extract(f, target_path) unless File.exist?(target_path)
150
+ files << target_path
151
+ end
152
+ end
153
+ end
154
+
155
+ files
156
+ end
157
+
158
+ # Unzips it as a zipped file into the destination directory.
159
+ def unzip_file(file, destination)
160
+ require 'swissmatch/zip'
161
+ Zip::ZipFile.open(file) do |zip_file|
162
+ zip_file.each do |f|
163
+ target_path = File.join(destination, f.name)
164
+ FileUtils.mkdir_p(File.dirname(target_path))
165
+ zip_file.extract(f, target_path) unless File.exist?(target_path)
166
+ end
167
+ end
168
+ end
169
+
170
+ # Used to convert numerical language codes to symbols
171
+ LanguageCodes = [nil, :de, :fr, :it, :rt]
172
+
173
+ # Loads the data into this DataFiles instance
174
+ #
175
+ # @return [self]
176
+ # Returns self.
177
+ def load!
178
+ @cantons, @communities, @zip_codes = *load
179
+ self
180
+ end
181
+
182
+ # @return [Array]
183
+ # Returns an array of the form [SwissMatch::Cantons, SwissMatch::Communities,
184
+ # SwissMatch::ZipCodes].
185
+ def load
186
+ reset_errors!
187
+
188
+ cantons = load_cantons
189
+ communities = load_communities(cantons)
190
+ zip_codes = load_zipcodes(cantons, communities)
191
+
192
+ [cantons, communities, zip_codes]
193
+ end
194
+
195
+ # @return [SwissMatch::Cantons]
196
+ # A SwissMatch::Cantons containing all cantons used by the swiss postal service.
197
+ def load_cantons
198
+ Cantons.new(
199
+ CantonData.map { |tag, name, name_de, name_fr, name_it, name_rt|
200
+ Canton.new(tag, name, name_de, name_fr, name_it, name_rt)
201
+ }
202
+ )
203
+ end
204
+
205
+ # @return [SwissMatch::Communities]
206
+ # An instance of SwissMatch::Communities containing all communities defined by the
207
+ # files known to this DataFiles instance.
208
+ def load_communities(cantons)
209
+ raise "Must load cantons first" unless cantons
210
+
211
+ file = Dir.enum_for(:glob, "#{@data_directory}/plz_c_*.txt").last
212
+ temporary = []
213
+ complete = {}
214
+ load_table(file, :community).each do |bfsnr, name, canton, agglomeration|
215
+ bfsnr = bfsnr.to_i
216
+ agglomeration = agglomeration.to_i
217
+ canton = cantons.by_license_tag(canton)
218
+ if agglomeration == bfsnr then
219
+ complete[bfsnr] = Community.new(bfsnr, name, canton, :self)
220
+ elsif agglomeration.nil? then
221
+ complete[bfsnr] = Community.new(bfsnr, name, canton, nil)
222
+ else
223
+ temporary << [bfsnr, name, canton, agglomeration]
224
+ end
225
+ end
226
+ temporary.each do |bfsnr, name, canton, agglomeration|
227
+ community = complete[agglomeration]
228
+ raise "Incomplete community referenced by #{bfsnr}: #{agglomeration}" unless agglomeration
229
+ complete[bfsnr] = Community.new(bfsnr, name, canton, community)
230
+ end
231
+
232
+ Communities.new(complete.values)
233
+ end
234
+
235
+ # TODO: load all files, not just the most recent
236
+ # TODO: calculate valid_until dates
237
+ #
238
+ # @return [SwissMatch::ZipCodes]
239
+ # An instance of SwissMatch::ZipCodes containing all zip codes defined by the
240
+ # files known to this DataFiles instance.
241
+ def load_zipcodes(cantons, communities)
242
+ raise "Must load cantons first" unless cantons
243
+ raise "Must load communities first" unless communities
244
+
245
+ temporary = {}
246
+ self_delivered = []
247
+ others = []
248
+ zip1_file = Dir.enum_for(:glob, "#{@data_directory}/plz_p1_*.txt").last
249
+ zip2_file = Dir.enum_for(:glob, "#{@data_directory}/plz_p2_*.txt").last
250
+ load_table(zip1_file, :zip_1).each do |row|
251
+ onrp = row.at(0).to_i
252
+ delivery_by = row.at(10).to_i
253
+ delivery_by = case delivery_by when 0 then nil; when onrp then :self; else delivery_by; end
254
+ language = LanguageCodes[row.at(7).to_i]
255
+ language_alternative = LanguageCodes[row.at(8).to_i]
256
+ name_short = Name.new(row.at(4), language)
257
+ name = Name.new(row.at(5), language)
258
+ data = [
259
+ onrp, # ordering_number
260
+ row.at(1).to_i, # type
261
+ row.at(2).to_i, # code
262
+ row.at(3).to_i, # add_on
263
+ name, # name (official)
264
+ [name], # names (official + alternative)
265
+ name_short, # name_short (official)
266
+ [name_short], # names_short (official + alternative)
267
+ [], # PLZ2 type 3 short names (additional region names)
268
+ [], # PLZ2 type 3 names (additional region names)
269
+ cantons.by_license_tag(row.at(6)), # canton
270
+ language,
271
+ language_alternative,
272
+ row.at(9) == "1", # sortfile_member
273
+ delivery_by, # delivery_by
274
+ communities.by_community_number(row.at(11).to_i), # community_number
275
+ Date.civil(*row.at(12).match(/^(\d{4})(\d\d)(\d\d)$/).captures.map(&:to_i)) # valid_from
276
+ ]
277
+ temporary[onrp] = data
278
+ if :self == delivery_by then
279
+ self_delivered << data
280
+ else
281
+ others << data
282
+ end
283
+ end
284
+
285
+ load_table(zip2_file, :zip_2).each do |onrp, rn, type, lang, short, name|
286
+ onrp = onrp.to_i
287
+ lang_code = lang.to_i
288
+ language = LanguageCodes[lang_code]
289
+ entry = temporary[onrp]
290
+ if type == "2"
291
+ entry[5] << Name.new(name, language, rn.to_i)
292
+ entry[7] << Name.new(short, language, rn.to_i)
293
+ elsif type == "3"
294
+ entry[8] << Name.new(name, language, rn.to_i)
295
+ entry[9] << Name.new(short, language, rn.to_i)
296
+ end
297
+ end
298
+
299
+ self_delivered.each do |row|
300
+ temporary[row.at(0)] = ZipCode.new(*row)
301
+ end
302
+ others.each do |row|
303
+ if row.at(14) then
304
+ raise "Delivery not found:\n#{row.inspect}" unless tmp = temporary[row.at(14)]
305
+ if tmp.kind_of?(Array) then
306
+ @errors << LoadError.new("Invalid reference: onrp #{row.at(0)} delivery by #{row.at(14)}", row)
307
+ row[14] = nil
308
+ else
309
+ row[14] = tmp
310
+ end
311
+ end
312
+ temporary[row.at(0)] = ZipCode.new(*row)
313
+ end
314
+
315
+ ZipCodes.new(temporary.values)
316
+ end
317
+
318
+ # Reads a file and parses using the pattern of the given name.
319
+ #
320
+ # @param [String] path
321
+ # The path of the file to parse
322
+ # @param [Symbol] pattern
323
+ # The pattern-name used to parse the file (see Expressions)
324
+ #
325
+ # @return [Array<Array<String>>]
326
+ # A 2 dimensional array representing the tabular data contained in the given file.
327
+ def load_table(path, pattern)
328
+ File.read(path, :encoding => Encoding::Windows_1252.to_s). # to_s because sadly, ruby 1.9.2 can't handle an Encoding instance as argument
329
+ encode(Encoding::UTF_8).
330
+ scan(Expressions[pattern])
331
+ end
332
+ end
333
+ end