Module: SmoOsBngGrids::Grid

Defined in:
lib/smo_os_bng_grids/grid.rb

Class Method Summary collapse

Class Method Details

.bounds(bng_ref) ⇒ Object

Disambiguates 4-char refs: 10km (NS56) vs 50km (NSNE).



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/smo_os_bng_grids/grid.rb', line 24

def self.bounds(bng_ref)
  bng_ref = bng_ref.to_s.strip.upcase
  letters = bng_ref[0, 2]
  suffix  = bng_ref[2..]

  case bng_ref.length
  when 2
    bounds_from_data(bng_ref, GRID_100KM, 100_000)
  when 4
    if suffix =~ /\A[0-9]{2}\z/
      bounds_from_data(bng_ref, GRID_10KM, 10_000)
    elsif QUADRANTS.include?(suffix)
      bounds_from_data(bng_ref, GRID_50KM, 50_000)
    else
      raise ArgumentError, "Unknown 4-char ref format: #{bng_ref.inspect}"
    end
  when 6
    bounds_6(bng_ref)
  when 8
    bounds_from_data(bng_ref, GRID_5KM, 5_000)
  else
    raise ArgumentError, "Cannot determine resolution from ref: #{bng_ref.inspect}"
  end
end

.bounds_6(bng_ref) ⇒ Object

Handles 6-char refs: 1km (NS5566) or ambiguous.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/smo_os_bng_grids/grid.rb', line 104

def self.bounds_6(bng_ref)
  letters = bng_ref[0, 2]
  suffix  = bng_ref[2, 4]

  if suffix =~ /\A[0-9]{4}\z/
    # 1km ref. Derive from parent 100km.
    parent = GRID_100KM[letters]
    raise ArgumentError, "Unknown 100km ref in: #{bng_ref.inspect}" unless parent

    e_digits = suffix[0, 2].to_i
    n_digits = suffix[2, 2].to_i
    min_e = parent[0] + e_digits * 1_000
    min_n = parent[1] + n_digits * 1_000
    { min_e: min_e, min_n: min_n, max_e: min_e + 1_000, max_n: min_n + 1_000 }
  elsif suffix =~ /\A[0-9]{2}(NE|NW|SE|SW)\z/
    # 5km ref (e.g. NS56NE).
    bounds_from_data(bng_ref, GRID_5KM, 5_000)
  else
    raise ArgumentError, "Unknown 6-char ref format: #{bng_ref.inspect}"
  end
end

.bounds_from_data(ref, dataset, size) ⇒ Object

Raises:

  • (ArgumentError)


95
96
97
98
99
100
101
# File 'lib/smo_os_bng_grids/grid.rb', line 95

def self.bounds_from_data(ref, dataset, size)
  entry = dataset[ref]
  raise ArgumentError, "Unknown grid ref: #{ref.inspect}" unless entry

  min_e, min_n = entry
  { min_e: min_e, min_n: min_n, max_e: min_e + size, max_n: min_n + size }
end

.oneKm_digits(easting, northing) ⇒ Object



148
149
150
151
152
153
154
# File 'lib/smo_os_bng_grids/grid.rb', line 148

def self.oneKm_digits(easting, northing)
  letters = ref_100km(easting, northing)
  origin  = GRID_100KM[letters]
  e_digits = (easting  - origin[0]) / 1_000
  n_digits = (northing - origin[1]) / 1_000
  format("%02d%02d", e_digits, n_digits)
end

.quadrant_suffix(easting, northing, half_size) ⇒ Object



156
157
158
159
160
# File 'lib/smo_os_bng_grids/grid.rb', line 156

def self.quadrant_suffix(easting, northing, half_size)
  ns = (northing % (half_size * 2)) >= half_size ? "N" : "S"
  ew = (easting  % (half_size * 2)) >= half_size ? "E" : "W"
  ns + ew
end

.ref_100km(easting, northing) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/smo_os_bng_grids/grid.rb', line 126

def self.ref_100km(easting, northing)
  e500 = easting  / 500_000
  n500 = northing / 500_000

  first_idx  = (3 - n500) * 5 + e500 + 2
  sub_e = (easting  % 500_000) / 100_000
  sub_n = (northing % 500_000) / 100_000
  second_idx = (4 - sub_n) * 5 + sub_e

  LETTERS[first_idx] + LETTERS[second_idx]
end

.ref_at(easting, northing, resolution: "10km") ⇒ Object

Returns the bng_ref string for a given easting/northing at a resolution. resolution: “100km”, “50km”, “10km”, “5km”, “1km”

Raises:

  • (ArgumentError)


7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/smo_os_bng_grids/grid.rb', line 7

def self.ref_at(easting, northing, resolution: "10km")
  validate_resolution!(resolution)
  validate_coords!(easting, northing)

  base = ref_100km(easting, northing)
  raise ArgumentError, "No 100km grid square at E=#{easting} N=#{northing}" unless GRID_100KM.key?(base)

  case resolution
  when "100km" then base
  when "50km"  then base + quadrant_suffix(easting, northing, 50_000)
  when "10km"  then base + tenKm_digits(easting, northing)
  when "5km"   then base + tenKm_digits(easting, northing) + quadrant_suffix(easting, northing, 5_000)
  when "1km"   then base + oneKm_digits(easting, northing)
  end
end

.resolution_of(bng_ref) ⇒ Object

Returns the resolution string for a bng_ref.



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/smo_os_bng_grids/grid.rb', line 81

def self.resolution_of(bng_ref)
  bng_ref = bng_ref.to_s.strip.upcase
  suffix  = bng_ref[2..]
  case bng_ref.length
  when 2 then "100km"
  when 4 then QUADRANTS.include?(suffix) ? "50km" : "10km"
  when 6 then suffix =~ /\A[0-9]{4}\z/ ? "1km" : "5km"
  when 8 then "5km"
  else raise ArgumentError, "Cannot determine resolution of #{bng_ref.inspect}"
  end
end

.tenKm_digits(easting, northing) ⇒ Object



140
141
142
143
144
145
146
# File 'lib/smo_os_bng_grids/grid.rb', line 140

def self.tenKm_digits(easting, northing)
  letters = ref_100km(easting, northing)
  origin  = GRID_100KM[letters]
  e_digit = (easting  - origin[0]) / 10_000
  n_digit = (northing - origin[1]) / 10_000
  format("%d%d", e_digit, n_digit)
end

.valid?(bng_ref) ⇒ Boolean

Returns true if the bng_ref exists in the OS dataset.

Returns:

  • (Boolean)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/smo_os_bng_grids/grid.rb', line 50

def self.valid?(bng_ref)
  bng_ref = bng_ref.to_s.strip.upcase
  letters = bng_ref[0, 2]
  suffix  = bng_ref[2..]

  case bng_ref.length
  when 2 then GRID_100KM.key?(bng_ref)
  when 4
    if suffix =~ /\A[0-9]{2}\z/
      GRID_10KM.key?(bng_ref)
    elsif QUADRANTS.include?(suffix)
      GRID_50KM.key?(bng_ref)
    else
      false
    end
  when 6
    if suffix =~ /\A[0-9]{4}\z/
      GRID_10KM.key?(letters + suffix[0, 2])
    elsif suffix =~ /\A[0-9]{2}(NE|NW|SE|SW)\z/
      GRID_5KM.key?(bng_ref)
    else
      false
    end
  when 8
    GRID_5KM.key?(bng_ref)
  else
    false
  end
end

.validate_coords!(easting, northing) ⇒ Object

Raises:

  • (ArgumentError)


168
169
170
171
172
173
# File 'lib/smo_os_bng_grids/grid.rb', line 168

def self.validate_coords!(easting, northing)
  raise ArgumentError, "Easting must be >= 0"  if easting  < 0
  raise ArgumentError, "Northing must be >= 0" if northing < 0
  raise ArgumentError, "Easting out of range"  if easting  > 700_000
  raise ArgumentError, "Northing out of range" if northing > 1_300_000
end

.validate_resolution!(res) ⇒ Object

Raises:

  • (ArgumentError)


162
163
164
165
166
# File 'lib/smo_os_bng_grids/grid.rb', line 162

def self.validate_resolution!(res)
  return if VALID_RESOLUTIONS.include?(res)

  raise ArgumentError, "Unknown resolution #{res.inspect}. Valid: #{VALID_RESOLUTIONS.join(', ')}"
end