ssb-sgis 1.1.11__tar.gz → 1.1.14__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.
Files changed (65) hide show
  1. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/PKG-INFO +1 -1
  2. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/pyproject.toml +1 -1
  3. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/polygon_operations.py +0 -6
  4. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/io/dapla_functions.py +26 -15
  5. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/explore.py +1 -1
  6. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/map.py +2 -0
  7. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/image_collection.py +88 -16
  8. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/LICENSE +0 -0
  9. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/README.md +0 -0
  10. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/__init__.py +0 -0
  11. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/conf.py +0 -0
  12. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/debug_config.py +0 -0
  13. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/exceptions.py +0 -0
  14. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/__init__.py +0 -0
  15. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/bounds.py +0 -0
  16. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/buffer_dissolve_explode.py +0 -0
  17. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/centerlines.py +0 -0
  18. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/cleaning.py +0 -0
  19. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/conversion.py +0 -0
  20. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/duplicates.py +0 -0
  21. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/general.py +0 -0
  22. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/geocoding.py +0 -0
  23. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/geometry_types.py +0 -0
  24. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/neighbors.py +0 -0
  25. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/overlay.py +0 -0
  26. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/point_operations.py +0 -0
  27. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/polygons_as_rings.py +0 -0
  28. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/geopandas_tools/sfilter.py +0 -0
  29. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/helpers.py +0 -0
  30. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/io/__init__.py +0 -0
  31. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/io/_is_dapla.py +0 -0
  32. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/io/opener.py +0 -0
  33. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/io/read_parquet.py +0 -0
  34. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/__init__.py +0 -0
  35. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/examine.py +0 -0
  36. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/httpserver.py +0 -0
  37. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/legend.py +0 -0
  38. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/maps.py +0 -0
  39. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/norge_i_bilder.json +0 -0
  40. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/thematicmap.py +0 -0
  41. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/tilesources.py +0 -0
  42. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/maps/wms.py +0 -0
  43. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/__init__.py +0 -0
  44. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/_get_route.py +0 -0
  45. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/_od_cost_matrix.py +0 -0
  46. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/_points.py +0 -0
  47. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/_service_area.py +0 -0
  48. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/closing_network_holes.py +0 -0
  49. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/cutting_lines.py +0 -0
  50. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/directednetwork.py +0 -0
  51. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/finding_isolated_networks.py +0 -0
  52. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/network.py +0 -0
  53. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/networkanalysis.py +0 -0
  54. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/networkanalysisrules.py +0 -0
  55. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/nodes.py +0 -0
  56. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/networkanalysis/traveling_salesman.py +0 -0
  57. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/parallel/__init__.py +0 -0
  58. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/parallel/parallel.py +0 -0
  59. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/py.typed +0 -0
  60. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/__init__.py +0 -0
  61. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/base.py +0 -0
  62. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/indices.py +0 -0
  63. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/regex.py +0 -0
  64. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/sentinel_config.py +0 -0
  65. {ssb_sgis-1.1.11 → ssb_sgis-1.1.14}/src/sgis/raster/zonal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ssb-sgis
3
- Version: 1.1.11
3
+ Version: 1.1.14
4
4
  Summary: GIS functions used at Statistics Norway.
5
5
  Home-page: https://github.com/statisticsnorway/ssb-sgis
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ssb-sgis"
3
- version = "1.1.11"
3
+ version = "1.1.14"
4
4
  description = "GIS functions used at Statistics Norway."
5
5
  authors = ["Morten Letnes <morten.letnes@ssb.no>"]
6
6
  license = "MIT"
@@ -425,12 +425,6 @@ def eliminate_by_longest(
425
425
  explore_locals(center=_DEBUG_CONFIG["center"])
426
426
 
427
427
  if not _recurse and len(isolated):
428
- if 0:
429
- isolated.geometry = isolated.buffer(
430
- -PRECISION,
431
- resolution=1,
432
- join_style=2,
433
- )
434
428
  out, isolated = _recursively_eliminate_new_neighbors(
435
429
  out,
436
430
  isolated,
@@ -109,7 +109,7 @@ def read_geopandas(
109
109
  if single_eq_filter:
110
110
  try:
111
111
  expression = "".join(next(iter(filters))).replace("==", "=")
112
- glob_func = _get_glob(file_system)
112
+ glob_func = _get_glob_func(file_system)
113
113
  paths = glob_func(str(Path(gcs_path) / expression))
114
114
  if paths:
115
115
  return _read_geopandas_from_iterable(
@@ -543,7 +543,7 @@ def _write_partitioned_geoparquet(
543
543
  if df[col].isna().all() and not kwargs.get("schema"):
544
544
  raise ValueError("Must specify 'schema' when all rows are NA.")
545
545
 
546
- glob_func = _get_glob(file_system)
546
+ glob_func = _get_glob_func(file_system)
547
547
 
548
548
  if file_system.exists(path) and file_system.isfile(path):
549
549
  _remove_file(path, file_system)
@@ -596,7 +596,7 @@ def _write_partitioned_geoparquet(
596
596
  executor.map(threaded_write, dfs, paths)
597
597
 
598
598
 
599
- def _get_glob(file_system) -> functools.partial:
599
+ def _get_glob_func(file_system) -> functools.partial:
600
600
  try:
601
601
  return functools.partial(file_system.glob)
602
602
  except AttributeError:
@@ -724,9 +724,9 @@ def _read_partitioned_parquet(
724
724
  **kwargs,
725
725
  ):
726
726
  file_system = _get_file_system(file_system, kwargs)
727
+ glob_func = _get_glob_func(file_system)
727
728
 
728
729
  if child_paths is None:
729
- glob_func = _get_glob(file_system)
730
730
  child_paths = list(glob_func(str(Path(path) / "**/*.parquet")))
731
731
 
732
732
  filters = _filters_to_expression(filters)
@@ -735,18 +735,29 @@ def _read_partitioned_parquet(
735
735
  bbox, _ = _get_bounds_parquet_from_open_file(file, file_system)
736
736
  return shapely.box(*bbox).intersects(to_shapely(mask))
737
737
 
738
- def read(path: str) -> pyarrow.Table | None:
739
- with file_system.open(path, "rb") as file:
740
- if mask is not None and not intersects(file, mask):
741
- return
738
+ def read(child_path: str) -> pyarrow.Table | None:
739
+ try:
740
+ with file_system.open(child_path, "rb") as file:
741
+ if mask is not None and not intersects(file, mask):
742
+ return
742
743
 
743
- # 'get' instead of 'pop' because dict is mutable
744
- schema = kwargs.get("schema", pq.read_schema(file))
745
- new_kwargs = {
746
- key: value for key, value in kwargs.items() if key != "schema"
747
- }
744
+ # 'get' instead of 'pop' because dict is mutable
745
+ schema = kwargs.get("schema", pq.read_schema(file))
746
+ new_kwargs = {
747
+ key: value for key, value in kwargs.items() if key != "schema"
748
+ }
748
749
 
749
- return read_func(file, schema=schema, filters=filters, **new_kwargs)
750
+ return read_func(file, schema=schema, filters=filters, **new_kwargs)
751
+ except ArrowInvalid as e:
752
+ if not len(
753
+ {
754
+ x
755
+ for x in glob_func(str(Path(child_path) / "**"))
756
+ if not paths_are_equal(child_path, x)
757
+ }
758
+ ):
759
+ raise e
760
+ # allow not being able to read hard-to-delete empty directories
750
761
 
751
762
  with ThreadPoolExecutor() as executor:
752
763
  results = [
@@ -790,7 +801,7 @@ def paths_are_equal(path1: Path | str, path2: Path | str) -> bool:
790
801
 
791
802
 
792
803
  def get_child_paths(path, file_system) -> list[str]:
793
- glob_func = _get_glob(file_system)
804
+ glob_func = _get_glob_func(file_system)
794
805
  return [
795
806
  x
796
807
  for x in glob_func(str(Path(path) / "**/*.parquet"))
@@ -828,7 +828,7 @@ class Explore(Map):
828
828
  if not len(gdf):
829
829
  continue
830
830
 
831
- gdf = self._to_single_geom_type(make_all_singlepart(gdf))
831
+ gdf = self._to_single_geom_type(make_all_singlepart(gdf, ignore_index=True))
832
832
 
833
833
  if not len(gdf):
834
834
  continue
@@ -729,6 +729,8 @@ class Map:
729
729
  """Place the column values into groups."""
730
730
  bins = bins.copy()
731
731
 
732
+ assert gdf.index.is_unique
733
+
732
734
  # if equal lenght, convert to integer and check for equality
733
735
  if len(bins) == len(self._unique_values):
734
736
  if gdf[self._column].isna().all():
@@ -2,6 +2,7 @@ import datetime
2
2
  import functools
3
3
  import glob
4
4
  import itertools
5
+ import json
5
6
  import os
6
7
  import random
7
8
  import re
@@ -214,13 +215,14 @@ class PixelwiseResults:
214
215
 
215
216
  def to_geopandas(self, column: str | list[str] = "value") -> GeoDataFrame:
216
217
  """Return GeoDataFrame with pixel geometries and values from the pixelwise operation."""
217
- minx, miny = self.bounds[:2]
218
218
  resx, resy = _res_as_tuple(self.res)
219
219
 
220
- minxs = np.full(self.row_indices.shape, minx) + (self.row_indices * resx)
221
- minys = np.full(self.col_indices.shape, miny) + (self.col_indices * resy)
220
+ # work ourselves inwards from the bottom left and top right corners
221
+ minx, _, _, maxy = self.bounds
222
+ minxs = np.full(self.col_indices.shape, minx) + (self.col_indices * resx)
223
+ maxys = np.full(self.row_indices.shape, maxy) - (self.row_indices * resy)
222
224
  maxxs = minxs + resx
223
- maxys = minys + resy
225
+ minys = maxys - resy
224
226
 
225
227
  return GeoDataFrame(
226
228
  {
@@ -467,8 +469,8 @@ class _ImageBase:
467
469
  regexes = (regexes,)
468
470
  return tuple(re.compile(regexes, flags=re.VERBOSE) for regexes in regexes)
469
471
 
470
- @staticmethod
471
472
  def _metadata_to_nested_dict(
473
+ self,
472
474
  metadata: str | Path | os.PathLike | dict | pd.DataFrame | None,
473
475
  ) -> dict[str, dict[str, Any]]:
474
476
  """Construct metadata dict from dictlike, DataFrame or file path.
@@ -931,14 +933,20 @@ class Band(_ImageBandBase):
931
933
  }
932
934
 
933
935
  if self.metadata:
936
+ parent = _fix_path(str(Path(self.path).parent))
934
937
  if self.path is not None:
935
938
  self.metadata = {
936
- key: value
937
- for key, value in self.metadata.items()
938
- if key == self.path
939
+ key: value for key, value in self.metadata.items() if key == parent
939
940
  }
940
- this_metadata = self.metadata[self.path]
941
- for key, value in this_metadata.items():
941
+ for key, value in self.metadata.get(parent, {}).items():
942
+ if key == "bands" and self.band_id in value:
943
+ band_metadata = value[self.band_id]
944
+ for band_key, band_value in band_metadata.items():
945
+ if band_key in dir(self):
946
+ setattr(self, f"_{band_key}", band_value)
947
+ else:
948
+ setattr(self, band_key, band_value)
949
+ continue
942
950
  if key in dir(self):
943
951
  setattr(self, f"_{key}", value)
944
952
  else:
@@ -1591,6 +1599,22 @@ class Image(_ImageBandBase):
1591
1599
  else:
1592
1600
  self._all_file_paths = None
1593
1601
 
1602
+ if not self.metadata and "metadata.json" in {
1603
+ Path(x).name for x in self._all_file_paths
1604
+ }:
1605
+ with _open_func(
1606
+ next(
1607
+ iter(
1608
+ {
1609
+ x
1610
+ for x in self._all_file_paths
1611
+ if str(x).endswith("metadata.json")
1612
+ }
1613
+ )
1614
+ )
1615
+ ) as file:
1616
+ self.metadata = json.load(file)
1617
+
1594
1618
  if df is None:
1595
1619
  if not self._all_file_paths:
1596
1620
  self._all_file_paths = {self.path}
@@ -1615,12 +1639,10 @@ class Image(_ImageBandBase):
1615
1639
  key: value for key, value in self.metadata.items() if self.path in key
1616
1640
  }
1617
1641
 
1618
- if self.metadata:
1619
- try:
1620
- metadata = self.metadata[self.path]
1621
- except KeyError as e:
1622
- metadata = {}
1623
- for key, value in metadata.items():
1642
+ if self.metadata.get(self.path, {}):
1643
+ for key, value in self.metadata[self.path].items():
1644
+ if key in {"bands"}:
1645
+ continue
1624
1646
  if key in dir(self):
1625
1647
  setattr(self, f"_{key}", value)
1626
1648
  else:
@@ -1704,6 +1726,56 @@ class Image(_ImageBandBase):
1704
1726
 
1705
1727
  return self
1706
1728
 
1729
+ def get_image_metadata_dict(self) -> dict:
1730
+ """Creates a nested dict of metadata.
1731
+
1732
+ The dict structure will be:
1733
+
1734
+ {
1735
+ image_path: {
1736
+ image_attribute: value,
1737
+ ...,
1738
+ "bands": {
1739
+ band_id: {
1740
+ band_attribute: band_value,
1741
+ },
1742
+ ...,
1743
+ }
1744
+ }
1745
+ }
1746
+ """
1747
+ path = self.path
1748
+ metadata = {
1749
+ path: {
1750
+ "bounds": self.bounds,
1751
+ "crs": str(pyproj.CRS(self.crs).to_string()),
1752
+ }
1753
+ }
1754
+ for key in self.metadata_attributes:
1755
+ metadata[path][key] = getattr(self, key)
1756
+
1757
+ metadata[path]["bands"] = {}
1758
+ for band in self:
1759
+ metadata[path]["bands"][band.band_id] = {}
1760
+ for key in band.metadata_attributes:
1761
+ if key in self.metadata_attributes:
1762
+ continue
1763
+ metadata[path]["bands"][band.band_id][key] = getattr(band, key)
1764
+ return metadata
1765
+
1766
+ def write_image_metadata(self) -> None:
1767
+ """Write file 'metadata.json' under image path.
1768
+
1769
+ The file will be used to give the image attributes
1770
+ and avoid the much slower metadata fetching with rasterio.
1771
+
1772
+ See method 'get_image_metadata_dict' for info on the structure of
1773
+ the json file.
1774
+ """
1775
+ metadata = self.get_image_metadata_dict()
1776
+ with _open_func(str(Path(self.path) / "metadata.json"), "w") as file:
1777
+ json.dump(metadata, file)
1778
+
1707
1779
  def _construct_image_from_bands(
1708
1780
  self, data: Sequence[Band], res: int | None
1709
1781
  ) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes