2018-01-28 22:56:08 +01:00
|
|
|
#include <QFile>
|
|
|
|
#include <QXmlStreamReader>
|
|
|
|
#include "onlinemap.h"
|
2018-02-20 23:37:19 +01:00
|
|
|
#include "wmtsmap.h"
|
2018-03-30 10:25:05 +02:00
|
|
|
#include "wmsmap.h"
|
2018-09-22 14:17:24 +02:00
|
|
|
#include "osm.h"
|
2020-12-09 23:07:05 +01:00
|
|
|
#include "invalidmap.h"
|
2018-01-29 00:19:57 +01:00
|
|
|
#include "mapsource.h"
|
2018-01-28 22:56:08 +01:00
|
|
|
|
|
|
|
|
2018-09-25 21:07:44 +02:00
|
|
|
MapSource::Config::Config() : type(OSM), zooms(OSM::ZOOMS), bounds(OSM::BOUNDS),
|
2018-11-17 10:10:35 +01:00
|
|
|
format("image/png"), rest(false), tileRatio(1.0), tileSize(256),
|
|
|
|
scalable(false) {}
|
2018-02-27 01:02:22 +01:00
|
|
|
|
|
|
|
|
2018-04-07 18:42:25 +02:00
|
|
|
static CoordinateSystem coordinateSystem(QXmlStreamReader &reader)
|
2018-04-05 20:38:23 +02:00
|
|
|
{
|
|
|
|
QXmlStreamAttributes attr = reader.attributes();
|
2020-12-22 22:09:09 +01:00
|
|
|
if (attr.value("axis") == QLatin1String("yx"))
|
2018-04-05 21:13:48 +02:00
|
|
|
return CoordinateSystem::YX;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (attr.value("axis") == QLatin1String("xy"))
|
2018-04-05 21:13:48 +02:00
|
|
|
return CoordinateSystem::XY;
|
2018-04-05 20:38:23 +02:00
|
|
|
else
|
2018-04-05 21:13:48 +02:00
|
|
|
return CoordinateSystem::Unknown;
|
2018-04-05 20:38:23 +02:00
|
|
|
}
|
|
|
|
|
2018-02-27 21:50:29 +01:00
|
|
|
Range MapSource::zooms(QXmlStreamReader &reader)
|
2018-01-28 22:56:08 +01:00
|
|
|
{
|
|
|
|
const QXmlStreamAttributes &attr = reader.attributes();
|
|
|
|
int min, max;
|
|
|
|
bool res;
|
|
|
|
|
|
|
|
if (attr.hasAttribute("min")) {
|
|
|
|
min = attr.value("min").toString().toInt(&res);
|
2019-11-10 18:38:47 +01:00
|
|
|
if (!res || min < 0) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid minimal zoom level");
|
2018-02-27 21:50:29 +01:00
|
|
|
return Range();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
min = OSM::ZOOMS.min();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
|
|
|
if (attr.hasAttribute("max")) {
|
|
|
|
max = attr.value("max").toString().toInt(&res);
|
2019-11-10 18:38:47 +01:00
|
|
|
if (!res || max < min) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid maximal zoom level");
|
2018-02-27 21:50:29 +01:00
|
|
|
return Range();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
max = OSM::ZOOMS.max();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
2018-02-27 21:50:29 +01:00
|
|
|
return Range(min, max);
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
|
2018-02-27 21:50:29 +01:00
|
|
|
RectC MapSource::bounds(QXmlStreamReader &reader)
|
2018-01-28 22:56:08 +01:00
|
|
|
{
|
|
|
|
const QXmlStreamAttributes &attr = reader.attributes();
|
|
|
|
double top, left, bottom, right;
|
|
|
|
bool res;
|
|
|
|
|
|
|
|
if (attr.hasAttribute("top")) {
|
|
|
|
top = attr.value("top").toString().toDouble(&res);
|
2018-09-25 21:07:44 +02:00
|
|
|
if (!res || (top < OSM::BOUNDS.bottom() || top > OSM::BOUNDS.top())) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid bounds top value");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
top = OSM::BOUNDS.top();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
|
|
|
if (attr.hasAttribute("bottom")) {
|
|
|
|
bottom = attr.value("bottom").toString().toDouble(&res);
|
2018-09-25 21:07:44 +02:00
|
|
|
if (!res || (bottom < OSM::BOUNDS.bottom()
|
|
|
|
|| bottom > OSM::BOUNDS.top())) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid bounds bottom value");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
bottom = OSM::BOUNDS.bottom();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
|
|
|
if (attr.hasAttribute("left")) {
|
|
|
|
left = attr.value("left").toString().toDouble(&res);
|
2018-09-25 21:07:44 +02:00
|
|
|
if (!res || (left < OSM::BOUNDS.left() || left > OSM::BOUNDS.right())) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid bounds left value");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
left = OSM::BOUNDS.left();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
|
|
|
if (attr.hasAttribute("right")) {
|
|
|
|
right = attr.value("right").toString().toDouble(&res);
|
2018-09-25 21:07:44 +02:00
|
|
|
if (!res || (right < OSM::BOUNDS.left()
|
|
|
|
|| right > OSM::BOUNDS.right())) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid bounds right value");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
} else
|
2018-09-25 21:07:44 +02:00
|
|
|
right = OSM::BOUNDS.right();
|
2018-01-28 22:56:08 +01:00
|
|
|
|
2018-02-27 21:50:29 +01:00
|
|
|
if (bottom >= top) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid bottom/top bounds combination");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
2018-02-27 21:50:29 +01:00
|
|
|
if (left >= right) {
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.raiseError("Invalid left/right bounds combination");
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC();
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
|
2018-02-27 21:50:29 +01:00
|
|
|
return RectC(Coordinates(left, top), Coordinates(right, bottom));
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
|
|
|
|
2018-12-08 02:27:27 +01:00
|
|
|
void MapSource::tile(QXmlStreamReader &reader, Config &config)
|
|
|
|
{
|
|
|
|
QXmlStreamAttributes attr = reader.attributes();
|
|
|
|
bool ok;
|
|
|
|
|
|
|
|
if (attr.hasAttribute("size")) {
|
|
|
|
int size = attr.value("size").toString().toInt(&ok);
|
|
|
|
if (!ok || size < 0) {
|
|
|
|
reader.raiseError("Invalid tile size");
|
|
|
|
return;
|
|
|
|
} else
|
|
|
|
config.tileSize = size;
|
|
|
|
}
|
|
|
|
if (attr.hasAttribute("type")) {
|
2020-12-22 22:09:09 +01:00
|
|
|
if (attr.value("type") == QLatin1String("raster"))
|
2018-12-08 02:27:27 +01:00
|
|
|
config.scalable = false;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (attr.value("type") == QLatin1String("vector"))
|
2018-12-08 02:27:27 +01:00
|
|
|
config.scalable = true;
|
|
|
|
else {
|
|
|
|
reader.raiseError("Invalid tile type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (attr.hasAttribute("pixelRatio")) {
|
|
|
|
qreal ratio = attr.value("pixelRatio").toString().toDouble(&ok);
|
|
|
|
if (!ok || ratio < 0) {
|
|
|
|
reader.raiseError("Invalid tile pixelRatio");
|
|
|
|
return;
|
|
|
|
} else
|
|
|
|
config.tileRatio = ratio;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-27 01:02:22 +01:00
|
|
|
void MapSource::map(QXmlStreamReader &reader, Config &config)
|
2018-01-28 22:56:08 +01:00
|
|
|
{
|
2018-03-30 10:25:05 +02:00
|
|
|
const QXmlStreamAttributes &attr = reader.attributes();
|
2020-12-22 22:09:09 +01:00
|
|
|
QStringView type = attr.value("type");
|
2018-09-22 13:32:54 +02:00
|
|
|
|
2020-12-22 22:09:09 +01:00
|
|
|
if (type == QLatin1String("WMTS"))
|
2018-09-22 13:32:54 +02:00
|
|
|
config.type = WMTS;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (type == QLatin1String("WMS"))
|
2018-09-22 13:32:54 +02:00
|
|
|
config.type = WMS;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (type == QLatin1String("TMS"))
|
2018-09-22 13:32:54 +02:00
|
|
|
config.type = TMS;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (type == QLatin1String("QuadTiles"))
|
2019-05-20 23:23:24 +02:00
|
|
|
config.type = QuadTiles;
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (type == QLatin1String("OSM") || type.isEmpty())
|
2018-09-22 13:32:54 +02:00
|
|
|
config.type = OSM;
|
|
|
|
else {
|
|
|
|
reader.raiseError("Invalid map type");
|
|
|
|
return;
|
|
|
|
}
|
2018-02-20 23:37:19 +01:00
|
|
|
|
2018-01-28 22:56:08 +01:00
|
|
|
while (reader.readNextStartElement()) {
|
2020-12-22 22:09:09 +01:00
|
|
|
if (reader.name() == QLatin1String("name"))
|
2018-02-27 01:02:22 +01:00
|
|
|
config.name = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (reader.name() == QLatin1String("url")) {
|
|
|
|
config.rest = (reader.attributes().value("type")
|
|
|
|
== QLatin1String("REST")) ? true : false;
|
2018-02-27 01:02:22 +01:00
|
|
|
config.url = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("zoom")) {
|
2018-03-30 10:25:05 +02:00
|
|
|
config.zooms = zooms(reader);
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.skipCurrentElement();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("bounds")) {
|
2018-03-30 10:25:05 +02:00
|
|
|
config.bounds = bounds(reader);
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.skipCurrentElement();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("format"))
|
2018-03-30 10:25:05 +02:00
|
|
|
config.format = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (reader.name() == QLatin1String("layer"))
|
2018-03-30 10:25:05 +02:00
|
|
|
config.layer = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (reader.name() == QLatin1String("style"))
|
2018-03-30 10:25:05 +02:00
|
|
|
config.style = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
else if (reader.name() == QLatin1String("set")) {
|
2018-04-07 18:42:25 +02:00
|
|
|
config.coordinateSystem = coordinateSystem(reader);
|
2018-03-30 10:25:05 +02:00
|
|
|
config.set = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("dimension")) {
|
2018-03-11 10:31:41 +01:00
|
|
|
QXmlStreamAttributes attr = reader.attributes();
|
|
|
|
if (!attr.hasAttribute("id"))
|
|
|
|
reader.raiseError("Missing dimension id");
|
|
|
|
else
|
2019-08-01 08:36:58 +02:00
|
|
|
config.dimensions.append(KV<QString, QString>
|
|
|
|
(attr.value("id").toString(), reader.readElementText()));
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("crs")) {
|
2018-04-07 18:42:25 +02:00
|
|
|
config.coordinateSystem = coordinateSystem(reader);
|
2018-03-30 10:25:05 +02:00
|
|
|
config.crs = reader.readElementText();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("authorization")) {
|
2018-04-01 20:01:25 +02:00
|
|
|
QXmlStreamAttributes attr = reader.attributes();
|
|
|
|
config.authorization = Authorization(
|
|
|
|
attr.value("username").toString(),
|
|
|
|
attr.value("password").toString());
|
|
|
|
reader.skipCurrentElement();
|
2020-12-22 22:09:09 +01:00
|
|
|
} else if (reader.name() == QLatin1String("tile")) {
|
2018-12-08 02:27:27 +01:00
|
|
|
tile(reader, config);
|
|
|
|
reader.skipCurrentElement();
|
2018-02-25 08:58:40 +01:00
|
|
|
} else
|
2018-01-28 22:56:08 +01:00
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 09:38:35 +02:00
|
|
|
Map *MapSource::create(const QString &path, bool *isDir)
|
2018-01-28 22:56:08 +01:00
|
|
|
{
|
2018-02-27 01:02:22 +01:00
|
|
|
Config config;
|
2018-05-22 22:40:15 +02:00
|
|
|
QFile file(path);
|
2018-03-30 10:25:05 +02:00
|
|
|
|
2018-01-28 22:56:08 +01:00
|
|
|
|
2022-04-29 23:16:10 +02:00
|
|
|
if (isDir)
|
|
|
|
*isDir = false;
|
|
|
|
|
2020-12-09 23:07:05 +01:00
|
|
|
if (!file.open(QFile::ReadOnly | QFile::Text))
|
|
|
|
return new InvalidMap(path, file.errorString());
|
2018-01-28 22:56:08 +01:00
|
|
|
|
2018-05-22 22:40:15 +02:00
|
|
|
QXmlStreamReader reader(&file);
|
2018-01-28 22:56:08 +01:00
|
|
|
if (reader.readNextStartElement()) {
|
2020-12-22 22:09:09 +01:00
|
|
|
if (reader.name() == QLatin1String("map"))
|
2018-02-27 01:02:22 +01:00
|
|
|
map(reader, config);
|
2018-01-28 22:56:08 +01:00
|
|
|
else
|
2018-01-29 00:19:57 +01:00
|
|
|
reader.raiseError("Not an online map source file");
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|
2020-12-09 23:07:05 +01:00
|
|
|
if (reader.error())
|
|
|
|
return new InvalidMap(path, QString("%1: %2").arg(reader.lineNumber())
|
|
|
|
.arg(reader.errorString()));
|
2018-02-27 01:02:22 +01:00
|
|
|
|
2020-12-09 23:07:05 +01:00
|
|
|
if (config.name.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing name definition");
|
|
|
|
if (config.url.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing URL definition");
|
2018-03-30 10:25:05 +02:00
|
|
|
if (config.type == WMTS || config.type == WMS) {
|
2020-12-09 23:07:05 +01:00
|
|
|
if (config.layer.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing layer definition");
|
|
|
|
if (config.format.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing format definition");
|
2018-03-30 10:25:05 +02:00
|
|
|
}
|
|
|
|
if (config.type == WMTS) {
|
2020-12-09 23:07:05 +01:00
|
|
|
if (config.set.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing set definiton");
|
2018-03-30 10:25:05 +02:00
|
|
|
}
|
|
|
|
if (config.type == WMS) {
|
2020-12-09 23:07:05 +01:00
|
|
|
if (config.crs.isEmpty())
|
|
|
|
return new InvalidMap(path, "Missing CRS definiton");
|
2018-02-27 01:02:22 +01:00
|
|
|
}
|
|
|
|
|
2018-09-22 13:32:54 +02:00
|
|
|
switch (config.type) {
|
|
|
|
case WMTS:
|
2020-12-02 23:58:11 +01:00
|
|
|
return new WMTSMap(path, config.name, WMTS::Setup(config.url,
|
|
|
|
config.layer, config.set, config.style, config.format, config.rest,
|
2018-09-22 13:32:54 +02:00
|
|
|
config.coordinateSystem, config.dimensions, config.authorization),
|
|
|
|
config.tileRatio);
|
|
|
|
case WMS:
|
2020-12-02 23:58:11 +01:00
|
|
|
return new WMSMap(path, config.name, WMS::Setup(config.url,
|
|
|
|
config.layer, config.style, config.format, config.crs,
|
|
|
|
config.coordinateSystem, config.dimensions, config.authorization),
|
|
|
|
config.tileSize);
|
2018-09-22 13:32:54 +02:00
|
|
|
case TMS:
|
2020-12-02 23:58:11 +01:00
|
|
|
return new OnlineMap(path, config.name, config.url, config.zooms,
|
2018-11-15 00:38:03 +01:00
|
|
|
config.bounds, config.tileRatio, config.authorization,
|
2019-05-20 23:23:24 +02:00
|
|
|
config.tileSize, config.scalable, true, false);
|
2018-09-22 13:32:54 +02:00
|
|
|
case OSM:
|
2020-12-02 23:58:11 +01:00
|
|
|
return new OnlineMap(path, config.name, config.url, config.zooms,
|
2018-11-15 00:38:03 +01:00
|
|
|
config.bounds, config.tileRatio, config.authorization,
|
2019-05-20 23:23:24 +02:00
|
|
|
config.tileSize, config.scalable, false, false);
|
|
|
|
case QuadTiles:
|
2020-12-02 23:58:11 +01:00
|
|
|
return new OnlineMap(path, config.name, config.url, config.zooms,
|
2019-05-20 23:23:24 +02:00
|
|
|
config.bounds, config.tileRatio, config.authorization,
|
|
|
|
config.tileSize, config.scalable, false, true);
|
2018-09-22 13:32:54 +02:00
|
|
|
default:
|
2020-12-09 23:07:05 +01:00
|
|
|
return new InvalidMap(path, "Invalid map type");
|
2018-09-22 13:32:54 +02:00
|
|
|
}
|
2018-01-28 22:56:08 +01:00
|
|
|
}
|