From b3d834de7730f20e33461ee7963d73ed83fa5a11 Mon Sep 17 00:00:00 2001 From: Just van den Broecke Date: Mon, 17 Mar 2014 18:51:50 +0100 Subject: [PATCH] issue #230 - enhancing AddLayers GUI for Layer previews --- src/script/plugins/AddLayers.js | 24 +++++++++++++++------ src/script/plugins/LayerSource.js | 36 ++++++++++++++++++++----------- src/script/plugins/TMSSource.js | 27 +++++++++++++++++++---- src/script/plugins/WMSSource.js | 23 +++++++++++++++++--- src/theme/all.css | 7 ++++++ 5 files changed, 91 insertions(+), 26 deletions(-) diff --git a/src/script/plugins/AddLayers.js b/src/script/plugins/AddLayers.js index 36ea85bb..b94b8603 100644 --- a/src/script/plugins/AddLayers.js +++ b/src/script/plugins/AddLayers.js @@ -235,17 +235,17 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { */ layerGridHeight: 300, - /** api: config[layerCardLegendWidth] + /** api: config[layerPreviewWidth] * ``Number`` * Width of the legend image inside the Layer card (``templatedLayerGrid`` must be true). */ - layerCardLegendWidth: 20, + layerPreviewWidth: 20, - /** api: config[layerCardLegendHeight] + /** api: config[layerPreviewHeight] * ``Number`` * Height of the legend image inside the Layer card (``templatedLayerGrid`` must be true). */ - layerCardLegendHeight: 20, + layerPreviewHeight: 20, /** private: property[selectedSource] * :class:`gxp.plugins.LayerSource` @@ -471,7 +471,11 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { // {layer.url.params.LAYERS} var tplLayerURL ='{url}'; var tplLayerName ='{LAYERS}'; - var tplReqURL = tplLayerURL +'REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH='+ this.layerCardLegendWidth +'&HEIGHT='+ this.layerCardLegendHeight +'&LAYER=' + tplLayerName; + var tplReqURL = tplLayerURL +'REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH='+ this.layerPreviewWidth +'&HEIGHT='+ this.layerPreviewHeight +'&LAYER=' + tplLayerName; +// var previewImage = ''; + var previewImage = ''; + var layerPreviewWidth = this.layerPreviewWidth, layerPreviewHeight = this.layerPreviewHeight; + var self = this; var tpl = '
' + '
{title}' + @@ -502,7 +506,7 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { '' + '' + '' + '' + '' + @@ -531,7 +535,6 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { '' + '
' + - '' + + '{previewImage}' + '

Name: {name}
Abstract: {abstract}

' + '
' + - '

' + '
'; tpl = new Ext.XTemplate(tpl); tpl.compile(); @@ -544,6 +547,13 @@ gxp.plugins.AddLayers = Ext.extend(gxp.plugins.Tool, { renderer: function (value, metaData, record, rowIndex, colIndex, view) { var data = record.data; data.id = record.id; + var source = self.target.layerSources[record.store.sourceId]; + var previewImageURL = source.getPreviewImageURL(record, layerPreviewWidth, layerPreviewHeight); + if (previewImageURL) { + data.previewImage = ''; + } else { + data.previewImage = '
 
'; + } data.rowIndex = rowIndex; return tpl.apply(data); }} diff --git a/src/script/plugins/LayerSource.js b/src/script/plugins/LayerSource.js index 9570c3d1..7fe4adf5 100644 --- a/src/script/plugins/LayerSource.js +++ b/src/script/plugins/LayerSource.js @@ -1,6 +1,6 @@ /** * Copyright (c) 2008-2011 The Open Planning Project - * + * * Published under the GPL license. * See https://github.com/opengeo/gxp/raw/master/license.txt for the full text * of the license. @@ -21,9 +21,9 @@ Ext.namespace("gxp.plugins"); * there, the viewer will create layers from it by looking at objects in * the ``layers`` array of its ``map`` config option, calling the source's * ``createLayerRecord`` method. - */ + */ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { - + /** api: property[store] * ``GeoExt.data.LayerStore`` */ @@ -33,7 +33,7 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { * ``Object`` * The object that this plugin is plugged into. */ - + /** api: property[lazy] * ``Boolean``. true when the source is ready, but its store hasn't * been loaded yet (i.e. lazy source). Read-only. @@ -45,19 +45,19 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { * up in the AddLayers dialog. Defaults to false. */ hidden: false, - + /** api: property[title] * ``String`` * A descriptive title for this layer source. */ title: "", - + /** private: method[constructor] */ constructor: function(config) { this.initialConfig = config; Ext.apply(this, config); - + this.addEvents( /** api: event[ready] * Fires when the layer source is ready for action. @@ -70,7 +70,7 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { ); gxp.plugins.LayerSource.superclass.constructor.apply(this, arguments); }, - + /** api: method[init] * :arg target: ``Object`` The object initializing this plugin. * @@ -80,7 +80,7 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { this.target = target; this.createStore(); }, - + /** private: method[getMapProjection] * :returns: ``OpenLayers.Projection`` */ @@ -90,7 +90,7 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { (projConfig && new OpenLayers.Projection(projConfig)) || new OpenLayers.Projection("EPSG:4326"); }, - + /** api: method[getProjection] * :arg layerRecord: ``GeoExt.data.LayerRecord`` a record from this * source's store @@ -114,7 +114,7 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { mapProj; return proj.equals(mapProj) ? mapProj : null; }, - + /** api: method[createStore] * * Creates a store of layer records. Fires "ready" when store is loaded. @@ -151,7 +151,19 @@ gxp.plugins.LayerSource = Ext.extend(Ext.util.Observable, { selected: record.get("selected") }; }, - + + /** api: method[getPreviewImageURL] + * :arg record: :class:`GeoExt.data.LayerRecord` + * :arg width: :Number:image width + * :arg height: :Number:image height + * :returns: ``String`` + * + * Create a preview image URL or encoded image for given record. + */ + getPreviewImageURL: function(record, width, height) { + return null; + }, + /** api: method[getState] * :returns: ``Object`` * diff --git a/src/script/plugins/TMSSource.js b/src/script/plugins/TMSSource.js index 0b1c1ef7..0f907b3e 100644 --- a/src/script/plugins/TMSSource.js +++ b/src/script/plugins/TMSSource.js @@ -1,6 +1,6 @@ /** * Copyright (c) 2008-2011 The Open Planning Project - * + * * Published under the GPL license. * See https://github.com/opengeo/gxp/raw/master/license.txt for the full text * of the license. @@ -80,7 +80,7 @@ gxp.data.TMSCapabilitiesReader = Ext.extend(Ext.data.DataReader, { var layername = url.substring(url.indexOf(this.meta.version + '/') + 6); records.push(new this.recordType({ layer: new OpenLayers.Layer.TMS( - tileMap.title, + tileMap.title, (this.meta.baseUrl.indexOf(this.meta.version) !== -1) ? this.meta.baseUrl.replace(this.meta.version + '/', '') : this.meta.baseUrl, { layername: layername } @@ -93,6 +93,9 @@ gxp.data.TMSCapabilitiesReader = Ext.extend(Ext.data.DataReader, { } } } + if (records.length == 0) { + alert('No suitable TMS layers found, maybe a mismatch with your Map projection?') + } return { totalRecords: records.length, success: true, @@ -169,8 +172,8 @@ gxp.plugins.TMSSource = Ext.extend(gxp.plugins.LayerSource, { method: "GET" }), reader: new gxp.data.TMSCapabilitiesReader({ - baseUrl: this.url, - version: this.version, + baseUrl: this.url, + version: this.version, mapProjection: this.getMapProjection() }) }); @@ -211,6 +214,22 @@ gxp.plugins.TMSSource = Ext.extend(gxp.plugins.LayerSource, { }); } } + }, + + /** api: method[getPreviewImageURL] + * :arg record: :class:`GeoExt.data.LayerRecord` + * :arg width: :Number:image width + * :arg height: :Number:image height + * :returns: ``String`` + * + * Create a preview image URL or encoded image for given record. + */ + getPreviewImageURL: function (record, width, height) { + var layerURL = record.data.tileMapUrl; + var tile = '/0/0/0.png'; + + var url = layerURL + tile; + return url; } }); diff --git a/src/script/plugins/WMSSource.js b/src/script/plugins/WMSSource.js index 0fa7e0f6..8050cedc 100644 --- a/src/script/plugins/WMSSource.js +++ b/src/script/plugins/WMSSource.js @@ -212,6 +212,22 @@ gxp.plugins.WMSSource = Ext.extend(gxp.plugins.LayerSource, { this.target.on("authorizationchange", this.onAuthorizationChange, this); }, + /** api: method[getPreviewImageURL] + * :arg record: :class:`GeoExt.data.LayerRecord` + * :arg width: :Number:image width + * :arg height: :Number:image height + * :returns: ``String`` + * + * Create a preview image URL or encoded image for given record. + */ + getPreviewImageURL: function (record, width, height) { + var layerURL = record.data.layer.url; + var layerName = record.data.name; + + var url = layerURL + 'REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=' + width + '&HEIGHT=' + height + '&LAYER=' + layerName; + return url; + }, + /** private: method[onAuthorizationChange] * Reload the store when the authorization changes. */ @@ -531,15 +547,16 @@ gxp.plugins.WMSSource = Ext.extend(gxp.plugins.LayerSource, { CQL_FILTER: config.cql_filter }); - var singleTile = false; + // JvdB: default singleTile is true makes sense for most WMS Layers + var singleTile = true; if ("tiled" in config) { singleTile = !config.tiled; - } else { + } /* else { // for now, if layer has a time dimension, use single tile if (original.data.dimensions && original.data.dimensions.time) { singleTile = true; } - } + } */ layer.setName(config.title || layer.name); layer.addOptions({ diff --git a/src/theme/all.css b/src/theme/all.css index 15ec5afb..a2b1401a 100644 --- a/src/theme/all.css +++ b/src/theme/all.css @@ -607,6 +607,7 @@ div.olMap div.gx-overlay-playback { } /* START AddLayers CSS */ + .layercardgrid .x-grid3-row { background-color: #eeeeee; border-color:#eeeeee; @@ -693,6 +694,12 @@ div.olMap div.gx-overlay-playback { /*height: 20px;*/ } +.layercard .preview-notavailable { + background-repeat: no-repeat; + /* No image available icon. Created via http://duri.me */ + background-image: url(data:image/jpg;base64,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) +} + .x-btn .gxp-icon-metadata, .x-menu-item .gxp-icon-metadata, .x-tree-node .gxp-icon-metadata { /* tag_purple.png */ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHpSURBVDjLhZNbbxJhEIb3T/RWw78g2fjLvLE2ppe1TYNtvGuNRo6BcA4kIBBOgXCU3QXploCAmNQE/VY55PWbj7CWcPBibuab95l3ZmelZrOJRqOBWq2GarWKSqWCcrmMUqmEYrF4BEA6FFK9XsdyudyKfr8vILlc7iBEos4k6PV6orOu6yaEctwF0un0XohElqmYulGiUCiUptMp5vO5yBMwm80ikUjshEjUdV3IxX+45Z5hGPj29RcykbF463a7SKVSiMfjWxCJOq8tLxYLkPj72MCbEw3nz1WkwytIp9MhF4hEIhsQic/IJpOJKJrNZqKz7aWGm7Mu3l/quDppmxBN08gFAoGACZHy+fwzPiMbj1dFSvVBdL49v8PHq/stiKqq5AJer1dABCWTych8RjYajURRu/EDtmMV7y7+QWzHGj4FV++tVotcwO12H5mzJJNJmc/IhsPhFuSDTcfb0w6uTz/zr7MQLkKhEJxO59ONjfL55FgsxgaDgQm5fKHg+lUbtxdt/Jwaj8UWc4THEY1G5XA4zOgSxeLqD7h5/QW/jbkpdjgcFnOJu44jGAzKfr+f0SWuPzGJeX5DvBdA4fP5rHzTjA5MUZSd4oMACo/HY3W5XIzEdrvdsvOU//e78q5WLn6y7/0viZYv/mL7AwwAAAAASUVORK5CYII=);