Source code for blocksnet.method.vacant_area.vacant_area

from typing import ClassVar

import geopandas as gpd
import osmnx as ox
import pandas as pd

from ...models import Block, ServiceType
from ..base_method import BaseMethod


[docs]class VacantArea(BaseMethod): local_crs: ClassVar[int] = 32636 roads_buffer: ClassVar[int] = 10 buildings_buffer: ClassVar[int] = 10 min_lenght: ClassVar[int] = 3 min_area: ClassVar[int] = 100 area_attitude: ClassVar[int] = 1.9 @staticmethod def _dwn_other(block, local_crs) -> gpd.GeoSeries: try: other = ox.features_from_polygon(block, tags={"man_made": True, "aeroway": True, "military": True}) other["geometry"] = other["geometry"].to_crs(local_crs) return other.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_leisure(block, local_crs) -> gpd.GeoSeries: try: leisure = ox.features_from_polygon(block, tags={"leisure": True}) leisure["geometry"] = leisure["geometry"].to_crs(local_crs) return leisure.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_landuse(block, local_crs) -> gpd.GeoSeries: try: landuse = ox.features_from_polygon(block, tags={"landuse": True}) if not landuse.empty: landuse = landuse[landuse["landuse"] != "residential"] landuse["geometry"] = landuse["geometry"].to_crs(local_crs) return landuse.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_amenity(block, local_crs) -> gpd.GeoSeries: try: amenity = ox.features_from_polygon(block, tags={"amenity": True}) amenity["geometry"] = amenity["geometry"].to_crs(local_crs) return amenity.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_buildings(block, local_crs, buildings_buffer) -> gpd.GeoSeries: try: buildings = ox.features_from_polygon(block, tags={"building": True}) if buildings_buffer: buildings["geometry"] = buildings["geometry"].to_crs(local_crs).buffer(buildings_buffer) return buildings.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_natural(block, local_crs) -> gpd.GeoSeries: try: natural = ox.features_from_polygon(block, tags={"natural": True}) if not natural.empty: natural = natural[natural["natural"] != "bay"] natural["geometry"] = natural["geometry"].to_crs(local_crs) return natural.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_waterway(block, local_crs) -> gpd.GeoSeries: try: waterway = ox.features_from_polygon(block, tags={"waterway": True}) waterway["geometry"] = waterway["geometry"].to_crs(local_crs) return waterway.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_highway(block, local_crs, roads_buffer) -> gpd.GeoSeries: try: highway = ox.features_from_polygon(block, tags={"highway": True}) condition = ( (highway["highway"] != "path") & (highway["highway"] != "footway") & (highway["highway"] != "pedestrian") ) filtered_highway = highway[condition] if roads_buffer: filtered_highway["geometry"] = filtered_highway["geometry"].to_crs(local_crs).buffer(roads_buffer) filtered_highway["geometry"] = filtered_highway["geometry"].to_crs(local_crs).buffer(1) return filtered_highway.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_path(block, local_crs) -> gpd.GeoSeries: try: tags = {"highway": "path", "highway": "footway"} path = ox.features_from_polygon(block, tags=tags) path["geometry"] = path["geometry"].to_crs(local_crs).buffer(1) return path.geometry except: return gpd.GeoSeries() @staticmethod def _dwn_railway(block, local_crs) -> gpd.GeoSeries: try: railway = ox.features_from_polygon(block, tags={"railway": True}) if not railway.empty: railway = railway[railway["railway"] != "subway"] railway["geometry"] = railway["geometry"].to_crs(local_crs) return railway.geometry except: return gpd.GeoSeries() @staticmethod def _create_minimum_bounding_rectangle(polygon) -> gpd.GeoSeries: return polygon.minimum_rotated_rectangle @staticmethod def _buffer_and_union(row, buffer_distance=1.1) -> gpd.GeoSeries: polygon = row["geometry"] buffer_polygon = polygon.buffer(buffer_distance) return buffer_polygon
[docs] def _get_blocks_gdf(self) -> gpd.GeoDataFrame: """Returns blocks gdf for provision assessment""" data: list[dict] = [] for block in self.city_model.blocks: data.append({"id": block.id, "geometry": block.geometry}) gdf = gpd.GeoDataFrame(data).set_index("id").set_crs(epsg=self.city_model.epsg) return gdf
[docs] def get_vacant_area(self, block: int | Block) -> gpd.GeoDataFrame: blocks = self._get_blocks_gdf() blocks = gpd.GeoDataFrame(geometry=gpd.GeoSeries(blocks.geometry)) if block: if not isinstance(block, Block): block = self.city_model[block] block_gdf = gpd.GeoDataFrame([blocks.iloc[block.id]], crs=blocks.crs) block_buffer = block_gdf["geometry"].buffer(20).to_crs(epsg=4326).iloc[0] else: block_gdf = blocks block_buffer = blocks.buffer(20).to_crs(epsg=4326).unary_union leisure = self._dwn_leisure(block_buffer, self.local_crs) landuse = self._dwn_landuse(block_buffer, self.local_crs) other = self._dwn_other(block_buffer, self.local_crs) amenity = self._dwn_amenity(block_buffer, self.local_crs) buildings = self._dwn_buildings(block_buffer, self.local_crs, self.buildings_buffer) natural = self._dwn_natural(block_buffer, self.local_crs) waterway = self._dwn_waterway(block_buffer, self.local_crs) highway = self._dwn_highway(block_buffer, self.local_crs, self.roads_buffer) railway = self._dwn_railway(block_buffer, self.local_crs) path = self._dwn_path(block_buffer, self.local_crs) occupied_area = [leisure, other, landuse, amenity, buildings, natural, waterway, highway, railway, path] occupied_area = pd.concat(occupied_area) occupied_area = gpd.GeoDataFrame(geometry=gpd.GeoSeries(occupied_area)) block_buffer2 = gpd.GeoDataFrame(geometry=block_gdf.buffer(60)) polygon = occupied_area.geometry.geom_type == "Polygon" multipolygon = occupied_area.geometry.geom_type == "MultiPolygon" blocks_new = gpd.overlay(block_buffer2, occupied_area[polygon], how="difference") blocks_new = gpd.overlay(blocks_new, occupied_area[multipolygon], how="difference") blocks_new = gpd.overlay(block_gdf, blocks_new, how="intersection") blocks_exploded = blocks_new.explode(index_parts=True) blocks_exploded.reset_index(drop=True, inplace=True) blocks_exploded["buffered_geometry"] = blocks_exploded.apply(self._buffer_and_union, axis=1) unified_geometry = blocks_exploded["buffered_geometry"].unary_union result_gdf = gpd.GeoDataFrame(geometry=[unified_geometry], crs=blocks_exploded.crs) result_gdf_exploded = result_gdf.explode(index_parts=True) result_gdf_exploded["area"] = result_gdf_exploded["geometry"].area result_gdf_exploded.reset_index(drop=True, inplace=True) blocks_filtered_area = result_gdf_exploded[result_gdf_exploded["geometry"].area >= self.min_area] if blocks_filtered_area.empty: print("The block has no vacant area") return gpd.GeoDataFrame() for index, row in blocks_filtered_area.iterrows(): polygon = row["geometry"] mbr = self._create_minimum_bounding_rectangle(polygon) if polygon.area * self.area_attitude < mbr.area: blocks_filtered_area.drop(index, inplace=True) gdf = blocks_filtered_area gdf["length"] = gdf.geometry.length threshold_ratio = self.min_lenght # Задайте ваш пороговый параметр filtered_gdf = gdf[(gdf["area"] / gdf["length"] <= threshold_ratio)] indices_to_remove = filtered_gdf.index result_gdf = gdf.drop(indices_to_remove) result_gdf.reset_index(drop=True, inplace=True) return result_gdf