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