cars 1.0.0rc2__cp312-cp312-win_amd64.whl
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.
Potentially problematic release.
This version of cars might be problematic. Click here for more details.
- cars/__init__.py +86 -0
- cars/applications/__init__.py +40 -0
- cars/applications/application.py +117 -0
- cars/applications/application_constants.py +29 -0
- cars/applications/application_template.py +146 -0
- cars/applications/auxiliary_filling/__init__.py +29 -0
- cars/applications/auxiliary_filling/abstract_auxiliary_filling_app.py +105 -0
- cars/applications/auxiliary_filling/auxiliary_filling_algo.py +475 -0
- cars/applications/auxiliary_filling/auxiliary_filling_from_sensors_app.py +632 -0
- cars/applications/auxiliary_filling/auxiliary_filling_wrappers.py +90 -0
- cars/applications/dem_generation/__init__.py +30 -0
- cars/applications/dem_generation/abstract_dem_generation_app.py +116 -0
- cars/applications/dem_generation/bulldozer_config/base_config.yaml +42 -0
- cars/applications/dem_generation/bulldozer_dem_app.py +641 -0
- cars/applications/dem_generation/bulldozer_memory.py +55 -0
- cars/applications/dem_generation/dem_generation_algo.py +107 -0
- cars/applications/dem_generation/dem_generation_constants.py +32 -0
- cars/applications/dem_generation/dem_generation_wrappers.py +323 -0
- cars/applications/dense_match_filling/__init__.py +30 -0
- cars/applications/dense_match_filling/abstract_dense_match_filling_app.py +242 -0
- cars/applications/dense_match_filling/fill_disp_algo.py +113 -0
- cars/applications/dense_match_filling/fill_disp_constants.py +39 -0
- cars/applications/dense_match_filling/fill_disp_wrappers.py +83 -0
- cars/applications/dense_match_filling/zero_padding_app.py +302 -0
- cars/applications/dense_matching/__init__.py +30 -0
- cars/applications/dense_matching/abstract_dense_matching_app.py +261 -0
- cars/applications/dense_matching/census_mccnn_sgm_app.py +1461 -0
- cars/applications/dense_matching/cpp/__init__.py +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.dll.a +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.cp312-win_amd64.pyd +0 -0
- cars/applications/dense_matching/cpp/dense_matching_cpp.py +94 -0
- cars/applications/dense_matching/cpp/includes/dense_matching.hpp +58 -0
- cars/applications/dense_matching/cpp/meson.build +9 -0
- cars/applications/dense_matching/cpp/src/bindings.cpp +13 -0
- cars/applications/dense_matching/cpp/src/dense_matching.cpp +207 -0
- cars/applications/dense_matching/dense_matching_algo.py +401 -0
- cars/applications/dense_matching/dense_matching_constants.py +89 -0
- cars/applications/dense_matching/dense_matching_wrappers.py +951 -0
- cars/applications/dense_matching/disparity_grid_algo.py +597 -0
- cars/applications/dense_matching/loaders/__init__.py +23 -0
- cars/applications/dense_matching/loaders/config_census_sgm_default.json +31 -0
- cars/applications/dense_matching/loaders/config_census_sgm_homogeneous.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_mountain_and_vegetation.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_shadow.json +30 -0
- cars/applications/dense_matching/loaders/config_census_sgm_sparse.json +36 -0
- cars/applications/dense_matching/loaders/config_census_sgm_urban.json +30 -0
- cars/applications/dense_matching/loaders/config_mapping.json +13 -0
- cars/applications/dense_matching/loaders/config_mccnn.json +28 -0
- cars/applications/dense_matching/loaders/global_land_cover_map.tif +0 -0
- cars/applications/dense_matching/loaders/pandora_loader.py +593 -0
- cars/applications/dsm_filling/__init__.py +32 -0
- cars/applications/dsm_filling/abstract_dsm_filling_app.py +101 -0
- cars/applications/dsm_filling/border_interpolation_app.py +278 -0
- cars/applications/dsm_filling/bulldozer_config/base_config.yaml +44 -0
- cars/applications/dsm_filling/bulldozer_filling_app.py +288 -0
- cars/applications/dsm_filling/exogenous_filling_app.py +341 -0
- cars/applications/dsm_merging/__init__.py +28 -0
- cars/applications/dsm_merging/abstract_dsm_merging_app.py +101 -0
- cars/applications/dsm_merging/weighted_fusion_app.py +639 -0
- cars/applications/grid_correction/__init__.py +30 -0
- cars/applications/grid_correction/abstract_grid_correction_app.py +103 -0
- cars/applications/grid_correction/grid_correction_app.py +557 -0
- cars/applications/grid_generation/__init__.py +30 -0
- cars/applications/grid_generation/abstract_grid_generation_app.py +142 -0
- cars/applications/grid_generation/epipolar_grid_generation_app.py +327 -0
- cars/applications/grid_generation/grid_generation_algo.py +388 -0
- cars/applications/grid_generation/grid_generation_constants.py +46 -0
- cars/applications/grid_generation/transform_grid.py +88 -0
- cars/applications/ground_truth_reprojection/__init__.py +30 -0
- cars/applications/ground_truth_reprojection/abstract_ground_truth_reprojection_app.py +137 -0
- cars/applications/ground_truth_reprojection/direct_localization_app.py +629 -0
- cars/applications/ground_truth_reprojection/ground_truth_reprojection_algo.py +275 -0
- cars/applications/point_cloud_outlier_removal/__init__.py +30 -0
- cars/applications/point_cloud_outlier_removal/abstract_outlier_removal_app.py +385 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_algo.py +392 -0
- cars/applications/point_cloud_outlier_removal/outlier_removal_constants.py +43 -0
- cars/applications/point_cloud_outlier_removal/small_components_app.py +522 -0
- cars/applications/point_cloud_outlier_removal/statistical_app.py +528 -0
- cars/applications/rasterization/__init__.py +30 -0
- cars/applications/rasterization/abstract_pc_rasterization_app.py +183 -0
- cars/applications/rasterization/rasterization_algo.py +534 -0
- cars/applications/rasterization/rasterization_constants.py +38 -0
- cars/applications/rasterization/rasterization_wrappers.py +639 -0
- cars/applications/rasterization/simple_gaussian_app.py +1152 -0
- cars/applications/resampling/__init__.py +28 -0
- cars/applications/resampling/abstract_resampling_app.py +187 -0
- cars/applications/resampling/bicubic_resampling_app.py +760 -0
- cars/applications/resampling/resampling_algo.py +590 -0
- cars/applications/resampling/resampling_constants.py +36 -0
- cars/applications/resampling/resampling_wrappers.py +309 -0
- cars/applications/sensors_subsampling/__init__.py +32 -0
- cars/applications/sensors_subsampling/abstract_subsampling_app.py +109 -0
- cars/applications/sensors_subsampling/rasterio_subsampling_app.py +420 -0
- cars/applications/sensors_subsampling/subsampling_algo.py +108 -0
- cars/applications/sparse_matching/__init__.py +30 -0
- cars/applications/sparse_matching/abstract_sparse_matching_app.py +599 -0
- cars/applications/sparse_matching/sift_app.py +724 -0
- cars/applications/sparse_matching/sparse_matching_algo.py +360 -0
- cars/applications/sparse_matching/sparse_matching_constants.py +66 -0
- cars/applications/sparse_matching/sparse_matching_wrappers.py +282 -0
- cars/applications/triangulation/__init__.py +32 -0
- cars/applications/triangulation/abstract_triangulation_app.py +227 -0
- cars/applications/triangulation/line_of_sight_intersection_app.py +1243 -0
- cars/applications/triangulation/pc_transform.py +552 -0
- cars/applications/triangulation/triangulation_algo.py +371 -0
- cars/applications/triangulation/triangulation_constants.py +38 -0
- cars/applications/triangulation/triangulation_wrappers.py +259 -0
- cars/bundleadjustment.py +750 -0
- cars/cars.py +179 -0
- cars/conf/__init__.py +23 -0
- cars/conf/geoid/egm96.grd +0 -0
- cars/conf/geoid/egm96.grd.hdr +15 -0
- cars/conf/input_parameters.py +156 -0
- cars/conf/mask_cst.py +35 -0
- cars/core/__init__.py +23 -0
- cars/core/cars_logging.py +402 -0
- cars/core/constants.py +191 -0
- cars/core/constants_disparity.py +50 -0
- cars/core/datasets.py +140 -0
- cars/core/geometry/__init__.py +27 -0
- cars/core/geometry/abstract_geometry.py +1119 -0
- cars/core/geometry/shareloc_geometry.py +598 -0
- cars/core/inputs.py +568 -0
- cars/core/outputs.py +176 -0
- cars/core/preprocessing.py +722 -0
- cars/core/projection.py +843 -0
- cars/core/roi_tools.py +215 -0
- cars/core/tiling.py +774 -0
- cars/core/utils.py +164 -0
- cars/data_structures/__init__.py +23 -0
- cars/data_structures/cars_dataset.py +1544 -0
- cars/data_structures/cars_dict.py +74 -0
- cars/data_structures/corresponding_tiles_tools.py +186 -0
- cars/data_structures/dataframe_converter.py +185 -0
- cars/data_structures/format_transformation.py +297 -0
- cars/devibrate.py +689 -0
- cars/extractroi.py +264 -0
- cars/orchestrator/__init__.py +23 -0
- cars/orchestrator/achievement_tracker.py +125 -0
- cars/orchestrator/cluster/__init__.py +37 -0
- cars/orchestrator/cluster/abstract_cluster.py +250 -0
- cars/orchestrator/cluster/abstract_dask_cluster.py +381 -0
- cars/orchestrator/cluster/dask_cluster_tools.py +103 -0
- cars/orchestrator/cluster/dask_config/README.md +94 -0
- cars/orchestrator/cluster/dask_config/dask.yaml +21 -0
- cars/orchestrator/cluster/dask_config/distributed.yaml +70 -0
- cars/orchestrator/cluster/dask_config/jobqueue.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask-schema.yaml +137 -0
- cars/orchestrator/cluster/dask_config/reference_confs/dask.yaml +26 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed-schema.yaml +1009 -0
- cars/orchestrator/cluster/dask_config/reference_confs/distributed.yaml +273 -0
- cars/orchestrator/cluster/dask_config/reference_confs/jobqueue.yaml +212 -0
- cars/orchestrator/cluster/dask_jobqueue_utils.py +204 -0
- cars/orchestrator/cluster/local_dask_cluster.py +116 -0
- cars/orchestrator/cluster/log_wrapper.py +728 -0
- cars/orchestrator/cluster/mp_cluster/__init__.py +27 -0
- cars/orchestrator/cluster/mp_cluster/mp_factorizer.py +212 -0
- cars/orchestrator/cluster/mp_cluster/mp_objects.py +535 -0
- cars/orchestrator/cluster/mp_cluster/mp_tools.py +93 -0
- cars/orchestrator/cluster/mp_cluster/mp_wrapper.py +505 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_cluster.py +986 -0
- cars/orchestrator/cluster/mp_cluster/multiprocessing_profiler.py +399 -0
- cars/orchestrator/cluster/pbs_dask_cluster.py +207 -0
- cars/orchestrator/cluster/sequential_cluster.py +139 -0
- cars/orchestrator/cluster/slurm_dask_cluster.py +234 -0
- cars/orchestrator/memory_tools.py +47 -0
- cars/orchestrator/orchestrator.py +755 -0
- cars/orchestrator/orchestrator_constants.py +29 -0
- cars/orchestrator/registry/__init__.py +23 -0
- cars/orchestrator/registry/abstract_registry.py +143 -0
- cars/orchestrator/registry/compute_registry.py +106 -0
- cars/orchestrator/registry/id_generator.py +116 -0
- cars/orchestrator/registry/replacer_registry.py +213 -0
- cars/orchestrator/registry/saver_registry.py +363 -0
- cars/orchestrator/registry/unseen_registry.py +118 -0
- cars/orchestrator/tiles_profiler.py +279 -0
- cars/pipelines/__init__.py +26 -0
- cars/pipelines/conf_resolution/conf_final_resolution.yaml +5 -0
- cars/pipelines/conf_resolution/conf_first_resolution.yaml +4 -0
- cars/pipelines/conf_resolution/conf_intermediate_resolution.yaml +2 -0
- cars/pipelines/default/__init__.py +26 -0
- cars/pipelines/default/default_pipeline.py +1088 -0
- cars/pipelines/filling/__init__.py +26 -0
- cars/pipelines/filling/filling.py +981 -0
- cars/pipelines/formatting/__init__.py +26 -0
- cars/pipelines/formatting/formatting.py +186 -0
- cars/pipelines/merging/__init__.py +26 -0
- cars/pipelines/merging/merging.py +439 -0
- cars/pipelines/parameters/__init__.py +0 -0
- cars/pipelines/parameters/advanced_parameters.py +256 -0
- cars/pipelines/parameters/advanced_parameters_constants.py +68 -0
- cars/pipelines/parameters/application_parameters.py +72 -0
- cars/pipelines/parameters/depth_map_inputs.py +0 -0
- cars/pipelines/parameters/dsm_inputs.py +349 -0
- cars/pipelines/parameters/dsm_inputs_constants.py +25 -0
- cars/pipelines/parameters/output_constants.py +52 -0
- cars/pipelines/parameters/output_parameters.py +438 -0
- cars/pipelines/parameters/sensor_inputs.py +859 -0
- cars/pipelines/parameters/sensor_inputs_constants.py +51 -0
- cars/pipelines/parameters/sensor_loaders/__init__.py +29 -0
- cars/pipelines/parameters/sensor_loaders/basic_classif_loader.py +86 -0
- cars/pipelines/parameters/sensor_loaders/basic_image_loader.py +98 -0
- cars/pipelines/parameters/sensor_loaders/pivot_classif_loader.py +90 -0
- cars/pipelines/parameters/sensor_loaders/pivot_image_loader.py +105 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader.py +93 -0
- cars/pipelines/parameters/sensor_loaders/sensor_loader_template.py +71 -0
- cars/pipelines/parameters/sensor_loaders/slurp_classif_loader.py +86 -0
- cars/pipelines/pipeline.py +119 -0
- cars/pipelines/pipeline_constants.py +38 -0
- cars/pipelines/pipeline_template.py +135 -0
- cars/pipelines/subsampling/__init__.py +26 -0
- cars/pipelines/subsampling/subsampling.py +358 -0
- cars/pipelines/surface_modeling/__init__.py +26 -0
- cars/pipelines/surface_modeling/surface_modeling.py +2098 -0
- cars/pipelines/tie_points/__init__.py +26 -0
- cars/pipelines/tie_points/tie_points.py +536 -0
- cars/starter.py +167 -0
- cars-1.0.0rc2.dist-info/DELVEWHEEL +2 -0
- cars-1.0.0rc2.dist-info/METADATA +289 -0
- cars-1.0.0rc2.dist-info/RECORD +225 -0
- cars-1.0.0rc2.dist-info/WHEEL +4 -0
- cars-1.0.0rc2.dist-info/entry_points.txt +8 -0
- cars.libs/libgcc_s_seh-1-b2494fcbd4d80cf2c98fdd5261f6d850.dll +0 -0
- cars.libs/libstdc++-6-e9b0d12ae0e9555bbae55e8dfd08c3f7.dll +0 -0
- cars.libs/libwinpthread-1-7882d1b093714ccdfaf4e0789a817792.dll +0 -0
cars/core/projection.py
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# coding: utf8
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2020 Centre National d'Etudes Spatiales (CNES).
|
|
5
|
+
#
|
|
6
|
+
# This file is part of CARS
|
|
7
|
+
# (see https://github.com/CNES/cars).
|
|
8
|
+
#
|
|
9
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
10
|
+
# you may not use this file except in compliance with the License.
|
|
11
|
+
# You may obtain a copy of the License at
|
|
12
|
+
#
|
|
13
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
14
|
+
#
|
|
15
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
16
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
17
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
18
|
+
# See the License for the specific language governing permissions and
|
|
19
|
+
# limitations under the License.
|
|
20
|
+
#
|
|
21
|
+
"""
|
|
22
|
+
Projection module:
|
|
23
|
+
contains some general purpose functions using polygons and data projections
|
|
24
|
+
"""
|
|
25
|
+
# pylint: disable=C0302(too-many-lines)
|
|
26
|
+
|
|
27
|
+
# Standard imports
|
|
28
|
+
import logging
|
|
29
|
+
import os
|
|
30
|
+
from typing import List, Tuple
|
|
31
|
+
|
|
32
|
+
# Third party imports
|
|
33
|
+
import numpy as np
|
|
34
|
+
import pandas
|
|
35
|
+
import pyproj
|
|
36
|
+
import rasterio as rio
|
|
37
|
+
import xarray as xr
|
|
38
|
+
from pyproj import CRS
|
|
39
|
+
from rasterio.features import shapes
|
|
40
|
+
from shapely.geometry import Polygon, shape
|
|
41
|
+
from shapely.ops import transform
|
|
42
|
+
|
|
43
|
+
from cars.core import constants as cst
|
|
44
|
+
from cars.core import inputs, outputs, utils
|
|
45
|
+
from cars.orchestrator.cluster.log_wrapper import cars_profile
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def compute_dem_intersection_with_poly( # noqa: C901
|
|
49
|
+
srtm_file: str, ref_poly: Polygon, ref_epsg: int
|
|
50
|
+
) -> Polygon:
|
|
51
|
+
"""
|
|
52
|
+
Compute the intersection polygon between the defined dem regions
|
|
53
|
+
and the reference polygon in input
|
|
54
|
+
|
|
55
|
+
:raise Exception: when the input dem doesn't intersect the reference polygon
|
|
56
|
+
|
|
57
|
+
:param srtm_file: srtm file
|
|
58
|
+
:param ref_poly: reference polygon
|
|
59
|
+
:param ref_epsg: reference epsg code
|
|
60
|
+
:return: The intersection polygon between the defined dem regions
|
|
61
|
+
and the reference polygon in input
|
|
62
|
+
"""
|
|
63
|
+
dem_poly = None
|
|
64
|
+
|
|
65
|
+
if os.path.isdir(os.path.abspath(srtm_file)):
|
|
66
|
+
raise RuntimeError("srtm input should be a file and not a directory")
|
|
67
|
+
|
|
68
|
+
if inputs.rasterio_can_open(srtm_file):
|
|
69
|
+
with rio.open(srtm_file) as data:
|
|
70
|
+
xmin = min(data.bounds.left, data.bounds.right)
|
|
71
|
+
ymin = min(data.bounds.bottom, data.bounds.top)
|
|
72
|
+
xmax = max(data.bounds.left, data.bounds.right)
|
|
73
|
+
ymax = max(data.bounds.bottom, data.bounds.top)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
file_epsg = data.crs.to_epsg()
|
|
77
|
+
file_bb = Polygon(
|
|
78
|
+
[
|
|
79
|
+
(xmin, ymin),
|
|
80
|
+
(xmin, ymax),
|
|
81
|
+
(xmax, ymax),
|
|
82
|
+
(xmax, ymin),
|
|
83
|
+
(xmin, ymin),
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# transform polygon if needed
|
|
88
|
+
if ref_epsg != file_epsg:
|
|
89
|
+
file_bb = polygon_projection(file_bb, file_epsg, ref_epsg)
|
|
90
|
+
|
|
91
|
+
left, bottom, right, top = ref_poly.bounds
|
|
92
|
+
|
|
93
|
+
# To ensure that the window used for extracting the SRTM
|
|
94
|
+
# polygons completely contains the ref_poly,
|
|
95
|
+
# a margin must be applied. Using the exact bounds of
|
|
96
|
+
# ref_poly could potentially lead to issues.
|
|
97
|
+
spatial_ref = CRS.from_epsg(ref_epsg)
|
|
98
|
+
if spatial_ref.is_geographic:
|
|
99
|
+
margin = 0.001
|
|
100
|
+
else:
|
|
101
|
+
margin = 50
|
|
102
|
+
|
|
103
|
+
left = left - margin
|
|
104
|
+
right = right + margin
|
|
105
|
+
bottom = bottom - margin
|
|
106
|
+
top = top + margin
|
|
107
|
+
|
|
108
|
+
min_row, min_col = data.index(left, top)
|
|
109
|
+
max_row, max_col = data.index(right, bottom)
|
|
110
|
+
|
|
111
|
+
window = rio.windows.Window(
|
|
112
|
+
col_off=int(min_col),
|
|
113
|
+
row_off=int(min_row),
|
|
114
|
+
width=int(max_col - min_col),
|
|
115
|
+
height=int(max_row - min_row),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# if the srtm tile intersects the reference polygon
|
|
119
|
+
if file_bb.intersects(ref_poly):
|
|
120
|
+
local_dem_poly = None
|
|
121
|
+
|
|
122
|
+
# retrieve valid polygons
|
|
123
|
+
for poly, val in shapes(
|
|
124
|
+
data.read(1, window=window),
|
|
125
|
+
data.dataset_mask(window=window),
|
|
126
|
+
transform=data.window_transform(window),
|
|
127
|
+
):
|
|
128
|
+
if val != 0:
|
|
129
|
+
poly = shape(poly)
|
|
130
|
+
poly = poly.buffer(0)
|
|
131
|
+
if ref_epsg != file_epsg:
|
|
132
|
+
poly = polygon_projection(
|
|
133
|
+
poly, file_epsg, ref_epsg
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# combine valid polygons
|
|
137
|
+
if local_dem_poly is None:
|
|
138
|
+
local_dem_poly = poly
|
|
139
|
+
else:
|
|
140
|
+
local_dem_poly = poly.union(local_dem_poly)
|
|
141
|
+
|
|
142
|
+
# combine the tile valid polygon to the other
|
|
143
|
+
# tiles' ones
|
|
144
|
+
if dem_poly is None:
|
|
145
|
+
dem_poly = local_dem_poly
|
|
146
|
+
else:
|
|
147
|
+
dem_poly = dem_poly.union(local_dem_poly)
|
|
148
|
+
|
|
149
|
+
except AttributeError as attribute_error:
|
|
150
|
+
logging.warning(
|
|
151
|
+
"Impossible to read the SRTM"
|
|
152
|
+
"tile epsg code: {}".format(attribute_error)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# compute dem coverage polygon over the reference polygon
|
|
156
|
+
if dem_poly is None or not dem_poly.intersects(ref_poly):
|
|
157
|
+
raise RuntimeError("The input DEM does not intersect the useful zone")
|
|
158
|
+
|
|
159
|
+
dem_cover = dem_poly.intersection(ref_poly)
|
|
160
|
+
|
|
161
|
+
area_cover = dem_cover.area
|
|
162
|
+
area_inter = ref_poly.area
|
|
163
|
+
|
|
164
|
+
return dem_cover, area_cover / area_inter * 100.0
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def polygon_projection(poly: Polygon, epsg_in: int, epsg_out: int) -> Polygon:
|
|
168
|
+
"""
|
|
169
|
+
Projects a polygon from an initial epsg code to another
|
|
170
|
+
|
|
171
|
+
:param poly: poly to project
|
|
172
|
+
:param epsg_in: initial epsg code
|
|
173
|
+
:param epsg_out: final epsg code
|
|
174
|
+
:return: The polygon in the final projection
|
|
175
|
+
"""
|
|
176
|
+
# Get CRS from input EPSG codes
|
|
177
|
+
crs_in = pyproj.CRS.from_epsg(epsg_in)
|
|
178
|
+
crs_out = pyproj.CRS.from_epsg(epsg_out)
|
|
179
|
+
# Project polygon between CRS (keep always_xy for compatibility)
|
|
180
|
+
project = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
181
|
+
poly = transform(project.transform, poly)
|
|
182
|
+
|
|
183
|
+
return poly
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def polygon_projection_crs(poly: Polygon, crs_in: CRS, crs_out: CRS) -> Polygon:
|
|
187
|
+
"""
|
|
188
|
+
Projects a polygon from an initial crs to another
|
|
189
|
+
|
|
190
|
+
:param poly: poly to project
|
|
191
|
+
:param crs_in: initial crs
|
|
192
|
+
:param crs_out: final crs
|
|
193
|
+
:return: The polygon in the final projection
|
|
194
|
+
"""
|
|
195
|
+
# Project polygon between CRS (keep always_xy for compatibility)
|
|
196
|
+
project = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
197
|
+
poly = transform(project.transform, poly)
|
|
198
|
+
|
|
199
|
+
return poly
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def geo_to_ecef(
|
|
203
|
+
lat: np.ndarray, lon: np.ndarray, alt: np.ndarray
|
|
204
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
205
|
+
"""
|
|
206
|
+
Point transformation from Geodetic of ellipsoid WGS-84) to ECEF
|
|
207
|
+
ECEF: Earth-centered, Earth-fixed
|
|
208
|
+
|
|
209
|
+
:param lat: input geodetic latitude (angle in degree)
|
|
210
|
+
:param lon: input geodetic longitude (angle in degree)
|
|
211
|
+
:param alt: input altitude above geodetic ellipsoid (meters)
|
|
212
|
+
:return: ECEF (Earth centered, Earth fixed) x, y, z coordinates tuple
|
|
213
|
+
(in meters)
|
|
214
|
+
"""
|
|
215
|
+
epsg_in = 4979 # EPSG code for Geocentric WGS84 in lat, lon, alt (degree)
|
|
216
|
+
epsg_out = 4978 # EPSG code for ECEF WGS84 in x, y, z (meters)
|
|
217
|
+
|
|
218
|
+
return point_cloud_conversion(
|
|
219
|
+
np.array([[lon, lat, alt]]), epsg_in, epsg_out
|
|
220
|
+
)[0]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def ecef_to_enu( # pylint: disable=too-many-positional-arguments
|
|
224
|
+
x_ecef: np.ndarray,
|
|
225
|
+
y_ecef: np.ndarray,
|
|
226
|
+
z_ecef: np.ndarray,
|
|
227
|
+
lat0: np.ndarray,
|
|
228
|
+
lon0: np.ndarray,
|
|
229
|
+
alt0: np.ndarray,
|
|
230
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
231
|
+
"""
|
|
232
|
+
Coordinates conversion from ECEF Earth Centered to
|
|
233
|
+
East North Up Coordinate from a reference point (lat0, lon0, alt0)
|
|
234
|
+
|
|
235
|
+
See Wikipedia page for details:
|
|
236
|
+
https://en.wikipedia.org/wiki/Geographic_coordinate_conversion
|
|
237
|
+
|
|
238
|
+
:param x_ecef: target x ECEF coordinate (meters)
|
|
239
|
+
:param y_ecef: target y ECEF coordinate (meters)
|
|
240
|
+
:param z_ecef: target z ECEF coordinate (meters)
|
|
241
|
+
:param lat0: Reference geodetic latitude
|
|
242
|
+
:param lon0: Reference geodetic longitude
|
|
243
|
+
:param alt0: Reference altitude above geodetic ellipsoid (meters)
|
|
244
|
+
:return: ENU (xEast, yNorth zUp) target coordinates tuple (meters)
|
|
245
|
+
"""
|
|
246
|
+
# Intermediate computing for ENU conversion
|
|
247
|
+
cos_lat0 = np.cos(np.radians(lat0))
|
|
248
|
+
sin_lat0 = np.sin(np.radians(lat0))
|
|
249
|
+
|
|
250
|
+
cos_long0 = np.cos(np.radians(lon0))
|
|
251
|
+
sin_long0 = np.sin(np.radians(lon0))
|
|
252
|
+
|
|
253
|
+
# Determine ECEF coordinates from reference geodetic
|
|
254
|
+
x0_ecef, y0_ecef, z0_ecef = geo_to_ecef(lat0, lon0, alt0)
|
|
255
|
+
|
|
256
|
+
x_east = (-(x_ecef - x0_ecef) * sin_long0) + (
|
|
257
|
+
(y_ecef - y0_ecef) * cos_long0
|
|
258
|
+
)
|
|
259
|
+
y_north = (
|
|
260
|
+
(-cos_long0 * sin_lat0 * (x_ecef - x0_ecef))
|
|
261
|
+
- (sin_lat0 * sin_long0 * (y_ecef - y0_ecef))
|
|
262
|
+
+ (cos_lat0 * (z_ecef - z0_ecef))
|
|
263
|
+
)
|
|
264
|
+
z_up = (
|
|
265
|
+
(cos_lat0 * cos_long0 * (x_ecef - x0_ecef))
|
|
266
|
+
+ (cos_lat0 * sin_long0 * (y_ecef - y0_ecef))
|
|
267
|
+
+ (sin_lat0 * (z_ecef - z0_ecef))
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return x_east, y_north, z_up
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def geo_to_enu( # pylint: disable=too-many-positional-arguments
|
|
274
|
+
lat: np.ndarray,
|
|
275
|
+
lon: np.ndarray,
|
|
276
|
+
alt: np.ndarray,
|
|
277
|
+
lat0: np.ndarray,
|
|
278
|
+
lon0: np.ndarray,
|
|
279
|
+
alt0: np.ndarray,
|
|
280
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
281
|
+
"""
|
|
282
|
+
Point transformation from WGS-84 Geodetic coordinates to to ENU.
|
|
283
|
+
Use geo_to_ecef and ecef_to_enu functions.
|
|
284
|
+
|
|
285
|
+
:param lat: input geodetic latitude (angle in degree)
|
|
286
|
+
:param lon: input geodetic longitude (angle in degree)
|
|
287
|
+
:param alt: input altitude above geodetic ellipsoid (meters)
|
|
288
|
+
:param lat0: Reference geodetic latitude
|
|
289
|
+
:param lon0: Reference geodetic longitude
|
|
290
|
+
:param alt0: Reference altitude above geodetic ellipsoid (meters)
|
|
291
|
+
:return: ENU (xEast, yNorth zUp) target coordinates tuple (meters)
|
|
292
|
+
"""
|
|
293
|
+
x_ecef, y_ecef, z_ecef = geo_to_ecef(lat, lon, alt)
|
|
294
|
+
return ecef_to_enu(x_ecef, y_ecef, z_ecef, lat0, lon0, alt0)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def enu_to_aer(
|
|
298
|
+
x_east: np.ndarray, y_north: np.ndarray, z_up: np.ndarray
|
|
299
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
300
|
+
"""
|
|
301
|
+
ENU coordinates to Azimuth, Elevation angle, Range from ENU origin
|
|
302
|
+
Beware: Elevation angle is not the altitude.
|
|
303
|
+
|
|
304
|
+
:param x_east: ENU East coordinate (meters)
|
|
305
|
+
:param y_north: ENU North coordinate (meters)
|
|
306
|
+
:param z_up: ENU Up coordinate (meters)
|
|
307
|
+
:return: Azimuth, Elevation Angle, Slant Range (degrees, degrees, meters)
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
xy_range = np.hypot(x_east, y_north) # Distance of e, n vector
|
|
311
|
+
xyz_range = np.hypot(xy_range, z_up) # Distance of e, n, u vector
|
|
312
|
+
elevation = np.arctan2(z_up, xy_range)
|
|
313
|
+
azimuth = np.arctan2(x_east, y_north) % (2 * np.pi)
|
|
314
|
+
# From [-pi,+pi] to [0,2pi]
|
|
315
|
+
|
|
316
|
+
azimuth = np.degrees(azimuth)
|
|
317
|
+
elevation = np.degrees(elevation)
|
|
318
|
+
|
|
319
|
+
return azimuth, elevation, xyz_range
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def geo_to_aer( # pylint: disable=too-many-positional-arguments
|
|
323
|
+
lat: np.ndarray,
|
|
324
|
+
lon: np.ndarray,
|
|
325
|
+
alt: np.ndarray,
|
|
326
|
+
lat0: np.ndarray,
|
|
327
|
+
lon0: np.ndarray,
|
|
328
|
+
alt0: np.ndarray,
|
|
329
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
330
|
+
"""
|
|
331
|
+
Gives Azimuth, Elevation angle and Slant Range
|
|
332
|
+
from a Reference to a Point with geodetic coordinates.
|
|
333
|
+
|
|
334
|
+
:param lat: input geodetic latitude (angle in degree)
|
|
335
|
+
:param lon: input geodetic longitude (angle in degree)
|
|
336
|
+
:param alt: input altitude above geodetic ellipsoid (meters)
|
|
337
|
+
:param lat0: Reference geodetic latitude
|
|
338
|
+
:param lon0: Reference geodetic longitude
|
|
339
|
+
:param alt0: Reference altitude above geodetic ellipsoid (meters)
|
|
340
|
+
:return: Azimuth, Elevation Angle, Slant Range (degrees, degrees, meters)
|
|
341
|
+
"""
|
|
342
|
+
x_east, y_north, z_up = geo_to_enu(lat, lon, alt, lat0, lon0, alt0)
|
|
343
|
+
return enu_to_aer(x_east, y_north, z_up)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def point_cloud_conversion(
|
|
347
|
+
cloud_in: np.ndarray, epsg_in: int, epsg_out: int
|
|
348
|
+
) -> np.ndarray:
|
|
349
|
+
"""
|
|
350
|
+
Convert a point cloud from a SRS to another one.
|
|
351
|
+
|
|
352
|
+
:param cloud_in: cloud to project
|
|
353
|
+
:param epsg_in: EPSG code of the input SRS
|
|
354
|
+
:param epsg_out: EPSG code of the output SRS
|
|
355
|
+
:return: Projected point cloud
|
|
356
|
+
"""
|
|
357
|
+
# Get CRS from input EPSG codes
|
|
358
|
+
crs_in = pyproj.CRS.from_epsg(epsg_in)
|
|
359
|
+
crs_out = pyproj.CRS.from_epsg(epsg_out)
|
|
360
|
+
|
|
361
|
+
# Project point cloud between CRS (keep always_xy for compatibility)
|
|
362
|
+
cloud_in = np.array(cloud_in).T
|
|
363
|
+
transformer = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
364
|
+
|
|
365
|
+
cloud_in = transformer.transform(*cloud_in)
|
|
366
|
+
cloud_in = np.array(cloud_in).T
|
|
367
|
+
|
|
368
|
+
return cloud_in
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def point_cloud_conversion_crs(
|
|
372
|
+
cloud_in: np.ndarray, crs_in: int, crs_out: int
|
|
373
|
+
) -> np.ndarray:
|
|
374
|
+
"""
|
|
375
|
+
Convert a point cloud from a SRS to another one.
|
|
376
|
+
|
|
377
|
+
:param cloud_in: cloud to project
|
|
378
|
+
:param crs_in: crs of the input SRS
|
|
379
|
+
:param crs_out: crs of the output SRS
|
|
380
|
+
:return: Projected point cloud
|
|
381
|
+
"""
|
|
382
|
+
# Project point cloud between CRS (keep always_xy for compatibility)
|
|
383
|
+
cloud_in = np.array(cloud_in).T
|
|
384
|
+
transformer = pyproj.Transformer.from_crs(crs_in, crs_out, always_xy=True)
|
|
385
|
+
|
|
386
|
+
cloud_in = transformer.transform(*cloud_in)
|
|
387
|
+
cloud_in = np.array(cloud_in).T
|
|
388
|
+
|
|
389
|
+
return cloud_in
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def get_xyz_np_array_from_dataset(
|
|
393
|
+
cloud_in: xr.Dataset,
|
|
394
|
+
) -> Tuple[np.array, List[int]]:
|
|
395
|
+
"""
|
|
396
|
+
Get a numpy array of size (nb_points, 3) with the columns
|
|
397
|
+
being the x, y and z coordinates from a dataset as given
|
|
398
|
+
in output of the triangulation.
|
|
399
|
+
|
|
400
|
+
The original epipolar geometry shape is also given in output
|
|
401
|
+
in order to reshape the output numpy array in its
|
|
402
|
+
original geometry if necessary.
|
|
403
|
+
|
|
404
|
+
:param cloud_in: input xarray dataset
|
|
405
|
+
:return: a tuple composed of the xyz numàpy array and its original shape
|
|
406
|
+
"""
|
|
407
|
+
xyz = np.stack(
|
|
408
|
+
(cloud_in[cst.X].values, cloud_in[cst.Y].values, cloud_in[cst.Z]),
|
|
409
|
+
axis=-1,
|
|
410
|
+
)
|
|
411
|
+
xyz_shape = xyz.shape
|
|
412
|
+
xyz = np.reshape(xyz, (-1, 3))
|
|
413
|
+
|
|
414
|
+
return xyz, xyz_shape
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def get_converted_xy_np_arrays_from_dataset(
|
|
418
|
+
cloud_in: xr.Dataset, epsg_out: int
|
|
419
|
+
) -> Tuple[np.array, np.array]:
|
|
420
|
+
"""
|
|
421
|
+
Get the x and y coordinates as numpy array
|
|
422
|
+
in the new referential indicated by epsg_out.
|
|
423
|
+
TODO: add test
|
|
424
|
+
|
|
425
|
+
:param cloud_in: input xarray dataset
|
|
426
|
+
:param epsg_out: target epsg code
|
|
427
|
+
:return: a tuple composed of the x and y numpy arrays
|
|
428
|
+
"""
|
|
429
|
+
xyz, xyz_shape = get_xyz_np_array_from_dataset(cloud_in)
|
|
430
|
+
epsg = int(cloud_in.attrs[cst.EPSG])
|
|
431
|
+
xyz = point_cloud_conversion(xyz, epsg, epsg_out)
|
|
432
|
+
xyz = xyz.reshape(xyz_shape)
|
|
433
|
+
if isinstance(cloud_in, xr.Dataset):
|
|
434
|
+
proj_x = xyz[:, :, 0]
|
|
435
|
+
proj_y = xyz[:, :, 1]
|
|
436
|
+
else:
|
|
437
|
+
proj_x = xyz[:, 0]
|
|
438
|
+
proj_y = xyz[:, 1]
|
|
439
|
+
return proj_x, proj_y
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def point_cloud_conversion_dataset(cloud: xr.Dataset, epsg_out: int):
|
|
443
|
+
"""
|
|
444
|
+
Convert a point cloud as an xarray.Dataset to another epsg (inplace)
|
|
445
|
+
TODO: add test
|
|
446
|
+
|
|
447
|
+
:param cloud: cloud to project
|
|
448
|
+
:param epsg_out: EPSG code of the output SRS
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
if cloud.attrs[cst.EPSG] != epsg_out:
|
|
452
|
+
xyz, xyz_shape = get_xyz_np_array_from_dataset(cloud)
|
|
453
|
+
|
|
454
|
+
xyz = point_cloud_conversion(xyz, cloud.attrs[cst.EPSG], epsg_out)
|
|
455
|
+
xyz = xyz.reshape(xyz_shape)
|
|
456
|
+
xyz[xyz == np.inf] = np.nan
|
|
457
|
+
if isinstance(cloud, xr.Dataset):
|
|
458
|
+
# # Update cloud_in x, y and z values
|
|
459
|
+
cloud[cst.X].values = xyz[:, :, 0]
|
|
460
|
+
cloud[cst.Y].values = xyz[:, :, 1]
|
|
461
|
+
cloud[cst.Z].values = xyz[:, :, 2]
|
|
462
|
+
|
|
463
|
+
# # Update EPSG code
|
|
464
|
+
cloud.attrs[cst.EPSG] = epsg_out
|
|
465
|
+
elif isinstance(cloud, pandas.DataFrame):
|
|
466
|
+
cloud[cst.X] = xyz[:, 0]
|
|
467
|
+
cloud[cst.Y] = xyz[:, 1]
|
|
468
|
+
cloud[cst.Z] = xyz[:, 2]
|
|
469
|
+
cloud.attrs[cst.EPSG] = epsg_out
|
|
470
|
+
else:
|
|
471
|
+
logging.error(
|
|
472
|
+
"point_cloud_conversion_dataset error: point cloud is unknown"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def point_cloud_conversion_dataframe(
|
|
477
|
+
cloud: pandas.DataFrame, epsg_in: int, epsg_out: int
|
|
478
|
+
):
|
|
479
|
+
"""
|
|
480
|
+
Convert a point cloud as a panda.DataFrame to another epsg (inplace)
|
|
481
|
+
|
|
482
|
+
:param cloud: cloud to project
|
|
483
|
+
:param epsg_in: EPSG code of the input SRS
|
|
484
|
+
:param epsg_out: EPSG code of the output SRS
|
|
485
|
+
"""
|
|
486
|
+
xyz_in = cloud.loc[:, [cst.X, cst.Y, cst.Z]].values
|
|
487
|
+
|
|
488
|
+
if xyz_in.shape[0] != 0:
|
|
489
|
+
xyz_in = point_cloud_conversion(xyz_in, epsg_in, epsg_out)
|
|
490
|
+
cloud[cst.X] = xyz_in[:, 0]
|
|
491
|
+
cloud[cst.Y] = xyz_in[:, 1]
|
|
492
|
+
cloud[cst.Z] = xyz_in[:, 2]
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def ground_polygon_from_envelopes(
|
|
496
|
+
poly_envelope1: Polygon,
|
|
497
|
+
poly_envelope2: Polygon,
|
|
498
|
+
epsg1: int,
|
|
499
|
+
epsg2: int,
|
|
500
|
+
tgt_epsg: int = 4326,
|
|
501
|
+
) -> Tuple[Polygon, Tuple[int, int, int, int]]:
|
|
502
|
+
"""
|
|
503
|
+
compute the ground polygon of the intersection of two envelopes
|
|
504
|
+
|
|
505
|
+
:raise: Exception when the envelopes don't intersect one to each other
|
|
506
|
+
|
|
507
|
+
:param poly_envelope1: path to the first envelope
|
|
508
|
+
:param poly_envelope2: path to the second envelope
|
|
509
|
+
:param epsg1: EPSG code of poly_envelope1
|
|
510
|
+
:param epsg2: EPSG code of poly_envelope2
|
|
511
|
+
:param tgt_epsg: EPSG code of the new projection
|
|
512
|
+
(default value is set to 4326)
|
|
513
|
+
:return: a tuple with the shapely polygon of the intersection
|
|
514
|
+
and the intersection's bounding box
|
|
515
|
+
(described by a tuple (minx, miny, maxx, maxy))
|
|
516
|
+
"""
|
|
517
|
+
# project to the correct epsg if necessary
|
|
518
|
+
if epsg1 != tgt_epsg:
|
|
519
|
+
poly_envelope1 = polygon_projection(poly_envelope1, epsg1, tgt_epsg)
|
|
520
|
+
|
|
521
|
+
if epsg2 != tgt_epsg:
|
|
522
|
+
poly_envelope2 = polygon_projection(poly_envelope2, epsg2, tgt_epsg)
|
|
523
|
+
|
|
524
|
+
# intersect both envelopes
|
|
525
|
+
if poly_envelope1.intersects(poly_envelope2):
|
|
526
|
+
inter = poly_envelope1.intersection(poly_envelope2)
|
|
527
|
+
else:
|
|
528
|
+
raise RuntimeError("The two envelopes do not intersect one another")
|
|
529
|
+
|
|
530
|
+
return inter, inter.bounds
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# pylint: disable=too-many-positional-arguments
|
|
534
|
+
@cars_profile(name="Ground intersection envelopes", interval=0.5)
|
|
535
|
+
def ground_intersection_envelopes(
|
|
536
|
+
sensor1,
|
|
537
|
+
sensor2,
|
|
538
|
+
geomodel1,
|
|
539
|
+
geomodel2,
|
|
540
|
+
geometry_plugin: str,
|
|
541
|
+
envelope_1_path: str,
|
|
542
|
+
envelope_2_path: str,
|
|
543
|
+
out_intersect_path: str,
|
|
544
|
+
envelope_file_driver: str = "GeoJSON",
|
|
545
|
+
intersect_file_driver: str = "GPKG",
|
|
546
|
+
) -> Tuple[Polygon, Tuple[int, int, int, int]]:
|
|
547
|
+
"""
|
|
548
|
+
Compute ground intersection of two images with envelopes:
|
|
549
|
+
1/ Create envelopes for left, right images
|
|
550
|
+
2/ Read vectors and polygons with adequate EPSG codes.
|
|
551
|
+
3/ compute the ground polygon of the intersection of two envelopes
|
|
552
|
+
4/ Write the GPKG vector
|
|
553
|
+
|
|
554
|
+
Returns a shapely polygon and intersection bounding box
|
|
555
|
+
|
|
556
|
+
:raise: Exception when the envelopes don't intersect one to each other
|
|
557
|
+
|
|
558
|
+
:param sensor1: path to left sensor image
|
|
559
|
+
:param sensor2: path to right sensor image
|
|
560
|
+
:param geomodel1: path and attributes for left geomodel
|
|
561
|
+
:param geomodel2: path and attributes for right geomodel
|
|
562
|
+
:param geometry_plugin: name of geometry plugin to use
|
|
563
|
+
:param envelope_1_path: Path to the output shapefile left
|
|
564
|
+
:param envelope_2_path: Path to the output shapefile right
|
|
565
|
+
:param dem_dir: Directory containing DEM tiles
|
|
566
|
+
:param default_alt: Default altitude above ellipsoid
|
|
567
|
+
:param out_intersect_path: out vector file path to create
|
|
568
|
+
:param envelope_file_driver: driver used to write envelope files
|
|
569
|
+
:param intersect_file_driver: driver used to write the intersection file
|
|
570
|
+
:return: a tuple with the shapely polygon of the intersection
|
|
571
|
+
and the intersection's bounding box
|
|
572
|
+
(described by a tuple (minx, miny, maxx, maxy))
|
|
573
|
+
"""
|
|
574
|
+
geometry_plugin.image_envelope(
|
|
575
|
+
sensor1, geomodel1, envelope_1_path, envelope_file_driver
|
|
576
|
+
)
|
|
577
|
+
geometry_plugin.image_envelope(
|
|
578
|
+
sensor2, geomodel2, envelope_2_path, envelope_file_driver
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Read vectors shapefiles
|
|
582
|
+
poly1, epsg1 = inputs.read_vector(envelope_1_path)
|
|
583
|
+
poly2, epsg2 = inputs.read_vector(envelope_2_path)
|
|
584
|
+
|
|
585
|
+
# Find polygon intersection from left, right polygons
|
|
586
|
+
inter_poly, (
|
|
587
|
+
inter_xmin,
|
|
588
|
+
inter_ymin,
|
|
589
|
+
inter_xmax,
|
|
590
|
+
inter_ymax,
|
|
591
|
+
) = ground_polygon_from_envelopes(poly1, poly2, epsg1, epsg2, epsg1)
|
|
592
|
+
|
|
593
|
+
# Write intersection file vector from inter_poly
|
|
594
|
+
outputs.write_vector(
|
|
595
|
+
[inter_poly], out_intersect_path, epsg1, intersect_file_driver
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
return inter_poly, (inter_xmin, inter_ymin, inter_xmax, inter_ymax)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def get_ground_direction( # pylint: disable=too-many-positional-arguments
|
|
602
|
+
sensor,
|
|
603
|
+
geomodel,
|
|
604
|
+
geometry_plugin: str,
|
|
605
|
+
x_coord: float = None,
|
|
606
|
+
y_coord: float = None,
|
|
607
|
+
z0_coord: float = None,
|
|
608
|
+
z_coord: float = None,
|
|
609
|
+
) -> np.ndarray:
|
|
610
|
+
"""
|
|
611
|
+
For a given image (x,y) point, compute the direction vector to ground
|
|
612
|
+
The function uses the direct localization operation and makes a z
|
|
613
|
+
variation to get a ground direction vector.
|
|
614
|
+
By default, (x,y) is put at image center and z0, z at RPC geometric
|
|
615
|
+
model limits.
|
|
616
|
+
|
|
617
|
+
:param TODO
|
|
618
|
+
:param geometry_plugin: name of geometry plugin to use
|
|
619
|
+
:param x_coord: X Coordinate in input image sensor
|
|
620
|
+
:param y_coord: Y Coordinate in input image sensor
|
|
621
|
+
:param z0_coord: Z altitude reference coordinate
|
|
622
|
+
:param z_coord: Z Altitude coordinate to take the image
|
|
623
|
+
:param dem: path to the dem directory
|
|
624
|
+
:param geoid: path to the geoid file
|
|
625
|
+
:return: (lat0,lon0,alt0, lat,lon,alt) origin and end vector coordinates
|
|
626
|
+
"""
|
|
627
|
+
# Define x, y in image center if not defined
|
|
628
|
+
img_size_x, img_size_y = inputs.rasterio_get_size(sensor)
|
|
629
|
+
|
|
630
|
+
if x_coord is None:
|
|
631
|
+
x_coord = img_size_x / 2
|
|
632
|
+
if y_coord is None:
|
|
633
|
+
y_coord = img_size_y / 2
|
|
634
|
+
|
|
635
|
+
# Check x, y to be in image
|
|
636
|
+
assert x_coord >= 0
|
|
637
|
+
assert x_coord <= img_size_x
|
|
638
|
+
assert y_coord >= 0
|
|
639
|
+
assert y_coord <= img_size_y
|
|
640
|
+
|
|
641
|
+
# Define z and z0 from img RPC constraints if not defined
|
|
642
|
+
(min_alt, max_alt) = utils.get_elevation_range_from_metadata(sensor)
|
|
643
|
+
if z0_coord is None:
|
|
644
|
+
z0_coord = min_alt
|
|
645
|
+
if z_coord is None:
|
|
646
|
+
z_coord = max_alt
|
|
647
|
+
|
|
648
|
+
# Check z0 and z to be in RPC constraints
|
|
649
|
+
assert z0_coord >= min_alt
|
|
650
|
+
assert z0_coord <= max_alt
|
|
651
|
+
assert z_coord >= min_alt
|
|
652
|
+
assert z_coord <= max_alt
|
|
653
|
+
|
|
654
|
+
# Get origin vector coordinate with z0 altitude
|
|
655
|
+
lat0, lon0, alt0 = geometry_plugin.direct_loc(
|
|
656
|
+
sensor,
|
|
657
|
+
geomodel,
|
|
658
|
+
np.array([x_coord]),
|
|
659
|
+
np.array([y_coord]),
|
|
660
|
+
np.array([z0_coord]),
|
|
661
|
+
)
|
|
662
|
+
# Get end vector coordinate with z altitude
|
|
663
|
+
lat, lon, alt = geometry_plugin.direct_loc(
|
|
664
|
+
sensor,
|
|
665
|
+
geomodel,
|
|
666
|
+
np.array([x_coord]),
|
|
667
|
+
np.array([y_coord]),
|
|
668
|
+
z_coord=np.array([z_coord]),
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
return np.array([lat0, lon0, alt0, lat, lon, alt])
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def get_ground_angles( # pylint: disable=too-many-positional-arguments
|
|
675
|
+
sensor1,
|
|
676
|
+
sensor2,
|
|
677
|
+
geomodel1,
|
|
678
|
+
geomodel2,
|
|
679
|
+
geometry_plugin,
|
|
680
|
+
x1_coord: float = None,
|
|
681
|
+
y1_coord: float = None,
|
|
682
|
+
z1_0_coord: float = None,
|
|
683
|
+
z1_coord: float = None,
|
|
684
|
+
x2_coord: float = None,
|
|
685
|
+
y2_coord: float = None,
|
|
686
|
+
z2_0_coord: float = None,
|
|
687
|
+
z2_coord: float = None,
|
|
688
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
689
|
+
"""
|
|
690
|
+
For a given image (x,y) point, compute the Azimuth angle,
|
|
691
|
+
Elevation angle (not the altitude !) and Range from Ground z0 perspective
|
|
692
|
+
for both stereo image (img1: left and img2: right)
|
|
693
|
+
|
|
694
|
+
Calculate also the convergence angle between the two satellites from ground.
|
|
695
|
+
|
|
696
|
+
The function use get_ground_direction function to have coordinates of
|
|
697
|
+
ground direction vector and compute angles and range.
|
|
698
|
+
|
|
699
|
+
Ref: Jeong, Jaehoon. (2017).
|
|
700
|
+
IMAGING GEOMETRY AND POSITIONING ACCURACY OF DUAL SATELLITE STEREO IMAGES:
|
|
701
|
+
A REVIEW. ISPRS Annals of Photogrammetry, Remote Sensing and Spatial
|
|
702
|
+
Information Sciences.
|
|
703
|
+
IV-2/W4. 235-242. 10.5194/isprs-annals-IV-2-W4-235-2017.
|
|
704
|
+
|
|
705
|
+
Perspectives: get bisector elevation (BIE), and asymmetry angle
|
|
706
|
+
|
|
707
|
+
:param TODO
|
|
708
|
+
:param geometry_plugin: name of geometry plugin to use
|
|
709
|
+
:param x1_coord: X Coordinate in input left image1 sensor
|
|
710
|
+
:param y1_coord: Y Coordinate in input left image1 sensor
|
|
711
|
+
:param z1_0_coord: Left image1 Z altitude origin coordinate for ground
|
|
712
|
+
direction vector
|
|
713
|
+
:param z1_coord: Left image1 Z altitude end coordinate for ground
|
|
714
|
+
direction vector
|
|
715
|
+
:param x2_coord: X Coordinate in input right image2 sensor
|
|
716
|
+
:param y2_coord: Y Coordinate in input right image2 sensor
|
|
717
|
+
:param z2_0_coord: Right image2 Z altitude origin coordinate for ground
|
|
718
|
+
direction vector
|
|
719
|
+
:param z2_coord: Right image2 Z altitude end coordinate for ground
|
|
720
|
+
direction vector
|
|
721
|
+
:return: Left Azimuth, Left Elevation Angle,
|
|
722
|
+
Right Azimuth, Right Elevation Angle, Convergence Angle
|
|
723
|
+
"""
|
|
724
|
+
# TODO remove ? unused
|
|
725
|
+
|
|
726
|
+
# Get image1 <-> satellite vector from image1 metadata geometric model
|
|
727
|
+
lat1_0, lon1_0, alt1_0, lat1, lon1, alt1 = get_ground_direction(
|
|
728
|
+
sensor1,
|
|
729
|
+
geomodel1,
|
|
730
|
+
geometry_plugin,
|
|
731
|
+
x1_coord,
|
|
732
|
+
y1_coord,
|
|
733
|
+
z1_0_coord,
|
|
734
|
+
z1_coord,
|
|
735
|
+
)
|
|
736
|
+
# Get East North Up vector for left image1
|
|
737
|
+
x1_e, y1_n, y1_u = enu1 = geo_to_enu(
|
|
738
|
+
lat1, lon1, alt1, lat1_0, lon1_0, alt1_0
|
|
739
|
+
)
|
|
740
|
+
# Convert vector to Azimuth, Elevation, Range (unused)
|
|
741
|
+
az1, elev_angle1, __ = enu_to_aer(x1_e, y1_n, y1_u)
|
|
742
|
+
|
|
743
|
+
# Get image2 <-> satellite vector from image2 metadata geometric model
|
|
744
|
+
lat2_0, lon2_0, alt2_0, lat2, lon2, alt2 = get_ground_direction(
|
|
745
|
+
sensor2,
|
|
746
|
+
geomodel2,
|
|
747
|
+
geometry_plugin,
|
|
748
|
+
x2_coord,
|
|
749
|
+
y2_coord,
|
|
750
|
+
z2_0_coord,
|
|
751
|
+
z2_coord,
|
|
752
|
+
)
|
|
753
|
+
# Get East North Up vector for right image2
|
|
754
|
+
x2_e, y2_n, y2_u = enu2 = geo_to_enu(
|
|
755
|
+
lat2, lon2, alt2, lat2_0, lon2_0, alt2_0
|
|
756
|
+
)
|
|
757
|
+
# Convert ENU to Azimuth, Elevation, Range (unused)
|
|
758
|
+
az2, elev_angle2, __ = enu_to_aer(x2_e, y2_n, y2_u)
|
|
759
|
+
|
|
760
|
+
# Get convergence angle from two enu vectors.
|
|
761
|
+
convergence_angle = np.degrees(utils.angle_vectors(enu1, enu2))
|
|
762
|
+
|
|
763
|
+
return az1, elev_angle1, az2, elev_angle2, convergence_angle
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def get_output_crs(epsg, out_conf):
|
|
767
|
+
"""
|
|
768
|
+
Détermine le CRS de sortie en fonction de la config.
|
|
769
|
+
"""
|
|
770
|
+
geoid = out_conf.get("geoid")
|
|
771
|
+
crs_epsg = CRS(f"EPSG:{epsg}")
|
|
772
|
+
|
|
773
|
+
if len(crs_epsg.axis_info) != 2:
|
|
774
|
+
return crs_epsg # the user himself set a 3D CRS
|
|
775
|
+
|
|
776
|
+
geoid_is_path = isinstance(geoid, str)
|
|
777
|
+
|
|
778
|
+
if geoid_is_path: # user given geoid
|
|
779
|
+
vepsg = guess_vcrs_from_file_name(geoid)
|
|
780
|
+
if vepsg is None:
|
|
781
|
+
custom_wkt = (
|
|
782
|
+
'VERTCRS["Custom geoid height",'
|
|
783
|
+
+ f' VDATUM["Custom geoid model (file: {geoid})"],'
|
|
784
|
+
+ " CS[vertical,1],"
|
|
785
|
+
+ ' AXIS["gravity-related height (h)", up],'
|
|
786
|
+
+ ' LENGTHUNIT["metre", 1, ID["EPSG", 9001]]'
|
|
787
|
+
"]"
|
|
788
|
+
)
|
|
789
|
+
logging.warning(
|
|
790
|
+
"Could not create a known VCRS from the geoid file."
|
|
791
|
+
)
|
|
792
|
+
return CRS.from_wkt(
|
|
793
|
+
f'COMPOUNDCRS["EPSG:{epsg} + Custom geoid height",'
|
|
794
|
+
f" {crs_epsg.to_wkt()},"
|
|
795
|
+
f" {custom_wkt}]"
|
|
796
|
+
)
|
|
797
|
+
# a vepsg was found using the geoid file
|
|
798
|
+
return CRS(f"EPSG:{epsg}+{vepsg}")
|
|
799
|
+
|
|
800
|
+
if geoid: # geoid == True
|
|
801
|
+
return CRS(f"EPSG:{epsg}+5773")
|
|
802
|
+
|
|
803
|
+
# geoid == False
|
|
804
|
+
wgs84_wkt = (
|
|
805
|
+
'VERTCRS["WGS 84 ellipsoidal height",'
|
|
806
|
+
+ ' VDATUM["WGS 84"],'
|
|
807
|
+
+ " CS[vertical,1],"
|
|
808
|
+
+ ' AXIS["ellipsoidal height (h)", up],'
|
|
809
|
+
+ ' LENGTHUNIT["metre", 1, ID["EPSG", 9001]]'
|
|
810
|
+
"]"
|
|
811
|
+
)
|
|
812
|
+
logging.warning("The output VCRS is WGS84.")
|
|
813
|
+
|
|
814
|
+
return CRS.from_wkt(
|
|
815
|
+
f'COMPOUNDCRS["EPSG:{epsg} + WGS84 ellipsoidal height",'
|
|
816
|
+
f" {crs_epsg.to_wkt()},"
|
|
817
|
+
f" {wgs84_wkt}]"
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def guess_vcrs_from_file_name(filepath):
|
|
822
|
+
"""
|
|
823
|
+
Tries to detect the geoid's EPSG from the file name
|
|
824
|
+
"""
|
|
825
|
+
filename = os.path.basename(filepath).lower()
|
|
826
|
+
|
|
827
|
+
known_models = {
|
|
828
|
+
"egm96": 5773, # EGM96 height
|
|
829
|
+
"egm_96": 5773, # alias
|
|
830
|
+
"egm 96": 5773, # alias
|
|
831
|
+
"egm1996": 5773, # alias
|
|
832
|
+
"egm08": 3855, # EGM2008 height
|
|
833
|
+
"egm_08": 3855, # alias
|
|
834
|
+
"egm 08": 3855, # alias
|
|
835
|
+
"egm2008": 3855, # alias
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
for key, vepsg in known_models.items():
|
|
839
|
+
if key in filename:
|
|
840
|
+
return vepsg
|
|
841
|
+
|
|
842
|
+
# aucun match connu
|
|
843
|
+
return None
|