getTile(z, x, y, pixelRatio, projection) {
    const sourceProjection = this.getProjection();
    if (
      projection &&
      ((sourceProjection && !equivalent(sourceProjection, projection)) ||
        this.transformMatrix)
    ) {
      return this.getReprojTile_(z, x, y, projection, sourceProjection);
    }

    const size = this.getTileSize(z);

    const sourceLoader = this.loader_;

    const controller = new AbortController();

    /**
     * @type {LoaderOptions}
     */
    const loaderOptions = {
      signal: controller.signal,
      crossOrigin: this.crossOrigin_,
    };

    const tileCoord = this.getTileCoordForTileUrlFunction([z, x, y]);
    if (!tileCoord) {
      return null;
    }

    const requestZ = tileCoord[0];
    const requestX = tileCoord[1];
    const requestY = tileCoord[2];
    const range = this.getTileGrid()?.getFullTileRange(requestZ);
    if (range) {
      loaderOptions.maxY = range.getHeight() - 1;
    }
    function loader() {
      return toPromise(function () {
        return sourceLoader(requestZ, requestX, requestY, loaderOptions);
      });
    }

    /**
     * @type {import("../DataTile.js").Options}
     */
    const options = Object.assign(
      {
        tileCoord: [z, x, y],
        loader: loader,
        size: size,
        controller: controller,
      },
      this.tileOptions,
    );

    const tile = /** @type {TileType} */ (
      /** @type {*} */ (new DataTile(options))
    );
    tile.key = this.getKey();
    tile.addEventListener(EventType.CHANGE, this.handleTileChange_);

    return tile;
  }
@param {number} z Tile coordinate z.
@param {number} x Tile coordinate x.
@param {number} y Tile coordinate y.
@param {number} pixelRatio Pixel ratio.
@param {import("../proj/Projection.js").default} [projection] Projection.
@return {TileType|null} Tile (or null if outside source extent).
@override