vignettes/geographiclib-overview.Rmd
geographiclib-overview.RmdGeographicLib C++ library for precise geodetic calculations. All functions are fully vectorized and return rich metadata.
Convert geographic coordinates to MGRS codes and back:
# Forward conversion
(code <- mgrs_fwd(c(147.325, -42.881)))
#> [1] "55GEN2654152348"
# Reverse returns rich metadata
mgrs_rev(code)
#> lon lat x y zone northp precision convergence scale
#> 1 147.325 -42.881 526541.5 5252348 55 FALSE 5 -0.2211584 0.9996087
#> grid_zone square_100km crs
#> 1 55G EN EPSG:32755Variable precision from 100km (0) to 1m (5):
Direct access to UTM projections with automatic zone selection:
pts <- cbind(lon = c(147.325, -74.006, 0), lat = c(-42.881, 40.7128, 88))
utmups_fwd(pts)
#> x y zone northp convergence scale lon lat
#> 1 526541.3 5252349 55 FALSE -0.2211566 0.9996087 147.325 -42.8810
#> 2 583959.4 4507351 18 TRUE 0.6483920 0.9996868 -74.006 40.7128
#> 3 2000000.0 1777931 0 TRUE 0.0000000 0.9943028 0.000 88.0000
#> crs
#> 1 EPSG:32755
#> 2 EPSG:32618
#> 3 EPSG:32661Reverse conversion requires zone and hemisphere:
utm <- utmups_fwd(c(147.325, -42.881))
utmups_rev(utm$x, utm$y, utm$zone, utm$northp)
#> lon lat x y zone northp convergence scale crs
#> 1 147.325 -42.881 526541.3 5252349 55 FALSE -0.2211566 0.9996087 EPSG:32755Encode locations as compact strings with hierarchical precision:
# Default length 12 (~19mm precision)
(gh <- geohash_fwd(c(147.325, -42.881)))
#> [1] "r22u03yb164p"
# Truncation preserves containment
substr(gh, 1, c(4, 6, 8))
#> [1] "r22u"
# Reverse shows resolution
geohash_rev(gh)
#> lon lat len lat_resolution lon_resolution
#> 1 147.325 -42.881 12 1.676381e-07 3.352761e-07Resolution table for different lengths:
geohash_resolution(c(4, 6, 8, 10, 12))
#> len lat_resolution lon_resolution
#> 1 4 1.757812e-01 3.515625e-01
#> 2 6 5.493164e-03 1.098633e-02
#> 3 8 1.716614e-04 3.433228e-04
#> 4 10 5.364418e-06 1.072884e-05
#> 5 12 1.676381e-07 3.352761e-07Aviation-oriented grid system:
georef_fwd(c(-0.1, 51.5), precision = 0)
#> [1] "MKQG"
georef_fwd(c(-0.1, 51.5), precision = 1)
#> [1] "MKQG5430"
georef_fwd(c(-0.1, 51.5), precision = 2)
#> [1] "MKQG5430"
georef_rev("GJPJ3230")
#> lon lat precision lat_resolution lon_resolution
#> 1 -76.45833 38.50833 2 0.001666667 0.001666667The LCC projection is widely used for aeronautical charts and regional coordinate systems:
# Single standard parallel (tangent cone)
pts <- cbind(lon = c(-100, -99, -98), lat = c(40, 41, 42))
lcc_fwd(pts, lon0 = -100, stdlat = 40)
#> x y convergence scale lon lat
#> 1 0.00 0.0 0.0000000 1.000000 -100 40
#> 2 84146.25 111521.9 0.6427876 1.000152 -99 41
#> 3 165789.24 224013.2 1.2855752 1.000613 -98 42
# Two standard parallels (secant cone)
lcc_fwd(pts, lon0 = -96, stdlat1 = 33, stdlat2 = 45)
#> x y convergence scale lon lat
#> 1 -339643.8 108321.8 -2.521985 0.9946660 -100 40
#> 2 -251122.5 215464.1 -1.891489 0.9950973 -99 41
#> 3 -164998.9 323691.8 -1.260993 0.9958400 -98 42Project relative to any center point - preserves distances from center:
pts <- cbind(lon = c(-74, 139.7, 151.2), lat = c(40.7, 35.7, -33.9))
result <- azeq_fwd(pts, lon0 = -0.1, lat0 = 51.5)
result
#> x y azi scale lon lat lon0 lat0
#> 1 -5302905 1761511 -128.7635 0.8770643 -74.0 40.7 -0.1 51.5
#> 2 5029432 8155050 156.2494 0.6652729 139.7 35.7 -0.1 51.5
#> 3 14783025 8374075 139.2137 0.1711044 151.2 -33.9 -0.1 51.5
# Distance from London = sqrt(x^2 + y^2)
sqrt(result$x^2 + result$y^2) / 1000
#> [1] 5587.820 9581.233 16990.084Historical projection used for large-scale topographic mapping:
pts <- cbind(lon = c(-100, -99, -101), lat = c(40, 41, 39))
cassini_fwd(pts, lon0 = -100, lat0 = 40)
#> x y azi rk lon lat
#> 1 0.00 7.069289e-10 90.00000 1.0000000 -100 40
#> 2 84133.35 1.115260e+05 90.65610 0.9999129 -99 41
#> 3 -86624.66 -1.105493e+05 89.37064 0.9999076 -101 39Geodesics appear as straight lines - useful for great circle route planning:
# Project relative to a center point
gnomonic_fwd(cbind(lon = c(-74, 2.3), lat = c(40.7, 48.9)), lon0 = -37, lat0 = 46)
#> x y azi rk lon lat
#> 1 -3276316 127487.1 -113.67126 0.8893703 -74.0 40.7
#> 2 2972069 1123467.3 98.77886 0.8951499 2.3 48.9Grid reference system for Great Britain (requires OSGB36 datum coordinates):
# Convert OSGB36 coordinates to grid
london <- c(-0.127, 51.507)
osgb_fwd(london)
#> easting northing convergence scale lon lat
#> 1 529972.5 180390.9 1.466171 0.9998087 -0.127 51.507
# Get alphanumeric grid reference
osgb_gridref(london, precision = 3) # 100m precision
#> [1] "TQ299803"
# Parse a grid reference
osgb_gridref_rev("TQ308080")
#> lon lat easting northing precision
#> 1 -0.1407134 50.85658 530850 108050 3Convert to East-North-Up coordinates relative to a local origin:
# Set up local system centered on London
pts <- cbind(lon = c(-0.1, -0.2, 0.0), lat = c(51.5, 51.6, 51.4))
localcartesian_fwd(pts, lon0 = -0.1, lat0 = 51.5)
#> x y z lon lat h
#> 1 0.000 0.00 0.00000 -0.1 51.5 0
#> 2 -6928.841 11130.61 -13.47326 -0.2 51.6 0
#> 3 6959.234 -11120.93 -13.48954 0.0 51.4 0Convert between geodetic and Earth-Centered Earth-Fixed coordinates:
geocentric_fwd(c(-0.1, 51.5))
#> X Y Z lon lat h
#> 1 3978642 -6944.048 4968362 -0.1 51.5 0
# With height
geocentric_fwd(c(-0.1, 51.5), h = 1000)
#> X Y Z lon lat h
#> 1 3979265 -6945.135 4969145 -0.1 51.5 1000Access ellipsoid parameters and derived quantities:
ellipsoid_params()
#> $a
#> [1] 6378137
#>
#> $f
#> [1] 0.003352811
#>
#> $b
#> [1] 6356752
#>
#> $e2
#> [1] 0.00669438
#>
#> $ep2
#> [1] 0.006739497
#>
#> $n
#> [1] 0.00167922
#>
#> $area
#> [1] 5.100656e+14
#>
#> $volume
#> [1] 1.083207e+21
ellipsoid_curvature(c(0, 45, 90))
#> lat meridional transverse
#> 1 0 6335439 6378137
#> 2 45 6367382 6388838
#> 3 90 6399594 6399594Solve geodesic problems on the WGS84 ellipsoid.
# London to New York
geodesic_inverse(c(-0.1, 51.5), c(-74, 40.7))
#> lon1 lat1 lon2 lat2 s12 azi1 azi2 m12 M12 M21
#> 1 -0.1 51.5 -74 40.7 5587820 -71.62462 -128.7635 4900877 0.6407216 0.6404073
#> S12
#> 1 -4.040644e+13
# 1000km east from London
geodesic_direct(c(-0.1, 51.5), azi = 90, s = 1000000)
#> lon1 lat1 azi1 s12 lon2 lat2 azi2 m12 M12 M21
#> 1 -0.1 51.5 90 1e+06 14.12014 50.62607 101.0838 995914 0.9877522 0.9877514
#> S12
#> 1 7.838198e+12Generate points along the shortest path between two locations:
path <- geodesic_path(c(-0.1, 51.5), c(-74, 40.7), n = 5)
path
#> lon lat azi s
#> 1 -0.10000 51.50000 -71.62462 0
#> 2 -20.47028 53.76472 -87.88020 1396955
#> 3 -41.26329 52.38360 -104.56966 2793910
#> 4 -59.44254 47.71610 -118.56584 4190865
#> 5 -74.00000 40.70000 -128.76352 5587820
cities <- cbind(
lon = c(-0.1, -74, 139.7, 151.2),
lat = c(51.5, 40.7, 35.7, -33.9)
)
rownames(cities) <- c("London", "NYC", "Tokyo", "Sydney")
# Distance matrix in km
round(geodesic_distance_matrix(cities) / 1000)
#> [,1] [,2] [,3] [,4]
#> [1,] 0 5588 9581 16990
#> [2,] 5588 0 10873 15991
#> [3,] 9581 10873 0 7797
#> [4,] 16990 15991 7797 0Rhumb lines are paths of constant bearing. They are not the shortest path (geodesics are), but they maintain a constant compass heading - useful for navigation.
# Rhumb distance vs geodesic: London to New York
geodesic_inverse(c(-0.1, 51.5), c(-74, 40.7))$s12 / 1000 # km
#> [1] 5587.82
rhumb_inverse(c(-0.1, 51.5), c(-74, 40.7))$s12 / 1000 # km (longer!)
#> [1] 5812.568
# Generate rhumb line path
rhumb_path(c(-0.1, 51.5), c(-74, 40.7), n = 5)
#> Warning in format.data.frame(if (omit) x[seq_len(n0), , drop = FALSE] else x, :
#> corrupt data frame: columns will be truncated or padded with NAs
#> lon lat s azi12
#> 1 -0.10000 51.50000 0 -101.9189
#> 2 -20.00072 48.80191 1453142 <NA>
#> 3 -38.86061 46.10255 2906284 <NA>
#> 4 -56.82095 43.40192 4359426 <NA>
#> 5 -74.00000 40.70000 5812568 <NA>Key properties: east-west rhumb lines maintain constant latitude, north-south rhumb lines maintain constant longitude:
# Heading due east maintains latitude
rhumb_direct(c(0, 45), azi = 90, s = 1000000)
#> lon1 lat1 azi12 s12 lon2 lat2 S12
#> 1 0 45 90 1e+06 12.68282 45 6.338984e+12Compute accurate polygon area and perimeter on the ellipsoid:
# Triangle: London - New York - Sydney
triangle <- cbind(
lon = c(-0.1, -74, 151.2),
lat = c(51.5, 40.7, -33.9)
)
result <- polygon_area(triangle)
result
#> $area
#> [1] -1.444896e+14
#>
#> $perimeter
#> [1] 38568531
#>
#> $n
#> [1] 3
# Area in millions of km²
abs(result$area) / 1e12
#> [1] 144.4896Multiple polygons with the id argument:
pts <- cbind(
lon = c(0, 1, 1, 10, 11, 11),
lat = c(0, 0, 1, 0, 0, 1)
)
polygon_area(pts, id = c(1, 1, 1, 2, 2, 2))
#> id area perimeter n
#> 1 1 6154854787 378793.4 3
#> 2 2 6154854787 378793.4 3All functions handle polar regions automatically using UPS (Universal Polar Stereographic) when appropriate:
polar <- cbind(c(0, 180), c(89, -89))
# MGRS uses polar grid zones
mgrs_fwd(polar)
#> [1] "ZAF0000088973" "BAL0000088973"
# UTM/UPS shows zone 0 for polar
utmups_fwd(polar)
#> x y zone northp convergence scale lon lat crs
#> 1 2e+06 1888973 0 TRUE 0 0.9940757 0 89 EPSG:32661
#> 2 2e+06 1888973 0 FALSE -180 0.9940757 180 -89 EPSG:32761All functions are implemented in C++ and fully vectorized:
# 40,000+ points in milliseconds
x <- cbind(runif(40000, -180, 180), runif(40000, -80, 80))
system.time(mgrs_fwd(x))
#> user system elapsed
#> 0.035 0.000 0.035
system.time(geohash_fwd(x))
#> user system elapsed
#> 0.013 0.000 0.012
system.time(geodesic_distance_matrix(x[1:100,]))
#> user system elapsed
#> 0.059 0.000 0.060