swissmatch-location 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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