After three weeks of learning about web maps I finally found answers to all of
my questions. They will be more in the form of tutorial since this may help some
other beginner.
First I learned how standard tiling for EPSG:3857 works. It's nicely explained on
http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/.
Key thing is zoom factor (z). At zoom level 0 whole world is one 256x256 tile. Coordinate
origin is at the center of the tile (Greenwich at equator), vertical coordinates
(y) going up
to 20037508.342789244 and down to -20037508.342789244, horizontal coordinates (x)
going left to -20037508.342789244
and right to 20037508.342789244. At zoom level 1 there are 4 equal tiles (2x2),
at zoom level 2 there are 16 equal tiles (4x4) and so on. Tiles are numbered from
upper left corner right and down. Upper left is (0,0), first right is (1,0), first
down is (0,1) and so on. Generally at zoom level z there are 2z rows
and columns of tiles,
tile (2z/2, 2z/2 - 1) has upper left corner at coordinates
(0,0). Another nice tutorial on map tiling systems:
http://www.liedman.net/tiled-maps/.
Now I had to figure out tiling system of my tiles. One tile covers 128 x 128
m area. Since number of tile rows and columns at zoom level z is 2z,
I had to find z for which tiles cover whole Slovenia, which covers roughly 260 x 163
km. With z=11 I got 2048 rows/columns covering area of 262.144 x 262.144 km.
Now the question was how to handle my tiles, which are based on different
CRS, namely ellipsoidal transverse Mercator EPSG:3912 (old Slovenian D48, which is local Slovenian Gauß-Krüger projection).
So my tiles have origin, zoom and projection that differ from EPSG:3857. I found
Leaflet plugin
Proj4Leaflet which "allows you to use all kinds of weird projections in
Leaflet".
To use the Proj4Leaflet plugin I needed the folowing data:
- EPSG:3912 decription in proj4js format: "+proj=tmerc +lat_0=0 +lon_0=15
+k=0.9999 +x_0=500000 +y_0=-5000000 +ellps=bessel
+towgs84=682,-203,480,0,0,0,0 +units=m +no_defs" (which I found here:
http://epsg.io/3912)
- Map area origin. To calculate it I took a reference tile for which I
had original row and column numbers and projected coordinates of upper left
corner: refRow, refCol, refX and refY. Coordinates of origin (upper left
corner), which has row and column number 0, are then origX = refX - refCol*128
and origY = refY - refRow*128, assuming that original tiling division was the
same as the new one (which luckily proved to be so). Calculated origin was
(368000, 243144).
- Pixel/m resolutions
for all zoom levels. Since at higest zoom level 11 on pixel was 0.5m, the rest
were calculate by multiplying by 2 and the result was: [1024, 512, 256, 128,
64, 32, 16, 8, 4, 2, 1, 0.5]
Creation of the map with my custom CRS and for standard tile naming then
looked like this:
var crs3912 = new L.Proj.CRS('EPSG:3912',
'+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000
+ellps=bessel +towgs84=682,-203,480,0,0,0,0 +units=m +no_defs',
{
origin: [368000, 243144],
resolutions: [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5],
});
var map = L.map('map',
crs: crs3912,
});
var myLayer = new L.TileLayer("tiles/{z}/{y}/{x}.jpg",
tileSize: 256,
maxZoom: 11,
minZoom: 11,
attribution: '© <a href="http://www.gu.gov.si/">GURS</a>'
});
map.addLayer(myLayer);
map.setView([46.047711, 14.507136], 11);
Tile naming of my tiles is completely different, so I just renamed a few of
them to standard naming to test the above solution and it worked correctly.
The next question was would it be possible to use my tiles without renaming
them. Their naming scheme is "tiles/ortofoto5000/{y}/SI{x}{y}.jpg", where x and
y are tile coordinates in three digit hexadecimal format and y is in reverse order (from bottom up). I found the solution
to this problem here
https://stackoverflow.com/questions/43826338/leaflet-custom-url-custom-tiles
and here
http://leafletjs.com/examples/extending/extending-2-layers.html.
Creation of the map now
looked like this:
L.TileLayer.MyCustomLayerClass = L.TileLayer.extend({
getTileUrl: function(coords) {
coords.x = ('00' + (coords.x).toString(16)).slice(-3);
coords.y = ('00' + (2047 - coords.y).toString(16)).slice(-3);
return L.TileLayer.prototype.getTileUrl.call(this, coords);
}
});
L.tileLayer.myCustomLayer = function(templateUrl, options) {
return new L.TileLayer.MyCustomLayerClass(templateUrl, options);
}
var crs3912 = new L.Proj.CRS('EPSG:3912',
"+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000
+ellps=bessel +towgs84=682,-203,480,0,0,0,0 +units=m +no_defs",
{
origin: [368000, 243144],
resolutions: [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5],
});
var map = L.map('map', {
crs: crs3912,
});
var myLayer = L.tileLayer.myCustomLayer("tiles/ortofoto5000/{y}/SI{x}{y}.jpg", {
tileSize: 256,
maxZoom: 11,
minZoom: 11,
attribution: '© <a href="http://www.gu.gov.si/">GURS</a>'
});
map.addLayer(myLayer);
map.setView([46.180527, 14.507304], 11);
And now the final challenge: would this be possible also with original tiles
available on the web and for all the zoom levels? Tiling scheme of the original
tiles on the web is quite complex. I managed to decipher it with the help of
Telerik Fiddler web debugging tool (http://www.telerik.com/fiddler).
I just went through all the zoom levels and watched web requests.
So here is then the final solution:
L.TileLayer.MyCustomLayerClass = L.TileLayer.extend({
getTileUrl: function(coords) {
var tileS;
var tileX;
var tileY;
var tileZ;
var tileName;
var hexX;
var hexY;
var hexXhexY;
tileS = this._getSubdomain(coords);
tileX = coords.x;
tileY = (2048 / Math.pow(2, 11 - coords.z)) - coords.y - 1;
tileZ = 'S' + '789ABCDEFGHI'.charAt(coords.z);
switch (Math.ceil(coords.z / 4)) {
case 3:
var hexX = ('00' + (tileX.toString(16)).toUpperCase()).slice(-3);
var hexY = ('00' + (tileY.toString(16)).toUpperCase()).slice(-3);
var hexXhexY = hexX + hexY;
var tileName = hexXhexY.substr(0, 2) + '/' + hexXhexY.substr(2, 2) + '/' + tileZ
+ hexXhexY + '.jpg';
break;
case 2:
var hexX = ('0' + (tileX.toString(16)).toUpperCase()).slice(-2);
var hexY = ('0' + (tileY.toString(16)).toUpperCase()).slice(-2);
var hexXhexY = hexX + hexY;
var tileName = hexXhexY.substr(0, 2) + '/' + tileZ + hexXhexY + '.jpg';
break;
default:
var hexX = (tileX.toString(16)).toUpperCase();
var hexY = (tileY.toString(16)).toUpperCase();
var tileName = tileZ + hexX + hexY + '.jpg';
}
return 'http://gpcl' + tileS +
'.geopedia.si/v1/AUTH_d7e1266c-6b4e-4629-91e6-17d4b370846d/gurs.dof.50cm.2011.epsg:3912/'
+ tileZ + '/' + tileName;
}
});
L.tileLayer.myCustomLayer = function(templateUrl, options)
return new L.TileLayer.MyCustomLayerClass(templateUrl, options);
}
var crs3912 = new L.Proj.CRS('EPSG:3912',
'+proj=tmerc +lat_0=0 +lon_0=15 +k=0.9999 +x_0=500000 +y_0=-5000000
+ellps=bessel +towgs84=682,-203,480,0,0,0,0 +units=m +no_defs',
{
origin: [368000, 243144],
resolutions: [1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5],
});
var map = L.map('map', {
crs: crs3912,
});
var myLayer = L.tileLayer.myCustomLayer("", {
subdomains: ['01', '02', '03', '04', '05', '06', '07', '08', '09'],
tileSize: 256,
maxZoom: 11,
minZoom: 0,
attribution: '© <a href="http://www.gu.gov.si/">GURS</a>'
});
map.addLayer(myLayer);
map.setView([46.047711, 14.507136], 11);
Working example is available at jsfiddle
https://jsfiddle.net/TomazicM/vfefa69x/. Example includes also display of
tile grid and zoom level and projected coordinates, which are useful for
debugging purposes.
Tile grid is displyed with the help of the following CSS modification:
.leaflet-tile {
border: solid red 1px;
}
Zoom level and projected coordinates are displayed with the help of
Leaflet.Coordinates
plugin.
I also had to learn how to include those github resources into jsfiddle
example that do not give the right mime type when referenced. I found the
solution here
https://blog.radix.cc/using-rawgit-to-serve-files-a2e4acad7f2d.
Finnaly I tried this solution on Android 7.0. I simply copied the examples with the resources to Android folder /sdcard/maps/LeafletExamples and called examples in browser by file:///sdcard/maps/LeafletExamples/ExampleN.html. It worked in Chrome and Firefox.