rslearn 0.0.1__py3-none-any.whl → 0.0.21__py3-none-any.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.
- rslearn/arg_parser.py +31 -0
- rslearn/config/__init__.py +6 -12
- rslearn/config/dataset.py +520 -401
- rslearn/const.py +9 -15
- rslearn/data_sources/__init__.py +8 -23
- rslearn/data_sources/aws_landsat.py +242 -98
- rslearn/data_sources/aws_open_data.py +111 -151
- rslearn/data_sources/aws_sentinel1.py +131 -0
- rslearn/data_sources/climate_data_store.py +471 -0
- rslearn/data_sources/copernicus.py +884 -12
- rslearn/data_sources/data_source.py +43 -12
- rslearn/data_sources/earthdaily.py +484 -0
- rslearn/data_sources/earthdata_srtm.py +282 -0
- rslearn/data_sources/eurocrops.py +242 -0
- rslearn/data_sources/gcp_public_data.py +578 -222
- rslearn/data_sources/google_earth_engine.py +461 -135
- rslearn/data_sources/local_files.py +219 -150
- rslearn/data_sources/openstreetmap.py +51 -89
- rslearn/data_sources/planet.py +24 -60
- rslearn/data_sources/planet_basemap.py +275 -0
- rslearn/data_sources/planetary_computer.py +798 -0
- rslearn/data_sources/usda_cdl.py +195 -0
- rslearn/data_sources/usgs_landsat.py +115 -83
- rslearn/data_sources/utils.py +249 -61
- rslearn/data_sources/vector_source.py +1 -0
- rslearn/data_sources/worldcereal.py +449 -0
- rslearn/data_sources/worldcover.py +144 -0
- rslearn/data_sources/worldpop.py +153 -0
- rslearn/data_sources/xyz_tiles.py +150 -107
- rslearn/dataset/__init__.py +8 -2
- rslearn/dataset/add_windows.py +2 -2
- rslearn/dataset/dataset.py +40 -51
- rslearn/dataset/handler_summaries.py +131 -0
- rslearn/dataset/manage.py +313 -74
- rslearn/dataset/materialize.py +431 -107
- rslearn/dataset/remap.py +29 -4
- rslearn/dataset/storage/__init__.py +1 -0
- rslearn/dataset/storage/file.py +202 -0
- rslearn/dataset/storage/storage.py +140 -0
- rslearn/dataset/window.py +181 -44
- rslearn/lightning_cli.py +454 -0
- rslearn/log_utils.py +24 -0
- rslearn/main.py +384 -181
- rslearn/models/anysat.py +215 -0
- rslearn/models/attention_pooling.py +177 -0
- rslearn/models/clay/clay.py +231 -0
- rslearn/models/clay/configs/metadata.yaml +295 -0
- rslearn/models/clip.py +68 -0
- rslearn/models/component.py +111 -0
- rslearn/models/concatenate_features.py +103 -0
- rslearn/models/conv.py +63 -0
- rslearn/models/croma.py +306 -0
- rslearn/models/detr/__init__.py +5 -0
- rslearn/models/detr/box_ops.py +103 -0
- rslearn/models/detr/detr.py +504 -0
- rslearn/models/detr/matcher.py +107 -0
- rslearn/models/detr/position_encoding.py +114 -0
- rslearn/models/detr/transformer.py +429 -0
- rslearn/models/detr/util.py +24 -0
- rslearn/models/dinov3.py +177 -0
- rslearn/models/faster_rcnn.py +30 -28
- rslearn/models/feature_center_crop.py +53 -0
- rslearn/models/fpn.py +19 -8
- rslearn/models/galileo/__init__.py +5 -0
- rslearn/models/galileo/galileo.py +595 -0
- rslearn/models/galileo/single_file_galileo.py +1678 -0
- rslearn/models/module_wrapper.py +65 -0
- rslearn/models/molmo.py +69 -0
- rslearn/models/multitask.py +384 -28
- rslearn/models/olmoearth_pretrain/__init__.py +1 -0
- rslearn/models/olmoearth_pretrain/model.py +421 -0
- rslearn/models/olmoearth_pretrain/norm.py +86 -0
- rslearn/models/panopticon.py +170 -0
- rslearn/models/panopticon_data/sensors/drone.yaml +32 -0
- rslearn/models/panopticon_data/sensors/enmap.yaml +904 -0
- rslearn/models/panopticon_data/sensors/goes.yaml +9 -0
- rslearn/models/panopticon_data/sensors/himawari.yaml +9 -0
- rslearn/models/panopticon_data/sensors/intuition.yaml +606 -0
- rslearn/models/panopticon_data/sensors/landsat8.yaml +84 -0
- rslearn/models/panopticon_data/sensors/modis_terra.yaml +99 -0
- rslearn/models/panopticon_data/sensors/qb2_ge1.yaml +34 -0
- rslearn/models/panopticon_data/sensors/sentinel1.yaml +85 -0
- rslearn/models/panopticon_data/sensors/sentinel2.yaml +97 -0
- rslearn/models/panopticon_data/sensors/superdove.yaml +60 -0
- rslearn/models/panopticon_data/sensors/wv23.yaml +63 -0
- rslearn/models/pick_features.py +17 -10
- rslearn/models/pooling_decoder.py +60 -7
- rslearn/models/presto/__init__.py +5 -0
- rslearn/models/presto/presto.py +297 -0
- rslearn/models/presto/single_file_presto.py +926 -0
- rslearn/models/prithvi.py +1147 -0
- rslearn/models/resize_features.py +59 -0
- rslearn/models/sam2_enc.py +13 -9
- rslearn/models/satlaspretrain.py +38 -18
- rslearn/models/simple_time_series.py +188 -77
- rslearn/models/singletask.py +24 -13
- rslearn/models/ssl4eo_s12.py +40 -30
- rslearn/models/swin.py +44 -32
- rslearn/models/task_embedding.py +250 -0
- rslearn/models/terramind.py +256 -0
- rslearn/models/trunk.py +139 -0
- rslearn/models/unet.py +68 -22
- rslearn/models/upsample.py +48 -0
- rslearn/models/use_croma.py +508 -0
- rslearn/template_params.py +26 -0
- rslearn/tile_stores/__init__.py +41 -18
- rslearn/tile_stores/default.py +409 -0
- rslearn/tile_stores/tile_store.py +236 -132
- rslearn/train/all_patches_dataset.py +530 -0
- rslearn/train/callbacks/adapters.py +53 -0
- rslearn/train/callbacks/freeze_unfreeze.py +348 -17
- rslearn/train/callbacks/gradients.py +129 -0
- rslearn/train/callbacks/peft.py +116 -0
- rslearn/train/data_module.py +444 -20
- rslearn/train/dataset.py +588 -235
- rslearn/train/lightning_module.py +192 -62
- rslearn/train/model_context.py +88 -0
- rslearn/train/optimizer.py +31 -0
- rslearn/train/prediction_writer.py +319 -84
- rslearn/train/scheduler.py +92 -0
- rslearn/train/tasks/classification.py +55 -28
- rslearn/train/tasks/detection.py +132 -76
- rslearn/train/tasks/embedding.py +120 -0
- rslearn/train/tasks/multi_task.py +28 -14
- rslearn/train/tasks/per_pixel_regression.py +291 -0
- rslearn/train/tasks/regression.py +161 -44
- rslearn/train/tasks/segmentation.py +428 -53
- rslearn/train/tasks/task.py +6 -5
- rslearn/train/transforms/__init__.py +1 -1
- rslearn/train/transforms/concatenate.py +54 -10
- rslearn/train/transforms/crop.py +29 -11
- rslearn/train/transforms/flip.py +18 -6
- rslearn/train/transforms/mask.py +78 -0
- rslearn/train/transforms/normalize.py +101 -17
- rslearn/train/transforms/pad.py +19 -7
- rslearn/train/transforms/resize.py +83 -0
- rslearn/train/transforms/select_bands.py +76 -0
- rslearn/train/transforms/sentinel1.py +75 -0
- rslearn/train/transforms/transform.py +89 -70
- rslearn/utils/__init__.py +2 -6
- rslearn/utils/array.py +8 -6
- rslearn/utils/feature.py +2 -2
- rslearn/utils/fsspec.py +90 -1
- rslearn/utils/geometry.py +347 -7
- rslearn/utils/get_utm_ups_crs.py +2 -3
- rslearn/utils/grid_index.py +5 -5
- rslearn/utils/jsonargparse.py +178 -0
- rslearn/utils/mp.py +4 -3
- rslearn/utils/raster_format.py +268 -116
- rslearn/utils/rtree_index.py +64 -17
- rslearn/utils/sqlite_index.py +7 -1
- rslearn/utils/vector_format.py +252 -97
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/METADATA +532 -283
- rslearn-0.0.21.dist-info/RECORD +167 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/WHEEL +1 -1
- rslearn-0.0.21.dist-info/licenses/NOTICE +115 -0
- rslearn/data_sources/raster_source.py +0 -309
- rslearn/models/registry.py +0 -5
- rslearn/tile_stores/file.py +0 -242
- rslearn/utils/mgrs.py +0 -24
- rslearn/utils/utils.py +0 -22
- rslearn-0.0.1.dist-info/RECORD +0 -88
- /rslearn/{data_sources/geotiff.py → py.typed} +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/entry_points.txt +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info/licenses}/LICENSE +0 -0
- {rslearn-0.0.1.dist-info → rslearn-0.0.21.dist-info}/top_level.txt +0 -0
rslearn/data_sources/utils.py
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"""Utilities shared by data sources."""
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import UTC, datetime, timedelta
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
|
|
7
|
+
import shapely
|
|
8
|
+
|
|
3
9
|
from rslearn.config import QueryConfig, SpaceMode, TimeMode
|
|
4
10
|
from rslearn.data_sources import Item
|
|
11
|
+
from rslearn.log_utils import get_logger
|
|
5
12
|
from rslearn.utils import STGeometry, shp_intersects
|
|
6
13
|
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
7
16
|
MOSAIC_MIN_ITEM_COVERAGE = 0.1
|
|
8
17
|
"""Minimum fraction of area that item should cover when adding it to a mosaic group."""
|
|
9
18
|
|
|
@@ -11,17 +20,203 @@ MOSAIC_REMAINDER_EPSILON = 0.01
|
|
|
11
20
|
"""Fraction of original geometry area below which mosaic is considered to contain the
|
|
12
21
|
entire geometry."""
|
|
13
22
|
|
|
23
|
+
ItemType = TypeVar("ItemType", bound=Item)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class PendingMosaic:
|
|
28
|
+
"""A mosaic being created by match_candidate_items_to_window.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
items: the list of items in the mosaic.
|
|
32
|
+
remainder: the remainder of the geometry that is not covered by any of the
|
|
33
|
+
items.
|
|
34
|
+
completed: whether the mosaic is done (sufficient coverage of the geometry).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Cannot use list[ItemType] here.
|
|
38
|
+
items: list
|
|
39
|
+
remainder: shapely.Polygon
|
|
40
|
+
completed: bool = False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def mosaic_matching(
|
|
44
|
+
window_geometry: STGeometry,
|
|
45
|
+
items: list[ItemType],
|
|
46
|
+
item_shps: list[shapely.Geometry],
|
|
47
|
+
max_matches: int,
|
|
48
|
+
) -> list[list[ItemType]]:
|
|
49
|
+
"""Spatial item matching for mosaic space mode.
|
|
50
|
+
|
|
51
|
+
This attempts to piece together items into mosaics that fully cover the window
|
|
52
|
+
geometry. If there are items leftover that only partially cover the window
|
|
53
|
+
geometry, they are returned as partial mosaics.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
window_geometry: the geometry of the window.
|
|
57
|
+
items: list of items.
|
|
58
|
+
item_shps: the item shapes projected to the window's projection.
|
|
59
|
+
max_matches: the maximum number of matches (mosaics) to create.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
list of item groups, each one corresponding to a different mosaic.
|
|
63
|
+
"""
|
|
64
|
+
# To create mosaics, we iterate over the items in order, and add each item to
|
|
65
|
+
# the first mosaic that the new item adds coverage to.
|
|
66
|
+
|
|
67
|
+
# max_matches could be very high if the user just wants us to create as many
|
|
68
|
+
# mosaics as possible, so we initialize the list here as empty and just add
|
|
69
|
+
# more pending mosaics when it is necessary.
|
|
70
|
+
pending_mosaics: list[PendingMosaic] = []
|
|
71
|
+
|
|
72
|
+
for item, item_shp in zip(items, item_shps):
|
|
73
|
+
# See if the item can match with any existing mosaic.
|
|
74
|
+
item_matched = False
|
|
75
|
+
|
|
76
|
+
for pending_mosaic in pending_mosaics:
|
|
77
|
+
if pending_mosaic.completed:
|
|
78
|
+
continue
|
|
79
|
+
if not shp_intersects(item_shp, pending_mosaic.remainder):
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
# Check if the intersection area is too small.
|
|
83
|
+
# If it is a sizable part of the item or of the geometry, then proceed.
|
|
84
|
+
intersect_area = item_shp.intersection(pending_mosaic.remainder).area
|
|
85
|
+
if (
|
|
86
|
+
intersect_area / item_shp.area < MOSAIC_MIN_ITEM_COVERAGE
|
|
87
|
+
and intersect_area / pending_mosaic.remainder.area
|
|
88
|
+
< MOSAIC_MIN_ITEM_COVERAGE
|
|
89
|
+
):
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
pending_mosaic.remainder = pending_mosaic.remainder - item_shp
|
|
93
|
+
pending_mosaic.items.append(item)
|
|
94
|
+
item_matched = True
|
|
95
|
+
|
|
96
|
+
# Mark the mosaic completed if it has sufficient coverage of the
|
|
97
|
+
# geometry.
|
|
98
|
+
if (
|
|
99
|
+
pending_mosaic.remainder.area / window_geometry.shp.area
|
|
100
|
+
< MOSAIC_REMAINDER_EPSILON
|
|
101
|
+
):
|
|
102
|
+
pending_mosaic.completed = True
|
|
103
|
+
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if item_matched:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# See if we can add a new mosaic based on this item. There must be room for
|
|
110
|
+
# more mosaics, but the item must also intersect the requested geometry.
|
|
111
|
+
if len(pending_mosaics) >= max_matches:
|
|
112
|
+
continue
|
|
113
|
+
intersect_area = item_shp.intersection(window_geometry.shp).area
|
|
114
|
+
if (
|
|
115
|
+
intersect_area / item_shp.area < MOSAIC_MIN_ITEM_COVERAGE
|
|
116
|
+
and intersect_area / window_geometry.shp.area < MOSAIC_MIN_ITEM_COVERAGE
|
|
117
|
+
):
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
pending_mosaics.append(
|
|
121
|
+
PendingMosaic(
|
|
122
|
+
items=[item],
|
|
123
|
+
remainder=window_geometry.shp - item_shp,
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return [pending_mosaic.items for pending_mosaic in pending_mosaics]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def per_period_mosaic_matching(
|
|
131
|
+
window_geometry: STGeometry,
|
|
132
|
+
item_list: list[ItemType],
|
|
133
|
+
period_duration: timedelta,
|
|
134
|
+
max_matches: int,
|
|
135
|
+
) -> list[list[ItemType]]:
|
|
136
|
+
"""Match items to the geometry with one mosaic per period.
|
|
137
|
+
|
|
138
|
+
We divide the time range of the geometry into shorter periods. Within each period,
|
|
139
|
+
we use the items corresponding to that period to create a mosaic. The returned item
|
|
140
|
+
groups include one group per period, starting from the most recent periods, up to
|
|
141
|
+
the provided max_matches.
|
|
142
|
+
|
|
143
|
+
The periods are also bounded to the window's time range, and aligned with the end
|
|
144
|
+
of that time range, i.e. the most recent window is
|
|
145
|
+
(end_time - period_duration, end_time), the next is
|
|
146
|
+
(end_time - 2*period_duration, end_time - period_duration), and so on. Note that
|
|
147
|
+
this means that if the window duration is shorter than the period_duration, there
|
|
148
|
+
will be zero matches.
|
|
149
|
+
|
|
150
|
+
This is used e.g. when a model should process three mosaics, where each mosaic
|
|
151
|
+
should come from a different month. This gives more diversity of images, since
|
|
152
|
+
simply searching for the least cloudy images could result in selecting all of the
|
|
153
|
+
images from the same month.
|
|
154
|
+
|
|
155
|
+
max_matches may be smaller than the total number of periods in the given time
|
|
156
|
+
range. In this case, we prefer to use mosaics of the most recent periods. However,
|
|
157
|
+
sometimes there may be no items in a period; in that case, the older periods are
|
|
158
|
+
used as a fallback. This means that reducing the window duration down to match
|
|
159
|
+
max_matches*period_duration is not equivalent to a longer window duration.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
window_geometry: the window geometry to match items to.
|
|
163
|
+
item_list: the list of items.
|
|
164
|
+
period_duration: the duration of one period.
|
|
165
|
+
max_matches: the number of per-period mosaics to create.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
the matched item groups, where each group contains items that yield a
|
|
169
|
+
per-period mosaic.
|
|
170
|
+
"""
|
|
171
|
+
if window_geometry.time_range is None:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
"all windows must have time range for per period mosaic matching"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# For each period, we create an STGeometry with modified time range matching that
|
|
177
|
+
# period, and use it with match_candidate_items_to_window to get a mosaic.
|
|
178
|
+
cur_groups: list[list[ItemType]] = []
|
|
179
|
+
period_start = window_geometry.time_range[1] - period_duration
|
|
180
|
+
while (
|
|
181
|
+
period_start >= window_geometry.time_range[0] and len(cur_groups) < max_matches
|
|
182
|
+
):
|
|
183
|
+
period_time_range = (
|
|
184
|
+
period_start,
|
|
185
|
+
period_start + period_duration,
|
|
186
|
+
)
|
|
187
|
+
period_start -= period_duration
|
|
188
|
+
period_geom = STGeometry(
|
|
189
|
+
window_geometry.projection, window_geometry.shp, period_time_range
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# We modify the QueryConfig here since caller should be asking for
|
|
193
|
+
# multiple mosaics, but we just want one mosaic per period.
|
|
194
|
+
period_groups = match_candidate_items_to_window(
|
|
195
|
+
period_geom,
|
|
196
|
+
item_list,
|
|
197
|
+
QueryConfig(space_mode=SpaceMode.MOSAIC, max_matches=1),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# There should be zero or one group depending on whether there were
|
|
201
|
+
# any items that matched. We keep the group if it is there.
|
|
202
|
+
if len(period_groups) == 0 or len(period_groups[0]) == 0:
|
|
203
|
+
# No matches for this period.
|
|
204
|
+
continue
|
|
205
|
+
cur_groups.append(period_groups[0])
|
|
206
|
+
|
|
207
|
+
return cur_groups
|
|
208
|
+
|
|
14
209
|
|
|
15
210
|
def match_candidate_items_to_window(
|
|
16
|
-
geometry: STGeometry, items: list[
|
|
17
|
-
) -> list[list[
|
|
211
|
+
geometry: STGeometry, items: list[ItemType], query_config: QueryConfig
|
|
212
|
+
) -> list[list[ItemType]]:
|
|
18
213
|
"""Match candidate items to a window based on the query configuration.
|
|
19
214
|
|
|
20
215
|
Candidate items should be collected that intersect with the window's spatial
|
|
21
216
|
extent.
|
|
22
217
|
|
|
23
218
|
Args:
|
|
24
|
-
geometry: the window
|
|
219
|
+
geometry: the window's geometry
|
|
25
220
|
items: all items from the data source that intersect spatially with the geometry
|
|
26
221
|
query_config: the query configuration to use for matching
|
|
27
222
|
|
|
@@ -30,45 +225,45 @@ def match_candidate_items_to_window(
|
|
|
30
225
|
"""
|
|
31
226
|
# Use time mode to filter and order the items.
|
|
32
227
|
if geometry.time_range:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if not item.time_range or item.range[1] <= geometry.time_range[0]
|
|
49
|
-
]
|
|
50
|
-
elif query_config.time_mode == TimeMode.AFTER:
|
|
51
|
-
items = [
|
|
52
|
-
item
|
|
53
|
-
for item in items
|
|
54
|
-
if not item.time_range
|
|
55
|
-
or item.time_range[0] >= geometry.time_range[1]
|
|
56
|
-
]
|
|
228
|
+
items = [
|
|
229
|
+
item
|
|
230
|
+
for item in items
|
|
231
|
+
if geometry.intersects_time_range(item.geometry.time_range)
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
placeholder_datetime = datetime.now(UTC)
|
|
235
|
+
if query_config.time_mode == TimeMode.BEFORE:
|
|
236
|
+
items.sort(
|
|
237
|
+
key=lambda item: item.geometry.time_range[0]
|
|
238
|
+
if item.geometry.time_range
|
|
239
|
+
else placeholder_datetime,
|
|
240
|
+
reverse=True,
|
|
241
|
+
)
|
|
242
|
+
elif query_config.time_mode == TimeMode.AFTER:
|
|
57
243
|
items.sort(
|
|
58
|
-
key=lambda item: geometry.
|
|
244
|
+
key=lambda item: item.geometry.time_range[0]
|
|
245
|
+
if item.geometry.time_range
|
|
246
|
+
else placeholder_datetime,
|
|
247
|
+
reverse=False,
|
|
59
248
|
)
|
|
60
249
|
|
|
61
250
|
# Now apply space mode.
|
|
62
251
|
item_shps = []
|
|
63
252
|
for item in items:
|
|
64
253
|
item_geom = item.geometry
|
|
254
|
+
# We need to re-project items to the geometry projection for the spatial checks
|
|
255
|
+
# below. Unless the item's geometry indicates global coverage, in which case we
|
|
256
|
+
# set it to match the geometry to show that it should cover the entire
|
|
257
|
+
# geometry.
|
|
65
258
|
if item_geom.projection != geometry.projection:
|
|
66
|
-
|
|
259
|
+
if item_geom.is_global():
|
|
260
|
+
item_geom = geometry
|
|
261
|
+
else:
|
|
262
|
+
item_geom = item_geom.to_projection(geometry.projection)
|
|
67
263
|
item_shps.append(item_geom.shp)
|
|
68
264
|
|
|
69
|
-
groups = []
|
|
70
|
-
|
|
71
265
|
if query_config.space_mode == SpaceMode.CONTAINS:
|
|
266
|
+
groups = []
|
|
72
267
|
for item, item_shp in zip(items, item_shps):
|
|
73
268
|
if not item_shp.contains(geometry.shp):
|
|
74
269
|
continue
|
|
@@ -77,6 +272,7 @@ def match_candidate_items_to_window(
|
|
|
77
272
|
break
|
|
78
273
|
|
|
79
274
|
elif query_config.space_mode == SpaceMode.INTERSECTS:
|
|
275
|
+
groups = []
|
|
80
276
|
for item, item_shp in zip(items, item_shps):
|
|
81
277
|
if not shp_intersects(item_shp, geometry.shp):
|
|
82
278
|
continue
|
|
@@ -85,40 +281,32 @@ def match_candidate_items_to_window(
|
|
|
85
281
|
break
|
|
86
282
|
|
|
87
283
|
elif query_config.space_mode == SpaceMode.MOSAIC:
|
|
88
|
-
|
|
89
|
-
# Each time the geometry is fully covered, we start another group.
|
|
90
|
-
# We terminate when there are no more items or we have exceeded max_matches.
|
|
91
|
-
cur_remainder = None
|
|
92
|
-
cur_group = []
|
|
284
|
+
groups = mosaic_matching(geometry, items, item_shps, query_config.max_matches)
|
|
93
285
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
286
|
+
elif query_config.space_mode == SpaceMode.PER_PERIOD_MOSAIC:
|
|
287
|
+
groups = per_period_mosaic_matching(
|
|
288
|
+
geometry, items, query_config.period_duration, query_config.max_matches
|
|
289
|
+
)
|
|
97
290
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# If it is a sizable part of the item or of the geometry, then continue.
|
|
103
|
-
intersect_area = item_shp.intersection(cur_remainder).area
|
|
104
|
-
if (
|
|
105
|
-
intersect_area / item_shp.area < MOSAIC_MIN_ITEM_COVERAGE
|
|
106
|
-
and intersect_area / cur_remainder.area < MOSAIC_MIN_ITEM_COVERAGE
|
|
107
|
-
):
|
|
291
|
+
elif query_config.space_mode == SpaceMode.COMPOSITE:
|
|
292
|
+
group = []
|
|
293
|
+
for item, item_shp in zip(items, item_shps):
|
|
294
|
+
if not shp_intersects(item_shp, geometry.shp):
|
|
108
295
|
continue
|
|
296
|
+
group.append(item)
|
|
297
|
+
groups = [group]
|
|
109
298
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if cur_remainder.area / geometry.shp.area < MOSAIC_REMAINDER_EPSILON:
|
|
114
|
-
cur_remainder = None
|
|
115
|
-
groups.append(cur_group)
|
|
116
|
-
cur_group = []
|
|
117
|
-
|
|
118
|
-
if len(groups) >= query_config.max_matches:
|
|
119
|
-
break
|
|
299
|
+
else:
|
|
300
|
+
raise ValueError(f"invalid space mode {query_config.space_mode}")
|
|
120
301
|
|
|
121
|
-
|
|
122
|
-
|
|
302
|
+
# Enforce minimum matches if set.
|
|
303
|
+
if len(groups) < query_config.min_matches:
|
|
304
|
+
logger.warning(
|
|
305
|
+
"Window rejected: found %d matches (required: %d) for time range %s",
|
|
306
|
+
len(groups),
|
|
307
|
+
query_config.min_matches,
|
|
308
|
+
geometry.time_range if geometry.time_range else "unlimited",
|
|
309
|
+
)
|
|
310
|
+
return []
|
|
123
311
|
|
|
124
312
|
return groups
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Placeholder for a vector data source."""
|