VML.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  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/Elements.js
  7. */
  8. /**
  9. * Class: OpenLayers.Renderer.VML
  10. * Render vector features in browsers with VML capability. Construct a new
  11. * VML renderer with the <OpenLayers.Renderer.VML> constructor.
  12. *
  13. * Note that for all calculations in this class, we use (num | 0) to truncate a
  14. * float value to an integer. This is done because it seems that VML doesn't
  15. * support float values.
  16. *
  17. * Inherits from:
  18. * - <OpenLayers.Renderer.Elements>
  19. */
  20. OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
  21. /**
  22. * Property: xmlns
  23. * {String} XML Namespace URN
  24. */
  25. xmlns: "urn:schemas-microsoft-com:vml",
  26. /**
  27. * Property: symbolCache
  28. * {DOMElement} node holding symbols. This hash is keyed by symbol name,
  29. * and each value is a hash with a "path" and an "extent" property.
  30. */
  31. symbolCache: {},
  32. /**
  33. * Property: offset
  34. * {Object} Hash with "x" and "y" properties
  35. */
  36. offset: null,
  37. /**
  38. * Constructor: OpenLayers.Renderer.VML
  39. * Create a new VML renderer.
  40. *
  41. * Parameters:
  42. * containerID - {String} The id for the element that contains the renderer
  43. */
  44. initialize: function(containerID) {
  45. if (!this.supported()) {
  46. return;
  47. }
  48. if (!document.namespaces.olv) {
  49. document.namespaces.add("olv", this.xmlns);
  50. var style = document.createStyleSheet();
  51. var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox'];
  52. for (var i = 0, len = shapes.length; i < len; i++) {
  53. style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
  54. "position: absolute; display: inline-block;");
  55. }
  56. }
  57. OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
  58. arguments);
  59. },
  60. /**
  61. * APIMethod: supported
  62. * Determine whether a browser supports this renderer.
  63. *
  64. * Returns:
  65. * {Boolean} The browser supports the VML renderer
  66. */
  67. supported: function() {
  68. return !!(document.namespaces);
  69. },
  70. /**
  71. * Method: setExtent
  72. * Set the renderer's extent
  73. *
  74. * Parameters:
  75. * extent - {<OpenLayers.Bounds>}
  76. * resolutionChanged - {Boolean}
  77. *
  78. * Returns:
  79. * {Boolean} true to notify the layer that the new extent does not exceed
  80. * the coordinate range, and the features will not need to be redrawn.
  81. */
  82. setExtent: function(extent, resolutionChanged) {
  83. var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
  84. var resolution = this.getResolution();
  85. var left = (extent.left/resolution) | 0;
  86. var top = (extent.top/resolution - this.size.h) | 0;
  87. if (resolutionChanged || !this.offset) {
  88. this.offset = {x: left, y: top};
  89. left = 0;
  90. top = 0;
  91. } else {
  92. left = left - this.offset.x;
  93. top = top - this.offset.y;
  94. }
  95. var org = (left - this.xOffset) + " " + top;
  96. this.root.coordorigin = org;
  97. var roots = [this.root, this.vectorRoot, this.textRoot];
  98. var root;
  99. for(var i=0, len=roots.length; i<len; ++i) {
  100. root = roots[i];
  101. var size = this.size.w + " " + this.size.h;
  102. root.coordsize = size;
  103. }
  104. // flip the VML display Y axis upside down so it
  105. // matches the display Y axis of the map
  106. this.root.style.flip = "y";
  107. return coordSysUnchanged;
  108. },
  109. /**
  110. * Method: setSize
  111. * Set the size of the drawing surface
  112. *
  113. * Parameters:
  114. * size - {<OpenLayers.Size>} the size of the drawing surface
  115. */
  116. setSize: function(size) {
  117. OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
  118. // setting width and height on all roots to avoid flicker which we
  119. // would get with 100% width and height on child roots
  120. var roots = [
  121. this.rendererRoot,
  122. this.root,
  123. this.vectorRoot,
  124. this.textRoot
  125. ];
  126. var w = this.size.w + "px";
  127. var h = this.size.h + "px";
  128. var root;
  129. for(var i=0, len=roots.length; i<len; ++i) {
  130. root = roots[i];
  131. root.style.width = w;
  132. root.style.height = h;
  133. }
  134. },
  135. /**
  136. * Method: getNodeType
  137. * Get the node type for a geometry and style
  138. *
  139. * Parameters:
  140. * geometry - {<OpenLayers.Geometry>}
  141. * style - {Object}
  142. *
  143. * Returns:
  144. * {String} The corresponding node type for the specified geometry
  145. */
  146. getNodeType: function(geometry, style) {
  147. var nodeType = null;
  148. switch (geometry.CLASS_NAME) {
  149. case "OpenLayers.Geometry.Point":
  150. if (style.externalGraphic) {
  151. nodeType = "olv:rect";
  152. } else if (this.isComplexSymbol(style.graphicName)) {
  153. nodeType = "olv:shape";
  154. } else {
  155. nodeType = "olv:oval";
  156. }
  157. break;
  158. case "OpenLayers.Geometry.Rectangle":
  159. nodeType = "olv:rect";
  160. break;
  161. case "OpenLayers.Geometry.LineString":
  162. case "OpenLayers.Geometry.LinearRing":
  163. case "OpenLayers.Geometry.Polygon":
  164. case "OpenLayers.Geometry.Curve":
  165. nodeType = "olv:shape";
  166. break;
  167. default:
  168. break;
  169. }
  170. return nodeType;
  171. },
  172. /**
  173. * Method: setStyle
  174. * Use to set all the style attributes to a VML node.
  175. *
  176. * Parameters:
  177. * node - {DOMElement} An VML element to decorate
  178. * style - {Object}
  179. * options - {Object} Currently supported options include
  180. * 'isFilled' {Boolean} and
  181. * 'isStroked' {Boolean}
  182. * geometry - {<OpenLayers.Geometry>}
  183. */
  184. setStyle: function(node, style, options, geometry) {
  185. style = style || node._style;
  186. options = options || node._options;
  187. var fillColor = style.fillColor;
  188. var title = style.title || style.graphicTitle;
  189. if (title) {
  190. node.title = title;
  191. }
  192. if (node._geometryClass === "OpenLayers.Geometry.Point") {
  193. if (style.externalGraphic) {
  194. options.isFilled = true;
  195. var width = style.graphicWidth || style.graphicHeight;
  196. var height = style.graphicHeight || style.graphicWidth;
  197. width = width ? width : style.pointRadius*2;
  198. height = height ? height : style.pointRadius*2;
  199. var resolution = this.getResolution();
  200. var xOffset = (style.graphicXOffset != undefined) ?
  201. style.graphicXOffset : -(0.5 * width);
  202. var yOffset = (style.graphicYOffset != undefined) ?
  203. style.graphicYOffset : -(0.5 * height);
  204. node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
  205. node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
  206. node.style.width = width + "px";
  207. node.style.height = height + "px";
  208. node.style.flip = "y";
  209. // modify fillColor and options for stroke styling below
  210. fillColor = "none";
  211. options.isStroked = false;
  212. } else if (this.isComplexSymbol(style.graphicName)) {
  213. var cache = this.importSymbol(style.graphicName);
  214. node.path = cache.path;
  215. node.coordorigin = cache.left + "," + cache.bottom;
  216. var size = cache.size;
  217. node.coordsize = size + "," + size;
  218. this.drawCircle(node, geometry, style.pointRadius);
  219. node.style.flip = "y";
  220. } else {
  221. this.drawCircle(node, geometry, style.pointRadius);
  222. }
  223. }
  224. // fill
  225. if (options.isFilled) {
  226. node.fillcolor = fillColor;
  227. } else {
  228. node.filled = "false";
  229. }
  230. var fills = node.getElementsByTagName("fill");
  231. var fill = (fills.length == 0) ? null : fills[0];
  232. if (!options.isFilled) {
  233. if (fill) {
  234. node.removeChild(fill);
  235. }
  236. } else {
  237. if (!fill) {
  238. fill = this.createNode('olv:fill', node.id + "_fill");
  239. }
  240. fill.opacity = style.fillOpacity;
  241. if (node._geometryClass === "OpenLayers.Geometry.Point" &&
  242. style.externalGraphic) {
  243. // override fillOpacity
  244. if (style.graphicOpacity) {
  245. fill.opacity = style.graphicOpacity;
  246. }
  247. fill.src = style.externalGraphic;
  248. fill.type = "frame";
  249. if (!(style.graphicWidth && style.graphicHeight)) {
  250. fill.aspect = "atmost";
  251. }
  252. }
  253. if (fill.parentNode != node) {
  254. node.appendChild(fill);
  255. }
  256. }
  257. // additional rendering for rotated graphics or symbols
  258. var rotation = style.rotation;
  259. if ((rotation !== undefined || node._rotation !== undefined)) {
  260. node._rotation = rotation;
  261. if (style.externalGraphic) {
  262. this.graphicRotate(node, xOffset, yOffset, style);
  263. // make the fill fully transparent, because we now have
  264. // the graphic as imagedata element. We cannot just remove
  265. // the fill, because this is part of the hack described
  266. // in graphicRotate
  267. fill.opacity = 0;
  268. } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
  269. node.style.rotation = rotation || 0;
  270. }
  271. }
  272. // stroke
  273. var strokes = node.getElementsByTagName("stroke");
  274. var stroke = (strokes.length == 0) ? null : strokes[0];
  275. if (!options.isStroked) {
  276. node.stroked = false;
  277. if (stroke) {
  278. stroke.on = false;
  279. }
  280. } else {
  281. if (!stroke) {
  282. stroke = this.createNode('olv:stroke', node.id + "_stroke");
  283. node.appendChild(stroke);
  284. }
  285. stroke.on = true;
  286. stroke.color = style.strokeColor;
  287. stroke.weight = style.strokeWidth + "px";
  288. stroke.opacity = style.strokeOpacity;
  289. stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
  290. (style.strokeLinecap || 'round');
  291. if (style.strokeDashstyle) {
  292. stroke.dashstyle = this.dashStyle(style);
  293. }
  294. }
  295. if (style.cursor != "inherit" && style.cursor != null) {
  296. node.style.cursor = style.cursor;
  297. }
  298. return node;
  299. },
  300. /**
  301. * Method: graphicRotate
  302. * If a point is to be styled with externalGraphic and rotation, VML fills
  303. * cannot be used to display the graphic, because rotation of graphic
  304. * fills is not supported by the VML implementation of Internet Explorer.
  305. * This method creates a olv:imagedata element inside the VML node,
  306. * DXImageTransform.Matrix and BasicImage filters for rotation and
  307. * opacity, and a 3-step hack to remove rendering artefacts from the
  308. * graphic and preserve the ability of graphics to trigger events.
  309. * Finally, OpenLayers methods are used to determine the correct
  310. * insertion point of the rotated image, because DXImageTransform.Matrix
  311. * does the rotation without the ability to specify a rotation center
  312. * point.
  313. *
  314. * Parameters:
  315. * node - {DOMElement}
  316. * xOffset - {Number} rotation center relative to image, x coordinate
  317. * yOffset - {Number} rotation center relative to image, y coordinate
  318. * style - {Object}
  319. */
  320. graphicRotate: function(node, xOffset, yOffset, style) {
  321. var style = style || node._style;
  322. var rotation = style.rotation || 0;
  323. var aspectRatio, size;
  324. if (!(style.graphicWidth && style.graphicHeight)) {
  325. // load the image to determine its size
  326. var img = new Image();
  327. img.onreadystatechange = OpenLayers.Function.bind(function() {
  328. if(img.readyState == "complete" ||
  329. img.readyState == "interactive") {
  330. aspectRatio = img.width / img.height;
  331. size = Math.max(style.pointRadius * 2,
  332. style.graphicWidth || 0,
  333. style.graphicHeight || 0);
  334. xOffset = xOffset * aspectRatio;
  335. style.graphicWidth = size * aspectRatio;
  336. style.graphicHeight = size;
  337. this.graphicRotate(node, xOffset, yOffset, style);
  338. }
  339. }, this);
  340. img.src = style.externalGraphic;
  341. // will be called again by the onreadystate handler
  342. return;
  343. } else {
  344. size = Math.max(style.graphicWidth, style.graphicHeight);
  345. aspectRatio = style.graphicWidth / style.graphicHeight;
  346. }
  347. var width = Math.round(style.graphicWidth || size * aspectRatio);
  348. var height = Math.round(style.graphicHeight || size);
  349. node.style.width = width + "px";
  350. node.style.height = height + "px";
  351. // Three steps are required to remove artefacts for images with
  352. // transparent backgrounds (resulting from using DXImageTransform
  353. // filters on svg objects), while preserving awareness for browser
  354. // events on images:
  355. // - Use the fill as usual (like for unrotated images) to handle
  356. // events
  357. // - specify an imagedata element with the same src as the fill
  358. // - style the imagedata element with an AlphaImageLoader filter
  359. // with empty src
  360. var image = document.getElementById(node.id + "_image");
  361. if (!image) {
  362. image = this.createNode("olv:imagedata", node.id + "_image");
  363. node.appendChild(image);
  364. }
  365. image.style.width = width + "px";
  366. image.style.height = height + "px";
  367. image.src = style.externalGraphic;
  368. image.style.filter =
  369. "progid:DXImageTransform.Microsoft.AlphaImageLoader(" +
  370. "src='', sizingMethod='scale')";
  371. var rot = rotation * Math.PI / 180;
  372. var sintheta = Math.sin(rot);
  373. var costheta = Math.cos(rot);
  374. // do the rotation on the image
  375. var filter =
  376. "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
  377. ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
  378. ",SizingMethod='auto expand')\n";
  379. // set the opacity (needed for the imagedata)
  380. var opacity = style.graphicOpacity || style.fillOpacity;
  381. if (opacity && opacity != 1) {
  382. filter +=
  383. "progid:DXImageTransform.Microsoft.BasicImage(opacity=" +
  384. opacity+")\n";
  385. }
  386. node.style.filter = filter;
  387. // do the rotation again on a box, so we know the insertion point
  388. var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
  389. var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
  390. imgBox.rotate(style.rotation, centerPoint);
  391. var imgBounds = imgBox.getBounds();
  392. node.style.left = Math.round(
  393. parseInt(node.style.left) + imgBounds.left) + "px";
  394. node.style.top = Math.round(
  395. parseInt(node.style.top) - imgBounds.bottom) + "px";
  396. },
  397. /**
  398. * Method: postDraw
  399. * Does some node postprocessing to work around browser issues:
  400. * - Some versions of Internet Explorer seem to be unable to set fillcolor
  401. * and strokecolor to "none" correctly before the fill node is appended
  402. * to a visible vml node. This method takes care of that and sets
  403. * fillcolor and strokecolor again if needed.
  404. * - In some cases, a node won't become visible after being drawn. Setting
  405. * style.visibility to "visible" works around that.
  406. *
  407. * Parameters:
  408. * node - {DOMElement}
  409. */
  410. postDraw: function(node) {
  411. node.style.visibility = "visible";
  412. var fillColor = node._style.fillColor;
  413. var strokeColor = node._style.strokeColor;
  414. if (fillColor == "none" &&
  415. node.fillcolor != fillColor) {
  416. node.fillcolor = fillColor;
  417. }
  418. if (strokeColor == "none" &&
  419. node.strokecolor != strokeColor) {
  420. node.strokecolor = strokeColor;
  421. }
  422. },
  423. /**
  424. * Method: setNodeDimension
  425. * Get the geometry's bounds, convert it to our vml coordinate system,
  426. * then set the node's position, size, and local coordinate system.
  427. *
  428. * Parameters:
  429. * node - {DOMElement}
  430. * geometry - {<OpenLayers.Geometry>}
  431. */
  432. setNodeDimension: function(node, geometry) {
  433. var bbox = geometry.getBounds();
  434. if(bbox) {
  435. var resolution = this.getResolution();
  436. var scaledBox =
  437. new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0,
  438. (bbox.bottom/resolution - this.offset.y) | 0,
  439. ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
  440. (bbox.top/resolution - this.offset.y) | 0);
  441. // Set the internal coordinate system to draw the path
  442. node.style.left = scaledBox.left + "px";
  443. node.style.top = scaledBox.top + "px";
  444. node.style.width = scaledBox.getWidth() + "px";
  445. node.style.height = scaledBox.getHeight() + "px";
  446. node.coordorigin = scaledBox.left + " " + scaledBox.top;
  447. node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
  448. }
  449. },
  450. /**
  451. * Method: dashStyle
  452. *
  453. * Parameters:
  454. * style - {Object}
  455. *
  456. * Returns:
  457. * {String} A VML compliant 'stroke-dasharray' value
  458. */
  459. dashStyle: function(style) {
  460. var dash = style.strokeDashstyle;
  461. switch (dash) {
  462. case 'solid':
  463. case 'dot':
  464. case 'dash':
  465. case 'dashdot':
  466. case 'longdash':
  467. case 'longdashdot':
  468. return dash;
  469. default:
  470. // very basic guessing of dash style patterns
  471. var parts = dash.split(/[ ,]/);
  472. if (parts.length == 2) {
  473. if (1*parts[0] >= 2*parts[1]) {
  474. return "longdash";
  475. }
  476. return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
  477. } else if (parts.length == 4) {
  478. return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
  479. "dashdot";
  480. }
  481. return "solid";
  482. }
  483. },
  484. /**
  485. * Method: createNode
  486. * Create a new node
  487. *
  488. * Parameters:
  489. * type - {String} Kind of node to draw
  490. * id - {String} Id for node
  491. *
  492. * Returns:
  493. * {DOMElement} A new node of the given type and id
  494. */
  495. createNode: function(type, id) {
  496. var node = document.createElement(type);
  497. if (id) {
  498. node.id = id;
  499. }
  500. // IE hack to make elements unselectable, to prevent 'blue flash'
  501. // while dragging vectors; #1410
  502. node.unselectable = 'on';
  503. node.onselectstart = OpenLayers.Function.False;
  504. return node;
  505. },
  506. /**
  507. * Method: nodeTypeCompare
  508. * Determine whether a node is of a given type
  509. *
  510. * Parameters:
  511. * node - {DOMElement} An VML element
  512. * type - {String} Kind of node
  513. *
  514. * Returns:
  515. * {Boolean} Whether or not the specified node is of the specified type
  516. */
  517. nodeTypeCompare: function(node, type) {
  518. //split type
  519. var subType = type;
  520. var splitIndex = subType.indexOf(":");
  521. if (splitIndex != -1) {
  522. subType = subType.substr(splitIndex+1);
  523. }
  524. //split nodeName
  525. var nodeName = node.nodeName;
  526. splitIndex = nodeName.indexOf(":");
  527. if (splitIndex != -1) {
  528. nodeName = nodeName.substr(splitIndex+1);
  529. }
  530. return (subType == nodeName);
  531. },
  532. /**
  533. * Method: createRenderRoot
  534. * Create the renderer root
  535. *
  536. * Returns:
  537. * {DOMElement} The specific render engine's root element
  538. */
  539. createRenderRoot: function() {
  540. return this.nodeFactory(this.container.id + "_vmlRoot", "div");
  541. },
  542. /**
  543. * Method: createRoot
  544. * Create the main root element
  545. *
  546. * Parameters:
  547. * suffix - {String} suffix to append to the id
  548. *
  549. * Returns:
  550. * {DOMElement}
  551. */
  552. createRoot: function(suffix) {
  553. return this.nodeFactory(this.container.id + suffix, "olv:group");
  554. },
  555. /**************************************
  556. * *
  557. * GEOMETRY DRAWING FUNCTIONS *
  558. * *
  559. **************************************/
  560. /**
  561. * Method: drawPoint
  562. * Render a point
  563. *
  564. * Parameters:
  565. * node - {DOMElement}
  566. * geometry - {<OpenLayers.Geometry>}
  567. *
  568. * Returns:
  569. * {DOMElement} or false if the point could not be drawn
  570. */
  571. drawPoint: function(node, geometry) {
  572. return this.drawCircle(node, geometry, 1);
  573. },
  574. /**
  575. * Method: drawCircle
  576. * Render a circle.
  577. * Size and Center a circle given geometry (x,y center) and radius
  578. *
  579. * Parameters:
  580. * node - {DOMElement}
  581. * geometry - {<OpenLayers.Geometry>}
  582. * radius - {float}
  583. *
  584. * Returns:
  585. * {DOMElement} or false if the circle could not ne drawn
  586. */
  587. drawCircle: function(node, geometry, radius) {
  588. if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
  589. var resolution = this.getResolution();
  590. node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
  591. node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px";
  592. var diameter = radius * 2;
  593. node.style.width = diameter + "px";
  594. node.style.height = diameter + "px";
  595. return node;
  596. }
  597. return false;
  598. },
  599. /**
  600. * Method: drawLineString
  601. * Render a linestring.
  602. *
  603. * Parameters:
  604. * node - {DOMElement}
  605. * geometry - {<OpenLayers.Geometry>}
  606. *
  607. * Returns:
  608. * {DOMElement}
  609. */
  610. drawLineString: function(node, geometry) {
  611. return this.drawLine(node, geometry, false);
  612. },
  613. /**
  614. * Method: drawLinearRing
  615. * Render a linearring
  616. *
  617. * Parameters:
  618. * node - {DOMElement}
  619. * geometry - {<OpenLayers.Geometry>}
  620. *
  621. * Returns:
  622. * {DOMElement}
  623. */
  624. drawLinearRing: function(node, geometry) {
  625. return this.drawLine(node, geometry, true);
  626. },
  627. /**
  628. * Method: DrawLine
  629. * Render a line.
  630. *
  631. * Parameters:
  632. * node - {DOMElement}
  633. * geometry - {<OpenLayers.Geometry>}
  634. * closeLine - {Boolean} Close the line? (make it a ring?)
  635. *
  636. * Returns:
  637. * {DOMElement}
  638. */
  639. drawLine: function(node, geometry, closeLine) {
  640. this.setNodeDimension(node, geometry);
  641. var resolution = this.getResolution();
  642. var numComponents = geometry.components.length;
  643. var parts = new Array(numComponents);
  644. var comp, x, y;
  645. for (var i = 0; i < numComponents; i++) {
  646. comp = geometry.components[i];
  647. x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0;
  648. y = (comp.y/resolution - this.offset.y) | 0;
  649. parts[i] = " " + x + "," + y + " l ";
  650. }
  651. var end = (closeLine) ? " x e" : " e";
  652. node.path = "m" + parts.join("") + end;
  653. return node;
  654. },
  655. /**
  656. * Method: drawPolygon
  657. * Render a polygon
  658. *
  659. * Parameters:
  660. * node - {DOMElement}
  661. * geometry - {<OpenLayers.Geometry>}
  662. *
  663. * Returns:
  664. * {DOMElement}
  665. */
  666. drawPolygon: function(node, geometry) {
  667. this.setNodeDimension(node, geometry);
  668. var resolution = this.getResolution();
  669. var path = [];
  670. var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
  671. for (j=0, jj=geometry.components.length; j<jj; j++) {
  672. path.push("m");
  673. points = geometry.components[j].components;
  674. // we only close paths of interior rings with area
  675. area = (j === 0);
  676. first = null;
  677. second = null;
  678. for (i=0, ii=points.length; i<ii; i++) {
  679. comp = points[i];
  680. x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
  681. y = (comp.y / resolution - this.offset.y) | 0;
  682. pathComp = " " + x + "," + y;
  683. path.push(pathComp);
  684. if (i==0) {
  685. path.push(" l");
  686. }
  687. if (!area) {
  688. // IE improperly renders sub-paths that have no area.
  689. // Instead of checking the area of every ring, we confirm
  690. // the ring has at least three distinct points. This does
  691. // not catch all non-zero area cases, but it greatly improves
  692. // interior ring digitizing and is a minor performance hit
  693. // when rendering rings with many points.
  694. if (!first) {
  695. first = pathComp;
  696. } else if (first != pathComp) {
  697. if (!second) {
  698. second = pathComp;
  699. } else if (second != pathComp) {
  700. // stop looking
  701. area = true;
  702. }
  703. }
  704. }
  705. }
  706. path.push(area ? " x " : " ");
  707. }
  708. path.push("e");
  709. node.path = path.join("");
  710. return node;
  711. },
  712. /**
  713. * Method: drawRectangle
  714. * Render a rectangle
  715. *
  716. * Parameters:
  717. * node - {DOMElement}
  718. * geometry - {<OpenLayers.Geometry>}
  719. *
  720. * Returns:
  721. * {DOMElement}
  722. */
  723. drawRectangle: function(node, geometry) {
  724. var resolution = this.getResolution();
  725. node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
  726. node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
  727. node.style.width = ((geometry.width/resolution) | 0) + "px";
  728. node.style.height = ((geometry.height/resolution) | 0) + "px";
  729. return node;
  730. },
  731. /**
  732. * Method: drawText
  733. * This method is only called by the renderer itself.
  734. *
  735. * Parameters:
  736. * featureId - {String}
  737. * style -
  738. * location - {<OpenLayers.Geometry.Point>}
  739. */
  740. drawText: function(featureId, style, location) {
  741. var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
  742. var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
  743. var resolution = this.getResolution();
  744. label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
  745. label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
  746. label.style.flip = "y";
  747. textbox.innerText = style.label;
  748. if (style.cursor != "inherit" && style.cursor != null) {
  749. textbox.style.cursor = style.cursor;
  750. }
  751. if (style.fontColor) {
  752. textbox.style.color = style.fontColor;
  753. }
  754. if (style.fontOpacity) {
  755. textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
  756. }
  757. if (style.fontFamily) {
  758. textbox.style.fontFamily = style.fontFamily;
  759. }
  760. if (style.fontSize) {
  761. textbox.style.fontSize = style.fontSize;
  762. }
  763. if (style.fontWeight) {
  764. textbox.style.fontWeight = style.fontWeight;
  765. }
  766. if (style.fontStyle) {
  767. textbox.style.fontStyle = style.fontStyle;
  768. }
  769. if(style.labelSelect === true) {
  770. label._featureId = featureId;
  771. textbox._featureId = featureId;
  772. textbox._geometry = location;
  773. textbox._geometryClass = location.CLASS_NAME;
  774. }
  775. textbox.style.whiteSpace = "nowrap";
  776. // fun with IE: IE7 in standards compliant mode does not display any
  777. // text with a left inset of 0. So we set this to 1px and subtract one
  778. // pixel later when we set label.style.left
  779. textbox.inset = "1px,0px,0px,0px";
  780. if(!label.parentNode) {
  781. label.appendChild(textbox);
  782. this.textRoot.appendChild(label);
  783. }
  784. var align = style.labelAlign || "cm";
  785. if (align.length == 1) {
  786. align += "m";
  787. }
  788. var xshift = textbox.clientWidth *
  789. (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
  790. var yshift = textbox.clientHeight *
  791. (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
  792. label.style.left = parseInt(label.style.left)-xshift-1+"px";
  793. label.style.top = parseInt(label.style.top)+yshift+"px";
  794. },
  795. /**
  796. * Method: moveRoot
  797. * moves this renderer's root to a different renderer.
  798. *
  799. * Parameters:
  800. * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
  801. * root - {DOMElement} optional root node. To be used when this renderer
  802. * holds roots from multiple layers to tell this method which one to
  803. * detach
  804. *
  805. * Returns:
  806. * {Boolean} true if successful, false otherwise
  807. */
  808. moveRoot: function(renderer) {
  809. var layer = this.map.getLayer(renderer.container.id);
  810. if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
  811. layer = this.map.getLayer(this.container.id);
  812. }
  813. layer && layer.renderer.clear();
  814. OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
  815. layer && layer.redraw();
  816. },
  817. /**
  818. * Method: importSymbol
  819. * add a new symbol definition from the rendererer's symbol hash
  820. *
  821. * Parameters:
  822. * graphicName - {String} name of the symbol to import
  823. *
  824. * Returns:
  825. * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
  826. */
  827. importSymbol: function (graphicName) {
  828. var id = this.container.id + "-" + graphicName;
  829. // check if symbol already exists in the cache
  830. var cache = this.symbolCache[id];
  831. if (cache) {
  832. return cache;
  833. }
  834. var symbol = OpenLayers.Renderer.symbol[graphicName];
  835. if (!symbol) {
  836. throw new Error(graphicName + ' is not a valid symbol name');
  837. }
  838. var symbolExtent = new OpenLayers.Bounds(
  839. Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
  840. var pathitems = ["m"];
  841. for (var i=0; i<symbol.length; i=i+2) {
  842. var x = symbol[i];
  843. var y = symbol[i+1];
  844. symbolExtent.left = Math.min(symbolExtent.left, x);
  845. symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
  846. symbolExtent.right = Math.max(symbolExtent.right, x);
  847. symbolExtent.top = Math.max(symbolExtent.top, y);
  848. pathitems.push(x);
  849. pathitems.push(y);
  850. if (i == 0) {
  851. pathitems.push("l");
  852. }
  853. }
  854. pathitems.push("x e");
  855. var path = pathitems.join(" ");
  856. var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
  857. if(diff > 0) {
  858. symbolExtent.bottom = symbolExtent.bottom - diff;
  859. symbolExtent.top = symbolExtent.top + diff;
  860. } else {
  861. symbolExtent.left = symbolExtent.left + diff;
  862. symbolExtent.right = symbolExtent.right - diff;
  863. }
  864. cache = {
  865. path: path,
  866. size: symbolExtent.getWidth(), // equals getHeight() now
  867. left: symbolExtent.left,
  868. bottom: symbolExtent.bottom
  869. };
  870. this.symbolCache[id] = cache;
  871. return cache;
  872. },
  873. CLASS_NAME: "OpenLayers.Renderer.VML"
  874. });
  875. /**
  876. * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
  877. * {Object}
  878. */
  879. OpenLayers.Renderer.VML.LABEL_SHIFT = {
  880. "l": 0,
  881. "c": .5,
  882. "r": 1,
  883. "t": 0,
  884. "m": .5,
  885. "b": 1
  886. };