I want to manually convert coordinates (latitude-longitude in degree decimals) data into Lambert Conformal Conic (more precisely, EPSG:3347). I already know how to use GIS software functions for this (st_transform
in R, ogr2ogr
in gdal, using directly QGIS, etc.). All my search online points to those specific functions.
What I need is the actual equation used to do the conversion so I can program my own function in another software (SAS, Excel, etc) that is less GIS friendly.
I've found this link that refers to this manual which I tried to reproduce in R, with no success. I'm not even sure it's to good formula.
## The coordinates to reproject
coo = data.frame(lat=c(46.313503, 44.336374, 50.174041, 50.955616, 48.221700),
lon=c(-72.663290, -77.936728, -100.085165, -124.870320, -56.842978))
lat = coo$lat *pi / 180
lon = coo$lon *pi / 180
Then extract the information about the projection I want.
sf::st_crs(3347)
Coordinate Reference System:
User input: EPSG:3347
wkt:
PROJCRS["NAD83 / Statistics Canada Lambert",
BASEGEOGCRS["NAD83",
DATUM["North American Datum 1983",
ELLIPSOID["GRS 1980",6378137,298.257222101,
LENGTHUNIT["metre",1]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433]],
ID["EPSG",4269]],
CONVERSION["Statistics Canada Lambert",
METHOD["Lambert Conic Conformal (2SP)",
ID["EPSG",9802]],
PARAMETER["Latitude of false origin",63.390675,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8821]],
PARAMETER["Longitude of false origin",-91.8666666666667,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8822]],
PARAMETER["Latitude of 1st standard parallel",49,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8823]],
PARAMETER["Latitude of 2nd standard parallel",77,
ANGLEUNIT["degree",0.0174532925199433],
ID["EPSG",8824]],
PARAMETER["Easting at false origin",6200000,
LENGTHUNIT["metre",1],
ID["EPSG",8826]],
PARAMETER["Northing at false origin",3000000,
LENGTHUNIT["metre",1],
ID["EPSG",8827]]],
CS[Cartesian,2],
AXIS["(E)",east,
ORDER[1],
LENGTHUNIT["metre",1]],
AXIS["(N)",north,
ORDER[2],
LENGTHUNIT["metre",1]],
USAGE[
SCOPE["Topographic mapping (small scale)."],
AREA["Canada - onshore and offshore - Alberta; British Columbia; Manitoba; New Brunswick; Newfoundland and Labrador; Northwest Territories; Nova Scotia; Nunavut; Ontario; Prince Edward Island; Quebec; Saskatchewan; Yukon."],
BBOX[38.21,-141.01,86.46,-40.73]],
ID["EPSG",3347]]
Which we transform into R variables and transform in Radian instead of degree
lat1 = 49 * pi / 180
lat2 = 77 * pi / 180
latF = 63.390675 * pi / 180 # is it the latitude of the false origin? not sure.
lon0 = -91.8666666666667 * pi / 180 # Not sure about this one.
EF = 6200000
NF = 3000000
Other variables I need to extract. Their description was at page 6 of the manual cited above.
a = 6378137
f = 1 / 298.257222101
e = sqrt(2*f - f^2)
Then, we adapt the formula in the website
m1 = cos(lat1)/(1 - e^2 * sin(lat1)^2)^0.5 # for m1, lat1, and m2, lat2 where lat1 and lat2 are the latitudes of the two standard parallels.
m2 = cos(lat2)/(1 - e^2 * sin(lat2)^2)^0.5
t1 = tan(pi/4 - lat1/2)/((1 - e * sin(lat1))/(1 + e * sin(lat1)))^(e^2) # for t1, t2, tF and t using lat1, lat2, latF and lat respectively.
t2 = tan(pi/4 - lat2/2)/((1 - e * sin(lat2))/(1 + e * sin(lat2)))^(e^2)
tF = tan(pi/4 - latF/2)/((1 - e * sin(latF))/(1 + e * sin(latF)))^(e^2)
t = tan(pi/4 - lat/2)/((1 - e * sin(lat))/(1 + e * sin(lat)))^(e^2)
I'm not sure I'm using the right latF
and lat
Then:
n = (log(m1) - log(m2))/(log(t1) - log(t2))
FF = m1/(n * t1^n)
r = a * FF * t^n # for rF and r, where rF is the radius of the parallel of latitude of the false origin.
rF = a * FF * tF^n
theta = n * (lon - lon0)
Here, I'm confuse about lon0
is it the Longitude of false origin? And lon
is it the lon I want to convert?
Finally:
E = EF + r * sin(theta)
N = NF + rF - r * cos(theta)
Running this produce:
cbind(E, N)
E N
[1,] 7673386 1349263
[2,] 7325110 1025063
[3,] 5617116 1594762
[4,] 3999380 2227177
[5,] 8680877 2039659
If I compare to what st_transform
gives me:
sf::st_as_sf(coo, coords = c("lon", "lat"), remove=F, crs = st_crs(4326)) |>
sf::st_transform(st_crs(3347)) |>
sf::st_coordinates()
X Y
[1,] 7673199 1352744
[2,] 7324899 1029094
[3,] 5617097 1596987
[4,] 3999454 2229785
[5,] 8680546 2043038
I'm not quite there yet. It's close, but not the same.
In summary, what is the mathematical procedure to convert degree-decimals coordinates to lcc. why doesn't the process I did is not producing similar values than st_transform
?