Canvas.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  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/Renderer.js
  7. */
  8. /**
  9. * Class: OpenLayers.Renderer.Canvas
  10. * A renderer based on the 2D 'canvas' drawing element.
  11. *
  12. * Inherits:
  13. * - <OpenLayers.Renderer>
  14. */
  15. OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
  16. /**
  17. * APIProperty: hitDetection
  18. * {Boolean} Allow for hit detection of features. Default is true.
  19. */
  20. hitDetection: true,
  21. /**
  22. * Property: hitOverflow
  23. * {Number} The method for converting feature identifiers to color values
  24. * supports 16777215 sequential values. Two features cannot be
  25. * predictably detected if their identifiers differ by more than this
  26. * value. The hitOverflow allows for bigger numbers (but the
  27. * difference in values is still limited).
  28. */
  29. hitOverflow: 0,
  30. /**
  31. * Property: canvas
  32. * {Canvas} The canvas context object.
  33. */
  34. canvas: null,
  35. /**
  36. * Property: features
  37. * {Object} Internal object of feature/style pairs for use in redrawing the layer.
  38. */
  39. features: null,
  40. /**
  41. * Property: pendingRedraw
  42. * {Boolean} The renderer needs a redraw call to render features added while
  43. * the renderer was locked.
  44. */
  45. pendingRedraw: false,
  46. /**
  47. * Property: cachedSymbolBounds
  48. * {Object} Internal cache of calculated symbol extents.
  49. */
  50. cachedSymbolBounds: {},
  51. /**
  52. * Constructor: OpenLayers.Renderer.Canvas
  53. *
  54. * Parameters:
  55. * containerID - {<String>}
  56. * options - {Object} Optional properties to be set on the renderer.
  57. */
  58. initialize: function(containerID, options) {
  59. OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
  60. this.root = document.createElement("canvas");
  61. this.container.appendChild(this.root);
  62. this.canvas = this.root.getContext("2d");
  63. this.features = {};
  64. if (this.hitDetection) {
  65. this.hitCanvas = document.createElement("canvas");
  66. this.hitContext = this.hitCanvas.getContext("2d");
  67. }
  68. },
  69. /**
  70. * Method: setExtent
  71. * Set the visible part of the layer.
  72. *
  73. * Parameters:
  74. * extent - {<OpenLayers.Bounds>}
  75. * resolutionChanged - {Boolean}
  76. *
  77. * Returns:
  78. * {Boolean} true to notify the layer that the new extent does not exceed
  79. * the coordinate range, and the features will not need to be redrawn.
  80. * False otherwise.
  81. */
  82. setExtent: function() {
  83. OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
  84. // always redraw features
  85. return false;
  86. },
  87. /**
  88. * Method: eraseGeometry
  89. * Erase a geometry from the renderer. Because the Canvas renderer has
  90. * 'memory' of the features that it has drawn, we have to remove the
  91. * feature so it doesn't redraw.
  92. *
  93. * Parameters:
  94. * geometry - {<OpenLayers.Geometry>}
  95. * featureId - {String}
  96. */
  97. eraseGeometry: function(geometry, featureId) {
  98. this.eraseFeatures(this.features[featureId][0]);
  99. },
  100. /**
  101. * APIMethod: supported
  102. *
  103. * Returns:
  104. * {Boolean} Whether or not the browser supports the renderer class
  105. */
  106. supported: function() {
  107. return OpenLayers.CANVAS_SUPPORTED;
  108. },
  109. /**
  110. * Method: setSize
  111. * Sets the size of the drawing surface.
  112. *
  113. * Once the size is updated, redraw the canvas.
  114. *
  115. * Parameters:
  116. * size - {<OpenLayers.Size>}
  117. */
  118. setSize: function(size) {
  119. this.size = size.clone();
  120. var root = this.root;
  121. root.style.width = size.w + "px";
  122. root.style.height = size.h + "px";
  123. root.width = size.w;
  124. root.height = size.h;
  125. this.resolution = null;
  126. if (this.hitDetection) {
  127. var hitCanvas = this.hitCanvas;
  128. hitCanvas.style.width = size.w + "px";
  129. hitCanvas.style.height = size.h + "px";
  130. hitCanvas.width = size.w;
  131. hitCanvas.height = size.h;
  132. }
  133. },
  134. /**
  135. * Method: drawFeature
  136. * Draw the feature. Stores the feature in the features list,
  137. * then redraws the layer.
  138. *
  139. * Parameters:
  140. * feature - {<OpenLayers.Feature.Vector>}
  141. * style - {<Object>}
  142. *
  143. * Returns:
  144. * {Boolean} The feature has been drawn completely. If the feature has no
  145. * geometry, undefined will be returned. If the feature is not rendered
  146. * for other reasons, false will be returned.
  147. */
  148. drawFeature: function(feature, style) {
  149. var rendered;
  150. if (feature.geometry) {
  151. style = this.applyDefaultSymbolizer(style || feature.style);
  152. // don't render if display none or feature outside extent
  153. var bounds = feature.geometry.getBounds();
  154. var worldBounds;
  155. if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
  156. worldBounds = this.map.getMaxExtent();
  157. }
  158. var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
  159. rendered = (style.display !== "none") && !!bounds && intersects;
  160. if (rendered) {
  161. // keep track of what we have rendered for redraw
  162. this.features[feature.id] = [feature, style];
  163. }
  164. else {
  165. // remove from features tracked for redraw
  166. delete(this.features[feature.id]);
  167. }
  168. this.pendingRedraw = true;
  169. }
  170. if (this.pendingRedraw && !this.locked) {
  171. this.redraw();
  172. this.pendingRedraw = false;
  173. }
  174. return rendered;
  175. },
  176. /**
  177. * Method: drawGeometry
  178. * Used when looping (in redraw) over the features; draws
  179. * the canvas.
  180. *
  181. * Parameters:
  182. * geometry - {<OpenLayers.Geometry>}
  183. * style - {Object}
  184. */
  185. drawGeometry: function(geometry, style, featureId) {
  186. var className = geometry.CLASS_NAME;
  187. if ((className == "OpenLayers.Geometry.Collection") ||
  188. (className == "OpenLayers.Geometry.MultiPoint") ||
  189. (className == "OpenLayers.Geometry.MultiLineString") ||
  190. (className == "OpenLayers.Geometry.MultiPolygon")) {
  191. for (var i = 0; i < geometry.components.length; i++) {
  192. this.drawGeometry(geometry.components[i], style, featureId);
  193. }
  194. return;
  195. }
  196. switch (geometry.CLASS_NAME) {
  197. case "OpenLayers.Geometry.Point":
  198. this.drawPoint(geometry, style, featureId);
  199. break;
  200. case "OpenLayers.Geometry.LineString":
  201. this.drawLineString(geometry, style, featureId);
  202. break;
  203. case "OpenLayers.Geometry.LinearRing":
  204. this.drawLinearRing(geometry, style, featureId);
  205. break;
  206. case "OpenLayers.Geometry.Polygon":
  207. this.drawPolygon(geometry, style, featureId);
  208. break;
  209. default:
  210. break;
  211. }
  212. },
  213. /**
  214. * Method: drawExternalGraphic
  215. * Called to draw External graphics.
  216. *
  217. * Parameters:
  218. * geometry - {<OpenLayers.Geometry>}
  219. * style - {Object}
  220. * featureId - {String}
  221. */
  222. drawExternalGraphic: function(geometry, style, featureId) {
  223. var img = new Image();
  224. var title = style.title || style.graphicTitle;
  225. if (title) {
  226. img.title = title;
  227. }
  228. var width = style.graphicWidth || style.graphicHeight;
  229. var height = style.graphicHeight || style.graphicWidth;
  230. width = width ? width : style.pointRadius * 2;
  231. height = height ? height : style.pointRadius * 2;
  232. var xOffset = (style.graphicXOffset != undefined) ?
  233. style.graphicXOffset : -(0.5 * width);
  234. var yOffset = (style.graphicYOffset != undefined) ?
  235. style.graphicYOffset : -(0.5 * height);
  236. var opacity = style.graphicOpacity || style.fillOpacity;
  237. var onLoad = function() {
  238. if(!this.features[featureId]) {
  239. return;
  240. }
  241. var pt = this.getLocalXY(geometry);
  242. var p0 = pt[0];
  243. var p1 = pt[1];
  244. if(!isNaN(p0) && !isNaN(p1)) {
  245. var x = (p0 + xOffset) | 0;
  246. var y = (p1 + yOffset) | 0;
  247. var canvas = this.canvas;
  248. canvas.globalAlpha = opacity;
  249. var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
  250. (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
  251. /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
  252. // 320 is the screen width of the G1 phone, for
  253. // which drawImage works out of the box.
  254. 320 / window.screen.width : 1
  255. );
  256. canvas.drawImage(
  257. img, x*factor, y*factor, width*factor, height*factor
  258. );
  259. if (this.hitDetection) {
  260. this.setHitContextStyle("fill", featureId);
  261. this.hitContext.fillRect(x, y, width, height);
  262. }
  263. }
  264. };
  265. img.onload = OpenLayers.Function.bind(onLoad, this);
  266. img.src = style.externalGraphic;
  267. },
  268. /**
  269. * Method: drawNamedSymbol
  270. * Called to draw Well Known Graphic Symbol Name.
  271. * This method is only called by the renderer itself.
  272. *
  273. * Parameters:
  274. * geometry - {<OpenLayers.Geometry>}
  275. * style - {Object}
  276. * featureId - {String}
  277. */
  278. drawNamedSymbol: function(geometry, style, featureId) {
  279. var x, y, cx, cy, i, symbolBounds, scaling, angle;
  280. var unscaledStrokeWidth;
  281. var deg2rad = Math.PI / 180.0;
  282. var symbol = OpenLayers.Renderer.symbol[style.graphicName];
  283. if (!symbol) {
  284. throw new Error(style.graphicName + ' is not a valid symbol name');
  285. }
  286. if (!symbol.length || symbol.length < 2) return;
  287. var pt = this.getLocalXY(geometry);
  288. var p0 = pt[0];
  289. var p1 = pt[1];
  290. if (isNaN(p0) || isNaN(p1)) return;
  291. // Use rounded line caps
  292. this.canvas.lineCap = "round";
  293. this.canvas.lineJoin = "round";
  294. if (this.hitDetection) {
  295. this.hitContext.lineCap = "round";
  296. this.hitContext.lineJoin = "round";
  297. }
  298. // Scale and rotate symbols, using precalculated bounds whenever possible.
  299. if (style.graphicName in this.cachedSymbolBounds) {
  300. symbolBounds = this.cachedSymbolBounds[style.graphicName];
  301. } else {
  302. symbolBounds = new OpenLayers.Bounds();
  303. for(i = 0; i < symbol.length; i+=2) {
  304. symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
  305. }
  306. this.cachedSymbolBounds[style.graphicName] = symbolBounds;
  307. }
  308. // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
  309. // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
  310. this.canvas.save();
  311. if (this.hitDetection) { this.hitContext.save(); }
  312. // Step 3: place symbol at the desired location
  313. this.canvas.translate(p0,p1);
  314. if (this.hitDetection) { this.hitContext.translate(p0,p1); }
  315. // Step 2a. rotate the symbol if necessary
  316. angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
  317. if (!isNaN(angle)) {
  318. this.canvas.rotate(angle);
  319. if (this.hitDetection) { this.hitContext.rotate(angle); }
  320. }
  321. // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
  322. scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
  323. this.canvas.scale(scaling,scaling);
  324. if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
  325. // Step 1: center the symbol at the origin
  326. cx = symbolBounds.getCenterLonLat().lon;
  327. cy = symbolBounds.getCenterLonLat().lat;
  328. this.canvas.translate(-cx,-cy);
  329. if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }
  330. // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
  331. // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
  332. unscaledStrokeWidth = style.strokeWidth;
  333. style.strokeWidth = unscaledStrokeWidth / scaling;
  334. if (style.fill !== false) {
  335. this.setCanvasStyle("fill", style);
  336. this.canvas.beginPath();
  337. for (i=0; i<symbol.length; i=i+2) {
  338. x = symbol[i];
  339. y = symbol[i+1];
  340. if (i == 0) this.canvas.moveTo(x,y);
  341. this.canvas.lineTo(x,y);
  342. }
  343. this.canvas.closePath();
  344. this.canvas.fill();
  345. if (this.hitDetection) {
  346. this.setHitContextStyle("fill", featureId, style);
  347. this.hitContext.beginPath();
  348. for (i=0; i<symbol.length; i=i+2) {
  349. x = symbol[i];
  350. y = symbol[i+1];
  351. if (i == 0) this.canvas.moveTo(x,y);
  352. this.hitContext.lineTo(x,y);
  353. }
  354. this.hitContext.closePath();
  355. this.hitContext.fill();
  356. }
  357. }
  358. if (style.stroke !== false) {
  359. this.setCanvasStyle("stroke", style);
  360. this.canvas.beginPath();
  361. for (i=0; i<symbol.length; i=i+2) {
  362. x = symbol[i];
  363. y = symbol[i+1];
  364. if (i == 0) this.canvas.moveTo(x,y);
  365. this.canvas.lineTo(x,y);
  366. }
  367. this.canvas.closePath();
  368. this.canvas.stroke();
  369. if (this.hitDetection) {
  370. this.setHitContextStyle("stroke", featureId, style, scaling);
  371. this.hitContext.beginPath();
  372. for (i=0; i<symbol.length; i=i+2) {
  373. x = symbol[i];
  374. y = symbol[i+1];
  375. if (i == 0) this.hitContext.moveTo(x,y);
  376. this.hitContext.lineTo(x,y);
  377. }
  378. this.hitContext.closePath();
  379. this.hitContext.stroke();
  380. }
  381. }
  382. style.strokeWidth = unscaledStrokeWidth;
  383. this.canvas.restore();
  384. if (this.hitDetection) { this.hitContext.restore(); }
  385. this.setCanvasStyle("reset");
  386. },
  387. /**
  388. * Method: setCanvasStyle
  389. * Prepare the canvas for drawing by setting various global settings.
  390. *
  391. * Parameters:
  392. * type - {String} one of 'stroke', 'fill', or 'reset'
  393. * style - {Object} Symbolizer hash
  394. */
  395. setCanvasStyle: function(type, style) {
  396. if (type === "fill") {
  397. this.canvas.globalAlpha = style['fillOpacity'];
  398. this.canvas.fillStyle = style['fillColor'];
  399. } else if (type === "stroke") {
  400. this.canvas.globalAlpha = style['strokeOpacity'];
  401. this.canvas.strokeStyle = style['strokeColor'];
  402. this.canvas.lineWidth = style['strokeWidth'];
  403. } else {
  404. this.canvas.globalAlpha = 0;
  405. this.canvas.lineWidth = 1;
  406. }
  407. },
  408. /**
  409. * Method: featureIdToHex
  410. * Convert a feature ID string into an RGB hex string.
  411. *
  412. * Parameters:
  413. * featureId - {String} Feature id
  414. *
  415. * Returns:
  416. * {String} RGB hex string.
  417. */
  418. featureIdToHex: function(featureId) {
  419. var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
  420. if (id >= 16777216) {
  421. this.hitOverflow = id - 16777215;
  422. id = id % 16777216 + 1;
  423. }
  424. var hex = "000000" + id.toString(16);
  425. var len = hex.length;
  426. hex = "#" + hex.substring(len-6, len);
  427. return hex;
  428. },
  429. /**
  430. * Method: setHitContextStyle
  431. * Prepare the hit canvas for drawing by setting various global settings.
  432. *
  433. * Parameters:
  434. * type - {String} one of 'stroke', 'fill', or 'reset'
  435. * featureId - {String} The feature id.
  436. * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
  437. */
  438. setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
  439. var hex = this.featureIdToHex(featureId);
  440. if (type == "fill") {
  441. this.hitContext.globalAlpha = 1.0;
  442. this.hitContext.fillStyle = hex;
  443. } else if (type == "stroke") {
  444. this.hitContext.globalAlpha = 1.0;
  445. this.hitContext.strokeStyle = hex;
  446. // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol
  447. // on a transformed canvas, so the antialias width bump has to scale as well.
  448. if (typeof strokeScaling === "undefined") {
  449. this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
  450. } else {
  451. if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
  452. }
  453. } else {
  454. this.hitContext.globalAlpha = 0;
  455. this.hitContext.lineWidth = 1;
  456. }
  457. },
  458. /**
  459. * Method: drawPoint
  460. * This method is only called by the renderer itself.
  461. *
  462. * Parameters:
  463. * geometry - {<OpenLayers.Geometry>}
  464. * style - {Object}
  465. * featureId - {String}
  466. */
  467. drawPoint: function(geometry, style, featureId) {
  468. if(style.graphic !== false) {
  469. if(style.externalGraphic) {
  470. this.drawExternalGraphic(geometry, style, featureId);
  471. } else if (style.graphicName && (style.graphicName != "circle")) {
  472. this.drawNamedSymbol(geometry, style, featureId);
  473. } else {
  474. var pt = this.getLocalXY(geometry);
  475. var p0 = pt[0];
  476. var p1 = pt[1];
  477. if(!isNaN(p0) && !isNaN(p1)) {
  478. var twoPi = Math.PI*2;
  479. var radius = style.pointRadius;
  480. if(style.fill !== false) {
  481. this.setCanvasStyle("fill", style);
  482. this.canvas.beginPath();
  483. this.canvas.arc(p0, p1, radius, 0, twoPi, true);
  484. this.canvas.fill();
  485. if (this.hitDetection) {
  486. this.setHitContextStyle("fill", featureId, style);
  487. this.hitContext.beginPath();
  488. this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
  489. this.hitContext.fill();
  490. }
  491. }
  492. if(style.stroke !== false) {
  493. this.setCanvasStyle("stroke", style);
  494. this.canvas.beginPath();
  495. this.canvas.arc(p0, p1, radius, 0, twoPi, true);
  496. this.canvas.stroke();
  497. if (this.hitDetection) {
  498. this.setHitContextStyle("stroke", featureId, style);
  499. this.hitContext.beginPath();
  500. this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
  501. this.hitContext.stroke();
  502. }
  503. this.setCanvasStyle("reset");
  504. }
  505. }
  506. }
  507. }
  508. },
  509. /**
  510. * Method: drawLineString
  511. * This method is only called by the renderer itself.
  512. *
  513. * Parameters:
  514. * geometry - {<OpenLayers.Geometry>}
  515. * style - {Object}
  516. * featureId - {String}
  517. */
  518. drawLineString: function(geometry, style, featureId) {
  519. style = OpenLayers.Util.applyDefaults({fill: false}, style);
  520. this.drawLinearRing(geometry, style, featureId);
  521. },
  522. /**
  523. * Method: drawLinearRing
  524. * This method is only called by the renderer itself.
  525. *
  526. * Parameters:
  527. * geometry - {<OpenLayers.Geometry>}
  528. * style - {Object}
  529. * featureId - {String}
  530. */
  531. drawLinearRing: function(geometry, style, featureId) {
  532. if (style.fill !== false) {
  533. this.setCanvasStyle("fill", style);
  534. this.renderPath(this.canvas, geometry, style, featureId, "fill");
  535. if (this.hitDetection) {
  536. this.setHitContextStyle("fill", featureId, style);
  537. this.renderPath(this.hitContext, geometry, style, featureId, "fill");
  538. }
  539. }
  540. if (style.stroke !== false) {
  541. this.setCanvasStyle("stroke", style);
  542. this.renderPath(this.canvas, geometry, style, featureId, "stroke");
  543. if (this.hitDetection) {
  544. this.setHitContextStyle("stroke", featureId, style);
  545. this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
  546. }
  547. }
  548. this.setCanvasStyle("reset");
  549. },
  550. /**
  551. * Method: renderPath
  552. * Render a path with stroke and optional fill.
  553. */
  554. renderPath: function(context, geometry, style, featureId, type) {
  555. var components = geometry.components;
  556. var len = components.length;
  557. context.beginPath();
  558. var start = this.getLocalXY(components[0]);
  559. var x = start[0];
  560. var y = start[1];
  561. if (!isNaN(x) && !isNaN(y)) {
  562. context.moveTo(start[0], start[1]);
  563. for (var i=1; i<len; ++i) {
  564. var pt = this.getLocalXY(components[i]);
  565. context.lineTo(pt[0], pt[1]);
  566. }
  567. if (type === "fill") {
  568. context.fill();
  569. } else {
  570. context.stroke();
  571. }
  572. }
  573. },
  574. /**
  575. * Method: drawPolygon
  576. * This method is only called by the renderer itself.
  577. *
  578. * Parameters:
  579. * geometry - {<OpenLayers.Geometry>}
  580. * style - {Object}
  581. * featureId - {String}
  582. */
  583. drawPolygon: function(geometry, style, featureId) {
  584. var components = geometry.components;
  585. var len = components.length;
  586. this.drawLinearRing(components[0], style, featureId);
  587. // erase inner rings
  588. for (var i=1; i<len; ++i) {
  589. /**
  590. * Note that this is overly agressive. Here we punch holes through
  591. * all previously rendered features on the same canvas. A better
  592. * solution for polygons with interior rings would be to draw the
  593. * polygon on a sketch canvas first. We could erase all holes
  594. * there and then copy the drawing to the layer canvas.
  595. * TODO: http://trac.osgeo.org/openlayers/ticket/3130
  596. */
  597. this.canvas.globalCompositeOperation = "destination-out";
  598. if (this.hitDetection) {
  599. this.hitContext.globalCompositeOperation = "destination-out";
  600. }
  601. this.drawLinearRing(
  602. components[i],
  603. OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
  604. featureId
  605. );
  606. this.canvas.globalCompositeOperation = "source-over";
  607. if (this.hitDetection) {
  608. this.hitContext.globalCompositeOperation = "source-over";
  609. }
  610. this.drawLinearRing(
  611. components[i],
  612. OpenLayers.Util.applyDefaults({fill: false}, style),
  613. featureId
  614. );
  615. }
  616. },
  617. /**
  618. * Method: drawText
  619. * This method is only called by the renderer itself.
  620. *
  621. * Parameters:
  622. * location - {<OpenLayers.Point>}
  623. * style - {Object}
  624. */
  625. drawText: function(location, style) {
  626. var pt = this.getLocalXY(location);
  627. this.setCanvasStyle("reset");
  628. this.canvas.fillStyle = style.fontColor;
  629. this.canvas.globalAlpha = style.fontOpacity || 1.0;
  630. var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
  631. "normal", // "font-variant" not supported
  632. style.fontWeight ? style.fontWeight : "normal",
  633. style.fontSize ? style.fontSize : "1em",
  634. style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
  635. var labelRows = style.label.split('\n');
  636. var numRows = labelRows.length;
  637. if (this.canvas.fillText) {
  638. // HTML5
  639. this.canvas.font = fontStyle;
  640. this.canvas.textAlign =
  641. OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
  642. "center";
  643. this.canvas.textBaseline =
  644. OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
  645. "middle";
  646. var vfactor =
  647. OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
  648. if (vfactor == null) {
  649. vfactor = -.5;
  650. }
  651. var lineHeight =
  652. this.canvas.measureText('Mg').height ||
  653. this.canvas.measureText('xx').width;
  654. pt[1] += lineHeight*vfactor*(numRows-1);
  655. for (var i = 0; i < numRows; i++) {
  656. if (style.labelOutlineWidth) {
  657. this.canvas.save();
  658. this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
  659. this.canvas.strokeStyle = style.labelOutlineColor;
  660. this.canvas.lineWidth = style.labelOutlineWidth;
  661. this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
  662. this.canvas.restore();
  663. }
  664. this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
  665. }
  666. } else if (this.canvas.mozDrawText) {
  667. // Mozilla pre-Gecko1.9.1 (<FF3.1)
  668. this.canvas.mozTextStyle = fontStyle;
  669. // No built-in text alignment, so we measure and adjust the position
  670. var hfactor =
  671. OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
  672. if (hfactor == null) {
  673. hfactor = -.5;
  674. }
  675. var vfactor =
  676. OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
  677. if (vfactor == null) {
  678. vfactor = -.5;
  679. }
  680. var lineHeight = this.canvas.mozMeasureText('xx');
  681. pt[1] += lineHeight*(1 + (vfactor*numRows));
  682. for (var i = 0; i < numRows; i++) {
  683. var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
  684. var y = pt[1] + (i*lineHeight);
  685. this.canvas.translate(x, y);
  686. this.canvas.mozDrawText(labelRows[i]);
  687. this.canvas.translate(-x, -y);
  688. }
  689. }
  690. this.setCanvasStyle("reset");
  691. },
  692. /**
  693. * Method: getLocalXY
  694. * transform geographic xy into pixel xy
  695. *
  696. * Parameters:
  697. * point - {<OpenLayers.Geometry.Point>}
  698. */
  699. getLocalXY: function(point) {
  700. var resolution = this.getResolution();
  701. var extent = this.extent;
  702. var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
  703. var y = ((extent.top / resolution) - point.y / resolution);
  704. return [x, y];
  705. },
  706. /**
  707. * Method: clear
  708. * Clear all vectors from the renderer.
  709. */
  710. clear: function() {
  711. var height = this.root.height;
  712. var width = this.root.width;
  713. this.canvas.clearRect(0, 0, width, height);
  714. this.features = {};
  715. if (this.hitDetection) {
  716. this.hitContext.clearRect(0, 0, width, height);
  717. }
  718. },
  719. /**
  720. * Method: getFeatureIdFromEvent
  721. * Returns a feature id from an event on the renderer.
  722. *
  723. * Parameters:
  724. * evt - {<OpenLayers.Event>}
  725. *
  726. * Returns:
  727. * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
  728. * feature instead of a feature id to avoid an unnecessary lookup on the
  729. * layer.
  730. */
  731. getFeatureIdFromEvent: function(evt) {
  732. var featureId, feature;
  733. if (this.hitDetection && this.root.style.display !== "none") {
  734. // this dragging check should go in the feature handler
  735. if (!this.map.dragging) {
  736. var xy = evt.xy;
  737. var x = xy.x | 0;
  738. var y = xy.y | 0;
  739. var data = this.hitContext.getImageData(x, y, 1, 1).data;
  740. if (data[3] === 255) { // antialiased
  741. var id = data[2] + (256 * (data[1] + (256 * data[0])));
  742. if (id) {
  743. featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
  744. try {
  745. feature = this.features[featureId][0];
  746. } catch(err) {
  747. // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
  748. // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
  749. // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
  750. }
  751. }
  752. }
  753. }
  754. }
  755. return feature;
  756. },
  757. /**
  758. * Method: eraseFeatures
  759. * This is called by the layer to erase features; removes the feature from
  760. * the list, then redraws the layer.
  761. *
  762. * Parameters:
  763. * features - {Array(<OpenLayers.Feature.Vector>)}
  764. */
  765. eraseFeatures: function(features) {
  766. if(!(OpenLayers.Util.isArray(features))) {
  767. features = [features];
  768. }
  769. for(var i=0; i<features.length; ++i) {
  770. delete this.features[features[i].id];
  771. }
  772. this.redraw();
  773. },
  774. /**
  775. * Method: redraw
  776. * The real 'meat' of the function: any time things have changed,
  777. * redraw() can be called to loop over all the data and (you guessed
  778. * it) redraw it. Unlike Elements-based Renderers, we can't interact
  779. * with things once they're drawn, to remove them, for example, so
  780. * instead we have to just clear everything and draw from scratch.
  781. */
  782. redraw: function() {
  783. if (!this.locked) {
  784. var height = this.root.height;
  785. var width = this.root.width;
  786. this.canvas.clearRect(0, 0, width, height);
  787. if (this.hitDetection) {
  788. this.hitContext.clearRect(0, 0, width, height);
  789. }
  790. var labelMap = [];
  791. var feature, geometry, style;
  792. var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
  793. for (var id in this.features) {
  794. if (!this.features.hasOwnProperty(id)) { continue; }
  795. feature = this.features[id][0];
  796. geometry = feature.geometry;
  797. this.calculateFeatureDx(geometry.getBounds(), worldBounds);
  798. style = this.features[id][1];
  799. this.drawGeometry(geometry, style, feature.id);
  800. if(style.label) {
  801. labelMap.push([feature, style]);
  802. }
  803. }
  804. var item;
  805. for (var i=0, len=labelMap.length; i<len; ++i) {
  806. item = labelMap[i];
  807. this.drawText(item[0].geometry.getCentroid(), item[1]);
  808. }
  809. }
  810. },
  811. CLASS_NAME: "OpenLayers.Renderer.Canvas"
  812. });
  813. /**
  814. * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
  815. * {Object}
  816. */
  817. OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
  818. "l": "left",
  819. "r": "right",
  820. "t": "top",
  821. "b": "bottom"
  822. };
  823. /**
  824. * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
  825. * {Object}
  826. */
  827. OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
  828. "l": 0,
  829. "r": -1,
  830. "t": 0,
  831. "b": -1
  832. };
  833. /**
  834. * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
  835. * {Number} Scale factor to apply to the canvas drawImage arguments. This
  836. * is always 1 except for Android 2.1 devices, to work around
  837. * http://code.google.com/p/android/issues/detail?id=5141.
  838. */
  839. OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;