Skip to content

Commit 9f0c29f

Browse files
authored
Add slope algorithm (#1088)
* Add slope algorithm * Add test for slope algorithm
1 parent ea71607 commit 9f0c29f

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

src/titiler/core/tests/test_algorithms.py

+23
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,29 @@ def test_hillshade():
168168
assert out.array[0, 0, 0] is numpy.ma.masked
169169

170170

171+
def test_slope():
172+
"""test slope."""
173+
algo = default_algorithms.get("slope")()
174+
175+
arr = numpy.random.randint(0, 5000, (1, 262, 262), dtype="uint16")
176+
img = ImageData(arr)
177+
out = algo(img)
178+
assert out.array.shape == (1, 256, 256)
179+
assert out.array.dtype == "float32"
180+
181+
arr = numpy.ma.MaskedArray(
182+
numpy.random.randint(0, 5000, (1, 262, 262), dtype="uint16"),
183+
mask=numpy.zeros((1, 262, 262), dtype="bool"),
184+
)
185+
arr.mask[0, 0:100, 0:100] = True
186+
187+
img = ImageData(arr)
188+
out = algo(img)
189+
assert out.array.shape == (1, 256, 256)
190+
assert out.array.dtype == "float32"
191+
assert out.array[0, 0, 0] is numpy.ma.masked
192+
193+
171194
def test_contours():
172195
"""test contours."""
173196
algo = default_algorithms.get("contours")()

src/titiler/core/titiler/core/algorithm/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111

1212
from titiler.core.algorithm.base import AlgorithmMetadata # noqa
1313
from titiler.core.algorithm.base import BaseAlgorithm
14-
from titiler.core.algorithm.dem import Contours, HillShade, TerrainRGB, Terrarium
14+
from titiler.core.algorithm.dem import Contours, HillShade, Slope, TerrainRGB, Terrarium
1515
from titiler.core.algorithm.index import NormalizedIndex
1616
from titiler.core.algorithm.ops import CastToInt, Ceil, Floor
1717

1818
default_algorithms: Dict[str, Type[BaseAlgorithm]] = {
1919
"hillshade": HillShade,
20+
"slope": Slope,
2021
"contours": Contours,
2122
"normalizedIndex": NormalizedIndex,
2223
"terrarium": Terrarium,

src/titiler/core/titiler/core/algorithm/dem.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from titiler.core.algorithm.base import BaseAlgorithm
1111

12-
__all__ = ["HillShade", "Contours", "Terrarium", "TerrainRGB"]
12+
__all__ = ["HillShade", "Slope", "Contours", "Terrarium", "TerrainRGB"]
1313

1414

1515
class HillShade(BaseAlgorithm):
@@ -63,6 +63,53 @@ def __call__(self, img: ImageData) -> ImageData:
6363
)
6464

6565

66+
class Slope(BaseAlgorithm):
67+
"""Slope calculation."""
68+
69+
title: str = "Slope"
70+
description: str = "Calculate degrees of slope from DEM dataset."
71+
72+
# parameters
73+
buffer: int = Field(3, ge=0, le=99, description="Buffer size for edge effects")
74+
75+
# metadata
76+
input_nbands: int = 1
77+
output_nbands: int = 1
78+
output_dtype: str = "float32"
79+
80+
def __call__(self, img: ImageData) -> ImageData:
81+
"""Calculate degrees slope from DEM dataset."""
82+
# Get the pixel size from the transform
83+
pixel_size_x = abs(img.transform[0])
84+
pixel_size_y = abs(img.transform[4])
85+
86+
x, y = numpy.gradient(img.array[0])
87+
dx = x / pixel_size_x
88+
dy = y / pixel_size_y
89+
90+
slope = numpy.arctan(numpy.sqrt(dx * dx + dy * dy)) * (180 / numpy.pi)
91+
92+
bounds = img.bounds
93+
if self.buffer:
94+
slope = slope[self.buffer : -self.buffer, self.buffer : -self.buffer]
95+
96+
window = windows.Window(
97+
col_off=self.buffer,
98+
row_off=self.buffer,
99+
width=slope.shape[1],
100+
height=slope.shape[0],
101+
)
102+
bounds = windows.bounds(window, img.transform)
103+
104+
return ImageData(
105+
slope.astype(self.output_dtype),
106+
assets=img.assets,
107+
crs=img.crs,
108+
bounds=bounds,
109+
band_names=["slope"],
110+
)
111+
112+
66113
class Contours(BaseAlgorithm):
67114
"""Contours.
68115

0 commit comments

Comments
 (0)