grid_square 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm gemset use ruby-1.9.3@grid_square --create
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - ruby-head
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ before_install:
8
+ - gem install bundler
9
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+ gem "rspec"
3
+ gem "rspec-given"
data/Gemfile.lock ADDED
@@ -0,0 +1,21 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ rspec (2.11.0)
6
+ rspec-core (~> 2.11.0)
7
+ rspec-expectations (~> 2.11.0)
8
+ rspec-mocks (~> 2.11.0)
9
+ rspec-core (2.11.1)
10
+ rspec-expectations (2.11.2)
11
+ diff-lcs (~> 1.1.3)
12
+ rspec-given (1.5.1)
13
+ rspec (> 1.2.8)
14
+ rspec-mocks (2.11.2)
15
+
16
+ PLATFORMS
17
+ ruby
18
+
19
+ DEPENDENCIES
20
+ rspec
21
+ rspec-given
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # GridSquare
2
+
3
+ Calculate between GridSquare references and latitude/longitude.
4
+
5
+ ```
6
+ grid = GridSquare.new "DN40bi"
7
+
8
+ grid.center
9
+ # => #<Location:0x000001008514d8 @longitude=-109.20833333333333, @latitude=41.6875>
10
+
11
+ grid.width
12
+ # => 0.08333333333333333
13
+
14
+ grid.height
15
+ # => 0.041666666666666664
16
+
17
+ GridSquare.encode(-111.866785, 40.363840).subsquare
18
+ # => "DN40bi"
19
+
20
+ # With arbitrary precision: specify the number of fields
21
+ GridSquare.encode(-111.866785, 40.363840, 10).grid_reference
22
+ # => "DN40BI57XH67OE24bd98"
23
+ ```
24
+
25
+
26
+ # How the US GridSquare System Works
27
+
28
+ Okay in theory the GridSquare system is really complicated. But in
29
+ practice is just base conversion to a number that has a changing
30
+ radix. The first pair of digits are base 18, and the following pairs
31
+ alternate between base 10 and base 24.
32
+
33
+ Here's how it works:
34
+
35
+ The first two letters of a grid reference express the longitude and
36
+ latitude in base 18 (A through R). This pair of letters divides up the
37
+ whole planet into 18 equal "fields", each field being 10 degrees high
38
+ (latitude) and 20 degrees wide (longitude).
39
+
40
+ The next two digits are decimals that divide up each field into 10x10
41
+ "squares" (that's their name; they're obviously not square or even
42
+ rectangular). Squares are 2 degrees wide and 1 degree tall.
43
+
44
+ The next two letters are base 24 (A through X) and divide each square
45
+ into 24x24 "subsquares". Each subsquare is 5 minutes wide and 2.5
46
+ minutes tall.
47
+
48
+ The next two digits are decimals. The official spec calls this the
49
+ "extended subsquare".
50
+
51
+ Officially the locator system stops at extended subsquares (8 digits)
52
+ but unofficially it can be extended by repeating the base 10, base 24
53
+ pairs indefinitely to give precision to the subatomic level and
54
+ beyond. This is of course quite silly as the whole system is based on
55
+ the notion of a spherical earth and the point of the system is to
56
+ economically tell a complete stranger approximately where in the world
57
+ the person they are talking to is located.
58
+
59
+ By convention, the code is capitalized except for the last 2 letters,
60
+ though the first two characters are always capitalized.
61
+
62
+
63
+ # Who Uses this Crap?
64
+
65
+ Ham Radio operators use them to quickly exchange approximate location.
66
+ By convention, shortwave operators give their location in squares
67
+ while VHF and UHF operators give their location in subsquares. A
68
+ square has a precision of about 280km; subsquares give a location to
69
+ within 12km.
70
+
71
+
72
+ # Really?
73
+
74
+ Yes. It even has an amusing name, the Maidenhead Locator System:
75
+ http://en.wikipedia.org/wiki/Maidenhead_Locator_System
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grid_square/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "grid_square"
8
+ gem.version = GridSquare::VERSION
9
+ gem.authors = ["Graham McIntire", "David Brady"]
10
+ gem.email = ["gmcintire@gmail.com"]
11
+ gem.description = %q{Encode/decode between lat/lng locations and US GridSquare references}
12
+ gem.summary = %q{Encode/decode between lat/lng locations and US GridSquare references}
13
+ gem.homepage = "https://github.com/gmcintire/grid_square"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,3 @@
1
+ module GridSquare
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,76 @@
1
+ #require 'location'
2
+ #require 'radix_enumerator'
3
+ #require 'string_ext'
4
+
5
+ class GridSquare
6
+ attr_reader :grid_reference, :origin
7
+
8
+ # location can be a string containing a GS reference or a pair
9
+ # containing longitude and latitude.
10
+ def initialize(grid_reference)
11
+ @grid_reference = grid_reference
12
+ decode
13
+ end
14
+
15
+ def self.encode(longitude, latitude, precision=4 )
16
+ longitude += 180
17
+ latitude += 90
18
+ radixes = RadixEnumerator.new
19
+ grid_reference = ''
20
+ size = Location.new(360.0, 180.0)
21
+ precision.times do
22
+ zero,radix = *radixes.next
23
+
24
+ lng = (radix * longitude / size.longitude).floor
25
+ lat = (radix * latitude / size.latitude).floor
26
+
27
+ grid_reference += "%s%s" % [(zero + lng).chr, (zero + lat).chr]
28
+ size /= radix
29
+ longitude -= lng * size.longitude
30
+ latitude -= lat * size.latitude
31
+ end
32
+
33
+ new grid_reference.downcase_last
34
+ end
35
+
36
+ # Maidenhead locator names
37
+ def field; precision 1; end
38
+ def square; precision 2; end
39
+ def subsquare; precision 3; end
40
+ def extended_subsquare; precision 4; end
41
+
42
+ # Return code to a given number of 2-digit fields
43
+ def precision(num_fields)
44
+ raise IndexError.new "GridSquare.square: insufficient precision to index #{num_fields} fields" unless grid_reference.length >= num_fields * 2
45
+ grid_reference[0...num_fields*2].downcase_last
46
+ end
47
+
48
+ def decode
49
+ radixes = RadixEnumerator.new
50
+ @origin, @size = Location.new(-180.0, -90.0), Location.new(360.0, 180.0)
51
+
52
+ @grid_reference.upcase.chars.each_slice(2) do |lng, lat|
53
+ zero, radix = *radixes.next
54
+ @size /= radix
55
+ @origin += Location.new(@size.longitude * (lng.ord - zero), @size.latitude * (lat.ord - zero))
56
+ end
57
+ end
58
+
59
+ def width; @size.longitude; end
60
+ def height; @size.latitude; end
61
+
62
+ def left; @origin.longitude; end
63
+ def bottom; @origin.latitude; end
64
+ def top; (@origin + @size).latitude; end
65
+ def right; (@origin + @size).longitude; end
66
+
67
+ alias :west :left
68
+ alias :south :bottom
69
+ alias :east :right
70
+ alias :north :top
71
+
72
+ def center
73
+ @origin + @size/2
74
+ end
75
+ end
76
+
data/lib/location.rb ADDED
@@ -0,0 +1,24 @@
1
+ module GridSquare
2
+ class Location
3
+ attr_accessor :longitude, :latitude
4
+ def initialize(longitude, latitude)
5
+ @longitude, @latitude = longitude, latitude
6
+ end
7
+
8
+ def /(scalar)
9
+ Location.new longitude/scalar, latitude/scalar
10
+ end
11
+
12
+ def *(scalar)
13
+ Location.new longitude*scalar, latitude*scalar
14
+ end
15
+
16
+ def +(other)
17
+ Location.new longitude+other.longitude, latitude+other.latitude
18
+ end
19
+
20
+ def -(other)
21
+ Location.new longitude-other.longitude, latitude-other.latitude
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,12 @@
1
+ module GridSquare
2
+ class RadixEnumerator
3
+ def next
4
+ if @enum.nil?
5
+ @enum = ([["0".ord, 10], [?A.ord, 24]]).cycle
6
+ [?A.ord, 18]
7
+ else
8
+ @enum.next
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/string_ext.rb ADDED
@@ -0,0 +1,9 @@
1
+ class String
2
+ def downcase_last
3
+ if length > 4
4
+ self[0...-4].upcase + self[-4..-1].downcase
5
+ else
6
+ upcase
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+ require_relative "../../lib/grid_square"
3
+
4
+ describe GridSquare do
5
+ context "created from field" do
6
+ Given(:grid_square) { GridSquare.new "DN" }
7
+
8
+ Then { grid_square.field.should == "DN" }
9
+ Then { lambda { grid_square.square }.should raise_error(IndexError) }
10
+ Then { lambda { grid_square.subsquare }.should raise_error(IndexError) }
11
+ Then { lambda { grid_square.extended_subsquare.should }.should raise_error(IndexError) }
12
+ Then { lambda { grid_square.precision 10 }.should raise_error(IndexError) }
13
+
14
+ Then { grid_square.west.should be_within(0.00001).of -120.0 }
15
+ Then { grid_square.south.should be_within(0.00001).of 40.0 }
16
+ Then { grid_square.north.should be_within(0.00001).of 50.0 }
17
+ Then { grid_square.east.should be_within(0.00001).of -100.0 }
18
+
19
+ Then { grid_square.width.should be_within(0.00001).of 20.0 }
20
+ Then { grid_square.height.should be_within(0.00001).of 10.0 }
21
+
22
+ Then { grid_square.center.longitude.should be_within(0.00001).of -110.0 }
23
+ Then { grid_square.center.latitude.should be_within(0.00001).of 45.0 }
24
+ end
25
+
26
+ context "created from square" do
27
+ Given(:grid_square) { GridSquare.new "DN41" }
28
+
29
+ Then { grid_square.field.should == "DN" }
30
+ Then { grid_square.square.should == "DN41" }
31
+ Then { lambda { grid_square.subsquare.should == "DN41bi" }.should raise_error(IndexError) }
32
+ Then { lambda { grid_square.extended_subsquare.should }.should raise_error(IndexError) }
33
+ Then { lambda { grid_square.precision 10 }.should raise_error(IndexError) }
34
+
35
+ Then { grid_square.west.should be_within(0.00001).of -112.0 }
36
+ Then { grid_square.south.should be_within(0.00001).of 41.0 }
37
+
38
+ Then { grid_square.width.should be_within(0.00001).of 2.0 }
39
+ Then { grid_square.height.should be_within(0.00001).of 1.0 }
40
+
41
+ Then { grid_square.center.longitude.should be_within(0.00001).of -111.0 }
42
+ Then { grid_square.center.latitude.should be_within(0.00001).of 41.5 }
43
+ end
44
+
45
+ context "created from subsquare" do
46
+ Given(:grid_square) { GridSquare.new "DN41bi" }
47
+
48
+ Then { grid_square.field.should == "DN" }
49
+ Then { grid_square.square.should == "DN41" }
50
+ Then { grid_square.subsquare.should == "DN41bi" }
51
+ Then { lambda { grid_square.extended_subsquare.should }.should raise_error(IndexError) }
52
+ Then { lambda { grid_square.precision 10 }.should raise_error(IndexError) }
53
+
54
+ Then { grid_square.west.should be_within(0.00001).of -111.91666 }
55
+ Then { grid_square.south.should be_within(0.00001).of 41.33333 }
56
+
57
+ Then { grid_square.width.should be_within(0.00001).of 0.083333 }
58
+ Then { grid_square.height.should be_within(0.00001).of 0.041666 }
59
+
60
+ Then { grid_square.center.longitude.should be_within(0.00001).of -111.875 }
61
+ Then { grid_square.center.latitude.should be_within(0.00001).of 41.35416 }
62
+ end
63
+
64
+ context "created from extended subsquare" do
65
+ Given(:grid_square) { GridSquare.new "DN41bi63" }
66
+
67
+ Then { grid_square.field.should == "DN" }
68
+ Then { grid_square.square.should == "DN41" }
69
+ Then { grid_square.subsquare.should == "DN41bi" }
70
+ Then { grid_square.extended_subsquare.should == "DN41bi63" }
71
+ Then { lambda { grid_square.precision 10 }.should raise_error(IndexError) }
72
+
73
+ Then { grid_square.west.should be_within(0.00001).of -111.86666 }
74
+ Then { grid_square.south.should be_within(0.00001).of 41.34583 }
75
+
76
+ Then { grid_square.width.should be_within(0.00001).of 0.0083333 }
77
+ Then { grid_square.height.should be_within(0.00001).of 0.0041666 }
78
+
79
+ Then { grid_square.center.longitude.should be_within(0.00001).of -111.8625 }
80
+ Then { grid_square.center.latitude.should be_within(0.00001).of 41.34792 }
81
+ end
82
+
83
+ context "proper field casing" do
84
+ Given(:upper) { GridSquare.new "DN41BI83QR" }
85
+ Given(:lower) { GridSquare.new "dn41bi83qr" }
86
+
87
+ Then { upper.field.should == "DN" }
88
+ Then { upper.square.should == "DN41" }
89
+ Then { upper.subsquare.should == "DN41bi" }
90
+ Then { upper.precision(4).should == "DN41bi83" }
91
+
92
+ Then { lower.field.should == "DN" }
93
+ Then { lower.square.should == "DN41" }
94
+ Then { lower.subsquare.should == "DN41bi" }
95
+ Then { lower.precision(4).should == "DN41bi83" }
96
+ end
97
+
98
+ context "created from a ridiculously long GridSquare code" do
99
+ Given(:ludicrous) { GridSquare.new "RN91SK59MC07FQ84VK31SM03PF57XS16DU32MS24LX34EI48RQ28CS09MA87VO18GS16WK64JL24XF21KJ12FO37RJ73QL28IU39AS55HC86SW74JD70VQ61SF52QA21SD28GB60HT40SN34VG10NP11TP27CR36OA76LD28TI36JE73TF03OT41UH60IX96IH80HX45KC53SJ57AF07HQ38NI74WT84LP33UQ34OP06TV69IA96LB80VW57MV04SJ88AV74GO73JW26BF53DE74BH31EV57WP25WO98IL47BT13AD44WC66BL08IA01KN55FV05EI50KM77HV72LF21NG51XI69QW28PO80FW77AI36BE19ID25JR96KA39DA28LK83PL86TP85AH21LB31NF52BQ06WJ64AQ05XK16RJ16SP96KE85BU61BS68OK10HC39QH86XB51PO62RA27JH93PM08NF46XS98HN76LJ55KQ68AQ34JR96OF09DJ13NP76BP09AP83MK60SE83FX63OS03AG31HA46KB00QF06LU66DX71PH65WU26IF88AO21LE62DI34GF23hc34" }
100
+ Then { ludicrous.field.should == "RN" }
101
+ Then { ludicrous.square.should == "RN91" }
102
+ Then { ludicrous.subsquare.should == "RN91sk" }
103
+ Then { ludicrous.extended_subsquare.should == "RN91sk59" }
104
+ Then { lambda { ludicrous.precision 301 }.should raise_error(IndexError) }
105
+ end
106
+
107
+ describe ".encode" do
108
+ Given(:grid_square) { GridSquare.encode -111.866785, 40.363840 }
109
+
110
+ Then { grid_square.grid_reference.should == "DN40bi57" }
111
+
112
+ context "with precision" do
113
+ Given(:grid_square) { GridSquare.encode -111.866785, 40.363840, 10 }
114
+
115
+ Then { grid_square.grid_reference.should == "DN40BI57XH67OE24bd98" }
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/location'
3
+
4
+ module GridSquare
5
+ describe Location do
6
+ # We can use Locations to represent exact points...
7
+ Given(:origin) { Location.new 0.0, 0.0 }
8
+
9
+ # Or spans/sizes
10
+ Given(:window) { Location.new -180.0, -90.0 }
11
+
12
+ Then { window.longitude.should be_within(0.000001).of -180.0 }
13
+ Then { window.latitude.should be_within(0.000001).of -90.0 }
14
+
15
+ context "translation +" do
16
+ Given(:new_origin) { origin + Location.new(50.0, 40.0) }
17
+ Then { new_origin.longitude.should be_within(0.000001).of 50.0 }
18
+ Then { new_origin.latitude.should be_within(0.000001).of 40.0 }
19
+ end
20
+
21
+ context "translation -" do
22
+ Given(:new_origin) { origin - Location.new(50.0, 40.0) }
23
+ Then { new_origin.longitude.should be_within(0.000001).of -50.0 }
24
+ Then { new_origin.latitude.should be_within(0.000001).of -40.0 }
25
+ end
26
+
27
+ context "scaling *" do
28
+ Given(:new_window) { window * 2 }
29
+ Then { new_window.longitude.should be_within(0.000001).of -360.0 }
30
+ Then { new_window.latitude.should be_within(0.000001).of -180.0 }
31
+ end
32
+
33
+ context "scaling /" do
34
+ Given(:new_window) { window / 2 }
35
+ Then { new_window.longitude.should be_within(0.000001).of -90.0 }
36
+ Then { new_window.latitude.should be_within(0.000001).of -45.0 }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require_relative '../../lib/radix_enumerator'
3
+
4
+ module GridSquare
5
+ describe RadixEnumerator do
6
+ Given(:radix) { RadixEnumerator.new }
7
+
8
+ Then {
9
+ radix.next[0].should == ?A.ord
10
+ radix.next[0].should == "0".ord
11
+ radix.next[0].should == ?A.ord
12
+ radix.next[0].should == "0".ord
13
+ radix.next[0].should == ?A.ord
14
+ radix.next[0].should == "0".ord
15
+ radix.next[0].should == ?A.ord
16
+ radix.next[0].should == "0".ord
17
+ radix.next[0].should == ?A.ord
18
+ }
19
+
20
+ Then {
21
+ radix.next[1].should == 18
22
+ radix.next[1].should == 10
23
+ radix.next[1].should == 24
24
+ radix.next[1].should == 10
25
+ radix.next[1].should == 24
26
+ radix.next[1].should == 10
27
+ radix.next[1].should == 24
28
+ radix.next[1].should == 10
29
+ radix.next[1].should == 24
30
+ }
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require_relative "../../lib/string_ext"
3
+
4
+ describe String do
5
+ describe "#downcase_last" do
6
+ context "field" do
7
+ Given(:upper) { "DN" }
8
+ Given(:lower) { "dn" }
9
+
10
+ Then { upper.downcase_last.should == "DN" }
11
+ Then { lower.downcase_last.should == "DN" }
12
+ end
13
+
14
+ context "ending in digits" do
15
+ Given(:upper) { "DN41BI83" }
16
+ Given(:lower) { "dn41bi83" }
17
+
18
+ Then { upper.downcase_last.should == "DN41bi83" }
19
+ Then { lower.downcase_last.should == "DN41bi83" }
20
+ end
21
+
22
+ context "subsquare or longer" do
23
+ Given(:upper) { "DN41BI83QR" }
24
+ Given(:lower) { "dn41bi83qr" }
25
+
26
+ Then { upper.downcase_last.should == "DN41BI83qr" }
27
+ Then { lower.downcase_last.should == "DN41BI83qr" }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ require "rspec-given"
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grid_square
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Graham McIntire
9
+ - David Brady
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-11-16 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: Encode/decode between lat/lng locations and US GridSquare references
16
+ email:
17
+ - gmcintire@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .rspec
23
+ - .rvmrc
24
+ - .travis.yml
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - README.md
28
+ - Rakefile
29
+ - grid_square.gemspec
30
+ - lib/grid_square.rb
31
+ - lib/grid_square/version.rb
32
+ - lib/location.rb
33
+ - lib/radix_enumerator.rb
34
+ - lib/string_ext.rb
35
+ - spec/lib/grid_square_spec.rb
36
+ - spec/lib/location_spec.rb
37
+ - spec/lib/radix_enumerator_spec.rb
38
+ - spec/lib/string_ext_spec.rb
39
+ - spec/spec_helper.rb
40
+ homepage: https://github.com/gmcintire/grid_square
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.23
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Encode/decode between lat/lng locations and US GridSquare references
64
+ test_files:
65
+ - spec/lib/grid_square_spec.rb
66
+ - spec/lib/location_spec.rb
67
+ - spec/lib/radix_enumerator_spec.rb
68
+ - spec/lib/string_ext_spec.rb
69
+ - spec/spec_helper.rb