Reusable Accessible Mapping Platform

API Docs for: 5.3.1
Show:

File: src/js/RAMP/Modules/map.js

  1. /*global define, esri, i18n, console, $, RAMP, proj4, window */
  2.  
  3. /**
  4. *
  5. * A RAMP Map module with ESRI and Dojo AMD Modules
  6. * This module provides function to create an ESRI web map control. A global config object is
  7. * required to initialize the map.
  8. *
  9. * @module RAMP
  10. * @submodule Map
  11. * @main Map
  12. */
  13.  
  14. /**
  15. * Map class represents the ESRI map object. The map is generated based on the application configuration and templates.
  16. *
  17. * ####Imports RAMP Modules:
  18. * {{#crossLink "GlobalStorage"}}{{/crossLink}}
  19. * {{#crossLink "RAMP"}}{{/crossLink}}
  20. * {{#crossLink "FeatureClickHandler"}}{{/crossLink}}
  21. * {{#crossLink "MapClickHandler"}}{{/crossLink}}
  22. * {{#crossLink "Navigation"}}{{/crossLink}}
  23. * {{#crossLink "EventManager"}}{{/crossLink}}
  24. * {{#crossLink "Util"}}{{/crossLink}}
  25. * {{#crossLink "Array"}}{{/crossLink}}
  26. *
  27. * @class Map
  28. * @static
  29. * @uses dojo/_base/declare
  30. * @uses dojo/_base/array
  31. * @uses dojo/dom
  32. * @uses dojo/dom-construct
  33. * @uses dojo/number
  34. * @uses dojo/query
  35. * @uses dojo/topic
  36. * @uses dojo/on
  37. * @uses esri/map
  38. * @uses esri/layers/FeatureLayer
  39. * @uses esri/layers/ArcGISTiledMapServiceLayer
  40. * @uses esri/layers/ArcGISDynamicMapServiceLayer
  41. * @uses esri/layers/WMSLayer
  42. * @uses esri/SpatialReference
  43. * @uses esri/dijit/Scalebar
  44. * @uses esri/geometry/Extent
  45. * @uses esri/tasks/GeometryService
  46. * @uses esri/tasks/ProjectParameters
  47. */
  48.  
  49. define([
  50. /* Dojo */
  51. "dojo/_base/declare", "dojo/_base/array", "dojo/dom",
  52. "dojo/dom-construct", "dojo/number", "dojo/query", "dojo/topic", "dojo/on",
  53.  
  54. /* Esri */
  55. "esri/map", "esri/layers/FeatureLayer", "esri/layers/ArcGISTiledMapServiceLayer", "esri/layers/ArcGISDynamicMapServiceLayer",
  56. "esri/SpatialReference", "esri/dijit/Scalebar", "esri/geometry/Extent", "esri/layers/WMSLayer", "esri/tasks/GeometryService", "esri/tasks/ProjectParameters",
  57.  
  58. /* Ramp */
  59. "ramp/globalStorage", "ramp/ramp", "ramp/featureClickHandler", "ramp/mapClickHandler", "ramp/navigation", "ramp/eventManager",
  60.  
  61. /* Util */
  62. "utils/util", "utils/array", "utils/dictionary"],
  63.  
  64. function (
  65. /* Dojo */
  66. declare, dojoArray, dom, domConstruct, number, query, topic, dojoOn,
  67.  
  68. /* Esri */
  69. EsriMap, FeatureLayer, ArcGISTiledMapServiceLayer, ArcGISDynamicMapServiceLayer,
  70. SpatialReference, EsriScalebar, EsriExtent, WMSLayer, GeometryService, ProjectParameters,
  71.  
  72. /* Ramp */
  73. GlobalStorage, Ramp, FeatureClickHandler, MapClickHandler, Navigation, EventManager,
  74.  
  75. /* Util */
  76. UtilMisc, UtilArray, UtilDict) {
  77. "use strict";
  78.  
  79. /**
  80. * An Array of {{#crossLink "Esri/layers/FeatureLayer"}}{{/crossLink}} objects.
  81. *
  82. * @private
  83. * @property featureLayers {Array}
  84. */
  85. var featureLayers,
  86.  
  87. /**
  88. * An Array of {{#crossLink "Esri/layer/WMSLayer"}}{{/crossLink}} objects.
  89. *
  90. * @private
  91. * @property wmsLayers {Array}
  92. */
  93. wmsLayers = [],
  94.  
  95. /**
  96. * Maps the id of a graphic layer to the GraphicsLayer Object that represents its extent bounding box.
  97. * A dictionary of String, {{#crossLink "Esri/layers/GraphicsLayer"}}{{/crossLink}} pairs.
  98. *
  99. * @private
  100. * @property boundingBoxMapping {Object}
  101. */
  102. boundingBoxMapping = {},
  103.  
  104. /**
  105. * The map not only contains feature layers, but also other layers such as the
  106. * basemap layer, highlight layer, bounding box layer, etc. This variable is
  107. * used to store the starting index of the feature layers in the map.
  108. *
  109. * @private
  110. * @property featureLayerStartIndex {Integer}
  111. */
  112. featureLayerStartIndex,
  113.  
  114. map,
  115.  
  116. fullExtent,
  117. maxExtent,
  118. initExtent;
  119.  
  120. /**
  121. * Shows the loading image.
  122. *
  123. * @private
  124. * @method _showLoadingImg
  125. */
  126. function _showLoadingImg() {
  127. $("#map-load-indicator").removeClass("hidden");
  128. }
  129.  
  130. /**
  131. * Hides the loading image.
  132. *
  133. * @private
  134. * @method _hideLoadingImg
  135. */
  136. function _hideLoadingImg() {
  137. $("#map-load-indicator").addClass("hidden");
  138. }
  139.  
  140. /**
  141. * Update Map Scale when zoom level changes
  142. *
  143. * @private
  144. * @method _updateScale
  145. * @param {Object} event
  146. */
  147. function _updateScale(event) {
  148. if (event.levelChange) {
  149. var currentScale = number.format(event.lod.scale),
  150. scaleLabelText = "1 : " + currentScale;
  151.  
  152. domConstruct.empty('scaleLabel');
  153. $("#scaleLabel").text(scaleLabelText);
  154. }
  155. }
  156.  
  157. /**
  158. * Initialize Map Scale
  159. *
  160. * @private
  161. * @method _initScale
  162. * @param {Object} event
  163. */
  164. function _initScale(event) {
  165. var map = event.map,
  166. scaleDiv = domConstruct.create("div", {
  167. id: "scaleDiv",
  168. class: "esriScalebarLabel"
  169. }),
  170. currentScale,
  171. scaleLabelText;
  172. $(scaleDiv).html("<span>" + i18n.t('map.scale') + "</span><br><span id='scaleLabel'><span/>");
  173. currentScale = number.format(map.getScale());
  174. scaleLabelText = "1 : " + currentScale;
  175.  
  176. domConstruct.place(scaleDiv, query(".esriScalebarRuler")[0], "before");
  177. domConstruct.empty('scaleLabel');
  178. $("#scaleLabel").text(scaleLabelText);
  179.  
  180. // Change the css class of the scale bar so it shows up against
  181. // the map
  182. topic.subscribe(EventManager.BasemapSelector.BASEMAP_CHANGED, function (attr) {
  183. $(".esriScalebar > div").removeClass().addClass(attr.cssStyle);
  184. });
  185. }
  186.  
  187. /**
  188. * Republishes map events to the outside using topic.publish
  189. *
  190. * @method _initRepublishers
  191. * @param {Object} map object
  192. * @private
  193. */
  194. function _initRepublishers(map) {
  195. var prefix = "map";
  196.  
  197. /**
  198. * Republish map events using topic.publish
  199. *
  200. * @method republish
  201. * @param {String} name
  202. * @private
  203. */
  204. function republish(name) {
  205. map.on(name, function (event) {
  206. topic.publish(prefix + "/" + name, event);
  207. });
  208. }
  209.  
  210. republish("update-end");
  211. republish("extent-change");
  212. republish("zoom-start");
  213. republish("zoom-end");
  214. republish("pan-start");
  215. republish("pan-end");
  216. }
  217.  
  218. /**
  219. * Subscribe to external events (published using topic.publish)
  220. * and react accordingly
  221. *
  222. * @method _initListeners
  223. * @param {Object} map map object
  224. * @private
  225. */
  226. function _initListeners(map) {
  227. /* SUBSCRIBED EVENTS */
  228. topic.subscribe(EventManager.Map.CENTER_AT, function (event) {
  229. map.centerAt(event.point);
  230. });
  231.  
  232. topic.subscribe(EventManager.Map.CENTER_AND_ZOOM, function (event) {
  233. var point = new esri.geometry.Point(event.graphic.geometry.x, event.graphic.geometry.y, map.spatialReference),
  234. d = map.centerAndZoom(point, event.level); // Last parameter is the level
  235.  
  236. if (event.callback) {
  237. d.then(event.callback);
  238. }
  239. });
  240.  
  241. topic.subscribe(EventManager.Map.SET_EXTENT, function (event) {
  242. event.extent.spatialReference = map.spatialReference;
  243. var d = map.setExtent(event.extent);
  244.  
  245. if (event.callback) {
  246. d.then(event.callback);
  247. }
  248. });
  249.  
  250. /* NAVIGATION EVENTS */
  251. topic.subscribe(EventManager.Navigation.PAN, function (event) {
  252. // event.direction panUp, panDown, panLeft, panRight
  253. // this same as calling map.panUp(), map.panDown(), map.panLeft(), map.panRight()
  254. map[event.direction]();
  255. });
  256.  
  257. topic.subscribe(EventManager.Navigation.ZOOM_STEP, function (event) {
  258. map.setLevel(map.getLevel() + event.level);
  259. });
  260.  
  261. topic.subscribe(EventManager.Navigation.ZOOM, function (event) {
  262. map.setLevel(event.level);
  263. });
  264.  
  265. topic.subscribe(EventManager.Navigation.FULL_EXTENT, function () {
  266. map.setExtent(fullExtent);
  267. });
  268.  
  269. /* GUI EVENTS */
  270. topic.subscribe(EventManager.GUI.LAYOUT_CHANGE, function () {
  271. map.resize(true);
  272. });
  273.  
  274. // Unhighlight the point when the subpanel is collapsed
  275. topic.subscribe(EventManager.GUI.SUBPANEL_CHANGE, function (eventArg) {
  276. // unhighlight only if the panel closing is the details panel
  277. if (!eventArg.visible &&
  278. eventArg.isComplete &&
  279. (eventArg.origin === ("rampPopup" || "datagrid"))) {
  280. topic.publish(EventManager.FeatureHighlighter.HIGHLIGHT_HIDE, {});
  281. }
  282. });
  283.  
  284. /* START BOUNDING BOX TOGGLE */
  285.  
  286. topic.subscribe(EventManager.FilterManager.LAYER_VISIBILITY_TOGGLED, function (evt) {
  287. var setTo = evt.state,
  288. layerId = evt.id,
  289. // either take url (would need mapping to layer on map),
  290. // map id in config, graphic layer id
  291. layer = map.getLayer(layerId);
  292.  
  293. layer.setVisibility(setTo);
  294. //loops through any static layers that are mapped to the feature layer being toggled
  295. try {
  296. dojoArray.forEach(GlobalStorage.LayerMap[layerId], function (staticLayer) {
  297. var layer = map.getLayer(staticLayer);
  298. layer.setVisibility(setTo);
  299. });
  300. }
  301. catch (err) {
  302. }
  303. });
  304.  
  305. topic.subscribe(EventManager.FilterManager.LAYER_TRANSPARENCY_CHANGED, function (evt) {
  306. var layer = map.getLayer(evt.layerId);
  307.  
  308. if (layer !== undefined) {
  309. layer.setOpacity(evt.value);
  310. //loops through any static layers that are mapped to the feature layer being toggled
  311. try {
  312. dojoArray.forEach(GlobalStorage.LayerMap[evt.layerId], function (staticLayer) {
  313. var layer = map.getLayer(staticLayer);
  314. layer.setOpacity(evt.value);
  315. });
  316. }
  317. catch (err) {
  318. }
  319. }
  320. });
  321.  
  322. topic.subscribe(EventManager.FilterManager.BOX_VISIBILITY_TOGGLED, function (evt) {
  323. setBoundingBoxVisibility(evt.id, evt.state);
  324. });
  325. /* END BOUNDING BOX TOGGLE */
  326.  
  327. topic.subscribe(EventManager.FilterManager.SELECTION_CHANGED, function (evt) {
  328. // this is handling the user trying to re-order the layers
  329. // graphical and feature layers must be handled separately from
  330. // all other layers as ESRI separates the two internally
  331.  
  332. var newIndex = evt.index,
  333. featureLayers;
  334.  
  335. if (map.layerIds.contains(evt.id)) {
  336. featureLayers = dojoArray.map(map.graphicsLayerIds, function (x) {
  337. return map.getLayer(x).type === 'Feature Layer' ? 1 : 0;
  338. }).sum();
  339. newIndex += 1 - featureLayers; // offset by 1 basemap not accounted for
  340. console.log('newIndex ' + newIndex);
  341. console.log(map.layerIds);
  342. } else {
  343. if (!featureLayerStartIndex) {
  344. // Find the index of the first feature layer
  345. featureLayerStartIndex = UtilArray.indexOf(map.graphicsLayerIds, function (layerId) {
  346. var layer = map.getLayer(layerId);
  347. return layer.type && layer.type === "Feature Layer";
  348. });
  349. }
  350. newIndex += featureLayerStartIndex;
  351. }
  352. map.reorderLayer(map.getLayer(evt.id), newIndex);
  353. topic.publish(EventManager.Map.REORDER_END);
  354. });
  355.  
  356. //TODO this will likely get removed or amended by aly
  357. /* Add Layer subscription*/
  358. topic.subscribe(EventManager.Map.ADD_LAYER, function () {
  359. var type = dom.byId("addLayer-select-type").value,
  360. URL = dom.byId("addLayer-URL-input").value,
  361. opacity = dom.byId("addLayer-Opacity").value;
  362.  
  363. console.log(type + " | " + URL + " | " + opacity);
  364. addStaticLayer(type, URL, opacity);
  365. });
  366.  
  367. topic.subscribe(EventManager.Map.ADD_LAYER_READY, function (temp_layer) {
  368. map.addLayer(temp_layer);
  369. });
  370. }
  371. /**
  372. * Creates event handlers for the map control: click, mouse-over, load, extent change, and update events.
  373. *
  374. * @private
  375. * @method _initEventHandlers
  376. * @param {Object} map A ESRI map object
  377. */
  378. function _initEventHandlers(map) {
  379. map.on("load", _initScale);
  380. map.on("extent-change", function (event) {
  381. _updateScale(event);
  382.  
  383. console.log("map - >> extent-change", event);
  384. dojoOn.once(map, "update-end", function () {
  385. console.log("map - >> update-end - >> Apply extent Filter");
  386. topic.publish(EventManager.Datagrid.APPLY_EXTENT_FILTER);
  387. });
  388. });
  389.  
  390. // Deselect all highlighted points if the map is clicked
  391. map.on("click", function (evt) {
  392. FeatureClickHandler.onFeatureDeselect(evt);
  393. topic.publish(EventManager.Map.CLICK, evt);
  394. });
  395.  
  396. // Show/Hide spinner for map loading
  397. map.on("update-start", _showLoadingImg);
  398. map.on("update-end", _hideLoadingImg);
  399.  
  400. // code that would wait until all layers were loaded. not used anymore, but could be useful to keep around to steal later
  401. /*
  402. var handle = map.on("update-end", function () {
  403. var isAllLoaded = dojoArray.every(
  404. map.graphicsLayerIds.concat(map.layerIds),
  405. function (layerId) {
  406. var layer = map.getLayer(layerId);
  407. console.log(layer.loaded, layerId, layer);
  408. return layer.loaded;
  409. }
  410. );
  411.  
  412. console.log("map -> is all layers loaded: ", isAllLoaded);
  413.  
  414. if (isAllLoaded) {
  415. handle.remove();
  416. console.log("map ->", EventManager.Map.ALL_LAYERS_LOADED);
  417. topic.publish(EventManager.Map.ALL_LAYERS_LOADED);
  418. }
  419. });
  420. */
  421. }
  422.  
  423. /**
  424. * Will project an extent to a desired spatial reference, using client side projection library.
  425. * Avoids the need for Esri Geometry Service
  426. *
  427. * @method localProjectExtent
  428. * @private
  429. * @param {Esri/Extent} extent extent to be projected
  430. * @param {Esri/SpatialReference} sr {{#crossLink "Esri/SpatialReference"}}{{/crossLink}} to project to
  431. * @return {Esri/Extent} extent in the desired projection
  432. */
  433. function localProjectExtent(extent, sr) {
  434. //TODO can we handle WKT?
  435. // we can now
  436.  
  437. // interpolates two points by splitting the line in half recursively
  438. function interpolate(p0, p1, steps) {
  439. var mid, i0, i1;
  440.  
  441. if (steps === 0) { return [p0, p1]; }
  442.  
  443. mid = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
  444. if (steps === 1) {
  445. return [p0, mid, p1];
  446. }
  447. if (steps > 1) {
  448. i0 = interpolate(p0, mid, steps - 1);
  449. i1 = interpolate(mid, p1, steps - 1);
  450. return i0.concat(i1.slice(1));
  451. }
  452. }
  453.  
  454. var points = [[extent.xmin, extent.ymin], [extent.xmax, extent.ymin], [extent.xmax, extent.ymax], [extent.xmin, extent.ymax], [extent.xmin, extent.ymin]],
  455. projConvert, transformed, projExtent, x0, y0, x1, y1, xvals, yvals, interpolatedPoly = [], srcProj;
  456.  
  457. // interpolate each edge by splitting it in half 3 times (since lines are not guaranteed to project to lines we need to consider
  458. // max / min points in the middle of line segments)
  459. [0, 1, 2, 3].map(function (i) { return interpolate(points[i], points[i + 1], 3).slice(1); }).forEach(function (seg) { interpolatedPoly = interpolatedPoly.concat(seg); });
  460.  
  461. //reproject the extent
  462. if (extent.spatialReference.wkid) {
  463. srcProj = 'EPSG:' + extent.spatialReference.wkid;
  464. } else if (extent.spatialReference.wkt) {
  465. srcProj = extent.spatialReference.wkt;
  466. } else {
  467. throw new Error('No WKT or WKID specified on extent.spatialReference');
  468. }
  469. projConvert = proj4(srcProj, 'EPSG:' + sr.wkid);
  470. transformed = interpolatedPoly.map(function (x) { return projConvert.forward(x); });
  471.  
  472. xvals = transformed.map(function (x) { return x[0]; });
  473. yvals = transformed.map(function (x) { return x[1]; });
  474.  
  475. x0 = Math.min.apply(null, xvals);
  476. x1 = Math.max.apply(null, xvals);
  477.  
  478. y0 = Math.min.apply(null, yvals);
  479. y1 = Math.max.apply(null, yvals);
  480.  
  481. projExtent = new EsriExtent(x0, y0, x1, y1, sr);
  482. console.log('localProjectExtent complete: ' + JSON.stringify(projExtent));
  483.  
  484. return projExtent;
  485. }
  486.  
  487. /**
  488. * project an extent to a new spatial reference, if required
  489. * when projection is finished, call another function and pass the result to it.
  490. *
  491. * @method projectExtent
  492. * @private
  493. * @param {Object} extent an extent object from the configuration
  494. * @param {Esri/SpatialReference} sr {{#crossLink "Esri/SpatialReference"}}{{/crossLink}} to project to
  495. * @param {Function} callWhenDone function to call when extent is projected. expects geometry parameter
  496. */
  497. function projectExtent(extent, sr, callWhenDone) {
  498. var realExtent;
  499.  
  500. //convert configuration extent to proper esri extent object
  501. realExtent = new EsriExtent(extent);
  502.  
  503. if (UtilMisc.isSpatialRefEqual(realExtent.spatialReference, sr)) {
  504. //the extent is already in the correct projection.
  505. //go to the next step
  506. callWhenDone([realExtent]);
  507. } else {
  508. //need to re-project the extent
  509.  
  510. var projectedExtent = localProjectExtent(realExtent, sr);
  511. callWhenDone([projectedExtent]);
  512.  
  513. //Geometry Service Version. Makes a more accurate bounding box, but requires an arcserver
  514. /*
  515. var geomSrv, geomParams;
  516. geomSrv = new GeometryService(RAMP.config.geometryServiceUrl);
  517. geomParams = new ProjectParameters();
  518. geomParams.geometries = [realExtent];
  519. geomParams.outSR = sr;
  520.  
  521. geomSrv.project(geomParams, function (projectedExtents) {
  522. //after service returns, continue to next step
  523. callWhenDone(projectedExtents);
  524. });
  525. */
  526. }
  527. }
  528.  
  529. /**
  530. * process the projected default extent, begin projection of full extent.
  531. * used as an asynchronous gate for the projection.
  532. *
  533. * @method projectFullExtent
  534. * @private
  535. * @param {Array} projectedDefaultExtent an array containing the default extent object in the map's projection
  536. */
  537. function projectFullExtent(projectedDefaultExtent) {
  538. //store projected result
  539. RAMP.config.extents.defaultExtent = projectedDefaultExtent[0];
  540.  
  541. //project the full extent. when finished, process max extent
  542. projectExtent(RAMP.config.extents.fullExtent, projectedDefaultExtent[0].spatialReference, projectMaxExtent);
  543. }
  544.  
  545. /**
  546. * process the projected full extent, begin projection of maximum extent.
  547. * used as an asynchronous gate for the projection.
  548. *
  549. * @method projectMaxExtent
  550. * @private
  551. * @param {Array} projectedFullExtent an array containing the full extent object in the map's projection
  552. */
  553. function projectMaxExtent(projectedFullExtent) {
  554. //store projected result
  555. RAMP.config.extents.fullExtent = projectedFullExtent[0];
  556.  
  557. //project the max extent. when finished, tell map to continue loading
  558. projectExtent(RAMP.config.extents.maximumExtent, projectedFullExtent[0].spatialReference, finishExtentProjection);
  559. }
  560.  
  561. /**
  562. * process the projected maximum extent, then alert app to continue loading the map.
  563. * used as an asynchronous gate for the projection.
  564. *
  565. * @method finishExtentProjection
  566. * @private
  567. * @param {Array} projectedMaxExtent an array containing the maximum extent object in the map's projection
  568. */
  569. function finishExtentProjection(projectedMaxExtent) {
  570. //store projected result
  571. RAMP.config.extents.maximumExtent = projectedMaxExtent[0];
  572.  
  573. //throw event
  574. topic.publish(EventManager.Map.EXTENTS_REPROJECTED);
  575. }
  576.  
  577. /**
  578. * Add a static, non-interactive Layer to the map
  579. * NOTE: this function is currently not being used.
  580. *
  581. * @private
  582. * @method AddStaticLayer
  583. * @param {String} layer_type A value which controls how the layer is going to be added to the map
  584. * @param {String} layer_url A URL pointing to a valid map service endpoint
  585. * @param {Number} layer_op A value between 0.0 and 1.0 which determines the transparency of the layer
  586. */
  587. function addStaticLayer(layer_type, layer_url, layer_op) {
  588. //TODO: consider removing this?
  589. layer_op = layer_op / 100; // change percentage to decimal
  590. var tempLayer;
  591. switch (layer_type) {
  592. case "feature":
  593. tempLayer = new FeatureLayer(layer_url, {
  594. opacity: layer_op,
  595. mode: FeatureLayer.MODE_SNAPSHOT
  596. });
  597. break;
  598.  
  599. case "tile":
  600. tempLayer = new ArcGISTiledMapServiceLayer(layer_url, {
  601. opacity: layer_op
  602. });
  603. break;
  604.  
  605. case "dynamic":
  606. tempLayer = new ArcGISDynamicMapServiceLayer(layer_url, {
  607. opacity: layer_op
  608. });
  609. break;
  610.  
  611. default:
  612.  
  613. break;
  614. }
  615.  
  616. topic.publish(EventManager.Map.ADD_LAYER_READY, tempLayer);
  617. topic.publish(EventManager.GUI.ADD_LAYER_PANEL_CHANGE, {
  618. visible: false
  619. });
  620. }
  621.  
  622. /**
  623. * Sets the visibility of the bounding box that belongs to the layer with the given layerId.
  624. * Note: the layerId needs to be the ID of the featurelayer, not the ID of the actual bounding
  625. * box layer.
  626. *
  627. * @private
  628. * @method setBoundingBoxVisibility
  629. * @param {String} layerId the id of the layer whose bounding box visibility should be set
  630. */
  631. function setBoundingBoxVisibility(layerId, visibility) {
  632. var boxLayer = boundingBoxMapping[layerId];
  633.  
  634. //if (boxLayer.graphics.isEmpty() && visibility) {
  635. // // get bounding box info from config object
  636. // var boundingBoxExtent;
  637. // var layerConfig = dojoArray.find(config.featureLayers, function (layerConfig) {
  638. // return layerConfig.id === layerId;
  639. // });
  640.  
  641. // boundingBoxExtent.xmin = layerConfig.boundingBox.extent.xmin;
  642. // boundingBoxExtent.ymin = layerConfig.boundingBox.extent.ymin;
  643. // boundingBoxExtent.xmax = layerConfig.boundingBox.extent.xmax;
  644. // boundingBoxExtent.ymax = layerConfig.boundingBox.extent.ymax;
  645.  
  646. // var extentGraphic = new esri.Graphic({
  647. // geometry: boundingBoxExtent,
  648. // symbol: {
  649. // color: [255, 0, 0, 64],
  650. // outline: {
  651. // color: [240, 128, 128, 255],
  652. // width: 1,
  653. // type: "esriSLS",
  654. // style: "esriSLSSolid"
  655. // },
  656. // type: "esriSFS",
  657. // style: "esriSFSSolid"
  658. // }
  659. // });
  660. // boxLayer.add(extentGraphic);
  661. //}
  662.  
  663. boxLayer.setVisibility(visibility);
  664. }
  665.  
  666. function resolveLayerOpacity(layerOpacity) {
  667. return layerOpacity.default || 1;
  668. }
  669.  
  670. /**
  671. * Sets up loading event handlers and initializes the .ramp object of a layer
  672. * Circular reference errors prevent us from calling LayerLoader directly from this module
  673. *
  674. * @private
  675. * @method prepLayer
  676. * @param {Object} layer layer to be prepped
  677. * @param {Object} config config object for the layer
  678. * @param {Boolean} userLayer optional. indicates if layer was added by a user. default value is false
  679. */
  680. function prepLayer(layer, config, userLayer) {
  681. layer.ramp = {
  682. config: config,
  683. user: typeof userLayer === 'boolean' ? userLayer : false,
  684. load: {
  685. state: "loading",
  686. inLS: false, //layer has entry in layer selector
  687. inCount: false //layer is included in the layer counts
  688. }
  689. };
  690.  
  691. layer.on('load', function (evt) {
  692. //console.log("PREP LOAD OK " + evt.layer.url);
  693. topic.publish(EventManager.LayerLoader.LAYER_LOADED, { layer: evt.layer });
  694. });
  695.  
  696. layer.on('error', function (evt) {
  697. //console.log("PREP LOAD FAIL " + evt.target.url);
  698. evt.target.ramp.loadOk = false;
  699. console.log('layer failed to load');
  700. console.log(evt);
  701. topic.publish(EventManager.LayerLoader.LAYER_ERROR, {
  702. layer: evt.target,
  703. error: evt.error
  704. });
  705. });
  706.  
  707. //since the update-start event doesn't let you know who threw it (supposed to but doesn't), we need to tack the handler
  708. //function onto the actual layer object so we can use the "this" keyword to grab the sending layer
  709. layer.ramp.load.onUpdateStart = function () {
  710. topic.publish(EventManager.LayerLoader.LAYER_UPDATING, { layer: this });
  711. };
  712.  
  713. layer.on('update-start', layer.ramp.load.onUpdateStart);
  714.  
  715. //add update end handler for layer
  716. layer.on('update-end', function (evt) {
  717. topic.publish(EventManager.LayerLoader.LAYER_UPDATED, { layer: evt.target });
  718. });
  719. }
  720.  
  721. return {
  722. /**
  723. * For a specified layer, zooms to the closest level that has some visible data.
  724. * @param {String} layerId a layer id to zoom to.
  725. * @method zoomToLayerScale
  726. */
  727. zoomToLayerScale: function (layerId) {
  728. var layer = map.getLayer(layerId),
  729. lods = map._params.lods,
  730. currentLevel = map.getLevel(),
  731. topLod,
  732. bottomLod,
  733. lod,
  734. i;
  735.  
  736. //gis lesson.
  737. //min scale means dont show the layer if zoomed out beyond the min scale
  738. //max scale means dont show the layer if zoomed in beyond the max scale
  739. //from a numerical perspective, min > max (as the scale number represents 1/number )
  740.  
  741. for (i = 0; i < lods.length; i += 1) {
  742. lod = lods[i];
  743. //console.log("lod", lod, lod.scale > layer.minScale);
  744. if (!topLod && lod.scale <= layer.minScale) {
  745. topLod = lod;
  746. }
  747.  
  748. if (!bottomLod && lod.scale <= layer.maxScale) {
  749. bottomLod = lods[Math.max(0, i - 1)];
  750. }
  751. }
  752.  
  753. //assign defaults for open-ended ranges
  754. //we will never have the case where both values are 0, as in that case the layer is visible everywhere, and this
  755. //function will not be called (it's only used when layer is out-of-scale range).
  756.  
  757. if (layer.minScale === 0) {
  758. //no ceiling. top = bottom
  759. topLod = bottomLod;
  760. }
  761.  
  762. if (layer.maxScale === 0) {
  763. //no floor. bottom = top
  764. bottomLod = topLod;
  765. }
  766.  
  767. //console.log(topLod, bottomLod, map.getLevel(), map.getZoom(), Math.abs(topLod.level - currentLevel) <= Math.abs(bottomLod.level - currentLevel));
  768.  
  769. if (Math.abs(topLod.level - currentLevel) <= Math.abs(bottomLod.level - currentLevel)) {
  770. map.setZoom(topLod.level);
  771. } else {
  772. map.setZoom(bottomLod.level);
  773. }
  774. },
  775.  
  776. /**
  777. * For a specified layer, determine if the layer's scale fit into map LOD range.
  778. * Return ture if in range
  779. * @param { number } maxScale maximum scale.
  780. * @param { number } minScale minimum scale
  781. * @method layerInLODRange
  782. * @type { boolean }
  783. */
  784. layerInLODRange: function (maxScale, minScale) {
  785. var lods = map._params.lods,
  786. topLod = -1,
  787. bottomLod = -1,
  788. lod,
  789. i,
  790. inRange = false;
  791.  
  792. //gis lesson.
  793. //min scale means dont show the layer if zoomed out beyond the min scale
  794. //max scale means dont show the layer if zoomed in beyond the max scale
  795. //from a numerical perspective, min > max (as the scale number represents 1/number )
  796. if (maxScale === 0) {
  797. bottomLod = 0;
  798. }
  799.  
  800. if (minScale === 0) {
  801. topLod = 0;
  802. }
  803.  
  804. for (i = 0; i < lods.length; i += 1) {
  805. lod = lods[i];
  806.  
  807. if (topLod === -1 && lod.scale <= minScale) {
  808. topLod = lod;
  809. }
  810.  
  811. if (bottomLod === -1 && lod.scale <= maxScale) {
  812. bottomLod = lods[Math.max(0, i - 1)];
  813. }
  814. }
  815.  
  816. if (maxScale === 0 && minScale === 0) {
  817. inRange = true;
  818. } else if (minScale === 0) {
  819. // check only maxScale (bottomLod)
  820. inRange = (bottomLod === -1) ? false : true;
  821. } else if (maxScale === 0) {
  822. // check only minScale (topLod)
  823. inRange = (topLod === -1) ? false : true;
  824. } else {
  825. inRange = (topLod !== -1 && bottomLod !== -1);
  826. }
  827.  
  828. return inRange;
  829. },
  830.  
  831. /**
  832. * The maximum extent of the map control is allowed to go to
  833. * @property getMaxExtent
  834. * @type {Object}
  835. *
  836. */
  837. getMaxExtent: function () {
  838. return maxExtent;
  839. },
  840. /**
  841. * Return the map control object
  842. * @property getMap
  843. * @type {Object}
  844. *
  845. */
  846. getMap: function () {
  847. if (!map) {
  848. console.log("trying to get map before it is available!");
  849. }
  850. return map;
  851. },
  852.  
  853. /**
  854. * Returns a list of feature layers that are currently visible on the map.
  855. * @method getVisibleFeatureLayers
  856. * @return {Array} an array of {{#crossLink "Esri/layers/FeatureLayer"}}{{/crossLink}} objects
  857. *
  858. */
  859. getVisibleFeatureLayers: function () {
  860. // Return only the feature layers
  861. //TODO do we need to consider static layers here?
  862. return dojoArray.filter(map.getLayersVisibleAtScale(), function (layer) {
  863. return layer.type && (layer.type === "Feature Layer") && layer.visible;
  864. });
  865. },
  866.  
  867. getVisibleLayers: function () {
  868. return map.getLayersVisibleAtScale();
  869. },
  870.  
  871. getInvisibleLayers: function () {
  872. var visibleLayers,
  873. allLayers,
  874. invisibleLayers;
  875.  
  876. visibleLayers = this.getVisibleLayers();
  877. allLayers = map._layers;
  878. invisibleLayers = [];
  879.  
  880. UtilDict.forEachEntry(allLayers, function (key, value) {
  881. var index = UtilArray.indexOf(visibleLayers, function (vl) {
  882. return key === vl.id;
  883. });
  884.  
  885. if (index === -1) {
  886. invisibleLayers.push(value);
  887. }
  888. });
  889.  
  890. return invisibleLayers;
  891. },
  892.  
  893. /**
  894. * Returns the mapping of feature layer ids to assocciated bounding box layers.
  895. * @method getBoundingBoxMapping
  896. * @return {Object} A dictionary of String, {{#crossLink "Esri/layers/GraphicsLayer"}}{{/crossLink}} pairs.
  897. *
  898. */
  899. getBoundingBoxMapping: function () {
  900. return boundingBoxMapping;
  901. },
  902.  
  903. /**
  904. * Return the feature layer corresponding to the given id.
  905. *
  906. * @method getFeatureLayer
  907. * @private
  908. * @param {String} featureId the id of the feature layer
  909. * @return {Esri/layers/FeatureLayer} feature layer
  910. */
  911. getFeatureLayer: function (featureId) {
  912. return map.getLayer(featureId);
  913. },
  914.  
  915. /**
  916. * Apply extent defaulting prior to URL overrides.
  917. *
  918. * @method applyExtentDefaulting
  919. * @private
  920. */
  921. applyExtentDefaulting: function () {
  922. //if full extent is missing, set to default extent.
  923. if (!(RAMP.config.extents.fullExtent)) {
  924. //need to deserialize/reserialize to avoid pointing to actual defaultExtent, which may be changed later by the Bookmark Link module
  925. RAMP.config.extents.fullExtent = JSON.parse(JSON.stringify(RAMP.config.extents.defaultExtent));
  926. }
  927.  
  928. //if maximum extent is missing, set to full extent.
  929. if (!(RAMP.config.extents.maximumExtent)) {
  930. RAMP.config.extents.maximumExtent = JSON.parse(JSON.stringify(RAMP.config.extents.fullExtent));
  931. }
  932. },
  933.  
  934. /**
  935. * initiate the projection of the config extents to basemap extents
  936. *
  937. * @method projectConfigExtents
  938. */
  939. projectConfigExtents: function () {
  940. //extract default basemap projection
  941. var mapSR = new SpatialReference(RAMP.config.basemaps[RAMP.config.initialBasemapIndex].spatialReference);
  942.  
  943. //project the default extent. when finished, process full extent
  944. projectExtent(RAMP.config.extents.defaultExtent, mapSR, projectFullExtent);
  945. },
  946.  
  947. /**
  948. * Given an ESRI Extent Object, returns a new ESRI Extent Object that
  949. * contains the extent adjusted according to this map's maximum extent
  950. *
  951. * NOTE: this method is currently unused!
  952. *
  953. * @param {esri/geometry/Extent} e the extent Object
  954. * @param {esri/geometry/Extent} maxExtent the maximum extent
  955. * @return {esri/geometry/Extent} An adjusted extent, if the target extent is outside the boundary
  956. * @method checkBoundary
  957. */
  958. checkBoundary: function (e, maxExtent) {
  959. var extent = e,
  960. width = extent.width(),
  961. height = extent.height(),
  962. centerX = extent.centerX(),
  963. centerY = extent.centerY(),
  964. flag, adjustedEx;
  965.  
  966. adjustedEx = extent.clone();
  967.  
  968. var maxHeight = maxExtent.height();
  969. if (height > maxHeight) {
  970. height = maxHeight;
  971. }
  972.  
  973. if (centerY > maxExtent.ymax) {
  974. adjustedEx.ymax = maxExtent.ymax;
  975. adjustedEx.ymin = maxExtent.ymax - height;
  976. flag = true;
  977. //} else if (extent.ymin < maxExtent.ymin) {
  978. } else if (centerY < maxExtent.ymin) {
  979. adjustedEx.ymin = maxExtent.ymin;
  980. adjustedEx.ymax = maxExtent.ymin + height;
  981. flag = true;
  982. }
  983.  
  984. var maxWidth = maxExtent.width();
  985. if (width > maxWidth) {
  986. width = maxWidth;
  987. }
  988.  
  989. if (centerX > maxExtent.xmax) {
  990. adjustedEx.xmax = maxExtent.xmax;
  991. adjustedEx.xmin = maxExtent.xmax - width;
  992. flag = true;
  993. } else if (centerX < maxExtent.xmin) {
  994. adjustedEx.xmin = maxExtent.xmin;
  995. adjustedEx.xmax = maxExtent.xmin + width;
  996. flag = true;
  997. }
  998.  
  999. if (flag) {
  1000. return adjustedEx;
  1001. }
  1002. },
  1003.  
  1004. /**
  1005. * Create a new FeatureLayer object based on the config input
  1006. *
  1007. * @method makeFeatureLayer
  1008. * @param {Object} layerConfig config object for the layer to create
  1009. * @param {Boolean} userLayer optional specifies if layer was added by a user
  1010. * @return {Esri/layers/FeatureLayer} feature layer object (unloaded)
  1011. */
  1012. makeFeatureLayer: function (layerConfig, userLayer) {
  1013. // TODO: source of possible errors; add error handling
  1014. var fl = new FeatureLayer(layerConfig.url, {
  1015. id: layerConfig.id,
  1016. mode: FeatureLayer.MODE_SNAPSHOT,
  1017. outFields: [layerConfig.layerAttributes],
  1018. visible: layerConfig.settings.visible,
  1019. opacity: resolveLayerOpacity(layerConfig.settings.opacity),
  1020. maxAllowableOffset: layerConfig.maxAllowableOffset
  1021. });
  1022.  
  1023. prepLayer(fl, layerConfig, userLayer);
  1024.  
  1025. fl.ramp.type = GlobalStorage.layerType.feature;
  1026.  
  1027. return fl;
  1028. },
  1029.  
  1030. /**
  1031. * Return the feature layer corresponding to the given url.
  1032. *
  1033. * @method makeWmsLayer
  1034. * @param {Object} layerConfig config object for the layer to create
  1035. * @param {Boolean} userLayer optional specifies if layer was added by a user
  1036. * @return {Esri/layers/WMSLayer} WMS layer
  1037. */
  1038.  
  1039. makeWmsLayer: function (layerConfig, userLayer) {
  1040. var wmsl = new WMSLayer(layerConfig.url, {
  1041. id: layerConfig.id,
  1042. format: layerConfig.format,
  1043. opacity: resolveLayerOpacity(layerConfig.settings.opacity),
  1044. visibleLayers: [layerConfig.layerName]
  1045. //resourceInfo: {
  1046. // extent: new EsriExtent(layer.extent),
  1047. // layerInfos: [new WMSLayerInfo({name:layer.layerName,title:layer.displayName})]
  1048. //}
  1049. });
  1050.  
  1051. prepLayer(wmsl, layerConfig, userLayer);
  1052.  
  1053. wmsl.ramp.type = GlobalStorage.layerType.wms;
  1054.  
  1055. wmsl.setVisibility(layerConfig.settings.visible);
  1056.  
  1057. return wmsl;
  1058. },
  1059.  
  1060. /**
  1061. * Return the static layer corresponding to the given url.
  1062. *
  1063. * @method makeStaticLayer
  1064. * @private
  1065. * @param {Object} layerConfig config object for the layer to create
  1066. * @param {Boolean} userLayer optional specifies if layer was added by a user
  1067. * @return {Object} layer object of the appropriate type
  1068. */
  1069.  
  1070. makeStaticLayer: function (layerConfig, userLayer) {
  1071. var tempLayer,
  1072. layerType = layerConfig.layerType || "feature";
  1073. //determine layer type and process
  1074. switch (layerType) {
  1075. case "feature":
  1076. tempLayer = new FeatureLayer(layerConfig.url, {
  1077. opacity: resolveLayerOpacity(layerConfig.settings.opacity),
  1078. mode: FeatureLayer.MODE_SNAPSHOT,
  1079. visible: layerConfig.settings.visible,
  1080. id: layerConfig.id,
  1081. maxAllowableOffset: layerConfig.maxAllowableOffset
  1082. });
  1083.  
  1084. prepLayer(tempLayer, layerConfig, userLayer);
  1085.  
  1086. tempLayer.ramp.type = GlobalStorage.layerType.Static;
  1087.  
  1088. break;
  1089.  
  1090. //We are currently not supporting other static layer types at the moment.
  1091. //Future versions should re-implement these cases
  1092. /*
  1093. case "tile":
  1094. tempLayer = new ArcGISTiledMapServiceLayer(staticLayer.url, {
  1095. opacity: resolveLayerOpacity(staticLayer.settings.opacity),
  1096. id: staticLayer.id
  1097. });
  1098. console.log("tile layer added. " + staticLayer.id);
  1099. break;
  1100.  
  1101. case "dynamic":
  1102. tempLayer = new ArcGISDynamicMapServiceLayer(staticLayer.url, {
  1103. opacity: resolveLayerOpacity(staticLayer.settings.opacity),
  1104. id: staticLayer.id
  1105. });
  1106. console.log("dynamic layer added. " + staticLayer.id);
  1107. break;
  1108. */
  1109.  
  1110. default:
  1111. console.log("unknown static layer type encountered: " + layerType);
  1112. break;
  1113. }
  1114. return tempLayer;
  1115. },
  1116.  
  1117. /**
  1118. * Adds custom ramp properties to layer. Adds handlers to loading events.
  1119. *
  1120. * @method enhanceLayer
  1121. * @param {Object} layer layer to be prepped
  1122. * @param {Object} config config object for the layer
  1123. * @param {Boolean} userLayer optional. indicates if layer was added by a user. default value is false
  1124. */
  1125. enhanceLayer: function (layer, config, userLayer) {
  1126. //call the private function
  1127. prepLayer(layer, config, userLayer);
  1128. },
  1129.  
  1130. /**
  1131. * Will project an extent to a desired spatial reference, using client side projection library.
  1132. * Avoids the need for Esri Geometry Service
  1133. *
  1134. * @method localProjectExtent
  1135. * @param {Esri/Extent} extent extent to be projected
  1136. * @param {Esri/SpatialReference} sr {{#crossLink "Esri/SpatialReference"}}{{/crossLink}} to project to
  1137. * @return {Esri/Extent} extent in the desired projection
  1138. */
  1139. localProjectExtent: localProjectExtent,
  1140.  
  1141. /*
  1142. * Initialize map control with configuration objects provided in the bootstrapper.js file.
  1143. *
  1144. * Initialize extent
  1145. * Generate and load initial base map
  1146. * Generate map layer objects
  1147. * Show scalebar
  1148. * Publish events to outside for other modules to use
  1149. * Subscribe events to update map control
  1150. *
  1151. * @method init
  1152. * @param {Object} mapDiv the HTML div that will store the map control
  1153. * @constructor
  1154. *
  1155. */
  1156. init: function () {
  1157. var that = this,
  1158.  
  1159. schemaBasemap = RAMP.config.basemaps[RAMP.config.initialBasemapIndex],
  1160.  
  1161. /**
  1162. * The URL of the first layer of the basemap that is on by default.
  1163. *
  1164. * @property url
  1165. * @private
  1166. * @type {String}
  1167. */
  1168. url = schemaBasemap.layers[0].url,
  1169.  
  1170. /**
  1171. * The basemap layer
  1172. *
  1173. * @property baseLayer
  1174. * @private
  1175. * @type {Esri/layers/ArcGISTiledMapServiceLayer}
  1176. */
  1177. baseLayer = new ArcGISTiledMapServiceLayer(url, {
  1178. id: "basemapLayer"
  1179. }),
  1180.  
  1181. loadListener = baseLayer.on('update-end', function () {
  1182. //only load things once, pls!
  1183. loadListener.remove();
  1184.  
  1185. //basemap has loaded. continue on with the map loading
  1186. topic.publish(EventManager.Map.INITIAL_BASEMAP_LOADED);
  1187. });
  1188.  
  1189. baseLayer.on('error', function (evt) {
  1190. //basemap has died. long live the basemap.
  1191. //TODO some proper error handling here. error page? message to user of catastrophic failure?
  1192. console.log('initial basemap failed to load: ' + evt.error.message);
  1193. window.location.href = RAMP.config.mapInitFailUrl;
  1194. });
  1195.  
  1196. /**
  1197. * The initial extent of the map
  1198. *
  1199. * @property InitExtent
  1200. * @private
  1201. * @type {esri/geometry/Extent}
  1202. */
  1203. initExtent = new EsriExtent(RAMP.config.extents.defaultExtent);
  1204.  
  1205. /**
  1206. * Used for full extent in nav widget
  1207. *
  1208. * @property fullExtent
  1209. * @private
  1210. * @type {esri/geometry/Extent}
  1211. */
  1212. fullExtent = new EsriExtent(RAMP.config.extents.fullExtent);
  1213.  
  1214. /**
  1215. * The maximum extent of the map
  1216. *
  1217. * @property maxExtent
  1218. * @private
  1219. * @type {esri/geometry/Extent}
  1220. */
  1221. maxExtent = new EsriExtent(RAMP.config.extents.maximumExtent);
  1222.  
  1223. //generate WMS layers array
  1224. wmsLayers = dojoArray.map(RAMP.config.layers.wms, function (layer) {
  1225. return that.makeWmsLayer(layer);
  1226. });
  1227.  
  1228. //generate feature layers array
  1229. featureLayers = dojoArray.map(RAMP.config.layers.feature, function (layerConfig) {
  1230. var fl;
  1231.  
  1232. if (layerConfig.isStatic) {
  1233. fl = that.makeStaticLayer(layerConfig);
  1234. } else {
  1235. fl = that.makeFeatureLayer(layerConfig);
  1236. }
  1237.  
  1238. return fl;
  1239. });
  1240.  
  1241. // determine the basmap wkid from config
  1242. var tileSchema = schemaBasemap.tileSchema;
  1243.  
  1244. // add custom level of details if lod exists in config.json
  1245. if (tileSchema) {
  1246. var levelOfDetails = UtilArray.find(RAMP.config.LODs, function (configLOD) {
  1247. return configLOD.tileSchema === tileSchema;
  1248. });
  1249.  
  1250. //the map!
  1251. map = new EsriMap(RAMP.config.divNames.map, {
  1252. extent: initExtent,
  1253. logo: false,
  1254. minZoom: RAMP.config.zoomLevels.min,
  1255. maxZoom: RAMP.config.zoomLevels.max,
  1256. slider: false,
  1257. lods: levelOfDetails.lod
  1258. });
  1259. } else {
  1260. //the map!
  1261. map = new EsriMap(RAMP.config.divNames.map, {
  1262. extent: initExtent,
  1263. logo: false,
  1264. minZoom: RAMP.config.zoomLevels.min,
  1265. maxZoom: RAMP.config.zoomLevels.max,
  1266. slider: false
  1267. });
  1268. }
  1269.  
  1270. RAMP.map = map;
  1271. MapClickHandler.init(map);
  1272.  
  1273. /* START - Add static layers */
  1274. //NOTE: this type of thing is not currenlty supported by the config schema. Need to revisit and determine if we want to keep this code or not.
  1275. // this only deals with static layers that are bound to a feature layer. stand alone static layers are handled like normal feature layers
  1276.  
  1277. //if this does get implemented, this code should be moved to the layerLoader.js onLayerLoaded function. After a feature layer successfully loads,
  1278. //we should then load any of it's static layers. Extra tricky because these static layers do not appear in the layer selector (their state is bound
  1279. //to the feature layer.
  1280. //may want to consider another layerType .BoundStatic
  1281.  
  1282. var staticLayers = [],
  1283. perLayerStaticMaps = [],
  1284. staticLayerMap = [];
  1285.  
  1286. dojoArray.forEach(RAMP.config.layers.feature, function (layer) {
  1287. perLayerStaticMaps = [];
  1288. dojoArray.forEach(layer.staticLayers, function (staticLayer, i) {
  1289. var tempLayer = that.makeStaticLayer(staticLayer);
  1290.  
  1291. staticLayers.push(tempLayer);
  1292. //creates an array of all static layers defined for the current, single feature layer
  1293. perLayerStaticMaps[i] = staticLayer.id;
  1294. });
  1295. //adds the static layer id array as a value to an array indexed by feature layer id
  1296. staticLayerMap[layer.id] = perLayerStaticMaps;
  1297. });
  1298.  
  1299. RAMP.staticLayerMap = staticLayerMap;
  1300.  
  1301. /* End - Add static layers */
  1302.  
  1303. //This was intended to be used to distinguish layers from each other when crawling; Looks like we are not using it. Commenting out for now. SZ
  1304. baseLayer.ramp = {
  1305. type: GlobalStorage.layerType.Basemap
  1306. };
  1307.  
  1308. //save layer objects to load after basemap.
  1309. //static layers is currently empty always
  1310. RAMP.startupLayers = wmsLayers.concat(staticLayers, featureLayers);
  1311.  
  1312. //add the basemap
  1313. map.addLayer(baseLayer);
  1314.  
  1315. /* Start - Show scalebar */
  1316. var scalebar = new EsriScalebar({
  1317. map: map,
  1318. attachTo: "bottom-left",
  1319. scalebarUnit: "metric"
  1320. });
  1321.  
  1322. scalebar.show();
  1323.  
  1324. /* End - Show scalebar */
  1325.  
  1326. _initRepublishers(map);
  1327. _initListeners(map);
  1328. _initEventHandlers(map);
  1329. }
  1330. };
  1331. });