Compare commits
35 Commits
2277d2fe64
...
main
Author | SHA1 | Date | |
---|---|---|---|
0a5bec6c4b
|
|||
6389406744
|
|||
41441a7803
|
|||
f0964d8fb7
|
|||
e58ad34e43
|
|||
af61173e9d
|
|||
2e5b5970a9
|
|||
ec9ac8d7ab
|
|||
1c99c5ca47
|
|||
e3fd6a7f88
|
|||
036e1604bd
|
|||
bc23d63c43
|
|||
bd8d39fc1e
|
|||
a4a8cd9e9f
|
|||
0d622302ac
|
|||
c60a105b2d
|
|||
b65dc10bc6
|
|||
2b6523c728
|
|||
0ab4aa7976
|
|||
68b8606688
|
|||
15239117f5
|
|||
7d9b7d90cd
|
|||
b85a1b7734
|
|||
eade9e84de
|
|||
7ed0924108
|
|||
86d274ac84
|
|||
368f07da32
|
|||
070849c427
|
|||
735191947d
|
|||
0486234b9f
|
|||
6884084f2a
|
|||
b4f61308ab
|
|||
12598b88cc
|
|||
11949228ee
|
|||
820fc0cc19
|
64
.gitignore
vendored
@ -1,52 +1,26 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring 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
|
||||||
|
|
||||||
# Local data
|
# dependencies
|
||||||
secrets.py
|
/node_modules
|
||||||
settings_local.py
|
/.pnp
|
||||||
*.log
|
.pnp.js
|
||||||
media/
|
|
||||||
output/
|
|
||||||
/static/
|
|
||||||
/static_files/
|
|
||||||
|
|
||||||
# Virtualenv
|
# testing
|
||||||
.env/
|
/coverage
|
||||||
env/
|
|
||||||
.venv/
|
|
||||||
venv/
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
node_modules/
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
22
manage.py
@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sncf.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()
|
|
11129
sncf-station/package-lock.json → package-lock.json
generated
54
package.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "trainvel-front",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
|
"@mapbox/polyline": "^1.2.1",
|
||||||
|
"@mui/icons-material": "^6.1.6",
|
||||||
|
"@mui/material": "^6.1.6",
|
||||||
|
"@mui/x-date-pickers": "^7.22.2",
|
||||||
|
"@tanstack/react-query": "^5.59.20",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.0.1",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
|
"@turf/rhumb-bearing": "^7.1.0",
|
||||||
|
"@turf/rhumb-distance": "^7.1.0",
|
||||||
|
"@types/leaflet": "^1.9.14",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-leaflet": "^4.2.1",
|
||||||
|
"react-router-dom": "^6.28.0",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"react-transition-group": "^4.4.5",
|
||||||
|
"sass": "^1.80.6",
|
||||||
|
"web-vitals": "^4.2.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
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 |
@ -7,7 +7,7 @@
|
|||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Écrans en gare affichant les horaires des trains des gares SNCF."
|
content="Écrans en gare affichant les horaires des trains des gares."
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
|
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 |
23
sncf-station/.gitignore
vendored
@ -1,23 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "sncf-station",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@emotion/react": "^11.11.3",
|
|
||||||
"@emotion/styled": "^11.11.0",
|
|
||||||
"@mui/material": "^5.15.6",
|
|
||||||
"@mui/x-date-pickers": "^6.19.2",
|
|
||||||
"@tanstack/query-sync-storage-persister": "^5.18.0",
|
|
||||||
"@tanstack/react-query": "^5.18.0",
|
|
||||||
"@tanstack/react-query-persist-client": "^5.18.0",
|
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
|
||||||
"@testing-library/react": "^13.4.0",
|
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"dayjs": "^1.11.10",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-router-dom": "^6.21.3",
|
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"react-transition-group": "^4.4.5",
|
|
||||||
"sass": "^1.70.0",
|
|
||||||
"web-vitals": "^2.1.4"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test",
|
|
||||||
"eject": "react-scripts eject"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"proxy": "http://localhost:8000"
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
import {Autocomplete, TextField} from "@mui/material";
|
|
||||||
import {useRef, useState} from "react";
|
|
||||||
|
|
||||||
function AutocompleteStop(params) {
|
|
||||||
const [options, setOptions] = useState([])
|
|
||||||
const previousController = useRef()
|
|
||||||
|
|
||||||
function onInputChange(event, value) {
|
|
||||||
if (!value) {
|
|
||||||
setOptions([])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousController.current)
|
|
||||||
previousController.current.abort()
|
|
||||||
|
|
||||||
const controller = new AbortController()
|
|
||||||
const signal = controller.signal
|
|
||||||
previousController.current = controller
|
|
||||||
fetch("/api/gtfs/stop/?location_type=1&search=" + value, {signal})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => data.results)
|
|
||||||
.then(setOptions)
|
|
||||||
.catch()
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Autocomplete
|
|
||||||
id="stop"
|
|
||||||
options={options}
|
|
||||||
onInputChange={onInputChange}
|
|
||||||
filterOptions={(x) => x}
|
|
||||||
getOptionKey={option => option.id}
|
|
||||||
getOptionLabel={option => option.name}
|
|
||||||
groupBy={option => getOptionGroup(option)}
|
|
||||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
|
||||||
renderInput={(params) => <TextField {...params} label="Arrêt" />}
|
|
||||||
{...params} />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOptionGroup(option) {
|
|
||||||
switch (option.transport_type) {
|
|
||||||
case "TGV":
|
|
||||||
case "IC":
|
|
||||||
case "TER":
|
|
||||||
return "TGV/TER/Intercités"
|
|
||||||
case "TN":
|
|
||||||
return "Transilien"
|
|
||||||
case "ES":
|
|
||||||
return "Eurostar"
|
|
||||||
case "TI":
|
|
||||||
return "Trenitalia France"
|
|
||||||
case "RENFE":
|
|
||||||
return "RENFE"
|
|
||||||
case "OBB":
|
|
||||||
return "ÖBB"
|
|
||||||
default:
|
|
||||||
return option.transport_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AutocompleteStop;
|
|
@ -1,77 +0,0 @@
|
|||||||
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
|
|
||||||
import TrainsTable from "./TrainsTable"
|
|
||||||
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 AutocompleteStop from "./AutocompleteStop";
|
|
||||||
|
|
||||||
function DateTimeSelector({stop, date, time}) {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
function onStationSelected(event, stop) {
|
|
||||||
if (stop !== null)
|
|
||||||
navigate(`/station/${stop.id}/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<Box component="form" display="flex" alignItems="center" sx={{'& .MuiTextField-root': { m: 1, width: '25ch' },}}>
|
|
||||||
<FormLabel>
|
|
||||||
Changer la gare recherchée :
|
|
||||||
</FormLabel>
|
|
||||||
<AutocompleteStop 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 {stopId} = 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 stopQuery = useQuery({
|
|
||||||
queryKey: ['stop', stopId],
|
|
||||||
queryFn: () => fetch(`/api/gtfs/stop/${stopId}/`)
|
|
||||||
.then(response => response.json()),
|
|
||||||
enabled: !!stopId,
|
|
||||||
})
|
|
||||||
const stop = stopQuery.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 {stop.name}</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<DateTimeSelector stop={stop} date={date} time={time} />
|
|
||||||
<TrainsTable stop={stop} date={date} time={time} tableType="departures" />
|
|
||||||
<TrainsTable stop={stop} date={date} time={time} tableType="arrivals" />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Station;
|
|
@ -1,360 +0,0 @@
|
|||||||
import {
|
|
||||||
Box,
|
|
||||||
styled,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Typography
|
|
||||||
} from "@mui/material"
|
|
||||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
|
||||||
import {useQueries, useQuery} from "@tanstack/react-query";
|
|
||||||
import {useCallback, useEffect, useMemo, useRef, useState} from "react";
|
|
||||||
|
|
||||||
const StyledTableRow = styled(TableRow)(({ theme, tabletype }) => ({
|
|
||||||
'tbody &:nth-of-type(odd)': {
|
|
||||||
backgroundColor: theme.palette.sncf[tabletype].light,
|
|
||||||
},
|
|
||||||
'th, &:nth-of-type(even)': {
|
|
||||||
backgroundColor: theme.palette.sncf[tabletype].dark,
|
|
||||||
},
|
|
||||||
// hide last border
|
|
||||||
'&:last-child td, &:last-child th': {
|
|
||||||
border: 0,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function TrainsTable({stop, date, time, tableType}) {
|
|
||||||
return <>
|
|
||||||
<TableContainer>
|
|
||||||
<Table>
|
|
||||||
<TrainsTableHeader tableType={tableType} />
|
|
||||||
<TrainsTableBody stop={stop} 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({stop, 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}/?stop_id=${stop.id}&date=${date}&time=${time}&offset=${0}&limit=${20}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => data.results)
|
|
||||||
.then(data => [...data])
|
|
||||||
}, [stop.id, date, time, tableType])
|
|
||||||
|
|
||||||
const trainsQuery = useQuery({
|
|
||||||
queryKey: ['trains', stop.id, tableType],
|
|
||||||
queryFn: updateTrains,
|
|
||||||
enabled: !!stop.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/?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.transport_type) {
|
|
||||||
case "TGV":
|
|
||||||
case "TER":
|
|
||||||
case "IC":
|
|
||||||
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 "TN":
|
|
||||||
return route.short_name
|
|
||||||
case "ES":
|
|
||||||
return "Eurostar"
|
|
||||||
case "TI":
|
|
||||||
return "Trenitalia"
|
|
||||||
case "RENFE":
|
|
||||||
return "RENFE"
|
|
||||||
case "OBB":
|
|
||||||
if (trip.short_name.startsWith("NJ"))
|
|
||||||
return "NJ"
|
|
||||||
return "ÖBB"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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":
|
|
||||||
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,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ApiConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "sncf.api"
|
|
@ -1,76 +0,0 @@
|
|||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
|
||||||
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
|
||||||
|
|
||||||
|
|
||||||
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__'
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -1,301 +0,0 @@
|
|||||||
from datetime import datetime, timedelta, date
|
|
||||||
|
|
||||||
from django.db.models import F, Q, Value, When, Case, Exists, OuterRef
|
|
||||||
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 sncf.api.serializers import AgencySerializer, StopSerializer, RouteSerializer, TripSerializer, \
|
|
||||||
StopTimeSerializer, CalendarSerializer, CalendarDateSerializer, TransferSerializer, \
|
|
||||||
FeedInfoSerializer, TripUpdateSerializer, StopTimeUpdateSerializer
|
|
||||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
|
||||||
Transfer, FeedInfo, TripUpdate, StopTimeUpdate
|
|
||||||
|
|
||||||
CACHE_CONTROL = cache_control(max_age=7200)
|
|
||||||
LAST_MODIFIED = last_modified(lambda *args, **kwargs: datetime.fromisoformat(
|
|
||||||
FeedInfo.objects.get(publisher_name="SNCF_default").version))
|
|
||||||
LOOKUP_VALUE_REGEX = r"[\w.: |-]+"
|
|
||||||
|
|
||||||
|
|
||||||
@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
|
|
||||||
filter_backends = [DjangoFilterBackend]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
stop_id = self.request.query_params.get('stop_id', None)
|
|
||||||
stop_name = self.request.query_params.get('stop_name', None)
|
|
||||||
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
|
||||||
query_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 stop_id:
|
|
||||||
stop = Stop.objects.get(id=stop_id)
|
|
||||||
stops = Stop.objects.filter(Q(id=stop_id)
|
|
||||||
| Q(parent_station=stop_id))
|
|
||||||
if stop.location_type == 0 and stop.parent_station_id is not None:
|
|
||||||
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
|
|
||||||
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
|
|
||||||
elif stop_name:
|
|
||||||
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
|
|
||||||
stop_filter = Q(stop__in=stops)
|
|
||||||
|
|
||||||
def calendar_filter(d: date):
|
|
||||||
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) \
|
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
|
||||||
.filter(departure_time_real__gte=query_time) \
|
|
||||||
.filter(Q(pickup_type=0) | 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) \
|
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
|
||||||
.filter(departure_time_real__gte=time_yesterday) \
|
|
||||||
.filter(Q(pickup_type=0) | 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) \
|
|
||||||
.annotate(departure_time_real=departure_time_real(query_date)) \
|
|
||||||
.filter(departure_time_real__gte=timedelta(0)) \
|
|
||||||
.filter(Q(pickup_type=0) | 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()
|
|
||||||
|
|
||||||
stop_id = self.request.query_params.get('stop_id', None)
|
|
||||||
stop_name = self.request.query_params.get('stop_name', None)
|
|
||||||
query_date = date.fromisoformat(self.request.query_params.get('date', now.date().isoformat()))
|
|
||||||
query_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 stop_id:
|
|
||||||
stop = Stop.objects.get(id=stop_id)
|
|
||||||
stops = Stop.objects.filter(Q(id=stop_id)
|
|
||||||
| Q(parent_station=stop_id))
|
|
||||||
if stop.location_type == 0 and stop.parent_station_id is not None:
|
|
||||||
stops |= Stop.objects.filter(parent_station=stop.parent_station_id)
|
|
||||||
stop_filter = Q(stop__in=stops.values_list('id', flat=True))
|
|
||||||
elif stop_name:
|
|
||||||
stops = Stop.objects.filter(name__iexact=stop_name).values_list('id', flat=True)
|
|
||||||
stop_filter = Q(stop__in=stops)
|
|
||||||
|
|
||||||
def calendar_filter(d: date):
|
|
||||||
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) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(query_date)) \
|
|
||||||
.filter(arrival_time_real__gte=query_time) \
|
|
||||||
.filter(Q(drop_off_type=0) | 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) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(yesterday)) \
|
|
||||||
.filter(arrival_time_real__gte=time_yesterday) \
|
|
||||||
.filter(Q(drop_off_type=0) | 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) \
|
|
||||||
.annotate(arrival_time_real=arrival_time_real(tomorrow)) \
|
|
||||||
.filter(arrival_time_real__gte=timedelta(0)) \
|
|
||||||
.filter(Q(drop_off_type=0) | 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
sncf/asgi.py
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI config for sncf 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", "sncf.settings")
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
157
sncf/settings.py
@ -1,157 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for sncf 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_filters",
|
|
||||||
"rest_framework",
|
|
||||||
|
|
||||||
"sncf.api",
|
|
||||||
"sncfgtfs",
|
|
||||||
]
|
|
||||||
|
|
||||||
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 = "sncf.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 = "sncf.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,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from .settings_local import *
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
@ -1,20 +0,0 @@
|
|||||||
SECRET_KEY = "CHANGE ME"
|
|
||||||
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['sncf.emy.lu']
|
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
|
||||||
"https://sncf.emy.lu",
|
|
||||||
]
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "sncf",
|
|
||||||
"USER": "sncf",
|
|
||||||
"PASSWORD": "CHANGE ME",
|
|
||||||
"HOST": "localhost",
|
|
||||||
"PORT": "5432",
|
|
||||||
}
|
|
||||||
}
|
|
44
sncf/urls.py
@ -1,44 +0,0 @@
|
|||||||
"""
|
|
||||||
URL configuration for sncf 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 sncf.api.views import AgencyViewSet, StopViewSet, RouteViewSet, TripViewSet, StopTimeViewSet, \
|
|
||||||
CalendarViewSet, CalendarDateViewSet, TransferViewSet, FeedInfoViewSet, NextDeparturesViewSet, NextArrivalsViewSet, \
|
|
||||||
TripUpdateViewSet, StopTimeUpdateViewSet
|
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
|
||||||
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
sncf/wsgi.py
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for sncf 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", "sncf.settings")
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
@ -1,138 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
|
||||||
Transfer, FeedInfo, StopTimeUpdate, TripUpdate
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarDateInline(admin.TabularInline):
|
|
||||||
model = CalendarDate
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class TripInline(admin.TabularInline):
|
|
||||||
model = Trip
|
|
||||||
extra = 0
|
|
||||||
autocomplete_fields = ('route', 'service',)
|
|
||||||
show_change_link = True
|
|
||||||
ordering = ('service',)
|
|
||||||
|
|
||||||
|
|
||||||
class StopTimeInline(admin.TabularInline):
|
|
||||||
model = StopTime
|
|
||||||
extra = 0
|
|
||||||
autocomplete_fields = ('stop',)
|
|
||||||
show_change_link = True
|
|
||||||
ordering = ('stop_sequence',)
|
|
||||||
|
|
||||||
|
|
||||||
class TripUpdateInline(admin.StackedInline):
|
|
||||||
model = TripUpdate
|
|
||||||
extra = 0
|
|
||||||
autocomplete_fields = ('trip',)
|
|
||||||
|
|
||||||
|
|
||||||
class StopTimeUpdateInline(admin.StackedInline):
|
|
||||||
model = StopTimeUpdate
|
|
||||||
extra = 0
|
|
||||||
autocomplete_fields = ('trip_update', 'stop_time',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Agency)
|
|
||||||
class AgencyAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'id', 'url', 'timezone',)
|
|
||||||
search_fields = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Stop)
|
|
||||||
class StopAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'id', 'lat', 'lon', 'location_type',)
|
|
||||||
list_filter = ('location_type', 'transport_type',)
|
|
||||||
search_fields = ('name', 'id',)
|
|
||||||
ordering = ('name',)
|
|
||||||
autocomplete_fields = ('parent_station',)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Route)
|
|
||||||
class RouteAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('short_name', 'long_name', 'id', 'type',)
|
|
||||||
list_filter = ('transport_type', 'type', 'agency',)
|
|
||||||
search_fields = ('long_name', 'short_name', 'id',)
|
|
||||||
ordering = ('long_name',)
|
|
||||||
autocomplete_fields = ('agency',)
|
|
||||||
inlines = (TripInline,)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Trip)
|
|
||||||
class TripAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'route', 'service', 'headsign', 'direction_id',)
|
|
||||||
list_filter = ('direction_id', 'route__transport_type',)
|
|
||||||
search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',)
|
|
||||||
ordering = ('route', 'service',)
|
|
||||||
autocomplete_fields = ('route', 'service',)
|
|
||||||
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__transport_type',)
|
|
||||||
search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',)
|
|
||||||
ordering = ('trip', 'stop_sequence',)
|
|
||||||
autocomplete_fields = ('trip', 'stop',)
|
|
||||||
inlines = (StopTimeUpdateInline,)
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Calendar)
|
|
||||||
class CalendarAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'transport_type', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
|
|
||||||
'saturday', 'sunday', 'start_date', 'end_date',)
|
|
||||||
list_filter = ('transport_type', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
|
|
||||||
'start_date', 'end_date',)
|
|
||||||
search_fields = ('id', 'start_date', 'end_date',)
|
|
||||||
ordering = ('transport_type', '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__transport_type',)
|
|
||||||
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',)
|
|
||||||
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',)
|
|
||||||
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',)
|
|
@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class SncfgtfsConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "sncfgtfs"
|
|
@ -1,436 +0,0 @@
|
|||||||
from google.protobuf.internal import containers as _containers
|
|
||||||
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
|
|
||||||
from google.protobuf.internal import python_message as _python_message
|
|
||||||
from google.protobuf import descriptor as _descriptor
|
|
||||||
from google.protobuf import message as _message
|
|
||||||
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union
|
|
||||||
|
|
||||||
DESCRIPTOR: _descriptor.FileDescriptor
|
|
||||||
|
|
||||||
class FeedMessage(_message.Message):
|
|
||||||
__slots__ = ("header", "entity")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
HEADER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ENTITY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
header: FeedHeader
|
|
||||||
entity: _containers.RepeatedCompositeFieldContainer[FeedEntity]
|
|
||||||
def __init__(self, header: _Optional[_Union[FeedHeader, _Mapping]] = ..., entity: _Optional[_Iterable[_Union[FeedEntity, _Mapping]]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class FeedHeader(_message.Message):
|
|
||||||
__slots__ = ("gtfs_realtime_version", "incrementality", "timestamp")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class Incrementality(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
FULL_DATASET: _ClassVar[FeedHeader.Incrementality]
|
|
||||||
DIFFERENTIAL: _ClassVar[FeedHeader.Incrementality]
|
|
||||||
FULL_DATASET: FeedHeader.Incrementality
|
|
||||||
DIFFERENTIAL: FeedHeader.Incrementality
|
|
||||||
GTFS_REALTIME_VERSION_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
INCREMENTALITY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
gtfs_realtime_version: str
|
|
||||||
incrementality: FeedHeader.Incrementality
|
|
||||||
timestamp: int
|
|
||||||
def __init__(self, gtfs_realtime_version: _Optional[str] = ..., incrementality: _Optional[_Union[FeedHeader.Incrementality, str]] = ..., timestamp: _Optional[int] = ...) -> None: ...
|
|
||||||
|
|
||||||
class FeedEntity(_message.Message):
|
|
||||||
__slots__ = ("id", "is_deleted", "trip_update", "vehicle", "alert", "shape")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
IS_DELETED_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TRIP_UPDATE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
VEHICLE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ALERT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SHAPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
id: str
|
|
||||||
is_deleted: bool
|
|
||||||
trip_update: TripUpdate
|
|
||||||
vehicle: VehiclePosition
|
|
||||||
alert: Alert
|
|
||||||
shape: Shape
|
|
||||||
def __init__(self, id: _Optional[str] = ..., is_deleted: bool = ..., trip_update: _Optional[_Union[TripUpdate, _Mapping]] = ..., vehicle: _Optional[_Union[VehiclePosition, _Mapping]] = ..., alert: _Optional[_Union[Alert, _Mapping]] = ..., shape: _Optional[_Union[Shape, _Mapping]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TripUpdate(_message.Message):
|
|
||||||
__slots__ = ("trip", "vehicle", "stop_time_update", "timestamp", "delay", "trip_properties")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class StopTimeEvent(_message.Message):
|
|
||||||
__slots__ = ("delay", "time", "uncertainty")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
DELAY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TIME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
UNCERTAINTY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
delay: int
|
|
||||||
time: int
|
|
||||||
uncertainty: int
|
|
||||||
def __init__(self, delay: _Optional[int] = ..., time: _Optional[int] = ..., uncertainty: _Optional[int] = ...) -> None: ...
|
|
||||||
class StopTimeUpdate(_message.Message):
|
|
||||||
__slots__ = ("stop_sequence", "stop_id", "arrival", "departure", "departure_occupancy_status", "schedule_relationship", "stop_time_properties")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
SCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
|
|
||||||
SKIPPED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
|
|
||||||
NO_DATA: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
|
|
||||||
UNSCHEDULED: _ClassVar[TripUpdate.StopTimeUpdate.ScheduleRelationship]
|
|
||||||
SCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship
|
|
||||||
SKIPPED: TripUpdate.StopTimeUpdate.ScheduleRelationship
|
|
||||||
NO_DATA: TripUpdate.StopTimeUpdate.ScheduleRelationship
|
|
||||||
UNSCHEDULED: TripUpdate.StopTimeUpdate.ScheduleRelationship
|
|
||||||
class StopTimeProperties(_message.Message):
|
|
||||||
__slots__ = ("assigned_stop_id",)
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
ASSIGNED_STOP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
assigned_stop_id: str
|
|
||||||
def __init__(self, assigned_stop_id: _Optional[str] = ...) -> None: ...
|
|
||||||
STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ARRIVAL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DEPARTURE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DEPARTURE_OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_TIME_PROPERTIES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
stop_sequence: int
|
|
||||||
stop_id: str
|
|
||||||
arrival: TripUpdate.StopTimeEvent
|
|
||||||
departure: TripUpdate.StopTimeEvent
|
|
||||||
departure_occupancy_status: VehiclePosition.OccupancyStatus
|
|
||||||
schedule_relationship: TripUpdate.StopTimeUpdate.ScheduleRelationship
|
|
||||||
stop_time_properties: TripUpdate.StopTimeUpdate.StopTimeProperties
|
|
||||||
def __init__(self, stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., arrival: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure: _Optional[_Union[TripUpdate.StopTimeEvent, _Mapping]] = ..., departure_occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., schedule_relationship: _Optional[_Union[TripUpdate.StopTimeUpdate.ScheduleRelationship, str]] = ..., stop_time_properties: _Optional[_Union[TripUpdate.StopTimeUpdate.StopTimeProperties, _Mapping]] = ...) -> None: ...
|
|
||||||
class TripProperties(_message.Message):
|
|
||||||
__slots__ = ("trip_id", "start_date", "start_time", "shape_id")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
TRIP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
START_DATE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
START_TIME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SHAPE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
trip_id: str
|
|
||||||
start_date: str
|
|
||||||
start_time: str
|
|
||||||
shape_id: str
|
|
||||||
def __init__(self, trip_id: _Optional[str] = ..., start_date: _Optional[str] = ..., start_time: _Optional[str] = ..., shape_id: _Optional[str] = ...) -> None: ...
|
|
||||||
TRIP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
VEHICLE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_TIME_UPDATE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DELAY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TRIP_PROPERTIES_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
trip: TripDescriptor
|
|
||||||
vehicle: VehicleDescriptor
|
|
||||||
stop_time_update: _containers.RepeatedCompositeFieldContainer[TripUpdate.StopTimeUpdate]
|
|
||||||
timestamp: int
|
|
||||||
delay: int
|
|
||||||
trip_properties: TripUpdate.TripProperties
|
|
||||||
def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., stop_time_update: _Optional[_Iterable[_Union[TripUpdate.StopTimeUpdate, _Mapping]]] = ..., timestamp: _Optional[int] = ..., delay: _Optional[int] = ..., trip_properties: _Optional[_Union[TripUpdate.TripProperties, _Mapping]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class VehiclePosition(_message.Message):
|
|
||||||
__slots__ = ("trip", "vehicle", "position", "current_stop_sequence", "stop_id", "current_status", "timestamp", "congestion_level", "occupancy_status", "occupancy_percentage", "multi_carriage_details")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class VehicleStopStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
INCOMING_AT: _ClassVar[VehiclePosition.VehicleStopStatus]
|
|
||||||
STOPPED_AT: _ClassVar[VehiclePosition.VehicleStopStatus]
|
|
||||||
IN_TRANSIT_TO: _ClassVar[VehiclePosition.VehicleStopStatus]
|
|
||||||
INCOMING_AT: VehiclePosition.VehicleStopStatus
|
|
||||||
STOPPED_AT: VehiclePosition.VehicleStopStatus
|
|
||||||
IN_TRANSIT_TO: VehiclePosition.VehicleStopStatus
|
|
||||||
class CongestionLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
UNKNOWN_CONGESTION_LEVEL: _ClassVar[VehiclePosition.CongestionLevel]
|
|
||||||
RUNNING_SMOOTHLY: _ClassVar[VehiclePosition.CongestionLevel]
|
|
||||||
STOP_AND_GO: _ClassVar[VehiclePosition.CongestionLevel]
|
|
||||||
CONGESTION: _ClassVar[VehiclePosition.CongestionLevel]
|
|
||||||
SEVERE_CONGESTION: _ClassVar[VehiclePosition.CongestionLevel]
|
|
||||||
UNKNOWN_CONGESTION_LEVEL: VehiclePosition.CongestionLevel
|
|
||||||
RUNNING_SMOOTHLY: VehiclePosition.CongestionLevel
|
|
||||||
STOP_AND_GO: VehiclePosition.CongestionLevel
|
|
||||||
CONGESTION: VehiclePosition.CongestionLevel
|
|
||||||
SEVERE_CONGESTION: VehiclePosition.CongestionLevel
|
|
||||||
class OccupancyStatus(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
EMPTY: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
MANY_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
FEW_SEATS_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
CRUSHED_STANDING_ROOM_ONLY: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
FULL: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
NOT_ACCEPTING_PASSENGERS: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
NO_DATA_AVAILABLE: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
NOT_BOARDABLE: _ClassVar[VehiclePosition.OccupancyStatus]
|
|
||||||
EMPTY: VehiclePosition.OccupancyStatus
|
|
||||||
MANY_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus
|
|
||||||
FEW_SEATS_AVAILABLE: VehiclePosition.OccupancyStatus
|
|
||||||
STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus
|
|
||||||
CRUSHED_STANDING_ROOM_ONLY: VehiclePosition.OccupancyStatus
|
|
||||||
FULL: VehiclePosition.OccupancyStatus
|
|
||||||
NOT_ACCEPTING_PASSENGERS: VehiclePosition.OccupancyStatus
|
|
||||||
NO_DATA_AVAILABLE: VehiclePosition.OccupancyStatus
|
|
||||||
NOT_BOARDABLE: VehiclePosition.OccupancyStatus
|
|
||||||
class CarriageDetails(_message.Message):
|
|
||||||
__slots__ = ("id", "label", "occupancy_status", "occupancy_percentage", "carriage_sequence")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LABEL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CARRIAGE_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
id: str
|
|
||||||
label: str
|
|
||||||
occupancy_status: VehiclePosition.OccupancyStatus
|
|
||||||
occupancy_percentage: int
|
|
||||||
carriage_sequence: int
|
|
||||||
def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., carriage_sequence: _Optional[int] = ...) -> None: ...
|
|
||||||
TRIP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
VEHICLE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
POSITION_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CURRENT_STOP_SEQUENCE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CURRENT_STATUS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CONGESTION_LEVEL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
OCCUPANCY_STATUS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
OCCUPANCY_PERCENTAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
MULTI_CARRIAGE_DETAILS_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
trip: TripDescriptor
|
|
||||||
vehicle: VehicleDescriptor
|
|
||||||
position: Position
|
|
||||||
current_stop_sequence: int
|
|
||||||
stop_id: str
|
|
||||||
current_status: VehiclePosition.VehicleStopStatus
|
|
||||||
timestamp: int
|
|
||||||
congestion_level: VehiclePosition.CongestionLevel
|
|
||||||
occupancy_status: VehiclePosition.OccupancyStatus
|
|
||||||
occupancy_percentage: int
|
|
||||||
multi_carriage_details: _containers.RepeatedCompositeFieldContainer[VehiclePosition.CarriageDetails]
|
|
||||||
def __init__(self, trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., vehicle: _Optional[_Union[VehicleDescriptor, _Mapping]] = ..., position: _Optional[_Union[Position, _Mapping]] = ..., current_stop_sequence: _Optional[int] = ..., stop_id: _Optional[str] = ..., current_status: _Optional[_Union[VehiclePosition.VehicleStopStatus, str]] = ..., timestamp: _Optional[int] = ..., congestion_level: _Optional[_Union[VehiclePosition.CongestionLevel, str]] = ..., occupancy_status: _Optional[_Union[VehiclePosition.OccupancyStatus, str]] = ..., occupancy_percentage: _Optional[int] = ..., multi_carriage_details: _Optional[_Iterable[_Union[VehiclePosition.CarriageDetails, _Mapping]]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Alert(_message.Message):
|
|
||||||
__slots__ = ("active_period", "informed_entity", "cause", "effect", "url", "header_text", "description_text", "tts_header_text", "tts_description_text", "severity_level", "image", "image_alternative_text", "cause_detail", "effect_detail")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class Cause(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
UNKNOWN_CAUSE: _ClassVar[Alert.Cause]
|
|
||||||
OTHER_CAUSE: _ClassVar[Alert.Cause]
|
|
||||||
TECHNICAL_PROBLEM: _ClassVar[Alert.Cause]
|
|
||||||
STRIKE: _ClassVar[Alert.Cause]
|
|
||||||
DEMONSTRATION: _ClassVar[Alert.Cause]
|
|
||||||
ACCIDENT: _ClassVar[Alert.Cause]
|
|
||||||
HOLIDAY: _ClassVar[Alert.Cause]
|
|
||||||
WEATHER: _ClassVar[Alert.Cause]
|
|
||||||
MAINTENANCE: _ClassVar[Alert.Cause]
|
|
||||||
CONSTRUCTION: _ClassVar[Alert.Cause]
|
|
||||||
POLICE_ACTIVITY: _ClassVar[Alert.Cause]
|
|
||||||
MEDICAL_EMERGENCY: _ClassVar[Alert.Cause]
|
|
||||||
UNKNOWN_CAUSE: Alert.Cause
|
|
||||||
OTHER_CAUSE: Alert.Cause
|
|
||||||
TECHNICAL_PROBLEM: Alert.Cause
|
|
||||||
STRIKE: Alert.Cause
|
|
||||||
DEMONSTRATION: Alert.Cause
|
|
||||||
ACCIDENT: Alert.Cause
|
|
||||||
HOLIDAY: Alert.Cause
|
|
||||||
WEATHER: Alert.Cause
|
|
||||||
MAINTENANCE: Alert.Cause
|
|
||||||
CONSTRUCTION: Alert.Cause
|
|
||||||
POLICE_ACTIVITY: Alert.Cause
|
|
||||||
MEDICAL_EMERGENCY: Alert.Cause
|
|
||||||
class Effect(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
NO_SERVICE: _ClassVar[Alert.Effect]
|
|
||||||
REDUCED_SERVICE: _ClassVar[Alert.Effect]
|
|
||||||
SIGNIFICANT_DELAYS: _ClassVar[Alert.Effect]
|
|
||||||
DETOUR: _ClassVar[Alert.Effect]
|
|
||||||
ADDITIONAL_SERVICE: _ClassVar[Alert.Effect]
|
|
||||||
MODIFIED_SERVICE: _ClassVar[Alert.Effect]
|
|
||||||
OTHER_EFFECT: _ClassVar[Alert.Effect]
|
|
||||||
UNKNOWN_EFFECT: _ClassVar[Alert.Effect]
|
|
||||||
STOP_MOVED: _ClassVar[Alert.Effect]
|
|
||||||
NO_EFFECT: _ClassVar[Alert.Effect]
|
|
||||||
ACCESSIBILITY_ISSUE: _ClassVar[Alert.Effect]
|
|
||||||
NO_SERVICE: Alert.Effect
|
|
||||||
REDUCED_SERVICE: Alert.Effect
|
|
||||||
SIGNIFICANT_DELAYS: Alert.Effect
|
|
||||||
DETOUR: Alert.Effect
|
|
||||||
ADDITIONAL_SERVICE: Alert.Effect
|
|
||||||
MODIFIED_SERVICE: Alert.Effect
|
|
||||||
OTHER_EFFECT: Alert.Effect
|
|
||||||
UNKNOWN_EFFECT: Alert.Effect
|
|
||||||
STOP_MOVED: Alert.Effect
|
|
||||||
NO_EFFECT: Alert.Effect
|
|
||||||
ACCESSIBILITY_ISSUE: Alert.Effect
|
|
||||||
class SeverityLevel(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
UNKNOWN_SEVERITY: _ClassVar[Alert.SeverityLevel]
|
|
||||||
INFO: _ClassVar[Alert.SeverityLevel]
|
|
||||||
WARNING: _ClassVar[Alert.SeverityLevel]
|
|
||||||
SEVERE: _ClassVar[Alert.SeverityLevel]
|
|
||||||
UNKNOWN_SEVERITY: Alert.SeverityLevel
|
|
||||||
INFO: Alert.SeverityLevel
|
|
||||||
WARNING: Alert.SeverityLevel
|
|
||||||
SEVERE: Alert.SeverityLevel
|
|
||||||
ACTIVE_PERIOD_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
INFORMED_ENTITY_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CAUSE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
EFFECT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
URL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
HEADER_TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TTS_HEADER_TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TTS_DESCRIPTION_TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SEVERITY_LEVEL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
IMAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
IMAGE_ALTERNATIVE_TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
CAUSE_DETAIL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
EFFECT_DETAIL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
active_period: _containers.RepeatedCompositeFieldContainer[TimeRange]
|
|
||||||
informed_entity: _containers.RepeatedCompositeFieldContainer[EntitySelector]
|
|
||||||
cause: Alert.Cause
|
|
||||||
effect: Alert.Effect
|
|
||||||
url: TranslatedString
|
|
||||||
header_text: TranslatedString
|
|
||||||
description_text: TranslatedString
|
|
||||||
tts_header_text: TranslatedString
|
|
||||||
tts_description_text: TranslatedString
|
|
||||||
severity_level: Alert.SeverityLevel
|
|
||||||
image: TranslatedImage
|
|
||||||
image_alternative_text: TranslatedString
|
|
||||||
cause_detail: TranslatedString
|
|
||||||
effect_detail: TranslatedString
|
|
||||||
def __init__(self, active_period: _Optional[_Iterable[_Union[TimeRange, _Mapping]]] = ..., informed_entity: _Optional[_Iterable[_Union[EntitySelector, _Mapping]]] = ..., cause: _Optional[_Union[Alert.Cause, str]] = ..., effect: _Optional[_Union[Alert.Effect, str]] = ..., url: _Optional[_Union[TranslatedString, _Mapping]] = ..., header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_header_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., tts_description_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., severity_level: _Optional[_Union[Alert.SeverityLevel, str]] = ..., image: _Optional[_Union[TranslatedImage, _Mapping]] = ..., image_alternative_text: _Optional[_Union[TranslatedString, _Mapping]] = ..., cause_detail: _Optional[_Union[TranslatedString, _Mapping]] = ..., effect_detail: _Optional[_Union[TranslatedString, _Mapping]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TimeRange(_message.Message):
|
|
||||||
__slots__ = ("start", "end")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
START_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
END_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
start: int
|
|
||||||
end: int
|
|
||||||
def __init__(self, start: _Optional[int] = ..., end: _Optional[int] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Position(_message.Message):
|
|
||||||
__slots__ = ("latitude", "longitude", "bearing", "odometer", "speed")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
LATITUDE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LONGITUDE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
BEARING_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ODOMETER_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SPEED_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
latitude: float
|
|
||||||
longitude: float
|
|
||||||
bearing: float
|
|
||||||
odometer: float
|
|
||||||
speed: float
|
|
||||||
def __init__(self, latitude: _Optional[float] = ..., longitude: _Optional[float] = ..., bearing: _Optional[float] = ..., odometer: _Optional[float] = ..., speed: _Optional[float] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TripDescriptor(_message.Message):
|
|
||||||
__slots__ = ("trip_id", "route_id", "direction_id", "start_time", "start_date", "schedule_relationship")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class ScheduleRelationship(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
SCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
ADDED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
UNSCHEDULED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
CANCELED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
REPLACEMENT: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
DUPLICATED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
DELETED: _ClassVar[TripDescriptor.ScheduleRelationship]
|
|
||||||
SCHEDULED: TripDescriptor.ScheduleRelationship
|
|
||||||
ADDED: TripDescriptor.ScheduleRelationship
|
|
||||||
UNSCHEDULED: TripDescriptor.ScheduleRelationship
|
|
||||||
CANCELED: TripDescriptor.ScheduleRelationship
|
|
||||||
REPLACEMENT: TripDescriptor.ScheduleRelationship
|
|
||||||
DUPLICATED: TripDescriptor.ScheduleRelationship
|
|
||||||
DELETED: TripDescriptor.ScheduleRelationship
|
|
||||||
TRIP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DIRECTION_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
START_TIME_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
START_DATE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
SCHEDULE_RELATIONSHIP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
trip_id: str
|
|
||||||
route_id: str
|
|
||||||
direction_id: int
|
|
||||||
start_time: str
|
|
||||||
start_date: str
|
|
||||||
schedule_relationship: TripDescriptor.ScheduleRelationship
|
|
||||||
def __init__(self, trip_id: _Optional[str] = ..., route_id: _Optional[str] = ..., direction_id: _Optional[int] = ..., start_time: _Optional[str] = ..., start_date: _Optional[str] = ..., schedule_relationship: _Optional[_Union[TripDescriptor.ScheduleRelationship, str]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class VehicleDescriptor(_message.Message):
|
|
||||||
__slots__ = ("id", "label", "license_plate", "wheelchair_accessible")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class WheelchairAccessible(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
|
|
||||||
__slots__ = ()
|
|
||||||
NO_VALUE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
|
|
||||||
UNKNOWN: _ClassVar[VehicleDescriptor.WheelchairAccessible]
|
|
||||||
WHEELCHAIR_ACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
|
|
||||||
WHEELCHAIR_INACCESSIBLE: _ClassVar[VehicleDescriptor.WheelchairAccessible]
|
|
||||||
NO_VALUE: VehicleDescriptor.WheelchairAccessible
|
|
||||||
UNKNOWN: VehicleDescriptor.WheelchairAccessible
|
|
||||||
WHEELCHAIR_ACCESSIBLE: VehicleDescriptor.WheelchairAccessible
|
|
||||||
WHEELCHAIR_INACCESSIBLE: VehicleDescriptor.WheelchairAccessible
|
|
||||||
ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LABEL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LICENSE_PLATE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
WHEELCHAIR_ACCESSIBLE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
id: str
|
|
||||||
label: str
|
|
||||||
license_plate: str
|
|
||||||
wheelchair_accessible: VehicleDescriptor.WheelchairAccessible
|
|
||||||
def __init__(self, id: _Optional[str] = ..., label: _Optional[str] = ..., license_plate: _Optional[str] = ..., wheelchair_accessible: _Optional[_Union[VehicleDescriptor.WheelchairAccessible, str]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class EntitySelector(_message.Message):
|
|
||||||
__slots__ = ("agency_id", "route_id", "route_type", "trip", "stop_id", "direction_id")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
AGENCY_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ROUTE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ROUTE_TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
TRIP_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
STOP_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
DIRECTION_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
agency_id: str
|
|
||||||
route_id: str
|
|
||||||
route_type: int
|
|
||||||
trip: TripDescriptor
|
|
||||||
stop_id: str
|
|
||||||
direction_id: int
|
|
||||||
def __init__(self, agency_id: _Optional[str] = ..., route_id: _Optional[str] = ..., route_type: _Optional[int] = ..., trip: _Optional[_Union[TripDescriptor, _Mapping]] = ..., stop_id: _Optional[str] = ..., direction_id: _Optional[int] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TranslatedString(_message.Message):
|
|
||||||
__slots__ = ("translation",)
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class Translation(_message.Message):
|
|
||||||
__slots__ = ("text", "language")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
TEXT_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LANGUAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
text: str
|
|
||||||
language: str
|
|
||||||
def __init__(self, text: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ...
|
|
||||||
TRANSLATION_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
translation: _containers.RepeatedCompositeFieldContainer[TranslatedString.Translation]
|
|
||||||
def __init__(self, translation: _Optional[_Iterable[_Union[TranslatedString.Translation, _Mapping]]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class TranslatedImage(_message.Message):
|
|
||||||
__slots__ = ("localized_image",)
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
class LocalizedImage(_message.Message):
|
|
||||||
__slots__ = ("url", "media_type", "language")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
URL_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
MEDIA_TYPE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
LANGUAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
url: str
|
|
||||||
media_type: str
|
|
||||||
language: str
|
|
||||||
def __init__(self, url: _Optional[str] = ..., media_type: _Optional[str] = ..., language: _Optional[str] = ...) -> None: ...
|
|
||||||
LOCALIZED_IMAGE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
localized_image: _containers.RepeatedCompositeFieldContainer[TranslatedImage.LocalizedImage]
|
|
||||||
def __init__(self, localized_image: _Optional[_Iterable[_Union[TranslatedImage.LocalizedImage, _Mapping]]] = ...) -> None: ...
|
|
||||||
|
|
||||||
class Shape(_message.Message):
|
|
||||||
__slots__ = ("shape_id", "encoded_polyline")
|
|
||||||
Extensions: _python_message._ExtensionDict
|
|
||||||
SHAPE_ID_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
ENCODED_POLYLINE_FIELD_NUMBER: _ClassVar[int]
|
|
||||||
shape_id: str
|
|
||||||
encoded_polyline: str
|
|
||||||
def __init__(self, shape_id: _Optional[str] = ..., encoded_polyline: _Optional[str] = ...) -> None: ...
|
|
@ -1,566 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: 1.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2024-02-10 19:57+0100\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"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:6
|
|
||||||
msgid "TGV"
|
|
||||||
msgstr "TGV"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:7
|
|
||||||
msgid "TER"
|
|
||||||
msgstr "TER"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:8
|
|
||||||
msgid "Intercités"
|
|
||||||
msgstr "Intercités"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:9
|
|
||||||
msgid "Transilien"
|
|
||||||
msgstr "Transilien"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:10
|
|
||||||
msgid "Eurostar"
|
|
||||||
msgstr "Eurostar"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:11
|
|
||||||
msgid "Trenitalia"
|
|
||||||
msgstr "Trenitalia"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:12
|
|
||||||
msgid "Renfe"
|
|
||||||
msgstr "Renfe"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:13
|
|
||||||
msgid "ÖBB"
|
|
||||||
msgstr "ÖBB"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:17
|
|
||||||
msgid "Stop/platform"
|
|
||||||
msgstr "Arrêt / quai"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:18
|
|
||||||
msgid "Station"
|
|
||||||
msgstr "Gare"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:19
|
|
||||||
msgid "Entrance/exit"
|
|
||||||
msgstr "Entrée / sortie"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:20
|
|
||||||
msgid "Generic node"
|
|
||||||
msgstr "Nœud générique"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:21
|
|
||||||
msgid "Boarding area"
|
|
||||||
msgstr "Zone d'embarquement"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:25
|
|
||||||
msgid "No information"
|
|
||||||
msgstr "Pas d'information"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:26
|
|
||||||
msgid "Possible"
|
|
||||||
msgstr "Possible"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:27 sncfgtfs/models.py:57
|
|
||||||
msgid "Not possible"
|
|
||||||
msgstr "Impossible"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:31
|
|
||||||
msgid "Regular"
|
|
||||||
msgstr "Régulier"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:32
|
|
||||||
msgid "None"
|
|
||||||
msgstr "Aucun"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:33
|
|
||||||
msgid "Must phone agency"
|
|
||||||
msgstr "Doit téléphoner à l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:34
|
|
||||||
msgid "Must coordinate with driver"
|
|
||||||
msgstr "Doit se coordonner avec læ conducteurice"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:38
|
|
||||||
msgid "Tram"
|
|
||||||
msgstr "Tram"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:39
|
|
||||||
msgid "Metro"
|
|
||||||
msgstr "Métro"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:40
|
|
||||||
msgid "Rail"
|
|
||||||
msgstr "Rail"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:41
|
|
||||||
msgid "Bus"
|
|
||||||
msgstr "Bus"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:42
|
|
||||||
msgid "Ferry"
|
|
||||||
msgstr "Ferry"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:43
|
|
||||||
msgid "Cable car"
|
|
||||||
msgstr "Câble"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:44
|
|
||||||
msgid "Gondola"
|
|
||||||
msgstr "Gondole"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:45
|
|
||||||
msgid "Funicular"
|
|
||||||
msgstr "Funiculaire"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:49
|
|
||||||
msgid "Outbound"
|
|
||||||
msgstr "Vers l'extérieur"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:50
|
|
||||||
msgid "Inbound"
|
|
||||||
msgstr "Vers l'intérieur"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:54
|
|
||||||
msgid "Recommended"
|
|
||||||
msgstr "Recommandé"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:55
|
|
||||||
msgid "Timed"
|
|
||||||
msgstr "Correspondance programmée"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:56
|
|
||||||
msgid "Minimum time"
|
|
||||||
msgstr "Temps de correspondance minimum requis"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:61 sncfgtfs/models.py:67
|
|
||||||
msgid "Added"
|
|
||||||
msgstr "Ajouté"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:62
|
|
||||||
msgid "Removed"
|
|
||||||
msgstr "Supprimé"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:66 sncfgtfs/models.py:76
|
|
||||||
msgid "Scheduled"
|
|
||||||
msgstr "Planifié"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:68 sncfgtfs/models.py:79
|
|
||||||
msgid "Unscheduled"
|
|
||||||
msgstr "Non planifié"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:69
|
|
||||||
msgid "Canceled"
|
|
||||||
msgstr "Annulé"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:70
|
|
||||||
msgid "Replacement"
|
|
||||||
msgstr "Remplacé"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:71
|
|
||||||
msgid "Duplicated"
|
|
||||||
msgstr "Dupliqué"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:72
|
|
||||||
msgid "Deleted"
|
|
||||||
msgstr "Supprimé"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:77
|
|
||||||
msgid "Skipped"
|
|
||||||
msgstr "Sauté"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:78
|
|
||||||
msgid "No data"
|
|
||||||
msgstr "Pas de données"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:86
|
|
||||||
msgid "Agency ID"
|
|
||||||
msgstr "ID de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:91
|
|
||||||
msgid "Agency name"
|
|
||||||
msgstr "Nom de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:95
|
|
||||||
msgid "Agency URL"
|
|
||||||
msgstr "URL de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:100
|
|
||||||
msgid "Agency timezone"
|
|
||||||
msgstr "Fuseau horaire de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:105
|
|
||||||
msgid "Agency language"
|
|
||||||
msgstr "Langue de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:111
|
|
||||||
msgid "Agency phone"
|
|
||||||
msgstr "Téléphone de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:116
|
|
||||||
msgid "Agency email"
|
|
||||||
msgstr "Adresse email de l'agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:124 sncfgtfs/models.py:242
|
|
||||||
msgid "Agency"
|
|
||||||
msgstr "Agence"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:125
|
|
||||||
msgid "Agencies"
|
|
||||||
msgstr "Agences"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:133 sncfgtfs/models.py:459
|
|
||||||
msgid "Stop ID"
|
|
||||||
msgstr "ID de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:138
|
|
||||||
msgid "Stop code"
|
|
||||||
msgstr "Code de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:144
|
|
||||||
msgid "Stop name"
|
|
||||||
msgstr "Nom de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:149
|
|
||||||
msgid "Stop description"
|
|
||||||
msgstr "Description de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:154
|
|
||||||
msgid "Stop longitude"
|
|
||||||
msgstr "Longitude de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:158
|
|
||||||
msgid "Stop latitude"
|
|
||||||
msgstr "Latitude de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:163
|
|
||||||
msgid "Zone ID"
|
|
||||||
msgstr "ID de la zone"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:167
|
|
||||||
msgid "Stop URL"
|
|
||||||
msgstr "URL de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:172
|
|
||||||
msgid "Location type"
|
|
||||||
msgstr "Type de localisation"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:181
|
|
||||||
msgid "Parent station"
|
|
||||||
msgstr "Gare parente"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:189
|
|
||||||
msgid "Stop timezone"
|
|
||||||
msgstr "Fuseau horaire de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:195
|
|
||||||
msgid "Level ID"
|
|
||||||
msgstr "ID du niveau"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:200
|
|
||||||
msgid "Wheelchair boarding"
|
|
||||||
msgstr "Embarquement en fauteuil roulant"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:208
|
|
||||||
msgid "Platform code"
|
|
||||||
msgstr "Code du quai"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:214 sncfgtfs/models.py:286 sncfgtfs/models.py:560
|
|
||||||
msgid "Transport type"
|
|
||||||
msgstr "Type de transport"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:227
|
|
||||||
msgid "Stop"
|
|
||||||
msgstr "Arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:228
|
|
||||||
msgid "Stops"
|
|
||||||
msgstr "Arrêts"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:236 sncfgtfs/models.py:438 sncfgtfs/models.py:577
|
|
||||||
#: sncfgtfs/models.py:609
|
|
||||||
msgid "ID"
|
|
||||||
msgstr "Identifiant"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:248
|
|
||||||
msgid "Route short name"
|
|
||||||
msgstr "Nom court de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:253
|
|
||||||
msgid "Route long name"
|
|
||||||
msgstr "Nom long de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:258
|
|
||||||
msgid "Route description"
|
|
||||||
msgstr "Description de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:263
|
|
||||||
msgid "Route type"
|
|
||||||
msgstr "Type de ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:268
|
|
||||||
msgid "Route URL"
|
|
||||||
msgstr "URL de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:274
|
|
||||||
msgid "Route color"
|
|
||||||
msgstr "Couleur de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:280
|
|
||||||
msgid "Route text color"
|
|
||||||
msgstr "Couleur du texte de la ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:294 sncfgtfs/models.py:309
|
|
||||||
msgid "Route"
|
|
||||||
msgstr "Ligne"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:295
|
|
||||||
msgid "Routes"
|
|
||||||
msgstr "Lignes"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:303
|
|
||||||
msgid "Trip ID"
|
|
||||||
msgstr "ID du trajet"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:316 sncfgtfs/models.py:583
|
|
||||||
msgid "Service"
|
|
||||||
msgstr "Service"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:322
|
|
||||||
msgid "Trip headsign"
|
|
||||||
msgstr "Destination du trajet"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:328
|
|
||||||
msgid "Trip short name"
|
|
||||||
msgstr "Nom court du trajet"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:333
|
|
||||||
msgid "Direction"
|
|
||||||
msgstr "Direction"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:340
|
|
||||||
msgid "Block ID"
|
|
||||||
msgstr "ID du bloc"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:346
|
|
||||||
msgid "Shape ID"
|
|
||||||
msgstr "ID de la forme"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:351
|
|
||||||
msgid "Wheelchair accessible"
|
|
||||||
msgstr "Accessible en fauteuil roulant"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:358
|
|
||||||
msgid "Bikes allowed"
|
|
||||||
msgstr "Vélos autorisés"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:365
|
|
||||||
msgid "Last update"
|
|
||||||
msgstr "Dernière mise à jour"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:430 sncfgtfs/models.py:444 sncfgtfs/models.py:681
|
|
||||||
msgid "Trip"
|
|
||||||
msgstr "Trajet"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:431
|
|
||||||
msgid "Trips"
|
|
||||||
msgstr "Trajets"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:449 sncfgtfs/models.py:731
|
|
||||||
msgid "Arrival time"
|
|
||||||
msgstr "Heure d'arrivée"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:453 sncfgtfs/models.py:739
|
|
||||||
msgid "Departure time"
|
|
||||||
msgstr "Heure de départ"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:464
|
|
||||||
msgid "Stop sequence"
|
|
||||||
msgstr "Séquence de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:469
|
|
||||||
msgid "Stop headsign"
|
|
||||||
msgstr "Destination de l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:474
|
|
||||||
msgid "Pickup type"
|
|
||||||
msgstr "Type de prise en charge"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:481
|
|
||||||
msgid "Drop off type"
|
|
||||||
msgstr "Type de dépose"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:488
|
|
||||||
msgid "Timepoint"
|
|
||||||
msgstr "Ponctualité"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:511 sncfgtfs/models.py:721
|
|
||||||
msgid "Stop time"
|
|
||||||
msgstr "Heure d'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:512
|
|
||||||
msgid "Stop times"
|
|
||||||
msgstr "Heures d'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:519
|
|
||||||
msgid "Service ID"
|
|
||||||
msgstr "ID du service"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:523
|
|
||||||
msgid "Monday"
|
|
||||||
msgstr "Lundi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:527
|
|
||||||
msgid "Tuesday"
|
|
||||||
msgstr "Mardi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:531
|
|
||||||
msgid "Wednesday"
|
|
||||||
msgstr "Mercredi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:535
|
|
||||||
msgid "Thursday"
|
|
||||||
msgstr "Jeudi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:539
|
|
||||||
msgid "Friday"
|
|
||||||
msgstr "Vendredi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:543
|
|
||||||
msgid "Saturday"
|
|
||||||
msgstr "Samedi"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:547
|
|
||||||
msgid "Sunday"
|
|
||||||
msgstr "Dimanche"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:551 sncfgtfs/models.py:687
|
|
||||||
msgid "Start date"
|
|
||||||
msgstr "Date de début"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:555
|
|
||||||
msgid "End date"
|
|
||||||
msgstr "Date de fin"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:568
|
|
||||||
msgid "Calendar"
|
|
||||||
msgstr "Calendrier"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:569
|
|
||||||
msgid "Calendars"
|
|
||||||
msgstr "Calendriers"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:588
|
|
||||||
msgid "Date"
|
|
||||||
msgstr "Date"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:592
|
|
||||||
msgid "Exception type"
|
|
||||||
msgstr "Type d'exception"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:600
|
|
||||||
msgid "Calendar date"
|
|
||||||
msgstr "Date du calendrier"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:601
|
|
||||||
msgid "Calendar dates"
|
|
||||||
msgstr "Dates du calendrier"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:615
|
|
||||||
msgid "From stop"
|
|
||||||
msgstr "Depuis l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:622
|
|
||||||
msgid "To stop"
|
|
||||||
msgstr "Jusqu'à l'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:627
|
|
||||||
msgid "Transfer type"
|
|
||||||
msgstr "Type de correspondance"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:633
|
|
||||||
msgid "Minimum transfer time"
|
|
||||||
msgstr "Temps de correspondance minimum"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:638
|
|
||||||
msgid "Transfer"
|
|
||||||
msgstr "Correspondance"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:639
|
|
||||||
msgid "Transfers"
|
|
||||||
msgstr "Correspondances"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:646
|
|
||||||
msgid "Feed publisher name"
|
|
||||||
msgstr "Nom de l'éditeur du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:650
|
|
||||||
msgid "Feed publisher URL"
|
|
||||||
msgstr "URL de l'éditeur du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:655
|
|
||||||
msgid "Feed language"
|
|
||||||
msgstr "Langue du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:659
|
|
||||||
msgid "Feed start date"
|
|
||||||
msgstr "Date de début du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:663
|
|
||||||
msgid "Feed end date"
|
|
||||||
msgstr "Date de fin du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:668
|
|
||||||
msgid "Feed version"
|
|
||||||
msgstr "Version du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:672
|
|
||||||
msgid "Feed info"
|
|
||||||
msgstr "Information du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:673
|
|
||||||
msgid "Feed infos"
|
|
||||||
msgstr "Informations du flux"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:691
|
|
||||||
msgid "Start time"
|
|
||||||
msgstr "Heure de début"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:695 sncfgtfs/models.py:743
|
|
||||||
msgid "Schedule relationship"
|
|
||||||
msgstr "Relation de la planification"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:704 sncfgtfs/models.py:714
|
|
||||||
msgid "Trip update"
|
|
||||||
msgstr "Mise à jour du trajet"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:705
|
|
||||||
msgid "Trip updates"
|
|
||||||
msgstr "Mises à jour des trajets"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:727
|
|
||||||
msgid "Arrival delay"
|
|
||||||
msgstr "Retard à l'arrivée"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:735
|
|
||||||
msgid "Departure delay"
|
|
||||||
msgstr "Retard au départ"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:752
|
|
||||||
msgid "Stop time update"
|
|
||||||
msgstr "Mise à jour du temps d'arrêt"
|
|
||||||
|
|
||||||
#: sncfgtfs/models.py:753
|
|
||||||
msgid "Stop time updates"
|
|
||||||
msgstr "Mises à jour des temps d'arrêt"
|
|
@ -1,404 +0,0 @@
|
|||||||
import csv
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from io import BytesIO
|
|
||||||
from zipfile import ZipFile
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
|
|
||||||
from sncfgtfs.models import Agency, Calendar, CalendarDate, FeedInfo, Route, Stop, StopTime, Transfer, Trip
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Update the SNCF GTFS database."
|
|
||||||
|
|
||||||
GTFS_FEEDS = {
|
|
||||||
"TGV": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export_gtfs_voyages.zip",
|
|
||||||
"IC": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-intercites-gtfs-last.zip",
|
|
||||||
"TER": "https://eu.ftp.opendatasoft.com/sncf/gtfs/export-ter-gtfs-last.zip",
|
|
||||||
"TN": "https://eu.ftp.opendatasoft.com/sncf/gtfs/transilien-gtfs.zip",
|
|
||||||
"ES": "https://www.data.gouv.fr/fr/datasets/r/9089b550-696e-4ae0-87b5-40ea55a14292",
|
|
||||||
"TI": "https://thello.axelor.com/public/gtfs/gtfs.zip",
|
|
||||||
"RENFE": "https://ssl.renfe.com/gtransit/Fichero_AV_LD/google_transit.zip",
|
|
||||||
"OBB": "https://static.oebb.at/open-data/soll-fahrplan-gtfs/GTFS_OP_2024_obb.zip",
|
|
||||||
}
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument('--bulk_size', type=int, default=1000, 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, *args, **options):
|
|
||||||
bulk_size = options['bulk_size']
|
|
||||||
dry_run = options['dry_run']
|
|
||||||
force = options['force']
|
|
||||||
if dry_run:
|
|
||||||
self.stdout.write(self.style.WARNING("Dry run mode activated."))
|
|
||||||
|
|
||||||
if not FeedInfo.objects.exists():
|
|
||||||
last_update_date = "1970-01-01"
|
|
||||||
else:
|
|
||||||
last_update_date = FeedInfo.objects.get(publisher_name='SNCF_default').version
|
|
||||||
|
|
||||||
for url in self.GTFS_FEEDS.values():
|
|
||||||
resp = requests.head(url)
|
|
||||||
if "Last-Modified" not in resp.headers:
|
|
||||||
continue
|
|
||||||
last_modified = resp.headers["Last-Modified"]
|
|
||||||
last_modified = datetime.strptime(last_modified, "%a, %d %b %Y %H:%M:%S %Z")
|
|
||||||
if last_modified.date().isoformat() > last_update_date:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if not force:
|
|
||||||
self.stdout.write(self.style.WARNING("Database already up-to-date."))
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stdout.write("Updating database...")
|
|
||||||
|
|
||||||
for transport_type, feed_url in self.GTFS_FEEDS.items():
|
|
||||||
self.stdout.write(f"Downloading {transport_type} GTFS feed...")
|
|
||||||
with ZipFile(BytesIO(requests.get(feed_url).content)) as zipfile:
|
|
||||||
def read_file(filename):
|
|
||||||
lines = zipfile.read(filename).decode().replace('\ufeff', '').splitlines()
|
|
||||||
return [line.strip() for line in lines]
|
|
||||||
|
|
||||||
agencies = []
|
|
||||||
for agency_dict in csv.DictReader(read_file("agency.txt")):
|
|
||||||
agency_dict: dict
|
|
||||||
if transport_type == "ES" \
|
|
||||||
and agency_dict['agency_id'] != 'ES' and agency_dict['agency_id'] != 'ER':
|
|
||||||
continue
|
|
||||||
agency = Agency(
|
|
||||||
id=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', ""),
|
|
||||||
)
|
|
||||||
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'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
agencies.clear()
|
|
||||||
|
|
||||||
stops = []
|
|
||||||
for stop_dict in csv.DictReader(read_file("stops.txt")):
|
|
||||||
stop_dict: dict
|
|
||||||
stop_id = stop_dict['stop_id']
|
|
||||||
if transport_type in ["ES", "TI", "RENFE"]:
|
|
||||||
stop_id = f"{transport_type}-{stop_id}"
|
|
||||||
|
|
||||||
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', 1) or 1,
|
|
||||||
parent_station_id=stop_dict.get('parent_station', None) or None,
|
|
||||||
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', ""),
|
|
||||||
transport_type=transport_type,
|
|
||||||
)
|
|
||||||
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',
|
|
||||||
'transport_type'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
stops.clear()
|
|
||||||
|
|
||||||
routes = []
|
|
||||||
for route_dict in csv.DictReader(read_file("routes.txt")):
|
|
||||||
route_dict: dict
|
|
||||||
route_id = route_dict['route_id']
|
|
||||||
if transport_type == "TI":
|
|
||||||
route_id = f"{transport_type}-{route_id}"
|
|
||||||
route = Route(
|
|
||||||
id=route_id,
|
|
||||||
agency_id=route_dict['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', ""),
|
|
||||||
transport_type=transport_type,
|
|
||||||
)
|
|
||||||
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',
|
|
||||||
'transport_type'],
|
|
||||||
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',
|
|
||||||
'transport_type'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
routes.clear()
|
|
||||||
|
|
||||||
Calendar.objects.filter(transport_type=transport_type).delete()
|
|
||||||
calendars = {}
|
|
||||||
if "calendar.txt" in zipfile.namelist():
|
|
||||||
for calendar_dict in csv.DictReader(read_file("calendar.txt")):
|
|
||||||
calendar_dict: dict
|
|
||||||
calendar = Calendar(
|
|
||||||
id=f"{transport_type}-{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'],
|
|
||||||
transport_type=transport_type,
|
|
||||||
)
|
|
||||||
calendars[calendar.id] = calendar
|
|
||||||
|
|
||||||
if len(calendars) >= bulk_size and not dry_run:
|
|
||||||
Calendar.objects.bulk_create(calendars.values(),
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
|
||||||
'friday', 'saturday', 'sunday', 'start_date',
|
|
||||||
'end_date', 'transport_type'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
calendars.clear()
|
|
||||||
if calendars and not dry_run:
|
|
||||||
Calendar.objects.bulk_create(calendars.values(), update_conflicts=True,
|
|
||||||
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
|
||||||
'friday', 'saturday', 'sunday', 'start_date',
|
|
||||||
'end_date', 'transport_type'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
calendars.clear()
|
|
||||||
|
|
||||||
calendar_dates = []
|
|
||||||
for calendar_date_dict in csv.DictReader(read_file("calendar_dates.txt")):
|
|
||||||
calendar_date_dict: dict
|
|
||||||
calendar_date = CalendarDate(
|
|
||||||
id=f"{transport_type}-{calendar_date_dict['service_id']}-{calendar_date_dict['date']}",
|
|
||||||
service_id=f"{transport_type}-{calendar_date_dict['service_id']}",
|
|
||||||
date=calendar_date_dict['date'],
|
|
||||||
exception_type=calendar_date_dict['exception_type'],
|
|
||||||
)
|
|
||||||
calendar_dates.append(calendar_date)
|
|
||||||
|
|
||||||
if calendar_date.service_id not in calendars:
|
|
||||||
calendar = Calendar(
|
|
||||||
id=f"{transport_type}-{calendar_date_dict['service_id']}",
|
|
||||||
monday=False,
|
|
||||||
tuesday=False,
|
|
||||||
wednesday=False,
|
|
||||||
thursday=False,
|
|
||||||
friday=False,
|
|
||||||
saturday=False,
|
|
||||||
sunday=False,
|
|
||||||
start_date=calendar_date_dict['date'],
|
|
||||||
end_date=calendar_date_dict['date'],
|
|
||||||
transport_type=transport_type,
|
|
||||||
)
|
|
||||||
calendars[calendar.id] = calendar
|
|
||||||
else:
|
|
||||||
calendar = calendars[f"{transport_type}-{calendar_date_dict['service_id']}"]
|
|
||||||
if calendar.start_date > calendar_date.date:
|
|
||||||
calendar.start_date = calendar_date.date
|
|
||||||
if calendar.end_date < calendar_date.date:
|
|
||||||
calendar.end_date = calendar_date.date
|
|
||||||
|
|
||||||
if calendar_dates and not dry_run:
|
|
||||||
Calendar.objects.bulk_create(calendars.values(),
|
|
||||||
batch_size=bulk_size,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['start_date', 'end_date'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
CalendarDate.objects.bulk_create(calendar_dates,
|
|
||||||
batch_size=bulk_size,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['service_id', 'date', 'exception_type'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
calendars.clear()
|
|
||||||
calendar_dates.clear()
|
|
||||||
|
|
||||||
trips = []
|
|
||||||
for trip_dict in csv.DictReader(read_file("trips.txt")):
|
|
||||||
trip_dict: dict
|
|
||||||
trip_id = trip_dict['trip_id']
|
|
||||||
route_id = trip_dict['route_id']
|
|
||||||
if transport_type in ["TGV", "IC", "TER"]:
|
|
||||||
trip_id, last_update = trip_id.split(':', 1)
|
|
||||||
last_update = datetime.fromisoformat(last_update)
|
|
||||||
elif transport_type in ["ES", "RENFE"]:
|
|
||||||
trip_id = f"{transport_type}-{trip_id}"
|
|
||||||
last_update = None
|
|
||||||
elif transport_type == "TI":
|
|
||||||
trip_id = f"{transport_type}-{trip_id}"
|
|
||||||
route_id = f"{transport_type}-{route_id}"
|
|
||||||
last_update = None
|
|
||||||
else:
|
|
||||||
last_update = None
|
|
||||||
trip = Trip(
|
|
||||||
id=trip_id,
|
|
||||||
route_id=route_id,
|
|
||||||
service_id=f"{transport_type}-{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),
|
|
||||||
last_update=last_update,
|
|
||||||
)
|
|
||||||
trips.append(trip)
|
|
||||||
|
|
||||||
if len(trips) >= bulk_size and not dry_run:
|
|
||||||
Trip.objects.bulk_create(trips,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
|
||||||
'direction_id', 'block_id', 'shape_id',
|
|
||||||
'wheelchair_accessible', 'bikes_allowed'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
trips.clear()
|
|
||||||
if trips and not dry_run:
|
|
||||||
Trip.objects.bulk_create(trips,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['route_id', 'service_id', 'headsign', 'short_name',
|
|
||||||
'direction_id', 'block_id', 'shape_id',
|
|
||||||
'wheelchair_accessible', 'bikes_allowed'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
trips.clear()
|
|
||||||
|
|
||||||
stop_times = []
|
|
||||||
for stop_time_dict in csv.DictReader(read_file("stop_times.txt")):
|
|
||||||
stop_time_dict: dict
|
|
||||||
|
|
||||||
stop_id = stop_time_dict['stop_id']
|
|
||||||
if transport_type in ["ES", "TI", "RENFE"]:
|
|
||||||
stop_id = f"{transport_type}-{stop_id}"
|
|
||||||
|
|
||||||
trip_id = stop_time_dict['trip_id']
|
|
||||||
if transport_type in ["TGV", "IC", "TER"]:
|
|
||||||
trip_id = trip_id.split(':', 1)[0]
|
|
||||||
elif transport_type in ["ES", "TI", "RENFE"]:
|
|
||||||
trip_id = f"{transport_type}-{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', 0)
|
|
||||||
drop_off_type = stop_time_dict.get('drop_off_type', 0)
|
|
||||||
if transport_type in ["ES", "RENFE", "OBB"]:
|
|
||||||
if stop_time_dict['stop_sequence'] == "1":
|
|
||||||
drop_off_type = 1
|
|
||||||
elif arr_time == dep_time:
|
|
||||||
pickup_type = 1
|
|
||||||
elif transport_type == "TI":
|
|
||||||
if stop_time_dict['stop_sequence'] == "0":
|
|
||||||
drop_off_type = 1
|
|
||||||
elif arr_time == dep_time:
|
|
||||||
pickup_type = 1
|
|
||||||
|
|
||||||
st = StopTime(
|
|
||||||
id=f"{trip_id}-{stop_id}-{stop_time_dict['departure_time']}",
|
|
||||||
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:
|
|
||||||
StopTime.objects.bulk_create(stop_times,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['stop_id', 'arrival_time', 'departure_time',
|
|
||||||
'stop_headsign', 'pickup_type',
|
|
||||||
'drop_off_type', 'timepoint'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
stop_times.clear()
|
|
||||||
if stop_times and not dry_run:
|
|
||||||
StopTime.objects.bulk_create(stop_times,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['stop_id', 'arrival_time', 'departure_time',
|
|
||||||
'stop_headsign', 'pickup_type',
|
|
||||||
'drop_off_type', 'timepoint'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
stop_times.clear()
|
|
||||||
|
|
||||||
if "transfers.txt" in zipfile.namelist():
|
|
||||||
transfers = []
|
|
||||||
for transfer_dict in csv.DictReader(read_file("transfers.txt")):
|
|
||||||
transfer_dict: dict
|
|
||||||
from_stop_id = transfer_dict['from_stop_id']
|
|
||||||
to_stop_id = transfer_dict['to_stop_id']
|
|
||||||
if transport_type in ["ES", "RENFE", "OBB"]:
|
|
||||||
from_stop_id = f"{transport_type}-{from_stop_id}"
|
|
||||||
to_stop_id = f"{transport_type}-{to_stop_id}"
|
|
||||||
|
|
||||||
transfer = Transfer(
|
|
||||||
id=f"{from_stop_id}-{to_stop_id}",
|
|
||||||
from_stop_id=transfer_dict['from_stop_id'],
|
|
||||||
to_stop_id=transfer_dict['to_stop_id'],
|
|
||||||
transfer_type=transfer_dict['transfer_type'],
|
|
||||||
min_transfer_time=transfer_dict['min_transfer_time'],
|
|
||||||
)
|
|
||||||
transfers.append(transfer)
|
|
||||||
|
|
||||||
if len(transfers) >= bulk_size and not dry_run:
|
|
||||||
Transfer.objects.bulk_create(transfers,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['transfer_type', 'min_transfer_time'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
transfers.clear()
|
|
||||||
|
|
||||||
if transfers and not dry_run:
|
|
||||||
Transfer.objects.bulk_create(transfers,
|
|
||||||
update_conflicts=True,
|
|
||||||
update_fields=['transfer_type', 'min_transfer_time'],
|
|
||||||
unique_fields=['id'])
|
|
||||||
transfers.clear()
|
|
||||||
|
|
||||||
if "feed_info.txt" in zipfile.namelist() and not dry_run:
|
|
||||||
for feed_info_dict in csv.DictReader(read_file("feed_info.txt")):
|
|
||||||
feed_info_dict: dict
|
|
||||||
FeedInfo.objects.update_or_create(
|
|
||||||
publisher_name=feed_info_dict['feed_publisher_name'],
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
)
|
|
@ -1,272 +0,0 @@
|
|||||||
from datetime import timedelta, datetime, date, time
|
|
||||||
from zoneinfo import ZoneInfo
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
from sncfgtfs.gtfs_realtime_pb2 import FeedMessage
|
|
||||||
from sncfgtfs.models import Agency, Calendar, CalendarDate, ExceptionType, LocationType, PickupType, \
|
|
||||||
Route, RouteType, Stop, StopScheduleRelationship, StopTime, StopTimeUpdate, \
|
|
||||||
Trip, TripUpdate, TripScheduleRelationship
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Update the SNCF GTFS Realtime database."
|
|
||||||
|
|
||||||
GTFS_RT_FEEDS = {
|
|
||||||
"TGV": "https://proxy.transport.data.gouv.fr/resource/sncf-tgv-gtfs-rt-trip-updates",
|
|
||||||
"IC": "https://proxy.transport.data.gouv.fr/resource/sncf-ic-gtfs-rt-trip-updates",
|
|
||||||
"TER": "https://proxy.transport.data.gouv.fr/resource/sncf-ter-gtfs-rt-trip-updates",
|
|
||||||
"TI": "https://thello.axelor.com/public/gtfs/GTFS-RT.bin",
|
|
||||||
}
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
for feed_type, feed_url in self.GTFS_RT_FEEDS.items():
|
|
||||||
self.stdout.write(f"Updating {feed_type} feed...")
|
|
||||||
feed_message = FeedMessage()
|
|
||||||
feed_message.ParseFromString(requests.get(feed_url).content)
|
|
||||||
|
|
||||||
stop_times_updates = []
|
|
||||||
|
|
||||||
for entity in feed_message.entity:
|
|
||||||
if entity.HasField("trip_update"):
|
|
||||||
trip_update = entity.trip_update
|
|
||||||
trip_id = trip_update.trip.trip_id
|
|
||||||
if feed_type in ["TGV", "IC", "TER"]:
|
|
||||||
trip_id = trip_id.split(":", 1)[0]
|
|
||||||
|
|
||||||
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, feed_type)
|
|
||||||
|
|
||||||
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
|
|
||||||
if stop_id.startswith('StopArea:'):
|
|
||||||
# On est dans le cadre d'une gare. On cherche le quai associé.
|
|
||||||
if StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists():
|
|
||||||
# U
|
|
||||||
stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop
|
|
||||||
else:
|
|
||||||
stops = [s for s in Stop.objects.filter(parent_station_id=stop_id).all()
|
|
||||||
for s2 in StopTime.objects.filter(trip_id=trip_id).all()
|
|
||||||
if s.stop_type in s2.stop.stop_type
|
|
||||||
or s2.stop.stop_type in s.stop_type]
|
|
||||||
stop = stops[0] if stops else Stop.objects.get(id=stop_id)
|
|
||||||
|
|
||||||
st, _created = StopTime.objects.update_or_create(
|
|
||||||
id=f"{trip_id}-{stop.id}",
|
|
||||||
trip_id=trip_id,
|
|
||||||
stop_id=stop.id,
|
|
||||||
defaults={
|
|
||||||
"stop_sequence": stop_sequence,
|
|
||||||
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
|
|
||||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
|
||||||
"departure_time": datetime.fromtimestamp(stop_time_update.departure.time,
|
|
||||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
|
||||||
"pickup_type": (PickupType.REGULAR if stop_time_update.departure.time
|
|
||||||
else PickupType.NONE),
|
|
||||||
"drop_off_type": (PickupType.REGULAR if stop_time_update.arrival.time
|
|
||||||
else PickupType.NONE),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif stop_time_update.schedule_relationship == StopScheduleRelationship.SKIPPED:
|
|
||||||
st = StopTime.objects.get(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id),
|
|
||||||
trip_id=trip_id)
|
|
||||||
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()
|
|
||||||
else:
|
|
||||||
qs = StopTime.objects.filter(Q(stop=stop_id) | Q(stop__parent_station_id=stop_id),
|
|
||||||
trip_id=trip_id)
|
|
||||||
if qs.count() == 1:
|
|
||||||
st = qs.first()
|
|
||||||
else:
|
|
||||||
st = qs.get(stop_sequence=stop_sequence)
|
|
||||||
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))
|
|
||||||
|
|
||||||
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, trip_id, start_dt, feed_type):
|
|
||||||
headsign = trip_id[5:-1]
|
|
||||||
trip_qs = Trip.objects.all()
|
|
||||||
trip_ids = trip_qs.values_list('id', flat=True)
|
|
||||||
|
|
||||||
first_stop_queryset = StopTime.objects.filter(
|
|
||||||
stop__parent_station_id=trip_update.stop_time_update[0].stop_id,
|
|
||||||
).values('trip_id')
|
|
||||||
last_stop_queryset = StopTime.objects.filter(
|
|
||||||
stop__parent_station_id=trip_update.stop_time_update[-1].stop_id,
|
|
||||||
).values('trip_id')
|
|
||||||
|
|
||||||
trip_ids = trip_ids.intersection(first_stop_queryset).intersection(last_stop_queryset)
|
|
||||||
# print(trip_id, trip_ids)
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
|
||||||
stop_id = stop_time_update.stop_id
|
|
||||||
st_queryset = StopTime.objects.filter(stop__parent_station_id=stop_id)
|
|
||||||
if stop_sequence == 0:
|
|
||||||
st_queryset = st_queryset.filter(stop_sequence=0)
|
|
||||||
# print(stop_sequence, Stop.objects.get(id=stop_id).name, stop_time_update)
|
|
||||||
# print(trip_ids)
|
|
||||||
# print(st_queryset.values('trip_id').all())
|
|
||||||
trip_ids_restrict = trip_ids.intersection(st_queryset.values('trip_id'))
|
|
||||||
if trip_ids_restrict:
|
|
||||||
trip_ids = trip_ids_restrict
|
|
||||||
else:
|
|
||||||
stop = Stop.objects.get(id=stop_id)
|
|
||||||
self.stdout.write(self.style.WARNING(f"Warning: No trip is found passing by stop "
|
|
||||||
f"{stop.name} ({stop_id})"))
|
|
||||||
trip_ids = set(trip_ids)
|
|
||||||
route_ids = set(Trip.objects.filter(id__in=trip_ids).values_list('route_id', flat=True))
|
|
||||||
self.stdout.write(f"{len(route_ids)} routes found on trip for new train {headsign}")
|
|
||||||
if not route_ids:
|
|
||||||
origin_id = trip_update.stop_time_update[0].stop_id
|
|
||||||
origin = Stop.objects.get(id=origin_id)
|
|
||||||
destination_id = trip_update.stop_time_update[-1].stop_id
|
|
||||||
destination = Stop.objects.get(id=destination_id)
|
|
||||||
trip_name = f"{origin.name} - {destination.name}"
|
|
||||||
trip_reverse_name = f"{destination.name} - {origin.name}"
|
|
||||||
route_qs = Route.objects.filter(long_name=trip_name, transport_type=feed_type)
|
|
||||||
route_reverse_qs = Route.objects.filter(long_name=trip_reverse_name,
|
|
||||||
transport_type=feed_type)
|
|
||||||
if route_qs.exists():
|
|
||||||
route_ids = set(route_qs.values_list('id', flat=True))
|
|
||||||
elif route_reverse_qs.exists():
|
|
||||||
route_ids = set(route_reverse_qs.values_list('id', flat=True))
|
|
||||||
else:
|
|
||||||
self.stdout.write(f"Route not found for trip {trip_id} ({trip_name}). Creating new one")
|
|
||||||
route = Route.objects.create(
|
|
||||||
id=f"CREATED-{trip_name}",
|
|
||||||
agency=Agency.objects.filter(routes__transport_type=feed_type).first(),
|
|
||||||
transport_type=feed_type,
|
|
||||||
type=RouteType.RAIL,
|
|
||||||
short_name=trip_name,
|
|
||||||
long_name=trip_name,
|
|
||||||
)
|
|
||||||
route_ids = {route.id}
|
|
||||||
self.stdout.write(f"Route {route.id} created for trip {trip_id} ({trip_name})")
|
|
||||||
elif len(route_ids) > 1:
|
|
||||||
self.stdout.write(f"Multiple routes found for trip {trip_id}.")
|
|
||||||
self.stdout.write(", ".join(route_ids))
|
|
||||||
route_id = route_ids.pop()
|
|
||||||
|
|
||||||
Calendar.objects.update_or_create(
|
|
||||||
id=f"{feed_type}-new-{headsign}",
|
|
||||||
defaults={
|
|
||||||
"transport_type": feed_type,
|
|
||||||
"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"{feed_type}-{headsign}-{trip_update.trip.start_date}",
|
|
||||||
defaults={
|
|
||||||
"service_id": f"{feed_type}-new-{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"{feed_type}-new-{headsign}",
|
|
||||||
"headsign": headsign,
|
|
||||||
"direction_id": trip_update.trip.direction_id,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
sample_trip = Trip.objects.filter(id__in=trip_ids, route_id=route_id)
|
|
||||||
sample_trip = sample_trip.first() if sample_trip.exists() else None
|
|
||||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
|
||||||
stop_id = stop_time_update.stop_id
|
|
||||||
stop = Stop.objects.get(id=stop_id)
|
|
||||||
if stop.location_type == LocationType.STATION:
|
|
||||||
if not StopTime.objects.filter(trip_id=trip_id).exists():
|
|
||||||
if sample_trip:
|
|
||||||
stop = StopTime.objects.get(trip_id=sample_trip.id,
|
|
||||||
stop__parent_station_id=stop_id).stop
|
|
||||||
elif StopTime.objects.filter(trip_id=trip_id, stop__parent_station_id=stop_id).exists():
|
|
||||||
stop = StopTime.objects.get(trip_id=trip_id, stop__parent_station_id=stop_id).stop
|
|
||||||
else:
|
|
||||||
stops = [s for s in Stop.objects.filter(parent_station_id=stop_id).all()
|
|
||||||
for s2 in StopTime.objects.filter(trip_id=trip_id).all()
|
|
||||||
if s.stop_type in s2.stop.stop_type
|
|
||||||
or s2.stop.stop_type in s.stop_type]
|
|
||||||
stop = stops[0] if stops else stop
|
|
||||||
stop_id = 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"{trip_id}-{stop_id}",
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,695 +0,0 @@
|
|||||||
# Generated by Django 5.0.1 on 2024-02-10 16:30
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = []
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
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"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Agency",
|
|
||||||
"verbose_name_plural": "Agencies",
|
|
||||||
"ordering": ("name",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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")),
|
|
||||||
(
|
|
||||||
"transport_type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("TGV", "TGV"),
|
|
||||||
("TER", "TER"),
|
|
||||||
("IC", "Intercités"),
|
|
||||||
("TN", "Transilien"),
|
|
||||||
("ES", "Eurostar"),
|
|
||||||
("TI", "Trenitalia"),
|
|
||||||
("RENFE", "Renfe"),
|
|
||||||
("OBB", "ÖBB"),
|
|
||||||
],
|
|
||||||
max_length=255,
|
|
||||||
verbose_name="Transport type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Calendar",
|
|
||||||
"verbose_name_plural": "Calendars",
|
|
||||||
"ordering": ("id",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Feed info",
|
|
||||||
"verbose_name_plural": "Feed infos",
|
|
||||||
"ordering": ("publisher_name",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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="sncfgtfs.calendar",
|
|
||||||
verbose_name="Service",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Calendar date",
|
|
||||||
"verbose_name_plural": "Calendar dates",
|
|
||||||
"ordering": ("id",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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(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"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"transport_type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("TGV", "TGV"),
|
|
||||||
("TER", "TER"),
|
|
||||||
("IC", "Intercités"),
|
|
||||||
("TN", "Transilien"),
|
|
||||||
("ES", "Eurostar"),
|
|
||||||
("TI", "Trenitalia"),
|
|
||||||
("RENFE", "Renfe"),
|
|
||||||
("OBB", "ÖBB"),
|
|
||||||
],
|
|
||||||
max_length=255,
|
|
||||||
verbose_name="Transport type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"agency",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="routes",
|
|
||||||
to="sncfgtfs.agency",
|
|
||||||
verbose_name="Agency",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
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(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"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"transport_type",
|
|
||||||
models.CharField(
|
|
||||||
choices=[
|
|
||||||
("TGV", "TGV"),
|
|
||||||
("TER", "TER"),
|
|
||||||
("IC", "Intercités"),
|
|
||||||
("TN", "Transilien"),
|
|
||||||
("ES", "Eurostar"),
|
|
||||||
("TI", "Trenitalia"),
|
|
||||||
("RENFE", "Renfe"),
|
|
||||||
("OBB", "ÖBB"),
|
|
||||||
],
|
|
||||||
max_length=255,
|
|
||||||
verbose_name="Transport type",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"parent_station",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="children",
|
|
||||||
to="sncfgtfs.stop",
|
|
||||||
verbose_name="Parent station",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Stop",
|
|
||||||
"verbose_name_plural": "Stops",
|
|
||||||
"ordering": ("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="sncfgtfs.stop",
|
|
||||||
verbose_name="From stop",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"to_stop",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="transfers_to",
|
|
||||||
to="sncfgtfs.stop",
|
|
||||||
verbose_name="To stop",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Transfer",
|
|
||||||
"verbose_name_plural": "Transfers",
|
|
||||||
"ordering": ("id",),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"last_update",
|
|
||||||
models.DateTimeField(null=True, verbose_name="Last update"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"route",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="trips",
|
|
||||||
to="sncfgtfs.route",
|
|
||||||
verbose_name="Route",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"service",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="trips",
|
|
||||||
to="sncfgtfs.calendar",
|
|
||||||
verbose_name="Service",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Trip",
|
|
||||||
"verbose_name_plural": "Trips",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"stop",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="stop_times",
|
|
||||||
to="sncfgtfs.stop",
|
|
||||||
verbose_name="Stop ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"trip",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="stop_times",
|
|
||||||
to="sncfgtfs.trip",
|
|
||||||
verbose_name="Trip",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Stop time",
|
|
||||||
"verbose_name_plural": "Stop times",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="TripUpdate",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"trip",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
primary_key=True,
|
|
||||||
related_name="update",
|
|
||||||
serialize=False,
|
|
||||||
to="sncfgtfs.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"),
|
|
||||||
"unique_together": {("trip", "start_date", "start_time")},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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="sncfgtfs.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",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"trip_update",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
related_name="stop_time_updates",
|
|
||||||
to="sncfgtfs.tripupdate",
|
|
||||||
verbose_name="Trip update",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "Stop time update",
|
|
||||||
"verbose_name_plural": "Stop time updates",
|
|
||||||
"ordering": ("trip_update", "stop_time"),
|
|
||||||
"unique_together": {("trip_update", "stop_time")},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,755 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class TransportType(models.TextChoices):
|
|
||||||
TGV = "TGV", _("TGV")
|
|
||||||
TER = "TER", _("TER")
|
|
||||||
INTERCITES = "IC", _("Intercités")
|
|
||||||
TRANSILIEN = "TN", _("Transilien")
|
|
||||||
EUROSTAR = "ES", _("Eurostar")
|
|
||||||
TRENITALIA = "TI", _("Trenitalia")
|
|
||||||
RENFE = "RENFE", _("Renfe")
|
|
||||||
OBB = "OBB", _("ÖBB")
|
|
||||||
|
|
||||||
|
|
||||||
class LocationType(models.IntegerChoices):
|
|
||||||
STOP_PLATFORM = 0, _("Stop/platform")
|
|
||||||
STATION = 1, _("Station")
|
|
||||||
ENTRANCE_EXIT = 2, _("Entrance/exit")
|
|
||||||
GENERIC_NODE = 3, _("Generic node")
|
|
||||||
BOARDING_AREA = 4, _("Boarding area")
|
|
||||||
|
|
||||||
|
|
||||||
class AccessInformation(models.IntegerChoices):
|
|
||||||
NO_INFORMATION = 0, _("No information")
|
|
||||||
POSSIBLE = 1, _("Possible")
|
|
||||||
NOT_POSSIBLE = 2, _("Not possible")
|
|
||||||
|
|
||||||
|
|
||||||
class PickupType(models.IntegerChoices):
|
|
||||||
REGULAR = 0, _("Regular")
|
|
||||||
NONE = 1, _("None")
|
|
||||||
MUST_PHONE_AGENCY = 2, _("Must phone agency")
|
|
||||||
MUST_COORDINATE_WITH_DRIVER = 3, _("Must coordinate with driver")
|
|
||||||
|
|
||||||
|
|
||||||
class RouteType(models.IntegerChoices):
|
|
||||||
TRAM = 0, _("Tram")
|
|
||||||
METRO = 1, _("Metro")
|
|
||||||
RAIL = 2, _("Rail")
|
|
||||||
BUS = 3, _("Bus")
|
|
||||||
FERRY = 4, _("Ferry")
|
|
||||||
CABLE_CAR = 5, _("Cable car")
|
|
||||||
GONDOLA = 6, _("Gondola")
|
|
||||||
FUNICULAR = 7, _("Funicular")
|
|
||||||
|
|
||||||
|
|
||||||
class Direction(models.IntegerChoices):
|
|
||||||
OUTBOUND = 0, _("Outbound")
|
|
||||||
INBOUND = 1, _("Inbound")
|
|
||||||
|
|
||||||
|
|
||||||
class TransferType(models.IntegerChoices):
|
|
||||||
RECOMMENDED = 0, _("Recommended")
|
|
||||||
TIMED = 1, _("Timed")
|
|
||||||
MINIMUM_TIME = 2, _("Minimum time")
|
|
||||||
NOT_POSSIBLE = 3, _("Not possible")
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionType(models.IntegerChoices):
|
|
||||||
ADDED = 1, _("Added")
|
|
||||||
REMOVED = 2, _("Removed")
|
|
||||||
|
|
||||||
|
|
||||||
class TripScheduleRelationship(models.IntegerChoices):
|
|
||||||
SCHEDULED = 0, _("Scheduled")
|
|
||||||
ADDED = 1, _("Added")
|
|
||||||
UNSCHEDULED = 2, _("Unscheduled")
|
|
||||||
CANCELED = 3, _("Canceled")
|
|
||||||
REPLACEMENT = 5, _("Replacement")
|
|
||||||
DUPLICATED = 6, _("Duplicated")
|
|
||||||
DELETED = 7, _("Deleted")
|
|
||||||
|
|
||||||
|
|
||||||
class StopScheduleRelationship(models.IntegerChoices):
|
|
||||||
SCHEDULED = 0, _("Scheduled")
|
|
||||||
SKIPPED = 1, _("Skipped")
|
|
||||||
NO_DATA = 2, _("No data")
|
|
||||||
UNSCHEDULED = 3, _("Unscheduled")
|
|
||||||
|
|
||||||
|
|
||||||
class Agency(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
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(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Agency language"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
phone = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Agency phone"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
email = models.EmailField(
|
|
||||||
verbose_name=_("Agency email"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Agency")
|
|
||||||
verbose_name_plural = _("Agencies")
|
|
||||||
ordering = ("name",)
|
|
||||||
|
|
||||||
|
|
||||||
class Stop(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("Stop ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
code = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Stop code"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Stop name"),
|
|
||||||
)
|
|
||||||
|
|
||||||
desc = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Stop description"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
lon = models.FloatField(
|
|
||||||
verbose_name=_("Stop longitude"),
|
|
||||||
)
|
|
||||||
|
|
||||||
lat = models.FloatField(
|
|
||||||
verbose_name=_("Stop latitude"),
|
|
||||||
)
|
|
||||||
|
|
||||||
zone_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Zone ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
url = models.URLField(
|
|
||||||
verbose_name=_("Stop URL"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
location_type = models.IntegerField(
|
|
||||||
verbose_name=_("Location type"),
|
|
||||||
blank=True,
|
|
||||||
choices=LocationType,
|
|
||||||
default=LocationType.STOP_PLATFORM,
|
|
||||||
)
|
|
||||||
|
|
||||||
parent_station = models.ForeignKey(
|
|
||||||
to="Stop",
|
|
||||||
on_delete=models.PROTECT,
|
|
||||||
verbose_name=_("Parent station"),
|
|
||||||
related_name="children",
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
timezone = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Stop timezone"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
level_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Level ID"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
wheelchair_boarding = models.IntegerField(
|
|
||||||
verbose_name=_("Wheelchair boarding"),
|
|
||||||
blank=True,
|
|
||||||
choices=AccessInformation,
|
|
||||||
default=AccessInformation.NO_INFORMATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
platform_code = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Platform code"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
transport_type = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Transport type"),
|
|
||||||
choices=TransportType,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def stop_type(self):
|
|
||||||
train_type = self.id.split('StopPoint:OCE')[-1].split('StopArea:OCE')[-1].split('-')[0]
|
|
||||||
return train_type
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name} ({self.id})"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Stop")
|
|
||||||
verbose_name_plural = _("Stops")
|
|
||||||
ordering = ("id",)
|
|
||||||
|
|
||||||
|
|
||||||
class Route(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
agency = models.ForeignKey(
|
|
||||||
to="Agency",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Agency"),
|
|
||||||
related_name="routes",
|
|
||||||
)
|
|
||||||
|
|
||||||
short_name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route short name"),
|
|
||||||
)
|
|
||||||
|
|
||||||
long_name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route long name"),
|
|
||||||
)
|
|
||||||
|
|
||||||
desc = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route description"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
type = models.IntegerField(
|
|
||||||
verbose_name=_("Route type"),
|
|
||||||
choices=RouteType,
|
|
||||||
)
|
|
||||||
|
|
||||||
url = models.URLField(
|
|
||||||
verbose_name=_("Route URL"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
color = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route color"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
text_color = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Route text color"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
transport_type = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Transport type"),
|
|
||||||
choices=TransportType,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.long_name}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Route")
|
|
||||||
verbose_name_plural = _("Routes")
|
|
||||||
ordering = ("id",)
|
|
||||||
|
|
||||||
|
|
||||||
class Trip(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("Trip ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
route = models.ForeignKey(
|
|
||||||
to="Route",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Route"),
|
|
||||||
related_name="trips",
|
|
||||||
)
|
|
||||||
|
|
||||||
service = models.ForeignKey(
|
|
||||||
to="Calendar",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Service"),
|
|
||||||
related_name="trips",
|
|
||||||
)
|
|
||||||
|
|
||||||
headsign = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Trip headsign"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
short_name = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Trip short name"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
direction_id = models.IntegerField(
|
|
||||||
verbose_name=_("Direction"),
|
|
||||||
choices=Direction,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
block_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Block ID"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
shape_id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Shape ID"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
wheelchair_accessible = models.IntegerField(
|
|
||||||
verbose_name=_("Wheelchair accessible"),
|
|
||||||
choices=AccessInformation,
|
|
||||||
default=AccessInformation.NO_INFORMATION,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
bikes_allowed = models.IntegerField(
|
|
||||||
verbose_name=_("Bikes allowed"),
|
|
||||||
choices=AccessInformation,
|
|
||||||
default=AccessInformation.NO_INFORMATION,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
last_update = models.DateTimeField(
|
|
||||||
verbose_name=_("Last update"),
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def origin(self):
|
|
||||||
return self.stop_times.order_by('stop_sequence').first().stop
|
|
||||||
|
|
||||||
@property
|
|
||||||
def destination(self):
|
|
||||||
return self.stop_times.order_by('-stop_sequence').first().stop
|
|
||||||
|
|
||||||
@property
|
|
||||||
def departure_time(self):
|
|
||||||
dep_time = self.stop_times.order_by('stop_sequence').first().departure_time
|
|
||||||
hours = int(dep_time.total_seconds() // 3600)
|
|
||||||
minutes = int((dep_time.total_seconds() % 3600) // 60)
|
|
||||||
return f"{hours:02}:{minutes:02}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arrival_time(self):
|
|
||||||
arr_time = self.stop_times.order_by('-stop_sequence').first().arrival_time
|
|
||||||
hours = int(arr_time.total_seconds() // 3600)
|
|
||||||
minutes = int((arr_time.total_seconds() % 3600) // 60)
|
|
||||||
return f"{hours:02}:{minutes:02}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def train_type(self):
|
|
||||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
|
||||||
return self.route.short_name
|
|
||||||
else:
|
|
||||||
return self.origin.stop_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def train_number(self):
|
|
||||||
if self.route.transport_type == TransportType.TRANSILIEN:
|
|
||||||
return self.short_name
|
|
||||||
else:
|
|
||||||
return self.headsign
|
|
||||||
|
|
||||||
@property
|
|
||||||
def color(self):
|
|
||||||
if self.route.color:
|
|
||||||
return self.route.color
|
|
||||||
elif self.train_type == "OUIGO":
|
|
||||||
return "E60075"
|
|
||||||
return "FFFFFF"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def text_color(self):
|
|
||||||
if self.route.text_color:
|
|
||||||
return self.route.text_color
|
|
||||||
elif self.train_type == "OUIGO":
|
|
||||||
return "FFFFFF"
|
|
||||||
elif self.train_type == "TGV INOUI":
|
|
||||||
return "9B2743"
|
|
||||||
elif self.train_type == "INTER-CITÉS" or self.train_type == "INTER-CITÉS de nuit":
|
|
||||||
return "404042"
|
|
||||||
return "000000"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.origin.name} {self.departure_time} → {self.destination.name} {self.arrival_time}" \
|
|
||||||
f" - {self.service_id}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Trip")
|
|
||||||
verbose_name_plural = _("Trips")
|
|
||||||
|
|
||||||
|
|
||||||
class StopTime(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
trip = models.ForeignKey(
|
|
||||||
to="Trip",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Trip"),
|
|
||||||
related_name="stop_times",
|
|
||||||
)
|
|
||||||
|
|
||||||
arrival_time = models.DurationField(
|
|
||||||
verbose_name=_("Arrival time"),
|
|
||||||
)
|
|
||||||
|
|
||||||
departure_time = models.DurationField(
|
|
||||||
verbose_name=_("Departure time"),
|
|
||||||
)
|
|
||||||
|
|
||||||
stop = models.ForeignKey(
|
|
||||||
to="Stop",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Stop ID"),
|
|
||||||
related_name="stop_times",
|
|
||||||
)
|
|
||||||
|
|
||||||
stop_sequence = models.IntegerField(
|
|
||||||
verbose_name=_("Stop sequence"),
|
|
||||||
)
|
|
||||||
|
|
||||||
stop_headsign = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Stop headsign"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
pickup_type = models.IntegerField(
|
|
||||||
verbose_name=_("Pickup type"),
|
|
||||||
choices=PickupType,
|
|
||||||
default=PickupType.REGULAR,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
drop_off_type = models.IntegerField(
|
|
||||||
verbose_name=_("Drop off type"),
|
|
||||||
choices=PickupType,
|
|
||||||
default=PickupType.REGULAR,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
timepoint = models.BooleanField(
|
|
||||||
verbose_name=_("Timepoint"),
|
|
||||||
default=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pretty_arrival_time(self):
|
|
||||||
seconds = self.arrival_time.total_seconds()
|
|
||||||
hours = int(seconds // 3600) % 24
|
|
||||||
minutes = int((seconds % 3600) // 60)
|
|
||||||
return f"{hours:02}:{minutes:02}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pretty_departure_time(self):
|
|
||||||
seconds = self.departure_time.total_seconds()
|
|
||||||
hours = int(seconds // 3600) % 24
|
|
||||||
minutes = int((seconds % 3600) // 60)
|
|
||||||
return f"{hours:02}:{minutes:02}"
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.stop.name} - {self.trip_id}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Stop time")
|
|
||||||
verbose_name_plural = _("Stop times")
|
|
||||||
|
|
||||||
|
|
||||||
class Calendar(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
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"),
|
|
||||||
)
|
|
||||||
|
|
||||||
transport_type = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
verbose_name=_("Transport type"),
|
|
||||||
choices=TransportType,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Calendar")
|
|
||||||
verbose_name_plural = _("Calendars")
|
|
||||||
ordering = ("id",)
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarDate(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
service = models.ForeignKey(
|
|
||||||
to="Calendar",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Service"),
|
|
||||||
related_name="dates",
|
|
||||||
)
|
|
||||||
|
|
||||||
date = models.DateField(
|
|
||||||
verbose_name=_("Date"),
|
|
||||||
)
|
|
||||||
|
|
||||||
exception_type = models.IntegerField(
|
|
||||||
verbose_name=_("Exception type"),
|
|
||||||
choices=ExceptionType,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.service.id} - {self.date} - {self.exception_type}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Calendar date")
|
|
||||||
verbose_name_plural = _("Calendar dates")
|
|
||||||
ordering = ("id",)
|
|
||||||
|
|
||||||
|
|
||||||
class Transfer(models.Model):
|
|
||||||
id = models.CharField(
|
|
||||||
max_length=255,
|
|
||||||
primary_key=True,
|
|
||||||
verbose_name=_("ID"),
|
|
||||||
)
|
|
||||||
|
|
||||||
from_stop = models.ForeignKey(
|
|
||||||
to="Stop",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("From stop"),
|
|
||||||
related_name="transfers_from",
|
|
||||||
)
|
|
||||||
|
|
||||||
to_stop = models.ForeignKey(
|
|
||||||
to="Stop",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("To stop"),
|
|
||||||
related_name="transfers_to",
|
|
||||||
)
|
|
||||||
|
|
||||||
transfer_type = models.IntegerField(
|
|
||||||
verbose_name=_("Transfer type"),
|
|
||||||
choices=TransferType,
|
|
||||||
default=TransferType.RECOMMENDED,
|
|
||||||
)
|
|
||||||
|
|
||||||
min_transfer_time = models.IntegerField(
|
|
||||||
verbose_name=_("Minimum transfer time"),
|
|
||||||
blank=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Transfer")
|
|
||||||
verbose_name_plural = _("Transfers")
|
|
||||||
ordering = ("id",)
|
|
||||||
|
|
||||||
|
|
||||||
class FeedInfo(models.Model):
|
|
||||||
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"),
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Feed info")
|
|
||||||
verbose_name_plural = _("Feed infos")
|
|
||||||
ordering = ("publisher_name",)
|
|
||||||
|
|
||||||
|
|
||||||
class TripUpdate(models.Model):
|
|
||||||
trip = models.OneToOneField(
|
|
||||||
to="Trip",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Trip"),
|
|
||||||
related_name="update",
|
|
||||||
primary_key=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
start_date = models.DateField(
|
|
||||||
verbose_name=_("Start date"),
|
|
||||||
)
|
|
||||||
|
|
||||||
start_time = models.TimeField(
|
|
||||||
verbose_name=_("Start time"),
|
|
||||||
)
|
|
||||||
|
|
||||||
schedule_relationship = models.IntegerField(
|
|
||||||
verbose_name=_("Schedule relationship"),
|
|
||||||
choices=TripScheduleRelationship,
|
|
||||||
default=TripScheduleRelationship.SCHEDULED,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.trip)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Trip update")
|
|
||||||
verbose_name_plural = _("Trip updates")
|
|
||||||
ordering = ("start_date", "trip",)
|
|
||||||
unique_together = ("trip", "start_date", "start_time",)
|
|
||||||
|
|
||||||
|
|
||||||
class StopTimeUpdate(models.Model):
|
|
||||||
trip_update = models.ForeignKey(
|
|
||||||
to="TripUpdate",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Trip update"),
|
|
||||||
related_name="stop_time_updates",
|
|
||||||
)
|
|
||||||
|
|
||||||
stop_time = models.OneToOneField(
|
|
||||||
to="StopTime",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
verbose_name=_("Stop time"),
|
|
||||||
related_name="update",
|
|
||||||
primary_key=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
|
||||||
verbose_name=_("Schedule relationship"),
|
|
||||||
choices=StopScheduleRelationship,
|
|
||||||
default=StopScheduleRelationship.SCHEDULED,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.trip_update)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Stop time update")
|
|
||||||
verbose_name_plural = _("Stop time updates")
|
|
||||||
ordering = ("trip_update", "stop_time",)
|
|
||||||
unique_together = ("trip_update", "stop_time",)
|
|
@ -1,6 +0,0 @@
|
|||||||
Django>=5.0,<6.0
|
|
||||||
django-cors-headers
|
|
||||||
django-filter
|
|
||||||
djangorestframework
|
|
||||||
protobuf
|
|
||||||
requests
|
|
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
@ -2,14 +2,14 @@ import {createBrowserRouter, RouterProvider} from "react-router-dom"
|
|||||||
import Station from "./Station"
|
import Station from "./Station"
|
||||||
import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@mui/material"
|
import {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@mui/material"
|
||||||
import React, {useMemo} from "react"
|
import React, {useMemo} from "react"
|
||||||
import {frFR, LocalizationProvider} from "@mui/x-date-pickers"
|
import {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 {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister";
|
import Home from "./Home"
|
||||||
import {persistQueryClient} from "@tanstack/react-query-persist-client";
|
import TrainMap from "./Map"
|
||||||
import Home from "./Home";
|
import dayjs from "dayjs"
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -18,8 +18,12 @@ function App() {
|
|||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/station/:stopId",
|
path: "/station/:theme/:stationId",
|
||||||
element: <Station />
|
element: <Station />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/map",
|
||||||
|
element: <TrainMap />
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -54,19 +58,12 @@ function App() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const localStoragePersister = createSyncStoragePersister({
|
dayjs.locale('fr')
|
||||||
storage: window.localStorage,
|
|
||||||
})
|
|
||||||
|
|
||||||
persistQueryClient({
|
|
||||||
queryClient,
|
|
||||||
persister: localStoragePersister,
|
|
||||||
})
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs} localeText={frFR.components.MuiLocalizationProvider.defaultProps.localeText} adapterLocale="fr">
|
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
37
src/AutocompleteStation.jsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {Autocomplete, TextField} from "@mui/material";
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
function AutocompleteStation(params) {
|
||||||
|
const [options, setOptions] = useState([])
|
||||||
|
|
||||||
|
function onInputChange(event, value) {
|
||||||
|
if (!value) {
|
||||||
|
setOptions([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(setOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Autocomplete
|
||||||
|
id="stop"
|
||||||
|
options={options}
|
||||||
|
onInputChange={onInputChange}
|
||||||
|
filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))}
|
||||||
|
getOptionKey={option => option.id}
|
||||||
|
getOptionLabel={option => option.name}
|
||||||
|
groupBy={option => getOptionGroup(option)}
|
||||||
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
|
renderInput={(params) => <TextField {...params} label="Arrêt" />}
|
||||||
|
{...params} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptionGroup(option) {
|
||||||
|
return option.id.split('_')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutocompleteStation;
|
@ -1,19 +1,19 @@
|
|||||||
import AutocompleteStop from "./AutocompleteStop"
|
import AutocompleteStation from "./AutocompleteStation"
|
||||||
import {useNavigate} from "react-router-dom"
|
import {useNavigate} from "react-router-dom"
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
function onStationSelected(event, stop) {
|
function onStationSelected(event, station) {
|
||||||
navigate(`/station/${stop.id}/`)
|
navigate(`/station/sncf/${station.id}/`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<h1>Horaires SNCF</h1>
|
<h1>Horaires des trains</h1>
|
||||||
<h2>
|
<h2>
|
||||||
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
|
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
|
||||||
</h2>
|
</h2>
|
||||||
<AutocompleteStop onChange={onStationSelected} />
|
<AutocompleteStation onChange={onStationSelected} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
194
src/Map.js
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import "leaflet/dist/leaflet.css"
|
||||||
|
import L from 'leaflet'
|
||||||
|
import {MapContainer, Marker, TileLayer, useMapEvents} from 'react-leaflet'
|
||||||
|
import {useEffect, useMemo, useState} from "react"
|
||||||
|
import dayjs from "dayjs"
|
||||||
|
import polyline from "@mapbox/polyline"
|
||||||
|
import getDistance from '@turf/rhumb-distance'
|
||||||
|
import getBearing from '@turf/rhumb-bearing'
|
||||||
|
|
||||||
|
export default function TrainMap () {
|
||||||
|
return <>
|
||||||
|
<MapContainer center={[46.47, 2.37]} zoom={6} style={{height: "100vh"}}>
|
||||||
|
<TileLayer
|
||||||
|
attribution='Données cartographiques : © Les contributeurices <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
|
/>
|
||||||
|
<TileLayer
|
||||||
|
attribution="Rendu : OpenRailwayMap"
|
||||||
|
url="https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png"></TileLayer>
|
||||||
|
|
||||||
|
<MapContent />
|
||||||
|
</MapContainer>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapContent () {
|
||||||
|
const [latitude, setLatitude] = useState(46.47)
|
||||||
|
const [longitude, setLongitude] = useState(2.37)
|
||||||
|
const [zoom, setZoom] = useState(6)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/map/initial`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
setLatitude(data['lat'])
|
||||||
|
setLongitude(data['lon'])
|
||||||
|
setZoom(data['zoom'])
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const map = useMapEvents({
|
||||||
|
moveend: () => {
|
||||||
|
updateTrips(map, setTrips)
|
||||||
|
},
|
||||||
|
zoomend: () => {
|
||||||
|
updateTrips(map, setTrips)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
map.flyTo([latitude, longitude], zoom)
|
||||||
|
}, [map, latitude, longitude, zoom])
|
||||||
|
|
||||||
|
const [trips, setTrips] = useState([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
updateTrips(map, setTrips)
|
||||||
|
setInterval(() => updateTrips(map, setTrips), 30000)
|
||||||
|
}, [map])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{trips.map(trip => <TripMarker trip={trip} />)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function TripMarker ({trip}) {
|
||||||
|
const [position, setPosition] = useState([trip.from.lat, trip.from.lon])
|
||||||
|
const [heading, setHeading] = useState(0)
|
||||||
|
const style = getModeStyle(trip.mode)
|
||||||
|
|
||||||
|
const keyframes = useMemo(() => {
|
||||||
|
const keyframes = []
|
||||||
|
const departure = dayjs(trip.departure)
|
||||||
|
const arrival = dayjs(trip.arrival)
|
||||||
|
const coordinates = polyline.decode(trip.polyline)
|
||||||
|
const totalDuration = arrival.diff(departure, 'seconds')
|
||||||
|
let currDistance = 0
|
||||||
|
|
||||||
|
let totalDistance = 0
|
||||||
|
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||||
|
let from = coordinates[i]
|
||||||
|
let to = coordinates[i + 1]
|
||||||
|
totalDistance += getDistance(from, to, { units: 'meters' })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||||
|
let from = coordinates[i]
|
||||||
|
let to = coordinates[i + 1]
|
||||||
|
|
||||||
|
const distance = getDistance(from, to, { units: 'meters' })
|
||||||
|
const heading = getBearing(from, to)
|
||||||
|
|
||||||
|
const r = currDistance / totalDistance
|
||||||
|
keyframes.push({ point: from, time: departure.add(r * totalDuration, 'seconds'), heading: heading })
|
||||||
|
|
||||||
|
currDistance += distance
|
||||||
|
}
|
||||||
|
keyframes.push({ point: coordinates[coordinates.length - 1], time: arrival, heading: 0 })
|
||||||
|
|
||||||
|
return keyframes
|
||||||
|
}, [trip])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const now = dayjs()
|
||||||
|
const index = keyframes.findIndex((kf) => kf.time >= now)
|
||||||
|
if (index === -1 || index === 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
const startState = keyframes[index - 1]
|
||||||
|
const endState = keyframes[index]
|
||||||
|
const r = (now.diff(startState.time)) / (endState.time.diff(startState.time))
|
||||||
|
const lat = startState.point[0] * (1 - r) + endState.point[0] * r
|
||||||
|
const lon = startState.point[1] * (1 - r) + endState.point[1] * r
|
||||||
|
setPosition([lat, lon])
|
||||||
|
setHeading(startState.heading)
|
||||||
|
}, 100)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [keyframes])
|
||||||
|
const icon = L.divIcon({
|
||||||
|
html: `<svg fill="${style[1]}" fill-opacity="0.8" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
\t width="36px" height="36px" viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<g transform="rotate(${-heading - 90}, 256, 256)">
|
||||||
|
\t<path d="M256 17.108c-75.73 0-137.122 61.392-137.122 137.122.055 23.25 6.022 46.107 11.58 56.262L256 494.892l119.982-274.244h-.063c11.27-20.324 17.188-43.18 17.202-66.418C393.122 78.5 331.73 17.108 256 17.108zm0 68.56a68.56 68.56 0 0 1 68.56 68.562A68.56 68.56 0 0 1 256 222.79a68.56 68.56 0 0 1-68.56-68.56A68.56 68.56 0 0 1 256 85.67z" />
|
||||||
|
</g>
|
||||||
|
</svg>`,
|
||||||
|
className: "",
|
||||||
|
iconSize: [36, 36],
|
||||||
|
iconAnchor: [36, 36],
|
||||||
|
})
|
||||||
|
return <Marker position={position} icon={icon} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModeStyle (mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'WALK':
|
||||||
|
case 'FLEXIBLE':
|
||||||
|
return ['walk', 'hsl(var(--foreground) / 1)', 'hsl(var(--background) / 1)']
|
||||||
|
case 'BIKE':
|
||||||
|
case 'BIKE_TO_PARK':
|
||||||
|
case 'BIKE_RENTAL':
|
||||||
|
case 'SCOOTER_RENTAL':
|
||||||
|
return ['bike', '#075985', 'white']
|
||||||
|
case 'CAR':
|
||||||
|
case 'CAR_TO_PARK':
|
||||||
|
case 'CAR_HAILING':
|
||||||
|
case 'CAR_SHARING':
|
||||||
|
case 'CAR_PICKUP':
|
||||||
|
case 'CAR_RENTAL':
|
||||||
|
return ['car', '#333', 'white']
|
||||||
|
case 'TRANSIT':
|
||||||
|
case 'BUS':
|
||||||
|
return ['bus', '#ff9800', 'white']
|
||||||
|
case 'COACH':
|
||||||
|
return ['bus', '#9ccc65', 'white']
|
||||||
|
case 'TRAM':
|
||||||
|
return ['tram', '#ff9800', 'white']
|
||||||
|
case 'METRO':
|
||||||
|
return ['sbahn', '#4caf50', 'white']
|
||||||
|
case 'SUBWAY':
|
||||||
|
return ['ubahn', '#3f51b5', 'white']
|
||||||
|
case 'FERRY':
|
||||||
|
return ['ship', '#00acc1', 'white']
|
||||||
|
case 'AIRPLANE':
|
||||||
|
return ['plane', '#90a4ae', 'white']
|
||||||
|
case 'HIGHSPEED_RAIL':
|
||||||
|
return ['train', '#9c27b0', 'white']
|
||||||
|
case 'LONG_DISTANCE':
|
||||||
|
return ['train', '#e91e63', 'white']
|
||||||
|
case 'NIGHT_RAIL':
|
||||||
|
return ['train', '#1a237e', 'white']
|
||||||
|
case 'REGIONAL_FAST_RAIL':
|
||||||
|
case 'REGIONAL_RAIL':
|
||||||
|
case 'RAIL':
|
||||||
|
return ['train', '#f44336', 'white']
|
||||||
|
}
|
||||||
|
return ['train', '#000000', 'white']
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTrips(map, setTrips) {
|
||||||
|
const bounds = map.getBounds()
|
||||||
|
const now = dayjs()
|
||||||
|
const now_plus_1_min = now.add(60000)
|
||||||
|
const query_params = new URLSearchParams({
|
||||||
|
min: `${bounds.getNorth()},${bounds.getWest()}`,
|
||||||
|
max: `${bounds.getSouth()},${bounds.getEast()}`,
|
||||||
|
zoom: map.getZoom(),
|
||||||
|
startTime: now.format(),
|
||||||
|
endTime: now_plus_1_min.format(),
|
||||||
|
}).toString()
|
||||||
|
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/map/trips?${query_params}`)
|
||||||
|
.then(data => data.json())
|
||||||
|
.then(setTrips)
|
||||||
|
}
|
90
src/Station.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import {useNavigate, useParams, useSearchParams} from "react-router-dom"
|
||||||
|
import TrainsTable from "./TrainsTable"
|
||||||
|
import {useEffect, useState} from "react"
|
||||||
|
import {Box, Checkbox, 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, realtime, setRealtime}) {
|
||||||
|
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} disabled={realtime} readOnly={realtime} />
|
||||||
|
<Checkbox onChange={event => setRealtime(event.target.checked)} checked={realtime} />
|
||||||
|
<FormLabel>
|
||||||
|
Temps réel
|
||||||
|
</FormLabel>
|
||||||
|
</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 [realtime, setRealtime] = useState(searchParams.get('realtime') === "1" || false)
|
||||||
|
const [datetime, setDatetime] = useState(dayjs(searchParams.get('time') || undefined))
|
||||||
|
if ((searchParams.get('realtime') === null || searchParams.get('realtime') === "0")
|
||||||
|
&& (searchParams.get('time') === null || realtime)) {
|
||||||
|
searchParams.set('realtime', "1")
|
||||||
|
searchParams.delete("time")
|
||||||
|
setRealtime(true)
|
||||||
|
window.history.replaceState({}, '', '?' + searchParams.toString())
|
||||||
|
}
|
||||||
|
else if (datetime.format() !== searchParams.get('time') && !realtime) {
|
||||||
|
searchParams.set('time', datetime.format())
|
||||||
|
searchParams.set('realtime', "0")
|
||||||
|
window.history.replaceState({}, '', '?' + searchParams.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
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…"}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (realtime) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setDatetime(dayjs())
|
||||||
|
}, 5000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [realtime])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Station">
|
||||||
|
<header className="App-header">
|
||||||
|
<h1>Horaires en gare de {station.name}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<DateTimeSelector datetime={datetime} setDatetime={setDatetime} realtime={realtime} setRealtime={setRealtime} />
|
||||||
|
{/*<TripsFilter />*/}
|
||||||
|
<TrainsTable station={station} datetime={datetime} realtime={realtime} tableType="departures" />
|
||||||
|
<TrainsTable station={station} datetime={datetime} realtime={realtime} tableType="arrivals" />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Station;
|
318
src/TrainsTable.js
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
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, realtime, tableType}) {
|
||||||
|
return <>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TrainsTableHeader tableType={tableType} />
|
||||||
|
<TrainsTableBody station={station} datetime={datetime} realtime={realtime} 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, realtime, 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 params = {
|
||||||
|
stopId: station.stopId,
|
||||||
|
arriveBy: tableType === "arrivals",
|
||||||
|
direction: "LATER",
|
||||||
|
n: 20,
|
||||||
|
}
|
||||||
|
if (!realtime)
|
||||||
|
params['time'] = datetime.format()
|
||||||
|
const query_params = new URLSearchParams(params).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, tableType, datetime, realtime])
|
||||||
|
|
||||||
|
const trainsQuery = useQuery({
|
||||||
|
queryKey: ['trains', station.stopId, tableType],
|
||||||
|
queryFn: updateTrains,
|
||||||
|
enabled: !!station.stopId,
|
||||||
|
})
|
||||||
|
const trains = useMemo(() => trainsQuery.data ?? [], [trainsQuery.data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (realtime) {
|
||||||
|
let validTrains = trains?.filter(filterTime) ?? []
|
||||||
|
if ((trains?.length > 0 && validTrains.length < trains?.length))
|
||||||
|
trainsQuery.refetch().then()
|
||||||
|
}
|
||||||
|
}, [trains, filterTime, trainsQuery, realtime])
|
||||||
|
|
||||||
|
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-IDFM":
|
||||||
|
const route_split = train.routeShortName.split(" ")
|
||||||
|
if (route_split[0] === "Bus")
|
||||||
|
return route_split[1]
|
||||||
|
return route_split[0]
|
||||||
|
case "FR-GES-CTS":
|
||||||
|
return train.routeShortName.split(" ")[1]
|
||||||
|
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":
|
||||||
|
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;
|
165
src/TripsFilter.js
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import {useState} from "react"
|
||||||
|
import {
|
||||||
|
Box, Button,
|
||||||
|
Checkbox, Chip, FormControl,
|
||||||
|
FormControlLabel,
|
||||||
|
InputLabel, MenuItem, OutlinedInput, Select
|
||||||
|
} from "@mui/material"
|
||||||
|
import DirectionsBusTwoToneIcon from '@mui/icons-material/DirectionsBusTwoTone'
|
||||||
|
import SubwayTwoToneIcon from '@mui/icons-material/SubwayTwoTone'
|
||||||
|
import TrainTwoToneIcon from '@mui/icons-material/TrainTwoTone'
|
||||||
|
import TramTwoToneIcon from '@mui/icons-material/TramTwoTone'
|
||||||
|
|
||||||
|
function TripsFilter() {
|
||||||
|
const [transportModeFilter, setTransportModeFilter] = useState(
|
||||||
|
{longDistanceTrain: true, regionalTrain: true, metro: true, tram: true, bus: true})
|
||||||
|
const transportModeNames = {
|
||||||
|
train: "Trains",
|
||||||
|
longDistanceTrain: "Trains longue distance",
|
||||||
|
regionalTrain: "Trains régionaux",
|
||||||
|
metro: "Métro",
|
||||||
|
tram: "Tram",
|
||||||
|
bus: "Bus",
|
||||||
|
}
|
||||||
|
|
||||||
|
const trainCheckbox = <>
|
||||||
|
<TrainTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.longDistanceTrain && transportModeFilter.regionalTrain}
|
||||||
|
indeterminate={transportModeFilter.longDistanceTrain !== transportModeFilter.regionalTrain}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter(
|
||||||
|
{...transportModeFilter, longDistanceTrain: event.target.checked, regionalTrain: event.target.checked})}
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
const longDistanceTrainCheckbox = <>
|
||||||
|
<TrainTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.longDistanceTrain}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter({...transportModeFilter, longDistanceTrain: event.target.checked})} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
const regionalTrainCheckbox = <>
|
||||||
|
<TrainTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.regionalTrain}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter({...transportModeFilter, regionalTrain: event.target.checked})} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
const metroCheckbox = <>
|
||||||
|
<SubwayTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.metro}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter({...transportModeFilter, metro: event.target.checked})} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
const tramCheckbox = <>
|
||||||
|
<TramTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.tram}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter({...transportModeFilter, tram: event.target.checked})} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
const busCheckbox = <>
|
||||||
|
<DirectionsBusTwoToneIcon />
|
||||||
|
<Checkbox
|
||||||
|
checked={transportModeFilter.bus}
|
||||||
|
onChange={(event) =>
|
||||||
|
setTransportModeFilter({...transportModeFilter, bus: event.target.checked})} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
// TODO Fetch routes that are accessible from one stop
|
||||||
|
// For now, we have the tram and bus routes accessible in Strasbourg main station
|
||||||
|
const routesList = [
|
||||||
|
{name: "Tous"},
|
||||||
|
{name: "A", bgColor: "#E10D19", color: "#FFFFFF"},
|
||||||
|
{name: "C", bgColor: "#F29400", color: "#FFFFFF"},
|
||||||
|
{name: "D", bgColor: "#009933", color: "#FFFFFF"},
|
||||||
|
{name: "G", bgColor: "#F6C900", color: "#000000"},
|
||||||
|
{name: "H", bgColor: "#A62341", color: "#FFFFFF"},
|
||||||
|
{name: "2", bgColor: "#FF0000", color: "#FFFFFF"},
|
||||||
|
{name: "10", bgColor: "#FFAA00", color: "#000000"},
|
||||||
|
]
|
||||||
|
const routesDict = {}
|
||||||
|
for (const route of routesList) {
|
||||||
|
routesDict[route.name] = route
|
||||||
|
}
|
||||||
|
|
||||||
|
const [selectedRoutes, setSelectedRoutes] = useState(["Tous"])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<h2>Filtres</h2>
|
||||||
|
<Box display="flex" alignItems="center" sx={{mb: 3}}>
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel>Mode de transport</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedRoutes}
|
||||||
|
input={<OutlinedInput id="select-multiple-chip" label="Lignes" />}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
|
{Object.keys(transportModeFilter).filter(key => transportModeFilter[key]).map((filterType) => (
|
||||||
|
<Chip key={filterType} label={transportModeNames[filterType]} sx={{fontWeight: "bold"}} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MenuItem key="train" value="train">
|
||||||
|
<FormControlLabel label={transportModeNames["train"]} control={trainCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="longDistanceTrain" value="longDistanceTrain">
|
||||||
|
<FormControlLabel label={transportModeNames["longDistanceTrain"]} sx={{pl: 4}} control={longDistanceTrainCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="regionalTrain" value="regionalTrain">
|
||||||
|
<FormControlLabel label={transportModeNames["regionalTrain"]} sx={{pl: 4}} control={regionalTrainCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="metro" value="metro">
|
||||||
|
<FormControlLabel label={transportModeNames["metro"]} control={metroCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="tram" value="tram">
|
||||||
|
<FormControlLabel label={transportModeNames["tram"]} control={tramCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key="bus" value="bus">
|
||||||
|
<FormControlLabel label={transportModeNames["bus"]} control={busCheckbox} />
|
||||||
|
</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl>
|
||||||
|
<InputLabel>Ligne</InputLabel>
|
||||||
|
<Select
|
||||||
|
multiple
|
||||||
|
value={selectedRoutes}
|
||||||
|
onChange={(event) => setSelectedRoutes(event.target.value)}
|
||||||
|
input={<OutlinedInput id="select-multiple-chip" label="Lignes" />}
|
||||||
|
renderValue={(selected) => (
|
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
|
{selected.map(routeName => routesDict[routeName]).map((route) => (
|
||||||
|
<Chip key={route.name} label={route.name} sx={{backgroundColor: route.bgColor, color: route.color, fontWeight: "bold"}} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{routesList.map((route) =>
|
||||||
|
<MenuItem key={route.name} value={route.name}>
|
||||||
|
<Checkbox checked={selectedRoutes.includes(route.name)} />
|
||||||
|
<Chip label={route.name} sx={{backgroundColor: route.bgColor, color: route.color, fontWeight: "bold"}} />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<Button>
|
||||||
|
Filtrer
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TripsFilter
|
@ -1,4 +1,4 @@
|
|||||||
import React, {useMemo} from 'react';
|
import React 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 |