ssb-sgis 1.0.9__tar.gz → 1.0.10__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.
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/PKG-INFO +1 -1
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/pyproject.toml +1 -1
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/cleaning.py +3 -3
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/_is_dapla.py +2 -5
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/image_collection.py +112 -47
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/LICENSE +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/README.md +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/__init__.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/debug_config.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/exceptions.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/__init__.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/bounds.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/centerlines.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/conversion.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/duplicates.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/general.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/geocoding.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/geometry_types.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/neighbors.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/overlay.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/point_operations.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/polygon_operations.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/geopandas_tools/sfilter.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/helpers.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/dapla_functions.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/opener.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/io/read_parquet.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/__init__.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/examine.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/explore.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/httpserver.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/legend.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/map.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/maps.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/thematicmap.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/maps/tilesources.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/__init__.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_get_route.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_points.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/_service_area.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/cutting_lines.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/directednetwork.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/network.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/networkanalysis.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/nodes.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/parallel/parallel.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/py.typed +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/__init__.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/base.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/indices.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/regex.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/sentinel_config.py +0 -0
- {ssb_sgis-1.0.9 → ssb_sgis-1.0.10}/src/sgis/raster/zonal.py +0 -0
|
@@ -579,9 +579,9 @@ def _snap_to_anchors(
|
|
|
579
579
|
# browser=True,
|
|
580
580
|
)
|
|
581
581
|
|
|
582
|
-
print(
|
|
583
|
-
|
|
584
|
-
) # , j2, j3, x)
|
|
582
|
+
# print(
|
|
583
|
+
# "line_is_simple", line_is_simple, range_index, i, index, j
|
|
584
|
+
# ) # , j2, j3, x)
|
|
585
585
|
|
|
586
586
|
if not line_is_simple:
|
|
587
587
|
# for j4 in range(len(ring)):
|
|
@@ -7,8 +7,5 @@ import os
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def is_dapla() -> bool:
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
return os.environ["GCS_TOKEN_PROVIDER_KEY"] == "google"
|
|
13
|
-
except KeyError:
|
|
14
|
-
return False
|
|
10
|
+
"""Simply checks if an os environment variable contains the text 'dapla'."""
|
|
11
|
+
return any("dapla" in key.lower() for key in os.environ)
|
|
@@ -177,6 +177,90 @@ def _get_child_paths_threaded(data: Sequence[str]) -> set[str]:
|
|
|
177
177
|
return set(itertools.chain.from_iterable(all_paths))
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
@dataclass
|
|
181
|
+
class PixelwiseResults:
|
|
182
|
+
"""Container of results from pixelwise operation to be converted."""
|
|
183
|
+
|
|
184
|
+
row_indices: np.ndarray
|
|
185
|
+
col_indices: np.ndarray
|
|
186
|
+
results: list[Any]
|
|
187
|
+
res: int | tuple[int, int]
|
|
188
|
+
bounds: tuple[float, float, float, float]
|
|
189
|
+
shape: tuple[int, int]
|
|
190
|
+
crs: Any
|
|
191
|
+
nodata: int | float | None
|
|
192
|
+
|
|
193
|
+
def to_tuple(self) -> tuple[int, int, Any]:
|
|
194
|
+
"""Return 3-length tuple of row indices, column indices and pixelwise results."""
|
|
195
|
+
return self.row_indices, self.col_indices, self.results
|
|
196
|
+
|
|
197
|
+
def to_dict(self) -> dict[tuple[int, int], Any]:
|
|
198
|
+
"""Return dictionary with row and column indices as keys and pixelwise results as values."""
|
|
199
|
+
return {
|
|
200
|
+
(int(row), int(col)): value
|
|
201
|
+
for row, col, value in zip(
|
|
202
|
+
self.row_indices, self.col_indices, self.results, strict=True
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def to_geopandas(self, column: str = "value") -> GeoDataFrame:
|
|
207
|
+
"""Return GeoDataFrame with pixel geometries and values from the pixelwise operation."""
|
|
208
|
+
minx, miny = self.bounds[:2]
|
|
209
|
+
resx, resy = _res_as_tuple(self.res)
|
|
210
|
+
|
|
211
|
+
minxs = np.full(self.row_indices.shape, minx) + (minx * self.row_indices * resx)
|
|
212
|
+
minys = np.full(self.col_indices.shape, miny) + (miny * self.col_indices * resy)
|
|
213
|
+
maxxs = minxs + resx
|
|
214
|
+
maxys = minys + resy
|
|
215
|
+
|
|
216
|
+
return GeoDataFrame(
|
|
217
|
+
{
|
|
218
|
+
column: self.results,
|
|
219
|
+
"geometry": [
|
|
220
|
+
box(minx, miny, maxx, maxy)
|
|
221
|
+
for minx, miny, maxx, maxy in zip(
|
|
222
|
+
minxs, minys, maxxs, maxys, strict=True
|
|
223
|
+
)
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
index=[self.row_indices, self.col_indices],
|
|
227
|
+
crs=self.crs,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def to_numpy(self) -> np.ndarray | tuple[np.ndarray, ...]:
|
|
231
|
+
"""Reshape pixelwise results to 2d numpy arrays in the shape of the full arrays of the image bands."""
|
|
232
|
+
try:
|
|
233
|
+
n_out_arrays = len(next(iter(self.results)))
|
|
234
|
+
except TypeError:
|
|
235
|
+
n_out_arrays = 1
|
|
236
|
+
|
|
237
|
+
out_arrays = [
|
|
238
|
+
np.full(self.shape, self.nodata).astype(np.float64)
|
|
239
|
+
for _ in range(n_out_arrays)
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
for row, col, these_results in zip(
|
|
243
|
+
self.row_indices, self.col_indices, self.results, strict=True
|
|
244
|
+
):
|
|
245
|
+
if these_results is None:
|
|
246
|
+
continue
|
|
247
|
+
for i, arr in enumerate(out_arrays):
|
|
248
|
+
try:
|
|
249
|
+
arr[row, col] = these_results[i]
|
|
250
|
+
except TypeError:
|
|
251
|
+
arr[row, col] = these_results
|
|
252
|
+
|
|
253
|
+
for i, array in enumerate(out_arrays):
|
|
254
|
+
all_are_integers = np.all(np.mod(array, 1) == 0)
|
|
255
|
+
if all_are_integers:
|
|
256
|
+
out_arrays[i] = array.astype(int)
|
|
257
|
+
|
|
258
|
+
if len(out_arrays) == 1:
|
|
259
|
+
return out_arrays[0]
|
|
260
|
+
|
|
261
|
+
return tuple(out_arrays)
|
|
262
|
+
|
|
263
|
+
|
|
180
264
|
class ImageCollectionGroupBy:
|
|
181
265
|
"""Iterator and merger class returned from groupby.
|
|
182
266
|
|
|
@@ -617,19 +701,19 @@ class _ImageBandBase(_ImageBase):
|
|
|
617
701
|
return nonmissing_metadata_attributes
|
|
618
702
|
|
|
619
703
|
# read all xml content once
|
|
620
|
-
file_contents:
|
|
704
|
+
file_contents: dict[str, str] = {}
|
|
621
705
|
for path in self._all_file_paths:
|
|
622
706
|
if ".xml" not in path:
|
|
623
707
|
continue
|
|
624
708
|
with _open_func(path, "rb") as file:
|
|
625
|
-
file_contents
|
|
709
|
+
file_contents[path] = file.read().decode("utf-8")
|
|
626
710
|
|
|
627
711
|
def is_last_xml(i: int) -> bool:
|
|
628
712
|
return i == len(file_contents) - 1
|
|
629
713
|
|
|
630
714
|
for attr, value in missing_metadata_attributes.items():
|
|
631
715
|
results = None
|
|
632
|
-
for i, file_content in enumerate(file_contents):
|
|
716
|
+
for i, file_content in enumerate(file_contents.values()):
|
|
633
717
|
if isinstance(value, str) and value in dir(self):
|
|
634
718
|
# method or a hardcoded value
|
|
635
719
|
value: Callable | Any = getattr(self, value)
|
|
@@ -639,7 +723,7 @@ class _ImageBandBase(_ImageBase):
|
|
|
639
723
|
results = value(file_content)
|
|
640
724
|
except _RegexError as e:
|
|
641
725
|
if is_last_xml(i):
|
|
642
|
-
raise e.__class__(self.path, e) from e
|
|
726
|
+
raise e.__class__(self.path, list(file_contents), e) from e
|
|
643
727
|
continue
|
|
644
728
|
if results is not None:
|
|
645
729
|
break
|
|
@@ -804,9 +888,7 @@ class Band(_ImageBandBase):
|
|
|
804
888
|
)
|
|
805
889
|
else:
|
|
806
890
|
self._path = _fix_path(str(data))
|
|
807
|
-
if callable(res) and res() is None
|
|
808
|
-
res = None
|
|
809
|
-
self._res = res
|
|
891
|
+
self._res = res if not (callable(res) and res() is None) else None
|
|
810
892
|
|
|
811
893
|
if cmap is not None:
|
|
812
894
|
self.cmap = cmap
|
|
@@ -1476,7 +1558,7 @@ class Image(_ImageBandBase):
|
|
|
1476
1558
|
f"'data' must be string, Path-like or a sequence of Band. Got {data}"
|
|
1477
1559
|
)
|
|
1478
1560
|
|
|
1479
|
-
self._res = res
|
|
1561
|
+
self._res = res if not (callable(res) and res() is None) else None
|
|
1480
1562
|
self._path = _fix_path(data)
|
|
1481
1563
|
|
|
1482
1564
|
if all_file_paths is None and self.path:
|
|
@@ -1955,7 +2037,7 @@ class ImageCollection(_ImageBase):
|
|
|
1955
2037
|
self.nodata = nodata
|
|
1956
2038
|
self.level = level
|
|
1957
2039
|
self.processes = processes
|
|
1958
|
-
self._res = res
|
|
2040
|
+
self._res = res if not (callable(res) and res() is None) else None
|
|
1959
2041
|
self._crs = None
|
|
1960
2042
|
|
|
1961
2043
|
self._df = None
|
|
@@ -2079,6 +2161,7 @@ class ImageCollection(_ImageBase):
|
|
|
2079
2161
|
kwargs: dict | None = None,
|
|
2080
2162
|
index_aligned_kwargs: dict | None = None,
|
|
2081
2163
|
masked: bool = True,
|
|
2164
|
+
processes: int | None = None,
|
|
2082
2165
|
) -> np.ndarray | tuple[np.ndarray] | None:
|
|
2083
2166
|
"""Run a function for each pixel.
|
|
2084
2167
|
|
|
@@ -2108,13 +2191,23 @@ class ImageCollection(_ImageBase):
|
|
|
2108
2191
|
else:
|
|
2109
2192
|
mask_array = None
|
|
2110
2193
|
|
|
2111
|
-
|
|
2194
|
+
nonmissing_row_indices, nonmissing_col_indices, results = pixelwise(
|
|
2112
2195
|
func=func,
|
|
2113
2196
|
values=values,
|
|
2114
2197
|
mask_array=mask_array,
|
|
2115
2198
|
index_aligned_kwargs=index_aligned_kwargs,
|
|
2116
2199
|
kwargs=kwargs,
|
|
2117
|
-
processes=self.processes,
|
|
2200
|
+
processes=processes or self.processes,
|
|
2201
|
+
)
|
|
2202
|
+
|
|
2203
|
+
return PixelwiseResults(
|
|
2204
|
+
nonmissing_row_indices,
|
|
2205
|
+
nonmissing_col_indices,
|
|
2206
|
+
results,
|
|
2207
|
+
shape=values.shape[1:],
|
|
2208
|
+
res=self.res,
|
|
2209
|
+
bounds=self.bounds,
|
|
2210
|
+
crs=self.crs,
|
|
2118
2211
|
nodata=self.nodata or np.nan,
|
|
2119
2212
|
)
|
|
2120
2213
|
|
|
@@ -2990,14 +3083,14 @@ class Sentinel2Config:
|
|
|
2990
3083
|
xml_file,
|
|
2991
3084
|
)
|
|
2992
3085
|
if match_ is None:
|
|
2993
|
-
|
|
3086
|
+
return None
|
|
2994
3087
|
|
|
2995
3088
|
if "NOT_REFINED" in match_.group(0):
|
|
2996
3089
|
return False
|
|
2997
3090
|
elif "REFINED" in match_.group(0):
|
|
2998
3091
|
return True
|
|
2999
3092
|
else:
|
|
3000
|
-
raise _RegexError()
|
|
3093
|
+
raise _RegexError(xml_file)
|
|
3001
3094
|
|
|
3002
3095
|
def _get_boa_quantification_value(self, xml_file: str) -> int:
|
|
3003
3096
|
return int(
|
|
@@ -3553,26 +3646,25 @@ def pixelwise(
|
|
|
3553
3646
|
index_aligned_kwargs: dict | None = None,
|
|
3554
3647
|
kwargs: dict | None = None,
|
|
3555
3648
|
processes: int = 1,
|
|
3556
|
-
|
|
3557
|
-
) -> Any:
|
|
3649
|
+
) -> tuple[np.ndarray, np.ndarray, list[Any]]:
|
|
3558
3650
|
"""Run a function for each pixel of a 3d array."""
|
|
3559
3651
|
index_aligned_kwargs = index_aligned_kwargs or {}
|
|
3560
3652
|
kwargs = kwargs or {}
|
|
3561
3653
|
|
|
3562
3654
|
if mask_array is not None:
|
|
3655
|
+
# skip pixels where all values are masked
|
|
3563
3656
|
not_all_missing = np.all(mask_array, axis=0) == False
|
|
3564
|
-
|
|
3565
3657
|
else:
|
|
3566
3658
|
mask_array = np.full(values.shape, False)
|
|
3567
3659
|
not_all_missing = np.full(values.shape[1:], True)
|
|
3568
3660
|
|
|
3569
|
-
nonmissing_row_indices, nonmissing_col_indices = not_all_missing.nonzero()
|
|
3570
|
-
|
|
3571
3661
|
def select_pixel_values(row: int, col: int) -> np.ndarray:
|
|
3572
3662
|
return values[~mask_array[:, row, col], row, col]
|
|
3573
3663
|
|
|
3664
|
+
# loop through long 1d arrays of aligned row and col indices
|
|
3665
|
+
nonmissing_row_indices, nonmissing_col_indices = not_all_missing.nonzero()
|
|
3574
3666
|
with joblib.Parallel(n_jobs=processes, backend="loky") as parallel:
|
|
3575
|
-
results: list[
|
|
3667
|
+
results: list[Any] = parallel(
|
|
3576
3668
|
joblib.delayed(func)(
|
|
3577
3669
|
select_pixel_values(row, col),
|
|
3578
3670
|
**kwargs,
|
|
@@ -3586,31 +3678,4 @@ def pixelwise(
|
|
|
3586
3678
|
)
|
|
3587
3679
|
)
|
|
3588
3680
|
|
|
3589
|
-
|
|
3590
|
-
return
|
|
3591
|
-
|
|
3592
|
-
try:
|
|
3593
|
-
n_out_arrays = len(next(iter(results)))
|
|
3594
|
-
except TypeError:
|
|
3595
|
-
n_out_arrays = 1
|
|
3596
|
-
|
|
3597
|
-
out_arrays = tuple(np.full(values.shape[1:], nodata) for _ in range(n_out_arrays))
|
|
3598
|
-
|
|
3599
|
-
counter = 0
|
|
3600
|
-
for row, col in zip(nonmissing_row_indices, nonmissing_col_indices, strict=True):
|
|
3601
|
-
these_results = results[counter]
|
|
3602
|
-
if these_results is None:
|
|
3603
|
-
counter += 1
|
|
3604
|
-
continue
|
|
3605
|
-
for i, arr in enumerate(out_arrays):
|
|
3606
|
-
try:
|
|
3607
|
-
arr[row, col] = these_results[i]
|
|
3608
|
-
except TypeError:
|
|
3609
|
-
arr[row, col] = these_results
|
|
3610
|
-
counter += 1
|
|
3611
|
-
assert counter == len(results), (counter, len(results))
|
|
3612
|
-
|
|
3613
|
-
if len(out_arrays) == 1:
|
|
3614
|
-
return out_arrays[0]
|
|
3615
|
-
|
|
3616
|
-
return out_arrays
|
|
3681
|
+
return nonmissing_row_indices, nonmissing_col_indices, results
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|