Measure.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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/Control.js
  7. * @requires OpenLayers/Feature/Vector.js
  8. */
  9. /**
  10. * Class: OpenLayers.Control.Measure
  11. * Allows for drawing of features for measurements.
  12. *
  13. * Inherits from:
  14. * - <OpenLayers.Control>
  15. */
  16. OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, {
  17. /**
  18. * Constant: EVENT_TYPES
  19. * {Array(String)} Supported application event types. Register a listener
  20. * for a particular event with the following syntax:
  21. * (code)
  22. * control.events.register(type, obj, listener);
  23. * (end)
  24. *
  25. * Listeners will be called with a reference to an event object. The
  26. * properties of this event depends on exactly what happened.
  27. *
  28. * Supported control event types (in addition to those from <OpenLayers.Control>):
  29. * measure - Triggered when a measurement sketch is complete. Listeners
  30. * will receive an event with measure, units, order, and geometry
  31. * properties.
  32. * measurepartial - Triggered when a new point is added to the
  33. * measurement sketch or if the <immediate> property is true and the
  34. * measurement sketch is modified. Listeners receive an event with measure,
  35. * units, order, and geometry.
  36. */
  37. EVENT_TYPES: ['measure', 'measurepartial'],
  38. /**
  39. * APIProperty: handlerOptions
  40. * {Object} Used to set non-default properties on the control's handler
  41. */
  42. handlerOptions: null,
  43. /**
  44. * Property: callbacks
  45. * {Object} The functions that are sent to the handler for callback
  46. */
  47. callbacks: null,
  48. /**
  49. * Property: displaySystem
  50. * {String} Display system for output measurements. Supported values
  51. * are 'english', 'metric', and 'geographic'. Default is 'metric'.
  52. */
  53. displaySystem: 'metric',
  54. /**
  55. * Property: geodesic
  56. * {Boolean} Calculate geodesic metrics instead of planar metrics. This
  57. * requires that geometries can be transformed into Geographic/WGS84
  58. * (if that is not already the map projection). Default is false.
  59. */
  60. geodesic: false,
  61. /**
  62. * Property: displaySystemUnits
  63. * {Object} Units for various measurement systems. Values are arrays
  64. * of unit abbreviations (from OpenLayers.INCHES_PER_UNIT) in decreasing
  65. * order of length.
  66. */
  67. displaySystemUnits: {
  68. geographic: ['dd'],
  69. english: ['mi', 'ft', 'in'],
  70. metric: ['km', 'm']
  71. },
  72. /**
  73. * Property: delay
  74. * {Number} Number of milliseconds between clicks before the event is
  75. * considered a double-click. The "measurepartial" event will not
  76. * be triggered if the sketch is completed within this time. This
  77. * is required for IE where creating a browser reflow (if a listener
  78. * is modifying the DOM by displaying the measurement values) messes
  79. * with the dblclick listener in the sketch handler.
  80. */
  81. partialDelay: 300,
  82. /**
  83. * Property: delayedTrigger
  84. * {Number} Timeout id of trigger for measurepartial.
  85. */
  86. delayedTrigger: null,
  87. /**
  88. * APIProperty: persist
  89. * {Boolean} Keep the temporary measurement sketch drawn after the
  90. * measurement is complete. The geometry will persist until a new
  91. * measurement is started, the control is deactivated, or <cancel> is
  92. * called.
  93. */
  94. persist: false,
  95. /**
  96. * APIProperty: immediate
  97. * {Boolean} Activates the immediate measurement so that the "measurepartial"
  98. * event is also fired once the measurement sketch is modified.
  99. * Default is false.
  100. */
  101. immediate : false,
  102. /**
  103. * Constructor: OpenLayers.Control.Measure
  104. *
  105. * Parameters:
  106. * handler - {<OpenLayers.Handler>}
  107. * options - {Object}
  108. */
  109. initialize: function(handler, options) {
  110. // concatenate events specific to measure with those from the base
  111. this.EVENT_TYPES =
  112. OpenLayers.Control.Measure.prototype.EVENT_TYPES.concat(
  113. OpenLayers.Control.prototype.EVENT_TYPES
  114. );
  115. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  116. var callbacks = {done: this.measureComplete,
  117. point: this.measurePartial};
  118. if (this.immediate){
  119. callbacks.modify = this.measureImmediate;
  120. }
  121. this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
  122. // let the handler options override, so old code that passes 'persist'
  123. // directly to the handler does not need an update
  124. this.handlerOptions = OpenLayers.Util.extend(
  125. {persist: this.persist}, this.handlerOptions
  126. );
  127. this.handler = new handler(this, this.callbacks, this.handlerOptions);
  128. },
  129. /**
  130. * APIMethod: deactivate
  131. */
  132. deactivate: function() {
  133. this.cancelDelay();
  134. return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
  135. },
  136. /**
  137. * APIMethod: cancel
  138. * Stop the control from measuring. If <persist> is true, the temporary
  139. * sketch will be erased.
  140. */
  141. cancel: function() {
  142. this.cancelDelay();
  143. this.handler.cancel();
  144. },
  145. /**
  146. * APIMethod: setImmediate
  147. * Sets the <immediate> property. Changes the activity of immediate
  148. * measurement.
  149. */
  150. setImmediate: function(immediate) {
  151. this.immediate = immediate;
  152. if (this.immediate){
  153. this.callbacks.modify = this.measureImmediate;
  154. } else {
  155. delete this.callbacks.modify;
  156. }
  157. },
  158. /**
  159. * Method: updateHandler
  160. *
  161. * Parameters:
  162. * handler - {Function} One of the sketch handler constructors.
  163. * options - {Object} Options for the handler.
  164. */
  165. updateHandler: function(handler, options) {
  166. var active = this.active;
  167. if(active) {
  168. this.deactivate();
  169. }
  170. this.handler = new handler(this, this.callbacks, options);
  171. if(active) {
  172. this.activate();
  173. }
  174. },
  175. /**
  176. * Method: measureComplete
  177. * Called when the measurement sketch is done.
  178. *
  179. * Parameters:
  180. * geometry - {<OpenLayers.Geometry>}
  181. */
  182. measureComplete: function(geometry) {
  183. this.cancelDelay();
  184. this.measure(geometry, "measure");
  185. },
  186. /**
  187. * Method: measurePartial
  188. * Called each time a new point is added to the measurement sketch.
  189. *
  190. * Parameters:
  191. * point - {<OpenLayers.Geometry.Point>} The last point added.
  192. * geometry - {<OpenLayers.Geometry>} The sketch geometry.
  193. */
  194. measurePartial: function(point, geometry) {
  195. this.cancelDelay();
  196. geometry = geometry.clone();
  197. // when we're wating for a dblclick, we have to trigger measurepartial
  198. // after some delay to deal with reflow issues in IE
  199. if (this.handler.freehandMode(this.handler.evt)) {
  200. // no dblclick in freehand mode
  201. this.measure(geometry, "measurepartial");
  202. } else {
  203. this.delayedTrigger = window.setTimeout(
  204. OpenLayers.Function.bind(function() {
  205. this.delayedTrigger = null;
  206. this.measure(geometry, "measurepartial");
  207. }, this),
  208. this.partialDelay
  209. );
  210. }
  211. },
  212. /**
  213. * Method: measureImmediate
  214. * Called each time the measurement sketch is modified.
  215. *
  216. * Parameters: point - {<OpenLayers.Geometry.Point>} The point at the
  217. * mouseposition. feature - {<OpenLayers.Feature.Vector>} The sketch feature.
  218. */
  219. measureImmediate : function(point, feature, drawing) {
  220. if (drawing && this.delayedTrigger === null &&
  221. !this.handler.freehandMode(this.handler.evt)) {
  222. this.measure(feature.geometry, "measurepartial");
  223. }
  224. },
  225. /**
  226. * Method: cancelDelay
  227. * Cancels the delay measurement that measurePartial began.
  228. */
  229. cancelDelay: function() {
  230. if (this.delayedTrigger !== null) {
  231. window.clearTimeout(this.delayedTrigger);
  232. this.delayedTrigger = null;
  233. }
  234. },
  235. /**
  236. * Method: measure
  237. *
  238. * Parameters:
  239. * geometry - {<OpenLayers.Geometry>}
  240. * eventType - {String}
  241. */
  242. measure: function(geometry, eventType) {
  243. var stat, order;
  244. if(geometry.CLASS_NAME.indexOf('LineString') > -1) {
  245. stat = this.getBestLength(geometry);
  246. order = 1;
  247. } else {
  248. stat = this.getBestArea(geometry);
  249. order = 2;
  250. }
  251. this.events.triggerEvent(eventType, {
  252. measure: stat[0],
  253. units: stat[1],
  254. order: order,
  255. geometry: geometry
  256. });
  257. },
  258. /**
  259. * Method: getBestArea
  260. * Based on the <displaySystem> returns the area of a geometry.
  261. *
  262. * Parameters:
  263. * geometry - {<OpenLayers.Geometry>}
  264. *
  265. * Returns:
  266. * {Array([Float, String])} Returns a two item array containing the
  267. * area and the units abbreviation.
  268. */
  269. getBestArea: function(geometry) {
  270. var units = this.displaySystemUnits[this.displaySystem];
  271. var unit, area;
  272. for(var i=0, len=units.length; i<len; ++i) {
  273. unit = units[i];
  274. area = this.getArea(geometry, unit);
  275. if(area > 1) {
  276. break;
  277. }
  278. }
  279. return [area, unit];
  280. },
  281. /**
  282. * Method: getArea
  283. *
  284. * Parameters:
  285. * geometry - {<OpenLayers.Geometry>}
  286. * units - {String} Unit abbreviation
  287. *
  288. * Returns:
  289. * {Float} The geometry area in the given units.
  290. */
  291. getArea: function(geometry, units) {
  292. var area, geomUnits;
  293. if(this.geodesic) {
  294. area = geometry.getGeodesicArea(this.map.getProjectionObject());
  295. geomUnits = "m";
  296. } else {
  297. area = geometry.getArea();
  298. geomUnits = this.map.getUnits();
  299. }
  300. var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
  301. if(inPerDisplayUnit) {
  302. var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
  303. area *= Math.pow((inPerMapUnit / inPerDisplayUnit), 2);
  304. }
  305. return area;
  306. },
  307. /**
  308. * Method: getBestLength
  309. * Based on the <displaySystem> returns the length of a geometry.
  310. *
  311. * Parameters:
  312. * geometry - {<OpenLayers.Geometry>}
  313. *
  314. * Returns:
  315. * {Array([Float, String])} Returns a two item array containing the
  316. * length and the units abbreviation.
  317. */
  318. getBestLength: function(geometry) {
  319. var units = this.displaySystemUnits[this.displaySystem];
  320. var unit, length;
  321. for(var i=0, len=units.length; i<len; ++i) {
  322. unit = units[i];
  323. length = this.getLength(geometry, unit);
  324. if(length > 1) {
  325. break;
  326. }
  327. }
  328. return [length, unit];
  329. },
  330. /**
  331. * Method: getLength
  332. *
  333. * Parameters:
  334. * geometry - {<OpenLayers.Geometry>}
  335. * units - {String} Unit abbreviation
  336. *
  337. * Returns:
  338. * {Float} The geometry length in the given units.
  339. */
  340. getLength: function(geometry, units) {
  341. var length, geomUnits;
  342. if(this.geodesic) {
  343. length = geometry.getGeodesicLength(this.map.getProjectionObject());
  344. geomUnits = "m";
  345. } else {
  346. length = geometry.getLength();
  347. geomUnits = this.map.getUnits();
  348. }
  349. var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
  350. if(inPerDisplayUnit) {
  351. var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
  352. length *= (inPerMapUnit / inPerDisplayUnit);
  353. }
  354. return length;
  355. },
  356. CLASS_NAME: "OpenLayers.Control.Measure"
  357. });