Compare commits
615 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
85881d99dd | ||
|
d3a5c97143 | ||
|
8ed3ad15c1 | ||
|
72f79efbdd | ||
|
161516b721 | ||
|
e48d5ff993 | ||
|
31abb8bee0 | ||
|
e2177cf443 | ||
|
664b45cdde | ||
|
368dad0d11 | ||
|
4dfe5361b3 | ||
|
3596991362 | ||
|
bf4c67034a | ||
|
ad5d919139 | ||
|
9fb894181d | ||
|
ffdf2d3c9b | ||
|
cbe66020f9 | ||
|
056081ace6 | ||
|
a48ba58cae | ||
|
9f67f1bdde | ||
|
55c0786130 | ||
|
fef8cb369f | ||
|
d35e54859a | ||
|
daafa9fbf2 | ||
|
34799db0cc | ||
|
41133a65ad | ||
|
1016f6870c | ||
|
72f93abf16 | ||
|
a3538d9007 | ||
|
66cc146a02 | ||
|
aab27e6be8 | ||
|
cea1e7c620 | ||
|
f8a9995050 | ||
|
0de8b82ff9 | ||
|
7d00bfdf23 | ||
|
3db2fa322b | ||
|
f4a2605a23 | ||
|
d95967247d | ||
|
da8707a17f | ||
|
3f9f6ef40d | ||
|
b051ae284e | ||
|
ea880ccf7b | ||
|
ab412165b2 | ||
|
7974fb0a05 | ||
|
59c0208caa | ||
|
9b5fb6a408 | ||
|
945f5efb74 | ||
|
decdcc5d46 | ||
|
34bd0a045d | ||
|
ed819c3b51 | ||
|
a42afef5a8 | ||
|
410ecfd3c7 | ||
|
8c75b4b0c6 | ||
|
fd94fbd2e6 | ||
|
5c4bed9593 | ||
|
2c092f6b39 | ||
|
cc2104eb49 | ||
|
c780998dc8 | ||
|
0063ddba7f | ||
|
510847a3b2 | ||
|
0c1990f655 | ||
|
7315428079 | ||
|
515fbd0991 | ||
|
975abe9b5c | ||
|
b6186d884c | ||
|
e6ba32bc07 | ||
|
fd4caf7595 | ||
|
45e59f6a5b | ||
|
dfecbbb976 | ||
|
50fa97564e | ||
|
8f9982c313 | ||
|
01c6f0c6e5 | ||
|
7e02aac641 | ||
|
4f792fbead | ||
|
9e12a3b0d8 | ||
|
09d6384b1f | ||
|
11ffba0a8e | ||
|
199d41f20d | ||
|
e931f91475 | ||
|
11d597e733 | ||
|
1960aca276 | ||
|
f17aea8657 | ||
|
dfaed546e0 | ||
|
803f0c0a49 | ||
|
0d6f9d4634 | ||
|
c042733845 | ||
|
1fc486b1b9 | ||
|
b50ee48386 | ||
|
cf5886579f | ||
|
72d005a082 | ||
|
9e8c90b6f9 | ||
|
d3e23dcb5d | ||
|
176886f1ad | ||
|
673cf38121 | ||
|
860bc0adeb | ||
|
be56e24d9a | ||
|
27850ed122 | ||
|
0d2dddf978 | ||
|
fba91a0633 | ||
|
1612b5a8b7 | ||
|
4f13aabb6c | ||
|
8050ec843f | ||
|
885849fe82 | ||
|
14c0d3f550 | ||
|
0f46b57020 | ||
|
152954ee70 | ||
|
2631c928b7 | ||
|
9e3ae6e6fd | ||
|
eae8886b95 | ||
|
00c7a631b1 | ||
|
bd03e0c454 | ||
|
981e117731 | ||
|
d2a557acd9 | ||
|
f91ac22bfc | ||
|
db1cf0a8aa | ||
|
46b3f4857f | ||
|
b5b03cc8d7 | ||
|
8f86216fe0 | ||
|
bbfe3b3084 | ||
|
56d69ab68a | ||
|
e491c64ceb | ||
|
26c30d2fb5 | ||
|
6ae21b3ee0 | ||
|
12d1b1a4fe | ||
|
8a5f75546f | ||
|
74d807a3ae | ||
|
bf6537071a | ||
|
1cb891ef92 | ||
|
79a770f0af | ||
|
44610ab1c4 | ||
|
7c35dda115 | ||
|
146976c8a3 | ||
|
ae111041dd | ||
|
2e00705b64 | ||
|
b19ade3850 | ||
|
fe770630bb | ||
|
8df31b4fe6 | ||
|
e29b900342 | ||
|
150e7166f6 | ||
|
1d6e4a6f5d | ||
|
52343ff833 | ||
|
544491b91d | ||
|
7d3ba895f5 | ||
|
d5bf19a64f | ||
|
5890802d6d | ||
|
26a918d2e6 | ||
|
1ba5e0035e | ||
|
1e0657ee1e | ||
|
2a5c85a9d5 | ||
|
cbd4c0250d | ||
|
08ca40d3f7 | ||
|
9386a56464 | ||
|
d24998705d | ||
|
4de08ce68d | ||
|
9f65589279 | ||
|
6f2ec7b335 | ||
|
89a282be82 | ||
|
51baca34ae | ||
|
481a0fc406 | ||
|
60e33e609b | ||
|
339d781ca6 | ||
|
b7819be42d | ||
|
c570c2cd0e | ||
|
30b4fe1fc6 | ||
|
1ddefbe8eb | ||
|
57ddf46813 | ||
|
ba720bcb84 | ||
|
d608fa93b8 | ||
|
bb81e5c785 | ||
|
bde0d0e2ab | ||
|
145f1cc0b1 | ||
|
2273e0174f | ||
|
b9a00ed68b | ||
|
a40a87cd39 | ||
|
88bddbabc4 | ||
|
ef59be8abf | ||
|
b83d9c4037 | ||
|
678157b478 | ||
|
1a21c88484 | ||
|
ddb4bd338b | ||
|
6fad5676b4 | ||
|
6dc65cc991 | ||
|
10b81aa2c8 | ||
|
2fc7473a8c | ||
|
b675a648c0 | ||
|
e0fe7bce17 | ||
|
eb2623d677 | ||
|
302d409fab | ||
|
c5d8f4510e | ||
|
cd18bea7ba | ||
|
55b70b86c8 | ||
|
d9f97d3202 | ||
|
a52412c41e | ||
|
be4e687aa3 | ||
|
4b386326c7 | ||
|
4f771ed2a5 | ||
|
580e946cc0 | ||
|
5dc4610785 | ||
|
9d44691111 | ||
|
bb2c045325 | ||
|
9f18e9cc2c | ||
|
c6f787f761 | ||
|
cfab4f6369 | ||
|
fb06bb4bb1 | ||
|
87f57bdb38 | ||
|
b9309a7f80 | ||
|
96886c73c6 | ||
|
443b81012e | ||
|
f691a47306 | ||
|
02a657f373 | ||
|
a5e5c045c9 | ||
|
c288d09dcc | ||
|
5c8963f02d | ||
|
12a3d6cad2 | ||
|
bbdb6e5988 | ||
|
f6a76ec666 | ||
|
83c478875f | ||
|
449be47e91 | ||
|
a240695b65 | ||
|
84618be5df | ||
|
f333705cc2 | ||
|
f45aa875b7 | ||
|
834985ba81 | ||
|
d1c6c9fb82 | ||
|
0b584a84e7 | ||
|
fadcb3391d | ||
|
66d7c45a0c | ||
|
65a7d6589e | ||
|
ce4c61cc7f | ||
|
677ed7122a | ||
|
f3fd24583d | ||
|
5a7429d30d | ||
|
cb177b7a24 | ||
|
15140e49ee | ||
|
c4f02e4681 | ||
|
60030b1e69 | ||
|
21f03b3f9e | ||
|
922740da5a | ||
|
c1d750a246 | ||
|
72a79ab073 | ||
|
f6dea7fcb3 | ||
|
4b5e217107 | ||
|
6b51cd370b | ||
|
1c5c84587e | ||
|
2c9692b029 | ||
|
3a6cae7836 | ||
|
ba51c74771 | ||
|
bd454b4b6b | ||
|
b63fe0a9af | ||
|
525bdc5bef | ||
|
5938ebb609 | ||
|
d99c5d6c14 | ||
|
2bfc8a154b | ||
|
5a8461a6c1 | ||
|
dfacf064e8 | ||
|
6562d77935 | ||
|
2ead8dafa2 | ||
|
b18f162d87 | ||
|
8497bd36f7 | ||
|
2729aefd6e | ||
|
7a3b659d4e | ||
|
50a27c213c | ||
|
d1af35763a | ||
|
92b67a0a73 | ||
|
d170825685 | ||
|
d9e412696d | ||
|
08db949fbf | ||
|
d788fb8f07 | ||
|
e32aeb9fbb | ||
|
d7d49214cd | ||
|
ca42300648 | ||
|
393fc249b2 | ||
|
2ae7743c7d | ||
|
1d752708c4 | ||
|
4f963b8bb3 | ||
|
bc9d67b328 | ||
|
656b6db9a3 | ||
|
a57a162248 | ||
|
60396cbeef | ||
|
f88711db72 | ||
|
4f0e998dab | ||
|
fd3338ccb5 | ||
|
3d87cd490d | ||
|
1d637717d5 | ||
|
45fae55ac0 | ||
|
bbeff81a16 | ||
|
afac483b35 | ||
|
66b0c1ff7a | ||
|
93264c6e41 | ||
|
97f03a2eb3 | ||
|
45b5c107af | ||
|
99ee75ab8b | ||
|
84e0628a8e | ||
|
15ab78a3a8 | ||
|
bb153521e2 | ||
|
ff0fc2b1c8 | ||
|
264925324f | ||
|
d81b4b9d18 | ||
|
4cc262b563 | ||
|
4203578093 | ||
|
cfc90da91e | ||
|
8786bf51c7 | ||
|
ae95aa7575 | ||
|
b113bfea99 | ||
|
0f65b869fd | ||
|
c21a763dc7 | ||
|
207b120dee | ||
|
49b2324ea1 | ||
|
314cef0c75 | ||
|
04b1602310 | ||
|
2f76fec686 | ||
|
435452ba50 | ||
|
5d626d3c10 | ||
|
49c81edd3a | ||
|
05797dd711 | ||
|
86abdcd700 | ||
|
d6585d3691 | ||
|
acf94e5fab | ||
|
60661b68c3 | ||
|
5e2ea67df9 | ||
|
26dbcf3ca3 | ||
|
3382bfa29f | ||
|
7ac2d81062 | ||
|
caa639beb8 | ||
|
039031b68d | ||
|
19bf87cf3a | ||
|
8efc1c5d5e | ||
|
e8566e817e | ||
|
7deb1b86e0 | ||
|
c5a67fa938 | ||
|
be78202e0b | ||
|
0e0bac0e5c | ||
|
6ca0c705c8 | ||
|
4c044f93fe | ||
|
48d89889fe | ||
|
47464fc18d | ||
|
74fa914c8c | ||
|
55fbafe0d0 | ||
|
5fa4478d40 | ||
|
633754306e | ||
|
3f70b8a36c | ||
|
16f60edc50 | ||
|
76b271ebc5 | ||
|
3fd91ccb6b | ||
|
e2764d12f1 | ||
|
8c51c59fe9 | ||
|
cf8c11f038 | ||
|
31504baabe | ||
|
8f8c1ad39b | ||
|
494c07d383 | ||
|
de40fd4e42 | ||
|
6a5309e22e | ||
|
56ac678c0a | ||
|
1769cb4a59 | ||
|
9deb60cd08 | ||
|
a687bec9b6 | ||
|
c9d88add12 | ||
|
6ce476d2a2 | ||
|
0d686bb8d8 | ||
|
f3bde1fde3 | ||
|
bfc9d40f43 | ||
|
f0e245183a | ||
|
4d4abb27b5 | ||
|
6eda91a541 | ||
|
87d4f9627b | ||
|
29b641c72d | ||
|
6f687ff9e3 | ||
|
ece3eb3b0e | ||
|
d97286de57 | ||
|
c1e8c3b8f3 | ||
|
3cefa968a0 | ||
|
b7773c6452 | ||
|
9ed9c2b028 | ||
|
fc500db69b | ||
|
0233c523ea | ||
|
4b8256e0f8 | ||
|
551571fc17 | ||
|
43073fa1e8 | ||
|
baab7dd0ec | ||
|
f87e8adc95 | ||
|
3f31bcbe5f | ||
|
9f00195100 | ||
|
6fed91d728 | ||
|
a8133e0d77 | ||
|
06b147323f | ||
|
47882ccb00 | ||
|
73e5178f1f | ||
|
8846bfbbcd | ||
|
16db1c5b03 | ||
|
800122e1af | ||
|
3ac86f5fd3 | ||
|
bd17eed9f5 | ||
|
c707188ed5 | ||
|
539d293388 | ||
|
12cd05764b | ||
|
92d239b7f8 | ||
|
a7c1e0bc49 | ||
|
a55b4ca0e9 | ||
|
958b61c343 | ||
|
0a9cb6afe7 | ||
|
67b66b5568 | ||
|
9967393820 | ||
|
75f55f8d04 | ||
|
2aba917e3d | ||
|
f2a6922586 | ||
|
f6c07afee6 | ||
|
22f5d0cc45 | ||
|
f4722f516e | ||
|
607c4dba5a | ||
|
b3b7fec337 | ||
|
5145655c46 | ||
|
a106e2768f | ||
|
04cf9013a8 | ||
|
3c4baaf8cb | ||
|
667fd483cc | ||
|
f693f062ec | ||
|
89f8edbddc | ||
|
73f544333a | ||
|
d4fe84a7cf | ||
|
84a34be10a | ||
|
180cd0cc6e | ||
|
78ea179c46 | ||
|
9eee907467 | ||
|
8603799fa7 | ||
|
78b3a5a5d4 | ||
|
71547d059f | ||
|
0ebfa5f258 | ||
|
4ef52a82af | ||
|
ec0699dd45 | ||
|
e7ba697fc4 | ||
|
0d294c1075 | ||
|
23d11fedc6 | ||
|
e3e2b42277 | ||
|
675158cba9 | ||
|
1e6ede278f | ||
|
75875e2781 | ||
|
cd948535a5 | ||
|
33d325b0fc | ||
|
4825f9aee8 | ||
|
7d7cc2653c | ||
|
0c04ad07b6 | ||
|
1c569ce39d | ||
|
f41312597c | ||
|
4181df378f | ||
|
13bc8c3488 | ||
|
4c7af7f492 | ||
|
19ac7b2fb0 | ||
|
217498a207 | ||
|
25a2940ebc | ||
|
2fcbdaacfd | ||
|
8ef4efbe39 | ||
|
a21a195f1b | ||
|
dc798c0e07 | ||
|
410f47b5ce | ||
|
1eaf7e4fbf | ||
|
e1a484018f | ||
|
8fc2c06b45 | ||
|
5cbe78457c | ||
|
840e16a3f2 | ||
|
f9fcffeeba | ||
|
5978135bb6 | ||
|
dde52e43aa | ||
|
1efa60757b | ||
|
13448ee297 | ||
|
cce8440755 | ||
|
00e797dc2d | ||
|
e7658c618e | ||
|
45657268f9 | ||
|
1d9b5d93ca | ||
|
4b7fc5fb57 | ||
|
67f007064a | ||
|
896b247fe9 | ||
|
3d216f6295 | ||
|
afdac4b94f | ||
|
e055301fa8 | ||
|
25343589ab | ||
|
0560ae9f86 | ||
|
4a9b5b5939 | ||
|
101026fe3c | ||
|
912988e174 | ||
|
033a5cd5a5 | ||
|
6a794cd610 | ||
|
63152ef4e3 | ||
|
31ea5ff6c6 | ||
|
03867bf61b | ||
|
829fe84baf | ||
|
f4132221d6 | ||
|
5e5de824de | ||
|
02b8cae3c7 | ||
|
0a10327fb1 | ||
|
bd3073bbed | ||
|
73a7c6b87a | ||
|
788aa03e80 | ||
|
874f51870a | ||
|
26e690dc0d | ||
|
5b024a1465 | ||
|
0c4de0dc38 | ||
|
0377e809ea | ||
|
b24288b7ef | ||
|
42e9c476f5 | ||
|
dd8169ea84 | ||
|
6d8c963af2 | ||
|
53e7ece4cc | ||
|
3710517fdc | ||
|
6fb339e21c | ||
|
4bb0fc0ebe | ||
|
bfc92c26bc | ||
|
f11259d734 | ||
|
2481c21365 | ||
|
e7a3d65cad | ||
|
f52f7da5eb | ||
|
4841e40a72 | ||
|
262aa3c1df | ||
|
05718df884 | ||
|
d1574100af | ||
|
255b0219b6 | ||
|
d57006dd97 | ||
|
b165c2080d | ||
|
f53a226505 | ||
|
9a27483af6 | ||
|
651a650f2e | ||
|
a66fa6c462 | ||
|
1f28f7b79e | ||
|
672a271cbe | ||
|
d244fd0da8 | ||
|
d7e6cdf082 | ||
|
bdfcd7aa6d | ||
|
28ab872e72 | ||
|
ff0f0fc9f9 | ||
|
991feb9ba3 | ||
|
6132d82359 | ||
|
51c3ab65d7 | ||
|
ea89af6ae2 | ||
|
8a97258025 | ||
|
590b2092f7 | ||
|
265554813b | ||
|
a941bda795 | ||
|
e27ce12ea0 | ||
|
593092fedd | ||
|
481b19f30e | ||
|
f00c048b19 | ||
|
42784dcca2 | ||
|
7611f717c8 | ||
|
5ff9051104 | ||
|
fd35ddb5c6 | ||
|
4c3aa03012 | ||
|
0f86eb910e | ||
|
a822e0b0b4 | ||
|
c7a3ded1a2 | ||
|
c9129188c7 | ||
|
17d569039b | ||
|
056429759c | ||
|
d58712ced6 | ||
|
1645df71a4 | ||
|
c38cc9c58b | ||
|
0c838290dc | ||
|
b839983a9f | ||
|
2891afb707 | ||
|
40b032c9bb | ||
|
bea9b43787 | ||
|
c2919a9177 | ||
|
d07a7c813b | ||
|
fef914cdc2 | ||
|
d353fc48cb | ||
|
ad67549f08 | ||
|
f2cd50bdc7 | ||
|
306500bd2f | ||
|
2c2a45411e | ||
|
3e3b4f09bc | ||
|
66012862db | ||
|
5efa4bd40e | ||
|
9d2863b6d2 | ||
|
6eff56e2e2 | ||
|
262ef7e722 | ||
|
9960cd2667 | ||
|
afad11d3fe | ||
|
a7748e6392 | ||
|
34deb99997 | ||
|
c48a02196e | ||
|
2c93bed235 | ||
|
d17416b54f | ||
|
3c79836a9e | ||
|
c7b7eca95d | ||
|
70d5240402 | ||
|
7ebb0e878b | ||
|
1c8e8ff795 | ||
|
eb386093f0 | ||
|
b644e9588b | ||
|
795f2c82d0 | ||
|
5e63b49558 | ||
|
26951e6589 | ||
|
8c2876c7a8 | ||
|
378a43e730 | ||
|
107abdf2e8 | ||
|
421ca69233 | ||
|
c1b4903c0f | ||
|
535cbb881a | ||
|
2e750634bf | ||
|
f01d9dbafa | ||
|
75aa04ef72 | ||
|
0b244fd13d | ||
|
3f47fa1898 | ||
|
d01c45d714 | ||
|
ed19aa1bc1 | ||
|
14cce7def8 | ||
|
8dc0f92a0d | ||
|
fe8efcc1e4 | ||
|
f2e7ff151c | ||
|
93cad03edb | ||
|
c7a7ae720a | ||
|
f4b09ce286 | ||
|
d334bdc6ba | ||
|
fe0cc34f8f | ||
|
90fbe75213 | ||
|
67ddce9ab6 |
7
.gitignore
vendored
7
.gitignore
vendored
@ -1 +1,6 @@
|
||||
/npm_modules
|
||||
/node_modules
|
||||
.DS_Store
|
||||
test/rendering/layers/
|
||||
test/rendering/cache/
|
||||
test/rendering-mss/npm-debug.log
|
||||
.idea/
|
||||
|
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@ -0,0 +1,9 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- '6'
|
||||
- '8'
|
||||
- '10'
|
||||
|
||||
script:
|
||||
- npm test
|
9
CHANGELOG.carto.md
Normal file
9
CHANGELOG.carto.md
Normal file
@ -0,0 +1,9 @@
|
||||
## CARTO's Changelog
|
||||
|
||||
## 0.15.1-cdb5
|
||||
2018-11-20
|
||||
|
||||
* Support Node.js 6, 8 and, 10
|
||||
* Drop support for Node.js 0.10 and 0.11
|
||||
* Add package-lock.json
|
||||
* Add CHANGELOG.carto.md
|
151
CHANGELOG.md
151
CHANGELOG.md
@ -1,5 +1,156 @@
|
||||
## Changelog
|
||||
|
||||
## 0.14.0
|
||||
|
||||
* Support for Mapnik 3.x
|
||||
* Bump `mapnik-reference` dependency to ~6.0.1.
|
||||
|
||||
## 0.13.0
|
||||
|
||||
* Allows optional args in transforms.
|
||||
* Bump `mapnik-reference` dependency to 5.1.x.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
* Drop mml2json and xml2js dependency.
|
||||
|
||||
## 0.11.0
|
||||
|
||||
* Switch API to be synchronous. All errors should be caught using try/catch now.
|
||||
|
||||
## 0.10.0
|
||||
|
||||
* Remove automatic inclusion of `maximum-extent` on Map element to allow geometries that are buffered past extent bounds (e.g. dateline).
|
||||
* Bump `mapnik-reference` dependency to ~5.0.9 (with `shield-halo-rasterizer`)
|
||||
|
||||
## 0.9.6
|
||||
|
||||
* Fixed support for `text-face-name` values with `&` like `El&Font Bubble`
|
||||
* Fixed support for filtering on fields containing single quotes. Now `#layer[name="it's"] { ... }` is possible.
|
||||
* Fixed support for filtering on fields containing `&`. Now `#layer["Hello&Goodbye"="yes"] { ... }` is possible.
|
||||
* Added support for exponential notation in filters. Now `#layer[value = 1.2e3] { ... }` is possible.
|
||||
* Bump `mapnik-reference` dependency to ~5.0.8 (with support for Mapnik v2.3.0 and 3.x)
|
||||
|
||||
## 0.9.5
|
||||
|
||||
* Various speed optimizations to help address #20 (#231)
|
||||
* Fixed support for fields that contain the word `zoom` in them (previous clashed with `zoom` keyword)
|
||||
* Fixed support for a space in front of `zoom` keyword (#288)
|
||||
* Improved error messages when color functions encounter invalid color (#309)
|
||||
* The `carto` command line tool now exits cleanly when millstone is used
|
||||
* The `carto` command line tool now only localized with millstone if requested (#243)
|
||||
* Added man page for `carto` (#257)
|
||||
* Fix repeated comments in selectors. Fixes #260
|
||||
* Fixed `image-filter` duplication (#270)
|
||||
* Quote all needed XML chars. See #263.
|
||||
* Added higher tolerance for various characters in field names (#230)
|
||||
* Bump `mapnik-reference` dependency to ~5.0.7 (with support for Mapnik v2.2.0)
|
||||
* Adds compatibility with screen units.
|
||||
* Fixed ability to use carto as global module (#236)
|
||||
* Now using 'console' instead of `util` for `stderr` (#217)
|
||||
|
||||
## 0.9.4
|
||||
|
||||
* Fixes nesting of regex calls
|
||||
|
||||
## 0.9.3
|
||||
|
||||
* Allows `text-face-name` properties to be unquoted
|
||||
* Detects inline Format XML tags in `text-name` and passes such output
|
||||
straight to XML for advanced text names.
|
||||
* Fixes bugs around concatenation of strings in expressions
|
||||
* Fixes parsing of comments in between selectors
|
||||
* Fixes parsing of whitespace in calls
|
||||
* Improved error messages for unknown properties - advises user on
|
||||
the property name most closely matching the incorrect input.
|
||||
* Improved errors for calls, advises user on number of arguments
|
||||
* Fixes instance inheritance - thanks @gravitystorm!
|
||||
|
||||
## 0.9.2
|
||||
|
||||
Tagged Sept 6, 2012
|
||||
|
||||
* Bump `mapnik-reference` dependency to ~5.0.0
|
||||
* Better support for unsigned types in certain Mapnik styling properties
|
||||
|
||||
## 0.9.1
|
||||
|
||||
Tagged Aug 15, 2012
|
||||
|
||||
* Improved error handling for different target `mapnik-reference` versions (strk)
|
||||
* Bump `mapnik-reference` dependency to ~4.0.3
|
||||
* Fixed handling of image-filter syntax as per [Mapnik changes](https://github.com/mapnik/mapnik/issues/1384)
|
||||
|
||||
## 0.9.0
|
||||
|
||||
* Bump `mapnik-reference` dependency to ~4.0.0 to pull in new properties.
|
||||
* Adapted to `comp-op` rename upstream in `mapnik-reference`.
|
||||
* Adapted to `transform` rename upstream in `mapnik-reference` and Mapnik.
|
||||
|
||||
## 0.8.1
|
||||
|
||||
* Bump `mapnik-reference` dependency to ~3.1.0 to pull in new properties.
|
||||
|
||||
## 0.8.0
|
||||
|
||||
* Adds the modulus operator `%` as an option
|
||||
* Adds a new field-type like `[FIELD]` instead of "[FIELD]"
|
||||
* Supports function syntax for transforms, optionally with variables and arguments.
|
||||
|
||||
### 0.7.1
|
||||
|
||||
* Updated mapnik-reference to `~2.2.1`
|
||||
* Added support for `status` parameter on layers.
|
||||
* Command line `carto` program gained `--nosymlink` option to pass to millstone to use absolute paths instead of symlinking files.
|
||||
* Removed unsupported mixin code.
|
||||
|
||||
### 0.7.0
|
||||
|
||||
* Updated mapnik-reference to `~2.1.0`
|
||||
* Support an `opacity` property on any style that is a style-level property
|
||||
|
||||
### 0.6.0
|
||||
|
||||
* Bump `mapnik-reference` dependency to 1.0.0 to allow for using `buffer-size` in the
|
||||
`Map` element.
|
||||
|
||||
### 0.5.0
|
||||
|
||||
* Now uses the [mapnik-reference](https://github.com/mapnik/mapnik-reference) npm module
|
||||
instead of copying `reference.json` when it's updated
|
||||
* Adds a second parameter to `carto.Renderer` - an object which has a key `mapnik_version`
|
||||
that specifies the version of Mapnik this stylesheet should target.
|
||||
|
||||
### 0.4.10
|
||||
|
||||
* Updated reference.json
|
||||
|
||||
### 0.4.9
|
||||
|
||||
* Render TileJSON, Mapnik options to Mapnik XML parameters.
|
||||
|
||||
### 0.4.8
|
||||
|
||||
* Updated reference.json
|
||||
|
||||
### 0.4.7
|
||||
|
||||
* Removed deprecation warnings re: sys/util
|
||||
* Updated reference.json
|
||||
* Updated underscore dependency
|
||||
|
||||
### 0.4.6
|
||||
|
||||
* Node >=v0.6.x compatibility
|
||||
* Dropped cartox
|
||||
* Updated reference.json
|
||||
|
||||
### 0.4.5
|
||||
|
||||
* Fixes text-name with HTML entities
|
||||
* Fixes function calls with incorrect number of arguments
|
||||
* Fixes invalid code segments not having eval
|
||||
|
||||
### 0.4.3
|
||||
|
||||
* Fixes serialization bug with invalid selectors.
|
||||
|
34
DEVELOPING.md
Normal file
34
DEVELOPING.md
Normal file
@ -0,0 +1,34 @@
|
||||
## Developing
|
||||
|
||||
Installing:
|
||||
|
||||
git clone git@github.com:mapbox/carto.git
|
||||
npm install
|
||||
|
||||
Test:
|
||||
|
||||
npm test
|
||||
|
||||
Running the head binary:
|
||||
|
||||
./bin/carto
|
||||
|
||||
## Documentation
|
||||
|
||||
This repository contains auto-generated documentation of the content of Carto
|
||||
that's published on Mapbox.com.
|
||||
|
||||
git fetch origin gh-pages:gh-pages
|
||||
|
||||
Edit `_docs/package.json` to point to the head version of [mapnik-reference](https://github.com/mapnik/mapnik-reference).
|
||||
|
||||
cd _docs
|
||||
npm install
|
||||
node generate.js
|
||||
|
||||
Then run up a directory and run the testing server:
|
||||
|
||||
cd ../
|
||||
jekyll serve -p 4000
|
||||
|
||||
Test the new site at `localhost:4000/carto` and if things look good then git add your changes and push.
|
25
Makefile
25
Makefile
@ -2,21 +2,34 @@
|
||||
# Run all tests
|
||||
#
|
||||
|
||||
expresso = ./node_modules/.bin/expresso
|
||||
docco = ./node_modules/.bin/docco
|
||||
expresso = ./node_modules/.bin/mocha
|
||||
UGLIFYJS=./node_modules/.bin/uglifyjs
|
||||
BROWSERIFY = ./node_modules/.bin/browserify
|
||||
|
||||
dist/carto.js: dist/carto.uncompressed.js $(shell $(BROWSERIFY) --list lib/carto/index.js)
|
||||
$(UGLIFYJS) dist/carto.uncompressed.js > $@
|
||||
|
||||
dist/carto.uncompressed.js: dist $(shell $(BROWSERIFY) --list lib/carto/index.js)
|
||||
$(BROWSERIFY) lib/carto/index.js --exclude node_modules/underscore/underscore.js --standalone carto > $@
|
||||
|
||||
|
||||
lint:
|
||||
./node_modules/.bin/jshint lib/carto/*.js lib/carto/tree/*.js
|
||||
|
||||
ifndef only
|
||||
test:
|
||||
$(expresso) -I lib test/*.test.js
|
||||
@NODE_PATH=./lib:$NODE_PATH $(expresso) -R spec -I lib test/*.test.js
|
||||
else
|
||||
test:
|
||||
$(expresso) -I lib test/${only}.test.js
|
||||
@NODE_PATH=./lib:$NODE_PATH $(expresso) -R spec -I lib test/${only}.test.js
|
||||
endif
|
||||
|
||||
doc:
|
||||
$(docco) lib/carto/*.js lib/carto/tree/*.js
|
||||
check: test
|
||||
|
||||
dist:
|
||||
mkdir -p dist
|
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: test
|
||||
|
279
README.md
279
README.md
@ -1,268 +1,83 @@
|
||||
# carto
|
||||
# CartoCSS
|
||||
|
||||
Is a stylesheet renderer for Mapnik. It's an evolution of the [Cascadenik](https://github.com/mapnik/Cascadenik) idea and language, with an emphasis on speed and flexibility.
|
||||
[![Build Status](https://travis-ci.org/CartoDB/carto.png?branch=master)](https://travis-ci.org/CartoDB/carto)
|
||||
|
||||
## Installation
|
||||
Is as stylesheet renderer for javascript, It's an evolution of the Mapnik renderer from Mapbox.
|
||||
Please, see original [Mapbox repo](http://github.com/mapbox/carto) for more information and credits
|
||||
|
||||
npm install carto
|
||||
## Quick Start
|
||||
|
||||
## MML
|
||||
_incompatibility_
|
||||
```javascript
|
||||
// shader is a CartoCSS object
|
||||
|
||||
* MML files are assumed to be JSON, not XML. The files are near-identical to the XML files accepted by Cascadenik, just translated into JSON.
|
||||
* Carto will not embed files or download URLs for you. Stylesheets should be embedded directly into your MML JSON and any datasources should be paths (relative or absolute) that would be acceptable in Mapnik XML.
|
||||
The [millstone project](https://github.com/mapbox/millstone) aims to fill this need.
|
||||
var cartocss = [
|
||||
'#layer {',
|
||||
' marker-width: [property]',
|
||||
' marker-fill: red',
|
||||
'}'
|
||||
].join('')
|
||||
var shader = new carto.RendererJS().render(cartocss);
|
||||
var layers = shader.getLayers()
|
||||
for (var i = 0; i < layers.length; ++i) {
|
||||
var layer = layers[i];
|
||||
console.log("layer name: ", layer.fullName())
|
||||
console.log("- frames: ", layer.frames())
|
||||
console.log("- attachment: ", layer.attachment())
|
||||
|
||||
carto.js MML:
|
||||
var layerShader = layer.getStyle({ property: 1 }, { zoom: 10 })
|
||||
console.log(layerShader['marker-width']) // 1
|
||||
console.log(layerShader['marker-fill']) // #FF0000
|
||||
}
|
||||
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [{"id":"style.mss","data":"Map {\n background-color: #fff;\n}\n\n#world {\n line-color: #ccc;\n line-width: 0.5;\n polygon-fill: #eee;\n}"}],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs",
|
||||
"Datasource": {
|
||||
"file": "world_borders",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
Cascadenik MML
|
||||
# API
|
||||
|
||||
<pre><Stylesheet><![CDATA[
|
||||
Map
|
||||
{
|
||||
map-bgcolor: #69f;
|
||||
}
|
||||
## RendererJS
|
||||
|
||||
Layer
|
||||
{
|
||||
line-width: 1;
|
||||
line-color: #696;
|
||||
polygon-fill: #6f9;
|
||||
}
|
||||
]]></Stylesheet>
|
||||
<Layer srs="+proj=latlong +ellps=WGS84 +datum=WGS84 +no_defs">
|
||||
<Datasource>
|
||||
<Parameter name="type">shape</Parameter>
|
||||
<Parameter name="file">world_borders</Parameter>
|
||||
</Datasource>
|
||||
</Layer>
|
||||
</Map></pre>
|
||||
### render(cartocss)
|
||||
|
||||
## Attachments and Instances
|
||||
_new_
|
||||
## CartoCSS
|
||||
|
||||
In CSS, a certain object can only have one instance of a property. A `<div>` has a specific border width and color, rules that match better than others (#id instead of .class) override previous definitions. `carto.js` acts the same way normally for the sake of familiarity and organization, but Mapnik itself is more powerful.
|
||||
compiled cartocss object
|
||||
|
||||
Layers in Mapnik can have multiple [borders](http://trac.mapnik.org/wiki/LineSymbolizer) and multiple copies of other attributes. This ability is useful in drawing line outlines, like in the case of road borders or 'glow' effects around coasts. `carto.js` makes this accessible by allowing attachments to styles:
|
||||
### getLayers
|
||||
|
||||
#world {
|
||||
line-color: #fff;
|
||||
line-width: 3;
|
||||
}
|
||||
return the layers, an array of ``CartoCSS.Layer`` object
|
||||
|
||||
#world::outline {
|
||||
line-color: #000;
|
||||
line-width: 6;
|
||||
}
|
||||
### getDefault
|
||||
|
||||
Attachments are optional: if you don't define them, carto.js does overriding of styles just like Cascadenik.
|
||||
|
||||
This brings us to another _incompatibility_: `line-inline` and `line-outline` have been removed from the language, because attachments are capable of the same trick.
|
||||
|
||||
While attachments allow creating implicit "layers" with the same data, using **instances** allows you to create multiple symbolizers in the same style/layer:
|
||||
|
||||
#roads {
|
||||
casing/line-width: 6;
|
||||
casing/line-color: #333;
|
||||
line-width: 4;
|
||||
line-color: #666;
|
||||
}
|
||||
|
||||
This makes Mapnik first draw the line of color #333 with a width of 6, and then immediately afterwards, it draws the same line again with width 4 and color #666. Contrast that to attachments: Mapnik would first draw all casings before proceeding to the actual lines.
|
||||
|
||||
## text-name
|
||||
_incompatibility_
|
||||
|
||||
Instead of the name attribute of the [TextSymbolizer](http://trac.mapnik.org/wiki/TextSymbolizer) and [ShieldSymbolizer](http://trac.mapnik.org/wiki/ShieldSymbolizer) being a part of the selector, it is a property of a rule. Thus the evaluation is less complex and one can use expressions in names.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>cascadenik</th>
|
||||
<th>carto.js</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign='top'>
|
||||
<pre>
|
||||
#world NAME {
|
||||
text-face-name: "Arial";
|
||||
}</pre>
|
||||
</td>
|
||||
<td valign='top'>
|
||||
<pre>
|
||||
#world {
|
||||
text-name: "NAME";
|
||||
text-face-name: "Arial";
|
||||
}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Mapnik2
|
||||
_new_
|
||||
|
||||
`carto.js` is only compatible with [Mapnik2](http://trac.mapnik.org/wiki/Mapnik2). Compatibility with Mapnik 0.7.x is not planned.
|
||||
|
||||
## Rasters and Buildings
|
||||
_new_
|
||||
|
||||
Rasters are supported in carto.js - it knows how to download `.vrt`, `.tiff`, and soon other raster formats, and the properties of the [RasterSymbolizer](http://trac.mapnik.org/wiki/RasterSymbolizer) are exposed in the language.
|
||||
|
||||
The [BuildingSymbolizer](http://trac.mapnik.org/wiki/BuildingSymbolizer) is also supported in `carto.js`. The code stores symbolizer types and properties in a JSON file (in `tree/reference.json`), so new Mapnik features can be quickly implemented here.
|
||||
|
||||
## Variables & Expressions
|
||||
_new_
|
||||
|
||||
`carto.js` inherits from its basis in [less.js](http://lesscss.org/) some new features in CSS. One can define variables in stylesheets, and use expressions to modify them.
|
||||
|
||||
@mybackground: #2B4D2D;
|
||||
|
||||
Map {
|
||||
background-color: @mybackground
|
||||
}
|
||||
|
||||
#world {
|
||||
polygon-fill: @mybackground + #222;
|
||||
line-color: darken(@mybackground, 10%);
|
||||
}
|
||||
|
||||
## Nested Styles
|
||||
_new_
|
||||
|
||||
`carto.js` also inherits nesting of rules from less.js.
|
||||
|
||||
/* Applies to all layers with .land class */
|
||||
.land {
|
||||
line-color: #ccc;
|
||||
line-width: 0.5;
|
||||
polygon-fill: #eee;
|
||||
/* Applies to #lakes.land */
|
||||
#lakes {
|
||||
polygon-fill: #000;
|
||||
}
|
||||
}
|
||||
|
||||
This can be a convenient way to group style changes by zoom level:
|
||||
|
||||
[zoom > 1] {
|
||||
/* Applies to all layers at zoom > 1 */
|
||||
polygon-gamma: 0.3;
|
||||
#world {
|
||||
polygon-fill: #323;
|
||||
}
|
||||
#lakes {
|
||||
polygon-fill: #144;
|
||||
}
|
||||
}
|
||||
|
||||
## FontSets
|
||||
_new_
|
||||
|
||||
By defining multiple fonts in a `text-face-name` definition, you create [FontSets](http://trac.mapnik.org/wiki/FontSet) in `carto.js`. These are useful for supporting multiple character sets and fallback fonts for distributed styles.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>carto</th><th>XML</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign='top'>
|
||||
|
||||
<pre>#world {
|
||||
text-name: "[NAME]";
|
||||
text-size: 11;
|
||||
text-face-name: "Georgia Regular", "Arial Italic";
|
||||
}</pre>
|
||||
|
||||
</td>
|
||||
<td valign='top'>
|
||||
<pre><FontSet name="fontset-0">
|
||||
<Font face-name="Georgia Regular"/>
|
||||
<Font face-name="Arial Italic"/>
|
||||
</FontSet>
|
||||
<Style name="world-text">
|
||||
<Rule>
|
||||
<TextSymbolizer fontset-name="fontset-0"
|
||||
size="11"
|
||||
name="[NAME]"/>
|
||||
</Rule>
|
||||
</Style></pre>
|
||||
</td>
|
||||
<tr>
|
||||
</table>
|
||||
return the default layer (``CartoCSS.Layer``), usually the Map layer
|
||||
|
||||
|
||||
## Usage
|
||||
### findLayer(where)
|
||||
|
||||
#### Using the binary
|
||||
find a layer using where object.
|
||||
|
||||
Install `millstone` to enable support for localizing external resources (URLs and local files) referenced in your mml file.
|
||||
```
|
||||
shader.findLayer({ name: 'test' })
|
||||
```
|
||||
|
||||
npm install millstone
|
||||
carto map_file.json
|
||||
## CartoCSS.Layer
|
||||
|
||||
#### Using the code
|
||||
### getStyle(props, context)
|
||||
|
||||
Currently `carto.js` is designed to be invoked from [node.js](http://nodejs.org/).
|
||||
The `Renderer` interface is the main API for developers, and it takes an MML file as a string as input.
|
||||
return the evaluated style:
|
||||
- props: object containing properties needed to render the style. If the cartocss style uses
|
||||
some variables they should be passed in this object
|
||||
- context: rendering context variables like ``zoom`` or animation ``frame``
|
||||
|
||||
// defined variables:
|
||||
// - input (the name or identifier of the file being parsed)
|
||||
// - data (a string containing the MML or an object of MML)
|
||||
var carto = require('carto');
|
||||
|
||||
new carto.Renderer({
|
||||
filename: input,
|
||||
local_data_dir: path.dirname(input),
|
||||
}).render(data, function(err, output) {
|
||||
if (err) {
|
||||
if (Array.isArray(err)) {
|
||||
err.forEach(function(e) {
|
||||
carto.writeError(e, options);
|
||||
});
|
||||
} else { throw err; }
|
||||
} else {
|
||||
sys.puts(output);
|
||||
}
|
||||
});
|
||||
|
||||
## TextMate bundle
|
||||
|
||||
There's a TextMate bundle that offers syntax highlighting for `.mss` and `.mml` files in the `build` directory. To install, download or clone this repository, then double-click on the `carto.tmbundle` icon in that folder.
|
||||
|
||||
The TextMate bundle **requires** [node-mapnik](https://github.com/mapnik/node-mapnik) and Carto installed globally - the versions that are installed locally in TileMill or other tools can't be automatically discovered.
|
||||
|
||||
npm install mapnik -g
|
||||
npm install carto -g
|
||||
|
||||
## Credits
|
||||
|
||||
`carto.js` is based on [less.js](https://github.com/cloudhead/less.js), a CSS compiler written by Alexis Sellier.
|
||||
|
||||
It depends on:
|
||||
|
||||
* [underscore.js](https://github.com/documentcloud/underscore/)
|
||||
|
||||
Only for running tests:
|
||||
## Reference Documentation
|
||||
|
||||
* [expresso](https://github.com/visionmedia/expresso)
|
||||
* [sax-js](https://github.com/isaacs/sax-js/)
|
||||
* [mapbox.com/carto](http://mapbox.com/carto/)
|
||||
|
||||
## Authors
|
||||
|
||||
* Tom MacWright (tmcw)
|
||||
* Konstantin Käfer (kkaefer)
|
||||
* AJ Ashton (ajashton)
|
||||
* Dane Springmeyer (springmeyer)
|
||||
|
213
bin/carto
213
bin/carto
@ -2,46 +2,40 @@
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
carto = require('carto');
|
||||
carto = require('../lib/carto'),
|
||||
url = require('url'),
|
||||
_ = require('underscore');
|
||||
|
||||
var args = process.argv.slice(1);
|
||||
var options = {};
|
||||
var existsSync = require('fs').existsSync || require('path').existsSync
|
||||
|
||||
args = args.filter(function (arg) {
|
||||
var match;
|
||||
var optimist = require('optimist')
|
||||
.usage("Usage: $0 <source MML file>")
|
||||
.options('h', {alias:'help', describe:'Display this help message'})
|
||||
.options('v', {alias:'version', boolean:true, describe:'Display version information'})
|
||||
.options('b', {alias:'benchmark', boolean:true, describe:'Outputs total compile time'})
|
||||
.options('l', {alias:'localize', boolean:true, default:false, describe:'Use millstone to localize resources when loading an MML'})
|
||||
.options('n', {alias:'nosymlink', boolean:true, describe:'Use absolute paths instead of symlinking files'})
|
||||
.options('ppi', {describe:'Pixels per inch used to convert m, mm, cm, in, pt, pc to pixels', default:90.714});
|
||||
|
||||
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
|
||||
else { return arg }
|
||||
var options = optimist.argv;
|
||||
|
||||
switch (arg) {
|
||||
case 'v':
|
||||
case 'version':
|
||||
sys.puts("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
|
||||
process.exit(0);
|
||||
break;
|
||||
case 'b':
|
||||
case 'benchmark':
|
||||
options.benchmark = true;
|
||||
break;
|
||||
if (options.help) {
|
||||
optimist.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
default:
|
||||
sys.puts("Usage: carto <source MML file>");
|
||||
sys.puts("Options:");
|
||||
sys.puts(" -v --version Parse JSON map manifest");
|
||||
sys.puts(" -b --benchmark Outputs total compile time");
|
||||
process.exit(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (options.version) {
|
||||
console.log("carto " + carto.version.join('.') + " (Carto map stylesheet compiler)");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
var input = args[1];
|
||||
var input = options._[0];
|
||||
if (input && input[0] != '/') {
|
||||
input = path.join(process.cwd(), input);
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
sys.puts("carto: no input files");
|
||||
console.log("carto: no input files ('carto -h or --help' for help)");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@ -49,63 +43,120 @@ if (options.benchmark) {
|
||||
var start = +new Date;
|
||||
}
|
||||
|
||||
var ext = path.extname(input);
|
||||
|
||||
if (!ext) {
|
||||
console.log("carto: please pass either a .mml file or .mss file");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(input)) {
|
||||
console.log("carto: file does not exist: '" + input + "'");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function compileMML(err, data) {
|
||||
// force drain the millstone download pool now
|
||||
// to ensure we can exit without waiting
|
||||
if (options.localize && millstone.drainPool) {
|
||||
millstone.drainPool(function() {});
|
||||
}
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
var output = renderer.render(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
function compileMSS(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: path.basename(input),
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
var output = renderer.renderMSS(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
var data = fs.readFileSync(input, 'utf-8');
|
||||
} catch(err) {
|
||||
sys.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch(err) {
|
||||
sys.puts("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var millstone = undefined;
|
||||
|
||||
try {
|
||||
require.resolve('millstone');
|
||||
millstone = require('millstone');
|
||||
} catch (err) {}
|
||||
|
||||
if (!millstone) {
|
||||
console.warn('carto: Millstone not found. Externals will not be resolved.');
|
||||
return compile(null, data);
|
||||
} else {
|
||||
millstone.resolve({
|
||||
mml: data,
|
||||
base: path.dirname(input),
|
||||
cache: path.join(path.dirname(input), 'cache')
|
||||
}, compile);
|
||||
}
|
||||
|
||||
function compile(err, data) {
|
||||
if (err) throw err;
|
||||
if (ext == '.mml') {
|
||||
try {
|
||||
new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark
|
||||
}).render(data, function(err, output) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
throw err;
|
||||
process.exit(1);
|
||||
} else {
|
||||
if (!options.benchmark) {
|
||||
sys.puts(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
sys.error(e.stack);
|
||||
} else {
|
||||
sys.error(e);
|
||||
}
|
||||
data = JSON.parse(data);
|
||||
} catch(err) {
|
||||
console.error("carto: " + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if (options.localize) {
|
||||
var millstone = undefined;
|
||||
try {
|
||||
require.resolve('millstone');
|
||||
millstone = require('millstone');
|
||||
} catch (err) {
|
||||
console.error('carto: Millstone not found, required if localizing stylesheet resources. ' + err.message.replace(/^[A-Z]+, /, ''));
|
||||
process.exit(1);
|
||||
}
|
||||
millstone.resolve({
|
||||
mml: data,
|
||||
base: path.dirname(input),
|
||||
cache: path.join(path.dirname(input), 'cache'),
|
||||
nosymlink: options.nosymlink
|
||||
}, compileMML);
|
||||
} else {
|
||||
data.Stylesheet = data.Stylesheet.map(function(x) {
|
||||
if (typeof x !== 'string') {
|
||||
return { id: x, data: x.data }
|
||||
}
|
||||
return { id: x, data: fs.readFileSync(path.join(path.dirname(input), x), 'utf8') }
|
||||
});
|
||||
compileMML(null,data);
|
||||
}
|
||||
} else if (ext == '.mss') {
|
||||
compileMSS(null,data);
|
||||
} else {
|
||||
console.log("carto: please pass either a .mml file or .mss file");
|
||||
}
|
||||
|
233
bin/cartox
233
bin/cartox
@ -1,233 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var sax = require('sax'),
|
||||
fs = require('fs'),
|
||||
_ = require('underscore')._,
|
||||
path = require('path'),
|
||||
sys = require('sys');
|
||||
|
||||
require.paths.unshift(path.join(__dirname, '../lib'), path.join(__dirname, '../lib/node'));
|
||||
|
||||
var carto = require('carto'),
|
||||
args = process.argv.slice(1);
|
||||
|
||||
var options = {
|
||||
silent: false,
|
||||
json: false
|
||||
};
|
||||
|
||||
xml2tree = function(xml, callback) {
|
||||
var parser = sax.parser(true);
|
||||
var tree = [ {} ];
|
||||
parser.onopentag = function(node) {
|
||||
if (!(node.name in tree[0])) tree[0][node.name] = [];
|
||||
tree[0][node.name].push(node.attributes);
|
||||
tree.unshift(node.attributes);
|
||||
};
|
||||
|
||||
parser.onclosetag = function() {
|
||||
tree.shift();
|
||||
if (tree.length === 1) callback(tree[0]);
|
||||
};
|
||||
|
||||
parser.ontext = parser.oncdata = function(text) {
|
||||
if (text.trim()) tree[0].text = (tree[0].text || '') + text;
|
||||
};
|
||||
|
||||
parser.write(xml.toString());
|
||||
}
|
||||
|
||||
args = args.filter(function (arg) {
|
||||
var match;
|
||||
|
||||
if (match = arg.match(/^--?([a-z][0-9a-z-]*)$/i)) { arg = match[1] }
|
||||
else { return arg }
|
||||
|
||||
switch (arg) {
|
||||
case 'h':
|
||||
case 'help':
|
||||
sys.puts("Usage: cartox source");
|
||||
process.exit(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var input = args[1];
|
||||
if (input && input[0] != '/') {
|
||||
input = path.join(process.cwd(), input);
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
sys.puts("cartox: no input files");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var output = args[2];
|
||||
if (output && output[0] != '/') {
|
||||
output = path.join(process.cwd(), output);
|
||||
}
|
||||
|
||||
function upStyle(s) {
|
||||
this.name = s.name;
|
||||
this.rules = [];
|
||||
}
|
||||
|
||||
upStyle.prototype.toMSS = function() {
|
||||
return '.' + this.name
|
||||
+ ' {\n'
|
||||
+ this.rules.join('\n')
|
||||
+ '\n}';
|
||||
};
|
||||
|
||||
function upRule(xmlRule) {
|
||||
this.filters = xmlRule.Filter ? this.upFilter(xmlRule.Filter[0].text) : [];
|
||||
this.zooms = (xmlRule.MaxScaleDenominator || xmlRule.MinScaleDenomintor)
|
||||
? this.upZoom(xmlRule) : [];
|
||||
this.rules = this.upSymbolizers(xmlRule);
|
||||
}
|
||||
|
||||
upRule.prototype.upZoom = function(xmlRule) {
|
||||
var zoomFilters = [];
|
||||
var findZoom = function(denom) {
|
||||
for (var i in carto.tree.Zoom.ranges) {
|
||||
if (carto.tree.Zoom.ranges[i] == denom) return i;
|
||||
}
|
||||
};
|
||||
|
||||
if (xmlRule.MaxScaleDenominator) {
|
||||
zoomFilters.push('[zoom >= ' + findZoom(xmlRule.MaxScaleDenominator[0].text) + ']');
|
||||
}
|
||||
if (xmlRule.MinScaleDenominator) {
|
||||
zoomFilters.push('[zoom <= ' + findZoom(xmlRule.MinScaleDenominator[0].text) + ']');
|
||||
}
|
||||
return zoomFilters;
|
||||
};
|
||||
|
||||
upRule.prototype.upFilter = function(xmlFilter) {
|
||||
var filters = [];
|
||||
var thisfilter = [];
|
||||
var invert = false;
|
||||
var xmlTok = xmlFilter.split(/\s+/);
|
||||
var curTok = '';
|
||||
for (var i in xmlTok) {
|
||||
curTok = xmlTok[i].replace(/(\[|\]|\)|\()/g, '');
|
||||
if (curTok == 'and') {
|
||||
filters.push(thisfilter);
|
||||
thisfilter = [];
|
||||
} else if (curTok == 'not') {
|
||||
invert = true;
|
||||
} else if (invert && (['='].indexOf(curTok) !== -1)) {
|
||||
thisfilter.push('!=');
|
||||
invert = false;
|
||||
} else {
|
||||
thisfilter.push(curTok);
|
||||
}
|
||||
}
|
||||
if (thisfilter) filters.push(thisfilter);
|
||||
return filters.map(function(f) {
|
||||
return f.join(' ');
|
||||
});
|
||||
};
|
||||
|
||||
upRule.prototype.upSymbolizers = function(xmlRule) {
|
||||
var css_rules = [];
|
||||
var symnames = _.map(_.keys(carto.tree.Reference.data.symbolizers), function(symbolizer) {
|
||||
return [symbolizer.charAt(0).toUpperCase() +
|
||||
symbolizer.slice(1).replace(/\-./, function(str) {
|
||||
return str[1].toUpperCase();
|
||||
}) + 'Symbolizer', carto.tree.Reference.data.symbolizers[symbolizer]];
|
||||
});
|
||||
var symmap = _.reduce(symnames, function(memo, s) {
|
||||
memo[s[0]] = s[1];
|
||||
return memo;
|
||||
}, {});
|
||||
var cssmap = function(symbolizer, name) {
|
||||
return symmap[symbolizer][name].css;
|
||||
}
|
||||
for (var i in xmlRule) {
|
||||
if (i in symmap) {
|
||||
for (var j in xmlRule[i][0]) {
|
||||
if (j == 'CssParameter') {
|
||||
sys.error("cartox: no support for CssParameter, please upgrade your xml to Mapnik 2.0 syntax first");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!symmap[i][j]) {
|
||||
sys.error('Property ' + j + ' not yet supported');
|
||||
} else if (symmap[i][j].type == 'uri') {
|
||||
css_rules.push(cssmap(i, j) + ': url("' + xmlRule[i][0][j] + '");');
|
||||
} else if (['float', 'color', 'boolean', 'numbers'].indexOf(symmap[i][j].type) !== -1 || _.isArray(symmap[i][j].type)) {
|
||||
css_rules.push(cssmap(i, j) + ': ' + xmlRule[i][0][j] + ';');
|
||||
} else {
|
||||
css_rules.push(cssmap(i, j) + ': "' + xmlRule[i][0][j] + '";');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return css_rules;
|
||||
};
|
||||
|
||||
|
||||
upRule.prototype.toMSS = function() {
|
||||
if (this.filters.length || this.zooms.length) {
|
||||
return ' ' + this.filters.map(function(f) {
|
||||
return '[' + f + ']';
|
||||
}).join('') +
|
||||
this.zooms.map(function(f) {
|
||||
return f;
|
||||
}).join('') +
|
||||
' {\n '
|
||||
+ this.rules.join('\n ')
|
||||
+ '\n }\n';
|
||||
} else {
|
||||
' ' + this.rules.join('\n ');
|
||||
}
|
||||
}
|
||||
|
||||
function upDatasource(ds) {
|
||||
var params = {};
|
||||
ds[0].Parameter.forEach(function(param) {
|
||||
params[param.name] = param.text;
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
fs.readFile(input, 'utf-8', function (e, data) {
|
||||
var styles = [];
|
||||
var document = {};
|
||||
var layers = [];
|
||||
if (e) throw e;
|
||||
xml2tree(data, function(mapnik_xml) {
|
||||
mapnik_xml.Map[0].Style.forEach(function(s) {
|
||||
var newStyle = new upStyle(s);
|
||||
s.Rule.forEach(function(r) {
|
||||
newStyle.rules.push((new upRule(r)).toMSS());
|
||||
});
|
||||
styles.push(newStyle.toMSS());
|
||||
});
|
||||
styles = styles.reverse();
|
||||
mapnik_xml.Map[0].Layer.forEach(function(l) {
|
||||
var additional_classes = l.StyleName ? ' ' + l.StyleName.map(function(s) { return s.text; }).join(' ') : '';
|
||||
var newLayer = {
|
||||
name: l.name,
|
||||
class: l.name + '_style' + additional_classes,
|
||||
srs: l.srs
|
||||
};
|
||||
newLayer.Datasource = upDatasource(l.Datasource);
|
||||
layers.push(newLayer);
|
||||
});
|
||||
if (output) {
|
||||
document.Stylesheet = [output.replace('mml', 'mss')];
|
||||
document.Layer = layers;
|
||||
fs.writeFile(output, JSON.stringify(document), 'utf8', function(err) {
|
||||
sys.error('write MML');
|
||||
});
|
||||
fs.writeFile(output.replace('mml', 'mss'), styles.join('\n\n'), 'utf8', function(err) {
|
||||
sys.error('write MSS');
|
||||
});
|
||||
} else {
|
||||
document.Stylesheet = [{ id: 'gen', data: styles.join('\n') }];
|
||||
document.Layer = layers;
|
||||
console.log(JSON.stringify(document));
|
||||
}
|
||||
});
|
||||
});
|
@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var xml2js = require('xml2js'),
|
||||
fs = require('fs'),
|
||||
sys = require('sys');
|
||||
|
||||
if (!process.ARGV[2]) {
|
||||
console.log('Please specify a XML file.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.readFile(process.ARGV[2], 'utf-8', function(err, data) {
|
||||
if (err) throw err;
|
||||
|
||||
// Replace entities.
|
||||
var entities = {};
|
||||
var match = data.match(/<!ENTITY([^>]|"([^"]|\\")*")+>/g)
|
||||
if (match != null) {
|
||||
match.forEach(function(entity) {
|
||||
var parts = entity.match(/^<!ENTITY\s+(\w+)\s+"(.+)">$/);
|
||||
entities['&' + parts[1] + ';'] = parts[2];
|
||||
});
|
||||
}
|
||||
data = data.replace(/&\w+;/g, function(entity) {
|
||||
return entities[entity];
|
||||
});
|
||||
|
||||
function addAttributes(obj) {
|
||||
if (obj['@']) for (var key in obj['@']) obj[key] = obj['@'][key];
|
||||
delete obj['@'];
|
||||
return obj;
|
||||
}
|
||||
|
||||
function simplifyExternal(obj) {
|
||||
if (obj.src) return obj.src;
|
||||
else return obj;
|
||||
}
|
||||
|
||||
var parser = new xml2js.Parser();
|
||||
parser.addListener('end', function(json) {
|
||||
console.log(JSON.stringify(json, function(key, value) {
|
||||
if (!key) {
|
||||
return addAttributes(value);
|
||||
}
|
||||
else if (key === 'Stylesheet') {
|
||||
if (Array.isArray(value)) return value.map(addAttributes).map(simplifyExternal);
|
||||
else return [ simplifyExternal(addAttributes(value)) ];
|
||||
}
|
||||
else if (key === 'Layer' || key === 'Stylesheet') {
|
||||
if (Array.isArray(value)) return value.map(addAttributes);
|
||||
else return [ addAttributes(value) ];
|
||||
}
|
||||
else if (key === 'Datasource') {
|
||||
value = addAttributes(value);
|
||||
value.Parameter.forEach(function(parameter) {
|
||||
value[parameter['@'].name] = parameter['#'];
|
||||
});
|
||||
delete value.Parameter;
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}, 4));
|
||||
});
|
||||
parser.parseString(data);
|
||||
});
|
@ -264,11 +264,11 @@
|
||||
</dict>
|
||||
</dict>
|
||||
<key>match</key>
|
||||
<string>\b(background-color|background-image|srs|buffer|font-directory|polygon-fill|polygon-gamma|polygon-opacity|polygon-meta-output|polygon-meta-writer|line-color|line-width|line-opacity|line-join|line-cap|line-gamma|line-dasharray|line-meta-output|line-meta-writer|marker-file|marker-opacity|marker-line-color|marker-line-width|marker-line-opacity|marker-placement|marker-type|marker-width|marker-height|marker-fill|marker-allow-overlap|marker-spacing|marker-max-error|marker-transform|marker-meta-output|marker-meta-writer|shield-name|shield-face-name|shield-size|shield-fill|shield-min-distance|shield-halo-fill|shield-halo-radius|shield-spacing|shield-character-spacing|shield-line-spacing|shield-file|shield-width|shield-height|shield-type|shield-text-dx|shield-text-dy|shield-dx|shield-dy|shield-meta-output|shield-meta-writer|line-pattern-file|line-pattern-width|line-pattern-height|line-pattern-type|line-pattern-meta-output|line-pattern-meta-writer|polygon-pattern-file|polygon-pattern-width|polygon-pattern-height|polygon-pattern-type|polygon-pattern-meta-output|polygon-pattern-meta-writer|raster-opacity|raster-mode|raster-scaling|point-file|point-width|point-height|point-type|point-allow-overlap|point-placement|point-meta-output|point-meta-writer|text-name|text-face-name|text-size|text-ratio|text-wrap-width|text-spacing|text-character-spacing|text-line-spacing|text-label-position-tolerance|text-max-char-angle-delta|text-fill|text-halo-fill|text-halo-radius|text-dx|text-dy|text-avoid-edges|text-min-distance|text-min-padding|text-allow-overlap|text-placement|text-placement-type|text-placements|text-transform|text-meta-output|text-meta-writer|building-fill|building-fill-opacity|building-height)\s*:</string>
|
||||
<string>\b(background-color|background-image|srs|buffer|font-directory|polygon-fill|polygon-gamma|polygon-opacity|polygon-meta-output|polygon-meta-writer|line-color|line-width|line-opacity|line-join|line-cap|line-gamma|line-dasharray|line-meta-output|line-meta-writer|marker-file|marker-opacity|marker-line-color|marker-line-width|marker-line-opacity|marker-placement|marker-type|marker-width|marker-height|marker-fill|marker-allow-overlap|marker-spacing|marker-max-error|marker-transform|marker-meta-output|marker-meta-writer|shield-name|shield-face-name|shield-size|shield-fill|shield-min-distance|shield-halo-fill|shield-halo-radius|shield-spacing|shield-character-spacing|shield-line-spacing|shield-file|shield-width|shield-height|shield-type|shield-text-dx|shield-text-dy|shield-dx|shield-dy|shield-meta-output|shield-meta-writer|line-pattern-file|line-pattern-width|line-pattern-height|line-pattern-type|line-pattern-meta-output|line-pattern-meta-writer|polygon-pattern-file|polygon-pattern-width|polygon-pattern-height|polygon-pattern-type|polygon-pattern-meta-output|polygon-pattern-meta-writer|raster-opacity|raster-comp-op|raster-scaling|point-file|point-width|point-height|point-type|point-allow-overlap|point-placement|point-meta-output|point-meta-writer|text-name|text-face-name|text-size|text-ratio|text-wrap-width|text-spacing|text-character-spacing|text-line-spacing|text-label-position-tolerance|text-max-char-angle-delta|text-fill|text-halo-fill|text-halo-radius|text-dx|text-dy|text-avoid-edges|text-min-distance|text-min-padding|text-allow-overlap|text-placement|text-placement-type|text-placements|text-transform|text-meta-output|text-meta-writer|building-fill|building-fill-opacity|building-height)\s*:</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>match</key>
|
||||
<string>\b(miter|round|bevel|butt|round|square|point|line|arrow|ellipse|png|jpg|svg|normal|grain_merge|grain_merge2|multiply|multiply2|divide|divide2|screen|hard_light|fast|bilinear|bilinear8|centroid|interior|point|line|vertex|interior|none|uppercase|lowercase)\b</string>
|
||||
<string>\b(miter|round|bevel|butt|round|square|point|line|arrow|ellipse|png|jpg|svg|normal|grain_merge|grain_merge2|multiply|multiply2|divide|divide2|screen|hard_light|near|bilinear|bilinear8|centroid|interior|point|line|vertex|interior|none|uppercase|lowercase)\b</string>
|
||||
<key>name</key>
|
||||
<string>meta.property-value.carto</string>
|
||||
</dict>
|
||||
|
7
build/header.js
Normal file
7
build/header.js
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
// LESS - Leaner CSS v@VERSION
|
||||
// http://lesscss.org
|
||||
//
|
||||
// Copyright (c) 2010, Alexis Sellier
|
||||
// Licensed under the MIT license.
|
||||
//
|
@ -2,8 +2,7 @@
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
_ = require('underscore')._,
|
||||
sys = require('sys');
|
||||
_ = require('underscore')._;
|
||||
|
||||
var carto = require('../lib/carto');
|
||||
|
||||
|
@ -47,7 +47,7 @@ syn region cartoFontDescriptorFunction contained matchgroup=cartoFunctionName st
|
||||
syn match cartoUnicodeRange contained "U+[0-9A-Fa-f?]\+"
|
||||
syn match cartoUnicodeRange contained "U+\x\+-\x\+"
|
||||
|
||||
syn match cartoKeywordAttr "/\|miter\|round\|bevel\|butt\|round\|square\|point\|line\|arrow\|ellipse\|point\|line\|vertex\|interior\|local\|global\|normal\|grain_merge\|grain_merge2\|multiply\|multiply2\|divide\|divide2\|screen\|hard_light\|fast\|bilinear\|bilinear8\|bicubic\|spline16\|gaussian\|lanczos\|centroid\|interior\|top\|middle\|bottom\|point\|line\|vertex\|interior\|dummy\|simple\|none\|uppercase\|lowercase\|capitalize\|/"
|
||||
syn match cartoKeywordAttr "/\|miter\|round\|bevel\|butt\|round\|square\|point\|line\|arrow\|ellipse\|point\|line\|vertex\|interior\|local\|global\|normal\|grain_merge\|grain_merge2\|multiply\|multiply2\|divide\|divide2\|screen\|hard_light\|near\|bilinear\|bilinear8\|bicubic\|spline16\|gaussian\|lanczos\|centroid\|interior\|top\|middle\|bottom\|point\|line\|vertex\|interior\|dummy\|simple\|none\|uppercase\|lowercase\|capitalize\|/"
|
||||
|
||||
" syn keyword cartoColor contained {{#colors}}{{.}} {{/colors}}
|
||||
syn match cartoColor "/\|aliceblue\|antiquewhite\|aqua\|aquamarine\|azure\|beige\|bisque\|black\|blanchedalmond\|blue\|blueviolet\|brown\|burlywood\|cadetblue\|chartreuse\|chocolate\|coral\|cornflowerblue\|cornsilk\|crimson\|cyan\|darkblue\|darkcyan\|darkgoldenrod\|darkgray\|darkgreen\|darkgrey\|darkkhaki\|darkmagenta\|darkolivegreen\|darkorange\|darkorchid\|darkred\|darksalmon\|darkseagreen\|darkslateblue\|darkslategrey\|darkturquoise\|darkviolet\|deeppink\|deepskyblue\|dimgray\|dimgrey\|dodgerblue\|firebrick\|floralwhite\|forestgreen\|fuchsia\|gainsboro\|ghostwhite\|gold\|goldenrod\|gray\|grey\|green\|greenyellow\|honeydew\|hotpink\|indianred\|indigo\|ivory\|khaki\|lavender\|lavenderblush\|lawngreen\|lemonchiffon\|lightblue\|lightcoral\|lightcyan\|lightgoldenrodyellow\|lightgray\|lightgreen\|lightgrey\|lightpink\|lightsalmon\|lightseagreen\|lightskyblue\|lightslategray\|lightslategrey\|lightsteelblue\|lightyellow\|lime\|limegreen\|linen\|magenta\|maroon\|mediumaquamarine\|mediumblue\|mediumorchid\|mediumpurple\|mediumseagreen\|mediumslateblue\|mediumspringgreen\|mediumturquoise\|mediumvioletred\|midnightblue\|mintcream\|mistyrose\|moccasin\|navajowhite\|navy\|oldlace\|olive\|olivedrab\|orange\|orangered\|orchid\|palegoldenrod\|palegreen\|paleturquoise\|palevioletred\|papayawhip\|peachpuff\|peru\|pink\|plum\|powderblue\|purple\|red\|rosybrown\|royalblue\|saddlebrown\|salmon\|sandybrown\|seagreen\|seashell\|sienna\|silver\|skyblue\|slateblue\|slategray\|slategrey\|snow\|springgreen\|steelblue\|tan\|teal\|thistle\|tomato\|turquoise\|violet\|wheat\|white\|whitesmoke\|yellow\|yellowgreen\|transparent\|/"
|
||||
|
4
dist/carto.js
vendored
Normal file
4
dist/carto.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7375
dist/carto.uncompressed.js
vendored
Normal file
7375
dist/carto.uncompressed.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
15
docs-generator/README.md
Normal file
15
docs-generator/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Generating CartoCSS docs
|
||||
|
||||
From the `docs-generator/` directory:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
$ node generate.js
|
||||
```
|
||||
|
||||
Will save docs to `docs/`.
|
23
docs-generator/generate.js
Normal file
23
docs-generator/generate.js
Normal file
@ -0,0 +1,23 @@
|
||||
var fs = require('fs'),
|
||||
path = require('path'),
|
||||
refs = require('mapnik-reference'),
|
||||
_ = require('underscore');
|
||||
|
||||
function tmpl(x) {
|
||||
return _.template(fs.readFileSync(path.join(__dirname, x), 'utf-8'));
|
||||
}
|
||||
|
||||
var index = tmpl('index._');
|
||||
var table = tmpl('symbolizers._');
|
||||
var versions = Object.keys(refs.version);
|
||||
|
||||
for (var v in refs.version) {
|
||||
var ref = refs.version[v];
|
||||
fs.writeFileSync(path.join(__dirname, '../docs/' + v + '.md'), index({
|
||||
symbolizers: ref.symbolizers,
|
||||
table: table,
|
||||
version: v,
|
||||
versions: versions,
|
||||
_: _
|
||||
}));
|
||||
}
|
144
docs-generator/index._
Normal file
144
docs-generator/index._
Normal file
@ -0,0 +1,144 @@
|
||||
# Carto documentation
|
||||
|
||||
The following is a list of properties provided in CartoCSS that you can apply to map elements.
|
||||
<%= table({symbolizers:symbolizers}) %>
|
||||
|
||||
### Values
|
||||
|
||||
Below is a list of values and an explanation of any expression that can be applied to properties in CartCSS.
|
||||
|
||||
### Color
|
||||
|
||||
CartoCSS accepts a variety of syntaxes for colors - HTML-style hex values, rgb, rgba, hsl, and hsla. It also supports the predefined HTML colors names, like `yellow` and `blue`.
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-color: #ff0;
|
||||
line-color: #ffff00;
|
||||
line-color: rgb(255, 255, 0);
|
||||
line-color: rgba(255, 255, 0, 1);
|
||||
line-color: hsl(100, 50%, 50%);
|
||||
line-color: hsla(100, 50%, 50%, 1);
|
||||
line-color: yellow;
|
||||
}
|
||||
```
|
||||
|
||||
Especially of note is the support for hsl, which can be [easier to reason about than rgb()](http://mothereffinghsl.com/). Carto also includes several color operation functions [borrowed from less](http://lesscss.org/functions/#color-operations):
|
||||
|
||||
``` css
|
||||
// lighten and darken colors
|
||||
lighten(#ace, 10%);
|
||||
darken(#ace, 10%);
|
||||
|
||||
// saturate and desaturate
|
||||
saturate(#550000, 10%);
|
||||
desaturate(#00ff00, 10%);
|
||||
|
||||
// increase or decrease the opacity of a color
|
||||
fadein(#fafafa, 10%);
|
||||
fadeout(#fefefe, 14%);
|
||||
|
||||
// spin rotates a color around the color wheel by degrees
|
||||
spin(#ff00ff, 10);
|
||||
|
||||
// mix generates a color in between two other colors.
|
||||
mix(#fff, #000, 50%);
|
||||
```
|
||||
|
||||
These functions all take arguments which can be color variables, literal colors, or the results of other functions operating on colors.
|
||||
|
||||
### Float
|
||||
|
||||
Float is a fancy way of saying 'number'. In CartoCSS, you specify _just a number_ - unlike CSS, there are no units, but everything is specified in pixels.
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-width: 2;
|
||||
}
|
||||
```
|
||||
|
||||
It's also possible to do simple math with number values:
|
||||
|
||||
``` css
|
||||
#line {
|
||||
line-width: 4 / 2; // division
|
||||
line-width: 4 + 2; // addition
|
||||
line-width: 4 - 2; // subtraction
|
||||
line-width: 4 * 2; // multiplication
|
||||
line-width: 4 % 2; // modulus
|
||||
}
|
||||
```
|
||||
|
||||
### URI
|
||||
|
||||
URI is a fancy way of saying URL. When an argument is a URI, you use the same kind of `url('place.png')` notation that you would with HTML. Quotes around the URL aren't required, but are highly recommended. URIs can be paths to places on your computer, or on the internet.
|
||||
|
||||
```css
|
||||
#markers {
|
||||
marker-file: url('marker.png');
|
||||
}
|
||||
```
|
||||
|
||||
### String
|
||||
|
||||
A string is basically just text. In the case of CartoCSS, you're going to put it in quotes. Strings can be anything, though pay attention to the cases of `text-name` and `shield-name` - they actually will refer to features, which you refer to by putting them in brackets, as seen in the example below.
|
||||
|
||||
```css
|
||||
#labels {
|
||||
text-name: "[MY_FIELD]";
|
||||
}
|
||||
```
|
||||
|
||||
### Boolean
|
||||
|
||||
Boolean means yes or no, so it accepts the values `true` or `false`.
|
||||
|
||||
```css
|
||||
#markers {
|
||||
marker-allow-overlap:true;
|
||||
}
|
||||
```
|
||||
|
||||
### Expressions
|
||||
|
||||
Expressions are statements that can include fields, numbers, and other types in a really flexible way. You have run into expressions before, in the realm of 'fields', where you'd specify `"[FIELD]"`, but expressions allow you to drop the quotes and also do quick addition, division, multiplication, and concatenation from within Carto syntax.
|
||||
|
||||
```css
|
||||
#buildings {
|
||||
building-height: [HEIGHT_FIELD] * 10;
|
||||
}
|
||||
```
|
||||
|
||||
### Numbers
|
||||
Numbers are comma-separated lists of one or more number in a specific order. They're used in line dash arrays, in which the numbers specify intervals of line, break, and line again.
|
||||
|
||||
```css
|
||||
#disputedboundary {
|
||||
line-dasharray: 1, 4, 2;
|
||||
}
|
||||
```
|
||||
|
||||
### Percentages
|
||||
In Carto, the percentage symbol, `%` universally means `value/100`. It's meant to be used with ratio-related properties, like opacity rules.
|
||||
|
||||
_You should not use percentages as widths, heights, or other properties - unlike CSS, percentages are not relative to cascaded classes or page size, they're, as stated, simply the value divided by one hundred._
|
||||
|
||||
```css
|
||||
#world {
|
||||
// this syntax
|
||||
polygon-opacity: 50%;
|
||||
|
||||
// is equivalent to
|
||||
polygon-opacity: 0.5;
|
||||
}
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
Functions are comma-separated lists of one or more functions. For instance, transforms use the `functions` type to allow for transforms within Carto, which are optionally chainable.
|
||||
|
||||
```css
|
||||
#point {
|
||||
point-transform: scale(2, 2);
|
||||
}
|
||||
```
|
20
docs-generator/package.json
Normal file
20
docs-generator/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "carto-site",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Mapnik Stylesheet Compiler",
|
||||
"url": "https://github.com/mapbox/carto",
|
||||
"repositories": [{
|
||||
"type": "git",
|
||||
"url": "http://github.com/mapbox/carto.git"
|
||||
}],
|
||||
"author": {
|
||||
"name": "MapBox",
|
||||
"url": "http://mapbox.com/",
|
||||
"email": "info@mapbox.com"
|
||||
},
|
||||
"dependencies": {
|
||||
"mapnik-reference": "5.0.x",
|
||||
"underscore": "~1.3.3"
|
||||
}
|
||||
}
|
13
docs-generator/symbolizers._
Normal file
13
docs-generator/symbolizers._
Normal file
@ -0,0 +1,13 @@
|
||||
<% _(symbolizers).each(function(symbolizer, name) { %>
|
||||
<% if (name == '*') { %>## All elements<% } else { %>## <%= name %><% } %>
|
||||
<% _(symbolizer).chain().filter(function(p) { return p.css; }).each(function(p) { %>
|
||||
##### <%= p.css.replace(/\s/g, '') %> <% if (_.isArray(p.type)) { %>`keyword`<% } else { %>`<%= p.type %>`<% } %>
|
||||
<% if (_.isArray(p.type)) { %><% _(p.type).each(function(type) { %>`<%= type %>`<% }); %><% } %>
|
||||
|
||||
<% if (typeof p['default-value'] !== '') { %>Default Value: <%= p['default-value'] %><% } %>
|
||||
<% if (p['default-meaning']) { %>_(<%- p['default-meaning'] %>)_<% } %>
|
||||
<% if (typeof p['range'] !== 'undefined') { %>Range: <%= '' + p['range'] %><% } %>
|
||||
<% if (p.doc) { %><%- p.doc%><% } %>
|
||||
* * *
|
||||
<% }); %>
|
||||
<% }); %>
|
1159
docs/2.0.0.md
Normal file
1159
docs/2.0.0.md
Normal file
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.1.md
Normal file
1159
docs/2.0.1.md
Normal file
File diff suppressed because it is too large
Load Diff
1159
docs/2.0.2.md
Normal file
1159
docs/2.0.2.md
Normal file
File diff suppressed because it is too large
Load Diff
1486
docs/2.1.0.md
Normal file
1486
docs/2.1.0.md
Normal file
File diff suppressed because it is too large
Load Diff
1495
docs/2.1.1.md
Normal file
1495
docs/2.1.1.md
Normal file
File diff suppressed because it is too large
Load Diff
1687
docs/latest.md
Normal file
1687
docs/latest.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,19 +5,40 @@ tree.functions = {
|
||||
return this.rgba(r, g, b, 1.0);
|
||||
},
|
||||
rgba: function (r, g, b, a) {
|
||||
var rgb = [r, g, b].map(function (c) { return number(c); }),
|
||||
a = number(a);
|
||||
var rgb = [r, g, b].map(function (c) { return number(c); });
|
||||
a = number(a);
|
||||
if (rgb.some(isNaN) || isNaN(a)) return null;
|
||||
return new tree.Color(rgb, a);
|
||||
},
|
||||
// Only require val
|
||||
stop: function (val) {
|
||||
var color, mode;
|
||||
if (arguments.length > 1) color = arguments[1];
|
||||
if (arguments.length > 2) mode = arguments[2];
|
||||
|
||||
return {
|
||||
is: 'tag',
|
||||
val: val,
|
||||
color: color,
|
||||
mode: mode,
|
||||
toString: function(env) {
|
||||
return '\n\t<stop value="' + val.ev(env) + '"' +
|
||||
(color ? ' color="' + color.ev(env) + '" ' : '') +
|
||||
(mode ? ' mode="' + mode.ev(env) + '" ' : '') +
|
||||
'/>';
|
||||
}
|
||||
};
|
||||
},
|
||||
hsl: function (h, s, l) {
|
||||
return this.hsla(h, s, l, 1.0);
|
||||
},
|
||||
hsla: function (h, s, l, a) {
|
||||
h = (number(h) % 360) / 360;
|
||||
s = number(s); l = number(l); a = number(a);
|
||||
if ([h, s, l, a].some(isNaN)) return null;
|
||||
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
|
||||
var m1 = l * 2 - m2;
|
||||
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s,
|
||||
m1 = l * 2 - m2;
|
||||
|
||||
return this.rgba(hue(h + 1/3) * 255,
|
||||
hue(h) * 255,
|
||||
@ -33,18 +54,23 @@ tree.functions = {
|
||||
}
|
||||
},
|
||||
hue: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().h));
|
||||
},
|
||||
saturation: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().s * 100), '%');
|
||||
},
|
||||
lightness: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(Math.round(color.toHSL().l * 100), '%');
|
||||
},
|
||||
alpha: function (color) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
return new tree.Dimension(color.toHSL().a);
|
||||
},
|
||||
saturate: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s += amount.value / 100;
|
||||
@ -52,6 +78,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
desaturate: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.s -= amount.value / 100;
|
||||
@ -59,6 +86,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
lighten: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l += amount.value / 100;
|
||||
@ -66,6 +94,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
darken: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.l -= amount.value / 100;
|
||||
@ -73,6 +102,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadein: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a += amount.value / 100;
|
||||
@ -80,6 +110,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
fadeout: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
|
||||
hsl.a -= amount.value / 100;
|
||||
@ -87,6 +118,7 @@ tree.functions = {
|
||||
return hsla(hsl);
|
||||
},
|
||||
spin: function (color, amount) {
|
||||
if (!('toHSL' in color)) return null;
|
||||
var hsl = color.toHSL();
|
||||
var hue = (hsl.h + amount.value) % 360;
|
||||
|
||||
@ -94,6 +126,13 @@ tree.functions = {
|
||||
|
||||
return hsla(hsl);
|
||||
},
|
||||
replace: function (entity, a, b) {
|
||||
if (entity.is === 'field') {
|
||||
return entity.toString + '.replace(' + a.toString() + ', ' + b.toString() + ')';
|
||||
} else {
|
||||
return entity.replace(a, b);
|
||||
}
|
||||
},
|
||||
//
|
||||
// Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
|
||||
// http://sass-lang.com
|
||||
@ -117,9 +156,6 @@ tree.functions = {
|
||||
greyscale: function (color) {
|
||||
return this.desaturate(color, new tree.Dimension(100));
|
||||
},
|
||||
e: function (str) {
|
||||
return new tree.Anonymous(str instanceof tree.JavaScript ? str.evaluated : str);
|
||||
},
|
||||
'%': function (quoted /* arg, arg, ...*/) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
str = quoted.value;
|
||||
@ -129,12 +165,33 @@ tree.functions = {
|
||||
.replace(/%[da]/, args[i].toString());
|
||||
}
|
||||
str = str.replace(/%%/g, '%');
|
||||
return new tree.Quoted('"' + str + '"', str);
|
||||
return new tree.Quoted(str);
|
||||
}
|
||||
};
|
||||
|
||||
function hsla(hsla) {
|
||||
return tree.functions.hsla(hsla.h, hsla.s, hsla.l, hsla.a);
|
||||
var image_filter_functors = [
|
||||
'emboss', 'blur', 'gray', 'sobel', 'edge-detect',
|
||||
'x-gradient', 'y-gradient', 'sharpen'];
|
||||
|
||||
for (var i = 0; i < image_filter_functors.length; i++) {
|
||||
var f = image_filter_functors[i];
|
||||
tree.functions[f] = (function(f) {
|
||||
return function() {
|
||||
return new tree.ImageFilter(f);
|
||||
};
|
||||
})(f);
|
||||
}
|
||||
|
||||
tree.functions['agg-stack-blur'] = function(x, y) {
|
||||
return new tree.ImageFilter('agg-stack-blur', [x, y]);
|
||||
};
|
||||
|
||||
tree.functions['scale-hsla'] = function(h0,h1,s0,s1,l0,l1,a0,a1) {
|
||||
return new tree.ImageFilter('scale-hsla', [h0,h1,s0,s1,l0,l1,a0,a1]);
|
||||
};
|
||||
|
||||
function hsla(h) {
|
||||
return tree.functions.hsla(h.h, h.s, h.l, h.a);
|
||||
}
|
||||
|
||||
function number(n) {
|
||||
@ -143,7 +200,7 @@ function number(n) {
|
||||
} else if (typeof(n) === 'number') {
|
||||
return n;
|
||||
} else {
|
||||
throw new Error('Color functions take numbers as parameters.');
|
||||
return NaN;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,27 @@
|
||||
var sys = require('sys');
|
||||
var util = require('util'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
|
||||
function getVersion() {
|
||||
if (process.browser) {
|
||||
return require('../../package.json').version.split('.');
|
||||
} else if (parseInt(process.version.split('.')[1], 10) > 4) {
|
||||
return require('../../package.json').version.split('.');
|
||||
} else {
|
||||
// older node
|
||||
var package_json = JSON.parse(fs.readFileSync(path.join(__dirname,'../../package.json')));
|
||||
return package_json.version.split('.');
|
||||
}
|
||||
}
|
||||
|
||||
var carto = {
|
||||
version: [0, 4, 3],
|
||||
version: getVersion(),
|
||||
Parser: require('./parser').Parser,
|
||||
Renderer: require('./renderer').Renderer,
|
||||
tree: require('./tree'),
|
||||
RendererJS: require('./renderer_js'),
|
||||
default_reference: require('./torque-reference'),
|
||||
|
||||
// @TODO
|
||||
writeError: function(ctx, options) {
|
||||
@ -18,7 +36,7 @@ var carto = {
|
||||
options.indent = options.indent || '';
|
||||
|
||||
if (!('index' in ctx) || !extract) {
|
||||
return sys.error(options.indent + (ctx.stack || ctx.message));
|
||||
return util.error(options.indent + (ctx.stack || ctx.message));
|
||||
}
|
||||
|
||||
if (typeof(extract[0]) === 'string') {
|
||||
@ -35,30 +53,49 @@ var carto = {
|
||||
if (typeof(extract[2]) === 'string') {
|
||||
error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey'));
|
||||
}
|
||||
error = options.indent + error.join('\n' + options.indent) + '\033[0m\n';
|
||||
error = options.indent + error.join('\n' + options.indent) + '\x1B[0m\n';
|
||||
|
||||
message = options.indent + message + stylize(ctx.message, 'red');
|
||||
ctx.filename && (message += stylize(' in ', 'red') + ctx.filename);
|
||||
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
|
||||
|
||||
sys.error(message, error);
|
||||
util.error(message, error);
|
||||
|
||||
if (ctx.callLine) {
|
||||
sys.error(stylize('from ', 'red') + (ctx.filename || ''));
|
||||
sys.error(stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract);
|
||||
util.error(stylize('from ', 'red') + (ctx.filename || ''));
|
||||
util.error(stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract);
|
||||
}
|
||||
if (ctx.stack) { sys.error(stylize(ctx.stack, 'red')); }
|
||||
if (ctx.stack) { util.error(stylize(ctx.stack, 'red')); }
|
||||
}
|
||||
};
|
||||
|
||||
[ 'alpha', 'anonymous', 'call', 'color', 'comment', 'definition', 'dimension',
|
||||
'directive', 'element', 'expression', 'filterset', 'filter',
|
||||
'keyword', 'layer', 'mixin', 'operation', 'quoted',
|
||||
'reference', 'rule', 'ruleset', 'selector', 'style', 'url', 'value',
|
||||
'variable', 'zoom', 'invalid', 'fontset'
|
||||
].forEach(function(n) {
|
||||
require('./tree/' + n);
|
||||
});
|
||||
|
||||
require('./tree/call');
|
||||
require('./tree/color');
|
||||
require('./tree/comment');
|
||||
require('./tree/definition');
|
||||
require('./tree/dimension');
|
||||
require('./tree/element');
|
||||
require('./tree/expression');
|
||||
require('./tree/filterset');
|
||||
require('./tree/filter');
|
||||
require('./tree/field');
|
||||
require('./tree/keyword');
|
||||
require('./tree/layer');
|
||||
require('./tree/literal');
|
||||
require('./tree/operation');
|
||||
require('./tree/quoted');
|
||||
require('./tree/imagefilter');
|
||||
require('./tree/reference');
|
||||
require('./tree/rule');
|
||||
require('./tree/ruleset');
|
||||
require('./tree/selector');
|
||||
require('./tree/style');
|
||||
require('./tree/url');
|
||||
require('./tree/value');
|
||||
require('./tree/variable');
|
||||
require('./tree/zoom');
|
||||
require('./tree/invalid');
|
||||
require('./tree/fontset');
|
||||
require('./tree/frame_offset');
|
||||
require('./functions');
|
||||
|
||||
for (var k in carto) { exports[k] = carto[k]; }
|
||||
@ -74,7 +111,6 @@ function stylize(str, style) {
|
||||
'red' : [31, 39],
|
||||
'grey' : [90, 39]
|
||||
};
|
||||
return '\033[' + styles[style][0] + 'm' + str +
|
||||
'\033[' + styles[style][1] + 'm';
|
||||
return '\x1B[' + styles[style][0] + 'm' + str +
|
||||
'\x1B[' + styles[style][1] + 'm';
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,105 +1,240 @@
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
var carto = require('./index');
|
||||
var tree = require('./tree');
|
||||
|
||||
carto.Renderer = function Renderer(env) {
|
||||
carto.Renderer = function Renderer(env, options) {
|
||||
this.env = env || {};
|
||||
this.options = options || {};
|
||||
this.options.mapnik_version = this.options.mapnik_version || '3.0.0';
|
||||
};
|
||||
|
||||
// Prepare a MML document (given as an object) into a
|
||||
// fully-localized XML file ready for Mapnik2 consumption
|
||||
//
|
||||
// - @param {String} str the JSON file as a string.
|
||||
// - @param {Object} env renderer environment options.
|
||||
carto.Renderer.prototype.render = function render(m, callback) {
|
||||
/**
|
||||
* Prepare a MSS document (given as an string) into a
|
||||
* XML Style fragment (mostly useful for debugging)
|
||||
*
|
||||
* @param {String} data the mss contents as a string.
|
||||
*/
|
||||
carto.Renderer.prototype.renderMSS = function render(data) {
|
||||
// effects is a container for side-effects, which currently
|
||||
// are limited to FontSets.
|
||||
env = _(this.env).defaults({
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: []
|
||||
});
|
||||
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
var styles = [];
|
||||
|
||||
if (env.benchmark) console.time('Parsing MSS');
|
||||
var parser = (carto.Parser(env)).parse(data);
|
||||
if (env.benchmark) console.timeEnd('Parsing MSS');
|
||||
|
||||
if (env.benchmark) console.time('Rule generation');
|
||||
var rule_list = parser.toList(env);
|
||||
if (env.benchmark) console.timeEnd('Rule generation');
|
||||
|
||||
if (env.benchmark) console.time('Rule inheritance');
|
||||
var rules = inheritDefinitions(rule_list, env);
|
||||
if (env.benchmark) console.timeEnd('Rule inheritance');
|
||||
|
||||
if (env.benchmark) console.time('Style sort');
|
||||
var sorted = sortStyles(rules,env);
|
||||
if (env.benchmark) console.timeEnd('Style sort');
|
||||
|
||||
if (env.benchmark) console.time('Total Style generation');
|
||||
for (var k = 0, rule, style_name; k < sorted.length; k++) {
|
||||
rule = sorted[k];
|
||||
style_name = 'style' + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
|
||||
styles.push(style_name);
|
||||
var bench_name = '\tStyle "'+style_name+'" (#'+k+') toXML';
|
||||
if (env.benchmark) console.time(bench_name);
|
||||
// env.effects can be modified by this call
|
||||
output.push(carto.tree.StyleXML(style_name, rule.attachment, rule, env));
|
||||
if (env.benchmark) console.timeEnd(bench_name);
|
||||
}
|
||||
if (env.benchmark) console.timeEnd('Total Style generation');
|
||||
if (env.errors) throw env.errors;
|
||||
return output.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepare a MML document (given as an object) into a
|
||||
* fully-localized XML file ready for Mapnik2 consumption
|
||||
*
|
||||
* @param {String} m - the JSON file as a string.
|
||||
*/
|
||||
carto.Renderer.prototype.render = function render(m) {
|
||||
// effects is a container for side-effects, which currently
|
||||
// are limited to FontSets.
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: [],
|
||||
ppi: 90.714
|
||||
});
|
||||
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
|
||||
// Transform stylesheets into rulesets.
|
||||
var rulesets = _(m.Stylesheet).chain()
|
||||
// Transform stylesheets into definitions.
|
||||
var definitions = _.chain(m.Stylesheet)
|
||||
.map(function(s) {
|
||||
if (typeof s == 'string') {
|
||||
throw new Error("Stylesheet object is expected not a string: '" + s + "'");
|
||||
}
|
||||
// Passing the environment from stylesheet to stylesheet,
|
||||
// allows frames and effects to be maintained.
|
||||
env = _(env).extend({filename:s.id});
|
||||
env = _.extend(env, {filename:s.id});
|
||||
|
||||
// @TODO try/catch?
|
||||
var time = +new Date(),
|
||||
root = (carto.Parser(env)).parse(s.data);
|
||||
if (env.benchmark)
|
||||
console.warn('Parsing time: ' + ((new Date() - time)) + 'ms');
|
||||
console.warn('Parsing time: ' + (new Date() - time) + 'ms');
|
||||
return root.toList(env);
|
||||
})
|
||||
.flatten()
|
||||
.value();
|
||||
|
||||
function appliesTo(name, classIndex) {
|
||||
return function(definition) {
|
||||
return definition.appliesTo(l.name, classIndex);
|
||||
};
|
||||
}
|
||||
|
||||
// Iterate through layers and create styles custom-built
|
||||
// for each of them, and apply those styles to the layers.
|
||||
m.Layer.forEach(function(l) {
|
||||
l.styles = [];
|
||||
var styles, l, classIndex, rules, sorted, matching;
|
||||
for (var i = 0; i < m.Layer.length; i++) {
|
||||
l = m.Layer[i];
|
||||
styles = [];
|
||||
classIndex = {};
|
||||
|
||||
if (env.benchmark) console.warn('processing layer: ' + l.id);
|
||||
// Classes are given as space-separated alphanumeric strings.
|
||||
var classes = (l['class'] || '').split(/\s+/g);
|
||||
var matching = rulesets.filter(function(definition) {
|
||||
return definition.appliesTo(l.name, classes);
|
||||
});
|
||||
var rules = inheritRules(matching, env);
|
||||
sorted = sortStyles(rules, env);
|
||||
_(sorted).each(function(rule) {
|
||||
var style = new tree.Style(l.name, rule.attachment, rule);
|
||||
if (style) {
|
||||
l.styles.push(style.name);
|
||||
|
||||
// env.effects can be modified by this call
|
||||
output.push(style.toXML(env));
|
||||
}
|
||||
});
|
||||
if (l.styles.length) {
|
||||
output.push((new carto.tree.Layer(l)).toXML());
|
||||
for (var j = 0; j < classes.length; j++) {
|
||||
classIndex[classes[j]] = true;
|
||||
}
|
||||
});
|
||||
matching = definitions.filter(appliesTo(l.name, classIndex));
|
||||
rules = inheritDefinitions(matching, env);
|
||||
sorted = sortStyles(rules, env);
|
||||
|
||||
for (var k = 0, rule, style_name; k < sorted.length; k++) {
|
||||
rule = sorted[k];
|
||||
style_name = l.name + (rule.attachment !== '__default__' ? '-' + rule.attachment : '');
|
||||
|
||||
// env.effects can be modified by this call
|
||||
var styleXML = carto.tree.StyleXML(style_name, rule.attachment, rule, env);
|
||||
|
||||
if (styleXML) {
|
||||
output.push(styleXML);
|
||||
styles.push(style_name);
|
||||
}
|
||||
}
|
||||
|
||||
output.push(carto.tree.LayerXML(l, styles));
|
||||
}
|
||||
|
||||
output.unshift(env.effects.map(function(e) {
|
||||
return e.toXML(env);
|
||||
}).join('\n'));
|
||||
|
||||
try {
|
||||
var map_properties = getMapProperties(m, rulesets, env);
|
||||
} catch (err) {
|
||||
env.error(err);
|
||||
return callback(err);
|
||||
}
|
||||
var map_properties = getMapProperties(m, definitions, env);
|
||||
|
||||
// Exit on errors.
|
||||
if (env.errors) return callback(env.errors);
|
||||
if (env.errors) throw env.errors;
|
||||
|
||||
// Pass TileJSON and other custom parameters through to Mapnik XML.
|
||||
var parameters = _.reduce(m, function(memo, v, k) {
|
||||
if (!v && v !== 0) return memo;
|
||||
|
||||
switch (k) {
|
||||
// Known skippable properties.
|
||||
case 'srs':
|
||||
case 'Layer':
|
||||
case 'Stylesheet':
|
||||
break;
|
||||
// Non URL-bound TileJSON properties.
|
||||
case 'bounds':
|
||||
case 'center':
|
||||
case 'minzoom':
|
||||
case 'maxzoom':
|
||||
case 'version':
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
break;
|
||||
// Properties that require CDATA.
|
||||
case 'name':
|
||||
case 'description':
|
||||
case 'legend':
|
||||
case 'attribution':
|
||||
case 'template':
|
||||
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
|
||||
break;
|
||||
// Mapnik image format.
|
||||
case 'format':
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
break;
|
||||
// Mapnik interactivity settings.
|
||||
case 'interactivity':
|
||||
memo.push(' <Parameter name="interactivity_layer">' + v.layer + '</Parameter>');
|
||||
memo.push(' <Parameter name="interactivity_fields">' + v.fields + '</Parameter>');
|
||||
break;
|
||||
// Support any additional scalar properties.
|
||||
default:
|
||||
if ('string' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '"><![CDATA[' + v + ']]></Parameter>');
|
||||
} else if ('number' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
} else if ('boolean' === typeof v) {
|
||||
memo.push(' <Parameter name="' + k + '">' + v + '</Parameter>');
|
||||
}
|
||||
break;
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
if (parameters.length) output.unshift(
|
||||
'<Parameters>\n' +
|
||||
parameters.join('\n') +
|
||||
'\n</Parameters>\n'
|
||||
);
|
||||
|
||||
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
|
||||
|
||||
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
|
||||
output.unshift(
|
||||
'<?xml version="1.0" ' +
|
||||
'encoding="utf-8"?>\n' +
|
||||
'<!DOCTYPE Map[]>\n' +
|
||||
'<Map' + properties + '>\n');
|
||||
'<Map' + properties +'>\n');
|
||||
output.push('</Map>');
|
||||
return callback(null, output.join('\n'));
|
||||
return output.join('\n');
|
||||
};
|
||||
|
||||
// This function currently modifies 'current'
|
||||
function addRules(current, definition, existing) {
|
||||
var newFilters = definition.filters;
|
||||
var newRules = definition.rules;
|
||||
var updatedFilters, clone, previous;
|
||||
/**
|
||||
* This function currently modifies 'current'
|
||||
* @param {Array} current current list of rules
|
||||
* @param {Object} definition a Definition object to add to the rules
|
||||
* @param {Object} byFilter an object/dictionary of existing filters. This is
|
||||
* actually keyed `attachment->filter`
|
||||
* @param {Object} env the current environment
|
||||
*/
|
||||
function addRules(current, definition, byFilter, env) {
|
||||
var newFilters = definition.filters,
|
||||
newRules = definition.rules,
|
||||
updatedFilters, clone, previous;
|
||||
|
||||
// The current definition might have been split up into
|
||||
// multiple definitions already.
|
||||
for (var k = 0; k < current.length; k++) {
|
||||
updatedFilters = current[k].filters.cloneWith(newFilters);
|
||||
if (updatedFilters) {
|
||||
previous = existing[updatedFilters];
|
||||
previous = byFilter[updatedFilters];
|
||||
if (previous) {
|
||||
// There's already a definition with those exact
|
||||
// filters. Add the current definitions' rules
|
||||
@ -116,32 +251,63 @@ function addRules(current, definition, existing) {
|
||||
// to make sure that in the next loop iteration, we're
|
||||
// not performing the same task for this element again,
|
||||
// hence the k++.
|
||||
byFilter[updatedFilters] = clone;
|
||||
current.splice(k, 0, clone);
|
||||
k++;
|
||||
}
|
||||
}
|
||||
} else if (updatedFilters === null) {
|
||||
// if updatedFilters is null, then adding the filters doesn't
|
||||
// invalidate or split the selector, so we addRules to the
|
||||
// combined selector
|
||||
|
||||
// Filters can be added, but they don't change the
|
||||
// filters. This means we don't have to split the
|
||||
// definition.
|
||||
//
|
||||
// this is cloned here because of shared classes, see
|
||||
// sharedclass.mss
|
||||
current[k] = current[k].clone();
|
||||
current[k].addRules(newRules);
|
||||
}
|
||||
// if updatedFeatures is false, then the filters split the rule,
|
||||
// so they aren't the same inheritance chain
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
// Apply inherited styles from their ancestors to them.
|
||||
function inheritRules(definitions, env) {
|
||||
/**
|
||||
* Apply inherited styles from their ancestors to them.
|
||||
*
|
||||
* called either once per render (in the case of mss) or per layer
|
||||
* (for mml)
|
||||
*
|
||||
* @param {Object} definitions - a list of definitions objects
|
||||
* that contain .rules
|
||||
* @param {Object} env - the environment
|
||||
* @return {Array<Array>} an array of arrays is returned,
|
||||
* in which each array refers to a specific attachment
|
||||
*/
|
||||
function inheritDefinitions(definitions, env) {
|
||||
var inheritTime = +new Date();
|
||||
// definitions are ordered by specificity,
|
||||
// high (index 0) to low
|
||||
var byAttachment = {}, byFilter = {};
|
||||
var byAttachment = {},
|
||||
byFilter = {};
|
||||
var result = [];
|
||||
var current, previous, attachment;
|
||||
|
||||
// Evaluate the filters specified by each definition with the given
|
||||
// environment to correctly resolve variable references
|
||||
definitions.forEach(function(d) {
|
||||
d.filters.ev(env);
|
||||
});
|
||||
|
||||
for (var i = 0; i < definitions.length; i++) {
|
||||
|
||||
attachment = definitions[i].attachment;
|
||||
current = [definitions[i]];
|
||||
|
||||
if (!byAttachment[attachment]) {
|
||||
byAttachment[attachment] = [];
|
||||
byAttachment[attachment].attachment = attachment;
|
||||
@ -149,12 +315,11 @@ function inheritRules(definitions, env) {
|
||||
result.push(byAttachment[attachment]);
|
||||
}
|
||||
|
||||
current = [definitions[i]];
|
||||
// Iterate over all subsequent rules.
|
||||
for (var j = i + 1; j < definitions.length; j++) {
|
||||
if (definitions[j].attachment === attachment) {
|
||||
// Only inherit rules from the same attachment.
|
||||
current = addRules(current, definitions[j], byFilter);
|
||||
current = addRules(current, definitions[j], byFilter[attachment], env);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,46 +332,54 @@ function inheritRules(definitions, env) {
|
||||
if (env.benchmark) console.warn('Inheritance time: ' + ((new Date() - inheritTime)) + 'ms');
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// Sort styles by the minimum index of their rules.
|
||||
// This sorts a slice of the styles, so it returns a sorted
|
||||
// array but does not change the input.
|
||||
function sortStylesIndex(a, b) { return b.index - a.index; }
|
||||
function sortStyles(styles, env) {
|
||||
styles.forEach(function(style) {
|
||||
for (var i = 0; i < styles.length; i++) {
|
||||
var style = styles[i];
|
||||
style.index = Infinity;
|
||||
style.forEach(function(block) {
|
||||
block.rules.forEach(function(rule) {
|
||||
for (var b = 0; b < style.length; b++) {
|
||||
var rules = style[b].rules;
|
||||
for (var r = 0; r < rules.length; r++) {
|
||||
var rule = rules[r];
|
||||
if (rule.index < style.index) {
|
||||
style.index = rule.index;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = styles.slice();
|
||||
result.sort(function(a, b) {
|
||||
return b.index - a.index;
|
||||
});
|
||||
result.sort(sortStylesIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find a rule like Map { background-color: #fff; },
|
||||
// if any, and return a list of properties to be inserted
|
||||
// into the <Map element of the resulting XML. Translates
|
||||
// properties of the mml object at `m` directly into XML
|
||||
// properties.
|
||||
//
|
||||
// - @param {Object} m the mml object.
|
||||
// - @param {Array} rulesets the output of toList.
|
||||
// - @param {Object} env.
|
||||
// - @return {String} rendered properties.
|
||||
function getMapProperties(m, rulesets, env) {
|
||||
/**
|
||||
* Find a rule like Map { background-color: #fff; },
|
||||
* if any, and return a list of properties to be inserted
|
||||
* into the <Map element of the resulting XML. Translates
|
||||
* properties of the mml object at `m` directly into XML
|
||||
* properties.
|
||||
*
|
||||
* @param {Object} m the mml object.
|
||||
* @param {Array} definitions the output of toList.
|
||||
* @param {Object} env
|
||||
* @return {String} rendered properties.
|
||||
*/
|
||||
function getMapProperties(m, definitions, env) {
|
||||
var rules = {};
|
||||
var symbolizers = tree.Reference.data.symbolizers.map;
|
||||
var symbolizers = carto.tree.Reference.data.symbolizers.map;
|
||||
|
||||
_(m).each(function(value, key) {
|
||||
if (key in symbolizers) rules[key] = key + '="' + value + '"';
|
||||
});
|
||||
|
||||
rulesets.filter(function(r) {
|
||||
definitions.filter(function(r) {
|
||||
return r.elements.join('') === 'Map';
|
||||
}).forEach(function(r) {
|
||||
for (var i = 0; i < r.rules.length; i++) {
|
||||
@ -217,10 +390,13 @@ function getMapProperties(m, rulesets, env) {
|
||||
index: r.rules[i].index
|
||||
});
|
||||
}
|
||||
rules[key] = r.rules[i].eval(env).toXML(env);
|
||||
rules[key] = r.rules[i].ev(env).toXML(env);
|
||||
}
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
|
||||
module.exports = carto;
|
||||
module.exports.addRules = addRules;
|
||||
module.exports.inheritDefinitions = inheritDefinitions;
|
||||
module.exports.sortStyles = sortStyles;
|
||||
|
302
lib/carto/renderer_js.js
Normal file
302
lib/carto/renderer_js.js
Normal file
@ -0,0 +1,302 @@
|
||||
(function(carto) {
|
||||
var tree = require('./tree');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
|
||||
function CartoCSS(style, options) {
|
||||
this.options = options || {};
|
||||
this.imageURLs = [];
|
||||
if(style) {
|
||||
this.setStyle(style);
|
||||
}
|
||||
}
|
||||
|
||||
CartoCSS.Layer = function(shader, options) {
|
||||
this.options = options;
|
||||
this.shader = shader;
|
||||
};
|
||||
|
||||
|
||||
CartoCSS.Layer.prototype = {
|
||||
|
||||
fullName: function() {
|
||||
return this.shader.attachment;
|
||||
},
|
||||
|
||||
name: function() {
|
||||
return this.fullName().split('::')[0];
|
||||
},
|
||||
|
||||
// frames this layer need to be rendered
|
||||
frames: function() {
|
||||
return this.shader.frames;
|
||||
},
|
||||
|
||||
attachment: function() {
|
||||
return this.fullName().split('::')[1];
|
||||
},
|
||||
|
||||
eval: function(prop) {
|
||||
var p = this.shader[prop];
|
||||
if (!p || !p.style) return;
|
||||
return p.style({}, { zoom: 0, 'frame-offset': 0 });
|
||||
},
|
||||
|
||||
/*
|
||||
* `props`: feature properties
|
||||
* `context`: rendering properties, i.e zoom
|
||||
*/
|
||||
getStyle: function(props, context) {
|
||||
var style = {};
|
||||
for(var i in this.shader) {
|
||||
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
|
||||
style[i] = this.shader[i].style(props, context);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
/**
|
||||
* return the symbolizers that need to be rendered with
|
||||
* this style. The order is the rendering order.
|
||||
* @returns a list with 3 possible values 'line', 'marker', 'polygon'
|
||||
*/
|
||||
getSymbolizers: function() {
|
||||
return this.shader.symbolizers;
|
||||
},
|
||||
|
||||
/**
|
||||
* returns if the style varies with some feature property.
|
||||
* Useful to optimize rendering
|
||||
*/
|
||||
isVariable: function() {
|
||||
for(var i in this.shader) {
|
||||
if(i !== 'attachment' && i !== 'zoom' && i !== 'frames' && i !== 'symbolizers') {
|
||||
if (!this.shader[i].constant) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getShader: function() {
|
||||
return this.shader;
|
||||
},
|
||||
|
||||
/**
|
||||
* returns true if a feature needs to be rendered
|
||||
*/
|
||||
filter: function(featureType, props, context) {
|
||||
for(var i in this.shader) {
|
||||
var s = this.shader[i](props, context);
|
||||
if(s) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
//
|
||||
// given a geoemtry type returns the transformed one acording the CartoCSS
|
||||
// For points there are two kind of types: point and sprite, the first one
|
||||
// is a circle, second one is an image sprite
|
||||
//
|
||||
// the other geometry types are the same than geojson (polygon, linestring...)
|
||||
//
|
||||
transformGeometry: function(type) {
|
||||
return type;
|
||||
},
|
||||
|
||||
transformGeometries: function(geojson) {
|
||||
return geojson;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CartoCSS.prototype = {
|
||||
|
||||
setStyle: function(style) {
|
||||
var layers = this.parse(style);
|
||||
if(!layers) {
|
||||
throw new Error(this.parse_env.errors);
|
||||
}
|
||||
this.layers = layers.map(function(shader) {
|
||||
return new CartoCSS.Layer(shader);
|
||||
});
|
||||
},
|
||||
|
||||
getLayers: function() {
|
||||
return this.layers;
|
||||
},
|
||||
|
||||
getDefault: function() {
|
||||
return this.findLayer({ attachment: '__default__' });
|
||||
},
|
||||
|
||||
findLayer: function(where) {
|
||||
return _.find(this.layers, function(value) {
|
||||
for (var key in where) {
|
||||
var v = value[key];
|
||||
if (typeof(v) === 'function') {
|
||||
v = v.call(value);
|
||||
}
|
||||
if (where[key] !== v) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
_createFn: function(ops) {
|
||||
var body = ops.join('\n');
|
||||
if(this.options.debug) console.log(body);
|
||||
return Function("data","ctx", "var _value = null; " + body + "; return _value; ");
|
||||
},
|
||||
|
||||
_compile: function(shader) {
|
||||
if(typeof shader === 'string') {
|
||||
shader = eval("(function() { return " + shader +"; })()");
|
||||
}
|
||||
this.shader_src = shader;
|
||||
for(var attr in shader) {
|
||||
var c = mapper[attr];
|
||||
if(c) {
|
||||
this.compiled[c] = eval("(function() { return shader[attr]; })();");
|
||||
}
|
||||
}
|
||||
},
|
||||
getImageURLs: function(){
|
||||
return this.imageURLs;
|
||||
},
|
||||
|
||||
parse: function(cartocss) {
|
||||
var parse_env = {
|
||||
frames: [],
|
||||
errors: [],
|
||||
error: function(obj) {
|
||||
this.errors.push(obj);
|
||||
}
|
||||
};
|
||||
this.parse_env = parse_env;
|
||||
|
||||
var ruleset = null;
|
||||
try {
|
||||
ruleset = (new carto.Parser(parse_env)).parse(cartocss);
|
||||
} catch(e) {
|
||||
// add the style.mss string to match the response from the server
|
||||
parse_env.errors.push(e.message);
|
||||
return;
|
||||
}
|
||||
if(ruleset) {
|
||||
|
||||
function defKey(def) {
|
||||
return def.elements[0] + "::" + def.attachment;
|
||||
}
|
||||
var defs = ruleset.toList(parse_env);
|
||||
defs.reverse();
|
||||
// group by elements[0].value::attachment
|
||||
var layers = {};
|
||||
for(var i = 0; i < defs.length; ++i) {
|
||||
var def = defs[i];
|
||||
var key = defKey(def);
|
||||
var layer = layers[key] = (layers[key] || {
|
||||
symbolizers: []
|
||||
});
|
||||
|
||||
for(var u = 0; u<def.rules.length; u++){
|
||||
var rule = def.rules[u];
|
||||
if(rule.name === "marker-file" || rule.name === "point-file"){
|
||||
var value = rule.value.value[0].value[0].value.value;
|
||||
this.imageURLs.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
layer.frames = [];
|
||||
layer.zoom = tree.Zoom.all;
|
||||
var props = def.toJS(parse_env);
|
||||
if (this.options.debug) console.log("props", props);
|
||||
for(var v in props) {
|
||||
var lyr = layer[v] = layer[v] || {
|
||||
constant: false,
|
||||
symbolizer: null,
|
||||
js: [],
|
||||
index: 0
|
||||
};
|
||||
// build javascript statements
|
||||
lyr.js.push(props[v].map(function(a) { return a.js; }).join('\n'));
|
||||
// get symbolizer for prop
|
||||
lyr.symbolizer = _.first(props[v].map(function(a) { return a.symbolizer; }));
|
||||
// serach the max index to know rendering order
|
||||
lyr.index = _.max(props[v].map(function(a) { return a.index; }).concat(lyr.index));
|
||||
lyr.constant = !_.any(props[v].map(function(a) { return !a.constant; }));
|
||||
// True when the property is filtered.
|
||||
lyr.filtered = props[v][0].filtered;
|
||||
}
|
||||
}
|
||||
|
||||
var ordered_layers = [];
|
||||
if (this.options.debug) console.log(layers);
|
||||
|
||||
var done = {};
|
||||
for(var i = 0; i < defs.length; ++i) {
|
||||
var def = defs[i];
|
||||
|
||||
if (this.options.strict) {
|
||||
def.toXML(parse_env, {});
|
||||
if (parse_env.errors.message) {
|
||||
throw new Error(parse_env.errors.message);
|
||||
}
|
||||
}
|
||||
|
||||
var k = defKey(def);
|
||||
var layer = layers[k];
|
||||
if(!done[k]) {
|
||||
if(this.options.debug) console.log("**", k);
|
||||
for(var prop in layer) {
|
||||
if (prop !== 'zoom' && prop !== 'frames' && prop !== 'symbolizers') {
|
||||
if(this.options.debug) console.log("*", prop);
|
||||
layer[prop].style = this._createFn(layer[prop].js);
|
||||
layer.symbolizers.push(layer[prop].symbolizer);
|
||||
layer.symbolizers = _.uniq(layer.symbolizers);
|
||||
}
|
||||
}
|
||||
layer.attachment = k;
|
||||
ordered_layers.push(layer);
|
||||
done[k] = true;
|
||||
}
|
||||
layer.zoom |= def.zoom;
|
||||
layer.frames.push(def.frame_offset);
|
||||
}
|
||||
|
||||
// uniq the frames
|
||||
for(i = 0; i < ordered_layers.length; ++i) {
|
||||
ordered_layers[i].frames = _.uniq(ordered_layers[i].frames);
|
||||
}
|
||||
|
||||
return ordered_layers;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
carto.RendererJS = function (options) {
|
||||
this.options = options || {};
|
||||
this.options.mapnik_version = this.options.mapnik_version || 'latest';
|
||||
this.reference = this.options.reference || require('./torque-reference').version.latest;
|
||||
this.options.strict = this.options.hasOwnProperty('strict') ? this.options.strict : false;
|
||||
};
|
||||
|
||||
// Prepare a javascript object which contains the layers
|
||||
carto.RendererJS.prototype.render = function render(cartocss, callback) {
|
||||
tree.Reference.setData(this.reference);
|
||||
return new CartoCSS(cartocss, this.options);
|
||||
}
|
||||
|
||||
if(typeof(module) !== 'undefined') {
|
||||
module.exports = carto.RendererJS;
|
||||
}
|
||||
|
||||
|
||||
})(require('../carto'));
|
1942
lib/carto/torque-reference.js
Normal file
1942
lib/carto/torque-reference.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,11 @@
|
||||
/**
|
||||
* TODO: document this. What does this do?
|
||||
*/
|
||||
module.exports.find = function (obj, fun) {
|
||||
for (var i = 0, r; i < obj.length; i++) {
|
||||
if (r = fun.call(obj, obj[i])) { return r; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if(typeof(module) !== "undefined") {
|
||||
module.exports.find = function (obj, fun) {
|
||||
for (var i = 0, r; i < obj.length; i++) {
|
||||
if (r = fun.call(obj, obj[i])) { return r; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
@ -1,14 +0,0 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Alpha = function Alpha(val) {
|
||||
this.value = val;
|
||||
};
|
||||
tree.Alpha.prototype = {
|
||||
toString: function() {
|
||||
return 'alpha(opacity=' +
|
||||
(this.value.toString ? this.value.toString() : this.value) + ')';
|
||||
},
|
||||
eval: function() { return this; }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -1,13 +0,0 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Anonymous = function Anonymous(string) {
|
||||
this.value = string.value || string;
|
||||
};
|
||||
tree.Anonymous.prototype = {
|
||||
toString: function() {
|
||||
return this.value;
|
||||
},
|
||||
eval: function() { return this; }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -1,27 +1,23 @@
|
||||
(function(tree) {
|
||||
|
||||
//
|
||||
// A function call node.
|
||||
//
|
||||
tree.Call = function Call(name, args) {
|
||||
var _ = global._ || require('underscore');
|
||||
tree.Call = function Call(name, args, index) {
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Call.prototype = {
|
||||
//
|
||||
// When evaluating a function call,
|
||||
is: 'call',
|
||||
// When evuating a function call,
|
||||
// we either find the function in `tree.functions` [1],
|
||||
// in which case we call it, passing the evaluated arguments,
|
||||
// or we simply print it out as it appeared originally [2].
|
||||
//
|
||||
// The *functions.js* file contains the built-in functions.
|
||||
//
|
||||
// The reason why we evaluate the arguments, is in the case where
|
||||
// we try to pass a variable to a function, like: `saturate(@color)`.
|
||||
// The function should receive the value, not the variable.
|
||||
//
|
||||
eval: function(env) {
|
||||
var args = this.args.map(function(a) { return a.eval(env); });
|
||||
'ev': function(env) {
|
||||
var args = this.args.map(function(a) { return a.ev(env); });
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (args[i].is === 'undefined') {
|
||||
@ -32,16 +28,84 @@ tree.Call.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.name in tree.functions) { // 1.
|
||||
return tree.functions[this.name].apply(tree.functions, args);
|
||||
} else { // 2.
|
||||
return new tree.Anonymous(this.name +
|
||||
'(' + args.map(function(a) { return a.toString(); }).join(', ') + ')');
|
||||
if (this.name in tree.functions) {
|
||||
if (tree.functions[this.name].length <= args.length) {
|
||||
var val = tree.functions[this.name].apply(tree.functions, args);
|
||||
if (val === null) {
|
||||
env.error({
|
||||
message: 'incorrect arguments given to ' + this.name + '()',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
return val;
|
||||
} else {
|
||||
env.error({
|
||||
message: 'incorrect number of arguments for ' + this.name +
|
||||
'(). ' + tree.functions[this.name].length + ' expected.',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
var fn = tree.Reference.mapnikFunctions[this.name];
|
||||
if (fn === undefined) {
|
||||
var functions = _.pairs(tree.Reference.mapnikFunctions);
|
||||
// cheap closest, needs improvement.
|
||||
var name = this.name;
|
||||
var mean = functions.map(function(f) {
|
||||
return [f[0], tree.Reference.editDistance(name, f[0]), f[1]];
|
||||
}).sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
});
|
||||
env.error({
|
||||
message: 'unknown function ' + this.name + '(), did you mean ' +
|
||||
mean[0][0] + '(' + mean[0][2] + ')',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
if (fn !== args.length &&
|
||||
!(Array.isArray(fn) && _.include(fn, args.length)) &&
|
||||
// support variable-arg functions like `colorize-alpha`
|
||||
fn !== -1) {
|
||||
env.error({
|
||||
message: 'function ' + this.name + '() takes ' +
|
||||
fn + ' arguments and was given ' + args.length,
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
} else {
|
||||
// Save the evaluated versions of arguments
|
||||
this.args = args;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toString: function(env) {
|
||||
return this.eval(env).toString();
|
||||
toString: function(env, format) {
|
||||
if (this.args.length) {
|
||||
return this.name + '(' + this.args.join(',') + ')';
|
||||
} else {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
(function(tree) {
|
||||
//
|
||||
// RGB Colors - #ff0014, #eee
|
||||
//
|
||||
// can be initialized with a 3 or 6 char string or a 3 or 4 element
|
||||
// numerical array
|
||||
tree.Color = function Color(rgb, a) {
|
||||
//
|
||||
// The end goal here, is to parse the arguments
|
||||
// into an integer triplet, such as `128, 255, 0`
|
||||
//
|
||||
// This facilitates operations and conversions.
|
||||
//
|
||||
if (Array.isArray(rgb)) {
|
||||
this.rgb = rgb;
|
||||
this.rgb = rgb.slice(0, 3);
|
||||
} else if (rgb.length == 6) {
|
||||
this.rgb = rgb.match(/.{2}/g).map(function(c) {
|
||||
return parseInt(c, 16);
|
||||
@ -20,18 +18,24 @@ tree.Color = function Color(rgb, a) {
|
||||
return parseInt(c + c, 16);
|
||||
});
|
||||
}
|
||||
this.is = 'color';
|
||||
this.alpha = typeof(a) === 'number' ? a : 1;
|
||||
};
|
||||
tree.Color.prototype = {
|
||||
eval: function() { return this; },
|
||||
|
||||
//
|
||||
if (typeof(a) === 'number') {
|
||||
this.alpha = a;
|
||||
} else if (rgb.length === 4) {
|
||||
this.alpha = rgb[3];
|
||||
} else {
|
||||
this.alpha = 1;
|
||||
}
|
||||
};
|
||||
|
||||
tree.Color.prototype = {
|
||||
is: 'color',
|
||||
'ev': function() { return this; },
|
||||
|
||||
// If we have some transparency, the only way to represent it
|
||||
// is via `rgba`. Otherwise, we use the hex representation,
|
||||
// which has better compatibility with older browsers.
|
||||
// Values are capped between `0` and `255`, rounded and zero-padded.
|
||||
//
|
||||
toString: function() {
|
||||
if (this.alpha < 1.0) {
|
||||
return 'rgba(' + this.rgb.map(function(c) {
|
||||
@ -46,13 +50,11 @@ tree.Color.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Operations have to be done per-channel, if not,
|
||||
// channels will spill onto each other. Once we have
|
||||
// our result, in the form of an integer triplet,
|
||||
// we create a new Color node to hold the result.
|
||||
//
|
||||
operate: function(op, other) {
|
||||
operate: function(env, op, other) {
|
||||
var result = [];
|
||||
|
||||
if (! (other instanceof tree.Color)) {
|
||||
@ -90,5 +92,4 @@ tree.Color.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -4,11 +4,12 @@ tree.Comment = function Comment(value, silent) {
|
||||
this.value = value;
|
||||
this.silent = !!silent;
|
||||
};
|
||||
|
||||
tree.Comment.prototype = {
|
||||
toString: function(env) {
|
||||
return '<!--' + this.value + '-->';
|
||||
},
|
||||
eval: function() { return this; }
|
||||
'ev': function() { return this; }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,18 +1,26 @@
|
||||
(function(tree) {
|
||||
var assert = require('assert');
|
||||
var assert = require('assert'),
|
||||
_ = global._ || require('underscore');
|
||||
|
||||
// A definition is the combination of a selector and rules, like
|
||||
// #foo {
|
||||
// polygon-opacity:1.0;
|
||||
// }
|
||||
//
|
||||
// The selector can have filters
|
||||
tree.Definition = function Definition(selector, rules) {
|
||||
this.elements = selector.elements;
|
||||
assert.ok(selector.filters instanceof tree.Filterset);
|
||||
this.rules = rules;
|
||||
this.ruleIndex = [];
|
||||
this.ruleIndex = {};
|
||||
for (var i = 0; i < this.rules.length; i++) {
|
||||
if ('zoom' in this.rules[i]) this.rules[i] = this.rules[i].clone();
|
||||
this.rules[i].zoom = selector.zoom;
|
||||
this.ruleIndex.push(this.rules[i].updateID());
|
||||
this.ruleIndex[this.rules[i].updateID()] = true;
|
||||
}
|
||||
this.filters = selector.filters;
|
||||
this.zoom = selector.zoom;
|
||||
this.frame_offset = selector.frame_offset;
|
||||
this.attachment = selector.attachment || '__default__';
|
||||
this.specificity = selector.specificity();
|
||||
};
|
||||
@ -29,7 +37,7 @@ tree.Definition.prototype.clone = function(filters) {
|
||||
if (filters) assert.ok(filters instanceof tree.Filterset);
|
||||
var clone = Object.create(tree.Definition.prototype);
|
||||
clone.rules = this.rules.slice();
|
||||
clone.ruleIndex = this.ruleIndex.slice();
|
||||
clone.ruleIndex = _.clone(this.ruleIndex);
|
||||
clone.filters = filters ? filters : this.filters.clone();
|
||||
clone.attachment = this.attachment;
|
||||
return clone;
|
||||
@ -40,9 +48,9 @@ tree.Definition.prototype.addRules = function(rules) {
|
||||
|
||||
// Add only unique rules.
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
if (this.ruleIndex.indexOf(rules[i].id) < 0) {
|
||||
if (!this.ruleIndex[rules[i].id]) {
|
||||
this.rules.push(rules[i]);
|
||||
this.ruleIndex.push(rules[i].id);
|
||||
this.ruleIndex[rules[i].id] = true;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
@ -50,25 +58,34 @@ tree.Definition.prototype.addRules = function(rules) {
|
||||
return added;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine whether this selector matches a given id
|
||||
* and array of classes, by determining whether
|
||||
* all elements it contains match.
|
||||
*/
|
||||
// Determine whether this selector matches a given id
|
||||
// and array of classes, by determining whether
|
||||
// all elements it contains match.
|
||||
tree.Definition.prototype.appliesTo = function(id, classes) {
|
||||
for (var i = 0; i < this.elements.length; i++) {
|
||||
if (!this.elements[i].matches(id, classes)) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0, l = this.elements.length; i < l; i++) {
|
||||
var elem = this.elements[i];
|
||||
if (!(elem.wildcard ||
|
||||
(elem.type === 'class' && classes[elem.clean]) ||
|
||||
(elem.type === 'id' && id === elem.clean))) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
function symbolizerName(symbolizer) {
|
||||
function capitalize(str) { return str[1].toUpperCase(); }
|
||||
return symbolizer.charAt(0).toUpperCase() +
|
||||
symbolizer.slice(1).replace(/\-./, capitalize) + 'Symbolizer';
|
||||
}
|
||||
|
||||
// Get a simple list of the symbolizers, in order
|
||||
function symbolizerList(sym_order) {
|
||||
return sym_order.sort(function(a, b) { return a[1] - b[1]; })
|
||||
.map(function(v) { return v[0]; });
|
||||
}
|
||||
|
||||
tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
var xml = ' <Rule>\n';
|
||||
xml += tree.Zoom.toXML(zoom).join('');
|
||||
xml += this.filters.toXML(env);
|
||||
|
||||
var xml = zoom.toXML(env).join('') + this.filters.toXML(env);
|
||||
|
||||
// Sort symbolizers by the index of their first property definition
|
||||
var sym_order = [], indexes = [];
|
||||
for (var key in symbolizers) {
|
||||
@ -80,17 +97,20 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
sym_order.push([key, min_idx]);
|
||||
}
|
||||
|
||||
// Get a simple list of the symbolizers, in order
|
||||
sym_order = sym_order.sort(function(a, b) {
|
||||
return a[1] - b[1];
|
||||
}).map(function(v) {
|
||||
return v[0];
|
||||
});
|
||||
sym_order = symbolizerList(sym_order);
|
||||
var sym_count = 0;
|
||||
|
||||
for (var i = 0; i < sym_order.length; i++) {
|
||||
attributes = symbolizers[sym_order[i]];
|
||||
var attributes = symbolizers[sym_order[i]];
|
||||
var symbolizer = sym_order[i].split('/').pop();
|
||||
if (fail = tree.Reference.requiredProperties(symbolizer, attributes)) {
|
||||
|
||||
// Skip the magical * symbolizer which is used for universal properties
|
||||
// which are bubbled up to Style elements intead of Symbolizer elements.
|
||||
if (symbolizer === '*') continue;
|
||||
sym_count++;
|
||||
|
||||
var fail = tree.Reference.requiredProperties(symbolizer, attributes);
|
||||
if (fail) {
|
||||
var rule = attributes[Object.keys(attributes).shift()];
|
||||
env.error({
|
||||
message: fail,
|
||||
@ -99,32 +119,43 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
});
|
||||
}
|
||||
|
||||
var name = symbolizer.charAt(0).toUpperCase() +
|
||||
symbolizer.slice(1).replace(/\-./, function(str) {
|
||||
return str[1].toUpperCase();
|
||||
}) + 'Symbolizer';
|
||||
var name = symbolizerName(symbolizer);
|
||||
|
||||
var selfclosing = true, tagcontent;
|
||||
xml += ' <' + name + ' ';
|
||||
for (var key in attributes) {
|
||||
var x = tree.Reference.selector(attributes[key].name);
|
||||
for (var j in attributes) {
|
||||
if (symbolizer === 'map') env.error({
|
||||
message: 'Map properties are not permitted in other rules',
|
||||
index: attributes[j].index,
|
||||
filename: attributes[j].filename
|
||||
});
|
||||
var x = tree.Reference.selector(attributes[j].name);
|
||||
if (x && x.serialization && x.serialization === 'content') {
|
||||
selfclosing = false;
|
||||
tagcontent = attributes[key].eval(env).toXML(env, true);
|
||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
||||
} else if (x && x.serialization && x.serialization === 'tag') {
|
||||
selfclosing = false;
|
||||
tagcontent = attributes[j].ev(env).toXML(env, true);
|
||||
} else {
|
||||
xml += attributes[key].eval(env).toXML(env) + ' ';
|
||||
xml += attributes[j].ev(env).toXML(env) + ' ';
|
||||
}
|
||||
}
|
||||
if (selfclosing) {
|
||||
xml += '/>\n';
|
||||
} else {
|
||||
xml += '>' + tagcontent + '</' + name + '>\n';
|
||||
} else if (typeof tagcontent !== "undefined") {
|
||||
if (tagcontent.indexOf('<') != -1) {
|
||||
xml += '>' + tagcontent + '</' + name + '>\n';
|
||||
} else {
|
||||
xml += '><![CDATA[' + tagcontent + ']]></' + name + '>\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
xml += ' </Rule>\n';
|
||||
return xml;
|
||||
if (!sym_count || !xml) return '';
|
||||
return ' <Rule>\n' + xml + ' </Rule>\n';
|
||||
};
|
||||
|
||||
// Take a zoom range of zooms and 'i', the index of a rule in this.rules,
|
||||
// and finds all applicable symbolizers
|
||||
tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
|
||||
var symbolizers = {}, child;
|
||||
|
||||
@ -148,18 +179,18 @@ tree.Definition.prototype.collectSymbolizers = function(zooms, i) {
|
||||
}
|
||||
};
|
||||
|
||||
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
|
||||
// scaledenominators that cover the whole range from the first to last bit set.
|
||||
// This algorithm can produces zoom ranges that may have holes. However,
|
||||
// when using the filter-mode="first", more specific zoom filters will always
|
||||
// end up before broader ranges. The filter-mode will pick those first before
|
||||
// resorting to the zoom range with the hole and stop processing further rules.
|
||||
tree.Definition.prototype.toXML = function(env, existing) {
|
||||
// The tree.Zoom.toString function ignores the holes in zoom ranges and outputs
|
||||
// scaledenominators that cover the whole range from the first to last bit set.
|
||||
// This algorithm can produces zoom ranges that may have holes. However,
|
||||
// when using the filter-mode="first", more specific zoom filters will always
|
||||
// end up before broader ranges. The filter-mode will pick those first before
|
||||
// resorting to the zoom range with the hole and stop processing further rules.
|
||||
var filter = this.filters.toString();
|
||||
if (!(filter in existing)) existing[filter] = tree.Zoom.all;
|
||||
|
||||
var available = tree.Zoom.all, xml = '', zoom, symbolizers;
|
||||
var zooms = { available: tree.Zoom.all };
|
||||
var available = tree.Zoom.all, xml = '', zoom, symbolizers,
|
||||
zooms = { available: tree.Zoom.all };
|
||||
for (var i = 0; i < this.rules.length && available; i++) {
|
||||
zooms.rule = this.rules[i].zoom;
|
||||
if (!(existing[filter] & zooms.rule)) continue;
|
||||
@ -167,7 +198,8 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
||||
while (zooms.current = zooms.rule & available) {
|
||||
if (symbolizers = this.collectSymbolizers(zooms, i)) {
|
||||
if (!(existing[filter] & zooms.current)) continue;
|
||||
xml += this.symbolizersToXML(env, symbolizers, existing[filter] & zooms.current);
|
||||
xml += this.symbolizersToXML(env, symbolizers,
|
||||
(new tree.Zoom()).setZoom(existing[filter] & zooms.current));
|
||||
existing[filter] &= ~zooms.current;
|
||||
}
|
||||
}
|
||||
@ -176,4 +208,41 @@ tree.Definition.prototype.toXML = function(env, existing) {
|
||||
return xml;
|
||||
};
|
||||
|
||||
tree.Definition.prototype.toJS = function(env) {
|
||||
var shaderAttrs = {};
|
||||
var frame_offset = this.frame_offset;
|
||||
var zoomFilter = "(" + this.zoom + " & (1 << ctx.zoom))";
|
||||
var filters = [zoomFilter];
|
||||
var originalFilters = this.filters.toJS(env);
|
||||
// Ignore default zoom for filtering (https://github.com/CartoDB/carto/issues/40)
|
||||
var zoomFiltered = this.zoom !== tree.Zoom.all;
|
||||
|
||||
if (originalFilters) {
|
||||
filters.push(originalFilters);
|
||||
}
|
||||
|
||||
if (frame_offset) {
|
||||
filters.push('ctx["frame-offset"] === ' + frame_offset);
|
||||
}
|
||||
|
||||
_.each(this.rules, function (rule) {
|
||||
var exportedRule = {};
|
||||
|
||||
if (!rule instanceof tree.Rule) {
|
||||
throw new Error("Ruleset not supported");
|
||||
}
|
||||
|
||||
exportedRule.index = rule.index;
|
||||
exportedRule.symbolizer = rule.symbolizer;
|
||||
exportedRule.js = "if(" + filters.join(" && ") + "){" + rule.value.toJS(env) + "}";
|
||||
exportedRule.constant = rule.value.ev(env).is !== 'field';
|
||||
exportedRule.filtered = zoomFiltered || (originalFilters !== '');
|
||||
shaderAttrs[rule.name] = shaderAttrs[rule.name] || [];
|
||||
shaderAttrs[rule.name].push(exportedRule);
|
||||
});
|
||||
|
||||
return shaderAttrs;
|
||||
};
|
||||
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,42 +1,98 @@
|
||||
(function(tree) {
|
||||
|
||||
var _ = global._ || require('underscore');
|
||||
//
|
||||
// A number with a unit
|
||||
//
|
||||
tree.Dimension = function Dimension(value, unit, index) {
|
||||
this.value = parseFloat(value);
|
||||
this.unit = unit || null;
|
||||
this.is = 'float';
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Dimension.prototype = {
|
||||
eval: function (env) {
|
||||
if (this.unit && ['px', '%'].indexOf(this.unit) === -1) {
|
||||
env.error({
|
||||
is: 'float',
|
||||
physical_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc'],
|
||||
screen_units: ['px', '%'],
|
||||
all_units: ['m', 'cm', 'in', 'mm', 'pt', 'pc', 'px', '%'],
|
||||
densities: {
|
||||
m: 0.0254,
|
||||
mm: 25.4,
|
||||
cm: 2.54,
|
||||
pt: 72,
|
||||
pc: 6
|
||||
},
|
||||
ev: function (env) {
|
||||
if (this.unit && !_.contains(this.all_units, this.unit)) {
|
||||
env.error({
|
||||
message: "Invalid unit: '" + this.unit + "'",
|
||||
index: this.index
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
|
||||
// normalize units which are not px or %
|
||||
if (this.unit && _.contains(this.physical_units, this.unit)) {
|
||||
if (!env.ppi) {
|
||||
env.error({
|
||||
message: "ppi is not set, so metric units can't be used",
|
||||
index: this.index
|
||||
});
|
||||
return { is: 'undefined', value: 'undefined' };
|
||||
}
|
||||
// convert all units to inch
|
||||
// convert inch to px using ppi
|
||||
this.value = (this.value / this.densities[this.unit]) * env.ppi;
|
||||
this.unit = 'px';
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
round: function() {
|
||||
this.value = Math.round(this.value);
|
||||
return this;
|
||||
},
|
||||
toColor: function() {
|
||||
return new tree.Color([this.value, this.value, this.value]);
|
||||
},
|
||||
toString: function() {
|
||||
return this.value;
|
||||
round: function() {
|
||||
this.value = Math.round(this.value);
|
||||
return this;
|
||||
},
|
||||
toString: function() {
|
||||
return this.value.toString();
|
||||
},
|
||||
operate: function(env, op, other) {
|
||||
if (this.unit === '%' && other.unit !== '%') {
|
||||
env.error({
|
||||
message: 'If two operands differ, the first must not be %',
|
||||
index: this.index
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
// In an operation between two Dimensions,
|
||||
// we default to the first Dimension's unit,
|
||||
// so `1px + 2em` will yield `3px`.
|
||||
// In the future, we could implement some unit
|
||||
// conversions such that `100cm + 10mm` would yield
|
||||
// `101cm`.
|
||||
operate: function(op, other) {
|
||||
if (this.unit !== '%' && other.unit === '%') {
|
||||
if (op === '*' || op === '/' || op === '%') {
|
||||
env.error({
|
||||
message: 'Percent values can only be added or subtracted from other values',
|
||||
index: this.index
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
return new tree.Dimension(tree.operate(op,
|
||||
this.value, this.value * other.value * 0.01),
|
||||
this.unit);
|
||||
}
|
||||
|
||||
//here the operands are either the same (% or undefined or px), or one is undefined and the other is px
|
||||
return new tree.Dimension(tree.operate(op, this.value, other.value),
|
||||
this.unit || other.unit);
|
||||
this.unit || other.unit);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Directive = function Directive(name, value) {
|
||||
this.name = name;
|
||||
if (Array.isArray(value)) {
|
||||
this.ruleset = new tree.Ruleset([], value);
|
||||
} else {
|
||||
this.value = value;
|
||||
}
|
||||
};
|
||||
tree.Directive.prototype = {
|
||||
toString: function(ctx, env) {
|
||||
if (this.ruleset) {
|
||||
this.ruleset.root = true;
|
||||
return this.name + ' {\n ' +
|
||||
this.ruleset.toString(ctx, env).trim().replace(/\n/g, '\n ') +
|
||||
'\n}\n';
|
||||
} else {
|
||||
return this.name + ' ' + this.value.toString() + ';\n';
|
||||
}
|
||||
},
|
||||
eval: function(env) {
|
||||
env.frames.unshift(this);
|
||||
this.ruleset = this.ruleset && this.ruleset.eval(env);
|
||||
env.frames.shift();
|
||||
return this;
|
||||
},
|
||||
variable: function(name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
|
||||
find: function() { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
|
||||
rulesets: function() { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -3,31 +3,28 @@
|
||||
// An element is an id or class selector
|
||||
tree.Element = function Element(value) {
|
||||
this.value = value.trim();
|
||||
if (this.value[0] === '#') {
|
||||
this.type = 'id';
|
||||
this.clean = this.value.replace(/^#/, '');
|
||||
}
|
||||
if (this.value[0] === '.') {
|
||||
this.type = 'class';
|
||||
this.clean = this.value.replace(/^\./, '');
|
||||
}
|
||||
if (this.value.indexOf('*') !== -1) {
|
||||
this.type = 'wildcard';
|
||||
}
|
||||
};
|
||||
|
||||
// Determine the 'specificity matrix' of this
|
||||
// specific selector
|
||||
tree.Element.prototype.specificity = function() {
|
||||
return [
|
||||
(this.value[0] == '#') ? 1 : 0, // a
|
||||
(this.value[0] == '.') ? 1 : 0 // b
|
||||
(this.type === 'id') ? 1 : 0, // a
|
||||
(this.type === 'class') ? 1 : 0 // b
|
||||
];
|
||||
};
|
||||
|
||||
tree.Element.prototype.toString = function() {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
// Determine whether this element matches an id or classes.
|
||||
// An element is a single id or class, or check whether the given
|
||||
// array of classes contains this, or the id is equal to this.
|
||||
//
|
||||
// Takes a plain string for id and plain strings in the array of
|
||||
// classes.
|
||||
tree.Element.prototype.matches = function(id, classes) {
|
||||
return (classes.indexOf(this.value.replace(/^\./, '')) !== -1) ||
|
||||
(this.value.replace(/^#/, '') === id) ||
|
||||
(this.value === '*');
|
||||
};
|
||||
tree.Element.prototype.toString = function() { return this.value; };
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,16 +1,21 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Expression = function Expression(value) { this.value = value };
|
||||
tree.Expression = function Expression(value) {
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
tree.Expression.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'expression',
|
||||
ev: function(env) {
|
||||
if (this.value.length > 1) {
|
||||
return new tree.Expression(this.value.map(function(e) {
|
||||
return e.eval(env);
|
||||
return e.ev(env);
|
||||
}));
|
||||
} else {
|
||||
return this.value[0].eval(env);
|
||||
return this.value[0].ev(env);
|
||||
}
|
||||
},
|
||||
|
||||
toString: function(env) {
|
||||
return this.value.map(function(e) {
|
||||
return e.toString(env);
|
||||
|
17
lib/carto/tree/field.js
Normal file
17
lib/carto/tree/field.js
Normal file
@ -0,0 +1,17 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Field = function Field(content) {
|
||||
this.value = content || '';
|
||||
};
|
||||
|
||||
tree.Field.prototype = {
|
||||
is: 'field',
|
||||
toString: function() {
|
||||
return '[' + this.value + ']';
|
||||
},
|
||||
'ev': function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -1,56 +1,64 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Filter = function Filter(key, op, val, index, filename) {
|
||||
if (key.is) {
|
||||
this.key = key.value;
|
||||
this._key = key;
|
||||
} else {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
this.key = key;
|
||||
this.op = op;
|
||||
this.val = val;
|
||||
this.index = index;
|
||||
this.filename = filename;
|
||||
|
||||
if (val.is) {
|
||||
this.val = val.value;
|
||||
this._val = val;
|
||||
} else {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
if (this.op !== '=' && this.op !== '!=') {
|
||||
this.val = 1 * this.val;
|
||||
}
|
||||
|
||||
this.id = this.key + this.op + this.val;
|
||||
};
|
||||
|
||||
// xmlsafe, numeric, suffix
|
||||
var ops = {
|
||||
'<': [' < ', 'numeric'],
|
||||
'>': [' > ', 'numeric'],
|
||||
'=': [' = ', 'both'],
|
||||
'!=': [' != ', 'both'],
|
||||
'<=': [' <= ', 'numeric'],
|
||||
'>=': [' >= ', 'numeric'],
|
||||
'=~': ['.match(', 'string', ')']
|
||||
};
|
||||
|
||||
// XML-safe versions of comparators
|
||||
var opXML = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'=': '=',
|
||||
'!=': '!=',
|
||||
'<=': '<=',
|
||||
'>=': '>='
|
||||
tree.Filter.prototype.ev = function(env) {
|
||||
this.key = this.key.ev(env);
|
||||
this.val = this.val.ev(env);
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toXML = function(env) {
|
||||
if (this.op !== '=' && this.op !== '!=' && isNaN(this.val)) {
|
||||
if (tree.Reference.data.filter) {
|
||||
if (this.key.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.key.toString())) {
|
||||
env.error({
|
||||
message: this.key.toString() + ' is not a valid keyword in a filter expression',
|
||||
index: this.index,
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
if (this.val.is === 'keyword' && -1 === tree.Reference.data.filter.value.indexOf(this.val.toString())) {
|
||||
env.error({
|
||||
message: this.val.toString() + ' is not a valid keyword in a filter expression',
|
||||
index: this.index,
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
}
|
||||
var key = this.key.toString(false);
|
||||
var val = this.val.toString(this.val.is == 'string');
|
||||
|
||||
if (
|
||||
(ops[this.op][1] == 'numeric' && isNaN(val) && this.val.is !== 'field') ||
|
||||
(ops[this.op][1] == 'string' && (val)[0] != "'")
|
||||
) {
|
||||
env.error({
|
||||
message: 'Cannot use operator "' + this.op + '" with value ' + this.val,
|
||||
index: this.index,
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
if (this.val.eval) this._val = this.val.eval(env);
|
||||
if (this.key.eval) this._key = this.key.eval(env);
|
||||
if (this._key) var key = this._key.toString(false);
|
||||
if (this._val) var val = this._val.toString(this._val.is == 'string');
|
||||
|
||||
return '[' + (key || this.key) + '] ' + opXML[this.op] + ' ' + (val || this.val);
|
||||
return key + ops[this.op][0] + val + (ops[this.op][2] || '');
|
||||
};
|
||||
|
||||
tree.Filter.prototype.toString = function() {
|
||||
|
@ -1,207 +1,270 @@
|
||||
var tree = require('../tree');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
tree.Filterset = function Filterset() {};
|
||||
tree.Filterset = function Filterset() {
|
||||
this.filters = {};
|
||||
};
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'toXML', {
|
||||
enumerable: false,
|
||||
value: function(env) {
|
||||
var filters = [];
|
||||
for (var id in this) {
|
||||
filters.push('(' + this[id].toXML(env).trim() + ')');
|
||||
}
|
||||
|
||||
if (filters.length) {
|
||||
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
tree.Filterset.prototype.toXML = function(env) {
|
||||
var filters = [];
|
||||
for (var id in this.filters) {
|
||||
filters.push('(' + this.filters[id].toXML(env).trim() + ')');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'toString', {
|
||||
enumerable: false,
|
||||
value: function() {
|
||||
var arr = [];
|
||||
for (var id in this) arr.push(this[id].id);
|
||||
arr.sort();
|
||||
return arr.join('\t');
|
||||
if (filters.length) {
|
||||
return ' <Filter>' + filters.join(' and ') + '</Filter>\n';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(tree.Filterset.prototype, 'clone', {
|
||||
enumerable: false,
|
||||
value: function() {
|
||||
var clone = new tree.Filterset();
|
||||
for (var id in this) {
|
||||
clone[id] = this[id];
|
||||
}
|
||||
return clone;
|
||||
tree.Filterset.prototype.toString = function() {
|
||||
var arr = [];
|
||||
for (var id in this.filters) arr.push(this.filters[id].id);
|
||||
return arr.sort().join('\t');
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.ev = function(env) {
|
||||
for (var i in this.filters) {
|
||||
this.filters[i].ev(env);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.clone = function() {
|
||||
var clone = new tree.Filterset();
|
||||
for (var id in this.filters) {
|
||||
clone.filters[id] = this.filters[id];
|
||||
}
|
||||
return clone;
|
||||
};
|
||||
|
||||
// Note: other has to be a tree.Filterset.
|
||||
Object.defineProperty(tree.Filterset.prototype, 'cloneWith', {
|
||||
enumerable: false,
|
||||
value: function(other) {
|
||||
var additions;
|
||||
for (var id in other) {
|
||||
var status = this.addable(other[id]);
|
||||
if (status === false) {
|
||||
return false;
|
||||
tree.Filterset.prototype.cloneWith = function(other) {
|
||||
var additions = [];
|
||||
for (var id in other.filters) {
|
||||
var status = this.addable(other.filters[id]);
|
||||
// status is true, false or null. if it's null we don't fail this
|
||||
// clone nor do we add the filter.
|
||||
if (status === false) {
|
||||
return false;
|
||||
}
|
||||
if (status === true) {
|
||||
// Adding the filter will override another value.
|
||||
additions.push(other.filters[id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the other filters doesn't make this filterset invalid, but it
|
||||
// doesn't add anything to it either.
|
||||
if (!additions.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can successfully add all filters. Now clone the filterset and add the
|
||||
// new rules.
|
||||
var clone = new tree.Filterset();
|
||||
|
||||
// We can add the rules that are already present without going through the
|
||||
// add function as a Filterset is always in it's simplest canonical form.
|
||||
for (id in this.filters) {
|
||||
clone.filters[id] = this.filters[id];
|
||||
}
|
||||
|
||||
// Only add new filters that actually change the filter.
|
||||
while (id = additions.shift()) {
|
||||
clone.add(id);
|
||||
}
|
||||
|
||||
return clone;
|
||||
};
|
||||
|
||||
tree.Filterset.prototype.toJS = function(env) {
|
||||
var opMap = {
|
||||
'=': '==='
|
||||
};
|
||||
return _.map(this.filters, function(filter) {
|
||||
var op = filter.op;
|
||||
if(op in opMap) {
|
||||
op = opMap[op];
|
||||
}
|
||||
var val = filter.val;
|
||||
if(filter._val !== undefined) {
|
||||
val = filter._val.toString(true);
|
||||
}
|
||||
var attrs = "data";
|
||||
if (op === '=~') {
|
||||
return "(" + attrs + "['" + filter.key.value + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val) + ")";
|
||||
}
|
||||
return attrs + "['" + filter.key.value + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val);
|
||||
}).join(' && ');
|
||||
};
|
||||
|
||||
// Returns true when the new filter can be added, false otherwise.
|
||||
// It can also return null, and on the other side we test for === true or
|
||||
// false
|
||||
tree.Filterset.prototype.addable = function(filter) {
|
||||
var key = filter.key.toString(),
|
||||
value = filter.val.toString();
|
||||
|
||||
if (value.match(/^[0-9]+(\.[0-9]*)?$/)) value = parseFloat(value);
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
// if there is already foo= and we're adding foo=
|
||||
if (this.filters[key + '='] !== undefined) {
|
||||
if (this.filters[key + '='].val.toString() != value) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (status === true) {
|
||||
// Adding the filter will override another value.
|
||||
if (!additions) additions = [];
|
||||
additions.push(other[id]);
|
||||
if (this.filters[key + '!=' + value] !== undefined) return false;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
|
||||
return true;
|
||||
|
||||
case '=~':
|
||||
return true;
|
||||
|
||||
case '!=':
|
||||
if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null;
|
||||
if (this.filters[key + '!=' + value] !== undefined) return null;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '>':
|
||||
if (key + '=' in this.filters) {
|
||||
if (this.filters[key + '='].val <= value) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return false;
|
||||
if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
|
||||
return true;
|
||||
|
||||
case '>=':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : null;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return null;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return null;
|
||||
return true;
|
||||
|
||||
case '<':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : null;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '<=':
|
||||
if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : null;
|
||||
if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
|
||||
if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
|
||||
if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
|
||||
if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return null;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Does the new filter constitute a conflict?
|
||||
tree.Filterset.prototype.conflict = function(filter) {
|
||||
var key = filter.key.toString(),
|
||||
value = filter.val.toString();
|
||||
|
||||
if (!isNaN(parseFloat(value))) value = parseFloat(value);
|
||||
|
||||
// if (a=b) && (a=c)
|
||||
// if (a=b) && (a!=b)
|
||||
// or (a!=b) && (a=b)
|
||||
if ((filter.op === '=' && this.filters[key + '='] !== undefined &&
|
||||
value != this.filters[key + '='].val.toString()) ||
|
||||
(filter.op === '!=' && this.filters[key + '='] !== undefined &&
|
||||
value == this.filters[key + '='].val.toString()) ||
|
||||
(filter.op === '=' && this.filters[key + '!='] !== undefined &&
|
||||
value == this.filters[key + '!='].val.toString())) {
|
||||
return filter.toString() + ' added to ' + this.toString() + ' produces an invalid filter';
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Only call this function for filters that have been cleared by .addable().
|
||||
tree.Filterset.prototype.add = function(filter, env) {
|
||||
var key = filter.key.toString(),
|
||||
id,
|
||||
op = filter.op,
|
||||
conflict = this.conflict(filter),
|
||||
numval;
|
||||
|
||||
if (conflict) return conflict;
|
||||
|
||||
if (op === '=') {
|
||||
for (var i in this.filters) {
|
||||
if (this.filters[i].key == key) delete this.filters[i];
|
||||
}
|
||||
this.filters[key + '='] = filter;
|
||||
} else if (op === '!=') {
|
||||
this.filters[key + '!=' + filter.val] = filter;
|
||||
} else if (op === '=~') {
|
||||
this.filters[key + '=~' + filter.val] = filter;
|
||||
} else if (op === '>') {
|
||||
// If there are other filters that are also >
|
||||
// but are less than this one, they don't matter, so
|
||||
// remove them.
|
||||
for (var j in this.filters) {
|
||||
if (this.filters[j].key == key && this.filters[j].val <= filter.val) {
|
||||
delete this.filters[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the other filters doesn't make this filterset invalid, but it
|
||||
// doesn't add anything to it either.
|
||||
if (!additions) return null;
|
||||
|
||||
// We can successfully add all filters. Now clone the filterset and add the
|
||||
// new rules.
|
||||
var clone = new tree.Filterset();
|
||||
|
||||
// We can add the rules that are already present without going through the
|
||||
// add function as a Filterset is always in it's simplest canonical form.
|
||||
for (var id in this)
|
||||
clone[id] = this[id];
|
||||
|
||||
// Only add new filters that actually change the filter.
|
||||
while (id = additions.shift())
|
||||
clone.add(id);
|
||||
|
||||
return clone;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns true when the new filter can be added, false otherwise.
|
||||
*/
|
||||
Object.defineProperty(tree.Filterset.prototype, 'addable', {
|
||||
enumerable: false,
|
||||
value: function(filter) {
|
||||
var key = filter.key, value = filter.val;
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
if (key + '=' in this) return (this[key + '='].val != value) ? false : null;
|
||||
if (key + '!=' + value in this) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return false;
|
||||
return true;
|
||||
|
||||
case '!=':
|
||||
if (key + '=' in this) return (this[key + '='].val == value) ? false : null;
|
||||
if (key + '!=' + value in this) return null;
|
||||
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return null;
|
||||
|
||||
return true;
|
||||
|
||||
case '>':
|
||||
if (key + '=' in this) return (this[key + '='].val <= value) ? false : null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val <= value) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return null;
|
||||
return true;
|
||||
|
||||
case '>=':
|
||||
if (key + '=' in this) return (this[key + '='].val < value) ? false : null;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return false;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return false;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return null;
|
||||
if (key + '>=' in this && this[key + '>='].val >= value) return null;
|
||||
return true;
|
||||
|
||||
case '<':
|
||||
if (key + '=' in this) return (this[key + '='].val >= value) ? false : null;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val >= value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val < value) return null;
|
||||
return true;
|
||||
|
||||
case '<=':
|
||||
if (key + '=' in this) return (this[key + '='].val > value) ? false : null;
|
||||
if (key + '>' in this && this[key + '>'].val >= value) return false;
|
||||
if (key + '>=' in this && this[key + '>='].val > value) return false;
|
||||
if (key + '<' in this && this[key + '<'].val <= value) return null;
|
||||
if (key + '<=' in this && this[key + '<='].val <= value) return null;
|
||||
return true;
|
||||
this.filters[key + '>'] = filter;
|
||||
} else if (op === '>=') {
|
||||
for (var k in this.filters) {
|
||||
numval = (+this.filters[k].val.toString());
|
||||
if (this.filters[k].key == key && numval < filter.val) {
|
||||
delete this.filters[k];
|
||||
}
|
||||
}
|
||||
if (this.filters[key + '!=' + filter.val] !== undefined) {
|
||||
delete this.filters[key + '!=' + filter.val];
|
||||
filter.op = '>';
|
||||
this.filters[key + '>'] = filter;
|
||||
}
|
||||
else {
|
||||
this.filters[key + '>='] = filter;
|
||||
}
|
||||
} else if (op === '<') {
|
||||
for (var l in this.filters) {
|
||||
numval = (+this.filters[l].val.toString());
|
||||
if (this.filters[l].key == key && numval >= filter.val) {
|
||||
delete this.filters[l];
|
||||
}
|
||||
}
|
||||
this.filters[key + '<'] = filter;
|
||||
} else if (op === '<=') {
|
||||
for (var m in this.filters) {
|
||||
numval = (+this.filters[m].val.toString());
|
||||
if (this.filters[m].key == key && numval > filter.val) {
|
||||
delete this.filters[m];
|
||||
}
|
||||
}
|
||||
if (this.filters[key + '!=' + filter.val] !== undefined) {
|
||||
delete this.filters[key + '!=' + filter.val];
|
||||
filter.op = '<';
|
||||
this.filters[key + '<'] = filter;
|
||||
}
|
||||
else {
|
||||
this.filters[key + '<='] = filter;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Only call this function for filters that have been cleared by .addable().
|
||||
*/
|
||||
Object.defineProperty(tree.Filterset.prototype, 'add', {
|
||||
enumerable: false,
|
||||
value: function(filter) {
|
||||
var key = filter.key;
|
||||
|
||||
switch (filter.op) {
|
||||
case '=':
|
||||
for (var id in this)
|
||||
if (this[id].key == key)
|
||||
delete this[id];
|
||||
this[key + '='] = filter;
|
||||
break;
|
||||
|
||||
case '!=':
|
||||
this[key + '!=' + filter.val] = filter;
|
||||
break;
|
||||
|
||||
case '>':
|
||||
for (var id in this)
|
||||
if (this[id].key == key && this[id].val <= filter.val)
|
||||
delete this[id];
|
||||
this[key + '>'] = filter;
|
||||
break;
|
||||
|
||||
case '>=':
|
||||
for (var id in this)
|
||||
if (this[id].key == key && this[id].val < filter.val)
|
||||
delete this[id];
|
||||
if (key + '!=' + filter.val in this) {
|
||||
delete this[key + '!=' + filter.val];
|
||||
filter.op = '>';
|
||||
this[key + '>'] = filter;
|
||||
}
|
||||
else {
|
||||
this[key + '>='] = filter;
|
||||
}
|
||||
break;
|
||||
|
||||
case '<':
|
||||
for (var id in this)
|
||||
if (this[id].key == key && this[id].val >= filter.val)
|
||||
delete this[id];
|
||||
this[key + '<'] = filter;
|
||||
break;
|
||||
|
||||
case '<=':
|
||||
for (var id in this)
|
||||
if (this[id].key == key && this[id].val > filter.val)
|
||||
delete this[id];
|
||||
if (key + '!=' + filter.val in this) {
|
||||
delete this[key + '!=' + filter.val];
|
||||
filter.op = '<';
|
||||
this[key + '<'] = filter;
|
||||
}
|
||||
else {
|
||||
this[key + '<='] = filter;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -1,23 +1,16 @@
|
||||
(function(tree) {
|
||||
|
||||
tree._getFontSet = function(env, fonts) {
|
||||
var find_existing = function(fonts) {
|
||||
var findFonts = fonts.join('');
|
||||
for (var i = 0; i < env.effects.length; i++) {
|
||||
if (findFonts == env.effects[i].fonts.join('')) {
|
||||
return env.effects[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var existing = false;
|
||||
if (existing = find_existing(fonts)) {
|
||||
return existing;
|
||||
} else {
|
||||
var new_fontset = new tree.FontSet(env, fonts);
|
||||
env.effects.push(new_fontset);
|
||||
return new_fontset;
|
||||
var fontKey = fonts.join('');
|
||||
if (env._fontMap && env._fontMap[fontKey]) {
|
||||
return env._fontMap[fontKey];
|
||||
}
|
||||
|
||||
var new_fontset = new tree.FontSet(env, fonts);
|
||||
env.effects.push(new_fontset);
|
||||
if (!env._fontMap) env._fontMap = {};
|
||||
env._fontMap[fontKey] = new_fontset;
|
||||
return new_fontset;
|
||||
};
|
||||
|
||||
tree.FontSet = function FontSet(env, fonts) {
|
||||
@ -26,13 +19,13 @@ tree.FontSet = function FontSet(env, fonts) {
|
||||
};
|
||||
|
||||
tree.FontSet.prototype.toXML = function(env) {
|
||||
return '<FontSet name="'
|
||||
+ this.name
|
||||
+ '">\n'
|
||||
+ this.fonts.map(function(f) {
|
||||
return '<FontSet name="' +
|
||||
this.name +
|
||||
'">\n' +
|
||||
this.fonts.map(function(f) {
|
||||
return ' <Font face-name="' + f +'"/>';
|
||||
}).join('\n')
|
||||
+ '\n</FontSet>'
|
||||
}).join('\n') +
|
||||
'\n</FontSet>';
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
27
lib/carto/tree/frame_offset.js
Normal file
27
lib/carto/tree/frame_offset.js
Normal file
@ -0,0 +1,27 @@
|
||||
var tree = require('../tree');
|
||||
|
||||
// Storage for Frame offset value
|
||||
// and stores them as bit-sequences so that they can be combined,
|
||||
// inverted, and compared quickly.
|
||||
tree.FrameOffset = function(op, value, index) {
|
||||
value = parseInt(value, 10);
|
||||
if (value > tree.FrameOffset.max || value <= 0) {
|
||||
throw {
|
||||
message: 'Only frame-offset levels between 1 and ' +
|
||||
tree.FrameOffset.max + ' supported.',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
|
||||
if (op !== '=') {
|
||||
throw {
|
||||
message: 'only = operator is supported for frame-offset',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
tree.FrameOffset.max = 32;
|
||||
tree.FrameOffset.none = 0;
|
||||
|
22
lib/carto/tree/imagefilter.js
Normal file
22
lib/carto/tree/imagefilter.js
Normal file
@ -0,0 +1,22 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.ImageFilter = function ImageFilter(filter, args) {
|
||||
this.filter = filter;
|
||||
this.args = args || null;
|
||||
};
|
||||
|
||||
tree.ImageFilter.prototype = {
|
||||
is: 'imagefilter',
|
||||
ev: function() { return this; },
|
||||
|
||||
toString: function() {
|
||||
if (this.args) {
|
||||
return this.filter + '(' + this.args.join(',') + ')';
|
||||
} else {
|
||||
return this.filter;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
})(require('../tree'));
|
@ -1,77 +0,0 @@
|
||||
(function(tree) {
|
||||
//
|
||||
// CSS @import node
|
||||
//
|
||||
// The general strategy here is that we don't want to wait
|
||||
// for the parsing to be completed, before we start importing
|
||||
// the file. That's because in the context of a browser,
|
||||
// most of the time will be spent waiting for the server to respond.
|
||||
//
|
||||
// On creation, we push the import path to our import queue, though
|
||||
// `import,push`, we also pass it a callback, which it'll call once
|
||||
// the file has been fetched, and parsed.
|
||||
//
|
||||
tree.Import = function Import(path, imports) {
|
||||
var that = this;
|
||||
|
||||
this._path = path;
|
||||
|
||||
// The '.mess' extension is optional
|
||||
if (path instanceof tree.Quoted) {
|
||||
this.path = /\.(le?|c)ss$/.test(path.value) ? path.value : path.value + '.mess';
|
||||
} else {
|
||||
this.path = path.value.value || path.value;
|
||||
}
|
||||
|
||||
this.css = /css$/.test(this.path);
|
||||
|
||||
// Only pre-compile .mess files
|
||||
if (! this.css) {
|
||||
imports.push(this.path, function(root) {
|
||||
if (! root) {
|
||||
throw new Error('Error parsing ' + that.path);
|
||||
}
|
||||
that.root = root;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// The actual import node doesn't return anything, when converted to CSS.
|
||||
// The reason is that it's used at the evaluation stage, so that the rules
|
||||
// it imports can be treated like any other rules.
|
||||
//
|
||||
// In `eval`, we make sure all Import nodes get evaluated, recursively, so
|
||||
// we end up with a flat structure, which can easily be imported in the parent
|
||||
// ruleset.
|
||||
//
|
||||
tree.Import.prototype = {
|
||||
toString: function() {
|
||||
if (this.css) {
|
||||
return '@import ' + this._path.toString() + ';\n';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
eval: function(env) {
|
||||
var ruleset;
|
||||
|
||||
if (this.css) {
|
||||
return this;
|
||||
} else {
|
||||
ruleset = new tree.Ruleset(null, this.root.rules.slice(0));
|
||||
|
||||
for (var i = 0; i < ruleset.rules.length; i++) {
|
||||
if (ruleset.rules[i] instanceof tree.Import) {
|
||||
Array.prototype
|
||||
.splice
|
||||
.apply(ruleset.rules,
|
||||
[i, 1].concat(ruleset.rules[i].eval(env)));
|
||||
}
|
||||
}
|
||||
return ruleset.rules;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -5,4 +5,18 @@ tree.Invalid = function Invalid(chunk, index, message) {
|
||||
this.type = 'syntax';
|
||||
this.message = message || "Invalid code: " + this.chunk;
|
||||
};
|
||||
|
||||
tree.Invalid.prototype.is = 'invalid';
|
||||
|
||||
tree.Invalid.prototype.ev = function(env) {
|
||||
env.error({
|
||||
chunk: this.chunk,
|
||||
index: this.index,
|
||||
type: 'syntax',
|
||||
message: this.message || "Invalid code: " + this.chunk
|
||||
});
|
||||
return {
|
||||
is: 'undefined'
|
||||
};
|
||||
};
|
||||
})(require('../tree'));
|
||||
|
@ -1,38 +0,0 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.JavaScript = function JavaScript(string, index) {
|
||||
this.expression = string;
|
||||
this.index = index;
|
||||
};
|
||||
tree.JavaScript.prototype = {
|
||||
toString: function() {
|
||||
return JSON.stringify(this.evaluated);
|
||||
},
|
||||
eval: function(env) {
|
||||
var result,
|
||||
expression = new Function('return (' + this.expression + ')'),
|
||||
context = {};
|
||||
|
||||
for (var k in env.frames[0].variables()) {
|
||||
context[k.slice(1)] = {
|
||||
value: env.frames[0].variables()[k].value,
|
||||
toJS: function() {
|
||||
return this.value.eval(env).toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
this.evaluated = expression.call(context);
|
||||
} catch (e) {
|
||||
throw {
|
||||
message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" ,
|
||||
index: this.index
|
||||
};
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -10,8 +10,8 @@ tree.Keyword = function Keyword(value) {
|
||||
this.is = special[value] ? special[value] : 'keyword';
|
||||
};
|
||||
tree.Keyword.prototype = {
|
||||
eval: function() { return this },
|
||||
toString: function() { return this.value }
|
||||
ev: function() { return this; },
|
||||
toString: function() { return this.value; }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,35 +1,36 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Layer = function Layer(obj) {
|
||||
this.name = obj.name;
|
||||
this.styles = obj.styles;
|
||||
this.properties = obj.properties || {};
|
||||
this.srs = obj.srs;
|
||||
this.datasource = obj.Datasource;
|
||||
};
|
||||
|
||||
tree.Layer.prototype.toXML = function() {
|
||||
tree.LayerXML = function(obj, styles) {
|
||||
var dsoptions = [];
|
||||
for (var i in this.datasource) {
|
||||
for (var i in obj.Datasource) {
|
||||
dsoptions.push('<Parameter name="' + i + '"><![CDATA[' +
|
||||
this.datasource[i] + ']]></Parameter>');
|
||||
obj.Datasource[i] + ']]></Parameter>');
|
||||
}
|
||||
|
||||
var prop_string = '';
|
||||
for (var i in this.properties) {
|
||||
prop_string += ' ' + i + '="' + this.properties[i] + '"\n';
|
||||
for (var prop in obj.properties) {
|
||||
if (prop === 'minzoom') {
|
||||
prop_string += ' maxzoom="' + tree.Zoom.ranges[obj.properties[prop]] + '"\n';
|
||||
} else if (prop === 'maxzoom') {
|
||||
prop_string += ' minzoom="' + tree.Zoom.ranges[obj.properties[prop]+1] + '"\n';
|
||||
} else {
|
||||
prop_string += ' ' + prop + '="' + obj.properties[prop] + '"\n';
|
||||
}
|
||||
}
|
||||
|
||||
return '<Layer' +
|
||||
' name="' + this.name + '"\n' +
|
||||
' name="' + obj.name + '"\n' +
|
||||
prop_string +
|
||||
' srs="' + this.srs + '">\n ' +
|
||||
this.styles.reverse().map(function(s) {
|
||||
((typeof obj.status === 'undefined') ? '' : ' status="' + obj.status + '"\n') +
|
||||
((typeof obj.srs === 'undefined') ? '' : ' srs="' + obj.srs + '"') + '>\n ' +
|
||||
styles.reverse().map(function(s) {
|
||||
return '<StyleName>' + s + '</StyleName>';
|
||||
}).join('\n ') +
|
||||
(dsoptions.length ?
|
||||
'\n <Datasource>\n ' +
|
||||
dsoptions.join('\n ') +
|
||||
'\n </Datasource>\n' +
|
||||
'\n </Datasource>\n'
|
||||
: '') +
|
||||
' </Layer>\n';
|
||||
};
|
||||
|
||||
|
20
lib/carto/tree/literal.js
Normal file
20
lib/carto/tree/literal.js
Normal file
@ -0,0 +1,20 @@
|
||||
// A literal is a literal string for Mapnik - the
|
||||
// result of the combination of a `tree.Field` with any
|
||||
// other type.
|
||||
(function(tree) {
|
||||
|
||||
tree.Literal = function Field(content) {
|
||||
this.value = content || '';
|
||||
this.is = 'field';
|
||||
};
|
||||
|
||||
tree.Literal.prototype = {
|
||||
toString: function() {
|
||||
return this.value;
|
||||
},
|
||||
'ev': function() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -1,99 +0,0 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.mixin = {};
|
||||
tree.mixin.Call = function Call(elements, args, index) {
|
||||
this.selector = new tree.Selector(null, null, elements);
|
||||
this.arguments = args;
|
||||
this.index = index;
|
||||
};
|
||||
tree.mixin.Call.prototype = {
|
||||
eval: function(env) {
|
||||
var mixins, rules = [], match = false;
|
||||
|
||||
for (var i = 0; i < env.frames.length; i++) {
|
||||
if ((mixins = env.frames[i].find(this.selector)).length > 0) {
|
||||
for (var m = 0; m < mixins.length; m++) {
|
||||
if (mixins[m].match(this.arguments, env)) {
|
||||
try {
|
||||
Array.prototype.push.apply(
|
||||
rules, mixins[m].eval(env, this.arguments).rules);
|
||||
match = true;
|
||||
} catch (e) {
|
||||
throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return rules;
|
||||
} else {
|
||||
throw { message: 'No matching definition was found for `' +
|
||||
this.selector.toString().trim() + '(' +
|
||||
this.arguments.map(function(a) {
|
||||
return a.toString();
|
||||
}).join(', ') + ')`',
|
||||
index: this.index };
|
||||
}
|
||||
}
|
||||
}
|
||||
throw { message: this.selector.toString().trim() + ' is undefined',
|
||||
index: this.index };
|
||||
}
|
||||
};
|
||||
|
||||
tree.mixin.Definition = function Definition(name, params, rules) {
|
||||
this.name = name;
|
||||
this.selectors = [new tree.Selector(null, null, [new tree.Element(null, name)])];
|
||||
this.params = params;
|
||||
this.arity = params.length;
|
||||
this.rules = rules;
|
||||
this._lookups = {};
|
||||
this.required = params.reduce(function(count, p) {
|
||||
if (p.name && !p.value) { return count + 1 }
|
||||
else { return count }
|
||||
}, 0);
|
||||
this.parent = tree.Ruleset.prototype;
|
||||
this.frames = [];
|
||||
};
|
||||
tree.mixin.Definition.prototype = {
|
||||
toString: function() { return '' },
|
||||
variable: function(name) { return this.parent.variable.call(this, name) },
|
||||
variables: function() { return this.parent.variables.call(this) },
|
||||
find: function() { return this.parent.find.apply(this, arguments) },
|
||||
rulesets: function() { return this.parent.rulesets.apply(this) },
|
||||
|
||||
eval: function(env, args) {
|
||||
var frame = new tree.Ruleset(null, []), context;
|
||||
|
||||
for (var i = 0, val; i < this.params.length; i++) {
|
||||
if (this.params[i].name) {
|
||||
if (val = (args && args[i]) || this.params[i].value) {
|
||||
frame.rules.unshift(new tree.Rule(this.params[i].name, val.eval(env)));
|
||||
} else {
|
||||
throw { message: 'wrong number of arguments for ' + this.name +
|
||||
' (' + args.length + ' for ' + this.arity + ')' };
|
||||
}
|
||||
}
|
||||
}
|
||||
return new tree.Ruleset(null, this.rules.slice(0)).eval({
|
||||
frames: [this, frame].concat(this.frames, env.frames)
|
||||
});
|
||||
},
|
||||
match: function(args, env) {
|
||||
var argsLength = (args && args.length) || 0, len;
|
||||
|
||||
if (argsLength < this.required) { return false }
|
||||
|
||||
len = Math.min(argsLength, this.arity);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (!this.params[i].name) {
|
||||
if (args[i].eval(env).toString() != this.params[i].value.eval(env).toString()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
@ -1,14 +1,18 @@
|
||||
// An operation is an expression with an op in between two operands,
|
||||
// like 2 + 1.
|
||||
(function(tree) {
|
||||
|
||||
|
||||
tree.Operation = function Operation(op, operands, index) {
|
||||
this.op = op.trim();
|
||||
this.operands = operands;
|
||||
this.index = index;
|
||||
};
|
||||
tree.Operation.prototype.eval = function(env) {
|
||||
var a = this.operands[0].eval(env),
|
||||
b = this.operands[1].eval(env),
|
||||
|
||||
tree.Operation.prototype.is = 'operation';
|
||||
|
||||
tree.Operation.prototype.ev = function(env) {
|
||||
var a = this.operands[0].ev(env),
|
||||
b = this.operands[1].ev(env),
|
||||
temp;
|
||||
|
||||
if (a.is === 'undefined' || b.is === 'undefined') {
|
||||
@ -30,12 +34,14 @@ tree.Operation.prototype.eval = function(env) {
|
||||
}
|
||||
}
|
||||
|
||||
if (a instanceof tree.Quoted || b instanceof tree.Quoted) {
|
||||
// Only concatenate plain strings, because this is easily
|
||||
// pre-processed
|
||||
if (a instanceof tree.Quoted && b instanceof tree.Quoted && this.op !== '+') {
|
||||
env.error({
|
||||
message: 'One cannot add, subtract, divide, or multiply strings.',
|
||||
message: "Can't subtract, divide, or multiply strings.",
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename,
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
@ -43,7 +49,39 @@ tree.Operation.prototype.eval = function(env) {
|
||||
};
|
||||
}
|
||||
|
||||
return a.operate(this.op, b);
|
||||
// Fields, literals, dimensions, and quoted strings can be combined.
|
||||
if (a instanceof tree.Field || b instanceof tree.Field ||
|
||||
a instanceof tree.Literal || b instanceof tree.Literal) {
|
||||
if (a.is === 'color' || b.is === 'color') {
|
||||
env.error({
|
||||
message: "Can't subtract, divide, or multiply colors in expressions.",
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
} else {
|
||||
return new tree.Literal(a.ev(env).toString(true) + this.op + b.ev(env).toString(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (a.operate === undefined) {
|
||||
env.error({
|
||||
message: 'Cannot do math with type ' + a.is + '.',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
value: 'undefined'
|
||||
};
|
||||
}
|
||||
|
||||
return a.operate(env, this.op, b);
|
||||
};
|
||||
|
||||
tree.operate = function(op, a, b) {
|
||||
@ -51,6 +89,7 @@ tree.operate = function(op, a, b) {
|
||||
case '+': return a + b;
|
||||
case '-': return a - b;
|
||||
case '*': return a * b;
|
||||
case '%': return a % b;
|
||||
case '/': return a / b;
|
||||
}
|
||||
};
|
||||
|
@ -1,16 +1,29 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.Quoted = function Quoted(str, content) {
|
||||
tree.Quoted = function Quoted(content) {
|
||||
this.value = content || '';
|
||||
this.quote = str.charAt(0);
|
||||
this.is = 'string';
|
||||
};
|
||||
|
||||
tree.Quoted.prototype = {
|
||||
is: 'string',
|
||||
|
||||
toString: function(quotes) {
|
||||
return (quotes === true) ? "'" + this.value + "'" : this.value;
|
||||
var escapedValue = this.value
|
||||
.replace(/&/g, '&')
|
||||
var xmlvalue = escapedValue
|
||||
.replace(/\'/g, '\\\'')
|
||||
.replace(/\"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/\>/g, '>');
|
||||
return (quotes === true) ? "'" + xmlvalue + "'" : escapedValue;
|
||||
},
|
||||
eval: function() {
|
||||
|
||||
'ev': function() {
|
||||
return this;
|
||||
},
|
||||
|
||||
operate: function(env, op, other) {
|
||||
return new tree.Quoted(tree.operate(op, this.toString(), other.toString(this.contains_field)));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,85 +1,98 @@
|
||||
// Carto pulls in a reference from the `mapnik-reference`
|
||||
// module. This file builds indexes from that file for its various
|
||||
// options, and provides validation methods for property: value
|
||||
// combinations.
|
||||
(function(tree) {
|
||||
var fs = require('fs');
|
||||
|
||||
tree.Reference = {
|
||||
data: JSON.parse(fs.readFileSync(__dirname + '/reference.json'))
|
||||
var _ = global._ || require('underscore'),
|
||||
ref = {};
|
||||
|
||||
ref.setData = function(data) {
|
||||
ref.data = data;
|
||||
ref.selector_cache = generateSelectorCache(data);
|
||||
ref.mapnikFunctions = generateMapnikFunctions(data);
|
||||
|
||||
ref.mapnikFunctions.matrix = [6];
|
||||
ref.mapnikFunctions.translate = [1, 2];
|
||||
ref.mapnikFunctions.scale = [1, 2];
|
||||
ref.mapnikFunctions.rotate = [1, 3];
|
||||
ref.mapnikFunctions.skewX = [1];
|
||||
ref.mapnikFunctions.skewY = [1];
|
||||
|
||||
ref.required_cache = generateRequiredProperties(data);
|
||||
};
|
||||
|
||||
tree.Reference.required_prop_list_cache = {};
|
||||
ref.setVersion = function(version) {
|
||||
var mapnik_reference = require('mapnik-reference');
|
||||
if (mapnik_reference.version.hasOwnProperty(version)) {
|
||||
ref.setData(mapnik_reference.version[version]);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.selectors = tree.Reference.selectors || (function() {
|
||||
var list = [];
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (tree.Reference.data.symbolizers[i][j].hasOwnProperty('css')) {
|
||||
list.push(tree.Reference.data.symbolizers[i][j].css);
|
||||
ref.selectorData = function(selector, i) {
|
||||
if (ref.selector_cache[selector]) return ref.selector_cache[selector][i];
|
||||
};
|
||||
|
||||
ref.validSelector = function(selector) { return !!ref.selector_cache[selector]; };
|
||||
ref.selectorName = function(selector) { return ref.selectorData(selector, 2); };
|
||||
ref.selector = function(selector) { return ref.selectorData(selector, 0); };
|
||||
ref.symbolizer = function(selector) { return ref.selectorData(selector, 1); };
|
||||
|
||||
function generateSelectorCache(data) {
|
||||
var index = {};
|
||||
for (var i in data.symbolizers) {
|
||||
for (var j in data.symbolizers[i]) {
|
||||
if (data.symbolizers[i][j].hasOwnProperty('css')) {
|
||||
index[data.symbolizers[i][j].css] = [data.symbolizers[i][j], i, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
})();
|
||||
return index;
|
||||
}
|
||||
|
||||
tree.Reference.validSelector = function(selector) {
|
||||
return tree.Reference.selectors.indexOf(selector) !== -1;
|
||||
};
|
||||
|
||||
tree.Reference.selectorName = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return j;
|
||||
function generateMapnikFunctions(data) {
|
||||
var functions = {};
|
||||
for (var i in data.symbolizers) {
|
||||
for (var j in data.symbolizers[i]) {
|
||||
if (data.symbolizers[i][j].type === 'functions') {
|
||||
for (var k = 0; k < data.symbolizers[i][j].functions.length; k++) {
|
||||
var fn = data.symbolizers[i][j].functions[k];
|
||||
functions[fn[0]] = fn[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return functions;
|
||||
}
|
||||
|
||||
tree.Reference.selector = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return tree.Reference.data.symbolizers[i][j];
|
||||
function generateRequiredProperties(data) {
|
||||
var cache = {};
|
||||
for (var symbolizer_name in data.symbolizers) {
|
||||
cache[symbolizer_name] = [];
|
||||
for (var j in data.symbolizers[symbolizer_name]) {
|
||||
if (data.symbolizers[symbolizer_name][j].required) {
|
||||
cache[symbolizer_name].push(data.symbolizers[symbolizer_name][j].css);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
tree.Reference.symbolizer = function(selector) {
|
||||
for (var i in tree.Reference.data.symbolizers) {
|
||||
for (var j in tree.Reference.data.symbolizers[i]) {
|
||||
if (selector == tree.Reference.data.symbolizers[i][j].css) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.requiredPropertyList = function(symbolizer_name) {
|
||||
if (this.required_prop_list_cache[symbolizer_name]) {
|
||||
return this.required_prop_list_cache[symbolizer_name];
|
||||
}
|
||||
var properties = [];
|
||||
for (var j in tree.Reference.data.symbolizers[symbolizer_name]) {
|
||||
if (tree.Reference.data.symbolizers[symbolizer_name][j].required) {
|
||||
properties.push(tree.Reference.data.symbolizers[symbolizer_name][j].css);
|
||||
}
|
||||
}
|
||||
return this.required_prop_list_cache[symbolizer_name] = properties;
|
||||
};
|
||||
|
||||
tree.Reference.requiredProperties = function(symbolizer_name, rules) {
|
||||
var req = tree.Reference.requiredPropertyList(symbolizer_name);
|
||||
for (i in req) {
|
||||
ref.requiredProperties = function(symbolizer_name, rules) {
|
||||
var req = ref.required_cache[symbolizer_name];
|
||||
for (var i in req) {
|
||||
if (!(req[i] in rules)) {
|
||||
return 'Property ' + req[i] + ' required for defining '
|
||||
+ symbolizer_name + ' styles.';
|
||||
return 'Property ' + req[i] + ' required for defining ' +
|
||||
symbolizer_name + ' styles.';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: finish implementation - this is dead code
|
||||
*/
|
||||
tree.Reference._validateValue = {
|
||||
// TODO: finish implementation - this is dead code
|
||||
ref._validateValue = {
|
||||
'font': function(env, value) {
|
||||
if (env.validation_data && env.validation_data.fonts) {
|
||||
return env.validation_data.fonts.indexOf(value) != -1;
|
||||
@ -89,48 +102,118 @@ tree.Reference._validateValue = {
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference.isFont = function(selector) {
|
||||
return tree.Reference.selector(selector).validate == 'font';
|
||||
}
|
||||
ref.isFont = function(selector) {
|
||||
return ref.selector(selector).validate == 'font';
|
||||
};
|
||||
|
||||
tree.Reference.validValue = function(env, selector, value) {
|
||||
if (value[0]) {
|
||||
return tree.Reference.selector(selector).type == value[0].is;
|
||||
} else {
|
||||
// TODO: handle in reusable way
|
||||
if (value.value[0].is == 'keyword') {
|
||||
return tree.Reference
|
||||
.selector(selector).type
|
||||
.indexOf(value.value[0].value) !== -1;
|
||||
} else if (value.value[0].is == 'undefined') {
|
||||
// caught earlier in the chain - ignore here so that
|
||||
// error is not overridden
|
||||
return true;
|
||||
} else if (tree.Reference.selector(selector).type == 'numbers') {
|
||||
for (i in value.value) {
|
||||
if (value.value[i].is !== 'float') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (tree.Reference.selector(selector).validate) {
|
||||
var valid = false;
|
||||
for (var i = 0; i < value.value.length; i++) {
|
||||
if (tree.Reference.selector(selector).type == value.value[i].is &&
|
||||
tree.Reference
|
||||
._validateValue
|
||||
[tree.Reference.selector(selector).validate]
|
||||
(env, value.value[i].value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
// https://gist.github.com/982927
|
||||
ref.editDistance = function(a, b){
|
||||
if (a.length === 0) return b.length;
|
||||
if (b.length === 0) return a.length;
|
||||
var matrix = [];
|
||||
for (var i = 0; i <= b.length; i++) { matrix[i] = [i]; }
|
||||
for (var j = 0; j <= a.length; j++) { matrix[0][j] = j; }
|
||||
for (i = 1; i <= b.length; i++) {
|
||||
for (j = 1; j <= a.length; j++) {
|
||||
if (b.charAt(i-1) == a.charAt(j-1)) {
|
||||
matrix[i][j] = matrix[i-1][j-1];
|
||||
} else {
|
||||
return tree.Reference.selector(selector).type == value.value[0].is;
|
||||
matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
|
||||
Math.min(matrix[i][j-1] + 1, // insertion
|
||||
matrix[i-1][j] + 1)); // deletion
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix[b.length][a.length];
|
||||
};
|
||||
|
||||
function validateFunctions(value, selector) {
|
||||
if (value.value[0].is === 'string') return true;
|
||||
for (var i in value.value) {
|
||||
for (var j in value.value[i].value) {
|
||||
if (value.value[i].value[j].is !== 'call') return false;
|
||||
var f = _.find(ref
|
||||
.selector(selector).functions, function(x) {
|
||||
return x[0] == value.value[i].value[j].name;
|
||||
});
|
||||
if (!(f && f[1] == -1)) {
|
||||
// This filter is unknown or given an incorrect number of arguments
|
||||
if (!f || f[1] !== value.value[i].value[j].args.length) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateKeyword(value, selector) {
|
||||
if (typeof ref.selector(selector).type === 'object') {
|
||||
return ref.selector(selector).type
|
||||
.indexOf(value.value[0].value) !== -1;
|
||||
} else {
|
||||
// allow unquoted keywords as strings
|
||||
return ref.selector(selector).type === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
ref.validValue = function(env, selector, value) {
|
||||
var i, j;
|
||||
// TODO: handle in reusable way
|
||||
if (!ref.selector(selector)) {
|
||||
return false;
|
||||
} else if (value.value[0].is == 'keyword') {
|
||||
return validateKeyword(value, selector);
|
||||
} else if (value.value[0].is == 'undefined') {
|
||||
// caught earlier in the chain - ignore here so that
|
||||
// error is not overridden
|
||||
return true;
|
||||
} else if (ref.selector(selector).type == 'numbers') {
|
||||
for (i in value.value) {
|
||||
if (value.value[i].is !== 'float') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (ref.selector(selector).type == 'tags') {
|
||||
if (!value.value) return false;
|
||||
if (!value.value[0].value) {
|
||||
return value.value[0].is === 'tag';
|
||||
}
|
||||
for (i = 0; i < value.value[0].value.length; i++) {
|
||||
if (value.value[0].value[i].is !== 'tag') return false;
|
||||
}
|
||||
return true;
|
||||
} else if (ref.selector(selector).type == 'functions') {
|
||||
// For backwards compatibility, you can specify a string for `functions`-compatible
|
||||
// values, though they will not be validated.
|
||||
return validateFunctions(value, selector);
|
||||
} else if (ref.selector(selector).type === 'unsigned') {
|
||||
if (value.value[0].is === 'float') {
|
||||
value.value[0].round();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if ((ref.selector(selector).expression)) {
|
||||
return true;
|
||||
} else {
|
||||
if (ref.selector(selector).validate) {
|
||||
var valid = false;
|
||||
for (i = 0; i < value.value.length; i++) {
|
||||
if (ref.selector(selector).type == value.value[i].is &&
|
||||
ref
|
||||
._validateValue
|
||||
[ref.selector(selector).validate]
|
||||
(env, value.value[i].value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
} else {
|
||||
return ref.selector(selector).type == value.value[0].is;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tree.Reference = ref;
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,4 +1,7 @@
|
||||
(function(tree) {
|
||||
// a rule is a single property and value combination, or variable
|
||||
// name and value combination, like
|
||||
// polygon-opacity: 1.0; or @opacity: 1.0;
|
||||
tree.Rule = function Rule(name, value, index, filename) {
|
||||
var parts = name.split('/');
|
||||
this.name = parts.pop();
|
||||
@ -11,9 +14,11 @@ tree.Rule = function Rule(name, value, index, filename) {
|
||||
this.variable = (name.charAt(0) === '@');
|
||||
};
|
||||
|
||||
tree.Rule.prototype.is = 'rule';
|
||||
|
||||
tree.Rule.prototype.clone = function() {
|
||||
var clone = Object.create(tree.Rule.prototype);
|
||||
clone.name = this.name
|
||||
clone.name = this.name;
|
||||
clone.value = this.value;
|
||||
clone.index = this.index;
|
||||
clone.instance = this.instance;
|
||||
@ -24,21 +29,32 @@ tree.Rule.prototype.clone = function() {
|
||||
};
|
||||
|
||||
tree.Rule.prototype.updateID = function() {
|
||||
return this.id = this.zoom + '#' + this.name;
|
||||
return this.id = this.zoom + '#' + this.instance + '#' + this.name;
|
||||
};
|
||||
|
||||
tree.Rule.prototype.toString = function() {
|
||||
return '[' + tree.Zoom.toString(this.zoom) + '] ' + this.name + ': ' + this.value;
|
||||
};
|
||||
|
||||
function getMean(name) {
|
||||
return Object.keys(tree.Reference.selector_cache).map(function(f) {
|
||||
return [f, tree.Reference.editDistance(name, f)];
|
||||
}).sort(function(a, b) { return a[1] - b[1]; });
|
||||
}
|
||||
|
||||
// second argument, if true, outputs the value of this
|
||||
// rule without the usual attribute="content" wrapping. Right
|
||||
// now this is just for the TextSymbolizer, but applies to other
|
||||
// properties in reference.json which specify serialization=content
|
||||
tree.Rule.prototype.toXML = function(env, content) {
|
||||
tree.Rule.prototype.toXML = function(env, content, sep, format) {
|
||||
if (!tree.Reference.validSelector(this.name)) {
|
||||
var mean = getMean(this.name);
|
||||
var mean_message = '';
|
||||
if (mean[0][1] < 3) {
|
||||
mean_message = '. Did you mean ' + mean[0][0] + '?';
|
||||
}
|
||||
return env.error({
|
||||
message: "Unrecognized rule: " + this.name,
|
||||
message: "Unrecognized rule: " + this.name + mean_message,
|
||||
index: this.index,
|
||||
type: 'syntax',
|
||||
filename: this.filename
|
||||
@ -47,18 +63,35 @@ tree.Rule.prototype.toXML = function(env, content) {
|
||||
|
||||
if ((this.value instanceof tree.Value) &&
|
||||
!tree.Reference.validValue(env, this.name, this.value)) {
|
||||
return env.error({
|
||||
message: 'Invalid value for ' +
|
||||
this.name +
|
||||
', a valid ' +
|
||||
(tree.Reference.selector(this.name).validate ||
|
||||
tree.Reference.selector(this.name).type) +
|
||||
' is expected. ' + this.value +
|
||||
' was given.',
|
||||
index: this.index,
|
||||
type: 'syntax',
|
||||
filename: this.filename
|
||||
});
|
||||
if (!tree.Reference.selector(this.name)) {
|
||||
return env.error({
|
||||
message: 'Unrecognized property: ' +
|
||||
this.name,
|
||||
index: this.index,
|
||||
type: 'syntax',
|
||||
filename: this.filename
|
||||
});
|
||||
} else {
|
||||
var typename;
|
||||
if (tree.Reference.selector(this.name).validate) {
|
||||
typename = tree.Reference.selector(this.name).validate;
|
||||
} else if (typeof tree.Reference.selector(this.name).type === 'object') {
|
||||
typename = 'keyword (options: ' + tree.Reference.selector(this.name).type.join(', ') + ')';
|
||||
} else {
|
||||
typename = tree.Reference.selector(this.name).type;
|
||||
}
|
||||
return env.error({
|
||||
message: 'Invalid value for ' +
|
||||
this.name +
|
||||
', the type ' + typename +
|
||||
' is expected. ' + this.value +
|
||||
' (of type ' + this.value.value[0].is + ') ' +
|
||||
' was given.',
|
||||
index: this.index,
|
||||
type: 'syntax',
|
||||
filename: this.filename
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (this.variable) {
|
||||
@ -67,7 +100,7 @@ tree.Rule.prototype.toXML = function(env, content) {
|
||||
var f = tree._getFontSet(env, this.value.value);
|
||||
return 'fontset-name="' + f.name + '"';
|
||||
} else if (content) {
|
||||
return this.value.toString(env, this.name);
|
||||
return this.value.toString(env, this.name, sep);
|
||||
} else {
|
||||
return tree.Reference.selectorName(this.name) +
|
||||
'="' +
|
||||
@ -76,26 +109,12 @@ tree.Rule.prototype.toXML = function(env, content) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Rule eval chain should add fontsets to env.frames
|
||||
*/
|
||||
tree.Rule.prototype.eval = function(context) {
|
||||
// TODO: Rule ev chain should add fontsets to env.frames
|
||||
tree.Rule.prototype.ev = function(context) {
|
||||
return new tree.Rule(this.name,
|
||||
this.value.eval(context),
|
||||
this.value.ev(context),
|
||||
this.index,
|
||||
this.filename);
|
||||
};
|
||||
|
||||
tree.Shorthand = function Shorthand(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
};
|
||||
|
||||
tree.Shorthand.prototype = {
|
||||
toString: function(env) {
|
||||
return this.a.toString(env) + '/' + this.b.toString(env);
|
||||
},
|
||||
eval: function() { return this }
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -7,46 +7,19 @@ tree.Ruleset = function Ruleset(selectors, rules) {
|
||||
this._lookups = {};
|
||||
};
|
||||
tree.Ruleset.prototype = {
|
||||
eval: function(env) {
|
||||
var ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
|
||||
is: 'ruleset',
|
||||
'ev': function(env) {
|
||||
var i,
|
||||
ruleset = new tree.Ruleset(this.selectors, this.rules.slice(0));
|
||||
ruleset.root = this.root;
|
||||
|
||||
// push the current ruleset to the frames stack
|
||||
env.frames.unshift(ruleset);
|
||||
|
||||
// Evaluate imports
|
||||
if (ruleset.root) {
|
||||
for (var i = 0; i < ruleset.rules.length; i++) {
|
||||
if (ruleset.rules[i] instanceof tree.Import) {
|
||||
Array.prototype.splice
|
||||
.apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the frames around mixin definitions,
|
||||
// so they can be evaluated like closures when the time comes.
|
||||
for (var i = 0; i < ruleset.rules.length; i++) {
|
||||
if (ruleset.rules[i] instanceof tree.mixin.Definition) {
|
||||
ruleset.rules[i].frames = env.frames.slice(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate mixin calls.
|
||||
for (var i = 0; i < ruleset.rules.length; i++) {
|
||||
if (ruleset.rules[i] instanceof tree.mixin.Call) {
|
||||
Array.prototype.splice
|
||||
.apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env)));
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate everything else
|
||||
for (var i = 0, rule; i < ruleset.rules.length; i++) {
|
||||
for (i = 0, rule; i < ruleset.rules.length; i++) {
|
||||
rule = ruleset.rules[i];
|
||||
|
||||
if (! (rule instanceof tree.mixin.Definition)) {
|
||||
ruleset.rules[i] = rule.eval ? rule.eval(env) : rule;
|
||||
}
|
||||
ruleset.rules[i] = rule.ev ? rule.ev(env) : rule;
|
||||
}
|
||||
|
||||
// Pop the stack
|
||||
@ -58,7 +31,7 @@ tree.Ruleset.prototype = {
|
||||
return !args || args.length === 0;
|
||||
},
|
||||
variables: function() {
|
||||
if (this._variables) { return this._variables }
|
||||
if (this._variables) { return this._variables; }
|
||||
else {
|
||||
return this._variables = this.rules.reduce(function(hash, r) {
|
||||
if (r instanceof tree.Rule && r.variable === true) {
|
||||
@ -71,19 +44,11 @@ tree.Ruleset.prototype = {
|
||||
variable: function(name) {
|
||||
return this.variables()[name];
|
||||
},
|
||||
/**
|
||||
* Extend this rule by adding rules from another ruleset
|
||||
*
|
||||
* Currently this is designed to accept less specific
|
||||
* rules and add their values only if this ruleset doesn't
|
||||
* contain them.
|
||||
*/
|
||||
|
||||
rulesets: function() {
|
||||
if (this._rulesets) { return this._rulesets }
|
||||
if (this._rulesets) { return this._rulesets; }
|
||||
else {
|
||||
return this._rulesets = this.rules.filter(function(r) {
|
||||
return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition);
|
||||
return (r instanceof tree.Ruleset);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -92,15 +57,16 @@ tree.Ruleset.prototype = {
|
||||
var rules = [], rule, match,
|
||||
key = selector.toString();
|
||||
|
||||
if (key in this._lookups) { return this._lookups[key] }
|
||||
if (key in this._lookups) { return this._lookups[key]; }
|
||||
|
||||
this.rulesets().forEach(function(rule) {
|
||||
if (rule !== self) {
|
||||
for (var j = 0; j < rule.selectors.length; j++) {
|
||||
if (match = selector.match(rule.selectors[j])) {
|
||||
match = selector.match(rule.selectors[j]);
|
||||
if (match) {
|
||||
if (selector.elements.length > 1) {
|
||||
Array.prototype.push.apply(rules, rule.find(
|
||||
new tree.Selector(null, null, selector.elements.slice(1)), self));
|
||||
new tree.Selector(null, null, null, selector.elements.slice(1)), self));
|
||||
} else {
|
||||
rules.push(rule);
|
||||
}
|
||||
@ -111,19 +77,35 @@ tree.Ruleset.prototype = {
|
||||
});
|
||||
return this._lookups[key] = rules;
|
||||
},
|
||||
// Zooms can use variables. This replaces tree.Zoom objects on selectors
|
||||
// with simple bit-arrays that we can compare easily.
|
||||
evZooms: function(env) {
|
||||
for (var i = 0; i < this.selectors.length; i++) {
|
||||
var zval = tree.Zoom.all;
|
||||
for (var z = 0; z < this.selectors[i].zoom.length; z++) {
|
||||
zval = zval & this.selectors[i].zoom[z].ev(env).zoom;
|
||||
}
|
||||
this.selectors[i].zoom = zval;
|
||||
}
|
||||
},
|
||||
flatten: function(result, parents, env) {
|
||||
var selectors = [];
|
||||
if (this.selectors.length == 0) {
|
||||
var selectors = [], i, j;
|
||||
if (this.selectors.length === 0) {
|
||||
env.frames = env.frames.concat(this.rules);
|
||||
}
|
||||
for (var i = 0; i < this.selectors.length; i++) {
|
||||
// evaluate zoom variables on this object.
|
||||
this.evZooms(env);
|
||||
for (i = 0; i < this.selectors.length; i++) {
|
||||
var child = this.selectors[i];
|
||||
|
||||
// This is an invalid filterset.
|
||||
if (!child.filters) continue;
|
||||
if (!child.filters) {
|
||||
// TODO: is this internal inconsistency?
|
||||
// This is an invalid filterset.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parents.length) {
|
||||
for (var j = 0; j < parents.length; j++) {
|
||||
for (j = 0; j < parents.length; j++) {
|
||||
var parent = parents[j];
|
||||
|
||||
var mergedFilters = parent.filters.cloneWith(child.filters);
|
||||
@ -132,7 +114,10 @@ tree.Ruleset.prototype = {
|
||||
// filters. This means that we only have to clone when
|
||||
// the zoom levels or the attachment is different too.
|
||||
if (parent.zoom === (parent.zoom & child.zoom) &&
|
||||
parent.attachment === child.attachment) {
|
||||
parent.frame_offset === child.frame_offset &&
|
||||
parent.attachment === child.attachment &&
|
||||
parent.elements.join() === child.elements.join()) {
|
||||
selectors.push(parent);
|
||||
continue;
|
||||
} else {
|
||||
mergedFilters = parent.filters;
|
||||
@ -146,6 +131,7 @@ tree.Ruleset.prototype = {
|
||||
var clone = Object.create(tree.Selector.prototype);
|
||||
clone.filters = mergedFilters;
|
||||
clone.zoom = parent.zoom & child.zoom;
|
||||
clone.frame_offset = child.frame_offset;
|
||||
clone.elements = parent.elements.concat(child.elements);
|
||||
if (parent.attachment && child.attachment) {
|
||||
clone.attachment = parent.attachment + '/' + child.attachment;
|
||||
@ -161,9 +147,10 @@ tree.Ruleset.prototype = {
|
||||
}
|
||||
|
||||
var rules = [];
|
||||
for (var i = 0; i < this.rules.length; i++) {
|
||||
for (i = 0; i < this.rules.length; i++) {
|
||||
var rule = this.rules[i];
|
||||
|
||||
// Recursively flatten any nested rulesets
|
||||
if (rule instanceof tree.Ruleset) {
|
||||
rule.flatten(result, selectors, env);
|
||||
} else if (rule instanceof tree.Rule) {
|
||||
@ -174,7 +161,7 @@ tree.Ruleset.prototype = {
|
||||
}
|
||||
|
||||
var index = rules.length ? rules[0].index : false;
|
||||
for (var i = 0; i < selectors.length; i++) {
|
||||
for (i = 0; i < selectors.length; i++) {
|
||||
// For specificity sort, use the position of the first rule to allow
|
||||
// defining attachments that are under current element as a descendant
|
||||
// selector.
|
||||
|
@ -1,23 +1,20 @@
|
||||
var assert = require('assert');
|
||||
|
||||
(function(tree) {
|
||||
|
||||
tree.Selector = function Selector(filters, zoom, elements, attachment, conditions, index) {
|
||||
tree.Selector = function Selector(filters, zoom, frame_offset, elements, attachment, conditions, index) {
|
||||
this.elements = elements || [];
|
||||
this.attachment = attachment;
|
||||
this.filters = filters || {};
|
||||
this.frame_offset = frame_offset;
|
||||
this.zoom = typeof zoom !== 'undefined' ? zoom : tree.Zoom.all;
|
||||
this.conditions = conditions;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the specificity of this selector
|
||||
* based on the specificity of its elements - calling
|
||||
* Element.specificity() in order to do so
|
||||
*
|
||||
* [ID, Class, Filters, Position in document]
|
||||
*/
|
||||
// Determine the specificity of this selector
|
||||
// based on the specificity of its elements - calling
|
||||
// Element.specificity() in order to do so
|
||||
//
|
||||
// [ID, Class, Filters, Position in document]
|
||||
tree.Selector.prototype.specificity = function() {
|
||||
return this.elements.reduce(function(memo, e) {
|
||||
var spec = e.specificity();
|
||||
|
@ -1,18 +1,68 @@
|
||||
(function(tree) {
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
tree.Style = function Style(name, attachment, definitions) {
|
||||
this.attachment = attachment;
|
||||
this.definitions = definitions;
|
||||
this.name = name + (attachment !== '__default__' ? '-' + attachment : '');
|
||||
};
|
||||
|
||||
tree.Style.prototype.toXML = function(env) {
|
||||
// Given a style's name, attachment, definitions, and an environment object,
|
||||
// return a stringified style for Mapnik
|
||||
tree.StyleXML = function(name, attachment, definitions, env) {
|
||||
var existing = {};
|
||||
var rules = this.definitions.map(function(definition) {
|
||||
var image_filters = [], image_filters_inflate = [], direct_image_filters = [], comp_op = [], opacity = [];
|
||||
|
||||
for (var i = 0; i < definitions.length; i++) {
|
||||
for (var j = 0; j < definitions[i].rules.length; j++) {
|
||||
if (definitions[i].rules[j].name === 'image-filters') {
|
||||
image_filters.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'image-filters-inflate') {
|
||||
image_filters_inflate.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'direct-image-filters') {
|
||||
direct_image_filters.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'comp-op') {
|
||||
comp_op.push(definitions[i].rules[j]);
|
||||
}
|
||||
if (definitions[i].rules[j].name === 'opacity') {
|
||||
opacity.push(definitions[i].rules[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rules = definitions.map(function(definition) {
|
||||
return definition.toXML(env, existing);
|
||||
});
|
||||
|
||||
return '<Style name="' + this.name + '" filter-mode="first">\n' + rules.join('') + '</Style>';
|
||||
var attrs_xml = '';
|
||||
|
||||
if (image_filters.length) {
|
||||
attrs_xml += ' image-filters="' + _.chain(image_filters)
|
||||
// prevent identical filters from being duplicated in the style
|
||||
.uniq(function(i) { return i.id; }).map(function(f) {
|
||||
return f.ev(env).toXML(env, true, ',', 'image-filter');
|
||||
}).value().join(',') + '"';
|
||||
}
|
||||
|
||||
if (image_filters_inflate.length) {
|
||||
attrs_xml += ' image-filters-inflate="' + image_filters_inflate[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
|
||||
if (direct_image_filters.length) {
|
||||
attrs_xml += ' direct-image-filters="' + _.chain(direct_image_filters)
|
||||
// prevent identical filters from being duplicated in the style
|
||||
.uniq(function(i) { return i.id; }).map(function(f) {
|
||||
return f.ev(env).toXML(env, true, ',', 'direct-image-filter');
|
||||
}).value().join(',') + '"';
|
||||
}
|
||||
|
||||
if (comp_op.length && comp_op[0].value.ev(env).value != 'src-over') {
|
||||
attrs_xml += ' comp-op="' + comp_op[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
|
||||
if (opacity.length && opacity[0].value.ev(env).value != 1) {
|
||||
attrs_xml += ' opacity="' + opacity[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
var rule_string = rules.join('');
|
||||
if (!attrs_xml && !rule_string) return '';
|
||||
return '<Style name="' + name + '" filter-mode="first"' + attrs_xml + '>\n' + rule_string + '</Style>';
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -1,26 +1,17 @@
|
||||
(function(tree) {
|
||||
|
||||
tree.URL = function URL(val, paths) {
|
||||
if (val.data) {
|
||||
this.attrs = val;
|
||||
} else {
|
||||
// Add the base path if the URL is relative and we are in the browser
|
||||
if (!/^(?:https?:\/|file:\/)?\//.test(val.value) && paths.length > 0 && typeof(process) === 'undefined') {
|
||||
val.value = paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
|
||||
}
|
||||
this.value = val;
|
||||
this.paths = paths;
|
||||
this.is = 'uri';
|
||||
}
|
||||
this.value = val;
|
||||
this.paths = paths;
|
||||
};
|
||||
|
||||
tree.URL.prototype = {
|
||||
is: 'uri',
|
||||
toString: function() {
|
||||
return this.value.toString();
|
||||
},
|
||||
eval: function(ctx) {
|
||||
return this.attrs ? this : new tree.URL(this.value.eval(ctx), this.paths);
|
||||
// URL case no longer supported.
|
||||
// @TODO: throw an error?
|
||||
ev: function(ctx) {
|
||||
return new tree.URL(this.value.ev(ctx), this.paths);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,22 +2,23 @@
|
||||
|
||||
tree.Value = function Value(value) {
|
||||
this.value = value;
|
||||
this.is = 'value';
|
||||
};
|
||||
|
||||
tree.Value.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'value',
|
||||
ev: function(env) {
|
||||
if (this.value.length === 1) {
|
||||
return this.value[0].eval(env);
|
||||
return this.value[0].ev(env);
|
||||
} else {
|
||||
return new tree.Value(this.value.map(function(v) {
|
||||
return v.eval(env);
|
||||
return v.ev(env);
|
||||
}));
|
||||
}
|
||||
},
|
||||
toString: function(env, selector) {
|
||||
toString: function(env, selector, sep, format) {
|
||||
return this.value.map(function(e) {
|
||||
return e.toString(env);
|
||||
}).join(', ');
|
||||
return e.toString(env, format);
|
||||
}).join(sep || ', ');
|
||||
},
|
||||
clone: function() {
|
||||
var obj = Object.create(tree.Value.prototype);
|
||||
@ -25,7 +26,34 @@ tree.Value.prototype = {
|
||||
else obj.value = this.value;
|
||||
obj.is = this.is;
|
||||
return obj;
|
||||
},
|
||||
|
||||
toJS: function(env) {
|
||||
//var v = this.value[0].value[0];
|
||||
var val = this.ev(env);
|
||||
var v = val.toString();
|
||||
if(val.is === "color" || val.is === 'uri' || val.is === 'string' || val.is === 'keyword') {
|
||||
v = "'" + v.replace(/&/g, '&') + "'";
|
||||
} else if (Array.isArray(this.value) && this.value.length > 1) {
|
||||
// This covers something like `line-dasharray: 5, 10;`
|
||||
// where the return _value has more than one element.
|
||||
// Without this the generated code will look like:
|
||||
// _value = 5, 10; which will ignore the 10.
|
||||
v = '[' + this.value.join(',') + ']';
|
||||
} else if (val.is === 'field') {
|
||||
// replace [variable] by ctx['variable']
|
||||
v = v.replace(/\[([^\]]*)\]/g, function(matched) {
|
||||
return matched.replace(/\[(.*)\]/g, "data['$1']");
|
||||
});
|
||||
}else if (val.is === 'call') {
|
||||
v = JSON.stringify({
|
||||
name: val.name,
|
||||
args: val.args
|
||||
})
|
||||
}
|
||||
return "_value = " + v + ";";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -5,26 +5,30 @@ tree.Variable = function Variable(name, index, filename) {
|
||||
this.index = index;
|
||||
this.filename = filename;
|
||||
};
|
||||
|
||||
tree.Variable.prototype = {
|
||||
eval: function(env) {
|
||||
is: 'variable',
|
||||
toString: function() {
|
||||
return this.name;
|
||||
},
|
||||
ev: function(env) {
|
||||
var variable,
|
||||
v,
|
||||
that = this,
|
||||
name = this.name;
|
||||
|
||||
if (this._css) return this._css;
|
||||
|
||||
var thisframe = env.frames.filter(function(f) {
|
||||
return f.name == that.name;
|
||||
});
|
||||
return f.name == this.name;
|
||||
}.bind(this));
|
||||
if (thisframe.length) {
|
||||
return thisframe[0].value.eval(env);
|
||||
return thisframe[0].value.ev(env);
|
||||
} else {
|
||||
env.error({
|
||||
message: 'variable ' + this.name + ' is undefined',
|
||||
index: this.index,
|
||||
type: 'runtime',
|
||||
filename: this.filename,
|
||||
filename: this.filename
|
||||
});
|
||||
return {
|
||||
is: 'undefined',
|
||||
|
@ -4,23 +4,34 @@ var tree = require('../tree');
|
||||
// and stores them as bit-sequences so that they can be combined,
|
||||
// inverted, and compared quickly.
|
||||
tree.Zoom = function(op, value, index) {
|
||||
value = parseInt(value);
|
||||
if (value > tree.Zoom.maxZoom || value < 0) {
|
||||
throw {
|
||||
message: 'Only zoom levels between 0 and '
|
||||
+ tree.Zoom.maxZoom + ' supported.',
|
||||
index: index
|
||||
};
|
||||
}
|
||||
this.op = op;
|
||||
this.value = value;
|
||||
this.index = index;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.setZoom = function(zoom) {
|
||||
this.zoom = zoom;
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.ev = function(env) {
|
||||
var start = 0,
|
||||
end = Infinity,
|
||||
value = parseInt(this.value.ev(env).toString(), 10),
|
||||
zoom = 0;
|
||||
|
||||
switch (op) {
|
||||
if (value > tree.Zoom.maxZoom || value < 0) {
|
||||
env.error({
|
||||
message: 'Only zoom levels between 0 and ' +
|
||||
tree.Zoom.maxZoom + ' supported.',
|
||||
index: this.index
|
||||
});
|
||||
}
|
||||
|
||||
switch (this.op) {
|
||||
case '=':
|
||||
return 1 << value;
|
||||
break;
|
||||
this.zoom = 1 << value;
|
||||
return this;
|
||||
case '>':
|
||||
start = value + 1;
|
||||
break;
|
||||
@ -39,7 +50,12 @@ tree.Zoom = function(op, value, index) {
|
||||
zoom |= (1 << i);
|
||||
}
|
||||
}
|
||||
return zoom;
|
||||
this.zoom = zoom;
|
||||
return this;
|
||||
};
|
||||
|
||||
tree.Zoom.prototype.toString = function() {
|
||||
return this.zoom;
|
||||
};
|
||||
|
||||
// Covers all zoomlevels from 0 to 22
|
||||
@ -75,29 +91,28 @@ tree.Zoom.ranges = {
|
||||
};
|
||||
|
||||
// Only works for single range zooms. `[XXX....XXXXX.........]` is invalid.
|
||||
tree.Zoom.toXML = function(zoom) {
|
||||
tree.Zoom.prototype.toXML = function() {
|
||||
var conditions = [];
|
||||
if (zoom != tree.Zoom.all) {
|
||||
if (this.zoom != tree.Zoom.all) {
|
||||
var start = null, end = null;
|
||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||
if (zoom & (1 << i)) {
|
||||
if (start == null) start = i;
|
||||
if (this.zoom & (1 << i)) {
|
||||
if (start === null) start = i;
|
||||
end = i;
|
||||
}
|
||||
}
|
||||
if (start > 0) conditions.push(' <MaxScaleDenominator>'
|
||||
+ tree.Zoom.ranges[start] + '</MaxScaleDenominator>\n');
|
||||
if (end < 22) conditions.push(' <MinScaleDenominator>'
|
||||
+ tree.Zoom.ranges[end + 1] + '</MinScaleDenominator>\n');
|
||||
if (start > 0) conditions.push(' <MaxScaleDenominator>' +
|
||||
tree.Zoom.ranges[start] + '</MaxScaleDenominator>\n');
|
||||
if (end < 22) conditions.push(' <MinScaleDenominator>' +
|
||||
tree.Zoom.ranges[end + 1] + '</MinScaleDenominator>\n');
|
||||
}
|
||||
return conditions;
|
||||
};
|
||||
|
||||
|
||||
tree.Zoom.toString = function(zoom) {
|
||||
tree.Zoom.prototype.toString = function() {
|
||||
var str = '';
|
||||
for (var i = 0; i <= tree.Zoom.maxZoom; i++) {
|
||||
str += (zoom & (1 << i)) ? 'X' : '.';
|
||||
str += (this.zoom & (1 << i)) ? 'X' : '.';
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
24
man/carto.1
Normal file
24
man/carto.1
Normal file
@ -0,0 +1,24 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.12.
|
||||
.TH CARTO "1" "March 2013" "carto 0.9.4" "User Commands"
|
||||
.SH NAME
|
||||
carto \- Carto map stylesheet compiler
|
||||
.SH SYNOPSIS
|
||||
.B carto [OPTION]
|
||||
\fI<source MML file>\fR
|
||||
.SH DESCRIPTION
|
||||
Carto is a stylesheet renderer for Mapnik. It's an evolution of
|
||||
the Cascadenik idea and language, with an emphasis on speed and
|
||||
flexibility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-v\fR \fB\-\-version\fR
|
||||
Parse JSON map manifest
|
||||
.TP
|
||||
\fB\-b\fR \fB\-\-benchmark\fR
|
||||
Outputs total compile time
|
||||
.TP
|
||||
\fB\-n\fR \fB\-\-nosymlink\fR
|
||||
Use absolute paths instead of symlinking files
|
||||
.SH REPORTING BUGS
|
||||
Please report bugs on the GitHub issue tracker:
|
||||
<\fBhttps://github.com/mapbox/carto/issues\fR>
|
2110
package-lock.json
generated
Normal file
2110
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
61
package.json
61
package.json
@ -1,18 +1,17 @@
|
||||
{
|
||||
"name": "carto",
|
||||
"description": "Mapnik Stylesheet Compiler",
|
||||
"url": "https://github.com/mapbox/carto",
|
||||
"repositories": [{
|
||||
"version": "0.15.1-cdb5",
|
||||
"description": "CartoCSS Stylesheet Compiler",
|
||||
"url": "https://github.com/cartodb/carto",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/mapbox/carto.git"
|
||||
}],
|
||||
"url": "http://github.com/cartodb/carto.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "MapBox",
|
||||
"url": "http://mapbox.com/",
|
||||
"email": "info@mapbox.com"
|
||||
"name": "CartoDB",
|
||||
"url": "http://cartodb.com/"
|
||||
},
|
||||
"keywords": [
|
||||
"mapnik",
|
||||
"maps",
|
||||
"css",
|
||||
"stylesheets"
|
||||
@ -20,33 +19,45 @@
|
||||
"contributors": [
|
||||
"Tom MacWright <macwright@gmail.com>",
|
||||
"Konstantin Käfer",
|
||||
"Alexis Sellier <self@cloudhead.net>"
|
||||
"Alexis Sellier <self@cloudhead.net>",
|
||||
"Raul Ochoa <rochoa@cartodb.com>",
|
||||
"Javi Santana <jsantana@cartodb.com>"
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache"
|
||||
}
|
||||
],
|
||||
"version": "0.4.4",
|
||||
"licenses": [{
|
||||
"type": "Apache"
|
||||
}],
|
||||
"bin": {
|
||||
"carto": "./bin/carto",
|
||||
"cartox": "./bin/cartox",
|
||||
"mml2json.js": "./bin/mml2json.js"
|
||||
"carto": "./bin/carto"
|
||||
},
|
||||
"man": "./man/carto.1",
|
||||
"main": "./lib/carto/index",
|
||||
"engines": {
|
||||
"node": "0.4.x"
|
||||
"node": ">=0.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"underscore": "1.1.x",
|
||||
"xml2js": ">= 0.1.0"
|
||||
"underscore": "1.8.3",
|
||||
"mapnik-reference": "~6.0.2",
|
||||
"optimist": "~0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expresso": "0.8.x",
|
||||
"docco": "0.3.x",
|
||||
"mocha": "1.12.x",
|
||||
"jshint": "0.2.x",
|
||||
"sax": "0.1.x"
|
||||
"sax": "0.1.x",
|
||||
"istanbul": "~0.2.14",
|
||||
"coveralls": "~2.10.1",
|
||||
"browserify": "~7.0.0",
|
||||
"uglify-js": "1.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
"pretest": "npm install --dev",
|
||||
"test": "expresso"
|
||||
"pretest": "npm install",
|
||||
"test": "mocha -R spec",
|
||||
"tdd": "env HIDE_LOGS=true mocha -w -R spec",
|
||||
"coverage": "istanbul cover ./node_modules/.bin/_mocha && coveralls < ./coverage/lcov.info",
|
||||
"bump": "npm version patch",
|
||||
"bump:major": "npm version major",
|
||||
"bump:minor": "npm version minor",
|
||||
"postversion": "git push origin master --follow-tags"
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"opened.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "data",
|
||||
"name": "data",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://client-data.s3.amazonaws.com/ndi-afghanistan/district_boundaries.zip",
|
||||
"type": "shape"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE Map[]>
|
||||
<Map srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over">
|
||||
<Map srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over">
|
||||
|
||||
|
||||
<Style name="data-polygon" filter-mode="first">
|
||||
@ -2003,7 +2003,7 @@
|
||||
<Layer
|
||||
id="data"
|
||||
name="data"
|
||||
srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over">
|
||||
srs="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over">
|
||||
<StyleName>data-polygon</StyleName>
|
||||
<Datasource>
|
||||
<Parameter name="file">[absolute path]</Parameter>
|
||||
|
37
test/bincarto.test.js
Normal file
37
test/bincarto.test.js
Normal file
@ -0,0 +1,37 @@
|
||||
var assert = require('assert');
|
||||
var exec = require('child_process').exec;
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var helper = require('./support/helper');
|
||||
var bin = path.resolve(path.join(__dirname, '..', 'bin', 'carto'));
|
||||
var fs = require('fs');
|
||||
|
||||
describe('bin/carto', function() {
|
||||
it('errors on no input', function(done) {
|
||||
exec(bin, function(err, stdout, stderr) {
|
||||
assert.equal(1, err.code);
|
||||
assert.equal("carto: no input files ('carto -h or --help' for help)\n", stdout);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('renders mml', function(done) {
|
||||
var file = path.join(__dirname, 'rendering', 'identity.mml');
|
||||
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
|
||||
assert.ifError(err);
|
||||
helper.compareToXMLFile(helper.resultFile(file), stdout, done, [
|
||||
helper.removeAbsoluteImages,
|
||||
helper.removeAbsoluteDatasources
|
||||
]);
|
||||
});
|
||||
});
|
||||
it('renders mss', function(done) {
|
||||
var file = path.join(__dirname, 'rendering-mss', 'empty_name.mss');
|
||||
exec(util.format('%s %s', bin, file), function(err, stdout, stderr) {
|
||||
assert.ifError(err);
|
||||
var expected = file.replace(path.extname(file),'')+'.xml';
|
||||
var expected_data = fs.readFileSync(expected, 'utf8');
|
||||
assert.equal(stdout,expected_data + '\n');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
25
test/color.test.js
Normal file
25
test/color.test.js
Normal file
@ -0,0 +1,25 @@
|
||||
var assert = require('assert');
|
||||
var tree = require('../lib/carto/tree.js');
|
||||
require('../lib/carto/functions');
|
||||
require('../lib/carto/tree/color');
|
||||
require('../lib/carto/tree/dimension');
|
||||
|
||||
describe('Color', function() {
|
||||
describe('basic functionality', function() {
|
||||
it('should be constructed', function() {
|
||||
var f = new tree.Color([0, 0, 0], 1);
|
||||
assert.deepEqual(f.toHSL(), {"h":0,"s":0,"l":0,"a":1});
|
||||
assert.ok(f);
|
||||
});
|
||||
});
|
||||
describe('functions', function() {
|
||||
it('should be constructed', function() {
|
||||
assert.deepEqual(tree.functions.rgb(0, 0, 0), new tree.Color([0, 0, 0], 1));
|
||||
assert.deepEqual(tree.functions.hue(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0));
|
||||
assert.deepEqual(tree.functions.saturation(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
|
||||
assert.deepEqual(tree.functions.lightness(new tree.Color([0, 0, 0], 1)), new tree.Dimension(0, '%'));
|
||||
assert.deepEqual(tree.functions.alpha(new tree.Color([0, 0, 0], 1)), new tree.Dimension(1));
|
||||
assert.deepEqual(tree.functions.greyscale(new tree.Color([0, 0, 0], 1)), new tree.Color([0, 0, 0], 1));
|
||||
});
|
||||
});
|
||||
});
|
14
test/comment.test.js
Normal file
14
test/comment.test.js
Normal file
@ -0,0 +1,14 @@
|
||||
var assert = require('assert');
|
||||
var tree = require('../lib/carto/tree.js');
|
||||
require('../lib/carto/tree/comment');
|
||||
|
||||
describe('Comment', function() {
|
||||
describe('basic functionality', function() {
|
||||
it('should be constructed', function() {
|
||||
var f = new tree.Comment('hello world');
|
||||
assert.deepEqual(f.toString(), '<!--hello world-->');
|
||||
assert.deepEqual(f.ev(), f);
|
||||
assert.ok(f);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,4 @@
|
||||
var path = require('path'),
|
||||
sys = require('sys'),
|
||||
assert = require('assert'),
|
||||
fs = require('fs');
|
||||
|
||||
@ -7,8 +6,10 @@ var carto = require('../lib/carto');
|
||||
var tree = require('../lib/carto/tree');
|
||||
var helper = require('./support/helper');
|
||||
|
||||
describe('Error handling mml+mss', function() {
|
||||
helper.files('errorhandling', 'mml', function(file) {
|
||||
exports['errorhandling ' + path.basename(file)] = function(beforeExit) {
|
||||
var basename = path.basename(file);
|
||||
it('should handle errors in ' + basename, function(done) {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mml = helper.mml(file);
|
||||
@ -18,34 +19,58 @@ helper.files('errorhandling', 'mml', function(file) {
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).render(mml, function (err) {
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||
});
|
||||
});
|
||||
}).render(mml);
|
||||
// should not get here
|
||||
assert.ok(false);
|
||||
done();
|
||||
} catch(err) {
|
||||
if (err.message.indexOf('***') > -1) throw err;
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
fs.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||
});
|
||||
// fs.writeFileSync(helper.resultFile(file), output);
|
||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
||||
assert.deepEqual(output, data);
|
||||
done();
|
||||
}
|
||||
|
||||
|
||||
beforeExit(function() {
|
||||
/*
|
||||
if (!completed && renderResult) {
|
||||
console.warn(helper.stylize('renderer produced:', 'bold'));
|
||||
console.warn(renderResult);
|
||||
}
|
||||
assert.ok(completed, 'Rendering finished.');
|
||||
*/
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling mss', function() {
|
||||
helper.files('errorhandling', 'mss', function(file) {
|
||||
var basename = path.basename(file);
|
||||
if (basename == 'multi_stylesheets_a.mss') {
|
||||
return;
|
||||
}
|
||||
it('should handle errors in ' + basename, function(done) {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mss = helper.mss(file);
|
||||
try {
|
||||
new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
// note: we use the basename here so that the expected error result
|
||||
// will match if the style was loaded from mml
|
||||
filename: basename
|
||||
}).renderMSS(mss);
|
||||
// should not get here
|
||||
assert.ok(false);
|
||||
done();
|
||||
} catch(err) {
|
||||
if (err.message.indexOf('***') > -1) throw err;
|
||||
var result = helper.resultFile(file);
|
||||
var output = err.message;
|
||||
// @TODO for some reason, fs.readFile includes an additional \n
|
||||
// at the end of read files. Determine why.
|
||||
// fs.writeFileSync(helper.resultFile(file), output);
|
||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
||||
assert.deepEqual(output, data);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
15
test/errorhandling/bad_op.mml
Normal file
15
test/errorhandling/bad_op.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"bad_op.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/bad_op.mss
Normal file
3
test/errorhandling/bad_op.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 20% + 2px;
|
||||
}
|
1
test/errorhandling/bad_op.result
Normal file
1
test/errorhandling/bad_op.result
Normal file
@ -0,0 +1 @@
|
||||
bad_op.mss:2:4 If two operands differ, the first must not be %
|
15
test/errorhandling/bad_op_2.mml
Normal file
15
test/errorhandling/bad_op_2.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"bad_op_2.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/bad_op_2.mss
Normal file
3
test/errorhandling/bad_op_2.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 20px * 2%;
|
||||
}
|
1
test/errorhandling/bad_op_2.result
Normal file
1
test/errorhandling/bad_op_2.result
Normal file
@ -0,0 +1 @@
|
||||
bad_op_2.mss:2:4 Percent values can only be added or subtracted from other values
|
15
test/errorhandling/color_functions.mml
Normal file
15
test/errorhandling/color_functions.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"color_functions.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
4
test/errorhandling/color_functions.mss
Normal file
4
test/errorhandling/color_functions.mss
Normal file
@ -0,0 +1,4 @@
|
||||
@foo: 'bar';
|
||||
#world {
|
||||
polygon-fill: hsl(1, @foo, 3);
|
||||
}
|
1
test/errorhandling/color_functions.result
Normal file
1
test/errorhandling/color_functions.result
Normal file
@ -0,0 +1 @@
|
||||
color_functions.mss:3:31 incorrect arguments given to hsl()
|
15
test/errorhandling/contradiction.mml
Normal file
15
test/errorhandling/contradiction.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"contradiction.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/district.geojson",
|
||||
"type": "ogr",
|
||||
"layer": "OGRGeoJSON"
|
||||
}
|
||||
}]
|
||||
}
|
3
test/errorhandling/contradiction.mss
Normal file
3
test/errorhandling/contradiction.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world[FeatureCla!=""][FeatureCla=""] {
|
||||
polygon-fill: #fff;
|
||||
}
|
1
test/errorhandling/contradiction.result
Normal file
1
test/errorhandling/contradiction.result
Normal file
@ -0,0 +1 @@
|
||||
contradiction.mss:1:37 [[FeatureCla]=] added to [FeatureCla]!= produces an invalid filter
|
3
test/errorhandling/contradiction_2.mss
Normal file
3
test/errorhandling/contradiction_2.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world[FeatureCla=""][FeatureCla!=""] {
|
||||
polygon-fill: #fff;
|
||||
}
|
1
test/errorhandling/contradiction_2.result
Normal file
1
test/errorhandling/contradiction_2.result
Normal file
@ -0,0 +1 @@
|
||||
contradiction_2.mss:1:37 [[FeatureCla]!=] added to [FeatureCla]= produces an invalid filter
|
15
test/errorhandling/function_args.mml
Normal file
15
test/errorhandling/function_args.mml
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"function_args.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
}
|
||||
}]
|
||||
}
|
4
test/errorhandling/function_args.mss
Normal file
4
test/errorhandling/function_args.mss
Normal file
@ -0,0 +1,4 @@
|
||||
#world {
|
||||
point-transform: scale(2, 2);
|
||||
image-filters: agg-stack-blu(2, 1);
|
||||
}
|
1
test/errorhandling/function_args.result
Normal file
1
test/errorhandling/function_args.result
Normal file
@ -0,0 +1 @@
|
||||
function_args.mss:3:38 unknown function agg-stack-blu(), did you mean agg-stack-blur(2)
|
3
test/errorhandling/invalid_color_in_fn.mss
Normal file
3
test/errorhandling/invalid_color_in_fn.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
polygon-fill: spin(#f00f00f, 10);
|
||||
}
|
1
test/errorhandling/invalid_color_in_fn.result
Normal file
1
test/errorhandling/invalid_color_in_fn.result
Normal file
@ -0,0 +1 @@
|
||||
invalid_color_in_fn.mss:2:34 incorrect arguments given to spin()
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"invalid_property.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
|
@ -1 +1 @@
|
||||
invalid_property.mss:3:2 Unrecognized rule: polygonopacity
|
||||
invalid_property.mss:3:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
@ -1,12 +1,12 @@
|
||||
{
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Stylesheet": [
|
||||
"invalid_value.mss"
|
||||
],
|
||||
"Layer": [{
|
||||
"id": "world",
|
||||
"name": "world",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"srs": "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over",
|
||||
"Datasource": {
|
||||
"file": "http://tilemill-data.s3.amazonaws.com/test_data/shape_demo.zip",
|
||||
"type": "shape"
|
||||
|
@ -1,3 +1,4 @@
|
||||
#world[zoom=5] {
|
||||
polygon-opacity: #f00;
|
||||
text-face-name: 2;
|
||||
text-name: 'foo';
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user