Search code examples
javascriptleafletgisproj4js

Vertical alignment of TMS tiles in Leaflet using EPSG:25832 projection


I am using Leaflet with Proj4Leaflet to work with tiles in 25832. The application is fairly simple: I am trying to overlay tiles in EPSG:25832 onto a omniscale basemap. I have copied the individual resolutions and origin from the tilemap meta information. The problem I am facing is that the map is not aligned and once I zoom in the tiles are not placed in the correct order. I'd appreciate any kind of support here (by the way, this is a working example which is using openlayers).

I guess I am doing something wrong here:

// Set resolutions
var resolutions = [156367.7919628329,78183.89598141646,39091.94799070823,19545.973995354114,9772.986997677057,4886.4934988385285,2443.2467494192642,1221.6233747096321,610.8116873548161,305.40584367740803,152.70292183870401,76.35146091935201,38.175730459676004,19.087865229838002,9.543932614919001,4.7719663074595005,2.3859831537297502,1.1929915768648751];

// Define CRS
var rs25832 = new L.Proj.CRS(
    'EPSG:25832',
    proj4rs25832def, 
    {
        origin: [ 273211.2532533697, 6111822.37943825 ],
        resolutions: resolutions
    }
);

...using the tiles information from https://mapproxy.bba.atenekom.eu/tms/1.0.0/privat_alle_50_mbit/germany .

Afterwards I add a tile layer

var url = 'https://mapproxy.bba.atenekom.eu/tms/1.0.0/privat_alle_50_mbit/germany/{z}/{x}/{y}.png';  

var tileLayer = L.tileLayer(
    url, 
    {
        tms: true,
        crs: rs25832,
        continuousWorld: true,
        maxZoom: resolutions.length
    }
);

And add them to the map..

// Setup map
var map = L.map('map', {
    crs: rs25832,
    center: [ 50.8805, 7.3389 ],
    zoom:5,
    maxZoom: resolutions.length,
    layers: [ baseWms, tileLayer ]
});

The bare minimum of code can be found here: https://jsfiddle.net/6gcam7w5/8/

incorrect alignment


Solution

  • This boils down to how the Y coordinate of TMS tiles is inverted (it becomes higher when going north, as opposed to default TileLayers, in which the Y coordinate becomes larger when going south).

    Having a look on the Leaflet code that takes care of this specific feature will shed some light on the issue:

        if (this._map && !this._map.options.crs.infinite) {
            var invertedY = this._globalTileRange.max.y - coords.y;
            if (this.options.tms) {
                data['y'] = invertedY;
            }
            data['-y'] = invertedY;
        }
    

    There are two things critical to calculating the right Y coordinate for your tiles here:

    • The CRS must be finite (it must have bounds)
    • There must be a finite global tile range (which in Leaflet is ultimately defined by the CRS bounds and not the TileLayer bounds)

    Long story short, your CRS should be defined with known bounds. For this particular case, taking information from the TMS capabilities document...

    <BoundingBox minx="273211.2532533697" miny="5200000.0" maxx="961083.6232988155" maxy="6111822.37943825"/>
    

    ...and turned into a L.Bounds definition when defining the Leaflet CRS, like...

    // Define CRS
    var rs25832 = new L.Proj.CRS(
        'EPSG:25832',
        proj4rs25832def, 
        {
            origin: [ 273211.2532533697, 6111822.37943825 ],
            resolutions: resolutions,
            bounds: [[273211.2532533697, 5200000],[961083.6232988155, 6111822.37943825]]
        }
    );
    

    Stuff should just work (with no need to pass the CRS to the tilelayers, since they will all use the map's), as in this working example.