Metadata-Version: 2.4
Name: geo_gremlin
Version: 0.1.2
Summary: Bunch of geo related stuff. Utilities for vector and raster geospatial processing.
Author: Illia Ovcharenko
License-Expression: MIT
Project-URL: Homepage, https://github.com/IlliaOvcharenko/geo-gremlin
Project-URL: Repository, https://github.com/IlliaOvcharenko/geo-gremlin
Project-URL: Issues, https://github.com/IlliaOvcharenko/geo-gremlin/issues
Keywords: gis,geospatial,vector,raster,wms
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: logkittt==0.1.1
Requires-Dist: fire>=0.6
Provides-Extra: vector
Requires-Dist: numpy>=1.24; extra == "vector"
Requires-Dist: pandas>=2.0; extra == "vector"
Requires-Dist: geopandas>=1.0; extra == "vector"
Requires-Dist: shapely>=2.0; extra == "vector"
Requires-Dist: rtree>=1.0; extra == "vector"
Requires-Dist: tqdm>=4.65; extra == "vector"
Requires-Dist: matplotlib>=3.7; extra == "vector"
Provides-Extra: raster
Requires-Dist: numpy>=1.24; extra == "raster"
Requires-Dist: pandas>=2.0; extra == "raster"
Requires-Dist: geopandas>=1.0; extra == "raster"
Requires-Dist: shapely>=2.0; extra == "raster"
Requires-Dist: tqdm>=4.65; extra == "raster"
Requires-Dist: rasterio>=1.3; extra == "raster"
Requires-Dist: opencv-python-headless>=4.8; extra == "raster"
Requires-Dist: aiohttp>=3.9; extra == "raster"
Requires-Dist: aiofiles>=23.2; extra == "raster"
Requires-Dist: mercantile>=1.2; extra == "raster"
Provides-Extra: gdal
Requires-Dist: GDAL; extra == "gdal"
Dynamic: license-file

<h1 align="left">
  geo-gremlin
  <img src="https://raw.githubusercontent.com/IlliaOvcharenko/geo-gremlin/main/figs/geo-gremlin-1.jpeg" alt="GeoGremlin logo" align="right" width="200" />
</h1>

Bunch of geo related stuff:

- download tiles from WMS
- makes polygons orthogonal 
- resolves geometries overlaps
- etc.

### Install

```bash
pip install "geo-gremlin[vector,raster]"
```

If you need any GDAL-based helpers (like `run_gdal_retiling`, `gdal_imread`), install GDAL in two steps:
1. Install system GDAL separately (for example via `brew` on macOS or `conda`).
2. Install Python bindings with the same version as your system GDAL to avoid ABI/version mismatch:

### CLI

#### WMS tile operations
<details>
<summary><code>geo-gremlin install-bash-completion</code></summary>

Install bash compeltion for CLI into `~/.bashrc` file.
Run it once after install.

</details>

</br>

<details>
<summary><code>geo-gremlin download_wms_tiles</code></summary>

```bash
geo-gremlin download_wms_tiles \
    WMS_SERVER \
    ZOOM_LEVEL \
    BOUNDS_FN \
    OUTPUT_DIR \
    [--n-workers=100] \
    [--first-n-tiles=N] \
    [--wld-suffix=.wld]
```

Download map tiles intersecting a GeoJSON boundary. Tiles outside the exact boundary are masked, and a world file is created for every downloaded image. Tiles are loaded in `EPSG:3857` projection (web-mercator).

```bash
geo-gremlin download_wms_tiles \
    "esri" \
    19 \
    data/test-wms/bounds.geojson \
    ./test
```

This creates the downloaded tiles in `test/esri_19/`.

Parameters:

- `WMS_SERVER`: tile provider: `"esri"`, `"google"`, `"bing"`, `"yandex"`, or `"mapbox"`.
- `ZOOM_LEVEL`: zoom level used to select tiles, `19` is usually the best you can get.
- `BOUNDS_FN`: path to a vector file defining the download boundary as Polygon feature.
- `OUTPUT_DIR`: parent directory for the generated `<server>_<zoom>` folder.
- `--n-workers`: number of concurrent download workers. Defaults to `100`.
- `--first-n-tiles`: optionally limit the number of downloaded tiles.
- `--wld-suffix`: suffix used for generated world files. Defaults to `.wld`.

</details>

</br>

<details>
<summary><code>geo-gremlin merge_wms_tiles</code></summary>

```bash
geo-gremlin merge-wms-tiles \
    INPUT_DIR \
    [--output=OUTPUT_TIF] \
    [--crs=EPSG:3857]
```

Merge georeferenced PNG or WebP tiles into a single GeoTIFF.

```bash
geo-gremlin merge-wms-tiles test/esri_19/
```

When `--output` is omitted, this example creates `test/esri_19.tif`.

Parameters:

- `INPUT_DIR`: directory containing image tiles and their world files.
- `--output`: output GeoTIFF path. By default, a `.tif` file is created alongside the input directory.
- `--crs`: coordinate reference system assigned to the output. Defaults to `EPSG:3857`.

Requires GDAL and its Python bindings.

</details>

</br>

<details>
<summary><code>geo-gremlin retile_wms_tiles</code></summary>

```bash
geo-gremlin retile-wms-tiles \
    INPUT_DIR \
    OUTPUT_DIR \
    [--tile-width=512] \
    [--tile-height=512] \
    [--tile-overlap=0] \
    [--tile-base-name=tile]
```

Retile georeferenced PNG tiles into uniformly sized tiles. Empty tiles are removed, and partial edge tiles are padded to the requested dimensions.

```bash
geo-gremlin retile-wms-tiles \
    test/esri_19/ \
    test/esri_19_retiled/
```

Parameters:

- `INPUT_DIR`: directory containing PNG tiles and their world files.
- `OUTPUT_DIR`: directory where the retiled PNG files will be created.
- `--tile-width`: output tile width in pixels. Defaults to `512`.
- `--tile-height`: output tile height in pixels. Defaults to `512`.
- `--tile-overlap`: overlap between adjacent tiles in pixels. Defaults to `0`.
- `--tile-base-name`: base name used for generated tiles. Defaults to `tile`.

Requires GDAL and its Python bindings.

</details>

### API
#### Vector operations

<details>
<summary><code>orthogonalize(...)</code></summary>

```python
orthogonalize(
    gdf: gpd.GeoDataFrame,
    *,
    n_workers: int = 6,
    verbose: bool = False,
) -> gpd.GeoDataFrame
```

Orthogonalize all polygon geometries in a GeoDataFrame. It is a batch wrapper over `orthogonalize_poly`, with optional multiprocessing.

```python
from geo_gremlin.vector.ortho import orthogonalize

ortho_gdf = orthogonalize(overlap_gdf, n_workers=1, verbose=False)
```

Parameters:
- `gdf`: `gpd.GeoDataFrame`, input GeoDataFrame with polygon geometries.
- `n_workers`: `int`, number of worker processes (`1` runs sequentially).
- `verbose`: `bool`, show progress bars when `True`.
</details>

<!-- <details> -->
<!-- <summary><code>orthogonalize_poly(...)</code></summary> -->

<!-- ```python -->
<!-- orthogonalize_poly( -->
<!--     poly: Polygon, -->
<!-- ) -> Polygon -->
<!-- ``` -->

<!-- Orthogonalize a single polygon by generating several proposals and keeping the one with better IoU to the original. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.ortho import orthogonalize_poly -->

<!-- ortho_poly = orthogonalize_poly(poly) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `poly`: `Polygon`, shapely polygon to orthogonalize. -->
<!-- </details> -->

</br>

<details>
<summary><code>resolve_overlaps(...)</code></summary>

```python
resolve_overlaps(
    gdf: gpd.GeoDataFrame,
    mode: ResolveMode = "union",
    verbose: bool = False,
    **params,
) -> gpd.GeoDataFrame
```

Resolve intersecting polygons in a GeoDataFrame by grouping overlaps and merging them using a selected mode.

```python
from geo_gremlin.vector.overlap import resolve_overlaps

overlap_gdf = resolve_overlaps(test_gdf, mode="union", verbose=False)
```

Parameters:
- `gdf`: `gpd.GeoDataFrame`, input GeoDataFrame with polygon geometries.
- `mode`: `ResolveMode`, merge strategy: `"union"`, `"intersection"`, `"largest"`, or `"iou_select"`.
- `verbose`: `bool`, show progress bars when `True`.
- `**params`: extra params for mode-specific behavior (`iou_threshold` for `"iou_select"`).
</details>

</br>

<!-- <details> -->
<!-- <summary><code>PolyVizConfig(...)</code></summary> -->

<!-- ```python -->
<!-- PolyVizConfig( -->
<!--     poly: Polygon, -->
<!--     color: str, -->
<!--     enumerate_vert: bool = True, -->
<!-- ) -->
<!-- ``` -->

<!-- Small config object for drawing one polygon with matplotlib. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import PolyVizConfig -->

<!-- pc = PolyVizConfig(poly=poly, color="red", enumerate_vert=False) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `poly`: `Polygon`, polygon to draw. -->
<!-- - `color`: `str`, matplotlib-compatible color. -->
<!-- - `enumerate_vert`: `bool`, draw vertex indices when `True`. -->
<!-- </details> -->

<!-- <details> -->
<!-- <summary><code>GdfVizConfig(...)</code></summary> -->

<!-- ```python -->
<!-- GdfVizConfig( -->
<!--     gdf: gpd.GeoDataFrame, -->
<!--     color: str, -->
<!--     title: str | None = None, -->
<!--     enumerate_vert: bool = True, -->
<!-- ) -->
<!-- ``` -->

<!-- Config object for drawing all geometries from one GeoDataFrame. Handy for `plot_gdf` and `subplot_gdf`. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import GdfVizConfig -->

<!-- cfg = GdfVizConfig(test_gdf, "blue", "input", False) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `gdf`: `gpd.GeoDataFrame`, GeoDataFrame to visualize. -->
<!-- - `color`: `str`, matplotlib-compatible color. -->
<!-- - `title`: `str | None`, optional subplot title. -->
<!-- - `enumerate_vert`: `bool`, draw vertex indices when `True`. -->
<!-- </details> -->

<!-- <details> -->
<!-- <summary><code>plot_polygons(...)</code></summary> -->

<!-- ```python -->
<!-- plot_polygons( -->
<!--     polys: list[PolyVizConfig], -->
<!--     fig_fn: str | Path, -->
<!--     *, -->
<!--     fig_size: tuple[int, int] | None = None, -->
<!-- ) -> None -->
<!-- ``` -->

<!-- Plot polygons on one figure and save it. If you need side-by-side panels, use `subplot_polygons`. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import PolyVizConfig, plot_polygons -->

<!-- plot_polygons( -->
<!--     polys=[PolyVizConfig(poly, "red", enumerate_vert=False)], -->
<!--     fig_fn="archive/figs/poly.png", -->
<!--     fig_size=(8, 8), -->
<!-- ) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `polys`: `list[PolyVizConfig]`, polygon configs to draw. -->
<!-- - `fig_fn`: `str | Path`, output image path. -->
<!-- - `fig_size`: `tuple[int, int] | None`, figure size in inches (`(10, 10)` by default). -->
<!-- </details> -->

<!-- <details> -->
<!-- <summary><code>plot_gdf(...)</code></summary> -->

<!-- ```python -->
<!-- plot_gdf( -->
<!--     gdf_config: GdfVizConfig, -->
<!--     fig_fn: str | Path, -->
<!--     *, -->
<!--     fig_size: tuple[int, int] | None = None, -->
<!-- ) -> None -->
<!-- ``` -->

<!-- Plot one GeoDataFrame config and save it. This is a convenience wrapper over `plot_polygons`. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import GdfVizConfig, plot_gdf -->

<!-- plot_gdf( -->
<!--     gdf_config=GdfVizConfig(test_gdf, "blue", "input", False), -->
<!--     fig_fn="archive/figs/input.png", -->
<!--     fig_size=(10, 10), -->
<!-- ) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `gdf_config`: `GdfVizConfig`, what to plot and how. -->
<!-- - `fig_fn`: `str | Path`, output image path. -->
<!-- - `fig_size`: `tuple[int, int] | None`, figure size in inches. -->
<!-- </details> -->

<!-- <details> -->
<!-- <summary><code>subplot_polygons(...)</code></summary> -->

<!-- ```python -->
<!-- subplot_polygons( -->
<!--     polys_per_plot: list[list[PolyVizConfig]], -->
<!--     fig_fn: str | Path, -->
<!--     titles: list[str | None], -->
<!--     *, -->
<!--     fig_size: tuple[int, int] | None = None, -->
<!--     borders: bool = True, -->
<!-- ) -> None -->
<!-- ``` -->

<!-- Plot several polygon groups as side-by-side subplots, useful for before/after comparisons. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import PolyVizConfig, subplot_polygons -->

<!-- subplot_polygons( -->
<!--     polys_per_plot=[[PolyVizConfig(poly_a, "blue")], [PolyVizConfig(poly_b, "red")]], -->
<!--     fig_fn="archive/figs/compare.png", -->
<!--     titles=["a", "b"], -->
<!--     fig_size=(16, 8), -->
<!--     borders=False, -->
<!-- ) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `polys_per_plot`: `list[list[PolyVizConfig]]`, one list of polygons per subplot. -->
<!-- - `fig_fn`: `str | Path`, output image path. -->
<!-- - `titles`: `list[str | None]`, title per subplot. -->
<!-- - `fig_size`: `tuple[int, int] | None`, figure size in inches. -->
<!-- - `borders`: `bool`, hide axes when `False`. -->
<!-- </details> -->

<!-- <details> -->
<!-- <summary><code>subplot_gdf(...)</code></summary> -->

<!-- ```python -->
<!-- subplot_gdf( -->
<!--     gdf_configs: list[GdfVizConfig], -->
<!--     fig_fn: str | Path, -->
<!--     *, -->
<!--     fig_size: tuple[int, int] | None = None, -->
<!--     borders: bool = True, -->
<!-- ) -> None -->
<!-- ``` -->

<!-- Plot several GeoDataFrames in side-by-side subplots. This wraps `subplot_polygons`. -->

<!-- ```python -->
<!-- from geo_gremlin.vector.vis import GdfVizConfig, subplot_gdf -->

<!-- subplot_gdf( -->
<!--     [ -->
<!--         GdfVizConfig(test_gdf, "blue", "input", False), -->
<!--         GdfVizConfig(overlap_gdf, "green", "overlap", False), -->
<!--         GdfVizConfig(ortho_gdf, "red", "ortho", False), -->
<!--     ], -->
<!--     "archive/figs/demo.png", -->
<!--     fig_size=(30, 15), -->
<!--     borders=False, -->
<!-- ) -->
<!-- ``` -->

<!-- Parameters: -->
<!-- - `gdf_configs`: `list[GdfVizConfig]`, configs to draw. -->
<!-- - `fig_fn`: `str | Path`, output image path. -->
<!-- - `fig_size`: `tuple[int, int] | None`, figure size in inches. -->
<!-- - `borders`: `bool`, hide axes when `False`. -->
<!-- </details> -->

<!-- <details> -->
<!--   <summary>Toggle to see example image</summary> -->

<!--   <p align="left"> -->
<!--     <img src="https://raw.githubusercontent.com/IlliaOvcharenko/geo-gremlin/main/figs/vector/demo-matplot.jpeg" alt="demo-matplot" width="auto" /> -->
<!--   </p> -->
<!-- </details> -->

<details>
<summary>Check results of vector ops?</summary>
<img src="https://raw.githubusercontent.com/IlliaOvcharenko/geo-gremlin/main/figs/vector/demo-matplot.jpeg" alt="demo-matplot" width="auto" />
</details>
</br>


Check [demo_vector.py](./examples/demo_vector.py).

<!-- #### Raster ops -->
<!-- TBD -->

### Dev
For dev dependencies check [requirements.txt](./requirements.txt).
