Compare commits
No commits in common. "1c99c5ca4707f0b5b4cd0e678a64b28571d610c1" and "bc23d63c43367586d4ac8a4abeb06e3ad691d4c0" have entirely different histories.
1c99c5ca47
...
bc23d63c43
65
.gitignore
vendored
@ -1,26 +1,53 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# Byte-compiled / optimized / DLL files
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.swp
|
||||||
|
*.egg-info
|
||||||
|
_build
|
||||||
|
.tox
|
||||||
|
.coverage
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# PyCharm project settings
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# VSCode project settings
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# dependencies
|
# Local data
|
||||||
/node_modules
|
secrets.py
|
||||||
/.pnp
|
settings_local.py
|
||||||
.pnp.js
|
*.log
|
||||||
|
*.txt
|
||||||
|
media/
|
||||||
|
output/
|
||||||
|
/static/
|
||||||
|
/static_files/
|
||||||
|
|
||||||
# testing
|
# Virtualenv
|
||||||
/coverage
|
.env/
|
||||||
|
env/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
# production
|
node_modules/
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/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()
|
@ -1,3 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
8
requirements.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
|
@ -1,72 +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 {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;
|
|
@ -1,310 +0,0 @@
|
|||||||
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;
|
|
23
trainvel-front/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 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*
|
@ -46,5 +46,6 @@
|
|||||||
"last 1 firefox version",
|
"last 1 firefox version",
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"proxy": "http://localhost:8000"
|
||||||
}
|
}
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
@ -6,9 +6,10 @@ import {frFR, LocalizationProvider} from "@mui/x-date-pickers"
|
|||||||
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
|
import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs"
|
||||||
import 'dayjs/locale/fr'
|
import 'dayjs/locale/fr'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
|
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
||||||
import Home from "./Home"
|
import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister";
|
||||||
import dayjs from "dayjs"
|
import {persistQueryClient} from "@tanstack/react-query-persist-client";
|
||||||
|
import Home from "./Home";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -17,7 +18,7 @@ function App() {
|
|||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/station/:theme/:stationId",
|
path: "/station/:theme/:stationSlug",
|
||||||
element: <Station />
|
element: <Station />
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -53,7 +54,14 @@ function App() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
dayjs.locale('fr')
|
const localStoragePersister = createSyncStoragePersister({
|
||||||
|
storage: window.localStorage,
|
||||||
|
})
|
||||||
|
|
||||||
|
persistQueryClient({
|
||||||
|
queryClient,
|
||||||
|
persister: localStoragePersister,
|
||||||
|
})
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
@ -1,8 +1,9 @@
|
|||||||
import {Autocomplete, TextField} from "@mui/material";
|
import {Autocomplete, TextField} from "@mui/material";
|
||||||
import {useState} from "react";
|
import {useRef, useState} from "react";
|
||||||
|
|
||||||
function AutocompleteStation(params) {
|
function AutocompleteStation(params) {
|
||||||
const [options, setOptions] = useState([])
|
const [options, setOptions] = useState([])
|
||||||
|
const previousController = useRef()
|
||||||
|
|
||||||
function onInputChange(event, value) {
|
function onInputChange(event, value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@ -10,9 +11,17 @@ function AutocompleteStation(params) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`)
|
if (previousController.current)
|
||||||
|
previousController.current.abort()
|
||||||
|
|
||||||
|
const controller = new AbortController()
|
||||||
|
const signal = controller.signal
|
||||||
|
previousController.current = controller
|
||||||
|
fetch("/api/core/station/?search=" + value, {signal})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
|
.then(data => data.results)
|
||||||
.then(setOptions)
|
.then(setOptions)
|
||||||
|
.catch()
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@ -20,7 +29,7 @@ function AutocompleteStation(params) {
|
|||||||
id="stop"
|
id="stop"
|
||||||
options={options}
|
options={options}
|
||||||
onInputChange={onInputChange}
|
onInputChange={onInputChange}
|
||||||
filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))}
|
filterOptions={(x) => x}
|
||||||
getOptionKey={option => option.id}
|
getOptionKey={option => option.id}
|
||||||
getOptionLabel={option => option.name}
|
getOptionLabel={option => option.name}
|
||||||
groupBy={option => getOptionGroup(option)}
|
groupBy={option => getOptionGroup(option)}
|
||||||
@ -31,7 +40,7 @@ function AutocompleteStation(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getOptionGroup(option) {
|
function getOptionGroup(option) {
|
||||||
return option.id.split('_')[0]
|
return option.country
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AutocompleteStation;
|
export default AutocompleteStation;
|
@ -5,7 +5,7 @@ function Home() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
function onStationSelected(event, station) {
|
function onStationSelected(event, station) {
|
||||||
navigate(`/station/sncf/${station.id}/`)
|
navigate(`/station/sncf/${station.slug}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
79
trainvel-front/src/Station.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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;
|
364
trainvel-front/src/TrainsTable.js
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
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;
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, {useMemo} from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
0
trainvel/__init__.py
Normal file
0
trainvel/api/__init__.py
Normal file
6
trainvel/api/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "trainvel.api"
|
91
trainvel/api/serializers.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
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__'
|
3
trainvel/api/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
357
trainvel/api/views.py
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
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()
|
16
trainvel/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
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()
|
0
trainvel/core/__init__.py
Normal file
23
trainvel/core/admin.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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]
|
8
trainvel/core/apps.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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")
|
325
trainvel/core/locale/fr/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
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"
|
0
trainvel/core/management/__init__.py
Normal file
0
trainvel/core/management/commands/__init__.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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;")
|
603
trainvel/core/migrations/0001_initial.py
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
# 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",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,40 @@
|
|||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
trainvel/core/migrations/__init__.py
Normal file
519
trainvel/core/models.py
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
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")
|
3
trainvel/core/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
trainvel/core/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
0
trainvel/gtfs/__init__.py
Normal file
166
trainvel/gtfs/admin.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
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',)
|
14
trainvel/gtfs/apps.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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")
|
180
trainvel/gtfs/fixtures/gtfs_feeds.json
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
1035
trainvel/gtfs/gtfs-realtime.proto
Normal file
89
trainvel/gtfs/gtfs_realtime_pb2.py
Normal file
436
trainvel/gtfs/gtfs_realtime_pb2.pyi
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
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: ...
|
825
trainvel/gtfs/locale/fr/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,825 @@
|
|||||||
|
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"
|
0
trainvel/gtfs/management/__init__.py
Normal file
0
trainvel/gtfs/management/commands/__init__.py
Normal file
20
trainvel/gtfs/management/commands/augment_data.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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()
|
430
trainvel/gtfs/management/commands/update_trainvel_gtfs.py
Normal file
@ -0,0 +1,430 @@
|
|||||||
|
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),
|
||||||
|
)
|
||||||
|
)
|
204
trainvel/gtfs/management/commands/update_trainvel_gtfs_rt.py
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
911
trainvel/gtfs/migrations/0001_initial.py
Normal file
@ -0,0 +1,911 @@
|
|||||||
|
# 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")},
|
||||||
|
),
|
||||||
|
]
|
26
trainvel/gtfs/migrations/0002_alter_stop_parent_station.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,72 @@
|
|||||||
|
# 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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,39 @@
|
|||||||
|
# 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,73 @@
|
|||||||
|
# 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
trainvel/gtfs/migrations/__init__.py
Normal file
1068
trainvel/gtfs/models.py
Normal file
14
trainvel/gtfs/signals.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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
|
3
trainvel/gtfs/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
163
trainvel/settings.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
"""
|
||||||
|
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
|
22
trainvel/settings_local_example.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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"
|
45
trainvel/urls.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"""
|
||||||
|
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')),
|
||||||
|
]
|
16
trainvel/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
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()
|