Added documentation

- Using sphinx to documente the project
- Autodocumenting Python using numpy-style docstr and sphinx autodoc with napoleon
This commit is contained in:
Johannes Randerath 2024-06-03 20:29:28 +02:00
parent f0ce32250a
commit c22049f499
40 changed files with 1303 additions and 26 deletions

2
.gitignore vendored
View File

@ -1,6 +1,7 @@
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*/__pycache__/
*.py[cod] *.py[cod]
*$py.class *$py.class
@ -10,6 +11,7 @@ __pycache__/
# Distribution / packaging # Distribution / packaging
.Python .Python
build/ build/
docs/build
develop-eggs/ develop-eggs/
dist/ dist/
downloads/ downloads/

View File

@ -1,3 +1,6 @@
# transport-accessibility # transport-accessibility
For polish public transport ### Tool for cities to easily publish their public transport in a standardized form
Public transport is often provided by many, smaller companies, operating mostly in their respective regions. A problem deriving from this is that, on a interregional, national and international, few machine-readable data is available. This impacts customers, as it makes it more difficult to find public transport routes, more so while travelling in regions they are less familiar with.
We designed a tool to provide city officials or volunteers to map the public transport in their jurisdiction using an easy-to-use graphical utility.

View File

@ -1,6 +1,7 @@
## General ## General
- Decide for a license - Decide for a license
- Decide how to license produced data as the work of inserting the data and the data itself is not ours. Make the users agree on a FLOSS or CC license for the GTFS files produced from their data? What if someone doesn't own the data they upload? What if it's not free - How can we produce data in that case? ToS? - Decide how to license produced data as the work of inserting the data and the data itself is not ours. Make the users agree on a FLOSS or CC license for the GTFS files produced from their data? What if someone doesn't own the data they upload? What if it's not free - How can we produce data in that case? ToS?
- Add documentation. Sphinx?
## Frontend ## Frontend
- Add TODOs - Add TODOs

8
bin/docutils Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/pybabel Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from babel.messages.frontend import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/pygmentize Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pygments.cmdline import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

23
bin/rst2html.py Executable file
View File

@ -0,0 +1,23 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2html.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing HTML.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates (X)HTML documents from standalone reStructuredText '
'sources. ' + default_description)
publish_cmdline(writer_name='html', description=description)

26
bin/rst2html4.py Executable file
View File

@ -0,0 +1,26 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2html4.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing (X)HTML.
The output conforms to XHTML 1.0 transitional
and almost to HTML 4.01 transitional (except for closing empty tags).
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates (X)HTML documents from standalone reStructuredText '
'sources. ' + default_description)
publish_cmdline(writer_name='html4', description=description)

33
bin/rst2html5.py Executable file
View File

@ -0,0 +1,33 @@
#!/home/johannes/code/transport-accessibility/bin/python
# :Copyright: © 2015 Günter Milde.
# :License: Released under the terms of the `2-Clause BSD license`_, in short:
#
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.
# This file is offered as-is, without any warranty.
#
# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause
#
# Revision: $Revision: 9021 $
# Date: $Date: 2022-03-04 16:54:22 +0100 (Fr, 04. Mär 2022) $
"""
A minimal front end to the Docutils Publisher, producing HTML 5 documents.
The output is also valid XML.
"""
try:
import locale # module missing in Jython
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates HTML5 documents from standalone '
'reStructuredText sources.\n'
+ default_description)
publish_cmdline(writer_name='html5', description=description)

26
bin/rst2latex.py Executable file
View File

@ -0,0 +1,26 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2latex.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing LaTeX.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline
description = ('Generates LaTeX documents from standalone reStructuredText '
'sources. '
'Reads from <source> (default is stdin) and writes to '
'<destination> (default is stdout). See '
'<https://docutils.sourceforge.io/docs/user/latex.html> for '
'the full reference.')
publish_cmdline(writer_name='latex', description=description)

27
bin/rst2man.py Executable file
View File

@ -0,0 +1,27 @@
#!/home/johannes/code/transport-accessibility/bin/python
# Author:
# Contact: grubert@users.sf.net
# Copyright: This module has been placed in the public domain.
"""
man.py
======
This module provides a simple command line interface that uses the
man page writer to output from ReStructuredText source.
"""
import locale
try:
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
from docutils.writers import manpage
description = ("Generates plain unix manual documents. "
+ default_description)
publish_cmdline(writer=manpage.Writer(), description=description)

28
bin/rst2odt.py Executable file
View File

@ -0,0 +1,28 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2odt.py 9115 2022-07-28 17:06:24Z milde $
# Author: Dave Kuhlman <dkuhlman@rexx.com>
# Copyright: This module has been placed in the public domain.
"""
A front end to the Docutils Publisher, producing OpenOffice documents.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline_to_binary, default_description
from docutils.writers.odf_odt import Writer, Reader
description = ('Generates OpenDocument/OpenOffice/ODF documents from '
'standalone reStructuredText sources. ' + default_description)
writer = Writer()
reader = Reader()
output = publish_cmdline_to_binary(reader=reader, writer=writer,
description=description)

20
bin/rst2odt_prepstyles.py Executable file
View File

@ -0,0 +1,20 @@
#!/home/johannes/code/transport-accessibility/bin/python
# Copyright: This module has been placed in the public domain.
"""
Adapt a word-processor-generated styles.odt for odtwriter use:
Drop page size specifications from styles.xml in STYLE_FILE.odt.
See https://docutils.sourceforge.io/docs/user/odt.html#page-size
Provisional backwards compatibility stub (to be removed in Docutils >= 0.21).
The actual code moved to the "docutils" library package and can be started
with ``python -m docutils.writers.odf_odt.prepstyles``.
"""
from docutils.writers.odf_odt import prepstyles
if __name__ == '__main__':
prepstyles.main()

23
bin/rst2pseudoxml.py Executable file
View File

@ -0,0 +1,23 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2pseudoxml.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing pseudo-XML.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates pseudo-XML from standalone reStructuredText '
'sources (for testing purposes). ' + default_description)
publish_cmdline(description=description)

24
bin/rst2s5.py Executable file
View File

@ -0,0 +1,24 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2s5.py 9115 2022-07-28 17:06:24Z milde $
# Author: Chris Liechti <cliechti@gmx.net>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing HTML slides using
the S5 template system.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates S5 (X)HTML slideshow documents from standalone '
'reStructuredText sources. ' + default_description)
publish_cmdline(writer_name='s5', description=description)

27
bin/rst2xetex.py Executable file
View File

@ -0,0 +1,27 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2xetex.py 9115 2022-07-28 17:06:24Z milde $
# Author: Guenter Milde
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing Lua/XeLaTeX code.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline
description = ('Generates LaTeX documents from standalone reStructuredText '
'sources for compilation with the Unicode-aware TeX variants '
'XeLaTeX or LuaLaTeX. '
'Reads from <source> (default is stdin) and writes to '
'<destination> (default is stdout). See '
'<https://docutils.sourceforge.io/docs/user/latex.html> for '
'the full reference.')
publish_cmdline(writer_name='xetex', description=description)

23
bin/rst2xml.py Executable file
View File

@ -0,0 +1,23 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rst2xml.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing Docutils XML.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates Docutils-native XML from standalone '
'reStructuredText sources. ' + default_description)
publish_cmdline(writer_name='xml', description=description)

25
bin/rstpep2html.py Executable file
View File

@ -0,0 +1,25 @@
#!/home/johannes/code/transport-accessibility/bin/python
# $Id: rstpep2html.py 9115 2022-07-28 17:06:24Z milde $
# Author: David Goodger <goodger@python.org>
# Copyright: This module has been placed in the public domain.
"""
A minimal front end to the Docutils Publisher, producing HTML from PEP
(Python Enhancement Proposal) documents.
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except Exception:
pass
from docutils.core import publish_cmdline, default_description
description = ('Generates (X)HTML from reStructuredText-format PEP files. '
+ default_description)
publish_cmdline(reader_name='pep', writer_name='pep_html',
description=description)

8
bin/sphinx-apidoc Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sphinx.ext.apidoc import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/sphinx-autobuild Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sphinx_autobuild.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/sphinx-autogen Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sphinx.ext.autosummary.generate import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/sphinx-build Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sphinx.cmd.build import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/sphinx-quickstart Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from sphinx.cmd.quickstart import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/uvicorn Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from uvicorn.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
bin/watchfiles Executable file
View File

@ -0,0 +1,8 @@
#!/home/johannes/code/transport-accessibility/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from watchfiles.cli import cli
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(cli())

20
docs/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

42
docs/source/conf.py Normal file
View File

@ -0,0 +1,42 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
import os
import sys
import django
sys.path.insert(0, os.path.abspath('../../transport_accessibility'))
os.environ['DJANGO_SETTINGS_MODULE'] = 'transport_accessibility.settings'
django.setup()
project = 'Transport Accessibility'
copyright = '2024, Jan Kiljanski and Johannes Randerath'
author = 'Jan Kiljanski and Johannes Randerath'
release = '0.1 Beta 1'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.autosummary',
]
templates_path = ['_templates']
exclude_patterns = ['**/migrations/*', 'manage.py', 'settings.py', 'admin.py', ]
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
# html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
autosummary_generate = True

20
docs/source/index.rst Normal file
View File

@ -0,0 +1,20 @@
.. Transport Accessibility documentation master file, created by
sphinx-quickstart on Mon Jun 3 13:49:21 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Transport Accessibility's documentation!
===================================================
.. toctree::
:maxdepth: 2
:caption: Contents:
modules
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

7
docs/source/manage.rst Normal file
View File

@ -0,0 +1,7 @@
manage module
=============
.. automodule:: manage
:members:
:undoc-members:
:show-inheritance:

9
docs/source/modules.rst Normal file
View File

@ -0,0 +1,9 @@
transport_accessibility
=======================
.. toctree::
:maxdepth: 4
manage
pt_map
transport_accessibility

84
docs/source/pt_map.rst Normal file
View File

@ -0,0 +1,84 @@
pt\_map package
===============
Subpackages
-----------
.. toctree::
:maxdepth: 4
Submodules
----------
pt\_map.admin module
--------------------
.. automodule:: pt_map.admin
:members:
:undoc-members:
:show-inheritance:
pt\_map.apps module
-------------------
.. automodule:: pt_map.apps
:members:
:undoc-members:
:show-inheritance:
pt\_map.bridge module
---------------------
.. automodule:: pt_map.bridge
:members:
:undoc-members:
:show-inheritance:
pt\_map.gtfs module
-------------------
.. automodule:: pt_map.gtfs
:members:
:undoc-members:
:show-inheritance:
pt\_map.models module
---------------------
.. automodule:: pt_map.models
:members:
:undoc-members:
:show-inheritance:
pt\_map.tests module
--------------------
.. automodule:: pt_map.tests
:members:
:undoc-members:
:show-inheritance:
pt\_map.urls module
-------------------
.. automodule:: pt_map.urls
:members:
:undoc-members:
:show-inheritance:
pt\_map.views module
--------------------
.. automodule:: pt_map.views
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: pt_map
:members:
:undoc-members:
:show-inheritance:

View File

@ -0,0 +1,45 @@
transport\_accessibility package
================================
Submodules
----------
transport\_accessibility.asgi module
------------------------------------
.. automodule:: transport_accessibility.asgi
:members:
:undoc-members:
:show-inheritance:
transport\_accessibility.settings module
----------------------------------------
.. automodule:: transport_accessibility.settings
:members:
:undoc-members:
:show-inheritance:
transport\_accessibility.urls module
------------------------------------
.. automodule:: transport_accessibility.urls
:members:
:undoc-members:
:show-inheritance:
transport\_accessibility.wsgi module
------------------------------------
.. automodule:: transport_accessibility.wsgi
:members:
:undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: transport_accessibility
:members:
:undoc-members:
:show-inheritance:

View File

@ -1,18 +1,86 @@
alabaster==0.7.16
anyio==4.4.0
asgiref==3.8.1 asgiref==3.8.1
astroid==3.2.2 astroid==3.2.2
autocommand==2.2.2
Babel==2.15.0
backports.csv==1.0.7
beautifulsoup4==4.12.3
certifi==2024.6.2
cffi==1.16.0
charset-normalizer==3.3.2
cheroot==10.0.1
CherryPy==18.9.0
click==8.1.7
colorama==0.4.6
cryptography==42.0.7
dill==0.3.8 dill==0.3.8
Django==5.0.6 Django==5.0.6
docutils==0.20.1
feedparser==6.0.11
future==1.0.0
h11==0.14.0
idna==3.7
imagesize==1.4.1
inflect==7.2.1
isort==5.13.2 isort==5.13.2
jaraco.collections==5.0.1
jaraco.context==5.3.0
jaraco.functools==4.0.1
jaraco.text==3.12.0
Jinja2==3.1.4
joblib==1.4.2
lxml==5.2.2
MarkupSafe==2.0.1
mccabe==0.7.0 mccabe==0.7.0
more-itertools==10.2.0
mysqlclient==2.2.4 mysqlclient==2.2.4
nltk==3.8.1
numpy==1.26.4 numpy==1.26.4
packaging==24.0
pandas==2.2.2 pandas==2.2.2
parsimonious==0.10.0
Pattern==3.6
pdfminer.six==20231228
platformdirs==4.2.2 platformdirs==4.2.2
portend==3.2.0
pycparser==2.22
Pygments==2.18.0
pylint==3.2.2 pylint==3.2.2
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-docx==1.1.2
pytz==2024.1 pytz==2024.1
regex==2024.5.15
requests==2.32.3
scipy==1.13.1
setuptools==70.0.0
sgmllib3k==1.0.0
six==1.16.0 six==1.16.0
sniffio==1.3.1
snowballstemmer==2.2.0
soupsieve==2.5
Sphinx==7.3.7
sphinx-autobuild==2024.4.16
sphinx-js==3.2.2
sphinx-rtd-theme==2.0.0
sphinxcontrib-applehelp==1.0.8
sphinxcontrib-devhelp==1.0.6
sphinxcontrib-htmlhelp==2.0.5
sphinxcontrib-jquery==4.1
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.7
sphinxcontrib-serializinghtml==1.1.10
sqlparse==0.5.0 sqlparse==0.5.0
starlette==0.37.2
tempora==5.5.1
tomlkit==0.12.5 tomlkit==0.12.5
tqdm==4.66.4
typeguard==4.3.0
typing_extensions==4.12.1
tzdata==2024.1 tzdata==2024.1
urllib3==2.2.1
uvicorn==0.30.1
watchfiles==0.22.0
websockets==12.0
wheel==0.43.0 wheel==0.43.0
zc.lockfile==3.0.post1

View File

@ -1,3 +1,37 @@
"""
Bridge
======
Bridge between Django with its models and database and gtfs.GTFS as intermediate object for File IO.
Contents
--------
Constants
---------
gtfs_schema : dir{str,list[str]}
Maps GTFS file names (without filename extension) to fields described by the GTFS Reference
reversed_file_mapping : dict(str,str)
Map CamelCased filenames to '_'-separated
Functions
---------
to_camel_case(s):
Converts '_'-separated str to CamelCase with capital first letter
standardize_time(time_str):
Converts str in unicode time format to %H:%M:%S format with normalized 24 hour time
is_NaN(v):
Checks if given variable is either a str expressing NaN or NaN as object
stdz(v):
Standardize date and time formats
gtfs_to_db(g):
Write an existing gtfs.GTFS object to the database using the GTFS compliant models
db_to_gtfs(q, folder_path):
Convert list of query sets to gtfs.GTFS object and write to specified folder if validation for GTFS compliance passes.
"""
import pt_map.gtfs import pt_map.gtfs
import pt_map.models import pt_map.models
import pandas as pd import pandas as pd
@ -250,9 +284,34 @@ gtfs_schema = {
} }
def to_camel_case(s: str): def to_camel_case(s: str):
"""
Convert '_'-separated str to CamelCase with the first letter capitalized.
Parameters
----------
s : str
'_'-separated string
Returns
-------
str
CamelCased str, first letter capitalized
"""
return ''.join(word.capitalize() for word in s.split('_')) return ''.join(word.capitalize() for word in s.split('_'))
def standardize_time(time_str: str): def standardize_time(time_str: str):
"""
Convert time str to standardized %H:%M:%S format.
Parameters
----------
time_str: str
str encoding time
Returns
-------
str in format '%H:%M:%S'
"""
date_str = f"Jan 19, 1999 {time_str}" date_str = f"Jan 19, 1999 {time_str}"
ntuple=email.utils.parsedate(date_str) ntuple=email.utils.parsedate(date_str)
timestamp=time.mktime(ntuple) timestamp=time.mktime(ntuple)
@ -261,9 +320,36 @@ def standardize_time(time_str: str):
def is_NaN(v): def is_NaN(v):
"""
Returns
-------
True
If v is either a str representing NaN or NaN as an object
False
Otherwise
"""
return (isinstance(v, str) and v.lower() == "nan") or (isinstance(v, numbers.Number) and math.isnan(v)) return (isinstance(v, str) and v.lower() == "nan") or (isinstance(v, numbers.Number) and math.isnan(v))
def stdz(v, m: django.db.models.Model, f: str): def stdz(v, m: django.db.models.Model, f: str):
"""
Convert Time and Date str to a format our db can easily work with.
Parameters
----------
v : object
object to be standardized
m : django.db.models.Model
model to be written to
f : str
field name in question
Returns
-------
Converted str
If m.f is a DateField or a TimeField
Unchanged str
Otherwise
"""
if m._meta.get_field(f).get_internal_type() == 'DateField': if m._meta.get_field(f).get_internal_type() == 'DateField':
return str(v) return str(v)
if m._meta.get_field(f).get_internal_type() == 'TimeField': if m._meta.get_field(f).get_internal_type() == 'TimeField':
@ -272,6 +358,14 @@ def stdz(v, m: django.db.models.Model, f: str):
def gtfs_to_db(g: pt_map.gtfs.GTFS): def gtfs_to_db(g: pt_map.gtfs.GTFS):
"""
Given a gtfs.GTFS object, write GTFS-compliantly to db by creating the correct models
Parameters
----------
g : gtfs.GTFS
GTFS object to be saved to db
"""
for k,v in gtfs_schema.items(): for k,v in gtfs_schema.items():
name = to_camel_case(singularize(k)) name = to_camel_case(singularize(k))
m = getattr(pt_map.models, name) m = getattr(pt_map.models, name)
@ -319,7 +413,22 @@ reversed_file_mapping = {
} }
def db_to_gtfs(q: list[django.db.models.query.QuerySet], folder_path: str): def db_to_gtfs(q: list[django.db.models.query.QuerySet], folder_path: str = ""):
"""
Convert given list of query sets to gtfs.GTFS object
Parameters
----------
q : list[django.db.models.query.QuerySet]
List of QuerySets containing the retrieved data to be Converted
folder_path : str
path to be set as the results folder_path instance variable
Returns
-------
gtfs.GTFS
object containing the queried data
"""
dfs = {reversed_file_mapping[m.model.__name__]: (pd.DataFrame(list(m.values())) if m else pd.DataFrame()) for m in q} dfs = {reversed_file_mapping[m.model.__name__]: (pd.DataFrame(list(m.values())) if m else pd.DataFrame()) for m in q}
g = pt_map.gtfs.GTFS(folder_path, dfs) g = pt_map.gtfs.GTFS(folder_path, dfs)
g.validate() g.validate()

View File

@ -1,8 +1,57 @@
import pandas as pd import pandas as pd
import os import os
class GTFS: class GTFS:
def __init__(self, folder_path: str, dfs: list[list] = None): """
DataFrame based representation of the GTFS standard, able to read folder of GTFS files, validate a GTFS object for accordance with the standard and write its data to a GTFS folder.
Attributes
----------
folder_path : str
Path to folder where the data is read from and/or to be written to
agency, stops, routes, trips, stop_times, calendar, calendar_dates, fare_attributes, fare_rules, timeframes, fare_media, fare_products, fare_leg_rules, fare_transfer_rules, areas, stop_areas, networks, route_networks, shapes, frequencies, transfers, pathways, levels, location_groups, location_group_stops, locations_geojson, booking_rules, translations, feed_info, attributions : GTFSFile
Objects representing the data in the corresponding .txt/.geojson files in the GTFS Reference.
errors: list[str]
Human readable messages explaining why a validation failed if it did.
Methods
-------
get_files():
Return all member objects of type GTFSFile
get_fields(name):
Return all fields present in a given instance of a GTFSFile
export(path, dirname):
Save all GTFS data represented by the current instance as a folder of files corresponding to the GTFS Reference.
validate():
For all GTFSFile member objects, validate if they individually conmply with GTFS.
validate_required_fields(df, required_fields):
Check if a DataFrame contains all required fields according to the GTFS reference for the file it represents.
validate_optional_fields(df, optional_fields):
Check if a DataFrame does not contain any unexpected fields, not compliant with the GTFS reference for the file it represents.
validate_lat_lon(df):
Check if a Stop is correctly mapped using geographical coordinates.
"""
def __init__(self, folder_path: str = "", dfs: dict[str, pd.DataFrame] = None):
"""
Parameters
----------
folder_path : str
Path of the folder to read GTFS data from or potentially write it to when export() is called. Defaults to an empty str.
dfs : dict[str : pd.DataFrame]
DataFrames containing the data to be represented by this object as values, corresponding GTFSFile.file_names as keys.
Raises
------
TypeError
If neither folder_path nor dfs is provided
If folder_path is not a valid str or dfs is not a dict of DataFrames
ValueError
If folder_path is not a well formatted path
"""
self.folder_path = folder_path self.folder_path = folder_path
self.agency = self.Agency(self.folder_path, dfs) self.agency = self.Agency(self.folder_path, dfs)
self.stops = self.Stops(self.folder_path, dfs) self.stops = self.Stops(self.folder_path, dfs)
@ -37,14 +86,50 @@ class GTFS:
self.errors = [] self.errors = []
class GTFSFile: class GTFSFile:
"""
All given fields and their corresponding values are stored as a DataFrame.
Attributes
----------
file_name : str
Extension-less name of the corresponding .txt file from the GTFS Reference
folder_path : str
Folder to read data from or potentially write it to
data : pd.DataFrame
All csv data from the corresponding .txt file represented as a Pandas DataFrame
Methods
-------
load_data(dfs):
Load data from list of DataFrames if given else read it from the corresponding .txt file in csv format.
"""
def __init__(self, folder_path, file_name, dfs): def __init__(self, folder_path, file_name, dfs):
"""
Parameters
----------
folder_path : str
Where to read GTFS files from or write it to
file_name : str
Name of the .txt file without the .txt Extension
dfs : dict[str, pd.DataFrame]
If given, data variable is set as corresponding DataFrame in this dict
If not, data is read from the csv
"""
self.file_name = file_name self.file_name = file_name
self.file_path = f"{folder_path}/{file_name}.txt" self.file_path = f"{folder_path}/{file_name}.txt"
self.data = self.load_data(dfs) self.data = self.load_data(dfs)
def load_data(self, dfs): def load_data(self, dfs):
"""
Fill the data attribute with GTFS data either with a given DataFrame or from the corresponding csv
Parameters
----------
dfs : dict[str, pd.DataFrame]
Dict of dataframes mapped to the corresponding file names. If given, the corresponding DataFrame is returned if the key exists else an empty DataFrame
"""
if dfs: if dfs:
return dfs[self.file_name] return dfs[self.file_name] if self.file_name in dfs.keys() else pd.DataFrame()
else: else:
try: try:
return pd.read_csv(self.file_path) return pd.read_csv(self.file_path)
@ -52,106 +137,184 @@ class GTFS:
return pd.DataFrame() return pd.DataFrame()
class Agency(GTFSFile): class Agency(GTFSFile):
"""
Represents agency.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'agency', dfs) super().__init__(folder_path, 'agency', dfs)
class Stops(GTFSFile): class Stops(GTFSFile):
"""
Represents stops.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'stops', dfs) super().__init__(folder_path, 'stops', dfs)
class Routes(GTFSFile): class Routes(GTFSFile):
"""
Represents routes.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'routes', dfs) super().__init__(folder_path, 'routes', dfs)
class Trips(GTFSFile): class Trips(GTFSFile):
"""
Represents trips.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'trips', dfs) super().__init__(folder_path, 'trips', dfs)
class StopTimes(GTFSFile): class StopTimes(GTFSFile):
"""
Represents stop_times.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'stop_times', dfs) super().__init__(folder_path, 'stop_times', dfs)
class Calendar(GTFSFile): class Calendar(GTFSFile):
"""
Represents calendar.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'calendar', dfs) super().__init__(folder_path, 'calendar', dfs)
class CalendarDates(GTFSFile): class CalendarDates(GTFSFile):
"""
Represents calendar_dates.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'calendar_dates', dfs) super().__init__(folder_path, 'calendar_dates', dfs)
class FareAttributes(GTFSFile): class FareAttributes(GTFSFile):
"""
Represents fare_attributes.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_attributes', dfs) super().__init__(folder_path, 'fare_attributes', dfs)
class FareRules(GTFSFile): class FareRules(GTFSFile):
"""
Represents fare_rules.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_rules', dfs) super().__init__(folder_path, 'fare_rules', dfs)
class Timeframes(GTFSFile): class Timeframes(GTFSFile):
"""
Represents timeframes.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'timeframes', dfs) super().__init__(folder_path, 'timeframes', dfs)
class FareMedia(GTFSFile): class FareMedia(GTFSFile):
"""
Represents fare_media.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_media', dfs) super().__init__(folder_path, 'fare_media', dfs)
class FareProducts(GTFSFile): class FareProducts(GTFSFile):
"""
Represents fare_products.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_products', dfs) super().__init__(folder_path, 'fare_products', dfs)
class FareLegRules(GTFSFile): class FareLegRules(GTFSFile):
"""
Represents fare_leg_rules.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_leg_rules', dfs) super().__init__(folder_path, 'fare_leg_rules', dfs)
class FareTransferRules(GTFSFile): class FareTransferRules(GTFSFile):
"""
Represents fare_transfer_rules.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'fare_transfer_rules', dfs) super().__init__(folder_path, 'fare_transfer_rules', dfs)
class Areas(GTFSFile): class Areas(GTFSFile):
"""
Represents areas.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'areas', dfs) super().__init__(folder_path, 'areas', dfs)
class StopAreas(GTFSFile): class StopAreas(GTFSFile):
"""
Represents stop_areas.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'stop_areas', dfs) super().__init__(folder_path, 'stop_areas', dfs)
class Networks(GTFSFile): class Networks(GTFSFile):
"""
Represents networks.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'networks', dfs) super().__init__(folder_path, 'networks', dfs)
class RouteNetworks(GTFSFile): class RouteNetworks(GTFSFile):
"""
Represents route_networks.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'route_networks', dfs) super().__init__(folder_path, 'route_networks', dfs)
class Shapes(GTFSFile): class Shapes(GTFSFile):
"""
Represents shapes.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'shapes', dfs) super().__init__(folder_path, 'shapes', dfs)
class Frequencies(GTFSFile): class Frequencies(GTFSFile):
"""
Represents frequencies.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'frequencies', dfs) super().__init__(folder_path, 'frequencies', dfs)
class Transfers(GTFSFile): class Transfers(GTFSFile):
"""
Represents transfers.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'transfers', dfs) super().__init__(folder_path, 'transfers', dfs)
class Pathways(GTFSFile): class Pathways(GTFSFile):
"""
Represents pathways.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'pathways', dfs) super().__init__(folder_path, 'pathways', dfs)
class Levels(GTFSFile): class Levels(GTFSFile):
"""
Represents levels.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'levels', dfs) super().__init__(folder_path, 'levels', dfs)
class LocationGroups(GTFSFile): class LocationGroups(GTFSFile):
"""
Represents location_groups.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'location_groups', dfs) super().__init__(folder_path, 'location_groups', dfs)
class LocationGroupStops(GTFSFile): class LocationGroupStops(GTFSFile):
"""
Represents location_group_stops.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'location_group_stops', dfs) super().__init__(folder_path, 'location_group_stops', dfs)
class LocationsGeojson(GTFSFile): class LocationsGeojson(GTFSFile):
"""
Represents locations.geojson from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
self.file_path = f"{folder_path}/locations.geojson" self.file_path = f"{folder_path}/locations.geojson"
if os.path.exists(self.file_path): if os.path.exists(self.file_path):
@ -166,31 +329,77 @@ class GTFS:
return pd.DataFrame() return pd.DataFrame()
class BookingRules(GTFSFile): class BookingRules(GTFSFile):
"""
Represents booking_rules.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'booking_rules', dfs) super().__init__(folder_path, 'booking_rules', dfs)
class Translations(GTFSFile): class Translations(GTFSFile):
"""
Represents translations.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'translations', dfs) super().__init__(folder_path, 'translations', dfs)
class FeedInfo(GTFSFile): class FeedInfo(GTFSFile):
"""
Represents feed_info.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'feed_info', dfs) super().__init__(folder_path, 'feed_info', dfs)
class Attributions(GTFSFile): class Attributions(GTFSFile):
"""
Represents attributions.txt from the GTFS reference
"""
def __init__(self, folder_path, dfs): def __init__(self, folder_path, dfs):
super().__init__(folder_path, 'attributions', dfs) super().__init__(folder_path, 'attributions', dfs)
def get_files(self): def get_files(self):
"""
Get all GTFSFile object
Returns
-------
list[GTFSFile]
All member objects of type GTFSFile
"""
return [attr for attr in dir(self) if isinstance(getattr(self, attr), self.GTFSFile)] return [attr for attr in dir(self) if isinstance(getattr(self, attr), self.GTFSFile)]
def get_fields(self, name): def get_fields(self, name):
"""
Given the name of a file specified in the GTFS specification, return all fields present in the data.
Parameters
----------
name : str
name of a file as specified by GTFS (ommiting the .txt/.geojson extension)
Returns
-------
list[pd.core.indexes.range.RangeIndex]
a list of all the fields present in the datastream of the specified file
"""
file = getattr(self, name) file = getattr(self, name)
if not file: if not file:
return None return None
return list(file.data.columns) return list(file.data.columns)
def export(self, path, dirname): def export(self, path = None, dirname = ""):
"""
Save this object's data to files as specified by GTFS.
Parameters
----------
path : str
parent directory where to save the files, defaults to the objects folder_path property
dirname : str
If specified, subdirectory to create or use inside path. Default behaviour is to save directly to path.
"""
if not path:
path = self.folder_path
else:
path = f"{os.path.normpath(path)}/{dirname}" path = f"{os.path.normpath(path)}/{dirname}"
if not os.path.exists(path): if not os.path.exists(path):
os.mkdir(path) os.mkdir(path)
@ -204,6 +413,15 @@ class GTFS:
df.to_csv(fpath, index=False) df.to_csv(fpath, index=False)
def validate(self): def validate(self):
"""
Check this object's data for compliance with the GTFS reference. Resets self.errors and stores human readable error messages to it.
Returns
-------
list[str]
List of human readable error messages, also saved to self.errors, if any, else None.
"""
self.error = []
if not self.agency.data.empty: if not self.agency.data.empty:
self.validate_agency() self.validate_agency()
if not self.stops.data.empty: if not self.stops.data.empty:
@ -271,12 +489,18 @@ class GTFS:
return self.errors return self.errors
def validate_agency(self): def validate_agency(self):
"""
Check Agency object for compliance with the GTFS reference.
"""
required_fields = ["agency_name", "agency_url", "agency_timezone"] required_fields = ["agency_name", "agency_url", "agency_timezone"]
optional_fields = ["agency_id", "agency_lang", "agency_phone", "agency_fare_url", "agency_email"] optional_fields = ["agency_id", "agency_lang", "agency_phone", "agency_fare_url", "agency_email"]
self.validate_required_fields(self.agency.data, required_fields, "agency.txt") self.validate_required_fields(self.agency.data, required_fields, "agency.txt")
self.validate_optional_fields(self.agency.data, optional_fields, "agency.txt") self.validate_optional_fields(self.agency.data, optional_fields, "agency.txt")
def validate_stops(self): def validate_stops(self):
"""
Check Stops object for compliance with the GTFS reference.
"""
required_fields = ["stop_id", "stop_name"] required_fields = ["stop_id", "stop_name"]
optional_fields = ["stop_code", "stop_desc", "stop_lat", "stop_lon", "zone_id", "stop_url", optional_fields = ["stop_code", "stop_desc", "stop_lat", "stop_lon", "zone_id", "stop_url",
"location_type", "parent_station", "stop_timezone", "wheelchair_boarding", "location_type", "parent_station", "stop_timezone", "wheelchair_boarding",
@ -286,6 +510,9 @@ class GTFS:
self.validate_lat_lon(self.stops.data) self.validate_lat_lon(self.stops.data)
def validate_routes(self): def validate_routes(self):
"""
Check Routes object for compliance with the GTFS reference.
"""
required_fields = ["route_id", "route_short_name", "route_long_name", "route_type"] required_fields = ["route_id", "route_short_name", "route_long_name", "route_type"]
optional_fields = ["agency_id", "route_desc", "route_url", "route_color", "route_text_color", optional_fields = ["agency_id", "route_desc", "route_url", "route_color", "route_text_color",
"route_sort_order", "continuous_pickup", "continuous_drop_off"] "route_sort_order", "continuous_pickup", "continuous_drop_off"]
@ -293,6 +520,9 @@ class GTFS:
self.validate_optional_fields(self.routes.data, optional_fields, "routes.txt") self.validate_optional_fields(self.routes.data, optional_fields, "routes.txt")
def validate_trips(self): def validate_trips(self):
"""
Check Trips object for compliance with the GTFS reference.
"""
required_fields = ["route_id", "service_id", "trip_id"] required_fields = ["route_id", "service_id", "trip_id"]
optional_fields = ["trip_headsign", "trip_short_name", "direction_id", "block_id", "shape_id", optional_fields = ["trip_headsign", "trip_short_name", "direction_id", "block_id", "shape_id",
"wheelchair_accessible", "bikes_allowed"] "wheelchair_accessible", "bikes_allowed"]
@ -300,6 +530,9 @@ class GTFS:
self.validate_optional_fields(self.trips.data, optional_fields, "trips.txt") self.validate_optional_fields(self.trips.data, optional_fields, "trips.txt")
def validate_stop_times(self): def validate_stop_times(self):
"""
Check StopTimes object for compliance with the GTFS reference.
"""
required_fields = ["trip_id", "arrival_time", "departure_time", "stop_id", "stop_sequence"] required_fields = ["trip_id", "arrival_time", "departure_time", "stop_id", "stop_sequence"]
optional_fields = ["stop_headsign", "pickup_type", "drop_off_type", "shape_dist_traveled", optional_fields = ["stop_headsign", "pickup_type", "drop_off_type", "shape_dist_traveled",
"timepoint"] "timepoint"]
@ -307,164 +540,276 @@ class GTFS:
self.validate_optional_fields(self.stop_times.data, optional_fields, "stop_times.txt") self.validate_optional_fields(self.stop_times.data, optional_fields, "stop_times.txt")
def validate_calendar(self): def validate_calendar(self):
"""
Check Calendar object for compliance with the GTFS reference.
"""
required_fields = ["service_id", "monday", "tuesday", "wednesday", "thursday", "friday", required_fields = ["service_id", "monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday", "start_date", "end_date"] "saturday", "sunday", "start_date", "end_date"]
self.validate_required_fields(self.calendar.data, required_fields, "calendar.txt") self.validate_required_fields(self.calendar.data, required_fields, "calendar.txt")
def validate_calendar_dates(self): def validate_calendar_dates(self):
"""
Check CalendarDates object for compliance with the GTFS reference.
"""
required_fields = ["service_id", "date", "exception_type"] required_fields = ["service_id", "date", "exception_type"]
self.validate_required_fields(self.calendar_dates.data, required_fields, "calendar_dates.txt") self.validate_required_fields(self.calendar_dates.data, required_fields, "calendar_dates.txt")
def validate_fare_attributes(self): def validate_fare_attributes(self):
"""
Check FareAttributes object for compliance with the GTFS reference.
"""
required_fields = ["fare_id", "price", "currency_type", "payment_method", "transfers"] required_fields = ["fare_id", "price", "currency_type", "payment_method", "transfers"]
optional_fields = ["agency_id", "transfer_duration"] optional_fields = ["agency_id", "transfer_duration"]
self.validate_required_fields(self.fare_attributes.data, required_fields, "fare_attributes.txt") self.validate_required_fields(self.fare_attributes.data, required_fields, "fare_attributes.txt")
self.validate_optional_fields(self.fare_attributes.data, optional_fields, "fare_attributes.txt") self.validate_optional_fields(self.fare_attributes.data, optional_fields, "fare_attributes.txt")
def validate_fare_rules(self): def validate_fare_rules(self):
"""
Check FareRules object for compliance with the GTFS reference.
"""
required_fields = ["fare_id"] required_fields = ["fare_id"]
optional_fields = ["route_id", "origin_id", "destination_id", "contains_id"] optional_fields = ["route_id", "origin_id", "destination_id", "contains_id"]
self.validate_required_fields(self.fare_rules.data, required_fields, "fare_rules.txt") self.validate_required_fields(self.fare_rules.data, required_fields, "fare_rules.txt")
self.validate_optional_fields(self.fare_rules.data, optional_fields, "fare_rules.txt") self.validate_optional_fields(self.fare_rules.data, optional_fields, "fare_rules.txt")
def validate_timeframes(self): def validate_timeframes(self):
"""
Check Timeframes object for compliance with the GTFS reference.
"""
required_fields = ["timeframe_id", "start_time", "end_time"] required_fields = ["timeframe_id", "start_time", "end_time"]
optional_fields = ["timeframe_name", "timeframe_desc"] optional_fields = ["timeframe_name", "timeframe_desc"]
self.validate_required_fields(self.timeframes.data, required_fields, "timeframes.txt") self.validate_required_fields(self.timeframes.data, required_fields, "timeframes.txt")
self.validate_optional_fields(self.timeframes.data, optional_fields, "timeframes.txt") self.validate_optional_fields(self.timeframes.data, optional_fields, "timeframes.txt")
def validate_fare_media(self): def validate_fare_media(self):
"""
Check FareMedia object for compliance with the GTFS reference.
"""
required_fields = ["media_id", "media_name", "media_type"] required_fields = ["media_id", "media_name", "media_type"]
optional_fields = ["media_desc"] optional_fields = ["media_desc"]
self.validate_required_fields(self.fare_media.data, required_fields, "fare_media.txt") self.validate_required_fields(self.fare_media.data, required_fields, "fare_media.txt")
self.validate_optional_fields(self.fare_media.data, optional_fields, "fare_media.txt") self.validate_optional_fields(self.fare_media.data, optional_fields, "fare_media.txt")
def validate_fare_products(self): def validate_fare_products(self):
"""
Check FareProducts object for compliance with the GTFS reference.
"""
required_fields = ["product_id", "product_name", "product_type", "product_price", "currency"] required_fields = ["product_id", "product_name", "product_type", "product_price", "currency"]
optional_fields = ["product_desc"] optional_fields = ["product_desc"]
self.validate_required_fields(self.fare_products.data, required_fields, "fare_products.txt") self.validate_required_fields(self.fare_products.data, required_fields, "fare_products.txt")
self.validate_optional_fields(self.fare_products.data, optional_fields, "fare_products.txt") self.validate_optional_fields(self.fare_products.data, optional_fields, "fare_products.txt")
def validate_fare_leg_rules(self): def validate_fare_leg_rules(self):
"""
Check FareLegRules object for compliance with the GTFS reference.
"""
required_fields = ["leg_id", "from_stop_id", "to_stop_id"] required_fields = ["leg_id", "from_stop_id", "to_stop_id"]
optional_fields = ["leg_desc"] optional_fields = ["leg_desc"]
self.validate_required_fields(self.fare_leg_rules.data, required_fields, "fare_leg_rules.txt") self.validate_required_fields(self.fare_leg_rules.data, required_fields, "fare_leg_rules.txt")
self.validate_optional_fields(self.fare_leg_rules.data, optional_fields, "fare_leg_rules.txt") self.validate_optional_fields(self.fare_leg_rules.data, optional_fields, "fare_leg_rules.txt")
def validate_fare_transfer_rules(self): def validate_fare_transfer_rules(self):
"""
Check FareTransferRules object for compliance with the GTFS reference.
"""
required_fields = ["from_leg_id", "to_leg_id", "transfer_type"] required_fields = ["from_leg_id", "to_leg_id", "transfer_type"]
optional_fields = ["transfer_time"] optional_fields = ["transfer_time"]
self.validate_required_fields(self.fare_transfer_rules.data, required_fields, "fare_transfer_rules.txt") self.validate_required_fields(self.fare_transfer_rules.data, required_fields, "fare_transfer_rules.txt")
self.validate_optional_fields(self.fare_transfer_rules.data, optional_fields, "fare_transfer_rules.txt") self.validate_optional_fields(self.fare_transfer_rules.data, optional_fields, "fare_transfer_rules.txt")
def validate_areas(self): def validate_areas(self):
"""
Check Areas object for compliance with the GTFS reference.
"""
required_fields = ["area_id", "area_name"] required_fields = ["area_id", "area_name"]
optional_fields = ["area_desc"] optional_fields = ["area_desc"]
self.validate_required_fields(self.areas.data, required_fields, "areas.txt") self.validate_required_fields(self.areas.data, required_fields, "areas.txt")
self.validate_optional_fields(self.areas.data, optional_fields, "areas.txt") self.validate_optional_fields(self.areas.data, optional_fields, "areas.txt")
def validate_stop_areas(self): def validate_stop_areas(self):
"""
Check StopAreas object for compliance with the GTFS reference.
"""
required_fields = ["stop_id", "area_id"] required_fields = ["stop_id", "area_id"]
optional_fields = [] optional_fields = []
self.validate_required_fields(self.stop_areas.data, required_fields, "stop_areas.txt") self.validate_required_fields(self.stop_areas.data, required_fields, "stop_areas.txt")
self.validate_optional_fields(self.stop_areas.data, optional_fields, "stop_areas.txt") self.validate_optional_fields(self.stop_areas.data, optional_fields, "stop_areas.txt")
def validate_networks(self): def validate_networks(self):
"""
Check Networks object for compliance with the GTFS reference.
"""
required_fields = ["network_id", "network_name"] required_fields = ["network_id", "network_name"]
optional_fields = ["network_desc"] optional_fields = ["network_desc"]
self.validate_required_fields(self.networks.data, required_fields, "networks.txt") self.validate_required_fields(self.networks.data, required_fields, "networks.txt")
self.validate_optional_fields(self.networks.data, optional_fields, "networks.txt") self.validate_optional_fields(self.networks.data, optional_fields, "networks.txt")
def validate_route_networks(self): def validate_route_networks(self):
"""
Check RouteNetworks object for compliance with the GTFS reference.
"""
required_fields = ["route_id", "network_id"] required_fields = ["route_id", "network_id"]
optional_fields = [] optional_fields = []
self.validate_required_fields(self.route_networks.data, required_fields, "route_networks.txt") self.validate_required_fields(self.route_networks.data, required_fields, "route_networks.txt")
self.validate_optional_fields(self.route_networks.data, optional_fields, "route_networks.txt") self.validate_optional_fields(self.route_networks.data, optional_fields, "route_networks.txt")
def validate_shapes(self): def validate_shapes(self):
"""
Check Shapes object for compliance with the GTFS reference.
"""
required_fields = ["shape_id", "shape_pt_lat", "shape_pt_lon", "shape_pt_sequence"] required_fields = ["shape_id", "shape_pt_lat", "shape_pt_lon", "shape_pt_sequence"]
optional_fields = ["shape_dist_traveled"] optional_fields = ["shape_dist_traveled"]
self.validate_required_fields(self.shapes.data, required_fields, "shapes.txt") self.validate_required_fields(self.shapes.data, required_fields, "shapes.txt")
self.validate_optional_fields(self.shapes.data, optional_fields, "shapes.txt") self.validate_optional_fields(self.shapes.data, optional_fields, "shapes.txt")
def validate_frequencies(self): def validate_frequencies(self):
"""
Check Frequencies object for compliance with the GTFS reference.
"""
required_fields = ["trip_id", "start_time", "end_time", "headway_secs"] required_fields = ["trip_id", "start_time", "end_time", "headway_secs"]
optional_fields = ["exact_times"] optional_fields = ["exact_times"]
self.validate_required_fields(self.frequencies.data, required_fields, "frequencies.txt") self.validate_required_fields(self.frequencies.data, required_fields, "frequencies.txt")
self.validate_optional_fields(self.frequencies.data, optional_fields, "frequencies.txt") self.validate_optional_fields(self.frequencies.data, optional_fields, "frequencies.txt")
def validate_transfers(self): def validate_transfers(self):
"""
Check Transfers object for compliance with the GTFS reference.
"""
required_fields = ["from_stop_id", "to_stop_id", "transfer_type"] required_fields = ["from_stop_id", "to_stop_id", "transfer_type"]
optional_fields = ["min_transfer_time"] optional_fields = ["min_transfer_time"]
self.validate_required_fields(self.transfers.data, required_fields, "transfers.txt") self.validate_required_fields(self.transfers.data, required_fields, "transfers.txt")
self.validate_optional_fields(self.transfers.data, optional_fields, "transfers.txt") self.validate_optional_fields(self.transfers.data, optional_fields, "transfers.txt")
def validate_pathways(self): def validate_pathways(self):
"""
Check Pathways object for compliance with the GTFS reference.
"""
required_fields = ["pathway_id", "from_stop_id", "to_stop_id", "pathway_mode", "is_bidirectional"] required_fields = ["pathway_id", "from_stop_id", "to_stop_id", "pathway_mode", "is_bidirectional"]
optional_fields = ["length", "traversal_time", "stair_count", "max_slope", "min_width", "signposted_as", "reversed_signposted_as"] optional_fields = ["length", "traversal_time", "stair_count", "max_slope", "min_width", "signposted_as", "reversed_signposted_as"]
self.validate_required_fields(self.pathways.data, required_fields, "pathways.txt") self.validate_required_fields(self.pathways.data, required_fields, "pathways.txt")
self.validate_optional_fields(self.pathways.data, optional_fields, "pathways.txt") self.validate_optional_fields(self.pathways.data, optional_fields, "pathways.txt")
def validate_levels(self): def validate_levels(self):
"""
Check Levels object for compliance with the GTFS reference.
"""
required_fields = ["level_id", "level_index"] required_fields = ["level_id", "level_index"]
optional_fields = ["level_name"] optional_fields = ["level_name"]
self.validate_required_fields(self.levels.data, required_fields, "levels.txt") self.validate_required_fields(self.levels.data, required_fields, "levels.txt")
self.validate_optional_fields(self.levels.data, optional_fields, "levels.txt") self.validate_optional_fields(self.levels.data, optional_fields, "levels.txt")
def validate_location_groups(self): def validate_location_groups(self):
"""
Check Agency LocationGroups for compliance with the GTFS reference.
"""
required_fields = ["location_group_id", "location_group_name"] required_fields = ["location_group_id", "location_group_name"]
optional_fields = ["location_group_desc"] optional_fields = ["location_group_desc"]
self.validate_required_fields(self.location_groups.data, required_fields, "location_groups.txt") self.validate_required_fields(self.location_groups.data, required_fields, "location_groups.txt")
self.validate_optional_fields(self.location_groups.data, optional_fields, "location_groups.txt") self.validate_optional_fields(self.location_groups.data, optional_fields, "location_groups.txt")
def validate_location_group_stops(self): def validate_location_group_stops(self):
"""
Check LocationGroupStops object for compliance with the GTFS reference.
"""
required_fields = ["location_group_id", "stop_id"] required_fields = ["location_group_id", "stop_id"]
optional_fields = [] optional_fields = []
self.validate_required_fields(self.location_group_stops.data, required_fields, "location_group_stops.txt") self.validate_required_fields(self.location_group_stops.data, required_fields, "location_group_stops.txt")
self.validate_optional_fields(self.location_group_stops.data, optional_fields, "location_group_stops.txt") self.validate_optional_fields(self.location_group_stops.data, optional_fields, "location_group_stops.txt")
def validate_locations_geojson(self): def validate_locations_geojson(self):
"""
Check LocationsGeojson object for compliance with the GTFS reference.
"""
required_fields = ["type", "features"] required_fields = ["type", "features"]
optional_fields = [] optional_fields = []
self.validate_required_fields(self.locations_geojson.data, required_fields, "locations.geojson") self.validate_required_fields(self.locations_geojson.data, required_fields, "locations.geojson")
self.validate_optional_fields(self.locations_geojson.data, optional_fields, "locations.geojson") self.validate_optional_fields(self.locations_geojson.data, optional_fields, "locations.geojson")
def validate_booking_rules(self): def validate_booking_rules(self):
"""
Check BookingRules object for compliance with the GTFS reference.
"""
required_fields = ["booking_rule_id"] required_fields = ["booking_rule_id"]
optional_fields = ["booking_rule_name", "booking_rule_desc"] optional_fields = ["booking_rule_name", "booking_rule_desc"]
self.validate_required_fields(self.booking_rules.data, required_fields, "booking_rules.txt") self.validate_required_fields(self.booking_rules.data, required_fields, "booking_rules.txt")
self.validate_optional_fields(self.booking_rules.data, optional_fields, "booking_rules.txt") self.validate_optional_fields(self.booking_rules.data, optional_fields, "booking_rules.txt")
def validate_translations(self): def validate_translations(self):
"""
Check Translations object for compliance with the GTFS reference.
"""
required_fields = ["table_name", "field_name", "language", "translation"] required_fields = ["table_name", "field_name", "language", "translation"]
optional_fields = ["record_id", "record_sub_id", "field_value"] optional_fields = ["record_id", "record_sub_id", "field_value"]
self.validate_required_fields(self.translations.data, required_fields, "translations.txt") self.validate_required_fields(self.translations.data, required_fields, "translations.txt")
self.validate_optional_fields(self.translations.data, optional_fields, "translations.txt") self.validate_optional_fields(self.translations.data, optional_fields, "translations.txt")
def validate_feed_info(self): def validate_feed_info(self):
"""
Check FeedInfo object for compliance with the GTFS reference.
"""
required_fields = ["feed_publisher_name", "feed_publisher_url", "feed_lang"] required_fields = ["feed_publisher_name", "feed_publisher_url", "feed_lang"]
optional_fields = ["feed_start_date", "feed_end_date", "feed_version"] optional_fields = ["feed_start_date", "feed_end_date", "feed_version"]
self.validate_required_fields(self.feed_info.data, required_fields, "feed_info.txt") self.validate_required_fields(self.feed_info.data, required_fields, "feed_info.txt")
self.validate_optional_fields(self.feed_info.data, optional_fields, "feed_info.txt") self.validate_optional_fields(self.feed_info.data, optional_fields, "feed_info.txt")
def validate_attributions(self): def validate_attributions(self):
"""
Check Attributions object for compliance with the GTFS reference.
"""
required_fields = ["attribution_id"] required_fields = ["attribution_id"]
optional_fields = ["agency_id", "route_id", "trip_id", "organization_name", "is_producer", "is_operator", "is_authority", "attribution_url", "attribution_email", "attribution_phone"] optional_fields = ["agency_id", "route_id", "trip_id", "organization_name", "is_producer", "is_operator", "is_authority", "attribution_url", "attribution_email", "attribution_phone"]
self.validate_required_fields(self.attributions.data, required_fields, "attributions.txt") self.validate_required_fields(self.attributions.data, required_fields, "attributions.txt")
self.validate_optional_fields(self.attributions.data, optional_fields, "attributions.txt") self.validate_optional_fields(self.attributions.data, optional_fields, "attributions.txt")
def validate_required_fields(self, df, required_fields, file_name): def validate_required_fields(self, df, required_fields, file_name):
"""
Check if a DataFrame contains all required fields according to the GTFS reference for the file it represents.
Parameters
----------
df : pd.DataFrame
DataFrame to be checked
required_fields : list[str]
list of field names to check for inclusion in df
file_name : str
Name to be printed in error message if validation fails
"""
missing_fields = set(required_fields) - set(df.columns) missing_fields = set(required_fields) - set(df.columns)
if missing_fields: if missing_fields:
self.errors.append(f"{file_name} is missing required fields: {missing_fields}") self.errors.append(f"{file_name} is missing required fields: {missing_fields}")
def validate_optional_fields(self, df, optional_fields, file_name): def validate_optional_fields(self, df, optional_fields, file_name):
"""
Check if DataFrame contains fields not specified in the GTFS Reference.
Parameters
----------
df : pd.DataFrame
DataFrame to be checked
optional_fields: list[str]
list of field names allowed in df
file_name : str
Name to be printed in error message if validation fails
"""
unexpected_fields = set(df.columns) - set(optional_fields) - set(df.columns) unexpected_fields = set(df.columns) - set(optional_fields) - set(df.columns)
if unexpected_fields: if unexpected_fields:
self.errors.append(f"{file_name} has unexpected fields: {unexpected_fields}") self.errors.append(f"{file_name} has unexpected fields: {unexpected_fields}")
def validate_lat_lon(self, df): def validate_lat_lon(self, df, file_name="stops.txt", prefix="stop"):
if 'stop_lat' in df.columns and 'stop_lon' in df.columns: """
if df[['stop_lat', 'stop_lon']].isnull().any().any(): Check if a DataFrame contains either no coordinates of a given prefix or both latitude and longitude.
self.errors.append(f"stops.txt has missing lat/lon values.")
Parameters
----------
df : pd.DataFrame
DataFrame to be checked
file_name : str
Name to be printed in error message if validation fails
prefix : str
Prefix to be used for coordinate fileds. Expands to {prefix}_lat and {prefix}_lon
"""
if f"{prefix}_lat" in df.columns and f"{prefix}_lon" in df.columns:
if df[[f"{prefix}_lat", f"{prefix}_lon"]].isnull().any().any():
self.errors.append(f"{file_name} has missing lat/lon values.")

View File

@ -1,6 +1,21 @@
"""
Models
======
Django database models representing the files of the GTFS Reference with all their fields
Attributes
----------
Classes
-------
Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, Pathway, Level, FeedInfo, LocationsGeojson, BookingRule, Translation, Attribution, LocationGroup, LocationGroupStop, RouteNetwork, Network, StopArea, Area, FareMedium, FareProduct, FareLegRule, FareTransferRule, Timeframe
Different files as described in the GTFS Reference
"""
from django.db import models from django.db import models
class Agency(models.Model): class Agency(models.Model):
"""
Represents agency.txt from the GTFS Reference.
"""
agency_id = models.CharField(max_length=255, primary_key=True) agency_id = models.CharField(max_length=255, primary_key=True)
agency_name = models.CharField(max_length=255) agency_name = models.CharField(max_length=255)
agency_url = models.URLField() agency_url = models.URLField()
@ -11,6 +26,9 @@ class Agency(models.Model):
agency_email = models.EmailField(blank=True, null=True) agency_email = models.EmailField(blank=True, null=True)
class Stop(models.Model): class Stop(models.Model):
"""
Represents stop.txt from the GTFS Reference.
"""
stop_id = models.CharField(max_length=255, primary_key=True) stop_id = models.CharField(max_length=255, primary_key=True)
stop_code = models.CharField(max_length=50, blank=True, null=True) stop_code = models.CharField(max_length=50, blank=True, null=True)
stop_name = models.CharField(max_length=255) stop_name = models.CharField(max_length=255)
@ -27,6 +45,9 @@ class Stop(models.Model):
platform_code = models.CharField(max_length=50, blank=True, null=True) platform_code = models.CharField(max_length=50, blank=True, null=True)
class Route(models.Model): class Route(models.Model):
"""
Represents route.txt from the GTFS Reference.
"""
route_id = models.CharField(max_length=255, primary_key=True) route_id = models.CharField(max_length=255, primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
route_short_name = models.CharField(max_length=50) route_short_name = models.CharField(max_length=50)
@ -41,6 +62,9 @@ class Route(models.Model):
continuous_drop_off = models.IntegerField(blank=True, null=True) continuous_drop_off = models.IntegerField(blank=True, null=True)
class Trip(models.Model): class Trip(models.Model):
"""
Represents trip.txt from the GTFS Reference.
"""
trip_id = models.CharField(max_length=255, primary_key=True) trip_id = models.CharField(max_length=255, primary_key=True)
route = models.ForeignKey(Route, on_delete=models.CASCADE) route = models.ForeignKey(Route, on_delete=models.CASCADE)
service_id = models.CharField(max_length=255) service_id = models.CharField(max_length=255)
@ -53,6 +77,9 @@ class Trip(models.Model):
bikes_allowed = models.IntegerField(blank=True, null=True) bikes_allowed = models.IntegerField(blank=True, null=True)
class StopTime(models.Model): class StopTime(models.Model):
"""
Represents stop_time.txt from the GTFS Reference.
"""
trip = models.ForeignKey(Trip, on_delete=models.CASCADE) trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
arrival_time = models.TimeField(blank=True, null=True) arrival_time = models.TimeField(blank=True, null=True)
departure_time = models.TimeField(blank=True, null=True) departure_time = models.TimeField(blank=True, null=True)
@ -68,6 +95,9 @@ class StopTime(models.Model):
unique_together = (('trip', 'stop_sequence'),) unique_together = (('trip', 'stop_sequence'),)
class Calendar(models.Model): class Calendar(models.Model):
"""
Represents calendar.txt from the GTFS Reference.
"""
service_id = models.CharField(max_length=255, primary_key=True) service_id = models.CharField(max_length=255, primary_key=True)
monday = models.BooleanField() monday = models.BooleanField()
tuesday = models.BooleanField() tuesday = models.BooleanField()
@ -80,6 +110,9 @@ class Calendar(models.Model):
end_date = models.DateField() end_date = models.DateField()
class CalendarDate(models.Model): class CalendarDate(models.Model):
"""
Represents calendar_date.txt from the GTFS Reference.
"""
service_id = models.CharField(max_length=255) service_id = models.CharField(max_length=255)
date = models.DateField() date = models.DateField()
exception_type = models.IntegerField() exception_type = models.IntegerField()
@ -88,6 +121,9 @@ class CalendarDate(models.Model):
unique_together = (('service_id', 'date'),) unique_together = (('service_id', 'date'),)
class FareAttribute(models.Model): class FareAttribute(models.Model):
"""
Represents fare_attribute.txt from the GTFS Reference.
"""
fare_id = models.CharField(max_length=255, primary_key=True) fare_id = models.CharField(max_length=255, primary_key=True)
price = models.FloatField() price = models.FloatField()
currency_type = models.CharField(max_length=3) currency_type = models.CharField(max_length=3)
@ -97,6 +133,9 @@ class FareAttribute(models.Model):
transfer_duration = models.IntegerField(blank=True, null=True) transfer_duration = models.IntegerField(blank=True, null=True)
class FareRule(models.Model): class FareRule(models.Model):
"""
Represents fare_rule.txt from the GTFS Reference.
"""
fare = models.ForeignKey(FareAttribute, on_delete=models.CASCADE) fare = models.ForeignKey(FareAttribute, on_delete=models.CASCADE)
route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True)
origin_id = models.CharField(max_length=255, blank=True, null=True) origin_id = models.CharField(max_length=255, blank=True, null=True)
@ -104,6 +143,9 @@ class FareRule(models.Model):
contains_id = models.CharField(max_length=255, blank=True, null=True) contains_id = models.CharField(max_length=255, blank=True, null=True)
class Shape(models.Model): class Shape(models.Model):
"""
Represents shape.txt from the GTFS Reference.
"""
shape_id = models.CharField(max_length=255) shape_id = models.CharField(max_length=255)
shape_pt_lat = models.FloatField() shape_pt_lat = models.FloatField()
shape_pt_lon = models.FloatField() shape_pt_lon = models.FloatField()
@ -114,6 +156,9 @@ class Shape(models.Model):
unique_together = (('shape_id', 'shape_pt_sequence'),) unique_together = (('shape_id', 'shape_pt_sequence'),)
class Frequency(models.Model): class Frequency(models.Model):
"""
Represents frequency.txt from the GTFS Reference.
"""
trip = models.ForeignKey(Trip, on_delete=models.CASCADE) trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
start_time = models.TimeField() start_time = models.TimeField()
end_time = models.TimeField() end_time = models.TimeField()
@ -121,6 +166,9 @@ class Frequency(models.Model):
exact_times = models.IntegerField(blank=True, null=True) exact_times = models.IntegerField(blank=True, null=True)
class Transfer(models.Model): class Transfer(models.Model):
"""
Represents transfer.txt from the GTFS Reference.
"""
from_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_from') from_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_from')
to_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_to') to_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_to')
transfer_type = models.IntegerField() transfer_type = models.IntegerField()
@ -130,6 +178,9 @@ class Transfer(models.Model):
unique_together = (('from_stop', 'to_stop'),) unique_together = (('from_stop', 'to_stop'),)
class Pathway(models.Model): class Pathway(models.Model):
"""
Represents lathway.txt from the GTFS Reference.
"""
pathway_id = models.CharField(max_length=255, primary_key=True) pathway_id = models.CharField(max_length=255, primary_key=True)
from_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_from') from_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_from')
to_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_to') to_stop = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_to')
@ -144,11 +195,17 @@ class Pathway(models.Model):
reversed_signposted_as = models.CharField(max_length=255, blank=True, null=True) reversed_signposted_as = models.CharField(max_length=255, blank=True, null=True)
class Level(models.Model): class Level(models.Model):
"""
Represents level.txt from the GTFS Reference.
"""
level_id = models.CharField(max_length=255, primary_key=True) level_id = models.CharField(max_length=255, primary_key=True)
level_index = models.FloatField() level_index = models.FloatField()
level_name = models.CharField(max_length=255, blank=True, null=True) level_name = models.CharField(max_length=255, blank=True, null=True)
class FeedInfo(models.Model): class FeedInfo(models.Model):
"""
Represents feed_info.txt from the GTFS Reference.
"""
feed_publisher_name = models.CharField(max_length=255) feed_publisher_name = models.CharField(max_length=255)
feed_publisher_url = models.URLField() feed_publisher_url = models.URLField()
feed_lang = models.CharField(max_length=255) feed_lang = models.CharField(max_length=255)
@ -161,6 +218,9 @@ class FeedInfo(models.Model):
feed_id = models.BigAutoField(primary_key=True) feed_id = models.BigAutoField(primary_key=True)
class LocationsGeojson(models.Model): class LocationsGeojson(models.Model):
"""
Represents locations.geojson from the GTFS Reference.
"""
location_id = models.CharField(max_length=255, primary_key=True) location_id = models.CharField(max_length=255, primary_key=True)
location_name = models.CharField(max_length=255) location_name = models.CharField(max_length=255)
location_lat = models.FloatField() location_lat = models.FloatField()
@ -170,6 +230,9 @@ class LocationsGeojson(models.Model):
wheelchair_boarding = models.BooleanField(blank=True, null=True) wheelchair_boarding = models.BooleanField(blank=True, null=True)
class BookingRule(models.Model): class BookingRule(models.Model):
"""
Represents booking_rule.txt from the GTFS Reference.
"""
booking_rule_id = models.CharField(max_length=255, primary_key=True) booking_rule_id = models.CharField(max_length=255, primary_key=True)
trip = models.ForeignKey(Trip, on_delete=models.CASCADE) trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
start_time = models.TimeField(blank=True, null=True) start_time = models.TimeField(blank=True, null=True)
@ -179,12 +242,18 @@ class BookingRule(models.Model):
booking_rule_instructions = models.TextField(blank=True, null=True) booking_rule_instructions = models.TextField(blank=True, null=True)
class Translation(models.Model): class Translation(models.Model):
"""
Represents translation.txt from the GTFS Reference.
"""
table_name = models.CharField(max_length=255) table_name = models.CharField(max_length=255)
field_name = models.CharField(max_length=255) field_name = models.CharField(max_length=255)
language = models.CharField(max_length=2) language = models.CharField(max_length=2)
translation = models.TextField() translation = models.TextField()
class Attribution(models.Model): class Attribution(models.Model):
"""
Represents attribution.txt from the GTFS Reference.
"""
attribution_id = models.CharField(max_length=255, primary_key=True) attribution_id = models.CharField(max_length=255, primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE) agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
attribution_name = models.CharField(max_length=255) attribution_name = models.CharField(max_length=255)
@ -193,53 +262,86 @@ class Attribution(models.Model):
attribution_phone = models.CharField(max_length=50, blank=True, null=True) attribution_phone = models.CharField(max_length=50, blank=True, null=True)
class LocationGroup(models.Model): class LocationGroup(models.Model):
"""
Represents location_group.txt from the GTFS Reference.
"""
location_group_id = models.CharField(max_length=255, primary_key=True) location_group_id = models.CharField(max_length=255, primary_key=True)
location_group_name = models.CharField(max_length=255) location_group_name = models.CharField(max_length=255)
location_group_type = models.CharField(max_length=255) location_group_type = models.CharField(max_length=255)
class LocationGroupStop(models.Model): class LocationGroupStop(models.Model):
"""
Represents location_groupStop.txt from the GTFS Reference.
"""
location_group = models.ForeignKey(LocationGroup, on_delete=models.CASCADE) location_group = models.ForeignKey(LocationGroup, on_delete=models.CASCADE)
stop = models.ForeignKey(Stop, on_delete=models.CASCADE) stop = models.ForeignKey(Stop, on_delete=models.CASCADE)
class RouteNetwork(models.Model): class RouteNetwork(models.Model):
"""
Represents route_network.txt from the GTFS Reference.
"""
route_network_id = models.CharField(max_length=255, primary_key=True) route_network_id = models.CharField(max_length=255, primary_key=True)
route_network_name = models.CharField(max_length=255) route_network_name = models.CharField(max_length=255)
class Network(models.Model): class Network(models.Model):
"""
Represents network.txt from the GTFS Reference.
"""
network_id = models.CharField(max_length=255, primary_key=True) network_id = models.CharField(max_length=255, primary_key=True)
network_name = models.CharField(max_length=255) network_name = models.CharField(max_length=255)
class StopArea(models.Model): class StopArea(models.Model):
"""
Represents stop_area.txt from the GTFS Reference.
"""
stop_area_id = models.CharField(max_length=255, primary_key=True) stop_area_id = models.CharField(max_length=255, primary_key=True)
stop_area_name = models.CharField(max_length=255) stop_area_name = models.CharField(max_length=255)
stop_area_description = models.TextField(blank=True, null=True) stop_area_description = models.TextField(blank=True, null=True)
class Area(models.Model): class Area(models.Model):
"""
Represents area.txt from the GTFS Reference.
"""
area_id = models.CharField(max_length=255, primary_key=True) area_id = models.CharField(max_length=255, primary_key=True)
area_name = models.CharField(max_length=255) area_name = models.CharField(max_length=255)
area_description = models.TextField(blank=True, null=True) area_description = models.TextField(blank=True, null=True)
class FareMedium(models.Model): class FareMedium(models.Model):
"""
Represents fare_medium.txt from the GTFS Reference.
"""
fare_media_id = models.CharField(max_length=255, primary_key=True) fare_media_id = models.CharField(max_length=255, primary_key=True)
fare_media_name = models.CharField(max_length=255) fare_media_name = models.CharField(max_length=255)
fare_media_description = models.TextField(blank=True, null=True) fare_media_description = models.TextField(blank=True, null=True)
class FareProduct(models.Model): class FareProduct(models.Model):
"""
Represents fare_product.txt from the GTFS Reference.
"""
fare_product_id = models.CharField(max_length=255, primary_key=True) fare_product_id = models.CharField(max_length=255, primary_key=True)
fare_product_name = models.CharField(max_length=255) fare_product_name = models.CharField(max_length=255)
fare_product_description = models.TextField(blank=True, null=True) fare_product_description = models.TextField(blank=True, null=True)
class FareLegRule(models.Model): class FareLegRule(models.Model):
"""
Represents fare_legRule.txt from the GTFS Reference.
"""
fare_leg_rule_id = models.CharField(max_length=255, primary_key=True) fare_leg_rule_id = models.CharField(max_length=255, primary_key=True)
fare_leg_rule_name = models.CharField(max_length=255) fare_leg_rule_name = models.CharField(max_length=255)
fare_leg_rule_description = models.TextField(blank=True, null=True) fare_leg_rule_description = models.TextField(blank=True, null=True)
class FareTransferRule(models.Model): class FareTransferRule(models.Model):
"""
Represents faretransfer_rule.txt from the GTFS Reference.
"""
fare_transfer_rule_id = models.CharField(max_length=255, primary_key=True) fare_transfer_rule_id = models.CharField(max_length=255, primary_key=True)
fare_transfer_rule_name = models.CharField(max_length=255) fare_transfer_rule_name = models.CharField(max_length=255)
fare_transfer_rule_description = models.TextField(blank=True, null=True) fare_transfer_rule_description = models.TextField(blank=True, null=True)
class Timeframe(models.Model): class Timeframe(models.Model):
"""
Represents timeframe.txt from the GTFS Reference.
"""
time_frame_id = models.CharField(max_length=255, primary_key=True) time_frame_id = models.CharField(max_length=255, primary_key=True)
start_date = models.DateField() start_date = models.DateField()
end_date = models.DateField() end_date = models.DateField()

View File

@ -1,7 +1,10 @@
"""
Redirecting HTTP requests destined for the app pt_map to the correct views
"""
from django.urls import path from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("", views.index, name="index"), path("", views.index, name="index"),
path("testing/", views.testing, name="testing"), ]
]

View File

@ -1,3 +1,13 @@
"""
Views
=====
Views reacting to Http Requests by interfacing between backend and frontend.
Functions
---------
index(request)
Home page
"""
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponse from django.http import HttpResponse
from .models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, FeedInfo from .models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, FeedInfo

View File

@ -1,18 +1,5 @@
""" """
URL configuration for transport_accessibility project. URL configuration for transport_accessibility 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.contrib import admin
from django.urls import path, include from django.urls import path, include