Skip to content

Instantly share code, notes, and snippets.

@Sumbera
Last active Feb 20, 2022
Embed
What would you like to do?
WMS overlay on MapBox-gl-js (0.5.2)

Quick and dirty test of the WMS capabilities of the new MapBox-gl-js 0.5.2 API. First of all, yes ! it is possible to overlay (legacy) WMS over the vector WebGL rendered base map and with right mouse click it rotates (try it)... however the way is not straightforward:

  • Needs some 'hacks' as current version of the API doesn't have enough events to supply custom URL before it is loaded. But check latest version of mapbox, it might have better support for this.

  • Another issue is that WMS server has to provide HTTP header with Access-Control-Allow-Origin:* to avoid WebGL CORS failure when loading image (gl.texImage2D). Usually WMS servers don't care about this, as for normal img tags CORS doesn't apply. Here WebGL has access to raw image data so WMS provider has to explicitly agree with this.

  • Build process of mapbox-gl-js tend to be as many other large js projects complicated, slow, complex. And specifically on Windows platform it is more difficult to get mapbox-gl-js install and build running then on Mac.

Code is documented to guide you through the process.

Calculate spherical mercator coordinates from tile coordinates:

  // -- rutine originaly found in GlobalMercator.js, simplified
 // -- calculates spherical mercator coordinates from tile coordinates
 function tileBounds(tx, ty, zoom, tileSize) {
    function pixelsToMeters(px, py, zoom) {
     var res = (2 * Math.PI * 6378137 / 256) / Math.pow(2, zoom),
         originShift = 2 * Math.PI * 6378137 / 2,
         x = px * res - originShift,
         y = py * res - originShift;
     return [Math.abs(x), Math.abs(y)];
     };
   var min = pixelsToMeters(tx * tileSize, ty * tileSize, zoom),
         max = pixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom);
return min.concat(max);
}

Subclassing source_loadTile:

 // -- save orig _loadTile function so we can call it later
 // -- there was no good pre-load event at mapbox API to get hooked and patch url
// -- we need to use undocumented _loadTile 
 var origFunc = sourceObj._loadTile;
    // -- replace _loadTile with own implementation
 sourceObj._loadTile = function (id) {
    // -- we have to patch sourceObj.url, dirty ! 
    // -- we basically change url on the fly with correct BBOX coordinates 
    // -- and leave rest on original _loadTile processing
     var origUrl =sourceObj.tiles[0]
                      .substring(0,sourceObj.tiles[0].indexOf('&BBOX'));
     var origUrl = origUrl +"&BBOX={mleft},{mbottom},{mright},{mtop}";
     sourceObj.tiles[0] = patchUrl(id, [origUrl]);
     // -- call original method
     return  origFunc.call(sourceObj, id);
 }

Screenshot

alt text

[enjoy] 3

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>WMS overlay with MabBox-gl-js (0.5.2)</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src="http://www.sumbera.com/gist/js/mapbox/dist/mapbox-gl.js"></script>
<link href="http://www.sumbera.com/gist/js/mapbox/dist/mapbox-gl.css" rel="stylesheet" />
<script src="http://www.sumbera.com/gist/js/mapbox/mbstyles/hybrid.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
// -- please use your own token from mapbox.com, I left it here so you can see combination of vector + WMS raster
mapboxgl.accessToken = 'pk.eyJ1IjoiZTMyYSIsImEiOiJRM1FiY2FRIn0.kSwNe2a3ft2ZDNgrMnVpJg';
stylingJSON.layers.push(
{
"id": "cadastral overlay",
"type": "raster",
"source": "simple-tiles",
"minzoom": 0,
"maxzoom": 22
});
var map = new mapboxgl.Map({
container: 'map', // container id
style: stylingJSON, //'http://www.sumbera.com/gist/js/mapbox/mbstyles/hybrid.json', // -- stylesheet location need for layer
center: [50.0863, 14.4124], // -- starting position chale's bridge, Prague
zoom: 16 // starting zoom
});
// -- we need to create source to enable sublassing
// -- use czech cadastral WMS service www.cuzk.cz
var sourceObj = new mapboxgl.Source({
"type": "raster",
"tiles": ["http://services.cuzk.cz/wms/wms.asp?FORMAT=image%2Fpng&TRANSPARENT=true&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&LAYERS=KN&SRS=EPSG%3A900913&WIDTH=256&HEIGHT=256&BBOX={mleft},{mbottom},{mright},{mtop}"],
"maxzoom": 20,
"tileSize": 256
});
// -- rutine originaly found in GlobalMercator.js, simplified
// -- calculates spherical mercator coordinates from tile coordinates
function tileBounds(tx, ty, zoom, tileSize) {
function pixelsToMeters(px, py, zoom) {
var res = (2 * Math.PI * 6378137 / 256) / Math.pow(2, zoom),
originShift = 2 * Math.PI * 6378137 / 2,
x = px * res - originShift,
y = py * res - originShift;
return [Math.abs(x), Math.abs(y)];
};
var min = pixelsToMeters(tx * tileSize, ty * tileSize, zoom),
max = pixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom);
return min.concat(max);
}
// -- copy of the mapbox source/tile_coord.js as we neeed to get x,y,z from tile
// -- and the TileCoord is closed in closure (no outside access )
function TileCoord_fromID(id) {
var z = id % 32, dim = 1 << z;
var xy = ((id - z) / 32);
var x = xy % dim, y = ((xy - x) / dim) % dim;
var w = Math.floor(xy / (dim * dim));
if (w % 2 !== 0) w = w * -1 - 1;
w /= 2;
return { z: z, x: x, y: y, w: w };
};
//-- copy from mapbox TileCoord.url
// -- will patch url with new template options
function patchUrl(id, urls) {
var pos = TileCoord_fromID(id);
// -- get sph.merc.bounds
var mbounds = tileBounds(pos.x, pos.y, pos.z, 256);
// -- we don't use anyway urls, only url[0] for now
return urls[(pos.x + pos.y) % urls.length]
.replace('{mleft}', mbounds[0])
.replace('{mbottom}', mbounds[3])
.replace('{mright}', mbounds[2])
.replace('{mtop}', mbounds[1]);
};
// -- save orig _loadTile function so we can call it later
// -- there was no good pre-load event at mapbox API to get hooked and patch url
// -- we need to use undocumented _loadTile
var origFunc = sourceObj._loadTile;
// -- replace _loadTile with own implementation
sourceObj._loadTile = function (id) {
// -- we have to patch sourceObj.url, dirty !
// -- we basically change url on the fly with correct BBOX coordinates and leave rest on original _loadTile processing
var origUrl = sourceObj.tiles[0].substring(0,sourceObj.tiles[0].indexOf('&BBOX'));
var origUrl = origUrl +"&BBOX={mleft},{mbottom},{mright},{mtop}";
sourceObj.tiles[0] = patchUrl(id, [origUrl]);
// -- call original method
return origFunc.call(sourceObj, id);
}
//-- add source to map
map.addSource('simple-tiles', sourceObj);
// -- couldn't find how to dynamically add layer for the raster, need to have it in the hybrid.json, see above and check bottom of the hybrid.json file
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment