You can work it out from the origin of the tile matrix + the tile X (or Y) times the tile size.
So given a tile matrix set:
<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>
A tile in the first level EPSG:4326:0
with a X = 1
and Y = 0
will have it's top left corner at (-180 + tilesize*X, 90 - tilesize *Y)
and tilesize
is 256 * 2.8e-4 / 111319
where 111319
is a magic number for the number of metres in 60 Nautical Miles because we are using degrees here, for a projected CRS it would be the number of metres per unit of the projection.
Here's the same maths in Java:
private static final double PixelSizeMeters = 0.28e-3;
public static ReferencedEnvelope getExtentFromTileName(
WMTSTileIdentifier tileIdentifier, TileService service) {
WMTSZoomLevel zl = new WMTSZoomLevel(tileIdentifier.getZ(), (WMTSTileService) service);
TileMatrix tileMatrix =
((WMTSTileService) service).getMatrixSet().getMatrices().get(zl.getZoomLevel());
CoordinateReferenceSystem crs = tileMatrix.getCrs();
CoordinateSystem coordinateSystem = crs.getCoordinateSystem();
double pixelSpan = getPixelSpan(tileMatrix);
double tileSpanY = (tileMatrix.getTileHeight() * pixelSpan);
double tileSpanX = (tileMatrix.getTileWidth() * pixelSpan);
double tileMatrixMinX;
double tileMatrixMaxY;
boolean longFirst = coordinateSystem.getAxis(0).getDirection().equals(AxisDirection.EAST);
if (longFirst) {
tileMatrixMinX = tileMatrix.getTopLeft().getX();
tileMatrixMaxY = tileMatrix.getTopLeft().getY();
} else {
tileMatrixMaxY = tileMatrix.getTopLeft().getX();
tileMatrixMinX = tileMatrix.getTopLeft().getY();
}
ReferencedEnvelope ret = new ReferencedEnvelope(crs);
double minX = tileIdentifier.getX() * tileSpanX + tileMatrixMinX;
double maxY = tileMatrixMaxY - tileIdentifier.getY() * tileSpanY;
double maxX = minX + tileSpanX;
double minY = maxY - tileSpanY;
if (longFirst) {
ret.expandToInclude(minX, minY);
ret.expandToInclude(maxX, maxY);
} else {
ret.expandToInclude(minY, minX);
ret.expandToInclude(maxY, maxX);
}
return ret;
}
/** */
private static double getPixelSpan(TileMatrix tileMatrix) {
CoordinateSystem coordinateSystem = tileMatrix.getCrs().getCoordinateSystem();
@SuppressWarnings("unchecked")
Unit<Length> unit = (Unit<Length>) coordinateSystem.getAxis(0).getUnit();
// now divide by meters per unit!
double pixelSpan = tileMatrix.getDenominator() * PixelSizeMeters;
if (unit.equals(NonSI.DEGREE_ANGLE)) {
/*
* use the length of a degree at the equator = 60 nautical miles!
* unit = USCustomary.NAUTICAL_MILE; UnitConverter metersperunit =
* unit.getConverterTo(SI.METRE); pixelSpan /=
* metersperunit.convert(60.0);
*/
// constant value from
// https://msi.nga.mil/MSISiteContent/StaticFiles/Calculators/degree.html
// apparently - 60.10764611706782 NaMiles
pixelSpan /= 111319;
} else {
UnitConverter metersperunit = unit.getConverterTo(SI.METRE);
pixelSpan /= metersperunit.convert(1);
}
return pixelSpan;
}