rastafari 0.2.3__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.
@@ -0,0 +1,28 @@
1
+ repos:
2
+ - repo: https://github.com/ambv/black
3
+ rev: "23.7.0"
4
+ hooks:
5
+ - id: black
6
+ - repo: https://github.com/pycqa/isort
7
+ rev: "5.12.0"
8
+ hooks:
9
+ - id: isort
10
+ - repo: https://github.com/pycqa/flake8
11
+ rev: "6.1.0"
12
+ hooks:
13
+ - id: flake8
14
+ - repo: https://github.com/pre-commit/pre-commit-hooks
15
+ rev: "v4.4.0"
16
+ hooks:
17
+ - id: check-added-large-files
18
+ args: ["--enforce-all"]
19
+ - id: check-merge-conflict
20
+ - id: check-symlinks
21
+ - id: check-toml
22
+ - id: debug-statements
23
+ - id: end-of-file-fixer
24
+ - id: fix-encoding-pragma
25
+ args: [--remove]
26
+ - id: mixed-line-ending
27
+ args: [--fix, lf]
28
+ - id: trailing-whitespace
@@ -0,0 +1,12 @@
1
+ # Change log
2
+
3
+ ## 0.2.0
4
+
5
+ - Add support for Python 3.11 (!1).
6
+ - Drop support for Python 3.8 (!1).
7
+ - Add type annotations (!2).
8
+
9
+
10
+ ## 0.1.0
11
+
12
+ - Initial release.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2023 David Segersson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include CHANGES.md
2
+ include .pre-commit-config.yaml
3
+ recursive-include src/rastafari *.pyx
4
+ include test-requirements.txt
5
+ recursive-include tests *.py
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.1
2
+ Name: rastafari
3
+ Version: 0.2.3
4
+ Summary: Rasterize vector features to grids
5
+ Home-page: https://gitlab.com/foclair/rastafari
6
+ Author: David Segersson, Eef van Dongen
7
+ Author-email: david.segersson@smhi.se
8
+ License: "see LICENSE file"
9
+ Project-URL: Bug Tracker, https://gitlab.com/foclair/rastafari/-/issues
10
+ Keywords: rasterize,DDA,resample
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: test
16
+ License-File: LICENSE
17
+
18
+ # Rastafari
19
+
20
+ Rasterize vector features. This library is built to rasterize emission sources in the CLAIR air quality modelling system. In contrast to most available libraries used to rasterize vector features, this library produces rasters with weights proportional to the fraction of each cell that is covered by a feature.
21
+
22
+
23
+ ### Features:
24
+
25
+ * line rasterization using DDA algorithm, producing weights proportional to fraction of line intersected by each cell
26
+ * polygon rasterization using even-odd rule, producing weights proportional to fraction of polygon included in each cell
27
+ * mass-consistent resampling of a raster using inverse nearest neighbour algorithm, allowing for different projections in source and target
28
+
29
+ See rastafari/tests for examples.
30
+
31
+ ## Install
32
+
33
+ Use pip to install Rastafari:
34
+
35
+ ```console
36
+ python -m pip install rastafari
37
+ ```
38
+
39
+
40
+ ## Development
41
+
42
+ To install Rastafari in a local venv for development, run:
43
+ ```console
44
+ git clone https://gitlab.com/foclair/rastafari.git
45
+ cd rastafari
46
+ python -m venv .venv
47
+ . .venv/bin/activate
48
+ python -m pip install -U pip
49
+ python -m pip install cython wheel
50
+ RASTAFARI_USE_CYTHON=1 python -m pip install --no-build-isolation -e .[test]
51
+ ```
52
+
53
+ Rastafari uses [pre-commit][]. Install it and run `pre-commit
54
+ install` to run its checks every time you commit. Run the checks
55
+ manually with
56
+
57
+ ```console
58
+ pre-commit run -a
59
+ ```
60
+
61
+ [pytest][] is used as a test runner:
62
+
63
+ ```console
64
+ pytest
65
+ ```
66
+
67
+ Rastafari comes fully typed. Use [mypy][] to check the type annotations:
68
+
69
+ ```console
70
+ mypy .
71
+ ```
72
+
73
+ All these tools will be run in CI in case you forget.
74
+
75
+ [mypy]: https://www.mypy-lang.org/
76
+ [pre-commit]: https://pre-commit.com/
77
+ [pytest]: https://pytest.org/
78
+ [pip-tools]: https://github.com/jazzband/pip-tools/
79
+
80
+
81
+ ## Maintenance
82
+
83
+ This package is maintained by [David Segersson][] at SMHI.
84
+
85
+ [David Segersson]: mailto:david.segersson@smhi.se
@@ -0,0 +1,68 @@
1
+ # Rastafari
2
+
3
+ Rasterize vector features. This library is built to rasterize emission sources in the CLAIR air quality modelling system. In contrast to most available libraries used to rasterize vector features, this library produces rasters with weights proportional to the fraction of each cell that is covered by a feature.
4
+
5
+
6
+ ### Features:
7
+
8
+ * line rasterization using DDA algorithm, producing weights proportional to fraction of line intersected by each cell
9
+ * polygon rasterization using even-odd rule, producing weights proportional to fraction of polygon included in each cell
10
+ * mass-consistent resampling of a raster using inverse nearest neighbour algorithm, allowing for different projections in source and target
11
+
12
+ See rastafari/tests for examples.
13
+
14
+ ## Install
15
+
16
+ Use pip to install Rastafari:
17
+
18
+ ```console
19
+ python -m pip install rastafari
20
+ ```
21
+
22
+
23
+ ## Development
24
+
25
+ To install Rastafari in a local venv for development, run:
26
+ ```console
27
+ git clone https://gitlab.com/foclair/rastafari.git
28
+ cd rastafari
29
+ python -m venv .venv
30
+ . .venv/bin/activate
31
+ python -m pip install -U pip
32
+ python -m pip install cython wheel
33
+ RASTAFARI_USE_CYTHON=1 python -m pip install --no-build-isolation -e .[test]
34
+ ```
35
+
36
+ Rastafari uses [pre-commit][]. Install it and run `pre-commit
37
+ install` to run its checks every time you commit. Run the checks
38
+ manually with
39
+
40
+ ```console
41
+ pre-commit run -a
42
+ ```
43
+
44
+ [pytest][] is used as a test runner:
45
+
46
+ ```console
47
+ pytest
48
+ ```
49
+
50
+ Rastafari comes fully typed. Use [mypy][] to check the type annotations:
51
+
52
+ ```console
53
+ mypy .
54
+ ```
55
+
56
+ All these tools will be run in CI in case you forget.
57
+
58
+ [mypy]: https://www.mypy-lang.org/
59
+ [pre-commit]: https://pre-commit.com/
60
+ [pytest]: https://pytest.org/
61
+ [pip-tools]: https://github.com/jazzband/pip-tools/
62
+
63
+
64
+ ## Maintenance
65
+
66
+ This package is maintained by [David Segersson][] at SMHI.
67
+
68
+ [David Segersson]: mailto:david.segersson@smhi.se
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "Cython", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.check-manifest]
6
+ ignore = [
7
+ ".gitlab-ci.yml",
8
+ "src/rastafari/*.c",
9
+ ]
10
+
11
+ [tool.coverage.run]
12
+ source = ["rastafari"]
13
+ branch = true
14
+
15
+ [tool.mypy]
16
+ strict = true
17
+ exclude = ['^setup\.py$']
18
+
19
+ [tool.pytest.ini_options]
20
+ filterwarnings = ["error"]
@@ -0,0 +1,57 @@
1
+ [metadata]
2
+ name = rastafari
3
+ version = attr: rastafari.__version__
4
+ author = David Segersson, Eef van Dongen
5
+ author_email = david.segersson@smhi.se
6
+ description = Rasterize vector features to grids
7
+ long_description = file: README.md
8
+ long_description_content_type = text/markdown
9
+ keywords = rasterize, DDA, resample
10
+ classifiers =
11
+ License :: OSI Approved :: MIT License
12
+ Programming Language :: Python :: 3
13
+ license = "see LICENSE file"
14
+ url = https://gitlab.com/foclair/rastafari
15
+ project_urls =
16
+ Bug Tracker = https://gitlab.com/foclair/rastafari/-/issues
17
+
18
+ [options]
19
+ package_dir =
20
+ = src
21
+ packages = find:
22
+ python_requires = >=3.9
23
+ zip_safe = False
24
+ install_requires =
25
+ numpy
26
+ pyproj
27
+
28
+ [options.extras_require]
29
+ test =
30
+ mypy
31
+ pytest
32
+ pytest-cov
33
+
34
+ [options.packages.find]
35
+ where = src
36
+
37
+ [options.package_data]
38
+ rastafari =
39
+ core.pyi
40
+ py.typed
41
+
42
+ [flake8]
43
+ exclude =
44
+ .direnv,
45
+ .venv
46
+ extend-ignore = E203
47
+ max-line-length = 88
48
+
49
+ [black]
50
+ exclude =
51
+ .direnv,
52
+ .venv
53
+
54
+ [egg_info]
55
+ tag_build =
56
+ tag_date = 0
57
+
@@ -0,0 +1,17 @@
1
+ """Build and install Rastafari."""
2
+
3
+ import os
4
+
5
+ from setuptools import Extension, setup
6
+ USE_CYTHON = os.environ.get("RASTAFARI_USE_CYTHON", False)
7
+ print(f"USE_CYTHON: {USE_CYTHON}")
8
+ ext = "pyx" if USE_CYTHON else "c"
9
+ extensions = [
10
+ Extension("rastafari.core", [f"src/rastafari/core.{ext}"]),
11
+ ]
12
+ if USE_CYTHON:
13
+ from Cython.Build import cythonize
14
+
15
+ extensions = cythonize(extensions, compiler_directives={"language_level": "3"})
16
+
17
+ setup(ext_modules=extensions)
@@ -0,0 +1,137 @@
1
+ """Rasterize vector features to grids."""
2
+
3
+ import math
4
+
5
+ import numpy as np
6
+ import numpy.typing as npt
7
+ import pyproj
8
+
9
+ from .core import ddaf_line_subpixel, even_odd_polygon_fill
10
+
11
+ __all__ = [
12
+ "ddaf_line_subpixel",
13
+ "even_odd_polygon_fill",
14
+ "resample_band",
15
+ ]
16
+
17
+ __version__ = "0.2.3"
18
+
19
+ ExtentType = tuple[float, float, float, float]
20
+ WeightsDict = dict[tuple[int, int], float]
21
+
22
+
23
+ def resample_band(
24
+ source_grid: npt.NDArray[np.float_],
25
+ source_extent: ExtentType,
26
+ source_nodata: float,
27
+ target_extent: ExtentType,
28
+ target_nx: int,
29
+ target_ny: int,
30
+ source_srid: int,
31
+ target_srid: int,
32
+ subgridcells: int = 2,
33
+ ) -> tuple[tuple[npt.NDArray[np.intp], ...], npt.NDArray[np.float_]]:
34
+ """Resample raster using inverse nearest neighbour algorithm.
35
+
36
+ The original raster is refined and each pixel is sorted into the
37
+ target grid.
38
+
39
+ args:
40
+ source_grid: original raster as a numpy array ((X, Y))
41
+ source_extent: extent of the original raster
42
+ source_nodata: nodata value of source raster
43
+ target_extent: target extent coordinates as (x1, y1, x2, y2)
44
+ target_nx: number of cells in x-direction of target grid
45
+ target_ny: number of cells in y-direction of target grid
46
+ source_srid: srid of source raster as integer
47
+ target_srid: srid of target raster as integer
48
+ kwargs:
49
+ subgridcells: divide each cell side by this factor to
50
+ increase accuracy in resampling
51
+ """
52
+ # target extent
53
+ tx0, ty0, tx1, ty1 = target_extent
54
+
55
+ # source extent
56
+ sx0, sy0, sx1, sy1 = source_extent
57
+
58
+ sny = source_grid.shape[0]
59
+ snx = source_grid.shape[1]
60
+
61
+ # calculate grid cell-size
62
+ target_dy = (ty1 - ty0) / target_ny
63
+ target_dx = (tx1 - tx0) / target_nx
64
+
65
+ source_dy = (sy1 - sy0) / sny
66
+ source_dx = (sx1 - sx0) / snx
67
+
68
+ # refinement factor < 0 means sorting into a coarser target grid
69
+ refinement_factor = max(
70
+ source_dx * subgridcells / target_dx, source_dy * subgridcells / target_dy
71
+ )
72
+
73
+ target_grid = np.zeros((target_ny, target_nx), dtype=float)
74
+
75
+ # set nodata values to zero (if added by ST_Clip in emission query)
76
+ source_grid[source_grid == source_nodata] = 0
77
+ # upsample the source grid for improved accuracy
78
+ if refinement_factor > 1 and subgridcells > 1:
79
+ refinement_factor = math.ceil(refinement_factor)
80
+ # values are divided to maintain raster sum after upsampling
81
+ source_grid = (
82
+ (source_grid / (refinement_factor * refinement_factor))
83
+ .repeat(refinement_factor, axis=0)
84
+ .repeat(refinement_factor, axis=1)
85
+ )
86
+
87
+ # adjust cell size to upsampled grid
88
+ source_dx /= refinement_factor
89
+ source_dy /= refinement_factor
90
+
91
+ # get row and col indices of nonzero cells
92
+ source_rows, source_cols = np.nonzero(source_grid)
93
+
94
+ # get cell centre coordinates of non-zero cells
95
+ ccx = sx0 + (source_cols + 0.5) * source_dx
96
+ ccy = sy1 - (source_rows + 0.5) * source_dy
97
+ cell_centers = np.vstack((ccx, ccy)).T
98
+ if cell_centers.size == 0:
99
+ # all cells are zero in source grid
100
+ return ((), np.array([]))
101
+
102
+ # if target srid is different from source srid,
103
+ # transform cell centers to target srid
104
+ if source_srid != target_srid:
105
+ transformer = pyproj.Transformer.from_crs(
106
+ source_srid, target_srid, always_xy=True
107
+ )
108
+ transformer.transform(cell_centers[:, 0], cell_centers[:, 1], inplace=True)
109
+
110
+ # get indices within output bounds
111
+ target_cols = ((cell_centers[:, 0] - tx0) / target_dx).astype(int)
112
+ target_rows = target_ny - (np.ceil((cell_centers[:, 1] - ty0) / target_dy)).astype(
113
+ int
114
+ )
115
+ # handle edge case (row or col = ny or ny)
116
+ target_cols = np.clip(target_cols, 0, target_nx - 1, target_cols)
117
+ target_rows = np.clip(target_rows, 0, target_ny - 1, target_rows)
118
+
119
+ # create mask array for cells within target extent
120
+ cells_within_target_extent = (
121
+ (cell_centers[:, 0] >= tx0)
122
+ & (cell_centers[:, 0] < tx1)
123
+ & (cell_centers[:, 1] >= ty0)
124
+ & (cell_centers[:, 1] < ty1)
125
+ )
126
+ np.add.at(
127
+ target_grid,
128
+ (
129
+ target_rows[cells_within_target_extent],
130
+ target_cols[cells_within_target_extent],
131
+ ),
132
+ source_grid[
133
+ source_rows[cells_within_target_extent],
134
+ source_cols[cells_within_target_extent],
135
+ ],
136
+ )
137
+ return (target_grid.nonzero(), target_grid[target_grid > 0].ravel())