├── 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 |
--------------------------------------------------------------------------------