I have developed an app for iOS using the Swift language. The app uses online map services, including WMTS, WMS, TMS, XYZ and makes heavy use of OpenLayers to display these. I would like to be display some of these maps offline and therefore need to be able to cache selected portions of them locally (ie, selected extent and selected range of zoom levels).

(Don't need to remind me about copyright or licensing issues - I have my own Geoserver service so it's fine for those maps at least, and the app will have warning to users of other map services.)

TMS Works

I can cache a portion of a TMS map service, and it works fine offline displayed from the cache. I essentially calculate which tiles are required, download them, and store them on the file system in the same heirarchy as they are in their online URLs. Then they can simply be called by substituting the "http://servername/.../" with "file:///.../" in the base service URL.

WMTS is Complicated

However, I'm having trouble doing the same thing for WMTS, which has a much more complicated Capabilities.xml. Additionally, WMTS GetTile URLs are not well suited to offline caching as they do not include "/" characters and can include characters that are inappropriate for filenames. So if I attempted to replicate a similar method to what I'm doing with TMS, all the tiles would end up in the same directory, making for very slow file system traversal and therefore inefficient reading of the tiles, plus I would have to work out some scheme for translating the file paths AND the Capabilities to avoid certain characters that don't play well with file names (eg, ":" is often used in WMTS tile paths, but is forbidden in iOS file names - if you see it in a file name, it is actually using a different character in the real file name, behind the scenes).

Proposed WMTS Work-Around

In order to work around these issues, I'm attempting to produce an offline TMS cache for the online WMTS service. This enables me to work with simple file names and a directory tree that doesn't result in excessive numbers of files in any one directory. I should then be able to feed the TMS cache to OpenLayers in the same way that I already do for my cached TMS services.

This is almost working. It downloads all of the right tiles, but the row numbers, column numbers, extent and origin are all different to what I get when I download from the equivalent TMS service from the same Geoserver, and the cached map doesn't display in OpenLayers.

Can anybody provide advice on how to convert online WMTS to an offline cache that I can feed into OpenLayers?

As an example, just one of the issues I'm struggling with is... how to convert WMTS' array of DIFFERENT origins ("TopLeftCorner") into a single TMS origin? (The origin-X-coordinate seems to be same as for TMS, but none of the WMTS origin Y-coordinates are anywhere near the TMS origin-Y coordinate for the same Geoserver map layer!)

I'd like to provide some code to show what I'm trying so far, but the nature of the beast is so complicated that it would require a prohibitively large base of code to be posted (and how many GIS people know Swift anyhow?).


Partial answer (see update below):

I can now cache WMTS to a local TMS-compatible file store which can be fed into OpenLayers. The local file store includes a 'plist' file which includes all the other information that needs to be read in and sent to OpenLayers (eg, SRS, extent, origin, etc).

Here are some of the key factors that I figured out for client-side conversion of an online WMTS service to a local TMS cache.

Y axis is reversed

The Y axis in in the opposite direction betwen WMTS and TMS. This affects all other items below, to some degree.

TMS Origin

This can be calculated from WMTS as:

TopLeftCorner.Y - ( TileHeight * RESOLUTION * MatrixHeight )

where most of the values come directly from the WMTS capablilities but the RESOLUTION has been pre-calculated (in this case taken from an OpenLayers WMTS 'options' object for the WMTS layer - presumably based on the WMTS Capabilities' ScaleDenominator, TileSize, etc).

Note that in practice, I actually used a different formula with the min/maxTileX/Y values from OpenLayers WMTS 'options' object, which includes negative values for the Y axis already and includes the first/last tile numbers instead of the number of tiles and requiring to calculate which is the first tile number.

TMS Extent

Using the TMS Origin calculated above, add to both axis a width and height based on the number of tiles at that zoom level and the resolution at that zoom level, which can be gleaned from WMTS Capabilities (or OpenLayers 'options' object).

TMS Tile Order

Of course the Y axis being reversed means that the tile numbering has to count down from the last tile at the same time as the WMTS tile numbering is counting up.

And the col/x and row/y numbers themselves must be calculated separately for both WMTS and TMS.


Turns out that my solution only works for some WMTS services and not for others. I have not had time to investigate further. So I have un-accepted this answer and and will have to get back to investigating the issue some time.

  • Would you happen to have any code, or more in-depth explanation? thanks! – Biaspoint Dec 30 '20 at 22:38
  • 1
    Not that I can share just yet. Turns out my solution works for some WMTS services, but not for others. I will un-accept this answer and update it a bit. – Son of a Beach Dec 30 '20 at 22:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.