From b1f5e759cee635d3ab2bc67d4912c79ba90e06e5 Mon Sep 17 00:00:00 2001 From: Vaclav Blazek Date: Thu, 4 Apr 2019 21:01:41 +0200 Subject: [PATCH] WMTS: WIP --- externals/vts-libs | 2 +- mapproxy/src/mapproxy/core.cpp | 2 +- .../mapproxy/generator/tms-raster-base.cpp | 34 ++- .../mapproxy/generator/tms-raster-base.hpp | 3 + mapproxy/src/mapproxy/ol/ol.js | 5 +- mapproxy/src/mapproxy/resource.cpp | 18 +- mapproxy/src/mapproxy/resource.hpp | 42 +++- mapproxy/src/mapproxy/support/wmts.cpp | 194 ++++++++++++++++-- mapproxy/src/mapproxy/support/wmts.hpp | 14 +- 9 files changed, 272 insertions(+), 42 deletions(-) diff --git a/externals/vts-libs b/externals/vts-libs index 6fc8570..bc0f76c 160000 --- a/externals/vts-libs +++ b/externals/vts-libs @@ -1 +1 @@ -Subproject commit 6fc8570508aea9a15c108b1b39eb6d8366620cd8 +Subproject commit bc0f76ccb168318405de6dde1bf131383c407fc8 diff --git a/mapproxy/src/mapproxy/core.cpp b/mapproxy/src/mapproxy/core.cpp index 5de7651..b017a9f 100644 --- a/mapproxy/src/mapproxy/core.cpp +++ b/mapproxy/src/mapproxy/core.cpp @@ -323,7 +323,7 @@ void Core::Detail::generateReferenceFrameDems(const FileInfo &fi, Sink &sink) auto link(prependRoot(boost::filesystem::path() , record.resourceId , Resource::Generator::Type::surface - , ResourceRoot::Depth::type)); + , ResourceRoot::Depth::interface)); os << "" << record.id.id << "\n"; diff --git a/mapproxy/src/mapproxy/generator/tms-raster-base.cpp b/mapproxy/src/mapproxy/generator/tms-raster-base.cpp index de2cdb2..cd7bfaa 100644 --- a/mapproxy/src/mapproxy/generator/tms-raster-base.cpp +++ b/mapproxy/src/mapproxy/generator/tms-raster-base.cpp @@ -25,11 +25,14 @@ */ #include "utility/raise.hpp" +#include "utility/httpquery.hpp" #include "../support/wmts.hpp" #include "tms-raster-base.hpp" +namespace uq = utility::query; + namespace generator { TmsRasterBase @@ -72,6 +75,33 @@ const vre::Wmts& TmsRasterBase::getWmts() const return *wmts_; } +namespace { + +bool hasIntrospection(const std::string &query) +{ + return !uq::empty(uq::find(uq::splitQuery(query), "is")); +} + +} // namespace + +WmtsLayer TmsRasterBase::wmtsLayer(bool introspection) const +{ + WmtsLayer layer(resource()); + + // build root path + if (introspection) { + // this is URL used in introspection -> local + layer.rootPath = "./"; + layer.format = format_; + } else { + LOG(warn4) << "TODO: Use external path; must be configured."; + layer.rootPath = "./"; + layer.format = format_; + } + + return layer; +} + Generator::Task TmsRasterBase ::wmtsInterface(const FileInfo &fileInfo, Sink &sink) const { @@ -85,7 +115,9 @@ ::wmtsInterface(const FileInfo &fileInfo, Sink &sink) const break; case WmtsFileInfo::Type::capabilities: - sink.content(wmtsCapabilities({resource()}), fi.sinkFileInfo()); + sink.content + (wmtsCapabilities({wmtsLayer(hasIntrospection(fileInfo.query))}) + , 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 c29654e..066aaa8 100644 --- a/mapproxy/src/mapproxy/generator/tms-raster-base.hpp +++ b/mapproxy/src/mapproxy/generator/tms-raster-base.hpp @@ -31,6 +31,7 @@ #include "vts-libs/registry/extensions.hpp" +#include "../support/wmts.hpp" #include "../generator.hpp" namespace vre = vtslibs::registry::extensions; @@ -52,6 +53,8 @@ class TmsRasterBase : public Generator { Task wmtsInterface(const FileInfo &fileInfo, Sink &sink) const; + WmtsLayer wmtsLayer(bool introspection) const; + const vre::Wmts& getWmts() const; RasterFormat format_; diff --git a/mapproxy/src/mapproxy/ol/ol.js b/mapproxy/src/mapproxy/ol/ol.js index 0d49912..2c14215 100644 --- a/mapproxy/src/mapproxy/ol/ol.js +++ b/mapproxy/src/mapproxy/ol/ol.js @@ -15,10 +15,11 @@ function startBrowser() { var parser = new WMTSCapabilities(); - fetch('./WMTSCapabilities.xml').then(function(response) { + 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 @@ -27,6 +28,8 @@ function startBrowser() { matrixSet: 'EPSG:3857' }); + console.dir(options); + map = new Map({ layers: [ new TileLayer({ diff --git a/mapproxy/src/mapproxy/resource.cpp b/mapproxy/src/mapproxy/resource.cpp index 529833f..cf491ef 100644 --- a/mapproxy/src/mapproxy/resource.cpp +++ b/mapproxy/src/mapproxy/resource.cpp @@ -532,7 +532,7 @@ Changed Resource::changed(const Resource &o) const boost::filesystem::path prependRoot(const boost::filesystem::path &path , const Resource::Id &resource - , Resource::Generator::Type generatorType + , GeneratorInterface generatorIface , const ResourceRoot &root) { boost::filesystem::path out; @@ -545,8 +545,8 @@ boost::filesystem::path prependRoot(const boost::filesystem::path &path out /= resource.referenceFrame; // fall through - case ResourceRoot::type: - out /= boost::lexical_cast(generatorType); + case ResourceRoot::interface: + out /= boost::lexical_cast(generatorIface); // fall through case ResourceRoot::group: @@ -568,17 +568,17 @@ boost::filesystem::path prependRoot(const boost::filesystem::path &path std::string prependRoot(const std::string &path , const Resource::Id &resource - , Resource::Generator::Type generatorType + , const GeneratorInterface &generatorIface , const ResourceRoot &root) { fs::path tmp(path); - return prependRoot(tmp, resource, generatorType, root).string(); + return prependRoot(tmp, resource, generatorIface, root).string(); } ResourceRoot resolveRoot(const Resource::Id &thisResource - , Resource::Generator::Type thisGeneratorType + , const GeneratorInterface &thisGeneratorIface , const Resource::Id &thatResource - , Resource::Generator::Type thatGeneratorType + , const GeneratorInterface &thatGeneratorIface , ResourceRoot::Depth thisDepth) { // compute difference between two resources @@ -588,8 +588,8 @@ ResourceRoot resolveRoot(const Resource::Id &thisResource return { ResourceRoot::referenceFrame, 4 }; } - if (thisGeneratorType != thatGeneratorType) { - return { ResourceRoot::type, 3 }; + if (thisGeneratorIface != thatGeneratorIface) { + return { ResourceRoot::interface, 3 }; } if (thisResource.group != thatResource.group) { diff --git a/mapproxy/src/mapproxy/resource.hpp b/mapproxy/src/mapproxy/resource.hpp index c10c766..e3ff4df 100644 --- a/mapproxy/src/mapproxy/resource.hpp +++ b/mapproxy/src/mapproxy/resource.hpp @@ -233,11 +233,15 @@ struct GeneratorInterface { Type type; Interface interface; - GeneratorInterface(Type type = Type(), Interface interface = Interface()) + GeneratorInterface(Type type = Type() + , Interface interface = Interface::vts) : type(type), interface(interface) {} operator Type() const { return type; } + + bool operator==(const GeneratorInterface &o) const; + bool operator!=(const GeneratorInterface &o) const; }; vr::Credits asInlineCredits(const Resource &res); @@ -274,7 +278,7 @@ struct ResourceRoot { * the code. */ enum Depth : int { - referenceFrame = 0, type = 1, group = 2, id = 3, none = 4 + referenceFrame = 0, interface = 1, group = 2, id = 3, none = 4 }; /** Root location. @@ -300,9 +304,9 @@ struct ResourceRoot { /** Computes path from this resource to that resource. */ ResourceRoot resolveRoot(const Resource::Id &thisResource - , Resource::Generator::Type thisGeneratorType + , const GeneratorInterface &thisGeneratorIface , const Resource::Id &thatResource - , Resource::Generator::Type thatGeneratorType + , const GeneratorInterface &thatGeneratorIface , ResourceRoot::Depth thisDepth = ResourceRoot::none); /** Computes path from this resource to that resource. @@ -315,7 +319,7 @@ ResourceRoot resolveRoot(const Resource &thisResource */ UTILITY_GENERATE_ENUM_IO(ResourceRoot::Depth, ((referenceFrame)) - ((type)) + ((interface)) ((group)) ((id)) ((none)) @@ -364,11 +368,11 @@ std::string prependRoot(const std::string &path, const Resource &resource boost::filesystem::path prependRoot(const boost::filesystem::path &path , const Resource::Id &resource - , Resource::Generator::Type generatorType + , GeneratorInterface generatorInterface , const ResourceRoot &root); std::string prependRoot(const std::string &path, const Resource::Id &resource - , Resource::Generator::Type generatorType + , const GeneratorInterface &generatorIface , const ResourceRoot &root); std::string contentType(RasterFormat format); @@ -447,6 +451,22 @@ inline std::string prependRoot(const std::string &path return prependRoot(path, resource.id, resource.generator.type, root); } +inline boost::filesystem::path prependRoot(const boost::filesystem::path &path + , const Resource &resource + , const GeneratorInterface &iface + , const ResourceRoot &root) +{ + return prependRoot(path, resource.id, iface, root); +} + +inline std::string prependRoot(const std::string &path + , const Resource &resource + , const GeneratorInterface &iface + , const ResourceRoot &root) +{ + return prependRoot(path, resource.id, iface, root); +} + inline ResourceRoot resolveRoot(const Resource &thisResource , const Resource &thatResource , ResourceRoot::Depth thisDepth) @@ -456,4 +476,12 @@ inline ResourceRoot resolveRoot(const Resource &thisResource , thisDepth); } +inline bool GeneratorInterface::operator==(const GeneratorInterface &o) const { + return (type == o.type) && (interface != o.interface); +} + +inline bool GeneratorInterface::operator!=(const GeneratorInterface &o) const { + return !(*this == o); +} + #endif // mapproxy_resource_hpp_included_ diff --git a/mapproxy/src/mapproxy/support/wmts.cpp b/mapproxy/src/mapproxy/support/wmts.cpp index 6d7fc8e..fdd9551 100644 --- a/mapproxy/src/mapproxy/support/wmts.cpp +++ b/mapproxy/src/mapproxy/support/wmts.cpp @@ -24,12 +24,36 @@ * POSSIBILITY OF SUCH DAMAGE. */ +#include + +#include +#include + #include +#include "dbglog/dbglog.hpp" + +#include "utility/format.hpp" + +#include "vts-libs/registry/extensions.hpp" +#include "vts-libs/vts/csconvertor.hpp" + +#include "../error.hpp" + #include "wmts.hpp" +namespace vr = vtslibs::registry; +namespace vre = vtslibs::registry::extensions; +namespace vts = vtslibs::vts; + namespace { +typedef tinyxml2::XMLPrinter XML; + +template T&& extract(T &&value) { return std::forward(value); } +const char* extract(const std::string &value) { return value.c_str(); } +const char* extract(std::string &&value) { return value.c_str(); } + /** Stack-based XML element writer. * * Can be used independently or combined with nested elements via push/pop @@ -37,7 +61,7 @@ namespace { */ class Element { public: - Element(tinyxml2::XMLPrinter &x, const char *name) + Element(XML &x, const char *name) : x_(x), closed_(false), parent_() { x_.OpenElement(name); @@ -47,23 +71,24 @@ class Element { template Element& attribute(const char *name, T &&value) { - x_.PushAttribute(name, std::forward(value)); + x_.PushAttribute(name, extract(std::forward(value))); return *this; } - Element& attribute(const char *name, const std::string &value) { - x_.PushAttribute(name, value.c_str()); + template + Element& text(T &&value) { + x_.PushText(extract(std::forward(value))); return *this; } + /** Add element with single text + */ template - Element& text(T &&value) { - x_.PushText(std::forward(value)); + Element& text(const char *element, T &&value) { + Element(x_, element).text(std::forward(value)); return *this; } - Element& text(const std::string &value) { return text(value.c_str()); } - /** Push nested element. Must be pop()ed to work properly. * NB: Return by value! */ @@ -82,40 +107,165 @@ class Element { x_.OpenElement(name); } - tinyxml2::XMLPrinter &x_; + XML &x_; bool closed_; Element *parent_; }; +template +void text(XML &x, const char *element, T &&value) { + Element(x, element).text(std::forward(value)); +} + /** TODO: make configurable */ -void serviceIdentification(tinyxml2::XMLPrinter &x) +void serviceIdentification(XML &x) { Element(x, "ows:ServiceIdentification") - .push("ows:Title").text("VTS Mapproxy").pop() - .push("ows:ServiceType").text("OGC WMTS").pop() - .push("ows:ServiceTypeVersion").text("1.3.0").pop() + .text("ows:Title", "VTS Mapproxy") + .text("ows:ServiceType", "OGC WMTS") + .text("ows:ServiceTypeVersion", "1.3.0") ; } /** TODO: make configurable */ -void serviceProvider(tinyxml2::XMLPrinter &x) +void serviceProvider(XML &x) { Element(x, "ows:ServiceProvider") - .push("ows:ProviderName").text("Mapproxy").pop() - .push("ows:ProviderSite").text("https://melown.com").pop() - .push("ows:ServiceContact").pop() + .text("ows:ProviderName", "Mapproxy") + .text("ows:ProviderSite", "https://melown.com") + .text("ows:ServiceContact") ; } -} // namespace +typedef std::set RfSet; + +void boundingBox(XML &x, const math::Extents2 &extents + , const boost::optional &crs + = boost::none) +{ + 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))); +} -std::string wmtsCapabilities(const Resource &resources) +RfSet content(XML &x, const WmtsLayer::list &layers) { - (void) resources; + 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()); + + l.text("Format", contentType(layer.format)); + + // TODO: extents from reference frame + // + // +#if 0 + const auto wgs84Extents + (vts::CsConvertor(contentSrs, vr::system.srs + (contentSrs).srsDef.geographic())(rfExtents)); + boundingBox(x, wgs84Extents); +#endif + + { + 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); - tinyxml2::XMLPrinter x; + // TODO: TileMatrixSetLimits from resource (tile/lod range) + } + + // remember reference frame + rfs.insert(r.referenceFrame); + } + + return rfs; +} + +void tileMatrixSet(XML &x, const vr::ReferenceFrame &rf) +{ + 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); + + // compute extents in reference frame SRS + math::Extents2 rfExtents(math::InvalidExtents{}); + + std::string contentSrs; + + 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 (!math::valid(rfExtents)) { + LOGTHROW(err1, Error) + << "Malformed WMTS extension in reference frame <" + << rf.id << ">: No root node found for wmts.content <" + << *wmts->content << "> in the reference frame."; + } + contentSrs = *wmts->content; + } 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" + "is not single-rooted."; + } + const auto &node(rf.division.nodes.begin()->second); + rfExtents = node.extents; + contentSrs = node.srs; + } + + const auto extents + (vts::CsConvertor(contentSrs, physicalSrs)(rfExtents)); + + boundingBox(x, extents, wmts->projection); + e.text("ows:SupportedCRS", wmts->projection); + if (wmts->wellKnownScaleSet) { + e.text("ows:WellKnownScaleSet", *wmts->wellKnownScaleSet); + } +} + +void matrixSets(XML &x, const RfSet &rfs) +{ + for (const auto *rf : rfs) { tileMatrixSet(x, *rf); } +} + +} // namespace + +std::string wmtsCapabilities(const WmtsLayer::list &layers) +{ + XML x; // xml.PushHeader(false, false); x.PushDeclaration(R"(xml version="1.0" encoding="UTF-8")"); @@ -137,6 +287,8 @@ std::string wmtsCapabilities(const Resource &resources) serviceIdentification(x); serviceProvider(x); + + matrixSets(x, content(x, layers)); } return x.CStr(); diff --git a/mapproxy/src/mapproxy/support/wmts.hpp b/mapproxy/src/mapproxy/support/wmts.hpp index 534e558..539527f 100644 --- a/mapproxy/src/mapproxy/support/wmts.hpp +++ b/mapproxy/src/mapproxy/support/wmts.hpp @@ -29,6 +29,18 @@ #include "../resource.hpp" -std::string wmtsCapabilities(const Resource &resources); +struct WmtsLayer { + const Resource &resource; + std::string rootPath; + RasterFormat format; + + typedef std::vector list; + + WmtsLayer(const Resource &resource) + : resource(resource), format(RasterFormat::jpg) + {} +}; + +std::string wmtsCapabilities(const WmtsLayer::list &layers); #endif // mapproxy_support_wtms_hpp_included_