Display trains that are near a station
This commit is contained in:
parent
735191947d
commit
070849c427
|
@ -18,7 +18,7 @@ function App() {
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/station/:theme/:stopId",
|
path: "/station/:theme/:stationSlug",
|
||||||
element: <Station />
|
element: <Station />
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Autocomplete, TextField} from "@mui/material";
|
import {Autocomplete, TextField} from "@mui/material";
|
||||||
import {useRef, useState} from "react";
|
import {useRef, useState} from "react";
|
||||||
|
|
||||||
function AutocompleteStop(params) {
|
function AutocompleteStation(params) {
|
||||||
const [options, setOptions] = useState([])
|
const [options, setOptions] = useState([])
|
||||||
const previousController = useRef()
|
const previousController = useRef()
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ function AutocompleteStop(params) {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const signal = controller.signal
|
const signal = controller.signal
|
||||||
previousController.current = controller
|
previousController.current = controller
|
||||||
fetch("/api/gtfs/stop/?location_type=1&search=" + value, {signal})
|
fetch("/api/core/station/?search=" + value, {signal})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => data.results)
|
.then(data => data.results)
|
||||||
.then(setOptions)
|
.then(setOptions)
|
||||||
|
@ -40,24 +40,7 @@ function AutocompleteStop(params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOptionGroup(option) {
|
function getOptionGroup(option) {
|
||||||
switch (option.gtfs_feed) {
|
return option.country
|
||||||
case "FR-SNCF-TGV":
|
|
||||||
case "FR-SNCF-IC":
|
|
||||||
case "FR-SNCF-TER":
|
|
||||||
return "TGV/TER/Intercités"
|
|
||||||
case "FR-IDF-TN":
|
|
||||||
return "Transilien"
|
|
||||||
case "FR-EUROSTAR":
|
|
||||||
return "Eurostar"
|
|
||||||
case "IT-FRA-TI":
|
|
||||||
return "Trenitalia France"
|
|
||||||
case "ES-RENFE":
|
|
||||||
return "RENFE"
|
|
||||||
case "AT-OBB":
|
|
||||||
return "ÖBB"
|
|
||||||
default:
|
|
||||||
return option.gtfs_feed
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AutocompleteStop;
|
export default AutocompleteStation;
|
|
@ -1,11 +1,11 @@
|
||||||
import AutocompleteStop from "./AutocompleteStop"
|
import AutocompleteStation from "./AutocompleteStation"
|
||||||
import {useNavigate} from "react-router-dom"
|
import {useNavigate} from "react-router-dom"
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
function onStationSelected(event, stop) {
|
function onStationSelected(event, station) {
|
||||||
navigate(`/station/sncf/${stop.id}/`)
|
navigate(`/station/sncf/${station.slug}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
@ -13,7 +13,7 @@ function Home() {
|
||||||
<h2>
|
<h2>
|
||||||
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
|
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
|
||||||
</h2>
|
</h2>
|
||||||
<AutocompleteStop onChange={onStationSelected} />
|
<AutocompleteStation onChange={onStationSelected} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,14 @@ import {Box, Button, FormLabel} from "@mui/material";
|
||||||
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
|
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {useQuery, useQueryClient} from "@tanstack/react-query";
|
import {useQuery, useQueryClient} from "@tanstack/react-query";
|
||||||
import AutocompleteStop from "./AutocompleteStop";
|
import AutocompleteStation from "./AutocompleteStation";
|
||||||
|
|
||||||
function DateTimeSelector({stop, date, time}) {
|
function DateTimeSelector({station, date, time}) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
function onStationSelected(event, stop) {
|
function onStationSelected(event, station) {
|
||||||
if (stop !== null)
|
if (station !== null)
|
||||||
navigate(`/station/sncf/${stop.id}/`)
|
navigate(`/station/sncf/${station.slug}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
@ -20,7 +20,7 @@ function DateTimeSelector({stop, date, time}) {
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Changer la gare recherchée :
|
Changer la gare recherchée :
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<AutocompleteStop onChange={onStationSelected} />
|
<AutocompleteStation onChange={onStationSelected} />
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Modifier la date et l'heure de recherche :
|
Modifier la date et l'heure de recherche :
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
@ -32,7 +32,7 @@ function DateTimeSelector({stop, date, time}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Station() {
|
function Station() {
|
||||||
let {stopId, theme} = useParams()
|
let {theme, stationSlug} = useParams()
|
||||||
let [searchParams, _setSearchParams] = useSearchParams()
|
let [searchParams, _setSearchParams] = useSearchParams()
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||||
|
@ -41,13 +41,13 @@ function Station() {
|
||||||
let [time, setTime] = useState(searchParams.get('time') || timeNow)
|
let [time, setTime] = useState(searchParams.get('time') || timeNow)
|
||||||
|
|
||||||
useQueryClient()
|
useQueryClient()
|
||||||
const stopQuery = useQuery({
|
const stationQuery = useQuery({
|
||||||
queryKey: ['stop', stopId],
|
queryKey: ['station', stationSlug],
|
||||||
queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`)
|
queryFn: () => fetch(`/api/core/station/${stationSlug}/`)
|
||||||
.then(response => response.json()),
|
.then(response => response.json()),
|
||||||
enabled: !!stopId,
|
enabled: !!stationSlug,
|
||||||
})
|
})
|
||||||
const stop = stopQuery.data ?? {name: "Chargement…"}
|
const station = stationQuery.data ?? {name: "Chargement…"}
|
||||||
|
|
||||||
if (time === timeNow) {
|
if (time === timeNow) {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
@ -62,13 +62,13 @@ function Station() {
|
||||||
return (
|
return (
|
||||||
<div className="Station">
|
<div className="Station">
|
||||||
<header className="App-header">
|
<header className="App-header">
|
||||||
<h1>Horaires en gare de {stop.name}</h1>
|
<h1>Horaires en gare de {station.name}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<DateTimeSelector stop={stop} date={date} time={time} />
|
<DateTimeSelector station={station} date={date} time={time} />
|
||||||
<TrainsTable stop={stop} date={date} time={time} tableType="departures" />
|
<TrainsTable station={station} date={date} time={time} tableType="departures" />
|
||||||
<TrainsTable stop={stop} date={date} time={time} tableType="arrivals" />
|
<TrainsTable station={station} date={date} time={time} tableType="arrivals" />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,12 +26,12 @@ const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function TrainsTable({stop, date, time, tableType}) {
|
function TrainsTable({station, date, time, tableType}) {
|
||||||
return <>
|
return <>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TrainsTableHeader tableType={tableType} />
|
<TrainsTableHeader tableType={tableType} />
|
||||||
<TrainsTableBody stop={stop} date={date} time={time} tableType={tableType} />
|
<TrainsTableBody station={station} date={date} time={time} tableType={tableType} />
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</>
|
</>
|
||||||
|
@ -49,7 +49,7 @@ function TrainsTableHeader({tableType}) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
||||||
function TrainsTableBody({stop, date, time, tableType}) {
|
function TrainsTableBody({station, date, time, tableType}) {
|
||||||
const filterTime = useCallback((train) => {
|
const filterTime = useCallback((train) => {
|
||||||
if (tableType === "departures")
|
if (tableType === "departures")
|
||||||
return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}`
|
return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}`
|
||||||
|
@ -58,16 +58,16 @@ function TrainsTableBody({stop, date, time, tableType}) {
|
||||||
}, [date, time, tableType])
|
}, [date, time, tableType])
|
||||||
|
|
||||||
const updateTrains = useCallback(() => {
|
const updateTrains = useCallback(() => {
|
||||||
return fetch(`/api/station/next_${tableType}/?stop_id=${stop.id}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
|
return fetch(`/api/station/next_${tableType}/?station_slug=${station.slug}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => data.results)
|
.then(data => data.results)
|
||||||
.then(data => [...data])
|
.then(data => [...data])
|
||||||
}, [stop.id, date, time, tableType])
|
}, [station.id, date, time, tableType])
|
||||||
|
|
||||||
const trainsQuery = useQuery({
|
const trainsQuery = useQuery({
|
||||||
queryKey: ['trains', stop.id, tableType],
|
queryKey: ['trains', station.id, tableType],
|
||||||
queryFn: updateTrains,
|
queryFn: updateTrains,
|
||||||
enabled: !!stop.id,
|
enabled: !!station.id,
|
||||||
})
|
})
|
||||||
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
|
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ function TrainRow({train, tableType, date, time}) {
|
||||||
|
|
||||||
const stopTimesQuery = useQuery({
|
const stopTimesQuery = useQuery({
|
||||||
queryKey: ['stop_times', trip.id],
|
queryKey: ['stop_times', trip.id],
|
||||||
queryFn: () => fetch(`/api/gtfs/stop_time/?trip=${trip.id}&order=stop_sequence&limit=1000`)
|
queryFn: () => fetch(`/api/gtfs/stop_time/?${new URLSearchParams({trip: trip.id, order: 'stop_sequence', limit: 1000})}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => data.results),
|
.then(data => data.results),
|
||||||
enabled: !!trip.id,
|
enabled: !!trip.id,
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from trainvel.core.models import Station
|
||||||
from trainvel.gtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
from trainvel.gtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
||||||
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class StationSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Station
|
||||||
|
lookup_field = 'slug'
|
||||||
|
fields = ('id', 'slug', 'name', 'uic', 'uic8_sncf', 'latitude', 'longitude', 'country',
|
||||||
|
'country_hint', 'main_station_hint',)
|
||||||
|
|
||||||
|
|
||||||
class AgencySerializer(serializers.ModelSerializer):
|
class AgencySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Agency
|
model = Agency
|
||||||
|
|
|
@ -10,13 +10,28 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
|
||||||
from trainvel.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
from trainvel.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
||||||
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
||||||
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer
|
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer, StationSerializer
|
||||||
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, StopTimeUpdate, \
|
from trainvel.core.models import Station
|
||||||
Transfer, Trip, TripUpdate
|
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
|
||||||
|
StopTimeUpdate, \
|
||||||
|
Transfer, Trip, TripUpdate, PickupType
|
||||||
|
|
||||||
CACHE_CONTROL = cache_control(max_age=7200)
|
CACHE_CONTROL = cache_control(max_age=30)
|
||||||
LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified)
|
LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified)
|
||||||
LOOKUP_VALUE_REGEX = r"[\w.: |-]+"
|
LOOKUP_VALUE_REGEX = r"[\w.: |+-]+"
|
||||||
|
|
||||||
|
|
||||||
|
class StationViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
queryset = Station.objects.filter(is_suggestable=True)
|
||||||
|
serializer_class = StationSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||||
|
filterset_fields = '__all__'
|
||||||
|
search_fields = ['name', 'slug',
|
||||||
|
'info_de', 'info_en', 'info_es', 'info_fr', 'info_it', 'info_nb', 'info_nl', 'info_cs',
|
||||||
|
'info_da', 'info_hu', 'info_ja', 'info_ko', 'info_pl', 'info_pt', 'info_ru', 'info_sv',
|
||||||
|
'info_tr', 'info_zh', ]
|
||||||
|
lookup_field = 'slug'
|
||||||
|
lookup_value_regex = LOOKUP_VALUE_REGEX
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
||||||
|
@ -135,8 +150,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
stop_id = self.request.query_params.get('stop_id', None)
|
station_slug = self.request.query_params.get('station_slug', None)
|
||||||
stop_name = self.request.query_params.get('stop_name', None)
|
|
||||||
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
||||||
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
|
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
|
||||||
query_time = timedelta(seconds=int(query_time[:2]) * 3600
|
query_time = timedelta(seconds=int(query_time[:2]) * 3600
|
||||||
|
@ -148,16 +162,10 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
tomorrow = query_date + timedelta(days=1)
|
tomorrow = query_date + timedelta(days=1)
|
||||||
|
|
||||||
stop_filter = Q(stop__location_type=0)
|
stop_filter = Q(stop__location_type=0)
|
||||||
if stop_id:
|
if station_slug:
|
||||||
stop = Stop.objects.get(id=stop_id)
|
station = Station.objects.get(is_suggestable=True, slug=station_slug)
|
||||||
stops = Stop.objects.filter(Q(id=stop_id)
|
near_stops = station.get_near_stops()
|
||||||
| Q(parent_station=stop_id))
|
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
||||||
if stop.location_type == 0 and stop.parent_station_id is not None:
|
|
||||||
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
|
|
||||||
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
|
|
||||||
elif stop_name:
|
|
||||||
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
|
|
||||||
stop_filter = Q(stop__in=stops)
|
|
||||||
|
|
||||||
def calendar_filter(d: date):
|
def calendar_filter(d: date):
|
||||||
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
|
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
|
||||||
|
@ -189,7 +197,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
qs_today = StopTime.objects.filter(stop_filter) \
|
qs_today = StopTime.objects.filter(stop_filter) \
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
.annotate(departure_time_real=departure_time_real(query_date)) \
|
||||||
.filter(departure_time_real__gte=query_time) \
|
.filter(departure_time_real__gte=query_time) \
|
||||||
.filter(Q(pickup_type=0) | canceled_filter(query_date)) \
|
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(query_date)) \
|
||||||
.filter(calendar_filter(query_date)) \
|
.filter(calendar_filter(query_date)) \
|
||||||
.annotate(departure_date=Value(query_date)) \
|
.annotate(departure_date=Value(query_date)) \
|
||||||
.annotate(departure_time_24h=F('departure_time'))
|
.annotate(departure_time_24h=F('departure_time'))
|
||||||
|
@ -221,8 +229,7 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
stop_id = self.request.query_params.get('stop_id', None)
|
station_slug = self.request.query_params.get('station_slug', None)
|
||||||
stop_name = self.request.query_params.get('stop_name', None)
|
|
||||||
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
||||||
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
|
query_time = self.request.query_params.get('time', now.time().isoformat(timespec='seconds'))
|
||||||
query_time = timedelta(seconds=int(query_time[:2]) * 3600
|
query_time = timedelta(seconds=int(query_time[:2]) * 3600
|
||||||
|
@ -235,16 +242,10 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
tomorrow = query_date + timedelta(days=1)
|
tomorrow = query_date + timedelta(days=1)
|
||||||
|
|
||||||
stop_filter = Q(stop__location_type=0)
|
stop_filter = Q(stop__location_type=0)
|
||||||
if stop_id:
|
if station_slug:
|
||||||
stop = Stop.objects.get(id=stop_id)
|
station = Station.objects.get(is_suggestable=True, slug=station_slug)
|
||||||
stops = Stop.objects.filter(Q(id=stop_id)
|
near_stops = station.get_near_stops()
|
||||||
| Q(parent_station=stop_id))
|
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
||||||
if stop.location_type == 0 and stop.parent_station_id is not None:
|
|
||||||
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
|
|
||||||
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
|
|
||||||
elif stop_name:
|
|
||||||
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
|
|
||||||
stop_filter = Q(stop__in=stops)
|
|
||||||
|
|
||||||
def calendar_filter(d: date):
|
def calendar_filter(d: date):
|
||||||
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
|
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
from django.conf import settings
|
||||||
from django.db import models
|
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 django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from trainvel.gtfs.models import Country
|
from trainvel.gtfs.models import Country, Stop
|
||||||
|
|
||||||
|
|
||||||
class Station(models.Model):
|
class Station(models.Model):
|
||||||
|
@ -498,6 +501,16 @@ class Station(models.Model):
|
||||||
default=None,
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
|
@ -294,14 +294,14 @@ class Command(BaseCommand):
|
||||||
dep_h, dep_m, dep_s = map(int, dep_time.split(':'))
|
dep_h, dep_m, dep_s = map(int, dep_time.split(':'))
|
||||||
dep_time = dep_h * 3600 + dep_m * 60 + dep_s
|
dep_time = dep_h * 3600 + dep_m * 60 + dep_s
|
||||||
|
|
||||||
pickup_type = stop_time_dict.get('pickup_type', 0)
|
pickup_type = stop_time_dict.get('pickup_type', PickupType.REGULAR)
|
||||||
drop_off_type = stop_time_dict.get('drop_off_type', 0)
|
drop_off_type = stop_time_dict.get('drop_off_type', PickupType.REGULAR)
|
||||||
if stop_time_dict['stop_sequence'] == "1":
|
# if stop_time_dict['stop_sequence'] == "1":
|
||||||
# First stop
|
# # First stop
|
||||||
drop_off_type = PickupType.NONE
|
# drop_off_type = PickupType.NONE
|
||||||
elif arr_time == dep_time:
|
# elif arr_time == dep_time:
|
||||||
# Last stop
|
# # Last stop
|
||||||
pickup_type = PickupType.NONE
|
# pickup_type = PickupType.NONE
|
||||||
|
|
||||||
st = StopTime(
|
st = StopTime(
|
||||||
id=f"{gtfs_code}-{stop_time_dict['trip_id']}-{stop_time_dict['stop_id']}"
|
id=f"{gtfs_code}-{stop_time_dict['trip_id']}-{stop_time_dict['stop_id']}"
|
||||||
|
@ -349,7 +349,7 @@ class Command(BaseCommand):
|
||||||
from_stop_id=from_stop_id,
|
from_stop_id=from_stop_id,
|
||||||
to_stop_id=to_stop_id,
|
to_stop_id=to_stop_id,
|
||||||
transfer_type=transfer_dict['transfer_type'],
|
transfer_type=transfer_dict['transfer_type'],
|
||||||
min_transfer_time=transfer_dict['min_transfer_time'],
|
min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0,
|
||||||
)
|
)
|
||||||
transfers.append(transfer)
|
transfers.append(transfer)
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class Command(BaseCommand):
|
||||||
headers = {}
|
headers = {}
|
||||||
if gtfs_code == "CH-ALL":
|
if gtfs_code == "CH-ALL":
|
||||||
headers["Authorization"] = settings.OPENTRANSPORTDATA_SWISS_TOKEN
|
headers["Authorization"] = settings.OPENTRANSPORTDATA_SWISS_TOKEN
|
||||||
resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True)
|
resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True, headers=headers)
|
||||||
feed_message = FeedMessage()
|
feed_message = FeedMessage()
|
||||||
feed_message.ParseFromString(resp.content)
|
feed_message.ParseFromString(resp.content)
|
||||||
|
|
||||||
|
@ -41,87 +41,88 @@ class Command(BaseCommand):
|
||||||
f.write(str(feed_message))
|
f.write(str(feed_message))
|
||||||
|
|
||||||
for entity in feed_message.entity:
|
for entity in feed_message.entity:
|
||||||
if entity.HasField("trip_update"):
|
try:
|
||||||
trip_update = entity.trip_update
|
if entity.HasField("trip_update"):
|
||||||
trip_id = trip_update.trip.trip_id
|
trip_update = entity.trip_update
|
||||||
trip_id = f"{gtfs_code}-{trip_id}"
|
trip_id = trip_update.trip.trip_id
|
||||||
|
trip_id = f"{gtfs_code}-{trip_id}"
|
||||||
|
|
||||||
start_date = date(year=int(trip_update.trip.start_date[:4]),
|
start_date = date(year=int(trip_update.trip.start_date[:4]),
|
||||||
month=int(trip_update.trip.start_date[4:6]),
|
month=int(trip_update.trip.start_date[4:6]),
|
||||||
day=int(trip_update.trip.start_date[6:]))
|
day=int(trip_update.trip.start_date[6:]))
|
||||||
start_dt = datetime.combine(start_date, time(0), tzinfo=ZoneInfo("Europe/Paris"))
|
start_dt = datetime.combine(start_date, time(0), tzinfo=ZoneInfo("Europe/Paris"))
|
||||||
|
|
||||||
if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED:
|
if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED:
|
||||||
# C'est un trajet nouveau. On crée le trajet associé.
|
# C'est un trajet nouveau. On crée le trajet associé.
|
||||||
self.create_trip(trip_update, trip_id, start_dt, gtfs_feed)
|
self.create_trip(trip_update, trip_id, start_dt, gtfs_feed)
|
||||||
|
|
||||||
if not Trip.objects.filter(id=trip_id).exists():
|
if not Trip.objects.filter(id=trip_id).exists():
|
||||||
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
|
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Création du TripUpdate
|
# Création du TripUpdate
|
||||||
tu, _created = TripUpdate.objects.update_or_create(
|
tu, _created = TripUpdate.objects.update_or_create(
|
||||||
trip_id=trip_id,
|
trip_id=trip_id,
|
||||||
start_date=trip_update.trip.start_date,
|
start_date=trip_update.trip.start_date,
|
||||||
start_time=trip_update.trip.start_time,
|
start_time=trip_update.trip.start_time,
|
||||||
defaults=dict(
|
defaults=dict(
|
||||||
schedule_relationship=trip_update.trip.schedule_relationship,
|
schedule_relationship=trip_update.trip.schedule_relationship,
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
|
||||||
stop_id = stop_time_update.stop_id
|
|
||||||
stop_id = f"{gtfs_code}-{stop_id}"
|
|
||||||
if StopTime.objects.filter(trip_id=trip_id, stop=stop_id).exists():
|
|
||||||
st = StopTime.objects.filter(trip_id=trip_id, stop=stop_id)
|
|
||||||
if st.count() > 1:
|
|
||||||
st = st.get(stop_sequence=stop_sequence)
|
|
||||||
else:
|
|
||||||
st = st.first()
|
|
||||||
else:
|
|
||||||
# Stop is added
|
|
||||||
st = StopTime.objects.create(
|
|
||||||
id=f"{trip_id}-{stop_time_update.stop_id}",
|
|
||||||
trip_id=trip_id,
|
|
||||||
stop_id=stop_id,
|
|
||||||
defaults={
|
|
||||||
"stop_sequence": stop_sequence,
|
|
||||||
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
|
|
||||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
|
||||||
"departure_time": datetime.fromtimestamp(stop_time_update.departure.time,
|
|
||||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
|
||||||
"pickup_type": (PickupType.REGULAR if stop_time_update.departure.time
|
|
||||||
else PickupType.NONE),
|
|
||||||
"drop_off_type": (PickupType.REGULAR if stop_time_update.arrival.time
|
|
||||||
else PickupType.NONE),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
|
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
||||||
if st.pickup_type != PickupType.NONE or st.drop_off_type != PickupType.NONE:
|
stop_id = stop_time_update.stop_id
|
||||||
st.pickup_type = PickupType.NONE
|
stop_id = f"{gtfs_code}-{stop_id}"
|
||||||
st.drop_off_type = PickupType.NONE
|
if StopTime.objects.filter(trip_id=trip_id, stop=stop_id).exists():
|
||||||
|
st = StopTime.objects.filter(trip_id=trip_id, stop=stop_id)
|
||||||
|
if st.count() > 1:
|
||||||
|
st = st.get(stop_sequence=stop_sequence)
|
||||||
|
else:
|
||||||
|
st = st.first()
|
||||||
|
else:
|
||||||
|
# Stop is added
|
||||||
|
st = StopTime.objects.create(
|
||||||
|
id=f"{trip_id}-{stop_time_update.stop_id}",
|
||||||
|
trip_id=trip_id,
|
||||||
|
stop_id=stop_id,
|
||||||
|
stop_sequence=stop_sequence,
|
||||||
|
arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time,
|
||||||
|
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
||||||
|
departure_time=datetime.fromtimestamp(stop_time_update.departure.time,
|
||||||
|
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
||||||
|
pickup_type=(PickupType.REGULAR if stop_time_update.departure.time
|
||||||
|
else PickupType.NONE),
|
||||||
|
drop_off_type=(PickupType.REGULAR if stop_time_update.arrival.time
|
||||||
|
else PickupType.NONE),
|
||||||
|
)
|
||||||
|
|
||||||
|
if stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
|
||||||
|
if st.pickup_type != PickupType.NONE or st.drop_off_type != PickupType.NONE:
|
||||||
|
st.pickup_type = PickupType.NONE
|
||||||
|
st.drop_off_type = PickupType.NONE
|
||||||
|
st.save()
|
||||||
|
|
||||||
|
if st.stop_sequence != stop_sequence:
|
||||||
|
st.stop_sequence = stop_sequence
|
||||||
st.save()
|
st.save()
|
||||||
|
|
||||||
if st.stop_sequence != stop_sequence:
|
st_update = StopTimeUpdate(
|
||||||
st.stop_sequence = stop_sequence
|
trip_update=tu,
|
||||||
st.save()
|
stop_time=st,
|
||||||
|
arrival_delay=timedelta(seconds=stop_time_update.arrival.delay),
|
||||||
st_update = StopTimeUpdate(
|
arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time,
|
||||||
trip_update=tu,
|
tz=ZoneInfo("Europe/Paris")),
|
||||||
stop_time=st,
|
departure_delay=timedelta(seconds=stop_time_update.departure.delay),
|
||||||
arrival_delay=timedelta(seconds=stop_time_update.arrival.delay),
|
departure_time=datetime.fromtimestamp(stop_time_update.departure.time,
|
||||||
arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time,
|
tz=ZoneInfo("Europe/Paris")),
|
||||||
tz=ZoneInfo("Europe/Paris")),
|
schedule_relationship=stop_time_update.schedule_relationship
|
||||||
departure_delay=timedelta(seconds=stop_time_update.departure.delay),
|
or StopScheduleRelationship.SCHEDULED,
|
||||||
departure_time=datetime.fromtimestamp(stop_time_update.departure.time,
|
)
|
||||||
tz=ZoneInfo("Europe/Paris")),
|
stop_times_updates.append(st_update)
|
||||||
schedule_relationship=stop_time_update.schedule_relationship
|
else:
|
||||||
or StopScheduleRelationship.SCHEDULED,
|
self.stdout.write(str(entity))
|
||||||
)
|
except Exception as e:
|
||||||
stop_times_updates.append(st_update)
|
self.stderr.write(self.style.ERROR(f"Error while processing entity: {e}"))
|
||||||
else:
|
|
||||||
self.stdout.write(str(entity))
|
|
||||||
|
|
||||||
StopTimeUpdate.objects.bulk_create(stop_times_updates,
|
StopTimeUpdate.objects.bulk_create(stop_times_updates,
|
||||||
update_conflicts=True,
|
update_conflicts=True,
|
||||||
|
|
|
@ -151,6 +151,8 @@ REST_FRAMEWORK = {
|
||||||
'PAGE_SIZE': 20,
|
'PAGE_SIZE': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
STATION_RADIUS = 300
|
||||||
|
|
||||||
OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME"
|
OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,11 +18,12 @@ from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from trainvel.api.views import AgencyViewSet, StopViewSet, RouteViewSet, TripViewSet, StopTimeViewSet, \
|
from trainvel.api.views import AgencyViewSet, StopViewSet, RouteViewSet, StationViewSet, TripViewSet, StopTimeViewSet, \
|
||||||
CalendarViewSet, CalendarDateViewSet, TransferViewSet, FeedInfoViewSet, NextDeparturesViewSet, NextArrivalsViewSet, \
|
CalendarViewSet, CalendarDateViewSet, TransferViewSet, FeedInfoViewSet, NextDeparturesViewSet, NextArrivalsViewSet, \
|
||||||
TripUpdateViewSet, StopTimeUpdateViewSet
|
TripUpdateViewSet, StopTimeUpdateViewSet
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
router.register("core/station", StationViewSet)
|
||||||
router.register("gtfs/agency", AgencyViewSet)
|
router.register("gtfs/agency", AgencyViewSet)
|
||||||
router.register("gtfs/stop", StopViewSet)
|
router.register("gtfs/stop", StopViewSet)
|
||||||
router.register("gtfs/route", RouteViewSet)
|
router.register("gtfs/route", RouteViewSet)
|
||||||
|
|
Loading…
Reference in New Issue