constructor(options) {
    /**
     * @type {Options}
     */
    options = options || {};
    const template = options.template || 'z:{z} x:{x} y:{y}';
    const source = options.source;
    const color = options.color || 'grey';

    super({
      transition: 0,
      wrapX:
        options.wrapX !== undefined
          ? options.wrapX
          : source !== undefined
            ? source.getWrapX()
            : undefined,
    });

    const setReady = () => {
      this.projection =
        options.projection !== undefined
          ? getProjection(options.projection)
          : source !== undefined
            ? source.getProjection()
            : this.projection;
      this.tileGrid =
        options.tileGrid !== undefined
          ? options.tileGrid
          : source !== undefined
            ? source.getTileGrid()
            : this.tileGrid;
      this.zDirection =
        options.zDirection !== undefined
          ? options.zDirection
          : source !== undefined
            ? source.zDirection
            : this.zDirection;

      if (source instanceof DataTile) {
        this.transformMatrix = source.transformMatrix?.slice() || null;
      }

      const tileGrid = this.tileGrid;
      if (tileGrid) {
        this.setTileSizes(
          tileGrid
            .getResolutions()
            .map((r, i) =>
              toSize(tileGrid.getTileSize(i)).map((s) =>
                Math.max(Math.floor(s), 1),
              ),
            ),
        );
      }

      this.setLoader((z, x, y, loaderOptions) => {
        const text = renderXYZTemplate(template, z, x, y, loaderOptions.maxY);
        const [width, height] = this.getTileSize(z);
        const context = createCanvasContext2D(width, height);

        context.strokeStyle = color;
        context.strokeRect(0.5, 0.5, width + 0.5, height + 0.5);

        context.fillStyle = color;
        context.strokeStyle = 'white';
        context.textAlign = 'center';
        context.textBaseline = 'middle';
        context.font = '24px sans-serif';
        context.lineWidth = 4;
        context.strokeText(text, width / 2, height / 2, width);
        context.fillText(text, width / 2, height / 2, width);
        // make the loader aysnc, so it behaves like other sources that fetch data from a remote server
        return Promise.resolve(context.canvas);
      });
      this.setState('ready');
    };

    if (source === undefined || source.getState() === 'ready') {
      setReady();
    } else {
      const handler = () => {
        if (source.getState() === 'ready') {
          source.removeEventListener(EventType.CHANGE, handler);
          setReady();
        }
      };
      source.addEventListener(EventType.CHANGE, handler);
    }
  }
@param {Options} [options] Debug tile options.