Touching up WMSGetFeatureInfo. Requests are now sent out with correct case on request parameter. Requests are only sent out if there are layers to query. The control has a single format with configurable options. Requests can be made in cases where layer url differs from control url. The control url property is optional. In cases where it is not provided, the url of the first eligible layer will be used. Tests pass and example works (for the first time) in IE as well.

git-svn-id: http://svn.openlayers.org/trunk/openlayers@9300 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
Tim Schaub
2009-04-15 23:44:29 +00:00
parent cd815bc2a2
commit d54440b0e9
4 changed files with 354 additions and 179 deletions

View File

@@ -5,39 +5,75 @@
<link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
<link rel="stylesheet" href="style.css" type="text/css" />
<style type="text/css">
ul, li { padding-left: 0px; margin-left: 0px; }
ul, li {
padding-left: 0px;
margin-left: 0px;
list-style: none;
}
#info {
position: absolute;
top: 6em;
left: 550px;
}
#info table td {
border:1px solid #ddd;
border-collapse: collapse;
margin: 0;
padding: 0;
font-size: 90%;
padding: .2em .1em;
background:#fff;
}
#info table th{
padding:.2em .2em;
text-transform: uppercase;
font-weight: bold;
background: #eee;
}
tr.odd td {
background:#eee;
}
table.featureInfo caption {
text-align:left;
font-size:100%;
font-weight:bold;
text-transform:uppercase;
padding:.2em .2em;
}
</style>
<script defer="defer" type="text/javascript">
OpenLayers.ProxyHost = "/dev/examples/proxy.cgi?url=";
OpenLayers.ProxyHost = "/proxy/?url=";
var map, infocontrols, vegetation, highlightlayer;
var map, infocontrols, water, highlightlayer;
function load() {
map = new OpenLayers.Map('map', {
maxExtent: new OpenLayers.Bounds(143.834,-43.648,148.479,-39.573)
});
var roads = new OpenLayers.Layer.WMS("State Boundaries",
var political = new OpenLayers.Layer.WMS("State Boundaries",
"http://demo.opengeo.org/geoserver/wms",
{'layers': 'topp:tasmania_state_boundaries', transparent: true, format: 'image/png'},
{'layers': 'topp:tasmania_state_boundaries', transparent: true, format: 'image/gif'},
{isBaseLayer: true}
);
var natural = new OpenLayers.Layer.WMS("Roads",
var roads = new OpenLayers.Layer.WMS("Roads",
"http://demo.opengeo.org/geoserver/wms",
{'layers': 'topp:tasmania_roads', transparent: true, format: 'image/png'},
{'layers': 'topp:tasmania_roads', transparent: true, format: 'image/gif'},
{isBaseLayer: false}
);
var points = new OpenLayers.Layer.WMS("Cities",
var cities = new OpenLayers.Layer.WMS("Cities",
"http://demo.opengeo.org/geoserver/wms",
{'layers': 'topp:tasmania_cities', transparent: true, format: 'image/png'},
{'layers': 'topp:tasmania_cities', transparent: true, format: 'image/gif'},
{isBaseLayer: false}
);
vegetation = new OpenLayers.Layer.WMS("Bodies of Water",
water = new OpenLayers.Layer.WMS("Bodies of Water",
"http://demo.opengeo.org/geoserver/wms",
{'layers': 'topp:tasmania_water_bodies', transparent: true, format: 'image/png'},
{'layers': 'topp:tasmania_water_bodies', transparent: true, format: 'image/gif'},
{isBaseLayer: false}
);
@@ -48,25 +84,27 @@
);
infoControls = {
click: new OpenLayers.Control.WMSGetFeatureInfo('http://demo.opengeo.org/geoserver/wms', {
click: new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://demo.opengeo.org/geoserver/wms',
title: 'Identify features by clicking',
layers: [vegetation],
layers: [water],
queryVisible: true
}),
hover: new OpenLayers.Control.WMSGetFeatureInfo('http://demo.opengeo.org/geoserver/wms', {
hover: new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://demo.opengeo.org/geoserver/wms',
title: 'Identify features by clicking',
layers: [vegetation],
layers: [water],
hover: true,
// defining a custom format here
formats: {'application/vnd.ogc.gml': new OpenLayers.Format.GML({
// defining a custom format options here
formatOptions: {
typeName: 'water_bodies',
featureNS: 'http://www.openplans.org/topp'
})},
},
queryVisible: true
})
}
map.addLayers([roads, natural, points, vegetation, highlightLayer]);
map.addLayers([political, roads, cities, water, highlightLayer]);
for (var i in infoControls) {
infoControls[i].events.register("getfeatureinfo", this, showInfo);
map.addControl(infoControls[i]);
@@ -84,7 +122,7 @@
highlightLayer.addFeatures(evt.features);
highlightLayer.redraw();
} else {
$('nodeList').innerHTML = evt.text;
$('responseText').innerHTML = evt.text;
}
}
@@ -110,7 +148,7 @@
for (var key in infoControls) {
var control = infoControls[key];
if (element.value == 'Specified') {
control.layers = [vegetation];
control.layers = [water];
} else {
control.layers = null;
}
@@ -126,15 +164,13 @@
<div id="tags"></div>
<p id="shortdesc">
Demonstrates the WMSGetFeatureInfo control for fetching information about a position from WMS.
Demonstrates the WMSGetFeatureInfo control for fetching information about a position from WMS (via GetFeatureInfo request).
</p>
<a id="permalink" href="">Permalink</a><br />
<div style="float:right;width:28%">
<h1 style="font-size:1.3em;">Tasmania</h1>
<p style="font-size:.8em;">Click on the map to get feature info.</p>
<div id="nodeList">
<div id="info">
<h1>Tasmania</h1>
<p>Click on the map to get feature info.</p>
<div id="responseText">
</div>
</div>
<div id="map" class="smallmap"></div>
@@ -169,12 +205,12 @@
<li>
<input type="radio" name="layerSelection" value="Specified" id="Specified"
onclick="toggleLayers(this);" checked="checked" />
<label for="html">Get Vegetation info</label>
<label for="Specified">Get water body info</label>
</li>
<li>
<input type="radio" name="layerSelection" value="Auto" id="Auto"
onclick="toggleLayers(this);" />
<label for="">Get info for visible layers</label>
<label for="Auto">Get info for visible layers</label>
</li>
</ul>
</body>

View File

@@ -26,43 +26,50 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
/**
* APIProperty: hover
* {Boolean} Send WMS request on mouse moves. This will cause the
* "hoverfeature" and "outfeature" events to be triggered.
* {Boolean} Send GetFeatureInfo requests when mouse stops moving.
* Default is false.
*/
hover: false,
/**
* APIProperty: maxFeatures
* {Integer} maximum number of features to return from a WMS query. This
* has the same effect as the feature_count parameter on WMS GetFeatureInfo
* requests. Will be ignored for box selections.
* {Integer} Maximum number of features to return from a WMS query. This
* sets the feature_count parameter on WMS GetFeatureInfo
* requests.
*/
maxFeatures: 10,
/**
* Property: layers
* {<Array(OpenLayers.Layer.WMS>} The Layer objects for which to find
* feature info. If omitted, search all WMS layers from the map whose url
* is the same as the one in this control's configuration. See the
* <queryVisible> property
* {Array(<OpenLayers.Layer.WMS>)} The layers to query for feature info.
* If omitted, all map WMS layers with a url that matches this <url> or
* <layerUrl> will be considered.
*/
layers: null,
/**
* Property: queryVisible
* {Boolean} If true, filter out hidden layers when searching the map for
* layers to query. If an explicit layers parameter is set, then this does
* nothing.
* layers to query. Default is false.
*/
queryVisible: false,
/**
* Property: url
* {String} The URL of the WMS service to use.
* {String} The URL of the WMS service to use. If not provided, the url
* of the first eligible layer will be used.
*/
url: null,
/**
* Property: layerUrls
* {Array(String)} Optional list of urls for layers that should be queried.
* This can be used when the layer url differs from the url used for
* making GetFeatureInfo requests (in the case of a layer using cached
* tiles).
*/
layerUrls: null,
/**
* Property: infoFormat
* {String} The mimetype to request from the server
@@ -82,17 +89,18 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
vendorParams: {},
/**
* Property: formats
* An object mapping from mime-types to OL Format objects to use when
* parsing GFI responses. The default mapping is
* (start code)
* {
* 'application/vnd.ogc.gml': new OpenLayers.Format.WMSGetFeatureInfo()
* }
* (end)
* An object provided here will extend the default mapping.
* Property: format
* {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
* Default is <OpenLayers.Format.WMSGetFeatureInfo>.
*/
formats: null,
format: null,
/**
* Property: formatOptions
* {Object} Optional properties to set on the format (if one is not provided
* in the <format> property.
*/
formatOptions: null,
/**
* APIProperty: handlerOptions
@@ -122,24 +130,23 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
/**
* Constant: EVENT_TYPES
*
* Supported event types:
* - *getfeatureinfo* Triggered when a GetFeatureInfo response is received.
* The event object has a text property with:
* text: a string containing the body of the response,
* features: an array of the parsed features, if parsing succeeded.
* xy: the position of the mouse click or hover event that
* triggered the request
* Supported event types (in addition to those from <OpenLayers.Control>):
* getfeatureinfo - Triggered when a GetFeatureInfo response is received.
* The event object has a *text* property with the body of the
* response (String), a *features* property with an array of the
* parsed features, an *xy* property with the position of the mouse
* click or hover event that triggered the request, and a *request*
* property with the request itself.
*/
EVENT_TYPES: ["getfeatureinfo"],
/**
* Constructor: <OpenLayers.Control.SelectFeature>
* Constructor: <OpenLayers.Control.WMSGetFeatureInfo>
*
* Parameters:
* url - {String}
* options - {Object}
*/
initialize: function(url, options) {
initialize: function(options) {
// concatenate events specific to vector with those from the base
this.EVENT_TYPES =
OpenLayers.Control.WMSGetFeatureInfo.prototype.EVENT_TYPES.concat(
@@ -150,13 +157,12 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
options.handlerOptions = options.handlerOptions || {};
OpenLayers.Control.prototype.initialize.apply(this, [options]);
this.url = url;
this.layers = options.layers;
this.formats = OpenLayers.Util.extend({
'application/vnd.ogc.gml': new OpenLayers.Format.WMSGetFeatureInfo()
}, options.formats);
if(!this.format) {
this.format = new OpenLayers.Format.WMSGetFeatureInfo(
options.formatOptions
);
}
if (this.hover) {
this.handler = new OpenLayers.Handler.Hover(
@@ -244,29 +250,62 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
* inspecting the map or using a client-provided array
*/
findLayers: function() {
if (this.layers) return this.layers;
var layers = [];
for (var i = 0, len = this.map.layers.length; i < len; i++) {
var mapLayer = this.map.layers[i];
if (mapLayer instanceof OpenLayers.Layer.WMS
&& mapLayer.url === this.url
&& (!this.queryVisible || mapLayer.getVisibility())) {
layers.push(mapLayer);
var candidates = this.layers || this.map.layers;
var layer, url;
for(var i=0, len=candidates.length; i<len; ++i) {
layer = candidates[i];
if(layer instanceof OpenLayers.Layer.WMS &&
(!this.queryVisible || layer.getVisibility())) {
url = layer.url instanceof Array ? layer.url[0] : layer.url;
// if the control was not configured with a url, set it
// to the first layer url
if(!this.url) {
this.url = url;
}
if(this.urlMatches(url)) {
layers.push(layer);
}
}
}
return layers;
},
/**
* Method: urlMatches
* Test to see if the provided url matches either the control <url> or one
* of the <layerUrls>.
*
* Parameters:
* url - {String} The url to test.
*
* Returns:
* {Boolean} The provided url matches the control <url> or one of the
* <layerUrls>.
*/
urlMatches: function(url) {
var matches = OpenLayers.Util.isEquivalentUrl(this.url, url);
if(!matches && this.layerUrls) {
for(var i=0, len=this.layerUrls.length; i<len; ++i) {
if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i], url)) {
matches = true;
break;
}
}
}
return matches;
},
/**
* Method: request
* Sends a GetFeatureInfo request to the WMS
*
* Parameters:
* clickPosition - The position on the map where the mouse event occurred
* clickPosition - {<OpenLayers.Pixel>} The position on the map where the
* mouse event occurred.
* options - {Object} additional options for this method.
*
* Valid options:
@@ -278,6 +317,7 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
var styleNames = [];
var layers = this.findLayers();
if(layers.length > 0) {
for (var i = 0, len = layers.length; i < len; i++) {
layerNames = layerNames.concat(layers[i].params.LAYERS);
@@ -301,7 +341,7 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
params: OpenLayers.Util.applyDefaults({
service: "WMS",
version: "1.1.0",
request: 'getfeatureinfo',
request: "GetFeatureInfo",
layers: layerNames,
query_layers: layerNames,
styles: styleNames,
@@ -314,10 +354,10 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
width: this.map.getSize().w,
info_format: this.infoFormat
}, this.vendorParams),
callback: this.handleResponse,
scope: OpenLayers.Util.extend({
xy: clickPosition
}, this)
callback: function(request) {
this.handleResponse(clickPosition, request);
},
scope: this
};
var response = OpenLayers.Request.GET(wmsOptions);
@@ -325,30 +365,31 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
if (options.hover === true) {
this.hoverRequest = response.priv;
}
}
},
/**
* Method: handleResponse
* Handler for the GetFeatureInfo response. Will be called in an extended
* scope of this instance plus an xy property with the click position.
* Handler for the GetFeatureInfo response.
*
* Parameters:
* response - {Object}
* xy - {<OpenLayers.Pixel>} The position on the map where the
* mouse event occurred.
* request - {XMLHttpRequest} The request object.
*/
handleResponse: function(response) {
var features;
var fmt = this.formats[
response.getResponseHeader("Content-type").split(";")[0]
];
if (fmt) {
features = fmt.read(response.responseXml ||
response.responseText);
handleResponse: function(xy, request) {
var doc = request.responseXML;
if(!doc || !doc.documentElement) {
doc = request.responseText;
}
var features = this.format.read(doc);
this.events.triggerEvent("getfeatureinfo", {
text: response.responseText,
text: request.responseText,
features: features,
xy: this.xy
request: request,
xy: xy
});
// Reset the cursor.

View File

@@ -77,6 +77,7 @@ OpenLayers.Format.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Format.XML, {
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
}
var root = data.documentElement;
if(root) {
var scope = this;
var read = this["read_" + root.nodeName];
if(read) {
@@ -86,6 +87,9 @@ OpenLayers.Format.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Format.XML, {
// GetFeatureInfo responses
result = new OpenLayers.Format.GML((this.options ? this.options : {})).read(data);
}
} else {
result = data;
}
return result;
},

View File

@@ -2,37 +2,40 @@
<head>
<script src="../../lib/OpenLayers.js"></script>
<script type="text/javascript">
function test_WMSGetFeatureInfo_constructor(t) {
t.plan(4);
function test_initialize(t) {
t.plan(5);
var options = {
layers: 'ns:type',
formats: {"application/vnd.ogc.gml": "foo"}
url: 'http://localhost/wms',
layers: ["foo"],
formatOptions: {
foo: "bar"
}
};
var layer = "bar";
var control = new OpenLayers.Control.WMSGetFeatureInfo('http://localhost/wms', options);
var control = new OpenLayers.Control.WMSGetFeatureInfo(options);
t.ok(control instanceof OpenLayers.Control.WMSGetFeatureInfo,
"new OpenLayers.Control.WMSGetFeatureInfo returns an instance");
t.eq(control.url, 'http://localhost/wms',
"constructor sets url correctly");
t.eq(control.layers, "ns:type",
"constructor sets options correctly on feature handler"
t.eq(control.layers, ["foo"],
"constructor layers"
);
t.eq(control.formats["application/vnd.ogc.gml"], "foo", 'Custom format passed through properly');
t.ok(control.format instanceof OpenLayers.Format.WMSGetFeatureInfo, "format created");
t.eq(control.format.foo, "bar", "format options used")
}
function test_Control_WMSGetFeatureInfo_destroy(t) {
function test_destroy(t) {
t.plan(2);
var map = new OpenLayers.Map("map");
var click = new OpenLayers.Control.WMSGetFeatureInfo('http://localhost/wms', {
layers: 'ns:type',
featureNS: 'http://localhost/ns',
featureType: 'type'
var click = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
layers: ["foo"]
});
var hover = new OpenLayers.Control.WMSGetFeatureInfo('http://localhost/wms', {
layers: 'ns:type',
featureNS: 'http://localhost/ns',
featureType: 'type',
var hover = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
layers: ["foo"],
hover: true
});
@@ -48,7 +51,7 @@
hover.destroy();
}
function test_Control_WMSGetFeatureInfo_click(t) {
function test_click(t) {
t.plan(4);
var map = new OpenLayers.Map('map');
@@ -68,15 +71,17 @@
control.getInfoForHover({xy: {x: 50, y: 50}});
}
function test_Control_WMSGetFeatureInfo_activate(t) {
function test_activate(t) {
t.plan(4);
var map = new OpenLayers.Map("map");
var click = new OpenLayers.Control.WMSGetFeatureInfo('http://localhost/wms', {
var click = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
featureType: 'type',
featureNS: 'http://localhost/ns',
layers: 'ns:type'
});
var hover = new OpenLayers.Control.WMSGetFeatureInfo('http://localhost/wms', {
var hover = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
featureType: 'type',
featureNS: 'http://localhost/ns',
layers: 'ns:type',
@@ -96,15 +101,17 @@
"hover handler is active after activating control");
}
function test_Control_WMSGetFeatureInfo_deactivate(t) {
function test_deactivate(t) {
t.plan(2);
var map = new OpenLayers.Map("map");
var click = new OpenLayers.Control.WMSGetFeatureInfo("http://localhost/wms", {
var click = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
featureType: 'type',
featureNS: 'http://localhost/ns',
layers: 'ns:type'
});
var hover = new OpenLayers.Control.WMSGetFeatureInfo("http://localhost/wms", {
var hover = new OpenLayers.Control.WMSGetFeatureInfo({
url: 'http://localhost/wms',
featureType: 'type',
featureNS: 'http://localhost/ns',
layers: 'ns:type'
@@ -130,8 +137,8 @@
// Verify that things work all right when we combine different types for the STYLES and LAYERS
// params in the WMS Layers involved
function test_Control_WMSGetFeatureInfo_MixedParams(t) {
t.plan(1);
function test_mixedParams(t) {
t.plan(2);
var map = new OpenLayers.Map("map", {
getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
}
@@ -155,7 +162,7 @@
layers: "a,b,c,d"
});
var click = new OpenLayers.Control.WMSGetFeatureInfo("http://localhost/wms", {
var click = new OpenLayers.Control.WMSGetFeatureInfo({
featureType: 'type',
featureNS: 'ns',
layers: [a, b, c, d]
@@ -163,17 +170,104 @@
map.addControl(click);
var log = {};
var _request = OpenLayers.Request.GET;
OpenLayers.Request.GET = function(options) {
t.eq(
options.params.styles.join(","), "a,b,c,d,a,b,c,d,,,,,,,,",
"Styles merged correctly"
);
log.options = options;
};
click.activate();
click.getInfoForClick({xy: {x: 50, y: 50}});
OpenLayers.Request.GET = _request;
t.eq(
log.options && log.options.url,
"http://localhost/wms",
"url from first layer used"
);
t.eq(
log.options && log.options.params.styles.join(","),
"a,b,c,d,a,b,c,d,,,,,,,,",
"Styles merged correctly"
);
}
function test_urlMatches(t) {
t.plan(5);
var control = new OpenLayers.Control.WMSGetFeatureInfo({
url: "http://host/wms?one=1&two=2"
});
t.ok(!control.urlMatches("foo"), "doesn't match garbage");
t.ok(control.urlMatches("http://host:80/wms?two=2&one=1"), "matches equivalent url");
// give the control more urls to match from
control.layerUrls = ["http://a.host/wms", "http://b.host/wms"];
t.ok(control.urlMatches("http://host:80/wms?two=2&one=1"), "still matches equivalent url");
t.ok(control.urlMatches("http://a.host:80/wms"), "matches equivalent of first of layerUrls");
t.ok(control.urlMatches("http://b.host:80/wms"), "matches equivalent of second of layerUrls");
}
function test_layerUrls(t) {
t.plan(4);
var map = new OpenLayers.Map({
div: "map",
getExtent: function() {
return new OpenLayers.Bounds(-180,-90,180,90);
}
});
var a = new OpenLayers.Layer.WMS(
null, "http://a.mirror/wms", {layers: "a"}
);
var b = new OpenLayers.Layer.WMS(
null, "http://b.mirror/wms", {layers: "b"}
);
var c = new OpenLayers.Layer.WMS(
null, ["http://c.mirror/wms", "http://d.mirror/wms"], {layers: "c"}
);
var control = new OpenLayers.Control.WMSGetFeatureInfo({
url: "http://host/wms",
layers: [a, b, c]
});
map.addControl(control);
control.activate();
// log calls to GET
var log;
var _request = OpenLayers.Request.GET;
OpenLayers.Request.GET = function(options) {
log.options = options;
};
// control url doesn't match layer urls, no request issued
log = {};
control.getInfoForClick({xy: {x: 50, y: 50}});
t.ok(!log.options, "no url match, no request issued");
// give control a list of urls to match
log = {};
control.layerUrls = ["http://a.mirror/wms", "http://b.mirror/wms"];
control.getInfoForClick({xy: {x: 50, y: 50}});
t.eq(log.options && log.options.url, "http://host/wms", "some match, request issued");
t.eq(log.options && log.options.params["query_layers"].join(","), "a,b", "selected layers queried");
// show that a layer can be matched if it has a urls array itself (first needs to be matched)
log = {};
control.layerUrls = ["http://c.mirror/wms"];
control.getInfoForClick({xy: {x: 50, y: 50}});
t.eq(log.options && log.options.params["query_layers"].join(","), "c", "layer with urls array can be queried");
// clean up
OpenLayers.Request.GET = _request;
map.destroy();
}
</script>