-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrong calculation of obscuring feature calculation #89
Comments
I think it's more than that. The initial test of I'm stuck on version 2.2, and my workaround is to negate distance and reverse the elements of the tuple, so pass: |
Actually, that only works for positive heights. So, to work for both positive and negative (relative) heights, the workaround should be: observer_elevation = (-copysign(1, height) * distance, fabs(height)) Oh, and apparently, it's possible for floats to be -0.0 in addition to 0.0 where -0.0 != 0.0. So, that initial test should probably also be |
Here is what I'd recommend: def adjust_to_obscuring_feature(elevation: Tuple[float, float]) -> float:
"""Calculate the number of degrees to adjust for an obscuring feature"""
height, distance = elevation
assert not distance < 0
return -copysign(1, height) * degrees(
acos(distance / sqrt(distance * distance + height * height))
) |
Please note that the parameter elevation defines the position of the observer relative to the obstacle and not the geometry of the obstacle (see also https://sffjunkie.github.io/astral/index.html#elevation-of-the-sun). E.g.: Test cases for the correct implementation therefore include: Given that, the correct implementation is achieved by replacing
(the other parts of the function are taken from https://github.com/sffjunkie/astral/blob/master/src/astral/sun.py without modification. Especially, |
Your implementation produces the same values as what I suggested, but still with an incorrect sign. >>> for e in ((0, 5000), (10, 5000), (500, 5000), (-10, 5000), (-500, 5000)):
... print(adjust_to_obscuring_feature_mine(e), adjust_to_obscuring_feature(e))
...
-0.0 0.0
-0.11459140623948247 0.11459140623778596
-5.710593137499696 5.710593137499642
0.11459140623948247 -0.11459140623778596
5.710593137499696 -5.710593137499642 E.g., you say:
If the obstruction is 10 meters higher than the observer, then the sunrise should be slightly later, and the sunset should be slightly earlier. Also, "Especially, elevation[0]=0 is a valid input and should return 0.0)", which is true, but you see it still does even without that: >>> def adjust_to_obscuring_feature(elevation: Tuple[float, float]) -> float:
... """ Calculate the number of degrees to adjust for an obscuring feature"""
... sign = -1 if elevation[0] < 0.0 else 1
... return sign * math.degrees(
... math.asin(math.fabs(elevation[0]) / math.sqrt(pow(elevation[0], 2) + pow(elevation[1], 2)))
... )
...
>>> adjust_to_obscuring_feature((0, 5000))
0.0 So, bottom line, either way works, as long as yours is changed to negate the return value. |
I think you quoted the wrong part of the docs. You should look here: https://sffjunkie.github.io/astral/index.html#times-of-the-sun Especially where it says, "For the second case pass a tuple of 2 floats. The first being the vertical distance to the top of the feature and the second the horizontal distance to the feature." This clearly means the opposite of what you explained. Also, although poorly documented in the code, there are places where it says the same thing. I think it's reasonable to state the documentation and implementation are currently in need of change with respect to this issue. Which makes me wonder. Is this package still being maintained? There hasn't been a release or commit in over a year. |
FWIW: >>> timeit.timeit("adjust_to_obscuring_feature_mine((500, 5000))", globals=globals())
0.3826604629866779
>>> timeit.timeit("adjust_to_obscuring_feature((500, 5000))", globals=globals())
0.47058659396134317 |
You did not test the module as a whole calculating sunset and sunrise. The return value of adjust_to_obscuring_feature must meet the requirements of the other methods in the module. Example: import astral
import datetime as dt
def adjust_to_obscuring_feature(elevation: Tuple[float, float]) -> float:
""" Calculate the number of degrees to adjust for an obscuring feature
correcting error in astral.sun: https://github.com/sffjunkie/astral/blob/master/src/astral/sun.py line 249
use asin instead of acos
"""
if elevation[0] == 0.0:
return 0.0
sign = -1 if elevation[0] < 0.0 else 1
return sign * math.degrees(
math.asin(math.fabs(elevation[0]) / math.sqrt(pow(elevation[0], 2) + pow(elevation[1], 2)))
)
# replace method in module
astral.sun.adjust_to_obscuring_feature = adjust_to_obscuring_feature
# define location
LATITUDE = 47.8
LONGITUDE = 11.8
ELEVATION = (-10, 5000)
LOCATION = astral.Observer(LATITUDE, LONGITUDE, ELEVATION)
print(astral.sun.sun(LOCATION, date=dt.datetime.now().date())["sunrise"])
print(astral.sun.sun(LOCATION, date=dt.datetime.now().date())["sunset"]) This is what needs to work properly. |
Yes, I did, I just did not share all those details here. It comes down to how "height" (or elevation[0]) is defined. If it is defined as "height of observer relative to the obstruction", then your implementation works. However, if it is defined as "height of obstruction relative to observer", as I believe the documentation and code defines it, then it doesn't. Both implementations "work", in that they both return a correct absolute value of the result, with the only issue being the definition of height and the needed sign that implies. I believe the implementation I suggested is a bit simpler and faster, but again, either is fine, as long as the definition of height is addressed. |
You are right, it boils down to a definition. To me the line LOCATION = astral.Observer(LATITUDE, LONGITUDE, ELEVATION) seems to define the location of the observer, including his/her elevation. Positive Regarding the performance of the implementation, we could improve it like so: def adjust_to_obscuring_feature(elevation: Tuple[float, float]) -> float:
""" Calculate the number of degrees to adjust for an obscuring feature"""
if elevation[0] == 0.0:
return 0.0
return math.degrees(math.asin(elevation[0]) / math.sqrt(elevation[0]*elevation[0] + elevation[1]*elevation[1])) The handling of the sign is obsolete if |
I think there is a bug in method
adjust_to_obscuring_feature
in modulesun.py
in line 249:If
elevation[0]
is the height difference andelevation[1]
is the distance to obscuring feature, then the calculation should useasin
instead ofacos
:asin(fabs(elevation[0]) / sqrt(pow(elevation[0], 2) + pow(elevation[1], 2)))
The text was updated successfully, but these errors were encountered: