void QgsGeometryValidator::checkRingIntersections( int p0, int i0, const QgsPolylineXY &ring0, int p1, int i1, const QgsPolylineXY &ring1 ) { for ( int i = 0; !mStop && i < ring0.size() - 1; i++ ) { QgsVector v = ring0[i + 1] - ring0[i]; for ( int j = 0; !mStop && j < ring1.size() - 1; j++ ) { QgsVector w = ring1[j + 1] - ring1[j]; QgsPointXY s; if ( intersectLines( ring0[i], v, ring1[j], w, s ) ) { double d = -distLine2Point( ring0[i], v.perpVector(), s ); if ( d >= 0 && d <= v.length() ) { d = -distLine2Point( ring1[j], w.perpVector(), s ); if ( d > 0 && d < w.length() && ring0[i + 1] != ring1[j + 1] && ring0[i + 1] != ring1[j] && ring0[i + 0] != ring1[j + 1] && ring0[i + 0] != ring1[j] ) { QString msg = QObject::tr( "segment %1 of ring %2 of polygon %3 intersects segment %4 of ring %5 of polygon %6 at %7" ) .arg( i0 ).arg( i ).arg( p0 ) .arg( i1 ).arg( j ).arg( p1 ) .arg( s.toString() ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg, s ) ); mErrorCount++; } } } } } }
QString ProjectorData::cpToString() { QString myString; for ( int i = 0; i < mCPRows; i++ ) { if ( i > 0 ) myString += '\n'; for ( int j = 0; j < mCPCols; j++ ) { if ( j > 0 ) myString += QLatin1String( " " ); QgsPointXY myPoint = mCPMatrix[i][j]; if ( mCPLegalMatrix[i][j] ) { myString += myPoint.toString(); } else { myString += QLatin1String( "(-,-)" ); } } } return myString; }
void QgsMapToolDigitizeFeature::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) { QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mLayer ); if ( !vlayer ) //if no given layer take the current from canvas vlayer = currentVectorLayer(); if ( !vlayer ) { notifyNotVectorLayer(); return; } QgsWkbTypes::Type layerWKBType = vlayer->wkbType(); QgsVectorDataProvider *provider = vlayer->dataProvider(); if ( !( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) ) { emit messageEmitted( tr( "The data provider for this layer does not support the addition of features." ), Qgis::Warning ); return; } if ( !vlayer->isEditable() ) { notifyNotEditableLayer(); return; } // POINT CAPTURING if ( mode() == CapturePoint ) { if ( e->button() != Qt::LeftButton ) return; //check we only use this tool for point/multipoint layers if ( vlayer->geometryType() != QgsWkbTypes::PointGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture point' tool on this vector layer" ), Qgis::Warning ); return; } QgsPointXY savePoint; //point in layer coordinates try { QgsPoint fetchPoint; int res; res = fetchLayerPoint( e->mapPointMatch(), fetchPoint ); if ( res == 0 ) { savePoint = QgsPointXY( fetchPoint.x(), fetchPoint.y() ); } else { savePoint = toLayerCoordinates( vlayer, e->mapPoint() ); } QgsDebugMsg( "savePoint = " + savePoint.toString() ); } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::Warning ); return; } //only do the rest for provider with feature addition support //note that for the grass provider, this will return false since //grass provider has its own mechanism of feature addition if ( provider->capabilities() & QgsVectorDataProvider::AddFeatures ) { QgsFeature f( vlayer->fields(), 0 ); QgsGeometry g; if ( layerWKBType == QgsWkbTypes::Point ) { g = QgsGeometry::fromPointXY( savePoint ); } else if ( !QgsWkbTypes::isMultiType( layerWKBType ) && QgsWkbTypes::hasZ( layerWKBType ) ) { g = QgsGeometry( new QgsPoint( QgsWkbTypes::PointZ, savePoint.x(), savePoint.y(), defaultZValue() ) ); } else if ( QgsWkbTypes::isMultiType( layerWKBType ) && !QgsWkbTypes::hasZ( layerWKBType ) ) { g = QgsGeometry::fromMultiPointXY( QgsMultiPointXY() << savePoint ); } else if ( QgsWkbTypes::isMultiType( layerWKBType ) && QgsWkbTypes::hasZ( layerWKBType ) ) { QgsMultiPoint *mp = new QgsMultiPoint(); mp->addGeometry( new QgsPoint( QgsWkbTypes::PointZ, savePoint.x(), savePoint.y(), defaultZValue() ) ); g = QgsGeometry( mp ); } else { // if layer supports more types (mCheckGeometryType is false) g = QgsGeometry::fromPointXY( savePoint ); } if ( QgsWkbTypes::hasM( layerWKBType ) ) { g.get()->addMValue(); } f.setGeometry( g ); f.setValid( true ); digitized( f ); // we are done with digitizing for now so instruct advanced digitizing dock to reset its CAD points cadDockWidget()->clearPoints(); } } // LINE AND POLYGON CAPTURING else if ( mode() == CaptureLine || mode() == CapturePolygon ) { //check we only use the line tool for line/multiline layers if ( mode() == CaptureLine && vlayer->geometryType() != QgsWkbTypes::LineGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture line' tool on this vector layer" ), Qgis::Warning ); return; } //check we only use the polygon tool for polygon/multipolygon layers if ( mode() == CapturePolygon && vlayer->geometryType() != QgsWkbTypes::PolygonGeometry && mCheckGeometryType ) { emit messageEmitted( tr( "Wrong editing tool, cannot apply the 'capture polygon' tool on this vector layer" ), Qgis::Warning ); return; } //add point to list and to rubber band if ( e->button() == Qt::LeftButton ) { int error = addVertex( e->mapPoint(), e->mapPointMatch() ); if ( error == 1 ) { //current layer is not a vector layer return; } else if ( error == 2 ) { //problem with coordinate transformation emit messageEmitted( tr( "Cannot transform the point to the layers coordinate system" ), Qgis::Warning ); return; } startCapturing(); } else if ( e->button() == Qt::RightButton ) { // End of string deleteTempRubberBand(); //lines: bail out if there are not at least two vertices if ( mode() == CaptureLine && size() < 2 ) { stopCapturing(); return; } //polygons: bail out if there are not at least two vertices if ( mode() == CapturePolygon && size() < 3 ) { stopCapturing(); return; } if ( mode() == CapturePolygon ) { closePolygon(); } //create QgsFeature with wkb representation std::unique_ptr< QgsFeature > f( new QgsFeature( vlayer->fields(), 0 ) ); //does compoundcurve contain circular strings? //does provider support circular strings? bool hasCurvedSegments = captureCurve()->hasCurvedSegments(); bool providerSupportsCurvedSegments = vlayer->dataProvider()->capabilities() & QgsVectorDataProvider::CircularGeometries; QList<QgsPointLocator::Match> snappingMatchesList; QgsCurve *curveToAdd = nullptr; if ( hasCurvedSegments && providerSupportsCurvedSegments ) { curveToAdd = captureCurve()->clone(); } else { curveToAdd = captureCurve()->curveToLine(); snappingMatchesList = snappingMatches(); } if ( mode() == CaptureLine ) { QgsGeometry g( curveToAdd ); f->setGeometry( g ); } else { QgsCurvePolygon *poly = nullptr; if ( hasCurvedSegments && providerSupportsCurvedSegments ) { poly = new QgsCurvePolygon(); } else { poly = new QgsPolygon(); } poly->setExteriorRing( curveToAdd ); QgsGeometry g( poly ); f->setGeometry( g ); QgsGeometry featGeom = f->geometry(); int avoidIntersectionsReturn = featGeom.avoidIntersections( QgsProject::instance()->avoidIntersectionsLayers() ); f->setGeometry( featGeom ); if ( avoidIntersectionsReturn == 1 ) { //not a polygon type. Impossible to get there } if ( f->geometry().isEmpty() ) //avoid intersection might have removed the whole geometry { emit messageEmitted( tr( "The feature cannot be added because it's geometry collapsed due to intersection avoidance" ), Qgis::Critical ); stopCapturing(); return; } } f->setValid( true ); digitized( *f ); stopCapturing(); } } }
QVariantMap QgsShortestPathPointToPointAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { loadCommonParams( parameters, context, feedback ); QgsFields fields; fields.append( QgsField( QStringLiteral( "start" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "end" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "cost" ), QVariant::Double ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::LineString, mNetwork->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QgsPointXY startPoint = parameterAsPoint( parameters, QStringLiteral( "START_POINT" ), context, mNetwork->sourceCrs() ); QgsPointXY endPoint = parameterAsPoint( parameters, QStringLiteral( "END_POINT" ), context, mNetwork->sourceCrs() ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector< QgsPointXY > points; points << startPoint << endPoint; QVector< QgsPointXY > snappedPoints; mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback ); feedback->pushInfo( QObject::tr( "Calculating shortest path…" ) ); QgsGraph *graph = mBuilder->graph(); int idxStart = graph->findVertex( snappedPoints[0] ); int idxEnd = graph->findVertex( snappedPoints[1] ); QVector< int > tree; QVector< double > costs; QgsGraphAnalyzer::dijkstra( graph, idxStart, 0, &tree, &costs ); if ( tree.at( idxEnd ) == -1 ) { throw QgsProcessingException( QObject::tr( "There is no route from start point to end point." ) ); } QVector<QgsPointXY> route; route.push_front( graph->vertex( idxEnd ).point() ); double cost = costs.at( idxEnd ); while ( idxEnd != idxStart ) { idxEnd = graph->edge( tree.at( idxEnd ) ).fromVertex(); route.push_front( graph->vertex( idxEnd ).point() ); } feedback->pushInfo( QObject::tr( "Writing results…" ) ); QgsGeometry geom = QgsGeometry::fromPolylineXY( route ); QgsFeature feat; feat.setFields( fields ); QgsAttributes attributes; attributes << startPoint.toString() << endPoint.toString() << cost / mMultiplier; feat.setGeometry( geom ); feat.setAttributes( attributes ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); outputs.insert( QStringLiteral( "TRAVEL_COST" ), cost / mMultiplier ); return outputs; }
QVariantMap QgsShortestPathPointToLayerAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { loadCommonParams( parameters, context, feedback ); QgsPointXY startPoint = parameterAsPoint( parameters, QStringLiteral( "START_POINT" ), context, mNetwork->sourceCrs() ); std::unique_ptr< QgsFeatureSource > endPoints( parameterAsSource( parameters, QStringLiteral( "END_POINTS" ), context ) ); if ( !endPoints ) throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "END_POINTS" ) ) ); QgsFields fields = endPoints->fields(); fields.append( QgsField( QStringLiteral( "start" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "end" ), QVariant::String ) ); fields.append( QgsField( QStringLiteral( "cost" ), QVariant::Double ) ); QString dest; std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, QgsWkbTypes::LineString, mNetwork->sourceCrs() ) ); if ( !sink ) throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) ); QVector< QgsPointXY > points; points.push_front( startPoint ); QHash< int, QgsAttributes > sourceAttributes; loadPoints( endPoints.get(), points, sourceAttributes, context, feedback ); feedback->pushInfo( QObject::tr( "Building graph…" ) ); QVector< QgsPointXY > snappedPoints; mDirector->makeGraph( mBuilder.get(), points, snappedPoints, feedback ); feedback->pushInfo( QObject::tr( "Calculating shortest paths…" ) ); QgsGraph *graph = mBuilder->graph(); int idxStart = graph->findVertex( snappedPoints[0] ); int idxEnd; QVector< int > tree; QVector< double > costs; QgsGraphAnalyzer::dijkstra( graph, idxStart, 0, &tree, &costs ); QVector<QgsPointXY> route; double cost; QgsFeature feat; feat.setFields( fields ); QgsAttributes attributes; int step = points.size() > 0 ? 100.0 / points.size() : 1; for ( int i = 1; i < points.size(); i++ ) { if ( feedback->isCanceled() ) { break; } idxEnd = graph->findVertex( snappedPoints[i] ); if ( tree.at( idxEnd ) == -1 ) { feedback->reportError( QObject::tr( "There is no route from start point (%1) to end point (%2)." ) .arg( startPoint.toString(), points[i].toString() ) ); feat.clearGeometry(); attributes = sourceAttributes.value( i ); attributes.append( QVariant() ); attributes.append( points[i].toString() ); feat.setAttributes( attributes ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); continue; } route.clear(); route.push_front( graph->vertex( idxEnd ).point() ); cost = costs.at( idxEnd ); while ( idxEnd != idxStart ) { idxEnd = graph->edge( tree.at( idxEnd ) ).fromVertex(); route.push_front( graph->vertex( idxEnd ).point() ); } QgsGeometry geom = QgsGeometry::fromPolylineXY( route ); QgsFeature feat; feat.setFields( fields ); attributes = sourceAttributes.value( i ); attributes.append( startPoint.toString() ); attributes.append( points[i].toString() ); attributes.append( cost / mMultiplier ); feat.setAttributes( attributes ); feat.setGeometry( geom ); sink->addFeature( feat, QgsFeatureSink::FastInsert ); feedback->setProgress( i * step ); } QVariantMap outputs; outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; }
bool QgsMapRendererJob::reprojectToLayerExtent( const QgsMapLayer *ml, const QgsCoordinateTransform &ct, QgsRectangle &extent, QgsRectangle &r2 ) { bool split = false; try { #ifdef QGISDEBUG // QgsLogger::debug<QgsRectangle>("Getting extent of canvas in layers CS. Canvas is ", extent, __FILE__, __FUNCTION__, __LINE__); #endif // Split the extent into two if the source CRS is // geographic and the extent crosses the split in // geographic coordinates (usually +/- 180 degrees, // and is assumed to be so here), and draw each // extent separately. static const double SPLIT_COORD = 180.0; if ( ml->crs().isGeographic() ) { if ( ml->type() == QgsMapLayer::VectorLayer && !ct.destinationCrs().isGeographic() ) { // if we transform from a projected coordinate system check // check if transforming back roughly returns the input // extend - otherwise render the world. QgsRectangle extent1 = ct.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform ); QgsRectangle extent2 = ct.transformBoundingBox( extent1, QgsCoordinateTransform::ForwardTransform ); QgsDebugMsgLevel( QString( "\n0:%1 %2x%3\n1:%4\n2:%5 %6x%7 (w:%8 h:%9)" ) .arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ) .arg( extent1.toString(), extent2.toString() ).arg( extent2.width() ).arg( extent2.height() ) .arg( std::fabs( 1.0 - extent2.width() / extent.width() ) ) .arg( std::fabs( 1.0 - extent2.height() / extent.height() ) ) , 3 ); if ( std::fabs( 1.0 - extent2.width() / extent.width() ) < 0.5 && std::fabs( 1.0 - extent2.height() / extent.height() ) < 0.5 ) { extent = extent1; } else { extent = QgsRectangle( -180.0, -90.0, 180.0, 90.0 ); } } else { // Note: ll = lower left point QgsPointXY ll = ct.transform( extent.xMinimum(), extent.yMinimum(), QgsCoordinateTransform::ReverseTransform ); // and ur = upper right point QgsPointXY ur = ct.transform( extent.xMaximum(), extent.yMaximum(), QgsCoordinateTransform::ReverseTransform ); QgsDebugMsgLevel( QString( "in:%1 (ll:%2 ur:%3)" ).arg( extent.toString(), ll.toString(), ur.toString() ), 4 ); extent = ct.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform ); QgsDebugMsgLevel( QString( "out:%1 (w:%2 h:%3)" ).arg( extent.toString() ).arg( extent.width() ).arg( extent.height() ), 4 ); if ( ll.x() > ur.x() ) { // the coordinates projected in reverse order than what one would expect. // we are probably looking at an area that includes longitude of 180 degrees. // we need to take into account coordinates from two intervals: (-180,x1) and (x2,180) // so let's use (-180,180). This hopefully does not add too much overhead. It is // more straightforward than rendering with two separate extents and more consistent // for rendering, labeling and caching as everything is rendered just in one go extent.setXMinimum( -SPLIT_COORD ); extent.setXMaximum( SPLIT_COORD ); } } // TODO: the above rule still does not help if using a projection that covers the whole // world. E.g. with EPSG:3857 the longitude spectrum -180 to +180 is mapped to approx. // -2e7 to +2e7. Converting extent from -5e7 to +5e7 is transformed as -90 to +90, // but in fact the extent should cover the whole world. } else // can't cross 180 { if ( ct.destinationCrs().isGeographic() && ( extent.xMinimum() <= -180 || extent.xMaximum() >= 180 || extent.yMinimum() <= -90 || extent.yMaximum() >= 90 ) ) // Use unlimited rectangle because otherwise we may end up transforming wrong coordinates. // E.g. longitude -200 to +160 would be understood as +40 to +160 due to periodicity. // We could try to clamp coords to (-180,180) for lon resp. (-90,90) for lat, // but this seems like a safer choice. extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX ); else extent = ct.transformBoundingBox( extent, QgsCoordinateTransform::ReverseTransform ); } } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( "Transform error caught" ); extent = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX ); r2 = QgsRectangle( -DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX ); } return split; }
bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, QgsPointXY point, const QgsRectangle &viewExtent, double mapUnitsPerPixel ) { QgsDebugMsg( "point = " + point.toString() ); if ( !layer ) return false; QgsRasterDataProvider *dprovider = layer->dataProvider(); if ( !dprovider ) return false; int capabilities = dprovider->capabilities(); if ( !( capabilities & QgsRasterDataProvider::Identify ) ) return false; QgsPointXY pointInCanvasCrs = point; try { point = toLayerCoordinates( layer, point ); } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( QString( "coordinate not reprojectable: %1" ).arg( cse.what() ) ); return false; } QgsDebugMsg( QString( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) ); if ( !layer->extent().contains( point ) ) return false; QMap< QString, QString > attributes, derivedAttributes; QgsRaster::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( QStringLiteral( "identify/format" ) ).toString() ); // check if the format is really supported otherwise use first supported format if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) ) { if ( capabilities & QgsRasterInterface::IdentifyFeature ) format = QgsRaster::IdentifyFormatFeature; else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRaster::IdentifyFormatValue; else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRaster::IdentifyFormatHtml; else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRaster::IdentifyFormatText; else return false; } QgsRasterIdentifyResult identifyResult; // We can only use current map canvas context (extent, width, height) if layer is not reprojected, if ( dprovider->crs() != mCanvas->mapSettings().destinationCrs() ) { // To get some reasonable response for point/line WMS vector layers we must // use a context with approximately a resolution in layer CRS units // corresponding to current map canvas resolution (for examplei UMN Mapserver // in msWMSFeatureInfo() -> msQueryByRect() is using requested pixel // + TOLERANCE (layer param) for feature selection) // QgsRectangle r; r.setXMinimum( pointInCanvasCrs.x() - mapUnitsPerPixel / 2. ); r.setXMaximum( pointInCanvasCrs.x() + mapUnitsPerPixel / 2. ); r.setYMinimum( pointInCanvasCrs.y() - mapUnitsPerPixel / 2. ); r.setYMaximum( pointInCanvasCrs.y() + mapUnitsPerPixel / 2. ); r = toLayerCoordinates( layer, r ); // will be a bit larger // Mapserver (6.0.3, for example) does not work with 1x1 pixel box // but that is fixed (the rect is enlarged) in the WMS provider identifyResult = dprovider->identify( point, format, r, 1, 1 ); } else { // It would be nice to use the same extent and size which was used for drawing, // so that WCS can use cache from last draw, unfortunately QgsRasterLayer::draw() // is doing some tricks with extent and size to align raster to output which // would be difficult to replicate here. // Note: cutting the extent may result in slightly different x and y resolutions // and thus shifted point calculated back in QGIS WMS (using average resolution) //viewExtent = dprovider->extent().intersect( &viewExtent ); // Width and height are calculated from not projected extent and we hope that // are similar to source width and height used to reproject layer for drawing. // TODO: may be very dangerous, because it may result in different resolutions // in source CRS, and WMS server (QGIS server) calcs wrong coor using average resolution. int width = std::round( viewExtent.width() / mapUnitsPerPixel ); int height = std::round( viewExtent.height() / mapUnitsPerPixel ); QgsDebugMsg( QString( "viewExtent.width = %1 viewExtent.height = %2" ).arg( viewExtent.width() ).arg( viewExtent.height() ) ); QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) ); QgsDebugMsg( QString( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) ); identifyResult = dprovider->identify( point, format, viewExtent, width, height ); } derivedAttributes.insert( tr( "(clicked coordinate X)" ), formatXCoordinate( pointInCanvasCrs ) ); derivedAttributes.insert( tr( "(clicked coordinate Y)" ), formatYCoordinate( pointInCanvasCrs ) ); if ( identifyResult.isValid() ) { QMap<int, QVariant> values = identifyResult.results(); QgsGeometry geometry; if ( format == QgsRaster::IdentifyFormatValue ) { Q_FOREACH ( int bandNo, values.keys() ) { QString valueString; if ( values.value( bandNo ).isNull() ) { valueString = tr( "no data" ); } else { QVariant value( values.value( bandNo ) ); // The cast is legit. Quoting QT doc : // "Although this function is declared as returning QVariant::Type, // the return value should be interpreted as QMetaType::Type" if ( static_cast<QMetaType::Type>( value.type() ) == QMetaType::Float ) { valueString = QgsRasterBlock::printValue( value.toFloat() ); } else { valueString = QgsRasterBlock::printValue( value.toDouble() ); } } attributes.insert( dprovider->generateBandName( bandNo ), valueString ); } QString label = layer->name(); results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) ); }
bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, QgsPointXY point, const QgsRectangle &viewExtent, double mapUnitsPerPixel ) { QgsDebugMsg( "point = " + point.toString() ); if ( !layer ) return false; QgsRasterDataProvider *dprovider = layer->dataProvider(); if ( !dprovider ) return false; int capabilities = dprovider->capabilities(); if ( !( capabilities & QgsRasterDataProvider::Identify ) ) return false; QgsPointXY pointInCanvasCrs = point; try { point = toLayerCoordinates( layer, point ); } catch ( QgsCsException &cse ) { Q_UNUSED( cse ); QgsDebugMsg( QString( "coordinate not reprojectable: %1" ).arg( cse.what() ) ); return false; } QgsDebugMsg( QString( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) ); if ( !layer->extent().contains( point ) ) return false; QMap< QString, QString > attributes, derivedAttributes; QgsRaster::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( QStringLiteral( "identify/format" ) ).toString() ); // check if the format is really supported otherwise use first supported format if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) ) { if ( capabilities & QgsRasterInterface::IdentifyFeature ) format = QgsRaster::IdentifyFormatFeature; else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRaster::IdentifyFormatValue; else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRaster::IdentifyFormatHtml; else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRaster::IdentifyFormatText; else return false; } QgsRasterIdentifyResult identifyResult; // We can only use current map canvas context (extent, width, height) if layer is not reprojected, if ( dprovider->crs() != mCanvas->mapSettings().destinationCrs() ) { // To get some reasonable response for point/line WMS vector layers we must // use a context with approximately a resolution in layer CRS units // corresponding to current map canvas resolution (for examplei UMN Mapserver // in msWMSFeatureInfo() -> msQueryByRect() is using requested pixel // + TOLERANCE (layer param) for feature selection) // QgsRectangle r; r.setXMinimum( pointInCanvasCrs.x() - mapUnitsPerPixel / 2. ); r.setXMaximum( pointInCanvasCrs.x() + mapUnitsPerPixel / 2. ); r.setYMinimum( pointInCanvasCrs.y() - mapUnitsPerPixel / 2. ); r.setYMaximum( pointInCanvasCrs.y() + mapUnitsPerPixel / 2. ); r = toLayerCoordinates( layer, r ); // will be a bit larger // Mapserver (6.0.3, for example) does not work with 1x1 pixel box // but that is fixed (the rect is enlarged) in the WMS provider identifyResult = dprovider->identify( point, format, r, 1, 1 ); } else { // It would be nice to use the same extent and size which was used for drawing, // so that WCS can use cache from last draw, unfortunately QgsRasterLayer::draw() // is doing some tricks with extent and size to align raster to output which // would be difficult to replicate here. // Note: cutting the extent may result in slightly different x and y resolutions // and thus shifted point calculated back in QGIS WMS (using average resolution) //viewExtent = dprovider->extent().intersect( &viewExtent ); // Width and height are calculated from not projected extent and we hope that // are similar to source width and height used to reproject layer for drawing. // TODO: may be very dangerous, because it may result in different resolutions // in source CRS, and WMS server (QGIS server) calcs wrong coor using average resolution. int width = std::round( viewExtent.width() / mapUnitsPerPixel ); int height = std::round( viewExtent.height() / mapUnitsPerPixel ); QgsDebugMsg( QString( "viewExtent.width = %1 viewExtent.height = %2" ).arg( viewExtent.width() ).arg( viewExtent.height() ) ); QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) ); QgsDebugMsg( QString( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) ); identifyResult = dprovider->identify( point, format, viewExtent, width, height ); } derivedAttributes.insert( tr( "(clicked coordinate X)" ), formatXCoordinate( pointInCanvasCrs ) ); derivedAttributes.insert( tr( "(clicked coordinate Y)" ), formatYCoordinate( pointInCanvasCrs ) ); if ( identifyResult.isValid() ) { QMap<int, QVariant> values = identifyResult.results(); QgsGeometry geometry; if ( format == QgsRaster::IdentifyFormatValue ) { for ( auto it = values.constBegin(); it != values.constEnd(); ++it ) { QString valueString; if ( it.value().isNull() ) { valueString = tr( "no data" ); } else { QVariant value( it.value() ); // The cast is legit. Quoting QT doc : // "Although this function is declared as returning QVariant::Type, // the return value should be interpreted as QMetaType::Type" if ( static_cast<QMetaType::Type>( value.type() ) == QMetaType::Float ) { valueString = QgsRasterBlock::printValue( value.toFloat() ); } else { valueString = QgsRasterBlock::printValue( value.toDouble() ); } } attributes.insert( dprovider->generateBandName( it.key() ), valueString ); } QString label = layer->name(); results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) ); } else if ( format == QgsRaster::IdentifyFormatFeature ) { for ( auto it = values.constBegin(); it != values.constEnd(); ++it ) { QVariant value = it.value(); if ( value.type() == QVariant::Bool && !value.toBool() ) { // sublayer not visible or not queryable continue; } if ( value.type() == QVariant::String ) { // error // TODO: better error reporting QString label = layer->subLayers().value( it.key() ); attributes.clear(); attributes.insert( tr( "Error" ), value.toString() ); results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) ); continue; } // list of feature stores for a single sublayer const QgsFeatureStoreList featureStoreList = it.value().value<QgsFeatureStoreList>(); for ( const QgsFeatureStore &featureStore : featureStoreList ) { const QgsFeatureList storeFeatures = featureStore.features(); for ( QgsFeature feature : storeFeatures ) { attributes.clear(); // WMS sublayer and feature type, a sublayer may contain multiple feature types. // Sublayer name may be the same as layer name and feature type name // may be the same as sublayer. We try to avoid duplicities in label. QString sublayer = featureStore.params().value( QStringLiteral( "sublayer" ) ).toString(); QString featureType = featureStore.params().value( QStringLiteral( "featureType" ) ).toString(); // Strip UMN MapServer '_feature' featureType.remove( QStringLiteral( "_feature" ) ); QStringList labels; if ( sublayer.compare( layer->name(), Qt::CaseInsensitive ) != 0 ) { labels << sublayer; } if ( featureType.compare( sublayer, Qt::CaseInsensitive ) != 0 || labels.isEmpty() ) { labels << featureType; } QMap< QString, QString > derAttributes = derivedAttributes; derAttributes.unite( featureDerivedAttributes( &feature, layer ) ); IdentifyResult identifyResult( qobject_cast<QgsMapLayer *>( layer ), labels.join( QStringLiteral( " / " ) ), featureStore.fields(), feature, derAttributes ); identifyResult.mParams.insert( QStringLiteral( "getFeatureInfoUrl" ), featureStore.params().value( QStringLiteral( "getFeatureInfoUrl" ) ) ); results->append( identifyResult ); } } } } else // text or html { QgsDebugMsg( QString( "%1 HTML or text values" ).arg( values.size() ) ); for ( auto it = values.constBegin(); it != values.constEnd(); ++it ) { QString value = it.value().toString(); attributes.clear(); attributes.insert( QLatin1String( "" ), value ); QString label = layer->subLayers().value( it.key() ); results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) ); } } } else { attributes.clear(); QString value = identifyResult.error().message( QgsErrorMessage::Text ); attributes.insert( tr( "Error" ), value ); QString label = tr( "Identify error" ); results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) ); } return true; }
void QgsGeometryValidator::validatePolyline( int i, QgsPolylineXY line, bool ring ) { if ( ring ) { if ( line.size() < 4 ) { QString msg = QObject::tr( "ring %1 with less than four points" ).arg( i ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg ) ); mErrorCount++; return; } if ( line[0] != line[ line.size() - 1 ] ) { QString msg = QObject::tr( "ring %1 not closed" ).arg( i ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg ) ); mErrorCount++; return; } } else if ( line.size() < 2 ) { QString msg = QObject::tr( "line %1 with less than two points" ).arg( i ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg ) ); mErrorCount++; return; } int j = 0; while ( j < line.size() - 1 ) { int n = 0; while ( j < line.size() - 1 && line[j] == line[j + 1] ) { line.remove( j ); n++; } if ( n > 0 ) { QString msg = QObject::tr( "line %1 contains %n duplicate node(s) at %2", "number of duplicate nodes", n ).arg( i ).arg( j ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg, line[j] ) ); mErrorCount++; } j++; } for ( j = 0; !mStop && j < line.size() - 3; j++ ) { QgsVector v = line[j + 1] - line[j]; double vl = v.length(); int n = ( j == 0 && ring ) ? line.size() - 2 : line.size() - 1; for ( int k = j + 2; !mStop && k < n; k++ ) { QgsVector w = line[k + 1] - line[k]; QgsPointXY s; if ( !intersectLines( line[j], v, line[k], w, s ) ) continue; double d = 0.0; try { d = -distLine2Point( line[j], v.perpVector(), s ); } catch ( QgsException &e ) { Q_UNUSED( e ); QgsDebugMsg( "Error validating: " + e.what() ); continue; } if ( d < 0 || d > vl ) continue; try { d = -distLine2Point( line[k], w.perpVector(), s ); } catch ( QgsException &e ) { Q_UNUSED( e ); QgsDebugMsg( "Error validating: " + e.what() ); continue; } if ( d <= 0 || d >= w.length() ) continue; QString msg = QObject::tr( "segments %1 and %2 of line %3 intersect at %4" ).arg( j ).arg( k ).arg( i ).arg( s.toString() ); QgsDebugMsg( msg ); emit errorFound( QgsGeometry::Error( msg, s ) ); mErrorCount++; } } }