Urban Observatory API Example¶

This notebook demonstrates how to use the uo_pyfetch library to fetch data from the Urban Observatory Sensor API.

In [11]:
from datetime import datetime, timedelta
import uo_pyfetch
from IPython.display import HTML

Define bounding box for Newcastle area¶

In [2]:
bbox = [-1.652756, 54.973377, -1.620483, 54.983721]

Fetch available sensor variables¶

In [3]:
variables = uo_pyfetch.get_variables()
print("Available variables:")
for v in variables["Variables"][:5]:
    print(f" - {v['Name']} ({v.get('Units', 'no units')})")
Available variables:
 - allpollu (%)
 - Ammonia (?)
 - Average Speed (KmPH)
 - Battery (V)
 - Brood (Celsius)

Fetch available sensor themes¶

In [4]:
themes = uo_pyfetch.get_themes()
print("\nAvailable themes:")
for t in themes["Themes"]:
    print(f" - {t['Name']}")
Available themes:
 - Sewage
 - Sensor Metrics
 - Electrical
 - Seismic
 - Water Quality
 - Traffic
 - Air Quality
 - Water Level
 - Bee Hive
 - People
 - Building
 - Environment
 - Weather
 - Noise
 - Vehicles

Fetch sensors within bounding box¶

In [13]:
print("\nFetching sensors in bbox...")
sensors_df = uo_pyfetch.get_sensors(limit=50, bbox=bbox)
HTML(sensors_df.head().to_html())
Fetching sensors in bbox...
Out[13]:
Sensor_Name Location_WKT Sensor_Centroid_Longitude Ground_Height_Above_Sea_Level Sensor_Centroid_Latitude Sensor_Height_Above_Ground Broker_Name Raw_ID
0 PER_NEWCASTLE_STJAMES POINT(-1.62099 54.97408) -1.62099 50.750000 54.97408 2.0 Newcastle Council RSS feed 137
1 PER_NEWCASTLE_STJAMES_METRO POINT(-1.62099 54.97408) -1.62099 50.750000 54.97408 2.0 Newcastle Council RSS feed 220
2 PER_BUILDING_OAT_NUBS_TREND_MOM POINT(-1.62332 54.97415) -1.62332 56.509998 54.97415 2.0 Building External Sensor 615
3 PER_BUILDING_OAT_SPORTS_HALL_TREND_MOM POINT(-1.62469 54.98274) -1.62469 66.709999 54.98274 2.0 Building External Sensor 630
4 PER_BUILDING_OAT_RICH_RD_BLR_HSE_TREND_MOM POINT(-1.62419 54.98162) -1.62419 64.959999 54.98162 2.0 Building External Sensor 634

Fetch recent sensor data (last 2 hours)¶

In [12]:
print("\nFetching sensor data (last 2 hours)...")
sensor_data_df = uo_pyfetch.get_sensor_data(
    last_n_hours=2,
    variables=["NO2", "PM10"],
    bbox=bbox,
    limit=1000
)
HTML(sensor_data_df.to_html(index=False))
Fetching sensor data (last 2 hours)...
Out[12]:
Sensor_Name Variable Value Timestamp Flagged Location_WKT Sensor_Centroid_Longitude Ground_Height_Above_Sea_Level Sensor_Centroid_Latitude Sensor_Height_Above_Ground Broker_Name Raw_ID
PER_AIRMON_MESH306245 NO2 23.21424 2025-06-05 10:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 6.95000 2025-06-05 10:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 NO2 22.25544 2025-06-05 10:45:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 7.54000 2025-06-05 10:45:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 NO2 24.17304 2025-06-05 11:00:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 7.35000 2025-06-05 11:00:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 NO2 26.37264 2025-06-05 11:15:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 7.20000 2025-06-05 11:15:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 NO2 23.90984 2025-06-05 11:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 6.92000 2025-06-05 11:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 NO2 23.77824 2025-06-05 11:45:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
PER_AIRMON_MESH306245 PM10 6.02000 2025-06-05 11:45:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521

Fetch full history for a specific sensor if data is available¶

In [24]:
if not sensor_data_df.empty:
    sensor_name = sensor_data_df.iloc[0]["Sensor_Name"]
    print(f"\nFetching full history for sensor: {sensor_name}")
    start = datetime.now() - timedelta(days=1)
    end = datetime.now()

    sensor_specific_df = uo_pyfetch.get_sensor_data_by_name(
        sensor_name,
        start=start,
        end=end,
        variables=["NO2"]
    )
else:
    print("\nNo sensors found in bbox.")

HTML(sensor_specific_df.head().to_html())
Fetching full history for sensor: PER_AIRMON_MESH306245
Out[24]:
Sensor_Name Variable Value Timestamp Flagged Location_WKT Sensor_Centroid_Longitude Ground_Height_Above_Sea_Level Sensor_Centroid_Latitude Sensor_Height_Above_Ground Broker_Name Raw_ID
0 PER_AIRMON_MESH306245 NO2 25.31984 2025-06-04 13:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
1 PER_AIRMON_MESH306245 NO2 26.91784 2025-06-04 13:45:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
2 PER_AIRMON_MESH306245 NO2 24.51144 2025-06-04 14:00:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
3 PER_AIRMON_MESH306245 NO2 27.57584 2025-06-04 14:15:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
4 PER_AIRMON_MESH306245 NO2 27.50064 2025-06-04 14:30:00 False POINT(-1.648605 54.977836) -1.648605 111.389999 54.977836 2.0 aq_mesh_api 79521
In [ ]: