The easy way to do this is to make WMTS requests instead of WMS requests since then all you need to do is specify the zoom level, row and column number to get a hit.
If you have to calculate the tile bounds you should fetch the WMTS capabilities document. Then under the layer you are asking for will be a TileMatrixSetLink
that tells you the projection and limits for each level of tiles.
<TileMatrixSetLink>
<TileMatrixSet>EPSG:4326</TileMatrixSet>
<TileMatrixSetLimits>
<TileMatrixLimits>
<TileMatrix>EPSG:4326:0</TileMatrix>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
<MinTileCol>0</MinTileCol>
<MaxTileCol>0</MaxTileCol>
</TileMatrixLimits>
<TileMatrixLimits>
<TileMatrix>EPSG:4326:1</TileMatrix>
<MinTileRow>0</MinTileRow>
<MaxTileRow>0</MaxTileRow>
<MinTileCol>0</MinTileCol>
<MaxTileCol>1</MaxTileCol>
</TileMatrixLimits>
<TileMatrixLimits>
[.....]
This gives you the row and column ranges for each zoom level, don't request a tile outside these limits. Then look for the TileMatrixSet
towards the end of the file that matches this name.
<TileMatrixSet>
<ows:Identifier>EPSG:4326</ows:Identifier>
<ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
<TileMatrix>
<ows:Identifier>EPSG:4326:0</ows:Identifier>
<ScaleDenominator>2.795411320143589E8</ScaleDenominator>
<TopLeftCorner>90.0 -180.0</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>2</MatrixWidth>
<MatrixHeight>1</MatrixHeight>
</TileMatrix>
<TileMatrix>
<ows:Identifier>EPSG:4326:1</ows:Identifier>
<ScaleDenominator>1.3977056600717944E8</ScaleDenominator>
<TopLeftCorner>90.0 -180.0</TopLeftCorner>
<TileWidth>256</TileWidth>
<TileHeight>256</TileHeight>
<MatrixWidth>4</MatrixWidth>
<MatrixHeight>2</MatrixHeight>
</TileMatrix>
[......]
Now for any zoom level, you can look up the origin of the grid and then calculate the bounds for any tile as:
pixelSize = scaleDenom * 2.8e-4
if CRS is degrees then / by 111319 else convert from metres if needed
tileWidth = 256 * pixelSize
tileHeight = 256 * pixelSize
minX = col * tileWidth + originX
maxY = originY - row * tileHeight
maxX = minX + tileWidth
minY = maxY - tileHeight
You can see a full implementation with axis order handling and all the other exciting issues from GeoTools.