1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Layer.js
7 * @requires OpenLayers/Renderer.js
8 * @requires OpenLayers/StyleMap.js
9 * @requires OpenLayers/Feature/Vector.js
10 * @requires OpenLayers/Console.js
14 * Class: OpenLayers.Layer.Vector
15 * Instances of OpenLayers.Layer.Vector are used to render vector data from
16 * a variety of sources. Create a new vector layer with the
17 * <OpenLayers.Layer.Vector> constructor.
20 * - <OpenLayers.Layer>
22 OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
25 * Constant: EVENT_TYPES
26 * {Array(String)} Supported application event types. Register a listener
27 * for a particular event with the following syntax:
29 * layer.events.register(type, obj, listener);
32 * Listeners will be called with a reference to an event object. The
33 * properties of this event depends on exactly what happened.
35 * All event objects have at least the following properties:
36 * object - {Object} A reference to layer.events.object.
37 * element - {DOMElement} A reference to layer.events.element.
39 * Supported map event types (in addition to those from <OpenLayers.Layer>):
40 * beforefeatureadded - Triggered before a feature is added. Listeners
41 * will receive an object with a *feature* property referencing the
42 * feature to be added. To stop the feature from being added, a
43 * listener should return false.
44 * beforefeaturesadded - Triggered before an array of features is added.
45 * Listeners will receive an object with a *features* property
46 * referencing the feature to be added. To stop the features from
47 * being added, a listener should return false.
48 * featureadded - Triggered after a feature is added. The event
49 * object passed to listeners will have a *feature* property with a
50 * reference to the added feature.
51 * featuresadded - Triggered after features are added. The event
52 * object passed to listeners will have a *features* property with a
53 * reference to an array of added features.
54 * beforefeatureremoved - Triggered before a feature is removed. Listeners
55 * will receive an object with a *feature* property referencing the
56 * feature to be removed.
57 * featureremoved - Triggerd after a feature is removed. The event
58 * object passed to listeners will have a *feature* property with a
59 * reference to the removed feature.
60 * featuresremoved - Triggered after features are removed. The event
61 * object passed to listeners will have a *features* property with a
62 * reference to an array of removed features.
63 * featureselected - Triggered after a feature is selected. Listeners
64 * will receive an object with a *feature* property referencing the
66 * featureunselected - Triggered after a feature is unselected.
67 * Listeners will receive an object with a *feature* property
68 * referencing the unselected feature.
69 * beforefeaturemodified - Triggered when a feature is selected to
70 * be modified. Listeners will receive an object with a *feature*
71 * property referencing the selected feature.
72 * featuremodified - Triggered when a feature has been modified.
73 * Listeners will receive an object with a *feature* property referencing
74 * the modified feature.
75 * afterfeaturemodified - Triggered when a feature is finished being modified.
76 * Listeners will receive an object with a *feature* property referencing
77 * the modified feature.
78 * vertexmodified - Triggered when a vertex within any feature geometry
79 * has been modified. Listeners will receive an object with a
80 * *feature* property referencing the modified feature, a *vertex*
81 * property referencing the vertex modified (always a point geometry),
82 * and a *pixel* property referencing the pixel location of the
84 * sketchstarted - Triggered when a feature sketch bound for this layer
85 * is started. Listeners will receive an object with a *feature*
86 * property referencing the new sketch feature and a *vertex* property
87 * referencing the creation point.
88 * sketchmodified - Triggered when a feature sketch bound for this layer
89 * is modified. Listeners will receive an object with a *vertex*
90 * property referencing the modified vertex and a *feature* property
91 * referencing the sketch feature.
92 * sketchcomplete - Triggered when a feature sketch bound for this layer
93 * is complete. Listeners will receive an object with a *feature*
94 * property referencing the sketch feature. By returning false, a
95 * listener can stop the sketch feature from being added to the layer.
96 * refresh - Triggered when something wants a strategy to ask the protocol
97 * for a new set of features.
99 EVENT_TYPES: ["beforefeatureadded", "beforefeaturesadded",
100 "featureadded", "featuresadded",
101 "beforefeatureremoved", "featureremoved", "featuresremoved",
102 "beforefeatureselected", "featureselected", "featureunselected",
103 "beforefeaturemodified", "featuremodified", "afterfeaturemodified",
104 "vertexmodified", "sketchstarted", "sketchmodified",
105 "sketchcomplete", "refresh"],
108 * APIProperty: isBaseLayer
109 * {Boolean} The layer is a base layer. Default is true. Set this property
110 * in the layer options
115 * APIProperty: isFixed
116 * {Boolean} Whether the layer remains in one place while dragging the
122 * APIProperty: isVector
123 * {Boolean} Whether the layer is a vector layer.
128 * APIProperty: features
129 * {Array(<OpenLayers.Feature.Vector>)}
134 * Property: selectedFeatures
135 * {Array(<OpenLayers.Feature.Vector>)}
137 selectedFeatures: null,
140 * Property: unrenderedFeatures
141 * {Object} hash of features, keyed by feature.id, that the renderer
144 unrenderedFeatures: null,
147 * APIProperty: reportError
148 * {Boolean} report friendly error message when loading of renderer
155 * {Object} Default style for the layer
161 * {<OpenLayers.StyleMap>}
166 * Property: strategies
167 * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
173 * {<OpenLayers.Protocol>} Optional protocol for the layer.
178 * Property: renderers
179 * {Array(String)} List of supported Renderer classes. Add to this list to
180 * add support for additional renderers. This list is ordered:
181 * the first renderer which returns true for the 'supported()'
182 * method will be used, if not defined in the 'renderer' option.
184 renderers: ['SVG', 'VML', 'Canvas'],
188 * {<OpenLayers.Renderer>}
193 * APIProperty: rendererOptions
194 * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
197 rendererOptions: null,
200 * APIProperty: geometryType
201 * {String} geometryType allows you to limit the types of geometries this
202 * layer supports. This should be set to something like
203 * "OpenLayers.Geometry.Point" to limit types.
209 * {Boolean} Whether the Vector Layer features have been drawn yet.
214 * Constructor: OpenLayers.Layer.Vector
215 * Create a new vector layer
218 * name - {String} A name for the layer
219 * options - {Object} Optional object with non-default properties to set on
223 * {<OpenLayers.Layer.Vector>} A new vector layer
225 initialize: function(name, options) {
227 // concatenate events specific to vector with those from the base
229 OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(
230 OpenLayers.Layer.prototype.EVENT_TYPES
233 OpenLayers.Layer.prototype.initialize.apply(this, arguments);
235 // allow user-set renderer, otherwise assign one
236 if (!this.renderer || !this.renderer.supported()) {
237 this.assignRenderer();
240 // if no valid renderer found, display error
241 if (!this.renderer || !this.renderer.supported()) {
242 this.renderer = null;
246 if (!this.styleMap) {
247 this.styleMap = new OpenLayers.StyleMap();
251 this.selectedFeatures = [];
252 this.unrenderedFeatures = {};
254 // Allow for custom layer behavior
256 for(var i=0, len=this.strategies.length; i<len; i++) {
257 this.strategies[i].setLayer(this);
267 destroy: function() {
268 if (this.strategies) {
269 var strategy, i, len;
270 for(i=0, len=this.strategies.length; i<len; i++) {
271 strategy = this.strategies[i];
272 if(strategy.autoDestroy) {
276 this.strategies = null;
279 if(this.protocol.autoDestroy) {
280 this.protocol.destroy();
282 this.protocol = null;
284 this.destroyFeatures();
285 this.features = null;
286 this.selectedFeatures = null;
287 this.unrenderedFeatures = null;
289 this.renderer.destroy();
291 this.renderer = null;
292 this.geometryType = null;
294 OpenLayers.Layer.prototype.destroy.apply(this, arguments);
299 * Ask the layer to request features again and redraw them. Triggers
300 * the refresh event if the layer is in range and visible.
303 * obj - {Object} Optional object with properties for any listener of
306 refresh: function(obj) {
307 if(this.calculateInRange() && this.visibility) {
308 this.events.triggerEvent("refresh", obj);
313 * Method: assignRenderer
314 * Iterates through the available renderer implementations and selects
315 * and assigns the first one whose "supported()" function returns true.
317 assignRenderer: function() {
318 for (var i=0, len=this.renderers.length; i<len; i++) {
319 var rendererClass = OpenLayers.Renderer[this.renderers[i]];
320 if (rendererClass && rendererClass.prototype.supported()) {
321 this.renderer = new rendererClass(this.div,
322 this.rendererOptions);
329 * Method: displayError
330 * Let the user know their browser isn't supported.
332 displayError: function() {
333 if (this.reportError) {
334 OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
335 {'renderers':this.renderers.join("\n")}));
341 * The layer has been added to the map.
343 * If there is no renderer set, the layer can't be used. Remove it.
344 * Otherwise, give the renderer a reference to the map and set its size.
347 * map - {<OpenLayers.Map>}
349 setMap: function(map) {
350 OpenLayers.Layer.prototype.setMap.apply(this, arguments);
352 if (!this.renderer) {
353 this.map.removeLayer(this);
355 this.renderer.map = this.map;
356 this.renderer.setSize(this.map.getSize());
362 * Called at the end of the map.addLayer sequence. At this point, the map
363 * will have a base layer. Any autoActivate strategies will be
366 afterAdd: function() {
367 if(this.strategies) {
368 var strategy, i, len;
369 for(i=0, len=this.strategies.length; i<len; i++) {
370 strategy = this.strategies[i];
371 if(strategy.autoActivate) {
380 * The layer has been removed from the map.
383 * map - {<OpenLayers.Map>}
385 removeMap: function(map) {
386 if(this.strategies) {
387 var strategy, i, len;
388 for(i=0, len=this.strategies.length; i<len; i++) {
389 strategy = this.strategies[i];
390 if(strategy.autoActivate) {
391 strategy.deactivate();
398 * Method: onMapResize
399 * Notify the renderer of the change in size.
402 onMapResize: function() {
403 OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
404 this.renderer.setSize(this.map.getSize());
409 * Reset the vector layer's div so that it once again is lined up with
410 * the map. Notify the renderer of the change of extent, and in the
411 * case of a change of zoom level (resolution), have the
412 * renderer redraw features.
414 * If the layer has not yet been drawn, cycle through the layer's
415 * features and draw each one.
418 * bounds - {<OpenLayers.Bounds>}
419 * zoomChanged - {Boolean}
420 * dragging - {Boolean}
422 moveTo: function(bounds, zoomChanged, dragging) {
423 OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
425 var coordSysUnchanged = true;
428 this.renderer.root.style.visibility = "hidden";
430 this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px";
431 this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px";
432 var extent = this.map.getExtent();
433 coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
435 this.renderer.root.style.visibility = "visible";
437 // Force a reflow on gecko based browsers to prevent jump/flicker.
438 // This seems to happen on only certain configurations; it was originally
439 // noticed in FF 2.0 and Linux.
440 if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {
441 this.div.scrollLeft = this.div.scrollLeft;
444 if(!zoomChanged && coordSysUnchanged) {
445 for(var i in this.unrenderedFeatures) {
446 var feature = this.unrenderedFeatures[i];
447 this.drawFeature(feature);
452 if (!this.drawn || zoomChanged || !coordSysUnchanged) {
455 for(var i=0, len=this.features.length; i<len; i++) {
456 this.renderer.locked = (i !== (len - 1));
457 feature = this.features[i];
458 this.drawFeature(feature);
465 * Hide or show the Layer
468 * display - {Boolean}
470 display: function(display) {
471 OpenLayers.Layer.prototype.display.apply(this, arguments);
472 // we need to set the display style of the root in case it is attached
473 // to a foreign layer
474 var currentDisplay = this.div.style.display;
475 if(currentDisplay != this.renderer.root.style.display) {
476 this.renderer.root.style.display = currentDisplay;
481 * APIMethod: addFeatures
482 * Add Features to the layer.
485 * features - {Array(<OpenLayers.Feature.Vector>)}
488 addFeatures: function(features, options) {
489 if (!(features instanceof Array)) {
490 features = [features];
493 var notify = !options || !options.silent;
495 var event = {features: features};
496 var ret = this.events.triggerEvent("beforefeaturesadded", event);
500 features = event.features;
504 for (var i=0, len=features.length; i<len; i++) {
505 if (i != (features.length - 1)) {
506 this.renderer.locked = true;
508 this.renderer.locked = false;
510 var feature = features[i];
512 if (this.geometryType &&
513 !(feature.geometry instanceof this.geometryType)) {
514 var throwStr = OpenLayers.i18n('componentShouldBe',
515 {'geomType':this.geometryType.prototype.CLASS_NAME});
519 this.features.push(feature);
521 //give feature reference to its layer
522 feature.layer = this;
524 if (!feature.style && this.style) {
525 feature.style = OpenLayers.Util.extend({}, this.style);
529 if(this.events.triggerEvent("beforefeatureadded",
530 {feature: feature}) === false) {
533 this.preFeatureInsert(feature);
536 this.drawFeature(feature);
539 this.events.triggerEvent("featureadded", {
542 this.onFeatureInsert(feature);
547 this.events.triggerEvent("featuresadded", {features: features});
553 * APIMethod: removeFeatures
554 * Remove features from the layer. This erases any drawn features and
555 * removes them from the layer's control. The beforefeatureremoved
556 * and featureremoved events will be triggered for each feature. The
557 * featuresremoved event will be triggered after all features have
558 * been removed. To supress event triggering, use the silent option.
561 * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
563 * options - {Object} Optional properties for changing behavior of the
567 * silent - {Boolean} Supress event triggering. Default is false.
569 removeFeatures: function(features, options) {
570 if(!features || features.length === 0) {
573 if (!(features instanceof Array)) {
574 features = [features];
576 if (features === this.features) {
577 features = features.slice();
580 var notify = !options || !options.silent;
582 for (var i = features.length - 1; i >= 0; i--) {
583 // We remain locked so long as we're not at 0
584 // and the 'next' feature has a geometry. We do the geometry check
585 // because if all the features after the current one are 'null', we
586 // won't call eraseGeometry, so we break the 'renderer functions
587 // will always be called with locked=false *last*' rule. The end result
588 // is a possible gratiutious unlocking to save a loop through the rest
589 // of the list checking the remaining features every time. So long as
590 // null geoms are rare, this is probably okay.
591 if (i != 0 && features[i-1].geometry) {
592 this.renderer.locked = true;
594 this.renderer.locked = false;
597 var feature = features[i];
598 delete this.unrenderedFeatures[feature.id];
601 this.events.triggerEvent("beforefeatureremoved", {
606 this.features = OpenLayers.Util.removeItem(this.features, feature);
607 // feature has no layer at this point
608 feature.layer = null;
610 if (feature.geometry) {
611 this.renderer.eraseFeatures(feature);
614 //in the case that this feature is one of the selected features,
615 // remove it from that array as well.
616 if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
617 OpenLayers.Util.removeItem(this.selectedFeatures, feature);
621 this.events.triggerEvent("featureremoved", {
628 this.events.triggerEvent("featuresremoved", {features: features});
633 * APIMethod: destroyFeatures
634 * Erase and destroy features on the layer.
637 * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
638 * features to destroy. If not supplied, all features on the layer
642 destroyFeatures: function(features, options) {
643 var all = (features == undefined); // evaluates to true if
646 features = this.features;
649 this.removeFeatures(features, options);
650 for(var i=features.length-1; i>=0; i--) {
651 features[i].destroy();
657 * APIMethod: drawFeature
658 * Draw (or redraw) a feature on the layer. If the optional style argument
659 * is included, this style will be used. If no style is included, the
660 * feature's style will be used. If the feature doesn't have a style,
661 * the layer's style will be used.
663 * This function is not designed to be used when adding features to
664 * the layer (use addFeatures instead). It is meant to be used when
665 * the style of a feature has changed, or in some other way needs to
666 * visually updated *after* it has already been added to a layer. You
667 * must add the feature to the layer for most layer-related events to
671 * feature - {<OpenLayers.Feature.Vector>}
672 * style - {Object} Symbolizer hash or {String} renderIntent
674 drawFeature: function(feature, style) {
675 // don't try to draw the feature with the renderer if the layer is not
680 if (typeof style != "object") {
681 if(!style && feature.state === OpenLayers.State.DELETE) {
684 var renderIntent = style || feature.renderIntent;
685 style = feature.style || this.style;
687 style = this.styleMap.createSymbolizer(feature, renderIntent);
691 if (!this.renderer.drawFeature(feature, style)) {
692 this.unrenderedFeatures[feature.id] = feature;
694 delete this.unrenderedFeatures[feature.id];
699 * Method: eraseFeatures
700 * Erase features from the layer.
703 * features - {Array(<OpenLayers.Feature.Vector>)}
705 eraseFeatures: function(features) {
706 this.renderer.eraseFeatures(features);
710 * Method: getFeatureFromEvent
711 * Given an event, return a feature if the event occurred over one.
712 * Otherwise, return null.
718 * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
720 getFeatureFromEvent: function(evt) {
721 if (!this.renderer) {
722 OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));
725 var featureId = this.renderer.getFeatureIdFromEvent(evt);
726 return this.getFeatureById(featureId);
730 * APIMethod: getFeatureById
731 * Given a feature id, return the feature if it exists in the features array
734 * featureId - {String}
737 * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
740 getFeatureById: function(featureId) {
741 //TBD - would it be more efficient to use a hash for this.features?
743 for(var i=0, len=this.features.length; i<len; ++i) {
744 if(this.features[i].id == featureId) {
745 feature = this.features[i];
753 * Unselect the selected features
754 * i.e. clears the featureSelection array
755 * change the style back
756 clearSelection: function() {
758 var vectorLayer = this.map.vectorLayer;
759 for (var i = 0; i < this.map.featureSelection.length; i++) {
760 var featureSelection = this.map.featureSelection[i];
761 vectorLayer.drawFeature(featureSelection, vectorLayer.style);
763 this.map.featureSelection = [];
769 * APIMethod: onFeatureInsert
770 * method called after a feature is inserted.
771 * Does nothing by default. Override this if you
772 * need to do something on feature updates.
775 * feature - {<OpenLayers.Feature.Vector>}
777 onFeatureInsert: function(feature) {
781 * APIMethod: preFeatureInsert
782 * method called before a feature is inserted.
783 * Does nothing by default. Override this if you
784 * need to do something when features are first added to the
785 * layer, but before they are drawn, such as adjust the style.
788 * feature - {<OpenLayers.Feature.Vector>}
790 preFeatureInsert: function(feature) {
794 * APIMethod: getDataExtent
795 * Calculates the max extent which includes all of the features.
798 * {<OpenLayers.Bounds>}
800 getDataExtent: function () {
801 var maxExtent = null;
803 if(this.features && (this.features.length > 0)) {
804 maxExtent = new OpenLayers.Bounds();
805 for(var i=0, len=this.features.length; i<len; i++) {
806 maxExtent.extend(this.features[i].geometry.getBounds());
813 CLASS_NAME: "OpenLayers.Layer.Vector"