Skip to content

Commit

Permalink
First pass for custom semantic schema registration
Browse files Browse the repository at this point in the history
  • Loading branch information
javagl committed Dec 17, 2024
1 parent 43f941e commit bce9e5c
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 239 deletions.
18 changes: 18 additions & 0 deletions src/validation/ValidationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ export class ValidationContext {
*/
private _activeTilesetUris: Set<string>;

/**
* The set of schema objects that will be used for validating
* metadata class property semantics, in the
* `ClassPropertySemanticsValidator`.
*/
private _semanticMatchingSchemas: Set<any>;

constructor(
baseUri: string,
resourceResolver: ResourceResolver,
Expand All @@ -74,6 +81,7 @@ export class ValidationContext {
this._resourceResolver = resourceResolver;
this._extensionsFound = new Set<string>();
this._activeTilesetUris = new Set<string>();
this._semanticMatchingSchemas = new Set<any>();
}

/**
Expand Down Expand Up @@ -105,6 +113,7 @@ export class ValidationContext {
);
derived._extensionsFound = new Set<string>();
derived._activeTilesetUris = this._activeTilesetUris;
derived._semanticMatchingSchemas = this._semanticMatchingSchemas;
return derived;
}

Expand Down Expand Up @@ -140,6 +149,7 @@ export class ValidationContext {
);
derived._extensionsFound = new Set<string>();
derived._activeTilesetUris = this._activeTilesetUris;
derived._semanticMatchingSchemas = this._semanticMatchingSchemas;
return derived;
}

Expand Down Expand Up @@ -181,6 +191,14 @@ export class ValidationContext {
return this._activeTilesetUris.has(uri);
}

addSemanticMatchingSchema(schema: any) {
this._semanticMatchingSchemas.add(schema);
}

getSemanticMatchingSchemas(): any[] {
return [...this._semanticMatchingSchemas];
}

getOptions(): ValidationOptions {
return this._options;
}
Expand Down
269 changes: 269 additions & 0 deletions src/validation/ValidationContexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
import fs from "fs";

import { ValidationContext } from "./ValidationContext";

/**
* Methods related to `ValidationContext` instances
*/
export class ValidationContexts {
/**
* The metadata schema that contains the definitions of the
* Cesium Metadata semantics.
*
* Note that this is not a valid `Schema` object, for the reasons
* described in `addSemanticMatchingSchema`
*/
private static cesiumMetadataSemanticsSchema = {
id: "CesiumMetadataSemantics-0.0.1",
classes: {
GeneralSemantics: {
properties: {
ID: {
description: "The unique identifier for the entity.",
type: "STRING",
},
NAME: {
description:
"The name of the entity. Names should be human-readable, and do not have to be unique.",
type: "STRING",
},
DESCRIPTION: {
description:
"Description of the entity. Typically at least a phrase, and possibly several sentences or paragraphs.",
type: "STRING",
},
ATTRIBUTION_IDS: {
description:
"List of attribution IDs that index into a global list of attribution strings. This semantic may be assigned to metadata at any level of granularity including tileset, group, subtree, tile, content, feature, vertex, and texel granularity. The global list of attribution strings is located in a tileset or subtree with the property semantic ATTRIBUTION_STRINGS. The following precedence order is used to locate the attribution strings: first the containing subtree (if applicable), then the containing external tileset (if applicable), and finally the root tileset.",
type: "SCALAR",
array: true,
componentType: "UINT(8|16|32|64)",
},
ATTRIBUTION_STRINGS: {
description:
"List of attribution strings. Each string contains information about a data provider or copyright text. Text may include embedded markup languages such as HTML. This semantic may be assigned to metadata at any granularity (wherever STRING property values can be encoded). When used in combination with ATTRIBUTION_IDS it is assigned to subtrees and tilesets.",
type: "STRING",
array: true,
},
},
},
TilesetMetadataSemantics: {
properties: {
TILESET_FEATURE_ID_LABELS: {
description:
"The union of all the feature ID labels in glTF content using the EXT_mesh_features and EXT_instance_features extensions.",
type: "STRING",
array: true,
},
TILESET_CRS_GEOCENTRIC: {
description:
"The geocentric coordinate reference system (CRS) of the tileset.",
type: "STRING",
},
TILESET_CRS_COORDINATE_EPOCH: {
description:
"The coordinate epoch for coordinates that are referenced to a dynamic CRS such as WGS 84.",
type: "STRING",
},
},
},
TileMetadataSemantics: {
properties: {
TILE_BOUNDING_BOX: {
description:
"The bounding volume of the tile, expressed as a box. Equivalent to tile.boundingVolume.box.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 12,
},
TILE_BOUNDING_REGION: {
description:
"The bounding volume of the tile, expressed as a region. Equivalent to tile.boundingVolume.region.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 6,
},
TILE_BOUNDING_SPHERE: {
description:
"The bounding volume of the tile, expressed as a sphere. Equivalent to tile.boundingVolume.sphere.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 4,
},
TILE_BOUNDING_S2_CELL: {
description:
"The bounding volume of the tile, expressed as an S2 Cell ID using the 64-bit representation instead of the hexadecimal representation. Only applicable to 3DTILES_bounding_volume_S2.",
type: "SCALAR",
componentType: "UINT64",
},
TILE_MINIMUM_HEIGHT: {
description:
"The minimum height of the tile above (or below) the WGS84 ellipsoid.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
},
TILE_MAXIMUM_HEIGHT: {
description:
"The maximum height of the tile above (or below) the WGS84 ellipsoid.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
},
TILE_HORIZON_OCCLUSION_POINT: {
description:
"The horizon occlusion point of the tile expressed in an ellipsoid-scaled fixed frame. If this point is below the horizon, the entire tile is below the horizon.",
type: "VEC3",
componentType: "FLOAT(32|64)",
},
TILE_GEOMETRIC_ERROR: {
description:
"The geometric error of the tile. Equivalent to tile.geometricError.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
},
TILE_REFINE: {
description:
"The tile refinement type. Valid values are 0 (ADD) and 1 (REPLACE). Equivalent to tile.refine.",
type: "SCALAR",
componentType: "UINT8",
},
TILE_TRANSFORM: {
description: "The tile transform. Equivalent to tile.transform.",
type: "MAT4",
componentType: "FLOAT(32|64)",
},
},
},
ContentMetadataSemantics: {
properties: {
CONTENT_BOUNDING_BOX: {
description:
"The bounding volume of the content of a tile, expressed as a box. Equivalent to tile.content.boundingVolume.box.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 12,
},
CONTENT_BOUNDING_REGION: {
description:
"The bounding volume of the content of a tile, expressed as a region. Equivalent to tile.content.boundingVolume.region.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 6,
},
CONTENT_BOUNDING_SPHERE: {
description:
"The bounding volume of the content of a tile, expressed as a sphere. Equivalent to tile.content.boundingVolume.sphere.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
array: true,
count: 4,
},
CONTENT_BOUNDING_S2_CELL: {
description:
"The bounding volume of the content of a tile, expressed as an S2 Cell ID using the 64-bit representation instead of the hexadecimal representation. Only applicable to 3DTILES_bounding_volume_S2.",
type: "SCALAR",
componentType: "UINT64",
},
CONTENT_MINIMUM_HEIGHT: {
description:
"The minimum height of the content of a tile above (or below) the WGS84 ellipsoid.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
},
CONTENT_MAXIMUM_HEIGHT: {
description:
"The maximum height of the content of a tile above (or below) the WGS84 ellipsoid.",
type: "SCALAR",
componentType: "FLOAT(32|64)",
},
CONTENT_HORIZON_OCCLUSION_POINT: {
description:
"The horizon occlusion point of the content of a tile expressed in an ellipsoid-scaled fixed frame. If this point is below the horizon, the entire content is below the horizon.",
type: "VEC3",
componentType: "FLOAT(32|64)",
},
CONTENT_URI: {
description:
"The content uri. Overrides the implicit tile's generated content uri. Equivalent to tile.content.uri",
type: "STRING",
componentType: "FLOAT(32|64)",
},
CONTENT_GROUP_ID: {
description:
"The content group ID. Equivalent to tile.content.group.",
type: "SCALAR",
componentType: "UINT(8|16|32|64)",
},
},
},
},
};

/**
* Initialize the schemas that are used for matching metadata property
* semantics.
*
* This will register the default Cesium Metadata Semantic definitions,
* as well as the semantic definitions from the specified schema files.
*
* @param context - The validation context
* @param schemaFileNames - The schema file names
*/
static initializeSemanticMatchingSchemas(
context: ValidationContext,
schemaFileNames: string[] | undefined
) {
context.addSemanticMatchingSchema(
ValidationContexts.cesiumMetadataSemanticsSchema
);
if (schemaFileNames) {
for (const schemaFileName of schemaFileNames) {
console.log(
"Registering metadata schema semantic definitions from ",
schemaFileName
);
ValidationContexts.addSemanticMatchingSchema(context, schemaFileName);
}
}
}

/**
* Add the specified schema to the given context, as a schema to be used
* for matching metadata property semantics.
*
* The specified file is supposed to contain a full, valid metadata schema,
* where the property names are just semantic names.
*
* To support legacy semantic definitions, it is allowed for the
* `matchingSchema.classes[className].properties[semanticName].componentType`
* to be a string that is used for creating a regular expression that the
* actual `componentType` has to match. E.g. this may be `"FLOAT(32|64)"`
* when the component type can either be `FLOAT32` or `FLOAT64`.
*
* Eventually, it might make sense to make the component types
* unambiguous, so that the semantics definition is actually
* a proper `Schema`. This could be achieved by specific semantics
* like `GEOMETRIC_ERROR_FLOAT32`.
*
* See https://github.com/CesiumGS/3d-tiles/issues/643
*
* @param context - The validation context
* @param schemaFileName - The schema file name
*/
private static addSemanticMatchingSchema(
context: ValidationContext,
schemaFileName: string
) {
try {
const fileContents = fs.readFileSync(schemaFileName);
const schema = JSON.parse(fileContents.toString());
context.addSemanticMatchingSchema(schema);
} catch (e) {
console.error(e);
}
}
}
21 changes: 20 additions & 1 deletion src/validation/ValidationOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export class ValidationOptions {
*/
private _excludeContentTypes: string[] | undefined;

/**
* The names of files that contain metadata schemas with
* semantic definitions
*/
private _semanticSchemaFileNames: string[] | undefined;

/**
* Default constructor.
*
Expand All @@ -48,10 +54,11 @@ export class ValidationOptions {
this._contentValidationIssueSeverity = ValidationIssueSeverity.INFO;
this._includeContentTypes = undefined;
this._excludeContentTypes = undefined;
this._semanticSchemaFileNames = undefined;
}

/**
* The flag that incicates whether content data should
* The flag that indicates whether content data should
* be validated at all. When this is `false`, then
* all content data validations will be skipped.
*/
Expand Down Expand Up @@ -138,6 +145,18 @@ export class ValidationOptions {
this._excludeContentTypes = value;
}

/**
* The names of files that contain metadata schema definitions
* for the valid metadata semantics
*/
get semanticSchemaFileNames(): string[] | undefined {
return this._semanticSchemaFileNames;
}

set semanticSchemaFileNames(value: string[] | undefined) {
this._semanticSchemaFileNames = value;
}

/**
* Creates a new `ValidationOptions` object where each property is
* initialized from the given JSON object.
Expand Down
Loading

0 comments on commit bce9e5c

Please sign in to comment.