diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 0d7d14d452e..654733cd618 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -10,35 +10,12 @@ import type {GeoJSON, GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson import MercatorCoordinate from '../../../geo/mercator_coordinate'; import EXTENT from '../../../data/extent'; import Point from '@mapbox/point-geometry'; +import type {CanonicalTileID} from '../../../source/tile_id'; type GeoJSONPolygons =| GeoJSONPolygon | GeoJSONMultiPolygon; +// minX, minY, maxX, maxY type BBox = [number, number, number, number]; - -function calcBBox(bbox: BBox, geom, type) { - if (type === 'Point') { - updateBBox(bbox, geom); - } else if (type === 'MultiPoint' || type === 'LineString') { - for (let i = 0; i < geom.length; ++i) { - updateBBox(bbox, geom[i]); - } - } else if (type === 'Polygon' || type === 'MultiLineString') { - for (let i = 0; i < geom.length; i++) { - for (let j = 0; j < geom[i].length; j++) { - updateBBox(bbox, geom[i][j]); - } - } - } else if (type === 'MultiPolygon') { - for (let i = 0; i < geom.length; i++) { - for (let j = 0; j < geom[i].length; j++) { - for (let k = 0; k < geom[i][j].length; k++) { - updateBBox(bbox, geom[i][j][k]); - } - } - } - } -} - function updateBBox(bbox: BBox, coord: Point) { bbox[0] = Math.min(bbox[0], coord[0]); bbox[1] = Math.min(bbox[1], coord[1]); @@ -46,7 +23,7 @@ function updateBBox(bbox: BBox, coord: Point) { bbox[3] = Math.max(bbox[3], coord[1]); } -function boxWithinBox(bbox1, bbox2) { +function boxWithinBox(bbox1: BBox, bbox2: BBox) { if (bbox1[0] <= bbox2[0]) return false; if (bbox1[2] >= bbox2[2]) return false; if (bbox1[1] <= bbox2[1]) return false; @@ -54,21 +31,10 @@ function boxWithinBox(bbox1, bbox2) { return true; } -function getLngLatPoint(coord: Point, canonical) { +function getTileCoordinates(p, canonical: CanonicalTileID) { + const coord = MercatorCoordinate.fromLngLat({lng: p[0], lat: p[1]}, 0); const tilesAtZoom = Math.pow(2, canonical.z); - const x = (coord.x / EXTENT + canonical.x) / tilesAtZoom; - const y = (coord.y / EXTENT + canonical.y) / tilesAtZoom; - const p = new MercatorCoordinate(x, y).toLngLat(); - - return [p.lng, p.lat]; -} - -function getLngLatPoints(line, canonical) { - const coords = []; - for (let i = 0; i < line.length; ++i) { - coords.push(getLngLatPoint(line[i], canonical)); - } - return coords; + return [Math.round(coord.x * tilesAtZoom * EXTENT), Math.round(coord.y * tilesAtZoom * EXTENT)]; } function onBoundary(p, p1, p2) { @@ -97,13 +63,10 @@ function pointWithinPolygon(point, rings) { } function pointWithinPolygons(point, polygons) { - if (polygons.type === 'Polygon') { - return pointWithinPolygon(point, polygons.coordinates); + for (let i = 0; i < polygons.length; i++) { + if (pointWithinPolygon(point, polygons[i])) return true; } - for (let i = 0; i < polygons.coordinates.length; i++) { - if (!pointWithinPolygon(point, polygons.coordinates[i])) return false; - } - return true; + return false; } function perp(v1, v2) { @@ -168,59 +131,120 @@ function lineStringWithinPolygon(line, polygon) { } function lineStringWithinPolygons(line, polygons) { - if (polygons.type === 'Polygon') { - return lineStringWithinPolygon(line, polygons.coordinates); + for (let i = 0; i < polygons.length; i++) { + if (lineStringWithinPolygon(line, polygons[i])) return true; } - for (let i = 0; i < polygons.coordinates.length; i++) { - if (!lineStringWithinPolygon(line, polygons.coordinates[i])) return false; + return false; +} + +function getTilePolygon(coordinates, bbox, canonical) { + const polygon = []; + for (let i = 0; i < coordinates.length; i++) { + const ring = []; + for (let j = 0; j < coordinates[i].length; j++) { + const coord = getTileCoordinates(coordinates[i][j], canonical); + updateBBox(bbox, coord); + ring.push(coord); + } + polygon.push(ring); } - return true; + return polygon; } -function pointsWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { +function getTilePolygons(coordinates, bbox, canonical) { + const polygons = []; + for (let i = 0; i < coordinates.length; i++) { + const polygon = getTilePolygon(coordinates[i], bbox, canonical); + polygons.push(polygon); + } + return polygons; +} + +function pointsWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons) { const pointBBox = [Infinity, Infinity, -Infinity, -Infinity]; - const lngLatPoints = []; + const polyBBox = [Infinity, Infinity, -Infinity, -Infinity]; + const canonical = ctx.canonicalID(); + const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; + const tilePoints = []; + for (const points of ctx.geometry()) { for (const point of points) { - const p = getLngLatPoint(point, ctx.canonicalID()); - lngLatPoints.push(p); + const p = [point.x + shifts[0], point.y + shifts[1]]; updateBBox(pointBBox, p); + tilePoints.push(p); + } + } + + if (polygonGeometry.type === 'Polygon') { + const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical); + if (!boxWithinBox(pointBBox, polyBBox)) return false; + + for (const point of tilePoints) { + if (!pointWithinPolygon(point, tilePolygon)) return false; } } - if (!boxWithinBox(pointBBox, polyBBox)) return false; - for (let i = 0; i < lngLatPoints.length; ++i) { - if (!pointWithinPolygons(lngLatPoints[i], polygonGeometry)) return false; + + if (polygonGeometry.type === 'MultiPolygon') { + const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical); + if (!boxWithinBox(pointBBox, polyBBox)) return false; + + for (const point of tilePoints) { + if (!pointWithinPolygons(point, tilePolygons)) return false; + } } + return true; } -function linesWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { +function linesWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons) { const lineBBox = [Infinity, Infinity, -Infinity, -Infinity]; - const lineCoords = []; + const polyBBox = [Infinity, Infinity, -Infinity, -Infinity]; + + const canonical = ctx.canonicalID(); + const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; + const tileLines = []; + for (const line of ctx.geometry()) { - const lineCoord = getLngLatPoints(line, ctx.canonicalID()); - lineCoords.push(lineCoord); - calcBBox(lineBBox, lineCoord, 'LineString'); + const tileLine = []; + for (const point of line) { + const p = [point.x + shifts[0], point.y + shifts[1]]; + updateBBox(lineBBox, p); + tileLine.push(p); + } + tileLines.push(tileLine); } - if (!boxWithinBox(lineBBox, polyBBox)) return false; - for (let i = 0; i < lineCoords.length; ++i) { - if (!lineStringWithinPolygons(lineCoords[i], polygonGeometry)) return false; + + if (polygonGeometry.type === 'Polygon') { + const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical); + if (!boxWithinBox(lineBBox, polyBBox)) return false; + + for (const line of tileLines) { + if (!lineStringWithinPolygon(line, tilePolygon)) return false; + } + } + + if (polygonGeometry.type === 'MultiPolygon') { + const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical); + + if (!boxWithinBox(lineBBox, polyBBox)) return false; + + for (const line of tileLines) { + if (!lineStringWithinPolygons(line, tilePolygons)) return false; + } } return true; + } class Within implements Expression { type: Type; geojson: GeoJSON geometries: GeoJSONPolygons; - polyBBox: BBox; constructor(geojson: GeoJSON, geometries: GeoJSONPolygons) { this.type = BooleanType; this.geojson = geojson; this.geometries = geometries; - this.polyBBox = [Infinity, Infinity, -Infinity, -Infinity]; - calcBBox(this.polyBBox, this.geometries.coordinates, this.geometries.type); } static parse(args: $ReadOnlyArray, context: ParsingContext) { @@ -250,9 +274,9 @@ class Within implements Expression { evaluate(ctx: EvaluationContext) { if (ctx.geometry() != null && ctx.canonicalID() != null) { if (ctx.geometryType() === 'Point') { - return pointsWithinPolygons(ctx, this.geometries, this.polyBBox); + return pointsWithinPolygons(ctx, this.geometries); } else if (ctx.geometryType() === 'LineString') { - return linesWithinPolygons(ctx, this.geometries, this.polyBBox); + return linesWithinPolygons(ctx, this.geometries); } } return false; diff --git a/test/expression.test.js b/test/expression.test.js index 4465ca27886..d58cc480973 100644 --- a/test/expression.test.js +++ b/test/expression.test.js @@ -7,9 +7,15 @@ import ignores from './ignores.json'; import {CanonicalTileID} from '../src/source/tile_id'; import MercatorCoordinate from '../src/geo/mercator_coordinate'; -function convertPoint(coord, canonical, out) { +function getPoint(coord, canonical) { const p = canonical.getTilePoint(MercatorCoordinate.fromLngLat({lng: coord[0], lat: coord[1]}, 0)); - out.push([p]); + p.x = Math.round(p.x); + p.y = Math.round(p.y); + return p; +} + +function convertPoint(coord, canonical, out) { + out.push([getPoint(coord, canonical)]); } function convertPoints(coords, canonical, out) { @@ -18,14 +24,17 @@ function convertPoints(coords, canonical, out) { } } +function convertLine(line, canonical, out) { + const l = []; + for (let i = 0; i < line.length; i++) { + l.push(getPoint(line[i], canonical)); + } + out.push(l); +} + function convertLines(lines, canonical, out) { for (let i = 0; i < lines.length; i++) { - const geom = []; - const ring = lines[i]; - for (let j = 0; j < ring.length; j++) { - convertPoint(ring[j], canonical, geom); - } - out.push(geom); + convertLine(lines[i], canonical, out); } } @@ -38,15 +47,17 @@ function getGeometry(feature, geometry, canonical) { if (type === 'Point') { convertPoint(coords, canonical, feature.geometry); } else if (type === 'MultiPoint') { + feature.type = 'Point'; convertPoints(coords, canonical, feature.geometry); } else if (type === 'LineString') { - convertPoints(coords, canonical, feature.geometry); + convertLine(coords, canonical, feature.geometry); } else if (type === 'MultiLineString') { + feature.type = 'LineString'; convertLines(coords, canonical, feature.geometry); } else if (type === 'Polygon') { convertLines(coords, canonical, feature.geometry); - } else if (type === 'MultiPolygon') { + feature.type = 'Polygon'; for (let i = 0; i < coords.length; i++) { const polygon = []; convertLines(coords[i], canonical, polygon); diff --git a/test/integration/expression-tests/within/line-within-polygons/test.json b/test/integration/expression-tests/within/line-within-polygons/test.json new file mode 100644 index 00000000000..c193086bcd3 --- /dev/null +++ b/test/integration/expression-tests/within/line-within-polygons/test.json @@ -0,0 +1,81 @@ +{ + "expression": ["within", { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "LineString", + "coordinates": [[3, 3], [4, 1]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "LineString", + "coordinates": [[3, 3], [-2, -2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "LineString", + "coordinates": [[0, 0], [2, 2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "LineString", + "coordinates": [[1, 3], [-2, -2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "LineString", + "coordinates": [[-1, -1], [-2, -2]] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [true, false, false, false, true], + "serialized": ["within", { + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]], + "type": "MultiPolygon" + }] + } +} diff --git a/test/integration/expression-tests/within/lines-within-polygon/test.json b/test/integration/expression-tests/within/lines-within-polygon/test.json new file mode 100644 index 00000000000..ac914ed9b33 --- /dev/null +++ b/test/integration/expression-tests/within/lines-within-polygon/test.json @@ -0,0 +1,44 @@ +{ + "expression": ["within", { + "type": "Polygon", + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiLineString", + "coordinates": [[[3, 3], [4, 1]], [[-2, -2], [-1, -1]]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiLineString", + "coordinates": [[[3, 3], [2, 2]], [[1, 1], [2, 2], [3, 2]]] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, true], + "serialized": ["within", { + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + "type": "Polygon" + }] + } +} diff --git a/test/integration/expression-tests/within/lines-within-polygons/test.json b/test/integration/expression-tests/within/lines-within-polygons/test.json new file mode 100644 index 00000000000..936463c000b --- /dev/null +++ b/test/integration/expression-tests/within/lines-within-polygons/test.json @@ -0,0 +1,57 @@ +{ + "expression": ["within", { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiLineString", + "coordinates": [[[3, 3], [4, 1]], [[-2, -2], [-1, -1]]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiLineString", + "coordinates": [[[3, 3], [2, 2]], [[1, 1], [2, 2], [3, 2]]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiLineString", + "coordinates": [[[3, 3], [2, 2]], [[-1, 1], [2, 2], [3, 2]]] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [true, true, false], + "serialized": ["within", { + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]], + "type": "MultiPolygon" + }] + } +} diff --git a/test/integration/expression-tests/within/point-on-boundary-1/test.json b/test/integration/expression-tests/within/point-on-boundary-1/test.json new file mode 100644 index 00000000000..8716649bd85 --- /dev/null +++ b/test/integration/expression-tests/within/point-on-boundary-1/test.json @@ -0,0 +1,248 @@ +{ + "expression": ["within", { + "type": "Polygon", + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]] + }], + "inputs": [[{ + "zoom": 0, + "canonicalID": { + "z": 0, + "x": 0, + "y": 0 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 1, + "canonicalID": { + "z": 1, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 2, + "canonicalID": { + "z": 2, + "x": 2, + "y": 2 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 4, + "canonicalID": { + "z": 4, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 5, + "canonicalID": { + "z": 5, + "x": 16, + "y": 16 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 6, + "canonicalID": { + "z": 6, + "x": 32, + "y": 32 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 7, + "canonicalID": { + "z": 7, + "x": 65, + "y": 63 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 8, + "canonicalID": { + "z": 8, + "x": 131, + "y": 124 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 9, + "canonicalID": { + "z": 9, + "x": 263, + "y": 248 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 10, + "canonicalID": { + "z": 10, + "x": 526, + "y": 497 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 11, + "canonicalID": { + "z": 11, + "x": 1052, + "y": 995 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 12, + "canonicalID": { + "z": 12, + "x": 2104, + "y": 1991 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 13, + "canonicalID": { + "z": 13, + "x": 4209, + "y": 3982 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 14, + "canonicalID": { + "z": 14, + "x": 8419, + "y": 7964 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 15, + "canonicalID": { + "z": 15, + "x": 16839, + "y": 15928 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 16, + "canonicalID": { + "z": 16, + "x": 33678, + "y": 31856 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 17, + "canonicalID": { + "z": 17, + "x": 67356, + "y": 67313 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }], [{ + "zoom": 18, + "canonicalID": { + "z": 18, + "x": 134712, + "y": 127426 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [5, 5] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ], + "serialized": ["within", { + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + "type": "Polygon" + }] + } +} diff --git a/test/integration/expression-tests/within/point-on-boundary-2/test.json b/test/integration/expression-tests/within/point-on-boundary-2/test.json new file mode 100644 index 00000000000..7e67501ce44 --- /dev/null +++ b/test/integration/expression-tests/within/point-on-boundary-2/test.json @@ -0,0 +1,248 @@ +{ + "expression": ["within", { + "type": "Polygon", + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]] + }], + "inputs": [[{ + "zoom": 0, + "canonicalID": { + "z": 0, + "x": 0, + "y": 0 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 1, + "canonicalID": { + "z": 1, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 2, + "canonicalID": { + "z": 2, + "x": 2, + "y": 2 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 4, + "canonicalID": { + "z": 4, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 5, + "canonicalID": { + "z": 5, + "x": 16, + "y": 16 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 6, + "canonicalID": { + "z": 6, + "x": 32, + "y": 32 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 7, + "canonicalID": { + "z": 7, + "x": 65, + "y": 63 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 8, + "canonicalID": { + "z": 8, + "x": 131, + "y": 126 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 9, + "canonicalID": { + "z": 9, + "x": 261, + "y": 261 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 10, + "canonicalID": { + "z": 10, + "x": 512, + "y": 503 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 11, + "canonicalID": { + "z": 11, + "x": 1024, + "y": 1006 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 12, + "canonicalID": { + "z": 12, + "x": 2048, + "y": 2013 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 13, + "canonicalID": { + "z": 13, + "x": 4096, + "y": 4027 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 14, + "canonicalID": { + "z": 14, + "x": 8192, + "y": 8055 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 15, + "canonicalID": { + "z": 15, + "x": 16384, + "y": 16110 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 16, + "canonicalID": { + "z": 16, + "x": 32768, + "y": 32221 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 17, + "canonicalID": { + "z": 17, + "x": 65536, + "y": 64443 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }], [{ + "zoom": 18, + "canonicalID": { + "z": 18, + "x": 131072, + "y": 128888 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [0, 3] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ], + "serialized": ["within", { + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + "type": "Polygon" + }] + } +} diff --git a/test/integration/expression-tests/within/point-on-boundary-3/test.json b/test/integration/expression-tests/within/point-on-boundary-3/test.json new file mode 100644 index 00000000000..15ae0528d04 --- /dev/null +++ b/test/integration/expression-tests/within/point-on-boundary-3/test.json @@ -0,0 +1,248 @@ +{ + "expression": ["within", { + "type": "Polygon", + "coordinates": [[[-10, -10], [10, -10], [10, 10], [-10, 10], [-10, -10]]] + }], + "inputs": [[{ + "zoom": 0, + "canonicalID": { + "z": 0, + "x": 0, + "y": 0 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 1, + "canonicalID": { + "z": 1, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 2, + "canonicalID": { + "z": 2, + "x": 2, + "y": 2 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 4, + "canonicalID": { + "z": 4, + "x": 1, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 5, + "canonicalID": { + "z": 5, + "x": 16, + "y": 16 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 6, + "canonicalID": { + "z": 6, + "x": 32, + "y": 32 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 7, + "canonicalID": { + "z": 7, + "x": 65, + "y": 63 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 8, + "canonicalID": { + "z": 8, + "x": 135, + "y": 135 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 9, + "canonicalID": { + "z": 9, + "x": 270, + "y": 270 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 10, + "canonicalID": { + "z": 10, + "x": 540, + "y": 540 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 11, + "canonicalID": { + "z": 11, + "x": 1080, + "y": 1081 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 12, + "canonicalID": { + "z": 12, + "x": 2161, + "y": 2162 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 13, + "canonicalID": { + "z": 13, + "x": 4323, + "y": 4324 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 14, + "canonicalID": { + "z": 14, + "x": 8650, + "y": 8650 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 15, + "canonicalID": { + "z": 15, + "x": 17295, + "y": 17298 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 16, + "canonicalID": { + "z": 16, + "x": 34588, + "y": 34597 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 17, + "canonicalID": { + "z": 17, + "x": 69176, + "y": 69195 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }], [{ + "zoom": 18, + "canonicalID": { + "z": 18, + "x": 138353, + "y": 138391 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [10, -10] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ], + "serialized": ["within", { + "coordinates": [[[-10, -10], [10, -10], [10, 10], [-10, 10], [-10, -10]]], + "type": "Polygon" + }] + } +} diff --git a/test/integration/expression-tests/within/point-within-polygons/test.json b/test/integration/expression-tests/within/point-within-polygons/test.json new file mode 100644 index 00000000000..29f10acb40d --- /dev/null +++ b/test/integration/expression-tests/within/point-within-polygons/test.json @@ -0,0 +1,106 @@ +{ + "expression": ["within", { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [6, 6] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [2, 2] + } + }],[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [3, 4] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [-1, 3] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 0, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [-1, -5] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 5, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [2, -2] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 2, + "x": 0, + "y": 1 + } + }, { + "geometry": { + "type": "Point", + "coordinates": [-2, -2] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, true, true, false, false, false, true], + "serialized": ["within", { + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]], + "type": "MultiPolygon" + }] + } + } + \ No newline at end of file diff --git a/test/integration/expression-tests/within/points-within-polygon/test.json b/test/integration/expression-tests/within/points-within-polygon/test.json new file mode 100644 index 00000000000..6f2f45def7a --- /dev/null +++ b/test/integration/expression-tests/within/points-within-polygon/test.json @@ -0,0 +1,45 @@ +{ + "expression": ["within", { + "type": "Polygon", + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[6, 6], [3, 4], [2, 2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[2, 2], [3, 3]] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, true], + "serialized": ["within", { + "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + "type": "Polygon" + }] + } + } + \ No newline at end of file diff --git a/test/integration/expression-tests/within/points-within-polygons/test.json b/test/integration/expression-tests/within/points-within-polygons/test.json new file mode 100644 index 00000000000..c170f6134b7 --- /dev/null +++ b/test/integration/expression-tests/within/points-within-polygons/test.json @@ -0,0 +1,94 @@ +{ + "expression": ["within", { + "type": "MultiPolygon", + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], + [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]] + }], + "inputs": [[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[6, 6], [2, 2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[2, 2], [3, 3]] + } + }],[{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[3, 4], [-2, -2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 3, + "y": 3 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[-1, 3], [1, 3]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 3, + "x": 0, + "y": 1 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[2, 3], [-2, 3], [-2, -2]] + } + }], [{ + "zoom": 3, + "canonicalID": { + "z": 5, + "x": 15, + "y": 16 + } + }, { + "geometry": { + "type": "MultiPoint", + "coordinates": [[-2, -2], [-1, -1]] + } + }]], + "expected": { + "compiled": { + "type": "boolean", + "isFeatureConstant": false, + "isZoomConstant": true, + "result": "success" + }, + "outputs": [false, true, true, false, false, true], + "serialized": ["within", { + "coordinates": [[[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]], [[[0, 0], [-3, 0], [-3, -3], [0, -3], [0, 0]]]], + "type": "MultiPolygon" + }] + } + } + \ No newline at end of file diff --git a/test/integration/render-tests/within/within-feature-collection-geojson/expected.png b/test/integration/render-tests/within/within-feature-collection-geojson/expected.png new file mode 100644 index 00000000000..076ce2fd6a4 Binary files /dev/null and b/test/integration/render-tests/within/within-feature-collection-geojson/expected.png differ diff --git a/test/integration/render-tests/within/within-feature-collection-geojson/style.json b/test/integration/render-tests/within/within-feature-collection-geojson/style.json new file mode 100644 index 00000000000..55aef92253f --- /dev/null +++ b/test/integration/render-tests/within/within-feature-collection-geojson/style.json @@ -0,0 +1,207 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 125, + "height": 125 + } + }, + "zoom": 2, + "center": [ + -24, + -36,5 + ], + "sources": { + "line": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [ + -23.5546875, + -34.59704151614416 + ], + [ + -23.642578125, + -36.87962060502676 + ] + ], + [ + [ + -30.585937499999996, + -38.34165619279593 + ], + [ + -21.62109375, + -39.70718665682654 + ] + ], + [ + [ + -24.960937499999996, + -34.813803317113134 + ], + [ + -27.24609375, + -41.96765920367816 + ] + ] + ] + } + } + ] + } + }, + "polygon": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -28.388671875, + -33.06392419812064 + ], + [ + -28.564453125, + -38.13455657705412 + ], + [ + -22.148437499999996, + -37.64903402157864 + ], + [ + -19.86328125, + -33.35806161277886 + ], + [ + -28.388671875, + -33.06392419812064 + ] + ] + ], [ + [ + [ + -31.113281249999996, + -39.16414104768742 + ], + [ + -27.861328125, + -43.580390855607845 + ], + [ + -21.884765625, + -41.640078384678915 + ], + [ + -31.113281249999996, + -39.16414104768742 + ] + ] + ]] + } + } + ] + } + } + }, + "layers": [ + { + "id": "border", + "type": "fill", + "source": "polygon", + "paint": { + "fill-color": "black", + "fill-opacity": 0.5 + } + }, + { + "id": "draw", + "type": "line", + "source": "line", + "paint": { + "line-color": [ + "case", + [ + "within", + { + "type": "MultiPolygon", + "coordinates": + [ + [ + [ + [ + -28.388671875, + -33.06392419812064 + ], + [ + -28.564453125, + -38.13455657705412 + ], + [ + -22.148437499999996, + -37.64903402157864 + ], + [ + -19.86328125, + -33.35806161277886 + ], + [ + -28.388671875, + -33.06392419812064 + ] + ] + ], + [ + [ + [ + -31.113281249999996, + -39.16414104768742 + ], + [ + -27.861328125, + -43.580390855607845 + ], + [ + -21.884765625, + -41.640078384678915 + ], + [ + -31.113281249999996, + -39.16414104768742 + ] + ] + ] + ] + } + ], + "red", + "blue" + ] + } + }, + { + "id": "circle", + "type": "circle", + "source": "line", + "paint": { + "circle-color": "red", + "circle-radius": 2 + } + } + ] +} \ No newline at end of file diff --git a/test/integration/render-tests/within/within-feature-geojson/expected.png b/test/integration/render-tests/within/within-feature-geojson/expected.png new file mode 100644 index 00000000000..6a18483facd Binary files /dev/null and b/test/integration/render-tests/within/within-feature-geojson/expected.png differ diff --git a/test/integration/render-tests/within/within-feature-geojson/style.json b/test/integration/render-tests/within/within-feature-geojson/style.json new file mode 100644 index 00000000000..76db109ccad --- /dev/null +++ b/test/integration/render-tests/within/within-feature-geojson/style.json @@ -0,0 +1,89 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 64, + "height": 64 + } + }, + "zoom": 2, + "center": [3.25, 3.25], + "sources": { + "points": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + }, + "geometry": { + "type": "MultiPoint", + "coordinates": [[ + 1.9775390625, + 2.3284603685731593 + ], + [ + 2.021484375, + 7.798078531355303 + ]] + } + } + ] + } + }, + "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": "boarder", + "type": "fill", + "source": "polygon", + "paint": { + "fill-color": "black", + "fill-opacity": 0.5 + } + }, + { + "id": "circle", + "type": "circle", + "source": "points", + "paint": { + "circle-radius": 5, + "circle-color": ["case", ["within", { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], [0, 5], [5, 5], [5, 0], [0, 0] + ] + ] + } + } + ], "red", "blue"] + } + } + ] +}