It seems that all WMTS service that were suspicious in my question, were right; I was wrong.
World of CRS definitions is not so simple.
Actual axis order to be used while constructing TopLeftCorner, results from CRS definition.
It has been explained e.g. at http://www.geotoolkit.org/modules/referencing/faq.html#axisOrder.
For CRS like EPSG:3857, EPSG:32633, axis order is "natural", i.e. horizontal one first, then vertical one.
For CRS like geodetic EPSG:4326, and projected EPSG:2180, EPSG:3006, axis order is "reversed", i.e. vertical one first, then horizontal one.
Despite this fact, coordinates in BoundingBox and WGS84BoundingBox are always listed in "natural" succession.
Actual order of axes can be detected by examining appropriate part of WKT representation of given CRS containing AXIS tags, e.g.:
- EPSG:3857: AXIS["X",EAST],AXIS["Y",NORTH]
- EPSG:32633: AXIS["Easting",EAST],AXIS["Northing",NORTH]
EAST first, then NORTH - no swapping required
- EPSG:2180: AXIS["x",NORTH],AXIS["y",EAST]
- EPSG:3006: AXIS["x",NORTH],AXIS["y",EAST]
NORTH first, then EAST - swapping is required
For geodetic CRS, swapping from lat, lon order to lon (horizontal), lat (vertical) is always required.
Or maybe there are other geodetic CRS that use lon, lat order? There are no AXIS tags in WKT for EPSG:4326.
Other links in this matter:
Axis Order Policy Guidance
Posts containing 'axis order' - GIS Stack Exchange
Spent quite some time testing, and as usual, the solution is fairly simple once you know how Leaflet wants to work.
The solution is to specify a custom CRS with a custom transform function. This s where you can specify what each map unit/pixel represents, and this is what is used by Leaflet internally for all distance calculations and - well - transforms :) .
Leaflet does neither support changing CRS (Coordinate Reference System) of a map after its been initialized, nor does it support different CRS per layer. This means you have to specify this custom CRS before you initialize the map. Or you have to reinitialize the map when you want to load a different layer: If you need to do that, know that you can use "map.remove()" to properly remove an already initialized map (this function is not documented, so I had to search a bit).
Look at this fiddle for how I fixed the problem fiddle posted in the question:
https://jsfiddle.net/pdqavdup/2/
var factorx = 0.125
var factory = 0.125
L.CRS.pr = L.extend({}, L.CRS.Simple, {
projection: L.Projection.LonLat,
transformation: new L.Transformation(factorx, 0, -factory, 0),
// Changing the transformation is the key part, everything else is the same.
// By specifying a factor, you specify what distance in meters one pixel occupies (as it still is CRS.Simple in all other regards).
// In this case, I have a tile layer with 256px pieces, so Leaflet thinks it's only 256 meters wide.
// I know the map is supposed to be 2048x2048 meters, so I specify a factor of 0.125 to multiply in both directions.
// In the actual project, I compute all that from the gdal2tiles tilemapresources.xml,
// which gives the necessary information about tilesizes, total bounds and units-per-pixel at different levels.
// Scale, zoom and distance are entirely unchanged from CRS.Simple
scale: function(zoom) {
return Math.pow(2, zoom);
},
zoom: function(scale) {
return Math.log(scale) / Math.LN2;
},
distance: function(latlng1, latlng2) {
var dx = latlng2.lng - latlng1.lng,
dy = latlng2.lat - latlng1.lat;
return Math.sqrt(dx * dx + dy * dy);
},
infinite: true
});
var MAP = L.map('map', {
crs: L.CRS.pr
}).setView([0, 0], 2);
var mapheight = 2048;
var mapwidth = 2048;
var sw = MAP.unproject([0, mapheight], 4); // Level 4, because this is the level where meters-per-pixel is exactly 1
var ne = MAP.unproject([mapwidth, 0], 4);
var layerbounds = new L.LatLngBounds(sw, ne);
var mapname = "beirut"
var mapimage = L.tileLayer('http://tournament.realitymod.com/mapviewer/tiles/' + mapname + '/{z}/{x}/{y}.jpg', {
minZoom: 0,
maxZoom: 5,
bounds: layerbounds,
noWrap: true,
attribution: '<a href="http://tournament.realitymod.com/showthread.php?t=34254">Project Reality Tournament</a>'
})
mapimage.addTo(MAP);
L.control.scale({
imperial: false
}).addTo(MAP);
Best Answer
I've managed to calculate the resolutions using the following equation:
resolution = ScaleDenominator * PixelWidth
529.166666667 = 1889880.95238 * 0.00028