Skip to content

Commit 0b6a007

Browse files
Merge branch 'main' into intro_text_solar_position_algorithms
2 parents b45d6d5 + dd98591 commit 0b6a007

File tree

15 files changed

+530
-114
lines changed

15 files changed

+530
-114
lines changed

.github/workflows/black_format.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ name: Lint and Auto Format
33
on:
44
push:
55
branches: [main]
6-
pull_request:
76

87
jobs:
98
lint:
@@ -26,7 +25,7 @@ jobs:
2625
run: pip install black
2726

2827
- name: Run Black (format code)
29-
run: black --verbose ./src --jupyter
28+
run: black --verbose ./src
3029

3130
- name: Check for changes
3231
id: check_diff
@@ -54,3 +53,4 @@ jobs:
5453
branch: black-fixes-${{ github.run_id }}
5554
title: "style: auto-format with black"
5655
body: "This PR was created automatically by the lint job to apply Black formatting."
56+
branch: black-fixes-${{ github.run_id }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,6 @@ cython_debug/
189189

190190
# PyPI configuration file
191191
.pypirc
192+
193+
# Skyfield files
194+
*.bsp

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
BSD 3-Clause License
22

3-
Copyright (c) 2021-2022, twoaxistracking Development Team
3+
Copyright (c) 2025, solposx Development Team
44
All rights reserved.
55

66
Redistribution and use in source and binary forms, with or without

docs/source/documentation.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
.. currentmodule:: solposx
22

33

4-
##################
5-
Code documentation
6-
##################
4+
#############
5+
Documentation
6+
#############
77

88
The documentation for the individual functions can be found by navigating to the relevant section below.
99

@@ -12,3 +12,4 @@ The documentation for the individual functions can be found by navigating to the
1212

1313
solarposition
1414
refraction
15+
tools

docs/source/solarposition.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ Solar position algorithms
99

1010
solarposition.iqbal
1111
solarposition.michalsky
12+
solarposition.nasa_horizons
1213
solarposition.noaa
1314
solarposition.psa
1415
solarposition.sg2
1516
solarposition.sg2_c
17+
solarposition.skyfield
1618
solarposition.spa
1719
solarposition.usno
1820
solarposition.walraven

docs/source/tools.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. currentmodule:: solposx
2+
3+
4+
Tools
5+
=====
6+
7+
.. autosummary::
8+
:toctree: generated/
9+
10+
tools.calc_error

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies = [
4646
dynamic = ["version"]
4747

4848
[project.optional-dependencies]
49-
optional = ["skyfield", "sg2"]
49+
optional = ["skyfield>=1.51", "sg2"]
5050
test = ["pytest>=7", "pytest-cov", "packaging"]
5151
doc = [
5252
"sphinx==8.2.3",

src/solposx/refraction/hughes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ def hughes(elevation, pressure=101325., temperature=12.):
3636
.. math::
3737
3838
\begin{align}
39-
& \frac{58.1}{\text{tan}(el)} - \frac{0.07}{\text{tan}(el)^3} + \frac{8.6\cdot 10^{-5}}{tan(el)^5} \text{ for } 5° < el <= 90°\\
40-
& el \cdot (-518.2 + el \cdot (103.4 + el \cdot (-12.79 + el \cdot 0.711))) + 1735 \text{ for } -0.575° < el <= 5°\\
41-
& \frac{-20.774}{\text{tan}(el)} \text{ for } el <= -0.575°\\
39+
5° < el \le 90° &: \quad \frac{58.1}{\text{tan}(el)} - \frac{0.07}{\text{tan}(el)^3} + \frac{8.6\cdot 10^{-5}}{\tan(el)^5}\\
40+
-0.575° < el \le 5° &: \quad el \cdot (-518.2 + el \cdot (103.4 + el \cdot (-12.79 + el \cdot 0.711))) + 1735\\
41+
el \le -0.575° &: \quad \frac{-20.774}{\text{tan}(el)}\\
4242
\end{align}
4343
4444
where :math:`el` is the true (unrefracted) solar elevation angle.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from solposx.solarposition.iqbal import iqbal # noqa: F401
22
from solposx.solarposition.michalsky import michalsky # noqa: F401
3+
from solposx.solarposition.nasa_horizons import nasa_horizons # noqa: F401
34
from solposx.solarposition.noaa import noaa # noqa: F401
45
from solposx.solarposition.psa import psa # noqa: F401
56
from solposx.solarposition.sg2 import sg2 # noqa: F401
67
from solposx.solarposition.sg2 import sg2_c # noqa: F401
8+
from solposx.solarposition.skyfield import skyfield # noqa: F401
79
from pvlib.solarposition import spa_python as spa # noqa: F401
810
from solposx.solarposition.usno import usno # noqa: F401
911
from solposx.solarposition.walraven import walraven # noqa: F401
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import requests
2+
import pandas as pd
3+
import io
4+
5+
URL = 'https://ssd.jpl.nasa.gov/api/horizons.api'
6+
7+
8+
def nasa_horizons(latitude, longitude, start, end, elevation=0.,
9+
time_step='1h', url=URL):
10+
"""
11+
Retrieve solar positions from NASA's Horizons web service.
12+
13+
The NASA Horizons [1]_ is an online solar system data and ephemeris
14+
computation service, which among other things can provide highly
15+
accurate calcultions of sun positions.
16+
17+
The NASA Horizons API is described in [2]_.
18+
19+
Parameters
20+
----------
21+
latitude : float
22+
Latitude in decimal degrees. Positive north of equator, negative
23+
to south. [degrees]
24+
longitude : float
25+
Longitude in decimal degrees. Positive east of prime meridian,
26+
negative to west. [degrees]
27+
start : datetime-like
28+
Start of the requested time period.
29+
end : datetime-like
30+
End of the requested time period.
31+
elevation : float, optional
32+
Elevation of the point of interest [m]. The default is 0 m.
33+
time_step : str, optional
34+
Time step size of the requested time series. '1m' for minutes,
35+
'1h' for hours, '1d' for days, '1mo' for months, and '1y' for years.
36+
The default is '1h'.
37+
url : str, optional
38+
API endpoint. The default is :const:`URL`.
39+
40+
Returns
41+
-------
42+
pandas.DataFrame
43+
DataFrame with the following columns (all values in degrees):
44+
45+
- uncertainty_right_ascension
46+
- uncertainty_declination
47+
- right_ascension
48+
- declination
49+
- apparent_right_ascesion
50+
- apparent_declination
51+
- apparent_azimuth
52+
- apparent_elevation
53+
54+
References
55+
----------
56+
.. [1] NASA Horizons Systems
57+
<https://ssd.jpl.nasa.gov/horizons/>`_
58+
.. [2] NASA Horizons API
59+
<https://ssd-api.jpl.nasa.gov/doc/horizons.html/>`_
60+
"""
61+
params = {
62+
"MAKE_EPHEM": "YES", # generate ephemeris
63+
"COMMAND": "10", # the sun
64+
"EPHEM_TYPE": "OBSERVER", # telescope observations
65+
"CENTER": "coord@399", # input coordinates for Earth (399)
66+
"COORD_TYPE": "GEODETIC", # latitude, longitude, elevation in degrees
67+
"SITE_COORD": f"{longitude},{latitude},{elevation/1000}",
68+
"START_TIME": pd.Timestamp(start).strftime('%Y-%m-%d %H:%M'),
69+
"STOP_TIME": pd.Timestamp(end).strftime('%Y-%m-%d %H:%M'),
70+
"STEP_SIZE": time_step,
71+
"QUANTITIES": "1,2,4,36",
72+
"REF_SYSTEM": "ICRF",
73+
"CAL_FORMAT": "CAL", # output date format
74+
"CAL_TYPE": "MIXED", # Gregorian or mixed Julian/Gregorian calendar
75+
"TIME_DIGITS": "FRACSEC", # output time precision
76+
"ANG_FORMAT": "DEG", # output angles in degrees
77+
"APPARENT": "AIRLESS", # no refraction
78+
# "RANGE_UNITS": "AU",
79+
# "SUPPRESS_RANGE_RATE": "NO",
80+
"SKIP_DAYLT": "NO", # include daylight periods
81+
"SOLAR_ELONG": "0,180",
82+
"EXTRA_PREC": "NO", # toggle additional digits on some angles (RA/DEC)
83+
"CSV_FORMAT": "NO",
84+
"OBJ_DATA": "NO" # whether to return summary data
85+
}
86+
87+
# manual formatting of the url as all parameters except format shall be
88+
# in enclosed in single quotes
89+
url_formatted = (
90+
url
91+
+ '?'
92+
+ "format=text&"
93+
+ '&'.join([f"{k}='{v}'" for k, v in params.items()])
94+
)
95+
96+
res = requests.get(url_formatted)
97+
98+
fbuf = io.StringIO(res.content.decode())
99+
100+
lines = fbuf.readlines()
101+
first_line = lines.index('$$SOE\n') + 1
102+
last_line = lines.index('$$EOE\n', first_line)
103+
header_line = lines[first_line - 3]
104+
data_str = [header_line] + lines[first_line: last_line]
105+
106+
data = pd.read_fwf(io.StringIO('\n'.join(data_str)),
107+
index_col=[0], na_values=['n.a.'])
108+
data.index = pd.to_datetime(data.index, format='%Y-%b-%d %H:%M:%S.%f')
109+
data.index = data.index.tz_localize('UTC')
110+
111+
data = data.rename(columns={
112+
'Unnamed: 1': 'units',
113+
'RA_3sigma': 'uncertainty_right_ascension',
114+
'DEC_3sigma': 'uncertainty_declination',
115+
})
116+
117+
# split columns as several params have a shared header name for two params
118+
column_name_split_map = {
119+
'R.A.___(ICRF)___DEC': ['right_ascension', 'declination'],
120+
'R.A._(a-appar)_DEC.': ['apparent_right_ascesion', 'apparent_declination'],
121+
'Azi____(a-app)___Elev': ['apparent_azimuth', 'apparent_elevation'],
122+
}
123+
124+
for old_name, new_names in column_name_split_map.items():
125+
data[new_names] = \
126+
data[old_name].str.split(r'\s+', expand=True).astype(float)
127+
128+
data = data.drop(columns=list(column_name_split_map.keys()))
129+
130+
data.index.name = 'time'
131+
try:
132+
del data['units']
133+
except KeyError:
134+
pass
135+
136+
return data

0 commit comments

Comments
 (0)