New WMTS GetCapabilities & RESTful example.
Uses the WMTS server of the ogdwien open data initiative of the City of Vienna. Shows how to build a fullscreen map that works well on both mobile and desktop clients. With lightweight permalinks and Geolocate control.
This commit is contained in:
@@ -0,0 +1,160 @@
|
|||||||
|
html, body, #map {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
#title, #tags, #shortdesc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.olControlAttribution {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: right;
|
||||||
|
color: #BFEFFF;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background: rgba(0,0,100,0.2);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 5px 0 0 0;
|
||||||
|
}
|
||||||
|
.olControlAttribution a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #BFEFFF;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel {
|
||||||
|
height: 108px;
|
||||||
|
width: 36px;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
left: inherit;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel div {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
left: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomInItemInactive,
|
||||||
|
div.olControlZoomPanel .olControlZoomOutItemInactive {
|
||||||
|
top: 0;
|
||||||
|
background: rgba(0,0,100,0.4);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomInItemInactive {
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomOutItemInactive {
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
top: 37px;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomOutItemInactive:after,
|
||||||
|
div.olControlZoomPanel .olControlZoomInItemInactive:after {
|
||||||
|
font-weight: bold;
|
||||||
|
content: '+';
|
||||||
|
font-size: 36px;
|
||||||
|
padding: 7px;
|
||||||
|
z-index: 2000;
|
||||||
|
color: #BFEFFF;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomOutItemInactive:after {
|
||||||
|
content: '–';
|
||||||
|
line-height: 0.9em;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel div.olControlGeolocateItemInactive,
|
||||||
|
div.olControlZoomPanel div.olControlGeolocateItemActive {
|
||||||
|
position: absolute;
|
||||||
|
right: 20px;
|
||||||
|
top: 98px;
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
background: #ccc url(img/locate.png) center no-repeat;
|
||||||
|
background-color: rgba(0,0,100,0.4);
|
||||||
|
}
|
||||||
|
div.olControlZoomPanel div.olControlGeolocateItemActive {
|
||||||
|
background-color: rgba(0,0,100,0.2);
|
||||||
|
}
|
||||||
|
div.olControlGeolocateItemInactive:after {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 36px;
|
||||||
|
padding: 7px;
|
||||||
|
z-index: 2000;
|
||||||
|
color: #BFEFFF;
|
||||||
|
line-height: 1em;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.layerPanel {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 82px;
|
||||||
|
}
|
||||||
|
div.layerPanel div {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
div.layerPanel div:after {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: arial;
|
||||||
|
padding: 8px;
|
||||||
|
color: #BFEFFF;
|
||||||
|
line-height: 36px;
|
||||||
|
border-radius: 5px 5px 5px 5px;
|
||||||
|
background-color: #ccc;
|
||||||
|
background: rgba(0,0,100,0.4);
|
||||||
|
}
|
||||||
|
div.layerPanel div.labelButtonItemInactive:after,
|
||||||
|
div.layerPanel div.labelButtonItemActive:after {
|
||||||
|
content: 'Labels';
|
||||||
|
}
|
||||||
|
:lang(de) div.layerPanel div.labelButtonItemInactive:after,
|
||||||
|
:lang(de) div.layerPanel div.labelButtonItemActive:after {
|
||||||
|
content: 'Text';
|
||||||
|
}
|
||||||
|
div.layerPanel div.labelButtonItemActive:after {
|
||||||
|
text-decoration: underline;
|
||||||
|
background: rgba(0,0,100,0.2);
|
||||||
|
}
|
||||||
|
div.layerPanel div.aerialButtonItemInactive:after,
|
||||||
|
div.layerPanel div.aerialButtonItemActive:after {
|
||||||
|
content: 'Aerial';
|
||||||
|
border-radius: 5px 0 0 5px;
|
||||||
|
}
|
||||||
|
:lang(de) div.layerPanel div.aerialButtonItemInactive:after,
|
||||||
|
:lang(de) div.layerPanel div.aerialButtonItemActive:after {
|
||||||
|
content: 'Luftbild';
|
||||||
|
}
|
||||||
|
div.layerPanel div.aerialButtonItemActive:after {
|
||||||
|
text-decoration: underline;
|
||||||
|
background: rgba(0,0,100,0.2);
|
||||||
|
}
|
||||||
|
div.layerPanel div.mapButtonItemInactive:after,
|
||||||
|
div.layerPanel div.mapButtonItemActive:after {
|
||||||
|
content: 'Map';
|
||||||
|
border-radius: 0 5px 5px 0;
|
||||||
|
}
|
||||||
|
:lang(de) div.layerPanel div.mapButtonItemInactive:after,
|
||||||
|
:lang(de) div.layerPanel div.mapButtonItemActive:after {
|
||||||
|
content: 'Karte';
|
||||||
|
}
|
||||||
|
div.layerPanel div.mapButtonItemActive:after {
|
||||||
|
text-decoration: underline;
|
||||||
|
background: rgba(0,0,100,0.2);
|
||||||
|
}
|
||||||
|
div.layerPanel div.mapButtonItemInactive,
|
||||||
|
div.layerPanel div.mapButtonItemActive {
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>City of Vienna WMTS with REST Encoding and Geolocate</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<link rel="stylesheet" href="mobile-wmts-vienna.css" type="text/css">
|
||||||
|
<script src="../lib/OpenLayers.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="title">City of Vienna WMTS for Desktop and Mobile Devices</h1>
|
||||||
|
<div id="tags">
|
||||||
|
mobile, vienna, ogdwien, rest, restful, wmts, geolocate, permalink
|
||||||
|
</div>
|
||||||
|
<p id="shortdesc">
|
||||||
|
A full-screen map for both desktop and mobile devices. Uses
|
||||||
|
language dependent CSS content and the WMTSCapabilities format to
|
||||||
|
retrieve layers from the ogdwien open data initiative of the City
|
||||||
|
of Vienna. Also has a lightweight custom anchor permalink
|
||||||
|
functionality and uses the Geolocate control.
|
||||||
|
</p>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script src="mobile-wmts-vienna.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
var map;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
OpenLayers.ProxyHost = "proxy.cgi?url=";
|
||||||
|
|
||||||
|
// Set document language for css content
|
||||||
|
document.documentElement.lang = (navigator.userLanguage || navigator.language).split("-")[0];
|
||||||
|
|
||||||
|
// A panel for switching between Aerial and Map, and for turning labels
|
||||||
|
// on and off.
|
||||||
|
var layerPanel = new OpenLayers.Control.Panel({
|
||||||
|
displayClass: "layerPanel",
|
||||||
|
autoActivate: true
|
||||||
|
});
|
||||||
|
var aerialButton = new OpenLayers.Control({
|
||||||
|
type: OpenLayers.Control.TYPE_TOOL,
|
||||||
|
displayClass: "aerialButton",
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
if (aerial) {map.setBaseLayer(aerial);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var mapButton = new OpenLayers.Control({
|
||||||
|
type: OpenLayers.Control.TYPE_TOOL,
|
||||||
|
displayClass: "mapButton",
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
if (fmzk) {map.setBaseLayer(fmzk);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var labelButton = new OpenLayers.Control({
|
||||||
|
type: OpenLayers.Control.TYPE_TOGGLE,
|
||||||
|
displayClass: "labelButton",
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
if (labels) {labels.setVisibility(true);}
|
||||||
|
},
|
||||||
|
deactivate: function() {
|
||||||
|
if (labels) {labels.setVisibility(false);}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layerPanel.addControls([aerialButton, mapButton, labelButton]);
|
||||||
|
|
||||||
|
var zoomPanel = new OpenLayers.Control.ZoomPanel();
|
||||||
|
|
||||||
|
// Geolocate control for the Locate button - the locationupdated handler
|
||||||
|
// draws a cross at the location and a circle showing the accuracy radius.
|
||||||
|
zoomPanel.addControls([
|
||||||
|
new OpenLayers.Control.Geolocate({
|
||||||
|
type: OpenLayers.Control.TYPE_TOGGLE,
|
||||||
|
geolocationOptions: {
|
||||||
|
enableHighAccuracy: false,
|
||||||
|
maximumAge: 0,
|
||||||
|
timeout: 7000
|
||||||
|
},
|
||||||
|
eventListeners: {
|
||||||
|
activate: function() {
|
||||||
|
map.addLayer(vector);
|
||||||
|
},
|
||||||
|
deactivate: function() {
|
||||||
|
map.removeLayer(vector);
|
||||||
|
vector.removeAllFeatures();
|
||||||
|
},
|
||||||
|
locationupdated: function(e) {
|
||||||
|
vector.removeAllFeatures();
|
||||||
|
vector.addFeatures([
|
||||||
|
new OpenLayers.Feature.Vector(e.point, null, {
|
||||||
|
graphicName: 'cross',
|
||||||
|
strokeColor: '#f00',
|
||||||
|
strokeWidth: 2,
|
||||||
|
fillOpacity: 0,
|
||||||
|
pointRadius: 10
|
||||||
|
}),
|
||||||
|
new OpenLayers.Feature.Vector(
|
||||||
|
OpenLayers.Geometry.Polygon.createRegularPolygon(
|
||||||
|
new OpenLayers.Geometry.Point(e.point.x, e.point.y),
|
||||||
|
e.position.coords.accuracy / 2, 50, 0
|
||||||
|
), null, {
|
||||||
|
fillOpacity: 0.1,
|
||||||
|
fillColor: '#000',
|
||||||
|
strokeColor: '#f00',
|
||||||
|
strokeOpacity: 0.6
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
map.zoomToExtent(vector.getDataExtent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Map with navigation controls optimized for touch devices
|
||||||
|
map = new OpenLayers.Map({
|
||||||
|
div: "map",
|
||||||
|
theme: null,
|
||||||
|
projection: "EPSG:3857",
|
||||||
|
units: "m",
|
||||||
|
maxExtent: new OpenLayers.Bounds(
|
||||||
|
-20037508.34, -20037508.34, 20037508.34, 20037508.34
|
||||||
|
),
|
||||||
|
maxResolution: 156543.0339,
|
||||||
|
numZoomLevels: 20,
|
||||||
|
controls: [
|
||||||
|
new OpenLayers.Control.TouchNavigation({
|
||||||
|
mouseWheelOptions: {
|
||||||
|
cumulative: false,
|
||||||
|
interval: 20
|
||||||
|
},
|
||||||
|
dragPanOptions: {
|
||||||
|
enableKinetic: {
|
||||||
|
deceleration: 0.02
|
||||||
|
}
|
||||||
|
},
|
||||||
|
zoomBoxEnabled: false
|
||||||
|
}),
|
||||||
|
new OpenLayers.Control.Attribution(),
|
||||||
|
zoomPanel,
|
||||||
|
layerPanel
|
||||||
|
],
|
||||||
|
eventListeners: {
|
||||||
|
moveend: function() {
|
||||||
|
// update anchor for permalinks
|
||||||
|
var ctr = map.getCenter();
|
||||||
|
window.location.hash = "x="+ctr.lon+"&y="+ctr.lat+"&z="+map.getZoom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layerPanel.activateControl(mapButton);
|
||||||
|
layerPanel.activateControl(labelButton);
|
||||||
|
|
||||||
|
// Vector layer for the location cross and circle
|
||||||
|
var vector = new OpenLayers.Layer.Vector("Vector Layer");
|
||||||
|
|
||||||
|
// The WMTS layers we're going to add
|
||||||
|
var fmzk, aerial, labels;
|
||||||
|
|
||||||
|
// The WMTSCapabilities format and the default options for the layers
|
||||||
|
var format = new OpenLayers.Format.WMTSCapabilities(), defaults = {
|
||||||
|
requestEncoding: "REST",
|
||||||
|
matrixSet: "google3857",
|
||||||
|
transitionEffect: "resize",
|
||||||
|
tileLoadingDelay: 0,
|
||||||
|
attribution: 'Datenquelle: Stadt Wien - <a href="http://data.wien.gv.at">data.wien.gv.at</a>'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request capabilities and create layers
|
||||||
|
OpenLayers.Request.GET({
|
||||||
|
url: "http://maps.wien.gv.at/wmts/1.0.0/WMTSCapabilities.xml",
|
||||||
|
success: function(request) {
|
||||||
|
var doc = request.responseText,
|
||||||
|
caps = format.read(doc);
|
||||||
|
fmzk = format.createLayer(caps, OpenLayers.Util.applyDefaults(
|
||||||
|
{layer:"fmzk", requestEncoding:"REST"}, defaults
|
||||||
|
));
|
||||||
|
aerial = format.createLayer(caps, OpenLayers.Util.applyDefaults(
|
||||||
|
{layer:"lb", requestEncoding:"REST"}, defaults
|
||||||
|
));
|
||||||
|
labels = format.createLayer(caps, OpenLayers.Util.applyDefaults(
|
||||||
|
{layer:"beschriftung", requestEncoding:"REST", isBaseLayer: false},
|
||||||
|
defaults
|
||||||
|
));
|
||||||
|
map.addLayers([fmzk, aerial, labels]);
|
||||||
|
|
||||||
|
// zoom to initial extent or restore position from permalink
|
||||||
|
var extent = fmzk.tileFullExtent,
|
||||||
|
ctr = extent.getCenterLonLat(),
|
||||||
|
zoom = map.getZoomForExtent(extent, true),
|
||||||
|
params = OpenLayers.Util.getParameters("?"+window.location.hash.substr(1));
|
||||||
|
OpenLayers.Util.applyDefaults(params, {x:ctr.lon, y:ctr.lat, z:zoom});
|
||||||
|
map.setCenter(new OpenLayers.LonLat(params.x, params.y), params.z);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Reliably hide the address bar on Android and iOS devices. From
|
||||||
|
// http://blog.nateps.com/how-to-hide-the-address-bar-in-a-full-screen
|
||||||
|
(function() {
|
||||||
|
var page = document.getElementById("map"),
|
||||||
|
ua = navigator.userAgent,
|
||||||
|
iphone = ~ua.indexOf('iPhone') || ~ua.indexOf('iPod'),
|
||||||
|
ipad = ~ua.indexOf('iPad'),
|
||||||
|
ios = iphone || ipad,
|
||||||
|
// Detect if this is running as a fullscreen app from the homescreen
|
||||||
|
fullscreen = window.navigator.standalone,
|
||||||
|
android = ~ua.indexOf('Android'),
|
||||||
|
lastWidth = 0;
|
||||||
|
|
||||||
|
if (android) {
|
||||||
|
// Android's browser adds the scroll position to the innerHeight, just to
|
||||||
|
// make this really fucking difficult. Thus, once we are scrolled, the
|
||||||
|
// page height value needs to be corrected in case the page is loaded
|
||||||
|
// when already scrolled down. The pageYOffset is of no use, since it always
|
||||||
|
// returns 0 while the address bar is displayed.
|
||||||
|
window.onscroll = function() {
|
||||||
|
page.style.height = window.innerHeight + 'px';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var setupScroll = window.onload = function() {
|
||||||
|
// Start out by adding the height of the location bar to the width, so that
|
||||||
|
// we can scroll past it
|
||||||
|
if (ios) {
|
||||||
|
// iOS reliably returns the innerWindow size for documentElement.clientHeight
|
||||||
|
// but window.innerHeight is sometimes the wrong value after rotating
|
||||||
|
// the orientation
|
||||||
|
var height = document.documentElement.clientHeight;
|
||||||
|
// Only add extra padding to the height on iphone / ipod, since the ipad
|
||||||
|
// browser doesn't scroll off the location bar.
|
||||||
|
if (iphone && !fullscreen) height += 60;
|
||||||
|
page.style.height = height + 'px';
|
||||||
|
} else if (android) {
|
||||||
|
// The stock Android browser has a location bar height of 56 pixels, but
|
||||||
|
// this very likely could be broken in other Android browsers.
|
||||||
|
page.style.height = (window.innerHeight + 56) + 'px';
|
||||||
|
}
|
||||||
|
// Scroll after a timeout, since iOS will scroll to the top of the page
|
||||||
|
// after it fires the onload event
|
||||||
|
setTimeout(scrollTo, 0, 0, 1);
|
||||||
|
};
|
||||||
|
(window.onresize = function() {
|
||||||
|
var pageWidth = page.offsetWidth;
|
||||||
|
// Android doesn't support orientation change, so check for when the width
|
||||||
|
// changes to figure out when the orientation changes
|
||||||
|
if (lastWidth == pageWidth) return;
|
||||||
|
lastWidth = pageWidth;
|
||||||
|
setupScroll();
|
||||||
|
})();
|
||||||
|
})();
|
||||||
+2
-1
@@ -21,7 +21,8 @@ allowedHosts = ['www.openlayers.org', 'openlayers.org',
|
|||||||
'sigma.openplans.org', 'demo.opengeo.org',
|
'sigma.openplans.org', 'demo.opengeo.org',
|
||||||
'www.openstreetmap.org', 'sample.azavea.com',
|
'www.openstreetmap.org', 'sample.azavea.com',
|
||||||
'v2.suite.opengeo.org', 'v-swe.uni-muenster.de:8080',
|
'v2.suite.opengeo.org', 'v-swe.uni-muenster.de:8080',
|
||||||
'vmap0.tiles.osgeo.org', 'www.openrouteservice.org']
|
'vmap0.tiles.osgeo.org', 'www.openrouteservice.org',
|
||||||
|
'maps.wien.gv.at']
|
||||||
|
|
||||||
method = os.environ["REQUEST_METHOD"]
|
method = os.environ["REQUEST_METHOD"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user