Skip to content

Commit 29526fd

Browse files
authored
DBC22-4679: added weather station relations to cams (#1047)
1 parent 6bef830 commit 29526fd

8 files changed

Lines changed: 108 additions & 25 deletions

File tree

src/backend/apps/feed/tasks.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from apps.event.tasks import populate_all_event_data
55
from apps.ferry.tasks import populate_all_ferry_data, populate_coastal_ferry_data
66
from apps.rest.tasks import populate_all_rest_stop_data
7-
from apps.shared.tasks import populate_all_district_data
7+
from apps.shared.tasks import populate_all_district_data, update_object_relations
88
from apps.weather.tasks import (
99
populate_all_high_elevation_forecast_data,
1010
populate_all_local_weather_data,
@@ -107,6 +107,12 @@ def update_districts():
107107
populate_all_district_data()
108108

109109

110+
@db_periodic_task(crontab(hour="*/24", minute="30", day_of_week="0"))
111+
@lock_task('relations-lock')
112+
def update_relations():
113+
update_object_relations()
114+
115+
110116
@db_periodic_task(crontab(minute="*/20"))
111117
@lock_task('wildfires-lock')
112118
def update_wildfires():

src/backend/apps/shared/tasks.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from apps.feed.client import FeedClient
66
from apps.shared.models import Area
77
from apps.shared.serializers import DistrictSerializer
8-
from apps.webcam.tasks import update_camera_area_relations
8+
from apps.webcam.tasks import update_camera_relations
99
from django.core.exceptions import ObjectDoesNotExist
1010
from email_log.models import Email
1111
from huey import crontab
@@ -42,9 +42,7 @@ def populate_all_district_data():
4242
for district_data in feed_data:
4343
populate_district_from_data(district_data)
4444

45-
update_area_relations()
4645

47-
48-
def update_area_relations():
49-
update_camera_area_relations()
46+
def update_object_relations():
47+
update_camera_relations()
5048
update_event_area_relations()

src/backend/apps/shared/test_urls.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from apps.event import views as event_views
22
from apps.shared import views as shared_views
3-
from apps.webcam import views as webcam_views
3+
from apps.webcam import views as camera_views
44
from django.urls import include, path
55
from rest_framework import routers
66

77
test_router = routers.DefaultRouter()
8-
test_router.register(r"webcams", webcam_views.WebcamTestViewSet,
9-
basename="test_webcams")
10-
test_router.register(r"delays", event_views.EventTestViewSet,
11-
basename="test_delays")
8+
test_router.register(r"cameras", camera_views.CameraViewSet,
9+
basename="test_cameras")
10+
test_router.register(r"events", event_views.EventTestViewSet,
11+
basename="test_events")
1212

1313
urlpatterns = [
1414
path("", include(test_router.urls)),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Generated by Django 4.2.22 on 2025-08-29 20:35
2+
3+
import django.db.models.deletion
4+
from apps.webcam.tasks import update_camera_relations
5+
from django.db import migrations, models
6+
7+
8+
def update_relations(apps, schema_editor):
9+
update_camera_relations()
10+
11+
12+
class Migration(migrations.Migration):
13+
14+
dependencies = [
15+
('weather', '0016_currentweather_code'),
16+
('webcam', '0010_webcam_group_webcam_nearby_objs'),
17+
]
18+
19+
operations = [
20+
migrations.AddField(
21+
model_name='webcam',
22+
name='hev_station',
23+
field=models.ForeignKey(
24+
blank=True,
25+
null=True,
26+
on_delete=django.db.models.deletion.SET_NULL,
27+
to='weather.highelevationforecast'
28+
),
29+
),
30+
migrations.AddField(
31+
model_name='webcam',
32+
name='local_weather_station',
33+
field=models.ForeignKey(
34+
blank=True,
35+
null=True,
36+
on_delete=django.db.models.deletion.SET_NULL,
37+
to='weather.currentweather'
38+
),
39+
),
40+
migrations.AddField(
41+
model_name='webcam',
42+
name='regional_weather_station',
43+
field=models.ForeignKey(
44+
blank=True,
45+
null=True,
46+
on_delete=django.db.models.deletion.SET_NULL,
47+
to='weather.regionalweather'
48+
),
49+
),
50+
migrations.RunPython(update_relations),
51+
]

src/backend/apps/webcam/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from apps.shared.models import Area, BaseModel
2+
from apps.weather.models import CurrentWeather, HighElevationForecast, RegionalWeather
23
from django.contrib.gis.db import models
34

45

@@ -19,7 +20,12 @@ class Webcam(BaseModel):
1920
location = models.PointField()
2021
orientation = models.CharField(max_length=32, blank=True)
2122
elevation = models.PositiveSmallIntegerField()
23+
24+
# Foreign keys
2225
area = models.ForeignKey(Area, on_delete=models.SET_NULL, null=True, blank=False)
26+
regional_weather_station = models.ForeignKey(RegionalWeather, on_delete=models.SET_NULL, null=True, blank=True)
27+
local_weather_station = models.ForeignKey(CurrentWeather, on_delete=models.SET_NULL, null=True, blank=True)
28+
hev_station = models.ForeignKey(HighElevationForecast, on_delete=models.SET_NULL, null=True, blank=True)
2329

2430
# Relations
2531
group = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True)

src/backend/apps/webcam/tasks.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from apps.webcam.models import Webcam
2424
from apps.webcam.serializers import WebcamSerializer
2525
from django.conf import settings
26+
from django.contrib.gis.db.models.functions import Distance
2627
from django.contrib.gis.geos import LineString, MultiLineString, Point
2728
from django.core.exceptions import ObjectDoesNotExist
2829
from django.db.models import F
@@ -341,10 +342,40 @@ def build_route_geometries(coords=hwy_coords):
341342
RouteGeometry.objects.filter(id=key).update(routes=MultiLineString(ls_routes))
342343

343344

344-
def update_camera_area_relations():
345+
# DBC22-4679 find and update the nearest weather stations for a camera
346+
def update_camera_weather_station(camera, weather_class):
347+
meters_in_degrees = 0.000008983152841195 # 1 meter in degrees at the equator
348+
349+
stations_qs = weather_class.objects.filter(
350+
location__dwithin=(camera.location, 30000 * meters_in_degrees), # 30km in degrees
351+
elevation__lte=camera.elevation + 300,
352+
elevation__gte=camera.elevation - 300, # within 300m elevation,
353+
) if isinstance(weather_class, CurrentWeather) else weather_class.objects.all()
354+
355+
closest_station = stations_qs.annotate(
356+
distance=Distance('location', camera.location)
357+
).order_by('distance').first()
358+
359+
if closest_station:
360+
if weather_class == CurrentWeather:
361+
Webcam.objects.filter(id=camera.id).update(local_weather_station=closest_station)
362+
363+
elif weather_class == RegionalWeather:
364+
Webcam.objects.filter(id=camera.id).update(regional_weather_station=closest_station)
365+
366+
elif weather_class == HighElevationForecast:
367+
Webcam.objects.filter(id=camera.id).update(hev_station=closest_station)
368+
369+
370+
def update_camera_relations():
345371
for area in Area.objects.all():
346372
Webcam.objects.filter(location__within=area.geometry).update(area=area)
347373

374+
for camera in Webcam.objects.all():
375+
update_camera_weather_station(camera, RegionalWeather)
376+
update_camera_weather_station(camera, CurrentWeather)
377+
update_camera_weather_station(camera, HighElevationForecast)
378+
348379

349380
def update_camera_group_id(camera):
350381
try:

src/backend/apps/webcam/urls.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
from django.urls import include, path
33
from rest_framework import routers
44

5-
webcam_router = routers.DefaultRouter()
6-
webcam_router.register(r"", webcam_views.WebcamViewSet, basename="webcams")
5+
camera_router = routers.DefaultRouter()
6+
camera_router.register(r"", webcam_views.CameraViewSet, basename="webcams")
77

88
urlpatterns = [
9-
path("", include(webcam_router.urls)),
9+
path("", include(camera_router.urls)),
1010
]

src/backend/apps/webcam/views.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,6 @@
33
from rest_framework import viewsets
44

55

6-
class WebcamAPI:
6+
class CameraViewSet(viewsets.ReadOnlyModelViewSet):
77
queryset = Webcam.objects.filter(should_appear=True)
88
serializer_class = WebcamSerializer
9-
10-
11-
class WebcamViewSet(WebcamAPI, viewsets.ReadOnlyModelViewSet):
12-
pass
13-
14-
15-
class WebcamTestViewSet(viewsets.ReadOnlyModelViewSet):
16-
queryset = WebcamAPI.queryset
17-
serializer_class = WebcamAPI.serializer_class

0 commit comments

Comments
 (0)