/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @author Arne Kepp, OpenGeo, Copyright 2009 * */ package org.geowebcache.service.wmts; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.geowebcache.config.legends.LegendInfo; import org.geowebcache.config.legends.LegendInfoBuilder; import org.geowebcache.config.meta.ServiceContact; import org.geowebcache.config.meta.ServiceInformation; import org.geowebcache.config.meta.ServiceProvider; import org.geowebcache.conveyor.Conveyor.CacheResult; import org.geowebcache.filter.parameters.ParameterFilter; import org.geowebcache.grid.Grid; import org.geowebcache.grid.GridSet; import org.geowebcache.grid.GridSetBroker; import org.geowebcache.grid.GridSubset; import org.geowebcache.grid.SRS; import org.geowebcache.io.XMLBuilder; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.TileLayerDispatcher; import org.geowebcache.layer.meta.LayerMetaInformation; import org.geowebcache.layer.meta.MetadataURL; import org.geowebcache.mime.MimeType; import org.geowebcache.stats.RuntimeStats; import org.geowebcache.util.ServletUtils; import org.geowebcache.util.URLMangler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; public class WMTSGetCapabilities { private static Log log = LogFactory.getLog(WMTSGetCapabilities.class); private TileLayerDispatcher tld; private GridSetBroker gsb; private String baseUrl; private final Collection<WMTSExtension> extensions; protected WMTSGetCapabilities(TileLayerDispatcher tld, GridSetBroker gsb, HttpServletRequest servReq, String baseUrl, String contextPath, URLMangler urlMangler) { this(tld, gsb, servReq, baseUrl, contextPath, urlMangler, Collections.emptyList()); } protected WMTSGetCapabilities(TileLayerDispatcher tld, GridSetBroker gsb, HttpServletRequest servReq, String baseUrl, String contextPath, URLMangler urlMangler, Collection<WMTSExtension> extensions) { this.tld = tld; this.gsb = gsb; String forcedBaseUrl = ServletUtils.stringFromMap(servReq.getParameterMap(), servReq.getCharacterEncoding(), "base_url"); if(forcedBaseUrl!=null) { this.baseUrl = forcedBaseUrl; } else { this.baseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.SERVICE_PATH); } this.extensions = extensions; } protected void writeResponse(HttpServletResponse response, RuntimeStats stats) { final Charset encoding = StandardCharsets.UTF_8; byte[] data = generateGetCapabilities(encoding).getBytes(encoding); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/vnd.ogc.wms_xml"); response.setCharacterEncoding(encoding.name()); response.setContentLength(data.length); response.setHeader("content-disposition", "inline;filename=wmts-getcapabilities.xml"); stats.log(data.length, CacheResult.OTHER); try { OutputStream os = response.getOutputStream(); os.write(data); os.flush(); } catch (IOException ioe) { log.debug("Caught IOException" + ioe.getMessage()); } } private String generateGetCapabilities(Charset encoding) { StringBuilder str = new StringBuilder(); XMLBuilder xml = new XMLBuilder(str); try { xml.header("1.0", encoding); xml.indentElement("Capabilities"); xml.attribute("xmlns", "http://www.opengis.net/wmts/1.0"); xml.attribute("xmlns:ows", "http://www.opengis.net/ows/1.1"); xml.attribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); xml.attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); xml.attribute("xmlns:gml", "http://www.opengis.net/gml"); // allow extensions to register their names spaces for(WMTSExtension extension : extensions) { extension.registerNamespaces(xml); } StringBuilder schemasLocations = new StringBuilder("http://www.opengis.net/wmts/1.0 "); schemasLocations.append("http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd "); // allow extensions to register their schemas locations for(WMTSExtension extension : extensions) { for(String schemaLocation : extension.getSchemaLocations()) { schemasLocations.append(schemaLocation).append(" "); } } schemasLocations.delete(schemasLocations.length() -1 , schemasLocations.length()); // add schemas locations xml.attribute("xsi:schemaLocation", schemasLocations.toString()); xml.attribute("version", "1.0.0"); // There were some contradictions in the draft schema, haven't checked whether they've fixed those //str.append("xsi:schemaLocation=\"http://www.opengis.net/wmts/1.0 http://geowebcache.org/schema/opengis/wmts/1.0.0/wmtsGetCapabilities_response.xsd\"\n"); ServiceInformation serviceInformation = getServiceInformation(); serviceIdentification(xml, serviceInformation); serviceProvider(xml, serviceInformation); operationsMetadata(xml); contents(xml); xml.indentElement("ServiceMetadataURL") .attribute("xlink:href", baseUrl+"?REQUEST=getcapabilities&VERSION=1.0.0") .endElement(); xml.endElement("Capabilities"); return str.toString(); } catch (IOException e) { // Should not happen as StringBuilder doesn't throw throw new IllegalStateException(e); } } /** * Composes service information using information provided by extensions. */ private ServiceInformation getServiceInformation() { ServiceInformation servInfo = tld.getServiceInformation(); for (WMTSExtension extension : extensions) { ServiceInformation serviceInformation = extension.getServiceInformation(); if (serviceInformation != null) { if (servInfo == null) { servInfo = new ServiceInformation(); } mergeServiceInformation(servInfo, serviceInformation); } } return servInfo; } /** * Substitute serviceA information with no NULL information from serviceB. */ private void mergeServiceInformation(ServiceInformation serviceA, ServiceInformation serviceB) { if (serviceB.getTitle() != null) { serviceA.setTitle(serviceB.getTitle()); } if (serviceB.getDescription() != null) { serviceA.setDescription(serviceB.getDescription()); } if (serviceB.getKeywords() != null) { serviceA.getKeywords().addAll(serviceB.getKeywords()); } if (serviceB.getServiceProvider() != null) { serviceA.setServiceProvider(serviceB.getServiceProvider()); } if (serviceB.getFees() != null) { serviceA.setFees(serviceB.getFees()); } if (serviceB.getAccessConstraints() != null) { serviceA.setAccessConstraints(serviceB.getAccessConstraints()); } if (serviceB.getProviderName() != null) { serviceA.setProviderName(serviceB.getProviderName()); } if (serviceB.getProviderSite() != null) { serviceA.setProviderSite(serviceB.getProviderSite()); } if (serviceB.getServiceProvider() != null) { if(serviceA.getServiceProvider() != null) { mergeProviderInformation(serviceA.getServiceProvider(), serviceB.getServiceProvider()); } else { serviceA.setServiceProvider(serviceB.getServiceProvider()); } } } /** * Substitute providerA information with no NULL information from providerB. */ private void mergeProviderInformation(ServiceProvider providerA, ServiceProvider providerB) { if (providerB.getProviderName() != null) { providerA.setProviderName(providerB.getProviderName()); } if (providerB.getProviderSite() != null) { providerA.setProviderSite(providerB.getProviderSite()); } if (providerB.getServiceContact() != null) { if (providerA.getServiceContact() != null) { mergeContactInformation(providerA.getServiceContact(), providerB.getServiceContact()); } else { providerA.setServiceContact(providerB.getServiceContact()); } } } /** * Substitute contactA information with no NULL information from contactB. */ private void mergeContactInformation(ServiceContact contactA, ServiceContact contactB) { if (contactB.getIndividualName() != null) { contactA.setIndividualName(contactB.getIndividualName()); } if (contactB.getPositionName() != null) { contactA.setPositionName(contactB.getPositionName()); } if (contactB.getAddressType() != null) { contactA.setAddressType(contactB.getAddressType()); } if (contactB.getAddressStreet() != null) { contactA.setAddressStreet(contactB.getAddressStreet()); } if (contactB.getAddressCity() != null) { contactA.setAddressCity(contactB.getAddressCity()); } if (contactB.getAddressAdministrativeArea() != null) { contactA.setAddressAdministrativeArea(contactB.getAddressAdministrativeArea()); } if (contactB.getAddressPostalCode() != null) { contactA.setAddressPostalCode(contactB.getAddressPostalCode()); } if (contactB.getAddressCountry() != null) { contactA.setAddressCountry(contactB.getAddressCountry()); } if (contactB.getPhoneNumber() != null) { contactA.setPhoneNumber(contactB.getPhoneNumber()); } if (contactB.getFaxNumber() != null) { contactA.setFaxNumber(contactB.getFaxNumber()); } if (contactB.getAddressEmail() != null) { contactA.setAddressEmail(contactB.getAddressEmail()); } } private void serviceIdentification(XMLBuilder xml, ServiceInformation servInfo) throws IOException { xml.indentElement("ows:ServiceIdentification"); if (servInfo != null) { appendTag(xml, "ows:Title", servInfo.getTitle(), "Web Map Tile Service - GeoWebCache"); appendTag(xml, "ows:Abstract", servInfo.getDescription(), null); if (servInfo != null && servInfo.getKeywords() != null) { xml.indentElement("ows:Keywords"); Iterator<String> keywordIter = servInfo.getKeywords().iterator(); while(keywordIter.hasNext()) { appendTag(xml, "ows:Keyword", keywordIter.next(), null); } xml.endElement(); } } else { xml.simpleElement("ows:Title","Web Map Tile Service - GeoWebCache", true); } xml.simpleElement("ows:ServiceType","OGC WMTS", true); xml.simpleElement("ows:ServiceTypeVersion","1.0.0", true); if (servInfo != null) { appendTag(xml, "ows:Fees", servInfo.getFees(), null); appendTag(xml, "ows:AccessConstraints", servInfo.getAccessConstraints(), null); } xml.endElement("ows:ServiceIdentification"); } private void serviceProvider(XMLBuilder xml, ServiceInformation servInfo) throws IOException { ServiceProvider servProv = null; if(servInfo != null) { servProv = servInfo.getServiceProvider(); } xml.indentElement("ows:ServiceProvider"); if(servProv != null) { appendTag(xml, "ows:ProviderName", servProv.getProviderName(), null); if(servProv.getProviderSite() != null) { xml.indentElement("ows:ProviderSite").attribute("xlink:href", servProv.getProviderSite()).endElement(); } ServiceContact servCont = servProv.getServiceContact(); if(servCont != null) { xml.indentElement("ows:ServiceContact"); appendTag(xml, "ows:IndividualName", servCont.getIndividualName(), null); appendTag(xml, "ows:PositionName", servCont.getPositionName(), null); xml.indentElement("ows:ContactInfo"); if(servCont.getPhoneNumber() != null || servCont.getFaxNumber() != null) { xml.indentElement("ows:Phone"); appendTag(xml, "ows:Voice", servCont.getPhoneNumber(), null); appendTag(xml, "ows:Facsimile", servCont.getFaxNumber(), null); xml.endElement(); } xml.indentElement("ows:Address"); appendTag(xml, "ows:DeliveryPoint", servCont.getAddressStreet(), null); appendTag(xml, "ows:City", servCont.getAddressCity(), null); appendTag(xml, "ows:AdministrativeArea", servCont.getAddressAdministrativeArea(), null); appendTag(xml, "ows:PostalCode", servCont.getAddressPostalCode(), null); appendTag(xml, "ows:Country", servCont.getAddressCountry(), null); appendTag(xml, "ows:ElectronicMailAddress", servCont.getAddressEmail(), null); xml.endElement("ows:Address"); xml.endElement(); xml.endElement(); } } else { appendTag(xml, "ows:ProviderName", baseUrl, null); xml.indentElement("ows:ProviderSite").attribute("xlink:href", baseUrl).endElement(); xml.indentElement("ows:ServiceContact"); appendTag(xml, "ows:IndividualName", "GeoWebCache User", null); xml.endElement(); } xml.endElement("ows:ServiceProvider"); } private void operationsMetadata(XMLBuilder xml) throws IOException { xml.indentElement("ows:OperationsMetadata"); operation(xml, "GetCapabilities", baseUrl); operation(xml, "GetTile", baseUrl); operation(xml, "GetFeatureInfo", baseUrl); // allow extension to inject their own metadata for (WMTSExtension extension : extensions) { List<WMTSExtension.OperationMetadata> operationsMetaData = extension.getExtraOperationsMetadata(); if (operationsMetaData != null) { for (WMTSExtension.OperationMetadata operationMetadata : operationsMetaData) { operation(xml, operationMetadata.getName(), operationMetadata.getBaseUrl() == null ? baseUrl : operationMetadata.getBaseUrl()); } } extension.encodedOperationsMetadata(xml); } xml.endElement("ows:OperationsMetadata"); } private void operation(XMLBuilder xml, String operationName, String baseUrl) throws IOException { xml.indentElement("ows:Operation").attribute("name", operationName); xml.indentElement("ows:DCP"); xml.indentElement("ows:HTTP"); xml.indentElement("ows:Get").attribute("xlink:href", baseUrl+"?"); xml.indentElement("ows:Constraint").attribute("name", "GetEncoding"); xml.indentElement("ows:AllowedValues"); xml.simpleElement("ows:Value", "KVP", true); xml.endElement(); xml.endElement(); xml.endElement(); xml.endElement(); xml.endElement(); xml.endElement("ows:Operation"); } private void contents(XMLBuilder xml) throws IOException { xml.indentElement("Contents"); Iterable<TileLayer> iter = tld.getLayerList(); for (TileLayer layer : iter) { if (!layer.isEnabled() || !layer.isAdvertised()) { continue; } layer(xml, layer, baseUrl); } for (GridSet gset : gsb.getGridSets()) { tileMatrixSet(xml, gset); } xml.endElement("Contents"); } private void layer(XMLBuilder xml, TileLayer layer, String baseurl) throws IOException { xml.indentElement("Layer"); LayerMetaInformation layerMeta = layer.getMetaInformation(); if (layerMeta == null) { appendTag(xml, "ows:Title", layer.getName(), null); } else { appendTag(xml, "ows:Title", layerMeta.getTitle(), null); appendTag(xml, "ows:Abstract", layerMeta.getDescription(), null); } layerWGS84BoundingBox(xml, layer); appendTag(xml, "ows:Identifier", layer.getName(), null); if (layer.getMetadataURLs() != null) { for (MetadataURL metadataURL : layer.getMetadataURLs()) { xml.indentElement("MetadataURL"); xml.attribute("type", metadataURL.getType()); xml.simpleElement("Format", metadataURL.getFormat(), true); xml.indentElement("OnlineResource") .attribute("xmlns:xlink", "http://www.w3.org/1999/xlink") .attribute("xlink:type", "simple") .attribute("xlink:href", metadataURL.getUrl().toString()) .endElement(); xml.endElement(); } } // We need the filters for styles and dimensions List<ParameterFilter> filters = layer.getParameterFilters(); layerStyles(xml, layer, filters); layerFormats(xml, layer); layerInfoFormats(xml, layer); if(filters != null) { layerDimensions(xml, layer, filters); } layerGridSubSets(xml, layer); // TODO REST // str.append(" <ResourceURL format=\"image/png\" resourceType=\"tile\" template=\"http://www.maps.cat/wmts/BlueMarbleNextGeneration/default/BigWorldPixel/{TileMatrix}/{TileRow}/{TileCol}.png\"/>\n"); // allow extensions to contribute extra metadata to this layer for (WMTSExtension extension : extensions) { extension.encodeLayer(xml, layer); } xml.endElement("Layer"); } private void layerWGS84BoundingBox(XMLBuilder xml, TileLayer layer) throws IOException { GridSubset subset = layer.getGridSubsetForSRS(SRS.getEPSG4326()); if(subset != null) { double[] coords = subset.getOriginalExtent().getCoords(); xml.indentElement("ows:WGS84BoundingBox"); xml.simpleElement("ows:LowerCorner", coords[0]+" "+coords[1], true); xml.simpleElement("ows:UpperCorner", coords[2]+" "+coords[3], true); xml.endElement("ows:WGS84BoundingBox"); return; } subset = layer.getGridSubsetForSRS(SRS.getEPSG900913()); if(subset != null) { double[] coords = subset.getOriginalExtent().getCoords(); double originShift = 2 * Math.PI * 6378137 / 2.0; double mx = coords[0]; double my = coords[1]; double lon = (mx / originShift) * 180.0 ; double lat = (my / originShift) * 180.0 ; lat = 180 / Math.PI * (2 * Math.atan( Math.exp( lat * Math.PI / 180.0)) - Math.PI / 2.0); xml.indentElement("ows:WGS84BoundingBox"); xml.simpleElement("ows:LowerCorner", lon+" "+lat, true); mx = coords[2]; my = coords[3]; lon = (mx / originShift) * 180.0 ; lat = (my / originShift) * 180.0 ; lat = 180 / Math.PI * (2 * Math.atan( Math.exp( lat * Math.PI / 180.0)) - Math.PI / 2.0); xml.simpleElement("ows:UpperCorner", lon+" "+lat, true); xml.endElement("ows:WGS84BoundingBox"); return; } } /** * Helper method that get layer legends info by merging deprecated * legends info objects with the new ones. */ private Map<String, LegendInfo> getLegendsInfo(TileLayer layer) { Map<String, LegendInfo> legendsInfo = new HashMap<>(); for (Map.Entry<String, TileLayer.LegendInfo> entry : layer.getLegendsInfo().entrySet()) { // convert deprecated model to new model legendsInfo.put(entry.getKey(), new LegendInfoBuilder() .withWidth(entry.getValue().width) .withHeight(entry.getValue().height) .withFormat(entry.getValue().format) .withCompleteUrl(entry.getValue().legendUrl) .withStyleName(entry.getKey()) .build()); } // add the new legend info model objects legendsInfo.putAll(layer.getLayerLegendsInfo()); return legendsInfo; } private void layerStyles(XMLBuilder xml, TileLayer layer, List<ParameterFilter> filters) throws IOException { String defStyle = layer.getStyles(); Map<String, LegendInfo> legendsInfo = getLegendsInfo(layer); if(filters == null) { xml.indentElement("Style"); xml.attribute("isDefault", "true"); if(defStyle == null) { xml.simpleElement("ows:Identifier", "", true); } else { xml.simpleElement("ows:Identifier", TileLayer.encodeDimensionValue(defStyle), true); } encodeStyleLegendGraphic(xml, legendsInfo.get(defStyle)); xml.endElement("Style"); } else { ParameterFilter stylesFilter = null; Iterator<ParameterFilter> iter = filters.iterator(); while(stylesFilter == null && iter.hasNext()) { ParameterFilter filter = iter.next(); if(filter.getKey().equalsIgnoreCase("STYLES")) { stylesFilter = filter; } } List<String> legalStyles=null; if(stylesFilter != null) legalStyles = stylesFilter.getLegalValues(); if(legalStyles!=null && !legalStyles.isEmpty()) { // There's a style filter listing at least one value String defVal = stylesFilter.getDefaultValue(); if(defVal == null) { if(defStyle != null) { defVal = defStyle; } else { defVal = ""; } } for(String value:legalStyles) { xml.indentElement("Style"); if(value.equals(defVal)) { xml.attribute("isDefault", "true"); } xml.simpleElement("ows:Identifier", TileLayer.encodeDimensionValue(value), true); encodeStyleLegendGraphic(xml, legendsInfo.get(value)); xml.endElement(); } } else { // Couldn't get a list of styles so just say there's a default. xml.indentElement("Style"); xml.attribute("isDefault", "true"); xml.simpleElement("ows:Identifier", "", true); if (defStyle != null) { encodeStyleLegendGraphic(xml, legendsInfo.get(defStyle)); } xml.endElement(); } } } /** * XML encodes the provided legend information. If the provided information legend is NULL * nothing is done. */ private void encodeStyleLegendGraphic(XMLBuilder xml, LegendInfo legendInfo) throws IOException { if (legendInfo == null) { // nothing to do return; } xml.indentElement("LegendURL"); // validate mandatory attributes checkNotNull(legendInfo.getFormat(), "Legend format is mandatory in WMTS."); checkNotNull(legendInfo.getLegendUrl(), "Legend URL is mandatory in WMTS."); // add mandatory attributes xml.attribute("format", legendInfo.getFormat()); xml.attribute("xlink:href", legendInfo.getLegendUrl()); // add optional attributes if (legendInfo.getWidth() != null) { xml.attribute("width", String.valueOf(legendInfo.getWidth())); } if (legendInfo.getHeight() != null) { xml.attribute("height", String.valueOf(legendInfo.getHeight())); } if (legendInfo.getMinScale() != null) { xml.attribute("minScaleDenominator", String.valueOf(legendInfo.getMinScale())); } if (legendInfo.getMaxScale() != null) { xml.attribute("maxScaleDenominator", String.valueOf(legendInfo.getMaxScale())); } xml.endElement("LegendURL"); } private void layerFormats(XMLBuilder xml, TileLayer layer) throws IOException { Iterator<MimeType> mimeIter = layer.getMimeTypes().iterator(); while(mimeIter.hasNext()){ xml.simpleElement("Format", mimeIter.next().getFormat(), true); } } private void layerInfoFormats(XMLBuilder xml, TileLayer layer) throws IOException { if (layer.isQueryable()) { Iterator<MimeType> mimeIter = layer.getInfoMimeTypes().iterator(); while (mimeIter.hasNext()) { xml.simpleElement("InfoFormat", mimeIter.next().getFormat(), true); } } } private void layerDimensions(XMLBuilder xml, TileLayer layer, List<ParameterFilter> filters) throws IOException { Iterator<ParameterFilter> iter = filters.iterator(); while(iter.hasNext()) { ParameterFilter filter = iter.next(); if(! filter.getKey().equalsIgnoreCase("STYLES")) { List<String> values = filter.getLegalValues(); if(values != null) { dimensionDescription(xml, filter, values); } } } } private void dimensionDescription(XMLBuilder xml, ParameterFilter filter, List<String> values) throws IOException { xml.startElement("Dimension"); xml.simpleElement("Identifier", filter.getKey(), false); String defaultStr = TileLayer.encodeDimensionValue(filter.getDefaultValue()); xml.simpleElement("Default", defaultStr, false); Iterator<String> iter = values.iterator(); while(iter.hasNext()) { String value = TileLayer.encodeDimensionValue(iter.next()); xml.simpleElement("Value", value, false); } xml.endElement("Dimension"); } private void layerGridSubSets(XMLBuilder xml, TileLayer layer) throws IOException { for (String gridSetId : layer.getGridSubsets()) { GridSubset gridSubset = layer.getGridSubset(gridSetId); xml.indentElement("TileMatrixSetLink"); xml.simpleElement("TileMatrixSet", gridSubset.getName(), true); if (! gridSubset.fullGridSetCoverage()) { String[] levelNames = gridSubset.getGridNames(); long[][] wmtsLimits = gridSubset.getWMTSCoverages(); xml.indentElement("TileMatrixSetLimits"); for (int i = 0; i < levelNames.length; i++) { xml.indentElement("TileMatrixLimits"); xml.simpleElement("TileMatrix", levelNames[i], true); xml.simpleElement("MinTileRow", Long.toString(wmtsLimits[i][1]), true); xml.simpleElement("MaxTileRow", Long.toString(wmtsLimits[i][3]), true); xml.simpleElement("MinTileCol", Long.toString(wmtsLimits[i][0]), true); xml.simpleElement("MaxTileCol", Long.toString(wmtsLimits[i][2]), true); xml.endElement(); } xml.endElement(); } xml.endElement("TileMatrixSetLink"); } } private void tileMatrixSet(XMLBuilder xml, GridSet gridSet) throws IOException { xml.indentElement("TileMatrixSet"); xml.simpleElement("ows:Identifier", gridSet.getName(), true); // If the following is not good enough, please get in touch and we will try to fix it :) xml.simpleElement("ows:SupportedCRS", "urn:ogc:def:crs:EPSG::"+gridSet.getSrs().getNumber(), true); // TODO detect these str.append(" <WellKnownScaleSet>urn:ogc:def:wkss:GlobalCRS84Pixel</WellKnownScaleSet>\n"); Grid[] grids = gridSet.getGridLevels(); for(int i=0; i<grids.length; i++) { double[] tlCoordinates = gridSet.getOrderedTopLeftCorner(i); tileMatrix(xml, grids[i], tlCoordinates, gridSet.getTileWidth(), gridSet.getTileHeight(), gridSet.isScaleWarning()); } xml.endElement("TileMatrixSet"); } private void tileMatrix(XMLBuilder xml, Grid grid, double[] tlCoordinates, int tileWidth, int tileHeight, boolean scaleWarning) throws IOException { xml.indentElement("TileMatrix"); if(scaleWarning) { xml.simpleElement("ows:Abstract", "The grid was not well-defined, the scale therefore assumes 1m per map unit.", true); } xml.simpleElement("ows:Identifier", grid.getName(), true); xml.simpleElement("ScaleDenominator", Double.toString(grid.getScaleDenominator()), true); xml.indentElement("TopLeftCorner") .text(Double.toString(tlCoordinates[0])) .text(" ") .text(Double.toString(tlCoordinates[1])) .endElement(); xml.simpleElement("TileWidth", Integer.toString(tileWidth), true); xml.simpleElement("TileHeight", Integer.toString(tileHeight), true); xml.simpleElement("MatrixWidth", Long.toString(grid.getNumTilesWide()), true); xml.simpleElement("MatrixHeight", Long.toString(grid.getNumTilesHigh()), true); xml.endElement("TileMatrix"); } private void appendTag(XMLBuilder xml, String tagName, String value, String defaultValue) throws IOException { if(value == null) { if(defaultValue == null) return; else value = defaultValue; } xml.simpleElement(tagName, value, true); } }