Elements.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Renderer.js
  7. */
  8. /**
  9. * Class: OpenLayers.ElementsIndexer
  10. * This class takes care of figuring out which order elements should be
  11. * placed in the DOM based on given indexing methods.
  12. */
  13. OpenLayers.ElementsIndexer = OpenLayers.Class({
  14. /**
  15. * Property: maxZIndex
  16. * {Integer} This is the largest-most z-index value for a node
  17. * contained within the indexer.
  18. */
  19. maxZIndex: null,
  20. /**
  21. * Property: order
  22. * {Array<String>} This is an array of node id's stored in the
  23. * order that they should show up on screen. Id's higher up in the
  24. * array (higher array index) represent nodes with higher z-indeces.
  25. */
  26. order: null,
  27. /**
  28. * Property: indices
  29. * {Object} This is a hash that maps node ids to their z-index value
  30. * stored in the indexer. This is done to make finding a nodes z-index
  31. * value O(1).
  32. */
  33. indices: null,
  34. /**
  35. * Property: compare
  36. * {Function} This is the function used to determine placement of
  37. * of a new node within the indexer. If null, this defaults to to
  38. * the Z_ORDER_DRAWING_ORDER comparison method.
  39. */
  40. compare: null,
  41. /**
  42. * APIMethod: initialize
  43. * Create a new indexer with
  44. *
  45. * Parameters:
  46. * yOrdering - {Boolean} Whether to use y-ordering.
  47. */
  48. initialize: function(yOrdering) {
  49. this.compare = yOrdering ?
  50. OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
  51. OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
  52. this.clear();
  53. },
  54. /**
  55. * APIMethod: insert
  56. * Insert a new node into the indexer. In order to find the correct
  57. * positioning for the node to be inserted, this method uses a binary
  58. * search. This makes inserting O(log(n)).
  59. *
  60. * Parameters:
  61. * newNode - {DOMElement} The new node to be inserted.
  62. *
  63. * Returns
  64. * {DOMElement} the node before which we should insert our newNode, or
  65. * null if newNode can just be appended.
  66. */
  67. insert: function(newNode) {
  68. // If the node is known to the indexer, remove it so we can
  69. // recalculate where it should go.
  70. if (this.exists(newNode)) {
  71. this.remove(newNode);
  72. }
  73. var nodeId = newNode.id;
  74. this.determineZIndex(newNode);
  75. var leftIndex = -1;
  76. var rightIndex = this.order.length;
  77. var middle;
  78. while (rightIndex - leftIndex > 1) {
  79. middle = parseInt((leftIndex + rightIndex) / 2);
  80. var placement = this.compare(this, newNode,
  81. OpenLayers.Util.getElement(this.order[middle]));
  82. if (placement > 0) {
  83. leftIndex = middle;
  84. } else {
  85. rightIndex = middle;
  86. }
  87. }
  88. this.order.splice(rightIndex, 0, nodeId);
  89. this.indices[nodeId] = this.getZIndex(newNode);
  90. // If the new node should be before another in the index
  91. // order, return the node before which we have to insert the new one;
  92. // else, return null to indicate that the new node can be appended.
  93. return this.getNextElement(rightIndex);
  94. },
  95. /**
  96. * APIMethod: remove
  97. *
  98. * Parameters:
  99. * node - {DOMElement} The node to be removed.
  100. */
  101. remove: function(node) {
  102. var nodeId = node.id;
  103. var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
  104. if (arrayIndex >= 0) {
  105. // Remove it from the order array, as well as deleting the node
  106. // from the indeces hash.
  107. this.order.splice(arrayIndex, 1);
  108. delete this.indices[nodeId];
  109. // Reset the maxium z-index based on the last item in the
  110. // order array.
  111. if (this.order.length > 0) {
  112. var lastId = this.order[this.order.length - 1];
  113. this.maxZIndex = this.indices[lastId];
  114. } else {
  115. this.maxZIndex = 0;
  116. }
  117. }
  118. },
  119. /**
  120. * APIMethod: clear
  121. */
  122. clear: function() {
  123. this.order = [];
  124. this.indices = {};
  125. this.maxZIndex = 0;
  126. },
  127. /**
  128. * APIMethod: exists
  129. *
  130. * Parameters:
  131. * node - {DOMElement} The node to test for existence.
  132. *
  133. * Returns:
  134. * {Boolean} Whether or not the node exists in the indexer?
  135. */
  136. exists: function(node) {
  137. return (this.indices[node.id] != null);
  138. },
  139. /**
  140. * APIMethod: getZIndex
  141. * Get the z-index value for the current node from the node data itself.
  142. *
  143. * Parameters:
  144. * node - {DOMElement} The node whose z-index to get.
  145. *
  146. * Returns:
  147. * {Integer} The z-index value for the specified node (from the node
  148. * data itself).
  149. */
  150. getZIndex: function(node) {
  151. return node._style.graphicZIndex;
  152. },
  153. /**
  154. * Method: determineZIndex
  155. * Determine the z-index for the current node if there isn't one,
  156. * and set the maximum value if we've found a new maximum.
  157. *
  158. * Parameters:
  159. * node - {DOMElement}
  160. */
  161. determineZIndex: function(node) {
  162. var zIndex = node._style.graphicZIndex;
  163. // Everything must have a zIndex. If none is specified,
  164. // this means the user *must* (hint: assumption) want this
  165. // node to succomb to drawing order. To enforce drawing order
  166. // over all indexing methods, we'll create a new z-index that's
  167. // greater than any currently in the indexer.
  168. if (zIndex == null) {
  169. zIndex = this.maxZIndex;
  170. node._style.graphicZIndex = zIndex;
  171. } else if (zIndex > this.maxZIndex) {
  172. this.maxZIndex = zIndex;
  173. }
  174. },
  175. /**
  176. * APIMethod: getNextElement
  177. * Get the next element in the order stack.
  178. *
  179. * Parameters:
  180. * index - {Integer} The index of the current node in this.order.
  181. *
  182. * Returns:
  183. * {DOMElement} the node following the index passed in, or
  184. * null.
  185. */
  186. getNextElement: function(index) {
  187. var nextIndex = index + 1;
  188. if (nextIndex < this.order.length) {
  189. var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
  190. if (nextElement == undefined) {
  191. nextElement = this.getNextElement(nextIndex);
  192. }
  193. return nextElement;
  194. } else {
  195. return null;
  196. }
  197. },
  198. CLASS_NAME: "OpenLayers.ElementsIndexer"
  199. });
  200. /**
  201. * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
  202. * These are the compare methods for figuring out where a new node should be
  203. * placed within the indexer. These methods are very similar to general
  204. * sorting methods in that they return -1, 0, and 1 to specify the
  205. * direction in which new nodes fall in the ordering.
  206. */
  207. OpenLayers.ElementsIndexer.IndexingMethods = {
  208. /**
  209. * Method: Z_ORDER
  210. * This compare method is used by other comparison methods.
  211. * It can be used individually for ordering, but is not recommended,
  212. * because it doesn't subscribe to drawing order.
  213. *
  214. * Parameters:
  215. * indexer - {<OpenLayers.ElementsIndexer>}
  216. * newNode - {DOMElement}
  217. * nextNode - {DOMElement}
  218. *
  219. * Returns:
  220. * {Integer}
  221. */
  222. Z_ORDER: function(indexer, newNode, nextNode) {
  223. var newZIndex = indexer.getZIndex(newNode);
  224. var returnVal = 0;
  225. if (nextNode) {
  226. var nextZIndex = indexer.getZIndex(nextNode);
  227. returnVal = newZIndex - nextZIndex;
  228. }
  229. return returnVal;
  230. },
  231. /**
  232. * APIMethod: Z_ORDER_DRAWING_ORDER
  233. * This method orders nodes by their z-index, but does so in a way
  234. * that, if there are other nodes with the same z-index, the newest
  235. * drawn will be the front most within that z-index. This is the
  236. * default indexing method.
  237. *
  238. * Parameters:
  239. * indexer - {<OpenLayers.ElementsIndexer>}
  240. * newNode - {DOMElement}
  241. * nextNode - {DOMElement}
  242. *
  243. * Returns:
  244. * {Integer}
  245. */
  246. Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
  247. var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
  248. indexer,
  249. newNode,
  250. nextNode
  251. );
  252. // Make Z_ORDER subscribe to drawing order by pushing it above
  253. // all of the other nodes with the same z-index.
  254. if (nextNode && returnVal == 0) {
  255. returnVal = 1;
  256. }
  257. return returnVal;
  258. },
  259. /**
  260. * APIMethod: Z_ORDER_Y_ORDER
  261. * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
  262. * best describes which ordering methods have precedence (though, the
  263. * name would be too long). This method orders nodes by their z-index,
  264. * but does so in a way that, if there are other nodes with the same
  265. * z-index, the nodes with the lower y position will be "closer" than
  266. * those with a higher y position. If two nodes have the exact same y
  267. * position, however, then this method will revert to using drawing
  268. * order to decide placement.
  269. *
  270. * Parameters:
  271. * indexer - {<OpenLayers.ElementsIndexer>}
  272. * newNode - {DOMElement}
  273. * nextNode - {DOMElement}
  274. *
  275. * Returns:
  276. * {Integer}
  277. */
  278. Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
  279. var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
  280. indexer,
  281. newNode,
  282. nextNode
  283. );
  284. if (nextNode && returnVal === 0) {
  285. var result = nextNode._boundsBottom - newNode._boundsBottom;
  286. returnVal = (result === 0) ? 1 : result;
  287. }
  288. return returnVal;
  289. }
  290. };
  291. /**
  292. * Class: OpenLayers.Renderer.Elements
  293. * This is another virtual class in that it should never be instantiated by
  294. * itself as a Renderer. It exists because there is *tons* of shared
  295. * functionality between different vector libraries which use nodes/elements
  296. * as a base for rendering vectors.
  297. *
  298. * The highlevel bits of code that are implemented here are the adding and
  299. * removing of geometries, which is essentially the same for any
  300. * element-based renderer. The details of creating each node and drawing the
  301. * paths are of course different, but the machinery is the same.
  302. *
  303. * Inherits:
  304. * - <OpenLayers.Renderer>
  305. */
  306. OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
  307. /**
  308. * Property: rendererRoot
  309. * {DOMElement}
  310. */
  311. rendererRoot: null,
  312. /**
  313. * Property: root
  314. * {DOMElement}
  315. */
  316. root: null,
  317. /**
  318. * Property: vectorRoot
  319. * {DOMElement}
  320. */
  321. vectorRoot: null,
  322. /**
  323. * Property: textRoot
  324. * {DOMElement}
  325. */
  326. textRoot: null,
  327. /**
  328. * Property: xmlns
  329. * {String}
  330. */
  331. xmlns: null,
  332. /**
  333. * Property: xOffset
  334. * {Number} Offset to apply to the renderer viewport translation in x
  335. * direction. If the renderer extent's center is on the right of the
  336. * dateline (i.e. exceeds the world bounds), we shift the viewport to the
  337. * left by one world width. This avoids that features disappear from the
  338. * map viewport. Because our dateline handling logic in other places
  339. * ensures that extents crossing the dateline always have a center
  340. * exceeding the world bounds on the left, we need this offset to make sure
  341. * that the same is true for the renderer extent in pixel space as well.
  342. */
  343. xOffset: 0,
  344. /**
  345. * Property: rightOfDateLine
  346. * {Boolean} Keeps track of the location of the map extent relative to the
  347. * date line. The <setExtent> method compares this value (which is the one
  348. * from the previous <setExtent> call) with the current position of the map
  349. * extent relative to the date line and updates the xOffset when the extent
  350. * has moved from one side of the date line to the other.
  351. */
  352. /**
  353. * Property: Indexer
  354. * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
  355. * created upon initialization if the zIndexing or yOrdering options
  356. * passed to this renderer's constructor are set to true.
  357. */
  358. indexer: null,
  359. /**
  360. * Constant: BACKGROUND_ID_SUFFIX
  361. * {String}
  362. */
  363. BACKGROUND_ID_SUFFIX: "_background",
  364. /**
  365. * Constant: LABEL_ID_SUFFIX
  366. * {String}
  367. */
  368. LABEL_ID_SUFFIX: "_label",
  369. /**
  370. * Constant: LABEL_OUTLINE_SUFFIX
  371. * {String}
  372. */
  373. LABEL_OUTLINE_SUFFIX: "_outline",
  374. /**
  375. * Constructor: OpenLayers.Renderer.Elements
  376. *
  377. * Parameters:
  378. * containerID - {String}
  379. * options - {Object} options for this renderer.
  380. *
  381. * Supported options are:
  382. * yOrdering - {Boolean} Whether to use y-ordering
  383. * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
  384. * if yOrdering is set to true.
  385. */
  386. initialize: function(containerID, options) {
  387. OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
  388. this.rendererRoot = this.createRenderRoot();
  389. this.root = this.createRoot("_root");
  390. this.vectorRoot = this.createRoot("_vroot");
  391. this.textRoot = this.createRoot("_troot");
  392. this.root.appendChild(this.vectorRoot);
  393. this.root.appendChild(this.textRoot);
  394. this.rendererRoot.appendChild(this.root);
  395. this.container.appendChild(this.rendererRoot);
  396. if(options && (options.zIndexing || options.yOrdering)) {
  397. this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
  398. }
  399. },
  400. /**
  401. * Method: destroy
  402. */
  403. destroy: function() {
  404. this.clear();
  405. this.rendererRoot = null;
  406. this.root = null;
  407. this.xmlns = null;
  408. OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
  409. },
  410. /**
  411. * Method: clear
  412. * Remove all the elements from the root
  413. */
  414. clear: function() {
  415. var child;
  416. var root = this.vectorRoot;
  417. if (root) {
  418. while (child = root.firstChild) {
  419. root.removeChild(child);
  420. }
  421. }
  422. root = this.textRoot;
  423. if (root) {
  424. while (child = root.firstChild) {
  425. root.removeChild(child);
  426. }
  427. }
  428. if (this.indexer) {
  429. this.indexer.clear();
  430. }
  431. },
  432. /**
  433. * Method: setExtent
  434. * Set the visible part of the layer.
  435. *
  436. * Parameters:
  437. * extent - {<OpenLayers.Bounds>}
  438. * resolutionChanged - {Boolean}
  439. *
  440. * Returns:
  441. * {Boolean} true to notify the layer that the new extent does not exceed
  442. * the coordinate range, and the features will not need to be redrawn.
  443. * False otherwise.
  444. */
  445. setExtent: function(extent, resolutionChanged) {
  446. var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
  447. var resolution = this.getResolution();
  448. if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
  449. var rightOfDateLine,
  450. ratio = extent.getWidth() / this.map.getExtent().getWidth(),
  451. extent = extent.scale(1 / ratio),
  452. world = this.map.getMaxExtent();
  453. if (world.right > extent.left && world.right < extent.right) {
  454. rightOfDateLine = true;
  455. } else if (world.left > extent.left && world.left < extent.right) {
  456. rightOfDateLine = false;
  457. }
  458. if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
  459. coordSysUnchanged = false;
  460. this.xOffset = rightOfDateLine === true ?
  461. world.getWidth() / resolution : 0;
  462. }
  463. this.rightOfDateLine = rightOfDateLine;
  464. }
  465. return coordSysUnchanged;
  466. },
  467. /**
  468. * Method: getNodeType
  469. * This function is in charge of asking the specific renderer which type
  470. * of node to create for the given geometry and style. All geometries
  471. * in an Elements-based renderer consist of one node and some
  472. * attributes. We have the nodeFactory() function which creates a node
  473. * for us, but it takes a 'type' as input, and that is precisely what
  474. * this function tells us.
  475. *
  476. * Parameters:
  477. * geometry - {<OpenLayers.Geometry>}
  478. * style - {Object}
  479. *
  480. * Returns:
  481. * {String} The corresponding node type for the specified geometry
  482. */
  483. getNodeType: function(geometry, style) { },
  484. /**
  485. * Method: drawGeometry
  486. * Draw the geometry, creating new nodes, setting paths, setting style,
  487. * setting featureId on the node. This method should only be called
  488. * by the renderer itself.
  489. *
  490. * Parameters:
  491. * geometry - {<OpenLayers.Geometry>}
  492. * style - {Object}
  493. * featureId - {String}
  494. *
  495. * Returns:
  496. * {Boolean} true if the geometry has been drawn completely; null if
  497. * incomplete; false otherwise
  498. */
  499. drawGeometry: function(geometry, style, featureId) {
  500. var className = geometry.CLASS_NAME;
  501. var rendered = true;
  502. if ((className == "OpenLayers.Geometry.Collection") ||
  503. (className == "OpenLayers.Geometry.MultiPoint") ||
  504. (className == "OpenLayers.Geometry.MultiLineString") ||
  505. (className == "OpenLayers.Geometry.MultiPolygon")) {
  506. for (var i = 0, len=geometry.components.length; i<len; i++) {
  507. rendered = this.drawGeometry(
  508. geometry.components[i], style, featureId) && rendered;
  509. }
  510. return rendered;
  511. }
  512. rendered = false;
  513. var removeBackground = false;
  514. if (style.display != "none") {
  515. if (style.backgroundGraphic) {
  516. this.redrawBackgroundNode(geometry.id, geometry, style,
  517. featureId);
  518. } else {
  519. removeBackground = true;
  520. }
  521. rendered = this.redrawNode(geometry.id, geometry, style,
  522. featureId);
  523. }
  524. if (rendered == false) {
  525. var node = document.getElementById(geometry.id);
  526. if (node) {
  527. if (node._style.backgroundGraphic) {
  528. removeBackground = true;
  529. }
  530. node.parentNode.removeChild(node);
  531. }
  532. }
  533. if (removeBackground) {
  534. var node = document.getElementById(
  535. geometry.id + this.BACKGROUND_ID_SUFFIX);
  536. if (node) {
  537. node.parentNode.removeChild(node);
  538. }
  539. }
  540. return rendered;
  541. },
  542. /**
  543. * Method: redrawNode
  544. *
  545. * Parameters:
  546. * id - {String}
  547. * geometry - {<OpenLayers.Geometry>}
  548. * style - {Object}
  549. * featureId - {String}
  550. *
  551. * Returns:
  552. * {Boolean} true if the complete geometry could be drawn, null if parts of
  553. * the geometry could not be drawn, false otherwise
  554. */
  555. redrawNode: function(id, geometry, style, featureId) {
  556. style = this.applyDefaultSymbolizer(style);
  557. // Get the node if it's already on the map.
  558. var node = this.nodeFactory(id, this.getNodeType(geometry, style));
  559. // Set the data for the node, then draw it.
  560. node._featureId = featureId;
  561. node._boundsBottom = geometry.getBounds().bottom;
  562. node._geometryClass = geometry.CLASS_NAME;
  563. node._style = style;
  564. var drawResult = this.drawGeometryNode(node, geometry, style);
  565. if(drawResult === false) {
  566. return false;
  567. }
  568. node = drawResult.node;
  569. // Insert the node into the indexer so it can show us where to
  570. // place it. Note that this operation is O(log(n)). If there's a
  571. // performance problem (when dragging, for instance) this is
  572. // likely where it would be.
  573. if (this.indexer) {
  574. var insert = this.indexer.insert(node);
  575. if (insert) {
  576. this.vectorRoot.insertBefore(node, insert);
  577. } else {
  578. this.vectorRoot.appendChild(node);
  579. }
  580. } else {
  581. // if there's no indexer, simply append the node to root,
  582. // but only if the node is a new one
  583. if (node.parentNode !== this.vectorRoot){
  584. this.vectorRoot.appendChild(node);
  585. }
  586. }
  587. this.postDraw(node);
  588. return drawResult.complete;
  589. },
  590. /**
  591. * Method: redrawBackgroundNode
  592. * Redraws the node using special 'background' style properties. Basically
  593. * just calls redrawNode(), but instead of directly using the
  594. * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
  595. * 'graphicZIndex' properties directly from the specified 'style'
  596. * parameter, we create a new style object and set those properties
  597. * from the corresponding 'background'-prefixed properties from
  598. * specified 'style' parameter.
  599. *
  600. * Parameters:
  601. * id - {String}
  602. * geometry - {<OpenLayers.Geometry>}
  603. * style - {Object}
  604. * featureId - {String}
  605. *
  606. * Returns:
  607. * {Boolean} true if the complete geometry could be drawn, null if parts of
  608. * the geometry could not be drawn, false otherwise
  609. */
  610. redrawBackgroundNode: function(id, geometry, style, featureId) {
  611. var backgroundStyle = OpenLayers.Util.extend({}, style);
  612. // Set regular style attributes to apply to the background styles.
  613. backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
  614. backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
  615. backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
  616. backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
  617. backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
  618. backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
  619. // Erase background styles.
  620. backgroundStyle.backgroundGraphic = null;
  621. backgroundStyle.backgroundXOffset = null;
  622. backgroundStyle.backgroundYOffset = null;
  623. backgroundStyle.backgroundGraphicZIndex = null;
  624. return this.redrawNode(
  625. id + this.BACKGROUND_ID_SUFFIX,
  626. geometry,
  627. backgroundStyle,
  628. null
  629. );
  630. },
  631. /**
  632. * Method: drawGeometryNode
  633. * Given a node, draw a geometry on the specified layer.
  634. * node and geometry are required arguments, style is optional.
  635. * This method is only called by the render itself.
  636. *
  637. * Parameters:
  638. * node - {DOMElement}
  639. * geometry - {<OpenLayers.Geometry>}
  640. * style - {Object}
  641. *
  642. * Returns:
  643. * {Object} a hash with properties "node" (the drawn node) and "complete"
  644. * (null if parts of the geometry could not be drawn, false if nothing
  645. * could be drawn)
  646. */
  647. drawGeometryNode: function(node, geometry, style) {
  648. style = style || node._style;
  649. var options = {
  650. 'isFilled': style.fill === undefined ?
  651. true :
  652. style.fill,
  653. 'isStroked': style.stroke === undefined ?
  654. !!style.strokeWidth :
  655. style.stroke
  656. };
  657. var drawn;
  658. switch (geometry.CLASS_NAME) {
  659. case "OpenLayers.Geometry.Point":
  660. if(style.graphic === false) {
  661. options.isFilled = false;
  662. options.isStroked = false;
  663. }
  664. drawn = this.drawPoint(node, geometry);
  665. break;
  666. case "OpenLayers.Geometry.LineString":
  667. options.isFilled = false;
  668. drawn = this.drawLineString(node, geometry);
  669. break;
  670. case "OpenLayers.Geometry.LinearRing":
  671. drawn = this.drawLinearRing(node, geometry);
  672. break;
  673. case "OpenLayers.Geometry.Polygon":
  674. drawn = this.drawPolygon(node, geometry);
  675. break;
  676. case "OpenLayers.Geometry.Rectangle":
  677. drawn = this.drawRectangle(node, geometry);
  678. break;
  679. default:
  680. break;
  681. }
  682. node._options = options;
  683. //set style
  684. //TBD simplify this
  685. if (drawn != false) {
  686. return {
  687. node: this.setStyle(node, style, options, geometry),
  688. complete: drawn
  689. };
  690. } else {
  691. return false;
  692. }
  693. },
  694. /**
  695. * Method: postDraw
  696. * Things that have do be done after the geometry node is appended
  697. * to its parent node. To be overridden by subclasses.
  698. *
  699. * Parameters:
  700. * node - {DOMElement}
  701. */
  702. postDraw: function(node) {},
  703. /**
  704. * Method: drawPoint
  705. * Virtual function for drawing Point Geometry.
  706. * Should be implemented by subclasses.
  707. * This method is only called by the renderer itself.
  708. *
  709. * Parameters:
  710. * node - {DOMElement}
  711. * geometry - {<OpenLayers.Geometry>}
  712. *
  713. * Returns:
  714. * {DOMElement} or false if the renderer could not draw the point
  715. */
  716. drawPoint: function(node, geometry) {},
  717. /**
  718. * Method: drawLineString
  719. * Virtual function for drawing LineString Geometry.
  720. * Should be implemented by subclasses.
  721. * This method is only called by the renderer itself.
  722. *
  723. * Parameters:
  724. * node - {DOMElement}
  725. * geometry - {<OpenLayers.Geometry>}
  726. *
  727. * Returns:
  728. * {DOMElement} or null if the renderer could not draw all components of
  729. * the linestring, or false if nothing could be drawn
  730. */
  731. drawLineString: function(node, geometry) {},
  732. /**
  733. * Method: drawLinearRing
  734. * Virtual function for drawing LinearRing Geometry.
  735. * Should be implemented by subclasses.
  736. * This method is only called by the renderer itself.
  737. *
  738. * Parameters:
  739. * node - {DOMElement}
  740. * geometry - {<OpenLayers.Geometry>}
  741. *
  742. * Returns:
  743. * {DOMElement} or null if the renderer could not draw all components
  744. * of the linear ring, or false if nothing could be drawn
  745. */
  746. drawLinearRing: function(node, geometry) {},
  747. /**
  748. * Method: drawPolygon
  749. * Virtual function for drawing Polygon Geometry.
  750. * Should be implemented by subclasses.
  751. * This method is only called by the renderer itself.
  752. *
  753. * Parameters:
  754. * node - {DOMElement}
  755. * geometry - {<OpenLayers.Geometry>}
  756. *
  757. * Returns:
  758. * {DOMElement} or null if the renderer could not draw all components
  759. * of the polygon, or false if nothing could be drawn
  760. */
  761. drawPolygon: function(node, geometry) {},
  762. /**
  763. * Method: drawRectangle
  764. * Virtual function for drawing Rectangle Geometry.
  765. * Should be implemented by subclasses.
  766. * This method is only called by the renderer itself.
  767. *
  768. * Parameters:
  769. * node - {DOMElement}
  770. * geometry - {<OpenLayers.Geometry>}
  771. *
  772. * Returns:
  773. * {DOMElement} or false if the renderer could not draw the rectangle
  774. */
  775. drawRectangle: function(node, geometry) {},
  776. /**
  777. * Method: drawCircle
  778. * Virtual function for drawing Circle Geometry.
  779. * Should be implemented by subclasses.
  780. * This method is only called by the renderer itself.
  781. *
  782. * Parameters:
  783. * node - {DOMElement}
  784. * geometry - {<OpenLayers.Geometry>}
  785. *
  786. * Returns:
  787. * {DOMElement} or false if the renderer could not draw the circle
  788. */
  789. drawCircle: function(node, geometry) {},
  790. /**
  791. * Method: removeText
  792. * Removes a label
  793. *
  794. * Parameters:
  795. * featureId - {String}
  796. */
  797. removeText: function(featureId) {
  798. var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
  799. if (label) {
  800. this.textRoot.removeChild(label);
  801. }
  802. var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
  803. if (outline) {
  804. this.textRoot.removeChild(outline);
  805. }
  806. },
  807. /**
  808. * Method: getFeatureIdFromEvent
  809. *
  810. * Parameters:
  811. * evt - {Object} An <OpenLayers.Event> object
  812. *
  813. * Returns:
  814. * {String} A feature id or undefined.
  815. */
  816. getFeatureIdFromEvent: function(evt) {
  817. var target = evt.target;
  818. var useElement = target && target.correspondingUseElement;
  819. var node = useElement ? useElement : (target || evt.srcElement);
  820. return node._featureId;
  821. },
  822. /**
  823. * Method: eraseGeometry
  824. * Erase a geometry from the renderer. In the case of a multi-geometry,
  825. * we cycle through and recurse on ourselves. Otherwise, we look for a
  826. * node with the geometry.id, destroy its geometry, and remove it from
  827. * the DOM.
  828. *
  829. * Parameters:
  830. * geometry - {<OpenLayers.Geometry>}
  831. * featureId - {String}
  832. */
  833. eraseGeometry: function(geometry, featureId) {
  834. if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
  835. (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
  836. (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
  837. (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
  838. for (var i=0, len=geometry.components.length; i<len; i++) {
  839. this.eraseGeometry(geometry.components[i], featureId);
  840. }
  841. } else {
  842. var element = OpenLayers.Util.getElement(geometry.id);
  843. if (element && element.parentNode) {
  844. if (element.geometry) {
  845. element.geometry.destroy();
  846. element.geometry = null;
  847. }
  848. element.parentNode.removeChild(element);
  849. if (this.indexer) {
  850. this.indexer.remove(element);
  851. }
  852. if (element._style.backgroundGraphic) {
  853. var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
  854. var bElem = OpenLayers.Util.getElement(backgroundId);
  855. if (bElem && bElem.parentNode) {
  856. // No need to destroy the geometry since the element and the background
  857. // node share the same geometry.
  858. bElem.parentNode.removeChild(bElem);
  859. }
  860. }
  861. }
  862. }
  863. },
  864. /**
  865. * Method: nodeFactory
  866. * Create new node of the specified type, with the (optional) specified id.
  867. *
  868. * If node already exists with same ID and a different type, we remove it
  869. * and then call ourselves again to recreate it.
  870. *
  871. * Parameters:
  872. * id - {String}
  873. * type - {String} type Kind of node to draw.
  874. *
  875. * Returns:
  876. * {DOMElement} A new node of the given type and id.
  877. */
  878. nodeFactory: function(id, type) {
  879. var node = OpenLayers.Util.getElement(id);
  880. if (node) {
  881. if (!this.nodeTypeCompare(node, type)) {
  882. node.parentNode.removeChild(node);
  883. node = this.nodeFactory(id, type);
  884. }
  885. } else {
  886. node = this.createNode(type, id);
  887. }
  888. return node;
  889. },
  890. /**
  891. * Method: nodeTypeCompare
  892. *
  893. * Parameters:
  894. * node - {DOMElement}
  895. * type - {String} Kind of node
  896. *
  897. * Returns:
  898. * {Boolean} Whether or not the specified node is of the specified type
  899. * This function must be overridden by subclasses.
  900. */
  901. nodeTypeCompare: function(node, type) {},
  902. /**
  903. * Method: createNode
  904. *
  905. * Parameters:
  906. * type - {String} Kind of node to draw.
  907. * id - {String} Id for node.
  908. *
  909. * Returns:
  910. * {DOMElement} A new node of the given type and id.
  911. * This function must be overridden by subclasses.
  912. */
  913. createNode: function(type, id) {},
  914. /**
  915. * Method: moveRoot
  916. * moves this renderer's root to a different renderer.
  917. *
  918. * Parameters:
  919. * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
  920. */
  921. moveRoot: function(renderer) {
  922. var root = this.root;
  923. if(renderer.root.parentNode == this.rendererRoot) {
  924. root = renderer.root;
  925. }
  926. root.parentNode.removeChild(root);
  927. renderer.rendererRoot.appendChild(root);
  928. },
  929. /**
  930. * Method: getRenderLayerId
  931. * Gets the layer that this renderer's output appears on. If moveRoot was
  932. * used, this will be different from the id of the layer containing the
  933. * features rendered by this renderer.
  934. *
  935. * Returns:
  936. * {String} the id of the output layer.
  937. */
  938. getRenderLayerId: function() {
  939. return this.root.parentNode.parentNode.id;
  940. },
  941. /**
  942. * Method: isComplexSymbol
  943. * Determines if a symbol cannot be rendered using drawCircle
  944. *
  945. * Parameters:
  946. * graphicName - {String}
  947. *
  948. * Returns
  949. * {Boolean} true if the symbol is complex, false if not
  950. */
  951. isComplexSymbol: function(graphicName) {
  952. return (graphicName != "circle") && !!graphicName;
  953. },
  954. CLASS_NAME: "OpenLayers.Renderer.Elements"
  955. });