From 79335dd58273e5999ac1d8a9c816ca4dfd84d56e Mon Sep 17 00:00:00 2001 From: Vaclav Blazek Date: Fri, 5 Apr 2019 20:05:13 +0200 Subject: [PATCH] WMTS: working with introspection; missing external URL and TileMatrixLimits --- externals/libgeo | 2 +- externals/vts-libs | 2 +- mapproxy/src/mapproxy/fileinfo.cpp | 7 +- mapproxy/src/mapproxy/fileinfo.hpp | 2 + .../mapproxy/generator/tms-raster-base.cpp | 40 ++- .../mapproxy/generator/tms-raster-base.hpp | 2 +- mapproxy/src/mapproxy/ol/ol.js | 27 +- mapproxy/src/mapproxy/resource.hpp | 4 +- mapproxy/src/mapproxy/support/wmts.cpp | 313 +++++++++++++----- mapproxy/src/mapproxy/support/wmts.hpp | 7 +- 10 files changed, 289 insertions(+), 117 deletions(-) diff --git a/externals/libgeo b/externals/libgeo index 4eb29b2..19b26f7 160000 --- a/externals/libgeo +++ b/externals/libgeo @@ -1 +1 @@ -Subproject commit 4eb29b2a4dfb930e309f51adcd6c81315f963b43 +Subproject commit 19b26f725bdb5e2dc1f226385d4df01b8e5eb130 diff --git a/externals/vts-libs b/externals/vts-libs index bc0f76c..b26f161 160000 --- a/externals/vts-libs +++ b/externals/vts-libs @@ -1 +1 @@ -Subproject commit bc0f76ccb168318405de6dde1bf131383c407fc8 +Subproject commit b26f161898d03a6fd30c6473966c23bd1c5ae43a diff --git a/mapproxy/src/mapproxy/fileinfo.cpp b/mapproxy/src/mapproxy/fileinfo.cpp index 54e737d..a84e180 100644 --- a/mapproxy/src/mapproxy/fileinfo.cpp +++ b/mapproxy/src/mapproxy/fileinfo.cpp @@ -66,7 +66,7 @@ namespace constants { const std::string LayerJson("layer.json"); const std::string CesiumConf("cesium.conf"); - const char *WmtsCapabilities("WMTSCapabilities.xml"); + const std::string WmtsCapabilities("WMTSCapabilities.xml"); const std::string DisableBrowserHeader("X-Mapproxy-Disable-Browser"); @@ -663,3 +663,8 @@ Sink::FileInfo WmtsFileInfo::sinkFileInfo(std::time_t lastModified) const return {}; } + +const std::string& WmtsFileInfo::capabilitesName() +{ + return constants::WmtsCapabilities; +} diff --git a/mapproxy/src/mapproxy/fileinfo.hpp b/mapproxy/src/mapproxy/fileinfo.hpp index e36eef4..93a40f0 100644 --- a/mapproxy/src/mapproxy/fileinfo.hpp +++ b/mapproxy/src/mapproxy/fileinfo.hpp @@ -263,6 +263,8 @@ struct WmtsFileInfo { /** Valid only when type == Type::support; */ const vs::SupportFile *support; + + static const std::string& capabilitesName(); }; #endif // mapproxy_fileinfo_hpp_included_ diff --git a/mapproxy/src/mapproxy/generator/tms-raster-base.cpp b/mapproxy/src/mapproxy/generator/tms-raster-base.cpp index cd7bfaa..6a25bae 100644 --- a/mapproxy/src/mapproxy/generator/tms-raster-base.cpp +++ b/mapproxy/src/mapproxy/generator/tms-raster-base.cpp @@ -75,31 +75,39 @@ const vre::Wmts& TmsRasterBase::getWmts() const return *wmts_; } -namespace { - -bool hasIntrospection(const std::string &query) +WmtsResources TmsRasterBase::wmtsResources(const WmtsFileInfo &fileInfo) const { - return !uq::empty(uq::find(uq::splitQuery(query), "is")); -} + const auto &fi(fileInfo.fileInfo); + const bool introspection + (!uq::empty(uq::find(uq::splitQuery(fi.query), "is"))); -} // namespace + WmtsResources resources; -WmtsLayer TmsRasterBase::wmtsLayer(bool introspection) const -{ - WmtsLayer layer(resource()); + resources.layers.emplace_back(resource()); + auto &layer(resources.layers.back()); + + layer.format = format_; // build root path if (introspection) { - // this is URL used in introspection -> local - layer.rootPath = "./"; - layer.format = format_; + // used in introspection -> local, can be relative from wmts interface + // to vts interface + const auto resdiff + (resolveRoot + (id(), fi.interface + , id(), fi.interface.as(GeneratorInterface::Interface::vts))); + + layer.rootPath = + prependRoot(std::string(), resource(), resdiff); + + resources.capabilitiesUrl = "./" + fileInfo.capabilitesName(); } else { LOG(warn4) << "TODO: Use external path; must be configured."; layer.rootPath = "./"; - layer.format = format_; + resources.capabilitiesUrl = "./" + fileInfo.capabilitesName(); } - return layer; + return resources; } Generator::Task TmsRasterBase @@ -115,9 +123,7 @@ ::wmtsInterface(const FileInfo &fileInfo, Sink &sink) const break; case WmtsFileInfo::Type::capabilities: - sink.content - (wmtsCapabilities({wmtsLayer(hasIntrospection(fileInfo.query))}) - , fi.sinkFileInfo()); + sink.content(wmtsCapabilities(wmtsResources(fi)), fi.sinkFileInfo()); return {}; case WmtsFileInfo::Type::support: diff --git a/mapproxy/src/mapproxy/generator/tms-raster-base.hpp b/mapproxy/src/mapproxy/generator/tms-raster-base.hpp index 066aaa8..b8c0119 100644 --- a/mapproxy/src/mapproxy/generator/tms-raster-base.hpp +++ b/mapproxy/src/mapproxy/generator/tms-raster-base.hpp @@ -53,7 +53,7 @@ class TmsRasterBase : public Generator { Task wmtsInterface(const FileInfo &fileInfo, Sink &sink) const; - WmtsLayer wmtsLayer(bool introspection) const; + WmtsResources wmtsResources(const WmtsFileInfo &fileInfo) const; const vre::Wmts& getWmts() const; diff --git a/mapproxy/src/mapproxy/ol/ol.js b/mapproxy/src/mapproxy/ol/ol.js index 2c14215..9629f11 100644 --- a/mapproxy/src/mapproxy/ol/ol.js +++ b/mapproxy/src/mapproxy/ol/ol.js @@ -1,9 +1,8 @@ -var map; - function startBrowser() { var getWidth = ol.extent.getWidth; var getTopLeft = ol.extent.getTopLeft; var getProjection = ol.proj.get; + var transform = ol.proj.transform; var WMTS = ol.source.WMTS; var Map = ol.Map; @@ -15,32 +14,38 @@ function startBrowser() { var parser = new WMTSCapabilities(); + var map; + fetch('./WMTSCapabilities.xml?is=1').then(function(response) { return response.text(); }).then(function(text) { var capabilities = parser.read(text); - console.dir(capabilities); - // TODO: use first layer from capabilities + // use first layer + var layer = capabilities.Contents.Layer[0]; + // use single tile matrix var options = optionsFromCapabilities(capabilities, { - layer: 'bmng', - matrixSet: 'EPSG:3857' + layer: layer.Identifier }); - console.dir(options); + // get center of WGS84 bounding box and convert it to SRS of tile matrix + var bb = layer.WGS84BoundingBox; + var center = transform([ (bb[0] + bb[2]) / 2, (bb[1] + bb[3]) / 2 ] + , getProjection("CRS:84") + , options.projection); map = new Map({ layers: [ new TileLayer({ - opacity: 1, - source: new WMTS(options) + opacity: 1 + , source: new WMTS(options) }) ], target: 'map', view: new View({ - center: [0, 0], - zoom: 0 + center: center + , zoom: 0 }) }); }); diff --git a/mapproxy/src/mapproxy/resource.hpp b/mapproxy/src/mapproxy/resource.hpp index e3ff4df..8581d6e 100644 --- a/mapproxy/src/mapproxy/resource.hpp +++ b/mapproxy/src/mapproxy/resource.hpp @@ -240,6 +240,8 @@ struct GeneratorInterface { operator Type() const { return type; } + GeneratorInterface as(Interface other) const { return { type, other }; } + bool operator==(const GeneratorInterface &o) const; bool operator!=(const GeneratorInterface &o) const; }; @@ -477,7 +479,7 @@ inline ResourceRoot resolveRoot(const Resource &thisResource } inline bool GeneratorInterface::operator==(const GeneratorInterface &o) const { - return (type == o.type) && (interface != o.interface); + return (type == o.type) && (interface == o.interface); } inline bool GeneratorInterface::operator!=(const GeneratorInterface &o) const { diff --git a/mapproxy/src/mapproxy/support/wmts.cpp b/mapproxy/src/mapproxy/support/wmts.cpp index fdd9551..456011b 100644 --- a/mapproxy/src/mapproxy/support/wmts.cpp +++ b/mapproxy/src/mapproxy/support/wmts.cpp @@ -24,7 +24,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include +#include #include #include @@ -37,8 +37,10 @@ #include "vts-libs/registry/extensions.hpp" #include "vts-libs/vts/csconvertor.hpp" +#include "vts-libs/vts/tileop.hpp" #include "../error.hpp" +#include "../support/metatile.hpp" #include "wmts.hpp" @@ -48,6 +50,8 @@ namespace vts = vtslibs::vts; namespace { +constexpr double wmtsResolution(0.28e-3); + typedef tinyxml2::XMLPrinter XML; template T&& extract(T &&value) { return std::forward(value); } @@ -117,6 +121,19 @@ void text(XML &x, const char *element, T &&value) { Element(x, element).text(std::forward(value)); } +std::string makeTemplate(const WmtsLayer &layer) +{ + std::ostringstream os; + os << layer.rootPath; + if (!layer.rootPath.empty() && layer.rootPath.back() != '/') { + os << '/'; + } + + os << "{TileMatrix}-{TileCol}-{TileRow}." << layer.format; + + return os.str(); +} + /** TODO: make configurable */ void serviceIdentification(XML &x) @@ -135,11 +152,14 @@ void serviceProvider(XML &x) Element(x, "ows:ServiceProvider") .text("ows:ProviderName", "Mapproxy") .text("ows:ProviderSite", "https://melown.com") - .text("ows:ServiceContact") + .push("ows:ServiceContact").pop() ; } -typedef std::set RfSet; +void point(Element &e, const char *name, const math::Point2 &p) +{ + e.text(name, utility::format("%.9f %.9f", p(0), p(1))); +} void boundingBox(XML &x, const math::Extents2 &extents , const boost::optional &crs @@ -148,122 +168,248 @@ void boundingBox(XML &x, const math::Extents2 &extents Element bb(x, crs ? "ows:BoundingBox" : "ows:WGS84BoundingBox"); bb.attribute("crs", crs ? crs->c_str() : "urn:ogc:def:crs:OGC:2:84"); - // TODO: use custom format - bb.text("ows:LowerCorner" - , utility::format("%.9f %.9f", extents.ll(0), extents.ll(1))); - bb.text("ows:UpperCorner" - , utility::format("%.9f %.9f", extents.ur(0), extents.ur(1))); + point(bb, "ows:LowerCorner", extents.ll); + point(bb, "ows:UpperCorner", extents.ur); } -RfSet content(XML &x, const WmtsLayer::list &layers) +template +void widthAndHeight(Element &e, const char *widthName, const char *heightName + , const math::Size2_ &size) { - RfSet rfs; - - Element c(x, "Contents"); - for (const auto &layer : layers) { - const auto &r(layer.resource); - - Element l(x, "Layer"); - l.text("ows:Title", "VTS Mapproxy resource <" + r.id.id + ">"); - l.text("ows:Abstract", r.comment); - Element(x, "ows::Keywords"); - l.text("ows:Identifier", r.id.fullId()); + e.text(widthName, size.width); + e.text(heightName, size.height); +} - l.text("Format", contentType(layer.format)); +struct TileMatrixSet { + std::string id; + std::string description; - // TODO: extents from reference frame - // - // -#if 0 - const auto wgs84Extents - (vts::CsConvertor(contentSrs, vr::system.srs - (contentSrs).srsDef.geographic())(rfExtents)); - boundingBox(x, wgs84Extents); -#endif + math::Extents2 extents; + std::string srs; + std::string extentsSrs; + vts::LodRange lodRange; + vts::TileRange tileRange; + vre::Wmts wmts; + double metersPerUnit; + geo::SrsDefinition geographic; - { - Element s(x, "Style"); - s.attribute("isDefault", true); - s.text("ows:Identifier", "default"); - } + TileMatrixSet(const vr::ReferenceFrame &rf); - { - // TileMatrixSet -> reference frame - Element tmsl(x, "TileMatrixSetLink"); - tmsl.text("TileMatrixSet", r.id.referenceFrame); - - // TODO: TileMatrixSetLimits from resource (tile/lod range) - } - - // remember reference frame - rfs.insert(r.referenceFrame); - } - - return rfs; -} + math::Extents2 computeExtents(const Resource &r) const; +}; -void tileMatrixSet(XML &x, const vr::ReferenceFrame &rf) +TileMatrixSet::TileMatrixSet(const vr::ReferenceFrame &rf) + : id(rf.id), description(rf.description) + , lodRange(vts::LodRange::emptyRange()) + , tileRange(math::InvalidExtents{}) + , metersPerUnit(1.0) { - const auto *wmts(rf.findExtension()); - - Element e(x, "TileMatrixSet"); - - e.text("ows:Title", "VTS reference frame <" + rf.id + ">"); - e.text("ows:Abstract", rf.description); - - const auto &physicalSrs(wmts->physicalSrs ? *wmts->physicalSrs - : rf.model.physicalSrs); + if (const auto *ext = rf.findExtension()) { + wmts = *ext; + } else { + LOGTHROW(err1, Error) + << "Reference frame <" << id << "> has no WMTS extension."; + } - // compute extents in reference frame SRS - math::Extents2 rfExtents(math::InvalidExtents{}); + if (wmts.content) { + srs = *wmts.content; - std::string contentSrs; + // this ... is here to silence compiler's "may-be-uninitialized" + auto lod([]()->boost::optional { return boost::none; }()); - if (wmts->content) { // find subtree-roots that provide data in "content" SRS for (const auto &item : rf.division.nodes) { const auto &node(item.second); - if (node.srs != wmts->content) { continue; } - - math::update(rfExtents, node.extents); + if (node.srs != srs) { continue; } + + if (!lod) { + lod = node.id.lod; + } else if (*lod != node.id.lod) { + LOGTHROW(err1, Error) + << "Malformed WMTS extension in reference frame <" + << id << ">: not all content root nodes are" + "at the same LOD."; + } + + math::update(extents, node.extents); + math::update(tileRange, node.id.x, node.id.y); } - if (!math::valid(rfExtents)) { + + if (!lod) { LOGTHROW(err1, Error) << "Malformed WMTS extension in reference frame <" - << rf.id << ">: No root node found for wmts.content <" - << *wmts->content << "> in the reference frame."; + << id << ">: No root node found for wmts.content <" + << srs << "> in the reference frame."; } - contentSrs = *wmts->content; + lodRange = vts::LodRange(*lod); } else { if (rf.division.nodes.size() != 1) { LOGTHROW(err1, Error) << "Malformed WMTS extension in reference frame <" - << rf.id << ">: No wmts.content and the reference frame" + << id << ">: No wmts.content and the reference frame" "is not single-rooted."; } const auto &node(rf.division.nodes.begin()->second); - rfExtents = node.extents; - contentSrs = node.srs; + extents = node.extents; + srs = node.srs; + lodRange = vts::LodRange(node.id.lod); + tileRange = vts::tileRange(node.id); + } + + extentsSrs = wmts.extentsSrs ? *wmts.extentsSrs : srs; + extents = vts::CsConvertor(srs, extentsSrs)(extents); + + { + const auto srsDef(vr::system.srs(extentsSrs).srsDef); + geographic = srsDef.geographic(); + + // get linear unit of given SRS (force computation for angluar systems) + metersPerUnit = geo::linearUnit(srsDef, true); + } +} + +math::Extents2 TileMatrixSet::computeExtents(const Resource &r) const +{ + const vts::CsConvertor conv(srs, extentsSrs); + + math::Extents2 e(math::InvalidExtents{}); + + // treat min lod as one gigantic metatile + for (const auto &block : metatileBlocks + (r, vts::TileId(r.lodRange.min, 0, 0), r.lodRange.min, false)) + { + if (block.srs == srs) { + math::update(e, conv(block.extents)); + } } - const auto extents - (vts::CsConvertor(contentSrs, physicalSrs)(rfExtents)); + return e; +} + +void tileMatrix(XML &x, vts::Lod lod, const vts::TileRange &tr + , const math::Extents2 &extents + , const double metersPerUnit) +{ + Element e(x, "TileMatrix"); + + const auto ts(vr::BoundLayer::tileSize()); + const auto trs(vts::tileRangesSize(tr)); + const auto es(math::size(extents)); + + e.text("ows:Identifier", lod); + + // scale denominator: + const auto pixelSize(es.width / (trs.width * ts.width)); + const auto scaleDenominator(pixelSize * metersPerUnit / wmtsResolution); + e.text("ScaleDenominator", scaleDenominator); + + point(e, "TopLeftCorner", math::ul(extents)); + widthAndHeight(e, "TileWidth", "TileHeight", ts); + widthAndHeight(e, "MatrixWidth", "MatrixHeight", trs); +} + +/** Describe reference frame as tile matrix set + */ +void tileMatrixSet(XML &x, const TileMatrixSet &tms) +{ + Element e(x, "TileMatrixSet"); + + e.text("ows:Identifier", tms.id); + e.text("ows:Title", "VTS reference frame <" + tms.id + ">"); + e.text("ows:Abstract", tms.description); + + boundingBox(x, tms.extents, tms.wmts.projection); + e.text("ows:SupportedCRS", tms.wmts.projection); + if (tms.wmts.wellKnownScaleSet) { + e.text("ows:WellKnownScaleSet", *tms.wmts.wellKnownScaleSet); + } - boundingBox(x, extents, wmts->projection); - e.text("ows:SupportedCRS", wmts->projection); - if (wmts->wellKnownScaleSet) { - e.text("ows:WellKnownScaleSet", *wmts->wellKnownScaleSet); + auto tr(tms.tileRange); + for (const auto lod : tms.lodRange) { + tileMatrix(x, lod, tr, tms.extents, tms.metersPerUnit); + tr = vts::childRange(tr); } } -void matrixSets(XML &x, const RfSet &rfs) +void layerExtents(XML &x, const TileMatrixSet &tms, const Resource &r) +{ + const auto extents(tms.computeExtents(r)); + + boundingBox(x, extents, tms.wmts.projection); + boundingBox(x, vts::CsConvertor(tms.extentsSrs, tms.geographic)(extents)); +} + +void content(XML &x, const WmtsLayer::list &layers) +{ + typedef std::map map; + map tmss; + + Element c(x, "Contents"); + for (const auto &layer : layers) { + const auto &r(layer.resource); + + // remember reference frame + auto ftmss(tmss.find(r.referenceFrame)); + if (ftmss == tmss.end()) { + ftmss = tmss.insert + (map::value_type + (r.referenceFrame, TileMatrixSet(*r.referenceFrame))).first; + } + auto &tms(ftmss->second); + + // update tile-matrix-set lod range + update(tms.lodRange, r.lodRange.max); + + Element l(x, "Layer"); + l.text("ows:Title", "VTS Mapproxy resource <" + r.id.id + ">"); + l.text("ows:Abstract", r.comment); + l.text("ows:Identifier", r.id.fullId()); + + layerExtents(x, tms, r); + + const auto ct(contentType(layer.format)); + l.text("Format", ct); + Element(x, "ResourceURL") + .attribute("format", ct) + .attribute("resourceType", "tile") + .attribute("template", makeTemplate(layer)) + ; + + { + Element s(x, "Style"); + s.attribute("isDefault", true); + s.text("ows:Identifier", "default"); + } + + { + // TileMatrixSet -> reference frame + Element tmsl(x, "TileMatrixSetLink"); + tmsl.text("TileMatrixSet", r.id.referenceFrame); + } + } + + for (const auto &tms : tmss) { tileMatrixSet(x, tms.second); } +} + +void operationsMetadata(XML &x, const WmtsResources &resources) { - for (const auto *rf : rfs) { tileMatrixSet(x, *rf); } + Element om(x, "ows:OperationsMetadata"); + Element op(x, "ows:Operation"); + op.attribute("name", "GetCapabilities"); + + Element dcp(x, "ows:DCP"); + Element http(x, "ows:HTTP"); + Element get(x, "ows:Get"); + get.attribute("xlink:href", resources.capabilitiesUrl); + + Element c(x, "ows:Constraint"); + Element aw(x, "ows:AllowedValues"); + aw.text("ows:Value", "REST"); } } // namespace -std::string wmtsCapabilities(const WmtsLayer::list &layers) +std::string wmtsCapabilities(const WmtsResources &resources) { XML x; @@ -287,8 +433,9 @@ std::string wmtsCapabilities(const WmtsLayer::list &layers) serviceIdentification(x); serviceProvider(x); + operationsMetadata(x, resources); - matrixSets(x, content(x, layers)); + content(x, resources.layers); } return x.CStr(); diff --git a/mapproxy/src/mapproxy/support/wmts.hpp b/mapproxy/src/mapproxy/support/wmts.hpp index 539527f..b0a8caf 100644 --- a/mapproxy/src/mapproxy/support/wmts.hpp +++ b/mapproxy/src/mapproxy/support/wmts.hpp @@ -41,6 +41,11 @@ struct WmtsLayer { {} }; -std::string wmtsCapabilities(const WmtsLayer::list &layers); +struct WmtsResources { + std::string capabilitiesUrl; + WmtsLayer::list layers; +}; + +std::string wmtsCapabilities(const WmtsResources &resources); #endif // mapproxy_support_wtms_hpp_included_