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