diff --git a/CMakeLists.txt b/CMakeLists.txt index 357224ad21d..1288712817a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,12 @@ IF(WITH_CORE) SET (HAVE_GUI TRUE) # used in qgsconfig.h ENDIF() + # try to configure and build MDAL support + SET (WITH_INTERNAL_MDAL TRUE CACHE BOOL "Determines whether MDAL support should be built") + IF (NOT WITH_INTERNAL_MDAL) + SET (MDAL_PREFIX "" CACHE PATH "Path to MDAL base directory") + ENDIF (NOT WITH_INTERNAL_MDAL) + # try to configure and build POSTGRESQL support SET (WITH_POSTGRESQL TRUE CACHE BOOL "Determines whether POSTGRESQL support should be built") IF (WITH_POSTGRESQL) @@ -258,6 +264,10 @@ IF(WITH_CORE) FIND_PACKAGE(Postgres) # PostgreSQL provider ENDIF (WITH_POSTGRESQL) + IF (NOT WITH_INTERNAL_MDAL) + FIND_PACKAGE(MDAL REQUIRED) # MDAL provider + ENDIF (NOT WITH_INTERNAL_MDAL) + FIND_PACKAGE(SpatiaLite REQUIRED) IF (NOT PROJ_FOUND OR NOT GEOS_FOUND OR NOT GDAL_FOUND) diff --git a/cmake/FindMDAL.cmake b/cmake/FindMDAL.cmake new file mode 100644 index 00000000000..72d8ecbfbe0 --- /dev/null +++ b/cmake/FindMDAL.cmake @@ -0,0 +1,32 @@ +# Find MDAL +# ~~~~~~~~~ +# Copyright (c) 2018, Peter Petrik +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# +# Once run this will define: +# MDAL_FOUND - System has MDAL +# MDAL_INCLUDE_DIRS - The MDAL include directories +# MDAL_LIBRARIES - The libraries needed to use MDAL +# MDAL_DEFINITIONS - Compiler switches required for using MDAL + +FIND_PACKAGE(PkgConfig) +PKG_CHECK_MODULES(PC_MDAL QUIET libmdal) +SET(MDAL_DEFINITIONS ${PC_MDAL_CFLAGS_OTHER}) + +FIND_PATH(MDAL_INCLUDE_DIR mdal.h + HINTS ${PC_MDAL_INCLUDEDIR} ${PC_MDAL_INCLUDE_DIRS} ${MDAL_PREFIX}/include + PATH_SUFFIXES libmdal ) + +FIND_LIBRARY(MDAL_LIBRARY NAMES mdal libmdal + HINTS ${PC_MDAL_LIBDIR} ${PC_MDAL_LIBRARY_DIRS} ${MDAL_PREFIX}/lib) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(MDAL DEFAULT_MSG + MDAL_LIBRARY MDAL_INCLUDE_DIR) + +MARK_AS_ADVANCED(MDAL_INCLUDE_DIR MDAL_LIBRARY ) + +SET(MDAL_LIBRARIES ${MDAL_LIBRARY} ) +SET(MDAL_INCLUDE_DIRS ${MDAL_INCLUDE_DIR} ) diff --git a/external/mdal/api/mdal.h b/external/mdal/api/mdal.h new file mode 100644 index 00000000000..0569ac4d1e4 --- /dev/null +++ b/external/mdal/api/mdal.h @@ -0,0 +1,91 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#ifndef MDAL_H +#define MDAL_H + +#ifdef MDAL_STATIC +# define MDAL_EXPORT +#else +# if defined _WIN32 || defined __CYGWIN__ +# ifdef mdal_EXPORTS +# ifdef __GNUC__ +# define MDAL_EXPORT __attribute__ ((dllexport)) +# else +# define MDAL_EXPORT __declspec(dllexport) // Note: actually gcc seems to also supports this syntax. +# endif +# else +# ifdef __GNUC__ +# define MDAL_EXPORT __attribute__ ((dllimport)) +# else +# define MDAL_EXPORT __declspec(dllimport) // Note: actually gcc seems to also supports this syntax. +# endif +# endif +# else +# if __GNUC__ >= 4 +# define MDAL_EXPORT __attribute__ ((visibility ("default"))) +# else +# define MDAL_EXPORT +# endif +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Statuses */ +enum Status +{ + None, + // Errors + Err_NotEnoughMemory, + Err_FileNotFound, + Err_UnknownFormat, + Err_IncompatibleMesh, + Err_InvalidData, + Err_MissingDriver, + // Warnings + Warn_UnsupportedElement, + Warn_InvalidElements, + Warn_ElementWithInvalidNode, + Warn_ElementNotUnique, + Warn_NodeNotUnique +}; + +/* Mesh */ +typedef void *MeshH; + +//! Return MDAL version +MDAL_EXPORT const char *MDAL_Version(); + +//! Return last status message +MDAL_EXPORT Status MDAL_LastStatus(); + +//! Load mesh file. On error see MDAL_LastStatus for error type This effectively loads whole mesh in-memory +MDAL_EXPORT MeshH MDAL_LoadMesh( const char *meshFile ); +//! Close mesh, free the memory +MDAL_EXPORT void MDAL_CloseMesh( MeshH mesh ); + +//! Return vertex count for the mesh +MDAL_EXPORT size_t MDAL_M_vertexCount( MeshH mesh ); +//! Return vertex X coord for the mesh +MDAL_EXPORT double MDAL_M_vertexXCoordinatesAt( MeshH mesh, size_t index ); +//! Return vertex Y coord for the mesh +MDAL_EXPORT double MDAL_M_vertexYCoordinatesAt( MeshH mesh, size_t index ); +//! Return face count for the mesh +MDAL_EXPORT size_t MDAL_M_faceCount( MeshH mesh ); +//! Return number of vertices face consist of, e.g. 3 for triangle +MDAL_EXPORT size_t MDAL_M_faceVerticesCountAt( MeshH mesh, size_t index ); +//! Return vertex index for face +MDAL_EXPORT size_t MDAL_M_faceVerticesIndexAt( MeshH mesh, size_t face_index, size_t vertex_index ); + +#ifdef __cplusplus +} +#endif + +#endif //MDAL_H diff --git a/external/mdal/frmts/mdal_2dm.cpp b/external/mdal/frmts/mdal_2dm.cpp new file mode 100644 index 00000000000..2428ba87804 --- /dev/null +++ b/external/mdal/frmts/mdal_2dm.cpp @@ -0,0 +1,204 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdal_2dm.hpp" +#include "mdal.h" +#include "mdal_utils.hpp" + +MDAL::Loader2dm::Loader2dm( const std::string &meshFile ): + mMeshFile( meshFile ) +{ +} + +MDAL::Mesh *MDAL::Loader2dm::load( Status *status ) +{ + if ( status ) *status = Status::None; + + if ( !MDAL::fileExists( mMeshFile ) ) + { + if ( status ) *status = Status::Err_FileNotFound; + return 0; + } + + std::ifstream in( mMeshFile, std::ifstream::in ); + std::string line; + if ( !std::getline( in, line ) || !startsWith( line, "MESH2D" ) ) + { + if ( status ) *status = Status::Err_UnknownFormat; + return 0; + } + + size_t elemCount = 0; + size_t nodeCount = 0; + + // Find out how many nodes and elements are contained in the .2dm mesh file + while ( std::getline( in, line ) ) + { + if ( startsWith( line, "E4Q" ) || + startsWith( line, "E3T" ) ) + { + elemCount++; + } + else if ( startsWith( line, "ND" ) ) + { + nodeCount++; + } + else if ( startsWith( line, "E2L" ) || + startsWith( line, "E3L" ) || + startsWith( line, "E6T" ) || + startsWith( line, "E8Q" ) || + startsWith( line, "E9Q" ) ) + { + if ( status ) *status = Status::Warn_UnsupportedElement; + elemCount += 1; // We still count them as elements + } + } + + // Allocate memory + std::vector vertices( nodeCount ); + std::vector faces( elemCount ); + + in.clear(); + in.seekg( 0, std::ios::beg ); + + std::vector chunks; + + size_t elemIndex = 0; + size_t nodeIndex = 0; + std::map elemIDtoIndex; + std::map nodeIDtoIndex; + + while ( std::getline( in, line ) ) + { + if ( startsWith( line, "E4Q" ) ) + { + chunks = split( line, " ", SplitBehaviour::SkipEmptyParts ); + assert( elemIndex < elemCount ); + + size_t elemID = toSizeT( chunks[1] ); + + std::map::iterator search = elemIDtoIndex.find( elemID ); + if ( search != elemIDtoIndex.end() ) + { + if ( status ) *status = Status::Warn_ElementNotUnique; + continue; + } + elemIDtoIndex[elemID] = elemIndex; + Face &face = faces[elemIndex]; + face.resize( 4 ); + // Right now we just store node IDs here - we will convert them to node indices afterwards + for ( size_t i = 0; i < 4; ++i ) + face[i] = toSizeT( chunks[i + 2] ); + + elemIndex++; + } + else if ( startsWith( line, "E3T" ) ) + { + chunks = split( line, " ", SplitBehaviour::SkipEmptyParts ); + assert( elemIndex < elemCount ); + + size_t elemID = toSizeT( chunks[1] ); + + std::map::iterator search = elemIDtoIndex.find( elemID ); + if ( search != elemIDtoIndex.end() ) + { + if ( status ) *status = Status::Warn_ElementNotUnique; + continue; + } + elemIDtoIndex[elemID] = elemIndex; + Face &face = faces[elemIndex]; + face.resize( 3 ); + // Right now we just store node IDs here - we will convert them to node indices afterwards + for ( size_t i = 0; i < 3; ++i ) + { + face[i] = toSizeT( chunks[i + 2] ); + } + + elemIndex++; + } + else if ( startsWith( line, "E2L" ) || + startsWith( line, "E3L" ) || + startsWith( line, "E6T" ) || + startsWith( line, "E8Q" ) || + startsWith( line, "E9Q" ) ) + { + // We do not yet support these elements + chunks = split( line, " ", SplitBehaviour::SkipEmptyParts ); + assert( elemIndex < elemCount ); + + size_t elemID = toSizeT( chunks[1] ); + + std::map::iterator search = elemIDtoIndex.find( elemID ); + if ( search != elemIDtoIndex.end() ) + { + if ( status ) *status = Status::Warn_ElementNotUnique; + continue; + } + elemIDtoIndex[elemID] = elemIndex; + assert( false ); //TODO mark element as unusable + + elemIndex++; + } + else if ( startsWith( line, "ND" ) ) + { + chunks = split( line, " ", SplitBehaviour::SkipEmptyParts ); + size_t nodeID = toSizeT( chunks[1] ); + + std::map::iterator search = nodeIDtoIndex.find( nodeID ); + if ( search != nodeIDtoIndex.end() ) + { + if ( status ) *status = Status::Warn_NodeNotUnique; + continue; + } + nodeIDtoIndex[nodeID] = nodeIndex; + assert( nodeIndex < nodeCount ); + Vertex &vertex = vertices[nodeIndex]; + vertex.x = toDouble( chunks[2] ); + vertex.y = toDouble( chunks[3] ); + + nodeIndex++; + } + } + + for ( std::vector::iterator it = faces.begin(); it != faces.end(); ++it ) + { + Face &face = *it; + for ( Face::size_type nd = 0; nd < face.size(); ++nd ) + { + size_t nodeID = face[nd]; + + std::map::iterator ni2i = nodeIDtoIndex.find( nodeID ); + if ( ni2i != nodeIDtoIndex.end() ) + { + face[nd] = ni2i->second; // convert from ID to index + } + else + { + assert( false ); //TODO mark element as unusable + + if ( status ) *status = Status::Warn_ElementWithInvalidNode; + } + } + + //TODO check validity of the face + //check that we have distinct nodes + } + + Mesh *mesh = new Mesh; + mesh->faces = faces; + mesh->vertices = vertices; + + return mesh; +} diff --git a/external/mdal/frmts/mdal_2dm.hpp b/external/mdal/frmts/mdal_2dm.hpp new file mode 100644 index 00000000000..28cce0ff028 --- /dev/null +++ b/external/mdal/frmts/mdal_2dm.hpp @@ -0,0 +1,28 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#ifndef MDAL_2DM_HPP +#define MDAL_2DM_HPP + +#include + +#include "mdal_defines.hpp" +#include "mdal.h" + +namespace MDAL +{ + + class Loader2dm + { + public: + Loader2dm( const std::string &meshFile ); + Mesh *load( Status *status ); + + private: + std::string mMeshFile; + }; + +} // namespace MDAL +#endif //MDAL_2DM_HPP diff --git a/external/mdal/mdal.cpp b/external/mdal/mdal.cpp new file mode 100644 index 00000000000..a6d18fe2e7d --- /dev/null +++ b/external/mdal/mdal.cpp @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "mdal.h" +#include "mdal_loader.hpp" +#include "mdal_defines.hpp" + +static Status sLastStatus; + +const char *MDAL_Version() +{ + return "0.0.1"; +} + +Status MDAL_LastStatus() +{ + return sLastStatus; +} + +MeshH MDAL_LoadMesh( const char *meshFile ) +{ + if ( !meshFile ) + return nullptr; + + std::string filename( meshFile ); + return ( MeshH ) MDAL::Loader::load( filename, &sLastStatus ); +} + + +void MDAL_CloseMesh( MeshH mesh ) +{ + if ( mesh ) + { + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + delete m; + } +} + + +size_t MDAL_M_vertexCount( MeshH mesh ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + return m->vertices.size(); +} + +double MDAL_M_vertexXCoordinatesAt( MeshH mesh, size_t index ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + assert( m->vertices.size() > index ); + return m->vertices[index].x; +} + +double MDAL_M_vertexYCoordinatesAt( MeshH mesh, size_t index ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + assert( m->vertices.size() > index ); + return m->vertices[index].y; +} + +size_t MDAL_M_faceCount( MeshH mesh ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + return m->faces.size(); +} + +size_t MDAL_M_faceVerticesCountAt( MeshH mesh, size_t index ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + assert( m->faces.size() > index ); + return m->faces[index].size(); +} + +size_t MDAL_M_faceVerticesIndexAt( MeshH mesh, size_t face_index, size_t vertex_index ) +{ + assert( mesh ); + MDAL::Mesh *m = ( MDAL::Mesh * ) mesh; + assert( m->faces.size() > face_index ); + assert( m->faces[face_index].size() > vertex_index ); + return m->faces[face_index][vertex_index]; +} diff --git a/external/mdal/mdal_defines.hpp b/external/mdal/mdal_defines.hpp new file mode 100644 index 00000000000..f1259b6c653 --- /dev/null +++ b/external/mdal/mdal_defines.hpp @@ -0,0 +1,31 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#ifndef MDAL_DEFINES_HPP +#define MDAL_DEFINES_HPP + +#include +#include + +namespace MDAL +{ + + typedef struct + { + double x; + double y; + } Vertex; + + typedef std::vector Face; + + struct Mesh + { + std::vector vertices; + std::vector faces; + }; + +} // namespace MDAL +#endif //MDAL_DEFINES_HPP + diff --git a/external/mdal/mdal_loader.cpp b/external/mdal/mdal_loader.cpp new file mode 100644 index 00000000000..7671d40e67e --- /dev/null +++ b/external/mdal/mdal_loader.cpp @@ -0,0 +1,13 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#include "mdal_loader.hpp" +#include "frmts/mdal_2dm.hpp" + +MDAL::Mesh *MDAL::Loader::load( const std::string &meshFile, Status *status ) +{ + MDAL::Loader2dm loader( meshFile ); + return loader.load( status ); +} diff --git a/external/mdal/mdal_loader.hpp b/external/mdal/mdal_loader.hpp new file mode 100644 index 00000000000..edd917b11b1 --- /dev/null +++ b/external/mdal/mdal_loader.hpp @@ -0,0 +1,24 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#ifndef MDAL_LOADER_HPP +#define MDAL_LOADER_HPP + +#include + +#include "mdal.h" +#include "mdal_defines.hpp" + +namespace MDAL +{ + + class Loader + { + public: + static Mesh *load( const std::string &meshFile, Status *status ); + }; + +} // namespace MDAL +#endif //MDAL_LOADER_HPP diff --git a/external/mdal/mdal_utils.cpp b/external/mdal/mdal_utils.cpp new file mode 100644 index 00000000000..57a94e1ec5a --- /dev/null +++ b/external/mdal/mdal_utils.cpp @@ -0,0 +1,56 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#include "mdal_utils.hpp" +#include + +bool MDAL::fileExists( const std::string &filename ) +{ + std::ifstream in( filename ); + return in.good(); +} + + +bool MDAL::startsWith( const std::string &str, const std::string &substr ) +{ + return str.rfind( substr, 0 ) == 0; +} + +std::vector MDAL::split( const std::string &str, const std::string &delimiter, SplitBehaviour behaviour ) +{ + std::string remaining( str ); + std::vector list; + size_t pos = 0; + std::string token; + while ( ( pos = remaining.find( delimiter ) ) != std::string::npos ) + { + token = remaining.substr( 0, pos ); + + if ( behaviour == SplitBehaviour::SkipEmptyParts ) + { + if ( !token.empty() ) + list.push_back( token ); + } + else + list.push_back( token ); + + remaining.erase( 0, pos + delimiter.length() ); + } + list.push_back( remaining ); + return list; +} + +size_t MDAL::toSizeT( const std::string &str ) +{ + int i = atoi( str.c_str() ); + if ( i < 0 ) // consistent with atoi return + i = 0; + return i; +} + +double MDAL::toDouble( const std::string &str ) +{ + return atof( str.c_str() ); +} diff --git a/external/mdal/mdal_utils.hpp b/external/mdal/mdal_utils.hpp new file mode 100644 index 00000000000..b50d6caaff6 --- /dev/null +++ b/external/mdal/mdal_utils.hpp @@ -0,0 +1,34 @@ +/* + MDAL - Mesh Data Abstraction Library (MIT License) + Copyright (C) 2018 Peter Petrik (zilolv at gmail dot com) +*/ + +#ifndef MDAL_UTILS_HPP +#define MDAL_UTILS_HPP + +#include +#include +#include + +namespace MDAL +{ + + /** Return whether file exists */ + bool fileExists( const std::string &filename ); + + // strings + bool startsWith( const std::string &str, const std::string &substr ); + + /** Return 0 if not possible to convert */ + size_t toSizeT( const std::string &str ); + double toDouble( const std::string &str ); + + enum SplitBehaviour + { + SkipEmptyParts, + KeepEmptyParts + }; + std::vector split( const std::string &str, const std::string &delimiter, SplitBehaviour behaviour ); + +} // namespace MDAL +#endif //MDAL_UTILS_HPP diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index e8ffb9f293e..24a1fe6ab08 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -119,6 +119,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/layout ${CMAKE_SOURCE_DIR}/src/core/locator ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/mesh ${CMAKE_SOURCE_DIR}/src/core/processing ${CMAKE_SOURCE_DIR}/src/core/processing/models ${CMAKE_SOURCE_DIR}/src/core/providers diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 9732f9a36f4..fa21ee1df5c 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -433,3 +433,6 @@ %Include qgsuserprofilemanager.sip %Include symbology/qgsarrowsymbollayer.sip %Include qgsuserprofile.sip +%Include mesh/qgsmeshdataprovider.sip +%Include mesh/qgsmeshlayer.sip + diff --git a/python/core/mesh/qgsmeshdataprovider.sip.in b/python/core/mesh/qgsmeshdataprovider.sip.in new file mode 100644 index 00000000000..3bb56cb64c0 --- /dev/null +++ b/python/core/mesh/qgsmeshdataprovider.sip.in @@ -0,0 +1,98 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsmeshdataprovider.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + +typedef QgsPoint QgsMeshVertex; //xyz coords of vertex +typedef QVector QgsMeshFace; //list of vertex indexes + +class QgsMeshSource /Abstract/ +{ +%Docstring +Mesh is a collection of vertices and faces in 2D or 3D space +- vertex - XY(Z) point (in the mesh's coordinate reference system) +- faces - sets of vertices forming a closed shape - typically triangles or quadrilaterals + +Base on the underlying data provider/format, whole mesh is either stored in memory or +read on demand + +.. versionadded:: 3.2 +%End + +%TypeHeaderCode +#include "qgsmeshdataprovider.h" +%End + public: + virtual ~QgsMeshSource(); + + virtual size_t vertexCount() const = 0; +%Docstring + Return number of vertexes in the native mesh + +:return: Number of vertexes in the mesh +%End + + virtual size_t faceCount() const = 0; +%Docstring + Return number of faces in the native mesh + +:return: Number of faces in the mesh +%End + + virtual QgsMeshVertex vertex( size_t index ) const = 0; +%Docstring + Factory for mesh vertex with index + +:return: new mesh vertex on index +%End + + virtual QgsMeshFace face( size_t index ) const = 0; +%Docstring + Factory for mesh face with index + +:return: new mesh face on index +%End +}; + +class QgsMeshDataProvider: QgsDataProvider, QgsMeshSource +{ +%Docstring +Base class for providing data for :py:class:`QgsMeshLayer` + +Responsible for reading native mesh data + +.. seealso:: :py:class:`QgsMeshSource` +%End + +%TypeHeaderCode +#include "qgsmeshdataprovider.h" +%End + public: + QgsMeshDataProvider( const QString &uri = QString() ); +%Docstring +Ctor +%End + + virtual QgsRectangle extent() const; +%Docstring +Returns the extent of the layer + +:return: QgsRectangle containing the extent of the layer +%End +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsmeshdataprovider.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/mesh/qgsmeshlayer.sip.in b/python/core/mesh/qgsmeshlayer.sip.in new file mode 100644 index 00000000000..7bee996bcf6 --- /dev/null +++ b/python/core/mesh/qgsmeshlayer.sip.in @@ -0,0 +1,174 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsmeshlayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + + + +class QgsMeshLayer : QgsMapLayer +{ +%Docstring + +Represents a mesh layer supporting display of data on structured or unstructured meshes + +The QgsMeshLayer is instantiated by specifying the name of a data provider, +such as mdal, and url defining the specific data set to connect to. +The vector layer constructor in turn instantiates a QgsMeshDataProvider subclass +corresponding to the provider type, and passes it the url. The data provider +connects to the data source. + +The QgsMeshLayer provides a common interface to the different data types. It does not +yet support editing transactions. + +The main data providers supported by QGIS are listed below. + +\section providers Mesh data providers + +\subsection memory Memory data providerType (mesh_memory) + +The memory data provider is used to construct in memory data, for example scratch +data. There is no inherent persistent storage of the data. The data source uri is constructed. +Data can be populated by setMesh(const QString &vertices, const QString &faces), where +vertices and faces is comma separated coordinates and connections for mesh. +E.g. to create mesh with one quad and one triangle + +.. code-block:: + + QString uri( + "1.0, 2.0 \n" \ + "2.0, 2.0 \n" \ + "3.0, 2.0 \n" \ + "2.0, 3.0 \n" \ + "1.0, 3.0 \n" \ + "---" + "0, 1, 3, 4 \n" \ + "1, 2, 3 \n" + ); + QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch layer", "memory_mesh"); + +\subsection mdal MDAL data provider (mdal) + +Accesses data using the MDAL drivers (https://github.com/lutraconsulting/MDAL). The url +is the MDAL connection string. QGIS must be built with MDAL support to allow this provider. + +.. code-block:: + + QString uri = "test/land.2dm"; + QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch Layer", "mdal"); + +.. versionadded:: 3.2 +%End + +%TypeHeaderCode +#include "qgsmeshlayer.h" +%End + public: + + explicit QgsMeshLayer( const QString &path = QString(), const QString &baseName = QString(), const QString &providerLib = "mesh_memory" ); +%Docstring +Constructor - creates a mesh layer + +The QgsMeshLayer is constructed by instantiating a data provider. The provider +interprets the supplied path (url) of the data source to connect to and access the +data. + +:param path: The path or url of the parameter. Typically this encodes +parameters used by the data provider as url query items. +:param baseName: The name used to represent the layer in the legend +:param providerLib: The name of the data provider, e.g., "mesh_memory", "mdal" +%End + ~QgsMeshLayer(); + + + virtual QgsMeshDataProvider *dataProvider(); + +%Docstring +Return data provider +%End + + + virtual QgsMeshLayer *clone() const /Factory/; + +%Docstring +Returns a new instance equivalent to this one. A new provider is +created for the same data source and renderers are cloned too. + +:return: a new layer instance +%End + + virtual QgsRectangle extent() const; + +%Docstring +Returns the extent of the layer. +%End + + virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) /Factory/; +%Docstring +Return new instance of QgsMapLayerRenderer that will be used for rendering of given context +%End + + QString providerType() const; +%Docstring +Return the provider type for this layer +%End + + + + QgsSymbol *nativeMeshSymbol(); +%Docstring +Returns a line symbol used for rendering native mesh. +%End + + QgsSymbol *triangularMeshSymbol(); +%Docstring +Returns a line symbol used for rendering of triangular (derived) mesh. + +.. seealso:: :py:func:`toggleTriangularMeshRendering` +%End + + void toggleTriangularMeshRendering( bool toggle ); +%Docstring +Toggle rendering of triangular (derived) mesh. Off by default +%End + + bool readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ); +%Docstring +Read the symbology for the current layer from the Dom node supplied. + +:param node: node that will contain the symbology definition for this layer. +:param errorMessage: reference to string that will be updated with any error messages +:param context: reading context (used for transform from relative to absolute paths) + +:return: true in case of success. +%End + + bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const; +%Docstring +Write the symbology for the layer into the docment provided. + +:param node: the node that will have the style element added to it. +:param doc: the document that will have the QDomNode added. +:param errorMessage: reference to string that will be updated with any error messages +:param context: writing context (used for transform from absolute to relative paths) + +:return: true in case of success. +%End + + private: // Private methods + QgsMeshLayer( const QgsMeshLayer &rhs ); +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsmeshlayer.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/mesh/qgsnativemesh.sip.in b/python/core/mesh/qgsnativemesh.sip.in new file mode 100644 index 00000000000..5be24a6f0ce --- /dev/null +++ b/python/core/mesh/qgsnativemesh.sip.in @@ -0,0 +1,28 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsnativemesh.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +typedef QgsPoint QgsMeshVertex; //xyz coords of vertex +typedef std::vector QgsMeshFace; //list of vertex indexes + +struct QgsNativeMesh +{ + std::vector vertices; + std::vector faces; +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/mesh/qgsnativemesh.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/qgsdataitem.sip.in b/python/core/qgsdataitem.sip.in index 0bcc087614a..9edd3c04712 100644 --- a/python/core/qgsdataitem.sip.in +++ b/python/core/qgsdataitem.sip.in @@ -394,7 +394,8 @@ Item that represents a layer that can be opened with one of the providers TableLayer, Database, Table, - Plugin + Plugin, + Mesh }; @@ -470,6 +471,7 @@ Returns the icon name of the given ``layerType`` static QIcon iconTable(); static QIcon iconRaster(); static QIcon iconDefault(); + static QIcon iconMesh(); virtual QString layerName() const; %Docstring diff --git a/python/core/qgsdataprovider.sip.in b/python/core/qgsdataprovider.sip.in index 2c6f7317470..71b3c84b508 100644 --- a/python/core/qgsdataprovider.sip.in +++ b/python/core/qgsdataprovider.sip.in @@ -38,6 +38,10 @@ to generic QgsVectorDataProvider's) depends on it. { sipType = sipType_QgsRasterDataProvider; } + else if ( qobject_cast( sipCpp ) ) + { + sipType = sipType_QgsMeshDataProvider; + } else { sipType = 0; diff --git a/python/core/qgsmaplayer.sip.in b/python/core/qgsmaplayer.sip.in index b87409f1eee..ecd1fdde649 100644 --- a/python/core/qgsmaplayer.sip.in +++ b/python/core/qgsmaplayer.sip.in @@ -41,6 +41,9 @@ This is the base class for all map layer types (vector, raster). case QgsMapLayer::PluginLayer: sipType = sipType_QgsPluginLayer; break; + case QgsMapLayer::MeshLayer: + sipType = sipType_QgsMeshLayer; + break; default: sipType = nullptr; break; @@ -53,7 +56,8 @@ This is the base class for all map layer types (vector, raster). { VectorLayer, RasterLayer, - PluginLayer + PluginLayer, + MeshLayer }; enum PropertyType diff --git a/python/core/qgsmimedatautils.sip.in b/python/core/qgsmimedatautils.sip.in index a8130dbac14..d0b028ac96b 100644 --- a/python/core/qgsmimedatautils.sip.in +++ b/python/core/qgsmimedatautils.sip.in @@ -52,6 +52,14 @@ Get vector layer from uri if possible, otherwise returns 0 and error is set %Docstring Get raster layer from uri if possible, otherwise returns 0 and error is set +:param owner: set to true if caller becomes owner +:param error: set to error message if cannot get raster +%End + + QgsMeshLayer *meshLayer( bool &owner, QString &error ) const; +%Docstring +Get mesh layer from uri if possible, otherwise returns 0 and error is set + :param owner: set to true if caller becomes owner :param error: set to error message if cannot get raster %End diff --git a/python/core/qgsproviderregistry.sip.in b/python/core/qgsproviderregistry.sip.in index 1bc45faacec..e739ee915c9 100644 --- a/python/core/qgsproviderregistry.sip.in +++ b/python/core/qgsproviderregistry.sip.in @@ -162,6 +162,7 @@ buildSupportedRasterFileFilter to a string, which is then returned. This replaces :py:func:`QgsRasterLayer.buildSupportedRasterFileFilter()` %End + virtual QString databaseDrivers() const; %Docstring Return a string containing the available database drivers diff --git a/src/analysis/processing/qgsalgorithmpackage.cpp b/src/analysis/processing/qgsalgorithmpackage.cpp index 60e795585f0..a6771a0ca60 100644 --- a/src/analysis/processing/qgsalgorithmpackage.cpp +++ b/src/analysis/processing/qgsalgorithmpackage.cpp @@ -142,6 +142,12 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap ¶meters feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) ); errored = true; break; + + case QgsMapLayer::MeshLayer: + //not supported + feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) ); + errored = true; + break; } } diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 346f51deec4..a9a151c58ec 100755 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -597,6 +597,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/geometry ${CMAKE_SOURCE_DIR}/src/core/geocms/geonode ${CMAKE_SOURCE_DIR}/src/core/metadata + ${CMAKE_SOURCE_DIR}/src/core/mesh ${CMAKE_SOURCE_DIR}/src/core/layertree ${CMAKE_SOURCE_DIR}/src/core/locator ${CMAKE_SOURCE_DIR}/src/core/providers/memory diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 1143d8b1151..96ea3bce3d9 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -228,6 +228,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX(); #include "qgsmessageviewer.h" #include "qgsmessagebar.h" #include "qgsmessagebaritem.h" +#include "qgsmeshlayer.h" #include "qgsmemoryproviderutils.h" #include "qgsmimedatautils.h" #include "qgsmessagelog.h" @@ -1667,6 +1668,11 @@ void QgisApp::handleDropUriList( const QgsMimeDataUtils::UriList &lst ) { addRasterLayer( uri, u.name, u.providerKey ); } + else if ( u.layerType == QLatin1String( "mesh" ) ) + { + QgsMeshLayer *layer = new QgsMeshLayer( uri, u.name, u.providerKey ); + addMapLayer( layer ); + } else if ( u.layerType == QLatin1String( "plugin" ) ) { addPluginLayer( uri, u.name, u.providerKey ); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4fb219682cf..fc32ca63329 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -446,6 +446,12 @@ SET(QGIS_CORE_SRCS raster/qgssinglebandpseudocolorrenderer.cpp raster/qgshillshaderenderer.cpp + mesh/qgsmeshdataprovider.cpp + mesh/qgsmeshlayer.cpp + mesh/qgsmeshlayerrenderer.cpp + mesh/qgsmeshmemorydataprovider.cpp + mesh/qgstriangularmesh.cpp + geometry/qgsabstractgeometry.cpp geometry/qgsbox3d.cpp geometry/qgscircle.cpp @@ -682,6 +688,10 @@ SET(QGIS_CORE_MOC_HDRS raster/qgsrasterlayerrenderer.h raster/qgsrasterprojector.h + mesh/qgsmeshdataprovider.h + mesh/qgsmeshlayer.h + mesh/qgsmeshmemorydataprovider.h + geometry/qgsabstractgeometry.h geometry/qgsgeometry.h geometry/qgspoint.h @@ -1058,6 +1068,9 @@ SET(QGIS_CORE_HDRS raster/qgssinglebandpseudocolorrenderer.h raster/qgshillshaderenderer.h + mesh/qgstriangularmesh.h + mesh/qgsmeshlayerrenderer.h + scalebar/qgsdoubleboxscalebarrenderer.h scalebar/qgsnumericscalebarrenderer.h scalebar/qgsscalebarsettings.h @@ -1177,6 +1190,7 @@ INCLUDE_DIRECTORIES( scalebar symbology metadata + mesh ${CMAKE_SOURCE_DIR}/external/nmea ) IF (WITH_INTERNAL_QEXTSERIALPORT) diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 0fdecb3e243..abb530ccfe7 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -3622,6 +3622,8 @@ static QVariant fcnGetLayerProperty( const QVariantList &values, const QgsExpres return QCoreApplication::translate( "expressions", "Vector" ); case QgsMapLayer::RasterLayer: return QCoreApplication::translate( "expressions", "Raster" ); + case QgsMapLayer::MeshLayer: + return QCoreApplication::translate( "expressions", "Mesh" ); case QgsMapLayer::PluginLayer: return QCoreApplication::translate( "expressions", "Plugin" ); } diff --git a/src/core/mesh/qgsmeshdataprovider.cpp b/src/core/mesh/qgsmeshdataprovider.cpp new file mode 100644 index 00000000000..208a6d6c234 --- /dev/null +++ b/src/core/mesh/qgsmeshdataprovider.cpp @@ -0,0 +1,40 @@ +/*************************************************************************** + qgsmeshdataprovider.cpp + ----------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshdataprovider.h" + +QgsMeshDataProvider::QgsMeshDataProvider( const QString &uri ) + : QgsDataProvider( uri ) +{ +} + + +QgsRectangle QgsMeshDataProvider::extent() const +{ + QgsRectangle rec; + rec.setMinimal(); + for ( size_t i = 0; i < vertexCount(); ++i ) + { + QgsMeshVertex v = vertex( i ); + rec.setXMinimum( std::min( rec.xMinimum(), v.x() ) ); + rec.setYMinimum( std::min( rec.yMinimum(), v.y() ) ); + rec.setXMaximum( std::max( rec.xMaximum(), v.x() ) ); + rec.setYMaximum( std::max( rec.yMaximum(), v.y() ) ); + } + return rec; + +} diff --git a/src/core/mesh/qgsmeshdataprovider.h b/src/core/mesh/qgsmeshdataprovider.h new file mode 100644 index 00000000000..05f6c18308b --- /dev/null +++ b/src/core/mesh/qgsmeshdataprovider.h @@ -0,0 +1,104 @@ +/*************************************************************************** + qgsmeshdataprovider.h + --------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHDATAPROVIDER_H +#define QGSMESHDATAPROVIDER_H + +#include + +#include "qgis_core.h" +#include "qgis.h" +#include "qgspoint.h" +#include "qgsrectangle.h" +#include "qgsdataprovider.h" +#include "qgscoordinatereferencesystem.h" + +#include +#include +#include +#include + +typedef QgsPoint QgsMeshVertex; //xyz coords of vertex +typedef QVector QgsMeshFace; //list of vertex indexes + +/** + * \ingroup core + * Mesh is a collection of vertices and faces in 2D or 3D space + * - vertex - XY(Z) point (in the mesh's coordinate reference system) + * - faces - sets of vertices forming a closed shape - typically triangles or quadrilaterals + * + * Base on the underlying data provider/format, whole mesh is either stored in memory or + * read on demand + * + * \since QGIS 3.2 + */ +class CORE_EXPORT QgsMeshSource SIP_ABSTRACT +{ + public: + //! Dtor + virtual ~QgsMeshSource() = default; + + /** + * \brief Return number of vertexes in the native mesh + * \returns Number of vertexes in the mesh + */ + virtual size_t vertexCount() const = 0; + + /** + * \brief Return number of faces in the native mesh + * \returns Number of faces in the mesh + */ + virtual size_t faceCount() const = 0; + + /** + * \brief Factory for mesh vertex with index + * \returns new mesh vertex on index + */ + virtual QgsMeshVertex vertex( size_t index ) const = 0; + + /** + * \brief Factory for mesh face with index + * \returns new mesh face on index + */ + virtual QgsMeshFace face( size_t index ) const = 0; +}; + +/** + * \ingroup core + * Base class for providing data for QgsMeshLayer + * + * Responsible for reading native mesh data + * + * \see QgsMeshSource + * + */ +class CORE_EXPORT QgsMeshDataProvider: public QgsDataProvider, public QgsMeshSource +{ + Q_OBJECT + + public: + //! Ctor + QgsMeshDataProvider( const QString &uri = QString() ); + + /** + * Returns the extent of the layer + * \returns QgsRectangle containing the extent of the layer + */ + virtual QgsRectangle extent() const; +}; + +#endif // QGSMESHDATAPROVIDER_H diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp new file mode 100644 index 00000000000..0b50b737d65 --- /dev/null +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -0,0 +1,217 @@ +/*************************************************************************** + qgsmeshlayer.cpp + ---------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include + +#include "qgsmeshlayer.h" +#include "qgis.h" +#include "qgsmaplayerrenderer.h" +#include "qgsmeshdataprovider.h" +#include "qgsmeshlayerrenderer.h" +#include "qgstriangularmesh.h" +#include "qgssinglesymbolrenderer.h" +#include "qgsmeshmemorydataprovider.h" +#include "qgsfillsymbollayer.h" +#include "qgsproviderregistry.h" +#include "qgslogger.h" + +QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, + const QString &baseName, + const QString &providerKey ) + : QgsMapLayer( MeshLayer, baseName, meshLayerPath ) + , mProviderKey( providerKey ) +{ + // load data + setDataProvider( providerKey ); + + QgsSymbolLayerList l1; + l1 << new QgsSimpleFillSymbolLayer( Qt::white, Qt::NoBrush, Qt::black, Qt::SolidLine, 1.0 ); + mNativeMeshSymbol = new QgsFillSymbol( l1 ); + + + toggleTriangularMeshRendering( false ); + +} // QgsMeshLayer ctor + + + +QgsMeshLayer::~QgsMeshLayer() +{ + clearMeshes(); + + if ( mDataProvider ) + delete mDataProvider; + + if ( mNativeMeshSymbol ) + delete mNativeMeshSymbol; + + if ( mTriangularMeshSymbol ) + delete mTriangularMeshSymbol; +} + +QgsMeshDataProvider *QgsMeshLayer::dataProvider() +{ + return mDataProvider; +} + +const QgsMeshDataProvider *QgsMeshLayer::dataProvider() const +{ + return mDataProvider; +} + +QgsMeshLayer *QgsMeshLayer::clone() const +{ + QgsMeshLayer *layer = new QgsMeshLayer( source(), name(), mProviderKey ); + QgsMapLayer::clone( layer ); + return layer; +} + +QgsRectangle QgsMeshLayer::extent() const +{ + if ( mDataProvider ) + return mDataProvider->extent(); + else + { + QgsRectangle rec; + rec.setMinimal(); + return rec; + } +} + +QString QgsMeshLayer::providerType() const +{ + return mProviderKey; +} + +void QgsMeshLayer::toggleTriangularMeshRendering( bool toggle ) +{ + if ( toggle && mTriangularMeshSymbol ) + return; + + if ( mTriangularMeshSymbol ) + delete mTriangularMeshSymbol; + + if ( toggle ) + { + QgsSymbolLayerList l2; + l2 << new QgsSimpleFillSymbolLayer( Qt::white, Qt::NoBrush, Qt::red, Qt::SolidLine, 0.26 ); + mTriangularMeshSymbol = new QgsFillSymbol( l2 ); + } + else + { + mTriangularMeshSymbol = nullptr; + } + triggerRepaint(); +} + +void QgsMeshLayer::fillNativeMesh() +{ + Q_ASSERT( !mNativeMesh ); + + mNativeMesh = new QgsMesh(); + + if ( !( dataProvider() && dataProvider()->isValid() ) ) + return; + + mNativeMesh->vertices.resize( dataProvider()->vertexCount() ); + for ( size_t i = 0; i < dataProvider()->vertexCount(); ++i ) + { + mNativeMesh->vertices[i] = dataProvider()->vertex( i ); + } + + mNativeMesh->faces.resize( dataProvider()->faceCount() ); + for ( size_t i = 0; i < dataProvider()->faceCount(); ++i ) + { + mNativeMesh->faces[i] = dataProvider()->face( i ); + } +} + +QgsMapLayerRenderer *QgsMeshLayer::createMapRenderer( QgsRenderContext &rendererContext ) +{ + if ( !mNativeMesh ) + { + fillNativeMesh(); + } + + if ( !mTriangularMesh ) + mTriangularMesh = new QgsTriangularMesh(); + + triangularMesh()->update( mNativeMesh, &rendererContext ); + return new QgsMeshLayerRenderer( this, rendererContext ); +} + +bool QgsMeshLayer::readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ) +{ + Q_UNUSED( node ); + Q_UNUSED( errorMessage ); + Q_UNUSED( context ); + return true; +} + +bool QgsMeshLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const +{ + Q_UNUSED( node ); + Q_UNUSED( doc ); + Q_UNUSED( errorMessage ); + Q_UNUSED( context ); + return true; +} + +void QgsMeshLayer::clearMeshes() +{ + if ( mTriangularMesh ) + delete mTriangularMesh; + + if ( mNativeMesh ) + delete mNativeMesh; + +} + +bool QgsMeshLayer::setDataProvider( QString const &provider ) +{ + clearMeshes(); + + mProviderKey = provider; + QString dataSource = mDataSource; + + if ( mDataProvider ) + delete mDataProvider; + + mDataProvider = qobject_cast( QgsProviderRegistry::instance()->createProvider( provider, dataSource ) ); + if ( !mDataProvider ) + { + QgsDebugMsgLevel( QStringLiteral( "Unable to get mesh data provider" ), 2 ); + return false; + } + + mDataProvider->setParent( this ); + QgsDebugMsgLevel( QStringLiteral( "Instantiated the mesh data provider plugin" ), 2 ); + + mValid = mDataProvider->isValid(); + if ( !mValid ) + { + QgsDebugMsgLevel( QStringLiteral( "Invalid mesh provider plugin %1" ).arg( QString( mDataSource.toUtf8() ) ), 2 ); + return false; + } + + if ( provider == QStringLiteral( "mesh_memory" ) ) + { + // required so that source differs between memory layers + mDataSource = mDataSource + QStringLiteral( "&uid=%1" ).arg( QUuid::createUuid().toString() ); + } + + return true; +} // QgsMeshLayer:: setDataProvider diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h new file mode 100644 index 00000000000..fe8df374e62 --- /dev/null +++ b/src/core/mesh/qgsmeshlayer.h @@ -0,0 +1,224 @@ +/*************************************************************************** + qgsmeshlayer.h + -------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHLAYER_H +#define QGSMESHLAYER_H + +#include "qgis_core.h" +#include +#include +#include +#include +#include +#include +#include + +#include "qgis.h" +#include "qgsmaplayer.h" +#include "qgsrendercontext.h" +#include "qgsmeshdataprovider.h" + +class QgsMapLayerRenderer; +class QgsSymbol; + +class QgsMeshDataProvider; +class QgsNativeMesh; +class QgsTriangularMesh; +struct QgsMesh; + +/** + * \ingroup core + * + * Represents a mesh layer supporting display of data on structured or unstructured meshes + * + * The QgsMeshLayer is instantiated by specifying the name of a data provider, + * such as mdal, and url defining the specific data set to connect to. + * The vector layer constructor in turn instantiates a QgsMeshDataProvider subclass + * corresponding to the provider type, and passes it the url. The data provider + * connects to the data source. + * + * The QgsMeshLayer provides a common interface to the different data types. It does not + * yet support editing transactions. + * + * The main data providers supported by QGIS are listed below. + * + * \section providers Mesh data providers + * + * \subsection memory Memory data providerType (mesh_memory) + * + * The memory data provider is used to construct in memory data, for example scratch + * data. There is no inherent persistent storage of the data. The data source uri is constructed. + * Data can be populated by setMesh(const QString &vertices, const QString &faces), where + * vertices and faces is comma separated coordinates and connections for mesh. + * E.g. to create mesh with one quad and one triangle + * + * \code + * QString uri( + * "1.0, 2.0 \n" \ + * "2.0, 2.0 \n" \ + * "3.0, 2.0 \n" \ + * "2.0, 3.0 \n" \ + * "1.0, 3.0 \n" \ + * "---" + * "0, 1, 3, 4 \n" \ + * "1, 2, 3 \n" + * ); + * QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch layer", "memory_mesh"); + * \endcode + * + * \subsection mdal MDAL data provider (mdal) + * + * Accesses data using the MDAL drivers (https://github.com/lutraconsulting/MDAL). The url + * is the MDAL connection string. QGIS must be built with MDAL support to allow this provider. + + * \code + * QString uri = "test/land.2dm"; + * QgsMeshLayer *scratchLayer = new QgsMeshLayer(uri, "My Scratch Layer", "mdal"); + * \endcode + * + * \since QGIS 3.2 + */ +class CORE_EXPORT QgsMeshLayer : public QgsMapLayer +{ + Q_OBJECT + public: + + /** + * Constructor - creates a mesh layer + * + * The QgsMeshLayer is constructed by instantiating a data provider. The provider + * interprets the supplied path (url) of the data source to connect to and access the + * data. + * + * \param path The path or url of the parameter. Typically this encodes + * parameters used by the data provider as url query items. + * \param baseName The name used to represent the layer in the legend + * \param providerLib The name of the data provider, e.g., "mesh_memory", "mdal" + */ + explicit QgsMeshLayer( const QString &path = QString(), const QString &baseName = QString(), const QString &providerLib = "mesh_memory" ); + //! Dtor + ~QgsMeshLayer() override; + + //! QgsMeshLayer cannot be copied. + QgsMeshLayer( const QgsMeshLayer &rhs ) = delete; + //! QgsMeshLayer cannot be copied. + QgsMeshLayer &operator=( QgsMeshLayer const &rhs ) = delete; + + //! Return data provider + QgsMeshDataProvider *dataProvider() override; + + //! Return const data provider + const QgsMeshDataProvider *dataProvider() const override SIP_SKIP; + + /** + * Returns a new instance equivalent to this one. A new provider is + * created for the same data source and renderers are cloned too. + * \returns a new layer instance + */ + QgsMeshLayer *clone() const override SIP_FACTORY; + + //! Returns the extent of the layer. + QgsRectangle extent() const override; + + /** + * Return new instance of QgsMapLayerRenderer that will be used for rendering of given context + */ + virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) SIP_FACTORY; + + //! Return the provider type for this layer + QString providerType() const; + + //! return native mesh (nullprt before rendering) + QgsMesh *nativeMesh() SIP_SKIP {return mNativeMesh;} + + //! return triangular mesh (nullprt before rendering) + QgsTriangularMesh *triangularMesh() SIP_SKIP {return mTriangularMesh;} + + //! Returns a line symbol used for rendering native mesh. + QgsSymbol *nativeMeshSymbol() {return mNativeMeshSymbol;} + + /** + * Returns a line symbol used for rendering of triangular (derived) mesh. + * \see toggleTriangularMeshRendering + */ + QgsSymbol *triangularMeshSymbol() {return mTriangularMeshSymbol;} + + //! Toggle rendering of triangular (derived) mesh. Off by default + void toggleTriangularMeshRendering( bool toggle ); + + /** + * Read the symbology for the current layer from the Dom node supplied. + * \param node node that will contain the symbology definition for this layer. + * \param errorMessage reference to string that will be updated with any error messages + * \param context reading context (used for transform from relative to absolute paths) + * \returns true in case of success. + */ + bool readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ); + + /** + * Write the symbology for the layer into the docment provided. + * \param node the node that will have the style element added to it. + * \param doc the document that will have the QDomNode added. + * \param errorMessage reference to string that will be updated with any error messages + * \param context writing context (used for transform from absolute to relative paths) + * \returns true in case of success. + */ + bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const; + + private: // Private methods + + /** + * Returns true if the provider is in read-only mode + */ + bool isReadOnly() const override {return true;} + + /** + * Bind layer to a specific data provider + * \param provider provider key string, must match a valid QgsMeshDataProvider key. E.g. "mesh_memory", etc. + */ + bool setDataProvider( QString const &provider ); + +#ifdef SIP_RUN + QgsMeshLayer( const QgsMeshLayer &rhs ); +#endif + + private: + //! Clear native and triangular mesh + void clearMeshes(); + void fillNativeMesh(); + + private: + //! Pointer to native mesh structure, used as cache for rendering + QgsMesh *mNativeMesh = nullptr; + + //! Pointer to derived mesh structure + QgsTriangularMesh *mTriangularMesh = nullptr; + + //! Pointer to data provider derived from the abastract base class QgsMeshDataProvider + QgsMeshDataProvider *mDataProvider = nullptr; + + //! Data provider key + QString mProviderKey; + + //! rendering native mesh + QgsSymbol *mNativeMeshSymbol = nullptr; + + //! rendering triangular mesh + QgsSymbol *mTriangularMeshSymbol = nullptr; +}; + +#endif //QGSMESHLAYER_H diff --git a/src/core/mesh/qgsmeshlayerrenderer.cpp b/src/core/mesh/qgsmeshlayerrenderer.cpp new file mode 100644 index 00000000000..45071ad3c6d --- /dev/null +++ b/src/core/mesh/qgsmeshlayerrenderer.cpp @@ -0,0 +1,109 @@ +/*************************************************************************** + qgsmeshlayerrenderer.cpp + ------------------------ + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshlayerrenderer.h" + +#include "qgsrenderer.h" +#include "qgsrendercontext.h" +#include "qgsmeshlayer.h" +#include "qgsexception.h" +#include "qgslogger.h" +#include "qgssettings.h" +#include "qgssinglesymbolrenderer.h" +#include "qgsfield.h" +#include "qgstriangularmesh.h" +#include "qgspointxy.h" + +#include + + +QgsMeshLayerRenderer::QgsMeshLayerRenderer( QgsMeshLayer *layer, QgsRenderContext &context ) + : QgsMapLayerRenderer( layer->id() ) + , mContext( context ) +{ + // make copies for mesh data + Q_ASSERT( layer->nativeMesh() ); + Q_ASSERT( layer->triangularMesh() ); + mNativeMesh = *( layer->nativeMesh() ); + mTriangularMesh = *( layer->triangularMesh() ); + + // make copies for symbols + if ( layer->nativeMeshSymbol() ) + { + mNativeMeshSymbol = layer->nativeMeshSymbol()->clone(); + } + + if ( layer->triangularMeshSymbol() ) + { + mTriangularMeshSymbol = layer->triangularMeshSymbol()->clone(); + } +} + + +QgsMeshLayerRenderer::~QgsMeshLayerRenderer() +{ + if ( mNativeMeshSymbol ) + delete mNativeMeshSymbol; + + if ( mTriangularMeshSymbol ) + delete mTriangularMeshSymbol; +} + + + +bool QgsMeshLayerRenderer::render() +{ + renderMesh( mNativeMeshSymbol, mNativeMesh.faces ); // native mesh + renderMesh( mTriangularMeshSymbol, mTriangularMesh.triangles() ); // triangular mesh + + return true; +} + +void QgsMeshLayerRenderer::renderMesh( QgsSymbol *symbol, const QVector &faces ) +{ + if ( !symbol ) + return; + + QgsFields fields; + QgsSingleSymbolRenderer renderer( symbol->clone() ); + renderer.startRender( mContext, fields ); + + for ( int i = 0; i < faces.size(); ++i ) + { + if ( mContext.renderingStopped() ) + break; + + const QgsMeshFace &face = faces[i]; + QgsFeature feat; + feat.setFields( fields ); + QVector ring; + for ( int j = 0; j < face.size(); ++j ) + { + int vertex_id = face[j]; + Q_ASSERT( vertex_id < mTriangularMesh.vertices().size() ); //Triangular mesh vertices contains also native mesh vertices + const QgsPoint &vertex = mTriangularMesh.vertices()[vertex_id]; + ring.append( vertex ); + } + QgsPolygonXY polygon; + polygon.append( ring ); + QgsGeometry geom = QgsGeometry::fromPolygonXY( polygon ); + feat.setGeometry( geom ); + renderer.renderFeature( feat, mContext ); + } + + renderer.stopRender( mContext ); +} diff --git a/src/core/mesh/qgsmeshlayerrenderer.h b/src/core/mesh/qgsmeshlayerrenderer.h new file mode 100644 index 00000000000..a6d7841d5fc --- /dev/null +++ b/src/core/mesh/qgsmeshlayerrenderer.h @@ -0,0 +1,79 @@ +/*************************************************************************** + qgsmeshlayerrenderer.h + ---------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHLAYERRENDERER_H +#define QGSMESHLAYERRENDERER_H + +class QgsRenderContext; +class QgsMeshLayer; +class QgsMeshVectorFieldRenderer; +class QgsSingleSymbolRenderer; +class QgsTriangularMesh; +class QgsSymbol; + +#define SIP_NO_FILE + +#include +#include + +#include "qgis.h" +#include "qgsfeedback.h" + +#include "qgsmaplayerrenderer.h" +#include "qgsmeshdataprovider.h" +#include "qgstriangularmesh.h" + +/** + * \ingroup core + * Implementation of threaded rendering for mesh layers. + * + * \since QGIS 3.2 + * \note not available in Python bindings + */ +class QgsMeshLayerRenderer : public QgsMapLayerRenderer +{ + public: + QgsMeshLayerRenderer( QgsMeshLayer *layer, QgsRenderContext &context ); + ~QgsMeshLayerRenderer() override; + + bool render() override; + + private: + void renderMesh( QgsSymbol *symbol, const QVector &faces ); + + + protected: + // copy from mesh layer + QgsMesh mNativeMesh; + + // copy from mesh layer + QgsTriangularMesh mTriangularMesh; + + // copy from mesh layer + QgsSymbol *mNativeMeshSymbol = nullptr; + + // copy from mesh layer + QgsSymbol *mTriangularMeshSymbol = nullptr; + + // rendering context + QgsRenderContext &mContext; + + +}; + + +#endif // QGSMESHLAYERRENDERER_H diff --git a/src/core/mesh/qgsmeshmemorydataprovider.cpp b/src/core/mesh/qgsmeshmemorydataprovider.cpp new file mode 100644 index 00000000000..814165a0b0d --- /dev/null +++ b/src/core/mesh/qgsmeshmemorydataprovider.cpp @@ -0,0 +1,166 @@ +/*************************************************************************** + qgsmeshmemorydataprovider.cpp + ----------------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmeshmemorydataprovider.h" +#include +#include +#include + +static const QString TEXT_PROVIDER_KEY = QStringLiteral( "mesh_memory" ); +static const QString TEXT_PROVIDER_DESCRIPTION = QStringLiteral( "Mesh memory provider" ); + +bool QgsMeshMemoryDataProvider::isValid() const +{ + return true; +} + +QString QgsMeshMemoryDataProvider::name() const +{ + return "mesh_memory"; +} + +QString QgsMeshMemoryDataProvider::description() const +{ + return "memory data provider for mesh layer"; +} + +QgsCoordinateReferenceSystem QgsMeshMemoryDataProvider::crs() const +{ + return QgsCoordinateReferenceSystem(); +} + +QgsMeshMemoryDataProvider::QgsMeshMemoryDataProvider( const QString &uri ) + : QgsMeshDataProvider( uri ) + , mIsValid( false ) +{ + mIsValid = splitSections( uri ); +} + +QgsMeshMemoryDataProvider::~QgsMeshMemoryDataProvider() +{ +} + +QString QgsMeshMemoryDataProvider::providerKey() +{ + return TEXT_PROVIDER_KEY; +} + +QString QgsMeshMemoryDataProvider::providerDescription() +{ + return TEXT_PROVIDER_DESCRIPTION; +} + +QgsMeshMemoryDataProvider *QgsMeshMemoryDataProvider::createProvider( const QString &uri ) +{ + return new QgsMeshMemoryDataProvider( uri ); +} + +bool QgsMeshMemoryDataProvider::splitSections( const QString &uri ) +{ + QStringList sections = uri.split( "---", QString::SkipEmptyParts ); + if ( sections.size() != 2 ) + { + setError( QgsError( QStringLiteral( "Invalid mesh definition, does not contain 2 sections" ), + QStringLiteral( "Mesh Memory Provider" ) ) ); + return false; + } + + if ( addVertices( sections[0] ) ) + return addFaces( sections[1] ); + else + return false; +} + +bool QgsMeshMemoryDataProvider::addVertices( const QString &def ) +{ + QVector vertices; + + QStringList verticesCoords = def.split( "\n", QString::SkipEmptyParts ); + for ( int i = 0; i < verticesCoords.size(); ++i ) + { + QStringList coords = verticesCoords[i].split( ",", QString::SkipEmptyParts ); + if ( coords.size() != 2 ) + { + setError( QgsError( QStringLiteral( "Invalid mesh definition, vertex definition does not contain x, y" ), + QStringLiteral( "Mesh Memory Provider" ) ) ); + return false; + } + double x = coords.at( 0 ).toDouble(); + double y = coords.at( 1 ).toDouble(); + QgsMeshVertex vertex( x, y ); + vertices.push_back( vertex ); + } + + mVertices = vertices; + return true; +} + +bool QgsMeshMemoryDataProvider::addFaces( const QString &def ) +{ + QVector faces; + + QStringList facesVertices = def.split( "\n", QString::SkipEmptyParts ); + for ( int i = 0; i < facesVertices.size(); ++i ) + { + QStringList vertices = facesVertices[i].split( ",", QString::SkipEmptyParts ); + if ( vertices.size() < 3 ) + { + setError( QgsError( QStringLiteral( "Invalid mesh definition, face must contain at least 3 vertices" ), + QStringLiteral( "Mesh Memory Provider" ) ) ); + return false; + } + QgsMeshFace face; + for ( int j = 0; j < vertices.size(); ++j ) + { + int vertex_id = vertices[j].toInt(); + face.push_back( vertex_id ); + if ( face[j] >= mVertices.size() ) + { + setError( QgsError( QStringLiteral( "Invalid mesh definition, missing vertex id defined in face" ), QStringLiteral( "Mesh Memory Provider" ) ) ); + return false; + } + } + faces.push_back( face ); + } + + mFaces = faces; + return true; +} + +size_t QgsMeshMemoryDataProvider::vertexCount() const +{ + return mVertices.size(); +} + +size_t QgsMeshMemoryDataProvider::faceCount() const +{ + return mFaces.size(); +} + +QgsMeshVertex QgsMeshMemoryDataProvider::vertex( size_t index ) const +{ + Q_ASSERT( vertexCount() > index ); + return mVertices[index]; +} + +QgsMeshFace QgsMeshMemoryDataProvider::face( size_t index ) const +{ + Q_ASSERT( faceCount() > index ); + return mFaces[index]; +} + + diff --git a/src/core/mesh/qgsmeshmemorydataprovider.h b/src/core/mesh/qgsmeshmemorydataprovider.h new file mode 100644 index 00000000000..dbbe72c439b --- /dev/null +++ b/src/core/mesh/qgsmeshmemorydataprovider.h @@ -0,0 +1,100 @@ +/*************************************************************************** + qgsmeshmemorydataprovider.h + --------------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMESHMEMORYDATAPROVIDER_H +#define QGSMESHMEMORYDATAPROVIDER_H + +#define SIP_NO_FILE + +///@cond PRIVATE + +#include + +#include + +#include "qgis_core.h" +#include "qgis.h" +#include "qgsmeshdataprovider.h" +#include "qgsrectangle.h" + +/** + * \ingroup core + * Provides data stored in-memory for QgsMeshLayer. Useful for plugins or tests. + * \since QGIS 3.2 + */ +class QgsMeshMemoryDataProvider: public QgsMeshDataProvider +{ + Q_OBJECT + + public: + + /** + * Construct a mesh in-memory data provider from data string + * + * Data string constains simple definition of vertices and faces + * Each entry is separated by "\n" sign and section deliminer "---" + * vertex is x and y coordinate separated by comma + * face is list of vertex indexes, numbered from 0 + * For example: + * + * \code + * QString uri( + * "1.0, 2.0 \n" \ + * "2.0, 2.0 \n" \ + * "3.0, 2.0 \n" \ + * "2.0, 3.0 \n" \ + * "1.0, 3.0 \n" \ + * "---" + * "0, 1, 3, 4 \n" \ + * "1, 2, 3 \n" + * ); + * \endcode + */ + QgsMeshMemoryDataProvider( const QString &uri = QString() ); + ~QgsMeshMemoryDataProvider(); + + bool isValid() const override; + QString name() const override; + QString description() const override; + QgsCoordinateReferenceSystem crs() const override; + + size_t vertexCount() const override; + size_t faceCount() const override; + QgsMeshVertex vertex( size_t index ) const override; + QgsMeshFace face( size_t index ) const override; + + //! Returns the memory provider key + static QString providerKey(); + //! Returns the memory provider description + static QString providerDescription(); + //! Provider factory + static QgsMeshMemoryDataProvider *createProvider( const QString &uri ); + private: + bool splitSections( const QString &uri ); + + bool addVertices( const QString &def ); + bool addFaces( const QString &def ); + + QVector mVertices; + QVector mFaces; + + bool mIsValid; +}; + +///@endcond + +#endif // QGSMESHMEMORYDATAPROVIDER_H diff --git a/src/core/mesh/qgstriangularmesh.cpp b/src/core/mesh/qgstriangularmesh.cpp new file mode 100644 index 00000000000..c61b8779fca --- /dev/null +++ b/src/core/mesh/qgstriangularmesh.cpp @@ -0,0 +1,110 @@ +/*************************************************************************** + qgstriangularmesh.cpp + --------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstriangularmesh.h" +#include "qgsmeshdataprovider.h" +#include "qgsrendercontext.h" +#include "qgscoordinatereferencesystem.h" +#include "qgscoordinatetransform.h" +#include "qgsmeshlayer.h" + + +QgsTriangularMesh::QgsTriangularMesh( ) +{ +} + +QgsTriangularMesh::~QgsTriangularMesh() +{ +} + +void QgsTriangularMesh::update( QgsMesh *nativeMesh, QgsRenderContext *context ) +{ + Q_ASSERT( nativeMesh ); + Q_ASSERT( context ); + + mTriangularMesh.vertices.clear(); + mTriangularMesh.faces.clear(); + mTrianglesToNativeFaces.clear(); + + // TRANSFORM VERTICES + QgsCoordinateTransform transform = context->coordinateTransform(); + mTriangularMesh.vertices.resize( nativeMesh->vertices.size() ); + for ( int i = 0; i < nativeMesh->vertices.size(); ++i ) + { + QgsMeshVertex vertex = nativeMesh->vertices[i]; + if ( transform.isValid() ) + { + QgsPointXY mapPoint = transform.transform( QgsPointXY( vertex.x(), vertex.y() ) ); + QgsMeshVertex mapVertex( mapPoint ); + mapVertex.setZ( vertex.z() ); + mapVertex.setM( vertex.m() ); + mTriangularMesh.vertices[i] = mapVertex; + } + else + { + mTriangularMesh.vertices[i] = vertex; + } + } + + // CREATE TRIANGULAR MESH + for ( int i = 0; i < nativeMesh->faces.size(); ++i ) + { + QgsMeshFace face = nativeMesh->faces[i] ; + if ( face.size() == 3 ) + { + // triangle + mTriangularMesh.faces.push_back( face ); + mTrianglesToNativeFaces.push_back( i ); + } + else if ( face.size() == 4 ) + { + // quad + QgsMeshFace face1; + face1.push_back( face[0] ); + face1.push_back( face[1] ); + face1.push_back( face[2] ); + + mTriangularMesh.faces.push_back( face1 ); + mTrianglesToNativeFaces.push_back( i ); + + QgsMeshFace face2; + face2.push_back( face[0] ); + face2.push_back( face[2] ); + face2.push_back( face[3] ); + + mTriangularMesh.faces.push_back( face2 ); + mTrianglesToNativeFaces.push_back( i ); + } + } + +} + +const QVector &QgsTriangularMesh::vertices() const +{ + return mTriangularMesh.vertices; +} + +const QVector &QgsTriangularMesh::triangles() const +{ + return mTriangularMesh.faces; +} + +const QVector &QgsTriangularMesh::trianglesToNativeFaces() const +{ + return mTrianglesToNativeFaces; +} + diff --git a/src/core/mesh/qgstriangularmesh.h b/src/core/mesh/qgstriangularmesh.h new file mode 100644 index 00000000000..d97732a3215 --- /dev/null +++ b/src/core/mesh/qgstriangularmesh.h @@ -0,0 +1,83 @@ +/*************************************************************************** + qgstriangularmesh.h + ------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSTRIANGULARMESH_H +#define QGSTRIANGULARMESH_H + + +#define SIP_NO_FILE + +#include +#include +#include + +#include "qgis.h" +#include "qgis_core.h" +#include "qgsmeshdataprovider.h" +#include "qgscoordinatereferencesystem.h" + +class QgsRenderContext; + +//! Mesh - vertices and faces +struct CORE_EXPORT QgsMesh +{ + //! vertices + QVector vertices; + //! faces + QVector faces; +}; + +/** + * Triangular/Derived Mesh + * \since QGIS 3.2 + */ +class CORE_EXPORT QgsTriangularMesh +{ + public: + //! Ctor + QgsTriangularMesh(); + //! Dtor + ~QgsTriangularMesh(); + + /** + * Construct triangular mesh from layer's native mesh and context + * \param layer QgsMeshLayer to get native mesh data + * \param context Rendering context to estimate number of triagles to create for an face + */ + void update( QgsMesh *nativeMesh, QgsRenderContext *context ); + + /** + * Return vertices in map CRS + * + * The list of consist of vertices from native mesh (0-N) and + * extra vertices needed to create triangles (N+1 - len) + */ + const QVector &vertices() const ; + //! Return triangles + const QVector &triangles() const ; + //! Return mapping between triangles and original faces + const QVector &trianglesToNativeFaces() const ; + + private: + // vertices: map CRS; 0-N ... native vertices, N+1 - len ... extra vertices + // faces are derived triangles + QgsMesh mTriangularMesh; + QVector mTrianglesToNativeFaces; //len(mTrianglesToNativeFaces) == len(mTriangles). Mapping derived -> native +}; + + +#endif // QGSTRIANGULARMESH_H diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index cfb0632b5ff..cc358f889a5 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -111,6 +111,8 @@ QgsMapLayer *QgsProcessingUtils::mapLayerFromStore( const QString &string, QgsMa return !canUseLayer( qobject_cast< QgsRasterLayer * >( layer ) ); case QgsMapLayer::PluginLayer: return true; + case QgsMapLayer::MeshLayer: + return false; } return true; } ), layers.end() ); diff --git a/src/core/qgsdataitem.cpp b/src/core/qgsdataitem.cpp index 28b07943296..191d61bd5f1 100644 --- a/src/core/qgsdataitem.cpp +++ b/src/core/qgsdataitem.cpp @@ -74,6 +74,12 @@ QIcon QgsLayerItem::iconRaster() return QgsApplication::getThemeIcon( QStringLiteral( "/mIconRaster.svg" ) ); } +QIcon QgsLayerItem::iconMesh() +{ + // TODO new icon! + return QgsApplication::getThemeIcon( QStringLiteral( "/mIconPointLayer.svg" ) ); +} + QIcon QgsLayerItem::iconDefault() { return QgsApplication::getThemeIcon( QStringLiteral( "/mIconLayer.png" ) ); @@ -602,6 +608,8 @@ QgsMapLayer::LayerType QgsLayerItem::mapLayerType() const { if ( mLayerType == QgsLayerItem::Raster ) return QgsMapLayer::RasterLayer; + if ( mLayerType == QgsLayerItem::Mesh ) + return QgsMapLayer::MeshLayer; if ( mLayerType == QgsLayerItem::Plugin ) return QgsMapLayer::PluginLayer; return QgsMapLayer::VectorLayer; @@ -637,6 +645,8 @@ QString QgsLayerItem::iconName( QgsLayerItem::LayerType layerType ) case Raster: return QStringLiteral( "/mIconRaster.svg" ); break; + case Mesh: + //TODO add icon! default: return QStringLiteral( "/mIconLayer.png" ); break; @@ -670,6 +680,9 @@ QgsMimeDataUtils::Uri QgsLayerItem::mimeUri() const case QgsMapLayer::RasterLayer: u.layerType = QStringLiteral( "raster" ); break; + case QgsMapLayer::MeshLayer: + u.layerType = QStringLiteral( "mesh" ); + break; case QgsMapLayer::PluginLayer: u.layerType = QStringLiteral( "plugin" ); break; diff --git a/src/core/qgsdataitem.h b/src/core/qgsdataitem.h index 962cb97fe03..2b69f8622c9 100644 --- a/src/core/qgsdataitem.h +++ b/src/core/qgsdataitem.h @@ -422,7 +422,8 @@ class CORE_EXPORT QgsLayerItem : public QgsDataItem TableLayer, Database, Table, - Plugin //!< Added in 2.10 + Plugin, //!< Added in 2.10 + Mesh //!< Added in 3.2 }; Q_ENUM( LayerType ); @@ -498,6 +499,7 @@ class CORE_EXPORT QgsLayerItem : public QgsDataItem static QIcon iconTable(); static QIcon iconRaster(); static QIcon iconDefault(); + static QIcon iconMesh(); //! \returns the layer name virtual QString layerName() const { return name(); } diff --git a/src/core/qgsdataprovider.h b/src/core/qgsdataprovider.h index 6047cedd2a6..a86974a5dd5 100644 --- a/src/core/qgsdataprovider.h +++ b/src/core/qgsdataprovider.h @@ -59,6 +59,10 @@ class CORE_EXPORT QgsDataProvider : public QObject { sipType = sipType_QgsRasterDataProvider; } + else if ( qobject_cast( sipCpp ) ) + { + sipType = sipType_QgsMeshDataProvider; + } else { sipType = 0; diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 3563b92a01a..3b381f30e64 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -85,6 +85,9 @@ class CORE_EXPORT QgsMapLayer : public QObject case QgsMapLayer::PluginLayer: sipType = sipType_QgsPluginLayer; break; + case QgsMapLayer::MeshLayer: + sipType = sipType_QgsMeshLayer; + break; default: sipType = nullptr; break; @@ -100,7 +103,8 @@ class CORE_EXPORT QgsMapLayer : public QObject { VectorLayer, RasterLayer, - PluginLayer + PluginLayer, + MeshLayer //!< Added in 3.2 }; /** diff --git a/src/core/qgsmaplayermodel.cpp b/src/core/qgsmaplayermodel.cpp index 5be821b7998..f76e93284ae 100644 --- a/src/core/qgsmaplayermodel.cpp +++ b/src/core/qgsmaplayermodel.cpp @@ -359,6 +359,11 @@ QIcon QgsMapLayerModel::iconForLayer( QgsMapLayer *layer ) return QgsLayerItem::iconRaster(); } + case QgsMapLayer::MeshLayer: + { + return QgsLayerItem::iconMesh(); + } + case QgsMapLayer::VectorLayer: { QgsVectorLayer *vl = dynamic_cast( layer ); diff --git a/src/core/qgsmimedatautils.cpp b/src/core/qgsmimedatautils.cpp index 765cd7fa382..1b8cc84ba75 100644 --- a/src/core/qgsmimedatautils.cpp +++ b/src/core/qgsmimedatautils.cpp @@ -24,6 +24,7 @@ #include "qgsrasterlayer.h" #include "qgsvectordataprovider.h" #include "qgsvectorlayer.h" +#include "qgsmeshlayer.h" static const char *QGIS_URILIST_MIMETYPE = "application/x-vnd.qgis.qgis.uri"; @@ -108,6 +109,18 @@ QgsRasterLayer *QgsMimeDataUtils::Uri::rasterLayer( bool &owner, QString &error return new QgsRasterLayer( uri, name, providerKey ); } +QgsMeshLayer *QgsMimeDataUtils::Uri::meshLayer( bool &owner, QString &error ) const +{ + owner = false; + if ( layerType != QLatin1String( "mesh" ) ) + { + error = QObject::tr( "%1: Not a mesh layer." ).arg( name ); + return nullptr; + } + owner = true; + return new QgsMeshLayer( uri, name, providerKey ); +} + // ----- bool QgsMimeDataUtils::isUriList( const QMimeData *data ) diff --git a/src/core/qgsmimedatautils.h b/src/core/qgsmimedatautils.h index 879bd132a17..c121e6345da 100644 --- a/src/core/qgsmimedatautils.h +++ b/src/core/qgsmimedatautils.h @@ -24,6 +24,7 @@ class QgsLayerItem; class QgsLayerTreeNode; class QgsVectorLayer; class QgsRasterLayer; +class QgsMeshLayer; /** * \ingroup core @@ -63,7 +64,14 @@ class CORE_EXPORT QgsMimeDataUtils */ QgsRasterLayer *rasterLayer( bool &owner, QString &error ) const; - //! Type of URI. Recognized types: "vector" / "raster" / "plugin" / "custom" / "project" + /** + * Get mesh layer from uri if possible, otherwise returns 0 and error is set + * \param owner set to true if caller becomes owner + * \param error set to error message if cannot get raster + */ + QgsMeshLayer *meshLayer( bool &owner, QString &error ) const; + + //! Type of URI. Recognized types: "vector" / "raster" / "mesh" / "plugin" / "custom" / "project" QString layerType; /** diff --git a/src/core/qgsproviderregistry.cpp b/src/core/qgsproviderregistry.cpp index fe68c604363..ef74da62042 100644 --- a/src/core/qgsproviderregistry.cpp +++ b/src/core/qgsproviderregistry.cpp @@ -31,7 +31,7 @@ #include "qgsvectorlayer.h" #include "qgsproject.h" #include "providers/memory/qgsmemoryprovider.h" - +#include "mesh/qgsmeshmemorydataprovider.h" // typedefs for provider plugin functions of interest typedef QString providerkey_t(); @@ -87,6 +87,7 @@ void QgsProviderRegistry::init() { // add standard providers mProviders[ QgsMemoryProvider::providerKey() ] = new QgsProviderMetadata( QgsMemoryProvider::providerKey(), QgsMemoryProvider::providerDescription(), &QgsMemoryProvider::createProvider ); + mProviders[ QgsMeshMemoryDataProvider::providerKey() ] = new QgsProviderMetadata( QgsMeshMemoryDataProvider::providerKey(), QgsMeshMemoryDataProvider::providerDescription(), &QgsMeshMemoryDataProvider::createProvider ); mLibraryDirectory.setSorting( QDir::Name | QDir::IgnoreCase ); mLibraryDirectory.setFilter( QDir::Files | QDir::NoSymLinks ); diff --git a/src/core/qgsproviderregistry.h b/src/core/qgsproviderregistry.h index b38c41ca12f..fb8aa1694d3 100644 --- a/src/core/qgsproviderregistry.h +++ b/src/core/qgsproviderregistry.h @@ -167,6 +167,7 @@ class CORE_EXPORT QgsProviderRegistry \note This replaces QgsRasterLayer::buildSupportedRasterFileFilter() */ virtual QString fileRasterFilters() const; + //! Return a string containing the available database drivers virtual QString databaseDrivers() const; //! Return a string containing the available directory drivers diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index e96f8c82b82..acffbfc33a1 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -890,6 +890,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/src/core/locator ${CMAKE_SOURCE_DIR}/src/core/metadata ${CMAKE_SOURCE_DIR}/src/core/processing + ${CMAKE_SOURCE_DIR}/src/core/mesh ${CMAKE_SOURCE_DIR}/src/core/providers/memory ${CMAKE_SOURCE_DIR}/src/core/raster ${CMAKE_SOURCE_DIR}/src/core/scalebar diff --git a/src/gui/qgsbrowserdockwidget_p.cpp b/src/gui/qgsbrowserdockwidget_p.cpp index 625e590e60a..6db14611582 100644 --- a/src/gui/qgsbrowserdockwidget_p.cpp +++ b/src/gui/qgsbrowserdockwidget_p.cpp @@ -35,7 +35,7 @@ #include "qgsvectorlayer.h" #include "qgsproject.h" #include "qgssettings.h" - +#include "qgsmeshlayer.h" #include @@ -154,6 +154,20 @@ void QgsBrowserLayerProperties::setItem( QgsDataItem *item ) delete layer; } } + else if ( type == QgsMapLayer::MeshLayer ) + { + QgsDebugMsg( "creating mesh layer" ); + QgsMeshLayer *layer = new QgsMeshLayer( layerItem->uri(), layerItem->uri(), layerItem->providerKey() ); + if ( layer ) + { + if ( layer->isValid() ) + { + layerCrs = layer->crs(); + layerMetadata = layer->htmlMetadata(); + } + delete layer; + } + } else if ( type == QgsMapLayer::VectorLayer ) { QgsDebugMsg( "creating vector layer" ); diff --git a/src/providers/CMakeLists.txt b/src/providers/CMakeLists.txt index 258069639c8..81d04df2a69 100644 --- a/src/providers/CMakeLists.txt +++ b/src/providers/CMakeLists.txt @@ -16,6 +16,7 @@ ADD_SUBDIRECTORY(wfs) ADD_SUBDIRECTORY(spatialite) ADD_SUBDIRECTORY(virtual) ADD_SUBDIRECTORY(db2) +ADD_SUBDIRECTORY(mdal) IF (WITH_ORACLE) ADD_SUBDIRECTORY(oracle) diff --git a/src/providers/mdal/CMakeLists.txt b/src/providers/mdal/CMakeLists.txt new file mode 100644 index 00000000000..20bbb1b0199 --- /dev/null +++ b/src/providers/mdal/CMakeLists.txt @@ -0,0 +1,80 @@ +SET(MDAL_SRCS + qgsmdalprovider.cpp + qgsmdaldataitems.cpp +) +SET(MDAL_MOC_HDRS + qgsmdalprovider.h + qgsmdaldataitems.h +) +SET(MDAL_HDRS +) + + +######################################################## +# Compile internal MDAL +IF (WITH_INTERNAL_MDAL) + ADD_DEFINITIONS(-DMDAL_STATIC) + + INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/external/mdal + ${CMAKE_SOURCE_DIR}/external/mdal/api + ) + + SET(MDAL_LIB_SRCS + ${CMAKE_SOURCE_DIR}/external/mdal/mdal.cpp + ${CMAKE_SOURCE_DIR}/external/mdal/mdal_utils.cpp + ${CMAKE_SOURCE_DIR}/external/mdal/mdal_loader.cpp + ${CMAKE_SOURCE_DIR}/external/mdal/frmts/mdal_2dm.cpp + ) + + SET(MDAL_LIB_HDRS + ${CMAKE_SOURCE_DIR}/external/mdal/api/mdal.h + ${CMAKE_SOURCE_DIR}/external/mdal/mdal_utils.hpp + ${CMAKE_SOURCE_DIR}/external/mdal/mdal_loader.hpp + ${CMAKE_SOURCE_DIR}/external/mdal/mdal_defines.hpp + ${CMAKE_SOURCE_DIR}/external/mdal/frmts/mdal_2dm.hpp + ) + + UNSET(MDAL_LIBRARY) + UNSET(MDAL_INCLUDE_DIR) + +ELSE (WITH_INTERNAL_MDAL) + + INCLUDE_DIRECTORIES (SYSTEM + ${MDAL_INCLUDE_DIR} + ) + +ENDIF (WITH_INTERNAL_MDAL) + +######################################################## + +INCLUDE_DIRECTORIES ( + ${CMAKE_SOURCE_DIR}/src/core + ${CMAKE_SOURCE_DIR}/src/core/mesh + ${CMAKE_SOURCE_DIR}/src/core/expression + ${CMAKE_SOURCE_DIR}/src/core/geometry + ${CMAKE_SOURCE_DIR}/src/core/metadata + + ${CMAKE_BINARY_DIR}/src/core +) + +QT5_WRAP_CPP(MDAL_MOC_SRCS ${MDAL_MOC_HDRS}) +ADD_LIBRARY (mdalprovider MODULE ${MDAL_SRCS} ${MDAL_MOC_HDRS} ${MDAL_MOC_SRCS} ${MDAL_LIB_SRCS} ${MDAL_LIB_HDRS}) + +TARGET_LINK_LIBRARIES (mdalprovider + qgis_core + ${MDAL_LIBRARY} +) + +# clang-tidy +IF(CLANG_TIDY_EXE) + SET_TARGET_PROPERTIES( + mdalprovider PROPERTIES + CXX_CLANG_TIDY "${DO_CLANG_TIDY}" + ) +ENDIF(CLANG_TIDY_EXE) + +INSTALL(TARGETS mdalprovider + RUNTIME DESTINATION ${QGIS_PLUGIN_DIR} + LIBRARY DESTINATION ${QGIS_PLUGIN_DIR}) + diff --git a/src/providers/mdal/qgsmdaldataitems.cpp b/src/providers/mdal/qgsmdaldataitems.cpp new file mode 100644 index 00000000000..9adf1158f28 --- /dev/null +++ b/src/providers/mdal/qgsmdaldataitems.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + qgsmdaldataitems.cpp + --------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "qgsmdaldataitems.h" +#include "qgsmdalprovider.h" +#include "qgslogger.h" +#include "qgssettings.h" + +#include + + +QgsMdalLayerItem::QgsMdalLayerItem( QgsDataItem *parent, + const QString &name, const QString &path, const QString &uri ) + : QgsLayerItem( parent, name, path, uri, QgsLayerItem::Mesh, QStringLiteral( "mdal" ) ) +{ + mToolTip = uri; + setState( Populated ); +} + +QString QgsMdalLayerItem::layerName() const +{ + QFileInfo info( name() ); + return info.completeBaseName(); +} + +// --------------------------------------------------------------------------- +static QStringList sExtensions = QStringList(); + +QGISEXTERN int dataCapabilities() +{ + return QgsDataProvider::File; +} + +QGISEXTERN QgsDataItem *dataItem( QString path, QgsDataItem *parentItem ) +{ + if ( path.isEmpty() ) + return nullptr; + + QgsDebugMsgLevel( "thePath = " + path, 2 ); + + // get suffix, removing .gz if present + QFileInfo info( path ); + QString suffix = info.suffix().toLower(); + // extract basename with extension + info.setFile( path ); + QString name = info.fileName(); + + // allow only normal files + if ( !info.isFile() ) + return nullptr; + + // get supported extensions + if ( sExtensions.isEmpty() ) + { + // TODO ask MDAL for extensions ! + sExtensions << QStringLiteral( "2dm" ); + } + + // Filter files by extension + if ( !sExtensions.contains( suffix ) ) + return nullptr; + + return new QgsMdalLayerItem( parentItem, name, path, path ); +} + diff --git a/src/providers/mdal/qgsmdaldataitems.h b/src/providers/mdal/qgsmdaldataitems.h new file mode 100644 index 00000000000..d7d034ffeb4 --- /dev/null +++ b/src/providers/mdal/qgsmdaldataitems.h @@ -0,0 +1,29 @@ +/*************************************************************************** + qgsmdaldataitems.h + ------------------ + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef QGSMDALDATAITEMS_H +#define QGSMDALDATAITEMS_H + +#include "qgsdataitem.h" + +class QgsMdalLayerItem : public QgsLayerItem +{ + Q_OBJECT + public: + QgsMdalLayerItem( QgsDataItem *parent, const QString &name, const QString &path, const QString &uri ); + + QString layerName() const override; +}; + +#endif // QGSMDALDATAITEMS_H diff --git a/src/providers/mdal/qgsmdalprovider.cpp b/src/providers/mdal/qgsmdalprovider.cpp new file mode 100644 index 00000000000..0481962e89d --- /dev/null +++ b/src/providers/mdal/qgsmdalprovider.cpp @@ -0,0 +1,137 @@ +/*************************************************************************** + qgsmeshmemorydataprovider.cpp + ----------------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmdalprovider.h" +#include +#include +#include +#include "mdal.h" + +static const QString TEXT_PROVIDER_KEY = QStringLiteral( "mdal" ); +static const QString TEXT_PROVIDER_DESCRIPTION = QStringLiteral( "MDAL provider" ); + +bool QgsMdalProvider::isValid() const +{ + return mMeshH != nullptr; +} + +QString QgsMdalProvider::name() const +{ + return TEXT_PROVIDER_KEY; +} + +QString QgsMdalProvider::description() const +{ + return TEXT_PROVIDER_DESCRIPTION; +} + +QgsCoordinateReferenceSystem QgsMdalProvider::crs() const +{ + return QgsCoordinateReferenceSystem(); +} + +QgsMdalProvider::QgsMdalProvider( const QString &uri ) + : QgsMeshDataProvider( uri ) +{ + QByteArray curi = uri.toAscii(); + mMeshH = MDAL_LoadMesh( curi.constData() ); +} + +QgsMdalProvider::~QgsMdalProvider() +{ + if ( mMeshH ) + MDAL_CloseMesh( mMeshH ); +} + +size_t QgsMdalProvider::vertexCount() const +{ + if ( mMeshH ) + return MDAL_M_vertexCount( mMeshH ); + else + return ( size_t ) 0; +} + +size_t QgsMdalProvider::faceCount() const +{ + if ( mMeshH ) + return MDAL_M_faceCount( mMeshH ); + else + return ( size_t ) 0; +} + +QgsMeshVertex QgsMdalProvider::vertex( size_t index ) const +{ + Q_ASSERT( index < vertexCount() ); + double x = MDAL_M_vertexXCoordinatesAt( mMeshH, index ); + double y = MDAL_M_vertexYCoordinatesAt( mMeshH, index ); + QgsMeshVertex vertex( x, y ); + return vertex; +} + +QgsMeshFace QgsMdalProvider::face( size_t index ) const +{ + Q_ASSERT( index < faceCount() ); + QgsMeshFace face; + int n_face_vertices = MDAL_M_faceVerticesCountAt( mMeshH, index ); + for ( size_t j = 0; j < n_face_vertices; ++j ) + { + int vertex_index = MDAL_M_faceVerticesIndexAt( mMeshH, index, j ); + face.push_back( vertex_index ); + } + return face; +} + +/*----------------------------------------------------------------------------------------------*/ + +/** + * Class factory to return a pointer to a newly created + * QgsGdalProvider object + */ +QGISEXTERN QgsMdalProvider *classFactory( const QString *uri ) +{ + return new QgsMdalProvider( *uri ); +} + +/** + * Required key function (used to map the plugin to a data store type) +*/ +QGISEXTERN QString providerKey() +{ + return TEXT_PROVIDER_KEY; +} + +/** + * Required description function + */ +QGISEXTERN QString description() +{ + return TEXT_PROVIDER_DESCRIPTION; +} + +/** + * Required isProvider function. Used to determine if this shared library + * is a data provider plugin + */ +QGISEXTERN bool isProvider() +{ + return true; +} + +QGISEXTERN void cleanupProvider() +{ +} + diff --git a/src/providers/mdal/qgsmdalprovider.h b/src/providers/mdal/qgsmdalprovider.h new file mode 100644 index 00000000000..08c7af2f83f --- /dev/null +++ b/src/providers/mdal/qgsmdalprovider.h @@ -0,0 +1,71 @@ +/*************************************************************************** + qgsmdalprovider.h + ----------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMDALPROVIDER_H +#define QGSGDALPROVIDER_H + +#include + +#include "qgscoordinatereferencesystem.h" +#include "qgsdataitem.h" +#include "qgsmeshdataprovider.h" +#include "qgsrectangle.h" + +#include +#include +#include +#include +#include + +#include + +class QMutex; +class QgsCoordinateTransform; + +/** + \brief Data provider for MDAL layers. +*/ +class QgsMdalProvider : public QgsMeshDataProvider +{ + Q_OBJECT + + public: + + /** + * Constructor for the provider. + * + * \param uri file name + * \param newDataset handle of newly created dataset. + * + */ + QgsMdalProvider( const QString &uri = QString() ); + ~QgsMdalProvider(); + + bool isValid() const override; + QString name() const override; + QString description() const override; + QgsCoordinateReferenceSystem crs() const override; + + size_t vertexCount() const override; + size_t faceCount() const override; + QgsMeshVertex vertex( size_t index ) const override; + QgsMeshFace face( size_t index ) const override; + + private: + MeshH mMeshH; +}; + +#endif + diff --git a/src/providers/ogr/qgsgeopackagedataitems.cpp b/src/providers/ogr/qgsgeopackagedataitems.cpp index 22f1bf67207..74305afdc80 100644 --- a/src/providers/ogr/qgsgeopackagedataitems.cpp +++ b/src/providers/ogr/qgsgeopackagedataitems.cpp @@ -228,6 +228,12 @@ bool QgsGeoPackageCollectionItem::handleDrop( const QMimeData *data, Qt::DropAct srcLayer = dropUri.vectorLayer( owner, error ); isVector = true; } + else if ( dropUri.layerType == QStringLiteral( "mesh" ) ) + { + // unsuported + hasError = true; + continue; + } else { srcLayer = dropUri.rasterLayer( owner, error ); diff --git a/tests/src/core/CMakeLists.txt b/tests/src/core/CMakeLists.txt index 4769a6c9d72..837d56adf56 100755 --- a/tests/src/core/CMakeLists.txt +++ b/tests/src/core/CMakeLists.txt @@ -20,6 +20,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src/core/raster ${CMAKE_SOURCE_DIR}/src/core/scalebar ${CMAKE_SOURCE_DIR}/src/core/symbology + ${CMAKE_SOURCE_DIR}/src/core/mesh ${CMAKE_SOURCE_DIR}/src/test ${CMAKE_BINARY_DIR}/src/core ) @@ -186,7 +187,9 @@ SET(TESTS testqgsvectorlayerjoinbuffer.cpp testqgsvectorlayer.cpp testziplayer.cpp - ) + testqgsmeshlayer.cpp + testqgsmeshlayerrenderer.cpp + ) IF(WITH_QTWEBKIT) SET(TESTS ${TESTS} diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp new file mode 100644 index 00000000000..4dd3aab9df6 --- /dev/null +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + testqgsmeshlayer.cpp + -------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstest.h" +#include +#include +#include +#include +#include +#include +#include +#include + +//qgis includes... +#include "qgsmaplayer.h" +#include "qgsmeshlayer.h" +#include "qgsapplication.h" +#include "qgsproviderregistry.h" +#include "qgsproject.h" +#include "qgsmaprenderersequentialjob.h" +#include "qgsmeshmemorydataprovider.h" + +/** + * \ingroup UnitTests + * This is a unit test for a mesh layer + */ +class TestQgsMeshLayer : public QObject +{ + Q_OBJECT + + public: + TestQgsMeshLayer() = default; + + private: + QgsMeshLayer *mMemoryLayer = nullptr; + QgsMeshLayer *mMdalLayer = nullptr; + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init() {} // will be called before each testfunction is executed. + void cleanup() {} // will be called after every testfunction. + + void test_data_provider(); + void test_extent(); +}; + +void TestQgsMeshLayer::initTestCase() +{ + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + QgsApplication::showSettings(); + QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt + myDataDir += "/mesh"; + // Memory layer + QFile f( myDataDir + "/quad_and_triangle.txt" ); + QVERIFY( f.open( QIODevice::ReadOnly | QIODevice::Text ) ); + QString uri( f.readAll() ); + QVERIFY( !uri.isEmpty() ); + mMemoryLayer = new QgsMeshLayer( uri, "Triangle and Quad Memory", "mesh_memory" ); + QVERIFY( mMemoryLayer->isValid() ); + QgsProject::instance()->addMapLayers( + QList() << mMemoryLayer ); + + // MDAL Layer + uri = myDataDir + "/quad_and_triangle.2dm"; + mMdalLayer = new QgsMeshLayer( uri, "Triangle and Quad MDAL", "mdal" ); + QVERIFY( mMdalLayer->isValid() ); + QgsProject::instance()->addMapLayers( + QList() << mMdalLayer ); +} + +void TestQgsMeshLayer::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsMeshLayer::test_data_provider() +{ + QList dataProviders; + dataProviders.append( mMemoryLayer->dataProvider() ); + dataProviders.append( mMdalLayer->dataProvider() ); + + QgsRectangle expectedExtent( 1000.0, 2000.0, 3000.0, 3000.0 ); + + foreach ( auto dp, dataProviders ) + { + QVERIFY( dp != nullptr ); + QVERIFY( dp->isValid() ); + QCOMPARE( expectedExtent, dp->extent() ); + + QCOMPARE( ( size_t ) 5, dp->vertexCount() ); + QCOMPARE( QgsMeshVertex( 1000.0, 2000.0 ), dp->vertex( 0 ) ); + QCOMPARE( QgsMeshVertex( 2000.0, 2000.0 ), dp->vertex( 1 ) ); + QCOMPARE( QgsMeshVertex( 3000.0, 2000.0 ), dp->vertex( 2 ) ); + QCOMPARE( QgsMeshVertex( 2000.0, 3000.0 ), dp->vertex( 3 ) ); + QCOMPARE( QgsMeshVertex( 1000.0, 3000.0 ), dp->vertex( 4 ) ); + + QCOMPARE( ( size_t ) 2, dp->faceCount() ); + QgsMeshFace f1; + f1 << 0 << 1 << 3 << 4; + QCOMPARE( f1, dp->face( 0 ) ); + + QgsMeshFace f2; + f2 << 1 << 2 << 3; + QCOMPARE( f2, dp->face( 1 ) ); + } +} + +void TestQgsMeshLayer::test_extent() +{ + QCOMPARE( mMemoryLayer->dataProvider()->extent(), mMemoryLayer->extent() ); + QCOMPARE( mMdalLayer->dataProvider()->extent(), mMdalLayer->extent() ); +} + +QGSTEST_MAIN( TestQgsMeshLayer ) +#include "testqgsmeshlayer.moc" diff --git a/tests/src/core/testqgsmeshlayerrenderer.cpp b/tests/src/core/testqgsmeshlayerrenderer.cpp new file mode 100644 index 00000000000..b2c82a87e51 --- /dev/null +++ b/tests/src/core/testqgsmeshlayerrenderer.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + testqgsmeshlayer.cpp + -------------------- + begin : April 2018 + copyright : (C) 2018 by Peter Petrik + email : zilolv at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgstest.h" +#include +#include +#include +#include +#include +#include +#include +#include + +//qgis includes... +#include "qgsmaplayer.h" +#include "qgsmeshlayer.h" +#include "qgsapplication.h" +#include "qgsproviderregistry.h" +#include "qgsproject.h" +#include "qgsmaprenderersequentialjob.h" +#include "qgsmeshmemorydataprovider.h" + +//qgis test includes +#include "qgsrenderchecker.h" + +/** + * \ingroup UnitTests + * This is a unit test for the different renderers for mesh layers. + */ +class TestQgsMeshRenderer : public QObject +{ + Q_OBJECT + + public: + TestQgsMeshRenderer() = default; + + private: + QgsMeshLayer *mMemoryLayer = nullptr; + QgsMapSettings *mMapSettings = nullptr; + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init() {} // will be called before each testfunction is executed. + void cleanup() {} // will be called after every testfunction. + bool imageCheck( const QString &testType ); + + void test_native_mesh_rendering(); + void test_triangular_mesh_rendering(); +}; + +void TestQgsMeshRenderer::initTestCase() +{ + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + QgsApplication::showSettings(); + QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt + myDataDir += "/mesh"; + + mMapSettings = new QgsMapSettings(); + + // Memory layer + QFile f( myDataDir + "/quad_and_triangle.txt" ); + QVERIFY( f.open( QIODevice::ReadOnly | QIODevice::Text ) ); + QString uri( f.readAll() ); + QVERIFY( !uri.isEmpty() ); + mMemoryLayer = new QgsMeshLayer( uri, "Triangle and Quad Memory", "mesh_memory" ); + QVERIFY( mMemoryLayer->isValid() ); + QgsProject::instance()->addMapLayers( + QList() << mMemoryLayer ); + mMapSettings->setLayers( + QList() << mMemoryLayer ); +} +void TestQgsMeshRenderer::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +bool TestQgsMeshRenderer::imageCheck( const QString &testType ) +{ + mMapSettings->setExtent( mMemoryLayer->extent() ); + mMapSettings->setDestinationCrs( mMemoryLayer->crs() ); + mMapSettings->setOutputDpi( 96 ); + QgsRenderChecker myChecker; + myChecker.setControlPathPrefix( QStringLiteral( "mesh" ) ); + myChecker.setControlName( "expected_" + testType ); + myChecker.setMapSettings( *mMapSettings ); + myChecker.setColorTolerance( 15 ); + bool myResultFlag = myChecker.runTest( testType, 0 ); + return myResultFlag; +} + +void TestQgsMeshRenderer::test_native_mesh_rendering() +{ + mMemoryLayer->toggleTriangularMeshRendering( false ); + QVERIFY( mMemoryLayer->triangularMeshSymbol() == nullptr ); + QVERIFY( imageCheck( "quad_and_triangle_native_mesh" ) ); +} + +void TestQgsMeshRenderer::test_triangular_mesh_rendering() +{ + mMemoryLayer->toggleTriangularMeshRendering( true ); + QVERIFY( mMemoryLayer->triangularMeshSymbol() != nullptr ); + QVERIFY( imageCheck( "quad_and_triangle_triangular_mesh" ) ); +} + +QGSTEST_MAIN( TestQgsMeshRenderer ) +#include "testqgsmeshlayerrenderer.moc" diff --git a/tests/testdata/control_images/mesh/expected_quad_and_triangle_native_mesh/expected_quad_and_triangle_native_mesh.png b/tests/testdata/control_images/mesh/expected_quad_and_triangle_native_mesh/expected_quad_and_triangle_native_mesh.png new file mode 100644 index 00000000000..f2f30a58c10 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_quad_and_triangle_native_mesh/expected_quad_and_triangle_native_mesh.png differ diff --git a/tests/testdata/control_images/mesh/expected_quad_and_triangle_triangular_mesh/expected_quad_and_triangle_triangular_mesh.png b/tests/testdata/control_images/mesh/expected_quad_and_triangle_triangular_mesh/expected_quad_and_triangle_triangular_mesh.png new file mode 100644 index 00000000000..01142263691 Binary files /dev/null and b/tests/testdata/control_images/mesh/expected_quad_and_triangle_triangular_mesh/expected_quad_and_triangle_triangular_mesh.png differ diff --git a/tests/testdata/mesh/quad_and_triangle.2dm b/tests/testdata/mesh/quad_and_triangle.2dm new file mode 100644 index 00000000000..01a1aeaa95b --- /dev/null +++ b/tests/testdata/mesh/quad_and_triangle.2dm @@ -0,0 +1,8 @@ +MESH2D 1000.000 2000.000 0.000000 200 300 1.000 1.000 +ND 1 1000.000 2000.000 20.000 +ND 2 2000.000 2000.000 30.000 +ND 3 3000.000 2000.000 40.000 +ND 4 2000.000 3000.000 50.000 +ND 5 1000.000 3000.000 10.000 +E4Q 1 1 2 4 5 1 +E3T 2 2 3 4 1 diff --git a/tests/testdata/mesh/quad_and_triangle.txt b/tests/testdata/mesh/quad_and_triangle.txt new file mode 100644 index 00000000000..c29cd14f60c --- /dev/null +++ b/tests/testdata/mesh/quad_and_triangle.txt @@ -0,0 +1,8 @@ +1000.0, 2000.0 +2000.0, 2000.0 +3000.0, 2000.0 +2000.0, 3000.0 +1000.0, 3000.0 +--- +0, 1, 3, 4 +1, 2, 3