Source: map/layer/Layer/Layer.js

  1. import { EventObject } from "../../../events/EventObject.js";
  2. import { generateUniqueId } from "../../../utils/utils.js";
  3. import { resolveComplexTemplate } from "../../../utils/template/resolveComplexTemplate.js";
  4. import { enhanceHTML } from "../../../utils/template/enhanceHTML.js";
  5. import { LayerProxy } from "./Layer.proxy.js";
  6. import { NETWORK_LOADING_ERROR } from "../../utils/messageTemplates.js";
  7. import LayerMetadata from "./Layer.metadata.md?raw";
  8. import { LayerSchema } from "./Layer.schema.js";
  9. export { Layer }
  10. /**
  11. * Abstract Layer class
  12. *
  13. * @author rhess <robin.hess@awi.de>
  14. * @memberof vef.map.layer
  15. */
  16. class Layer extends EventObject {
  17. /**
  18. * Set all properties for the layer based on the options object.
  19. *
  20. * @param {object} config
  21. * @param {object} cache
  22. * @param {string} id
  23. */
  24. constructor(config, cache, id) {
  25. super({
  26. "layer_select": [],
  27. "layer_deselect": [],
  28. "layer_update": [],
  29. "layer_request_remove": [],
  30. "layer_loading": [],
  31. "layer_loaded": [],
  32. "layer_message": [],
  33. "layer_click": [],
  34. "layer_mouseover": [],
  35. "layer_mouseout": []
  36. });
  37. this.uniqueId = id || generateUniqueId();
  38. // layer metadata containers
  39. this.config = Object.assign({}, config || {});
  40. this.cache = Object.assign({}, cache || {});
  41. this.defaults = {};
  42. this.schema = {};
  43. // initialize the default properties
  44. this.setSchema_(LayerSchema.getSchema());
  45. // apply the catalogId to the config, if it is only defined in the cache
  46. if ("catalogId" in this.cache) {
  47. this.config.catalogId = this.cache.catalogId;
  48. }
  49. // internal properties
  50. this.messages_ = [];
  51. this.popupEvents_ = [];
  52. this.projectionMatches_ = true;
  53. this.filterMatches_ = true;
  54. // active map layer state
  55. this.crs_ = (this.availableCrs.length > 0) ? this.availableCrs[0] : "";
  56. this.mapLayers_ = {};
  57. this.activeLayer_ = new LayerProxy();
  58. // framework specific map implementations
  59. this.layerProxies_ = {};
  60. }
  61. get deactivated() {
  62. return (!this.projectionMatches_ || !this.filterMatches_);
  63. }
  64. set deactivated(val) {
  65. console.log("not implemented, only the calculated getter is relevant");
  66. }
  67. /**
  68. * Helper method to get a property from the config, cache or default container
  69. *
  70. * @param {string} name
  71. * @private
  72. * @returns {any} property
  73. */
  74. getProperty_(name) {
  75. if (name in this.config) return this.config[name];
  76. if (name in this.cache) return this.cache[name];
  77. if (name in this.defaults) return this.defaults[name];
  78. return undefined;
  79. }
  80. /**
  81. * Register the getter and setter for a property and add default
  82. * values for properties based on the JSON Schema
  83. * @param {object} schema json schema
  84. * @private
  85. */
  86. setSchema_(schema) {
  87. this.schema = schema;
  88. for (let name in schema.properties) {
  89. if (name in this) delete this[name];
  90. this.defaults[name] = schema.properties[name].default;
  91. Object.defineProperty(this, name, {
  92. get() { return this.getProperty_(name); },
  93. set(val) { this.config[name] = val; },
  94. configurable: true
  95. });
  96. }
  97. // set layer type based on default for all content objects
  98. const type = this.defaults.type;
  99. this.config.type = type;
  100. this.cache.type = type;
  101. }
  102. /**
  103. * Register the getter and setter for a property and add default values for properties
  104. * in the exported config object. Existing values are not overwritten
  105. *
  106. * @private
  107. * @param {object} config
  108. */
  109. addConfig_(config) {
  110. for (let name in config) {
  111. if (name in this) delete this[name];
  112. if (!(name in this.config)) this.config[name] = config[name];
  113. Object.defineProperty(this, name, {
  114. get() { return this.config[name]; },
  115. set(val) { this.config[name] = val; },
  116. configurable: true
  117. });
  118. }
  119. }
  120. getConfig_(container) {
  121. container = JSON.parse(JSON.stringify(container));
  122. for (let key in container) {
  123. if (
  124. (container[key] === null) ||
  125. (((typeof container[key] == "string") || Array.isArray(container[key])) && (container[key].length == 0)) ||
  126. ((typeof container[key] == "object") && (Object.keys(container[key]).length == 0))
  127. ) delete container[key];
  128. }
  129. // remove deprecated keys
  130. if ("settings" in container) delete container.settings;
  131. if ("filter" in container) delete container.filter;
  132. // remove default keys
  133. if (container.active === false) delete container.active;
  134. if (container.expanded === false) delete container.expanded;
  135. return container;
  136. }
  137. /**
  138. * create a map layer based on the service options
  139. *
  140. * @param {string} type e.g. "leaflet"
  141. * @private
  142. */
  143. createMapLayer_(type, options) {
  144. if (type in this.layerProxies_) {
  145. const proxy = new this.layerProxies_[type](options || {});
  146. let error = false;
  147. proxy.on("layerproxy_loading", () => {
  148. error = false;
  149. this.fire("layer_loading");
  150. });
  151. proxy.on("layerproxy_loaded", () => {
  152. this.fire("layer_loaded");
  153. if (!error) this.removeMessage(NETWORK_LOADING_ERROR);
  154. });
  155. proxy.on('layerproxy_error', () => {
  156. error = true;
  157. this.addMessage(NETWORK_LOADING_ERROR);
  158. });
  159. this.mapLayers_[type] = proxy;
  160. return proxy;
  161. } else {
  162. return null;
  163. }
  164. }
  165. /**
  166. * Helper method to update the mapLayer according to
  167. * the current state. Might be necessary after switching the map type
  168. *
  169. * @private
  170. */
  171. updateMapLayer_() { }
  172. getConfig() {
  173. const config = this.getConfig_(this.config);
  174. if (("catalogId" in config) && ("type" in config)) delete config.type;
  175. return config;
  176. }
  177. getCache() {
  178. return this.getConfig_(this.cache);
  179. }
  180. /**
  181. * Print the Metadata from this layer as html.
  182. */
  183. printMetadata(layer) {
  184. const container = resolveComplexTemplate(LayerMetadata, {
  185. layer: layer || this,
  186. datasetDescription: this?.dataFrame?.description || {},
  187. sensorMetadata: this?.sensorMetadata || {},
  188. }, {
  189. markdown: true,
  190. postProcess: true
  191. });
  192. enhanceHTML(container, {});
  193. return container;
  194. }
  195. /**
  196. * get the events that can be triggered by buttons in a popup
  197. */
  198. getPopupEvents() {
  199. return this.popupEvents_.slice();
  200. }
  201. /**
  202. * add an event for the buttons in the popup
  203. */
  204. addPopupEvent(title, callback) {
  205. if ((typeof title == "string") && (typeof callback == "function")) {
  206. this.popupEvents_.push({
  207. title: title,
  208. callback: callback
  209. });
  210. }
  211. }
  212. /**
  213. * returns the map layer proxy for the
  214. * specific map implementation
  215. *
  216. * @param {string} type
  217. * @returns {object} map layer for framework
  218. */
  219. getLayerProxy(type) {
  220. return (type in this.mapLayers_)
  221. ? this.mapLayers_[type]
  222. : this.createMapLayer_(type);
  223. }
  224. /**
  225. * enables the map layer for the
  226. * specific map implementation
  227. *
  228. * @param {string} type
  229. */
  230. enable(type) {
  231. if (type in this.mapLayers_) {
  232. this.active = true;
  233. this.activeLayer_ = this.mapLayers_[type];
  234. this.updateMapLayer_(type);
  235. return true;
  236. }
  237. return false;
  238. }
  239. /**
  240. * Unset the active state of the layer
  241. */
  242. disable() {
  243. this.active = false;
  244. }
  245. /**
  246. * Returns the Legend Graphic for this layer.
  247. * May depend on current color scale.
  248. *
  249. * @returns html <img>-tag (null or undefined if there is no legend graphic)
  250. */
  251. getLegendGraphic() { }
  252. /**
  253. * Set Filter without applying it
  254. *
  255. * @param {object} config generic filter config
  256. */
  257. setFilter(config) {
  258. this.filter = config;
  259. }
  260. /**
  261. * Apply the defined filter. Uses generic
  262. * filter syntax and transforms it internally
  263. */
  264. applyFilter() { }
  265. /**
  266. * Set the style of the layer.
  267. *
  268. * @param {string} styleName style name
  269. * @returns {boolean} returns true if successful
  270. */
  271. setStyle(styleName) { }
  272. /**
  273. * Get the active style of the layer.
  274. */
  275. getStyle() { }
  276. /**
  277. * Get the active colorscale for the layer
  278. */
  279. getColorScale() { }
  280. /**
  281. * Set the colorscale for the layer
  282. *
  283. * @param colorScale
  284. */
  285. setColorScale(colorScale) { }
  286. /**
  287. * Dispose the Layer
  288. */
  289. dispose() { }
  290. /**
  291. * Get the transparency value
  292. *
  293. * @returns {number} number between 0 and 1
  294. */
  295. getOpacity() {
  296. return this.opacity;
  297. }
  298. /**
  299. * Set the transparency between 0 and 1
  300. * @param {number} opacity style name
  301. */
  302. setOpacity(opacity) {
  303. this.opacity = opacity;
  304. this.activeLayer_.setOpacity(opacity);
  305. }
  306. /**
  307. * Reload the Layer on the map
  308. */
  309. reload() {
  310. this.activeLayer_.reload();
  311. }
  312. /**
  313. * Switch the projection for the visualization
  314. * of the layer. CRS hast to be included in availableCRS.
  315. *
  316. * @param {string} crs target crs code
  317. */
  318. setProjection(crs) {
  319. crs = crs.toUpperCase();
  320. if (this.availableCrs.includes(crs)) {
  321. this.crs_ = crs;
  322. this.activeLayer_.setProjection(crs);
  323. this.projectionMatches_ = true;
  324. } else {
  325. this.projectionMatches_ = false;
  326. }
  327. return this.projectionMatches_;
  328. }
  329. /**
  330. * Get the bounding box as an object
  331. *
  332. * @returns {object} { min: {x, y}, max: {x, y}, crs: "CRS:84" }
  333. */
  334. getBounds() {
  335. return {
  336. min: { y: -90, x: -180 },
  337. max: { y: 90, x: 180 },
  338. crs: "CRS:84"
  339. }
  340. }
  341. /**
  342. * Get all active messages
  343. *
  344. * @returns {object[]} messages
  345. */
  346. getMessages() {
  347. // implementation of layer-specific messages needs to be done in child classes
  348. return [...this.messages_];
  349. }
  350. /**
  351. * Add a message to the layer
  352. * @param {object} message
  353. */
  354. addMessage(message) {
  355. if (this.messages_.indexOf(message) > -1) return;
  356. this.messages_.push(message);
  357. this.fire("layer_message", this.getMessages());
  358. }
  359. /**
  360. * Remove a message from the layer
  361. * @param {object} message
  362. */
  363. removeMessage(message) {
  364. const index = this.messages_.indexOf(message);
  365. if (index > -1) {
  366. this.messages_.splice(index, 1);
  367. this.fire("layer_message", this.getMessages());
  368. }
  369. }
  370. /**
  371. * @param {string} attributeField
  372. * @returns {string[]} array of unique values
  373. */
  374. async getUniqueValues(attributeField) {
  375. return [];
  376. }
  377. /**
  378. * @returns {string[]} array of attribute names
  379. */
  380. getAttributeNames() {
  381. return Object.keys(this.attributeFields);
  382. }
  383. }