ObjectNat 0.2.7__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ObjectNat might be problematic. Click here for more details.
- objectnat/_api.py +5 -8
- objectnat/_config.py +0 -24
- objectnat/_version.py +1 -1
- objectnat/methods/coverage_zones/__init__.py +2 -0
- objectnat/methods/coverage_zones/graph_coverage.py +118 -0
- objectnat/methods/coverage_zones/radius_voronoi.py +45 -0
- objectnat/methods/isochrones/__init__.py +1 -0
- objectnat/methods/isochrones/isochrone_utils.py +130 -0
- objectnat/methods/isochrones/isochrones.py +325 -0
- objectnat/methods/noise/__init__.py +2 -2
- objectnat/methods/noise/noise_sim.py +14 -9
- objectnat/methods/point_clustering/__init__.py +1 -0
- objectnat/methods/{cluster_points_in_polygons.py → point_clustering/cluster_points_in_polygons.py} +22 -28
- objectnat/methods/provision/__init__.py +1 -0
- objectnat/methods/provision/provision.py +4 -4
- objectnat/methods/provision/provision_model.py +17 -18
- objectnat/methods/utils/geom_utils.py +54 -3
- objectnat/methods/utils/graph_utils.py +127 -0
- objectnat/methods/utils/math_utils.py +32 -0
- objectnat/methods/visibility/__init__.py +6 -0
- objectnat/methods/{visibility_analysis.py → visibility/visibility_analysis.py} +167 -208
- objectnat-1.0.0.dist-info/METADATA +143 -0
- objectnat-1.0.0.dist-info/RECORD +32 -0
- objectnat/methods/balanced_buildings.py +0 -69
- objectnat/methods/coverage_zones.py +0 -90
- objectnat/methods/isochrones.py +0 -143
- objectnat/methods/living_buildings_osm.py +0 -168
- objectnat-0.2.7.dist-info/METADATA +0 -118
- objectnat-0.2.7.dist-info/RECORD +0 -26
- {objectnat-0.2.7.dist-info → objectnat-1.0.0.dist-info}/LICENSE.txt +0 -0
- {objectnat-0.2.7.dist-info → objectnat-1.0.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
objectnat/__init__.py,sha256=OnDvrLPLEeYIE_9qOVYgMc-PkRzIqShtGxirguEXiRU,260
|
|
2
|
+
objectnat/_api.py,sha256=5TvsMjWjiR7kFIWnfRJxngWOrC_eKQ7Pt-TSMpjqzI0,595
|
|
3
|
+
objectnat/_config.py,sha256=fGPsMZqA8FVBBOINxRiTFkOOZsNLyablM5G0tdKeQB4,1306
|
|
4
|
+
objectnat/_version.py,sha256=lzGFsymf0DtA_1oAZcPbeQ557iY-1BRkekhfA2qaFh8,18
|
|
5
|
+
objectnat/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
objectnat/methods/coverage_zones/__init__.py,sha256=cdXjW041ysfIlr4sGQ_Xa2nok4tHfMJTEf8pem-0m-U,95
|
|
7
|
+
objectnat/methods/coverage_zones/graph_coverage.py,sha256=3nVZekDAmbz2ZzxsayX9KIgudlSZF3OnZ9g5kFmVt8o,4797
|
|
8
|
+
objectnat/methods/coverage_zones/radius_voronoi.py,sha256=H4_lvSc770TO6-xq6EzgWuUf00N5vYCMO3X6UJhZYAQ,1735
|
|
9
|
+
objectnat/methods/isochrones/__init__.py,sha256=bDfUZPbS3_PuTEB2QcRTYjvyJtUvjbDhAw6QJvD_ih4,90
|
|
10
|
+
objectnat/methods/isochrones/isochrone_utils.py,sha256=KubqeysYetj1dbWJ_WGrewfM_jWYmRFjj8AWz8aEc1I,4825
|
|
11
|
+
objectnat/methods/isochrones/isochrones.py,sha256=GoaFGiLSsG-RRXA5jtij3zsrwxH5ThKDh8XexZ3rD3k,14046
|
|
12
|
+
objectnat/methods/noise/__init__.py,sha256=6JV586JdOqE0OUFUyxsrCoGuDxRuB-6ItSA5CRuFdsE,152
|
|
13
|
+
objectnat/methods/noise/noise_exceptions.py,sha256=nTav5kp6RNpi0kxD9cMULTApOuvAu9wEiX28fkLAnOc,634
|
|
14
|
+
objectnat/methods/noise/noise_init_data.py,sha256=Vp-R_yH7CgYqZEtbGAdr1iiIbgauReniLQ_a2TcszhY,503
|
|
15
|
+
objectnat/methods/noise/noise_reduce.py,sha256=B85ifAN_mHiBKJso-cZiSkj7588w2sA-ugGvEal4CBw,6885
|
|
16
|
+
objectnat/methods/noise/noise_sim.py,sha256=qb0C2CD-sIEcdUWO3xSwCGjuR94xPgU81FC94TeXHBo,20278
|
|
17
|
+
objectnat/methods/point_clustering/__init__.py,sha256=pX2qDUCvs9LJI36mr65vbdRml6AE8hIYYxIJLdQZQxs,61
|
|
18
|
+
objectnat/methods/point_clustering/cluster_points_in_polygons.py,sha256=kwiZHY3TCUCE-nN5IdhCwDESWJvSCZUfrUJU3yC1csc,5042
|
|
19
|
+
objectnat/methods/provision/__init__.py,sha256=0Uy66n2xH0Y45JyhIYHEVfC2rig6bMYp6PV2KkNhbK8,80
|
|
20
|
+
objectnat/methods/provision/provision.py,sha256=g00mN6RGFZcLg0PIMgywnO3TWiFqx3sTYqTeG3Kl33s,4749
|
|
21
|
+
objectnat/methods/provision/provision_exceptions.py,sha256=lznEmlmZDzGIOtapZVqZDMutIi5eGbFuVCYeVa7VZWk,1687
|
|
22
|
+
objectnat/methods/provision/provision_model.py,sha256=_IKS3pe_i7gLcA56baB0g3mH77T9pPP_4FzjuK_FZ5Y,14529
|
|
23
|
+
objectnat/methods/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
objectnat/methods/utils/geom_utils.py,sha256=6tt7gKztDYOTqaZ1sLO4vN2sNPV9qeFyKK1BbvwwD18,4582
|
|
25
|
+
objectnat/methods/utils/graph_utils.py,sha256=M983Hy1CXRIGg_q71tW0HVB8ZS4ZpDK5I2J3imyznUQ,4799
|
|
26
|
+
objectnat/methods/utils/math_utils.py,sha256=Vc8U15LtFOwgIt1YSOSKWYOIiW_1XLuMGOa6ejBpEUk,839
|
|
27
|
+
objectnat/methods/visibility/__init__.py,sha256=Mx1kaoV-yfQUxlMkgNF4AhjSweFEJMEx3NBis5OM3mA,161
|
|
28
|
+
objectnat/methods/visibility/visibility_analysis.py,sha256=fDIZ5S7oR-5ZEsjZPFexb7oFw-1X6uj0A4xuP3pX354,21183
|
|
29
|
+
objectnat-1.0.0.dist-info/LICENSE.txt,sha256=yPEioMfTd7JAQgAU6J13inS1BSjwd82HFlRSoIb4My8,1498
|
|
30
|
+
objectnat-1.0.0.dist-info/METADATA,sha256=ybm0Wwz002c0AWq9qJw6g6ZTveQB4yZJLekYC3kB70I,8079
|
|
31
|
+
objectnat-1.0.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
32
|
+
objectnat-1.0.0.dist-info/RECORD,,
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import geopandas as gpd
|
|
2
|
-
import population_restorator.balancer.houses as b_build
|
|
3
|
-
import population_restorator.balancer.territories as b_terr
|
|
4
|
-
|
|
5
|
-
from objectnat import config
|
|
6
|
-
|
|
7
|
-
logger = config.logger
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def get_balanced_buildings(
|
|
11
|
-
living_buildings: gpd.GeoDataFrame,
|
|
12
|
-
population: int,
|
|
13
|
-
territories: gpd.GeoDataFrame | None = None,
|
|
14
|
-
) -> gpd.GeoDataFrame:
|
|
15
|
-
"""
|
|
16
|
-
Balance city population into living buildings according to their spot area * `storeys_count`.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
living_buildings (gpd.GeoDataFrame): (multi)polygons of buildings where people live. Must contain either
|
|
20
|
-
"storeys_count" or "living_area" attributes.
|
|
21
|
-
|
|
22
|
-
population (int): Total city population
|
|
23
|
-
|
|
24
|
-
territories (gpd.GeoDataFrame, optional): If more detailed population is known, it can be set via this parameter
|
|
25
|
-
in "population" field. In case territories are given, only buildings inside them will be populated.
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
gpd.GeoDataFrame: The balanced living buildings.
|
|
29
|
-
|
|
30
|
-
Raises:
|
|
31
|
-
ValueError: If population is missing, or if living buildings are not set,
|
|
32
|
-
or if living buildings are missing the `storeys_count` attribute, or if buildings contain no valid value.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
if population is None:
|
|
36
|
-
raise ValueError("Population value is missing")
|
|
37
|
-
if living_buildings.shape[0] == 0:
|
|
38
|
-
raise ValueError("Living buildings are not set")
|
|
39
|
-
if "living_area" not in living_buildings.columns and "storeys_count" not in living_buildings.columns:
|
|
40
|
-
raise ValueError("Living buildings are missing `storeys_count` attribute")
|
|
41
|
-
logger.debug(f"Evacuating {population} residents into the provided building")
|
|
42
|
-
indexes = living_buildings.index
|
|
43
|
-
living_buildings = living_buildings.copy()
|
|
44
|
-
living_buildings.reset_index(drop=True, inplace=True)
|
|
45
|
-
if "living_area" not in living_buildings.columns:
|
|
46
|
-
living_buildings["living_area"] = living_buildings.area * living_buildings["storeys_count"]
|
|
47
|
-
living_buildings = living_buildings[living_buildings["living_area"].notna()]
|
|
48
|
-
if living_buildings.shape[0] == 0:
|
|
49
|
-
raise ValueError("Buildings contain no valid value")
|
|
50
|
-
if territories is None:
|
|
51
|
-
city_territory = b_terr.Territory("city", population, None, living_buildings)
|
|
52
|
-
else:
|
|
53
|
-
inner_territories = [
|
|
54
|
-
b_terr.Territory(
|
|
55
|
-
f"terr_{i}",
|
|
56
|
-
terr.get("population"),
|
|
57
|
-
None,
|
|
58
|
-
living_buildings[terr.contains(living_buildings.centroid)].copy(),
|
|
59
|
-
)
|
|
60
|
-
for i, (_, terr) in enumerate(territories.iterrows())
|
|
61
|
-
]
|
|
62
|
-
city_territory = b_terr.Territory("city", population, inner_territories, None)
|
|
63
|
-
|
|
64
|
-
b_terr.balance_territories(city_territory)
|
|
65
|
-
b_build.balance_houses(city_territory)
|
|
66
|
-
|
|
67
|
-
houses = city_territory.get_all_houses()
|
|
68
|
-
houses.set_index(indexes, drop=True, inplace=True)
|
|
69
|
-
return houses
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
|
-
import geopandas as gpd
|
|
4
|
-
import networkx as nx
|
|
5
|
-
|
|
6
|
-
from .isochrones import get_accessibility_isochrones
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def get_radius_zone_coverage(services: gpd.GeoDataFrame, radius: int) -> gpd.GeoDataFrame:
|
|
10
|
-
"""
|
|
11
|
-
Create a buffer zone with a defined radius around each service location.
|
|
12
|
-
|
|
13
|
-
Parameters
|
|
14
|
-
----------
|
|
15
|
-
services : gpd.GeoDataFrame
|
|
16
|
-
GeoDataFrame containing the service locations.
|
|
17
|
-
radius : int
|
|
18
|
-
The radius for the buffer in meters.
|
|
19
|
-
|
|
20
|
-
Returns
|
|
21
|
-
-------
|
|
22
|
-
gpd.GeoDataFrame
|
|
23
|
-
GeoDataFrame with the buffer zones around each service location.
|
|
24
|
-
|
|
25
|
-
Examples
|
|
26
|
-
--------
|
|
27
|
-
>>> import geopandas as gpd
|
|
28
|
-
>>> from shapely.geometry import Point
|
|
29
|
-
|
|
30
|
-
>>> # Create a sample GeoDataFrame for services
|
|
31
|
-
>>> services = gpd.read_file('services.geojson')
|
|
32
|
-
|
|
33
|
-
>>> # Define the radius
|
|
34
|
-
>>> radius = 50
|
|
35
|
-
|
|
36
|
-
>>> # Get radius zone coverage
|
|
37
|
-
>>> radius_zones = get_radius_zone_coverage(services, radius)
|
|
38
|
-
>>> print(radius_zones)
|
|
39
|
-
"""
|
|
40
|
-
services["geometry"] = services["geometry"].buffer(radius)
|
|
41
|
-
return services
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def get_isochrone_zone_coverage(
|
|
45
|
-
services: gpd.GeoDataFrame,
|
|
46
|
-
weight_type: Literal["time_min", "length_meter"],
|
|
47
|
-
weight_value: int,
|
|
48
|
-
city_graph: nx.Graph,
|
|
49
|
-
) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
|
|
50
|
-
"""
|
|
51
|
-
Create isochrones for each service location based on travel time/distance.
|
|
52
|
-
|
|
53
|
-
Parameters
|
|
54
|
-
----------
|
|
55
|
-
services : gpd.GeoDataFrame
|
|
56
|
-
Layer containing the service locations.
|
|
57
|
-
weight_type : str
|
|
58
|
-
Type of weight used for calculating isochrones, either "time_min" or "length_meter".
|
|
59
|
-
weight_value : int
|
|
60
|
-
The value of the weight, representing time in minutes or distance in meters.
|
|
61
|
-
city_graph : nx.Graph
|
|
62
|
-
The graph representing the city's transportation network.
|
|
63
|
-
|
|
64
|
-
Returns
|
|
65
|
-
-------
|
|
66
|
-
tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]
|
|
67
|
-
The calculated isochrone zones, optionally including routes and stops.
|
|
68
|
-
|
|
69
|
-
Examples
|
|
70
|
-
--------
|
|
71
|
-
>>> import networkx as nx
|
|
72
|
-
>>> import geopandas as gpd
|
|
73
|
-
>>> from shapely.geometry import Point
|
|
74
|
-
>>> from iduedu import get_intermodal_graph
|
|
75
|
-
|
|
76
|
-
>>> # Create a sample city graph with get_intermodal_graph()
|
|
77
|
-
>>> graph = get_intermodal_graph(polygon=my_territory_polygon)
|
|
78
|
-
|
|
79
|
-
>>> # Create a sample GeoDataFrame for services
|
|
80
|
-
>>> services = gpd.read_file('services.geojson')
|
|
81
|
-
|
|
82
|
-
>>> # Define parameters
|
|
83
|
-
>>> weight_type = "time_min"
|
|
84
|
-
>>> weight_value = 10
|
|
85
|
-
|
|
86
|
-
>>> # Get isochrone zone coverage
|
|
87
|
-
>>> isochrones, pt_stops, pt_routes = get_isochrone_zone_coverage(services, weight_type, weight_value, city_graph)
|
|
88
|
-
"""
|
|
89
|
-
iso, routes, stops = get_accessibility_isochrones(services, weight_value, weight_type, city_graph)
|
|
90
|
-
return iso, routes, stops
|
objectnat/methods/isochrones.py
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
|
-
import geopandas as gpd
|
|
4
|
-
import networkx as nx
|
|
5
|
-
import numpy as np
|
|
6
|
-
import pandas as pd
|
|
7
|
-
from osmnx import graph_to_gdfs
|
|
8
|
-
from pyproj import CRS
|
|
9
|
-
from scipy.spatial import KDTree
|
|
10
|
-
from shapely import Point
|
|
11
|
-
from shapely.ops import unary_union
|
|
12
|
-
|
|
13
|
-
from objectnat import config
|
|
14
|
-
|
|
15
|
-
logger = config.logger
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def get_accessibility_isochrones(
|
|
19
|
-
points: gpd.GeoDataFrame,
|
|
20
|
-
weight_value: float,
|
|
21
|
-
weight_type: Literal["time_min", "length_meter"],
|
|
22
|
-
graph_nx: nx.Graph,
|
|
23
|
-
) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]:
|
|
24
|
-
"""
|
|
25
|
-
Calculate accessibility isochrones from a gpd.GeoDataFrame based on the provided city graph.
|
|
26
|
-
|
|
27
|
-
Isochrones represent areas that can be reached from a given point within a specific time or distance,
|
|
28
|
-
using a graph that contains road and transport network data.
|
|
29
|
-
|
|
30
|
-
Parameters
|
|
31
|
-
----------
|
|
32
|
-
points : gpd.GeoDataFrame
|
|
33
|
-
A GeoDataFrame containing the geometry from which accessibility isochrones should be calculated.
|
|
34
|
-
The CRS of this GeoDataFrame must match the CRS of the provided graph.
|
|
35
|
-
weight_value : float
|
|
36
|
-
The maximum distance or time threshold for calculating isochrones.
|
|
37
|
-
weight_type : Literal["time_min", "length_meter"]
|
|
38
|
-
The type of weight to use for distance calculations. Either time in minutes ("time_min") or distance
|
|
39
|
-
in meters ("length_meter").
|
|
40
|
-
graph_nx : nx.Graph
|
|
41
|
-
A NetworkX graph representing the city network.
|
|
42
|
-
The graph must contain the appropriate CRS and, for time-based isochrones, a speed attribute.
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]
|
|
47
|
-
A tuple containing:
|
|
48
|
-
- isochrones :
|
|
49
|
-
GeoDataFrame with the calculated isochrone geometries.
|
|
50
|
-
- public transport stops (if applicable) :
|
|
51
|
-
GeoDataFrame with public transport stops within the isochrone, or None if not applicable.
|
|
52
|
-
- public transport routes (if applicable) :
|
|
53
|
-
GeoDataFrame with public transport routes within the isochrone, or None if not applicable.
|
|
54
|
-
|
|
55
|
-
Examples
|
|
56
|
-
--------
|
|
57
|
-
>>> from iduedu import get_intermodal_graph
|
|
58
|
-
>>> graph = get_intermodal_graph(polygon=my_territory_polygon)
|
|
59
|
-
>>> points = gpd.GeoDataFrame(geometry=[Point(30.33, 59.95)], crs=4326).to_crs(graph.graph['crs'])
|
|
60
|
-
>>> isochrones, stops, routes = get_accessibility_isochrones(points,15,weight_type="time_min", graph_nx=graph)
|
|
61
|
-
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
assert points.crs == CRS.from_epsg(
|
|
65
|
-
graph_nx.graph["crs"]
|
|
66
|
-
), f'CRS mismatch , points.crs = {points.crs.to_epsg()}, graph["crs"] = {graph_nx.graph["crs"]}'
|
|
67
|
-
|
|
68
|
-
nodes_with_data = list(graph_nx.nodes(data=True))
|
|
69
|
-
logger.info("Calculating isochrones distances...")
|
|
70
|
-
coordinates = np.array([(data["x"], data["y"]) for node, data in nodes_with_data])
|
|
71
|
-
tree = KDTree(coordinates)
|
|
72
|
-
|
|
73
|
-
target_coord = [(p.x, p.y) for p in points.representative_point()]
|
|
74
|
-
distances, indices = tree.query(target_coord)
|
|
75
|
-
|
|
76
|
-
nearest_nodes = [nodes_with_data[idx][0] for idx in indices]
|
|
77
|
-
del nodes_with_data
|
|
78
|
-
dist_nearest = pd.DataFrame(data=distances, index=nearest_nodes, columns=["dist"])
|
|
79
|
-
speed = 0
|
|
80
|
-
if graph_nx.graph["type"] in ["walk", "intermodal"] and weight_type == "time_min":
|
|
81
|
-
try:
|
|
82
|
-
speed = graph_nx.graph["walk_speed"]
|
|
83
|
-
except KeyError:
|
|
84
|
-
logger.warning("There is no walk_speed in graph, set to the default speed - 83.33 m/min")
|
|
85
|
-
speed = 83.33
|
|
86
|
-
dist_nearest = dist_nearest / speed
|
|
87
|
-
elif weight_type == "time_min":
|
|
88
|
-
speed = 20 * 1000 / 60
|
|
89
|
-
dist_nearest = dist_nearest / speed
|
|
90
|
-
|
|
91
|
-
if (dist_nearest > weight_value).all().all():
|
|
92
|
-
raise RuntimeError(
|
|
93
|
-
"The point(s) lie further from the graph than weight_value, it's impossible to "
|
|
94
|
-
"construct isochrones. Check the coordinates of the point(s)/their projection"
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
data = []
|
|
98
|
-
for source in nearest_nodes:
|
|
99
|
-
dist, path = nx.single_source_dijkstra(graph_nx, source, weight=weight_type, cutoff=weight_value)
|
|
100
|
-
for target_node, way in path.items():
|
|
101
|
-
source = way[0]
|
|
102
|
-
distance = dist.get(target_node, np.nan)
|
|
103
|
-
data.append((source, target_node, distance))
|
|
104
|
-
del dist
|
|
105
|
-
dist_matrix = pd.DataFrame(data, columns=["source", "destination", "distance"])
|
|
106
|
-
del data
|
|
107
|
-
dist_matrix = dist_matrix.pivot_table(index="source", columns="destination", values="distance", sort=False)
|
|
108
|
-
|
|
109
|
-
dist_matrix = dist_matrix.add(dist_nearest.dist, axis=0)
|
|
110
|
-
dist_matrix = dist_matrix.mask(dist_matrix >= weight_value, np.nan)
|
|
111
|
-
dist_matrix.dropna(how="all", inplace=True)
|
|
112
|
-
|
|
113
|
-
results = []
|
|
114
|
-
logger.info("Building isochrones geometry...")
|
|
115
|
-
for _, row in dist_matrix.iterrows():
|
|
116
|
-
geometry = []
|
|
117
|
-
for node_to, value in row.items():
|
|
118
|
-
if not pd.isna(value):
|
|
119
|
-
node = graph_nx.nodes[node_to]
|
|
120
|
-
point = Point(node["x"], node["y"])
|
|
121
|
-
geometry.append(
|
|
122
|
-
point.buffer(round((weight_value - value) * speed * 0.8, 2))
|
|
123
|
-
if weight_type == "time_min"
|
|
124
|
-
else point.buffer(round((weight_value - value) * 0.8, 2))
|
|
125
|
-
)
|
|
126
|
-
geometry = unary_union(geometry)
|
|
127
|
-
results.append(geometry)
|
|
128
|
-
|
|
129
|
-
isochrones = gpd.GeoDataFrame(data=points, geometry=results, crs=graph_nx.graph["crs"])
|
|
130
|
-
isochrones["weight_type"] = weight_type
|
|
131
|
-
isochrones["weight_value"] = weight_value
|
|
132
|
-
|
|
133
|
-
isochrones_subgraph = graph_nx.subgraph(dist_matrix.columns)
|
|
134
|
-
nodes = pd.DataFrame.from_dict(dict(isochrones_subgraph.nodes(data=True)), orient="index")
|
|
135
|
-
if "desc" in nodes.columns and "stop" in nodes["desc"].unique():
|
|
136
|
-
pt_nodes = nodes[nodes["desc"] == "stop"]
|
|
137
|
-
nodes, edges = graph_to_gdfs(isochrones_subgraph.subgraph(pt_nodes.index))
|
|
138
|
-
nodes.reset_index(drop=True, inplace=True)
|
|
139
|
-
nodes = nodes[["desc", "route", "geometry"]]
|
|
140
|
-
edges.reset_index(drop=True, inplace=True)
|
|
141
|
-
edges = edges[["type", "route", "geometry"]]
|
|
142
|
-
return isochrones, nodes, edges
|
|
143
|
-
return isochrones, None, None
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import geopandas as gpd
|
|
2
|
-
import numpy as np
|
|
3
|
-
import osmnx as ox
|
|
4
|
-
import pandas as pd
|
|
5
|
-
from iduedu import get_boundary
|
|
6
|
-
from shapely import MultiPolygon, Polygon
|
|
7
|
-
|
|
8
|
-
from objectnat import config
|
|
9
|
-
|
|
10
|
-
logger = config.logger
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def eval_is_living(row: gpd.GeoSeries):
|
|
14
|
-
"""
|
|
15
|
-
Determine if a building is used for residential purposes based on its attributes.
|
|
16
|
-
|
|
17
|
-
Parameters
|
|
18
|
-
----------
|
|
19
|
-
row : gpd.GeoSeries
|
|
20
|
-
A GeoSeries representing a row in a GeoDataFrame, containing building attributes.
|
|
21
|
-
|
|
22
|
-
Returns
|
|
23
|
-
-------
|
|
24
|
-
bool
|
|
25
|
-
A boolean indicating whether the building is used for residential purposes.
|
|
26
|
-
|
|
27
|
-
Examples
|
|
28
|
-
--------
|
|
29
|
-
>>> buildings = download_buildings(osm_territory_id=421007)
|
|
30
|
-
>>> buildings['is_living'] = buildings.apply(eval_is_living, axis=1)
|
|
31
|
-
"""
|
|
32
|
-
return row["building"] in (
|
|
33
|
-
"apartments",
|
|
34
|
-
"house",
|
|
35
|
-
"residential",
|
|
36
|
-
"detached",
|
|
37
|
-
"dormitory",
|
|
38
|
-
"semidetached_house",
|
|
39
|
-
"bungalow",
|
|
40
|
-
"cabin",
|
|
41
|
-
"farm",
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def eval_population(source: gpd.GeoDataFrame, population_column: str, area_per_person: float = 33):
|
|
46
|
-
"""
|
|
47
|
-
Estimate the population of buildings in a GeoDataFrame based on their attributes.
|
|
48
|
-
|
|
49
|
-
Parameters
|
|
50
|
-
----------
|
|
51
|
-
source : gpd.GeoDataFrame
|
|
52
|
-
A GeoDataFrame containing building geometries and attributes.
|
|
53
|
-
population_column : str
|
|
54
|
-
The name of the column where the estimated population will be stored.
|
|
55
|
-
area_per_person : float
|
|
56
|
-
The standart living space per person im m², (default is 33)
|
|
57
|
-
Returns
|
|
58
|
-
-------
|
|
59
|
-
gpd.GeoDataFrame
|
|
60
|
-
A GeoDataFrame with an added column for estimated population.
|
|
61
|
-
|
|
62
|
-
Raises
|
|
63
|
-
------
|
|
64
|
-
RuntimeError
|
|
65
|
-
If the 'building:levels' column is not present in the provided GeoDataFrame.
|
|
66
|
-
|
|
67
|
-
Examples
|
|
68
|
-
--------
|
|
69
|
-
>>> source = gpd.read_file('buildings.shp')
|
|
70
|
-
>>> source['is_living'] = source.apply(eval_is_living, axis=1)
|
|
71
|
-
>>> population_df = eval_population(source, 'approximate_pop')
|
|
72
|
-
"""
|
|
73
|
-
if "building:levels" not in source.columns:
|
|
74
|
-
raise RuntimeError("No 'building:levels' column in provided GeoDataFrame")
|
|
75
|
-
df = source.copy()
|
|
76
|
-
local_crs = source.estimate_utm_crs()
|
|
77
|
-
df["area"] = df.to_crs(local_crs).geometry.area.astype(float)
|
|
78
|
-
df["building:levels_is_real"] = df["building:levels"].apply(lambda x: not pd.isna(x))
|
|
79
|
-
df["building:levels"] = df["building:levels"].fillna(1)
|
|
80
|
-
df["building:levels"] = pd.to_numeric(df["building:levels"], errors="coerce")
|
|
81
|
-
df = df.dropna(subset=["building:levels"])
|
|
82
|
-
df["building:levels"] = df["building:levels"].astype(int)
|
|
83
|
-
df[population_column] = np.nan
|
|
84
|
-
df.loc[df["is_living"] == 1, population_column] = (
|
|
85
|
-
df[df["is_living"] == 1]
|
|
86
|
-
.apply(
|
|
87
|
-
lambda row: (
|
|
88
|
-
3
|
|
89
|
-
if ((row["area"] <= 400) & (row["building:levels"] <= 2))
|
|
90
|
-
else (row["building:levels"] * row["area"] * 0.8 / area_per_person)
|
|
91
|
-
),
|
|
92
|
-
axis=1,
|
|
93
|
-
)
|
|
94
|
-
.round(0)
|
|
95
|
-
.astype(int)
|
|
96
|
-
)
|
|
97
|
-
return df
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def download_buildings(
|
|
101
|
-
osm_territory_id: int | None = None,
|
|
102
|
-
osm_territory_name: str | None = None,
|
|
103
|
-
terr_polygon: Polygon | MultiPolygon | None = None,
|
|
104
|
-
is_living_column: str = "is_living",
|
|
105
|
-
population_column: str = "approximate_pop",
|
|
106
|
-
area_per_person: float = 33,
|
|
107
|
-
) -> gpd.GeoDataFrame | None:
|
|
108
|
-
"""
|
|
109
|
-
Download building geometries and evaluate 'is_living' and 'population'
|
|
110
|
-
attributes for a specified territory from OpenStreetMap.
|
|
111
|
-
|
|
112
|
-
Parameters
|
|
113
|
-
----------
|
|
114
|
-
osm_territory_id : int, optional
|
|
115
|
-
The OpenStreetMap ID of the territory to download buildings for.
|
|
116
|
-
osm_territory_name : str, optional
|
|
117
|
-
The name of the territory to download buildings for.
|
|
118
|
-
terr_polygon : Polygon or MultiPolygon, optional
|
|
119
|
-
A Polygon or MultiPolygon geometry defining the territory to download buildings for.
|
|
120
|
-
is_living_column : str, optional
|
|
121
|
-
The name of the column indicating whether a building is residential (default is "is_living").
|
|
122
|
-
population_column : str, optional
|
|
123
|
-
The name of the column for storing estimated population (default is "approximate_pop").
|
|
124
|
-
area_per_person : float
|
|
125
|
-
The standart living space per person im m², (default is 33)
|
|
126
|
-
Returns
|
|
127
|
-
-------
|
|
128
|
-
gpd.GeoDataFrame or None
|
|
129
|
-
A GeoDataFrame containing building geometries and attributes, or None if no buildings are found.
|
|
130
|
-
|
|
131
|
-
Examples
|
|
132
|
-
--------
|
|
133
|
-
>>> buildings_df = download_buildings(osm_territory_name="Saint-Petersburg, Russia")
|
|
134
|
-
>>> buildings_df.head()
|
|
135
|
-
"""
|
|
136
|
-
polygon = get_boundary(osm_territory_id, osm_territory_name, terr_polygon)
|
|
137
|
-
|
|
138
|
-
logger.debug("Downloading buildings from OpenStreetMap and counting population...")
|
|
139
|
-
buildings = ox.features_from_polygon(polygon, tags={"building": True})
|
|
140
|
-
if not buildings.empty:
|
|
141
|
-
buildings = buildings.loc[
|
|
142
|
-
(buildings["geometry"].geom_type == "Polygon") | (buildings["geometry"].geom_type == "MultiPolygon")
|
|
143
|
-
]
|
|
144
|
-
if buildings.empty:
|
|
145
|
-
logger.warning("There are no buildings in the specified territory. Output GeoDataFrame is empty.")
|
|
146
|
-
return buildings
|
|
147
|
-
|
|
148
|
-
buildings[is_living_column] = buildings.apply(eval_is_living, axis=1)
|
|
149
|
-
buildings = eval_population(buildings, population_column, area_per_person)
|
|
150
|
-
buildings.reset_index(drop=True, inplace=True)
|
|
151
|
-
logger.debug("Done!")
|
|
152
|
-
return buildings[
|
|
153
|
-
[
|
|
154
|
-
"building",
|
|
155
|
-
"addr:street",
|
|
156
|
-
"addr:housenumber",
|
|
157
|
-
"amenity",
|
|
158
|
-
"area",
|
|
159
|
-
"name",
|
|
160
|
-
"building:levels",
|
|
161
|
-
"leisure",
|
|
162
|
-
"design:year",
|
|
163
|
-
is_living_column,
|
|
164
|
-
"building:levels_is_real",
|
|
165
|
-
population_column,
|
|
166
|
-
"geometry",
|
|
167
|
-
]
|
|
168
|
-
]
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: ObjectNat
|
|
3
|
-
Version: 0.2.7
|
|
4
|
-
Summary: ObjectNat is an open-source library created for geospatial analysis created by IDU team
|
|
5
|
-
License: BSD-3-Clause
|
|
6
|
-
Author: DDonnyy
|
|
7
|
-
Author-email: 63115678+DDonnyy@users.noreply.github.com
|
|
8
|
-
Requires-Python: >=3.10,<3.13
|
|
9
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Requires-Dist: geopandas (>=0.14.3,<0.15.0)
|
|
15
|
-
Requires-Dist: iduedu (>=0.2.2,<0.3.0)
|
|
16
|
-
Requires-Dist: networkx (>=3.3,<4.0)
|
|
17
|
-
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
18
|
-
Requires-Dist: pandarallel (>=1.6.5,<2.0.0)
|
|
19
|
-
Requires-Dist: pandas (>=2.2.0,<3.0.0)
|
|
20
|
-
Requires-Dist: population-restorator (>=0.2.3,<0.3.0)
|
|
21
|
-
Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
|
|
22
|
-
Requires-Dist: tqdm (>=4.66.2,<5.0.0)
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# ObjectNat - Meta Library
|
|
26
|
-
|
|
27
|
-
[](https://github.com/psf/black)
|
|
28
|
-
[](https://pypi.org/project/objectnat/)
|
|
29
|
-
|
|
30
|
-
- [РИДМИ (Russian)](README_ru.md)
|
|
31
|
-
<p align="center">
|
|
32
|
-
<img src="https://github.com/user-attachments/assets/d3878cce-8eba-4f96-8458-9a798d436120" alt="logo" width="400">
|
|
33
|
-
</p>
|
|
34
|
-
|
|
35
|
-
#### **ObjectNat** is an open-source library created for geospatial analysis created by **IDU team**
|
|
36
|
-
|
|
37
|
-
## ObjectNat Components
|
|
38
|
-
|
|
39
|
-
- [IduEdu](https://github.com/DDonnyy/IduEdu) : `IduEdu` provides graph functions
|
|
40
|
-
- [population-restorator](https://github.com/kanootoko/population-restorator) : `restorator` provides city resettlement
|
|
41
|
-
|
|
42
|
-
## Features and how to use
|
|
43
|
-
|
|
44
|
-
1. **[City graph from OSM (IduEdu)](./examples/get_any_graph.ipynb)** - Functions to assemble a road, pedestrian, and public transport graph
|
|
45
|
-
from OpenStreetMap (OSM) and creating Intermodal graph.
|
|
46
|
-
|
|
47
|
-
<img src="https://github.com/user-attachments/assets/8dc98da9-8462-415e-8cc8-bdfca788e206" alt="IntermodalGraph" height="250">
|
|
48
|
-
|
|
49
|
-
2. **[Adjacency matrix](./examples/calculate_adjacency_matrix.ipynb)** - Calculate adjacency matrix based on the provided graph and edge weight type
|
|
50
|
-
(time or distance). The intermodal graph can be obtained using the previous example.
|
|
51
|
-
|
|
52
|
-
3. **[Isochrones,transport accessibility](./examples/isochrone_generator.ipynb)** - Function for generating isochrones to
|
|
53
|
-
analyze transportation accessibility from specified starting coordinates. Isochrones can be constructed based on
|
|
54
|
-
pedestrian, automobile, or public transport graphs, or a combination thereof.
|
|
55
|
-
|
|
56
|
-
<img src="https://github.com/user-attachments/assets/37f308a5-db56-497d-b080-4edef3584fe5" alt="isochrones" height="250">
|
|
57
|
-
|
|
58
|
-
4. **[Population restoration](./examples/restore_population.ipynb)** - Function for resettling population into the provided
|
|
59
|
-
layer of residential buildings. This function distributes people among dwellings based on the total city population
|
|
60
|
-
and the living area of each house.
|
|
61
|
-
5. **[Service provision](./examples/calculate_provision.ipynb)** - Function for calculating the provision of residential buildings and population
|
|
62
|
-
with services.
|
|
63
|
-
|
|
64
|
-
<img src="https://github.com/user-attachments/assets/5f2b3c55-9a02-4d70-80f4-503b77023eda" alt="ProvisionSchools" height="250">
|
|
65
|
-
|
|
66
|
-
6. **[Visibility analysis](./examples/visibility_analysis.ipynb)** - Function to get a quick estimate of visibility from a
|
|
67
|
-
given point(s) to buildings within a given distance. Also, there is a visibility catchment area calculator for a large
|
|
68
|
-
urban area. This function is designed to work with at least 1000 points spaced 10-20 meters apart for optimal
|
|
69
|
-
results. Points can be generated using a road graph and random point distribution along edges.
|
|
70
|
-
|
|
71
|
-
<img src="https://github.com/user-attachments/assets/2927ac86-01e8-4b0e-9ea8-72ad81c13cf5" alt="visibility-from-point" height="250">
|
|
72
|
-
|
|
73
|
-
<img src="https://github.com/user-attachments/assets/b5b0d4b3-a02f-4ade-8772-475703cd6435" alt="visibility-catchment-area" height="250">
|
|
74
|
-
|
|
75
|
-
7. **[Noise simulation](./examples/noise_simulation.ipynb)** - Simulates noise propagation from a set of source points
|
|
76
|
-
considering obstacles, trees, and environmental factors.
|
|
77
|
-
**[Detailed information in Wiki](https://github.com/DDonnyy/ObjectNat/wiki/Noise-simulation)**
|
|
78
|
-
|
|
79
|
-
<img src="https://github.com/user-attachments/assets/dd185867-67c4-4d03-8905-d06dd1d36fb3" alt="noise_sim" height="250">
|
|
80
|
-
|
|
81
|
-
8. **[Point clusterization](./examples/point_clusterization.ipynb)** - Function to generate cluster polygons for given
|
|
82
|
-
points based on a specified minimum distance and minimum points per cluster. Optionally, calculate the relative ratio
|
|
83
|
-
between types of services within the clusters.
|
|
84
|
-
|
|
85
|
-
<img src="https://github.com/user-attachments/assets/2a9ad722-87d2-4954-9612-5ac3765aa824" alt="service-clusterization" height="250">
|
|
86
|
-
|
|
87
|
-
9. **[Living buildings from OSM](./examples/download_buildings_from_osm.ipynb)** - This function downloads building geometries from OpenStreetMap (OSM)
|
|
88
|
-
for a specified territory and assigns attributes to each building. Specifically, it determines whether a building
|
|
89
|
-
is residential (`is_living` attribute) and estimates the approximate number of inhabitants (`approximate_pop` attribute).
|
|
90
|
-
|
|
91
|
-
<img src="https://github.com/user-attachments/assets/d60dcd85-1a2e-4342-aae4-561aeda18858" alt="Living buildings" height="250">
|
|
92
|
-
|
|
93
|
-
## Installation
|
|
94
|
-
|
|
95
|
-
**ObjectNat** can be installed with ``pip``:
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
pip install ObjectNat
|
|
99
|
-
```
|
|
100
|
-
### Configuration changes
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
from objectnat import config
|
|
104
|
-
|
|
105
|
-
config.set_timeout(10) # Timeout for overpass queries
|
|
106
|
-
config.change_logger_lvl('INFO') # To mute all debug msgs
|
|
107
|
-
config.set_enable_tqdm(False) # To mute all tqdm's progress bars
|
|
108
|
-
config.set_overpass_url('http://your.overpass-api.de/interpreter/URL')
|
|
109
|
-
```
|
|
110
|
-
## Contacts
|
|
111
|
-
|
|
112
|
-
- [NCCR](https://actcognitive.org/) - National Center for Cognitive Research
|
|
113
|
-
- [IDU](https://idu.itmo.ru/) - Institute of Design and Urban Studies
|
|
114
|
-
- [Natalya Chichkova](https://t.me/nancy_nat) - project manager
|
|
115
|
-
- [Danila Oleynikov (Donny)](https://t.me/ddonny_dd) - lead software engineer
|
|
116
|
-
|
|
117
|
-
## Publications
|
|
118
|
-
|
objectnat-0.2.7.dist-info/RECORD
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
objectnat/__init__.py,sha256=OnDvrLPLEeYIE_9qOVYgMc-PkRzIqShtGxirguEXiRU,260
|
|
2
|
-
objectnat/_api.py,sha256=UX9XwHzpXbkPZksovT33kn5_g9r0qm_cGVbGLsBLoZg,746
|
|
3
|
-
objectnat/_config.py,sha256=sv13J3yMw1cmmkgPMf08zinLwPKYwXHJGfchqmurSg8,2268
|
|
4
|
-
objectnat/_version.py,sha256=ZQLNbaBb3Roa6Z5LEm--iZickCQhYJVSrDpqf4YBbzc,18
|
|
5
|
-
objectnat/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
objectnat/methods/balanced_buildings.py,sha256=hLT2QgmGWpROtnL8SJQIujeP6q9ou15yIdHpv66CfMs,2892
|
|
7
|
-
objectnat/methods/cluster_points_in_polygons.py,sha256=ANoPHB89Ih6SYUTs0VoYqW7zi9GVIytSOGySoQ3vby4,5073
|
|
8
|
-
objectnat/methods/coverage_zones.py,sha256=yMeK1DjneMAxxKv9busEKdAsP25xiJMcPCixlJCDI4s,2835
|
|
9
|
-
objectnat/methods/isochrones.py,sha256=EbcTMCz9W9_QzvL7Of_nXWVEMv_3CqqiEvLX0Z_pagY,6123
|
|
10
|
-
objectnat/methods/living_buildings_osm.py,sha256=DW7k-SWJA3Ot8qoE1FEsEH8PkB_uLQwd7KGVwfYFanE,5786
|
|
11
|
-
objectnat/methods/noise/__init__.py,sha256=7B05RT3g9NXzTBC9OLKkzlHF7EOdZfVs_QZJibTfusU,150
|
|
12
|
-
objectnat/methods/noise/noise_exceptions.py,sha256=nTav5kp6RNpi0kxD9cMULTApOuvAu9wEiX28fkLAnOc,634
|
|
13
|
-
objectnat/methods/noise/noise_init_data.py,sha256=Vp-R_yH7CgYqZEtbGAdr1iiIbgauReniLQ_a2TcszhY,503
|
|
14
|
-
objectnat/methods/noise/noise_reduce.py,sha256=B85ifAN_mHiBKJso-cZiSkj7588w2sA-ugGvEal4CBw,6885
|
|
15
|
-
objectnat/methods/noise/noise_sim.py,sha256=YWTzFnwIamsb9qlIZTWPNh26Z1S29uoS54ImHWYH8qg,20109
|
|
16
|
-
objectnat/methods/provision/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
objectnat/methods/provision/provision.py,sha256=YDhEbGQksfaLPmqt0GOl7zsBdOlCxXesznZQ40vODjY,4749
|
|
18
|
-
objectnat/methods/provision/provision_exceptions.py,sha256=lznEmlmZDzGIOtapZVqZDMutIi5eGbFuVCYeVa7VZWk,1687
|
|
19
|
-
objectnat/methods/provision/provision_model.py,sha256=lL-Oynm_0Z0PXbw2bnQ1gB1kmZmSR4pBmVtqWD_JQo4,14452
|
|
20
|
-
objectnat/methods/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
objectnat/methods/utils/geom_utils.py,sha256=HxC5xSDHFaTZW2HgUuXeRabGfDuz0J009hLVM60rLE4,2923
|
|
22
|
-
objectnat/methods/visibility_analysis.py,sha256=PolNPU9Qyc6DrOWUwZDY_ErODxrCsfrn-zfg1pIt7Po,21618
|
|
23
|
-
objectnat-0.2.7.dist-info/LICENSE.txt,sha256=yPEioMfTd7JAQgAU6J13inS1BSjwd82HFlRSoIb4My8,1498
|
|
24
|
-
objectnat-0.2.7.dist-info/METADATA,sha256=FRTcsM3_rR577RnhGAfRTb0g9A9rC1A0-15QwD6JUYY,6112
|
|
25
|
-
objectnat-0.2.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
26
|
-
objectnat-0.2.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|