voxcity 0.3.8__py3-none-any.whl → 0.3.10__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 voxcity might be problematic. Click here for more details.

voxcity/geo/network.py CHANGED
@@ -6,189 +6,500 @@ import geopandas as gpd
6
6
  from shapely.geometry import LineString
7
7
  import networkx as nx
8
8
  import osmnx as ox
9
+ import os
10
+ import shapely
11
+ from shapely.geometry import Point
12
+ from shapely.ops import transform
13
+ from pyproj import Transformer
14
+ from joblib import Parallel, delayed
9
15
 
10
16
  from .grid import grid_to_geodataframe
11
17
 
12
- def calculate_edge_values(G, gdf, value_col='value'):
18
+ def vectorized_edge_values(G, polygons_gdf, value_col='value'):
13
19
  """
14
- Calculate average values for graph edges based on intersection with polygons.
15
-
16
- Parameters:
17
- -----------
18
- G : NetworkX Graph
19
- Input graph with edges to analyze
20
- gdf : GeoDataFrame
21
- Grid containing polygons with values
22
- value_col : str, default 'value'
23
- Name of the column containing values in the grid
24
-
25
- Returns:
26
- --------
27
- dict
28
- Dictionary with edge identifiers (u,v,k) as keys and average values as values
20
+ Compute average polygon values along each edge by:
21
+ 1) Building an Edge GeoDataFrame in linestring form
22
+ 2) Using gpd.overlay or sjoin to get their intersection with polygons
23
+ 3) Computing length-weighted average
29
24
  """
25
+ # 1) Build edge GeoDataFrame (EPSG:4326)
26
+ records = []
27
+ for i, (u, v, k, data) in enumerate(G.edges(keys=True, data=True)):
28
+ if 'geometry' in data:
29
+ edge_geom = data['geometry']
30
+ else:
31
+ start_node = G.nodes[u]
32
+ end_node = G.nodes[v]
33
+ edge_geom = LineString([(start_node['x'], start_node['y']),
34
+ (end_node['x'], end_node['y'])])
35
+ records.append({
36
+ 'edge_id': i, # unique ID for grouping
37
+ 'u': u,
38
+ 'v': v,
39
+ 'k': k,
40
+ 'geometry': edge_geom
41
+ })
42
+
43
+ edges_gdf = gpd.GeoDataFrame(records, crs="EPSG:4326")
44
+ if polygons_gdf.crs != edges_gdf.crs:
45
+ polygons_gdf = polygons_gdf.to_crs(edges_gdf.crs)
46
+
47
+ # 2) Use a projected CRS for length calculations
48
+ edges_3857 = edges_gdf.to_crs(epsg=3857)
49
+ polys_3857 = polygons_gdf.to_crs(epsg=3857)
50
+
51
+ # 3) Intersection: lines vs polygons -> lines clipped to polygons
52
+ # gpd.overlay with how='intersection' can yield partial lines
53
+ intersected = gpd.overlay(edges_3857, polys_3857, how='intersection')
54
+
55
+ # Now each row is a geometry representing the intersection segment,
56
+ # with columns from edges + polygons.
57
+ # For lines, 'intersection' yields the line portion inside each polygon.
58
+ # We'll compute the length, then do a length-weighted average of value_col.
59
+
60
+ intersected['seg_length'] = intersected.geometry.length
61
+ # Weighted contribution = seg_length * polygon_value
62
+ intersected['weighted_val'] = intersected['seg_length'] * intersected[value_col]
63
+
64
+ # 4) Group by edge_id
65
+ grouped = intersected.groupby('edge_id')
66
+ results = grouped.apply(
67
+ lambda df: df['weighted_val'].sum() / df['seg_length'].sum()
68
+ if df['seg_length'].sum() > 0 else np.nan
69
+ )
70
+ # results is a Series with index=edge_id
71
+
72
+ # 5) Map results back to edges
30
73
  edge_values = {}
74
+ for edge_id, val in results.items():
75
+ rec = edges_gdf.iloc[edge_id]
76
+ edge_values[(rec['u'], rec['v'], rec['k'])] = val
77
+
78
+ return edge_values
79
+
80
+ def get_network_values(
81
+ grid,
82
+ rectangle_vertices,
83
+ meshsize,
84
+ value_name='value',
85
+ **kwargs
86
+ ):
87
+ defaults = {
88
+ 'network_type': 'walk',
89
+ 'vis_graph': True,
90
+ 'colormap': 'viridis',
91
+ 'vmin': None,
92
+ 'vmax': None,
93
+ 'edge_width': 1,
94
+ 'fig_size': (15,15),
95
+ 'zoom': 16,
96
+ 'basemap_style': ctx.providers.CartoDB.Positron,
97
+ 'save_path': None
98
+ }
99
+ settings = {**defaults, **kwargs}
100
+
101
+ # Build polygons GDF if needed
102
+ polygons_gdf = (grid if isinstance(grid, gpd.GeoDataFrame)
103
+ else grid_to_geodataframe(grid, rectangle_vertices, meshsize))
104
+ if polygons_gdf.crs is None:
105
+ polygons_gdf.set_crs(epsg=4326, inplace=True)
106
+
107
+ # BBox
108
+ north, south = rectangle_vertices[1][1], rectangle_vertices[0][1]
109
+ east, west = rectangle_vertices[2][0], rectangle_vertices[0][0]
110
+ bbox = (west, south, east, north)
111
+
112
+ # Download OSMnx network
113
+ G = ox.graph.graph_from_bbox(
114
+ bbox=bbox,
115
+ network_type=settings['network_type'],
116
+ simplify=True
117
+ )
118
+
119
+ # Compute edge values with the vectorized function
120
+ edge_values = vectorized_edge_values(G, polygons_gdf, value_col="value")
121
+ nx.set_edge_attributes(G, edge_values, name=value_name)
122
+
123
+ # Build edge GDF
124
+ edges_with_values = []
31
125
  for u, v, k, data in G.edges(data=True, keys=True):
32
126
  if 'geometry' in data:
33
- edge_line = data['geometry']
127
+ geom = data['geometry']
34
128
  else:
35
129
  start_node = G.nodes[u]
36
130
  end_node = G.nodes[v]
37
- edge_line = LineString([(start_node['x'], start_node['y']),
38
- (end_node['x'], end_node['y'])])
39
-
40
- intersecting_polys = gdf[gdf.geometry.intersects(edge_line)]
41
-
42
- if len(intersecting_polys) > 0:
43
- total_length = 0
44
- weighted_sum = 0
45
-
46
- for idx, poly in intersecting_polys.iterrows():
47
- if pd.isna(poly[value_col]):
48
- continue
49
-
50
- intersection = edge_line.intersection(poly.geometry)
51
- if not intersection.is_empty:
52
- length = intersection.length
53
- total_length += length
54
- weighted_sum += length * poly[value_col]
55
-
56
- if total_length > 0:
57
- avg_value = weighted_sum / total_length
58
- edge_values[(u, v, k)] = avg_value
59
- else:
60
- edge_values[(u, v, k)] = np.nan
131
+ geom = LineString([(start_node['x'], start_node['y']),
132
+ (end_node['x'], end_node['y'])])
133
+
134
+ val = data.get(value_name, np.nan)
135
+ edges_with_values.append({
136
+ 'u': u, 'v': v, 'key': k,
137
+ 'geometry': geom,
138
+ value_name: val
139
+ })
140
+
141
+ edge_gdf = gpd.GeoDataFrame(edges_with_values, crs="EPSG:4326")
142
+
143
+ # Save
144
+ if settings['save_path']:
145
+ edge_gdf.to_file(settings['save_path'], driver="GPKG")
146
+
147
+ if settings['vis_graph']:
148
+ edge_gdf_web = edge_gdf.to_crs(epsg=3857)
149
+ fig, ax = plt.subplots(figsize=settings['fig_size'])
150
+ edge_gdf_web.plot(
151
+ column=value_name,
152
+ ax=ax,
153
+ cmap=settings['colormap'],
154
+ legend=True,
155
+ vmin=settings['vmin'],
156
+ vmax=settings['vmax'],
157
+ linewidth=settings['edge_width'],
158
+ legend_kwds={'label': value_name, 'shrink': 0.5}
159
+ )
160
+ ctx.add_basemap(ax, source=settings['basemap_style'], zoom=settings['zoom'])
161
+ ax.set_axis_off()
162
+ plt.show()
163
+
164
+ return G, edge_gdf
165
+
166
+ # -------------------------------------------------------------------
167
+ # Optionally import your DEM helper
168
+ # -------------------------------------------------------------------
169
+ from voxcity.geo.grid import grid_to_geodataframe
170
+
171
+ # -------------------------------------------------------------------
172
+ # 1) Functions for interpolation, parallelization, and slope
173
+ # -------------------------------------------------------------------
174
+
175
+ def interpolate_points_along_line(line, interval):
176
+ """
177
+ Interpolate points along a single LineString at a given interval (in meters).
178
+ If the line is shorter than `interval`, only start/end points are returned.
179
+
180
+ Parameters
181
+ ----------
182
+ line : shapely.geometry.LineString
183
+ Edge geometry in EPSG:4326 (lon/lat).
184
+ interval : float
185
+ Distance in meters between interpolated points.
186
+
187
+ Returns
188
+ -------
189
+ list of shapely.geometry.Point
190
+ Points in EPSG:4326 along the line.
191
+ """
192
+ if line.is_empty:
193
+ return []
194
+
195
+ # Transformers for metric distance calculations
196
+ project = Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True).transform
197
+ project_rev = Transformer.from_crs("EPSG:3857", "EPSG:4326", always_xy=True).transform
198
+
199
+ # Project line to Web Mercator
200
+ line_merc = shapely.ops.transform(project, line)
201
+ length_m = line_merc.length
202
+ if length_m == 0:
203
+ return [Point(line.coords[0])]
204
+
205
+ # If line is shorter than interval, just start & end
206
+ if length_m < interval:
207
+ return [Point(line.coords[0]), Point(line.coords[-1])]
208
+
209
+ # Otherwise, create distances
210
+ num_points = int(length_m // interval)
211
+ dists = [i * interval for i in range(num_points + 1)]
212
+ # Ensure end
213
+ if dists[-1] < length_m:
214
+ dists.append(length_m)
215
+
216
+ # Interpolate
217
+ points_merc = [line_merc.interpolate(d) for d in dists]
218
+ # Reproject back
219
+ return [shapely.ops.transform(project_rev, pt) for pt in points_merc]
220
+
221
+
222
+ def gather_interpolation_points(G, interval=10.0, n_jobs=1):
223
+ """
224
+ Gather all interpolation points for each edge in the graph into a single GeoDataFrame.
225
+ Can be parallelized with `n_jobs`.
226
+
227
+ Parameters
228
+ ----------
229
+ G : networkx.MultiDiGraph
230
+ OSMnx graph with 'geometry' attributes or x,y coordinates in the nodes.
231
+ interval : float, default=10.0
232
+ Interpolation distance interval in meters.
233
+ n_jobs : int, default=1
234
+ Number of parallel jobs (1 => no parallelization).
235
+
236
+ Returns
237
+ -------
238
+ gpd.GeoDataFrame
239
+ Columns: edge_id, index_in_edge, geometry (EPSG:4326).
240
+ """
241
+ edges = list(G.edges(keys=True, data=True))
242
+
243
+ def process_edge(u, v, k, data, idx):
244
+ if 'geometry' in data:
245
+ line = data['geometry']
61
246
  else:
62
- edge_values[(u, v, k)] = np.nan
63
-
64
- return edge_values
247
+ # If no geometry, build from node coords
248
+ start_node = G.nodes[u]
249
+ end_node = G.nodes[v]
250
+ line = LineString([(start_node['x'], start_node['y']),
251
+ (end_node['x'], end_node['y'])])
252
+
253
+ pts = interpolate_points_along_line(line, interval)
254
+ df = pd.DataFrame({
255
+ 'edge_id': [idx]*len(pts),
256
+ 'index_in_edge': np.arange(len(pts)),
257
+ 'geometry': pts
258
+ })
259
+ return df
260
+
261
+ # Parallel interpolation
262
+ results = Parallel(n_jobs=n_jobs, backend='threading')(
263
+ delayed(process_edge)(u, v, k, data, i)
264
+ for i, (u, v, k, data) in enumerate(edges)
265
+ )
266
+
267
+ all_points_df = pd.concat(results, ignore_index=True)
268
+ points_gdf = gpd.GeoDataFrame(all_points_df, geometry='geometry', crs="EPSG:4326")
269
+ return points_gdf
65
270
 
66
- def get_network_values(grid, rectangle_vertices, meshsize, value_name='value', **kwargs):
271
+
272
+ def fetch_elevations_for_points(points_gdf_3857, dem_gdf_3857, elevation_col='value'):
67
273
  """
68
- Analyze and visualize network values based on grid intersections.
69
-
70
- Parameters:
71
- -----------
72
- grid : GeoDataFrame
73
- Input grid with geometries and values
74
- rectangle_vertices : list
75
- List of coordinates defining the bounding box vertices
274
+ Do a spatial join (nearest) in a projected CRS (EPSG:3857) to fetch DEM elevations.
275
+
276
+ Parameters
277
+ ----------
278
+ points_gdf_3857 : gpd.GeoDataFrame
279
+ Interpolation points in EPSG:3857.
280
+ dem_gdf_3857 : gpd.GeoDataFrame
281
+ DEM polygons in EPSG:3857, must have `elevation_col`.
282
+ elevation_col : str, default='value'
283
+ Column with elevation values in dem_gdf_3857.
284
+
285
+ Returns
286
+ -------
287
+ gpd.GeoDataFrame
288
+ A copy of points_gdf_3857 with new column 'elevation'.
289
+ """
290
+ joined = gpd.sjoin_nearest(
291
+ points_gdf_3857,
292
+ dem_gdf_3857[[elevation_col, 'geometry']].copy(),
293
+ how='left',
294
+ distance_col='dist_to_poly'
295
+ )
296
+ joined.rename(columns={elevation_col: 'elevation'}, inplace=True)
297
+ return joined
298
+
299
+
300
+ def compute_slope_for_group(df):
301
+ """
302
+ Given a subset of points for a single edge, compute average slope between
303
+ consecutive points, using columns: geometry, elevation, index_in_edge.
304
+
305
+ Note: We assume df is already in EPSG:3857 for direct distance calculations.
306
+ """
307
+ # Sort by position along the edge
308
+ df = df.sort_values("index_in_edge")
309
+
310
+ # Coordinates
311
+ xs = df.geometry.x.to_numpy()
312
+ ys = df.geometry.y.to_numpy()
313
+ elevs = df["elevation"].to_numpy()
314
+
315
+ # Differences
316
+ dx = np.diff(xs)
317
+ dy = np.diff(ys)
318
+ horizontal_dist = np.sqrt(dx**2 + dy**2)
319
+ elev_diff = np.diff(elevs)
320
+
321
+ # Slope in %
322
+ valid_mask = horizontal_dist > 0
323
+ slopes = (np.abs(elev_diff[valid_mask]) / horizontal_dist[valid_mask]) * 100
324
+
325
+ if len(slopes) == 0:
326
+ return np.nan
327
+ return slopes.mean()
328
+
329
+
330
+ def calculate_edge_slopes_from_join(joined_points_gdf, n_edges):
331
+ """
332
+ Calculate average slopes for each edge by grouping joined points.
333
+
334
+ Parameters
335
+ ----------
336
+ joined_points_gdf : gpd.GeoDataFrame
337
+ Must have columns: edge_id, index_in_edge, elevation, geometry (EPSG:3857).
338
+ n_edges : int
339
+ Number of edges from the graph.
340
+
341
+ Returns
342
+ -------
343
+ dict
344
+ edge_id -> average slope (in %).
345
+ """
346
+ # We'll group by edge_id, ignoring the group columns in apply (pandas >= 2.1).
347
+ # If your pandas version < 2.1, just do a column subset after groupby.
348
+ # E.g. .groupby("edge_id", group_keys=False)[["geometry","elevation","index_in_edge"]]...
349
+ grouped = joined_points_gdf.groupby("edge_id", group_keys=False)
350
+ results = grouped[["geometry", "elevation", "index_in_edge"]].apply(compute_slope_for_group)
351
+
352
+ # Convert series -> dict
353
+ slope_dict = results.to_dict()
354
+
355
+ # Fill any missing edge IDs with NaN
356
+ for i in range(n_edges):
357
+ if i not in slope_dict:
358
+ slope_dict[i] = np.nan
359
+
360
+ return slope_dict
361
+
362
+ # -------------------------------------------------------------------
363
+ # 2) Main function to analyze network slopes
364
+ # -------------------------------------------------------------------
365
+
366
+ def analyze_network_slopes(
367
+ dem_grid,
368
+ meshsize,
369
+ value_name='slope',
370
+ interval=10.0,
371
+ n_jobs=1,
372
+ **kwargs
373
+ ):
374
+ """
375
+ Analyze and visualize network slopes based on DEM data, using vectorized + parallel methods.
376
+
377
+ Parameters
378
+ ----------
379
+ dem_grid : array-like
380
+ DEM grid data.
76
381
  meshsize : float
77
- Size of the mesh grid
78
- value_name : str, default 'value'
79
- Name of the column containing values in the grid
382
+ Mesh grid size.
383
+ value_name : str, default='slope'
384
+ Column name for slopes assigned to each edge.
385
+ interval : float, default=10.0
386
+ Interpolation distance in meters.
387
+ n_jobs : int, default=1
388
+ Parallelization for edge interpolation (1 => sequential).
80
389
  **kwargs : dict
81
- Optional arguments including:
82
- - network_type : str, default 'walk'
83
- Type of network to download ('walk', 'drive', 'all', etc.)
84
- - vis_graph : bool, default True
85
- Whether to visualize the graph
86
- - colormap : str, default 'viridis'
87
- Matplotlib colormap name for visualization
88
- - vmin : float, optional
89
- Minimum value for color scaling
90
- - vmax : float, optional
91
- Maximum value for color scaling
92
- - edge_width : float, default 1
93
- Width of the edges in visualization
94
- - fig_size : tuple, default (15,15)
95
- Figure size for visualization
96
- - zoom : int, default 16
97
- Zoom level for the basemap
98
- - basemap_style : ctx.providers, default CartoDB.Positron
99
- Contextily basemap provider
100
- - save_path : str, optional
101
- Path to save the output GeoPackage
102
-
103
- Returns:
104
- --------
105
- tuple : (NetworkX Graph, GeoDataFrame)
106
- Returns the processed graph and edge GeoDataFrame
390
+ Additional parameters:
391
+ - rectangle_vertices : list of (x, y) in EPSG:4326
392
+ - network_type : str, default='walk'
393
+ - vis_graph : bool, default=True
394
+ - colormap, vmin, vmax, edge_width, fig_size, zoom, basemap_style, alpha
395
+ - output_directory, output_file_name
107
396
  """
108
- # Set default values for optional arguments
109
397
  defaults = {
398
+ 'rectangle_vertices': None,
110
399
  'network_type': 'walk',
111
400
  'vis_graph': True,
112
401
  'colormap': 'viridis',
113
402
  'vmin': None,
114
403
  'vmax': None,
115
404
  'edge_width': 1,
116
- 'fig_size': (15,15),
405
+ 'fig_size': (15, 15),
117
406
  'zoom': 16,
118
407
  'basemap_style': ctx.providers.CartoDB.Positron,
119
- 'save_path': None
408
+ 'output_directory': None,
409
+ 'output_file_name': 'network_slopes',
410
+ 'alpha': 1.0
120
411
  }
121
-
122
- # Update defaults with provided kwargs
123
- settings = defaults.copy()
124
- settings.update(kwargs)
412
+ settings = {**defaults, **kwargs}
125
413
 
126
- grid_gdf = grid_to_geodataframe(grid, rectangle_vertices, meshsize)
127
-
128
- # Extract bounding box coordinates
129
- north, south = rectangle_vertices[1][1], rectangle_vertices[0][1]
130
- east, west = rectangle_vertices[2][0], rectangle_vertices[0][0]
414
+ # Validate bounding box
415
+ if settings['rectangle_vertices'] is None:
416
+ raise ValueError("Must supply 'rectangle_vertices' in kwargs.")
417
+
418
+ # 1) Build DEM GeoDataFrame in EPSG:4326
419
+ dem_gdf = grid_to_geodataframe(dem_grid, settings['rectangle_vertices'], meshsize)
420
+ if dem_gdf.crs is None:
421
+ dem_gdf.set_crs(epsg=4326, inplace=True)
422
+
423
+ # 2) Download bounding box from rectangle_vertices
424
+ north, south = settings['rectangle_vertices'][1][1], settings['rectangle_vertices'][0][1]
425
+ east, west = settings['rectangle_vertices'][2][0], settings['rectangle_vertices'][0][0]
131
426
  bbox = (west, south, east, north)
427
+
428
+ G = ox.graph.graph_from_bbox(
429
+ bbox=bbox,
430
+ network_type=settings['network_type'],
431
+ simplify=True
432
+ )
433
+
434
+ # 3) Interpolate points along edges (EPSG:4326)
435
+ points_gdf_4326 = gather_interpolation_points(G, interval=interval, n_jobs=n_jobs)
132
436
 
133
- # Download the road network
134
- G = ox.graph.graph_from_bbox(bbox=bbox, network_type=settings['network_type'], simplify=True)
135
-
136
- # Calculate edge values using the separate function
137
- edge_values = calculate_edge_values(G, grid_gdf, "value")
138
-
139
- # Add values to the graph
140
- nx.set_edge_attributes(G, edge_values, value_name)
141
-
142
- # Create GeoDataFrame from edges
437
+ # 4) Reproject DEM + Points to EPSG:3857 for correct distance operations
438
+ dem_gdf_3857 = dem_gdf.to_crs(epsg=3857)
439
+ points_gdf_3857 = points_gdf_4326.to_crs(epsg=3857)
440
+
441
+ # 5) Perform spatial join to get elevations
442
+ joined_points_3857 = fetch_elevations_for_points(points_gdf_3857, dem_gdf_3857, elevation_col='value')
443
+
444
+ # 6) Compute slopes for each edge
445
+ n_edges = len(list(G.edges(keys=True)))
446
+ slope_dict = calculate_edge_slopes_from_join(joined_points_3857, n_edges)
447
+
448
+ # 7) Assign slopes back to G
449
+ edges = list(G.edges(keys=True, data=True))
450
+ edge_slopes = {}
451
+ for i, (u, v, k, data) in enumerate(edges):
452
+ edge_slopes[(u, v, k)] = slope_dict.get(i, np.nan)
453
+ nx.set_edge_attributes(G, edge_slopes, name=value_name)
454
+
455
+ # 8) Build an edge GeoDataFrame in EPSG:4326
143
456
  edges_with_values = []
144
- for u, v, k, data in G.edges(data=True, keys=True):
457
+ for (u, v, k, data), edge_id in zip(edges, range(len(edges))):
145
458
  if 'geometry' in data:
146
- edge_line = data['geometry']
459
+ geom = data['geometry']
147
460
  else:
148
461
  start_node = G.nodes[u]
149
462
  end_node = G.nodes[v]
150
- edge_line = LineString([(start_node['x'], start_node['y']),
151
- (end_node['x'], end_node['y'])])
152
-
463
+ geom = LineString([(start_node['x'], start_node['y']),
464
+ (end_node['x'], end_node['y'])])
465
+
153
466
  edges_with_values.append({
154
- 'geometry': edge_line,
155
- value_name: data.get(value_name, np.nan),
156
467
  'u': u,
157
468
  'v': v,
158
- 'key': k
469
+ 'key': k,
470
+ 'geometry': geom,
471
+ value_name: slope_dict.get(edge_id, np.nan)
159
472
  })
160
-
161
- edge_gdf = gpd.GeoDataFrame(edges_with_values)
162
-
163
- # Set CRS and save if requested
164
- if edge_gdf.crs is None:
165
- edge_gdf.set_crs(epsg=4326, inplace=True)
166
-
167
- if settings['save_path']:
168
- edge_gdf.to_file(settings['save_path'], driver="GPKG")
169
-
170
- # Visualize if requested
473
+
474
+ edge_gdf = gpd.GeoDataFrame(edges_with_values, crs="EPSG:4326")
475
+
476
+ # 9) Save output if requested
477
+ if settings['output_directory']:
478
+ os.makedirs(settings['output_directory'], exist_ok=True)
479
+ out_path = os.path.join(
480
+ settings['output_directory'],
481
+ f"{settings['output_file_name']}.gpkg"
482
+ )
483
+ edge_gdf.to_file(out_path, driver="GPKG")
484
+
485
+ # 10) Visualization
171
486
  if settings['vis_graph']:
172
487
  edge_gdf_web = edge_gdf.to_crs(epsg=3857)
173
-
174
488
  fig, ax = plt.subplots(figsize=settings['fig_size'])
175
-
176
- plot = edge_gdf_web.plot(column=value_name,
177
- ax=ax,
178
- cmap=settings['colormap'],
179
- legend=True,
180
- vmin=settings['vmin'],
181
- vmax=settings['vmax'],
182
- linewidth=settings['edge_width'],
183
- legend_kwds={'label': value_name,
184
- 'shrink': 0.5}) # Make colorbar 50% smaller
185
-
186
- ctx.add_basemap(ax,
187
- source=settings['basemap_style'],
188
- zoom=settings['zoom'])
189
-
489
+ edge_gdf_web.plot(
490
+ column=value_name,
491
+ ax=ax,
492
+ cmap=settings['colormap'],
493
+ legend=True,
494
+ vmin=settings['vmin'],
495
+ vmax=settings['vmax'],
496
+ linewidth=settings['edge_width'],
497
+ alpha=settings['alpha'],
498
+ legend_kwds={'label': f"{value_name} (%)"}
499
+ )
500
+ ctx.add_basemap(ax, source=settings['basemap_style'], zoom=settings['zoom'])
190
501
  ax.set_axis_off()
191
- # plt.title(f'Network {value_name} Analysis', pad=20)
502
+ plt.title(f'Network {value_name} Analysis', pad=20)
192
503
  plt.show()
193
-
504
+
194
505
  return G, edge_gdf