ObjectNat 0.1.5__py3-none-any.whl → 0.2.1__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.

@@ -0,0 +1,325 @@
1
+ # pylint: disable=singleton-comparison
2
+ from typing import Tuple
3
+
4
+ import geopandas as gpd
5
+ import numpy as np
6
+ import pandas as pd
7
+ from shapely import LineString
8
+
9
+ from objectnat import config
10
+
11
+ from .provision_exceptions import CapacityKeyError, DemandKeyError
12
+
13
+ logger = config.logger
14
+
15
+ from pandarallel import pandarallel
16
+
17
+
18
+ class CityProvision:
19
+ """
20
+ Represents the logic for city provision calculations using a gravity or linear model.
21
+
22
+ Args:
23
+ services (InstanceOf[gpd.GeoDataFrame]): GeoDataFrame representing the services available in the city.
24
+ demanded_buildings (InstanceOf[gpd.GeoDataFrame]): GeoDataFrame representing the buildings with demands for services.
25
+ adjacency_matrix (InstanceOf[pd.DataFrame]): DataFrame representing the adjacency matrix between buildings.
26
+ threshold (int): Threshold value for the provision calculations.
27
+ calculation_type (str, optional): Type of calculation ("gravity" or "linear"). Defaults to "gravity".
28
+
29
+ Returns:
30
+ CityProvision: The CityProvision object.
31
+
32
+ Raises: KeyError: If the 'demand' column is missing in the provided 'demanded_buildings' GeoDataFrame,
33
+ or if the 'capacity' column is missing in the provided 'services' GeoDataFrame. ValueError: If the 'capacity'
34
+ column in 'services' or 'demand' column 'demanded_buildings' GeoDataFrame has no valid value.
35
+ """
36
+
37
+ _destination_matrix = None
38
+
39
+ def __init__(
40
+ self,
41
+ services: gpd.GeoDataFrame,
42
+ demanded_buildings: gpd.GeoDataFrame,
43
+ adjacency_matrix: pd.DataFrame,
44
+ threshold: int,
45
+ ):
46
+ self.services = self.ensure_services(services)
47
+ self.demanded_buildings = self.ensure_buildings(demanded_buildings)
48
+ self.adjacency_matrix = self.delete_useless_matrix_rows(adjacency_matrix.copy(), demanded_buildings, services)
49
+ self.threshold = threshold
50
+ self.check_crs(self.demanded_buildings, self.services)
51
+ pandarallel.initialize(progress_bar=False, verbose=0)
52
+
53
+ @staticmethod
54
+ def ensure_buildings(v: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
55
+ if "demand" not in v.columns:
56
+ raise DemandKeyError
57
+ v = v.copy()
58
+ v["demand_left"] = v["demand"]
59
+ return v
60
+
61
+ @staticmethod
62
+ def ensure_services(v: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
63
+ if "capacity" not in v.columns:
64
+ raise CapacityKeyError
65
+ v = v.copy()
66
+ v["capacity_left"] = v["capacity"]
67
+ return v
68
+
69
+ @staticmethod
70
+ def check_crs(demanded_buildings, services):
71
+ assert (
72
+ demanded_buildings.crs == services.crs
73
+ ), f"\nThe CRS in the provided geodataframes are different.\nBuildings CRS:{demanded_buildings.crs}\nServices CRS:{services.crs} \n"
74
+
75
+ @staticmethod
76
+ def delete_useless_matrix_rows(adjacency_matrix, demanded_buildings, services):
77
+ adjacency_matrix.index = adjacency_matrix.index.astype(int)
78
+
79
+ builds_indexes = set(demanded_buildings.index.astype(int).tolist())
80
+ rows = set(adjacency_matrix.index.astype(int).tolist())
81
+ dif = rows ^ builds_indexes
82
+ adjacency_matrix.drop(index=(list(dif)), axis=0, inplace=True)
83
+
84
+ service_indexes = set(services.index.astype(int).tolist())
85
+ columns = set(adjacency_matrix.columns.astype(int).tolist())
86
+ dif = columns ^ service_indexes
87
+ adjacency_matrix.drop(columns=(list(dif)), axis=0, inplace=True)
88
+ return adjacency_matrix
89
+
90
+ def get_provisions(self) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
91
+ self._destination_matrix = pd.DataFrame(
92
+ 0,
93
+ index=self.adjacency_matrix.columns,
94
+ columns=self.adjacency_matrix.index,
95
+ )
96
+ self.adjacency_matrix = self.adjacency_matrix.transpose()
97
+ logger.debug(
98
+ "Calculating provision from {} services to {} buildings.",
99
+ len(self.services),
100
+ len(self.demanded_buildings),
101
+ )
102
+ self.adjacency_matrix = self.adjacency_matrix.where(self.adjacency_matrix <= self.threshold * 3, np.inf)
103
+
104
+ self._destination_matrix = self._provision_loop_gravity(
105
+ self.demanded_buildings.copy(),
106
+ self.services.copy(),
107
+ self.adjacency_matrix.copy() + 1,
108
+ self.threshold,
109
+ self._destination_matrix.copy(),
110
+ )
111
+ _additional_options(
112
+ self.demanded_buildings,
113
+ self.services,
114
+ self.adjacency_matrix,
115
+ self._destination_matrix,
116
+ self.threshold,
117
+ )
118
+
119
+ # self.demanded_buildings['provision_value'] = self.demanded_buildings['provision_value'].fillna(0)
120
+ # self.services = self.services.fillna(0)
121
+
122
+ return (
123
+ self.demanded_buildings,
124
+ self.services,
125
+ _calc_links(
126
+ self._destination_matrix,
127
+ self.services,
128
+ self.demanded_buildings,
129
+ self.adjacency_matrix,
130
+ ),
131
+ )
132
+
133
+ def _provision_loop_gravity(
134
+ self,
135
+ houses_table: gpd.GeoDataFrame,
136
+ services_table: gpd.GeoDataFrame,
137
+ distance_matrix: pd.DataFrame,
138
+ selection_range,
139
+ destination_matrix: pd.DataFrame,
140
+ best_houses=0.9,
141
+ ):
142
+ def apply_function_based_on_size(df, func, axis, threshold=500):
143
+ if len(df) > threshold:
144
+ return df.parallel_apply(func, axis=axis)
145
+ else:
146
+ return df.apply(func, axis=axis)
147
+
148
+ def _calculate_flows_y(loc):
149
+ import numpy as np
150
+ import pandas as pd
151
+
152
+ c = services_table.loc[loc.name]["capacity_left"]
153
+ p = 1 / loc / loc
154
+ p = p / p.sum()
155
+ threshold = p.quantile(best_houses)
156
+ p = p[p >= threshold]
157
+ p = p / p.sum()
158
+ if p.sum() == 0:
159
+ return loc
160
+ rng = np.random.default_rng(seed=0)
161
+ r = pd.Series(0, p.index)
162
+ choice = np.unique(rng.choice(p.index, int(c), p=p.values), return_counts=True)
163
+ choice = r.add(pd.Series(choice[1], choice[0]), fill_value=0)
164
+
165
+ return choice
166
+
167
+ def _balance_flows_to_demands(loc):
168
+ import numpy as np
169
+ import pandas as pd
170
+
171
+ d = houses_table.loc[loc.name]["demand_left"]
172
+ loc = loc[loc > 0]
173
+ if loc.sum() > 0:
174
+ p = loc / loc.sum()
175
+ rng = np.random.default_rng(seed=0)
176
+ r = pd.Series(0, p.index)
177
+ choice = np.unique(rng.choice(p.index, int(d), p=p.values), return_counts=True)
178
+ choice = r.add(pd.Series(choice[1], choice[0]), fill_value=0)
179
+ choice = pd.Series(
180
+ data=np.minimum(loc.sort_index().values, choice.sort_index().values),
181
+ index=loc.sort_index().index,
182
+ )
183
+ return choice
184
+ return loc
185
+
186
+ temp_destination_matrix = apply_function_based_on_size(
187
+ distance_matrix, lambda x: _calculate_flows_y(x[x <= selection_range]), 1
188
+ )
189
+
190
+ temp_destination_matrix = temp_destination_matrix.fillna(0)
191
+
192
+ temp_destination_matrix = apply_function_based_on_size(temp_destination_matrix, _balance_flows_to_demands, 0)
193
+
194
+ temp_destination_matrix = temp_destination_matrix.fillna(0)
195
+ destination_matrix = destination_matrix.add(temp_destination_matrix, fill_value=0)
196
+
197
+ axis_1 = destination_matrix.sum(axis=1)
198
+ axis_0 = destination_matrix.sum(axis=0)
199
+
200
+ services_table["capacity_left"] = services_table["capacity"].subtract(axis_1, fill_value=0)
201
+ houses_table["demand_left"] = houses_table["demand"].subtract(axis_0, fill_value=0)
202
+
203
+ distance_matrix = distance_matrix.drop(
204
+ index=services_table[services_table["capacity_left"] == 0].index.values,
205
+ columns=houses_table[houses_table["demand_left"] == 0].index.values,
206
+ errors="ignore",
207
+ )
208
+
209
+ distance_matrix = distance_matrix.loc[~(distance_matrix == np.inf).all(axis=1)]
210
+ distance_matrix = distance_matrix.loc[:, ~(distance_matrix == np.inf).all(axis=0)]
211
+
212
+ selection_range += selection_range
213
+
214
+ if best_houses > 0.1:
215
+ best_houses -= 0.1
216
+ if best_houses <= 0.1:
217
+ best_houses = 0
218
+ if len(distance_matrix.columns) > 0 and len(distance_matrix.index) > 0:
219
+ return self._provision_loop_gravity(
220
+ houses_table, services_table, distance_matrix, selection_range, destination_matrix, best_houses
221
+ )
222
+ return destination_matrix
223
+
224
+
225
+ def _calc_links(
226
+ destination_matrix: pd.DataFrame,
227
+ services: gpd.GeoDataFrame,
228
+ buildings: gpd.GeoDataFrame,
229
+ distance_matrix: pd.DataFrame,
230
+ ):
231
+ def subfunc(loc):
232
+ try:
233
+ return [
234
+ {
235
+ "building_index": int(k),
236
+ "demand": int(v),
237
+ "service_index": int(loc.name),
238
+ }
239
+ for k, v in loc.to_dict().items()
240
+ ]
241
+ except:
242
+ return np.NaN
243
+
244
+ def subfunc_geom(loc):
245
+ return LineString(
246
+ (
247
+ buildings_["geometry"][loc["building_index"]],
248
+ services_["geometry"][loc["service_index"]],
249
+ )
250
+ )
251
+
252
+ buildings_ = buildings.copy()
253
+ services_ = services.copy()
254
+ buildings_.geometry = buildings_.representative_point()
255
+ services_.geometry = services_.representative_point()
256
+ flat_matrix = destination_matrix.transpose().apply(lambda x: subfunc(x[x > 0]), result_type="reduce")
257
+
258
+ distribution_links = gpd.GeoDataFrame(data=[item for sublist in list(flat_matrix) for item in sublist])
259
+
260
+ distribution_links["distance"] = distribution_links.apply(
261
+ lambda x: distance_matrix.loc[x["service_index"]][x["building_index"]],
262
+ axis=1,
263
+ result_type="reduce",
264
+ )
265
+
266
+ sel = distribution_links["building_index"].isin(buildings_.index.values) & distribution_links["service_index"].isin(
267
+ services_.index.values
268
+ )
269
+ sel = distribution_links.loc[sel[sel].index.values]
270
+ distribution_links = distribution_links.set_geometry(sel.apply(subfunc_geom, axis=1)).set_crs(buildings_.crs)
271
+ distribution_links["distance"] = distribution_links["distance"].astype(float).round(2)
272
+ return distribution_links
273
+
274
+
275
+ def _additional_options(
276
+ buildings,
277
+ services,
278
+ matrix,
279
+ destination_matrix,
280
+ normative_distance,
281
+ ):
282
+ buildings["avg_dist"] = 0
283
+ buildings["supplyed_demands_within"] = 0
284
+ buildings["supplyed_demands_without"] = 0
285
+ services["carried_capacity_within"] = 0
286
+ services["carried_capacity_without"] = 0
287
+ for i in range(len(destination_matrix)):
288
+ loc = destination_matrix.iloc[i]
289
+ distances_all = matrix.loc[loc.name]
290
+ distances = distances_all[distances_all <= normative_distance]
291
+ s = matrix.loc[loc.name] <= normative_distance
292
+ within = loc[s]
293
+ without = loc[~s]
294
+ within = within[within > 0]
295
+ without = without[without > 0]
296
+ buildings["avg_dist"] = (
297
+ buildings["avg_dist"]
298
+ .add(distances.multiply(within, fill_value=0), fill_value=0)
299
+ .add(distances_all.multiply(without, fill_value=0), fill_value=0)
300
+ )
301
+ buildings["demand_left"] = buildings["demand_left"].sub(within.add(without, fill_value=0), fill_value=0)
302
+ buildings["supplyed_demands_within"] = buildings["supplyed_demands_within"].add(within, fill_value=0)
303
+ buildings["supplyed_demands_without"] = buildings["supplyed_demands_without"].add(without, fill_value=0)
304
+
305
+ services.at[loc.name, "capacity_left"] = (
306
+ services.at[loc.name, "capacity_left"] - within.add(without, fill_value=0).sum()
307
+ )
308
+ services.at[loc.name, "carried_capacity_within"] = (
309
+ services.at[loc.name, "carried_capacity_within"] + within.sum()
310
+ )
311
+ services.at[loc.name, "carried_capacity_without"] = (
312
+ services.at[loc.name, "carried_capacity_without"] + without.sum()
313
+ )
314
+ buildings["avg_dist"] = (buildings["avg_dist"] / (buildings["demand"] - buildings["demand_left"])).astype(
315
+ np.float32
316
+ )
317
+ buildings["avg_dist"] = buildings.apply(
318
+ lambda x: np.nan if (x["demand"] == x["demand_left"]) else round(x["avg_dist"], 2), axis=1
319
+ )
320
+ buildings["provison_value"] = (buildings["supplyed_demands_within"] / buildings["demand"]).astype(float).round(2)
321
+ services["service_load"] = (services["capacity"] - services["capacity_left"]).astype(np.uint16)
322
+ buildings["supplyed_demands_within"] = buildings["supplyed_demands_within"].astype(np.uint16)
323
+ buildings["supplyed_demands_without"] = buildings["supplyed_demands_without"].astype(np.uint16)
324
+ services["carried_capacity_within"] = services["carried_capacity_within"].astype(np.uint16)
325
+ services["carried_capacity_without"] = services["carried_capacity_without"].astype(np.uint16)
@@ -0,0 +1,90 @@
1
+ from typing import Tuple
2
+
3
+ import geopandas as gpd
4
+ import numpy as np
5
+ import pandas as pd
6
+
7
+ from .city_provision import CityProvision
8
+
9
+
10
+ def get_service_provision(
11
+ buildings: gpd.GeoDataFrame,
12
+ adjacency_matrix: pd.DataFrame,
13
+ services: gpd.GeoDataFrame,
14
+ threshold: int,
15
+ ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
16
+ """Calculate load from buildings with demands on the given services using the distances matrix between them.
17
+
18
+ Args:
19
+ services (gpd.GeoDataFrame): GeoDataFrame of services
20
+ adjacency_matrix (pd.DataFrame): DataFrame representing the adjacency matrix
21
+ buildings (gpd.GeoDataFrame): GeoDataFrame of demanded buildings
22
+ threshold (int): Threshold value
23
+ Returns:
24
+ Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]: Tuple of GeoDataFrames representing provision
25
+ buildings, provision services, and provision links
26
+ """
27
+ provision_buildings, provision_services, provision_links = CityProvision(
28
+ services=services,
29
+ demanded_buildings=buildings,
30
+ adjacency_matrix=adjacency_matrix,
31
+ threshold=threshold,
32
+ ).get_provisions()
33
+ return provision_buildings, provision_services, provision_links
34
+
35
+
36
+ def clip_provision(
37
+ buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, selection_zone: gpd.GeoDataFrame
38
+ ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
39
+
40
+ assert (
41
+ selection_zone.crs == buildings.crs == services.crs == links.crs
42
+ ), f"CRS mismatch: buildings_crs:{buildings.crs}, links_crs:{links.crs} , services_crs:{services.crs}, selection_zone_crs:{selection_zone.crs}"
43
+
44
+ s = buildings.intersects(selection_zone.unary_union)
45
+ buildings = buildings.loc[s[s].index]
46
+ links = links[links["building_index"].isin(buildings.index.tolist())]
47
+ services_to_keep = set(links["service_index"].tolist())
48
+ services.drop(list(set(services.index.tolist()) - services_to_keep), inplace=True)
49
+ return buildings, services, links
50
+
51
+
52
+ def recalculate_links(
53
+ buildings: gpd.GeoDataFrame, services: gpd.GeoDataFrame, links: gpd.GeoDataFrame, new_max_dist: float
54
+ ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
55
+ buildings = buildings.copy()
56
+ services = services.copy()
57
+ links = links.copy()
58
+
59
+ max_dist = links["distance"].max()
60
+ assert new_max_dist <= max_dist, "New distance exceeds max links distance"
61
+
62
+ links_to_recalculate = links[links["distance"] > new_max_dist]
63
+ links_to_keep = links[links["distance"] <= new_max_dist]
64
+
65
+ free_demand = links_to_recalculate.groupby("building_index").agg({"demand": list, "distance": list})
66
+ free_demand["distance"] = free_demand.apply(
67
+ lambda x: sum((x1 * x2) for x1, x2 in zip(x.demand, x.distance)), axis=1
68
+ )
69
+ free_demand["demand"] = free_demand["demand"].apply(sum)
70
+ free_demand = free_demand.reindex(buildings.index, fill_value=0)
71
+ new_sum_time = (buildings["supplyed_demands_within"] + buildings["supplyed_demands_without"]) * buildings[
72
+ "avg_dist"
73
+ ] - free_demand["distance"]
74
+
75
+ buildings["demand_left"] = buildings["demand_left"] + free_demand["demand"]
76
+ buildings["supplyed_demands_without"] = buildings["supplyed_demands_without"] - free_demand["demand"]
77
+ buildings["avg_dist"] = new_sum_time / (
78
+ buildings["supplyed_demands_without"] + buildings["supplyed_demands_within"]
79
+ )
80
+ buildings["avg_dist"] = buildings.apply(
81
+ lambda x: np.nan if (x["demand"] == x["demand_left"]) else round(x["avg_dist"], 2), axis=1
82
+ )
83
+
84
+ free_capacity = links_to_recalculate.groupby("service_index").agg({"demand": "sum"})
85
+ free_capacity = free_capacity.reindex(services.index, fill_value=0)
86
+ services["capacity_left"] = services["capacity_left"] + free_capacity["demand"]
87
+ services["carried_capacity_without"] = services["carried_capacity_without"] - free_capacity["demand"]
88
+ services["service_load"] = services["service_load"] - free_capacity["demand"]
89
+
90
+ return buildings, services, links_to_keep
@@ -0,0 +1,59 @@
1
+ class CapacityKeyError(KeyError):
2
+ def __init__(self, *args):
3
+ if args:
4
+ self.message = args[0]
5
+ else:
6
+ self.message = None
7
+
8
+ def __str__(self):
9
+ if self.message:
10
+ return "CapacityKeyError, {0} ".format(self.message)
11
+
12
+ return (
13
+ "Column 'capacity' was not found in provided 'services' GeoDataFrame. This attribute "
14
+ "corresponds to the total capacity for each service."
15
+ )
16
+
17
+
18
+ class CapacityValueError(ValueError):
19
+ def __init__(self, *args):
20
+ if args:
21
+ self.message = args[0]
22
+ else:
23
+ self.message = None
24
+
25
+ def __str__(self):
26
+ if self.message:
27
+ return "CapacityValueError, {0} ".format(self.message)
28
+
29
+ return "Column 'capacity' in 'services' GeoDataFrame has no valid value."
30
+
31
+
32
+ class DemandKeyError(KeyError):
33
+ def __init__(self, *args):
34
+ if args:
35
+ self.message = args[0]
36
+ else:
37
+ self.message = None
38
+
39
+ def __str__(self):
40
+ if self.message:
41
+ return "DemandKeyError, {0} ".format(self.message)
42
+
43
+ return (
44
+ "The column 'demand' was not found in the provided 'demanded_buildings' GeoDataFrame. "
45
+ "This attribute corresponds to the number of demands for the selected service in each building."
46
+ )
47
+
48
+
49
+ class DemandValueError(ValueError):
50
+ def __init__(self, *args):
51
+ if args:
52
+ self.message = args[0]
53
+ else:
54
+ self.message = None
55
+
56
+ def __str__(self):
57
+ if self.message:
58
+ return "DemandValueError, {0} ".format(self.message)
59
+ return "Column 'demand' in 'demanded_buildings' GeoDataFrame has no valid value."
@@ -4,12 +4,15 @@ from multiprocessing import cpu_count
4
4
  import geopandas as gpd
5
5
  import numpy as np
6
6
  import pandas as pd
7
- from loguru import logger
8
7
  from pandarallel import pandarallel
9
8
  from shapely import LineString, MultiPolygon, Point, Polygon
10
9
  from shapely.ops import polygonize, unary_union
11
10
  from tqdm.contrib.concurrent import process_map
12
11
 
12
+ from objectnat import config
13
+
14
+ logger = config.logger
15
+
13
16
 
14
17
  def get_visibility_accurate(point_from: Point, obstacles: gpd.GeoDataFrame, view_distance) -> Polygon:
15
18
  """
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ObjectNat
3
- Version: 0.1.5
3
+ Version: 0.2.1
4
4
  Summary: ObjectNat is an open-source library created for geospatial analysis created by IDU team
5
5
  License: BSD-3-Clause
6
- Author: Danila
6
+ Author: DDonnyy
7
7
  Author-email: 63115678+DDonnyy@users.noreply.github.com
8
- Requires-Python: >=3.10,<4.0
8
+ Requires-Python: >=3.10,<3.13
9
9
  Classifier: License :: OSI Approved :: BSD License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
- Requires-Dist: dongraphio (>=0.3.13,<0.4.0)
15
14
  Requires-Dist: geopandas (>=0.14.3,<0.15.0)
15
+ Requires-Dist: iduedu (>=0.1.4,<0.2.0)
16
16
  Requires-Dist: joblib (>=1.4.2,<2.0.0)
17
17
  Requires-Dist: networkit (>=11.0,<12.0)
18
18
  Requires-Dist: networkx (>=3.2.1,<4.0.0)
@@ -20,7 +20,7 @@ Requires-Dist: numpy (>=1.23.5,<2.0.0)
20
20
  Requires-Dist: pandarallel (>=1.6.5,<2.0.0)
21
21
  Requires-Dist: pandas (>=2.2.0,<3.0.0)
22
22
  Requires-Dist: population-restorator (>=0.2.3,<0.3.0)
23
- Requires-Dist: provisio (>=0.1.7,<0.2.0)
23
+ Requires-Dist: pulp (>=2.8.0,<3.0.0)
24
24
  Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
25
25
  Requires-Dist: tqdm (>=4.66.2,<5.0.0)
26
26
  Description-Content-Type: text/markdown
@@ -28,59 +28,64 @@ Description-Content-Type: text/markdown
28
28
  # ObjectNat - Meta Library
29
29
 
30
30
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
31
+ [![PyPI version](https://img.shields.io/pypi/v/objectnat.svg)](https://pypi.org/project/objectnat/)
31
32
 
33
+ - [РИДМИ (Russian)](README_ru.md)
32
34
  <p align="center">
33
- <img src="https://i.ibb.co/FWtHNQv/logo.png" alt="logo" width="400">
35
+ <img src="https://github.com/user-attachments/assets/d3878cce-8eba-4f96-8458-9a798d436120" alt="logo" width="400">
34
36
  </p>
35
37
 
36
38
  #### **ObjectNat** is an open-source library created for geospatial analysis created by **IDU team**
37
39
 
38
40
  ## ObjectNat Components
39
41
 
40
- - [dongraphio](https://github.com/DDonnyy/dongraphio) : `dongraphio` provides graph functions
41
- - [provisio](https://github.com/DDonnyy/provisio) : `provisio` provides main provisio fuctions
42
+ - [IduEdu](https://github.com/DDonnyy/IduEdu) : `IduEdu` provides graph functions
42
43
  - [population-restorator](https://github.com/kanootoko/population-restorator) : `restorator` provides city resettlement
43
44
 
44
45
  ## Features and how to use
45
46
 
46
- 1. **[City graph from OSM](./examples/graph_generator.ipynb)** - Function to assemble a road, pedestrian, and public
47
- transport graph from OpenStreetMap (OSM) and creating Intermodal graph.
47
+ 1. **[City graph from OSM (IduEdu)](https://github.com/DDonnyy/IduEdu/blob/main/examples/get_any_graph.ipynb)** - Functions to assemble a road, pedestrian,
48
+ and public transport graph from OpenStreetMap (OSM) and creating Intermodal graph.
48
49
 
49
- <img src="https://i.ibb.co/VpsPgL1/Graph-generator-v1.webp" alt="Graph-generator-v1" height="250">
50
+ <img src="https://github.com/user-attachments/assets/8dc98da9-8462-415e-8cc8-bdfca788e206" alt="IntermodalGraph" height="250">
50
51
 
51
52
  2. **[Adjacency matrix](./examples/calculate_adjacency_matrix.ipynb)** - Calculate adjacency matrix based on the provided
52
53
  graph and edge weight type (time or distance). The intermodal graph can be obtained using the previous example.
54
+
53
55
  3. **[Isochrones,transport accessibility](./examples/isochrone_generator.ipynb)** - Function for generating isochrones to
54
56
  analyze transportation accessibility from specified starting coordinates. Isochrones can be constructed based on
55
57
  pedestrian, automobile, or public transport graphs, or a combination thereof.
56
58
 
57
- <img src="https://i.ibb.co/QM0tmZ2/isochrones-from-2-points.webp" alt="isochrones-from-2-points" height="250">
59
+ <img src="https://github.com/user-attachments/assets/37f308a5-db56-497d-b080-4edef3584fe5" alt="isochrones" height="250">
58
60
 
59
61
  4. **[Population restoration](./examples/restore_population.ipynb)** - Function for resettling population into the provided
60
62
  layer of residential buildings. This function distributes people among dwellings based on the total city population
61
63
  and the living area of each house.
62
64
  5. **[Service provision](./examples/calculate_provision.ipynb)** - Function for calculating the provision of residential
63
- buildings and population with services. In case of missing data, this function utilizes previously described
64
- functionality to retrieve the necessary information.
65
+ buildings and population with services.
66
+
67
+ <img src="https://github.com/user-attachments/assets/5f2b3c55-9a02-4d70-80f4-503b77023eda" alt="ProvisionSchools" height="250">
65
68
 
66
- <img src="https://i.ibb.co/CW7Xj5F/Burger-Provision5min.webp" alt="Burger-Provision5min" height="250">
67
-
68
69
  6. **[Visibility analysis](./examples/visibility_analysis.ipynb)** - Function to get a quick estimate of visibility from a
69
70
  given point(s) to buildings within a given distance. Also, there is a visibility catchment area calculator for a
70
71
  large
71
72
  urban area. This function is designed to work with at least 1000 points spaced 10-20 meters apart for optimal
72
73
  results. Points can be generated using a road graph and random point distribution along edges.
73
74
 
74
- <img src="https://i.ibb.co/LxcGTfN/visibility-from-point.webp" alt="visibility-from-point" height="250">
75
-
76
- <img src="https://i.ibb.co/zNRzXc5/visibility-catchment-area.webp" alt="visibility-catchment-area" height="250">
75
+ <img src="https://github.com/user-attachments/assets/2927ac86-01e8-4b0e-9ea8-72ad81c13cf5" alt="visibility-from-point" height="250">
77
76
 
77
+ <img src="https://github.com/user-attachments/assets/b5b0d4b3-a02f-4ade-8772-475703cd6435" alt="visibility-catchment-area" height="250">
78
+
78
79
  7. **[Point clusterization](./examples/point_clusterization.ipynb)** - Function to generate cluster polygons for given
79
80
  points based on a specified minimum distance and minimum points per cluster. Optionally, calculate the relative ratio
80
81
  between types of services within the clusters.
81
82
 
82
- <img src="https://i.ibb.co/fF3c4YC/service-clusterization.webp" alt="service-clusterization" height="250">
83
-
83
+ <img src="https://github.com/user-attachments/assets/2a9ad722-87d2-4954-9612-5ac3765aa824" alt="service-clusterization" height="250">
84
+
85
+ 8. **[Living buildings from OSM](./examples/download_buildings_from_osm.ipynb)** - This function downloads building geometries from OpenStreetMap (OSM) for a specified territory and assigns attributes to each building. Specifically, it determines whether a building is residential (`is_living` attribute) and estimates the approximate number of inhabitants (`approximate_pop` attribute).
86
+
87
+ <img src="https://github.com/user-attachments/assets/d60dcd85-1a2e-4342-aae4-561aeda18858" alt="Living buildings" height="250">
88
+
84
89
  ## Installation
85
90
 
86
91
  **ObjectNat** can be installed with ``pip``:
@@ -88,7 +93,16 @@ Description-Content-Type: text/markdown
88
93
  ```
89
94
  pip install ObjectNat
90
95
  ```
96
+ ### Configuration changes
91
97
 
98
+ ```python
99
+ from objectnat import config
100
+
101
+ config.set_timeout(10) # Timeout for overpass queries
102
+ config.change_logger_lvl('INFO') # To mute all debug msgs
103
+ config.set_enable_tqdm(False) # To mute all tqdm's progress bars
104
+ config.set_overpass_url('http://your.overpass-api.de/interpreter/URL')
105
+ ```
92
106
  ## Contacts
93
107
 
94
108
  - [NCCR](https://actcognitive.org/) - National
@@ -0,0 +1,21 @@
1
+ objectnat/__init__.py,sha256=OnDvrLPLEeYIE_9qOVYgMc-PkRzIqShtGxirguEXiRU,260
2
+ objectnat/_api.py,sha256=oiEO2P-tv6AMDdNoT8d0BWMmgeUJa4bhzGDTU2BWTXI,704
3
+ objectnat/_config.py,sha256=t4nv83Tj4fwYjdzwUh0bA8b_12DqL-GlEVfKaG_hccg,2107
4
+ objectnat/_version.py,sha256=Vdi6OffDRorPQeWjvXo2MPbidl7CNworxvziT78bjl0,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=2oHK-_CauEz8dZX6r-UGEopkqdUENLGaJPpMTuVV_o8,4678
8
+ objectnat/methods/coverage_zones.py,sha256=yMeK1DjneMAxxKv9busEKdAsP25xiJMcPCixlJCDI4s,2835
9
+ objectnat/methods/isochrones.py,sha256=CeNTVpUnlITaacamB5mJQjnbphXckC1FJ0L1EThswhU,6111
10
+ objectnat/methods/living_buildings_osm.py,sha256=pHyeDSKhs4j05Wr3Z_QBxLfLbiZpbwdj_SXz7qQ7V2M,6041
11
+ objectnat/methods/provision/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ objectnat/methods/provision/city_provision.py,sha256=71Vg2tZie_C2paeFtbf8-eeQ8H7P3dECHo108bVEm0Q,13026
13
+ objectnat/methods/provision/provision.py,sha256=BrwfKcGoRwNcLL9fZ6mU9m6kMuGnAIgxGHfXdgHA39o,4057
14
+ objectnat/methods/provision/provision_exceptions.py,sha256=-TK4A-vacUuzlPJGSt2YyawRwKDLCZFlAbuIvIf1FnY,1723
15
+ objectnat/methods/visibility_analysis.py,sha256=__S01m4YcIZbUcr6Umzvr4NpaCsajXxKNcfJm3zquVY,20690
16
+ objectnat/utils/__init__.py,sha256=w8R5V_Ws_GUt4hLwpudMgjXvocG4vCxWSzVw_jTReQ4,44
17
+ objectnat/utils/utils.py,sha256=_vbCW-XTHwZOR3yNlzf_vgNwbYwonhGlduSznGufEgs,638
18
+ objectnat-0.2.1.dist-info/LICENSE.txt,sha256=yPEioMfTd7JAQgAU6J13inS1BSjwd82HFlRSoIb4My8,1498
19
+ objectnat-0.2.1.dist-info/METADATA,sha256=ajah19veqGq4DwU3l9UNIyBbE04MRb9_VL6TD_vRHEI,5923
20
+ objectnat-0.2.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
21
+ objectnat-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any