1 #include <QXmlStreamReader>
2 #include <QFile>
3 #include <QFileInfo>
4 #include <QEventLoop>
5 #include <QTextStream>
6 #include <QStringList>
7 #include <QtAlgorithms>
8 #include <QXmlStreamReader>
9 #include "downloader.h"
10 #include "pcs.h"
11 #include "crs.h"
12 #include "wmts.h"
13
14
bareFormat(const QString & format)15 static QString bareFormat(const QString &format)
16 {
17 return format.left(format.indexOf(';')).trimmed();
18 }
19
skipParentElement(QXmlStreamReader & reader)20 static void skipParentElement(QXmlStreamReader &reader)
21 {
22 while (reader.readNextStartElement())
23 reader.skipCurrentElement();
24 }
25
tileMatrix(QXmlStreamReader & reader)26 WMTS::TileMatrix WMTS::tileMatrix(QXmlStreamReader &reader)
27 {
28 TileMatrix matrix;
29
30 while (reader.readNextStartElement()) {
31 if (reader.name() == "Identifier")
32 matrix.id = reader.readElementText();
33 else if (reader.name() == "ScaleDenominator")
34 matrix.scaleDenominator = reader.readElementText().toDouble();
35 else if (reader.name() == "TopLeftCorner") {
36 QString str = reader.readElementText();
37 QTextStream ts(&str);
38 ts >> matrix.topLeft.rx() >> matrix.topLeft.ry();
39 } else if (reader.name() == "TileWidth")
40 matrix.tile.setWidth(reader.readElementText().toInt());
41 else if (reader.name() == "TileHeight")
42 matrix.tile.setHeight(reader.readElementText().toInt());
43 else if (reader.name() == "MatrixWidth")
44 matrix.matrix.setWidth(reader.readElementText().toInt());
45 else if (reader.name() == "MatrixHeight")
46 matrix.matrix.setHeight(reader.readElementText().toInt());
47 else
48 reader.skipCurrentElement();
49 }
50
51 if (!matrix.isValid())
52 reader.raiseError("Invalid TileMatrix definition");
53
54 return matrix;
55 }
56
tileMatrixSet(QXmlStreamReader & reader,CTX & ctx)57 void WMTS::tileMatrixSet(QXmlStreamReader &reader, CTX &ctx)
58 {
59 while (reader.readNextStartElement()) {
60 if (reader.name() == "Identifier") {
61 if (reader.readElementText() != _setup.set()) {
62 skipParentElement(reader);
63 return;
64 }
65 } else if (reader.name() == "SupportedCRS")
66 ctx.crs = reader.readElementText();
67 else if (reader.name() == "TileMatrix")
68 ctx.matrixes.insert(tileMatrix(reader));
69 else
70 reader.skipCurrentElement();
71 }
72 }
73
tileMatrixLimits(QXmlStreamReader & reader)74 WMTS::MatrixLimits WMTS::tileMatrixLimits(QXmlStreamReader &reader)
75 {
76 MatrixLimits limits;
77
78 while (reader.readNextStartElement()) {
79 if (reader.name() == "TileMatrix")
80 limits.id = reader.readElementText();
81 else if (reader.name() == "MinTileRow")
82 limits.rect.setTop(reader.readElementText().toInt());
83 else if (reader.name() == "MaxTileRow")
84 limits.rect.setBottom(reader.readElementText().toInt());
85 else if (reader.name() == "MinTileCol")
86 limits.rect.setLeft(reader.readElementText().toInt());
87 else if (reader.name() == "MaxTileCol")
88 limits.rect.setRight(reader.readElementText().toInt());
89 else
90 reader.skipCurrentElement();
91 }
92
93 if (!limits.isValid())
94 reader.raiseError("Invalid TileMatrixLimits definition");
95
96 return limits;
97 }
98
tileMatrixSetLimits(QXmlStreamReader & reader)99 QSet<WMTS::MatrixLimits> WMTS::tileMatrixSetLimits(QXmlStreamReader &reader)
100 {
101 QSet<MatrixLimits> limits;
102
103 while (reader.readNextStartElement()) {
104 if (reader.name() == "TileMatrixLimits")
105 limits.insert(tileMatrixLimits(reader));
106 else
107 reader.skipCurrentElement();
108 }
109
110 return limits;
111 }
112
tileMatrixSetLink(QXmlStreamReader & reader,CTX & ctx)113 void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, CTX &ctx)
114 {
115 while (reader.readNextStartElement()) {
116 if (reader.name() == "TileMatrixSet") {
117 if (reader.readElementText() == _setup.set())
118 ctx.hasSet = true;
119 else {
120 skipParentElement(reader);
121 return;
122 }
123 } else if (reader.name() == "TileMatrixSetLimits")
124 ctx.limits = tileMatrixSetLimits(reader);
125 else
126 reader.skipCurrentElement();
127 }
128 }
129
wgs84BoundingBox(QXmlStreamReader & reader)130 RectC WMTS::wgs84BoundingBox(QXmlStreamReader &reader)
131 {
132 Coordinates topLeft, bottomRight;
133
134 while (reader.readNextStartElement()) {
135 if (reader.name() == "LowerCorner") {
136 QString str = reader.readElementText();
137 QTextStream(&str) >> topLeft.rlon() >> bottomRight.rlat();
138 } else if (reader.name() == "UpperCorner") {
139 QString str = reader.readElementText();
140 QTextStream(&str) >> bottomRight.rlon() >> topLeft.rlat();
141 } else
142 reader.skipCurrentElement();
143 }
144
145 return RectC(topLeft, bottomRight);
146 }
147
style(QXmlStreamReader & reader)148 QString WMTS::style(QXmlStreamReader &reader)
149 {
150 QString id;
151
152 while (reader.readNextStartElement()) {
153 if (reader.name() == "Identifier")
154 id = reader.readElementText();
155 else
156 reader.skipCurrentElement();
157 }
158
159 return id;
160 }
161
layer(QXmlStreamReader & reader,CTX & ctx)162 void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
163 {
164 while (reader.readNextStartElement()) {
165 if (reader.name() == "Identifier") {
166 if (reader.readElementText() == _setup.layer())
167 ctx.hasLayer = true;
168 else {
169 skipParentElement(reader);
170 return;
171 }
172 } else if (reader.name() == "TileMatrixSetLink")
173 tileMatrixSetLink(reader, ctx);
174 else if (reader.name() == "WGS84BoundingBox")
175 ctx.bbox = wgs84BoundingBox(reader);
176 else if (reader.name() == "ResourceURL") {
177 const QXmlStreamAttributes &attr = reader.attributes();
178 if (attr.value("resourceType") == "tile" && _setup.rest())
179 _tileUrl = attr.value("template").toString();
180 reader.skipCurrentElement();
181 } else if (reader.name() == "Style") {
182 const QXmlStreamAttributes &attr = reader.attributes();
183 bool isDefault = (attr.value("isDefault") == "true");
184 QString s = style(reader);
185 if (isDefault)
186 ctx.defaultStyle = s;
187 if (s == _setup.style())
188 ctx.hasStyle = true;
189 } else if (reader.name() == "Format") {
190 QString format(reader.readElementText());
191 if (bareFormat(format) == bareFormat(_setup.format()))
192 ctx.hasFormat = true;
193 } else
194 reader.skipCurrentElement();
195 }
196 }
197
contents(QXmlStreamReader & reader,CTX & ctx)198 void WMTS::contents(QXmlStreamReader &reader, CTX &ctx)
199 {
200 while (reader.readNextStartElement()) {
201 if (reader.name() == "TileMatrixSet")
202 tileMatrixSet(reader, ctx);
203 else if (reader.name() == "Layer")
204 layer(reader, ctx);
205 else
206 reader.skipCurrentElement();
207 }
208 }
209
capabilities(QXmlStreamReader & reader,CTX & ctx)210 void WMTS::capabilities(QXmlStreamReader &reader, CTX &ctx)
211 {
212 while (reader.readNextStartElement()) {
213 if (reader.name() == "Contents")
214 contents(reader, ctx);
215 else
216 reader.skipCurrentElement();
217 }
218 }
219
createZooms(const CTX & ctx)220 void WMTS::createZooms(const CTX &ctx)
221 {
222 for (QSet<TileMatrix>::const_iterator mi = ctx.matrixes.constBegin();
223 mi != ctx.matrixes.constEnd(); ++mi) {
224 QSet<MatrixLimits>::const_iterator li = ctx.limits.find(
225 MatrixLimits(mi->id));
226 if (!ctx.limits.isEmpty() && li == ctx.limits.constEnd())
227 continue;
228 _zooms.append(Zoom(mi->id, mi->scaleDenominator, mi->topLeft, mi->tile,
229 mi->matrix, li == ctx.limits.constEnd() ? QRect() : li->rect));
230 }
231
232 qSort(_zooms);
233 }
234
parseCapabilities(CTX & ctx)235 bool WMTS::parseCapabilities(CTX &ctx)
236 {
237 QFile file(_path);
238 QXmlStreamReader reader;
239
240 if (!file.open(QFile::ReadOnly | QFile::Text)) {
241 _errorString = file.errorString();
242 return false;
243 }
244
245 reader.setDevice(&file);
246 if (reader.readNextStartElement()) {
247 if (reader.name() == "Capabilities")
248 capabilities(reader, ctx);
249 else
250 reader.raiseError("Not a Capabilities XML file");
251 }
252 if (reader.error()) {
253 _errorString = QString("%1:%2: %3").arg(_path).arg(reader.lineNumber())
254 .arg(reader.errorString());
255 return false;
256 }
257
258 if (!ctx.hasLayer) {
259 _errorString = _setup.layer() + ": layer not provided";
260 return false;
261 }
262 if (!ctx.hasStyle && !_setup.style().isEmpty()) {
263 _errorString = _setup.style() + ": style not provided";
264 return false;
265 }
266 if (!ctx.hasStyle && _setup.style().isEmpty()
267 && ctx.defaultStyle.isEmpty()) {
268 _errorString = "Default style not provided";
269 return false;
270 }
271 if (!_setup.rest() && !ctx.hasFormat) {
272 _errorString = _setup.format() + ": format not provided";
273 return false;
274 }
275 if (!ctx.hasSet) {
276 _errorString = _setup.set() + ": set not provided";
277 return false;
278 }
279 if (ctx.crs.isNull()) {
280 _errorString = "Missing CRS definition";
281 return false;
282 }
283 _projection = CRS::projection(ctx.crs);
284 if (!_projection.isValid()) {
285 _errorString = ctx.crs + ": unknown CRS";
286 return false;
287 }
288 createZooms(ctx);
289 if (_zooms.isEmpty()) {
290 _errorString = "No usable tile matrix found";
291 return false;
292 }
293 if (_setup.rest() && _tileUrl.isNull()) {
294 _errorString = "Missing tile URL template";
295 return false;
296 }
297 _bbox = ctx.bbox;
298 _cs = (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
299 ? _projection.coordinateSystem() : _setup.coordinateSystem();
300
301 return true;
302 }
303
downloadCapabilities(const QString & url)304 bool WMTS::downloadCapabilities(const QString &url)
305 {
306 if (!_downloader) {
307 _downloader = new Downloader(this);
308 connect(_downloader, SIGNAL(finished()), this,
309 SLOT(capabilitiesReady()));
310 }
311
312 QList<Download> dl;
313 dl.append(Download(url, _path));
314
315 return _downloader->get(dl, _setup.authorization());
316 }
317
capabilitiesReady()318 void WMTS::capabilitiesReady()
319 {
320 if (!QFileInfo(_path).exists()) {
321 _errorString = "Error downloading capabilities XML file";
322 _valid = false;
323 } else {
324 _ready = true;
325 _valid = init();
326 }
327
328 emit downloadFinished();
329 }
330
init()331 bool WMTS::init()
332 {
333 CTX ctx;
334 if (!parseCapabilities(ctx))
335 return false;
336
337 QString style = _setup.style().isEmpty() ? ctx.defaultStyle : _setup.style();
338 if (!_setup.rest()) {
339 _tileUrl = QString("%1%2service=WMTS&Version=1.0.0&request=GetTile"
340 "&Format=%3&Layer=%4&Style=%5&TileMatrixSet=%6&TileMatrix=$z"
341 "&TileRow=$y&TileCol=$x").arg(_setup.url(),
342 _setup.url().contains('?') ? "&" : "?" , _setup.format(),
343 _setup.layer(), style, _setup.set());
344 for (int i = 0; i < _setup.dimensions().size(); i++) {
345 const KV<QString, QString> &dim = _setup.dimensions().at(i);
346 _tileUrl.append(QString("&%1=%2").arg(dim.key(), dim.value()));
347 }
348 } else {
349 _tileUrl.replace("{Style}", style, Qt::CaseInsensitive);
350 _tileUrl.replace("{TileMatrixSet}", _setup.set(), Qt::CaseInsensitive);
351 _tileUrl.replace("{TileMatrix}", "$z", Qt::CaseInsensitive);
352 _tileUrl.replace("{TileRow}", "$y", Qt::CaseInsensitive);
353 _tileUrl.replace("{TileCol}", "$x", Qt::CaseInsensitive);
354 for (int i = 0; i < _setup.dimensions().size(); i++) {
355 const KV<QString, QString> &dim = _setup.dimensions().at(i);
356 _tileUrl.replace(QString("{%1}").arg(dim.key()), dim.value(),
357 Qt::CaseInsensitive);
358 }
359 }
360
361 return true;
362 }
363
WMTS(const QString & file,const WMTS::Setup & setup,QObject * parent)364 WMTS::WMTS(const QString &file, const WMTS::Setup &setup, QObject *parent)
365 : QObject(parent), _setup(setup), _downloader(0), _valid(false), _ready(false)
366 {
367 QUrl url(setup.rest() ? setup.url() : QString(
368 "%1%2service=WMTS&Version=1.0.0&request=GetCapabilities").arg(setup.url(),
369 setup.url().contains('?') ? "&" : "?"));
370
371 _path = url.isLocalFile() ? url.toLocalFile() : file;
372
373 if (!url.isLocalFile() && !QFileInfo(file).exists())
374 _valid = downloadCapabilities(url.toString());
375 else {
376 _ready = true;
377 _valid = init();
378 }
379 }
380
381 #ifndef QT_NO_DEBUG
operator <<(QDebug dbg,const WMTS::Setup & setup)382 QDebug operator<<(QDebug dbg, const WMTS::Setup &setup)
383 {
384 dbg.nospace() << "Setup(" << setup.url() << ", " << setup.layer() << ", "
385 << setup.set() << ", " << setup.style() << ", " << setup.format() << ", "
386 << setup.rest() << ")";
387 return dbg.space();
388 }
389
operator <<(QDebug dbg,const WMTS::Zoom & zoom)390 QDebug operator<<(QDebug dbg, const WMTS::Zoom &zoom)
391 {
392 dbg.nospace() << "Zoom(" << zoom.id() << ", " << zoom.scaleDenominator()
393 << ", " << zoom.topLeft() << ", " << zoom.tile() << ", " << zoom.matrix()
394 << ", " << zoom.limits() << ")";
395 return dbg.space();
396 }
397 #endif // QT_NO_DEBUG
398