diff --git a/css_colours.py b/css_colours.py new file mode 100644 index 0000000..0dea3e9 --- /dev/null +++ b/css_colours.py @@ -0,0 +1,165 @@ +import re + +# Source: https://www.w3.org/TR/css3-color/#svg-color +CSS_COLOURS = { + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgreen': '#006400', + 'darkgrey': '#a9a9a9', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'grey': '#808080', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightgrey': '#d3d3d3', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370db', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#db7093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32', +} + + +def normalize_colour(c): + if not c: + return None + c = c.strip().lower() + if c in CSS_COLOURS: + return CSS_COLOURS[c] + if re.match(r'^#?[0-9a-f]{3}([0-9a-f]{3})?$', c): + if len(c) == 4: + return c[0]+c[1]+c[1]+c[2]+c[2]+c[3]+c[3] + return c + raise ValueError('Unknown colour code: {}'.format(c)) diff --git a/mapsme_subways.py b/mapsme_subways.py index 9a29199..d08ef79 100755 --- a/mapsme_subways.py +++ b/mapsme_subways.py @@ -152,12 +152,14 @@ def dump_data(city, f): elif isinstance(data, dict): f.write('\n') for k, v in data.items(): + if v is None: + continue f.write(indent + str(k) + ': ') write_yaml(v, f, indent + ' ') if isinstance(v, (list, set, dict)): f.write('\n') - elif data is not None: - f.write(data) + else: + f.write(str(data)) f.write('\n') INCLUDE_STOP_AREAS = False @@ -282,6 +284,9 @@ def prepare_mapsme_data(transfers, cities): raise Exception('Got {}, expected {}'.format(elid, typ)) return osm_id << 1 + def format_colour(c): + return c[1:] if c else None + stops = {} # el_id -> station data networks = [] for city in cities: @@ -292,8 +297,8 @@ def prepare_mapsme_data(transfers, cities): 'type': route.mode, 'ref': route.ref, 'name': route.name, - 'colour': route.colour, - 'casing': route.casing, + 'colour': format_colour(route.colour), + 'casing': format_colour(route.casing), 'route_id': uid(route.id, 'r'), 'itineraries': [] } diff --git a/subway_structure.py b/subway_structure.py index 822b08e..033a726 100644 --- a/subway_structure.py +++ b/subway_structure.py @@ -3,6 +3,7 @@ import logging import math import urllib.parse import urllib.request +from css_colours import normalize_colour from collections import Counter, defaultdict @@ -139,7 +140,10 @@ class Station: self.modes = Station.get_modes(el) self.name = el['tags'].get('name', '?') self.int_name = el['tags'].get('int_name', el['tags'].get('name:en', None)) - self.colour = el['tags'].get('colour', None) + try: + self.colour = normalize_colour(el['tags'].get('colour', None)) + except ValueError as e: + city.warn(str(e), el) self.center = el_center(el) if self.center is None: raise Exception('Could not find center of {}'.format(el)) @@ -196,7 +200,11 @@ class StopArea: self.name = stop_area['tags'].get('name', self.name) self.int_name = stop_area['tags'].get( 'int_name', stop_area['tags'].get('name:en', self.int_name)) - self.colour = stop_area['tags'].get('colour', self.colour) + try: + self.colour = normalize_colour( + stop_area['tags'].get('colour')) or self.colour + except ValueError as e: + city.warn(str(e), stop_area) # If we have a stop area, add all elements from it warned_about_tracks = False @@ -471,8 +479,16 @@ class Route: self.name = relation['tags'].get('name', None) if 'colour' not in relation['tags'] and 'colour' not in master_tags: city.warn('Missing colour on a route', relation) - self.colour = relation['tags'].get('colour', master_tags.get('colour', None)) - self.casing = relation['tags'].get('colour:casing', master_tags.get('colour:casing', None)) + try: + self.colour = normalize_colour(relation['tags'].get( + 'colour', master_tags.get('colour', None))) + except ValueError as e: + city.warn(str(e), relation) + try: + self.casing = normalize_colour(relation['tags'].get( + 'colour:casing', master_tags.get('colour:casing', None))) + except ValueError as e: + city.warn(str(e), relation) self.network = Route.get_network(relation) self.mode = relation['tags']['route'] # self.tracks would be a list of (lon, lat) for the longest stretch. Can be empty @@ -604,8 +620,14 @@ class RouteMaster: self.has_master = master is not None if master: self.ref = master['tags'].get('ref', master['tags'].get('name', None)) - self.colour = master['tags'].get('colour', None) - self.casing = master['tags'].get('colour:casing', None) + try: + self.colour = normalize_colour(master['tags'].get('colour', None)) + except ValueError as e: + city.warn(str(e), relation) + try: + self.casing = normalize_colour(master['tags'].get('colour:casing', None)) + except ValueError as e: + city.warn(str(e), relation) self.network = Route.get_network(master) self.mode = master['tags'].get('route_master', None) # This tag is required, but okay self.name = master['tags'].get('name', None)