Geographical Support
Class |
Purpose |
|---|---|
|
A validated (latitude, longitude) point |
|
A shapely |
Both can be attached to TimeSeries and TimeSeriesTable objects.
[1]:
from datetime import datetime, timedelta, timezone
import numpy as np
import timedatamodel as tdm
base = datetime(2024, 1, 15, tzinfo=timezone.utc)
timestamps = [base + timedelta(hours=i) for i in range(24)]
rng = np.random.default_rng(42)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 5
1 from datetime import datetime, timedelta, timezone
3 import numpy as np
----> 5 import timedatamodel as tdm
7 base = datetime(2024, 1, 15, tzinfo=timezone.utc)
8 timestamps = [base + timedelta(hours=i) for i in range(24)]
ModuleNotFoundError: No module named 'timedatamodel'
GeoLocation — point coordinates
GeoLocation is a frozen dataclass that validates latitude ∈ [−90, 90] and longitude ∈ [−180, 180].
[2]:
oslo = tdm.GeoLocation(latitude=59.91, longitude=10.75)
bergen = tdm.GeoLocation(latitude=60.39, longitude=5.32)
tromsoe = tdm.GeoLocation(latitude=69.65, longitude=18.96)
print(f"Oslo: {oslo}")
print(f"Bergen: {bergen}")
print(f"Tromsø: {tromsoe}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[2], line 1
----> 1 oslo = tdm.GeoLocation(latitude=59.91, longitude=10.75)
2 bergen = tdm.GeoLocation(latitude=60.39, longitude=5.32)
3 tromsoe = tdm.GeoLocation(latitude=69.65, longitude=18.96)
NameError: name 'tdm' is not defined
[3]:
try:
bad = tdm.GeoLocation(latitude=200, longitude=0)
except ValueError as e:
print(f"Validation error: {e}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[3], line 2
1 try:
----> 2 bad = tdm.GeoLocation(latitude=200, longitude=0)
3 except ValueError as e:
4 print(f"Validation error: {e}")
NameError: name 'tdm' is not defined
Distance, bearing, and midpoint
GeoLocation provides Haversine-based spatial methods — no external dependency required.
[4]:
d_km = oslo.distance_to(bergen)
d_mi = oslo.distance_to(bergen, unit="mi")
print(f"Oslo → Bergen: {d_km:.1f} km ({d_mi:.1f} mi)")
d_tromsoe = oslo.distance_to(tromsoe)
print(f"Oslo → Tromsø: {d_tromsoe:.1f} km")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[4], line 1
----> 1 d_km = oslo.distance_to(bergen)
2 d_mi = oslo.distance_to(bergen, unit="mi")
3 print(f"Oslo → Bergen: {d_km:.1f} km ({d_mi:.1f} mi)")
NameError: name 'oslo' is not defined
[5]:
bearing = oslo.bearing_to(bergen)
print(f"Initial bearing Oslo → Bergen: {bearing:.1f}°")
mid = oslo.midpoint(bergen)
print(f"Geographic midpoint: {mid}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[5], line 1
----> 1 bearing = oslo.bearing_to(bergen)
2 print(f"Initial bearing Oslo → Bergen: {bearing:.1f}°")
4 mid = oslo.midpoint(bergen)
NameError: name 'oslo' is not defined
Offset — displace a point
offset(distance_km, bearing_deg) produces a new GeoLocation displaced from the original.
[6]:
east_of_oslo = oslo.offset(100, 90) # 100 km due east
print(f"100 km east of Oslo: {east_of_oslo}")
print(f"Verify distance: {oslo.distance_to(east_of_oslo):.1f} km")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[6], line 1
----> 1 east_of_oslo = oslo.offset(100, 90) # 100 km due east
2 print(f"100 km east of Oslo: {east_of_oslo}")
3 print(f"Verify distance: {oslo.distance_to(east_of_oslo):.1f} km")
NameError: name 'oslo' is not defined
Attaching location to a TimeSeries
Pass a GeoLocation via the location parameter. It shows up in the rich repr.
[7]:
ts_oslo = tdm.TimeSeries(
tdm.Frequency.PT1H,
timezone="Europe/Oslo",
timestamps=timestamps,
values=(5.0 + rng.normal(0, 2, 24)).tolist(),
name="temperature",
unit="°C",
description="Hourly temperature at Oslo Blindern",
data_type=tdm.DataType.MEASUREMENT,
location=oslo,
)
ts_oslo
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[7], line 1
----> 1 ts_oslo = tdm.TimeSeries(
2 tdm.Frequency.PT1H,
3 timezone="Europe/Oslo",
4 timestamps=timestamps,
5 values=(5.0 + rng.normal(0, 2, 24)).tolist(),
6 name="temperature",
7 unit="°C",
8 description="Hourly temperature at Oslo Blindern",
9 data_type=tdm.DataType.MEASUREMENT,
10 location=oslo,
11 )
12 ts_oslo
NameError: name 'tdm' is not defined
[8]:
print(f"Location: {ts_oslo.location}")
print(f"Lat: {ts_oslo.location.latitude}, Lon: {ts_oslo.location.longitude}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[8], line 1
----> 1 print(f"Location: {ts_oslo.location}")
2 print(f"Lat: {ts_oslo.location.latitude}, Lon: {ts_oslo.location.longitude}")
NameError: name 'ts_oslo' is not defined
Per-column locations in a TimeSeriesTable
When columns represent different physical locations, attach one GeoLocation per column.
[9]:
table = tdm.TimeSeriesTable(
tdm.Frequency.PT1H,
timezone="Europe/Oslo",
timestamps=timestamps,
values=np.column_stack([
5.0 + rng.normal(0, 2, 24),
2.0 + rng.normal(0, 3, 24),
-8.0 + rng.normal(0, 4, 24),
]),
names=["Oslo", "Bergen", "Tromsø"],
units=["°C", "°C", "°C"],
locations=[oslo, bergen, tromsoe],
data_types=[tdm.DataType.MEASUREMENT],
)
table
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[9], line 1
----> 1 table = tdm.TimeSeriesTable(
2 tdm.Frequency.PT1H,
3 timezone="Europe/Oslo",
4 timestamps=timestamps,
5 values=np.column_stack([
6 5.0 + rng.normal(0, 2, 24),
7 2.0 + rng.normal(0, 3, 24),
8 -8.0 + rng.normal(0, 4, 24),
9 ]),
10 names=["Oslo", "Bergen", "Tromsø"],
11 units=["°C", "°C", "°C"],
12 locations=[oslo, bergen, tromsoe],
13 data_types=[tdm.DataType.MEASUREMENT],
14 )
15 table
NameError: name 'tdm' is not defined
[10]:
for i, name in enumerate(table.column_names):
loc = table.locations[i] if len(table.locations) > 1 else table.locations[0]
print(f"{name:8s} {loc}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[10], line 1
----> 1 for i, name in enumerate(table.column_names):
2 loc = table.locations[i] if len(table.locations) > 1 else table.locations[0]
3 print(f"{name:8s} {loc}")
NameError: name 'table' is not defined
Computing distances between stations
[11]:
stations = {"Oslo": oslo, "Bergen": bergen, "Tromsø": tromsoe}
print("Pairwise distances (km):")
for a_name, a_loc in stations.items():
for b_name, b_loc in stations.items():
if a_name < b_name:
print(f" {a_name:8s} ↔ {b_name:8s} {a_loc.distance_to(b_loc):7.1f} km")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[11], line 1
----> 1 stations = {"Oslo": oslo, "Bergen": bergen, "Tromsø": tromsoe}
3 print("Pairwise distances (km):")
4 for a_name, a_loc in stations.items():
NameError: name 'oslo' is not defined
GeoArea — polygon regions
GeoArea wraps a shapely Polygon for representing bidding zones, countries, or catchment areas.shapely dependency:pip install timedatamodel[geo]
[12]:
try:
southern_norway = tdm.GeoArea.from_coordinates(
[
(57.5, 4.5),
(57.5, 12.5),
(63.0, 12.5),
(63.0, 4.5),
(57.5, 4.5),
],
name="Southern Norway",
)
print(f"Area name: {southern_norway.name}")
print(f"Bounds: {southern_norway.bounds}")
print(f"Centroid: {southern_norway.centroid}")
except ImportError:
southern_norway = None
print("shapely not installed — skip this cell or: pip install timedatamodel[geo]")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[12], line 2
1 try:
----> 2 southern_norway = tdm.GeoArea.from_coordinates(
3 [
4 (57.5, 4.5),
5 (57.5, 12.5),
6 (63.0, 12.5),
7 (63.0, 4.5),
8 (57.5, 4.5),
9 ],
10 name="Southern Norway",
11 )
13 print(f"Area name: {southern_norway.name}")
14 print(f"Bounds: {southern_norway.bounds}")
NameError: name 'tdm' is not defined
Bounding box — quick rectangular areas
GeoArea.bounding_box(center, radius_km) creates a rectangular area around a point.
[13]:
try:
oslo_region = tdm.GeoArea.bounding_box(oslo, radius_km=50, name="Oslo 50km")
print(f"Area: {oslo_region.name}")
print(f"Bounds: {oslo_region.bounds}")
print(f"Centroid: {oslo_region.centroid}")
except ImportError:
oslo_region = None
print("shapely not installed — skipping")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[13], line 2
1 try:
----> 2 oslo_region = tdm.GeoArea.bounding_box(oslo, radius_km=50, name="Oslo 50km")
3 print(f"Area: {oslo_region.name}")
4 print(f"Bounds: {oslo_region.bounds}")
NameError: name 'tdm' is not defined
Spatial queries
GeoArea supports contains_point, contains_area, and overlaps.GeoLocation has a matching is_within(area) method.[14]:
try:
if southern_norway is not None:
for name, loc in stations.items():
inside = southern_norway.contains_point(loc)
also = loc.is_within(southern_norway)
print(f"{name:8s} in Southern Norway? {inside} (is_within: {also})")
except ImportError:
print("shapely not installed — skipping")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[14], line 2
1 try:
----> 2 if southern_norway is not None:
3 for name, loc in stations.items():
4 inside = southern_norway.contains_point(loc)
NameError: name 'southern_norway' is not defined
[15]:
try:
if southern_norway is not None and oslo_region is not None:
print(f"Southern Norway contains Oslo 50km region? {southern_norway.contains_area(oslo_region)}")
print(f"Oslo 50km region overlaps Southern Norway? {oslo_region.overlaps(southern_norway)}")
except ImportError:
print("shapely not installed — skipping")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[15], line 2
1 try:
----> 2 if southern_norway is not None and oslo_region is not None:
3 print(f"Southern Norway contains Oslo 50km region? {southern_norway.contains_area(oslo_region)}")
4 print(f"Oslo 50km region overlaps Southern Norway? {oslo_region.overlaps(southern_norway)}")
NameError: name 'southern_norway' is not defined
Attaching a GeoArea to a TimeSeries
Both GeoLocation and GeoArea are accepted by the location parameter.
[16]:
try:
if southern_norway is not None:
ts_area = tdm.TimeSeries(
tdm.Frequency.PT1H,
timestamps=timestamps,
values=(120 + rng.normal(0, 15, 24)).tolist(),
name="wind_south",
unit="MW",
description="Aggregated wind production in southern Norway",
location=southern_norway,
)
ts_area
except ImportError:
print("shapely not installed — skipping")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[16], line 2
1 try:
----> 2 if southern_norway is not None:
3 ts_area = tdm.TimeSeries(
4 tdm.Frequency.PT1H,
5 timestamps=timestamps,
(...) 10 location=southern_norway,
11 )
12 ts_area
NameError: name 'southern_norway' is not defined
Distance between areas and points
GeoArea.distance_to() accepts both GeoLocation and GeoArea.[17]:
try:
if southern_norway is not None:
print(f"Southern Norway → Oslo (contained): {southern_norway.distance_to(oslo):.1f} km")
print(f"Southern Norway → Tromsø (outside): {southern_norway.distance_to(tromsoe):.1f} km")
except ImportError:
print("shapely not installed — skipping")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[17], line 2
1 try:
----> 2 if southern_norway is not None:
3 print(f"Southern Norway → Oslo (contained): {southern_norway.distance_to(oslo):.1f} km")
4 print(f"Southern Norway → Tromsø (outside): {southern_norway.distance_to(tromsoe):.1f} km")
NameError: name 'southern_norway' is not defined
Location survives serialization
GeoLocation round-trips through JSON.[18]:
json_str = ts_oslo.to_json()
ts_back = tdm.TimeSeries.from_json(json_str, location=oslo)
print(f"Name: {ts_back.name}")
print(f"Location: {ts_back.location}")
print(f"Match: {ts_back.location == oslo}")
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[18], line 1
----> 1 json_str = ts_oslo.to_json()
2 ts_back = tdm.TimeSeries.from_json(json_str, location=oslo)
4 print(f"Name: {ts_back.name}")
NameError: name 'ts_oslo' is not defined
Summary
Feature |
API |
|---|---|
Point coordinates |
|
Distance |
|
Bearing |
|
Midpoint |
|
Offset |
|
Polygon regions |
|
Quick box |
|
Spatial queries |
|
Attach to series |
|
Next up: nb_10 demonstrates hierarchical time series — organising series into tree structures with bottom-up aggregation.