Skip to content

Commit

Permalink
exposes distances from accessibility computations
Browse files Browse the repository at this point in the history
  • Loading branch information
songololo committed Nov 17, 2023
1 parent 23bba88 commit 64d87ba
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 8 deletions.
4 changes: 3 additions & 1 deletion docs/src/pages/metrics/layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ representation of variations of metrics along street-fronts.
</div>
<div class="desc">

The input `node_gdf` parameter is returned with additional columns populated with the calcualted metrics.</div>
The input `node_gdf` parameter is returned with additional columns populated with the calcualted metrics. Three columns will be returned for each input landuse class and distance combination; a simple count of reachable locations, a distance weighted count of reachable locations, and the smallest distance to the nearest location.</div>
</div>

<div class="param-set">
Expand Down Expand Up @@ -391,6 +391,8 @@ print(nodes_gdf.columns)
print(nodes_gdf["cc_metric_c_400_weighted"])
# non-weighted form
print(nodes_gdf["cc_metric_c_400_non_weighted"])
# nearest distance to landuse
print(nodes_gdf["cc_metric_c_400_distance"])
```


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cityseer"
version = '4.4.0'
version = '4.5.0'
description = "Computational tools for network-based pedestrian-scale urban analysis"
readme = "README.md"
requires-python = ">=3.10, <3.12"
Expand Down
10 changes: 8 additions & 2 deletions pysrc/cityseer/metrics/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ def compute_accessibilities(
Returns
-------
nodes_gdf: GeoDataFrame
The input `node_gdf` parameter is returned with additional columns populated with the calcualted metrics.
The input `node_gdf` parameter is returned with additional columns populated with the calcualted metrics. Three
columns will be returned for each input landuse class and distance combination; a simple count of reachable
locations, a distance weighted count of reachable locations, and the smallest distance to the nearest location.
data_gdf: GeoDataFrame
The input `data_gdf` is returned with two additional columns: `nearest_assigned` and `next_neareset_assign`.
Expand Down Expand Up @@ -223,6 +225,8 @@ def compute_accessibilities(
print(nodes_gdf["cc_metric_c_400_weighted"])
# non-weighted form
print(nodes_gdf["cc_metric_c_400_non_weighted"])
# nearest distance to landuse
print(nodes_gdf["cc_metric_c_400_distance"])
```
"""
Expand Down Expand Up @@ -253,9 +257,11 @@ def compute_accessibilities(
for acc_key in accessibility_keys:
for dist_key in distances:
ac_nw_data_key = config.prep_gdf_key(f"{acc_key}_{dist_key}_non_weighted")
ac_wt_data_key = config.prep_gdf_key(f"{acc_key}_{dist_key}_weighted")
nodes_gdf[ac_nw_data_key] = result[acc_key].unweighted[dist_key] # type: ignore
ac_wt_data_key = config.prep_gdf_key(f"{acc_key}_{dist_key}_weighted")
nodes_gdf[ac_wt_data_key] = result[acc_key].weighted[dist_key] # type: ignore
ac_dist_data_key = config.prep_gdf_key(f"{acc_key}_{dist_key}_distance")
nodes_gdf[ac_dist_data_key] = result[acc_key].distance[dist_key] # type: ignore

return nodes_gdf, data_gdf

Expand Down
1 change: 1 addition & 0 deletions pysrc/cityseer/rustalgos.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ def raos_quadratic_diversity(
class AccessibilityResult:
weighted: dict[int, npt.ArrayLike]
unweighted: dict[int, npt.ArrayLike]
distance: dict[int, npt.ArrayLike]

class MixedUsesResult:
hill: dict[int, dict[int, npt.ArrayLike]] | None
Expand Down
23 changes: 23 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct AccessibilityResult {
weighted: HashMap<u32, Py<PyArray1<f32>>>,
#[pyo3(get)]
unweighted: HashMap<u32, Py<PyArray1<f32>>>,
#[pyo3(get)]
distance: HashMap<u32, Py<PyArray1<f32>>>,
}
#[pyclass]
pub struct MixedUsesResult {
Expand Down Expand Up @@ -328,6 +330,20 @@ impl DataMap {
)
})
.collect();
let dists: HashMap<String, MetricResult> = accessibility_keys
.clone()
.into_iter()
.map(|acc_key| {
(
acc_key,
MetricResult::new(
distances.clone(),
network_structure.node_count(),
f32::INFINITY,
),
)
})
.collect();
// indices
let node_indices: Vec<usize> = network_structure.node_indices();
// iter
Expand Down Expand Up @@ -372,6 +388,12 @@ impl DataMap {
let val_wt = clipped_beta_wt(b, mcw, data_dist);
metrics_wt[&lu_class].metric[i][*netw_src_idx]
.fetch_add(val_wt.unwrap(), Ordering::Relaxed);
let current_dist =
dists[&lu_class].metric[i][*netw_src_idx].load(Ordering::Relaxed);
if data_dist < current_dist {
dists[&lu_class].metric[i][*netw_src_idx]
.store(data_dist, Ordering::Relaxed);
}
}
}
}
Expand All @@ -384,6 +406,7 @@ impl DataMap {
AccessibilityResult {
weighted: metrics_wt[acc_key].load(),
unweighted: metrics[acc_key].load(),
distance: dists[acc_key].load(),
},
);
}
Expand Down
7 changes: 7 additions & 0 deletions tests/metrics/test_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ def test_compute_accessibilities(primal_graph):
atol=config.ATOL,
rtol=config.RTOL,
)
acc_data_key_dist = config.prep_gdf_key(f"{acc_key}_{dist_key}_distance")
assert np.allclose(
nodes_gdf[acc_data_key_dist].values,
accessibility_data[acc_key].distance[dist_key],
atol=config.ATOL,
rtol=config.RTOL,
)
# most integrity checks happen in underlying method
with pytest.raises(ValueError):
nodes_gdf, data_gdf = layers.compute_accessibilities(
Expand Down
36 changes: 32 additions & 4 deletions tests/rustalgos/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_aggregate_to_src_idx(primal_graph):
nearest_netw_node = network_structure.get_node_payload(data_entry.nearest_assign)
nearest_assign_dist = tree_map[data_entry.nearest_assign].short_dist
# add tail
if not np.isinf(nearest_assign_dist):
if not np.isposinf(nearest_assign_dist):
nearest_assign_dist += nearest_netw_node.coord.hypot(data_entry.coord)
else:
nearest_assign_dist = np.inf
Expand All @@ -55,7 +55,7 @@ def test_aggregate_to_src_idx(primal_graph):
next_nearest_netw_node = network_structure.get_node_payload(data_entry.next_nearest_assign)
next_nearest_assign_dist = tree_map[data_entry.next_nearest_assign].short_dist
# add tail
if not np.isinf(next_nearest_assign_dist):
if not np.isposinf(next_nearest_assign_dist):
next_nearest_assign_dist += next_nearest_netw_node.coord.hypot(data_entry.coord)
else:
next_nearest_assign_dist = np.inf
Expand All @@ -65,9 +65,9 @@ def test_aggregate_to_src_idx(primal_graph):
assert data_key not in reachable_entries
elif deduplicate and data_key in ["45", "46", "47", "48"]:
assert data_key not in reachable_entries and "49" in reachable_entries
elif np.isinf(nearest_assign_dist) and next_nearest_assign_dist < max_dist:
elif np.isposinf(nearest_assign_dist) and next_nearest_assign_dist < max_dist:
assert reachable_entries[data_key] - next_nearest_assign_dist < config.ATOL
elif np.isinf(next_nearest_assign_dist) and nearest_assign_dist < max_dist:
elif np.isposinf(next_nearest_assign_dist) and nearest_assign_dist < max_dist:
assert reachable_entries[data_key] - nearest_assign_dist < config.ATOL
else:
assert (
Expand Down Expand Up @@ -116,6 +116,10 @@ def test_accessibility(primal_graph):
b_wt = 0
c_wt = 0
z_wt = 0
a_dist = np.inf
b_dist = np.inf
c_dist = np.inf
z_dist = np.inf
# iterate reachable
reachable_entries = data_map.aggregate_to_src_idx(src_idx, network_structure, max_dist)
for data_key, data_dist in reachable_entries.items():
Expand All @@ -127,15 +131,23 @@ def test_accessibility(primal_graph):
if data_class == "a":
a_nw += 1
a_wt += np.exp(-beta * data_dist)
if data_dist < a_dist:
a_dist = data_dist
elif data_class == "b":
b_nw += 1
b_wt += np.exp(-beta * data_dist)
if data_dist < b_dist:
b_dist = data_dist
elif data_class == "c":
c_nw += 1
c_wt += np.exp(-beta * data_dist)
if data_dist < c_dist:
c_dist = data_dist
elif data_class == "z":
z_nw += 1
z_wt += np.exp(-beta * data_dist)
if data_dist < z_dist:
z_dist = data_dist
# assertions
assert accessibilities["a"].unweighted[dist][src_idx] - a_nw < config.ATOL
assert accessibilities["b"].unweighted[dist][src_idx] - b_nw < config.ATOL
Expand All @@ -145,6 +157,22 @@ def test_accessibility(primal_graph):
assert accessibilities["b"].weighted[dist][src_idx] - b_wt < config.ATOL
assert accessibilities["c"].weighted[dist][src_idx] - c_wt < config.ATOL
assert accessibilities["z"].weighted[dist][src_idx] - z_wt < config.ATOL
if np.isfinite(a_dist):
assert accessibilities["a"].distance[dist][src_idx] - a_dist < config.ATOL
else:
assert np.isposinf(a_dist) and np.isposinf(accessibilities["a"].distance[dist][src_idx])
if np.isfinite(b_dist):
assert accessibilities["b"].distance[dist][src_idx] - b_dist < config.ATOL
else:
assert np.isposinf(b_dist) and np.isposinf(accessibilities["b"].distance[dist][src_idx])
if np.isfinite(c_dist):
assert accessibilities["c"].distance[dist][src_idx] - c_dist < config.ATOL
else:
assert np.isposinf(c_dist) and np.isposinf(accessibilities["c"].distance[dist][src_idx])
if np.isfinite(z_dist):
assert accessibilities["z"].distance[dist][src_idx] - z_dist < config.ATOL
else:
assert np.isposinf(z_dist) and np.isposinf(accessibilities["z"].distance[dist][src_idx])
# check for deduplication
assert z_nw in [0, 1]
assert z_wt <= 1
Expand Down

0 comments on commit 64d87ba

Please sign in to comment.