capabilties in OpenLayers Vector drawing, including: * Vector support for iPhone, Safari 2.x series browsers * Improved performance of dragging the map with a large number of geometries. The Vector layer default renderer order is now SVG, VML, Canvas, so browsers with Canvas support and no SVG or VML will be able to draw vectors. The Canvas layer has a number of limitations: getFeatureFromEvent is much slower than the other types of layer, and any change to any feature requires a redraw of the entire canvas. Because Canvas is typically a pretty fast drawing implementation, the latter is less problematic than it might otherwise be. r=pagameba,fred, with glances from a couple other people. (Closes #1512) git-svn-id: http://svn.openlayers.org/trunk/openlayers@7862 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
686 lines
22 KiB
JavaScript
686 lines
22 KiB
JavaScript
/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
|
|
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
|
* full text of the license. */
|
|
|
|
/**
|
|
* @requires OpenLayers/Layer.js
|
|
* @requires OpenLayers/Renderer.js
|
|
* @requires OpenLayers/StyleMap.js
|
|
* @requires OpenLayers/Feature/Vector.js
|
|
*/
|
|
|
|
/**
|
|
* Class: OpenLayers.Layer.Vector
|
|
* Instances of OpenLayers.Layer.Vector are used to render vector data from
|
|
* a variety of sources. Create a new vector layer with the
|
|
* <OpenLayers.Layer.Vector> constructor.
|
|
*
|
|
* Inherits from:
|
|
* - <OpenLayers.Layer>
|
|
*/
|
|
OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
|
|
|
|
/**
|
|
* Constant: EVENT_TYPES
|
|
* {Array(String)} Supported application event types. Register a listener
|
|
* for a particular event with the following syntax:
|
|
* (code)
|
|
* layer.events.register(type, obj, listener);
|
|
* (end)
|
|
*
|
|
* Listeners will be called with a reference to an event object. The
|
|
* properties of this event depends on exactly what happened.
|
|
*
|
|
* All event objects have at least the following properties:
|
|
* - *object* {Object} A reference to layer.events.object.
|
|
* - *element* {DOMElement} A reference to layer.events.element.
|
|
*
|
|
* Supported map event types (in addition to those from <OpenLayers.Layer>):
|
|
* - *beforefeatureadded* Triggered before a feature is added. Listeners
|
|
* will receive an object with a *feature* property referencing the
|
|
* feature to be added.
|
|
* - *featureadded* Triggered after a feature is added. The event
|
|
* object passed to listeners will have a *feature* property with a
|
|
* reference to the added feature.
|
|
* - *featuresadded* Triggered after features are added. The event
|
|
* object passed to listeners will have a *features* property with a
|
|
* reference to an array of added features.
|
|
* - *beforefeatureremoved* Triggered before a feature is removed. Listeners
|
|
* will receive an object with a *feature* property referencing the
|
|
* feature to be removed.
|
|
* - *featureremoved* Triggerd after a feature is removed. The event
|
|
* object passed to listeners will have a *feature* property with a
|
|
* reference to the removed feature.
|
|
* - *featuresremoved* Triggered after features are removed. The event
|
|
* object passed to listeners will have a *features* property with a
|
|
* reference to an array of removed features.
|
|
* - *featureselected* Triggered after a feature is selected. Listeners
|
|
* will receive an object with a *feature* property referencing the
|
|
* selected feature.
|
|
* - *featureunselected* Triggered after a feature is unselected.
|
|
* Listeners will receive an object with a *feature* property
|
|
* referencing the unselected feature.
|
|
* - *beforefeaturemodified* Triggered when a feature is selected to
|
|
* be modified. Listeners will receive an object with a *feature*
|
|
* property referencing the selected feature.
|
|
* - *featuremodified* Triggered when a feature has been modified.
|
|
* Listeners will receive an object with a *feature* property referencing
|
|
* the modified feature.
|
|
* - *afterfeaturemodified* Triggered when a feature is finished being modified.
|
|
* Listeners will receive an object with a *feature* property referencing
|
|
* the modified feature.
|
|
*/
|
|
EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded",
|
|
"beforefeatureremoved", "featureremoved", "featuresremoved",
|
|
"beforefeatureselected", "featureselected", "featureunselected",
|
|
"beforefeaturemodified", "featuremodified", "afterfeaturemodified"],
|
|
|
|
/**
|
|
* APIProperty: isBaseLayer
|
|
* {Boolean} The layer is a base layer. Default is true. Set this property
|
|
* in the layer options
|
|
*/
|
|
isBaseLayer: false,
|
|
|
|
/**
|
|
* APIProperty: isFixed
|
|
* {Boolean} Whether the layer remains in one place while dragging the
|
|
* map.
|
|
*/
|
|
isFixed: false,
|
|
|
|
/**
|
|
* APIProperty: isVector
|
|
* {Boolean} Whether the layer is a vector layer.
|
|
*/
|
|
isVector: true,
|
|
|
|
/**
|
|
* APIProperty: features
|
|
* {Array(<OpenLayers.Feature.Vector>)}
|
|
*/
|
|
features: null,
|
|
|
|
/**
|
|
* Property: selectedFeatures
|
|
* {Array(<OpenLayers.Feature.Vector>)}
|
|
*/
|
|
selectedFeatures: null,
|
|
|
|
/**
|
|
* APIProperty: reportError
|
|
* {Boolean} report friendly error message when loading of renderer
|
|
* fails.
|
|
*/
|
|
reportError: true,
|
|
|
|
/**
|
|
* APIProperty: style
|
|
* {Object} Default style for the layer
|
|
*/
|
|
style: null,
|
|
|
|
/**
|
|
* Property: styleMap
|
|
* {<OpenLayers.StyleMap>}
|
|
*/
|
|
styleMap: null,
|
|
|
|
/**
|
|
* Property: strategies
|
|
* {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
|
|
*/
|
|
strategies: null,
|
|
|
|
/**
|
|
* Property: protocol
|
|
* {<OpenLayers.Protocol>} Optional protocol for the layer.
|
|
*/
|
|
protocol: null,
|
|
|
|
/**
|
|
* Property: renderers
|
|
* {Array(String)} List of supported Renderer classes. Add to this list to
|
|
* add support for additional renderers. This list is ordered:
|
|
* the first renderer which returns true for the 'supported()'
|
|
* method will be used, if not defined in the 'renderer' option.
|
|
*/
|
|
renderers: ['SVG', 'VML', 'Canvas'],
|
|
|
|
/**
|
|
* Property: renderer
|
|
* {<OpenLayers.Renderer>}
|
|
*/
|
|
renderer: null,
|
|
|
|
/**
|
|
* APIProperty: rendererOptions
|
|
* {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
|
|
* supported options.
|
|
*/
|
|
rendererOptions: null,
|
|
|
|
/**
|
|
* APIProperty: geometryType
|
|
* {String} geometryType allows you to limit the types of geometries this
|
|
* layer supports. This should be set to something like
|
|
* "OpenLayers.Geometry.Point" to limit types.
|
|
*/
|
|
geometryType: null,
|
|
|
|
/**
|
|
* Property: drawn
|
|
* {Boolean} Whether the Vector Layer features have been drawn yet.
|
|
*/
|
|
drawn: false,
|
|
|
|
/**
|
|
* Constructor: OpenLayers.Layer.Vector
|
|
* Create a new vector layer
|
|
*
|
|
* Parameters:
|
|
* name - {String} A name for the layer
|
|
* options - {Object} Optional object with non-default properties to set on
|
|
* the layer.
|
|
*
|
|
* Returns:
|
|
* {<OpenLayers.Layer.Vector>} A new vector layer
|
|
*/
|
|
initialize: function(name, options) {
|
|
|
|
// concatenate events specific to vector with those from the base
|
|
this.EVENT_TYPES =
|
|
OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(
|
|
OpenLayers.Layer.prototype.EVENT_TYPES
|
|
);
|
|
|
|
OpenLayers.Layer.prototype.initialize.apply(this, arguments);
|
|
|
|
// allow user-set renderer, otherwise assign one
|
|
if (!this.renderer || !this.renderer.supported()) {
|
|
this.assignRenderer();
|
|
}
|
|
|
|
// if no valid renderer found, display error
|
|
if (!this.renderer || !this.renderer.supported()) {
|
|
this.renderer = null;
|
|
this.displayError();
|
|
}
|
|
|
|
if (!this.styleMap) {
|
|
this.styleMap = new OpenLayers.StyleMap();
|
|
}
|
|
|
|
this.features = [];
|
|
this.selectedFeatures = [];
|
|
|
|
// Allow for custom layer behavior
|
|
if(this.strategies){
|
|
for(var i=0, len=this.strategies.length; i<len; i++) {
|
|
this.strategies[i].setLayer(this);
|
|
}
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* APIMethod: destroy
|
|
* Destroy this layer
|
|
*/
|
|
destroy: function() {
|
|
if (this.strategies) {
|
|
var strategy, i, len;
|
|
for(i=0, len=this.strategies.length; i<len; i++) {
|
|
strategy = this.strategies[i];
|
|
if(strategy.autoDestroy) {
|
|
strategy.destroy();
|
|
}
|
|
}
|
|
this.strategies = null;
|
|
}
|
|
if (this.protocol) {
|
|
if(this.protocol.autoDestroy) {
|
|
this.protocol.destroy();
|
|
}
|
|
this.protocol = null;
|
|
}
|
|
this.destroyFeatures();
|
|
this.features = null;
|
|
this.selectedFeatures = null;
|
|
if (this.renderer) {
|
|
this.renderer.destroy();
|
|
}
|
|
this.renderer = null;
|
|
this.geometryType = null;
|
|
this.drawn = null;
|
|
OpenLayers.Layer.prototype.destroy.apply(this, arguments);
|
|
},
|
|
|
|
/**
|
|
* Method: assignRenderer
|
|
* Iterates through the available renderer implementations and selects
|
|
* and assigns the first one whose "supported()" function returns true.
|
|
*/
|
|
assignRenderer: function() {
|
|
for (var i=0, len=this.renderers.length; i<this.renderers.length; i++) {
|
|
var rendererClass = OpenLayers.Renderer[this.renderers[i]];
|
|
if (rendererClass && rendererClass.prototype.supported()) {
|
|
this.renderer = new rendererClass(this.div,
|
|
this.rendererOptions);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: displayError
|
|
* Let the user know their browser isn't supported.
|
|
*/
|
|
displayError: function() {
|
|
if (this.reportError) {
|
|
OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
|
|
{'renderers':this.renderers.join("\n")}));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: setMap
|
|
* The layer has been added to the map.
|
|
*
|
|
* If there is no renderer set, the layer can't be used. Remove it.
|
|
* Otherwise, give the renderer a reference to the map and set its size.
|
|
*
|
|
* Parameters:
|
|
* map - {<OpenLayers.Map>}
|
|
*/
|
|
setMap: function(map) {
|
|
OpenLayers.Layer.prototype.setMap.apply(this, arguments);
|
|
|
|
if (!this.renderer) {
|
|
this.map.removeLayer(this);
|
|
} else {
|
|
this.renderer.map = this.map;
|
|
this.renderer.setSize(this.map.getSize());
|
|
}
|
|
if(this.strategies) {
|
|
var strategy, i, len;
|
|
for(i=0, len=this.strategies.length; i<len; i++) {
|
|
strategy = this.strategies[i];
|
|
if(strategy.autoActivate) {
|
|
strategy.activate();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: removeMap
|
|
* The layer has been removed from the map.
|
|
*
|
|
* Parameters:
|
|
* map - {<OpenLayers.Map>}
|
|
*/
|
|
removeMap: function(map) {
|
|
if(this.strategies) {
|
|
var strategy, i, len;
|
|
for(i=0, len=this.strategies.length; i<len; i++) {
|
|
strategy = this.strategies[i];
|
|
if(strategy.autoActivate) {
|
|
strategy.deactivate();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Method: onMapResize
|
|
* Notify the renderer of the change in size.
|
|
*
|
|
*/
|
|
onMapResize: function() {
|
|
OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
|
|
this.renderer.setSize(this.map.getSize());
|
|
},
|
|
|
|
/**
|
|
* Method: moveTo
|
|
* Reset the vector layer's div so that it once again is lined up with
|
|
* the map. Notify the renderer of the change of extent, and in the
|
|
* case of a change of zoom level (resolution), have the
|
|
* renderer redraw features.
|
|
*
|
|
* If the layer has not yet been drawn, cycle through the layer's
|
|
* features and draw each one.
|
|
*
|
|
* Parameters:
|
|
* bounds - {<OpenLayers.Bounds>}
|
|
* zoomChanged - {Boolean}
|
|
* dragging - {Boolean}
|
|
*/
|
|
moveTo: function(bounds, zoomChanged, dragging) {
|
|
OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
|
|
|
|
if (!dragging) {
|
|
this.renderer.root.style.visibility = "hidden";
|
|
|
|
this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px";
|
|
this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px";
|
|
var extent = this.map.getExtent();
|
|
this.renderer.setExtent(extent);
|
|
|
|
this.renderer.root.style.visibility = "visible";
|
|
|
|
// Force a reflow on gecko based browsers to prevent jump/flicker.
|
|
// This seems to happen on only certain configurations; it was originally
|
|
// noticed in FF 2.0 and Linux.
|
|
if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {
|
|
this.div.scrollLeft = this.div.scrollLeft;
|
|
}
|
|
}
|
|
|
|
if (!this.drawn || zoomChanged) {
|
|
this.drawn = true;
|
|
for(var i=0, len=this.features.length; i<len; i++) {
|
|
if (i != (this.features.length - 1)) {
|
|
this.renderer.locked = true;
|
|
} else {
|
|
this.renderer.locked = false;
|
|
}
|
|
this.drawFeature(this.features[i]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: addFeatures
|
|
* Add Features to the layer.
|
|
*
|
|
* Parameters:
|
|
* features - {Array(<OpenLayers.Feature.Vector>)}
|
|
* options - {Object}
|
|
*/
|
|
addFeatures: function(features, options) {
|
|
if (!(features instanceof Array)) {
|
|
features = [features];
|
|
}
|
|
|
|
var notify = !options || !options.silent;
|
|
|
|
for (var i=0, len=features.length; i<len; i++) {
|
|
if (i != (features.length - 1)) {
|
|
this.renderer.locked = true;
|
|
} else {
|
|
this.renderer.locked = false;
|
|
}
|
|
var feature = features[i];
|
|
|
|
if (this.geometryType &&
|
|
!(feature.geometry instanceof this.geometryType)) {
|
|
var throwStr = OpenLayers.i18n('componentShouldBe',
|
|
{'geomType':this.geometryType.prototype.CLASS_NAME});
|
|
throw throwStr;
|
|
}
|
|
|
|
this.features.push(feature);
|
|
|
|
//give feature reference to its layer
|
|
feature.layer = this;
|
|
|
|
if (!feature.style && this.style) {
|
|
feature.style = OpenLayers.Util.extend({}, this.style);
|
|
}
|
|
|
|
if (notify) {
|
|
this.events.triggerEvent("beforefeatureadded", {
|
|
feature: feature
|
|
});
|
|
this.preFeatureInsert(feature);
|
|
}
|
|
|
|
if (this.drawn) {
|
|
this.drawFeature(feature);
|
|
}
|
|
|
|
if (notify) {
|
|
this.events.triggerEvent("featureadded", {
|
|
feature: feature
|
|
});
|
|
this.onFeatureInsert(feature);
|
|
}
|
|
}
|
|
|
|
if(notify) {
|
|
this.events.triggerEvent("featuresadded", {features: features});
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* APIMethod: removeFeatures
|
|
*
|
|
* Parameters:
|
|
* features - {Array(<OpenLayers.Feature.Vector>)}
|
|
* options - {Object}
|
|
*/
|
|
removeFeatures: function(features, options) {
|
|
if (!(features instanceof Array)) {
|
|
features = [features];
|
|
}
|
|
if (features.length <= 0) {
|
|
return;
|
|
}
|
|
|
|
var notify = !options || !options.silent;
|
|
|
|
for (var i = features.length - 1; i >= 0; i--) {
|
|
// We remain locked so long as we're not at 0
|
|
// and the 'next' feature has a geometry. We do the geometry check
|
|
// because if all the features after the current one are 'null', we
|
|
// won't call eraseGeometry, so we break the 'renderer functions
|
|
// will always be called with locked=false *last*' rule. The end result
|
|
// is a possible gratiutious unlocking to save a loop through the rest
|
|
// of the list checking the remaining features every time. So long as
|
|
// null geoms are rare, this is probably okay.
|
|
if (i != 0 && features[i-1].geometry) {
|
|
this.renderer.locked = true;
|
|
} else {
|
|
this.renderer.locked = false;
|
|
}
|
|
|
|
var feature = features[i];
|
|
|
|
if (notify) {
|
|
this.events.triggerEvent("beforefeatureremoved", {
|
|
feature: feature
|
|
});
|
|
}
|
|
|
|
this.features = OpenLayers.Util.removeItem(this.features, feature);
|
|
// feature has no layer at this point
|
|
feature.layer = null;
|
|
|
|
if (feature.geometry) {
|
|
this.renderer.eraseGeometry(feature.geometry);
|
|
}
|
|
|
|
//in the case that this feature is one of the selected features,
|
|
// remove it from that array as well.
|
|
if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
|
|
OpenLayers.Util.removeItem(this.selectedFeatures, feature);
|
|
}
|
|
|
|
if (notify) {
|
|
this.events.triggerEvent("featureremoved", {
|
|
feature: feature
|
|
});
|
|
}
|
|
}
|
|
|
|
if (notify) {
|
|
this.events.triggerEvent("featuresremoved", {features: features});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: destroyFeatures
|
|
* Erase and destroy features on the layer.
|
|
*
|
|
* Parameters:
|
|
* features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
|
|
* features to destroy. If not supplied, all features on the layer
|
|
* will be destroyed.
|
|
* options - {Object}
|
|
*/
|
|
destroyFeatures: function(features, options) {
|
|
var all = (features == undefined); // evaluates to true if
|
|
// features is null
|
|
if(all) {
|
|
features = this.features;
|
|
}
|
|
this.removeFeatures(features, options);
|
|
for (var i = 0; i < features.length; i++) {
|
|
features[i].destroy();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* APIMethod: drawFeature
|
|
* Draw (or redraw) a feature on the layer. If the optional style argument
|
|
* is included, this style will be used. If no style is included, the
|
|
* feature's style will be used. If the feature doesn't have a style,
|
|
* the layer's style will be used.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
* style - {Object} Symbolizer hash or {String} renderIntent
|
|
*/
|
|
drawFeature: function(feature, style) {
|
|
if (typeof style != "object") {
|
|
var renderIntent = typeof style == "string" ?
|
|
style : feature.renderIntent;
|
|
style = feature.style || this.style;
|
|
if (!style) {
|
|
style = this.styleMap.createSymbolizer(feature, renderIntent);
|
|
}
|
|
}
|
|
|
|
this.renderer.drawFeature(feature, style);
|
|
},
|
|
|
|
/**
|
|
* Method: eraseFeatures
|
|
* Erase features from the layer.
|
|
*
|
|
* Parameters:
|
|
* features - {Array(<OpenLayers.Feature.Vector>)}
|
|
*/
|
|
eraseFeatures: function(features) {
|
|
this.renderer.eraseFeatures(features);
|
|
},
|
|
|
|
/**
|
|
* Method: getFeatureFromEvent
|
|
* Given an event, return a feature if the event occurred over one.
|
|
* Otherwise, return null.
|
|
*
|
|
* Parameters:
|
|
* evt - {Event}
|
|
*
|
|
* Returns:
|
|
* {<OpenLayers.Feature.Vector>} A feature if one was under the event.
|
|
*/
|
|
getFeatureFromEvent: function(evt) {
|
|
if (!this.renderer) {
|
|
OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));
|
|
return null;
|
|
}
|
|
var featureId = this.renderer.getFeatureIdFromEvent(evt);
|
|
return this.getFeatureById(featureId);
|
|
},
|
|
|
|
/**
|
|
* APIMethod: getFeatureById
|
|
* Given a feature id, return the feature if it exists in the features array
|
|
*
|
|
* Parameters:
|
|
* featureId - {String}
|
|
*
|
|
* Returns:
|
|
* {<OpenLayers.Feature.Vector>} A feature corresponding to the given
|
|
* featureId
|
|
*/
|
|
getFeatureById: function(featureId) {
|
|
//TBD - would it be more efficient to use a hash for this.features?
|
|
var feature = null;
|
|
for(var i=0, len=this.features.length; i<len; ++i) {
|
|
if(this.features[i].id == featureId) {
|
|
feature = this.features[i];
|
|
break;
|
|
}
|
|
}
|
|
return feature;
|
|
},
|
|
|
|
/**
|
|
* Unselect the selected features
|
|
* i.e. clears the featureSelection array
|
|
* change the style back
|
|
clearSelection: function() {
|
|
|
|
var vectorLayer = this.map.vectorLayer;
|
|
for (var i = 0; i < this.map.featureSelection.length; i++) {
|
|
var featureSelection = this.map.featureSelection[i];
|
|
vectorLayer.drawFeature(featureSelection, vectorLayer.style);
|
|
}
|
|
this.map.featureSelection = [];
|
|
},
|
|
*/
|
|
|
|
|
|
/**
|
|
* APIMethod: onFeatureInsert
|
|
* method called after a feature is inserted.
|
|
* Does nothing by default. Override this if you
|
|
* need to do something on feature updates.
|
|
*
|
|
* Paarameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
*/
|
|
onFeatureInsert: function(feature) {
|
|
},
|
|
|
|
/**
|
|
* APIMethod: preFeatureInsert
|
|
* method called before a feature is inserted.
|
|
* Does nothing by default. Override this if you
|
|
* need to do something when features are first added to the
|
|
* layer, but before they are drawn, such as adjust the style.
|
|
*
|
|
* Parameters:
|
|
* feature - {<OpenLayers.Feature.Vector>}
|
|
*/
|
|
preFeatureInsert: function(feature) {
|
|
},
|
|
|
|
/**
|
|
* APIMethod: getDataExtent
|
|
* Calculates the max extent which includes all of the features.
|
|
*
|
|
* Returns:
|
|
* {<OpenLayers.Bounds>}
|
|
*/
|
|
getDataExtent: function () {
|
|
var maxExtent = null;
|
|
if( this.features && (this.features.length > 0)){
|
|
var maxExtent = this.features[0].geometry.getBounds();
|
|
for(var i=0, len=this.features.length; i<len; i++){
|
|
maxExtent.extend(this.features[i].geometry.getBounds());
|
|
}
|
|
}
|
|
|
|
return maxExtent;
|
|
},
|
|
|
|
CLASS_NAME: "OpenLayers.Layer.Vector"
|
|
});
|