forked from AmericanRedCross/street-view-green-view
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcreate_points.py
141 lines (120 loc) · 3.99 KB
/
create_points.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""Rewrite of createPoints.py from the mittrees/Treepedia_Public project.
See: https://github.com/mittrees/Treepedia_Public/blob/master/Treepedia/createPoints.py
"""
from pathlib import Path
try:
from typing import Annotated
except ImportError:
# For Python <3.9
from typing_extensions import Annotated
import geopandas as gpd
import numpy as np
import shapely
import typer
DEFAULT_MINI_DIST = 20.0 # meters
HIGHWAY_VALUES = {
None,
" ",
"bridleway",
"footway",
"motorway",
"motorway_link",
"pedestrian",
"primary",
"primary_link",
"secondary",
"secondary_link",
"service",
"steps",
"tertiary",
"tertiary_link",
"trunk",
"trunk_link",
}
app = typer.Typer()
def remove_highways(gdf: gpd.GeoDataFrame):
"""Returns a copy of a GeoDataFrame of OpenStreetMap road features with highways
removed.
Args:
gdf (geopandas.GeoDataFrame): OpenStreetMap features.
Returns:
geopandas.GeoDataFrame: Copy of input GeoDataFrame with highway features
removed.
"""
if "highway" not in gdf.columns:
raise ValueError(
"'highway' column not found in input GeoDataFrame. "
"Input data must be of OpenStreetMap roads."
)
out_gdf = gdf[~gdf["highway"].isin(HIGHWAY_VALUES)].copy()
return out_gdf
def interpolate_along_line(
line: shapely.LineString, mini_dist: float
) -> shapely.MultiPoint:
"""Given a LineString, returns a MultiPoint feature with interpolated points with
distaince `mini_dist` between each point, excluding the endpoint.
Args:
line (shapely.LineString): input line
mini_dist (float): distance in meters between interpolated points
Returns:
shapely.MultiPoint: new MultiPoint feature containing interpolated points
"""
new_coords = [
line.interpolate(dist)
for dist in np.linspace(
0.0, line.length, num=int(line.length / mini_dist), endpoint=False
)
]
return shapely.MultiPoint(new_coords)
def create_points(gdf: gpd.GeoDataFrame, mini_dist: float = DEFAULT_MINI_DIST):
"""Given a GeoDataFrame of OpenStreetMap data with LineString features, returns an
exploded GeodataFrame of Point features interpolated along the lines with distance
`mini_dist` in meters.
Args:
gdf (geopandas.GeoDataFrame): input GeoDataFrame of LineString features
mini_dist (float): distance in meters between interpolated points
Returns:
geopandas.GeoDataFrame: new linestrings with interpolated points
"""
if not (gdf.geom_type == "LineString").all():
raise ValueError("Input GeoDataFrame must contain only LineString features.")
# Drop metadata other than 'osm_id'
gdf = gdf[["osm_id", "highway", "geometry"]]
# EPSG:3857 is pseudo WGS84 with unit in meters
gdf = gdf.to_crs("EPSG:3857")
# Interpolate along lines and explode
gdf["geometry"] = gdf["geometry"].apply(interpolate_along_line, args=(mini_dist,))
gdf = gdf.explode(ignore_index=True, index_parts=False)
# Convert output to WGS84
gdf.to_crs("EPSG:4326", inplace=True)
return gdf
@app.command()
def main(
in_file: Annotated[
Path,
typer.Argument(
help=(
"Path to input OpenStreetMap roads data file. Must be a geospatial "
"vector format readable by geopandas."
)
),
],
out_file: Annotated[
Path,
typer.Argument(
help=(
"Path to write interpolated points data. The file extension should "
"correspond to a geospatial vector format writable by geopandas."
)
),
],
mini_dist: Annotated[
float, typer.Option(help="Distance in meters between interpolated points.")
] = DEFAULT_MINI_DIST,
):
gdf = gpd.read_file(in_file)
gdf = remove_highways(gdf)
gdf = create_points(gdf, mini_dist=mini_dist)
gdf.to_file(out_file)
if __name__ == "__main__":
app()