mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-22 00:06:12 -05:00
Initial work on vector tile layer support
This commit is contained in:
parent
d10267f3aa
commit
8105ad1fe5
@ -331,6 +331,11 @@ IF(WITH_CORE)
|
||||
MESSAGE (SEND_ERROR "sqlite3 dependency was not found!")
|
||||
ENDIF (NOT SQLITE3_FOUND)
|
||||
|
||||
FIND_PACKAGE(Protobuf REQUIRED) # for decoding of vector tiles in MVT format
|
||||
MESSAGE(STATUS "Found Protobuf: ${Protobuf_LIBRARIES}")
|
||||
FIND_PACKAGE(ZLIB REQUIRED) # for decompression of vector tiles in MBTiles file
|
||||
MESSAGE(STATUS "Found zlib: ${ZLIB_LIBRARIES}")
|
||||
|
||||
# optional
|
||||
IF (WITH_POSTGRESQL)
|
||||
FIND_PACKAGE(Postgres) # PostgreSQL provider
|
||||
|
@ -123,6 +123,7 @@ INCLUDE_DIRECTORIES(
|
||||
${CMAKE_SOURCE_DIR}/src/core/symbology
|
||||
${CMAKE_SOURCE_DIR}/src/core/classification
|
||||
${CMAKE_SOURCE_DIR}/src/core/validity
|
||||
${CMAKE_SOURCE_DIR}/src/core/vectortile
|
||||
${CMAKE_SOURCE_DIR}/src/plugins
|
||||
${CMAKE_SOURCE_DIR}/external
|
||||
${CMAKE_SOURCE_DIR}/external/nlohmann
|
||||
|
@ -534,3 +534,4 @@
|
||||
%Include auto_generated/gps/qgsgpsconnectionregistry.sip
|
||||
%Include auto_generated/symbology/qgsmasksymbollayer.sip
|
||||
%Include auto_generated/qgsuserprofile.sip
|
||||
%Include auto_generated/vectortile/qgsvectortilelayer.sip
|
||||
|
@ -105,7 +105,7 @@ astyleit() {
|
||||
|
||||
for f in "$@"; do
|
||||
case "$f" in
|
||||
src/plugins/grass/qtermwidget/*|external/o2/*|external/qt-unix-signals/*|external/rtree/*|external/astyle/*|external/kdbush/*|external/poly2tri/*|external/wintoast/*|external/qt3dextra-headers/*|external/meshOptimizer/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*)
|
||||
src/plugins/grass/qtermwidget/*|external/o2/*|external/qt-unix-signals/*|external/rtree/*|external/astyle/*|external/kdbush/*|external/poly2tri/*|external/wintoast/*|external/qt3dextra-headers/*|external/meshOptimizer/*|python/ext-libs/*|ui_*.py|*.astyle|tests/testdata/*|editors/*|src/core/vectortile/*.pb.*)
|
||||
echo -ne "$f skipped $elcr"
|
||||
continue
|
||||
;;
|
||||
|
@ -312,6 +312,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgsmapunitscale.cpp
|
||||
qgsmargins.cpp
|
||||
qgsmaskidprovider.cpp
|
||||
qgsmbtilesreader.cpp
|
||||
qgsmessagelog.cpp
|
||||
qgsmessageoutput.cpp
|
||||
qgsmimedatautils.cpp
|
||||
@ -395,6 +396,7 @@ SET(QGIS_CORE_SRCS
|
||||
qgstessellator.cpp
|
||||
qgstextrenderer.cpp
|
||||
qgstilecache.cpp
|
||||
qgstiles.cpp
|
||||
qgstolerance.cpp
|
||||
qgstracer.cpp
|
||||
qgstranslationcontext.cpp
|
||||
@ -635,6 +637,14 @@ SET(QGIS_CORE_SRCS
|
||||
validity/qgsvaliditycheckcontext.cpp
|
||||
validity/qgsvaliditycheckregistry.cpp
|
||||
|
||||
vectortile/qgsvectortilebasicrenderer.cpp
|
||||
vectortile/qgsvectortilelayer.cpp
|
||||
vectortile/qgsvectortilelayerrenderer.cpp
|
||||
vectortile/qgsvectortileloader.cpp
|
||||
vectortile/qgsvectortilemvtdecoder.cpp
|
||||
vectortile/qgsvectortileutils.cpp
|
||||
vectortile/vector_tile.pb.cc
|
||||
|
||||
${CMAKE_CURRENT_BINARY_DIR}/qgsexpression_texts.cpp
|
||||
|
||||
qgsuserprofile.cpp
|
||||
@ -833,6 +843,7 @@ SET(QGIS_CORE_HDRS
|
||||
qgsmapunitscale.h
|
||||
qgsmargins.h
|
||||
qgsmaskidprovider.h
|
||||
qgsmbtilesreader.h
|
||||
qgsmessagelog.h
|
||||
qgsmessageoutput.h
|
||||
qgsmimedatautils.h
|
||||
@ -924,6 +935,7 @@ SET(QGIS_CORE_HDRS
|
||||
qgstextrenderer.h
|
||||
qgsthreadingutils.h
|
||||
qgstilecache.h
|
||||
qgstiles.h
|
||||
qgstolerance.h
|
||||
qgstracer.h
|
||||
qgstrackedvectorlayertools.h
|
||||
@ -1319,6 +1331,14 @@ SET(QGIS_CORE_HDRS
|
||||
validity/qgsabstractvaliditycheck.h
|
||||
validity/qgsvaliditycheckcontext.h
|
||||
validity/qgsvaliditycheckregistry.h
|
||||
|
||||
vectortile/qgsvectortilebasicrenderer.h
|
||||
vectortile/qgsvectortilelayer.h
|
||||
vectortile/qgsvectortilelayerrenderer.h
|
||||
vectortile/qgsvectortileloader.h
|
||||
vectortile/qgsvectortilemvtdecoder.h
|
||||
vectortile/qgsvectortilerenderer.h
|
||||
vectortile/qgsvectortileutils.h
|
||||
)
|
||||
|
||||
SET(QGIS_CORE_PRIVATE_HDRS
|
||||
@ -1408,6 +1428,7 @@ INCLUDE_DIRECTORIES(
|
||||
symbology
|
||||
mesh
|
||||
validity
|
||||
vectortile
|
||||
${CMAKE_SOURCE_DIR}/external
|
||||
${CMAKE_SOURCE_DIR}/external/nlohmann
|
||||
${CMAKE_SOURCE_DIR}/external/kdbush/include
|
||||
@ -1429,6 +1450,8 @@ INCLUDE_DIRECTORIES(SYSTEM
|
||||
${QCA_INCLUDE_DIR}
|
||||
${QTKEYCHAIN_INCLUDE_DIR}
|
||||
${Qt5SerialPort_INCLUDE_DIRS}
|
||||
${Protobuf_INCLUDE_DIRS}
|
||||
${ZLIB_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
|
||||
@ -1563,6 +1586,8 @@ TARGET_LINK_LIBRARIES(qgis_core
|
||||
${SQLITE3_LIBRARY}
|
||||
${SPATIALITE_LIBRARY}
|
||||
${LIBZIP_LIBRARY}
|
||||
${Protobuf_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
)
|
||||
|
||||
IF (FORCE_STATIC_PROVIDERS)
|
||||
|
@ -16,6 +16,7 @@
|
||||
#ifndef QGSMBTILESREADER_H
|
||||
#define QGSMBTILESREADER_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
|
||||
#include "sqlite3.h"
|
||||
#include "qgssqliteutils.h"
|
||||
@ -23,7 +24,7 @@
|
||||
class QImage;
|
||||
class QgsRectangle;
|
||||
|
||||
class QgsMBTilesReader
|
||||
class CORE_EXPORT QgsMBTilesReader
|
||||
{
|
||||
public:
|
||||
explicit QgsMBTilesReader( const QString &filename );
|
259
src/core/vectortile/qgsvectortilebasicrenderer.cpp
Normal file
259
src/core/vectortile/qgsvectortilebasicrenderer.cpp
Normal file
@ -0,0 +1,259 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilebasicrenderer.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 "qgsvectortilebasicrenderer.h"
|
||||
|
||||
#include "qgslinesymbollayer.h"
|
||||
#include "qgssymbollayerutils.h"
|
||||
#include "qgsvectortileutils.h"
|
||||
|
||||
|
||||
QgsVectorTileBasicRendererStyle::QgsVectorTileBasicRendererStyle( const QString &stName, const QString &laName, QgsWkbTypes::GeometryType geomType )
|
||||
: mStyleName( stName )
|
||||
, mLayerName( laName )
|
||||
, mGeometryType( geomType )
|
||||
{
|
||||
}
|
||||
|
||||
QgsVectorTileBasicRendererStyle::QgsVectorTileBasicRendererStyle( const QgsVectorTileBasicRendererStyle &other )
|
||||
{
|
||||
operator=( other );
|
||||
}
|
||||
|
||||
QgsVectorTileBasicRendererStyle &QgsVectorTileBasicRendererStyle::operator=( const QgsVectorTileBasicRendererStyle &other )
|
||||
{
|
||||
mStyleName = other.mStyleName;
|
||||
mLayerName = other.mLayerName;
|
||||
mGeometryType = other.mGeometryType;
|
||||
mSymbol.reset( other.mSymbol ? other.mSymbol->clone() : nullptr );
|
||||
mEnabled = other.mEnabled;
|
||||
mExpression = other.mExpression;
|
||||
mMinZoomLevel = other.mMinZoomLevel;
|
||||
mMaxZoomLevel = other.mMaxZoomLevel;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRendererStyle::setSymbol( QgsSymbol *sym )
|
||||
{
|
||||
mSymbol.reset( sym );
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRendererStyle::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
elem.setAttribute( "name", mStyleName );
|
||||
elem.setAttribute( "layer", mLayerName );
|
||||
elem.setAttribute( "geometry", mGeometryType );
|
||||
elem.setAttribute( "enabled", mEnabled ? "1" : "0" );
|
||||
elem.setAttribute( "expression", mExpression );
|
||||
elem.setAttribute( "min-zoom", mMinZoomLevel );
|
||||
elem.setAttribute( "max-zoom", mMaxZoomLevel );
|
||||
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
QgsSymbolMap symbols;
|
||||
symbols[QStringLiteral( "0" )] = mSymbol.get();
|
||||
QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
|
||||
elem.appendChild( symbolsElem );
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRendererStyle::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
mStyleName = elem.attribute( "name" );
|
||||
mLayerName = elem.attribute( "layer" );
|
||||
mGeometryType = static_cast<QgsWkbTypes::GeometryType>( elem.attribute( "geometry" ).toInt() );
|
||||
mEnabled = elem.attribute( "enabled" ).toInt();
|
||||
mExpression = elem.attribute( "expression" );
|
||||
mMinZoomLevel = elem.attribute( "min-zoom" ).toInt();
|
||||
mMaxZoomLevel = elem.attribute( "max-zoom" ).toInt();
|
||||
|
||||
mSymbol.reset();
|
||||
QDomElement symbolsElem = elem.firstChildElement( QStringLiteral( "symbols" ) );
|
||||
if ( !symbolsElem.isNull() )
|
||||
{
|
||||
QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
|
||||
if ( !symbolMap.contains( QStringLiteral( "0" ) ) )
|
||||
{
|
||||
mSymbol.reset( symbolMap.take( QStringLiteral( "0" ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////
|
||||
|
||||
|
||||
QgsVectorTileBasicRenderer::QgsVectorTileBasicRenderer()
|
||||
{
|
||||
setDefaultStyle();
|
||||
}
|
||||
|
||||
QString QgsVectorTileBasicRenderer::type() const
|
||||
{
|
||||
return "basic";
|
||||
}
|
||||
|
||||
QgsVectorTileBasicRenderer *QgsVectorTileBasicRenderer::clone() const
|
||||
{
|
||||
QgsVectorTileBasicRenderer *r = new QgsVectorTileBasicRenderer;
|
||||
r->mStyles = mStyles;
|
||||
r->mStyles.detach(); // make a deep copy to make sure symbols get cloned
|
||||
return r;
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange )
|
||||
{
|
||||
Q_UNUSED( context )
|
||||
Q_UNUSED( tileRange )
|
||||
// figure out required fields for different layers
|
||||
for ( const QgsVectorTileBasicRendererStyle &layerStyle : qgis::as_const( mStyles ) )
|
||||
{
|
||||
if ( layerStyle.isActive( tileZoom ) && !layerStyle.filterExpression().isEmpty() )
|
||||
{
|
||||
QgsExpression expr( layerStyle.filterExpression() );
|
||||
mRequiredFields[layerStyle.layerName()].unite( expr.referencedColumns() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMap<QString, QSet<QString> > QgsVectorTileBasicRenderer::usedAttributes( const QgsRenderContext & )
|
||||
{
|
||||
return mRequiredFields;
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::stopRender( QgsRenderContext &context )
|
||||
{
|
||||
Q_UNUSED( context )
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context )
|
||||
{
|
||||
const QgsVectorTileFeatures tileData = tile.features;
|
||||
int zoomLevel = tile.id.zoomLevel();
|
||||
|
||||
for ( const QgsVectorTileBasicRendererStyle &layerStyle : qgis::as_const( mStyles ) )
|
||||
{
|
||||
if ( !layerStyle.isActive( zoomLevel ) )
|
||||
continue;
|
||||
|
||||
QgsFields fields = QgsVectorTileUtils::makeQgisFields( mRequiredFields[layerStyle.layerName()] );
|
||||
|
||||
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Layer" ) );
|
||||
scope->setFields( fields );
|
||||
context.expressionContext().appendScope( scope );
|
||||
|
||||
QgsExpression filterExpression( layerStyle.filterExpression() );
|
||||
filterExpression.prepare( &context.expressionContext() );
|
||||
|
||||
QgsSymbol *sym = layerStyle.symbol();
|
||||
sym->startRender( context, QgsFields() );
|
||||
if ( layerStyle.layerName().isEmpty() )
|
||||
{
|
||||
// matching all layers
|
||||
for ( QString layerName : tileData.keys() )
|
||||
{
|
||||
for ( const QgsFeature &f : tileData[layerName] )
|
||||
{
|
||||
scope->setFeature( f );
|
||||
if ( filterExpression.isValid() && !filterExpression.evaluate( &context.expressionContext() ).toBool() )
|
||||
continue;
|
||||
|
||||
if ( QgsWkbTypes::geometryType( f.geometry().wkbType() ) == layerStyle.geometryType() )
|
||||
sym->renderFeature( f, context );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( tileData.contains( layerStyle.layerName() ) )
|
||||
{
|
||||
// matching one particular layer
|
||||
for ( const QgsFeature &f : tileData[layerStyle.layerName()] )
|
||||
{
|
||||
scope->setFeature( f );
|
||||
if ( filterExpression.isValid() && !filterExpression.evaluate( &context.expressionContext() ).toBool() )
|
||||
continue;
|
||||
|
||||
if ( QgsWkbTypes::geometryType( f.geometry().wkbType() ) == layerStyle.geometryType() )
|
||||
sym->renderFeature( f, context );
|
||||
}
|
||||
}
|
||||
sym->stopRender( context );
|
||||
|
||||
delete context.expressionContext().popScope();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
QDomElement elemStyles = doc.createElement( "styles" );
|
||||
for ( const QgsVectorTileBasicRendererStyle &layerStyle : mStyles )
|
||||
{
|
||||
QDomElement elemStyle = doc.createElement( "style" );
|
||||
layerStyle.writeXml( elemStyle, context );
|
||||
elemStyles.appendChild( elemStyle );
|
||||
}
|
||||
elem.appendChild( elemStyles );
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
mStyles.clear();
|
||||
|
||||
QDomElement elemStyles = elem.firstChildElement( "styles" );
|
||||
QDomElement elemStyle = elemStyles.firstChildElement( "style" );
|
||||
while ( !elemStyle.isNull() )
|
||||
{
|
||||
QgsVectorTileBasicRendererStyle layerStyle;
|
||||
layerStyle.readXml( elemStyle, context );
|
||||
mStyles.append( layerStyle );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::setStyles( const QList<QgsVectorTileBasicRendererStyle> &styles )
|
||||
{
|
||||
mStyles = styles;
|
||||
}
|
||||
|
||||
QList<QgsVectorTileBasicRendererStyle> QgsVectorTileBasicRenderer::styles() const
|
||||
{
|
||||
return mStyles;
|
||||
}
|
||||
|
||||
void QgsVectorTileBasicRenderer::setDefaultStyle()
|
||||
{
|
||||
QColor color = Qt::blue;
|
||||
QColor polygonColor = color;
|
||||
polygonColor.setAlpha( 100 );
|
||||
QColor pointColor = Qt::red;
|
||||
|
||||
QgsFillSymbol *polygonSymbol = static_cast<QgsFillSymbol *>( QgsLineSymbol::defaultSymbol( QgsWkbTypes::PolygonGeometry ) );
|
||||
polygonSymbol->setColor( polygonColor );
|
||||
|
||||
QgsLineSymbol *lineSymbol = static_cast<QgsLineSymbol *>( QgsLineSymbol::defaultSymbol( QgsWkbTypes::LineGeometry ) );
|
||||
lineSymbol->setColor( color );
|
||||
|
||||
QgsMarkerSymbol *pointSymbol = static_cast<QgsMarkerSymbol *>( QgsLineSymbol::defaultSymbol( QgsWkbTypes::PointGeometry ) );
|
||||
pointSymbol->setColor( pointColor );
|
||||
|
||||
QgsVectorTileBasicRendererStyle st1( "polygons", QString(), QgsWkbTypes::PolygonGeometry );
|
||||
st1.setSymbol( polygonSymbol );
|
||||
|
||||
QgsVectorTileBasicRendererStyle st2( "lines", QString(), QgsWkbTypes::LineGeometry );
|
||||
st2.setSymbol( lineSymbol );
|
||||
|
||||
QgsVectorTileBasicRendererStyle st3( "points", QString(), QgsWkbTypes::PointGeometry );
|
||||
st3.setSymbol( pointSymbol );
|
||||
|
||||
QList<QgsVectorTileBasicRendererStyle> lst;
|
||||
lst << st1 << st2 << st3;
|
||||
setStyles( lst );
|
||||
}
|
156
src/core/vectortile/qgsvectortilebasicrenderer.h
Normal file
156
src/core/vectortile/qgsvectortilebasicrenderer.h
Normal file
@ -0,0 +1,156 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilebasicrenderer.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILEBASICRENDERER_H
|
||||
#define QGSVECTORTILEBASICRENDERER_H
|
||||
|
||||
#include "qgsvectortilerenderer.h"
|
||||
|
||||
class QgsLineSymbol;
|
||||
class QgsFillSymbol;
|
||||
class QgsMarkerSymbol;
|
||||
|
||||
class QgsSymbol;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Definition of map rendering of a subset of vector tile data. The subset of data is defined by:
|
||||
* 1. sub-layer name
|
||||
* 2. geometry type (a single sub-layer may have multiple geometry types)
|
||||
* 3. filter expression
|
||||
*
|
||||
* Renering is determined by the associated symbol (QgsSymbol). Symbol has to be of the same
|
||||
* type as the chosen geometryType() - i.e. QgsMarkerSymbol for points, QgsLineSymbol for linestrings
|
||||
* and QgsFillSymbol for polygons.
|
||||
*
|
||||
* It is possible to further constrain when this style is applied by setting a range of allowed
|
||||
* zoom levels, or by disabling it.
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
struct QgsVectorTileBasicRendererStyle
|
||||
{
|
||||
public:
|
||||
//! Constructs a style object
|
||||
QgsVectorTileBasicRendererStyle( const QString &stName = QString(), const QString &laName = QString(), QgsWkbTypes::GeometryType geomType = QgsWkbTypes::UnknownGeometry );
|
||||
|
||||
QgsVectorTileBasicRendererStyle( const QgsVectorTileBasicRendererStyle &other );
|
||||
QgsVectorTileBasicRendererStyle &operator=( const QgsVectorTileBasicRendererStyle &other );
|
||||
|
||||
//! Sets human readable name of this style
|
||||
void setStyleName( const QString &name ) { mStyleName = name; }
|
||||
//! Returns human readable name of this style
|
||||
QString styleName() const { return mStyleName; }
|
||||
|
||||
//! Sets name of the sub-layer to render (empty layer means that all layers match)
|
||||
void setLayerName( const QString &name ) { mLayerName = name; }
|
||||
//! Returns name of the sub-layer to render (empty layer means that all layers match)
|
||||
QString layerName() const { return mLayerName; }
|
||||
|
||||
//! Sets type of the geometry that will be used (point / line / polygon)
|
||||
void setGeometryType( QgsWkbTypes::GeometryType geomType ) { mGeometryType = geomType; }
|
||||
//! Returns type of the geometry that will be used (point / line / polygon)
|
||||
QgsWkbTypes::GeometryType geometryType() const { return mGeometryType; }
|
||||
|
||||
//! Sets filter expression (empty filter means that all features match)
|
||||
void setFilterExpression( const QString &expr ) { mExpression = expr; }
|
||||
//! Returns filter expression (empty filter means that all features match)
|
||||
QString filterExpression() const { return mExpression; }
|
||||
|
||||
//! Sets symbol for rendering. Takes ownership of the symbol.
|
||||
void setSymbol( QgsSymbol *sym );
|
||||
//! Returns symbol for rendering
|
||||
QgsSymbol *symbol() const { return mSymbol.get(); }
|
||||
|
||||
//! Sets whether this style is enabled (used for rendering)
|
||||
void setEnabled( bool enabled ) { mEnabled = enabled; }
|
||||
//! Returns whether this style is enabled (used for rendering)
|
||||
bool isEnabled() const { return mEnabled; }
|
||||
|
||||
//! Sets minimum zoom level index (negative number means no limit)
|
||||
void setMinZoomLevel( int minZoom ) { mMinZoomLevel = minZoom; }
|
||||
//! Returns minimum zoom level index (negative number means no limit)
|
||||
int minZoomLevel() const { return mMinZoomLevel; }
|
||||
|
||||
//! Sets maximum zoom level index (negative number means no limit)
|
||||
void setMaxZoomLevel( int maxZoom ) { mMaxZoomLevel = maxZoom; }
|
||||
//! Returns maxnimum zoom level index (negative number means no limit)
|
||||
int maxZoomLevel() const { return mMaxZoomLevel; }
|
||||
|
||||
//! Returns whether the style is active at given zoom level (also checks "enabled" flag)
|
||||
bool isActive( int zoomLevel ) const
|
||||
{
|
||||
return mEnabled && ( mMinZoomLevel == -1 || zoomLevel >= mMinZoomLevel ) && ( mMaxZoomLevel == -1 || zoomLevel <= mMaxZoomLevel );
|
||||
}
|
||||
|
||||
//! Writes object content to given DOM element
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const;
|
||||
//! Reads object content from given DOM element
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context );
|
||||
|
||||
private:
|
||||
QString mStyleName;
|
||||
QString mLayerName;
|
||||
QgsWkbTypes::GeometryType mGeometryType;
|
||||
std::unique_ptr<QgsSymbol> mSymbol;
|
||||
bool mEnabled = true;
|
||||
QString mExpression;
|
||||
int mMinZoomLevel = -1;
|
||||
int mMaxZoomLevel = -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* The default vector tile renderer implementation. It has an ordered list of "styles",
|
||||
* each defines a rendering rule.
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileBasicRenderer : public QgsVectorTileRenderer
|
||||
{
|
||||
public:
|
||||
//! Constructs renderer with some default styles
|
||||
QgsVectorTileBasicRenderer();
|
||||
|
||||
QString type() const override;
|
||||
QgsVectorTileBasicRenderer *clone() const override;
|
||||
void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange ) override;
|
||||
QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext & ) override;
|
||||
void stopRender( QgsRenderContext &context ) override;
|
||||
void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context ) override;
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
|
||||
//! Sets list of styles of the renderer
|
||||
void setStyles( const QList<QgsVectorTileBasicRendererStyle> &styles );
|
||||
//! Returns list of styles of the renderer
|
||||
QList<QgsVectorTileBasicRendererStyle> styles() const;
|
||||
|
||||
private:
|
||||
void setDefaultStyle();
|
||||
|
||||
private:
|
||||
//! List of rendering styles
|
||||
QList<QgsVectorTileBasicRendererStyle> mStyles;
|
||||
|
||||
// temporary bits
|
||||
|
||||
//! Names of required fields for each sub-layer (only valid between startRender/stopRender calls)
|
||||
QMap<QString, QSet<QString> > mRequiredFields;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSVECTORTILEBASICRENDERER_H
|
176
src/core/vectortile/qgsvectortilelayer.cpp
Normal file
176
src/core/vectortile/qgsvectortilelayer.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilelayer.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 "qgsvectortilelayer.h"
|
||||
|
||||
#include "qgsvectortilelayerrenderer.h"
|
||||
#include "qgsmbtilesreader.h"
|
||||
#include "qgsvectortilebasicrenderer.h"
|
||||
#include "qgsvectortileloader.h"
|
||||
|
||||
#include "qgsdatasourceuri.h"
|
||||
|
||||
QgsVectorTileLayer::QgsVectorTileLayer( const QString &uri, const QString &baseName )
|
||||
: QgsPluginLayer( "vector-tile", baseName )
|
||||
{
|
||||
mDataSource = uri;
|
||||
|
||||
QgsDataSourceUri dsUri;
|
||||
dsUri.setEncodedUri( uri );
|
||||
|
||||
mSourceType = dsUri.param( "type" );
|
||||
mSourcePath = dsUri.param( "url" );
|
||||
if ( mSourceType == "xyz" )
|
||||
{
|
||||
// online tiles
|
||||
mSourceMinZoom = 0;
|
||||
mSourceMaxZoom = 14;
|
||||
|
||||
setExtent( QgsRectangle( -20037508.3427892, -20037508.3427892, 20037508.3427892, 20037508.3427892 ) );
|
||||
}
|
||||
else if ( mSourceType == "mbtiles" )
|
||||
{
|
||||
QgsMBTilesReader reader( mSourcePath );
|
||||
if ( !reader.open() )
|
||||
{
|
||||
qDebug() << "failed to open MBTiles file:" << mSourcePath;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "name:" << reader.metadataValue( "name" );
|
||||
bool minZoomOk, maxZoomOk;
|
||||
int minZoom = reader.metadataValue( "minzoom" ).toInt( &minZoomOk );
|
||||
int maxZoom = reader.metadataValue( "maxzoom" ).toInt( &maxZoomOk );
|
||||
if ( minZoomOk )
|
||||
mSourceMinZoom = minZoom;
|
||||
if ( maxZoomOk )
|
||||
mSourceMaxZoom = maxZoom;
|
||||
qDebug() << "zoom range:" << mSourceMinZoom << mSourceMaxZoom;
|
||||
|
||||
QgsRectangle r = reader.extent();
|
||||
// TODO: reproject to EPSG:3857
|
||||
setExtent( r );
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: report error - unknown type
|
||||
return;
|
||||
}
|
||||
|
||||
setCrs( QgsCoordinateReferenceSystem( "EPSG:3857" ) );
|
||||
setValid( true );
|
||||
|
||||
// set a default renderer
|
||||
setRenderer( new QgsVectorTileBasicRenderer );
|
||||
}
|
||||
|
||||
QgsVectorTileLayer::~QgsVectorTileLayer() = default;
|
||||
|
||||
|
||||
QgsPluginLayer *QgsVectorTileLayer::clone() const
|
||||
{
|
||||
QgsVectorTileLayer *layer = new QgsVectorTileLayer( source(), name() );
|
||||
layer->setRenderer( renderer() ? renderer()->clone() : nullptr );
|
||||
return layer;
|
||||
}
|
||||
|
||||
QgsMapLayerRenderer *QgsVectorTileLayer::createMapRenderer( QgsRenderContext &rendererContext )
|
||||
{
|
||||
return new QgsVectorTileLayerRenderer( this, rendererContext );
|
||||
}
|
||||
|
||||
bool QgsVectorTileLayer::readXml( const QDomNode &layerNode, QgsReadWriteContext &context )
|
||||
{
|
||||
QString errorMsg;
|
||||
return readSymbology( layerNode, errorMsg, context );
|
||||
}
|
||||
|
||||
bool QgsVectorTileLayer::writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomElement mapLayerNode = layerNode.toElement();
|
||||
mapLayerNode.setAttribute( "type", "vector-tile" );
|
||||
|
||||
QString errorMsg;
|
||||
return writeSymbology( layerNode, doc, errorMsg, context );
|
||||
}
|
||||
|
||||
bool QgsVectorTileLayer::readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories )
|
||||
{
|
||||
QDomElement elem = node.toElement();
|
||||
|
||||
readCommonStyle( elem, context, categories );
|
||||
|
||||
QDomElement elemRenderer = elem.firstChildElement( "renderer" );
|
||||
if ( elemRenderer.isNull() )
|
||||
{
|
||||
errorMessage = "Missing <renderer> tag";
|
||||
return false;
|
||||
}
|
||||
QString rendererType = elemRenderer.attribute( "type" );
|
||||
QgsVectorTileRenderer *r = nullptr;
|
||||
if ( rendererType == "basic" )
|
||||
r = new QgsVectorTileBasicRenderer;
|
||||
//else if ( rendererType == "mapbox-gl" )
|
||||
// r = new MapboxGLStyleRenderer;
|
||||
else
|
||||
{
|
||||
errorMessage = "Unknown renderer type: " + rendererType;
|
||||
return false;
|
||||
}
|
||||
|
||||
r->readXml( elemRenderer, context );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsVectorTileLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context, QgsMapLayer::StyleCategories categories ) const
|
||||
{
|
||||
Q_UNUSED( errorMessage )
|
||||
QDomElement elem = node.toElement();
|
||||
|
||||
writeCommonStyle( elem, doc, context, categories );
|
||||
|
||||
if ( mRenderer )
|
||||
{
|
||||
QDomElement elemRenderer = doc.createElement( "renderer" );
|
||||
elemRenderer.setAttribute( "type", mRenderer->type() );
|
||||
mRenderer->writeXml( elemRenderer, context );
|
||||
elem.appendChild( elemRenderer );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void QgsVectorTileLayer::setTransformContext( const QgsCoordinateTransformContext &transformContext )
|
||||
{
|
||||
Q_UNUSED( transformContext )
|
||||
}
|
||||
|
||||
QByteArray QgsVectorTileLayer::getRawTile( QgsTileXYZ tileID )
|
||||
{
|
||||
QgsTileRange tileRange( tileID.column(), tileID.column(), tileID.row(), tileID.row() );
|
||||
QList<QgsVectorTileRawData> rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, tileID.zoomLevel(), QPointF(), tileRange );
|
||||
if ( rawTiles.isEmpty() )
|
||||
return QByteArray();
|
||||
return rawTiles.first().data;
|
||||
}
|
||||
|
||||
void QgsVectorTileLayer::setRenderer( QgsVectorTileRenderer *r )
|
||||
{
|
||||
mRenderer.reset( r );
|
||||
}
|
||||
|
||||
QgsVectorTileRenderer *QgsVectorTileLayer::renderer() const
|
||||
{
|
||||
return mRenderer.get();
|
||||
}
|
148
src/core/vectortile/qgsvectortilelayer.h
Normal file
148
src/core/vectortile/qgsvectortilelayer.h
Normal file
@ -0,0 +1,148 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilelayer.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILELAYER_H
|
||||
#define QGSVECTORTILELAYER_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis_sip.h"
|
||||
|
||||
#include "qgspluginlayer.h"
|
||||
|
||||
class QgsVectorTileRenderer;
|
||||
|
||||
struct QgsTileXYZ;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Implements a map layer that is dedicated to rendering of vector tiles.
|
||||
* Vector tiles compared to "ordinary" vector layers are pre-processed data
|
||||
* optimized for fast rendering. A dataset is provided with a series of zoom levels
|
||||
* for different map scales. Each zoom level has a matrix of tiles that contain
|
||||
* actual data. A single vector tile may be a a file stored on a local drive,
|
||||
* requested over HTTP request or retrieved from a database.
|
||||
*
|
||||
* Content of a vector tile is divided into one or more named sub-layers. Each such
|
||||
* sub-layer may contain many features which consist of geometry and attributes.
|
||||
* Contrary to traditional vector layers, these sub-layers do not need to have a rigid
|
||||
* schema where geometry type and attributes are the same for all features. A single
|
||||
* sub-layer may have multiple geometry types in a single tile or have some attributes
|
||||
* defined only at particular zoom levels.
|
||||
*
|
||||
* Vector tile layer currently does not use the concept of data providers that other
|
||||
* layer types use. The process of rendering of vector tiles looks like this:
|
||||
*
|
||||
* +--------+ +------+ +---------+
|
||||
* | DATA | | RAW | | DECODED |
|
||||
* | | --> LOADER --> | | --> DECODER --> | | --> RENDERER
|
||||
* | SOURCE | | TILE | | TILE |
|
||||
* +--------+ +------+ +---------+
|
||||
*
|
||||
* Data source is a place from where tiles are fetched from (URL for HTTP access, local
|
||||
* files, MBTiles file, GeoPackage file or others. Loader (QgsVectorTileLoader) class
|
||||
* takes care of loading data from the data source. The "raw tile" data is just a blob
|
||||
* (QByteArray) that is encoded in some way. There are multiple ways how vector tiles
|
||||
* are encoded just like there are different formats how to store images. For example,
|
||||
* tiles can be encoded using Mapbox Vector Tiles (MVT) format or in GeoJSON. Decoder
|
||||
* (QgsVectorTileDecoder) takes care of decoding raw tile data into QgsFeature objects.
|
||||
* A decoded tile is essentially an array of vector features for each sub-layer found
|
||||
* in the tile - this is what vector tile renderer (QgsVectorTileRenderer) expects
|
||||
* and does the map rendering.
|
||||
*
|
||||
* To construct a vector tile layer, it is best to use QgsDataSourceUri class and set
|
||||
* the following parameters to get a valid encoded URI:
|
||||
* - "type" - what kind of data source will be used
|
||||
* - "url" - URL or path of the data source (specific to each data source type, see below)
|
||||
*
|
||||
* Currently supported data source types:
|
||||
* - "xyz" - the "url" should be a template like http://example.com/{z}/{x}/{y}.pbf where
|
||||
* {x},{y},{z} will be replaced by tile coordinates
|
||||
* - "mbtiles" - tiles read from a MBTiles file (a SQLite database)
|
||||
*
|
||||
* Currently supported decoders:
|
||||
* - MVT - following Mapbox Vector Tiles specification
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class CORE_EXPORT QgsVectorTileLayer : public QgsPluginLayer
|
||||
{
|
||||
public:
|
||||
//! Constructs a new vector tile layer
|
||||
explicit QgsVectorTileLayer( const QString &path = QString(), const QString &baseName = QString() );
|
||||
~QgsVectorTileLayer();
|
||||
|
||||
// implementation of virtual functions from QgsMapLayer
|
||||
|
||||
QgsPluginLayer *clone() const override;
|
||||
|
||||
virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override;
|
||||
|
||||
virtual bool readXml( const QDomNode &layerNode, QgsReadWriteContext &context ) override;
|
||||
|
||||
virtual bool writeXml( QDomNode &layerNode, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
|
||||
|
||||
virtual bool readSymbology( const QDomNode &node, QString &errorMessage,
|
||||
QgsReadWriteContext &context, StyleCategories categories = AllStyleCategories ) override;
|
||||
|
||||
virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context,
|
||||
StyleCategories categories = AllStyleCategories ) const override;
|
||||
|
||||
virtual void setTransformContext( const QgsCoordinateTransformContext &transformContext ) override;
|
||||
|
||||
// new methods
|
||||
|
||||
//! Returns type of the data source
|
||||
QString sourceType() const { return mSourceType; }
|
||||
//! Returns URL/path of the data source (syntax different to each data source type)
|
||||
QString sourcePath() const { return mSourcePath; }
|
||||
|
||||
//! Returns minimum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int sourceMinZoom() const { return mSourceMinZoom; }
|
||||
//! Returns maximum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int sourceMaxZoom() const { return mSourceMaxZoom; }
|
||||
|
||||
/**
|
||||
* Fetches raw tile data for the give tile coordinates. If failed to fetch tile data,
|
||||
* it will return an empty byte array.
|
||||
*
|
||||
* \note This call may issue a network request (depending on the source type) and will block
|
||||
* the caller until the request is finished.
|
||||
*/
|
||||
QByteArray getRawTile( QgsTileXYZ tileID ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Sets renderer for the map layer.
|
||||
* \note Takes ownership of the passed renderer
|
||||
*/
|
||||
void setRenderer( QgsVectorTileRenderer *r ) SIP_SKIP;
|
||||
//! Returns currently assigned renderer
|
||||
QgsVectorTileRenderer *renderer() const SIP_SKIP;
|
||||
|
||||
private:
|
||||
//! Type of the data source
|
||||
QString mSourceType;
|
||||
//! URL/Path of the data source
|
||||
QString mSourcePath;
|
||||
//! Minimum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int mSourceMinZoom = -1;
|
||||
//! Maximum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int mSourceMaxZoom = -1;
|
||||
|
||||
//! Renderer assigned to the layer to draw map
|
||||
std::unique_ptr<QgsVectorTileRenderer> mRenderer;
|
||||
};
|
||||
|
||||
|
||||
#endif // QGSVECTORTILELAYER_H
|
184
src/core/vectortile/qgsvectortilelayerrenderer.cpp
Normal file
184
src/core/vectortile/qgsvectortilelayerrenderer.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilelayerrenderer.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 "qgsvectortilelayerrenderer.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgsfeedback.h"
|
||||
|
||||
#include "qgsvectortilemvtdecoder.h"
|
||||
#include "qgsvectortilelayer.h"
|
||||
#include "qgsvectortileloader.h"
|
||||
#include "qgsvectortileutils.h"
|
||||
|
||||
|
||||
QgsVectorTileLayerRenderer::QgsVectorTileLayerRenderer( QgsVectorTileLayer *layer, QgsRenderContext &context )
|
||||
: QgsMapLayerRenderer( layer->id(), &context )
|
||||
, mSourceType( layer->sourceType() )
|
||||
, mSourcePath( layer->sourcePath() )
|
||||
, mSourceMinZoom( layer->sourceMinZoom() )
|
||||
, mSourceMaxZoom( layer->sourceMaxZoom() )
|
||||
, mRenderer( layer->renderer()->clone() )
|
||||
, mFeedback( new QgsFeedback )
|
||||
{
|
||||
}
|
||||
|
||||
bool QgsVectorTileLayerRenderer::render()
|
||||
{
|
||||
QgsRenderContext &ctx = *renderContext();
|
||||
|
||||
if ( ctx.renderingStopped() )
|
||||
return false;
|
||||
|
||||
QElapsedTimer tTotal;
|
||||
tTotal.start();
|
||||
|
||||
qDebug() << "MVT rend" << ctx.extent().toString( -1 );
|
||||
|
||||
mTileZoom = QgsVectorTileUtils::scaleToZoomLevel( ctx.rendererScale(), mSourceMinZoom, mSourceMaxZoom );
|
||||
qDebug() << "MVT zoom level" << mTileZoom;
|
||||
|
||||
mTileMatrix = QgsTileMatrix::fromWebMercator( mTileZoom );
|
||||
|
||||
mTileRange = mTileMatrix.tileRangeFromExtent( ctx.extent() );
|
||||
qDebug() << "MVT tile range" << mTileRange.startColumn() << mTileRange.endColumn() << " | " << mTileRange.startRow() << mTileRange.endRow();
|
||||
|
||||
// view center is used to sort the order of tiles for fetching and rendering
|
||||
QPointF viewCenter = mTileMatrix.mapToTileCoordinates( ctx.extent().center() );
|
||||
|
||||
if ( !mTileRange.isValid() )
|
||||
{
|
||||
qDebug() << "outside of range";
|
||||
return true; // nothing to do
|
||||
}
|
||||
|
||||
bool isAsync = ( mSourceType == "xyz" );
|
||||
|
||||
std::unique_ptr<QgsVectorTileLoader> asyncLoader;
|
||||
QList<QgsVectorTileRawData> rawTiles;
|
||||
if ( !isAsync )
|
||||
{
|
||||
QElapsedTimer tFetch;
|
||||
tFetch.start();
|
||||
rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, mTileZoom, viewCenter, mTileRange );
|
||||
qDebug() << "FETCH TIME" << tFetch.elapsed() / 1000.;
|
||||
qDebug() << "fetched tiles:" << rawTiles.count();
|
||||
}
|
||||
else
|
||||
{
|
||||
asyncLoader.reset( new QgsVectorTileLoader( mSourcePath, mTileZoom, mTileRange, viewCenter, mFeedback.get() ) );
|
||||
QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, [this]( const QgsVectorTileRawData & rawTile )
|
||||
{
|
||||
qDebug() << "got async tile" << rawTile.id.column() << rawTile.id.row() << rawTile.id.zoomLevel();
|
||||
if ( !rawTile.data.isEmpty() )
|
||||
decodeAndDrawTile( rawTile );
|
||||
} );
|
||||
}
|
||||
|
||||
if ( ctx.renderingStopped() )
|
||||
return false;
|
||||
|
||||
mRenderer->startRender( *renderContext(), mTileZoom, mTileRange );
|
||||
|
||||
QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( *renderContext() );
|
||||
|
||||
QMap<QString, QgsFields> perLayerFields;
|
||||
for ( QString layerName : requiredFields.keys() )
|
||||
mPerLayerFields[layerName] = QgsVectorTileUtils::makeQgisFields( requiredFields[layerName] );
|
||||
|
||||
if ( !isAsync )
|
||||
{
|
||||
for ( QgsVectorTileRawData &rawTile : rawTiles )
|
||||
{
|
||||
if ( ctx.renderingStopped() )
|
||||
break;
|
||||
|
||||
decodeAndDrawTile( rawTile );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Block until tiles are fetched and rendered. If the rendering gets cancelled at some point,
|
||||
// the async loader will catch the signal, abort requests and return from downloadBlocking()
|
||||
asyncLoader->downloadBlocking();
|
||||
}
|
||||
|
||||
mRenderer->stopRender( ctx );
|
||||
|
||||
ctx.painter()->setClipping( false );
|
||||
|
||||
qDebug() << "DECODE TIME" << mTotalDecodeTime / 1000.;
|
||||
qDebug() << "DRAW TIME" << mTotalDrawTime / 1000.;
|
||||
qDebug() << "TOTAL TIME" << tTotal.elapsed() / 1000.;
|
||||
|
||||
return !ctx.renderingStopped();
|
||||
}
|
||||
|
||||
void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
|
||||
{
|
||||
QgsRenderContext &ctx = *renderContext();
|
||||
|
||||
qDebug() << "decoding tile " << rawTile.id.zoomLevel() << rawTile.id.column() << rawTile.id.row();
|
||||
|
||||
QElapsedTimer tLoad;
|
||||
tLoad.start();
|
||||
|
||||
// currently only MVT encoding supported
|
||||
QgsVectorTileMVTDecoder decoder;
|
||||
if ( !decoder.decode( rawTile.id, rawTile.data ) )
|
||||
{
|
||||
qDebug() << "Failed to parse raw tile data!";
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ctx.renderingStopped() )
|
||||
return;
|
||||
|
||||
QgsVectorTileRendererData tile;
|
||||
tile.id = rawTile.id;
|
||||
tile.features = decoder.layerFeatures( mPerLayerFields );
|
||||
|
||||
mTotalDecodeTime += tLoad.elapsed();
|
||||
|
||||
// calculate tile polygon in screen coordinates
|
||||
tile.tilePolygon = QgsVectorTileUtils::tilePolygon( rawTile.id, mTileMatrix, ctx.mapToPixel() );
|
||||
|
||||
if ( ctx.renderingStopped() )
|
||||
return;
|
||||
|
||||
// set up clipping so that rendering does not go behind tile's extent
|
||||
|
||||
ctx.painter()->setClipRegion( QRegion( tile.tilePolygon ) );
|
||||
|
||||
qDebug() << "drawing tile" << tile.id.zoomLevel() << tile.id.column() << tile.id.row();
|
||||
|
||||
QElapsedTimer tDraw;
|
||||
tDraw.start();
|
||||
|
||||
mRenderer->renderTile( tile, ctx );
|
||||
mTotalDrawTime += tDraw.elapsed();
|
||||
|
||||
if ( mDrawTileBoundaries )
|
||||
{
|
||||
ctx.painter()->setClipping( false );
|
||||
|
||||
QPen pen( Qt::red );
|
||||
pen.setWidth( 3 );
|
||||
ctx.painter()->setPen( pen );
|
||||
ctx.painter()->drawPolygon( tile.tilePolygon );
|
||||
}
|
||||
}
|
83
src/core/vectortile/qgsvectortilelayerrenderer.h
Normal file
83
src/core/vectortile/qgsvectortilelayerrenderer.h
Normal file
@ -0,0 +1,83 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilelayerrenderer.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILELAYERRENDERER_H
|
||||
#define QGSVECTORTILELAYERRENDERER_H
|
||||
|
||||
#include "qgsmaplayerrenderer.h"
|
||||
|
||||
class QgsVectorTileLayer;
|
||||
class QgsVectorTileRawData;
|
||||
|
||||
#include "qgsvectortilerenderer.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* This class provides map rendering functionality for vector tile layers.
|
||||
* In render() function (assumed to be run in a worker thread) it will:
|
||||
* 1. fetch vector tiles using QgsVectorTileLoader
|
||||
* 2. decode raw tiles into QgsFeature objects using QgsVectorTileDecoder
|
||||
* 3. render tiles using a class derived from QgsVectorTileRenderer
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileLayerRenderer : public QgsMapLayerRenderer
|
||||
{
|
||||
public:
|
||||
//! Creates the renderer. Always called from main thread, should copy whatever necessary from the layer
|
||||
QgsVectorTileLayerRenderer( QgsVectorTileLayer *layer, QgsRenderContext &context );
|
||||
|
||||
virtual bool render() override;
|
||||
virtual QgsFeedback *feedback() const override { return mFeedback.get(); }
|
||||
|
||||
private:
|
||||
void decodeAndDrawTile( const QgsVectorTileRawData &rawTile );
|
||||
|
||||
// data coming from the vector tile layer
|
||||
|
||||
//! Type of the source from which we will be loading tiles (e.g. "xyz" or "mbtiles")
|
||||
QString mSourceType;
|
||||
//! Path/URL of the source. Format depends on source type
|
||||
QString mSourcePath;
|
||||
//! Minimum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int mSourceMinZoom = -1;
|
||||
//! Maximum zoom level at which source has any valid tiles (negative = unconstrained)
|
||||
int mSourceMaxZoom = -1;
|
||||
//! Tile renderer object to do rendering of individual tiles
|
||||
std::unique_ptr<QgsVectorTileRenderer> mRenderer;
|
||||
|
||||
//! Whether to draw boundaries of tiles (useful for debugging)
|
||||
bool mDrawTileBoundaries = true;
|
||||
|
||||
// temporary data used during rendering process
|
||||
|
||||
//! Feedback object that may be used by the caller to cancel the rendering
|
||||
std::unique_ptr<QgsFeedback> mFeedback;
|
||||
//! Zoom level at which we will be rendering
|
||||
int mTileZoom = 0;
|
||||
//! Definition of the tile matrix for our zoom level
|
||||
QgsTileMatrix mTileMatrix;
|
||||
//!< Block of tiles we will be rendering in that zoom level
|
||||
QgsTileRange mTileRange;
|
||||
//! Cached QgsFields object for each sub-layer that will be rendered
|
||||
QMap<QString, QgsFields> mPerLayerFields;
|
||||
//! Counter of total elapsed time to decode tiles (ms)
|
||||
int mTotalDecodeTime = 0;
|
||||
//! Counter of total elapsed time to render tiles (ms)
|
||||
int mTotalDrawTime = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif // QGSVECTORTILELAYERRENDERER_H
|
265
src/core/vectortile/qgsvectortileloader.cpp
Normal file
265
src/core/vectortile/qgsvectortileloader.cpp
Normal file
@ -0,0 +1,265 @@
|
||||
/***************************************************************************
|
||||
qgsvectortileloader.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 "qgsvectortileloader.h"
|
||||
|
||||
#include <QtDebug>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "qgsblockingnetworkrequest.h"
|
||||
#include "qgsmbtilesreader.h"
|
||||
#include "qgsnetworkaccessmanager.h"
|
||||
#include "qgsvectortileutils.h"
|
||||
|
||||
QgsVectorTileLoader::QgsVectorTileLoader( const QString &uri, int zoomLevel, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback )
|
||||
: mEventLoop( new QEventLoop )
|
||||
, mFeedback( feedback )
|
||||
{
|
||||
if ( feedback )
|
||||
{
|
||||
connect( feedback, &QgsFeedback::canceled, this, &QgsVectorTileLoader::canceled, Qt::QueuedConnection );
|
||||
|
||||
// rendering could have been canceled before we started to listen to canceled() signal
|
||||
// so let's check before doing the download and maybe quit prematurely
|
||||
if ( feedback->isCanceled() )
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "starting loader";
|
||||
QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, zoomLevel );
|
||||
QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
|
||||
for ( QgsTileXYZ id : qgis::as_const( tiles ) )
|
||||
{
|
||||
loadFromNetworkAsync( id, uri );
|
||||
}
|
||||
}
|
||||
|
||||
QgsVectorTileLoader::~QgsVectorTileLoader()
|
||||
{
|
||||
qDebug() << "terminating loader";
|
||||
|
||||
if ( !mReplies.isEmpty() )
|
||||
{
|
||||
// this can happen when the loader is terminated without getting requests finalized
|
||||
// (e.g. downloadBlocking() was not called)
|
||||
canceled();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsVectorTileLoader::downloadBlocking()
|
||||
{
|
||||
qDebug() << "starting event loop" << mReplies.count() << "requests";
|
||||
|
||||
if ( mFeedback && mFeedback->isCanceled() )
|
||||
{
|
||||
qDebug() << "actually not - we were cancelled";
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
mEventLoop->exec( QEventLoop::ExcludeUserInputEvents );
|
||||
|
||||
qDebug() << "download blocking finished";
|
||||
|
||||
Q_ASSERT( mReplies.isEmpty() );
|
||||
}
|
||||
|
||||
void QgsVectorTileLoader::loadFromNetworkAsync( const QgsTileXYZ &id, const QString &requestUrl )
|
||||
{
|
||||
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id );
|
||||
QNetworkRequest request( url );
|
||||
// TODO: some extra headers? QgsSetRequestInitiatorClass / auth / "Accept" header
|
||||
request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ), id.column() );
|
||||
request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ), id.row() );
|
||||
request.setAttribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ), id.zoomLevel() );
|
||||
|
||||
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
|
||||
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
|
||||
|
||||
QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
|
||||
connect( reply, &QNetworkReply::finished, this, &QgsVectorTileLoader::tileReplyFinished );
|
||||
|
||||
mReplies << reply;
|
||||
}
|
||||
|
||||
void QgsVectorTileLoader::tileReplyFinished()
|
||||
{
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>( sender() );
|
||||
|
||||
int reqX = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 1 ) ).toInt();
|
||||
int reqY = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 2 ) ).toInt();
|
||||
int reqZ = reply->request().attribute( static_cast<QNetworkRequest::Attribute>( QNetworkRequest::User + 3 ) ).toInt();
|
||||
QgsTileXYZ tileID( reqX, reqY, reqZ );
|
||||
|
||||
if ( reply->error() == QNetworkReply::NoError )
|
||||
{
|
||||
// TODO: handle redirections?
|
||||
|
||||
qDebug() << "tile reply - all good!";
|
||||
QByteArray rawData = reply->readAll();
|
||||
mReplies.removeOne( reply );
|
||||
reply->deleteLater();
|
||||
|
||||
emit tileRequestFinished( QgsVectorTileRawData( tileID, rawData ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "tile reply - error! " << reply->errorString();
|
||||
mReplies.removeOne( reply );
|
||||
reply->deleteLater();
|
||||
|
||||
emit tileRequestFinished( QgsVectorTileRawData( tileID, QByteArray() ) );
|
||||
}
|
||||
|
||||
if ( mReplies.isEmpty() )
|
||||
{
|
||||
// exist the event loop
|
||||
QMetaObject::invokeMethod( mEventLoop.get(), "quit", Qt::QueuedConnection );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsVectorTileLoader::canceled()
|
||||
{
|
||||
qDebug() << "cancelling pending requests";
|
||||
const QList<QNetworkReply *> replies = mReplies;
|
||||
for ( QNetworkReply *reply : replies )
|
||||
{
|
||||
qDebug() << "aborting request";
|
||||
reply->abort();
|
||||
}
|
||||
}
|
||||
|
||||
//////
|
||||
|
||||
QList<QgsVectorTileRawData> QgsVectorTileLoader::blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, int zoomLevel, const QPointF &viewCenter, const QgsTileRange &range )
|
||||
{
|
||||
QList<QgsVectorTileRawData> rawTiles;
|
||||
|
||||
QgsMBTilesReader mbReader( sourcePath );
|
||||
bool isUrl = ( sourceType == "xyz" );
|
||||
if ( !isUrl )
|
||||
{
|
||||
bool res = mbReader.open();
|
||||
Q_ASSERT( res );
|
||||
}
|
||||
|
||||
QVector<QgsTileXYZ> tiles = QgsVectorTileUtils::tilesInRange( range, zoomLevel );
|
||||
QgsVectorTileUtils::sortTilesByDistanceFromCenter( tiles, viewCenter );
|
||||
for ( QgsTileXYZ id : qgis::as_const( tiles ) )
|
||||
{
|
||||
QByteArray rawData = isUrl ? loadFromNetwork( id, sourcePath ) : loadFromMBTiles( id, mbReader );
|
||||
if ( !rawData.isEmpty() )
|
||||
{
|
||||
rawTiles.append( QgsVectorTileRawData( id, rawData ) );
|
||||
}
|
||||
}
|
||||
return rawTiles;
|
||||
}
|
||||
|
||||
QByteArray QgsVectorTileLoader::loadFromNetwork( const QgsTileXYZ &id, const QString &requestUrl )
|
||||
{
|
||||
QString url = QgsVectorTileUtils::formatXYZUrlTemplate( requestUrl, id );
|
||||
QNetworkRequest nr;
|
||||
nr.setUrl( QUrl( url ) );
|
||||
QgsBlockingNetworkRequest req;
|
||||
qDebug() << "requestiong" << url;
|
||||
QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
|
||||
qDebug() << "get" << errCode;
|
||||
QgsNetworkReplyContent reply = req.reply();
|
||||
qDebug() << "content size" << reply.content().size();
|
||||
return reply.content();
|
||||
}
|
||||
|
||||
|
||||
QByteArray QgsVectorTileLoader::loadFromMBTiles( const QgsTileXYZ &id, QgsMBTilesReader &mbTileReader )
|
||||
{
|
||||
// MBTiles uses TMS specs with Y starting at the bottom while XYZ uses Y starting at the top
|
||||
int rowTMS = pow( 2, id.zoomLevel() ) - id.row() - 1;
|
||||
QByteArray gzippedTileData = mbTileReader.tileData( id.zoomLevel(), id.column(), rowTMS );
|
||||
if ( gzippedTileData.isEmpty() )
|
||||
{
|
||||
qDebug() << "Failed to get tile" << id.zoomLevel() << id.column() << id.row();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// TODO: check format is "pbf"
|
||||
|
||||
QByteArray data;
|
||||
if ( !decodeGzip( gzippedTileData, data ) )
|
||||
{
|
||||
qDebug() << "failed to decompress tile" << id.zoomLevel() << id.column() << id.row();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
qDebug() << "tile blob size" << gzippedTileData.size() << " -> uncompressed size" << data.size();
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
bool QgsVectorTileLoader::decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut )
|
||||
{
|
||||
unsigned char *bytesInPtr = reinterpret_cast<unsigned char *>( const_cast<char *>( bytesIn.constData() ) );
|
||||
uint bytesInLeft = static_cast<uint>( bytesIn.count() );
|
||||
|
||||
const uint CHUNK = 16384;
|
||||
unsigned char out[CHUNK];
|
||||
const int DEC_MAGIC_NUM_FOR_GZIP = 16;
|
||||
|
||||
// allocate inflate state
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
|
||||
int ret = inflateInit2( &strm, MAX_WBITS + DEC_MAGIC_NUM_FOR_GZIP );
|
||||
if ( ret != Z_OK )
|
||||
return false;
|
||||
|
||||
while ( ret != Z_STREAM_END ) // done when inflate() says it's done
|
||||
{
|
||||
// prepare next chunk
|
||||
uint bytesToProcess = std::min( CHUNK, bytesInLeft );
|
||||
strm.next_in = bytesInPtr;
|
||||
strm.avail_in = bytesToProcess;
|
||||
bytesInPtr += bytesToProcess;
|
||||
bytesInLeft -= bytesToProcess;
|
||||
|
||||
if ( bytesToProcess == 0 )
|
||||
break; // we end with an error - no more data but inflate() wants more data
|
||||
|
||||
// run inflate() on input until output buffer not full
|
||||
do
|
||||
{
|
||||
strm.avail_out = CHUNK;
|
||||
strm.next_out = out;
|
||||
ret = inflate( &strm, Z_NO_FLUSH );
|
||||
Q_ASSERT( ret != Z_STREAM_ERROR ); // state not clobbered
|
||||
if ( ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR )
|
||||
{
|
||||
inflateEnd( &strm );
|
||||
return false;
|
||||
}
|
||||
unsigned have = CHUNK - strm.avail_out;
|
||||
bytesOut.append( QByteArray::fromRawData( reinterpret_cast<const char *>( out ), static_cast<int>( have ) ) );
|
||||
}
|
||||
while ( strm.avail_out == 0 );
|
||||
}
|
||||
|
||||
inflateEnd( &strm );
|
||||
return ret == Z_STREAM_END;
|
||||
}
|
100
src/core/vectortile/qgsvectortileloader.h
Normal file
100
src/core/vectortile/qgsvectortileloader.h
Normal file
@ -0,0 +1,100 @@
|
||||
/***************************************************************************
|
||||
qgsvectortileloader.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILELOADER_H
|
||||
#define QGSVECTORTILELOADER_H
|
||||
|
||||
class QByteArray;
|
||||
|
||||
#include "qgsvectortilerenderer.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Keeps track of raw tile data that need to be decoded
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileRawData
|
||||
{
|
||||
public:
|
||||
QgsVectorTileRawData( QgsTileXYZ tileID = QgsTileXYZ(), const QByteArray &raw = QByteArray() )
|
||||
: id( tileID ), data( raw ) {}
|
||||
|
||||
//! Tile position in tile matrix set
|
||||
QgsTileXYZ id;
|
||||
//! Raw tile data
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
|
||||
class QNetworkReply;
|
||||
class QEventLoop;
|
||||
|
||||
class QgsMBTilesReader;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* The loader class takes care of loading raw vector tile data from a tile source.
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
//! Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched.
|
||||
static QList<QgsVectorTileRawData> blockingFetchTileRawData( const QString &sourceType, const QString &sourcePath, int zoomLevel, const QPointF &viewCenter, const QgsTileRange &range );
|
||||
|
||||
//! Returns raw tile data for a single tile, doing a HTTP request. Block the caller until tile data are downloaded.
|
||||
static QByteArray loadFromNetwork( const QgsTileXYZ &id, const QString &requestUrl );
|
||||
//! Returns raw tile data for a signle tile loaded from MBTiles file
|
||||
static QByteArray loadFromMBTiles( const QgsTileXYZ &id, QgsMBTilesReader &mbTileReader );
|
||||
//! Decodes gzip byte stream, returns true on success
|
||||
static bool decodeGzip( const QByteArray &bytesIn, QByteArray &bytesOut );
|
||||
|
||||
//
|
||||
// non-static stuff
|
||||
//
|
||||
|
||||
//! Constructs tile loader for doing asynchronous requests and starts network requests
|
||||
QgsVectorTileLoader( const QString &uri, int zoomLevel, const QgsTileRange &range, const QPointF &viewCenter, QgsFeedback *feedback );
|
||||
~QgsVectorTileLoader();
|
||||
|
||||
//! Blocks the caller until all asynchronous requests are finished (with a success or a failure)
|
||||
void downloadBlocking();
|
||||
|
||||
private:
|
||||
void loadFromNetworkAsync( const QgsTileXYZ &id, const QString &requestUrl );
|
||||
|
||||
private slots:
|
||||
void tileReplyFinished();
|
||||
void canceled();
|
||||
|
||||
signals:
|
||||
//! Emitted when a tile request has finished. If a tile request has failed, the returned raw tile byte array is empty.
|
||||
void tileRequestFinished( const QgsVectorTileRawData &rawTile );
|
||||
|
||||
private:
|
||||
//! Event loop used for blocking download
|
||||
std::unique_ptr<QEventLoop> mEventLoop;
|
||||
//! Feedback object that allows cancellation of pending requests
|
||||
QgsFeedback *mFeedback;
|
||||
//! Running tile requests
|
||||
QList<QNetworkReply *> mReplies;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSVECTORTILELOADER_H
|
322
src/core/vectortile/qgsvectortilemvtdecoder.cpp
Normal file
322
src/core/vectortile/qgsvectortilemvtdecoder.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilemvtdecoder.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 <string>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "qgsvectortilemvtdecoder.h"
|
||||
|
||||
#include "qgsvectortilelayerrenderer.h"
|
||||
#include "qgsvectortileutils.h"
|
||||
|
||||
#include "qgsmultipoint.h"
|
||||
#include "qgslinestring.h"
|
||||
#include "qgsmultilinestring.h"
|
||||
#include "qgsmultipolygon.h"
|
||||
#include "qgspolygon.h"
|
||||
|
||||
|
||||
inline bool _isExteriorRing( const QVector<QgsPoint> &pts )
|
||||
{
|
||||
// Exterior rings have POSITIVE area while interior rings have NEGATIVE area
|
||||
// when calculated with https://en.wikipedia.org/wiki/Shoelace_formula
|
||||
// The orientation of axes is that X grows to the right and Y grows to the bottom.
|
||||
// the input data are expected to form a closed ring, i.e. first pt == last pt.
|
||||
|
||||
double total = 0.0;
|
||||
const QgsPoint *ptsPtr = pts.constData();
|
||||
int count = pts.count();
|
||||
for ( int i = 0; i < count - 1; i++ )
|
||||
{
|
||||
double val = ( pts[i + 1].x() - ptsPtr[i].x() ) * ( ptsPtr[i + 1].y() + pts[i].y() );
|
||||
//double val = ptsPtr[i].x() * (-ptsPtr[i+1].y()) - ptsPtr[i+1].x() * (-ptsPtr[i].y()); // gives the same result
|
||||
total += val;
|
||||
}
|
||||
return total >= 0;
|
||||
}
|
||||
|
||||
|
||||
bool QgsVectorTileMVTDecoder::decode( QgsTileXYZ tileID, const QByteArray &rawTileData )
|
||||
{
|
||||
if ( !tile.ParseFromArray( rawTileData.constData(), rawTileData.count() ) )
|
||||
return false;
|
||||
|
||||
mTileID = tileID;
|
||||
|
||||
mLayerNameToIndex.clear();
|
||||
for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
|
||||
{
|
||||
const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
|
||||
QString layerName = layer.name().c_str();
|
||||
mLayerNameToIndex[layerName] = layerNum;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList QgsVectorTileMVTDecoder::layers() const
|
||||
{
|
||||
QStringList layerNames;
|
||||
for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
|
||||
{
|
||||
const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
|
||||
QString layerName = layer.name().c_str();
|
||||
layerNames << layerName;
|
||||
}
|
||||
return layerNames;
|
||||
}
|
||||
|
||||
QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName ) const
|
||||
{
|
||||
if ( !mLayerNameToIndex.contains( layerName ) )
|
||||
return QStringList();
|
||||
|
||||
const ::vector_tile::Tile_Layer &layer = tile.layers( mLayerNameToIndex[layerName] );
|
||||
QStringList fieldNames;
|
||||
for ( int i = 0; i < layer.keys_size(); ++i )
|
||||
{
|
||||
QString fieldName = layer.keys( i ).c_str();
|
||||
fieldNames << fieldName;
|
||||
}
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields ) const
|
||||
{
|
||||
QgsVectorTileFeatures features;
|
||||
|
||||
int numTiles = static_cast<int>( pow( 2, mTileID.zoomLevel() ) ); // assuming we won't ever go over 30 zoom levels
|
||||
double z0xMin = -20037508.3427892, z0yMin = -20037508.3427892;
|
||||
double z0xMax = 20037508.3427892, z0yMax = 20037508.3427892;
|
||||
double tileDX = ( z0xMax - z0xMin ) / numTiles;
|
||||
double tileDY = ( z0yMax - z0yMin ) / numTiles;
|
||||
double tileXMin = z0xMin + mTileID.column() * tileDX;
|
||||
double tileYMax = z0yMax - mTileID.row() * tileDY;
|
||||
|
||||
for ( int layerNum = 0; layerNum < tile.layers_size(); layerNum++ )
|
||||
{
|
||||
const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );
|
||||
|
||||
QString layerName = layer.name().c_str();
|
||||
QVector<QgsFeature> layerFeatures;
|
||||
QgsFields layerFields = perLayerFields[layerName];
|
||||
|
||||
// figure out how field indexes in MVT encoding map to field indexes in QgsFields (we may not use all available fields)
|
||||
QHash<int, int> tagKeyIndexToFieldIndex;
|
||||
for ( int i = 0; i < layer.keys_size(); ++i )
|
||||
{
|
||||
int fieldIndex = layerFields.indexOf( layer.keys( i ).c_str() );
|
||||
if ( fieldIndex != -1 )
|
||||
tagKeyIndexToFieldIndex.insert( i, fieldIndex );
|
||||
}
|
||||
|
||||
// go through features of a layer
|
||||
for ( int featureNum = 0; featureNum < layer.features_size(); featureNum++ )
|
||||
{
|
||||
const ::vector_tile::Tile_Feature &feature = layer.features( featureNum );
|
||||
|
||||
QgsFeature f( layerFields, static_cast<QgsFeatureId>( feature.id() ) );
|
||||
|
||||
// initialize all fields to empty string.
|
||||
// TODO: this should not be necessary, but some rules don't like NULL
|
||||
for ( int i = 0; i < layerFields.count(); ++i )
|
||||
f.setAttribute( i, QStringLiteral( "" ) );
|
||||
|
||||
//
|
||||
// parse attributes
|
||||
//
|
||||
|
||||
for ( int tagNum = 0; tagNum < feature.tags_size(); tagNum += 2 )
|
||||
{
|
||||
int keyIndex = static_cast<int>( feature.tags( tagNum ) );
|
||||
int fieldIndex = tagKeyIndexToFieldIndex.value( keyIndex, -1 );
|
||||
if ( fieldIndex == -1 )
|
||||
continue;
|
||||
|
||||
int valueIndex = static_cast<int>( feature.tags( tagNum + 1 ) );
|
||||
const ::vector_tile::Tile_Value &value = layer.values( valueIndex );
|
||||
|
||||
if ( value.has_string_value() )
|
||||
f.setAttribute( fieldIndex, QString::fromStdString( value.string_value() ) );
|
||||
else if ( value.has_float_value() )
|
||||
f.setAttribute( fieldIndex, static_cast<double>( value.float_value() ) );
|
||||
else if ( value.has_double_value() )
|
||||
f.setAttribute( fieldIndex, value.double_value() );
|
||||
else if ( value.has_int_value() )
|
||||
f.setAttribute( fieldIndex, static_cast<int>( value.int_value() ) );
|
||||
else if ( value.has_uint_value() )
|
||||
f.setAttribute( fieldIndex, static_cast<int>( value.uint_value() ) );
|
||||
else if ( value.has_sint_value() )
|
||||
f.setAttribute( fieldIndex, static_cast<int>( value.sint_value() ) );
|
||||
else if ( value.has_bool_value() )
|
||||
f.setAttribute( fieldIndex, static_cast<int>( value.bool_value() ) ); // or keep it bool? (do we have good support for that?)
|
||||
else
|
||||
{
|
||||
// TODO: report - should not happen
|
||||
Q_ASSERT( false );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// parse geometry
|
||||
//
|
||||
|
||||
int extent = static_cast<int>( layer.extent() );
|
||||
int cursorx = 0, cursory = 0;
|
||||
|
||||
QVector<QgsPoint *> outputPoints; // for point/multi-point
|
||||
QVector<QgsLineString *> outputLinestrings; // for linestring/multi-linestring
|
||||
QVector<QgsPolygon *> outputPolygons;
|
||||
QVector<QgsPoint> tmpPoints;
|
||||
|
||||
for ( int i = 0; i < feature.geometry_size(); i ++ )
|
||||
{
|
||||
unsigned g = feature.geometry( i );
|
||||
unsigned cmdId = g & 0x7;
|
||||
unsigned cmdCount = g >> 3;
|
||||
if ( cmdId == 1 ) // MoveTo
|
||||
{
|
||||
for ( unsigned j = 0; j < cmdCount; j++ )
|
||||
{
|
||||
unsigned v = feature.geometry( i + 1 );
|
||||
unsigned w = feature.geometry( i + 2 );
|
||||
int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
|
||||
int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
|
||||
cursorx += dx;
|
||||
cursory += dy;
|
||||
double px = tileXMin + tileDX * double( cursorx ) / double( extent );
|
||||
double py = tileYMax - tileDY * double( cursory ) / double( extent );
|
||||
|
||||
if ( feature.type() == vector_tile::Tile_GeomType_POINT )
|
||||
{
|
||||
outputPoints.append( new QgsPoint( px, py ) );
|
||||
}
|
||||
else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
|
||||
{
|
||||
if ( tmpPoints.size() > 0 )
|
||||
{
|
||||
outputLinestrings.append( new QgsLineString( tmpPoints ) );
|
||||
tmpPoints.clear();
|
||||
}
|
||||
tmpPoints.append( QgsPoint( px, py ) );
|
||||
}
|
||||
else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
|
||||
{
|
||||
tmpPoints.append( QgsPoint( px, py ) );
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
else if ( cmdId == 2 ) // LineTo
|
||||
{
|
||||
for ( unsigned j = 0; j < cmdCount; j++ )
|
||||
{
|
||||
unsigned v = feature.geometry( i + 1 );
|
||||
unsigned w = feature.geometry( i + 2 );
|
||||
int dx = ( ( v >> 1 ) ^ ( -( v & 1 ) ) );
|
||||
int dy = ( ( w >> 1 ) ^ ( -( w & 1 ) ) );
|
||||
cursorx += dx;
|
||||
cursory += dy;
|
||||
double px = tileXMin + tileDX * double( cursorx ) / double( extent );
|
||||
double py = tileYMax - tileDY * double( cursory ) / double( extent );
|
||||
|
||||
tmpPoints.push_back( QgsPoint( px, py ) );
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
else if ( cmdId == 7 ) // ClosePath
|
||||
{
|
||||
if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
|
||||
{
|
||||
tmpPoints.append( tmpPoints.first() ); // close the ring
|
||||
|
||||
if ( _isExteriorRing( tmpPoints ) )
|
||||
{
|
||||
// start a new polygon
|
||||
QgsPolygon *p = new QgsPolygon;
|
||||
p->setExteriorRing( new QgsLineString( tmpPoints ) );
|
||||
outputPolygons.append( p );
|
||||
tmpPoints.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// interior ring (hole)
|
||||
Q_ASSERT( outputPolygons.count() != 0 ); // TODO: better error handling
|
||||
outputPolygons[outputPolygons.count() - 1]->addInteriorRing( new QgsLineString( tmpPoints ) );
|
||||
tmpPoints.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "huh?"; // TODO: handle properly
|
||||
}
|
||||
}
|
||||
|
||||
QString geomType;
|
||||
if ( feature.type() == vector_tile::Tile_GeomType_POINT )
|
||||
{
|
||||
geomType = QStringLiteral( "Point" );
|
||||
if ( outputPoints.count() == 1 )
|
||||
f.setGeometry( QgsGeometry( outputPoints[0] ) );
|
||||
else
|
||||
{
|
||||
QgsMultiPoint *mp = new QgsMultiPoint;
|
||||
for ( int k = 0; k < outputPoints.count(); ++k )
|
||||
mp->addGeometry( outputPoints[k] );
|
||||
f.setGeometry( QgsGeometry( mp ) );
|
||||
}
|
||||
}
|
||||
else if ( feature.type() == vector_tile::Tile_GeomType_LINESTRING )
|
||||
{
|
||||
geomType = QStringLiteral( "LineString" );
|
||||
|
||||
// finish the linestring we have started
|
||||
outputLinestrings.append( new QgsLineString( tmpPoints ) );
|
||||
|
||||
if ( outputLinestrings.count() == 1 )
|
||||
f.setGeometry( QgsGeometry( outputLinestrings[0] ) );
|
||||
else
|
||||
{
|
||||
QgsMultiLineString *mls = new QgsMultiLineString;
|
||||
for ( int k = 0; k < outputLinestrings.count(); ++k )
|
||||
mls->addGeometry( outputLinestrings[k] );
|
||||
f.setGeometry( QgsGeometry( mls ) );
|
||||
}
|
||||
}
|
||||
else if ( feature.type() == vector_tile::Tile_GeomType_POLYGON )
|
||||
{
|
||||
geomType = QStringLiteral( "Polygon" );
|
||||
|
||||
if ( outputPolygons.count() == 1 )
|
||||
f.setGeometry( QgsGeometry( outputPolygons[0] ) );
|
||||
else
|
||||
{
|
||||
QgsMultiPolygon *mpl = new QgsMultiPolygon;
|
||||
for ( int k = 0; k < outputPolygons.count(); ++k )
|
||||
mpl->addGeometry( outputPolygons[k] );
|
||||
f.setGeometry( QgsGeometry( mpl ) );
|
||||
}
|
||||
}
|
||||
|
||||
f.setAttribute( "ty_pe", geomType );
|
||||
|
||||
layerFeatures.append( f );
|
||||
}
|
||||
|
||||
features[layerName] = layerFeatures;
|
||||
}
|
||||
return features;
|
||||
}
|
56
src/core/vectortile/qgsvectortilemvtdecoder.h
Normal file
56
src/core/vectortile/qgsvectortilemvtdecoder.h
Normal file
@ -0,0 +1,56 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilemvtdecoder.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILEMVTDECODER_H
|
||||
#define QGSVECTORTILEMVTDECODER_H
|
||||
|
||||
class QgsFeature;
|
||||
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
|
||||
#include "vector_tile.pb.h"
|
||||
|
||||
#include "qgsvectortilerenderer.h"
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* This class is responsible for decoding raw tile data written with Mapbox Vector Tiles encoding.
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileMVTDecoder
|
||||
{
|
||||
public:
|
||||
|
||||
//! Tries to decode raw tile data, returns true on success
|
||||
bool decode( QgsTileXYZ tileID, const QByteArray &rawTileData );
|
||||
|
||||
//! Returns a list of sub-layer names in a tile. It can only be called after a successful decode()
|
||||
QStringList layers() const;
|
||||
|
||||
//! Returns a list of all field names in a tile. It can only be called after a successful decode()
|
||||
QStringList layerFieldNames( const QString &layerName ) const;
|
||||
|
||||
//! Returns decoded features grouped by sub-layers. It can only be called after a successful decode()
|
||||
QgsVectorTileFeatures layerFeatures( const QMap<QString, QgsFields> &perLayerFields ) const;
|
||||
|
||||
private:
|
||||
vector_tile::Tile tile;
|
||||
QgsTileXYZ mTileID;
|
||||
QMap<QString, int> mLayerNameToIndex;
|
||||
};
|
||||
|
||||
#endif // QGSVECTORTILEMVTDECODER_H
|
90
src/core/vectortile/qgsvectortilerenderer.h
Normal file
90
src/core/vectortile/qgsvectortilerenderer.h
Normal file
@ -0,0 +1,90 @@
|
||||
/***************************************************************************
|
||||
qgsvectortilerenderer.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILERENDERER_H
|
||||
#define QGSVECTORTILERENDERER_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
|
||||
#include "qgsfeature.h"
|
||||
|
||||
#include "qgstiles.h"
|
||||
|
||||
class QgsRenderContext;
|
||||
|
||||
//! Features of a vector tile, grouped by sub-layer names (key of the map)
|
||||
typedef QMap<QString, QVector<QgsFeature> > QgsVectorTileFeatures;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Contains decoded features of a single vector tile and any other data necessary
|
||||
* for rendering of it.
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
struct QgsVectorTileRendererData
|
||||
{
|
||||
//! Position of the tile in the tile matrix set
|
||||
QgsTileXYZ id;
|
||||
//! Features of the tile grouped into sub-layers
|
||||
QgsVectorTileFeatures features;
|
||||
//! Polygon (made out of four corners of the tile) in screen coordinates calculated from render context
|
||||
QPolygon tilePolygon;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Abstract base class for all vector tile renderer implementations.
|
||||
*
|
||||
* For rendering it is expected that client code calls:
|
||||
* 1. startRender() to prepare renderer
|
||||
* 2. renderTile() for each tile
|
||||
* 3. stopRender() to clean up renderer and free resources
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class CORE_EXPORT QgsVectorTileRenderer
|
||||
{
|
||||
public:
|
||||
virtual ~QgsVectorTileRenderer() = default;
|
||||
|
||||
//! Returns unique type name of the renderer implementation
|
||||
virtual QString type() const = 0;
|
||||
|
||||
//! Returns a clone of the renderer
|
||||
virtual QgsVectorTileRenderer *clone() const = 0;
|
||||
|
||||
//! Initializes rendering. It should be paired with a stopRender() call.
|
||||
virtual void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange ) = 0;
|
||||
|
||||
//! Returns field names of sub-layers that will be used for rendering. Must be called between startRender/stopRender.
|
||||
virtual QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext & ) = 0;
|
||||
|
||||
//! Finishes rendering and cleans up any resources
|
||||
virtual void stopRender( QgsRenderContext &context ) = 0;
|
||||
|
||||
//! Renders given vector tile. Must be called between startRender/stopRender.
|
||||
virtual void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context ) = 0;
|
||||
|
||||
//! Writes renderer's properties to given XML element
|
||||
virtual void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const = 0;
|
||||
//! Reads renderer's properties from given XML element
|
||||
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) = 0;
|
||||
//! Resolves references to other objects - second phase of loading - after readXml()
|
||||
virtual void resolveReferences( const QgsProject &project ) { Q_UNUSED( project ) }
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSVECTORTILERENDERER_H
|
171
src/core/vectortile/qgsvectortileutils.cpp
Normal file
171
src/core/vectortile/qgsvectortileutils.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
/***************************************************************************
|
||||
qgsvectortileutils.cpp
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 "qgsvectortileutils.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <QPolygon>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "qgscoordinatetransform.h"
|
||||
#include "qgsgeometrycollection.h"
|
||||
#include "qgsfields.h"
|
||||
#include "qgsmaptopixel.h"
|
||||
#include "qgsrectangle.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
|
||||
#include "qgsvectortilemvtdecoder.h"
|
||||
#include "qgsvectortilelayer.h"
|
||||
#include "qgsvectortilerenderer.h"
|
||||
|
||||
|
||||
|
||||
QPolygon QgsVectorTileUtils::tilePolygon( QgsTileXYZ id, const QgsTileMatrix &tm, const QgsMapToPixel &mtp )
|
||||
{
|
||||
QgsRectangle r = tm.tileExtent( id );
|
||||
QgsPointXY p00a = mtp.transform( r.xMinimum(), r.yMinimum() );
|
||||
QgsPointXY p11a = mtp.transform( r.xMaximum(), r.yMaximum() );
|
||||
QgsPointXY p01a = mtp.transform( r.xMinimum(), r.yMaximum() );
|
||||
QgsPointXY p10a = mtp.transform( r.xMaximum(), r.yMinimum() );
|
||||
QPolygon path;
|
||||
path << p00a.toQPointF().toPoint();
|
||||
path << p01a.toQPointF().toPoint();
|
||||
path << p11a.toQPointF().toPoint();
|
||||
path << p10a.toQPointF().toPoint();
|
||||
return path;
|
||||
}
|
||||
|
||||
QgsFields QgsVectorTileUtils::makeQgisFields( QSet<QString> flds )
|
||||
{
|
||||
QgsFields fields;
|
||||
for ( QString fieldName : flds )
|
||||
{
|
||||
fields.append( QgsField( fieldName, QVariant::String ) );
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
int QgsVectorTileUtils::scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom )
|
||||
{
|
||||
qDebug() << "MVT map scale 1 :" << mapScale;
|
||||
|
||||
double s0 = 559082264.0287178; // scale denominator at zoom level 0 of GoogleCRS84Quad
|
||||
double tileZoom2 = log( s0 / mapScale ) / log( 2 );
|
||||
tileZoom2 -= 1; // TODO: it seems that map scale is double (is that because of high-dpi screen?)
|
||||
int tileZoom = static_cast<int>( round( tileZoom2 ) );
|
||||
|
||||
if ( tileZoom < sourceMinZoom )
|
||||
tileZoom = sourceMinZoom;
|
||||
if ( tileZoom > sourceMaxZoom )
|
||||
tileZoom = sourceMaxZoom;
|
||||
|
||||
return tileZoom;
|
||||
}
|
||||
|
||||
QgsVectorLayer *QgsVectorTileUtils::makeVectorLayerForTile( QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName )
|
||||
{
|
||||
QgsVectorTileMVTDecoder decoder;
|
||||
decoder.decode( tileID, mvt->getRawTile( tileID ) );
|
||||
qDebug() << decoder.layers();
|
||||
QSet<QString> fieldNames = QSet<QString>::fromList( decoder.layerFieldNames( layerName ) );
|
||||
fieldNames << "ty_pe"; // geom. type
|
||||
QMap<QString, QgsFields> perLayerFields;
|
||||
QgsFields fields = QgsVectorTileUtils::makeQgisFields( fieldNames );
|
||||
perLayerFields[layerName] = fields;
|
||||
QgsVectorTileFeatures data = decoder.layerFeatures( perLayerFields );
|
||||
QgsFeatureList featuresList = data[layerName].toList();
|
||||
|
||||
// turn all geometries to geom. collections (otherwise they won't be accepted by memory provider)
|
||||
for ( int i = 0; i < featuresList.count(); ++i )
|
||||
{
|
||||
QgsGeometry g = featuresList[i].geometry();
|
||||
QgsGeometryCollection *gc = new QgsGeometryCollection;
|
||||
const QgsAbstractGeometry *gg = g.constGet();
|
||||
if ( const QgsGeometryCollection *ggc = qgsgeometry_cast<const QgsGeometryCollection *>( gg ) )
|
||||
{
|
||||
for ( int k = 0; k < ggc->numGeometries(); ++k )
|
||||
gc->addGeometry( ggc->geometryN( k )->clone() );
|
||||
}
|
||||
else
|
||||
gc->addGeometry( gg->clone() );
|
||||
featuresList[i].setGeometry( QgsGeometry( gc ) );
|
||||
}
|
||||
|
||||
QgsVectorLayer *vl = new QgsVectorLayer( "GeometryCollection", layerName, "memory" );
|
||||
vl->dataProvider()->addAttributes( fields.toList() );
|
||||
vl->updateFields();
|
||||
bool res = vl->dataProvider()->addFeatures( featuresList );
|
||||
Q_ASSERT( res );
|
||||
Q_ASSERT( featuresList.count() == vl->featureCount() );
|
||||
vl->updateExtents();
|
||||
qDebug() << "layer" << layerName << "features" << vl->featureCount();
|
||||
return vl;
|
||||
}
|
||||
|
||||
|
||||
QString QgsVectorTileUtils::formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile )
|
||||
{
|
||||
QString turl( url );
|
||||
|
||||
turl.replace( QLatin1String( "{x}" ), QString::number( tile.column() ), Qt::CaseInsensitive );
|
||||
// TODO: inverted Y axis
|
||||
// if ( turl.contains( QLatin1String( "{-y}" ) ) )
|
||||
// {
|
||||
// turl.replace( QLatin1String( "{-y}" ), QString::number( tm.matrixHeight - tile.tileRow - 1 ), Qt::CaseInsensitive );
|
||||
// }
|
||||
// else
|
||||
{
|
||||
turl.replace( QLatin1String( "{y}" ), QString::number( tile.row() ), Qt::CaseInsensitive );
|
||||
}
|
||||
turl.replace( QLatin1String( "{z}" ), QString::number( tile.zoomLevel() ), Qt::CaseInsensitive );
|
||||
return turl;
|
||||
}
|
||||
|
||||
//! a helper class for ordering tile requests according to the distance from view center
|
||||
struct LessThanTileRequest
|
||||
{
|
||||
QPointF center; //!< Center in tile matrix (!) coordinates
|
||||
bool operator()( const QgsTileXYZ &req1, const QgsTileXYZ &req2 )
|
||||
{
|
||||
QPointF p1( req1.column() + 0.5, req1.row() + 0.5 );
|
||||
QPointF p2( req2.column() + 0.5, req2.row() + 0.5 );
|
||||
// using chessboard distance (loading order more natural than euclidean/manhattan distance)
|
||||
double d1 = std::max( std::fabs( center.x() - p1.x() ), std::fabs( center.y() - p1.y() ) );
|
||||
double d2 = std::max( std::fabs( center.x() - p2.x() ), std::fabs( center.y() - p2.y() ) );
|
||||
return d1 < d2;
|
||||
}
|
||||
};
|
||||
|
||||
QVector<QgsTileXYZ> QgsVectorTileUtils::tilesInRange( const QgsTileRange &range, int zoomLevel )
|
||||
{
|
||||
QVector<QgsTileXYZ> tiles;
|
||||
for ( int tileRow = range.startRow(); tileRow <= range.endRow(); ++tileRow )
|
||||
{
|
||||
for ( int tileColumn = range.startColumn(); tileColumn <= range.endColumn(); ++tileColumn )
|
||||
{
|
||||
tiles.append( QgsTileXYZ( tileColumn, tileRow, zoomLevel ) );
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
void QgsVectorTileUtils::sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, const QPointF ¢er )
|
||||
{
|
||||
LessThanTileRequest cmp;
|
||||
cmp.center = center;
|
||||
std::sort( tiles.begin(), tiles.end(), cmp );
|
||||
}
|
62
src/core/vectortile/qgsvectortileutils.h
Normal file
62
src/core/vectortile/qgsvectortileutils.h
Normal file
@ -0,0 +1,62 @@
|
||||
/***************************************************************************
|
||||
qgsvectortileutils.h
|
||||
--------------------------------------
|
||||
Date : March 2020
|
||||
Copyright : (C) 2020 by Martin Dobias
|
||||
Email : wonder dot sk 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 QGSVECTORTILEUTILS_H
|
||||
#define QGSVECTORTILEUTILS_H
|
||||
|
||||
#include <QSet>
|
||||
|
||||
class QPointF;
|
||||
class QPolygon;
|
||||
|
||||
class QgsCoordinateTransform;
|
||||
class QgsFields;
|
||||
class QgsMapToPixel;
|
||||
class QgsRectangle;
|
||||
class QgsVectorLayer;
|
||||
|
||||
class QgsTileMatrix;
|
||||
class QgsTileRange;
|
||||
struct QgsTileXYZ;
|
||||
class QgsVectorTileLayer;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* Random utility functions for working with vector tiles
|
||||
*
|
||||
* \since QGIS 3.14
|
||||
*/
|
||||
class QgsVectorTileUtils
|
||||
{
|
||||
public:
|
||||
|
||||
//! Returns a list of tiles in the given tile range
|
||||
static QVector<QgsTileXYZ> tilesInRange( const QgsTileRange &range, int zoomLevel );
|
||||
//! Orders tile requests according to the distance from view center (given in tile matrix coords)
|
||||
static void sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, const QPointF ¢er );
|
||||
|
||||
//! Returns polygon (made by four corners of the tile) in screen coordinates
|
||||
static QPolygon tilePolygon( QgsTileXYZ id, const QgsTileMatrix &tm, const QgsMapToPixel &mtp );
|
||||
//! Returns QgsFields instance based on the set of field names
|
||||
static QgsFields makeQgisFields( QSet<QString> flds );
|
||||
//! Finds best fitting zoom level (assuming GoogleCRS84Quad tile matrix set) given map scale denominator and allowed zoom level range
|
||||
static int scaleToZoomLevel( double mapScale, int sourceMinZoom, int sourceMaxZoom );
|
||||
//! Returns a temporary vector layer for given sub-layer of tile in vector tile layer
|
||||
static QgsVectorLayer *makeVectorLayerForTile( QgsVectorTileLayer *mvt, QgsTileXYZ tileID, const QString &layerName );
|
||||
//! Returns formatted tile URL string replacing {x}, {y}, {z} placeholders
|
||||
static QString formatXYZUrlTemplate( const QString &url, QgsTileXYZ tile );
|
||||
};
|
||||
|
||||
#endif // QGSVECTORTILEUTILS_H
|
2527
src/core/vectortile/vector_tile.pb.cc
Normal file
2527
src/core/vectortile/vector_tile.pb.cc
Normal file
File diff suppressed because it is too large
Load Diff
1260
src/core/vectortile/vector_tile.pb.h
Normal file
1260
src/core/vectortile/vector_tile.pb.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,4 @@
|
||||
SET (WMS_SRCS
|
||||
qgsmbtilesreader.cpp
|
||||
qgswmscapabilities.cpp
|
||||
qgswmsprovider.cpp
|
||||
qgswmsconnection.cpp
|
||||
|
Loading…
x
Reference in New Issue
Block a user