if HAS_GPD and openroads is not None and not aadt.empty:
latest_year = int(aadt["year"].max())
if "or_exposure" not in globals():
latest_aadt = aadt.loc[
aadt["year"] == latest_year, ["link_id", "estimated_aadt"]
].copy()
or_exposure = openroads[["link_id", "road_classification", "geometry"]].merge(
latest_aadt, on="link_id", how="inner"
)
or_exposure = or_exposure[or_exposure.geometry.notna()].copy()
if or_exposure.crs is not None and or_exposure.crs.to_epsg() != 4326:
or_exposure = or_exposure.to_crs(4326)
leeds_bbox = {
"min_lon": -1.64,
"max_lon": -1.42,
"min_lat": 53.74,
"max_lat": 53.87,
}
leeds_links = or_exposure.cx[
leeds_bbox["min_lon"]:leeds_bbox["max_lon"],
leeds_bbox["min_lat"]:leeds_bbox["max_lat"],
].copy()
aadf = pd.read_parquet(ROOT / "data" / "processed" / "aadf" / "aadf_clean.parquet")
aadf_latest = aadf[
(aadf["year"] == latest_year)
& aadf["longitude"].between(leeds_bbox["min_lon"], leeds_bbox["max_lon"])
& aadf["latitude"].between(leeds_bbox["min_lat"], leeds_bbox["max_lat"])
& aadf["all_motor_vehicles"].notna()
].copy()
aadf_points = gpd.GeoDataFrame(
aadf_latest,
geometry=gpd.points_from_xy(aadf_latest["longitude"], aadf_latest["latitude"]),
crs="EPSG:4326",
)
aadf_points["method_group"] = np.where(
aadf_points["estimation_method"].eq("Counted"), "Counted", "Estimated"
)
traffic_norm = exposure_norm(
pd.concat(
[
leeds_links["estimated_aadt"],
aadf_points["all_motor_vehicles"],
],
ignore_index=True,
)
)
fig, ax = plt.subplots(figsize=(9, 8), dpi=160)
leeds_links.plot(
column="estimated_aadt",
ax=ax,
cmap=EXPOSURE_CMAP,
norm=traffic_norm,
linewidth=1.0,
alpha=0.9,
legend=True,
legend_kwds={"label": "Traffic volume (vehicles/day, log scale)", "shrink": 0.72},
zorder=1,
)
if not aadf_points.empty:
sizes = 36 + 130 * (
np.log1p(aadf_points["all_motor_vehicles"])
- np.log1p(aadf_points["all_motor_vehicles"]).min()
) / (
np.log1p(aadf_points["all_motor_vehicles"]).max()
- np.log1p(aadf_points["all_motor_vehicles"]).min()
+ 1e-9
)
marker_specs = {
"Counted": {"marker": "s", "label": "AADF Counted"},
"Estimated": {"marker": "o", "label": "AADF Estimated"},
}
for method_group, spec in marker_specs.items():
point_subset = aadf_points[aadf_points["method_group"].eq(method_group)]
if point_subset.empty:
continue
ax.scatter(
point_subset.geometry.x,
point_subset.geometry.y,
s=sizes.loc[point_subset.index],
c=point_subset["all_motor_vehicles"],
cmap=EXPOSURE_CMAP,
norm=traffic_norm,
marker=spec["marker"],
edgecolor="#111827",
linewidth=0.75,
alpha=0.95,
zorder=3,
)
shape_handles = [
Line2D(
[0],
[0],
marker=spec["marker"],
color="none",
label=spec["label"],
markerfacecolor="#d1d5db",
markeredgecolor="#111827",
markersize=8,
)
for spec in marker_specs.values()
]
ax.legend(handles=shape_handles, loc="lower left", frameon=True, framealpha=0.9)
ax.set_xlim(leeds_bbox["min_lon"], leeds_bbox["max_lon"])
ax.set_ylim(leeds_bbox["min_lat"], leeds_bbox["max_lat"])
ax.set_aspect(1 / np.cos(np.deg2rad(53.80)), adjustable="box")
ax.set_title(f"Estimated AADT and AADF count points — Leeds detail ({latest_year})")
ax.set_axis_off()
plt.tight_layout()
plt.show()