forked from AmericanRedCross/street-view-green-view
-
Notifications
You must be signed in to change notification settings - Fork 0
/
create_points.py
148 lines (127 loc) · 4.42 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
142
143
144
145
146
147
148
"""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_TO_KEEP = {
"primary",
"primary_link",
"secondary",
"secondary_link",
"tertiary",
"tertiary_link",
"residential",
}
app = typer.Typer()
def filter_by_highway_type(gdf: gpd.GeoDataFrame):
"""Returns a copy of a GeoDataFrame of OpenStreetMap road features filtered by
highway type.
Args:
gdf (geopandas.GeoDataFrame): OpenStreetMap features.
Returns:
geopandas.GeoDataFrame: Copy of input GeoDataFrame of features filtered by
highway type.
"""
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_TO_KEEP)].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 (gdf.geometry.isna()).any():
raise ValueError(
"Input GeoDataFrame contains null geometries. "
"Rerun with --drop-null to exclude these features."
)
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,
drop_null: Annotated[
bool,
typer.Option(
"--drop-null",
help="Set whether features with null geometries should be removed",
),
] = False,
):
"""Create a dataset of interpolated points along OpenStreetMap roads."""
gdf = gpd.read_file(in_file)
gdf = filter_by_highway_type(gdf)
if drop_null:
gdf = gdf[~gdf.geometry.isna()]
else:
pass
gdf = create_points(gdf, mini_dist=mini_dist)
gdf.to_file(out_file)
if __name__ == "__main__":
app()