featureLayer.js

  1. var inherit = require('./inherit');
  2. var layer = require('./layer');
  3. var geo_event = require('./event');
  4. var registry = require('./registry');
  5. /**
  6. * Layer to draw points, lines, and polygons on the map The polydata layer
  7. * provide mechanisms to create and draw geometrical shapes such as points,
  8. * lines, and polygons.
  9. * @class
  10. * @alias geo.featureLayer
  11. * @extends geo.layer
  12. * @param {geo.layer.spec} [arg] Specification for the new layer.
  13. * @returns {geo.featureLayer}
  14. */
  15. var featureLayer = function (arg) {
  16. 'use strict';
  17. if (!(this instanceof featureLayer)) {
  18. return new featureLayer(arg);
  19. }
  20. layer.call(this, arg);
  21. /**
  22. * private
  23. */
  24. var m_this = this,
  25. m_features = [],
  26. s_init = this._init,
  27. s_exit = this._exit,
  28. s_update = this._update,
  29. s_visible = this.visible,
  30. s_selectionAPI = this.selectionAPI,
  31. s_draw = this.draw;
  32. /**
  33. * Create a feature by name.
  34. *
  35. * @param {string} featureName The name of the feature to create.
  36. * @param {object} arg Properties for the new feature.
  37. * @returns {geo.feature} The created feature.
  38. */
  39. this.createFeature = function (featureName, arg) {
  40. var newFeature = registry.createFeature(
  41. featureName, m_this, m_this.renderer(), arg);
  42. if (newFeature) {
  43. m_this.addFeature(newFeature);
  44. } else {
  45. console.warn('Layer renderer (' + m_this.rendererName() + ') does not support feature type "' + featureName + '"');
  46. }
  47. return newFeature;
  48. };
  49. /**
  50. * Add a feature to the layer if it is not already present.
  51. *
  52. * @param {object} feature the feature to add.
  53. * @returns {this}
  54. */
  55. this.addFeature = function (feature) {
  56. /* try to remove the feature first so that we don't have two copies */
  57. m_this.removeFeature(feature);
  58. m_this.addChild(feature);
  59. m_features.push(feature);
  60. m_this.dataTime().modified();
  61. m_this.modified();
  62. return m_this;
  63. };
  64. /**
  65. * Remove a feature without destroying it.
  66. *
  67. * @param {geo.feature} feature The feature to remove.
  68. * @returns {this}
  69. */
  70. this.removeFeature = function (feature) {
  71. var pos;
  72. pos = m_features.indexOf(feature);
  73. if (pos >= 0) {
  74. m_features.splice(pos, 1);
  75. m_this.removeChild(feature);
  76. m_this.dataTime().modified();
  77. m_this.modified();
  78. }
  79. return m_this;
  80. };
  81. /**
  82. * Delete feature.
  83. *
  84. * @param {geo.feature} feature The feature to delete.
  85. * @returns {this}
  86. */
  87. this.deleteFeature = function (feature) {
  88. // call _exit first, as destroying the feature affect other features
  89. if (feature) {
  90. if (m_features.indexOf(feature) >= 0) {
  91. feature._exit();
  92. }
  93. m_this.removeFeature(feature);
  94. }
  95. return m_this;
  96. };
  97. /**
  98. * Get/Set drawables.
  99. *
  100. * @param {geo.feature[]} [val] A list of features, or unspecified to return
  101. * the current feature list. If a list is provided, features are added or
  102. * removed as needed.
  103. * @returns {geo.feature[]|this} The current features associated with the
  104. * layer or the current layer.
  105. */
  106. this.features = function (val) {
  107. if (val === undefined) {
  108. return m_features.slice();
  109. } else {
  110. // delete existing features that aren't in the new array. Since features
  111. // can affect other features during their exit process, make sure each
  112. // feature still exists as we work through the list.
  113. var existing = m_features.slice();
  114. var i;
  115. for (i = 0; i < existing.length; i += 1) {
  116. if (val.indexOf(existing[i]) < 0 && m_features.indexOf(existing[i]) >= 0) {
  117. m_this.deleteFeature(existing[i]);
  118. }
  119. }
  120. for (i = 0; i < val.length; i += 1) {
  121. m_this.addFeature(val[i]);
  122. }
  123. return m_this;
  124. }
  125. };
  126. /**
  127. * Get or set the gcs for all features. For features, the default is usually
  128. * the map's ingcs.
  129. *
  130. * @param {string?} [val] If `undefined`, return the current gcs. If
  131. * `null`, use the map's interface gcs. Otherwise, set a new value for
  132. * the gcs. When getting the current gcs, if not all features have the
  133. * same gcs, `undefined` will be returned.
  134. * @returns {string|this} A string used by {@link geo.transform}. If the
  135. * map interface gcs is in use, that value will be returned. If the gcs
  136. * is set, return the current class instance.
  137. */
  138. this.gcsFeatures = function (val) {
  139. if (val === undefined) {
  140. var gcs, mixed;
  141. this.features().forEach((feature) => {
  142. if (gcs === undefined) {
  143. gcs = feature.gcs();
  144. } else if (feature.gcs() !== gcs) {
  145. mixed = true;
  146. }
  147. });
  148. return mixed ? undefined : gcs;
  149. }
  150. m_this.features().forEach((feature) => {
  151. if (feature.gcs() !== val) {
  152. feature.gcs(val);
  153. }
  154. });
  155. return m_this;
  156. };
  157. /**
  158. * Initialize.
  159. *
  160. * @returns {this}
  161. */
  162. this._init = function () {
  163. if (m_this.initialized()) {
  164. return m_this;
  165. }
  166. // Call super class init
  167. s_init.call(m_this, true);
  168. // Bind events to handlers
  169. m_this.geoOn(geo_event.resize, function (event) {
  170. if (m_this.renderer()) {
  171. m_this.renderer()._resize(event.x, event.y, event.width, event.height);
  172. m_this._update({event: event});
  173. m_this.renderer()._render();
  174. } else {
  175. m_this._update({event: event});
  176. }
  177. });
  178. m_this.geoOn(geo_event.pan, function (event) {
  179. m_this._update({event: event});
  180. if (m_this.renderer()) {
  181. m_this.renderer()._render();
  182. }
  183. });
  184. m_this.geoOn(geo_event.rotate, function (event) {
  185. m_this._update({event: event});
  186. if (m_this.renderer()) {
  187. m_this.renderer()._render();
  188. }
  189. });
  190. m_this.geoOn(geo_event.zoom, function (event) {
  191. m_this._update({event: event});
  192. if (m_this.renderer()) {
  193. m_this.renderer()._render();
  194. }
  195. });
  196. return m_this;
  197. };
  198. /**
  199. * Update layer.
  200. *
  201. * @param {object} request A value to pass to the parent class.
  202. * @returns {this}
  203. */
  204. this._update = function (request) {
  205. var i;
  206. if (!m_features.length) {
  207. return m_this;
  208. }
  209. // Call base class update
  210. s_update.call(m_this, request);
  211. if (m_this.dataTime().timestamp() > m_this.updateTime().timestamp()) {
  212. for (i = 0; i < m_features.length; i += 1) {
  213. m_features[i].renderer(m_this.renderer());
  214. }
  215. }
  216. for (i = 0; i < m_features.length; i += 1) {
  217. m_features[i]._update();
  218. }
  219. m_this.updateTime().modified();
  220. return m_this;
  221. };
  222. /**
  223. * Free all resources.
  224. */
  225. this._exit = function () {
  226. m_this.clear();
  227. s_exit();
  228. };
  229. /**
  230. * Draw. If the layer is visible, call the parent class's draw function and
  231. * the renderer's render function.
  232. *
  233. * @returns {this}
  234. */
  235. this.draw = function () {
  236. if (m_this.visible()) {
  237. // Call sceneObject.draw, which calls draw on all child objects.
  238. s_draw();
  239. // Now call render on the renderer. In certain cases it may not do
  240. // anything if the child objects are drawn on the screen already.
  241. if (m_this.renderer()) {
  242. m_this.renderer()._render();
  243. }
  244. }
  245. return m_this;
  246. };
  247. /**
  248. * Get/Set visibility of the layer.
  249. *
  250. * @param {boolean} [val] If specified, change the visibility, otherwise
  251. * return it.
  252. * @returns {boolean|this} The current visibility or the layer.
  253. */
  254. this.visible = function (val) {
  255. if (val === undefined) {
  256. return s_visible();
  257. }
  258. if (m_this.visible() !== val) {
  259. s_visible(val);
  260. // take a copy of the features; changing visible could mutate them.
  261. var features = m_features.slice(), i;
  262. for (i = 0; i < features.length; i += 1) {
  263. features[i].visible(features[i].visible(undefined, true), true);
  264. }
  265. if (val) {
  266. m_this.draw();
  267. }
  268. }
  269. return m_this;
  270. };
  271. /**
  272. * Get/Set selectionAPI of the layer.
  273. *
  274. * @param {boolean} [val] If specified change the selectionAPI state of the
  275. * layer, otherwise return the current state.
  276. * @returns {boolean|this} The selectionAPI state or the current layer.
  277. */
  278. this.selectionAPI = function (val) {
  279. if (val === undefined) {
  280. return s_selectionAPI();
  281. }
  282. if (m_this.selectionAPI() !== val) {
  283. s_selectionAPI(val);
  284. // take a copy of the features; changing selectionAPI could mutate them.
  285. var features = m_features.slice(), i;
  286. for (i = 0; i < features.length; i += 1) {
  287. features[i].selectionAPI(features[i].selectionAPI(undefined, true), true);
  288. }
  289. }
  290. return m_this;
  291. };
  292. /**
  293. * Clear all features in layer.
  294. *
  295. * @returns {this}
  296. */
  297. this.clear = function () {
  298. while (m_features.length) {
  299. m_this.deleteFeature(m_features[0]);
  300. }
  301. return m_this;
  302. };
  303. return m_this;
  304. };
  305. inherit(featureLayer, layer);
  306. registry.registerLayer('feature', featureLayer);
  307. module.exports = featureLayer;