Skip to content

Commit 4c68d6f

Browse files
authored
Feature/Add support for OpenStreetMaps (#28)
* Move internal file methods to utils file * Add OpenStreetMap config to figure * Breakup WKTPlot to add osm components * Separate figure creation from plotting logic * Fix flake8 errors * Add standard plot tests * Add osm plot unittests * Bump minor version * Add flake8 check for tests * Add standard map unittests * Add unittests for osm mapper * Remove non Python 3.7 compatible string format * Rename maps to mappers * Add docstring to _create_figure abstractmethod * Remove commented code * Fix method signature * Add more examples * Update README
1 parent 5622c54 commit 4c68d6f

29 files changed

Lines changed: 970 additions & 680 deletions

README.md

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,63 @@ The [well-known-text](https://en.wikipedia.org/wiki/Well-known_text_representati
1717

1818
WKTPlot is a library provides an easy-to-use API for visualizing well-known-text strings and shapely objects programatically. This library wraps around the [Bokeh](https://github.com/bokeh/bokeh) library, which is a powerful plotting library for generating interactive visualizations. Bokeh also provides a rich assortment of [stylizing options](https://docs.bokeh.org/en/latest/docs/user_guide/styling.html) which are all usable through WKTPlot's `add_shape` method.
1919

20+
---
21+
22+
### Supported datatypes
23+
WKTPlot supports the majority of well-known-text primitives, including:
24+
* Point
25+
* MultiPoint
26+
* LineString
27+
* MultiLineString
28+
* LinearRing
29+
* Polygon
30+
* MultiPolygon
31+
* GeometryCollection
32+
33+
---
34+
2035
## Basic Usage
2136
``` python
22-
from shapely.geometry import Polygon
37+
from shapely.geometry import LineString
2338
from wktplot import WKTPlot
2439

2540
# Create plot object
2641
plot = WKTPlot(title="My first plot!", save_dir="/path/to/directory")
2742

2843
# Define shapes either through well-known-text (WKT) string, or shapely object
29-
shape_1 = "POINT (30 10)"
30-
shape_2 = Polygon([[30, 10], [40, 40], [20, 40], [10, 20], [30, 10]])
44+
line_string = LineString([[45, 5], [30, -7], [40, 10]])
45+
polygon = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"
46+
points = "MULTIPOINT (17 11, 13 0, 22 -5, 25 7)"
3147

32-
# Add shapes to the plot
33-
plot.add_shape(shape_1, fill_color="green", line_width=3)
34-
plot.add_shape(shape_2, fill_color="cyan", fill_alpha=0.7)
48+
# Add shapes to the plot with style args
49+
plot.add_shape(line_string, line_color="firebrick", line_alpha=0.5, line_width=20)
50+
plot.add_shape(polygon, fill_color="#6495ED", fill_alpha=0.5)
51+
plot.add_shape(points, fill_color=(50, 205, 50, 0.25), fill_alpha=0.7, size=30)
3552

36-
# Save the plot to disk [/path/to/directory/my_first_plot.html]
53+
# Save plot to disk [/path/to/directory/my_first_plot.html]
3754
plot.save()
3855
```
3956

40-
### Supported datatypes
41-
WKTPlot supports majority of shapely objects including:
42-
* Point
43-
* MultiPoint
44-
* LineString
45-
* MultiLineString
46-
* LinearRing
47-
* Polygon
48-
* MultiPolygon
49-
* GeometryCollection
57+
![Output](https://i.imgur.com/aajbppI.png)
58+
59+
---
60+
## OpenStreetMaps
61+
WKTPlot now supports the ability to integrate with OpenStreetMaps. Shape coordinates will be projected to the Mercator coordinate system, which appear to distort shape proportions compared to standard geometric projection.
62+
```python
63+
# Import OpenStreetMaps plotting class
64+
from wktplot.plots.osm import OpenStreetMapsPlot
65+
66+
# Create plot object just like standard WKTPlot class
67+
plot = OpenStreetMapsPlot("Open Street Map Plot", save_dir="/path/to/directory")
68+
69+
shape = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))"
70+
plot.add_shape(shape, fill_alpha=0.5, fill_color="firebrick")
71+
72+
plot.save()
73+
```
74+
![Output](https://i.imgur.com/JdUDMh7.png)
75+
76+
---
5077

5178
## Advanced Usage
5279
Example for plotting from shapefile. Shapefile is of California's county boundaries from [here](https://data.ca.gov/dataset/ca-geographic-boundaries).
@@ -70,6 +97,33 @@ plot.save()
7097
Which will result in this output:
7198
![CaliforniaCounties](https://i.imgur.com/YPQQlml.png)
7299

73-
## Future Plans
74-
* Add native support for visualizing GeoDataframes and shapefiles.
75-
* Make web view more interactive.
100+
---
101+
102+
## Development
103+
### Bugs / Feature Requests
104+
Plese open an `Issue` in Github with any bugs found or feature requests, and follow the prompts so that developers can reproduce or implement the necessary changes.
105+
106+
### Local development
107+
Development of this model is centered around the Makefile. All you need to spin up a working environment to build and test this module can be done with the Makefile.
108+
109+
1. Clone the repository onto your machine.
110+
```sh
111+
git clone https://github.com/FuzzFoundation/WKTPlot.git
112+
```
113+
2. Create the Python virtaul environment and install module's development / testing dependencies. This will also install WKTPlot in [develop mode](https://setuptools.pypa.io/en/latest/userguide/development_mode.html).
114+
```sh
115+
make develop
116+
```
117+
3. Activate virtual environment
118+
```sh
119+
source venv/bin/activate
120+
```
121+
4. Run linting and unittests.
122+
```sh
123+
make test
124+
```
125+
5. When you want to remove the virtual environment and clean up after development.
126+
```sh
127+
deactivate
128+
make clean # This will remove all generated files, like .coverage and build/
129+
make sparkling # This will remove all generate files and the virtual env.

examples/basic.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from shapely.geometry import LineString
2+
from wktplot import WKTPlot
3+
4+
# Create plot object
5+
plot = WKTPlot(title="My first plot!", save_dir="/path/to/directory")
6+
7+
# Define shapes either through well-known-text (WKT) string, or shapely object
8+
line_string = LineString([[45, 5], [30, -7], [40, 10]])
9+
polygon = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))"
10+
points = "MULTIPOINT (17 11, 13 0, 22 -5, 25 7)"
11+
12+
# Add shapes to the plot with style args
13+
plot.add_shape(line_string, line_color="firebrick", line_alpha=0.5, line_width=20)
14+
plot.add_shape(polygon, fill_color="#6495ED", fill_alpha=0.5)
15+
plot.add_shape(points, fill_color=(50, 205, 50, 0.25), fill_alpha=0.7, size=30)
16+
17+
# Save plot to disk [/path/to/directory/my_first_plot.html]
18+
plot.save()

examples/open_street_map.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from wktplot.plots.osm import OpenStreetMapsPlot
2+
3+
shape = "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))"
4+
5+
plot = OpenStreetMapsPlot("Open Street Map Plot", save_dir="/path/to/directory")
6+
plot.add_shape(shape, fill_alpha=0.5, fill_color="firebrick")
7+
plot.save()

requirements-dev.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
flake8>=4.0.1
2-
pytest-cov>=2.10.1
32
pytest>=6.1.2
3+
pytest-cov>=2.10.1
4+
pytest-mock>=3.8.2

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ exclude =
4545
.git,
4646
.eggs,
4747
__pycache__,
48-
tests/,
4948
build/,
5049
dist/,
5150
setup.py,

src/wktplot/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .wktplot import WKTPlot # noqa: F401
1+
from .plots.standard import WKTPlot # noqa: F401
22

3-
__version__ = "2.2.1"
3+
__version__ = "2.3.0"

src/wktplot/common/file_utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import random
2+
import re
3+
import string
4+
5+
from re import Pattern
6+
from typing import List
7+
8+
ALPHA_NUM_REGEX: Pattern = re.compile(r'[A-Za-z0-9]+')
9+
10+
11+
def sanitize_text(text: str) -> str:
12+
""" Remove symbols from given argument `text`.
13+
e.g. "wow 123_ @#$% 1" --> "wow_123_1"
14+
15+
Args:
16+
text (str): Text to remove symbols from.
17+
18+
Returns:
19+
str: Sanitized text.
20+
"""
21+
22+
words: List[str] = ALPHA_NUM_REGEX.findall(text.lower())
23+
return "_".join(words)
24+
25+
26+
def get_random_string(string_length: int = 6) -> str:
27+
""" Generate string of random alpha-numeric charcters of a given length `string_length`.
28+
29+
Args:
30+
string_length (int, default = 6): String length of returned string.
31+
32+
Returns:
33+
str: Random string containing alpha-numeric characters.
34+
"""
35+
36+
options: str = string.ascii_letters + string.digits
37+
random_chars: List[str] = random.choices(options, k=string_length)
38+
return "".join(random_chars)

src/wktplot/common/types.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from shapely.geometry import (
2+
GeometryCollection,
3+
LineString,
4+
LinearRing,
5+
MultiLineString,
6+
MultiPoint,
7+
MultiPolygon,
8+
Point,
9+
Polygon,
10+
)
11+
from shapely.geometry.base import BaseGeometry
12+
from typing import Tuple, Type
13+
14+
15+
SUPPORTED_GEOMS: Tuple[Type[BaseGeometry]] = (
16+
GeometryCollection,
17+
LineString,
18+
LinearRing,
19+
MultiLineString,
20+
MultiPoint,
21+
MultiPolygon,
22+
Point,
23+
Polygon,
24+
)

src/wktplot/mappers/__init__.py

Whitespace-only changes.

src/wktplot/mappers/base.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from abc import ABC, abstractclassmethod
2+
from shapely.geometry import Point, LineString, LinearRing, Polygon
3+
from typing import List, Tuple, Union
4+
5+
6+
class BaseMapper(ABC):
7+
8+
@abstractclassmethod
9+
def _get_point_coords(cls, shape: Point) -> Tuple[float, float]:
10+
""" Get x, y coordinate of the given `shape` geometry.
11+
12+
Args:
13+
shape (obj: Point): Point shape.
14+
15+
Returns:
16+
tuple[float, float]: Tuple with x, y coordinate values.
17+
"""
18+
19+
@abstractclassmethod
20+
def _get_line_string_coords(cls, shape: Union[LineString, LinearRing]) -> Tuple[List[int], List[int]]:
21+
""" Get x, y coordinates of the given `shape` geometry.
22+
23+
Args:
24+
shape (obj: LineString | obj: LinearRing): LineString or LinearRing shape.
25+
26+
Returns:
27+
tuple[list[float], list[float]]: Tuple with x, y coordinate value lists.
28+
"""
29+
30+
@abstractclassmethod
31+
def _get_polygon_coords(cls, shape: Polygon) -> Tuple[List[List[float]], List[List[float]]]:
32+
""" Get x, y coordinates of the given `shape` geometry.
33+
34+
Args:
35+
shape (obj: Polygon): Polygon shape.
36+
37+
Returns:
38+
tuple[list[list[float]], list[list[float]]]: Tuple with x, y coordinate values as 2D lists.
39+
"""

0 commit comments

Comments
 (0)