VML.js 34 KB

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