Skip to content
85 changes: 72 additions & 13 deletions app/inpututils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ void InputUtils::setExtentToGeom( const QgsGeometry &geom, InputMapSettings *map
const QgsRectangle bbox = geom.boundingBox();
QgsRectangle currentExtent = mapSettings->mapSettings().visibleExtent();

if ( bbox.isEmpty() ) // Deal with an empty bouding box e.g : a point
if ( bbox.isEmpty() ) // Deal with an empty boudning box e.g : a point
{
const QgsVector offset = currentExtent.center() - bbox.center();
currentExtent -= offset;
Expand All @@ -321,33 +321,87 @@ void InputUtils::setExtentToGeom( const QgsGeometry &geom, InputMapSettings *map
mapSettings->setExtent( currentExtent );
}

QPointF InputUtils::relevantGeometryCenterToScreenCoordinates( const QgsGeometry &geom, InputMapSettings *mapSettings )
QPointF InputUtils::whereToPanWhenIdentifying( const QgsGeometry &geom, const InputMapSettings *mapSettings, double bottomOffset, const QPointF &identifyLocation )
{
QPointF screenPoint;
QgsPoint target;
if ( !mapSettings || geom.isNull() || !geom.constGet() )
return screenPoint;
return { std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() };

QgsRectangle effectiveExtent( mapSettings->visibleExtent() );

// canvas size in logical pixels; bottomOffset is in logical pixels too and
// screenToCoordinate() expects logical pixel coordinates
const double canvasWidth = mapSettings->outputSize().width() / mapSettings->devicePixelRatio();
const double canvasHeight = mapSettings->outputSize().height() / mapSettings->devicePixelRatio();
const QPointF bottomPoint( canvasWidth / 2.0, canvasHeight - bottomOffset );

const QgsRectangle currentExtent = mapSettings->mapSettings().visibleExtent();
const QgsPoint bottomPointMap = mapSettings->screenToCoordinate( bottomPoint );

// Cut the geometry extent to current extent
const QgsRectangle intersectedExtent = currentExtent.intersect( geom.boundingBox() );
effectiveExtent.setYMinimum( bottomPointMap.y() );

if ( !intersectedExtent.isNull() )
// Now we calculate a safeEffectiveExtent, slightly smaller, so that we don't allow geometries too close to the screen borders
constexpr double EXTENT_BUFFER_SCALE = 0.82;
const QgsRectangle safeEffectiveExtent( effectiveExtent.scaled( EXTENT_BUFFER_SCALE ) );

QPointF screenPoint;
QgsPoint target;
if ( safeEffectiveExtent.contains( geom.boundingBox() ) )
{
target = QgsPoint( intersectedExtent.center() );
// If the whole geometry is visible, don't move map
return { std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() };
}
else
else if ( effectiveExtent.width() >= geom.boundingBox().width() &&
effectiveExtent.height() >= geom.boundingBox().height() )
{
// The geometry is outside the current viewed extent
setExtentToGeom( geom, mapSettings );
// if the whole geometry would fit without changing scale, center it
target = QgsPoint( geom.boundingBox().center() );
}
else
{
// the geometry is big, let's pan to the point the user clicked on the map
target = QgsPoint( identifyLocation );
}

screenPoint = mapSettings->coordinateToScreen( target );
screenPoint.ry() += bottomOffset / 2;

return screenPoint;
}

QgsRectangle InputUtils::drawerCompensatedExtent( const QgsGeometry &geom, InputMapSettings *mapSettings, double bottomOffset )
{
const QgsRectangle bbox = geom.boundingBox();
QgsRectangle currentExtent = mapSettings->mapSettings().visibleExtent();

// canvas size in logical pixels; the bottom bottomOffset of the canvas is covered by another
// component (e.g. preview drawer), so we center the geometry in the remaining visible part
const double canvasWidth = mapSettings->outputSize().width() / mapSettings->devicePixelRatio();
const double canvasHeight = mapSettings->outputSize().height() / mapSettings->devicePixelRatio();
const double visibleHeight = std::max( canvasHeight - bottomOffset, 1.0 );

if ( bbox.isEmpty() ) // Deal with an empty bouding box e.g : a point
Comment thread
uclaros marked this conversation as resolved.
{
const QgsPointXY center( bbox.center().x(), bbox.center().y() - bottomOffset / 2.0 * mapSettings->mapUnitsPerPoint() );
const QgsVector offset = currentExtent.center() - center;
currentExtent -= offset;
}
else
{
QgsRectangle paddedBbox = bbox;

// Add an offset to encompass handles etc..
// This number is based on what feels comfortable for the user
constexpr double SCALE_FACTOR = 1.18;
paddedBbox.scale( SCALE_FACTOR );

const double mapUnitsPerPoint = std::max( paddedBbox.width() / canvasWidth, paddedBbox.height() / visibleHeight );
const double cx = bbox.center().x();
const double cy = bbox.center().y() - bottomOffset / 2.0 * mapUnitsPerPoint;
currentExtent = QgsRectangle( cx - canvasWidth * mapUnitsPerPoint / 2, cy - canvasHeight * mapUnitsPerPoint / 2,
cx + canvasWidth * mapUnitsPerPoint / 2, cy + canvasHeight * mapUnitsPerPoint / 2 );
}
return currentExtent;
}

double InputUtils::convertCoordinateString( const QString &rationalValue )
{
QStringList values = rationalValue.split( "," );
Expand Down Expand Up @@ -2145,6 +2199,11 @@ QString InputUtils::getUniqueString( const QString &newString, const QStringList
return uniqueString;
}

QgsRectangle InputUtils::extentFromMinMax( double xMin, double yMin, double xMax, double yMax )
{
return QgsRectangle( xMin, yMin, xMax, yMax );
}

bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
{
int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
Expand Down
25 changes: 22 additions & 3 deletions app/inpututils.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,29 @@ class InputUtils: public QObject
Q_INVOKABLE void setExtentToGeom( const QgsGeometry &geom, InputMapSettings *mapSettings );

/**
* Returns the center point of the \a geom currently displayed on screen
* Returns the screen point where the map should jump to when identifying features.
* If the map should not pan, a QPointF with NaNs is returned.
*
* Fall back to \see setExtentToGeom in case we don't find any geometry relevant
* \param geom The geometry identified
* \param mapSettings Current map settings
* \param bottomOffset Pixel size (height) of drawer that may be covering the map canvas, eg preview panel
* \param identifyLocation The map coords of the location the user clicked for identification
*
* Nota Bene: Assume geometry and map canvas CRS are the same
*/
Q_INVOKABLE QPointF relevantGeometryCenterToScreenCoordinates( const QgsGeometry &geom, InputMapSettings *mapSettings );
Q_INVOKABLE static QPointF whereToPanWhenIdentifying( const QgsGeometry &geom, const InputMapSettings *mapSettings, double bottomOffset, const QPointF &identifyLocation );

/**
* Returns the extent that the map needs to zoom to so that the \a geom fits (with a safe buffer) the visible part
* of the map canvas when the canvas lower part is covered by a drawer of \a bottomOffset pixels high.
*
* \param geom The geometry identified
* \param mapSettings Current map settings
* \param bottomOffset Pixel size (height) of drawer that may be covering the map canvas, eg preview panel
*
* Nota Bene: Assume geometry and map canvas CRS are the same
*/
Q_INVOKABLE static QgsRectangle drawerCompensatedExtent( const QgsGeometry &geom, InputMapSettings *mapSettings, double bottomOffset );

// utility functions to extract information from map settings
// (in theory this data should be directly available from .MapTransform
Expand Down Expand Up @@ -681,6 +697,9 @@ class InputUtils: public QObject
*/
static QString getUniqueString( const QString &newString, const QStringList &existingStrings );

//! Trivial constructor for QgsRectangle since the actual constructor is not invokable
Q_INVOKABLE static QgsRectangle extentFromMinMax( double xMin, double yMin, double xMax, double yMax );

public slots:
void onQgsLogMessageReceived( const QString &message, const QString &tag, Qgis::MessageLevel level );

Expand Down
19 changes: 5 additions & 14 deletions app/qml/form/MMFormController.qml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Item {
property bool layerIsReadOnly: featureLayerPair?.layer?.readOnly ?? false
property bool layerIsSpatial: featureLayerPair ? __inputUtils.isSpatialLayer( featureLayerPair.layer ) : false

property real drawerHeight: drawer.height
property real previewPanelHeight: previewPanel.implicitHeight

signal closed()
signal saveRequested()
Expand All @@ -46,7 +46,7 @@ Item {
signal createLinkedFeature( var targetLayer, var parentPair )
signal multiSelectFeature( var feature )
signal stakeoutFeature( var feature )
signal previewPanelChanged( var panelHeight )
signal previewPanelChanged()

function openDrawer() {
root.panelState = "form"
Expand Down Expand Up @@ -142,11 +142,6 @@ Item {
edge: Qt.BottomEdge
closePolicy: Popup.CloseOnEscape // prevents the drawer closing while moving canvas

onOpened: {
if ( panelState === "preview" )
previewPanelChanged( previewPanel.implicitHeight )
}

onClosed: {
if ( statesManager.state !== "hidden" )
statesManager.state = "closed"
Expand Down Expand Up @@ -179,8 +174,9 @@ Item {

onCloseClicked: drawer.close()

onImplicitHeightChanged: {
previewPanelChanged( previewPanel.implicitHeight )
onDoneLoading: {
if ( root.panelState === "preview" )
root.previewPanelChanged()
}
}

Expand Down Expand Up @@ -243,9 +239,4 @@ Item {
}
}
}

onFeatureLayerPairChanged: {
if ( panelState === "preview" )
previewPanelChanged( previewPanel.implicitHeight )
}
}
10 changes: 5 additions & 5 deletions app/qml/form/MMFormStackController.qml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ Item {
return root.height // form is the highest always
}
else if ( form.panelState === "preview" ) {
if ( form.drawerHeight > maxHeight ) {
maxHeight = form.drawerHeight
if ( form.previewPanelHeight > maxHeight ) {
maxHeight = form.previewPanelHeight
}
}
}
Expand All @@ -54,7 +54,7 @@ Item {
signal createLinkedFeatureRequested( var targetLayer, var parentPair )
signal multiSelectFeature( var feature )
signal stakeoutFeature( var feature )
signal previewPanelChanged( var panelHeight )
signal previewPanelChanged()

function openForm( pair, formState, panelState ) {
if ( formsStack.depth === 0 )
Expand Down Expand Up @@ -308,8 +308,8 @@ Item {
formsStack.syncWhenFormCloses = true
}

onPreviewPanelChanged: function( panelHeight ) {
root.previewPanelChanged( panelHeight )
onPreviewPanelChanged: {
root.previewPanelChanged()
}

onEditGeometry: function( pair ) {
Expand Down
19 changes: 19 additions & 0 deletions app/qml/form/MMPreviewDrawer.qml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Item {
signal selectMoreClicked( var feature )
signal stakeoutClicked( var feature )
signal closeClicked()
// this signal fires once after content-layout has settled when the feature changes
signal doneLoading()

MouseArea {
anchors.fill: parent
Expand Down Expand Up @@ -271,6 +273,23 @@ Item {
}
}

Timer {
id: heightDebounceTimer
interval: 100
onTriggered: root.doneLoading()
}

onImplicitHeightChanged: {
heightDebounceTimer.restart()
}

Connections {
target: controller
function onFeatureLayerPairChanged() {
heightDebounceTimer.start()
}
}

QtObject {
id: internal

Expand Down
24 changes: 12 additions & 12 deletions app/qml/main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,16 @@ ApplicationWindow {
projDialog.open()
}

function identifyFeature( pair ) {
let hasNullGeometry = pair.feature.geometry.isNull
function identifyFeature( pair, point = null ) {
map.identifyLocation = point

if ( hasNullGeometry ) {
const skipPreview = __inputUtils.isEmptyGeometry( pair.feature.geometry )
if ( skipPreview ) {
formsStackManager.openForm( pair, "readOnly", "form" )
}
else if ( pair.valid ) {
map.highlightPair( pair )
formsStackManager.openForm( pair, "readOnly", "preview")
formsStackManager.openForm( pair, "readOnly", "preview" )
}
}

Expand Down Expand Up @@ -180,8 +181,8 @@ ApplicationWindow {
return 0
}

onFeatureIdentified: function( pair ) {
formsStackManager.openForm( pair, "readOnly", "preview" );
onFeatureIdentified: function( pair, point ) {
window.identifyFeature( pair, point )
}

onFeaturesIdentified: function( pairs ) {
Expand All @@ -190,6 +191,7 @@ ApplicationWindow {
}

onNothingIdentified: {
map.hideHighlight()
Comment thread
Withalion marked this conversation as resolved.
formsStackManager.closeDrawer()
}
Comment thread
uclaros marked this conversation as resolved.

Expand Down Expand Up @@ -860,8 +862,8 @@ ApplicationWindow {
closeDrawer()
}

onPreviewPanelChanged: function( panelHeight ) {
map.jumpToHighlighted( panelHeight - mapToolbar.height )
onPreviewPanelChanged: {
map.jumpToHighlighted()
}
Comment thread
uclaros marked this conversation as resolved.
}

Expand Down Expand Up @@ -952,15 +954,13 @@ ApplicationWindow {
drawerHeader.title: qsTr( "Select feature" )
list.model: MM.StaticFeaturesModel {}

list. delegate: MMListDelegate {
list.delegate: MMListDelegate {
text: model.FeatureTitle
secondaryText: model.LayerName
leftContent: MMIcon { source: model.LayerIcon }
onClicked: {
let pair = model.FeaturePair
featurePairSelection.close()
map.highlightPair( pair )
formsStackManager.openForm( pair, "readOnly", "preview" );
window.identifyFeature( model.FeaturePair );
}
}

Expand Down
Loading
Loading