Merge pull request #274 from tschaub/utfgrid

UTFGrid Tile, Layer, and Control.  This adds support for responsive handling of interactions with large numbers of features represented by UTFGrids.
This commit is contained in:
Tim Schaub
2012-03-09 08:49:30 -08:00
74 changed files with 1815 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<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">
<title>OpenLayers UTFGrid Geography Class</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<style>
#flag {
position: relative;
z-index: 999;
height: 0px;
width: 0px;
-moz-transition: all 0.1s linear;
-webkit-transition: all 0.1s linear;
}
#flag img {
position: absolute;
width: 80px;
-moz-box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
}
.olControlAttribution {
bottom: 5px;
font-size: 9px;
}
</style>
</head>
<body>
<h1 id="title">OpenLayers UTFGrid Geography Class Example</h1>
<div id="shortdesc">
This page demonstrates the use of the OpenLayers UTFGrid Controls.
</div>
<div id="map" class="smallmap">
<div id="flag"></div>
</div>
<p>Point to a country and try to guess the name before it shows up: <strong id="output">&nbsp;</strong>
<div id="docs">
<p>
See the <a href="utfgrid-geography-class.js" target="_blank">utfgrid-geography-class.js</a> source for
detail on using UTFGrids in OpenLayers.
</p>
</div>
<script src="../lib/OpenLayers.js"></script>
<script src="utfgrid-geography-class.js"></script>
</body>
</html>

View File

@@ -0,0 +1,62 @@
var osm = new OpenLayers.Layer.XYZ(
"MapQuest OSM",
[
"http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
"http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
"http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
"http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png"
],
{transitionEffect: "resize", wrapDateLine: true}
);
var utfgrid = new OpenLayers.Layer.UTFGrid({
url: "utfgrid/geography-class/${z}/${x}/${y}.grid.json",
utfgridResolution: 4, // default is 2
displayInLayerSwitcher: false
});
var map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
numZoomLevels: 3,
layers: [osm, utfgrid],
controls: [
new OpenLayers.Control.Navigation({
dragPanOptions: {
enableKinetic: true
}
}),
new OpenLayers.Control.Zoom()
],
center: [0, 0],
zoom: 1
});
var output = document.getElementById("output");
var flag = document.getElementById("flag");
var callback = function(infoLookup, loc, pixel) {
var msg = "";
if (infoLookup) {
var info;
for (var idx in infoLookup) {
// idx can be used to retrieve layer from map.layers[idx]
info = infoLookup[idx];
if (info && info.data) {
output.innerHTML = info.data.admin;
flag.innerHTML = "<img src='data:image/png;base64," + info.data.flag_png + "'>";
flag.style.left = (pixel.x + 15) + "px";
flag.style.top = (pixel.y + 15) + "px";
} else {
output.innerHTML = flag.innerHTML = "&nbsp;";
}
}
}
};
var control = new OpenLayers.Control.UTFGrid({
callback: callback,
handlerMode: "move"
});
map.addControl(control);

64
examples/utfgrid.html Normal file
View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<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">
<title>OpenLayers UTFGrid Demo</title>
<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
<link rel="stylesheet" href="style.css" type="text/css">
<style>
#attrs {
height: 1.5em;
}
#controlToggle li { list-style: none; }
</style>
</head>
<body>
<h1 id="title">OpenLayers UTFGrid Demo</h1>
<div>
<div id="shortdesc">
This page demonstrates the use of the OpenLayers UTFGrid Controls.
</div>
<div id="map" class="smallmap"></div>
<p>
When the selected event is triggered, the underlying feature
attributes are shown below.
</p>
<div id="attrs">&nbsp;</div>
<ul id="controlToggle">
<li>
<input type="radio" name="type" value="move" id="moveHandler"
onclick="toggleControl(this);" checked="checked" />
<label for="moveHandler">Move</label>
</li>
<li>
<input type="radio" name="type" value="hover" id="hoverHandler"
onclick="toggleControl(this);" />
<label for="hoverHandler">Hover</label>
</li>
<li>
<input type="radio" name="type" value="click" id="clickHandler"
onclick="toggleControl(this);" />
<label for="clickHandler">Click</label>
</li>
</ul>
</div>
<div id="docs">
<p>UTFGrids can be used to output highly optimized feature "hit grids."
The UTFGrid encoding scheme encodes interactivity data for a tile in a
space efficient manner. It is designed to be used in browsers for
interactive features like displaying tooltips without having to hit the
server for an "info query."
</p>
<p>
See the <a href="utfgrid.js" target="_blank">utfgrid.js source</a> for
detail on using UTFGrids in OpenLayers. For more info, view the
<a href="https://github.com/mapbox/utfgrid-spec">UTFGrid Specification</a>.
</p>
</div>
<script src="../lib/OpenLayers.js"></script>
<script src="utfgrid.js"></script>
</body>
</html>

61
examples/utfgrid.js Normal file
View File

@@ -0,0 +1,61 @@
var osm = new OpenLayers.Layer.OSM();
var utfgrid = new OpenLayers.Layer.UTFGrid({
url: "utfgrid/world_utfgrid/${z}/${x}/${y}.json",
utfgridResolution: 4, // default is 2
displayInLayerSwitcher: false
});
var map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
controls: [],
layers: [osm, utfgrid],
center: [0, 0],
zoom: 1
});
var callback = function(infoLookup) {
var msg = "";
if (infoLookup) {
var info;
for (var idx in infoLookup) {
// idx can be used to retrieve layer from map.layers[idx]
info = infoLookup[idx];
if (info && info.data) {
msg += "[" + info.id + "] <strong>In 2005, " +
info.data.NAME + " had a population of " +
info.data.POP2005 + " people.</strong> ";
}
}
}
document.getElementById("attrs").innerHTML = msg;
};
var controls = {
move: new OpenLayers.Control.UTFGrid({
callback: callback,
handlerMode: "move"
}),
hover: new OpenLayers.Control.UTFGrid({
callback: callback,
handlerMode: "hover"
}),
click: new OpenLayers.Control.UTFGrid({
callback: callback,
handlerMode: "click"
})
};
for (var key in controls) {
map.addControl(controls[key]);
}
function toggleControl(el) {
for (var c in controls) {
controls[c].deactivate();
}
controls[el.value].activate();
}
// activate the control that responds to mousemove
toggleControl({value: "move"});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<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">
<title>OpenLayers Multiple UTFGrid Demo</title>
<link rel="stylesheet" href="style.css" type="text/css">
<style>
#controlToggle li { list-style: none; }
</style>
</head>
<body>
<h1 id="title">OpenLayers Multiple UTFGrid Demo</h1>
<div id="shortdesc">
This page demonstrates the use of the OpenLayers UTFGrid Controls with
more than one UTFGrid Layer.
</div>
<div id="map" class="smallmap"></div>
<ul id="controlToggle">
<li>
<input type="radio" name="type" value="move_pop" id="moveHandler"
onclick="toggleControl(this);" checked="checked" />
<label for="moveHandler">View population stats</label>
</li>
<li>
<input type="radio" name="type" value="move_bio" id="hoverHandler"
onclick="toggleControl(this);" />
<label for="hoverHandler">View bioregion stats</label>
</li>
<li>
<input type="radio" name="type" value="move_both" id="clickHandler"
onclick="toggleControl(this);" />
<label for="clickHandler">View all stats</label>
</li>
</ul>
<div id="docs">
<p>
This example demonstrates the use of two separate UTFGrid layers.
See the <a href="utfgrid_twogrids.js">utfgrid_twogrids.js source</a>
for detail on how this is done.
</p>
</div>
<div id="attrsdiv"></div>
<script src="../lib/OpenLayers.js"></script>
<script src="utfgrid_twogrids.js"></script>
<script>
</script>
</body>
</html>

View File

@@ -0,0 +1,70 @@
var osm = new OpenLayers.Layer.OSM();
var population = new OpenLayers.Layer.UTFGrid({
name: "World Population",
url: "utfgrid/world_utfgrid/${z}/${x}/${y}.json",
utfgridResolution: 4 // default is 2
});
var bioregions = new OpenLayers.Layer.UTFGrid({
name: "World Bioregions",
url: "utfgrid/bio_utfgrid/${z}/${x}/${y}.json",
utfgridResolution: 4 // default is 2
});
var map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
controls: [],
layers: [osm, population, bioregions],
center: [0, 0],
zoom: 1
});
var callback = function(infoLookup) {
var msg = "";
if (infoLookup) {
var layer, info;
for (var idx in infoLookup) {
layer = map.layers[idx];
info = infoLookup[idx];
if (info && info.data) {
msg += "<strong>" + layer.name + "</strong><br>";
msg += "feature id: " + info.id + "<br>";
for (var key in info.data) {
msg += key + ": " + info.data[key] + "<br>";
}
}
}
}
document.getElementById("attrsdiv").innerHTML = msg;
};
var controls = {
move_pop: new OpenLayers.Control.UTFGrid({
callback: callback,
layers: [population],
handlerMode: "move"
}),
move_bio: new OpenLayers.Control.UTFGrid({
callback: callback,
layers: [bioregions],
handlerMode: "move"
}),
move_both: new OpenLayers.Control.UTFGrid({
callback: callback,
layers: null, // same as all map.layers
handlerMode: "move"
})
};
for (var key in controls) {
map.addControl(controls[key]);
}
function toggleControl(el) {
for (var c in controls) {
controls[c].deactivate();
}
controls[el.value].activate();
}
toggleControl({value: "move_pop"});

View File

@@ -159,6 +159,7 @@
"OpenLayers/Tile.js",
"OpenLayers/Tile/Image.js",
"OpenLayers/Tile/Image/IFrame.js",
"OpenLayers/Tile/UTFGrid.js",
"OpenLayers/Layer/Image.js",
"OpenLayers/Layer/SphericalMercator.js",
"OpenLayers/Layer/EventPane.js",
@@ -181,6 +182,7 @@
"OpenLayers/Layer/GeoRSS.js",
"OpenLayers/Layer/Boxes.js",
"OpenLayers/Layer/XYZ.js",
"OpenLayers/Layer/UTFGrid.js",
"OpenLayers/Layer/OSM.js",
"OpenLayers/Layer/Bing.js",
"OpenLayers/Layer/TMS.js",
@@ -238,6 +240,7 @@
"OpenLayers/Control/WMTSGetFeatureInfo.js",
"OpenLayers/Control/Graticule.js",
"OpenLayers/Control/TransformFeature.js",
"OpenLayers/Control/UTFGrid.js",
"OpenLayers/Control/SLDSelect.js",
"OpenLayers/Control/Zoom.js",
"OpenLayers/Geometry.js",

View File

@@ -0,0 +1,240 @@
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
* full list of contributors). 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/Hover.js
* @requires OpenLayers/Handler/Click.js
*/
/**
* Class: OpenLayers.Control.UTFGrid
*
* This Control provides behavior associated with UTFGrid Layers.
* These 'hit grids' provide underlying feature attributes without
* calling the server (again). This control allows Mousemove, Hovering
* and Click events to trigger callbacks that use the attributes in
* whatever way you need.
*
* The most common example may be a UTFGrid layer containing feature
* attributes that are displayed in a div as you mouseover.
*
* Example Code:
*
* (start code)
* var world_utfgrid = new OpenLayers.Layer.UTFGrid(
* 'UTFGrid Layer',
* "http://tiles/world_utfgrid/${z}/${x}/${y}.json"
* );
* map.addLayer(world_utfgrid);
*
* var control = new OpenLayers.Control.UTFGrid({
* layers: [world_utfgrid],
* handlerMode: 'move',
* callback: function(infoLookup) {
* // do something with returned data
*
* }
* })
* (end code)
*
*
* Inherits from:
* - <OpenLayers.Control>
*/
OpenLayers.Control.UTFGrid = OpenLayers.Class(OpenLayers.Control, {
/**
* APIProperty: autoActivate
* {Boolean} Activate the control when it is added to a map. Default is
* true.
*/
autoActivate: true,
/**
* APIProperty: Layers
* List of layers to consider. Must be Layer.UTFGrids
* `null` is the default indicating all UTFGrid Layers are queried.
* {Array} <OpenLayers.Layer.UTFGrid>
*/
layers: null,
/* Property: defaultHandlerOptions
* The default opts passed to the handler constructors
*/
defaultHandlerOptions: {
'delay': 300,
'pixelTolerance': 4,
'stopMove': false,
'single': true,
'double': false,
'stopSingle': false,
'stopDouble': false
},
/* APIProperty: handlerMode
* Defaults to 'click'. Can be 'hover' or 'move'.
*/
handlerMode: 'click',
/**
* APIMethod: setHandler
* sets this.handlerMode and calls resetHandler()
*
* Parameters:
* hm - {String} Handler Mode string; 'click', 'hover' or 'move'.
*/
setHandler: function(hm) {
this.handlerMode = hm;
this.resetHandler();
},
/**
* Method: resetHandler
* Deactivates the old hanlder and creates a new
* <OpenLayers.Handler> based on the mode specified in
* this.handlerMode
*
*/
resetHandler: function() {
if (this.handler) {
this.handler.deactivate();
this.handler.destroy();
this.handler = null;
}
if (this.handlerMode == 'hover') {
// Handle this event on hover
this.handler = new OpenLayers.Handler.Hover(
this,
{'pause': this.handleEvent, 'move': this.reset},
this.handlerOptions
);
} else if (this.handlerMode == 'click') {
// Handle this event on click
this.handler = new OpenLayers.Handler.Click(
this, {
'click': this.handleEvent
}, this.handlerOptions
);
} else if (this.handlerMode == 'move') {
this.handler = new OpenLayers.Handler.Hover(
this,
// Handle this event while hovering OR moving
{'pause': this.handleEvent, 'move': this.handleEvent},
this.handlerOptions
);
}
if (this.handler) {
return true;
} else {
return false;
}
},
/**
* Constructor: <OpenLayers.Control.UTFGrid>
*
* Parameters:
* options - {Object}
*/
initialize: function(options) {
options = options || {};
options.handlerOptions = options.handlerOptions || this.defaultHandlerOptions;
OpenLayers.Control.prototype.initialize.apply(this, [options]);
this.resetHandler();
},
/**
* Method: handleEvent
* Internal method called when specified event is triggered.
*
* This method does several things:
*
* Gets the lonLat of the event.
*
* Loops through the appropriate hit grid layers and gathers the attributes.
*
* Passes the attributes to the callback
*
* Parameters:
* evt - {<OpenLayers.Event>}
*/
handleEvent: function(evt) {
if (evt == null) {
this.reset();
return;
}
var lonLat = this.map.getLonLatFromPixel(evt.xy);
if (!lonLat) {
return;
}
var layers = this.findLayers();
if (layers.length > 0) {
var infoLookup = {};
var layer, idx;
for (var i=0, len=layers.length; i<len; i++) {
layer = layers[i];
idx = OpenLayers.Util.indexOf(this.map.layers, layer);
infoLookup[idx] = layer.getFeatureInfo(lonLat);
}
this.callback(infoLookup, lonLat, evt.xy);
}
},
/**
* APIMethod: callback
* Function to be called when a mouse event corresponds with a location that
* includes data in one of the configured UTFGrid layers.
*
* Parameters:
* infoLookup - {Object} Keys of this object are layer indexes and can be
* used to resolve a layer in the map.layers array. The structure of
* the property values depend on the data included in the underlying
* UTFGrid and may be any valid JSON type.
*/
callback: function(infoLookup) {
// to be provided in the constructor
},
/**
* Method: reset
* Calls the callback with null.
*/
reset: function(evt) {
this.callback(null);
},
/**
* Method: findLayers
* Internal method to get the layers, independent of whether we are
* inspecting the map or using a client-provided array
*
* The default value of this.layers is null; this causes the
* findLayers method to return ALL UTFGrid layers encountered.
*
* Parameters:
* None
*
* Returns:
* {Array} Layers to handle on each event
*/
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.UTFGrid ) {
layers.push(layer);
}
}
return layers;
},
CLASS_NAME: "OpenLayers.Control.UTFGrid"
});

View File

@@ -420,6 +420,64 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
}
},
/**
* Method: getTileData
* Given a map location, retrieve a tile and the pixel offset within that
* tile corresponding to the location. If there is not an existing
* tile in the grid that covers the given location, null will be
* returned.
*
* Parameters:
* loc - {<OpenLayers.LonLat>} map location
*
* Returns:
* {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
* i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
* offset from top left).
*/
getTileData: function(loc) {
var data = null,
x = loc.lon,
y = loc.lat,
numRows = this.grid.length;
if (this.map && numRows) {
var res = this.map.getResolution(),
tileWidth = this.tileSize.w,
tileHeight = this.tileSize.h,
bounds = this.grid[0][0].bounds,
left = bounds.left,
top = bounds.top;
if (x < left) {
// deal with multiple worlds
if (this.map.baseLayer.wrapDateLine) {
var worldWidth = this.map.getMaxExtent().getWidth();
var worldsAway = Math.ceil((left - x) / worldWidth);
x += worldWidth * worldsAway;
}
}
// tile distance to location (fractional number of tiles);
var dtx = (x - left) / (res * tileWidth);
var dty = (top - y) / (res * tileHeight);
// index of tile in grid
var col = Math.floor(dtx);
var row = Math.floor(dty);
if (row >= 0 && row < numRows) {
var tile = this.grid[row][col];
if (tile) {
data = {
tile: tile,
// pixel index within tile
i: Math.floor((dtx - col) * tileWidth),
j: Math.floor((dty - row) * tileHeight)
};
}
}
}
return data;
},
/**
* Method: queueTileDraw
* Adds a tile to the animation queue that will draw it.

View File

@@ -0,0 +1,178 @@
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the Clear BSD license.
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Layer/XYZ.js
* @requires OpenLayers/Tile/UTFGrid.js
*/
/**
* Class: OpenLayers.Layer.UTFGrid
* This Layer reads from UTFGrid tiled data sources. Since UTFGrids are
* essentially JSON-based ASCII art with attached attributes, they are not
* visibly rendered. In order to use them in the map, you must add a
* <OpenLayers.Control.UTFGrid> ontrol as well.
*
* Example:
*
* (start code)
* var world_utfgrid = new OpenLayers.Layer.UTFGrid({
* url: "/tiles/world_utfgrid/${z}/${x}/${y}.json",
* utfgridResolution: 4,
* displayInLayerSwitcher: false
* );
* map.addLayer(world_utfgrid);
*
* var control = new OpenLayers.Control.UTFGrid({
* layers: [world_utfgrid],
* handlerMode: 'move',
* callback: function(dataLookup) {
* // do something with returned data
* }
* })
* (end code)
*
*
* Inherits from:
* - <OpenLayers.Layer.XYZ>
*/
OpenLayers.Layer.UTFGrid = OpenLayers.Class(OpenLayers.Layer.XYZ, {
/**
* APIProperty: isBaseLayer
* Default is true, as this is designed to be a base tile source.
*/
isBaseLayer: false,
/**
* APIProperty: projection
* {<OpenLayers.Projection>}
* Source projection for the UTFGrids. Default is "EPSG:900913".
*/
projection: new OpenLayers.Projection("EPSG:900913"),
/**
* Property: useJSONP
* {Boolean}
* Should we use a JSONP script approach instead of a standard AJAX call?
*
* Set to true for using utfgrids from another server.
* Avoids same-domain policy restrictions.
* Note that this only works if the server accepts
* the callback GET parameter and dynamically
* wraps the returned json in a function call.
*
* Default is false
*/
useJSONP: false,
/**
* APIProperty: url
* {String}
* URL tempate for UTFGrid tiles. Include x, y, and z parameters.
* E.g. "/tiles/${z}/${x}/${y}.json"
*/
/**
* APIProperty: utfgridResolution
* {Number}
* Ratio of the pixel width to the width of a UTFGrid data point. If an
* entry in the grid represents a 4x4 block of pixels, the
* utfgridResolution would be 4. Default is 2 (specified in
* <OpenLayers.Tile.UTFGrid>).
*/
/**
* Property: tileClass
* {<OpenLayers.Tile>} The tile class to use for this layer.
* Defaults is <OpenLayers.Tile.UTFGrid>.
*/
tileClass: OpenLayers.Tile.UTFGrid,
/**
* Constructor: OpenLayers.Layer.UTFGrid
* Create a new UTFGrid layer.
*
* Parameters:
* config - {Object} Configuration properties for the layer.
*
* Required configuration properties:
* url - {String} The url template for UTFGrid tiles. See the <url> property.
*/
initialize: function(options) {
OpenLayers.Layer.Grid.prototype.initialize.apply(
this, [options.name, options.url, {}, options]
);
this.tileOptions = OpenLayers.Util.extend({
utfgridResolution: this.utfgridResolution
}, this.tileOptions);
},
/**
* APIMethod: clone
* Create a clone of this layer
*
* Parameters:
* obj - {Object} Only used by a subclass of this layer.
*
* Returns:
* {<OpenLayers.Layer.UTFGrid>} An exact clone of this OpenLayers.Layer.UTFGrid
*/
clone: function (obj) {
if (obj == null) {
obj = new OpenLayers.Layer.UTFGrid(this.getOptions());
}
// get all additions from superclasses
obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
return obj;
},
/**
* APIProperty: getFeatureInfo
* Get details about a feature associated with a map location. The object
* returned will have id and data properties. If the given location
* doesn't correspond to a feature, null will be returned.
*
* Parameters:
* location - {<OpenLayers.LonLat>} map location
*
* Returns:
* {Object} Object representing the feature id and UTFGrid data
* corresponding to the given map location. Returns null if the given
* location doesn't hit a feature.
*/
getFeatureInfo: function(location) {
var info = null;
var tileInfo = this.getTileData(location);
if (tileInfo.tile) {
info = tileInfo.tile.getFeatureInfo(tileInfo.i, tileInfo.j);
}
return info;
},
/**
* APIMethod: getFeatureId
* Get the identifier for the feature associated with a map location.
*
* Parameters:
* location - {<OpenLayers.LonLat>} map location
*
* Returns:
* {String} The feature identifier corresponding to the given map location.
* Returns null if the location doesn't hit a feature.
*/
getFeatureId: function(location) {
var id = null;
var info = this.getTileData(location);
if (info.tile) {
id = info.tile.getFeatureId(info.i, info.j);
}
return id;
},
CLASS_NAME: "OpenLayers.Layer.UTFGrid"
});

View File

@@ -0,0 +1,251 @@
/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the Clear BSD license.
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Tile.js
* @requires OpenLayers/Format/JSON.js
*/
/**
* Class: OpenLayers.Tile.UTFGrid
* Instances of OpenLayers.Tile.UTFGrid are used to manage
* UTFGrids. This is an unusual tile type in that it doesn't have a
* rendered image; only a 'hit grid' that can be used to
* look up feature attributes.
*
* See the <OpenLayers.Tile.UTFGrid> constructor for details on constructing a
* new instance.
*
* Inherits from:
* - <OpenLayers.Tile>
*/
OpenLayers.Tile.UTFGrid = OpenLayers.Class(OpenLayers.Tile, {
/**
* Property: url
* {String}
* The URL of the UTFGrid file being requested. Provided by the <getURL>
* method.
*/
url: null,
/**
* Property: utfgridResolution
* {Number}
* Ratio of the pixel width to the width of a UTFGrid data point. If an
* entry in the grid represents a 4x4 block of pixels, the
* utfgridResolution would be 4. Default is 2.
*/
utfgridResolution: 2,
/**
* Property: json
* {Object}
* Stores the parsed JSON tile data structure.
*/
json: null,
/**
* Property: format
* {OpenLayers.Format.JSON}
* Parser instance used to parse JSON for cross browser support. The native
* JSON.parse method will be used where available (all except IE<8).
*/
format: null,
/**
* Constructor: OpenLayers.Tile.UTFGrid
* Constructor for a new <OpenLayers.Tile.UTFGrid> instance.
*
* Parameters:
* layer - {<OpenLayers.Layer>} layer that the tile will go in.
* position - {<OpenLayers.Pixel>}
* bounds - {<OpenLayers.Bounds>}
* url - {<String>} Deprecated. Remove me in 3.0.
* size - {<OpenLayers.Size>}
* options - {Object}
*/
/**
* APIMethod: destroy
* Clean up.
*/
destroy: function() {
this.clear();
OpenLayers.Tile.prototype.destroy.apply(this, arguments);
},
/**
* Method: draw
* Check that a tile should be drawn, and draw it.
* In the case of UTFGrids, "drawing" it means fetching and
* parsing the json.
*
* Returns:
* {Boolean} Was a tile drawn?
*/
draw: function() {
var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments);
if (drawn) {
if (this.isLoading) {
this.abortLoading();
//if we're already loading, send 'reload' instead of 'loadstart'.
this.events.triggerEvent("reload");
} else {
this.isLoading = true;
this.events.triggerEvent("loadstart");
}
this.url = this.layer.getURL(this.bounds);
if (this.layer.useJSONP) {
// Use JSONP method to avoid xbrowser policy
var ols = new OpenLayers.Protocol.Script({
url: this.url,
callback: function(response) {
this.isLoading = false;
this.events.triggerEvent("loadend");
this.json = response.data;
},
scope: this
});
ols.read();
this.request = ols;
} else {
// Use standard XHR
this.request = OpenLayers.Request.GET({
url: this.url,
callback: function(response) {
this.isLoading = false;
this.events.triggerEvent("loadend");
if (response.status === 200) {
this.parseData(response.responseText);
}
},
scope: this
});
}
} else {
this.unload();
}
return drawn;
},
/**
* Method: abortLoading
* Cancel a pending request.
*/
abortLoading: function() {
if (this.request) {
this.request.abort();
delete this.request;
}
this.isLoading = false;
},
/**
* Method: getFeatureInfo
* Get feature information associated with a pixel offset. If the pixel
* offset corresponds to a feature, the returned object will have id
* and data properties. Otherwise, null will be returned.
*
*
* Parameters:
* i - {Number} X-axis pixel offset (from top left of tile)
* j - {Number} Y-axis pixel offset (from top left of tile)
*
* Returns:
* {Object} Object with feature id and data properties corresponding to the
* given pixel offset.
*/
getFeatureInfo: function(i, j) {
var info = null;
if (this.json) {
var id = this.getFeatureId(i, j);
if (id !== null) {
info = {id: id, data: this.json.data[id]};
}
}
return info;
},
/**
* Method: getFeatureId
* Get the identifier for the feature associated with a pixel offset.
*
* Parameters:
* i - {Number} X-axis pixel offset (from top left of tile)
* j - {Number} Y-axis pixel offset (from top left of tile)
*
* Returns:
* {Object} The feature identifier corresponding to the given pixel offset.
* Returns null if pixel doesn't correspond to a feature.
*/
getFeatureId: function(i, j) {
var id = null;
if (this.json) {
var resolution = this.utfgridResolution;
var row = Math.floor(j / resolution);
var col = Math.floor(i / resolution);
var charCode = this.json.grid[row].charCodeAt(col);
var index = this.indexFromCharCode(charCode);
var keys = this.json.keys;
if (!isNaN(index) && (index in keys)) {
id = keys[index];
}
}
return id;
},
/**
* Method: indexFromCharCode
* Given a character code for one of the UTFGrid "grid" characters,
* resolve the integer index for the feature id in the UTFGrid "keys"
* array.
*
* Parameters:
* charCode - {Integer}
*
* Returns:
* {Integer} Index for the feature id from the keys array.
*/
indexFromCharCode: function(charCode) {
if (charCode >= 93) {
charCode--;
}
if (charCode >= 35) {
charCode --;
}
return charCode - 32;
},
/**
* Method: parseData
* Parse the JSON from a request
*
* Parameters:
* str - {String} UTFGrid as a JSON string.
*
* Returns:
* {Object} parsed javascript data
*/
parseData: function(str) {
if (!this.format) {
this.format = new OpenLayers.Format.JSON();
}
this.json = this.format.read(str);
},
/**
* Method: clear
* Delete data stored with this tile.
*/
clear: function() {
this.json = null;
},
CLASS_NAME: "OpenLayers.Tile.UTFGrid"
});

119
tests/Control/UTFGrid.html Normal file
View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script>
/**
* Because browsers that implement requestAnimationFrame may not execute
* animation functions while a window is not displayed (e.g. in a hidden
* iframe as in these tests), we mask the native implementations here. The
* native requestAnimationFrame functionality is tested in Util.html and
* in PanZoom.html (where a popup is opened before panning). The panTo tests
* here will test the fallback setTimeout implementation for animation.
*/
window.requestAnimationFrame =
window.webkitRequestAnimationFrame =
window.mozRequestAnimationFrame =
window.oRequestAnimationFrame =
window.msRequestAnimationFrame = null;
</script>
<script src="../OLLoader.js"></script>
<script type="text/javascript">
var map, layer, control;
var log;
function setUp() {
layer = new OpenLayers.Layer.UTFGrid({
url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
isBaseLayer: true,
utfgridResolution: 4
});
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
layers: [layer],
center: [0, 0],
zoom: 1
});
log = [];
control = new OpenLayers.Control.UTFGrid({
callback: function(infoLookup, loc, pixel) {
log.push([infoLookup, loc, pixel]);
}
});
map.addControl(control);
}
function tearDown() {
map.destroy();
map = null;
layer = null;
control = null;
log = [];
}
function test_constructor(t) {
t.plan(2);
var control = new OpenLayers.Control.UTFGrid();
t.ok(control instanceof OpenLayers.Control.UTFGrid, "utfgrid instance");
t.eq(control.handlerMode, "click", "control mode");
control.destroy();
}
function test_handleEvent(t) {
setUp();
var cases = [{
evt: {xy: {x: 100, y: 70}},
lookup: {
"0": {
id: "207",
data: {
NAME: "United States",
POP2005: 299846449
}
}
}
}, {
evt: {xy: {x: 350, y: 20}},
lookup: {
"0": {
id: "245",
data: {
NAME: "Russia",
POP2005: 143953092
}
}
}
}];
var len = cases.length;
t.plan(4*len);
// wait for tile loading to finish
t.delay_call(0.5, function() {
var c, arg;
for (var i=0; i<len; ++i) {
c = cases[i];
t.eq(log.length, i, i + ": log length before");
control.handleEvent(c.evt);
t.eq(log.length, i+1, i + ": log length after");
t.eq(log[i][0], c.lookup, i + ": callback infoLookup");
t.eq(log[i][2], c.evt.xy, i + ": callback pixel");
}
tearDown();
});
}
</script>
</head>
<body>
<div id="map" style="height: 256px; width: 512px"></div>
</body>
</html>

View File

@@ -1366,6 +1366,132 @@
map.destroy();
});
}
function test_getGridData(t) {
t.plan(12);
var layer = new OpenLayers.Layer.Grid(null, null, null, {
isBaseLayer: true, getURL: function() {
return "/bogus/path/to/tile";
}
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
controls: [],
center: [0, 0],
zoom: 1
});
// get tile data for [0, 0]
var data = layer.getTileData({lon: 0, lat: 0});
t.ok(data && data.tile, "[0, 0]: got tile data");
t.eq(data.i, 0, "[0, 0]: i");
t.eq(data.j, 128, "[0, 0]: j");
t.ok(
data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
"[0, 0]: tile bounds " + data.tile.bounds.toString()
);
// get tile data for [-110, 45]
data = layer.getTileData({lon: -110, lat: 45});
t.ok(data && data.tile, "[-110, 45]: got tile data");
t.eq(data.i, 99, "[-110, 45]: i");
t.eq(data.j, 64, "[-110, 45]: j");
t.ok(
data.tile.bounds.equals({left: -180, bottom: -90, right: 0, top: 90}),
"[-110, 45]: tile bounds " + data.tile.bounds.toString()
);
// get tile data for [0, 300] (north of grid)
data = layer.getTileData({lon: 0, lat: 300})
t.eq(data, null, "[0, 300]: north of grid");
// get tile data for [400, 0] (east of grid)
data = layer.getTileData({lon: 400, lat: 0})
t.eq(data, null, "[400, 0]: east of grid");
// get tile data for [0, -500] (south of grid)
data = layer.getTileData({lon: 0, lat: -500})
t.eq(data, null, "[0, -500]: south of grid");
// get tile data for [-200, 0] (west of grid)
data = layer.getTileData({lon: -200, lat: 0})
t.eq(data, null, "[-200, 0]: west of grid");
map.destroy();
}
function test_getGridData_wrapped(t) {
t.plan(18);
var layer = new OpenLayers.Layer.Grid(null, null, null, {
isBaseLayer: true, getURL: function() {
return "/bogus/path/to/tile";
},
wrapDateLine: true
});
var map = new OpenLayers.Map({
div: "map",
layers: [layer],
controls: [],
center: [-50, 0],
zoom: 1
});
// get tile data for [0, 0]
var data = layer.getTileData({lon: 0, lat: 0});
t.ok(data && data.tile, "[0, 0]: got tile data");
t.eq(data.i, 0, "[0, 0]: i");
t.eq(data.j, 128, "[0, 0]: j");
t.ok(
data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
"[0, 0]: tile bounds " + data.tile.bounds.toString()
);
// get tile data for [-110, 45]
data = layer.getTileData({lon: -110, lat: 45});
t.ok(data && data.tile, "[-110, 45]: got tile data");
t.eq(data.i, 99, "[-110, 45]: i");
t.eq(data.j, 64, "[-110, 45]: j");
t.ok(
data.tile.bounds.equals({left: -180, bottom: -90, right: 0, top: 90}),
"[-110, 45]: tile bounds " + data.tile.bounds.toString()
);
// get tile data for [0, 300] (north of grid)
data = layer.getTileData({lon: 0, lat: 300})
t.eq(data, null, "[0, 300]: north of grid");
// get tile data for [400, 0] (equivalent to [40, 0] and visible on map)
data = layer.getTileData({lon: 400, lat: 0})
t.ok(data && data.tile, "[400, 0]: got tile data");
t.eq(data.i, 56, "[400, 0]: i");
t.eq(data.j, 128, "[400, 0]: j");
t.ok(
data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
"[400, 0]: tile bounds " + data.tile.bounds.toString()
);
// get tile data for [0, -500] (south of grid)
data = layer.getTileData({lon: 0, lat: -500})
t.eq(data, null, "[0, -500]: south of grid");
// get tile data for [-200, 0] (equivalent to [160, 0] and wrapped to west side map)
data = layer.getTileData({lon: -200, lat: 0})
t.ok(data && data.tile, "[-200, 0]: got tile data");
t.eq(data.i, 227, "[-200, 0]: i");
t.eq(data.j, 128, "[-200, 0]: j");
t.ok(
data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
"[-200, 0]: tile bounds " + data.tile.bounds.toString()
);
map.destroy();
}
</script>
</head>
<body>

115
tests/Layer/UTFGrid.html Normal file
View File

@@ -0,0 +1,115 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script>
/**
* Because browsers that implement requestAnimationFrame may not execute
* animation functions while a window is not displayed (e.g. in a hidden
* iframe as in these tests), we mask the native implementations here. The
* native requestAnimationFrame functionality is tested in Util.html and
* in PanZoom.html (where a popup is opened before panning). The panTo tests
* here will test the fallback setTimeout implementation for animation.
*/
window.requestAnimationFrame =
window.webkitRequestAnimationFrame =
window.mozRequestAnimationFrame =
window.oRequestAnimationFrame =
window.msRequestAnimationFrame = null;
</script>
<script src="../OLLoader.js"></script>
<script type="text/javascript">
var map, layer;
function setUp() {
layer = new OpenLayers.Layer.UTFGrid({
url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
isBaseLayer: true,
utfgridResolution: 4
});
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
layers: [layer],
center: [0, 0],
zoom: 1
});
}
function tearDown() {
map.destroy();
map = null;
layer = null;
}
function test_constructor(t) {
t.plan(4);
var layer = new OpenLayers.Layer.UTFGrid({
name: "foo",
url: "path/to/tiles/${z}/${x}/${y}",
utfgridResolution: 8
});
t.ok(layer instanceof OpenLayers.Layer.UTFGrid, "utfgrid instance");
t.eq(layer.name, "foo", "layer name");
t.eq(layer.url, "path/to/tiles/${z}/${x}/${y}", "layer url");
t.eq(layer.utfgridResolution, 8, "layer utfgridResolution");
layer.destroy();
}
function test_clone(t) {
t.plan(3);
setUp();
var clone = layer.clone();
t.ok(layer instanceof OpenLayers.Layer.UTFGrid, "utfgrid instance");
t.eq(layer.url, "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json", "layer url");
t.eq(layer.utfgridResolution, 4, "layer utfgridResolution");
clone.destroy();
tearDown();
}
function test_getFeatureInfo(t) {
t.plan(2);
setUp();
// wait for tile loading to finish
t.delay_call(0.5, function() {
var loc = new OpenLayers.LonLat(-110, 45).transform("EPSG:4326", "EPSG:900913");
var info = layer.getFeatureInfo(loc);
t.eq(info.id, "207", "feature id");
t.eq(info.data, {POP2005: 299846449, NAME: "United States"}, "feature data");
tearDown();
});
}
function test_getFeatureId(t) {
t.plan(2);
setUp();
// wait for tile loading to finish
t.delay_call(0.5, function() {
var ca = new OpenLayers.LonLat(-110, 55).transform("EPSG:4326", "EPSG:900913");
var ru = new OpenLayers.LonLat(90, 75).transform("EPSG:4326", "EPSG:900913");
t.eq(layer.getFeatureId(ca), "24", "feature id for ca");
t.eq(layer.getFeatureId(ru), "245", "feature id for ru");
tearDown();
});
}
</script>
</head>
<body>
<div id="map" style="height: 256px; width: 512px"></div>
</body>
</html>

305
tests/Tile/UTFGrid.html Normal file
View File

@@ -0,0 +1,305 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script>
/**
* Because browsers that implement requestAnimationFrame may not execute
* animation functions while a window is not displayed (e.g. in a hidden
* iframe as in these tests), we mask the native implementations here. The
* native requestAnimationFrame functionality is tested in Util.html and
* in PanZoom.html (where a popup is opened before panning). The panTo tests
* here will test the fallback setTimeout implementation for animation.
*/
window.requestAnimationFrame =
window.webkitRequestAnimationFrame =
window.mozRequestAnimationFrame =
window.oRequestAnimationFrame =
window.msRequestAnimationFrame = null;
</script>
<script src="../OLLoader.js"></script>
<script type="text/javascript">
var map, layer;
function setUp() {
layer = new OpenLayers.Layer.UTFGrid({
url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
isBaseLayer: true,
utfgridResolution: 4
});
map = new OpenLayers.Map({
div: "map",
projection: "EPSG:900913",
layers: [layer],
center: [0, 0],
zoom: 1
});
}
function tearDown() {
map.destroy();
map = null;
layer = null;
}
function test_constructor(t) {
t.plan(7);
setUp();
var position = new OpenLayers.Pixel(20, 30);
var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
var url = "http://example.com/";
var size = new OpenLayers.Size(5, 6);
var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
t.ok(tile instanceof OpenLayers.Tile, "tile instance");
t.ok(tile instanceof OpenLayers.Tile.UTFGrid, "UTFGrid tile instance");
t.ok(tile.layer === layer, "layer set");
t.ok(tile.position.equals(position), "position set");
t.ok(tile.bounds.equals(bounds), "bounds set");
t.eq(tile.url, url, "url set");
t.ok(tile.size.equals(size), "size set");
tearDown();
}
function test_parseData(t) {
t.plan(2);
setUp();
var tile = layer.grid[0][0];
tile.parseData('{"foo": "bar"}');
t.eq(tile.json, {foo: "bar"}, "valid json parsed");
var err, obj;
try {
obj = tile.parseData('foo bar');
} catch (e) {
err = e;
}
// The JSON format doesn't actually throw on IE6, so we also check
// for undefined here.
t.ok(err instanceof Error || obj === undefined, "throws on invalid json");
tearDown();
}
function test_draw(t) {
t.plan(7);
setUp();
var position = new OpenLayers.Pixel(20, 30);
var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
var url = "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json";
var size = new OpenLayers.Size(256, 256);
var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
var log = [];
function logger(event) {
log.push(event);
}
tile.events.on({
loadstart: logger,
reload: logger,
loadend: logger
});
t.eq(log.length, 0, "no events logged");
// start tile loading
tile.draw();
t.eq(log.length, 1, "[first draw] one event");
t.eq(log[0].type, "loadstart", "[first draw] loadstart");
// restart tile loading
log.length = 0;
tile.draw();
t.eq(log.length, 1, "[second draw] first event");
t.eq(log[0].type, "reload", "[second draw] reload");
// wait for tile loading to finish
t.delay_call(1, function() {
t.eq(log.length, 2, "[second draw] second event");
t.eq(log[1].type, "loadend", "[second draw] loadend");
tearDown();
});
}
function test_abortLoading(t) {
t.plan(7);
setUp();
var position = new OpenLayers.Pixel(20, 30);
var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
var url = "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json";
var size = new OpenLayers.Size(256, 256);
var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
var log = [];
function logger(event) {
log.push(event);
}
tile.events.on({
loadstart: logger,
reload: logger,
loadend: logger
});
t.eq(log.length, 0, "no events logged");
// start tile loading
tile.draw();
t.eq(log.length, 1, "[first draw] one event");
t.eq(log[0].type, "loadstart", "[first draw] loadstart");
// abort tile loading
log.length = 0;
tile.abortLoading();
t.eq(log.length, 0, "[first abort] no events logged"); // TODO: does anybody need an abort event?
// abort again for the heck of it
var err;
try {
tile.abortLoading();
} catch (e) {
err = e;
}
t.ok(!err, "[second abort] no trouble");
t.eq(log.length, 0, "[second abort] no events");
// wait to confirm tile loading doesn't happen after abort
t.delay_call(1, function() {
t.eq(log.length, 0, "[wait] no events");
tearDown();
});
}
function test_getFeatureId(t) {
t.plan(3);
setUp();
var tile = layer.grid[1][1];
t.delay_call(0.5, function() {
var id = tile.getFeatureId(16, 60);
t.eq(id, "238", "feature 238 at 16, 60");
t.eq(tile.getFeatureId(18, 63), id, "same feature at 18, 63");
t.eq(tile.getFeatureId(300, 10), null, "null id outside tile");
tearDown();
});
}
function test_getFeatureInfo(t) {
t.plan(3);
setUp();
var tile = layer.grid[1][1];
t.delay_call(0.5, function() {
var info = tile.getFeatureInfo(16, 60);
var exp = {
id: "238",
data: {
NAME: "Svalbard",
POP2005: 0
}
};
t.eq(info, exp, "feature info at 16, 60");
t.eq(tile.getFeatureInfo(17, 62), exp, "same feature at 17, 62");
t.eq(tile.getFeatureInfo(300, 10), null, "undefined outside tile");
tearDown();
});
}
// While I dislike committing tests that aren't run, I'd like to make an
// exception here. This test (or something like it) should pass. When
// https://github.com/mapbox/utfgrid-spec/issues/1 is resolved, we should
// either modify this or update demo.json and enable the test.
function xtest_getFeatureId_demo(t) {
/**
* The UTFGrid 1.2 spec (https://github.com/mapbox/utfgrid-spec/blob/master/1.2/utfgrid.md)
* links to a demo.json to be used for testing implementations. This
* file is constructed with 256x256 data points. Each data point maps
* to a "feature id" using this heuristic:
*
* // x and y are pixel offsets from top left of 256x256 tile
* if (y < 255 || x < 222) {
* id = (y * 256) + x
* } else {
* id = 65501; // max number of ids that can be encoded
* }
*/
t.plan(1);
setUp();
// look at this beauty of a constructor
var tile = new OpenLayers.Tile.UTFGrid(
layer, // layer
new OpenLayers.Pixel(0, 0), // position
new OpenLayers.Bounds(0, 0, 256, 256), // bounds
"../data/utfgrid/demo-1.1.json", // url
new OpenLayers.Size(256, 256), // size
{utfgridResolution: 1} // options
);
var err;
var request = new OpenLayers.Request.GET({
url: tile.url,
success: function(req) {
try {
tile.parseData(req.responseText);
} catch (e) {
err = e;
}
},
failure: function(req) {
err = new Error("Failed to fetch json. Status: " + req.status);
}
});
// wait for response and parsing, then make assertions
t.delay_call(1, function() {
if (err) {
t.fail(err);
} else {
var got, exp, failure;
outer: for (var y=0; y<256; ++y) {
for (var x=0; x<256; ++x) {
if (y<255 || x<222) {
exp = String((y * 256) + x);
} else {
exp = "65501";
}
got = tile.getFeatureId(x, y);
if (got !== exp) {
failure = "Failed to get id for (" + x + ", " + y + "): " +
"got " + got + " but expected " + exp;
break outer;
}
}
}
if (!failure) {
t.ok(true, "resolved feature ids for all data points");
} else {
t.fail(failure);
}
}
tearDown();
});
}
</script>
</head>
<body>
<div id="map" style="height:550px;width:500px"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -0,0 +1 @@
{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]}

View File

@@ -41,6 +41,7 @@
<li>Control/Split.html</li>
<li>Control/TouchNavigation.html</li>
<li>Control/TransformFeature.html</li>
<li>Control/UTFGrid.html</li>
<li>Control/WMSGetFeatureInfo.html</li>
<li>Control/WMTSGetFeatureInfo.html</li>
<li>Control/PanPanel.html</li>
@@ -165,6 +166,7 @@
<li>Layer/Text.html</li>
<li>Layer/TileCache.html</li>
<li>Layer/TMS.html</li>
<li>Layer/UTFGrid.html</li>
<li>Layer/Vector.html</li>
<li>Layer/Vector/RootContainer.html</li>
<li>Layer/WMS.html</li>
@@ -220,6 +222,7 @@
<li>Tile.html</li>
<li>Tile/Image.html</li>
<li>Tile/Image/IFrame.html</li>
<li>Tile/UTFGrid.html</li>
<li>Tween.html</li>
<li>Kinetic.html</li>
<li>Util.html</li>