TileMapServiceImageryProvider creates the rectangle using the wrong box parameters

I’m trying to use the TileMapServiceImageryProvider on tiles I created using MapTiler 0.5.4.

Here are problems I had when it uses the tilemapresource.xml to set up the rectangle:

In loadXML, the wrong coordinates are used to set up the box
(latitudes (y) are passed into longitudes and longitudes (x) are passed into
latitudes):

var sw = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘miny’)),
parseFloat(bbox.getAttribute(‘minx’)));

var ne = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘maxy’)),
parseFloat(bbox.getAttribute(‘maxx’)));

should
be:

var sw = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘minx’)),
parseFloat(bbox.getAttribute(‘miny’)));

var ne = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘maxx’)),
parseFloat(bbox.getAttribute(‘maxy’)));

The code works for the Cesium examples because the
BoundingBox is set up incorrectly in tilemapresource.xml for Cesium_Logo_Color,
NaturalEarthII, and SmallArea. See example from Natural Earth:

EPSG:4326

It should be:

EPSG:4326

When I create my map tiles using MapTiler,
using the OSGEO TMS tiling scheme option, the generated tilemapresource.xml
file creates the bounding box using meters instead of latitude/longitude.
Shouldn’t the build rectangle code check for EPSG:900913 vs EPSG:4326? And if
it’s mercator it should unproject the bounding box coordinates before making the
rectangle.

See tilemapresource.xml below:

<?xml version="1.0" encoding="utf-8"?>

EPSG:900913

Hi,

Thanks for reporting this. Apparently gdal2tiles.py, which was used to generate the tilesets you mentioned, flips the X and Y values. I’ll try to find some time to add some better heuristics to TileMapServiceImageryProvider to account for this. Is your TMS server available publicly for testing by any chance?

Thanks,

Kevin

We don’t have stood up a TMS server yet, so I attached zip file of my output for MapTiler.

Quick fix options:

  1. edit by hand the boundingbox to use lat/lon instead of meters in the tilemapresource.xml (but swap the x for y)

  2. pass in the bounding box as an option in the TileMapServiceImageryProvider (note: do not need to swap x for y)

  3. edit TileMapServiceImageryProvider:

// Try to load remaining parameters from XML

loadXML(url + ‘tilemapresource.xml’).then(function(xml) {

var tileFormatRegex = /tileformat/i;

var tileSetRegex = /tileset/i;

var tileSetsRegex = /tilesets/i;

var bboxRegex = /boundingbox/i;

var srsRegex = /srs/i; // XXX: GDIT fix

var format, bbox, tilesets;

var srs; // XXX: GDIT fix

var tilesetsList = ; //list of TileSets

// Allowing options properties to override XML values

var nodeList = xml.childNodes[0].childNodes;

// Iterate XML Document nodes for properties

for (var i = 0; i < nodeList.length; i++){

if (tileFormatRegex.test(nodeList.item(i).nodeName)){

format = nodeList.item(i);

} else if (tileSetsRegex.test(nodeList.item(i).nodeName)){

tilesets = nodeList.item(i); // Node list of TileSets

var tileSetNodes = nodeList.item(i).childNodes;

// Iterate the nodes to find all TileSets

for(var j = 0; j < tileSetNodes.length; j++){

if (tileSetRegex.test(tileSetNodes.item(j).nodeName)){

// Add them to tilesets list

tilesetsList.push(tileSetNodes.item(j));

}

}

} else if (bboxRegex.test(nodeList.item(i).nodeName)){

bbox = nodeList.item(i);

} else if (srsRegex.test(nodeList.item(i).nodeName)){ // XXX: GDIT fix

srs = nodeList.item(i).textContent;

}

}

that._fileExtension = defaultValue(options.fileExtension, format.getAttribute(‘extension’));

that._tileWidth = defaultValue(options.tileWidth, parseInt(format.getAttribute(‘width’), 10));

that._tileHeight = defaultValue(options.tileHeight, parseInt(format.getAttribute(‘height’), 10));

that._minimumLevel = defaultValue(options.minimumLevel, parseInt(tilesetsList[0].getAttribute(‘order’), 10));

that._maximumLevel = defaultValue(options.maximumLevel, parseInt(tilesetsList[tilesetsList.length - 1].getAttribute(‘order’), 10));

// rectangle handling

that._rectangle = options.rectangle;

if (!defined(that._rectangle)) {

// XXX: GDIT: check srs

var sw, ne;

if (srs == ‘EPSG:4326’) {

// XXX: GDIT Fix bounding box parse

// var sw = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘miny’)), parseFloat(bbox.getAttribute(‘minx’)));

// var ne = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘maxy’)), parseFloat(bbox.getAttribute(‘maxx’)));

sw = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘minx’)), parseFloat(bbox.getAttribute(‘miny’)));

ne = Cartographic.fromDegrees(parseFloat(bbox.getAttribute(‘maxx’)), parseFloat(bbox.getAttribute(‘maxy’)));

}

else if (srs == ‘EPSG:900913’ || srs == ‘EPSG:3857’) {

// XXX: GDIT Convert bounding box from m to lat/lon instead

var projection = new WebMercatorProjection(Ellipsoid.WGS84);

sw = projection.unproject(new Cartesian2(parseFloat(bbox.getAttribute(‘minx’)), parseFloat(bbox.getAttribute(‘miny’))));

ne = projection.unproject(new Cartesian2(parseFloat(bbox.getAttribute(‘maxx’)), parseFloat(bbox.getAttribute(‘maxy’))));

}

else {

sw = Cartographic.fromDegrees(-180, -90);

ne = sw = Cartographic.fromDegrees(180, 90);

}

that._rectangle = new Rectangle(sw.longitude, sw.latitude, ne.longitude, ne.latitude);

} else {

that._rectangle = Rectangle.clone(that._rectangle);

}

temperature_OSGEO.zip (55.8 KB)

Hi,

I just opened a pull request that should fix the problem:

https://github.com/AnalyticalGraphicsInc/cesium/pull/1983

With any luck it will make it into 1.0 on Friday, but I can’t promise it.

I did notice that your temperature_OSGEO example TMS layer seems to be georeferenced incorrectly, though. This is true in Cesium but also in the Google Maps and Open Layers clients included in your zip file. Just a heads up.

Kevin

Thanks Kevin, the fix is exactly what I needed. And you are correct, the images I attached were poorly georeferenced (I should have warned you). I cheated when I made the tiles and just used some close coordinates, I wanted to give you an example with small number of levels than I was working with.
I love Cesium, looking forward to 1.0.

The pull request was just merged to master, so it will ship with 1.0 on Friday.

Hey Kevin,

Still having issues with TileMapServiceImageryProvider
flipping the bounding box when the projection is Mercator.

Update, we are getting closer to standing up a tms
server. Instead of me creating my own test data, I will be able to use the
service. When I created the tiles using MapTiler I was getting aglobal-mercator projection and a bounding box with x y coordinates, which was
working great with the Cesium 1.0 release. Now, when I get it from the service I get the Mercator
projection with lat/lon bounding box (not flipped) and I have to flip them to
work. Is it still necessary for Cesium to flip the bounding box for Mercator projection?
We are running version 1.11.0 gdal on a Linux CentOS. We are using
gdal2tiles.py to make the tiles.

tilemapresource.xml (928 Bytes)

Hi,

We’ve definitely seen older versions of gdal2tiles.py write “mercator” and flip X and Y. It’s unfortunate if they fixed that bug without also switching to the new spec-conforming name (global-mercator). We may be able to improve our heuristics for determining when we need to flip, but perhaps the most robust solution is to just make it a user-specified option that can override our heuristics. I don’t think I’ll get to doing that right away, so perhaps you’d like to take a stab at it? I think it will be a straightforward modification to TileMapServiceImageryProvider.js.

Thanks,

Kevin

Thanks for the idea Kevin.

I added a new option : overrideFlipXY and added check for ‘mercator’ for degrees vs XY when creating rectangle.

I made my changes against the latest version of TileMapServiceImageryProvider.js. See attachment.

TileMapServiceImageryProvider.js (22.8 KB)

Thanks, Ellen. Would you mind signing a Contributor’s License Agreement? Details are here in the “License” section:
https://github.com/AnalyticalGraphicsInc/cesium/blob/master/CONTRIBUTING.md

The CLA essentially gives us formal permission to use your changes. You can just email the form, no need to mail anything by post.

Thanks,

Kevin

Submitted license last Friday (Aug 29).

I can confirm we received the CLA. Thanks Ellen!

Patrick

I’ve been playing around with trying to generate our own imagery datasets, using gdal2tiles. I’ve installed OSGeo4W, and have used that to install GDAL 1.11.3-1. However, I’m running into problems showing the output in Cesium, and this thread appears relevant.

I see that gdal2tiles had a patch applied ( https://trac.osgeo.org/gdal/ticket/5336 ) that fixes the “flipped x/y” problem, and that that version is part of the GDAL 1.11 release. After running it, I can see that the generated tilemapresource.xml files appear to now be “correct”. However, since the profile value is still “geodetic”, Cesium is still trying to apply its own hack to work around the old behavior, and thus mis-interpreting the correct data.

I looked at the history for TileMapServiceImageryProvider.js, and don’t see any evidence that the “overrideFlipXY” patch provided in this discussion thread was ever applied.

Are there any possible better heuristics that could be used to handle this? As Kevin said earlier, it’s a bit of a pain that things have changed like this.

For that matter, the history of gdal2tiles itself seems pretty messed up at this point. There’s several other patches in the GDAL bug tracker that have never been applied, there’s a fork used by the Maptiler application that people have talked about merging in, MapTiler itself has gone closed-source (although the old Google Code repo is still technically accessible), there’s a couple other forks floating around with various changes… pretty hard to nail down what features and tweaks are actually available and where.

What issue are you seeing with the OSGeo4w version? I remember using it earlier this year and not having any issues with it, except needing to give Cesium a valid rectangle to work with.

Per the issue I linked, gdal2tiles used to have its X and Y values flipped. For example, an older tileset we use has this line in tilemapresource.xml:

  <BoundingBox minx="-90.00000000000000" miny="-180.00000000000000" maxx="90.00000000000000" maxy="180.00000000000000"/>

``

But one of the test tilesets I just generated using the gdal2tiles in GDAL 1.11, which includes the changes from GDAL ticket #5336, looks like this:

  <BoundingBox minx="-180.00000000000000" miny="-90.00000000000000" maxx="180.00000000000000" maxy="90.00000000000000"/>

``

When I try to load that test tileset, Cesium crashes with a rendering error. ImageryLayer blows up because the following line returns undefined:

var clippedImageryRectangle = Rectangle.intersection(imageryRectangle, imageryBounds, clippedRectangleScratch);

``

This happens because TileMapServiceImageryProvider tries to determine if the tileset was generated by gdal2tiles, by looking for a profile value of “geodetic” or “mercator” instead of “global-geodetic” and “global-mercator”. If it sees one of those values, it assumes it needs to flip the X and Y bounds. However, the current version of gdal2tiles has X and Y correct, so when TMSIP flips those anyway, things break.

I suppose there’s a couple ways I can work around this. I can manually edit a tilemapresource.xml that is “correct”, and swap the numbers back to being “wrong” so that Cesium loads them okay. I can manually edit the “profile” value to have the “global-” prefix so that Cesium would decide doesn’t need to flip the X and Y values. I could edit gdal2tiles.py to actually output either of those approaches.

On the Cesium side - if this patch was merged in to allow overriding the X/Y flipping, I could then add some additional metadata to a newly generated dataset so that our code would know to enable that flag. Wouldn’t help us right now, though, as we probably won’t be upgrading our Cesium version again before this release is done.

I may just go with the “edit gdal2tiles.py” approach in the end. As I said, there’s a number of forks floating around with varying fixes and capabilities. There’s an unapplied ticket+patch in the GDAL tracker to handle empty tiles without crashing. The version included with Maptiler supports JPG output. Someone else wrote some parallel processing capabilities. Sadly, per a couple of the tickets, gdal2tiles.py seems to be unmaintained atm, and while there’s been discussion of trying to get these various changes into the mainline, apparently no one has. If I’m gonna have to sorta keep my own copy anyway to get it to do what I want, might as well make a couple other tweaks as well.

Mark, sorry we all missed this back in October when you reported it, but I just opened a pull request to fix Cesium for newer versions of gdal2tiles and added a flag to enable older buggy versions with the flipped XY. See the PR for the gory details: https://github.com/AnalyticalGraphicsInc/cesium/pull/3489

Thanks,

Matt

W00T! Glad to hear it, and nice to hear my comments helped get this worked out. (Also my fault for not directly filing a bug against Cesium at the time.)

I actually didn’t wind up generating whole new datasets for us back then. Did a bunch of research, wrote up some internal documentation describing all the tools and concepts and source datasets we would need to do so, and left it as a possibility for a later development cycle. So, not critical for us right this minute, but should be helpful down the road.

Thanks!

Mark