mapbox / tilejson-spec

JSON format for describing map tilesets.
255 stars 52 forks source link

Support for other projections #6

Closed perliedman closed 8 years ago

perliedman commented 12 years ago

This pull request adds support for other projections than EPSG:900913 to TileJSON.

Support is added by a number of optional properties. If these properties are left out, they default to values that give EPSG:900913, so these changes are backwards compatible.

As a proof of concept, I have also written Leaflet support for this version of TileJSON: https://github.com/perliedman/leaflet-tilejson

An online example is also shown here: http://karta.kartena.se/ - the default map layer is configured using this version of TileJSON, with maps using the EPSG:2400 projection (Swedish local projection RT90).

I have intentionally not changed the version of the spec, as I see this more as a proof of concept currently, and would very much like to hear your input on extending the spec to include support for other projections, if this is something you'd like to consider at all.

tmcw commented 12 years ago

A few comments:

Also, re your notes in leaflet-tilejson, have you seen Wax? It implements UTFGrid & multiple URL support for Leaflet.

perliedman commented 12 years ago

minZoom and maxZoom defines what zoom levels are actually available, while scales define the scales for zoom levels that might or might not be available; scales is always based from zoom level 0.

This can be important when you have two layers with partially overlapping zoom levels (layer A covers zoom levels 0-10, layer B covers 8-14) - level numbering must match, so you can't define only the scales (and obviously not only min/maxZoom).

Regarding which one "wins", none of them can - it's more like an invalid TileJSON if the scales aren't defined for all levels in the range 0 <= z < maxZoom. More scales is ok though, although the client might not use them.

The CRS name is a bit questionable to be honest, and it makes it perfectly possible to write an ambiguous TileJSON document. The reason to not only have the proj4 string is that it doesn't contain any "human readable" identifier of the projection, and it's really nice to have a fast way to see what projection is actually used. In this case, it's clear that the proj4 string wins over the CRS, which is just metadata. The CRS could be removed from the spec, and it would be up to each TileJSON author to put this information in the description or similar.

Regarding bounds: for many projections, a WGS84 bounding box will not align properly with a bounding box in the projected CRS. For example, we use EPSG:2400 a lot, where the y-axis isn't perfectly north-south aligned, and longitudes are curved. A bounding box in WGS84 can of course be a close approximation of the data coverage, but it will not be an exact representation of the data actually available.

I am aware of Wax, but haven't actually looked at it, which now that you mention it was probably a bit stupid. I guess I just assumed the code would be hard to extract or use without the rest of Wax (which we currently do not use). I should look into it more, now that you mention it.

tmcw commented 12 years ago
perliedman commented 12 years ago

Scales are decided when the tile set is created. For Google Maps, this is easy since there is an established convention defining the number of pixels to cover the dataset on level 0, and doubling for each zoom level. This is the part that ends up with 256 * 2 ^ zoom somewhere in the implementations of spherical mercator (for example: https://github.com/stamen/modestmaps-js/blob/master/src/coordinate.js#L47 or https://github.com/CloudMade/Leaflet/blob/master/src/geo/crs/CRS.js#L22 )

In the general case, typically with older tile sets, the scales might have been chosen manually and do not necessarily match a function; the resolution does not necessarily double for each level you zoom in. Because of this, it is necessary to list the scale values to use for each zoom level.

As a side not, the TMS specification, as I understand it, solves the same issue by defining level 0 as the most zoomed in level and requires it to be 1 pixel = 1 projected coordinate unit; each zoom level then doubles the scale / doubles the resolution for each level you zoom out. This has the disadvantage that you can't have zoom levels with more than one pixel per projected unit, and also forces you to use the factor 2 between each level, which might not be the case if you already have a tile set. It also turns the concept of zoom levels up side down (0 is most zoomed in), which I think is undesirable.

There might be other and better ways to express the scales and transformation parts. In the current form, they quite closely model what we had to do to get general projection support working in Leaflet, and as a consequence might be unnecessarily tightly coupled with how Leaflet handles projections and zoom levels / scales.

Regarding the bounds: I don't think a rectangle in WGS84 (compared to an axis aligned bounding box), if that is what you mean, solves the issue either, since you can't make the assumption that a straight line in WGS84 will map to a straight line in the projected CRS; as I mentioned, EPSG:2400 projects longitudinal lines to slightly curved lines, for example. It's really a question of what you want to use the bounds for: if it's just an indication of the general area where tiles are available, I'd say a WGS84 bounding box is fine, but if you want to express the exact area covered, there's really no alternative to using the bounding box in projected coordinates (IMHO).

The tiling scheme might also be a result of modelling to close to Leaflet: the scheme basically tells how the tile grids rows are arranged: "xyz" is the name Leaflet uses for the Google Maps/OSM way, meaning row 0 is at the top, and row coordinates grow as you go down. TMS uses the opposite, rows start at the bottom and grow upwards. If there is a better and perhaps more commonly used term than "xyz", I would be happy to use it.

perliedman commented 11 years ago

I realize I must have misread your last question.

AFAIK, there is no standard or real convention on how to do xyz tiling with other projections than spherical mercator. On the other hand, as I'm sure you already know, there are only a few sensible ways to do it. I've tried to document my thoughts on it and other issues regarding local projections in this blog post: http://blog.kartena.se/local-projections-in-a-world-of-spherical-mercator/

In short, a few of the properties I've added to the spec could probably be removed if there was an actual standard, and some of the complexity in my changes comes from the fact that multiple schemes must be supported.

It would be interesting to hear if you see any possibility of this being part of the spec, or at least if you have some suggestion on how to go about making it a more official extension to the spec (I realize you might not want to add local projection support for all TileJSON implementations).

As a proof of this being at least sort of usable, I might mention that we at Kartena have served most of our customers' maps using TileJSON with local projections for the last six months and have been very happy to avoid copying our tile configuration to every single solution.

tmcw commented 11 years ago

AFAIK, there is no standard or real convention on how to do xyz tiling with other projections than spherical mercator.

This is really part of the crux: unless TileJSON can communicate all needed information - the projection implementation, or the linear transformation between projected and unprojected points - then it's unlikely we'll support other projections, since we'll be relying on assumptions outside of the spec.

kkaefer commented 11 years ago

You could use +init=espg:900913 as proj string and drop crs alltogether.

springmeyer commented 11 years ago

@perliedman - thanks for your proposal and detailed thoughts. We use tilejson (and generally assume spherical mercator) throughout our stack of tools that handle serverside rendering as well as clientside. As Mapnik supports any projection (through proj4) it makes logical sense to give consideration to supporting more than mercator in TileMill/tilelive.js. Is this something you've also considered, or are you purely aiming for tilejson support in leaflet? What I'm getting at is that a fork of tilelive.js and/or tilemill that added support for your new tilejson parameters (even in their prototype state) might help move this conversation forward.

perliedman commented 11 years ago

@springmeyer I (or rather we at Kartena) have briefly considered it, but it got put on hold since we figured out that we could actually trick TileMill into designing maps in other projections, even though you have to get used to some odd behaviour that this results in. We then render them in Mapnik without problem. We've also extended TileStache with projection support (https://github.com/migurski/TileStache/pull/72) to complete our toolchain.

You are correct that our current use of TileJSON is mostly for easy and centralized configuration of Leaflet maps (and our own, old proprietary slippy map client). However, since we nowadays use TileMill more officially, I might be able to put some time into actually adding generalized projection support for TileMill; since it sort-of-works today, it might be in reach even if I have no experience with TileMill's or tilelive.js's codebases.

I might also be able to address some of the ambiguities in the spec proposal that @tmcw pointed out earlier now that I have some more experience with adding projection support to various tools. I'll keep you updated on my progress.

tmcw commented 8 years ago

Closing, this needs to happen after a standardized any-projection tiling spec, rather than before.

perliedman commented 8 years ago

:+1: Didn't realize this was open until now.

I think it's fine for this to live as an extension to TileJSON, since it's very unlikely that all tools supporting TileJSON would be interested in general projection support.

For anyone else finding this issue, wanting to use TileJSON with other projections, feel free to contact me, perhaps we could make a more official extension outside the TileJSON spec.

nigels-com commented 6 years ago

A more minimal variation of this would work well for some of our intended use cases. The crs tag to indicate the coordinate system, and an axis aligned box (in the given CRS) to define the location of the level 0 tile. If these tags are not specified, it's assumed to be crs=EPSG:3857 and box=-180.0 -85.06 180.0 85.06

See: epsg.io 3857

In this arrangement implementing a mixed-CRS map is a client-side problem.