Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/sources/mvt.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import DataSource, {NetworkTileSource} from './data_source';
import Geo from '../utils/geo';
import log from '../utils/log';

import Pbf from 'pbf';
import {VectorTile, VectorTileFeature} from '@mapbox/vector-tile';

const PARSE_JSON_TYPE = {
NONE: 0,
ALL: 1,
SOME: 2
};

const PARSE_JSON_TEST = ['{', '[']; // one-time allocated array/strings

/**
Mapbox Vector Tile format
@class MVTSource
Expand All @@ -13,6 +22,27 @@ export class MVTSource extends NetworkTileSource {
constructor (source, sources) {
super(source, sources);
this.response_type = 'arraybuffer'; // binary data

// Optionally parse some or all properties from JSON strings
if (source.parse_json === true) {
// try to parse all properties (least efficient)
this.parse_json_type = PARSE_JSON_TYPE.ALL;
}
else if (Array.isArray(source.parse_json)) {
// try to parse a specific list of property names (more efficient)
this.parse_json_type = PARSE_JSON_TYPE.SOME;
this.parse_json_prop_list = source.parse_json;
}
else {
if (source.parse_json != null) {
let msg = `Data source '${this.name}': 'parse_json' parameter should be 'true', or an array of ` +
`property names (was '${JSON.stringify(source.parse_json)}')`;
log({ level: 'warn', once: true }, msg);
}

// skip parsing entirely (default behavior)
this.parse_json_type = PARSE_JSON_TYPE.NONE;
}
}

parseSourceData (tile, source, response) {
Expand Down Expand Up @@ -55,6 +85,8 @@ export class MVTSource extends NetworkTileSource {
properties: feature.properties
};

this.parseJSONProperties(feature_geojson);

var geometry = feature_geojson.geometry;
var coordinates = feature.loadGeometry();
for (var r=0; r < coordinates.length; r++) {
Expand Down Expand Up @@ -92,6 +124,36 @@ export class MVTSource extends NetworkTileSource {
return layers;
}

// Optionally parse some or all feature properties from JSON strings
parseJSONProperties (feature) {
if (this.parse_json_type !== PARSE_JSON_TYPE.NONE) {
const props = feature.properties;

// if specified, check list of explicit properties to parse
if (this.parse_json_type === PARSE_JSON_TYPE.SOME) {
this.parse_json_prop_list.forEach(p => {
try {
props[p] = JSON.parse(props[p]);
} catch (e) {
// continue with original value if couldn't parse as JSON
}
});
}
// otherwise try to parse all properties
else {
for (const p in props) {
// check if this property looks like JSON, and parse if so
if (PARSE_JSON_TEST.indexOf(props[p][0]) > -1) {
try {
props[p] = JSON.parse(props[p]);
} catch (e) {
// continue with original value if couldn't parse as JSON
}
}
}
}
}
}
}

// Decode multipolygons, which are encoded as a single set of rings
Expand Down
50 changes: 45 additions & 5 deletions src/styles/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@ function maybeQuote(value) {

function lookUp(key) {
if (key[0] === '$') {
// keys prefixed with $ are special properties in the context object (not feature properties)
return 'context[\'' + key.substring(1) + '\']';
}
else if (key.indexOf('.') > -1) {
if (key.indexOf('\\.') === -1) { // no escaped dot notation
// un-escaped dot notation indicates a nested feature property
return `context.feature.properties${key.split('.').map(k => '[\'' + k + '\']').join('')}`;
}
else { // mixed escaped/unescaped dot notation
// escaped dot notation will be interpreted as a single-level feature property with dots in the name
// this splits on unescaped dots, which requires a temporary swap of escaped and unescaped dots
let keys = key
.replace(/\\\./g, '__TANGRAM_DELIMITER__')
.split('.')
.map(s => s.replace(/__TANGRAM_DELIMITER__/g, '.'));
return `context.feature.properties${keys.map(k => '[\'' + k + '\']').join('')}`;
}
}
// single-level feature property
return 'context.feature.properties[\'' + key + '\']';
}

Expand Down Expand Up @@ -53,23 +70,43 @@ function propertyMatchesBoolean(key, value) {
return wrap(lookUp(key) + (value ? ' != ' : ' == ') + 'null');
}

function rangeMatch(key, values, options) {
function rangeMatch(key, value, options) {
var expressions = [];
var transform = options && (typeof options.rangeTransform === 'function') && options.rangeTransform;

if (values.max) {
var max = transform ? transform(values.max) : values.max;
if (value.max) {
var max = transform ? transform(value.max) : value.max;
expressions.push('' + lookUp(key) + ' < ' + max);
}

if (values.min) {
var min = transform ? min = transform(values.min) : values.min;
if (value.min) {
var min = transform ? min = transform(value.min) : value.min;
expressions.push('' + lookUp(key) + ' >= ' + min);
}

return wrap(expressions.join(' && '));
}

function includesMatch(key, value) {
let expressions = [];

// the array includes ONE OE MORE of the provided values (a single value is converted to an array)
if (value.includes_any) {
const vals = Array.isArray(value.includes_any) ? value.includes_any : [value.includes_any];
const arr = '['+ vals.map(maybeQuote).join(',') + ']';
expressions.push(`${arr}.some(function(v) { return ${lookUp(key)}.indexOf(v) > -1 })`);
}

// the array includes ALL of the provided values (a single value is converted to an array)
if (value.includes_all) {
const vals = Array.isArray(value.includes_all) ? value.includes_all : [value.includes_all];
const arr = '[' + vals.map(maybeQuote).join(',') + ']';
expressions.push(`${arr}.every(function(v) { return ${lookUp(key)}.indexOf(v) > -1 })`);
}

return wrap(expressions.join(' && '));
}

function parseFilter(filter, options) {
var filterAST = [];

Expand Down Expand Up @@ -111,6 +148,9 @@ function parseFilter(filter, options) {
if (value.max || value.min) {
filterAST.push(rangeMatch(key, value, options));
}
else if (value.includes_any || value.includes_all) {
filterAST.push(includesMatch(key, value, options));
}
} else if (value == null) {
filterAST.push(nullValue(key, value));
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/styles/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ class Layer {
// Context property
this.context_prop_matches = this.context_prop_matches || [];
this.context_prop_matches.push([key.substring(1), array ? val : [val]]);
delete this.filter[key];
}
else {
// Feature property
else if (key.indexOf('.') === -1) { // exclude nested feature properties
// Single-level feature property
this.feature_prop_matches = this.feature_prop_matches || [];
this.feature_prop_matches.push([key, array ? val : [val]]);
delete this.filter[key];
}

delete this.filter[key];
}
});
}
Expand Down