Measure.js 12 KB

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