| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| #!/usr/bin/env python2 | ||
|
|
||
| from colormath.color_conversions import convert_color | ||
| from colormath.color_objects import LabColor, LCHabColor, sRGBColor | ||
| from colormath.color_diff import delta_e_cie2000 | ||
| import numpy | ||
|
|
||
| from collections import OrderedDict, namedtuple | ||
|
|
||
| class Color: | ||
| def __init__(self, lch_tuple): | ||
| self.m_lch = LCHabColor(*lch_tuple) | ||
|
|
||
| def lch(self): | ||
| return "Lch({:.0f},{:.0f},{:.0f})".format(*(self.m_lch.get_value_tuple())) | ||
|
|
||
| def rgb(self): | ||
| rgb = convert_color(self.m_lch, sRGBColor) | ||
| if (rgb.rgb_r != rgb.clamped_rgb_r or rgb.rgb_g != rgb.clamped_rgb_g or rgb.rgb_b != rgb.clamped_rgb_b): | ||
| raise Exception("Colour {} is outside sRGB".format(self.lch())) | ||
| return rgb.get_rgb_hex() | ||
|
|
||
| def rgb_error(self): | ||
| return delta_e_cie2000(convert_color(self.m_lch, LabColor), | ||
| convert_color(sRGBColor.new_from_rgb_hex(self.rgb()), LabColor)) | ||
|
|
||
| road_classes = ["motorway", "trunk", "primary", "secondary"] | ||
| colour_divisions = len(road_classes) - 1 | ||
| hues = OrderedDict() | ||
|
|
||
| # The minimum and maximum hue for the road colours | ||
| # Because hue is a circle, it may be needed to add/subtract 360 to the min or | ||
| # max when changing them | ||
|
|
||
| min_h = 10 | ||
| max_h = 106 | ||
| delta_h = (max_h - min_h) / colour_divisions | ||
|
|
||
| h = min_h | ||
| for name in road_classes: | ||
| hues[name] = h | ||
| h = (h + delta_h) % 360 | ||
|
|
||
| print hues | ||
| # A class to hold information for each line | ||
| ColourInfo = namedtuple("ColourInfo", ["start_l", "end_l", "start_c", "end_c"]) | ||
|
|
||
| line_colour_infos = OrderedDict() | ||
|
|
||
| # The saturation and lightness for each type of line | ||
| line_colour_infos["fill"] = ColourInfo(start_l = 70, end_l = 97, start_c = 35, end_c = 29) | ||
| line_colour_infos["low-zoom"] = ColourInfo(start_l = 62, end_l = 92, start_c = 50, end_c = 50) | ||
|
|
||
| line_colour_infos["casing"] = ColourInfo(start_l = 50, end_l = 50, start_c = 70, end_c = 55) | ||
| line_colour_infos["low-zoom-casing"] = ColourInfo(start_l = 50, end_l = 70, start_c = 50, end_c = 65) | ||
|
|
||
| # Colours for the MSS | ||
| colours = OrderedDict() | ||
|
|
||
| for line_name, line_colour_info in line_colour_infos.iteritems(): | ||
| c = line_colour_info.start_c | ||
| delta_c = (line_colour_info.end_c - line_colour_info.start_c) / colour_divisions | ||
| l = line_colour_info.start_l | ||
| delta_l = (line_colour_info.end_l - line_colour_info.start_l) / colour_divisions | ||
|
|
||
| for name in road_classes: | ||
| colours[name + "-" + line_name] = Color((l, c, hues[name])) | ||
| c += delta_c | ||
| l += delta_l | ||
|
|
||
| for name, colour in colours.iteritems(): | ||
| print "@{name}: {rgb}; // {lch}, error {delta:.1f}".format(name = name, rgb = colour.rgb(), lch = colour.lch(), delta = colour.rgb_error()) | ||
|
|
||
| # Generate colours for the shields | ||
| shield_colour_info = {} | ||
| shield_colour_info["fill"] = ColourInfo(start_l = 85, end_l = 95, start_c = 12, end_c = 14) | ||
| shield_colour_info["stroke_fill"] = ColourInfo(start_l = 70, end_l = 80, start_c = 22, end_c = 24) | ||
| shield_colour_info["text"] = ColourInfo(start_l = 20, end_l = 25, start_c = 40, end_c = 42) | ||
| shield_colours = OrderedDict() | ||
|
|
||
|
|
||
| c1 = shield_colour_info["fill"].start_c | ||
| delta_c1 = (shield_colour_info["fill"].end_c - shield_colour_info["fill"].start_c) / colour_divisions | ||
| l1 = shield_colour_info["fill"].start_l | ||
| delta_l1 = (shield_colour_info["fill"].end_l - shield_colour_info["fill"].start_l) / colour_divisions | ||
|
|
||
| c2 = shield_colour_info["stroke_fill"].start_c | ||
| delta_c2 = (shield_colour_info["stroke_fill"].end_c - shield_colour_info["stroke_fill"].start_c) / colour_divisions | ||
| l2 = shield_colour_info["stroke_fill"].start_l | ||
| delta_l2 = (shield_colour_info["stroke_fill"].end_l - shield_colour_info["stroke_fill"].start_l) / colour_divisions | ||
|
|
||
| c3 = shield_colour_info["text"].start_c | ||
| delta_c3 = (shield_colour_info["text"].end_c - shield_colour_info["text"].start_c) / colour_divisions | ||
| l3 = shield_colour_info["text"].start_l | ||
| delta_l3 = (shield_colour_info["text"].end_l - shield_colour_info["text"].start_l) / colour_divisions | ||
|
|
||
| for name in road_classes: | ||
| shield_colours[name] = {} | ||
| shield_colours[name]["fill"] = Color((l1, c1, hues[name])) | ||
| shield_colours[name]["stroke_fill"] = Color((l2, c2, hues[name])) | ||
| shield_colours[name]["text"] = Color((l3, c3, hues[name])) | ||
| c1 += delta_c1 | ||
| l1 += delta_l1 | ||
| c2 += delta_c2 | ||
| l2 += delta_l2 | ||
| c3 += delta_c3 | ||
| l3 += delta_l3 | ||
|
|
||
| shield_colours["tertiary"] = {} | ||
| shield_colours["tertiary"]["fill"] = Color((shield_colour_info["fill"].end_l, 0, 0)) | ||
| shield_colours["tertiary"]["stroke_fill"] = Color((shield_colour_info["stroke_fill"].end_l, 0, 0)) | ||
| shield_colours["tertiary"]["text"] = Color((shield_colour_info["text"].end_l, 0, 0)) | ||
|
|
||
| print "\n\nRoad shield information\n\n" | ||
| for name, colour in shield_colours.iteritems(): | ||
| print "@shield-{name}-fill: {rgb}; // {lch}, error {delta:.1f}".format(name = name, rgb = colour["text"].rgb(), lch = colour["text"].lch(), delta = colour["text"].rgb_error()) | ||
|
|
||
| print "\n" | ||
| for name, colour in shield_colours.iteritems(): | ||
| # note that the two additional blanks at the beginning are intentional | ||
| print " config['{name}']['fill'] = '{rgb}' # {lch}, error {delta:.1f}".format(name = name, rgb = colour["fill"].rgb(), lch = colour["fill"].lch(), delta = colour["fill"].rgb_error()) | ||
| print " config['{name}']['stroke_fill'] = '{rgb}' # {lch}, error {delta:.1f}".format(name = name, rgb = colour["stroke_fill"].rgb(), lch = colour["stroke_fill"].lch(), delta = colour["stroke_fill"].rgb_error()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| # generate highway shields | ||
|
|
||
| from __future__ import print_function | ||
| import copy, lxml.etree, math, os | ||
|
|
||
| def main(): | ||
|
|
||
| namespace = 'http://www.w3.org/2000/svg' | ||
| svgns = '{' + namespace + '}' | ||
| svgnsmap = {None: namespace} | ||
|
|
||
| config = {} | ||
| config['base'] = {} | ||
|
|
||
| config['base']['rounded_corners'] = 2 | ||
| config['base']['font_height'] = 9.1 | ||
| config['base']['font_width'] = 5.9 | ||
| config['base']['padding_x'] = 4 | ||
| config['base']['padding_y'] = 2 | ||
| config['base']['fill'] = '#ddd' | ||
| config['base']['stroke_width'] = 1 | ||
| config['base']['stroke_fill'] = '#000' | ||
|
|
||
| config['global'] = {} | ||
|
|
||
| config['global']['types'] = ['motorway', 'trunk', 'primary', 'secondary', 'tertiary'] | ||
| config['global']['max_width'] = 11 | ||
| config['global']['max_height'] = 4 | ||
| config['global']['output_dir'] = '../symbols/shields/' # specified relative to the script location | ||
|
|
||
| config['global']['additional_sizes'] = ['base', 'z16', 'z18'] | ||
|
|
||
| # specific values overwrite config['base'] ones | ||
| config['motorway'] = {} | ||
| config['trunk'] = {} | ||
| config['primary'] = {} | ||
| config['secondary'] = {} | ||
| config['tertiary'] = {} | ||
|
|
||
| # colour values are generated by generate_road_colours.py | ||
|
|
||
| config['motorway']['fill'] = '#eccdd1' # Lch(85,12,10), error 0.3 | ||
| config['motorway']['stroke_fill'] = '#d39da5' # Lch(70,22,10), error 0.2 | ||
| config['trunk']['fill'] = '#f2d7ce' # Lch(88,12,42), error 1.0 | ||
| config['trunk']['stroke_fill'] = '#d7a899' # Lch(73,22,42), error 0.9 | ||
| config['primary']['fill'] = '#f3e3cf' # Lch(91,12,74), error 1.4 | ||
| config['primary']['stroke_fill'] = '#d1b795' # Lch(76,22,74), error 1.8 | ||
| config['secondary']['fill'] = '#eeefd7' # Lch(94,12,106), error 1.3 | ||
| config['secondary']['stroke_fill'] = '#c4c69c' # Lch(79,22,106), error 1.5 | ||
| config['tertiary']['fill'] = '#f1f1f1' # Lch(95,0,0), error 0.1 | ||
| config['tertiary']['stroke_fill'] = '#c6c6c6' # Lch(80,0,0), error 0.1 | ||
|
|
||
| # changes for different size versions | ||
| config['z16'] = {} | ||
| config['z18'] = {} | ||
|
|
||
| config['z16']['font_width'] = 6.5 | ||
| config['z16']['font_height'] = 10.1 | ||
| config['z18']['font_width'] = 7.2 | ||
| config['z18']['font_height'] = 11.1 | ||
|
|
||
| if not os.path.exists(os.path.dirname(config['global']['output_dir'])): | ||
| os.makedirs(os.path.dirname(config['global']['output_dir'])) | ||
|
|
||
| for height in range(1, config['global']['max_height'] + 1): | ||
| for width in range(1, config['global']['max_width'] + 1): | ||
| for shield_type in config['global']['types']: | ||
|
|
||
| # merge base config and specific styles | ||
| vars = copy.deepcopy(config['base']) | ||
| if shield_type in config: | ||
| for option in config[shield_type]: | ||
| vars[option] = config[shield_type][option] | ||
|
|
||
| for shield_size in config['global']['additional_sizes']: | ||
|
|
||
| if shield_size != 'base': | ||
| if shield_size in config: | ||
| for option in config[shield_size]: | ||
| vars[option] = config[shield_size][option] | ||
|
|
||
| shield_width = 2 * vars['padding_x'] + math.ceil(vars['font_width'] * width) | ||
| shield_height = 2 * vars['padding_y'] + math.ceil(vars['font_height'] * height) | ||
|
|
||
| svg = lxml.etree.Element('svg', nsmap=svgnsmap) | ||
| svg.set('width', '100%') | ||
| svg.set('height', '100%') | ||
| svg.set('viewBox', '0 0 ' + str(shield_width + vars['stroke_width']) + ' ' + str(shield_height + vars['stroke_width'])) | ||
|
|
||
| if vars['stroke_width'] > 0: | ||
| offset_x = vars['stroke_width'] / 2.0 | ||
| offset_y = vars['stroke_width'] / 2.0 | ||
| else: | ||
| offset_x = 0 | ||
| offset_y = 0 | ||
|
|
||
| shield = lxml.etree.Element(svgns + 'rect') | ||
| shield.set('x', str(offset_x)) | ||
| shield.set('y', str(offset_y)) | ||
| shield.set('width', str(shield_width)) | ||
| shield.set('height', str(shield_height)) | ||
| if vars['rounded_corners'] > 0: | ||
| shield.set('rx', str(vars['rounded_corners'])) | ||
| shield.set('ry', str(vars['rounded_corners'])) | ||
| shield.set('id', 'shield') | ||
|
|
||
| stroke = '' | ||
| if vars['stroke_width'] > 0: | ||
| stroke = 'stroke:' + vars['stroke_fill'] + ';stroke-width:' + str(vars['stroke_width']) + ';' | ||
|
|
||
| shield.set('style', 'fill:' + vars['fill'] + ';' + stroke) | ||
|
|
||
| svg.append(shield) | ||
|
|
||
| filename = shield_type + '_' + str(width) + 'x' + str(height) | ||
| if shield_size != 'base': | ||
| filename = filename + '_' + shield_size | ||
|
|
||
| filename = filename + '.svg' | ||
|
|
||
| # save file | ||
| try: | ||
| shieldfile = open(os.path.join(os.path.dirname(__file__), config['global']['output_dir'] + filename), 'w') | ||
| shieldfile.write(lxml.etree.tostring(svg, encoding='utf-8', xml_declaration=True, pretty_print=True)) | ||
| shieldfile.close() | ||
| except IOError: | ||
| print('Could not save file ' + filename + '.') | ||
| continue | ||
|
|
||
| if __name__ == "__main__": main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,30 @@ | ||
| #!/usr/bin/env python | ||
|
|
||
| from __future__ import print_function | ||
| import argparse, json, os, sys, yaml | ||
|
|
||
| parser = argparse.ArgumentParser(description='Keeps project files in sync by converting project.yaml to project.mml.') | ||
| parser.add_argument('--check', dest='check', help='write generated JSON to stdout instead to project.mml', required=False, action='store_true', default=False) | ||
| args = parser.parse_args() | ||
|
|
||
| yaml_path = os.path.join(os.path.dirname(__file__), '../project.yaml') | ||
| mml_path = os.path.join(os.path.dirname(__file__), '../project.mml') | ||
|
|
||
| try: | ||
| yaml_file = open(yaml_path) | ||
| yaml = yaml.safe_load(yaml_file) | ||
| yaml_file.close() | ||
|
|
||
| try: | ||
| if (args.check == False): | ||
| mml_file = open(mml_path, 'w') | ||
| json.dump(yaml, mml_file, indent=2, separators=(',', ': ')) | ||
| mml_file.close() | ||
| else: | ||
| json.dump(yaml, sys.stdout, indent=2, separators=(',', ': ')) | ||
| except IOError: | ||
| print('Could not save MML file. Aborting.') | ||
| sys.exit(1) | ||
| except IOError: | ||
| print('Could not read YAML file. Aborting.') | ||
| sys.exit(1) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| Pattern file for quarry is generated in two steps. | ||
|
|
||
| SVG file is generated using JSDotPattern generator (http://www.imagico.de/map/jsdotpattern.php) using these options: | ||
|
|
||
| - symbol definition: quarry1 | ||
| - symbol pattern (foreground color): #E6E6E6 | ||
| - point generation | ||
| - distance: 45 | ||
| - regular triangular dot pattern | ||
| - rendering: render (px aligned) | ||
| - patten size: 512 | ||
|
|
||
| and then converted to PNG file: | ||
|
|
||
| ``` | ||
| convert quarry.svg quarry.png | ||
| ``` | ||
|
|
||
| Original SVG pattern file (included in this folder) was however made also with scale 0.12 using https://commons.wikimedia.org/wiki/File:Schlaegel_und_Eisen_nach_DIN_21800.svg and then manually converted to PNG. |