Backend changes and sql convenience

- Added sql files to create and drop db and to feed example data.
- Added bridge to import (and later export) data from a gtfs.GTFS object
  to the database.
- Updated models and migrations to implement the whole GTFS reference.
This commit is contained in:
Johannes Randerath 2024-06-02 20:58:39 +02:00
parent e8d02517af
commit af5a2c862d
26 changed files with 31006 additions and 64 deletions

8
bin/calc-prorate Executable file
View File

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

8
bin/cheroot Executable file
View File

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

8
bin/cherryd Executable file
View File

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

473
bin/dumppdf.py Executable file
View File

@ -0,0 +1,473 @@
#!/home/johannes/code/transport-accessibility/bin/python
"""Extract pdf structure in XML format"""
import logging
import os.path
import re
import sys
from typing import Any, Container, Dict, Iterable, List, Optional, TextIO, Union, cast
from argparse import ArgumentParser
import pdfminer
from pdfminer.pdfdocument import PDFDocument, PDFNoOutlines, PDFXRefFallback
from pdfminer.pdfpage import PDFPage
from pdfminer.pdfparser import PDFParser
from pdfminer.pdftypes import PDFObjectNotFound, PDFValueError
from pdfminer.pdftypes import PDFStream, PDFObjRef, resolve1, stream_value
from pdfminer.psparser import PSKeyword, PSLiteral, LIT
from pdfminer.utils import isnumber
logging.basicConfig()
logger = logging.getLogger(__name__)
ESC_PAT = re.compile(r'[\000-\037&<>()"\042\047\134\177-\377]')
def escape(s: Union[str, bytes]) -> str:
if isinstance(s, bytes):
us = str(s, "latin-1")
else:
us = s
return ESC_PAT.sub(lambda m: "&#%d;" % ord(m.group(0)), us)
def dumpxml(out: TextIO, obj: object, codec: Optional[str] = None) -> None:
if obj is None:
out.write("<null />")
return
if isinstance(obj, dict):
out.write('<dict size="%d">\n' % len(obj))
for (k, v) in obj.items():
out.write("<key>%s</key>\n" % k)
out.write("<value>")
dumpxml(out, v)
out.write("</value>\n")
out.write("</dict>")
return
if isinstance(obj, list):
out.write('<list size="%d">\n' % len(obj))
for v in obj:
dumpxml(out, v)
out.write("\n")
out.write("</list>")
return
if isinstance(obj, (str, bytes)):
out.write('<string size="%d">%s</string>' % (len(obj), escape(obj)))
return
if isinstance(obj, PDFStream):
if codec == "raw":
# Bug: writing bytes to text I/O. This will raise TypeError.
out.write(obj.get_rawdata()) # type: ignore [arg-type]
elif codec == "binary":
# Bug: writing bytes to text I/O. This will raise TypeError.
out.write(obj.get_data()) # type: ignore [arg-type]
else:
out.write("<stream>\n<props>\n")
dumpxml(out, obj.attrs)
out.write("\n</props>\n")
if codec == "text":
data = obj.get_data()
out.write('<data size="%d">%s</data>\n' % (len(data), escape(data)))
out.write("</stream>")
return
if isinstance(obj, PDFObjRef):
out.write('<ref id="%d" />' % obj.objid)
return
if isinstance(obj, PSKeyword):
# Likely bug: obj.name is bytes, not str
out.write("<keyword>%s</keyword>" % obj.name) # type: ignore [str-bytes-safe]
return
if isinstance(obj, PSLiteral):
# Likely bug: obj.name may be bytes, not str
out.write("<literal>%s</literal>" % obj.name) # type: ignore [str-bytes-safe]
return
if isnumber(obj):
out.write("<number>%s</number>" % obj)
return
raise TypeError(obj)
def dumptrailers(
out: TextIO, doc: PDFDocument, show_fallback_xref: bool = False
) -> None:
for xref in doc.xrefs:
if not isinstance(xref, PDFXRefFallback) or show_fallback_xref:
out.write("<trailer>\n")
dumpxml(out, xref.get_trailer())
out.write("\n</trailer>\n\n")
no_xrefs = all(isinstance(xref, PDFXRefFallback) for xref in doc.xrefs)
if no_xrefs and not show_fallback_xref:
msg = (
"This PDF does not have an xref. Use --show-fallback-xref if "
"you want to display the content of a fallback xref that "
"contains all objects."
)
logger.warning(msg)
return
def dumpallobjs(
out: TextIO,
doc: PDFDocument,
codec: Optional[str] = None,
show_fallback_xref: bool = False,
) -> None:
visited = set()
out.write("<pdf>")
for xref in doc.xrefs:
for objid in xref.get_objids():
if objid in visited:
continue
visited.add(objid)
try:
obj = doc.getobj(objid)
if obj is None:
continue
out.write('<object id="%d">\n' % objid)
dumpxml(out, obj, codec=codec)
out.write("\n</object>\n\n")
except PDFObjectNotFound as e:
print("not found: %r" % e)
dumptrailers(out, doc, show_fallback_xref)
out.write("</pdf>")
return
def dumpoutline(
outfp: TextIO,
fname: str,
objids: Any,
pagenos: Container[int],
password: str = "",
dumpall: bool = False,
codec: Optional[str] = None,
extractdir: Optional[str] = None,
) -> None:
fp = open(fname, "rb")
parser = PDFParser(fp)
doc = PDFDocument(parser, password)
pages = {
page.pageid: pageno
for (pageno, page) in enumerate(PDFPage.create_pages(doc), 1)
}
def resolve_dest(dest: object) -> Any:
if isinstance(dest, (str, bytes)):
dest = resolve1(doc.get_dest(dest))
elif isinstance(dest, PSLiteral):
dest = resolve1(doc.get_dest(dest.name))
if isinstance(dest, dict):
dest = dest["D"]
if isinstance(dest, PDFObjRef):
dest = dest.resolve()
return dest
try:
outlines = doc.get_outlines()
outfp.write("<outlines>\n")
for (level, title, dest, a, se) in outlines:
pageno = None
if dest:
dest = resolve_dest(dest)
pageno = pages[dest[0].objid]
elif a:
action = a
if isinstance(action, dict):
subtype = action.get("S")
if subtype and repr(subtype) == "/'GoTo'" and action.get("D"):
dest = resolve_dest(action["D"])
pageno = pages[dest[0].objid]
s = escape(title)
outfp.write('<outline level="{!r}" title="{}">\n'.format(level, s))
if dest is not None:
outfp.write("<dest>")
dumpxml(outfp, dest)
outfp.write("</dest>\n")
if pageno is not None:
outfp.write("<pageno>%r</pageno>\n" % pageno)
outfp.write("</outline>\n")
outfp.write("</outlines>\n")
except PDFNoOutlines:
pass
parser.close()
fp.close()
return
LITERAL_FILESPEC = LIT("Filespec")
LITERAL_EMBEDDEDFILE = LIT("EmbeddedFile")
def extractembedded(fname: str, password: str, extractdir: str) -> None:
def extract1(objid: int, obj: Dict[str, Any]) -> None:
filename = os.path.basename(obj.get("UF") or cast(bytes, obj.get("F")).decode())
fileref = obj["EF"].get("UF") or obj["EF"].get("F")
fileobj = doc.getobj(fileref.objid)
if not isinstance(fileobj, PDFStream):
error_msg = (
"unable to process PDF: reference for %r is not a "
"PDFStream" % filename
)
raise PDFValueError(error_msg)
if fileobj.get("Type") is not LITERAL_EMBEDDEDFILE:
raise PDFValueError(
"unable to process PDF: reference for %r "
"is not an EmbeddedFile" % (filename)
)
path = os.path.join(extractdir, "%.6d-%s" % (objid, filename))
if os.path.exists(path):
raise IOError("file exists: %r" % path)
print("extracting: %r" % path)
os.makedirs(os.path.dirname(path), exist_ok=True)
out = open(path, "wb")
out.write(fileobj.get_data())
out.close()
return
with open(fname, "rb") as fp:
parser = PDFParser(fp)
doc = PDFDocument(parser, password)
extracted_objids = set()
for xref in doc.xrefs:
for objid in xref.get_objids():
obj = doc.getobj(objid)
if (
objid not in extracted_objids
and isinstance(obj, dict)
and obj.get("Type") is LITERAL_FILESPEC
):
extracted_objids.add(objid)
extract1(objid, obj)
return
def dumppdf(
outfp: TextIO,
fname: str,
objids: Iterable[int],
pagenos: Container[int],
password: str = "",
dumpall: bool = False,
codec: Optional[str] = None,
extractdir: Optional[str] = None,
show_fallback_xref: bool = False,
) -> None:
fp = open(fname, "rb")
parser = PDFParser(fp)
doc = PDFDocument(parser, password)
if objids:
for objid in objids:
obj = doc.getobj(objid)
dumpxml(outfp, obj, codec=codec)
if pagenos:
for (pageno, page) in enumerate(PDFPage.create_pages(doc)):
if pageno in pagenos:
if codec:
for obj in page.contents:
obj = stream_value(obj)
dumpxml(outfp, obj, codec=codec)
else:
dumpxml(outfp, page.attrs)
if dumpall:
dumpallobjs(outfp, doc, codec, show_fallback_xref)
if (not objids) and (not pagenos) and (not dumpall):
dumptrailers(outfp, doc, show_fallback_xref)
fp.close()
if codec not in ("raw", "binary"):
outfp.write("\n")
return
def create_parser() -> ArgumentParser:
parser = ArgumentParser(description=__doc__, add_help=True)
parser.add_argument(
"files",
type=str,
default=None,
nargs="+",
help="One or more paths to PDF files.",
)
parser.add_argument(
"--version",
"-v",
action="version",
version="pdfminer.six v{}".format(pdfminer.__version__),
)
parser.add_argument(
"--debug",
"-d",
default=False,
action="store_true",
help="Use debug logging level.",
)
procedure_parser = parser.add_mutually_exclusive_group()
procedure_parser.add_argument(
"--extract-toc",
"-T",
default=False,
action="store_true",
help="Extract structure of outline",
)
procedure_parser.add_argument(
"--extract-embedded", "-E", type=str, help="Extract embedded files"
)
parse_params = parser.add_argument_group(
"Parser", description="Used during PDF parsing"
)
parse_params.add_argument(
"--page-numbers",
type=int,
default=None,
nargs="+",
help="A space-seperated list of page numbers to parse.",
)
parse_params.add_argument(
"--pagenos",
"-p",
type=str,
help="A comma-separated list of page numbers to parse. Included for "
"legacy applications, use --page-numbers for more idiomatic "
"argument entry.",
)
parse_params.add_argument(
"--objects",
"-i",
type=str,
help="Comma separated list of object numbers to extract",
)
parse_params.add_argument(
"--all",
"-a",
default=False,
action="store_true",
help="If the structure of all objects should be extracted",
)
parse_params.add_argument(
"--show-fallback-xref",
action="store_true",
help="Additionally show the fallback xref. Use this if the PDF "
"has zero or only invalid xref's. This setting is ignored if "
"--extract-toc or --extract-embedded is used.",
)
parse_params.add_argument(
"--password",
"-P",
type=str,
default="",
help="The password to use for decrypting PDF file.",
)
output_params = parser.add_argument_group(
"Output", description="Used during output generation."
)
output_params.add_argument(
"--outfile",
"-o",
type=str,
default="-",
help='Path to file where output is written. Or "-" (default) to '
"write to stdout.",
)
codec_parser = output_params.add_mutually_exclusive_group()
codec_parser.add_argument(
"--raw-stream",
"-r",
default=False,
action="store_true",
help="Write stream objects without encoding",
)
codec_parser.add_argument(
"--binary-stream",
"-b",
default=False,
action="store_true",
help="Write stream objects with binary encoding",
)
codec_parser.add_argument(
"--text-stream",
"-t",
default=False,
action="store_true",
help="Write stream objects as plain text",
)
return parser
def main(argv: Optional[List[str]] = None) -> None:
parser = create_parser()
args = parser.parse_args(args=argv)
if args.debug:
logging.getLogger().setLevel(logging.DEBUG)
if args.outfile == "-":
outfp = sys.stdout
else:
outfp = open(args.outfile, "w")
if args.objects:
objids = [int(x) for x in args.objects.split(",")]
else:
objids = []
if args.page_numbers:
pagenos = {x - 1 for x in args.page_numbers}
elif args.pagenos:
pagenos = {int(x) - 1 for x in args.pagenos.split(",")}
else:
pagenos = set()
password = args.password
if args.raw_stream:
codec: Optional[str] = "raw"
elif args.binary_stream:
codec = "binary"
elif args.text_stream:
codec = "text"
else:
codec = None
for fname in args.files:
if args.extract_toc:
dumpoutline(
outfp,
fname,
objids,
pagenos,
password=password,
dumpall=args.all,
codec=codec,
extractdir=None,
)
elif args.extract_embedded:
extractembedded(fname, password=password, extractdir=args.extract_embedded)
else:
dumppdf(
outfp,
fname,
objids,
pagenos,
password=password,
dumpall=args.all,
codec=codec,
extractdir=None,
show_fallback_xref=args.show_fallback_xref,
)
outfp.close()
if __name__ == "__main__":
main()

8
bin/futurize Executable file
View File

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

8
bin/nltk Executable file
View File

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

8
bin/normalizer Executable file
View File

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

8
bin/pasteurize Executable file
View File

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

317
bin/pdf2txt.py Executable file
View File

@ -0,0 +1,317 @@
#!/home/johannes/code/transport-accessibility/bin/python
"""A command line tool for extracting text and images from PDF and
output it to plain text, html, xml or tags."""
import argparse
import logging
import sys
from typing import Any, Container, Iterable, List, Optional
import pdfminer.high_level
from pdfminer.layout import LAParams
from pdfminer.utils import AnyIO
logging.basicConfig()
OUTPUT_TYPES = ((".htm", "html"), (".html", "html"), (".xml", "xml"), (".tag", "tag"))
def float_or_disabled(x: str) -> Optional[float]:
if x.lower().strip() == "disabled":
return None
try:
return float(x)
except ValueError:
raise argparse.ArgumentTypeError("invalid float value: {}".format(x))
def extract_text(
files: Iterable[str] = [],
outfile: str = "-",
laparams: Optional[LAParams] = None,
output_type: str = "text",
codec: str = "utf-8",
strip_control: bool = False,
maxpages: int = 0,
page_numbers: Optional[Container[int]] = None,
password: str = "",
scale: float = 1.0,
rotation: int = 0,
layoutmode: str = "normal",
output_dir: Optional[str] = None,
debug: bool = False,
disable_caching: bool = False,
**kwargs: Any
) -> AnyIO:
if not files:
raise ValueError("Must provide files to work upon!")
if output_type == "text" and outfile != "-":
for override, alttype in OUTPUT_TYPES:
if outfile.endswith(override):
output_type = alttype
if outfile == "-":
outfp: AnyIO = sys.stdout
if sys.stdout.encoding is not None:
codec = "utf-8"
else:
outfp = open(outfile, "wb")
for fname in files:
with open(fname, "rb") as fp:
pdfminer.high_level.extract_text_to_fp(fp, **locals())
return outfp
def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__, add_help=True)
parser.add_argument(
"files",
type=str,
default=None,
nargs="+",
help="One or more paths to PDF files.",
)
parser.add_argument(
"--version",
"-v",
action="version",
version="pdfminer.six v{}".format(pdfminer.__version__),
)
parser.add_argument(
"--debug",
"-d",
default=False,
action="store_true",
help="Use debug logging level.",
)
parser.add_argument(
"--disable-caching",
"-C",
default=False,
action="store_true",
help="If caching or resources, such as fonts, should be disabled.",
)
parse_params = parser.add_argument_group(
"Parser", description="Used during PDF parsing"
)
parse_params.add_argument(
"--page-numbers",
type=int,
default=None,
nargs="+",
help="A space-seperated list of page numbers to parse.",
)
parse_params.add_argument(
"--pagenos",
"-p",
type=str,
help="A comma-separated list of page numbers to parse. "
"Included for legacy applications, use --page-numbers "
"for more idiomatic argument entry.",
)
parse_params.add_argument(
"--maxpages",
"-m",
type=int,
default=0,
help="The maximum number of pages to parse.",
)
parse_params.add_argument(
"--password",
"-P",
type=str,
default="",
help="The password to use for decrypting PDF file.",
)
parse_params.add_argument(
"--rotation",
"-R",
default=0,
type=int,
help="The number of degrees to rotate the PDF "
"before other types of processing.",
)
la_params = LAParams() # will be used for defaults
la_param_group = parser.add_argument_group(
"Layout analysis", description="Used during layout analysis."
)
la_param_group.add_argument(
"--no-laparams",
"-n",
default=False,
action="store_true",
help="If layout analysis parameters should be ignored.",
)
la_param_group.add_argument(
"--detect-vertical",
"-V",
default=la_params.detect_vertical,
action="store_true",
help="If vertical text should be considered during layout analysis",
)
la_param_group.add_argument(
"--line-overlap",
type=float,
default=la_params.line_overlap,
help="If two characters have more overlap than this they "
"are considered to be on the same line. The overlap is specified "
"relative to the minimum height of both characters.",
)
la_param_group.add_argument(
"--char-margin",
"-M",
type=float,
default=la_params.char_margin,
help="If two characters are closer together than this margin they "
"are considered to be part of the same line. The margin is "
"specified relative to the width of the character.",
)
la_param_group.add_argument(
"--word-margin",
"-W",
type=float,
default=la_params.word_margin,
help="If two characters on the same line are further apart than this "
"margin then they are considered to be two separate words, and "
"an intermediate space will be added for readability. The margin "
"is specified relative to the width of the character.",
)
la_param_group.add_argument(
"--line-margin",
"-L",
type=float,
default=la_params.line_margin,
help="If two lines are close together they are considered to "
"be part of the same paragraph. The margin is specified "
"relative to the height of a line.",
)
la_param_group.add_argument(
"--boxes-flow",
"-F",
type=float_or_disabled,
default=la_params.boxes_flow,
help="Specifies how much a horizontal and vertical position of a "
"text matters when determining the order of lines. The value "
"should be within the range of -1.0 (only horizontal position "
"matters) to +1.0 (only vertical position matters). You can also "
"pass `disabled` to disable advanced layout analysis, and "
"instead return text based on the position of the bottom left "
"corner of the text box.",
)
la_param_group.add_argument(
"--all-texts",
"-A",
default=la_params.all_texts,
action="store_true",
help="If layout analysis should be performed on text in figures.",
)
output_params = parser.add_argument_group(
"Output", description="Used during output generation."
)
output_params.add_argument(
"--outfile",
"-o",
type=str,
default="-",
help="Path to file where output is written. "
'Or "-" (default) to write to stdout.',
)
output_params.add_argument(
"--output_type",
"-t",
type=str,
default="text",
help="Type of output to generate {text,html,xml,tag}.",
)
output_params.add_argument(
"--codec",
"-c",
type=str,
default="utf-8",
help="Text encoding to use in output file.",
)
output_params.add_argument(
"--output-dir",
"-O",
default=None,
help="The output directory to put extracted images in. If not given, "
"images are not extracted.",
)
output_params.add_argument(
"--layoutmode",
"-Y",
default="normal",
type=str,
help="Type of layout to use when generating html "
"{normal,exact,loose}. If normal,each line is"
" positioned separately in the html. If exact"
", each character is positioned separately in"
" the html. If loose, same result as normal "
"but with an additional newline after each "
"text line. Only used when output_type is html.",
)
output_params.add_argument(
"--scale",
"-s",
type=float,
default=1.0,
help="The amount of zoom to use when generating html file. "
"Only used when output_type is html.",
)
output_params.add_argument(
"--strip-control",
"-S",
default=False,
action="store_true",
help="Remove control statement from text. "
"Only used when output_type is xml.",
)
return parser
def parse_args(args: Optional[List[str]]) -> argparse.Namespace:
parsed_args = create_parser().parse_args(args=args)
# Propagate parsed layout parameters to LAParams object
if parsed_args.no_laparams:
parsed_args.laparams = None
else:
parsed_args.laparams = LAParams(
line_overlap=parsed_args.line_overlap,
char_margin=parsed_args.char_margin,
line_margin=parsed_args.line_margin,
word_margin=parsed_args.word_margin,
boxes_flow=parsed_args.boxes_flow,
detect_vertical=parsed_args.detect_vertical,
all_texts=parsed_args.all_texts,
)
if parsed_args.page_numbers:
parsed_args.page_numbers = {x - 1 for x in parsed_args.page_numbers}
if parsed_args.pagenos:
parsed_args.page_numbers = {int(x) - 1 for x in parsed_args.pagenos.split(",")}
if parsed_args.output_type == "text" and parsed_args.outfile != "-":
for override, alttype in OUTPUT_TYPES:
if parsed_args.outfile.endswith(override):
parsed_args.output_type = alttype
return parsed_args
def main(args: Optional[List[str]] = None) -> int:
parsed_args = parse_args(args)
outfp = extract_text(**vars(parsed_args))
outfp.close()
return 0
if __name__ == "__main__":
sys.exit(main())

8
bin/tqdm Executable file
View File

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

1
delete_db.sql Normal file
View File

@ -0,0 +1 @@
DROP DATABASE transport_accessibility;

28504
example.sql Normal file

File diff suppressed because it is too large Load Diff

3
setup_db.sql Normal file
View File

@ -0,0 +1,3 @@
CREATE DATABASE IF NOT EXISTS transport_accessibility;
CREATE USER IF NOT EXISTS 'transport_accessibility'@'localhost' IDENTIFIED BY 'L8AClYIsC55SEAWTgYopD';
GRANT ALL PRIVILEGES ON transport_accessibility.* TO 'transport_accessibility'@'localhost';

View File

@ -0,0 +1,291 @@
import pt_map.gtfs
import pt_map.models
import pandas as pd
from pattern.text.en import singularize
import math
import numbers
import email.utils
import time
import datetime
import django.db.models
gtfs_schema = {
"agency": [
"agency_id",
"agency_name",
"agency_url",
"agency_timezone",
"agency_lang",
"agency_phone",
"agency_email",
"agency_fare_url"
],
"stops": [
"stop_id",
"stop_code",
"stop_name",
"stop_desc",
"stop_lat",
"stop_lon",
"zone_id",
"stop_url",
"location_type",
"parent_station",
"stop_timezone",
"wheelchair_boarding",
"level_id",
"platform_code"
],
"routes": [
"route_id",
"agency_id",
"route_short_name",
"route_long_name",
"route_desc",
"route_type",
"route_url",
"route_color",
"route_text_color",
"route_sort_order",
"continuous_pickup",
"continuous_drop_off"
],
"trips": [
"trip_id",
"route_id",
"service_id",
"trip_headsign",
"trip_short_name",
"direction_id",
"block_id",
"shape_id",
"wheelchair_accessible",
"bikes_allowed"
],
"stop_times": [
"trip_id",
"arrival_time",
"departure_time",
"stop_id",
"stop_sequence",
"stop_headsign",
"pickup_type",
"drop_off_type",
"shape_dist_traveled",
"timepoint"
],
"calendar": [
"service_id",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
"sunday",
"start_date",
"end_date"
],
"calendar_dates": [
"service_id",
"date",
"exception_type"
],
"fare_attributes": [
"fare_id",
"price",
"currency_type",
"payment_method",
"transfers",
"transfer_duration"
],
"fare_rules": [
"fare_id",
"route_id",
"origin_id",
"destination_id",
"contains_id"
],
"timeframes": [
"timeframe_id",
"start_time",
"end_time",
"headway_sec",
"exact_times"
],
"fare_media": [
"media_id",
"agency_id",
"fare_id",
"seat_type",
"price"
],
"fare_products": [
"product_id",
"agency_id",
"product_type",
"fare_id",
"product_name",
"short_name",
"description",
"duration",
"transfers"
],
"fare_leg_rules": [
"fare_id",
"route_id",
"origin_id",
"destination_id",
"contains_id"
],
"fare_transfer_rules": [
"from_fare_id",
"to_fare_id",
"transfer_type",
"min_transfer_time"
],
"areas": [
"area_id",
"area_name",
"area_description"
],
"stop_areas": [
"stop_area_id",
"stop_id",
"area_id",
"location_type",
"parent_station",
"fare_zone_id"
],
"networks": [
"network_id",
"network_name",
"network_description"
],
"route_networks": [
"route_id",
"network_id"
],
"shapes": [
"shape_id",
"shape_pt_lat",
"shape_pt_lon",
"shape_pt_sequence",
"shape_dist_traveled"
],
"frequencies": [
"trip_id",
"start_time",
"end_time",
"headway_secs",
"exact_times"
],
"transfers": [
"from_stop_id",
"to_stop_id",
"transfer_type",
"min_transfer_time"
],
"pathways": [
"pathway_id",
"from_stop_id",
"to_stop_id",
"pathway_mode",
"is_bidirectional",
"length",
"traversal_time",
"stair_count",
"max_slope",
"min_width",
"signposted_as",
"reversed_signposted_as"
],
"levels": [
"level_id",
"level_index",
"level_name"
],
"location_groups": [
"location_group_id",
"location_group_name"
],
"location_group_stops": [
"location_group_id",
"stop_id"
],
"locations_geojson": [
"type",
"features"
],
"booking_rules": [
"rule_id",
"stop_id",
"rule_type",
"booking_url",
"admission_rules",
"admission_requirements"
],
"translations": [
"table_name",
"field_name",
"language",
"translation"
],
"feed_info": [
"feed_publisher_name",
"feed_publisher_url",
"feed_lang",
"default_lang",
"feed_start_date",
"feed_end_date",
"feed_version",
"feed_contact_email",
"feed_contact_url"
],
"attributions": [
"attribution_id",
"organization_name",
"is_producer"
]
}
def to_camel_case(s: str):
return ''.join(word.capitalize() for word in s.split('_'))
def standardize_time(time_str):
date_str = f"Jan 19, 1999 {time_str}"
ntuple=email.utils.parsedate(date_str)
timestamp=time.mktime(ntuple)
date=datetime.datetime.fromtimestamp(timestamp)
return date.strftime('%H:%M:%S')
def is_NaN(v):
return (isinstance(v, str) and v.lower() == "nan") or (isinstance(v, numbers.Number) and math.isnan(v))
def stdz(v, m, f):
if m._meta.get_field(f).get_internal_type() == 'DateField':
return str(v)
if m._meta.get_field(f).get_internal_type() == 'TimeField':
return standardize_time(v)
return v
def gtfs_to_db(g: pt_map.gtfs.GTFS):
for k,v in gtfs_schema.items():
name = to_camel_case(singularize(k))
m = getattr(pt_map.models, name)
df = getattr(g, k).data
print("\n\n\n\n")
print(name)
print("#############################################################################################")
if not df.empty:
for _, row in df.iterrows():
defaults = {field: stdz(row.get(field), m, field) for field in v if row.get(field) and not is_NaN(row[field])}
kw_args = {v[0]: row[v[0]]}
m.objects.update_or_create(
defaults = defaults,
**kw_args,
)

View File

@ -13,10 +13,27 @@ class GTFS:
self.calendar_dates = self.CalendarDates(self.folder_path)
self.fare_attributes = self.FareAttributes(self.folder_path)
self.fare_rules = self.FareRules(self.folder_path)
self.timeframes = self.Timeframes(self.folder_path)
self.fare_media = self.FareMedia(self.folder_path)
self.fare_products = self.FareProducts(self.folder_path)
self.fare_leg_rules = self.FareLegRules(self.folder_path)
self.fare_transfer_rules = self.FareTransferRules(self.folder_path)
self.areas = self.Areas(self.folder_path)
self.stop_areas = self.StopAreas(self.folder_path)
self.networks = self.Networks(self.folder_path)
self.route_networks = self.RouteNetworks(self.folder_path)
self.shapes = self.Shapes(self.folder_path)
self.frequencies = self.Frequencies(self.folder_path)
self.transfers = self.Transfers(self.folder_path)
self.pathways = self.Pathways(self.folder_path)
self.levels = self.Levels(self.folder_path)
self.location_groups = self.LocationGroups(self.folder_path)
self.location_group_stops = self.LocationGroupStops(self.folder_path)
self.locations_geojson = self.LocationsGeojson(self.folder_path)
self.booking_rules = self.BookingRules(self.folder_path)
self.translations = self.Translations(self.folder_path)
self.feed_info = self.FeedInfo(self.folder_path)
self.attributions = self.Attributions(self.folder_path)
self.errors = []
class GTFSFile:
@ -66,6 +83,42 @@ class GTFS:
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_rules')
class Timeframes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'timeframes')
class FareMedia(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_media')
class FareProducts(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_products')
class FareLegRules(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_leg_rules')
class FareTransferRules(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_transfer_rules')
class Areas(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'areas')
class StopAreas(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'stop_areas')
class Networks(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'networks')
class RouteNetworks(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'route_networks')
class Shapes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'shapes')
@ -78,33 +131,74 @@ class GTFS:
def __init__(self, folder_path):
super().__init__(folder_path, 'transfers')
class Pathways(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'pathways')
class Levels(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'levels')
class LocationGroups(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'location_groups')
class LocationGroupStops(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'location_group_stops')
class LocationsGeojson(GTFSFile):
def __init__(self, folder_path):
self.file_path = f"{folder_path}/locations.geojson"
if os.path.exists(self.file_path):
self.data = self.load_data()
else:
self.data = pd.DataFrame()
def load_data(self):
try:
return pd.read_json(self.file_path)
except ValueError:
return pd.DataFrame()
class BookingRules(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'booking_rules')
class Translations(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'translations')
class FeedInfo(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'feed_info')
class Attributions(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'attributions')
def get_files(self):
return [attr for attr in list(set(dir(self)) - set(dir(GTFS))) 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):
file = getattr(self, name)
if not file:
return None
return list(set(dir(file)) - set(dir(GTFSFile)))
return list(file.data.columns)
def export(self, path, dirname):
path = f"{os.path.normpath(path)}/{dirname}"
if not os.path.exists(path):
os.mkdir(path)
print(self.get_files())
for name in self.get_files():
df = getattr(self, name).data
fpath = f"{path}/{name}.txt"
# print(f"name: {name}")
print(name)
if name == 'locations_geojson':
fpath = f"{path}/{name}.geojson"
df.to_json(fpath)
else:
df.to_csv(fpath, index=False)
def validate(self):
self.validate_agency()
self.validate_stops()
@ -112,6 +206,30 @@ class GTFS:
self.validate_trips()
self.validate_stop_times()
self.validate_calendar()
self.validate_calendar_dates()
self.validate_fare_attributes()
self.validate_fare_rules()
self.validate_timeframes()
self.validate_fare_media()
self.validate_fare_products()
self.validate_fare_leg_rules()
self.validate_fare_transfer_rules()
self.validate_areas()
self.validate_stop_areas()
self.validate_networks()
self.validate_route_networks()
self.validate_shapes()
self.validate_frequencies()
self.validate_transfers()
self.validate_pathways()
self.validate_levels()
self.validate_location_groups()
self.validate_location_group_stops()
self.validate_locations_geojson()
self.validate_booking_rules()
self.validate_translations()
self.validate_feed_info()
self.validate_attributions()
self.validate_cross_references()
if not self.errors:
@ -120,54 +238,207 @@ class GTFS:
return self.errors
def validate_agency(self):
required_fields = ["agency_id", "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"]
self.validate_required_fields(self.agency.data, required_fields, "agency.txt")
self.validate_optional_fields(self.agency.data, optional_fields, "agency.txt")
def validate_stops(self):
required_fields = ["stop_id", "stop_name", "stop_lat", "stop_lon"]
required_fields = ["stop_id", "stop_name"]
optional_fields = ["stop_code", "stop_desc", "stop_lat", "stop_lon", "zone_id", "stop_url",
"location_type", "parent_station", "stop_timezone", "wheelchair_boarding",
"level_id", "platform_code"]
self.validate_required_fields(self.stops.data, required_fields, "stops.txt")
self.validate_optional_fields(self.stops.data, optional_fields, "stops.txt")
self.validate_lat_lon(self.stops.data)
def validate_routes(self):
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",
"route_sort_order", "continuous_pickup", "continuous_drop_off"]
self.validate_required_fields(self.routes.data, required_fields, "routes.txt")
self.validate_optional_fields(self.routes.data, optional_fields, "routes.txt")
def validate_trips(self):
required_fields = ["route_id", "service_id", "trip_id"]
optional_fields = ["trip_headsign", "trip_short_name", "direction_id", "block_id", "shape_id",
"wheelchair_accessible", "bikes_allowed"]
self.validate_required_fields(self.trips.data, required_fields, "trips.txt")
self.validate_optional_fields(self.trips.data, optional_fields, "trips.txt")
def validate_stop_times(self):
required_fields = ["trip_id", "arrival_time", "departure_time", "stop_id", "stop_sequence"]
optional_fields = ["stop_headsign", "pickup_type", "drop_off_type", "shape_dist_traveled",
"timepoint"]
self.validate_required_fields(self.stop_times.data, required_fields, "stop_times.txt")
self.validate_optional_fields(self.stop_times.data, optional_fields, "stop_times.txt")
def validate_calendar(self):
required_fields = ["service_id", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "start_date", "end_date"]
required_fields = ["service_id", "monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday", "start_date", "end_date"]
self.validate_required_fields(self.calendar.data, required_fields, "calendar.txt")
def validate_required_fields(self, data, required_fields, filename):
for field in required_fields:
if field not in data.columns:
self.errors.append(f"Error: {filename} missing required field: {field}")
def validate_calendar_dates(self):
required_fields = ["service_id", "date", "exception_type"]
self.validate_required_fields(self.calendar_dates.data, required_fields, "calendar_dates.txt")
def validate_lat_lon(self, data):
for index, row in data.iterrows():
if not (-90 <= row['stop_lat'] <= 90):
self.errors.append(f"Error: stops.txt invalid latitude at row {index}: {row['stop_lat']}")
if not (-180 <= row['stop_lon'] <= 180):
self.errors.append(f"Error: stops.txt invalid longitude at row {index}: {row['stop_lon']}")
def validate_fare_attributes(self):
required_fields = ["fare_id", "price", "currency_type", "payment_method", "transfers"]
optional_fields = ["agency_id", "transfer_duration"]
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")
def validate_fare_rules(self):
required_fields = ["fare_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_optional_fields(self.fare_rules.data, optional_fields, "fare_rules.txt")
def validate_timeframes(self):
required_fields = ["timeframe_id", "start_time", "end_time"]
optional_fields = ["timeframe_name", "timeframe_desc"]
self.validate_required_fields(self.timeframes.data, required_fields, "timeframes.txt")
self.validate_optional_fields(self.timeframes.data, optional_fields, "timeframes.txt")
def validate_fare_media(self):
required_fields = ["media_id", "media_name", "media_type"]
optional_fields = ["media_desc"]
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")
def validate_fare_products(self):
required_fields = ["product_id", "product_name", "product_type", "product_price", "currency"]
optional_fields = ["product_desc"]
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")
def validate_fare_leg_rules(self):
required_fields = ["leg_id", "from_stop_id", "to_stop_id"]
optional_fields = ["leg_desc"]
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")
def validate_fare_transfer_rules(self):
required_fields = ["from_leg_id", "to_leg_id", "transfer_type"]
optional_fields = ["transfer_time"]
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")
def validate_areas(self):
required_fields = ["area_id", "area_name"]
optional_fields = ["area_desc"]
self.validate_required_fields(self.areas.data, required_fields, "areas.txt")
self.validate_optional_fields(self.areas.data, optional_fields, "areas.txt")
def validate_stop_areas(self):
required_fields = ["stop_id", "area_id"]
optional_fields = []
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")
def validate_networks(self):
required_fields = ["network_id", "network_name"]
optional_fields = ["network_desc"]
self.validate_required_fields(self.networks.data, required_fields, "networks.txt")
self.validate_optional_fields(self.networks.data, optional_fields, "networks.txt")
def validate_route_networks(self):
required_fields = ["route_id", "network_id"]
optional_fields = []
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")
def validate_shapes(self):
required_fields = ["shape_id", "shape_pt_lat", "shape_pt_lon", "shape_pt_sequence"]
optional_fields = ["shape_dist_traveled"]
self.validate_required_fields(self.shapes.data, required_fields, "shapes.txt")
self.validate_optional_fields(self.shapes.data, optional_fields, "shapes.txt")
def validate_frequencies(self):
required_fields = ["trip_id", "start_time", "end_time", "headway_secs"]
optional_fields = ["exact_times"]
self.validate_required_fields(self.frequencies.data, required_fields, "frequencies.txt")
self.validate_optional_fields(self.frequencies.data, optional_fields, "frequencies.txt")
def validate_transfers(self):
required_fields = ["from_stop_id", "to_stop_id", "transfer_type"]
optional_fields = ["min_transfer_time"]
self.validate_required_fields(self.transfers.data, required_fields, "transfers.txt")
self.validate_optional_fields(self.transfers.data, optional_fields, "transfers.txt")
def validate_pathways(self):
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"]
self.validate_required_fields(self.pathways.data, required_fields, "pathways.txt")
self.validate_optional_fields(self.pathways.data, optional_fields, "pathways.txt")
def validate_levels(self):
required_fields = ["level_id", "level_index"]
optional_fields = ["level_name"]
self.validate_required_fields(self.levels.data, required_fields, "levels.txt")
self.validate_optional_fields(self.levels.data, optional_fields, "levels.txt")
def validate_location_groups(self):
required_fields = ["location_group_id", "location_group_name"]
optional_fields = ["location_group_desc"]
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")
def validate_location_group_stops(self):
required_fields = ["location_group_id", "stop_id"]
optional_fields = []
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")
def validate_locations_geojson(self):
required_fields = ["type", "features"]
optional_fields = []
self.validate_required_fields(self.locations_geojson.data, required_fields, "locations.geojson")
self.validate_optional_fields(self.locations_geojson.data, optional_fields, "locations.geojson")
def validate_booking_rules(self):
required_fields = ["booking_rule_id"]
optional_fields = ["booking_rule_name", "booking_rule_desc"]
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")
def validate_translations(self):
required_fields = ["table_name", "field_name", "language", "translation"]
optional_fields = ["record_id", "record_sub_id", "field_value"]
self.validate_required_fields(self.translations.data, required_fields, "translations.txt")
self.validate_optional_fields(self.translations.data, optional_fields, "translations.txt")
def validate_feed_info(self):
required_fields = ["feed_publisher_name", "feed_publisher_url", "feed_lang"]
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_optional_fields(self.feed_info.data, optional_fields, "feed_info.txt")
def validate_attributions(self):
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"]
self.validate_required_fields(self.attributions.data, required_fields, "attributions.txt")
self.validate_optional_fields(self.attributions.data, optional_fields, "attributions.txt")
def validate_required_fields(self, df, required_fields, file_name):
missing_fields = set(required_fields) - set(df.columns)
if missing_fields:
self.errors.append(f"{file_name} is missing required fields: {missing_fields}")
def validate_optional_fields(self, df, optional_fields, file_name):
unexpected_fields = set(df.columns) - set(optional_fields) - set(df.columns)
if unexpected_fields:
self.errors.append(f"{file_name} has unexpected fields: {unexpected_fields}")
def validate_lat_lon(self, df):
if 'stop_lat' in df.columns and 'stop_lon' in df.columns:
if df[['stop_lat', 'stop_lon']].isnull().any().any():
self.errors.append(f"stops.txt has missing lat/lon values.")
def validate_cross_references(self):
# Validate that trip_ids in stop_times.txt exist in trips.txt
stop_times_trip_ids = set(self.stop_times.data['trip_id'])
trips_trip_ids = set(self.trips.data['trip_id'])
missing_trip_ids = stop_times_trip_ids - trips_trip_ids
for trip_id in missing_trip_ids:
self.errors.append(f"Error: trip_id {trip_id} in stop_times.txt does not exist in trips.txt")
# Validate that stop_ids in stop_times.txt exist in stops.txt
stop_times_stop_ids = set(self.stop_times.data['stop_id'])
stops_stop_ids = set(self.stops.data['stop_id'])
missing_stop_ids = stop_times_stop_ids - stops_stop_ids
for stop_id in missing_stop_ids:
self.errors.append(f"Error: stop_id {stop_id} in stop_times.txt does not exist in stops.txt")
# Example: Validate that all stop_ids in stop_times.txt exist in stops.txt
if not self.stop_times.data.empty and not self.stops.data.empty:
invalid_stops = set(self.stop_times.data['stop_id']) - set(self.stops.data['stop_id'])
if invalid_stops:
self.errors.append(f"stop_times.txt has invalid stop_ids: {invalid_stops}")

View File

@ -0,0 +1,173 @@
import pandas as pd
import os
class GTFS:
def __init__(self, folder_path):
self.folder_path = folder_path
self.agency = self.Agency(self.folder_path)
self.stops = self.Stops(self.folder_path)
self.routes = self.Routes(self.folder_path)
self.trips = self.Trips(self.folder_path)
self.stop_times = self.StopTimes(self.folder_path)
self.calendar = self.Calendar(self.folder_path)
self.calendar_dates = self.CalendarDates(self.folder_path)
self.fare_attributes = self.FareAttributes(self.folder_path)
self.fare_rules = self.FareRules(self.folder_path)
self.shapes = self.Shapes(self.folder_path)
self.frequencies = self.Frequencies(self.folder_path)
self.transfers = self.Transfers(self.folder_path)
self.feed_info = self.FeedInfo(self.folder_path)
self.errors = []
class GTFSFile:
def __init__(self, folder_path, file_name):
self.file_path = f"{folder_path}/{file_name}.txt"
self.data = self.load_data()
def load_data(self):
try:
return pd.read_csv(self.file_path)
except FileNotFoundError:
return pd.DataFrame()
class Agency(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'agency')
class Stops(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'stops')
class Routes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'routes')
class Trips(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'trips')
class StopTimes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'stop_times')
class Calendar(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'calendar')
class CalendarDates(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'calendar_dates')
class FareAttributes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_attributes')
class FareRules(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'fare_rules')
class Shapes(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'shapes')
class Frequencies(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'frequencies')
class Transfers(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'transfers')
class FeedInfo(GTFSFile):
def __init__(self, folder_path):
super().__init__(folder_path, 'feed_info')
def get_files(self):
return [attr for attr in list(set(dir(self)) - set(dir(GTFS))) if isinstance(getattr(self,attr),self.GTFSFile)]
def get_fields(self, name):
file = getattr(self, name)
if not file:
return None
return list(set(dir(file)) - set(dir(GTFSFile)))
def export(self, path, dirname):
path = f"{os.path.normpath(path)}/{dirname}"
if not os.path.exists(path):
os.mkdir(path)
print(self.get_files())
for name in self.get_files():
df = getattr(self, name).data
fpath = f"{path}/{name}.txt"
# print(f"name: {name}")
print(name)
df.to_csv(fpath, index=False)
def validate(self):
self.validate_agency()
self.validate_stops()
self.validate_routes()
self.validate_trips()
self.validate_stop_times()
self.validate_calendar()
self.validate_cross_references()
if not self.errors:
return None
else:
return self.errors
def validate_agency(self):
required_fields = ["agency_id", "agency_name", "agency_url", "agency_timezone"]
self.validate_required_fields(self.agency.data, required_fields, "agency.txt")
def validate_stops(self):
required_fields = ["stop_id", "stop_name", "stop_lat", "stop_lon"]
self.validate_required_fields(self.stops.data, required_fields, "stops.txt")
self.validate_lat_lon(self.stops.data)
def validate_routes(self):
required_fields = ["route_id", "route_short_name", "route_long_name", "route_type"]
self.validate_required_fields(self.routes.data, required_fields, "routes.txt")
def validate_trips(self):
required_fields = ["route_id", "service_id", "trip_id"]
self.validate_required_fields(self.trips.data, required_fields, "trips.txt")
def validate_stop_times(self):
required_fields = ["trip_id", "arrival_time", "departure_time", "stop_id", "stop_sequence"]
self.validate_required_fields(self.stop_times.data, required_fields, "stop_times.txt")
def validate_calendar(self):
required_fields = ["service_id", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", "start_date", "end_date"]
self.validate_required_fields(self.calendar.data, required_fields, "calendar.txt")
def validate_required_fields(self, data, required_fields, filename):
for field in required_fields:
if field not in data.columns:
self.errors.append(f"Error: {filename} missing required field: {field}")
def validate_lat_lon(self, data):
for index, row in data.iterrows():
if not (-90 <= row['stop_lat'] <= 90):
self.errors.append(f"Error: stops.txt invalid latitude at row {index}: {row['stop_lat']}")
if not (-180 <= row['stop_lon'] <= 180):
self.errors.append(f"Error: stops.txt invalid longitude at row {index}: {row['stop_lon']}")
def validate_cross_references(self):
# Validate that trip_ids in stop_times.txt exist in trips.txt
stop_times_trip_ids = set(self.stop_times.data['trip_id'])
trips_trip_ids = set(self.trips.data['trip_id'])
missing_trip_ids = stop_times_trip_ids - trips_trip_ids
for trip_id in missing_trip_ids:
self.errors.append(f"Error: trip_id {trip_id} in stop_times.txt does not exist in trips.txt")
# Validate that stop_ids in stop_times.txt exist in stops.txt
stop_times_stop_ids = set(self.stop_times.data['stop_id'])
stops_stop_ids = set(self.stops.data['stop_id'])
missing_stop_ids = stop_times_stop_ids - stops_stop_ids
for stop_id in missing_stop_ids:
self.errors.append(f"Error: stop_id {stop_id} in stop_times.txt does not exist in stops.txt")

View File

@ -0,0 +1,205 @@
# Generated by Django 5.0.6 on 2024-06-02 11:40
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0004_alter_agency_agency_id_alter_calendar_service_id_and_more'),
]
operations = [
migrations.CreateModel(
name='Level',
fields=[
('level_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('level_index', models.FloatField()),
('level_name', models.CharField(blank=True, max_length=255, null=True)),
],
),
migrations.RemoveField(
model_name='stop',
name='tts_stop_name',
),
migrations.AddField(
model_name='agency',
name='agency_lang',
field=models.CharField(blank=True, max_length=2, null=True),
),
migrations.AddField(
model_name='agency',
name='agency_timezone',
field=models.CharField(default=django.utils.timezone.now, max_length=255),
preserve_default=False,
),
migrations.AddField(
model_name='feedinfo',
name='default_lang',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='feedinfo',
name='feed_contact_email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
migrations.AddField(
model_name='feedinfo',
name='feed_contact_url',
field=models.URLField(blank=True, null=True),
),
migrations.AddField(
model_name='route',
name='continuous_drop_off',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='route',
name='continuous_pickup',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='route',
name='route_sort_order',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='agency',
name='agency_email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
migrations.AlterField(
model_name='agency',
name='agency_fare_url',
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name='agency',
name='agency_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='agency',
name='agency_name',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='agency',
name='agency_phone',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AlterField(
model_name='calendar',
name='service_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='calendardate',
name='service_id',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='fareattribute',
name='currency_type',
field=models.CharField(max_length=3),
),
migrations.AlterField(
model_name='fareattribute',
name='fare_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='route',
name='agency',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pt_map.agency'),
),
migrations.AlterField(
model_name='route',
name='route_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='route',
name='route_long_name',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='route',
name='route_short_name',
field=models.CharField(max_length=50),
),
migrations.AlterField(
model_name='stop',
name='platform_code',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AlterField(
model_name='stop',
name='stop_code',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AlterField(
model_name='stop',
name='stop_desc',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='stop',
name='stop_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.AlterField(
model_name='stop',
name='stop_lat',
field=models.FloatField(),
),
migrations.AlterField(
model_name='stop',
name='stop_lon',
field=models.FloatField(),
),
migrations.AlterField(
model_name='stop',
name='stop_name',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='stop',
name='zone_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='stoptime',
name='arrival_time',
field=models.TimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='stoptime',
name='departure_time',
field=models.TimeField(blank=True, null=True),
),
migrations.AlterField(
model_name='trip',
name='trip_id',
field=models.CharField(max_length=255, primary_key=True, serialize=False),
),
migrations.CreateModel(
name='Pathway',
fields=[
('pathway_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('pathway_mode', models.IntegerField()),
('is_bidirectional', models.IntegerField()),
('length', models.FloatField(blank=True, null=True)),
('traversal_time', models.IntegerField(blank=True, null=True)),
('stair_count', models.IntegerField(blank=True, null=True)),
('max_slope', models.FloatField(blank=True, null=True)),
('min_width', models.FloatField(blank=True, null=True)),
('signposted_as', models.CharField(blank=True, max_length=255, null=True)),
('reversed_signposted_as', models.CharField(blank=True, max_length=255, null=True)),
('from_stop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pathways_from', to='pt_map.stop')),
('to_stop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pathways_to', to='pt_map.stop')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.6 on 2024-06-02 12:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0005_level_remove_stop_tts_stop_name_agency_agency_lang_and_more'),
]
operations = [
migrations.AlterField(
model_name='route',
name='route_type',
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,59 @@
# Generated by Django 5.0.6 on 2024-06-02 16:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0006_alter_route_route_type'),
]
operations = [
migrations.CreateModel(
name='Location',
fields=[
('location_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('location_name', models.CharField(max_length=255)),
('location_lat', models.FloatField()),
('location_lon', models.FloatField()),
('location_type', models.CharField(max_length=255)),
('parent_location_id', models.CharField(blank=True, max_length=255, null=True)),
('wheelchair_boarding', models.BooleanField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Translation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('table_name', models.CharField(max_length=255)),
('field_name', models.CharField(max_length=255)),
('language', models.CharField(max_length=2)),
('translation', models.TextField()),
],
),
migrations.CreateModel(
name='Attribution',
fields=[
('attribution_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('attribution_name', models.CharField(max_length=255)),
('attribution_url', models.URLField()),
('attribution_email', models.EmailField(blank=True, max_length=254, null=True)),
('attribution_phone', models.CharField(blank=True, max_length=50, null=True)),
('agency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pt_map.agency')),
],
),
migrations.CreateModel(
name='BookingRule',
fields=[
('booking_rule_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('start_time', models.TimeField(blank=True, null=True)),
('end_time', models.TimeField(blank=True, null=True)),
('booking_type', models.CharField(max_length=255)),
('rule_criteria', models.TextField(blank=True, null=True)),
('booking_rule_instructions', models.TextField(blank=True, null=True)),
('trip', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pt_map.trip')),
],
),
]

View File

@ -0,0 +1,102 @@
# Generated by Django 5.0.6 on 2024-06-02 17:04
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0007_location_translation_attribution_bookingrule'),
]
operations = [
migrations.CreateModel(
name='Area',
fields=[
('area_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('area_name', models.CharField(max_length=255)),
('area_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='FareLegRule',
fields=[
('fare_leg_rule_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('fare_leg_rule_name', models.CharField(max_length=255)),
('fare_leg_rule_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='FareMedia',
fields=[
('fare_media_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('fare_media_name', models.CharField(max_length=255)),
('fare_media_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='FareProduct',
fields=[
('fare_product_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('fare_product_name', models.CharField(max_length=255)),
('fare_product_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='FareTransferRule',
fields=[
('fare_transfer_rule_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('fare_transfer_rule_name', models.CharField(max_length=255)),
('fare_transfer_rule_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='LocationGroup',
fields=[
('location_group_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('location_group_name', models.CharField(max_length=255)),
('location_group_type', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='Network',
fields=[
('network_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('network_name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='RouteNetwork',
fields=[
('route_network_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('route_network_name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='StopArea',
fields=[
('stop_area_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('stop_area_name', models.CharField(max_length=255)),
('stop_area_description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='TimeFrame',
fields=[
('time_frame_id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('start_date', models.DateField()),
('end_date', models.DateField()),
('start_time', models.TimeField()),
('end_time', models.TimeField()),
],
),
migrations.CreateModel(
name='LocationGroupStop',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('location_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pt_map.locationgroup')),
('stop', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pt_map.stop')),
],
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.6 on 2024-06-02 17:10
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0008_area_farelegrule_faremedia_fareproduct_and_more'),
]
operations = [
migrations.RenameModel(
old_name='FareMedia',
new_name='FareMedium',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.6 on 2024-06-02 17:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0009_rename_faremedia_faremedium'),
]
operations = [
migrations.RenameModel(
old_name='Location',
new_name='LocationGeojson',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.6 on 2024-06-02 17:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pt_map', '0010_rename_location_locationgeojson'),
]
operations = [
migrations.RenameModel(
old_name='LocationGeojson',
new_name='LocationsGeojson',
),
]

View File

@ -1,44 +1,47 @@
from django.db import models
class Agency(models.Model):
agency_id = models.BigAutoField(primary_key=True)
agency_name = models.CharField(max_length=250)
agency_id = models.CharField(max_length=255, primary_key=True)
agency_name = models.CharField(max_length=255)
agency_url = models.URLField()
agency_phone = models.CharField(max_length=15)
agency_email = models.EmailField()
agency_fare_url = models.URLField()
agency_timezone = models.CharField(max_length=255)
agency_lang = models.CharField(max_length=2, blank=True, null=True)
agency_phone = models.CharField(max_length=50, blank=True, null=True)
agency_fare_url = models.URLField(blank=True, null=True)
agency_email = models.EmailField(blank=True, null=True)
class Stop(models.Model):
stop_id = models.BigAutoField(primary_key=True)
stop_code = models.CharField(max_length=50)
stop_name = models.CharField(max_length=250)
tts_stop_name = models.CharField(max_length=250)
stop_desc = models.CharField(max_length=500)
stop_lat = models.IntegerField()
stop_lon = models.IntegerField()
zone_id = models.IntegerField(unique=True)
stop_id = models.CharField(max_length=255, primary_key=True)
stop_code = models.CharField(max_length=50, blank=True, null=True)
stop_name = models.CharField(max_length=255)
stop_desc = models.TextField(blank=True, null=True)
stop_lat = models.FloatField()
stop_lon = models.FloatField()
zone_id = models.CharField(max_length=255, blank=True, null=True)
stop_url = models.URLField(blank=True, null=True)
location_type = models.IntegerField(blank=True, null=True)
parent_station = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True)
stop_timezone = models.CharField(max_length=255, blank=True, null=True)
wheelchair_boarding = models.IntegerField(blank=True, null=True)
level_id = models.CharField(max_length=255, blank=True, null=True)
platform_code = models.CharField(max_length=255, blank=True, null=True)
platform_code = models.CharField(max_length=50, blank=True, null=True)
class Route(models.Model):
route_id = models.BigAutoField(primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
route_short_name = models.CharField(max_length=255)
route_long_name = models.CharField(max_length=255)
route_id = models.CharField(max_length=255, primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
route_short_name = models.CharField(max_length=50)
route_long_name = models.CharField(max_length=255, blank=True, null=True)
route_desc = models.TextField(blank=True, null=True)
route_type = models.IntegerField()
route_type = models.IntegerField(default=0)
route_url = models.URLField(blank=True, null=True)
route_color = models.CharField(max_length=6, blank=True, null=True)
route_text_color = models.CharField(max_length=6, blank=True, null=True)
route_sort_order = models.IntegerField(blank=True, null=True)
continuous_pickup = models.IntegerField(blank=True, null=True)
continuous_drop_off = models.IntegerField(blank=True, null=True)
class Trip(models.Model):
trip_id = models.BigAutoField(primary_key=True)
trip_id = models.CharField(max_length=255, primary_key=True)
route = models.ForeignKey(Route, on_delete=models.CASCADE)
service_id = models.CharField(max_length=255)
trip_headsign = models.CharField(max_length=255, blank=True, null=True)
@ -51,8 +54,8 @@ class Trip(models.Model):
class StopTime(models.Model):
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
arrival_time = models.TimeField()
departure_time = models.TimeField()
arrival_time = models.TimeField(blank=True, null=True)
departure_time = models.TimeField(blank=True, null=True)
stop = models.ForeignKey(Stop, on_delete=models.CASCADE)
stop_sequence = models.IntegerField()
stop_headsign = models.CharField(max_length=255, blank=True, null=True)
@ -65,7 +68,7 @@ class StopTime(models.Model):
unique_together = (('trip', 'stop_sequence'),)
class Calendar(models.Model):
service_id = models.BigAutoField(primary_key=True)
service_id = models.CharField(max_length=255, primary_key=True)
monday = models.BooleanField()
tuesday = models.BooleanField()
wednesday = models.BooleanField()
@ -77,18 +80,17 @@ class Calendar(models.Model):
end_date = models.DateField()
class CalendarDate(models.Model):
service_id = models.ForeignKey(Calendar, on_delete=models.CASCADE)
service_id = models.CharField(max_length=255)
date = models.DateField()
exception_type = models.IntegerField()
class Meta:
unique_together = (('service_id', 'date'),)
class FareAttribute(models.Model):
fare_id = models.BigAutoField(primary_key=True)
fare_id = models.CharField(max_length=255, primary_key=True)
price = models.FloatField()
currency_type = models.CharField(max_length=255)
currency_type = models.CharField(max_length=3)
payment_method = models.IntegerField()
transfers = models.IntegerField()
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
@ -127,11 +129,120 @@ class Transfer(models.Model):
class Meta:
unique_together = (('from_stop', 'to_stop'),)
class Pathway(models.Model):
pathway_id = models.CharField(max_length=255, primary_key=True)
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')
pathway_mode = models.IntegerField()
is_bidirectional = models.IntegerField()
length = models.FloatField(blank=True, null=True)
traversal_time = models.IntegerField(blank=True, null=True)
stair_count = models.IntegerField(blank=True, null=True)
max_slope = models.FloatField(blank=True, null=True)
min_width = models.FloatField(blank=True, null=True)
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):
level_id = models.CharField(max_length=255, primary_key=True)
level_index = models.FloatField()
level_name = models.CharField(max_length=255, blank=True, null=True)
class FeedInfo(models.Model):
feed_publisher_name = models.CharField(max_length=255)
feed_publisher_url = models.URLField()
feed_lang = models.CharField(max_length=255)
default_lang = models.CharField(max_length=255, blank=True, null=True)
feed_start_date = models.DateField(blank=True, null=True)
feed_end_date = models.DateField(blank=True, null=True)
feed_version = models.CharField(max_length=255, blank=True, null=True)
feed_contact_email = models.EmailField(blank=True, null=True)
feed_contact_url = models.URLField(blank=True, null=True)
feed_id = models.BigAutoField(primary_key=True)
class LocationsGeojson(models.Model):
location_id = models.CharField(max_length=255, primary_key=True)
location_name = models.CharField(max_length=255)
location_lat = models.FloatField()
location_lon = models.FloatField()
location_type = models.CharField(max_length=255)
parent_location_id = models.CharField(max_length=255, blank=True, null=True)
wheelchair_boarding = models.BooleanField(blank=True, null=True)
class BookingRule(models.Model):
booking_rule_id = models.CharField(max_length=255, primary_key=True)
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
start_time = models.TimeField(blank=True, null=True)
end_time = models.TimeField(blank=True, null=True)
booking_type = models.CharField(max_length=255)
rule_criteria = models.TextField(blank=True, null=True)
booking_rule_instructions = models.TextField(blank=True, null=True)
class Translation(models.Model):
table_name = models.CharField(max_length=255)
field_name = models.CharField(max_length=255)
language = models.CharField(max_length=2)
translation = models.TextField()
class Attribution(models.Model):
attribution_id = models.CharField(max_length=255, primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
attribution_name = models.CharField(max_length=255)
attribution_url = models.URLField()
attribution_email = models.EmailField(blank=True, null=True)
attribution_phone = models.CharField(max_length=50, blank=True, null=True)
class LocationGroup(models.Model):
location_group_id = models.CharField(max_length=255, primary_key=True)
location_group_name = models.CharField(max_length=255)
location_group_type = models.CharField(max_length=255)
class LocationGroupStop(models.Model):
location_group = models.ForeignKey(LocationGroup, on_delete=models.CASCADE)
stop = models.ForeignKey(Stop, on_delete=models.CASCADE)
class RouteNetwork(models.Model):
route_network_id = models.CharField(max_length=255, primary_key=True)
route_network_name = models.CharField(max_length=255)
class Network(models.Model):
network_id = models.CharField(max_length=255, primary_key=True)
network_name = models.CharField(max_length=255)
class StopArea(models.Model):
stop_area_id = models.CharField(max_length=255, primary_key=True)
stop_area_name = models.CharField(max_length=255)
stop_area_description = models.TextField(blank=True, null=True)
class Area(models.Model):
area_id = models.CharField(max_length=255, primary_key=True)
area_name = models.CharField(max_length=255)
area_description = models.TextField(blank=True, null=True)
class FareMedium(models.Model):
fare_media_id = models.CharField(max_length=255, primary_key=True)
fare_media_name = models.CharField(max_length=255)
fare_media_description = models.TextField(blank=True, null=True)
class FareProduct(models.Model):
fare_product_id = models.CharField(max_length=255, primary_key=True)
fare_product_name = models.CharField(max_length=255)
fare_product_description = models.TextField(blank=True, null=True)
class FareLegRule(models.Model):
fare_leg_rule_id = models.CharField(max_length=255, primary_key=True)
fare_leg_rule_name = models.CharField(max_length=255)
fare_leg_rule_description = models.TextField(blank=True, null=True)
class FareTransferRule(models.Model):
fare_transfer_rule_id = models.CharField(max_length=255, primary_key=True)
fare_transfer_rule_name = models.CharField(max_length=255)
fare_transfer_rule_description = models.TextField(blank=True, null=True)
class Timeframe(models.Model):
time_frame_id = models.CharField(max_length=255, primary_key=True)
start_date = models.DateField()
end_date = models.DateField()
start_time = models.TimeField()
end_time = models.TimeField()

View File

@ -0,0 +1,137 @@
from django.db import models
class Agency(models.Model):
agency_id = models.BigAutoField(primary_key=True)
agency_name = models.CharField(max_length=250)
agency_url = models.URLField()
agency_phone = models.CharField(max_length=15)
agency_email = models.EmailField()
agency_fare_url = models.URLField()
class Stop(models.Model):
stop_id = models.BigAutoField(primary_key=True)
stop_code = models.CharField(max_length=50)
stop_name = models.CharField(max_length=250)
tts_stop_name = models.CharField(max_length=250)
stop_desc = models.CharField(max_length=500)
stop_lat = models.IntegerField()
stop_lon = models.IntegerField()
zone_id = models.IntegerField(unique=True)
stop_url = models.URLField(blank=True, null=True)
location_type = models.IntegerField(blank=True, null=True)
parent_station = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True)
stop_timezone = models.CharField(max_length=255, blank=True, null=True)
wheelchair_boarding = models.IntegerField(blank=True, null=True)
level_id = models.CharField(max_length=255, blank=True, null=True)
platform_code = models.CharField(max_length=255, blank=True, null=True)
class Route(models.Model):
route_id = models.BigAutoField(primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE)
route_short_name = models.CharField(max_length=255)
route_long_name = models.CharField(max_length=255)
route_desc = models.TextField(blank=True, null=True)
route_type = models.IntegerField()
route_url = models.URLField(blank=True, null=True)
route_color = models.CharField(max_length=6, blank=True, null=True)
route_text_color = models.CharField(max_length=6, blank=True, null=True)
class Trip(models.Model):
trip_id = models.BigAutoField(primary_key=True)
route = models.ForeignKey(Route, on_delete=models.CASCADE)
service_id = models.CharField(max_length=255)
trip_headsign = models.CharField(max_length=255, blank=True, null=True)
trip_short_name = models.CharField(max_length=255, blank=True, null=True)
direction_id = models.IntegerField(blank=True, null=True)
block_id = models.CharField(max_length=255, blank=True, null=True)
shape_id = models.CharField(max_length=255, blank=True, null=True)
wheelchair_accessible = models.IntegerField(blank=True, null=True)
bikes_allowed = models.IntegerField(blank=True, null=True)
class StopTime(models.Model):
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
arrival_time = models.TimeField()
departure_time = models.TimeField()
stop = models.ForeignKey(Stop, on_delete=models.CASCADE)
stop_sequence = models.IntegerField()
stop_headsign = models.CharField(max_length=255, blank=True, null=True)
pickup_type = models.IntegerField(blank=True, null=True)
drop_off_type = models.IntegerField(blank=True, null=True)
shape_dist_traveled = models.FloatField(blank=True, null=True)
timepoint = models.IntegerField(blank=True, null=True)
class Meta:
unique_together = (('trip', 'stop_sequence'),)
class Calendar(models.Model):
service_id = models.BigAutoField(primary_key=True)
monday = models.BooleanField()
tuesday = models.BooleanField()
wednesday = models.BooleanField()
thursday = models.BooleanField()
friday = models.BooleanField()
saturday = models.BooleanField()
sunday = models.BooleanField()
start_date = models.DateField()
end_date = models.DateField()
class CalendarDate(models.Model):
service_id = models.ForeignKey(Calendar, on_delete=models.CASCADE)
date = models.DateField()
exception_type = models.IntegerField()
class Meta:
unique_together = (('service_id', 'date'),)
class FareAttribute(models.Model):
fare_id = models.BigAutoField(primary_key=True)
price = models.FloatField()
currency_type = models.CharField(max_length=255)
payment_method = models.IntegerField()
transfers = models.IntegerField()
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
transfer_duration = models.IntegerField(blank=True, null=True)
class FareRule(models.Model):
fare = models.ForeignKey(FareAttribute, on_delete=models.CASCADE)
route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True)
origin_id = models.CharField(max_length=255, blank=True, null=True)
destination_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):
shape_id = models.CharField(max_length=255)
shape_pt_lat = models.FloatField()
shape_pt_lon = models.FloatField()
shape_pt_sequence = models.IntegerField()
shape_dist_traveled = models.FloatField(blank=True, null=True)
class Meta:
unique_together = (('shape_id', 'shape_pt_sequence'),)
class Frequency(models.Model):
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
start_time = models.TimeField()
end_time = models.TimeField()
headway_secs = models.IntegerField()
exact_times = models.IntegerField(blank=True, null=True)
class Transfer(models.Model):
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')
transfer_type = models.IntegerField()
min_transfer_time = models.IntegerField(blank=True, null=True)
class Meta:
unique_together = (('from_stop', 'to_stop'),)
class FeedInfo(models.Model):
feed_publisher_name = models.CharField(max_length=255)
feed_publisher_url = models.URLField()
feed_lang = models.CharField(max_length=255)
feed_start_date = models.DateField(blank=True, null=True)
feed_end_date = models.DateField(blank=True, null=True)
feed_version = models.CharField(max_length=255, blank=True, null=True)
feed_id = models.BigAutoField(primary_key=True)

View File

@ -0,0 +1,162 @@
from django.db import models
class Agency(models.Model):
agency_id = models.CharField(max_length=255, primary_key=True)
agency_name = models.CharField(max_length=255)
agency_url = models.URLField()
agency_timezone = models.CharField(max_length=255)
agency_lang = models.CharField(max_length=2, blank=True, null=True)
agency_phone = models.CharField(max_length=50, blank=True, null=True)
agency_fare_url = models.URLField(blank=True, null=True)
agency_email = models.EmailField(blank=True, null=True)
class Stop(models.Model):
stop_id = models.CharField(max_length=255, primary_key=True)
stop_code = models.CharField(max_length=50, blank=True, null=True)
stop_name = models.CharField(max_length=255)
stop_desc = models.TextField(blank=True, null=True)
stop_lat = models.FloatField()
stop_lon = models.FloatField()
zone_id = models.CharField(max_length=255, blank=True, null=True)
stop_url = models.URLField(blank=True, null=True)
location_type = models.IntegerField(blank=True, null=True)
parent_station = models.ForeignKey('self', on_delete=models.SET_NULL, blank=True, null=True)
stop_timezone = models.CharField(max_length=255, blank=True, null=True)
wheelchair_boarding = models.IntegerField(blank=True, null=True)
level_id = models.CharField(max_length=255, blank=True, null=True)
platform_code = models.CharField(max_length=50, blank=True, null=True)
class Route(models.Model):
route_id = models.CharField(max_length=255, primary_key=True)
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
route_short_name = models.CharField(max_length=50)
route_long_name = models.CharField(max_length=255, blank=True, null=True)
route_desc = models.TextField(blank=True, null=True)
route_type = models.IntegerField(default=0)
route_url = models.URLField(blank=True, null=True)
route_color = models.CharField(max_length=6, blank=True, null=True)
route_text_color = models.CharField(max_length=6, blank=True, null=True)
route_sort_order = models.IntegerField(blank=True, null=True)
continuous_pickup = models.IntegerField(blank=True, null=True)
continuous_drop_off = models.IntegerField(blank=True, null=True)
class Trip(models.Model):
trip_id = models.CharField(max_length=255, primary_key=True)
route = models.ForeignKey(Route, on_delete=models.CASCADE)
service_id = models.CharField(max_length=255)
trip_headsign = models.CharField(max_length=255, blank=True, null=True)
trip_short_name = models.CharField(max_length=255, blank=True, null=True)
direction_id = models.IntegerField(blank=True, null=True)
block_id = models.CharField(max_length=255, blank=True, null=True)
shape_id = models.CharField(max_length=255, blank=True, null=True)
wheelchair_accessible = models.IntegerField(blank=True, null=True)
bikes_allowed = models.IntegerField(blank=True, null=True)
class StopTime(models.Model):
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
arrival_time = models.TimeField(blank=True, null=True)
departure_time = models.TimeField(blank=True, null=True)
stop = models.ForeignKey(Stop, on_delete=models.CASCADE)
stop_sequence = models.IntegerField()
stop_headsign = models.CharField(max_length=255, blank=True, null=True)
pickup_type = models.IntegerField(blank=True, null=True)
drop_off_type = models.IntegerField(blank=True, null=True)
shape_dist_traveled = models.FloatField(blank=True, null=True)
timepoint = models.IntegerField(blank=True, null=True)
class Meta:
unique_together = (('trip', 'stop_sequence'),)
class Calendar(models.Model):
service_id = models.CharField(max_length=255, primary_key=True)
monday = models.BooleanField()
tuesday = models.BooleanField()
wednesday = models.BooleanField()
thursday = models.BooleanField()
friday = models.BooleanField()
saturday = models.BooleanField()
sunday = models.BooleanField()
start_date = models.DateField()
end_date = models.DateField()
class CalendarDate(models.Model):
service_id = models.CharField(max_length=255)
date = models.DateField()
exception_type = models.IntegerField()
class Meta:
unique_together = (('service_id', 'date'),)
class FareAttribute(models.Model):
fare_id = models.CharField(max_length=255, primary_key=True)
price = models.FloatField()
currency_type = models.CharField(max_length=3)
payment_method = models.IntegerField()
transfers = models.IntegerField()
agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True)
transfer_duration = models.IntegerField(blank=True, null=True)
class FareRule(models.Model):
fare = models.ForeignKey(FareAttribute, on_delete=models.CASCADE)
route = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True)
origin_id = models.CharField(max_length=255, blank=True, null=True)
destination_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):
shape_id = models.CharField(max_length=255)
shape_pt_lat = models.FloatField()
shape_pt_lon = models.FloatField()
shape_pt_sequence = models.IntegerField()
shape_dist_traveled = models.FloatField(blank=True, null=True)
class Meta:
unique_together = (('shape_id', 'shape_pt_sequence'),)
class Frequency(models.Model):
trip = models.ForeignKey(Trip, on_delete=models.CASCADE)
start_time = models.TimeField()
end_time = models.TimeField()
headway_secs = models.IntegerField()
exact_times = models.IntegerField(blank=True, null=True)
class Transfer(models.Model):
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')
transfer_type = models.IntegerField()
min_transfer_time = models.IntegerField(blank=True, null=True)
class Meta:
unique_together = (('from_stop', 'to_stop'),)
class Pathway(models.Model):
pathway_id = models.CharField(max_length=255, primary_key=True)
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')
pathway_mode = models.IntegerField()
is_bidirectional = models.IntegerField()
length = models.FloatField(blank=True, null=True)
traversal_time = models.IntegerField(blank=True, null=True)
stair_count = models.IntegerField(blank=True, null=True)
max_slope = models.FloatField(blank=True, null=True)
min_width = models.FloatField(blank=True, null=True)
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):
level_id = models.CharField(max_length=255, primary_key=True)
level_index = models.FloatField()
level_name = models.CharField(max_length=255, blank=True, null=True)
class FeedInfo(models.Model):
feed_publisher_name = models.CharField(max_length=255)
feed_publisher_url = models.URLField()
feed_lang = models.CharField(max_length=255)
default_lang = models.CharField(max_length=255, blank=True, null=True)
feed_start_date = models.DateField(blank=True, null=True)
feed_end_date = models.DateField(blank=True, null=True)
feed_version = models.CharField(max_length=255, blank=True, null=True)
feed_contact_email = models.EmailField(blank=True, null=True)
feed_contact_url = models.URLField(blank=True, null=True)
feed_id = models.BigAutoField(primary_key=True)