Skip to content


Subversion checkout URL

You can clone with
Download ZIP


UTFGrid Layer, Tile and Controls #244

wants to merge 8 commits into from

5 participants


See the related email to dev list at:

More info here:

I'd love to see support for UTFGrids included in the next release. Let me know if there's anything I can do to clean things up to make this happen...


I've added a few commits that I'm hoping you'll consider. These are included in a pull request. I wanted to make some changes that were based on recent work in master. The bulk of the commits in that pull request will go away if you merge master with your branch.

The first change (34a9c46) just adds the new UTFGrid component to the debug loader so examples can work without a build. The next change (6284e44) updates the examples so they fit in a bit better with the rest (and run without the missing ./OpenLayers.js).

My understanding of the UTFGrid spec is that the structure of property values in the data member may be of any type. The existing implementation assumes that the property values will be objects with keys representing feature attribute names and values representing these attribute values. The UTFGrid spec examples show a simple data structure with strings that wouldn't work with this implementation. I've updated things (e1dffac) so that the user provided callback receives a data lookup - keys corresponding to layer index and values straight from the data member.

This change (e1dffac) also gives the layer a getData method. I think this is a bit more flexible/sensible than having the control extract data from the layer's tiles.

I'd be interested to here if others have comments on this. That's it for now. More review later.


I'm adding commits in my fork while reviewing.

  • 73e6973 - Removed image stuff from UTFGrid tile.
  • e6f0aa0 - Tweaked use of Script protocol. Open question: doesn't the GeoJSON format throw when parsing the UTFGrid data? Good candidate for a test.
  • c18f6a2 - I changed the JSON parsing to use the JSON format. This is essentially for IE7 support. I'm open for pushback here. I know there's a push to minimize our build size, and folks are scoffing at legacy IE support. But I've sat in some fairly big agencies (USGS, NOAA) and seen first hand the value in having OpenLayers be a bit more concerned about cross-browser compatibility than others.
  • 12fa9b5 - Since loadstart is fired, it makes sense to fire loadend as well.
  • faeb691 - Initial tests for UTFGrid tiles.
  • 551c582 - Updated layer and tile API so tile is responsible for getting feature id/data given pixel offsets and layer does the same for map locations.

This is all looking great from my end. I like the new API a lot and seems like the most sensible way to do it.

What else is left?


We should put together tests for the layer and control as well. I've grabbed the UTFGrid demo.json to validate the implementation and am finding that I can't get tests to pass (see this issue).


In 4d31a3e, I've changed the layer's getFeatureData method to getFeatureInfo. The old getFeatureData method was already retrieving feature ids, and it doesn't make sense to force the user to do two requests for the complete bundle of information.

In experimenting with the UTFGrid highlighting, I've noticed a few other issues with the layer. Will be good to write up tests for these.


I think it would be great to get this into 2.12. There are a few more changes I'd like to make. Matt, I'm interested to know how these sound to you.

Single arg layer constructor

I think single arg constructors are the way forward for the library. Details upon request. For this layer, here's the difference.

Instead of this

var layer = new OpenLayers.Layer.UTFGrid(
    {utfgridResolution: 4, displayInLayerSwitcher: false}

A user would write this

var layer = new OpenLayers.Layer.UTFGrid({
    url: "utfgrids/${z}/${x}/${y}.json",
    utfgridResolution: 4, 
    displayInLayerSwitcher: false

The difference gets more pronounced when you start assigning more layer properties in the constructor. The three arg constructor is particularly awkward when you have a bunch of options (highlightStyle, useJSONP, zoomOffset, etc.)


The layer and control still need tests.

I'm also interested to work out what is going on with character codes between 55296 and 57343 (mapbox/utfgrid-spec#1). I'm fairly confident there is nothing wrong with this implementation. It's either an encoding issue with the example/tests or a real consideration that needs to be accounted for in the spec. So, while I don't think it should block inclusion of these components, I'd like to figure out why the demo.js tests won't pass.


I'd like to see the JSONP functionality in action. Are there reliable servers that we could use in an example? I think it also makes sense to have examples with pan/zoom navigation. I understand it's tough to load up a bunch of static UTFGrids. But at least being able to pan a bit or zoom once would be nice.


Maybe persnickety, but I'm inclined to change utfgridResolution to gridResolution. While a bit ambiguous, I find the latter easier to type (the spec refers to this as factor - even tidier, but less descriptive).

I'd like to clean up the examples a bit more. Minor stuff: separate js file, some name/structure changes.


Single Arg constructor



I'm a bit unfamiliar with javascript unit testing and I probably won't have time to really delve into them until later next week unfortunately.


The problem is that OpenLayers script protocol uses a global callback registry so the returned JSONP file must dynamically wrap the json in that function. At the moment, the jsonp support is not very practical since the two main utfgrid servers don't support dynamic jsonp callbacks.

Tilestream serves up jsonp files with a hardcoded grid(..) callback and, for reasons of caching and CDN storage, probably will continue to do so.

I've added dynamic jsonp callbacks to my Tilestache fork at but that hasn't been pulled into the upstream master yet nor are there really any reliable servers out there running it.

Pan/Zoom in examples

I will update the examples and generate another zoom level of tiles if it's OK to add them to the repository.


-1 on gridResolution .. its a bit too ambiguous since we refer to the tile layout as the "grid" ... I would be in favor of utfgridFactor or gridFactor to match the specs more closely.


Thanks for the feedback. All sounds good. I'm happy to stick with your utfgridResolution - it is the most descriptive. If you can pile in a few more utfgrids, I think that would be great. You'll likely notice some dateline wrapping issues - not specific to this code I think, but something I want to check out. Might be easiest to have a multiple zoom level example with a restricted extent & set of resolutions.

I'll try to find a bit more time for the tests.


Making additional changes discussed above.

  • 2feb860 - example restructuring
  • 36d22bb - single arg constructor
  • 268b842 - test for layer and control (passing in IE6, Chrome 17, etc.)
  • 32db586 - providing more detail to the callback
  • c6aa996 - example with multiple zoom levels
@tschaub tschaub referenced this pull request

UTFGrid support #274


As mentioned on the other pull request, I only opened that in case Matt doesn't get a chance to pull in my additional commits. I'd like to get some additional eyes on this, but I think it's in good shape. I still want to have an example that allows for some navigation. I've pulled down three zoom levels of grids from MapBox's geography-class layer. I'll try to put together an updated example with that before too long.


Complete batch of commits merged w/ 1a44458. Thanks @perrygeo for the collaboration on this.

@tschaub tschaub closed this

I just got back from vacation and was psyched to see it merged already. Thanks for all the hard work on this - it looks great! Anything else to do to tie up loose ends before the release?


Using imageSize for the frame size does not make sense to me. The tileSize should be used for the frame size. imageSize should be for the image inside the frame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 8, 2012
  1. @probins
Commits on Feb 21, 2012
  1. @ahocevar

    Removing imageOffset and using getImageSize.

    ahocevar authored
    This fixes a regression that was introduced with the Tile.Image overhaul. See
Commits on Feb 26, 2012
  1. @elemoine

    Merge pull request #203 from probins/http

    elemoine authored
    HTTP protocol: enable POST for update/delete
  2. @ahocevar

    Merge pull request #239 from ahocevar/3625

    ahocevar authored
    Removing imageOffset and using getImageSize. r=@elemoine
Commits on Feb 27, 2012
  1. @perrygeo
  2. @perrygeo
  3. @perrygeo

    Merged tschaub/utfgrid

    perrygeo authored
Commits on Feb 28, 2012
  1. @perrygeo
This page is out of date. Refresh to see the latest.
2  examples/gutter.html
@@ -45,7 +45,7 @@ <h1 id="title">Gutter Example</h1>
{layers: 'topp:states'},
{gutter: 15});
- var states = new OpenLayers.Layer.WMS( "Roads (no gutter)",
+ var states = new OpenLayers.Layer.WMS( "States (no gutter)",
{layers: 'topp:states'});
map.addLayers([states, states15]);
11 lib/OpenLayers/Layer.js
@@ -159,13 +159,6 @@ OpenLayers.Layer = OpenLayers.Class({
imageSize: null,
- /**
- * Property: imageOffset
- * {<OpenLayers.Pixel>} For layers with a gutter, the image offset
- * represents displacement due to the gutter.
- */
- imageOffset: null,
@@ -693,7 +686,7 @@ OpenLayers.Layer = OpenLayers.Class({
* APIMethod: setTileSize
* Set the tile size based on the map size. This also sets layer.imageSize
- * and layer.imageOffset for use by Tile.Image.
+ * or use by Tile.Image.
* Parameters:
* size - {<OpenLayers.Size>}
@@ -710,8 +703,6 @@ OpenLayers.Layer = OpenLayers.Class({
// + ": layers with " +
// "gutters need non-null tile sizes");
- this.imageOffset = new OpenLayers.Pixel(-this.gutter,
- -this.gutter);
this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
tileSize.h + (2*this.gutter));
29 lib/OpenLayers/Protocol/HTTP.js
@@ -62,13 +62,28 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
scope: null,
- * Property: readWithPOST
+ * APIProperty: readWithPOST
* {Boolean} true if read operations are done with POST requests
* instead of GET, defaults to false.
readWithPOST: false,
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+ /**
* Property: wildcarded.
* {Boolean} If true percent signs are added around values
* read from LIKE filters, for example if the protocol
@@ -293,7 +308,8 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
requestType: "update"
- resp.priv = OpenLayers.Request.PUT({
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
url: url,
callback: this.createCallback(this.handleUpdate, resp, options),
headers: options.headers,
@@ -344,11 +360,16 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
requestType: "delete"
- resp.priv = OpenLayers.Request.DELETE({
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
url: url,
callback: this.createCallback(this.handleDelete, resp, options),
headers: options.headers
- });
+ };
+ if (this.deleteWithPOST) {
+ = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
return resp;
12 lib/OpenLayers/Tile/Image.js
@@ -208,11 +208,12 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
* code.
positionTile: function() {
- var style = this.getTile().style;
+ var style = this.getTile().style,
+ size = this.layer.getImageSize(this.bounds);
style.left = this.position.x + "%"; = this.position.y + "%";
- style.width = this.size.w + "%";
- style.height = this.size.h + "%";
+ style.width = size.w + "%";
+ style.height = size.h + "%";
@@ -256,11 +257,6 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
var top = this.layer.gutter / this.layer.tileSize.h * 100;
style.left = -left + "%"; = -top + "%";
- style.width = (2 * left + 100) + "%";
- style.height = (2 * top + 100) + "%";
- } else {
- style.width = "100%";
- style.height = "100%";
style.visibility = "hidden";
style.opacity = 0;
5 tests/Layer.html
@@ -764,7 +764,7 @@
function test_layer_setTileSize(t) {
- t.plan(6);
+ t.plan(4);
layer = new OpenLayers.Layer();
@@ -784,7 +784,6 @@
var size = new OpenLayers.Size(2,2);
t.ok(layer.tileSize.equals(size), "size paramater set correctly to layer's tile size");
- t.ok(layer.imageOffset == null, "imageOffset and imageSize null when no gutters")
//set on layer
layer.tileSize = layerTileSize;
@@ -803,10 +802,8 @@
size = new OpenLayers.Size(10,100);
- var desiredImageOffset = new OpenLayers.Pixel(-15, -15);
var desiredImageSize = new OpenLayers.Size(40, 130);
- t.ok(layer.imageOffset.equals(desiredImageOffset), "image offset correctly calculated");
t.ok(layer.imageSize.equals(desiredImageSize), "image size correctly calculated");
11 tests/Tile/Image.html
@@ -295,9 +295,6 @@
t.ok(tile.layer.imageSize == null,
"zero size gutter doesn't set image size");
- t.ok(tile.layer.imageOffset == null,
- "zero size gutter doesn't set image offset");
var zero_gutter_bounds = tile.bounds;
@@ -312,8 +309,12 @@
tile.size.h + (2 * gutter))),
"gutter properly changes image size");
- t.ok(tile.layer.imageOffset.equals(new OpenLayers.Pixel(-gutter, -gutter)),
- "gutter properly sets image offset");
+ var offsetLeft = -(gutter / layer.tileSize.w * 100) | 0;
+ var offsetTop = -(gutter / layer.tileSize.h * 100) | 0;
+ t.eq(parseInt(, 10), offsetLeft,
+ "gutter properly sets image left style");
+ t.eq(parseInt(, 10), offsetTop,
+ "gutter properly sets image top style");
"gutter doesn't affect tile bounds");
Something went wrong with that request. Please try again.