/**
* @fileOverview
* Copyright (c) 2013 Regione Autonoma della Sardegna
* Published under the GPL license.
* See https://sardegnageoportale.it/webgis/license.txt
* for the full text of the license.
* @version 0.1
*/
/**
* @requires plugins/WMSSource.js
* @requires OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
*/
/**
* @namespace framework.plugins
*/
Ext.namespace("framework.plugins");
/** @example
* Configuration in the :class:`gxp.Viewer`:
*
* .. code-block:: javascript
*
* defaultSourceType: "framework_gwcsource",
* sources: {
* "opengeo": {
* url: "http://suite.opengeo.org/geoserver/wms"
* }
* }
*
* A typical configuration for a layer from this source (in the ``layers``
* array of the viewer's ``map`` config option would look like this:
*
* .. code-block:: javascript
*
* {
* source: "opengeo",
* name: "world",
* group: "background"
* }
*
*/
/**
* Plugin for using Geowebcache layers with :class:`gxp.Viewer` instances. The
* plugin issues a GetCapabilities request to create a store of the WMS's
* layers. If tilesets are available, it will use them.
* @name_ GWCSource
* @class Plugin for using Geowebcache service
* @constructor
* @extends gxp.plugins.WMSSource
*/
framework.plugins.GWCSource = Ext.extend(gxp.plugins.WMSSource,
/**
* @lends framework.plugins.GWCSource.prototype
*/
{
/**
* framework_gwcsource plugin type.
* @public
* @type String
*/
ptype: "framework_gwcsource",
/** api: config[version]
* Only WMS 1.1.1 is supported at the moment.
* @public
* @type String
*/
version: "1.1.1",
/**
* Only WFS 1.1.0 is supported at the moment.
* @public
* @type String
*/
url_wfs: "/geoserver/ows",
/**
* @constructor
* @private
* @param {Array(String)} List of config properties that are required for each
* layer from this source to allow lazy loading. Default is
* ``["title", "bbox"]``. When the source loads layers from a WMS-C that
* does not use subsets of the default Web Mercator grid, not provide
* tiles for all default Web Mercator resolutions, and not use a tileSize
* of 256x256 pixels, ``tileOrigin``, ``resolutions`` and ``tileSize``
* should be included in this list.
*/
constructor: function(config) {
config.baseParams = {
SERVICE: "WMS",
REQUEST: "GetCapabilities",
TILED: true
};
if (!config.format) {
this.format = new OpenLayers.Format.WMSCapabilities({
keepData: true,
profile: "WMSC",
allowFallback: true
});
}
framework.plugins.GWCSource.superclass.constructor.apply(this, arguments);
},
/**
* Creates a store of layer records. Fires "ready" when store is loaded.
* @public
*/
createStore: function() {
var baseParams = this.baseParams || {
SERVICE: "WMS",
REQUEST: "GetCapabilities"
};
if (this.version) {
baseParams.VERSION = this.version;
}
var lazy = this.isLazy();
this.store = new GeoExt.data.WMSCapabilitiesStore({
// Since we want our parameters (e.g. VERSION) to override any in the
// given URL, we need to remove corresponding paramters from the
// provided URL. Simply setting baseParams on the store is also not
// enough because Ext just tacks these parameters on to the URL - so
// we get requests like ?Request=GetCapabilities&REQUEST=GetCapabilities
// (assuming the user provides a URL with a Request parameter in it).
url: this.trimUrl(this.url, baseParams),
baseParams: baseParams,
format: this.format,
autoLoad: !lazy,
layerParams: {exceptions: null},
listeners: {
load: function() {
// The load event is fired even if a bogus capabilities doc
// is read (http://trac.geoext.org/ticket/295).
// Until this changes, we duck type a bad capabilities
// object and fire failure if found.
if (!this.store.reader.raw || !this.store.reader.raw.service) {
this.fireEvent("failure", this, "Invalid capabilities document.");
} else {
if (!this.title) {
this.title = this.store.reader.raw.service.title;
}
if (!this.ready) {
this.ready = true;
this.fireEvent("ready", this);
} else {
this.lazy = false;
//TODO Here we could update all records from this
// source on the map that were added when the
// source was lazy.
}
}
// clean up data stored on format after parsing is complete
delete this.format.data;
},
exception: function(proxy, type, action, options, response, error) {
delete this.store;
var msg, details = "";
if (type === "response") {
if (typeof error == "string") {
msg = error;
} else {
msg = "Invalid response from server.";
// special error handling in IE
var data = this.format && this.format.data;
if (data && data.parseError) {
msg += " " + data.parseError.reason + " - line: " + data.parseError.line;
}
var status = response.status;
if (status >= 200 && status < 300) {
// TODO: consider pushing this into GeoExt
var report = error && error.arg && error.arg.exceptionReport;
details = gxp.util.getOGCExceptionText(report);
} else {
details = "Status: " + status;
}
}
} else {
msg = "Trouble creating layer store from response.";
details = "Unable to handle response.";
}
// TODO: decide on signature for failure listeners
this.fireEvent("failure", this, msg, details);
// clean up data stored on format after parsing is complete
delete this.format.data;
},
scope: this
}
});
if (lazy) {
this.lazy = true;
// ping server of lazy source with an incomplete request, to see if
// it is available
Ext.Ajax.request({
method: "GET",
url: this.url,
params: {SERVICE: "WMS"},
callback: function(options, success, response) {
var status = response.status;
// responseText should not be empty (OGCException)
if (status >= 200 && status < 403 && response.responseText) {
this.ready = true;
this.fireEvent("ready", this);
} else {
this.fireEvent("failure", this,
"Layer source not available.",
"Unable to contact WMS service."
);
}
},
scope: this
});
}
},
/**
* createLayerRecord
* @private
* @param {object} config configuration object
*/
createLayerRecord: function(config) {
var record = framework.plugins.GWCSource.superclass.createLayerRecord.apply(this, arguments);
if (!record) {
return;
}
var caps, srs;
if (this.store.reader.raw) {
caps = this.store.reader.raw.capability;
}
var tileSets = (caps && caps.vendorSpecific) ?
caps.vendorSpecific.tileSets : (config.capability && config.capability.tileSets);
var layer = record.get("layer");
if (tileSets) {
var mapProjection = this.getProjection(record) || this.getMapProjection();
// look for tileset with same name and equivalent projection
for (var i=0, len=tileSets.length; i=0; --i) {
tileSetsCap = tileSets[i];
if (tileSetsCap.layers === name && tileSetsCap.srs[layer.projection]) {
config.capability.tileSets = [tileSetsCap];
break;
}
}
}
}
if (!(config.capability && config.capability.tileSets)) {
var tileSize = layer.options.tileSize;
if (tileSize) {
config.tileSize = [tileSize.w, tileSize.h];
}
config.tileOrigin = layer.options.tileOrigin;
config.resolutions = layer.options.resolutions;
}
return Ext.applyIf(config, {
// the "tiled" property is already used to indicate singleTile
// the "cached" property will indicate whether to send the TILED param
cached: !!layer.params.TILED
});
},
/**
* Helper function to fetch the schema for a layer of this source
* @private
* @param {String} url. The url to the WFS endpoint
* @param {String} typeName. The typeName to use
* @param {function} callback Callback function. Will be called with
* a ``GeoExt.data.AttributeStore`` containing the schema as first
* argument, or false if the WMS does not support DescribeLayer or the
* layer is not associated with a WFS feature type.
* @param {scope} Object. Optional scope for the callback
*/
fetchSchema: function(url, typeName, callback, scope) {
var schema = this.schemaCache[typeName];
if (schema) {
if (schema.getCount() == 0) {
schema.on("load", function() {
callback.call(scope, schema);
}, this, {single: true});
} else {
callback.call(scope, schema);
}
} else {
schema = new GeoExt.data.AttributeStore({
url: url,
baseParams: {
SERVICE: "WFS",
//TODO should get version from WFS GetCapabilities
VERSION: "1.1.0",
REQUEST: "DescribeFeatureType",
TYPENAME: typeName
},
autoLoad: true,
listeners: {
"load": function() {
callback.call(scope, schema);
},
scope: this
}
});
this.schemaCache[typeName] = schema;
}
},
/**
* Gets the schema for a layer of this source, if the layer is a feature
* layer.
* @public
* @param {GeoExt.data.LayerRecord} rec the WMS layer to issue a WFS DescribeFeatureType request for
* @param {Function} callback Will be called with
* a ``GeoExt.data.AttributeStore`` containing the schema as first
* argument, or false if the WMS does not support DescribeLayer or the
* layer is not associated with a WFS feature type.
* @param {scope} scope Optional scope for the callback
*/
getSchema: function(rec, callback, scope) {
if (!this.schemaCache) {
this.schemaCache = {};
}
this.describeLayer(rec, function(r) {
if (r && r.get("owsType") == "WFS") {
var typeName = r.get("typeName");
var url = r.get("owsURL");
this.fetchSchema(url, typeName, callback, scope);
} else if (!r) {
// When DescribeLayer is not supported, we make the following
// assumptions:
// 1. URL of the WFS is the same as the URL of the WMS
// 2. typeName is the same as the WMS Layer name
//this.fetchSchema(this.url, rec.get('name'), callback, scope);
this.fetchSchema(this.url_wfs, rec.get('name'), callback, scope);
} else {
callback.call(scope, false);
}
}, this);
},
/** Creates a WFS protocol for the given WMS layer record.
* @public
* @param {GeoExt.data.LayerRecord} record
* @param {function} callback
* @param {type} scope
* @returns {OpenLayers.Protocol.WFS}
*/
getWFSProtocol: function(record, callback, scope) {
this.getSchema(record, function(schema) {
var protocol = false;
if (schema) {
var geometryName;
var geomRegex = /gml:((Multi)?(Point|Line|Polygon|Curve|Surface|Geometry)).*/;
schema.each(function(r) {
var match = geomRegex.exec(r.get("type"));
if (match) {
geometryName = r.get("name");
}
}, this);
protocol = new OpenLayers.Protocol.WFS({
version: "1.1.0",
srsName: record.getLayer().projection.getCode(),
url: schema.url,
featureType: schema.reader.raw.featureTypes[0].typeName,
featureNS: schema.reader.raw.targetNamespace,
geometryName: geometryName
});
}
callback.call(scope, protocol, schema, record);
}, this);
}
});
Ext.preg(framework.plugins.GWCSource.prototype.ptype, framework.plugins.GWCSource);