-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 66fd938
Showing
75 changed files
with
19,706 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[flake8] | ||
max-line-length = 88 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.vscode | ||
*.pyc | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2024 OCELL | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
# Mapy Project | ||
|
||
## Overview | ||
Mapy is a Python library designed easily render static maps in python. It is designed to be simple to use and easy to integrate with existing codebases. The library supports rendering background, tiled raster, filled polygon, and other layers on the map. It directly supports geometric primitives, allowing users to directly render shapely geometries. | ||
|
||
Input data must be in the `EPSG:4326` - WGS84 projection. | ||
|
||
### Supported Layer Types | ||
|
||
| type | status | description | data source| | ||
| ---- | ------ | ----------- | -------- | | ||
| `BackgroundLayer` | ✅ | renders a simple background with a single color | Color | | ||
| `TiledRasterLayer` | ✅ | renders a tiled raster layer can can load xyz tiles | xyz via http(s) | | ||
| `FillLayer` | ✅ | renders a fill layer for polygons| Polygon and MultiPolygon | | ||
| `LineLayer` | ✅ | renders a line layer that draw LineStrings | LineString and MultiLineString| | ||
| `CircleLayer` | ✅ | renders a circle layer for Points | Point and MultiPoint | | ||
| `SymbolLayer` | ✅ | renders a symbol and/or text for points| Point and MultiPoint | | ||
| `Attribution` | ✅ | an attribution | str and list[str] | | ||
|
||
|
||
## Installation | ||
To install the Mapy library, clone the repository and install the required dependencies: | ||
|
||
```bash | ||
git clone <repository-url> | ||
cd mapy | ||
poetry install | ||
# or | ||
pip install . | ||
``` | ||
|
||
This library uses Cairo. You have to install cairo with your package manger of choice. | ||
|
||
on mac | ||
|
||
```bash | ||
brew install cairo | ||
``` | ||
|
||
## Usage | ||
|
||
The Mapy library is designed to be simple to use. The following sections provide examples of how to create a map with different layers. Note that in almost all cases you would have to add an attribution layer to the map. For example, if you use OpenStreetMap tiles, you would have to add the OpenStreetMap attribution to the map. This is not done automatically! | ||
|
||
|
||
### Creating a simple Map | ||
|
||
Here is an example of how to create a simple map with a filled polygon: | ||
|
||
```python | ||
import mapy | ||
my_map = mapy.Map() | ||
tile_layer = mapy.TiledRasterLayer( | ||
[ | ||
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", | ||
] | ||
) | ||
my_map.add_layer(tile_layer) | ||
my_map.add_layer(Attribution("© OpenStreetMap contributors")) | ||
|
||
surf = my_map.render( | ||
mapy.FixedScreenSize( | ||
Box.from_lng_lat(5.988, 47.302, 15.016, 54.983), mapy.ScreenSize(512, 512) | ||
) | ||
) | ||
surf.write_to_png("my_map.png") | ||
|
||
``` | ||
|
||
If you want to use the map on a public place be sure to include proper attribution. This code will generate a map with a filled polygon and save it as `simple_map.png`. | ||
|
||
|
||
|
||
#### Background Layer | ||
A background layer provides a solid color background for the map. | ||
|
||
```python | ||
background_layer = mapy.BackgroundLayer(mapy.Color(1, 1, 1)) | ||
my_map.add_layer(background_layer) | ||
``` | ||
|
||
#### Tiled Raster Layer | ||
A tiled raster layer allows the use of map tiles from sources like OpenStreetMap. | ||
|
||
```python | ||
tile_layer = mapy.TiledRasterLayer( | ||
[ | ||
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", | ||
] | ||
) | ||
my_map.add_layer(tile_layer) | ||
``` | ||
|
||
#### Fill Layer | ||
A fill layer can be used to add filled polygons with customizable colors and borders. | ||
|
||
```python | ||
from shapely.geometry import shape | ||
|
||
polygon = shape(json) | ||
fill_layer = mapy.FillLayer( | ||
[ | ||
mapy.FillItem( | ||
polygon, | ||
fill_color=mapy.Color(0.5, 0.5, 0.5, 0.3), | ||
line_color=mapy.Color(0, 0, 0), | ||
line_width=2, | ||
) | ||
] | ||
) | ||
my_map.add_layer(fill_layer) | ||
``` | ||
|
||
#### Line Layer | ||
A line layer can be used to show LineStrings on the map | ||
|
||
```python | ||
from shapely.geometry import shape | ||
|
||
line = shape(json) | ||
fill_layer = mapy.LineLayer( | ||
[ | ||
mapy.LineItem( | ||
line, | ||
join=mapy.LineJoin.round, | ||
cap=mapy.LineCap.round | ||
width=12, | ||
outline_width=3, | ||
outline_color=Colors.BLACK, | ||
) | ||
] | ||
) | ||
my_map.add_layer(fill_layer) | ||
``` | ||
|
||
The `LineItem` options `cap` and `join` lead to the following results: | ||
|
||
 | ||
|
||
|
||
#### Circle Layer | ||
A circle layer can be used to show Points on the map | ||
|
||
```python | ||
from shapely.geometry import shape | ||
|
||
point = shape(json) | ||
circle_layer = mapy.CircleLayer( | ||
[ | ||
mapy.CircleItem( | ||
point, | ||
fill_color=mapy.Color(0.5, 0.5, 0.5, 0.3), | ||
line_color=mapy.Color(0, 0, 0), | ||
line_width=2, | ||
radius=10, | ||
) | ||
] | ||
) | ||
my_map.add_layer(circle_layer) | ||
``` | ||
|
||
#### Symbol Layer | ||
A symbol layer can be used to show Points on the map. You can load custom icons by using the `mapy.Icon.from_path` class method. | ||
|
||
##### Limitations | ||
- The text is not automatically placed relative to the symbol. You have to calculate the position yourself. | ||
- No collision detection is implemented. If you place multiple symbols with text on top of each other, the text and symbols will overlap. This might get added in the future, but is somewhat complicated to implement. | ||
|
||
|
||
|
||
|
||
```python | ||
from shapely.geometry import shape | ||
|
||
point = shape(json) | ||
symbol_layer = mapy.SymbolLayer( | ||
[ | ||
mapy.SymbolItem( | ||
point, | ||
icon=mapy.Icons.PIN_24, | ||
text="Hello World", | ||
text_offset=(0, 16) | ||
) | ||
] | ||
) | ||
my_map.add_layer(symbol_layer) | ||
``` | ||
|
||
You can set the anchor of the text with the `text_anchor` parameter. The default is `mapy.TextAnchor.BOTTOM_LEFT`. The following options are available: | ||
|
||
| | | | | ||
| - | - | - | | ||
|  |  |  | | ||
|  |  |  | | ||
|  |  |  | | ||
|
||
|
||
|
||
#### Attribution | ||
An attribution layer can be used to add attribution to the map. This is important if you use tiles from a public source like OpenStreetMap. | ||
|
||
```python | ||
attribution = mapy.Attribution("© OpenStreetMap contributors") | ||
my_map.add_layer(attribution) | ||
``` | ||
|
||
|
||
|
||
## Testing | ||
|
||
The project includes unit tests to ensure the functionality of various components. To run the tests, use the following command: | ||
|
||
```bash | ||
pytest | ||
``` | ||
|
||
## Output Example | ||
|
||
The image below is an example of a map created using the Mapy library: | ||
|
||
 | ||
|
||
## License | ||
This project is licensed under the MIT License. | ||
|
||
## Contributing | ||
Contributions are welcome! Please submit a pull request or open an issue for any changes or suggestions. | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import json | ||
from typing import Any | ||
|
||
import mapy | ||
from shapely.geometry import shape, Polygon | ||
|
||
import random | ||
from mapy.geo_util import Box, merge_bounds | ||
|
||
|
||
def load_geojson(file_path: str) -> tuple[list[Polygon], dict[str, Any]]: | ||
with open(file_path, "r") as f: | ||
data = json.load(f) | ||
features = data["features"] | ||
geoms = [] | ||
properties = [] | ||
for feature in features: | ||
geom = shape(feature["geometry"]) | ||
properties.append(feature["properties"]) | ||
geoms.append(geom) | ||
return geoms, properties | ||
|
||
|
||
def build_fill_items(polygons: list[Polygon]) -> list[mapy.FillItem]: | ||
items = [] | ||
for poly in polygons: | ||
fill_color = mapy.Color.from_hsv(random.random(), 0.7, 0.5, 0.2) | ||
line_color = mapy.Color(0, 0, 0, 0.6) | ||
line_width = 1 | ||
items.append(mapy.FillItem(poly, fill_color, line_color, line_width)) | ||
return items | ||
|
||
|
||
def build_symbol_items( | ||
polygons: list[Polygon], properties: list[dict[str, Any]] | ||
) -> list[mapy.SymbolItem]: | ||
items = [] | ||
for poly, props in zip(polygons, properties): | ||
poly.centroid | ||
text = props["NAME_2"] | ||
symbol_item = mapy.SymbolItem( | ||
poly.centroid, | ||
text=text, | ||
text_weight=mapy.FontWeight.BOLD, | ||
text_size=18, | ||
text_color=mapy.Colors.BLACK, | ||
text_outline_color=mapy.Colors.WHITE, | ||
text_outline_width=2, | ||
text_anchor=mapy.TextAnchor.CENTER, | ||
text_offset=(0, 40) if text == "Brandenburg" else (0, 0), | ||
) | ||
items.append(symbol_item) | ||
return items | ||
|
||
|
||
def main(): | ||
map = mapy.Map() | ||
random.seed(0) | ||
geoms, properties = load_geojson("example/districts_germany.json") | ||
bboxes = [Box(*geom.bounds) for geom in geoms] | ||
bbox = merge_bounds(bboxes).with_relative_padding(0.05) | ||
|
||
tile_layer = mapy.TiledRasterLayer( | ||
[ | ||
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", | ||
] | ||
) | ||
|
||
map.add_layer(tile_layer) | ||
map.add_layer(mapy.FillLayer(build_fill_items(geoms))) | ||
map.add_layer(mapy.SymbolLayer(build_symbol_items(geoms, properties))) | ||
map.add_layer(mapy.Attribution("© OpenStreetMap contributors")) | ||
|
||
render_mode = mapy.FixedScreenSize(bbox, mapy.ScreenSize(1400, 1175)) | ||
map.render(render_mode).write_to_png("images/EnforcedScreenSize.png") | ||
render_mode = mapy.FixedBBox(bbox, 1000**2) | ||
map.render(render_mode).write_to_png("images/EnforcedBBox.png") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Oops, something went wrong.