520 lines
11 KiB
Python
520 lines
11 KiB
Python
from django.conf import settings
|
|
from django.db import models
|
|
from django.db.models import F, QuerySet
|
|
from django.db.models.functions import ACos, Sin, Radians, Cos
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from trainvel.gtfs.models import Country, Stop
|
|
|
|
|
|
class Station(models.Model):
|
|
"""
|
|
Describes the content of the stations CSV file generated by Trainline.
|
|
The CSV file can be found at https://raw.githubusercontent.com/trainline-eu/stations/master/stations.csv
|
|
"""
|
|
|
|
name = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("name"),
|
|
)
|
|
|
|
slug = models.SlugField(
|
|
max_length=255,
|
|
verbose_name=_("slug"),
|
|
)
|
|
|
|
uic = models.IntegerField(
|
|
verbose_name=_("UIC"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
uic8_sncf = models.IntegerField(
|
|
verbose_name=_("UIC8 SNCF"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
latitude = models.FloatField(
|
|
verbose_name=_("latitude"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
longitude = models.FloatField(
|
|
verbose_name=_("longitude"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
parent_station = models.ForeignKey(
|
|
"Station",
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_("parent station"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
related_name="children",
|
|
)
|
|
|
|
country = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("country"),
|
|
choices=Country,
|
|
)
|
|
|
|
time_zone = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("timezone"),
|
|
)
|
|
|
|
is_city = models.BooleanField(
|
|
verbose_name=_("is city"),
|
|
)
|
|
|
|
is_main_station = models.BooleanField(
|
|
verbose_name=_("is main station"),
|
|
)
|
|
|
|
is_airport = models.BooleanField(
|
|
verbose_name=_("is airport"),
|
|
)
|
|
|
|
is_suggestable = models.BooleanField(
|
|
verbose_name=_("is suggestable"),
|
|
)
|
|
|
|
country_hint = models.BooleanField(
|
|
verbose_name=_("country hint"),
|
|
)
|
|
|
|
main_station_hint = models.BooleanField(
|
|
verbose_name=_("main station hint"),
|
|
)
|
|
|
|
sncf_id = models.CharField(
|
|
max_length=5,
|
|
verbose_name=_("SNCF ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
sncf_tvs_id = models.CharField(
|
|
max_length=3,
|
|
verbose_name=_("SNCF TVS ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
sncf_is_enabled = models.BooleanField(
|
|
verbose_name=_("SNCF is enabled"),
|
|
)
|
|
|
|
entur_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Entur ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
entur_is_enabled = models.BooleanField(
|
|
verbose_name=_("Entur is enabled"),
|
|
)
|
|
|
|
db_id = models.IntegerField(
|
|
verbose_name=_("DB ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
db_is_enabled = models.BooleanField(
|
|
verbose_name=_("DB is enabled"),
|
|
)
|
|
|
|
busbud_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Busbud ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
busbud_is_enabled = models.BooleanField(
|
|
verbose_name=_("Busbud is enabled"),
|
|
)
|
|
|
|
distribusion_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("distribusion ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
distribusion_is_enabled = models.BooleanField(
|
|
verbose_name=_("distribusion is enabled"),
|
|
)
|
|
|
|
flixbus_id = models.IntegerField(
|
|
verbose_name=_("Flixbus ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
flixbus_is_enabled = models.BooleanField(
|
|
verbose_name=_("Flixbus is enabled"),
|
|
)
|
|
|
|
cff_id = models.IntegerField(
|
|
verbose_name=_("CFF ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
cff_is_enabled = models.BooleanField(
|
|
verbose_name=_("CFF is enabled"),
|
|
)
|
|
|
|
leoexpress_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Leo Express ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
leoexpress_is_enabled = models.BooleanField(
|
|
verbose_name=_("Leo Express is enabled"),
|
|
)
|
|
|
|
obb_id = models.IntegerField(
|
|
verbose_name=_("ÖBB ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
obb_is_enabled = models.BooleanField(
|
|
verbose_name=_("ÖBB is enabled"),
|
|
)
|
|
|
|
ouigo_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Ouigo ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
ouigo_is_enabled = models.BooleanField(
|
|
verbose_name=_("Ouigo is enabled"),
|
|
)
|
|
|
|
trenitalia_id = models.IntegerField(
|
|
verbose_name=_("Trenitalia ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
trenitalia_is_enabled = models.BooleanField(
|
|
verbose_name=_("Trenitalia is enabled"),
|
|
)
|
|
|
|
trenitalia_rtvt_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Trenitalia RTVT ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
trenord_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Trenord ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
ntv_rtiv_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("NTV RTIV ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
ntv_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("NTV ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
ntv_is_enabled = models.BooleanField(
|
|
verbose_name=_("NTV is enabled"),
|
|
)
|
|
|
|
hkx_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("HKX ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
hkx_is_enabled = models.BooleanField(
|
|
verbose_name=_("HKX is enabled"),
|
|
)
|
|
|
|
renfe_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Renfe ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
renfe_is_enabled = models.BooleanField(
|
|
verbose_name=_("Renfe is enabled"),
|
|
)
|
|
|
|
atoc_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("ATOC ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
atoc_is_enabled = models.BooleanField(
|
|
verbose_name=_("ATOC is enabled"),
|
|
)
|
|
|
|
benerail_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Benerail ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
benerail_is_enabled = models.BooleanField(
|
|
verbose_name=_("Benerail is enabled"),
|
|
)
|
|
|
|
westbahn_id = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("Westbahn ID"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
westbahn_is_enabled = models.BooleanField(
|
|
verbose_name=_("Westbahn is enabled"),
|
|
)
|
|
|
|
sncf_self_service_machine = models.BooleanField(
|
|
verbose_name=_("SNCF self-service machine"),
|
|
)
|
|
|
|
same_as = models.ForeignKey(
|
|
"Station",
|
|
on_delete=models.CASCADE,
|
|
verbose_name=_("same as"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
related_name="same_as_other",
|
|
)
|
|
|
|
info_de = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (DE)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_en = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (EN)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_es = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (ES)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_fr = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (FR)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_it = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (IT)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_nb = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (NB)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_nl = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (NL)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_cs = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (CS)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_da = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (DA)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_hu = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (HU)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_ja = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (JA)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_ko = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (KO)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_pl = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (PL)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_pt = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (PT)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_ru = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (RU)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_sv = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (SV)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_tr = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (TR)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
info_zh = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("info (ZH)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
normalized_code_trainline = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("normalized code (Trainline)"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
iata_airport_code = models.CharField(
|
|
max_length=255,
|
|
verbose_name=_("IATA airport code"),
|
|
blank=True,
|
|
null=True,
|
|
default=None,
|
|
)
|
|
|
|
def get_near_stops(self, radius: float = settings.STATION_RADIUS) -> QuerySet[Stop]:
|
|
"""
|
|
Returns a queryset of all stops that are in a radius of radius meters around the station.
|
|
It calculates a distance from each stop to the station using spatial coordinates.
|
|
"""
|
|
return Stop.objects.annotate(distance=6371000 * ACos(
|
|
Sin(Radians(self.latitude)) * Sin(Radians(F('lat')))
|
|
+ Cos(Radians(self.latitude)) * Cos(Radians(F('lat'))) * Cos(Radians(F('lon')) - Radians(self.longitude))))\
|
|
.filter(distance__lte=radius)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Meta:
|
|
verbose_name = _("station")
|
|
verbose_name_plural = _("stations")
|