MetadataClassProperty.js 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import Cartesian4 from "../Core/Cartesian4.js";
  4. import Check from "../Core/Check.js";
  5. import clone from "../Core/clone.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defined from "../Core/defined.js";
  8. import DeveloperError from "../Core/DeveloperError.js";
  9. import Matrix2 from "../Core/Matrix2.js";
  10. import Matrix3 from "../Core/Matrix3.js";
  11. import Matrix4 from "../Core/Matrix4.js";
  12. import MetadataType from "./MetadataType.js";
  13. import MetadataComponentType from "./MetadataComponentType.js";
  14. /**
  15. * A metadata property, as part of a {@link MetadataClass}.
  16. * <p>
  17. * See the {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata|3D Metadata Specification} for 3D Tiles
  18. * </p>
  19. *
  20. * @param {object} options Object with the following properties:
  21. * @param {string} options.id The ID of the property.
  22. * @param {MetadataType} options.type The type of the property such as SCALAR, VEC2, VEC3.
  23. * @param {MetadataComponentType} [options.componentType] The component type of the property. This includes integer (e.g. INT8 or UINT16), and floating point (FLOAT32 and FLOAT64) values.
  24. * @param {MetadataEnum} [options.enumType] The enum type of the property. Only defined when type is ENUM.
  25. * @param {boolean} [options.isArray=false] True if a property is an array (either fixed length or variable length), false otherwise.
  26. * @param {boolean} [options.isVariableLengthArray=false] True if a property is a variable length array, false otherwise.
  27. * @param {number} [options.arrayLength] The number of array elements. Only defined for fixed length arrays.
  28. * @param {boolean} [options.normalized=false] Whether the property is normalized.
  29. * @param {number|number[]|number[][]} [options.min] A number or an array of numbers storing the minimum allowable value of this property. Only defined when type is a numeric type.
  30. * @param {number|number[]|number[][]} [options.max] A number or an array of numbers storing the maximum allowable value of this property. Only defined when type is a numeric type.
  31. * @param {number|number[]|number[][]} [options.offset] The offset to be added to property values as part of the value transform.
  32. * @param {number|number[]|number[][]} [options.scale] The scale to be multiplied to property values as part of the value transform.
  33. * @param {boolean|number|string|Array} [options.noData] The no-data sentinel value that represents null values.
  34. * @param {boolean|number|string|Array} [options.default] A default value to use when an entity's property value is not defined.
  35. * @param {boolean} [options.required=false] Whether the property is required.
  36. * @param {string} [options.name] The name of the property.
  37. * @param {string} [options.description] The description of the property.
  38. * @param {string} [options.semantic] An identifier that describes how this property should be interpreted.
  39. * @param {*} [options.extras] Extra user-defined properties.
  40. * @param {object} [options.extensions] An object containing extensions.
  41. *
  42. * @alias MetadataClassProperty
  43. * @constructor
  44. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  45. */
  46. function MetadataClassProperty(options) {
  47. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  48. const id = options.id;
  49. const type = options.type;
  50. //>>includeStart('debug', pragmas.debug);
  51. Check.typeOf.string("options.id", id);
  52. Check.typeOf.string("options.type", type);
  53. //>>includeEnd('debug');
  54. const componentType = options.componentType;
  55. const enumType = options.enumType;
  56. const normalized =
  57. defined(componentType) &&
  58. MetadataComponentType.isIntegerType(componentType) &&
  59. defaultValue(options.normalized, false);
  60. // Basic information about this property
  61. this._id = id;
  62. this._name = options.name;
  63. this._description = options.description;
  64. this._semantic = options.semantic;
  65. // Only for unit testing purposes, not documented in the API
  66. this._isLegacyExtension = options.isLegacyExtension;
  67. // Details about basic types
  68. this._type = type;
  69. this._componentType = componentType;
  70. this._enumType = enumType;
  71. this._valueType = defined(enumType) ? enumType.valueType : componentType;
  72. // Details about arrays
  73. this._isArray = defaultValue(options.isArray, false);
  74. this._isVariableLengthArray = defaultValue(
  75. options.isVariableLengthArray,
  76. false
  77. );
  78. this._arrayLength = options.arrayLength;
  79. // min and max allowed values
  80. this._min = clone(options.min, true);
  81. this._max = clone(options.max, true);
  82. // properties that adjust the range of metadata values
  83. this._normalized = normalized;
  84. let offset = clone(options.offset, true);
  85. let scale = clone(options.scale, true);
  86. const hasValueTransform = defined(offset) || defined(scale);
  87. const enableNestedArrays = true;
  88. if (!defined(offset)) {
  89. offset = this.expandConstant(0, enableNestedArrays);
  90. }
  91. if (!defined(scale)) {
  92. scale = this.expandConstant(1, enableNestedArrays);
  93. }
  94. this._offset = offset;
  95. this._scale = scale;
  96. this._hasValueTransform = hasValueTransform;
  97. // sentinel value for missing data, and a default value to use
  98. // in its place if needed.
  99. this._noData = clone(options.noData, true);
  100. // For vector and array types, this is stored as an array of values.
  101. this._default = clone(options.default, true);
  102. this._required = defaultValue(options.required, true);
  103. // extras and extensions
  104. this._extras = clone(options.extras, true);
  105. this._extensions = clone(options.extensions, true);
  106. }
  107. /**
  108. * Creates a {@link MetadataClassProperty} from either 3D Tiles 1.1, 3DTILES_metadata, EXT_structural_metadata, or EXT_feature_metadata.
  109. *
  110. * @param {object} options Object with the following properties:
  111. * @param {string} options.id The ID of the property.
  112. * @param {object} options.property The property JSON object.
  113. * @param {Object<string, MetadataEnum>} [options.enums] A dictionary of enums.
  114. *
  115. * @returns {MetadataClassProperty} The newly created metadata class property.
  116. *
  117. * @private
  118. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  119. */
  120. MetadataClassProperty.fromJson = function (options) {
  121. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  122. const id = options.id;
  123. const property = options.property;
  124. //>>includeStart('debug', pragmas.debug);
  125. Check.typeOf.string("options.id", id);
  126. Check.typeOf.object("options.property", property);
  127. Check.typeOf.string("options.property.type", property.type);
  128. //>>includeEnd('debug');
  129. // Try to determine if this is the legacy extension. This is not
  130. // always possible, as there are some types that are valid in both
  131. // extensions.
  132. const isLegacyExtension = isLegacy(property);
  133. const parsedType = parseType(property, options.enums);
  134. // EXT_feature_metadata had an optional flag, while EXT_structural_metadata
  135. // has a required flag. The defaults are not the same, and there's some cases
  136. // like {type: BOOLEAN} that are ambiguous. Coalesce this into a single
  137. // required flag
  138. let required;
  139. if (!defined(isLegacyExtension)) {
  140. // Impossible to tell which extension was used, so don't require
  141. // the property
  142. required = false;
  143. } else if (isLegacyExtension) {
  144. required = defined(property.optional) ? !property.optional : true;
  145. } else {
  146. required = defaultValue(property.required, false);
  147. }
  148. return new MetadataClassProperty({
  149. id: id,
  150. type: parsedType.type,
  151. componentType: parsedType.componentType,
  152. enumType: parsedType.enumType,
  153. isArray: parsedType.isArray,
  154. isVariableLengthArray: parsedType.isVariableLengthArray,
  155. arrayLength: parsedType.arrayLength,
  156. normalized: property.normalized,
  157. min: property.min,
  158. max: property.max,
  159. offset: property.offset,
  160. scale: property.scale,
  161. noData: property.noData,
  162. default: property.default,
  163. required: required,
  164. name: property.name,
  165. description: property.description,
  166. semantic: property.semantic,
  167. extras: property.extras,
  168. extensions: property.extensions,
  169. isLegacyExtension: isLegacyExtension,
  170. });
  171. };
  172. Object.defineProperties(MetadataClassProperty.prototype, {
  173. /**
  174. * The ID of the property.
  175. *
  176. * @memberof MetadataClassProperty.prototype
  177. * @type {string}
  178. * @readonly
  179. */
  180. id: {
  181. get: function () {
  182. return this._id;
  183. },
  184. },
  185. /**
  186. * The name of the property.
  187. *
  188. * @memberof MetadataClassProperty.prototype
  189. * @type {string}
  190. * @readonly
  191. */
  192. name: {
  193. get: function () {
  194. return this._name;
  195. },
  196. },
  197. /**
  198. * The description of the property.
  199. *
  200. * @memberof MetadataClassProperty.prototype
  201. * @type {string}
  202. * @readonly
  203. */
  204. description: {
  205. get: function () {
  206. return this._description;
  207. },
  208. },
  209. /**
  210. * The type of the property such as SCALAR, VEC2, VEC3
  211. *
  212. * @memberof MetadataClassProperty.prototype
  213. * @type {MetadataType}
  214. * @readonly
  215. */
  216. type: {
  217. get: function () {
  218. return this._type;
  219. },
  220. },
  221. /**
  222. * The enum type of the property. Only defined when type is ENUM.
  223. *
  224. * @memberof MetadataClassProperty.prototype
  225. * @type {MetadataEnum}
  226. * @readonly
  227. */
  228. enumType: {
  229. get: function () {
  230. return this._enumType;
  231. },
  232. },
  233. /**
  234. * The component type of the property. This includes integer
  235. * (e.g. INT8 or UINT16), and floating point (FLOAT32 and FLOAT64) values
  236. *
  237. * @memberof MetadataClassProperty.prototype
  238. * @type {MetadataComponentType}
  239. * @readonly
  240. */
  241. componentType: {
  242. get: function () {
  243. return this._componentType;
  244. },
  245. },
  246. /**
  247. * The datatype used for storing each component of the property. This
  248. * is usually the same as componentType except for ENUM, where this
  249. * returns an integer type
  250. *
  251. * @memberof MetadataClassProperty.prototype
  252. * @type {MetadataComponentType}
  253. * @readonly
  254. * @private
  255. */
  256. valueType: {
  257. get: function () {
  258. return this._valueType;
  259. },
  260. },
  261. /**
  262. * True if a property is an array (either fixed length or variable length),
  263. * false otherwise.
  264. *
  265. * @memberof MetadataClassProperty.prototype
  266. * @type {boolean}
  267. * @readonly
  268. */
  269. isArray: {
  270. get: function () {
  271. return this._isArray;
  272. },
  273. },
  274. /**
  275. * True if a property is a variable length array, false otherwise.
  276. *
  277. * @memberof MetadataClassProperty.prototype
  278. * @type {boolean}
  279. * @readonly
  280. */
  281. isVariableLengthArray: {
  282. get: function () {
  283. return this._isVariableLengthArray;
  284. },
  285. },
  286. /**
  287. * The number of array elements. Only defined for fixed-size
  288. * arrays.
  289. *
  290. * @memberof MetadataClassProperty.prototype
  291. * @type {number}
  292. * @readonly
  293. */
  294. arrayLength: {
  295. get: function () {
  296. return this._arrayLength;
  297. },
  298. },
  299. /**
  300. * Whether the property is normalized.
  301. *
  302. * @memberof MetadataClassProperty.prototype
  303. * @type {boolean}
  304. * @readonly
  305. */
  306. normalized: {
  307. get: function () {
  308. return this._normalized;
  309. },
  310. },
  311. /**
  312. * A number or an array of numbers storing the maximum allowable value of this property. Only defined when type is a numeric type.
  313. *
  314. * @memberof MetadataClassProperty.prototype
  315. * @type {number|number[]|number[][]}
  316. * @readonly
  317. */
  318. max: {
  319. get: function () {
  320. return this._max;
  321. },
  322. },
  323. /**
  324. * A number or an array of numbers storing the minimum allowable value of this property. Only defined when type is a numeric type.
  325. *
  326. * @memberof MetadataClassProperty.prototype
  327. * @type {number|number[]|number[][]}
  328. * @readonly
  329. */
  330. min: {
  331. get: function () {
  332. return this._min;
  333. },
  334. },
  335. /**
  336. * The no-data sentinel value that represents null values
  337. *
  338. * @memberof MetadataClassProperty.prototype
  339. * @type {boolean|number|string|Array}
  340. * @readonly
  341. */
  342. noData: {
  343. get: function () {
  344. return this._noData;
  345. },
  346. },
  347. /**
  348. * A default value to use when an entity's property value is not defined.
  349. *
  350. * @memberof MetadataClassProperty.prototype
  351. * @type {boolean|number|string|Array}
  352. * @readonly
  353. */
  354. default: {
  355. get: function () {
  356. return this._default;
  357. },
  358. },
  359. /**
  360. * Whether the property is required.
  361. *
  362. * @memberof MetadataClassProperty.prototype
  363. * @type {boolean}
  364. * @readonly
  365. */
  366. required: {
  367. get: function () {
  368. return this._required;
  369. },
  370. },
  371. /**
  372. * An identifier that describes how this property should be interpreted.
  373. *
  374. * @memberof MetadataClassProperty.prototype
  375. * @type {string}
  376. * @readonly
  377. */
  378. semantic: {
  379. get: function () {
  380. return this._semantic;
  381. },
  382. },
  383. /**
  384. * True if offset/scale should be applied. If both offset/scale were
  385. * undefined, they default to identity so this property is set false
  386. *
  387. * @memberof MetadataClassProperty.prototype
  388. * @type {boolean}
  389. * @readonly
  390. * @private
  391. */
  392. hasValueTransform: {
  393. get: function () {
  394. return this._hasValueTransform;
  395. },
  396. },
  397. /**
  398. * The offset to be added to property values as part of the value transform.
  399. *
  400. * @memberof MetadataClassProperty.prototype
  401. * @type {number|number[]|number[][]}
  402. * @readonly
  403. */
  404. offset: {
  405. get: function () {
  406. return this._offset;
  407. },
  408. },
  409. /**
  410. * The scale to be multiplied to property values as part of the value transform.
  411. *
  412. * @memberof MetadataClassProperty.prototype
  413. * @type {number|number[]|number[][]}
  414. * @readonly
  415. */
  416. scale: {
  417. get: function () {
  418. return this._scale;
  419. },
  420. },
  421. /**
  422. * Extra user-defined properties.
  423. *
  424. * @memberof MetadataClassProperty.prototype
  425. * @type {*}
  426. * @readonly
  427. */
  428. extras: {
  429. get: function () {
  430. return this._extras;
  431. },
  432. },
  433. /**
  434. * An object containing extensions.
  435. *
  436. * @memberof MetadataClassProperty.prototype
  437. * @type {object}
  438. * @readonly
  439. */
  440. extensions: {
  441. get: function () {
  442. return this._extensions;
  443. },
  444. },
  445. });
  446. function isLegacy(property) {
  447. if (property.type === "ARRAY") {
  448. return true;
  449. }
  450. // New property types in EXT_structural_metadata
  451. const type = property.type;
  452. if (
  453. type === MetadataType.SCALAR ||
  454. MetadataType.isMatrixType(type) ||
  455. MetadataType.isVectorType(type)
  456. ) {
  457. return false;
  458. }
  459. // EXT_feature_metadata allowed numeric types as a type. Now they are
  460. // represented as {type: SINGLE, componentType: type}
  461. if (defined(MetadataComponentType[type])) {
  462. return true;
  463. }
  464. // New properties in EXT_structural_metadata
  465. if (
  466. defined(property.noData) ||
  467. defined(property.scale) ||
  468. defined(property.offset) ||
  469. defined(property.required) ||
  470. defined(property.count) ||
  471. defined(property.array)
  472. ) {
  473. return false;
  474. }
  475. // Properties that only exist in EXT_feature_metadata
  476. if (defined(property.optional)) {
  477. return false;
  478. }
  479. // impossible to tell, give up.
  480. return undefined;
  481. }
  482. function parseType(property, enums) {
  483. const type = property.type;
  484. const componentType = property.componentType;
  485. // EXT_feature_metadata had an ARRAY type. This is now handled
  486. // with array + count, so some details need to be transcoded
  487. const isLegacyArray = type === "ARRAY";
  488. let isArray;
  489. let arrayLength;
  490. let isVariableLengthArray;
  491. if (isLegacyArray) {
  492. // definitely EXT_feature_metadata
  493. isArray = true;
  494. arrayLength = property.componentCount;
  495. isVariableLengthArray = !defined(arrayLength);
  496. } else if (property.array) {
  497. isArray = true;
  498. arrayLength = property.count;
  499. isVariableLengthArray = !defined(property.count);
  500. } else {
  501. // Could be either extension. Some cases are impossible to distinguish
  502. // Default to a single value
  503. isArray = false;
  504. arrayLength = undefined;
  505. isVariableLengthArray = false;
  506. }
  507. let enumType;
  508. if (defined(property.enumType)) {
  509. enumType = enums[property.enumType];
  510. }
  511. // In both EXT_feature_metadata and EXT_structural_metadata, ENUM appears
  512. // as a type.
  513. if (type === MetadataType.ENUM) {
  514. return {
  515. type: type,
  516. componentType: undefined,
  517. enumType: enumType,
  518. valueType: enumType.valueType,
  519. isArray: isArray,
  520. isVariableLengthArray: isVariableLengthArray,
  521. arrayLength: arrayLength,
  522. };
  523. }
  524. // In EXT_feature_metadata, ENUM also appears as an ARRAY componentType
  525. if (isLegacyArray && componentType === MetadataType.ENUM) {
  526. return {
  527. type: componentType,
  528. componentType: undefined,
  529. enumType: enumType,
  530. valueType: enumType.valueType,
  531. isArray: isArray,
  532. isVariableLengthArray: isVariableLengthArray,
  533. arrayLength: arrayLength,
  534. };
  535. }
  536. // EXT_structural_metadata only: SCALAR, VECN and MATN
  537. if (
  538. type === MetadataType.SCALAR ||
  539. MetadataType.isMatrixType(type) ||
  540. MetadataType.isVectorType(type)
  541. ) {
  542. return {
  543. type: type,
  544. componentType: componentType,
  545. enumType: undefined,
  546. valueType: componentType,
  547. isArray: isArray,
  548. isVariableLengthArray: isVariableLengthArray,
  549. arrayLength: arrayLength,
  550. };
  551. }
  552. // In both EXT_structural_metadata and EXT_feature_metadata,
  553. // BOOLEAN and STRING appear as types
  554. if (type === MetadataType.BOOLEAN || type === MetadataType.STRING) {
  555. return {
  556. type: type,
  557. componentType: undefined,
  558. enumType: undefined,
  559. valueType: undefined,
  560. isArray: isArray,
  561. isVariableLengthArray: isVariableLengthArray,
  562. arrayLength: arrayLength,
  563. };
  564. }
  565. // EXT_feature_metadata also allows BOOLEAN and STRING as an ARRAY
  566. // componentType
  567. if (
  568. isLegacyArray &&
  569. (componentType === MetadataType.BOOLEAN ||
  570. componentType === MetadataType.STRING)
  571. ) {
  572. return {
  573. type: componentType,
  574. componentType: undefined,
  575. enumType: undefined,
  576. valueType: undefined,
  577. isArray: isArray,
  578. isVariableLengthArray: isVariableLengthArray,
  579. arrayLength: arrayLength,
  580. };
  581. }
  582. // Both EXT_feature_metadata and EXT_structural_metadata allow numeric types like
  583. // INT32 or FLOAT64 as a componentType.
  584. if (defined(componentType) && defined(MetadataComponentType[componentType])) {
  585. return {
  586. type: MetadataType.SCALAR,
  587. componentType: componentType,
  588. enumType: undefined,
  589. valueType: componentType,
  590. isArray: isArray,
  591. isVariableLengthArray: isVariableLengthArray,
  592. arrayLength: arrayLength,
  593. };
  594. }
  595. // EXT_feature_metadata: integer and float types were allowed as types,
  596. // but now these are expressed as {type: SCALAR, componentType: type}
  597. if (defined(MetadataComponentType[type])) {
  598. return {
  599. type: MetadataType.SCALAR,
  600. componentType: type,
  601. enumType: undefined,
  602. valueType: type,
  603. isArray: isArray,
  604. isVariableLengthArray: isVariableLengthArray,
  605. arrayLength: arrayLength,
  606. };
  607. }
  608. //>>includeStart('debug', pragmas.debug);
  609. throw new DeveloperError(
  610. `unknown metadata type {type: ${type}, componentType: ${componentType})`
  611. );
  612. //>>includeEnd('debug');
  613. }
  614. /**
  615. * Normalizes integer property values. If the property is not normalized
  616. * the value is returned unmodified.
  617. * <p>
  618. * Given the way normalization is defined in {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#normalized-values|the 3D Metadata Specification},
  619. * normalize and unnormalize are almost, but not quite inverses. In particular,
  620. * the smallest signed integer value will be off by one after normalizing and
  621. * unnormalizing. See
  622. * {@link https://www.desmos.com/calculator/nledg1evut|this Desmos graph} for
  623. * an example using INT8.
  624. * </p>
  625. * <p>
  626. * Furthermore, for 64-bit integer types, there may be a loss of precision
  627. * due to conversion to Number
  628. * </p>
  629. *
  630. * @param {*} value The integer value or array of integer values.
  631. * @returns {*} The normalized value or array of normalized values.
  632. *
  633. * @private
  634. */
  635. MetadataClassProperty.prototype.normalize = function (value) {
  636. if (!this._normalized) {
  637. return value;
  638. }
  639. return normalizeInPlace(
  640. value,
  641. this._valueType,
  642. MetadataComponentType.normalize
  643. );
  644. };
  645. /**
  646. * Unnormalizes integer property values. If the property is not normalized
  647. * the value is returned unmodified.
  648. * <p>
  649. * Given the way normalization is defined in {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/Metadata#normalized-values|the 3D Metadata Specification},
  650. * normalize and unnormalize are almost, but not quite inverses. In particular,
  651. * the smallest signed integer value will be off by one after normalizing and
  652. * unnormalizing. See
  653. * {@link https://www.desmos.com/calculator/nledg1evut|this Desmos graph} for
  654. * an example using INT8.
  655. * </p>
  656. * <p>
  657. * Furthermore, for 64-bit integer types, there may be a loss of precision
  658. * due to conversion to Number
  659. * </p>
  660. *
  661. * @param {*} value The normalized value or array of normalized values.
  662. * @returns {*} The integer value or array of integer values.
  663. *
  664. * @private
  665. */
  666. MetadataClassProperty.prototype.unnormalize = function (value) {
  667. if (!this._normalized) {
  668. return value;
  669. }
  670. return normalizeInPlace(
  671. value,
  672. this._valueType,
  673. MetadataComponentType.unnormalize
  674. );
  675. };
  676. /**
  677. * @private
  678. */
  679. MetadataClassProperty.prototype.applyValueTransform = function (value) {
  680. // variable length arrays do not have a well-defined offset/scale so this
  681. // is forbidden by the spec
  682. if (!this._hasValueTransform || this._isVariableLengthArray) {
  683. return value;
  684. }
  685. return MetadataClassProperty.valueTransformInPlace(
  686. value,
  687. this._offset,
  688. this._scale,
  689. MetadataComponentType.applyValueTransform
  690. );
  691. };
  692. /**
  693. * @private
  694. */
  695. MetadataClassProperty.prototype.unapplyValueTransform = function (value) {
  696. // variable length arrays do not have a well-defined offset/scale so this
  697. // is forbidden by the spec
  698. if (!this._hasValueTransform || this._isVariableLengthArray) {
  699. return value;
  700. }
  701. return MetadataClassProperty.valueTransformInPlace(
  702. value,
  703. this._offset,
  704. this._scale,
  705. MetadataComponentType.unapplyValueTransform
  706. );
  707. };
  708. /**
  709. * @private
  710. */
  711. MetadataClassProperty.prototype.expandConstant = function (
  712. constant,
  713. enableNestedArrays
  714. ) {
  715. enableNestedArrays = defaultValue(enableNestedArrays, false);
  716. const isArray = this._isArray;
  717. const arrayLength = this._arrayLength;
  718. const componentCount = MetadataType.getComponentCount(this._type);
  719. const isNested = isArray && componentCount > 1;
  720. // scalar values can be returned directly
  721. if (!isArray && componentCount === 1) {
  722. return constant;
  723. }
  724. // vector and matrix values
  725. if (!isArray) {
  726. return new Array(componentCount).fill(constant);
  727. }
  728. // arrays of scalars
  729. if (!isNested) {
  730. return new Array(arrayLength).fill(constant);
  731. }
  732. // arrays of vectors/matrices: flattened
  733. if (!enableNestedArrays) {
  734. return new Array(this._arrayLength * componentCount).fill(constant);
  735. }
  736. // array of vectors/matrices: nested
  737. const innerConstant = new Array(componentCount).fill(constant);
  738. // This array fill duplicates the pointer to the inner arrays. Since this is
  739. // intended for use with constants, no need to clone the array.
  740. return new Array(this._arrayLength).fill(innerConstant);
  741. };
  742. /**
  743. * If the value is the noData sentinel, return undefined. Otherwise, return
  744. * the value.
  745. * @param {*} value The raw value
  746. * @returns {*} Either the value or undefined if the value was a no data value.
  747. *
  748. * @private
  749. */
  750. MetadataClassProperty.prototype.handleNoData = function (value) {
  751. const sentinel = this._noData;
  752. if (!defined(sentinel)) {
  753. return value;
  754. }
  755. if (arrayEquals(value, sentinel)) {
  756. return undefined;
  757. }
  758. return value;
  759. };
  760. function arrayEquals(left, right) {
  761. if (!Array.isArray(left)) {
  762. return left === right;
  763. }
  764. if (!Array.isArray(right)) {
  765. return false;
  766. }
  767. if (left.length !== right.length) {
  768. return false;
  769. }
  770. for (let i = 0; i < left.length; i++) {
  771. if (!arrayEquals(left[i], right[i])) {
  772. return false;
  773. }
  774. }
  775. return true;
  776. }
  777. /**
  778. * Unpack VECN values into {@link Cartesian2}, {@link Cartesian3}, or
  779. * {@link Cartesian4} and MATN values into {@link Matrix2}, {@link Matrix3}, or
  780. * {@link Matrix4} depending on N. All other values (including arrays of
  781. * other sizes) are passed through unaltered.
  782. *
  783. * @param {*} value the original, normalized values.
  784. * @param {boolean} [enableNestedArrays=false] If true, arrays of vectors are represented as nested arrays. This is used for JSON encoding but not binary encoding
  785. * @returns {*} The appropriate vector or matrix type if the value is a vector or matrix type, respectively. If the property is an array of vectors or matrices, an array of the appropriate vector or matrix type is returned. Otherwise, the value is returned unaltered.
  786. * @private
  787. */
  788. MetadataClassProperty.prototype.unpackVectorAndMatrixTypes = function (
  789. value,
  790. enableNestedArrays
  791. ) {
  792. enableNestedArrays = defaultValue(enableNestedArrays, false);
  793. const MathType = MetadataType.getMathType(this._type);
  794. const isArray = this._isArray;
  795. const componentCount = MetadataType.getComponentCount(this._type);
  796. const isNested = isArray && componentCount > 1;
  797. if (!defined(MathType)) {
  798. return value;
  799. }
  800. if (enableNestedArrays && isNested) {
  801. return value.map(function (x) {
  802. return MathType.unpack(x);
  803. });
  804. }
  805. if (isArray) {
  806. return MathType.unpackArray(value);
  807. }
  808. return MathType.unpack(value);
  809. };
  810. /**
  811. * Pack a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} into an
  812. * array if this property is an <code>VECN</code>.
  813. * Pack a {@link Matrix2}, {@link Matrix3}, or {@link Matrix4} into an
  814. * array if this property is an <code>MATN</code>.
  815. * All other values (including arrays of other sizes) are passed through unaltered.
  816. *
  817. * @param {*} value The value of this property
  818. * @param {boolean} [enableNestedArrays=false] If true, arrays of vectors are represented as nested arrays. This is used for JSON encoding but not binary encoding
  819. * @returns {*} An array of the appropriate length if the property is a vector or matrix type. Otherwise, the value is returned unaltered.
  820. * @private
  821. */
  822. MetadataClassProperty.prototype.packVectorAndMatrixTypes = function (
  823. value,
  824. enableNestedArrays
  825. ) {
  826. enableNestedArrays = defaultValue(enableNestedArrays, false);
  827. const MathType = MetadataType.getMathType(this._type);
  828. const isArray = this._isArray;
  829. const componentCount = MetadataType.getComponentCount(this._type);
  830. const isNested = isArray && componentCount > 1;
  831. if (!defined(MathType)) {
  832. return value;
  833. }
  834. if (enableNestedArrays && isNested) {
  835. return value.map(function (x) {
  836. return MathType.pack(x, []);
  837. });
  838. }
  839. if (isArray) {
  840. return MathType.packArray(value, []);
  841. }
  842. return MathType.pack(value, []);
  843. };
  844. /**
  845. * Validates whether the given value conforms to the property.
  846. *
  847. * @param {*} value The value.
  848. * @returns {string|undefined} An error message if the value does not conform to the property, otherwise undefined.
  849. * @private
  850. */
  851. MetadataClassProperty.prototype.validate = function (value) {
  852. if (!defined(value) && defined(this._default)) {
  853. // no value, but we have a default to use.
  854. return undefined;
  855. }
  856. if (this._required && !defined(value)) {
  857. return `required property must have a value`;
  858. }
  859. if (this._isArray) {
  860. return validateArray(this, value);
  861. }
  862. return validateSingleValue(this, value);
  863. };
  864. function validateArray(classProperty, value) {
  865. if (!Array.isArray(value)) {
  866. return `value ${value} must be an array`;
  867. }
  868. const length = value.length;
  869. if (
  870. !classProperty._isVariableLengthArray &&
  871. length !== classProperty._arrayLength
  872. ) {
  873. return "Array length does not match property.arrayLength";
  874. }
  875. for (let i = 0; i < length; i++) {
  876. const message = validateSingleValue(classProperty, value[i]);
  877. if (defined(message)) {
  878. return message;
  879. }
  880. }
  881. }
  882. function validateSingleValue(classProperty, value) {
  883. const type = classProperty._type;
  884. const componentType = classProperty._componentType;
  885. const enumType = classProperty._enumType;
  886. const normalized = classProperty._normalized;
  887. if (MetadataType.isVectorType(type)) {
  888. return validateVector(value, type, componentType);
  889. } else if (MetadataType.isMatrixType(type)) {
  890. return validateMatrix(value, type, componentType);
  891. } else if (type === MetadataType.STRING) {
  892. return validateString(value);
  893. } else if (type === MetadataType.BOOLEAN) {
  894. return validateBoolean(value);
  895. } else if (type === MetadataType.ENUM) {
  896. return validateEnum(value, enumType);
  897. }
  898. return validateScalar(value, componentType, normalized);
  899. }
  900. function validateVector(value, type, componentType) {
  901. if (!MetadataComponentType.isVectorCompatible(componentType)) {
  902. return `componentType ${componentType} is incompatible with vector type ${type}`;
  903. }
  904. if (type === MetadataType.VEC2 && !(value instanceof Cartesian2)) {
  905. return `vector value ${value} must be a Cartesian2`;
  906. }
  907. if (type === MetadataType.VEC3 && !(value instanceof Cartesian3)) {
  908. return `vector value ${value} must be a Cartesian3`;
  909. }
  910. if (type === MetadataType.VEC4 && !(value instanceof Cartesian4)) {
  911. return `vector value ${value} must be a Cartesian4`;
  912. }
  913. }
  914. function validateMatrix(value, type, componentType) {
  915. if (!MetadataComponentType.isVectorCompatible(componentType)) {
  916. return `componentType ${componentType} is incompatible with matrix type ${type}`;
  917. }
  918. if (type === MetadataType.MAT2 && !(value instanceof Matrix2)) {
  919. return `matrix value ${value} must be a Matrix2`;
  920. }
  921. if (type === MetadataType.MAT3 && !(value instanceof Matrix3)) {
  922. return `matrix value ${value} must be a Matrix3`;
  923. }
  924. if (type === MetadataType.MAT4 && !(value instanceof Matrix4)) {
  925. return `matrix value ${value} must be a Matrix4`;
  926. }
  927. }
  928. function validateString(value) {
  929. if (typeof value !== "string") {
  930. return getTypeErrorMessage(value, MetadataType.STRING);
  931. }
  932. }
  933. function validateBoolean(value) {
  934. if (typeof value !== "boolean") {
  935. return getTypeErrorMessage(value, MetadataType.BOOLEAN);
  936. }
  937. }
  938. function validateEnum(value, enumType) {
  939. const javascriptType = typeof value;
  940. if (defined(enumType)) {
  941. if (javascriptType !== "string" || !defined(enumType.valuesByName[value])) {
  942. return `value ${value} is not a valid enum name for ${enumType.id}`;
  943. }
  944. return;
  945. }
  946. }
  947. function validateScalar(value, componentType, normalized) {
  948. const javascriptType = typeof value;
  949. switch (componentType) {
  950. case MetadataComponentType.INT8:
  951. case MetadataComponentType.UINT8:
  952. case MetadataComponentType.INT16:
  953. case MetadataComponentType.UINT16:
  954. case MetadataComponentType.INT32:
  955. case MetadataComponentType.UINT32:
  956. case MetadataComponentType.FLOAT32:
  957. case MetadataComponentType.FLOAT64:
  958. if (javascriptType !== "number") {
  959. return getTypeErrorMessage(value, componentType);
  960. }
  961. if (!isFinite(value)) {
  962. return getNonFiniteErrorMessage(value, componentType);
  963. }
  964. return checkInRange(value, componentType, normalized);
  965. case MetadataComponentType.INT64:
  966. case MetadataComponentType.UINT64:
  967. if (javascriptType !== "number" && javascriptType !== "bigint") {
  968. return getTypeErrorMessage(value, componentType);
  969. }
  970. if (javascriptType === "number" && !isFinite(value)) {
  971. return getNonFiniteErrorMessage(value, componentType);
  972. }
  973. return checkInRange(value, componentType, normalized);
  974. }
  975. }
  976. function getTypeErrorMessage(value, type) {
  977. return `value ${value} does not match type ${type}`;
  978. }
  979. function getOutOfRangeErrorMessage(value, type, normalized) {
  980. let errorMessage = `value ${value} is out of range for type ${type}`;
  981. if (normalized) {
  982. errorMessage += " (normalized)";
  983. }
  984. return errorMessage;
  985. }
  986. function checkInRange(value, componentType, normalized) {
  987. if (normalized) {
  988. const min = MetadataComponentType.isUnsignedIntegerType(componentType)
  989. ? 0.0
  990. : -1.0;
  991. const max = 1.0;
  992. if (value < min || value > max) {
  993. return getOutOfRangeErrorMessage(value, componentType, normalized);
  994. }
  995. return;
  996. }
  997. if (
  998. value < MetadataComponentType.getMinimum(componentType) ||
  999. value > MetadataComponentType.getMaximum(componentType)
  1000. ) {
  1001. return getOutOfRangeErrorMessage(value, componentType, normalized);
  1002. }
  1003. }
  1004. function getNonFiniteErrorMessage(value, type) {
  1005. return `value ${value} of type ${type} must be finite`;
  1006. }
  1007. function normalizeInPlace(values, valueType, normalizeFunction) {
  1008. if (!Array.isArray(values)) {
  1009. return normalizeFunction(values, valueType);
  1010. }
  1011. for (let i = 0; i < values.length; i++) {
  1012. values[i] = normalizeInPlace(values[i], valueType, normalizeFunction);
  1013. }
  1014. return values;
  1015. }
  1016. /**
  1017. * @private
  1018. */
  1019. MetadataClassProperty.valueTransformInPlace = function (
  1020. values,
  1021. offsets,
  1022. scales,
  1023. transformationFunction
  1024. ) {
  1025. if (!Array.isArray(values)) {
  1026. // transform a single value
  1027. return transformationFunction(values, offsets, scales);
  1028. }
  1029. for (let i = 0; i < values.length; i++) {
  1030. // offsets and scales must be the same array shape as values.
  1031. values[i] = MetadataClassProperty.valueTransformInPlace(
  1032. values[i],
  1033. offsets[i],
  1034. scales[i],
  1035. transformationFunction
  1036. );
  1037. }
  1038. return values;
  1039. };
  1040. export default MetadataClassProperty;