Convert geodetic coordinates to geocentric (cartesian)
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
I'd like to retain as much float precision as possible without having to resort to specialized libraries so I need this code to work with only standard modules.
I'm doing this in QGIS where specialized libraries aren't available by default. numpy
would be OK to use.
Sources for the computations can be found on Wikipedia.
Here is my code:
import math
## Ellipsoid Parameters as tuples (semi major axis, inverse flattening)
grs80 = (6378137, 298.257222100882711)
wgs84 = (6378137, 298.257223563)
def geodetic_to_geocentric(ellps, lat, lon, h):
"""
Compute the Geocentric (Cartesian) Coordinates X, Y, Z
given the Geodetic Coordinates lat, lon + Ellipsoid Height h
"""
a, rf = ellps
lat_rad = math.radians(lat)
lon_rad = math.radians(lon)
N = a / math.sqrt(1 - (1 - (1 - 1 / rf) ** 2) * (math.sin(lat_rad)) ** 2)
X = (N + h) * math.cos(lat_rad) * math.cos(lon_rad)
Y = (N + h) * math.cos(lat_rad) * math.sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * math.sin(lat_rad)
return X, Y, Z
Input:
lat = 43.21009
lon = -78.120123
h = 124
print(geodetic_to_geocentric(wgs84, lat, lon, h))
Output:
(958506.0102730404, -4556367.372558064, 4344627.16166323)
This online converter gives the following output:
X: 958506.01027304
Y: -4556367.37255806
Z: 4344627.16160147
So, quite close for X and Y but slightly different for Z. Is this a rounding error ? If so, on which side ? Is there a better strategy to do the computations that may retain better precision ?
python coordinate-system geospatial unit-conversion
 |Â
show 5 more comments
up vote
4
down vote
favorite
I'd like to retain as much float precision as possible without having to resort to specialized libraries so I need this code to work with only standard modules.
I'm doing this in QGIS where specialized libraries aren't available by default. numpy
would be OK to use.
Sources for the computations can be found on Wikipedia.
Here is my code:
import math
## Ellipsoid Parameters as tuples (semi major axis, inverse flattening)
grs80 = (6378137, 298.257222100882711)
wgs84 = (6378137, 298.257223563)
def geodetic_to_geocentric(ellps, lat, lon, h):
"""
Compute the Geocentric (Cartesian) Coordinates X, Y, Z
given the Geodetic Coordinates lat, lon + Ellipsoid Height h
"""
a, rf = ellps
lat_rad = math.radians(lat)
lon_rad = math.radians(lon)
N = a / math.sqrt(1 - (1 - (1 - 1 / rf) ** 2) * (math.sin(lat_rad)) ** 2)
X = (N + h) * math.cos(lat_rad) * math.cos(lon_rad)
Y = (N + h) * math.cos(lat_rad) * math.sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * math.sin(lat_rad)
return X, Y, Z
Input:
lat = 43.21009
lon = -78.120123
h = 124
print(geodetic_to_geocentric(wgs84, lat, lon, h))
Output:
(958506.0102730404, -4556367.372558064, 4344627.16166323)
This online converter gives the following output:
X: 958506.01027304
Y: -4556367.37255806
Z: 4344627.16160147
So, quite close for X and Y but slightly different for Z. Is this a rounding error ? If so, on which side ? Is there a better strategy to do the computations that may retain better precision ?
python coordinate-system geospatial unit-conversion
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
Units arem
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.
â YeO
Jun 6 at 6:43
2
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
1
Because I really want to ? :-) More seriously, I want to do this within QGIS wheresympy
,bigfloat
and such aren't available by default.numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.
â YeO
Jun 6 at 8:47
1
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22
 |Â
show 5 more comments
up vote
4
down vote
favorite
up vote
4
down vote
favorite
I'd like to retain as much float precision as possible without having to resort to specialized libraries so I need this code to work with only standard modules.
I'm doing this in QGIS where specialized libraries aren't available by default. numpy
would be OK to use.
Sources for the computations can be found on Wikipedia.
Here is my code:
import math
## Ellipsoid Parameters as tuples (semi major axis, inverse flattening)
grs80 = (6378137, 298.257222100882711)
wgs84 = (6378137, 298.257223563)
def geodetic_to_geocentric(ellps, lat, lon, h):
"""
Compute the Geocentric (Cartesian) Coordinates X, Y, Z
given the Geodetic Coordinates lat, lon + Ellipsoid Height h
"""
a, rf = ellps
lat_rad = math.radians(lat)
lon_rad = math.radians(lon)
N = a / math.sqrt(1 - (1 - (1 - 1 / rf) ** 2) * (math.sin(lat_rad)) ** 2)
X = (N + h) * math.cos(lat_rad) * math.cos(lon_rad)
Y = (N + h) * math.cos(lat_rad) * math.sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * math.sin(lat_rad)
return X, Y, Z
Input:
lat = 43.21009
lon = -78.120123
h = 124
print(geodetic_to_geocentric(wgs84, lat, lon, h))
Output:
(958506.0102730404, -4556367.372558064, 4344627.16166323)
This online converter gives the following output:
X: 958506.01027304
Y: -4556367.37255806
Z: 4344627.16160147
So, quite close for X and Y but slightly different for Z. Is this a rounding error ? If so, on which side ? Is there a better strategy to do the computations that may retain better precision ?
python coordinate-system geospatial unit-conversion
I'd like to retain as much float precision as possible without having to resort to specialized libraries so I need this code to work with only standard modules.
I'm doing this in QGIS where specialized libraries aren't available by default. numpy
would be OK to use.
Sources for the computations can be found on Wikipedia.
Here is my code:
import math
## Ellipsoid Parameters as tuples (semi major axis, inverse flattening)
grs80 = (6378137, 298.257222100882711)
wgs84 = (6378137, 298.257223563)
def geodetic_to_geocentric(ellps, lat, lon, h):
"""
Compute the Geocentric (Cartesian) Coordinates X, Y, Z
given the Geodetic Coordinates lat, lon + Ellipsoid Height h
"""
a, rf = ellps
lat_rad = math.radians(lat)
lon_rad = math.radians(lon)
N = a / math.sqrt(1 - (1 - (1 - 1 / rf) ** 2) * (math.sin(lat_rad)) ** 2)
X = (N + h) * math.cos(lat_rad) * math.cos(lon_rad)
Y = (N + h) * math.cos(lat_rad) * math.sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * math.sin(lat_rad)
return X, Y, Z
Input:
lat = 43.21009
lon = -78.120123
h = 124
print(geodetic_to_geocentric(wgs84, lat, lon, h))
Output:
(958506.0102730404, -4556367.372558064, 4344627.16166323)
This online converter gives the following output:
X: 958506.01027304
Y: -4556367.37255806
Z: 4344627.16160147
So, quite close for X and Y but slightly different for Z. Is this a rounding error ? If so, on which side ? Is there a better strategy to do the computations that may retain better precision ?
python coordinate-system geospatial unit-conversion
edited Jun 6 at 9:22
Mathias Ettinger
21.8k32875
21.8k32875
asked Jun 6 at 3:45
YeO
1138
1138
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
Units arem
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.
â YeO
Jun 6 at 6:43
2
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
1
Because I really want to ? :-) More seriously, I want to do this within QGIS wheresympy
,bigfloat
and such aren't available by default.numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.
â YeO
Jun 6 at 8:47
1
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22
 |Â
show 5 more comments
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
Units arem
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.
â YeO
Jun 6 at 6:43
2
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
1
Because I really want to ? :-) More seriously, I want to do this within QGIS wheresympy
,bigfloat
and such aren't available by default.numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.
â YeO
Jun 6 at 8:47
1
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
Units are
m
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.â YeO
Jun 6 at 6:43
Units are
m
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.â YeO
Jun 6 at 6:43
2
2
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
1
1
Because I really want to ? :-) More seriously, I want to do this within QGIS where
sympy
, bigfloat
and such aren't available by default. numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.â YeO
Jun 6 at 8:47
Because I really want to ? :-) More seriously, I want to do this within QGIS where
sympy
, bigfloat
and such aren't available by default. numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.â YeO
Jun 6 at 8:47
1
1
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22
 |Â
show 5 more comments
2 Answers
2
active
oldest
votes
up vote
7
down vote
I can't explain the discrepancy with the online conversion tool without seeing the source code for the latter.
1. Review
The comment for the ellipsoid parameters should give the units for the semi-major axis. Also, I think "reciprocal flattening" would be a better description of the second parameter, given that you use the name
rf
later on in the code.The docstring for
geodetic_to_geocentric
should give the units (degrees) for the latitude and longitude parameters."Height above ellipsoid" would be a clearer description for the
h
parameter.The parameters to
geodetic_to_geocentric
could be spelled out in full, which would help understand their meaning. Parameter names are documentation too.Comments would help understand the meaning of values like
N
.The expressions
(N + h) * cos(lat)
,sin(lat)
, and(1 - 1 / rf) ** 2
are evaluated twice. These could be stored in local variables to avoid duplicated work.
2. Revised code
from math import cos, radians, sin, sqrt
# Ellipsoid parameters: semi major axis in metres, reciprocal flattening.
GRS80 = 6378137, 298.257222100882711
WGS84 = 6378137, 298.257223563
def geodetic_to_geocentric(ellipsoid, latitude, longitude, height):
"""Return geocentric (Cartesian) Coordinates x, y, z corresponding to
the geodetic coordinates given by latitude and longitude (in
degrees) and height above ellipsoid. The ellipsoid must be
specified by a pair (semi-major axis, reciprocal flattening).
"""
ÃÂ = radians(latitude)
û = radians(longitude)
sin_ÃÂ = sin(ÃÂ)
a, rf = ellipsoid # semi-major axis, reciprocal flattening
e2 = 1 - (1 - 1 / rf) ** 2 # eccentricity squared
n = a / sqrt(1 - e2 * sin_ÃÂ ** 2) # prime vertical radius
r = (n + height) * cos(ÃÂ) # perpendicular distance from z axis
x = r * cos(û)
y = r * sin(û)
z = (n * (1 - e2) + height) * sin_ÃÂ
return x, y, z
Thanks. Technically, the valuee2
is not the eccentricity squared, that would be1 - (1 - 1/rf) ** 2
. Also,n
is theprime vertical radius of curvature
, not the distance from centre (ÃÂ is the Geodetic Latitude, not the Geocentric one).
â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluatinge2
as(2 - 1 / rf) / rf
: if using a different ellipsoid which hasrf
very close to1
or very large, you eliminate potential loss of significance due to subtracting a number very close to1
from1
.
â Peter Taylor
Jun 6 at 14:48
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
add a comment |Â
up vote
5
down vote
Precision in numeric calculus is usually handled by the decimal
module. Compare:
>>> 1.001 + 0.0001
1.0010999999999999
>>> Decimal('1.001') + Decimal('0.0001')
Decimal('1.0011')
However, trigonometric functions would convert a decimal.Decimal
back to a float
, thus defeating the purpose. However, the decimal
module provide some recipe to help fill the void. Using the recipe for pi()
you can reimplement math.radians
easily; cos()
and sin()
are covered by the recipes; and math.sqrt
is available directly on Decimal
objects. So you could write:
def geodetic_to_geocentric(ellps, lat, lon, h):
a, rf = ellps
lat_rad = radians(lat)
lon_rad = radians(lon)
N = a / (1 - (1 - (1 - 1 / rf) ** 2) * (sin(lat_rad)) ** 2).sqrt()
X = (N + h) * cos(lat_rad) * cos(lon_rad)
Y = (N + h) * cos(lat_rad) * sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * sin(lat_rad)
return X, Y, Z
Which would yield:
>>> lat = Decimal('43.21009')
>>> lon = Decimal('-78.120123')
>>> h = Decimal('124')
>>> print(geodetic_to_geocentric(wgs84, lat, lon, h))
(Decimal('958506.0102730405668418845812'), Decimal('-4556367.372558064424670955239'), Decimal('4344627.161663229280843240044'))
Which is closer to your result than the one you expected. So I wouldn't worry in this case.
Note however that computation using Decimal
s are usually slower
>>> timeit.timeit("geodetic_to_geocentric((Decimal('6378137'), Decimal('298.257223563')), Decimal('43.21009'), Decimal('-78.120123'), Decimal('124'))", setup='from __main__ import geodetic_to_geocentric_decimal as geodetic_to_geocentric; from decimal import Decimal')
86.26855880199582
>>> timeit.timeit("geodetic_to_geocentric((6378137, 298.257223563), 43.21009, -78.120123, 124)", setup='from __main__ import geodetic_to_geocentric')
1.40149550899514
Lastly, since you cache values of angles in radian, you could as well store some other values that you compute twice in variable. This includes:
sin(lat_rand)
cos(lat_rand)
(1 - 1 / rf) ** 2
And talking about variables, you could make an effort to improve naming a bit. Some variable names may make sense to you as they may be usual in your domain; but rf
, ellps
or N
doesn't convey much meaning to me, for instance.
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I thinka
for semi major axis andrf
for reciprocal flattening are pretty standard notations (they are theproj4
parameters for instance) for ellipsoid parameters.
â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
add a comment |Â
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
7
down vote
I can't explain the discrepancy with the online conversion tool without seeing the source code for the latter.
1. Review
The comment for the ellipsoid parameters should give the units for the semi-major axis. Also, I think "reciprocal flattening" would be a better description of the second parameter, given that you use the name
rf
later on in the code.The docstring for
geodetic_to_geocentric
should give the units (degrees) for the latitude and longitude parameters."Height above ellipsoid" would be a clearer description for the
h
parameter.The parameters to
geodetic_to_geocentric
could be spelled out in full, which would help understand their meaning. Parameter names are documentation too.Comments would help understand the meaning of values like
N
.The expressions
(N + h) * cos(lat)
,sin(lat)
, and(1 - 1 / rf) ** 2
are evaluated twice. These could be stored in local variables to avoid duplicated work.
2. Revised code
from math import cos, radians, sin, sqrt
# Ellipsoid parameters: semi major axis in metres, reciprocal flattening.
GRS80 = 6378137, 298.257222100882711
WGS84 = 6378137, 298.257223563
def geodetic_to_geocentric(ellipsoid, latitude, longitude, height):
"""Return geocentric (Cartesian) Coordinates x, y, z corresponding to
the geodetic coordinates given by latitude and longitude (in
degrees) and height above ellipsoid. The ellipsoid must be
specified by a pair (semi-major axis, reciprocal flattening).
"""
ÃÂ = radians(latitude)
û = radians(longitude)
sin_ÃÂ = sin(ÃÂ)
a, rf = ellipsoid # semi-major axis, reciprocal flattening
e2 = 1 - (1 - 1 / rf) ** 2 # eccentricity squared
n = a / sqrt(1 - e2 * sin_ÃÂ ** 2) # prime vertical radius
r = (n + height) * cos(ÃÂ) # perpendicular distance from z axis
x = r * cos(û)
y = r * sin(û)
z = (n * (1 - e2) + height) * sin_ÃÂ
return x, y, z
Thanks. Technically, the valuee2
is not the eccentricity squared, that would be1 - (1 - 1/rf) ** 2
. Also,n
is theprime vertical radius of curvature
, not the distance from centre (ÃÂ is the Geodetic Latitude, not the Geocentric one).
â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluatinge2
as(2 - 1 / rf) / rf
: if using a different ellipsoid which hasrf
very close to1
or very large, you eliminate potential loss of significance due to subtracting a number very close to1
from1
.
â Peter Taylor
Jun 6 at 14:48
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
add a comment |Â
up vote
7
down vote
I can't explain the discrepancy with the online conversion tool without seeing the source code for the latter.
1. Review
The comment for the ellipsoid parameters should give the units for the semi-major axis. Also, I think "reciprocal flattening" would be a better description of the second parameter, given that you use the name
rf
later on in the code.The docstring for
geodetic_to_geocentric
should give the units (degrees) for the latitude and longitude parameters."Height above ellipsoid" would be a clearer description for the
h
parameter.The parameters to
geodetic_to_geocentric
could be spelled out in full, which would help understand their meaning. Parameter names are documentation too.Comments would help understand the meaning of values like
N
.The expressions
(N + h) * cos(lat)
,sin(lat)
, and(1 - 1 / rf) ** 2
are evaluated twice. These could be stored in local variables to avoid duplicated work.
2. Revised code
from math import cos, radians, sin, sqrt
# Ellipsoid parameters: semi major axis in metres, reciprocal flattening.
GRS80 = 6378137, 298.257222100882711
WGS84 = 6378137, 298.257223563
def geodetic_to_geocentric(ellipsoid, latitude, longitude, height):
"""Return geocentric (Cartesian) Coordinates x, y, z corresponding to
the geodetic coordinates given by latitude and longitude (in
degrees) and height above ellipsoid. The ellipsoid must be
specified by a pair (semi-major axis, reciprocal flattening).
"""
ÃÂ = radians(latitude)
û = radians(longitude)
sin_ÃÂ = sin(ÃÂ)
a, rf = ellipsoid # semi-major axis, reciprocal flattening
e2 = 1 - (1 - 1 / rf) ** 2 # eccentricity squared
n = a / sqrt(1 - e2 * sin_ÃÂ ** 2) # prime vertical radius
r = (n + height) * cos(ÃÂ) # perpendicular distance from z axis
x = r * cos(û)
y = r * sin(û)
z = (n * (1 - e2) + height) * sin_ÃÂ
return x, y, z
Thanks. Technically, the valuee2
is not the eccentricity squared, that would be1 - (1 - 1/rf) ** 2
. Also,n
is theprime vertical radius of curvature
, not the distance from centre (ÃÂ is the Geodetic Latitude, not the Geocentric one).
â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluatinge2
as(2 - 1 / rf) / rf
: if using a different ellipsoid which hasrf
very close to1
or very large, you eliminate potential loss of significance due to subtracting a number very close to1
from1
.
â Peter Taylor
Jun 6 at 14:48
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
add a comment |Â
up vote
7
down vote
up vote
7
down vote
I can't explain the discrepancy with the online conversion tool without seeing the source code for the latter.
1. Review
The comment for the ellipsoid parameters should give the units for the semi-major axis. Also, I think "reciprocal flattening" would be a better description of the second parameter, given that you use the name
rf
later on in the code.The docstring for
geodetic_to_geocentric
should give the units (degrees) for the latitude and longitude parameters."Height above ellipsoid" would be a clearer description for the
h
parameter.The parameters to
geodetic_to_geocentric
could be spelled out in full, which would help understand their meaning. Parameter names are documentation too.Comments would help understand the meaning of values like
N
.The expressions
(N + h) * cos(lat)
,sin(lat)
, and(1 - 1 / rf) ** 2
are evaluated twice. These could be stored in local variables to avoid duplicated work.
2. Revised code
from math import cos, radians, sin, sqrt
# Ellipsoid parameters: semi major axis in metres, reciprocal flattening.
GRS80 = 6378137, 298.257222100882711
WGS84 = 6378137, 298.257223563
def geodetic_to_geocentric(ellipsoid, latitude, longitude, height):
"""Return geocentric (Cartesian) Coordinates x, y, z corresponding to
the geodetic coordinates given by latitude and longitude (in
degrees) and height above ellipsoid. The ellipsoid must be
specified by a pair (semi-major axis, reciprocal flattening).
"""
ÃÂ = radians(latitude)
û = radians(longitude)
sin_ÃÂ = sin(ÃÂ)
a, rf = ellipsoid # semi-major axis, reciprocal flattening
e2 = 1 - (1 - 1 / rf) ** 2 # eccentricity squared
n = a / sqrt(1 - e2 * sin_ÃÂ ** 2) # prime vertical radius
r = (n + height) * cos(ÃÂ) # perpendicular distance from z axis
x = r * cos(û)
y = r * sin(û)
z = (n * (1 - e2) + height) * sin_ÃÂ
return x, y, z
I can't explain the discrepancy with the online conversion tool without seeing the source code for the latter.
1. Review
The comment for the ellipsoid parameters should give the units for the semi-major axis. Also, I think "reciprocal flattening" would be a better description of the second parameter, given that you use the name
rf
later on in the code.The docstring for
geodetic_to_geocentric
should give the units (degrees) for the latitude and longitude parameters."Height above ellipsoid" would be a clearer description for the
h
parameter.The parameters to
geodetic_to_geocentric
could be spelled out in full, which would help understand their meaning. Parameter names are documentation too.Comments would help understand the meaning of values like
N
.The expressions
(N + h) * cos(lat)
,sin(lat)
, and(1 - 1 / rf) ** 2
are evaluated twice. These could be stored in local variables to avoid duplicated work.
2. Revised code
from math import cos, radians, sin, sqrt
# Ellipsoid parameters: semi major axis in metres, reciprocal flattening.
GRS80 = 6378137, 298.257222100882711
WGS84 = 6378137, 298.257223563
def geodetic_to_geocentric(ellipsoid, latitude, longitude, height):
"""Return geocentric (Cartesian) Coordinates x, y, z corresponding to
the geodetic coordinates given by latitude and longitude (in
degrees) and height above ellipsoid. The ellipsoid must be
specified by a pair (semi-major axis, reciprocal flattening).
"""
ÃÂ = radians(latitude)
û = radians(longitude)
sin_ÃÂ = sin(ÃÂ)
a, rf = ellipsoid # semi-major axis, reciprocal flattening
e2 = 1 - (1 - 1 / rf) ** 2 # eccentricity squared
n = a / sqrt(1 - e2 * sin_ÃÂ ** 2) # prime vertical radius
r = (n + height) * cos(ÃÂ) # perpendicular distance from z axis
x = r * cos(û)
y = r * sin(û)
z = (n * (1 - e2) + height) * sin_ÃÂ
return x, y, z
edited Jun 6 at 11:06
answered Jun 6 at 10:29
Gareth Rees
41.1k394166
41.1k394166
Thanks. Technically, the valuee2
is not the eccentricity squared, that would be1 - (1 - 1/rf) ** 2
. Also,n
is theprime vertical radius of curvature
, not the distance from centre (ÃÂ is the Geodetic Latitude, not the Geocentric one).
â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluatinge2
as(2 - 1 / rf) / rf
: if using a different ellipsoid which hasrf
very close to1
or very large, you eliminate potential loss of significance due to subtracting a number very close to1
from1
.
â Peter Taylor
Jun 6 at 14:48
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
add a comment |Â
Thanks. Technically, the valuee2
is not the eccentricity squared, that would be1 - (1 - 1/rf) ** 2
. Also,n
is theprime vertical radius of curvature
, not the distance from centre (ÃÂ is the Geodetic Latitude, not the Geocentric one).
â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluatinge2
as(2 - 1 / rf) / rf
: if using a different ellipsoid which hasrf
very close to1
or very large, you eliminate potential loss of significance due to subtracting a number very close to1
from1
.
â Peter Taylor
Jun 6 at 14:48
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
Thanks. Technically, the value
e2
is not the eccentricity squared, that would be 1 - (1 - 1/rf) ** 2
. Also, n
is the prime vertical radius of curvature
, not the distance from centre (àis the Geodetic Latitude, not the Geocentric one).â YeO
Jun 6 at 10:59
Thanks. Technically, the value
e2
is not the eccentricity squared, that would be 1 - (1 - 1/rf) ** 2
. Also, n
is the prime vertical radius of curvature
, not the distance from centre (àis the Geodetic Latitude, not the Geocentric one).â YeO
Jun 6 at 10:59
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
@YeO: Thanks for the corrections, now fixed.
â Gareth Rees
Jun 6 at 11:06
There's a reasonable numerical analytic case to be made for evaluating
e2
as (2 - 1 / rf) / rf
: if using a different ellipsoid which has rf
very close to 1
or very large, you eliminate potential loss of significance due to subtracting a number very close to 1
from 1
.â Peter Taylor
Jun 6 at 14:48
There's a reasonable numerical analytic case to be made for evaluating
e2
as (2 - 1 / rf) / rf
: if using a different ellipsoid which has rf
very close to 1
or very large, you eliminate potential loss of significance due to subtracting a number very close to 1
from 1
.â Peter Taylor
Jun 6 at 14:48
1
1
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
Upvoted just for using greek letters in variable names. This is a severely under-used feature of Python that could help dramatically simplify the correlation between code and traditional math or academic papers.
â scnerd
Jun 6 at 18:45
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
@PeterTaylor Thanks, that is actually the kind of advice I was looking for! In this case, rf will usually be ~300 as I'm interested in actual coordinates on earth.
â YeO
Jun 6 at 19:19
add a comment |Â
up vote
5
down vote
Precision in numeric calculus is usually handled by the decimal
module. Compare:
>>> 1.001 + 0.0001
1.0010999999999999
>>> Decimal('1.001') + Decimal('0.0001')
Decimal('1.0011')
However, trigonometric functions would convert a decimal.Decimal
back to a float
, thus defeating the purpose. However, the decimal
module provide some recipe to help fill the void. Using the recipe for pi()
you can reimplement math.radians
easily; cos()
and sin()
are covered by the recipes; and math.sqrt
is available directly on Decimal
objects. So you could write:
def geodetic_to_geocentric(ellps, lat, lon, h):
a, rf = ellps
lat_rad = radians(lat)
lon_rad = radians(lon)
N = a / (1 - (1 - (1 - 1 / rf) ** 2) * (sin(lat_rad)) ** 2).sqrt()
X = (N + h) * cos(lat_rad) * cos(lon_rad)
Y = (N + h) * cos(lat_rad) * sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * sin(lat_rad)
return X, Y, Z
Which would yield:
>>> lat = Decimal('43.21009')
>>> lon = Decimal('-78.120123')
>>> h = Decimal('124')
>>> print(geodetic_to_geocentric(wgs84, lat, lon, h))
(Decimal('958506.0102730405668418845812'), Decimal('-4556367.372558064424670955239'), Decimal('4344627.161663229280843240044'))
Which is closer to your result than the one you expected. So I wouldn't worry in this case.
Note however that computation using Decimal
s are usually slower
>>> timeit.timeit("geodetic_to_geocentric((Decimal('6378137'), Decimal('298.257223563')), Decimal('43.21009'), Decimal('-78.120123'), Decimal('124'))", setup='from __main__ import geodetic_to_geocentric_decimal as geodetic_to_geocentric; from decimal import Decimal')
86.26855880199582
>>> timeit.timeit("geodetic_to_geocentric((6378137, 298.257223563), 43.21009, -78.120123, 124)", setup='from __main__ import geodetic_to_geocentric')
1.40149550899514
Lastly, since you cache values of angles in radian, you could as well store some other values that you compute twice in variable. This includes:
sin(lat_rand)
cos(lat_rand)
(1 - 1 / rf) ** 2
And talking about variables, you could make an effort to improve naming a bit. Some variable names may make sense to you as they may be usual in your domain; but rf
, ellps
or N
doesn't convey much meaning to me, for instance.
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I thinka
for semi major axis andrf
for reciprocal flattening are pretty standard notations (they are theproj4
parameters for instance) for ellipsoid parameters.
â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
add a comment |Â
up vote
5
down vote
Precision in numeric calculus is usually handled by the decimal
module. Compare:
>>> 1.001 + 0.0001
1.0010999999999999
>>> Decimal('1.001') + Decimal('0.0001')
Decimal('1.0011')
However, trigonometric functions would convert a decimal.Decimal
back to a float
, thus defeating the purpose. However, the decimal
module provide some recipe to help fill the void. Using the recipe for pi()
you can reimplement math.radians
easily; cos()
and sin()
are covered by the recipes; and math.sqrt
is available directly on Decimal
objects. So you could write:
def geodetic_to_geocentric(ellps, lat, lon, h):
a, rf = ellps
lat_rad = radians(lat)
lon_rad = radians(lon)
N = a / (1 - (1 - (1 - 1 / rf) ** 2) * (sin(lat_rad)) ** 2).sqrt()
X = (N + h) * cos(lat_rad) * cos(lon_rad)
Y = (N + h) * cos(lat_rad) * sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * sin(lat_rad)
return X, Y, Z
Which would yield:
>>> lat = Decimal('43.21009')
>>> lon = Decimal('-78.120123')
>>> h = Decimal('124')
>>> print(geodetic_to_geocentric(wgs84, lat, lon, h))
(Decimal('958506.0102730405668418845812'), Decimal('-4556367.372558064424670955239'), Decimal('4344627.161663229280843240044'))
Which is closer to your result than the one you expected. So I wouldn't worry in this case.
Note however that computation using Decimal
s are usually slower
>>> timeit.timeit("geodetic_to_geocentric((Decimal('6378137'), Decimal('298.257223563')), Decimal('43.21009'), Decimal('-78.120123'), Decimal('124'))", setup='from __main__ import geodetic_to_geocentric_decimal as geodetic_to_geocentric; from decimal import Decimal')
86.26855880199582
>>> timeit.timeit("geodetic_to_geocentric((6378137, 298.257223563), 43.21009, -78.120123, 124)", setup='from __main__ import geodetic_to_geocentric')
1.40149550899514
Lastly, since you cache values of angles in radian, you could as well store some other values that you compute twice in variable. This includes:
sin(lat_rand)
cos(lat_rand)
(1 - 1 / rf) ** 2
And talking about variables, you could make an effort to improve naming a bit. Some variable names may make sense to you as they may be usual in your domain; but rf
, ellps
or N
doesn't convey much meaning to me, for instance.
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I thinka
for semi major axis andrf
for reciprocal flattening are pretty standard notations (they are theproj4
parameters for instance) for ellipsoid parameters.
â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
add a comment |Â
up vote
5
down vote
up vote
5
down vote
Precision in numeric calculus is usually handled by the decimal
module. Compare:
>>> 1.001 + 0.0001
1.0010999999999999
>>> Decimal('1.001') + Decimal('0.0001')
Decimal('1.0011')
However, trigonometric functions would convert a decimal.Decimal
back to a float
, thus defeating the purpose. However, the decimal
module provide some recipe to help fill the void. Using the recipe for pi()
you can reimplement math.radians
easily; cos()
and sin()
are covered by the recipes; and math.sqrt
is available directly on Decimal
objects. So you could write:
def geodetic_to_geocentric(ellps, lat, lon, h):
a, rf = ellps
lat_rad = radians(lat)
lon_rad = radians(lon)
N = a / (1 - (1 - (1 - 1 / rf) ** 2) * (sin(lat_rad)) ** 2).sqrt()
X = (N + h) * cos(lat_rad) * cos(lon_rad)
Y = (N + h) * cos(lat_rad) * sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * sin(lat_rad)
return X, Y, Z
Which would yield:
>>> lat = Decimal('43.21009')
>>> lon = Decimal('-78.120123')
>>> h = Decimal('124')
>>> print(geodetic_to_geocentric(wgs84, lat, lon, h))
(Decimal('958506.0102730405668418845812'), Decimal('-4556367.372558064424670955239'), Decimal('4344627.161663229280843240044'))
Which is closer to your result than the one you expected. So I wouldn't worry in this case.
Note however that computation using Decimal
s are usually slower
>>> timeit.timeit("geodetic_to_geocentric((Decimal('6378137'), Decimal('298.257223563')), Decimal('43.21009'), Decimal('-78.120123'), Decimal('124'))", setup='from __main__ import geodetic_to_geocentric_decimal as geodetic_to_geocentric; from decimal import Decimal')
86.26855880199582
>>> timeit.timeit("geodetic_to_geocentric((6378137, 298.257223563), 43.21009, -78.120123, 124)", setup='from __main__ import geodetic_to_geocentric')
1.40149550899514
Lastly, since you cache values of angles in radian, you could as well store some other values that you compute twice in variable. This includes:
sin(lat_rand)
cos(lat_rand)
(1 - 1 / rf) ** 2
And talking about variables, you could make an effort to improve naming a bit. Some variable names may make sense to you as they may be usual in your domain; but rf
, ellps
or N
doesn't convey much meaning to me, for instance.
Precision in numeric calculus is usually handled by the decimal
module. Compare:
>>> 1.001 + 0.0001
1.0010999999999999
>>> Decimal('1.001') + Decimal('0.0001')
Decimal('1.0011')
However, trigonometric functions would convert a decimal.Decimal
back to a float
, thus defeating the purpose. However, the decimal
module provide some recipe to help fill the void. Using the recipe for pi()
you can reimplement math.radians
easily; cos()
and sin()
are covered by the recipes; and math.sqrt
is available directly on Decimal
objects. So you could write:
def geodetic_to_geocentric(ellps, lat, lon, h):
a, rf = ellps
lat_rad = radians(lat)
lon_rad = radians(lon)
N = a / (1 - (1 - (1 - 1 / rf) ** 2) * (sin(lat_rad)) ** 2).sqrt()
X = (N + h) * cos(lat_rad) * cos(lon_rad)
Y = (N + h) * cos(lat_rad) * sin(lon_rad)
Z = ((1 - 1 / rf) ** 2 * N + h) * sin(lat_rad)
return X, Y, Z
Which would yield:
>>> lat = Decimal('43.21009')
>>> lon = Decimal('-78.120123')
>>> h = Decimal('124')
>>> print(geodetic_to_geocentric(wgs84, lat, lon, h))
(Decimal('958506.0102730405668418845812'), Decimal('-4556367.372558064424670955239'), Decimal('4344627.161663229280843240044'))
Which is closer to your result than the one you expected. So I wouldn't worry in this case.
Note however that computation using Decimal
s are usually slower
>>> timeit.timeit("geodetic_to_geocentric((Decimal('6378137'), Decimal('298.257223563')), Decimal('43.21009'), Decimal('-78.120123'), Decimal('124'))", setup='from __main__ import geodetic_to_geocentric_decimal as geodetic_to_geocentric; from decimal import Decimal')
86.26855880199582
>>> timeit.timeit("geodetic_to_geocentric((6378137, 298.257223563), 43.21009, -78.120123, 124)", setup='from __main__ import geodetic_to_geocentric')
1.40149550899514
Lastly, since you cache values of angles in radian, you could as well store some other values that you compute twice in variable. This includes:
sin(lat_rand)
cos(lat_rand)
(1 - 1 / rf) ** 2
And talking about variables, you could make an effort to improve naming a bit. Some variable names may make sense to you as they may be usual in your domain; but rf
, ellps
or N
doesn't convey much meaning to me, for instance.
answered Jun 6 at 8:47
Mathias Ettinger
21.8k32875
21.8k32875
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I thinka
for semi major axis andrf
for reciprocal flattening are pretty standard notations (they are theproj4
parameters for instance) for ellipsoid parameters.
â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
add a comment |Â
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I thinka
for semi major axis andrf
for reciprocal flattening are pretty standard notations (they are theproj4
parameters for instance) for ellipsoid parameters.
â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I think
a
for semi major axis and rf
for reciprocal flattening are pretty standard notations (they are the proj4
parameters for instance) for ellipsoid parameters.â YeO
Jun 6 at 9:06
thanks for the advice. I always thought caching a value did not retain as much precision as doing the computation each time. With regards to variable naming, fair enough remark, but I think
a
for semi major axis and rf
for reciprocal flattening are pretty standard notations (they are the proj4
parameters for instance) for ellipsoid parameters.â YeO
Jun 6 at 9:06
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
@YeO If they are standard notation for your domain, then it might be fine, it's just that I know very few of it and these names don't "speak" to me.
â Mathias Ettinger
Jun 6 at 9:07
2
2
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@YeO: In Python it makes no difference whether you assign an intermediate value to a variable or not; the value is the same whether or not you give it a name.
â Gareth Rees
Jun 6 at 10:41
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
@GarethRees thanks for this clarification! It's very good to know.
â YeO
Jun 6 at 11:01
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195933%2fconvert-geodetic-coordinates-to-geocentric-cartesian%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
What are the units of the output? How far off is it, in meters?
â 200_success
Jun 6 at 6:41
Units are
m
yes. So it's actually very close but I'm just wondering why the Z computation is off after the 4th decimal when X and Y are pretty consistent.â YeO
Jun 6 at 6:43
2
So, the heights are in agreement to within a tenth of a millimeter, but that's not good enough? What application is this code for?
â 200_success
Jun 6 at 6:48
1
Because I really want to ? :-) More seriously, I want to do this within QGIS where
sympy
,bigfloat
and such aren't available by default.numpy
is actually acceptable as it is available. There won't be a huge amount of points, less than 100 and usually actually around 10~20 max.â YeO
Jun 6 at 8:47
1
Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
â Mathias Ettinger
Jun 6 at 9:22