Skip to content

Commit 9ea9c2e

Browse files
authored
Merge pull request #1 from mblackgeo/develop
Implementation of package v0.1.0
2 parents 6031034 + 4b5784c commit 9ea9c2e

22 files changed

+2074
-2
lines changed

.flake8

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
max-line-length = 88
3+
exclude = tests/*
4+
max-complexity = 10

.github/workflows/pipeline.yml

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: lint and test
2+
3+
on: [push]
4+
5+
jobs:
6+
lint_and_test:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v2
11+
- name: Set up Python 3.8
12+
uses: actions/setup-python@v2
13+
with:
14+
python-version: 3.8
15+
16+
- name: Install poetry
17+
run: pip install poetry==1.1.12
18+
19+
- name: Install code
20+
run: poetry install
21+
22+
- name: Lint with flake8
23+
run: |
24+
# stop the build if there are Python syntax errors or undefined names
25+
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
26+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
27+
poetry run flake8 . --count --exit-zero --statistics
28+
29+
- name: Check formatting with Black
30+
run: poetry run black . --check
31+
32+
- name: Check import order with isort
33+
run: poetry run isort -c spatial_kde
34+
35+
- name: Test with pytest
36+
run: poetry run pytest -v --cov spatial_kde

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,7 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
.vscode/
132+
133+
tests/data/**/*.gpkg-*

.pre-commit-config.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v2.3.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- repo: https://github.com/psf/black
7+
rev: 21.9b0
8+
hooks:
9+
- id: black
10+
- repo: https://gitlab.com/pycqa/flake8
11+
rev: 3.7.9
12+
hooks:
13+
- id: flake8
14+
- repo: https://github.com/pycqa/isort
15+
rev: 5.8.0
16+
hooks:
17+
- id: isort
18+
name: isort (python)

Makefile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
help:
2+
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'
3+
4+
install: ## Create a new environment with poetry and install with pre-commit hooks
5+
poetry install
6+
pre-commit install
7+
8+
test: ## Run the test suite using pytest
9+
poetry run pytest --cov spatial_kde
10+
11+
lint: ## Run linting checks with flake8 and black
12+
poetry run flake8 spatial_kde/
13+
poetry run black --check spatial_kde/
14+
15+
format: ## Run black to format the code
16+
poetry run black .
17+
18+
test-release: ## Build the package and releast to test-PyPI
19+
poetry config repositories.testpypi https://test.pypi.org/legacy/
20+
poetry publish --build -r testpypi
21+
22+
release: ## Build the package and release to PyPI
23+
poetry publish

README.md

+119-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,119 @@
1-
# spatial-kde
2-
Spatial Kernel Density / Heatmap creation from point based vector data
1+
# Spatial Kernel Density Esimation
2+
Create Spatial Kernel Density / Heatmap raster from point based vector data, à la QGIS / ArcGIS.
3+
4+
![Example showing KDE generated from weighted points](example.png)
5+
6+
Creates a kernel density (heatmap) raster from vector point data using kernel density estimation. The density is calculated based on the number of points in a location, with larger numbers of clustered points resulting in larger values, and points can be optionally weighted. Kernel Density / Heatmaps allow easy for identification of hotspots and clustering of points. This implementation provides an equivalent to [QGIS' Heatmap](https://docs.qgis.org/3.16/en/docs/user_manual/processing_algs/qgis/interpolation.html#heatmap-kernel-density-estimation) and [ArcGIS/ArcMap/ArcPro's Kernel Density spatial analyst](https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/kernel-density.htm) function. Note that any distance calculations are planar, therefore care should be taken when using points over large areas that are in a geographic coordinate system.
7+
8+
The implementation of kernel density uses the Quartic kernel for it's estimates, with the methodology implemented [following QGIS](https://github.com/qgis/QGIS/blob/master/src/analysis/raster/qgskde.cpp) and as described in ArcGIS' documentation explaining [how Kernel Density works](https://pro.arcgis.com/en/pro-app/latest/tool-reference/spatial-analyst/how-kernel-density-works.htm). There are many alternative kernel density estimate (KDE) functions available in popular python libraries that may offer better performance, for example [scipy](https://docs.scipy.org/doc/scipy/reference/stats.html#univariate-and-multivariate-kernel-density-estimation), [scikit-learn](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KernelDensity.html), [KDEpy](https://kdepy.readthedocs.io/en/latest/index.html) etc., though these alternatives may not implement the Quartic kernel (with optional weights), as is typically found in GIS software. Additionally, performance with this package will be reduced compared to the native C++ implementaion in QGIS.
9+
10+
## Installation
11+
12+
The package can be installed from pip:
13+
14+
```shell
15+
pip install spatial-kde
16+
```
17+
18+
## Usage
19+
20+
After installation, the `skde` tool is available from the command line with the following usage:
21+
22+
```shell
23+
Usage: skde [OPTIONS] VECTOR OUTPUT
24+
25+
Create a Spatial Kernel Density / Heatmap raster from an input vector.
26+
27+
The input vector file must be readable by GeoPandas and contain Point type
28+
geometry (for non-point geometries the centroid will be used for the KDE).
29+
30+
Arguments:
31+
VECTOR Path to input vector file [required]
32+
OUTPUT Output path for created raster [required]
33+
34+
Options:
35+
--radius FLOAT Radius/Bandwith for the KDE. Same units as
36+
the CRS of `vector`. [default: 1]
37+
--output-pixel-size FLOAT Output pixel size (resolution). Same units
38+
as the CRS of `vector`. [default: 1]
39+
--output-driver TEXT Output driver (file format) used by rasterio
40+
(Default = GeoTiff). [default: GTiff]
41+
--weight-field TEXT Optional field in `vector` containing
42+
weights of each point.
43+
--scaled / --no-scaled Set to True to scale the KDE values, leave
44+
false to use raw values. [default: no-
45+
scaled]
46+
```
47+
48+
Alternatively, the [`spatial_kernel_density`](spatial_kde/kde.py) function can be used in python:
49+
50+
```python
51+
from typing import Optional
52+
53+
import geopandas as gpd
54+
from spatial_kde import spatial_kernel_density
55+
56+
57+
spatial_kernel_density(
58+
points: gpd.GeoDataFrame = gdf,
59+
radius: float = 1.0,
60+
output_path: str = "/output/path.tif",
61+
output_pixel_size: float = 1.0,
62+
output_driver: str = "GTiff",
63+
weight_col: Optional[str] = None,
64+
scaled: bool = False,
65+
)
66+
67+
"""Calculate Kernel Density / heatmap from ``points``
68+
69+
.. note:: Distance calculations are planar so care should be taken with data
70+
that is in geographic coordinate systems
71+
72+
Parameters
73+
----------
74+
points : gpd.GeoDataFrame
75+
Input GeoDataFrame of points to generate a KDE from
76+
radius : float
77+
Radius of KDE, same units as the coordinate reference system of ``points``
78+
Sometimes referred to as search radius or bandwidth
79+
output_path : str
80+
Path to write output raster to
81+
output_pixel_size : float
82+
Output cell/pixel size of the created array. Same units as the coordinate
83+
reference system of ``points``
84+
output_driver : str
85+
Output format (driver) used to create image. See also
86+
https://rasterio.readthedocs.io/en/latest/api/rasterio.drivers.html
87+
weight_col : Optional[str], optional
88+
A column in ``points`` to weight the kernel density by, any points that
89+
are NaN in this field will not contribute to the KDE.
90+
If None, the all points will have uniform weight of 1.
91+
scaled : bool
92+
If True will output mathematically scaled values, else will output raw
93+
values.
94+
"""
95+
```
96+
97+
## Development
98+
99+
Prequisites:
100+
101+
* [poetry](https://python-poetry.org/)
102+
* [pre-commit](https://pre-commit.com/)
103+
104+
The Makefile includes helpful commands setting a development environment, get started by installing the package into a new environment and setting up pre-commit by running `make install`. Run `make help` to see additional available commands (e.g. linting, testing and so on).
105+
106+
* [Pytest](https://docs.pytest.org/en/6.2.x/) is used for testing the application (see `/tests`).
107+
* Code is linted using [flake8](https://flake8.pycqa.org/en/latest/)
108+
* Code formatting is validated using [Black](https://github.com/psf/black)
109+
* [pre-commit](https://pre-commit.com/) is used to run these checks locally before files are pushed to git
110+
* The [Github Actions pipeline](.github/workflows/pipeline.yml) runs these checks and tests
111+
112+
## TODO
113+
114+
- [ ] Github actions pipeline runs on a matrix of python versions
115+
- [ ] Documentation (e.g. mkdocs, read-the-docs w/ sphinx or similar)
116+
- [ ] Tooling for managing versioning/releasing (e.g. bump2version)
117+
- [x] Makefile commands for releasing to (test) pypi
118+
- [ ] Support geodesic distance calculation
119+
- [ ] Performance improvements

example.png

25.9 KB
Loading

0 commit comments

Comments
 (0)