Index: includes/openlayers.behaviors.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/openlayers/includes/Attic/openlayers.behaviors.inc,v
retrieving revision 1.1.2.14
diff -u -p -r1.1.2.14 openlayers.behaviors.inc
--- includes/openlayers.behaviors.inc	7 Jun 2010 20:21:25 -0000	1.1.2.14
+++ includes/openlayers.behaviors.inc	23 Jul 2010 16:19:32 -0000
@@ -46,6 +46,17 @@ function _openlayers_openlayers_behavior
         'parent' => 'openlayers_behavior',
       ),
     ),
+    'openlayers_behavior_query' => array(
+      'title' => t('Query'),
+      'description' => t('Adds info boxes on maps clicks.'),
+      'type' => 'layer',
+      'path' => drupal_get_path('module', 'openlayers') .'/includes/behaviors',
+      'file' => 'openlayers_behavior_query.inc',
+      'behavior' => array(
+        'class' => 'openlayers_behavior_query',
+        'parent' => 'openlayers_behavior',
+      ),
+    ),
     'openlayers_behavior_tooltip' => array(
       'title' => t('Tooltip'),
       'description' => t('Adds info boxes on hover to points or shapes on maps.'),
Index: includes/behaviors/openlayers_behavior_query.inc
===================================================================
RCS file: includes/behaviors/openlayers_behavior_query.inc
diff -N includes/behaviors/openlayers_behavior_query.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/behaviors/openlayers_behavior_query.inc	23 Jul 2010 16:19:33 -0000
@@ -0,0 +1,79 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Implementation of OpenLayers behavior.
+ */
+
+/**
+ * Query Behavior
+ */
+class openlayers_behavior_query extends openlayers_behavior {
+  /**
+   * Provide initial values for options.
+   */
+  function options_init() {
+    return array(
+    );
+  }
+
+  /**
+   * Provide form for configurations per map.
+   */
+  function options_form($defaults) {
+
+    // Build a list of supported layers
+    // We support: vectors and WMS for now
+    $queriable_layers = array();
+    foreach ($this->map['layers'] as $id => $name) {
+      $layer = openlayers_layer_load($id);
+      if ( isset( $layer->data['vector'] )
+           && $layer->data['vector'] == TRUE) {
+        $queriable_layers[$id] = $name;
+      }
+      else if ( isset( $layer->data['layer_handler'] )
+                && $layer->data['layer_handler'] == 'wms') {
+        // TODO: for WMS check queriability
+        $queriable_layers[$id] = $name;
+      }
+    }
+
+    return array(
+      'layers' => array(
+        '#title' => t('Layers'),
+        '#type' => 'checkboxes',
+        '#options' => $queriable_layers,
+        '#description' => t('Select layers to enable query on.'),
+        '#default_value' => isset($defaults['layers']) ? 
+          $defaults['layers'] : array(),
+      ),
+      'radius' => array(
+        '#title' => t('Query radius'),
+        '#type' => 'textfield',
+        '#default_value' => (isset($defaults['radius'])) ?
+          $defaults['radius'] : 10,
+        '#description' => t('Radius to use for the query, in pixels. This is currently only supported for vector layers as WMS specs do not allow for that.'),
+      ),
+      'timeout' => array(
+        '#title' => t('Query timeout'),
+        '#type' => 'textfield',
+        '#default_value' => (isset($defaults['timeout'])) ?
+          $defaults['timeout'] : 1000,
+        '#description' => t('Timeout for WMS layer queries, in milliseconds'),
+      ),
+    );
+  }
+
+  /**
+   * Render.
+   */
+  function render(&$map) {
+    drupal_add_css(drupal_get_path('module', 'openlayers') .
+      '/includes/behaviors/js/openlayers_behavior_query.css');
+    drupal_add_js(drupal_get_path('module', 'openlayers') .
+      '/includes/behaviors/js/openlayers_behavior_query.js');
+    return $this->options;
+  }
+}
+
Index: includes/behaviors/js/openlayers_behavior_query.css
===================================================================
RCS file: includes/behaviors/js/openlayers_behavior_query.css
diff -N includes/behaviors/js/openlayers_behavior_query.css
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/behaviors/js/openlayers_behavior_query.css	23 Jul 2010 16:19:33 -0000
@@ -0,0 +1,11 @@
+/* $Id: openlayers_behavior_drawfeatures.css,v 1.1.2.2 2010/05/22 23:04:53 zzolo Exp $ */
+
+/**
+ * @file
+ * CSS for OpenLayers Query Behavior
+ */
+
+.openlayers-query .openlayers-query-layer {
+  background-color: #AAAAAA;
+}
+
Index: includes/behaviors/js/openlayers_behavior_query.js
===================================================================
RCS file: includes/behaviors/js/openlayers_behavior_query.js
diff -N includes/behaviors/js/openlayers_behavior_query.js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/behaviors/js/openlayers_behavior_query.js	23 Jul 2010 16:19:33 -0000
@@ -0,0 +1,357 @@
+// $Id$
+
+/**
+ * @file
+ * Query Behavior
+ */
+
+/*
+ * OpenLayers Query Control class
+ */
+Drupal.openlayers.QueryControl = OpenLayers.Class(OpenLayers.Control, {
+
+  defaultHandlerOptions: {
+    'single': true,
+    'double': false,
+    'pixelTolerance': 0,
+    'stopSingle': false,
+    'stopDouble': false
+  },
+
+  /**
+   * Constructor: <Drupal.behaviors.QueryControl>
+   *
+   * Parameters:
+   * @options - {Object}
+   * In addition to common Control options, this class also accepts:
+   *  'radius'           Query radius (for vector layers)
+   *  'timeout'          Timeout for WMS GetFeatureInfo request, milliseconds
+   *  'qlayer_ids'       Identifying values for layers to query
+   *  'qlayer_id_field'  Name of the field containing layer's identifier
+   */
+  initialize: function(options) {
+
+    this.handlerOptions = OpenLayers.Util.extend(
+      {}, this.defaultHandlerOptions
+    );
+
+    OpenLayers.Control.prototype.initialize.apply(this, arguments); 
+
+    //console.log("handlerOptions: "); console.dir(this.handlerOptions);
+    this.handler = new OpenLayers.Handler.Click(
+      this, {
+        'click': this.trigger
+      }, this.handlerOptions
+    );
+
+  }, 
+
+  /**
+   * Method: getCandidateLayers
+   * Internal method for lazy candidate layers extraction
+   *
+   * Returns:
+   * {Array} list of layer references found on map with given id
+   */
+  getCandidateLayers: function() {
+
+    if ( ! this.candidateLayers ) {
+      this.candidateLayers = [];
+
+      for (var i=0, len=this.qlayers.length; i<len; ++i) {
+        var layer_id = this.qlayers[i];
+        var selectedLayer = this.map.getLayersBy(this.qlayers_id_field,
+                                                 layer_id);
+        if (typeof selectedLayer[0] != 'undefined') {
+          this.candidateLayers.push(selectedLayer[0]);
+        }
+      }
+    }
+
+    //console.log("Candidate layers:"); console.dir(this.candidateLayers);
+
+    return this.candidateLayers;
+  },
+
+  /* private
+   *
+   * Compute per-query informations 
+   * - layers to query (only visible ones from the candidate list)
+   * - tolerance in geographical units (depends on scale)
+   * - queryPoint in geographical units (depends on query point)
+   */
+  prepareQuery: function(evt) {
+
+    // TODO: cancel anything related to previous query
+    //       if any
+
+    var px1 = evt.xy;
+    var px2 = evt.xy.add(this.radius, 0);
+
+    var lonlat1 = this.map.getLonLatFromPixel(px1);
+    var lonlat2 = this.map.getLonLatFromPixel(px2);
+
+    var queryPoint = new OpenLayers.Geometry.Point(lonlat1.lon, lonlat1.lat);
+    var distPoint = new OpenLayers.Geometry.Point(lonlat2.lon, lonlat2.lat);
+
+    this.tolerance = queryPoint.distanceTo(distPoint);
+    this.queryPoint = queryPoint;
+    this.queryPixel = this.map.getLonLatFromPixel(px1);
+    this.layerInfo = [];
+
+    // Find the actual layers to query, based on their visibility
+    this.queryLayers = [];
+    var candidateLayers = this.getCandidateLayers();
+    for (var i=0, len=candidateLayers.length; i<len; ++i) {
+      var layer = candidateLayers[i];
+
+      // Only query visible layers
+      if ( layer.getVisibility() ) {
+        var typ = layer.CLASS_NAME.split('.')[2];
+        var meth = 'query'+typ+'Layer';
+        if ( typeof this[meth] == 'function' ) {
+          this.queryLayers.push({'layer': layer, 'meth': meth});
+        }
+      }
+
+    }
+
+    // TODO: draw a circle representing the query area ?
+
+  },
+
+  /* Private: perform a clik on map... */
+  trigger: function(evt) {
+
+    if ( ! evt ) return;
+
+    this.prepareQuery(evt);
+
+    /* Query layers */
+    for (var i=0, len=this.queryLayers.length; i<len; ++i) {
+      var layer = this.queryLayers[i];
+      var meth = layer.meth;
+      this[meth](evt, layer);
+    }
+  },
+
+  addLayerInfo: function(querylayer, info) {
+
+    var layer = querylayer.layer;
+    this.layerInfo.push({'layer': layer, 'info': info});
+
+    if (this.layerInfo.length >= this.queryLayers.length) {
+      this.addPopup();
+    } 
+
+    // TODO: debug log when getting called past the expected number of times ?
+  },
+
+  addPopup: function() {
+
+    var content = [];
+
+    for (var i=0, len=this.layerInfo.length; i<len; ++i) {
+      var data = this.layerInfo[i];
+      if ( data.info != '') {
+        var info = [];
+        info.push('<div class="openlayers-query openlayers-query-layer">');
+        info.push('<div class="openlayers-query openlayers-query-layer-name">');
+        info.push('<strong>' + data.layer.name + '</strong>');
+        info.push('</div>');
+        info.push(data.info);
+        info.push('</div>');
+        content.push(info.join(''));
+      }
+    }
+
+    var html = content.join('<hr>');
+    if ( html == '' ) html = 'Nothing here';
+
+    var popup = new OpenLayers.Popup.FramedCloud(
+      'popup',
+      this.queryPixel,
+      null,
+      html,
+      null,
+      true,
+      function (e) {
+        this.map.removePopup(this.map.queryPopup);
+      }
+    );
+    if ( typeof this.map.queryPopup != 'undefined' ) {
+      this.map.removePopup(this.map.queryPopup);
+    }
+    this.map.queryPopup = popup;
+    this.map.addPopup(popup);
+  },
+
+  /* private
+   *
+   * Perform query against a WMS layer
+   *
+   * @param querylayer internal structure containing
+   *                   informations about the layer to query
+   */
+  queryWMSLayer: function(evt, querylayer) {
+
+    var layer = querylayer.layer;
+
+    // TODO: add request for possibly multiple-features
+    // TODO2: try to ask for a tolerance (but WMS spec don't mention that)
+    var url = layer.getFullRequestString({
+      REQUEST: "GetFeatureInfo",
+      EXCEPTIONS: "application/vnd.ogc.se_xml",
+      BBOX: layer.map.getExtent().toBBOX(),
+      X: evt.xy.x,
+      Y: evt.xy.y,
+      INFO_FORMAT: 'text/html',
+      QUERY_LAYERS: layer.params.LAYERS,
+      WIDTH: layer.map.size.w,
+      HEIGHT: layer.map.size.h
+    });
+
+    var request = null
+    try {
+      request = OpenLayers.Request.GET({
+        url: url,
+        callback: function(req) {
+
+          /*  Remove timeout, if any */
+          if ( querylayer.timeout ) {
+            window.clearTimeout(querylayer.timeout);
+            delete querylayer.timeout;
+          }
+
+          // see http://docs.openlayers.org/library/request.html
+          var info = '';
+          if ( req.status == 200 ) {
+            // extract just the body content
+            var match = req.responseText.match(/<body[^>]*>([\s\S]*)<\/body>/);
+            if (match && !match[1].match(/^\s*$/)) {
+              info += match[1];
+            }
+          } else {
+            //info += req.status+" response from server";
+          }
+          this.addLayerInfo(querylayer, info);
+        },
+        scope: this
+        //, proxy: <proxy_here>, or defaults to OpenLayers.ProxyHost
+        //                       which can be set on a preset-basis
+      });
+
+      querylayer.request = request;
+      var timeoutHandler = function() {
+        if ( querylayer.request ) {
+          //console.log("Aborting request");
+          querylayer.request.abort();
+        }
+        this.addLayerInfo(querylayer, '-timeout-');
+      }
+
+
+      /*  Add timeout */
+      querylayer.timeout = window.setTimeout(
+        OpenLayers.Function.bind( timeoutHandler, this ),
+        this.timeout );
+
+    } catch (e) {
+
+      this.addLayerInfo(querylayer,
+        'Could not query layer "'+layer.name+'": ' + e + ' (need proxy?)');
+
+    }
+
+  },
+
+  /* private
+   *
+   * Perform query against a Vector layer
+   * requires 'this.queryPoint' and 'this.tolerance' being set,
+   * which are taken care of by 'this.prepareQuery'
+   *
+   * @param querylayer internal structure containing
+   *                   informations about the layer to query
+   */
+  queryVectorLayer: function(evt, querylayer) {
+    
+    var layer = querylayer.layer;
+    var info = [];
+
+    for(var i=0, len=layer.features.length; i<len; ++i)  {
+      var feature = layer.features[i];
+      var dist = this.queryPoint.distanceTo(feature.geometry);
+      if ( dist <= this.tolerance ) {
+        //info.push('Distant: '+dist);
+        info.push(Drupal.theme('openlayersFeatureInfo', feature));
+      }
+    }
+
+    this.addLayerInfo(querylayer, info.join(''));
+  }
+
+});
+
+/* 
+ * Thematic feature info formatter
+ */
+Drupal.theme.prototype.openlayersFeatureInfo = function(feature) {
+  var output = '';
+  if(feature.cluster)
+  {
+    var visited = []; // to keep track of already-visited items
+    for(var i=0, len=feature.cluster.length; i<len; ++i) {
+      var pf = feature.cluster[i]; // pseudo-feature
+      if ( typeof pf.drupalFID != 'undefined' ) {
+        var mapwide_id = feature.layer.drupalID + pf.drupalFID;
+        if (mapwide_id in visited) continue;
+        visited[mapwide_id] = true;
+      }
+      output += '<div class="openlayers-query openlayers-query-feature">' +
+        arguments.callee(pf) + '</div>';
+    }
+  }
+  else
+  {
+    output =
+      '<div class="openlayers-query openlayers-query-feature-name">' +
+      feature.attributes.name +
+      '</div>' +
+      '<div class="openlayers-query openlayers-query-feature-description">' +
+      feature.attributes.description +
+      '</div>';
+  }
+  return output;
+};
+
+/**
+ * Behavior for Query 
+ */
+Drupal.behaviors.openlayers_behavior_query = function(context) {
+
+  var data = $(context).data('openlayers');
+  if (data && data.map.behaviors['openlayers_behavior_query']) {
+
+    var options = data.map.behaviors['openlayers_behavior_query'];
+
+    var qlayers = [];
+    for (var i in options.layers) {
+      var layer_id = options.layers[i];
+      if ( layer_id !== 0 ) qlayers.push(layer_id);
+    }
+
+    var queryControl = new Drupal.openlayers.QueryControl({
+      qlayers_id_field: 'drupalID',
+      qlayers: qlayers,
+      radius: parseInt(options.radius, 10),
+      timeout: parseInt(options.timeout, 10),
+      autoActivate: true
+    });
+
+    var map = data.openlayers;
+    map.addControl(queryControl);
+  }
+
+};
+
