diff --git a/README.md b/README.md index 01c8b3e..cc02739 100644 --- a/README.md +++ b/README.md @@ -2,5 +2,25 @@ ### 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. +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. https://www.gnu.org/software/make/ + +## Installation instructions +### Dependencies +First, make sure to have all dependencies installed: + +- An up-to-date version of [Python](https://www.python.org/) and pip. +- The project's Python dependencies. We strongly recommend using a virtual environment and install the dependencies using `pip install -r requirements.txt` in the project's root directory. +- An up-to-date version of [Mariadb](https://mariadb.com/downloads/) +- If you want to build the documentation, you will need [Sphinx](https://www.sphinx-doc.org/en/master/) and [Make](https://www.gnu.org/software/make/) + +### Database +- The database can be installed using `sudo mariadb -u root < setup_db.sql` +- The migrations should be applied using `python transport_accessibility/manage.py migrate` +- If you are having database problems and where using an old version of the database before, try resetting the database by issuing `sudo mariadb -u root < delete_db.sql && sudo mariadb -u root < setup_db.sql`, as we changed the character encoding in one of the earlier commits. + +### Load example data +- Download some real or sample GTFS data from the Internet. +- Open a django shell: `python transport_accessibility/manage.py shell` +- Import the database bridging functions: `import pt_map.gtfs, pt_map.bridge` +- Load sample data to db: `pt_map.bridge.gtfs_to_db(pt_map.gtfs.GTFS("/path/to/folder"))` (This might take some time) diff --git a/docs/source/transport_accessibility.gaphor b/docs/source/transport_accessibility.gaphor new file mode 100644 index 0000000..4fd4d0e --- /dev/null +++ b/docs/source/transport_accessibility.gaphor @@ -0,0 +1,447 @@ + + + + +diagram { + /* line-style: sloppy 0.3; */ +} + +controlflow { + dash-style: 0; +} + + + + + +Query + + + + + + + + + + + + +1. Use Cases + + + + + + + + + + + + + +2. Domain Analysis + + + + + + + + + + + + +3. Architectural Design + + + + + + + + + + + + + + + + + + +4. Detailed Design + + + + + + + + + + + + + +uc + + + + + +Use Cases + + + + + + + + + +Analyze functional requirements by +identifying user roles – actors – and +associating them to their use cases. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 93.76953125, 79.1953125) + + +(0.0, 0.0) + + +258.0 + + +110.0 + + + + + + + + + + +act + + + + + +Use Case Scenarios + + + + + + + + + +Model abstract use case implementation by creating activity diagrams visualizing activity flows for primary and secondary use case scenarios. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 58.0, 79.0) + + +(0.0, 0.0) + + +364.0 + + +95.0 + + + + + + + + + + +cls + + + + + +Domain Entities + + + + + + + + + + +Perform domain analysis by modeling domain entities and their relationships using simplified class diagram. This diagram will serve as a visual dictionary of concepts. It will also be a starting point for your design-level data model. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 46.015625, 81.69921875) + + +(0.0, 0.0) + + +400.0 + + +100.0 + + + + + + + + + + +pkg + + + + + +Package Dependencies + + + + + + + + + +sd + + + + + +Interactions + + + + + + + + + +Prepare package dependencies diagram to group your implementation classes in appropriate way. This will help you maintain the code well structured and avoid design flaws such as cyclic dependencies or dependencies on unstable parts + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 106.71484375, 51.49999999999997) + + +(0.0, 0.0) + + +271.5703125 + + +144.0 + + + + + + + + + + +Model component interactions for use case scenarios in order to find out new methods in +existing classes or even new classes with specific responsibilities. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 43.0, 73.0) + + +(0.0, 0.0) + + +322.0 + + +93.0 + + + + + + + + + + +cls + + + + + +Detailed Class Design + + + + + + + + + +Create detailed class diagrams to capture your object-oriented data model. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 72.0, 76.5) + + +(0.0, 0.0) + + +215.0 + + +76.0 + + + + + + + + + + +cmp + + + + + +Implementation Architecture + + + + + + + + + +Define the major artifacts that manifest implementation of your components. Indicate how they are deployed on hardware nodes, their inter-dependencies and communication protocols. + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, -7.59375, 75.08984375) + + +(0.0, 0.0) + + +271.0 + + +129.0 + + + + + + + + + + +Interaction + + + + + + + + + + + + +(1.0, 0.0, 0.0, 1.0, 100.0, 100.0) + + +(0.0, 0.0) + + +150.0 + + +100.0 + + + + + + + + + \ No newline at end of file diff --git a/setup_db.sql b/setup_db.sql index 67f4349..f46b191 100644 --- a/setup_db.sql +++ b/setup_db.sql @@ -1,3 +1,3 @@ -CREATE DATABASE IF NOT EXISTS transport_accessibility; +CREATE DATABASE IF NOT EXISTS transport_accessibility DEFAULT CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'; CREATE USER IF NOT EXISTS 'transport_accessibility'@'localhost' IDENTIFIED BY 'L8AClYIsC55SEAWTgYopD'; GRANT ALL PRIVILEGES ON transport_accessibility.* TO 'transport_accessibility'@'localhost'; diff --git a/transport_accessibility/foreignkeys b/transport_accessibility/foreignkeys new file mode 100644 index 0000000..154adc4 --- /dev/null +++ b/transport_accessibility/foreignkeys @@ -0,0 +1,376 @@ +""" +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 + +class Agency(models.Model): + """ + Represents agency.txt from the GTFS Reference. + """ + 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 Level(models.Model): + """ + Represents level.txt from the GTFS Reference. + """ + 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 Stop(models.Model): + """ + Represents stop.txt from the GTFS Reference. + """ + 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.CASCADE, 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.ForeignKey(Level, on_delete=models.SET_NULL, blank=True, null=True) + platform_code = models.CharField(max_length=50, blank=True, null=True) + +class Route(models.Model): + """ + Represents route.txt from the GTFS Reference. + """ + route_id = models.IntegerField(primary_key=True) + agency_id = 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 Shape(models.Model): + """ + Represents shape.txt from the GTFS Reference. + """ + 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 Calendar(models.Model): + """ + Represents calendar.txt from the GTFS Reference. + """ + service_id = models.IntegerField() + 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): + """ + Represents calendar_date.txt from the GTFS Reference. + """ + service_id = models.IntegerField() + date = models.DateField() + exception_type = models.IntegerField() + + class Meta: + unique_together = (('service_id', 'date'),) + +class Trip(models.Model): + """ + Represents trip.txt from the GTFS Reference. + """ + trip_id = models.CharField(max_length=255, primary_key=True) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE) + service_id = models.IntegerField() + 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.ForeignKey(Shape, on_delete=models.CASCADE, blank=True) + wheelchair_accessible = models.IntegerField(blank=True, null=True) + bikes_allowed = models.IntegerField(blank=True, null=True) + +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_name = models.CharField(max_length=255) + location_group_type = models.CharField(max_length=255) + +class LocationsGeojson(models.Model): + """ + Represents locations.geojson from the GTFS Reference. + """ + 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 StopTime(models.Model): + """ + Represents stop_time.txt from the GTFS Reference. + """ + trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) + arrival_time = models.TimeField(blank=True, null=True) + departure_time = models.TimeField(blank=True, null=True) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + location_group_id = models.ForeignKey(LocationGroup, on_delete=models.SET_NULL, blank=True, null=True) + location_id = models.ForeignKey(LocationsGeojson, on_delete=models.SET_NULL, blank=True, null=True) + 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_id', 'stop_sequence'),) + +class FareAttribute(models.Model): + """ + Represents fare_attribute.txt from the GTFS Reference. + """ + 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_id = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) + transfer_duration = models.IntegerField(blank=True, null=True) + +class FareRule(models.Model): + """ + Represents fare_rule.txt from the GTFS Reference. + """ + fare_id = models.ForeignKey(FareAttribute, on_delete=models.CASCADE) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + origin_id = models.IntegerField(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 Frequency(models.Model): + """ + Represents frequency.txt from the GTFS Reference. + """ + trip_id = 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): + """ + Represents transfer.txt from the GTFS Reference. + """ + from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_from_stop') + to_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_to_stop') + from_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_route') + to_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_route') + from_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_trip') + to_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_trip') + 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): + """ + Represents lathway.txt from the GTFS Reference. + """ + pathway_id = models.CharField(max_length=255, primary_key=True) + from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_from') + to_stop_id = 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 FeedInfo(models.Model): + """ + Represents feed_info.txt from the GTFS Reference. + """ + 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 BookingRule(models.Model): + """ + Represents booking_rule.txt from the GTFS Reference. + """ + booking_rule_id = models.CharField(max_length=255, primary_key=True) + trip_id = 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): + """ + Represents translation.txt from the GTFS Reference. + """ + 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): + """ + Represents attribution.txt from the GTFS Reference. + """ + attribution_id = models.BigAutoField(primary_key=True) + agency_id = models.ForeignKey(Agency, on_delete=models.SET_NULL, blank=True, null=True) + route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True) + trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True) + 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 LocationGroupStop(models.Model): + """ + Represents location_groupStop.txt from the GTFS Reference. + """ + location_group_id = models.ForeignKey(LocationGroup, on_delete=models.CASCADE) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + +class Network(models.Model): + """ + Represents network.txt from the GTFS Reference. + """ + network_id = models.CharField(max_length=255, primary_key=True) + network_name = models.CharField(max_length=255) + +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_name = models.CharField(max_length=255) + network_id = models.ForeignKey(Network, on_delete=models.CASCADE) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE) + +class Area(models.Model): + """ + Represents area.txt from the GTFS Reference. + """ + 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 StopArea(models.Model): + """ + Represents stop_area.txt from the GTFS Reference. + """ + 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) + area_id = models.ForeignKey(Area, on_delete=models.CASCADE) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + +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_name = models.CharField(max_length=255) + fare_media_description = models.TextField(blank=True, null=True) + +class FareProduct(models.Model): + """ + Represents fare_product.txt from the GTFS Reference. + """ + fare_product_id = models.BigAutoField(primary_key=True) + fare_product_name = models.CharField(max_length=255) + fare_product_description = models.TextField(blank=True, null=True) + amount = models.FloatField() + curreny = models.CharField(max_length=64) + +class Timeframe(models.Model): + """ + Represents timeframe.txt from the GTFS Reference. + """ + timeframe_group_id = models.IntegerField(primary_key=True) + service_id = models.IntegerField() + start_date = models.DateField() + end_date = models.DateField() + start_time = models.TimeField() + end_time = models.TimeField() + +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_name = models.CharField(max_length=255) + fare_leg_rule_description = models.TextField(blank=True, null=True) + network_id = models.ForeignKey(Network, blank=True, null=True, on_delete=models.SET_NULL) + from_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_area') + to_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_area') + from_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_timeframe') + to_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_timeframe') + fare_product_id = models.ForeignKey(FareProduct, on_delete=models.CASCADE) + +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_name = models.CharField(max_length=255) + fare_transfer_rule_description = models.TextField(blank=True, null=True) + from_leg_group_id = models.IntegerField(blank=True, null=True) + to_leg_group_id = models.IntegerField(blank=True, null=True) + fare_product_id = models.ForeignKey(FareProduct, on_delete=models.SET_NULL, blank=True, null=True) + + + diff --git a/transport_accessibility/get_foreignkeys.awk b/transport_accessibility/get_foreignkeys.awk new file mode 100644 index 0000000..774a53c --- /dev/null +++ b/transport_accessibility/get_foreignkeys.awk @@ -0,0 +1,28 @@ +BEGIN{ + classname = "" + FS = "[ |(,]" + printf "[\n" +} +{ + #print "1: "$1 " 2: "$2 " 3: "$3 " 4: "$4 + if ($1 == "class") { + classname = $2 + printf "\t(pt_map.models." classname ", [" + }else if ($0 == "" && classname != ""){ + printf "]),\n" + classname = "" + }else { + fk = index($0, "models.ForeignKey") + if (fk > 0) { + first = match($0, /\(/)+1 + last = index(substr($0, first), ",")-1 + printf "(pt_map.models." substr($0, first, last) ", " + first = match($0, /[a-z]/) + last = index(substr($0, first), " = ")-1 + printf "\'" substr($0, first, last) "\'), " + } + } +} +END{ + print "]" +} diff --git a/transport_accessibility/pt_map/bridge.py b/transport_accessibility/pt_map/bridge.py index 13cda7e..220b0ce 100644 --- a/transport_accessibility/pt_map/bridge.py +++ b/transport_accessibility/pt_map/bridge.py @@ -443,7 +443,6 @@ def to_snake_case(name): def unqfk(ts, fk): if not isinstance(fk, str): fk = str(int(fk)) - print(f"fk: {fk}") return f"{ts}{fk}".strip() def gtfs_to_db(g: pt_map.gtfs.GTFS): @@ -468,6 +467,9 @@ def gtfs_to_db(g: pt_map.gtfs.GTFS): if row.get(fk[1]): row[fk[1]] = fk[0].objects.get(**{primary_keys[fk[0]]: unqfk(ts, row[fk[1]])}) defaults = {field: stdz(row.get(field), m, field) for field in v if row.get(field) and not is_NaN(row[field])} + print(model[0]) + if model[0] == pt_map.models.StopTime: + print(row) if primary_keys[m]: row[primary_keys[m]] = unqfk(ts, row[primary_keys[m]]) defaults[primary_keys[m]] = row[primary_keys[m]] @@ -481,7 +483,7 @@ def gtfs_to_db(g: pt_map.gtfs.GTFS): **{primary_keys[m]: row[primary_keys[m]]} ) else: - m.objects.update_or_create(defaults=defaults) + m.objects.create(**defaults) reversed_file_mapping = { diff --git a/transport_accessibility/pt_map/gtfs_schema.py b/transport_accessibility/pt_map/gtfs_schema.py new file mode 100644 index 0000000..8c47487 --- /dev/null +++ b/transport_accessibility/pt_map/gtfs_schema.py @@ -0,0 +1,240 @@ + +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" + ] +} diff --git a/transport_accessibility/pt_map/migrations/0002_alter_calendardate_unique_together_and_more.py b/transport_accessibility/pt_map/migrations/0002_alter_calendardate_unique_together_and_more.py deleted file mode 100644 index d9ea27f..0000000 --- a/transport_accessibility/pt_map/migrations/0002_alter_calendardate_unique_together_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.0.6 on 2024-05-31 22:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('pt_map', '0001_initial'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='calendardate', - unique_together={('service_id', 'date')}, - ), - migrations.AlterUniqueTogether( - name='shape', - unique_together={('shape_id', 'shape_pt_sequence')}, - ), - migrations.AlterUniqueTogether( - name='stoptime', - unique_together={('trip', 'stop_sequence')}, - ), - migrations.AlterUniqueTogether( - name='transfer', - unique_together={('from_stop', 'to_stop')}, - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0003_alter_calendar_service_id_and_more.py b/transport_accessibility/pt_map/migrations/0003_alter_calendar_service_id_and_more.py deleted file mode 100644 index 1730a5e..0000000 --- a/transport_accessibility/pt_map/migrations/0003_alter_calendar_service_id_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.0.6 on 2024-05-31 23:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pt_map', '0002_alter_calendardate_unique_together_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='calendar', - name='service_id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='fareattribute', - name='fare_id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='feedinfo', - name='feed_id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='route', - name='route_id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='trip', - name='trip_id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0004_alter_agency_agency_id_alter_calendar_service_id_and_more.py b/transport_accessibility/pt_map/migrations/0004_alter_agency_agency_id_alter_calendar_service_id_and_more.py deleted file mode 100644 index 02cb73e..0000000 --- a/transport_accessibility/pt_map/migrations/0004_alter_agency_agency_id_alter_calendar_service_id_and_more.py +++ /dev/null @@ -1,48 +0,0 @@ -# Generated by Django 5.0.6 on 2024-05-31 23:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('pt_map', '0003_alter_calendar_service_id_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='agency', - name='agency_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='calendar', - name='service_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='fareattribute', - name='fare_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='feedinfo', - name='feed_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='route', - name='route_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='stop', - name='stop_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='trip', - name='trip_id', - field=models.BigAutoField(primary_key=True, serialize=False), - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0005_level_remove_stop_tts_stop_name_agency_agency_lang_and_more.py b/transport_accessibility/pt_map/migrations/0005_level_remove_stop_tts_stop_name_agency_agency_lang_and_more.py deleted file mode 100644 index 78d4375..0000000 --- a/transport_accessibility/pt_map/migrations/0005_level_remove_stop_tts_stop_name_agency_agency_lang_and_more.py +++ /dev/null @@ -1,205 +0,0 @@ -# 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')), - ], - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0006_alter_route_route_type.py b/transport_accessibility/pt_map/migrations/0006_alter_route_route_type.py deleted file mode 100644 index 1d2cf78..0000000 --- a/transport_accessibility/pt_map/migrations/0006_alter_route_route_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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), - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0007_location_translation_attribution_bookingrule.py b/transport_accessibility/pt_map/migrations/0007_location_translation_attribution_bookingrule.py deleted file mode 100644 index 9e64377..0000000 --- a/transport_accessibility/pt_map/migrations/0007_location_translation_attribution_bookingrule.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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')), - ], - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0008_area_farelegrule_faremedia_fareproduct_and_more.py b/transport_accessibility/pt_map/migrations/0008_area_farelegrule_faremedia_fareproduct_and_more.py deleted file mode 100644 index 865185d..0000000 --- a/transport_accessibility/pt_map/migrations/0008_area_farelegrule_faremedia_fareproduct_and_more.py +++ /dev/null @@ -1,102 +0,0 @@ -# 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')), - ], - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0009_rename_faremedia_faremedium.py b/transport_accessibility/pt_map/migrations/0009_rename_faremedia_faremedium.py deleted file mode 100644 index df1b1fd..0000000 --- a/transport_accessibility/pt_map/migrations/0009_rename_faremedia_faremedium.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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', - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0010_rename_location_locationgeojson.py b/transport_accessibility/pt_map/migrations/0010_rename_location_locationgeojson.py deleted file mode 100644 index 049f688..0000000 --- a/transport_accessibility/pt_map/migrations/0010_rename_location_locationgeojson.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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', - ), - ] diff --git a/transport_accessibility/pt_map/migrations/0011_rename_locationgeojson_locationsgeojson.py b/transport_accessibility/pt_map/migrations/0011_rename_locationgeojson_locationsgeojson.py deleted file mode 100644 index ef22311..0000000 --- a/transport_accessibility/pt_map/migrations/0011_rename_locationgeojson_locationsgeojson.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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', - ), - ] diff --git a/transport_accessibility/pt_map/models.py b/transport_accessibility/pt_map/models.py index a8148a0..1cd8482 100644 --- a/transport_accessibility/pt_map/models.py +++ b/transport_accessibility/pt_map/models.py @@ -11,6 +11,7 @@ Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, Fare Different files as described in the GTFS Reference """ from django.db import models +from pt_map.gtfs_schema import gtfs_schema class Agency(models.Model): """ @@ -25,6 +26,14 @@ class Agency(models.Model): agency_fare_url = models.URLField(blank=True, null=True) agency_email = models.EmailField(blank=True, null=True) +class Level(models.Model): + """ + Represents level.txt from the GTFS Reference. + """ + 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 Stop(models.Model): """ Represents stop.txt from the GTFS Reference. @@ -38,18 +47,18 @@ class Stop(models.Model): 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) + parent_station = models.ForeignKey('self', on_delete=models.CASCADE, 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) + level_id = models.ForeignKey(Level, on_delete=models.SET_NULL, blank=True, null=True) platform_code = models.CharField(max_length=50, blank=True, null=True) class Route(models.Model): """ Represents route.txt from the GTFS Reference. """ - route_id = models.CharField(max_length=255, primary_key=True) - agency = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) + route_id = models.CharField(max_length=255,primary_key=True) + agency_id = 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) @@ -61,44 +70,24 @@ class Route(models.Model): continuous_pickup = models.IntegerField(blank=True, null=True) continuous_drop_off = models.IntegerField(blank=True, null=True) -class Trip(models.Model): +class Shape(models.Model): """ - Represents trip.txt from the GTFS Reference. + Represents shape.txt from the GTFS Reference. """ - 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): - """ - Represents stop_time.txt from the GTFS Reference. - """ - 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_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) - timepoint = models.IntegerField(blank=True, null=True) class Meta: - unique_together = (('trip', 'stop_sequence'),) + unique_together = (('shape_id', 'shape_pt_sequence'),) 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() tuesday = models.BooleanField() wednesday = models.BooleanField() @@ -120,6 +109,75 @@ class CalendarDate(models.Model): class Meta: unique_together = (('service_id', 'date'),) +class Trip(models.Model): + """ + Represents trip.txt from the GTFS Reference. + """ + trip_id = models.CharField(max_length=255, primary_key=True) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE) + service_id = models.IntegerField() + 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.ForeignKey(Shape, on_delete=models.CASCADE, blank=True) + wheelchair_accessible = models.IntegerField(blank=True, null=True) + bikes_allowed = models.IntegerField(blank=True, null=True) + + def as_dict(self): + return { + "trip_id": self.trip_id, + "route_id": self.route_id.route_id, + "service_id": self.service_id, + "trip_headsign": self.trip_headsign, + "trip_short_name": self.trip_short_name, + "direction_id": self.direction_id, + "block_id": self.block_id, + "shape_id": self.shape_id.shape_id, + "wheelchair_accessible": self.wheelchair_accessible, + "bikes_allowed": self.bikes_allowed, + } + +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_name = models.CharField(max_length=255) + location_group_type = models.CharField(max_length=255) + +class LocationsGeojson(models.Model): + """ + Represents locations.geojson from the GTFS Reference. + """ + 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 StopTime(models.Model): + """ + Represents stop_time.txt from the GTFS Reference. + """ + trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) + arrival_time = models.TimeField(blank=True, null=True) + departure_time = models.TimeField(blank=True, null=True) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + location_group_id = models.ForeignKey(LocationGroup, on_delete=models.SET_NULL, blank=True, null=True) + location_id = models.ForeignKey(LocationsGeojson, on_delete=models.SET_NULL, blank=True, null=True) + 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_id', 'stop_sequence'),) + class FareAttribute(models.Model): """ Represents fare_attribute.txt from the GTFS Reference. @@ -129,37 +187,24 @@ class FareAttribute(models.Model): 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) + agency_id = models.ForeignKey(Agency, on_delete=models.CASCADE, blank=True, null=True) transfer_duration = models.IntegerField(blank=True, null=True) class FareRule(models.Model): """ Represents fare_rule.txt from the GTFS Reference. """ - 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) + fare_id = models.ForeignKey(FareAttribute, on_delete=models.CASCADE) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE, blank=True, null=True) + origin_id = models.IntegerField(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): - """ - Represents shape.txt from the GTFS Reference. - """ - 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): """ Represents frequency.txt from the GTFS Reference. """ - trip = models.ForeignKey(Trip, on_delete=models.CASCADE) + trip_id = models.ForeignKey(Trip, on_delete=models.CASCADE) start_time = models.TimeField() end_time = models.TimeField() headway_secs = models.IntegerField() @@ -169,21 +214,25 @@ class Transfer(models.Model): """ Represents transfer.txt from the GTFS Reference. """ - 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') + from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_from_stop') + to_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='transfers_to_stop') + from_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_route') + to_route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_route') + from_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_from_trip') + to_trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True, related_name='transfers_to_trip') transfer_type = models.IntegerField() min_transfer_time = models.IntegerField(blank=True, null=True) class Meta: - unique_together = (('from_stop', 'to_stop'),) + unique_together = (('from_stop_id', 'to_stop_id'),) class Pathway(models.Model): """ Represents lathway.txt from the GTFS Reference. """ 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') + from_stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE, related_name='pathways_from') + to_stop_id = 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) @@ -194,14 +243,6 @@ class Pathway(models.Model): 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): - """ - Represents level.txt from the GTFS Reference. - """ - 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): """ Represents feed_info.txt from the GTFS Reference. @@ -217,24 +258,12 @@ class FeedInfo(models.Model): feed_contact_url = models.URLField(blank=True, null=True) feed_id = models.BigAutoField(primary_key=True) -class LocationsGeojson(models.Model): - """ - Represents locations.geojson from the GTFS Reference. - """ - 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): """ Represents booking_rule.txt from the GTFS Reference. """ booking_rule_id = models.CharField(max_length=255, primary_key=True) - trip = models.ForeignKey(Trip, on_delete=models.CASCADE) + trip_id = 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) @@ -254,34 +283,21 @@ class Attribution(models.Model): """ Represents attribution.txt from the GTFS Reference. """ - attribution_id = models.CharField(max_length=255, primary_key=True) - agency = models.ForeignKey(Agency, on_delete=models.CASCADE) + attribution_id = models.BigAutoField(primary_key=True) + agency_id = models.ForeignKey(Agency, on_delete=models.SET_NULL, blank=True, null=True) + route_id = models.ForeignKey(Route, on_delete=models.SET_NULL, blank=True, null=True) + trip_id = models.ForeignKey(Trip, on_delete=models.SET_NULL, blank=True, null=True) 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): - """ - Represents location_group.txt from the GTFS Reference. - """ - 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): """ Represents location_groupStop.txt from the GTFS Reference. """ - location_group = models.ForeignKey(LocationGroup, on_delete=models.CASCADE) - stop = models.ForeignKey(Stop, on_delete=models.CASCADE) - -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_name = models.CharField(max_length=255) + location_group_id = models.ForeignKey(LocationGroup, on_delete=models.CASCADE) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) class Network(models.Model): """ @@ -290,13 +306,14 @@ 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): +class RouteNetwork(models.Model): """ - Represents stop_area.txt from the GTFS Reference. + Represents route_network.txt from the GTFS Reference. """ - 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) + route_network_id = models.CharField(max_length=255, primary_key=True) + route_network_name = models.CharField(max_length=255) + network_id = models.ForeignKey(Network, on_delete=models.CASCADE) + route_id = models.ForeignKey(Route, on_delete=models.CASCADE) class Area(models.Model): """ @@ -306,6 +323,16 @@ class Area(models.Model): area_name = models.CharField(max_length=255) area_description = models.TextField(blank=True, null=True) +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_name = models.CharField(max_length=255) + stop_area_description = models.TextField(blank=True, null=True) + area_id = models.ForeignKey(Area, on_delete=models.CASCADE) + stop_id = models.ForeignKey(Stop, on_delete=models.CASCADE) + class FareMedium(models.Model): """ Represents fare_medium.txt from the GTFS Reference. @@ -318,9 +345,22 @@ 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.BigAutoField(primary_key=True) fare_product_name = models.CharField(max_length=255) fare_product_description = models.TextField(blank=True, null=True) + amount = models.FloatField() + curreny = models.CharField(max_length=64) + +class Timeframe(models.Model): + """ + Represents timeframe.txt from the GTFS Reference. + """ + timeframe_group_id = models.CharField(max_length=255,primary_key=True) + service_id = models.IntegerField() + start_date = models.DateField() + end_date = models.DateField() + start_time = models.TimeField() + end_time = models.TimeField() class FareLegRule(models.Model): """ @@ -329,6 +369,12 @@ 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) + network_id = models.ForeignKey(Network, blank=True, null=True, on_delete=models.SET_NULL) + from_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_area') + to_area_id = models.ForeignKey(Area, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_area') + from_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_from_timeframe') + to_timeframe_group_id = models.ForeignKey(Timeframe, blank=True, null=True, on_delete=models.SET_NULL, related_name='farelegrule_to_timeframe') + fare_product_id = models.ForeignKey(FareProduct, on_delete=models.CASCADE) class FareTransferRule(models.Model): """ @@ -337,14 +383,9 @@ 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) + from_leg_group_id = models.IntegerField(blank=True, null=True) + to_leg_group_id = models.IntegerField(blank=True, null=True) + fare_product_id = models.ForeignKey(FareProduct, on_delete=models.SET_NULL, blank=True, null=True) + -class Timeframe(models.Model): - """ - Represents timeframe.txt from the GTFS Reference. - """ - 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() diff --git a/transport_accessibility/pt_map/query.py b/transport_accessibility/pt_map/query.py new file mode 100644 index 0000000..adf2abd --- /dev/null +++ b/transport_accessibility/pt_map/query.py @@ -0,0 +1,57 @@ +""" +Query +===== + +Interface between backend/database and Views. Aims to abstract database lookups for the frontend as well as possible. + +Contents +-------- +Classes +------- + +Functions +--------- + +Public variables +---------------- +""" +import django.db.models +import pt_map.models +from pt_map.class_names import class_names + +class GTFSQuery: + """ + Base datatype conveniently storing data requiring queries involving multiple tables as if they were a GTFS Feed as described by the GTFS specification. + Main abstraction element between data handling and frontend. + + Attributes + ---------- + + Methods + ------- + """ + def __init__(self, queries: dict[str, django.db.models.query.QuerySet]): + """ + Parameters + ---------- + queries : dict[str, django.db.models.query.QuerySet] + dict containing + keys: str specifying the file of the GTFS reference they represent either as the file name omitting the extension or as Model name in CamelCase or as model name all lower case. + values: QuerySets of the specified models + + Raises + ------ + TypeError + If queries is not present or of bad type + ValueError + If queries contains a Key not specified as a file in the GTFS reference + """ + if not queries or not isinstance(queries, dict): + raise TypeError("Missing dict of QuerySets") + for key,value in queries.items(): + for names in class_names: + if names.get(key): + cls = names[key] + if not cls: + raise ValueError("Bad GTFS file name") + diff --git a/transport_accessibility/pt_map/views.py b/transport_accessibility/pt_map/views.py index baa8e63..10b510f 100644 --- a/transport_accessibility/pt_map/views.py +++ b/transport_accessibility/pt_map/views.py @@ -10,9 +10,26 @@ index(request) """ from django.shortcuts import render from django.http import HttpResponse -from .models import Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, Shape, Frequency, Transfer, FeedInfo +from .models import * + +def print_r(r, s): + if not len(r): + print(s) + return r def index(request): - context = {} - return HttpResponse(request, "templates/map.html", context) + stops = [{name: getattr(s, name) for name in ['stop_id', 'stop_name', 'stop_lat', 'stop_lon']} for s in Stop.objects.all()] + route_name = lambda r : r.route_short_name if r.route_short_name else r.route_long_name + routes = [{"route_id": r.route_id, "route_type": r.route_type, "route_name": route_name(r), "agency_id": r.agency_id.agency_id} for r in Route.objects.all()] + trips = {r["route_id"]: [t for t in Trip.objects.filter(route_id_id=r["route_id"])] for r in routes} + #stop_sequence = {} + #for r in routes: + # seq = [] + # for s in StopTime.objects.filter(trip_id_id__exact=trips[r["route_id"]][0].trip_id) + # stop_sequence[r["route_id"]] = [s for s in StopTime.objects.filter(trip_id_id__exact=trips[r["route_id"]].get(0).trip_id) if len(trips[r["route_id"]])] + #print(stop_sequences) + #print(trips[routes[0]["route_id"]][0]) + #timetables = {r["route_id"]: {"stop_sequence": stop_sequences[r["route_id"]], "stop_times": {stop: sorted([st.departure_time for st in [StopTime.objects.filter(trip_id=t.trip_id.trip_id) for t in trips[r["route_id"]]]]) for stop in stop_sequences[r["route_id"]]}} for r in routes} + context = {"stops": stops, "routes": routes, }#"timetables": timetables} + return render(request,"map.html", context)