Skip to content

Commit 74c0daf

Browse files
committed
DBC22-5477: DMS layer
1 parent e6a0ac5 commit 74c0daf

63 files changed

Lines changed: 1581 additions & 7 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ DRIVEBC_COSTAL_FERRY_DATA_URL=<costal ferry data url>
7979
DRIVEBC_OPENMAPS_API_URL=<openmaps api url>
8080
WEATHER_CLIENT_ID=<weather client id>
8181
WEATHER_CLIENT_SECRET=<weather client secret>
82+
DRIVEBC_DMS_API_BASE_URL=<dms api url>
8283

8384
# TESTS
8485
# optional: set to config.test.DbcRunner to user test runner allowing for

src/backend/apps/dms/__init__.py

Whitespace-only changes.

src/backend/apps/dms/admin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from apps.dms.models import Dms
2+
from django.contrib.gis import admin
3+
4+
5+
class DmsAdmin(admin.GISModelAdmin):
6+
readonly_fields = ('id', )
7+
8+
admin.site.register(Dms, DmsAdmin)

src/backend/apps/dms/apps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from django.apps import AppConfig
2+
3+
4+
class DmsConfig(AppConfig):
5+
name = 'apps.dms'

src/backend/apps/dms/management/__init__.py

Whitespace-only changes.

src/backend/apps/dms/management/commands/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from apps.dms.tasks import populate_all_dms_data
2+
from django.core.management.base import BaseCommand
3+
4+
5+
class Command(BaseCommand):
6+
def handle(self, *args, **options):
7+
populate_all_dms_data()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Generated by Django 5.2.10 on 2026-01-28 15:11
2+
3+
import django.contrib.gis.db.models.fields
4+
import django_prometheus.models
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
initial = True
11+
12+
dependencies = [
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Dms',
18+
fields=[
19+
('created_at', models.DateTimeField(auto_now_add=True)),
20+
('modified_at', models.DateTimeField(auto_now=True)),
21+
('id', models.CharField(blank=True, default='', max_length=128, primary_key=True, serialize=False)),
22+
('name', models.CharField(blank=True, default='', max_length=128)),
23+
('name_override', models.CharField(blank=True, max_length=128)),
24+
('category', models.CharField(blank=True, default='', max_length=128)),
25+
('description', models.CharField(blank=True, default='', max_length=750)),
26+
('roadway_name', models.CharField(blank=True, default='', max_length=128)),
27+
('roadway_direction', models.CharField(blank=True, default='', max_length=64)),
28+
('static_text', models.CharField(blank=True, default='', max_length=256)),
29+
('message_text', models.CharField(blank=True, default='', max_length=512)),
30+
('status', models.CharField(blank=True, default='', max_length=64)),
31+
('location', django.contrib.gis.db.models.fields.GeometryField(blank=True, null=True, srid=4326)),
32+
('updated_datetime_utc', models.DateTimeField(blank=True, null=True)),
33+
('message_expiry_datetime_utc', models.DateTimeField(blank=True, null=True)),
34+
('cache_datetime_utc', models.DateTimeField(blank=True, null=True)),
35+
('message_display_1', models.TextField(blank=True, default='')),
36+
('message_display_2', models.TextField(blank=True, default='')),
37+
('message_display_3', models.TextField(blank=True, default='')),
38+
('is_on', models.BooleanField(default=True)),
39+
],
40+
options={
41+
'abstract': False,
42+
},
43+
bases=(django_prometheus.models.ExportModelOperationsMixin('dms'), models.Model),
44+
),
45+
]

src/backend/apps/dms/migrations/__init__.py

Whitespace-only changes.

src/backend/apps/dms/models.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from apps.shared.models import BaseModel
2+
from django.contrib.gis.db import models
3+
from django.contrib.gis.geos import Point
4+
from django.core.cache import cache
5+
from django_prometheus.models import ExportModelOperationsMixin
6+
from apps.shared.enums import CacheKey
7+
import re
8+
from typing import Tuple
9+
10+
NTCIP_TOKEN_PATTERN = re.compile(r"\[[^\]]+\]")
11+
12+
def parse_dms_pages(raw: str) -> Tuple[str, str, str]:
13+
if not raw:
14+
return "", "", ""
15+
16+
text = raw
17+
18+
# Normalize newlines
19+
text = re.sub(r"\[nl\]", "\n", text, flags=re.IGNORECASE)
20+
21+
# Split pages BEFORE removing other tokens
22+
pages = re.split(r"\[np\]", text, flags=re.IGNORECASE)
23+
24+
cleaned_pages = []
25+
26+
for page in pages[:3]: # max 3 pages
27+
# Remove remaining NTCIP formatting tokens
28+
page = NTCIP_TOKEN_PATTERN.sub("", page)
29+
30+
# Normalize whitespace
31+
page = re.sub(r"[ \t]+", " ", page)
32+
page = re.sub(r"\n{3,}", "\n\n", page)
33+
34+
cleaned_pages.append(page.strip())
35+
36+
# Pad missing pages
37+
while len(cleaned_pages) < 3:
38+
cleaned_pages.append("")
39+
40+
return tuple(cleaned_pages)
41+
42+
43+
class Dms(ExportModelOperationsMixin('dms'), BaseModel):
44+
id = models.CharField(primary_key=True, max_length=128, blank=True, default='')
45+
name = models.CharField(max_length=128, blank=True, default='')
46+
name_override = models.CharField(blank=True, max_length=128)
47+
category = models.CharField(max_length=128, blank=True, default='')
48+
description = models.CharField(max_length=750, blank=True, default='')
49+
roadway_name = models.CharField(max_length=128, blank=True, default='')
50+
roadway_direction = models.CharField(max_length=64, blank=True, default='')
51+
static_text = models.CharField(max_length=256, blank=True, default='')
52+
message_text = models.CharField(max_length=512, blank=True, default='')
53+
status = models.CharField(max_length=64, blank=True, default='')
54+
location = models.GeometryField(blank=True, null=True)
55+
updated_datetime_utc = models.DateTimeField(blank=True, null=True)
56+
message_expiry_datetime_utc = models.DateTimeField(blank=True, null=True)
57+
cache_datetime_utc = models.DateTimeField(blank=True, null=True)
58+
59+
message_display_1 = models.TextField(blank=True, default="")
60+
message_display_2 = models.TextField(blank=True, default="")
61+
message_display_3 = models.TextField(blank=True, default="")
62+
63+
is_on = models.BooleanField(default=True)
64+
65+
def __str__(self):
66+
return f"dms for {self.pk}"
67+
68+
def save(self, *args, **kwargs):
69+
self.message_display_1, self.message_display_2, self.message_display_3 = (
70+
parse_dms_pages(self.message_text)
71+
)
72+
super().save(*args, **kwargs)
73+
cache.delete(CacheKey.DMS_LIST)

0 commit comments

Comments
 (0)