mapbox / vector-tile-spec

Mapbox Vector Tile specification
https://www.mapbox.com/vector-tiles/specification/
Other
890 stars 209 forks source link

Make tile_zoom mandatory in the 3.0 specification #136

Open tumic0 opened 5 years ago

tumic0 commented 5 years ago

The missing zoom level in the tiles is the biggest design flaw in the 2.x specification as without it you can not handle the MVT(PBF) files like ordinary images. When the _tilezoom is left optional in the specification, image plugins (like QtPBFImagePlugin) can not be updated to (reliable) enable working with MVT(PBF) files like ordinary images at least for v.3 tiles.

joto commented 5 years ago

I am not sure I understand. What are "ordinary images"? If you mean something like JPG or PNG tiles, they don't contain a zoom level either.

Generally the idea in the 2.0 spec was that you don't need the tile x/y/zoom in the tile itself, because you have it in the URL or filename or inside the MBTiles file or whatever (depending on how you access the tiles). This is the same as with raster tiles. But sometimes it is useful to have the tile x/y/zoom in the tile itself, so in the spec 3.0 we have this as an option. But it is unlikely that we can make this "mandatory", because we want to be backwards compatible as much as possible, although I would like to see this as "recommended".

tumic0 commented 5 years ago

Yes, I mean ordinary raster or vector images like PNG, JPEG or SVG. All those formats contain all data required to render them in the image data. Unfortunately, this is not true for MVT(PBF). Having the zoom in the file name is as if you would name a PNG image "image_with_red_background.png" and expect a renderer to display it correctly with a red background. Naturally, this is nonsense so there is no image plugin API that would enable gathering some required info from the filename. But in case of MVT(PBF) you need the zoom level, otherwise you can not render the tile.

When the zoom is part of the data, you will get instant support for MVT(PBF) tiles in ordinary image viewers (like Gwenview) or any SW that supports only raster MBTiles/xyz tiles without the need of modifying a single source line in the SW. You just install the appropriate image plugin, e.g. QtPBFImagePlugin in case of a Qt application.

joto commented 5 years ago

Okay, I see your point. But if you are arguing that you need the zoom level for the tile to be able to render as a self-contained unit, you'd also need the style in the tile, because without a style that fits the data in the tile, you can't render either. And rendering only a single tile isn't that useful either, so you need x/y also.

tumic0 commented 5 years ago

The style can be part of the plugin (like it is in case of QtPBFImagePlugin(1)), it makes not much sense to have it in all the tiles.

The x/y indexes do not provide any benefits, personally I even think they would be contra-productive as this would be another step to break compatibility with existing systems like the MVT(PBF) specification with the missing zoom level. Broken compatibility is what I see as the biggest design flaw of the vector tiles specification. Normally one would design the tile format so, that it keeps the benefits of the raster tiles (can be viewed with regular image viewers + existing SW can use them without modification only by using a new image plugin(2)) and additionally it will bring new features like "lossless zoom" etc. But the MVT(PBF) tiles have lost all the benefits only because of a single missing byte in the tile which is so ridiculous, that one may doubt whether this was only poor design and not intentional...

[1] QtPBFImagePlugin supports only the OpenMapTiles scheme at the moment, but generally nothing prevents it to add support for other style schemes, the layer names are part of the tile, so there is data that can be used to select the correct style.

[2] Adding support for JPEG tiles to SW that supports only PNG tiles is almost everywhere as easy as install/add a JPEG image plugin, so why shouldn't this be also the case of MVT(PBF) tiles?!

rbrundritt commented 2 months ago

I agree that having the zoom/x/y information in the tile itself would be very useful. I sometimes get people asking for help debugging an issue with vector tiles and they send me the tile file which only have the Y value in the name since I don't have the whole file path. So then I have to go back and ask for it. Additionally, in generally, there are several ways in which tiles are hosted and shared, so getting the zoom/x/y information is not consistent between apps, which can cause complications. With a raster tile you can load it and compare to existing imagery and often tell if it's in the wrong location or not. With vector tiles, unless you really know the data, it can be hard to tell if the tile is rendered in the right place. When I first started working with vector tiles ages ago, and was parsing them, I was surprised this information wasn't one of the first things in the tile.

Instead of zoom, or zoom/x/y, perhaps just having tile ID instead, as each tile can be represented as a single integer value. This is what the NetTopologySuite library for vector tile does. You can then calculate the zoom, x, y values from this single integer value. This would mean a single property that contains three insightful pieces of information.

/// <summary>
/// Calculates the tile id of the tile at position (x, y) for the given zoom.
/// </summary>
/// <param name="zoom"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static ulong CalculateTileId(int zoom, int x, int y)
{
    ulong id = CalculateTileId(zoom);
    long width = (long)(1 << zoom);// System.Math.Pow(2, zoom);
    return id + (ulong) x + (ulong) (y * width);
}

/// <summary>
/// Calculate the tile given the id.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static (int x, int y, int zoom) CalculateTile(ulong id)
{
    // find out the zoom level first.
    int zoom = 0;
    if (id > 0)
    {
        // only if the id is at least at zoom level 1.
        while (id >= CalculateTileId(zoom))
        {
            // move to the next zoom level and keep searching.
            zoom++;
        }

        zoom--;
    }

    // calculate the x-y.
    ulong local = id - CalculateTileId(zoom);
    ulong width = (ulong)(1 << zoom);// System.Math.Pow(2, zoom);
    int x = (int) (local % width);
    int y = (int) (local / width);

    return (x, y, zoom);
}

/// <summary>
/// Calculates the tile id of the tile at position (0, 0) for the given zoom.
/// </summary>
/// <param name="zoom"></param>
/// <returns></returns>
public static ulong CalculateTileId(int zoom)
{
    switch (zoom)
    {
        case 0:
            return 0;
        case 1:
            return 1;
        case 2:
            return 5;
        case 3:
            return 21;
        case 4:
            return 85;
        case 5:
            return 341;
        case 6:
            return 1365;
        case 7:
            return 5461;
        case 8:
            return 21845;
        case 9:
            return 87381;
        case 10:
            return 349525;
        case 11:
            return 1398101;
        case 12:
            return 5592405;
        case 13:
            return 22369621;
        case 14:
            return 89478485;
        case 15:
            return 357913941;
        case 16:
            return 1431655765;
        case 17:
            return 5726623061;
        case 18:
            return 22906492245;
        case 19:
            return 91625968981;
        case 20:
            return 366503875925;
        case 21:
            return 1466015503701;
        case 22:
            return 5864062014805;
        case 23:
            return 23456248059221;
        case 24:
            return 93824992236885;
    }

    //Calculate the tileId if zoom level doesn't match one of the above precalculated values.
    return (ulong)(Math.Pow(4, zoom) - 1) / 3;
}

This is similar to the Quadkey tile naming convention used by Bing Maps, but more developer friendly.