Support arbitrary-precision Maidenhead grid squares
Extend grid validation and decoding from 4/6 chars to unlimited even-length grids (8, 10, etc). Recursive decoder handles alternating letter(/24) and digit(/10) subdivision pairs after the field+square. Validated against gridmap-web's Maidenhead implementation.
This commit is contained in:
parent
4af6c5db73
commit
737de10bf4
1 changed files with 40 additions and 12 deletions
|
|
@ -1,16 +1,24 @@
|
||||||
defmodule Microwaveprop.Radio.Maidenhead do
|
defmodule Microwaveprop.Radio.Maidenhead do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
|
# Maidenhead grid: Field (2 letters A-R) + Square (2 digits) + optional
|
||||||
|
# repeating pairs of Subsquare (2 letters) and Extended Square (2 digits).
|
||||||
|
# Minimum 4 chars, always even length. E.g. EM12, EM12kp, EM12kp37, EM12kp37ab
|
||||||
@spec valid?(any()) :: boolean()
|
@spec valid?(any()) :: boolean()
|
||||||
def valid?(nil), do: false
|
def valid?(nil), do: false
|
||||||
|
|
||||||
def valid?(grid) when is_binary(grid) do
|
def valid?(grid) when is_binary(grid) do
|
||||||
String.match?(grid, ~r/^[A-Ra-r]{2}[0-9]{2}$/) or
|
len = byte_size(grid)
|
||||||
String.match?(grid, ~r/^[A-Ra-r]{2}[0-9]{2}[A-Xa-x]{2}$/)
|
len >= 4 and rem(len, 2) == 0 and String.match?(grid, grid_regex())
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid?(_), do: false
|
def valid?(_), do: false
|
||||||
|
|
||||||
|
# Field: [A-Ra-r]{2}, Square: [0-9]{2}, then alternating letter/digit pairs
|
||||||
|
defp grid_regex do
|
||||||
|
~r/^[A-Ra-r]{2}[0-9]{2}(?:[A-Xa-x]{2}(?:[0-9]{2}(?:[A-Xa-x]{2}(?:[0-9]{2})*)*)*)*$/
|
||||||
|
end
|
||||||
|
|
||||||
@spec to_latlon(any()) :: {:ok, {float(), float()}} | :error
|
@spec to_latlon(any()) :: {:ok, {float(), float()}} | :error
|
||||||
def to_latlon(nil), do: :error
|
def to_latlon(nil), do: :error
|
||||||
|
|
||||||
|
|
@ -20,22 +28,42 @@ defmodule Microwaveprop.Radio.Maidenhead do
|
||||||
if valid?(grid) do
|
if valid?(grid) do
|
||||||
<<f1, f2, s1, s2>> <> rest = grid
|
<<f1, f2, s1, s2>> <> rest = grid
|
||||||
|
|
||||||
lon = (f1 - ?A) * 20 + (s1 - ?0) * 2 - 180
|
# Field: 18 divisions (A-R), 20° lon / 10° lat each
|
||||||
lat = (f2 - ?A) * 10 + (s2 - ?0) * 1 - 90
|
lon = (f1 - ?A) * 20.0 - 180.0
|
||||||
|
lat = (f2 - ?A) * 10.0 - 90.0
|
||||||
|
|
||||||
case rest do
|
# Square: 10 divisions (0-9), 2° lon / 1° lat each
|
||||||
<<ss1, ss2>> ->
|
lon = lon + (s1 - ?0) * 2.0
|
||||||
lon = lon + (ss1 - ?A) * (5 / 60) + 5 / 120
|
lat = lat + (s2 - ?0) * 1.0
|
||||||
lat = lat + (ss2 - ?A) * (2.5 / 60) + 2.5 / 120
|
|
||||||
{:ok, {lat, lon}}
|
|
||||||
|
|
||||||
"" ->
|
chars = String.to_charlist(rest)
|
||||||
{:ok, {lat + 0.5, lon + 1.0}}
|
{lat, lon, lat_size, lon_size} = decode_pairs(chars, lat, lon, 1.0, 2.0, :letter)
|
||||||
end
|
{:ok, {lat + lat_size / 2, lon + lon_size / 2}}
|
||||||
else
|
else
|
||||||
:error
|
:error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_latlon(_), do: :error
|
def to_latlon(_), do: :error
|
||||||
|
|
||||||
|
# Decode remaining alternating letter(24)/digit(10) pairs after field+square
|
||||||
|
defp decode_pairs([c1, c2 | rest], lat, lon, lat_size, lon_size, :letter) do
|
||||||
|
cell_lon = lon_size / 24
|
||||||
|
cell_lat = lat_size / 24
|
||||||
|
lon = lon + (c1 - ?A) * cell_lon
|
||||||
|
lat = lat + (c2 - ?A) * cell_lat
|
||||||
|
decode_pairs(rest, lat, lon, cell_lat, cell_lon, :digit)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_pairs([c1, c2 | rest], lat, lon, lat_size, lon_size, :digit) do
|
||||||
|
cell_lon = lon_size / 10
|
||||||
|
cell_lat = lat_size / 10
|
||||||
|
lon = lon + (c1 - ?0) * cell_lon
|
||||||
|
lat = lat + (c2 - ?0) * cell_lat
|
||||||
|
decode_pairs(rest, lat, lon, cell_lat, cell_lon, :letter)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_pairs([], lat, lon, lat_size, lon_size, _type) do
|
||||||
|
{lat, lon, lat_size, lon_size}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue