diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index ffe1662d3b5..a096d14ed73 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -85,7 +85,7 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - const needGeometry = Array.isArray(filter) && filter.length !== 0 && filter[0] === 'within'; + const needGeometry = geometryNeeded(filter); return {filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), needGeometry}; } @@ -96,6 +96,15 @@ function compare(a, b) { return a < b ? -1 : a > b ? 1 : 0; } +function geometryNeeded(filter) { + if (!Array.isArray(filter)) return false; + if (filter[0] === 'within') return true; + for (let index = 1; index < filter.length; index++) { + if (geometryNeeded(filter[index])) return true; + } + return false; +} + function convertFilter(filter: ?Array): mixed { if (!filter) return true; const op = filter[0]; @@ -114,6 +123,7 @@ function convertFilter(filter: ?Array): mixed { op === '!in' ? convertNegation(convertInOp(filter[1], filter.slice(2))) : op === 'has' ? convertHasOp(filter[1]) : op === '!has' ? convertNegation(convertHasOp(filter[1])) : + op === 'within' ? filter : true; return converted; } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index b7a7b35c70b..8b12accf811 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2443,6 +2443,9 @@ }, "!has": { "doc": "`[\"!has\", key]` `feature[key]` does not exist" + }, + "within": { + "doc": "`[\"within\", object]` feature geometry is within object geometry" } }, "doc": "The filter operator." diff --git a/src/style-spec/validate/validate_filter.js b/src/style-spec/validate/validate_filter.js index 470ce40731a..804b7baac38 100644 --- a/src/style-spec/validate/validate_filter.js +++ b/src/style-spec/validate/validate_filter.js @@ -104,8 +104,14 @@ function validateNonExpressionFilter(options) { errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`)); } break; - + case 'within': + type = getType(value[1]); + if (value.length !== 2) { + errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`)); + } else if (type !== 'object') { + errors.push(new ValidationError(`${key}[1]`, value[1], `object expected, ${type} found`)); + } + break; } - return errors; } diff --git a/test/integration/render-tests/within/within-in-complex-filter/expected.png b/test/integration/render-tests/within/within-in-complex-filter/expected.png new file mode 100644 index 00000000000..7f3f172cee2 Binary files /dev/null and b/test/integration/render-tests/within/within-in-complex-filter/expected.png differ diff --git a/test/integration/render-tests/within/within-in-complex-filter/style.json b/test/integration/render-tests/within/within-in-complex-filter/style.json new file mode 100644 index 00000000000..f6c69364438 --- /dev/null +++ b/test/integration/render-tests/within/within-in-complex-filter/style.json @@ -0,0 +1,102 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "zoom": 3, + "center": [2.5, 2.5], + "sources": { + "points": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "number": [5] + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1.9775390625, + 2.3284603685731593 + ] + } + }, + { + "type": "Feature", + "properties": { + "number": [5] + }, + "geometry": { + "type": "Point", + "coordinates": [ + 1.7138671875, + -1.7136116598836224 + ] + } + } + ] + } + }, + "polygon": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [[0, 0], + [0, 5], + [5, 5], + [5, 0], + [0, 0]] + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "border", + "type": "fill", + "source": "polygon", + "paint": { + "fill-color": "black", + "fill-opacity": 0.5 + } + }, + { + "id": "circle", + "type": "circle", + "source": "points", + "filter": ["all", ["in", 5, ["get", "number"]], ["==", ["within", { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [0, 5], + [5, 5], + [5, 0], + [0, 0] + ] + ] + }], true] + ], + "paint": { + "circle-radius": 5, + "circle-color": "red" + } + } + ] +}