Commit 1b06fef1 authored by Volker Krause's avatar Volker Krause
Browse files

Initial version of an OSM/MapCSS-based indoor map renderer

This is an experiment to see if we could get to station and airport maps.

So far this contains a basic scene graph and a QPainter-based renderer,
map projection, primitive floor level separation, zoom level based LoD and
a MapCSS parser/evaluator. The last bit allows to define both the visual
appearance as well as the selection of which elements to show in a
completely declarative way (including live reloading at runtime).

There's still plenty of gaps though, many MapCSS properties aren't
supported yet, proper z ordering is missing, multi-polygons show render
glitches, cased lines aren't implemented yet, label layouting is non-
existent, and there is still the unsolved problem of actually getting
the OSM data. For the last bit, Marble's vector tile server looks most
promising, but needs a content update.

Anyway, considering how little time it took to get to this state, this
looks promising to pursue further.
parent 65566a1e
......@@ -33,6 +33,11 @@ find_package(ZLIB REQUIRED)
find_package(OsmTools)
set_package_properties(OsmTools PROPERTIES TYPE OPTIONAL PURPOSE "Needed only for regenereating line metadata tables (ie. you most likely don't need this)")
find_package(FLEX)
set_package_properties(FLEX PROPERTIES TYPE RECOMMENDED PURPOSE "MapCSS parser for indoor map rendering.")
find_package(BISON)
set_package_properties(BISON PROPERTIES TYPE RECOMMENDED PURPOSE "MapCSS parser for indoor map rendering.")
if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050d00)
add_definitions(-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x054400)
......
......@@ -2,3 +2,5 @@ add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
ecm_add_test(osmtypetest.cpp LINK_LIBRARIES Qt5::Test KOSM)
ecm_add_test(mapviewtest.cpp LINK_LIBRARIES Qt5::Test KOSMIndoorMap)
ecm_add_test(mapcssparsertest.cpp LINK_LIBRARIES Qt5::Test KOSMIndoorMap)
/** test file included via import */
area[railway=platform] {
color: #550022;
}
@import url("included.mapcss");
// union selector
area[railway=platform],
relation[type=multipolygon][railway=platform]
{
color: #ff550022;
fill-color: #80f0e0d0;
}
// chained selector
area[railway=platform] node[sign]
{
unsupportedproperty: todo;
fill-color: #ff0000;
opacity: 0.5;
}
// condition with colon-separated keys
node[building:part][building:part=elevator]
{
opacity: 1;
}
// text properties
* {
text: ref;
text-color: #ff00ff;
}
// line properties
line {
dashes: 3,5;
}
// zoom ranges
// TODO: some of this doesn't really work yet
// node|z12-13 {}
node|z10 {}
node|z-10 {}
node|z10- {}
//node|z12-13[name] {}
node|z14-[name] {}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <map/style/mapcssparser.h>
#include <map/style/mapcssstyle.h>
#include <QFile>
#include <QTest>
using namespace KOSMIndoorMap;
class MapCSSParserTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testParse()
{
MapCSSParser p;
const auto style = p.parse(QStringLiteral(SOURCE_DIR "/data/mapcss/parser-test.mapcss"));
QVERIFY(!p.hasError());
QFile outFile(QStringLiteral(SOURCE_DIR "/data/mapcss/parser-test.mapcss.out"));
QVERIFY(outFile.open(QFile::WriteOnly));
style.write(&outFile);
}
};
QTEST_GUILESS_MAIN(MapCSSParserTest)
#include "mapcssparsertest.moc"
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <map/renderer/view.h>
#include <QTest>
using namespace KOSMIndoorMap;
class MapViewTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testProjection()
{
View v;
QCOMPARE(v.mapGeoToScene(OSM::Coordinate{0.0, 0.0}), QPointF(128.0, 128.0));
QCOMPARE(v.mapGeoToScene(OSM::Coordinate{0.0, -180.0}), QPointF(0.0, 128.0));
QCOMPARE(v.mapGeoToScene(OSM::Coordinate{0.0, 180.0}), QPointF(256.0, 128.0));
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{90.0, 0.0}).y()) < 0.00001);
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{85.0512, 0.0}).y()) < 0.00001);
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{60.0, 0.0}).y() - 74.3423) < 0.00001);
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{-60.0, 0.0}).y() - 181.65769194) < 0.00001);
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{-85.0512, 0.0}).y() - 256.0) < 0.00001);
QVERIFY(std::abs(v.mapGeoToScene(OSM::Coordinate{-90.0, 0.0}).y() - 256.0) < 0.00001);
QCOMPARE(v.mapGeoToScene(OSM::BoundingBox{OSM::Coordinate{-90.0, -180.0}, OSM::Coordinate{90.0, 180.0}}).toRect(), QRect(0, 0, 256, 256));
QCOMPARE(v.mapGeoToScene(OSM::BoundingBox{OSM::Coordinate{0.0, 0.0}, OSM::Coordinate{90.0, 90.0}}).toRect(), QRect(128, 0, 64, 128));
}
void testViewport()
{
{
View v;
v.setScreenSize({100, 200});
v.setSceneBoundingBox(QRectF{QPointF{13.0, 52.0}, QPointF{14.0, 54.0}});
QCOMPARE(v.viewport(), QRectF(QPointF{13.0, 52.0}, QPointF{14.0, 54.0}));
} {
View v;
v.setScreenSize({200, 100});
v.setSceneBoundingBox(QRectF(QPointF{13.0, 52.0}, QPointF{14.0, 54.0}));
QCOMPARE(v.viewport(), QRectF(QPointF{13.0, 52.0}, QPointF{14.0, 52.5}));
} {
View v;
v.setScreenSize({100, 100});
v.setSceneBoundingBox(QRectF(QPointF{13.0, 52.0}, QPointF{14.0, 54.0}));
QCOMPARE(v.viewport(), QRectF(QPointF{13.0, 52.0}, QPointF{14.0, 53.0}));
}
}
void testTransform()
{
{
View v;
v.setScreenSize({100, 100});
v.setSceneBoundingBox(QRectF(QPointF{13.0, 53.0}, QPointF{14.0, 54.0}));
QCOMPARE(v.sceneToScreenTransform().m11(), 100.0);
QCOMPARE(v.sceneToScreenTransform().m22(), 100.0);
QCOMPARE(v.sceneToScreenTransform().m31(), -1300.0);
QCOMPARE(v.sceneToScreenTransform().m32(), -5300.0);
} {
View v;
v.setScreenSize({100, 100});
v.setSceneBoundingBox(QRectF(QPointF{13.0, 52.0}, QPointF{15.0, 53.0}));
QCOMPARE(v.sceneToScreenTransform().m11(), 100.0);
QCOMPARE(v.sceneToScreenTransform().m22(), 100.0);
QCOMPARE(v.sceneToScreenTransform().m31(), -1300.0);
QCOMPARE(v.sceneToScreenTransform().m32(), -5200.0);
} {
View v;
v.setScreenSize({100, 200});
v.setSceneBoundingBox(QRectF(QPointF{13.0, 52.0}, QPointF{13.1, 52.5}));
QCOMPARE(v.sceneToScreenTransform().m11(), 1000.0);
QCOMPARE(v.sceneToScreenTransform().m22(), 1000.0);
QCOMPARE(v.sceneToScreenTransform().m31(), -13000.0);
QCOMPARE(v.sceneToScreenTransform().m32(), -52000.0);
}
}
void testZoomLevel()
{
View v;
v.setScreenSize({512, 256});
v.setSceneBoundingBox(QRectF(QPointF{-180.0, -90.0}, QPointF{180.0, 90.0}));
QCOMPARE(v.zoomLevel(), 1.0);
v.setScreenSize({1024, 512});
v.setSceneBoundingBox(QRectF(QPointF{-180.0, -90.0}, QPointF{180.0, 90.0}));
QCOMPARE(v.zoomLevel(), 2.0);
v.zoomIn();
QCOMPARE(v.zoomLevel(), 3.0);
v.zoomOut();
QCOMPARE(v.zoomLevel(), 2.0);
}
};
QTEST_GUILESS_MAIN(MapViewTest)
#include "mapviewtest.moc"
add_subdirectory(osm)
if (BISON_FOUND AND FLEX_FOUND)
add_subdirectory(map)
endif()
if (NOT CROSS_COMPILING)
add_subdirectory(tools)
endif()
flex_target(mapcssscanner
style/mapcsslexer.l
${CMAKE_CURRENT_BINARY_DIR}/mapcsslexer.cpp
DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/mapcssscanner.h
)
bison_target(mapcssparser
style/mapcssparser.y
${CMAKE_CURRENT_BINARY_DIR}/mapcssparser_p.cpp
DEFINES_FILE ${CMAKE_CURRENT_BINARY_DIR}/mapcssparser_p.h
)
add_flex_bison_dependency(mapcssscanner mapcssparser)
add_library(KOSMIndoorMap STATIC
assets/assets.qrc
loader/maploader.cpp
renderer/painterrenderer.cpp
renderer/view.cpp
scene/scenecontroller.cpp
scene/scenegraph.cpp
scene/scenegraphitem.cpp
style/mapcsscondition.cpp
style/mapcssdeclaration.cpp
style/mapcssparser.cpp
style/mapcssresult.cpp
style/mapcssrule.cpp
style/mapcssselector.cpp
style/mapcssstate.cpp
style/mapcssstyle.cpp
${BISON_mapcssparser_OUTPUTS}
${FLEX_mapcssscanner_OUTPUTS}
)
target_include_directories(KOSMIndoorMap PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
target_include_directories(KOSMIndoorMap PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
target_link_libraries(KOSMIndoorMap
PUBLIC Qt5::Gui KOSM
)
<RCC>
<qresource prefix="/org.kde.kosmindoormap/assets/">
<file>css/breeze-common.mapcss</file>
<file>css/breeze-dark.mapcss</file>
<file>css/breeze-light.mapcss</file>
<file>css/diagnostic.mapcss</file>
</qresource>
</RCC>
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* common definitions between light and dark style */
/** Various outdoor stuff. */
area[water]
{
fill-color: #103daee9;
z-index: -1000;
}
*[leisure=park],
*[landuse=grass]
{
fill-color: #1027ae60;
z-index: -999;
}
area|z17-[amenity=parking]
{
text: "P";
text-color: #3daee9;
}
area|z17-[amenity=parking][access=private]
{
text: "🚫";
}
*|z17-[amenity=bicycle_parking]
{
text: "🚲";
}
/** Railway tracks */
way[railway=rail],
way[railway=light_rail],
way[railway=subway],
way[railway=tram]
{
color: #31363b;
dashes: 2,2;
width: 1.5; // meter
}
/** Platforms */
area|z17-[railway=platform],
area|z17-[public_transport=platform],
relation|z17-[type=multipolygon][railway=platform],
way|z17-[public_transport=platform]
{
text: name;
}
area|z17-[railway=platform][ref],
area|z17-[public_transport=platform][ref],
relation|z17-[type=multipolygon][railway=platform][ref],
way|z17-[public_transport=platform][ref]
{
text: ref;
}
area|z17-[railway=platform][local_ref],
area|z17-[public_transport=platform][local_ref],
relation|z17-[type=multipolygon][railway=platform][local_ref],
way|z17-[public_transport=platform][local_ref]
{
text: local_ref;
}
/** Shops/etc */
*|z19- [shop]
{
text: name;
}
/** icons */
node|z17-[vending=public_transport_tickets] { text: "🎫"; }
area|z18-[room=toilets],
relation|z16-[type=multipolygon][room=toilets],
node|z18-[amenity=toilets]
{
text: "🚻";
}
node|z18-[amenity=atm] { text: "🏧"; }
*|z18-[amenity=pharmacy] { text: "⚕"; }
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Dark theme stylesheet */
@import url("breeze-common.mapcss");
canvas {
fill-color: #232629;
text-color: #eff0f1;
}
/** Generic outdoor stuff */
area[amenity=parking] { fill-color: #31363b; }
area[building],
relation[type=multipolygon][man_made=bridge]
{
fill-color: #20bdc3c7;
z-index: -20;
}
/** Generic building stuff */
area[indoor],
relation[type=multipolygon][building],
relation[type=multipolygon][building:part=yes]
{
fill-color: #80bdc3c7;
}
area[indoor=corridor],
area[stairwell],
relation[type=multipolygon][indoor=corridor],
area[tunnel]
{
fill-color: #bdc3c7;
}
area[stairwell] { color: #7f8c8d; width: 0; }
area[indoor=room],
area[shop],
relation[type=multipolygon][indoor=room]
{
fill-color: #7f8c8d;
color: #000000;
width: 0; // 1px
}
area[indoor=wall]
{
fill-color: #7f8c8d;
}
line[highway=steps] { color: #f67400; width: 1; z-index: 1; }
line[highway=steps][conveying] { color: #27ae60; width: 1; }
/** Railway stuff */
area[railway=platform],
area[public_transport=platform],
relation[type=multipolygon][railway=platform]
{
fill-color: #31363b;
color: #bdc3c7;
width: 0;
}
line[public_transport=platform]
{
color: #31363b;
width: 1.5; // meter
}
/** Specific elements */
area[room=toilets],
relation[type=multipolygon][room=toilets]
{
fill-color: #3daee9;
}
area[building:part=elevator]
{
fill-color: #27ae60;
color: #7f8c8d;
width: 0;
}
area[shop=ticket]
{
fill-color: #da4453;
}
/** Restricted access, disused, etc: keep this last. */
area[indoor=room][access=private],
area[shop=vacant]
{
fill-color: #4d4d4d;
}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** Light theme stylesheet */
@import url("breeze-common.mapcss");
canvas {
fill-color: #fcfcfc;
text-color: #31363b;
}
/** Generic outdoor stuff */
area[amenity=parking] { fill-color: #f8f7f6; }
area[building],
relation[type=multipolygon][man_made=bridge]
{
fill-color: #20eff0f1;
z-index: -20;
}
/** Generic building stuff */
area[indoor],
relation[type=multipolygon][building],
relation[type=multipolygon][building:part=yes]
{
fill-color: #80eff0f1;
}
area[indoor=corridor],
area[stairwell],
relation[type=multipolygon][indoor=corridor],
area[tunnel]
{
fill-color: #eff0f1;
}
area[stairwell] { color: #e0dfde; width: 0; }
area[indoor=room],
area[shop],
relation[type=multipolygon][indoor=room]
{
fill-color: #dad9d8;
color: #31363b;
width: 0; // 1px
}
area[indoor=wall]
{
fill-color: #888786;
}
line [highway=steps] { color: #b08000; width: 1; z-index: 1; }
line[highway=steps][conveying] { color: #006e28; width: 1; }
/** Railway stuff */
area[railway=platform],
area[public_transport=platform],
relation[type=multipolygon][railway=platform]
{
fill-color: #bdc3c7;
color: #31363b;
width: 0;
}
line[public_transport=platform]
{
color: #bdc3c7;
width: 1.5; // meter
}
/** Specific elements */
area[room=toilets],
relation[type=multipolygon][room=toilets]
{
fill-color: #8ecbe9;
}
area[building:part=elevator]
{
fill-color: #006e28;
color: #31363b;
width: 0;
}
area[shop=ticket]
{
fill-color: #bf0303;
}
/** Restricted access, disused, etc: keep this last. */
area[indoor=room][access=private],
area[shop=vacant]
{
fill-color: #888786;
}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.