Skip to content

Commit

Permalink
allows multiple stats columns
Browse files Browse the repository at this point in the history
  • Loading branch information
songololo committed Jan 29, 2025
1 parent c5d197a commit 9bbdd97
Show file tree
Hide file tree
Showing 28 changed files with 878 additions and 655 deletions.
262 changes: 133 additions & 129 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ name = "cityseer"
crate-type = ["cdylib"]

[dependencies]
atomic_float = "0.1.0"
ndarray = "0.15.6"
numpy = "0.18.0"
petgraph = "0.6.3"
pyo3 = { version="0.18.3", features=["multiple-pymethods"]}
rand = "0.8.5"
rand_distr = "0.4.3"
atomic_float = "1.1.0"
ndarray = "0.16.1"
numpy = "0.23.0"
petgraph = "0.7.1"
pyo3 = { version = "0.23.4", features = ["multiple-pymethods"] }
rand = "0.9.0"
rand_distr = "0.5.0"
rayon = "1.7.0"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ The `cityseer-api` `Python` package addresses a range of issues specific to comp

## Development

`brew install pdm rust rust-analyzer rustfmt`
`pdm install`
`brew install uv rust rust-analyzer rustfmt`
`uv sync`
Binary file modified docs/public/images/assignment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/assignment_decomposed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/assignment_plot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/betas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_clean.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_colour.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_decomposed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_dual.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_raw.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/graph_simple.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/intro_mixed_uses.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/public/images/intro_segment_harmonic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cityseer"
version = '4.17.5'
version = '4.18.0'
description = "Computational tools for network-based pedestrian-scale urban analysis"
readme = "README.md"
requires-python = ">=3.10, <3.14"
Expand Down Expand Up @@ -43,12 +43,12 @@ dependencies = [
"requests>=2.27.1",
"scikit-learn>=1.0.2",
"tqdm>=4.63.1",
"shapely>=2.0.0",
"numpy>=2.0.0",
"geopandas>=1.0.0",
"shapely>=2.0.6",
"numpy>=2.2.2",
"geopandas>=1.0.1",
"rasterio>=1.3.9",
"fiona>=1.9.6",
"osmnx>=2.0.0",
"osmnx>=2.0.1",
]

[project.urls]
Expand Down
59 changes: 31 additions & 28 deletions pysrc/cityseer/metrics/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ def compute_mixed_uses(

def compute_stats(
data_gdf: gpd.GeoDataFrame,
stats_column_label: str,
stats_column_labels: list[str],
nodes_gdf: gpd.GeoDataFrame,
network_structure: rustalgos.NetworkStructure,
max_netw_assign_dist: int = 400,
Expand All @@ -507,8 +507,8 @@ def compute_stats(
representing data points. The coordinates of data points should correspond as precisely as possible to the
location of the feature in space; or, in the case of buildings, should ideally correspond to the location of the
building entrance.
stats_column_label: str
The column label corresponding to the column in `data_gdf` from which to take numerical information.
stats_column_labels: list[str]
The column labels corresponding to the columns in `data_gdf` from which to take numerical information.
nodes_gdf
A [`GeoDataFrame`](https://geopandas.org/en/stable/docs/user_guide/data_structures.html#geodataframe)
representing nodes. Best generated with the
Expand Down Expand Up @@ -598,18 +598,20 @@ def compute_stats(
:::
"""
if stats_column_label not in data_gdf.columns:
raise ValueError("The specified numerical stats column name can't be found in the GeoDataFrame.")
data_map, data_gdf = assign_gdf_to_network(data_gdf, network_structure, max_netw_assign_dist, data_id_col)
if not config.QUIET_MODE:
logger.info("Computing statistics.")
# extract landuses
stats_map: dict[str, float] = data_gdf[stats_column_label].to_dict() # type: ignore
stats_maps = []
for stats_column_label in stats_column_labels:
if stats_column_label not in data_gdf.columns:
raise ValueError("The specified numerical stats column name can't be found in the GeoDataFrame.")
stats_maps.append(data_gdf[stats_column_label].to_dict()) # type: ignore)
# stats
partial_func = partial(
data_map.stats,
network_structure=network_structure,
numerical_map=stats_map,
numerical_maps=stats_maps,
distances=distances,
betas=betas,
angular=angular,
Expand All @@ -621,26 +623,27 @@ def compute_stats(
result = config.wrap_progress(total=network_structure.node_count(), rust_struct=data_map, partial_func=partial_func)
# unpack the numerical arrays
distances, betas = rustalgos.pair_distances_and_betas(distances, betas)
for dist_key in distances:
k = config.prep_gdf_key(f"{stats_column_label}_sum", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result.sum[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_sum", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result.sum_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_mean", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result.mean[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_mean", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result.mean_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_count", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result.count[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_count", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result.count_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_var", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result.variance[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_var", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result.variance_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_max", dist_key, angular=angular)
nodes_gdf[k] = result.max[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_min", dist_key, angular=angular)
nodes_gdf[k] = result.min[dist_key] # type: ignore
for idx, stats_column_label in enumerate(stats_column_labels):
for dist_key in distances:
k = config.prep_gdf_key(f"{stats_column_label}_sum", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result[idx].sum[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_sum", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result[idx].sum_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_mean", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result[idx].mean[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_mean", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result[idx].mean_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_count", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result[idx].count[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_count", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result[idx].count_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_var", dist_key, angular=angular, weighted=False)
nodes_gdf[k] = result[idx].variance[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_var", dist_key, angular=angular, weighted=True)
nodes_gdf[k] = result[idx].variance_wt[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_max", dist_key, angular=angular)
nodes_gdf[k] = result[idx].max[dist_key] # type: ignore
k = config.prep_gdf_key(f"{stats_column_label}_min", dist_key, angular=angular)
nodes_gdf[k] = result[idx].min[dist_key] # type: ignore

return nodes_gdf, data_gdf
4 changes: 2 additions & 2 deletions pysrc/cityseer/rustalgos.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -690,15 +690,15 @@ class DataMap:
def stats(
self,
network_structure: NetworkStructure,
numerical_map: dict[str, float],
numerical_maps: list[dict[str, float]],
distances: list[int] | None = None,
betas: list[float] | None = None,
angular: bool | None = None,
spatial_tolerance: int | None = None,
min_threshold_wt: float | None = None,
jitter_scale: float | None = None,
pbar_disabled: bool | None = None,
) -> StatsResult: ...
) -> list[StatsResult]: ...

class Viewshed:
@classmethod
Expand Down
46 changes: 38 additions & 8 deletions src/centrality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl NetworkStructure {
impedance heuristic - which can be different from metres. Distance map in metres is used for defining max
distances and computing equivalent distance measures.
*/
#[pyo3(signature = (src_idx, max_dist, jitter_scale=None))]
pub fn dijkstra_tree_shortest(
&self,
src_idx: usize,
Expand All @@ -114,7 +115,7 @@ impl NetworkStructure {
distance: 0.0,
});
// random number generator
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
while let Some(NodeDistance { node_idx, .. }) = active.pop() {
tree_map[node_idx].visited = true;
visited_nodes.push(node_idx);
Expand Down Expand Up @@ -173,7 +174,7 @@ impl NetworkStructure {
// inject jitter
let mut jitter: f32 = 0.0;
if jitter_scale > 0.0 {
jitter = rng.gen::<f32>() * jitter_scale;
jitter = rng.random::<f32>() * jitter_scale;
}
/*
if impedance less than prior distances for this node then update shortest path
Expand All @@ -186,6 +187,7 @@ impl NetworkStructure {
}
(visited_nodes, tree_map)
}
#[pyo3(signature = (src_idx, max_dist, jitter_scale=None))]
pub fn dijkstra_tree_simplest(
&self,
src_idx: usize,
Expand All @@ -210,7 +212,7 @@ impl NetworkStructure {
distance: 0.0,
});
// random number generator
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
while let Some(NodeDistance { node_idx, .. }) = active.pop() {
tree_map[node_idx].visited = true;
visited_nodes.push(node_idx);
Expand Down Expand Up @@ -269,7 +271,7 @@ impl NetworkStructure {
// inject jitter
let mut jitter: f32 = 0.0;
if jitter_scale > 0.0 {
jitter = rng.gen::<f32>() * jitter_scale;
jitter = rng.random::<f32>() * jitter_scale;
}
/*
if impedance less than prior distances for this node then update shortest path
Expand All @@ -284,6 +286,7 @@ impl NetworkStructure {
}
(visited_nodes, tree_map)
}
#[pyo3(signature = (src_idx, max_dist, jitter_scale=None))]
pub fn dijkstra_tree_segment(
&self,
src_idx: usize,
Expand All @@ -309,7 +312,7 @@ impl NetworkStructure {
distance: 0.0,
});
// random number generator
let mut rng = rand::thread_rng();
let mut rng = rand::rng();
while let Some(NodeDistance { node_idx, .. }) = active.pop() {
tree_map[node_idx].visited = true;
visited_nodes.push(node_idx);
Expand Down Expand Up @@ -363,7 +366,7 @@ impl NetworkStructure {
// inject jitter
let mut jitter: f32 = 0.0;
if jitter_scale > 0.0 {
jitter = rng.gen::<f32>() * jitter_scale;
jitter = rng.random::<f32>() * jitter_scale;
}
/*
if impedance less than prior distances for this node then update shortest path
Expand All @@ -384,6 +387,15 @@ impl NetworkStructure {
}
(visited_nodes, visited_edges, tree_map, edge_map)
}
#[pyo3(signature = (
distances=None,
betas=None,
compute_closeness=None,
compute_betweenness=None,
min_threshold_wt=None,
jitter_scale=None,
pbar_disabled=None
))]
pub fn local_node_centrality_shortest(
&self,
distances: Option<Vec<u32>>,
Expand Down Expand Up @@ -532,7 +544,17 @@ impl NetworkStructure {
});
Ok(result)
}

#[pyo3(signature = (
distances=None,
betas=None,
compute_closeness=None,
compute_betweenness=None,
min_threshold_wt=None,
angular_scaling_unit=None,
farness_scaling_offset=None,
jitter_scale=None,
pbar_disabled=None
))]
pub fn local_node_centrality_simplest(
&self,
distances: Option<Vec<u32>>,
Expand Down Expand Up @@ -658,7 +680,15 @@ impl NetworkStructure {
});
Ok(result)
}

#[pyo3(signature = (
distances=None,
betas=None,
compute_closeness=None,
compute_betweenness=None,
min_threshold_wt=None,
jitter_scale=None,
pbar_disabled=None
))]
pub fn local_segment_centrality(
&self,
distances: Option<Vec<u32>>,
Expand Down
Loading

0 comments on commit 9bbdd97

Please sign in to comment.