Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: GlacioHack/geoutils
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.1.9
Choose a base ref
...
head repository: GlacioHack/geoutils
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.1.10
Choose a head ref
  • 6 commits
  • 15 files changed
  • 1 contributor

Commits on Sep 30, 2024

  1. Fix bug for raster with nodata equal to 0 in stack_rasters (#609)

    rhugonnet authored Sep 30, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    d80304d View commit details

Commits on Oct 5, 2024

  1. Add Rasterio 1.4 support, and fix OSGeo import (#611)

    rhugonnet authored Oct 5, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    d504873 View commit details

Commits on Oct 24, 2024

  1. Improve resolution of Sphinx-Gallery figures and pin max Python versi…

    …on for pip (#613)
    rhugonnet authored Oct 24, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    5d4fab1 View commit details

Commits on Oct 30, 2024

  1. Add Python 3.12 support (#618)

    rhugonnet authored Oct 30, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    c2b0a60 View commit details
  2. Add tight layout by default in Raster.plot() (#617)

    rhugonnet authored Oct 30, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    a548d06 View commit details
  3. Update release number to 0.1.10 (#619)

    rhugonnet authored Oct 30, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e7538d1 View commit details
3 changes: 2 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -2,5 +2,6 @@

- [ ] Resolves #xxx,
- [ ] Tests added, otherwise issue #xxx opened,
- [ ] New optional dependencies added to both `dev-environment.yml` and `setup.cfg`,
- [ ] Fully documented, including `api/*.md` for new API.
- [ ] New optional dependencies or Python version support added to both `dev-environment.yml` and `setup.cfg`,
- [ ] If contributor workflow (test, doc, linting) or Python version support changed, update `CONTRIBUTING.md`.
4 changes: 2 additions & 2 deletions .github/workflows/pip-checks.yml
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11", "3.12"]

# Run all shells using bash (including Windows)
defaults:
@@ -36,7 +36,7 @@ jobs:
use-mamba: true
channel-priority: strict
activate-environment: geoutils-pip
python-version:
python-version: ${{ matrix.python-version }}

# Use pip install
- name: Install project
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
2 changes: 1 addition & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ["3.9", "3.10", "3.11"]
python-version: ["3.10", "3.11", "3.12"]

defaults:
run:
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ For more details, see the rest of this document.

## Development environment

GeoUtils currently supports only Python versions of 3.9 and higher, see `environment.yml` for detailed dependencies.
GeoUtils currently supports only Python versions of 3.10 to 3.12, see `environment.yml` for detailed dependencies.

### Setup

3 changes: 2 additions & 1 deletion dev-environment.yml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ name: geoutils-dev
channels:
- conda-forge
dependencies:
- python>=3.9,<3.12
- python>=3.10,<3.13
- geopandas>=0.12.0
- matplotlib=3.*
- pyproj=3.*
@@ -22,6 +22,7 @@ dependencies:
- scikit-image

# Test dependencies
- gdal # To test functionalities against GDAL
- pytest=7.*
- pytest-xdist
- pytest-lazy-fixture
6 changes: 6 additions & 0 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
sys.path.append(os.path.abspath("../.."))
sys.path.append(os.path.abspath("../../geoutils/"))
sys.path.append(os.path.abspath(".."))
sys.path.insert(0, os.path.dirname(__file__))

from sphinx_gallery.sorting import ExampleTitleSortKey, ExplicitOrder

@@ -104,6 +105,11 @@
"backreferences_dir": "gen_modules/backreferences",
"doc_module": ("geoutils"), # Which function/class levels are used to create galleries
"remove_config_comments": True, # To remove comments such as sphinx-gallery-thumbnail-number (only works in code, not in text)
"reset_modules": (
"matplotlib",
"sphinxext.reset_mpl",
),
# To reset matplotlib for each gallery (and run custom function that fixes the default DPI)
}

extlinks = {
2 changes: 1 addition & 1 deletion doc/source/index.md
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@ GeoUtils is a Python package for **accessible**, **efficient** and **reliable**

```{important}
:class: margin
GeoUtils ``v0.1`` is released, with most features drafted 3 years ago now finalized 🎉! We are working on an **Xarray accessor** and a few other features for 2024.
GeoUtils ``v0.1`` is released, with most features drafted 3 years ago now finalized 🎉! We are working on an **Xarray accessor** and a few other features for 2025.
```

GeoUtils is built on top of core geospatial packages (Rasterio, GeoPandas, PyProj) and numerical packages
8 changes: 8 additions & 0 deletions doc/source/sphinxext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Functions for documentation configuration only, importable by sphinx"""
# To reset resolution setting for each sphinx-gallery example
def reset_mpl(gallery_conf, fname):
# To get a good resolution for displayed figures
from matplotlib import pyplot

pyplot.rcParams["figure.dpi"] = 600
pyplot.rcParams["savefig.dpi"] = 600
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ name: geoutils
channels:
- conda-forge
dependencies:
- python>=3.9,<3.12
- python>=3.10,<3.13
- geopandas>=0.12.0
- matplotlib=3.*
- pyproj=3.*
12 changes: 8 additions & 4 deletions geoutils/raster/multiraster.py
Original file line number Diff line number Diff line change
@@ -165,26 +165,30 @@ def stack_rasters(
return_rio_bbox=True,
)

# Make a data list and add all of the reprojected rasters into it.
# Make a data list and add all the reprojected rasters into it.
data: list[NDArrayNum] = []

for raster in tqdm(rasters, disable=not progress):
# Check that data is loaded, otherwise temporarily load it
if not raster.is_loaded:
raster.load()

nodata = reference_raster.nodata or gu.raster.raster._default_nodata(reference_raster.data.dtype)
nodata = reference_raster.nodata if not None else gu.raster.raster._default_nodata(reference_raster.data.dtype)
# Reproject to reference grid
reprojected_raster = raster.reproject(
bounds=dst_bounds,
res=reference_raster.res,
crs=reference_raster.crs,
dtype=reference_raster.data.dtype,
nodata=reference_raster.nodata,
nodata=nodata,
resampling=resampling_method,
silent=True,
)
reprojected_raster.set_nodata(nodata)
# If the georeferenced grid was the same, reproject() will have returned self with a warning (silenced here),
# and we want to copy the raster and just modify its nodata (or would modify raster inputs of this function)
if reprojected_raster.georeferenced_grid_equal(raster):
reprojected_raster = reprojected_raster.copy()
reprojected_raster.set_nodata(nodata)

# Optionally calculate difference
if diff:
1 change: 1 addition & 0 deletions geoutils/raster/raster.py
Original file line number Diff line number Diff line change
@@ -3304,6 +3304,7 @@ def plot(
cbar = None

plt.sca(ax0)
plt.tight_layout()

# If returning axes
if return_axes:
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ version_file = "geoutils/_version.py"
fallback_version = "0.0.1"

[tool.black]
target_version = ['py36']
target_version = ['py310']

[tool.pytest.ini_options]
addopts = "--doctest-modules -W error::UserWarning"
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[metadata]
author = The GlacioHack Team
name = geoutils
version = 0.1.9
version = 0.1.10
description = Analysis and handling of georeferenced rasters and vectors
keywords = raster, vector, geospatial, gis, xarray
long_description = file: README.md
@@ -34,7 +34,7 @@ packages = find:
scripts = bin/geoviewer.py
zip_safe = False # https://mypy.readthedocs.io/en/stable/installed_packages.html
include_package_data = True
python_requires = >=3.9
python_requires = >=3.10,<3.13
# Avoid pinning dependencies in requirements.txt (which we don't do anyways, and we rely mostly on Conda)
# (https://caremad.io/posts/2013/07/setup-vs-requirement/, https://github.com/pypa/setuptools/issues/1951)
install_requires = file: requirements.txt
96 changes: 90 additions & 6 deletions tests/test_raster/test_multiraster.py
Original file line number Diff line number Diff line change
@@ -14,11 +14,12 @@
import geoutils as gu
from geoutils import examples
from geoutils.raster import RasterType
from geoutils.raster.raster import _default_nodata


class StackMergeImages:
class RealImageStack:
"""
Test cases for stacking and merging images
Real test cases for stacking and merging images
Split an image with some overlap, then stack/merge it, and validate bounds and shape.
Param `cls` is used to set the type of the output, e.g. gu.Raster (default).
"""
@@ -67,24 +68,93 @@ def __init__(
)


class SyntheticImageStack:
"""
Synthetic image stack for tests
Create a small synthetic example, where one can specify nodata value, values in second image (and potentially more
in the future).
"""

def __init__(self, nodata: int | float, img2_value: int | float):

shape = (10, 10)
data_int = np.ones(shape).astype(np.uint16)
data_mask = np.zeros(shape).astype(bool)
data_masked = np.ma.masked_array(data=data_int, mask=data_mask, fill_value=nodata)
img = gu.Raster.from_array(
data=data_masked,
transform=rio.transform.Affine(
1000.0,
0.0,
1_000_000.0,
0.0,
-1000.0,
1_000_000.0,
),
crs=pyproj.CRS.from_string("EPSG:3857"),
nodata=nodata,
)
self.img = img

# Find the easting midpoint of the img
x_midpoint = np.mean([self.img.bounds.right, self.img.bounds.left])
x_midpoint -= (x_midpoint - self.img.bounds.left) % self.img.res[0]

# Cut the img into two imgs that slightly overlap each other.
self.img1 = img.copy()
self.img1.crop(
rio.coords.BoundingBox(
right=x_midpoint + img.res[0] * 3, left=img.bounds.left, top=img.bounds.top, bottom=img.bounds.bottom
),
inplace=True,
)
self.img2 = img.copy()
self.img2.crop(
rio.coords.BoundingBox(
left=x_midpoint - img.res[0] * 3, right=img.bounds.right, top=img.bounds.top, bottom=img.bounds.bottom
),
inplace=True,
)

# Define a second raster with only 5s and the value defined above
self.img2[:5, :5] = img2_value

self.img3 = self.img1.copy()
self.img3.crop(
rio.coords.BoundingBox(
left=x_midpoint - self.img.res[0] * 3,
right=self.img.bounds.right - self.img.res[0] * 2,
top=self.img.bounds.top,
bottom=self.img.bounds.bottom,
),
inplace=True,
)


@pytest.fixture
def images_1d(): # type: ignore
return StackMergeImages("everest_landsat_b4")
return RealImageStack("everest_landsat_b4")


@pytest.fixture
def images_different_crs(): # type: ignore
return StackMergeImages("everest_landsat_b4", different_crs=4326)
return RealImageStack("everest_landsat_b4", different_crs=4326)


@pytest.fixture
def sat_images(): # type: ignore
return StackMergeImages("everest_landsat_b4", cls=gu.SatelliteImage)
return RealImageStack("everest_landsat_b4", cls=gu.SatelliteImage)


@pytest.fixture
def images_3d(): # type: ignore
return StackMergeImages("everest_landsat_rgb")
return RealImageStack("everest_landsat_rgb")


@pytest.fixture
def images_nodata_zero(): # type: ignore
return SyntheticImageStack(nodata=0, img2_value=65534)


class TestMultiRaster:
@@ -95,6 +165,7 @@ class TestMultiRaster:
pytest.lazy_fixture("sat_images"),
pytest.lazy_fixture("images_different_crs"),
pytest.lazy_fixture("images_3d"),
pytest.lazy_fixture("images_nodata_zero"),
],
) # type: ignore
def test_stack_rasters(self, rasters) -> None: # type: ignore
@@ -105,6 +176,7 @@ def test_stack_rasters(self, rasters) -> None: # type: ignore
"ignore", category=UserWarning, message="New nodata value cells already exist in the data array.*"
)
warnings.filterwarnings("ignore", category=UserWarning, message="For reprojection, nodata must be set.*")
warnings.filterwarnings("ignore", category=UserWarning, message="Unmasked values equal to*")

# Merge the two overlapping DEMs and check that output bounds and shape is correct
if rasters.img1.count > 1:
@@ -139,6 +211,7 @@ def test_stack_rasters(self, rasters) -> None: # type: ignore
)
assert merged_bounds == stacked_img.bounds

nodata_ref = rasters.img1.nodata
# Check that reference works with input Raster
stacked_img = gu.raster.stack_rasters([rasters.img1, rasters.img2], reference=rasters.img, use_ref_bounds=True)
assert rasters.img.bounds == stacked_img.bounds
@@ -170,6 +243,16 @@ def test_stack_rasters(self, rasters) -> None: # type: ignore
stacked_img = gu.raster.stack_rasters([rasters.img1, rasters.img2], resampling_method="bilinear")
assert not np.array_equal(np.unique(stacked_img.data.compressed()), np.array([1, 5]))

# Check input nodata is not modified inplace (issue 609)
new_nodata_ref = rasters.img1.nodata
assert nodata_ref == new_nodata_ref

# Check nodata value output is consistent with reference input
if nodata_ref is not None:
assert stacked_img.nodata == nodata_ref
else:
assert stacked_img.nodata == _default_nodata(rasters.img1.dtype)

@pytest.mark.parametrize(
"rasters",
[
@@ -187,6 +270,7 @@ def test_merge_rasters(self, rasters) -> None: # type: ignore
"ignore", category=UserWarning, message="New nodata value cells already exist in the data array.*"
)
warnings.filterwarnings("ignore", category=UserWarning, message="For reprojection, nodata must be set.*")
warnings.filterwarnings("ignore", category=UserWarning, message="Unmasked values equal to*")

# Ignore warning already checked in test_stack_rasters
if rasters.img1.count > 1: