├── src ├── __init__.py ├── backend │ ├── __init__.py │ └── postgis │ │ └── __init__.py ├── mapcss │ ├── webcolors │ │ ├── __init__.py │ │ ├── setup.py │ │ ├── LICENSE.txt │ │ ├── README.txt │ │ ├── PKG-INFO │ │ └── webcolors.py │ ├── Rule.py │ ├── Eval.py │ ├── Condition.py │ ├── StyleChooser.py │ └── __init__.py ├── styles │ ├── linky.mapcss │ ├── default │ │ ├── dirt.png │ │ ├── field.png │ │ ├── grass.png │ │ ├── sand.png │ │ ├── asphalt.png │ │ ├── grass2.png │ │ ├── parking.png │ │ └── paving_stones6s.png │ ├── icons │ │ └── PD │ │ │ ├── buildings-hatch.png │ │ │ ├── minsk_metro_blue.png │ │ │ ├── minsk_metro_red.png │ │ │ ├── praha-metro-green.png │ │ │ ├── praha-metro-red.png │ │ │ ├── vienna-ubahn-blue.png │ │ │ ├── vienna-ubahn-red.png │ │ │ ├── construction-hatch.png │ │ │ ├── praha-metro-yellow.png │ │ │ ├── vienna-ubahn-brown.png │ │ │ ├── vienna-ubahn-green.png │ │ │ ├── vienna-ubahn-orange.png │ │ │ ├── vienna-ubahn-purple.png │ │ │ └── ekaterinburg-metro-green.png │ ├── landuses.mapcss │ ├── openstreetinfo.mapcss │ ├── mapink.mapcss │ ├── gisrussa.mapcss │ └── default.mapcss ├── debug.py ├── test_stylesheet.py └── render.py ├── .gitignore └── README /src/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /src/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/data/ 2 | src/tiles/ 3 | *~ 4 | *.pyc 5 | *pycache* 6 | *swp 7 | *bak 8 | -------------------------------------------------------------------------------- /src/styles/linky.mapcss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/linky.mapcss -------------------------------------------------------------------------------- /src/styles/default/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/dirt.png -------------------------------------------------------------------------------- /src/styles/default/field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/field.png -------------------------------------------------------------------------------- /src/styles/default/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/grass.png -------------------------------------------------------------------------------- /src/styles/default/sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/sand.png -------------------------------------------------------------------------------- /src/styles/default/asphalt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/asphalt.png -------------------------------------------------------------------------------- /src/styles/default/grass2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/grass2.png -------------------------------------------------------------------------------- /src/styles/default/parking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/parking.png -------------------------------------------------------------------------------- /src/styles/default/paving_stones6s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/default/paving_stones6s.png -------------------------------------------------------------------------------- /src/styles/icons/PD/buildings-hatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/buildings-hatch.png -------------------------------------------------------------------------------- /src/styles/icons/PD/minsk_metro_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/minsk_metro_blue.png -------------------------------------------------------------------------------- /src/styles/icons/PD/minsk_metro_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/minsk_metro_red.png -------------------------------------------------------------------------------- /src/styles/icons/PD/praha-metro-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/praha-metro-green.png -------------------------------------------------------------------------------- /src/styles/icons/PD/praha-metro-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/praha-metro-red.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-blue.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-red.png -------------------------------------------------------------------------------- /src/styles/icons/PD/construction-hatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/construction-hatch.png -------------------------------------------------------------------------------- /src/styles/icons/PD/praha-metro-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/praha-metro-yellow.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-brown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-brown.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-green.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-orange.png -------------------------------------------------------------------------------- /src/styles/icons/PD/vienna-ubahn-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/vienna-ubahn-purple.png -------------------------------------------------------------------------------- /src/styles/icons/PD/ekaterinburg-metro-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zverik/kothic/master/src/styles/icons/PD/ekaterinburg-metro-green.png -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Kothic Map Renderer 2 | 3 | Depends: 4 | 1. tWMS http://twms.googlecode.com/ 5 | symlink twms directory into src/ 6 | 2. python-lxml 7 | 3. python-psyco, if available. 8 | 4. pygtk for GUI 9 | 5. pycairo 10 | 6. python-psycopg2 11 | 7. python-shapely 12 | 8. python-pyproj 13 | 14 | To run: 15 | 1. Install all dependancies. 16 | 2. cd src/ 17 | 3. cat small_test_osm_dump.osm | python osm2tiles.py 18 | 4. wait a bit and check if tiles got generated 19 | 5. python kothic.py to run GUI 20 | 21 | NOT READY FOR DAILY USAGE! 22 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='webcolors', 6 | version='1.3', 7 | description='A library for working with sRGB color specifications as used in HTML and CSS.', 8 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.txt')).read(), 9 | author='James Bennett', 10 | author_email='james@b-list.org', 11 | url='http://www.bitbucket.org/ubernostrum/webcolors/overview/', 12 | py_modules=['webcolors'], 13 | download_url='http://bitbucket.org/ubernostrum/webcolors/downloads/webcolors-1.3.tar.gz', 14 | classifiers=['Development Status :: 5 - Production/Stable', 15 | 'Environment :: Web Environment', 16 | 'Intended Audience :: Developers', 17 | 'License :: OSI Approved :: BSD License', 18 | 'Operating System :: OS Independent', 19 | 'Programming Language :: Python', 20 | 'Topic :: Utilities'], 21 | ) 22 | -------------------------------------------------------------------------------- /src/styles/landuses.mapcss: -------------------------------------------------------------------------------- 1 | canvas{fill-color:#a0bc92} 2 | 3 | way[landuse] {fill-color: red; } 4 | way[landuse=garages] {fill-color: yellow; } 5 | way[amenity=parking] {fill-color: #cccc00; } 6 | way[landuse=military] {fill-color: pink; } 7 | way[landuse=retail] {fill-color: blue; } 8 | 9 | way[landuse=reservoir] {fill-color: lightblue; } 10 | way[natural=water] {fill-color: lightblue; } 11 | way[waterway=riverbank] {fill-color: lightblue; } 12 | 13 | way[landuse=cemetery] {fill-color: black; } 14 | 15 | 16 | way[landuse=industrial] {fill-color: gray; } 17 | way[landuse=residential] {fill-color: white; } 18 | 19 | 20 | way[landuse=allotments] {fill-color: #ccffcc; } 21 | way[landuse=field] {fill-color: #ccffcc; } 22 | way[landuse=farmland] {fill-color: #ccffcc; } 23 | way[landuse=farm] {fill-color: #ccffcc; } 24 | 25 | way[landuse=construction] {fill-color: #987654; } 26 | way[landuse=greenfield] {fill-color: #987654; } 27 | way[landuse=brownfield] {fill-color: #987654; } 28 | 29 | way[landuse=quarry] {fill-color: lightgray; } 30 | 31 | way[landuse=grass] {fill-color: lightgreen} 32 | way[landuse=meadow] {fill-color: lightgreen} 33 | 34 | way[landuse=forest] {fill-color: green} 35 | way[natural=wood] {fill-color: green} 36 | 37 | /*way[building] {fill-color: silver; }*/ -------------------------------------------------------------------------------- /src/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | import datetime 18 | import sys 19 | 20 | 21 | def debug(st): 22 | """ 23 | Debug write to stderr 24 | """ 25 | 26 | sys.stderr.write(str(st) + "\n") 27 | sys.stderr.flush() 28 | 29 | 30 | class Timer: 31 | """ 32 | A small timer for debugging 33 | """ 34 | def __init__(self, comment): 35 | self.time = datetime.datetime.now() 36 | self.comment = comment 37 | debug("%s started" % comment) 38 | 39 | def stop(self): 40 | debug("%s finished in %s" % (self.comment, str(datetime.datetime.now() - self.time))) 41 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2009, James Bennett 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /src/styles/openstreetinfo.mapcss: -------------------------------------------------------------------------------- 1 | /* 2 | Deja Vu MapCSS styles 3 | OpenStreetInfo style 4 | */ 5 | 6 | canvas {fill-color: #ffffc8} 7 | 8 | way[landuse=residential] 9 | {fill-color: #daf4a4; color:#b8cd14 } 10 | 11 | way[highway] 12 | {width: eval( any( metric(tag("width")), metric ( num(tag("lanes")) * 4), metric("7m"))); 13 | color:#ffffff; 14 | text: name; text-position: line; text-color:#0000ff;text-halo-radius:2;text-halo-color:#ffffc8} 15 | 16 | way[highway] 17 | {casing-width: eval(min(1, num(prop("width"))/5 ));} 18 | 19 | 20 | way[highway][area=yes]{fill-color: #ffffff;width:0} 21 | 22 | /* With this eval, if bridge is applied to invisible line, no bridge renders */ 23 | way[bridge=yes] {casing-width:eval(min(3, num(prop("width"))/2 ));} 24 | 25 | 26 | way[natural=forest], 27 | way[natural=wood], 28 | way[landuse=forest], 29 | way[landuse=wood] 30 | {fill-color: #68ec80; color: #45a56b} 31 | 32 | way|z15-[landuse=grass], 33 | way[natural=grass]{fill-color: #e7ffd0; color: #45a56b} 34 | 35 | 36 | way[landuse=garages] 37 | {fill-color: #d2e8ed; color: #cad4e1} 38 | 39 | way[waterway=riverbank], 40 | way[natural=water] {fill-color: #5ba7ff; color: #0000a0} 41 | 42 | way[waterway=river], 43 | way[waterway=stream]{color: #5ba7ff;casing-width: 1} 44 | 45 | way[leisure=stadium]{fill-color: #d0ffff; casing-width: 2; casing-color: #00ccff;z-index:10;} 46 | 47 | way[railway=tram]{width: eval( any( metric(tag("width")), metric("1.52m")));color: #ffffff; casing-color: #000000} 48 | {width: eval( metric("2.7m")); color: #000000; dashes: 1,10; z-index:1; object-id: "shpala"} 49 | 50 | /*way[landuse=industrial] {fill-color: #855}*/ 51 | way[landuse=military] {fill-color: pink} 52 | 53 | way[amenity=parking] {fill-color: #d2e8ed;color:cad4e1} 54 | 55 | 56 | 57 | way|z16-[building] { 58 | width: .5; 59 | text: addr:housenumber; text-halo-radius:1; text-position: center; 60 | 61 | fill-color: #EDEDED; 62 | extrude: eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("15m"))); 63 | extrude-face-color: #e2e2e2; 64 | extrude-edge-width: 1; 65 | extrude-edge-color: #404040; 66 | } -------------------------------------------------------------------------------- /src/mapcss/Rule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | type_matches = { 19 | "": ('area', 'line', 'way', 'node'), 20 | "area": ("area", "way"), 21 | "node": ("node",), 22 | "way": ("line", "area", "way"), 23 | "line": ("line", "area"), 24 | } 25 | 26 | class Rule(): 27 | def __init__(self, s=''): 28 | self.conditions = [] 29 | # self.isAnd = True 30 | self.minZoom = 0 31 | self.maxZoom = 19 32 | if s == "*": 33 | s = "" 34 | self.subject = s # "", "way", "node" or "relation" 35 | 36 | def __repr__(self): 37 | return "%s|z%s-%s %s" % (self.subject, self.minZoom, self.maxZoom, self.conditions) 38 | 39 | def test(self, obj, tags, zoom): 40 | if (zoom < self.minZoom) or (zoom > self.maxZoom): 41 | return False 42 | 43 | if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): 44 | return False 45 | 46 | subpart = "::default" 47 | 48 | for condition in self.conditions: 49 | res = condition.test(tags) 50 | if not res: 51 | return False 52 | if type(res) != bool: 53 | subpart = res 54 | return subpart 55 | 56 | def test_zoom(self, zoom): 57 | return (zoom >= self.minZoom) and (zoom <= self.maxZoom) 58 | 59 | def get_compatible_types(self): 60 | return type_matches.get(self.subject, (self.subject,)) 61 | 62 | def get_interesting_tags(self, obj, zoom): 63 | if obj: 64 | if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {}): 65 | return set() 66 | 67 | if zoom and not self.test_zoom(zoom): 68 | return set() 69 | 70 | a = set() 71 | for condition in self.conditions: 72 | a.update(condition.get_interesting_tags()) 73 | return a 74 | 75 | def get_numerics(self): 76 | a = set() 77 | for condition in self.conditions: 78 | a.add(condition.get_numerics()) 79 | a.discard(False) 80 | return a 81 | 82 | def get_sql_hints(self, obj, zoom): 83 | if obj: 84 | if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {":area": "yes"}): 85 | return set() 86 | if not self.test_zoom(zoom): 87 | return set() 88 | a = set() 89 | b = set() 90 | for condition in self.conditions: 91 | q = condition.get_sql() 92 | if q: 93 | if q[1]: 94 | a.add(q[0]) 95 | b.add(q[1]) 96 | b = " AND ".join(b) 97 | return a, b 98 | 99 | 100 | def _test_feature_compatibility(f1, f2, tags={}): 101 | """ 102 | Checks if feature of type f1 is compatible with f2. 103 | """ 104 | if f2 == f1: 105 | return True 106 | if f2 not in ("way", "area", "line"): 107 | return False 108 | elif f2 == "way" and f1 == "line": 109 | return True 110 | elif f2 == "way" and f1 == "area": 111 | return True 112 | elif f2 == "area" and f1 in ("way", "area"): 113 | # if ":area" in tags: 114 | return True 115 | # else: 116 | # return False 117 | elif f2 == "line" and f1 in ("way", "line", "area"): 118 | return True 119 | else: 120 | return False 121 | # print f1, f2, True 122 | return True 123 | -------------------------------------------------------------------------------- /src/mapcss/Eval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | NONE = "" 19 | 20 | 21 | class Eval(): 22 | def __init__(self, s='eval()'): 23 | """ 24 | Parse expression and convert it into Python 25 | """ 26 | s = s.strip()[5:-1].strip() 27 | self.expr_text = s 28 | try: 29 | self.expr = compile(s, "MapCSS expression", "eval") 30 | except: 31 | # print "Can't compile %s" % s 32 | self.expr = compile("0", "MapCSS expression", "eval") 33 | 34 | def extract_tags(self): 35 | """ 36 | Extracts list of tags that might be used in calculation 37 | """ 38 | def fake_compute(*x): 39 | """ 40 | Perform a fake computation. Always computes all the parameters, always returns 0. 41 | WARNING: Might not cope with complex statements. 42 | """ 43 | for t in x: 44 | q = x 45 | return 0 46 | tags = set([]) 47 | # print self.expr_text 48 | 49 | a = eval(self.expr, {}, { 50 | "tag": lambda x: max([tags.add(x), " "]), 51 | "prop": lambda x: "", 52 | "num": lambda x: 0, 53 | "metric": fake_compute, 54 | "zmetric": fake_compute, 55 | "str": lambda x: "", 56 | "any": fake_compute, 57 | "min": fake_compute, 58 | "max": fake_compute, 59 | }) 60 | return tags 61 | 62 | def compute(self, tags={}, props={}, xscale=1., zscale=0.5): 63 | """ 64 | Compute this eval() 65 | """ 66 | for k, v in tags.iteritems(): 67 | try: 68 | tag[k] = float(v) 69 | except: 70 | pass 71 | try: 72 | return str(eval(self.expr, {}, { 73 | "tag": lambda x: tags.get(x, ""), 74 | "prop": lambda x: props.get(x, ""), 75 | "num": m_num, 76 | "metric": lambda x: m_metric(x, xscale), 77 | "zmetric": lambda x: m_metric(x, zscale), 78 | "str": str, 79 | "any": m_any, 80 | "min": m_min, 81 | "max": m_max, 82 | "cond": m_cond, 83 | "boolean": m_boolean 84 | })) 85 | except: 86 | return "" 87 | 88 | def __repr__(self): 89 | return "eval(%s)" % self.expr_text 90 | 91 | 92 | def m_boolean(expr): 93 | expr = str(expr) 94 | if expr in ("", "0", "no", "false", "False"): 95 | return False 96 | else: 97 | return True 98 | 99 | 100 | def m_cond(why, yes, no): 101 | if m_boolean(why): 102 | return yes 103 | else: 104 | return no 105 | 106 | 107 | def m_min(*x): 108 | """ 109 | min() MapCSS Feature 110 | """ 111 | try: 112 | return min([m_num(t) for t in x]) 113 | except: 114 | return 0 115 | 116 | 117 | def m_max(*x): 118 | """ 119 | max() MapCSS Feature 120 | """ 121 | try: 122 | return max([m_num(t) for t in x]) 123 | except: 124 | return 0 125 | 126 | 127 | def m_any(*x): 128 | """ 129 | any() MapCSS feature 130 | """ 131 | for t in x: 132 | if t: 133 | return t 134 | else: 135 | return "" 136 | 137 | 138 | def m_num(x): 139 | """ 140 | num() MapCSS feature 141 | """ 142 | try: 143 | return float(str(x)) 144 | except ValueError: 145 | return 0 146 | 147 | 148 | def m_metric(x, t): 149 | """ 150 | metric() and zmetric() function. 151 | """ 152 | x = str(x) 153 | try: 154 | return float(x) * float(t) 155 | except: 156 | "Heuristics." 157 | # FIXME: add ft, m and friends 158 | x = x.strip() 159 | try: 160 | if x[-2:] in ("cm", "CM", "см"): 161 | return float(x[0:-2]) * float(t) / 100 162 | if x[-2:] in ("mm", "MM", "мм"): 163 | return float(x[0:-2]) * float(t) / 1000 164 | if x[-1] in ("m", "M", "м"): 165 | return float(x[0:-1]) * float(t) 166 | except: 167 | return "" 168 | # def str(x): 169 | #""" 170 | # str() MapCSS feature 171 | #""" 172 | # return __builtins__.str(x) 173 | 174 | 175 | if __name__ == "__main__": 176 | a = Eval(""" eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("1m"))) """) 177 | print repr(a) 178 | print a.compute({"building:levels": "3"}) 179 | print a.extract_tags() 180 | -------------------------------------------------------------------------------- /src/backend/postgis/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | # from debug import debug 19 | from twms import projections 20 | import psycopg2 21 | import shapely.wkb 22 | 23 | 24 | class Empty: 25 | def copy(self): 26 | a = Empty() 27 | a.tags = self.tags.copy() 28 | a.coords = self.coords[:] 29 | a.center = self.center 30 | a.cs = self.cs[:] 31 | return a 32 | 33 | 34 | class Way: 35 | def __init__(self, tags, geom): 36 | 37 | self.cs = [] 38 | # print [x.split("=") for x in tags.split(";")] 39 | self.tags = tags 40 | # calculating center point 41 | # c= geom 42 | # sumz = [(c[0],c[1])] 43 | # for k in range(2, len(c), 2): 44 | # sumz.append((c[k], c[k + 1])) 45 | self.coords = geom 46 | # left for the better times: 47 | self.center = reduce(lambda x, y: (x[0] + y[0], x[1] + y[1]), self.coords) 48 | self.center = (self.center[0] / len(self.coords), self.center[1] / len(self.coords)) 49 | # debug(self.center) 50 | 51 | def copy(self): 52 | a = Empty() 53 | a.tags = self.tags.copy() 54 | a.coords = self.coords[:] 55 | a.center = self.center 56 | a.cs = self.cs[:] 57 | return a 58 | 59 | 60 | class PostGisBackend: 61 | """ 62 | A class that gives out vector data on demand. 63 | """ 64 | 65 | def __init__(self, database="dbname=gis user=mapz host=komzpa.net", max_zoom=16, proj="EPSG:3857", path="tiles", lang="ru", ): 66 | 67 | # debug("Bakend created") 68 | self.database = database 69 | self.max_zoom = max_zoom # no better tiles available 70 | self.path = path # path to tile files 71 | self.lang = lang # map language to use 72 | self.tiles = {} # loaded vector tiles go here 73 | self.proj = proj # which projection used to cut map in tiles 74 | self.keep_tiles = 190 # a number of tiles to cache in memory 75 | self.tile_load_log = [] # used when selecting which tile to unload 76 | 77 | def get_vectors(self, bbox, zoom, sql_hint=None, tags_hint=None): 78 | """ 79 | Fetches vectors for given bbox. 80 | sql_hint is a list of sets of (key, sql_for_key) 81 | """ 82 | a = psycopg2.connect(self.database) 83 | b = a.cursor() 84 | bbox = tuple(projections.from4326(bbox, self.proj)) 85 | ### FIXME: hardcoded EPSG:3857 in database 86 | tables = ("planet_osm_line", "planet_osm_polygon") # FIXME: points 87 | resp = {} 88 | for table in tables: 89 | add = "" 90 | taghint = "*" 91 | if sql_hint: 92 | adp = [] 93 | 94 | for tp in sql_hint: 95 | add = [] 96 | b.execute("SELECT * FROM %s LIMIT 1;" % table) 97 | names = [q[0] for q in b.description] 98 | 99 | for j in tp[0]: 100 | if j not in names: 101 | break 102 | else: 103 | add.append(tp[1]) 104 | if add: 105 | add = " OR ".join(add) 106 | add = "(" + add + ")" 107 | adp.append(add) 108 | 109 | if tags_hint: 110 | taghint = ", ".join(['"' + j + '"' for j in tags_hint if j in names]) + ", way, osm_id" 111 | 112 | adp = " OR ".join(adp) 113 | 114 | req = "SELECT %s FROM %s WHERE (%s) and way && SetSRID('BOX3D(%s %s,%s %s)'::box3d,900913);" % (taghint, table, adp, bbox[0], bbox[1], bbox[2], bbox[3]) 115 | print req 116 | b.execute(req) 117 | names = [q[0] for q in b.description] 118 | 119 | for row in b.fetchall(): 120 | 121 | row_dict = dict(map(None, names, row)) 122 | for k, v in row_dict.items(): 123 | if not v: 124 | del row_dict[k] 125 | geom = shapely.wkb.loads(row_dict["way"].decode('hex')) 126 | ### FIXME: a dirty hack to basically support polygons, needs lots of rewrite 127 | try: 128 | geom = list(geom.coords) 129 | except NotImplementedError: 130 | "trying polygons" 131 | try: 132 | geom = geom.boundary 133 | geom = list(geom.coords) 134 | row_dict[":area"] = "yes" 135 | except NotImplementedError: 136 | "multipolygon" 137 | continue 138 | ### FIXME 139 | 140 | # geom = projections.to4326(geom, self.proj) 141 | del row_dict["way"] 142 | oid = row_dict["osm_id"] 143 | del row_dict["osm_id"] 144 | w = Way(row_dict, geom) 145 | # print row_dict 146 | resp[oid] = w 147 | a.close() 148 | del a 149 | 150 | return resp 151 | -------------------------------------------------------------------------------- /src/styles/mapink.mapcss: -------------------------------------------------------------------------------- 1 | canvas{fill-color:#B5D0D0} 2 | 3 | area[natural=ocean]{fill-color:#B5D0D0} 4 | area[natural=coastline]{fill-color:#B5D0D0} 5 | /*area[natural=coastline]{fill-color:#F1EEE8}*/ 6 | 7 | 8 | area|z10-[landuse=military]{fill-color:#F1EEE8; z-index:100} 9 | 10 | 11 | line|z2-3[boundary=administrative][admin_level=2] {color:#B2B0AE; width:.3} 12 | line|z4-6[boundary=administrative][admin_level=2] {color:#9d6c9d; width:.5} 13 | line|z7-[boundary=administrative][admin_level=2] {color:#9d6c9d; width: 1} 14 | line|z10-[boundary=administrative][admin_level=2]::halo {color:#9d6c9d; width: 6; opacity:0.3} 15 | line|z4-6[boundary=administrative][admin_level=3] {color:#9d6c9d; width:.3} 16 | line|z7-[boundary=administrative][admin_level=3] {color:#9d6c9d; width:.5} 17 | line|z4-6[boundary=administrative][admin_level=4] {color:#9d6c9d; width:.2} 18 | line|z7-[boundary=administrative][admin_level=4] {color:#9d6c9d; width:.3} 19 | 20 | node|z2-4[place=country]{text-color:#9d6c9d; 21 | text: name; collision-sort-by: population; font-size: 10,9,8,7; 22 | text-halo-radius: 1; text-halo-color: white; 23 | -x-mapnik-min-distance: 5; max-width: 20; z-index: 15 24 | } 25 | 26 | node|z5-6[place=country]{text-color:#9d6c9d; 27 | text: name; collision-sort-by: population; font-size: 12,11,10,9,8,7; 28 | text-halo-radius: 1.5; text-halo-color: white; 29 | -x-mapnik-min-distance: 5; max-width: 20; z-index: 15 30 | } 31 | 32 | 33 | node|z4[place=state]{text-color:#9d6c9d; 34 | font-family: "DejaVu Sans Oblique"; 35 | text: ref; collision-sort-by: population; font-size: 9,8,7; 36 | text-halo-radius: 1; text-halo-color: white; 37 | -x-mapnik-min-distance: 2; max-width: 20; z-index: 10 38 | } 39 | 40 | node|z5-6[place=state]{text-color:#9d6c9d; 41 | font-family: "DejaVu Sans Oblique"; 42 | text: name; collision-sort-by: population; font-size: 9,8,7,6; 43 | text-halo-radius: 1; text-halo-color: white; 44 | -x-mapnik-min-distance: 3; max-width: 30; z-index: 10 45 | } 46 | 47 | node|z7-[place=state]{text-color:#9d6c9d; 48 | font-family: "DejaVu Sans Oblique"; 49 | text: name; collision-sort-by: population; font-size: 11,10,9,8,7; 50 | text-halo-radius: 1; text-halo-color: white; 51 | -x-mapnik-min-distance: 2; max-width: 80; z-index: 10 52 | } 53 | 54 | node|z3-4[place=city]{text-color:grey; 55 | text: name; collision-sort-by: population; font-size: 9,8,7; 56 | text-halo-radius: 1; text-halo-color: white; 57 | -x-mapnik-min-distance: 10; max-width: 20; z-index: 5 58 | } 59 | 60 | node|z5[place=city]{text-color:black; 61 | text: name; collision-sort-by: population; font-size: 9,8,7; 62 | text-halo-radius: 1; text-halo-color: white; 63 | -x-mapnik-min-distance: 7; max-width: 20; z-index: 5 64 | } 65 | 66 | node|z6-8[place=city][capital?] 67 | {text-color:black; 68 | text: name; collision-sort-by: population; font-size: 13,12,11,10,9,8,7; 69 | text-halo-radius: 1; text-halo-color: white; 70 | -x-mapnik-min-distance: 2; max-width: 20; z-index: 7 71 | } 72 | 73 | 74 | node|z6-8[place=city][!capital?] 75 | {text-color:black; 76 | text: name; collision-sort-by: population; font-size: 9,8,7; 77 | text-halo-radius: 1; text-halo-color: white; 78 | -x-mapnik-min-distance: 1; max-width: 20; z-index: 5 79 | } 80 | 81 | node|z8-[place=town] 82 | {text-color:black; 83 | text: name; collision-sort-by: population; font-size: 8,7; 84 | text-halo-radius: 1; text-halo-color: white; 85 | -x-mapnik-min-distance: 5; max-width: 20; z-index: 5 86 | } 87 | 88 | node|z9-[place=city] 89 | {text-color:black; 90 | text: name; collision-sort-by: population; font-size: 12,11,10,9,8,7; 91 | text-halo-radius: 1; text-halo-color: white; 92 | -x-mapnik-min-distance: 2; max-width: 20; z-index: 7 93 | } 94 | 95 | 96 | node|z10-[place=village] 97 | {text-color:black; 98 | text: name; collision-sort-by: population; font-size: 7,6,5; 99 | text-halo-radius: 1; text-halo-color: white; 100 | -x-mapnik-min-distance: 2; max-width: 20; z-index: 7 101 | } 102 | 103 | 104 | line|z5-6[highway=motorway] {color: #d6dfea; width: 0.35} 105 | line|z7[highway=motorway] {color: #809bc0; width: 1} 106 | line|z8[highway=motorway] {color: #809bc0; width: 1} 107 | line|z9-10[highway=motorway]{color: #809bc0; width: 1.5} 108 | line|z11[highway=motorway] {color: #809bc0; width: 2} 109 | line|z12-[highway=motorway] {color: #809bc0; width: 2.5} 110 | area|z14-[area:highway=motorway] {fill-color: #809bc0} 111 | 112 | line|z0[highway=trunk] {color: #cdeacd; width: 0.35} 113 | line|z5-6[highway=trunk] {color: #cdeacd; width: 0.35} 114 | line|z7[highway=trunk] {color: #a9dba9; width: 1; casing-width:.3; casing-color:#F1EEE8} 115 | line|z8[highway=trunk] {color: #a9dba9; width: 1; casing-width:3; casing-color:#F1EEE8} 116 | line|z9-10[highway=trunk] {color: #98D296; width: 1.5; casing-width:1; casing-color:#F1EEE8} 117 | line|z10-[highway=trunk_link] {color: #98D296; width: 1.5; casing-width:1; casing-color:#F1EEE8} 118 | line|z11[highway=trunk] {color: #98D296; width: 2; casing-width:1; casing-color:#F1EEE8} 119 | line|z12-[highway=trunk] {color: #98D296; width: 2.5; casing-width:1; casing-color:#F1EEE8} 120 | area|z14-[area:highway=trunk] {fill-color: #98D296} 121 | 122 | 123 | line|z7[highway=primary] {color: #ec989a; width: 0.5} 124 | line|z8[highway=primary] {color: #ec989a; width: 0.5; casing-width:1; casing-color:#F1EEE8} 125 | line|z9-10[highway=primary] {color: #ec989a; width: 1.2} 126 | line|z11-[highway=primary] {color: #ec989a; width: 2} 127 | area|z14-[area:highway=primary] {fill-color: #ec989a} 128 | 129 | line|z9-10[highway=secondary] {color: #fed7a5; width: 1.2} 130 | line|z11-[highway=secondary] {color: #fed7a5; width: 2} 131 | area|z14-[area:highway=secondary] {fill-color: #fed7a5} 132 | 133 | area[area:highway]{z-index:100} 134 | 135 | line|z10-[highway=tertiary], 136 | line|z10-[highway=tertiary_link], 137 | line|z10-[highway=residential], 138 | line|z10-[highway=unclassified], 139 | line|z10-[highway=living_street] {color: #BCBCBC; width: .7} 140 | 141 | 142 | line|z11-[highway=trunk] 143 | {text: name; text-position: line; font-size: 8; 144 | text-halo-radius: 1; text-halo-color: #98D296; text-spacing: 256;} 145 | line|z11-[highway=primary] 146 | {text: name; text-position: line; font-size: 8; 147 | text-halo-radius: 1; text-halo-color: #ec989a; text-spacing: 256;} 148 | line|z11-[highway=motorway] 149 | {text: name; text-position: line; font-size: 8; 150 | text-halo-radius: 1; text-halo-color: #809bc0; text-spacing: 256;} 151 | line|z11-[highway=secondary] 152 | {text: name; text-position: line; font-size: 8; 153 | text-halo-radius: 1; text-halo-color: #fed7a5; text-spacing: 256;} 154 | 155 | 156 | 157 | line|z6-9[railway=rail] {color: grey; width: 0.27} 158 | line|z10-[railway=rail] {color: #aaa; width: 1} 159 | 160 | area|z6-[natural=water], 161 | area|z6-[waterway=riverbank], 162 | area|z8-[landuse=reservoir], 163 | {fill-color:#B5D0D0} 164 | 165 | line|z9-[waterway=river]{color:#B5D0D0; width:1.2} 166 | 167 | 168 | area|z8-[landuse=forest], 169 | area|z8-[natural=wood] 170 | {fill-color:#AED0A0; fill-position: background; z-index:5} 171 | 172 | area|z8-[landuse=residential], 173 | area|z8-[place=town], 174 | area|z8-[place=village], 175 | area|z8-[place=hamlet] 176 | {fill-color:#dddddd; fill-position: background} 177 | 178 | 179 | area|z7-[boundary=national_park]{fill-color:#E5E8DD;fill-position:background; color:green; width:.3; dashes:2,2} 180 | 181 | area|z8-[boundary=national_park]{text: name; font-family: DejaVu Sans Bold; font-size:8; 182 | text-halo-radius: 1.5; text-halo-color: white; text-color: #9c9; z-index:6; max-width: 40} 183 | 184 | 185 | line|z7-[route=ferry] {color:#66f; width:0.4; dashes:4,4; z-index:5} 186 | 187 | 188 | area|z9-[landuse=farmland], 189 | area|z9-[landuse=farm] 190 | {fill-color:#E9D8BD;fill-position:background} 191 | 192 | area|z9-[landuse=field], 193 | {fill-color:#D5D2BA;fill-position:background} 194 | 195 | 196 | area|z10-[landuse=construction], 197 | area|z10-[landuse=brownfield]{fill-color:#B3B592;fill-position:background} 198 | 199 | area|z10-[landuse=industrial]{fill-color:#DED1D5;fill-position:background} 200 | area|z10-[landuse=grass]{fill-color:#CEEBA8;fill-position:background} 201 | area|z10-[leisure=park]{fill-color:#CCF5C9;fill-position:background} 202 | 203 | area|z12-[building][building!=no][building!=planned]{fill-color:#C1B0AE} 204 | 205 | area|z15-[building]{text: "addr:housenumber"; 206 | font-size:8; 207 | text-halo-radius: 1; 208 | text-halo-color: white; 209 | /* text-position:line; -x-mapnik-snap-to-street: true */ 210 | } -------------------------------------------------------------------------------- /src/styles/gisrussa.mapcss: -------------------------------------------------------------------------------- 1 | /* 2 | GisRussa style 3 | 4 | by Hind, 2010 5 | 6 | */ 7 | 8 | canvas 9 | { antialiasing: none; fill-color: #F1FFFF; fill-opacity: 1.0; } 10 | 11 | /* =============== Polygons =============== */ 12 | 13 | /* Defaults for all elements */ 14 | way { text-color: black; font-weight: normal; font-size: 8; } 15 | node { text-color: black; font-weight: normal; font-size: 6; text-position: center; text-offset: 10; z-index: 1; } 16 | 17 | /* Places on small zoomlevels */ 18 | area|z8-10[place] 19 | { fill-color : #FFFACD; text-position: center; text: name; } 20 | 21 | /* Places on large zoomlevels */ 22 | area|z11-12[landuse=residential] 23 | { fill-color : #FFE4C4; text-position: center; text: name; } 24 | 25 | /* Parkings */ 26 | area|z12-[amenity=parking] 27 | { fill-color : #F0F0F0; text-position: center; text: name; } 28 | 29 | /* Retail area */ 30 | area|z12-[landuse=retail] 31 | { fill-color : #F8B880; text-position: center; text: name; } 32 | 33 | /* Schools, colleges, universities */ 34 | area|z12-[amenity=school], 35 | area|z12-[amenity=university] 36 | { fill-color : #F8B880; text-position: center; text: name; } 37 | 38 | /* Hospitals */ 39 | area[amenity=doctors],area[amenity=hospital] 40 | { fill-color : #F8B880; text-position: center; text: name; } 41 | 42 | /* Industrial area */ 43 | area|z11-[landuse=industrial] 44 | { fill-color : #E8E8E8; text-position: center; text: name; } 45 | 46 | /* Buildings */ 47 | area|z13-[building][building!=garages] 48 | { fill-color : #969696; text-position: center; text: addr:housenumber; } 49 | 50 | /* Garages */ 51 | area|z13-[building=garages] 52 | { fill-color : #E2E2E2; text-position: center; text: name; } 53 | 54 | /* Parks */ 55 | area|z12-[leisure=park] 56 | { fill-color : #90BE00; text-position: center; text: name; } 57 | 58 | /* Stadiums, leisures */ 59 | area|z12-[leisure] 60 | { fill-color : #F8B880; text-position: center; text: name; } 61 | 62 | /* Water */ 63 | area|z9-[natural=water],way[waterway=riverbank] 64 | { fill-color : #0080FF; text-position: center; text: name; } 65 | 66 | /* Forests and woods */ 67 | area|z9-[natural=wood],way[landuse=forest] 68 | { fill-color : #B7E999; text-position: center; text: name; } 69 | 70 | /* Wetlands */ 71 | area|z10-[natural=wetland] 72 | { fill-color : #4ACA4A; text-position: center; text: name; } 73 | 74 | /* =============== Ways =============== */ 75 | 76 | /* Trunks */ 77 | way[highway=trunk] 78 | { color: #C46442; width: 4; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; shield-color: white; shield-frame-color: #C46442; shield-frame-width: 1; shield-shape: rectangular; shield-text: ref; } 79 | 80 | /* Primaries */ 81 | way[highway=primary] 82 | { color: #DC7C5A; width: 3; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 83 | 84 | /* Secondaries, unclassified */ 85 | way[highway=secondary],way|z12-[highway=unclassified] 86 | { color: #E68664; width: 2; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 87 | 88 | /* Tertiaries */ 89 | way|z12-[highway=tertiary] 90 | { color: #E88866; width: 2; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 91 | 92 | /* Residentials */ 93 | way|z12-[highway=residential] 94 | { color: #EE8E6C; width: 1; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 95 | 96 | /* Services, pedestrians */ 97 | way|z12-[highway=service],way[highway=pedestrian] 98 | { color: #C46442; width: 1; text-color: black; text-position: line; text: name; text-offset: -8; } 99 | 100 | /* Primary_links */ 101 | way|z11-[highway=primary_link] 102 | { color: #E88866; width: 2; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 103 | 104 | /* Tracks */ 105 | way|z12-[highway=track] 106 | { color: black; width: 1; text-color: black; text-position: line; text: name; dashes: 4; text-offset: -8; } 107 | 108 | /* Trunk_links */ 109 | way|z11-[highway=trunk_link] 110 | { color: #C46442; width: 2; text-color: black; text-position: line; text: name; text-offset: -8; } 111 | 112 | /* Unknown roads */ 113 | way|z12-[highway=road] 114 | { color: red; width: 1; text-color: black; text-position: line; text: name; text-offset: -8; } 115 | 116 | /* Roundabouts */ 117 | way|z11-[highway][junction=roundabout] 118 | { color: #E88866; width: 2; text-color: black; text-position: line; text: name; casing-width: 1; casing-color: black; text-offset: -8; } 119 | 120 | /* Railroads */ 121 | way|z11-[railway] 122 | { color: white; width: 1; text-color: black; text-position: line; text: name; dashes: 3; casing-width: 1; casing-color: black; } 123 | 124 | /* Footways, paths */ 125 | way|z12-[highway=footway],way|z12-[highway=path] 126 | { color: black; width: 1; text-color: black; text-position: line; text: name; text-offset: -8; } 127 | 128 | /* Streams */ 129 | way|z12-[waterway=stream] 130 | { color: black; width: 1; text-color: blue; text-position: line; text: name; text-offset: -8; } 131 | 132 | /* Boundary=administrative, level 4 */ 133 | way[boundary=administrative][admin_level=4] 134 | { color: #00C864; width: 1; text-color: #00C864; text-position: line; text: name; } 135 | /* { color: red; width: 1; dashes: 4; } */ 136 | 137 | /* Boundary=administrative, level 6 */ 138 | way[boundary=administrative][admin_level=6] 139 | { color: #00C864; width: 1; text-color: #00C864; text-position: line; text: name; } 140 | /* { color: black; width: 1; dashes: 4; } */ 141 | 142 | /* Rivers */ 143 | way|z9-[waterway] 144 | { color: blue; width: 1; text-color: blue; text-position: line; text: name; text-offset: -8; } 145 | 146 | /* Power lines */ 147 | way|z12-[power=line] 148 | { color: gray; width: 1; } 149 | 150 | /* Fences, walls */ 151 | way|z12-[barrier=fence],way|z12-[barrier=wall] 152 | { color: #00C864; width: 1; text-color: #00C864; text-position: line; text: name; dashes: 4; } 153 | 154 | /* =============== Nodes =============== */ 155 | 156 | /* Place names */ 157 | node[place] { z-index:10; } 158 | node[place=city] 159 | { icon-image: icons/0001.png; font-size: 10; text: name; font-weight: 700; } 160 | 161 | node[place=town] 162 | { icon-image: icons/0002.png; font-size: 10; text: name; font-weight: 700; } 163 | 164 | node|z13-[place=village] 165 | { icon-image: icons/0003.png; font-size: 9; text: name; font-weight: 400; } 166 | 167 | node|z13-[place=suburb],node|z13-[place=hamlet] 168 | { icon-image: icons/0004.png; font-size: 9; text: name; font-weight: 200; } 169 | 170 | node|z13-[traffic_calming=bump],node|z12-[traffic_calming=hump] 171 | { icon-image: icons/1283.png; text: name; } 172 | 173 | node|z13-[highway=bus_stop] 174 | { icon-image: icons/1240.png; text: name; } 175 | 176 | node|z13-[amenity=post_office] 177 | { icon-image: icons/1077.png; text: name; } 178 | 179 | node|z13-[natural=tree] 180 | { icon-image: icons/1040.png; text: name; } 181 | 182 | node|z13-[shop=convenience],node|z13-[shop=mall],node|z13-[shop=supermarket] 183 | { icon-image: icons/1070.png; text: name; } 184 | 185 | node|z13-[shop=alcohol] 186 | { icon-image: icons/1054.png; text: name; } 187 | 188 | node|z13-[shop=clothes] 189 | { icon-image: icons/1071.png; text: name; } 190 | 191 | node|z13-[amenity=restaurant],node|z13-[amenity=food_court],node|z13-[amenity=fast_food] 192 | { icon-image: icons/1031.png; text: name; } 193 | 194 | node|z13-[tourism=hotel],node|z13-[tourism=hostel],node|z13-[tourism=motel] 195 | { icon-image: icons/1032.png; text: name; } 196 | 197 | node|z13-[tourism=camp_site] 198 | { icon-image: icons/1033.png; text: name; } 199 | 200 | node|z13-[historic] 201 | { icon-image: icons/1034.png; text: name; } 202 | 203 | node|z13-[amenity=library] 204 | { icon-image: icons/1037.png; text: name; } 205 | 206 | node|z13-[tourism=viewpoint] 207 | { icon-image: icons/1038.png; text: name; } 208 | 209 | node|z13-[amenity=school] 210 | { icon-image: icons/1039.png; text: name; } 211 | 212 | node|z13-[tourism=zoo] 213 | { icon-image: icons/1041.png; text: name; } 214 | 215 | node|z13-[amenity=theatre] 216 | { icon-image: icons/1044.png; text: name; } 217 | 218 | node|z13-[amenity=bar],node|z13-[amenity=pub] 219 | { icon-image: icons/1045.png; text: name; } 220 | 221 | node|z13-[leisure=golf_course],node|z13-[sport=golf] 222 | { icon-image: icons/1048.png; text: name; } 223 | 224 | node|z13-[leisure=ice_rink],node|z13-[sport=skating] 225 | { icon-image: icons/1051.png; text: name; } 226 | 227 | node|z13-[sport=swimming] 228 | { icon-image: icons/1052.png; text: name; } 229 | 230 | node|z13-[leisure=sports_centre][!sport] 231 | { icon-image: icons/1053.png; text: name; } 232 | 233 | node|z13-[amenity=pharmacy] 234 | { icon-image: icons/1072.png; text: name; } 235 | 236 | node|z13-[amenity=fuel] 237 | { icon-image: icons/1074.png; text: name; } 238 | 239 | node|z13-[amenity=bank] 240 | { icon-image: icons/1078.png; text: name; } 241 | 242 | node|z13-[amenity=parking] 243 | { icon-image: icons/1098.png; text: name; } 244 | 245 | node|z13-[amenity=doctors],node|z13-[amenity=hospital] 246 | { icon-image: icons/1104.png; text: name; } 247 | -------------------------------------------------------------------------------- /src/mapcss/Condition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | import re 19 | 20 | INVERSIONS = {"eq": "ne", "true": "false", "set": "unset", "<": ">=", ">": "<="} 21 | in2 = {} 22 | for a, b in INVERSIONS.iteritems(): 23 | in2[b] = a 24 | INVERSIONS.update(in2) 25 | del in2 26 | 27 | 28 | class Condition: 29 | def __init__(self, typez, params): 30 | self.type = typez # eq, regex, lt, gt etc. 31 | if type(params) == type(str()): 32 | params = (params,) 33 | self.params = params # e.g. ('highway','primary') 34 | if typez == "regex": 35 | self.regex = re.compile(self.params[0], re.I) 36 | 37 | self.compiled_regex = "" 38 | 39 | def get_interesting_tags(self): 40 | if self.params[0][:2] == "::": 41 | return [] 42 | return set([self.params[0]]) 43 | 44 | def get_numerics(self): 45 | if self.type in ("<", ">", ">=", "<="): 46 | return self.params[0] 47 | else: 48 | return False 49 | 50 | def test(self, tags): 51 | """ 52 | Test a hash against this condition 53 | """ 54 | t = self.type 55 | params = self.params 56 | if t == 'eq': # don't compare tags against sublayers 57 | if params[0][:2] == "::": 58 | return params[1] 59 | try: 60 | if t == 'eq': 61 | return tags[params[0]] == params[1] 62 | if t == 'ne': 63 | return tags.get(params[0], "") != params[1] 64 | if t == 'regex': 65 | return bool(self.regex.match(tags[params[0]])) 66 | if t == 'true': 67 | return tags.get(params[0]) == 'yes' 68 | if t == 'untrue': 69 | return tags.get(params[0]) == 'no' 70 | if t == 'set': 71 | if params[0] in tags: 72 | return tags[params[0]] != '' 73 | return False 74 | if t == 'unset': 75 | if params[0] in tags: 76 | return tags[params[0]] == '' 77 | return True 78 | 79 | if t == '<': 80 | return (Number(tags[params[0]]) < Number(params[1])) 81 | if t == '<=': 82 | return (Number(tags[params[0]]) <= Number(params[1])) 83 | if t == '>': 84 | return (Number(tags[params[0]]) > Number(params[1])) 85 | if t == '>=': 86 | return (Number(tags[params[0]]) >= Number(params[1])) 87 | except KeyError: 88 | pass 89 | return False 90 | 91 | def inverse(self): 92 | """ 93 | Get a not-A for condition A 94 | """ 95 | t = self.type 96 | params = self.params 97 | try: 98 | return Condition(INVERSIONS[t], params) 99 | if t == 'regex': 100 | ### FIXME: learn how to invert regexes 101 | return Condition("regex", params) 102 | except KeyError: 103 | pass 104 | return self 105 | 106 | def get_sql(self): 107 | # params = [re.escape(x) for x in self.params] 108 | params = self.params 109 | t = self.type 110 | if t == 'eq': # don't compare tags against sublayers 111 | if params[0][:2] == "::": 112 | return ("", "") 113 | try: 114 | if t == 'eq': 115 | return params[0], '"%s" = \'%s\'' % (params[0], params[1]) 116 | if t == 'ne': 117 | return params[0], '("%s" != \'%s\' or "%s" IS NULL)' % (params[0], params[1], params[0]) 118 | if t == 'regex': 119 | return params[0], '"%s" ~ \'%s\'' % (params[0], params[1].replace("'", "\\'")) 120 | if t == 'true': 121 | return params[0], '"%s" = \'yes\'' % (params[0]) 122 | if t == 'untrue': 123 | return params[0], '"%s" = \'no\'' % (params[0]) 124 | if t == 'set': 125 | return params[0], '"%s" IS NOT NULL' % (params[0]) 126 | if t == 'unset': 127 | return params[0], '"%s" IS NULL' % (params[0]) 128 | 129 | if t == '<': 130 | return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) < %s ELSE false END) """ % (params[0], params[0], params[1]) 131 | if t == '<=': 132 | return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) <= %s ELSE false END)""" % (params[0], params[0], params[1]) 133 | if t == '>': 134 | return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) > %s ELSE false END) """ % (params[0], params[0], params[1]) 135 | if t == '>=': 136 | return params[0], """(CASE WHEN "%s" ~ E'^[-]?[[:digit:]]+([.][[:digit:]]+)?$' THEN CAST ("%s" AS FLOAT) >= %s ELSE false END) """ % (params[0], params[0], params[1]) 137 | except KeyError: 138 | pass 139 | 140 | def get_mapnik_filter(self): 141 | # params = [re.escape(x) for x in self.params] 142 | params = self.params 143 | t = self.type 144 | if t == 'eq': # don't compare tags against sublayers 145 | if params[0][:2] == "::": 146 | return '' 147 | try: 148 | if t == 'eq': 149 | return '[%s] = \'%s\'' % (params[0], params[1]) 150 | if t == 'ne': 151 | return 'not([%s] = \'%s\')' % (params[0], params[1]) 152 | if t == 'regex': 153 | return '[%s].match(\'%s\')' % (params[0], params[1].replace("'", "\\'")) 154 | if t == 'true': 155 | return '[%s] = \'yes\'' % (params[0]) 156 | if t == 'untrue': 157 | return '[%s] = \'no\'' % (params[0]) 158 | if t == 'set': 159 | return '[%s] != \'\'' % (params[0]) 160 | if t == 'unset': 161 | return 'not([%s] != \'\')' % (params[0]) 162 | 163 | if t == '<': 164 | return '[%s__num] < %s' % (params[0], float(params[1])) 165 | if t == '<=': 166 | return '[%s__num] <= %s' % (params[0], float(params[1])) 167 | if t == '>': 168 | return '[%s__num] > %s' % (params[0], float(params[1])) 169 | if t == '>=': 170 | return '[%s__num] >= %s' % (params[0], float(params[1])) 171 | # return "" 172 | except KeyError: 173 | pass 174 | 175 | def __repr__(self): 176 | return "%s %s " % (self.type, repr(self.params)) 177 | 178 | def __eq__(self, a): 179 | return (self.params == a.params) and (self.type == a.type) 180 | 181 | def and_with(self, c2): 182 | """ 183 | merges two rules with AND. 184 | """ 185 | # TODO: possible other minimizations 186 | if c2.params[0] == self.params[0]: 187 | if c2.params == self.params: 188 | if c2.type == INVERSIONS[self.type]: # for example, eq AND ne = 0 189 | return False 190 | if c2.type == self.type: 191 | return (self,) 192 | 193 | if self.type == ">=" and c2.type == "<=": # a<=2 and a>=2 --> a=2 194 | return (Condition("eq", self.params),) 195 | if self.type == "<=" and c2.type == ">=": 196 | return (Condition("eq", self.params),) 197 | if self.type == ">" and c2.type == "<": 198 | return False 199 | if self.type == "<" and c2.type == ">": 200 | return False 201 | 202 | if c2.type == "eq" and self.type in ("ne", "<", ">"): 203 | if c2.params[1] != self.params[1]: 204 | return (c2,) 205 | if self.type == "eq" and c2.type in ("ne", "<", ">"): 206 | if c2.params[1] != self.params[1]: 207 | return (self,) 208 | if self.type == "eq" and c2.type == "eq": 209 | if c2.params[1] != self.params[1]: 210 | return False 211 | if c2.type == "set" and self.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): # a is set and a == b -> a == b 212 | return (self,) 213 | if c2.type == "unset" and self.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): # a is unset and a == b -> impossible 214 | return False 215 | if self.type == "set" and c2.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): 216 | return (c2,) 217 | if self.type == "unset" and c2.type in ("eq", "ne", "regex", "<", "<=", ">", ">="): 218 | return False 219 | 220 | return self, c2 221 | 222 | 223 | def Number(tt): 224 | """ 225 | Wrap float() not to produce exceptions 226 | """ 227 | 228 | try: 229 | return float(tt) 230 | except ValueError: 231 | return 0 232 | -------------------------------------------------------------------------------- /src/styles/default.mapcss: -------------------------------------------------------------------------------- 1 | canvas{fill-color:#a0bc92} 2 | 3 | 4 | /* ---------- DEFAULTS ---------- */ 5 | way { 6 | /* text: name; */text-position: line; text-halo-radius:2; 7 | linecap: round; 8 | opacity: eval( min(1, (num(any(tag("layer"),1))+5)/6) ); 9 | casing-opacity: eval( min(1, (num(any(tag("layer"),1))+5)/6) ); 10 | 11 | } 12 | 13 | 14 | 15 | /* ---------- ROADS ---------- */ 16 | way|z16-[highway] /*[!lanes]*/ 17 | {color:white; 18 | 19 | /*casing-width: 1; */ } 20 | 21 | 22 | /*way|z16-[highway][lanes] 23 | {width: eval(cond(num(tag("lanes")) >= 1, metric("3.6m")-metric("10cm"), 0)); 24 | color:grey; linecap: round; 25 | casing-width: eval(cond(num(tag("lanes")) >= 1, metric("10cm"), 0)); 26 | casing-color:white; casing-dashes: eval(str( metric("1m") ) +"," + str( metric("1m") )); casing-linecap:butt; 27 | offset: eval(cond((num(tag("lanes"))/2-int(num(tag("lanes")))/2) != 0, metric("3.6m")/2, 0)); 28 | 29 | } 30 | {width: eval(cond(num(tag("lanes")) >= 2, metric("3.6m")-metric("10cm"), 0)); 31 | color:grey; linecap: round; 32 | casing-width: eval(cond(num(tag("lanes")) >= 2, metric("10cm"), 0)); 33 | casing-color:white;casing-dashes: eval(str( metric("1m") ) +"," + str( metric("1m") )); casing-linecap:butt; 34 | offset: eval(cond((num(tag("lanes"))/2-int(num(tag("lanes")))/2) != 0, metric("3.6m")/2, 0)+(2-1)*metric("3.6m")); 35 | 36 | } 37 | {width: eval(cond(num(tag("lanes")) >= 3, metric("3.6m")-metric("10cm"), 0)); 38 | color:grey; linecap: round; 39 | casing-width: eval(cond(num(tag("lanes")) >= 3, metric("10cm"), 0)); 40 | casing-color:white;casing-dashes: eval(str( metric("1m") ) +"," + str( metric("1m") )); casing-linecap:butt; 41 | offset: eval((cond((num(tag("lanes"))/2-int(num(tag("lanes")))/2) != 0, metric("3.6m")/2, 0)-(2-1)*metric("3.6m"))); 42 | 43 | } 44 | {width: eval(cond(num(tag("lanes")) >= 4, metric("3.6m")-metric("10cm"), 0)); 45 | color:grey; linecap: round; 46 | casing-width: eval(cond(num(tag("lanes")) >= 4, metric("10cm"), 0)); 47 | casing-color:white;casing-dashes: eval(str( metric("1m") ) +"," + str( metric("1m") )); casing-linecap:butt; 48 | offset: eval(cond((num(tag("lanes"))/2-int(num(tag("lanes")))/2) != 0, metric("3.6m")/2, 0)+(3-1)*metric("3.6m")); 49 | 50 | } 51 | {width: eval(cond(num(tag("lanes")) >= 5, metric("3.6m")-metric("10cm"), 0)); 52 | color:grey; linecap: round; 53 | casing-width: eval(cond(num(tag("lanes")) >= 5, metric("10cm"), 0)); 54 | casing-color:white;casing-dashes: eval(str( metric("1m") ) +"," + str( metric("1m") )); casing-linecap:butt; 55 | offset: eval((cond((num(tag("lanes"))/2-int(num(tag("lanes")))/2) != 0, metric("3.6m")/2, 0)-(3-1)*metric("3.6m"))); 56 | }*/ 57 | 58 | way|z16-[highway=motorway], 59 | way|z16-[highway=trunk], 60 | {width:eval(metric(4*4));} 61 | 62 | 63 | way|z16-[highway=primary] 64 | {width:eval(metric(3*4));} 65 | 66 | way|z16-[highway=secondary], 67 | way|z16-[highway=tertiary], 68 | way|z16-[highway=residential], 69 | way|z16-[highway=trunk_link], 70 | way|z16-[highway=motorway_link], 71 | way|z16-[highway=primary_link], 72 | way|z16-[highway=secondary_link], 73 | {width:eval(metric(2*4));} 74 | 75 | way|z16-[highway=service], 76 | way|z16-[highway=track], 77 | {width:eval(metric(1.5*4));} 78 | 79 | way|z16-[highway=footway], 80 | way|z16-[highway=pedestrian], 81 | way|z16-[highway=path], 82 | way|z16-[highway=steps], 83 | {width:eval(metric(1*4)); 84 | } 85 | 86 | 87 | way[highway][lanes]{width:eval(metric( num(tag("lanes")) * 4));} 88 | way[width]{width:eval(metric(tag("width")));} 89 | 90 | way|z16-[highway=motorway], 91 | way|z16-[highway=motorway_link], 92 | way|z16-[highway=trunk], 93 | way|z16-[highway=trunk_link], 94 | way|z16-[highway=primary], 95 | way|z16-[highway=primary_link], 96 | way|z16-[highway=secondary], 97 | way|z16-[highway=secondary_link], 98 | way|z16-[highway=tertiary], 99 | way|z16-[highway=tertiary_link], 100 | way|z16-[highway=residential], 101 | way|z16-[highway=service], 102 | 103 | {image: "styles/default/asphalt.png"; 104 | z-index:5; 105 | } 106 | 107 | area|z16-[amenity=parking], 108 | {fill-image: "styles/default/asphalt.png"; 109 | z-index:5; casing-width:1; 110 | } 111 | 112 | 113 | way|z16-[highway=footway], 114 | way|z16-[highway=pedestrian], 115 | {image: "styles/default/paving_stones6s.png"; 116 | z-index:4;} 117 | 118 | way|z16-[highway=track], 119 | {image: "styles/default/dirt.png"; 120 | z-index:3;} 121 | 122 | way|z16-[highway=path], 123 | {image: "styles/default/sand.png";z-index:2;} 124 | 125 | 126 | way|z16-[layer<0], 127 | {image: ""} 128 | 129 | 130 | way|z16-[surface=asphalt] {image: "styles/default/asphalt.png"} 131 | 132 | way|z16-[surface=grass] {image: eval("styles/default/"+tag("surface")+".png")} 133 | way|z16-[surface=sand] {image: eval("styles/default/"+tag("surface")+".png")} 134 | way|z16-[surface=dirt] {image: eval("styles/default/"+tag("surface")+".png")} 135 | way|z16-[surface=granite] {color:#655} 136 | way|z16-[surface=paving_stones] {image: styles/default/paving_stones6s.png} 137 | way|z16-[landuse=field] {fill-image: styles/default/field.png} 138 | 139 | way|z17-[barrier] 140 | {casing-width:1; width: eval(metric(tag("width")))} 141 | 142 | way|z-16[highway=residential], 143 | way|z-16[highway=tertiary], 144 | way|z-16[highway=living_street] 145 | {width: 3; color:#ffffff; z-index:10;casing-width: 1;} 146 | 147 | way|z15-16[highway=service][living_street!=yes], 148 | way|z-16[highway=unclassified] 149 | {width: 2.5; color:#ccc; z-index:9;casing-width: 1;} 150 | 151 | 152 | way|z-16[highway=primary], 153 | way|z-16[highway=motorway], 154 | way|z-16[highway=trunk] 155 | {width: 4; color: #ff0; z-index:11;casing-width: 1;} 156 | 157 | way|z-16[highway=primary_link], 158 | way|z-16[highway=motorway_link], 159 | way|z-16[highway=trunk_link] 160 | {width: 3.5; color: #ff0; z-index:11;casing-width: 1;} 161 | 162 | way|z-16[highway=secondary] 163 | {width: 4; color: orange; z-index:10;casing-width: 1;} 164 | 165 | way|z15-16[highway=track] 166 | {width: 3; color:#252; z-index:8;casing-width: 1;} 167 | 168 | way|z15-16[living_street=yes] 169 | {width: 2; z-index: 2;casing-width: 1;} 170 | 171 | 172 | way|z15-16[highway=footway], 173 | way|z15-16[highway=pedestrian], 174 | way|z15-16[highway=path] 175 | {width:eval(max(2, prop("width"))); color:#655; casing-dashes: 3,1; z-index:3;casing-width: 1;} 176 | 177 | way|z15-[highway=steps] 178 | {z-index:5; width:eval(max(2, prop("width"))); color:#655; casing-dashes: 1,0; linecap: butt;casing-width: 1;} 179 | {z-index:6; width:eval(max(2, prop("width"))); dashes: eval("1," + str( max(num(any(num(metric(tag("step:length")))/100, num(metric("0.3m"))))-1, 1) ) ); color: black;casing-width: 1;} 180 | 181 | 182 | 183 | way[bridge=yes] {casing-width:eval(min(3, num(prop("width"))/2 ));casing-linecap:butt} 184 | 185 | 186 | area[highway][area=yes] {fill-color: eval(any(prop("fill-color"),prop("color"))); fill-image: eval(any(prop("fill-image"), prop("image")));} 187 | 188 | 189 | /* ---------- FORESTS ---------- */ 190 | way[natural=forest], 191 | way[natural=wood], 192 | way[landuse=forest], 193 | way[landuse=wood] 194 | {fill-color: #020} 195 | 196 | /* ---------- WATER ---------- */ 197 | way[waterway=riverbank], 198 | way[landuse=reservoir], 199 | way[natural=water] {fill-color: #009} 200 | 201 | way[waterway=river], 202 | way[waterway=stream]{color: #009;width: eval(max(3, metric(tag("width")) ))} 203 | 204 | 205 | 206 | 207 | /* ---------- BUILDINGS ---------- */ 208 | 209 | way|z16-[building] {fill-color: #522; 210 | /*text: addr:housenumber;*/ 211 | text-halo-radius:2; z-index:100; text-position: center; 212 | opacity: 1; 213 | fill-opacity: 1; 214 | extrude: eval(zmetric("3m")*2); 215 | extrude-face-color: #e2e2e2; 216 | extrude-face-opacity: 1; 217 | extrude-edge-width: 1; 218 | extrude-edge-color: #404040; 219 | } 220 | way|z16-[barrier]{raise: eval(zmetric(tag("min_height")));extrude-face-opacity: 0.5} 221 | way|z16-[barrier]{extrude: eval( zmetric(3) - num(prop("raise")) ); } 222 | way|z16-[barrier][height]{extrude: eval( zmetric(tag("height")) - num(prop("raise")) ); } 223 | 224 | 225 | way|z16-[building]{raise: eval( any( zmetric(tag("min_height")), zmetric ( num(tag("building:min_level")) * 3)));} 226 | 227 | way|z16-[building][building:levels]{extrude: eval( zmetric(num(tag("building:levels"))*3) - num(prop("raise")) );} 228 | way|z16-[building][height]{extrude: eval( zmetric(tag("height")) - num(prop("raise")) );} 229 | 230 | 231 | 232 | /* ---------- INDUSTRY ---------- */ 233 | way|z17-[power=line] {width: 1; color:#ccc} 234 | 235 | 236 | way|z10-[landuse=industrial] {fill-color: #855} 237 | way|z10-[landuse=military] {fill-color: pink} 238 | 239 | /* ---------- GARDENS&co ---------- */ 240 | way|z13-[landuse=allotments] {fill-color: #452; opacity: 0.8} 241 | way|z10-[landuse=field] {fill-color: #CCCC4E; opacity: 0.8} 242 | 243 | /* ---------- GRASS ---------- */ 244 | 245 | way|z16-[landuse=residential], 246 | {fill-color: #cceecc; opacity: 0.8; fill-image: styles/default/grass.png; } 247 | way|z16-[landuse=grass], 248 | way|z16-[natural=grass] 249 | {fill-color: #cceecc; opacity: 0.8; fill-image: styles/default/grass.png; z-index: 6} 250 | 251 | 252 | /* ---------- AMENITIES ---------- */ 253 | way|z15-[amenity=parking] {icon-image: styles/default/parking.png; } 254 | 255 | way[amenity=bench] {extrude: eval(zmetric("0.6m")); width:eval(metric("0.5m")); extrude-face-opacity:0;extrude-edge-color:black;color:brown;opacity:0.75} 256 | {offset:eval(metric("0.25m")); extrude-face-color:brown;extrude:eval(zmetric("1.2m"));extrude-face-opacity: 0.5} 257 | 258 | /* ---------- BORDERS ---------- */ 259 | /*line[boundary=administrative] {width: 2; color: red; dashes:5,3; z-index:15} 260 | area|z-17[boundary=administrative] {text: name; text-position:center}*/ 261 | -------------------------------------------------------------------------------- /src/mapcss/StyleChooser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | 19 | from Rule import Rule 20 | from webcolors.webcolors import whatever_to_cairo as colorparser 21 | from webcolors.webcolors import cairo_to_hex 22 | from Eval import Eval 23 | 24 | 25 | def make_nice_style(r): 26 | ra = {} 27 | for a, b in r.iteritems(): 28 | "checking and nicifying style table" 29 | if type(b) == type(Eval()): 30 | ra[a] = b 31 | elif "color" in a: 32 | "parsing color value to 3-tuple" 33 | # print "res:", b 34 | if b and (type(b) != tuple): 35 | # if not b: 36 | # print sl, ftype, tags, zoom, scale, zscale 37 | # else: 38 | ra[a] = colorparser(b) 39 | elif b: 40 | ra[a] = b 41 | elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")): 42 | "these things are float's or not in table at all" 43 | try: 44 | ra[a] = float(b) 45 | except ValueError: 46 | pass 47 | elif "dashes" in a and type(b) != list: 48 | "these things are arrays of float's or not in table at all" 49 | try: 50 | b = b.split(",") 51 | b = [float(x) for x in b] 52 | ra[a] = b 53 | except ValueError: 54 | ra[a] = [] 55 | else: 56 | ra[a] = b 57 | return ra 58 | 59 | 60 | class StyleChooser: 61 | """ 62 | A StyleChooser object is equivalent to one CSS selector+declaration. 63 | 64 | Its ruleChains property is an array of all the selectors, which would 65 | traditionally be comma-separated. For example: 66 | h1, h2, h3 em 67 | is three ruleChains. 68 | 69 | Each ruleChain is itself an array of nested selectors. So the above 70 | example would roughly be encoded as: 71 | [[h1],[h2],[h3,em]] 72 | ^^ ^^ ^^ ^^ each of these is a Rule 73 | 74 | The styles property is an array of all the style objects to be drawn 75 | if any of the ruleChains evaluate to true. 76 | """ 77 | def __repr__(self): 78 | return "{(%s) : [%s] }\n" % (self.ruleChains, self.styles) 79 | 80 | def __init__(self, scalepair): 81 | self.ruleChains = [] 82 | self.styles = [] 83 | self.eval_type = type(Eval()) 84 | self.scalepair = scalepair 85 | self.selzooms = None 86 | self.compatible_types = set() 87 | self.has_evals = False 88 | 89 | def get_numerics(self): 90 | """ 91 | Returns a set of number-compared values. 92 | """ 93 | a = set() 94 | for r in self.ruleChains: 95 | a.update(r.get_numerics()) 96 | a.discard(False) 97 | return a 98 | 99 | def get_interesting_tags(self, ztype, zoom): 100 | """ 101 | Returns a set of tags that were used in here. 102 | """ 103 | ### FIXME 104 | a = set() 105 | for r in self.ruleChains: 106 | a.update(r.get_interesting_tags(ztype, zoom)) 107 | if a: # FIXME: semi-illegal optimization, may wreck in future on tagless matches 108 | for r in self.styles: 109 | for c, b in r.iteritems(): 110 | if type(b) == self.eval_type: 111 | a.update(b.extract_tags()) 112 | return a 113 | 114 | def get_sql_hints(self, type, zoom): 115 | """ 116 | Returns a set of tags that were used in here in form of SQL-hints. 117 | """ 118 | a = set() 119 | b = "" 120 | needed = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text"]) 121 | 122 | if not needed.isdisjoint(set(self.styles[0].keys())): 123 | for r in self.ruleChains: 124 | p = r.get_sql_hints(type, zoom) 125 | if p: 126 | q = "(" + p[1] + ")" # [t[1] for t in p] 127 | if q == "()": 128 | q = "" 129 | if b and q: 130 | b += " OR " + q 131 | else: 132 | b = q 133 | a.update(p[0]) 134 | # no need to check for eval's 135 | return a, b 136 | 137 | def updateStyles(self, sl, ftype, tags, zoom, scale, zscale): 138 | # Are any of the ruleChains fulfilled? 139 | if self.selzooms: 140 | if zoom < self.selzooms[0] or zoom > self.selzooms[1]: 141 | return sl 142 | 143 | #if ftype not in self.compatible_types: 144 | # return sl 145 | 146 | object_id = self.testChain(self.ruleChains, ftype, tags, zoom) 147 | 148 | if not object_id: 149 | return sl 150 | 151 | w = 0 152 | 153 | for r in self.styles: 154 | if self.has_evals: 155 | ra = {} 156 | for a, b in r.iteritems(): 157 | "calculating eval()'s" 158 | if type(b) == self.eval_type: 159 | combined_style = {} 160 | for t in sl: 161 | combined_style.update(t) 162 | for p, q in combined_style.iteritems(): 163 | if "color" in p: 164 | combined_style[p] = cairo_to_hex(q) 165 | b = b.compute(tags, combined_style, scale, zscale) 166 | ra[a] = b 167 | #r = ra 168 | ra = make_nice_style(ra) 169 | else: 170 | ra = r.copy() 171 | 172 | ra["object-id"] = str(object_id) 173 | hasall = False 174 | allinit = {} 175 | for x in sl: 176 | if x.get("object-id") == "::*": 177 | allinit = x.copy() 178 | if ra["object-id"] == "::*": 179 | oid = x.get("object-id") 180 | x.update(ra) 181 | x["object-id"] = oid 182 | if oid == "::*": 183 | hasall = True 184 | if x.get("object-id") == ra["object-id"]: 185 | x.update(ra) 186 | break 187 | else: 188 | if not hasall: 189 | allinit.update(ra) 190 | sl.append(allinit) 191 | return sl 192 | 193 | def testChain(self, chain, obj, tags, zoom): 194 | """ 195 | Tests an object against a chain 196 | """ 197 | for r in chain: 198 | tt = r.test(obj, tags, zoom) 199 | if tt: 200 | return tt 201 | return False 202 | 203 | def newGroup(self): 204 | """ 205 | starts a new ruleChain in this.ruleChains 206 | """ 207 | pass 208 | 209 | def newObject(self, e=''): 210 | """ 211 | adds into the current ruleChain (starting a new Rule) 212 | """ 213 | rule = Rule(e) 214 | rule.minZoom = float(self.scalepair[0]) 215 | rule.maxZoom = float(self.scalepair[1]) 216 | self.ruleChains.append(rule) 217 | 218 | def addZoom(self, z): 219 | """ 220 | adds into the current ruleChain (existing Rule) 221 | """ 222 | self.ruleChains[-1].minZoom = float(z[0]) 223 | self.ruleChains[-1].maxZoom = float(z[1]) 224 | 225 | def addCondition(self, c): 226 | """ 227 | adds into the current ruleChain (existing Rule) 228 | """ 229 | self.ruleChains[-1].conditions.append(c) 230 | 231 | def addStyles(self, a): 232 | """ 233 | adds to this.styles 234 | """ 235 | for r in self.ruleChains: 236 | if not self.selzooms: 237 | self.selzooms = [r.minZoom, r.maxZoom] 238 | else: 239 | self.selzooms[0] = min(self.selzooms[0], r.minZoom) 240 | self.selzooms[1] = max(self.selzooms[1], r.maxZoom) 241 | self.compatible_types.update(r.get_compatible_types()) 242 | rb = [] 243 | for r in a: 244 | ra = {} 245 | for a, b in r.iteritems(): 246 | a = a.strip() 247 | b = b.strip() 248 | if a == "casing-width": 249 | "josm support" 250 | if b[0] == "+": 251 | try: 252 | b = str(float(b) / 2) 253 | except: 254 | pass 255 | if "text" == a[-4:]: 256 | if b[:5] != "eval(": 257 | b = "eval(tag(\"" + b + "\"))" 258 | if b[:5] == "eval(": 259 | b = Eval(b) 260 | self.has_evals = True 261 | ra[a] = b 262 | ra = make_nice_style(ra) 263 | rb.append(ra) 264 | self.styles = self.styles + rb 265 | -------------------------------------------------------------------------------- /src/test_stylesheet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from mapcss import MapCSS 4 | import mapcss.webcolors 5 | whatever_to_hex = mapcss.webcolors.webcolors.whatever_to_hex 6 | import sys 7 | 8 | reload(sys) 9 | sys.setdefaultencoding("utf-8") 10 | 11 | minzoom = 0 12 | maxzoom = 19 13 | 14 | style = MapCSS(minzoom, maxzoom) 15 | style.parse(filename=sys.argv[1], clamp=False) 16 | TOTAL_TESTS = 0 17 | FAILED_TESTS = 0 18 | 19 | 20 | def get_color_lightness(c): 21 | if c == 0: 22 | return 0 23 | return int((30. * c[0] + 15. * c[2] + 45. * c[1]) / 6.) 24 | 25 | 26 | def renderable(a): 27 | return any([any([y in ["width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-color", "pattern-image", "shield-text"] for y in x if x[y]]) for x in a]) 28 | 29 | 30 | def is_default(x): 31 | return x.get('object-id') == '::default' 32 | 33 | 34 | def compare_order(a, function, b): 35 | "a is over b on all zooms" 36 | global TOTAL_TESTS, FAILED_TESTS 37 | z_offset = {"top": 10000, "bottom": -10000} 38 | for zoom in range(minzoom, maxzoom + 1): 39 | for typ1 in ['line', 'node', 'area']: 40 | for typ2 in ['line', 'node', 'area']: 41 | sa = [x.get('z-index', 0.) + z_offset.get(x.get('-x-kot-layer'), 0) for x in style.get_style(typ1, a, zoom) if renderable([x]) and is_default(x)] 42 | sb = [x.get('z-index', 0.) + z_offset.get(x.get('-x-kot-layer'), 0) for x in style.get_style(typ2, b, zoom) if renderable([x]) and is_default(x)] 43 | if sa and sb: 44 | mia = min(sa) 45 | mab = max(sb) 46 | TOTAL_TESTS += 1 47 | if (function == "over") and (mia <= mab): 48 | print "ORDER: z%s\t[%s %s %s %s %s]\t[%s, %s], " % (zoom, typ1, mia, function, typ2, mab, repr(a), repr(b)) 49 | print style.get_style(typ1, a, zoom) 50 | print style.get_style(typ2, b, zoom) 51 | FAILED_TESTS += 1 52 | 53 | 54 | def compare_line_lightness(a, function, b): 55 | "a darker than b on all zooms" 56 | global TOTAL_TESTS, FAILED_TESTS 57 | for zoom in range(minzoom, maxzoom + 1): 58 | for typ1 in ['line', 'node', 'area']: 59 | for typ2 in ['line', 'node', 'area']: 60 | sa = [get_color_lightness(x.get('color', 0.)) for x in style.get_style(typ1, a, zoom) if x.get("width", 0) > 0] 61 | sb = [get_color_lightness(x.get('color', 0.)) for x in style.get_style(typ2, b, zoom) if x.get("width", 0) > 0] 62 | if sa and sb: 63 | mia = min(sa) 64 | mab = max(sb) 65 | TOTAL_TESTS += 1 66 | if (function == "darker") and (mia >= mab): 67 | print "LIGHT: z%s\t[%s %s %s %s %s]\t[%s, %s], " % (zoom, typ1, mia, function, typ2, mab, repr(a), repr(b)) 68 | FAILED_TESTS += 1 69 | 70 | 71 | def compare_visibility(a, function, b): 72 | "a is visible with b on all zooms" 73 | global TOTAL_TESTS, FAILED_TESTS 74 | for zoom in range(minzoom, maxzoom + 1): 75 | for typ in ['line', 'node', 'area']: 76 | sa = [x.get('z-index', 0.) for x in style.get_style(typ, a, zoom) if x] 77 | sb = [x.get('z-index', 0.) for x in style.get_style(typ, b, zoom) if x] 78 | if sa or sb: 79 | TOTAL_TESTS += 1 80 | if (function == "both") and not ((sa) and (sb)): 81 | print "VISIBILITY: z%s\t[%s %s %s %s %s]\t[%s, %s], " % (zoom, typ, bool(sa), function, typ, bool(sb), repr(a), repr(b)) 82 | FAILED_TESTS += 1 83 | 84 | 85 | def has_stable_labels(a): 86 | "a has labels that don't appear-diasppear-appear on zoom-in" 87 | global TOTAL_TESTS, FAILED_TESTS 88 | prev = {"line": False, "node": False, "area": False} 89 | for zoom in range(minzoom, maxzoom + 1): 90 | for typ in ['line', 'node', 'area']: 91 | sa = any(["text" in x for x in style.get_style(typ, a, zoom)]) 92 | sb = prev[typ] 93 | if sa or sb: 94 | TOTAL_TESTS += 1 95 | if sb and not sa: 96 | print "LABELS: %s|z%s\t[%s]" % (typ, zoom, repr(a)) 97 | FAILED_TESTS += 1 98 | else: 99 | prev[typ] = sa 100 | 101 | 102 | def has_darker_casings(a): 103 | "a has casings that are darker than the line itself" 104 | global TOTAL_TESTS, FAILED_TESTS 105 | for zoom in range(minzoom, maxzoom + 1): 106 | for typ in ['line', 'node', 'area']: 107 | sa = [x for x in style.get_style(typ, a, zoom) if ("width" in x and "casing-width" in x)] 108 | 109 | if sa: 110 | TOTAL_TESTS += 1 111 | for x in sa: 112 | light_color = get_color_lightness(x.get('color', 0.)) 113 | light_casing = get_color_lightness(x.get('casing-color', 0.)) 114 | if light_color != (light_casing + 2): 115 | print "CASINGS: %s|z%s\t[%s], base: %x (%s) casing: %x (%s)" % (typ, zoom, repr(a), light_color, x.get('width'), light_casing, x.get('casing-width')) 116 | FAILED_TESTS += 1 117 | 118 | compare_order({'area:highway': 'primary'}, "over", {'highway': 'primary'}) 119 | 120 | compare_order({'highway': 'primary'}, "over", {'waterway': 'river'}) 121 | compare_order({'highway': 'primary'}, "over", {'waterway': 'canal'}) 122 | compare_order({'highway': 'path'}, "over", {'waterway': 'river'}) 123 | compare_order({"highway": "motorway"}, "over", {'highway': 'primary'}) 124 | compare_line_lightness({"highway": "motorway"}, "darker", {'highway': 'primary'}) 125 | 126 | compare_order({"highway": "motorway_link"}, "over", {'highway': 'primary_link'}) 127 | compare_line_lightness({"highway": "motorway_link"}, "darker", {'highway': 'primary_link'}) 128 | compare_order({"highway": "trunk"}, "over", {'highway': 'primary'}) 129 | compare_line_lightness({"highway": "trunk"}, "darker", {'highway': 'primary'}) 130 | compare_order({"highway": "trunk_link"}, "over", {'highway': 'primary_link'}) 131 | compare_order({'highway': 'primary'}, "over", {'highway': 'residential'}) 132 | compare_order({'highway': 'primary'}, "over", {'highway': 'secondary'}) 133 | compare_order({'highway': 'primary_link'}, "over", {'highway': 'secondary_link'}) 134 | compare_order({'highway': 'secondary'}, "over", {'highway': 'tertiary'}) 135 | compare_order({'highway': 'secondary_link'}, "over", {'highway': 'tertiary_link'}) 136 | compare_order({'highway': 'tertiary'}, "over", {'highway': 'residential'}) 137 | compare_order({'highway': 'tertiary'}, "over", {'highway': 'service'}) 138 | compare_order({'highway': 'tertiary'}, "over", {'highway': 'unclassified'}) 139 | 140 | compare_order({'highway': 'tertiary'}, "over", {"highway": "road"}) 141 | compare_order({'highway': 'residential'}, "over", {'highway': "track"}) 142 | compare_order({'highway': 'residential'}, "over", {'highway': "service"}) 143 | compare_order({'highway': 'residential'}, "over", {"highway": "living_street"}) 144 | compare_order({'highway': 'unclassified'}, "over", {'highway': "track"}) 145 | compare_order({'highway': 'unclassified'}, "over", {'highway': "construction"}) 146 | compare_order({'highway': 'residential'}, "over", {'highway': "path", "bicycle": "yes"}) 147 | compare_order({'highway': 'track'}, "over", {'highway': "path"}) 148 | compare_order({"highway": "steps"}, "over", {'highway': "pedestrian"}) 149 | compare_order({"highway": "steps"}, "over", {'highway': "cycleway"}) 150 | compare_order({"highway": "service"}, "over", {'highway': "footway"}) 151 | compare_order({"highway": "service"}, "over", {'highway': "path"}) 152 | 153 | 154 | compare_order({"highway": "service"}, "over", {'building': "yes"}) 155 | 156 | compare_order({"railway": "rail"}, "over", {"waterway": "riverbank"}) 157 | 158 | compare_order({"amenity": "cafe"}, "over", {'amenity': "parking"}) 159 | compare_order({"amenity": "bank"}, "over", {'amenity': "atm"}) 160 | compare_order({"amenity": "bank"}, "over", {'amenity': "atm"}) 161 | compare_order({"railway": "station"}, "over", {'leisure': "park"}) 162 | compare_order({"railway": "station"}, "over", {"highway": "bus_stop"}) 163 | compare_order({"highway": "tertiary"}, "over", {"highway": "bus_stop"}) 164 | compare_order({"highway": "secondary"}, "over", {"highway": "bus_stop"}) 165 | compare_order({"highway": "bus_stop"}, "over", {"amenity": "police"}) 166 | compare_order({"place": "suburb"}, "over", {'leisure': "park"}) 167 | 168 | compare_order({"highway": "path"}, "over", {'man_made': "cut_line"}) 169 | compare_order({"highway": "footway"}, "over", {'man_made': "cut_line"}) 170 | compare_order({"highway": "motorway"}, "over", {'man_made': "cut_line"}) 171 | 172 | 173 | compare_visibility({"highway": "primary"}, "both", {'highway': 'primary_link'}) 174 | compare_visibility({"highway": "primary"}, "both", {'highway': 'trunk_link'}) 175 | compare_visibility({"highway": "secondary"}, "both", {'highway': 'secondary_link'}) 176 | compare_visibility({"highway": "secondary"}, "both", {'highway': 'primary_link'}) 177 | compare_visibility({"highway": "tertiary"}, "both", {'highway': 'tertiary_link'}) 178 | 179 | has_stable_labels({"highway": "trunk", "name": "name", "int_name": "int_name"}) 180 | has_stable_labels({"highway": "motorway", "name": "name", "int_name": "int_name"}) 181 | has_stable_labels({"highway": "primary", "name": "name", "int_name": "int_name"}) 182 | has_stable_labels({"highway": "secondary", "name": "name", "int_name": "int_name"}) 183 | has_stable_labels({"highway": "tertiary", "name": "name", "int_name": "int_name"}) 184 | has_stable_labels({"highway": "residential", "name": "name", "int_name": "int_name"}) 185 | has_stable_labels({"highway": "unclassified", "name": "name", "int_name": "int_name"}) 186 | 187 | has_darker_casings({'highway': 'motorway'}) 188 | has_darker_casings({'highway': 'motorway_link'}) 189 | has_darker_casings({'highway': 'trunk'}) 190 | has_darker_casings({'highway': 'trunk_link'}) 191 | has_darker_casings({'highway': 'primary'}) 192 | has_darker_casings({'highway': 'primary_link'}) 193 | has_darker_casings({'highway': 'secondary'}) 194 | has_darker_casings({'highway': 'secondary_link'}) 195 | has_darker_casings({'highway': 'tertiary'}) 196 | has_darker_casings({'highway': 'tertiary_link'}) 197 | has_darker_casings({'highway': 'residential'}) 198 | has_darker_casings({'highway': 'unclassified'}) 199 | 200 | 201 | print "Failed tests: %s (%s%%)" % (FAILED_TESTS, 100 * FAILED_TESTS / TOTAL_TESTS) 202 | print "Passed tests:", TOTAL_TESTS - FAILED_TESTS 203 | print "Total tests:", TOTAL_TESTS 204 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/README.txt: -------------------------------------------------------------------------------- 1 | Web colors 2 | ========== 3 | 4 | A simple library for working with the color names and color codes 5 | defined by the HTML and CSS specifications. 6 | 7 | 8 | An overview of HTML and CSS colors 9 | ---------------------------------- 10 | 11 | Colors on the Web are specified in `the sRGB color space`_, where each 12 | color is made up of a red component, a green component and a blue 13 | component. This is useful because it maps (fairly) cleanly to the red, 14 | green and blue components of pixels on a computer display, and to the 15 | cone cells of a human eye, which come in three sets roughly 16 | corresponding to the wavelengths of light associated with red, green 17 | and blue. 18 | 19 | `The HTML 4 standard`_ defines two ways to specify sRGB colors: 20 | 21 | * A hash mark ('#') followed by three pairs of hexdecimal digits, 22 | specifying values for red, green and blue components in that order; 23 | for example, ``#0099cc``. Since each pair of hexadecimal digits can 24 | express 256 different values, this allows up to 256**3 or 16,777,216 25 | unique colors to be specified (though, due to differences in display 26 | technology, not all of these colors may be clearly distinguished on 27 | any given physical display). 28 | 29 | * A set of predefined color names which correspond to specific 30 | hexadecimal values; for example, ``white``. HTML 4 defines sixteen 31 | such colors. 32 | 33 | `The CSS 2 standard`_ allows any valid HTML 4 color specification, and 34 | adds three new ways to specify sRGB colors: 35 | 36 | * A hash mark followed by three hexadecimal digits, which is expanded 37 | into three hexadecimal pairs by repeating each digit; thus ``#09c`` 38 | is equivalent to ``#0099cc``. 39 | 40 | * The string 'rgb', followed by parentheses, between which are three 41 | numeric values each between 0 and 255, inclusive, which are taken to 42 | be the values of the red, green and blue components in that order; 43 | for example, ``rgb(0, 153, 204)``. 44 | 45 | * The same as above, except using percentages instead of numeric 46 | values; for example, ``rgb(0%, 60%, 80%)``. 47 | 48 | `The CSS 2.1 revision`_ does not add any new methods of specifying 49 | sRGB colors, but does add one additional named color. 50 | 51 | `The CSS 3 color module`_ (currently a W3C Candidate Recommendation) 52 | adds one new way to specify sRGB colors: 53 | 54 | * A hue-saturation-lightness triple (HSL), using the construct 55 | ``hsl()``. 56 | 57 | It also adds support for variable opacity of colors, by allowing the 58 | specification of alpha-channel information, through the ``rgba()`` and 59 | ``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()`` 60 | with one exception: a fourth value is supplied, indicating the level 61 | of opacity from ``0.0`` (completely transparent) to ``1.0`` 62 | (completely opaque). Though not technically a color, the keyword 63 | ``transparent`` is also made available in lieu of a color value, and 64 | corresponds to ``rgba(0,0,0,0)``. 65 | 66 | Additionally, CSS3 defines a new set of color names; this set is taken 67 | directly from the named colors defined for SVG (Scalable Vector 68 | Graphics) markup, and is a proper superset of the named colors defined 69 | in CSS 2.1. This set also has significant overlap with traditional X11 70 | color sets as defined by the ``rgb.txt`` file on many Unix and 71 | Unix-like operating systems, though the correspondence is not exact; 72 | the set of X11 colors is not standardized, and the set of CSS3 colors 73 | contains some definitions which diverge significantly from customary 74 | X11 definitions (for example, CSS3's ``green`` is not equivalent to 75 | X11's ``green``; the value which X11 designates ``green`` is 76 | designated ``lime`` in CSS3). 77 | 78 | .. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB 79 | .. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5 80 | .. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color 81 | .. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/ 82 | .. _The CSS 3 color module: http://www.w3.org/TR/css3-color/ 83 | 84 | What this module supports 85 | ------------------------- 86 | 87 | The mappings and functions within this module support the following 88 | methods of specifying sRGB colors, and conversions between them: 89 | 90 | * Six-digit hexadecimal. 91 | 92 | * Three-digit hexadecimal. 93 | 94 | * Integer ``rgb()`` triplet. 95 | 96 | * Percentage ``rgb()`` triplet. 97 | 98 | * Varying selections of predefined color names (see below). 99 | 100 | This module does not support ``hsl()`` triplets, nor does it support 101 | opacity/alpha-channel information via ``rgba()`` or ``hsla()``. 102 | 103 | If you need to convert between RGB-specified colors and HSL-specified 104 | colors, or colors specified via other means, consult `the colorsys 105 | module`_ in the Python standard library, which can perform conversions 106 | amongst several common color spaces. 107 | 108 | .. _the colorsys module: http://docs.python.org/library/colorsys.html 109 | 110 | Normalization 111 | ------------- 112 | 113 | For colors specified via hexadecimal values, this module will accept 114 | input in the following formats: 115 | 116 | * A hash mark (#) followed by three hexadecimal digits, where letters 117 | may be upper- or lower-case. 118 | 119 | * A hash mark (#) followed by six hexadecimal digits, where letters 120 | may be upper- or lower-case. 121 | 122 | For output which consists of a color specified via hexadecimal values, 123 | and for functions which perform intermediate conversion to hexadecimal 124 | before returning a result in another format, this module always 125 | normalizes such values to the following format: 126 | 127 | * A hash mark (#) followed by six hexadecimal digits, with letters 128 | forced to lower-case. 129 | 130 | The function ``normalize_hex()`` in this module can be used to perform 131 | this normalization manually if desired; see its documentation for an 132 | explanation of the normalization process. 133 | 134 | For colors specified via predefined names, this module will accept 135 | input in the following formats: 136 | 137 | * An entirely lower-case name, such as ``aliceblue``. 138 | 139 | * A name using initial capitals, such as ``AliceBlue``. 140 | 141 | For output which consists of a color specified via a predefined name, 142 | and for functions which perform intermediate conversion to a 143 | predefined name before returning a result in another format, this 144 | module always normalizes such values to be entirely lower-case. 145 | 146 | Mappings of color names 147 | ----------------------- 148 | 149 | For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS 150 | 3 -- this module exports two mappings: one of normalized color names 151 | to normalized hexadecimal values, and one of normalized hexadecimal 152 | values to normalized color names. These eight mappings are as follows: 153 | 154 | ``html4_names_to_hex`` 155 | Mapping of normalized HTML 4 color names to normalized hexadecimal 156 | values. 157 | 158 | ``html4_hex_to_names`` 159 | Mapping of normalized hexadecimal values to normalized HTML 4 160 | color names. 161 | 162 | ``css2_names_to_hex`` 163 | Mapping of normalized CSS 2 color names to normalized hexadecimal 164 | values. Because CSS 2 defines the same set of named colors as HTML 165 | 4, this is merely an alias for ``html4_names_to_hex``. 166 | 167 | ``css2_hex_to_names`` 168 | Mapping of normalized hexadecimal values to normalized CSS 2 color 169 | nams. For the reasons described above, this is merely an alias for 170 | ``html4_hex_to_names``. 171 | 172 | ``css21_names_to_hex`` 173 | Mapping of normalized CSS 2.1 color names to normalized 174 | hexadecimal values. This is identical to ``html4_names_to_hex``, 175 | except for one addition: ``orange``. 176 | 177 | ``css21_hex_to_names`` 178 | Mapping of normalized hexadecimal values to normalized CSS 2.1 179 | color names. As above, this is identical to ``html4_hex_to_names`` 180 | except for the addition of ``orange``. 181 | 182 | ``css3_names_to_hex`` 183 | Mapping of normalized CSS3 color names to normalized hexadecimal 184 | values. 185 | 186 | ``css3_hex_to_names`` 187 | Mapping of normalized hexadecimal values to normalized CSS3 color 188 | names. 189 | 190 | Normalization functions 191 | ----------------------- 192 | 193 | ``normalize_hex(hex_value)`` 194 | Normalize a hexadecimal color value to the following form and 195 | return the result:: 196 | 197 | #[a-f0-9]{6} 198 | 199 | In other words, the following transformations are applied as 200 | needed: 201 | 202 | * If the value contains only three hexadecimal digits, it is 203 | expanded to six. 204 | 205 | * The value is normalized to lower-case. 206 | 207 | If the supplied value cannot be interpreted as a hexadecimal color 208 | value, ``ValueError`` is raised. 209 | 210 | Example: 211 | 212 | >>> normalize_hex('#09c') 213 | '#0099cc' 214 | 215 | Conversions from named colors 216 | ----------------------------- 217 | 218 | ``name_to_hex(name, spec='css3')`` 219 | Convert a color name to a normalized hexadecimal color value. 220 | 221 | The optional keyword argument ``spec`` determines which 222 | specification's list of color names will be used; valid values are 223 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 224 | ``css3``. 225 | 226 | The color name will be normalized to lower-case before being 227 | looked up, and when no color of that name exists in the given 228 | specification, ``ValueError`` is raised. 229 | 230 | Example: 231 | 232 | >>> name_to_hex('deepskyblue') 233 | '#00bfff' 234 | 235 | ``name_to_rgb(name, spec='css3')`` 236 | Convert a color name to a 3-tuple of integers suitable for use in 237 | an ``rgb()`` triplet specifying that color. 238 | 239 | The optional keyword argument ``spec`` determines which 240 | specification's list of color names will be used; valid values are 241 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 242 | ``css3``. 243 | 244 | The color name will be normalized to lower-case before being 245 | looked up, and when no color of that name exists in the given 246 | specification, ``ValueError`` is raised. 247 | 248 | Example: 249 | 250 | >>> name_to_rgb('navy') 251 | (0, 0, 128) 252 | 253 | ``name_to_rgb_percent(name, spec='css3')`` 254 | Convert a color name to a 3-tuple of percentages suitable for use 255 | in an ``rgb()`` triplet specifying that color. 256 | 257 | The optional keyword argument ``spec`` determines which 258 | specification's list of color names will be used; valid values are 259 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 260 | ``css3``. 261 | 262 | The color name will be normalized to lower-case before being 263 | looked up, and when no color of that name exists in the given 264 | specification, ``ValueError`` is raised. 265 | 266 | Example: 267 | 268 | >>> name_to_rgb_percent('navy') 269 | ('0%', '0%', '50%') 270 | 271 | 272 | Conversions from hexadecimal values 273 | ----------------------------------- 274 | 275 | ``hex_to_name(hex_value, spec='css3')`` 276 | Convert a hexadecimal color value to its corresponding normalized 277 | color name, if any such name exists. 278 | 279 | The optional keyword argument ``spec`` determines which 280 | specification's list of color names will be used; valid values are 281 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 282 | ``css3``. 283 | 284 | The hexadecimal value will be normalized before being looked up, 285 | and when no color name for the value is found in the given 286 | specification, ``ValueError`` is raised. 287 | 288 | Example: 289 | 290 | >>> hex_to_name('#000080') 291 | 'navy' 292 | 293 | ``hex_to_rgb(hex_value)`` 294 | Convert a hexadecimal color value to a 3-tuple of integers 295 | suitable for use in an ``rgb()`` triplet specifying that color. 296 | 297 | The hexadecimal value will be normalized before being converted. 298 | 299 | Example: 300 | 301 | >>> hex_to_rgb('#000080') 302 | (0, 0, 128) 303 | 304 | ``hex_to_rgb_percent(hex_value)`` 305 | Convert a hexadecimal color value to a 3-tuple of percentages 306 | suitable for use in an ``rgb()`` triplet representing that color. 307 | 308 | The hexadecimal value will be normalized before converting. 309 | 310 | Example: 311 | 312 | >>> hex_to_rgb_percent('#ffffff') 313 | ('100%', '100%', '100%') 314 | 315 | 316 | Conversions from integer rgb() triplets 317 | --------------------------------------- 318 | 319 | ``rgb_to_name(rgb_triplet, spec='css3')`` 320 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 321 | color triplet, to its corresponding normalized color name, if any 322 | such name exists. 323 | 324 | The optional keyword argument ``spec`` determines which 325 | specification's list of color names will be used; valid values are 326 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 327 | ``css3``. 328 | 329 | If there is no matching name, ``ValueError`` is raised. 330 | 331 | Example: 332 | 333 | >>> rgb_to_name((0, 0, 0)) 334 | 'black' 335 | 336 | ``rgb_to_hex(rgb_triplet)`` 337 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 338 | color triplet, to a normalized hexadecimal value for that color. 339 | 340 | Example: 341 | 342 | >>> rgb_to_hex((255, 255, 255)) 343 | '#ffffff' 344 | 345 | ``rgb_to_rgb_percent(rgb_triplet)`` 346 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 347 | color triplet, to a 3-tuple of percentages suitable for use in 348 | representing that color. 349 | 350 | This function makes some trade-offs in terms of the accuracy of 351 | the final representation; for some common integer values, 352 | special-case logic is used to ensure a precise result (e.g., 353 | integer 128 will always convert to '50%', integer 32 will always 354 | convert to '12.5%'), but for all other values a standard Python 355 | ``float`` is used and rounded to two decimal places, which may 356 | result in a loss of precision for some values. 357 | 358 | Examples: 359 | 360 | >>> rgb_to_rgb_percent((255, 255, 255)) 361 | ('100%', '100%', '100%') 362 | >>> rgb_to_rgb_percent((0, 0, 128)) 363 | ('0%', '0%', '50%') 364 | >>> rgb_to_rgb_percent((33, 56, 192)) 365 | ('12.94%', '21.96%', '75.29%') 366 | >>> rgb_to_rgb_percent((64, 32, 16)) 367 | ('25%', '12.5%', '6.25%') 368 | 369 | 370 | Conversions from percentage rgb() triplets 371 | ------------------------------------------ 372 | 373 | ``rgb_percent_to_name(rgb_percent_triplet, spec='css3')`` 374 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 375 | color triplet, to its corresponding normalized color name, if any 376 | such name exists. 377 | 378 | The optional keyword argument ``spec`` determines which 379 | specification's list of color names will be used; valid values are 380 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 381 | ``css3``. 382 | 383 | If there is no matching name, ``ValueError`` is raised. 384 | 385 | Example: 386 | 387 | >>> rgb_percent_to_name(('0%', '0%', '50%')) 388 | 'navy' 389 | 390 | ``rgb_percent_to_hex(rgb_percent_triplet)`` 391 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 392 | color triplet, to a normalized hexadecimal color value for that 393 | color. 394 | 395 | Example: 396 | 397 | >>> rgb_percent_to_hex(('100%', '100%', '0%')) 398 | '#ffff00' 399 | 400 | ``rgb_percent_to_rgb(rgb_percent_triplet)`` 401 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 402 | color triplet, to a 3-tuple of integers suitable for use in 403 | representing that color. 404 | 405 | Some precision may be lost in this conversion. See the note 406 | regarding precision for ``rgb_to_rgb_percent()`` for details; 407 | generally speaking, the following is true for any 3-tuple ``t`` of 408 | integers in the range 0...255 inclusive:: 409 | 410 | t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) 411 | 412 | Examples: 413 | 414 | >>> rgb_percent_to_rgb(('100%', '100%', '100%')) 415 | (255, 255, 255) 416 | >>> rgb_percent_to_rgb(('0%', '0%', '50%')) 417 | (0, 0, 128) 418 | >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%')) 419 | (64, 32, 16) 420 | >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%')) 421 | (33, 56, 192) 422 | -------------------------------------------------------------------------------- /src/mapcss/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | import re 19 | import os 20 | import logging 21 | from hashlib import md5 22 | 23 | from copy import copy, deepcopy 24 | 25 | 26 | from StyleChooser import StyleChooser 27 | from Condition import Condition 28 | 29 | 30 | NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"]) 31 | 32 | 33 | WHITESPACE = re.compile(r'^ \s+ ', re.S | re.X) 34 | 35 | COMMENT = re.compile(r'^ \/\* .+? \*\/ \s* ', re.S | re.X) 36 | CLASS = re.compile(r'^ ([\.:]:?[*\w]+) \s* ', re.S | re.X) 37 | #NOT_CLASS = re.compile(r'^ !([\.:]\w+) \s* ', re.S | re.X) 38 | ZOOM = re.compile(r'^ \| \s* z([\d\-]+) \s* ', re.I | re.S | re.X) 39 | GROUP = re.compile(r'^ , \s* ', re.I | re.S | re.X) 40 | CONDITION = re.compile(r'^ \[(.+?)\] \s* ', re.S | re.X) 41 | OBJECT = re.compile(r'^ (\*|[\w]+) \s* ', re.S | re.X) 42 | DECLARATION = re.compile(r'^ \{(.+?)\} \s* ', re.S | re.X) 43 | IMPORT = re.compile(r'^@import\("(.+?)"\); \s* ', re.S | re.X) 44 | UNKNOWN = re.compile(r'^ (\S+) \s* ', re.S | re.X) 45 | 46 | ZOOM_MINMAX = re.compile(r'^ (\d+)\-(\d+) $', re.S | re.X) 47 | ZOOM_MIN = re.compile(r'^ (\d+)\- $', re.S | re.X) 48 | ZOOM_MAX = re.compile(r'^ \-(\d+) $', re.S | re.X) 49 | ZOOM_SINGLE = re.compile(r'^ (\d+) $', re.S | re.X) 50 | 51 | CONDITION_TRUE = re.compile(r'^ \s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X) 52 | CONDITION_invTRUE = re.compile(r'^ \s* [!] \s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X) 53 | CONDITION_FALSE = re.compile(r'^ \s* ([:\w]+) \s* = \s* no \s* $', re.I | re.S | re.X) 54 | CONDITION_SET = re.compile(r'^ \s* ([-:\w]+) \s* $', re.S | re.X) 55 | CONDITION_UNSET = re.compile(r'^ \s* !([:\w]+) \s* $', re.S | re.X) 56 | CONDITION_EQ = re.compile(r'^ \s* ([:\w]+) \s* = \s* (.+) \s* $', re.S | re.X) 57 | CONDITION_NE = re.compile(r'^ \s* ([:\w]+) \s* != \s* (.+) \s* $', re.S | re.X) 58 | CONDITION_GT = re.compile(r'^ \s* ([:\w]+) \s* > \s* (.+) \s* $', re.S | re.X) 59 | CONDITION_GE = re.compile(r'^ \s* ([:\w]+) \s* >= \s* (.+) \s* $', re.S | re.X) 60 | CONDITION_LT = re.compile(r'^ \s* ([:\w]+) \s* < \s* (.+) \s* $', re.S | re.X) 61 | CONDITION_LE = re.compile(r'^ \s* ([:\w]+) \s* <= \s* (.+) \s* $', re.S | re.X) 62 | CONDITION_REGEX = re.compile(r'^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $', re.S | re.X) 63 | 64 | ASSIGNMENT_EVAL = re.compile(r"^ \s* (\S+) \s* \: \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X) 65 | ASSIGNMENT = re.compile(r'^ \s* (\S+) \s* \: \s* (.+?) \s* $', re.S | re.X) 66 | SET_TAG_EVAL = re.compile(r"^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X) 67 | SET_TAG = re.compile(r'^ \s* set \s+(\S+)\s* = \s* (.+?) \s* $', re.I | re.S | re.X) 68 | SET_TAG_TRUE = re.compile(r'^ \s* set \s+(\S+)\s* $', re.I | re.S | re.X) 69 | EXIT = re.compile(r'^ \s* exit \s* $', re.I | re.S | re.X) 70 | 71 | oZOOM = 2 72 | oGROUP = 3 73 | oCONDITION = 4 74 | oOBJECT = 5 75 | oDECLARATION = 6 76 | oSUBPART = 7 77 | 78 | DASH = re.compile(r'\-/g') 79 | COLOR = re.compile(r'color$/') 80 | BOLD = re.compile(r'^bold$/i') 81 | ITALIC = re.compile(r'^italic|oblique$/i') 82 | UNDERLINE = re.compile(r'^underline$/i') 83 | CAPS = re.compile(r'^uppercase$/i') 84 | CENTER = re.compile(r'^center$/i') 85 | 86 | HEX = re.compile(r'^#([0-9a-f]+)$/i') 87 | 88 | class MapCSS(): 89 | def __init__(self, minscale=0, maxscale=19): 90 | """ 91 | """ 92 | self.cache = {} 93 | self.cache["style"] = {} 94 | self.minscale = minscale 95 | self.maxscale = maxscale 96 | self.scalepair = (minscale, maxscale) 97 | self.choosers = [] 98 | self.choosers_by_type = {} 99 | self.style_loaded = False 100 | 101 | def parseZoom(self, s): 102 | if ZOOM_MINMAX.match(s): 103 | return tuple([float(i) for i in ZOOM_MINMAX.match(s).groups()]) 104 | elif ZOOM_MIN.match(s): 105 | return float(ZOOM_MIN.match(s).groups()[0]), self.maxscale 106 | elif ZOOM_MAX.match(s): 107 | return float(self.minscale), float(ZOOM_MAX.match(s).groups()[0]) 108 | elif ZOOM_SINGLE.match(s): 109 | return float(ZOOM_SINGLE.match(s).groups()[0]), float(ZOOM_SINGLE.match(s).groups()[0]) 110 | else: 111 | logging.error("unparsed zoom: %s" % s) 112 | 113 | def get_style(self, type, tags={}, zoom=0, scale=1, zscale=.5, cache=True): 114 | """ 115 | Kothic styling API 116 | """ 117 | if cache: 118 | shash = md5(repr(type) + repr(tags) + repr(zoom)).digest() 119 | if shash in self.cache["style"]: 120 | return deepcopy(self.cache["style"][shash]) 121 | style = [] 122 | for chooser in self.choosers_by_type[type]: 123 | style = chooser.updateStyles(style, type, tags, zoom, scale, zscale) 124 | style = [x for x in style if x["object-id"] != "::*"] 125 | st = [] 126 | for x in style: 127 | for k, v in [('width', 0), ('casing-width', 0)]: 128 | if k in x: 129 | if x[k] == v: 130 | del x[k] 131 | st = [] 132 | for x in style: 133 | if not NEEDED_KEYS.isdisjoint(x): 134 | st.append(x) 135 | style = st 136 | 137 | if cache: 138 | self.cache["style"][shash] = deepcopy(style) 139 | return style 140 | 141 | def get_style_dict(self, type, tags={}, zoom=0, scale=1, zscale=.5, olddict={}, cache=True): 142 | r = self.get_style(type, tags, zoom, scale, zscale, cache) 143 | d = olddict 144 | for x in r: 145 | if x.get('object-id', '') not in d: 146 | d[x.get('object-id', '')] = {} 147 | d[x.get('object-id', '')].update(x) 148 | return d 149 | 150 | def get_interesting_tags(self, type=None, zoom=None): 151 | """ 152 | Get set of interesting tags. 153 | """ 154 | tags = set() 155 | for chooser in self.choosers: 156 | tags.update(chooser.get_interesting_tags(type, zoom)) 157 | return tags 158 | 159 | def get_sql_hints(self, type=None, zoom=None): 160 | """ 161 | Get set of interesting tags. 162 | """ 163 | hints = [] 164 | for chooser in self.choosers: 165 | p = chooser.get_sql_hints(type, zoom) 166 | if p: 167 | if p[0] and p[1]: 168 | hints.append(p) 169 | return hints 170 | 171 | def parse(self, css=None, clamp=True, stretch=1000, filename=None): 172 | """ 173 | Parses MapCSS given as string 174 | """ 175 | basepath = os.curdir 176 | if filename: 177 | basepath = os.path.dirname(filename) 178 | if not css: 179 | css = open(filename).read() 180 | if not self.style_loaded: 181 | self.choosers = [] 182 | log = logging.getLogger('mapcss.parser') 183 | previous = 0 # what was the previous CSS word? 184 | sc = StyleChooser(self.scalepair) # currently being assembled 185 | css_orig = css 186 | css = css.strip() 187 | while (css): 188 | 189 | # Class - :motorway, :builtup, :hover 190 | if CLASS.match(css): 191 | if previous == oDECLARATION: 192 | self.choosers.append(sc) 193 | sc = StyleChooser(self.scalepair) 194 | 195 | cond = CLASS.match(css).groups()[0] 196 | log.debug("class found: %s" % (cond)) 197 | css = CLASS.sub("", css) 198 | 199 | sc.addCondition(Condition('eq', ("::class", cond))) 200 | previous = oCONDITION 201 | 202 | ## Not class - !.motorway, !.builtup, !:hover 203 | #elif NOT_CLASS.match(css): 204 | #if (previous == oDECLARATION): 205 | #self.choosers.append(sc) 206 | #sc = StyleChooser(self.scalepair) 207 | 208 | #cond = NOT_CLASS.match(css).groups()[0] 209 | #log.debug("not_class found: %s" % (cond)) 210 | #css = NOT_CLASS.sub("", css) 211 | #sc.addCondition(Condition('ne', ("::class", cond))) 212 | #previous = oCONDITION 213 | 214 | # Zoom 215 | elif ZOOM.match(css): 216 | if (previous != oOBJECT & previous != oCONDITION): 217 | sc.newObject() 218 | 219 | cond = ZOOM.match(css).groups()[0] 220 | log.debug("zoom found: %s" % (cond)) 221 | css = ZOOM.sub("", css) 222 | sc.addZoom(self.parseZoom(cond)) 223 | previous = oZOOM 224 | 225 | # Grouping - just a comma 226 | elif GROUP.match(css): 227 | css = GROUP.sub("", css) 228 | sc.newGroup() 229 | previous = oGROUP 230 | 231 | # Condition - [highway=primary] 232 | elif CONDITION.match(css): 233 | if (previous == oDECLARATION): 234 | self.choosers.append(sc) 235 | sc = StyleChooser(self.scalepair) 236 | if (previous != oOBJECT) and (previous != oZOOM) and (previous != oCONDITION): 237 | sc.newObject() 238 | cond = CONDITION.match(css).groups()[0] 239 | log.debug("condition found: %s" % (cond)) 240 | css = CONDITION.sub("", css) 241 | sc.addCondition(parseCondition(cond)) 242 | previous = oCONDITION 243 | 244 | # Object - way, node, relation 245 | elif OBJECT.match(css): 246 | if (previous == oDECLARATION): 247 | self.choosers.append(sc) 248 | sc = StyleChooser(self.scalepair) 249 | obj = OBJECT.match(css).groups()[0] 250 | log.debug("object found: %s" % (obj)) 251 | css = OBJECT.sub("", css) 252 | sc.newObject(obj) 253 | previous = oOBJECT 254 | 255 | # Declaration - {...} 256 | elif DECLARATION.match(css): 257 | decl = DECLARATION.match(css).groups()[0] 258 | log.debug("declaration found: %s" % (decl)) 259 | sc.addStyles(parseDeclaration(decl)) 260 | css = DECLARATION.sub("", css) 261 | previous = oDECLARATION 262 | 263 | # CSS comment 264 | elif COMMENT.match(css): 265 | log.debug("comment found") 266 | css = COMMENT.sub("", css) 267 | 268 | # @import("filename.css"); 269 | elif IMPORT.match(css): 270 | log.debug("import found") 271 | filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) 272 | try: 273 | css = IMPORT.sub("", css) 274 | import_text = open(os.path.join(basepath, filename), "r").read().strip() 275 | css = import_text + css 276 | except IOError: 277 | log.warning("cannot import file %s" % (filename)) 278 | 279 | # Unknown pattern 280 | elif UNKNOWN.match(css): 281 | log.warning("unknown thing found on line %s: %s" % (unicode(css_orig[:-len(unicode(css))]).count("\n") + 1, UNKNOWN.match(css).group())) 282 | css = UNKNOWN.sub("", css) 283 | 284 | else: 285 | log.warning("choked on: %s" % (css)) 286 | return 287 | 288 | if (previous == oDECLARATION): 289 | self.choosers.append(sc) 290 | sc = StyleChooser(self.scalepair) 291 | try: 292 | if clamp: 293 | "clamp z-indexes, so they're tightly following integers" 294 | zindex = set() 295 | for chooser in self.choosers: 296 | for stylez in chooser.styles: 297 | zindex.add(float(stylez.get('z-index', 0))) 298 | zindex = list(zindex) 299 | zindex.sort() 300 | for chooser in self.choosers: 301 | for stylez in chooser.styles: 302 | if 'z-index' in stylez: 303 | if stretch: 304 | stylez['z-index'] = 1. * zindex.index(float(stylez.get('z-index', 0))) / len(zindex) * stretch 305 | else: 306 | stylez['z-index'] = zindex.index(float(stylez.get('z-index', 0))) 307 | 308 | except TypeError: 309 | pass 310 | for chooser in self.choosers: 311 | for t in chooser.compatible_types: 312 | if t not in self.choosers_by_type: 313 | self.choosers_by_type[t] = [chooser] 314 | else: 315 | self.choosers_by_type[t].append(chooser) 316 | 317 | 318 | def parseCondition(s): 319 | log = logging.getLogger('mapcss.parser.condition') 320 | if CONDITION_TRUE.match(s): 321 | a = CONDITION_TRUE.match(s).groups() 322 | log.debug("condition true: %s" % (a[0])) 323 | return Condition('true', a) 324 | if CONDITION_invTRUE.match(s): 325 | a = CONDITION_invTRUE.match(s).groups() 326 | log.debug("condition invtrue: %s" % (a[0])) 327 | return Condition('ne', (a[0], "yes")) 328 | 329 | if CONDITION_FALSE.match(s): 330 | a = CONDITION_FALSE.match(s).groups() 331 | log.debug("condition false: %s" % (a[0])) 332 | return Condition('false', a) 333 | 334 | if CONDITION_SET.match(s): 335 | a = CONDITION_SET.match(s).groups() 336 | log.debug("condition set: %s" % (a)) 337 | return Condition('set', a) 338 | 339 | if CONDITION_UNSET.match(s): 340 | a = CONDITION_UNSET.match(s).groups() 341 | log.debug("condition unset: %s" % (a)) 342 | return Condition('unset', a) 343 | 344 | if CONDITION_NE.match(s): 345 | a = CONDITION_NE.match(s).groups() 346 | log.debug("condition NE: %s = %s" % (a[0], a[1])) 347 | return Condition('ne', a) 348 | 349 | if CONDITION_LE.match(s): 350 | a = CONDITION_LE.match(s).groups() 351 | log.debug("condition LE: %s <= %s" % (a[0], a[1])) 352 | return Condition('<=', a) 353 | 354 | if CONDITION_GE.match(s): 355 | a = CONDITION_GE.match(s).groups() 356 | log.debug("condition GE: %s >= %s" % (a[0], a[1])) 357 | return Condition('>=', a) 358 | 359 | if CONDITION_LT.match(s): 360 | a = CONDITION_LT.match(s).groups() 361 | log.debug("condition LT: %s < %s" % (a[0], a[1])) 362 | return Condition('<', a) 363 | 364 | if CONDITION_GT.match(s): 365 | a = CONDITION_GT.match(s).groups() 366 | log.debug("condition GT: %s > %s" % (a[0], a[1])) 367 | return Condition('>', a) 368 | 369 | if CONDITION_REGEX.match(s): 370 | a = CONDITION_REGEX.match(s).groups() 371 | log.debug("condition REGEX: %s = %s" % (a[0], a[1])) 372 | return Condition('regex', a) 373 | 374 | if CONDITION_EQ.match(s): 375 | a = CONDITION_EQ.match(s).groups() 376 | log.debug("condition EQ: %s = %s" % (a[0], a[1])) 377 | return Condition('eq', a) 378 | 379 | else: 380 | log.warning("condition UNKNOWN: %s" % (s)) 381 | 382 | 383 | def parseDeclaration(s): 384 | """ 385 | Parse declaration string into list of styles 386 | """ 387 | styles = [] 388 | t = {} 389 | 390 | for a in s.split(';'): 391 | # if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); } 392 | if ASSIGNMENT.match(a): 393 | tzz = ASSIGNMENT.match(a).groups() 394 | t[tzz[0]] = tzz[1].strip().strip('"') 395 | logging.debug("%s == %s" % (tzz[0], tzz[1])) 396 | else: 397 | logging.debug("unknown %s" % (a)) 398 | return [t] 399 | 400 | 401 | if __name__ == "__main__": 402 | logging.basicConfig(level=logging.WARNING) 403 | mc = MapCSS(0, 19) 404 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: webcolors 3 | Version: 1.3 4 | Summary: A library for working with sRGB color specifications as used in HTML and CSS. 5 | Home-page: http://www.bitbucket.org/ubernostrum/webcolors/overview/ 6 | Author: James Bennett 7 | Author-email: james@b-list.org 8 | License: UNKNOWN 9 | Download-URL: http://bitbucket.org/ubernostrum/webcolors/downloads/webcolors-1.3.tar.gz 10 | Description: Web colors 11 | ========== 12 | 13 | A simple library for working with the color names and color codes 14 | defined by the HTML and CSS specifications. 15 | 16 | 17 | An overview of HTML and CSS colors 18 | ---------------------------------- 19 | 20 | Colors on the Web are specified in `the sRGB color space`_, where each 21 | color is made up of a red component, a green component and a blue 22 | component. This is useful because it maps (fairly) cleanly to the red, 23 | green and blue components of pixels on a computer display, and to the 24 | cone cells of a human eye, which come in three sets roughly 25 | corresponding to the wavelengths of light associated with red, green 26 | and blue. 27 | 28 | `The HTML 4 standard`_ defines two ways to specify sRGB colors: 29 | 30 | * A hash mark ('#') followed by three pairs of hexdecimal digits, 31 | specifying values for red, green and blue components in that order; 32 | for example, ``#0099cc``. Since each pair of hexadecimal digits can 33 | express 256 different values, this allows up to 256**3 or 16,777,216 34 | unique colors to be specified (though, due to differences in display 35 | technology, not all of these colors may be clearly distinguished on 36 | any given physical display). 37 | 38 | * A set of predefined color names which correspond to specific 39 | hexadecimal values; for example, ``white``. HTML 4 defines sixteen 40 | such colors. 41 | 42 | `The CSS 2 standard`_ allows any valid HTML 4 color specification, and 43 | adds three new ways to specify sRGB colors: 44 | 45 | * A hash mark followed by three hexadecimal digits, which is expanded 46 | into three hexadecimal pairs by repeating each digit; thus ``#09c`` 47 | is equivalent to ``#0099cc``. 48 | 49 | * The string 'rgb', followed by parentheses, between which are three 50 | numeric values each between 0 and 255, inclusive, which are taken to 51 | be the values of the red, green and blue components in that order; 52 | for example, ``rgb(0, 153, 204)``. 53 | 54 | * The same as above, except using percentages instead of numeric 55 | values; for example, ``rgb(0%, 60%, 80%)``. 56 | 57 | `The CSS 2.1 revision`_ does not add any new methods of specifying 58 | sRGB colors, but does add one additional named color. 59 | 60 | `The CSS 3 color module`_ (currently a W3C Candidate Recommendation) 61 | adds one new way to specify sRGB colors: 62 | 63 | * A hue-saturation-lightness triple (HSL), using the construct 64 | ``hsl()``. 65 | 66 | It also adds support for variable opacity of colors, by allowing the 67 | specification of alpha-channel information, through the ``rgba()`` and 68 | ``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()`` 69 | with one exception: a fourth value is supplied, indicating the level 70 | of opacity from ``0.0`` (completely transparent) to ``1.0`` 71 | (completely opaque). Though not technically a color, the keyword 72 | ``transparent`` is also made available in lieu of a color value, and 73 | corresponds to ``rgba(0,0,0,0)``. 74 | 75 | Additionally, CSS3 defines a new set of color names; this set is taken 76 | directly from the named colors defined for SVG (Scalable Vector 77 | Graphics) markup, and is a proper superset of the named colors defined 78 | in CSS 2.1. This set also has significant overlap with traditional X11 79 | color sets as defined by the ``rgb.txt`` file on many Unix and 80 | Unix-like operating systems, though the correspondence is not exact; 81 | the set of X11 colors is not standardized, and the set of CSS3 colors 82 | contains some definitions which diverge significantly from customary 83 | X11 definitions (for example, CSS3's ``green`` is not equivalent to 84 | X11's ``green``; the value which X11 designates ``green`` is 85 | designated ``lime`` in CSS3). 86 | 87 | .. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB 88 | .. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5 89 | .. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color 90 | .. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/ 91 | .. _The CSS 3 color module: http://www.w3.org/TR/css3-color/ 92 | 93 | What this module supports 94 | ------------------------- 95 | 96 | The mappings and functions within this module support the following 97 | methods of specifying sRGB colors, and conversions between them: 98 | 99 | * Six-digit hexadecimal. 100 | 101 | * Three-digit hexadecimal. 102 | 103 | * Integer ``rgb()`` triplet. 104 | 105 | * Percentage ``rgb()`` triplet. 106 | 107 | * Varying selections of predefined color names (see below). 108 | 109 | This module does not support ``hsl()`` triplets, nor does it support 110 | opacity/alpha-channel information via ``rgba()`` or ``hsla()``. 111 | 112 | If you need to convert between RGB-specified colors and HSL-specified 113 | colors, or colors specified via other means, consult `the colorsys 114 | module`_ in the Python standard library, which can perform conversions 115 | amongst several common color spaces. 116 | 117 | .. _the colorsys module: http://docs.python.org/library/colorsys.html 118 | 119 | Normalization 120 | ------------- 121 | 122 | For colors specified via hexadecimal values, this module will accept 123 | input in the following formats: 124 | 125 | * A hash mark (#) followed by three hexadecimal digits, where letters 126 | may be upper- or lower-case. 127 | 128 | * A hash mark (#) followed by six hexadecimal digits, where letters 129 | may be upper- or lower-case. 130 | 131 | For output which consists of a color specified via hexadecimal values, 132 | and for functions which perform intermediate conversion to hexadecimal 133 | before returning a result in another format, this module always 134 | normalizes such values to the following format: 135 | 136 | * A hash mark (#) followed by six hexadecimal digits, with letters 137 | forced to lower-case. 138 | 139 | The function ``normalize_hex()`` in this module can be used to perform 140 | this normalization manually if desired; see its documentation for an 141 | explanation of the normalization process. 142 | 143 | For colors specified via predefined names, this module will accept 144 | input in the following formats: 145 | 146 | * An entirely lower-case name, such as ``aliceblue``. 147 | 148 | * A name using initial capitals, such as ``AliceBlue``. 149 | 150 | For output which consists of a color specified via a predefined name, 151 | and for functions which perform intermediate conversion to a 152 | predefined name before returning a result in another format, this 153 | module always normalizes such values to be entirely lower-case. 154 | 155 | Mappings of color names 156 | ----------------------- 157 | 158 | For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS 159 | 3 -- this module exports two mappings: one of normalized color names 160 | to normalized hexadecimal values, and one of normalized hexadecimal 161 | values to normalized color names. These eight mappings are as follows: 162 | 163 | ``html4_names_to_hex`` 164 | Mapping of normalized HTML 4 color names to normalized hexadecimal 165 | values. 166 | 167 | ``html4_hex_to_names`` 168 | Mapping of normalized hexadecimal values to normalized HTML 4 169 | color names. 170 | 171 | ``css2_names_to_hex`` 172 | Mapping of normalized CSS 2 color names to normalized hexadecimal 173 | values. Because CSS 2 defines the same set of named colors as HTML 174 | 4, this is merely an alias for ``html4_names_to_hex``. 175 | 176 | ``css2_hex_to_names`` 177 | Mapping of normalized hexadecimal values to normalized CSS 2 color 178 | nams. For the reasons described above, this is merely an alias for 179 | ``html4_hex_to_names``. 180 | 181 | ``css21_names_to_hex`` 182 | Mapping of normalized CSS 2.1 color names to normalized 183 | hexadecimal values. This is identical to ``html4_names_to_hex``, 184 | except for one addition: ``orange``. 185 | 186 | ``css21_hex_to_names`` 187 | Mapping of normalized hexadecimal values to normalized CSS 2.1 188 | color names. As above, this is identical to ``html4_hex_to_names`` 189 | except for the addition of ``orange``. 190 | 191 | ``css3_names_to_hex`` 192 | Mapping of normalized CSS3 color names to normalized hexadecimal 193 | values. 194 | 195 | ``css3_hex_to_names`` 196 | Mapping of normalized hexadecimal values to normalized CSS3 color 197 | names. 198 | 199 | Normalization functions 200 | ----------------------- 201 | 202 | ``normalize_hex(hex_value)`` 203 | Normalize a hexadecimal color value to the following form and 204 | return the result:: 205 | 206 | #[a-f0-9]{6} 207 | 208 | In other words, the following transformations are applied as 209 | needed: 210 | 211 | * If the value contains only three hexadecimal digits, it is 212 | expanded to six. 213 | 214 | * The value is normalized to lower-case. 215 | 216 | If the supplied value cannot be interpreted as a hexadecimal color 217 | value, ``ValueError`` is raised. 218 | 219 | Example: 220 | 221 | >>> normalize_hex('#09c') 222 | '#0099cc' 223 | 224 | Conversions from named colors 225 | ----------------------------- 226 | 227 | ``name_to_hex(name, spec='css3')`` 228 | Convert a color name to a normalized hexadecimal color value. 229 | 230 | The optional keyword argument ``spec`` determines which 231 | specification's list of color names will be used; valid values are 232 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 233 | ``css3``. 234 | 235 | The color name will be normalized to lower-case before being 236 | looked up, and when no color of that name exists in the given 237 | specification, ``ValueError`` is raised. 238 | 239 | Example: 240 | 241 | >>> name_to_hex('deepskyblue') 242 | '#00bfff' 243 | 244 | ``name_to_rgb(name, spec='css3')`` 245 | Convert a color name to a 3-tuple of integers suitable for use in 246 | an ``rgb()`` triplet specifying that color. 247 | 248 | The optional keyword argument ``spec`` determines which 249 | specification's list of color names will be used; valid values are 250 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 251 | ``css3``. 252 | 253 | The color name will be normalized to lower-case before being 254 | looked up, and when no color of that name exists in the given 255 | specification, ``ValueError`` is raised. 256 | 257 | Example: 258 | 259 | >>> name_to_rgb('navy') 260 | (0, 0, 128) 261 | 262 | ``name_to_rgb_percent(name, spec='css3')`` 263 | Convert a color name to a 3-tuple of percentages suitable for use 264 | in an ``rgb()`` triplet specifying that color. 265 | 266 | The optional keyword argument ``spec`` determines which 267 | specification's list of color names will be used; valid values are 268 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 269 | ``css3``. 270 | 271 | The color name will be normalized to lower-case before being 272 | looked up, and when no color of that name exists in the given 273 | specification, ``ValueError`` is raised. 274 | 275 | Example: 276 | 277 | >>> name_to_rgb_percent('navy') 278 | ('0%', '0%', '50%') 279 | 280 | 281 | Conversions from hexadecimal values 282 | ----------------------------------- 283 | 284 | ``hex_to_name(hex_value, spec='css3')`` 285 | Convert a hexadecimal color value to its corresponding normalized 286 | color name, if any such name exists. 287 | 288 | The optional keyword argument ``spec`` determines which 289 | specification's list of color names will be used; valid values are 290 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 291 | ``css3``. 292 | 293 | The hexadecimal value will be normalized before being looked up, 294 | and when no color name for the value is found in the given 295 | specification, ``ValueError`` is raised. 296 | 297 | Example: 298 | 299 | >>> hex_to_name('#000080') 300 | 'navy' 301 | 302 | ``hex_to_rgb(hex_value)`` 303 | Convert a hexadecimal color value to a 3-tuple of integers 304 | suitable for use in an ``rgb()`` triplet specifying that color. 305 | 306 | The hexadecimal value will be normalized before being converted. 307 | 308 | Example: 309 | 310 | >>> hex_to_rgb('#000080') 311 | (0, 0, 128) 312 | 313 | ``hex_to_rgb_percent(hex_value)`` 314 | Convert a hexadecimal color value to a 3-tuple of percentages 315 | suitable for use in an ``rgb()`` triplet representing that color. 316 | 317 | The hexadecimal value will be normalized before converting. 318 | 319 | Example: 320 | 321 | >>> hex_to_rgb_percent('#ffffff') 322 | ('100%', '100%', '100%') 323 | 324 | 325 | Conversions from integer rgb() triplets 326 | --------------------------------------- 327 | 328 | ``rgb_to_name(rgb_triplet, spec='css3')`` 329 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 330 | color triplet, to its corresponding normalized color name, if any 331 | such name exists. 332 | 333 | The optional keyword argument ``spec`` determines which 334 | specification's list of color names will be used; valid values are 335 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 336 | ``css3``. 337 | 338 | If there is no matching name, ``ValueError`` is raised. 339 | 340 | Example: 341 | 342 | >>> rgb_to_name((0, 0, 0)) 343 | 'black' 344 | 345 | ``rgb_to_hex(rgb_triplet)`` 346 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 347 | color triplet, to a normalized hexadecimal value for that color. 348 | 349 | Example: 350 | 351 | >>> rgb_to_hex((255, 255, 255)) 352 | '#ffffff' 353 | 354 | ``rgb_to_rgb_percent(rgb_triplet)`` 355 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 356 | color triplet, to a 3-tuple of percentages suitable for use in 357 | representing that color. 358 | 359 | This function makes some trade-offs in terms of the accuracy of 360 | the final representation; for some common integer values, 361 | special-case logic is used to ensure a precise result (e.g., 362 | integer 128 will always convert to '50%', integer 32 will always 363 | convert to '12.5%'), but for all other values a standard Python 364 | ``float`` is used and rounded to two decimal places, which may 365 | result in a loss of precision for some values. 366 | 367 | Examples: 368 | 369 | >>> rgb_to_rgb_percent((255, 255, 255)) 370 | ('100%', '100%', '100%') 371 | >>> rgb_to_rgb_percent((0, 0, 128)) 372 | ('0%', '0%', '50%') 373 | >>> rgb_to_rgb_percent((33, 56, 192)) 374 | ('12.94%', '21.96%', '75.29%') 375 | >>> rgb_to_rgb_percent((64, 32, 16)) 376 | ('25%', '12.5%', '6.25%') 377 | 378 | 379 | Conversions from percentage rgb() triplets 380 | ------------------------------------------ 381 | 382 | ``rgb_percent_to_name(rgb_percent_triplet, spec='css3')`` 383 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 384 | color triplet, to its corresponding normalized color name, if any 385 | such name exists. 386 | 387 | The optional keyword argument ``spec`` determines which 388 | specification's list of color names will be used; valid values are 389 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 390 | ``css3``. 391 | 392 | If there is no matching name, ``ValueError`` is raised. 393 | 394 | Example: 395 | 396 | >>> rgb_percent_to_name(('0%', '0%', '50%')) 397 | 'navy' 398 | 399 | ``rgb_percent_to_hex(rgb_percent_triplet)`` 400 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 401 | color triplet, to a normalized hexadecimal color value for that 402 | color. 403 | 404 | Example: 405 | 406 | >>> rgb_percent_to_hex(('100%', '100%', '0%')) 407 | '#ffff00' 408 | 409 | ``rgb_percent_to_rgb(rgb_percent_triplet)`` 410 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 411 | color triplet, to a 3-tuple of integers suitable for use in 412 | representing that color. 413 | 414 | Some precision may be lost in this conversion. See the note 415 | regarding precision for ``rgb_to_rgb_percent()`` for details; 416 | generally speaking, the following is true for any 3-tuple ``t`` of 417 | integers in the range 0...255 inclusive:: 418 | 419 | t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) 420 | 421 | Examples: 422 | 423 | >>> rgb_percent_to_rgb(('100%', '100%', '100%')) 424 | (255, 255, 255) 425 | >>> rgb_percent_to_rgb(('0%', '0%', '50%')) 426 | (0, 0, 128) 427 | >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%')) 428 | (64, 32, 16) 429 | >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%')) 430 | (33, 56, 192) 431 | 432 | Platform: UNKNOWN 433 | Classifier: Development Status :: 5 - Production/Stable 434 | Classifier: Environment :: Web Environment 435 | Classifier: Intended Audience :: Developers 436 | Classifier: License :: OSI Approved :: BSD License 437 | Classifier: Operating System :: OS Independent 438 | Classifier: Programming Language :: Python 439 | Classifier: Topic :: Utilities 440 | -------------------------------------------------------------------------------- /src/render.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # This file is part of kothic, the realtime map renderer. 4 | 5 | # kothic is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # kothic is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with kothic. If not, see . 17 | 18 | from debug import debug, Timer 19 | from twms import projections 20 | import cairo 21 | import math 22 | import os as os_module 23 | from copy import deepcopy 24 | import pangocairo 25 | import pango 26 | 27 | 28 | def line(cr, c): 29 | cr.move_to(*c[0]) 30 | for k in c: 31 | cr.line_to(*k) 32 | cr.stroke() 33 | 34 | 35 | def poly(cr, c): 36 | cr.move_to(*c[0]) 37 | for k in c: 38 | cr.line_to(*k) 39 | cr.fill() 40 | 41 | 42 | def offset_line(line, offset): 43 | a = [] 44 | prevcoord = line[0] 45 | for coord in line: 46 | if coord != prevcoord: 47 | angle = - math.atan2(coord[1] - prevcoord[1], coord[0] - prevcoord[0]) 48 | dx = offset * math.sin(angle) 49 | dy = offset * math.cos(angle) 50 | a.append((prevcoord[0] + dx, prevcoord[1] + dy)) 51 | a.append((coord[0] + dx, coord[1] + dy)) 52 | prevcoord = coord 53 | return a 54 | 55 | 56 | class RasterTile: 57 | def __init__(self, width, height, zoomlevel, data_backend, raster_proj="EPSG:3857"): 58 | self.w = width 59 | self.h = height 60 | self.surface = cairo.ImageSurface(cairo.FORMAT_RGB24, self.w, self.h) 61 | self.offset_x = 0 62 | self.offset_y = 0 63 | self.bbox = (0., 0., 0., 0.) 64 | self.bbox_p = (0., 0., 0., 0.) 65 | self.zoomlevel = zoomlevel 66 | self.zoom = None 67 | self.data = data_backend 68 | self.proj = raster_proj 69 | 70 | def __del__(self): 71 | del self.surface 72 | 73 | def screen2lonlat(self, x, y): 74 | lo1, la1, lo2, la2 = self.bbox_p 75 | debug("%s %s - %s %s" % (x, y, self.w, self.h)) 76 | debug(self.bbox_p) 77 | return projections.to4326((1. * x / self.w * (lo2 - lo1) + lo1, la2 + (1. * y / (self.h) * (la1 - la2))), self.proj) 78 | # return (x - self.w/2)/(math.cos(self.center_coord[1]*math.pi/180)*self.zoom) + self.center_coord[0], -(y - self.h/2)/self.zoom + self.center_coord[1] 79 | 80 | def lonlat2screen(self, (lon, lat), epsg4326=False): 81 | if epsg4326: 82 | lon, lat = projections.from4326((lon, lat), self.proj) 83 | lo1, la1, lo2, la2 = self.bbox_p 84 | return ((lon - lo1) * (self.w - 1) / abs(lo2 - lo1), ((la2 - lat) * (self.h - 1) / (la2 - la1))) 85 | # return (lon - self.center_coord[0])*self.lcc*self.zoom + self.w/2, -(lat - self.center_coord[1])*self.zoom + self.h/2 86 | 87 | def update_surface_by_center(self, lonlat, zoom, style): 88 | self.zoom = zoom 89 | xy = projections.from4326(lonlat, self.proj) 90 | xy1 = projections.to4326((xy[0] - 40075016 * 0.5 ** self.zoom / 256 * self.w, xy[1] - 40075016 * 0.5 ** self.zoom / 256 * self.h), self.proj) 91 | xy2 = projections.to4326((xy[0] + 40075016 * 0.5 ** self.zoom / 256 * self.w, xy[1] + 40075016 * 0.5 ** self.zoom / 256 * self.h), self.proj) 92 | bbox = (xy1[0], xy1[1], xy2[0], xy2[1]) 93 | debug(bbox) 94 | return self.update_surface(bbox, zoom, style) 95 | 96 | def update_surface(self, bbox, zoom, style, callback=lambda x=None: None): 97 | rendertimer = Timer("Rendering image") 98 | if "image" not in style.cache: 99 | style.cache["image"] = ImageLoader() 100 | 101 | timer = Timer("Getting data") 102 | self.zoom = zoom 103 | self.bbox = bbox 104 | self.bbox_p = projections.from4326(bbox, self.proj) 105 | 106 | print self.bbox_p 107 | scale = abs(self.w / (self.bbox_p[0] - self.bbox_p[2]) / math.cos(math.pi * (self.bbox[1] + self.bbox[3]) / 2 / 180)) 108 | zscale = 0.5 * scale 109 | cr = cairo.Context(self.surface) 110 | # getting and setting canvas properties 111 | bgs = style.get_style("canvas", {}, self.zoom, scale, zscale) 112 | if not bgs: 113 | bgs = [{}] 114 | bgs = bgs[0] 115 | cr.rectangle(0, 0, self.w, self.h) 116 | # canvas color and opcity 117 | color = bgs.get("fill-color", (0.7, 0.7, 0.7)) 118 | cr.set_source_rgba(color[0], color[1], color[2], bgs.get("fill-opacity", 1)) 119 | cr.fill() 120 | callback() 121 | 122 | # canvas antialiasing 123 | antialias = bgs.get("antialias", "full") 124 | if antialias == "none": 125 | "no antialiasing enabled" 126 | cr.set_antialias(1) 127 | # cr.font_options_set_antialias(1) 128 | elif antialias == "text": 129 | "only text antialiased" 130 | cr.set_antialias(1) 131 | # cr.font_options_set_antialias(2) 132 | else: 133 | "full antialias" 134 | cr.set_antialias(2) 135 | # cr.font_options_set_antialias(2) 136 | 137 | datatimer = Timer("Asking backend") 138 | if "get_sql_hints" in dir(style): 139 | hints = style.get_sql_hints('way', self.zoom) 140 | else: 141 | hints = None 142 | if "get_interesting_tags" in dir(style): 143 | itags = style.get_interesting_tags(zoom=self.zoom) 144 | else: 145 | itags = None 146 | 147 | # enlarge bbox by 20% to each side. results in more vectors, but makes less artifaces. 148 | span_x, span_y = bbox[2] - bbox[0], bbox[3] - bbox[1] 149 | bbox_expand = [bbox[0] - 0.2 * span_x, bbox[1] - 0.2 * span_y, bbox[2] + 0.2 * span_x, bbox[3] + 0.2 * span_y] 150 | vectors = self.data.get_vectors(bbox_expand, self.zoom, hints, itags).values() 151 | datatimer.stop() 152 | datatimer = Timer("Applying styles") 153 | ww = [] 154 | 155 | for way in vectors: 156 | type = "line" 157 | if way.coords[0] == way.coords[-1]: 158 | type == "area" 159 | st = style.get_style("area", way.tags, self.zoom, scale, zscale) 160 | if st: 161 | for fpt in st: 162 | # debug(fpt) 163 | ww.append([way.copy(), fpt]) 164 | 165 | datatimer.stop() 166 | debug("%s objects on screen (%s in dataset)" % (len(ww), len(vectors))) 167 | 168 | er = Timer("Projecing data") 169 | if self.data.proj != self.proj: 170 | for w in ww: 171 | w[0].cs = [self.lonlat2screen(coord) for coord in projections.transform(w[0].coords, self.data.proj, self.proj)] 172 | else: 173 | for w in ww: 174 | w[0].cs = [self.lonlat2screen(coord) for coord in w[0].coords] 175 | for w in ww: 176 | if "offset" in w[1]: 177 | offset = float(w[1]["offset"]) 178 | w[0] = w[0].copy() 179 | w[0].cs = offset_line(w[0].cs, offset) 180 | if "raise" in w[1] and not "extrude" in w[1]: 181 | w[0] = w[0].copy() 182 | offset = float(w[1]["raise"]) 183 | w[0].cs_real = w[0].cs 184 | w[0].cs = [(x, y - offset) for x, y in w[0].cs] 185 | if "extrude" in w[1]: 186 | if w[1]["extrude"] < 2: 187 | del w[1]["extrude"] 188 | if "extrude" in w[1] and "fill-color" not in w[1] and "width" in w[1]: 189 | w[1]["fill-color"] = w[1].get("color", (0, 0, 0)) 190 | w[1]["fill-opacity"] = w[1].get("opacity", 1) 191 | w[0] = w[0].copy() 192 | # print w[0].cs 193 | w[0].cs = offset_line(w[0].cs, w[1]["width"] / 2) 194 | # print w[0].cs 195 | aa = offset_line(w[0].cs, -w[1]["width"]) 196 | del w[1]["width"] 197 | aa.reverse() 198 | w[0].cs.extend(aa) 199 | 200 | er.stop() 201 | 202 | ww.sort(key=lambda x: x[1]["layer"]) 203 | layers = list(set([int(x[1]["layer"] / 100.) for x in ww])) 204 | layers.sort() 205 | objs_by_layers = {} 206 | for layer in layers: 207 | objs_by_layers[layer] = [] 208 | for obj in ww: 209 | objs_by_layers[int(obj[1]["layer"] / 100.)].append(obj) 210 | 211 | del ww 212 | timer.stop() 213 | timer = Timer("Rasterizing image") 214 | linecaps = {"butt": 0, "round": 1, "square": 2} 215 | linejoin = {"miter": 0, "round": 1, "bevel": 2} 216 | 217 | text_rendered_at = set([(-100, -100)]) 218 | for layer in layers: 219 | data = objs_by_layers[layer] 220 | # data.sort(lambda x,y:cmp(max([x1[1] for x1 in x[0].cs]), max([x1[1] for x1 in y[0].cs]))) 221 | 222 | # - fill polygons 223 | for obj in data: 224 | if ("fill-color" in obj[1] or "fill-image" in obj[1]) and not "extrude" in obj[1]: # TODO: fill-image 225 | color = obj[1].get("fill-color", (0, 0, 0)) 226 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("fill-opacity", 1)) 227 | 228 | if "fill-image" in obj[1]: 229 | image = style.cache["image"][obj[1]["fill-image"]] 230 | if image: 231 | pattern = cairo.SurfacePattern(image) 232 | pattern.set_extend(cairo.EXTEND_REPEAT) 233 | cr.set_source(pattern) 234 | poly(cr, obj[0].cs) 235 | 236 | # - draw casings on layer 237 | for obj in data: 238 | ### Extras: casing-linecap, casing-linejoin 239 | if "casing-width" in obj[1] or "casing-color" in obj[1] and "extrude" not in obj[1]: 240 | cr.set_dash(obj[1].get("casing-dashes", obj[1].get("dashes", []))) 241 | cr.set_line_join(linejoin.get(obj[1].get("casing-linejoin", obj[1].get("linejoin", "round")), 1)) 242 | color = obj[1].get("casing-color", (0, 0, 0)) 243 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("casing-opacity", 1)) 244 | ## TODO: good combining of transparent lines and casing 245 | ## Probable solution: render casing, render way as mask and put casing with mask chopped out onto image 246 | 247 | cr.set_line_width(obj[1].get("width", 0) + obj[1].get("casing-width", 1)) 248 | cr.set_line_cap(linecaps.get(obj[1].get("casing-linecap", obj[1].get("linecap", "butt")), 0)) 249 | line(cr, obj[0].cs) 250 | 251 | # - draw line centers 252 | for obj in data: 253 | if ("width" in obj[1] or "color" in obj[1] or "image" in obj[1]) and "extrude" not in obj[1]: 254 | cr.set_dash(obj[1].get("dashes", [])) 255 | cr.set_line_join(linejoin.get(obj[1].get("linejoin", "round"), 1)) 256 | color = obj[1].get("color", (0, 0, 0)) 257 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("opacity", 1)) 258 | ## TODO: better overlapping of transparent lines. 259 | ## Probable solution: render them (while they're of the same opacity and layer) on a temporary canvas that's merged into main later 260 | cr.set_line_width(obj[1].get("width", 1)) 261 | cr.set_line_cap(linecaps.get(obj[1].get("linecap", "butt"), 0)) 262 | if "image" in obj[1]: 263 | image = style.cache["image"][obj[1]["image"]] 264 | if image: 265 | pattern = cairo.SurfacePattern(image) 266 | pattern.set_extend(cairo.EXTEND_REPEAT) 267 | cr.set_source(pattern) 268 | line(cr, obj[0].cs) 269 | 270 | callback() 271 | 272 | # - extruding polygons 273 | # data.sort(lambda x,y:cmp(max([x1[1] for x1 in x[0].cs]), max([x1[1] for x1 in y[0].cs]))) 274 | # Pass 1. Creating list of extruded polygons 275 | extlist = [] 276 | # fromat: (coords, ("h"/"v", y,z), real_obj) 277 | for obj in data: 278 | if "extrude" in obj[1]: 279 | def face_to_poly(face, hgt): 280 | """ 281 | Converts a line into height-up extruded poly 282 | """ 283 | return [face[0], face[1], (face[1][0], face[1][1] - hgt), (face[0][0], face[0][1] - hgt), face[0]] 284 | hgt = obj[1]["extrude"] 285 | raised = float(obj[1].get("raise", 0)) 286 | excoords = [(a[0], a[1] - hgt - raised) for a in obj[0].cs] 287 | 288 | faces = [] 289 | coord = obj[0].cs[-1] 290 | # p_coord = (coord[0],coord[1]-raised) 291 | p_coord = False 292 | for coord in obj[0].cs: 293 | c = (coord[0], coord[1] - raised) 294 | if p_coord: 295 | extlist.append((face_to_poly([c, p_coord], hgt), ("v", min(coord[1], p_coord[1]), hgt), obj)) 296 | p_coord = c 297 | 298 | extlist.append((excoords, ("h", min(coord[1], p_coord[1]), hgt), obj)) 299 | # faces.sort(lambda x,y:cmp(max([x1[1] for x1 in x]), max([x1[1] for x1 in y]))) 300 | 301 | # Pass 2. Sorting 302 | def compare_things(a, b): 303 | """ 304 | Custom comparator for extlist sorting. 305 | Sorts back-to-front, bottom-to-top, | > \ > _, horizontal-to-vertical. 306 | """ 307 | t1, t2 = a[1], b[1] 308 | if t1[1] > t2[1]: # back-to-front 309 | return 1 310 | if t1[1] < t2[1]: 311 | return -1 312 | if t1[2] > t2[2]: # bottom-to-top 313 | return 1 314 | if t1[2] < t2[2]: 315 | return -1 316 | if t1[0] < t2[0]: # h-to-v 317 | return 1 318 | if t1[0] > t2[0]: 319 | return -1 320 | 321 | return cmp(math.sin(math.atan2(a[0][0][0] - a[0][1][0], a[0][0][0] - a[0][1][0])), math.sin(math.atan2(b[0][0][0] - b[0][1][0], b[0][0][0] - b[0][1][0]))) 322 | print t1 323 | print t2 324 | 325 | extlist.sort(compare_things) 326 | 327 | # Pass 3. Rendering using painter's algorythm 328 | cr.set_dash([]) 329 | for ply, prop, obj in extlist: 330 | if prop[0] == "v": 331 | color = obj[1].get("extrude-face-color", obj[1].get("color", (0, 0, 0))) 332 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("extrude-face-opacity", obj[1].get("opacity", 1))) 333 | poly(cr, ply) 334 | color = obj[1].get("extrude-edge-color", obj[1].get("color", (0, 0, 0))) 335 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("extrude-edge-opacity", obj[1].get("opacity", 1))) 336 | cr.set_line_width(.5) 337 | line(cr, ply) 338 | if prop[0] == "h": 339 | if "fill-color" in obj[1]: 340 | color = obj[1]["fill-color"] 341 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("fill-opacity", obj[1].get("opacity", 1))) 342 | poly(cr, ply) 343 | color = obj[1].get("extrude-edge-color", obj[1].get("color", (0, 0, 0))) 344 | cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("extrude-edge-opacity", obj[1].get("opacity", 1))) 345 | cr.set_line_width(1) 346 | line(cr, ply) 347 | 348 | # cr.set_line_width (obj[1].get("width", 1)) 349 | # color = obj[1].get("color", (0,0,0) ) 350 | # cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("extrude-edge-opacity", obj[1].get("opacity", 1))) 351 | # line(cr,excoords) 352 | # if "fill-color" in obj[1]: 353 | # color = obj[1]["fill-color"] 354 | # cr.set_source_rgba(color[0], color[1], color[2], obj[1].get("fill-opacity", 1)) 355 | # poly(cr,excoords) 356 | for obj in data: 357 | if "icon-image" in obj[1]: 358 | image = style.cache["image"][obj[1]["icon-image"]] 359 | if image: 360 | dy = image.get_height() / 2 361 | dx = image.get_width() / 2 362 | 363 | where = self.lonlat2screen(projections.transform(obj[0].center, self.data.proj, self.proj)) 364 | cr.set_source_surface(image, where[0] - dx, where[1] - dy) 365 | cr.paint() 366 | 367 | callback() 368 | # - render text labels 369 | texttimer = Timer("Text rendering") 370 | cr.set_line_join(1) # setting linejoin to "round" to get less artifacts on halo render 371 | for obj in data: 372 | if "text" in obj[1]: 373 | 374 | text = obj[1]["text"] 375 | # cr.set_line_width (obj[1].get("width", 1)) 376 | # cr.set_font_size(float(obj[1].get("font-size", 9))) 377 | ft_desc = pango.FontDescription() 378 | 379 | ft_desc.set_family(obj[1].get('font-family', 'sans')) 380 | ft_desc.set_size(pango.SCALE * int(obj[1].get('font-size', 9))) 381 | fontstyle = obj[1].get('font-style', 'normal') 382 | if fontstyle == 'italic': 383 | fontstyle = pango.STYLE_ITALIC 384 | else: 385 | fontstyle = pango.STYLE_NORMAL 386 | ft_desc.set_style(fontstyle) 387 | fontweight = obj[1].get('font-weight', 400) 388 | try: 389 | fontweight = int(fontweight) 390 | except ValueError: 391 | if fontweight == 'bold': 392 | fontweight = 700 393 | else: 394 | fontweight = 400 395 | ft_desc.set_weight(fontweight) 396 | if obj[1].get('text-transform', None) == 'uppercase': 397 | text = text.upper() 398 | p_ctx = pangocairo.CairoContext(cr) 399 | p_layout = p_ctx.create_layout() 400 | p_layout.set_font_description(ft_desc) 401 | p_layout.set_text(text) 402 | p_attrs = pango.AttrList() 403 | decoration = obj[1].get('text-decoration', 'none') 404 | if decoration == 'underline': 405 | p_attrs.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, end_index=-1)) 406 | decoration = obj[1].get('font-variant', 'none') 407 | if decoration == 'small-caps': 408 | p_attrs.insert(pango.AttrVariant(pango.VARIANT_SMALL_CAPS, start_index=0, end_index=-1)) 409 | 410 | p_layout.set_attributes(p_attrs) 411 | 412 | if obj[1].get("text-position", "center") == "center": 413 | where = self.lonlat2screen(projections.transform(obj[0].center, self.data.proj, self.proj)) 414 | for t in text_rendered_at: 415 | if ((t[0] - where[0]) ** 2 + (t[1] - where[1]) ** 2) < 15 * 15: 416 | break 417 | else: 418 | text_rendered_at.add(where) 419 | # debug ("drawing text: %s at %s"%(text, where)) 420 | if "text-halo-color" in obj[1] or "text-halo-radius" in obj[1]: 421 | cr.new_path() 422 | cr.move_to(where[0], where[1]) 423 | cr.set_line_width(obj[1].get("text-halo-radius", 1)) 424 | color = obj[1].get("text-halo-color", (1., 1., 1.)) 425 | cr.set_source_rgb(color[0], color[1], color[2]) 426 | cr.text_path(text) 427 | cr.stroke() 428 | cr.new_path() 429 | cr.move_to(where[0], where[1]) 430 | cr.set_line_width(obj[1].get("text-halo-radius", 1)) 431 | color = obj[1].get("text-color", (0., 0., 0.)) 432 | cr.set_source_rgb(color[0], color[1], color[2]) 433 | cr.text_path(text) 434 | cr.fill() 435 | else: # render text along line 436 | c = obj[0].cs 437 | text = unicode(text, "utf-8") 438 | # - calculate line length 439 | length = reduce(lambda x, y: (x[0] + ((y[0] - x[1]) ** 2 + (y[1] - x[2]) ** 2) ** 0.5, y[0], y[1]), c, (0, c[0][0], c[0][1]))[0] 440 | # print length, text, cr.text_extents(text) 441 | if length > cr.text_extents(text)[2]: 442 | 443 | # - function to get (x, y, normale) from (c, length_along_c) 444 | def get_xy_from_len(c, length_along_c): 445 | x0, y0 = c[0] 446 | 447 | for x, y in c: 448 | seg_len = ((x - x0) ** 2 + (y - y0) ** 2) ** 0.5 449 | if length_along_c < seg_len: 450 | normed = length_along_c / seg_len 451 | return (x - x0) * normed + x0, (y - y0) * normed + y0, math.atan2(y - y0, x - x0) 452 | else: 453 | length_along_c -= seg_len 454 | x0, y0 = x, y 455 | else: 456 | return None 457 | da = 0 458 | os = 1 459 | z = length / 2 - cr.text_extents(text)[2] / 2 460 | # print get_xy_from_len(c,z) 461 | if c[0][0] < c[1][0] and get_xy_from_len(c, z)[2] < math.pi / 2 and get_xy_from_len(c, z)[2] > -math.pi / 2: 462 | da = 0 463 | os = 1 464 | z = length / 2 - cr.text_extents(text)[2] / 2 465 | else: 466 | da = math.pi 467 | os = -1 468 | z = length / 2 + cr.text_extents(text)[2] / 2 469 | z1 = z 470 | if "text-halo-color" in obj[1] or "text-halo-radius" in obj[1]: 471 | cr.set_line_width(obj[1].get("text-halo-radius", 1.5) * 2) 472 | color = obj[1].get("text-halo-color", (1., 1., 1.)) 473 | cr.set_source_rgb(color[0], color[1], color[2]) 474 | xy = get_xy_from_len(c, z) 475 | cr.save() 476 | # cr.move_to(xy[0],xy[1]) 477 | p_ctx.translate(xy[0], xy[1]) 478 | cr.rotate(xy[2] + da) 479 | # p_ctx.translate(x,y) 480 | # p_ctx.show_layout(p_layout) 481 | p_ctx.layout_path(p_layout) 482 | 483 | cr.restore() 484 | cr.stroke() 485 | # for letter in text: 486 | # cr.new_path() 487 | # xy = get_xy_from_len(c,z) 488 | # print letter, cr.text_extents(letter) 489 | # cr.move_to(xy[0],xy[1]) 490 | # cr.save() 491 | # cr.rotate(xy[2]+da) 492 | # cr.text_path(letter) 493 | # cr.restore() 494 | # cr.stroke() 495 | # z += os*cr.text_extents(letter)[4] 496 | 497 | color = obj[1].get("text-color", (0., 0., 0.)) 498 | cr.set_source_rgb(color[0], color[1], color[2]) 499 | z = z1 500 | xy = get_xy_from_len(c, z) 501 | cr.save() 502 | # cr.move_to(xy[0],xy[1]) 503 | p_ctx.translate(xy[0], xy[1]) 504 | cr.rotate(xy[2] + da) 505 | # p_ctx.translate(x,y) 506 | p_ctx.show_layout(p_layout) 507 | cr.restore() 508 | 509 | # for letter in text: 510 | # cr.new_path() 511 | # xy = get_xy_from_len(c,z) 512 | # print letter, cr.text_extents(letter) 513 | # cr.move_to(xy[0],xy[1]) 514 | # cr.save() 515 | # cr.rotate(xy[2]+da) 516 | # cr.text_path(letter) 517 | # cr.restore() 518 | # cr.fill() 519 | # z += os*cr.text_extents(letter)[4] 520 | 521 | texttimer.stop() 522 | del data 523 | del layers 524 | 525 | timer.stop() 526 | rendertimer.stop() 527 | debug(self.bbox) 528 | callback(True) 529 | 530 | 531 | class ImageLoader: 532 | def __init__(self): 533 | self.cache = {} 534 | 535 | def __getitem__(self, url): 536 | if url in self.cache: 537 | return self.cache[url] 538 | else: 539 | print url, os_module.path.exists(url) 540 | if os_module.path.exists(url): 541 | self.cache[url] = cairo.ImageSurface.create_from_png(url) 542 | return self.cache[url] 543 | else: 544 | return False 545 | -------------------------------------------------------------------------------- /src/mapcss/webcolors/webcolors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | A simple library for working with the color names and color codes 4 | defined by the HTML and CSS specifications. 5 | 6 | An overview of HTML and CSS colors 7 | ---------------------------------- 8 | 9 | Colors on the Web are specified in `the sRGB color space`_, where each 10 | color is made up of a red component, a green component and a blue 11 | component. This is useful because it maps (fairly) cleanly to the red, 12 | green and blue components of pixels on a computer display, and to the 13 | cone cells of a human eye, which come in three sets roughly 14 | corresponding to the wavelengths of light associated with red, green 15 | and blue. 16 | 17 | `The HTML 4 standard`_ defines two ways to specify sRGB colors: 18 | 19 | * A hash mark ('#') followed by three pairs of hexdecimal digits, 20 | specifying values for red, green and blue components in that order; 21 | for example, ``#0099cc``. Since each pair of hexadecimal digits can 22 | express 256 different values, this allows up to 256**3 or 16,777,216 23 | unique colors to be specified (though, due to differences in display 24 | technology, not all of these colors may be clearly distinguished on 25 | any given physical display). 26 | 27 | * A set of predefined color names which correspond to specific 28 | hexadecimal values; for example, ``white``. HTML 4 defines sixteen 29 | such colors. 30 | 31 | `The CSS 2 standard`_ allows any valid HTML 4 color specification, and 32 | adds three new ways to specify sRGB colors: 33 | 34 | * A hash mark followed by three hexadecimal digits, which is expanded 35 | into three hexadecimal pairs by repeating each digit; thus ``#09c`` 36 | is equivalent to ``#0099cc``. 37 | 38 | * The string 'rgb', followed by parentheses, between which are three 39 | numeric values each between 0 and 255, inclusive, which are taken to 40 | be the values of the red, green and blue components in that order; 41 | for example, ``rgb(0, 153, 204)``. 42 | 43 | * The same as above, except using percentages instead of numeric 44 | values; for example, ``rgb(0%, 60%, 80%)``. 45 | 46 | `The CSS 2.1 revision`_ does not add any new methods of specifying 47 | sRGB colors, but does add one additional named color. 48 | 49 | `The CSS 3 color module`_ (currently a W3C Candidate Recommendation) 50 | adds one new way to specify sRGB colors: 51 | 52 | * A hue-saturation-lightness triple (HSL), using the construct 53 | ``hsl()``. 54 | 55 | It also adds support for variable opacity of colors, by allowing the 56 | specification of alpha-channel information, through the ``rgba()`` and 57 | ``hsla()`` constructs, which are identical to ``rgb()`` and ``hsl()`` 58 | with one exception: a fourth value is supplied, indicating the level 59 | of opacity from ``0.0`` (completely transparent) to ``1.0`` 60 | (completely opaque). Though not technically a color, the keyword 61 | ``transparent`` is also made available in lieu of a color value, and 62 | corresponds to ``rgba(0,0,0,0)``. 63 | 64 | Additionally, CSS3 defines a new set of color names; this set is taken 65 | directly from the named colors defined for SVG (Scalable Vector 66 | Graphics) markup, and is a proper superset of the named colors defined 67 | in CSS 2.1. This set also has significant overlap with traditional X11 68 | color sets as defined by the ``rgb.txt`` file on many Unix and 69 | Unix-like operating systems, though the correspondence is not exact; 70 | the set of X11 colors is not standardized, and the set of CSS3 colors 71 | contains some definitions which diverge significantly from customary 72 | X11 definitions (for example, CSS3's ``green`` is not equivalent to 73 | X11's ``green``; the value which X11 designates ``green`` is 74 | designated ``lime`` in CSS3). 75 | 76 | .. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB 77 | .. _The HTML 4 standard: http://www.w3.org/TR/html401/types.html#h-6.5 78 | .. _The CSS 2 standard: http://www.w3.org/TR/REC-CSS2/syndata.html#value-def-color 79 | .. _The CSS 2.1 revision: http://www.w3.org/TR/CSS21/ 80 | .. _The CSS 3 color module: http://www.w3.org/TR/css3-color/ 81 | 82 | What this module supports 83 | ------------------------- 84 | 85 | The mappings and functions within this module support the following 86 | methods of specifying sRGB colors, and conversions between them: 87 | 88 | * Six-digit hexadecimal. 89 | 90 | * Three-digit hexadecimal. 91 | 92 | * Integer ``rgb()`` triplet. 93 | 94 | * Percentage ``rgb()`` triplet. 95 | 96 | * Varying selections of predefined color names (see below). 97 | 98 | This module does not support ``hsl()`` triplets, nor does it support 99 | opacity/alpha-channel information via ``rgba()`` or ``hsla()``. 100 | 101 | If you need to convert between RGB-specified colors and HSL-specified 102 | colors, or colors specified via other means, consult `the colorsys 103 | module`_ in the Python standard library, which can perform conversions 104 | amongst several common color spaces. 105 | 106 | .. _the colorsys module: http://docs.python.org/library/colorsys.html 107 | 108 | Normalization 109 | ------------- 110 | 111 | For colors specified via hexadecimal values, this module will accept 112 | input in the following formats: 113 | 114 | * A hash mark (#) followed by three hexadecimal digits, where letters 115 | may be upper- or lower-case. 116 | 117 | * A hash mark (#) followed by six hexadecimal digits, where letters 118 | may be upper- or lower-case. 119 | 120 | For output which consists of a color specified via hexadecimal values, 121 | and for functions which perform intermediate conversion to hexadecimal 122 | before returning a result in another format, this module always 123 | normalizes such values to the following format: 124 | 125 | * A hash mark (#) followed by six hexadecimal digits, with letters 126 | forced to lower-case. 127 | 128 | The function ``normalize_hex()`` in this module can be used to perform 129 | this normalization manually if desired; see its documentation for an 130 | explanation of the normalization process. 131 | 132 | For colors specified via predefined names, this module will accept 133 | input in the following formats: 134 | 135 | * An entirely lower-case name, such as ``aliceblue``. 136 | 137 | * A name using initial capitals, such as ``AliceBlue``. 138 | 139 | For output which consists of a color specified via a predefined name, 140 | and for functions which perform intermediate conversion to a 141 | predefined name before returning a result in another format, this 142 | module always normalizes such values to be entirely lower-case. 143 | 144 | Mappings of color names 145 | ----------------------- 146 | 147 | For each set of defined color names -- HTML 4, CSS 2, CSS 2.1 and CSS 148 | 3 -- this module exports two mappings: one of normalized color names 149 | to normalized hexadecimal values, and one of normalized hexadecimal 150 | values to normalized color names. These eight mappings are as follows: 151 | 152 | ``html4_names_to_hex`` 153 | Mapping of normalized HTML 4 color names to normalized hexadecimal 154 | values. 155 | 156 | ``html4_hex_to_names`` 157 | Mapping of normalized hexadecimal values to normalized HTML 4 158 | color names. 159 | 160 | ``css2_names_to_hex`` 161 | Mapping of normalized CSS 2 color names to normalized hexadecimal 162 | values. Because CSS 2 defines the same set of named colors as HTML 163 | 4, this is merely an alias for ``html4_names_to_hex``. 164 | 165 | ``css2_hex_to_names`` 166 | Mapping of normalized hexadecimal values to normalized CSS 2 color 167 | nams. For the reasons described above, this is merely an alias for 168 | ``html4_hex_to_names``. 169 | 170 | ``css21_names_to_hex`` 171 | Mapping of normalized CSS 2.1 color names to normalized 172 | hexadecimal values. This is identical to ``html4_names_to_hex``, 173 | except for one addition: ``orange``. 174 | 175 | ``css21_hex_to_names`` 176 | Mapping of normalized hexadecimal values to normalized CSS 2.1 177 | color names. As above, this is identical to ``html4_hex_to_names`` 178 | except for the addition of ``orange``. 179 | 180 | ``css3_names_to_hex`` 181 | Mapping of normalized CSS3 color names to normalized hexadecimal 182 | values. 183 | 184 | ``css3_hex_to_names`` 185 | Mapping of normalized hexadecimal values to normalized CSS3 color 186 | names. 187 | 188 | """ 189 | 190 | import math 191 | import re 192 | from hashlib import md5 193 | 194 | 195 | def _reversedict(d): 196 | """ 197 | Internal helper for generating reverse mappings; given a 198 | dictionary, returns a new dictionary with keys and values swapped. 199 | 200 | """ 201 | return dict(zip(d.values(), d.keys())) 202 | 203 | HEX_COLOR_RE = re.compile(r'^#([a-fA-F0-9]|[a-fA-F0-9]{3}|[a-fA-F0-9]{6})$') 204 | 205 | SUPPORTED_SPECIFICATIONS = ('html4', 'css2', 'css21', 'css3') 206 | 207 | 208 | ###################################################################### 209 | # Mappings of color names to normalized hexadecimal color values. 210 | ###################################################################### 211 | 212 | 213 | html4_names_to_hex = { 214 | 'aqua': '#00ffff', 215 | 'black': '#000000', 216 | 'blue': '#0000ff', 217 | 'fuchsia': '#ff00ff', 218 | 'green': '#008000', 219 | 'grey': '#808080', 220 | 'lime': '#00ff00', 221 | 'maroon': '#800000', 222 | 'navy': '#000080', 223 | 'olive': '#808000', 224 | 'purple': '#800080', 225 | 'red': '#ff0000', 226 | 'silver': '#c0c0c0', 227 | 'teal': '#008080', 228 | 'white': '#ffffff', 229 | 'yellow': '#ffff00' 230 | } 231 | 232 | css2_names_to_hex = html4_names_to_hex 233 | 234 | css21_names_to_hex = dict(html4_names_to_hex, orange='#ffa500') 235 | 236 | css3_names_to_hex = { 237 | 'aliceblue': '#f0f8ff', 238 | 'antiquewhite': '#faebd7', 239 | 'aqua': '#00ffff', 240 | 'aquamarine': '#7fffd4', 241 | 'azure': '#f0ffff', 242 | 'beige': '#f5f5dc', 243 | 'bisque': '#ffe4c4', 244 | 'black': '#000000', 245 | 'blanchedalmond': '#ffebcd', 246 | 'blue': '#0000ff', 247 | 'blueviolet': '#8a2be2', 248 | 'brown': '#a52a2a', 249 | 'burlywood': '#deb887', 250 | 'cadetblue': '#5f9ea0', 251 | 'chartreuse': '#7fff00', 252 | 'chocolate': '#d2691e', 253 | 'coral': '#ff7f50', 254 | 'cornflowerblue': '#6495ed', 255 | 'cornsilk': '#fff8dc', 256 | 'crimson': '#dc143c', 257 | 'cyan': '#00ffff', 258 | 'darkblue': '#00008b', 259 | 'darkcyan': '#008b8b', 260 | 'darkgoldenrod': '#b8860b', 261 | 'darkgray': '#a9a9a9', 262 | 'darkgrey': '#a9a9a9', 263 | 'darkgreen': '#006400', 264 | 'darkkhaki': '#bdb76b', 265 | 'darkmagenta': '#8b008b', 266 | 'darkolivegreen': '#556b2f', 267 | 'darkorange': '#ff8c00', 268 | 'darkorchid': '#9932cc', 269 | 'darkred': '#8b0000', 270 | 'darksalmon': '#e9967a', 271 | 'darkseagreen': '#8fbc8f', 272 | 'darkslateblue': '#483d8b', 273 | 'darkslategray': '#2f4f4f', 274 | 'darkslategrey': '#2f4f4f', 275 | 'darkturquoise': '#00ced1', 276 | 'darkviolet': '#9400d3', 277 | 'deeppink': '#ff1493', 278 | 'deepskyblue': '#00bfff', 279 | 'dimgray': '#696969', 280 | 'dimgrey': '#696969', 281 | 'dodgerblue': '#1e90ff', 282 | 'firebrick': '#b22222', 283 | 'floralwhite': '#fffaf0', 284 | 'forestgreen': '#228b22', 285 | 'fuchsia': '#ff00ff', 286 | 'gainsboro': '#dcdcdc', 287 | 'ghostwhite': '#f8f8ff', 288 | 'gold': '#ffd700', 289 | 'goldenrod': '#daa520', 290 | 'gray': '#808080', 291 | 'grey': '#808080', 292 | 'green': '#008000', 293 | 'greenyellow': '#adff2f', 294 | 'honeydew': '#f0fff0', 295 | 'hotpink': '#ff69b4', 296 | 'indianred': '#cd5c5c', 297 | 'indigo': '#4b0082', 298 | 'ivory': '#fffff0', 299 | 'khaki': '#f0e68c', 300 | 'lavender': '#e6e6fa', 301 | 'lavenderblush': '#fff0f5', 302 | 'lawngreen': '#7cfc00', 303 | 'lemonchiffon': '#fffacd', 304 | 'lightblue': '#add8e6', 305 | 'lightcoral': '#f08080', 306 | 'lightcyan': '#e0ffff', 307 | 'lightgoldenrodyellow': '#fafad2', 308 | 'lightgray': '#d3d3d3', 309 | 'lightgrey': '#d3d3d3', 310 | 'lightgreen': '#90ee90', 311 | 'lightpink': '#ffb6c1', 312 | 'lightsalmon': '#ffa07a', 313 | 'lightseagreen': '#20b2aa', 314 | 'lightskyblue': '#87cefa', 315 | 'lightslategray': '#778899', 316 | 'lightslategrey': '#778899', 317 | 'lightsteelblue': '#b0c4de', 318 | 'lightyellow': '#ffffe0', 319 | 'lime': '#00ff00', 320 | 'limegreen': '#32cd32', 321 | 'linen': '#faf0e6', 322 | 'magenta': '#ff00ff', 323 | 'maroon': '#800000', 324 | 'mediumaquamarine': '#66cdaa', 325 | 'mediumblue': '#0000cd', 326 | 'mediumorchid': '#ba55d3', 327 | 'mediumpurple': '#9370d8', 328 | 'mediumseagreen': '#3cb371', 329 | 'mediumslateblue': '#7b68ee', 330 | 'mediumspringgreen': '#00fa9a', 331 | 'mediumturquoise': '#48d1cc', 332 | 'mediumvioletred': '#c71585', 333 | 'midnightblue': '#191970', 334 | 'mintcream': '#f5fffa', 335 | 'mistyrose': '#ffe4e1', 336 | 'moccasin': '#ffe4b5', 337 | 'navajowhite': '#ffdead', 338 | 'navy': '#000080', 339 | 'oldlace': '#fdf5e6', 340 | 'olive': '#808000', 341 | 'olivedrab': '#6b8e23', 342 | 'orange': '#ffa500', 343 | 'orangered': '#ff4500', 344 | 'orchid': '#da70d6', 345 | 'palegoldenrod': '#eee8aa', 346 | 'palegreen': '#98fb98', 347 | 'paleturquoise': '#afeeee', 348 | 'palevioletred': '#d87093', 349 | 'papayawhip': '#ffefd5', 350 | 'peachpuff': '#ffdab9', 351 | 'peru': '#cd853f', 352 | 'pink': '#ffc0cb', 353 | 'plum': '#dda0dd', 354 | 'powderblue': '#b0e0e6', 355 | 'purple': '#800080', 356 | 'red': '#ff0000', 357 | 'rosybrown': '#bc8f8f', 358 | 'royalblue': '#4169e1', 359 | 'saddlebrown': '#8b4513', 360 | 'salmon': '#fa8072', 361 | 'sandybrown': '#f4a460', 362 | 'seagreen': '#2e8b57', 363 | 'seashell': '#fff5ee', 364 | 'sienna': '#a0522d', 365 | 'silver': '#c0c0c0', 366 | 'skyblue': '#87ceeb', 367 | 'slateblue': '#6a5acd', 368 | 'slategray': '#708090', 369 | 'slategrey': '#708090', 370 | 'snow': '#fffafa', 371 | 'springgreen': '#00ff7f', 372 | 'steelblue': '#4682b4', 373 | 'tan': '#d2b48c', 374 | 'teal': '#008080', 375 | 'thistle': '#d8bfd8', 376 | 'tomato': '#ff6347', 377 | 'turquoise': '#40e0d0', 378 | 'violet': '#ee82ee', 379 | 'wheat': '#f5deb3', 380 | 'white': '#ffffff', 381 | 'whitesmoke': '#f5f5f5', 382 | 'yellow': '#ffff00', 383 | 'yellowgreen': '#9acd32', 384 | } 385 | 386 | 387 | ###################################################################### 388 | # Mappings of normalized hexadecimal color values to color names. 389 | ###################################################################### 390 | 391 | 392 | html4_hex_to_names = _reversedict(html4_names_to_hex) 393 | 394 | css2_hex_to_names = html4_hex_to_names 395 | 396 | css21_hex_to_names = _reversedict(css21_names_to_hex) 397 | 398 | css3_hex_to_names = _reversedict(css3_names_to_hex) 399 | 400 | 401 | ###################################################################### 402 | # Normalization routines. 403 | ###################################################################### 404 | 405 | 406 | def normalize_hex(hex_value): 407 | """ 408 | Normalize a hexadecimal color value to the following form and 409 | return the result:: 410 | 411 | #[a-f0-9]{6} 412 | 413 | In other words, the following transformations are applied as 414 | needed: 415 | 416 | * If the value contains only three hexadecimal digits, it is 417 | expanded to six. 418 | 419 | * The value is normalized to lower-case. 420 | 421 | If the supplied value cannot be interpreted as a hexadecimal color 422 | value, ``ValueError`` is raised. 423 | 424 | Examples: 425 | 426 | >>> normalize_hex('#09c') 427 | '#0099cc' 428 | >>> normalize_hex('#0099cc') 429 | '#0099cc' 430 | >>> normalize_hex('#09C') 431 | '#0099cc' 432 | >>> normalize_hex('#0099CC') 433 | '#0099cc' 434 | >>> normalize_hex('0099cc') 435 | Traceback (most recent call last): 436 | ... 437 | ValueError: '0099cc' is not a valid hexadecimal color value. 438 | >>> normalize_hex('#0099QX') 439 | Traceback (most recent call last): 440 | ... 441 | ValueError: '#0099QX' is not a valid hexadecimal color value. 442 | >>> normalize_hex('foobarbaz') 443 | Traceback (most recent call last): 444 | ... 445 | ValueError: 'foobarbaz' is not a valid hexadecimal color value. 446 | >>> normalize_hex('#0') 447 | Traceback (most recent call last): 448 | ... 449 | ValueError: '#0' is not a valid hexadecimal color value. 450 | 451 | """ 452 | try: 453 | hex_digits = HEX_COLOR_RE.match(hex_value).groups()[0] 454 | except AttributeError: 455 | raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value) 456 | if len(hex_digits) == 3: 457 | hex_digits = ''.join(map(lambda s: 2 * s, hex_digits)) 458 | elif len(hex_digits) == 1: 459 | hex_digits = hex_digits * 6 460 | return '#%s' % hex_digits.lower() 461 | 462 | 463 | ###################################################################### 464 | # Conversions from color names to various formats. 465 | ###################################################################### 466 | 467 | 468 | def name_to_hex(name, spec='css3'): 469 | """ 470 | Convert a color name to a normalized hexadecimal color value. 471 | 472 | The optional keyword argument ``spec`` determines which 473 | specification's list of color names will be used; valid values are 474 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 475 | ``css3``. 476 | 477 | The color name will be normalized to lower-case before being 478 | looked up, and when no color of that name exists in the given 479 | specification, ``ValueError`` is raised. 480 | 481 | Examples: 482 | 483 | >>> name_to_hex('deepskyblue') 484 | '#00bfff' 485 | >>> name_to_hex('DeepSkyBlue') 486 | '#00bfff' 487 | >>> name_to_hex('white', spec='html4') 488 | '#ffffff' 489 | >>> name_to_hex('white', spec='css2') 490 | '#ffffff' 491 | >>> name_to_hex('white', spec='css21') 492 | '#ffffff' 493 | >>> name_to_hex('white', spec='css3') 494 | '#ffffff' 495 | >>> name_to_hex('white', spec='css4') 496 | Traceback (most recent call last): 497 | ... 498 | TypeError: 'css4' is not a supported specification for color name lookups; supported specifications are: html4, css2, css21, css3. 499 | >>> name_to_hex('deepskyblue', spec='css2') 500 | Traceback (most recent call last): 501 | ... 502 | ValueError: 'deepskyblue' is not defined as a named color in css2. 503 | 504 | """ 505 | if spec not in SUPPORTED_SPECIFICATIONS: 506 | raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, 507 | ', '.join(SUPPORTED_SPECIFICATIONS))) 508 | normalized = name.lower() 509 | try: 510 | hex_value = globals()['%s_names_to_hex' % spec][normalized] 511 | except KeyError: 512 | raise ValueError("'%s' is not defined as a named color in %s." % (name, spec)) 513 | return hex_value 514 | 515 | 516 | def name_to_rgb(name, spec='css3'): 517 | """ 518 | Convert a color name to a 3-tuple of integers suitable for use in 519 | an ``rgb()`` triplet specifying that color. 520 | 521 | The optional keyword argument ``spec`` determines which 522 | specification's list of color names will be used; valid values are 523 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 524 | ``css3``. 525 | 526 | The color name will be normalized to lower-case before being 527 | looked up, and when no color of that name exists in the given 528 | specification, ``ValueError`` is raised. 529 | 530 | Examples: 531 | 532 | >>> name_to_rgb('navy') 533 | (0, 0, 128) 534 | >>> name_to_rgb('cadetblue') 535 | (95, 158, 160) 536 | >>> name_to_rgb('cadetblue', spec='html4') 537 | Traceback (most recent call last): 538 | ... 539 | ValueError: 'cadetblue' is not defined as a named color in html4. 540 | 541 | """ 542 | return hex_to_rgb(name_to_hex(name, spec=spec)) 543 | 544 | 545 | def name_to_rgb_percent(name, spec='css3'): 546 | """ 547 | Convert a color name to a 3-tuple of percentages suitable for use 548 | in an ``rgb()`` triplet specifying that color. 549 | 550 | The optional keyword argument ``spec`` determines which 551 | specification's list of color names will be used; valid values are 552 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 553 | ``css3``. 554 | 555 | The color name will be normalized to lower-case before being 556 | looked up, and when no color of that name exists in the given 557 | specification, ``ValueError`` is raised. 558 | 559 | Examples: 560 | 561 | >>> name_to_rgb_percent('white') 562 | ('100%', '100%', '100%') 563 | >>> name_to_rgb_percent('navy') 564 | ('0%', '0%', '50%') 565 | >>> name_to_rgb_percent('goldenrod') 566 | ('85.49%', '64.71%', '12.5%') 567 | 568 | """ 569 | return rgb_to_rgb_percent(name_to_rgb(name, spec=spec)) 570 | 571 | 572 | ###################################################################### 573 | # Conversions from hexadecimal color values to various formats. 574 | ###################################################################### 575 | 576 | 577 | def hex_to_name(hex_value, spec='css3'): 578 | """ 579 | Convert a hexadecimal color value to its corresponding normalized 580 | color name, if any such name exists. 581 | 582 | The optional keyword argument ``spec`` determines which 583 | specification's list of color names will be used; valid values are 584 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 585 | ``css3``. 586 | 587 | The hexadecimal value will be normalized before being looked up, 588 | and when no color name for the value is found in the given 589 | specification, ``ValueError`` is raised. 590 | 591 | Examples: 592 | 593 | >>> hex_to_name('#000080') 594 | 'navy' 595 | >>> hex_to_name('#000080', spec='html4') 596 | 'navy' 597 | >>> hex_to_name('#000080', spec='css2') 598 | 'navy' 599 | >>> hex_to_name('#000080', spec='css21') 600 | 'navy' 601 | >>> hex_to_name('#8b4513') 602 | 'saddlebrown' 603 | >>> hex_to_name('#8b4513', spec='html4') 604 | Traceback (most recent call last): 605 | ... 606 | ValueError: '#8b4513' has no defined color name in html4. 607 | >>> hex_to_name('#8b4513', spec='css4') 608 | Traceback (most recent call last): 609 | ... 610 | TypeError: 'css4' is not a supported specification for color name lookups; supported specifications are: html4, css2, css21, css3. 611 | 612 | """ 613 | if spec not in SUPPORTED_SPECIFICATIONS: 614 | raise TypeError("'%s' is not a supported specification for color name lookups; supported specifications are: %s." % (spec, 615 | ', '.join(SUPPORTED_SPECIFICATIONS))) 616 | normalized = normalize_hex(hex_value) 617 | try: 618 | name = globals()['%s_hex_to_names' % spec][normalized] 619 | except KeyError: 620 | raise ValueError("'%s' has no defined color name in %s." % (hex_value, spec)) 621 | return name 622 | 623 | 624 | def any_hex_to_name(hex_value): 625 | try: 626 | return hex_to_name(hex_value) 627 | except ValueError: 628 | return hex_value 629 | 630 | 631 | def hex_to_rgb(hex_value): 632 | """ 633 | Convert a hexadecimal color value to a 3-tuple of integers 634 | suitable for use in an ``rgb()`` triplet specifying that color. 635 | 636 | The hexadecimal value will be normalized before being converted. 637 | 638 | Examples: 639 | 640 | >>> hex_to_rgb('#000080') 641 | (0, 0, 128) 642 | >>> hex_to_rgb('#ffff00') 643 | (255, 255, 0) 644 | >>> hex_to_rgb('#f00') 645 | (255, 0, 0) 646 | >>> hex_to_rgb('#deb887') 647 | (222, 184, 135) 648 | 649 | """ 650 | hex_digits = normalize_hex(hex_value) 651 | return tuple(map(lambda s: int(s, 16), 652 | (hex_digits[1:3], hex_digits[3:5], hex_digits[5:7]))) 653 | 654 | 655 | def hex_to_rgb_percent(hex_value): 656 | """ 657 | Convert a hexadecimal color value to a 3-tuple of percentages 658 | suitable for use in an ``rgb()`` triplet representing that color. 659 | 660 | The hexadecimal value will be normalized before converting. 661 | 662 | Examples: 663 | 664 | >>> hex_to_rgb_percent('#ffffff') 665 | ('100%', '100%', '100%') 666 | >>> hex_to_rgb_percent('#000080') 667 | ('0%', '0%', '50%') 668 | 669 | """ 670 | return rgb_to_rgb_percent(hex_to_rgb(hex_value)) 671 | 672 | 673 | ###################################################################### 674 | # Conversions from integer rgb() triplets to various formats. 675 | ###################################################################### 676 | 677 | 678 | def rgb_to_name(rgb_triplet, spec='css3'): 679 | """ 680 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 681 | color triplet, to its corresponding normalized color name, if any 682 | such name exists. 683 | 684 | The optional keyword argument ``spec`` determines which 685 | specification's list of color names will be used; valid values are 686 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 687 | ``css3``. 688 | 689 | If there is no matching name, ``ValueError`` is raised. 690 | 691 | Examples: 692 | 693 | >>> rgb_to_name((0, 0, 0)) 694 | 'black' 695 | >>> rgb_to_name((0, 0, 128)) 696 | 'navy' 697 | >>> rgb_to_name((95, 158, 160)) 698 | 'cadetblue' 699 | 700 | """ 701 | return hex_to_name(rgb_to_hex(rgb_triplet), spec=spec) 702 | 703 | 704 | def rgb_to_hex(rgb_triplet): 705 | """ 706 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 707 | color triplet, to a normalized hexadecimal value for that color. 708 | 709 | Examples: 710 | 711 | >>> rgb_to_hex((255, 255, 255)) 712 | '#ffffff' 713 | >>> rgb_to_hex((0, 0, 128)) 714 | '#000080' 715 | >>> rgb_to_hex((33, 56, 192)) 716 | '#2138c0' 717 | 718 | """ 719 | return '#%02x%02x%02x' % rgb_triplet 720 | 721 | 722 | def rgb_to_rgb_percent(rgb_triplet): 723 | """ 724 | Convert a 3-tuple of integers, suitable for use in an ``rgb()`` 725 | color triplet, to a 3-tuple of percentages suitable for use in 726 | representing that color. 727 | 728 | This function makes some trade-offs in terms of the accuracy of 729 | the final representation; for some common integer values, 730 | special-case logic is used to ensure a precise result (e.g., 731 | integer 128 will always convert to '50%', integer 32 will always 732 | convert to '12.5%'), but for all other values a standard Python 733 | ``float`` is used and rounded to two decimal places, which may 734 | result in a loss of precision for some values. 735 | 736 | Examples: 737 | 738 | >>> rgb_to_rgb_percent((255, 255, 255)) 739 | ('100%', '100%', '100%') 740 | >>> rgb_to_rgb_percent((0, 0, 128)) 741 | ('0%', '0%', '50%') 742 | >>> rgb_to_rgb_percent((33, 56, 192)) 743 | ('12.94%', '21.96%', '75.29%') 744 | >>> rgb_to_rgb_percent((64, 32, 16)) 745 | ('25%', '12.5%', '6.25%') 746 | 747 | """ 748 | # In order to maintain precision for common values, 749 | # 256 / 2**n is special-cased for values of n 750 | # from 0 through 4, as well as 0 itself. 751 | specials = {255: '100%', 128: '50%', 64: '25%', 752 | 32: '12.5%', 16: '6.25%', 0: '0%'} 753 | return tuple(map(lambda d: specials.get(d, '%.02f%%' % ((d / 255.0) * 100)), 754 | rgb_triplet)) 755 | 756 | 757 | ###################################################################### 758 | # Conversions from percentage rgb() triplets to various formats. 759 | ###################################################################### 760 | 761 | 762 | def rgb_percent_to_name(rgb_percent_triplet, spec='css3'): 763 | """ 764 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 765 | color triplet, to its corresponding normalized color name, if any 766 | such name exists. 767 | 768 | The optional keyword argument ``spec`` determines which 769 | specification's list of color names will be used; valid values are 770 | ``html4``, ``css2``, ``css21`` and ``css3``, and the default is 771 | ``css3``. 772 | 773 | If there is no matching name, ``ValueError`` is raised. 774 | 775 | Examples: 776 | 777 | >>> rgb_percent_to_name(('0%', '0%', '0%')) 778 | 'black' 779 | >>> rgb_percent_to_name(('0%', '0%', '50%')) 780 | 'navy' 781 | >>> rgb_percent_to_name(('85.49%', '64.71%', '12.5%')) 782 | 'goldenrod' 783 | 784 | """ 785 | return rgb_to_name(rgb_percent_to_rgb(rgb_percent_triplet), spec=spec) 786 | 787 | 788 | def rgb_percent_to_hex(rgb_percent_triplet): 789 | """ 790 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 791 | color triplet, to a normalized hexadecimal color value for that 792 | color. 793 | 794 | Examples: 795 | 796 | >>> rgb_percent_to_hex(('100%', '100%', '0%')) 797 | '#ffff00' 798 | >>> rgb_percent_to_hex(('0%', '0%', '50%')) 799 | '#000080' 800 | >>> rgb_percent_to_hex(('85.49%', '64.71%', '12.5%')) 801 | '#daa520' 802 | 803 | """ 804 | return rgb_to_hex(rgb_percent_to_rgb(rgb_percent_triplet)) 805 | 806 | 807 | def _percent_to_integer(percent): 808 | """ 809 | Internal helper for converting a percentage value to an integer 810 | between 0 and 255 inclusive. 811 | 812 | """ 813 | num = float(percent.split('%')[0]) / 100.0 * 255 814 | e = num - math.floor(num) 815 | return e < 0.5 and int(math.floor(num)) or int(math.ceil(num)) 816 | 817 | 818 | def rgb_percent_to_rgb(rgb_percent_triplet): 819 | """ 820 | Convert a 3-tuple of percentages, suitable for use in an ``rgb()`` 821 | color triplet, to a 3-tuple of integers suitable for use in 822 | representing that color. 823 | 824 | Some precision may be lost in this conversion. See the note 825 | regarding precision for ``rgb_to_rgb_percent()`` for details; 826 | generally speaking, the following is true for any 3-tuple ``t`` of 827 | integers in the range 0...255 inclusive:: 828 | 829 | t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) 830 | 831 | Examples: 832 | 833 | >>> rgb_percent_to_rgb(('100%', '100%', '100%')) 834 | (255, 255, 255) 835 | >>> rgb_percent_to_rgb(('0%', '0%', '50%')) 836 | (0, 0, 128) 837 | >>> rgb_percent_to_rgb(('25%', '12.5%', '6.25%')) 838 | (64, 32, 16) 839 | >>> rgb_percent_to_rgb(('12.94%', '21.96%', '75.29%')) 840 | (33, 56, 192) 841 | 842 | """ 843 | return tuple(map(_percent_to_integer, rgb_percent_triplet)) 844 | 845 | 846 | def whatever_to_rgb(string): 847 | """ 848 | Converts CSS3 color or a hex into rgb triplet; hash of string if fails. 849 | """ 850 | string = string.strip().lower() 851 | try: 852 | return name_to_rgb(string) 853 | except ValueError: 854 | try: 855 | return hex_to_rgb(string) 856 | except ValueError: 857 | try: 858 | if string[:3] == "rgb": 859 | return tuple([float(i) for i in string[4:-1].split(",")][0:3]) 860 | except: 861 | return hex_to_rgb("#" + md5(string).hexdigest()[:6]) 862 | 863 | 864 | def whatever_to_hex(string): 865 | if type(string) == tuple: 866 | return cairo_to_hex(string).upper() 867 | return rgb_to_hex(whatever_to_rgb(string)).upper() 868 | 869 | 870 | def whatever_to_cairo(string): 871 | a = whatever_to_rgb(string) 872 | return a[0] / 255., a[1] / 255., a[2] / 255. 873 | 874 | 875 | def cairo_to_hex(cairo): 876 | return rgb_to_hex((cairo[0] * 255., cairo[1] * 255., cairo[2] * 255.)) 877 | 878 | if __name__ == '__main__': 879 | import doctest 880 | doctest.testmod() 881 | --------------------------------------------------------------------------------