ReferenceProperty.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*global define*/
  2. define([
  3. '../Core/defined',
  4. '../Core/defineProperties',
  5. '../Core/DeveloperError',
  6. '../Core/Event',
  7. '../Core/RuntimeError',
  8. './Property'
  9. ], function(
  10. defined,
  11. defineProperties,
  12. DeveloperError,
  13. Event,
  14. RuntimeError,
  15. Property) {
  16. "use strict";
  17. function resolveEntity(that) {
  18. var entityIsResolved = true;
  19. if (that._resolveEntity) {
  20. var targetEntity = that._targetCollection.getById(that._targetId);
  21. if (defined(targetEntity)) {
  22. targetEntity.definitionChanged.addEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, that);
  23. that._targetEntity = targetEntity;
  24. that._resolveEntity = false;
  25. } else {
  26. //The property has become detached. It has a valid value but is not currently resolved to an entity in the collection
  27. targetEntity = that._targetEntity;
  28. entityIsResolved = false;
  29. }
  30. if (!defined(targetEntity)) {
  31. throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.');
  32. }
  33. }
  34. return entityIsResolved;
  35. }
  36. function resolve(that) {
  37. var targetProperty = that._targetProperty;
  38. if (that._resolveProperty) {
  39. var entityIsResolved = resolveEntity(that);
  40. var names = that._targetPropertyNames;
  41. targetProperty = that._targetEntity;
  42. var length = names.length;
  43. for (var i = 0; i < length && defined(targetProperty); i++) {
  44. targetProperty = targetProperty[names[i]];
  45. }
  46. if (defined(targetProperty)) {
  47. that._targetProperty = targetProperty;
  48. that._resolveProperty = !entityIsResolved;
  49. } else if (!defined(that._targetProperty)) {
  50. throw new RuntimeError('targetProperty "' + that._targetId + '.' + names.join('.') + '" could not be resolved.');
  51. }
  52. }
  53. return targetProperty;
  54. }
  55. /**
  56. * A {@link Property} which transparently links to another property on a provided object.
  57. *
  58. * @alias ReferenceProperty
  59. * @constructor
  60. *
  61. * @param {EntityCollection} targetCollection The entity collection which will be used to resolve the reference.
  62. * @param {String} targetId The id of the entity which is being referenced.
  63. * @param {String} targetPropertyNames The name of the property on the target entity which we will use.
  64. *
  65. * @example
  66. * var collection = new Cesium.EntityCollection();
  67. *
  68. * //Create a new entity and assign a billboard scale.
  69. * var object1 = new Cesium.Entity('object1');
  70. * object1.billboard = new Cesium.BillboardGraphics();
  71. * object1.billboard.scale = new Cesium.ConstantProperty(2.0);
  72. * collection.add(object1);
  73. *
  74. * //Create a second entity and reference the scale from the first one.
  75. * var object2 = new Cesium.Entity('object2');
  76. * object2.model = new Cesium.ModelGraphics();
  77. * object2.model.scale = new Cesium.ReferenceProperty(collection, 'object1', ['billboard', 'scale']);
  78. * collection.add(object2);
  79. *
  80. * //Create a third object, but use the fromString helper function.
  81. * var object3 = new Cesium.Entity('object3');
  82. * object3.billboard = new Cesium.BillboardGraphics();
  83. * object3.billboard.scale = Cesium.ReferenceProperty.fromString(collection, 'object1#billboard.scale');
  84. * collection.add(object3);
  85. *
  86. * //You can refer to an entity with a # or . in id and property names by escaping them.
  87. * var object4 = new Cesium.Entity('#object.4');
  88. * object4.billboard = new Cesium.BillboardGraphics();
  89. * object4.billboard.scale = new Cesium.ConstantProperty(2.0);
  90. * collection.add(object4);
  91. *
  92. * var object5 = new Cesium.Entity('object5');
  93. * object5.billboard = new Cesium.BillboardGraphics();
  94. * object5.billboard.scale = Cesium.ReferenceProperty.fromString(collection, '\\#object\\.4#billboard.scale');
  95. * collection.add(object5);
  96. */
  97. var ReferenceProperty = function(targetCollection, targetId, targetPropertyNames) {
  98. //>>includeStart('debug', pragmas.debug);
  99. if (!defined(targetCollection)) {
  100. throw new DeveloperError('targetCollection is required.');
  101. }
  102. if (!defined(targetId) || targetId === '') {
  103. throw new DeveloperError('targetId is required.');
  104. }
  105. if (!defined(targetPropertyNames) || targetPropertyNames.length === 0) {
  106. throw new DeveloperError('targetPropertyNames is required.');
  107. }
  108. for (var i = 0; i < targetPropertyNames.length; i++) {
  109. var item = targetPropertyNames[i];
  110. if (!defined(item) || item === '') {
  111. throw new DeveloperError('reference contains invalid properties.');
  112. }
  113. }
  114. //>>includeEnd('debug');
  115. this._targetCollection = targetCollection;
  116. this._targetId = targetId;
  117. this._targetPropertyNames = targetPropertyNames;
  118. this._targetProperty = undefined;
  119. this._targetEntity = undefined;
  120. this._definitionChanged = new Event();
  121. this._resolveEntity = true;
  122. this._resolveProperty = true;
  123. targetCollection.collectionChanged.addEventListener(ReferenceProperty.prototype._onCollectionChanged, this);
  124. };
  125. defineProperties(ReferenceProperty.prototype, {
  126. /**
  127. * Gets a value indicating if this property is constant.
  128. * @memberof ReferenceProperty.prototype
  129. * @type {Boolean}
  130. * @readonly
  131. */
  132. isConstant : {
  133. get : function() {
  134. return Property.isConstant(resolve(this));
  135. }
  136. },
  137. /**
  138. * Gets the event that is raised whenever the definition of this property changes.
  139. * The definition is changed whenever the referenced property's definition is changed.
  140. * @memberof ReferenceProperty.prototype
  141. * @type {Event}
  142. * @readonly
  143. */
  144. definitionChanged : {
  145. get : function() {
  146. return this._definitionChanged;
  147. }
  148. },
  149. /**
  150. * Gets the reference frame that the position is defined in.
  151. * This property is only valid if the referenced property is a {@link PositionProperty}.
  152. * @memberof ReferenceProperty.prototype
  153. * @type {ReferenceFrame}
  154. * @readonly
  155. */
  156. referenceFrame : {
  157. get : function() {
  158. return resolve(this).referenceFrame;
  159. }
  160. },
  161. /**
  162. * Gets the id of the entity being referenced.
  163. * @memberof ReferenceProperty.prototype
  164. * @type {String}
  165. * @readonly
  166. */
  167. targetId : {
  168. get : function() {
  169. return this._targetId;
  170. }
  171. },
  172. /**
  173. * Gets the collection containing the entity being referenced.
  174. * @memberof ReferenceProperty.prototype
  175. * @type {EntityCollection}
  176. * @readonly
  177. */
  178. targetCollection : {
  179. get : function() {
  180. return this._targetCollection;
  181. }
  182. },
  183. /**
  184. * Gets the array of property names used to retrieve the referenced property.
  185. * @memberof ReferenceProperty.prototype
  186. * @type {String[]}
  187. * @readonly
  188. */
  189. targetPropertyNames : {
  190. get : function() {
  191. return this._targetPropertyNames;
  192. }
  193. },
  194. /**
  195. * Gets the resolved instance of the underlying referenced property.
  196. * @memberof ReferenceProperty.prototype
  197. * @type {Property}
  198. * @readonly
  199. */
  200. resolvedProperty : {
  201. get : function() {
  202. return resolve(this);
  203. }
  204. }
  205. });
  206. /**
  207. * Creates a new instance given the entity collection that will
  208. * be used to resolve it and a string indicating the target entity id and property.
  209. * The format of the string is "objectId#foo.bar", where # separates the id from
  210. * property path and . separates sub-properties. If the reference identifier or
  211. * or any sub-properties contains a # . or \ they must be escaped.
  212. *
  213. * @param {Entity} targetCollection
  214. * @param {String} referenceString
  215. * @returns A new instance of ReferenceProperty.
  216. *
  217. * @exception {DeveloperError} invalid referenceString.
  218. */
  219. ReferenceProperty.fromString = function(targetCollection, referenceString) {
  220. //>>includeStart('debug', pragmas.debug);
  221. if (!defined(targetCollection)) {
  222. throw new DeveloperError('targetCollection is required.');
  223. }
  224. if (!defined(referenceString)) {
  225. throw new DeveloperError('referenceString is required.');
  226. }
  227. //>>includeEnd('debug');
  228. var identifier;
  229. var values = [];
  230. var inIdentifier = true;
  231. var isEscaped = false;
  232. var token = '';
  233. for (var i = 0; i < referenceString.length; ++i) {
  234. var c = referenceString.charAt(i);
  235. if (isEscaped) {
  236. token += c;
  237. isEscaped = false;
  238. } else if (c === '\\') {
  239. isEscaped = true;
  240. } else if (inIdentifier && c === '#') {
  241. identifier = token;
  242. inIdentifier = false;
  243. token = '';
  244. } else if (!inIdentifier && c === '.') {
  245. values.push(token);
  246. token = '';
  247. } else {
  248. token += c;
  249. }
  250. }
  251. values.push(token);
  252. return new ReferenceProperty(targetCollection, identifier, values);
  253. };
  254. /**
  255. * Gets the value of the property at the provided time.
  256. *
  257. * @param {JulianDate} time The time for which to retrieve the value.
  258. * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
  259. * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
  260. */
  261. ReferenceProperty.prototype.getValue = function(time, result) {
  262. return resolve(this).getValue(time, result);
  263. };
  264. /**
  265. * Gets the value of the property at the provided time and in the provided reference frame.
  266. * This method is only valid if the property being referenced is a {@link PositionProperty}.
  267. *
  268. * @param {JulianDate} time The time for which to retrieve the value.
  269. * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result.
  270. * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned.
  271. * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
  272. */
  273. ReferenceProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
  274. return resolve(this).getValueInReferenceFrame(time, referenceFrame, result);
  275. };
  276. /**
  277. * Gets the {@link Material} type at the provided time.
  278. * This method is only valid if the property being referenced is a {@link MaterialProperty}.
  279. *
  280. * @param {JulianDate} time The time for which to retrieve the type.
  281. * @returns {String} The type of material.
  282. */
  283. ReferenceProperty.prototype.getType = function(time) {
  284. return resolve(this).getType(time);
  285. };
  286. /**
  287. * Compares this property to the provided property and returns
  288. * <code>true</code> if they are equal, <code>false</code> otherwise.
  289. *
  290. * @param {Property} [other] The other property.
  291. * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
  292. */
  293. ReferenceProperty.prototype.equals = function(other) {
  294. if (this === other) {
  295. return true;
  296. }
  297. var names = this._targetPropertyNames;
  298. var otherNames = other._targetPropertyNames;
  299. if (this._targetCollection !== other._targetCollection || //
  300. this._targetId !== other._targetId || //
  301. names.length !== otherNames.length) {
  302. return false;
  303. }
  304. var length = this._targetPropertyNames.length;
  305. for (var i = 0; i < length; i++) {
  306. if (names[i] !== otherNames[i]) {
  307. return false;
  308. }
  309. }
  310. return true;
  311. };
  312. ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) {
  313. if (this._targetPropertyNames[0] === name) {
  314. this._resolveProperty = true;
  315. this._definitionChanged.raiseEvent(this);
  316. }
  317. };
  318. ReferenceProperty.prototype._onCollectionChanged = function(collection, added, removed) {
  319. var targetEntity = this._targetEntity;
  320. if (defined(targetEntity)) {
  321. if (removed.indexOf(targetEntity) !== -1) {
  322. targetEntity.definitionChanged.removeEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, this);
  323. this._resolveEntity = true;
  324. this._resolveProperty = true;
  325. } else if (this._resolveEntity) {
  326. //If targetEntity is defined but resolveEntity is true, then the entity is detached
  327. //and any change to the collection needs to incur an attempt to resolve in order to re-attach.
  328. //without this if block, a reference that becomes re-attached will not signal definitionChanged
  329. resolve(this);
  330. if (!this._resolveEntity) {
  331. this._definitionChanged.raiseEvent(this);
  332. }
  333. }
  334. }
  335. };
  336. return ReferenceProperty;
  337. });