Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"geometry-type" to identify Multi- features #519

Merged
merged 27 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ce82b04
"geometry-type" to identify Milti- features
zstadler Feb 2, 2024
3d55f64
Fix and extend tests
zstadler Feb 8, 2024
334103f
Lint fixes, incomplete
zstadler Feb 8, 2024
4675c8b
Linting, continued
zstadler Feb 8, 2024
e3b7448
Linting, var -> let, const
zstadler Feb 9, 2024
a05f9ea
Linting, completed
zstadler Feb 9, 2024
80eb2d8
Merge branch 'main' into geometry-type
zstadler Oct 16, 2024
feb0451
Merge commit '4e9e832' into geometry-type-merge
zstadler Oct 16, 2024
031dd80
Merge commit 'b3d112a' into geometry-type-merge
zstadler Oct 16, 2024
ae718af
Merge commit '9db6f8f' into geometry-type-merge
zstadler Oct 16, 2024
f66ee52
Merge commit '8e10188' into geometry-type-merge
zstadler Oct 16, 2024
25dc3d4
Merge remote-tracking branch 'upstream/main' into geometry-type-merge
zstadler Oct 16, 2024
7fc93c4
Merge remote-tracking branch 'upstream/main' into geometry-type
zstadler Oct 16, 2024
a7d7621
Move lost code changes to their new location
zstadler Oct 16, 2024
cbdd941
Reuse `calculateSignedArea`
zstadler Oct 16, 2024
253108a
Simplify `geometryType()` code
zstadler Oct 19, 2024
ebe3bc4
Split test
zstadler Oct 19, 2024
66741c3
Use variables for sample geometries.
zstadler Oct 19, 2024
5262c3d
Extract code into `hasMultipleOuterRings()`
zstadler Oct 19, 2024
3c27179
Update src/util/classify_rings.ts
zstadler Oct 19, 2024
2ed3000
Updated `Changelog.md`
zstadler Oct 19, 2024
28930b3
Add `["geomerty-type"]` tests
zstadler Oct 19, 2024
e182527
Rename test.json to test.json
HarelM Oct 22, 2024
3f114b4
Rename test.json to test.json
HarelM Oct 22, 2024
b72e2b8
Rename test.json to test.json
HarelM Oct 22, 2024
3f4d870
Rename test.json to test.json
HarelM Oct 22, 2024
bbe4b5e
Rename test.json to test.json
HarelM Oct 22, 2024
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
4 changes: 2 additions & 2 deletions src/expression/compound_expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ CompoundExpression.register(expressions, {
'filter-type-==': [
BooleanType,
[StringType],
(ctx, [v]) => ctx.geometryType() === (v as any).value
(ctx, [v]) => ctx.geometryDollarType() === (v as any).value
],
'filter-<': [
BooleanType,
Expand Down Expand Up @@ -548,7 +548,7 @@ CompoundExpression.register(expressions, {
'filter-type-in': [
BooleanType,
[array(StringType)],
(ctx, [v]) => (v as any).value.indexOf(ctx.geometryType()) >= 0
(ctx, [v]) => (v as any).value.indexOf(ctx.geometryDollarType()) >= 0
],
'filter-id-in': [
BooleanType,
Expand Down
4 changes: 2 additions & 2 deletions src/expression/definitions/within.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,9 @@ class Within implements Expression {

evaluate(ctx: EvaluationContext) {
if (ctx.geometry() != null && ctx.canonicalID() != null) {
if (ctx.geometryType() === 'Point') {
if (ctx.geometryDollarType() === 'Point') {
return pointsWithinPolygons(ctx, this.geometries);
} else if (ctx.geometryType() === 'LineString') {
} else if (ctx.geometryDollarType() === 'LineString') {
return linesWithinPolygons(ctx, this.geometries);
}
}
Expand Down
38 changes: 37 additions & 1 deletion src/expression/evaluation_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ import {Color} from './values';
import type {FormattedSection} from './types/formatted';
import type {GlobalProperties, Feature, FeatureState} from './index';
import {ICanonicalTileID} from '../tiles_and_coordinates';
import {hasMultipleOuterRings} from '../util/classify_rings';

const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
const simpleGeometryType = {
'Unknown': 'Unknown',
'Point': 'Point',
'MultiPoint': 'Point',
'LineString': 'LineString',
'MultiLineString': 'LineString',
'Polygon': 'Polygon',
'MultiPolygon': 'Polygon'
};

class EvaluationContext {
globals: GlobalProperties;
Expand All @@ -14,6 +24,7 @@ class EvaluationContext {
canonical: ICanonicalTileID;

_parseColorCache: {[_: string]: Color};
_geometryType: string;

constructor() {
this.globals = null;
Expand All @@ -29,8 +40,33 @@ class EvaluationContext {
return this.feature && 'id' in this.feature ? this.feature.id : null;
}

geometryDollarType() {
return this.feature ?
typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : simpleGeometryType[this.feature.type] :
null;
HarelM marked this conversation as resolved.
Show resolved Hide resolved
}

geometryType() {
return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null;
let geometryType = this.feature.type;
if (typeof geometryType !== 'number') {
return geometryType;
}
geometryType = geometryTypes[this.feature.type];
if (geometryType === 'Unknown') {
return geometryType;
}
const geom = this.geometry();
const len = geom.length;
if (len === 1) {
return geometryType;
}
if (geometryType !== 'Polygon') {
return `Multi${geometryType}`;
}
if (hasMultipleOuterRings(geom)) {
return 'MultiPolygon';
}
return 'Polygon';
}

geometry() {
Expand Down
6 changes: 5 additions & 1 deletion src/feature_filter/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function runtimeTypeChecks(expectedTypes: ExpectedTypes): ExpressionFilterSpecif
function convertComparisonOp(property: string, value: any, op: string, expectedTypes?: ExpectedTypes | null): ExpressionFilterSpecification {
let get;
if (property === '$type') {
return [op, ['geometry-type'], value] as ExpressionFilterSpecification;
return convertInOp('$type', [value], op === '!=');
HarelM marked this conversation as resolved.
Show resolved Hide resolved
} else if (property === '$id') {
get = ['id'];
} else {
Expand Down Expand Up @@ -162,6 +162,10 @@ function convertInOp(property: string, values: Array<any>, negate = false): Expr

let get: ExpressionSpecification;
if (property === '$type') {
const len = values.length;
for (let i = 0; i < len; i++) {
values.push(`Multi${values[i]}`);
}
get = ['geometry-type'];
} else if (property === '$id') {
get = ['id'];
Expand Down
210 changes: 191 additions & 19 deletions src/feature_filter/feature_filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('convert legacy filters to expressions', () => {
[
'match',
['geometry-type'],
['LineString', 'Point', 'Polygon'],
['LineString', 'MultiLineString', 'MultiPoint', 'MultiPolygon', 'Point', 'Polygon'],
true,
false
],
Expand Down Expand Up @@ -328,10 +328,96 @@ function legacyFilterTests(createFilterExpr) {
expect(f({zoom: 0}, {properties: {}})).toBe(false);
});

test('==, $type', () => {
const f = createFilterExpr(['==', '$type', 'LineString']).filter;
expect(f({zoom: 0}, {type: 1})).toBe(false);
expect(f({zoom: 0}, {type: 2})).toBe(true);
const UnknownGeometry = {type: 0};
const PointGeometry = {type: 1, geometry: [
[{x: 0, y: 0}]]};
const MultiPointGeometry = {type: 1, geometry: [
[{x: 0, y: 0}],
[{x: 1, y: 1}]]};
const LinestringGeometry = {type: 2, geometry: [
[{x: 0, y: 0}, {x: 1, y: 1}]]};
const MultiLineStringGeometry = {type: 2, geometry: [
[{x: 0, y: 0}, {x: 1, y: 1}],
[{x: 2, y: 0}, {x: 1, y: 0}]]};
const PolygonGeometry = {type: 3, geometry: [
[{x: 0, y: 0}, {x: 3, y: 0}, {x: 3, y: 3}, {x: 0, y: 3}, {x: 0, y: 0}],
[{x: 0, y: 0}, {x: 0, y: 2}, {x: 2, y: 2}, {x: 2, y: 0}, {x: 0, y: 0}]]};
const MultiPolygonGeometry = {type: 3, geometry: [
[{x: 0, y: 0}, {x: 3, y: 0}, {x: 3, y: 3}, {x: 0, y: 3}, {x: 0, y: 0}],
[{x: 0, y: 0}, {x: 0, y: 2}, {x: 2, y: 2}, {x: 2, y: 0}, {x: 0, y: 0}],
[{x: 0, y: 0}, {x: 1, y: 0}, {x: 1, y: 1}, {x: 0, y: 1}, {x: 0, y: 0}]]};

test('==, $type, Point', () => {
const fPoint = createFilterExpr(['==', '$type', 'Point']).filter;
HarelM marked this conversation as resolved.
Show resolved Hide resolved
expect(fPoint({zoom: 0}, UnknownGeometry)).toBe(false);
expect(fPoint({zoom: 0}, PointGeometry)).toBe(true);
expect(fPoint({zoom: 0}, MultiPointGeometry)).toBe(true);
expect(fPoint({zoom: 0}, LinestringGeometry)).toBe(false);
expect(fPoint({zoom: 0}, MultiLineStringGeometry)).toBe(false);
expect(fPoint({zoom: 0}, PolygonGeometry)).toBe(false);
expect(fPoint({zoom: 0}, MultiPolygonGeometry)).toBe(false);
expect(fPoint({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'Point'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);
});

test('==, $type, LineString', () => {
const fLineString = createFilterExpr(['==', '$type', 'LineString']).filter;
expect(fLineString({zoom: 0}, UnknownGeometry)).toBe(false);
expect(fLineString({zoom: 0}, PointGeometry)).toBe(false);
expect(fLineString({zoom: 0}, MultiPointGeometry)).toBe(false);
expect(fLineString({zoom: 0}, LinestringGeometry)).toBe(true);
expect(fLineString({zoom: 0}, MultiLineStringGeometry)).toBe(true);
expect(fLineString({zoom: 0}, PolygonGeometry)).toBe(false);
expect(fLineString({zoom: 0}, MultiPolygonGeometry)).toBe(false);
expect(fLineString({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'Point'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);
});

test('==, $type, Polygon', () => {

const fPolygon = createFilterExpr(['==', '$type', 'Polygon']).filter;
expect(fPolygon({zoom: 0}, UnknownGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, PointGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, MultiPointGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, LinestringGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, MultiLineStringGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, PolygonGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, MultiPolygonGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'Point'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);
});

test('==, $type, Unknown', () => {
const fUnknown = createFilterExpr(['==', '$type', 'Unknown']).filter;
expect(fUnknown({zoom: 0}, UnknownGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, PointGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, MultiPointGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, LinestringGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, MultiLineStringGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, PolygonGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, MultiPolygonGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'Point'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);
});

test('==, $id', () => {
Expand Down Expand Up @@ -373,10 +459,76 @@ function legacyFilterTests(createFilterExpr) {
expect(f({zoom: 0}, {properties: {}})).toBe(true);
});

test('!=, $type', () => {
const f = createFilterExpr(['!=', '$type', 'LineString']).filter;
expect(f({zoom: 0}, {type: 1})).toBe(true);
expect(f({zoom: 0}, {type: 2})).toBe(false);
test('!=, $type, Point', () => {
const fPoint = createFilterExpr(['!=', '$type', 'Point']).filter;
expect(fPoint({zoom: 0}, UnknownGeometry)).toBe(true);
expect(fPoint({zoom: 0}, PointGeometry)).toBe(false);
expect(fPoint({zoom: 0}, MultiPointGeometry)).toBe(false);
expect(fPoint({zoom: 0}, LinestringGeometry)).toBe(true);
expect(fPoint({zoom: 0}, MultiLineStringGeometry)).toBe(true);
expect(fPoint({zoom: 0}, PolygonGeometry)).toBe(true);
expect(fPoint({zoom: 0}, MultiPolygonGeometry)).toBe(true);
expect(fPoint({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'Point'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(fPoint({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(fPoint({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);
});

test('!=, $type, LineString', () => {
const fLineString = createFilterExpr(['!=', '$type', 'LineString']).filter;
expect(fLineString({zoom: 0}, UnknownGeometry)).toBe(true);
expect(fLineString({zoom: 0}, PointGeometry)).toBe(true);
expect(fLineString({zoom: 0}, MultiPointGeometry)).toBe(true);
expect(fLineString({zoom: 0}, LinestringGeometry)).toBe(false);
expect(fLineString({zoom: 0}, MultiLineStringGeometry)).toBe(false);
expect(fLineString({zoom: 0}, PolygonGeometry)).toBe(true);
expect(fLineString({zoom: 0}, MultiPolygonGeometry)).toBe(true);
expect(fLineString({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'Point'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(fLineString({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(fLineString({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);
});

test('!=, $type, Polygon', () => {
const fPolygon = createFilterExpr(['!=', '$type', 'Polygon']).filter;
expect(fPolygon({zoom: 0}, UnknownGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, PointGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, MultiPointGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, LinestringGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, MultiLineStringGeometry)).toBe(true);
expect(fPolygon({zoom: 0}, PolygonGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, MultiPolygonGeometry)).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'Point'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(fPolygon({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(fPolygon({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);
});

test('!=, $type, Unknown', () => {
const fUnknown = createFilterExpr(['!=', '$type', 'Unknown']).filter;
expect(fUnknown({zoom: 0}, UnknownGeometry)).toBe(false);
expect(fUnknown({zoom: 0}, PointGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, MultiPointGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, LinestringGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, MultiLineStringGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, PolygonGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, MultiPolygonGeometry)).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(fUnknown({zoom: 0}, {type: 'Point'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(fUnknown({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);
});

test('<, number', () => {
Expand Down Expand Up @@ -563,15 +715,22 @@ function legacyFilterTests(createFilterExpr) {

test('in, $type', () => {
const f = createFilterExpr(['in', '$type', 'LineString', 'Polygon']).filter;
expect(f({zoom: 0}, {type: 1})).toBe(false);
expect(f({zoom: 0}, {type: 2})).toBe(true);
expect(f({zoom: 0}, {type: 3})).toBe(true);
expect(f({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(f({zoom: 0}, {type: 'Point'})).toBe(false);
expect(f({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(f({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(f({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(f({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(f({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);

const f1 = createFilterExpr(['in', '$type', 'Polygon', 'LineString', 'Point']).filter;
expect(f1({zoom: 0}, {type: 1})).toBe(true);
expect(f1({zoom: 0}, {type: 2})).toBe(true);
expect(f1({zoom: 0}, {type: 3})).toBe(true);

expect(f1({zoom: 0}, {type: 'Unknown'})).toBe(false);
expect(f1({zoom: 0}, {type: 'Point'})).toBe(true);
expect(f1({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(f1({zoom: 0}, {type: 'LineString'})).toBe(true);
expect(f1({zoom: 0}, {type: 'MultiLineString'})).toBe(true);
expect(f1({zoom: 0}, {type: 'Polygon'})).toBe(true);
expect(f1({zoom: 0}, {type: 'MultiPolygon'})).toBe(true);
});

test('!in, degenerate', () => {
Expand Down Expand Up @@ -621,9 +780,22 @@ function legacyFilterTests(createFilterExpr) {

test('!in, $type', () => {
const f = createFilterExpr(['!in', '$type', 'LineString', 'Polygon']).filter;
expect(f({zoom: 0}, {type: 1})).toBe(true);
expect(f({zoom: 0}, {type: 2})).toBe(false);
expect(f({zoom: 0}, {type: 3})).toBe(false);
expect(f({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(f({zoom: 0}, {type: 'Point'})).toBe(true);
expect(f({zoom: 0}, {type: 'MultiPoint'})).toBe(true);
expect(f({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(f({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(f({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(f({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);

const f1 = createFilterExpr(['!in', '$type', 'Polygon', 'LineString', 'Point']).filter;
expect(f1({zoom: 0}, {type: 'Unknown'})).toBe(true);
expect(f1({zoom: 0}, {type: 'Point'})).toBe(false);
expect(f1({zoom: 0}, {type: 'MultiPoint'})).toBe(false);
expect(f1({zoom: 0}, {type: 'LineString'})).toBe(false);
expect(f1({zoom: 0}, {type: 'MultiLineString'})).toBe(false);
expect(f1({zoom: 0}, {type: 'Polygon'})).toBe(false);
expect(f1({zoom: 0}, {type: 'MultiPolygon'})).toBe(false);
});

test('any', () => {
Expand Down
24 changes: 24 additions & 0 deletions src/util/classify_rings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,27 @@ function calculateSignedArea(ring: Point2D[]): number {
}
return sum;
}

/**
* Returns if there are multiple outer rings.
* The first ring is a outer ring. Its direction, cw or ccw, defines the direction of outer rings.
HarelM marked this conversation as resolved.
Show resolved Hide resolved
*
* @param rings - List of rings
* @returns Are there multiple outer rings
*/
export function hasMultipleOuterRings(rings: Point2D[][]): boolean {
// Following https://github.com/mapbox/vector-tile-js/blob/77851380b63b07fd0af3d5a3f144cc86fb39fdd1/lib/vectortilefeature.js#L197
const len = rings.length;
for (let i = 0, direction; i < len; i++) {
const area = calculateSignedArea(rings[i]);
if (area === 0) continue;
if (direction === undefined) {
// Keep the direction of the first ring
direction = area < 0;
} else if (direction === area < 0) {
// Same direction as the first ring -> a second outer ring
return true;
}
}
return false;
}