Layer.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342
  1. /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the Clear BSD license.
  3. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/BaseTypes/Class.js
  7. * @requires OpenLayers/Map.js
  8. * @requires OpenLayers/Projection.js
  9. */
  10. /**
  11. * Class: OpenLayers.Layer
  12. */
  13. OpenLayers.Layer = OpenLayers.Class({
  14. /**
  15. * APIProperty: id
  16. * {String}
  17. */
  18. id: null,
  19. /**
  20. * APIProperty: name
  21. * {String}
  22. */
  23. name: null,
  24. /**
  25. * APIProperty: div
  26. * {DOMElement}
  27. */
  28. div: null,
  29. /**
  30. * Property: opacity
  31. * {Float} The layer's opacity. Float number between 0.0 and 1.0.
  32. */
  33. opacity: null,
  34. /**
  35. * APIProperty: alwaysInRange
  36. * {Boolean} If a layer's display should not be scale-based, this should
  37. * be set to true. This will cause the layer, as an overlay, to always
  38. * be 'active', by always returning true from the calculateInRange()
  39. * function.
  40. *
  41. * If not explicitly specified for a layer, its value will be
  42. * determined on startup in initResolutions() based on whether or not
  43. * any scale-specific properties have been set as options on the
  44. * layer. If no scale-specific options have been set on the layer, we
  45. * assume that it should always be in range.
  46. *
  47. * See #987 for more info.
  48. */
  49. alwaysInRange: null,
  50. /**
  51. * Constant: EVENT_TYPES
  52. * {Array(String)} Supported application event types. Register a listener
  53. * for a particular event with the following syntax:
  54. * (code)
  55. * layer.events.register(type, obj, listener);
  56. * (end)
  57. *
  58. * Listeners will be called with a reference to an event object. The
  59. * properties of this event depends on exactly what happened.
  60. *
  61. * All event objects have at least the following properties:
  62. * object - {Object} A reference to layer.events.object.
  63. * element - {DOMElement} A reference to layer.events.element.
  64. *
  65. * Supported map event types:
  66. * loadstart - Triggered when layer loading starts.
  67. * loadend - Triggered when layer loading ends.
  68. * loadcancel - Triggered when layer loading is canceled.
  69. * visibilitychanged - Triggered when layer visibility is changed.
  70. * move - Triggered when layer moves (triggered with every mousemove
  71. * during a drag).
  72. * moveend - Triggered when layer is done moving, object passed as
  73. * argument has a zoomChanged boolean property which tells that the
  74. * zoom has changed.
  75. * added - Triggered after the layer is added to a map. Listeners will
  76. * receive an object with a *map* property referencing the map and a
  77. * *layer* property referencing the layer.
  78. * removed - Triggered after the layer is removed from the map. Listeners
  79. * will receive an object with a *map* property referencing the map and
  80. * a *layer* property referencing the layer.
  81. */
  82. EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged",
  83. "move", "moveend", "added", "removed"],
  84. /**
  85. * Constant: RESOLUTION_PROPERTIES
  86. * {Array} The properties that are used for calculating resolutions
  87. * information.
  88. */
  89. RESOLUTION_PROPERTIES: [
  90. 'scales', 'resolutions',
  91. 'maxScale', 'minScale',
  92. 'maxResolution', 'minResolution',
  93. 'numZoomLevels', 'maxZoomLevel'
  94. ],
  95. /**
  96. * APIProperty: events
  97. * {<OpenLayers.Events>}
  98. */
  99. events: null,
  100. /**
  101. * APIProperty: map
  102. * {<OpenLayers.Map>} This variable is set when the layer is added to
  103. * the map, via the accessor function setMap().
  104. */
  105. map: null,
  106. /**
  107. * APIProperty: isBaseLayer
  108. * {Boolean} Whether or not the layer is a base layer. This should be set
  109. * individually by all subclasses. Default is false
  110. */
  111. isBaseLayer: false,
  112. /**
  113. * Property: alpha
  114. * {Boolean} The layer's images have an alpha channel. Default is false.
  115. */
  116. alpha: false,
  117. /**
  118. * APIProperty: displayInLayerSwitcher
  119. * {Boolean} Display the layer's name in the layer switcher. Default is
  120. * true.
  121. */
  122. displayInLayerSwitcher: true,
  123. /**
  124. * APIProperty: visibility
  125. * {Boolean} The layer should be displayed in the map. Default is true.
  126. */
  127. visibility: true,
  128. /**
  129. * APIProperty: attribution
  130. * {String} Attribution string, displayed when an
  131. * <OpenLayers.Control.Attribution> has been added to the map.
  132. */
  133. attribution: null,
  134. /**
  135. * Property: inRange
  136. * {Boolean} The current map resolution is within the layer's min/max
  137. * range. This is set in <OpenLayers.Map.setCenter> whenever the zoom
  138. * changes.
  139. */
  140. inRange: false,
  141. /**
  142. * Propery: imageSize
  143. * {<OpenLayers.Size>} For layers with a gutter, the image is larger than
  144. * the tile by twice the gutter in each dimension.
  145. */
  146. imageSize: null,
  147. /**
  148. * Property: imageOffset
  149. * {<OpenLayers.Pixel>} For layers with a gutter, the image offset
  150. * represents displacement due to the gutter.
  151. */
  152. imageOffset: null,
  153. // OPTIONS
  154. /**
  155. * Property: options
  156. * {Object} An optional object whose properties will be set on the layer.
  157. * Any of the layer properties can be set as a property of the options
  158. * object and sent to the constructor when the layer is created.
  159. */
  160. options: null,
  161. /**
  162. * APIProperty: eventListeners
  163. * {Object} If set as an option at construction, the eventListeners
  164. * object will be registered with <OpenLayers.Events.on>. Object
  165. * structure must be a listeners object as shown in the example for
  166. * the events.on method.
  167. */
  168. eventListeners: null,
  169. /**
  170. * APIProperty: gutter
  171. * {Integer} Determines the width (in pixels) of the gutter around image
  172. * tiles to ignore. By setting this property to a non-zero value,
  173. * images will be requested that are wider and taller than the tile
  174. * size by a value of 2 x gutter. This allows artifacts of rendering
  175. * at tile edges to be ignored. Set a gutter value that is equal to
  176. * half the size of the widest symbol that needs to be displayed.
  177. * Defaults to zero. Non-tiled layers always have zero gutter.
  178. */
  179. gutter: 0,
  180. /**
  181. * APIProperty: projection
  182. * {<OpenLayers.Projection>} or {<String>} Set in the layer options to
  183. * override the default projection string this layer - also set maxExtent,
  184. * maxResolution, and units if appropriate. Can be either a string or
  185. * an <OpenLayers.Projection> object when created -- will be converted
  186. * to an object when setMap is called if a string is passed.
  187. */
  188. projection: null,
  189. /**
  190. * APIProperty: units
  191. * {String} The layer map units. Defaults to 'degrees'. Possible values
  192. * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
  193. */
  194. units: null,
  195. /**
  196. * APIProperty: scales
  197. * {Array} An array of map scales in descending order. The values in the
  198. * array correspond to the map scale denominator. Note that these
  199. * values only make sense if the display (monitor) resolution of the
  200. * client is correctly guessed by whomever is configuring the
  201. * application. In addition, the units property must also be set.
  202. * Use <resolutions> instead wherever possible.
  203. */
  204. scales: null,
  205. /**
  206. * APIProperty: resolutions
  207. * {Array} A list of map resolutions (map units per pixel) in descending
  208. * order. If this is not set in the layer constructor, it will be set
  209. * based on other resolution related properties (maxExtent,
  210. * maxResolution, maxScale, etc.).
  211. */
  212. resolutions: null,
  213. /**
  214. * APIProperty: maxExtent
  215. * {<OpenLayers.Bounds>} The center of these bounds will not stray outside
  216. * of the viewport extent during panning. In addition, if
  217. * <displayOutsideMaxExtent> is set to false, data will not be
  218. * requested that falls completely outside of these bounds.
  219. */
  220. maxExtent: null,
  221. /**
  222. * APIProperty: minExtent
  223. * {<OpenLayers.Bounds>}
  224. */
  225. minExtent: null,
  226. /**
  227. * APIProperty: maxResolution
  228. * {Float} Default max is 360 deg / 256 px, which corresponds to
  229. * zoom level 0 on gmaps. Specify a different value in the layer
  230. * options if you are not using a geographic projection and
  231. * displaying the whole world.
  232. */
  233. maxResolution: null,
  234. /**
  235. * APIProperty: minResolution
  236. * {Float}
  237. */
  238. minResolution: null,
  239. /**
  240. * APIProperty: numZoomLevels
  241. * {Integer}
  242. */
  243. numZoomLevels: null,
  244. /**
  245. * APIProperty: minScale
  246. * {Float}
  247. */
  248. minScale: null,
  249. /**
  250. * APIProperty: maxScale
  251. * {Float}
  252. */
  253. maxScale: null,
  254. /**
  255. * APIProperty: displayOutsideMaxExtent
  256. * {Boolean} Request map tiles that are completely outside of the max
  257. * extent for this layer. Defaults to false.
  258. */
  259. displayOutsideMaxExtent: false,
  260. /**
  261. * APIProperty: wrapDateLine
  262. * {Boolean} #487 for more info.
  263. */
  264. wrapDateLine: false,
  265. /**
  266. * APIProperty: transitionEffect
  267. * {String} The transition effect to use when the map is panned or
  268. * zoomed.
  269. *
  270. * There are currently two supported values:
  271. * - *null* No transition effect (the default).
  272. * - *resize* Existing tiles are resized on zoom to provide a visual
  273. * effect of the zoom having taken place immediately. As the
  274. * new tiles become available, they are drawn over top of the
  275. * resized tiles.
  276. */
  277. transitionEffect: null,
  278. /**
  279. * Property: SUPPORTED_TRANSITIONS
  280. * {Array} An immutable (that means don't change it!) list of supported
  281. * transitionEffect values.
  282. */
  283. SUPPORTED_TRANSITIONS: ['resize'],
  284. /**
  285. * Property: metadata
  286. * {Object} This object can be used to store additional information on a
  287. * layer object.
  288. */
  289. metadata: {},
  290. /**
  291. * Constructor: OpenLayers.Layer
  292. *
  293. * Parameters:
  294. * name - {String} The layer name
  295. * options - {Object} Hashtable of extra options to tag onto the layer
  296. */
  297. initialize: function(name, options) {
  298. this.addOptions(options);
  299. this.name = name;
  300. if (this.id == null) {
  301. this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
  302. this.div = OpenLayers.Util.createDiv(this.id);
  303. this.div.style.width = "100%";
  304. this.div.style.height = "100%";
  305. this.div.dir = "ltr";
  306. this.events = new OpenLayers.Events(this, this.div,
  307. this.EVENT_TYPES);
  308. if(this.eventListeners instanceof Object) {
  309. this.events.on(this.eventListeners);
  310. }
  311. }
  312. if (this.wrapDateLine) {
  313. this.displayOutsideMaxExtent = true;
  314. }
  315. },
  316. /**
  317. * Method: destroy
  318. * Destroy is a destructor: this is to alleviate cyclic references which
  319. * the Javascript garbage cleaner can not take care of on its own.
  320. *
  321. * Parameters:
  322. * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
  323. * been destroyed. Default is true.
  324. */
  325. destroy: function(setNewBaseLayer) {
  326. if (setNewBaseLayer == null) {
  327. setNewBaseLayer = true;
  328. }
  329. if (this.map != null) {
  330. this.map.removeLayer(this, setNewBaseLayer);
  331. }
  332. this.projection = null;
  333. this.map = null;
  334. this.name = null;
  335. this.div = null;
  336. this.options = null;
  337. if (this.events) {
  338. if(this.eventListeners) {
  339. this.events.un(this.eventListeners);
  340. }
  341. this.events.destroy();
  342. }
  343. this.eventListeners = null;
  344. this.events = null;
  345. },
  346. /**
  347. * Method: clone
  348. *
  349. * Parameters:
  350. * obj - {<OpenLayers.Layer>} The layer to be cloned
  351. *
  352. * Returns:
  353. * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
  354. */
  355. clone: function (obj) {
  356. if (obj == null) {
  357. obj = new OpenLayers.Layer(this.name, this.getOptions());
  358. }
  359. // catch any randomly tagged-on properties
  360. OpenLayers.Util.applyDefaults(obj, this);
  361. // a cloned layer should never have its map property set
  362. // because it has not been added to a map yet.
  363. obj.map = null;
  364. return obj;
  365. },
  366. /**
  367. * Method: getOptions
  368. * Extracts an object from the layer with the properties that were set as
  369. * options, but updates them with the values currently set on the
  370. * instance.
  371. *
  372. * Returns:
  373. * {Object} the <options> of the layer, representing the current state.
  374. */
  375. getOptions: function() {
  376. var options = {};
  377. for(var o in this.options) {
  378. options[o] = this[o];
  379. }
  380. return options;
  381. },
  382. /**
  383. * APIMethod: setName
  384. * Sets the new layer name for this layer. Can trigger a changelayer event
  385. * on the map.
  386. *
  387. * Parameters:
  388. * newName - {String} The new name.
  389. */
  390. setName: function(newName) {
  391. if (newName != this.name) {
  392. this.name = newName;
  393. if (this.map != null) {
  394. this.map.events.triggerEvent("changelayer", {
  395. layer: this,
  396. property: "name"
  397. });
  398. }
  399. }
  400. },
  401. /**
  402. * APIMethod: addOptions
  403. *
  404. * Parameters:
  405. * newOptions - {Object}
  406. * reinitialize - {Boolean} If set to true, and if resolution options of the
  407. * current baseLayer were changed, the map will be recentered to make
  408. * sure that it is displayed with a valid resolution, and a
  409. * changebaselayer event will be triggered.
  410. */
  411. addOptions: function (newOptions, reinitialize) {
  412. if (this.options == null) {
  413. this.options = {};
  414. }
  415. // update our copy for clone
  416. OpenLayers.Util.extend(this.options, newOptions);
  417. // add new options to this
  418. OpenLayers.Util.extend(this, newOptions);
  419. // make sure this.projection references a projection object
  420. if(typeof this.projection == "string") {
  421. this.projection = new OpenLayers.Projection(this.projection);
  422. }
  423. // get the units from the projection, if we have a projection
  424. // and it it has units
  425. if(this.projection && this.projection.getUnits()) {
  426. this.units = this.projection.getUnits();
  427. }
  428. // re-initialize resolutions if necessary, i.e. if any of the
  429. // properties of the "properties" array defined below is set
  430. // in the new options
  431. if(this.map) {
  432. // store current resolution so we can try to restore it later
  433. var resolution = this.map.getResolution();
  434. var properties = this.RESOLUTION_PROPERTIES.concat(
  435. ["projection", "units", "minExtent", "maxExtent"]
  436. );
  437. for(var o in newOptions) {
  438. if(newOptions.hasOwnProperty(o) &&
  439. OpenLayers.Util.indexOf(properties, o) >= 0) {
  440. this.initResolutions();
  441. if (reinitialize && this.map.baseLayer === this) {
  442. // update map position, and restore previous resolution
  443. this.map.setCenter(this.map.getCenter(),
  444. this.map.getZoomForResolution(resolution),
  445. false, true
  446. );
  447. // trigger a changebaselayer event to make sure that
  448. // all controls (especially
  449. // OpenLayers.Control.PanZoomBar) get notified of the
  450. // new options
  451. this.map.events.triggerEvent("changebaselayer", {
  452. layer: this
  453. });
  454. }
  455. break;
  456. }
  457. }
  458. }
  459. },
  460. /**
  461. * APIMethod: onMapResize
  462. * This function can be implemented by subclasses
  463. */
  464. onMapResize: function() {
  465. //this function can be implemented by subclasses
  466. },
  467. /**
  468. * APIMethod: redraw
  469. * Redraws the layer. Returns true if the layer was redrawn, false if not.
  470. *
  471. * Returns:
  472. * {Boolean} The layer was redrawn.
  473. */
  474. redraw: function() {
  475. var redrawn = false;
  476. if (this.map) {
  477. // min/max Range may have changed
  478. this.inRange = this.calculateInRange();
  479. // map's center might not yet be set
  480. var extent = this.getExtent();
  481. if (extent && this.inRange && this.visibility) {
  482. var zoomChanged = true;
  483. this.moveTo(extent, zoomChanged, false);
  484. this.events.triggerEvent("moveend",
  485. {"zoomChanged": zoomChanged});
  486. redrawn = true;
  487. }
  488. }
  489. return redrawn;
  490. },
  491. /**
  492. * Method: moveTo
  493. *
  494. * Parameters:
  495. * bounds - {<OpenLayers.Bounds>}
  496. * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
  497. * do some init work in that case.
  498. * dragging - {Boolean}
  499. */
  500. moveTo:function(bounds, zoomChanged, dragging) {
  501. var display = this.visibility;
  502. if (!this.isBaseLayer) {
  503. display = display && this.inRange;
  504. }
  505. this.display(display);
  506. },
  507. /**
  508. * Method: moveByPx
  509. * Move the layer based on pixel vector. To be implemented by subclasses.
  510. *
  511. * Parameters:
  512. * dx - {Number} The x coord of the displacement vector.
  513. * dy - {Number} The y coord of the displacement vector.
  514. */
  515. moveByPx: function(dx, dy) {
  516. },
  517. /**
  518. * Method: setMap
  519. * Set the map property for the layer. This is done through an accessor
  520. * so that subclasses can override this and take special action once
  521. * they have their map variable set.
  522. *
  523. * Here we take care to bring over any of the necessary default
  524. * properties from the map.
  525. *
  526. * Parameters:
  527. * map - {<OpenLayers.Map>}
  528. */
  529. setMap: function(map) {
  530. if (this.map == null) {
  531. this.map = map;
  532. // grab some essential layer data from the map if it hasn't already
  533. // been set
  534. this.maxExtent = this.maxExtent || this.map.maxExtent;
  535. this.minExtent = this.minExtent || this.map.minExtent;
  536. this.projection = this.projection || this.map.projection;
  537. if (typeof this.projection == "string") {
  538. this.projection = new OpenLayers.Projection(this.projection);
  539. }
  540. // Check the projection to see if we can get units -- if not, refer
  541. // to properties.
  542. this.units = this.projection.getUnits() ||
  543. this.units || this.map.units;
  544. this.initResolutions();
  545. if (!this.isBaseLayer) {
  546. this.inRange = this.calculateInRange();
  547. var show = ((this.visibility) && (this.inRange));
  548. this.div.style.display = show ? "" : "none";
  549. }
  550. // deal with gutters
  551. this.setTileSize();
  552. }
  553. },
  554. /**
  555. * Method: afterAdd
  556. * Called at the end of the map.addLayer sequence. At this point, the map
  557. * will have a base layer. To be overridden by subclasses.
  558. */
  559. afterAdd: function() {
  560. },
  561. /**
  562. * APIMethod: removeMap
  563. * Just as setMap() allows each layer the possibility to take a
  564. * personalized action on being added to the map, removeMap() allows
  565. * each layer to take a personalized action on being removed from it.
  566. * For now, this will be mostly unused, except for the EventPane layer,
  567. * which needs this hook so that it can remove the special invisible
  568. * pane.
  569. *
  570. * Parameters:
  571. * map - {<OpenLayers.Map>}
  572. */
  573. removeMap: function(map) {
  574. //to be overridden by subclasses
  575. },
  576. /**
  577. * APIMethod: getImageSize
  578. *
  579. * Parameters:
  580. * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
  581. * by subclasses that have to deal with different tile sizes at the
  582. * layer extent edges (e.g. Zoomify)
  583. *
  584. * Returns:
  585. * {<OpenLayers.Size>} The size that the image should be, taking into
  586. * account gutters.
  587. */
  588. getImageSize: function(bounds) {
  589. return (this.imageSize || this.tileSize);
  590. },
  591. /**
  592. * APIMethod: setTileSize
  593. * Set the tile size based on the map size. This also sets layer.imageSize
  594. * and layer.imageOffset for use by Tile.Image.
  595. *
  596. * Parameters:
  597. * size - {<OpenLayers.Size>}
  598. */
  599. setTileSize: function(size) {
  600. var tileSize = (size) ? size :
  601. ((this.tileSize) ? this.tileSize :
  602. this.map.getTileSize());
  603. this.tileSize = tileSize;
  604. if(this.gutter) {
  605. // layers with gutters need non-null tile sizes
  606. //if(tileSize == null) {
  607. // OpenLayers.console.error("Error in layer.setMap() for " +
  608. // this.name + ": layers with " +
  609. // "gutters need non-null tile sizes");
  610. //}
  611. this.imageOffset = new OpenLayers.Pixel(-this.gutter,
  612. -this.gutter);
  613. this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
  614. tileSize.h + (2*this.gutter));
  615. }
  616. },
  617. /**
  618. * APIMethod: getVisibility
  619. *
  620. * Returns:
  621. * {Boolean} The layer should be displayed (if in range).
  622. */
  623. getVisibility: function() {
  624. return this.visibility;
  625. },
  626. /**
  627. * APIMethod: setVisibility
  628. * Set the visibility flag for the layer and hide/show & redraw
  629. * accordingly. Fire event unless otherwise specified
  630. *
  631. * Note that visibility is no longer simply whether or not the layer's
  632. * style.display is set to "block". Now we store a 'visibility' state
  633. * property on the layer class, this allows us to remember whether or
  634. * not we *desire* for a layer to be visible. In the case where the
  635. * map's resolution is out of the layer's range, this desire may be
  636. * subverted.
  637. *
  638. * Parameters:
  639. * visibility - {Boolean} Whether or not to display the layer (if in range)
  640. */
  641. setVisibility: function(visibility) {
  642. if (visibility != this.visibility) {
  643. this.visibility = visibility;
  644. this.display(visibility);
  645. this.redraw();
  646. if (this.map != null) {
  647. this.map.events.triggerEvent("changelayer", {
  648. layer: this,
  649. property: "visibility"
  650. });
  651. }
  652. this.events.triggerEvent("visibilitychanged");
  653. }
  654. },
  655. /**
  656. * APIMethod: display
  657. * Hide or show the Layer. This is designed to be used internally, and
  658. * is not generally the way to enable or disable the layer. For that,
  659. * use the setVisibility function instead..
  660. *
  661. * Parameters:
  662. * display - {Boolean}
  663. */
  664. display: function(display) {
  665. if (display != (this.div.style.display != "none")) {
  666. this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
  667. }
  668. },
  669. /**
  670. * APIMethod: calculateInRange
  671. *
  672. * Returns:
  673. * {Boolean} The layer is displayable at the current map's current
  674. * resolution. Note that if 'alwaysInRange' is true for the layer,
  675. * this function will always return true.
  676. */
  677. calculateInRange: function() {
  678. var inRange = false;
  679. if (this.alwaysInRange) {
  680. inRange = true;
  681. } else {
  682. if (this.map) {
  683. var resolution = this.map.getResolution();
  684. inRange = ( (resolution >= this.minResolution) &&
  685. (resolution <= this.maxResolution) );
  686. }
  687. }
  688. return inRange;
  689. },
  690. /**
  691. * APIMethod: setIsBaseLayer
  692. *
  693. * Parameters:
  694. * isBaseLayer - {Boolean}
  695. */
  696. setIsBaseLayer: function(isBaseLayer) {
  697. if (isBaseLayer != this.isBaseLayer) {
  698. this.isBaseLayer = isBaseLayer;
  699. if (this.map != null) {
  700. this.map.events.triggerEvent("changebaselayer", {
  701. layer: this
  702. });
  703. }
  704. }
  705. },
  706. /********************************************************/
  707. /* */
  708. /* Baselayer Functions */
  709. /* */
  710. /********************************************************/
  711. /**
  712. * Method: initResolutions
  713. * This method's responsibility is to set up the 'resolutions' array
  714. * for the layer -- this array is what the layer will use to interface
  715. * between the zoom levels of the map and the resolution display
  716. * of the layer.
  717. *
  718. * The user has several options that determine how the array is set up.
  719. *
  720. * For a detailed explanation, see the following wiki from the
  721. * openlayers.org homepage:
  722. * http://trac.openlayers.org/wiki/SettingZoomLevels
  723. */
  724. initResolutions: function() {
  725. // ok we want resolutions, here's our strategy:
  726. //
  727. // 1. if resolutions are defined in the layer config, use them
  728. // 2. else, if scales are defined in the layer config then derive
  729. // resolutions from these scales
  730. // 3. else, attempt to calculate resolutions from maxResolution,
  731. // minResolution, numZoomLevels, maxZoomLevel set in the
  732. // layer config
  733. // 4. if we still don't have resolutions, and if resolutions
  734. // are defined in the same, use them
  735. // 5. else, if scales are defined in the map then derive
  736. // resolutions from these scales
  737. // 6. else, attempt to calculate resolutions from maxResolution,
  738. // minResolution, numZoomLevels, maxZoomLevel set in the
  739. // map
  740. // 7. hope for the best!
  741. var i, len, p;
  742. var props = {}, alwaysInRange = true;
  743. // get resolution data from layer config
  744. // (we also set alwaysInRange in the layer as appropriate)
  745. for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
  746. p = this.RESOLUTION_PROPERTIES[i];
  747. props[p] = this.options[p];
  748. if(alwaysInRange && this.options[p]) {
  749. alwaysInRange = false;
  750. }
  751. }
  752. if(this.alwaysInRange == null) {
  753. this.alwaysInRange = alwaysInRange;
  754. }
  755. // if we don't have resolutions then attempt to derive them from scales
  756. if(props.resolutions == null) {
  757. props.resolutions = this.resolutionsFromScales(props.scales);
  758. }
  759. // if we still don't have resolutions then attempt to calculate them
  760. if(props.resolutions == null) {
  761. props.resolutions = this.calculateResolutions(props);
  762. }
  763. // if we couldn't calculate resolutions then we look at we have
  764. // in the map
  765. if(props.resolutions == null) {
  766. for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
  767. p = this.RESOLUTION_PROPERTIES[i];
  768. props[p] = this.options[p] != null ?
  769. this.options[p] : this.map[p];
  770. }
  771. if(props.resolutions == null) {
  772. props.resolutions = this.resolutionsFromScales(props.scales);
  773. }
  774. if(props.resolutions == null) {
  775. props.resolutions = this.calculateResolutions(props);
  776. }
  777. }
  778. // ok, we new need to set properties in the instance
  779. // get maxResolution from the config if it's defined there
  780. var maxResolution;
  781. if(this.options.maxResolution &&
  782. this.options.maxResolution !== "auto") {
  783. maxResolution = this.options.maxResolution;
  784. }
  785. if(this.options.minScale) {
  786. maxResolution = OpenLayers.Util.getResolutionFromScale(
  787. this.options.minScale, this.units);
  788. }
  789. // get minResolution from the config if it's defined there
  790. var minResolution;
  791. if(this.options.minResolution &&
  792. this.options.minResolution !== "auto") {
  793. minResolution = this.options.minResolution;
  794. }
  795. if(this.options.maxScale) {
  796. minResolution = OpenLayers.Util.getResolutionFromScale(
  797. this.options.maxScale, this.units);
  798. }
  799. if(props.resolutions) {
  800. //sort resolutions array descendingly
  801. props.resolutions.sort(function(a, b) {
  802. return (b - a);
  803. });
  804. // if we still don't have a maxResolution get it from the
  805. // resolutions array
  806. if(!maxResolution) {
  807. maxResolution = props.resolutions[0];
  808. }
  809. // if we still don't have a minResolution get it from the
  810. // resolutions array
  811. if(!minResolution) {
  812. var lastIdx = props.resolutions.length - 1;
  813. minResolution = props.resolutions[lastIdx];
  814. }
  815. }
  816. this.resolutions = props.resolutions;
  817. if(this.resolutions) {
  818. len = this.resolutions.length;
  819. this.scales = new Array(len);
  820. for(i=0; i<len; i++) {
  821. this.scales[i] = OpenLayers.Util.getScaleFromResolution(
  822. this.resolutions[i], this.units);
  823. }
  824. this.numZoomLevels = len;
  825. }
  826. this.minResolution = minResolution;
  827. if(minResolution) {
  828. this.maxScale = OpenLayers.Util.getScaleFromResolution(
  829. minResolution, this.units);
  830. }
  831. this.maxResolution = maxResolution;
  832. if(maxResolution) {
  833. this.minScale = OpenLayers.Util.getScaleFromResolution(
  834. maxResolution, this.units);
  835. }
  836. },
  837. /**
  838. * Method: resolutionsFromScales
  839. * Derive resolutions from scales.
  840. *
  841. * Parameters:
  842. * scales - {Array(Number)} Scales
  843. *
  844. * Returns
  845. * {Array(Number)} Resolutions
  846. */
  847. resolutionsFromScales: function(scales) {
  848. if(scales == null) {
  849. return;
  850. }
  851. var resolutions, i, len;
  852. len = scales.length;
  853. resolutions = new Array(len);
  854. for(i=0; i<len; i++) {
  855. resolutions[i] = OpenLayers.Util.getResolutionFromScale(
  856. scales[i], this.units);
  857. }
  858. return resolutions;
  859. },
  860. /**
  861. * Method: calculateResolutions
  862. * Calculate resolutions based on the provided properties.
  863. *
  864. * Parameters:
  865. * props - {Object} Properties
  866. *
  867. * Return:
  868. * {Array({Number})} Array of resolutions.
  869. */
  870. calculateResolutions: function(props) {
  871. var viewSize, wRes, hRes;
  872. // determine maxResolution
  873. var maxResolution = props.maxResolution;
  874. if(props.minScale != null) {
  875. maxResolution =
  876. OpenLayers.Util.getResolutionFromScale(props.minScale,
  877. this.units);
  878. } else if(maxResolution == "auto" && this.maxExtent != null) {
  879. viewSize = this.map.getSize();
  880. wRes = this.maxExtent.getWidth() / viewSize.w;
  881. hRes = this.maxExtent.getHeight() / viewSize.h;
  882. maxResolution = Math.max(wRes, hRes);
  883. }
  884. // determine minResolution
  885. var minResolution = props.minResolution;
  886. if(props.maxScale != null) {
  887. minResolution =
  888. OpenLayers.Util.getResolutionFromScale(props.maxScale,
  889. this.units);
  890. } else if(props.minResolution == "auto" && this.minExtent != null) {
  891. viewSize = this.map.getSize();
  892. wRes = this.minExtent.getWidth() / viewSize.w;
  893. hRes = this.minExtent.getHeight()/ viewSize.h;
  894. minResolution = Math.max(wRes, hRes);
  895. }
  896. // determine numZoomLevels
  897. var maxZoomLevel = props.maxZoomLevel;
  898. var numZoomLevels = props.numZoomLevels;
  899. if(typeof minResolution === "number" &&
  900. typeof maxResolution === "number" && numZoomLevels === undefined) {
  901. var ratio = maxResolution / minResolution;
  902. numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
  903. } else if(numZoomLevels === undefined && maxZoomLevel != null) {
  904. numZoomLevels = maxZoomLevel + 1;
  905. }
  906. // are we able to calculate resolutions?
  907. if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
  908. (typeof maxResolution !== "number" &&
  909. typeof minResolution !== "number")) {
  910. return;
  911. }
  912. // now we have numZoomLevels and at least one of maxResolution
  913. // or minResolution, we can populate the resolutions array
  914. var resolutions = new Array(numZoomLevels);
  915. var base = 2;
  916. if(typeof minResolution == "number" &&
  917. typeof maxResolution == "number") {
  918. // if maxResolution and minResolution are set, we calculate
  919. // the base for exponential scaling that starts at
  920. // maxResolution and ends at minResolution in numZoomLevels
  921. // steps.
  922. base = Math.pow(
  923. (maxResolution / minResolution),
  924. (1 / (numZoomLevels - 1))
  925. );
  926. }
  927. var i;
  928. if(typeof maxResolution === "number") {
  929. for(i=0; i<numZoomLevels; i++) {
  930. resolutions[i] = maxResolution / Math.pow(base, i);
  931. }
  932. } else {
  933. for(i=0; i<numZoomLevels; i++) {
  934. resolutions[numZoomLevels - 1 - i] =
  935. minResolution * Math.pow(base, i);
  936. }
  937. }
  938. return resolutions;
  939. },
  940. /**
  941. * APIMethod: getResolution
  942. *
  943. * Returns:
  944. * {Float} The currently selected resolution of the map, taken from the
  945. * resolutions array, indexed by current zoom level.
  946. */
  947. getResolution: function() {
  948. var zoom = this.map.getZoom();
  949. return this.getResolutionForZoom(zoom);
  950. },
  951. /**
  952. * APIMethod: getExtent
  953. *
  954. * Returns:
  955. * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
  956. * bounds of the current viewPort.
  957. */
  958. getExtent: function() {
  959. // just use stock map calculateBounds function -- passing no arguments
  960. // means it will user map's current center & resolution
  961. //
  962. return this.map.calculateBounds();
  963. },
  964. /**
  965. * APIMethod: getZoomForExtent
  966. *
  967. * Parameters:
  968. * extent - {<OpenLayers.Bounds>}
  969. * closest - {Boolean} Find the zoom level that most closely fits the
  970. * specified bounds. Note that this may result in a zoom that does
  971. * not exactly contain the entire extent.
  972. * Default is false.
  973. *
  974. * Returns:
  975. * {Integer} The index of the zoomLevel (entry in the resolutions array)
  976. * for the passed-in extent. We do this by calculating the ideal
  977. * resolution for the given extent (based on the map size) and then
  978. * calling getZoomForResolution(), passing along the 'closest'
  979. * parameter.
  980. */
  981. getZoomForExtent: function(extent, closest) {
  982. var viewSize = this.map.getSize();
  983. var idealResolution = Math.max( extent.getWidth() / viewSize.w,
  984. extent.getHeight() / viewSize.h );
  985. return this.getZoomForResolution(idealResolution, closest);
  986. },
  987. /**
  988. * Method: getDataExtent
  989. * Calculates the max extent which includes all of the data for the layer.
  990. * This function is to be implemented by subclasses.
  991. *
  992. * Returns:
  993. * {<OpenLayers.Bounds>}
  994. */
  995. getDataExtent: function () {
  996. //to be implemented by subclasses
  997. },
  998. /**
  999. * APIMethod: getResolutionForZoom
  1000. *
  1001. * Parameter:
  1002. * zoom - {Float}
  1003. *
  1004. * Returns:
  1005. * {Float} A suitable resolution for the specified zoom.
  1006. */
  1007. getResolutionForZoom: function(zoom) {
  1008. zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
  1009. var resolution;
  1010. if(this.map.fractionalZoom) {
  1011. var low = Math.floor(zoom);
  1012. var high = Math.ceil(zoom);
  1013. resolution = this.resolutions[low] -
  1014. ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
  1015. } else {
  1016. resolution = this.resolutions[Math.round(zoom)];
  1017. }
  1018. return resolution;
  1019. },
  1020. /**
  1021. * APIMethod: getZoomForResolution
  1022. *
  1023. * Parameters:
  1024. * resolution - {Float}
  1025. * closest - {Boolean} Find the zoom level that corresponds to the absolute
  1026. * closest resolution, which may result in a zoom whose corresponding
  1027. * resolution is actually smaller than we would have desired (if this
  1028. * is being called from a getZoomForExtent() call, then this means that
  1029. * the returned zoom index might not actually contain the entire
  1030. * extent specified... but it'll be close).
  1031. * Default is false.
  1032. *
  1033. * Returns:
  1034. * {Integer} The index of the zoomLevel (entry in the resolutions array)
  1035. * that corresponds to the best fit resolution given the passed in
  1036. * value and the 'closest' specification.
  1037. */
  1038. getZoomForResolution: function(resolution, closest) {
  1039. var zoom, i, len;
  1040. if(this.map.fractionalZoom) {
  1041. var lowZoom = 0;
  1042. var highZoom = this.resolutions.length - 1;
  1043. var highRes = this.resolutions[lowZoom];
  1044. var lowRes = this.resolutions[highZoom];
  1045. var res;
  1046. for(i=0, len=this.resolutions.length; i<len; ++i) {
  1047. res = this.resolutions[i];
  1048. if(res >= resolution) {
  1049. highRes = res;
  1050. lowZoom = i;
  1051. }
  1052. if(res <= resolution) {
  1053. lowRes = res;
  1054. highZoom = i;
  1055. break;
  1056. }
  1057. }
  1058. var dRes = highRes - lowRes;
  1059. if(dRes > 0) {
  1060. zoom = lowZoom + ((highRes - resolution) / dRes);
  1061. } else {
  1062. zoom = lowZoom;
  1063. }
  1064. } else {
  1065. var diff;
  1066. var minDiff = Number.POSITIVE_INFINITY;
  1067. for(i=0, len=this.resolutions.length; i<len; i++) {
  1068. if (closest) {
  1069. diff = Math.abs(this.resolutions[i] - resolution);
  1070. if (diff > minDiff) {
  1071. break;
  1072. }
  1073. minDiff = diff;
  1074. } else {
  1075. if (this.resolutions[i] < resolution) {
  1076. break;
  1077. }
  1078. }
  1079. }
  1080. zoom = Math.max(0, i-1);
  1081. }
  1082. return zoom;
  1083. },
  1084. /**
  1085. * APIMethod: getLonLatFromViewPortPx
  1086. *
  1087. * Parameters:
  1088. * viewPortPx - {<OpenLayers.Pixel>}
  1089. *
  1090. * Returns:
  1091. * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
  1092. * view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
  1093. */
  1094. getLonLatFromViewPortPx: function (viewPortPx) {
  1095. var lonlat = null;
  1096. var map = this.map;
  1097. if (viewPortPx != null && map.minPx) {
  1098. var res = map.getResolution();
  1099. var maxExtent = map.getMaxExtent({restricted: true});
  1100. var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
  1101. var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
  1102. lonlat = new OpenLayers.LonLat(lon, lat);
  1103. if (this.wrapDateLine) {
  1104. lonlat = lonlat.wrapDateLine(this.maxExtent);
  1105. }
  1106. }
  1107. return lonlat;
  1108. },
  1109. /**
  1110. * APIMethod: getViewPortPxFromLonLat
  1111. * Returns a pixel location given a map location. This method will return
  1112. * fractional pixel values.
  1113. *
  1114. * Parameters:
  1115. * lonlat - {<OpenLayers.LonLat>}
  1116. *
  1117. * Returns:
  1118. * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
  1119. * <OpenLayers.LonLat>,translated into view port pixels.
  1120. */
  1121. getViewPortPxFromLonLat: function (lonlat) {
  1122. var px = null;
  1123. if (lonlat != null) {
  1124. var resolution = this.map.getResolution();
  1125. var extent = this.map.getExtent();
  1126. px = new OpenLayers.Pixel(
  1127. (1/resolution * (lonlat.lon - extent.left)),
  1128. (1/resolution * (extent.top - lonlat.lat))
  1129. );
  1130. }
  1131. return px;
  1132. },
  1133. /**
  1134. * APIMethod: setOpacity
  1135. * Sets the opacity for the entire layer (all images)
  1136. *
  1137. * Parameter:
  1138. * opacity - {Float}
  1139. */
  1140. setOpacity: function(opacity) {
  1141. if (opacity != this.opacity) {
  1142. this.opacity = opacity;
  1143. for(var i=0, len=this.div.childNodes.length; i<len; ++i) {
  1144. var element = this.div.childNodes[i].firstChild;
  1145. OpenLayers.Util.modifyDOMElement(element, null, null, null,
  1146. null, null, null, opacity);
  1147. }
  1148. if (this.map != null) {
  1149. this.map.events.triggerEvent("changelayer", {
  1150. layer: this,
  1151. property: "opacity"
  1152. });
  1153. }
  1154. }
  1155. },
  1156. /**
  1157. * Method: getZIndex
  1158. *
  1159. * Returns:
  1160. * {Integer} the z-index of this layer
  1161. */
  1162. getZIndex: function () {
  1163. return this.div.style.zIndex;
  1164. },
  1165. /**
  1166. * Method: setZIndex
  1167. *
  1168. * Parameters:
  1169. * zIndex - {Integer}
  1170. */
  1171. setZIndex: function (zIndex) {
  1172. this.div.style.zIndex = zIndex;
  1173. },
  1174. /**
  1175. * Method: adjustBounds
  1176. * This function will take a bounds, and if wrapDateLine option is set
  1177. * on the layer, it will return a bounds which is wrapped around the
  1178. * world. We do not wrap for bounds which *cross* the
  1179. * maxExtent.left/right, only bounds which are entirely to the left
  1180. * or entirely to the right.
  1181. *
  1182. * Parameters:
  1183. * bounds - {<OpenLayers.Bounds>}
  1184. */
  1185. adjustBounds: function (bounds) {
  1186. if (this.gutter) {
  1187. // Adjust the extent of a bounds in map units by the
  1188. // layer's gutter in pixels.
  1189. var mapGutter = this.gutter * this.map.getResolution();
  1190. bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
  1191. bounds.bottom - mapGutter,
  1192. bounds.right + mapGutter,
  1193. bounds.top + mapGutter);
  1194. }
  1195. if (this.wrapDateLine) {
  1196. // wrap around the date line, within the limits of rounding error
  1197. var wrappingOptions = {
  1198. 'rightTolerance':this.getResolution(),
  1199. 'leftTolerance':this.getResolution()
  1200. };
  1201. bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
  1202. }
  1203. return bounds;
  1204. },
  1205. CLASS_NAME: "OpenLayers.Layer"
  1206. });