kuva-metadata 1.1.7__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.
- kuva_metadata-1.1.7/.gitignore +134 -0
- kuva_metadata-1.1.7/PKG-INFO +83 -0
- kuva_metadata-1.1.7/README.md +65 -0
- kuva_metadata-1.1.7/kuva_metadata/__init__.py +25 -0
- kuva_metadata-1.1.7/kuva_metadata/custom_types.py +36 -0
- kuva_metadata-1.1.7/kuva_metadata/geometry_utils.py +189 -0
- kuva_metadata-1.1.7/kuva_metadata/helper_types.py +13 -0
- kuva_metadata-1.1.7/kuva_metadata/py.typed +0 -0
- kuva_metadata-1.1.7/kuva_metadata/sections_common.py +390 -0
- kuva_metadata-1.1.7/kuva_metadata/sections_l0.py +701 -0
- kuva_metadata-1.1.7/kuva_metadata/sections_l1.py +102 -0
- kuva_metadata-1.1.7/kuva_metadata/sections_l2.py +60 -0
- kuva_metadata-1.1.7/kuva_metadata/serializers.py +88 -0
- kuva_metadata-1.1.7/kuva_metadata/utils.py +3 -0
- kuva_metadata-1.1.7/kuva_metadata/validators.py +236 -0
- kuva_metadata-1.1.7/poetry.lock +964 -0
- kuva_metadata-1.1.7/pyproject.toml +56 -0
- kuva_metadata-1.1.7/tests/__init__.py +0 -0
- kuva_metadata-1.1.7/tests/test_data/band_metadata.json +55 -0
- kuva_metadata-1.1.7/tests/test_products.py +90 -0
- kuva_metadata-1.1.7/uv.lock +634 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
*.ipynb
|
|
12
|
+
build/
|
|
13
|
+
develop-eggs/
|
|
14
|
+
dist/
|
|
15
|
+
downloads/
|
|
16
|
+
eggs/
|
|
17
|
+
.eggs/
|
|
18
|
+
lib/
|
|
19
|
+
lib64/
|
|
20
|
+
parts/
|
|
21
|
+
sdist/
|
|
22
|
+
var/
|
|
23
|
+
wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
.hypothesis/
|
|
49
|
+
.pytest_cache/
|
|
50
|
+
|
|
51
|
+
# Translations
|
|
52
|
+
*.mo
|
|
53
|
+
*.pot
|
|
54
|
+
|
|
55
|
+
# Django stuff:
|
|
56
|
+
*.log
|
|
57
|
+
local_settings.py
|
|
58
|
+
db.sqlite3
|
|
59
|
+
|
|
60
|
+
# Flask stuff:
|
|
61
|
+
instance/
|
|
62
|
+
.webassets-cache
|
|
63
|
+
|
|
64
|
+
# Torch stuff
|
|
65
|
+
lightning_logs/
|
|
66
|
+
|
|
67
|
+
# Scrapy stuff:
|
|
68
|
+
.scrapy
|
|
69
|
+
|
|
70
|
+
# Sphinx documentation
|
|
71
|
+
docs/_build/
|
|
72
|
+
|
|
73
|
+
# PyBuilder
|
|
74
|
+
target/
|
|
75
|
+
|
|
76
|
+
# Jupyter Notebook
|
|
77
|
+
.ipynb_checkpoints
|
|
78
|
+
|
|
79
|
+
# VSCode
|
|
80
|
+
.vscode
|
|
81
|
+
|
|
82
|
+
# pyenv
|
|
83
|
+
.python-version
|
|
84
|
+
|
|
85
|
+
# celery beat schedule file
|
|
86
|
+
celerybeat-schedule
|
|
87
|
+
|
|
88
|
+
# SageMath parsed files
|
|
89
|
+
*.sage.py
|
|
90
|
+
|
|
91
|
+
# Environments
|
|
92
|
+
.env
|
|
93
|
+
.venv
|
|
94
|
+
env/
|
|
95
|
+
venv/
|
|
96
|
+
ENV/
|
|
97
|
+
env.bak/
|
|
98
|
+
venv.bak/
|
|
99
|
+
|
|
100
|
+
# Spyder project settings
|
|
101
|
+
.spyderproject
|
|
102
|
+
.spyproject
|
|
103
|
+
|
|
104
|
+
# Rope project settings
|
|
105
|
+
.ropeproject
|
|
106
|
+
|
|
107
|
+
# mkdocs documentation
|
|
108
|
+
/site
|
|
109
|
+
|
|
110
|
+
# mypy
|
|
111
|
+
.mypy_cache/
|
|
112
|
+
|
|
113
|
+
# Direnv stuff
|
|
114
|
+
.direnv/
|
|
115
|
+
.envrc
|
|
116
|
+
|
|
117
|
+
# Poetry
|
|
118
|
+
# poetry.lock
|
|
119
|
+
|
|
120
|
+
# Supervisord
|
|
121
|
+
supervisord.log
|
|
122
|
+
supervisord.pid
|
|
123
|
+
|
|
124
|
+
# Debug folders
|
|
125
|
+
_debug/
|
|
126
|
+
|
|
127
|
+
# Kuva data
|
|
128
|
+
*.tif
|
|
129
|
+
*.tiff
|
|
130
|
+
*.npy
|
|
131
|
+
hyperfield*.json
|
|
132
|
+
|
|
133
|
+
# Do not ignore Kuva files in the test_data directory
|
|
134
|
+
!kuva-reader/tests/test_data/**
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kuva-metadata
|
|
3
|
+
Version: 1.1.7
|
|
4
|
+
Summary: Definitions of Kuva Space product metadata
|
|
5
|
+
Author-email: Guillem Ballesteros <guillem@kuvaspace.com>, Lennert Antson <lennert.antson@kuvaspace.com>, Arthur Vandenhoeke <arthur.vandenhoeke@kuvaspace.com>, Olli Eloranta <olli.eloranta@kuvaspace.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: <=3.13,>=3.10
|
|
8
|
+
Requires-Dist: kuva-geometry<2.0.0,>=1.0.1
|
|
9
|
+
Requires-Dist: networkx<4.0.0,>=3.4.2
|
|
10
|
+
Requires-Dist: numpy-quaternion>=2022.4.4
|
|
11
|
+
Requires-Dist: numpy>=1.26.4
|
|
12
|
+
Requires-Dist: pint<1.0.0,>=0.22
|
|
13
|
+
Requires-Dist: pydantic<3.0.0,>=2.9.2
|
|
14
|
+
Requires-Dist: rasterio<2,>=1.4.3
|
|
15
|
+
Requires-Dist: shapely<3.0.0,>=2.0.6
|
|
16
|
+
Requires-Dist: sympy<2.0.0,>=1.13.3
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
<picture>
|
|
21
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/1d8b44f1-1999-4cfb-8744-32871056c253">
|
|
22
|
+
<img alt="kuva-space-logo" src="https://github.com/user-attachments/assets/d8f47cc8-1491-4d0c-a8cf-318ea7e0afdc" width="50%">
|
|
23
|
+
</picture>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
# Kuva Metadata
|
|
27
|
+
|
|
28
|
+
Images taken from a satellite are complicated beasts with lot of metadata associated
|
|
29
|
+
to them. This repository contains the metadata definitions for the Hyperfield products.
|
|
30
|
+
This metadata along with the acquired GeoTIFF images form the Kuva Space products in its
|
|
31
|
+
various processing levels.
|
|
32
|
+
|
|
33
|
+
With the metadata and images, we may process products to the
|
|
34
|
+
next processing levels, or do more precise processing than just with a GeoTIFF.
|
|
35
|
+
|
|
36
|
+
# Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install kuva-metadata
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This package is also included when installing the `kuva-reader`.
|
|
43
|
+
|
|
44
|
+
### Requirements
|
|
45
|
+
|
|
46
|
+
`Python 3.10` to `3.13`, preferably within a virtual environment
|
|
47
|
+
|
|
48
|
+
# Processing levels
|
|
49
|
+
|
|
50
|
+
Currently there are metadata definitions for the following processing levels of Kuva products:
|
|
51
|
+
|
|
52
|
+
- **L0**: Radiometrically corrected frames as TOA radiance
|
|
53
|
+
- **L1AB**: Band-aligned product formed from multiple L0 products
|
|
54
|
+
- **L1C**: Georeferences and orthorectified L1 product
|
|
55
|
+
- **L2A**: Atmospherically corrected product as BOA reflectance
|
|
56
|
+
|
|
57
|
+
# Architecture
|
|
58
|
+
|
|
59
|
+
All the metadata are defined as Pydantic models, this has several advantages:
|
|
60
|
+
|
|
61
|
+
- A very rich set of validations that are applied before data object construction
|
|
62
|
+
- The ability to easily (de)serialize (from)to JSON
|
|
63
|
+
|
|
64
|
+
# Contributing
|
|
65
|
+
|
|
66
|
+
Please follow the guidelines in [CONTRIBUTING.md](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CONTRIBUTING.md).
|
|
67
|
+
|
|
68
|
+
Also, please follow our [Code of Conduct](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CODE_OF_CONDUCT.md)
|
|
69
|
+
while discussing in the issues and pull requests.
|
|
70
|
+
|
|
71
|
+
# Contact information
|
|
72
|
+
|
|
73
|
+
For questions or support, please open an issue. If you have been given a support contact,
|
|
74
|
+
feel free to send them an email explaining your issue.
|
|
75
|
+
|
|
76
|
+
# License
|
|
77
|
+
|
|
78
|
+
The `kuva-reader` project software is under the [MIT license](https://github.com/KuvaSpace/kuva-data-processing/blob/main/LICENSE.md).
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Status of unit tests
|
|
82
|
+
|
|
83
|
+
[](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/1d8b44f1-1999-4cfb-8744-32871056c253">
|
|
4
|
+
<img alt="kuva-space-logo" src="https://github.com/user-attachments/assets/d8f47cc8-1491-4d0c-a8cf-318ea7e0afdc" width="50%">
|
|
5
|
+
</picture>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
# Kuva Metadata
|
|
9
|
+
|
|
10
|
+
Images taken from a satellite are complicated beasts with lot of metadata associated
|
|
11
|
+
to them. This repository contains the metadata definitions for the Hyperfield products.
|
|
12
|
+
This metadata along with the acquired GeoTIFF images form the Kuva Space products in its
|
|
13
|
+
various processing levels.
|
|
14
|
+
|
|
15
|
+
With the metadata and images, we may process products to the
|
|
16
|
+
next processing levels, or do more precise processing than just with a GeoTIFF.
|
|
17
|
+
|
|
18
|
+
# Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install kuva-metadata
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This package is also included when installing the `kuva-reader`.
|
|
25
|
+
|
|
26
|
+
### Requirements
|
|
27
|
+
|
|
28
|
+
`Python 3.10` to `3.13`, preferably within a virtual environment
|
|
29
|
+
|
|
30
|
+
# Processing levels
|
|
31
|
+
|
|
32
|
+
Currently there are metadata definitions for the following processing levels of Kuva products:
|
|
33
|
+
|
|
34
|
+
- **L0**: Radiometrically corrected frames as TOA radiance
|
|
35
|
+
- **L1AB**: Band-aligned product formed from multiple L0 products
|
|
36
|
+
- **L1C**: Georeferences and orthorectified L1 product
|
|
37
|
+
- **L2A**: Atmospherically corrected product as BOA reflectance
|
|
38
|
+
|
|
39
|
+
# Architecture
|
|
40
|
+
|
|
41
|
+
All the metadata are defined as Pydantic models, this has several advantages:
|
|
42
|
+
|
|
43
|
+
- A very rich set of validations that are applied before data object construction
|
|
44
|
+
- The ability to easily (de)serialize (from)to JSON
|
|
45
|
+
|
|
46
|
+
# Contributing
|
|
47
|
+
|
|
48
|
+
Please follow the guidelines in [CONTRIBUTING.md](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CONTRIBUTING.md).
|
|
49
|
+
|
|
50
|
+
Also, please follow our [Code of Conduct](https://github.com/KuvaSpace/kuva-data-processing/blob/main/CODE_OF_CONDUCT.md)
|
|
51
|
+
while discussing in the issues and pull requests.
|
|
52
|
+
|
|
53
|
+
# Contact information
|
|
54
|
+
|
|
55
|
+
For questions or support, please open an issue. If you have been given a support contact,
|
|
56
|
+
feel free to send them an email explaining your issue.
|
|
57
|
+
|
|
58
|
+
# License
|
|
59
|
+
|
|
60
|
+
The `kuva-reader` project software is under the [MIT license](https://github.com/KuvaSpace/kuva-data-processing/blob/main/LICENSE.md).
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Status of unit tests
|
|
64
|
+
|
|
65
|
+
[](https://github.com/KuvaSpace/kuva-data-processing/actions/workflows/test-kuva-metadata.yml)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This library defines the metadata format used by the sidecar files that accompany
|
|
3
|
+
the images produced by Hyperfield satellites.
|
|
4
|
+
|
|
5
|
+
The core library used is pydantic which among other nice things allows us to
|
|
6
|
+
(de)serialize (from)to JSON .
|
|
7
|
+
|
|
8
|
+
If you are interested on reading from the Hyperfield metadata database into such
|
|
9
|
+
objects then you want to look at the `hyperfield-db` project.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.1.2"
|
|
13
|
+
|
|
14
|
+
from .sections_common import MetadataBase
|
|
15
|
+
from .sections_l0 import MetadataLevel0
|
|
16
|
+
from .sections_l1 import MetadataLevel1AB, MetadataLevel1C
|
|
17
|
+
from .sections_l2 import MetadataLevel2A
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"MetadataBase",
|
|
21
|
+
"MetadataLevel0",
|
|
22
|
+
"MetadataLevel1AB",
|
|
23
|
+
"MetadataLevel1C",
|
|
24
|
+
"MetadataLevel2A",
|
|
25
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Custom classes to store in Pydantic models"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
from rasterio.crs import CRS
|
|
6
|
+
from shapely import Point, Polygon, get_coordinates
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CRSGeometry(BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
Store a `shapely.geometry` together with the relevant CRS.
|
|
12
|
+
|
|
13
|
+
Attributes
|
|
14
|
+
----------
|
|
15
|
+
geom: Polygon
|
|
16
|
+
Shapely polygon with the geometry
|
|
17
|
+
crs_epsg: rasterio.crs.CRS
|
|
18
|
+
CRS over which the polygon is represented.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
geom: Polygon | Point
|
|
22
|
+
crs_epsg: CRS
|
|
23
|
+
model_config = ConfigDict(
|
|
24
|
+
validate_assignment=True,
|
|
25
|
+
arbitrary_types_allowed=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def numpy(self) -> np.ndarray:
|
|
30
|
+
"""Turn the geometry into a numpy array of polygon coordinates.
|
|
31
|
+
|
|
32
|
+
Notes
|
|
33
|
+
-----
|
|
34
|
+
This loses the CRS information so make sure to keep track of it elsewhere
|
|
35
|
+
"""
|
|
36
|
+
return get_coordinates(self.geom, include_z=True).squeeze()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Geometry utilities to find the footprint associated with an in orbit camera"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import quaternion
|
|
9
|
+
import shapely
|
|
10
|
+
from kuva_geometry import ellipsoid, geometry
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .sections_l0 import Camera, Frame
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_sensor_corner_rays(
|
|
17
|
+
camera: Camera,
|
|
18
|
+
position: np.ndarray,
|
|
19
|
+
orientation: quaternion.quaternion,
|
|
20
|
+
use_negative_sensor_plane: bool = False,
|
|
21
|
+
):
|
|
22
|
+
"""Get the rays emanating from a camera associated with the corners of the sensor
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
camera
|
|
27
|
+
Camera intrinsic parameters
|
|
28
|
+
position
|
|
29
|
+
The position of the satellite in ECEF coordinates expressed in meters (3 values)
|
|
30
|
+
orientation
|
|
31
|
+
Quaternion describing the orientation of the satellite wrt ECEF
|
|
32
|
+
use_negative_sensor_plane, optional
|
|
33
|
+
Whether to use the negative sensor plane to calculate the rays, by default False
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
The vectors that join the back nodal point to the sensor corners.
|
|
38
|
+
"""
|
|
39
|
+
ray_origin, rays = geometry.get_sensor_corner_rays(
|
|
40
|
+
np.array(position),
|
|
41
|
+
orientation,
|
|
42
|
+
camera.focal_distance.to("meters").magnitude,
|
|
43
|
+
camera.width.to("meters").magnitude,
|
|
44
|
+
camera.height.to("meters").magnitude,
|
|
45
|
+
use_negative_sensor_plane,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return ray_origin, rays
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_sensor_rays(
|
|
52
|
+
sensor_coords: np.ndarray,
|
|
53
|
+
camera: Camera,
|
|
54
|
+
position: np.ndarray,
|
|
55
|
+
orientation: quaternion.quaternion,
|
|
56
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
57
|
+
"""
|
|
58
|
+
Produce an array with the ray vectors for a collections of sensor coords.
|
|
59
|
+
|
|
60
|
+
Sensor coords are measured from the center of the sensor and are valid within
|
|
61
|
+
[-0.5, 0.5]^2.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
sensor_coords
|
|
66
|
+
A rank-2 tensor of dimension (n_sensor_coords, 2)
|
|
67
|
+
camera
|
|
68
|
+
Camera intrinsic parameters
|
|
69
|
+
position
|
|
70
|
+
The position of the satellite in ECEF coordinates expressed in meters (3 values)
|
|
71
|
+
orientation
|
|
72
|
+
Quaternion describing the orientation of the satellite wrt ECEF
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
The position from which the rays emanate, i.e. the camera position and rank 2 tensor
|
|
77
|
+
of dimensions (n_sensor_coords, 3) describing the direction vector of the rays.
|
|
78
|
+
"""
|
|
79
|
+
rays = geometry.get_sensor_rays(
|
|
80
|
+
sensor_coords,
|
|
81
|
+
position,
|
|
82
|
+
orientation,
|
|
83
|
+
camera.focal_distance.to("meters").magnitude,
|
|
84
|
+
camera.width.to("meters").magnitude,
|
|
85
|
+
camera.height.to("meters").magnitude,
|
|
86
|
+
use_negative_sensor_plane=True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return position, rays
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def frame_ray_Earth_intersections(
|
|
93
|
+
sensor_coords: np.ndarray,
|
|
94
|
+
frame: Frame,
|
|
95
|
+
camera: Camera,
|
|
96
|
+
mode: str = "shapely",
|
|
97
|
+
) -> list[shapely.Point] | np.ndarray:
|
|
98
|
+
"""
|
|
99
|
+
Return the intersecton of camera ray with the WGS84 ellipsoid.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
sensor_coords
|
|
104
|
+
The coordinates of the ray we are interested on. Within sensor coords range
|
|
105
|
+
between +- 0.5 on both axis.
|
|
106
|
+
frame
|
|
107
|
+
Information about how the frame was taken. Namely camera orientation and
|
|
108
|
+
position.
|
|
109
|
+
camera
|
|
110
|
+
The camera parameters
|
|
111
|
+
mode, optional
|
|
112
|
+
Whether to return a list of shapely points or a numpy array, by default "shapely"
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
Intersection of camera rays on the Earth
|
|
117
|
+
"""
|
|
118
|
+
sat_pos, sat_ecef_orientation = frame.position.numpy, frame.sat_ecef_orientation
|
|
119
|
+
|
|
120
|
+
# Make sure the order is correct, i.e. begin from the right-hand side!
|
|
121
|
+
# The process is the same as for how we calculate homographies.
|
|
122
|
+
orientation = sat_ecef_orientation * camera.sensor_wrt_sat_axis_quaternion
|
|
123
|
+
|
|
124
|
+
_, rays = get_sensor_rays(sensor_coords, camera, sat_pos, orientation)
|
|
125
|
+
|
|
126
|
+
rays = rays / np.sqrt((rays**2).sum(axis=1))[:, None]
|
|
127
|
+
|
|
128
|
+
intersections = ellipsoid.ray_Earth_intersection_new(sat_pos, rays)
|
|
129
|
+
|
|
130
|
+
match mode:
|
|
131
|
+
case "shapely":
|
|
132
|
+
intersection_points = [
|
|
133
|
+
shapely.Point(
|
|
134
|
+
intersections[idx][0], intersections[idx][1], intersections[idx][2]
|
|
135
|
+
)
|
|
136
|
+
for idx in range(intersections.shape[0])
|
|
137
|
+
]
|
|
138
|
+
case "array":
|
|
139
|
+
intersection_points = intersections
|
|
140
|
+
case _:
|
|
141
|
+
e_ = "The valid modes are 'shapely' and 'array'."
|
|
142
|
+
raise ValueError(e_)
|
|
143
|
+
|
|
144
|
+
return intersection_points
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def frame_footprint(
|
|
148
|
+
frame: Frame,
|
|
149
|
+
camera: Camera,
|
|
150
|
+
use_negative_sensor_plane: bool = False,
|
|
151
|
+
) -> shapely.Polygon:
|
|
152
|
+
"""Find the footprint on the ground associated with the corners rays of a camera
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
frame
|
|
157
|
+
Information about how the frame was taken. Namely camera orientation and
|
|
158
|
+
position.
|
|
159
|
+
camera
|
|
160
|
+
The camera parameters
|
|
161
|
+
use_negative_sensor_plane, optional
|
|
162
|
+
Whether to use the negative sensor plane to calculate the rays, by default False
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
The footprint on the ground as a polygon
|
|
167
|
+
"""
|
|
168
|
+
sat_pos, sat_ecef_orientation = frame.position.numpy, frame.sat_ecef_orientation
|
|
169
|
+
|
|
170
|
+
orientation = sat_ecef_orientation * camera.sensor_wrt_sat_axis_quaternion
|
|
171
|
+
|
|
172
|
+
_, sensor_corners_ray = get_sensor_corner_rays(
|
|
173
|
+
camera, sat_pos, orientation, use_negative_sensor_plane
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
sensor_corners_ray = (
|
|
177
|
+
sensor_corners_ray / np.sqrt((sensor_corners_ray**2).sum(axis=1))[:, None]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
footprint = [
|
|
181
|
+
ellipsoid.ray_Earth_intersection(sat_pos, sensor_corners_ray[idx])
|
|
182
|
+
for idx in range(sensor_corners_ray.shape[0])
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
# Polygons are ordered X,Y despite the CRS being lat, long i.e. x,y. If you
|
|
186
|
+
# leave the points in the order they come in the 4326 CRS things will break.
|
|
187
|
+
footprint = shapely.Polygon(footprint)
|
|
188
|
+
|
|
189
|
+
return footprint
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Additional type aliases"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pint import Quantity
|
|
5
|
+
from quaternion import quaternion
|
|
6
|
+
from shapely import Polygon
|
|
7
|
+
|
|
8
|
+
# All this types with lists would be better expressed with tuples but pydantic
|
|
9
|
+
# reads the json as lists so we have a type to match.
|
|
10
|
+
Quantity_ = Quantity | list[float | str]
|
|
11
|
+
quaternion_ = quaternion | list[float]
|
|
12
|
+
array_3x3_ = np.ndarray | list[list[float]]
|
|
13
|
+
Polygon_ = Polygon | str
|
|
File without changes
|