Compare commits
No commits in common. "bc23d63c43367586d4ac8a4abeb06e3ad691d4c0" and "a4a8cd9e9f77500c99f15b539deff8c9d9337b0e" have entirely different histories.
bc23d63c43
...
a4a8cd9e9f
26
trainvel-front/package-lock.json
generated
26
trainvel-front/package-lock.json
generated
@ -10,7 +10,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.17",
|
|
||||||
"@mui/material": "^5.15.6",
|
"@mui/material": "^5.15.6",
|
||||||
"@mui/x-date-pickers": "^6.19.2",
|
"@mui/x-date-pickers": "^6.19.2",
|
||||||
"@tanstack/query-sync-storage-persister": "^5.18.0",
|
"@tanstack/query-sync-storage-persister": "^5.18.0",
|
||||||
@ -3477,31 +3476,6 @@
|
|||||||
"url": "https://opencollective.com/mui-org"
|
"url": "https://opencollective.com/mui-org"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@mui/icons-material": {
|
|
||||||
"version": "5.15.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.17.tgz",
|
|
||||||
"integrity": "sha512-xVzl2De7IY36s/keHX45YMiCpsIx3mNv2xwDgtBkRSnZQtVk+Gqufwj1ktUxEyjzEhBl0+PiNJqYC31C+n1n6A==",
|
|
||||||
"dependencies": {
|
|
||||||
"@babel/runtime": "^7.23.9"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/mui-org"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@mui/material": "^5.0.0",
|
|
||||||
"@types/react": "^17.0.0 || ^18.0.0",
|
|
||||||
"react": "^17.0.0 || ^18.0.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@mui/material": {
|
"node_modules/@mui/material": {
|
||||||
"version": "5.15.6",
|
"version": "5.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.6.tgz",
|
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.6.tgz",
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.17",
|
|
||||||
"@mui/material": "^5.15.6",
|
"@mui/material": "^5.15.6",
|
||||||
"@mui/x-date-pickers": "^6.19.2",
|
"@mui/x-date-pickers": "^6.19.2",
|
||||||
"@tanstack/query-sync-storage-persister": "^5.18.0",
|
"@tanstack/query-sync-storage-persister": "^5.18.0",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
|
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
|
||||||
import TrainsTable from "./TrainsTable"
|
import TrainsTable from "./TrainsTable"
|
||||||
import TripsFilter from "./TripsFilter"
|
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {Box, Button, FormLabel} from "@mui/material";
|
import {Box, Button, FormLabel} from "@mui/material";
|
||||||
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
|
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
|
||||||
@ -68,7 +67,6 @@ function Station() {
|
|||||||
|
|
||||||
<main>
|
<main>
|
||||||
<DateTimeSelector station={station} date={date} time={time} />
|
<DateTimeSelector station={station} date={date} time={time} />
|
||||||
<TripsFilter />
|
|
||||||
<TrainsTable station={station} date={date} time={time} tableType="departures" />
|
<TrainsTable station={station} date={date} time={time} tableType="departures" />
|
||||||
<TrainsTable station={station} date={date} time={time} tableType="arrivals" />
|
<TrainsTable station={station} date={date} time={time} tableType="arrivals" />
|
||||||
</main>
|
</main>
|
||||||
|
@ -257,8 +257,6 @@ function getTrainType(train, trip, route) {
|
|||||||
if (trip.short_name?.startsWith("NJ"))
|
if (trip.short_name?.startsWith("NJ"))
|
||||||
return "NJ"
|
return "NJ"
|
||||||
return "ÖBB"
|
return "ÖBB"
|
||||||
case "CH-ALL":
|
|
||||||
return route.desc
|
|
||||||
default:
|
default:
|
||||||
return trip.short_name?.split(" ")[0]
|
return trip.short_name?.split(" ")[0]
|
||||||
}
|
}
|
||||||
|
@ -1,165 +0,0 @@
|
|||||||
import {useState} from "react"
|
|
||||||
import {
|
|
||||||
Box, Button,
|
|
||||||
Checkbox, Chip, FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
InputLabel, MenuItem, OutlinedInput, Select
|
|
||||||
} from "@mui/material"
|
|
||||||
import DirectionsBusTwoToneIcon from '@mui/icons-material/DirectionsBusTwoTone'
|
|
||||||
import SubwayTwoToneIcon from '@mui/icons-material/SubwayTwoTone'
|
|
||||||
import TrainTwoToneIcon from '@mui/icons-material/TrainTwoTone'
|
|
||||||
import TramTwoToneIcon from '@mui/icons-material/TramTwoTone'
|
|
||||||
|
|
||||||
function TripsFilter() {
|
|
||||||
const [transportModeFilter, setTransportModeFilter] = useState(
|
|
||||||
{longDistanceTrain: true, regionalTrain: true, metro: true, tram: true, bus: true})
|
|
||||||
const transportModeNames = {
|
|
||||||
train: "Trains",
|
|
||||||
longDistanceTrain: "Trains longue distance",
|
|
||||||
regionalTrain: "Trains régionaux",
|
|
||||||
metro: "Métro",
|
|
||||||
tram: "Tram",
|
|
||||||
bus: "Bus",
|
|
||||||
}
|
|
||||||
|
|
||||||
const trainCheckbox = <>
|
|
||||||
<TrainTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.longDistanceTrain && transportModeFilter.regionalTrain}
|
|
||||||
indeterminate={transportModeFilter.longDistanceTrain !== transportModeFilter.regionalTrain}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter(
|
|
||||||
{...transportModeFilter, longDistanceTrain: event.target.checked, regionalTrain: event.target.checked})}
|
|
||||||
onClick={(event) => event.stopPropagation()}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
const longDistanceTrainCheckbox = <>
|
|
||||||
<TrainTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.longDistanceTrain}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter({...transportModeFilter, longDistanceTrain: event.target.checked})} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
const regionalTrainCheckbox = <>
|
|
||||||
<TrainTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.regionalTrain}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter({...transportModeFilter, regionalTrain: event.target.checked})} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
const metroCheckbox = <>
|
|
||||||
<SubwayTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.metro}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter({...transportModeFilter, metro: event.target.checked})} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
const tramCheckbox = <>
|
|
||||||
<TramTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.tram}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter({...transportModeFilter, tram: event.target.checked})} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
const busCheckbox = <>
|
|
||||||
<DirectionsBusTwoToneIcon />
|
|
||||||
<Checkbox
|
|
||||||
checked={transportModeFilter.bus}
|
|
||||||
onChange={(event) =>
|
|
||||||
setTransportModeFilter({...transportModeFilter, bus: event.target.checked})} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
// TODO Fetch routes that are accessible from one stop
|
|
||||||
// For now, we have the tram and bus routes accessible in Strasbourg main station
|
|
||||||
const routesList = [
|
|
||||||
{name: "Tous"},
|
|
||||||
{name: "A", bgColor: "#E10D19", color: "#FFFFFF"},
|
|
||||||
{name: "C", bgColor: "#F29400", color: "#FFFFFF"},
|
|
||||||
{name: "D", bgColor: "#009933", color: "#FFFFFF"},
|
|
||||||
{name: "G", bgColor: "#F6C900", color: "#000000"},
|
|
||||||
{name: "H", bgColor: "#A62341", color: "#FFFFFF"},
|
|
||||||
{name: "2", bgColor: "#FF0000", color: "#FFFFFF"},
|
|
||||||
{name: "10", bgColor: "#FFAA00", color: "#000000"},
|
|
||||||
]
|
|
||||||
const routesDict = {}
|
|
||||||
for (const route of routesList) {
|
|
||||||
routesDict[route.name] = route
|
|
||||||
}
|
|
||||||
|
|
||||||
const [selectedRoutes, setSelectedRoutes] = useState(["Tous"])
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<h2>Filtres</h2>
|
|
||||||
<Box display="flex" alignItems="center" sx={{mb: 3}}>
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel>Mode de transport</InputLabel>
|
|
||||||
<Select
|
|
||||||
multiple
|
|
||||||
value={selectedRoutes}
|
|
||||||
input={<OutlinedInput id="select-multiple-chip" label="Lignes" />}
|
|
||||||
renderValue={(selected) => (
|
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
|
||||||
{Object.keys(transportModeFilter).filter(key => transportModeFilter[key]).map((filterType) => (
|
|
||||||
<Chip key={filterType} label={transportModeNames[filterType]} sx={{fontWeight: "bold"}} />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<MenuItem key="train" value="train">
|
|
||||||
<FormControlLabel label={transportModeNames["train"]} control={trainCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem key="longDistanceTrain" value="longDistanceTrain">
|
|
||||||
<FormControlLabel label={transportModeNames["longDistanceTrain"]} sx={{pl: 4}} control={longDistanceTrainCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem key="regionalTrain" value="regionalTrain">
|
|
||||||
<FormControlLabel label={transportModeNames["regionalTrain"]} sx={{pl: 4}} control={regionalTrainCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem key="metro" value="metro">
|
|
||||||
<FormControlLabel label={transportModeNames["metro"]} control={metroCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem key="tram" value="tram">
|
|
||||||
<FormControlLabel label={transportModeNames["tram"]} control={tramCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem key="bus" value="bus">
|
|
||||||
<FormControlLabel label={transportModeNames["bus"]} control={busCheckbox} />
|
|
||||||
</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl>
|
|
||||||
<InputLabel>Ligne</InputLabel>
|
|
||||||
<Select
|
|
||||||
multiple
|
|
||||||
value={selectedRoutes}
|
|
||||||
onChange={(event) => setSelectedRoutes(event.target.value)}
|
|
||||||
input={<OutlinedInput id="select-multiple-chip" label="Lignes" />}
|
|
||||||
renderValue={(selected) => (
|
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
|
||||||
{selected.map(routeName => routesDict[routeName]).map((route) => (
|
|
||||||
<Chip key={route.name} label={route.name} sx={{backgroundColor: route.bgColor, color: route.color, fontWeight: "bold"}} />
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{routesList.map((route) =>
|
|
||||||
<MenuItem key={route.name} value={route.name}>
|
|
||||||
<Checkbox checked={selectedRoutes.includes(route.name)} />
|
|
||||||
<Chip label={route.name} sx={{backgroundColor: route.bgColor, color: route.color, fontWeight: "bold"}} />
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Button>
|
|
||||||
Filtrer
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TripsFilter
|
|
@ -1,8 +1,8 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from trainvel.core.models import Station
|
from trainvel.core.models import Station
|
||||||
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, \
|
from trainvel.gtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
||||||
Stop, StopTime, StopTimeUpdate, Transfer, Trip, TripUpdate
|
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
||||||
|
|
||||||
|
|
||||||
class StationSerializer(serializers.ModelSerializer):
|
class StationSerializer(serializers.ModelSerializer):
|
||||||
@ -13,12 +13,6 @@ class StationSerializer(serializers.ModelSerializer):
|
|||||||
'country_hint', 'main_station_hint',)
|
'country_hint', 'main_station_hint',)
|
||||||
|
|
||||||
|
|
||||||
class GTFSFeedSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = GTFSFeed
|
|
||||||
fields = '__all__'
|
|
||||||
|
|
||||||
|
|
||||||
class AgencySerializer(serializers.ModelSerializer):
|
class AgencySerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Agency
|
model = Agency
|
||||||
|
@ -8,12 +8,13 @@ from django_filters.rest_framework import DjangoFilterBackend
|
|||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||||
|
|
||||||
from trainvel.api.serializers import AgencySerializer, CalendarDateSerializer, CalendarSerializer, \
|
from trainvel.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
||||||
FeedInfoSerializer, GTFSFeedSerializer, RouteSerializer, StationSerializer, StopSerializer, StopTimeSerializer, \
|
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
||||||
StopTimeUpdateSerializer, TransferSerializer, TripSerializer, TripUpdateSerializer
|
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer, StationSerializer
|
||||||
from trainvel.core.models import Station
|
from trainvel.core.models import Station
|
||||||
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
|
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
|
||||||
StopTimeUpdate, Transfer, Trip, TripUpdate, PickupType
|
StopTimeUpdate, \
|
||||||
|
Transfer, Trip, TripUpdate, PickupType
|
||||||
|
|
||||||
CACHE_CONTROL = cache_control(max_age=30)
|
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)
|
||||||
@ -33,16 +34,6 @@ class StationViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
lookup_value_regex = LOOKUP_VALUE_REGEX
|
lookup_value_regex = LOOKUP_VALUE_REGEX
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
|
||||||
@method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
|
||||||
class GTFSFeedViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
queryset = GTFSFeed.objects.all()
|
|
||||||
serializer_class = GTFSFeedSerializer
|
|
||||||
filter_backends = [DjangoFilterBackend]
|
|
||||||
filterset_fields = '__all__'
|
|
||||||
lookup_value_regex = LOOKUP_VALUE_REGEX
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
@method_decorator(name='list', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
||||||
@method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
@method_decorator(name='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED])
|
||||||
class AgencyViewSet(viewsets.ReadOnlyModelViewSet):
|
class AgencyViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
@ -154,6 +145,7 @@ class StopTimeUpdateViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = StopTime.objects.none()
|
queryset = StopTime.objects.none()
|
||||||
serializer_class = StopTimeSerializer
|
serializer_class = StopTimeSerializer
|
||||||
|
filter_backends = [DjangoFilterBackend]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -175,21 +167,10 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
near_stops = station.get_near_stops()
|
near_stops = station.get_near_stops()
|
||||||
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
||||||
|
|
||||||
excluded_agencies = ~Q(trip__route__gtfs_feed__excluded_agencies=F('trip__route__agency_id'))
|
|
||||||
|
|
||||||
not_last_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id'))
|
not_last_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id'))
|
||||||
.filter(pickup_type=PickupType.REGULAR)
|
.filter(pickup_type=PickupType.REGULAR)
|
||||||
.order_by('-stop_sequence')[:1].values_list('stop_sequence'))
|
.order_by('-stop_sequence')[:1].values_list('stop_sequence'))
|
||||||
|
|
||||||
trip_filter = Q()
|
|
||||||
if self.request.query_params.get('route_name', None):
|
|
||||||
trip_filter &= Q(trip__route_name__in=self.request.query_params.get('route_name').split(','))
|
|
||||||
if self.request.query_params.get('transport_type', None):
|
|
||||||
trip_filter &= Q(trip__route__type__in=self.request.query_params.get('transport_type').split(','))
|
|
||||||
if self.request.query_params.get('long_distance', None) is not None:
|
|
||||||
long_distance = str(self.request.query_params.get('long_distance')) == 'true'
|
|
||||||
trip_filter &= Q(trip__long_distance=long_distance)
|
|
||||||
|
|
||||||
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)
|
||||||
.values_list('service_id')) \
|
.values_list('service_id')) \
|
||||||
@ -218,9 +199,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
|
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
|
||||||
|
|
||||||
qs_today = StopTime.objects.filter(stop_filter) \
|
qs_today = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_last_stop) \
|
.filter(not_last_stop) \
|
||||||
.filter(trip_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=PickupType.REGULAR) | canceled_filter(query_date)) \
|
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(query_date)) \
|
||||||
@ -229,9 +208,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
.annotate(departure_time_24h=F('departure_time'))
|
.annotate(departure_time_24h=F('departure_time'))
|
||||||
|
|
||||||
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_last_stop) \
|
.filter(not_last_stop) \
|
||||||
.filter(trip_filter) \
|
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
.annotate(departure_time_real=departure_time_real(query_date)) \
|
||||||
.filter(departure_time_real__gte=time_yesterday) \
|
.filter(departure_time_real__gte=time_yesterday) \
|
||||||
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
|
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
|
||||||
@ -240,9 +217,7 @@ class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
.annotate(departure_time_24h=F('departure_time') - timedelta(days=1))
|
.annotate(departure_time_24h=F('departure_time') - timedelta(days=1))
|
||||||
|
|
||||||
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_last_stop) \
|
.filter(not_last_stop) \
|
||||||
.filter(trip_filter) \
|
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
.annotate(departure_time_real=departure_time_real(query_date)) \
|
||||||
.filter(departure_time_real__gte=timedelta(0)) \
|
.filter(departure_time_real__gte=timedelta(0)) \
|
||||||
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
|
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
|
||||||
@ -279,21 +254,10 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
near_stops = station.get_near_stops()
|
near_stops = station.get_near_stops()
|
||||||
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
stop_filter = Q(stop_id__in=near_stops.values_list('id', flat=True))
|
||||||
|
|
||||||
excluded_agencies = ~Q(trip__route__gtfs_feed__excluded_agencies=F('trip__route__agency_id'))
|
|
||||||
|
|
||||||
not_first_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id'))
|
not_first_stop = ~Q(stop_sequence=StopTime.objects.filter(trip_id=OuterRef('trip_id'))
|
||||||
.filter(drop_off_type=PickupType.REGULAR)
|
.filter(drop_off_type=PickupType.REGULAR)
|
||||||
.order_by('stop_sequence')[:1].values_list('stop_sequence'))
|
.order_by('stop_sequence')[:1].values_list('stop_sequence'))
|
||||||
|
|
||||||
trip_filter = Q()
|
|
||||||
if self.request.query_params.get('route_name', None):
|
|
||||||
trip_filter &= Q(trip__route_name__in=self.request.query_params.get('route_name').split(','))
|
|
||||||
if self.request.query_params.get('transport_type', None):
|
|
||||||
trip_filter &= Q(trip__route__type__in=self.request.query_params.get('transport_type').split(','))
|
|
||||||
if self.request.query_params.get('long_distance', None) is not None:
|
|
||||||
long_distance = str(self.request.query_params.get('long_distance')) == 'true'
|
|
||||||
trip_filter &= Q(trip__long_distance=long_distance)
|
|
||||||
|
|
||||||
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)
|
||||||
.values_list('service_id')) \
|
.values_list('service_id')) \
|
||||||
@ -322,9 +286,7 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
|
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
|
||||||
|
|
||||||
qs_today = StopTime.objects.filter(stop_filter) \
|
qs_today = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_first_stop) \
|
.filter(not_first_stop) \
|
||||||
.filter(trip_filter) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(query_date)) \
|
.annotate(arrival_time_real=arrival_time_real(query_date)) \
|
||||||
.filter(arrival_time_real__gte=query_time) \
|
.filter(arrival_time_real__gte=query_time) \
|
||||||
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(query_date)) \
|
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(query_date)) \
|
||||||
@ -333,9 +295,7 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
.annotate(arrival_time_24h=F('arrival_time'))
|
.annotate(arrival_time_24h=F('arrival_time'))
|
||||||
|
|
||||||
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_first_stop) \
|
.filter(not_first_stop) \
|
||||||
.filter(trip_filter) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(yesterday)) \
|
.annotate(arrival_time_real=arrival_time_real(yesterday)) \
|
||||||
.filter(arrival_time_real__gte=time_yesterday) \
|
.filter(arrival_time_real__gte=time_yesterday) \
|
||||||
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
|
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
|
||||||
@ -344,9 +304,7 @@ class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
.annotate(arrival_time_24h=F('arrival_time') - timedelta(days=1))
|
.annotate(arrival_time_24h=F('arrival_time') - timedelta(days=1))
|
||||||
|
|
||||||
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
||||||
.filter(excluded_agencies) \
|
|
||||||
.filter(not_first_stop) \
|
.filter(not_first_stop) \
|
||||||
.filter(trip_filter) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(tomorrow)) \
|
.annotate(arrival_time_real=arrival_time_real(tomorrow)) \
|
||||||
.filter(arrival_time_real__gte=timedelta(0)) \
|
.filter(arrival_time_real__gte=timedelta(0)) \
|
||||||
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
|
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
|
||||||
|
@ -57,7 +57,6 @@ class GTFSFeedAdmin(admin.ModelAdmin):
|
|||||||
list_filter = ('country', 'last_modified',)
|
list_filter = ('country', 'last_modified',)
|
||||||
search_fields = ('name', 'code',)
|
search_fields = ('name', 'code',)
|
||||||
readonly_fields = ('code',)
|
readonly_fields = ('code',)
|
||||||
autocomplete_fields = ('excluded_agencies',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Agency)
|
@admin.register(Agency)
|
||||||
|
@ -6,13 +6,7 @@
|
|||||||
"name": "SNCF - TGV",
|
"name": "SNCF - TGV",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export_gtfs_voyages.zip",
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export_gtfs_voyages.zip",
|
||||||
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-tgv-gtfs-rt-trip-updates",
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-tgv-gtfs-rt-trip-updates"
|
||||||
"categorize_routes": false,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "SNCF-&stop:FR-SNCF-TGV-StopPoint:OCE([\\w\\s]+)-\\d+",
|
|
||||||
"trip_number_regex": "trip_headsign:(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -22,13 +16,7 @@
|
|||||||
"name": "SNCF - Intercités",
|
"name": "SNCF - Intercités",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-intercites-gtfs-last.zip",
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-intercites-gtfs-last.zip",
|
||||||
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ic-gtfs-rt-trip-updates",
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ic-gtfs-rt-trip-updates"
|
||||||
"categorize_routes": false,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "SNCF-&stop:FR-SNCF-IC-StopPoint:OCE([\\w\\s]+)-\\d+",
|
|
||||||
"trip_number_regex": "trip_headsign:(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -38,13 +26,7 @@
|
|||||||
"name": "SNCF - TER",
|
"name": "SNCF - TER",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-ter-gtfs-last.zip",
|
"feed_url": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-ter-gtfs-last.zip",
|
||||||
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ter-gtfs-rt-trip-updates",
|
"rt_feed_url": "https://proxy.transport.data.gouv.fr/resource/sncf-ter-gtfs-rt-trip-updates"
|
||||||
"categorize_routes": false,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "SNCF-&stop:FR-SNCF-TER-StopPoint:OCE([\\w\\s]+)-\\d+",
|
|
||||||
"trip_number_regex": "trip_headsign:(\\d+)",
|
|
||||||
"long_distance_regex": "^$",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -54,13 +36,7 @@
|
|||||||
"name": "Île-de-France Mobilités",
|
"name": "Île-de-France Mobilités",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://eu.ftp.opendatasoft.com/stif/GTFS/IDFM-gtfs.zip",
|
"feed_url": "https://eu.ftp.opendatasoft.com/stif/GTFS/IDFM-gtfs.zip",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w]+)",
|
|
||||||
"route_type_regex": "IDFM",
|
|
||||||
"trip_number_regex": "trip_short_name:([\\d\\w-]*)§",
|
|
||||||
"long_distance_regex": "^$",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -70,13 +46,7 @@
|
|||||||
"name": "Compagnie des Transports Strasbourgeois (CTS)",
|
"name": "Compagnie des Transports Strasbourgeois (CTS)",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://opendata.cts-strasbourg.eu/google_transit.zip",
|
"feed_url": "https://opendata.cts-strasbourg.eu/google_transit.zip",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\d]+)",
|
|
||||||
"route_type_regex": "CTS",
|
|
||||||
"trip_number_regex": "",
|
|
||||||
"long_distance_regex": "^$",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -86,13 +56,7 @@
|
|||||||
"name": "Eurostar",
|
"name": "Eurostar",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
|
"feed_url": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": false,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "Eurostar",
|
|
||||||
"trip_number_regex": "trip_short_name:(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -102,13 +66,7 @@
|
|||||||
"name": "Trenitalia France",
|
"name": "Trenitalia France",
|
||||||
"country": "FR",
|
"country": "FR",
|
||||||
"feed_url": "https://thello.axelor.com/public/gtfs/gtfs.zip",
|
"feed_url": "https://thello.axelor.com/public/gtfs/gtfs.zip",
|
||||||
"rt_feed_url": "https://thello.axelor.com/public/gtfs/GTFS-RT.bin",
|
"rt_feed_url": "https://thello.axelor.com/public/gtfs/GTFS-RT.bin"
|
||||||
"categorize_routes": false,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s/-]+)",
|
|
||||||
"route_type_regex": "Trenitalia France",
|
|
||||||
"trip_number_regex": "trip_id:IT-FRA-TI-(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -118,13 +76,7 @@
|
|||||||
"name": "Renfe",
|
"name": "Renfe",
|
||||||
"country": "ES",
|
"country": "ES",
|
||||||
"feed_url": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
|
"feed_url": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "RENFE-&route_short_name:([\\w\\s]+)",
|
|
||||||
"trip_number_regex": "trip_short_name:(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -134,13 +86,7 @@
|
|||||||
"name": "ÖBB",
|
"name": "ÖBB",
|
||||||
"country": "AT",
|
"country": "AT",
|
||||||
"feed_url": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
|
"feed_url": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "ÖBB-&trip_short_name:(\\w+)\\s",
|
|
||||||
"trip_number_regex": "trip_short_name:\\w+\\s(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -150,15 +96,7 @@
|
|||||||
"name": "Transports suisses",
|
"name": "Transports suisses",
|
||||||
"country": "CH",
|
"country": "CH",
|
||||||
"feed_url": "https://opentransportdata.swiss/fr/dataset/timetable-2024-gtfs2020/permalink",
|
"feed_url": "https://opentransportdata.swiss/fr/dataset/timetable-2024-gtfs2020/permalink",
|
||||||
"rt_feed_url": "https://api.opentransportdata.swiss/gtfsrt2020",
|
"rt_feed_url": "https://api.opentransportdata.swiss/gtfsrt2020"
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]*)",
|
|
||||||
"route_type_regex": "CH-&route_desc:([\\w\\s]*)",
|
|
||||||
"trip_number_regex": "trip_short_name:(\\d+)",
|
|
||||||
"long_distance_regex": "",
|
|
||||||
"excluded_agencies": [
|
|
||||||
"CH-ALL-87_LEX"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -168,13 +106,7 @@
|
|||||||
"name": "CFL",
|
"name": "CFL",
|
||||||
"country": "LU",
|
"country": "LU",
|
||||||
"feed_url": "https://data.public.lu/fr/datasets/r/aab2922d-27ff-4e53-a789-d990cf1ceb1e",
|
"feed_url": "https://data.public.lu/fr/datasets/r/aab2922d-27ff-4e53-a789-d990cf1ceb1e",
|
||||||
"rt_feed_url": "",
|
"rt_feed_url": ""
|
||||||
"categorize_routes": true,
|
|
||||||
"route_name_regex": "route_short_name:([\\w\\s]+)",
|
|
||||||
"route_type_regex": "CFL-&route_short_name:([\\w\\s]+)",
|
|
||||||
"trip_number_regex": "trip_short_name:(\\d+)",
|
|
||||||
"long_distance_regex": "route_type:3",
|
|
||||||
"excluded_agencies": []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
from django.core.management import BaseCommand
|
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
from tqdm import tqdm
|
|
||||||
from trainvel.gtfs.models import Trip
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument('--feed', type=str, help='GTFS Feed code')
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
trips = Trip.objects.filter(route_name="").prefetch_related('gtfs_feed', 'route')
|
|
||||||
if options['feed']:
|
|
||||||
trips = trips.filter(gtfs_feed__code=options['feed'])
|
|
||||||
|
|
||||||
with transaction.atomic():
|
|
||||||
for trip in tqdm(trips.all()):
|
|
||||||
trip.augment_data()
|
|
||||||
trip.save()
|
|
@ -377,30 +377,11 @@ class Command(BaseCommand):
|
|||||||
to_stop_id = transfer_dict['to_stop_id']
|
to_stop_id = transfer_dict['to_stop_id']
|
||||||
from_stop_id = f"{gtfs_code}-{from_stop_id}"
|
from_stop_id = f"{gtfs_code}-{from_stop_id}"
|
||||||
to_stop_id = f"{gtfs_code}-{to_stop_id}"
|
to_stop_id = f"{gtfs_code}-{to_stop_id}"
|
||||||
from_route_id = transfer_dict.get('from_route_id', None)
|
|
||||||
from_route_id = f"{gtfs_code}-{from_route_id}" if from_route_id else None
|
|
||||||
to_route_id = transfer_dict.get('to_route_id', None)
|
|
||||||
to_route_id = f"{gtfs_code}-{to_route_id}" if to_route_id else None
|
|
||||||
from_trip_id = transfer_dict.get('from_trip_id', None)
|
|
||||||
from_trip_id = f"{gtfs_code}-{from_trip_id}" if from_trip_id else None
|
|
||||||
to_trip_id = transfer_dict.get('to_trip_id', None)
|
|
||||||
to_trip_id = f"{gtfs_code}-{to_trip_id}" if to_trip_id else None
|
|
||||||
|
|
||||||
transfer_id = f"{gtfs_code}-{transfer_dict['from_stop_id']}-{transfer_dict['to_stop_id']}"
|
|
||||||
if from_route_id and to_route_id:
|
|
||||||
transfer_id += f"-{from_route_id}-{to_route_id}"
|
|
||||||
if from_trip_id and to_trip_id:
|
|
||||||
transfer_id += f"-{from_trip_id}-{to_trip_id}"
|
|
||||||
transfer_id += f"-{transfer_dict['transfer_type']}"
|
|
||||||
|
|
||||||
transfer = Transfer(
|
transfer = Transfer(
|
||||||
id=transfer_id,
|
id=f"{gtfs_code}-{transfer_dict['from_stop_id']}-{transfer_dict['to_stop_id']}",
|
||||||
from_stop_id=from_stop_id,
|
from_stop_id=from_stop_id,
|
||||||
to_stop_id=to_stop_id,
|
to_stop_id=to_stop_id,
|
||||||
from_route_id=from_route_id,
|
|
||||||
to_route_id=to_route_id,
|
|
||||||
from_trip_id=from_trip_id,
|
|
||||||
to_trip_id=to_trip_id,
|
|
||||||
transfer_type=transfer_dict['transfer_type'],
|
transfer_type=transfer_dict['transfer_type'],
|
||||||
min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0,
|
min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0,
|
||||||
)
|
)
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
# Generated by Django 5.0.6 on 2024-05-12 17:18
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("gtfs", "0002_alter_stop_parent_station"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="categorize_routes",
|
|
||||||
field=models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="If checked, trips can be categorized by route type.",
|
|
||||||
verbose_name="categorize routes",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="excluded_agencies",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Agencies that are part of another feed and shouldn't be displayed with this feed.",
|
|
||||||
to="gtfs.agency",
|
|
||||||
verbose_name="excluded agencies",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="long_distance_regex",
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
help_text="Regular expression that filters long distance trips.",
|
|
||||||
verbose_name="long distance regex",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="route_name_regex",
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default="route_short_name:([^§]+)",
|
|
||||||
help_text="Regular expression that catches the route name from a trip.",
|
|
||||||
verbose_name="route name regex",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="route_type_regex",
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default="gtfs_feed:([^§]+)&-&route_type:([^§]+)",
|
|
||||||
help_text="Regular expression that catches the route type from a trip.",
|
|
||||||
verbose_name="route name regex",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="gtfsfeed",
|
|
||||||
name="trip_number_regex",
|
|
||||||
field=models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default="trip_short_name:([^§]+)",
|
|
||||||
help_text="Regular expression that catches the trip number from a trip.",
|
|
||||||
verbose_name="route name regex",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,39 +0,0 @@
|
|||||||
# Generated by Django 5.0.6 on 2024-05-12 18:33
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("gtfs", "0003_gtfsfeed_categorize_routes_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="trip",
|
|
||||||
name="long_distance",
|
|
||||||
field=models.BooleanField(default=True, verbose_name="Long distance trip"),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="trip",
|
|
||||||
name="route_name",
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True, default="", max_length=255, verbose_name="Route name"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="trip",
|
|
||||||
name="route_type",
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True, default="", max_length=255, verbose_name="Route type"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="trip",
|
|
||||||
name="trip_number",
|
|
||||||
field=models.CharField(
|
|
||||||
blank=True, default="", max_length=255, verbose_name="Trip number"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,73 +0,0 @@
|
|||||||
# Generated by Django 5.0.6 on 2024-08-12 18:33
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("gtfs", "0004_trip_long_distance_trip_route_name_trip_route_type_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="transfer",
|
|
||||||
name="from_route",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="transfers_from",
|
|
||||||
to="gtfs.route",
|
|
||||||
verbose_name="From route",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="transfer",
|
|
||||||
name="from_trip",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="transfers_from",
|
|
||||||
to="gtfs.trip",
|
|
||||||
verbose_name="From trip",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="transfer",
|
|
||||||
name="to_route",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="transfers_to",
|
|
||||||
to="gtfs.route",
|
|
||||||
verbose_name="To route",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="transfer",
|
|
||||||
name="to_trip",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="transfers_to",
|
|
||||||
to="gtfs.trip",
|
|
||||||
verbose_name="To trip",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="transfer",
|
|
||||||
name="min_transfer_time",
|
|
||||||
field=models.IntegerField(
|
|
||||||
blank=True, default=None, verbose_name="Minimum transfer time"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,5 +1,3 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -58,15 +56,6 @@ class Country(models.TextChoices):
|
|||||||
UKRAINE = "UA", _("Ukraine")
|
UKRAINE = "UA", _("Ukraine")
|
||||||
|
|
||||||
|
|
||||||
class CategorizeTrips(models.TextChoices):
|
|
||||||
"""
|
|
||||||
Make trips be categorized by route type.
|
|
||||||
"""
|
|
||||||
GTFS_FEED = "gtfs_feed", _("GTFS Feed (all trips of the dataset)")
|
|
||||||
ROUTE_SHORT_NAME = "route_short_name", _("By route short name (e.g. A, S9,…)")
|
|
||||||
TRIP_SHORT_NAME_SPLIT = "route_short_name_split", _("By first character of the route short name (e.g. RJ, NJ,…)")
|
|
||||||
|
|
||||||
|
|
||||||
class LocationType(models.IntegerChoices):
|
class LocationType(models.IntegerChoices):
|
||||||
STOP_PLATFORM = 0, _("Stop/platform")
|
STOP_PLATFORM = 0, _("Stop/platform")
|
||||||
STATION = 1, _("Station")
|
STATION = 1, _("Station")
|
||||||
@ -183,47 +172,6 @@ class GTFSFeed(models.Model):
|
|||||||
"If it is not modified, the file is the same."),
|
"If it is not modified, the file is the same."),
|
||||||
)
|
)
|
||||||
|
|
||||||
excluded_agencies = models.ManyToManyField(
|
|
||||||
to="Agency",
|
|
||||||
verbose_name=_("excluded agencies"),
|
|
||||||
blank=True,
|
|
||||||
help_text=_("Agencies that are part of another feed and shouldn't be displayed with this feed."),
|
|
||||||
)
|
|
||||||
|
|
||||||
categorize_routes = models.BooleanField(
|
|
||||||
verbose_name=_("categorize routes"),
|
|
||||||
default=False,
|
|
||||||
help_text=_("If checked, trips can be categorized by route type."),
|
|
||||||
)
|
|
||||||
|
|
||||||
route_name_regex = models.TextField(
|
|
||||||
verbose_name=_("route name regex"),
|
|
||||||
blank=True,
|
|
||||||
default=r"route_short_name:([^§]+)",
|
|
||||||
help_text=_("Regular expression that catches the route name from a trip."),
|
|
||||||
)
|
|
||||||
|
|
||||||
route_type_regex = models.TextField(
|
|
||||||
verbose_name=_("route name regex"),
|
|
||||||
blank=True,
|
|
||||||
default=r"gtfs_feed:([^§]+)&-&route_type:([^§]+)",
|
|
||||||
help_text=_("Regular expression that catches the route type from a trip."),
|
|
||||||
)
|
|
||||||
|
|
||||||
trip_number_regex = models.TextField(
|
|
||||||
verbose_name=_("route name regex"),
|
|
||||||
blank=True,
|
|
||||||
default=r"trip_short_name:([^§]+)",
|
|
||||||
help_text=_("Regular expression that catches the trip number from a trip."),
|
|
||||||
)
|
|
||||||
|
|
||||||
long_distance_regex = models.TextField(
|
|
||||||
verbose_name=_("long distance regex"),
|
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
help_text=_("Regular expression that filters long distance trips."),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.name} ({self.code})"
|
return f"{self.name} ({self.code})"
|
||||||
|
|
||||||
@ -538,36 +486,6 @@ class Trip(models.Model):
|
|||||||
verbose_name=_("GTFS feed"),
|
verbose_name=_("GTFS feed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
"""
|
|
||||||
Augmented data
|
|
||||||
"""
|
|
||||||
|
|
||||||
route_name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route name"),
|
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
)
|
|
||||||
|
|
||||||
trip_number = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Trip number"),
|
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
)
|
|
||||||
|
|
||||||
route_type = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route type"),
|
|
||||||
blank=True,
|
|
||||||
default="",
|
|
||||||
)
|
|
||||||
|
|
||||||
long_distance = models.BooleanField(
|
|
||||||
verbose_name=_("Long distance trip"),
|
|
||||||
default=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def origin(self) -> Stop | None:
|
def origin(self) -> Stop | None:
|
||||||
return self.stop_times.order_by('stop_sequence').first().stop if self.stop_times.exists() else None
|
return self.stop_times.order_by('stop_sequence').first().stop if self.stop_times.exists() else None
|
||||||
@ -638,28 +556,6 @@ class Trip(models.Model):
|
|||||||
|
|
||||||
origin_destination.fget.short_description = _("Origin → Destination")
|
origin_destination.fget.short_description = _("Origin → Destination")
|
||||||
|
|
||||||
def augment_data(self):
|
|
||||||
first_stop = self.stop_times.first()
|
|
||||||
if not first_stop:
|
|
||||||
return
|
|
||||||
desc_line = first_stop.description_line
|
|
||||||
|
|
||||||
for key in ['route_name', 'route_type', 'trip_number']:
|
|
||||||
value = ""
|
|
||||||
full_pattern = getattr(self.gtfs_feed, f"{key}_regex")
|
|
||||||
for pattern in full_pattern.split('&'):
|
|
||||||
if '(' not in pattern:
|
|
||||||
value += pattern
|
|
||||||
continue
|
|
||||||
|
|
||||||
groups = re.findall(pattern, desc_line)
|
|
||||||
if not any(group is not None for group in groups):
|
|
||||||
raise ValueError(f"Pattern {pattern} not found in {desc_line}")
|
|
||||||
value += "".join(group for group in groups if group)
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
self.long_distance = bool(re.match(self.gtfs_feed.long_distance_regex, desc_line))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.origin_destination
|
return self.origin_destination
|
||||||
|
|
||||||
@ -742,28 +638,6 @@ class StopTime(models.Model):
|
|||||||
minutes = int((seconds % 3600) // 60)
|
minutes = int((seconds % 3600) // 60)
|
||||||
return f"{hours:02}:{minutes:02}"
|
return f"{hours:02}:{minutes:02}"
|
||||||
|
|
||||||
@property
|
|
||||||
def description_line(self) -> str:
|
|
||||||
return (f"gtfs_feed:{self.trip.gtfs_feed_id}"
|
|
||||||
f"§stop:{self.stop_id}"
|
|
||||||
f"§trip_id:{self.trip_id}"
|
|
||||||
f"§trip_short_name:{self.trip.short_name}"
|
|
||||||
f"§trip_headsign:{self.trip.headsign}"
|
|
||||||
f"§route_short_name:{self.trip.route.short_name}"
|
|
||||||
f"§route_desc:{self.trip.route.desc}"
|
|
||||||
f"§route_type:{self.trip.route.type}")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def description_line_expr(self):
|
|
||||||
return "gtfs_feed:" + models.F('trip__gtfs_feed_id') \
|
|
||||||
+ "§stop:" + models.F('stop_id') \
|
|
||||||
+ "§trip_id:" + models.F('trip_id') \
|
|
||||||
+ "§trip_short_name:" + models.F('trip__short_name') \
|
|
||||||
+ "§trip_headsign:" + models.F('trip__headsign') \
|
|
||||||
+ "§route_short_name:" + models.F('trip__route__short_name') \
|
|
||||||
+ "§route_desc:" + models.F('trip__route__desc') \
|
|
||||||
+ "§route_type:" + models.F('trip__route__type')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.stop.name} - {self.trip_id}"
|
return f"{self.stop.name} - {self.trip_id}"
|
||||||
|
|
||||||
@ -886,46 +760,6 @@ class Transfer(models.Model):
|
|||||||
related_name="transfers_to",
|
related_name="transfers_to",
|
||||||
)
|
)
|
||||||
|
|
||||||
from_route = models.ForeignKey(
|
|
||||||
to="Route",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("From route"),
|
|
||||||
related_name="transfers_from",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
to_route = models.ForeignKey(
|
|
||||||
to="Route",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("To route"),
|
|
||||||
related_name="transfers_to",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
from_trip = models.ForeignKey(
|
|
||||||
to="Trip",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("From trip"),
|
|
||||||
related_name="transfers_from",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
to_trip = models.ForeignKey(
|
|
||||||
to="Trip",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("To trip"),
|
|
||||||
related_name="transfers_to",
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
transfer_type = models.IntegerField(
|
transfer_type = models.IntegerField(
|
||||||
verbose_name=_("Transfer type"),
|
verbose_name=_("Transfer type"),
|
||||||
choices=TransferType,
|
choices=TransferType,
|
||||||
@ -935,7 +769,6 @@ class Transfer(models.Model):
|
|||||||
min_transfer_time = models.IntegerField(
|
min_transfer_time = models.IntegerField(
|
||||||
verbose_name=_("Minimum transfer time"),
|
verbose_name=_("Minimum transfer time"),
|
||||||
blank=True,
|
blank=True,
|
||||||
default=None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
Loading…
Reference in New Issue
Block a user