ReferenceProperty.js 14 KB

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