Compare commits
260 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 | ||
|
60030b1e69 | ||
|
d99c5d6c14 | ||
|
2bfc8a154b | ||
|
5a8461a6c1 | ||
|
dfacf064e8 | ||
|
6562d77935 | ||
|
d9e412696d | ||
|
08db949fbf | ||
|
e32aeb9fbb | ||
|
d7d49214cd | ||
|
ca42300648 | ||
|
393fc249b2 | ||
|
2ae7743c7d | ||
|
1d752708c4 | ||
|
4f963b8bb3 | ||
|
bc9d67b328 | ||
|
656b6db9a3 | ||
|
a57a162248 | ||
|
3d87cd490d | ||
|
97f03a2eb3 | ||
|
99ee75ab8b | ||
|
264925324f | ||
|
d81b4b9d18 | ||
|
6fb339e21c | ||
|
4bb0fc0ebe | ||
|
bfc92c26bc | ||
|
f11259d734 | ||
|
2481c21365 | ||
|
e7a3d65cad |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
test/rendering/layers/
|
||||
test/rendering/cache/
|
||||
test/rendering-mss/npm-debug.log
|
||||
.idea/
|
||||
|
10
.travis.yml
10
.travis.yml
@ -1,7 +1,9 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
- "0.6"
|
||||
- '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
|
31
CHANGELOG.md
31
CHANGELOG.md
@ -1,5 +1,36 @@
|
||||
## 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)
|
||||
|
@ -16,7 +16,7 @@ Running the head binary:
|
||||
## Documentation
|
||||
|
||||
This repository contains auto-generated documentation of the content of Carto
|
||||
that's published on MapBox.com.
|
||||
that's published on Mapbox.com.
|
||||
|
||||
git fetch origin gh-pages:gh-pages
|
||||
|
||||
|
15
Makefile
15
Makefile
@ -3,6 +3,15 @@
|
||||
#
|
||||
|
||||
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
|
||||
@ -17,4 +26,10 @@ endif
|
||||
|
||||
check: test
|
||||
|
||||
dist:
|
||||
mkdir -p dist
|
||||
|
||||
|
||||
|
||||
|
||||
.PHONY: test
|
||||
|
372
README.md
372
README.md
@ -1,307 +1,83 @@
|
||||
# CartoCSS
|
||||
|
||||
[![Build Status](https://secure.travis-ci.org/mapbox/carto.png)](http://travis-ci.org/mapbox/carto)
|
||||
[![Build Status](https://travis-ci.org/CartoDB/carto.png?branch=master)](https://travis-ci.org/CartoDB/carto)
|
||||
|
||||
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
|
||||
|
||||
## Quick Start
|
||||
|
||||
```javascript
|
||||
// shader is a CartoCSS object
|
||||
|
||||
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())
|
||||
|
||||
var layerShader = layer.getStyle({ property: 1 }, { zoom: 10 })
|
||||
console.log(layerShader['marker-width']) // 1
|
||||
console.log(layerShader['marker-fill']) // #FF0000
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
# API
|
||||
|
||||
## RendererJS
|
||||
|
||||
### render(cartocss)
|
||||
|
||||
## CartoCSS
|
||||
|
||||
compiled cartocss object
|
||||
|
||||
### getLayers
|
||||
|
||||
return the layers, an array of ``CartoCSS.Layer`` object
|
||||
|
||||
### getDefault
|
||||
|
||||
return the default layer (``CartoCSS.Layer``), usually the Map layer
|
||||
|
||||
|
||||
### findLayer(where)
|
||||
|
||||
find a layer using where object.
|
||||
|
||||
```
|
||||
shader.findLayer({ name: 'test' })
|
||||
```
|
||||
|
||||
## CartoCSS.Layer
|
||||
|
||||
### getStyle(props, context)
|
||||
|
||||
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``
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
* [mapbox.com/carto](http://mapbox.com/carto/)
|
||||
|
||||
## MML
|
||||
_incompatibility_
|
||||
|
||||
* 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.
|
||||
* CartoCSS 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.
|
||||
|
||||
CartoCSS MML:
|
||||
|
||||
{
|
||||
"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": [{"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
|
||||
|
||||
<pre><Stylesheet><![CDATA[
|
||||
Map {
|
||||
map-bgcolor: #69f;
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
## Attachments and Instances
|
||||
_new_
|
||||
|
||||
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. `CartoCSS` acts the same way normally for the sake of familiarity and organization, but Mapnik itself is more powerful.
|
||||
|
||||
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. `CartoCSS` makes this accessible by allowing attachments to styles:
|
||||
|
||||
#world {
|
||||
line-color: #fff;
|
||||
line-width: 3;
|
||||
}
|
||||
|
||||
#world::outline {
|
||||
line-color: #000;
|
||||
line-width: 6;
|
||||
}
|
||||
|
||||
Attachments are optional: if you don't define them, CartoCSS 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>CartoCSS</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>
|
||||
|
||||
## Mapnik
|
||||
_new_
|
||||
|
||||
CartoCSS is only compatible with Mapnik >= 2.x.x.
|
||||
|
||||
## Rasters and Buildings
|
||||
_new_
|
||||
|
||||
Rasters are supported in CartoCSS - 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 `CartoCSS`. 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_
|
||||
|
||||
CartoCSS 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_
|
||||
|
||||
CartoCSS 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 `CartoCSS`. 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>
|
||||
|
||||
## Filters
|
||||
|
||||
CartoCSS supports a variety of filter styles:
|
||||
|
||||
Numeric comparisons:
|
||||
|
||||
```
|
||||
#world[population > 100]
|
||||
#world[population < 100]
|
||||
#world[population >= 100]
|
||||
#world[population <= 100]
|
||||
```
|
||||
|
||||
General comparisons:
|
||||
|
||||
```
|
||||
#world[population = 100]
|
||||
#world[population != 100]
|
||||
```
|
||||
|
||||
|
||||
String comparisons:
|
||||
|
||||
```
|
||||
/* a regular expression over name */
|
||||
#world[name =~ "A.*"]
|
||||
```
|
||||
|
||||
## Developers
|
||||
|
||||
#### Installation
|
||||
|
||||
If you're using [TileMill](http://mapbox.com/tilemill/), you're already
|
||||
using CartoCSS and don't need to do a thing.
|
||||
|
||||
If you're a developer-type and want to use the `carto` binary with
|
||||
`node.js` (and you have [npm](http://npmjs.org/) installed),
|
||||
|
||||
npm install -g carto
|
||||
|
||||
#### From the binary
|
||||
|
||||
Install `millstone` to enable support for localizing external resources (URLs and local files) referenced in your mml file.
|
||||
|
||||
npm install millstone
|
||||
carto map_file.json
|
||||
|
||||
#### From code
|
||||
|
||||
Currently CartoCSS 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.
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
|
||||
### Vim
|
||||
|
||||
To install, download or clone this repository, then add the `vim-carto`
|
||||
directory located at `build/vim-carto` to your `~/.vim` file.
|
||||
|
||||
## Credits
|
||||
|
||||
CartoCSS 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:
|
||||
|
||||
* [mocha](https://github.com/visionmedia/mocha)
|
||||
* [sax-js](https://github.com/isaacs/sax-js/)
|
||||
|
||||
## Authors
|
||||
|
||||
* Tom MacWright (tmcw)
|
||||
* Konstantin Käfer (kkaefer)
|
||||
* AJ Ashton (ajashton)
|
||||
* Dane Springmeyer (springmeyer)
|
||||
|
62
bin/carto
62
bin/carto
@ -65,26 +65,13 @@ function compileMML(err, data) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
var renderer = new carto.Renderer({
|
||||
filename: input,
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
renderer.render(data, function(err, output) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
process.exit(1);
|
||||
} else {
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
}
|
||||
});
|
||||
var output = renderer.render(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
@ -93,6 +80,12 @@ function compileMML(err, data) {
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
function compileMSS(err, data) {
|
||||
@ -100,26 +93,13 @@ function compileMSS(err, data) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
var renderer = new carto.Renderer({
|
||||
filename: path.basename(input),
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
try {
|
||||
var renderer = new carto.Renderer({
|
||||
filename: path.basename(input),
|
||||
benchmark: options.benchmark,
|
||||
ppi: options.ppi
|
||||
});
|
||||
renderer.renderMSS(data, function(err, output) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
process.exit(1);
|
||||
} else {
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
}
|
||||
});
|
||||
var output = renderer.renderMSS(data);
|
||||
} catch (e) {
|
||||
if (e.stack) {
|
||||
console.error(e.stack);
|
||||
@ -128,6 +108,12 @@ function compileMSS(err, data) {
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
if (!options.benchmark) {
|
||||
console.log(output);
|
||||
} else {
|
||||
var duration = (+new Date) - start;
|
||||
console.log('TOTAL: ' + (duration) + 'ms');
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var xml2js = require('xml2js'),
|
||||
fs = require('fs');
|
||||
|
||||
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({
|
||||
explicitRoot: false,
|
||||
explicitArray: false
|
||||
});
|
||||
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);
|
||||
});
|
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.
|
||||
//
|
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
@ -4,7 +4,9 @@ var util = require('util'),
|
||||
|
||||
|
||||
function getVersion() {
|
||||
if (parseInt(process.version.split('.')[1], 10) > 4) {
|
||||
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
|
||||
@ -18,6 +20,8 @@ var carto = {
|
||||
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) {
|
||||
@ -49,7 +53,7 @@ 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');
|
||||
if (ctx.filename) (message += stylize(' in ', 'red') + ctx.filename);
|
||||
@ -64,15 +68,34 @@ var carto = {
|
||||
}
|
||||
};
|
||||
|
||||
[ 'call', 'color', 'comment', 'definition', 'dimension',
|
||||
'element', 'expression', 'filterset', 'filter', 'field',
|
||||
'keyword', 'layer', 'literal', 'operation', 'quoted', 'imagefilter',
|
||||
'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]; }
|
||||
@ -88,6 +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';
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
var carto = exports,
|
||||
tree = require('./tree'),
|
||||
_ = require('underscore');
|
||||
_ = global._ || require('underscore');
|
||||
|
||||
// Token matching is done with the `$` function, which either takes
|
||||
// a terminal string or regexp, or a non-terminal function to call.
|
||||
@ -102,6 +102,11 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
}
|
||||
|
||||
function extractErrorLine(style, errorIndex) {
|
||||
return (style.slice(0, errorIndex).match(/\n/g) || '').length + 1;
|
||||
}
|
||||
|
||||
|
||||
// Make an error object from a passed set of properties.
|
||||
// Accepted properties:
|
||||
// - `message`: Text of the error message.
|
||||
@ -109,8 +114,9 @@ carto.Parser = function Parser(env) {
|
||||
// - `index`: Char. index where the error occurred.
|
||||
function makeError(err) {
|
||||
var einput;
|
||||
var errorTemplate;
|
||||
|
||||
_(err).defaults({
|
||||
_.defaults(err, {
|
||||
index: furthest,
|
||||
filename: env.filename,
|
||||
message: 'Parse error.',
|
||||
@ -124,11 +130,12 @@ carto.Parser = function Parser(env) {
|
||||
einput = input;
|
||||
}
|
||||
|
||||
err.line = (einput.slice(0, err.index).match(/\n/g) || '').length + 1;
|
||||
err.line = extractErrorLine(einput, err.index);
|
||||
for (var n = err.index; n >= 0 && einput.charAt(n) !== '\n'; n--) {
|
||||
err.column++;
|
||||
}
|
||||
return new Error(_('<%=filename%>:<%=line%>:<%=column%> <%=message%>').template(err));
|
||||
errorTemplate = _.template('<%=filename%>:<%=line%>:<%=column%> <%=message%>');
|
||||
return new Error(errorTemplate(err));
|
||||
}
|
||||
|
||||
this.env = env = env || {};
|
||||
@ -138,6 +145,8 @@ carto.Parser = function Parser(env) {
|
||||
// The Parser
|
||||
parser = {
|
||||
|
||||
extractErrorLine: extractErrorLine,
|
||||
//
|
||||
// Parse an input string into an abstract syntax tree.
|
||||
// Throws an error on parse errors.
|
||||
parse: function(str) {
|
||||
@ -223,13 +232,14 @@ carto.Parser = function Parser(env) {
|
||||
// Start with the primary rule.
|
||||
// The whole syntax tree is held under a Ruleset node,
|
||||
// with the `root` property set to true, so no `{}` are
|
||||
// output. The callback is called when the input is parsed.
|
||||
// output.
|
||||
root = new tree.Ruleset([], $(this.parsers.primary));
|
||||
root.root = true;
|
||||
|
||||
// Get an array of Ruleset objects, flattened
|
||||
// and sorted according to specificitySort
|
||||
root.toList = (function() {
|
||||
var line, lines, column;
|
||||
return function(env) {
|
||||
env.error = function(e) {
|
||||
if (!env.errors) env.errors = new Error('');
|
||||
@ -462,11 +472,12 @@ carto.Parser = function Parser(env) {
|
||||
dimension: function() {
|
||||
var c = input.charCodeAt(i);
|
||||
if ((c > 57 || c < 45) || c === 47) return;
|
||||
var value = $(/^(-?\d*\.?\d+)(\%|\w+)?/);
|
||||
var value = $(/^(-?\d*\.?\d+(?:[eE][-+]?\d+)?)(\%|\w+)?/);
|
||||
if (value) {
|
||||
return new tree.Dimension(value[1], value[2], memo);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// The variable part of a variable definition.
|
||||
@ -517,11 +528,13 @@ carto.Parser = function Parser(env) {
|
||||
e, elements = [],
|
||||
f, filters = new tree.Filterset(),
|
||||
z, zooms = [],
|
||||
frame_offset = tree.FrameOffset.none;
|
||||
segments = 0, conditions = 0;
|
||||
|
||||
while (
|
||||
(e = $(this.element)) ||
|
||||
(z = $(this.zoom)) ||
|
||||
(fo = $(this.frame_offset)) ||
|
||||
(f = $(this.filter)) ||
|
||||
(a = $(this.attachment))
|
||||
) {
|
||||
@ -531,6 +544,9 @@ carto.Parser = function Parser(env) {
|
||||
} else if (z) {
|
||||
zooms.push(z);
|
||||
conditions++;
|
||||
} else if (fo) {
|
||||
frame_offset = fo;
|
||||
conditions++;
|
||||
} else if (f) {
|
||||
var err = filters.add(f);
|
||||
if (err) {
|
||||
@ -554,7 +570,7 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
|
||||
if (segments) {
|
||||
return new tree.Selector(filters, zooms, elements, attachment, conditions, memo);
|
||||
return new tree.Selector(filters, zooms, frame_offset, elements, attachment, conditions, memo);
|
||||
}
|
||||
},
|
||||
|
||||
@ -589,6 +605,17 @@ carto.Parser = function Parser(env) {
|
||||
}
|
||||
},
|
||||
|
||||
frame_offset: function() {
|
||||
save();
|
||||
var op, val;
|
||||
if ($(/^\[\s*frame-offset/g) &&
|
||||
(op = $(this.entities.comparison)) &&
|
||||
(val = $(/^\d+/)) &&
|
||||
$(']')) {
|
||||
return tree.FrameOffset(op, val, memo);
|
||||
}
|
||||
},
|
||||
|
||||
zoom: function() {
|
||||
save();
|
||||
var op, val;
|
||||
@ -699,10 +726,22 @@ carto.Parser = function Parser(env) {
|
||||
},
|
||||
// A sub-expression, contained by parenthensis
|
||||
sub: function() {
|
||||
var e;
|
||||
var e, expressions = [];
|
||||
|
||||
if ($('(') && (e = $(this.expression)) && $(')')) {
|
||||
return e;
|
||||
if ($('(')) {
|
||||
while (e = $(this.expression)) {
|
||||
expressions.push(e);
|
||||
if (! $(',')) { break; }
|
||||
}
|
||||
$(')');
|
||||
}
|
||||
|
||||
if (expressions.length > 1) {
|
||||
return new tree.Value(expressions.map(function(e) {
|
||||
return e.value[0];
|
||||
}));
|
||||
} else if (expressions.length === 1) {
|
||||
return new tree.Value(expressions);
|
||||
}
|
||||
},
|
||||
// This is a misnomer because it actually handles multiplication
|
||||
|
@ -1,28 +1,29 @@
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
var carto = require('./index');
|
||||
|
||||
carto.Renderer = function Renderer(env, options) {
|
||||
this.env = env || {};
|
||||
this.options = options || {};
|
||||
this.options.mapnik_version = this.options.mapnik_version || 'latest';
|
||||
this.options.mapnik_version = this.options.mapnik_version || '3.0.0';
|
||||
};
|
||||
|
||||
// Prepare a MSS document (given as an string) into a
|
||||
// XML Style fragment (mostly useful for debugging)
|
||||
//
|
||||
// - @param {String} str the mss contents as a string.
|
||||
// - @param {Object} env renderer environment options.
|
||||
carto.Renderer.prototype.renderMSS = function render(data, 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.
|
||||
var env = _(this.env).defaults({
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: []
|
||||
});
|
||||
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
return callback(new Error("Could not set mapnik version to " + this.options.mapnik_version), null);
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
@ -56,19 +57,20 @@ carto.Renderer.prototype.renderMSS = function render(data, callback) {
|
||||
if (env.benchmark) console.timeEnd(bench_name);
|
||||
}
|
||||
if (env.benchmark) console.timeEnd('Total Style generation');
|
||||
if (env.errors) return callback(env.errors);
|
||||
return callback(null, output.join('\n'));
|
||||
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} str the JSON file as a string.
|
||||
// - @param {Object} env renderer environment options.
|
||||
carto.Renderer.prototype.render = function render(m, callback) {
|
||||
/**
|
||||
* 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 = _(this.env).defaults({
|
||||
var env = _.defaults(this.env, {
|
||||
benchmark: false,
|
||||
validation_data: false,
|
||||
effects: [],
|
||||
@ -76,22 +78,21 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
});
|
||||
|
||||
if (!carto.tree.Reference.setVersion(this.options.mapnik_version)) {
|
||||
return callback(new Error("Could not set mapnik version to " + this.options.mapnik_version), null);
|
||||
throw new Error("Could not set mapnik version to " + this.options.mapnik_version);
|
||||
}
|
||||
|
||||
var output = [];
|
||||
|
||||
// Transform stylesheets into definitions.
|
||||
var definitions = _(m.Stylesheet).chain()
|
||||
var definitions = _.chain(m.Stylesheet)
|
||||
.map(function(s) {
|
||||
if (typeof s == 'string') {
|
||||
callback(new Error("Stylesheet object is expected not a string: '" + s + "'"));
|
||||
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)
|
||||
@ -145,19 +146,13 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
return e.toXML(env);
|
||||
}).join('\n'));
|
||||
|
||||
var map_properties;
|
||||
try {
|
||||
map_properties = getMapProperties(m, definitions, 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 = _(m).reduce(function(memo, v, k) {
|
||||
var parameters = _.reduce(m, function(memo, v, k) {
|
||||
if (!v && v !== 0) return memo;
|
||||
|
||||
switch (k) {
|
||||
@ -210,10 +205,7 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
'\n</Parameters>\n'
|
||||
);
|
||||
|
||||
var properties = _(map_properties).map(function(v) { return ' ' + v; }).join('');
|
||||
if(!map_properties['maximum-extent']) {
|
||||
properties += ' maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34"';
|
||||
}
|
||||
var properties = _.map(map_properties, function(v) { return ' ' + v; }).join('');
|
||||
|
||||
output.unshift(
|
||||
'<?xml version="1.0" ' +
|
||||
@ -221,16 +213,17 @@ carto.Renderer.prototype.render = function render(m, callback) {
|
||||
'<!DOCTYPE Map[]>\n' +
|
||||
'<Map' + properties +'>\n');
|
||||
output.push('</Map>');
|
||||
return callback(null, output.join('\n'));
|
||||
return output.join('\n');
|
||||
};
|
||||
|
||||
// This function currently modifies 'current'
|
||||
//
|
||||
// @param {Array} the 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
|
||||
/**
|
||||
* 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,
|
||||
@ -283,16 +276,18 @@ function addRules(current, definition, byFilter, env) {
|
||||
return current;
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
// result: an array of arrays is returned, in which each array refers to a
|
||||
// specific attachment
|
||||
/**
|
||||
* 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,
|
||||
@ -364,16 +359,18 @@ function sortStyles(styles, env) {
|
||||
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} definitions the output of toList.
|
||||
// - @param {Object} env.
|
||||
// - @return {String} rendered properties.
|
||||
/**
|
||||
* 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 = carto.tree.Reference.data.symbolizers.map;
|
||||
|
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,5 +1,5 @@
|
||||
(function(tree) {
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
tree.Call = function Call(name, args, index) {
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
@ -78,6 +78,7 @@ tree.Call.prototype = {
|
||||
};
|
||||
}
|
||||
if (fn !== args.length &&
|
||||
!(Array.isArray(fn) && _.include(fn, args.length)) &&
|
||||
// support variable-arg functions like `colorize-alpha`
|
||||
fn !== -1) {
|
||||
env.error({
|
||||
|
@ -1,6 +1,6 @@
|
||||
(function(tree) {
|
||||
var assert = require('assert'),
|
||||
_ = require('underscore');
|
||||
_ = global._ || require('underscore');
|
||||
|
||||
// A definition is the combination of a selector and rules, like
|
||||
// #foo {
|
||||
@ -20,6 +20,7 @@ tree.Definition = function Definition(selector, rules) {
|
||||
}
|
||||
this.filters = selector.filters;
|
||||
this.zoom = selector.zoom;
|
||||
this.frame_offset = selector.frame_offset;
|
||||
this.attachment = selector.attachment || '__default__';
|
||||
this.specificity = selector.specificity();
|
||||
};
|
||||
@ -97,6 +98,7 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
}
|
||||
|
||||
sym_order = symbolizerList(sym_order);
|
||||
var sym_count = 0;
|
||||
|
||||
for (var i = 0; i < sym_order.length; i++) {
|
||||
var attributes = symbolizers[sym_order[i]];
|
||||
@ -105,6 +107,7 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
// 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) {
|
||||
@ -147,7 +150,7 @@ tree.Definition.prototype.symbolizersToXML = function(env, symbolizers, zoom) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!xml) return '';
|
||||
if (!sym_count || !xml) return '';
|
||||
return ' <Rule>\n' + xml + ' </Rule>\n';
|
||||
};
|
||||
|
||||
@ -205,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,5 +1,5 @@
|
||||
(function(tree) {
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
//
|
||||
// A number with a unit
|
||||
//
|
||||
@ -47,6 +47,10 @@ tree.Dimension.prototype = {
|
||||
|
||||
return this;
|
||||
},
|
||||
round: function() {
|
||||
this.value = Math.round(this.value);
|
||||
return this;
|
||||
},
|
||||
toColor: function() {
|
||||
return new tree.Color([this.value, this.value, this.value]);
|
||||
},
|
||||
|
@ -1,4 +1,5 @@
|
||||
var tree = require('../tree');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
tree.Filterset = function Filterset() {
|
||||
this.filters = {};
|
||||
@ -77,6 +78,27 @@ tree.Filterset.prototype.cloneWith = function(other) {
|
||||
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
|
||||
|
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;
|
||||
|
@ -8,13 +8,14 @@ tree.Quoted.prototype = {
|
||||
is: 'string',
|
||||
|
||||
toString: function(quotes) {
|
||||
var xmlvalue = this.value
|
||||
var escapedValue = this.value
|
||||
.replace(/&/g, '&')
|
||||
.replace(/\'/g, ''')
|
||||
var xmlvalue = escapedValue
|
||||
.replace(/\'/g, '\\\'')
|
||||
.replace(/\"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/\>/g, '>');
|
||||
return (quotes === true) ? "'" + xmlvalue + "'" : this.value;
|
||||
return (quotes === true) ? "'" + xmlvalue + "'" : escapedValue;
|
||||
},
|
||||
|
||||
'ev': function() {
|
||||
|
@ -4,18 +4,26 @@
|
||||
// combinations.
|
||||
(function(tree) {
|
||||
|
||||
var _ = require('underscore'),
|
||||
mapnik_reference = require('mapnik-reference'),
|
||||
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);
|
||||
};
|
||||
|
||||
ref.setVersion = function(version) {
|
||||
var mapnik_reference = require('mapnik-reference');
|
||||
if (mapnik_reference.version.hasOwnProperty(version)) {
|
||||
ref.setData(mapnik_reference.version[version]);
|
||||
return true;
|
||||
@ -178,8 +186,6 @@ ref.validValue = function(env, selector, value) {
|
||||
// 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 === 'expression') {
|
||||
return true;
|
||||
} else if (ref.selector(selector).type === 'unsigned') {
|
||||
if (value.value[0].is === 'float') {
|
||||
value.value[0].round();
|
||||
@ -187,6 +193,8 @@ ref.validValue = function(env, selector, value) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if ((ref.selector(selector).expression)) {
|
||||
return true;
|
||||
} else {
|
||||
if (ref.selector(selector).validate) {
|
||||
var valid = false;
|
||||
@ -206,8 +214,6 @@ ref.validValue = function(env, selector, value) {
|
||||
}
|
||||
};
|
||||
|
||||
ref.setVersion('latest');
|
||||
|
||||
tree.Reference = ref;
|
||||
|
||||
})(require('../tree'));
|
||||
|
@ -66,7 +66,7 @@ tree.Ruleset.prototype = {
|
||||
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);
|
||||
}
|
||||
@ -114,6 +114,7 @@ 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.frame_offset === child.frame_offset &&
|
||||
parent.attachment === child.attachment &&
|
||||
parent.elements.join() === child.elements.join()) {
|
||||
selectors.push(parent);
|
||||
@ -130,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;
|
||||
|
@ -1,9 +1,10 @@
|
||||
(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;
|
||||
|
@ -1,17 +1,20 @@
|
||||
(function(tree) {
|
||||
var _ = require('underscore');
|
||||
var _ = global._ || require('underscore');
|
||||
|
||||
// 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 image_filters = [], direct_image_filters = [], comp_op = [], opacity = [];
|
||||
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]);
|
||||
}
|
||||
@ -38,6 +41,10 @@ tree.StyleXML = function(name, attachment, definitions, env) {
|
||||
}).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
|
||||
@ -46,11 +53,11 @@ tree.StyleXML = function(name, attachment, definitions, env) {
|
||||
}).value().join(',') + '"';
|
||||
}
|
||||
|
||||
if (comp_op.length) {
|
||||
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) {
|
||||
if (opacity.length && opacity[0].value.ev(env).value != 1) {
|
||||
attrs_xml += ' opacity="' + opacity[0].value.ev(env).toString() + '"';
|
||||
}
|
||||
var rule_string = rules.join('');
|
||||
|
@ -26,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'));
|
||||
|
2110
package-lock.json
generated
Normal file
2110
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -1,19 +1,17 @@
|
||||
{
|
||||
"name": "carto",
|
||||
"version": "0.9.5",
|
||||
"description": "Mapnik Stylesheet Compiler",
|
||||
"url": "https://github.com/mapbox/carto",
|
||||
"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"
|
||||
"type": "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"
|
||||
@ -21,14 +19,17 @@
|
||||
"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": [{
|
||||
"licenses": [
|
||||
{
|
||||
"type": "Apache"
|
||||
}],
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"carto": "./bin/carto",
|
||||
"mml2json.js": "./bin/mml2json.js"
|
||||
"carto": "./bin/carto"
|
||||
},
|
||||
"man": "./man/carto.1",
|
||||
"main": "./lib/carto/index",
|
||||
@ -36,18 +37,27 @@
|
||||
"node": ">=0.4.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"underscore": "~1.4.3",
|
||||
"mapnik-reference": "~5.0.7",
|
||||
"xml2js": "~0.2.4",
|
||||
"underscore": "1.8.3",
|
||||
"mapnik-reference": "~6.0.2",
|
||||
"optimist": "~0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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",
|
||||
"test": "mocha -R spec"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
});
|
||||
});
|
||||
});
|
@ -19,29 +19,20 @@ 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) {
|
||||
if (!err) {
|
||||
throw new Error("*** invalid error handling test found: " + basename + ": all error handling tests should throw!");
|
||||
}
|
||||
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));
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).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));
|
||||
done();
|
||||
});
|
||||
// fs.writeFileSync(helper.resultFile(file), output);
|
||||
var data = fs.readFileSync(helper.resultFile(file), 'utf8');
|
||||
assert.deepEqual(output, data);
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -65,29 +56,20 @@ helper.files('errorhandling', 'mss', function(file) {
|
||||
// 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, function (err) {
|
||||
if (!err) {
|
||||
throw new Error("*** invalid error handling test found: " + basename + ": all error handling tests should throw!");
|
||||
}
|
||||
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));
|
||||
done();
|
||||
});
|
||||
});
|
||||
}).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.readFile(helper.resultFile(file), 'utf8', function(err, data) {
|
||||
if (!err) assert.deepEqual(output, data.substr(0, data.length - 1));
|
||||
done();
|
||||
});
|
||||
// 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
|
@ -1,5 +1,4 @@
|
||||
#world[zoom=5] {
|
||||
text-face-name: 2;
|
||||
line-rasterizer: 'full';
|
||||
text-name: 'foo';
|
||||
}
|
||||
|
@ -1,2 +1 @@
|
||||
invalid_value.mss:2:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
|
||||
invalid_value.mss:3:2 Invalid value for line-rasterizer, the type keyword (options: full, fast) is expected. full (of type string) was given.
|
||||
|
15
test/errorhandling/invaliddimension.mml
Normal file
15
test/errorhandling/invaliddimension.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": [
|
||||
"invaliddimension.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/invaliddimension.mss
Normal file
3
test/errorhandling/invaliddimension.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#world {
|
||||
line-width: 10wifflewaffles;
|
||||
}
|
1
test/errorhandling/invaliddimension.result
Normal file
1
test/errorhandling/invaliddimension.result
Normal file
@ -0,0 +1 @@
|
||||
invaliddimension.mss:2:4 Invalid unit: 'wifflewaffles'
|
1
test/errorhandling/issue119.result
Normal file
1
test/errorhandling/issue119.result
Normal file
@ -0,0 +1 @@
|
||||
issue119.mss:2:2 Map properties are not permitted in other rules
|
1
test/errorhandling/issue124.result
Normal file
1
test/errorhandling/issue124.result
Normal file
@ -0,0 +1 @@
|
||||
issue124.mss:6:0 missing closing `}`
|
@ -1,4 +1,4 @@
|
||||
#t {
|
||||
text-name: invalid;
|
||||
text-face-name: "Dejagnu";
|
||||
text-name: valid;
|
||||
text-face-name: 2;
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
issue297.mss:2:2 Invalid value for text-name, the type expression is expected. invalid (of type keyword) was given.
|
||||
issue297.mss:3:2 Invalid value for text-face-name, the type font is expected. 2 (of type float) was given.
|
1
test/errorhandling/issue_204_a.result
Normal file
1
test/errorhandling/issue_204_a.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_a.mss:3:1 missing opening `{`
|
1
test/errorhandling/issue_204_b.result
Normal file
1
test/errorhandling/issue_204_b.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_b.mss:3:3 missing opening `{`
|
1
test/errorhandling/issue_204_c.result
Normal file
1
test/errorhandling/issue_204_c.result
Normal file
@ -0,0 +1 @@
|
||||
issue_204_c.mss:4:0 missing opening `{`
|
1
test/errorhandling/issue_218.result
Normal file
1
test/errorhandling/issue_218.result
Normal file
@ -0,0 +1 @@
|
||||
issue_218.mss:5:2 missing opening `{`
|
1
test/errorhandling/multi_stylesheets.result
Normal file
1
test/errorhandling/multi_stylesheets.result
Normal file
@ -0,0 +1 @@
|
||||
multi_stylesheets_b.mss:2:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
1
test/errorhandling/multi_stylesheets_b.result
Normal file
1
test/errorhandling/multi_stylesheets_b.result
Normal file
@ -0,0 +1 @@
|
||||
multi_stylesheets_b.mss:2:2 Unrecognized rule: polygonopacity. Did you mean polygon-opacity?
|
1
test/errorhandling/nopound.result
Normal file
1
test/errorhandling/nopound.result
Normal file
@ -0,0 +1 @@
|
||||
nopound.mss:1:0 Invalid code: world {
|
1
test/errorhandling/notenoughargs.result
Normal file
1
test/errorhandling/notenoughargs.result
Normal file
@ -0,0 +1 @@
|
||||
notenoughargs.mss:3:31 incorrect number of arguments for darken(). 2 expected.
|
102
test/filtered.test.js
Normal file
102
test/filtered.test.js
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Test the filtered field.
|
||||
*
|
||||
* When compiled, a rule provides metainformation fields like index, constant...etc
|
||||
* one of this fields is the "filtered field".
|
||||
*
|
||||
* This field gives information about whether a property is filtered or not.
|
||||
*
|
||||
* A property is filtered if it was activated inside a filter. In the following cartocss
|
||||
* code marker-color.filtered will be true because it's inside a population filter.
|
||||
*
|
||||
* #layer {
|
||||
* maker-width: 20;
|
||||
* [population > 100] {
|
||||
* marker-color: red; // this property is filtered
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* "zoom" is a special case, and it only should be considered when its value is not the default.
|
||||
*/
|
||||
var assert = require('assert');
|
||||
var Carto = require('../lib/carto/index.js');
|
||||
var renderer = new Carto.RendererJS({ strict: true });
|
||||
|
||||
describe('property.filtered', function () {
|
||||
it('should be false when the property is not filtered', function () {
|
||||
var style = [
|
||||
'#layer {',
|
||||
' marker-fill: red;',
|
||||
'}'
|
||||
].join('\n');
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
assert(!layers['marker-fill'].filtered);
|
||||
});
|
||||
|
||||
it('should be true when the property is filtered', function () {
|
||||
var style = [
|
||||
'#layer {',
|
||||
' [foo > 30] {',
|
||||
' marker-fill: red;',
|
||||
' }',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
assert(layers['marker-fill'].filtered);
|
||||
});
|
||||
|
||||
it('should be true when the property is filtered at first level', function () {
|
||||
var style = [
|
||||
'#layer [foo > 30] {',
|
||||
' marker-fill: red;',
|
||||
'}`'
|
||||
].join('\n');
|
||||
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
assert(layers['marker-fill'].filtered);
|
||||
});
|
||||
|
||||
it('should be false when the property is not filterd but there is another filtered properties', function () {
|
||||
var style = [
|
||||
'#layer {',
|
||||
' marker-fill: red;',
|
||||
' [bar < 200]{',
|
||||
' marker-allow-overlap: false;',
|
||||
' }',
|
||||
'}`'
|
||||
].join('\n');
|
||||
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
|
||||
assert(!layers['marker-fill'].filtered);
|
||||
assert(layers['marker-allow-overlap'].filtered);
|
||||
});
|
||||
|
||||
it('should be true when the property is filtered and have a default value', function () {
|
||||
var style = [
|
||||
'#layer {',
|
||||
' marker-fill: red;',
|
||||
' [bar < 200]{',
|
||||
' marker-fill: blue;',
|
||||
' }',
|
||||
'}`'
|
||||
].join('\n');
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
|
||||
assert(layers['marker-fill'].filtered);
|
||||
});
|
||||
|
||||
it('should be true when filtering by zoom', function () {
|
||||
var style = [
|
||||
'#layer {',
|
||||
' [zoom < 5]{',
|
||||
' marker-fill: blue;',
|
||||
' }',
|
||||
'}`'
|
||||
].join('\n');
|
||||
var layers = renderer.render(style).layers[0].shader;
|
||||
|
||||
assert(layers['marker-fill'].filtered);
|
||||
});
|
||||
});
|
56
test/index.html
Normal file
56
test/index.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
|
||||
<title>Carto Test</title>
|
||||
<script src='underscore.js' type='text/javascript'></script>
|
||||
<script src='reference.js' type='text/javascript'></script>
|
||||
<script src='../dist/carto.js' type='text/javascript'></script>
|
||||
<script>
|
||||
function test() {
|
||||
var style = document.getElementById('style').innerHTML;
|
||||
var parse_env = {
|
||||
error: function(obj) {
|
||||
document.getElementById('error').innerHTML += JSON.stringify(obj);
|
||||
}
|
||||
};
|
||||
var ruleset = (new carto.Parser(parse_env)).parse(style);
|
||||
var definition = ruleset.toList(parse_env);
|
||||
console.log(definition);
|
||||
console.log(window.a = (new carto.RendererJS()).render(style));
|
||||
|
||||
// for (var i in ruleset.rules) {
|
||||
// for (var j in ruleset.rules[i].rules) {
|
||||
// document.getElementById('output').innerHTML += ruleset.rules[i].rules[j].eval(parse_env).toXML(parse_env) + ' ';
|
||||
// }
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload='test()'>
|
||||
<h3>Style</h3>
|
||||
<pre id='style'>
|
||||
#world {
|
||||
line-width: 2;
|
||||
line-color: #f00;
|
||||
[frame-offset = 1] {
|
||||
line-width: 3;
|
||||
}
|
||||
[frame-offset = 2] {
|
||||
line-width: 3;
|
||||
}
|
||||
}
|
||||
|
||||
#worls[frame-offset = 10] {
|
||||
line-width: 4;
|
||||
}
|
||||
</pre>
|
||||
<h3>Error</h3>
|
||||
<pre id='error'>
|
||||
</pre>
|
||||
<h3>Output</h3>
|
||||
<pre id='output'>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -12,12 +12,12 @@ describe('Quoted', function() {
|
||||
it('should produce normal output', function() {
|
||||
var f = new tree.Quoted("Tom's & \"<quoted>\"");
|
||||
assert.ok(f);
|
||||
assert.equal(f.toString(), "Tom's & \"<quoted>\"");
|
||||
assert.equal(f.toString(), "Tom's & \"<quoted>\"");
|
||||
});
|
||||
it('should produce xml-friendly output', function() {
|
||||
var f = new tree.Quoted("Tom's & \"<quoted>\"");
|
||||
assert.ok(f);
|
||||
assert.equal(f.toString(true), "'Tom's & "<quoted>"'");
|
||||
assert.equal(f.toString(true), "'Tom\\'s & "<quoted>"'");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
1498
test/reference.js
Normal file
1498
test/reference.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,34 +11,30 @@ var helper = require('./support/helper');
|
||||
|
||||
describe('Rendering mss', function() {
|
||||
helper.files('rendering-mss', 'mss', function(file) {
|
||||
it('should render mss ' + path.basename(file) + ' correctly', function(done) {
|
||||
it('should render mss ' + path.basename(file) + ' correctly', function() {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mss = helper.mss(file);
|
||||
new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).renderMSS(mss, function (err, output) {
|
||||
if (err) {
|
||||
if (Array.isArray(err)){
|
||||
err.forEach(carto.writeError);
|
||||
done();
|
||||
} else {
|
||||
throw err;
|
||||
done();
|
||||
}
|
||||
try {
|
||||
var output = new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).renderMSS(mss);
|
||||
} catch(err) {
|
||||
if (Array.isArray(err)){
|
||||
err.forEach(carto.writeError);
|
||||
} else {
|
||||
var expected = file.replace(path.extname(file),'')+'.xml';
|
||||
if (!existsSync(expected)) {
|
||||
fs.writeFileSync(expected,output);
|
||||
}
|
||||
var expected_data = fs.readFileSync(expected).toString();
|
||||
assert.equal(output,expected_data);
|
||||
done();
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
var expected = file.replace(path.extname(file),'')+'.xml';
|
||||
if (!existsSync(expected)) {
|
||||
fs.writeFileSync(expected,output);
|
||||
}
|
||||
var expected_data = fs.readFileSync(expected).toString();
|
||||
assert.equal(output.trim(),expected_data.trim());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
#layer {
|
||||
image-filters:invert();
|
||||
direct-image-filters:invert();
|
||||
image-filters:invert();
|
||||
image-filters-inflate:true;
|
||||
direct-image-filters:invert();
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
<Style name="style" filter-mode="first" image-filters="invert" direct-image-filters="invert">
|
||||
<Style name="style" filter-mode="first" image-filters="invert" image-filters-inflate="true" direct-image-filters="invert">
|
||||
</Style>
|
4
test/rendering-mss/issue_303.mss
Normal file
4
test/rendering-mss/issue_303.mss
Normal file
@ -0,0 +1,4 @@
|
||||
#layer["Hello&Goodbye"="yes"] {
|
||||
text-name: [name];
|
||||
text-face-name: "El&Font Bubble Regular";
|
||||
}
|
6
test/rendering-mss/issue_303.xml
Normal file
6
test/rendering-mss/issue_303.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<Style name="style" filter-mode="first">
|
||||
<Rule>
|
||||
<Filter>([Hello&Goodbye] = 'yes')</Filter>
|
||||
<TextSymbolizer face-name="El&Font Bubble Regular" ><![CDATA[[name]]]></TextSymbolizer>
|
||||
</Rule>
|
||||
</Style>
|
9
test/rendering-mss/issue_315.mss
Normal file
9
test/rendering-mss/issue_315.mss
Normal file
@ -0,0 +1,9 @@
|
||||
#somelayername {
|
||||
[feature = 'highway_motorway'],
|
||||
[feature = 'highway_motorway_link'] {
|
||||
/* code for any motorway */
|
||||
[feature = 'highway_motorway_link'] {
|
||||
/* code specific for links */
|
||||
}
|
||||
}
|
||||
}
|
0
test/rendering-mss/issue_315.xml
Normal file
0
test/rendering-mss/issue_315.xml
Normal file
1
test/rendering-mss/issue_339.mss
Normal file
1
test/rendering-mss/issue_339.mss
Normal file
@ -0,0 +1 @@
|
||||
#poi_label[maki=''] { opacity:.5; }
|
2
test/rendering-mss/issue_339.xml
Normal file
2
test/rendering-mss/issue_339.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<Style name="style" filter-mode="first" opacity="0.5">
|
||||
</Style>
|
4
test/rendering-mss/issue_339b.mss
Normal file
4
test/rendering-mss/issue_339b.mss
Normal file
@ -0,0 +1,4 @@
|
||||
#poi_label[maki=''] {
|
||||
opacity:1.0;
|
||||
comp-op:src-over;
|
||||
}
|
0
test/rendering-mss/issue_339b.xml
Normal file
0
test/rendering-mss/issue_339b.xml
Normal file
3
test/rendering-mss/line-width-zoom.mss
Normal file
3
test/rendering-mss/line-width-zoom.mss
Normal file
@ -0,0 +1,3 @@
|
||||
#layer {
|
||||
line-width:"@zoom";
|
||||
}
|
5
test/rendering-mss/line-width-zoom.xml
Normal file
5
test/rendering-mss/line-width-zoom.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Style name="style" filter-mode="first">
|
||||
<Rule>
|
||||
<LineSymbolizer stroke-width="@zoom" />
|
||||
</Rule>
|
||||
</Style>
|
4
test/rendering-mss/text-face-name-escaping.mss
Normal file
4
test/rendering-mss/text-face-name-escaping.mss
Normal file
@ -0,0 +1,4 @@
|
||||
#layer {
|
||||
text-name: [name];
|
||||
text-face-name: "El&Font Bubble Regular";
|
||||
}
|
5
test/rendering-mss/text-face-name-escaping.xml
Normal file
5
test/rendering-mss/text-face-name-escaping.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Style name="style" filter-mode="first">
|
||||
<Rule>
|
||||
<TextSymbolizer face-name="El&Font Bubble Regular" ><![CDATA[[name]]]></TextSymbolizer>
|
||||
</Rule>
|
||||
</Style>
|
5
test/rendering-mss/variable-quoting-of-enum.mss
Normal file
5
test/rendering-mss/variable-quoting-of-enum.mss
Normal file
@ -0,0 +1,5 @@
|
||||
#world {
|
||||
line-rasterizer: 'fast';
|
||||
// carto should collapse to one
|
||||
line-rasterizer: fast;
|
||||
}
|
5
test/rendering-mss/variable-quoting-of-enum.xml
Normal file
5
test/rendering-mss/variable-quoting-of-enum.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<Style name="style" filter-mode="first">
|
||||
<Rule>
|
||||
<LineSymbolizer rasterizer="fast" />
|
||||
</Rule>
|
||||
</Style>
|
6
test/rendering-mss/zoom-like-field-name.xml
Normal file
6
test/rendering-mss/zoom-like-field-name.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<Style name="style" filter-mode="first">
|
||||
<Rule>
|
||||
<Filter>([zoomy] > 2)</Filter>
|
||||
<PolygonSymbolizer fill="#ff0000" />
|
||||
</Rule>
|
||||
</Style>
|
@ -12,52 +12,52 @@ helper.files('rendering', 'mml', function(file) {
|
||||
var completed = false;
|
||||
var renderResult;
|
||||
var mml = helper.mml(file);
|
||||
new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).render(mml, function (err, output) {
|
||||
if (err) {
|
||||
if (Array.isArray(err)){
|
||||
err.forEach(carto.writeError);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
var output = new carto.Renderer({
|
||||
paths: [ path.dirname(file) ],
|
||||
data_dir: path.join(__dirname, '../data'),
|
||||
local_data_dir: path.join(__dirname, 'rendering'),
|
||||
filename: file
|
||||
}).render(mml);
|
||||
} catch(err) {
|
||||
if (Array.isArray(err)){
|
||||
err.forEach(carto.writeError);
|
||||
return done();
|
||||
} else {
|
||||
var result = helper.resultFile(file);
|
||||
renderResult = output;
|
||||
helper.compareToXMLFile(result, output, function(err,expected_json,actual_json) {
|
||||
completed = true;
|
||||
var actual = file.replace(path.extname(file),'') + '-actual.json';
|
||||
var expected = file.replace(path.extname(file),'') + '-expected.json';
|
||||
if (err) {
|
||||
// disabled since it can break on large diffs
|
||||
/*
|
||||
console.warn(
|
||||
helper.stylize("Failure", 'red') + ': ' +
|
||||
helper.stylize(file, 'underline') +
|
||||
' differs from expected result.');
|
||||
helper.showDifferences(err);
|
||||
throw '';
|
||||
*/
|
||||
fs.writeFileSync(actual,JSON.stringify(actual_json,null,4));
|
||||
fs.writeFileSync(expected,JSON.stringify(expected_json,null,4));
|
||||
throw new Error('failed: xml ' + result + ' in json form does not match expected result:\n' + actual + ' (actual)\n' + expected + ' (expected)');
|
||||
} else {
|
||||
// cleanup any actual renders that no longer fail
|
||||
try {
|
||||
fs.unlinkSync(actual);
|
||||
fs.unlinkSync(expected);
|
||||
} catch (err) {}
|
||||
}
|
||||
done();
|
||||
}, [
|
||||
helper.removeAbsoluteImages,
|
||||
helper.removeAbsoluteDatasources
|
||||
]);
|
||||
return done(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
var result = helper.resultFile(file);
|
||||
renderResult = output;
|
||||
helper.compareToXMLFile(result, output, function(err,expected_json,actual_json) {
|
||||
completed = true;
|
||||
var actual = file.replace(path.extname(file),'') + '-actual.json';
|
||||
var expected = file.replace(path.extname(file),'') + '-expected.json';
|
||||
if (err) {
|
||||
// disabled since it can break on large diffs
|
||||
/*
|
||||
console.warn(
|
||||
helper.stylize("Failure", 'red') + ': ' +
|
||||
helper.stylize(file, 'underline') +
|
||||
' differs from expected result.');
|
||||
helper.showDifferences(err);
|
||||
throw '';
|
||||
*/
|
||||
fs.writeFileSync(actual,JSON.stringify(actual_json,null,4));
|
||||
fs.writeFileSync(expected,JSON.stringify(expected_json,null,4));
|
||||
throw new Error('failed: xml ' + result + ' in json form does not match expected result:\n' + actual + ' (actual)\n' + expected + ' (expected)');
|
||||
} else {
|
||||
// cleanup any actual renders that no longer fail
|
||||
try {
|
||||
fs.unlinkSync(actual);
|
||||
fs.unlinkSync(expected);
|
||||
} catch (err) {}
|
||||
}
|
||||
done();
|
||||
}, [
|
||||
helper.removeAbsoluteImages,
|
||||
helper.removeAbsoluteDatasources
|
||||
]);
|
||||
|
||||
// beforeExit(function() {
|
||||
// if (!completed && renderResult) {
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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" filter-mode="first" >
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" background-color="#ffffff" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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" background-color="#ffffff" >
|
||||
|
||||
|
||||
</Map>
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" buffer-size="256" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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" buffer-size="256">
|
||||
|
||||
<FontSet name="fontset-0">
|
||||
<Font face-name="Georgia Regular"/>
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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="world" filter-mode="first" >
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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="world" filter-mode="first">
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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="just_land" filter-mode="first" >
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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="world" filter-mode="first">
|
||||
|
@ -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.0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs +over" maximum-extent="-20037508.34,-20037508.34,20037508.34,20037508.34">
|
||||
<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" >
|
||||
|
||||
<FontSet name="fontset-0">
|
||||
<Font face-name="Georgia Regular"/>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user