MetadataClassProperty.js 29 KB

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