[OpenLayers-Commits] r11092 - in sandbox/elemoine/draw-feature: . build examples lib lib/OpenLayers lib/OpenLayers/Control lib/OpenLayers/Format lib/OpenLayers/Format/WMSCapabilities lib/OpenLayers/Layer lib/OpenLayers/Layer/Google tests tests/Control tests/Format tests/Format/WFST tests/Format/WMSCapabilities tests/Layer theme/default

commits-20090109 at openlayers.org commits-20090109 at openlayers.org
Thu Feb 10 08:33:20 EST 2011


Author: erilem
Date: 2011-02-10 05:33:20 -0800 (Thu, 10 Feb 2011)
New Revision: 11092

Added:
   sandbox/elemoine/draw-feature/examples/cql-format.html
   sandbox/elemoine/draw-feature/examples/cql-format.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Format/CQL.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
   sandbox/elemoine/draw-feature/tests/Format/CQL.html
   sandbox/elemoine/draw-feature/tests/Format/WMSCapabilities/v1_1_1_WMSC.html
Modified:
   sandbox/elemoine/draw-feature/
   sandbox/elemoine/draw-feature/build/library.cfg
   sandbox/elemoine/draw-feature/examples/google-v3.html
   sandbox/elemoine/draw-feature/examples/google-v3.js
   sandbox/elemoine/draw-feature/examples/osm-google.html
   sandbox/elemoine/draw-feature/examples/sos.html
   sandbox/elemoine/draw-feature/lib/OpenLayers.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Control/DragFeature.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Control/ModifyFeature.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Control/Pan.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Control/PanPanel.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Control/WMSGetFeatureInfo.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Layer.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Bing.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Google/v3.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Vector.js
   sandbox/elemoine/draw-feature/lib/OpenLayers/Map.js
   sandbox/elemoine/draw-feature/tests/Control/DragFeature.html
   sandbox/elemoine/draw-feature/tests/Control/ModifyFeature.html
   sandbox/elemoine/draw-feature/tests/Control/PanPanel.html
   sandbox/elemoine/draw-feature/tests/Control/WMSGetFeatureInfo.html
   sandbox/elemoine/draw-feature/tests/Format/WFST/v1_1_0.html
   sandbox/elemoine/draw-feature/tests/Layer.html
   sandbox/elemoine/draw-feature/tests/Layer/Bing.html
   sandbox/elemoine/draw-feature/tests/Map.html
   sandbox/elemoine/draw-feature/tests/list-tests.html
   sandbox/elemoine/draw-feature/theme/default/google.css
   sandbox/elemoine/draw-feature/theme/default/style.css
Log:
svn merge -r 11039:HEAD http://svn.openlayers.org/trunk/openlayers/ .


Property changes on: sandbox/elemoine/draw-feature
___________________________________________________________________
Modified: svn:mergeinfo
   - /sandbox/roberthl/openlayers:9745-9748
/trunk/openlayers:11027-11037
   + /sandbox/roberthl/openlayers:9745-9748
/trunk/openlayers:11027-11037,11040-11091

Modified: sandbox/elemoine/draw-feature/build/library.cfg
===================================================================
--- sandbox/elemoine/draw-feature/build/library.cfg	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/build/library.cfg	2011-02-10 13:33:20 UTC (rev 11092)
@@ -1,6 +1,5 @@
 # This file includes the OpenLayers code to create a build for everything that 
-# does not require vector support. build.py uses this profile if no other one
-# is specified.
+# does not require vector support.
 
 [first]
 

Copied: sandbox/elemoine/draw-feature/examples/cql-format.html (from rev 11091, trunk/openlayers/examples/cql-format.html)
===================================================================
--- sandbox/elemoine/draw-feature/examples/cql-format.html	                        (rev 0)
+++ sandbox/elemoine/draw-feature/examples/cql-format.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,51 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+    <head>
+        <title>
+            OpenLayers CQL Example
+        </title>
+        <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+        <link rel="stylesheet" href="style.css" type="text/css">
+        <style>
+            #cql {
+                width: 400px;
+            }
+            #output {
+                padding-top: 1em;
+                width: 512px;
+                height: 60px;
+                border: none;
+                color: #ff3333;
+            }
+        </style>
+        <script src="../lib/OpenLayers.js"></script>
+    </head>
+    <body>
+        <h1 id="title">CQL Filter Example</h1>
+        <div id="tags">
+            CQL, filter
+        </div>
+        <p id="shortdesc">
+            Demonstrate use the CQL filter.
+        </p>
+        <div id="map" class="smallmap"></div>
+        <div id="docs">
+            <p>
+                Enter text for a CQL filter to update the features displayed.
+                <br>
+                <form name="cql_form" id="cql_form">
+                    <label for="cql">CQL</label>
+                    <input id="cql" type="text" value="STATE_ABBR >= 'B' AND STATE_ABBR <= 'O'">
+                    <input type="submit" value="update">
+                    <input type="reset" value="reset">
+                </form>
+                <textarea id="output"></textarea>
+            </p><p>
+                View the <a href="cql-format.js" target="_blank">cql-format.js source</a> 
+                to see how this is done.
+            </p>
+        </div>
+        <script src="cql-format.js"></script>
+        <script src="http://demo.opengeo.org/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=GetFeature&amp;typename=topp:states&amp;outputFormat=json&amp;format_options=callback:loadFeatures" type="text/javascript"></script>
+    </body>
+</html>

Copied: sandbox/elemoine/draw-feature/examples/cql-format.js (from rev 11091, trunk/openlayers/examples/cql-format.js)
===================================================================
--- sandbox/elemoine/draw-feature/examples/cql-format.js	                        (rev 0)
+++ sandbox/elemoine/draw-feature/examples/cql-format.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,61 @@
+
+// use a CQL parser for easy filter creation
+var format = new OpenLayers.Format.CQL();
+
+// this rule will get a filter from the CQL text in the form
+var rule = new OpenLayers.Rule({
+    // We could also set a filter here.  E.g.
+    // filter: format.read("STATE_ABBR >= 'B' AND STATE_ABBR <= 'O'"),
+    symbolizer: {
+        fillColor: "#ff0000",
+        strokeColor: "#ffcccc",
+        fillOpacity: "0.5"
+    }    
+});
+
+var states = new OpenLayers.Layer.Vector("States", {
+    styleMap: new OpenLayers.StyleMap({
+        "default": new OpenLayers.Style(null, {rules: [rule]})
+    })
+});
+
+var map = new OpenLayers.Map({
+    div: "map",
+    layers: [
+        new OpenLayers.Layer.WMS(
+            "OpenLayers WMS",
+            "http://maps.opengeo.org/geowebcache/service/wms",
+            {layers: "openstreetmap", format: "image/png"}
+        ),
+        states
+    ],
+    center: new OpenLayers.LonLat(-101, 39),
+    zoom: 3
+});
+
+// called when features are fetched
+function loadFeatures(data) {
+    var features = new OpenLayers.Format.GeoJSON().read(data);
+    states.addFeatures(features);
+};
+
+// update filter and redraw when form is submitted
+var cql = document.getElementById("cql");
+var output = document.getElementById("output");
+function updateFilter() {
+    var filter;
+    try {
+        filter = format.read(cql.value);
+    } catch (err) {
+        output.value = err.message;
+    }
+    if (filter) {
+        output.value = "";
+        rule.filter = filter;
+        states.redraw();
+    }
+    return false;
+}
+updateFilter();
+var form = document.getElementById("cql_form");
+form.onsubmit = updateFilter;

Modified: sandbox/elemoine/draw-feature/examples/google-v3.html
===================================================================
--- sandbox/elemoine/draw-feature/examples/google-v3.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/examples/google-v3.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -5,7 +5,7 @@
         <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
         <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
         <link rel="stylesheet" href="style.css" type="text/css">
-        <script src="http://maps.google.com/maps/api/js?v=3.2&amp;sensor=false"></script>
+        <script src="http://maps.google.com/maps/api/js?v=3.3&amp;sensor=false"></script>
         <script src="../lib/OpenLayers.js"></script>
         <script src="google-v3.js"></script>
     </head>
@@ -19,6 +19,8 @@
         </p>
         <div id="map" class="smallmap"></div>
         <div id="docs">
+            <p><input id="animate" type="checkbox" checked="checked">Animated
+                zoom (if supported by GMaps on your device)</input></p>
             <p>
                 If you use the Google Maps v3 API with a Google layer, you don't
                 need to include an API key.  This layer only works in the 

Modified: sandbox/elemoine/draw-feature/examples/google-v3.js
===================================================================
--- sandbox/elemoine/draw-feature/examples/google-v3.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/examples/google-v3.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -29,4 +29,12 @@
         new OpenLayers.Projection("EPSG:4326"),
         map.getProjectionObject()
     ), 5);
+    
+    // add behavior to html
+    var animate = document.getElementById("animate");
+    animate.onclick = function() {
+        for (var i=map.layers.length-1; i>=0; --i) {
+            map.layers[i].animationEnabled = this.checked;
+        }
+    };
 }

Modified: sandbox/elemoine/draw-feature/examples/osm-google.html
===================================================================
--- sandbox/elemoine/draw-feature/examples/osm-google.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/examples/osm-google.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -5,7 +5,7 @@
         <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
         <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
         <link rel="stylesheet" href="style.css" type="text/css">
-        <script src="http://maps.google.com/maps/api/js?v=3.2&amp;sensor=false"></script>
+        <script src="http://maps.google.com/maps/api/js?v=3.3&amp;sensor=false"></script>
         <script src="../lib/OpenLayers.js"></script>
         <script src="osm-google.js"></script>
     </head>

Modified: sandbox/elemoine/draw-feature/examples/sos.html
===================================================================
--- sandbox/elemoine/draw-feature/examples/sos.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/examples/sos.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -1,6 +1,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <title>SOS Client Example</title>
     <link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="style.css" type="text/css" />
     <style type="text/css">
     .sosmap {
         width: 768px;

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Control/DragFeature.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Control/DragFeature.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Control/DragFeature.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -66,6 +66,28 @@
     onComplete: function(feature, pixel) {},
 
     /**
+     * APIProperty: onEnter
+     * {Function} Define this function if you want to know when the mouse
+     *     goes over a feature and thereby makes this feature a candidate
+     *     for dragging.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that is ready
+     *     to be dragged.
+     */
+    onEnter: function(feature) {},
+
+    /**
+     * APIProperty: onLeave
+     * {Function} Define this function if you want to know when the mouse
+     *     goes out of the feature that was dragged.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+     */
+    onLeave: function(feature) {},
+
+    /**
      * APIProperty: documentDrag
      * {Boolean} If set to true, mouse dragging will continue even if the
      *     mouse cursor leaves the map viewport. Default is false.
@@ -192,6 +214,7 @@
             this.handlers.drag.activate();
             this.over = true;
             OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over");
+            this.onEnter(feature);
         } else {
             if(this.feature.id == feature.id) {
                 this.over = true;
@@ -269,6 +292,7 @@
             OpenLayers.Element.removeClass(
                 this.map.viewPortDiv, this.displayClass + "Over"
             );
+            this.onLeave(feature);
             this.feature = null;
         } else {
             if(this.feature.id == feature.id) {

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Control/ModifyFeature.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Control/ModifyFeature.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Control/ModifyFeature.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -608,6 +608,11 @@
                vertex.geometry.parent) {
                 // remove the vertex
                 vertex.geometry.parent.removeComponent(vertex.geometry);
+                this.layer.events.triggerEvent("vertexremoved", {
+                    vertex: vertex.geometry,
+                    feature: this.feature,
+                    pixel: evt.xy
+                });
                 this.layer.drawFeature(this.feature, this.standalone ?
                                        undefined :
                                        this.selectControl.renderIntent);

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Control/Pan.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Control/Pan.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Control/Pan.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -20,11 +20,21 @@
     /** 
      * APIProperty: slideFactor
      * {Integer} Number of pixels by which we'll pan the map in any direction 
-     *     on clicking the arrow buttons, defaults to 50.
+     *     on clicking the arrow buttons, defaults to 50.  If you want to pan
+     *     by some ratio of the map dimensions, use <slideRatio> instead.
      */
     slideFactor: 50,
 
     /** 
+     * APIProperty: slideRatio
+     * {Number} The fraction of map width/height by which we'll pan the map            
+     *     on clicking the arrow buttons.  Default is null.  If set, will
+     *     override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+     *     pan up half the map height. 
+     */
+    slideRatio: null,
+
+    /** 
      * Property: direction
      * {String} in {'North', 'South', 'East', 'West'}
      */
@@ -61,18 +71,24 @@
      */
     trigger: function(){
     
+        var getSlideFactor = OpenLayers.Function.bind(function (dim) {
+            return this.slideRatio ?
+                this.map.getSize()[dim] * this.slideRatio :
+                this.slideFactor;
+        }, this);
+
         switch (this.direction) {
             case OpenLayers.Control.Pan.NORTH: 
-                this.map.pan(0, -this.slideFactor);
+                this.map.pan(0, -getSlideFactor("h"));
                 break;
             case OpenLayers.Control.Pan.SOUTH: 
-                this.map.pan(0, this.slideFactor);
+                this.map.pan(0, getSlideFactor("h"));
                 break;
             case OpenLayers.Control.Pan.WEST: 
-                this.map.pan(-this.slideFactor, 0);
+                this.map.pan(-getSlideFactor("w"), 0);
                 break;
             case OpenLayers.Control.Pan.EAST: 
-                this.map.pan(this.slideFactor, 0);
+                this.map.pan(getSlideFactor("w"), 0);
                 break;
         }
     },

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Control/PanPanel.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Control/PanPanel.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Control/PanPanel.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -33,10 +33,20 @@
     /** 
      * APIProperty: slideFactor
      * {Integer} Number of pixels by which we'll pan the map in any direction 
-     *     on clicking the arrow buttons, defaults to 50.
+     *     on clicking the arrow buttons, defaults to 50.  If you want to pan
+     *     by some ratio of the map dimensions, use <slideRatio> instead.
      */
     slideFactor: 50,
 
+    /** 
+     * APIProperty: slideRatio
+     * {Number} The fraction of map width/height by which we'll pan the map            
+     *     on clicking the arrow buttons.  Default is null.  If set, will
+     *     override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+     *     pan up half the map height. 
+     */
+    slideRatio: null,
+
     /**
      * Constructor: OpenLayers.Control.PanPanel 
      * Add the four directional pan buttons.
@@ -47,15 +57,15 @@
      */
     initialize: function(options) {
         OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+        var options = {
+            slideFactor: this.slideFactor,
+            slideRatio: this.slideRatio
+        };
         this.addControls([
-            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH,
-                                       {slideFactor: this.slideFactor}),
-            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH,
-                                       {slideFactor: this.slideFactor}),
-            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST,
-                                       {slideFactor: this.slideFactor}),
-            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST,
-                                       {slideFactor: this.slideFactor})
+            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH, options),
+            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH, options),
+            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST, options),
+            new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST, options)
         ]);
     },
 

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Control/WMSGetFeatureInfo.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Control/WMSGetFeatureInfo.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Control/WMSGetFeatureInfo.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -370,13 +370,13 @@
         }, (parseFloat(firstLayer.params.VERSION) >= 1.3) ?
             {
                 crs: projection,
-                i: clickPosition.x,
-                j: clickPosition.y
+                i: parseInt(clickPosition.x),
+                j: parseInt(clickPosition.y)
             } :
             {
                 srs: projection,
-                x: clickPosition.x,
-                y: clickPosition.y
+                x: parseInt(clickPosition.x),
+                y: parseInt(clickPosition.y)
             }
         );
         OpenLayers.Util.applyDefaults(params, this.vendorParams);

Copied: sandbox/elemoine/draw-feature/lib/OpenLayers/Format/CQL.js (from rev 11091, trunk/openlayers/lib/OpenLayers/Format/CQL.js)
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Format/CQL.js	                        (rev 0)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Format/CQL.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,438 @@
+/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for 
+ * full list of contributors). Published under the Clear BSD license.  
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WKT.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CQL
+ * Read CQL strings to get <OpenLayers.Filter> objects.  Write 
+ *     <OpenLayers.Filter> objects to get CQL strings. Create a new parser with 
+ *     the <OpenLayers.Format.CQL> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.CQL = (function() {
+    
+    var tokens = [
+        "PROPERTY", "COMPARISON", "VALUE", "LOGICAL"
+    ],
+
+    patterns = {
+        PROPERTY: /^[_a-zA-Z]\w*/,
+        COMPARISON: /^(=|<>|<=|<|>=|>|LIKE)/i,
+        COMMA: /^,/,
+        LOGICAL: /^(AND|OR)/i,
+        VALUE: /^('\w+'|\d+(\.\d*)?|\.\d+)/,
+        LPAREN: /^\(/,
+        RPAREN: /^\)/,
+        SPATIAL: /^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,
+        NOT: /^NOT/i,
+        BETWEEN: /^BETWEEN/i,
+        GEOMETRY: function(text) {
+            var type = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(text);
+            if (type) {
+                var len = text.length;
+                var idx = text.indexOf("(", type[0].length);
+                if (idx > -1) {
+                    var depth = 1;
+                    while (idx < len && depth > 0) {
+                        idx++;
+                        switch(text.charAt(idx)) {
+                            case '(':
+                                depth++;
+                                break;
+                            case ')':
+                                depth--;
+                                break;
+                            default:
+                                // in default case, do nothing
+                        }
+                    }
+                }
+                return [text.substr(0, idx+1)];
+            }
+        },
+        END: /^$/
+    },
+
+    follows = {
+        LPAREN: ['GEOMETRY', 'SPATIAL', 'PROPERTY', 'VALUE', 'LPAREN'],
+        RPAREN: ['NOT', 'LOGICAL', 'END', 'RPAREN'],
+        PROPERTY: ['COMPARISON', 'BETWEEN', 'COMMA'],
+        BETWEEN: ['VALUE'],
+        COMPARISON: ['VALUE'],
+        COMMA: ['GEOMETRY', 'VALUE', 'PROPERTY'],
+        VALUE: ['LOGICAL', 'COMMA', 'RPAREN', 'END'],
+        SPATIAL: ['LPAREN'],
+        LOGICAL: ['NOT', 'VALUE', 'SPATIAL', 'PROPERTY', 'LPAREN'],
+        NOT: ['PROPERTY', 'LPAREN'],
+        GEOMETRY: ['COMMA', 'RPAREN']
+    },
+
+    operators = {
+        '=': OpenLayers.Filter.Comparison.EQUAL_TO,
+        '<>': OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+        '<': OpenLayers.Filter.Comparison.LESS_THAN,
+        '<=': OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+        '>': OpenLayers.Filter.Comparison.GREATER_THAN,
+        '>=': OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+        'LIKE': OpenLayers.Filter.Comparison.LIKE,
+        'BETWEEN': OpenLayers.Filter.Comparison.BETWEEN
+    },
+
+    operatorReverse = {},
+
+    logicals = {
+        'AND': OpenLayers.Filter.Logical.AND,
+        'OR': OpenLayers.Filter.Logical.OR
+    },
+
+    logicalReverse = {},
+
+    precedence = {
+        'RPAREN': 3,
+        'LOGICAL': 2,
+        'COMPARISON': 1
+    };
+
+    var i;
+    for (i in operators) {
+        if (operators.hasOwnProperty(i)) {
+            operatorReverse[operators[i]] = i;
+        }
+    }
+
+    for (i in logicals) {
+        if (logicals.hasOwnProperty(i)) {
+            logicalReverse[logicals[i]] = i;
+        }
+    }
+
+    function tryToken(text, pattern) {
+        if (pattern instanceof RegExp) {
+            return pattern.exec(text);
+        } else {
+            return pattern(text);
+        }
+    }
+
+    function nextToken(text, tokens) {
+        var i, token, len = tokens.length;
+        for (i=0; i<len; i++) {
+            token = tokens[i];
+            var pat = patterns[token];
+            var matches = tryToken(text, pat);
+            if (matches) {
+                var match = matches[0];
+                var remainder = text.substr(match.length).replace(/^\s*/, "");
+                return {
+                    type: token,
+                    text: match,
+                    remainder: remainder
+                };
+            }
+        }
+
+        var msg = "ERROR: In parsing: [" + text + "], expected one of: ";
+        for (i=0; i<len; i++) {
+            token = tokens[i];
+            msg += "\n    " + token + ": " + patterns[token];
+        }
+
+        throw new Error(msg);
+    }
+
+    function tokenize(text) {
+        var results = [];
+        var token, expect = ["NOT", "GEOMETRY", "SPATIAL", "PROPERTY", "LPAREN"];
+
+        do {
+            token = nextToken(text, expect);
+            text = token.remainder;
+            expect = follows[token.type];
+            if (token.type != "END" && !expect) {
+                throw new Error("No follows list for " + token.type);
+            }
+            results.push(token);
+        } while (token.type != "END");
+
+        return results;
+    }
+
+    function buildAst(tokens) {
+        var operatorStack = [],
+            postfix = [];
+
+        while (tokens.length) {
+            var tok = tokens.shift();
+            switch (tok.type) {
+                case "PROPERTY":
+                case "GEOMETRY":
+                case "VALUE":
+                    postfix.push(tok);
+                    break;
+                case "COMPARISON":
+                case "BETWEEN":
+                case "LOGICAL":
+                    var p = precedence[tok.type];
+
+                    while (operatorStack.length > 0 &&
+                        (precedence[operatorStack[operatorStack.length - 1].type] <= p)
+                    ) {
+                        postfix.push(operatorStack.pop());
+                    }
+
+                    operatorStack.push(tok);
+                    break;
+                case "SPATIAL":
+                case "NOT":
+                case "LPAREN":
+                    operatorStack.push(tok);
+                    break;
+                case "RPAREN":
+                    while (operatorStack.length > 0 &&
+                        (operatorStack[operatorStack.length - 1].type != "LPAREN")
+                    ) {
+                        postfix.push(operatorStack.pop());
+                    }
+                    operatorStack.pop(); // toss out the LPAREN
+
+                    if (operatorStack.length > 0 &&
+                        operatorStack[operatorStack.length-1].type == "SPATIAL") {
+                        postfix.push(operatorStack.pop());
+                    }
+                case "COMMA":
+                case "END":
+                    break;
+                default:
+                    throw new Error("Unknown token type " + tok.type);
+            }
+        }
+
+        while (operatorStack.length > 0) {
+            postfix.push(operatorStack.pop());
+        }
+
+        function buildTree() {
+            var tok = postfix.pop();
+            switch (tok.type) {
+                case "LOGICAL":
+                    var rhs = buildTree(),
+                        lhs = buildTree();
+                    return new OpenLayers.Filter.Logical({
+                        filters: [lhs, rhs],
+                        type: logicals[tok.text.toUpperCase()]
+                    });
+                case "NOT":
+                    var operand = buildTree();
+                    return new OpenLayers.Filter.Logical({
+                        filters: [operand],
+                        type: OpenLayers.Filter.Logical.NOT
+                    });
+                case "BETWEEN":
+                    var min, max, property;
+                    postfix.pop(); // unneeded AND token here
+                    max = buildTree();
+                    min = buildTree();
+                    property = buildTree();
+                    return new OpenLayers.Filter.Comparison({
+                        property: property,
+                        lowerBoundary: min,
+                        upperBoundary: max,
+                        type: OpenLayers.Filter.Comparison.BETWEEN
+                    });
+                case "COMPARISON":
+                    var value = buildTree(),
+                        property = buildTree();
+                    return new OpenLayers.Filter.Comparison({
+                        property: property,
+                        value: value,
+                        type: operators[tok.text.toUpperCase()]
+                    });
+                case "VALUE":
+                    if ((/^'.*'$/).test(tok.text)) {
+                        return tok.text.substr(1, tok.text.length - 2);
+                    } else {
+                        return Number(tok.text);
+                    }
+                case "SPATIAL":
+                    switch(tok.text.toUpperCase()) {
+                        case "BBOX":
+                            var maxy = buildTree(),
+                                maxx = buildTree(),
+                                miny = buildTree(),
+                                minx = buildTree(),
+                                prop = buildTree();
+
+                            return new OpenLayers.Filter.Spatial({
+                                type: OpenLayers.Filter.Spatial.BBOX,
+                                property: prop,
+                                value: OpenLayers.Bounds.fromArray(
+                                    [minx, miny, maxx, maxy]
+                                )
+                            });
+                        case "INTERSECTS":
+                            var value = buildTree(),
+                                property = buildTree();
+                            return new OpenLayers.Filter.Spatial({
+                                type: OpenLayers.Filter.Spatial.INTERSECTS,
+                                property: property,
+                                value: value
+                            });
+                        case "WITHIN":
+                            var value = buildTree(),
+                                property = buildTree();
+                            return new OpenLayers.Filter.Spatial({
+                                type: OpenLayers.Filter.Spatial.WITHIN,
+                                property: property,
+                                value: value
+                            });
+                        case "CONTAINS":
+                            var value = buildTree(),
+                                property = buildTree();
+                            return new OpenLayers.Filter.Spatial({
+                                type: OpenLayers.Filter.Spatial.CONTAINS,
+                                property: property,
+                                value: value
+                            });
+                        case "DWITHIN":
+                            var distance = buildTree(),
+                                value = buildTree(),
+                                property = buildTree();
+                            return new OpenLayers.Filter.Spatial({
+                                type: OpenLayers.Filter.Spatial.DWITHIN,
+                                value: value,
+                                property: property,
+                                distance: Number(distance)
+                            });
+                    }
+                case "GEOMETRY":
+                    return OpenLayers.Geometry.fromWKT(tok.text);
+                default:
+                    return tok.text;
+            }
+        }
+
+        var result = buildTree();
+        if (postfix.length > 0) {
+            var msg = "Remaining tokens after building AST: \n";
+            for (var i = postfix.length - 1; i >= 0; i--) {
+                msg += postfix[i].type + ": " + postfix[i].text + "\n";
+            }
+            throw new Error(msg);
+        }
+
+        return result;
+    }
+
+    return OpenLayers.Class(OpenLayers.Format, {
+        /**
+         * APIMethod: read
+         * Generate a filter from a CQL string.
+
+         * Parameters:
+         * text - {String} The CQL text.
+         *
+         * Returns:
+         * {<OpenLayers.Filter>} A filter based on the CQL text.
+         */
+        read: function(text) { 
+            var result = buildAst(tokenize(text));
+            if (this.keepData) {
+                this.data = result;
+            };
+            return result;
+        },
+
+        /**
+         * APIMethod: write
+         * Convert a filter into a CQL string.
+
+         * Parameters:
+         * filter - {<OpenLayers.Filter>} The filter.
+         *
+         * Returns:
+         * {String} A CQL string based on the filter.
+         */
+        write: function(filter) {
+            if (filter instanceof OpenLayers.Geometry) {
+                return filter.toString();
+            }
+            switch (filter.CLASS_NAME) {
+                case "OpenLayers.Filter.Spatial":
+                    switch(filter.type) {
+                        case OpenLayers.Filter.Spatial.BBOX:
+                            return "BBOX(" +
+                                filter.property + "," +
+                                filter.value.toBBOX() +
+                                ")";
+                        case OpenLayers.Filter.Spatial.DWITHIN:
+                            return "DWITHIN(" +
+                                filter.property + ", " +
+                                this.write(filter.value) + ", " +
+                                filter.distance + ")";
+                        case OpenLayers.Filter.Spatial.WITHIN:
+                            return "WITHIN(" +
+                                filter.property + ", " +
+                                this.write(filter.value) + ")";
+                        case OpenLayers.Filter.Spatial.INTERSECTS:
+                            return "INTERSECTS(" +
+                                filter.property + ", " +
+                                this.write(filter.value) + ")";
+                        case OpenLayers.Filter.Spatial.CONTAINS:
+                            return "CONTAINS(" +
+                                filter.property + ", " +
+                                this.write(filter.value) + ")";
+                        default:
+                            throw new Error("Unknown spatial filter type: " + filter.type);
+                    }
+                case "OpenLayers.Filter.Logical":
+                    if (filter.type == OpenLayers.Filter.Logical.NOT) {
+                        // TODO: deal with precedence of logical operators to 
+                        // avoid extra parentheses (not urgent)
+                        return "NOT (" + this.write(filter.filters[0]) + ")";
+                    } else {
+                        var res = "(";
+                        var first = true;
+                        for (var i = 0; i < filter.filters.length; i++) {
+                            if (first) {
+                                first = false;
+                            } else {
+                                res += ") " + logicalReverse[filter.type] + " (";
+                            }
+                            res += this.write(filter.filters[i]);
+                        }
+                        return res + ")";
+                    }
+                case "OpenLayers.Filter.Comparison":
+                    if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) {
+                        return filter.property + " BETWEEN " + 
+                            this.write(filter.lowerBoundary) + " AND " + 
+                            this.write(filter.upperBoundary);
+                    } else {
+                        
+                        return filter.property +
+                            " " + operatorReverse[filter.type] + " " + 
+                            this.write(filter.value);
+                    }
+                case undefined:
+                    if (typeof filter === "string") {
+                        return "'" + filter + "'";
+                    } else if (typeof filter === "number") {
+                        return String(filter);
+                    }
+                default:
+                    throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter);
+            }
+        },
+
+        CLASS_NAME: "OpenLayers.Format.CQL"
+
+    });
+})();
+

Copied: sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js (from rev 11091, trunk/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js)
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js	                        (rev 0)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,90 @@
+/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for 
+ * full list of contributors). Published under the Clear BSD license.  
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_1_WMSC
+ * Read WMS-C Capabilities version 1.1.1.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.WMSCapabilities.v1_1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC = OpenLayers.Class(
+    OpenLayers.Format.WMSCapabilities.v1_1_1, {
+    
+    /**
+     * Property: version
+     * {String} The specific parser version.
+     */
+    version: "1.1.1",
+    
+    /**
+     * Property: profile
+     * {String} The specific profile
+     */
+    profile: "WMSC",
+    
+    /**
+     * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_1
+     * Create a new parser for WMS-C capabilities version 1.1.1.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    /**
+     * Property: readers
+     * Contains public functions, grouped by namespace prefix, that will
+     *     be applied when a namespaced node is found matching the function
+     *     name.  The function will be applied in the scope of this parser
+     *     with two arguments: the node being read and a context object passed
+     *     from the parent.
+     */
+    readers: {
+        "wms": OpenLayers.Util.applyDefaults({
+            "VendorSpecificCapabilities": function(node, obj) {
+                obj.vendorSpecific = {tileSets: []};
+                this.readChildNodes(node, obj.vendorSpecific);
+            },
+            "TileSet": function(node, vendorSpecific) {
+                var tileset = {srs: {}, bbox: {}, resolutions: []};
+                this.readChildNodes(node, tileset);
+                vendorSpecific.tileSets.push(tileset);
+            },
+            "Resolutions": function(node, tileset) {
+                var res = this.getChildValue(node).split(" ");
+                for (var i=0, len=res.length; i<len; i++) {
+                    if (res[i] != "") {
+                        tileset.resolutions.push(parseFloat(res[i]));
+                    }
+                }
+            },
+            "Width": function(node, tileset) {
+                tileset.width = parseInt(this.getChildValue(node));
+            },
+            "Height": function(node, tileset) {
+                tileset.height = parseInt(this.getChildValue(node));
+            },
+            "Layers": function(node, tileset) {
+                tileset.layers = this.getChildValue(node);
+            },
+            "Styles": function(node, tileset) {
+                tileset.styles = this.getChildValue(node);
+            }
+        }, OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.readers["wms"])
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC" 
+
+});

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Format/WMSCapabilities.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -29,6 +29,15 @@
     version: null,
     
     /**
+     * APIProperty: profile
+     * {String} If provided, use a custom profile.
+     *
+     * Currently supported profiles:
+     * - WMSC - parses vendor specific capabilities for WMS-C.
+     */
+    profile: null,
+    
+    /**
      * Property: parser
      * {<OpenLayers.Format>} A cached versioned format used for reading.
      */
@@ -63,12 +72,14 @@
         }
         var root = data.documentElement;
         var version = this.version || root.getAttribute("version") || this.defaultVersion;
+        var profile = this.profile ? "_" + this.profile : "";
         if(!this.parser || this.parser.version !== version) {
             var constr = OpenLayers.Format.WMSCapabilities[
-                "v" + version.replace(/\./g, "_")
+                "v" + version.replace(/\./g, "_") + profile
             ];
             if(!constr) {
-                throw "Can't find a WMS capabilities parser for version " + version;
+                throw "Can't find a WMS capabilities parser for version " +
+                    version + profile;
             }
             this.parser = new constr(this.options);
         }

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Bing.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Bing.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Bing.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -51,28 +51,6 @@
     metadataParams: null,
 
     /**
-     * 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>):
-     * added - Triggered after the layer is added to a map.  Listeners
-     *      will receive an object with a *map* property referencing the
-     *      map and a *layer* property referencing the layer.
-     */
-    EVENT_TYPES: ["added"],
-
-    /**
      * Constructor: OpenLayers.Layer.Bing
      * Create a new Bing layer.
      *
@@ -98,15 +76,8 @@
      * Any other documented layer properties can be provided in the config object.
      */
     initialize: function(options) {
-        // concatenate events specific to vector with those from the base
-        this.EVENT_TYPES =
-            OpenLayers.Layer.Bing.prototype.EVENT_TYPES.concat(
-            OpenLayers.Layer.prototype.EVENT_TYPES
-        );
-        
         options = OpenLayers.Util.applyDefaults({
-            zoomOffset: 1,
-            maxResolution: 78271.51695,
+            restrictedMinZoom: 1,
             sphericalMercator: true
         }, options)
         var name = options.name || "Bing " + (options.type || this.type);
@@ -152,20 +123,11 @@
         for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
             this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
         };
-        var resolution;
-        if (this.map && this.map.baseLayer === this && this.map.getCenter()) {
-            // if we are the current base layer and the map was centered
-            // already, we need to remember the current resolution to find a
-            // matching zoom level after the maxResolution changes
-            resolution = this.getResolution();
-        }
         this.addOptions({
-            maxResolution: 78271.51695 / Math.pow(2, res.zoomMin - 1),
-            numZoomLevels: res.zoomMax - res.zoomMin + 1
+            restrictedMinZoom: res.zoomMin,
+            numZoomLevels: res.zoomMax + 1
         });
-        // adjust zoom level to match the previous resolution - this triggers a
-        // moveTo on all layers.
-        resolution && this.map.zoomTo(this.getZoomForResolution(resolution));
+        this.updateAttribution();
         // redraw to replace "blank.gif" tiles with real tiles
         this.redraw();
     },
@@ -207,7 +169,7 @@
      */
     updateAttribution: function() {
         var metadata = this.metadata;
-        if (!metadata || !this.map) {
+        if (!metadata || !this.map || !this.map.center) {
             return;
         }
         var res = metadata.resourceSets[0].resources[0];
@@ -241,13 +203,8 @@
      */
     setMap: function() {
         OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
-        if (this.map.getCenter()) {
-            this.updateAttribution();
-        }
+        this.updateAttribution();
         this.map.events.register("moveend", this, this.updateAttribution);
-        // TODO: move this event to Layer
-        // http://trac.osgeo.org/openlayers/ticket/2983
-        this.events.triggerEvent("added", {map: this.map, layer: this});
     },
     
     /**

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Google/v3.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Google/v3.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Google/v3.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -51,6 +51,19 @@
         projection: "EPSG:900913"
     },
 
+    /**
+     * APIProperty: animationEnabled
+     * {Boolean} If set to true, the transition between zoom levels will be
+     *     animated (if supported by the GMaps API for the device used). Set to
+     *     false to match the zooming experience of other layer types. Default
+     *     is true. Note that the GMaps API does not give us control over zoom
+     *     animation, so if set to false, when zooming, this will make the
+     *     layer temporarily invisible, wait until GMaps reports the map being
+     *     idle, and make it visible again. The result will be a blank layer
+     *     for a few moments while zooming.
+     */
+    animationEnabled: true, 
+
     /** 
      * Method: loadMapObject
      * Load the GMap and register appropriate event listeners. If we can't 
@@ -134,6 +147,11 @@
 
         var cache = OpenLayers.Layer.Google.cache[this.map.id];
         var container = this.map.viewPortDiv;
+        
+        // move the Map Data popup to the container, if any
+        while (div.lastChild.style.display == "none") {
+            container.appendChild(div.lastChild);
+        }
 
         // move the ToS and branding stuff up to the container div
         var termsOfUse = div.lastChild;
@@ -335,6 +353,17 @@
      * zoom - {int} MapObject zoom format
      */
     setMapObjectCenter: function(center, zoom) {
+        if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
+            var mapContainer = this.getMapContainer();
+            google.maps.event.addListenerOnce(
+                this.mapObject, 
+                "idle", 
+                function() {
+                    mapContainer.style.visibility = "";
+                }
+            );
+            mapContainer.style.visibility = "hidden";
+        }
         this.mapObject.setOptions({
             center: center,
             zoom: zoom

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Vector.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Vector.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Layer/Vector.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -90,6 +90,12 @@
      *      property referencing the vertex modified (always a point geometry),
      *      and a *pixel* property referencing the pixel location of the
      *      modification.
+     * vertexremoved - Triggered when a vertex within any feature geometry
+     *      has been deleted.  Listeners will receive an object with a
+     *      *feature* property referencing the modified feature, a *vertex*
+     *      property referencing the vertex modified (always a point geometry),
+     *      and a *pixel* property referencing the pixel location of the
+     *      removal.
      * sketchstarted - Triggered when a feature sketch bound for this layer
      *      is started.  Listeners will receive an object with a *feature*
      *      property referencing the new sketch feature and a *vertex* property
@@ -110,8 +116,8 @@
                   "beforefeaturesremoved", "featureremoved", "featuresremoved",
                   "beforefeatureselected", "featureselected", "featureunselected", 
                   "beforefeaturemodified", "featuremodified", "afterfeaturemodified",
-                  "vertexmodified", "sketchstarted", "sketchmodified",
-                  "sketchcomplete", "refresh"],
+                  "vertexmodified", "vertexremoved", "sketchstarted",
+                  "sketchmodified", "sketchcomplete", "refresh"],
 
     /**
      * APIProperty: isBaseLayer

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Layer.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Layer.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Layer.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -81,9 +81,15 @@
      * moveend - Triggered when layer is done moving, object passed as
      *     argument has a zoomChanged boolean property which tells that the
      *     zoom has changed.
+     * added - Triggered after the layer is added to a map.  Listeners will
+     *     receive an object with a *map* property referencing the map and a
+     *     *layer* property referencing the layer.
+     * removed - Triggered after the layer is removed from the map.  Listeners
+     *     will receive an object with a *map* property referencing the map and
+     *     a *layer* property referencing the layer.
      */
     EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged",
-                  "move", "moveend"],
+                  "move", "moveend", "added", "removed"],
 
     /**
      * Constant: RESOLUTION_PROPERTIES
@@ -268,6 +274,18 @@
      * {Integer}
      */
     numZoomLevels: null,
+    
+    /**
+     * Property: restrictedMinZoom
+     * {Integer} Restriction of the minimum zoom level. This is used for layers
+     *     that only use a subset of the resolutions in the <resolutions>
+     *     array. This is independent of <numResolutions>, which always starts
+     *     counting at zoom level 0. If restrictedMinZoom is e.g. set to 2,
+     *     the first two zoom levels (0 and 1) will not be used by this layer.
+     *     If the layer is a base layer, zooming to the map's maxExtent means
+     *     setting the map's zoom to 2.
+     */
+    restrictedMinZoom: 0,
    
     /**
      * APIProperty: minScale
@@ -734,7 +752,8 @@
         } else {
             if (this.map) {
                 var resolution = this.map.getResolution();
-                inRange = ( (resolution >= this.minResolution) &&
+                inRange = ( this.map.getZoom() >= this.restrictedMinZoom &&
+                            (resolution >= this.minResolution) &&
                             (resolution <= this.maxResolution) );
             }
         }
@@ -1165,7 +1184,7 @@
             }
             zoom = Math.max(0, i-1);
         }
-        return zoom;
+        return Math.max(this.restrictedMinZoom, zoom);
     },
     
     /**

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers/Map.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers/Map.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers/Map.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -943,6 +943,7 @@
         }
 
         this.events.triggerEvent("addlayer", {layer: layer});
+		layer.events.triggerEvent("added", {map: this, layer: layer});
         layer.afterAdd();
     },
 
@@ -1017,6 +1018,7 @@
         this.resetLayersZIndex();
 
         this.events.triggerEvent("removelayer", {layer: layer});
+		layer.events.triggerEvent("removed", {map: this, layer: layer})
     },
 
     /**
@@ -1798,8 +1800,8 @@
      *           within the min/max range of zoom levels.
      */
     isValidZoomLevel: function(zoomLevel) {
-       return ( (zoomLevel != null) &&
-                (zoomLevel >= 0) && 
+        return ( (zoomLevel != null) &&
+                (zoomLevel >= this.getRestrictedMinZoom()) && 
                 (zoomLevel < this.getNumZoomLevels()) );
     },
     
@@ -1904,6 +1906,20 @@
     },
     
     /**
+     * Method: getRestricteMinZoom
+     *
+     * Returns:
+     * {Integer} the minimum zoom level allowed for the current baseLayer.
+     */
+    getRestrictedMinZoom: function() {
+        var minZoom = null;
+        if (this.baseLayer != null) {
+            minZoom = this.baseLayer.restrictedMinZoom;
+        }
+        return minZoom;
+    },
+    
+    /**
      * APIMethod: getNumZoomLevels
      * 
      * Returns:

Modified: sandbox/elemoine/draw-feature/lib/OpenLayers.js
===================================================================
--- sandbox/elemoine/draw-feature/lib/OpenLayers.js	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/lib/OpenLayers.js	2011-02-10 13:33:20 UTC (rev 11092)
@@ -251,6 +251,7 @@
             "OpenLayers/Format/WMSDescribeLayer.js",
             "OpenLayers/Format/WMSDescribeLayer/v1_1.js",
             "OpenLayers/Format/WKT.js",
+            "OpenLayers/Format/CQL.js",
             "OpenLayers/Format/OSM.js",
             "OpenLayers/Format/GPX.js",
             "OpenLayers/Format/Filter.js",
@@ -285,6 +286,7 @@
             "OpenLayers/Format/WMSCapabilities/v1_1_1.js",
             "OpenLayers/Format/WMSCapabilities/v1_3.js",
             "OpenLayers/Format/WMSCapabilities/v1_3_0.js",
+            "OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js",
             "OpenLayers/Format/WMSGetFeatureInfo.js",
             "OpenLayers/Format/SOSCapabilities.js",
             "OpenLayers/Format/SOSCapabilities/v1_0_0.js",

Modified: sandbox/elemoine/draw-feature/tests/Control/DragFeature.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Control/DragFeature.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Control/DragFeature.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -71,11 +71,14 @@
     }
     
     function test_Control_DragFeature_over(t) {
-        t.plan(3);
+        t.plan(5);
+        var log = [];
         var map = new OpenLayers.Map("map");
         var layer = new OpenLayers.Layer.Vector();
         map.addLayer(layer);
-        var control = new OpenLayers.Control.DragFeature(layer);
+        var control = new OpenLayers.Control.DragFeature(layer, {
+            onEnter: function(f) { log.push({feature: f}); }
+        });
         map.addControl(control);
         
         control.activate();
@@ -94,6 +97,10 @@
              "control gets the proper feature from the feature handler");
         t.ok(control.handlers.drag.active,
              "drag handler activated when over a feature");
+        t.eq(log.length, 1,
+             "onEnter called exactly once");
+        t.eq(log[0].feature.id, feature.id,
+             "onEnter called with expected feature");
     }
 
     function test_Control_DragFeature_down(t) {
@@ -241,11 +248,14 @@
     }
 
     function test_Control_DragFeature_out(t) {
-        t.plan(2);
+        t.plan(4);
+        var log = [];
         var map = new OpenLayers.Map("map");
         var layer = new OpenLayers.Layer.Vector();
         map.addLayer(layer);
-        var control = new OpenLayers.Control.DragFeature(layer);
+        var control = new OpenLayers.Control.DragFeature(layer, {
+            onLeave: function(f) { log.push({feature: f}); }
+        });
         map.addControl(control);
 
         control.activate();
@@ -268,7 +278,10 @@
         map.events.triggerEvent("mousemove", {type: "mousemove"});
         t.ok(control.feature == null,
              "feature is set to null on mouse out");
-        
+        t.eq(log.length, 1,
+             "onLeave called exactly once");
+        t.eq(log[0].feature.id, feature.id,
+             "onLeave called with expected feature");
     }
 
     </script>

Modified: sandbox/elemoine/draw-feature/tests/Control/ModifyFeature.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Control/ModifyFeature.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Control/ModifyFeature.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -74,7 +74,7 @@
     }
     
     function test_handleKeypress(t) {
-        t.plan(11);
+        t.plan(14);
 
         /**
          * There are two things that we want to test here
@@ -108,9 +108,17 @@
                      "vertex deletion: removeComponent called on parent with proper geometry");
             }
         };
-        layer.events.on({"featuremodified": function(event) {
-            t.eq(event.feature.id, poly.id, "vertex deletion: featuremodifed triggered");
-        }});
+        layer.events.on({
+            "featuremodified": function(event) {
+                t.eq(event.feature.id, poly.id, "vertex deletion: featuremodifed triggered");
+            },
+            "vertexremoved": function(evt) {
+                layer.events.unregister("vertexremoved", this, arguments.callee);
+                t.eq(evt.feature.id, poly.id, "vertexremoved triggered with correct feature");
+                t.eq(evt.vertex.id, point.geometry.id, "vertexremoved triggered with correct vertex");
+                t.eq(evt.pixel, "foo", "vertexremoved triggered with correct pixel");
+            }
+        });
         layer.drawFeature = function(feature) {
             t.eq(feature.id, poly.id,
                  "vertex deletion: drawFeature called with the proper feature");
@@ -123,7 +131,7 @@
                  "vertex deletion: onModification called with the proper feature");
         };
         // run the above four tests twice
-        control.handleKeypress({keyCode:delKey});
+        control.handleKeypress({keyCode:delKey, xy: "foo"});
         control.handleKeypress({keyCode:dKey});
         t.eq(control.feature.state, OpenLayers.State.UPDATE, "feature state set to update");
 

Modified: sandbox/elemoine/draw-feature/tests/Control/PanPanel.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Control/PanPanel.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Control/PanPanel.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -3,7 +3,7 @@
   <script src="../../lib/OpenLayers.js"></script>
   <script type="text/javascript">
     function test_constructor (t) {
-        t.plan(1);
+        t.plan(2);
         
         // set up
         var control;
@@ -15,9 +15,47 @@
              control.controls[2].slideFactor == 200 &&
              control.controls[3].slideFactor == 200,
              "ctor sets slideFactor in all Pan controls");
+        
+        control.destroy();
+
+        control = new OpenLayers.Control.PanPanel({slideRatio: .5});
+        t.ok(control.controls[0].slideRatio == .5 &&
+             control.controls[1].slideRatio == .5 &&
+             control.controls[2].slideRatio == .5 &&
+             control.controls[3].slideRatio == .5,
+             "ctor sets slideRatio in all Pan controls");
+        
+        control.destroy();
     }
+    
+    function test_slide(t) {
+        t.plan(2);
+        var map = new OpenLayers.Map("map", {
+            panMethod: null,
+            controls: [
+                new OpenLayers.Control.PanPanel(),
+                new OpenLayers.Control.PanPanel({slideRatio: .5})
+            ],
+            layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+            center: new OpenLayers.LonLat(0, 0),
+            zoom: 1
+        });
+        
+        map.controls[0].controls[0].trigger();
+        map.controls[0].controls[2].trigger();
+        map.pan(-50, 50);
+        t.eq(map.getCenter().toShortString(), "0, 0", "correct pan distance with slideFactor");
+        
+        map.controls[1].controls[0].trigger();
+        map.controls[1].controls[2].trigger();
+        map.pan(-128, 64);
+        t.eq(map.getCenter().toShortString(), "0, 0", "correct pan distance with slideRatio");        
+
+        map.destroy();
+    }
   </script>
 </head>
 <body>
+    <div id="map" style="width: 256px; height: 128px;"></div>
 </body>
 </html>

Modified: sandbox/elemoine/draw-feature/tests/Control/WMSGetFeatureInfo.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Control/WMSGetFeatureInfo.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Control/WMSGetFeatureInfo.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -233,7 +233,7 @@
     // Verify that things work all right when we combine different types for the STYLES and LAYERS
     // params in the WMS Layers involved
     function test_mixedParams(t) {
-        t.plan(3);
+        t.plan(5);
         var map = new OpenLayers.Map("map", {
             getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
             }
@@ -271,10 +271,22 @@
             log.options = options;
         };
         click.activate();
-        click.getInfoForClick({xy: {x: 50, y: 50}});
+        click.getInfoForClick({xy: {x: 50.2, y: 50.1}});
         OpenLayers.Request.GET = _request;
 
         t.eq(
+            log.options && log.options.params.X,
+            50,
+            "X should be an integer"
+        );
+
+        t.eq(
+            log.options && log.options.params.Y,
+            50,
+            "Y should be an integer" 
+        );
+
+        t.eq(
             log.options && log.options.url,
             "http://localhost/wms",
             "url from first layer used"
@@ -506,7 +518,7 @@
             log.options = options;
         };
         click.activate();
-        click.getInfoForClick({xy: {x: 50, y: 60}});
+        click.getInfoForClick({xy: {x: 50.1, y: 60.2}});
         OpenLayers.Request.GET = _request;
         t.eq(
             log.options && log.options.params.CRS,

Copied: sandbox/elemoine/draw-feature/tests/Format/CQL.html (from rev 11091, trunk/openlayers/tests/Format/CQL.html)
===================================================================
--- sandbox/elemoine/draw-feature/tests/Format/CQL.html	                        (rev 0)
+++ sandbox/elemoine/draw-feature/tests/Format/CQL.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,287 @@
+<html>
+    <head>
+        <script src="../../lib/OpenLayers.js"></script>
+
+        <script type="text/javascript">
+
+function test_CQL_Constructor(t) {
+    t.plan(5);
+    var options = {'foo': 'bar'};
+    var format  = new OpenLayers.Format.CQL(options);
+    t.ok(format instanceof OpenLayers.Format.CQL,
+         "new OpenLayers.Format.CQL object");
+    t.eq(format.foo, "bar", "constructor sets options correctly")
+    t.eq(typeof format.read, 'function', 'format has a read function');
+    t.eq(typeof format.write, 'function', 'format has a write function');
+    t.eq(format.options, options, "format.options correctly set");
+}
+
+function test_Comparison_string(t) {
+    t.plan(5);
+    var test_cql, format, filter;
+    test_cql = "X >= 'B'";
+    format = new OpenLayers.Format.CQL();
+    filter = format.read(test_cql);
+    t.ok(filter instanceof OpenLayers.Filter.Comparison,
+         "Parsing a simple >= filter produces a Filter.Comparison");
+    t.eq(filter.type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+         ">= parsed as Filter.Comparison.GREATER_THAN_OR_EQUAL_TO");
+    t.eq(filter.property, 'X',
+         "Property extracted from CQL text");
+    t.eq(filter.value, 'B',
+         "Value extracted from CQL text");
+         
+         
+    t.eq(format.write(filter), test_cql, "write returned test cql");
+}
+
+function test_Comparison_number(t) {
+    t.plan(5);
+    var test_cql, format, filter;
+    test_cql = "X >= 10";
+    format = new OpenLayers.Format.CQL();
+    filter = format.read(test_cql);
+    t.ok(filter instanceof OpenLayers.Filter.Comparison,
+         "Parsing a simple >= filter produces a Filter.Comparison");
+    t.eq(filter.type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+         ">= parsed as Filter.Comparison.GREATER_THAN_OR_EQUAL_TO");
+    t.eq(filter.property, 'X',
+         "Property extracted from CQL text");
+    t.eq(filter.value, 10,
+         "Value extracted from CQL text");
+         
+         
+    t.eq(format.write(filter), test_cql, "write returned test cql");
+}
+
+function test_Logical(t) {
+    t.plan(7);
+    var test_cql, format, filter;
+    test_cql = "X >= 'B' AND X < 'M'";
+    format = new OpenLayers.Format.CQL();
+    filter = format.read(test_cql);
+    t.ok(filter instanceof OpenLayers.Filter.Logical,
+         "Parsing ANDed filters produces a Filter.Logical");
+    t.eq(filter.type, OpenLayers.Filter.Logical.AND,
+         "AND parsed as Filter.Logical.AND");
+    t.eq(filter.filters.length, 2,
+         "AND Filter contains two subfilters");
+    t.ok(filter.filters[0] instanceof OpenLayers.Filter.Comparison,
+         "First sub-filter is a Filter.Comparison");
+    t.eq(filter.filters[0].type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+         "First sub-filter is the first filter in the CQL text");
+    t.ok(filter.filters[1] instanceof OpenLayers.Filter.Comparison,
+         "Second sub-filter is a Filter.Comparison");
+    t.eq(filter.filters[1].type, OpenLayers.Filter.Comparison.LESS_THAN,
+         "Second sub-filter is the second filter in the CQL text");
+
+}
+
+function test_Logical_write(t) {
+    t.plan(1);
+    var cql = "(X >= 'B') AND (X < 'M')";
+    var format = new OpenLayers.Format.CQL();
+    var filter = format.read(cql);
+    t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_Logical_spatial(t) {
+    t.plan(9);
+    var test_cql, format, filter;
+    test_cql = "INTERSECTS(the_geom, POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41))) AND CONTAINS(the_geom, POINT(-111 41))";
+    format = new OpenLayers.Format.CQL();
+    filter = format.read(test_cql);
+    t.ok(filter instanceof OpenLayers.Filter.Logical,
+         "Parsing ANDed filters produces a Filter.Logical");
+    t.eq(filter.type, OpenLayers.Filter.Logical.AND,
+         "AND parsed as Filter.Logical.AND");
+    t.eq(filter.filters.length, 2,
+         "AND Filter contains two subfilters");
+    t.ok(filter.filters[0] instanceof OpenLayers.Filter.Spatial,
+         "First sub-filter is a Filter.Spatial");
+    t.eq(filter.filters[0].type, OpenLayers.Filter.Spatial.INTERSECTS,
+         "First sub-filter is the first filter in the CQL text");
+    t.geom_eq(filter.filters[0].value, OpenLayers.Geometry.fromWKT("POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41))"),
+         "First sub-filter is has correct geometry");
+    t.ok(filter.filters[1] instanceof OpenLayers.Filter.Spatial,
+         "Second sub-filter is a Filter.Comparison");
+    t.eq(filter.filters[1].type, OpenLayers.Filter.Spatial.CONTAINS,
+         "Second sub-filter is the second filter in the CQL text");
+    t.geom_eq(filter.filters[1].value, OpenLayers.Geometry.fromWKT("POINT(-111 41)"),
+         "Second sub-filter is has correct geometry");
+}
+
+function test_Logical_spatial_write(t) {
+    // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+    t.plan(1);
+    var cql = "(INTERSECTS(the_geom, POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41)))) AND (CONTAINS(the_geom, POINT(-111 41)))";
+    var format = new OpenLayers.Format.CQL();
+    var filter = format.read(cql);
+    t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_Parentheticals(t) {
+    t.plan(2);
+    var format, cqlA, filterA, cqlB, filterB;
+    format = new OpenLayers.Format.CQL();
+    cqlA = "A = '1' AND B = '2' OR C = '3'";
+    cqlB = "A = '1' AND (B = '2' OR C = '3')";
+    filterA = format.read(cqlA);
+    filterB = format.read(cqlB);
+
+    t.ok(filterA instanceof OpenLayers.Filter.Logical &&
+         filterA.filters[0] instanceof OpenLayers.Filter.Logical &&
+         filterA.filters[1] instanceof OpenLayers.Filter.Comparison,
+         "Unparenthesized expression groups left to right");
+    t.ok(filterB instanceof OpenLayers.Filter.Logical &&
+         filterB.filters[0] instanceof OpenLayers.Filter.Comparison &&
+         filterB.filters[1] instanceof OpenLayers.Filter.Logical,
+         "Parenthesized expression groups as specified by parentheses");
+}
+
+function test_Parentheticals_write(t) {
+    // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+    t.plan(1);
+    var format = new OpenLayers.Format.CQL();
+    var cql = "(A = '1') AND ((B = '2') OR (C = '3'))";
+    var filter = format.read(cql);
+    t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_BBOX(t) {
+    t.plan(5);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "BBOX(the_geom,1,2,3,4)",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Spatial,
+         "Parsing BBOX expression produces Filter.Spatial");
+    t.eq(filter.type, OpenLayers.Filter.Spatial.BBOX,
+         "Spatial filter is a bbox filter");
+    t.eq(filter.property, "the_geom",
+         "Property name is as specified in CQL");
+    t.eq(filter.value.toBBOX(), "1,2,3,4",
+         "Value is as specified in CQL");
+
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_INTERSECTS(t) {
+    t.plan(5);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "INTERSECTS(the_geom, POINT(1 2))",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Spatial,
+         "Parsing BBOX expression produces Filter.Spatial");
+    t.eq(filter.type, OpenLayers.Filter.Spatial.INTERSECTS,
+         "Spatial filter is an intersects filter");
+    t.eq(filter.property, "the_geom",
+         "Property name is as specified in CQL");
+    t.ok(filter.value instanceof OpenLayers.Geometry,
+         "Value is a geometry");
+    
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_WITHIN(t) {
+    t.plan(5);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "WITHIN(the_geom, POLYGON((1 2,3 4,5 6,3 8,1 6,1 2)))",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Spatial,
+         "Parsing BBOX expression produces Filter.Spatial");
+    t.eq(filter.type, OpenLayers.Filter.Spatial.WITHIN,
+         "Spatial filter is a within filter");
+    t.eq(filter.property, "the_geom",
+         "Property name is as specified in CQL");
+    t.ok(filter.value instanceof OpenLayers.Geometry,
+         "Value is a geometry");
+
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_DWITHIN(t) {
+    t.plan(6);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "DWITHIN(the_geom, POINT(1 2), 6)",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Spatial,
+         "Parsing DWITHIN expression produces Filter.Spatial");
+    t.eq(filter.type, OpenLayers.Filter.Spatial.DWITHIN,
+         "Spatial filter is a DWITHIN filter");
+    t.eq(filter.property, "the_geom",
+         "Property name is as specified in CQL");
+    t.ok(filter.value instanceof OpenLayers.Geometry,
+         "Value is a geometry");
+    t.eq(filter.distance, 6,
+         "Distance is as specified in CQL");
+
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_CONTAINS(t) {
+    t.plan(5);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "CONTAINS(the_geom, POINT(1 2))",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Spatial,
+         "Parsing BBOX expression produces Filter.Spatial");
+    t.eq(filter.type, OpenLayers.Filter.Spatial.CONTAINS,
+         "Spatial filter is a within filter");
+    t.eq(filter.property, "the_geom",
+         "Property name is as specified in CQL");
+    t.ok(filter.value instanceof OpenLayers.Geometry,
+         "Value is a geometry");
+
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_NOT(t) {
+    t.plan(4);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "NOT X < 12",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Logical,
+         "Parsing NOT expression produces Logical.Not");
+    t.eq(filter.type, OpenLayers.Filter.Logical.NOT,
+         "Logical filter is a NOT filter");
+    t.eq(filter.filters[0].property, "X",
+         "Property name is as specified in CQL");
+    t.eq(filter.filters[0].value, 12, "Value is as specified in CQL");
+}
+
+function test_NOT_write(t) {
+    // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+    t.plan(1);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "NOT (X < 12)",
+        filter = format.read(cql);
+    t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_BETWEEN(t) {
+    t.plan(6);
+    var format = new OpenLayers.Format.CQL(),
+        cql = "A BETWEEN 0 AND 5",
+        filter = format.read(cql);
+    t.ok(filter instanceof OpenLayers.Filter.Comparison,
+         "Parsing BETWEEN expression produces Filter.Comparison");
+    t.eq(filter.type, OpenLayers.Filter.Comparison.BETWEEN,
+         "Comparison filter is a between filter");
+    t.eq(filter.property, "A",
+         "Property name is as specified in CQL");
+    t.eq(filter.lowerBoundary, 0, 'Lower boundary is as specified in CQL');
+    t.eq(filter.upperBoundary, 5, 'Upper bondary is as specified in CQL');
+
+    t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+        </script>
+    </head>
+    <body></body>
+</html>

Modified: sandbox/elemoine/draw-feature/tests/Format/WFST/v1_1_0.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Format/WFST/v1_1_0.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Format/WFST/v1_1_0.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -34,6 +34,21 @@
         t.eq(result.numberOfFeatures, 625, "numberOfFeatures of FeatureCollection correctly read");
     }
 
+    function test_read_boundedBy(t) {
+        t.plan(4);
+        var data = readXML("boundedBy");
+        var format = new OpenLayers.Format.WFST.v1_1_0({
+            featureNS: "http://mapserver.gis.umn.edu/mapserver",
+            featureType: "AAA212"
+        });
+        var result = format.read(data, {output: "object"});
+        var bounds = result.bounds;
+        t.eq(bounds.left.toFixed(3), '3197.880', "Left bounds of the feature collection correctly parsed");
+        t.eq(bounds.bottom.toFixed(3), '306457.313', "Bottom bounds of the feature collection correctly parsed");
+        t.eq(bounds.right.toFixed(3), '280339.156', "Right bounds of the feature collection correctly parsed");
+        t.eq(bounds.top.toFixed(3), '613850.438', "Top bounds of the feature collection corectly parsed");
+    }
+
     function test_write(t) {
 
         var format = new OpenLayers.Format.WFST.v1_1_0({
@@ -135,5 +150,55 @@
     </wfs:Query>
 </wfs:GetFeature>
 --></div>
+<div id="boundedBy"><!--
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+   xmlns:rws="http://mapserver.gis.umn.edu/mapserver"
+   xmlns:gml="http://www.opengis.net/gml"
+   xmlns:wfs="http://www.opengis.net/wfs"
+   xmlns:ogc="http://www.opengis.net/ogc"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://mapserver.gis.umn.edu/mapserver http://ontwikkel.intranet.rijkswaterstaat.nl/services/geoservices/ov_zonering?SERVICE=WFS&amp;VERSION=1.1.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=AAA212&amp;OUTPUTFORMAT=text/xml; subtype=gml/3.1.1  http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
+      <gml:boundedBy>
+      	<gml:Envelope srsName="EPSG:28992">
+      		<gml:lowerCorner>3197.880000 306457.313000</gml:lowerCorner>
+      		<gml:upperCorner>280339.156000 613850.438000</gml:upperCorner>
+      	</gml:Envelope>
+      </gml:boundedBy>
+    <gml:featureMember>
+
+      <rws:AAA212 gml:id="AAA212.791">
+        <gml:boundedBy>
+        	<gml:Envelope srsName="EPSG:28992">
+        		<gml:lowerCorner>196507.469000 502347.938000</gml:lowerCorner>
+        		<gml:upperCorner>202430.844000 510383.719000</gml:upperCorner>
+        	</gml:Envelope>
+        </gml:boundedBy>
+        <rws:geometry>
+
+          <gml:MultiSurface srsName="EPSG:28992">
+            <gml:surfaceMembers>
+              <gml:Polygon>
+                <gml:exterior>
+                  <gml:LinearRing>
+                    <gml:posList srsDimension="2">200448.047000 510383.719000 198475.031000 509253.875000 198477.422000 507339.688000 196507.469000 505841.969000 196507.625000 504980.281000 196621.359000 505029.969000 196825.328000 505114.000000 197310.031000 505183.469000 197636.609000 505148.750000 197837.594000 505061.563000 197941.031000 504953.688000 198003.094000 504817.719000 198023.781000 504721.688000 198016.391000 504597.531000 197907.234000 504363.219000 197716.734000 504013.969000 197700.156000 503567.563000 197775.531000 503373.969000 197930.688000 503153.781000 198034.234000 503045.594000 198170.078000 502932.125000 198504.047000 502725.250000 198858.719000 502550.875000 199138.000000 502460.719000 199336.000000 502347.938000 199044.125000 504910.969000 199549.359000 507065.781000 200280.594000 506878.938000 202430.844000 507474.625000 202430.844000 508850.906000 200448.047000 510383.719000 </gml:posList>
+                  </gml:LinearRing>
+                </gml:exterior>
+
+              </gml:Polygon>
+            </gml:surfaceMembers>
+          </gml:MultiSurface>
+        </rws:geometry>
+        <rws:OBJECTID>791</rws:OBJECTID>
+        <rws:HECTARES>1800.89</rws:HECTARES>
+        <rws:ZONENR>4620</rws:ZONENR>
+
+        <rws:NULZONES> </rws:NULZONES>
+        <rws:AREA>0</rws:AREA>
+        <rws:PERIMETER>24305.1</rws:PERIMETER>
+      </rws:AAA212>
+    </gml:featureMember>
+</wfs:FeatureCollection>
+--></div>
 </body>
 </html>

Copied: sandbox/elemoine/draw-feature/tests/Format/WMSCapabilities/v1_1_1_WMSC.html (from rev 11091, trunk/openlayers/tests/Format/WMSCapabilities/v1_1_1_WMSC.html)
===================================================================
--- sandbox/elemoine/draw-feature/tests/Format/WMSCapabilities/v1_1_1_WMSC.html	                        (rev 0)
+++ sandbox/elemoine/draw-feature/tests/Format/WMSCapabilities/v1_1_1_WMSC.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -0,0 +1,212 @@
+<html> 
+<head> 
+    <script src="../../../lib/OpenLayers.js"></script>
+    <script type="text/javascript">
+    
+    function test_read(t) {
+        
+        t.plan(9);
+
+        var xml = document.getElementById("wmsc").firstChild.nodeValue;
+        var doc = new OpenLayers.Format.XML().read(xml);
+        var format = new OpenLayers.Format.WMSCapabilities({profile: "WMSC"});
+        var obj = format.read(doc);
+        var tilesets = obj.capability.vendorSpecific.tileSets;
+        t.eq(tilesets.length, 2, "We expect 2 tilesets to be parsed");
+        var tileset = tilesets[0];
+        t.eq(tileset.bbox["EPSG:900913"].bbox, [-13697515.466796875, 5165920.118906248, -13619243.94984375, 5244191.635859374], "BBOX correctly parsed");
+        t.eq(tileset.format, "image/png", "Format correctly parsed");
+        t.eq(tileset.height, 256, "Height correctly parsed");
+        t.eq(tileset.width, 256, "Width correctly parsed");
+        t.eq(tileset.layers, "medford:hydro", "Layers correctly parsed");
+        t.eq(tileset.srs["EPSG:900913"], true, "SRS correctly parsed");
+        t.eq(tileset.resolutions, [156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135, 0.29858214169740677, 0.14929107084870338, 0.07464553542435169, 0.037322767712175846, 0.018661383856087923, 0.009330691928043961, 0.004665345964021981], "Resolutions correctly parsed");
+        t.eq(tileset.styles, "", "Styles correctly parsed");
+    }
+
+    </script> 
+</head> 
+<body>
+
+<div id="wmsc"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://schemas.opengis.net/wms/1.1.1/capabilities_1_1_1.dtd"[
+<!ELEMENT VendorSpecificCapabilities (TileSet*) >
+<!ELEMENT TileSet (SRS, BoundingBox?, Resolutions, Width, Height, Format, Layers*, Styles*) >
+<!ELEMENT Resolutions (#PCDATA) >
+<!ELEMENT Width (#PCDATA) >
+<!ELEMENT Height (#PCDATA) >
+<!ELEMENT Layers (#PCDATA) >
+<!ELEMENT Styles (#PCDATA) >
+]>
+<WMT_MS_Capabilities version="1.1.1" updateSequence="57">
+  <Service>
+    <Name>OGC:WMS</Name>
+    <Title>GeoServer Web Map Service</Title>
+    <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+    <KeywordList>
+      <Keyword>WFS</Keyword>
+      <Keyword>WMS</Keyword>
+      <Keyword>GEOSERVER</Keyword>
+    </KeywordList>
+    <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms"/>
+    <ContactInformation>
+      <ContactPersonPrimary>
+        <ContactPerson>OpenGeo</ContactPerson>
+        <ContactOrganization>OpenGeo</ContactOrganization>
+      </ContactPersonPrimary>
+      <ContactPosition>Outreach</ContactPosition>
+      <ContactAddress>
+        <AddressType>Work</AddressType>
+        <Address/>
+        <City>New York</City>
+        <StateOrProvince/>
+        <PostCode/>
+        <Country>USA</Country>
+      </ContactAddress>
+      <ContactVoiceTelephone/>
+      <ContactFacsimileTelephone/>
+      <ContactElectronicMailAddress>inquiry at opengeo.org</ContactElectronicMailAddress>
+    </ContactInformation>
+    <Fees>NONE</Fees>
+    <AccessConstraints>NONE</AccessConstraints>
+  </Service>
+  <Capability>
+    <Request>
+      <GetCapabilities>
+        <Format>application/vnd.ogc.wms_xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+            <Post>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Post>
+          </HTTP>
+        </DCPType>
+      </GetCapabilities>
+      <GetMap>
+        <Format>image/png</Format>
+        <Format>application/atom xml</Format>
+        <Format>application/atom+xml</Format>
+        <Format>application/openlayers</Format>
+        <Format>application/pdf</Format>
+        <Format>application/rss xml</Format>
+        <Format>application/rss+xml</Format>
+        <Format>application/vnd.google-earth.kml</Format>
+        <Format>application/vnd.google-earth.kml xml</Format>
+        <Format>application/vnd.google-earth.kml+xml</Format>
+        <Format>application/vnd.google-earth.kmz</Format>
+        <Format>application/vnd.google-earth.kmz xml</Format>
+        <Format>application/vnd.google-earth.kmz+xml</Format>
+        <Format>atom</Format>
+        <Format>image/geotiff</Format>
+        <Format>image/geotiff8</Format>
+        <Format>image/gif</Format>
+        <Format>image/jpeg</Format>
+        <Format>image/png8</Format>
+        <Format>image/svg</Format>
+        <Format>image/svg xml</Format>
+        <Format>image/svg+xml</Format>
+        <Format>image/tiff</Format>
+        <Format>image/tiff8</Format>
+        <Format>kml</Format>
+        <Format>kmz</Format>
+        <Format>openlayers</Format>
+        <Format>rss</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+          </HTTP>
+        </DCPType>
+      </GetMap>
+      <GetFeatureInfo>
+        <Format>text/plain</Format>
+        <Format>application/vnd.ogc.gml</Format>
+        <Format>text/html</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+            <Post>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Post>
+          </HTTP>
+        </DCPType>
+      </GetFeatureInfo>
+      <DescribeLayer>
+        <Format>application/vnd.ogc.wms_xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+          </HTTP>
+        </DCPType>
+      </DescribeLayer>
+      <GetLegendGraphic>
+        <Format>image/png</Format>
+        <Format>image/jpeg</Format>
+        <Format>image/gif</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+          </HTTP>
+        </DCPType>
+      </GetLegendGraphic>
+      <GetStyles>
+        <Format>application/vnd.ogc.sld+xml</Format>
+        <DCPType>
+          <HTTP>
+            <Get>
+              <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+            </Get>
+          </HTTP>
+        </DCPType>
+      </GetStyles>
+    </Request>
+    <Exception>
+      <Format>application/vnd.ogc.se_xml</Format>
+      <Format>application/vnd.ogc.se_inimage</Format>
+    </Exception>
+    <VendorSpecificCapabilities>
+      <TileSet>
+        <SRS>EPSG:900913</SRS>
+        <BoundingBox SRS="EPSG:900913" minx="-1.3697515466796875E7" miny="5165920.118906248" maxx="-1.361924394984375E7" maxy="5244191.635859374"/>
+        <Resolutions>156543.03390625 78271.516953125 39135.7584765625 19567.87923828125 9783.939619140625 4891.9698095703125 2445.9849047851562 1222.9924523925781 611.4962261962891 305.74811309814453 152.87405654907226 76.43702827453613 38.218514137268066 19.109257068634033 9.554628534317017 4.777314267158508 2.388657133579254 1.194328566789627 0.5971642833948135 0.29858214169740677 0.14929107084870338 0.07464553542435169 0.037322767712175846 0.018661383856087923 0.009330691928043961 0.004665345964021981 </Resolutions>
+        <Width>256</Width>
+        <Height>256</Height>
+        <Format>image/png</Format>
+        <Layers>medford:hydro</Layers>
+        <Styles/>
+      </TileSet>
+      <TileSet>
+        <SRS>EPSG:4326</SRS>
+        <BoundingBox SRS="EPSG:4326" minx="-123.046875" miny="42.1875" maxx="-122.6953125" maxy="42.5390625"/>
+        <Resolutions>0.703125 0.3515625 0.17578125 0.087890625 0.0439453125 0.02197265625 0.010986328125 0.0054931640625 0.00274658203125 0.001373291015625 6.866455078125E-4 3.4332275390625E-4 1.71661376953125E-4 8.58306884765625E-5 4.291534423828125E-5 2.1457672119140625E-5 1.0728836059570312E-5 5.364418029785156E-6 2.682209014892578E-6 1.341104507446289E-6 6.705522537231445E-7 3.3527612686157227E-7 1.6763806343078613E-7 8.381903171539307E-8 4.190951585769653E-8 2.0954757928848267E-8 </Resolutions>
+        <Width>256</Width>
+        <Height>256</Height>
+        <Format>image/gif</Format>
+        <Layers>medford</Layers>
+        <Styles/>
+      </TileSet>
+    </VendorSpecificCapabilities>
+    <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1" RemoteWFS="1"/>
+    <Layer queryable="0" opaque="0" noSubsets="0">
+      <Title>GeoServer Web Map Service</Title>
+      <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+      <SRS>EPSG:4326</SRS>
+      <SRS>EPSG:900913</SRS>
+      <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="83.624"/>
+    </Layer>
+  </Capability>
+</WMT_MS_Capabilities>
+--></div>
+</body> 
+</html> 

Modified: sandbox/elemoine/draw-feature/tests/Layer/Bing.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Layer/Bing.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Layer/Bing.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -24,7 +24,7 @@
         };
         layer = new OpenLayers.Layer.Bing({metadataParams: {foo: "bar"}});
         t.ok(layer instanceof OpenLayers.Layer.Bing, "returns OpenLayers.Layer.Bing object" );
-        t.delay_call(2, function() {
+        t.delay_call(5, function() {
             t.eq(log.length, 1, "processMetadata called");
             t.eq(OpenLayers.Util.getParameters(log[0]).foo, "bar", "metadataParams passed to url correctly.");
             OpenLayers.Layer.Bing.processMetadata = origProcessMetadata;
@@ -66,13 +66,13 @@
         })]);
         map2.zoomToMaxExtent();
         
-        t.delay_call(2.0, function() {
+        t.delay_call(5, function() {
             origProcessMetadata.call(layer, meta[0]);
             t.eq(extent.toBBOX(), map.getExtent().toBBOX(), "layer extent correct for base layer with zoomMin == 1.");
             map.destroy();
         });
 
-        t.delay_call(2.5, function() {
+        t.delay_call(6, function() {
             origProcessMetadata.call(layer2, meta[1]);
             t.eq(extent2.toBBOX(), map2.getExtent().toBBOX(), "layer extent correct for base layer with zoomMin == 2.");
             map2.destroy();

Modified: sandbox/elemoine/draw-feature/tests/Layer.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Layer.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Layer.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -556,7 +556,7 @@
 
     function test_Layer_getZoomForResolution(t) {
 
-        t.plan(12);
+        t.plan(13);
 
         var layer = new OpenLayers.Layer('Test Layer');
         layer.map = {};
@@ -584,6 +584,9 @@
              "(fractionalZoom) doesn't return zoom below zero");
         t.eq(layer.getZoomForResolution(1).toPrecision(6), (layer.resolutions.length - 1).toPrecision(6),
              "(fractionalZoom) doesn't return zoom above highest index");
+        
+        layer.restrictedMinZoom = 1;
+        t.eq(layer.getZoomForResolution(200), 1, "zoom all the way out, but we have a restrictedMinZoom of 1");
 
     }
     
@@ -751,16 +754,27 @@
     
     function test_afterAdd(t) {
         
-        t.plan(1);
+        t.plan(4);
         
+        var log = [];
         var map = new OpenLayers.Map("map");
-        var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+        var layer = new OpenLayers.Layer(null, {
+            isBaseLayer: true,
+            eventListeners: {
+                "added": function(evt) {
+                    log.push(evt);
+                }
+            }
+        });
         var hasBase = false;
         layer.afterAdd = function() {
             hasBase = !!(layer.map && layer.map.baseLayer);
         }
         map.addLayer(layer);
         t.eq(hasBase, true, "when afterAdd is called, map has a base layer");
+        t.eq(log.length, 1, "added event triggered");
+        t.eq(log[0].map.id, map.id, "added listener argument with correct map");
+        t.eq(log[0].layer.id, layer.id, "added listener argument with correct layer");
         
     }
     
@@ -806,11 +820,18 @@
 
 
     function test_Layer_destroy (t) {
-        t.plan( 5 );    
+        t.plan( 8 );    
 
+        var log = [];
         var map = new OpenLayers.Map('map');
         
-        layer = new OpenLayers.Layer('Test Layer');
+        layer = new OpenLayers.Layer('Test Layer', {
+            eventListeners: {
+                "removed": function(evt) {
+                    log.push(evt);
+                }
+            }
+        });
 
         map.addLayer(layer);
 
@@ -822,6 +843,9 @@
         t.eq( layer.options, null, "layer.options is null after destroy" );
 
         t.eq(map.layers.length, 0, "layer removed from map");
+        t.eq(log.length, 1, "removed event triggered");
+        t.eq(log[0].map.id, map.id, "removed listener argument with correct map");
+        t.eq(log[0].layer.id, layer.id, "removed listener argument with correct layer");
         
         map.destroy();
 

Modified: sandbox/elemoine/draw-feature/tests/Map.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/Map.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/Map.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -326,6 +326,34 @@
     }
  */
 
+    function test_Map_isValidZoomLevel(t) {
+        t.plan(6);
+        var map = new OpenLayers.Map("map");
+        map.addLayer(new OpenLayers.Layer(null, {
+            isBaseLayer: true, numZoomLevels: 19
+        }))
+        var valid;
+
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [0]);
+        t.eq(valid, true, "0 is a valid zoomLevel when baseLayer has no restrictedMinZoom");
+
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [18]);
+        t.eq(valid, true, "18 is a valid zoomLevel");
+
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [19]);
+        t.eq(valid, false, "19 is not a valid zoomLevel");
+        
+        map.baseLayer.restrictedMinZoom = 1;
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [0]);
+        t.eq(valid, false, "0 is not a valid zoomLevel when baseLayer has restrictedMinZoom of 1");
+
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [1]);
+        t.eq(valid, true, "1 is a valid zoomLevel");
+
+        valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [19]);
+        t.eq(valid, false, "19 is not a valid zoomLevel when baseLayer has restrictedMinZoom of 1");
+    }
+    
     function test_Map_isValidLonLat(t) {
         t.plan( 3 );    
 
@@ -699,11 +727,12 @@
     function test_Map_removeLayer(t) {
         t.plan(1);
         var f = function() {};
+        var events = {triggerEvent: f};
         var layers = [
-            {name: "fee", removeMap: f},
-            {name: "fi", removeMap: f},
-            {name: "fo", removeMap: f},
-            {name: "fum", removeMap: f}
+            {name: "fee", removeMap: f, events: events},
+            {name: "fi", removeMap: f, events: events},
+            {name: "fo", removeMap: f, events: events},
+            {name: "fum", removeMap: f, events: events}
         ];
         var map = {
             layers: layers,
@@ -1233,6 +1262,27 @@
         t.ok(maxExtent == map.baseLayer.maxExtent, "null options, valid baseLayer returns map.baseLayer.maxExtent");     
     }
 
+    function test_Map_getRestrictedMinZoom(t){
+        t.plan(3);
+
+        var map = {};
+
+      //no baseLayer
+        var minZoom = OpenLayers.Map.prototype.getRestrictedMinZoom.apply(map);
+        t.eq(minZoom, null, "no baseLayer returns null");
+        
+        map.baseLayer = new OpenLayers.Layer(null, {isBaseLayer: true});
+
+      //baseLayer
+        minZoom = OpenLayers.Map.prototype.getRestrictedMinZoom.apply(map);
+        t.eq(minZoom, 0, "default baseLayer.restrictedMinZoom returns 0");     
+
+      //custom minZoomLevel on baseLayer
+        map.baseLayer.restrictedMinZoom = 1;
+        minZoom = OpenLayers.Map.prototype.getRestrictedMinZoom.apply(map);
+        t.eq(minZoom, map.baseLayer.restrictedMinZoom, "custom baseLayer.restrictedMinZoom returns map.baseLayer.restrictedMinZoom");     
+    }
+    
     function test_Map_zoomToMaxExtent(t){
         t.plan(4)
 

Modified: sandbox/elemoine/draw-feature/tests/list-tests.html
===================================================================
--- sandbox/elemoine/draw-feature/tests/list-tests.html	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/tests/list-tests.html	2011-02-10 13:33:20 UTC (rev 11092)
@@ -53,6 +53,7 @@
     <li>Format/Atom.html</li>
     <li>Format/ArcXML.html</li>
     <li>Format/ArcXML/Features.html</li>
+    <li>Format/CQL.html</li>
     <li>Format/GeoJSON.html</li>
     <li>Format/GeoRSS.html</li>
     <li>Format/GML.html</li>
@@ -83,6 +84,7 @@
     <li>Format/WMC/v1.html</li>
     <li>Format/WMSCapabilities.html</li>
     <li>Format/WMSCapabilities/v1_1_1.html</li>
+    <li>Format/WMSCapabilities/v1_1_1_WMSC.html</li>
     <li>Format/WMSCapabilities/v1_3_0.html</li>
     <li>Format/WMSDescribeLayer.html</li>
     <li>Format/WMSGetFeatureInfo.html</li>

Modified: sandbox/elemoine/draw-feature/theme/default/google.css
===================================================================
--- sandbox/elemoine/draw-feature/theme/default/google.css	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/theme/default/google.css	2011-02-10 13:33:20 UTC (rev 11092)
@@ -3,8 +3,15 @@
     bottom: 2px;
     left: auto;  
 }
+.olLayerGoogleV3.olLayerGoogleCopyright {
+    bottom: 0px;
+    right: 0px !important;
+}
 .olLayerGooglePoweredBy {
     left: 2px;
     bottom: 2px;   
 }
+.olLayerGoogleV3.olLayerGooglePoweredBy {
+    bottom: 0px !important;
+}
 

Modified: sandbox/elemoine/draw-feature/theme/default/style.css
===================================================================
--- sandbox/elemoine/draw-feature/theme/default/style.css	2011-02-10 11:30:43 UTC (rev 11091)
+++ sandbox/elemoine/draw-feature/theme/default/style.css	2011-02-10 13:33:20 UTC (rev 11092)
@@ -16,12 +16,18 @@
 
 .olLayerGoogleCopyright {
     left: 2px;
-    bottom: 2px;  
+    bottom: 2px;
 }
+.olLayerGoogleV3.olLayerGoogleCopyright {
+    right: auto !important;
+}
 .olLayerGooglePoweredBy {
     left: 2px;
     bottom: 15px;   
 }
+.olLayerGoogleV3.olLayerGooglePoweredBy {
+    bottom: 15px !important;
+}
 .olControlAttribution {
     font-size: smaller; 
     right: 3px; 



More information about the Commits mailing list