diff --git a/examples/wmts-getfeatureinfo.html b/examples/wmts-getfeatureinfo.html new file mode 100644 index 0000000000..2e503229ec --- /dev/null +++ b/examples/wmts-getfeatureinfo.html @@ -0,0 +1,69 @@ + + + + OpenLayers WMTS GetFeatureInfo Example + + + + + + + + +

WMTS GetFeatureInfo Control

+ +

+ The WMTSGetFeatureInfo control allows retrieval of information about + features displayed in a WMTS layer. +

+ +
+ + +
+

+ This example uses an OpenLayers.Control.WMTSGetFeatureInfo + control layer to access information from WMTS layers. The + control is activated and configured to request feature + information when you click on the map. If the control's + drillDown property is set to true, multiple layers can be + queried. +

+ See the + wmts-getfeatureinfo.js source to see how this is done. +

+
+ + diff --git a/examples/wmts-getfeatureinfo.js b/examples/wmts-getfeatureinfo.js new file mode 100644 index 0000000000..6882c44ec8 --- /dev/null +++ b/examples/wmts-getfeatureinfo.js @@ -0,0 +1,99 @@ +OpenLayers.ProxyHost = "/proxy/?url="; +var map, control, popups = {}; + +function init() { + + map = new OpenLayers.Map({ + div: "map", + projection: "EPSG:900913", + units: "m", + maxExtent: new OpenLayers.Bounds( + -20037508.34, -20037508.34, 20037508.34, 20037508.34 + ), + maxResolution: 156543.0339 + }); + + var osm = new OpenLayers.Layer.OSM(); + + // If tile matrix identifiers differ from zoom levels (0, 1, 2, ...) + // then they must be explicitly provided. + var matrixIds = new Array(26); + for (var i=0; i<26; ++i) { + matrixIds[i] = "EPSG:900913:" + i; + } + + var zoning = new OpenLayers.Layer.WMTS({ + name: "zoning", + url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/", + layer: "medford:zoning", + matrixSet: "EPSG:900913", + matrixIds: matrixIds, + format: "image/png", + style: "_null", + opacity: 0.7, + isBaseLayer: false + }); + var buildings = new OpenLayers.Layer.WMTS({ + name: "building", + url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/", + layer: "medford:buildings", + matrixSet: "EPSG:900913", + matrixIds: matrixIds, + format: "image/png", + style: "_null", + isBaseLayer: false + }); + + map.addLayers([osm, zoning, buildings]); + + // create WMTS GetFeatureInfo control + control = new OpenLayers.Control.WMTSGetFeatureInfo({ + drillDown: true, + queryVisible: true, + eventListeners: { + getfeatureinfo: function(evt) { + var text; + var match = evt.text.match(/]*>([\s\S]*)<\/body>/); + if (match && !match[1].match(/^\s*$/)) { + text = match[1]; + } else { + text = "No " + evt.layer.name + " features in that area.
"; + } + var popupId = evt.xy.x + "," + evt.xy.y; + var popup = popups[popupId]; + if (!popup || !popup.map) { + popup = new OpenLayers.Popup.FramedCloud( + popupId, + map.getLonLatFromPixel(evt.xy), + null, + " ", + null, + true, + function(evt) { + delete popups[this.id]; + this.hide(); + OpenLayers.Event.stop(evt); + } + ); + popups[popupId] = popup; + map.addPopup(popup, true); + } + popup.setContentHTML(popup.contentHTML + text); + popup.show(); + } + } + }); + map.addControl(control); + control.activate(); + + map.addControl(new OpenLayers.Control.LayerSwitcher()); + map.setCenter(new OpenLayers.LonLat(-13678519, 5212803), 15); + + var drill = document.getElementById("drill"); + drill.checked = true; + drill.onchange = function() { + control.drillDown = drill.checked; + }; +} + +OpenLayers.Popup.FramedCloud.prototype.maxSize = new OpenLayers.Size(350, 200); diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 463f1f6816..9bb1f8b163 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -180,6 +180,7 @@ "OpenLayers/Control/NavigationHistory.js", "OpenLayers/Control/Measure.js", "OpenLayers/Control/WMSGetFeatureInfo.js", + "OpenLayers/Control/WMTSGetFeatureInfo.js", "OpenLayers/Control/Graticule.js", "OpenLayers/Control/TransformFeature.js", "OpenLayers/Control/SLDSelect.js", diff --git a/lib/OpenLayers/Control/WMTSGetFeatureInfo.js b/lib/OpenLayers/Control/WMTSGetFeatureInfo.js new file mode 100644 index 0000000000..37cc527e54 --- /dev/null +++ b/lib/OpenLayers/Control/WMTSGetFeatureInfo.js @@ -0,0 +1,441 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Click.js + * @requires OpenLayers/Handler/Hover.js + * @requires OpenLayers/Request.js + * @requires OpenLayers/Format/WMSGetFeatureInfo.js + */ + +/** + * Class: OpenLayers.Control.WMTSGetFeatureInfo + * The WMTSGetFeatureInfo control uses a WMTS query to get information about a + * point on the map. The information may be in a display-friendly format + * such as HTML, or a machine-friendly format such as GML, depending on the + * server's capabilities and the client's configuration. This control + * handles click or hover events, attempts to parse the results using an + * OpenLayers.Format, and fires a 'getfeatureinfo' event for each layer + * queried. + * + * Inherits from: + * - + */ +OpenLayers.Control.WMTSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: hover + * {Boolean} Send GetFeatureInfo requests when mouse stops moving. + * Default is false. + */ + hover: false, + + /** + * Property: requestEncoding + * {String} One of "KVP" or "REST". Only KVP encoding is supported at this + * time. + */ + requestEncoding: "KVP", + + /** + * APIProperty: drillDown + * {Boolean} Drill down over all WMTS layers in the map. When + * using drillDown mode, hover is not possible. A getfeatureinfo event + * will be fired for each layer queried. + */ + drillDown: false, + + /** + * APIProperty: maxFeatures + * {Integer} Maximum number of features to return from a WMTS query. This + * sets the feature_count parameter on WMTS GetFeatureInfo + * requests. + */ + maxFeatures: 10, + + /** APIProperty: clickCallback + * {String} The click callback to register in the + * {} object created when the hover + * option is set to false. Default is "click". + */ + clickCallback: "click", + + /** + * Property: layers + * {Array()} The layers to query for feature info. + * If omitted, all map WMTS layers will be considered. + */ + layers: null, + + /** + * APIProperty: queryVisible + * {Boolean} Filter out hidden layers when searching the map for layers to + * query. Default is true. + */ + queryVisible: true, + + /** + * Property: infoFormat + * {String} The mimetype to request from the server + */ + infoFormat: 'text/html', + + /** + * Property: vendorParams + * {Object} Additional parameters that will be added to the request, for + * WMTS implementations that support them. This could e.g. look like + * (start code) + * { + * radius: 5 + * } + * (end) + */ + vendorParams: {}, + + /** + * Property: format + * {} A format for parsing GetFeatureInfo responses. + * Default is . + */ + format: null, + + /** + * Property: formatOptions + * {Object} Optional properties to set on the format (if one is not provided + * in the property. + */ + formatOptions: null, + + /** + * APIProperty: handlerOptions + * {Object} Additional options for the handlers used by this control, e.g. + * (start code) + * { + * "click": {delay: 100}, + * "hover": {delay: 300} + * } + * (end) + */ + handlerOptions: null, + + /** + * Property: handler + * {Object} Reference to the for this control + */ + handler: null, + + /** + * Property: hoverRequest + * {} contains the currently running hover request + * (if any). + */ + hoverRequest: null, + + /** + * Constant: EVENT_TYPES + * + * Supported event types (in addition to those from ): + * beforegetfeatureinfo - Triggered before each request is sent. + * The event object has an *xy* property with the position of the + * mouse click or hover event that triggers the request and a *layer* + * property referencing the layer about to be queried. If a listener + * returns false, the request will not be issued. + * 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, a *layer* property + * referencing the layer queried and a *request* property with the + * request itself. If drillDown is set to true, one event will be fired + * for each layer queried. + * exception - Triggered when a GetFeatureInfo request fails (with a + * status other than 200) or whenparsing fails. Listeners will receive + * an event with *request*, *xy*, and *layer* properties. In the case + * of a parsing error, the event will also contain an *error* property. + */ + EVENT_TYPES: ["beforegetfeatureinfo", "getfeatureinfo", "exception"], + + /** + * Property: pending + * {Number} The number of pending requests. + */ + pending: 0, + + /** + * Constructor: + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + // concatenate events specific to vector with those from the base + this.EVENT_TYPES = + OpenLayers.Control.WMTSGetFeatureInfo.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + + options = options || {}; + options.handlerOptions = options.handlerOptions || {}; + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + if (!this.format) { + this.format = new OpenLayers.Format.WMSGetFeatureInfo( + options.formatOptions + ); + } + + if (this.drillDown === true) { + this.hover = false; + } + + if (this.hover) { + this.handler = new OpenLayers.Handler.Hover( + this, { + move: this.cancelHover, + pause: this.getInfoForHover + }, + OpenLayers.Util.extend( + this.handlerOptions.hover || {}, {delay: 250} + ) + ); + } else { + var callbacks = {}; + callbacks[this.clickCallback] = this.getInfoForClick; + this.handler = new OpenLayers.Handler.Click( + this, callbacks, this.handlerOptions.click || {} + ); + } + }, + + /** + * Method: activate + * Activates the control. + * + * Returns: + * {Boolean} The control was effectively activated. + */ + activate: function () { + if (!this.active) { + this.handler.activate(); + } + return OpenLayers.Control.prototype.activate.apply( + this, arguments + ); + }, + + /** + * Method: deactivate + * Deactivates the control. + * + * Returns: + * {Boolean} The control was effectively deactivated. + */ + deactivate: function () { + return OpenLayers.Control.prototype.deactivate.apply( + this, arguments + ); + }, + + /** + * Method: getInfoForClick + * Called on click + * + * Parameters: + * evt - {} + */ + getInfoForClick: function(evt) { + this.request(evt.xy, {}); + }, + + /** + * Method: getInfoForHover + * Pause callback for the hover handler + * + * Parameters: + * evt - {Object} + */ + getInfoForHover: function(evt) { + this.request(evt.xy, {hover: true}); + }, + + /** + * Method: cancelHover + * Cancel callback for the hover handler + */ + cancelHover: function() { + if (this.hoverRequest) { + --this.pending; + if (this.pending <= 0) { + OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait"); + this.pending = 0; + } + this.hoverRequest.abort(); + this.hoverRequest = null; + } + }, + + /** + * Method: findLayers + * Internal method to get the layers, independent of whether we are + * inspecting the map or using a client-provided array + */ + findLayers: function() { + var candidates = this.layers || this.map.layers; + var layers = []; + var layer; + for (var i=candidates.length-1; i>=0; --i) { + layer = candidates[i]; + if (layer instanceof OpenLayers.Layer.WMTS && + layer.requestEncoding === this.requestEncoding && + (!this.queryVisible || layer.getVisibility())) { + layers.push(layer); + if (!this.drillDown || this.hover) { + break; + } + } + } + return layers; + }, + + /** + * Method: buildRequestOptions + * Build an object with the relevant options for the GetFeatureInfo request. + * + * Parameters: + * layer - {} A WMTS layer. + * xy - {} The position on the map where the + * mouse event occurred. + */ + buildRequestOptions: function(layer, xy) { + var loc = this.map.getLonLatFromPixel(xy); + var getTileUrl = layer.getURL( + new OpenLayers.Bounds(loc.lon, loc.lat, loc.lon, loc.lat) + ); + var params = OpenLayers.Util.getParameters(getTileUrl); + var tileInfo = layer.getTileInfo(loc); + OpenLayers.Util.extend(params, { + service: "WMTS", + version: layer.version, + request: "GetFeatureInfo", + infoFormat: this.infoFormat, + i: tileInfo.i, + j: tileInfo.j + }); + OpenLayers.Util.applyDefaults(params, this.vendorParams); + return { + url: layer.url instanceof Array ? layer.url[0] : layer.url, + params: OpenLayers.Util.upperCaseObject(params), + callback: function(request) { + this.handleResponse(xy, request, layer); + }, + scope: this + }; + }, + + /** + * Method: request + * Sends a GetFeatureInfo request to the WMTS + * + * Parameters: + * xy - {} The position on the map where the mouse event + * occurred. + * options - {Object} additional options for this method. + * + * Valid options: + * - *hover* {Boolean} true if we do the request for the hover handler + */ + request: function(xy, options) { + options = options || {}; + var layers = this.findLayers(); + if (layers.length > 0) { + var issue, layer; + for (var i=0, len=layers.length; i 0) { + OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait"); + } + } + }, + + /** + * Method: handleResponse + * Handler for the GetFeatureInfo response. + * + * Parameters: + * xy - {} The position on the map where the mouse event + * occurred. + * request - {XMLHttpRequest} The request object. + * layer - {} The queried layer. + */ + handleResponse: function(xy, request, layer) { + --this.pending; + if (this.pending <= 0) { + OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait"); + this.pending = 0; + } + if (request.status && (request.status < 200 || request.status >= 300)) { + this.events.triggerEvent("exception", { + xy: xy, + request: request, + layer: layer + }); + } else { + var doc = request.responseXML; + if (!doc || !doc.documentElement) { + doc = request.responseText; + } + var features, except; + try { + features = this.format.read(doc); + } catch (error) { + except = true; + this.events.triggerEvent("exception", { + xy: xy, + request: request, + error: error, + layer: layer + }); + } + if (!except) { + this.events.triggerEvent("getfeatureinfo", { + text: request.responseText, + features: features, + request: request, + xy: xy, + layer: layer + }); + } + } + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.handler.setMap(map); + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Control.WMTSGetFeatureInfo" +}); diff --git a/tests/Control/WMTSGetFeatureInfo.html b/tests/Control/WMTSGetFeatureInfo.html new file mode 100644 index 0000000000..14597d53ff --- /dev/null +++ b/tests/Control/WMTSGetFeatureInfo.html @@ -0,0 +1,334 @@ + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index c52242d788..364170293f 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -37,6 +37,7 @@
  • Control/Split.html
  • Control/TransformFeature.html
  • Control/WMSGetFeatureInfo.html
  • +
  • Control/WMTSGetFeatureInfo.html
  • Control/PanPanel.html
  • Control/SLDSelect.html
  • Events.html