parse the json stylesheet

This commit is contained in:
Konstantin Käfer
2014-03-12 11:23:01 +01:00
parent 60229ed7db
commit 91d42911fb
12 changed files with 301 additions and 674 deletions

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var name = 'style';
var data = JSON.stringify(require('./style'), null, 4).split('\n').map(function(str) {
return JSON.stringify(str + '\n');
});
var length = data.length;
var header = '// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.\n\n';
header += '#ifndef LLMR_STYLE_RESOURCES\n';
header += '#define LLMR_STYLE_RESOURCES\n';
header += '\n';
header += 'namespace llmr {\n';
header += 'namespace resources {\n';
header += '\n';
header += 'extern const unsigned char ' + name + '[];\n';
header += 'extern const unsigned long ' + name + '_size;\n';
header += '\n';
header += '}\n';
header += '}\n';
header += '\n';
header += '#endif\n';
fs.writeFileSync('include/llmr/style/resources.hpp', header);
var code = '// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.\n';
code += '#include <llmr/style/resources.hpp>\n';
code += '\n';
code += 'using namespace llmr;\n';
code += '\n';
code += 'const unsigned char resources::' + name + '[] =\n';
code += ' ' + data.join('\n ') + '';
code += ';\n';
code += 'const unsigned long resources::' + name + '_size = sizeof(resources::' + name + ');\n';
fs.writeFileSync('src/style/resources.cpp', code);

View File

@@ -4,7 +4,7 @@
var fs = require('fs');
var name = 'style';
var data = fs.readFileSync('resources/style.pbf');
var data = JSON.stringify(require('./style'));
var length = data.length;
@@ -29,17 +29,7 @@ code += '#include <llmr/style/resources.hpp>\n';
code += '\n';
code += 'using namespace llmr;\n';
code += '\n';
code += 'const unsigned char resources::' + name + '[] = {\n';
for (var i = 0; i < data.length; i += 16) {
if (i > 0) {
code += ',\n';
}
var part = data.slice(i, i + 16);
code += ' ' + Array.prototype.join.call(part, ', ');
}
code += '\n};\n';
code += 'const unsigned char resources::' + name + '[] = R"JSON(' + data + ')JSON";\n';
code += 'const unsigned long resources::' + name + '_size = sizeof(resources::' + name + ');\n';
fs.writeFileSync('src/style/resources.cpp', code);

View File

@@ -1,238 +0,0 @@
#!/usr/bin/env node
'use strict';
var style = require('./style.js');
var Protobuf = require('./protobuf.js');
var fs = require('fs');
var pbf = new Protobuf();
// enum
var bucket_type = {
fill: 1,
line: 2,
point: 3
};
// enum
var cap_type = {
round: 1,
butt: 2,
square: 3
};
// enum
var join_type = {
miter: 1,
bevel: 2,
round: 3
};
// enum
var property_type = {
'null': 1,
constant: 2,
stops: 3,
linear: 4
};
function createValue(value) {
var pbf = new Protobuf();
if (typeof value === 'string') {
pbf.writeTaggedString(1 /* string_value */, value);
} else if (typeof value === 'boolean') {
pbf.writeTaggedBoolean(7 /* bool_value */, value);
} else {
// TODO:
console.warn('encode value: %s', value);
process.exit();
}
return pbf;
}
function createBucket(bucket, name) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* name */, name);
pbf.writeTaggedVarint(2 /* type */, bucket_type[bucket.type]);
pbf.writeTaggedString(3 /* source_name */, bucket.datasource);
pbf.writeTaggedString(4 /* source_layer */, bucket.layer);
if (bucket.field) {
pbf.writeTaggedString(5 /* source_field */, bucket.field);
var values = Array.isArray(bucket.value) ? bucket.value : [bucket.value];
for (var i = 0; i < values.length; i++) {
pbf.writeMessage(6 /* source_value */, createValue(values[i]));
}
}
if (bucket.cap) {
pbf.writeTaggedVarint(7 /* cap */, cap_type[bucket.cap]);
}
if (bucket.join) {
pbf.writeTaggedVarint(8 /* join */, join_type[bucket.join]);
}
// TODO: font stuff
return pbf;
}
function createStructure(structure) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* name */, structure.name);
if (structure.bucket) {
pbf.writeTaggedString(2 /* bucket_name */, structure.bucket);
} else if (structure.layers) {
for (var i = 0; i < structure.layers.length; i++) {
pbf.writeMessage(3 /* child_layer */, createStructure(structure.layers[i]));
}
}
return pbf;
}
function createProperty(type, values) {
var pbf = new Protobuf();
pbf.writeTaggedVarint(1 /* function */, property_type[type]);
pbf.writePackedFloats(2 /* value */, values.map(function(v) { return +v; }));
return pbf;
}
function convertProperty(orig_values) {
if (Array.isArray(orig_values)) {
var type = orig_values[0];
var values = [];
for (var i = 1; i < orig_values.length; i++) {
if (orig_values[0] === 'stops') {
values.push(orig_values[i].z, orig_values[i].val);
} else {
values.push(orig_values[i]);
}
}
return createProperty(type, values);
} else {
return createProperty('constant', [orig_values]);
}
}
function createFillClass(layer, name) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* layer_name */, name);
if ('antialias' in layer) {
pbf.writeMessage(4 /* antialias */, createProperty('constant', [layer.antialias]));
}
if ('color' in layer) {
var color = layer.color.match(/^#([0-9a-f]{6})$/i);
if (!color) {
console.warn('invalid color');
} else {
pbf.writeTaggedUInt32(5 /* fill_color */, parseInt(color[1] + 'ff', 16));
}
}
if ('stroke' in layer) {
var color = layer.stroke.match(/^#([0-9a-f]{6})$/i);
if (!color) {
console.warn('invalid color');
} else {
pbf.writeTaggedUInt32(6 /* stroke_color */, parseInt(color[1] + 'ff', 16));
}
}
if ('opacity' in layer) {
pbf.writeMessage(7 /* opacity */, convertProperty(layer.opacity));
}
if ('image' in layer) {
pbf.writeTaggedString(8 /* image */, layer.image);
}
return pbf;
}
function createLineClass(layer, name) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* layer_name */, name);
if ('color' in layer) {
var color = layer.color.match(/^#([0-9a-f]{6})$/i);
if (!color) {
console.warn('invalid color');
} else {
pbf.writeTaggedUInt32(3 /* color */, parseInt(color[1] + 'ff', 16));
}
}
if ('width' in layer) {
pbf.writeMessage(4 /* width */, convertProperty(layer.width));
}
if ('opacity' in layer) {
pbf.writeMessage(6 /* opacity */, convertProperty(layer.opacity));
}
return pbf;
}
function createMarkerClass(layer, name) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* layer_name */, name);
if ('color' in layer) {
var color = layer.color.match(/^#([0-9a-f]{6})$/i);
if (!color) {
console.warn('invalid color');
} else {
pbf.writeTaggedUInt32(3 /* color */, parseInt(color[1] + 'ff', 16));
}
}
if ('size' in layer) {
pbf.writeMessage(4 /* size */, convertProperty(layer.size));
}
if ('opacity' in layer) {
pbf.writeMessage(6 /* opacity */, convertProperty(layer.opacity));
}
if ('image' in layer) {
pbf.writeTaggedString(8 /* image */, layer.image);
}
return pbf;
}
function createClass(klass) {
var pbf = new Protobuf();
pbf.writeTaggedString(1 /* name */, klass.name);
for (var name in klass.layers) {
switch (klass.layers[name].type) {
case 'fill': pbf.writeMessage(2 /* fill */, createFillClass(klass.layers[name], name)); break;
case 'line': pbf.writeMessage(3 /* line */, createLineClass(klass.layers[name], name)); break;
case 'marker': pbf.writeMessage(4 /* marker */, createMarkerClass(klass.layers[name], name)); break;
}
}
return pbf;
}
for (var name in style.buckets) {
var bucket = style.buckets[name];
pbf.writeMessage(1 /* bucket */, createBucket(bucket, name));
}
for (var i = 0; i < style.structure.length; i++) {
var structure = style.structure[i];
pbf.writeMessage(2 /* structure */, createStructure(structure));
}
for (var i = 0; i < style.classes.length; i++) {
var klass = style.classes[i];
pbf.writeMessage(3 /* class */, createClass(klass));
}
fs.writeFileSync('resources/style.pbf', pbf.finish());

View File

@@ -171,35 +171,35 @@ module.exports = {
],
},
"alcohol_poi": {
"type": "marker",
"type": "point",
"color": "#cccccc",
"size": 18,
"image": "alcohol-shop",
"opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ]
},
"cafe_poi": {
"type": "marker",
"type": "point",
"color": "#cccccc",
"size": 18,
"image": "cafe",
"opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ]
},
"embassy_poi": {
"type": "marker",
"type": "point",
"color": "#cccccc",
"size": 18,
"image": "embassy",
"opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ]
},
"park_poi": {
"type": "marker",
"type": "point",
"color": "#cccccc",
"size": 18,
"image": "park",
"opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ]
},
"restaurant_poi": {
"type": "marker",
"type": "point",
"color": "#cccccc",
"size": 18,
"image": "restaurant",

View File

@@ -10,6 +10,7 @@ namespace llmr {
class ClassDescription {
public:
BackgroundClass background;
std::map<std::string, FillClass> fill;
std::map<std::string, LineClass> line;
std::map<std::string, PointClass> point;

View File

@@ -112,6 +112,10 @@ struct FillProperties {
std::string image;
};
struct BackgroundClass {
Color fill_color = {{ 0, 0, 0, 0 }};
};
}
#endif

View File

@@ -18,15 +18,15 @@ namespace llmr {
class Sprite;
class Style {
public:
struct exception : std::runtime_error { exception(const char *msg) : std::runtime_error(msg) {} };
public:
Style();
void reset();
void load(const uint8_t *const data, size_t bytes);
// This is commented out because it is not fully implemented yet. For now,
// we keep using the protobuf stylesheet format
// void loadJSON(const std::string& data);
void loadJSON(const uint8_t *const data, size_t bytes);
void cascade(float z);

View File

@@ -20,20 +20,10 @@
'action': ['bin/build-shaders.js'],
},
{
'action_name': 'Convert Style to Protobuf',
'action_name': 'Build Stylesheet',
'inputs': [
'bin/style.js'
],
'outputs': [
'resources/style.pbf'
],
'action': ['bin/convert-style.js'],
},
{
'action_name': 'Update Style Resources',
'inputs': [
'resources/style.pbf'
],
'outputs': [
'include/llmr/style/resources.hpp',
'src/style/resources.cpp'

Binary file not shown.

View File

@@ -30,8 +30,7 @@ void Map::setup(float pixelRatio) {
style.sprite = std::make_shared<Sprite>();
style.sprite->load(kSpriteURL, pixel_ratio);
style.load(resources::style, resources::style_size);
// style.loadJSON((const char *)resources::style, resources::style_size);
style.loadJSON(resources::style, resources::style_size);
}
void Map::loadSprite(const std::string& url) {
@@ -39,7 +38,7 @@ void Map::loadSprite(const std::string& url) {
}
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
style.load(data, bytes);
style.loadJSON(data, bytes);
update();
}

View File

@@ -3,113 +3,5 @@
using namespace llmr;
const unsigned char resources::style[] = {
10, 25, 10, 5, 119, 97, 116, 101, 114, 16, 1, 26, 7, 115, 116, 114,
101, 101, 116, 115, 34, 5, 119, 97, 116, 101, 114, 10, 60, 10, 10, 114,
111, 97, 100, 95, 108, 97, 114, 103, 101, 16, 2, 26, 7, 115, 116, 114,
101, 101, 116, 115, 34, 4, 114, 111, 97, 100, 42, 5, 99, 108, 97, 115,
115, 50, 10, 10, 8, 109, 111, 116, 111, 114, 119, 97, 121, 50, 6, 10,
4, 109, 97, 105, 110, 56, 1, 64, 3, 10, 52, 10, 12, 114, 111, 97,
100, 95, 114, 101, 103, 117, 108, 97, 114, 16, 2, 26, 7, 115, 116, 114,
101, 101, 116, 115, 34, 4, 114, 111, 97, 100, 42, 5, 99, 108, 97, 115,
115, 50, 8, 10, 6, 115, 116, 114, 101, 101, 116, 56, 1, 64, 3, 10,
60, 10, 12, 114, 111, 97, 100, 95, 108, 105, 109, 105, 116, 101, 100, 16,
2, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 4, 114, 111, 97, 100,
42, 5, 99, 108, 97, 115, 115, 50, 16, 10, 14, 115, 116, 114, 101, 101,
116, 95, 108, 105, 109, 105, 116, 101, 100, 56, 1, 64, 3, 10, 41, 10,
4, 112, 97, 114, 107, 16, 1, 26, 7, 115, 116, 114, 101, 101, 116, 115,
34, 7, 108, 97, 110, 100, 117, 115, 101, 42, 5, 99, 108, 97, 115, 115,
50, 6, 10, 4, 112, 97, 114, 107, 10, 41, 10, 4, 119, 111, 111, 100,
16, 1, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 7, 108, 97, 110,
100, 117, 115, 101, 42, 5, 99, 108, 97, 115, 115, 50, 6, 10, 4, 119,
111, 111, 100, 10, 45, 10, 6, 115, 99, 104, 111, 111, 108, 16, 1, 26,
7, 115, 116, 114, 101, 101, 116, 115, 34, 7, 108, 97, 110, 100, 117, 115,
101, 42, 5, 99, 108, 97, 115, 115, 50, 8, 10, 6, 115, 99, 104, 111,
111, 108, 10, 49, 10, 8, 99, 101, 109, 101, 116, 101, 114, 121, 16, 1,
26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 7, 108, 97, 110, 100, 117,
115, 101, 42, 5, 99, 108, 97, 115, 115, 50, 10, 10, 8, 99, 101, 109,
101, 116, 101, 114, 121, 10, 53, 10, 10, 105, 110, 100, 117, 115, 116, 114,
105, 97, 108, 16, 1, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 7,
108, 97, 110, 100, 117, 115, 101, 42, 5, 99, 108, 97, 115, 115, 50, 12,
10, 10, 105, 110, 100, 117, 115, 116, 114, 105, 97, 108, 10, 31, 10, 8,
98, 117, 105, 108, 100, 105, 110, 103, 16, 1, 26, 7, 115, 116, 114, 101,
101, 116, 115, 34, 8, 98, 117, 105, 108, 100, 105, 110, 103, 10, 52, 10,
11, 97, 108, 99, 111, 104, 111, 108, 95, 112, 111, 105, 16, 3, 26, 7,
115, 116, 114, 101, 101, 116, 115, 34, 9, 112, 111, 105, 95, 108, 97, 98,
101, 108, 42, 4, 116, 121, 112, 101, 50, 9, 10, 7, 65, 108, 99, 111,
104, 111, 108, 10, 46, 10, 8, 99, 97, 102, 101, 95, 112, 111, 105, 16,
3, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 9, 112, 111, 105, 95,
108, 97, 98, 101, 108, 42, 4, 116, 121, 112, 101, 50, 6, 10, 4, 67,
97, 102, 101, 10, 52, 10, 11, 101, 109, 98, 97, 115, 115, 121, 95, 112,
111, 105, 16, 3, 26, 7, 115, 116, 114, 101, 101, 116, 115, 34, 9, 112,
111, 105, 95, 108, 97, 98, 101, 108, 42, 4, 116, 121, 112, 101, 50, 9,
10, 7, 69, 109, 98, 97, 115, 115, 121, 10, 46, 10, 8, 112, 97, 114,
107, 95, 112, 111, 105, 16, 3, 26, 7, 115, 116, 114, 101, 101, 116, 115,
34, 9, 112, 111, 105, 95, 108, 97, 98, 101, 108, 42, 4, 116, 121, 112,
101, 50, 6, 10, 4, 80, 97, 114, 107, 10, 58, 10, 14, 114, 101, 115,
116, 97, 117, 114, 97, 110, 116, 95, 112, 111, 105, 16, 3, 26, 7, 115,
116, 114, 101, 101, 116, 115, 34, 9, 112, 111, 105, 95, 108, 97, 98, 101,
108, 42, 4, 116, 121, 112, 101, 50, 12, 10, 10, 82, 101, 115, 116, 97,
117, 114, 97, 110, 116, 18, 12, 10, 4, 112, 97, 114, 107, 18, 4, 112,
97, 114, 107, 18, 12, 10, 4, 119, 111, 111, 100, 18, 4, 119, 111, 111,
100, 18, 14, 10, 5, 119, 97, 116, 101, 114, 18, 5, 119, 97, 116, 101,
114, 18, 20, 10, 8, 98, 117, 105, 108, 100, 105, 110, 103, 18, 8, 98,
117, 105, 108, 100, 105, 110, 103, 18, 28, 10, 12, 114, 111, 97, 100, 95,
108, 105, 109, 105, 116, 101, 100, 18, 12, 114, 111, 97, 100, 95, 108, 105,
109, 105, 116, 101, 100, 18, 28, 10, 12, 114, 111, 97, 100, 95, 114, 101,
103, 117, 108, 97, 114, 18, 12, 114, 111, 97, 100, 95, 114, 101, 103, 117,
108, 97, 114, 18, 24, 10, 10, 114, 111, 97, 100, 95, 108, 97, 114, 103,
101, 18, 10, 114, 111, 97, 100, 95, 108, 97, 114, 103, 101, 18, 26, 10,
11, 97, 108, 99, 111, 104, 111, 108, 95, 112, 111, 105, 18, 11, 97, 108,
99, 111, 104, 111, 108, 95, 112, 111, 105, 18, 20, 10, 8, 99, 97, 102,
101, 95, 112, 111, 105, 18, 8, 99, 97, 102, 101, 95, 112, 111, 105, 18,
26, 10, 11, 101, 109, 98, 97, 115, 115, 121, 95, 112, 111, 105, 18, 11,
101, 109, 98, 97, 115, 115, 121, 95, 112, 111, 105, 18, 20, 10, 8, 112,
97, 114, 107, 95, 112, 111, 105, 18, 8, 112, 97, 114, 107, 95, 112, 111,
105, 18, 32, 10, 14, 114, 101, 115, 116, 97, 117, 114, 97, 110, 116, 95,
112, 111, 105, 18, 14, 114, 101, 115, 116, 97, 117, 114, 97, 110, 116, 95,
112, 111, 105, 26, 160, 5, 10, 7, 100, 101, 102, 97, 117, 108, 116, 18,
27, 10, 4, 112, 97, 114, 107, 34, 8, 8, 2, 18, 4, 0, 0, 128,
63, 45, 255, 159, 223, 200, 66, 4, 112, 97, 114, 107, 18, 31, 10, 4,
119, 111, 111, 100, 34, 8, 8, 2, 18, 4, 0, 0, 128, 63, 45, 255,
102, 170, 51, 58, 8, 8, 2, 18, 4, 205, 204, 204, 61, 18, 39, 10,
5, 119, 97, 116, 101, 114, 34, 8, 8, 2, 18, 4, 0, 0, 128, 63,
45, 255, 233, 224, 186, 53, 255, 230, 182, 115, 66, 10, 119, 97, 116, 101,
114, 95, 101, 116, 115, 121, 18, 51, 10, 8, 98, 117, 105, 108, 100, 105,
110, 103, 34, 8, 8, 2, 18, 4, 0, 0, 128, 63, 45, 255, 0, 0,
0, 58, 24, 8, 4, 18, 20, 0, 0, 80, 65, 0, 0, 0, 0, 205,
204, 204, 61, 0, 0, 0, 0, 205, 204, 204, 61, 26, 41, 10, 12, 114,
111, 97, 100, 95, 108, 105, 109, 105, 116, 101, 100, 29, 255, 187, 187, 187,
34, 20, 8, 3, 18, 16, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0,
240, 65, 0, 0, 128, 63, 26, 65, 10, 12, 114, 111, 97, 100, 95, 114,
101, 103, 117, 108, 97, 114, 29, 255, 153, 153, 153, 34, 44, 8, 3, 18,
40, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 80, 65, 0, 0, 0,
63, 0, 0, 128, 65, 0, 0, 0, 64, 0, 0, 160, 65, 0, 0, 0,
66, 0, 0, 240, 65, 0, 0, 0, 66, 26, 71, 10, 10, 114, 111, 97,
100, 95, 108, 97, 114, 103, 101, 29, 255, 102, 102, 102, 34, 52, 8, 3,
18, 48, 0, 0, 0, 0, 0, 0, 0, 63, 0, 0, 48, 65, 0, 0,
0, 63, 0, 0, 80, 65, 0, 0, 128, 63, 0, 0, 128, 65, 0, 0,
128, 64, 0, 0, 160, 65, 0, 0, 128, 66, 0, 0, 240, 65, 0, 0,
128, 66, 34, 68, 10, 11, 97, 108, 99, 111, 104, 111, 108, 95, 112, 111,
105, 29, 255, 204, 204, 204, 34, 8, 8, 2, 18, 4, 0, 0, 144, 65,
50, 24, 8, 4, 18, 20, 0, 0, 112, 65, 0, 0, 0, 0, 0, 0,
128, 63, 0, 0, 0, 0, 0, 0, 64, 63, 66, 12, 97, 108, 99, 111,
104, 111, 108, 45, 115, 104, 111, 112, 34, 57, 10, 8, 99, 97, 102, 101,
95, 112, 111, 105, 29, 255, 204, 204, 204, 34, 8, 8, 2, 18, 4, 0,
0, 144, 65, 50, 24, 8, 4, 18, 20, 0, 0, 112, 65, 0, 0, 0,
0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 64, 63, 66, 4, 99,
97, 102, 101, 34, 63, 10, 11, 101, 109, 98, 97, 115, 115, 121, 95, 112,
111, 105, 29, 255, 204, 204, 204, 34, 8, 8, 2, 18, 4, 0, 0, 144,
65, 50, 24, 8, 4, 18, 20, 0, 0, 112, 65, 0, 0, 0, 0, 0,
0, 128, 63, 0, 0, 0, 0, 0, 0, 64, 63, 66, 7, 101, 109, 98,
97, 115, 115, 121, 34, 57, 10, 8, 112, 97, 114, 107, 95, 112, 111, 105,
29, 255, 204, 204, 204, 34, 8, 8, 2, 18, 4, 0, 0, 144, 65, 50,
24, 8, 4, 18, 20, 0, 0, 112, 65, 0, 0, 0, 0, 0, 0, 128,
63, 0, 0, 0, 0, 0, 0, 64, 63, 66, 4, 112, 97, 114, 107, 34,
69, 10, 14, 114, 101, 115, 116, 97, 117, 114, 97, 110, 116, 95, 112, 111,
105, 29, 255, 204, 204, 204, 34, 8, 8, 2, 18, 4, 0, 0, 144, 65,
50, 24, 8, 4, 18, 20, 0, 0, 112, 65, 0, 0, 0, 0, 0, 0,
128, 63, 0, 0, 0, 0, 0, 0, 64, 63, 66, 10, 114, 101, 115, 116,
97, 117, 114, 97, 110, 116
};
const unsigned char resources::style[] = R"JSON({"buckets":{"water":{"datasource":"streets","layer":"water","type":"fill"},"road_large":{"datasource":"streets","layer":"road","field":"class","value":["motorway","main"],"type":"line","cap":"round","join":"round"},"road_regular":{"datasource":"streets","layer":"road","field":"class","value":"street","type":"line","cap":"round","join":"round"},"road_limited":{"datasource":"streets","layer":"road","field":"class","value":"street_limited","type":"line","cap":"round","join":"round"},"park":{"datasource":"streets","layer":"landuse","field":"class","value":"park","type":"fill"},"wood":{"datasource":"streets","layer":"landuse","field":"class","value":"wood","type":"fill"},"school":{"datasource":"streets","layer":"landuse","field":"class","value":"school","type":"fill"},"cemetery":{"datasource":"streets","layer":"landuse","field":"class","value":"cemetery","type":"fill"},"industrial":{"datasource":"streets","layer":"landuse","field":"class","value":"industrial","type":"fill"},"building":{"datasource":"streets","layer":"building","type":"fill"},"alcohol_poi":{"datasource":"streets","layer":"poi_label","field":"type","value":["Alcohol"],"type":"point"},"cafe_poi":{"datasource":"streets","layer":"poi_label","field":"type","value":["Cafe"],"type":"point"},"embassy_poi":{"datasource":"streets","layer":"poi_label","field":"type","value":["Embassy"],"type":"point"},"park_poi":{"datasource":"streets","layer":"poi_label","field":"type","value":["Park"],"type":"point"},"restaurant_poi":{"datasource":"streets","layer":"poi_label","field":"type","value":["Restaurant"],"type":"point"}},"sprite":"img/maki-sprite","structure":[{"name":"park","bucket":"park"},{"name":"wood","bucket":"wood"},{"name":"water","bucket":"water"},{"name":"building","bucket":"building"},{"name":"road_limited","bucket":"road_limited"},{"name":"road_regular","bucket":"road_regular"},{"name":"road_large","bucket":"road_large"},{"name":"alcohol_poi","bucket":"alcohol_poi"},{"name":"cafe_poi","bucket":"cafe_poi"},{"name":"embassy_poi","bucket":"embassy_poi"},{"name":"park_poi","bucket":"park_poi"},{"name":"restaurant_poi","bucket":"restaurant_poi"}],"classes":[{"name":"default","layers":{"background":{"type":"background","color":"#FFFFFF"},"park":{"type":"fill","color":"#c8df9f","antialias":true,"image":"park"},"wood":{"type":"fill","color":"#33AA66","antialias":true,"opacity":0.1},"water":{"type":"fill","color":"#bae0e9","stroke":"#73b6e6","antialias":true,"image":"water_etsy"},"building":{"type":"fill","color":"#000000","antialias":true,"opacity":["linear",13,0,0.1,0,0.1]},"road_limited":{"type":"line","color":"#BBBBBB","width":["stops",{"z":0,"val":1},{"z":30,"val":1}]},"road_regular":{"type":"line","color":"#999999","width":["stops",{"z":0,"val":0.5},{"z":13,"val":0.5},{"z":16,"val":2},{"z":20,"val":32},{"z":30,"val":32}]},"road_large":{"type":"line","color":"#666666","width":["stops",{"z":0,"val":0.5},{"z":11,"val":0.5},{"z":13,"val":1},{"z":16,"val":4},{"z":20,"val":64},{"z":30,"val":64}]},"alcohol_poi":{"type":"point","color":"#cccccc","size":18,"image":"alcohol-shop","opacity":["linear",15,0,1,0,0.75]},"cafe_poi":{"type":"point","color":"#cccccc","size":18,"image":"cafe","opacity":["linear",15,0,1,0,0.75]},"embassy_poi":{"type":"point","color":"#cccccc","size":18,"image":"embassy","opacity":["linear",15,0,1,0,0.75]},"park_poi":{"type":"point","color":"#cccccc","size":18,"image":"park","opacity":["linear",15,0,1,0,0.75]},"restaurant_poi":{"type":"point","color":"#cccccc","size":18,"image":"restaurant","opacity":["linear",15,0,1,0,0.75]}}}]})JSON";
const unsigned long resources::style_size = sizeof(resources::style);

View File

@@ -1,6 +1,7 @@
#include <llmr/style/style.hpp>
#include <llmr/util/color.hpp>
// #include <rapidjson/document.h>
#include <rapidjson/document.h>
using namespace llmr;
@@ -15,215 +16,6 @@ void Style::reset() {
computed.points.clear();
}
void Style::load(const uint8_t *const data, size_t bytes) {
pbf style(data, bytes);
while (style.next()) {
if (style.tag == 1) { // bucket
buckets.insert(parseBucket(style.message()));
} else if (style.tag == 2) { // structure
layers.push_back(parseLayer(style.message()));
} else if (style.tag == 3) { // class
classes.insert(parseClass(style.message()));
} else {
style.skip();
}
}
}
std::pair<std::string, BucketDescription> Style::parseBucket(pbf data) {
BucketDescription bucket;
std::string name;
while (data.next()) {
if (data.tag == 1) { // name
name = data.string();
} else if (data.tag == 2) { // type
bucket.type = (BucketType)data.varint();
} else if (data.tag == 3) { // source_name
bucket.source_name = data.string();
} else if (data.tag == 4) { // source_layer
bucket.source_layer = data.string();
} else if (data.tag == 5) { // source_field
bucket.source_field = data.string();
} else if (data.tag == 6) { // source_value
bucket.source_value.emplace_back(parseValue(data.message()));
} else if (data.tag == 7) { // cap
bucket.geometry.cap = (CapType)data.varint();
} else if (data.tag == 8) { // join
bucket.geometry.join = (JoinType)data.varint();
} else if (data.tag == 9) { // font
bucket.geometry.font = data.string();
} else if (data.tag == 10) { // font_size
bucket.geometry.font_size = data.float32();
} else {
data.skip();
}
}
return { name, std::forward<BucketDescription>(bucket) };
}
LayerDescription Style::parseLayer(pbf data) {
LayerDescription layer;
while (data.next()) {
if (data.tag == 1) { // name
layer.name = data.string();
} else if (data.tag == 2) { // bucket_name
layer.bucket_name = data.string();
} else if (data.tag == 3) { // child_layer
layer.child_layer.emplace_back(parseLayer(data.message()));
} else {
data.skip();
}
}
return layer;
}
std::pair<std::string, ClassDescription> Style::parseClass(pbf data) {
ClassDescription klass;
std::string name;
while (data.next()) {
if (data.tag == 1) { // name
name = data.string();
} else if (data.tag == 2) { // fill style
klass.fill.insert(parseFillClass(data.message()));
} else if (data.tag == 3) { // line style
klass.line.insert(parseLineClass(data.message()));
} else if (data.tag == 4) { // point style
klass.point.insert(parsePointClass(data.message()));
} else {
data.skip();
}
}
return { name, klass };
}
std::pair<std::string, FillClass> Style::parseFillClass(pbf data) {
FillClass fill;
std::string name;
while (data.next()) {
if (data.tag == 1) { // name
name = data.string();
} else if (data.tag == 2) { // hidden
fill.hidden = parseProperty<bool>(data.message());
} else if (data.tag == 3) { // winding
fill.winding = (Winding)data.varint();
} else if (data.tag == 4) { // antialias
fill.antialias = parseProperty<bool>(data.message());
} else if (data.tag == 5) { // fill_color
fill.fill_color = parseColor(data);
if (fill.stroke_color[3] == std::numeric_limits<float>::infinity()) {
fill.stroke_color = fill.fill_color;
}
} else if (data.tag == 6) { // stroke_color
fill.stroke_color = parseColor(data);
} else if (data.tag == 7) { // opacity
fill.opacity = parseProperty<float>(data.message());
} else if (data.tag == 8) { // image
fill.image = data.string();
} else {
data.skip();
}
}
return { name, fill };
}
std::pair<std::string, LineClass> Style::parseLineClass(pbf data) {
LineClass stroke;
std::string name;
while (data.next()) {
if (data.tag == 1) { // name
name = data.string();
} else if (data.tag == 2) { // hidden
stroke.hidden = parseProperty<bool>(data.message());
} else if (data.tag == 3) { // color
stroke.color = parseColor(data);
} else if (data.tag == 4) { // width
stroke.width = parseProperty<float>(data.message());
} else if (data.tag == 5) { // offset
stroke.offset = parseProperty<float>(data.message());
} else if (data.tag == 6) { // opacity
stroke.opacity = parseProperty<float>(data.message());
} else {
data.skip();
}
}
return { name, stroke };
}
std::pair<std::string, PointClass> Style::parsePointClass(pbf data) {
PointClass point;
std::string name;
while (data.next()) {
if (data.tag == 1) { // name
name = data.string();
} else if (data.tag == 2) { // hidden
point.hidden = parseProperty<bool>(data.message());
} else if (data.tag == 3) { // color
point.color = parseColor(data);
} else if (data.tag == 4) { // size
point.size = parseProperty<float>(data.message());
} else if (data.tag == 6) { // opacity
point.opacity = parseProperty<float>(data.message());
} else if (data.tag == 8) { // image
point.image = data.string();
} else {
data.skip();
}
}
return { name, point };
}
Color Style::parseColor(pbf& data) {
uint32_t rgba = data.fixed<uint32_t, 4>();
return {{
(float)((rgba >> 24) & 0xFF) / 0xFF,
(float)((rgba >> 16) & 0xFF) / 0xFF,
(float)((rgba >> 8) & 0xFF) / 0xFF,
(float)((rgba >> 0) & 0xFF) / 0xFF
}
};
}
template <typename T> FunctionProperty<T> Style::parseProperty(pbf data) {
FunctionProperty<T> property;
while (data.next()) {
if (data.tag == 1) { // function
switch ((Property)data.varint()) {
case Property::Null: property.function = &functions::null; break;
case Property::Constant: property.function = &functions::constant; break;
case Property::Stops: property.function = &functions::stops; break;
case Property::Linear: property.function = &functions::linear; break;
default: property.function = &functions::null; break;
}
} else if (data.tag == 2) { // value
// read a packed float32
pbf floats = data.message();
while (floats) {
property.values.push_back(floats.float32());
}
} else {
data.skip();
}
}
return property;
}
void Style::cascade(float z) {
reset();
@@ -285,7 +77,7 @@ void Style::cascade(float z) {
}
}
/*
void loadBucketsJSON(const rapidjson::Value& value, std::map<std::string, BucketDescription>& buckets);
BucketDescription loadBucketJSON(const rapidjson::Value& value);
@@ -296,11 +88,26 @@ std::pair<std::string, ClassDescription> loadClassDescriptionJSON(const rapidjso
void loadClassJSON(const std::string& name, const rapidjson::Value& value, ClassDescription& class_desc);
FillClass loadFillClassJSON(const rapidjson::Value& value);
LineClass loadLineClassJSON(const rapidjson::Value& value);
PointClass loadPointClassJSON(const rapidjson::Value& value);
BackgroundClass loadBackgroundClassJSON(const rapidjson::Value& value);
Value loadValueJSON(const rapidjson::Value& value);
void Style::loadJSON(const std::string& data) {
void Style::loadJSON(const uint8_t *const data, size_t bytes) {
rapidjson::Document document;
document.Parse<0>(data.c_str());
if (bytes <= 0) {
return;
}
if (data[bytes - 1] != 0) {
throw exception("style JSON string is not 0-terminated");
}
document.Parse<0>((const char *const)data);
buckets.clear();
layers.clear();
classes.clear();
if (document.IsObject()) {
if (document.HasMember("buckets")) {
@@ -315,7 +122,7 @@ void Style::loadJSON(const std::string& data) {
loadClassesJSON(document["classes"], classes);
}
} else {
fprintf(stderr, "stylesheet root must be an object\n");
throw Style::exception("stylesheet root must be an object");
}
}
@@ -323,10 +130,10 @@ void loadBucketsJSON(const rapidjson::Value& value, std::map<std::string, Bucket
if (value.IsObject()) {
rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
for (; itr != value.MemberEnd(); ++itr) {
buckets.emplace(itr->name.GetString(), loadBucketJSON(itr->value));
buckets.emplace(itr->name.GetString(), std::forward<BucketDescription>(loadBucketJSON(itr->value)));
}
} else {
fprintf(stderr, "buckets must be an object\n");
throw Style::exception("buckets must be an object");
}
}
@@ -342,25 +149,25 @@ BucketDescription loadBucketJSON(const rapidjson::Value& value) {
if (value.IsString()) {
bucket.type = bucketType({ value.GetString(), value.GetStringLength() });
} else {
fprintf(stderr, "bucket type must be a string\n");
throw Style::exception("bucket type must be a string");
}
} else if (name == "datasource") {
if (value.IsString()) {
bucket.source_name = { value.GetString(), value.GetStringLength() };
} else {
fprintf(stderr, "datasource name must be a string\n");
throw Style::exception("datasource name must be a string");
}
} else if (name == "layer") {
if (value.IsString()) {
bucket.source_layer = { value.GetString(), value.GetStringLength() };
} else {
fprintf(stderr, "layer name must be a string\n");
throw Style::exception("layer name must be a string");
}
} else if (name == "field") {
if (value.IsString()) {
bucket.source_field = { value.GetString(), value.GetStringLength() };
} else {
fprintf(stderr, "field name must be a string\n");
throw Style::exception("field name must be a string");
}
} else if (name == "value") {
if (value.IsArray()) {
@@ -374,25 +181,37 @@ BucketDescription loadBucketJSON(const rapidjson::Value& value) {
if (value.IsString()) {
bucket.geometry.cap = capType({ value.GetString(), value.GetStringLength() });
} else {
fprintf(stderr, "cap type must be a string\n");
throw Style::exception("cap type must be a string");
}
} else if (name == "join") {
if (value.IsString()) {
bucket.geometry.join = joinType({ value.GetString(), value.GetStringLength() });
} else {
fprintf(stderr, "join type must be a string\n");
throw Style::exception("join type must be a string");
}
} else if (name == "font") {
if (value.IsString()) {
bucket.geometry.font = { value.GetString(), value.GetStringLength() };
} else {
fprintf(stderr, "font stack must be a string\n");
throw Style::exception("font stack must be a string");
}
} else if (name == "fontSize") {
if (value.IsNumber()) {
bucket.geometry.font_size = value.GetDouble();
} else {
fprintf(stderr, "font size must be a number\n");
throw Style::exception("font size must be a number");
}
} else if (name == "miterLimit") {
if (value.IsNumber()) {
bucket.geometry.miter_limit = value.GetDouble();
} else {
throw Style::exception("miter limit must be a number");
}
} else if (name == "roundLimit") {
if (value.IsNumber()) {
bucket.geometry.round_limit = value.GetDouble();
} else {
throw Style::exception("round limit must be a number");
}
}
}
@@ -400,16 +219,14 @@ BucketDescription loadBucketJSON(const rapidjson::Value& value) {
return bucket;
}
void loadLayersJSON(const rapidjson::Value& value, std::vector<LayerDescription>& layers) {
if (value.IsArray()) {
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
layers.push_back(loadLayerJSON(value[i]));
}
} else {
fprintf(stderr, "structure must be an array\n");
throw Style::exception("structure must be an array");
}
}
LayerDescription loadLayerJSON(const rapidjson::Value& value) {
@@ -421,10 +238,10 @@ LayerDescription loadLayerJSON(const rapidjson::Value& value) {
if (name.IsString()) {
layer.name = { name.GetString(), name.GetStringLength() };
} else {
fprintf(stderr, "structure element name must be a string\n");
throw Style::exception("structure element name must be a string");
}
} else {
fprintf(stderr, "structure element must have a name\n");
throw Style::exception("structure element must have a name");
}
if (value.HasMember("bucket")) {
@@ -432,28 +249,27 @@ LayerDescription loadLayerJSON(const rapidjson::Value& value) {
if (bucket.IsString()) {
layer.bucket_name = { bucket.GetString(), bucket.GetStringLength() };
} else {
fprintf(stderr, "structure element bucket must be a string\n");
throw Style::exception("structure element bucket must be a string");
}
} else if (value.HasMember("layers")) {
loadLayersJSON(value["layers"], layer.child_layer);
} else {
fprintf(stderr, "structure element must have either a bucket name or child layers\n");
throw Style::exception("structure element must have either a bucket name or child layers");
}
} else {
fprintf(stderr, "structure element must be an object\n");
throw Style::exception("structure element must be an object");
}
return layer;
}
void loadClassesJSON(const rapidjson::Value& value, std::map<std::string, ClassDescription>& classes) {
if (value.IsArray()) {
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
classes.insert(loadClassDescriptionJSON(value[i]));
classes.insert(std::forward<std::pair<std::string, ClassDescription>>(loadClassDescriptionJSON(value[i])));
}
} else {
fprintf(stderr, "classes must be an array\n");
throw Style::exception("classes must be an array");
}
}
@@ -467,10 +283,10 @@ std::pair<std::string, ClassDescription> loadClassDescriptionJSON(const rapidjso
if (name.IsString()) {
klass_name = { name.GetString(), name.GetStringLength() };
} else {
fprintf(stderr, "class name must be a string\n");
throw Style::exception("class name must be a string");
}
} else {
fprintf(stderr, "class must have a name\n");
throw Style::exception("class must have a name");
}
if (value.HasMember("layers")) {
@@ -485,14 +301,14 @@ std::pair<std::string, ClassDescription> loadClassDescriptionJSON(const rapidjso
loadClassJSON(name, itr->value, klass);
}
} else {
fprintf(stderr, "class layer styles must be an object\n");
throw Style::exception("class layer styles must be an object");
}
} else {
fprintf(stderr, "class must have layer styles\n");
throw Style::exception("class must have layer styles");
}
}
return { klass_name, klass };
return { klass_name, std::forward<ClassDescription>(klass) };
}
void loadClassJSON(const std::string& name, const rapidjson::Value& value, ClassDescription& class_desc) {
@@ -502,40 +318,255 @@ void loadClassJSON(const std::string& name, const rapidjson::Value& value, Class
if (type.IsString()) {
std::string type_name = { type.GetString(), type.GetStringLength() };
if (type_name == "fill") {
class_desc.fill.insert({ type_name, loadFillClassJSON(value) });
class_desc.fill.insert({ name, std::forward<FillClass>(loadFillClassJSON(value)) });
} else if (type_name == "line") {
class_desc.line.insert({ type_name, loadLineClassJSON(value) });
class_desc.line.insert({ name, std::forward<LineClass>(loadLineClassJSON(value)) });
} else if (type_name == "point") {
class_desc.point.insert({ name, std::forward<PointClass>(loadPointClassJSON(value)) });
} else if (type_name == "background") {
class_desc.background = loadBackgroundClassJSON(value);
} else {
// TODO:
throw Style::exception("unkonwn class type name");
}
} else {
fprintf(stderr, "style class type must be a string\n");
throw Style::exception("style class type must be a string");
}
} else {
fprintf(stderr, "style class must specify a type\n");
throw Style::exception("style class must specify a type");
}
} else {
fprintf(stderr, "style class must be an object\n");
throw Style::exception("style class must be an object");
}
}
bool parseBoolean(const rapidjson::Value& value) {
if (!value.IsBool()) {
throw Style::exception("boolean value must be a boolean");
}
return value.GetBool();
}
std::string parseString(const rapidjson::Value& value) {
if (!value.IsString()) {
throw Style::exception("string value must be a string");
}
return { value.GetString(), value.GetStringLength() };
}
Color parseColor(const rapidjson::Value& value) {
if (!value.IsString()) {
throw Style::exception("color value must be a string");
}
llmr::util::CSSColor css_color = llmr::util::parseCSSColor({
value.GetString(),
value.GetStringLength()
});
return {{
(float)css_color.r / 255,
(float)css_color.g / 255,
(float)css_color.b / 255,
css_color.a
}
};
}
template <typename T>
typename FunctionProperty<T>::fn parseFunctionType(const rapidjson::Value& type) {
if (type.IsString()) {
std::string t { type.GetString(), type.GetStringLength() };
if (t == "constant") {
return &functions::constant;
} else if (t == "linear") {
return &functions::linear;
} else if (t == "stops") {
return &functions::stops;
} else {
throw Style::exception("unknown function type");
}
} else {
throw Style::exception("function type must be a string");
}
}
FunctionProperty<float> parseFloatFunction(const rapidjson::Value& value) {
FunctionProperty<float> property;
if (value.IsArray()) {
if (value.Size() < 1) {
throw Style::exception("value function does not have arguments");
}
property.function = parseFunctionType<float>(value[(rapidjson::SizeType)0]);
for (rapidjson::SizeType i = 1; i < value.Size(); ++i) {
const rapidjson::Value& stop = value[i];
if (stop.IsObject()) {
if (!stop.HasMember("z")) {
throw Style::exception("stop must have zoom level specification");
}
const rapidjson::Value& z = stop["z"];
if (!z.IsNumber()) {
throw Style::exception("zoom level in stops must be a number");
}
property.values.push_back(z.GetDouble());
if (!stop.HasMember("val")) {
throw Style::exception("stop must have value specification");
}
const rapidjson::Value& val = stop["val"];
if (!val.IsNumber()) {
throw Style::exception("value in stops must be a number");
}
property.values.push_back(val.GetDouble());
} else if (stop.IsNumber()) {
property.values.push_back(stop.GetDouble());
} else {
throw Style::exception("stop must be a number");
}
}
} else if (value.IsNumber()) {
property.function = &functions::constant;
property.values.push_back(value.GetDouble());
}
return property;
}
FunctionProperty<bool> parseBoolFunction(const rapidjson::Value& value) {
FunctionProperty<bool> property;
if (value.IsArray()) {
if (value.Size() < 1) {
throw Style::exception("value function does not have arguments");
}
property.function = parseFunctionType<bool>(value[(rapidjson::SizeType)0]);
for (rapidjson::SizeType i = 1; i < value.Size(); ++i) {
const rapidjson::Value& stop = value[i];
if (stop.IsObject()) {
if (!stop.HasMember("z")) {
throw Style::exception("stop must have zoom level specification");
}
const rapidjson::Value& z = stop["z"];
if (!z.IsBool()) {
throw Style::exception("zoom level in stops must be a number");
}
property.values.push_back(z.GetBool());
if (!stop.HasMember("val")) {
throw Style::exception("stop must have value specification");
}
const rapidjson::Value& val = stop["val"];
if (!val.IsBool()) {
throw Style::exception("value in stops must be a number");
}
property.values.push_back(val.GetBool());
} else if (stop.IsBool()) {
property.values.push_back(stop.GetBool());
} else {
throw Style::exception("stop must be a number");
}
}
} else if (value.IsBool()) {
property.function = &functions::constant;
property.values.push_back(value.GetBool());
}
return property;
}
FillClass loadFillClassJSON(const rapidjson::Value& value) {
FillClass klass;
if (value.HasMember("hidden")) {
const rapidjson::Value& hidden = value["hidden"];
// TODO...
klass.hidden = parseBoolFunction(value["hidden"]);
}
if (value.HasMember("color")) {
klass.fill_color = parseColor(value["color"]);
}
if (value.HasMember("stroke")) {
klass.stroke_color = parseColor(value["stroke"]);
} else {
klass.stroke_color = klass.fill_color;
}
if (value.HasMember("antialias")) {
klass.antialias = parseBoolean(value["antialias"]);
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
}
if (value.HasMember("opacity")) {
klass.opacity = parseFloatFunction(value["opacity"]);
}
return klass;
}
LineClass loadLineClassJSON(const rapidjson::Value& value) {
FillClass klass;
LineClass klass;
if (value.HasMember("hidden")) {
klass.hidden = parseBoolFunction(value["hidden"]);
}
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
}
if (value.HasMember("width")) {
klass.width = parseFloatFunction(value["width"]);
}
if (value.HasMember("opacity")) {
klass.opacity = parseFloatFunction(value["opacity"]);
}
return klass;
}
PointClass loadPointClassJSON(const rapidjson::Value& value) {
PointClass klass;
if (value.HasMember("hidden")) {
klass.hidden = parseBoolFunction(value["hidden"]);
}
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
}
if (value.HasMember("opacity")) {
klass.opacity = parseFloatFunction(value["opacity"]);
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
}
if (value.HasMember("size")) {
klass.size = parseFloatFunction(value["size"]);
}
return klass;
}
BackgroundClass loadBackgroundClassJSON(const rapidjson::Value& value) {
BackgroundClass klass;
if (value.HasMember("color")) {
klass.fill_color = parseColor(value["color"]);
}
return klass;
}
Value loadValueJSON(const rapidjson::Value& value) {
switch (value.GetType()) {
@@ -556,9 +587,7 @@ Value loadValueJSON(const rapidjson::Value& value) {
case rapidjson::kObjectType:
case rapidjson::kArrayType:
fprintf(stderr, "value cannot be an object or array\n");
throw Style::exception("value cannot be an object or array");
return false;
}
}
*/