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.
- besselian2shape-0.1.0/LICENSE +21 -0
- besselian2shape-0.1.0/PKG-INFO +146 -0
- besselian2shape-0.1.0/README.md +119 -0
- besselian2shape-0.1.0/pyproject.toml +51 -0
- besselian2shape-0.1.0/pyproject.toml.orig +45 -0
- besselian2shape-0.1.0/src/besselian2shape/__init__.py +16 -0
- besselian2shape-0.1.0/src/besselian2shape/cache.py +51 -0
- besselian2shape-0.1.0/src/besselian2shape/cli.py +127 -0
- besselian2shape-0.1.0/src/besselian2shape/elements.py +138 -0
- besselian2shape-0.1.0/src/besselian2shape/export.py +178 -0
- besselian2shape-0.1.0/src/besselian2shape/geometry.py +612 -0
- besselian2shape-0.1.0/src/besselian2shape/kml.py +210 -0
- besselian2shape-0.1.0/src/besselian2shape/raster.py +467 -0
- besselian2shape-0.1.0/src/besselian2shape/shapefiles.py +136 -0
|
@@ -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())
|