ObjectNat 0.2.1__py3-none-any.whl → 0.2.2__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/_version.py CHANGED
@@ -1 +1 @@
1
- VERSION = "0.2.1"
1
+ VERSION = "0.2.2"
@@ -21,10 +21,10 @@ def _get_cluster(services_select, min_dist, min_point, method):
21
21
  return services_select
22
22
 
23
23
 
24
- def _get_service_ratio(loc):
24
+ def _get_service_ratio(loc, service_code_column):
25
25
  all_services = loc.shape[0]
26
- loc["service_code"] = loc["service_code"].astype(str)
27
- services_count = loc.groupby("service_code").size()
26
+ loc[service_code_column] = loc[service_code_column].astype(str)
27
+ services_count = loc.groupby(service_code_column).size()
28
28
  return (services_count / all_services).round(2)
29
29
 
30
30
 
@@ -33,6 +33,7 @@ def get_clusters_polygon(
33
33
  min_dist: float | int = 100,
34
34
  min_point: int = 5,
35
35
  method: Literal["DBSCAN", "HDBSCAN"] = "HDBSCAN",
36
+ service_code_column: str = "service_code",
36
37
  ) -> tuple[gpd.GeoDataFrame, gpd.GeoDataFrame]:
37
38
  """
38
39
  Generate cluster polygons for given points based on a specified minimum distance and minimum points per cluster.
@@ -49,7 +50,8 @@ def get_clusters_polygon(
49
50
  Minimum number of points required to form a cluster. Defaults to 5.
50
51
  method : Literal["DBSCAN", "HDBSCAN"], optional
51
52
  The clustering method to use. Must be either "DBSCAN" or "HDBSCAN". Defaults to "HDBSCAN".
52
-
53
+ service_code_column : str, optional
54
+ Column, containing service type for relative ratio in clasterized polygons. Defaults to "service_code".
53
55
  Returns
54
56
  -------
55
57
  tuple[gpd.GeoDataFrame, gpd.GeoDataFrame]
@@ -72,17 +74,19 @@ def get_clusters_polygon(
72
74
 
73
75
  services_select = _get_cluster(points, min_dist, min_point, method)
74
76
 
75
- if "service_code" not in points.columns:
77
+ if service_code_column not in points.columns:
76
78
  logger.warning(
77
- "No 'service_code' column in provided GeoDataFrame, cluster polygons will be without relative ratio."
79
+ f"No {service_code_column} column in provided GeoDataFrame, cluster polygons will be without relative ratio"
78
80
  )
79
- points["service_code"] = 1
81
+ points[service_code_column] = service_code_column
80
82
 
81
83
  services_normal = services_select[services_select["cluster"] != -1]
82
84
  services_outlier = services_select[services_select["cluster"] == -1]
83
85
 
84
86
  if len(services_normal) > 0:
85
- cluster_service = services_normal.groupby("cluster", group_keys=True).apply(_get_service_ratio)
87
+ cluster_service = services_normal.groupby("cluster", group_keys=True).apply(
88
+ _get_service_ratio, service_code_column=service_code_column
89
+ )
86
90
  if isinstance(cluster_service, pd.Series):
87
91
  cluster_service = cluster_service.unstack(level=1, fill_value=0)
88
92
 
@@ -98,7 +102,9 @@ def get_clusters_polygon(
98
102
  new_clusters = list(range(clusters_outlier, clusters_outlier + len(services_outlier)))
99
103
  services_outlier.loc[:, "cluster"] = new_clusters
100
104
 
101
- cluster_service = services_outlier.groupby("cluster", group_keys=True).apply(_get_service_ratio)
105
+ cluster_service = services_outlier.groupby("cluster", group_keys=True).apply(
106
+ _get_service_ratio, service_code_column=service_code_column
107
+ )
102
108
  if isinstance(cluster_service, pd.Series):
103
109
  cluster_service = cluster_service.unstack(level=1, fill_value=0)
104
110
 
@@ -45,16 +45,19 @@ def get_accessibility_isochrones(
45
45
  -------
46
46
  tuple[gpd.GeoDataFrame, gpd.GeoDataFrame | None, gpd.GeoDataFrame | None]
47
47
  A tuple containing:
48
- - isochrones : GeoDataFrame with the calculated isochrone geometries.
49
- - public transport stops (if applicable) : GeoDataFrame with public transport stops within the isochrone, or None if not applicable.
50
- - public transport routes (if applicable) : GeoDataFrame with public transport routes within the isochrone, or None if not applicable.
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.
51
54
 
52
55
  Examples
53
56
  --------
54
57
  >>> from iduedu import get_intermodal_graph
55
58
  >>> graph = get_intermodal_graph(polygon=my_territory_polygon)
56
59
  >>> points = gpd.GeoDataFrame(geometry=[Point(30.33, 59.95)], crs=4326).to_crs(graph.graph['crs'])
57
- >>> isochrones, pt_stops, pt_routes = get_accessibility_isochrones(points, weight_value=15, weight_type="time_min", graph_nx=my_graph)
60
+ >>> isochrones, pt_stops, pt_routes = get_accessibility_isochrones(points,weight_value=15, weight_type="time_min", graph_nx=my_graph)
58
61
 
59
62
  """
60
63
 
@@ -31,7 +31,7 @@ def eval_is_living(row: gpd.GeoSeries):
31
31
  >>> buildings = download_buildings(osm_territory_id=421007)
32
32
  >>> buildings['is_living'] = buildings.apply(eval_is_living, axis=1)
33
33
  """
34
- if row["building"] in (
34
+ return row["building"] in (
35
35
  "apartments",
36
36
  "house",
37
37
  "residential",
@@ -41,10 +41,7 @@ def eval_is_living(row: gpd.GeoSeries):
41
41
  "bungalow",
42
42
  "cabin",
43
43
  "farm",
44
- ):
45
- return True
46
- else:
47
- return False
44
+ )
48
45
 
49
46
 
50
47
  def eval_population(source: gpd.GeoDataFrame, population_column: str, area_per_person: float = 33):
@@ -111,7 +108,8 @@ def download_buildings(
111
108
  area_per_person: float = 33,
112
109
  ) -> gpd.GeoDataFrame | None:
113
110
  """
114
- Download building geometries and evaluate 'is_living' and 'population' attributes for a specified territory from OpenStreetMap.
111
+ Download building geometries and evaluate 'is_living' and 'population'
112
+ attributes for a specified territory from OpenStreetMap.
115
113
 
116
114
  Parameters
117
115
  ----------
@@ -146,27 +144,27 @@ def download_buildings(
146
144
  (buildings["geometry"].geom_type == "Polygon") | (buildings["geometry"].geom_type == "MultiPolygon")
147
145
  ]
148
146
  if buildings.empty:
149
- logger.warning(f"There are no buildings in the specified territory. Output GeoDataFrame is empty.")
147
+ logger.warning("There are no buildings in the specified territory. Output GeoDataFrame is empty.")
150
148
  return buildings
151
- else:
152
- buildings[is_living_column] = buildings.apply(eval_is_living, axis=1)
153
- buildings = eval_population(buildings, population_column, area_per_person)
154
- buildings.reset_index(drop=True, inplace=True)
155
- logger.debug("Done!")
156
- return buildings[
157
- [
158
- "building",
159
- "addr:street",
160
- "addr:housenumber",
161
- "amenity",
162
- "area",
163
- "name",
164
- "building:levels",
165
- "leisure",
166
- "design:year",
167
- is_living_column,
168
- "building:levels_is_real",
169
- population_column,
170
- "geometry",
171
- ]
149
+
150
+ buildings[is_living_column] = buildings.apply(eval_is_living, axis=1)
151
+ buildings = eval_population(buildings, population_column, area_per_person)
152
+ buildings.reset_index(drop=True, inplace=True)
153
+ logger.debug("Done!")
154
+ return buildings[
155
+ [
156
+ "building",
157
+ "addr:street",
158
+ "addr:housenumber",
159
+ "amenity",
160
+ "area",
161
+ "name",
162
+ "building:levels",
163
+ "leisure",
164
+ "design:year",
165
+ is_living_column,
166
+ "building:levels_is_real",
167
+ population_column,
168
+ "geometry",
172
169
  ]
170
+ ]
@@ -5,15 +5,13 @@ import geopandas as gpd
5
5
  import numpy as np
6
6
  import pandas as pd
7
7
  from shapely import LineString
8
-
8
+ from pandarallel import pandarallel
9
9
  from objectnat import config
10
10
 
11
11
  from .provision_exceptions import CapacityKeyError, DemandKeyError
12
12
 
13
13
  logger = config.logger
14
14
 
15
- from pandarallel import pandarallel
16
-
17
15
 
18
16
  class CityProvision:
19
17
  """
@@ -45,7 +43,7 @@ class CityProvision:
45
43
  ):
46
44
  self.services = self.ensure_services(services)
47
45
  self.demanded_buildings = self.ensure_buildings(demanded_buildings)
48
- self.adjacency_matrix = self.delete_useless_matrix_rows(adjacency_matrix.copy(), demanded_buildings, services)
46
+ self.adjacency_matrix = self.delete_useless_matrix_rows_columns(adjacency_matrix, demanded_buildings, services)
49
47
  self.threshold = threshold
50
48
  self.check_crs(self.demanded_buildings, self.services)
51
49
  pandarallel.initialize(progress_bar=False, verbose=0)
@@ -54,7 +52,6 @@ class CityProvision:
54
52
  def ensure_buildings(v: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
55
53
  if "demand" not in v.columns:
56
54
  raise DemandKeyError
57
- v = v.copy()
58
55
  v["demand_left"] = v["demand"]
59
56
  return v
60
57
 
@@ -62,7 +59,6 @@ class CityProvision:
62
59
  def ensure_services(v: gpd.GeoDataFrame) -> gpd.GeoDataFrame:
63
60
  if "capacity" not in v.columns:
64
61
  raise CapacityKeyError
65
- v = v.copy()
66
62
  v["capacity_left"] = v["capacity"]
67
63
  return v
68
64
 
@@ -73,7 +69,7 @@ class CityProvision:
73
69
  ), f"\nThe CRS in the provided geodataframes are different.\nBuildings CRS:{demanded_buildings.crs}\nServices CRS:{services.crs} \n"
74
70
 
75
71
  @staticmethod
76
- def delete_useless_matrix_rows(adjacency_matrix, demanded_buildings, services):
72
+ def delete_useless_matrix_rows_columns(adjacency_matrix, demanded_buildings, services):
77
73
  adjacency_matrix.index = adjacency_matrix.index.astype(int)
78
74
 
79
75
  builds_indexes = set(demanded_buildings.index.astype(int).tolist())
@@ -142,12 +138,11 @@ class CityProvision:
142
138
  def apply_function_based_on_size(df, func, axis, threshold=500):
143
139
  if len(df) > threshold:
144
140
  return df.parallel_apply(func, axis=axis)
145
- else:
146
- return df.apply(func, axis=axis)
141
+ return df.apply(func, axis=axis)
147
142
 
148
143
  def _calculate_flows_y(loc):
149
- import numpy as np
150
- import pandas as pd
144
+ import numpy as np # pylint: disable=redefined-outer-name,reimported,import-outside-toplevel
145
+ import pandas as pd # pylint: disable=redefined-outer-name,reimported,import-outside-toplevel
151
146
 
152
147
  c = services_table.loc[loc.name]["capacity_left"]
153
148
  p = 1 / loc / loc
@@ -165,8 +160,8 @@ class CityProvision:
165
160
  return choice
166
161
 
167
162
  def _balance_flows_to_demands(loc):
168
- import numpy as np
169
- import pandas as pd
163
+ import numpy as np # pylint: disable=redefined-outer-name,reimported,import-outside-toplevel
164
+ import pandas as pd # pylint: disable=redefined-outer-name,reimported,import-outside-toplevel
170
165
 
171
166
  d = houses_table.loc[loc.name]["demand_left"]
172
167
  loc = loc[loc > 0]
@@ -228,6 +223,11 @@ def _calc_links(
228
223
  buildings: gpd.GeoDataFrame,
229
224
  distance_matrix: pd.DataFrame,
230
225
  ):
226
+ buildings_ = buildings.copy()
227
+ services_ = services.copy()
228
+ buildings_.geometry = buildings_.representative_point()
229
+ services_.geometry = services_.representative_point()
230
+
231
231
  def subfunc(loc):
232
232
  try:
233
233
  return [
@@ -244,15 +244,11 @@ def _calc_links(
244
244
  def subfunc_geom(loc):
245
245
  return LineString(
246
246
  (
247
- buildings_["geometry"][loc["building_index"]],
248
- services_["geometry"][loc["service_index"]],
247
+ buildings_.geometry[loc["building_index"]],
248
+ services_.geometry[loc["service_index"]],
249
249
  )
250
250
  )
251
251
 
252
- buildings_ = buildings.copy()
253
- services_ = services.copy()
254
- buildings_.geometry = buildings_.representative_point()
255
- services_.geometry = services_.representative_point()
256
252
  flat_matrix = destination_matrix.transpose().apply(lambda x: subfunc(x[x > 0]), result_type="reduce")
257
253
 
258
254
  distribution_links = gpd.GeoDataFrame(data=[item for sublist in list(flat_matrix) for item in sublist])
@@ -12,6 +12,8 @@ def get_service_provision(
12
12
  adjacency_matrix: pd.DataFrame,
13
13
  services: gpd.GeoDataFrame,
14
14
  threshold: int,
15
+ buildings_demand_column: str = "demand",
16
+ services_capacity_column: str = "capacity",
15
17
  ) -> Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]:
16
18
  """Calculate load from buildings with demands on the given services using the distances matrix between them.
17
19
 
@@ -20,10 +22,18 @@ def get_service_provision(
20
22
  adjacency_matrix (pd.DataFrame): DataFrame representing the adjacency matrix
21
23
  buildings (gpd.GeoDataFrame): GeoDataFrame of demanded buildings
22
24
  threshold (int): Threshold value
25
+ buildings_demand_column (str): column name of buildings demands
26
+ services_capacity_column (str): column name of services capacity
23
27
  Returns:
24
28
  Tuple[gpd.GeoDataFrame, gpd.GeoDataFrame, gpd.GeoDataFrame]: Tuple of GeoDataFrames representing provision
25
29
  buildings, provision services, and provision links
26
30
  """
31
+ buildings = buildings.copy()
32
+ services = services.copy()
33
+ adjacency_matrix = adjacency_matrix.copy()
34
+ buildings["demand"] = buildings[buildings_demand_column]
35
+ services["capacity"] = services[services_capacity_column]
36
+
27
37
  provision_buildings, provision_services, provision_links = CityProvision(
28
38
  services=services,
29
39
  demanded_buildings=buildings,
@@ -40,6 +50,9 @@ def clip_provision(
40
50
  assert (
41
51
  selection_zone.crs == buildings.crs == services.crs == links.crs
42
52
  ), f"CRS mismatch: buildings_crs:{buildings.crs}, links_crs:{links.crs} , services_crs:{services.crs}, selection_zone_crs:{selection_zone.crs}"
53
+ buildings = buildings.copy()
54
+ links = links.copy()
55
+ services = services.copy()
43
56
 
44
57
  s = buildings.intersects(selection_zone.unary_union)
45
58
  buildings = buildings.loc[s[s].index]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ObjectNat
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: ObjectNat is an open-source library created for geospatial analysis created by IDU team
5
5
  License: BSD-3-Clause
6
6
  Author: DDonnyy
@@ -44,7 +44,7 @@ Description-Content-Type: text/markdown
44
44
 
45
45
  ## Features and how to use
46
46
 
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,
47
+ 1. **[City graph from OSM (IduEdu)](./examples/get_any_graph.ipynb)** - Functions to assemble a road, pedestrian,
48
48
  and public transport graph from OpenStreetMap (OSM) and creating Intermodal graph.
49
49
 
50
50
  <img src="https://github.com/user-attachments/assets/8dc98da9-8462-415e-8cc8-bdfca788e206" alt="IntermodalGraph" height="250">
@@ -1,21 +1,21 @@
1
1
  objectnat/__init__.py,sha256=OnDvrLPLEeYIE_9qOVYgMc-PkRzIqShtGxirguEXiRU,260
2
2
  objectnat/_api.py,sha256=oiEO2P-tv6AMDdNoT8d0BWMmgeUJa4bhzGDTU2BWTXI,704
3
3
  objectnat/_config.py,sha256=t4nv83Tj4fwYjdzwUh0bA8b_12DqL-GlEVfKaG_hccg,2107
4
- objectnat/_version.py,sha256=Vdi6OffDRorPQeWjvXo2MPbidl7CNworxvziT78bjl0,18
4
+ objectnat/_version.py,sha256=qleN2zQctO8stGYcfqFUyavqMP9pxdKBbwgZWyKxPWQ,18
5
5
  objectnat/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  objectnat/methods/balanced_buildings.py,sha256=hLT2QgmGWpROtnL8SJQIujeP6q9ou15yIdHpv66CfMs,2892
7
- objectnat/methods/cluster_points_in_polygons.py,sha256=2oHK-_CauEz8dZX6r-UGEopkqdUENLGaJPpMTuVV_o8,4678
7
+ objectnat/methods/cluster_points_in_polygons.py,sha256=ANoPHB89Ih6SYUTs0VoYqW7zi9GVIytSOGySoQ3vby4,5073
8
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
9
+ objectnat/methods/isochrones.py,sha256=CBJprxcyPIYC4RJizqJ1MJL-Zkea4iyr7wHTOOQ7DC8,6146
10
+ objectnat/methods/living_buildings_osm.py,sha256=v0rC8xaqibZq9jZm5HVonmmC9VFXzgZwhqsxHA3sPlc,5904
11
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
12
+ objectnat/methods/provision/city_provision.py,sha256=cFmJJVb0qLR7yCILP9S5cS8fx136EsCUmjbmT0HQA6U,13264
13
+ objectnat/methods/provision/provision.py,sha256=lKgROkj1EmRgk9nLEQ0jGuQcY6BFnkG3oBuhHrRFbno,4619
14
14
  objectnat/methods/provision/provision_exceptions.py,sha256=-TK4A-vacUuzlPJGSt2YyawRwKDLCZFlAbuIvIf1FnY,1723
15
15
  objectnat/methods/visibility_analysis.py,sha256=__S01m4YcIZbUcr6Umzvr4NpaCsajXxKNcfJm3zquVY,20690
16
16
  objectnat/utils/__init__.py,sha256=w8R5V_Ws_GUt4hLwpudMgjXvocG4vCxWSzVw_jTReQ4,44
17
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,,
18
+ objectnat-0.2.2.dist-info/LICENSE.txt,sha256=yPEioMfTd7JAQgAU6J13inS1BSjwd82HFlRSoIb4My8,1498
19
+ objectnat-0.2.2.dist-info/METADATA,sha256=ROkC-bO-4ZWFHfsmL6mLSHza9lXdbT8Aj8FaHMFtmik,5881
20
+ objectnat-0.2.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
21
+ objectnat-0.2.2.dist-info/RECORD,,