From 96c4b662c02fa2aab03f333e642f0ae4dbaeb318 Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 19 Mar 2020 14:36:49 +0200 Subject: [PATCH] Use TileCoordinates instead of LngLats for `within` calculation (#9428) * Using TileCoordinates instead of LngLat for within calculation * Update tests * Adapt test data * Add render tests --- .../expression/definitions/within.js | 166 +++++++----- test/expression.test.js | 31 ++- .../within/line-within-polygons/test.json | 81 ++++++ .../within/lines-within-polygon/test.json | 44 ++++ .../within/lines-within-polygons/test.json | 57 ++++ .../within/point-on-boundary-1/test.json | 248 ++++++++++++++++++ .../within/point-on-boundary-2/test.json | 248 ++++++++++++++++++ .../within/point-on-boundary-3/test.json | 248 ++++++++++++++++++ .../within/point-within-polygons/test.json | 106 ++++++++ .../within/points-within-polygon/test.json | 45 ++++ .../within/points-within-polygons/test.json | 94 +++++++ .../expected.png | Bin 0 -> 2407 bytes .../style.json | 207 +++++++++++++++ .../within-feature-geojson/expected.png | Bin 0 -> 376 bytes .../within/within-feature-geojson/style.json | 89 +++++++ 15 files changed, 1583 insertions(+), 81 deletions(-) create mode 100644 test/integration/expression-tests/within/line-within-polygons/test.json create mode 100644 test/integration/expression-tests/within/lines-within-polygon/test.json create mode 100644 test/integration/expression-tests/within/lines-within-polygons/test.json create mode 100644 test/integration/expression-tests/within/point-on-boundary-1/test.json create mode 100644 test/integration/expression-tests/within/point-on-boundary-2/test.json create mode 100644 test/integration/expression-tests/within/point-on-boundary-3/test.json create mode 100644 test/integration/expression-tests/within/point-within-polygons/test.json create mode 100644 test/integration/expression-tests/within/points-within-polygon/test.json create mode 100644 test/integration/expression-tests/within/points-within-polygons/test.json create mode 100644 test/integration/render-tests/within/within-feature-collection-geojson/expected.png create mode 100644 test/integration/render-tests/within/within-feature-collection-geojson/style.json create mode 100644 test/integration/render-tests/within/within-feature-geojson/expected.png create mode 100644 test/integration/render-tests/within/within-feature-geojson/style.json 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 0000000000000000000000000000000000000000..076ce2fd6a494f514335833c09519f9d7ca22069 GIT binary patch literal 2407 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 Rx!(_zh%*A^*yP}!_78N3o6`UQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6a18483facd0c0bd9d7107bf0e5a1573ae36e1c3 GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV6^viaSW-L^LD187qg>?Lw(}H zsmnhwm|dHvr6884sL8amnM3f(A!d&Y#y5^0JEhWPZt(7h`!zjd;Z3_Y&%F8lfn!(A zkI#qH8U-883|oH%TE3~8-7&*`|AdStBbl51Czjl zonK!&Gbl1Jc6A6jT;eG(5!yNL)#=@5@6GpZsQsFLcJF~VXYHr^GTdK#;kB7y5Z8x& z9QS$CWEb$n_N+S0{=McCW0U#eSAREG+x=u%{l1{#@kS7#yE444dC6B$ z@pArIrG~F>!sOdSzph+;`uqo