rats 0.1.0

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