Skip to content
This repository has been archived by the owner on Jun 9, 2022. It is now read-only.

Commit

Permalink
ADD - Code infrastructure
Browse files Browse the repository at this point in the history
Signed-off-by: RaenonX <[email protected]>
  • Loading branch information
RaenonX committed Sep 22, 2020
1 parent 52ab271 commit b9efe8b
Show file tree
Hide file tree
Showing 31 changed files with 1,025 additions and 21 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Madison Metro Sim - CI Tests

on: [ push ]

jobs:
pytest:
name: Run tests

runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ macos-latest, windows-latest ]

continue-on-error: true

steps:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: '3.8'

- name: Install required packages
run: |
pip install -r requirements.txt
- name: Install pytest
run: |
pip install pytest
- name: Run tests
run: |
pytest
38 changes: 38 additions & 0 deletions .github/workflows/cqa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Madison Metro Sim - CQA

on: [ push ]

jobs:
cqa:
name: CQA

runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ macos-latest, windows-latest ]

continue-on-error: true

steps:
- uses: actions/checkout@v2

- uses: actions/setup-python@v2
with:
python-version: '3.8'

- name: Install required packages
run: |
pip install -r requirements.txt
- name: Install CQA checkers
run: |
pip install pylint pydocstyle
- name: pylint checks
run: |
pylint msnmetrosim
- name: pydocstyle checks
run: |
pydocstyle msnmetrosim --count
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,10 @@ venv/

# Generated map file (by folium)
map.html

# pytest cache file
.pytest_cache/

# Compiled files
__pycache__
*.pyc
30 changes: 30 additions & 0 deletions .pydocstyle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[pydocstyle]
ignore =
# Public method missing docstring - `pylint` will check if there's really missing the docstring
D102,
# Magic method missing docstring - no need for it
D105,
# __init__ missing docstring - optional. add details to class docstring
D107,
# Blank line required before docstring - mutually exclusive to D204
D203,
# Multi-line docstring summary should start at the first line - mutually exclusive to D213
D212,
# Section underline is over-indented
D215,
# First line should be in imperative mood
D401,
# First word of the docstring should not be This
D404,
# Section name should end with a newline
D406,
# Missing dashed underline after section
D407,
# Section underline should be in the line following the section’s name
D408,
# Section underline should match the length of its name
D409,
# No blank lines allowed between a section header and its content
D412,
# Missing blank line after last section
D413
3 changes: 3 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[FORMAT]

max-line-length=119
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,29 @@ Madison Metro Simulator.

------

2020 Fall CS 638 Project.
### Introduction

This is a project of UW Madison 2020 Fall CS 638 class.

The intended group of users of this program is its developers and technical users.

- This specification may be changed in the future


### Usage

1. Generate a map.

```python
py main.py
```

2. Open the generated `map.html` file.

------

- Intended for technical users. (confirmed with Tyler)
If you are first time running this program, execute the below first

- Does not necessarily need to use Python if there's any more efficient language / framework.
```bash
pip install -r requirements-dev.txt
```
26 changes: 8 additions & 18 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
import csv
import time

import folium
from msnmetrosim.views import generate_92_wkd_routes_and_stops


def main():
m = folium.Map(location=[43.080171, -89.380797])
print("Generating map object...")
folium_map = generate_92_wkd_routes_and_stops()

# Read csv

with open("data/mmt_gtfs/stops.csv", "r") as stops:
csv_reader = csv.reader(stops, delimiter=",")
next(csv_reader, None) # Dump header

for row in csv_reader:
lat = float(row[4])
lon = float(row[5])

name = f"#{row[0]} - {row[2]}"

folium.Circle((lat, lon), 0.5, popup=name).add_to(m)

m.save("map.html")
print("Exporting HTML...")
folium_map.save("map.html")


if __name__ == '__main__':
_start = time.time()
main()
print(f"Time spent: {(time.time() - _start) * 1000:.3f} ms")
1 change: 1 addition & 0 deletions msnmetrosim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Main code of the Madison Metro Simulator."""
5 changes: 5 additions & 0 deletions msnmetrosim/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Various data controllers. Loaded and parsed data will be stored into these controllers for use."""
from .route import MMTRouteDataController
from .shape import MMTShapeDataController, ShapeIdNotFoundError
from .stop import MMTStopDataController
from .trip import MMTTripDataController
80 changes: 80 additions & 0 deletions msnmetrosim/controllers/route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Controller of the MMT GTFS route data.
The complete MMT GTFS dataset can be downloaded here:
http://transitdata.cityofmadison.com/GTFS/mmt_gtfs.zip
"""
import csv
from typing import List, Dict

from msnmetrosim.models import MMTRoute

__all__ = ("MMTRouteDataController", "RouteIdNotFoundError")


class RouteIdNotFoundError(KeyError):
"""Raised if the given route ID is not found in the loaded data."""

def __init__(self, route_id: int):
super().__init__(f"Data of route ID <{route_id}> not found")


class MMTRouteDataController:
"""MMT route data controller."""

def _init_dict_by_rid(self, route: MMTRoute):
# This assumes that `route_id` in the original data is unique
self._dict_by_rid[route.route_id] = route

def __init__(self, routes: List[MMTRoute]):
self._dict_by_rid: Dict[int, MMTRoute] = {}

# Create a dict with route ID as key and route data entry as value
for route in routes:
self._init_dict_by_rid(route)

def get_route_by_route_id(self, route_id: int) -> MMTRoute:
"""
Get a :class:`MMTRoute` by ``route_id``.
:raise ServiceIdNotFoundError: if `route_id` is not in the loaded data
"""
if route_id not in self._dict_by_rid:
raise RouteIdNotFoundError(route_id)

return self._dict_by_rid[route_id]

@staticmethod
def load_from_file(file_path: str):
"""
Load the route data from route data file.
This file should be a csv with the following schema:
(
route_id,
service_id,
agency_id,
route_short_name,
route_long_name,
route_service_name,
route_desc,
route_type,
route_url,
route_color,
route_text_color,
bikes_allowed
)
This file could be found in the MMT GTFS dataset with the name ``routes.csv``.
"""
routes = []

with open(file_path, "r") as routes_file:
csv_reader = csv.reader(routes_file, delimiter=",")
next(csv_reader, None) # Dump header

for row in csv_reader:
routes.append(MMTRoute.parse_from_row(row))

return MMTRouteDataController(routes)
80 changes: 80 additions & 0 deletions msnmetrosim/controllers/shape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Controller of the MMT GTFS shape data.
The complete MMT GTFS dataset can be downloaded here:
http://transitdata.cityofmadison.com/GTFS/mmt_gtfs.zip
"""
import csv
from typing import List, Dict, Tuple

from msnmetrosim.models import MMTShape

__all__ = ("MMTShapeDataController", "ShapeIdNotFoundError")


class ShapeIdNotFoundError(KeyError):
"""Raised if the given shape ID is not found in the loaded data."""

def __init__(self, shape_id: int):
super().__init__(f"Data of shape ID <{shape_id}> not found")


class MMTShapeDataController:
"""MMT shape data controller."""

def _init_dict_by_id(self, shape: MMTShape):
sid = shape.shape_id

if sid not in self._dict_by_id:
self._dict_by_id[sid] = []

self._dict_by_id[sid].append(shape)

def __init__(self, shapes: List[MMTShape]):
"""
Initializes the shape data controller.
Data in ``shapes`` should be pre-processed as following, or the functions may misbehave.
GROUP BY shape_id, shape_pt_sequence ASC
:param shapes: list of shape data
"""
self._dict_by_id: Dict[int, List[MMTShape]] = {}

# Create a dict with ID as key and shape data entry as value
for shape in shapes:
self._init_dict_by_id(shape)

def get_shape_coords_by_id(self, shape_id: int) -> List[Tuple[float, float]]:
"""
Get a list of :class:`MMTShapes` by ``shape_id``.
:raise ShapeIdNotFoundError: if `shape_id` is not in the loaded data
"""
if shape_id not in self._dict_by_id:
raise ShapeIdNotFoundError(shape_id)

return [shape.coordinate for shape in self._dict_by_id[shape_id]]

@staticmethod
def load_from_file(file_path: str):
"""
Load the shape data from shape data file.
This file should be a csv with the following schema:
(shape_id, shape_code, shape_pt_lat, shape_pt_lon, shape_pt_sequence, shape_dist_traveled)
This file could be found in the MMT GTFS dataset with the name ``shapes.csv``.
"""
shapes = []

with open(file_path, "r") as shapes_file:
csv_reader = csv.reader(shapes_file, delimiter=",")
next(csv_reader, None) # Dump header

for row in csv_reader:
shapes.append(MMTShape.parse_from_row(row))

return MMTShapeDataController(shapes)
Loading

0 comments on commit b9efe8b

Please sign in to comment.