kopia lustrzana https://github.com/marceloprates/prettymaps
Added support for Point and Line geometries
rodzic
560ecf95ca
commit
b406259c43
|
@ -157,6 +157,54 @@ def plot(
|
||||||
scale_y=None,
|
scale_y=None,
|
||||||
rotation=None,
|
rotation=None,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Draw a map from OpenStreetMap data.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
query : string
|
||||||
|
The address to geocode and use as the central point around which to get the geometries
|
||||||
|
backup : dict
|
||||||
|
(Optional) feed the output from a previous 'plot()' run to save time
|
||||||
|
postprocessing: function
|
||||||
|
(Optional) Apply a postprocessing step to the 'layers' dict
|
||||||
|
radius
|
||||||
|
(Optional) If not None, draw the map centered around the address with this radius (in meters)
|
||||||
|
layers: dict
|
||||||
|
Specify the name of each layer and the OpenStreetMap tags to fetch
|
||||||
|
drawing_kwargs: dict
|
||||||
|
Drawing params for each layer (matplotlib params such as 'fc', 'ec', 'fill', etc.)
|
||||||
|
osm_credit: dict
|
||||||
|
OSM Caption parameters
|
||||||
|
figsize: Tuple
|
||||||
|
(Optional) Width and Height (in inches) for the Matplotlib figure. Defaults to (10, 10)
|
||||||
|
ax: axes
|
||||||
|
Matplotlib axes
|
||||||
|
title: String
|
||||||
|
(Optional) Title for the Matplotlib figure
|
||||||
|
vsketch: Vsketch
|
||||||
|
(Optional) Vsketch object for pen plotting
|
||||||
|
x: float
|
||||||
|
(Optional) Horizontal displacement
|
||||||
|
y: float
|
||||||
|
(Optional) Vertical displacement
|
||||||
|
scale_x: float
|
||||||
|
(Optional) Horizontal scale factor
|
||||||
|
scale_y: float
|
||||||
|
(Optional) Vertical scale factor
|
||||||
|
rotation: float
|
||||||
|
(Optional) Rotation in angles (0-360)
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
layers: dict
|
||||||
|
Dictionary of layers (each layer is a Shapely MultiPolygon)
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
# Interpret query
|
# Interpret query
|
||||||
query_mode = parse_query(query)
|
query_mode = parse_query(query)
|
||||||
|
|
|
@ -1,15 +1,34 @@
|
||||||
|
from ast import Dict
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from tokenize import Number, String
|
||||||
|
from typing import Optional, Union, Tuple
|
||||||
|
from xmlrpc.client import Boolean
|
||||||
|
|
||||||
import osmnx as ox
|
import osmnx as ox
|
||||||
from osmnx import utils_geo
|
from osmnx import utils_geo
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from shapely.geometry import Point, Polygon, MultiPolygon, MultiLineString
|
from shapely.geometry import Point, Polygon, MultiPolygon, LineString, MultiLineString
|
||||||
from shapely.ops import unary_union
|
from shapely.ops import unary_union
|
||||||
from geopandas import GeoDataFrame, read_file
|
from geopandas import GeoDataFrame, read_file
|
||||||
|
|
||||||
|
|
||||||
# Compute circular or square boundary given point, radius and crs
|
def get_boundary(
|
||||||
def get_boundary(point, radius, crs, circle=True, dilate=0):
|
point: Tuple, radius: float, crs: String, circle: Boolean = True, dilate: float = 0
|
||||||
|
) -> Polygon:
|
||||||
|
"""
|
||||||
|
Compute circular or square boundary given point, radius and crs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
point (Tuple): GPS coordinates
|
||||||
|
radius (Number): radius in meters
|
||||||
|
crs (String): Coordinate Reference System
|
||||||
|
circle (bool, optional): Whether to use a circular (True) or square (False) boundary. Defaults to True.
|
||||||
|
dilate (int, optional): Dilate the boundary by this much, in meters. Defaults to 0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Polygon: a shapely Polygon representing the boundary
|
||||||
|
"""
|
||||||
|
|
||||||
if circle:
|
if circle:
|
||||||
return (
|
return (
|
||||||
ox.project_gdf(GeoDataFrame(geometry=[Point(point[::-1])], crs=crs))
|
ox.project_gdf(GeoDataFrame(geometry=[Point(point[::-1])], crs=crs))
|
||||||
|
@ -28,8 +47,17 @@ def get_boundary(point, radius, crs, circle=True, dilate=0):
|
||||||
).buffer(dilate)
|
).buffer(dilate)
|
||||||
|
|
||||||
|
|
||||||
# Get perimeter
|
def get_perimeter(query, by_osmid: Boolean = False, **kwargs) -> GeoDataFrame:
|
||||||
def get_perimeter(query, by_osmid=False, **kwargs):
|
"""
|
||||||
|
Fetch perimeter given query
|
||||||
|
|
||||||
|
Args:
|
||||||
|
query (String): Query for the perimeter to be fetched (for example, "France")
|
||||||
|
by_osmid (bool, optional): Whether to fetch perimeter by OSM Id. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GeoDataFrame: GeoDataFrame representation of the perimeter
|
||||||
|
"""
|
||||||
return ox.geocode_to_gdf(
|
return ox.geocode_to_gdf(
|
||||||
query,
|
query,
|
||||||
by_osmid=by_osmid,
|
by_osmid=by_osmid,
|
||||||
|
@ -38,75 +66,36 @@ def get_perimeter(query, by_osmid=False, **kwargs):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Get coastline
|
def get_coast(
|
||||||
def get_coast(perimeter = None, point = None, radius = None, perimeter_tolerance = 0, union = True, buffer = 0, circle = True, dilate = 0, file_location = None):
|
perimeter=None,
|
||||||
|
point=None,
|
||||||
|
radius=None,
|
||||||
|
perimeter_tolerance=0,
|
||||||
|
union=True,
|
||||||
|
buffer=0,
|
||||||
|
circle=True,
|
||||||
|
dilate=0,
|
||||||
|
file_location=None,
|
||||||
|
):
|
||||||
|
|
||||||
if perimeter is not None:
|
if perimeter is not None:
|
||||||
# Boundary defined by polygon (perimeter)
|
# Boundary defined by polygon (perimeter)
|
||||||
bbox=perimeter.to_crs(3174)
|
bbox = perimeter.to_crs(3174)
|
||||||
bbox=bbox.buffer(perimeter_tolerance+dilate+buffer)
|
bbox = bbox.buffer(perimeter_tolerance + dilate + buffer)
|
||||||
bbox=bbox.to_crs(4326)
|
bbox = bbox.to_crs(4326)
|
||||||
bbox=bbox.envelope
|
bbox = bbox.envelope
|
||||||
# Load the polygons for the coastline from a file
|
# Load the polygons for the coastline from a file
|
||||||
geometries = read_file(file_location, bbox=bbox)
|
geometries = read_file(file_location, bbox=bbox)
|
||||||
perimeter = unary_union(ox.project_gdf(perimeter).geometry)
|
perimeter = unary_union(ox.project_gdf(perimeter).geometry)
|
||||||
|
|
||||||
elif (point is not None) and (radius is not None):
|
elif (point is not None) and (radius is not None):
|
||||||
# Boundary defined by circle with radius 'radius' around point
|
# Boundary defined by circle with radius 'radius' around point
|
||||||
north,south,west,east=utils_geo.bbox_from_point(point, dist=radius+dilate+buffer)
|
north, south, west, east = utils_geo.bbox_from_point(
|
||||||
bbox=(west,south,east,north)
|
point, dist=radius + dilate + buffer
|
||||||
|
)
|
||||||
|
bbox = (west, south, east, north)
|
||||||
# Load the polygons for the coastline from a file
|
# Load the polygons for the coastline from a file
|
||||||
geometries=read_file(file_location, bbox=bbox)
|
geometries = read_file(file_location, bbox=bbox)
|
||||||
perimeter = get_boundary(point, radius, geometries.crs, circle = circle, dilate = dilate)
|
|
||||||
|
|
||||||
# Project GDF
|
|
||||||
if len(geometries) > 0:
|
|
||||||
geometries = ox.project_gdf(geometries)
|
|
||||||
|
|
||||||
# Intersect with perimeter
|
|
||||||
geometries = geometries.intersection(perimeter)
|
|
||||||
|
|
||||||
if union:
|
|
||||||
geometries = unary_union(reduce(lambda x,y: x+y, [
|
|
||||||
[x] if type(x) == Polygon else list(x)
|
|
||||||
for x in geometries if type(x) in [Polygon, MultiPolygon]
|
|
||||||
], []))
|
|
||||||
else:
|
|
||||||
geometries = MultiPolygon(reduce(lambda x,y: x+y, [
|
|
||||||
[x] if type(x) == Polygon else list(x)
|
|
||||||
for x in geometries if type(x) in [Polygon, MultiPolygon]
|
|
||||||
], []))
|
|
||||||
|
|
||||||
return geometries
|
|
||||||
|
|
||||||
# Get geometries
|
|
||||||
def get_geometries(
|
|
||||||
perimeter=None,
|
|
||||||
point=None,
|
|
||||||
radius=None,
|
|
||||||
tags={},
|
|
||||||
perimeter_tolerance=0,
|
|
||||||
union=True,
|
|
||||||
circle=True,
|
|
||||||
dilate=0,
|
|
||||||
):
|
|
||||||
|
|
||||||
if perimeter is not None:
|
|
||||||
# Boundary defined by polygon (perimeter)
|
|
||||||
geometries = ox.geometries_from_polygon(
|
|
||||||
unary_union(perimeter.geometry).buffer(perimeter_tolerance)
|
|
||||||
if perimeter_tolerance > 0
|
|
||||||
else unary_union(perimeter.geometry),
|
|
||||||
tags={tags: True} if type(tags) == str else tags,
|
|
||||||
)
|
|
||||||
perimeter = unary_union(ox.project_gdf(perimeter).geometry)
|
|
||||||
|
|
||||||
elif (point is not None) and (radius is not None):
|
|
||||||
# Boundary defined by circle with radius 'radius' around point
|
|
||||||
geometries = ox.geometries_from_point(
|
|
||||||
point,
|
|
||||||
dist=radius + dilate,
|
|
||||||
tags={tags: True} if type(tags) == str else tags,
|
|
||||||
)
|
|
||||||
perimeter = get_boundary(
|
perimeter = get_boundary(
|
||||||
point, radius, geometries.crs, circle=circle, dilate=dilate
|
point, radius, geometries.crs, circle=circle, dilate=dilate
|
||||||
)
|
)
|
||||||
|
@ -146,19 +135,130 @@ def get_geometries(
|
||||||
return geometries
|
return geometries
|
||||||
|
|
||||||
|
|
||||||
# Get streets
|
def get_geometries(
|
||||||
|
perimeter: Optional[GeoDataFrame] = None,
|
||||||
|
point: Optional[Tuple] = None,
|
||||||
|
radius: Optional[float] = None,
|
||||||
|
tags: Dict = {},
|
||||||
|
perimeter_tolerance: float = 0,
|
||||||
|
union: Boolean = True,
|
||||||
|
circle: Boolean = True,
|
||||||
|
dilate: float = 0,
|
||||||
|
) -> Union[Polygon, MultiPolygon]:
|
||||||
|
"""Get geometries
|
||||||
|
|
||||||
|
Args:
|
||||||
|
perimeter (Optional[GeoDataFrame], optional): Perimeter from within geometries will be fetched. Defaults to None.
|
||||||
|
point (Optional[Tuple], optional): GPS coordinates. Defaults to None.
|
||||||
|
radius (Optional[Number], optional): Radius in meters. Defaults to None.
|
||||||
|
tags (Dict, optional): OpenStreetMap tags for the geometries to be fetched. Defaults to {}.
|
||||||
|
perimeter_tolerance (Number, optional): Tolerance in meters for fetching geometries that fall outside the perimeter. Defaults to 0.
|
||||||
|
union (Boolean, optional): Whether to compute the union of all geometries. Defaults to True.
|
||||||
|
circle (Boolean, optional): Whether to fetch geometries in a circular (True) or square (False) shape. Defaults to True.
|
||||||
|
dilate (Number, optional): Dilate the boundary by this much in meters. Defaults to 0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
[type]: [description]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if perimeter is not None:
|
||||||
|
# Boundary defined by polygon (perimeter)
|
||||||
|
geometries = ox.geometries_from_polygon(
|
||||||
|
unary_union(perimeter.geometry).buffer(perimeter_tolerance)
|
||||||
|
if perimeter_tolerance > 0
|
||||||
|
else unary_union(perimeter.geometry),
|
||||||
|
tags={tags: True} if type(tags) == str else tags,
|
||||||
|
)
|
||||||
|
perimeter = unary_union(ox.project_gdf(perimeter).geometry)
|
||||||
|
|
||||||
|
elif (point is not None) and (radius is not None):
|
||||||
|
# Boundary defined by circle with radius 'radius' around point
|
||||||
|
geometries = ox.geometries_from_point(
|
||||||
|
point,
|
||||||
|
dist=radius + dilate,
|
||||||
|
tags={tags: True} if type(tags) == str else tags,
|
||||||
|
)
|
||||||
|
perimeter = get_boundary(
|
||||||
|
point, radius, geometries.crs, circle=circle, dilate=dilate
|
||||||
|
)
|
||||||
|
|
||||||
|
# Project GDF
|
||||||
|
if len(geometries) > 0:
|
||||||
|
geometries = ox.project_gdf(geometries)
|
||||||
|
|
||||||
|
# Intersect with perimeter
|
||||||
|
geometries = geometries.intersection(perimeter)
|
||||||
|
|
||||||
|
if union:
|
||||||
|
polys = unary_union(
|
||||||
|
reduce(
|
||||||
|
lambda x, y: x + y,
|
||||||
|
[
|
||||||
|
[x] if type(x) == Polygon else list(x)
|
||||||
|
for x in geometries
|
||||||
|
if type(x) in [Polygon, MultiPolygon]
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
points = unary_union([
|
||||||
|
x for x in geometries
|
||||||
|
if isinstance(x, Point)
|
||||||
|
]).buffer(2)
|
||||||
|
|
||||||
|
lines = unary_union([
|
||||||
|
x for x in geometries
|
||||||
|
if isinstance(x, LineString)
|
||||||
|
]).buffer(3)
|
||||||
|
|
||||||
|
geometries = unary_union([polys, points, lines])
|
||||||
|
|
||||||
|
else:
|
||||||
|
geometries = MultiPolygon(
|
||||||
|
reduce(
|
||||||
|
lambda x, y: x + y,
|
||||||
|
[
|
||||||
|
[x] if type(x) == Polygon else list(x)
|
||||||
|
for x in geometries
|
||||||
|
if type(x) in [Polygon, MultiPolygon]
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return geometries
|
||||||
|
|
||||||
|
|
||||||
def get_streets(
|
def get_streets(
|
||||||
perimeter=None,
|
perimeter: Optional[GeoDataFrame] = None,
|
||||||
point=None,
|
point: Optional[Tuple] = None,
|
||||||
radius=None,
|
radius: Optional[float] = None,
|
||||||
layer="streets",
|
layer: String = "streets",
|
||||||
width=6,
|
width: float = 6,
|
||||||
custom_filter=None,
|
custom_filter: Optional[str] = None,
|
||||||
buffer=0,
|
buffer: float = 0,
|
||||||
retain_all=False,
|
retain_all: Boolean = False,
|
||||||
circle=True,
|
circle: Boolean = True,
|
||||||
dilate=0,
|
dilate: float = 0,
|
||||||
):
|
) -> MultiPolygon:
|
||||||
|
"""
|
||||||
|
Get streets
|
||||||
|
|
||||||
|
Args:
|
||||||
|
perimeter (Optional[GeoDataFrame], optional): [description]. Defaults to None.
|
||||||
|
point (Optional[Tuple], optional): [description]. Defaults to None.
|
||||||
|
radius (Optional[Number], optional): [description]. Defaults to None.
|
||||||
|
layer (String, optional): [description]. Defaults to "streets".
|
||||||
|
width (Number, optional): [description]. Defaults to 6.
|
||||||
|
custom_filter (Optional[String], optional): [description]. Defaults to None.
|
||||||
|
buffer (Number, optional): [description]. Defaults to 0.
|
||||||
|
retain_all (Boolean, optional): [description]. Defaults to False.
|
||||||
|
circle (Boolean, optional): [description]. Defaults to True.
|
||||||
|
dilate (Number, optional): [description]. Defaults to 0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MultiPolygon: [description]
|
||||||
|
"""
|
||||||
|
|
||||||
if layer == "streets":
|
if layer == "streets":
|
||||||
layer = "highway"
|
layer = "highway"
|
||||||
|
@ -226,8 +326,18 @@ def get_streets(
|
||||||
return streets
|
return streets
|
||||||
|
|
||||||
|
|
||||||
# Get any layer
|
def get_layer(layer: String, **kwargs) -> Union[Polygon, MultiPolygon]:
|
||||||
def get_layer(layer, **kwargs):
|
"""[summary]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
layer (String): [description]
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: [description]
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Union[Polygon, MultiPolygon]: [description]
|
||||||
|
"""
|
||||||
# Fetch perimeter
|
# Fetch perimeter
|
||||||
if layer == "perimeter":
|
if layer == "perimeter":
|
||||||
# If perimeter is already provided:
|
# If perimeter is already provided:
|
||||||
|
@ -246,10 +356,10 @@ def get_layer(layer, **kwargs):
|
||||||
else:
|
else:
|
||||||
raise Exception("Either 'perimeter' or 'point' & 'radius' must be provided")
|
raise Exception("Either 'perimeter' or 'point' & 'radius' must be provided")
|
||||||
# Fetch streets or railway
|
# Fetch streets or railway
|
||||||
if layer in ['streets', 'railway', 'waterway']:
|
if layer in ["streets", "railway", "waterway"]:
|
||||||
return get_streets(**kwargs, layer=layer)
|
return get_streets(**kwargs, layer=layer)
|
||||||
# Fetch Coastline
|
# Fetch Coastline
|
||||||
elif layer == 'coastline':
|
elif layer == "coastline":
|
||||||
return get_coast(**kwargs)
|
return get_coast(**kwargs)
|
||||||
# Fetch geometries
|
# Fetch geometries
|
||||||
else:
|
else:
|
||||||
|
|
Ładowanie…
Reference in New Issue