Compare commits
60 Commits
f2b6557cb7
...
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
|
|||
2277d2fe64
|
|||
9f3c031851
|
|||
d679aadf68
|
|||
da82ef7950
|
|||
f50391970f
|
|||
dd34b025ca
|
|||
0e87ecd8cf
|
|||
7486fb2b4d
|
|||
0d5a62ae7f
|
|||
2803cfe062
|
|||
347f1dfa60
|
|||
72e00ae781
|
|||
28faf5ad10
|
|||
e4bcf9bd2e
|
|||
8aff5fbf94
|
|||
22de576f53
|
|||
fd4157acbd
|
|||
11cf6bbf55
|
|||
a67ad3044f
|
|||
ee5eb13a01
|
|||
6ef1df2757
|
|||
16520c3664
|
|||
77c3ef9e74
|
|||
8d2ffe3014
|
|||
9ed97df4b5
|
64
.gitignore
vendored
@ -1,52 +1,26 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
dist
|
||||
build
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.swp
|
||||
*.egg-info
|
||||
_build
|
||||
.tox
|
||||
.coverage
|
||||
coverage
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# PyCharm project settings
|
||||
.idea
|
||||
|
||||
# VSCode project settings
|
||||
.vscode
|
||||
|
||||
# Local data
|
||||
secrets.py
|
||||
settings_local.py
|
||||
*.log
|
||||
media/
|
||||
output/
|
||||
/static/
|
||||
/static_files/
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# Virtualenv
|
||||
.env/
|
||||
env/
|
||||
.venv/
|
||||
venv/
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
node_modules/
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
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()
|
11141
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"
|
||||
]
|
||||
}
|
||||
}
|
5
public/bus.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="white" viewBox="0 0 35 35" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.384 5.25c.008-.04.068-.308.22-.672h19.792c.152.362.213.626.218.663 1.047 4.827 1.612 9.395 1.696 13.604H5.69c.079-4.201.65-8.774 1.694-13.596zm19.178 22.7c-1.398 0-2.532-1.082-2.532-2.418s1.134-2.418 2.532-2.418c1.4 0 2.532 1.082 2.532 2.418s-1.133 2.418-2.532 2.418zm-18.313 0c-1.4 0-2.53-1.082-2.53-2.418s1.13-2.418 2.53-2.418c1.398 0 2.534 1.082 2.534 2.418S9.647 27.95 8.25 27.95zM4.223 10.5c-1.636 11.16-.15 17.16-.076 17.452.186 1.014.67 1.607 1.45 1.82.004.804.006 3.44.006 3.44 0 .987.842 1.788 1.878 1.788h1.467c1.033 0 1.877-.8 1.877-1.789l-.008-3.336H23.91v3.333c0 .989.842 1.792 1.872 1.792l1.746-.005c.97 0 1.601-.7 1.601-1.787v-3.45c.923-.243 1.61-.935 1.741-1.881l-.013.07c.048-.186.692-2.77.692-7.608 0-2.624-.193-5.909-.766-9.836.29.216.665.322 1.055.263.75-.112 1.265-.787 1.146-1.503l-.407-2.456c-.119-.718-.823-1.207-1.573-1.094a1.378 1.378 0 0 0-.946.6c-.11-.56-.224-1.128-.35-1.706V4.6C29.39 3.013 27.735.019 23.736.005H20.68L11.272 0c-4.01.013-5.66 3.022-5.974 4.608-.129.58-.242 1.147-.352 1.707A1.373 1.373 0 0 0 4 5.713c-.75-.113-1.457.376-1.576 1.094l-.407 2.456c-.119.716.397 1.391 1.15 1.503.388.059.767-.05 1.056-.266z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
15
public/eurostar.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg version="1.0" width="1140.000000pt" height="260.000000pt" viewBox="0 0 1140.000000 260.000000" preserveAspectRatio="xMidYMid meet" id="svg22" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs26"/>
|
||||
<g transform="translate(0.000000,260.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none" id="g20" style="fill:#116bfe;fill-opacity:1">
|
||||
<path d="M1085 2579 c-194 -28 -429 -132 -592 -261 -499 -395 -632 -1129 -306 -1686 186 -318 511 -543 877 -607 128 -22 369 -20 493 4 375 73 671 296 852 642 66 125 124 301 133 399 2 30 0 35 -17 32 -32 -6 -41 -21 -78 -134 -44 -132 -88 -220 -162 -323 -181 -254 -444 -425 -742 -481 -135 -25 -385 -15 -513 21 -382 106 -686 398 -803 770 -40 128 -50 216 -44 387 7 221 56 383 169 566 63 101 232 279 327 342 396 264 889 269 1284 11 146 -96 305 -270 387 -425 17 -32 29 -61 27 -62 -3 -4 -537 -199 -830 -305 l-108 -39 -9 23 c-5 12 -23 180 -40 372 -17 193 -33 358 -36 368 -3 9 -14 17 -24 17 -21 0 -22 -5 -45 -260 -38 -407 -48 -505 -54 -512 -4 -4 -159 35 -346 87 -298 83 -342 93 -355 80 -9 -9 -11 -18 -5 -24 6 -5 133 -71 283 -146 149 -75 272 -141 272 -146 0 -5 -124 -71 -275 -146 -194 -97 -275 -143 -275 -154 0 -9 6 -19 13 -22 6 -2 156 35 332 84 176 49 330 89 341 89 19 0 22 -7 28 -77 7 -85 28 -302 51 -536 8 -81 15 -150 15 -153 0 -3 10 -4 22 -2 22 3 23 10 60 383 20 209 37 381 37 383 4 12 33 5 528 -134 89 -25 167 -43 173 -39 29 18 -14 47 -265 175 -148 76 -270 144 -270 150 0 6 192 105 427 219 235 114 434 213 441 220 13 10 13 18 2 54 -25 85 -77 193 -130 272 -179 270 -466 457 -795 519 -90 18 -351 20 -455 5z" id="path2" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M9060 2026 c-38 -62 -162 -169 -242 -208 -63 -31 -162 -61 -275 -84 -13 -2 -23 -11 -23 -19 0 -12 16 -15 79 -15 44 0 82 -4 85 -9 3 -5 -3 -35 -13 -67 -10 -33 -30 -102 -46 -154 -15 -52 -31 -108 -36 -125 -96 -312 -103 -422 -31 -498 50 -54 98 -71 193 -72 79 0 91 3 160 37 100 50 249 195 249 242 0 14 -10 11 -49 -18 -108 -80 -197 -98 -245 -50 -44 45 -40 68 74 449 23 77 48 164 56 194 8 29 17 58 20 62 3 5 70 9 148 9 141 0 144 0 150 23 15 58 18 57 -134 57 -77 0 -140 3 -140 8 0 4 18 63 40 132 22 69 40 128 40 133 0 4 -9 7 -19 7 -11 0 -28 -15 -41 -34z" id="path4" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M3654 1800 c-122 -18 -274 -91 -362 -174 -91 -85 -163 -224 -182 -349 -37 -249 89 -444 317 -492 232 -49 455 46 604 258 27 39 49 74 49 79 0 18 -24 6 -73 -37 -29 -25 -85 -62 -125 -82 -64 -32 -85 -37 -163 -41 -80 -4 -94 -2 -141 22 -91 45 -131 126 -132 261 0 193 114 424 238 485 23 11 54 20 68 20 30 0 73 -32 92 -68 23 -44 25 -226 4 -255 -8 -10 -77 -35 -173 -63 -258 -74 -244 -83 131 -84 160 0 292 4 307 9 21 8 28 20 37 68 16 75 7 189 -18 246 -68 153 -255 230 -478 197z" id="path6" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M6087 1800 c-108 -28 -197 -102 -336 -283 -45 -59 -87 -105 -92 -101 -11 7 -19 -7 113 191 59 90 108 165 108 168 0 3 -76 5 -169 5 -153 0 -170 -2 -175 -17 -12 -43 -236 -950 -236 -956 0 -4 70 -7 155 -7 l154 0 10 38 c6 20 35 136 65 258 61 245 80 300 131 379 63 99 164 147 232 111 36 -18 43 -33 43 -89 0 -50 -37 -133 -91 -204 -39 -53 -38 -74 3 -52 121 64 288 211 329 290 34 66 33 154 -3 201 -15 19 -42 42 -61 51 -43 21 -131 29 -180 17z" id="path8" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M6900 1803 c-77 -9 -165 -35 -238 -70 -107 -52 -225 -169 -275 -273 -102 -210 -83 -434 47 -564 186 -186 577 -157 798 59 104 101 169 239 183 390 8 85 -13 198 -49 259 -60 103 -173 175 -303 195 -71 11 -96 11 -163 4z m143 -77 c57 -35 77 -133 58 -281 -17 -127 -75 -343 -114 -428 -60 -130 -153 -200 -241 -183 -113 23 -141 143 -91 390 65 322 139 469 256 513 36 13 101 8 132 -11z" id="path10" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M7922 1800 c-224 -31 -351 -143 -354 -312 -1 -46 5 -74 20 -104 40 -79 99 -118 381 -252 30 -14 71 -42 93 -62 33 -33 38 -42 38 -83 0 -39 -6 -53 -35 -85 -41 -46 -90 -63 -184 -63 -82 0 -127 17 -165 62 -25 29 -28 39 -24 89 3 39 12 67 31 95 15 22 27 43 27 47 0 5 -65 8 -145 8 l-144 0 -12 -42 c-35 -133 53 -254 221 -305 78 -24 301 -23 390 1 194 53 310 172 310 318 0 134 -77 200 -390 335 -133 58 -173 154 -99 238 35 40 81 57 149 57 119 -1 196 -92 169 -197 -6 -22 -13 -50 -16 -63 l-5 -23 133 3 134 3 3 42 c8 113 -108 240 -255 278 -71 19 -195 25 -271 15z" id="path12" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M9775 1799 c-224 -29 -363 -132 -381 -282 l-7 -57 162 0 161 0 0 73 c1 137 52 199 172 205 80 5 118 -8 154 -52 25 -29 26 -35 21 -111 -4 -62 -27 -163 -42 -182 -1 -2 -95 -17 -210 -33 -246 -36 -281 -44 -360 -81 -152 -71 -225 -192 -195 -324 21 -94 84 -153 187 -176 71 -16 177 -2 253 34 74 35 185 130 239 206 25 33 50 61 58 61 7 0 13 -3 13 -6 0 -3 -34 -62 -75 -131 -41 -68 -75 -128 -75 -134 0 -5 67 -9 163 -9 l162 0 58 228 c89 345 99 391 99 467 3 227 -217 347 -557 304z m211 -511 c-10 -51 -82 -194 -122 -243 -111 -137 -328 -100 -300 50 10 50 67 109 140 145 62 30 221 78 265 79 21 1 23 -2 17 -31z" id="path14" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M11092 1790 c-29 -10 -68 -28 -87 -40 -44 -27 -154 -146 -230 -248 -33 -45 -66 -81 -74 -82 -20 0 -12 15 99 180 56 83 104 158 107 166 4 12 -21 14 -164 14 l-168 0 -119 -477 c-66 -263 -121 -484 -124 -491 -3 -10 29 -12 154 -10 l159 3 66 265 c74 294 105 375 174 449 76 81 173 103 221 50 41 -46 10 -168 -69 -272 -66 -86 -46 -89 71 -11 113 76 222 180 255 246 91 180 -67 332 -271 258z" id="path16" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
<path d="M4288 1428 c-103 -418 -102 -414 -88 -484 27 -131 154 -194 326 -165 132 23 257 108 364 246 40 52 60 67 60 45 0 -5 -34 -65 -75 -132 -41 -67 -75 -126 -75 -130 0 -4 74 -8 163 -8 l164 0 118 478 c65 262 120 483 123 490 3 10 -29 12 -154 10 l-158 -3 -8 -30 c-5 -16 -35 -142 -68 -279 -67 -276 -95 -347 -167 -424 -58 -62 -120 -91 -190 -92 -41 0 -58 5 -80 24 -25 21 -28 31 -26 78 0 29 37 198 82 375 45 178 81 329 81 338 0 13 -23 15 -153 15 l-152 0 -87 -352z" id="path18" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.8 KiB |
7
public/eurostar_mini.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg version="1.0" width="260.000000pt" height="260.000000pt" viewBox="0 0 260.000000 260.000000" preserveAspectRatio="xMidYMid meet" id="svg22" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs26"/>
|
||||
<g transform="translate(0.000000,260.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none" id="g20" style="fill:#116bfe;fill-opacity:1">
|
||||
<path d="M1085 2579 c-194 -28 -429 -132 -592 -261 -499 -395 -632 -1129 -306 -1686 186 -318 511 -543 877 -607 128 -22 369 -20 493 4 375 73 671 296 852 642 66 125 124 301 133 399 2 30 0 35 -17 32 -32 -6 -41 -21 -78 -134 -44 -132 -88 -220 -162 -323 -181 -254 -444 -425 -742 -481 -135 -25 -385 -15 -513 21 -382 106 -686 398 -803 770 -40 128 -50 216 -44 387 7 221 56 383 169 566 63 101 232 279 327 342 396 264 889 269 1284 11 146 -96 305 -270 387 -425 17 -32 29 -61 27 -62 -3 -4 -537 -199 -830 -305 l-108 -39 -9 23 c-5 12 -23 180 -40 372 -17 193 -33 358 -36 368 -3 9 -14 17 -24 17 -21 0 -22 -5 -45 -260 -38 -407 -48 -505 -54 -512 -4 -4 -159 35 -346 87 -298 83 -342 93 -355 80 -9 -9 -11 -18 -5 -24 6 -5 133 -71 283 -146 149 -75 272 -141 272 -146 0 -5 -124 -71 -275 -146 -194 -97 -275 -143 -275 -154 0 -9 6 -19 13 -22 6 -2 156 35 332 84 176 49 330 89 341 89 19 0 22 -7 28 -77 7 -85 28 -302 51 -536 8 -81 15 -150 15 -153 0 -3 10 -4 22 -2 22 3 23 10 60 383 20 209 37 381 37 383 4 12 33 5 528 -134 89 -25 167 -43 173 -39 29 18 -14 47 -265 175 -148 76 -270 144 -270 150 0 6 192 105 427 219 235 114 434 213 441 220 13 10 13 18 2 54 -25 85 -77 193 -130 272 -179 270 -466 457 -795 519 -90 18 -351 20 -455 5z" id="path2" style="fill:#FCF5D8;fill-opacity:1"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
18
public/frecciarossa.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 2244.00 378.00">
|
||||
<path fill="#e41c39" d=" M 1374.31 0.00 L 1778.81 0.00 C 1829.24 0.91 1881.04 4.11 1929.49 15.53 C 1993.49 30.62 2053.11 64.39 2103.31 105.20 C 2142.17 136.79 2173.33 159.00 2208.45 185.82 C 2221.50 195.79 2239.69 209.40 2244.00 223.57 L 2244.00 228.90 Q 2240.70 236.11 2231.46 238.97 C 2213.75 244.45 2196.00 245.52 2177.99 245.48 C 2129.56 245.39 2086.47 225.72 2046.36 200.37 C 2010.11 177.46 1969.60 147.19 1940.81 126.94 C 1892.18 92.73 1840.25 62.02 1783.77 42.71 Q 1726.85 23.26 1668.94 19.58 Q 1658.41 18.91 1619.94 17.54 Q 1495.78 13.11 1372.18 0.63 Q 1371.95 0.60 1372.03 0.38 Q 1372.09 0.22 1372.24 0.25 Q 1373.50 0.45 1374.31 0.00 Z"/>
|
||||
<path fill="#212733" d=" M 1200.44 115.44 C 1222.72 90.95 1256.33 84.12 1288.49 88.49 C 1313.77 91.92 1325.14 107.52 1318.08 132.66 C 1302.64 187.61 1261.87 224.02 1201.73 216.38 C 1141.50 208.73 1179.24 138.73 1200.44 115.44 Z M 1268.98 116.03 C 1235.51 112.38 1216.56 140.72 1208.28 168.66 C 1204.61 181.05 1207.57 188.14 1221.25 188.99 C 1252.69 190.95 1269.52 165.69 1278.11 139.53 C 1281.29 129.86 1283.04 117.57 1268.98 116.03 Z"/>
|
||||
<path fill="#212733" d=" M 178.75 138.50 L 251.22 138.50 Q 251.80 138.50 251.56 139.03 L 239.15 166.17 A 1.00 0.99 -77.3 0 1 238.25 166.75 L 165.02 166.75 Q 164.49 166.75 164.27 167.24 L 142.81 214.25 Q 142.58 214.75 142.03 214.75 L 107.23 214.75 Q 106.50 214.75 106.80 214.08 Q 121.35 182.00 136.15 149.92 C 148.48 123.20 161.98 101.63 192.25 93.75 Q 207.26 89.84 223.81 89.67 Q 248.69 89.41 273.69 89.52 A 0.33 0.33 0.0 0 1 273.99 89.98 L 261.40 117.57 A 0.71 0.70 -77.5 0 1 260.76 117.98 Q 238.06 118.04 215.53 118.01 Q 192.00 117.97 178.53 138.09 Q 178.26 138.50 178.75 138.50 Z"/>
|
||||
<path fill="#212733" d=" M 297.68 89.93 A 0.72 0.70 -77.5 0 1 298.32 89.51 Q 331.91 89.46 365.75 89.54 C 391.20 89.60 409.12 98.13 397.90 127.43 Q 387.32 155.06 358.52 164.68 Q 357.93 164.87 358.03 165.49 L 365.76 214.07 A 0.59 0.59 0.0 0 1 365.18 214.75 L 328.77 214.75 A 0.94 0.94 0.0 0 1 327.84 213.95 L 321.28 169.47 A 0.56 0.55 -4.2 0 0 320.73 169.00 L 297.78 169.00 Q 297.38 169.00 297.22 169.36 L 276.70 214.27 Q 276.48 214.75 275.96 214.75 L 241.49 214.75 A 0.53 0.53 0.0 0 1 241.01 214.00 L 297.68 89.93 Z M 319.95 120.02 L 311.09 139.31 Q 310.81 139.93 311.49 139.94 Q 327.21 140.18 342.94 139.86 Q 358.53 139.54 362.91 125.15 Q 363.54 123.07 363.57 122.84 C 363.96 119.42 354.81 119.01 352.75 119.00 Q 336.67 118.99 320.59 119.01 Q 320.18 119.01 320.03 119.39 Q 319.97 119.53 320.00 119.70 Q 320.02 119.87 319.95 120.02 Z"/>
|
||||
<path fill="#212733" d=" M 452.24 137.25 L 521.53 137.25 A 0.57 0.56 -77.8 0 1 522.04 138.05 L 509.48 165.52 Q 509.26 166.00 508.72 166.00 L 437.54 166.00 Q 437.14 166.00 436.99 166.37 C 432.13 178.79 432.96 186.25 448.54 186.24 Q 473.99 186.21 499.44 186.27 Q 500.00 186.27 499.76 186.78 L 487.23 214.20 A 0.89 0.88 -77.3 0 1 486.43 214.71 Q 462.14 214.84 438.01 214.71 C 399.97 214.50 387.18 191.81 401.76 157.22 C 418.80 116.77 444.48 90.34 490.81 89.77 Q 517.24 89.44 543.53 89.49 A 0.45 0.44 12.2 0 1 543.94 90.12 L 531.45 117.46 A 0.89 0.88 -77.3 0 1 530.65 117.97 Q 512.54 118.06 494.54 117.99 Q 480.61 117.94 475.08 119.40 Q 460.61 123.21 451.88 136.59 Q 451.46 137.25 452.24 137.25 Z"/>
|
||||
<path fill="#212733" d=" M 538.00 169.63 C 550.34 125.86 582.70 90.53 630.97 89.66 Q 652.31 89.28 684.00 89.58 Q 684.55 89.59 684.32 90.08 L 671.85 117.30 Q 671.66 117.72 671.19 117.73 Q 652.37 117.80 633.63 117.75 Q 619.99 117.72 614.51 119.20 C 594.66 124.56 584.69 146.60 577.11 163.59 C 574.05 170.46 570.41 180.21 579.55 184.44 Q 583.34 186.19 587.06 186.21 Q 613.46 186.29 639.54 186.24 A 0.53 0.52 12.2 0 1 640.02 186.98 L 627.54 214.31 Q 627.35 214.72 626.90 214.73 Q 602.22 214.81 577.50 214.72 C 549.70 214.61 529.45 199.98 538.00 169.63 Z"/>
|
||||
<path fill="#212733" d=" M 668.29 166.77 C 681.63 123.97 713.43 90.46 760.96 89.73 Q 787.18 89.33 813.51 89.54 A 0.44 0.44 0.0 0 1 813.90 90.16 L 801.48 117.24 A 0.87 0.87 0.0 0 1 800.69 117.75 Q 781.60 117.76 762.34 117.75 Q 749.65 117.74 744.29 119.25 C 723.27 125.16 712.81 148.71 705.43 167.11 C 701.14 177.80 704.15 186.14 716.82 186.19 Q 743.01 186.31 769.23 186.23 A 0.48 0.48 0.0 0 1 769.67 186.91 L 757.13 214.32 Q 756.93 214.76 756.45 214.76 Q 743.82 214.76 707.76 214.71 C 677.56 214.68 658.33 198.70 668.29 166.77 Z"/>
|
||||
<path fill="#212733" d=" M 852.81 89.50 L 889.07 89.50 A 0.37 0.37 0.0 0 1 889.41 90.02 L 832.50 214.53 A 0.37 0.37 0.0 0 1 832.16 214.75 L 795.94 214.75 A 0.37 0.37 0.0 0 1 795.60 214.23 L 852.47 89.72 A 0.37 0.37 0.0 0 1 852.81 89.50 Z"/>
|
||||
<path fill="#212733" d=" M 963.71 89.50 L 997.25 89.50 A 0.33 0.33 0.0 0 1 997.58 89.83 L 997.20 214.42 A 0.33 0.33 0.0 0 1 996.87 214.75 L 959.80 214.75 A 0.33 0.33 0.0 0 1 959.47 214.41 L 960.62 187.09 A 0.33 0.33 0.0 0 0 960.29 186.75 L 913.50 186.75 A 0.33 0.33 0.0 0 0 913.25 186.87 L 889.72 214.63 A 0.33 0.33 0.0 0 1 889.47 214.75 L 851.12 214.75 A 0.33 0.33 0.0 0 1 850.88 214.20 L 963.46 89.61 A 0.33 0.33 0.0 0 1 963.71 89.50 Z M 961.94 130.44 Q 950.71 143.69 939.23 156.88 Q 938.69 157.50 939.51 157.50 L 961.19 157.50 Q 961.88 157.50 961.91 156.81 L 963.07 130.23 A 0.47 0.47 0.0 0 0 962.39 129.79 Q 961.99 129.99 961.94 130.44 Z"/>
|
||||
<path fill="#212733" d=" M 1093.80 169.00 L 1071.20 169.00 A 1.01 1.01 0.0 0 0 1070.28 169.59 L 1049.83 214.36 Q 1049.65 214.75 1049.22 214.75 L 1014.52 214.75 A 0.45 0.45 0.0 0 1 1014.11 214.11 L 1070.70 90.24 Q 1071.03 89.53 1071.81 89.53 Q 1105.76 89.44 1140.24 89.52 C 1156.64 89.55 1178.71 94.70 1174.28 116.77 C 1169.81 139.04 1154.02 157.88 1131.83 164.51 A 0.92 0.92 0.0 0 0 1131.19 165.53 L 1138.97 214.43 A 0.28 0.28 0.0 0 1 1138.69 214.75 L 1101.56 214.75 A 0.52 0.52 0.0 0 1 1101.05 214.31 L 1094.44 169.56 A 0.65 0.65 0.0 0 0 1093.80 169.00 Z M 1093.31 119.56 L 1084.28 139.28 Q 1083.96 139.96 1084.71 139.97 Q 1094.76 140.07 1104.82 140.02 C 1121.37 139.94 1132.75 140.21 1136.76 122.66 A 1.47 1.45 -70.4 0 0 1135.99 121.02 Q 1131.90 118.99 1126.75 118.99 Q 1110.25 118.97 1094.16 119.02 A 0.94 0.93 12.3 0 0 1093.31 119.56 Z"/>
|
||||
<path fill="#212733" d=" M 1367.00 166.98 C 1351.35 167.00 1330.18 161.43 1334.95 140.26 C 1342.11 108.47 1367.59 89.66 1399.81 89.56 Q 1438.65 89.44 1477.51 89.52 A 0.49 0.49 0.0 0 1 1477.96 90.22 L 1465.09 118.32 Q 1464.89 118.75 1464.42 118.75 Q 1429.79 118.74 1395.28 118.80 Q 1379.87 118.82 1373.64 132.53 C 1372.11 135.90 1373.91 138.38 1377.44 138.41 C 1393.80 138.56 1410.76 137.63 1427.01 139.20 C 1460.30 142.42 1449.42 172.07 1437.36 189.38 C 1423.62 209.10 1402.88 214.68 1379.94 214.72 Q 1342.39 214.79 1305.22 214.74 Q 1304.38 214.73 1304.73 213.97 L 1317.48 186.02 A 0.91 0.89 12.2 0 1 1318.30 185.50 Q 1354.35 185.57 1390.73 185.29 Q 1405.11 185.17 1409.52 171.80 Q 1410.00 170.36 1409.62 168.97 A 0.99 0.98 -87.0 0 0 1409.03 168.31 Q 1405.99 167.14 1402.69 167.11 Q 1384.84 166.96 1367.00 166.98 Z"/>
|
||||
<path fill="#212733" d=" M 1494.44 109.44 C 1509.40 93.39 1528.45 89.60 1549.55 89.54 Q 1585.44 89.43 1621.33 89.53 Q 1621.83 89.53 1621.62 89.98 L 1608.68 118.24 Q 1608.46 118.74 1607.91 118.74 Q 1574.07 118.78 1540.25 118.74 C 1529.39 118.73 1519.65 123.79 1516.48 134.77 Q 1516.34 135.24 1516.57 135.67 Q 1518.10 138.52 1520.82 138.50 Q 1550.37 138.32 1565.25 138.69 Q 1571.48 138.84 1579.28 140.88 C 1605.74 147.79 1586.91 182.69 1576.81 194.47 C 1563.36 210.18 1544.20 214.66 1524.39 214.72 Q 1486.47 214.82 1448.54 214.72 A 0.38 0.37 -77.8 0 1 1448.20 214.19 L 1461.03 186.04 Q 1461.28 185.50 1461.87 185.50 Q 1494.43 185.51 1526.91 185.46 C 1540.35 185.44 1550.55 184.27 1553.34 169.97 A 1.71 1.71 0.0 0 0 1552.07 167.99 Q 1549.10 167.25 1546.18 167.21 Q 1528.34 166.97 1510.50 166.99 C 1498.51 167.00 1482.19 164.13 1478.78 150.94 C 1475.39 137.82 1485.34 119.20 1494.44 109.44 Z"/>
|
||||
<path fill="#212733" d=" M 1690.71 89.50 L 1724.17 89.50 A 0.39 0.39 0.0 0 1 1724.56 89.89 L 1724.18 214.36 A 0.39 0.39 0.0 0 1 1723.79 214.75 L 1686.83 214.75 A 0.39 0.39 0.0 0 1 1686.44 214.34 L 1687.59 187.16 A 0.39 0.39 0.0 0 0 1687.20 186.75 L 1640.52 186.75 A 0.39 0.39 0.0 0 0 1640.22 186.89 L 1616.70 214.61 A 0.39 0.39 0.0 0 1 1616.40 214.75 L 1578.23 214.75 A 0.39 0.39 0.0 0 1 1577.94 214.10 L 1690.42 89.63 A 0.39 0.39 0.0 0 1 1690.71 89.50 Z M 1688.94 130.44 Q 1677.62 143.65 1666.25 156.92 Q 1665.74 157.50 1666.51 157.50 L 1688.18 157.50 Q 1688.86 157.50 1688.89 156.82 L 1690.04 130.22 A 0.46 0.45 -12.7 0 0 1689.37 129.80 Q 1688.99 130.00 1688.94 130.44 Z"/>
|
||||
<path fill="#e41c39" d=" M 2110.65 378.00 L 2070.19 378.00 Q 2052.67 376.66 2031.50 373.51 C 1721.34 327.28 1406.93 310.07 1093.56 311.19 Q 676.72 312.68 261.93 353.87 Q 164.86 363.51 68.14 376.18 Q 60.11 377.23 52.30 377.25 Q 27.74 377.30 2.22 376.90 Q -0.44 376.85 2.17 376.36 C 204.15 337.88 409.11 314.70 614.07 299.69 Q 949.49 275.13 1285.81 277.48 C 1435.94 278.53 1586.36 283.86 1736.07 295.29 Q 1852.62 304.18 1968.28 321.03 Q 2002.91 326.08 2037.74 329.32 C 2084.15 333.63 2131.41 335.52 2177.74 329.51 C 2196.45 327.08 2236.13 318.94 2243.35 298.05 Q 2243.49 297.64 2243.76 297.98 Q 2243.94 298.22 2243.81 298.53 C 2220.62 351.89 2165.09 374.20 2110.65 378.00 Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 9.7 KiB |
9
public/ice.svg
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="1.8cm" height="1cm" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" viewBox="0 0 18 10" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Ebene_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<path fill="#B4B4B4" d="M4.3579 4.9781c-0.4772,2.8024 0.6168,4.1979 3.1041,4.2241 0.8818,0.0092 1.8783,-0.2956 1.9975,-0.3663l0.229 -1.2432c-0.555,0.1967 -1.0499,0.3536 -1.7118,0.3412 -0.8359,-0.0157 -1.6762,-0.2365 -1.6981,-1.7361 -0.0315,-2.1452 0.7768,-3.9344 2.7285,-3.9707 0.6681,-0.0125 0.9846,0.1048 1.5886,0.3435l0.241 -1.3081c-0.4988,-0.2078 -0.8895,-0.3933 -2.0459,-0.4144 -2.4648,-0.0449 -4.0281,1.7523 -4.4329,4.13zm-3.6799 4.12l1.9109 -0.0011 1.4863 -8.0684 -1.9371 0 -1.4601 8.0695zm11.2907 -8.0695l-1.5412 8.0695 5.6172 -0.0006 0.2395 -1.3003 -3.7164 0 0.337 -2.0365 2.7499 0.0005 0.2488 -1.3508 -2.7181 -0.0018 0.4078 -1.9854 3.5066 -0.0002 0.2568 -1.3943 -5.3879 -0.0001z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" data-bs-theme="dark">
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
@ -7,7 +7,7 @@
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
content="Écrans en gare affichant les horaires des trains des gares."
|
||||
/>
|
||||
<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 |
17
public/lyria.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="424.089px" height="153.688px" viewBox="0 0 424.089 153.688" enable-background="new 0 0 424.089 153.688" xml:space="preserve">
|
||||
<rect fill="#E5162B" width="424.089" height="153.688"/>
|
||||
<rect x="323.143" y="29.171" fill="#FFFFFF" width="14.464" height="14.467"/>
|
||||
<path fill="#FFFFFF" d="M323.143,49.187c-1.373-0.006-15.067-1.635-20.262,8.879l0.006-7.587H279.74l-12.806,38.417l-13.145-38.417 h-15.531l14.562,38.557h-28.956V29.164h-16.076v73.066h49.842c0,0,0.505,1.194,0.613,1.566c0.944,3.174-0.875,8.292-6.044,8.292 c-2.933,0-5.86,0.011-8.702,0.011v12.427c2.639,0,9.778-0.026,12.817-0.026c6.381,0,12.116-4.628,15.368-13.271l17.387-46.92v37.915 h14.538V80.15c0-20.61,17.899-17.467,19.53-17.439v39.52h14.46V58.09h-14.456V49.187z"/>
|
||||
<polygon fill="#FFFFFF" points="354.447,35.021 355.527,35.021 355.527,32.632 357.921,32.632 357.921,31.555 355.527,31.555 355.527,29.17 354.447,29.17 354.447,31.555 352.07,31.555 352.07,32.632 354.447,32.632 "/>
|
||||
<polygon fill="#FFFFFF" points="354.447,43.637 355.527,43.637 355.527,41.25 357.921,41.25 357.921,40.175 355.527,40.175 355.527,37.795 354.447,37.795 354.447,40.175 352.07,40.175 352.07,41.25 354.447,41.25 "/>
|
||||
<polygon fill="#FFFFFF" points="371.695,35.021 372.795,35.021 372.795,32.632 375.168,32.632 375.168,31.555 372.795,31.555 372.795,29.17 371.695,29.17 371.695,31.555 369.32,31.555 369.32,32.632 371.695,32.632 "/>
|
||||
<polygon fill="#FFFFFF" points="363.088,35.021 364.153,35.021 364.153,32.632 366.532,32.632 366.532,31.555 364.153,31.555 364.153,29.17 363.088,29.17 363.088,31.555 360.69,31.555 360.69,32.632 363.088,32.632 "/>
|
||||
<polygon fill="#FFFFFF" points="363.088,43.637 364.153,43.637 364.153,41.25 366.532,41.25 366.532,40.175 364.153,40.175 364.153,37.795 363.088,37.795 363.088,40.175 360.69,40.175 360.69,41.25 363.088,41.25 "/>
|
||||
<polygon fill="#FFFFFF" points="46.873,53.057 28.896,53.057 28.896,43.614 75.29,43.614 75.29,53.057 57.312,53.057 57.312,102.268 46.873,102.268 "/>
|
||||
<path fill="#FFFFFF" d="M133.688,97.96c-7.125,3.81-15.242,5.798-24.275,5.798c-18.059,0-30.983-12.262-30.983-30.567 c0-18.808,12.924-31.068,30.983-31.068c8.949,0,16.985,1.905,23.115,7.373l-7.706,7.788c-3.725-3.647-9.442-5.717-15.325-5.717 c-12.097,0-20.131,9.278-20.131,20.959c0,12.512,8.034,21.79,20.131,21.79c5.304,0,10.106-0.995,13.751-3.065V77.415h-12.011V67.97 h22.451V97.96z"/>
|
||||
<polygon fill="#FFFFFF" points="137.57,43.615 149.583,43.615 165.571,87.937 182.057,43.615 193.325,43.615 169.628,102.268 160.601,102.268 "/>
|
||||
<path fill="#FFFFFF" d="M347.324,57.896c1.477-2.216,3.362-3.993,5.649-5.338c2.289-1.341,4.863-2.298,7.725-2.87 c2.858-0.567,5.737-0.855,8.628-0.855c2.627,0,5.285,0.186,7.978,0.555c2.689,0.369,5.146,1.092,7.367,2.163 c2.223,1.075,4.04,2.568,5.452,4.483c1.414,1.913,2.119,4.446,2.119,7.601v27.083c0,2.35,0.136,4.6,0.404,6.745 c0.27,2.151,0.74,3.761,1.414,4.835h-14.536c-0.27-0.807-0.489-1.627-0.657-2.468c-0.169-0.839-0.284-1.695-0.353-2.567 c-2.288,2.351-4.981,3.994-8.076,4.932c-3.096,0.94-6.257,1.411-9.487,1.411c-2.493,0-4.812-0.302-6.968-0.906 c-2.154-0.605-4.038-1.543-5.651-2.821c-1.615-1.274-2.876-2.886-3.787-4.832c-0.907-1.948-1.361-4.262-1.361-6.948 c0-2.952,0.521-5.386,1.564-7.299c1.044-1.912,2.387-3.438,4.038-4.581c1.647-1.142,3.532-1.997,5.653-2.568 c2.12-0.57,4.256-1.022,6.41-1.359c2.151-0.334,4.273-0.604,6.358-0.804c2.086-0.202,3.937-0.504,5.555-0.906 c1.613-0.404,2.889-0.989,3.835-1.763c0.941-0.77,1.378-1.896,1.31-3.374c0-1.543-0.251-2.768-0.756-3.673 c-0.505-0.908-1.177-1.611-2.02-2.116c-0.839-0.503-1.815-0.837-2.926-1.006c-1.112-0.167-2.307-0.251-3.584-0.251 c-2.827,0-5.048,0.603-6.661,1.812c-1.618,1.207-2.56,3.221-2.829,6.04h-14.333C345,62.896,345.84,60.11,347.324,57.896 M375.637,78.082c-0.907,0.304-1.884,0.553-2.928,0.755c-1.044,0.201-2.136,0.372-3.278,0.503c-1.145,0.137-2.291,0.304-3.433,0.503 c-1.078,0.203-2.137,0.475-3.182,0.807c-1.044,0.338-1.951,0.79-2.726,1.359c-0.772,0.572-1.396,1.293-1.868,2.166 c-0.469,0.874-0.704,1.978-0.704,3.322c0,1.276,0.235,2.35,0.704,3.222c0.473,0.871,1.112,1.561,1.919,2.063 c0.81,0.503,1.751,0.856,2.829,1.057c1.072,0.204,2.185,0.302,3.33,0.302c2.826,0,5.014-0.469,6.561-1.409 c1.547-0.94,2.691-2.065,3.432-3.374c0.742-1.308,1.194-2.633,1.363-3.977c0.167-1.342,0.251-2.417,0.251-3.222v-5.336 C377.304,77.363,376.548,77.781,375.637,78.082"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
18
public/nightjet.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style="overflow:visible"
|
||||
enable-background="new 0 0 1371.57 281.204"
|
||||
overflow="visible"
|
||||
viewBox="0 0 947.40521 151.96"
|
||||
height="151.96001"
|
||||
width="947.40515"
|
||||
id="svg3336"
|
||||
version="1.1">
|
||||
<g id="g7806" transform="translate(68.810197,566.846)">
|
||||
<path
|
||||
id="path7808"
|
||||
d="m 461.858,-518.956 -3.649,15.83 -6.74,29.12 -5.62,24.36 c -0.3,1.29 -3.7,2.76 -8.46,2.76 h -22.96 c -4.68,-0.03 -7.33,-1.48 -7.05,-2.76 l 5.62,-24.36 6.72,-29.12 1.04,-4.5 c 0.53,-2.25 -3.53,-3.6 -7.92,-3.6 h -52.7 c -0.149,0.62 -0.25,1.11 -0.3,1.35 l -13.61,60.23 c 0,0 0,0.02 0,0.03 -0.199,0.771 -1.21,1.35 -2.55,1.79 -0.13,0.04 -0.25,0.08 -0.37,0.12 -0.569,0.16 -1.149,0.3 -1.779,0.42 -0.44,0.07 -0.91,0.15 -1.41,0.2 -0.92,0.12 -1.79,0.18 -2.57,0.19 -0.05,0 -0.1,0.01 -0.16,0.01 h -0.3 -1.37 -22.52 -0.431 c -4.699,-0.03 -7.34,-1.48 -7.04,-2.76 l 18.29,-81.15 c 0,-0.02 0.021,-0.02 0.021,-0.02 l 7.52,-33.39 c 0.28,-1.19 3.88,-2.62 8.87,-2.62 h 23.73 c 5.26,0 8.189,1.47 7.92,2.62 l -6.9,30.68 c 0,0 -0.04,0.09 -0.04,0.17 20.24,0.07 69.32,0.28 69.32,0.28 3.649,0 7.06,0.29 10.14,0.83 11.548,1.98 18.639,7.31 17.258,13.29 m 90.86,-11.97 -3.319,14.66 c -0.28,1.29 -3.841,2.77 -8.851,2.77 l -9.8,-0.01 c -5.12,0.03 -9.99,1.35 -10.5,3.62 l -13.61,60.24 c -0.28,1.3 -3.84,2.78 -8.85,2.78 h -24.19 c -4.939,-0.03 -7.779,-1.49 -7.47,-2.78 l 25.84,-114.58 c 0.271,-1.18 3.89,-2.62 8.88,-2.62 h 23.721 c 5.239,0 8.189,1.48 7.939,2.62 l -6.92,30.69 h 19.18 c 5.261,0 8.23,1.41 7.95,2.61 m 325.86,0.2 -3.311,14.61 c -0.29,1.29 -3.83,2.76 -8.819,2.76 h -9.761 c -5.109,0.02 -9.96,1.33 -10.47,3.59 l -13.56,60.04 c -0.29,1.3 -3.83,2.77 -8.82,2.77 h -24.11 c -4.939,-0.02 -7.75,-1.48 -7.449,-2.77 l 25.76,-114.2 c 0.27,-1.16 3.87,-2.59 8.83,-2.59 h 23.66 c 5.22,0 8.159,1.47 7.899,2.59 l -6.899,30.6 h 19.14 c 5.21,0 8.18,1.4 7.91,2.6 m -269.179,-34.37 c 4.84,0 7.76,1.38 7.5,2.55 l -3.2,14.24 c -0.45,1.93 -5.96,2.729 -8.99,2.71 h -23.43 c -4.841,-0.02 -7.601,-1.48 -7.33,-2.71 l 3.22,-14.24 c 0.32,-1.15 3.8,-2.55 8.67,-2.55 z m -22.731,136.48 c -1.61,7.189 -16.99,13.73 -34.811,13.73 -4.8,-0.011 -7.75,-1.17 -7.449,-2.531 l 25.37,-112.37 c 0.31,-1.16 3.81,-2.56 8.68,-2.56 l 23.26,0.01 c 5.14,-0.01 8.04,1.4 7.79,2.55 z m 159.02,-80.72 c 0.62,-2.73 -3.55,-3.99 -8.05,-4.02 h -55.069 c -4.75,0 -9.65,1.29 -10.141,3.53 l -2.479,10.93 h 73.39 z m 30.7,43.17 -3.729,16.54 c -0.271,1.24 -3.82,2.71 -8.83,2.71 h -114.58 c -17.72,0 -29.97,-6.56 -28.3,-13.86 l 13.25,-58.73 c 1.63,-7.22 17.43,-13.69 35.26,-13.69 h 90.14 c 17.69,0.07 30.53,6.18 28.84,13.69 l -7.76,34.3 c -0.34,1.53 -3.899,2.99 -8.89,2.99 l -105.601,0.01 -2.149,9.57 c -0.57,2.49 3.71,3.82 8.34,3.82 h 96.39 c 5.229,0.08 7.88,1.52 7.619,2.65 m -628.42,-97.63 c 4.59,0 7.34,1.37 7.07,2.53 l -3.271,14.14 c -0.439,1.92 -5.68,2.72 -8.529,2.7 h -22.181 c -4.609,-0.02 -7.189,-1.48 -6.91,-2.7 l 3.261,-14.14 c 0.319,-1.15 3.649,-2.53 8.26,-2.53 z m -7.79,32.54 c 4.87,-0.01 7.61,1.39 7.351,2.54 l -18.261,79.05 c -0.439,1.91 -5.68,2.72 -8.54,2.7 h -22.19 c -4.58,-0.03 -7.19,-1.45 -6.87,-2.7 l 18.23,-79.05 c 0.319,-1.15 3.649,-2.55 8.26,-2.55 z m -52.46,12.08 -3.01,13.03 -6.74,29.19 -6.29,27.26 c -0.16,0.67 -1.16,1.4 -2.77,1.94 -1.471,0.49 -3.44,0.82 -5.7,0.82 h -23.03 c -2.21,-0.01 -3.97,-0.35 -5.189,-0.82 -1.37,-0.54 -2.021,-1.27 -1.87,-1.94 l 6.29,-27.26 6.74,-29.19 0.859,-3.74 c 0.53,-2.27 -3.56,-3.59 -7.96,-3.59 h -54.56 l -1.681,7.33 -6.75,29.19 -6.279,27.26 c -0.16,0.67 -1.181,1.4 -2.78,1.94 -1.47,0.49 -3.42,0.82 -5.72,0.82 h -23.011 c -2.21,-0.01 -3.97,-0.35 -5.189,-0.82 -1.351,-0.54 -2.04,-1.27 -1.86,-1.94 l 6.28,-27.26 6.74,-29.19 5.68,-24.58 c 0.33,-1.18 3.729,-2.61 8.47,-2.61 h 101.88 c 17.32,0 29.21,6.55 27.45,14.16 m 165.88,4.84 c 4.17,0 8.05,1.37 7.62,3.35 l -0.56,2.45 -0.28,1.22 -6.44,27.89 -1.01,4.37 c -0.47,2.02 -4.979,3.37 -9.22,3.37 h -43.54 c -4.23,0 -8.13,-1.35 -7.66,-3.37 l 1,-4.37 6.45,-27.89 0.84,-3.64 c 0.46,-2.01 4.99,-3.38 9.16,-3.38 z m 20.28,-18.96 h -75.44 c -16.56,0 -30.8,6.26 -32.479,13.51 l -2.87,12.47 -0.83,3.55 -0.13,0.22 -6.46,27.9 -3.49,15.15 c -1.32,5.74 5.47,10.84 16.53,12.74 2.95,0.52 6.22,0.79 9.72,0.79 h 68.01 l -2.27,9.79 c -0.47,2.05 -5.01,3.27 -9.18,3.27 h -81.5 c -4.391,0.01 -7.58,1.33 -7.99,2.44 l -0.641,2.67 c -1.68,7.331 9.671,13.581 26.24,13.581 h 75.79 c 16.36,0 30.49,-6.17 32.16,-13.34 l 0.69,-2.91 3.739,-16.29 3.83,-16.58 c 0,0 0.011,-0.01 0.011,-0.03 l 3.479,-15.06 6.43,-27.89 2.88,-12.47 c 1.681,-7.251 -9.679,-13.511 -26.229,-13.511"
|
||||
style="fill:white;fill-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
52
public/ouigo.svg
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="230.88875" height="230.86626" id="svg2" version="1.1" inkscape:version="0.48.4 r9939" sodipodi:docname="Nouveau document 1">
|
||||
<defs id="defs4">
|
||||
<clipPath id="clipPath3001" clipPathUnits="userSpaceOnUse">
|
||||
<path id="path3003" d="m 120.688,3.172 353.899,0 0,365.373 -353.899,0 0,-365.373 z" inkscape:connector-curvature="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.35" inkscape:cx="122.4405" inkscape:cy="412.81147" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" fit-margin-top="1" fit-margin-left="1" fit-margin-right="1" fit-margin-bottom="1" inkscape:window-width="1920" inkscape:window-height="1017" inkscape:window-x="-8" inkscape:window-y="-8" inkscape:window-maximized="1"/>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:label="Calque 1" inkscape:groupmode="layer" id="layer1" transform="translate(-252.5595,-714.3074)">
|
||||
<g id="g2997" transform="matrix(1.25,0,0,-1.25,2.953125,1058.5434)">
|
||||
<g id="g2999" clip-path="url(#clipPath3001)">
|
||||
<g id="g3005" transform="translate(292.7454,251.5881)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c -6.325,0 -10.821,5.154 -10.821,11.29 0,6.126 4.391,11.181 10.725,11.181 6.339,0 10.825,-5.164 10.825,-11.299 C 10.729,5.06 6.337,0 0,0" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3007"/>
|
||||
</g>
|
||||
<g id="g3009" transform="translate(292.7454,91.7101)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c -6.325,0 -10.821,5.147 -10.821,11.284 0,6.123 4.391,11.172 10.725,11.172 6.339,0 10.825,-5.145 10.825,-11.292 C 10.729,5.053 6.337,0 0,0" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3011"/>
|
||||
</g>
|
||||
<g id="g3013" transform="translate(383.5961,183.0498)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c 0,-50.582 -40.979,-91.554 -91.548,-91.554 -50.59,0 -91.563,40.972 -91.563,91.554 0,50.518 40.973,91.539 91.563,91.539 C -40.979,91.539 0,50.518 0,0" style="fill:#e3006a;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3015"/>
|
||||
</g>
|
||||
<g id="g3017" transform="translate(250.5055,179.1495)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 0,16.799 c 0,2.19 1.729,3.934 3.932,3.934 2.191,0 3.936,-1.744 3.936,-3.934 l 0,-16.585 c 0,-5.83 2.906,-8.839 7.705,-8.839 4.801,0 7.711,2.904 7.711,8.559 l 0,16.865 c 0,2.19 1.734,3.934 3.928,3.934 2.202,0 3.932,-1.744 3.932,-3.934 l 0,-16.551 c 0,-10.821 -6.07,-16.123 -15.676,-16.123 C 5.879,-15.875 0,-10.528 0,0" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3019"/>
|
||||
</g>
|
||||
<g id="g3021" transform="translate(286.9556,195.9439)">
|
||||
<path inkscape:connector-curvature="0" d="M 0,0 C 0,2.2 1.744,3.938 3.938,3.938 6.136,3.938 7.874,2.2 7.874,0 l 0,-28.49 c 0,-2.2 -1.738,-3.938 -3.936,-3.938 C 1.744,-32.428 0,-30.69 0,-28.49 L 0,0 z" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3023"/>
|
||||
</g>
|
||||
<g id="g3025" transform="translate(295.6633,209.2582)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c 0,-2.633 -2.138,-4.771 -4.771,-4.771 -2.632,0 -4.764,2.138 -4.764,4.771 0,2.635 2.132,4.766 4.764,4.766 C -2.138,4.766 0,2.635 0,0" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3027"/>
|
||||
</g>
|
||||
<g id="g3029" transform="translate(330.6357,184.4189)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 -9.197,0 c -1.89,0 -3.418,-1.541 -3.418,-3.429 0,-1.892 1.528,-3.367 3.418,-3.367 l 5.747,0 0,-4.89 c -1.817,-1.685 -4.401,-2.363 -8.065,-2.363 -6.322,0 -11.123,4.852 -11.123,11.38 0,6.085 4.85,11.194 10.567,11.194 3.375,0 5.667,-0.923 7.82,-2.451 0.561,-0.406 1.267,-0.809 2.395,-0.809 2.149,0 3.885,1.734 3.885,3.878 0,1.522 -0.873,2.601 -1.633,3.162 -3.223,2.251 -6.8,3.471 -12.206,3.471 -10.828,0 -19.053,-8.379 -19.053,-18.541 0,-10.563 7.97,-18.433 19.108,-18.433 5.531,0 10.201,1.687 13.591,4.326 1.743,1.335 2.106,2.391 2.106,4.696 l 0,8.251 C 3.942,-1.747 2.2,0 0,0" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3031"/>
|
||||
</g>
|
||||
<g id="g3033" transform="translate(355.9935,170.4649)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c -6.333,0 -10.819,5.154 -10.819,11.288 0,6.128 4.392,11.183 10.725,11.183 6.335,0 10.826,-5.154 10.826,-11.29 C 10.732,5.062 6.331,0 0,0 m 0,29.714 c -11.027,0 -19.047,-8.371 -19.047,-18.533 0,-10.16 7.919,-18.43 18.953,-18.43 11.02,0 19.04,8.373 19.04,18.537 0,10.16 -7.911,18.426 -18.946,18.426" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3035"/>
|
||||
</g>
|
||||
<g id="g3037" transform="translate(228.1901,170.4649)">
|
||||
<path inkscape:connector-curvature="0" d="m 0,0 c -6.34,0 -10.826,5.154 -10.826,11.288 0,6.128 4.388,11.183 10.725,11.183 6.331,0 10.822,-5.154 10.822,-11.29 C 10.721,5.062 6.325,0 0,0 m 0,29.714 c -11.033,0 -19.055,-8.371 -19.055,-18.533 0,-10.16 7.917,-18.43 18.954,-18.43 11.027,0 19.041,8.373 19.041,18.537 0,10.16 -7.915,18.426 -18.94,18.426" style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" id="path3039"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.9 KiB |
23
public/renfe.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="400.1" height="152.9" version="1.1" viewBox="0 0 400.1 152.9" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#vlpa);fill:#830065;}
|
||||
</style>
|
||||
<title>Renfe</title>
|
||||
|
||||
<g transform="matrix(.1488 0 0 .1488 -8.704 -6.964)">
|
||||
|
||||
<defs>
|
||||
<rect id="vlpb" x="58.5" y="46.7" width="2689" height="1028"/>
|
||||
</defs>
|
||||
<clipPath id="vlpa">
|
||||
<use width="100%" height="100%" xlink:href="#vlpb"/>
|
||||
</clipPath>
|
||||
<path class="st0" d="m1622 355.2h-391c-20.6 115.9-54.7 314.2-71.6 411.1h166.1c12.7-72.9 47.5-272.8 62.9-360.3h124.6c52.3 0 89.9 35.3 81.2 95.8-8.3 47.9-28.4 163.3-46.1 264.5h166.1c11.8-68 34.8-199.8 45.6-261.9 14.8-84.2-29.7-149.2-137.8-149.2" clip-path="url(#vlpa)"/>
|
||||
<path class="st0" d="m773.5 717.3c-77.2-12.7-62.8-105-62.8-105 68.5 8.2 154.9 6.3 213.9 0.4 51.1-5 151.9-17.7 180.7-96.3 28.5-77.9 6.8-140.9-87.2-158.5-69.2-12.8-133.5-13.5-217.5-2.2-119.9 16.1-185.9 56.7-215.2 128-16.1 39.3-25.8 86-30.1 128.4-7.6 77.1 6.9 147.8 103.4 155.5 129.4 10.4 231.2 8.1 339-8.8l33.6-47.9c-102.3 17-218.9 12.9-257.8 6.4m-32.5-229c20.6-49 50.9-79.8 107.6-92.8 25.1-5.8 46.5-5.7 76.5 0.8 44.5 9.5 58.2 61.4 38.6 101.9-21.8 45.2-51 55.3-89.2 63.1-41.4 8.6-95.9 9.8-155.9 5.7 4.2-25.1 14.7-60.4 22.4-78.7" clip-path="url(#vlpa)"/>
|
||||
<path class="st0" d="m2404 717.3c-77.2-12.7-62.8-105-62.8-105 68.6 8.2 154.9 6.3 213.9 0.4 51.1-5 151.9-17.7 180.6-96.3 28.5-77.9 6.9-140.9-87.1-158.5-69.3-12.8-133.6-13.5-217.6-2.2-119.9 16.1-185.7 56.7-215.1 128-16.2 39.3-25.9 86-30.1 128.4-7.6 77.1 6.9 147.8 103.4 155.5 129.3 10.4 231.1 8.1 339.1-8.8l33.5-47.9c-102.3 17-218.8 12.9-257.8 6.4m-32.4-229c20.5-49 50.8-79.8 107.4-92.8 25.1-5.8 46.6-5.7 76.6 0.8 44.5 9.5 58.1 61.4 38.7 101.9-21.9 45.2-51 55.3-89.2 63.1-41.4 8.6-95.9 9.8-155.9 5.7 4.1-25.1 14.6-60.4 22.4-78.7" clip-path="url(#vlpa)"/>
|
||||
<path class="st0" d="m272.3 492.2c8.8-50.2 26.6-79.2 81.1-89.1 48.8-8.9 139.1-3 192.1 2.9l35.6-50.9c-80.3-5.8-215.9-12.4-296.3-0.7-114.6 16.6-161.3 40-174.3 113.3-7.7 43.9-42.3 242.3-52.1 298.6h166.1c11.4-65.4 39.7-227.7 47.8-274.1" clip-path="url(#vlpa)"/>
|
||||
<path class="st0" d="m2065 103.3c-94.4 66.2-126.4 147.4-143.8 247.2l-70.4 402.7c-15.8 91.6-22.8 139.4-38.4 184.4-12.5 37.7-53.6 93.1-122 94l-29.9 42.6c88.3 4.3 158.9-21.2 208.8-56.1 94.4-66.1 129.2-160.7 146.7-260.3 1.2-6.8 31.9-183.1 46.6-266.6 10.7-59.3 41.8-91.8 107.3-92.7l30.4-43.3h-114c16.2-92.4 22-132.3 35.3-170.7 13-37.7 53.7-93.1 121.9-93.9l30.4-43.3c-88.5-4.4-159 21.1-208.9 56" clip-path="url(#vlpa)"/>
|
||||
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
6
public/ter.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="398.9px" height="193.4px" viewBox="0 0 398.9 193.4" enable-background="new 0 0 398.9 193.4" xml:space="preserve">
|
||||
<path id="path11" fill="white" d="M143.2,144.6c0-1.2,0.1-2.4,0.4-3.6c1.5-7,6.9-14.3,13.9-18.6c22.2-13.6,40.6-9.6,43.3-8.1 c0.3,0.2,0.1,0.4-0.1,0.9c-2.5,6.4-25.6,26.7-55.2,37.5C144.1,150.4,143.2,147.3,143.2,144.6 M220,107.4c-2.8-3.1-10.2-6.5-20.2-8.1 c-9.7-1.5-29.8-2.6-51.6,8.6c-22.8,11.7-37.5,33.4-30.9,54c0.1,0-19.2,7.1-33.6,4.2c-7.8-1.6-15.1-7.3-17.4-15 c-3.6-11.8-0.6-32,19.6-50.4c26.7-24.4,66.8-33.1,83.9-36.8c7.9-1.7,8.9-2,9.1-3.1c0.1-0.5,0.4-2.3-1.2-4.2 c-2.4-2.8-9.3-5.8-28.3-3.8C129.8,55,103,62.9,95,65.4c5.5-11.5,22.5-42.1,25.5-47.9c4.9-9.4,6.7-13.4,6.7-15.6 c0-0.6-0.2-1.1-0.5-1.4c-2.8-2.6-18.8,6.1-31,20.5C83.4,35.4,76.9,47,67.2,62.2c-10.7,17-44.3,25.6-60.5,30c-4.3,1.2-5.6,1.4-6.2,2 c-0.5,0.6-0.7,1.4-0.5,2.2c1.4,4.8,9.6,9.9,18.1,10.3c6.2,0.3,13.5-0.7,25-3.9c-6.1,9.7-16.5,31.2-16.5,51.6 c0,15.9,7.3,34.8,38.4,37.8c24.5,2.3,48.2-6.4,64.9-12.8c18.8,14.6,44.4,13.8,44.5,13.8c65.7,0,96.2-52.6,101.8-62.4 c0,0.1,6,19.6,17.5,35.4c15.7,21.7,37.5,28.5,59.8,26.4c26.5-2.5,43.6-23,45.2-26.8c0.8-1.9,0-3.9-4.9-1.3 c-4.1,2.2-8.6,5.5-19.6,7.6c-49.2,9.3-63.9-41.8-74.4-63.7c-1.4-3.1-2.7-5.4-4.1-6c-3-1.2-26.2,2.9-28.7,7.8 c-25.8,48-59.4,58.7-84.8,59.1c-8.5,0.1-15.7-1.6-21.4-4.6c10-4.9,17-8.7,24.2-13.5c26.4-17.6,36-36.4,36-42.5 C220.8,108.3,220.5,107.9,220,107.4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
2
public/tgv_inoui.svg
Normal file
After Width: | Height: | Size: 5.2 KiB |
17
public/trenitalia.svg
Normal file
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,76 +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) {
|
||||
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,230 +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} 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} />
|
||||
</CSSTransition>)
|
||||
|
||||
return <>
|
||||
<TableBody>
|
||||
<TransitionGroup component={null}>
|
||||
{table_rows}
|
||||
</TransitionGroup>
|
||||
</TableBody>
|
||||
</>
|
||||
}
|
||||
|
||||
function TrainRow({train, tableType}) {
|
||||
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, route)
|
||||
|
||||
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…"}
|
||||
|
||||
let stopsFilter
|
||||
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={`#${getBackgroundColor(train, route)}`}
|
||||
color={`#${getTextColor(train, route)}`}>
|
||||
{trainType}
|
||||
</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" fontWeight="bold" color="#FFED02" fontSize={24}>
|
||||
{getDisplayTime(train, tableType)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography fontSize={24} fontWeight="bold" data-stop-id={headline.id}>{headline.name}</Typography>
|
||||
<span className="stops">{stopsNames}</span>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</>
|
||||
}
|
||||
|
||||
function getTrainType(train, route) {
|
||||
if (train.id.startsWith("IDFM"))
|
||||
return route.short_name
|
||||
else {
|
||||
let trainType = train.stop.split("StopPoint:OCE")[1].split("-")[0]
|
||||
if (trainType === "Train TER")
|
||||
trainType = "TER"
|
||||
else if (trainType === "INTERCITES")
|
||||
trainType = "INTER-CITÉS"
|
||||
else if (trainType === "INTERCITES de nuit")
|
||||
trainType = "INTER-CITÉS de nuit"
|
||||
return trainType
|
||||
}
|
||||
}
|
||||
|
||||
function getBackgroundColor(train, route) {
|
||||
if (route.color)
|
||||
return route.color
|
||||
else if (getTrainType(train, route) === "OUIGO")
|
||||
return "E60075"
|
||||
return "FFFFFF"
|
||||
}
|
||||
|
||||
function getTextColor(train, route) {
|
||||
if (route.text_color)
|
||||
return route.text_color
|
||||
else {
|
||||
let trainType = getTrainType(train, 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)
|
||||
}
|
||||
|
||||
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,63 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
||||
Transfer, FeedInfo
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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__'
|
@ -1,3 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -1,224 +0,0 @@
|
||||
from datetime import datetime, timedelta, date
|
||||
|
||||
from django.db.models import F, Q, Value
|
||||
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
|
||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
||||
Transfer, FeedInfo
|
||||
|
||||
|
||||
CACHE_CONTROL = cache_control(max_age=7200)
|
||||
LAST_MODIFIED = last_modified(lambda *args, **kwargs: datetime.fromisoformat(FeedInfo.objects.get().version))
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
@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',]
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
@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', ]
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
@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]
|
||||
|
||||
|
||||
@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__'
|
||||
|
||||
|
||||
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:
|
||||
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'))
|
||||
|
||||
qs_today = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(departure_time__gte=query_time, pickup_type=0), calendar_filter(query_date)) \
|
||||
.annotate(departure_date=Value(query_date)) \
|
||||
.annotate(departure_time_24h=F('departure_time'))
|
||||
|
||||
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(departure_time__gte=time_yesterday, pickup_type=0), calendar_filter(yesterday)) \
|
||||
.annotate(departure_date=Value(yesterday)) \
|
||||
.annotate(departure_time_24h=F('departure_time') - timedelta(days=1))
|
||||
|
||||
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(departure_time__gte=timedelta(0), pickup_type=0), 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))
|
||||
|
||||
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:
|
||||
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'))
|
||||
|
||||
qs_today = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(arrival_time__gte=query_time, drop_off_type=0), calendar_filter(query_date)) \
|
||||
.annotate(arrival_date=Value(query_date)) \
|
||||
.annotate(arrival_time_24h=F('arrival_time'))
|
||||
|
||||
qs_yesterday = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(arrival_time__gte=time_yesterday, drop_off_type=0), calendar_filter(yesterday)) \
|
||||
.annotate(arrival_date=Value(yesterday)) \
|
||||
.annotate(arrival_time_24h=F('arrival_time') - timedelta(days=1))
|
||||
|
||||
qs_tomorrow = StopTime.objects.filter(stop_filter) \
|
||||
.filter(Q(arrival_time__gte=timedelta(0), drop_off_type=0), 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",
|
||||
}
|
||||
}
|
41
sncf/urls.py
@ -1,41 +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
|
||||
|
||||
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("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,98 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from sncfgtfs.models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, \
|
||||
Transfer, FeedInfo, StopTimeUpdate, TripUpdate
|
||||
|
||||
|
||||
@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',)
|
||||
search_fields = ('name', 'id',)
|
||||
ordering = ('name',)
|
||||
autocomplete_fields = ('parent_station',)
|
||||
|
||||
|
||||
@admin.register(Route)
|
||||
class RouteAdmin(admin.ModelAdmin):
|
||||
list_display = ('long_name', 'short_name', 'id', 'type',)
|
||||
list_filter = ('type',)
|
||||
search_fields = ('long_name', 'short_name', 'id',)
|
||||
ordering = ('long_name',)
|
||||
autocomplete_fields = ('agency',)
|
||||
|
||||
|
||||
@admin.register(Trip)
|
||||
class TripAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'route', 'service', 'headsign', 'direction_id',)
|
||||
list_filter = ('direction_id', 'service__transport_type',)
|
||||
search_fields = ('id', 'route__id', 'route__long_name', 'service__id', 'headsign',)
|
||||
ordering = ('route', 'service',)
|
||||
|
||||
|
||||
@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__service__transport_type',)
|
||||
search_fields = ('trip__id', 'stop__name', 'arrival_time', 'departure_time',)
|
||||
ordering = ('trip', 'stop_sequence',)
|
||||
autocomplete_fields = ('trip', 'stop',)
|
||||
|
||||
|
||||
@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',)
|
||||
|
||||
|
||||
@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',)
|
||||
|
||||
|
||||
@admin.register(TripUpdate)
|
||||
class TripUpdateAdmin(admin.ModelAdmin):
|
||||
list_display = ('trip_id', 'start_date', 'start_time',)
|
||||
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,530 +0,0 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-04 22:16+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:13
|
||||
msgid "Stop/platform"
|
||||
msgstr "Arrêt / quai"
|
||||
|
||||
#: sncfgtfs/models.py:14
|
||||
msgid "Station"
|
||||
msgstr "Gare"
|
||||
|
||||
#: sncfgtfs/models.py:15
|
||||
msgid "Entrance/exit"
|
||||
msgstr "Entrée / sortie"
|
||||
|
||||
#: sncfgtfs/models.py:16
|
||||
msgid "Generic node"
|
||||
msgstr "Nœud générique"
|
||||
|
||||
#: sncfgtfs/models.py:17
|
||||
msgid "Boarding area"
|
||||
msgstr "Zone d'embarquement"
|
||||
|
||||
#: sncfgtfs/models.py:21
|
||||
msgid "No information"
|
||||
msgstr "Pas d'information"
|
||||
|
||||
#: sncfgtfs/models.py:22
|
||||
msgid "Possible"
|
||||
msgstr "Possible"
|
||||
|
||||
#: sncfgtfs/models.py:23 sncfgtfs/models.py:53
|
||||
msgid "Not possible"
|
||||
msgstr "Impossible"
|
||||
|
||||
#: sncfgtfs/models.py:27
|
||||
msgid "Regular"
|
||||
msgstr "Régulier"
|
||||
|
||||
#: sncfgtfs/models.py:28
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#: sncfgtfs/models.py:29
|
||||
msgid "Must phone agency"
|
||||
msgstr "Doit téléphoner à l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:30
|
||||
msgid "Must coordinate with driver"
|
||||
msgstr "Doit se coordonner avec læ conducteurice"
|
||||
|
||||
#: sncfgtfs/models.py:34
|
||||
msgid "Tram"
|
||||
msgstr "Tram"
|
||||
|
||||
#: sncfgtfs/models.py:35
|
||||
msgid "Metro"
|
||||
msgstr "Métro"
|
||||
|
||||
#: sncfgtfs/models.py:36
|
||||
msgid "Rail"
|
||||
msgstr "Rail"
|
||||
|
||||
#: sncfgtfs/models.py:37
|
||||
msgid "Bus"
|
||||
msgstr "Bus"
|
||||
|
||||
#: sncfgtfs/models.py:38
|
||||
msgid "Ferry"
|
||||
msgstr "Ferry"
|
||||
|
||||
#: sncfgtfs/models.py:39
|
||||
msgid "Cable car"
|
||||
msgstr "Câble"
|
||||
|
||||
#: sncfgtfs/models.py:40
|
||||
msgid "Gondola"
|
||||
msgstr "Gondole"
|
||||
|
||||
#: sncfgtfs/models.py:41
|
||||
msgid "Funicular"
|
||||
msgstr "Funiculaire"
|
||||
|
||||
#: sncfgtfs/models.py:45
|
||||
msgid "Outbound"
|
||||
msgstr "Vers l'extérieur"
|
||||
|
||||
#: sncfgtfs/models.py:46
|
||||
msgid "Inbound"
|
||||
msgstr "Vers l'intérieur"
|
||||
|
||||
#: sncfgtfs/models.py:50
|
||||
msgid "Recommended"
|
||||
msgstr "Recommandé"
|
||||
|
||||
#: sncfgtfs/models.py:51
|
||||
msgid "Timed"
|
||||
msgstr "Correspondance programmée"
|
||||
|
||||
#: sncfgtfs/models.py:52
|
||||
msgid "Minimum time"
|
||||
msgstr "Temps de correspondance minimum requis"
|
||||
|
||||
#: sncfgtfs/models.py:57
|
||||
msgid "Added"
|
||||
msgstr "Ajouté"
|
||||
|
||||
#: sncfgtfs/models.py:58
|
||||
msgid "Removed"
|
||||
msgstr "Supprimé"
|
||||
|
||||
#: sncfgtfs/models.py:62
|
||||
msgid "Scheduled"
|
||||
msgstr "Planifié"
|
||||
|
||||
#: sncfgtfs/models.py:63
|
||||
msgid "Skipped"
|
||||
msgstr "Sauté"
|
||||
|
||||
#: sncfgtfs/models.py:64
|
||||
msgid "No data"
|
||||
msgstr "Pas de données"
|
||||
|
||||
#: sncfgtfs/models.py:65
|
||||
msgid "Unscheduled"
|
||||
msgstr "Non planifié"
|
||||
|
||||
#: sncfgtfs/models.py:72 sncfgtfs/models.py:229
|
||||
msgid "Agency ID"
|
||||
msgstr "ID de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:78
|
||||
msgid "Agency name"
|
||||
msgstr "Nom de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:82
|
||||
msgid "Agency URL"
|
||||
msgstr "URL de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:87
|
||||
msgid "Agency timezone"
|
||||
msgstr "Fuseau horaire de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:92
|
||||
msgid "Agency language"
|
||||
msgstr "Langue de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:98
|
||||
msgid "Agency phone"
|
||||
msgstr "Téléphone de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:103
|
||||
msgid "Agency email"
|
||||
msgstr "Adresse email de l'agence"
|
||||
|
||||
#: sncfgtfs/models.py:111
|
||||
msgid "Agency"
|
||||
msgstr "Agence"
|
||||
|
||||
#: sncfgtfs/models.py:112
|
||||
msgid "Agencies"
|
||||
msgstr "Agences"
|
||||
|
||||
#: sncfgtfs/models.py:120 sncfgtfs/models.py:420
|
||||
msgid "Stop ID"
|
||||
msgstr "ID de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:125
|
||||
msgid "Stop code"
|
||||
msgstr "Code de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:131
|
||||
msgid "Stop name"
|
||||
msgstr "Nom de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:136
|
||||
msgid "Stop description"
|
||||
msgstr "Description de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:141
|
||||
msgid "Stop longitude"
|
||||
msgstr "Longitude de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:145
|
||||
msgid "Stop latitude"
|
||||
msgstr "Latitude de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:150
|
||||
msgid "Zone ID"
|
||||
msgstr "ID de la zone"
|
||||
|
||||
#: sncfgtfs/models.py:154
|
||||
msgid "Stop URL"
|
||||
msgstr "URL de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:159
|
||||
msgid "Location type"
|
||||
msgstr "Type de localisation"
|
||||
|
||||
#: sncfgtfs/models.py:168
|
||||
msgid "Parent station"
|
||||
msgstr "Gare parente"
|
||||
|
||||
#: sncfgtfs/models.py:176
|
||||
msgid "Stop timezone"
|
||||
msgstr "Fuseau horaire de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:182
|
||||
msgid "Level ID"
|
||||
msgstr "ID du niveau"
|
||||
|
||||
#: sncfgtfs/models.py:187
|
||||
msgid "Wheelchair boarding"
|
||||
msgstr "Embarquement en fauteuil roulant"
|
||||
|
||||
#: sncfgtfs/models.py:195
|
||||
msgid "Platform code"
|
||||
msgstr "Code du quai"
|
||||
|
||||
#: sncfgtfs/models.py:214
|
||||
msgid "Stop"
|
||||
msgstr "Arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:215
|
||||
msgid "Stops"
|
||||
msgstr "Arrêts"
|
||||
|
||||
#: sncfgtfs/models.py:223 sncfgtfs/models.py:399 sncfgtfs/models.py:538
|
||||
#: sncfgtfs/models.py:576
|
||||
msgid "ID"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#: sncfgtfs/models.py:235
|
||||
msgid "Route short name"
|
||||
msgstr "Nom court de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:240
|
||||
msgid "Route long name"
|
||||
msgstr "Nom long de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:245
|
||||
msgid "Route description"
|
||||
msgstr "Description de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:250
|
||||
msgid "Route type"
|
||||
msgstr "Type de ligne"
|
||||
|
||||
#: sncfgtfs/models.py:255
|
||||
msgid "Route URL"
|
||||
msgstr "URL de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:261
|
||||
msgid "Route color"
|
||||
msgstr "Couleur de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:267
|
||||
msgid "Route text color"
|
||||
msgstr "Couleur du texte de la ligne"
|
||||
|
||||
#: sncfgtfs/models.py:275 sncfgtfs/models.py:290
|
||||
msgid "Route"
|
||||
msgstr "Ligne"
|
||||
|
||||
#: sncfgtfs/models.py:276
|
||||
msgid "Routes"
|
||||
msgstr "Lignes"
|
||||
|
||||
#: sncfgtfs/models.py:284
|
||||
msgid "Trip ID"
|
||||
msgstr "ID du trajet"
|
||||
|
||||
#: sncfgtfs/models.py:297 sncfgtfs/models.py:544
|
||||
msgid "Service"
|
||||
msgstr "Service"
|
||||
|
||||
#: sncfgtfs/models.py:303
|
||||
msgid "Trip headsign"
|
||||
msgstr "Destination du trajet"
|
||||
|
||||
#: sncfgtfs/models.py:309
|
||||
msgid "Trip short name"
|
||||
msgstr "Nom court du trajet"
|
||||
|
||||
#: sncfgtfs/models.py:314
|
||||
msgid "Direction"
|
||||
msgstr "Direction"
|
||||
|
||||
#: sncfgtfs/models.py:321
|
||||
msgid "Block ID"
|
||||
msgstr "ID du bloc"
|
||||
|
||||
#: sncfgtfs/models.py:327
|
||||
msgid "Shape ID"
|
||||
msgstr "ID de la forme"
|
||||
|
||||
#: sncfgtfs/models.py:332
|
||||
msgid "Wheelchair accessible"
|
||||
msgstr "Accessible en fauteuil roulant"
|
||||
|
||||
#: sncfgtfs/models.py:339
|
||||
msgid "Bikes allowed"
|
||||
msgstr "Vélos autorisés"
|
||||
|
||||
#: sncfgtfs/models.py:391 sncfgtfs/models.py:405 sncfgtfs/models.py:648
|
||||
msgid "Trip"
|
||||
msgstr "Trajet"
|
||||
|
||||
#: sncfgtfs/models.py:392
|
||||
msgid "Trips"
|
||||
msgstr "Trajets"
|
||||
|
||||
#: sncfgtfs/models.py:410 sncfgtfs/models.py:698
|
||||
msgid "Arrival time"
|
||||
msgstr "Heure d'arrivée"
|
||||
|
||||
#: sncfgtfs/models.py:414 sncfgtfs/models.py:706
|
||||
msgid "Departure time"
|
||||
msgstr "Heure de départ"
|
||||
|
||||
#: sncfgtfs/models.py:425
|
||||
msgid "Stop sequence"
|
||||
msgstr "Séquence de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:430
|
||||
msgid "Stop headsign"
|
||||
msgstr "Destination de l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:435
|
||||
msgid "Pickup type"
|
||||
msgstr "Type de prise en charge"
|
||||
|
||||
#: sncfgtfs/models.py:442
|
||||
msgid "Drop off type"
|
||||
msgstr "Type de dépose"
|
||||
|
||||
#: sncfgtfs/models.py:449
|
||||
msgid "Timepoint"
|
||||
msgstr "Ponctualité"
|
||||
|
||||
#: sncfgtfs/models.py:472 sncfgtfs/models.py:688
|
||||
msgid "Stop time"
|
||||
msgstr "Heure d'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:473
|
||||
msgid "Stop times"
|
||||
msgstr "Heures d'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:480
|
||||
msgid "Service ID"
|
||||
msgstr "ID du service"
|
||||
|
||||
#: sncfgtfs/models.py:484
|
||||
msgid "Monday"
|
||||
msgstr "Lundi"
|
||||
|
||||
#: sncfgtfs/models.py:488
|
||||
msgid "Tuesday"
|
||||
msgstr "Mardi"
|
||||
|
||||
#: sncfgtfs/models.py:492
|
||||
msgid "Wednesday"
|
||||
msgstr "Mercredi"
|
||||
|
||||
#: sncfgtfs/models.py:496
|
||||
msgid "Thursday"
|
||||
msgstr "Jeudi"
|
||||
|
||||
#: sncfgtfs/models.py:500
|
||||
msgid "Friday"
|
||||
msgstr "Vendredi"
|
||||
|
||||
#: sncfgtfs/models.py:504
|
||||
msgid "Saturday"
|
||||
msgstr "Samedi"
|
||||
|
||||
#: sncfgtfs/models.py:508
|
||||
msgid "Sunday"
|
||||
msgstr "Dimanche"
|
||||
|
||||
#: sncfgtfs/models.py:512 sncfgtfs/models.py:654
|
||||
msgid "Start date"
|
||||
msgstr "Date de début"
|
||||
|
||||
#: sncfgtfs/models.py:516
|
||||
msgid "End date"
|
||||
msgstr "Date de fin"
|
||||
|
||||
#: sncfgtfs/models.py:521 sncfgtfs/models.py:559
|
||||
msgid "Transport type"
|
||||
msgstr "Type de transport"
|
||||
|
||||
#: sncfgtfs/models.py:529
|
||||
msgid "Calendar"
|
||||
msgstr "Calendrier"
|
||||
|
||||
#: sncfgtfs/models.py:530
|
||||
msgid "Calendars"
|
||||
msgstr "Calendriers"
|
||||
|
||||
#: sncfgtfs/models.py:549
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#: sncfgtfs/models.py:553
|
||||
msgid "Exception type"
|
||||
msgstr "Type d'exception"
|
||||
|
||||
#: sncfgtfs/models.py:567
|
||||
msgid "Calendar date"
|
||||
msgstr "Date du calendrier"
|
||||
|
||||
#: sncfgtfs/models.py:568
|
||||
msgid "Calendar dates"
|
||||
msgstr "Dates du calendrier"
|
||||
|
||||
#: sncfgtfs/models.py:582
|
||||
msgid "From stop"
|
||||
msgstr "Depuis l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:589
|
||||
msgid "To stop"
|
||||
msgstr "Jusqu'à l'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:594
|
||||
msgid "Transfer type"
|
||||
msgstr "Type de correspondance"
|
||||
|
||||
#: sncfgtfs/models.py:600
|
||||
msgid "Minimum transfer time"
|
||||
msgstr "Temps de correspondance minimum"
|
||||
|
||||
#: sncfgtfs/models.py:605
|
||||
msgid "Transfer"
|
||||
msgstr "Correspondance"
|
||||
|
||||
#: sncfgtfs/models.py:606
|
||||
msgid "Transfers"
|
||||
msgstr "Correspondances"
|
||||
|
||||
#: sncfgtfs/models.py:613
|
||||
msgid "Feed publisher name"
|
||||
msgstr "Nom de l'éditeur du flux"
|
||||
|
||||
#: sncfgtfs/models.py:617
|
||||
msgid "Feed publisher URL"
|
||||
msgstr "URL de l'éditeur du flux"
|
||||
|
||||
#: sncfgtfs/models.py:622
|
||||
msgid "Feed language"
|
||||
msgstr "Langue du flux"
|
||||
|
||||
#: sncfgtfs/models.py:626
|
||||
msgid "Feed start date"
|
||||
msgstr "Date de début du flux"
|
||||
|
||||
#: sncfgtfs/models.py:630
|
||||
msgid "Feed end date"
|
||||
msgstr "Date de fin du flux"
|
||||
|
||||
#: sncfgtfs/models.py:635
|
||||
msgid "Feed version"
|
||||
msgstr "Version du flux"
|
||||
|
||||
#: sncfgtfs/models.py:639
|
||||
msgid "Feed info"
|
||||
msgstr "Information du flux"
|
||||
|
||||
#: sncfgtfs/models.py:640
|
||||
msgid "Feed infos"
|
||||
msgstr "Informations du flux"
|
||||
|
||||
#: sncfgtfs/models.py:658
|
||||
msgid "Start time"
|
||||
msgstr "Heure de début"
|
||||
|
||||
#: sncfgtfs/models.py:662 sncfgtfs/models.py:710
|
||||
msgid "Schedule relationship"
|
||||
msgstr "Relation de la planification"
|
||||
|
||||
#: sncfgtfs/models.py:671 sncfgtfs/models.py:681
|
||||
msgid "Trip update"
|
||||
msgstr "Mise à jour du trajet"
|
||||
|
||||
#: sncfgtfs/models.py:672
|
||||
msgid "Trip updates"
|
||||
msgstr "Mises à jour des trajets"
|
||||
|
||||
#: sncfgtfs/models.py:694
|
||||
msgid "Arrival delay"
|
||||
msgstr "Retard à l'arrivée"
|
||||
|
||||
#: sncfgtfs/models.py:702
|
||||
msgid "Departure delay"
|
||||
msgstr "Retard au départ"
|
||||
|
||||
#: sncfgtfs/models.py:719
|
||||
msgid "Stop time update"
|
||||
msgstr "Mise à jour du temps d'arrêt"
|
||||
|
||||
#: sncfgtfs/models.py:720
|
||||
msgid "Stop time updates"
|
||||
msgstr "Mises à jour des temps d'arrêt"
|
@ -1,336 +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",
|
||||
}
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('--bulk_size', type=int, default=1000, help='Number of objects to create in bulk.')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
bulk_size = options['bulk_size']
|
||||
|
||||
if not FeedInfo.objects.exists():
|
||||
last_update_date = "1970-01-01"
|
||||
else:
|
||||
last_update_date = FeedInfo.objects.get().version
|
||||
|
||||
for url in self.GTFS_FEEDS.values():
|
||||
last_modified = requests.head(url).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:
|
||||
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:
|
||||
agencies = []
|
||||
for agency_dict in csv.DictReader(zipfile.read("agency.txt").decode().splitlines()):
|
||||
agency_dict: dict
|
||||
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['agency_lang'],
|
||||
phone=agency_dict.get('agency_phone', ""),
|
||||
email=agency_dict.get('agency_email', ""),
|
||||
)
|
||||
agencies.append(agency)
|
||||
if agencies:
|
||||
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(zipfile.read("stops.txt").decode().splitlines()):
|
||||
stop_dict: dict
|
||||
stop = Stop(
|
||||
id=stop_dict["stop_id"],
|
||||
name=stop_dict['stop_name'],
|
||||
desc=stop_dict['stop_desc'],
|
||||
lat=stop_dict['stop_lat'],
|
||||
lon=stop_dict['stop_lon'],
|
||||
zone_id=stop_dict['zone_id'],
|
||||
url=stop_dict['stop_url'],
|
||||
location_type=stop_dict['location_type'],
|
||||
parent_station_id=stop_dict['parent_station'] or None
|
||||
if last_update_date != "1970-01-01" or transport_type != "TN" else 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', ""),
|
||||
)
|
||||
stops.append(stop)
|
||||
|
||||
if len(stops) >= bulk_size:
|
||||
Stop.objects.bulk_create(stops,
|
||||
update_conflicts=True,
|
||||
update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url',
|
||||
'location_type', 'parent_station_id', 'timezone',
|
||||
'wheelchair_boarding', 'level_id', 'platform_code'],
|
||||
unique_fields=['id'])
|
||||
stops.clear()
|
||||
if stops:
|
||||
Stop.objects.bulk_create(stops,
|
||||
update_conflicts=True,
|
||||
update_fields=['name', 'desc', 'lat', 'lon', 'zone_id', 'url',
|
||||
'location_type', 'parent_station_id', 'timezone',
|
||||
'wheelchair_boarding', 'level_id', 'platform_code'],
|
||||
unique_fields=['id'])
|
||||
stops.clear()
|
||||
|
||||
routes = []
|
||||
for route_dict in csv.DictReader(zipfile.read("routes.txt").decode().splitlines()):
|
||||
route_dict: dict
|
||||
route = Route(
|
||||
id=route_dict['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['route_desc'],
|
||||
type=route_dict['route_type'],
|
||||
url=route_dict['route_url'],
|
||||
color=route_dict['route_color'],
|
||||
text_color=route_dict['route_text_color'],
|
||||
)
|
||||
routes.append(route)
|
||||
|
||||
if len(routes) >= bulk_size:
|
||||
Route.objects.bulk_create(routes,
|
||||
update_conflicts=True,
|
||||
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
||||
'type', 'url', 'color', 'text_color'],
|
||||
unique_fields=['id'])
|
||||
routes.clear()
|
||||
if routes:
|
||||
Route.objects.bulk_create(routes,
|
||||
update_conflicts=True,
|
||||
update_fields=['agency_id', 'short_name', 'long_name', 'desc',
|
||||
'type', 'url', 'color', 'text_color'],
|
||||
unique_fields=['id'])
|
||||
routes.clear()
|
||||
|
||||
calendar_ids = []
|
||||
if "calendar.txt" in zipfile.namelist():
|
||||
calendars = []
|
||||
for calendar_dict in csv.DictReader(zipfile.read("calendar.txt").decode().splitlines()):
|
||||
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.append(calendar)
|
||||
calendar_ids.append(calendar.id)
|
||||
|
||||
if len(calendars) >= bulk_size:
|
||||
Calendar.objects.bulk_create(calendars,
|
||||
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:
|
||||
Calendar.objects.bulk_create(calendars, update_conflicts=True,
|
||||
update_fields=['monday', 'tuesday', 'wednesday', 'thursday',
|
||||
'friday', 'saturday', 'sunday', 'start_date',
|
||||
'end_date', 'transport_type'],
|
||||
unique_fields=['id'])
|
||||
calendars.clear()
|
||||
|
||||
calendars = []
|
||||
calendar_dates = []
|
||||
for calendar_date_dict in csv.DictReader(zipfile.read("calendar_dates.txt").decode().splitlines()):
|
||||
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 calendar_ids:
|
||||
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.append(calendar)
|
||||
|
||||
if len(calendar_dates) >= bulk_size:
|
||||
Calendar.objects.bulk_create(calendars,
|
||||
update_conflicts=True,
|
||||
update_fields=['end_date'],
|
||||
unique_fields=['id'])
|
||||
CalendarDate.objects.bulk_create(calendar_dates,
|
||||
update_conflicts=True,
|
||||
update_fields=['service_id', 'date', 'exception_type'],
|
||||
unique_fields=['id'])
|
||||
calendars.clear()
|
||||
calendar_dates.clear()
|
||||
|
||||
if calendar_dates:
|
||||
Calendar.objects.bulk_create(calendars,
|
||||
update_conflicts=True,
|
||||
update_fields=['end_date'],
|
||||
unique_fields=['id'])
|
||||
CalendarDate.objects.bulk_create(calendar_dates,
|
||||
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(zipfile.read("trips.txt").decode().splitlines()):
|
||||
trip_dict: dict
|
||||
trip = Trip(
|
||||
id=trip_dict['trip_id'],
|
||||
route_id=trip_dict['route_id'],
|
||||
service_id=f"{transport_type}-{trip_dict['service_id']}",
|
||||
headsign=trip_dict['trip_headsign'],
|
||||
short_name=trip_dict.get('trip_short_name', ""),
|
||||
direction_id=trip_dict['direction_id'] or None,
|
||||
block_id=trip_dict['block_id'],
|
||||
shape_id=trip_dict['shape_id'],
|
||||
wheelchair_accessible=trip_dict.get('wheelchair_accessible', None),
|
||||
bikes_allowed=trip_dict.get('bikes_allowed', None),
|
||||
)
|
||||
trips.append(trip)
|
||||
|
||||
if len(trips) >= bulk_size:
|
||||
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:
|
||||
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(zipfile.read("stop_times.txt").decode().splitlines()):
|
||||
stop_time_dict: dict
|
||||
|
||||
arr_time = stop_time_dict['arrival_time']
|
||||
arr_time = int(arr_time[:2]) * 3600 + int(arr_time[3:5]) * 60 + int(arr_time[6:])
|
||||
dep_time = stop_time_dict['departure_time']
|
||||
dep_time = int(dep_time[:2]) * 3600 + int(dep_time[3:5]) * 60 + int(dep_time[6:])
|
||||
st = StopTime(
|
||||
id=f"{stop_time_dict['trip_id']}-{stop_time_dict['stop_sequence']}",
|
||||
trip_id=stop_time_dict['trip_id'],
|
||||
arrival_time=timedelta(seconds=arr_time),
|
||||
departure_time=timedelta(seconds=dep_time),
|
||||
stop_id=stop_time_dict['stop_id'],
|
||||
stop_sequence=stop_time_dict['stop_sequence'],
|
||||
stop_headsign=stop_time_dict['stop_headsign'],
|
||||
pickup_type=stop_time_dict['pickup_type'],
|
||||
drop_off_type=stop_time_dict['drop_off_type'],
|
||||
timepoint=stop_time_dict.get('timepoint', None),
|
||||
)
|
||||
stop_times.append(st)
|
||||
|
||||
if len(stop_times) >= bulk_size:
|
||||
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:
|
||||
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()
|
||||
|
||||
transfers = []
|
||||
for transfer_dict in csv.DictReader(zipfile.read("transfers.txt").decode().splitlines()):
|
||||
transfer_dict: dict
|
||||
transfer = Transfer(
|
||||
id=f"{transfer_dict['from_stop_id']}-{transfer_dict['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:
|
||||
Transfer.objects.bulk_create(transfers,
|
||||
update_conflicts=True,
|
||||
update_fields=['transfer_type', 'min_transfer_time'],
|
||||
unique_fields=['id'])
|
||||
transfers.clear()
|
||||
|
||||
if transfers:
|
||||
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():
|
||||
for feed_info_dict in csv.DictReader(zipfile.read("feed_info.txt").decode().splitlines()):
|
||||
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['feed_start_date'],
|
||||
end_date=feed_info_dict['feed_end_date'],
|
||||
version=feed_info_dict['feed_version'],
|
||||
)
|
||||
)
|
@ -1,158 +0,0 @@
|
||||
from datetime import timedelta, datetime, date, time
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import requests
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from sncfgtfs.gtfs_realtime_pb2 import FeedMessage
|
||||
from sncfgtfs.models import Calendar, CalendarDate, StopTime, StopTimeUpdate, Trip, TripUpdate, Stop
|
||||
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
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
|
||||
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 == 1:
|
||||
headsign = trip_id[5:-1]
|
||||
trip_qs = Trip.objects.all()
|
||||
trip_ids = trip_qs.values_list('id', flat=True)
|
||||
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)
|
||||
trip_ids_restrict = trip_ids.intersection(st_queryset.values('trip_id'))
|
||||
if trip_ids_restrict:
|
||||
trip_ids = trip_ids_restrict
|
||||
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 train {headsign}")
|
||||
if not route_ids:
|
||||
self.stdout.write(f"Route not found for trip {trip_id}.")
|
||||
continue
|
||||
elif len(route_ids) > 1:
|
||||
self.stdout.write(f"Multiple routes found for trip {trip_id}.")
|
||||
continue
|
||||
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_date,
|
||||
"end_date": start_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": 1,
|
||||
"transport_type": feed_type,
|
||||
}
|
||||
)
|
||||
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).first()
|
||||
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 == 1:
|
||||
if stop_sequence == 0:
|
||||
stop = sample_trip.stop_times.get(stop_sequence=0).stop
|
||||
else:
|
||||
previous_stop = sample_trip.stop_times.get(stop_sequence=stop_sequence - 1).stop
|
||||
stop = next(s for s in stop.children.all() \
|
||||
if s.location_type == 0 and s.stop_type == previous_stop.stop_type)
|
||||
stop_id = stop.id
|
||||
StopTime.objects.update_or_create(
|
||||
id=f"{trip_id}-{stop_sequence}",
|
||||
trip_id=trip_id,
|
||||
defaults={
|
||||
"stop_id": stop_id,
|
||||
"stop_sequence": stop_sequence,
|
||||
"arrival_time": datetime.fromtimestamp(stop_time_update.arrival.time,
|
||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
||||
"departure_time": datetime.fromtimestamp(stop_time_update.departure.time,
|
||||
tz=ZoneInfo("Europe/Paris")) - start_dt,
|
||||
"pickup_type": 0 if stop_time_update.departure.time else 1,
|
||||
"drop_off_type": 0 if stop_time_update.arrival.time else 1,
|
||||
}
|
||||
)
|
||||
|
||||
if not Trip.objects.filter(id=trip_id).exists():
|
||||
self.stdout.write(f"Trip {trip_id} does not exist in the GTFS feed.")
|
||||
continue
|
||||
|
||||
tu, _created = TripUpdate.objects.get_or_create(
|
||||
trip_id=trip_id,
|
||||
start_date=trip_update.trip.start_date,
|
||||
start_time=trip_update.trip.start_time,
|
||||
schedule_relationship=trip_update.trip.schedule_relationship,
|
||||
)
|
||||
for stop_sequence, stop_time_update in enumerate(trip_update.stop_time_update):
|
||||
if not StopTime.objects.filter(trip_id=trip_id, stop_sequence=stop_sequence).exists():
|
||||
self.stdout.write(f"Stop {stop_sequence} does not exist in GTFS feed for trip {trip_id}.")
|
||||
continue
|
||||
|
||||
st = StopTime.objects.get(trip_id=trip_id, stop_sequence=stop_sequence)
|
||||
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 0,
|
||||
)
|
||||
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'])
|
@ -1,581 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-01-27 14:08
|
||||
|
||||
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, unique=True, 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"),
|
||||
],
|
||||
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",
|
||||
),
|
||||
),
|
||||
(
|
||||
"transport_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("TGV", "TGV"),
|
||||
("TER", "TER"),
|
||||
("IC", "Intercités"),
|
||||
("TN", "Transilien"),
|
||||
],
|
||||
max_length=255,
|
||||
verbose_name="Transport 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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"agency",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="routes",
|
||||
to="sncfgtfs.agency",
|
||||
verbose_name="Agency ID",
|
||||
),
|
||||
),
|
||||
],
|
||||
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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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",
|
||||
),
|
||||
),
|
||||
(
|
||||
"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",
|
||||
"ordering": ("id",),
|
||||
},
|
||||
),
|
||||
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",
|
||||
},
|
||||
),
|
||||
]
|
@ -1,105 +0,0 @@
|
||||
# Generated by Django 5.0.1 on 2024-02-04 19:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("sncfgtfs", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="trip",
|
||||
options={"verbose_name": "Trip", "verbose_name_plural": "Trips"},
|
||||
),
|
||||
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, "Skipped"),
|
||||
(2, "No data"),
|
||||
(3, "Unscheduled"),
|
||||
],
|
||||
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,722 +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")
|
||||
|
||||
|
||||
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 ScheduleRelationship(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,
|
||||
unique=True,
|
||||
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,
|
||||
)
|
||||
|
||||
@property
|
||||
def stop_type(self):
|
||||
train_type = self.id.split('StopPoint:OCE')[1].split('-')[0]
|
||||
if train_type == "Train TER":
|
||||
train_type = "TER"
|
||||
elif train_type == "INTERCITES":
|
||||
train_type = "INTER-CITÉS"
|
||||
elif train_type == "INTERCITES de nuit":
|
||||
train_type = "INTER-CITÉS de nuit"
|
||||
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 ID"),
|
||||
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,
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
@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 train_type(self):
|
||||
if self.service.transport_type == TransportType.TRANSILIEN:
|
||||
return self.route.short_name
|
||||
else:
|
||||
return self.origin.stop_type
|
||||
|
||||
@property
|
||||
def train_number(self):
|
||||
if self.service.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.route.long_name} - {self.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.trip.route.long_name} - {self.trip_id} - {self.stop.name}"
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
transport_type = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Transport type"),
|
||||
choices=TransportType,
|
||||
)
|
||||
|
||||
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=ScheduleRelationship,
|
||||
default=ScheduleRelationship.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=ScheduleRelationship,
|
||||
default=ScheduleRelationship.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 {createTheme, CssBaseline, ThemeProvider, useMediaQuery} from "@mui/material"
|
||||
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 'dayjs/locale/fr'
|
||||
import './App.css'
|
||||
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
||||
import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister";
|
||||
import {persistQueryClient} from "@tanstack/react-query-persist-client";
|
||||
import Home from "./Home";
|
||||
import {QueryClient, QueryClientProvider} from "@tanstack/react-query"
|
||||
import Home from "./Home"
|
||||
import TrainMap from "./Map"
|
||||
import dayjs from "dayjs"
|
||||
|
||||
function App() {
|
||||
const router = createBrowserRouter([
|
||||
@ -18,8 +18,12 @@ function App() {
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: "/station/:stopId",
|
||||
path: "/station/:theme/:stationId",
|
||||
element: <Station />
|
||||
},
|
||||
{
|
||||
path: "/map",
|
||||
element: <TrainMap />
|
||||
}
|
||||
])
|
||||
|
||||
@ -54,19 +58,12 @@ function App() {
|
||||
},
|
||||
})
|
||||
|
||||
const localStoragePersister = createSyncStoragePersister({
|
||||
storage: window.localStorage,
|
||||
})
|
||||
|
||||
persistQueryClient({
|
||||
queryClient,
|
||||
persister: localStoragePersister,
|
||||
})
|
||||
dayjs.locale('fr')
|
||||
|
||||
return <>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} localeText={frFR.components.MuiLocalizationProvider.defaultProps.localeText} adapterLocale="fr">
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="fr">
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
@ -1,9 +1,8 @@
|
||||
import {Autocomplete, TextField} from "@mui/material";
|
||||
import {useRef, useState} from "react";
|
||||
import {useState} from "react";
|
||||
|
||||
function AutocompleteStop(params) {
|
||||
function AutocompleteStation(params) {
|
||||
const [options, setOptions] = useState([])
|
||||
const previousController = useRef()
|
||||
|
||||
function onInputChange(event, value) {
|
||||
if (!value) {
|
||||
@ -11,17 +10,9 @@ function AutocompleteStop(params) {
|
||||
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})
|
||||
fetch(`${process.env.REACT_APP_MOTIS_SERVER}/api/v1/geocode?language=fr&text=${value}`)
|
||||
.then(response => response.json())
|
||||
.then(data => data.results)
|
||||
.then(setOptions)
|
||||
.catch()
|
||||
}
|
||||
|
||||
return <>
|
||||
@ -29,14 +20,18 @@ function AutocompleteStop(params) {
|
||||
id="stop"
|
||||
options={options}
|
||||
onInputChange={onInputChange}
|
||||
filterOptions={(x) => x}
|
||||
filterOptions={(x) => x.filter(stop => stop.type === "STOP").filter(stop => !stop.id.startsWith("node/"))}
|
||||
getOptionKey={option => option.id}
|
||||
getOptionLabel={option => option.name}
|
||||
groupBy={option => option.id.startsWith("IDFM") ? "Transilien" : "TER/TGV/Intercités"}
|
||||
groupBy={option => getOptionGroup(option)}
|
||||
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||
renderInput={(params) => <TextField {...params} label="Arrêt" />}
|
||||
{...params} />
|
||||
</>
|
||||
}
|
||||
|
||||
export default AutocompleteStop;
|
||||
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"
|
||||
|
||||
function Home() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
function onStationSelected(event, stop) {
|
||||
navigate(`/station/${stop.id}/`)
|
||||
function onStationSelected(event, station) {
|
||||
navigate(`/station/sncf/${station.id}/`)
|
||||
}
|
||||
|
||||
return <>
|
||||
<h1>Horaires SNCF</h1>
|
||||
<h1>Horaires des trains</h1>
|
||||
<h2>
|
||||
Choisissez une gare dont vous désirez connaître le tableau des prochains départs et arrivées :
|
||||
</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 './index.css';
|
||||
import reportWebVitals from './reportWebVitals';
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |