vignettes/grid-reference-systems.Rmd
grid-reference-systems.RmdThis vignette covers the grid reference systems available in geographiclib: MGRS, Geohash, GARS, and Georef. These systems encode geographic coordinates as alphanumeric strings, useful for human communication and data storage. It also covers GeoCoords for universal coordinate parsing and conversion, and DMS functions for degrees-minutes-seconds formatting.
We’ll use locations from both hemispheres throughout this vignette:
locations <- data.frame(
name = c("Sydney", "Hobart", "McMurdo Station", "South Pole",
"London", "New York", "Tokyo", "Ushuaia"),
lon = c(151.21, 147.32, 166.67, 0, -0.13, -74.01, 139.69, -68.30),
lat = c(-33.87, -42.88, -77.85, -90, 51.51, 40.71, 35.69, -54.80)
)
locations
#> name lon lat
#> 1 Sydney 151.21 -33.87
#> 2 Hobart 147.32 -42.88
#> 3 McMurdo Station 166.67 -77.85
#> 4 South Pole 0.00 -90.00
#> 5 London -0.13 51.51
#> 6 New York -74.01 40.71
#> 7 Tokyo 139.69 35.69
#> 8 Ushuaia -68.30 -54.80MGRS is used by NATO militaries and provides unambiguous location references worldwide. It’s based on UTM zones (or UPS for polar regions).
# Convert all locations to MGRS
pts <- cbind(locations$lon, locations$lat)
codes <- mgrs_fwd(pts)
data.frame(name = locations$name, mgrs = codes)
#> name mgrs
#> 1 Sydney 56HLH3443550816
#> 2 Hobart 55GEN2613352461
#> 3 McMurdo Station 58CEU3923257811
#> 4 South Pole BAN0000000000
#> 5 London 30UXC9915210446
#> 6 New York 18TWL8362507036
#> 7 Tokyo 54SUE8146950356
#> 8 Ushuaia 19FEV4500027239An MGRS code has several components:
# Sydney's MGRS code broken down
sydney_mgrs <- mgrs_fwd(c(151.21, -33.87))
sydney_mgrs
#> [1] "56HLH3443550816"
# Get full metadata from reverse conversion
mgrs_rev(sydney_mgrs)
#> lon lat x y zone northp precision convergence scale
#> 1 151.21 -33.87 334435.5 6250816 56 FALSE 5 0.9978138 0.9999379
#> grid_zone square_100km crs
#> 1 56H LH EPSG:32756MGRS precision ranges from 100km (precision 0) to 1m (precision 5):
hobart <- c(147.32, -42.88)
precisions <- data.frame(
precision = 0:5,
resolution = c("100 km", "10 km", "1 km", "100 m", "10 m", "1 m"),
code = sapply(0:5, function(p) mgrs_fwd(hobart, precision = p))
)
precisions
#> precision resolution code
#> 1 0 100 km 55GEN
#> 2 1 10 km 55GEN25
#> 3 2 1 km 55GEN2652
#> 4 3 100 m 55GEN261524
#> 5 4 10 m 55GEN26135246
#> 6 5 1 m 55GEN2613352461For polar regions (>84°N or <80°S), MGRS uses Universal Polar Stereographic:
# Antarctic locations
antarctic <- cbind(
lon = c(166.67, 0, 77.85, -60),
lat = c(-77.85, -90, -85, -82)
)
antarctic_mgrs <- mgrs_fwd(antarctic)
antarctic_mgrs
#> [1] "58CEU3923257811" "BAN0000000000" "BHP4301516908" "AQS2960244788"
# Note zone = 0 indicates UPS
mgrs_rev(antarctic_mgrs)
#> lon lat x y zone northp precision convergence
#> 1 166.67000 -77.85000 539232.5 1357812 58 FALSE 5 -1.632614
#> 2 45.00000 -89.99999 2000000.5 2000000 0 FALSE 5 -45.000000
#> 3 77.84997 -85.00000 2543015.5 2116908 0 FALSE 5 -77.849968
#> 4 -60.00004 -82.00000 1229602.5 2444788 0 FALSE 5 60.000039
#> scale grid_zone square_100km crs
#> 1 0.9996188 58C EU EPSG:32758
#> 2 0.9940000 B AN EPSG:32761
#> 3 0.9958948 B HP EPSG:32761
#> 4 0.9988601 A QS EPSG:32761All MGRS functions are fully vectorized:
# Different precisions for different points
varied_precision <- mgrs_fwd(pts, precision = c(5, 4, 3, 2, 1, 0, 5, 4))
data.frame(name = locations$name, mgrs = varied_precision)
#> name mgrs
#> 1 Sydney 56HLH3443550816
#> 2 Hobart 55GEN26135246
#> 3 McMurdo Station 58CEU392578
#> 4 South Pole BAN0000
#> 5 London 30UXC91
#> 6 New York 18TWL
#> 7 Tokyo 54SUE8146950356
#> 8 Ushuaia 19FEV45002723Geohash encodes locations as base-32 strings with a useful property: truncating a geohash reduces precision but still contains the original point.
codes <- geohash_fwd(pts, len = 8)
data.frame(name = locations$name, geohash = codes)
#> name geohash
#> 1 Sydney r3gx2f5u
#> 2 Hobart r22u03gj
#> 3 McMurdo Station pdnt0ev6
#> 4 South Pole h0000000
#> 5 London gcpvj117
#> 6 New York dr5reg58
#> 7 Tokyo xn7749pj
#> 8 Ushuaia 4qr2jzcqThis is Geohash’s key feature - shorter codes are valid parent cells:
# Full precision for Sydney
sydney_gh <- geohash_fwd(c(151.21, -33.87), len = 12)
sydney_gh
#> [1] "r3gx2f5ubqkh"
# Truncate to see parent cells
data.frame(
length = 12:4,
geohash = substr(sydney_gh, 1, 12:4)
)
#> length geohash
#> 1 12 r3gx2f5ubqkh
#> 2 11 r3gx2f5ubqkh
#> 3 10 r3gx2f5ubqkh
#> 4 9 r3gx2f5ubqkh
#> 5 8 r3gx2f5ubqkh
#> 6 7 r3gx2f5ubqkh
#> 7 6 r3gx2f5ubqkh
#> 8 5 r3gx2f5ubqkh
#> 9 4 r3gx2f5ubqkh
geohash_resolution(1:12)
#> len lat_resolution lon_resolution
#> 1 1 4.500000e+01 4.500000e+01
#> 2 2 5.625000e+00 1.125000e+01
#> 3 3 1.406250e+00 1.406250e+00
#> 4 4 1.757812e-01 3.515625e-01
#> 5 5 4.394531e-02 4.394531e-02
#> 6 6 5.493164e-03 1.098633e-02
#> 7 7 1.373291e-03 1.373291e-03
#> 8 8 1.716614e-04 3.433228e-04
#> 9 9 4.291534e-05 4.291534e-05
#> 10 10 5.364418e-06 1.072884e-05
#> 11 11 1.341105e-06 1.341105e-06
#> 12 12 1.676381e-07 3.352761e-07
# What length for ~1km precision?
geohash_length(resolution = 1/111) # ~1 degree / 111 km
#> [1] 7
# What length for ~10m precision?
geohash_length(resolution = 10/111000)
#> [1] 9
southern <- cbind(
lon = c(151.21, 147.32, 166.67, -68.30, 77.85),
lat = c(-33.87, -42.88, -77.85, -54.80, -85)
)
rownames(southern) <- c("Sydney", "Hobart", "McMurdo", "Ushuaia", "Amundsen-Scott area")
# Convert and reverse
gh_codes <- geohash_fwd(southern, len = 8)
gh_codes
#> [1] "r3gx2f5u" "r22u03gj" "pdnt0ev6" "4qr2jzcq" "j8zk7w0x"
geohash_rev(gh_codes)
#> lon lat len lat_resolution lon_resolution
#> 1 151.21016 -33.87008 8 0.0001716614 0.0003433228
#> 2 147.31997 -42.88007 8 0.0001716614 0.0003433228
#> 3 166.66998 -77.85007 8 0.0001716614 0.0003433228
#> 4 -68.30011 -54.80006 8 0.0001716614 0.0003433228
#> 5 77.84998 -84.99993 8 0.0001716614 0.0003433228GARS is a military grid system with three precision levels: 30-minute, 15-minute, and 5-minute cells.
codes <- gars_fwd(pts, precision = 2) # 5-minute precision
data.frame(name = locations$name, gars = codes)
#> name gars
#> 1 Sydney 663ES36
#> 2 Hobart 655DY44
#> 3 McMurdo Station 694BA36
#> 4 South Pole 361AA37
#> 5 London 360MV48
#> 6 New York 212LX43
#> 7 Tokyo 640LM33
#> 8 Ushuaia 224CY33
sydney <- c(151.21, -33.87)
gars_codes <- data.frame(
precision = 0:2,
resolution = c("30 minute", "15 minute", "5 minute"),
code = sapply(0:2, function(p) gars_fwd(sydney, precision = p))
)
gars_codes
#> precision resolution code
#> 1 0 30 minute 663ES
#> 2 1 15 minute 663ES3
#> 3 2 5 minute 663ES36Georef is used primarily in aviation. It divides the world into 15° × 15° tiles then subdivides progressively.
codes <- georef_fwd(pts, precision = 2)
data.frame(name = locations$name, georef = codes)
#> name georef
#> 1 Sydney YDBM1207
#> 2 Hobart XDNC1907
#> 3 McMurdo Station ZABN4009
#> 4 South Pole NAAA0000
#> 5 London MKQG5230
#> 6 New York HJAL5942
#> 7 Tokyo XJEF4141
#> 8 Ushuaia HCGF4212
sydney <- c(151.21, -33.87)
georef_codes <- data.frame(
precision = c(-1, 0, 2, 3),
resolution = c("15 degree", "1 degree", "0.01 minute", "0.001 minute"),
code = sapply(c(-1, 0, 2, 3), function(p) georef_fwd(sydney, precision = p))
)
georef_codes
#> precision resolution code
#> 1 -1 15 degree YD
#> 2 0 1 degree YDBM
#> 3 2 0.01 minute YDBM1207
#> 4 3 0.001 minute YDBM126078Georef is particularly useful for aviation across hemispheres:
# Flight path: Sydney to Santiago via Antarctica
flight_pts <- cbind(
lon = c(151.21, 166.67, -70, -70.67),
lat = c(-33.87, -77.85, -85, -33.45)
)
rownames(flight_pts) <- c("Sydney", "McMurdo", "Over Antarctica", "Santiago")
georef_fwd(flight_pts, precision = 2)
#> [1] "YDBM1207" "ZABN4009" "HAFF0000" "HDEM1932"Each system has different strengths:
| System | Best For | Precision Range | Key Feature |
|---|---|---|---|
| MGRS | Military, hiking | 100km - 1m | Unambiguous worldwide |
| Geohash | Databases, URLs | ~5000km - 1mm | Truncation preserves containment |
| GARS | Military aviation | 30min - 5min | Simple, easy to read |
| Georef | Aviation | 15° - 0.001min | Used in flight planning |
| GeoCoords | Format conversion | N/A | Parses multiple input formats |
| DMS | Human-readable | Variable | Degrees, minutes, seconds notation |
# Same location in all systems
pt <- c(147.32, -42.88) # Hobart
data.frame(
system = c("MGRS", "Geohash", "GARS", "Georef", "DMS"),
code = c(
mgrs_fwd(pt, precision = 3),
geohash_fwd(pt, len = 8),
gars_fwd(pt, precision = 2),
georef_fwd(pt, precision = 2),
paste(
dms_encode(pt[2], prec = 4, indicator = "latitude"),
dms_encode(pt[1], prec = 4, indicator = "longitude")
)
)
)
#> system code
#> 1 MGRS 55GEN261524
#> 2 Geohash r22u03gj
#> 3 GARS 655DY44
#> 4 Georef XDNC1907
#> 5 DMS 42d52'48"S 147d19'12"EThe geocoords_parse() function provides a flexible way
to parse coordinate strings in multiple formats. This is particularly
useful when working with data from different sources that may use
different coordinate formats.
geocoords_parse() accepts coordinates in many
formats:
# Parse MGRS codes
geocoords_parse("33TWN0500049000")
#> lat lon zone northp easting northing
#> 1 47.39444 15.06626 33 TRUE 505000.5 5249000
# Parse UTM strings
geocoords_parse("33N 505000 4900000")
#> lat lon zone northp easting northing
#> 1 44.25322 15.06263 33 TRUE 505000 4900000
# Parse DMS (degrees, minutes, seconds)
geocoords_parse("44d 0' 0\" N 33d 0' 0\" E")
#> lat lon zone northp easting northing
#> 1 NA NA NA NA NA NA
# Parse decimal degrees (lat lon format)
geocoords_parse("44.0 33.0")
#> lat lon zone northp easting northing
#> 1 44 33 36 TRUE 5e+05 4871873The function is vectorized for batch processing:
# Mixed format inputs
inputs <- c(
"56HLU1060372300", # MGRS (Sydney area)
"55G 530000 5250000", # UTM (Hobart area)
"-33.87 151.21", # Decimal degrees
"51d 30' 0\" N 0d 7' 0\" W" # DMS (London)
)
parsed <- geocoords_parse(inputs)
parsed[, c("lat", "lon", "zone", "northp")]
#> lat lon zone northp
#> 1 NA NA NA NA
#> 2 NA NA NA NA
#> 3 -33.87 151.21 56 FALSE
#> 4 NA NA NA NAOnce parsed, coordinates can be used with any grid reference or projection function:
# Parse any input format
input <- "33TWN0500049000"
coords <- geocoords_parse(input)
# Then use with any system
pt <- c(coords$lon, coords$lat)
data.frame(
input = input,
lat = coords$lat,
lon = coords$lon,
mgrs = mgrs_fwd(pt, precision = 5),
geohash = geohash_fwd(pt, len = 8),
gars = gars_fwd(pt, precision = 2),
georef = georef_fwd(pt, precision = 2)
)
#> input lat lon mgrs geohash gars georef
#> 1 33TWN0500049000 47.39444 15.06626 33TWN0500049000 u26twgfu 391ML14 PKAC0323The DMS functions provide flexible parsing and formatting of angles in degrees-minutes-seconds notation. This is useful for working with coordinate data from various sources that use different formats.
dms_decode() parses DMS strings and returns the angle in
decimal degrees:
# Parse with hemisphere indicator
dms_decode("40d26'47\"N")
#> angle indicator
#> 1 40.44639 1
# Parse various formats
dms_decode(c(
"40:26:47", # Colon-separated
"-74d0'21.5\"", # Negative with d-'-" separators
"51d30'N", # Degrees and minutes only
"40.446S" # Decimal with hemisphere
))
#> angle indicator
#> 1 40.44639 0
#> 2 -74.00597 0
#> 3 51.50000 1
#> 4 -40.44600 1The function returns both the angle and an indicator showing whether a hemisphere designator was present (0=none, 1=latitude N/S, 2=longitude E/W).
For latitude/longitude pairs, dms_decode_latlon()
handles the hemisphere logic automatically:
# Parse a coordinate pair
dms_decode_latlon("40d26'47\"N", "74d0'21.5\"W")
#> lat lon
#> 1 40.44639 -74.00597
# Vectorized for multiple coordinates
dms_decode_latlon(
c("40d26'47\"N", "51d30'0\"N", "-33d52'10\""),
c("74d0'21.5\"W", "0d7'0\"W", "151d12'30\"")
)
#> lat lon
#> 1 40.44639 -74.0059722
#> 2 51.50000 -0.1166667
#> 3 -33.86944 151.2083333For angles without coordinates (like bearings or field-of-view):
# Parse angles (no hemisphere designators allowed)
dms_decode_angle(c("45:30:0", "123d45'6\"", "90.5"))
#> [1] 45.5000 123.7517 90.5000
# Parse azimuths (E/W allowed, result in [-180, 180])
dms_decode_azimuth(c("45:30:0", "90W", "135E"))
#> [1] 45.5 -90.0 135.0Convert decimal degrees to formatted DMS strings:
# Basic encoding with automatic component selection
dms_encode(40.446195, prec = 5)
#> [1] "40d26'46.3\""
dms_encode(c(40.446, -74.006), prec = 3)
#> [1] "40d26.8'" "-74d00.4'"
# With hemisphere indicators
dms_encode(40.446195, prec = 5, indicator = "latitude")
#> [1] "40d26'46.3\"N"
dms_encode(-74.006328, prec = 5, indicator = "longitude")
#> [1] "074d00'22.8\"W"
# Azimuth format (always positive, 0-360)
dms_encode(-45.5, indicator = "azimuth", prec = 4)
#> [1] "314d30'00\""Control the output format with precision and separator options:
angle <- 40.446195
# Different precisions
data.frame(
prec = 0:6,
output = sapply(0:6, function(p) dms_encode(angle, prec = p))
)
#> prec output
#> 1 0 40
#> 2 1 40.4
#> 3 2 40d27'
#> 4 3 40d26.8'
#> 5 4 40d26'46"
#> 6 5 40d26'46.3"
#> 7 6 40d26'46.30"
# Colon separator (ISO 6709 style)
dms_encode(angle, prec = 5, sep = ":")
#> [1] "40:26:46.3"
# Force specific trailing component
dms_encode(angle, component = "minute", prec = 4)
#> [1] "40d26.7717'"
dms_encode(angle, component = "second", prec = 2)
#> [1] "40d26'46.30\""Work with individual degree, minute, second components:
# Split into degrees and minutes
dms_split(c(40.446, -74.256))
#> d m
#> 1 40 26.76
#> 2 -74 -15.36
# Split into degrees, minutes, and seconds
dms_split(c(40.446195, -74.006328), seconds = TRUE)
#> d m s
#> 1 40 26 46.3020
#> 2 -74 0 -22.7808
# Combine components back to decimal
dms_combine(40, 26, 47)
#> [1] 40.44639
dms_combine(
d = c(40, -74, 51),
m = c(26, 0, 30),
s = c(47, 21.5, 0)
)
#> [1] 40.44639 -73.99403 51.50000DMS encoding and decoding are inverses (within precision limits):
# Original coordinates
original <- c(40.446195, -74.006328, 51.507351)
# Encode to DMS
encoded <- dms_encode(original, prec = 6, indicator = "latitude")
encoded
#> [1] "40d26'46.30\"N" "74d00'22.78\"S" "51d30'26.46\"N"
# Decode back
decoded <- dms_decode(encoded)
data.frame(
original = original,
encoded = encoded,
decoded = decoded$angle,
diff = abs(original - decoded$angle)
)
#> original encoded decoded diff
#> 1 40.44620 40d26'46.30"N 40.44619 5.555556e-07
#> 2 -74.00633 74d00'22.78"S -74.00633 2.222222e-07
#> 3 51.50735 51d30'26.46"N 51.50735 1.000000e-06DMS functions complement GeoCoords for complete coordinate handling:
# Parse mixed-format input with GeoCoords
input <- "40d26'47\"N 74d0'21.5\"W"
coords <- geocoords_parse(input)
# Format output in different styles
data.frame(
format = c("decimal", "DMS", "DMS-colon", "MGRS"),
value = c(
sprintf("%.6f, %.6f", coords$lat, coords$lon),
paste(
dms_encode(coords$lat, prec = 5, indicator = "latitude"),
dms_encode(coords$lon, prec = 5, indicator = "longitude")
),
paste(
dms_encode(coords$lat, prec = 5, sep = ":"),
dms_encode(coords$lon, prec = 5, sep = ":")
),
mgrs_fwd(c(coords$lon, coords$lat), precision = 4)
)
)
#> format value
#> 1 decimal 40.446389, -74.005972
#> 2 DMS 40d26'47.0"N 074d00'21.5"W
#> 3 DMS-colon 40:26:47.0 -74:00:21.5
#> 4 MGRS 18TWK84297777vignette("projections") for map projections (UTM/UPS,
LCC, etc.)vignette("geodesics") for distance and bearing
calculations