SVG.js 34 KB

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