SVG.js 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  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.SVG
  10. *
  11. * Inherits:
  12. * - <OpenLayers.Renderer.Elements>
  13. */
  14. OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
  15. /**
  16. * Property: xmlns
  17. * {String}
  18. */
  19. xmlns: "http://www.w3.org/2000/svg",
  20. /**
  21. * Property: xlinkns
  22. * {String}
  23. */
  24. xlinkns: "http://www.w3.org/1999/xlink",
  25. /**
  26. * Constant: MAX_PIXEL
  27. * {Integer} Firefox has a limitation where values larger or smaller than
  28. * about 15000 in an SVG document lock the browser up. This
  29. * works around it.
  30. */
  31. MAX_PIXEL: 15000,
  32. /**
  33. * Property: translationParameters
  34. * {Object} Hash with "x" and "y" properties
  35. */
  36. translationParameters: null,
  37. /**
  38. * Property: symbolMetrics
  39. * {Object} Cache for symbol metrics according to their svg coordinate
  40. * space. This is an object keyed by the symbol's id, and values are
  41. * an array of [width, centerX, centerY].
  42. */
  43. symbolMetrics: null,
  44. /**
  45. * Constructor: OpenLayers.Renderer.SVG
  46. *
  47. * Parameters:
  48. * containerID - {String}
  49. */
  50. initialize: function(containerID) {
  51. if (!this.supported()) {
  52. return;
  53. }
  54. OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
  55. arguments);
  56. this.translationParameters = {x: 0, y: 0};
  57. this.symbolMetrics = {};
  58. },
  59. /**
  60. * APIMethod: supported
  61. *
  62. * Returns:
  63. * {Boolean} Whether or not the browser supports the SVG renderer
  64. */
  65. supported: function() {
  66. var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
  67. return (document.implementation &&
  68. (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
  69. document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
  70. document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
  71. },
  72. /**
  73. * Method: inValidRange
  74. * See #669 for more information
  75. *
  76. * Parameters:
  77. * x - {Integer}
  78. * y - {Integer}
  79. * xyOnly - {Boolean} whether or not to just check for x and y, which means
  80. * to not take the current translation parameters into account if true.
  81. *
  82. * Returns:
  83. * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
  84. * valid range.
  85. */
  86. inValidRange: function(x, y, xyOnly) {
  87. var left = x + (xyOnly ? 0 : this.translationParameters.x);
  88. var top = y + (xyOnly ? 0 : this.translationParameters.y);
  89. return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
  90. top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
  91. },
  92. /**
  93. * Method: setExtent
  94. *
  95. * Parameters:
  96. * extent - {<OpenLayers.Bounds>}
  97. * resolutionChanged - {Boolean}
  98. *
  99. * Returns:
  100. * {Boolean} true to notify the layer that the new extent does not exceed
  101. * the coordinate range, and the features will not need to be redrawn.
  102. * False otherwise.
  103. */
  104. setExtent: function(extent, resolutionChanged) {
  105. OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,
  106. arguments);
  107. var resolution = this.getResolution();
  108. var left = -extent.left / resolution;
  109. var top = extent.top / resolution;
  110. // If the resolution has changed, start over changing the corner, because
  111. // the features will redraw.
  112. if (resolutionChanged) {
  113. this.left = left;
  114. this.top = top;
  115. // Set the viewbox
  116. var extentString = "0 0 " + this.size.w + " " + this.size.h;
  117. this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
  118. this.translate(0, 0);
  119. return true;
  120. } else {
  121. var inRange = this.translate(left - this.left, top - this.top);
  122. if (!inRange) {
  123. // recenter the coordinate system
  124. this.setExtent(extent, true);
  125. }
  126. return inRange;
  127. }
  128. },
  129. /**
  130. * Method: translate
  131. * Transforms the SVG coordinate system
  132. *
  133. * Parameters:
  134. * x - {Float}
  135. * y - {Float}
  136. *
  137. * Returns:
  138. * {Boolean} true if the translation parameters are in the valid coordinates
  139. * range, false otherwise.
  140. */
  141. translate: function(x, y) {
  142. if (!this.inValidRange(x, y, true)) {
  143. return false;
  144. } else {
  145. var transformString = "";
  146. if (x || y) {
  147. transformString = "translate(" + x + "," + y + ")";
  148. }
  149. this.root.setAttributeNS(null, "transform", transformString);
  150. this.translationParameters = {x: x, y: y};
  151. return true;
  152. }
  153. },
  154. /**
  155. * Method: setSize
  156. * Sets the size of the drawing surface.
  157. *
  158. * Parameters:
  159. * size - {<OpenLayers.Size>} The size of the drawing surface
  160. */
  161. setSize: function(size) {
  162. OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
  163. this.rendererRoot.setAttributeNS(null, "width", this.size.w);
  164. this.rendererRoot.setAttributeNS(null, "height", this.size.h);
  165. },
  166. /**
  167. * Method: getNodeType
  168. *
  169. * Parameters:
  170. * geometry - {<OpenLayers.Geometry>}
  171. * style - {Object}
  172. *
  173. * Returns:
  174. * {String} The corresponding node type for the specified geometry
  175. */
  176. getNodeType: function(geometry, style) {
  177. var nodeType = null;
  178. switch (geometry.CLASS_NAME) {
  179. case "OpenLayers.Geometry.Point":
  180. if (style.externalGraphic) {
  181. nodeType = "image";
  182. } else if (this.isComplexSymbol(style.graphicName)) {
  183. nodeType = "svg";
  184. } else {
  185. nodeType = "circle";
  186. }
  187. break;
  188. case "OpenLayers.Geometry.Rectangle":
  189. nodeType = "rect";
  190. break;
  191. case "OpenLayers.Geometry.LineString":
  192. nodeType = "polyline";
  193. break;
  194. case "OpenLayers.Geometry.LinearRing":
  195. nodeType = "polygon";
  196. break;
  197. case "OpenLayers.Geometry.Polygon":
  198. case "OpenLayers.Geometry.Curve":
  199. case "OpenLayers.Geometry.Surface":
  200. nodeType = "path";
  201. break;
  202. default:
  203. break;
  204. }
  205. return nodeType;
  206. },
  207. /**
  208. * Method: setStyle
  209. * Use to set all the style attributes to a SVG node.
  210. *
  211. * Takes care to adjust stroke width and point radius to be
  212. * resolution-relative
  213. *
  214. * Parameters:
  215. * node - {SVGDomElement} An SVG element to decorate
  216. * style - {Object}
  217. * options - {Object} Currently supported options include
  218. * 'isFilled' {Boolean} and
  219. * 'isStroked' {Boolean}
  220. */
  221. setStyle: function(node, style, options) {
  222. style = style || node._style;
  223. options = options || node._options;
  224. var r = parseFloat(node.getAttributeNS(null, "r"));
  225. var widthFactor = 1;
  226. var pos;
  227. if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
  228. node.style.visibility = "";
  229. if (style.graphic === false) {
  230. node.style.visibility = "hidden";
  231. } else if (style.externalGraphic) {
  232. pos = this.getPosition(node);
  233. if (style.graphicTitle) {
  234. node.setAttributeNS(null, "title", style.graphicTitle);
  235. //Standards-conformant SVG
  236. var label = this.nodeFactory(null, "title");
  237. label.textContent = style.graphicTitle;
  238. node.appendChild(label);
  239. }
  240. if (style.graphicWidth && style.graphicHeight) {
  241. node.setAttributeNS(null, "preserveAspectRatio", "none");
  242. }
  243. var width = style.graphicWidth || style.graphicHeight;
  244. var height = style.graphicHeight || style.graphicWidth;
  245. width = width ? width : style.pointRadius*2;
  246. height = height ? height : style.pointRadius*2;
  247. var xOffset = (style.graphicXOffset != undefined) ?
  248. style.graphicXOffset : -(0.5 * width);
  249. var yOffset = (style.graphicYOffset != undefined) ?
  250. style.graphicYOffset : -(0.5 * height);
  251. var opacity = style.graphicOpacity || style.fillOpacity;
  252. node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
  253. node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
  254. node.setAttributeNS(null, "width", width);
  255. node.setAttributeNS(null, "height", height);
  256. node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
  257. node.setAttributeNS(null, "style", "opacity: "+opacity);
  258. node.onclick = OpenLayers.Renderer.SVG.preventDefault;
  259. } else if (this.isComplexSymbol(style.graphicName)) {
  260. // the symbol viewBox is three times as large as the symbol
  261. var offset = style.pointRadius * 3;
  262. var size = offset * 2;
  263. var src = this.importSymbol(style.graphicName);
  264. pos = this.getPosition(node);
  265. widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
  266. // remove the node from the dom before we modify it. This
  267. // prevents various rendering issues in Safari and FF
  268. var parent = node.parentNode;
  269. var nextSibling = node.nextSibling;
  270. if(parent) {
  271. parent.removeChild(node);
  272. }
  273. // The more appropriate way to implement this would be use/defs,
  274. // but due to various issues in several browsers, it is safer to
  275. // copy the symbols instead of referencing them.
  276. // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
  277. // and this email thread
  278. // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
  279. node.firstChild && node.removeChild(node.firstChild);
  280. node.appendChild(src.firstChild.cloneNode(true));
  281. node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
  282. node.setAttributeNS(null, "width", size);
  283. node.setAttributeNS(null, "height", size);
  284. node.setAttributeNS(null, "x", pos.x - offset);
  285. node.setAttributeNS(null, "y", pos.y - offset);
  286. // now that the node has all its new properties, insert it
  287. // back into the dom where it was
  288. if(nextSibling) {
  289. parent.insertBefore(node, nextSibling);
  290. } else if(parent) {
  291. parent.appendChild(node);
  292. }
  293. } else {
  294. node.setAttributeNS(null, "r", style.pointRadius);
  295. }
  296. var rotation = style.rotation;
  297. if ((rotation !== undefined || node._rotation !== undefined) && pos) {
  298. node._rotation = rotation;
  299. rotation |= 0;
  300. if (node.nodeName !== "svg") {
  301. node.setAttributeNS(null, "transform",
  302. "rotate(" + rotation + " " + pos.x + " " +
  303. pos.y + ")");
  304. } else {
  305. var metrics = this.symbolMetrics[src.id];
  306. node.firstChild.setAttributeNS(null, "transform", "rotate("
  307. + rotation + " "
  308. + metrics[1] + " "
  309. + metrics[2] + ")");
  310. }
  311. }
  312. }
  313. if (options.isFilled) {
  314. node.setAttributeNS(null, "fill", style.fillColor);
  315. node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
  316. } else {
  317. node.setAttributeNS(null, "fill", "none");
  318. }
  319. if (options.isStroked) {
  320. node.setAttributeNS(null, "stroke", style.strokeColor);
  321. node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
  322. node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
  323. node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
  324. // Hard-coded linejoin for now, to make it look the same as in VML.
  325. // There is no strokeLinejoin property yet for symbolizers.
  326. node.setAttributeNS(null, "stroke-linejoin", "round");
  327. style.strokeDashstyle && node.setAttributeNS(null,
  328. "stroke-dasharray", this.dashStyle(style, widthFactor));
  329. } else {
  330. node.setAttributeNS(null, "stroke", "none");
  331. }
  332. if (style.pointerEvents) {
  333. node.setAttributeNS(null, "pointer-events", style.pointerEvents);
  334. }
  335. if (style.cursor != null) {
  336. node.setAttributeNS(null, "cursor", style.cursor);
  337. }
  338. return node;
  339. },
  340. /**
  341. * Method: dashStyle
  342. *
  343. * Parameters:
  344. * style - {Object}
  345. * widthFactor - {Number}
  346. *
  347. * Returns:
  348. * {String} A SVG compliant 'stroke-dasharray' value
  349. */
  350. dashStyle: function(style, widthFactor) {
  351. var w = style.strokeWidth * widthFactor;
  352. var str = style.strokeDashstyle;
  353. switch (str) {
  354. case 'solid':
  355. return 'none';
  356. case 'dot':
  357. return [1, 4 * w].join();
  358. case 'dash':
  359. return [4 * w, 4 * w].join();
  360. case 'dashdot':
  361. return [4 * w, 4 * w, 1, 4 * w].join();
  362. case 'longdash':
  363. return [8 * w, 4 * w].join();
  364. case 'longdashdot':
  365. return [8 * w, 4 * w, 1, 4 * w].join();
  366. default:
  367. return OpenLayers.String.trim(str).replace(/\s+/g, ",");
  368. }
  369. },
  370. /**
  371. * Method: createNode
  372. *
  373. * Parameters:
  374. * type - {String} Kind of node to draw
  375. * id - {String} Id for node
  376. *
  377. * Returns:
  378. * {DOMElement} A new node of the given type and id
  379. */
  380. createNode: function(type, id) {
  381. var node = document.createElementNS(this.xmlns, type);
  382. if (id) {
  383. node.setAttributeNS(null, "id", id);
  384. }
  385. return node;
  386. },
  387. /**
  388. * Method: nodeTypeCompare
  389. *
  390. * Parameters:
  391. * node - {SVGDomElement} An SVG element
  392. * type - {String} Kind of node
  393. *
  394. * Returns:
  395. * {Boolean} Whether or not the specified node is of the specified type
  396. */
  397. nodeTypeCompare: function(node, type) {
  398. return (type == node.nodeName);
  399. },
  400. /**
  401. * Method: createRenderRoot
  402. *
  403. * Returns:
  404. * {DOMElement} The specific render engine's root element
  405. */
  406. createRenderRoot: function() {
  407. return this.nodeFactory(this.container.id + "_svgRoot", "svg");
  408. },
  409. /**
  410. * Method: createRoot
  411. *
  412. * Parameter:
  413. * suffix - {String} suffix to append to the id
  414. *
  415. * Returns:
  416. * {DOMElement}
  417. */
  418. createRoot: function(suffix) {
  419. return this.nodeFactory(this.container.id + suffix, "g");
  420. },
  421. /**
  422. * Method: createDefs
  423. *
  424. * Returns:
  425. * {DOMElement} The element to which we'll add the symbol definitions
  426. */
  427. createDefs: function() {
  428. var defs = this.nodeFactory(this.container.id + "_defs", "defs");
  429. this.rendererRoot.appendChild(defs);
  430. return defs;
  431. },
  432. /**************************************
  433. * *
  434. * GEOMETRY DRAWING FUNCTIONS *
  435. * *
  436. **************************************/
  437. /**
  438. * Method: drawPoint
  439. * This method is only called by the renderer itself.
  440. *
  441. * Parameters:
  442. * node - {DOMElement}
  443. * geometry - {<OpenLayers.Geometry>}
  444. *
  445. * Returns:
  446. * {DOMElement} or false if the renderer could not draw the point
  447. */
  448. drawPoint: function(node, geometry) {
  449. return this.drawCircle(node, geometry, 1);
  450. },
  451. /**
  452. * Method: drawCircle
  453. * This method is only called by the renderer itself.
  454. *
  455. * Parameters:
  456. * node - {DOMElement}
  457. * geometry - {<OpenLayers.Geometry>}
  458. * radius - {Float}
  459. *
  460. * Returns:
  461. * {DOMElement} or false if the renderer could not draw the circle
  462. */
  463. drawCircle: function(node, geometry, radius) {
  464. var resolution = this.getResolution();
  465. var x = (geometry.x / resolution + this.left);
  466. var y = (this.top - geometry.y / resolution);
  467. if (this.inValidRange(x, y)) {
  468. node.setAttributeNS(null, "cx", x);
  469. node.setAttributeNS(null, "cy", y);
  470. node.setAttributeNS(null, "r", radius);
  471. return node;
  472. } else {
  473. return false;
  474. }
  475. },
  476. /**
  477. * Method: drawLineString
  478. * This method is only called by the renderer itself.
  479. *
  480. * Parameters:
  481. * node - {DOMElement}
  482. * geometry - {<OpenLayers.Geometry>}
  483. *
  484. * Returns:
  485. * {DOMElement} or null if the renderer could not draw all components of
  486. * the linestring, or false if nothing could be drawn
  487. */
  488. drawLineString: function(node, geometry) {
  489. var componentsResult = this.getComponentsString(geometry.components);
  490. if (componentsResult.path) {
  491. node.setAttributeNS(null, "points", componentsResult.path);
  492. return (componentsResult.complete ? node : null);
  493. } else {
  494. return false;
  495. }
  496. },
  497. /**
  498. * Method: drawLinearRing
  499. * This method is only called by the renderer itself.
  500. *
  501. * Parameters:
  502. * node - {DOMElement}
  503. * geometry - {<OpenLayers.Geometry>}
  504. *
  505. * Returns:
  506. * {DOMElement} or null if the renderer could not draw all components
  507. * of the linear ring, or false if nothing could be drawn
  508. */
  509. drawLinearRing: function(node, geometry) {
  510. var componentsResult = this.getComponentsString(geometry.components);
  511. if (componentsResult.path) {
  512. node.setAttributeNS(null, "points", componentsResult.path);
  513. return (componentsResult.complete ? node : null);
  514. } else {
  515. return false;
  516. }
  517. },
  518. /**
  519. * Method: drawPolygon
  520. * This method is only called by the renderer itself.
  521. *
  522. * Parameters:
  523. * node - {DOMElement}
  524. * geometry - {<OpenLayers.Geometry>}
  525. *
  526. * Returns:
  527. * {DOMElement} or null if the renderer could not draw all components
  528. * of the polygon, or false if nothing could be drawn
  529. */
  530. drawPolygon: function(node, geometry) {
  531. var d = "";
  532. var draw = true;
  533. var complete = true;
  534. var linearRingResult, path;
  535. for (var j=0, len=geometry.components.length; j<len; j++) {
  536. d += " M";
  537. linearRingResult = this.getComponentsString(
  538. geometry.components[j].components, " ");
  539. path = linearRingResult.path;
  540. if (path) {
  541. d += " " + path;
  542. complete = linearRingResult.complete && complete;
  543. } else {
  544. draw = false;
  545. }
  546. }
  547. d += " z";
  548. if (draw) {
  549. node.setAttributeNS(null, "d", d);
  550. node.setAttributeNS(null, "fill-rule", "evenodd");
  551. return complete ? node : null;
  552. } else {
  553. return false;
  554. }
  555. },
  556. /**
  557. * Method: drawRectangle
  558. * This method is only called by the renderer itself.
  559. *
  560. * Parameters:
  561. * node - {DOMElement}
  562. * geometry - {<OpenLayers.Geometry>}
  563. *
  564. * Returns:
  565. * {DOMElement} or false if the renderer could not draw the rectangle
  566. */
  567. drawRectangle: function(node, geometry) {
  568. var resolution = this.getResolution();
  569. var x = (geometry.x / resolution + this.left);
  570. var y = (this.top - geometry.y / resolution);
  571. if (this.inValidRange(x, y)) {
  572. node.setAttributeNS(null, "x", x);
  573. node.setAttributeNS(null, "y", y);
  574. node.setAttributeNS(null, "width", geometry.width / resolution);
  575. node.setAttributeNS(null, "height", geometry.height / resolution);
  576. return node;
  577. } else {
  578. return false;
  579. }
  580. },
  581. /**
  582. * Method: drawSurface
  583. * This method is only called by the renderer itself.
  584. *
  585. * Parameters:
  586. * node - {DOMElement}
  587. * geometry - {<OpenLayers.Geometry>}
  588. *
  589. * Returns:
  590. * {DOMElement} or false if the renderer could not draw the surface
  591. */
  592. drawSurface: function(node, geometry) {
  593. // create the svg path string representation
  594. var d = null;
  595. var draw = true;
  596. for (var i=0, len=geometry.components.length; i<len; i++) {
  597. if ((i%3) == 0 && (i/3) == 0) {
  598. var component = this.getShortString(geometry.components[i]);
  599. if (!component) { draw = false; }
  600. d = "M " + component;
  601. } else if ((i%3) == 1) {
  602. var component = this.getShortString(geometry.components[i]);
  603. if (!component) { draw = false; }
  604. d += " C " + component;
  605. } else {
  606. var component = this.getShortString(geometry.components[i]);
  607. if (!component) { draw = false; }
  608. d += " " + component;
  609. }
  610. }
  611. d += " Z";
  612. if (draw) {
  613. node.setAttributeNS(null, "d", d);
  614. return node;
  615. } else {
  616. return false;
  617. }
  618. },
  619. /**
  620. * Method: drawText
  621. * This method is only called by the renderer itself.
  622. *
  623. * Parameters:
  624. * featureId - {String}
  625. * style -
  626. * location - {<OpenLayers.Geometry.Point>}
  627. */
  628. drawText: function(featureId, style, location) {
  629. var resolution = this.getResolution();
  630. var x = (location.x / resolution + this.left);
  631. var y = (location.y / resolution - this.top);
  632. var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "text");
  633. label.setAttributeNS(null, "x", x);
  634. label.setAttributeNS(null, "y", -y);
  635. if (style.fontColor) {
  636. label.setAttributeNS(null, "fill", style.fontColor);
  637. }
  638. if (style.fontOpacity) {
  639. label.setAttributeNS(null, "opacity", style.fontOpacity);
  640. }
  641. if (style.fontFamily) {
  642. label.setAttributeNS(null, "font-family", style.fontFamily);
  643. }
  644. if (style.fontSize) {
  645. label.setAttributeNS(null, "font-size", style.fontSize);
  646. }
  647. if (style.fontWeight) {
  648. label.setAttributeNS(null, "font-weight", style.fontWeight);
  649. }
  650. if (style.fontStyle) {
  651. label.setAttributeNS(null, "font-style", style.fontStyle);
  652. }
  653. if (style.labelSelect === true) {
  654. label.setAttributeNS(null, "pointer-events", "visible");
  655. label._featureId = featureId;
  656. } else {
  657. label.setAttributeNS(null, "pointer-events", "none");
  658. }
  659. var align = style.labelAlign || "cm";
  660. label.setAttributeNS(null, "text-anchor",
  661. OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
  662. if (OpenLayers.IS_GECKO === true) {
  663. label.setAttributeNS(null, "dominant-baseline",
  664. OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
  665. }
  666. var labelRows = style.label.split('\n');
  667. var numRows = labelRows.length;
  668. while (label.childNodes.length > numRows) {
  669. label.removeChild(label.lastChild);
  670. }
  671. for (var i = 0; i < numRows; i++) {
  672. var tspan = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan_" + i, "tspan");
  673. if (style.labelSelect === true) {
  674. tspan._featureId = featureId;
  675. tspan._geometry = location;
  676. tspan._geometryClass = location.CLASS_NAME;
  677. }
  678. if (OpenLayers.IS_GECKO === false) {
  679. tspan.setAttributeNS(null, "baseline-shift",
  680. OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
  681. }
  682. tspan.setAttribute("x", x);
  683. if (i == 0) {
  684. var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
  685. if (vfactor == null) {
  686. vfactor = -.5;
  687. }
  688. tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
  689. } else {
  690. tspan.setAttribute("dy", "1em");
  691. }
  692. tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
  693. if (!tspan.parentNode) {
  694. label.appendChild(tspan);
  695. }
  696. }
  697. if (!label.parentNode) {
  698. this.textRoot.appendChild(label);
  699. }
  700. },
  701. /**
  702. * Method: getComponentString
  703. *
  704. * Parameters:
  705. * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
  706. * separator - {String} character between coordinate pairs. Defaults to ","
  707. *
  708. * Returns:
  709. * {Object} hash with properties "path" (the string created from the
  710. * components and "complete" (false if the renderer was unable to
  711. * draw all components)
  712. */
  713. getComponentsString: function(components, separator) {
  714. var renderCmp = [];
  715. var complete = true;
  716. var len = components.length;
  717. var strings = [];
  718. var str, component;
  719. for(var i=0; i<len; i++) {
  720. component = components[i];
  721. renderCmp.push(component);
  722. str = this.getShortString(component);
  723. if (str) {
  724. strings.push(str);
  725. } else {
  726. // The current component is outside the valid range. Let's
  727. // see if the previous or next component is inside the range.
  728. // If so, add the coordinate of the intersection with the
  729. // valid range bounds.
  730. if (i > 0) {
  731. if (this.getShortString(components[i - 1])) {
  732. strings.push(this.clipLine(components[i],
  733. components[i-1]));
  734. }
  735. }
  736. if (i < len - 1) {
  737. if (this.getShortString(components[i + 1])) {
  738. strings.push(this.clipLine(components[i],
  739. components[i+1]));
  740. }
  741. }
  742. complete = false;
  743. }
  744. }
  745. return {
  746. path: strings.join(separator || ","),
  747. complete: complete
  748. };
  749. },
  750. /**
  751. * Method: clipLine
  752. * Given two points (one inside the valid range, and one outside),
  753. * clips the line betweeen the two points so that the new points are both
  754. * inside the valid range.
  755. *
  756. * Parameters:
  757. * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
  758. * invalid point
  759. * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
  760. * valid point
  761. * Returns
  762. * {String} the SVG coordinate pair of the clipped point (like
  763. * getShortString), or an empty string if both passed componets are at
  764. * the same point.
  765. */
  766. clipLine: function(badComponent, goodComponent) {
  767. if (goodComponent.equals(badComponent)) {
  768. return "";
  769. }
  770. var resolution = this.getResolution();
  771. var maxX = this.MAX_PIXEL - this.translationParameters.x;
  772. var maxY = this.MAX_PIXEL - this.translationParameters.y;
  773. var x1 = goodComponent.x / resolution + this.left;
  774. var y1 = this.top - goodComponent.y / resolution;
  775. var x2 = badComponent.x / resolution + this.left;
  776. var y2 = this.top - badComponent.y / resolution;
  777. var k;
  778. if (x2 < -maxX || x2 > maxX) {
  779. k = (y2 - y1) / (x2 - x1);
  780. x2 = x2 < 0 ? -maxX : maxX;
  781. y2 = y1 + (x2 - x1) * k;
  782. }
  783. if (y2 < -maxY || y2 > maxY) {
  784. k = (x2 - x1) / (y2 - y1);
  785. y2 = y2 < 0 ? -maxY : maxY;
  786. x2 = x1 + (y2 - y1) * k;
  787. }
  788. return x2 + "," + y2;
  789. },
  790. /**
  791. * Method: getShortString
  792. *
  793. * Parameters:
  794. * point - {<OpenLayers.Geometry.Point>}
  795. *
  796. * Returns:
  797. * {String} or false if point is outside the valid range
  798. */
  799. getShortString: function(point) {
  800. var resolution = this.getResolution();
  801. var x = (point.x / resolution + this.left);
  802. var y = (this.top - point.y / resolution);
  803. if (this.inValidRange(x, y)) {
  804. return x + "," + y;
  805. } else {
  806. return false;
  807. }
  808. },
  809. /**
  810. * Method: getPosition
  811. * Finds the position of an svg node.
  812. *
  813. * Parameters:
  814. * node - {DOMElement}
  815. *
  816. * Returns:
  817. * {Object} hash with x and y properties, representing the coordinates
  818. * within the svg coordinate system
  819. */
  820. getPosition: function(node) {
  821. return({
  822. x: parseFloat(node.getAttributeNS(null, "cx")),
  823. y: parseFloat(node.getAttributeNS(null, "cy"))
  824. });
  825. },
  826. /**
  827. * Method: importSymbol
  828. * add a new symbol definition from the rendererer's symbol hash
  829. *
  830. * Parameters:
  831. * graphicName - {String} name of the symbol to import
  832. *
  833. * Returns:
  834. * {DOMElement} - the imported symbol
  835. */
  836. importSymbol: function (graphicName) {
  837. if (!this.defs) {
  838. // create svg defs tag
  839. this.defs = this.createDefs();
  840. }
  841. var id = this.container.id + "-" + graphicName;
  842. // check if symbol already exists in the defs
  843. var existing = document.getElementById(id)
  844. if (existing != null) {
  845. return existing;
  846. }
  847. var symbol = OpenLayers.Renderer.symbol[graphicName];
  848. if (!symbol) {
  849. throw new Error(graphicName + ' is not a valid symbol name');
  850. }
  851. var symbolNode = this.nodeFactory(id, "symbol");
  852. var node = this.nodeFactory(null, "polygon");
  853. symbolNode.appendChild(node);
  854. var symbolExtent = new OpenLayers.Bounds(
  855. Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
  856. var points = [];
  857. var x,y;
  858. for (var i=0; i<symbol.length; i=i+2) {
  859. x = symbol[i];
  860. y = symbol[i+1];
  861. symbolExtent.left = Math.min(symbolExtent.left, x);
  862. symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
  863. symbolExtent.right = Math.max(symbolExtent.right, x);
  864. symbolExtent.top = Math.max(symbolExtent.top, y);
  865. points.push(x, ",", y);
  866. }
  867. node.setAttributeNS(null, "points", points.join(" "));
  868. var width = symbolExtent.getWidth();
  869. var height = symbolExtent.getHeight();
  870. // create a viewBox three times as large as the symbol itself,
  871. // to allow for strokeWidth being displayed correctly at the corners.
  872. var viewBox = [symbolExtent.left - width,
  873. symbolExtent.bottom - height, width * 3, height * 3];
  874. symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
  875. this.symbolMetrics[id] = [
  876. Math.max(width, height),
  877. symbolExtent.getCenterLonLat().lon,
  878. symbolExtent.getCenterLonLat().lat
  879. ];
  880. this.defs.appendChild(symbolNode);
  881. return symbolNode;
  882. },
  883. /**
  884. * Method: getFeatureIdFromEvent
  885. *
  886. * Parameters:
  887. * evt - {Object} An <OpenLayers.Event> object
  888. *
  889. * Returns:
  890. * {<OpenLayers.Geometry>} A geometry from an event that
  891. * happened on a layer.
  892. */
  893. getFeatureIdFromEvent: function(evt) {
  894. var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
  895. if(!featureId) {
  896. var target = evt.target;
  897. featureId = target.parentNode && target != this.rendererRoot &&
  898. target.parentNode._featureId;
  899. }
  900. return featureId;
  901. },
  902. CLASS_NAME: "OpenLayers.Renderer.SVG"
  903. });
  904. /**
  905. * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
  906. * {Object}
  907. */
  908. OpenLayers.Renderer.SVG.LABEL_ALIGN = {
  909. "l": "start",
  910. "r": "end",
  911. "b": "bottom",
  912. "t": "hanging"
  913. };
  914. /**
  915. * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
  916. * {Object}
  917. */
  918. OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
  919. // according to
  920. // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
  921. // a baseline-shift of -70% shifts the text exactly from the
  922. // bottom to the top of the baseline, so -35% moves the text to
  923. // the center of the baseline.
  924. "t": "-70%",
  925. "b": "0"
  926. };
  927. /**
  928. * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
  929. * {Object}
  930. */
  931. OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
  932. "t": 0,
  933. "b": -1
  934. };
  935. /**
  936. * Function: OpenLayers.Renderer.SVG.preventDefault
  937. * Used to prevent default events (especially opening images in a new tab on
  938. * ctrl-click) from being executed for externalGraphic symbols
  939. */
  940. OpenLayers.Renderer.SVG.preventDefault = function(e) {
  941. e.preventDefault && e.preventDefault();
  942. };