Search code examples
openlayersopenlayers-6

WMTS layer on Openlayers 6 not displaying correctly


I don't have a lot of experience with WMTS layers, so I am trying my best here.

I was able to show the image but doesn't align correctly. If you need more information don't hesitate to let me know.

Openlayers version: 6.12

The API I am using is described here: https://www.metoffice.gov.uk/services/data/datapoint/inspire-layers-detailed-documentation

I get the capabilities and here is a sample: https://jpst.it/2MHNV

This is the map that I am using and the projection:

map = new ol.Map({
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM(),
            projection: 'EPSG:4326',
            // visible: false
        }),
    ],
    target: 'map',
    view: new ol.View({
        projection: 'EPSG:4326',
        center: ['-0.036220550537109375', '51.63796305656433'], // lonlat
        zoom: 3,
    })
});

And this is the code for adding this WMTS layer:

const projection = map.getView().getProjection('EPSG:4326');
const projectionExtent = projection.getExtent();
const size = ol.extent.getWidth(projectionExtent) / 256;
const resolutions = new Array(12);
const matrixIds = new Array(12);

for (let z = 0; z < 12; ++z) {
   // generate resolutions and matrixIds arrays for this WMTS
   resolutions[z] = size / Math.pow(2, z);
   matrixIds[z] = "EPSG:4326:" + z;
}

map.addLayer(new ol.layer.Tile({
        source: new ol.source.WMTS({
            url: `${LINK}?key=${inspireKEY}`,    // http://datapoint.metoffice.gov.uk/public/data/inspire/view/wmts
            projection: projection,
            layer: LAYER,    // image/png
            matrixSet: 'EPSG:4326', 
            format: FORMAT,    // RADAR_UK_Composite_Highres
            style: STYLE,    // Bitmap 1km Blue-Pale blue gradient 0.01 to 32mm/hr
            tileGrid : new ol.tilegrid.WMTS({
                origin: ol.extent.getTopLeft(projectionExtent), // topLeftCorner  [-180, 90]
                resolutions: resolutions,    // [1.40625, 0.703125, 0.3515625, 0.17578125, 0.087890625, 0.0439453125, 0.02197265625, 0.010986328125, 0.0054931640625, 0.00274658203125, 0.001373291015625, 0.0006866455078125]
                matrixIds: matrixIds    // ['EPSG:4326:0', 'EPSG:4326:1', 'EPSG:4326:2', 'EPSG:4326:3', 'EPSG:4326:4', 'EPSG:4326:5', 'EPSG:4326:6', 'EPSG:4326:7', 'EPSG:4326:8', 'EPSG:4326:9', 'EPSG:4326:10', 'EPSG:4326:11']
            })
        }),
        opacity: 0.7,
}));

And this is the result which doesn't align correctly:

enter image description here

Chrome example payload for request 1 tile

enter image description here


Solution

  • The resolutions are wrong. The matrixes start at 2 tiles wide, 1 tile high:

            "Identifier": "EPSG:4326:0",
            "ScaleDenominator": 2511.1607142857147,
            "TopLeftCorner": [
              -180,
              90
            ],
            "TileWidth": 256,
            "TileHeight": 256,
            "MatrixWidth": 2,
            "MatrixHeight": 1
    

    So to calculate the resolutions you need either

    const size = ol.extent.getWidth(projectionExtent) / 256 / 2;
    

    or

    const size = ol.extent.getHeight(projectionExtent) / 256;
    

    To display in EPSG:3857 make the view projection EPSG:3857 and use EPSG:4326 only for the WMTS definition

        view: new ol.View({
            projection: 'EPSG:3857',
            center: ol.proj.fromLonLat([-0.036220550537109375, 51.63796305656433]),
            zoom: 3,
        })
    });
    
    const projection = ol.proj.get('EPSG:4326');
    

    To remove the grey (where the RGB values seem to always be [199,191,193]) you could wrap in WMTS source inside an ol.source.Raster and set those pixels transparent

    map.addLayer(new ol.layer.Image({
        source: new ol.source.Raster({
            sources: [
                new ol.source.WMTS({
                    ...
                    ...
                    crossOrigin: ''
                })
            ],
            operation: function (pixels, data) {
                const pixel = pixels[0];
                if (pixel[0] == 199 && pixel[1] == 191 && pixel[2] == 193) {
                    pixel[3] = 0;
                }
                return pixel;
            }
        }),
        opacity: 0.7,
    }));
    

    That would need cross origin access - the data is hosted on http so it would not work if your application is hosted on https. Also there is a bug in OpenLayers 6.13 which causes flickering, so revert to 6.12.