Metadata-Version: 2.4
Name: gemf_map
Version: 1.0.4
Summary: Python library for reading and writing GEMF map tile files.
Project-URL: Homepage, https://github.com/ColinMoldenhauer/GEMF
Project-URL: Documentation, https://gemf.readthedocs.io
Project-URL: Repository, https://github.com/ColinMoldenhauer/GEMF
Project-URL: Bug Tracker, https://github.com/ColinMoldenhauer/GEMF/issues
Author-email: Colin Moldenhauer <colin.moldenhauer@posteo.de>
License-Expression: GPL-3.0-only
License-File: LICENSE
Keywords: gemf,locus,map,mobile map,tiles
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.9
Requires-Dist: mercantile
Requires-Dist: numpy
Requires-Dist: pillow
Provides-Extra: all
Requires-Dist: folium; extra == 'all'
Requires-Dist: matplotlib; extra == 'all'
Requires-Dist: mkdocs-material; extra == 'all'
Requires-Dist: mkdocstrings[python]; extra == 'all'
Requires-Dist: pytest-cov; extra == 'all'
Requires-Dist: pytest>=8.0; extra == 'all'
Requires-Dist: shapely; extra == 'all'
Provides-Extra: docs
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Provides-Extra: geometry
Requires-Dist: shapely; extra == 'geometry'
Provides-Extra: plot
Requires-Dist: folium; extra == 'plot'
Requires-Dist: matplotlib; extra == 'plot'
Provides-Extra: test
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# GEMF

Python package for the GEMF map format. From the format [specification](https://www.cgtk.co.uk/gemf):

> This tile store format is intended to provide a static (i.e. cannot be updated without regenerating from scratch) file containing a large number of tiles, stored
> in a manner that makes efficient use of SD cards and with which it is easy to access individual tiles very quickly. It is intended to overcome the existing issues
> with the way tiles are stored in most existing Android map applications as these are not very scalable.

`.gemf` files may be used in mobile mapping applications like [Locus](https://www.locusmap.app/)

# Installation
```cmd
pip install gemf-map
```


# Features
**Core features** are...
- reading `.gemf` map files via the `GEMF.from_file()` classmethod
- creating a GEMF object from PNG or JPG tiles via the `GEMF.from_tiles()` classmethod
- writing a newly created GEMF object to file via the `write()` method

**Further features** are...
- spatially subset (crop) a .gemf file
- extracting image tiles from binary `.gemf` files via the `save_tiles()` method
- adding tiles to an existing `.gemf` file (TODO)
- combining `GEMF` objects via `stack()`/`+`, `merge()`/`|`, `intersect()`/`&` and `difference()`/`-`

**Visualization features**:
- show the tiles of a .gemf file on a `folium` map
- show tile boundaries on a map

# Usage

## Core
```python
from gemf import GEMF

# load an existing .gemf file
my_gemf = GEMF.from_file("MY_GEMF.gemf")

# extract the tiles
my_gemf.save_tiles("PATH/TO/TILEDIR")

# create a GEMF object from tiles on disk
new_gemf = GEMF.from_tiles("PATH/TO/TILEDIR")

# write GEMF object to .gemf file
new_gemf.write("PATH/TO/GEMF_FILE.gemf")
```

## Lazy loading
`GEMF.from_file()` and `GEMF.from_tiles()` accept a `lazy` argument (default `False`).

With `lazy=False` (the default), the per-tile range details (location and size of every
tile's data) are read/computed immediately. This gives access to the full range of
features, but may take a while for files with a large number of tiles.

With `lazy=True`, this step is skipped. The following are available right away without
extra loading:
- `gemf.num_sources`
- `gemf.num_tiles`
- `gemf.get_zoom_levels()`
- inspecting `gemf.header.range_data` (the per-zoom-level bounding boxes)

Any operation that needs per-tile data (e.g. `save_tiles()`, `write()`, `crop()`,
`set_tile()`, the arithmetic operators) raises `DetailsNotLoadedError` on a lazily loaded
`GEMF`. Call `gemf.load_details()` first to load the range details on demand:

```python
my_gemf = GEMF.from_file("MY_GEMF.gemf", lazy=True)

# cheap, available immediately
print(my_gemf.num_tiles)

# load range details before performing per-tile operations
my_gemf.load_details()
my_gemf.save_tiles("PATH/TO/TILEDIR")
```

## Manipulation
```python
# crop to bounding box (all zoom levels will be cropped accordingly)
gemf_crop = my_gemf.crop(7, 60, 62, 36, 38)
```

## Combining GEMF objects
```python
# stack: add all sources of gemf2 onto gemf1 as new sources
combined = gemf1 + gemf2

# merge: union of tiles for sources present in both (matched by name)
# on conflicting tiles, "|" keeps gemf1's tile by default
merged = gemf1 | gemf2

# intersect: keep only tiles present in both, for shared sources
shared = gemf1 & gemf2

# difference: keep gemf1's tiles that are not present in gemf2's matching source
diff = gemf1 - gemf2
```


## Visualization
<!-- TODO: limitations for large gemf files -->
```python
from gemf.plot import GEMFPlot

# visualize tiles and tile boundaries
gemf_map = GEMFPlot(my_gemf)

# visualize tiles without boundaries
tile_map = GEMFPlot(my_gemf, show_tile_boundaries=False)

# visualize only boundaries (to avoid loading tile contents from disk)
boundary_map = GEMFPlot(my_gemf, show_tiles=False)
```

## Other
```python
# save the embedded tiles to image files
gemf_map.save_tiles("path/to/output/dir")
```