rats 0.1.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ docs
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Mark
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,181 @@
1
+ = rats
2
+
3
+ a.k.a. Ruby Alberta Township System
4
+
5
+ A ruby class to help with using the Alberta Township System
6
+
7
+ You may find it amusing that Alberta was Rat-free ... until now.
8
+
9
+ == ATS
10
+
11
+ Alberta Township system is a land survey grid used in Alberta, Canada that
12
+ divides the whole province into addressable locations.
13
+
14
+ A typical location looks like: NE 1-2-3 W4
15
+
16
+ Which can be broken down as follows ...
17
+
18
+ === Meridian
19
+
20
+ from above example: [W4] West of the 4th Meridian
21
+
22
+ The Meridian is a constant line running perpendicular to the line of longitude,
23
+ and increments approx. every 4th line of longitude, from East to West.
24
+
25
+ Alberta contains 3 of these Meridians, W4 through W6.
26
+
27
+ === Range
28
+ a.k.a Range Lines
29
+
30
+ from above example: [3]
31
+
32
+ The Range is the x-axis within the given Meridian. It increases East to West and
33
+ resets at the next Meridian. Each increase is approx. every 6 miles.
34
+
35
+ NOTE: The longitude lines are closer together the further North you get, so as
36
+ you go North, there is less Ranges from East to West.
37
+
38
+ === Township
39
+ a.k.a. Township Lines
40
+
41
+ from above example: [2]
42
+
43
+ The Township is the y-axis with the Meridian. It increases South to North and
44
+ never resets, running from the Alberta-US border to the Northern border of Alberta.
45
+ Each increase is approx. every 6 miles.
46
+
47
+ NOTE: The 6x6 mile "square" indicated by the Township (line) + Range (line) +
48
+ Meridian is confusingly called a Township. So you have the Township line and
49
+ the 6x6 mile Township "square".
50
+
51
+ NOTE: Due to the longitude lines getting closer as you go North, there are small
52
+ correction made South to North which results in a non-uniform stacking of Townships.
53
+ Therefore you can not assume that going straight North from one Township to another
54
+ will result in a Township with the same Township-line number.
55
+
56
+ === Section
57
+
58
+ from above example: [1]
59
+
60
+ The Section represents a number 1x1 miles "square" within the 6x6 mile Township.
61
+ There can be up to a maximum of 36 Sections in a township. The number starts with
62
+ 1 in the South-East corner and travels West to number 6. It then increases to 7
63
+ North of number 6 and counts up West-to-East until the number 12. It snakes back
64
+ and forth until it finishes at number 36 in the North-East corner.
65
+
66
+ === Quarter
67
+ a.k.a. Quarter Section
68
+
69
+ from above example: [NE]
70
+
71
+ The Quarter represents a 1/2 x 1/2 mile square within the 1x1 mile Section.
72
+ There can be 4 Quarters in a Section. They are identified by "NE", "NW", "SE"
73
+ and "SW".
74
+
75
+ == Purpose
76
+
77
+ The purpose of Rats is to make it easier to deal with location described using
78
+ the Alberta Township System, at the Quarter, Section or Township level.
79
+
80
+ === Validation
81
+
82
+ Included is validations, only allowing you to describe locations that actually
83
+ exist.
84
+
85
+ For example, Range-line 30 is valid for Township-lines 1-18, but not 18-126.
86
+
87
+ === Parsing
88
+
89
+ Included is string parsing, allowing you to extract and separate the different
90
+ fields from multiple string representations of the location.
91
+
92
+ For example, NE 1-2-3 W4 and 40300201NE are both recognized and parsed.
93
+
94
+ === Display
95
+
96
+ Included is string creation that can create common representations of the
97
+ location.
98
+
99
+ === Traversing
100
+
101
+ NOTE: This is experimental and can probably be improved and made more exact.
102
+ See 'Assumptions' to see where possible errors may exist.
103
+
104
+ In this case, traverse means go North, South, East or West of a given location
105
+ and know what the new location is.
106
+
107
+ Within a Township you can traverse any direction from:
108
+ - quarter to quarter
109
+ - quarter to section
110
+ - section to section
111
+
112
+ When leaving a Township, you can only traverse West and East from:
113
+ - quarter to quarter
114
+ - quarter to section
115
+ - quarter to township
116
+ - section to section
117
+ - section to township
118
+ - township to township
119
+
120
+ NOTE: The limitation in traversing from one Township to another is
121
+ due to correction lines and the nature that Townships due no run vertical
122
+ from North to South. It might be possible to include a way to allow this
123
+ but I do not currently need it.
124
+
125
+ = Usage
126
+
127
+ TODO: show examples
128
+
129
+ = Information
130
+
131
+ == links
132
+
133
+ http://en.wikipedia.org/wiki/Alberta_Township_System
134
+
135
+ == Road Map
136
+
137
+ Future:
138
+ - add support for Saskatchewan (no timeline)
139
+ - address bugs, if any
140
+
141
+ Except for the above, once this gem is finalized, I do not see future
142
+ development happening unless the North-South Township traversing needs
143
+ to be addressed or I want to add ATS to other-system conversions
144
+ (ie: latitude/longitude). Nothing else is planned.
145
+
146
+ Currently this point has not been reached and this gem is still in development.
147
+
148
+ == Assumptions
149
+
150
+ The only assumptions made are in respects to traversing. I do assume that
151
+ traversing within a township, that all sections and quarter sections are
152
+ perfectly aligned. If this is not correct, it must be pretty close, and
153
+ is close enough for my purpose.
154
+
155
+ I also assume that all townships are perfectly aligned from East to West.
156
+
157
+ Lastly, I assume that within every valid section, all four quarters are valid.
158
+ This may be the false. Even so, I do not have a list of which sections do not
159
+ have four quarters (let alone which ones are missing), so I couldn't do anything
160
+ about it anyway.
161
+
162
+ == Environment
163
+
164
+ This gem was created using:
165
+ - ruby 1.9.1-p378
166
+ - rspec 1.3.0
167
+ - rake 0.8.7
168
+
169
+ == Note on Patches/Pull Requests
170
+
171
+ * Fork the project.
172
+ * Make your feature addition or bug fix.
173
+ * Add tests for it. This is important so I don't break it in a
174
+ future version unintentionally.
175
+ * Commit, do not mess with rakefile, version, or history.
176
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
177
+ * Send me a pull request. Bonus points for topic branches.
178
+
179
+ == Copyright
180
+
181
+ Copyright (c) 2010 Mark. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rats"
8
+ gem.summary = "A ruby class to help with using the Alberta Township System"
9
+ gem.description = "A ruby class to help with using the Alberta Township System"
10
+ gem.email = "rats@attackcorp.com"
11
+ gem.homepage = "http://github.com/attack/rats"
12
+ gem.authors = ["Mark G"]
13
+ #gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ spec.spec_opts = ["-c"]
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "rats #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/rats/base.rb ADDED
@@ -0,0 +1,264 @@
1
+ module Rats
2
+ class Base
3
+
4
+ def initialize(*args)
5
+ return unless args.flatten!
6
+ @quarter = Rats::Quarter.new
7
+ @section = Rats::Section.new
8
+ @township = Rats::Township.new
9
+ @range = Rats::Range.new
10
+ @meridian = Rats::Meridian.new
11
+ if args.size == 1
12
+ parse_string(args.pop)
13
+ else
14
+ set_values(args)
15
+ end
16
+ end
17
+
18
+ def q; @quarter; end
19
+ def s; @section; end
20
+ def t; @township; end
21
+ def r; @range; end
22
+ def m; @meridian; end
23
+
24
+ def q=(v); @quarter = v; end
25
+ def s=(v); @section = v; end
26
+ def t=(v); @township = v; end
27
+ def r=(v); @range = v; end
28
+ def m=(v); @meridian = v ; end
29
+
30
+ def quarter; @quarter.v; end
31
+ def section; @section.v; end
32
+ def township; @township.v; end
33
+ def range; @range.v; end
34
+ def meridian; @meridian.v; end
35
+
36
+ def quarter=(v); @quarter.v = v; end
37
+ def section=(v); @section.v = v; end
38
+ def township=(v); @township.v = v; end
39
+ def range=(v); @range.v = v; end
40
+ def meridian=(v); @meridian.v = v; end
41
+
42
+ def location(format = :long)
43
+ case format
44
+ when :short
45
+ short_location
46
+ when :long
47
+ long_location
48
+ else
49
+ long_location
50
+ end
51
+ end
52
+
53
+ def location=(value)
54
+ return unless value
55
+ if value.is_a?(Array)
56
+ set_values(value)
57
+ else
58
+ parse_string(value)
59
+ end
60
+ end
61
+
62
+ def to_s
63
+ long_location
64
+ end
65
+
66
+ def scope
67
+ if self.meridian && self.range && self.township
68
+ if self.section
69
+ if self.quarter
70
+ :quarter
71
+ else
72
+ :section
73
+ end
74
+ else
75
+ :township
76
+ end
77
+ else
78
+ :unknown
79
+ end
80
+ end
81
+
82
+ def valid?
83
+ self.meridian && self.range && self.township && self.exists?
84
+ end
85
+
86
+ # test that a location actually exists
87
+ #
88
+ def exists?
89
+ # make sure meridian exists
90
+ return false unless TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian]
91
+
92
+ # make sure range exists
93
+ return false unless TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian][self.range]
94
+
95
+ # make sure township exists
96
+ return false unless TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian][self.range][:townships].include?(self.township)
97
+
98
+ # make sure section exists
99
+
100
+ # it is possible all sections exists for all townships
101
+ if TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian][self.range].has_key?(:sections)
102
+ # NO, now see if this township has valid sections
103
+ if TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian][self.range][:sections].has_key?(self.township)
104
+ # YES, check further to see that this section is listed
105
+ return TOWNSHIPS_BY_RANGE_AND_MERIDIAN[self.meridian][self.range][:sections][self.township].include?(self.section)
106
+ else
107
+ # the township isn't listed, therefore it has all sections
108
+ return true
109
+ end
110
+ else
111
+ # looks like we exist
112
+ return true
113
+ end
114
+ end
115
+
116
+ def up(level=nil); self.traverse(:up, level); end
117
+ def down(level=nil); self.traverse(:down, level); end
118
+ def left(level=nil); self.traverse(:left, level); end
119
+ def right(level=nil); self.traverse(:right, level); end
120
+ alias north up
121
+ alias south down
122
+ alias west left
123
+ alias east right
124
+
125
+ # this just walks from location to location in the given direction until
126
+ # it finds the next valid location.
127
+ #
128
+ def traverse(direction, level=nil)
129
+ return unless direction
130
+
131
+ new_location = self.dup
132
+ # change the scope of the traverse
133
+ # eg. allow quarter section to traverse to a section
134
+ #
135
+ case level
136
+ when :section
137
+ # we are traversing by section, remove quarter
138
+ new_location.q.nil!
139
+ when :township
140
+ # we are traversing by section, remove quarter & section
141
+ new_location.q.nil!
142
+ new_location.s.nil!
143
+ end
144
+
145
+ the_scope = new_location.scope
146
+ begin
147
+ case the_scope
148
+ when :quarter
149
+ new_location._traverse_quarter(direction)
150
+ when :section
151
+ new_location._traverse_section(direction)
152
+ when :township
153
+ new_location._traverse_range(direction)
154
+ end
155
+ end until new_location.valid?
156
+ new_location
157
+ end
158
+
159
+ def _traverse_quarter(direction)
160
+ begin
161
+ self.q = self.q.send(direction)
162
+ rescue OutOfSection
163
+ self.q = self.q.send(direction.to_s + '!')
164
+ self._traverse_section(direction)
165
+ end
166
+ end
167
+
168
+ def _traverse_section(direction)
169
+ begin
170
+ self.s = self.s.send(direction)
171
+ rescue OutOfTownship
172
+ self.s = self.s.send(direction.to_s + '!')
173
+ self._traverse_range(direction)
174
+ end
175
+ end
176
+
177
+ def _traverse_range(direction)
178
+ begin
179
+ self.r = self.r.send(direction)
180
+ rescue OutOfMeridian
181
+ self.r = self.r.send(direction.to_s + '!')
182
+ self._traverse_meridian(direction)
183
+ end
184
+ end
185
+
186
+ def _traverse_meridian(direction)
187
+ begin
188
+ self.m = self.m.send(direction)
189
+ rescue
190
+ raise OutOfAlberta
191
+ end
192
+ end
193
+
194
+ def to_a
195
+ [self.quarter, self.section, self.township, self.range, self.meridian].compact
196
+ end
197
+
198
+ private
199
+
200
+ def parse_string(location)
201
+ return unless location.respond_to?('to_s')
202
+
203
+ case location_type?(location)
204
+ when :description
205
+ parse_description(location)
206
+ when :parcel_id
207
+ parse_parcel_id(location)
208
+ end
209
+ end
210
+
211
+ def location_type?(location)
212
+ location.to_s.match(/^\d{8}\D{2}/) ? :parcel_id : :description
213
+ end
214
+
215
+ def parse_description(description)
216
+ quarter = description.to_s.scan(/^\D{2}/)
217
+ self.quarter = quarter[0].upcase if quarter && quarter.size > 0
218
+
219
+ numbers = description.to_s.scan(/\d{1,3}/)
220
+ if numbers
221
+ self.meridian = numbers.pop.to_i if numbers.size > 0
222
+ self.range = numbers.pop.to_i if numbers.size > 0
223
+ self.township = numbers.pop.to_i if numbers.size > 0
224
+ self.section = numbers.pop.to_i if numbers.size > 0
225
+ end
226
+ true
227
+ end
228
+
229
+ def parse_parcel_id(parcel_id)
230
+ result = parcel_id.to_s.scan(/^(\d{1})(\d{2})(\d{3})(\d{2})(\D{2})/)
231
+ return unless result && result.size > 0
232
+ numbers = result.pop
233
+ self.meridian = numbers.shift.to_i if numbers.size > 0
234
+ self.range = numbers.shift.to_i if numbers.size > 0
235
+ self.township = numbers.shift.to_i if numbers.size > 0
236
+ self.section = numbers.shift.to_i if numbers.size > 0
237
+ self.quarter = numbers.shift.to_s if numbers.size > 0
238
+ true
239
+ end
240
+
241
+ def set_values(values)
242
+ #self.quarter = values.shift
243
+ self.quarter = values.shift.to_s if values.size > 0
244
+ self.section = values.shift.to_i if values.size > 0
245
+ self.township = values.shift.to_i if values.size > 0
246
+ self.range = values.shift.to_i if values.size > 0
247
+ self.meridian = values.shift.to_i if values.size > 0
248
+ end
249
+
250
+
251
+ def long_location
252
+ if self.quarter
253
+ "#{@quarter.to_s} #{[@section.to_s,@township.to_s,@range.to_s].compact.join('-')} #{@meridian.to_s}".strip
254
+ else
255
+ "#{[@section.to_s,@township.to_s,@range.to_s].compact.join('-')} #{@meridian.to_s}".strip
256
+ end
257
+ end
258
+
259
+ def short_location
260
+ [@quarter.to_p,@section.to_p,@township.to_p,@range.to_p,@meridian.to_p].compact.join('').strip
261
+ end
262
+
263
+ end
264
+ end