hilight.js

  1. 'use strict';
  2. // TODO hilight layer would be a good candidate for a custom class which internally proxies to ESRI's GraphicsLayer.
  3. const defaultSymbols = require('./defaultHilightSymbols.json');
  4. // contains functions to support the hilight layer.
  5. function cloneBuilder(esriBundle) {
  6. /**
  7. * Clone a graphic from a map-bound layer.
  8. * @method cloneLayerGraphic
  9. * @param {Graphic} graphic an ESRI graphic that resides in a map layer.
  10. * @return {Object} an unbound copy of the graphic
  11. */
  12. return graphic => {
  13. const clone = new esriBundle.Graphic({
  14. geometry: graphic.geometry
  15. });
  16. clone.symbol = graphic.getLayer().renderer.getSymbol(graphic);
  17. return clone;
  18. };
  19. }
  20. function graphicBuilder(esriBundle) {
  21. /**
  22. * Generating a graphic from server geometry.
  23. * @method geomToGraphic
  24. * @param {Object} geometry feature geometry conforming to ESRI Geometry standard
  25. * @param {Object} symbol esri symbol in server format
  26. * @return {Object} an ESRI GraphicsLayer
  27. */
  28. return (geometry, symbol) => {
  29. const graphic = new esriBundle.Graphic({
  30. geometry
  31. });
  32. graphic.symbol = esriBundle.symbolJsonUtils.fromJson(symbol);
  33. return graphic;
  34. };
  35. }
  36. function getGraphicsBuilder(esriBundle, geoApi) {
  37. // TODO once document sites are up and running, figure out a way to hyperlink the graphicBundles parameter to the class documentation page in the viewer site
  38. /**
  39. * Generating a graphic from server geometry.
  40. * @method getUnboundGraphics
  41. * @param {Array} graphicBundles set of graphic bundles with properties .graphic, .source, .layer, .featureIdx.
  42. * @param {Object} spatialReference the projection the unbound graphics should be in
  43. * @return {Array} a set of promises that resolve with an unbound graphic, one for each graphic bundle provided
  44. */
  45. return (graphicBundles, spatialReference) => {
  46. // generate detached graphics to give to the hilight layer.
  47. // promises because server layers renderer is inside a promise
  48. return graphicBundles.map(bundle => {
  49. if (bundle.source === 'server') {
  50. let geom = bundle.graphic.geometry;
  51. // check projection
  52. if (!geoApi.proj.isSpatialRefEqual(geom.spatialReference, spatialReference)) {
  53. geom = geoApi.proj.localProjectGeometry(spatialReference, geom);
  54. }
  55. // determine symbol for this server graphic
  56. const attribs = bundle.layer.attributeBundle;
  57. return attribs[bundle.featureIdx].layerData.then(layerData => {
  58. const symb = geoApi.symbology.getGraphicSymbol(bundle.graphic.attributes, layerData.renderer);
  59. return geoApi.hilight.geomToGraphic(geom, symb);
  60. });
  61. } else {
  62. // local graphic. clone and hilight
  63. return Promise.resolve(geoApi.hilight.cloneLayerGraphic(bundle.graphic));
  64. }
  65. });
  66. };
  67. }
  68. function hilightBuilder(esriBundle) {
  69. /**
  70. * Generate a graphic layer to handle feature hilighting.
  71. * @method makeHilightLayer
  72. * @param {Object} options optional settings for the hilight layer
  73. * layerId - id to use for the hilight layer. defaults to rv_hilight
  74. * pinSymbol - esri symbol in server json format to symbolize the click marker. defaults to a red pin
  75. * hazeOpacity - how opaque the haze sheet behind the hilight is. 0 to 255, 0 being transparent. defaults to 127
  76. * @return {Object} an ESRI GraphicsLayer
  77. */
  78. return options => {
  79. // set options
  80. let id = 'rv_hilight';
  81. let hazeOpac = 127;
  82. let pinSymbol = defaultSymbols.pinSymbol;
  83. if (options) {
  84. if (options.layerId) {
  85. id = options.layerId;
  86. }
  87. if (options.pinSymbol) {
  88. pinSymbol = options.pinSymbol;
  89. }
  90. if (options.hazeOpacity) {
  91. hazeOpac = options.hazeOpacity;
  92. }
  93. }
  94. const hgl = new esriBundle.GraphicsLayer({ id, visible: true });
  95. // ensure highlight is top-most graphic layer
  96. function moveHilightToTop() {
  97. hgl._map.reorderLayer(hgl, hgl._map.graphicsLayerIds.length);
  98. }
  99. /**
  100. * Add a graphic to indicate where user clicked.
  101. * @method addPin
  102. * @param {Point} point an ESRI point object to use as the graphic location
  103. */
  104. hgl.addPin = point => {
  105. const pin = new esriBundle.Graphic({ symbol: pinSymbol });
  106. pin.setGeometry(point);
  107. hgl.add(pin);
  108. moveHilightToTop();
  109. };
  110. /**
  111. * Add a graphic or array of graphics to the highlight layer. Remove any previous graphics.
  112. * @method addHilight
  113. * @param {Graphic|Array} graphic an ESRI graphic, or array of ESRI graphics. Should be in map spatialReference, and not bound to a layer
  114. */
  115. hgl.addHilight = graphic => {
  116. const graphics = Array.isArray(graphic) ? graphic : [graphic];
  117. if (hgl._hilightGraphics) {
  118. // if active hilight graphics, remove them
  119. hgl._hilightGraphics.forEach(g => hgl.remove(g));
  120. } else {
  121. // first application of hilight. add haze background by creating a partially opaque layer for
  122. // the whole map extent with some buffer. This will go under the highlighted graphic to make it stand out.
  123. const hazeJson = {
  124. symbol: defaultSymbols.hazeSymbol
  125. };
  126. hazeJson.symbol.color[3] = hazeOpac;
  127. const hazeGraphic = new esriBundle.Graphic(hazeJson);
  128. hazeGraphic.setGeometry(hgl._map.extent.expand(1.5)); // expand to avoid edges on quick pan
  129. hazeGraphic.haze = true; // notifies layer to put this under any hilight graphics
  130. hgl.add(hazeGraphic);
  131. }
  132. // add new hilight graphics
  133. hgl._hilightGraphics = graphics;
  134. graphics.forEach(g => hgl.add(g));
  135. moveHilightToTop();
  136. };
  137. /**
  138. * Remove hilight from map
  139. * @method clearHilight
  140. */
  141. hgl.clearHilight = () => {
  142. // clear tracking vars, wipe the layer
  143. hgl._hilightGraphics = null;
  144. hgl.clear();
  145. };
  146. hgl.on('graphic-node-add', e => {
  147. // figure out if graphic needs to be at top or bottom of hilight layer
  148. // haze polygon goes to bottom, everything else to top
  149. const g = e.graphic;
  150. const dojoShape = g.getShape();
  151. if (g.haze) {
  152. dojoShape.moveToBack();
  153. } else {
  154. dojoShape.moveToFront();
  155. }
  156. });
  157. return hgl;
  158. };
  159. }
  160. module.exports = (esriBundle, geoApi) => ({
  161. makeHilightLayer: hilightBuilder(esriBundle),
  162. geomToGraphic: graphicBuilder(esriBundle),
  163. cloneLayerGraphic: cloneBuilder(esriBundle),
  164. getUnboundGraphics: getGraphicsBuilder(esriBundle, geoApi)
  165. });