"""Session 19 - Moisture distribution map across node locations (IDW interpolation). Edit NODES with the GPS coordinates you measured at the college farm (Google Maps -> long press). Values come from the cleaned node log (session 14) - or type the latest readings by hand. Output: outputs/s19_moisture_map.png + outputs/s19_nodes.geojson (open it in Leaflet / QGIS) """ import json import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import numpy as np import pandas as pd from common import OUT # Approximate positions inside the KFS Faculty of Agriculture farm - replace with real GPS points. NODES = { "greenhouse_01": (31.1052, 30.9431), "field_A": (31.1047, 30.9442), "field_B": (31.1041, 30.9436), "field_C": (31.1045, 30.9428), } clean_path = OUT / "node_log_clean.csv" if clean_path.exists(): log = pd.read_csv(clean_path) latest = log.dropna(subset=["soil_moisture_pct"]).groupby("node_id")["soil_moisture_pct"].mean() else: latest = pd.Series({"greenhouse_01": 38.0, "field_A": 24.0, "field_B": 17.0}) latest = latest.reindex(NODES.keys()).fillna(latest.mean()).round(1) print("Mean soil moisture per node (%):\n", latest) lat = np.array([NODES[n][0] for n in latest.index]); lon = np.array([NODES[n][1] for n in latest.index]) val = latest.values # Inverse Distance Weighting on a grid gx, gy = np.meshgrid(np.linspace(lon.min() - 4e-4, lon.max() + 4e-4, 160), np.linspace(lat.min() - 4e-4, lat.max() + 4e-4, 160)) d = np.sqrt((gx[..., None] - lon) ** 2 + (gy[..., None] - lat) ** 2) + 1e-9 w = 1 / d ** 2 grid = (w * val).sum(-1) / w.sum(-1) fig, ax = plt.subplots(figsize=(7, 6)) im = ax.contourf(gx, gy, grid, levels=20, cmap="RdYlBu") ax.scatter(lon, lat, c="k", s=40) for n, x, y_, v in zip(latest.index, lon, lat, val): ax.annotate(f"{n}\n{v}%", (x, y_), textcoords="offset points", xytext=(6, 6), fontsize=8) fig.colorbar(im, label="Soil moisture %") ax.set_title("KFS farm - soil moisture distribution (IDW)") ax.set_xlabel("Longitude"); ax.set_ylabel("Latitude") fig.tight_layout(); fig.savefig(OUT / "s19_moisture_map.png", dpi=130) geo = {"type": "FeatureCollection", "features": [ {"type": "Feature", "properties": {"node": n, "soil_moisture_pct": float(v)}, "geometry": {"type": "Point", "coordinates": [float(NODES[n][1]), float(NODES[n][0])]}} for n, v in latest.items()]} (OUT / "s19_nodes.geojson").write_text(json.dumps(geo, indent=2)) print("Saved map + GeoJSON to", OUT)