Search code examples
angulartypescriptopenlayers

An TileWMS -> TileLayer -> Map -> View based on a WMS `GetTile` request with openlayers only duplicates the same tile on the map


Until 2020, I was displaying an IGN map with Openlayers with this code:

import { Component, OnInit, Input } from '@angular/core';

import Map from 'ol/Map';
import View from 'ol/View';
import TileWMS from 'ol/source/TileWMS';
import * as olProj from 'ol/proj';
import TileLayer from 'ol/layer/Tile';

@Component({
  selector: 'carte',
  templateUrl: './carte.component.html',
  styleUrls: ['./carte.component.css']
})
export class CarteComponent implements OnInit {
  /** Carte Openlayers. */
  map: Map;
      
  /* Longitude. */
  @Input() longitude: number;
  
  /* Latitude. */
  @Input() latitude: number;
  
  /** Niveau de zoom initial. */
  @Input() zoom: number;

  constructor() {
  }

  ngOnInit(): void {
    var ign_source = new TileWMS({
      url: 'http://wxs.ign.fr/cdw20cou9clqe59fvn39rmcu/geoportail/r/wms',
      params: {'LAYERS': 'GEOGRAPHICALGRIDSYSTEMS.PLANIGN', 'TILED': false}
    });
    
    var ign = new TileLayer({
        source: ign_source
    });

    this.map = new Map({
        target: "carte_principale",
        
        view: new View({
            center: olProj.fromLonLat([this.longitude, this.latitude]),
            zoom: this.zoom
        })
    });
    
    this.map.addLayer(ign);
  }
}

Then IGN geoportail made big changes for months, and when it went back, I had to adapt my code to use it again.

It documentation looks to use GetTile requests to display the map, instead of GetMap it was suggesting to use before.
And according to it, I changed my code that way:

ngOnInit(): void {
  const planIGN =  new TileWMS( {
    url: 'https://wxs.ign.fr/decouverte/geoportail/wmts',

    params: {
      REQUEST: 'GetTile',
      VERSION: '1.0.0',
      LAYER: 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2',
      STYLE: 'normal',
      FORMAT: 'image/png',
      SERVICE: 'WMTS',
      TILEMATRIX: 14,
      TILEMATRIXSET: 'PM',
      TILECOL: 8180,
      TILEROW: 5905
    }});

  const ign = new TileLayer({
     source: planIGN
  });

  this.map = new Map({
     target: 'carte_principale',

     view: new View({
        center: olProj.fromLonLat([this.longitude, this.latitude]),
        zoom: this.zoom
     })
  });

  this.map.addLayer(ign);
}

It works... but the display isn't the one expected:

  • the same tile is duplicated along the map.
  • the latitude/longitude isn't resolved
  • the zoom doesn't work

The two last points, because I don't know how to parametrize the request correctly.

enter image description here

When I attempt that displaying with QGis (where the map is shown fine) using its network tool, it see that kind of requests exchanged, so I'm not so far:

https://wxs.ign.fr/decouverte/geoportail/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&STYLE=normal&FORMAT=image/png&TILEMATRIXSET=PM&TILEMATRIX=13&TILEROW=2946&TILECOL=4093

Openlayers is powerful, but often tricky for me:
what should I change to have a better behavior?


Epilog: after @Solomon and others replied to my question, here the code I've eventually produced and that worked:

import { Component, OnInit, Input } from '@angular/core';

import Map from 'ol/Map';
import View from 'ol/View';
import Tile from 'ol/layer/Tile';
import TileLayer from 'ol/layer/Tile';
import WMTS from 'ol/source/WMTS';
import WMTSTileGrid from 'ol/tilegrid/WMTS';

import {fromLonLat, get as getProjection} from 'ol/proj';
import {getWidth, getTopLeft} from 'ol/extent';

@Component({
  selector: 'carte',
  templateUrl: './carte.component.html',
  styleUrls: ['./carte.component.css']
})
export class CarteComponent implements OnInit {
  /** Carte Openlayers. */
  map: Map;

  /* Longitude. */
  @Input() longitude: number;

  /* Latitude. */
  @Input() latitude: number;

  /** Niveau de zoom initial. */
  @Input() zoom: number;

  /** Couche Plan IGN V2 */
  planIGNV2: TileLayer<WMTS> = this.couche('https://wxs.ign.fr/decouverte/geoportail/wmts', 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2',
    'EPSG:3857', 'image/png');

  /* Couche : Orthophotos
     orthoPhotos: TileLayer<WMTS> = this.couche('https://wxs.ign.fr/decouverte/geoportail/wmts', 'ORTHOIMAGERY.ORTHOPHOTOS',
     'EPSG:3857', 'image/jpeg')
  */

  ngOnInit() {
    const map = new Map({
      layers: [ this.planIGNV2 ],
      target: 'carte_principale', // id de l'élément HTML
      view: new View({
        center: fromLonLat([this.longitude, this.latitude]),
        zoom: this.zoom
      })
    });
  }

  /**
   * Crée une couche tuilée WMTS
   * @param url URL
   * @param layer Nom de la couche
   * @param projection Projection à utiliser pour calculer la grille de tuiles (exemple : EPSG:3857)
   * @param format Format d'image à utiliser.
   */
  couche(url: string, layer: string, projection: string, format: string): TileLayer<WMTS> {
    return new Tile({
      source : new WMTS({
        url,
        layer,
        matrixSet: 'PM',
        format,
        style: 'normal',
        tileGrid: this.grilleTuiles(projection)
      })
    });
  }

  /**
   * Renvoie une grille de tuiles
   * @param projection Projection (exemple : EPSG:3857)
   */
  grilleTuiles(projection: string): WMTSTileGrid {
    const proj = getProjection(projection);
    const maxResolution = getWidth(proj.getExtent()) / 256;
    const projectionExtent = proj.getExtent();

    const resolutions = [];
    const matrixIds = [];

    for (let i = 0; i < 20; i++) {
      matrixIds[i] = i.toString();
      resolutions[i] = maxResolution / Math.pow(2, i);
    }

    return new WMTSTileGrid( {
      origin: getTopLeft(projectionExtent), // coin supérieur gauche
      resolutions,
      matrixIds
    });
  }
}

Solution

  • I believe you are not doing this correctly at all. You are just asking for one single tile, the one located at TILECOL: 8180, TILEROW: 5905

    Make use of this code:

            const projection = ol.proj.get('EPSG:4326');
            const projectionExtent = projection.getExtent();
            // The matrixes start at 2 tiles wide, 1 tile high (OR WHATEVER YOU HAVE IN THE API DOCUMENTATION)
            const size = ol.extent.getWidth(projectionExtent) / 256 / 2; // or ol.extent.getHeight(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: <your_url_tile_provider>
                    projection: projection,
                    layer: <your layer name>,
                    matrixSet: 'EPSG:4326',
                    format: <your format>,
                    style: <your style>,
                    tileGrid : new ol.tilegrid.WMTS({
                        origin: ol.extent.getTopLeft(projectionExtent), // topLeftCorner
                        resolutions: resolutions,
                        matrixIds: matrixIds
                    }),
                }),
                opacity: 0.7, <whatever you want>
            }));