[OpenLayers-Commits] r11854 - in sandbox/cmoullet/openlayers: . build examples lib/OpenLayers lib/OpenLayers/Control lib/OpenLayers/Format/Filter lib/OpenLayers/Handler lib/OpenLayers/Layer lib/OpenLayers/Renderer tests/Control tests/Renderer

commits-20090109 at openlayers.org commits-20090109 at openlayers.org
Fri Apr 1 15:54:39 EDT 2011


Author: cmoullet
Date: 2011-04-01 12:54:37 -0700 (Fri, 01 Apr 2011)
New Revision: 11854

Added:
   sandbox/cmoullet/openlayers/examples/canvas-hit-detection.html
   sandbox/cmoullet/openlayers/examples/canvas-hit-detection.js
   sandbox/cmoullet/openlayers/examples/canvas-inspector.html
   sandbox/cmoullet/openlayers/examples/canvas-inspector.js
Modified:
   sandbox/cmoullet/openlayers/
   sandbox/cmoullet/openlayers/build/mobile.cfg
   sandbox/cmoullet/openlayers/examples/mobile-base.js
   sandbox/cmoullet/openlayers/examples/mobile-jq.js
   sandbox/cmoullet/openlayers/examples/mobile-layers.html
   sandbox/cmoullet/openlayers/examples/mobile-layers.js
   sandbox/cmoullet/openlayers/examples/mobile-sencha.html
   sandbox/cmoullet/openlayers/lib/OpenLayers/Control/DragFeature.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Format/Filter/v1.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Path.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Point.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Layer/Vector.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Map.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer.js
   sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer/Canvas.js
   sandbox/cmoullet/openlayers/tests/Control/DragFeature.html
   sandbox/cmoullet/openlayers/tests/Renderer/Canvas.html
Log:
Merge with trunk



Property changes on: sandbox/cmoullet/openlayers
___________________________________________________________________
Modified: svn:mergeinfo
   - /sandbox/roberthl/openlayers:9745-9748
/trunk/openlayers:11161-11775,11777-11842
   + /sandbox/roberthl/openlayers:9745-9748
/trunk/openlayers:11161-11775,11777-11853

Modified: sandbox/cmoullet/openlayers/build/mobile.cfg
===================================================================
--- sandbox/cmoullet/openlayers/build/mobile.cfg	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/build/mobile.cfg	2011-04-01 19:54:37 UTC (rev 11854)
@@ -9,6 +9,7 @@
 OpenLayers/Layer/SphericalMercator.js
 OpenLayers/Layer/XYZ.js
 OpenLayers/Layer/Bing.js
+OpenLayers/Layer/WMS.js
 OpenLayers/Control/TouchNavigation.js
 OpenLayers/Control/Geolocate.js
 OpenLayers/Control/ZoomPanel.js
@@ -24,6 +25,11 @@
 OpenLayers/Renderer/SVG.js
 OpenLayers/Renderer/Canvas.js
 OpenLayers/Format/GeoJSON.js
+OpenLayers/Format/KML.js
+OpenLayers/Protocol/HTTP.js
+OpenLayers/Protocol/WFS.js
+OpenLayers/Protocol/WFS/v1_0_0.js
+OpenLayers/Strategy/Fixed.js
 
 [exclude]
 

Copied: sandbox/cmoullet/openlayers/examples/canvas-hit-detection.html (from rev 11853, trunk/openlayers/examples/canvas-hit-detection.html)
===================================================================
--- sandbox/cmoullet/openlayers/examples/canvas-hit-detection.html	                        (rev 0)
+++ sandbox/cmoullet/openlayers/examples/canvas-hit-detection.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>OpenLayers Canvas Hit Detection Example</title>
+        <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="../lib/OpenLayers.js"></script>
+    </head>
+    <body>
+        <h1 id="title">Feature Hit Detection with Canvas</h1>
+        <p id="shortdesc">
+            Demonstrates detection of feature hits with the canvas renderer.
+        </p>
+        <div id="map" class="smallmap"></div>
+        <div id="docs">
+            <p>
+                View the <a href="canvas-hit-detection.js" target="_blank">canvas-hit-detection.js</a>
+                source to see how this is done.
+            </p>
+        </div>
+        <script src="canvas-hit-detection.js"></script>
+    </body>
+</html>

Copied: sandbox/cmoullet/openlayers/examples/canvas-hit-detection.js (from rev 11853, trunk/openlayers/examples/canvas-hit-detection.js)
===================================================================
--- sandbox/cmoullet/openlayers/examples/canvas-hit-detection.js	                        (rev 0)
+++ sandbox/cmoullet/openlayers/examples/canvas-hit-detection.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -0,0 +1,88 @@
+
+// create some sample features
+var Feature = OpenLayers.Feature.Vector;
+var Geometry = OpenLayers.Geometry;
+var features = [
+    new Feature(new Geometry.Point(-90, 45)),
+    new Feature(
+        new Geometry.Point(0, 45),
+        {cls: "one"}
+    ),
+    new Feature(
+        new Geometry.Point(90, 45),
+        {cls: "two"}
+    ),
+    new Feature(
+        Geometry.fromWKT("LINESTRING(-110 -60, -80 -40, -50 -60, -20 -40)")
+    ),
+    new Feature(
+        Geometry.fromWKT("POLYGON((20 -20, 110 -20, 110 -80, 20 -80, 20 -20), (40 -40, 90 -40, 90 -60, 40 -60, 40 -40))")
+    )    
+];
+
+// create rule based styles
+var Rule = OpenLayers.Rule;
+var Filter = OpenLayers.Filter;
+var style = new OpenLayers.Style({
+    pointRadius: 10,
+    strokeWidth: 2,
+    strokeOpacity: 0.7,
+    strokeColor: "navy",
+    fillColor: "#ffcc66",
+    fillOpacity: 1
+}, {
+    rules: [
+        new Rule({
+            filter: new Filter.Comparison({
+                type: "==",
+                property: "cls",
+                value: "one"
+            }),
+            symbolizer: {
+                externalGraphic: "../img/marker-blue.png"
+            }
+        }),
+        new Rule({
+            filter: new Filter.Comparison({
+                type: "==",
+                property: "cls",
+                value: "two"
+            }),
+            symbolizer: {
+                externalGraphic: "../img/marker-green.png"
+            }
+        }),
+        new Rule({
+            elseFilter: true,
+            symbolizer: {
+                graphicName: "circle"
+            }
+        })
+    ]
+});
+
+var layer = new OpenLayers.Layer.Vector(null, {
+    styleMap: new OpenLayers.StyleMap({
+        "default": style,
+        select: {
+            fillColor: "red",
+            pointRadius: 13,
+            strokeColor: "yellow",
+            strokeWidth: 3
+        }
+    }),
+    isBaseLayer: true,
+    renderers: ["Canvas"]
+});
+layer.addFeatures(features);
+
+var map = new OpenLayers.Map({
+    div: "map",
+    layers: [layer],
+    center: new OpenLayers.LonLat(0, 0),
+    zoom: 0
+});
+
+var select = new OpenLayers.Control.SelectFeature(layer, {hover: true});
+map.addControl(select);
+select.activate();

Copied: sandbox/cmoullet/openlayers/examples/canvas-inspector.html (from rev 11853, trunk/openlayers/examples/canvas-inspector.html)
===================================================================
--- sandbox/cmoullet/openlayers/examples/canvas-inspector.html	                        (rev 0)
+++ sandbox/cmoullet/openlayers/examples/canvas-inspector.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>OpenLayers Canvas Inspector</title>
+        <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="../lib/OpenLayers.js"></script>
+        <script src="Jugl.js"></script>
+        <style>
+            #template {
+                display: none;
+            }
+            #inspector table {
+                border-right: 1px solid #666;
+                border-bottom: 1px solid #666;
+            }
+            #inspector table td {
+                font-size: 9px;
+                text-align: center;
+                width: 60px;
+                height: 60px;
+                border-top: 1px solid #666;
+                border-left: 1px solid #666;
+            }
+        </style>
+    </head>
+    <body>
+        <h1 id="title">Canvas Inspector</h1>
+        <p id="shortdesc">
+            Displays pixel values for canvas context.
+        </p>
+        <div id="map" class="smallmap"></div>
+        <div id="docs">
+            <p>
+                View the <a href="canvas-inspector.js" target="_blank">canvas-inspector.js</a>
+                source to see how this is done.
+            </p>
+        </div>
+        <div id="inspector">
+        </div>
+        <table id="template">
+            <tr jugl:repeat="row new Array(rows)">
+                <td jugl:repeat="col new Array(cols)" 
+                    jugl:attributes="id 'c' + repeat.col.index + 'r' + repeat.row.index">
+                    &nbsp;
+                </td>
+            </tr>
+        </table>
+        <script src="canvas-inspector.js"></script>
+    </body>
+</html>

Copied: sandbox/cmoullet/openlayers/examples/canvas-inspector.js (from rev 11853, trunk/openlayers/examples/canvas-inspector.js)
===================================================================
--- sandbox/cmoullet/openlayers/examples/canvas-inspector.js	                        (rev 0)
+++ sandbox/cmoullet/openlayers/examples/canvas-inspector.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -0,0 +1,91 @@
+
+var features = [
+
+    new OpenLayers.Feature.Vector(
+        OpenLayers.Geometry.fromWKT(
+            "LINESTRING(-90 90, 90 -90)"
+        ),
+        {color: "#0f0000"}
+    ),
+    
+    new OpenLayers.Feature.Vector(
+        OpenLayers.Geometry.fromWKT(
+            "LINESTRING(100 50, -100 -50)"
+        ),
+        {color: "#00ff00"}
+    )
+
+];
+
+var layer = new OpenLayers.Layer.Vector(null, {
+    styleMap: new OpenLayers.StyleMap({
+        strokeWidth: 3,
+        strokeColor: "${color}"
+    }),
+    isBaseLayer: true,
+    renderers: ["Canvas"],
+    rendererOptions: {hitDetection: true}
+});
+layer.addFeatures(features);
+
+var map = new OpenLayers.Map({
+    div: "map",
+    layers: [layer],
+    center: new OpenLayers.LonLat(0, 0),
+    zoom: 0
+});
+
+var xOff = 2, yOff = 2;
+
+var rows = 1 + (2 * yOff);
+var cols = 1 + (2 * xOff);
+
+var template = new jugl.Template("template");
+template.process({
+    clone: true,
+    parent: "inspector",
+    context: {
+        rows: rows,
+        cols: cols
+    }
+});
+
+function isDark(r, g, b, a) {
+    a = a / 255;
+    var da = 1 - a;
+    // convert color values to decimal (assume white background)
+    r = (a * r / 255) + da;
+    g = (a * g / 255) + da;
+    b = (a * b / 255) + da;
+    // use w3C brightness measure
+    var brightness = (r * 0.299) + (g * 0.587) + (b * 0.144);
+    return brightness < 0.5;
+}
+
+var context = layer.renderer.canvas; //layer.renderer.hitContext;
+var size = map.getSize();
+map.events.on({
+    mousemove: function(event) {
+        var x = event.xy.x - 1; // TODO: fix this elsewhere
+        var y = event.xy.y;
+        if ((x >= xOff) && (x < size.w - xOff) && (y >= yOff) && (y < size.h - yOff)) {
+            var data = context.getImageData(x - xOff, y - yOff, rows, cols).data;
+            var offset, red, green, blue, alpha, cell;
+            for (var i=0; i<cols; ++i) {
+                for (var j=0; j<rows; ++j) {
+                    offset = (i * 4) + (j * 4 * cols);
+                    red = data[offset];
+                    green = data[offset + 1];
+                    blue = data[offset + 2];
+                    alpha = data[offset + 3];
+                    cell = document.getElementById("c" + i + "r" + j);
+                    cell.innerHTML = "R: " + red + "<br>G: " + green + "<br>B: " + blue + "<br>A: " + alpha;
+                    cell.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + (alpha / 255) + ")";
+                    cell.style.color = isDark(red, green, blue, alpha) ? "#ffffff" : "#000000";
+                }
+            }
+        }
+    }
+});
+
+

Modified: sandbox/cmoullet/openlayers/examples/mobile-base.js
===================================================================
--- sandbox/cmoullet/openlayers/examples/mobile-base.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/examples/mobile-base.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -7,11 +7,11 @@
 var gg = new OpenLayers.Projection("EPSG:4326");
 var sm = new OpenLayers.Projection("EPSG:900913");
 
-var init = function () {
+var init = function (onSelectFeatureFunction) {
 
     var vector = new OpenLayers.Layer.Vector("Vector Layer", {});
 
-    sprintersLayer = new OpenLayers.Layer.Vector("Sprinters", {
+    var sprintersLayer = new OpenLayers.Layer.Vector("Sprinters", {
         styleMap: new OpenLayers.StyleMap({
             externalGraphic: "img/mobile-loc.png",
             graphicOpacity: 1.0,
@@ -113,15 +113,15 @@
             }
                     ),
             new OpenLayers.Feature.Vector(
-                    OpenLayers.Geometry.Polygon.createRegularPolygon(
-                            new OpenLayers.Geometry.Point(e.point.x, e.point.y),
-                            e.position.coords.accuracy / 2,
-                            50,
-                            0
-                            ),
-            {},
-                    style
-                    )
+                OpenLayers.Geometry.Polygon.createRegularPolygon(
+                    new OpenLayers.Geometry.Point(e.point.x, e.point.y),
+                    e.position.coords.accuracy / 2,
+                    50,
+                    0
+                ),
+                {},
+                style
+            )
         ]);
         map.zoomToExtent(vector.getDataExtent());
     });

Modified: sandbox/cmoullet/openlayers/examples/mobile-jq.js
===================================================================
--- sandbox/cmoullet/openlayers/examples/mobile-jq.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/examples/mobile-jq.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -27,7 +27,10 @@
             map.updateSize();
         } else {
             // initialize map
-            init();
+            init(function(feature) { 
+                selectedFeature = feature; 
+                $.mobile.changePage($("#popup"), "pop"); 
+            });
         }
     }
     $(window).bind("orientationchange resize pageshow", fixContentHeight);
@@ -49,7 +52,6 @@
             control.activate();
         }
     });
-
     
     $('div#popup').live('pageshow',function(event, ui){
         var li = "";
@@ -150,4 +152,4 @@
             $(item).toggleClass('checked');
         }
     });
-}
\ No newline at end of file
+}

Modified: sandbox/cmoullet/openlayers/examples/mobile-layers.html
===================================================================
--- sandbox/cmoullet/openlayers/examples/mobile-layers.html	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/examples/mobile-layers.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -6,7 +6,7 @@
     <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0;">
     <meta name="apple-mobile-web-app-capable" content="yes">
     <link rel="stylesheet" href="style.mobile.css" type="text/css">
-    <script src="../lib/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers.js?mobile"></script>
     <script src="mobile-layers.js"></script>
     <style>
         html, body {
@@ -87,7 +87,7 @@
     mobile, WMS, WFS, KML
 </div>
 <p id="shortdesc">
-    A mobile example displaying various layer types: WMS, WFS, KML
+    A mobile example displaying various layer types: WMS, WFS, KML.
 </p>
 
 <div id="map"></div>

Modified: sandbox/cmoullet/openlayers/examples/mobile-layers.js
===================================================================
--- sandbox/cmoullet/openlayers/examples/mobile-layers.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/examples/mobile-layers.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -14,11 +14,14 @@
 setTimeout(fixSize, 700);
 setTimeout(fixSize, 1500);
 
+// allow testing of specific renderers via "?renderer=Canvas", etc
+var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
 OpenLayers.ProxyHost = "proxy.cgi?url=";
 
-var init = function () {
+function init() {
 
-    // create map
     map = new OpenLayers.Map({
         div: "map",
         theme: null,
@@ -31,18 +34,16 @@
                 }
             }),
             new OpenLayers.Control.ZoomPanel()
-        ],
-        layers: [
-            new OpenLayers.Layer.WMS("OpenLayers WMS",
-                    "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'}, {isBaseLayer: true, transitionEffect: 'resize'})
         ]
     });
 
-    // allow testing of specific renderers via "?renderer=Canvas", etc
-    var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
-    renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+    var wms = new OpenLayers.Layer.WMS("OpenLayers WMS",
+        "http://vmap0.tiles.osgeo.org/wms/vmap0",
+        {layers: 'basic'},
+        {isBaseLayer: true, transitionEffect: 'resize'}
+    )
 
-    var sundials = new OpenLayers.Layer.Vector("KML", {
+    var kml = new OpenLayers.Layer.Vector("KML", {
         projection: map.displayProjection,
         strategies: [new OpenLayers.Strategy.Fixed()],
         protocol: new OpenLayers.Protocol.HTTP({
@@ -65,6 +66,7 @@
         renderers: renderer
     });
 
-    map.addLayers([wfs, sundials]);
-    map.zoomToMaxExtent();
+    map.addLayers([wms, wfs, kml]);
+
+    map.setCenter(new OpenLayers.LonLat(-104, 42), 3);
 };

Modified: sandbox/cmoullet/openlayers/examples/mobile-sencha.html
===================================================================
--- sandbox/cmoullet/openlayers/examples/mobile-sencha.html	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/examples/mobile-sencha.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -1,162 +1,77 @@
 <!DOCTYPE html>
 <html>
-<head>
-    <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
-    <meta name="apple-mobile-web-app-capable" content="yes"/>
-    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-    <title>OpenLayers with Sencha Touch</title>
-    <script src="../lib/OpenLayers.js?mobile"></script>
-    <link rel="stylesheet" href="style.mobile.css" type="text/css">
-    <link rel="stylesheet" href="http://dev.sencha.com/deploy/touch/resources/css/sencha-touch.css">
-    <script src="http://dev.sencha.com/deploy/touch/sencha-touch.js"></script>
-    <script src="mobile-sencha.js"></script>
-    <script src="mobile-base.js"></script>
-    <style>
-        .searchList {
-            min-height: 150px;
-        }
+    <head>
+        <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
+        <meta name="apple-mobile-web-app-capable" content="yes"/>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+        <title>OpenLayers with Sencha Touch</title>
+        <script src="../lib/OpenLayers.js?mobile"></script>
+        <link rel="stylesheet" href="style.mobile.css" type="text/css">
+        <link rel="stylesheet" href="http://dev.sencha.com/deploy/touch/resources/css/sencha-touch.css">
+        <script src="http://dev.sencha.com/deploy/touch/sencha-touch.js"></script>
+        <script src="mobile-sencha.js"></script>
+        <script src="mobile-base.js"></script>
+        <style>
+            .searchList {
+                min-height: 150px;
+            }
 
-        .close-btn {
-            position: absolute;
-            right: 10px;
-            top: 10px;
-        }
+            .close-btn {
+                position: absolute;
+                right: 10px;
+                top: 10px;
+            }
 
-        img.minus {
-            -webkit-mask-image: url(img/minus1.png);
-        }
+            img.minus {
+                -webkit-mask-image: url(img/minus1.png);
+            }
 
-        img.layers {
-            -webkit-mask-image: url(img/list.png);
-        }
+            img.layers {
+                -webkit-mask-image: url(img/list.png);
+            }
 
-        .gx-layer-item {
-            margin-left: 10px;
-        }
+            .gx-layer-item {
+                margin-left: 10px;
+            }
 
-        #map {
-            width: 100%;
-            height: 100%;
-        }
+            #map {
+                width: 100%;
+                height: 100%;
+            }
 
-        .olControlAttribution {
-            font-size: 10px;
-            bottom: 5px;
-            right: 5px;
-        }
+            .olControlAttribution {
+                font-size: 10px;
+                bottom: 5px;
+                right: 5px;
+            }
 
-        #title, #tags, #shortdesc {
-            display: none;
-        }
-    </style>
-    <script>
-
-        var featurePopup;
-        var sprintersLayer;
-
-        var onSelectFeatureFunction = function(feature) {
-            var htmlContent = "";
-            for (var property in feature.data) {
-                if (feature.data[property] != 'undefined') {
-                    htmlContent = htmlContent + feature.data[property] + "<br>";
-                }
+            #title, #tags, #shortdesc {
+                display: none;
             }
-            if (featurePopup) {
-                featurePopup.destroy();
-            }
-            featurePopup = new Ext.Panel({
-                floating: true,
-                modal: true,
-                centered: true,
-                hideOnMaskTap: true,
-                width: 240,
-                html: htmlContent,
-                scroll: 'vertical'
-            });
-            featurePopup.show();
-        };
+        </style>
+        <script>
 
         var app = new Ext.Application({
             name: "ol",
             launch: function() {
                 this.viewport = new Ext.Panel({
                     fullscreen: true,
-                    dockedItems: [
-                        {
-                            dock: "bottom",
-                            xtype: "toolbar",
-                            ui: "light",
-                            layout: {
-                                pack: "center"
-                            },
-                            items: [
-                                {
-                                    iconCls: "search",
-                                    iconMask: true,
-                                    handler: function() {
-                                        // this is the app
-                                        if (!app.searchFormPopupPanel) {
-                                            app.searchFormPopupPanel = new App.SearchFormPopupPanel({
-                                                map: map
-                                            });
-                                        }
-                                        app.searchFormPopupPanel.show('pop');
-                                    }
-                                },
-                                {
-                                    iconCls: "locate",
-                                    iconMask: true,
-                                    handler: function() {
-                                        var geolocate = map.getControlsBy("id", "locate-control")[0];
-                                        if (geolocate.active) {
-                                            geolocate.getCurrentLocation();
-                                        } else {
-                                            geolocate.activate();
-                                        }
-                                    }
-                                },
-                                {
-                                    xtype: "spacer"
-                                },
-                                {
-                                    iconMask: true,
-                                    iconCls: "add",
-                                    handler: function() {
-                                        map.zoomIn();
-                                    }
-                                },
-                                {
-                                    iconMask: true,
-                                    iconCls: "minus",
-                                    handler: function() {
-                                        map.zoomOut();
-                                    }
-                                },
-                                {
-                                    xtype: "spacer"
-                                },
-                                {
-                                    iconMask: true,
-                                    iconCls: "layers",
-                                    handler: function() {
-                                        if (!app.popup) {
-                                            app.popup = new Ext.Panel({
-                                                floating: true,
-                                                modal: true,
-                                                centered: true,
-                                                hideOnMaskTap: true,
-                                                width: 240,
-                                                items: [
-                                                    {
-                                                        xtype: 'app_layerlist',
-                                                        map: map
-                                                    }
-                                                ],
-                                                scroll: 'vertical'
-                                            });
-                                        }
-                                        app.popup.show('pop');
-                                    }
+                    dockedItems: [{
+                        dock: "bottom",
+                        xtype: "toolbar",
+                        ui: "light",
+                        layout: {
+                            pack: "center"
+                        },
+                        items: [{
+                            iconCls: "search",
+                            iconMask: true,
+                            handler: function() {
+                                // this is the app
+                                if (!app.searchFormPopupPanel) {
+                                    app.searchFormPopupPanel = new App.SearchFormPopupPanel({
+                                        map: map
+                                    });
                                 }
                             ]
                         }
@@ -168,11 +83,37 @@
                             monitorResize: true,
                             id: "map",
                             listeners: {
-                                render: init,
+                                render: function() {
+                                    var self = this;
+                                    init(function(feature) {
+                                        var htmlContent = "";
+                                        for (var property in feature.data) {
+                                            if (feature.data[property] != 'undefined') {
+                                                htmlContent = htmlContent + feature.data[property] + "<br>";
+                                            }
+                                        }
+                                        if (self.featurePopup) {
+                                            self.featurePopup.destroy();
+                                        }
+                                        self.featurePopup = new Ext.Panel({
+                                            floating: true,
+                                            modal: true,
+                                            centered: true,
+                                            hideOnMaskTap: true,
+                                            width: 240,
+                                            html: htmlContent,
+                                            scroll: 'vertical'
+                                        });
+                                        self.featurePopup.show();
+                                    })
+                                },
                                 resize: function() {
                                     if (window.map) {
                                         map.updateSize();
                                     }
+                                },
+                                scope: {
+                                    featurePopup: null
                                 }
                             }
                         }
@@ -180,16 +121,16 @@
                 });
             }
         });
-    </script>
-</head>
-<body>
-<h1 id="title">OpenLayers with Sencha Touch</h1>
+        </script>
+    </head>
+    <body>
+        <h1 id="title">OpenLayers with Sencha Touch</h1>
 
-<div id="tags">
-    mobile, sencha touch
-</div>
-<p id="shortdesc">
-    Using Sencha Touch to display an OpenLayers map.
-</p>
-</body>
+        <div id="tags">
+            mobile, sencha touch
+        </div>
+        <p id="shortdesc">
+            Using Sencha Touch to display an OpenLayers map.
+        </p>
+    </body>
 </html>

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Control/DragFeature.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Control/DragFeature.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Control/DragFeature.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -151,6 +151,10 @@
             ),
             feature: new OpenLayers.Handler.Feature(
                 this, this.layer, OpenLayers.Util.extend({
+                    // 'click' and 'clickout' callback are for the mobile
+                    // support: no 'over' or 'out' in touch based browsers.
+                    click: this.clickFeature,
+                    clickout: this.clickoutFeature,
                     over: this.overFeature,
                     out: this.outFeature
                 }, this.featureCallbacks),
@@ -158,8 +162,35 @@
             )
         };
     },
-    
+
     /**
+     * Method: clickFeature
+     * Called when the feature handler detects a click-in on a feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    clickFeature: function(feature) {
+        if (this.overFeature(feature)) {
+            this.handlers.drag.dragstart(this.handlers.feature.evt);
+            // to let the events propagate to the feature handler (click callback)
+            this.handlers.drag.stopDown = false;
+        }
+    },
+
+    /**
+     * Method: clickoutFeature
+     * Called when the feature handler detects a click-out on a feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    clickoutFeature: function(feature) {
+        this.outFeature(feature);
+        this.handlers.drag.stopDown = true;
+    },
+
+    /**
      * APIMethod: destroy
      * Take care of things that are not handled in superclass
      */
@@ -207,11 +238,16 @@
      *
      * Parameters:
      * feature - {<OpenLayers.Feature.Vector>} The selected feature.
+     *
+     * Returns:
+     * {Boolean} Successfully activated the drag handler.
      */
     overFeature: function(feature) {
+        var activated = false;
         if(!this.handlers.drag.dragging) {
             this.feature = feature;
             this.handlers.drag.activate();
+            activated = true;
             this.over = true;
             OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over");
             this.onEnter(feature);
@@ -222,6 +258,7 @@
                 this.over = false;
             }
         }
+        return activated;
     },
 
     /**

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Format/Filter/v1.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Format/Filter/v1.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Format/Filter/v1.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -5,6 +5,7 @@
 /**
  * @requires OpenLayers/Format/Filter.js
  * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/Function.js
  */
 
 /**

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Path.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Path.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Path.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -262,8 +262,8 @@
     },
 
     /**
-     * Method: mousedown
-     * Handle mouse down.  Add a new point to the geometry and
+     * Method: down
+     * Handle mousedown and touchstart.  Add a new point to the geometry and
      * render it. Return determines whether to propagate the event on the map.
      * 
      * Parameters:
@@ -287,8 +287,8 @@
     },
 
     /**
-     * Method: mousemove
-     * Handle mouse move.  Adjust the geometry and redraw.
+     * Method: move
+     * Handle mousemove and touchmove.  Adjust the geometry and redraw.
      * Return determines whether to propagate the event on the map.
      * 
      * Parameters:
@@ -312,8 +312,8 @@
     },
     
     /**
-     * Method: mouseup
-     * Handle mouse up.  Send the latest point in the geometry to
+     * Method: up
+     * Handle mouseup and touchend.  Send the latest point in the geometry to
      * the control. Return determines whether to propagate the event on the map.
      * 
      * Parameters:

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Point.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Point.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Handler/Point.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -475,8 +475,8 @@
     },
   
     /**
-     * Method: mousedown
-     * Handle mouse down.  Adjust the geometry and redraw.
+     * Method: down
+     * Handle mousedown and touchstart.  Adjust the geometry and redraw.
      * Return determines whether to propagate the event on the map.
      * 
      * Parameters:
@@ -496,8 +496,8 @@
     },
 
     /**
-     * Method: mousemove
-     * Handle mouse move.  Adjust the geometry and redraw.
+     * Method: move
+     * Handle mousemove and touchmove.  Adjust the geometry and redraw.
      * Return determines whether to propagate the event on the map.
      * 
      * Parameters:
@@ -515,8 +515,8 @@
     },
 
     /**
-     * Method: mouseup
-     * Handle mouse up.  Send the latest point in the geometry to the control.
+     * Method: up
+     * Handle mouseup and touchend.  Send the latest point in the geometry to the control.
      * Return determines whether to propagate the event on the map.
      *
      * Parameters:

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Layer/Vector.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Layer/Vector.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Layer/Vector.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -810,7 +810,9 @@
             }
         }
         
-        if (!this.renderer.drawFeature(feature, style)) {
+        var drawn = this.renderer.drawFeature(feature, style);
+        //TODO remove the check for null when we get rid of Renderer.SVG
+        if (drawn === false || drawn === null) {
             this.unrenderedFeatures[feature.id] = feature;
         } else {
             delete this.unrenderedFeatures[feature.id];
@@ -843,9 +845,17 @@
         if (!this.renderer) {
             OpenLayers.Console.error(OpenLayers.i18n("getFeatureError")); 
             return null;
-        }    
+        }
+        var feature = null;
         var featureId = this.renderer.getFeatureIdFromEvent(evt);
-        return this.getFeatureById(featureId);
+        if (featureId) {
+            if (typeof featureId === "string") {
+                feature = this.getFeatureById(featureId);
+            } else {
+                feature = featureId;
+            }
+        }
+        return feature;
     },
 
     /**

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Map.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Map.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Map.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -1849,7 +1849,7 @@
             
             bounds = this.baseLayer.getExtent();
             
-            for (var i=0, len=this.layers.length; i<len; i++) {
+            for (var i=this.layers.length-1; i>=0; --i) {
                 var layer = this.layers[i];
                 if (layer !== this.baseLayer && !layer.isBaseLayer) {
                     var inRange = layer.calculateInRange();

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer/Canvas.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer/Canvas.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer/Canvas.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -15,6 +15,22 @@
  *  - <OpenLayers.Renderer>
  */
 OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+    
+    /**
+     * APIProperty: hitDetection
+     * {Boolean} Allow for hit detection of features.  Default is true.
+     */
+    hitDetection: true,
+    
+    /**
+     * Property: hitOverflow
+     * {Number} The method for converting feature identifiers to color values
+     *     supports 16777215 sequential values.  Two features cannot be 
+     *     predictably detected if their identifiers differ by more than this
+     *     value.  The hitOverflow allows for bigger numbers (but the 
+     *     difference in values is still limited).
+     */
+    hitOverflow: 0,
 
     /**
      * Property: canvas
@@ -32,14 +48,21 @@
      * Constructor: OpenLayers.Renderer.Canvas
      *
      * Parameters:
-     * containerID - {<String>} 
+     * containerID - {<String>}
+     * options - {Object} Optional properties to be set on the renderer.
      */
-    initialize: function(containerID) {
+    initialize: function(containerID, options) {
         OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
         this.root = document.createElement("canvas");
         this.container.appendChild(this.root);
         this.canvas = this.root.getContext("2d");
         this.features = {};
+        if (this.hitDetection) {
+            this.hitCanvas = document.createElement("canvas");
+            this.hitContext = this.hitCanvas.getContext("2d");
+            this.hitGraphicCanvas = document.createElement("canvas");
+            this.hitGraphicContext = this.hitGraphicCanvas.getContext("2d");
+        }
     },
     
     /** 
@@ -78,11 +101,24 @@
      */
     setSize: function(size) {
         this.size = size.clone();
-        this.root.style.width = size.w + "px";
-        this.root.style.height = size.h + "px";
-        this.root.width = size.w;
-        this.root.height = size.h;
+        var root = this.root;
+        root.style.width = size.w + "px";
+        root.style.height = size.h + "px";
+        root.width = size.w;
+        root.height = size.h;
         this.resolution = null;
+        if (this.hitDetection) {
+            var hitCanvas = this.hitCanvas;
+            hitCanvas.style.width = size.w + "px";
+            hitCanvas.style.height = size.h + "px";
+            hitCanvas.width = size.w;
+            hitCanvas.height = size.h;
+            var hitGraphicCanvas = this.hitGraphicCanvas;
+            hitGraphicCanvas.style.width = size.w + "px";
+            hitGraphicCanvas.style.height = size.h + "px";
+            hitGraphicCanvas.width = size.w;
+            hitGraphicCanvas.height = size.h;
+        }
     },
     
     /**
@@ -93,16 +129,25 @@
      * Parameters:
      * feature - {<OpenLayers.Feature.Vector>} 
      * style - {<Object>} 
+     *
+     * Returns:
+     * {Boolean} The feature has been drawn completely.  If the feature has no
+     *     geometry, undefined will be returned.  If the feature is not rendered
+     *     for other reasons, false will be returned.
      */
     drawFeature: function(feature, style) {
-        style = style || feature.style;
-        style = this.applyDefaultSymbolizer(style);  
-        
-        this.features[feature.id] = [feature, style]; 
-        this.redraw();
+        var rendered;
+        if (feature.geometry) {
+            style = style || feature.style;
+            style = this.applyDefaultSymbolizer(style);  
+
+            this.features[feature.id] = [feature, style]; 
+            this.redraw();
+            rendered = true;
+        }
+        return rendered;
     },
 
-
     /** 
      * Method: drawGeometry
      * Used when looping (in redraw) over the features; draws
@@ -112,29 +157,29 @@
      * geometry - {<OpenLayers.Geometry>} 
      * style - {Object} 
      */
-    drawGeometry: function(geometry, style) {
+    drawGeometry: function(geometry, style, featureId) {
         var className = geometry.CLASS_NAME;
         if ((className == "OpenLayers.Geometry.Collection") ||
             (className == "OpenLayers.Geometry.MultiPoint") ||
             (className == "OpenLayers.Geometry.MultiLineString") ||
             (className == "OpenLayers.Geometry.MultiPolygon")) {
             for (var i = 0; i < geometry.components.length; i++) {
-                this.drawGeometry(geometry.components[i], style);
+                this.drawGeometry(geometry.components[i], style, featureId);
             }
             return;
         }
         switch (geometry.CLASS_NAME) {
             case "OpenLayers.Geometry.Point":
-                this.drawPoint(geometry, style);
+                this.drawPoint(geometry, style, featureId);
                 break;
             case "OpenLayers.Geometry.LineString":
-                this.drawLineString(geometry, style);
+                this.drawLineString(geometry, style, featureId);
                 break;
             case "OpenLayers.Geometry.LinearRing":
-                this.drawLinearRing(geometry, style);
+                this.drawLinearRing(geometry, style, featureId);
                 break;
             case "OpenLayers.Geometry.Polygon":
-                this.drawPolygon(geometry, style);
+                this.drawPolygon(geometry, style, featureId);
                 break;
             default:
                 break;
@@ -148,37 +193,70 @@
      * Parameters: 
      * geometry - {<OpenLayers.Geometry>}
      * style    - {Object}
+     * featureId - {String}
      */ 
-    drawExternalGraphic: function(pt, style) {
-       var img = new Image();
-       
-       if(style.graphicTitle) {
-           img.title=style.graphicTitle;           
-       }
+    drawExternalGraphic: function(pt, style, featureId) {
+        var img = new Image();
 
-       var width = style.graphicWidth || style.graphicHeight;
-       var height = style.graphicHeight || style.graphicWidth;
-       width = width ? width : style.pointRadius*2;
-       height = height ? height : style.pointRadius*2;
-       var xOffset = (style.graphicXOffset != undefined) ?
+        if (style.graphicTitle) {
+            img.title = style.graphicTitle;           
+        }
+
+        var width = style.graphicWidth || style.graphicHeight;
+        var height = style.graphicHeight || style.graphicWidth;
+        width = width ? width : style.pointRadius * 2;
+        height = height ? height : style.pointRadius * 2;
+        var xOffset = (style.graphicXOffset != undefined) ?
            style.graphicXOffset : -(0.5 * width);
-       var yOffset = (style.graphicYOffset != undefined) ?
+        var yOffset = (style.graphicYOffset != undefined) ?
            style.graphicYOffset : -(0.5 * height);
-       
-       var context = { img: img, 
-                       x: (pt[0]+xOffset), 
-                       y: (pt[1]+yOffset), 
-                       width: width, 
-                       height: height, 
-                       opacity: style.graphicOpacity || style.fillOpacity,
-                       canvas: this.canvas };
 
-       img.onload = OpenLayers.Function.bind( function() {
-           this.canvas.globalAlpha = this.opacity;
-           this.canvas.drawImage(this.img, this.x, 
-                                 this.y, this.width, this.height);
-       }, context);
-       img.src = style.externalGraphic;
+        var x = pt[0] + xOffset;
+        var y = pt[1] + yOffset;
+
+        var numRows = this.root.width;
+        var numCols = this.root.height;
+
+        var opacity = style.graphicOpacity || style.fillOpacity;
+        
+        var rgb = this.featureIdToRGB(featureId);
+        var red = rgb[0];
+        var green = rgb[1];
+        var blue = rgb[2];
+        
+        var onLoad = function() {
+            // TODO: check that we haven't moved
+            var canvas = this.canvas;
+            canvas.globalAlpha = opacity;
+            canvas.drawImage(
+                img, x, y, width, height
+            );
+            if (this.hitDetection) {
+                var hitGraphicContext = this.hitGraphicContext;
+                var hitContext = this.hitContext;
+                hitGraphicContext.clearRect(0, 0, numRows, numCols);
+                hitGraphicContext.drawImage(
+                    img, 0, 0, width, height
+                );
+                var imagePixels = hitGraphicContext.getImageData(0, 0, width, height).data;
+                var indexData = hitContext.createImageData(width, height);
+                var indexPixels = indexData.data;
+                var pixelIndex;
+                for (var i=0, len=imagePixels.length; i<len; i+=4) {
+                    // look for visible pixels
+                    if (imagePixels[i+3] > 0) {
+                        indexData[i] = red;
+                        indexPixels[i+1] = green;
+                        indexPixels[i+2] = blue;
+                        indexPixels[i+3] = 255;
+                    }
+                }
+                hitContext.putImageData(indexData, x, y);
+            }
+        };
+
+        img.onload = OpenLayers.Function.bind(onLoad, this);
+        img.src = style.externalGraphic;
     },
 
     /**
@@ -190,10 +268,10 @@
      * style - {Object} Symbolizer hash
      */
     setCanvasStyle: function(type, style) {
-        if (type == "fill") {     
+        if (type === "fill") {     
             this.canvas.globalAlpha = style['fillOpacity'];
             this.canvas.fillStyle = style['fillColor'];
-        } else if (type == "stroke") {  
+        } else if (type === "stroke") {  
             this.canvas.globalAlpha = style['strokeOpacity'];
             this.canvas.strokeStyle = style['strokeColor'];
             this.canvas.lineWidth = style['strokeWidth'];
@@ -202,40 +280,124 @@
             this.canvas.lineWidth = 1;
         }
     },
+    
+    /**
+     * Method: featureIdToHex
+     * Convert a feature ID string into an RGB hex string.
+     *
+     * Parameters:
+     * featureId - {String} Feature id
+     *
+     * Returns:
+     * {String} RGB hex string.
+     */
+    featureIdToHex: function(featureId) {
+        var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
+        if (id >= 16777216) {
+            this.hitOverflow = id - 16777215;
+            id = id % 16777216 + 1;
+        }
+        var hex = "000000" + id.toString(16);
+        var len = hex.length;
+        hex = "#" + hex.substring(len-6, len);
+        return hex;
+    },
+    
+    /**
+     * Method: featureIdToRGB
+     * Convert a feature ID string into an RGB array.
+     *
+     * Parameters:
+     * featureId - {String} Feature id
+     *
+     * Returns:
+     * {Array} RGB values.
+     */
+    featureIdToRGB: function(featureId) {
+        var hex = this.featureIdToHex(featureId);
+        return [
+            parseInt(hex.substring(1, 3), 16),
+            parseInt(hex.substring(3, 5), 16),
+            parseInt(hex.substring(5, 7), 16)
+        ];
+    },
 
     /**
+     * Method: setHitContextStyle
+     * Prepare the hit canvas for drawing by setting various global settings.
+     *
+     * Parameters:
+     * type - {String} one of 'stroke', 'fill', or 'reset'
+     * featureId - {String} The feature id.
+     * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
+     */
+    setHitContextStyle: function(type, featureId, symbolizer) {
+        var hex = this.featureIdToHex(featureId);
+        if (type == "fill") {
+            this.hitContext.globalAlpha = 1.0;
+            this.hitContext.fillStyle = hex;
+        } else if (type == "stroke") {  
+            this.hitContext.globalAlpha = 1.0;
+            this.hitContext.strokeStyle = hex;
+            // bump up stroke width to deal with antialiasing
+            this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
+        } else {
+            this.hitContext.globalAlpha = 0;
+            this.hitContext.lineWidth = 1;
+        }
+    },
+
+    /**
      * Method: drawPoint
      * This method is only called by the renderer itself.
      * 
      * Parameters: 
      * geometry - {<OpenLayers.Geometry>}
      * style    - {Object}
+     * featureId - {String}
      */ 
-    drawPoint: function(geometry, style) {
+    drawPoint: function(geometry, style, featureId) {
         if(style.graphic !== false) {
             var pt = this.getLocalXY(geometry);
-            
-            if (style.externalGraphic) {
-                this.drawExternalGraphic(pt, style);
-            } else {
-                if(style.fill !== false) {
-                    this.setCanvasStyle("fill", style);
-                    this.canvas.beginPath();
-                    this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true);
-                    this.canvas.fill();
+            var p0 = pt[0];
+            var p1 = pt[1];
+            if (!isNaN(p0) && !isNaN(p1)) {
+                if (style.externalGraphic) {
+                    this.drawExternalGraphic(pt, style, featureId);
+                } else {
+                    var twoPi = Math.PI*2;
+                    var radius = style.pointRadius;
+                    if(style.fill !== false) {
+                        this.setCanvasStyle("fill", style);
+                        this.canvas.beginPath();
+                        this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+                        this.canvas.fill();
+                        if (this.hitDetection) {
+                            this.setHitContextStyle("fill", featureId, style);
+                            this.hitContext.beginPath();
+                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+                            this.hitContext.fill();
+                        }
+                    }
+
+                    if(style.stroke !== false) {
+                        this.setCanvasStyle("stroke", style);
+                        this.canvas.beginPath();
+                        this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+                        this.canvas.stroke();
+                        if (this.hitDetection) {
+                            this.setHitContextStyle("stroke", featureId, style);
+                            this.hitContext.beginPath();
+                            this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+                            this.hitContext.stroke();
+                        }
+                        this.setCanvasStyle("reset");
+                    }
                 }
-                
-                if(style.stroke !== false) {
-                    this.setCanvasStyle("stroke", style);
-                    this.canvas.beginPath();
-                    this.canvas.arc(pt[0], pt[1], style.pointRadius, 0, Math.PI*2, true);
-                    this.canvas.stroke();
-                    this.setCanvasStyle("reset");
-                }
             }
         }
     },
-
+    
     /**
      * Method: drawLineString
      * This method is only called by the renderer itself.
@@ -243,20 +405,11 @@
      * Parameters: 
      * geometry - {<OpenLayers.Geometry>}
      * style    - {Object}
+     * featureId - {String}
      */ 
-    drawLineString: function(geometry, style) {
-        if(style.stroke !== false) {
-            this.setCanvasStyle("stroke", style);
-            this.canvas.beginPath();
-            var start = this.getLocalXY(geometry.components[0]);
-            this.canvas.moveTo(start[0], start[1]);
-            for(var i = 1; i < geometry.components.length; i++) {
-                var pt = this.getLocalXY(geometry.components[i]);
-                this.canvas.lineTo(pt[0], pt[1]);
-            }
-            this.canvas.stroke();
-        }
-        this.setCanvasStyle("reset");
+    drawLineString: function(geometry, style, featureId) {
+        style = OpenLayers.Util.applyDefaults({fill: false}, style);
+        this.drawLinearRing(geometry, style, featureId);
     },    
     
     /**
@@ -266,52 +419,94 @@
      * Parameters: 
      * geometry - {<OpenLayers.Geometry>}
      * style    - {Object}
+     * featureId - {String}
      */ 
-    drawLinearRing: function(geometry, style) {
-        if(style.fill !== false) {
+    drawLinearRing: function(geometry, style, featureId) {
+        if (style.fill !== false) {
             this.setCanvasStyle("fill", style);
-            this.canvas.beginPath();
-            var start = this.getLocalXY(geometry.components[0]);
-            this.canvas.moveTo(start[0], start[1]);
-            for(var i = 1; i < geometry.components.length - 1 ; i++) {
-                var pt = this.getLocalXY(geometry.components[i]);
-                this.canvas.lineTo(pt[0], pt[1]);
+            this.renderPath(this.canvas, geometry, style, featureId, "fill");
+            if (this.hitDetection) {
+                this.setHitContextStyle("fill", featureId, style);
+                this.renderPath(this.hitContext, geometry, style, featureId, "fill");
             }
-            this.canvas.fill();
         }
-        
-        if(style.stroke !== false) {
+        if (style.stroke !== false) {
             this.setCanvasStyle("stroke", style);
-            this.canvas.beginPath();
-            var start = this.getLocalXY(geometry.components[0]);
-            this.canvas.moveTo(start[0], start[1]);
-            for(var i = 1; i < geometry.components.length; i++) {
-                var pt = this.getLocalXY(geometry.components[i]);
-                this.canvas.lineTo(pt[0], pt[1]);
+            this.renderPath(this.canvas, geometry, style, featureId, "stroke");
+            if (this.hitDetection) {
+                this.setHitContextStyle("stroke", featureId, style);
+                this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
             }
-            this.canvas.stroke();
         }
         this.setCanvasStyle("reset");
-    },    
+    },
     
     /**
+     * Method: renderPath
+     * Render a path with stroke and optional fill.
+     */
+    renderPath: function(context, geometry, style, featureId, type) {
+        var components = geometry.components;
+        var len = components.length;
+        context.beginPath();
+        var start = this.getLocalXY(components[0]);
+        var x = start[0];
+        var y = start[1];
+        if (!isNaN(x) && !isNaN(y)) {
+            context.moveTo(start[0], start[1]);
+            for (var i=1; i<len; ++i) {
+                var pt = this.getLocalXY(components[i]);
+                context.lineTo(pt[0], pt[1]);
+            }
+            if (type === "fill") {
+                context.fill();
+            } else {
+                context.stroke();
+            }
+        }
+    },
+    
+    /**
      * Method: drawPolygon
      * This method is only called by the renderer itself.
      * 
      * Parameters: 
      * geometry - {<OpenLayers.Geometry>}
      * style    - {Object}
+     * featureId - {String}
      */ 
-    drawPolygon: function(geometry, style) {
-        this.drawLinearRing(geometry.components[0], style);
-        for (var i = 1; i < geometry.components.length; i++) {
-            this.drawLinearRing(geometry.components[i], {
-                fillOpacity: 0, 
-                strokeWidth: 0, 
-                strokeOpacity: 0, 
-                strokeColor: '#000000', 
-                fillColor: '#000000'}
-            ); // inner rings are 'empty'  
+    drawPolygon: function(geometry, style, featureId) {
+        var components = geometry.components;
+        var len = components.length;
+        this.drawLinearRing(components[0], style, featureId);
+        // erase inner rings
+        for (var i=1; i<len; ++i) {
+            /** 
+             * Note that this is overly agressive.  Here we punch holes through 
+             * all previously rendered features on the same canvas.  A better 
+             * solution for polygons with interior rings would be to draw the 
+             * polygon on a sketch canvas first.  We could erase all holes 
+             * there and then copy the drawing to the layer canvas. 
+             * TODO: http://trac.osgeo.org/openlayers/ticket/3130 
+             */
+            this.canvas.globalCompositeOperation = "destination-out";
+            if (this.hitDetection) {
+                this.hitContext.globalCompositeOperation = "destination-out";
+            }
+            this.drawLinearRing(
+                components[i], 
+                OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
+                featureId
+            );
+            this.canvas.globalCompositeOperation = "source-over";
+            if (this.hitDetection) {
+                this.hitContext.globalCompositeOperation = "source-over";
+            }
+            this.drawLinearRing(
+                components[i], 
+                OpenLayers.Util.applyDefaults({fill: false}, style),
+                featureId
+            );
         }
     },
     
@@ -408,8 +603,13 @@
      * Clear all vectors from the renderer.
      */    
     clear: function() {
-        this.canvas.clearRect(0, 0, this.root.width, this.root.height);
+        var height = this.root.height;
+        var width = this.root.width;
+        this.canvas.clearRect(0, 0, width, height);
         this.features = {};
+        if (this.hitDetection) {
+            this.hitContext.clearRect(0, 0, width, height);
+        }
     },
 
     /**
@@ -420,23 +620,28 @@
      * evt - {<OpenLayers.Event>} 
      *
      * Returns:
-     * {String} A feature id or null.
+     * {<OpenLayers.Feature.Vector} A feature or null.  This method returns a 
+     *     feature instead of a feature id to avoid an unnecessary lookup on the
+     *     layer.
      */
     getFeatureIdFromEvent: function(evt) {
-        var loc = this.map.getLonLatFromPixel(evt.xy);
-        var resolution = this.getResolution();
-        var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5, 
-                                           loc.lat - resolution * 5, 
-                                           loc.lon + resolution * 5, 
-                                           loc.lat + resolution * 5);
-        var geom = bounds.toGeometry();
-        for (var feat in this.features) {
-            if (!this.features.hasOwnProperty(feat)) { continue; }
-            if (this.features[feat][0].geometry.intersects(geom)) {
-                return feat;
+        var feature = null;
+        if (this.hitDetection) {
+            // this dragging check should go in the feature handler
+            if (!this.map.dragging) {
+                var xy = evt.xy;
+                var x = xy.x | 0;
+                var y = xy.y | 0;
+                var data = this.hitContext.getImageData(x, y, 1, 1).data;
+                if (data[3] === 255) { // antialiased
+                    var id = data[2] + (256 * (data[1] + (256 * data[0])));
+                    if (id) {
+                        feature = this.features["OpenLayers.Feature.Vector_" + (id - 1 + this.hitOverflow)][0];
+                    }
+                }
             }
-        }   
-        return null;
+        }
+        return feature;
     },
     
     /**
@@ -467,7 +672,12 @@
      */
     redraw: function() {
         if (!this.locked) {
-            this.canvas.clearRect(0, 0, this.root.width, this.root.height);
+            var height = this.root.height;
+            var width = this.root.width;
+            this.canvas.clearRect(0, 0, width, height);
+            if (this.hitDetection) {
+                this.hitContext.clearRect(0, 0, width, height);
+            }
             var labelMap = [];
             var feature, style;
             for (var id in this.features) {
@@ -475,7 +685,7 @@
                 feature = this.features[id][0];
                 style = this.features[id][1];
                 if (!feature.geometry) { continue; }
-                this.drawGeometry(feature.geometry, style);
+                this.drawGeometry(feature.geometry, style, feature.id);
                 if(style.label) {
                     labelMap.push([feature, style]);
                 }

Modified: sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer.js
===================================================================
--- sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer.js	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/lib/OpenLayers/Renderer.js	2011-04-01 19:54:37 UTC (rev 11854)
@@ -83,6 +83,7 @@
      */
     initialize: function(containerID, options) {
         this.container = OpenLayers.Util.getElement(containerID);
+        OpenLayers.Util.extend(this, options);
     },
     
     /**

Modified: sandbox/cmoullet/openlayers/tests/Control/DragFeature.html
===================================================================
--- sandbox/cmoullet/openlayers/tests/Control/DragFeature.html	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/tests/Control/DragFeature.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -103,6 +103,41 @@
              "onEnter called with expected feature");
     }
 
+    function test_Control_DragFeature_over_touch(t) {
+        t.plan(7);
+        var log = [];
+        var map = new OpenLayers.Map("map");
+        var layer = new OpenLayers.Layer.Vector();
+        map.addLayer(layer);
+        var control = new OpenLayers.Control.DragFeature(layer, {
+            onEnter: function(f) { log.push({feature: f}); }
+        });
+        map.addControl(control);
+
+        control.activate();
+        t.ok(!control.handlers.drag.active,
+             "drag handler is not active before touch on a feature");
+
+        // simulate a touch on a feature
+        var feature = new OpenLayers.Feature.Vector();
+        feature.layer = layer;
+        layer.getFeatureFromEvent = function(evt) {
+            return feature;
+        }
+        map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+
+        t.eq(control.feature.id, feature.id,
+             "control gets the proper feature from the feature handler");
+        t.ok(control.handlers.drag.active,
+             "drag handler activated when touch on a feature");
+        t.ok(control.handlers.drag.started, "drag handler has started");
+        t.ok(!control.handlers.drag.stopDown, "drag handler is not stopping down");
+        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) {
         t.plan(3);
         var map = new OpenLayers.Map("map");
@@ -284,6 +319,44 @@
              "onLeave called with expected feature");
     }
 
+    function test_Control_DragFeature_out_touch(t) {
+        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, {
+            onLeave: function(f) { log.push({feature: f}); }
+        });
+        map.addControl(control);
+
+        control.activate();
+
+        // simulate a touch on a feature
+        var feature = new OpenLayers.Feature.Vector();
+        feature.layer = layer;
+        layer.getFeatureFromEvent = function() {
+            return feature;
+        };
+        map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+        t.eq(control.feature.id, feature.id,
+             "feature is set on mouse over");
+
+        // simulate a touch outside the feature
+        layer.getFeatureFromEvent = function() {
+            return null;
+        };
+        map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+        t.ok(control.feature == null,
+             "feature is set to null on mouse out");
+        t.ok(control.handlers.drag.stopDown,
+             "drag handler is stopping down again");
+        t.eq(log.length, 1,
+             "onLeave called exactly once");
+        t.eq(log[0].feature.id, feature.id,
+             "onLeave called with expected feature");
+    }
+
     </script>
 </head>
 <body>

Modified: sandbox/cmoullet/openlayers/tests/Renderer/Canvas.html
===================================================================
--- sandbox/cmoullet/openlayers/tests/Renderer/Canvas.html	2011-04-01 09:26:39 UTC (rev 11853)
+++ sandbox/cmoullet/openlayers/tests/Renderer/Canvas.html	2011-04-01 19:54:37 UTC (rev 11854)
@@ -56,6 +56,75 @@
         t.eq(r.resolution, resolution, "resolution is correctly set");
     }
 
+    function test_featureIdToRGB(t) {
+        if (!supported) {
+            t.plan(0); 
+            return;
+        }
+        t.plan(2);
+        var el = document.body;
+        el.id = "foo";
+        var renderer = new OpenLayers.Renderer.Canvas(el.id);
+        
+        var cases = [{
+            id: "foo_0", rgb: [0, 0, 1]
+        }, {
+            id: "foo_10", rgb: [0, 0, 11]
+        }, {
+            id: "foo_100", rgb: [0, 0, 101]
+        }, {
+            id: "foo_1000000", rgb: [15, 66, 65]
+        }, {
+            id: "foo_16777214", rgb: [255, 255, 255]
+        }, {
+            id: "foo_16777215", rgb: [0, 0, 1]
+        }];
+        t.plan(cases.length);
+        
+        var c;
+        for (var i=0; i<cases.length; ++i) {
+            c = cases[i];
+            t.eq(renderer.featureIdToRGB(c.id), c.rgb, c.id);
+        }
+        
+        renderer.destroy();
+    }
+
+    function test_featureIdToHex(t) {
+        if (!supported) {
+            t.plan(0); 
+            return;
+        }
+        t.plan(2);
+        var el = document.body;
+        el.id = "foo";
+        var renderer = new OpenLayers.Renderer.Canvas(el.id);
+        
+        var cases = [{
+            id: "foo_0", hex: "#000001"
+        }, {
+            id: "foo_10", hex: "#00000b"
+        }, {
+            id: "foo_100", hex: "#000065"
+        }, {
+            id: "foo_1000000", hex: "#0f4241"
+        }, {
+            id: "foo_16777214", hex: "#ffffff"
+        }, {
+            id: "foo_16777215", hex: "#000001"
+        }];
+        t.plan(cases.length);
+        
+        var c;
+        for (var i=0; i<cases.length; ++i) {
+            c = cases[i];
+            t.eq(renderer.featureIdToHex(c.id), c.hex, c.id);
+        }
+        
+        renderer.destroy();
+    }
+
+
     function test_Renderer_Canvas_destroy(t) {
         if (!supported) { t.plan(0); return; }
         t.plan(5);
@@ -77,7 +146,86 @@
         t.eq(r.resolution, null, "resolution nullified");
         t.eq(r.map, null, "map nullified");
     }
+    
+    function test_hitDetection(t) {
+        if (!supported) {
+            t.plan(0); 
+            return; 
+        }
+        
+        var layer = new OpenLayers.Layer.Vector(null, {
+            isBaseLayer: true,
+            resolutions: [1],
+            styleMap: new OpenLayers.StyleMap({
+                pointRadius: 5,
+                strokeWidth: 3,
+                fillColor: "red",
+                fillOpacity: 0.5,
+                strokeColor: "blue",
+                strokeOpacity: 0.75
+            }),
+            renderers: ["Canvas"]
+        });
+        
+        var map = new OpenLayers.Map({
+            div: "map",
+            controls: [],
+            layers: [layer],
+            center: new OpenLayers.LonLat(0, 0),
+            zoom: 0
+        });
+        
+        layer.addFeatures([
+            new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Point(-100, 0)
+            ),
+            new OpenLayers.Feature.Vector(
+                OpenLayers.Geometry.fromWKT("LINESTRING(-50 0, 50 0)")
+            ),
+            new OpenLayers.Feature.Vector(
+                OpenLayers.Geometry.fromWKT("POLYGON((100 -25, 150 -25, 150 25, 100 25, 100 -25), (120 -5, 130 -5, 130 5, 120 5, 120 -5))")
+            )
+        ]);
+        
+        var cases = [{
+            msg: "center of point", x: -100, y: 0, id: layer.features[0].id
+        }, {
+            msg: "edge of point", x: -103, y: 3, id: layer.features[0].id
+        }, {
+            msg: "outside point", x: -110, y: 3, id: null
+        }, {
+            msg: "center of line", x: 0, y: 0, id: layer.features[1].id
+        }, {
+            msg: "edge of line", x: 0, y: 1, id: layer.features[1].id
+        }, {
+            msg: "outside line", x: 0, y: 5, id: null
+        }, {
+            msg: "inside polygon", x: 110, y: 0, id: layer.features[2].id
+        }, {
+            msg: "edge of polygon", x: 99, y: 0, id: layer.features[2].id
+        }, {
+            msg: "inside polygon hole", x: 125, y: 0, id: null
+        }, {
+            msg: "outside polygon", x: 155, y: 0, id: null
+        }];
 
+        function px(x, y) {
+            return map.getPixelFromLonLat(
+                new OpenLayers.LonLat(x, y)
+            );
+        }        
+        
+        var num = cases.length;
+        t.plan(num);
+        var c, feature;
+        for (var i=0; i<num; ++i) {
+            c = cases[i];
+            feature = layer.renderer.getFeatureIdFromEvent({xy: px(c.x, c.y)});
+            t.eq(feature && feature.id, c.id, c.msg);
+        }
+        
+    }
+
   </script>
 </head>
 <body>



More information about the Commits mailing list