diff --git a/CMakeLists.txt b/CMakeLists.txt index d4894e601..3227e5076 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,14 @@ if(Boost_FOUND) set(Boost_DIR ${Boost_INCLUDE_DIR}) endif() +# Libigl +FetchContent_Declare( + libigl + GIT_REPOSITORY https://github.com/libigl/libigl.git + GIT_TAG v2.4.0 +) + +# Eigen FetchContent_Declare( eigen GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git @@ -159,7 +167,7 @@ FetchContent_Declare( set(BUILD_TESTING FALSE CACHE INTERNAL "Build the testing tree." FORCE) mark_as_advanced(FORCE BUILD_TESTING) -FetchContent_MakeAvailable(eigen) +FetchContent_MakeAvailable(eigen libigl) FetchContent_Declare( tclap @@ -241,6 +249,10 @@ if(Qt5_FOUND AND WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt) endif() endif() +#------------------------------------------------------------------------------ +# Setup libigl +option(LIBIGL_RESTRICTED_TRIANGLE "Enable Triangle library" ON) +igl_include(restricted triangle) #------------------------------------------------------------------------------ FetchContent_Declare( @@ -403,6 +415,8 @@ set(OpenInfraPlatform_UserInterface_Forms UserInterface/Forms/MainWindow.ui UserInterface/Forms/LicenseAndCopyrightInformation.ui UserInterface/Forms/PreferencesDialog.ui + UserInterface/Forms/HttpDownloadDialog.ui + UserInterface/Forms/HttpTreeModelDialog.ui # Currently not included. #UserInterface/Forms/CreateAccidentReport.ui @@ -521,6 +535,7 @@ target_link_libraries( OpenInfraPlatform.UI Qt5::Core Qt5::Gui Qt5::Widgets + Qt5::Network Qt5::Svg Qt5::PrintSupport Qt5::Quick @@ -542,6 +557,7 @@ target_link_libraries( OpenInfraPlatform.UI Rpcrt4.lib eigen ${PROJ_LIBRARIES} + igl_restricted::triangle ) set(QT_WINDEPLOY_FLAGS -xml -concurrent) diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index c743b1ab3..112065372 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -27,6 +27,7 @@ file(GLOB Core_DataManagement_Source src/DataManagement/*.*) file(GLOB Core_DataManagement_General_Source src/DataManagement/General/*.*) file(GLOB Core_IfcGeometryConverter_Source src/IfcGeometryConverter/*.*) file(GLOB Core_OffConverter_Source src/OffConverter/*.*) +file(GLOB Core_OsmDataConverter_Source src/OsmDataConverter/*.*) file(GLOB Core_Exception_Source src/Exception/*.*) file(GLOB Core_SplineInterpretation_Source src/SplineInterpretation/*.*) @@ -35,6 +36,7 @@ source_group(Core\\DataManagement FILES ${Core_DataManagement_Source}) source_group(Core\\DataManagement\\General FILES ${Core_DataManagement_General_Source}) source_group(Core\\IfcGeometryConverter FILES ${Core_IfcGeometryConverter_Source}) source_group(Core\\OffConverter FILES ${Core_OffConverter_Source}) +source_group(Core\\OsmDataConverter FILES ${Core_OsmDataConverter_Source}) source_group(Core\\Exception FILES ${Core_Exception_Source}) source_group(Core\\SplineInterpretation FILES ${Core_SplineInterpretation_Source}) @@ -44,6 +46,7 @@ add_library(OpenInfraPlatform.Core STATIC ${Core_DataManagement_General_Source} ${Core_IfcGeometryConverter_Source} ${Core_OffConverter_Source} + ${Core_OsmDataConverter_Source} ${Core_Exception_Source} ${Core_SplineInterpretation_Source} ) @@ -67,6 +70,7 @@ target_link_libraries(OpenInfraPlatform.Core PRIVATE BlueFramework.Application carve eigen + igl_restricted::triangle ) target_link_libraries(OpenInfraPlatform.Core PUBLIC OpenInfraPlatform.Base) diff --git a/Core/src/DataManagement/General/Data.cpp b/Core/src/DataManagement/General/Data.cpp index 303974cd7..32404e3fe 100644 --- a/Core/src/DataManagement/General/Data.cpp +++ b/Core/src/DataManagement/General/Data.cpp @@ -54,6 +54,8 @@ #include "IfcGeometryConverter\IfcImporterImpl.h" #include "OffConverter\OffModel.h" #include "OffConverter\OffReader.h" +#include "OsmDataConverter\OsmDataModel.h" +#include "OsmDataConverter\OsmDataReader.h" #include "Exception\IfcPeekReaderException.h" #include @@ -207,6 +209,21 @@ void OpenInfraPlatform::Core::DataManagement::Data::importJob(const std::string& return; } + else if (filetype == ".xml") { + // update status + OpenInfraPlatform::AsyncJob::getInstance().updateStatus(std::string("OSM data meshing in progress!")); + // import osmReader + OpenInfraPlatform::Core::OsmDataConverter::OsmReader osmReader; + // read the '.xml' file + auto osmModel = osmReader.readFile(filename); + // add the model to list of models (if any) + addModel(osmModel); + // change flag to OsmDataGeo. type + latestChangeFlag_ = ChangeFlag::OsmDataGeometry; + return; + } + + #ifdef OIP_WITH_POINT_CLOUD_PROCESSING QString extension = QString(filetype.substr(1, filetype.size() - 1).data()); if (buw::PointCloud::GetSupportedExtensions().contains(extension) diff --git a/Core/src/DataManagement/General/Data.h b/Core/src/DataManagement/General/Data.h index 8838abf31..1f44cd854 100644 --- a/Core/src/DataManagement/General/Data.h +++ b/Core/src/DataManagement/General/Data.h @@ -22,6 +22,7 @@ #include "..\EarlyBinding\src\EXPRESS\EXPRESSModel.h" #include "..\Core\src\IfcGeometryConverter\ConverterBuw.h" #include "..\Core\src\IfcGeometryConverter\IfcGeometryModel.h" +#include "..\Core\src\OsmDataConverter\OsmDataReader.h" #include #include @@ -52,7 +53,8 @@ namespace OpenInfraPlatform PointCloud = 1 << 1, Preferences = 1 << 2, OffGeometry = 1 << 3, - All = IfcGeometry | PointCloud | Preferences | OffGeometry + OsmDataGeometry = 1 << 4, + All = IfcGeometry | PointCloud | Preferences | OffGeometry | OsmDataGeometry }; inline ChangeFlag operator|(ChangeFlag a, ChangeFlag b) diff --git a/Core/src/Exception/OsmDataReaderException.h b/Core/src/Exception/OsmDataReaderException.h new file mode 100644 index 000000000..62143a290 --- /dev/null +++ b/Core/src/Exception/OsmDataReaderException.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#pragma once + +#ifndef OSMDATAREADEREXCEPTION_H +#define OSMDATAREADEREXCEPTION_H + +#include "namespace.h" + +#include +#include + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_EXCEPTION_BEGIN + +class OsmReaderException : public std::exception +{ +public: + + OsmReaderException(const std::string& msg) noexcept + { + message_ = msg; + } + + virtual char const* what() const override + { + return message_.data(); + } + +private: + std::string message_; + +}; + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_EXCEPTION_END + +EMBED_CORE_EXCEPTION_INTO_OIP_NAMESPACE(OsmReaderException); + +#endif // !OSMDATAREADEREXCEPTION_H diff --git a/Core/src/OsmDataConverter/OsmDataGeometry.cpp b/Core/src/OsmDataConverter/OsmDataGeometry.cpp new file mode 100644 index 000000000..cdabe7821 --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataGeometry.cpp @@ -0,0 +1,1035 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#include "OsmDataGeometry.h" + +OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::OsmGeometry() +{ + +} + + +OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::~OsmGeometry() +{ + +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::initializeContainer(const QDomElement& root, const QString attr) +{ + // store all 'relation' data type + osmGeometryContainer_.relationList = toQDomElementVector(root.elementsByTagName("relation")); + // store all 'way' data type + osmGeometryContainer_.wayList = toQDomElementVector(root.elementsByTagName("way")); + + // store bbox information for relations of 'miscellaneous' types + for (const auto& relationelement : osmGeometryContainer_.relationList) { + // keys values + if (retrieveFeatureAttribute>(relationelement, { "type", "building" }, { "site", "building" }).isEmpty()) + addToFeatureBBox(relationelement.attribute(attr), relationelement.firstChildElement()); + } + + // map id to position ['way'] + // store bbox information for ways of 'miscellaneous' types + for (unsigned int i = 0; i < osmGeometryContainer_.wayList.size(); i++) { + + QDomElement wayElement = osmGeometryContainer_.wayList.at(i).toElement(); + + // map id to index + QString id = wayElement.attribute(attr); + osmGeometryContainer_.wayIdPosition[id] = i; + + // add bbox information; only if 'miscellaneous' element + // keys + if (retrieveFeatureAttribute>(wayElement, { "building", "highway" }).isEmpty()) + addToFeatureBBox(id, wayElement.firstChildElement()); + } + + + // initalize highway width mapper + std::vector highwayType = { "residential", "tertiary", "secondary", "primary" }; + for (int highwayIt = 0; highwayIt < highwayType.size(); highwayIt++) { + + //double width = (highwayIt * 2.0) + 4.0; + double width = highwayIt + 4.0; + osmGeometryContainer_.highwayWidth[highwayIt] = std::make_pair(highwayType.at(highwayIt), width); + } + + // store all miscellaneous types + osmGeometryContainer_.miscTypes = { "amenity", "leisure", "landuse", "natural" }; + + // store all sub-feature colors (as HexRgb) in mapper + std::vector subFeature = setSubFeatures(); + std::vector subFeaColor = setSubFeatureColor(); + + for (unsigned int i = 0; i < subFeature.size(); i++) + osmGeometryContainer_.hexRGBScheme[subFeature[i]] = subFeaColor[i]; +} + + +std::vector OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::toQDomElementVector(const QDomNodeList nodeList) +{ + std::vector elementList(nodeList.size()); + std::generate(elementList.begin(), elementList.end(), [nodeList, i = 0]() mutable {return nodeList.item(i++).toElement(); }); + + return elementList; +} + + +std::vector OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::setSubFeatures() +{ + return std::vector { "grass", "forest", "wood", "park", "hospital", "farm", "cemetery", "playground", "pitch", "water", "school", "university", + "parking", "residential", "tertiary", "secondary", "primary", "building", "construction", "site" }; +} + + +std::vector OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::setSubFeatureColor() +{ + // grass forest wood park hospital farm + return std::vector { QColor("#16300d"), QColor("#8dc56c"), QColor("#aed1a0"), QColor("#b6fdb6"), QColor("#ffffc0"), QColor("#ead8bd"), + // cemetery playground pitch water school university parking + QColor("#aacbaf"), QColor("#dffce2"), QColor("#8ad3af"), QColor("#aad3df"), QColor("#f0f0d8"), QColor("#292828"), QColor("#0f0f0d"), + // residential tertiary secondary primary building construction site + QColor("#0d0d0d"), QColor("#0d0d0d"), QColor("#16180d"), QColor("#4a280d"), QColor("#f5f5dc"), QColor("#40402f"), QColor("#f5f5dc") }; +} + + +//void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::addToFeatureBBox(const QString featureID, const QString elementType, const QDomElement& boundsElement) +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::addToFeatureBBox(const QString featureID, const QDomElement& boundsElement) +{ + // get bbox infromation from bounds element + double minlat = boundsElement.attribute("minlat").toDouble(); + double minlon = boundsElement.attribute("minlon").toDouble(); + double maxlat = boundsElement.attribute("maxlat").toDouble(); + double maxlon = boundsElement.attribute("maxlon").toDouble(); + + // store the bbox infromation + osmGeometryContainer_.idBBox[featureID] = std::make_tuple(minlat, minlon, maxlat, maxlon); +} + + +double OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::roundOff(const double value, const bool roundDown, const double precision) +{ + if (roundDown) + return std::floor(value / precision) * precision; + return std::ceil(value / precision) * precision; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::applyPadding(double& minGeodeticCoord, double& maxGeodeticCoord, const double paddingValue) +{ + //minGeodeticCoord = roundOff(minGeodeticCoord, true); + //maxGeodeticCoord = roundOff(maxGeodeticCoord); + + // more control over the exact location of the bounding box corners + minGeodeticCoord -= paddingValue; + maxGeodeticCoord += paddingValue; +} + + +std::vector> OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::assembleGeodeticCoords(double& minlat, double& maxlat, double& minlon, double& maxlon) +{ + // padding value (obtained after trial & error) + const double paddingValue = 0.0010; + // latitude + applyPadding(minlat, maxlat, paddingValue); + // longitude + applyPadding(minlon, maxlon, paddingValue); + // S-W S-E N-E N-W + return std::vector>{ { minlat, minlon }, { minlat, maxlon }, { maxlat, maxlon }, { maxlat, minlon } }; +} + + +Polygon_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::createTerrain(const QDomElement& root, const QString tag, const double padding) +{ + // container + Polygon_t terrainGeo; + terrainGeo.first = "terrain"; + + // terrain bbox i.e. S-W & N-E coordinates + QDomElement terrainElement = root.namedItem(tag).toElement(); + + // retrieve min, max lat./long. & apply padding + double minlat = terrainElement.attribute("minlat").toDouble(); + double minlon = terrainElement.attribute("minlon").toDouble(); + double maxlat = terrainElement.attribute("maxlat").toDouble(); + double maxlon = terrainElement.attribute("maxlon").toDouble(); + + + // assemble geodetic coords & apply padding to make sure terrain always contains OSM features + std::vector> assembledGeoCoords = assembleGeodeticCoords(minlat, maxlat, minlon, maxlon); + + // elevation properties + unsigned int direction = static_cast(Direction::Z); + const double elevation = -0.5; + + // create terrain corner points (3D) + // N-W N-E (maxlat, maxlon) + // x ------- x + // | | -- z = 0 + // x ------- x + //(minlat, minlon) S-W S-E + // | + // x ------- x + // | | -- z = -z + // x ------- x + + Way_t terrainNodes; terrainNodes.reserve(8); + for (const auto& geoCoord : assembledGeoCoords) + { + Node_t n1 = projectWGS84(geoCoord[0], geoCoord[1], 0.0); + Node_t n2 = n1; + n2(direction) = n1(direction) + elevation; + terrainNodes.insert(terrainNodes.end(), { n1, n2 }); + } + terrainGeo.second.push_back(terrainNodes); + return terrainGeo; +} + + +buw::Vector3f OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::getColor(const QString subFeaName) +{ + if (osmGeometryContainer_.hexRGBScheme.count(subFeaName) > 0) + return hexRGBToFloat(osmGeometryContainer_.hexRGBScheme[subFeaName]); + + // default sub-feature color + return hexRGBToFloat(QColor("#ffffff")); +} + + +buw::Vector3f OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::hexRGBToFloat(const QColor hexRGBColor) +{ + buw::Vector3f floatColor; + floatColor.x() = hexRGBColor.redF(); + floatColor.y() = hexRGBColor.greenF(); + floatColor.z() = hexRGBColor.blueF(); + return floatColor; +} + + +double OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::retrieveBuildingHeight(const QDomElement element) +{ + // get all tags + QDomNodeList tags = getTags(element, "tag"); + + for (int i = 0; i < tags.count(); i++) { + QDomElement tag = tags.at(i).toElement(); + + // building:levels + if (tag.attribute("k") == "building:levels") + return ((tag.attribute("v").toDouble() * 6.0) + 6.0); + // height + else if (tag.attribute("k") == "height") + return tag.attribute("v").toDouble(); + } + // reference height (per floor in a building) + return 6.0; +} + + +bool OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::isValidMemberRelation(const QDomElement parentRelation) +{ + QDomNodeList members = getTags(parentRelation, "member"); + + for (unsigned int i = 0; i < members.count(); i++) { + QDomElement member = members.at(i).toElement(); + + if (member.attribute("type") == "relation") + return true; + + else if (isIncompleteGeometry(member)) + return true; + } + return false; +} + + +// https://stackoverflow.com/questions/71627443/calculate-offset-coordinate-in-a-cartesian-space +Way_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::offsetPoints(const Way_t elementNodes, const double& offsetDistance) +{ + Way_t offsetNodes; + offsetNodes.reserve(elementNodes.size() * 2); + + // start from 1 and get the previous line end point + for (unsigned int pointIt = 1; pointIt < elementNodes.size(); pointIt++) + { + // get start & end points of the line + const Node_t endNode = elementNodes.at(pointIt); + const Node_t startNode = elementNodes.at(pointIt - 1); + + // calculate the distance and direction vector + const Node_t dirVec = endNode - startNode; + const double dirLength = dirVec.norm(); + + Node_t normVec = { -dirVec.y(), dirVec.x(), 0.0 }; + normVec.normalize(); + + if (pointIt == 1) + addOffsetPoint(startNode, normVec, offsetDistance, offsetNodes); + + addOffsetPoint(endNode, normVec, offsetDistance, offsetNodes); + } + return offsetNodes; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::addOffsetPoint(const Node_t node, const Node_t normVec, const double offsetDistance, Way_t& offsetNodes) +{ + const Node_t offPos = node + normVec * offsetDistance; + const Node_t offNeg = node - normVec * offsetDistance; + offsetNodes.insert(offsetNodes.end(), { offPos, offNeg }); +} + + +QDomElement OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::mapIdToIndex(const QString id) +{ + // find index value + int index = findValue(id, osmGeometryContainer_.wayIdPosition); + + // if no reference of way found; return empty QDomElement + if (index == -1) + return QDomElement(); + + return osmGeometryContainer_.wayList.at(index).toElement(); +} + + +Node_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::extractLatLon(const QDomElement& nodeElement) +{ + // extract node information + double latitude = nodeElement.attribute("lat").toDouble(); + double longitude = nodeElement.attribute("lon").toDouble(); + + // convert from WGS-84 to Cartesian ECEF + return projectWGS84(latitude, longitude, 0.0); +} + + +// #TODO: this is approx. close but still not accurate (implement an accurate solution, if necessary) +Node_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::projectWGS84(double latitude, double longitude, double elevation) +{ + // convert latitude and longitude to radians + latitude = latitude * (M_PI / 180.0); + longitude = longitude * (M_PI / 180.0); + + // set radius of the Earth + const double earthRad = 6371000.0; + + // get Cartesian X,Y,Z + double x = earthRad * std::sin(latitude) * std::cos(longitude); + double y = earthRad * std::sin(latitude) * std::sin(longitude); + double z = elevation; + + return Node_t(-x, y, z); +} + + +bool OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::isRing(const Way_t& wayNodes) +{ + // 'building' and 'miscellaneous' features have 2 repition nodes to signify closed geo. + int repNode = 2; + // check first and last node + // only if size is greater than repition node + return wayNodes.size() >= repNode && wayNodes.front().isApprox(wayNodes[wayNodes.size() - repNode]); +} + + +bool OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::isIncompleteGeometry(const QDomElement& wayElement) +{ + QDomNodeList allNodes = getTags(wayElement, "nd"); + + for (unsigned int i = 0; i < allNodes.count(); i++) { + QDomElement nodeElement = allNodes.at(i).toElement(); + if (!hasLatLon(nodeElement)) + return true; + } + return false; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::extrude(Way_t& wayNodes, const double& extrusionFactor) +{ + // get the index (axis) to extrude + unsigned int direction = static_cast(extrusionDirection_); + + Way_t tmp = wayNodes; + unsigned int position = 1; + + for (auto it = tmp.begin(); it != tmp.end(); ++it) { + + if (extrusionFactor != 0.0) { + (*it)(direction) += extrusionFactor; + } + // append + wayNodes.insert(wayNodes.begin() + position, *it); + + // increment iterator + position += 2; + } +} + + +Polygon_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::getSurfacePlane(Polygon_t polygon3D, const QString featureType) +{ + Polygon_t polygon2D; + polygon2D.first = polygon3D.first; + + // 'building' and 'miscellaneous' features have repition nodes at the end to signify they're closed geo. + const unsigned int repNodes = (featureType == "building" || featureType == "miscellaneous") ? 2 : 0; + const unsigned int advance = 2; + + for (const auto& way3D : polygon3D.second) { + + Way_t way2D; + for (auto it = way3D.begin(); it != way3D.end() - repNodes; std::advance(it, advance)) { + way2D.push_back(*it); + } + polygon2D.second.push_back(way2D); + } + + return polygon2D; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::combineWayArbitrary(Polygon_t& polygon, Way_t& polygonNodes, std::vector>& polygonIndices) +{ + int start = 0, nextInd = 0, end = 0; + + for (auto it = polygon.second.begin(); it != polygon.second.end(); ++it) { + + // arrange polygon nodes + polygonNodes.insert(polygonNodes.end(), it->begin(), it->end()); + + // arrange indices + // example: count = 6 + // 5 4 3 + // x --- x --- x + // | | + // x --- x --- x + // 0 1 2 + + start = end; + end = polygonNodes.size(); + + for (int index = start; index < end; index++) { + nextInd = (index + 1) % end; + if (nextInd == 0) + nextInd = start; + + // append + polygonIndices.push_back(std::vector({ index, nextInd })); + } + } +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::combineHighway(Polygon_t& highwayPolygon, Way_t& highway, std::vector>& highwayIndices) +{ + int start = 0, count = highwayPolygon.second[0].size(), end = count - 1, nextInd = 0; + + for (auto it = highwayPolygon.second.begin(); it != highwayPolygon.second.end(); it++) + { + // append highway nodes + highway.insert(highway.end(), it->begin(), it->end()); + + // order highway indices + // example: count = 6 + // 0 2 4 + // x --- x --- x + // | | + // x --- x --- x + // 1 3 5 + + // even indices + for (int evenInd = start; evenInd < end; evenInd += 2) + { + nextInd = evenInd + 2; + if (evenInd == count - 2) + nextInd = end; + + // append + highwayIndices.push_back(std::vector({ evenInd, nextInd })); + } + + // odd indices + for (int oddInd = end; oddInd > start; oddInd -= 2) + { + nextInd = oddInd - 2; + if (oddInd == start + 1) + nextInd = start; + + // append + highwayIndices.push_back(std::vector({ oddInd, nextInd })); + } + } +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::mapStlToEigen(Eigen::MatrixXd& inputWays, Eigen::MatrixXi& inputIndices, Way_t& polyWays, std::vector>& polyIndices) +{ + // 3D to 2D + for (unsigned int i = 0; i < inputWays.rows(); i++) + { + inputWays.row(i) = Eigen::Vector2d::Map(&polyWays[i][0], polyWays[i].size() - 1); + inputIndices.row(i) = Eigen::Vector2i::Map(&polyIndices[i][0], polyIndices[i].size()); + } +} + + +Eigen::MatrixXf OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::castDoubleToFloat(Eigen::MatrixXd& doubleMatrix) +{ + Eigen::MatrixXf floatMatrix = doubleMatrix.cast(); + return floatMatrix; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::getHoles(std::vector& innerPolygon, Eigen::MatrixXd& holePoints) +{ + // midpoints container + QVector holeMidpoints; + + //for (int i = 0; i < innerPolygon.second.size(); i++) + for (auto& way : innerPolygon) + { + // hole mid-point + double x = 0, y = 0; + for (auto& node : way) + { + x += node.x(); + y += node.y(); + } + holeMidpoints.append(x / way.size()); + holeMidpoints.append(y / way.size()); + } + + // setup holePoints + int holeCount = holeMidpoints.size() / 2; + if (holeCount) { + holePoints.resize(holeCount, 2); + // stl vector to Eigen::MatrixXd + // special case: holeCount = 1 + if (holeCount == 1) { + holePoints(0, 0) = holeMidpoints[0]; + holePoints(0, 1) = holeMidpoints[1]; + } + else { // holeCount > 1 + for (unsigned int i = 0; i < holeCount; i++) { + int index = i * 2; + holePoints(i, 0) = holeMidpoints[index]; + holePoints(i, 1) = holeMidpoints[index + 1]; + } + //holePoints.row(i) = Eigen::Vector2d::Map(holeMidpoints.data(), holeMidpoints.size()); + } + + } +} + + +QDomNodeList OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::getPolygonList(QDomNodeList& relationWaysList, const QString& role) +{ + QDomNodeList polyList; + int polyIter = 0; + + for (int i = 0; i < relationWaysList.count(); i++) { + QDomNode relationWay = relationWaysList.at(i); + if (relationWay.toElement().attribute("role") == role) { + polyList.at(polyIter).appendChild(relationWay); + polyIter += 1; + } + } + return polyList; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::modifyRelationElements(std::vector& relationWaysList, const QString attribute, const QString value) +{ + // use std::remove_if to remove elements without the desired attribute + auto it = std::remove_if(relationWaysList.begin(), relationWaysList.end(), + [&](const QDomElement& element) { + return element.attribute(attribute) != value; + }); + + // erase the removed elements from the vector + relationWaysList.erase(it, relationWaysList.end()); +} + + +double OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::isWayContained(const QString miscID, const std::unordered_map& bboxContainer) +{ + // get bounds of the current way + auto it = bboxContainer.find(miscID); + // if not found + if (it == bboxContainer.end()) + return 0.5; + + // get the coordinates + auto currentBounds = it->second; + // south-west-north-east + double wayMinLat = std::get<0>(currentBounds); + double wayMinLon = std::get<1>(currentBounds); + double wayMaxLat = std::get<2>(currentBounds); + double wayMaxLon = std::get<3>(currentBounds); + + // count num. ways containing the current way within + double height = 0.5; + for (const auto& container : bboxContainer) { + + // check if id is not the same + if (it->first == container.first) + continue; + + // get bounds of all ways + double minlat = std::get<0>(container.second); + double minlon = std::get<1>(container.second); + double maxlat = std::get<2>(container.second); + double maxlon = std::get<3>(container.second); + + // check if within + if (wayMinLat >= minlat && wayMinLon >= minlon && wayMaxLat <= maxlat && wayMaxLon <= maxlon) + // increment the height + height += 0.5; + } + return height; +} + + +std::vector OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readOsmGeometry(QDomElement& root) +{ + // containers + // final output + std::vector osmData; + // osm features + OsmFeature_t highways; highways.first = "highway"; + OsmFeature_t buildings; buildings.first = "building"; + OsmFeature_t miscellaneous; miscellaneous.first = "miscellaneous"; + + // read OSM data 'relation' (building or miscellanous types) + unsigned int numRelation = osmGeometryContainer_.relationList.size(); + //if (!root.namedItem("relation").isNull()) + if (numRelation > 0) + { + //for (unsigned int relIt = 0; relIt < relationsList.count(); relIt++) + for (unsigned int relIt = 0; relIt < numRelation; relIt++) + { + //QDomElement relationElement = relationsList.at(relIt).toElement(); + QDomElement relationElement = osmGeometryContainer_.relationList.at(relIt); + + // invalid -> relation within relation OR incomplete geometry + if (isValidMemberRelation(relationElement)) + continue; + + QString miscInfo; + + // find if 'building' or 'misc.' relation + if (retrieveFeatureAttribute>(relationElement, { "type", "building" }, { "site", "building" }).isEmpty()) { + miscInfo = retrieveFeatureAttribute>(relationElement, osmGeometryContainer_.miscTypes); + } + + + if (!miscInfo.isEmpty()) + readRelation(miscellaneous, relationElement, "member"); + else + readRelation(buildings, relationElement, "member"); + } + } + + // read osm element 'way' (can be of type 'building', 'highway' or 'miscellaneous') + // #TODO: modify implementation of osmGeometryContainer_.wayList to delete 'way' elements read in 'relation' + unsigned int numWays = osmGeometryContainer_.wayList.size(); + bool ringCheck; + + for (unsigned int wayIt = 0; wayIt < numWays; wayIt++) { + + QDomElement wayElement = osmGeometryContainer_.wayList.at(wayIt).toElement(); + QString elementId = wayElement.attribute("id"); + int elementIndex = findValue(elementId, osmGeometryContainer_.wayIdPosition); + + // don't read in 'way' elements read already in 'relation' as 'member' + if (elementIndex != -1 && !(isIncompleteGeometry(wayElement))) { + + Polygon_t wayPolygon; + double physicalPropertyVal; + + // type "building" + if (!retrieveFeatureAttribute(wayElement, "building").isEmpty()) { + + // set building name + setPolygonName(wayPolygon, "building"); + + // get building height + physicalPropertyVal = retrieveBuildingHeight(wayElement); + + // read building + Way_t building = readFeature(wayElement, "building", physicalPropertyVal); + + // only accept "closed" building structure + if (isRing(building)) { + wayPolygon.second.push_back(building); + buildings.second.push_back(wayPolygon); + } + } + // type "highway" + else if (!retrieveFeatureAttribute(wayElement, "highway").isEmpty()) { + + // get highway info + QString highwayType = retrieveFeatureAttribute(wayElement, "highway"); + setPolygonName(wayPolygon, highwayType); + + // get highway width + physicalPropertyVal = findValue(highwayType, osmGeometryContainer_.highwayWidth); + if (physicalPropertyVal == -1) + physicalPropertyVal = 4.0; + + // read highway + Way_t highway = readFeature(wayElement, "highway", physicalPropertyVal); + + wayPolygon.second.push_back(highway); + highways.second.push_back(wayPolygon); + } + else { // type "miscellaneous" + + // get misc sub-feature + QString miscInfo = retrieveFeatureAttribute>(wayElement, osmGeometryContainer_.miscTypes); + setPolygonName(wayPolygon, miscInfo); + + // get height + physicalPropertyVal = isWayContained(elementId, osmGeometryContainer_.idBBox); + + // read miscellaneous sub-feature + Way_t miscWay = readFeature(wayElement, "miscellaneous", physicalPropertyVal); + + // only accept if "closed" + if (isRing(miscWay)) { + wayPolygon.second.push_back(miscWay); + miscellaneous.second.push_back(wayPolygon); + } + } + resetPolygonData(wayPolygon); + } + } + // append to final output + addToOutput(osmData, highways); + addToOutput(osmData, buildings); + addToOutput(osmData, miscellaneous); + + return osmData; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readRelation(OsmFeature_t& baseElement, QDomElement& relationElement, QString tag) +{ + // get all sub-elements 'member's of relationElement + QDomNodeList relationWaysList = getTags(relationElement, tag); + std::vector relationWayElements = toQDomElementVector(relationWaysList); + + // misc. relation + if (baseElement.first == "miscellaneous") { + + // get misc. type + QString miscType = retrieveFeatureAttribute>(relationElement, osmGeometryContainer_.miscTypes); + + // only obtain members with `role="outer"` attribute + modifyRelationElements(relationWayElements, "role", "inner"); + readMiscellaneousMultipolygon(relationWayElements, baseElement, miscType); + } + + else { // building relation + + // get relation type + QString relationType = retrieveFeatureAttribute(relationElement, "type"); + + // get height from "building:levels" attribute + double height = retrieveBuildingHeight(relationElement); + + // could be either 'building' or 'miscellaneous' structure (miscellaneous exists only as multipolygon) + if (relationType == "multipolygon") { + + // check if all the 'way' members of the 'relation' element in the correct order + if (relationWayElements.front().attribute("role") != "outer") + std::reverse(relationWayElements.begin(), relationWayElements.end()); + + readBuildingMultipolygon(relationWayElements, baseElement, height); + } + else if (relationType == "building") { + + // check if 'role="outline"' or 'role="outer"' + if (relationWayElements.front().attribute("role") != "outline" && relationWayElements.front().attribute("role") != "part") { + + // get first element which should be of 'role="outline"' + std::reverse(relationWayElements.begin(), relationWayElements.end()); + + QDomElement outlineElement = relationWayElements.front().toElement(); + + Polygon_t buildingPolygon = readBuildingTypeRelation(outlineElement, height); + setPolygonName(buildingPolygon, "building"); + + baseElement.second.push_back(buildingPolygon); + } + else { // treat it as a multipolygon + + // check if all the 'way' members of the 'relation' element in the correct order + if (relationWayElements.front().attribute("role") != "outer") + std::reverse(relationWayElements.begin(), relationWayElements.end()); + + readBuildingMultipolygon(relationWayElements, baseElement, height); + } + } + else if (relationType == "site") // type = 'site' + readSiteTypeRelation(relationWayElements, baseElement, height); + } +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readBuildingMultipolygon(std::vector& buildingList, OsmFeature_t& building, double height) +{ + // initialization + QString lastRole = "outer"; + bool lastWayClosed = false; + int distinctPoly = 0; + + // container + Polygon_t poly; + Way_t openWay; + + // set name + setPolygonName(poly, "multipolygon"); + + for (const QDomElement& polyWayElement : buildingList) { + + // initialization of variables for each loop + bool currentWayClosed; + QString currentRole = polyWayElement.attribute("role"); + QString polyWayId = polyWayElement.attribute("ref"); + + QDomElement wayElement = mapIdToIndex(polyWayId); + + // way doesn't exist independantly (i.e. only within relation element) + if (wayElement.isNull()) + wayElement = polyWayElement; + + Way_t currentWay = readFeature(wayElement, building.first, height); + + // check if the current way is closed (ring) or open + currentWayClosed = isRing(currentWay); + + // check for mulitpolygon cases -> https://wiki.openstreetmap.org/wiki/Relation:multipolygon#Multiple_ways_forming_a_ring + if (currentRole == "outer") { + + if (currentRole == lastRole) { + // "outer" followed by "outer" role + if (lastWayClosed == currentWayClosed && lastWayClosed) { + building.second.push_back(poly); + // clear all polygon data + resetPolygonData(poly); + distinctPoly = 0; + } + } + else { // lastRole != currentRole + + // reset polygon when distinctPolygon = 1 + if (distinctPoly == 1) { + building.second.push_back(poly); + resetPolygonData(poly); + distinctPoly = 0; + } + } + + // polygon boundary + if (currentWayClosed) { // polygon boundary closed + + poly.second.insert(poly.second.begin(), currentWay); + distinctPoly += 1; + + } + else { // polygon boundary not closed + + // add way to polygon list + openWay.insert(openWay.end(), currentWay.begin(), currentWay.end()); + + // check way "closed" after insertion of another way + currentWayClosed = isRing(currentWay); + if (currentWayClosed) { + + poly.second.insert(poly.second.begin(), openWay); + openWay.clear(); + } + } + + } + else { // currentRole = "inner" + + // polygon boundary closed + if (currentWayClosed) { + poly.second.insert(poly.second.end(), currentWay); + + } + else { // polygon boundary not closed + + // add way to polygon list + openWay.insert(openWay.end(), currentWay.begin(), currentWay.end()); + + // check if way "closed" after insertion of another way + currentWayClosed = isRing(openWay); + if (currentWayClosed) { + + poly.second.insert(poly.second.end(), openWay); + openWay.clear(); + } + } + } + // set last role & check + lastRole = currentRole; + lastWayClosed = currentWayClosed; + + deleteWayFromMap(polyWayId); + } + + // append remaining polygon data + if (poly.second.size() != 0) + building.second.push_back(poly); +} + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readMiscellaneousMultipolygon(std::vector& miscList, OsmFeature_t& miscellaneous, const QString miscType) +{ + // container + Polygon_t poly; + Way_t openWay; + + // set name + setPolygonName(poly, miscType); + + for (const QDomElement& miscElement : miscList) { + + // get height + double height = isWayContained(miscElement.attribute("id"), osmGeometryContainer_.idBBox); + + Way_t currentWay = readFeature(miscElement, "miscellaneous", height); + + // check closed + bool currentWayClosed = isRing(currentWay); + + // polygon boundary + if (currentWayClosed) { // polygon boundary closed + poly.second.insert(poly.second.begin(), currentWay); + } + else { // polygon boundary not closed + + // add way to polygon list + openWay.insert(openWay.end(), currentWay.begin(), currentWay.end()); + + // check way "closed" after insertion of another way + currentWayClosed = isRing(currentWay); + if (currentWayClosed) { + poly.second.insert(poly.second.begin(), openWay); + } + } + } + miscellaneous.second.push_back(poly); +} + +// #TODO: include support of 'role="part"'; for now only supports 'role="outline"' +Polygon_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readBuildingTypeRelation(QDomElement& outlineElement, double height) +{ + Polygon_t building; + + // get reference + QString outlineId = outlineElement.attribute("ref"); + QDomElement wayElement = mapIdToIndex(outlineId); + + if (wayElement.isNull()) + wayElement = outlineElement; + + // get the way nodes + Way_t outlineWay = readFeature(wayElement, "building", height); + + // only return closed ways + if (isRing(outlineWay)) + building.second.push_back(outlineWay); + + deleteWayFromMap(outlineId); + + return building; +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readSiteTypeRelation(std::vector& siteList, OsmFeature_t& feature, double height) +{ + Polygon_t sitePoly; + setPolygonName(sitePoly, "site"); + + for (int siteIt = 0; siteIt < siteList.size(); siteIt++) { + + // individual site way member + QDomElement siteMember = siteList.at(siteIt).toElement(); + + // get reference to 'way' + QString siteElementId = siteMember.attribute("ref"); + QDomElement siteElement = mapIdToIndex(siteElementId); + if (siteElement.isNull()) + siteElement = siteMember; + + // get height for individual site element + height = retrieveBuildingHeight(siteElement); + + Way_t siteWay = readFeature(siteElement, "building", height); + + // only return closed ways + if (isRing(siteWay)) + sitePoly.second.push_back(siteWay); + + deleteWayFromMap(siteElementId); + } + + feature.second.push_back(sitePoly); +} + + +Way_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readFeature(const QDomElement& feature, const QString featureType, double physicalProperty) +{ + if (physicalProperty == 4.0 && featureType == "building") { + if (!feature.namedItem("tag").isNull()) + physicalProperty = retrieveBuildingHeight(feature); + } + + QDomNodeList nodes = getTags(feature, "nd"); + return readNodes(featureType, nodes, physicalProperty); +} + + +Way_t OpenInfraPlatform::Core::OsmDataConverter::OsmGeometry::readNodes(QString featureType, QDomNodeList& nodeList, double propertyValue) +{ + Way_t nodes; + + // get all nodes + for (unsigned int nodeIt = 0; nodeIt < nodeList.count(); nodeIt++) { + QDomElement nodeElement = nodeList.at(nodeIt).toElement(); + Node_t node = extractLatLon(nodeElement); + nodes.push_back(node); + } + + // specify extrusion direction + extrusionDirection_ = Direction::Z; + + if (featureType == "highway") { + nodes = offsetPoints(nodes, propertyValue); + // change propertyValue now to extrude in z-direction + propertyValue = 2.0; + } + extrude(nodes, propertyValue); + + return nodes; +} \ No newline at end of file diff --git a/Core/src/OsmDataConverter/OsmDataGeometry.h b/Core/src/OsmDataConverter/OsmDataGeometry.h new file mode 100644 index 000000000..4d82226e0 --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataGeometry.h @@ -0,0 +1,540 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . +*/ + +// visual studio +#pragma once +// unix +#ifndef OSMDATAGEOMETRY_H +#define OSMDATAGEOMETRY_H + +// Qt5 includes +#include +#include +#include +#include + +// external libraries +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define _USE_MATH_DEFINES + +#include "namespace.h" + +// typedefs +using FeatureBBox_t = std::tuple; +using Node_t = Eigen::Vector3d; +using Way_t = std::vector >; +using Polygon_t = std::pair>; +using OsmFeature_t = std::pair>; + + +namespace OpenInfraPlatform +{ + namespace Core + { + namespace OsmDataConverter + { + struct OsmGeometryContainer { + std::vector wayList; + std::vector relationList; + std::vector miscTypes; + std::array, 4> highwayWidth; + std::unordered_map idBBox; + std::unordered_map wayIdPosition; + std::unordered_map hexRGBScheme; + }; + + enum class Direction : unsigned int { + X = 0, + Y = 1, + Z = 2 + }; + + /** + * @class OsmGeometry + * @brief Class to generate geometry data from OpenStreetMap (OSM) XML files. + */ + class OsmGeometry + { + private: + // container for OSM geometry data + OsmGeometryContainer osmGeometryContainer_; + // direction oif extrusion + Direction extrusionDirection_; + + public: + /** + * @brief Default constructor for OsmGeometry. + */ + explicit OsmGeometry(); + + /** + * @brief Destructor for OsmGeometry. + */ + virtual ~OsmGeometry(); + + /** + * @brief Initializes member variables with mappers and all OSM data types. + * @param root The QDomElement root node of the OSM file. + * @param attr The attribute to initialize. + */ + void initializeContainer(const QDomElement& root, const QString attr); + + /** + * @brief Generates a stl vector container from QDomNodeList using std::generate and lambda function. + * @param nodeList The QDomNodeList to generate the container from. + * @return The stl vector container generated from the QDomNodeList. + */ + std::vector toQDomElementVector(const QDomNodeList nodeList); + + /** + * @brief Sets all sub-features obtainable from OpenStreetMap. + * @return A vector of all sub-features. + */ + std::vector setSubFeatures(); + + //! [references from https://www.openstreetmap.org/#map] + /** + * @brief Sets the coloring scheme for respective sub - features. + * @return A vector of colors for each sub - feature. + */ + std::vector setSubFeatureColor(); + + /** + * @brief Appends bounding box (bbox) information obtained from the osm element via the tag ''. + * @param featureID The ID of the feature to append the bbox information to. + * @param boundsElement The QDomElement bounds information. + */ + void addToFeatureBBox(const QString featureID, const QDomElement& boundsElement); + + /** + * @brief Adds OsmFeature_t data to the final output stl vector. + * @param finalOutput The final output stl vector. + * @param osmElement The OsmFeature_t data to add to the final output. + */ + void addToOutput(std::vector& finalOutput, const OsmFeature_t& osmElement) { if (osmElement.second.size()) finalOutput.push_back(osmElement); } + + /** + * @brief Retrieves all tags of an element. + * @param element The QDomElement to retrieve the tags from. + * @param tagName The name of the tag to retrieve. + * @return A QDomNodeList containing all tags with the given name. + */ + QDomNodeList getTags(const QDomElement element, const QString tagName) { return element.elementsByTagName(tagName); } + + /** + * @brief Sets the OsmFeature_t.first value to the given name. + * @param polygon The Polygon_t data structure to set the name for. + * @param name The name to set. + */ + void setPolygonName(Polygon_t& polygon, const QString name) { polygon.first = name; } + + /** + * @brief Resets the Polygon name and data std::vector>. + * @param polygon The Polygon_t data structure to reset. + */ + void resetPolygonData(Polygon_t& polygon) { polygon.first.clear(); polygon.second.clear(); } + + /** + * @brief Retrieves the terrain bounds information. + * @param root The QDomElement root node of the OSM file. + * @param tag The tag to retrieve the bounds information for. + * @param padding The padding value to apply to the terrain. + * @return The Polygon_t + * This function retrieves the terrain bounds information from the OSM file using the provided tag, + * applies the padding value to the terrain, and returns the terrain bounds in the form of a Polygon_t object. + */ + Polygon_t createTerrain(const QDomElement& root, const QString tag, const double padding = 0.0025); + + /** + * @brief Retrieves the color for a sub-feature. + * @param subFeaName The name of the sub-feature to retrieve the color for. + * @return The color of the sub-feature. + */ + buw::Vector3f getColor(const QString subFeaName); + + /** + * @brief Converts a HexRgb color to a floating point value. + * @param hexRGBColor The color to convert. + * @return The converted color. + */ + buw::Vector3f hexRGBToFloat(const QColor hexRGBColor); + + /** + * @brief Applies padding to the terrain coordinates. + * @param minGeodeticCoord The minimum geodetic coordinate. + * @param maxGeodeticCoord The maximum geodetic coordinate. + * @param paddingValue The amount of padding to apply. + */ + void applyPadding(double& minGeodeticCoord, double& maxGeodeticCoord, const double paddingValue); + + /** + * @brief Rounds off a value to a specified precision. + * @param value The value to round off. + * @param roundDown If true, round down. Otherwise, round up. + * @param precision The precision to round off to. + * @return The rounded value. + */ + double roundOff(const double value, const bool roundDown = false, const double precision = 0.001); + + /** + * @brief Assembles the geodetic coordinates for the terrain. + * @param minlat The minimum latitude coordinate. + * @param maxlat The maximum latitude coordinate. + * @param minlon The minimum longitude coordinate. + * @param maxlon The maximum longitude coordinate. + * @return The geodetic coordinates for the terrain. + */ + std::vector> assembleGeodeticCoords(double& minlat, double& maxlat, double& minlon, double& maxlon); + + /** + * @brief Retrieve the height of a building element in an OSM file. + * @param element The building element to retrieve the height from. + * @return The height of the building element as a double. + */ + double retrieveBuildingHeight(const QDomElement element); + + /** + * @brief Retrieve the value of the specified attribute(s) of a QDomElement. + * + * This function retrieves the value of the specified attribute(s) of a QDomElement, which is typically an element in an XML file. + * The attribute(s) to retrieve are specified as a QString or a std::vector. + * The function searches through all tags of the given element to find the specified attribute(s), + * and returns the value of the 'v' attribute of the first tag that matches the specified key(s). + * + * @tparam T The type of the attribute(s) to retrieve. Can be either a QString or a std::vector. + * @param element The QDomElement to search for the specified attribute(s). + * @param attributes The attribute(s) to retrieve. Can be a single QString or a std::vector. + * @param values (optional) The value(s) of the attribute(s) to retrieve. Can be a single QString or a std::vector. If this parameter is not specified, all tags with the specified key(s) will be returned. + * @return The value of the first tag that matches the specified key(s) and (optionally) value(s), or an empty QString if no such tag is found. + * + * @note The attribute(s) must be specified as exact matches for the 'k' attribute of the tag element. + * @note The tag(s) are searched in order, so if multiple tags with the same key(s) are present, the function will return the value of the first one it encounters. + * @note The function returns an empty QString if no tag with the specified key(s) is found. + */ + //QString retrieveFeatureAttribute(const QDomElement& element, const T& attributes) + template + QString retrieveFeatureAttribute(const QDomElement& element, const T& attributes, const T& values = T()) + { + // get all tags + QDomNodeList tags = getTags(element, "tag"); + for (int i = 0; i < tags.count(); i++) { + QDomElement tag = tags.at(i).toElement(); + + // single attribute + // checks if values is empty or not, and also checks if the v attribute of the tag is equal to the values input parameter, + // and returns the attribute v value if the condition is true + if constexpr (std::is_same_v) { + if (tag.attribute("k") == attributes && (values.isEmpty() || tag.attribute("v") == values)) + //if (tag.attribute("k") == attributes) + return tag.attribute("v"); + } + // multiple attributes + // checks if values is empty or not, and also checks if the v attribute of the tag is equal to any of the values input parameter, + // using std::find function, and returns the attribute v value if the condition is true + else if constexpr (std::is_same_v>) { + for (const auto& attr : attributes) { + // special case: building [if it finds this attribute key, returns the attribute directly; can't name all building 'way' as arguments] + if (tag.attribute("k") == attr && !values.empty()) + return tag.attribute("v"); + + if (tag.attribute("k") == attr && (values.empty() || std::find(values.begin(), values.end(), tag.attribute("v")) != values.end())) + //if (tag.attribute("k") == attr) + return tag.attribute("v"); + } + } + } + return QString(); + } + + /** + * @brief Check if 'relation' member exists within 'relation' element. + * @param parentRelation The parent 'relation' element. + * @return True if 'relation' member exists, false otherwise. + */ + bool isValidMemberRelation(const QDomElement parentRelation); + + /** + * @brief Templated function to find mapper value. + * @tparam T The type of the container value. + * @param key The key to search for in the container. + * @param container The container to search for the key. + * @return The value associated with the key if it exists, or a default value if not. + */ + template + T findValue(const QString& key, const std::unordered_map& container) + { + auto it = container.find(key); + if (it != container.end()) + return it->second; + return T(-1); + } + + /** + * @brief Templated function to find array container value with the same signature as the one above. + * @tparam T The type of the container value. + * @tparam N The size of the container array. + * @param key The key to search for in the container. + * @param container The container to search for the key. + * @return The value associated with the key if it exists, or a default value if not. + */ + template + T findValue(const QString& key, const std::array, N>& container) + { + for (const auto& p : container) { + if (p.first == key) + return p.second; + } + return T(-1); + } + + /** + * @brief Offset point perpendicular to line formed by corresponding line end points. + * @param elementNodes The set of nodes for the line. + * @param offsetDistance The distance to offset the point. + * @return The offset point. + */ + Way_t offsetPoints(const Way_t elementNodes, const double& offsetDistance); + + /** + * @brief Add offset point perpendicular to line formed by corresponding line end points. + * @param node The node to offset. + * @param normVec The normal vector for the line. + * @param offsetDistance The distance to offset the point. + * @param offsetNodes The set of nodes for the line with the offset point added. + */ + void addOffsetPoint(const Node_t node, const Node_t normVec, const double offsetDistance, Way_t& offsetNodes); + + /** + * @brief Retrieve OSM feature by mapping id to index. + * @param id The id of the feature to retrieve. + * @return The element with the matching id. + */ + QDomElement mapIdToIndex(const QString id); + + /** + * @brief Extract latitude and longitude information from node. + * @param nodeElement The node element. + * @return The latitude and longitude of the node. + */ + Node_t extractLatLon(const QDomElement& nodeElement); + + /** + * @brief Convert nodes from WGS-84 CRS to Cartesian geocentric CRS. + * @param latitude The latitude of the node. + * @param longitude The longitude of the node. + * @param elevation The elevation of the node. + * @return The converted node. + */ + Node_t projectWGS84(double latitude, double longitude, double elevation); + + /** + * @brief Remove way from wayListContainer and mapper. + * @param wayId The id of the way to be deleted. + */ + void deleteWayFromMap(const QString wayId) { osmGeometryContainer_.wayIdPosition.erase(wayId); } + + /** + * @brief Check if 'way' is "closed". + * @param wayNodes The element nodes of the way. + * @return Whether or not the way is "closed". + */ + bool isRing(const Way_t& wayNodes); + + /** + * @brief Check if geometry incomplete. + * @param wayElement The QDomElement of the way. + * @return Whether or not the geometry is incomplete. + */ + bool isIncompleteGeometry(const QDomElement& wayElement); + + /** + * @brief Check if latitude and longitude info. given. + * @param nodeElement The QDomElement of the node. + * @return Whether or not the latitude and longitude information is given. + */ + bool hasLatLon(const QDomElement& nodeElement) { return (nodeElement.hasAttribute("lat") && nodeElement.hasAttribute("lon")); } + + /** + * @brief Extrude the provided 'wayNodes' in the X, Y, or Z direction by a factor of 'extrusionFactor' + * @param wayNodes The Way_t nodes to extrude + * @param extrusionFactor The factor to extrude the nodes by + */ + void extrude(Way_t& wayNodes, const double& extrusionFactor); + + + /** + * @brief Given a 3D 'polygon3D', return a 2D Polygon_t + * @param polygon3D The 3D polygon to get a 2D polygon from + * @param featureType The feature type of the polygon + * @return The 2D Polygon_t + */ + Polygon_t getSurfacePlane(Polygon_t polygon3D, const QString featureType); + + /** + * @brief Combine all the polygon nodes and indices to form a Polygon_t + * @param polygon The Polygon_t to update with the combined nodes and indices + * @param polygonNodes The polygon nodes to combine + * @param polygonIndices The polygon indices to combine + */ + void combineWayArbitrary(Polygon_t& polygon, Way_t& polygonNodes, std::vector>& polygonIndices); + + /** + * @brief Combine all highway nodes and indices to form a Polygon_t and order the indices + * @param highwayPolygon The highway Polygon_t to update with the combined nodes and indices + * @param highway The highway nodes to combine + * @param highwayIndices The highway indices to combine + */ + void combineHighway(Polygon_t& highwayPolygon, Way_t& highway, std::vector>& highwayIndices); + + /** + * @brief Map the provided STL containers to the corresponding Eigen containers + * @param inputWays The input STL container of ways to map + * @param inputIndices The input STL container of indices to map + * @param polyWays The output Eigen container of ways + * @param polyIndices The output Eigen container of indices + */ + void mapStlToEigen(Eigen::MatrixXd& inputWays, Eigen::MatrixXi& inputIndices, Way_t& polyWays, std::vector>& polyIndices); + + /** + * @brief Cast the provided Eigen matrix of doubles to an Eigen matrix of floats + * @param doubleMatrix The input Eigen matrix of doubles + * @return The output Eigen matrix of floats + */ + Eigen::MatrixXf castDoubleToFloat(Eigen::MatrixXd& doubleMatrix); + + /** + * @brief Given a set of 'innerPolygon' nodes, compute the midpoint of the nodes to form 'holePoints' + * @param innerPolygon The set of nodes to compute the midpoint of + * @param holePoints The output midpoint of the nodes + */ + void getHoles(std::vector& innerPolygon, Eigen::MatrixXd& holePoints); + + /** + * @brief Given a set of relation 'way' elements and a 'role', return all 'outer' and 'inner' polygon ways + * @param relationWaysList The relation 'way' elements to retrieve the polygon ways from + * @param role The role of the 'way' element to retrieve the polygon ways for + * @return The list of all 'outer' and 'inner' polygon ways + */ + QDomNodeList getPolygonList(QDomNodeList& relationWaysList, const QString& role); + + /** + * @brief Modify the attributes of the provided relation 'way' elements + * @param relationWaysList The list of relation 'way' elements to modify + * @param attribute The attribute to modify + * @param value The value to set the attribute to + */ + void modifyRelationElements(std::vector& relationWaysList, const QString attribute, const QString value); + + /** + * @brief Check if a 'way' element is contained inside another 'way' element by using bbox information. + * If contained, increment the height for miscellaneous features to avoid 'mesh flickering' phenomenon. + * @param miscID The id of the miscellaneous feature to check. + * @param bboxContainer The unordered map of feature id to their bounding boxes. + * @return The increment value of the height. + */ + double isWayContained(const QString miscID, const std::unordered_map& bboxContainer); + + /** + * @brief Read all OpenStreetMap features from the given root node. + * @param root The root node of the OpenStreetMap data. + * @return A vector of OpenStreetMap features. + */ + std::vector readOsmGeometry(QDomElement& root); + + /** + * @brief Read the 'relation' element of the given OpenStreetMap feature. + * @param baseElement The base OpenStreetMap feature to update. + * @param relationElement The 'relation' element to read. + * @param tag The tag for the relation. + */ + void readRelation(OsmFeature_t& baseElement, QDomElement& relationElement, QString tag); + + /** + * @brief Read the 'building' multipolygon from the given list of 'way' elements. + * @param buildingList The list of 'way' elements that make up the building. + * @param building The OpenStreetMap building feature to update. + * @param height The height of the building. + */ + void readBuildingMultipolygon(std::vector& buildingList, OsmFeature_t& building, double height); + + /** + * @brief Read the 'outer' boundary of the 'multipolygon' relation for miscellaneous features. + * @param miscList The list of 'relation' elements. + * @param miscellaneous The miscellaneous OpenStreetMap feature to update. + * @param miscType The type of the miscellaneous feature. + */ + void readMiscellaneousMultipolygon(std::vector& miscList, OsmFeature_t& miscellaneous, const QString miscType); + + /** + * @brief Read the 'building' relation element. + * + * @param outlineElement The 'way' element that outlines the building. + * @param height The height of the building. + * @return The polygon that represents the building. + */ + Polygon_t readBuildingTypeRelation(QDomElement& outlineElement, double height); + + /** + * @brief Read the 'site' relation element. + * @param siteList The list of 'way' elements that make up the site. + * @param element The OpenStreetMap feature to update. + * @param height The height of the site. + */ + void readSiteTypeRelation(std::vector& siteList, OsmFeature_t& element, double height); + + /** + * @brief Read the 'way' element of the OpenStreetMap feature. + * + * @param feature The 'way' element to read. + * @param featureType The type of the OpenStreetMap feature. + * @param physicalProperty The physical property of the OpenStreetMap feature. + * @return The nodes that make up the 'way' element. + */ + Way_t readFeature(const QDomElement& feature, const QString featureType, double physicalProperty); + + /** + * @brief Reads OSM element 'node' for all OSM elements. + * @param featureType The type of feature being read. + * @param nodeList The list of nodes to be read. + * @param propertyValue The value of the physical property to be assigned to the feature. + * @return The list of nodes read. + * This function reads the nodes of an OSM feature and returns them as a list of nodes. The physical property + * of the feature can also be assigned using the propertyValue parameter. If no propertyValue is provided, it is + * assumed to be zero. + */ + Way_t readNodes(QString featureType, QDomNodeList& nodeList, double propertyValue = 0.0); + }; + + } + } +} + +EMBED_CORE_OSMDATACONVERTER_INTO_OIP_NAMESPACE(OsmGeometry) + +#endif // !OSMDATAGEOMETRY_H diff --git a/Core/src/OsmDataConverter/OsmDataModel.cpp b/Core/src/OsmDataConverter/OsmDataModel.cpp new file mode 100644 index 000000000..24d733fe5 --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataModel.cpp @@ -0,0 +1,82 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#include "OsmDataModel.h" +#include +#include "../Exception/UnhandledException.h" + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmModel::reset() +{ + for (auto geometry : geometries_) + geometry->reset(); +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmModel::addGeometry(std::shared_ptr geometry) +{ + // lock the multithread access to the lists + geometryMutex_.lock(); + + // add to the list of geometries + geometries_.push_back(geometry); + + // free the access to the lists + geometryMutex_.unlock(); +} + + +// ************************************************************************************************************************************************************ +// Interface IModel implementation +// ************************************************************************************************************************************************************ + + +bool OpenInfraPlatform::Core::OsmDataConverter::OsmModel::isEmpty() const +{ + for (auto geometry : geometries_) + if (!geometry->isEmpty()) + return false; + return true; +} + + +std::string OpenInfraPlatform::Core::OsmDataConverter::OsmModel::getSource() const +{ + return filename_; +} + + +oip::BBox OpenInfraPlatform::Core::OsmDataConverter::OsmModel::getExtent() +{ + oip::BBox bb; + for (auto geometry : geometries_) + bb.update(geometry->bb); + return bb; +} + + +oip::GeorefMetadata OpenInfraPlatform::Core::OsmDataConverter::OsmModel::getGeorefMetadata() const +{ + throw oip::UnhandledException("OsmDataGeometryModel::getGeorefMetaData()"); +} + + +void OpenInfraPlatform::Core::OsmDataConverter::OsmModel::transformAllPoints( + const oip::GeorefMetadata& newGeorefMetadata, std::function const(double, double, double)>& transf) +{ + throw oip::UnhandledException("OsmDataGeometryModel::transformAllPoints()"); +} diff --git a/Core/src/OsmDataConverter/OsmDataModel.h b/Core/src/OsmDataConverter/OsmDataModel.h new file mode 100644 index 000000000..e38045f65 --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataModel.h @@ -0,0 +1,177 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +// visual studio +#pragma once +//unix +#ifndef OSMDATAMODEL_H +#define OSMDATAMODEL_H + +#include +#include +#include +#include +#include +#include + +#include "namespace.h" + +// typedefs +using PointLayout_t = buw::VertexPosition3Color3Normal3; + +namespace OpenInfraPlatform +{ + namespace Core + { + /** + * @namespace OsmDataConverter + * @brief Namepsce containing structs and OsmModel class for storing all OpenStreetMap data + */ + namespace OsmDataConverter + { + /** + * @struct IndexedMeshDescription + * @brief Struct containing the description of indexed mesh. + */ + struct IndexedMeshDescription { + std::vector indices; + std::vector points; // BlueFramework/Rasterizer/src/vertex.h + + /** + * @brief Method to check if indices and points vector is empty. + * @return Boolean indicating if the indices and points vector is empty. + */ + bool isEmpty() const { return (indices.size() == 0 && points.size() == 0); } + + /** + * @brief Method to reset the indices and points vector. + */ + void reset() { indices.clear(); points.clear(); } + }; + + /** + *@struct GeometryDescription + * @brief Struct containing the description of geometry, with the bounding box information + */ + struct GeometryDescription { + IndexedMeshDescription meshDescription; + oip::BBox bb; + /** + * @brief Method to check if meshDescription is empty. + * @return Boolean indicating if meshDescription is empty. + */ + bool isEmpty() const { return meshDescription.isEmpty(); } + + /** + * @brief Method to reset the meshDescription and bounding box. + */ + void reset() { meshDescription.reset(); bb.reset(); } + + /** + * @brief Method to add indices to meshDescription. + * @param indices Vector of integers representing indices to be added. + */ + void addIndices(const std::vector& indices) + { + meshDescription.indices.reserve(indices.size()); + for (const auto& index : indices) + meshDescription.indices.push_back(index); + } + + /** + * @brief Method to add points to meshDescription. + * @param points Vector of PointLayout_t representing points to be added. + */ + void addPoints(const std::vector& points) + { + meshDescription.points.reserve(points.size()); + for (const auto& point : points) + meshDescription.points.push_back(point); + } + + /** + * @brief Method to update the bounding box of geometry. + */ + void updateBBox() + { + for (const auto& point : meshDescription.points) + bb.update(point.position[0], point.position[1], point.position[2]); + } + }; + + /** + * @brief OsmModel is a class that represents a 3D model created from OpenStreetMap data. + * This class is derived from oip::IModel. + */ + class OsmModel : public oip::IModel + { + private: + /** + * @brief A vector of GeometryDescription shared pointers. + */ + std::vector> geometries_; + + /** + * @brief A mutex to protect the geometries vector from concurrent access. + */ + std::mutex geometryMutex_; + + std::string filename_; + + public: + /** + * @brief Resets the model by clearing the geometries vector. + */ + void reset(); + + /** + * @brief Adds a GeometryDescription shared pointer to the geometries vector. + * @param geometry The GeometryDescription shared pointer to be added. + */ + void addGeometry(std::shared_ptr geometry); + + /** + * @brief Returns a reference to the geometries vector. + * @return std::vector> const& A reference to the geometries vector. + */ + std::vector> const& geometries() const { return geometries_; } + + /** + * @brief Sets the filename of the OSM file from which the model was created. + * @param filename The filename of the OSM file. + */ + void setFilename(const std::string& filename) { filename_ = filename; } + + // ************************************************************************************************************************************************************ + // Interface IModel implementation + // ************************************************************************************************************************************************************ + + virtual bool isEmpty() const override; + virtual std::string getSource() const override; + virtual oip::BBox getExtent() override; + virtual oip::GeorefMetadata getGeorefMetadata() const override; + virtual void transformAllPoints(const oip::GeorefMetadata& newGeorefMetadata, std::function const(double, double, double)>& transf) override; + + // ************************************************************************************************************************************************************ + }; + } + } +} + +EMBED_CORE_OSMDATACONVERTER_INTO_OIP_NAMESPACE(OsmModel) + +#endif //OSMDATAMODEL_H \ No newline at end of file diff --git a/Core/src/OsmDataConverter/OsmDataReader.cpp b/Core/src/OsmDataConverter/OsmDataReader.cpp new file mode 100644 index 000000000..606357d6a --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataReader.cpp @@ -0,0 +1,549 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#include +#include + +#include "OsmDataReader.h" +#include "../Exception/OsmDataReaderException.h" +#include "../Exception/UnhandledException.h" + + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_BEGIN + +OsmReader::OsmReader() +{ + +} + + +OsmReader::~OsmReader() +{ + +} + + +std::shared_ptr OsmReader::readFile(const std::string& filename) +{ + // Create document to write XML + QDomDocument document; + + QString qFilename = QString::fromStdString(filename); + + // Initialize file_ member variable + QFile* file = new QFile(qFilename); + + // Open file for reading #TODO: catch error using OIP error logger + if (!file->open(QIODevice::ReadOnly | QIODevice::Text)) { + delete file; + throw oip::OsmReaderException("Could not open Xml file <" + filename + "> in OsmReader."); + } + else { + // load file + if (!document.setContent(file)) { + file->close(); + delete file; + throw oip::OsmReaderException("Could not load the Xml file <" + filename + "> for reading in OsmReader."); + } + } + + try + { + // intialize osm model + std::shared_ptr osmModel = std::make_shared(); + + // start timer + auto start = std::chrono::high_resolution_clock::now(); + + // get child root element () + QDomElement root = document.documentElement(); + + // all osm features + // initialize OSM data + osmGeometry_.initializeContainer(root, "id"); + + // read osm feature + std::vector osmData = osmGeometry_.readOsmGeometry(root); + + for (auto& osmFeature : osmData) + { + if (!osmFeature.second.empty()) + { + for (auto& polygon : osmFeature.second) + { + std::shared_ptr geometry = std::make_shared(); + geometry->reset(); + + // mesh geometry + meshGeometry(polygon, geometry, osmFeature.first); + + // add to osm model + osmModel->addGeometry(geometry); + } + } + } + + // osm terrain + Polygon_t terrainGeo = osmGeometry_.createTerrain(root, "bounds"); + std::shared_ptr terrain = std::make_shared(); + terrain->reset(); + // mesh terrain + meshGeometry(terrainGeo, terrain, "terrain"); + // add to osmModel + osmModel->addGeometry(terrain); + + // end timer + auto end = std::chrono::high_resolution_clock::now(); + // compute elapsed time + auto duration = std::chrono::duration_cast(end - start); + + // deallocate + QFile::remove(QString::fromStdString(filename)); + delete file; + return osmModel; + } + catch (const std::exception& e) + { + QFile::remove(QString::fromStdString(filename)); + delete file; + throw std::exception("Error in Xml file"); + } +} + + +void OsmReader::meshGeometry(Polygon_t& polygon, std::shared_ptr geometry, const QString featureType) +{ + // container + std::vector meshPoints; + std::vector meshIndices; + + // surface plane mesh + readSurfacePlaneFace(polygon, meshPoints, meshIndices, featureType); + + // side face (quads) + if (featureType == "highway") + readSideFaceHighway(polygon, meshPoints, meshIndices); + else + readSideFaceArbitrary(polygon, meshPoints, meshIndices); + + // add points to geometry + geometry->addPoints(meshPoints); + + // add indices to geometry (reset numbering) + int numOfPoints = meshPoints.size(); + for (int i = 0; i < numOfPoints; i++) + meshIndices.at(i) = i; + + // add indices + geometry->addIndices(meshIndices); + + // update bbox + geometry->updateBBox(); +} + + +void OsmReader::readSurfacePlaneFace(Polygon_t& polygon, std::vector& points, std::vector& pointIndices, const QString featureType) +{ + // get surface plane reference + Polygon_t surfacePlane = osmGeometry_.getSurfacePlane(polygon, featureType); + + // triangulate output + Eigen::MatrixXd triOutPoints; + Eigen::MatrixXi triOutIndices; + + // final output + Eigen::MatrixXf surfacePlanePoints; + Eigen::MatrixXi surfacePlaneIndices; + + if (surfacePlane.first == "site") { + + // container for all site meshes + std::vector> allSiteMeshes; + std::vector> allSiteIndices; + + // container for storing individual member mesh + // points + Eigen::MatrixXf siteMesh; + Eigen::MatrixXf bottomFacePoints; + Eigen::MatrixXf topFacePoints; + // indices + Eigen::MatrixXi siteIndices; + Eigen::MatrixXi bottomFaceIndices; + Eigen::MatrixXi topFaceIndices; + + // iterators + int localMeshRows = 0, localIndexRows = 0; + int globalMeshRows = 0, globalIndexRows = 0; + + // triangulatation input + Way_t inputPoints; + std::vector> inputIndices; + Eigen::MatrixXd holePoints; + + // for each site member + for (unsigned int i = 0; i < surfacePlane.second.size(); i++) { + + // set points + inputPoints = surfacePlane.second.at(i); + + // reset indices + if (inputIndices.size()) + inputIndices.clear(); + + // site indices + int size = inputPoints.size(); + for (int index = 0; index < size; index++) + inputIndices.push_back({ index, (index + 1) % size }); + + // get 2D + // mesh each site member individually + std::pair mesh = triangulate(inputPoints, inputIndices, holePoints); + + // get 3D + // bottom face + createMeshPoints(mesh.first, bottomFacePoints, polygon.second[i][0].z()); + createMeshIndices(mesh.second, bottomFaceIndices, globalMeshRows); + + // top face + createMeshPoints(mesh.first, topFacePoints, polygon.second[i][1].z()); + createMeshIndices(mesh.second, topFaceIndices, globalMeshRows + mesh.first.rows()); + + // set local iterators + localMeshRows = bottomFacePoints.rows() + topFacePoints.rows(); + localIndexRows = bottomFaceIndices.rows() + topFaceIndices.rows(); + + // combine mesh + mergeMeshes(siteMesh, std::vector>{bottomFacePoints, topFacePoints}, localMeshRows); + mergeMeshes(siteIndices, std::vector>{bottomFaceIndices, topFaceIndices}, localIndexRows); + + // store in container + allSiteMeshes.push_back(siteMesh); + allSiteIndices.push_back(siteIndices); + + // increment global iterators + globalMeshRows += bottomFacePoints.rows() + topFacePoints.rows(); + globalIndexRows += bottomFaceIndices.rows() + topFaceIndices.rows(); + } + + // create final output + mergeMeshes(surfacePlanePoints, allSiteMeshes, globalMeshRows); + mergeMeshes(surfacePlaneIndices, allSiteIndices, globalIndexRows); + + } + else { + + // 2D mesh + surfaceTriangulation(surfacePlane, triOutPoints, triOutIndices, featureType); + + // z-plane constant + std::vector zPlane = { polygon.second[0][0].z(), polygon.second[0][1].z() }; + surfaceMesh3D(triOutPoints, triOutIndices, surfacePlanePoints, surfacePlaneIndices, zPlane); + } + + // read the triangle mesh for the complete mesh + readFaceMesh(points, pointIndices, surfacePlanePoints, surfacePlaneIndices, polygon.first); +} + + +void OsmReader::readSideFaceArbitrary(Polygon_t& polygon, std::vector& points, std::vector& pointIndices) +{ + // create side face for boundary and hole + for (const auto& boundary : polygon.second) + { + // each face is a quad which is to be broken down into 2 triangles + unsigned int totalFacePoints = boundary.size(); + unsigned int faces = (totalFacePoints / 2) - 1; + + for (unsigned int face = 0; face < faces; face++) + { + // indexes required for setting up the triangles + unsigned int index1 = (face * 2); + unsigned int index2 = (face * 2) + 1; + unsigned int index3 = (face * 2) + 2; + unsigned int index4 = (face * 2) + 3; + + // read quad indices + // triangle 1 + readTriangleIndices(pointIndices, index1, index2, index3); + // triangle 2 + readTriangleIndices(pointIndices, index3, index2, index4); + + // face points + buw::Vector3f point1 = boundary[index1].cast(); + buw::Vector3f point2 = boundary[index2].cast(); + buw::Vector3f point3 = boundary[index3].cast(); + buw::Vector3f point4 = boundary[index4].cast(); + + // calculate triangle normals + // triangle1 + buw::Vector3f normal1 = calcNormal(point1, point2, point3); + // triangle2 + buw::Vector3f normal2 = calcNormal(point3, point2, point4); + + // read color + buw::Vector3f color = osmGeometry_.getColor(polygon.first); + + // read quad points + // triangle1 + readTrianglePoints(points, point1, point2, point3, color, normal1); + // triangle 2 + readTrianglePoints(points, point3, point2, point4, color, normal2); + } + } +} + + +void OsmReader::readSideFaceHighway(Polygon_t& polygon, std::vector& points, std::vector& pointIndices) +{ + Way_t outerBoundary = polygon.second.at(0); + unsigned int totalPoints = outerBoundary.size(); + unsigned int faces = totalPoints / 2, lastQuadStart = totalPoints - 4; + + // front & back face + for (unsigned int face = 0; face < 2; face++) + { + unsigned int index1 = (lastQuadStart * face); + unsigned int index2 = (lastQuadStart * face) + 1; + unsigned int index3 = (lastQuadStart * face) + 2; + unsigned int index4 = (lastQuadStart * face) + 3; + + // read quad indices + // triangle 1 + readTriangleIndices(pointIndices, index1, index2, index3); + // triangle 2 + readTriangleIndices(pointIndices, index3, index2, index4); + + // face points + buw::Vector3f point1 = outerBoundary[index1].cast(); + buw::Vector3f point2 = outerBoundary[index2].cast(); + buw::Vector3f point3 = outerBoundary[index3].cast(); + buw::Vector3f point4 = outerBoundary[index4].cast(); + + // calculate triangle normals + // triangle1 + buw::Vector3f normal1 = calcNormal(point1, point2, point3); + // triangle2 + buw::Vector3f normal2 = calcNormal(point3, point2, point4); + + // read color + buw::Vector3f color = osmGeometry_.getColor(polygon.first); + + // read quad points + // triangle1 + readTrianglePoints(points, point1, point2, point3, color, normal1); + // triangle 2 + readTrianglePoints(points, point3, point2, point4, color, normal2); + } + + // side face + for (unsigned int face = 0; face < faces - 2; face++) + { + unsigned int index1 = (face * 2); + unsigned int index2 = (face * 2) + 1; + unsigned int index3 = (face * 2) + 4; + unsigned int index4 = (face * 2) + 5; + + // read quad indices + // triangle 1 + readTriangleIndices(pointIndices, index1, index2, index3); + // triangle 2 + readTriangleIndices(pointIndices, index3, index2, index4); + + // face points + buw::Vector3f point1 = outerBoundary[index1].cast(); + buw::Vector3f point2 = outerBoundary[index2].cast(); + buw::Vector3f point3 = outerBoundary[index3].cast(); + buw::Vector3f point4 = outerBoundary[index4].cast(); + + // calculate triangle normals + // triangle1 + buw::Vector3f normal1 = calcNormal(point1, point2, point3); + // triangle2 + buw::Vector3f normal2 = calcNormal(point3, point2, point4); + + // read color + buw::Vector3f color = osmGeometry_.getColor(polygon.first); + + // read quad points + // triangle1 + readTrianglePoints(points, point1, point2, point3, color, normal1); + // triangle 2 + readTrianglePoints(points, point3, point2, point4, color, normal2); + } +} + + +void OsmReader::surfaceMesh3D(Eigen::MatrixXd& tri2DPoints, Eigen::MatrixXi& tri2DIndices, Eigen::MatrixXf& surface3DPoints, Eigen::MatrixXi& surface3DIndices, const std::vector zPlane) +{ + // points + Eigen::MatrixXf bottomFacePoints; + createMeshPoints(tri2DPoints, bottomFacePoints, zPlane[0]); + Eigen::MatrixXf topFacePoints; + createMeshPoints(tri2DPoints, topFacePoints, zPlane[1]); + int meshRows = bottomFacePoints.rows() + topFacePoints.rows(); + + // indices + Eigen::MatrixXi bottomFaceIndices; + createMeshIndices(tri2DIndices, bottomFaceIndices); + Eigen::MatrixXi topFaceIndices; + createMeshIndices(tri2DIndices, topFaceIndices, tri2DPoints.rows()); + int indexRows = bottomFaceIndices.rows() + topFaceIndices.rows(); + + // complete mesh + // points + mergeMeshes(surface3DPoints, std::vector>{bottomFacePoints, topFacePoints}, meshRows); + // indices + mergeMeshes(surface3DIndices, std::vector>{bottomFaceIndices, topFaceIndices}, indexRows); +} + + +void OsmReader::surfaceTriangulation(Polygon_t& inputPoly, Eigen::MatrixXd& triangulatedPoints, Eigen::MatrixXi& triangulatedIndices, const QString featureType) +{ + // all ways and indices container + Way_t allNodes; std::vector> allIndices; + + // add hole midpoints [triangulation algorithm omits triangulation in holes] + Eigen::MatrixXd holePoints; + if (inputPoly.second.size() > 1 && inputPoly.first == "multipolygon" && featureType == "building") { + // get all inner elements + //std::vector inner( inputPoly.second.begin() + 1, inputPoly.second.end() ); + std::vector inner(inputPoly.second.begin() + 1, inputPoly.second.end()); + // seperate hole from sub-element + osmGeometry_.getHoles(inner, holePoints); + } + + // for different features + if (featureType == "highway") + osmGeometry_.combineHighway(inputPoly, allNodes, allIndices); + else + osmGeometry_.combineWayArbitrary(inputPoly, allNodes, allIndices); + + // 2D mesh + std::pair mesh = triangulate(allNodes, allIndices, holePoints); + triangulatedPoints = mesh.first; + triangulatedIndices = mesh.second; +} + + +std::pair OsmReader::triangulate(Way_t& wayNodes, std::vector>& wayIndices, Eigen::MatrixXd& holePoints) +{ + // map stl to Eigen (nodes and indices) + Eigen::MatrixXd triInputPoints(wayNodes.size(), 2); + Eigen::MatrixXi triInputIndices(wayIndices.size(), 2); + osmGeometry_.mapStlToEigen(triInputPoints, triInputIndices, wayNodes, wayIndices); + + Eigen::MatrixXd triangulatedPoints; + Eigen::MatrixXi triangulatedIndices; + igl::triangle::triangulate(triInputPoints, triInputIndices, holePoints, "q", triangulatedPoints, triangulatedIndices); + + return std::pair(triangulatedPoints, triangulatedIndices); +} + + +void OsmReader::createMeshPoints(Eigen::MatrixXd triPoints, Eigen::MatrixXf& surfacePoints, const double& zPlane) +{ + // 2D to 3D + triPoints.conservativeResize(triPoints.rows(), triPoints.cols() + 1); + // convert from Xd to Xf + Eigen::MatrixXf triPointsFloat = triPoints.cast (); + // fill z + triPointsFloat.col(2).fill(zPlane); + // copy the result to output + surfacePoints = triPointsFloat.replicate(1, 1); +} + + +void OsmReader::createMeshIndices(Eigen::MatrixXi triIndices, Eigen::MatrixXi& surfaceIndices, int lastPoint) +{ + if (lastPoint != 0) + triIndices.array() += lastPoint; + + surfaceIndices = triIndices.replicate(1, 1); +} + + +void OsmReader::readFaceMesh(std::vector& points, std::vector& pointIndices, const Eigen::MatrixXf& faceMeshPoints, + const Eigen::MatrixXi& faceMeshIndices, const QString featureType) +{ + // get the triangles + int numOfTri = faceMeshIndices.rows(); + + for (int i = 0; i < numOfTri; i++) + { + // get the triangle points + buw::Vector3f point1 = faceMeshPoints.row(faceMeshIndices.row(i)[0]); + buw::Vector3f point2 = faceMeshPoints.row(faceMeshIndices.row(i)[1]); + buw::Vector3f point3 = faceMeshPoints.row(faceMeshIndices.row(i)[2]); + + // calculate the normal + buw::Vector3f normal = calcNormal(point1, point2, point3); + + // get the color + buw::Vector3f color = osmGeometry_.getColor(featureType); + + // create points with position, color and normal + points.push_back(PointLayout_t(point1, color, normal)); + points.push_back(PointLayout_t(point2, color, normal)); + points.push_back(PointLayout_t(point3, color, normal)); + } + + // create the indices + pointIndices.insert(pointIndices.end(), faceMeshIndices.data(), faceMeshIndices.data() + faceMeshIndices.size()); +} + + +void OsmReader::readTrianglePoints(std::vector& points, const buw::Vector3f& point1, const buw::Vector3f& point2, const buw::Vector3f& point3, + const buw::Vector3f& color, const buw::Vector3f& normal) +{ + // create points with position, color and normal + points.push_back(PointLayout_t(point1, color, normal)); + points.push_back(PointLayout_t(point2, color, normal)); + points.push_back(PointLayout_t(point3, color, normal)); +} + + +void OsmReader::readTriangleIndices(std::vector& pointIndices, const unsigned int index1, const unsigned int index2, const unsigned int index3) +{ + // triangle + pointIndices.push_back(index1); + pointIndices.push_back(index2); + pointIndices.push_back(index3); +} + + +buw::Vector3f OsmReader::calcNormal(const buw::Vector3f& point1, const buw::Vector3f& point2, const buw::Vector3f& point3) +{ + carve::geom::vector<3> vector1; + vector1.x = point1.x(); + vector1.y = point1.y(); + vector1.z = point1.z(); + carve::geom::vector<3> vector2; + vector2.x = point2.x(); + vector2.y = point2.y(); + vector2.z = point2.z(); + carve::geom::vector<3> vector3; + vector3.x = point3.x(); + vector3.y = point3.y(); + vector3.z = point3.z(); + + carve::geom::vector<3> normal = carve::geom::cross(vector1 - vector2, vector2 - vector3); + normal.normalize(); + + return buw::Vector3f(normal.x, normal.y, normal.z); +} + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_END \ No newline at end of file diff --git a/Core/src/OsmDataConverter/OsmDataReader.h b/Core/src/OsmDataConverter/OsmDataReader.h new file mode 100644 index 000000000..9ed1f9882 --- /dev/null +++ b/Core/src/OsmDataConverter/OsmDataReader.h @@ -0,0 +1,213 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#pragma once +#ifndef OpenInfraPlatform_Core_OsmDataConverter_51f00dc0_473e_4406_a0c2_2847806ff341_h +#define OpenInfraPlatform_Core_OsmDataConverter_51f00dc0_473e_4406_a0c2_2847806ff341_h + +// Qt5 includes +#include +#include +#include + +// external libraries +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "namespace.h" +#include "OsmDataModel.h" +#include "OsmDataGeometry.h" + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_BEGIN + +/** +* @brief The OsmReader class provides methods to read OSM data from a file and create 3D meshes from the data. +*/ +class OsmReader +{ +private: + // the OSM geometry + OsmGeometry osmGeometry_; + +public: + /** + * @brief Default constructor. + */ + explicit OsmReader(); + + /** + * @brief Destructor. + */ + virtual ~OsmReader(); + + /** + * @brief Reads OSM data from a file and returns the resulting OsmModel. + * @param filename The name of the file to read. + * @return A shared pointer to the OsmModel created from the file. + */ + std::shared_ptr readFile(const std::string& filename); + + /** + * @brief Creates a 3D mesh from the given polygon and geometry description. + * @param polygon The polygon to mesh. + * @param geometry The geometry description. + * @param featureType The type of feature. + */ + void meshGeometry(Polygon_t& polygon, std::shared_ptr geometry, const QString featureType); + + + void surfaceTriangulation(Polygon_t& inputPoly, Eigen::MatrixXd& triangulatedPoints, Eigen::MatrixXi& triangulatedIndices, const QString featureType); + + /** + * @brief Triangulates the input polygon and returns the resulting triangulated points and indices. + * @param inputPoly The input polygon to triangulate. + * @param triangulatedPoints The triangulated points. + * @param triangulatedIndices The triangulated indices. + * @param featureType The type of feature. + * @return A pair of the triangulated points and indices. + */ + std::pair triangulate(Way_t& triInputPoints, std::vector>& triInputIndices, Eigen::MatrixXd& holePoints); + + /** + * @brief Reads a face mesh from the given face mesh points and indices and the feature type. + * @param points The resulting points of the mesh. + * @param pointIndices The resulting point indices of the mesh. + * @param faceMeshPoints The face mesh points. + * @param faceMeshIndices The face mesh indices. + * @param featureType The type of feature. + */ + void readFaceMesh(std::vector& points, std::vector& pointIndices, const Eigen::MatrixXf& faceMeshPoints, + const Eigen::MatrixXi& faceMeshIndices, const QString featureType); + + /** + * @brief Reads a surface plane face from the given polygon and feature type. + * @param polygon The polygon to create the surface plane face from. + * @param points The resulting points of the face. + * @param pointIndices The resulting point indices of the face. + * @param featureType The type of feature. + */ + void readSurfacePlaneFace(Polygon_t& polygon, std::vector& points, std::vector& pointIndices, const QString featureType); + + /** + * @brief Reads a side face with arbitrary shape from the given polygon and feature type. + * @param polygon The polygon to create the side face from. + * @param points The resulting points of the face. + * @param pointIndices The resulting point indices of the face. + */ + void readSideFaceArbitrary(Polygon_t& polygon, std::vector& points, std::vector& pointIndices); + + /** + * @brief Read the side face of a highway feature. + * @param polygon The polygon to read. + * @param points The list of mesh points. + * @param pointIndices The list of indices that connect the mesh points. + */ + void readSideFaceHighway(Polygon_t& polygon, std::vector& points, std::vector& pointIndices); + + /** + * @brief Generate a 3D surface mesh from 2D triangulated points. + * @param tri2DPoints The 2D triangulated points. + * @param tri2DIndices The indices of the 2D triangulated points. + * @param surface3DPoints The output 3D surface points. + * @param surface3DIndices The output 3D surface indices. + * @param zPlane The Z values for each 2D point. + */ + void surfaceMesh3D(Eigen::MatrixXd& tri2DPoints, Eigen::MatrixXi& tri2DIndices, Eigen::MatrixXf& surface3DPoints, Eigen::MatrixXi& surface3DIndices, const std::vector zPlane); + + /** + * @brief Create 3D mesh points from 2D triangulated points. + * @param triPoints The 2D triangulated points. + * @param surfacePoints The output 3D surface points. + * @param zPlane The Z values for each 2D point. + */ + void createMeshPoints(Eigen::MatrixXd triPoints, Eigen::MatrixXf& surfacePoints, const double& zPlane = 0); + + /** + * @brief Create 3D mesh indices from 2D triangulated indices. + * @param triIndices The 2D triangulated indices. + * @param surfaceIndices The output 3D surface indices. + * @param lastPoint The last point in the mesh. + */ + void createMeshIndices(Eigen::MatrixXi triIndices, Eigen::MatrixXi& surfaceIndices, int lastPoint = 0); + + /** + * @brief Merge multiple meshes into one final mesh. + * @tparam T The type of the mesh element. + * @tparam Container The type of container for the mesh element. + * @tparam Args The arguments for the container. + * @param finalMesh The final merged mesh. + * @param meshElement The mesh element to merge. + * @param numRows The number of rows in the mesh. + */ + template class Container, typename... Args> + void mergeMeshes(T& finalMesh, const Container& meshElement, const int numRows); + + /** + * @brief Read triangle points from 3 given points. + * @param points The list of mesh points. + * @param point1 The first point of the triangle. + * @param point2 The second point of the triangle. + * @param point3 The third point of the triangle. + * @param color The color of the triangle. + * @param normal The normal vector of the triangle. + */ + void readTrianglePoints(std::vector& points, const buw::Vector3f& point1, const buw::Vector3f& point2, const buw::Vector3f& point3, + const buw::Vector3f& color, const buw::Vector3f& normal); + + /** + * @brief Reads triangle indices and adds them to a vector. + * @param pointIndices Vector of point indices to add triangle indices. + * @param index1 The first index of a point in a triangle. + * @param index2 The second index of a point in a triangle. + * @param index3 The third index of a point in a triangle. + */ + void readTriangleIndices(std::vector& pointIndices, const unsigned int index1, const unsigned int index2, const unsigned int index3); + + /** + * @brief Calculates the normal vector to a triangle defined by three points. + * @param point1 The first point in the triangle. + * @param point2 The second point in the triangle. + * @param point3 The third point in the triangle. + * @return The normal vector to the triangle. + */ + buw::Vector3f calcNormal(const buw::Vector3f& point1, const buw::Vector3f& point2, const buw::Vector3f& point3); +}; + +// typename should be an Eigen::Matrix +template class Container, typename... Args> +void OsmReader::mergeMeshes(T& finalMesh, const Container& meshElement, const int numRows) +{ + finalMesh.resize(numRows, meshElement.at(0).cols()); + int startIndex = 0; + for (auto& element : meshElement) { + finalMesh.block(startIndex, 0, element.rows(), meshElement.at(0).cols()) = element; + startIndex += element.rows(); + } +} + +OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_END + +EMBED_CORE_OSMDATACONVERTER_INTO_OIP_NAMESPACE(OsmReader) + +#endif // end define OpenInfraPlatform_Core_OsmDataConverter_51f00dc0_473e_4406_a0c2_2847806ff341_h \ No newline at end of file diff --git a/Core/src/OsmDataConverter/namespace.h b/Core/src/OsmDataConverter/namespace.h new file mode 100644 index 000000000..dbe179fee --- /dev/null +++ b/Core/src/OsmDataConverter/namespace.h @@ -0,0 +1,46 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . + + NOTE: This file has been generated with Tools/namespaceGenerator.py . +*/ + + +#pragma once +#ifndef OpenInfraPlatform_Core_namespace_2e59ccad_8fb5_4dc1_9eeb_689d15b8f45b_h +#define OpenInfraPlatform_Core_namespace_2e59ccad_8fb5_4dc1_9eeb_689d15b8f45b_h + +# if !defined OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_BEGIN +# define OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_BEGIN \ +namespace OpenInfraPlatform { \ +namespace Core { \ +namespace OsmDataConverter { +# endif + +# if !defined OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_END +# define OIP_NAMESPACE_OPENINFRAPLATFORM_CORE_OSMDATACONVERTER_END \ +}\ +}\ +} +# endif + +# if !defined EMBED_CORE_OSMDATACONVERTER_INTO_OIP_NAMESPACE +# define EMBED_CORE_OSMDATACONVERTER_INTO_OIP_NAMESPACE(X) \ +namespace oip { \ +using OpenInfraPlatform::Core::OsmDataConverter::X; \ +} +# endif + +#endif // end define OpenInfraPlatform_Core_namespace_2e59ccad_8fb5_4dc1_9eeb_689d15b8f45b_h diff --git a/Rendering/Effects/OsmGeometryEffect.cpp b/Rendering/Effects/OsmGeometryEffect.cpp new file mode 100644 index 000000000..5c7768b8f --- /dev/null +++ b/Rendering/Effects/OsmGeometryEffect.cpp @@ -0,0 +1,160 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#include "OsmGeometryEffect.h" + +#include "../Resources/RenderResources.h" +#include +#include + +#include + +OIP_NAMESPACE_OPENINFRAPLATFORM_RENDERING_BEGIN + +OsmGeometryEffect::OsmGeometryEffect(buw::IRenderSystem* renderSystem, + buw::ReferenceCounted viewport, + buw::ReferenceCounted depthStencilMSAA, + buw::ReferenceCounted worldBuffer) + : + EffectBase(renderSystem), + viewport_(viewport), + worldBuffer_(worldBuffer), + depthStencilMSAA_(depthStencilMSAA) +{ +} + + +OsmGeometryEffect::~OsmGeometryEffect() +{ + worldBuffer_ = nullptr; + vertexBuffer_ = nullptr; + indexBuffer_ = nullptr; + depthStencilMSAA_ = nullptr; + pipelineState_ = nullptr; + viewport_ = nullptr; +} + + +void OsmGeometryEffect::setOsmModel(buw::ReferenceCounted osmModel) +{ + osmModel_ = osmModel; +} + + +void OsmGeometryEffect::changeOffset(const buw::Vector3d& offsetOld, const buw::Vector3d& offsetNew) +{ + if (!osmModel_->isEmpty()) { + + //reset + if (vertexBuffer_) vertexBuffer_.reset(); + if (indexBuffer_) indexBuffer_.reset(); + + //tmp containers + std::vector verticesMesh; + std::vector indicesMesh; + + //process the data + for (auto& geometry : osmModel_->geometries()) + { + if (!geometry->isEmpty()) + { + std::vector vertices; + vertices.reserve(geometry->meshDescription.points.size()); + for (auto& point : geometry->meshDescription.points) + { + PointLayout_t vtx(point); + vtx.position[0] += offsetNew.x(); + vtx.position[1] += offsetNew.y(); + vtx.position[2] += offsetNew.z(); + vertices.push_back(vtx); + } + + const uint32_t verticesOffset = verticesMesh.size(); + verticesMesh.insert(verticesMesh.end(), vertices.begin(), vertices.end()); + + const uint32_t indicesOffset = indicesMesh.size(); + indicesMesh.insert(indicesMesh.end(), geometry->meshDescription.indices.begin(), geometry->meshDescription.indices.end()); + std::for_each(indicesMesh.begin() + indicesOffset, indicesMesh.end(), [&verticesOffset](uint32_t& index) {index += verticesOffset; }); + } + } + + //upload data + if (!verticesMesh.empty()) + { + // descriptions + buw::vertexBufferDescription vbd; + buw::indexBufferDescription ibd; + + vbd.data = verticesMesh.data(); + vbd.vertexCount = verticesMesh.size(); + vbd.vertexLayout = buw::VertexPosition3Color3Normal3::getVertexLayout(); + vertexBuffer_ = renderSystem()->createVertexBuffer(vbd); + + BLUE_LOG(trace) << "Done creating OSM geometry vertex buffer. Size:" << QString::number(verticesMesh.size()).toStdString(); + + ibd.data = &indicesMesh[0]; + ibd.indexCount = indicesMesh.size(); + ibd.format = buw::eIndexBufferFormat::UnsignedInt32; + indexBuffer_ = renderSystem()->createIndexBuffer(ibd); + } + } + else { + throw std::exception("The given OSMModel is empty!"); + } +} + + +void OsmGeometryEffect::v_init() +{ + try { + /*Create the pipeline state.*/ + buw::pipelineStateDescription psd; + // #TODO: create OsmGeometryEffect.be + psd.effectFilename = buw::Singleton::instance().getResourceRootDir() + "/Shader/OffGeometryEffect.be"; + psd.pipelineStateName = "main"; + psd.vertexLayout = buw::VertexPosition3Color3Normal3::getVertexLayout(); + psd.primitiveTopology = buw::ePrimitiveTopology::TriangleList; + psd.renderTargetFormats = { buw::eTextureFormat::R8G8B8A8_UnsignedNormalizedInt_SRGB }; + psd.useDepth = true; + psd.useMSAA = true; + pipelineState_ = createPipelineState(psd); + } + catch (...) { + pipelineState_ = nullptr; + vertexBuffer_ = nullptr; + indexBuffer_ = nullptr; + } +} + + +void OsmGeometryEffect::v_render() +{ + buw::ReferenceCounted renderTarget = renderSystem()->getBackBufferTarget(); + + if (pipelineState_ && vertexBuffer_ && indexBuffer_) + { + setPipelineState(pipelineState_); + setRenderTarget(renderTarget, depthStencilMSAA_); + setViewport(viewport_); + setConstantBuffer(worldBuffer_, "WorldBuffer"); + setVertexBuffer(vertexBuffer_); + setIndexBuffer(indexBuffer_); + drawIndexed(static_cast(indexBuffer_->getIndexCount())); + } +} + +OIP_NAMESPACE_OPENINFRAPLATFORM_RENDERING_END \ No newline at end of file diff --git a/Rendering/Effects/OsmGeometryEffect.h b/Rendering/Effects/OsmGeometryEffect.h new file mode 100644 index 000000000..e92ccefcf --- /dev/null +++ b/Rendering/Effects/OsmGeometryEffect.h @@ -0,0 +1,66 @@ +/* +Copyright (c) 2021 Technical University of Munich +Chair of Computational Modeling and Simulation. + +TUM Open Infra Platform is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License Version 3 +as published by the Free Software Foundation. + +TUM Open Infra Platform 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 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 . +*/ + +#pragma once +#ifndef OpenInfraPlatform_UserInterface_OsmGeometryEffect_7759dd4a_7404_4bcb_905e_b26a77541c80_h +#define OpenInfraPlatform_UserInterface_OsmGeometryEffect_7759dd4a_7404_4bcb_905e_b26a77541c80_h + +#include "OsmDataConverter\OsmDataModel.h" +#include +#include + +#include "..\namespace.h" + +OIP_NAMESPACE_OPENINFRAPLATFORM_RENDERING_BEGIN + +class OsmGeometryEffect : public EffectBase +{ +public: + + // Construct by providing the renderSystem, viewport, depthStencil and worldBuffer for camera etc. + OsmGeometryEffect(buw::IRenderSystem* renderSystem, + buw::ReferenceCounted viewport, + buw::ReferenceCounted depthStencilMSAA, + buw::ReferenceCounted worldBuffer); + + // Release all resources + virtual ~OsmGeometryEffect(); + + void setOsmModel(buw::ReferenceCounted osmModel); + +private: + void v_init(); + void v_render(); + + //! EffectBase interface - change offset to the new value + virtual void changeOffset(const buw::Vector3d& offsetOld, const buw::Vector3d& offsetNew) override; +private: + buw::ReferenceCounted worldBuffer_ = nullptr; + buw::ReferenceCounted vertexBuffer_ = nullptr; + buw::ReferenceCounted indexBuffer_ = nullptr; + buw::ReferenceCounted depthStencilMSAA_ = nullptr; + buw::ReferenceCounted pipelineState_ = nullptr; + buw::ReferenceCounted viewport_ = nullptr; + + buw::ReferenceCounted osmModel_ = nullptr; +}; + +OIP_NAMESPACE_OPENINFRAPLATFORM_RENDERING_END + +EMBED_RENDERING_INTO_OIP_NAMESPACE(OsmGeometryEffect) + +#endif //end define OpenInfraPlatform_UserInterface_OsmGeometryEffect_7759dd4a_7404_4bcb_905e_b26a77541c80_h \ No newline at end of file diff --git a/UserInterface/Dialogues/HttpDownloadDialog.cpp b/UserInterface/Dialogues/HttpDownloadDialog.cpp new file mode 100644 index 000000000..363f4bb8c --- /dev/null +++ b/UserInterface/Dialogues/HttpDownloadDialog.cpp @@ -0,0 +1,541 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . +*/ + +#include "HttpDownloadDialog.h" +#include +#include + +// #TODO: change 'tr' in QString where translation our 'sourceText' isn't needed + +OpenInfraPlatform::UserInterface::HttpDownloadDialog::HttpDownloadDialog(QWidget* parent /*=nullptr*/) : + QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint) +{ + setupUi(this); + + // initialize success to false (0) + successStatus_ = 0; + stopProcessing_ = false; + + // disable run button unless all mandatory fields are filled + this->runBtn->setEnabled(false); + + // initialize tree model instance + treeModel_ = new HttpTreeModel(this); + + // initialize manager for request & reply over HTTP + manager_ = new QNetworkAccessManager(this); + + // text input connections + // enable run button only when all mandatory fields have been entered + connect(this->countryCodeEdit, &QLineEdit::textChanged, this, &HttpDownloadDialog::checkFieldsFilled); + connect(this->cityEdit, &QLineEdit::textChanged, this, &HttpDownloadDialog::checkFieldsFilled); + connect(this->areaEdit, &QLineEdit::textChanged, this, &HttpDownloadDialog::checkFieldsFilled); + + //// HttpTreeModelDialog connections + //// on quit + //connect(treeModel_->quitBtn, &QPushButton::clicked, this, &HttpDownloadDialog::quitTreeModel); + //// on select button clicked + //connect(treeModel_->selectBtn, &QPushButton::clicked, this, &HttpDownloadDialog::treeModelSelect); + + // HttpDownloadDialog connections + // returns the dialog code success or fail using the 'done' slot + // https://stackoverflow.com/questions/37094751/qdialog-return-value-accepted-or-rejected-only + //connect(this, SIGNAL(transmitStatus(int)), this, SLOT(done(int))); + connect(this, &HttpDownloadDialog::transmitStatus, this, &HttpDownloadDialog::done); +} + +OpenInfraPlatform::UserInterface::HttpDownloadDialog::~HttpDownloadDialog() +{ + if (file_) { + file_->remove(); + delete file_; + file_ = nullptr; + } + this->reply_ = nullptr; + this->endpoint_.clear(); + this->translator_ = nullptr; + this->selectedPosition_ = NULL; + this->successStatus_ = NULL; +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::on_quitBtn_clicked() +{ + close(); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::checkFieldsFilled() +{ + bool filled = !(this->countryCodeEdit->text().isEmpty()) && !(this->cityEdit->text().isEmpty()) && !(this->areaEdit->text().isEmpty()); + this->runBtn->setEnabled(filled); +} + + +bool OpenInfraPlatform::UserInterface::HttpDownloadDialog::invalidChar() +{ + bool invalid = false; + // get all user inputs into a list + QStringList userInputs = (QStringList() << this->countryCodeEdit->text() << this->cityEdit->text() << this->areaEdit->text()); + + // allowed characters + QRegularExpression validRe("[!A-Za-z0-9,]+"); + + // if characters apart from the allowed -> invalid + if (userInputs.indexOf(validRe)) + invalid = true; + + return invalid; +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::checkGermanChar(QStringList& addresses) +{ + QRegularExpression germanChar("[äöüß]+"); + // if german char exists + for (int i = 0; i < addresses.length(); i++) + { + if (addresses[i].indexOf(germanChar)) + addresses[i].replace(QString::fromLatin1("ä"), QString("ae")).replace(QString::fromLatin1("ö"), QString("oe")) + .replace(QString::fromLatin1("ü"), QString("ue")).replace(QString::fromLatin1("ß"), QString("ss")); + } +} + + +QString OpenInfraPlatform::UserInterface::HttpDownloadDialog::toStartCase(QString uneditedString) +{ + QStringList parts = uneditedString.split(' ', QString::SkipEmptyParts); + for (unsigned int i = 0; i < parts.size(); i++) { + parts[i] = parts[i].toLower(); + parts[i].replace(0, 1, parts[i][0].toUpper()); + } + return concatenate(parts, " "); +} + +// #TODO: refactor for case where Nominatim is down and we still proceed +bool OpenInfraPlatform::UserInterface::HttpDownloadDialog::isApiStatusValid(const std::string apiDatabase) +{ + if (apiDatabase == "nominatim") + endpoint_.setUrl(QString("https://nominatim.openstreetmap.org/status.php")); + else if (apiDatabase == "overpass") + endpoint_.setUrl(QString("http://overpass-api.de/api/status")); + + // create request + QNetworkRequest request(endpoint_); + // get reply + reply_ = manager_->get(request); + // wait until response is recieved + synchronousRequest(reply_); + + // handle error + if (reply_->error()) { + QMessageBox::critical(this, tr("%1 API HTTP Error: %2").arg(QString::fromStdString(apiDatabase)).arg(reply_->error()), + tr("%1").arg(reply_->errorString())); + return false; + } + + // reset member variables + endpoint_.clear(); + reply_->deleteLater(); + + return true; +} + + +QString OpenInfraPlatform::UserInterface::HttpDownloadDialog::getQuery(const std::string apiDatabase) +{ + QStringList stringData; + // https://nominatim.org/release-docs/develop/api/Search/ + if (apiDatabase == "nominatim") + { + // replace german characters + QStringList addresses = QStringList() << this->areaEdit->text() << this->cityEdit->text(); + checkGermanChar(addresses); + + QString lookup = tr("q=%1, %2").arg(addresses.at(0)).arg(addresses.at(1)); + // nominatim requires '+' character instead of space + lookup.replace(' ', '+'); + + QString addParams = tr("&format=jsonv2&countrycodes=%1&addressdetails=1&limit=5&polygon_geojson=1").arg(this->countryCodeEdit->text().toLower()); + stringData << lookup << addParams; + } + else if (apiDatabase == "overpass") + { + QString formatting = tr("[out:xml][timeout:180][bbox:%1];").arg(overpassBBox_); + QString outputData = tr("out geom(%1);").arg(overpassBBox_); + QString extractOsmData = queryExtractOsmData(this->radiusSpinBox->value()); + stringData << formatting << extractOsmData << outputData; + } + + return concatenate(stringData); +} + + +QString OpenInfraPlatform::UserInterface::HttpDownloadDialog::queryExtractOsmData(const unsigned int radiusInput) +{ + //// radius provided + //if (radiusInput) { + // QString osmQueryAll = tr( + // "( ( ( way['building'][!'building:part']; way['amenity']; way['leisure']; way['landuse']; way['natural'];" // included ways + // "way['highway'~'^(primary|secondary|tertiary|residential)$'][!'tunnel']; );" + // "- ( way['amenity'='toilets']; way['natural'='tree_row']; ); );" // excluded ways + // "relation['type'~'^(multipolygon|building|site)$'][!'building:part'][!'landuse']; );" // relation + // ); + // return osmQueryAll; + //} + //else { + // // region name is case-sensitive; convert to CamelCase + // QString regionOfInterest = tr("area['name'='%1']->.region;").arg(toStartCase(this->areaEdit->text())); + // QString osmQueryInRegion = tr( + // "( ( ( way(area.region)['building'][!'building:part']; way(area.region)['amenity']; way(area.region)['leisure']; way(area.region)['landuse']; way(area.region)['natural'];" + // " way(area.region)['highway'~'^(primary|secondary|tertiary|residential)$'][!'tunnel']; );" + // "- ( way(area.region)['amenity'='toilets']; way(area.region)['natural'='tree_row']; ); );" + // "relation(area.region)['type'~'^(multipolygon|building|site)$'][!'building:part'][!'landuse']; );" + // ); + + // return concatenate(QStringList() << regionOfInterest << osmQueryInRegion); + //} + + QString osmQueryAll = tr( + "( ( ( way['building'][!'building:part']; way['amenity']; way['leisure']; way['landuse']; way['natural'];" // included ways + "way['highway'~'^(primary|secondary|tertiary|residential)$'][!'tunnel']; );" + "- ( way['amenity'='toilets']; way['natural'='tree_row']; ); );" // excluded ways + "relation['type'~'^(multipolygon|building|site)$'][!'building:part'][!'landuse']; );" // relation + ); + return osmQueryAll; +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::configureUrl(const std::string apiDatabase) +{ + QString queryValue; + // distinguish endpoints + if (apiDatabase == "nominatim") + { + endpoint_.setUrl(QString("https://nominatim.openstreetmap.org/search")); + queryValue = getQuery("nominatim"); + // https://doc.qt.io/qt-6/qurl.html#ParsingMode-enum + endpoint_.setQuery(queryValue, QUrl::DecodedMode); + } + else if (apiDatabase == "overpass") + { + endpoint_.setUrl(QString("https://overpass-api.de/api/interpreter")); + QString key = "data"; + QString queryValue = getQuery("overpass"); + QUrlQuery query; + query.addQueryItem(key, queryValue); + endpoint_.setQuery(query); + } +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::setupTreeModel(const QJsonArray& data) +{ + // initiate tree model + treeModel_->buildTreeModel(data); + + // wait till dialog returns + treeModel_->setModal(true); + int result = treeModel_->exec(); + // accepted + if (result == QDialog::Accepted) + selectedPosition_ = treeModel_->getSelectedPosition(); + + // deallocate + treeModel_->deleteLater(); + treeModel_ = nullptr; +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::extractBBox(const QJsonObject& selectedObject) +{ + // get the bounding box array + QJsonArray bboxArr = selectedObject.value("boundingbox").toArray(); + // check if user entered a radius + if (this->radiusSpinBox->value() != 0) + scaleBBox(bboxArr, this->radiusSpinBox->value()); + + // convert bbox to Overpass-API format + convertBBoxFormat(bboxArr); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::scaleBBox(QJsonArray& bbox, const int& radius) +{ + // scale N & E +ve, while S & W -ve + // radius is in metres while bbox is in Geodetic CRS (WGS-84) + // earth radius = 6371000m (approx.) + + // constant + const double scaleFactor = (radius / 6371000.0), degToRad = (180 / M_PI); + + // get data in double format for ease of calculation later + const double southLat = bbox.at(0).toString().toDouble(); + const double westLon = bbox.at(2).toString().toDouble(); + const double northLat = bbox.at(1).toString().toDouble(); + const double eastLon = bbox.at(3).toString().toDouble(); + + // perform the calculations + double newSouthLat = southLat - (scaleFactor * degToRad); + double newWestLon = westLon - (scaleFactor * degToRad) / cos(southLat * (M_PI / 180)); + double newNorthLat = northLat + (scaleFactor * degToRad); + double newEastLon = eastLon + (scaleFactor * degToRad) / cos(northLat * (M_PI / 180)); + + // replace bbox content with modified values + bbox.replace(0, QJsonValue(QString::number(newSouthLat, 'g', 7))); + bbox.replace(2, QJsonValue(QString::number(newWestLon, 'g', 7))); + bbox.replace(1, QJsonValue(QString::number(newNorthLat, 'g', 7))); + bbox.replace(3, QJsonValue(QString::number(newEastLon, 'g', 7))); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::convertBBoxFormat(const QJsonArray& nominatimBBox) +{ + // Nominatim bbox format: South Lat.[0], North Lat.[1], West Long.[2], East Long.[3] + // Overpass API format: South Lat.[0], West Long.[1], North Lat.[2], East Long.[3] + + overpassBBox_ = QString("%1, %2, %3, %4").arg(nominatimBBox.at(0).toString()).arg(nominatimBBox.at(2).toString()) + .arg(nominatimBBox.at(1).toString()).arg(nominatimBBox.at(3).toString()); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::on_runBtn_clicked() +{ + this->runBtn->setEnabled(false); + + // check if invalid (special) characters entered + if (invalidChar()) { + QMessageBox::critical(this, tr("Invalid Syntax"), tr("Please avoid special characters.")); + // fail + emit transmitStatus(successStatus_); + return; + } + + // reset any previous state + stopProcessing_ = false; + if (reply_) { + reply_->deleteLater(); + } + if (file_) { + file_->remove(); + delete file_; + file_ = nullptr; + } + + // handle Nominatim + processApi("nominatim"); + // handle error + if (stopProcessing_) { + emit transmitStatus(successStatus_); + return; + } + + // handle overpass + processApi("overpass"); + emit transmitStatus(successStatus_); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::processApi(const std::string apiDatabase) +{ + if (apiDatabase == "overpass") { + // create file to store OSM data + QString filename = "C:/Users/Moiez/Desktop/MasterThesis/OIP/Open-Infra-Platform/UserInterface/osm.xml"; + file_ = new QFile(filename); + } + + // check if API connection is valid + if (!isApiStatusValid(apiDatabase)) { + stopProcessing_ = true; + return; + } + + // configure URL with query + configureUrl(apiDatabase); + + // get the reply + startRequest(endpoint_); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::startRequest(const QUrl& url) +{ + // create request + QNetworkRequest request(url); + + // get reply + reply_ = manager_->get(request); + + // reset url member + endpoint_.clear(); + + // wait until response is recieved + synchronousRequest(reply_); + + // proceed to the slot 'httpDownloadFinished' for further processing and error handling + httpDownloadFinished(); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::synchronousRequest(QNetworkReply* reply) +{ + // create timer to timeout in case of network connectivity issue + QTimer getTimer; + // create an eventloop for synchronous response + QEventLoop loop; + // connect timeout signal to quit() slot of event loop + connect(&getTimer, &QTimer::timeout, &loop, &QEventLoop::quit); + // connect reply_ to quit() slot of event loop + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + // 60,000ms (60s) wait for get response + getTimer.start(60000); + // execute and wait for response + loop.exec(); +} + + +void OpenInfraPlatform::UserInterface::HttpDownloadDialog::httpDownloadFinished() +{ + qint64 httpStatus = reply_->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + QString contentType = reply_->header(QNetworkRequest::ContentTypeHeader).toString(); + + // error handling + if (httpStatus == 0 || reply_->error()) { + + // NULL (timeout) + if (httpStatus == 0) + QMessageBox::warning(this, tr("Network Connectivity Issue"), + tr("Network connection failed. Please try again in a few moments.")); + + // error while fetching data + else if (reply_ && reply_->error()) + QMessageBox::information(this, tr("HTTP Download"), + tr("Download failed: %1").arg(reply_->errorString())); + + // remove file if exists + if (file_->exists()) + file_->remove(); + + // delete reply_ + reply_->deleteLater(); + + // fail + stopProcessing_ = true; + return; + } + + // downloaded data + QByteArray buffer = reply_->readAll(); + + // Nominatim + if (reply_->url().host() == QString("nominatim.openstreetmap.org")) + { + QJsonParseError jsonError; + + // json parser here + QJsonDocument jsonDoc = QJsonDocument::fromJson(buffer, &jsonError); + if (jsonError.error == QJsonParseError::NoError) { + + qsizetype dataSize = jsonDoc.array().size(); + + // data recieved with no error + if (dataSize) { + QJsonArray data = jsonDoc.array(); + + // only show model tree if multiple data options retrieved + if (dataSize > 1) { + QMessageBox::information(this, tr("Data Loaded"), + tr("Multiple data have been retrieved.\n Please select one of the following.")); + + // setup HttpTreeModel dialog + setupTreeModel(data); + } + // get bbox + extractBBox(data.at(selectedPosition_).toObject()); + } + + // no data recieved with no error + else { + + QMessageBox::information(this, QString("No data loaded"), + QString("Please make sure there are no typos or wrong names provided and try again.")); + + reply_->deleteLater(); + + // fail + stopProcessing_ = true; + return; + } + + reply_->deleteLater(); + } + + // handle error + else { + QMessageBox::critical(this, tr("Json Parsing"), tr("%1.").arg(reply_->error())); + + reply_->deleteLater(); + + // fail + stopProcessing_ = true; + return; + } + } + + // Overpass API + else if (reply_->url().host() == QString("overpass-api.de")) { + + // handle file writing error + if (!file_->open(QIODevice::WriteOnly)) { + QMessageBox::information(this, tr("HTTP Download"), + tr("Error saving file: %1").arg(file_->errorString())); + + // fail + stopProcessing_ = true; + return; + } + else if (buffer.isEmpty()) { + + // handle empty buffer; i.e. overpass-api returned empty data + QMessageBox::warning(this, tr("HTTP Download"), + tr("Empty data retrieved from server. The file was not written.")); + + // remove file if exists + if (file_->exists()) + file_->remove(); + + // delete reply + reply_->deleteLater(); + + // fail + stopProcessing_ = true; + return; + } + + // write data to file + file_->write(buffer); + file_->flush(); + file_->close(); + + // success + reply_->deleteLater(); + successStatus_ = 1; + delete file_; + file_ = nullptr; + } +} + diff --git a/UserInterface/Dialogues/HttpDownloadDialog.h b/UserInterface/Dialogues/HttpDownloadDialog.h new file mode 100644 index 000000000..73a79a856 --- /dev/null +++ b/UserInterface/Dialogues/HttpDownloadDialog.h @@ -0,0 +1,245 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . +*/ + +#pragma once +#ifndef OpenInfraPlatform_UserInterface_HttpDownloadDialog_38da791c_994f_450a_abc1_6676bdf555c4_h +#define OpenInfraPlatform_UserInterface_HttpDownloadDialog_38da791c_994f_450a_abc1_6676bdf555c4_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DataManagement/General/Data.h" +#include "HttpTreeModel.h" + +#include "ui_HttpDownloadDialog.h" + +namespace OpenInfraPlatform +{ + namespace UserInterface + { + class HttpDownloadDialog : public QDialog, public Ui::HttpDownloadDialog + { + /** + * @class HttpDownloadDialog + * + * @brief A QDialog that allows the user to query OpenStreetMap data. + */ + Q_OBJECT; + + public: + /** + * @brief Default constructor + * + * @param parent QWidget parent + */ + HttpDownloadDialog(QWidget* parent = nullptr); + + /** + * @brief Virtual destructor + */ + virtual ~HttpDownloadDialog(); + + //! Getter for if HttpDownloadDialog was successful + //bool isSuccess() { return successStatus_; } + + signals: + /** + * @brief Emit a signal with a success status, resulting in code execution to end + * + * @param successStatus Success status of the HttpDownloadDialog + */ + void transmitStatus(int successStatus); + + /** + * @brief Emit a signal with a result value + * + * @param result Result value + */ + void resultAvailable(int result); + + private slots: + /** + * @brief Slot for when the quit button is clicked + */ + void on_quitBtn_clicked(); + + /** + * @brief Slot for when the user finishes inputting data + */ + void checkFieldsFilled(); + + /** + * @brief Slot for when the run button is clicked [contains wokrflow logic] + */ + void on_runBtn_clicked(); + + /** + * @brief Slot for when the HttpDownload has finished + */ + void httpDownloadFinished(); + + private: + /** + * @brief Check for invalid characters in the user input fields + * + * @return True if there are invalid characters in the input fields, false otherwise + */ + bool invalidChar(); + + /** + * @brief Check if German characters have been inputted (Nominatim can't handle German chars) + * + * @param addresses List of addresses + */ + void checkGermanChar(QStringList& addresses); + + /** + * @brief Converts user input to always capitalize the first letter (Overpass API is case-sensitive) + * + * @param uneditedString User input string + * + * @return Edited string with first letter capitalized + */ + QString toStartCase(QString uneditedString); + + /** + * @brief Handle Nominatim and Overpass API + * + * @param apiDatabase The API database to use + */ + void processApi(const std::string apiDatabase); + + /** + * @brief Check if API connection 'ok' (200) before querying + * + * @param apiDatabase The API database to use + * + * @return True if the API connection status is valid, false otherwise + */ + bool isApiStatusValid(const std::string apiDatabase); + + /** + * @brief Set up the URL to query the API + * + * @param apiDatabase The API database to use + */ + void configureUrl(const std::string apiDatabase); + + /** + * @brief Create separate queries for Nominatim and Overpass APIs + * + * @param apiDatabase The API database to use + * + * @return The query + */ + QString getQuery(const std::string apiDatabase); + + /** + * @brief Create query to extract OpenStreetMap data in Overpass API + * + * @param radiusInput Radius value for filtering data + * @return QString Query to extract OSM data in Overpass API + */ + QString queryExtractOsmData(const unsigned int radiusInput); + + /** + * @brief Start HTTP request + * + * @param url URL to start HTTP request + */ + void startRequest(const QUrl& url); + + /** + * @brief Setup HttpTreeModel + * + * This is to build the derived QDialog child class with information on different regions of interest of similar name + * + * @param data QJsonArray data for HttpTreeModel + */ + void setupTreeModel(const QJsonArray& data); + + /** + * @brief Join multiple strings together + * + * @param stringData QStringList to concatenate + * @param separator QString separator for concatenating the strings + * @return QString Concatenated QString + */ + QString concatenate(QStringList& stringData, QString seperator="") { return stringData.join(seperator); } + + /** + * @brief Wait for request to go through before processing + * + * @param reply QNetworkReply for waiting + */ + void synchronousRequest(QNetworkReply* reply); + + /** + * @brief Extract bounding box coordinates from Nominatim + * + * @param selectedObject Selected object + */ + void extractBBox(const QJsonObject& selectedObject); + + /** + * @brief Scale the bounding box coordinates according to the radius filter input + * + * @param bbox Bounding box coordinates to scale + * @param radius Radius value for scaling + */ + void scaleBBox(QJsonArray& bbox, const int& radius); + + /** + *@brief Convert bounding box format from geodetic(WGS - 84) to ECEF(Cartesian Geocentric) + * + * @param nominatimBBox Bounding box coordinates in WGS - 84 format + */ + void convertBBoxFormat(const QJsonArray& nominatimBBox); + + // member variable + QNetworkAccessManager* manager_ = nullptr; + QNetworkReply* reply_ = nullptr; + QUrl endpoint_; + QFile* file_ = nullptr; + QTranslator* translator_ = nullptr; + HttpTreeModel* treeModel_ = nullptr; + QString overpassBBox_; + int selectedPosition_ = NULL; // user selected option in HttpTreeModelDialog (default is 0) + int successStatus_ = NULL; // 1 -> only if both Nominatim and Overpass API run + bool stopProcessing_ = NULL; // flag + }; // end class PreferencesDialog + } // end namespace UserInterface +} // end namespace OpenInfraPlatform + +namespace buw +{ + using OpenInfraPlatform::UserInterface::HttpDownloadDialog; +} + +#endif // end define OpenInfraPlatform_UserInterface_HttpDownloadDialog_38da791c_994f_450a_abc1_6676bdf555c4_h diff --git a/UserInterface/Dialogues/HttpTreeModel.cpp b/UserInterface/Dialogues/HttpTreeModel.cpp new file mode 100644 index 000000000..31affdb8d --- /dev/null +++ b/UserInterface/Dialogues/HttpTreeModel.cpp @@ -0,0 +1,91 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . +*/ + +#include "HttpTreeModel.h" + +OpenInfraPlatform::UserInterface::HttpTreeModel::HttpTreeModel(QWidget* parent /*=nullptr*/) + : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint) +{ + setupUi(this); +} + + +OpenInfraPlatform::UserInterface::HttpTreeModel::~HttpTreeModel() +{ + +} + + +void OpenInfraPlatform::UserInterface::HttpTreeModel::on_selectBtn_clicked() +{ + // get the tree item index position + QModelIndex selectedModel = this->httpWidget->currentIndex(); + // handle case if child item selected + if (!selectedModel.parent().isValid()) + position_ = selectedModel.row(); + else + position_ = selectedModel.parent().row(); + + this->accept(); +} + + +void OpenInfraPlatform::UserInterface::HttpTreeModel::buildTreeModel(const QJsonArray& data) +{ + // set columns + this->httpWidget->setColumnCount(2); + + // column headers + this->httpWidget->setHeaderLabels(QStringList() << "Tag" << "Description"); + + // create root and child tree items for each data option + for (QJsonArray::const_iterator it = data.begin(); it != data.end(); ++it) + addTreeRoot(it->toObject()); +} + + +void OpenInfraPlatform::UserInterface::HttpTreeModel::addTreeRoot(const QJsonObject& rootObject) +{ + // if "osm_type" = "node" -> skip + if (rootObject.value("osm_type").toString() != "node") { + + // istantiate tree item + QTreeWidgetItem* rootItem = new QTreeWidgetItem(this->httpWidget); + + // set root name and description + rootItem->setText(0, rootObject.value("category").toString()); + rootItem->setText(1, rootObject.value("type").toString()); + + // add child for root (five top address details and postcode) [Nominatim] + addTreeChild(rootItem, rootObject.value("address").toObject()); + } +} + + +void OpenInfraPlatform::UserInterface::HttpTreeModel::addTreeChild(QTreeWidgetItem* parent, const QJsonObject& childObject) +{ + // set child name and description + for (QJsonObject::const_iterator it = childObject.begin(); it != childObject.end(); ++it) + { + // istantiate tree item + QTreeWidgetItem* childItem = new QTreeWidgetItem(); + // set child properties + childItem->setText(0, it.key()); + childItem->setText(1, it.value().toString()); + parent->addChild(childItem); + } +} \ No newline at end of file diff --git a/UserInterface/Dialogues/HttpTreeModel.h b/UserInterface/Dialogues/HttpTreeModel.h new file mode 100644 index 000000000..26f88b83c --- /dev/null +++ b/UserInterface/Dialogues/HttpTreeModel.h @@ -0,0 +1,125 @@ +/* + Copyright (c) 2021 Technical University of Munich + Chair of Computational Modeling and Simulation. + + TUM Open Infra Platform is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License Version 3 + as published by the Free Software Foundation. + + TUM Open Infra Platform 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 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 . +*/ + +#pragma once +#ifndef OpenInfraPlatform_UserInterface_HttpTreeModel_38da791c_994f_450a_abc1_6676bdf555c4_h +#define OpenInfraPlatform_UserInterface_HttpTreeModel_38da791c_994f_450a_abc1_6676bdf555c4_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ui_HttpTreeModelDialog.h" + +namespace OpenInfraPlatform +{ + namespace UserInterface + { + /** + * @class HttpTreeModel + * + * @brief A QDialog-derived class used for building a tree model from JSON data obtained from a HTTP request + * + * This class is used to build a tree model from JSON data obtained from a HTTP request. It is + * derived from QDialog and Ui::HttpTreeModelDialog to get access to the UI form elements. + * The tree model is built by adding a root item to the tree using the addTreeRoot method, then + * adding child items to the root item using the addTreeChild method. The tree model can be built + * using the buildTreeModel method, which takes in a QJsonArray of data to be displayed in the tree. + * The user can select a specific item in the tree by clicking on it and then clicking the Select button. + * The selected item's position in the tree can be retrieved using the getSelectedPosition method. + * + * @note This class requires the Qt5 framework to be installed in order to build and run. + */ + class HttpTreeModel : public QDialog, public Ui::HttpTreeModelDialog + { + Q_OBJECT; + + public: + /** + * @brief Constructor for HttpTreeModel + * + * @param parent The parent widget for the QDialog + */ + HttpTreeModel(QWidget* parent = nullptr); + + /** + * @brief Destructor for HttpTreeModel + */ + ~HttpTreeModel(); + + /** + * @brief Build the tree model from a QJsonArray + * + * This method builds the tree model from the given QJsonArray of data. It clears the existing + * tree model and creates a new one using the root item and child items specified in the QJsonArray. + * + * @param data The QJsonArray of data to be displayed in the tree + */ + void buildTreeModel(const QJsonArray& data); + + /** + * @brief Add a root item to the tree model + * + * This method adds a root item to the tree model using the given QJsonObject as the root item's data. + * + * @param rootObject The QJsonObject to be used as the root item's data + */ + void addTreeRoot(const QJsonObject& rootObject); + + /** + * @brief Add a child item to the specified parent item in the tree model + * + * This method adds a child item to the specified parent item in the tree model using the given + * QJsonObject as the child item's data. + * + * @param parent The parent item to which the child item should be added + * @param childObject The QJsonObject to be used as the child item's data + */ + void addTreeChild(QTreeWidgetItem* parent, const QJsonObject& childObject); + + /** + * @brief Get the position of the selected item in the tree + * + * This method returns the position of the selected item in the tree. The position is the index + * of the item in the tree model, starting from 0 for the first item. + * + * @return int The position of the selected item in the tree + */ + int getSelectedPosition() { return position_; } + + private slots: + /** + * @brief Slot for handling the Select button click event + * + * This slot is called when the user clicks the Select button. It retrieves the currently selected + * item in the tree and stores its position in the position_ member variable. + */ + void on_selectBtn_clicked(); + + private: + unsigned int position_; /** The position of the selected item in the tree */ + }; + } +} + +#endif // end define OpenInfraPlatform_UserInterface_HttpTreeModel_38da791c_994f_450a_abc1_6676bdf555c4_h \ No newline at end of file diff --git a/UserInterface/Forms/HttpDownloadDialog.ui b/UserInterface/Forms/HttpDownloadDialog.ui new file mode 100644 index 000000000..91454a57e --- /dev/null +++ b/UserInterface/Forms/HttpDownloadDialog.ui @@ -0,0 +1,420 @@ + + + HttpDownloadDialog + + + + 0 + 0 + 693 + 669 + + + + Http Download + + + + + 54 + 51 + 459 + 376 + + + + + + + + + <a href=https://en.wikipedia.org/wiki/ISO_3166-2>Country Code Reference </a> + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + <a href=https://www.openstreetmap.org>Region Lookup </a> + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + Qt::Vertical + + + + 20 + 13 + + + + + + + + + + + + + + + 0 + 0 + + + + Country Code: + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 28 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + County/City: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 28 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Town/District(opt:): + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 28 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Area of Interest: + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 28 + 20 + + + + + + + + + + + + + + + 0 + 0 + + + + + + + eg., DE + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + + + eg., München + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + + + eg., Maxvorstadt + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + + + eg., Technische Universität München + + + + + + + + + + + Qt::Vertical + + + + 17 + 21 + + + + + + + + + + + + Radius: + + + + + + + 5000 + + + 100 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Run + + + + + + + Quit + + + + + + + + + + + + + diff --git a/UserInterface/Forms/HttpTreeModelDialog.ui b/UserInterface/Forms/HttpTreeModelDialog.ui new file mode 100644 index 000000000..30370d791 --- /dev/null +++ b/UserInterface/Forms/HttpTreeModelDialog.ui @@ -0,0 +1,81 @@ + + + HttpTreeModelDialog + + + + 0 + 0 + 584 + 454 + + + + Dialog + + + + + 80 + 60 + 441 + 321 + + + + + + + + + + + + + + + + 25 + + + QAbstractItemView::SingleSelection + + + + 1 + + + + + + + + + + + + Qt::Horizontal + + + + 148 + 20 + + + + + + + + Select + + + + + + + + + + + diff --git a/UserInterface/Forms/MainWindow.ui b/UserInterface/Forms/MainWindow.ui index 07426e141..432040e1e 100644 --- a/UserInterface/Forms/MainWindow.ui +++ b/UserInterface/Forms/MainWindow.ui @@ -61,18 +61,18 @@ - - - QLayout::SetMaximumSize - - - - - Loaded models: - - - - + + + QLayout::SetMaximumSize + + + + + Loaded models: + + + + @@ -475,7 +475,7 @@ 9 9 - 264 + 281 368 @@ -730,8 +730,8 @@ 9 9 - 263 - 331 + 302 + 358 @@ -1133,7 +1133,7 @@ 10 10 - 261 + 292 371 @@ -1428,8 +1428,8 @@ 0 0 - 282 - 1120 + 318 + 1324 @@ -3028,7 +3028,7 @@ 0 0 980 - 21 + 26 @@ -3093,7 +3093,7 @@ Import - + @@ -3239,6 +3239,8 @@ + + @@ -3837,9 +3839,13 @@ Generate for given arc-centers - + + + + :/blueform/import.svg:/blueform/import.svg + - Import OSM file + Import OSM @@ -3895,9 +3901,9 @@ - - Convert Stroke to Alignment - + + Convert Stroke to Alignment + @@ -3967,6 +3973,15 @@ Point Cloud (*.bin) + + + + :/blueform/import.svg:/blueform/import.svg + + + Import OSM Data + + diff --git a/UserInterface/MainWindow.cpp b/UserInterface/MainWindow.cpp index 5d8327948..665b10230 100644 --- a/UserInterface/MainWindow.cpp +++ b/UserInterface/MainWindow.cpp @@ -41,6 +41,7 @@ #include //#include +//#include #include #include @@ -222,6 +223,9 @@ OpenInfraPlatform::UserInterface::MainWindow::MainWindow(QWidget* parent /*= nul #endif + // connect actionImportOsm shortcut (toolbar) + QObject::connect(ui_->actionImportOsm, &QAction::triggered, this, &MainWindow::on_actionImport_triggered); + // remove the UI elements not needed anymore (20201025) ui_->tabWidgetView->removeTab(ui_->tabWidgetView->indexOf(ui_->tabAlignment)); ui_->tabWidgetView->removeTab(ui_->tabWidgetView->indexOf(ui_->tabTerrain)); @@ -849,6 +853,61 @@ void OpenInfraPlatform::UserInterface::MainWindow::on_actionRedo_triggered() { handle_actionRedo_triggered(); } +void OpenInfraPlatform::UserInterface::MainWindow::on_actionImport_triggered() { + // get the data instance + auto& data = OpenInfraPlatform::Core::DataManagement::DocumentManager::getInstance().getData(); + + // check if models loaded + if (data.hasModels()) { + if (data.getLatesChangeFlag() == ChangeFlag::IfcGeometry) { // IFC model + // get the IFC model extents + oip::BBox ifcbb = data.getExtents(); + buw::Vector3d min = ifcbb.min(); + buw::Vector3d max = ifcbb.max(); + + // check if georef metadata provided + bool hasMetaData = false; + oip::GeorefMetadata ifcMetaData = data.getLastModel()->getGeorefMetadata(); + if (!ifcMetaData.codeEPSG.empty() || !ifcMetaData.WKT.empty()) { + hasMetaData = true; + const char *projCRS = ifcMetaData.codeEPSG.c_str(); + // global-to-local conversion (if scaled or not) + const std::string scaleFactor = ifcMetaData.data.find("Map Unit Factor to meters")->second; + + } + } + else if (data.getLatesChangeFlag() == ChangeFlag::OsmDataGeometry) { // OSM model + // #TODO: inform user that model would be deleted upon proceesing (clicking yes) + // --> if user accepts; delete model and start a new instance + } + } + else { // no model loaded + if (httpDownloadDialog_ == nullptr) { + httpDownloadDialog_ = new HttpDownloadDialog(this); + } + // connect to doneImporting when dilaog finished + //QObject::connect(httpDownloadDialog_, &QDialog::finished, this, &MainWindow::doneImporting); + QObject::connect(httpDownloadDialog_, &HttpDownloadDialog::transmitStatus, this, &MainWindow::doneImporting); + + // wait till dialog is closed + httpDownloadDialog_->exec(); + } +} + + +void OpenInfraPlatform::UserInterface::MainWindow::doneImporting(int result) +{ + // delete dialog instance after closed + httpDownloadDialog_->deleteLater(); + httpDownloadDialog_ = nullptr; + + // success (i.e. osm data properly loaded) + if (result) + OpenInfraPlatform::Core::DataManagement::DocumentManager::getInstance().getData().import( + "C:/Users/Moiez/Desktop/MasterThesis/OIP/Open-Infra-Platform/UserInterface/osm.xml"); + +} + void OpenInfraPlatform::UserInterface::MainWindow::jobStarting() { progressDialog_->setDisabled(false); diff --git a/UserInterface/MainWindow.h b/UserInterface/MainWindow.h index 744b4a6d5..dab14308c 100644 --- a/UserInterface/MainWindow.h +++ b/UserInterface/MainWindow.h @@ -24,6 +24,7 @@ #include "../UserInterface/Dialogues/LicenseAndCopyrightInformationDialog.h" #include "DataManagement/General/Data.h" #include "../UserInterface/Dialogues/PreferencesDialog.h" +#include "../UserInterface/Dialogues/HttpDownloadDialog.h" #include "../Core/src/DataManagement/General/ProgressCallback.h" //#include "../UserInterface/XYZImportDialog.h" @@ -144,9 +145,9 @@ namespace OpenInfraPlatform void on_actionShow_Log_Folder_triggered(); - + //! Triggers the OSM import workflow + void on_actionImport_triggered(); //void on_actionCreate_Accident_Report_triggered(); - //void on_actionImport_OSM_File_triggered(); //void on_actionLandXML_triggered(); //void on_actionLoad_Bridge(); @@ -303,6 +304,8 @@ namespace OpenInfraPlatform void reloadShader(); void cancelJob(); void aboutQt(); + //! Slot which handles teh result of the on_actionImport_triggered + void doneImporting(int result); private: void jobStarting(); @@ -366,6 +369,7 @@ namespace OpenInfraPlatform PreferencesDialog* preferencesDialog_ = nullptr; QProgressDialog* progressDialog_ = nullptr; LicenseAndCopyrightInformationDialog* licenseAndCopyrightInformationDialog_ = nullptr; + HttpDownloadDialog* httpDownloadDialog_ = nullptr; QProgressBar* progressBar_; @@ -376,6 +380,8 @@ namespace OpenInfraPlatform View* view_; Ui::MainWindow* ui_; + + bool loaded_; PrecisionTest precisionTest_; diff --git a/UserInterface/Resources/OpenInfraPlatform.qrc b/UserInterface/Resources/OpenInfraPlatform.qrc index 24c5f0263..03ccaa74b 100644 --- a/UserInterface/Resources/OpenInfraPlatform.qrc +++ b/UserInterface/Resources/OpenInfraPlatform.qrc @@ -1,49 +1,50 @@ - - undo.svg - clothoid.svg - open.png - redo.svg - - - splash.png - - - qml/Map.qml - ACA.svg - ACA2.svg - ACCA.svg - ACCA2.svg - LAAL.svg - LAAAL.svg - LClothoid.svg - LCCL.svg - LCACL.svg - widgets/up_arrow_disabled.png - widgets/down_arrow.png - widgets/up_arrow.png - icons/icon.ico - widgets/radiobutton_checked_disabled.png - widgets/radiobutton_checked_enabled.png - widgets/radiobutton_unchecked_disabled.png - widgets/radiobutton_unchecked_enabled.png - widgets/checkbox_checked_disabled.png - widgets/checkbox_checked_enabled.png - widgets/checkbox_indeterminate_disabled.png - widgets/checkbox_indeterminate_enabled.png - widgets/checkbox_unchecked_disabled.png - widgets/checkbox_unchecked_enabled.png - widgets/combobox_arrow_down.png - widgets/combobox_arrow_down_selected.png - widgets/menu_arrow_right.png - widgets/menu_arrow_right_selected.png - widgets/scrollbar_arrow_down_disabled.png - widgets/scrollbar_arrow_down_enabled.png - widgets/scrollbar_arrow_left_disabled.png - widgets/scrollbar_arrow_left_enabled.png - widgets/scrollbar_arrow_right_disabled.png - widgets/scrollbar_arrow_right_enabled.png - widgets/scrollbar_arrow_up_disabled.png - widgets/scrollbar_arrow_up_enabled.png - + + undo.svg + clothoid.svg + open.png + redo.svg + import.svg + + + splash.png + + + qml/Map.qml + ACA.svg + ACA2.svg + ACCA.svg + ACCA2.svg + LAAL.svg + LAAAL.svg + LClothoid.svg + LCCL.svg + LCACL.svg + widgets/up_arrow_disabled.png + widgets/down_arrow.png + widgets/up_arrow.png + icons/icon.ico + widgets/radiobutton_checked_disabled.png + widgets/radiobutton_checked_enabled.png + widgets/radiobutton_unchecked_disabled.png + widgets/radiobutton_unchecked_enabled.png + widgets/checkbox_checked_disabled.png + widgets/checkbox_checked_enabled.png + widgets/checkbox_indeterminate_disabled.png + widgets/checkbox_indeterminate_enabled.png + widgets/checkbox_unchecked_disabled.png + widgets/checkbox_unchecked_enabled.png + widgets/combobox_arrow_down.png + widgets/combobox_arrow_down_selected.png + widgets/menu_arrow_right.png + widgets/menu_arrow_right_selected.png + widgets/scrollbar_arrow_down_disabled.png + widgets/scrollbar_arrow_down_enabled.png + widgets/scrollbar_arrow_left_disabled.png + widgets/scrollbar_arrow_left_enabled.png + widgets/scrollbar_arrow_right_disabled.png + widgets/scrollbar_arrow_right_enabled.png + widgets/scrollbar_arrow_up_disabled.png + widgets/scrollbar_arrow_up_enabled.png + diff --git a/UserInterface/Resources/import.svg b/UserInterface/Resources/import.svg new file mode 100644 index 000000000..3c11efaa6 --- /dev/null +++ b/UserInterface/Resources/import.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UserInterface/ViewPanel/Viewport.cpp b/UserInterface/ViewPanel/Viewport.cpp index 9889b3655..80426c51a 100644 --- a/UserInterface/ViewPanel/Viewport.cpp +++ b/UserInterface/ViewPanel/Viewport.cpp @@ -187,6 +187,10 @@ Viewport::Viewport(const buw::eRenderAPI renderAPI, bool warp, bool msaa, QWidge offGeometryEffect_ = buw::makeReferenceCounted(renderSystem_.get(), viewport_, depthStencilMSAA_, worldBuffer_); offGeometryEffect_->init(); + BLUE_LOG(trace) << "Creating OsmGeometry effects"; + osmGeometryEffect_ = buw::makeReferenceCounted(renderSystem_.get(), viewport_, depthStencilMSAA_, worldBuffer_); + osmGeometryEffect_->init(); + timer_ = new QTimer(); timer_->setInterval(16); timer_->setSingleShot(false); @@ -228,6 +232,7 @@ Viewport::~Viewport() { skyboxEffect_ = nullptr; ifcGeometryEffect_ = nullptr; offGeometryEffect_ = nullptr; + osmGeometryEffect_ = nullptr; viewCube_ = nullptr; @@ -885,6 +890,23 @@ void Viewport::onChange( const ChangeFlag changeFlag ) } } + // change in OSM geometry? + if (changeFlag & ChangeFlag::OsmDataGeometry) { + // multiple models might've been loaded + for (auto& model : data.getModels()) + { + auto osmGeometryModel = std::dynamic_pointer_cast(model); + if (osmGeometryModel) + { + buw::ReferenceCounted osmGeometryEffect + = buw::makeReferenceCounted(renderSystem_.get(), viewport_, depthStencilMSAA_, worldBuffer_); + osmGeometryEffect->init(); + osmGeometryEffect->setOsmModel(osmGeometryModel); + activeEffects_.push_back(osmGeometryEffect); + } + } + } + // change in Point cloud #ifdef OIP_WITH_POINT_CLOUD_PROCESSING if(changeFlag & ChangeFlag::PointCloud ) { diff --git a/UserInterface/ViewPanel/Viewport.h b/UserInterface/ViewPanel/Viewport.h index e0e151cf7..3c29f6535 100644 --- a/UserInterface/ViewPanel/Viewport.h +++ b/UserInterface/ViewPanel/Viewport.h @@ -47,6 +47,7 @@ #include #include #include +#include #ifdef OIP_WITH_POINT_CLOUD_PROCESSING #include @@ -200,6 +201,7 @@ namespace OpenInfraPlatform { buw::ReferenceCounted boundingBoxEffect_; buw::ReferenceCounted skyboxEffect_; buw::ReferenceCounted offGeometryEffect_; + buw::ReferenceCounted osmGeometryEffect_; #ifdef OIP_WITH_POINT_CLOUD_PROCESSING buw::ReferenceCounted pointCloudEffect_ = nullptr;