Commits

Andrew Godwin committed 7537d47

The link extractor, some basic jubilee station data, some assorted other data in JSON format, a grapher, a merger, and a format guide.

Comments (0)

Files changed (6)

+
+Layout
+------
+
+Multiple JSON files can be loaded into one solver - stations will be merged,
+both sets of links used, closures unioned, and the last-loaded line
+definitions will be used for any given line code.
+
+Note that links are uniquely identified by [link_from, link_to, line] - if there
+are multiple definitions for a link's length with this same key, the last to be
+loaded will be used.
+
+Nodes are implicitly created, and must have names of the form "X-Y", where
+X is a station code, and Y is a unique identifier within that station (like
+P3 for Platform 3, or E1 for Exit 1)
+
+Structure
+---------
+
+Root <Object>
+
+  stations <Object> - A mapping of station code -> human name
+  
+  lines <Object>
+    [line_code] <Object>
+      name <String> - Line's human name
+      color <String> - HTML colour of line as #xxxxxx
+      display <Array> - Determines how stations are plotted on a line diagram.
+  
+  nodes <Object>
+    [node_code] <Object>
+      name <String> - Node's human name (Platform 3, or Old Road Exit)
+  
+  links <Array>
+    [
+      link_from <String>,
+      link_to <String>,
+      line <String>,
+      time_in_seconds <Integer>,
+      additional_info <Object>
+    ] - Note that line may be null to indicate walking link. additional_info may be null.
+  
+  closures <Array>
+    [
+      link_from <String>,
+      link_to <String>,
+      line <String>,
+      hours <Array>,
+      dates <Array>,
+      dows <Array>, 
+    ]- Note that line may be null to indicate walking link.
+
+Closures
+--------
+
+Closures simply use the three pieces of information that identify an edge -
+the start, end and type - to indicate that it is closed. This can indicate all
+sorts of closures:
+
+ - Line closure (no trains running on that track at all)
+   Close the edges representing the line into and out of the station
+   
+ - Station closure (trains run through without stopping)
+   Close the edges between the closed platform(s) and the surface exits.
+
+ - Lift closure (like Bank DLR)
+   Have one edge representing the lift route, and one the walking route (we
+   can add 'stairs' or 'escalator' additional_info to walking nodes to determine
+   disabled-friendly routes), and then just close off the lift one.
+
+The closure date format works a bit like crontabs - hours is an array of hours
+it's closed in, dates is a list of dates it's closed in, and dows is a list of
+days of the week it's closed in. A given hour period is closed if it matches all
+three constraints (an empty or null entry is treated as containing all possible
+entries).
+
+For example, permanently not letting people into Oxford Circus until 10 in the
+morning, weekdays, would have several of these, one for each platform:
+
+[
+  "OXC-E",
+  "OXC-P1",
+  null,
+  [4,5,6,7,8,9], -- 4am till 10am
+  null,          -- all dates
+  [1,2,3,4,5]    -- Mon - Fri
+]
+
+Note that this only closes off the routes into the station - people are still
+allowed to exit, since the "OXC-P1" -> "OXC-E" link is not closed.

data/london/jubilee.json

+{
+    "stations": {
+        "BST": "Baker Street",
+        "BER": "Bermondsey",
+        "BDS": "Bond Street",
+        "CWR": "Canada Water",
+        "CWF": "Canary Wharf",
+        "CNT": "Canning Town",
+        "CPK": "Canons Park",
+        "DHL": "Dollis Hill",
+        "FRD": "Finchley Road",
+        "GPK": "Green Park",
+        "KIL": "Kilburn",
+        "KBY": "Kingsbury",
+        "LON": "London Bridge",
+        "NEA": "Neasden",
+        "NGW": "North Greenwich",
+        "QBY": "Queensbury",
+        "SWK": "Southwark",
+        "SJW": "St John’s Wood",
+        "STA": "Stanmore",
+        "SFD": "Stratford",
+        "SWC": "Swiss Cottage",
+        "WLO": "Waterloo",
+        "WPK": "Wembley Park",
+        "WHM": "West Ham",
+        "WHD": "West Hampstead",
+        "WMS": "Westminster",
+        "WLG": "Willesden Green"
+    },
+    "lines": {
+        "J": {
+            "name": "Jubilee",
+            "color": "#94989A",
+            "display": [
+                [
+                    ["STA", "CPK", "QBY", "KBY", "WPK", "NEA", "DHL", "WLG", "KIL", "WHD", "FRD", "SWC", "SJW", "BST", "BDS", "GPK", "WMS", "WLO", "SWK", "LON", "BER", "CWR", "CWF", "NGW", "CNT", "WHM", "SFD"]
+                ]
+            ]
+        }
+    }
+}

data/london/mixedup.json

+{
+    "stations": {
+        "BST": "Baker Street",
+        "BER": "Bermondsey",
+        "BDS": "Bond Street",
+        "CWR": "Canada Water",
+        "CWF": "Canary Wharf",
+        "CNT": "Canning Town",
+        "CPK": "Canons Park",
+        "DHL": "Dollis Hill",
+        "FRD": "Finchley Road",
+        "GPK": "Green Park",
+        "KIL": "Kilburn",
+        "KBY": "Kingsbury",
+        "LON": "London Bridge",
+        "NEA": "Neasden",
+        "NGW": "North Greenwich",
+        "QBY": "Queensbury",
+        "SWK": "Southwark",
+        "SJW": "St John’s Wood",
+        "STA": "Stanmore",
+        "SFD": "Stratford",
+        "SWC": "Swiss Cottage",
+        "WLO": "Waterloo",
+        "WPK": "Wembley Park",
+        "WHM": "West Ham",
+        "WHD": "West Hampstead",
+        "WMS": "Westminster",
+        "WLG": "Willesden Green",
+        
+        "BHR": "Blackhorse Road",
+        "BRX": "Brixton",
+        "EUS": "Euston",
+        "FPK": "Finsbury Park",
+        "HBY": "Highbury and Islington",
+        "KXX": "King's Cross St Pancras",
+        "OXC": "Oxford Circus",
+        "PIM": "Pimlico",
+        "SVS": "Seven Sisters",
+        "STK": "Stockwell",
+        "TTH": "Tottenham Hale",
+        "VUX": "Vauxhall",
+        "VIC": "Victoria",
+        "WAL": "Walthamstow Central",
+        "WST": "Warren Street",
+        "CHX": "Charing Cross",
+        
+        "BST": "Baker Street",
+        "CHX": "Charing Cross",
+        "ERB": "Edgware Road (Bakerloo)",
+        "ELE": "Elephant and Castle",
+        "EMB": "Embankment",
+        "HSD": "Harlesden",
+        "HAW": "Harrow and Wealdstone",
+        "KGN": "Kensal Green",
+        "KNT": "Kenton",
+        "KPK": "Kilburn Park",
+        "LAM": "Lambeth North",
+        "MDV": "Maida Vale",
+        "MYB": "Marylebone",
+        "NWM": "North Wembley",
+        "OXC": "Oxford Circus",
+        "PAD": "Paddington",
+        "PIC": "Piccadilly Circus",
+        "QPK": "Queen's Park",
+        "RPK": "Regent's Park",
+        "SKT": "South Kenton",
+        "SPK": "Stonebridge Park",
+        "WAR": "Warwick Avenue",
+        "WLO": "Waterloo",
+        "WEM": "Wembley Central",
+        "WJN": "Willesden Junction",
+        
+        "ALD": "Aldgate",
+        "AME": "Amersham",
+        "BST": "Baker Street",
+        "BAR": "Barbican",
+        "CLF": "Chalfont and Latimer",
+        "CWD": "Chorleywood",
+        "CLW": "Colliers Wood",
+        "CRX": "Croxley",
+        "ETE": "Eastcote",
+        "ESQ": "Euston Square",
+        "FAR": "Farringdon",
+        "FRD": "Finchley Road",
+        "GPS": "Great Portland Street",
+        "HOH": "Harrow on the Hill",
+        "HDN": "Hillingdon",
+        "ICK": "Ickenham",
+        "KXX": "King's Cross St Pancras",
+        "LST": "Liverpool Street",
+        "MPK": "Moor Park",
+        "MGT": "Moorgate",
+        "NHR": "North Harrow",
+        "NWP": "Northwick Park",
+        "NWD": "Northwood",
+        "NWH": "Northwood Hills",
+        "PIN": "Pinner",
+        "RLN": "Rayners Lane",
+        "RKY": "Rickmansworth",
+        "RUI": "Ruislip",
+        "RUM": "Ruislip Manor",
+        "UXB": "Uxbridge",
+        "WAT": "Watford",
+        "WPK": "Wembley Park",
+        "WHR": "West Harrow"
+    },
+    "lines": {
+        "J": {
+            "name": "Jubilee",
+            "color": "#94989A",
+            "display": [
+                [
+                    ["STA", "CPK", "QBY", "KBY", "WPK", "NEA", "DHL", "WLG", "KIL", "WHD", "FRD", "SWC", "SJW", "BST", "BDS", "GPK", "WMS", "WLO", "SWK", "LON", "BER", "CWR", "CWF", "NGW", "CNT", "WHM", "SFD"]
+                ]
+            ]
+        },
+        "V": {
+            "name": "Victoria",
+            "color": "#2D9EDF",
+            "display": [
+                [
+                    ["WAL", "BHR", "TTH", "SVS", "FPK", "HBY", "KXX", "EUS", "WST", "OXC", "GPK", "VIC", "PIM", "VUX", "STK", "BRX"]
+                ]
+            ]
+        },
+        "B": {
+            "name": "Bakerloo",
+            "color": "#996633",
+            "display": [
+                [
+                    ["HAW", "KNT", "SKT", "NWM", "WEM", "SPK", "HSD", "WJN", "KGN", "QPK", "KPK", "MDV", "WAR", "PAD", "ERB", "MYB", "BST", "RPK", "OXC", "PIC", "CHX", "EMB", "WLO", "LAM", "ELE"]
+                ]
+            ]
+        },
+        "M": {
+            "name": "Metropolitan",
+            "color": "#79004D",
+            "display": [
+                [
+                    ["AME", "CLF"]
+                ],
+                [
+                    ["CWD", "RKY"],
+                    ["WAT", "CRX"]
+                ],
+                [
+                    ["MPK", "NWD", "NWH", "PIN", "NHR"],
+                    ["RUI", "RUM", "ETE", "RLN", "WHR"]
+                ],
+                [
+                    ["HOH"]
+                ]
+            ]
+        }
+    },
+    "links": {
+    
+    }
+}

solver/grapher.py

+
+import sys
+
+from merger import Merger
+
+class Grapher(object):
+    
+    """
+    Takes a line definition and renders it into graphs.
+    """
+    
+    def __init__(self, line_info):
+        self.line_info = line_info
+    
+    def dot(self):
+        "Outputs a GraphViz dot file."
+        output = "digraph G {\n"
+        
+        # Add station clusters
+        for station in self.line_info['stations']:
+            # Work out the nodes in this cluster
+            nodes = [
+                node for node in self.line_info['nodes']
+                if node.split("-")[0] == station
+            ]
+            output += "subgraph cluster_%s { label = \"%s\"; \"%s\"; }\n" % (
+                station,
+                station,
+                "\"; \"".join(nodes),
+            )
+        
+        # Add links
+        for s, e, l, d, o in self.line_info['links']:
+            output += "\"%s\" -> \"%s\";\n" % (s, e)
+        
+        output += "}"
+        
+        return output
+
+if __name__ == "__main__":
+    merger = Merger()
+    merger.add_filename(sys.argv[1])
+    print Grapher(merger.output).dot()
+    
+
+import sys
+try:
+    import json
+except ImportError:
+    import simplejson as json
+
+class Merger(object):
+    """
+    Takes n line representations and merges them into one.
+    """
+    
+    def __init__(self):
+        self.output = {
+            "stations": {},
+            "lines": {},
+            "nodes": {},
+            "links": [],
+            "closures": [],
+        }
+    
+    def add(self, line_info):
+        "Merges a line info with our current state."
+        # Merge stations
+        self.output['stations'].update(line_info.get("stations", {}))
+        # Merge lines
+        self.output['lines'].update(line_info.get("lines", {}))
+        # Merge nodes
+        self.output['nodes'].update(line_info.get("nodes", {}))
+        # Merge links
+        links = dict([
+            ((start, end, line), (distance, other))
+            for (start, end, line, distance, other)
+            in (self.output['links'] + line_info.get("links", []))
+        ])
+        self.output['links'] = [
+            (start, end, line, distance, other)
+            for ((start, end, line), (distance, other))
+            in links.items()
+        ]
+        # Merge closures
+        self.output['closures'] = list(set(self.output['closures'] + line_info.get("closures", [])))
+    
+    def add_filename(self, filename):
+        "Adds a definition from a filename."
+        self.add(json.load(open(filename)))
+    
+    def as_json(self):
+        return json.dumps(self.output, indent=2)
+
+if __name__ == "__main__":
+    merger = Merger()
+    for filename in sys.argv[1:]:
+        merger.add_filename(filename)
+    print merger.as_json()