Compare commits

...

3 Commits

Author SHA1 Message Date
Emmy D'Anello 1c99c5ca47
Use MOTIS API now 2024-11-11 21:24:01 +01:00
Emmy D'Anello e3fd6a7f88
Remove local proxy 2024-11-10 16:38:08 +01:00
Emmy D'Anello 036e1604bd
Drop trainvel backend, this is now a frontend-only app 2024-11-10 16:34:18 +01:00
87 changed files with 417 additions and 8457 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_MOTIS_SERVER=https://motis.luemy.eu

65
.gitignore vendored
View File

@ -1,53 +1,26 @@
# Byte-compiled / optimized / DLL files
dist
build
__pycache__
*.py[cod]
*$py.class
*.swp
*.egg-info
_build
.tox
.coverage
coverage
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Translations
*.mo
*.pot
# Jupyter Notebook
.ipynb_checkpoints
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# PyCharm project settings
.idea
# VSCode project settings
.vscode
# Local data
secrets.py
settings_local.py
*.log
*.txt
media/
output/
/static/
/static_files/
# dependencies
/node_modules
/.pnp
.pnp.js
# Virtualenv
.env/
env/
.venv/
venv/
db.sqlite3
db.sqlite3-journal
# testing
/coverage
node_modules/
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()

View File

@ -46,6 +46,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:8000"
}
}

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -1,8 +0,0 @@
Django>=5.0.4,<6.0
django-cors-headers~=4.3.1
django-extensions~=3.2.3
django-filter~=24.2
djangorestframework~=3.14.0
protobuf~=5.26.1
requests~=2.31.0
tqdm~=4.66.4

View File

@ -6,10 +6,9 @@ import {frFR, LocalizationProvider} from "@mui/x-date-pickers"
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
import 'dayjs/locale/fr'
import './App.css'
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister";
import {persistQueryClient} from "@tanstack/react-query-persist-client";
import Home from "./Home";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
import Home from "./Home"
import dayjs from "dayjs"
function App() {
const router = createBrowserRouter([
@ -18,7 +17,7 @@ function App() {
element: <Home />,
},
{
path: "/station/:theme/:stationSlug",
path: "/station/:theme/:stationId",
element: <Station />
}
])
@ -54,14 +53,7 @@ function App() {
},
})
const localStoragePersister = createSyncStoragePersister({
storage: window.localStorage,
})
persistQueryClient({
queryClient,
persister: localStoragePersister,
})
dayjs.locale('fr')
return <>
<ThemeProvider theme={theme}>

View File

@ -1,9 +1,8 @@
import {Autocomplete, TextField} from "@mui/material";
import {useRef, useState} from "react";
import {useState} from "react";
function AutocompleteStation(params) {
const [options, setOptions] = useState([])
const previousController = useRef()
function onInputChange(event, value) {
if (!value) {
@ -11,17 +10,9 @@ function AutocompleteStation(params) {
return
}
if (previousController.current)
previousController.current.abort()
const controller = new AbortController()
const signal = controller.signal
previousController.current = controller
fetch("/api/core/station/?search=" + value, {signal})
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`)
.then(response => response.json())
.then(data => data.results)
.then(setOptions)
.catch()
}
return <>
@ -29,7 +20,7 @@ function AutocompleteStation(params) {
id="stop"
options={options}
onInputChange={onInputChange}
filterOptions={(x) => x}
filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))}
getOptionKey={option => option.id}
getOptionLabel={option => option.name}
groupBy={option => getOptionGroup(option)}
@ -40,7 +31,7 @@ function AutocompleteStation(params) {
}
function getOptionGroup(option) {
return option.country
return option.id.split('_')[0]
}
export default AutocompleteStation;

View File

@ -5,7 +5,7 @@ function Home() {
const navigate = useNavigate()
function onStationSelected(event, station) {
navigate(`/station/sncf/${station.slug}/`)
navigate(`/station/sncf/${station.id}/`)
}
return <>

72
src/Station.js Normal file
View File

@ -0,0 +1,72 @@
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
import TrainsTable from "./TrainsTable"
import TripsFilter from "./TripsFilter"
import {useState} from "react"
import {Box, Button, FormLabel} from "@mui/material"
import {DateTimePicker} from "@mui/x-date-pickers"
import dayjs from "dayjs"
import {useQuery, useQueryClient} from "@tanstack/react-query"
import AutocompleteStation from "./AutocompleteStation"
function DateTimeSelector({datetime, setDatetime}) {
const navigate = useNavigate()
function onStationSelected(event, station) {
if (station !== null)
navigate(`/station/sncf/${station.id}/`)
}
return <>
<Box component="form" display="flex" alignItems="center" sx={{'& .MuiTextField-root': { m: 1, width: '25ch' },}}>
<FormLabel>
Changer la gare recherchée :
</FormLabel>
<AutocompleteStation onChange={onStationSelected} />
<FormLabel>
Modifier la date et l'heure de recherche :
</FormLabel>
<DateTimePicker name="date" label="Date" onChange={setDatetime} value={datetime} />
<Button type="submit">Rechercher</Button>
</Box>
</>
}
function Station() {
// eslint-disable-next-line no-unused-vars
let {theme, stationId} = useParams()
// eslint-disable-next-line no-unused-vars
let [searchParams, setSearchParams] = useSearchParams()
const [datetime, setDatetime] = useState(dayjs())
useQueryClient()
const stationQuery = useQuery({
queryKey: ['station', stationId],
queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?stopId=${stationId}&n=1`)
.then(response => response.json()),
enabled: !!stationId,
})
const station = stationQuery.data?.stopTimes[0].place ?? {name: "Chargement…"}
if (searchParams.get("time") === undefined) {
setInterval(() => {
setDatetime(dayjs())
}, 5000)
}
return (
<div className="Station">
<header className="App-header">
<h1>Horaires en gare de {station.name}</h1>
</header>
<main>
<DateTimeSelector datetime={datetime} setDatetime={setDatetime} />
<TripsFilter />
<TrainsTable station={station} datetime={datetime} tableType="departures" />
<TrainsTable station={station} datetime={datetime} tableType="arrivals" />
</main>
</div>
)
}
export default Station;

310
src/TrainsTable.js Normal file
View File

@ -0,0 +1,310 @@
import {
Box,
styled,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography
} from "@mui/material"
import {CSSTransition, TransitionGroup} from 'react-transition-group'
import {useQuery} from "@tanstack/react-query"
import {useCallback, useEffect, useMemo, useRef} from "react"
import dayjs from "dayjs"
const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
'tbody &:nth-of-type(odd)': {
backgroundColor: theme.palette.sncf[tabletype].light,
},
'th, &:nth-of-type(even)': {
backgroundColor: theme.palette.sncf[tabletype].dark,
},
// hide last border
'&:last-child td, &:last-child th': {
border: 0,
},
}));
function TrainsTable({station, datetime, tableType}) {
return <>
<TableContainer>
<Table>
<TrainsTableHeader tableType={tableType} />
<TrainsTableBody station={station} datetime={datetime} tableType={tableType} />
</Table>
</TableContainer>
</>
}
function TrainsTableHeader({tableType}) {
return <>
<TableHead>
<StyledTableRow tabletype={tableType}>
<TableCell colSpan="2" fontSize={16} fontWeight="bold">Train</TableCell>
<TableCell fontSize={16} fontWeight="bold">Heure</TableCell>
<TableCell fontSize={16} fontWeight="bold">Destination</TableCell>
</StyledTableRow>
</TableHead>
</>
}
function TrainsTableBody({station, datetime, tableType}) {
const filterTime = useCallback((train) => {
if (tableType === "departures")
return dayjs(train.place.departure) >= datetime
else
return dayjs(train.place.arrival) >= datetime
}, [datetime, tableType])
const updateTrains = useCallback(() => {
const query_params = new URLSearchParams({
stopId: station.stopId,
arriveBy: tableType === "arrivals",
time: datetime.format(),
n: 20,
}).toString()
return fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/stoptimes?${query_params}`)
.then(response => response.json())
.then(data => data.stopTimes)
.then(data => [...data])
}, [station.stopId, datetime, tableType])
const trainsQuery = useQuery({
queryKey: ['trains', station.stopId, tableType],
queryFn: updateTrains,
enabled: !!station.stopId,
})
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
useEffect(() => {
let validTrains = trains?.filter(filterTime) ?? []
if (trains?.length > 0 && validTrains.length < trains?.length)
trainsQuery.refetch().then()
}, [trains, filterTime, trainsQuery])
const nullRef = useRef(null)
let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}>
<TrainRow train={train} tableType={tableType} />
</CSSTransition>)
return <>
<TableBody>
<TransitionGroup component={null}>
{table_rows}
</TransitionGroup>
</TableBody>
</>
}
function TrainRow({train, tableType}) {
const tripQuery = useQuery({
queryKey: ['tripId', train.tripId],
queryFn: () => fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/trip?${new URLSearchParams({tripId: train.tripId})}`)
.then(response => response.json()),
enabled: !!train.tripId,
})
const trip = tripQuery.data ?? {}
const leg = trip.legs ? trip.legs[0] : null
const trainType = getTrainType(train)
const backgroundColor = getBackgroundColor(train)
const textColor = getTextColor(train)
const trainTypeDisplay = getTrainTypeDisplay(trainType)
const stops = useMemo(() => leg ? [leg.from, ...leg.intermediateStops, leg.to] : [], [leg])
const stopIndex = useMemo(() => {
if (stops.length === 0 || train.place.stopId === undefined)
return -1
for (let i = 0; i < stops.length; i++) {
const index = tableType === "departures" ? i : stops.length - 1 - i
const stop = stops[index]
let timeCond = tableType === "departures" ? stop.scheduledDeparture === train.place.scheduledDeparture
: stop.scheduledArrival === train.place.scheduledArrival
if (stop.stopId === train.place.stopId && timeCond)
return index
}
}, [stops, train, tableType])
const nextStops = tableType === "departures" ? stops.slice(stopIndex + 1) : stops.slice(0, stopIndex)
let headline = nextStops[tableType === "departures" ? nextStops.length - 1 : 0] ?? {name: "Chargement…"}
const canceled = false // TODO Implémenter l'annulation
const [delayed, prettyDelay] = getPrettyDelay(train, tableType)
let stopsNames = nextStops.map(stopTime => stopTime?.name ?? "").join(" > ") ?? ""
return <>
<StyledTableRow tabletype={tableType}>
<TableCell>
<div>
<Box display="flex"
justifyContent="center"
alignItems="center"
textAlign="center"
width="4em"
height="4em"
borderRadius="15%"
fontWeight="bold"
backgroundColor={backgroundColor}
color={textColor}>
{trainTypeDisplay}
</Box>
</div>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" justifyContent="center" textAlign="center">
<div>
<div>{train.routeShortName}</div>
<div>{train.headsign}</div>
</div>
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" justifyContent="center">
<Box>
<Box fontWeight="bold" color="#FFED02" fontSize={24}>
{getDisplayTime(train, tableType)}
</Box>
<Box color={delayed ? "#e86d2b" : "white"}
fontWeight={delayed ? "bold" : ""}>
{prettyDelay}
</Box>
</Box>
</Box>
</TableCell>
<TableCell>
<Box style={{textDecoration: canceled ? 'line-through': ''}}>
<Typography fontSize={24} fontWeight="bold" data-stop-id={headline.stopId}>{headline.name}</Typography>
<span className="stops">{stopsNames}</span>
</Box>
</TableCell>
</StyledTableRow>
</>
}
function getTrainType(train) {
if (train.place.stopId === undefined)
return ""
switch (train.place.stopId.split('_')[0]) {
case "FR-SNCF-TGV":
case "FR-SNCF-IC":
case "FR-SNCF-TER":
let trainType = train.place.stopId.split("StopPoint:OCE")[1].split("-")[0]
switch (trainType) {
case "Train TER":
return "TER"
case "INTERCITES":
return "INTER-CITÉS"
case "INTERCITES de nuit":
return "INTER-CITÉS de nuit"
default:
return trainType
}
case "FR-IDF-IDFM":
case "FR-GES-CTS":
return "A"
case "FR-EUROSTAR":
return "Eurostar"
case "IT-FRA-TI":
return "Trenitalia France"
case "ES-RENFE":
return "RENFE"
case "AT-OBB":
if (train.routeShortName?.startsWith("NJ"))
return "NJ"
return "ÖBB"
case "CH-ALL":
return "A"
default:
return train.routeShortName?.split(" ")[0]
}
}
function getTrainTypeDisplay(trainType) {
switch (trainType) {
case "TGV INOUI":
return <img src="/tgv_inoui.svg" alt="TGV INOUI" width="80%" />
case "OUIGO":
return <img src="/ouigo.svg" alt="OUIGO" width="80%" />
case "ICE":
return <img src="/ice.svg" alt="ICE" width="80%" />
case "Lyria":
return <img src="/lyria.svg" alt="Lyria" width="80%" />
case "TER":
return <img src="/ter.svg" alt="TER" width="80%" />
case "Car TER":
return <div><img src="/bus.svg" alt="Car" width="40%" />
<br/>
<img src="/ter.svg" alt="TER" width="40%" /></div>
case "Eurostar":
return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" />
case "Trenitalia":
case "Trenitalia France":
return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" />
case "RENFE":
return <img src="/renfe.svg" alt="RENFE" width="80%" />
case "NJ":
return <img src="/nightjet.svg" alt="NightJet" width="80%" />
default:
return trainType
}
}
function getBackgroundColor(train) {
let trainType = getTrainType(train)
switch (trainType) {
case "OUIGO":
return "#0096CA"
case "Eurostar":
return "#00286A"
case "NJ":
return "#272759"
default:
if (train.routeColor)
return `#${train.routeColor}`
return "#FFFFFF"
}
}
function getTextColor(train) {
if (train.routeTextColor)
return `#${train.routeTextColor}`
else {
let trainType = getTrainType(train)
switch (trainType) {
case "OUIGO":
return "#FFFFFF"
case "TGV INOUI":
return "#9B2743"
case "ICE":
return "#B4B4B4"
case "INTER-CITÉS":
case "INTER-CITÉS de nuit":
return "#404042"
default:
return "#000000"
}
}
}
function getDisplayTime(train, tableType) {
dayjs.locale('fr')
let time = tableType === "departures" ? train.place.scheduledDeparture : train.place.scheduledArrival
return dayjs(time).format('LT')
}
function getPrettyDelay(train, tableType) {
if (train === undefined || !train.realTime) {
return [false, ""]
}
const [scheduled, projected] = tableType === "departures" ? [train.place.scheduledDeparture, train.place.departure]
: [train.place.scheduledArrival, train.place.arrival]
const delay_minutes = dayjs(projected).diff(dayjs(scheduled), "minute")
if (delay_minutes === 0)
return [false, "À l'heure"]
return [true, `+${delay_minutes} min`]
}
export default TrainsTable;

View File

@ -1,4 +1,4 @@
import React, {useMemo} from 'react';
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import reportWebVitals from './reportWebVitals';

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1,79 +0,0 @@
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
import TrainsTable from "./TrainsTable"
import TripsFilter from "./TripsFilter"
import {useState} from "react";
import {Box, Button, FormLabel} from "@mui/material";
import {DatePicker, TimePicker} from "@mui/x-date-pickers";
import dayjs from "dayjs";
import {useQuery, useQueryClient} from "@tanstack/react-query";
import AutocompleteStation from "./AutocompleteStation";
function DateTimeSelector({station, date, time}) {
const navigate = useNavigate()
function onStationSelected(event, station) {
if (station !== null)
navigate(`/station/sncf/${station.slug}/`)
}
return <>
<Box component="form" display="flex" alignItems="center" sx={{'& .MuiTextField-root': { m: 1, width: '25ch' },}}>
<FormLabel>
Changer la gare recherchée :
</FormLabel>
<AutocompleteStation onChange={onStationSelected} />
<FormLabel>
Modifier la date et l'heure de recherche :
</FormLabel>
<DatePicker name="date" label="Date" format="YYYY-MM-DD" defaultValue={dayjs(`${date}`)} />
<TimePicker name="time" label="Heure" format="HH:mm" defaultValue={dayjs(`${date} ${time}`)} />
<Button type="submit">Rechercher</Button>
</Box>
</>
}
function Station() {
let {theme, stationSlug} = useParams()
let [searchParams, _setSearchParams] = useSearchParams()
const now = new Date()
let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
let [date, setDate] = useState(searchParams.get('date') || dateNow)
let [time, setTime] = useState(searchParams.get('time') || timeNow)
useQueryClient()
const stationQuery = useQuery({
queryKey: ['station', stationSlug],
queryFn: () => fetch(`/api/core/station/${stationSlug}/`)
.then(response => response.json()),
enabled: !!stationSlug,
})
const station = stationQuery.data ?? {name: "Chargement…"}
if (time === timeNow) {
setInterval(() => {
const now = new Date()
let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
setDate(dateNow)
setTime(timeNow)
}, 5000)
}
return (
<div className="Station">
<header className="App-header">
<h1>Horaires en gare de {station.name}</h1>
</header>
<main>
<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="arrivals" />
</main>
</div>
)
}
export default Station;

View File

@ -1,364 +0,0 @@
import {
Box,
styled,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography
} from "@mui/material"
import {CSSTransition, TransitionGroup} from 'react-transition-group'
import {useQueries, useQuery} from "@tanstack/react-query";
import {useCallback, useEffect, useMemo, useRef, useState} from "react";
const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
'tbody &:nth-of-type(odd)': {
backgroundColor: theme.palette.sncf[tabletype].light,
},
'th, &:nth-of-type(even)': {
backgroundColor: theme.palette.sncf[tabletype].dark,
},
// hide last border
'&:last-child td, &:last-child th': {
border: 0,
},
}));
function TrainsTable({station, date, time, tableType}) {
return <>
<TableContainer>
<Table>
<TrainsTableHeader tableType={tableType} />
<TrainsTableBody station={station} date={date} time={time} tableType={tableType} />
</Table>
</TableContainer>
</>
}
function TrainsTableHeader({tableType}) {
return <>
<TableHead>
<StyledTableRow tabletype={tableType}>
<TableCell colSpan="2" fontSize={16} fontWeight="bold">Train</TableCell>
<TableCell fontSize={16} fontWeight="bold">Heure</TableCell>
<TableCell fontSize={16} fontWeight="bold">Destination</TableCell>
</StyledTableRow>
</TableHead>
</>
}
function TrainsTableBody({station, date, time, tableType}) {
const filterTime = useCallback((train) => {
if (tableType === "departures")
return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}`
else
return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}`
}, [date, time, tableType])
const updateTrains = useCallback(() => {
return fetch(`/api/station/next_${tableType}/?station_slug=${station.slug}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
.then(response => response.json())
.then(data => data.results)
.then(data => [...data])
}, [station.id, date, time, tableType])
const trainsQuery = useQuery({
queryKey: ['trains', station.id, tableType],
queryFn: updateTrains,
enabled: !!station.id,
})
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
useEffect(() => {
let validTrains = trains?.filter(filterTime) ?? []
if (trains?.length > 0 && validTrains.length < trains?.length)
trainsQuery.refetch().then()
}, [trains, filterTime, trainsQuery])
const nullRef = useRef(null)
let table_rows = trains.map((train) => <CSSTransition key={train.id} timeout={500} classNames="shrink" nodeRef={nullRef}>
<TrainRow train={train} tableType={tableType} date={date} time={time} />
</CSSTransition>)
return <>
<TableBody>
<TransitionGroup component={null}>
{table_rows}
</TransitionGroup>
</TableBody>
</>
}
function TrainRow({train, tableType, date, time}) {
const tripQuery = useQuery({
queryKey: ['trip', train.trip],
queryFn: () => fetch(`/api/gtfs/trip/${train.trip}/`)
.then(response => response.json()),
enabled: !!train.trip,
})
const trip = tripQuery.data ?? {}
const routeQuery = useQuery({
queryKey: ['route', trip.route],
queryFn: () => fetch(`/api/gtfs/route/${trip.route}/`)
.then(response => response.json()),
enabled: !!trip.route,
})
const route = routeQuery.data ?? {}
const trainType = getTrainType(train, trip, route)
const backgroundColor = getBackgroundColor(train, trip, route)
const textColor = getTextColor(train, trip, route)
const trainTypeDisplay = getTrainTypeDisplay(trainType)
const stopTimesQuery = useQuery({
queryKey: ['stop_times', trip.id],
queryFn: () => fetch(`/api/gtfs/stop_time/?${new URLSearchParams({trip: trip.id, order: 'stop_sequence', limit: 1000})}`)
.then(response => response.json())
.then(data => data.results),
enabled: !!trip.id,
})
const stopTimes = stopTimesQuery.data ?? []
const stopIds = stopTimes.map(stop_time => stop_time.stop)
const stopQueries = useQueries({
queries: stopIds.map(stopId => ({
queryKey: ['stop', stopId],
queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`)
.then(response => response.json()),
enabled: !!stopId,
})),
})
const stops = stopTimes.map(((stopTime, i) => ({...stopTime, stop: stopQueries[i]?.data ?? {"name": "…"}}))) ?? []
let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"}
const realtimeTripQuery = useQuery({
queryKey: ['realtimeTrip', trip.id, date, time],
queryFn: () => fetch(`/api/gtfs-rt/trip_update/${trip.id}/`)
.then(response => response.json()),
enabled: !!trip.id,
})
const [realtimeTripData, setRealtimeTripData] = useState({})
useEffect(() => {
if (realtimeTripQuery.data)
setRealtimeTripData(realtimeTripQuery.data)
}, [realtimeTripQuery.data])
const tripScheduleRelationship = realtimeTripData.schedule_relationship ?? 0
const realtimeQuery = useQuery({
queryKey: ['realtime', train.id, date, time],
queryFn: () => fetch(`/api/gtfs-rt/stop_time_update/${train.id}/`)
.then(response => response.json()),
enabled: !!train.id,
})
const [realtimeData, setRealtimeData] = useState({})
useEffect(() => {
if (realtimeQuery.data)
setRealtimeData(realtimeQuery.data)
}, [realtimeQuery.data])
const stopScheduleRelationship = realtimeData.schedule_relationship ?? 0
const canceled = tripScheduleRelationship === 3 || stopScheduleRelationship === 1
const delay = tableType === "departures" ? realtimeData.departure_delay : realtimeData.arrival_delay
const prettyDelay = delay && !canceled ? getPrettyDelay(delay) : ""
const [prettyScheduleRelationship, scheduleRelationshipColor] = getPrettyScheduleRelationship(tripScheduleRelationship, stopScheduleRelationship)
let stopsFilter
if (canceled)
stopsFilter = (stop_time) => true
else if (tableType === "departures")
stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0
else
stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0
let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(" > ") ?? ""
return <>
<StyledTableRow tabletype={tableType}>
<TableCell>
<div>
<Box display="flex"
justifyContent="center"
alignItems="center"
textAlign="center"
width="4em"
height="4em"
borderRadius="15%"
fontWeight="bold"
backgroundColor={backgroundColor}
color={textColor}>
{trainTypeDisplay}
</Box>
</div>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" justifyContent="center" textAlign="center">
<div>
<div>{trip.short_name}</div>
<div>{trip.headsign}</div>
</div>
</Box>
</TableCell>
<TableCell>
<Box display="flex" alignItems="center" justifyContent="center">
<Box>
<Box fontWeight="bold" color="#FFED02" fontSize={24}>
{getDisplayTime(train, tableType)}
</Box>
<Box color={delay && delay !== "00:00:00" ? "#e86d2b" : "white"}
fontWeight={delay && delay !== "00:00:00" ? "bold" : ""}>
{prettyDelay}
</Box>
<Box color={scheduleRelationshipColor} fontWeight="bold">
{prettyScheduleRelationship}
</Box>
</Box>
</Box>
</TableCell>
<TableCell>
<Box style={{textDecoration: canceled ? 'line-through': ''}}>
<Typography fontSize={24} fontWeight="bold" data-stop-id={headline.id}>{headline.name}</Typography>
<span className="stops">{stopsNames}</span>
</Box>
</TableCell>
</StyledTableRow>
</>
}
function getTrainType(train, trip, route) {
switch (route.gtfs_feed) {
case "FR-SNCF-TGV":
case "FR-SNCF-IC":
case "FR-SNCF-TER":
let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0]
switch (trainType) {
case "Train TER":
return "TER"
case "INTERCITES":
return "INTER-CITÉS"
case "INTERCITES de nuit":
return "INTER-CITÉS de nuit"
default:
return trainType
}
case "FR-IDF-IDFM":
case "FR-GES-CTS":
return route.short_name
case "FR-EUROSTAR":
return "Eurostar"
case "IT-FRA-TI":
return "Trenitalia France"
case "ES-RENFE":
return "RENFE"
case "AT-OBB":
if (trip.short_name?.startsWith("NJ"))
return "NJ"
return "ÖBB"
case "CH-ALL":
return route.desc
default:
return trip.short_name?.split(" ")[0]
}
}
function getTrainTypeDisplay(trainType) {
switch (trainType) {
case "TGV INOUI":
return <img src="/tgv_inoui.svg" alt="TGV INOUI" width="80%" />
case "OUIGO":
return <img src="/ouigo.svg" alt="OUIGO" width="80%" />
case "ICE":
return <img src="/ice.svg" alt="ICE" width="80%" />
case "Lyria":
return <img src="/lyria.svg" alt="Lyria" width="80%" />
case "TER":
return <img src="/ter.svg" alt="TER" width="80%" />
case "Car TER":
return <div><img src="/bus.svg" alt="Car" width="40%" />
<br/>
<img src="/ter.svg" alt="TER" width="40%" /></div>
case "Eurostar":
return <img src="/eurostar_mini.svg" alt="Eurostar" width="80%" />
case "Trenitalia":
case "Trenitalia France":
return <img src="/trenitalia.svg" alt="Frecciarossa" width="80%" />
case "RENFE":
return <img src="/renfe.svg" alt="RENFE" width="80%" />
case "NJ":
return <img src="/nightjet.svg" alt="NightJet" width="80%" />
default:
return trainType
}
}
function getBackgroundColor(train, trip, route) {
let trainType = getTrainType(train, trip, route)
switch (trainType) {
case "OUIGO":
return "#0096CA"
case "Eurostar":
return "#00286A"
case "NJ":
return "#272759"
default:
if (route.color)
return `#${route.color}`
return "#FFFFFF"
}
}
function getTextColor(train, trip, route) {
if (route.text_color)
return `#${route.text_color}`
else {
let trainType = getTrainType(train, trip, route)
switch (trainType) {
case "OUIGO":
return "#FFFFFF"
case "TGV INOUI":
return "#9B2743"
case "ICE":
return "#B4B4B4"
case "INTER-CITÉS":
case "INTER-CITÉS de nuit":
return "#404042"
default:
return "#000000"
}
}
}
function getDisplayTime(train, tableType) {
let time = tableType === "departures" ? train.departure_time : train.arrival_time
let day_split = time.split(' ')
return day_split[day_split.length - 1].substring(0, 5)
}
function getPrettyDelay(delay) {
let delay_split = delay.split(':')
let hours = parseInt(delay_split[0])
let minutes = parseInt(delay_split[1])
let full_minutes = hours * 60 + minutes
return full_minutes ? `+${full_minutes} min` : "À l'heure"
}
function getPrettyScheduleRelationship(tripScheduledRelationship, stopScheduledRelationship) {
switch (tripScheduledRelationship) {
case 1:
return ["Ajouté", "#3ebb18"]
case 3:
return ["Supprimé", "#ff8701"]
default:
switch (stopScheduledRelationship) {
case 1:
return ["Supprimé", "#ff8701"]
default:
return ["", ""]
}
}
}
export default TrainsTable;

View File

View File

@ -1,6 +0,0 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "trainvel.api"

View File

@ -1,91 +0,0 @@
from rest_framework import serializers
from trainvel.core.models import Station
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, \
Stop, StopTime, StopTimeUpdate, Transfer, Trip, TripUpdate
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 GTFSFeedSerializer(serializers.ModelSerializer):
class Meta:
model = GTFSFeed
fields = '__all__'
class AgencySerializer(serializers.ModelSerializer):
class Meta:
model = Agency
fields = '__all__'
class StopSerializer(serializers.ModelSerializer):
class Meta:
model = Stop
fields = '__all__'
class RouteSerializer(serializers.ModelSerializer):
class Meta:
model = Route
fields = '__all__'
class TripSerializer(serializers.ModelSerializer):
class Meta:
model = Trip
fields = '__all__'
class StopTimeSerializer(serializers.ModelSerializer):
arrival_date = serializers.DateField(required=False)
departure_date = serializers.DateField(required=False)
arrival_time_24h = serializers.DurationField(required=False)
departure_time_24h = serializers.DurationField(required=False)
departure_time_real = serializers.CharField(required=False)
class Meta:
model = StopTime
fields = '__all__'
class CalendarSerializer(serializers.ModelSerializer):
class Meta:
model = Calendar
fields = '__all__'
class CalendarDateSerializer(serializers.ModelSerializer):
class Meta:
model = CalendarDate
fields = '__all__'
class TransferSerializer(serializers.ModelSerializer):
class Meta:
model = Transfer
fields = '__all__'
class FeedInfoSerializer(serializers.ModelSerializer):
class Meta:
model = FeedInfo
fields = '__all__'
class TripUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = TripUpdate
fields = '__all__'
class StopTimeUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = StopTimeUpdate
fields = '__all__'

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,357 +0,0 @@
from datetime import datetime, timedelta, date
from django.db.models import Exists, Case, F, Min, OuterRef, Q, Value, When
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control
from django.views.decorators.http import last_modified
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from rest_framework.filters import OrderingFilter, SearchFilter
from trainvel.api.serializers import AgencySerializer, CalendarDateSerializer, CalendarSerializer, \
FeedInfoSerializer, GTFSFeedSerializer, RouteSerializer, StationSerializer, StopSerializer, StopTimeSerializer, \
StopTimeUpdateSerializer, TransferSerializer, TripSerializer, TripUpdateSerializer
from trainvel.core.models import Station
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
StopTimeUpdate, Transfer, Trip, TripUpdate, PickupType
CACHE_CONTROL = cache_control(max_age=30)
LAST_MODIFIED = last_modified(lambda *args, **kwargs: GTFSFeed.objects.order_by('-last_modified').first().last_modified)
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='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='retrieve', decorator=[CACHE_CONTROL, LAST_MODIFIED])
class AgencyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Agency.objects.all()
serializer_class = AgencySerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
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 StopViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Stop.objects.all()
serializer_class = StopSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = '__all__'
search_fields = ['name',]
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 RouteViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Route.objects.all()
serializer_class = RouteSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
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 TripViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Trip.objects.all()
serializer_class = TripSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
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 StopTimeViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StopTime.objects.order_by('id').all()
serializer_class = StopTimeSerializer
filter_backends = [DjangoFilterBackend, OrderingFilter]
filterset_fields = '__all__'
ordering_fields = ['arrival_time', 'departure_time', 'stop_sequence', ]
ordering = ['stop_sequence', ]
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 CalendarViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Calendar.objects.all()
serializer_class = CalendarSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
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 CalendarDateViewSet(viewsets.ReadOnlyModelViewSet):
queryset = CalendarDate.objects.all()
serializer_class = CalendarDateSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
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 TransferViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Transfer.objects.all()
serializer_class = TransferSerializer
filter_backends = [DjangoFilterBackend]
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 FeedInfoViewSet(viewsets.ReadOnlyModelViewSet):
queryset = FeedInfo.objects.all()
serializer_class = FeedInfoSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
lookup_value_regex = LOOKUP_VALUE_REGEX
class TripUpdateViewSet(viewsets.ReadOnlyModelViewSet):
queryset = TripUpdate.objects.all()
serializer_class = TripUpdateSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
lookup_value_regex = LOOKUP_VALUE_REGEX
class StopTimeUpdateViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StopTimeUpdate.objects.all()
serializer_class = StopTimeUpdateSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = '__all__'
lookup_value_regex = LOOKUP_VALUE_REGEX
class NextDeparturesViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StopTime.objects.none()
serializer_class = StopTimeSerializer
def get_queryset(self):
now = datetime.now()
station_slug = self.request.query_params.get('station_slug', None)
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 = timedelta(seconds=int(query_time[:2]) * 3600
+ int(query_time[3:5]) * 60
+ (int(query_time[6:]) if len(query_time) > 6 else 0))
yesterday = query_date - timedelta(days=1)
time_yesterday = query_time + timedelta(days=1)
tomorrow = query_date + timedelta(days=1)
stop_filter = Q(stop__location_type=0)
if station_slug:
station = Station.objects.get(is_suggestable=True, slug=station_slug)
near_stops = station.get_near_stops()
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'))
.filter(pickup_type=PickupType.REGULAR)
.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):
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
.values_list('service_id')) \
| Q(trip__service_id__in=Calendar.objects.filter(
start_date__lte=d,
end_date__gte=d,
**{f"{d:%A}".lower(): True})
.filter(~Q(id__in=CalendarDate.objects.filter(date=d, exception_type=2)
.values_list('service_id', flat=True)))
.values_list('id'))
def stop_time_update_qs(d: date):
return StopTimeUpdate.objects.filter(trip_update__start_date=d) \
.exclude(departure_time=datetime.fromtimestamp(0)).filter(stop_time_id=OuterRef('pk'))
def departure_time_real(d: date):
return Case(
When(
condition=Exists(stop_time_update_qs(d)),
then=F('departure_time') + stop_time_update_qs(d).values('departure_delay'),
),
default=F('departure_time'),
)
def canceled_filter(d: date):
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
qs_today = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_last_stop) \
.filter(trip_filter) \
.annotate(departure_time_real=departure_time_real(query_date)) \
.filter(departure_time_real__gte=query_time) \
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(query_date)) \
.filter(calendar_filter(query_date)) \
.annotate(departure_date=Value(query_date)) \
.annotate(departure_time_24h=F('departure_time'))
qs_yesterday = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_last_stop) \
.filter(trip_filter) \
.annotate(departure_time_real=departure_time_real(query_date)) \
.filter(departure_time_real__gte=time_yesterday) \
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
.filter(calendar_filter(yesterday)) \
.annotate(departure_date=Value(yesterday)) \
.annotate(departure_time_24h=F('departure_time') - timedelta(days=1))
qs_tomorrow = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_last_stop) \
.filter(trip_filter) \
.annotate(departure_time_real=departure_time_real(query_date)) \
.filter(departure_time_real__gte=timedelta(0)) \
.filter(Q(pickup_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
.filter(calendar_filter(tomorrow)) \
.annotate(departure_date=Value(tomorrow)) \
.annotate(departure_time_24h=F('departure_time') + timedelta(days=1))
return qs_today.union(qs_yesterday).union(qs_tomorrow).order_by("departure_time_24h").all()
class NextArrivalsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = StopTime.objects.none()
serializer_class = StopTimeSerializer
filter_backends = [DjangoFilterBackend]
def get_queryset(self):
now = datetime.now()
station_slug = self.request.query_params.get('station_slug', None)
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 = timedelta(seconds=int(query_time[:2]) * 3600
+ int(query_time[3:5]) * 60
+ (int(query_time[6:]) if len(query_time) > 6 else 0))
query_time -= timedelta(minutes=5) # Keep the last trains of the 5 previous minutes
yesterday = query_date - timedelta(days=1)
time_yesterday = query_time + timedelta(days=1)
tomorrow = query_date + timedelta(days=1)
stop_filter = Q(stop__location_type=0)
if station_slug:
station = Station.objects.get(is_suggestable=True, slug=station_slug)
near_stops = station.get_near_stops()
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'))
.filter(drop_off_type=PickupType.REGULAR)
.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):
return Q(trip__service_id__in=CalendarDate.objects.filter(date=d, exception_type=1)
.values_list('service_id')) \
| Q(trip__service_id__in=Calendar.objects.filter(
start_date__lte=d,
end_date__gte=d,
**{f"{d:%A}".lower(): True})
.filter(~Q(id__in=CalendarDate.objects.filter(date=d, exception_type=2)
.values_list('service_id', flat=True)))
.values_list('id'))
def stop_time_update_qs(d: date):
return StopTimeUpdate.objects.filter(trip_update__start_date=d) \
.exclude(arrival_time=datetime.fromtimestamp(0)).filter(stop_time_id=OuterRef('pk'))
def arrival_time_real(d: date):
return Case(
When(
condition=Exists(stop_time_update_qs(d)),
then=F('arrival_time') + stop_time_update_qs(d).values('arrival_delay'),
),
default=F('arrival_time'),
)
def canceled_filter(d: date):
return Exists(stop_time_update_qs(d).filter(Q(schedule_relationship=1) | Q(schedule_relationship=3)))
qs_today = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_first_stop) \
.filter(trip_filter) \
.annotate(arrival_time_real=arrival_time_real(query_date)) \
.filter(arrival_time_real__gte=query_time) \
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(query_date)) \
.filter(calendar_filter(query_date)) \
.annotate(arrival_date=Value(query_date)) \
.annotate(arrival_time_24h=F('arrival_time'))
qs_yesterday = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_first_stop) \
.filter(trip_filter) \
.annotate(arrival_time_real=arrival_time_real(yesterday)) \
.filter(arrival_time_real__gte=time_yesterday) \
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(yesterday)) \
.filter(calendar_filter(yesterday)) \
.annotate(arrival_date=Value(yesterday)) \
.annotate(arrival_time_24h=F('arrival_time') - timedelta(days=1))
qs_tomorrow = StopTime.objects.filter(stop_filter) \
.filter(excluded_agencies) \
.filter(not_first_stop) \
.filter(trip_filter) \
.annotate(arrival_time_real=arrival_time_real(tomorrow)) \
.filter(arrival_time_real__gte=timedelta(0)) \
.filter(Q(drop_off_type=PickupType.REGULAR) | canceled_filter(tomorrow)) \
.filter(calendar_filter(tomorrow)) \
.annotate(arrival_date=Value(tomorrow)) \
.annotate(arrival_time_24h=F('arrival_time') + timedelta(days=1))
return qs_today.union(qs_yesterday).union(qs_tomorrow).order_by("arrival_time_24h").all()

View File

@ -1,16 +0,0 @@
"""
ASGI config for trainvel project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings")
application = get_asgi_application()

View File

@ -1,23 +0,0 @@
from django.contrib import admin
from trainvel.core.models import Station
class StationInline(admin.TabularInline):
model = Station
extra = 0
autocomplete_fields = ('parent_station', 'same_as',)
show_change_link = True
ordering = ('name',)
readonly_fields = ('id',)
fk_name = 'parent_station'
@admin.register(Station)
class StationAdmin(admin.ModelAdmin):
list_display = ('name', 'country', 'uic', 'latitude', 'longitude',)
list_filter = ('country', 'is_city', 'is_main_station', 'is_airport', 'is_suggestable',
'country_hint', 'main_station_hint',)
search_fields = ('name', 'slug',)
autocomplete_fields = ('parent_station', 'same_as',)
inlines = [StationInline]

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "trainvel.core"
verbose_name = _("Trainvel - Core")

View File

@ -1,325 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-09 22:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <ynerant@emy.lu>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: trainvel/core/apps.py:8
msgid "Trainvel - Core"
msgstr "Trainvel - Cœur"
#: trainvel/core/models.py:15
msgid "name"
msgstr "nom"
#: trainvel/core/models.py:20
msgid "slug"
msgstr "slug"
#: trainvel/core/models.py:24
msgid "UIC"
msgstr "UIC"
#: trainvel/core/models.py:31
msgid "UIC8 SNCF"
msgstr "UIC8 SNCF"
#: trainvel/core/models.py:38
msgid "latitude"
msgstr "latitude"
#: trainvel/core/models.py:45
msgid "longitude"
msgstr "longitude"
#: trainvel/core/models.py:54
msgid "parent station"
msgstr "gare parente"
#: trainvel/core/models.py:63
msgid "country"
msgstr "pays"
#: trainvel/core/models.py:69
msgid "timezone"
msgstr "fuseau horaire"
#: trainvel/core/models.py:73
msgid "is city"
msgstr "est une ville"
#: trainvel/core/models.py:77
msgid "is main station"
msgstr "est une gare principale"
#: trainvel/core/models.py:81
msgid "is airport"
msgstr "est un aéroport"
#: trainvel/core/models.py:85
msgid "is suggestable"
msgstr "est suggérable"
#: trainvel/core/models.py:89
msgid "country hint"
msgstr "indice de pays"
#: trainvel/core/models.py:93
msgid "main station hint"
msgstr "indice de gare principale"
#: trainvel/core/models.py:98
msgid "SNCF ID"
msgstr "ID SNCF"
#: trainvel/core/models.py:106
msgid "SNCF TVS ID"
msgstr "ID SNCF TVS"
#: trainvel/core/models.py:113
msgid "SNCF is enabled"
msgstr "SNCF est activé"
#: trainvel/core/models.py:118
msgid "Entur ID"
msgstr "ID Entur"
#: trainvel/core/models.py:125
msgid "Entur is enabled"
msgstr "Entur est activé"
#: trainvel/core/models.py:129
msgid "DB ID"
msgstr "ID DB"
#: trainvel/core/models.py:136
msgid "DB is enabled"
msgstr "DB est activé"
#: trainvel/core/models.py:141
msgid "Busbud ID"
msgstr "ID Busbud"
#: trainvel/core/models.py:148
msgid "Busbud is enabled"
msgstr "Busbud est activé"
#: trainvel/core/models.py:153
msgid "distribusion ID"
msgstr "ID distribusion"
#: trainvel/core/models.py:160
msgid "distribusion is enabled"
msgstr "distribusion est activé"
#: trainvel/core/models.py:164
msgid "Flixbus ID"
msgstr "ID Flixbus"
#: trainvel/core/models.py:171
msgid "Flixbus is enabled"
msgstr "Flixbus est activé"
#: trainvel/core/models.py:175
msgid "CFF ID"
msgstr "ID CFF"
#: trainvel/core/models.py:182
msgid "CFF is enabled"
msgstr "CFF est activé"
#: trainvel/core/models.py:187
msgid "Leo Express ID"
msgstr "ID Leo Express"
#: trainvel/core/models.py:194
msgid "Leo Express is enabled"
msgstr "Leo Express est activé"
#: trainvel/core/models.py:198
msgid "ÖBB ID"
msgstr "ID ÖBB"
#: trainvel/core/models.py:205
msgid "ÖBB is enabled"
msgstr "ÖBB est activé"
#: trainvel/core/models.py:210
msgid "Ouigo ID"
msgstr "ID Ouigo"
#: trainvel/core/models.py:217
msgid "Ouigo is enabled"
msgstr "Ouigo est activé"
#: trainvel/core/models.py:221
msgid "Trenitalia ID"
msgstr "ID Trenitalia"
#: trainvel/core/models.py:228
msgid "Trenitalia is enabled"
msgstr "Trenitalia est activé"
#: trainvel/core/models.py:233
msgid "Trenitalia RTVT ID"
msgstr "ID Trenitalia RTVT"
#: trainvel/core/models.py:241
msgid "Trenord ID"
msgstr "ID Trenord"
#: trainvel/core/models.py:249
msgid "NTV RTIV ID"
msgstr "ID NTV RTIV"
#: trainvel/core/models.py:257
msgid "NTV ID"
msgstr "ID NTV"
#: trainvel/core/models.py:264
msgid "NTV is enabled"
msgstr "NTV est activé"
#: trainvel/core/models.py:269
msgid "HKX ID"
msgstr "ID HKX"
#: trainvel/core/models.py:276
msgid "HKX is enabled"
msgstr "HKX est activé"
#: trainvel/core/models.py:280
msgid "Renfe ID"
msgstr "ID Renfe"
#: trainvel/core/models.py:287
msgid "Renfe is enabled"
msgstr "Renfe est activé"
#: trainvel/core/models.py:292
msgid "ATOC ID"
msgstr "ID ATOC"
#: trainvel/core/models.py:299
msgid "ATOC is enabled"
msgstr "ATOC est activé"
#: trainvel/core/models.py:304
msgid "Benerail ID"
msgstr "ID Benerail"
#: trainvel/core/models.py:311
msgid "Benerail is enabled"
msgstr "Benerail est activé"
#: trainvel/core/models.py:316
msgid "Westbahn ID"
msgstr "ID Westbahn"
#: trainvel/core/models.py:323
msgid "Westbahn is enabled"
msgstr "Westbahn est activé"
#: trainvel/core/models.py:327
msgid "SNCF self-service machine"
msgstr "Automate self-service SNCF"
#: trainvel/core/models.py:333
msgid "same as"
msgstr "identique à"
#: trainvel/core/models.py:342
msgid "info (DE)"
msgstr "info (DE)"
#: trainvel/core/models.py:350
msgid "info (EN)"
msgstr "info (EN)"
#: trainvel/core/models.py:358
msgid "info (ES)"
msgstr "info (ES)"
#: trainvel/core/models.py:366
msgid "info (FR)"
msgstr "info (FR)"
#: trainvel/core/models.py:374
msgid "info (IT)"
msgstr "info (IT)"
#: trainvel/core/models.py:382
msgid "info (NB)"
msgstr "info (NB)"
#: trainvel/core/models.py:390
msgid "info (NL)"
msgstr "info (NL)"
#: trainvel/core/models.py:398
msgid "info (CS)"
msgstr "info (CS)"
#: trainvel/core/models.py:406
msgid "info (DA)"
msgstr "info (DA)"
#: trainvel/core/models.py:414
msgid "info (HU)"
msgstr "info (HU)"
#: trainvel/core/models.py:422
msgid "info (JA)"
msgstr "info (JA)"
#: trainvel/core/models.py:430
msgid "info (KO)"
msgstr "info (KO)"
#: trainvel/core/models.py:438
msgid "info (PL)"
msgstr "info (PL)"
#: trainvel/core/models.py:446
msgid "info (PT)"
msgstr "info (PT)"
#: trainvel/core/models.py:454
msgid "info (RU)"
msgstr "info (RU)"
#: trainvel/core/models.py:462
msgid "info (SV)"
msgstr "info (SV)"
#: trainvel/core/models.py:470
msgid "info (TR)"
msgstr "info (TR)"
#: trainvel/core/models.py:478
msgid "info (ZH)"
msgstr "info (ZH)"
#: trainvel/core/models.py:486
msgid "normalized code (Trainline)"
msgstr "code normalisé (Trainline)"
#: trainvel/core/models.py:491
msgid "IATA airport code"
msgstr "code aéroport IATA"
#: trainvel/core/models.py:501
msgid "station"
msgstr "gare"
#: trainvel/core/models.py:502
msgid "stations"
msgstr "gares"

View File

@ -1,37 +0,0 @@
import csv
from time import time
import requests
from django.core.management import BaseCommand
from django.db import connection, transaction
from tqdm import tqdm
from trainvel.core.models import Station
class Command(BaseCommand):
def handle(self, *args, **options):
def convert_value(value: str) -> str:
return True if value == 't' else False if value == 'f' else (value or None)
stations = []
STATIONS_URL = "https://raw.githubusercontent.com/trainline-eu/stations/master/stations.csv"
with requests.get(STATIONS_URL, stream=True) as resp:
for row in csv.DictReader(tqdm(resp.iter_lines(decode_unicode=True)), delimiter=';'):
row: dict
values = {k.replace(':', '_').replace('normalised_code', 'normalized_code_trainline')
.replace('same_as', 'same_as_id'): convert_value(v)
for k, v in row.items()}
stations.append(values)
Station.objects.all().delete()
print("Deleted all stations.")
with connection.cursor() as cursor:
cursor.execute("BEGIN;")
keys = ", ".join(stations[0].keys())
cursor.executemany(
f"INSERT INTO core_station ({keys}) VALUES ({', '.join(['%s'] * len(values))});",
[list(station.values()) for station in stations],
)
cursor.execute("COMMIT;")

View File

@ -1,603 +0,0 @@
# Generated by Django 5.0.1 on 2024-05-09 22:15
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Station",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255, verbose_name="name")),
("slug", models.SlugField(max_length=255, verbose_name="slug")),
(
"uic",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="UIC"
),
),
(
"uic8_sncf",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="UIC8 SNCF"
),
),
(
"latitude",
models.FloatField(
blank=True, default=None, null=True, verbose_name="latitude"
),
),
(
"longitude",
models.FloatField(
blank=True, default=None, null=True, verbose_name="longitude"
),
),
(
"country",
models.CharField(
choices=[
("AL", "Albania"),
("AD", "Andorra"),
("AM", "Armenia"),
("AT", "Austria"),
("AZ", "Azerbaijan"),
("BE", "Belgium"),
("BA", "Bosnia and Herzegovina"),
("BG", "Bulgaria"),
("HR", "Croatia"),
("CY", "Cyprus"),
("CZ", "Czech Republic"),
("DK", "Denmark"),
("EE", "Estonia"),
("FI", "Finland"),
("FR", "France"),
("GE", "Georgia"),
("DE", "Germany"),
("GR", "Greece"),
("HU", "Hungary"),
("IS", "Iceland"),
("IE", "Ireland"),
("IT", "Italy"),
("LV", "Latvia"),
("LI", "Liechtenstein"),
("LT", "Lithuania"),
("LU", "Luxembourg"),
("MT", "Malta"),
("MD", "Moldova"),
("MC", "Monaco"),
("ME", "Montenegro"),
("NL", "Netherlands"),
("MK", "North Macedonia"),
("NO", "Norway"),
("PL", "Poland"),
("PT", "Portugal"),
("RO", "Romania"),
("SM", "San Marino"),
("RS", "Serbia"),
("SK", "Slovakia"),
("SI", "Slovenia"),
("ES", "Spain"),
("SE", "Sweden"),
("CH", "Switzerland"),
("TR", "Turkey"),
("GB", "United Kingdom"),
("UA", "Ukraine"),
],
max_length=255,
verbose_name="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(
blank=True,
default=None,
max_length=5,
null=True,
verbose_name="SNCF ID",
),
),
(
"sncf_tvs_id",
models.CharField(
blank=True,
default=None,
max_length=16,
null=True,
verbose_name="SNCF TVS ID",
),
),
(
"sncf_is_enabled",
models.BooleanField(verbose_name="SNCF is enabled"),
),
(
"entur_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Entur ID",
),
),
(
"entur_is_enabled",
models.BooleanField(verbose_name="Entur is enabled"),
),
(
"db_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="DB ID"
),
),
("db_is_enabled", models.BooleanField(verbose_name="DB is enabled")),
(
"busbud_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Busbud ID",
),
),
(
"busbud_is_enabled",
models.BooleanField(verbose_name="Busbud is enabled"),
),
(
"distribusion_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="distribusion ID",
),
),
(
"distribusion_is_enabled",
models.BooleanField(verbose_name="distribusion is enabled"),
),
(
"flixbus_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="Flixbus ID"
),
),
(
"flixbus_is_enabled",
models.BooleanField(verbose_name="Flixbus is enabled"),
),
(
"cff_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="CFF ID"
),
),
("cff_is_enabled", models.BooleanField(verbose_name="CFF is enabled")),
(
"leoexpress_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Leo Express ID",
),
),
(
"leoexpress_is_enabled",
models.BooleanField(verbose_name="Leo Express is enabled"),
),
(
"obb_id",
models.IntegerField(
blank=True, default=None, null=True, verbose_name="ÖBB ID"
),
),
("obb_is_enabled", models.BooleanField(verbose_name="ÖBB is enabled")),
(
"ouigo_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Ouigo ID",
),
),
(
"ouigo_is_enabled",
models.BooleanField(verbose_name="Ouigo is enabled"),
),
(
"trenitalia_id",
models.IntegerField(
blank=True,
default=None,
null=True,
verbose_name="Trenitalia ID",
),
),
(
"trenitalia_is_enabled",
models.BooleanField(verbose_name="Trenitalia is enabled"),
),
(
"trenitalia_rtvt_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Trenitalia RTVT ID",
),
),
(
"trenord_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Trenord ID",
),
),
(
"ntv_rtiv_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="NTV RTIV ID",
),
),
(
"ntv_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="NTV ID",
),
),
("ntv_is_enabled", models.BooleanField(verbose_name="NTV is enabled")),
(
"hkx_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="HKX ID",
),
),
("hkx_is_enabled", models.BooleanField(verbose_name="HKX is enabled")),
(
"renfe_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Renfe ID",
),
),
(
"renfe_is_enabled",
models.BooleanField(verbose_name="Renfe is enabled"),
),
(
"atoc_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="ATOC ID",
),
),
(
"atoc_is_enabled",
models.BooleanField(verbose_name="ATOC is enabled"),
),
(
"benerail_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Benerail ID",
),
),
(
"benerail_is_enabled",
models.BooleanField(verbose_name="Benerail is enabled"),
),
(
"westbahn_id",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="Westbahn ID",
),
),
(
"westbahn_is_enabled",
models.BooleanField(verbose_name="Westbahn is enabled"),
),
(
"sncf_self_service_machine",
models.BooleanField(verbose_name="SNCF self-service machine"),
),
(
"info_de",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (DE)",
),
),
(
"info_en",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (EN)",
),
),
(
"info_es",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (ES)",
),
),
(
"info_fr",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (FR)",
),
),
(
"info_it",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (IT)",
),
),
(
"info_nb",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (NB)",
),
),
(
"info_nl",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (NL)",
),
),
(
"info_cs",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (CS)",
),
),
(
"info_da",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (DA)",
),
),
(
"info_hu",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (HU)",
),
),
(
"info_ja",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (JA)",
),
),
(
"info_ko",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (KO)",
),
),
(
"info_pl",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (PL)",
),
),
(
"info_pt",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (PT)",
),
),
(
"info_ru",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (RU)",
),
),
(
"info_sv",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (SV)",
),
),
(
"info_tr",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (TR)",
),
),
(
"info_zh",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="info (ZH)",
),
),
(
"normalized_code_trainline",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="normalized code (Trainline)",
),
),
(
"iata_airport_code",
models.CharField(
blank=True,
default=None,
max_length=255,
null=True,
verbose_name="IATA airport code",
),
),
(
"parent_station",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="core.station",
verbose_name="parent station",
),
),
(
"same_as",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="same_as_other",
to="core.station",
verbose_name="same as",
),
),
],
options={
"verbose_name": "station",
"verbose_name_plural": "stations",
},
),
]

View File

@ -1,40 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-12 11:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="station",
name="parent_station",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="children",
to="core.station",
verbose_name="parent station",
),
),
migrations.AlterField(
model_name="station",
name="same_as",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="same_as_other",
to="core.station",
verbose_name="same as",
),
),
]

View File

@ -1,519 +0,0 @@
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.SET_NULL,
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=16,
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.SET_NULL,
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")

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@ -1,166 +0,0 @@
from django.contrib import admin
from django.forms import BaseInlineFormSet
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, \
Route, Stop, StopTime, StopTimeUpdate, Transfer, Trip, TripUpdate
class LimitModelFormset(BaseInlineFormSet):
""" Base Inline formset to limit inline Model query results. """
def get_queryset(self):
return super(LimitModelFormset, self).get_queryset()[:50]
class CalendarDateInline(admin.TabularInline):
model = CalendarDate
extra = 0
formset = LimitModelFormset
class TripInline(admin.TabularInline):
model = Trip
extra = 0
formset = LimitModelFormset
autocomplete_fields = ('route', 'service',)
show_change_link = True
ordering = ('service',)
readonly_fields = ('gtfs_feed',)
class StopTimeInline(admin.TabularInline):
model = StopTime
extra = 0
formset = LimitModelFormset
autocomplete_fields = ('stop',)
readonly_fields = ('id',)
show_change_link = True
ordering = ('stop_sequence',)
class TripUpdateInline(admin.StackedInline):
model = TripUpdate
extra = 0
formset = LimitModelFormset
autocomplete_fields = ('trip',)
class StopTimeUpdateInline(admin.StackedInline):
model = StopTimeUpdate
extra = 0
formset = LimitModelFormset
autocomplete_fields = ('trip_update', 'stop_time',)
@admin.register(GTFSFeed)
class GTFSFeedAdmin(admin.ModelAdmin):
list_display = ('name', 'code', 'country', 'last_modified',)
list_filter = ('country', 'last_modified',)
search_fields = ('name', 'code',)
readonly_fields = ('code',)
autocomplete_fields = ('excluded_agencies',)
@admin.register(Agency)
class AgencyAdmin(admin.ModelAdmin):
list_display = ('name', 'id', 'url', 'timezone', 'gtfs_feed',)
list_filter = ('gtfs_feed', 'timezone',)
search_fields = ('name',)
autocomplete_fields = ('gtfs_feed',)
@admin.register(Stop)
class StopAdmin(admin.ModelAdmin):
list_display = ('name', 'id', 'lat', 'lon', 'location_type',)
list_filter = ('location_type', 'gtfs_feed',)
search_fields = ('name', 'id',)
ordering = ('name',)
autocomplete_fields = ('parent_station', 'gtfs_feed',)
@admin.register(Route)
class RouteAdmin(admin.ModelAdmin):
list_display = ('__str__', 'id', 'type', 'gtfs_feed',)
list_filter = ('gtfs_feed', 'type', 'agency',)
search_fields = ('long_name', 'short_name', 'id',)
ordering = ('long_name',)
autocomplete_fields = ('agency', 'gtfs_feed',)
inlines = (TripInline,)
@admin.register(Trip)
class TripAdmin(admin.ModelAdmin):
list_display = ('id', 'origin_destination', 'route', 'service', 'headsign', 'direction_id',)
list_filter = ('direction_id', 'route__gtfs_feed',)
search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',)
ordering = ('route', 'service',)
autocomplete_fields = ('route', 'service', 'gtfs_feed',)
inlines = (StopTimeInline, TripUpdateInline,)
@admin.register(StopTime)
class StopTimeAdmin(admin.ModelAdmin):
list_display = ('trip', 'stop', 'arrival_time', 'departure_time',
'stop_sequence', 'pickup_type', 'drop_off_type',)
list_filter = ('pickup_type', 'drop_off_type', 'trip__route__gtfs_feed',)
search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',)
ordering = ('trip', 'stop_sequence',)
autocomplete_fields = ('trip', 'stop',)
readonly_fields = ('id',)
inlines = (StopTimeUpdateInline,)
@admin.register(Calendar)
class CalendarAdmin(admin.ModelAdmin):
list_display = ('id', 'gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
'saturday', 'sunday', 'start_date', 'end_date',)
list_filter = ('gtfs_feed', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
'start_date', 'end_date',)
search_fields = ('id', 'start_date', 'end_date',)
autocomplete_fields = ('gtfs_feed',)
ordering = ('gtfs_feed', 'id',)
inlines = (CalendarDateInline, TripInline,)
@admin.register(CalendarDate)
class CalendarDateAdmin(admin.ModelAdmin):
list_display = ('id', 'service_id', 'date', 'exception_type',)
list_filter = ('exception_type', 'date', 'service__gtfs_feed',)
search_fields = ('id', 'date',)
ordering = ('date', 'service_id',)
@admin.register(Transfer)
class TransferAdmin(admin.ModelAdmin):
list_display = ('from_stop', 'to_stop', 'transfer_type', 'min_transfer_time',)
list_filter = ('transfer_type',)
search_fields = ('from_stop__name', 'to_stop__name',)
autocomplete_fields = ('from_stop', 'to_stop',)
@admin.register(FeedInfo)
class FeedInfoAdmin(admin.ModelAdmin):
list_display = ('publisher_name', 'publisher_url', 'lang', 'start_date',
'end_date', 'version',)
search_fields = ('publisher_name', 'publisher_url', 'lang', 'start_date',
'end_date', 'version',)
autocomplete_fields = ('gtfs_feed',)
ordering = ('publisher_name',)
@admin.register(StopTimeUpdate)
class StopTimeUpdateAdmin(admin.ModelAdmin):
list_display = ('trip_update', 'stop_time', 'arrival_delay', 'arrival_time',
'departure_delay', 'departure_time', 'schedule_relationship',)
list_filter = ('schedule_relationship', 'trip_update__trip__gtfs_feed',)
search_fields = ('trip_update__trip__id', 'stop_time__stop__name', 'arrival_time', 'departure_time',)
ordering = ('trip_update', 'stop_time',)
autocomplete_fields = ('trip_update', 'stop_time',)
@admin.register(TripUpdate)
class TripUpdateAdmin(admin.ModelAdmin):
list_display = ('trip_id', 'start_date', 'start_time',)
list_filter = ('start_date', 'schedule_relationship',)
search_fields = ('trip__id', 'start_date', 'start_time',)
ordering = ('trip_id', 'start_date', 'start_time',)
autocomplete_fields = ('trip',)

View File

@ -1,14 +0,0 @@
from django.apps import AppConfig
from django.db.models.signals import pre_save
from django.utils.translation import gettext_lazy as _
class TrainvelGTFSConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "trainvel.gtfs"
verbose_name = _("Trainvel - GTFS")
def ready(self):
from trainvel.gtfs import signals
pre_save.connect(signals.keep_gtfs_feed_modification_date, sender="gtfs.GTFSFeed")

View File

@ -1,180 +0,0 @@
[
{
"model": "gtfs.gtfsfeed",
"pk": "FR-SNCF-TGV",
"fields": {
"name": "SNCF - TGV",
"country": "FR",
"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",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "FR-SNCF-IC",
"fields": {
"name": "SNCF - Intercités",
"country": "FR",
"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",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "FR-SNCF-TER",
"fields": {
"name": "SNCF - TER",
"country": "FR",
"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",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "FR-IDF-IDFM",
"fields": {
"name": "Île-de-France Mobilités",
"country": "FR",
"feed_url": "https://eu.ftp.opendatasoft.com/stif/GTFS/IDFM-gtfs.zip",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "FR-GES-CTS",
"fields": {
"name": "Compagnie des Transports Strasbourgeois (CTS)",
"country": "FR",
"feed_url": "https://opendata.cts-strasbourg.eu/google_transit.zip",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "FR-EUROSTAR",
"fields": {
"name": "Eurostar",
"country": "FR",
"feed_url": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "IT-FRA-TI",
"fields": {
"name": "Trenitalia France",
"country": "FR",
"feed_url": "https://thello.axelor.com/public/gtfs/gtfs.zip",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "ES-RENFE",
"fields": {
"name": "Renfe",
"country": "ES",
"feed_url": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "AT-ÖBB",
"fields": {
"name": "ÖBB",
"country": "AT",
"feed_url": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
"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": []
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "CH-ALL",
"fields": {
"name": "Transports suisses",
"country": "CH",
"feed_url": "https://opentransportdata.swiss/fr/dataset/timetable-2024-gtfs2020/permalink",
"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"
]
}
},
{
"model": "gtfs.gtfsfeed",
"pk": "LU-ALL",
"fields": {
"name": "CFL",
"country": "LU",
"feed_url": "https://data.public.lu/fr/datasets/r/aab2922d-27ff-4e53-a789-d990cf1ceb1e",
"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": []
}
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,436 +0,0 @@
from google.protobuf.internal import containers as _containers
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf.internal import python_message as _python_message
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
DESCRIPTOR: _descriptor.FileDescriptor
class FeedMessage(_message.Message):
__slots__ = ("header", "entity")
Extensions: _python_message._ExtensionDict
HEADER_FIELD_NUMBER: _ClassVar[int]
ENTITY_FIELD_NUMBER: _ClassVar[int]
header: FeedHeader
entity: _containers.RepeatedCompositeFieldContainer[FeedEntity]
def __init__(self, header: _Optional[_Union[FeedHeader, _Mapping]] = ..., entity: _Optional[_Iterable[_Union[FeedEntity, _Mapping]]] = ...) -> None: ...
class FeedHeader(_message.Message):
__slots__ = ("gtfs_realtime_version", "incrementality", "timestamp")
Extensions: _python_message._ExtensionDict
class Incrementality(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
FULL_DATASET: _ClassVar[FeedHeader.Incrementality]
DIFFERENTIAL: _ClassVar[FeedHeader.Incrementality]
FULL_DATASET: FeedHeader.Incrementality
DIFFERENTIAL: FeedHeader.Incrementality
GTFS_REALTIME_VERSION_FIELD_NUMBER: _ClassVar[int]
INCREMENTALITY_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
gtfs_realtime_version: str
incrementality: FeedHeader.Incrementality
timestamp: int
def __init__(self, gtfs_realtime_version: _Optional[str] = ..., incrementality: _Optional[_Union[FeedHeader.Incrementality, str]] = ..., timestamp: _Optional[int] = ...) -> None: ...
class FeedEntity(_message.Message):
__slots__ = ("id", "is_deleted", "trip_update", "vehicle", "alert", "shape")
Extensions: _python_message._ExtensionDict
ID_FIELD_NUMBER: _ClassVar[int]
IS_DELETED_FIELD_NUMBER: _ClassVar[int]
TRIP_UPDATE_FIELD_NUMBER: _ClassVar[int]
VEHICLE_FIELD_NUMBER: _ClassVar[int]
ALERT_FIELD_NUMBER: _ClassVar[int]
SHAPE_FIELD_NUMBER: _ClassVar[int]
id: str
is_deleted: bool
trip_update: TripUpdate
vehicle: VehiclePosition
alert: Alert
shape: Shape
def __init__(self, id: _Optional[str] = ..., is_deleted: bool = ..., trip_update: _Optional[_Union[TripUpdate, _Mapping]] = ..., vehicle: _Optional[_Union[VehiclePosition, _Mapping]] = ..., alert: _Optional[_Union[Alert, _Mapping]] = ..., shape: _Optional[_Union[Shape, _Mapping]] = ...) -> None: ...
class TripUpdate(_message.Message):
__slots__ = ("trip", "vehicle", "stop_time_update", "timestamp", "delay", "trip_properties")
Extensions: _python_message._ExtensionDict
class StopTimeEvent(_message.Message):
__slots__ = ("delay", "time", "uncertainty")
Extensions: _python_message._ExtensionDict
DELAY_FIELD_NUMBER: _ClassVar[int]
TIME_FIELD_NUMBER: _ClassVar[int]
UNCERTAINTY_FIELD_NUMBER: _ClassVar[int]
delay: int
time: int
uncertainty: int
def __init__(self, delay: _Optional[int] = ..., time: _Optional[int] = ..., uncertainty: _Optional[int] = ...) -> None: ...
class StopTimeUpdate(_message.Message):
__slots__ = ("stop_sequence", "stop_id", "arrival", "departure", "departure_occupancy_status", "schedule_relationship", "stop_time_properties")
Extensions: _python_message._ExtensionDict
class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
SCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
SKIPPED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
NO_DATA: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
UNSCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
SCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship
SKIPPED: TripUpdate.StopTimeUpdate.ScheduleRelationship
NO_DATA: TripUpdate.StopTimeUpdate.ScheduleRelationship
UNSCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship
class StopTimeProperties(_message.Message):
__slots__ = ("assigned_stop_id",)
Extensions: _python_message._ExtensionDict
ASSIGNED_STOP_ID_FIELD_NUMBER: _ClassVar[int]
assigned_stop_id: str
def __init__(self, assigned_stop_id: _Optional[str] = ...) -> None: ...
STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
STOP_ID_FIELD_NUMBER: _ClassVar[int]
ARRIVAL_FIELD_NUMBER: _ClassVar[int]
DEPARTURE_FIELD_NUMBER: _ClassVar[int]
DEPARTURE_OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int]
STOP_TIME_PROPERTIES_FIELD_NUMBER: _ClassVar[int]
stop_sequence: int
stop_id: str
arrival: TripUpdate.StopTimeEvent
departure: TripUpdate.StopTimeEvent
departure_occupancy_status: VehiclePosition.OccupancyStatus
schedule_relationship: TripUpdate.StopTimeUpdate.ScheduleRelationship
stop_time_properties: TripUpdate.StopTimeUpdate.StopTimeProperties
def __init__(self, stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., arrival: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure_occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., schedule_relationship: _Optional[_Union[TripUpdate.StopTimeUpdate.ScheduleRelationship, str]] = ..., stop_time_properties: _Optional[_Union[TripUpdate.StopTimeUpdate.StopTimeProperties, _Mapping]] = ...) -> None: ...
class TripProperties(_message.Message):
__slots__ = ("trip_id", "start_date", "start_time", "shape_id")
Extensions: _python_message._ExtensionDict
TRIP_ID_FIELD_NUMBER: _ClassVar[int]
START_DATE_FIELD_NUMBER: _ClassVar[int]
START_TIME_FIELD_NUMBER: _ClassVar[int]
SHAPE_ID_FIELD_NUMBER: _ClassVar[int]
trip_id: str
start_date: str
start_time: str
shape_id: str
def __init__(self, trip_id: _Optional[str] = ..., start_date: _Optional[str] = ..., start_time: _Optional[str] = ..., shape_id: _Optional[str] = ...) -> None: ...
TRIP_FIELD_NUMBER: _ClassVar[int]
VEHICLE_FIELD_NUMBER: _ClassVar[int]
STOP_TIME_UPDATE_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
DELAY_FIELD_NUMBER: _ClassVar[int]
TRIP_PROPERTIES_FIELD_NUMBER: _ClassVar[int]
trip: TripDescriptor
vehicle: VehicleDescriptor
stop_time_update: _containers.RepeatedCompositeFieldContainer[TripUpdate.StopTimeUpdate]
timestamp: int
delay: int
trip_properties: TripUpdate.TripProperties
def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., stop_time_update: _Optional[_Iterable[_Union[TripUpdate.StopTimeUpdate, _Mapping]]] = ..., timestamp: _Optional[int] = ..., delay: _Optional[int] = ..., trip_properties: _Optional[_Union[TripUpdate.TripProperties, _Mapping]] = ...) -> None: ...
class VehiclePosition(_message.Message):
__slots__ = ("trip", "vehicle", "position", "current_stop_sequence", "stop_id", "current_status", "timestamp", "congestion_level", "occupancy_status", "occupancy_percentage", "multi_carriage_details")
Extensions: _python_message._ExtensionDict
class VehicleStopStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
INCOMING_AT: _ClassVar[VehiclePosition.VehicleStopStatus]
STOPPED_AT: _ClassVar[VehiclePosition.VehicleStopStatus]
IN_TRANSIT_TO: _ClassVar[VehiclePosition.VehicleStopStatus]
INCOMING_AT: VehiclePosition.VehicleStopStatus
STOPPED_AT: VehiclePosition.VehicleStopStatus
IN_TRANSIT_TO: VehiclePosition.VehicleStopStatus
class CongestionLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
UNKNOWN_CONGESTION_LEVEL: _ClassVar[VehiclePosition.CongestionLevel]
RUNNING_SMOOTHLY: _ClassVar[VehiclePosition.CongestionLevel]
STOP_AND_GO: _ClassVar[VehiclePosition.CongestionLevel]
CONGESTION: _ClassVar[VehiclePosition.CongestionLevel]
SEVERE_CONGESTION: _ClassVar[VehiclePosition.CongestionLevel]
UNKNOWN_CONGESTION_LEVEL: VehiclePosition.CongestionLevel
RUNNING_SMOOTHLY: VehiclePosition.CongestionLevel
STOP_AND_GO: VehiclePosition.CongestionLevel
CONGESTION: VehiclePosition.CongestionLevel
SEVERE_CONGESTION: VehiclePosition.CongestionLevel
class OccupancyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
EMPTY: _ClassVar[VehiclePosition.OccupancyStatus]
MANY_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
FEW_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus]
CRUSHED_STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus]
FULL: _ClassVar[VehiclePosition.OccupancyStatus]
NOT_ACCEPTING_PASSENGERS: _ClassVar[VehiclePosition.OccupancyStatus]
NO_DATA_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
NOT_BOARDABLE: _ClassVar[VehiclePosition.OccupancyStatus]
EMPTY: VehiclePosition.OccupancyStatus
MANY_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus
FEW_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus
STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus
CRUSHED_STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus
FULL: VehiclePosition.OccupancyStatus
NOT_ACCEPTING_PASSENGERS: VehiclePosition.OccupancyStatus
NO_DATA_AVAILABLE: VehiclePosition.OccupancyStatus
NOT_BOARDABLE: VehiclePosition.OccupancyStatus
class CarriageDetails(_message.Message):
__slots__ = ("id", "label", "occupancy_status", "occupancy_percentage", "carriage_sequence")
Extensions: _python_message._ExtensionDict
ID_FIELD_NUMBER: _ClassVar[int]
LABEL_FIELD_NUMBER: _ClassVar[int]
OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int]
CARRIAGE_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
id: str
label: str
occupancy_status: VehiclePosition.OccupancyStatus
occupancy_percentage: int
carriage_sequence: int
def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., carriage_sequence: _Optional[int] = ...) -> None: ...
TRIP_FIELD_NUMBER: _ClassVar[int]
VEHICLE_FIELD_NUMBER: _ClassVar[int]
POSITION_FIELD_NUMBER: _ClassVar[int]
CURRENT_STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
STOP_ID_FIELD_NUMBER: _ClassVar[int]
CURRENT_STATUS_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
CONGESTION_LEVEL_FIELD_NUMBER: _ClassVar[int]
OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int]
MULTI_CARRIAGE_DETAILS_FIELD_NUMBER: _ClassVar[int]
trip: TripDescriptor
vehicle: VehicleDescriptor
position: Position
current_stop_sequence: int
stop_id: str
current_status: VehiclePosition.VehicleStopStatus
timestamp: int
congestion_level: VehiclePosition.CongestionLevel
occupancy_status: VehiclePosition.OccupancyStatus
occupancy_percentage: int
multi_carriage_details: _containers.RepeatedCompositeFieldContainer[VehiclePosition.CarriageDetails]
def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., position: _Optional[_Union[Position, _Mapping]] = ..., current_stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., current_status: _Optional[_Union[VehiclePosition.VehicleStopStatus, str]] = ..., timestamp: _Optional[int] = ..., congestion_level: _Optional[_Union[VehiclePosition.CongestionLevel, str]] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., multi_carriage_details: _Optional[_Iterable[_Union[VehiclePosition.CarriageDetails, _Mapping]]] = ...) -> None: ...
class Alert(_message.Message):
__slots__ = ("active_period", "informed_entity", "cause", "effect", "url", "header_text", "description_text", "tts_header_text", "tts_description_text", "severity_level", "image", "image_alternative_text", "cause_detail", "effect_detail")
Extensions: _python_message._ExtensionDict
class Cause(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
UNKNOWN_CAUSE: _ClassVar[Alert.Cause]
OTHER_CAUSE: _ClassVar[Alert.Cause]
TECHNICAL_PROBLEM: _ClassVar[Alert.Cause]
STRIKE: _ClassVar[Alert.Cause]
DEMONSTRATION: _ClassVar[Alert.Cause]
ACCIDENT: _ClassVar[Alert.Cause]
HOLIDAY: _ClassVar[Alert.Cause]
WEATHER: _ClassVar[Alert.Cause]
MAINTENANCE: _ClassVar[Alert.Cause]
CONSTRUCTION: _ClassVar[Alert.Cause]
POLICE_ACTIVITY: _ClassVar[Alert.Cause]
MEDICAL_EMERGENCY: _ClassVar[Alert.Cause]
UNKNOWN_CAUSE: Alert.Cause
OTHER_CAUSE: Alert.Cause
TECHNICAL_PROBLEM: Alert.Cause
STRIKE: Alert.Cause
DEMONSTRATION: Alert.Cause
ACCIDENT: Alert.Cause
HOLIDAY: Alert.Cause
WEATHER: Alert.Cause
MAINTENANCE: Alert.Cause
CONSTRUCTION: Alert.Cause
POLICE_ACTIVITY: Alert.Cause
MEDICAL_EMERGENCY: Alert.Cause
class Effect(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
NO_SERVICE: _ClassVar[Alert.Effect]
REDUCED_SERVICE: _ClassVar[Alert.Effect]
SIGNIFICANT_DELAYS: _ClassVar[Alert.Effect]
DETOUR: _ClassVar[Alert.Effect]
ADDITIONAL_SERVICE: _ClassVar[Alert.Effect]
MODIFIED_SERVICE: _ClassVar[Alert.Effect]
OTHER_EFFECT: _ClassVar[Alert.Effect]
UNKNOWN_EFFECT: _ClassVar[Alert.Effect]
STOP_MOVED: _ClassVar[Alert.Effect]
NO_EFFECT: _ClassVar[Alert.Effect]
ACCESSIBILITY_ISSUE: _ClassVar[Alert.Effect]
NO_SERVICE: Alert.Effect
REDUCED_SERVICE: Alert.Effect
SIGNIFICANT_DELAYS: Alert.Effect
DETOUR: Alert.Effect
ADDITIONAL_SERVICE: Alert.Effect
MODIFIED_SERVICE: Alert.Effect
OTHER_EFFECT: Alert.Effect
UNKNOWN_EFFECT: Alert.Effect
STOP_MOVED: Alert.Effect
NO_EFFECT: Alert.Effect
ACCESSIBILITY_ISSUE: Alert.Effect
class SeverityLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
UNKNOWN_SEVERITY: _ClassVar[Alert.SeverityLevel]
INFO: _ClassVar[Alert.SeverityLevel]
WARNING: _ClassVar[Alert.SeverityLevel]
SEVERE: _ClassVar[Alert.SeverityLevel]
UNKNOWN_SEVERITY: Alert.SeverityLevel
INFO: Alert.SeverityLevel
WARNING: Alert.SeverityLevel
SEVERE: Alert.SeverityLevel
ACTIVE_PERIOD_FIELD_NUMBER: _ClassVar[int]
INFORMED_ENTITY_FIELD_NUMBER: _ClassVar[int]
CAUSE_FIELD_NUMBER: _ClassVar[int]
EFFECT_FIELD_NUMBER: _ClassVar[int]
URL_FIELD_NUMBER: _ClassVar[int]
HEADER_TEXT_FIELD_NUMBER: _ClassVar[int]
DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int]
TTS_HEADER_TEXT_FIELD_NUMBER: _ClassVar[int]
TTS_DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int]
SEVERITY_LEVEL_FIELD_NUMBER: _ClassVar[int]
IMAGE_FIELD_NUMBER: _ClassVar[int]
IMAGE_ALTERNATIVE_TEXT_FIELD_NUMBER: _ClassVar[int]
CAUSE_DETAIL_FIELD_NUMBER: _ClassVar[int]
EFFECT_DETAIL_FIELD_NUMBER: _ClassVar[int]
active_period: _containers.RepeatedCompositeFieldContainer[TimeRange]
informed_entity: _containers.RepeatedCompositeFieldContainer[EntitySelector]
cause: Alert.Cause
effect: Alert.Effect
url: TranslatedString
header_text: TranslatedString
description_text: TranslatedString
tts_header_text: TranslatedString
tts_description_text: TranslatedString
severity_level: Alert.SeverityLevel
image: TranslatedImage
image_alternative_text: TranslatedString
cause_detail: TranslatedString
effect_detail: TranslatedString
def __init__(self, active_period: _Optional[_Iterable[_Union[TimeRange, _Mapping]]] = ..., informed_entity: _Optional[_Iterable[_Union[EntitySelector, _Mapping]]] = ..., cause: _Optional[_Union[Alert.Cause, str]] = ..., effect: _Optional[_Union[Alert.Effect, str]] = ..., url: _Optional[_Union[TranslatedString, _Mapping]] = ..., header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., severity_level: _Optional[_Union[Alert.SeverityLevel, str]] = ..., image: _Optional[_Union[TranslatedImage, _Mapping]] = ..., image_alternative_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., cause_detail: _Optional[_Union[TranslatedString, _Mapping]] = ..., effect_detail: _Optional[_Union[TranslatedString, _Mapping]] = ...) -> None: ...
class TimeRange(_message.Message):
__slots__ = ("start", "end")
Extensions: _python_message._ExtensionDict
START_FIELD_NUMBER: _ClassVar[int]
END_FIELD_NUMBER: _ClassVar[int]
start: int
end: int
def __init__(self, start: _Optional[int] = ..., end: _Optional[int] = ...) -> None: ...
class Position(_message.Message):
__slots__ = ("latitude", "longitude", "bearing", "odometer", "speed")
Extensions: _python_message._ExtensionDict
LATITUDE_FIELD_NUMBER: _ClassVar[int]
LONGITUDE_FIELD_NUMBER: _ClassVar[int]
BEARING_FIELD_NUMBER: _ClassVar[int]
ODOMETER_FIELD_NUMBER: _ClassVar[int]
SPEED_FIELD_NUMBER: _ClassVar[int]
latitude: float
longitude: float
bearing: float
odometer: float
speed: float
def __init__(self, latitude: _Optional[float] = ..., longitude: _Optional[float] = ..., bearing: _Optional[float] = ..., odometer: _Optional[float] = ..., speed: _Optional[float] = ...) -> None: ...
class TripDescriptor(_message.Message):
__slots__ = ("trip_id", "route_id", "direction_id", "start_time", "start_date", "schedule_relationship")
Extensions: _python_message._ExtensionDict
class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
SCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship]
ADDED: _ClassVar[TripDescriptor.ScheduleRelationship]
UNSCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship]
CANCELED: _ClassVar[TripDescriptor.ScheduleRelationship]
REPLACEMENT: _ClassVar[TripDescriptor.ScheduleRelationship]
DUPLICATED: _ClassVar[TripDescriptor.ScheduleRelationship]
DELETED: _ClassVar[TripDescriptor.ScheduleRelationship]
SCHEDULED: TripDescriptor.ScheduleRelationship
ADDED: TripDescriptor.ScheduleRelationship
UNSCHEDULED: TripDescriptor.ScheduleRelationship
CANCELED: TripDescriptor.ScheduleRelationship
REPLACEMENT: TripDescriptor.ScheduleRelationship
DUPLICATED: TripDescriptor.ScheduleRelationship
DELETED: TripDescriptor.ScheduleRelationship
TRIP_ID_FIELD_NUMBER: _ClassVar[int]
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
DIRECTION_ID_FIELD_NUMBER: _ClassVar[int]
START_TIME_FIELD_NUMBER: _ClassVar[int]
START_DATE_FIELD_NUMBER: _ClassVar[int]
SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int]
trip_id: str
route_id: str
direction_id: int
start_time: str
start_date: str
schedule_relationship: TripDescriptor.ScheduleRelationship
def __init__(self, trip_id: _Optional[str] = ..., route_id: _Optional[str] = ..., direction_id: _Optional[int] = ..., start_time: _Optional[str] = ..., start_date: _Optional[str] = ..., schedule_relationship: _Optional[_Union[TripDescriptor.ScheduleRelationship, str]] = ...) -> None: ...
class VehicleDescriptor(_message.Message):
__slots__ = ("id", "label", "license_plate", "wheelchair_accessible")
Extensions: _python_message._ExtensionDict
class WheelchairAccessible(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = ()
NO_VALUE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
UNKNOWN: _ClassVar[VehicleDescriptor.WheelchairAccessible]
WHEELCHAIR_ACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
WHEELCHAIR_INACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
NO_VALUE: VehicleDescriptor.WheelchairAccessible
UNKNOWN: VehicleDescriptor.WheelchairAccessible
WHEELCHAIR_ACCESSIBLE: VehicleDescriptor.WheelchairAccessible
WHEELCHAIR_INACCESSIBLE: VehicleDescriptor.WheelchairAccessible
ID_FIELD_NUMBER: _ClassVar[int]
LABEL_FIELD_NUMBER: _ClassVar[int]
LICENSE_PLATE_FIELD_NUMBER: _ClassVar[int]
WHEELCHAIR_ACCESSIBLE_FIELD_NUMBER: _ClassVar[int]
id: str
label: str
license_plate: str
wheelchair_accessible: VehicleDescriptor.WheelchairAccessible
def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., license_plate: _Optional[str] = ..., wheelchair_accessible: _Optional[_Union[VehicleDescriptor.WheelchairAccessible, str]] = ...) -> None: ...
class EntitySelector(_message.Message):
__slots__ = ("agency_id", "route_id", "route_type", "trip", "stop_id", "direction_id")
Extensions: _python_message._ExtensionDict
AGENCY_ID_FIELD_NUMBER: _ClassVar[int]
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
ROUTE_TYPE_FIELD_NUMBER: _ClassVar[int]
TRIP_FIELD_NUMBER: _ClassVar[int]
STOP_ID_FIELD_NUMBER: _ClassVar[int]
DIRECTION_ID_FIELD_NUMBER: _ClassVar[int]
agency_id: str
route_id: str
route_type: int
trip: TripDescriptor
stop_id: str
direction_id: int
def __init__(self, agency_id: _Optional[str] = ..., route_id: _Optional[str] = ..., route_type: _Optional[int] = ..., trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., stop_id: _Optional[str] = ..., direction_id: _Optional[int] = ...) -> None: ...
class TranslatedString(_message.Message):
__slots__ = ("translation",)
Extensions: _python_message._ExtensionDict
class Translation(_message.Message):
__slots__ = ("text", "language")
Extensions: _python_message._ExtensionDict
TEXT_FIELD_NUMBER: _ClassVar[int]
LANGUAGE_FIELD_NUMBER: _ClassVar[int]
text: str
language: str
def __init__(self, text: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ...
TRANSLATION_FIELD_NUMBER: _ClassVar[int]
translation: _containers.RepeatedCompositeFieldContainer[TranslatedString.Translation]
def __init__(self, translation: _Optional[_Iterable[_Union[TranslatedString.Translation, _Mapping]]] = ...) -> None: ...
class TranslatedImage(_message.Message):
__slots__ = ("localized_image",)
Extensions: _python_message._ExtensionDict
class LocalizedImage(_message.Message):
__slots__ = ("url", "media_type", "language")
Extensions: _python_message._ExtensionDict
URL_FIELD_NUMBER: _ClassVar[int]
MEDIA_TYPE_FIELD_NUMBER: _ClassVar[int]
LANGUAGE_FIELD_NUMBER: _ClassVar[int]
url: str
media_type: str
language: str
def __init__(self, url: _Optional[str] = ..., media_type: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ...
LOCALIZED_IMAGE_FIELD_NUMBER: _ClassVar[int]
localized_image: _containers.RepeatedCompositeFieldContainer[TranslatedImage.LocalizedImage]
def __init__(self, localized_image: _Optional[_Iterable[_Union[TranslatedImage.LocalizedImage, _Mapping]]] = ...) -> None: ...
class Shape(_message.Message):
__slots__ = ("shape_id", "encoded_polyline")
Extensions: _python_message._ExtensionDict
SHAPE_ID_FIELD_NUMBER: _ClassVar[int]
ENCODED_POLYLINE_FIELD_NUMBER: _ClassVar[int]
shape_id: str
encoded_polyline: str
def __init__(self, shape_id: _Optional[str] = ..., encoded_polyline: _Optional[str] = ...) -> None: ...

View File

@ -1,825 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-05-09 22:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Emmy D'Anello <ynerant@emy.lu>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: trainvel/gtfs/apps.py:8
msgid "Trainvel - GTFS"
msgstr "Trainvel - GTFS"
#: trainvel/gtfs/models.py:11
msgid "Albania"
msgstr "Albanie"
#: trainvel/gtfs/models.py:12
msgid "Andorra"
msgstr "Andorre"
#: trainvel/gtfs/models.py:13
msgid "Armenia"
msgstr "Arménie"
#: trainvel/gtfs/models.py:14
msgid "Austria"
msgstr "Autriche"
#: trainvel/gtfs/models.py:15
msgid "Azerbaijan"
msgstr "Azerbaijan"
#: trainvel/gtfs/models.py:16
msgid "Belgium"
msgstr "Belgique"
#: trainvel/gtfs/models.py:17
msgid "Bosnia and Herzegovina"
msgstr " Bosnie-Herzégovine"
#: trainvel/gtfs/models.py:18
msgid "Bulgaria"
msgstr "Bulgarie"
#: trainvel/gtfs/models.py:19
msgid "Croatia"
msgstr "Croatie"
#: trainvel/gtfs/models.py:20
msgid "Cyprus"
msgstr "Chypre"
#: trainvel/gtfs/models.py:21
msgid "Czech Republic"
msgstr "République Tchèque"
#: trainvel/gtfs/models.py:22
msgid "Denmark"
msgstr "Danemark"
#: trainvel/gtfs/models.py:23
msgid "Estonia"
msgstr "Estonie"
#: trainvel/gtfs/models.py:24
msgid "Finland"
msgstr "Finlande"
#: trainvel/gtfs/models.py:25
msgid "France"
msgstr "France"
#: trainvel/gtfs/models.py:26
msgid "Georgia"
msgstr "Géorgie"
#: trainvel/gtfs/models.py:27
msgid "Germany"
msgstr "Allemagne"
#: trainvel/gtfs/models.py:28
msgid "Greece"
msgstr "Grèce"
#: trainvel/gtfs/models.py:29
msgid "Hungary"
msgstr "Hongrie"
#: trainvel/gtfs/models.py:30
msgid "Iceland"
msgstr "Islande"
#: trainvel/gtfs/models.py:31
msgid "Ireland"
msgstr "Irlande"
#: trainvel/gtfs/models.py:32
msgid "Italy"
msgstr "Italie"
#: trainvel/gtfs/models.py:33
msgid "Latvia"
msgstr "Lettonie"
#: trainvel/gtfs/models.py:34
msgid "Liechtenstein"
msgstr "Liechtenstein"
#: trainvel/gtfs/models.py:35
msgid "Lithuania"
msgstr "Lituanie"
#: trainvel/gtfs/models.py:36
msgid "Luxembourg"
msgstr "Luxembourg"
#: trainvel/gtfs/models.py:37
msgid "Malta"
msgstr "Malte"
#: trainvel/gtfs/models.py:38
msgid "Moldova"
msgstr "Moldavie"
#: trainvel/gtfs/models.py:39
msgid "Monaco"
msgstr "Monaco"
#: trainvel/gtfs/models.py:40
msgid "Montenegro"
msgstr "Monténégro"
#: trainvel/gtfs/models.py:41
msgid "Netherlands"
msgstr "Pays-Bas"
#: trainvel/gtfs/models.py:42
msgid "North Macedonia"
msgstr "Macédoine du Nord"
#: trainvel/gtfs/models.py:43
msgid "Norway"
msgstr "Norvège"
#: trainvel/gtfs/models.py:44
msgid "Poland"
msgstr "Pologne"
#: trainvel/gtfs/models.py:45
msgid "Portugal"
msgstr "Portugal"
#: trainvel/gtfs/models.py:46
msgid "Romania"
msgstr "Roumanie"
#: trainvel/gtfs/models.py:47
msgid "San Marino"
msgstr "Saint-Marin"
#: trainvel/gtfs/models.py:48
msgid "Serbia"
msgstr "Serbie"
#: trainvel/gtfs/models.py:49
msgid "Slovakia"
msgstr "Slovaquie"
#: trainvel/gtfs/models.py:50
msgid "Slovenia"
msgstr "Slovénie"
#: trainvel/gtfs/models.py:51
msgid "Spain"
msgstr "Espagne"
#: trainvel/gtfs/models.py:52
msgid "Sweden"
msgstr "Suède"
#: trainvel/gtfs/models.py:53
msgid "Switzerland"
msgstr "Suisse"
#: trainvel/gtfs/models.py:54
msgid "Turkey"
msgstr "Turquie"
#: trainvel/gtfs/models.py:55
msgid "United Kingdom"
msgstr "Royaume-Uni"
#: trainvel/gtfs/models.py:56
msgid "Ukraine"
msgstr "Ukraine"
#: trainvel/gtfs/models.py:60
msgid "Stop/platform"
msgstr "Arrêt / quai"
#: trainvel/gtfs/models.py:61
msgid "Station"
msgstr "Gare"
#: trainvel/gtfs/models.py:62
msgid "Entrance/exit"
msgstr "Entrée / sortie"
#: trainvel/gtfs/models.py:63
msgid "Generic node"
msgstr "Nœud générique"
#: trainvel/gtfs/models.py:64
msgid "Boarding area"
msgstr "Zone d'embarquement"
#: trainvel/gtfs/models.py:68
msgid "No information"
msgstr "Pas d'information"
#: trainvel/gtfs/models.py:69
msgid "Possible"
msgstr "Possible"
#: trainvel/gtfs/models.py:70 trainvel/gtfs/models.py:100
msgid "Not possible"
msgstr "Impossible"
#: trainvel/gtfs/models.py:74
msgid "Regular"
msgstr "Régulier"
#: trainvel/gtfs/models.py:75
msgid "None"
msgstr "Aucun"
#: trainvel/gtfs/models.py:76
msgid "Must phone agency"
msgstr "Doit téléphoner à l'agence"
#: trainvel/gtfs/models.py:77
msgid "Must coordinate with driver"
msgstr "Doit se coordonner avec læ conducteurice"
#: trainvel/gtfs/models.py:81
msgid "Tram"
msgstr "Tram"
#: trainvel/gtfs/models.py:82
msgid "Metro"
msgstr "Métro"
#: trainvel/gtfs/models.py:83
msgid "Rail"
msgstr "Rail"
#: trainvel/gtfs/models.py:84
msgid "Bus"
msgstr "Bus"
#: trainvel/gtfs/models.py:85
msgid "Ferry"
msgstr "Ferry"
#: trainvel/gtfs/models.py:86
msgid "Cable car"
msgstr "Câble"
#: trainvel/gtfs/models.py:87
msgid "Gondola"
msgstr "Gondole"
#: trainvel/gtfs/models.py:88
msgid "Funicular"
msgstr "Funiculaire"
#: trainvel/gtfs/models.py:92
msgid "Outbound"
msgstr "Vers l'extérieur"
#: trainvel/gtfs/models.py:93
msgid "Inbound"
msgstr "Vers l'intérieur"
#: trainvel/gtfs/models.py:97
msgid "Recommended"
msgstr "Recommandé"
#: trainvel/gtfs/models.py:98
msgid "Timed"
msgstr "Correspondance programmée"
#: trainvel/gtfs/models.py:99
msgid "Minimum time"
msgstr "Temps de correspondance minimum requis"
#: trainvel/gtfs/models.py:104 trainvel/gtfs/models.py:110
msgid "Added"
msgstr "Ajouté"
#: trainvel/gtfs/models.py:105
msgid "Removed"
msgstr "Supprimé"
#: trainvel/gtfs/models.py:109 trainvel/gtfs/models.py:119
msgid "Scheduled"
msgstr "Planifié"
#: trainvel/gtfs/models.py:111 trainvel/gtfs/models.py:122
msgid "Unscheduled"
msgstr "Non planifié"
#: trainvel/gtfs/models.py:112
msgid "Canceled"
msgstr "Annulé"
#: trainvel/gtfs/models.py:113
msgid "Replacement"
msgstr "Remplacé"
#: trainvel/gtfs/models.py:114
msgid "Duplicated"
msgstr "Dupliqué"
#: trainvel/gtfs/models.py:115
msgid "Deleted"
msgstr "Supprimé"
#: trainvel/gtfs/models.py:120
msgid "Skipped"
msgstr "Sauté"
#: trainvel/gtfs/models.py:121
msgid "No data"
msgstr "Pas de données"
#: trainvel/gtfs/models.py:129
msgid "code"
msgstr "code"
#: trainvel/gtfs/models.py:130
msgid "Unique code of the feed."
msgstr "Code unique du flux."
#: trainvel/gtfs/models.py:135
msgid "name"
msgstr "nom"
#: trainvel/gtfs/models.py:137
msgid "Full name that describes the feed."
msgstr "Nom complet qui décrit le flux."
#: trainvel/gtfs/models.py:142
msgid "country"
msgstr "pays"
#: trainvel/gtfs/models.py:147
msgid "feed URL"
msgstr "URL du flux"
#: trainvel/gtfs/models.py:148
msgid ""
"URL to download the GTFS feed. Must point to a ZIP archive. See https://gtfs."
"org/schedule/ for more information."
msgstr ""
"URL où télécharger le flux GTFS. Doit pointer vers une archive ZIP. Voir "
"https://gtfs.org/fr/schedule/ pour plus d'informations."
#: trainvel/gtfs/models.py:153
msgid "realtime feed URL"
msgstr "URL du flux temps réel"
#: trainvel/gtfs/models.py:156
msgid ""
"URL to download the GTFS-Realtime feed, in the GTFS-RT format. See https://"
"gtfs.org/realtime/ for more information."
msgstr ""
"URL où télécharger le flux GTFS-Temps réel, au format GTFS-RT. Voir https://"
"gtfs.org/fr/realtime/ pour plus d'informations."
#: trainvel/gtfs/models.py:161
msgid "last modified date"
msgstr "Date de dernière modification"
#: trainvel/gtfs/models.py:168
msgid "ETag"
msgstr "ETag"
#: trainvel/gtfs/models.py:171
msgid ""
"If applicable, corresponds to the tag of the last downloaded file. If it is "
"not modified, the file is the same."
msgstr ""
"Si applicable, correspond au tag du dernier fichier téléchargé. S'il n'est "
"pas modifié, le fichier est considéré comme identique."
#: trainvel/gtfs/models.py:179 trainvel/gtfs/models.py:226
#: trainvel/gtfs/models.py:326 trainvel/gtfs/models.py:405
#: trainvel/gtfs/models.py:486 trainvel/gtfs/models.py:696
#: trainvel/gtfs/models.py:811
msgid "GTFS feed"
msgstr "flux GTFS"
#: trainvel/gtfs/models.py:180
msgid "GTFS feeds"
msgstr "flux GTFS"
#: trainvel/gtfs/models.py:189
msgid "Agency ID"
msgstr "ID de l'agence"
#: trainvel/gtfs/models.py:194
msgid "Agency name"
msgstr "Nom de l'agence"
#: trainvel/gtfs/models.py:198
msgid "Agency URL"
msgstr "URL de l'agence"
#: trainvel/gtfs/models.py:203
msgid "Agency timezone"
msgstr "Fuseau horaire de l'agence"
#: trainvel/gtfs/models.py:208
msgid "Agency language"
msgstr "Langue de l'agence"
#: trainvel/gtfs/models.py:214
msgid "Agency phone"
msgstr "Téléphone de l'agence"
#: trainvel/gtfs/models.py:219
msgid "Agency email"
msgstr "Adresse email de l'agence"
#: trainvel/gtfs/models.py:233 trainvel/gtfs/models.py:356
msgid "Agency"
msgstr "Agence"
#: trainvel/gtfs/models.py:234
msgid "Agencies"
msgstr "Agences"
#: trainvel/gtfs/models.py:243 trainvel/gtfs/models.py:593
msgid "Stop ID"
msgstr "ID de l'arrêt"
#: trainvel/gtfs/models.py:248
msgid "Stop code"
msgstr "Code de l'arrêt"
#: trainvel/gtfs/models.py:254
msgid "Stop name"
msgstr "Nom de l'arrêt"
#: trainvel/gtfs/models.py:259
msgid "Stop description"
msgstr "Description de l'arrêt"
#: trainvel/gtfs/models.py:264
msgid "Stop longitude"
msgstr "Longitude de l'arrêt"
#: trainvel/gtfs/models.py:268
msgid "Stop latitude"
msgstr "Latitude de l'arrêt"
#: trainvel/gtfs/models.py:273
msgid "Zone ID"
msgstr "ID de la zone"
#: trainvel/gtfs/models.py:278
msgid "Stop URL"
msgstr "URL de l'arrêt"
#: trainvel/gtfs/models.py:283
msgid "Location type"
msgstr "Type de localisation"
#: trainvel/gtfs/models.py:292
msgid "Parent station"
msgstr "Gare parente"
#: trainvel/gtfs/models.py:300
msgid "Stop timezone"
msgstr "Fuseau horaire de l'arrêt"
#: trainvel/gtfs/models.py:306
msgid "Level ID"
msgstr "ID du niveau"
#: trainvel/gtfs/models.py:311
msgid "Wheelchair boarding"
msgstr "Embarquement en fauteuil roulant"
#: trainvel/gtfs/models.py:319
msgid "Platform code"
msgstr "Code du quai"
#: trainvel/gtfs/models.py:338
msgid "Stop"
msgstr "Arrêt"
#: trainvel/gtfs/models.py:339
msgid "Stops"
msgstr "Arrêts"
#: trainvel/gtfs/models.py:350 trainvel/gtfs/models.py:572
#: trainvel/gtfs/models.py:713 trainvel/gtfs/models.py:746
msgid "ID"
msgstr "Identifiant"
#: trainvel/gtfs/models.py:365
msgid "Route short name"
msgstr "Nom court de la ligne"
#: trainvel/gtfs/models.py:370
msgid "Route long name"
msgstr "Nom long de la ligne"
#: trainvel/gtfs/models.py:376
msgid "Route description"
msgstr "Description de la ligne"
#: trainvel/gtfs/models.py:381
msgid "Route type"
msgstr "Type de ligne"
#: trainvel/gtfs/models.py:386
msgid "Route URL"
msgstr "URL de la ligne"
#: trainvel/gtfs/models.py:392
msgid "Route color"
msgstr "Couleur de la ligne"
#: trainvel/gtfs/models.py:398
msgid "Route text color"
msgstr "Couleur du texte de la ligne"
#: trainvel/gtfs/models.py:412 trainvel/gtfs/models.py:428
msgid "Route"
msgstr "Ligne"
#: trainvel/gtfs/models.py:413
msgid "Routes"
msgstr "Lignes"
#: trainvel/gtfs/models.py:422
msgid "Trip ID"
msgstr "ID du trajet"
#: trainvel/gtfs/models.py:435 trainvel/gtfs/models.py:719
msgid "Service"
msgstr "Service"
#: trainvel/gtfs/models.py:441
msgid "Trip headsign"
msgstr "Destination du trajet"
#: trainvel/gtfs/models.py:447
msgid "Trip short name"
msgstr "Nom court du trajet"
#: trainvel/gtfs/models.py:452
msgid "Direction"
msgstr "Direction"
#: trainvel/gtfs/models.py:459
msgid "Block ID"
msgstr "ID du bloc"
#: trainvel/gtfs/models.py:465
msgid "Shape ID"
msgstr "ID de la forme"
#: trainvel/gtfs/models.py:470
msgid "Wheelchair accessible"
msgstr "Accessible en fauteuil roulant"
#: trainvel/gtfs/models.py:477
msgid "Bikes allowed"
msgstr "Vélos autorisés"
#: trainvel/gtfs/models.py:500 trainvel/gtfs/models.py:509
#: trainvel/gtfs/models.py:552 trainvel/gtfs/models.py:554
msgid "Unknown"
msgstr "Inconnu"
#: trainvel/gtfs/models.py:557
msgid "Origin → Destination"
msgstr "Origine → Destination"
#: trainvel/gtfs/models.py:563 trainvel/gtfs/models.py:578
#: trainvel/gtfs/models.py:825
msgid "Trip"
msgstr "Trajet"
#: trainvel/gtfs/models.py:564
msgid "Trips"
msgstr "Trajets"
#: trainvel/gtfs/models.py:583 trainvel/gtfs/models.py:876
msgid "Arrival time"
msgstr "Heure d'arrivée"
#: trainvel/gtfs/models.py:587 trainvel/gtfs/models.py:884
msgid "Departure time"
msgstr "Heure de départ"
#: trainvel/gtfs/models.py:598
msgid "Stop sequence"
msgstr "Séquence de l'arrêt"
#: trainvel/gtfs/models.py:603
msgid "Stop headsign"
msgstr "Destination de l'arrêt"
#: trainvel/gtfs/models.py:608
msgid "Pickup type"
msgstr "Type de prise en charge"
#: trainvel/gtfs/models.py:615
msgid "Drop off type"
msgstr "Type de dépose"
#: trainvel/gtfs/models.py:622
msgid "Timepoint"
msgstr "Ponctualité"
#: trainvel/gtfs/models.py:645 trainvel/gtfs/models.py:866
msgid "Stop time"
msgstr "Heure d'arrêt"
#: trainvel/gtfs/models.py:646
msgid "Stop times"
msgstr "Heures d'arrêt"
#: trainvel/gtfs/models.py:654
msgid "Service ID"
msgstr "ID du service"
#: trainvel/gtfs/models.py:658
msgid "Monday"
msgstr "Lundi"
#: trainvel/gtfs/models.py:662
msgid "Tuesday"
msgstr "Mardi"
#: trainvel/gtfs/models.py:666
msgid "Wednesday"
msgstr "Mercredi"
#: trainvel/gtfs/models.py:670
msgid "Thursday"
msgstr "Jeudi"
#: trainvel/gtfs/models.py:674
msgid "Friday"
msgstr "Vendredi"
#: trainvel/gtfs/models.py:678
msgid "Saturday"
msgstr "Samedi"
#: trainvel/gtfs/models.py:682
msgid "Sunday"
msgstr "Dimanche"
#: trainvel/gtfs/models.py:686 trainvel/gtfs/models.py:831
msgid "Start date"
msgstr "Date de début"
#: trainvel/gtfs/models.py:690
msgid "End date"
msgstr "Date de fin"
#: trainvel/gtfs/models.py:703
msgid "Calendar"
msgstr "Calendrier"
#: trainvel/gtfs/models.py:704
msgid "Calendars"
msgstr "Calendriers"
#: trainvel/gtfs/models.py:724
msgid "Date"
msgstr "Date"
#: trainvel/gtfs/models.py:728
msgid "Exception type"
msgstr "Type d'exception"
#: trainvel/gtfs/models.py:736
msgid "Calendar date"
msgstr "Date du calendrier"
#: trainvel/gtfs/models.py:737
msgid "Calendar dates"
msgstr "Dates du calendrier"
#: trainvel/gtfs/models.py:752
msgid "From stop"
msgstr "Depuis l'arrêt"
#: trainvel/gtfs/models.py:759
msgid "To stop"
msgstr "Jusqu'à l'arrêt"
#: trainvel/gtfs/models.py:764
msgid "Transfer type"
msgstr "Type de correspondance"
#: trainvel/gtfs/models.py:770
msgid "Minimum transfer time"
msgstr "Temps de correspondance minimum"
#: trainvel/gtfs/models.py:775
msgid "Transfer"
msgstr "Correspondance"
#: trainvel/gtfs/models.py:776
msgid "Transfers"
msgstr "Correspondances"
#: trainvel/gtfs/models.py:783
msgid "Feed publisher name"
msgstr "Nom de l'éditeur du flux"
#: trainvel/gtfs/models.py:787
msgid "Feed publisher URL"
msgstr "URL de l'éditeur du flux"
#: trainvel/gtfs/models.py:792
msgid "Feed language"
msgstr "Langue du flux"
#: trainvel/gtfs/models.py:796
msgid "Feed start date"
msgstr "Date de début du flux"
#: trainvel/gtfs/models.py:800
msgid "Feed end date"
msgstr "Date de fin du flux"
#: trainvel/gtfs/models.py:805
msgid "Feed version"
msgstr "Version du flux"
#: trainvel/gtfs/models.py:815
msgid "Feed info"
msgstr "Information du flux"
#: trainvel/gtfs/models.py:816
msgid "Feed infos"
msgstr "Informations du flux"
#: trainvel/gtfs/models.py:835
msgid "Start time"
msgstr "Heure de début"
#: trainvel/gtfs/models.py:839 trainvel/gtfs/models.py:888
msgid "Schedule relationship"
msgstr "Relation de la planification"
#: trainvel/gtfs/models.py:848 trainvel/gtfs/models.py:859
msgid "Trip update"
msgstr "Mise à jour du trajet"
#: trainvel/gtfs/models.py:849
msgid "Trip updates"
msgstr "Mises à jour des trajets"
#: trainvel/gtfs/models.py:872
msgid "Arrival delay"
msgstr "Retard à l'arrivée"
#: trainvel/gtfs/models.py:880
msgid "Departure delay"
msgstr "Retard au départ"
#: trainvel/gtfs/models.py:897
msgid "Stop time update"
msgstr "Mise à jour du temps d'arrêt"
#: trainvel/gtfs/models.py:898
msgid "Stop time updates"
msgstr "Mises à jour des temps d'arrêt"
#~ msgid "TGV"
#~ msgstr "TGV"
#~ msgid "TER"
#~ msgstr "TER"
#~ msgid "Intercités"
#~ msgstr "Intercités"
#~ msgid "Transilien"
#~ msgstr "Transilien"
#~ msgid "Eurostar"
#~ msgstr "Eurostar"
#~ msgid "Trenitalia"
#~ msgstr "Trenitalia"
#~ msgid "Renfe"
#~ msgstr "Renfe"
#~ msgid "ÖBB"
#~ msgstr "ÖBB"
#~ msgid "Last update"
#~ msgstr "Dernière mise à jour"
#~ msgid "Transport type"
#~ msgstr "Type de transport"

View File

@ -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()

View File

@ -1,430 +0,0 @@
import csv
import os.path
import tempfile
from datetime import datetime, timedelta
from time import time
from zipfile import ZipFile
from zoneinfo import ZoneInfo
import requests
from django.core.management import BaseCommand
from django.db import transaction
from django.db.models import Q
from tqdm import tqdm
from trainvel.gtfs.models import Agency, Calendar, CalendarDate, FeedInfo, GTFSFeed, Route, Stop, StopTime, \
Transfer, Trip, PickupType, TripUpdate
class Command(BaseCommand):
help = "Update the Trainvel GTFS database."
def add_arguments(self, parser):
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
parser.add_argument('--bulk_size', '-b', type=int, default=2000, help="Number of objects to create in bulk.")
parser.add_argument('--dry-run', action='store_true',
help="Do not update the database, only print what would be done.")
parser.add_argument('--force', '-f', action='store_true', help="Force the update of the database.")
def handle(self, debug: bool = False, bulk_size: int = 100, dry_run: bool = False, force: bool = False,
verbosity: int = 1, *args, **options):
if dry_run:
self.stdout.write(self.style.WARNING("Dry run mode activated."))
self.stdout.write("Updating database...")
for gtfs_feed in GTFSFeed.objects.all():
if not force:
# Check if the source file was updated
resp = requests.head(gtfs_feed.feed_url, allow_redirects=True)
if 'ETag' in resp.headers and gtfs_feed.etag:
if resp.headers['ETag'] == gtfs_feed.etag:
if verbosity >= 1:
self.stdout.write(self.style.WARNING(f"Database is already up-to-date for {gtfs_feed}."))
continue
if 'Last-Modified' in resp.headers and gtfs_feed.last_modified:
last_modified = resp.headers['Last-Modified']
last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z") \
.replace(tzinfo=ZoneInfo(last_modified.split(' ')[-1]))
if last_modified <= gtfs_feed.last_modified:
if verbosity >= 1:
self.stdout.write(self.style.WARNING(f"Database is already up-to-date for {gtfs_feed}."))
continue
self.stdout.write(f"Downloading GTFS feed for {gtfs_feed}...")
resp = requests.get(gtfs_feed.feed_url, allow_redirects=True, stream=True)
with tempfile.TemporaryFile(suffix=".zip") as file:
for chunk in resp.iter_content(chunk_size=128):
file.write(chunk)
file.seek(0)
with tempfile.TemporaryDirectory() as tmp_dir:
with ZipFile(file) as zipfile:
zipfile.extractall(tmp_dir)
self.parse_gtfs(tmp_dir, gtfs_feed, bulk_size, dry_run, verbosity)
if 'ETag' in resp.headers:
gtfs_feed.etag = resp.headers['ETag']
gtfs_feed.save()
if 'Last-Modified' in resp.headers:
last_modified = resp.headers['Last-Modified']
gtfs_feed.last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z") \
.replace(tzinfo=ZoneInfo(last_modified.split(' ')[-1]))
gtfs_feed.save()
def parse_gtfs(self, zip_dir: str, gtfs_feed: GTFSFeed, bulk_size: int, dry_run: bool, verbosity: int):
gtfs_code = gtfs_feed.code
def read_csv(filename):
with open(os.path.join(zip_dir, filename), 'r') as f:
reader = csv.DictReader(f)
reader.fieldnames = [field.replace('\ufeff', '').strip()
for field in reader.fieldnames]
iterator = tqdm(reader, desc=filename, unit=' rows') if verbosity >= 2 else reader
for row in iterator:
yield {k.strip(): v.strip() for k, v in row.items()}
agencies = []
for agency_dict in read_csv("agency.txt"):
agency_dict: dict
agency = Agency(
id=f"{gtfs_code}-{agency_dict['agency_id']}",
name=agency_dict['agency_name'],
url=agency_dict['agency_url'],
timezone=agency_dict['agency_timezone'],
lang=agency_dict.get('agency_lang', "fr"),
phone=agency_dict.get('agency_phone', ""),
email=agency_dict.get('agency_email', ""),
gtfs_feed_id=gtfs_code,
)
agencies.append(agency)
if agencies and not dry_run:
Agency.objects.bulk_create(agencies,
update_conflicts=True,
update_fields=['name', 'url', 'timezone', 'lang', 'phone', 'email',
'gtfs_feed'],
unique_fields=['id'])
agencies.clear()
stops = []
for stop_dict in read_csv("stops.txt"):
stop_dict: dict
stop_id = stop_dict['stop_id']
stop_id = f"{gtfs_code}-{stop_id}"
parent_station_id = stop_dict.get('parent_station', None)
parent_station_id = f"{gtfs_code}-{parent_station_id}" if parent_station_id else None
stop = Stop(
id=stop_id,
name=stop_dict['stop_name'],
desc=stop_dict.get('stop_desc', ""),
lat=stop_dict['stop_lat'],
lon=stop_dict['stop_lon'],
zone_id=stop_dict.get('zone_id', ""),
url=stop_dict.get('stop_url', ""),
location_type=stop_dict.get('location_type', 0) or 0,
parent_station_id=parent_station_id,
timezone=stop_dict.get('stop_timezone', ""),
wheelchair_boarding=stop_dict.get('wheelchair_boarding', 0),
level_id=stop_dict.get('level_id', ""),
platform_code=stop_dict.get('platform_code', ""),
gtfs_feed_id=gtfs_code,
)
stops.append(stop)
if stops and not dry_run:
Stop.objects.bulk_create(stops,
batch_size=bulk_size,
update_conflicts=True,
update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url',
'location_type', 'parent_station_id', 'timezone',
'wheelchair_boarding', 'level_id', 'platform_code',
'gtfs_feed'],
unique_fields=['id'])
stops.clear()
routes = []
for route_dict in read_csv("routes.txt"):
route_dict: dict
route_id = route_dict['route_id']
route_id = f"{gtfs_code}-{route_id}"
# Agency is optional there is only one
agency_id = route_dict.get('agency_id', "") or Agency.objects.get(gtfs_feed_id=gtfs_code)
route = Route(
id=route_id,
agency_id=f"{gtfs_code}-{agency_id}",
short_name=route_dict['route_short_name'],
long_name=route_dict['route_long_name'],
desc=route_dict.get('route_desc', ""),
type=route_dict['route_type'],
url=route_dict.get('route_url', ""),
color=route_dict.get('route_color', ""),
text_color=route_dict.get('route_text_color', ""),
gtfs_feed_id=gtfs_code,
)
routes.append(route)
if len(routes) >= bulk_size and not dry_run:
Route.objects.bulk_create(routes,
update_conflicts=True,
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
'type', 'url', 'color', 'text_color',
'gtfs_feed'],
unique_fields=['id'])
routes.clear()
if routes and not dry_run:
Route.objects.bulk_create(routes,
update_conflicts=True,
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
'type', 'url', 'color', 'text_color',
'gtfs_feed'],
unique_fields=['id'])
routes.clear()
start_time = 0
if verbosity >= 1:
self.stdout.write("Deleting old calendars, trips and stop times…")
start_time = time()
TripUpdate.objects.filter(trip__gtfs_feed_id=gtfs_code).delete()
StopTime.objects.filter(trip__gtfs_feed_id=gtfs_code)._raw_delete(StopTime.objects.db)
Trip.objects.filter(gtfs_feed_id=gtfs_code)._raw_delete(Trip.objects.db)
Calendar.objects.filter(gtfs_feed_id=gtfs_code).delete()
if verbosity >= 1:
end = time()
self.stdout.write(f"Done in {end - start_time:.2f} s")
calendars = {}
if os.path.exists(os.path.join(zip_dir, "calendar.txt")):
for calendar_dict in read_csv("calendar.txt"):
calendar_dict: dict
calendar = Calendar(
id=f"{gtfs_code}-{calendar_dict['service_id']}",
monday=calendar_dict['monday'],
tuesday=calendar_dict['tuesday'],
wednesday=calendar_dict['wednesday'],
thursday=calendar_dict['thursday'],
friday=calendar_dict['friday'],
saturday=calendar_dict['saturday'],
sunday=calendar_dict['sunday'],
start_date=calendar_dict['start_date'],
end_date=calendar_dict['end_date'],
gtfs_feed_id=gtfs_code,
)
calendars[calendar.id] = calendar
if len(calendars) >= bulk_size and not dry_run:
Calendar.objects.bulk_create(calendars.values(), batch_size=bulk_size)
calendars.clear()
if calendars and not dry_run:
Calendar.objects.bulk_create(calendars.values(), batch_size=bulk_size)
calendars.clear()
calendar_dates = []
all_calendars = {calendar.id: calendar for calendar in Calendar.objects.filter(gtfs_feed_id=gtfs_code)}
new_calendars = {}
with transaction.atomic():
for calendar_date_dict in read_csv("calendar_dates.txt"):
calendar_date_dict: dict
service_id = f"{gtfs_code}-{calendar_date_dict['service_id']}"
date = datetime.fromisoformat(calendar_date_dict['date']).date()
calendar_date = CalendarDate(
id=f"{gtfs_code}-{calendar_date_dict['service_id']}-{calendar_date_dict['date']}",
service_id=service_id,
date=calendar_date_dict['date'],
exception_type=calendar_date_dict['exception_type'],
)
calendar_dates.append(calendar_date)
if service_id not in all_calendars:
calendar = Calendar(
id=service_id,
monday=False,
tuesday=False,
wednesday=False,
thursday=False,
friday=False,
saturday=False,
sunday=False,
start_date=date,
end_date=date,
gtfs_feed_id=gtfs_code,
)
all_calendars[service_id] = calendar
new_calendars[service_id] = calendar
else:
calendar = all_calendars[service_id]
if calendar.start_date > date:
calendar.start_date = date
if calendar.end_date < date:
calendar.end_date = date
if len(calendar_dates) >= bulk_size and not dry_run:
CalendarDate.objects.bulk_create(calendar_dates, batch_size=bulk_size)
calendar_dates.clear()
if (calendar_dates or new_calendars) and not dry_run:
Calendar.objects.bulk_create(new_calendars.values(), batch_size=bulk_size)
CalendarDate.objects.bulk_create(calendar_dates, batch_size=bulk_size)
new_calendars.clear()
calendar_dates.clear()
trips = []
# start_time = time()
for trip_dict in read_csv("trips.txt"):
trip_dict: dict
trip_id = trip_dict['trip_id']
route_id = trip_dict['route_id']
trip_id = f"{gtfs_code}-{trip_id}"
route_id = f"{gtfs_code}-{route_id}"
trip = Trip(
id=trip_id,
route_id=route_id,
service_id=f"{gtfs_code}-{trip_dict['service_id']}",
headsign=trip_dict.get('trip_headsign', ""),
short_name=trip_dict.get('trip_short_name', ""),
direction_id=trip_dict.get('direction_id', None) or None,
block_id=trip_dict.get('block_id', ""),
shape_id=trip_dict.get('shape_id', ""),
wheelchair_accessible=trip_dict.get('wheelchair_accessible', None),
bikes_allowed=trip_dict.get('bikes_allowed', None),
gtfs_feed_id=gtfs_code,
)
trips.append(trip)
if len(trips) >= bulk_size and not dry_run:
# now = time()
# print(f"Elapsed time: {now - start_time:.3f}s, "
# f"{1000 * (now - start_time) / len(trips):.2f}ms per iteration")
# start_time = now
Trip.objects.bulk_create(trips)
# now = time()
# print(f"Elapsed time: {now - start_time:.3f}s to save")
# start_time = now
trips.clear()
if trips and not dry_run:
Trip.objects.bulk_create(trips)
trips.clear()
stop_times = []
# start_time = time()
for stop_time_dict in read_csv("stop_times.txt"):
stop_time_dict: dict
stop_id = stop_time_dict['stop_id']
stop_id = f"{gtfs_code}-{stop_id}"
trip_id = stop_time_dict['trip_id']
trip_id = f"{gtfs_code}-{trip_id}"
arr_time = stop_time_dict['arrival_time']
arr_h, arr_m, arr_s = map(int, arr_time.split(':'))
arr_time = arr_h * 3600 + arr_m * 60 + arr_s
dep_time = stop_time_dict['departure_time']
dep_h, dep_m, dep_s = map(int, dep_time.split(':'))
dep_time = dep_h * 3600 + dep_m * 60 + dep_s
pickup_type = stop_time_dict.get('pickup_type', PickupType.REGULAR)
drop_off_type = stop_time_dict.get('drop_off_type', PickupType.REGULAR)
# if stop_time_dict['stop_sequence'] == "1":
# # First stop
# drop_off_type = PickupType.NONE
# elif arr_time == dep_time:
# # Last stop
# pickup_type = PickupType.NONE
st = StopTime(
id=f"{gtfs_code}-{stop_time_dict['trip_id']}-{stop_time_dict['stop_sequence']}",
trip_id=trip_id,
arrival_time=timedelta(seconds=arr_time),
departure_time=timedelta(seconds=dep_time),
stop_id=stop_id,
stop_sequence=stop_time_dict['stop_sequence'],
stop_headsign=stop_time_dict.get('stop_headsign', ""),
pickup_type=pickup_type,
drop_off_type=drop_off_type,
timepoint=stop_time_dict.get('timepoint', None),
)
stop_times.append(st)
if len(stop_times) >= bulk_size and not dry_run:
# now = time()
# print(f"Elapsed time: {now - start_time:.3f}s, "
# f"{1000 * (now - start_time) / len(stop_times):.2f}ms per iteration")
# start_time = now
StopTime.objects.bulk_create(stop_times)
# now = time()
# print(f"Elapsed time: {now - start_time:.3f}s to save")
# start_time = now
stop_times.clear()
if stop_times and not dry_run:
StopTime.objects.bulk_create(stop_times)
stop_times.clear()
if os.path.exists(os.path.join(zip_dir, "transfers.txt")):
Transfer.objects.filter(Q(from_stop__gtfs_feed_id=gtfs_code) | Q(to_stop__gtfs_feed_id=gtfs_code)).delete()
transfers = []
for transfer_dict in read_csv("transfers.txt"):
transfer_dict: dict
from_stop_id = transfer_dict['from_stop_id']
to_stop_id = transfer_dict['to_stop_id']
from_stop_id = f"{gtfs_code}-{from_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(
id=transfer_id,
from_stop_id=from_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'],
min_transfer_time=transfer_dict.get('min_transfer_time', 0) or 0,
)
transfers.append(transfer)
if len(transfers) >= bulk_size and not dry_run:
Transfer.objects.bulk_create(transfers)
transfers.clear()
if transfers and not dry_run:
Transfer.objects.bulk_create(transfers)
transfers.clear()
if os.path.exists(os.path.join(zip_dir, "feed_info.txt")) and not dry_run:
for feed_info_dict in read_csv("feed_info.txt"):
feed_info_dict: dict
FeedInfo.objects.update_or_create(
publisher_name=feed_info_dict['feed_publisher_name'],
gtfs_feed_id=gtfs_code,
defaults=dict(
publisher_url=feed_info_dict['feed_publisher_url'],
lang=feed_info_dict['feed_lang'],
start_date=feed_info_dict.get('feed_start_date', datetime.now().date()),
end_date=feed_info_dict.get('feed_end_date', datetime.now().date()),
version=feed_info_dict.get('feed_version', 1),
)
)

View File

@ -1,204 +0,0 @@
from datetime import timedelta, datetime, date, time
from zoneinfo import ZoneInfo
import requests
from django.core.management import BaseCommand
from trainvel import settings
from trainvel.gtfs.gtfs_realtime_pb2 import FeedMessage, TripUpdate as GTFSTripUpdate
from trainvel.gtfs.models import Calendar, CalendarDate, ExceptionType, GTFSFeed, PickupType, \
Route, RouteType, StopScheduleRelationship, StopTime, StopTimeUpdate, \
Trip, TripUpdate, TripScheduleRelationship
class Command(BaseCommand):
help = "Update the Trainvel GTFS Realtime database."
def add_arguments(self, parser):
parser.add_argument('--debug', '-d', action='store_true', help="Activate debug mode")
def handle(self, debug: bool = False, verbosity: int = 1, *args, **options):
for gtfs_feed in GTFSFeed.objects.all():
if not gtfs_feed.rt_feed_url:
if verbosity >= 2:
self.stdout.write(self.style.WARNING(f"No GTFS-RT feed found for {gtfs_feed}."))
continue
self.stdout.write(f"Updating GTFS-RT feed for {gtfs_feed}")
gtfs_code = gtfs_feed.code
headers = {}
if gtfs_code == "CH-ALL":
headers["Authorization"] = settings.OPENTRANSPORTDATA_SWISS_TOKEN
resp = requests.get(gtfs_feed.rt_feed_url, allow_redirects=True, headers=headers)
feed_message = FeedMessage()
feed_message.ParseFromString(resp.content)
stop_times_updates = []
if debug:
with open(f'feed_message-{gtfs_code}.txt', 'w') as f:
f.write(str(feed_message))
for entity in feed_message.entity:
try:
if entity.HasField("trip_update"):
trip_update = entity.trip_update
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]),
month=int(trip_update.trip.start_date[4:6]),
day=int(trip_update.trip.start_date[6:]))
start_dt = datetime.combine(start_date, time(0), tzinfo=ZoneInfo("Europe/Paris"))
if trip_update.trip.schedule_relationship == TripScheduleRelationship.ADDED:
# C'est un trajet nouveau. On crée le trajet associé.
self.create_trip(trip_update, trip_id, start_dt, gtfs_feed)
if not Trip.objects.filter(id=trip_id).exists():
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
continue
# Création du TripUpdate
tu, _created = TripUpdate.objects.update_or_create(
trip_id=trip_id,
start_date=trip_update.trip.start_date,
start_time=trip_update.trip.start_time,
defaults=dict(
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,
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_update = StopTimeUpdate(
trip_update=tu,
stop_time=st,
arrival_delay=timedelta(seconds=stop_time_update.arrival.delay),
arrival_time=datetime.fromtimestamp(stop_time_update.arrival.time,
tz=ZoneInfo("Europe/Paris")),
departure_delay=timedelta(seconds=stop_time_update.departure.delay),
departure_time=datetime.fromtimestamp(stop_time_update.departure.time,
tz=ZoneInfo("Europe/Paris")),
schedule_relationship=stop_time_update.schedule_relationship
or StopScheduleRelationship.SCHEDULED,
)
stop_times_updates.append(st_update)
else:
self.stdout.write(str(entity))
except Exception as e:
self.stderr.write(self.style.ERROR(f"Error while processing entity: {e}"))
StopTimeUpdate.objects.bulk_create(stop_times_updates,
update_conflicts=True,
update_fields=['arrival_delay', 'arrival_time',
'departure_delay', 'departure_time'],
unique_fields=['trip_update', 'stop_time'])
def create_trip(self, trip_update: GTFSTripUpdate, trip_id: str, start_dt: datetime, gtfs_feed: GTFSFeed) -> None:
headsign = trip_id[5:-1]
gtfs_code = gtfs_feed.code
route, _created = Route.objects.get_or_create(
id=f"{gtfs_code}-ADDED-{headsign}",
gtfs_feed=gtfs_feed,
type=RouteType.RAIL,
short_name="ADDED",
long_name="ADDED ROUTE",
)
Calendar.objects.update_or_create(
id=f"{gtfs_code}-ADDED-{headsign}",
defaults={
"gtfs_feed": gtfs_feed,
"monday": False,
"tuesday": False,
"wednesday": False,
"thursday": False,
"friday": False,
"saturday": False,
"sunday": False,
"start_date": start_dt.date(),
"end_date": start_dt.date(),
}
)
CalendarDate.objects.update_or_create(
id=f"{gtfs_code}-ADDED-{headsign}-{trip_update.trip.start_date}",
defaults={
"service_id": f"{gtfs_code}-ADDED-{headsign}",
"date": trip_update.trip.start_date,
"exception_type": ExceptionType.ADDED,
}
)
Trip.objects.update_or_create(
id=trip_id,
defaults={
"route_id": route.id,
"service_id": f"{gtfs_code}-ADDED-{headsign}",
"headsign": headsign,
"direction_id": trip_update.trip.direction_id,
"gtfs_feed": gtfs_feed,
}
)
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}"
arr_time = datetime.fromtimestamp(stop_time_update.arrival.time,
tz=ZoneInfo("Europe/Paris")) - start_dt
dep_time = datetime.fromtimestamp(stop_time_update.departure.time,
tz=ZoneInfo("Europe/Paris")) - start_dt
pickup_type = PickupType.REGULAR if stop_time_update.departure.time and stop_sequence > 0 \
else PickupType.NONE
drop_off_type = PickupType.REGULAR if stop_time_update.arrival.time \
and stop_sequence < len(trip_update.stop_time_update) - 1 else PickupType.NONE
StopTime.objects.update_or_create(
id=f"{gtfs_code}-{trip_id}-{stop_time_update.stop_sequence}",
trip_id=trip_id,
defaults={
"stop_id": stop_id,
"stop_sequence": stop_sequence,
"arrival_time": arr_time,
"departure_time": dep_time,
"pickup_type": pickup_type,
"drop_off_type": drop_off_type,
}
)

View File

@ -1,911 +0,0 @@
# Generated by Django 5.0.1 on 2024-05-09 17:34
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="StopTime",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("arrival_time", models.DurationField(verbose_name="Arrival time")),
("departure_time", models.DurationField(verbose_name="Departure time")),
("stop_sequence", models.IntegerField(verbose_name="Stop sequence")),
(
"stop_headsign",
models.CharField(
blank=True, max_length=255, verbose_name="Stop headsign"
),
),
(
"pickup_type",
models.IntegerField(
choices=[
(0, "Regular"),
(1, "None"),
(2, "Must phone agency"),
(3, "Must coordinate with driver"),
],
default=0,
null=True,
verbose_name="Pickup type",
),
),
(
"drop_off_type",
models.IntegerField(
choices=[
(0, "Regular"),
(1, "None"),
(2, "Must phone agency"),
(3, "Must coordinate with driver"),
],
default=0,
null=True,
verbose_name="Drop off type",
),
),
(
"timepoint",
models.BooleanField(
default=True, null=True, verbose_name="Timepoint"
),
),
],
options={
"verbose_name": "Stop time",
"verbose_name_plural": "Stop times",
},
),
migrations.CreateModel(
name="Trip",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="Trip ID",
),
),
(
"headsign",
models.CharField(
blank=True, max_length=255, verbose_name="Trip headsign"
),
),
(
"short_name",
models.CharField(
blank=True, max_length=255, verbose_name="Trip short name"
),
),
(
"direction_id",
models.IntegerField(
choices=[(0, "Outbound"), (1, "Inbound")],
null=True,
verbose_name="Direction",
),
),
(
"block_id",
models.CharField(
blank=True, max_length=255, verbose_name="Block ID"
),
),
(
"shape_id",
models.CharField(
blank=True, max_length=255, verbose_name="Shape ID"
),
),
(
"wheelchair_accessible",
models.IntegerField(
choices=[
(0, "No information"),
(1, "Possible"),
(2, "Not possible"),
],
default=0,
null=True,
verbose_name="Wheelchair accessible",
),
),
(
"bikes_allowed",
models.IntegerField(
choices=[
(0, "No information"),
(1, "Possible"),
(2, "Not possible"),
],
default=0,
null=True,
verbose_name="Bikes allowed",
),
),
],
options={
"verbose_name": "Trip",
"verbose_name_plural": "Trips",
},
),
migrations.CreateModel(
name="GTFSFeed",
fields=[
(
"code",
models.CharField(
help_text="Unique code of the feed.",
max_length=64,
primary_key=True,
serialize=False,
verbose_name="code",
),
),
(
"name",
models.CharField(
help_text="Full name that describes the feed.",
max_length=255,
unique=True,
verbose_name="name",
),
),
(
"country",
models.CharField(
choices=[
("AL", "Albania"),
("AD", "Andorra"),
("AM", "Armenia"),
("AT", "Austria"),
("AZ", "Azerbaijan"),
("BE", "Belgium"),
("BA", "Bosnia and Herzegovina"),
("BG", "Bulgaria"),
("HR", "Croatia"),
("CY", "Cyprus"),
("CZ", "Czech Republic"),
("DK", "Denmark"),
("EE", "Estonia"),
("FI", "Finland"),
("FR", "France"),
("GE", "Georgia"),
("DE", "Germany"),
("GR", "Greece"),
("HU", "Hungary"),
("IS", "Iceland"),
("IE", "Ireland"),
("IT", "Italy"),
("LV", "Latvia"),
("LI", "Liechtenstein"),
("LT", "Lithuania"),
("LU", "Luxembourg"),
("MT", "Malta"),
("MD", "Moldova"),
("MC", "Monaco"),
("ME", "Montenegro"),
("NL", "Netherlands"),
("MK", "North Macedonia"),
("NO", "Norway"),
("PL", "Poland"),
("PT", "Portugal"),
("RO", "Romania"),
("SM", "San Marino"),
("RS", "Serbia"),
("SK", "Slovakia"),
("SI", "Slovenia"),
("ES", "Spain"),
("SE", "Sweden"),
("CH", "Switzerland"),
("TR", "Turkey"),
("GB", "United Kingdom"),
("UA", "Ukraine"),
],
max_length=2,
verbose_name="country",
),
),
(
"feed_url",
models.URLField(
help_text="URL to download the GTFS feed. Must point to a ZIP archive. See https://gtfs.org/schedule/ for more information.",
verbose_name="feed URL",
),
),
(
"rt_feed_url",
models.URLField(
blank=True,
default="",
help_text="URL to download the GTFS-Realtime feed, in the GTFS-RT format. See https://gtfs.org/realtime/ for more information.",
verbose_name="realtime feed URL",
),
),
(
"last_modified",
models.DateTimeField(
default=None, null=True, verbose_name="last modified date"
),
),
(
"etag",
models.CharField(
blank=True,
default="",
help_text="If applicable, corresponds to the tag of the last downloaded file. If it is not modified, the file is the same.",
max_length=255,
verbose_name="ETag",
),
),
],
options={
"verbose_name": "GTFS feed",
"verbose_name_plural": "GTFS feeds",
"ordering": ("country", "name"),
"indexes": [
models.Index(fields=["name"], name="gtfs_gtfsfe_name_aabd02_idx")
],
},
),
migrations.CreateModel(
name="Calendar",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="Service ID",
),
),
("monday", models.BooleanField(verbose_name="Monday")),
("tuesday", models.BooleanField(verbose_name="Tuesday")),
("wednesday", models.BooleanField(verbose_name="Wednesday")),
("thursday", models.BooleanField(verbose_name="Thursday")),
("friday", models.BooleanField(verbose_name="Friday")),
("saturday", models.BooleanField(verbose_name="Saturday")),
("sunday", models.BooleanField(verbose_name="Sunday")),
("start_date", models.DateField(verbose_name="Start date")),
("end_date", models.DateField(verbose_name="End date")),
(
"gtfs_feed",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
],
options={
"verbose_name": "Calendar",
"verbose_name_plural": "Calendars",
"ordering": ("id",),
},
),
migrations.CreateModel(
name="Agency",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="Agency ID",
),
),
("name", models.CharField(max_length=255, verbose_name="Agency name")),
("url", models.URLField(verbose_name="Agency URL")),
(
"timezone",
models.CharField(max_length=255, verbose_name="Agency timezone"),
),
(
"lang",
models.CharField(
blank=True, max_length=255, verbose_name="Agency language"
),
),
(
"phone",
models.CharField(
blank=True, max_length=255, verbose_name="Agency phone"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="Agency email"
),
),
(
"gtfs_feed",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
],
options={
"verbose_name": "Agency",
"verbose_name_plural": "Agencies",
"ordering": ("name",),
},
),
migrations.CreateModel(
name="Route",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"short_name",
models.CharField(max_length=255, verbose_name="Route short name"),
),
(
"long_name",
models.CharField(
blank=True, max_length=255, verbose_name="Route long name"
),
),
(
"desc",
models.CharField(
blank=True, max_length=255, verbose_name="Route description"
),
),
(
"type",
models.IntegerField(
choices=[
(0, "Tram"),
(1, "Metro"),
(2, "Rail"),
(3, "Bus"),
(4, "Ferry"),
(5, "Cable car"),
(6, "Gondola"),
(7, "Funicular"),
],
verbose_name="Route type",
),
),
("url", models.URLField(blank=True, verbose_name="Route URL")),
(
"color",
models.CharField(
blank=True, max_length=255, verbose_name="Route color"
),
),
(
"text_color",
models.CharField(
blank=True, max_length=255, verbose_name="Route text color"
),
),
(
"agency",
models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="routes",
to="gtfs.agency",
verbose_name="Agency",
),
),
(
"gtfs_feed",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
],
options={
"verbose_name": "Route",
"verbose_name_plural": "Routes",
"ordering": ("id",),
},
),
migrations.CreateModel(
name="Stop",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="Stop ID",
),
),
(
"code",
models.CharField(
blank=True, max_length=255, verbose_name="Stop code"
),
),
("name", models.CharField(max_length=255, verbose_name="Stop name")),
(
"desc",
models.CharField(
blank=True, max_length=255, verbose_name="Stop description"
),
),
("lon", models.FloatField(verbose_name="Stop longitude")),
("lat", models.FloatField(verbose_name="Stop latitude")),
(
"zone_id",
models.CharField(
blank=True, max_length=255, verbose_name="Zone ID"
),
),
("url", models.URLField(blank=True, verbose_name="Stop URL")),
(
"location_type",
models.IntegerField(
blank=True,
choices=[
(0, "Stop/platform"),
(1, "Station"),
(2, "Entrance/exit"),
(3, "Generic node"),
(4, "Boarding area"),
],
default=0,
verbose_name="Location type",
),
),
(
"timezone",
models.CharField(
blank=True, max_length=255, verbose_name="Stop timezone"
),
),
(
"level_id",
models.CharField(
blank=True, max_length=255, verbose_name="Level ID"
),
),
(
"wheelchair_boarding",
models.IntegerField(
blank=True,
choices=[
(0, "No information"),
(1, "Possible"),
(2, "Not possible"),
],
default=0,
verbose_name="Wheelchair boarding",
),
),
(
"platform_code",
models.CharField(
blank=True, max_length=255, verbose_name="Platform code"
),
),
(
"gtfs_feed",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
(
"parent_station",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="children",
to="gtfs.stop",
verbose_name="Parent station",
),
),
],
options={
"verbose_name": "Stop",
"verbose_name_plural": "Stops",
"ordering": ("id",),
},
),
migrations.CreateModel(
name="StopTimeUpdate",
fields=[
(
"stop_time",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="update",
serialize=False,
to="gtfs.stoptime",
verbose_name="Stop time",
),
),
("arrival_delay", models.DurationField(verbose_name="Arrival delay")),
("arrival_time", models.DateTimeField(verbose_name="Arrival time")),
(
"departure_delay",
models.DurationField(verbose_name="Departure delay"),
),
("departure_time", models.DateTimeField(verbose_name="Departure time")),
(
"schedule_relationship",
models.IntegerField(
choices=[
(0, "Scheduled"),
(1, "Skipped"),
(2, "No data"),
(3, "Unscheduled"),
],
default=0,
verbose_name="Schedule relationship",
),
),
],
options={
"verbose_name": "Stop time update",
"verbose_name_plural": "Stop time updates",
"ordering": ("trip_update", "stop_time"),
},
),
migrations.AddField(
model_name="stoptime",
name="stop",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="stop_times",
to="gtfs.stop",
verbose_name="Stop ID",
),
),
migrations.CreateModel(
name="Transfer",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"transfer_type",
models.IntegerField(
choices=[
(0, "Recommended"),
(1, "Timed"),
(2, "Minimum time"),
(3, "Not possible"),
],
default=0,
verbose_name="Transfer type",
),
),
(
"min_transfer_time",
models.IntegerField(
blank=True, verbose_name="Minimum transfer time"
),
),
(
"from_stop",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="transfers_from",
to="gtfs.stop",
verbose_name="From stop",
),
),
(
"to_stop",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="transfers_to",
to="gtfs.stop",
verbose_name="To stop",
),
),
],
options={
"verbose_name": "Transfer",
"verbose_name_plural": "Transfers",
"ordering": ("id",),
},
),
migrations.CreateModel(
name="TripUpdate",
fields=[
(
"trip",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="update",
serialize=False,
to="gtfs.trip",
verbose_name="Trip",
),
),
("start_date", models.DateField(verbose_name="Start date")),
("start_time", models.TimeField(verbose_name="Start time")),
(
"schedule_relationship",
models.IntegerField(
choices=[
(0, "Scheduled"),
(1, "Added"),
(2, "Unscheduled"),
(3, "Canceled"),
(5, "Replacement"),
(6, "Duplicated"),
(7, "Deleted"),
],
default=0,
verbose_name="Schedule relationship",
),
),
],
options={
"verbose_name": "Trip update",
"verbose_name_plural": "Trip updates",
"ordering": ("start_date", "trip"),
},
),
migrations.AddField(
model_name="trip",
name="gtfs_feed",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
migrations.AddField(
model_name="trip",
name="route",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="trips",
to="gtfs.route",
verbose_name="Route",
),
),
migrations.AddField(
model_name="trip",
name="service",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="trips",
to="gtfs.calendar",
verbose_name="Service",
),
),
migrations.AddField(
model_name="stoptime",
name="trip",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="stop_times",
to="gtfs.trip",
verbose_name="Trip",
),
),
migrations.CreateModel(
name="CalendarDate",
fields=[
(
"id",
models.CharField(
max_length=255,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateField(verbose_name="Date")),
(
"exception_type",
models.IntegerField(
choices=[(1, "Added"), (2, "Removed")],
verbose_name="Exception type",
),
),
(
"service",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="dates",
to="gtfs.calendar",
verbose_name="Service",
),
),
],
options={
"verbose_name": "Calendar date",
"verbose_name_plural": "Calendar dates",
"ordering": ("id",),
"indexes": [
models.Index(
fields=["service"], name="gtfs_calend_service_211472_idx"
),
models.Index(fields=["date"], name="gtfs_calend_date_e90040_idx"),
],
},
),
migrations.CreateModel(
name="FeedInfo",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"publisher_name",
models.CharField(
max_length=255, verbose_name="Feed publisher name"
),
),
("publisher_url", models.URLField(verbose_name="Feed publisher URL")),
(
"lang",
models.CharField(max_length=255, verbose_name="Feed language"),
),
("start_date", models.DateField(verbose_name="Feed start date")),
("end_date", models.DateField(verbose_name="Feed end date")),
(
"version",
models.CharField(max_length=255, verbose_name="Feed version"),
),
(
"gtfs_feed",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="gtfs.gtfsfeed",
verbose_name="GTFS feed",
),
),
],
options={
"verbose_name": "Feed info",
"verbose_name_plural": "Feed infos",
"ordering": ("publisher_name",),
"indexes": [
models.Index(
fields=["gtfs_feed"], name="gtfs_feedin_gtfs_fe_73554b_idx"
)
],
},
),
migrations.AddIndex(
model_name="calendar",
index=models.Index(
fields=["gtfs_feed"], name="gtfs_calend_gtfs_fe_ff03d1_idx"
),
),
migrations.AddIndex(
model_name="agency",
index=models.Index(fields=["name"], name="gtfs_agency_name_a6dd2b_idx"),
),
migrations.AddIndex(
model_name="agency",
index=models.Index(
fields=["gtfs_feed"], name="gtfs_agency_gtfs_fe_86414c_idx"
),
),
migrations.AddIndex(
model_name="route",
index=models.Index(
fields=["gtfs_feed"], name="gtfs_route_gtfs_fe_c6ac59_idx"
),
),
migrations.AddIndex(
model_name="stop",
index=models.Index(fields=["name"], name="gtfs_stop_name_1c87d7_idx"),
),
migrations.AddIndex(
model_name="stop",
index=models.Index(fields=["code"], name="gtfs_stop_code_5f4ebc_idx"),
),
migrations.AddIndex(
model_name="stop",
index=models.Index(
fields=["gtfs_feed"], name="gtfs_stop_gtfs_fe_0e17d6_idx"
),
),
migrations.AddIndex(
model_name="tripupdate",
index=models.Index(fields=["trip"], name="gtfs_tripup_trip_id_b3ee0e_idx"),
),
migrations.AlterUniqueTogether(
name="tripupdate",
unique_together={("trip", "start_date", "start_time")},
),
migrations.AddField(
model_name="stoptimeupdate",
name="trip_update",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="stop_time_updates",
to="gtfs.tripupdate",
verbose_name="Trip update",
),
),
migrations.AddIndex(
model_name="trip",
index=models.Index(fields=["route"], name="gtfs_trip_route_i_6d85d9_idx"),
),
migrations.AddIndex(
model_name="trip",
index=models.Index(
fields=["gtfs_feed"], name="gtfs_trip_gtfs_fe_e63eac_idx"
),
),
migrations.AddIndex(
model_name="stoptime",
index=models.Index(fields=["stop"], name="gtfs_stopti_stop_id_64a4e3_idx"),
),
migrations.AddIndex(
model_name="stoptime",
index=models.Index(fields=["trip"], name="gtfs_stopti_trip_id_bec7fe_idx"),
),
migrations.AddIndex(
model_name="stoptimeupdate",
index=models.Index(
fields=["trip_update"], name="gtfs_stopti_trip_up_ffe901_idx"
),
),
migrations.AddIndex(
model_name="stoptimeupdate",
index=models.Index(
fields=["stop_time"], name="gtfs_stopti_stop_ti_4f2c63_idx"
),
),
migrations.AlterUniqueTogether(
name="stoptimeupdate",
unique_together={("trip_update", "stop_time")},
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-12 09:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("gtfs", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="stop",
name="parent_station",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="children",
to="gtfs.stop",
verbose_name="Parent station",
),
),
]

View File

@ -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",
),
),
]

View File

@ -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"
),
),
]

View File

@ -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"
),
),
]

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
from trainvel.gtfs.models import GTFSFeed
def keep_gtfs_feed_modification_date(instance: GTFSFeed, raw: bool = True, **_kwargs):
"""
Keep the last_modified and etag fields from the existing GTFSFeed instance when saving a new one.
"""
if raw and GTFSFeed.objects.filter(pk=instance.pk).exists():
# If a GTFSFeed instance is being saved from a raw query,
# we want to keep the last_modified and etag fields from the existing instance.
# This is useful when loading initial data from a fixture, for example.
old_instance = GTFSFeed.objects.get(pk=instance.pk)
instance.last_modified = old_instance.last_modified
instance.etag = old_instance.etag

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,163 +0,0 @@
"""
Django settings for trainvel project.
Generated by 'django-admin startproject' using Django 5.0.1.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
from corsheaders.defaults import default_headers
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "CHANGE ME"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
"django_extensions",
"django_filters",
"rest_framework",
"trainvel.api",
"trainvel.core",
"trainvel.gtfs",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]
CORS_ALLOW_HEADERS = (
*default_headers,
"If-Modified-Since",
'Cache-Control',
)
ROOT_URLCONF = "trainvel.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "trainvel.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = "fr-fr"
TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = "api-static/"
STATIC_ROOT = BASE_DIR / "static_files"
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 20,
}
STATION_RADIUS = 300
OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME"
try:
from .settings_local import *
except ImportError:
pass

View File

@ -1,22 +0,0 @@
SECRET_KEY = "CHANGE ME"
DEBUG = False
ALLOWED_HOSTS = ['sncf.emy.lu']
CORS_ALLOWED_ORIGINS = [
"https://sncf.emy.lu",
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "trainvel",
"USER": "trainvel",
"PASSWORD": "CHANGE ME",
"HOST": "localhost",
"PORT": "5432",
}
}
OPENTRANSPORTDATA_SWISS_TOKEN = "CHANGE ME"

View File

@ -1,45 +0,0 @@
"""
URL configuration for trainvel project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from trainvel.api.views import AgencyViewSet, StopViewSet, RouteViewSet, StationViewSet, TripViewSet, StopTimeViewSet, \
CalendarViewSet, CalendarDateViewSet, TransferViewSet, FeedInfoViewSet, NextDeparturesViewSet, NextArrivalsViewSet, \
TripUpdateViewSet, StopTimeUpdateViewSet
router = routers.DefaultRouter()
router.register("core/station", StationViewSet)
router.register("gtfs/agency", AgencyViewSet)
router.register("gtfs/stop", StopViewSet)
router.register("gtfs/route", RouteViewSet)
router.register("gtfs/trip", TripViewSet)
router.register("gtfs/stop_time", StopTimeViewSet)
router.register("gtfs/calendar", CalendarViewSet)
router.register("gtfs/calendar_date", CalendarDateViewSet)
router.register("gtfs/transfer", TransferViewSet)
router.register("gtfs/feed_info", FeedInfoViewSet)
router.register("gtfs-rt/trip_update", TripUpdateViewSet)
router.register("gtfs-rt/stop_time_update", StopTimeUpdateViewSet)
router.register("station/next_departures", NextDeparturesViewSet, basename="next_departures")
router.register("station/next_arrivals", NextArrivalsViewSet, basename="next_arrivals")
urlpatterns = [
path("admin/", admin.site.urls, name="admin"),
path("api/", include(router.urls)),
path("api-auth/", include('rest_framework.urls', namespace='rest_framework')),
]

View File

@ -1,16 +0,0 @@
"""
WSGI config for trainvel project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "trainvel.settings")
application = get_wsgi_application()