besselian2shape 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tony Rice
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,146 @@
1
+ Metadata-Version: 2.4
2
+ Name: besselian2shape
3
+ Version: 0.1.0
4
+ Summary: Generate ESRI Shapefiles and Google Earth KML/KMZ files describing a solar eclipse's path from NASA Besselian elements
5
+ Keywords: eclipse,astronomy,gis,shapefile,kml,besselian-elements
6
+ Author: Tony Rice
7
+ Author-email: Tony Rice <rtphokie@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
13
+ Classifier: Topic :: Scientific/Engineering :: GIS
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Operating System :: OS Independent
19
+ Requires-Dist: numpy>=2.4.6
20
+ Requires-Dist: platformdirs>=4.11.15
21
+ Requires-Dist: pyshp>=3.1.6
22
+ Requires-Dist: requests>=2.34.2
23
+ Requires-Python: >=3.11
24
+ Project-URL: Homepage, https://github.com/rtphokie/besselian2shape
25
+ Project-URL: Repository, https://github.com/rtphokie/besselian2shape
26
+ Description-Content-Type: text/markdown
27
+
28
+ # besselian2shape
29
+
30
+ Generate ESRI Shapefiles and Google Earth KML/KMZ files describing a solar
31
+ eclipse's visibility from NASA's Besselian elements dataset: the central
32
+ line, the umbral/antumbral path of totality or annularity, and the
33
+ penumbral region from which at least a partial eclipse is visible.
34
+
35
+ Besselian elements are downloaded once from NASA's [Five Millennium Canon
36
+ of Solar Eclipses](https://eclipse.gsfc.nasa.gov/) and cached locally.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install besselian2shape
42
+ ```
43
+
44
+ Requires Python 3.11+.
45
+
46
+ ## CLI usage
47
+
48
+ ```bash
49
+ besselian2shape YEAR MONTH DAY [options]
50
+ ```
51
+
52
+ `YEAR` uses astronomical numbering for BCE dates (1 BCE is year `0`, 2 BCE
53
+ is year `-1`, etc.).
54
+
55
+ ```bash
56
+ # Shapefiles (default) for the 2024 total eclipse, written to ./eclipse_2024-04-08
57
+ besselian2shape 2024 4 8
58
+
59
+ # KMZ file in a custom directory
60
+ besselian2shape 2017 8 21 -o out/ -f kmz
61
+
62
+ # Every format at once
63
+ besselian2shape 2024 10 2 -f all
64
+ ```
65
+
66
+ Options:
67
+
68
+ | Flag | Default | Description |
69
+ | --- | --- | --- |
70
+ | `-o, --output DIR` | `./eclipse_YYYY-MM-DD` | Output directory |
71
+ | `-f, --format {shp,kml,kmz,all}` | `shp` | Output format; may be repeated |
72
+ | `--step-minutes MINUTES` | `0.5` | Time resolution for sampling the central line and umbral path |
73
+ | `--penumbral-resolution DEGREES` | `0.25` | Grid resolution for the penumbral visibility boundary |
74
+ | `--penumbral-step-minutes MINUTES` | `1.5` | Time step for the penumbral visibility raster scan |
75
+ | `--refresh-cache` | | Re-download the Besselian elements dataset even if already cached |
76
+
77
+ Negative-year (BCE) example:
78
+
79
+ ```bash
80
+ besselian2shape -1999 6 12 -f kml
81
+ ```
82
+
83
+ Shapefile output writes up to three layers into the output directory:
84
+
85
+ - `penumbral_path.shp` -- polygon (all eclipse types)
86
+ - `central_line.shp` -- polyline (total/annular/hybrid only)
87
+ - `umbral_path.shp` -- polygon (total/annular/hybrid only)
88
+
89
+ KML/KMZ output writes a single file (`eclipse.kml` or `eclipse.kmz`)
90
+ containing the same layers as separate placemarks.
91
+
92
+ ## Python API
93
+
94
+ ```python
95
+ from besselian2shape import generate_eclipse_shapefiles, generate_eclipse_kml
96
+
97
+ # Shapefiles
98
+ written = generate_eclipse_shapefiles(2024, 4, 8, "output_dir")
99
+ # {"penumbral_path": Path(...), "central_line": Path(...), "umbral_path": Path(...)}
100
+
101
+ # KML/KMZ (format is inferred from the output path's suffix)
102
+ generate_eclipse_kml(2024, 4, 8, "output_dir/eclipse.kmz")
103
+ ```
104
+
105
+ Both functions raise `LookupError` if no eclipse occurred on the given
106
+ date. Both accept the same optional keyword arguments:
107
+
108
+ - `csv_path`: use a specific Besselian elements CSV instead of the cached
109
+ download.
110
+ - `step_minutes` (default `0.5`): sampling density along the central
111
+ line/umbral path.
112
+ - `penumbral_resolution_deg` / `penumbral_step_minutes` (defaults `0.25`,
113
+ `1.5`): grid resolution and time step for the penumbral coverage
114
+ raster -- lower values are more accurate but slower.
115
+ - `elements`: an already-looked-up `BesselianElements` instance, to avoid
116
+ parsing the CSV twice when generating more than one format for the same
117
+ eclipse:
118
+
119
+ ```python
120
+ from besselian2shape import find_by_date, generate_eclipse_shapefiles, generate_eclipse_kml
121
+
122
+ e = find_by_date(2024, 4, 8)
123
+ generate_eclipse_shapefiles(2024, 4, 8, "output_dir", elements=e)
124
+ generate_eclipse_kml(2024, 4, 8, "output_dir/eclipse.kmz", elements=e)
125
+ ```
126
+
127
+ ### Other public functions
128
+
129
+ - `find_by_date(year, month, day, csv_path=None) -> BesselianElements`
130
+ - `load_all(csv_path=None) -> list[BesselianElements]`
131
+ - `download_besselian_csv(force=False) -> Path`
132
+ - `get_cache_dir() -> Path`
133
+ - `get_cached_csv_path() -> Path`
134
+
135
+ `BesselianElements` is a frozen dataclass holding one eclipse's metadata
136
+ (date, Saros number, gamma, magnitude, path width, etc.) and its raw
137
+ Besselian polynomial coefficients (`x0..x3`, `y0..y3`, `d0..d2`,
138
+ `mu0..mu2`, `l10..l12`, `l20..l22`, `tan_f1`, `tan_f2`, `t0`, `tmin`,
139
+ `tmax`), as published in NASA's dataset.
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ uv sync
145
+ uv run pytest
146
+ ```
@@ -0,0 +1,119 @@
1
+ # besselian2shape
2
+
3
+ Generate ESRI Shapefiles and Google Earth KML/KMZ files describing a solar
4
+ eclipse's visibility from NASA's Besselian elements dataset: the central
5
+ line, the umbral/antumbral path of totality or annularity, and the
6
+ penumbral region from which at least a partial eclipse is visible.
7
+
8
+ Besselian elements are downloaded once from NASA's [Five Millennium Canon
9
+ of Solar Eclipses](https://eclipse.gsfc.nasa.gov/) and cached locally.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install besselian2shape
15
+ ```
16
+
17
+ Requires Python 3.11+.
18
+
19
+ ## CLI usage
20
+
21
+ ```bash
22
+ besselian2shape YEAR MONTH DAY [options]
23
+ ```
24
+
25
+ `YEAR` uses astronomical numbering for BCE dates (1 BCE is year `0`, 2 BCE
26
+ is year `-1`, etc.).
27
+
28
+ ```bash
29
+ # Shapefiles (default) for the 2024 total eclipse, written to ./eclipse_2024-04-08
30
+ besselian2shape 2024 4 8
31
+
32
+ # KMZ file in a custom directory
33
+ besselian2shape 2017 8 21 -o out/ -f kmz
34
+
35
+ # Every format at once
36
+ besselian2shape 2024 10 2 -f all
37
+ ```
38
+
39
+ Options:
40
+
41
+ | Flag | Default | Description |
42
+ | --- | --- | --- |
43
+ | `-o, --output DIR` | `./eclipse_YYYY-MM-DD` | Output directory |
44
+ | `-f, --format {shp,kml,kmz,all}` | `shp` | Output format; may be repeated |
45
+ | `--step-minutes MINUTES` | `0.5` | Time resolution for sampling the central line and umbral path |
46
+ | `--penumbral-resolution DEGREES` | `0.25` | Grid resolution for the penumbral visibility boundary |
47
+ | `--penumbral-step-minutes MINUTES` | `1.5` | Time step for the penumbral visibility raster scan |
48
+ | `--refresh-cache` | | Re-download the Besselian elements dataset even if already cached |
49
+
50
+ Negative-year (BCE) example:
51
+
52
+ ```bash
53
+ besselian2shape -1999 6 12 -f kml
54
+ ```
55
+
56
+ Shapefile output writes up to three layers into the output directory:
57
+
58
+ - `penumbral_path.shp` -- polygon (all eclipse types)
59
+ - `central_line.shp` -- polyline (total/annular/hybrid only)
60
+ - `umbral_path.shp` -- polygon (total/annular/hybrid only)
61
+
62
+ KML/KMZ output writes a single file (`eclipse.kml` or `eclipse.kmz`)
63
+ containing the same layers as separate placemarks.
64
+
65
+ ## Python API
66
+
67
+ ```python
68
+ from besselian2shape import generate_eclipse_shapefiles, generate_eclipse_kml
69
+
70
+ # Shapefiles
71
+ written = generate_eclipse_shapefiles(2024, 4, 8, "output_dir")
72
+ # {"penumbral_path": Path(...), "central_line": Path(...), "umbral_path": Path(...)}
73
+
74
+ # KML/KMZ (format is inferred from the output path's suffix)
75
+ generate_eclipse_kml(2024, 4, 8, "output_dir/eclipse.kmz")
76
+ ```
77
+
78
+ Both functions raise `LookupError` if no eclipse occurred on the given
79
+ date. Both accept the same optional keyword arguments:
80
+
81
+ - `csv_path`: use a specific Besselian elements CSV instead of the cached
82
+ download.
83
+ - `step_minutes` (default `0.5`): sampling density along the central
84
+ line/umbral path.
85
+ - `penumbral_resolution_deg` / `penumbral_step_minutes` (defaults `0.25`,
86
+ `1.5`): grid resolution and time step for the penumbral coverage
87
+ raster -- lower values are more accurate but slower.
88
+ - `elements`: an already-looked-up `BesselianElements` instance, to avoid
89
+ parsing the CSV twice when generating more than one format for the same
90
+ eclipse:
91
+
92
+ ```python
93
+ from besselian2shape import find_by_date, generate_eclipse_shapefiles, generate_eclipse_kml
94
+
95
+ e = find_by_date(2024, 4, 8)
96
+ generate_eclipse_shapefiles(2024, 4, 8, "output_dir", elements=e)
97
+ generate_eclipse_kml(2024, 4, 8, "output_dir/eclipse.kmz", elements=e)
98
+ ```
99
+
100
+ ### Other public functions
101
+
102
+ - `find_by_date(year, month, day, csv_path=None) -> BesselianElements`
103
+ - `load_all(csv_path=None) -> list[BesselianElements]`
104
+ - `download_besselian_csv(force=False) -> Path`
105
+ - `get_cache_dir() -> Path`
106
+ - `get_cached_csv_path() -> Path`
107
+
108
+ `BesselianElements` is a frozen dataclass holding one eclipse's metadata
109
+ (date, Saros number, gamma, magnitude, path width, etc.) and its raw
110
+ Besselian polynomial coefficients (`x0..x3`, `y0..y3`, `d0..d2`,
111
+ `mu0..mu2`, `l10..l12`, `l20..l22`, `tan_f1`, `tan_f2`, `t0`, `tmin`,
112
+ `tmax`), as published in NASA's dataset.
113
+
114
+ ## Development
115
+
116
+ ```bash
117
+ uv sync
118
+ uv run pytest
119
+ ```
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "besselian2shape"
3
+ version = "0.1.0"
4
+ description = "Generate ESRI Shapefiles and Google Earth KML/KMZ files describing a solar eclipse's path from NASA Besselian elements"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ keywords = [
10
+ "eclipse",
11
+ "astronomy",
12
+ "gis",
13
+ "shapefile",
14
+ "kml",
15
+ "besselian-elements",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Science/Research",
20
+ "Topic :: Scientific/Engineering :: Astronomy",
21
+ "Topic :: Scientific/Engineering :: GIS",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Operating System :: OS Independent",
27
+ ]
28
+ dependencies = [
29
+ "numpy>=2.4.6",
30
+ "platformdirs>=4.11.15",
31
+ "pyshp>=3.1.6",
32
+ "requests>=2.34.2",
33
+ ]
34
+
35
+ [[project.authors]]
36
+ name = "Tony Rice"
37
+ email = "rtphokie@gmail.com"
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/rtphokie/besselian2shape"
41
+ Repository = "https://github.com/rtphokie/besselian2shape"
42
+
43
+ [project.scripts]
44
+ besselian2shape = "besselian2shape:main"
45
+
46
+ [build-system]
47
+ requires = ["uv_build>=0.12.19,<0.13.0"]
48
+ build-backend = "uv_build"
49
+
50
+ [dependency-groups]
51
+ dev = ["pytest>=9.1.1"]
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "besselian2shape"
3
+ version = "0.1.0"
4
+ description = "Generate ESRI Shapefiles and Google Earth KML/KMZ files describing a solar eclipse's path from NASA Besselian elements"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "Tony Rice", email = "rtphokie@gmail.com" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["eclipse", "astronomy", "gis", "shapefile", "kml", "besselian-elements"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Science/Research",
16
+ "Topic :: Scientific/Engineering :: Astronomy",
17
+ "Topic :: Scientific/Engineering :: GIS",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Operating System :: OS Independent",
23
+ ]
24
+ dependencies = [
25
+ "numpy>=2.4.6",
26
+ "platformdirs>=4.11.15",
27
+ "pyshp>=3.1.6",
28
+ "requests>=2.34.2",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/rtphokie/besselian2shape"
33
+ Repository = "https://github.com/rtphokie/besselian2shape"
34
+
35
+ [project.scripts]
36
+ besselian2shape = "besselian2shape:main"
37
+
38
+ [build-system]
39
+ requires = ["uv_build>=0.12.19,<0.13.0"]
40
+ build-backend = "uv_build"
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "pytest>=9.1.1",
45
+ ]
@@ -0,0 +1,16 @@
1
+ from .cache import download_besselian_csv, get_cache_dir, get_cached_csv_path
2
+ from .cli import main
3
+ from .elements import BesselianElements, find_by_date, load_all
4
+ from .export import generate_eclipse_kml, generate_eclipse_shapefiles
5
+
6
+ __all__ = [
7
+ "BesselianElements",
8
+ "download_besselian_csv",
9
+ "find_by_date",
10
+ "generate_eclipse_kml",
11
+ "generate_eclipse_shapefiles",
12
+ "get_cache_dir",
13
+ "get_cached_csv_path",
14
+ "load_all",
15
+ "main",
16
+ ]
@@ -0,0 +1,51 @@
1
+ """Download and local caching of NASA's Besselian elements dataset."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ import requests
9
+ from platformdirs import user_cache_dir
10
+
11
+ BESSELIAN_ELEMENTS_URL = (
12
+ "https://eclipse.gsfc.nasa.gov/eclipse_besselian_from_mysqldump2.csv"
13
+ )
14
+
15
+ APP_NAME = "besselian2shape"
16
+
17
+
18
+ def get_cache_dir() -> Path:
19
+ """Return the local cache directory, creating it if necessary."""
20
+ cache_dir = Path(user_cache_dir(APP_NAME))
21
+ cache_dir.mkdir(parents=True, exist_ok=True)
22
+ return cache_dir
23
+
24
+
25
+ def get_cached_csv_path() -> Path:
26
+ """Return the path where the Besselian elements CSV is (or will be) cached."""
27
+ return get_cache_dir() / "eclipse_besselian_from_mysqldump2.csv"
28
+
29
+
30
+ def download_besselian_csv(force: bool = False) -> Path:
31
+ """Ensure the NASA Besselian elements CSV is cached locally, downloading it
32
+ if it is not already present (or if `force` is True).
33
+
34
+ Returns the path to the cached file.
35
+ """
36
+ dest = get_cached_csv_path()
37
+ if dest.exists() and not force:
38
+ return dest
39
+
40
+ response = requests.get(BESSELIAN_ELEMENTS_URL, stream=True, timeout=60)
41
+ response.raise_for_status()
42
+
43
+ tmp_path = dest.with_suffix(dest.suffix + ".part")
44
+ try:
45
+ with open(tmp_path, "wb") as f:
46
+ shutil.copyfileobj(response.raw, f)
47
+ tmp_path.replace(dest)
48
+ finally:
49
+ tmp_path.unlink(missing_ok=True)
50
+
51
+ return dest
@@ -0,0 +1,127 @@
1
+ """Command-line interface for besselian2shape."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from .cache import download_besselian_csv
10
+ from .elements import find_by_date
11
+ from .export import generate_eclipse_kml, generate_eclipse_shapefiles
12
+
13
+ _ALL_FORMATS = ("shp", "kml", "kmz")
14
+
15
+
16
+ def _parse_args(argv: list[str] | None) -> argparse.Namespace:
17
+ parser = argparse.ArgumentParser(
18
+ prog="besselian2shape",
19
+ description="Generate ESRI shapefiles and/or KML/KMZ files for a solar eclipse's path.",
20
+ )
21
+ parser.add_argument(
22
+ "year",
23
+ type=int,
24
+ help="eclipse year, astronomical numbering (1 BCE = 0, 2 BCE = -1, ...)",
25
+ )
26
+ parser.add_argument("month", type=int, help="eclipse month (1-12)")
27
+ parser.add_argument("day", type=int, help="eclipse day of month")
28
+ parser.add_argument(
29
+ "-o",
30
+ "--output",
31
+ type=Path,
32
+ default=None,
33
+ metavar="DIR",
34
+ help="output directory (default: ./eclipse_YYYY-MM-DD)",
35
+ )
36
+ parser.add_argument(
37
+ "-f",
38
+ "--format",
39
+ dest="formats",
40
+ action="append",
41
+ choices=(*_ALL_FORMATS, "all"),
42
+ metavar="{shp,kml,kmz,all}",
43
+ help="output format to generate; may be repeated (default: shp)",
44
+ )
45
+ parser.add_argument(
46
+ "--step-minutes",
47
+ type=float,
48
+ default=0.5,
49
+ metavar="MINUTES",
50
+ help="time resolution used to sample the eclipse path (default: 0.5)",
51
+ )
52
+ parser.add_argument(
53
+ "--penumbral-resolution",
54
+ type=float,
55
+ default=0.25,
56
+ metavar="DEGREES",
57
+ help="grid resolution for the penumbral visibility boundary (default: 0.25)",
58
+ )
59
+ parser.add_argument(
60
+ "--penumbral-step-minutes",
61
+ type=float,
62
+ default=1.5,
63
+ metavar="MINUTES",
64
+ help="time step for the penumbral visibility raster scan (default: 1.5)",
65
+ )
66
+ parser.add_argument(
67
+ "--refresh-cache",
68
+ action="store_true",
69
+ help="re-download the Besselian elements dataset even if already cached",
70
+ )
71
+ return parser.parse_args(argv)
72
+
73
+
74
+ def main(argv: list[str] | None = None) -> int:
75
+ args = _parse_args(argv)
76
+
77
+ formats = list(dict.fromkeys(args.formats or ["shp"])) # de-dupe, preserve order
78
+ if "all" in formats:
79
+ formats = list(_ALL_FORMATS)
80
+
81
+ if args.refresh_cache:
82
+ print("Refreshing cached Besselian elements dataset...", file=sys.stderr)
83
+ download_besselian_csv(force=True)
84
+
85
+ try:
86
+ elements = find_by_date(args.year, args.month, args.day)
87
+ except LookupError as exc:
88
+ print(f"error: {exc}", file=sys.stderr)
89
+ return 1
90
+
91
+ output_dir = args.output or Path(f"eclipse_{args.year:04d}-{args.month:02d}-{args.day:02d}")
92
+
93
+ if "shp" in formats:
94
+ written = generate_eclipse_shapefiles(
95
+ args.year,
96
+ args.month,
97
+ args.day,
98
+ output_dir,
99
+ step_minutes=args.step_minutes,
100
+ elements=elements,
101
+ penumbral_resolution_deg=args.penumbral_resolution,
102
+ penumbral_step_minutes=args.penumbral_step_minutes,
103
+ )
104
+ for name, path in written.items():
105
+ print(f"wrote {name}: {path}")
106
+
107
+ for fmt in ("kml", "kmz"):
108
+ if fmt not in formats:
109
+ continue
110
+ out_path = output_dir / f"eclipse.{fmt}"
111
+ written_path = generate_eclipse_kml(
112
+ args.year,
113
+ args.month,
114
+ args.day,
115
+ out_path,
116
+ step_minutes=args.step_minutes,
117
+ elements=elements,
118
+ penumbral_resolution_deg=args.penumbral_resolution,
119
+ penumbral_step_minutes=args.penumbral_step_minutes,
120
+ )
121
+ print(f"wrote {fmt}: {written_path}")
122
+
123
+ return 0
124
+
125
+
126
+ if __name__ == "__main__":
127
+ raise SystemExit(main())