OSM PBF Loader¶
OSMPbfLoader
can really quickly parse full OSM extract in the form of *.osm.pbf
file.
It can download and parse a lot of features much faster than the OSMOnlineLoader
, but it's much more useful when a lot of different features are required at once (like when using predefined filters).
When only a single or few features are needed, OSMOnlineLoader
might be a better choice, since OSMPbfLoader
will use a full extract of all features in a given region and will have to iterate over all of them.
In [1]:
Copied!
from srai.loaders.osm_loaders.filters import HEX2VEC_FILTER, GEOFABRIK_LAYERS
from srai.loaders.osm_loaders.filters.popular import get_popular_tags
from srai.loaders.osm_loaders import OSMPbfLoader
from srai.constants import REGIONS_INDEX, WGS84_CRS
from srai.regionalizers import geocode_to_region_gdf
from srai.geometry import buffer_geometry
from shapely.geometry import Point, box
import geopandas as gpd
from srai.loaders.osm_loaders.filters import HEX2VEC_FILTER, GEOFABRIK_LAYERS
from srai.loaders.osm_loaders.filters.popular import get_popular_tags
from srai.loaders.osm_loaders import OSMPbfLoader
from srai.constants import REGIONS_INDEX, WGS84_CRS
from srai.regionalizers import geocode_to_region_gdf
from srai.geometry import buffer_geometry
from shapely.geometry import Point, box
import geopandas as gpd
Using OSMPbfLoader to download data for a specific area¶
Download all features from HEX2VEC_FILTER
in Warsaw, Poland¶
In [2]:
Copied!
loader = OSMPbfLoader()
warsaw_gdf = geocode_to_region_gdf("Warsaw, Poland")
warsaw_features_gdf = loader.load(warsaw_gdf, HEX2VEC_FILTER)
warsaw_features_gdf
loader = OSMPbfLoader()
warsaw_gdf = geocode_to_region_gdf("Warsaw, Poland")
warsaw_features_gdf = loader.load(warsaw_gdf, HEX2VEC_FILTER)
warsaw_features_gdf
/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/srai/loaders/osm_loaders/pbf_file_downloader.py:154: UserWarning: Error occured (Expecting value: line 1 column 1 (char 0)). Auto-switching to 'geofabrik' download source. warnings.warn( Finding matching extracts: 100%|██████████| 1/1 [00:00<00:00, 112.39it/s] Filtering extracts: 100%|██████████| 1/1 [00:00<00:00, 522.20it/s] /opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/srai/loaders/osm_loaders/openstreetmap_extracts.py:344: FutureWarning: `unary_union` returned None due to all-None GeoSeries. In future, `unary_union` will return 'GEOMETRYCOLLECTION EMPTY' instead. ].unary_union mazowieckie.osm.pbf: 100%|██████████| 239M/239M [00:14<00:00, 16.8MiB/s] osmconvert: 100%|██████████| 246k/246k [00:00<00:00, 557kiB/s] Clipping PBF files: 100%|██████████| 1/1 [00:10<00:00, 10.50s/it] [Warsaw, Masovian Voivodeship, Poland] Counting pbf features: 5145050it [00:12, 396208.02it/s] [Warsaw, Masovian Voivodeship, Poland] Parsing pbf file #1: 100%|██████████| 5145050/5145050 [01:14<00:00, 68750.29it/s]
Out[2]:
geometry | aeroway | amenity | building | healthcare | historic | landuse | leisure | military | natural | office | shop | sport | tourism | water | waterway | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
feature_id | ||||||||||||||||
node/31005854 | POINT (20.94595 52.17691) | NaN | restaurant | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/31156693 | POINT (20.95489 52.27100) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | bakery | NaN | NaN | NaN | NaN |
node/31917380 | POINT (21.01451 52.21653) | NaN | NaN | NaN | NaN | memorial | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/32599714 | POINT (21.01518 52.21904) | NaN | parking_entrance | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/33238753 | POINT (20.92711 52.33046) | NaN | ferry_terminal | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
relation/16719047 | MULTIPOLYGON (((21.03470 52.29845, 21.03471 52... | NaN | NaN | NaN | NaN | NaN | NaN | track | NaN | NaN | NaN | NaN | athletics | NaN | NaN | NaN |
relation/16719055 | MULTIPOLYGON (((21.05686 52.28134, 21.05698 52... | NaN | NaN | NaN | NaN | NaN | NaN | track | NaN | NaN | NaN | NaN | athletics | NaN | NaN | NaN |
relation/16719082 | MULTIPOLYGON (((20.92627 52.25899, 20.92629 52... | NaN | NaN | NaN | NaN | NaN | NaN | track | NaN | NaN | NaN | NaN | athletics | NaN | NaN | NaN |
relation/16719084 | MULTIPOLYGON (((20.91954 52.25254, 20.91957 52... | NaN | NaN | NaN | NaN | NaN | NaN | track | NaN | NaN | NaN | NaN | athletics | NaN | NaN | NaN |
relation/16719157 | MULTIPOLYGON (((20.89233 52.25348, 20.89249 52... | NaN | NaN | NaN | NaN | NaN | NaN | track | NaN | NaN | NaN | NaN | athletics | NaN | NaN | NaN |
305548 rows × 16 columns
Plot features¶
Inspired by prettymaps
In [3]:
Copied!
clipped_features_gdf = warsaw_features_gdf.clip(warsaw_gdf.geometry.unary_union)
clipped_features_gdf = warsaw_features_gdf.clip(warsaw_gdf.geometry.unary_union)
In [4]:
Copied!
ax = warsaw_gdf.plot(color="lavender", figsize=(16, 16))
# plot water
clipped_features_gdf.dropna(subset=["water", "waterway"], how="all").plot(
ax=ax, color="deepskyblue"
)
# plot greenery
clipped_features_gdf[
clipped_features_gdf["landuse"].isin(
["grass", "orchard", "flowerbed", "forest", "greenfield", "meadow"]
)
].plot(ax=ax, color="mediumseagreen")
# plot buildings
clipped_features_gdf.dropna(subset=["building"], how="all").plot(
ax=ax, color="dimgray", markersize=0.1
)
xmin, ymin, xmax, ymax = warsaw_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()
ax = warsaw_gdf.plot(color="lavender", figsize=(16, 16))
# plot water
clipped_features_gdf.dropna(subset=["water", "waterway"], how="all").plot(
ax=ax, color="deepskyblue"
)
# plot greenery
clipped_features_gdf[
clipped_features_gdf["landuse"].isin(
["grass", "orchard", "flowerbed", "forest", "greenfield", "meadow"]
)
].plot(ax=ax, color="mediumseagreen")
# plot buildings
clipped_features_gdf.dropna(subset=["building"], how="all").plot(
ax=ax, color="dimgray", markersize=0.1
)
xmin, ymin, xmax, ymax = warsaw_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()
Download all features from popular tags based on OSMTagInfo in Vienna, Austria¶
In [5]:
Copied!
popular_tags = get_popular_tags(in_wiki_only=True)
num_keys = len(popular_tags)
f"Unique keys: {num_keys}."
popular_tags = get_popular_tags(in_wiki_only=True)
num_keys = len(popular_tags)
f"Unique keys: {num_keys}."
Out[5]:
'Unique keys: 320.'
In [6]:
Copied!
{k: popular_tags[k] for k in list(popular_tags)[:10]}
{k: popular_tags[k] for k in list(popular_tags)[:10]}
Out[6]:
{'4wd_only': ['yes'], 'LandPro08:reviewed': ['no'], 'abandoned': ['yes'], 'abandoned:railway': ['rail'], 'abutters': ['residential'], 'access': ['agricultural', 'customers', 'delivery', 'designated', 'destination', 'forestry', 'no', 'permissive', 'permit', 'private', 'unknown', 'yes'], 'addr:TW:dataset': ['137998'], 'addr:country': ['CZ'], 'addr:state': ['AZ', 'CA', 'CO', 'CT', 'FL', 'IN', 'KY', 'MD', 'ME', 'NC', 'NJ', 'NY', 'PA', 'TX'], 'admin_level': ['10', '11', '2', '4', '5', '6', '7', '8', '9']}
In [7]:
Copied!
vienna_center_circle = buffer_geometry(Point(16.37009, 48.20931), meters=1000)
vienna_center_circle_gdf = gpd.GeoDataFrame(
geometry=[vienna_center_circle],
crs=WGS84_CRS,
index=gpd.pd.Index(data=["Vienna"], name=REGIONS_INDEX),
)
vienna_center_circle = buffer_geometry(Point(16.37009, 48.20931), meters=1000)
vienna_center_circle_gdf = gpd.GeoDataFrame(
geometry=[vienna_center_circle],
crs=WGS84_CRS,
index=gpd.pd.Index(data=["Vienna"], name=REGIONS_INDEX),
)
In [8]:
Copied!
loader = OSMPbfLoader()
vienna_features_gdf = loader.load(vienna_center_circle_gdf, popular_tags)
vienna_features_gdf
loader = OSMPbfLoader()
vienna_features_gdf = loader.load(vienna_center_circle_gdf, popular_tags)
vienna_features_gdf
/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/srai/loaders/osm_loaders/pbf_file_downloader.py:154: UserWarning: Error occured (Expecting value: line 1 column 1 (char 0)). Auto-switching to 'geofabrik' download source. warnings.warn( Finding matching extracts: 100%|██████████| 1/1 [00:00<00:00, 138.99it/s] Filtering extracts: 100%|██████████| 1/1 [00:00<00:00, 832.53it/s] /opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/srai/loaders/osm_loaders/openstreetmap_extracts.py:344: FutureWarning: `unary_union` returned None due to all-None GeoSeries. In future, `unary_union` will return 'GEOMETRYCOLLECTION EMPTY' instead. ].unary_union austria.osm.pbf: 100%|██████████| 702M/702M [00:41<00:00, 17.9MiB/s] Clipping PBF files: 100%|██████████| 1/1 [00:26<00:00, 26.36s/it] [Vienna] Counting pbf features: 87277it [00:00, 373516.81it/s] [Vienna] Parsing pbf file #1: 100%|██████████| 87277/87277 [00:14<00:00, 5905.46it/s]
Out[8]:
geometry | abandoned | access | admin_level | advertising | amenity | area | area:highway | artwork_type | atm | ... | tram | tunnel | type | vehicle | vending | waste | water | water_source | waterway | wheelchair | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
feature_id | |||||||||||||||||||||
node/199732 | POINT (16.36007 48.20843) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/199748 | POINT (16.35783 48.21256) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/199753 | POINT (16.35756 48.21165) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/392790 | POINT (16.36620 48.21654) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/395420 | POINT (16.36053 48.20634) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
relation/14972437 | MULTIPOLYGON (((16.36625 48.20066, 16.36632 48... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | yes | NaN | NaN | NaN | NaN | NaN | NaN | NaN | yes |
relation/16221369 | MULTIPOLYGON (((16.36491 48.21639, 16.36493 48... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
relation/16221368 | MULTIPOLYGON (((16.36469 48.21660, 16.36480 48... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
relation/16241555 | MULTIPOLYGON (((16.35858 48.20522, 16.35866 48... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | yes | NaN | NaN | NaN | NaN | NaN | NaN | NaN | yes |
relation/897484 | MULTIPOLYGON (((16.36276 48.21254, 16.36291 48... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
21285 rows × 169 columns
Plot features¶
Uses default
preset colours from prettymaps
In [9]:
Copied!
clipped_vienna_features_gdf = vienna_features_gdf.clip(vienna_center_circle)
clipped_vienna_features_gdf = vienna_features_gdf.clip(vienna_center_circle)
In [10]:
Copied!
ax = vienna_center_circle_gdf.plot(color="#F2F4CB", figsize=(16, 16))
# plot water
clipped_vienna_features_gdf.dropna(subset=["water", "waterway"], how="all").plot(
ax=ax, color="#a8e1e6"
)
# plot streets
clipped_vienna_features_gdf.dropna(subset=["highway"], how="all").plot(
ax=ax, color="#475657", markersize=0.1
)
# plot buildings
clipped_vienna_features_gdf.dropna(subset=["building"], how="all").plot(ax=ax, color="#FF5E5B")
# plot parkings
clipped_vienna_features_gdf[
(clipped_vienna_features_gdf["amenity"] == "parking")
| (clipped_vienna_features_gdf["highway"] == "pedestrian")
].plot(ax=ax, color="#2F3737", markersize=0.1)
# plot greenery
clipped_vienna_features_gdf[
clipped_vienna_features_gdf["landuse"].isin(
["grass", "orchard", "flowerbed", "forest", "greenfield", "meadow"]
)
].plot(ax=ax, color="#8BB174")
xmin, ymin, xmax, ymax = vienna_center_circle_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()
ax = vienna_center_circle_gdf.plot(color="#F2F4CB", figsize=(16, 16))
# plot water
clipped_vienna_features_gdf.dropna(subset=["water", "waterway"], how="all").plot(
ax=ax, color="#a8e1e6"
)
# plot streets
clipped_vienna_features_gdf.dropna(subset=["highway"], how="all").plot(
ax=ax, color="#475657", markersize=0.1
)
# plot buildings
clipped_vienna_features_gdf.dropna(subset=["building"], how="all").plot(ax=ax, color="#FF5E5B")
# plot parkings
clipped_vienna_features_gdf[
(clipped_vienna_features_gdf["amenity"] == "parking")
| (clipped_vienna_features_gdf["highway"] == "pedestrian")
].plot(ax=ax, color="#2F3737", markersize=0.1)
# plot greenery
clipped_vienna_features_gdf[
clipped_vienna_features_gdf["landuse"].isin(
["grass", "orchard", "flowerbed", "forest", "greenfield", "meadow"]
)
].plot(ax=ax, color="#8BB174")
xmin, ymin, xmax, ymax = vienna_center_circle_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()
Download all grouped features based on Geofabrik layers in New York, USA¶
In [11]:
Copied!
manhattan_bbox = box(-73.994551, 40.762396, -73.936872, 40.804239)
manhattan_bbox_gdf = gpd.GeoDataFrame(
geometry=[manhattan_bbox],
crs=WGS84_CRS,
index=gpd.pd.Index(data=["New York"], name=REGIONS_INDEX),
)
manhattan_bbox = box(-73.994551, 40.762396, -73.936872, 40.804239)
manhattan_bbox_gdf = gpd.GeoDataFrame(
geometry=[manhattan_bbox],
crs=WGS84_CRS,
index=gpd.pd.Index(data=["New York"], name=REGIONS_INDEX),
)
In [12]:
Copied!
loader = OSMPbfLoader()
new_york_features_gdf = loader.load(manhattan_bbox_gdf, GEOFABRIK_LAYERS)
new_york_features_gdf
loader = OSMPbfLoader()
new_york_features_gdf = loader.load(manhattan_bbox_gdf, GEOFABRIK_LAYERS)
new_york_features_gdf
/opt/hostedtoolcache/Python/3.10.13/x64/lib/python3.10/site-packages/srai/loaders/osm_loaders/pbf_file_downloader.py:154: UserWarning: Error occured (Expecting value: line 1 column 1 (char 0)). Auto-switching to 'geofabrik' download source. warnings.warn( Finding matching extracts: 100%|██████████| 1/1 [00:00<00:00, 113.74it/s] Filtering extracts: 100%|██████████| 1/1 [00:00<00:00, 651.19it/s] new-york.osm.pbf: 100%|██████████| 416M/416M [00:25<00:00, 16.9MiB/s] new-jersey.osm.pbf: 100%|██████████| 134M/134M [00:08<00:00, 17.0MiB/s] Clipping PBF files: 100%|██████████| 2/2 [00:20<00:00, 10.50s/it] [New York] Counting pbf features: 314861it [00:00, 397518.11it/s] [New York] Parsing pbf file #1: 100%|██████████| 314861/314861 [00:07<00:00, 43837.79it/s] Grouping features: 100%|██████████| 28/28 [00:00<00:00, 37.69it/s]
Out[12]:
geometry | public | education | health | leisure | catering | accommodation | shopping | money | tourism | ... | major_roads | minor_roads | highway_links | very_small_roads | paths_unsuitable_for_cars | railways | waterways | buildings | landuse | water | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
feature_id | |||||||||||||||||||||
node/42421728 | POINT (-73.96004 40.79805) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/42421731 | POINT (-73.96147 40.79865) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/42421737 | POINT (-73.96287 40.79924) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/42421741 | POINT (-73.96569 40.80043) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
node/42421745 | POINT (-73.96800 40.80140) | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
way/1233234599 | LINESTRING (-73.94708 40.76467, -73.94695 40.7... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | highway=footway | NaN | NaN | NaN | NaN | NaN |
way/1234195275 | LINESTRING (-73.97684 40.78764, -73.97695 40.7... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | highway=footway | NaN | NaN | NaN | NaN | NaN |
way/1234195276 | LINESTRING (-73.97721 40.78781, -73.97727 40.7... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | highway=footway | NaN | NaN | NaN | NaN | NaN |
way/1234195277 | LINESTRING (-73.97693 40.78753, -73.97688 40.7... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | highway=footway | NaN | NaN | NaN | NaN | NaN |
relation/2389611 | MULTIPOLYGON (((-74.03462 40.72528, -74.03404 ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | natural=water |
45418 rows × 27 columns
Plot features¶
Inspired by https://snazzymaps.com/style/14889/flat-pale
In [13]:
Copied!
ax = manhattan_bbox_gdf.plot(color="#e7e7df", figsize=(16, 16))
# plot greenery
new_york_features_gdf[new_york_features_gdf["leisure"] == "leisure=park"].plot(
ax=ax, color="#bae5ce"
)
# plot water
new_york_features_gdf.dropna(subset=["water", "waterways"], how="all").plot(ax=ax, color="#c7eced")
# plot streets
new_york_features_gdf.dropna(subset=["paths_unsuitable_for_cars"], how="all").plot(
ax=ax, color="#e7e7df", linewidth=1
)
new_york_features_gdf.dropna(
subset=["very_small_roads", "highway_links", "minor_roads"], how="all"
).plot(ax=ax, color="#fff", linewidth=2)
new_york_features_gdf.dropna(subset=["major_roads"], how="all").plot(
ax=ax, color="#fac9a9", linewidth=3
)
# plot buildings
new_york_features_gdf.dropna(subset=["buildings"], how="all").plot(ax=ax, color="#cecebd")
xmin, ymin, xmax, ymax = manhattan_bbox_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()
ax = manhattan_bbox_gdf.plot(color="#e7e7df", figsize=(16, 16))
# plot greenery
new_york_features_gdf[new_york_features_gdf["leisure"] == "leisure=park"].plot(
ax=ax, color="#bae5ce"
)
# plot water
new_york_features_gdf.dropna(subset=["water", "waterways"], how="all").plot(ax=ax, color="#c7eced")
# plot streets
new_york_features_gdf.dropna(subset=["paths_unsuitable_for_cars"], how="all").plot(
ax=ax, color="#e7e7df", linewidth=1
)
new_york_features_gdf.dropna(
subset=["very_small_roads", "highway_links", "minor_roads"], how="all"
).plot(ax=ax, color="#fff", linewidth=2)
new_york_features_gdf.dropna(subset=["major_roads"], how="all").plot(
ax=ax, color="#fac9a9", linewidth=3
)
# plot buildings
new_york_features_gdf.dropna(subset=["buildings"], how="all").plot(ax=ax, color="#cecebd")
xmin, ymin, xmax, ymax = manhattan_bbox_gdf.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
ax.set_axis_off()