async loadTile_(z, x, y, attempt) {
    const tileGrid = this.getTileGrid();
    const extent = tileGrid.getTileCoordExtent([z, x, y]);
    const tileSize = this.getTileSize(z);
    const projection = this.getProjection();

    /**
     * @type {ProcessRequest}
     */
    const body = {
      input: {
        bounds: {
          bbox: extent,
          properties: {crs: getProjectionIdentifier(projection)},
        },
        data: this.inputData_,
      },
      output: {
        width: tileSize[0],
        height: tileSize[1],
      },
      evalscript: this.evalscript_,
    };

    /**
     * @type {RequestInit}
     */
    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.token_}`,
        'Access-Control-Request-Headers': 'Retry-After',
      },
      body: JSON.stringify(body),
      credentials: 'include',
    };

    const response = await fetch(this.processUrl_, options);
    if (!response.ok) {
      if (response.status === 429 && attempt < maxRetries - 1) {
        // The Retry-After header includes unreasonable wait times, instead use exponential backoff.
        const retryAfter = baseDelay * 2 ** attempt;
        await delay(retryAfter);
        return this.loadTile_(x, y, z, attempt + 1);
      }
      throw new Error(`Failed to get tile: ${response.statusText}`);
    }

    return imageFromResponse(response);
  }
@param {number} z The z tile index.
@param {number} x The x tile index.
@param {number} y The y tile index.
@param {number} attempt The attempt number (starting with 1).  Incremented with retries.
@return {Promise<import('../DataTile.js').Data>} The composed tile data.
@private