# Grid Reference Systems: MGRS, Geohash, GARS, and Georef

    library(geographiclib)

## Contents

- Example Locations
- MGRS - Military Grid Reference System
- Geohash
- GARS - Global Area Reference System
- Georef - World Geographic Reference System
- Comparison of Systems
- GeoCoords - Universal Coordinate Parsing
- DMS - Degrees, Minutes, Seconds Conversion
- See Also

This 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.

## Example Locations

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.80

## MGRS - Military Grid Reference System

MGRS is used by NATO militaries and provides unambiguous location references worldwide. It’s based on UTM zones (or UPS for polar regions).

### Basic Conversion

    # 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 19FEV4500027239

### Understanding MGRS Codes

An MGRS code has several components:

- Grid Zone Designator (e.g., “56H”): UTM zone number + latitude band letter
- 100km Square ID (e.g., “LU”): Two letters identifying the 100km square
- Numerical Location: Easting and northing within the square

    # 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:32756

### Precision Levels

MGRS 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 55GEN2613352461

### Polar Regions (UPS)

For 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:32761

### Vectorized Operations

All 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   19FEV45002723

## Geohash

Geohash encodes locations as base-32 strings with a useful property: truncating a geohash reduces precision but still contains the original point.

### Basic Conversion

    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 4qr2jzcq

### The Truncation Property

This 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

### Resolution by Length

    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

### Finding Required Length for Precision

    # 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 Hemisphere Examples

    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.0003433228

## GARS - Global Area Reference System

GARS is a military grid system with three precision levels: 30-minute, 15-minute, and 5-minute cells.

### Basic Conversion

    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

### Precision Levels

    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 663ES36

### GARS for Antarctic Locations

    antarctic_pts <- cbind(
      lon = c(166.67, 0, 77.85),
      lat = c(-77.85, -85, -82)
    )

    gars_fwd(antarctic_pts, precision = 2)
    #> [1] "694BA36" "361AL37" "516AS48"

## Georef - World Geographic Reference System

Georef is used primarily in aviation. It divides the world into 15° × 15° tiles then subdivides progressively.

### Basic Conversion

    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

### Precision Levels

    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 YDBM126078

### Georef for Flight Planning

Georef 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"

## Comparison of Systems

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"E

## GeoCoords - Universal Coordinate Parsing

The `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.

### Parsing Various 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  4871873

### Parsing Multiple Coordinates

The 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     NA

### Integration with Other Functions

Once 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 PKAC0323

## DMS - Degrees, Minutes, Seconds Conversion

The 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.

### Parsing DMS Strings

`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         1

The 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).

### Parsing Coordinate Pairs

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.2083333

### Parsing Angles and Azimuths

For 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.0

### Encoding to DMS Strings

Convert 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\""

### Output Formats

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\""

### Splitting and Combining Components

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.50000

### Round-Trip Conversion

DMS 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-06

### Integration with GeoCoords

DMS 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              18TWK84297777

## See Also

- `vignette("projections")` for map projections (UTM/UPS, LCC, etc.)
- `vignette("geodesics")` for distance and bearing calculations
- MGRS on Wikipedia
- Geohash on Wikipedia