Skip to content

Commit dd74a95

Browse files
committed
Fix false-positive geometry metadata violations on place-less features
The `/req/{prisms,polyhedra,circular-arcs}/metadata` rules detect whether a JSON-FG document contains a geometry of the relevant type by matching `place.type` against an enum. The `if` subschema only used JSON Schema `properties`, which is vacuously satisfied when the property is absent. As a result, a FeatureCollection containing any feature without a `place` member (or a `place` without a `type`) matched the `features.contains` subschema, which made the rule demand the conformance-class URI even though no geometry of that type is present — a false positive. This is what happens for the OGC workshop document `3D_polyhedron_dom_FG.json`: its first feature has no `place` member, so the collection was wrongly flagged with `/req/prisms/metadata` (and would also be flagged for polyhedra/circular-arcs) despite containing no Prism geometries. Require `place` (as an object) and `place.type` in the matched subschema so a feature only counts when it actually carries a geometry of the target type. Adds regression tests covering a FeatureCollection with a place-less feature.
1 parent 1ce8900 commit dd74a95

6 files changed

Lines changed: 51 additions & 3 deletions

File tree

src/specs/json-fg/rulesets/circular-arc.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ describe('/req/circular-arcs/metadata', () => {
2121
expect(violations).toHaveLength(0);
2222
});
2323

24+
test('Succeeds when a feature collection has a place-less feature and no circular arc geometry (regression: absent "place" must not trip the rule)', async () => {
25+
const { place: _omitted, ...placelessFeature } = featureCollectionDoc.features[0];
26+
27+
const violations = await spectral.run({
28+
...featureCollectionDoc,
29+
features: [placelessFeature, ...featureCollectionDoc.features.slice(1)],
30+
});
31+
32+
expect(violations.filter(v => v.code === '/req/circular-arcs/metadata')).toHaveLength(0);
33+
});
34+
2435
test('Fails when a feature place has type "CircularString" and does not include the Circular Arcs conformance class', async () => {
2536
const violations = await spectral.run({
2637
...featureDoc,

src/specs/json-fg/rulesets/circular-arcs.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ const circularArcs: RulesetDefinition = {
3030
if: {
3131
anyOf: [
3232
{
33-
required: ['type'],
33+
required: ['type', 'place'],
3434
properties: {
3535
type: {
3636
const: 'Feature',
3737
},
3838
place: {
39+
type: 'object',
40+
required: ['type'],
3941
properties: {
4042
type: {
4143
enum: CIRCULAR_ARC_TYPES,
@@ -52,8 +54,11 @@ const circularArcs: RulesetDefinition = {
5254
},
5355
features: {
5456
contains: {
57+
required: ['place'],
5558
properties: {
5659
place: {
60+
type: 'object',
61+
required: ['type'],
5762
properties: {
5863
type: {
5964
enum: CIRCULAR_ARC_TYPES,

src/specs/json-fg/rulesets/polyhedra.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ describe('/req/polyhedra/metadata', () => {
2121
expect(violations).toHaveLength(0);
2222
});
2323

24+
test('Succeeds when a feature collection has a place-less feature and no polyhedron geometry (regression: absent "place" must not trip the rule)', async () => {
25+
const { place: _omitted, ...placelessFeature } = featureCollectionDoc.features[0];
26+
27+
const violations = await spectral.run({
28+
...featureCollectionDoc,
29+
features: [placelessFeature, ...featureCollectionDoc.features.slice(1)],
30+
});
31+
32+
expect(violations.filter(v => v.code === '/req/polyhedra/metadata')).toHaveLength(0);
33+
});
34+
2435
test('Fails when a feature place has type "Polyhedron" and does not include the Polyhedra conformance class', async () => {
2536
const violations = await spectral.run({
2637
...featureDoc,

src/specs/json-fg/rulesets/polyhedra.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ const polyhedra: RulesetDefinition = {
2525
if: {
2626
anyOf: [
2727
{
28-
required: ['type'],
28+
required: ['type', 'place'],
2929
properties: {
3030
type: {
3131
const: 'Feature',
3232
},
3333
place: {
34+
type: 'object',
35+
required: ['type'],
3436
properties: {
3537
type: {
3638
enum: POLYHEDRON_TYPES,
@@ -47,8 +49,11 @@ const polyhedra: RulesetDefinition = {
4749
},
4850
features: {
4951
contains: {
52+
required: ['place'],
5053
properties: {
5154
place: {
55+
type: 'object',
56+
required: ['type'],
5257
properties: {
5358
type: {
5459
enum: POLYHEDRON_TYPES,

src/specs/json-fg/rulesets/prisms.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ describe('/req/prisms/metadata', () => {
2828
expect(violations).toHaveLength(0);
2929
});
3030

31+
test('Succeeds when a feature collection has a place-less feature and no prism geometry (regression: absent "place" must not trip the rule)', async () => {
32+
const { place: _omitted, ...placelessFeature } = featureCollectionDoc.features[0];
33+
34+
const violations = await spectral.run({
35+
...featureCollectionDoc,
36+
features: [placelessFeature, ...featureCollectionDoc.features.slice(1)],
37+
});
38+
39+
expect(violations.filter(v => v.code === '/req/prisms/metadata')).toHaveLength(0);
40+
});
41+
3142
test('Fails when a feature place has type "Prism" and does not include the Prisms conformance class', async () => {
3243
const violations = await spectral.run({
3344
...featureDoc,

src/specs/json-fg/rulesets/prisms.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ const prisms: RulesetDefinition = {
2626
if: {
2727
anyOf: [
2828
{
29-
required: ['type'],
29+
required: ['type', 'place'],
3030
properties: {
3131
type: {
3232
const: 'Feature',
3333
},
3434
place: {
35+
type: 'object',
36+
required: ['type'],
3537
properties: {
3638
type: {
3739
enum: PRISM_TYPES,
@@ -48,8 +50,11 @@ const prisms: RulesetDefinition = {
4850
},
4951
features: {
5052
contains: {
53+
required: ['place'],
5154
properties: {
5255
place: {
56+
type: 'object',
57+
required: ['type'],
5358
properties: {
5459
type: {
5560
enum: PRISM_TYPES,

0 commit comments

Comments
 (0)