layer/layerRec/dynamicRecord.js

  1. 'use strict';
  2. const attribRecord = require('./attribRecord.js')();
  3. const shared = require('./shared.js')();
  4. const placeholderFC = require('./placeholderFC.js')();
  5. const layerInterface = require('./layerInterface.js')();
  6. const dynamicFC = require('./dynamicFC.js')();
  7. const attribFC = require('./attribFC.js')();
  8. /**
  9. * @class DynamicRecord
  10. */
  11. class DynamicRecord extends attribRecord.AttribRecord {
  12. // TODO are we still using passthrough stuff?
  13. get _layerPassthroughBindings () {
  14. return ['setOpacity', 'setVisibility', 'setVisibleLayers', 'setLayerDrawingOptions'];
  15. }
  16. get _layerPassthroughProperties () {
  17. return ['visibleAtMapScale', 'visible', 'spatialReference', 'layerInfos', 'supportsDynamicLayers'];
  18. }
  19. get layerType () { return shared.clientLayerType.ESRI_DYNAMIC; }
  20. get isTrueDynamic () { return this._isTrueDynamic; }
  21. /**
  22. * Create a layer record with the appropriate geoApi layer type.
  23. * Regarding configuration -- in the standard case, the incoming config object
  24. * will be incomplete with regards to child state. It may not even have entries for all possible
  25. * child sub-layers. Given our config defaulting for children happens AFTER the layer loads,
  26. * it means what is passed in at the constructor is generally unreliable except for any child names,
  27. * and the class will treat that information as unreliable (the UI will set values after config defaulting
  28. * happens). In the rare case where the config is fully formed and we want to take advantage of that,
  29. * set the configIsComplete param to true. Be aware that if the config is not actually complete you may
  30. * get a layer in an undesired initial state.
  31. *
  32. * @param {Object} layerClass the ESRI api object for dynamic layers
  33. * @param {Object} esriRequest the ESRI api object for making web requests with proxy support
  34. * @param {Object} apiRef object pointing to the geoApi. allows us to call other geoApi functions
  35. * @param {Object} config layer config values
  36. * @param {Object} esriLayer an optional pre-constructed layer
  37. * @param {Function} epsgLookup an optional lookup function for EPSG codes (see geoService for signature)
  38. * @param {Boolean} configIsComplete an optional flag to indicate if the config is fully flushed out (i.e. things defined for all children). Defaults to false.
  39. */
  40. constructor (layerClass, esriRequest, apiRef, config, esriLayer, epsgLookup, configIsComplete = false) {
  41. // TODO might need some nonsense here. if not configIsComplete, and layer is set to visible in config,
  42. // we may need to hack the process so that the esri layer object is initialized as invisible,
  43. // but the config is still marked as visible so the UI knows to do the proper defaulting.
  44. // As is right now, the layer might start to pull an image from the server while our onLoad
  45. // event handler is running and shutting off visibilities.
  46. super(layerClass, esriRequest, apiRef, config, esriLayer, epsgLookup);
  47. this.ArcGISDynamicMapServiceLayer = layerClass;
  48. this._configIsComplete = configIsComplete;
  49. // TODO what is the case where we have dynamic layer already prepared
  50. // and passed in? Generally this only applies to file layers (which
  51. // are feature layers).
  52. this._proxies = {};
  53. // marks if layer supports dynamic capabilities, like child opacity, renderer change, layer reorder
  54. // TODO ensure false is best default (what is better for UI)
  55. this._isTrueDynamic = false;
  56. // manages delayed visibility changes to avoid cascading refreshes
  57. this._visDelay = {
  58. lastIdx: -1,
  59. parentToggle: false,
  60. parentValue: false
  61. };
  62. }
  63. /**
  64. * Return a proxy interface for a child layer
  65. *
  66. * @param {Integer} featureIdx index of child entry (leaf or group)
  67. * @return {Object} proxy interface for given child
  68. */
  69. getChildProxy (featureIdx) {
  70. // TODO verify we have integer coming in and not a string
  71. // NOTE we no longer have group proxies. Since it is possible for a proxy to
  72. // be requested prior to a dynamic layer being loaded (and thus have no
  73. // idea of the index is valid or the index is a group), we always give
  74. // a proxy and depend on the caller to be smart about it.
  75. const strIdx = featureIdx.toString();
  76. if (this._proxies[strIdx]) {
  77. return this._proxies[strIdx];
  78. } else {
  79. // throw new Error(`attempt to get non-existing child proxy. Index ${featureIdx}`);
  80. // to handle the case of a structured legend needing a proxy for a child prior to the
  81. // layer loading, we treat an unknown proxy request as that case and return
  82. // a proxy loaded with a placeholder.
  83. // TODO how to pass in a name? add an optional second parameter? expose a "set name" on the proxy?
  84. const pfc = new placeholderFC.PlaceholderFC(this, '');
  85. const tProxy = new layerInterface.LayerInterface(pfc);
  86. tProxy.convertToPlaceholder(pfc);
  87. this._proxies[strIdx] = tProxy;
  88. return tProxy;
  89. }
  90. }
  91. /**
  92. * Get feature count of a child layer.
  93. *
  94. * @function getFeatureCount
  95. * @param {String} featureIdx index of the child
  96. * @return {Promise} resolves with an integer indicating the feature count.
  97. */
  98. getFeatureCount (featureIdx) {
  99. // point url to sub-index we want
  100. // TODO might change how we manage index and url
  101. return super.getFeatureCount(this._layer.url + '/' + featureIdx);
  102. }
  103. /**
  104. * Will set the layer opacity, and update all child FCs to the same opacity.
  105. * Used for Dynamic layers that do not support child opacity.
  106. *
  107. * @function synchOpacity
  108. * @param {Numeric} opacity an opacity value (decimal between 0 and 1)
  109. */
  110. synchOpacity (opacity) {
  111. // in the case where a dynamic layer does not support child opacity, if a user
  112. // changes the opacity of a child, it actually just adjusts the opacity of the layer.
  113. // this means that all other children of the layer need to have their opacity set
  114. // to the same value. but we dont want to trigger a number of opacity change requests,
  115. // so we do some trickery here.
  116. Object.keys(this._featClasses).forEach(idx => {
  117. const fc = this._featClasses[idx];
  118. if (fc) {
  119. // important: must use the private ._opacity property here,
  120. // as we want to avoid the logic on the .opacity setter.
  121. fc._opacity = opacity;
  122. }
  123. });
  124. // update the layer itself.
  125. this.opacity = opacity;
  126. }
  127. /**
  128. * Triggers when the layer loads.
  129. *
  130. * @function onLoad
  131. */
  132. onLoad () {
  133. const loadPromises = super.onLoad();
  134. this._isTrueDynamic = this._layer.supportsDynamicLayers;
  135. // don't worry about structured legend. the legend part is separate from
  136. // the layers part. we just load what we are told to. the legend module
  137. // will handle the structured part.
  138. // see comments on the constructor to learn about _configIsComplete and
  139. // what type of scenarios you can expect for incoming configs
  140. // snapshot doesn't apply to child layers
  141. // we don't include bounding box / extent, as we are inheriting it.
  142. // a lack of the property means we use the layer definition
  143. const dummyState = {
  144. opacity: 1,
  145. visibility: false,
  146. query: false
  147. };
  148. // subfunction to clone a layerEntries config object.
  149. // since we are using typed objects with getters and setters,
  150. // our usual easy ways of cloning an object don't work (e.g. using
  151. // JSON.parse(JSON.stringify(x))). This is not a great solution (understatement),
  152. // but is being done as a quick n dirty workaround. At a later time,
  153. // the guts of this function can be re-examined for a better,
  154. // less hardcoded solution.
  155. const cloneConfig = origConfig => {
  156. const clone = {};
  157. // direct copies, no defaulting
  158. clone.name = origConfig.name;
  159. clone.index = origConfig.index;
  160. clone.stateOnly = origConfig.stateOnly;
  161. clone.nameField = origConfig.nameField;
  162. clone.highlightFeature = origConfig.highlightFeature || true; // simple default
  163. // an empty string is a valid property, so be wary of falsy logic
  164. clone.outfields = origConfig.hasOwnProperty('outfields') ? origConfig.outfields : '*';
  165. // with state, we are either complete, or pure defaults.
  166. // in the non-complete case, we treat our state as unreliable and
  167. // expect the client to assign properties as it does parent-child inheritance
  168. // defaulting (which occurs after this onLoad function has completed)
  169. if (this._configIsComplete) {
  170. clone.state = {
  171. visiblity: origConfig.visiblity,
  172. opacity: origConfig.opacity,
  173. query: origConfig.query
  174. };
  175. } else {
  176. clone.state = Object.assign({}, dummyState);
  177. }
  178. // if extent is present, we assume it is fully defined.
  179. // extents are not using fancy typed objects, so can directly reference
  180. clone.extent = origConfig.extent;
  181. return clone;
  182. };
  183. // collate any relevant overrides from the config.
  184. const subConfigs = {};
  185. this.config.layerEntries.forEach(le => {
  186. subConfigs[le.index.toString()] = {
  187. config: cloneConfig(le),
  188. defaulted: this._configIsComplete
  189. };
  190. });
  191. // subfunction to return a subconfig object.
  192. // if it does not exist or is not defaulted, will do that first
  193. // id param is an integer in string format
  194. const fetchSubConfig = (id, serverName = '') => {
  195. if (subConfigs[id]) {
  196. const subC = subConfigs[id];
  197. if (!subC.defaulted) {
  198. // config is incomplete, fill in blanks
  199. // we will never hit this code block a complete config was passed in
  200. // apply a server name if no name exists
  201. if (!subC.config.name) {
  202. subC.config.name = serverName;
  203. }
  204. // mark as defaulted so we don't do this again
  205. subC.defaulted = true;
  206. }
  207. return subC.config;
  208. } else {
  209. // no config at all. we apply defaults, and a name from the server if available
  210. const configSeed = {
  211. name: serverName,
  212. index: parseInt(id),
  213. stateOnly: true
  214. };
  215. const newConfig = cloneConfig(configSeed);
  216. subConfigs[id] = {
  217. config: newConfig,
  218. defaulted: true
  219. };
  220. return newConfig;
  221. }
  222. };
  223. // shortcut var to track all leafs that need attention
  224. // in the loading process
  225. const leafsToInit = [];
  226. // this subfunction will recursively crawl a dynamic layerInfo structure.
  227. // it will generate proxy objects for all groups and leafs under the
  228. // input layerInfo.
  229. // we also generate a tree structure of layerInfos that is in a format
  230. // that makes the client happy
  231. const processLayerInfo = (layerInfo, treeArray) => {
  232. const sId = layerInfo.id.toString();
  233. const subC = fetchSubConfig(sId, layerInfo.name);
  234. if (layerInfo.subLayerIds && layerInfo.subLayerIds.length > 0) {
  235. // group sublayer. set up our tree for the client, then crawl childs.
  236. const treeGroup = {
  237. entryIndex: layerInfo.id,
  238. name: subC.name,
  239. childs: []
  240. };
  241. treeArray.push(treeGroup);
  242. // process the kids in the group.
  243. // store the child leaves in the internal variable
  244. layerInfo.subLayerIds.forEach(slid => {
  245. processLayerInfo(this._layer.layerInfos[slid], treeGroup.childs);
  246. });
  247. } else {
  248. // leaf sublayer. make placeholders, add leaf to the tree
  249. const pfc = new placeholderFC.PlaceholderFC(this, subC.name);
  250. if (this._proxies[sId]) {
  251. // we have a pre-made proxy (structured legend). update it.
  252. this._proxies[sId].updateSource(pfc);
  253. } else {
  254. // set up new proxy
  255. const leafProxy = new layerInterface.LayerInterface(null);
  256. leafProxy.convertToPlaceholder(pfc);
  257. this._proxies[sId] = leafProxy;
  258. }
  259. treeArray.push({ entryIndex: layerInfo.id });
  260. leafsToInit.push(layerInfo.id.toString());
  261. }
  262. };
  263. this._childTree = []; // public structure describing the tree
  264. // process the child layers our config is interested in, and all their children.
  265. if (this.config.layerEntries) {
  266. this.config.layerEntries.forEach(le => {
  267. if (!le.stateOnly) {
  268. processLayerInfo(this._layer.layerInfos[le.index], this._childTree);
  269. }
  270. });
  271. }
  272. // converts server layer type string to client layer type string
  273. const serverLayerTypeToClientLayerType = serverType => {
  274. switch (serverType) {
  275. case 'Feature Layer':
  276. return shared.clientLayerType.ESRI_FEATURE;
  277. case 'Raster Layer':
  278. return shared.clientLayerType.ESRI_RASTER;
  279. default:
  280. console.warn('Unexpected layer type in serverLayerTypeToClientLayerType', serverType);
  281. return shared.clientLayerType.UNKNOWN;
  282. }
  283. };
  284. // process each leaf we walked to in the processLayerInfo loop above
  285. // idx is a string
  286. leafsToInit.forEach(idx => {
  287. const subC = subConfigs[idx].config;
  288. const attribPackage = this._apiRef.attribs.loadServerAttribs(this._layer.url, idx, subC.outfields);
  289. const dFC = new dynamicFC.DynamicFC(this, idx, attribPackage, subC);
  290. dFC.highlightFeature = subC.highlightFeature;
  291. this._featClasses[idx] = dFC;
  292. // if we have a proxy watching this leaf, replace its placeholder with the real data
  293. const leafProxy = this._proxies[idx];
  294. if (leafProxy) {
  295. leafProxy.convertToDynamicLeaf(dFC);
  296. }
  297. // load real symbols into our source
  298. loadPromises.push(dFC.loadSymbology());
  299. // update asynchronous values
  300. const pLD = dFC.getLayerData()
  301. .then(ld => {
  302. dFC.layerType = serverLayerTypeToClientLayerType(ld.layerType);
  303. // if we didn't have an extent defined on the config, use the layer extent
  304. if (!dFC.extent) {
  305. dFC.extent = ld.extent;
  306. }
  307. dFC._scaleSet.minScale = ld.minScale;
  308. dFC._scaleSet.maxScale = ld.maxScale;
  309. dFC.nameField = subC.nameField || ld.nameField || '';
  310. // skip a number of things if it is a raster layer
  311. // either way, return a promise so our loadPromises have a good
  312. // value to wait on.
  313. if (dFC.layerType === shared.clientLayerType.ESRI_FEATURE) {
  314. dFC.geomType = ld.geometryType;
  315. return this.getFeatureCount(idx).then(fc => {
  316. dFC.featureCount = fc;
  317. });
  318. } else {
  319. return Promise.resolve();
  320. }
  321. })
  322. .catch(() => {
  323. dFC.layerType = shared.clientLayerType.UNRESOLVED;
  324. });
  325. loadPromises.push(pLD);
  326. });
  327. // TODO careful now, as the dynamicFC.DynamicFC constructor also appears to be setting visibility on the parent.
  328. if (this._configIsComplete) {
  329. // if we have a complete config, want to set layer visibility
  330. // get an array of leaf ids that are visible.
  331. // use _featClasses as it contains keys that exist on the server and are
  332. // potentially visible in the client.
  333. const initVis = Object.keys(this._featClasses)
  334. .filter(fcId => {return fetchSubConfig(fcId).config.state.visibility; })
  335. .map(fcId => { return parseInt(fcId); });
  336. if (initVis.length === 0) {
  337. initVis.push(-1); // esri code for set all to invisible
  338. }
  339. this._layer.setVisibleLayers(initVis);
  340. } else {
  341. // default configuration for non-complete config.
  342. this._layer.setVisibility(false);
  343. this._layer.setVisibleLayers([-1]);
  344. }
  345. Promise.all(loadPromises).then(() => {
  346. this._stateChange(shared.states.LOADED);
  347. });
  348. }
  349. /**
  350. * Zoom to a valid scale level for a child layer.
  351. *
  352. * @function zoomToScale
  353. * @param {String} childIndex index of the child layer to target
  354. * @param {Object} map the map object
  355. * @param {Array} lods level of details array for basemap
  356. * @param {Boolean} zoomIn the zoom to scale direction; true need to zoom in; false need to zoom out
  357. * @param {Boolean} positionOverLayer ensures the map is over the layer's extent after zooming. only applied if zoomIn is true. defaults to true
  358. * @returns {Promise} promise that resolves after map finishes moving about
  359. */
  360. zoomToScale (childIdx, map, lods, zoomIn, positionOverLayer = true) {
  361. // get scale set from child, then execute zoom
  362. const scaleSet = this._featClasses[childIdx].getScaleSet();
  363. return this._zoomToScaleSet(map, lods, zoomIn, scaleSet, positionOverLayer);
  364. }
  365. /**
  366. * Indicates if the feature class is not visible at the given scale,
  367. * and if so, if we need to zoom in to see it or zoom out
  368. *
  369. * @function isOffScale
  370. * @param {String} childIndex index of the child layer to target
  371. * @param {Integer} mapScale the scale to test against
  372. * @returns {Object} has boolean properties `offScale` and `zoomIn`
  373. */
  374. isOffScale (childIdx, mapScale) {
  375. return this._featClasses[childIdx].isOffScale(mapScale);
  376. }
  377. /**
  378. * Indicates the layer is queryable.
  379. *
  380. * @function isQueryable
  381. * @param {String} childIndex index of the child layer to target
  382. * @returns {Boolean} the queryability of the layer
  383. */
  384. isQueryable (childIdx) {
  385. return this._featClasses[childIdx].queryable;
  386. }
  387. // TODO if we need this back, may need to implement as getChildGeomType.
  388. // appears this ovverrides the LayerRecord.getGeomType function, which returns
  389. // undefined, and that is what we want on the DynamicRecord level (as dynamic layer)
  390. // has no geometry.
  391. // Currently, all child requests for geometry go through the proxy,
  392. // so could be this child-targeting version is irrelevant.
  393. /*
  394. getGeomType (childIdx) {
  395. return this._featClasses[childIdx].geomType;
  396. }
  397. */
  398. /**
  399. * Get a structured representation of the heirarchy of child layers in this Record.
  400. * We omit parts of the tree that were not specified to load / be used in the config.
  401. *
  402. * @function getChildTree
  403. * @return {Object} structured representation of child layers
  404. */
  405. getChildTree () {
  406. // TODO document somehow the format of the return object
  407. if (this._childTree) {
  408. return this._childTree;
  409. } else {
  410. throw new Error('Called getChildTree before layer is loaded');
  411. }
  412. }
  413. /**
  414. * Get the best user-friendly name of a field. Uses alias if alias is defined, else uses the system attribute name.
  415. *
  416. * @function aliasedFieldName
  417. * @param {String} attribName the attribute name we want a nice name for
  418. * @param {String} childIndex index of the child layer whos attributes we are looking at
  419. * @return {Promise} resolves to the best available user friendly attribute name
  420. */
  421. aliasedFieldName (attribName, childIndex) {
  422. return this._featClasses[childIndex].aliasedFieldName(attribName);
  423. }
  424. /**
  425. * Retrieves attributes from a layer for a specified feature index
  426. *
  427. * @function getFormattedAttributes
  428. * @param {String} childIndex index of the child layer to get attributes for
  429. * @return {Promise} promise resolving with formatted attributes to be consumed by the datagrid and esri feature identify
  430. */
  431. getFormattedAttributes (childIndex) {
  432. return this._featClasses[childIndex].getFormattedAttributes();
  433. }
  434. /**
  435. * Fetches a graphic from the given child layer.
  436. * Will attempt local copy (unless overridden), will hit the server if not available.
  437. *
  438. * @function fetchGraphic
  439. * @param {String} childIndex index of the child layer to target
  440. * @param {Integer} objId ID of object being searched for
  441. * @param {Boolean} ignoreLocal indicates if we should ignore any local graphic in the layer. cached or server value will be used. defaults to false.
  442. * @returns {Promise} resolves with a bundle of information. .graphic is the graphic; .source is where it came from - 'layer' or 'server'; also .layerFC for convenience
  443. */
  444. fetchGraphic (childIndex, objId, ignoreLocal = false) {
  445. return this._featClasses[childIndex].fetchGraphic(objId, ignoreLocal);
  446. }
  447. /**
  448. * Will attempt to zoom the map view so the a graphic is prominent.
  449. *
  450. * @function zoomToGraphic
  451. * @param {String} childIndex index of the child layer to target
  452. * @param {Integer} objId Object ID of grahpic being searched for
  453. * @param {Object} map wrapper object for the map we want to zoom
  454. * @param {Object} offsetFraction an object with decimal properties `x` and `y` indicating percentage of offsetting on each axis
  455. * @return {Promise} resolves after the map is done moving
  456. */
  457. zoomToGraphic (childIndex, objId, map, offsetFraction) {
  458. return this._featClasses[childIndex].zoomToGraphic(objId, map, offsetFraction);
  459. }
  460. /**
  461. * Check to see if the attribute in question is an esriFieldTypeDate type.
  462. *
  463. * @function checkDateType
  464. * @param {String} attribName the attribute name we want to check if it's a date or not
  465. * @param {String} childIndex index of the child layer whos attributes we are looking at
  466. * @return {Promise} resolves to true or false based on the attribName type being esriFieldTypeDate
  467. */
  468. checkDateType (attribName, childIndex) {
  469. return this._featClasses[childIndex].checkDateType(attribName);
  470. }
  471. /**
  472. * Returns attribute data for a child layer.
  473. *
  474. * @function getAttribs
  475. * @param {String} childIndex the index of the child layer
  476. * @returns {Promise} resolves with a layer attribute data object
  477. */
  478. getAttribs (childIndex) {
  479. return this._featClasses[childIndex].getAttribs();
  480. }
  481. /**
  482. * Returns layer-specific data for a child layer
  483. *
  484. * @function getLayerData
  485. * @param {String} childIndex the index of the child layer
  486. * @returns {Promise} resolves with a layer data object
  487. */
  488. getLayerData (childIndex) {
  489. return this._featClasses[childIndex].getLayerData();
  490. }
  491. /**
  492. * Extract the feature name from a feature as best we can.
  493. *
  494. * @function getFeatureName
  495. * @param {String} childIndex the index of the child layer
  496. * @param {String} objId the object id of the attribute
  497. * @param {Object} attribs the dictionary of attributes for the feature.
  498. * @returns {String} the name of the feature
  499. */
  500. getFeatureName (childIndex, objId, attribs) {
  501. return this._featClasses[childIndex].getFeatureName(objId, attribs);
  502. }
  503. /**
  504. * Return symbology for a child layer.
  505. *
  506. * @function getSymbology
  507. * @param {String} childIndex the index of the child layer
  508. * @returns {Object} the symbology object
  509. */
  510. getSymbology (childIndex) {
  511. return this._featClasses[childIndex].symbology;
  512. }
  513. /**
  514. * Run a query on a dynamic layer, return the result as a promise.
  515. * Options:
  516. * - map {Object} map object. A geoApi wrapper, such as esriMap, not an actual esri api map
  517. * - geometry {Object} geometry (in map coordinates) to identify against
  518. * - mapExtent {Object} extent object of the current map view
  519. * - height {Integer} height of the map view in pixels
  520. * - width {Integer} width of the map view in pixels
  521. * - tolerance {Integer} an optional click tolerance for the identify. Defaults to 5
  522. * - returnGeometry {Boolean} if result geometery should be returned with results. Defaults to false
  523. *
  524. * @function identify
  525. * @param {Object} opts additional arguemets, see above.
  526. * @returns {Object} an object with identify results array and identify promise resolving when identify is complete; if an empty object is returned, it will be skipped
  527. */
  528. identify (opts) {
  529. // bundles results from all leaf layers
  530. const identifyResults = [];
  531. if (!shared.layerLoaded(this.state)) {
  532. opts.layerIds = []; // quick quit
  533. } else {
  534. opts.layerIds = this._layer.visibleLayers
  535. .filter(leafIndex => {
  536. if (leafIndex === -1) {
  537. // this is marker for nothing is visible. get rid of it
  538. return false;
  539. } else {
  540. const fc = this._featClasses[leafIndex];
  541. if (fc) {
  542. // keep if it is queryable and on-scale
  543. return fc.queryable && !fc.isOffScale(opts.map.getScale()).offScale;
  544. } else {
  545. // we dont have a feature class for this id.
  546. // it is likely a a group or something visible but not active
  547. return false;
  548. }
  549. }
  550. });
  551. }
  552. // if there are no layerIds to inspect, don't hit the server
  553. if (opts.layerIds.length === 0) {
  554. // TODO verifiy this is correct result format if layer should be excluded from the identify process
  555. return { identifyResults: [], identifyPromise: Promise.resolve() };
  556. }
  557. opts.layerIds.forEach(leafIndex => {
  558. const identifyResult = new shared.IdentifyResult(this.getChildProxy(leafIndex));
  559. identifyResults[leafIndex] = identifyResult;
  560. });
  561. // TODO verify if 0 is valid click tolerance. if so, need to address falsy logic.
  562. opts.tolerance = opts.tolerance || this.clickTolerance || 5;
  563. const identifyPromise = this._apiRef.layer.serverLayerIdentify(this._layer, opts)
  564. .then(clickResults => {
  565. const hitIndexes = []; // sublayers that we got results for
  566. // transform attributes of click results into {name,data} objects
  567. // one object per identified feature
  568. //
  569. // each feature will have its attributes converted into a table
  570. // placeholder for now until we figure out how to signal the panel that
  571. // we want to make a nice table
  572. clickResults.forEach(ele => {
  573. // NOTE: the identify service returns aliased field names, so no need to look them up here.
  574. // however, this means we need to un-alias the data when doing field lookups.
  575. // NOTE: ele.layerId is what we would call featureIdx
  576. hitIndexes.push(ele.layerId);
  577. // get metadata about this sublayer
  578. this.getLayerData(ele.layerId).then(lData => {
  579. const identifyResult = identifyResults[ele.layerId];
  580. if (lData.supportsFeatures) {
  581. const unAliasAtt = attribFC.AttribFC.unAliasAttribs(ele.feature.attributes, lData.fields);
  582. // TODO traditionally, we did not pass fields into attributesToDetails as data was
  583. // already aliased from the server. now, since we are extracting field type as
  584. // well, this means things like date formatting might not be applied to
  585. // identify results. examine the impact of providing the fields parameter
  586. // to data that is already aliased.
  587. identifyResult.data.push({
  588. name: ele.value,
  589. data: this.attributesToDetails(ele.feature.attributes),
  590. oid: unAliasAtt[lData.oidField],
  591. symbology: [{
  592. svgcode: this._apiRef.symbology.getGraphicIcon(unAliasAtt, lData.renderer)
  593. }]
  594. });
  595. }
  596. identifyResult.isLoading = false;
  597. });
  598. });
  599. // set the rest of the entries to loading false
  600. identifyResults.forEach(identifyResult => {
  601. if (hitIndexes.indexOf(identifyResult.requester.featureIdx) === -1) {
  602. identifyResult.isLoading = false;
  603. }
  604. });
  605. });
  606. return {
  607. identifyResults: identifyResults.filter(identifyResult => identifyResult), // collapse sparse array
  608. identifyPromise
  609. };
  610. }
  611. /**
  612. * Retrieves a child layer name from the server.
  613. *
  614. * @function getChildName
  615. * @param {String} childIndex index of the child layer to get attributes for
  616. * @return {String} server name of the child layer
  617. */
  618. getChildName (childIndex) {
  619. // TODO revisit logic. is this the best way to do this? what are the needs of the consuming code?
  620. // TODO restructure so WMS can use this too?
  621. // TODO what about config overrides of names?
  622. // TODO WHO IS EVEN USING THIS?
  623. // will not use FC classes, as we also need group names
  624. return this._layer.layerInfos[index].name;
  625. }
  626. // TODO we may want version of layerRecord.zoomToBoundary that targets a child index.
  627. // alternately this might go on the proxy and then we go direct from there.
  628. }
  629. module.exports = () => ({
  630. DynamicRecord
  631. });