First version of the WMS viewer. The viewer is based on OpenLayers, which is used to parse the GetCapabilities response and build a map with the layers found.

This commit is contained in:
Adrià Mercader 2011-03-22 17:33:58 +00:00
parent 16641741ff
commit 32a180f39c
26 changed files with 1582 additions and 3 deletions

View File

@ -18,7 +18,7 @@ from ckan.plugins import implements, SingletonPlugin
from ckan.plugins import IRoutes, IConfigurer
from ckan.plugins import IConfigurable, IGenshiStreamFilter
#import html
import html
log = getLogger(__name__)
@ -34,6 +34,21 @@ class Harvest(SingletonPlugin):
#self.enable_organizations = config.get('qa.organizations', False)
def filter(self, stream):
from pylons import request, tmpl_context as c
routes = request.environ.get('pylons.routes_dict')
if routes.get('controller') == 'package' and \
routes.get('action') == 'read' and c.pkg.id:
is_inspire = (c.pkg.extras.get('INSPIRE') == 'True')
# TODO: What about WFS, WCS...
is_wms = (c.pkg.extras.get('resource-type') == 'service')
if is_inspire and is_wms:
data = {'name': c.pkg.name}
stream = stream | Transformer('body//div[@class="resources subsection"]')\
.append(HTML(html.MAP_VIEW % data))
return stream
def before_map(self, map):
@ -63,10 +78,18 @@ class Harvest(SingletonPlugin):
controller='ckanext.harvest.controllers.view:ViewController',
action='delete')
map.connect('harvest_create', '/harvest/:id/refresh',
map.connect('harvesting_job_create', '/harvest/:id/refresh',
controller='ckanext.harvest.controllers.view:ViewController',
action='create_harvesting_job')
map.connect('map_view', '/package/:id/map',
controller='ckanext.harvest.controllers.view:ViewController',
action='map_view')
map.connect('proxy', '/proxy',
controller='ckanext.harvest.controllers.view:ViewController',
action='proxy')
map.connect('api_spatial_query', '/api/2/search/package/geo',
controller='ckanext.harvest.controllers.api:ApiController',
action='spatial_query')

View File

@ -3,7 +3,9 @@ import urllib2
import ckan.lib.helpers as h
from ckan.lib.base import BaseController, c, g, request, \
response, session, render, config, abort, redirect
#from ..dictization import *
from ckan.model import Package
class ViewController(BaseController):
@ -146,3 +148,27 @@ class ViewController(BaseController):
h.flash_error(msg)
finally:
redirect(h.url_for(controller='harvest', action='index', id=None))
def map_view(self,id):
#check if package exists
pkg = Package.get(id)
if pkg is None:
abort(404, 'Package not found')
c.url = pkg.url
return render('ckanext/harvest/map.html')
def proxy(self):
if not 'url' in request.params:
abort(400)
try:
server_response = urllib2.urlopen(request.params['url'])
headers = server_response.info()
if headers.get('Content-Type'):
response.content_type = headers.get('Content-Type')
return server_response.read()
except urllib2.HTTPError as e:
response.status_int = e.getcode()
return

5
ckanext/harvest/html.py Normal file
View File

@ -0,0 +1,5 @@
MAP_VIEW="""
<div class="mapview">
<a href="/package/%(name)s/map">View in map</a>
</div>
"""

View File

@ -0,0 +1,158 @@
var CKAN = CKAN || {};
CKAN.MapViewer = function($){
// Private
var defaultVersion = "1.3.0";
var preferredFormat = "image/png";
var proxy = "/proxy?url=";
var getURL = function(server,version){
if (server.indexOf("?") === -1)
server += "?"
var url = server +
"SERVICE=WMS" +
"&REQUEST=GetCapabilities" +
"&VERSION=" + defaultVersion
return (proxy) ? proxy + escape(url) : url;
}
var getFormat = function(formats){
for(var i = 0; i < formats.length; i++){
if (formats[i] == preferredFormat){
return formats[i];
}
}
return formats[0];
}
// Public
return {
map: null,
setup: function(server){
var url = getURL(server);
var self = this;
$.get(url,function(data){
// Most WMS Servers will return the version they prefer,
// regardless of which one you requested, so better check
// for the actual version returned.
var version = $(data).find("WMS_Capabilities").attr("version"); // 1.3.0
if (!version)
version = $(data).find("WMS_MS_Capabilities").attr("version"); // 1.1.1
var format = new OpenLayers.Format.WMSCapabilities({"version":version});
var capabilities = format.read(data);
if (capabilities.capability){
var layers = capabilities.capability.layers;
var olLayers = [];
var maxExtent = false;
var maxScale = false;
var minScale = false;
for (var count = 0; count < layers.length; count++){
layer = layers[count];
// Extend the maps's maxExtent to include this layer extent
layerMaxExtent = new OpenLayers.Bounds(layer.llbbox[0],layer.llbbox[1],layer.llbbox[2],layer.llbbox[3]);
if (!maxExtent){
maxExtent = layerMaxExtent;
} else {
maxExtent.extend(layerMaxExtent);
}
if (layer.maxScale && (layer.maxScale > maxScale || maxScale === false)) maxScale = layer.maxScale;
if (layer.minScale && (layer.minScale < minScale || minScale === false)) minScale = layer.minScale;
olLayers.push(new OpenLayers.Layer.WMS(
layer.title,
server,
{"layers": layer.name,
"format": getFormat(layer.formats),
"transparent":true
},
{"buffer":0,
"maxExtent": layerMaxExtent,
"maxScale": (layer.maxScale) ? layer.maxScale : null,
"minScale": (layer.minScale) ? layer.minScale : null,
"isBaseLayer": false,
"visibility": (count == 0)
}) //Tiled?
);
}
var dummyLayer = new OpenLayers.Layer("Dummy",{
"maxExtent": maxExtent,
"displayInLayerSwitcher":false,
"isBaseLayer":true,
"visibility":false,
"minScale": (minScale) ? minScale : null,
"maxScale": (maxScale) ? maxScale : null
});
olLayers.push(dummyLayer);
// Setup some sizes
var w = $("#container").width() * 0.50;
if (w > 1024) w = 1024;
$("#content").width($("#container").width());
$("#map").width(w);
$("#map").height(500);
// Create a new map
self.map = new OpenLayers.Map("map" ,
{
"projection": new OpenLayers.Projection("EPSG:4326"),
"maxResolution":"auto",
"controls":[
new OpenLayers.Control.PanZoomBar(),
new OpenLayers.Control.Navigation(),
new OpenLayers.Control.CustomMousePosition({
"displayClass":"olControlMousePosition",
"numDigits":4
}),
new OpenLayers.Control.LayerSwitcher({
"div": document.getElementById("layers"),
"roundedCorner":false
})
],
"theme":"/ckanext/harvest/js/openlayers/theme/default/style.css"
});
self.map.maxExtent = maxExtent;
self.map.addLayers(olLayers);
self.map.zoomTo(1);
} else {
$("#main").prepend(
$("<div></div>").attr("class","flash-banner-box").append(
$("<div></div>").attr("class","flash-banner error").html(
"Error parsing the WMS capabilities document"
)
)
);
}
})
}
}
}(jQuery)
OpenLayers.ImgPath = "/ckanext/harvest/js/openlayers/img/";
OpenLayers.Lang.en.overlays = "Layers";
OpenLayers.Control.CustomMousePosition = OpenLayers.Class(OpenLayers.Control.MousePosition,{
formatOutput: function(lonLat) {
var newHtml = OpenLayers.Control.MousePosition.prototype.formatOutput.apply(this, [lonLat]);
newHtml = "1:" + parseInt(this.map.getScale()) + " | WGS84 " + newHtml;
return newHtml;
},
CLASS_NAME: "OpenLayers.Control.CustomMousePosition"
})

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
This is a custom build of the OpenLayers Javascript mapping library,
slimmed down to only the features we need.
The file ckan.cfg contains the build profile used to build OpenLayers.
In order to add more functionality, new classes must be added in the
build profile, and then run the build command from the OpenLayers
distribution:
1. svn co http://svn.openlayers.org/trunk/openlayers
2. Modify ckan.cfg
3. Go to build/ and execute::
python build.py {path-to-ckan.cfg} {output-file}

View File

@ -0,0 +1,40 @@
[first]
OpenLayers/SingleFile.js
OpenLayers.js
OpenLayers/BaseTypes.js
OpenLayers/BaseTypes/Class.js
OpenLayers/Util.js
Rico/Corner.js
[last]
[include]
OpenLayers/Console.js
OpenLayers/Ajax.js
OpenLayers/Events.js
OpenLayers/Map.js
OpenLayers/Layer.js
OpenLayers/Layer/Grid.js
OpenLayers/Layer/HTTPRequest.js
OpenLayers/Layer/WMS.js
OpenLayers/Layer/WMS/Untiled.js
OpenLayers/Tile.js
OpenLayers/Tile/Image.js
OpenLayers/Control/Navigation.js
OpenLayers/Control/PanZoom.js
OpenLayers/Control/PanZoomBar.js
OpenLayers/Control/Scale.js
OpenLayers/Control/MousePosition.js
OpenLayers/Control/LayerSwitcher.js
OpenLayers/Format/XML.js
OpenLayers/Format/WMSCapabilities/v1_1_1.js
OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
OpenLayers/Format/WMSCapabilities/v1_3_0.js
[exclude]
Firebug/firebug.js
Firebug/firebugx.js
OpenLayers/Lang/de.js
OpenLayers/Lang/en-CA.js
OpenLayers/Lang/fr.js
OpenLayers/Lang/cs-CZ.js

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

View File

@ -0,0 +1,17 @@
.olLayerGoogleCopyright {
right: 3px;
bottom: 2px;
left: auto;
}
.olLayerGoogleV3.olLayerGoogleCopyright {
bottom: 0px;
right: 0px !important;
}
.olLayerGooglePoweredBy {
left: 2px;
bottom: 2px;
}
.olLayerGoogleV3.olLayerGooglePoweredBy {
bottom: 0px !important;
}

View File

@ -0,0 +1,7 @@
.olControlZoomPanel div {
background-image: url(img/zoom-panel-NOALPHA.png);
}
.olControlPanPanel div {
background-image: url(img/pan-panel-NOALPHA.png);
}

View File

@ -0,0 +1,423 @@
div.olMap {
z-index: 0;
padding: 0px!important;
margin: 0px!important;
cursor: default;
}
div.olMapViewport {
text-align: left;
}
div.olLayerDiv {
-moz-user-select: none;
-khtml-user-select: none;
}
.olLayerGoogleCopyright {
left: 2px;
bottom: 2px;
}
.olLayerGoogleV3.olLayerGoogleCopyright {
right: auto !important;
}
.olLayerGooglePoweredBy {
left: 2px;
bottom: 15px;
}
.olLayerGoogleV3.olLayerGooglePoweredBy {
bottom: 15px !important;
}
.olControlAttribution {
font-size: smaller;
right: 3px;
bottom: 4.5em;
position: absolute;
display: block;
}
.olControlScale {
right: 3px;
bottom: 3em;
display: block;
position: absolute;
font-size: smaller;
}
.olControlScaleLine {
display: block;
position: absolute;
left: 10px;
bottom: 15px;
font-size: xx-small;
}
.olControlScaleLineBottom {
border: solid 2px black;
border-bottom: none;
margin-top:-2px;
text-align: center;
}
.olControlScaleLineTop {
border: solid 2px black;
border-top: none;
text-align: center;
}
.olControlPermalink {
right: 3px;
bottom: 1.5em;
display: block;
position: absolute;
font-size: smaller;
}
div.olControlMousePosition {
bottom: 0em;
right: 3px;
display: block;
position: absolute;
font-family: Arial;
font-size: smaller;
}
.olControlOverviewMapContainer {
position: absolute;
bottom: 0px;
right: 0px;
}
.olControlOverviewMapElement {
padding: 10px 18px 10px 10px;
background-color: #00008B;
-moz-border-radius: 1em 0 0 0;
}
.olControlOverviewMapMinimizeButton {
right: 0px;
bottom: 80px;
cursor: pointer;
}
.olControlOverviewMapMaximizeButton {
right: 0px;
bottom: 80px;
cursor: pointer;
}
.olControlOverviewMapExtentRectangle {
overflow: hidden;
background-image: url("img/blank.gif");
cursor: move;
border: 2px dotted red;
}
.olControlOverviewMapRectReplacement {
overflow: hidden;
cursor: move;
background-image: url("img/overview_replacement.gif");
background-repeat: no-repeat;
background-position: center;
}
.olLayerGeoRSSDescription {
float:left;
width:100%;
overflow:auto;
font-size:1.0em;
}
.olLayerGeoRSSClose {
float:right;
color:gray;
font-size:1.2em;
margin-right:6px;
font-family:sans-serif;
}
.olLayerGeoRSSTitle {
float:left;font-size:1.2em;
}
.olPopupContent {
padding:5px;
overflow: auto;
}
.olControlNavToolbar {
width:0px;
height:0px;
}
.olControlNavToolbar div {
display:block;
width: 28px;
height: 28px;
top: 300px;
left: 6px;
position: relative;
}
.olControlNavigationHistory {
background-image: url("img/navigation_history.png");
background-repeat: no-repeat;
width: 24px;
height: 24px;
}
.olControlNavigationHistoryPreviousItemActive {
background-position: 0px 0px;
}
.olControlNavigationHistoryPreviousItemInactive {
background-position: 0px -24px;
}
.olControlNavigationHistoryNextItemActive {
background-position: -24px 0px;
}
.olControlNavigationHistoryNextItemInactive {
background-position: -24px -24px;
}
.olControlNavToolbar .olControlNavigationItemActive {
background-image: url("img/panning-hand-on.png");
background-repeat: no-repeat;
}
.olControlNavToolbar .olControlNavigationItemInactive {
background-image: url("img/panning-hand-off.png");
background-repeat: no-repeat;
}
.olControlNavToolbar .olControlZoomBoxItemActive {
background-image: url("img/drag-rectangle-on.png");
background-color: orange;
background-repeat: no-repeat;
}
.olControlNavToolbar .olControlZoomBoxItemInactive {
background-image: url("img/drag-rectangle-off.png");
background-repeat: no-repeat;
}
.olControlEditingToolbar {
top: 0px;
right: 0px;
height: 30px;
width: 200px;
}
.olControlEditingToolbar div {
background-image: url("img/editing_tool_bar.png");
background-repeat: no-repeat;
float:right;
width: 24px;
height: 24px;
margin: 5px;
}
.olControlEditingToolbar .olControlNavigationItemActive {
background-position: -103px -23px;
}
.olControlEditingToolbar .olControlNavigationItemInactive {
background-position: -103px -0px;
}
.olControlEditingToolbar .olControlDrawFeaturePointItemActive {
background-position: -77px -23px;
}
.olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
background-position: -77px -0px;
}
.olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
background-position: -51px 0px;
}
.olControlEditingToolbar .olControlDrawFeaturePathItemActive {
background-position: -51px -23px;
}
.olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive {
background-position: -26px 0px;
}
.olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
background-position: -26px -23px ;
}
div.olControlSaveFeaturesItemActive {
background-image: url(img/save_features_on.png);
background-repeat: no-repeat;
background-position: 0px 1px;
}
div.olControlSaveFeaturesItemInactive {
background-image: url(img/save_features_off.png);
background-repeat: no-repeat;
background-position: 0px 1px;
}
.olHandlerBoxZoomBox {
border: 2px solid red;
position: absolute;
background-color: white;
opacity: 0.50;
font-size: 1px;
filter: alpha(opacity=50);
}
.olHandlerBoxSelectFeature {
border: 2px solid blue;
position: absolute;
background-color: white;
opacity: 0.50;
font-size: 1px;
filter: alpha(opacity=50);
}
.olControlPanPanel {
top: 10px;
left: 5px;
}
.olControlPanPanel div {
background-image: url(img/pan-panel.png);
height: 18px;
width: 18px;
cursor: pointer;
position: absolute;
}
.olControlPanPanel .olControlPanNorthItemInactive {
top: 0px;
left: 9px;
background-position: 0px 0px;
}
.olControlPanPanel .olControlPanSouthItemInactive {
top: 36px;
left: 9px;
background-position: 18px 0px;
}
.olControlPanPanel .olControlPanWestItemInactive {
position: absolute;
top: 18px;
left: 0px;
background-position: 0px 18px;
}
.olControlPanPanel .olControlPanEastItemInactive {
top: 18px;
left: 18px;
background-position: 18px 18px;
}
.olControlZoomPanel {
top: 71px;
left: 14px;
}
.olControlZoomPanel div {
background-image: url(img/zoom-panel.png);
position: absolute;
height: 18px;
width: 18px;
cursor: pointer;
}
.olControlZoomPanel .olControlZoomInItemInactive {
top: 0px;
left: 0px;
background-position: 0px 0px;
}
.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
top: 18px;
left: 0px;
background-position: 0px -18px;
}
.olControlZoomPanel .olControlZoomOutItemInactive {
top: 36px;
left: 0px;
background-position: 0px 18px;
}
/*
* When a potential text is bigger than the image it move the image
* with some headers (closes #3154)
*/
.olControlPanZoomBar div {
font-size: 1px;
}
.olPopupCloseBox {
background: url("img/close.gif") no-repeat;
cursor: pointer;
}
.olFramedCloudPopupContent {
padding: 5px;
overflow: auto;
}
.olControlNoSelect {
-moz-user-select: none;
-khtml-user-select: none;
}
.olImageLoadError {
background-color: pink;
opacity: 0.5;
filter: alpha(opacity=50); /* IE */
}
/**
* Cursor styles
*/
.olCursorWait {
cursor: wait;
}
.olDragDown {
cursor: move;
}
.olDrawBox {
cursor: crosshair;
}
.olControlDragFeatureOver {
cursor: move;
}
.olControlDragFeatureActive.olControlDragFeatureOver.olDragDown {
cursor: -moz-grabbing;
}
/**
* Layer switcher
*/
.olControlLayerSwitcher {
position: absolute;
top: 25px;
right: 0px;
width: 20em;
font-family: sans-serif;
font-weight: bold;
margin-top: 3px;
margin-left: 3px;
margin-bottom: 3px;
font-size: smaller;
color: white;
background-color: transparent;
}
.olControlLayerSwitcher .layersDiv {
padding-top: 5px;
padding-left: 10px;
padding-bottom: 5px;
padding-right: 75px;
background-color: darkblue;
width: 100%;
height: 100%;
}
.olControlLayerSwitcher .layersDiv .baseLbl,
.olControlLayerSwitcher .layersDiv .dataLbl {
margin-top: 3px;
margin-left: 3px;
margin-bottom: 3px;
}
.olControlLayerSwitcher .layersDiv .baseLayersDiv,
.olControlLayerSwitcher .layersDiv .dataLayersDiv {
padding-left: 10px;
}
.olControlLayerSwitcher .maximizeDiv,
.olControlLayerSwitcher .minimizeDiv {
top: 5px;
right: 0px;
cursor: pointer;
}
.olBingAttribution {
color: #DDD;
}
.olBingAttribution.road {
color: #333;
}

View File

@ -0,0 +1,9 @@
#map{
float: left;
display: inline;
}
#layers{
float: left;
display: inline;
margin-left: 20px;
}

View File

@ -0,0 +1,29 @@
<html xmlns:py="http://genshi.edgewall.org/"
xmlns:i18n="http://genshi.edgewall.org/i18n"
xmlns:xi="http://www.w3.org/2001/XInclude"
py:strip="">
<py:def function="page_title">WMS preview</py:def>
<py:def function="optional_head">
<link type="text/css" rel="stylesheet" media="all" href="/ckanext/harvest/mapviewer.css" />
<script type="text/javascript" src="/ckanext/harvest/js/openlayers/OpenLayers_ckan.js"></script>
<script type="text/javascript" src="/ckanext/harvest/js/mapviewer.js"></script>
</py:def>
<div py:match="content">
<div class="map-view-content">
<h2>WMS preview</h2>
<script type="text/javascript">
//<![CDATA[
$(document).ready(function(){
CKAN.MapViewer.setup("${c.url}");
})
//]]>
</script>
<div id="map"></div>
<div id="layers"></div>
</div>
</div>
<xi:include href="../../layout.html" />
</html>