From dea89956451ceae63ca91f3e0df860e919c0b6d6 Mon Sep 17 00:00:00 2001 From: Mustafa Comoglu Date: Thu, 23 Jul 2026 15:45:29 +1000 Subject: [PATCH] [gui] Add arrival Set active/inactive by distance/azimuth/residual Adds two submenus, reachable both from the arrival table and from every plot tab (Distance, Azimuth, Travel time, Move-out, Polar): "Set active by..." -> Distance range..., Residual threshold..., Azimuth range... -- prompts for a range/threshold and resets state: arrivals matching the criterion are activated, everything else is deactivated. Same mechanism as dragging a rectangle on the Distance plot tab (residualsSelected()), just precise/typed and available for azimuth and residual too, and not tied to which tab happens to be open. "Set inactive by..." -> same three criteria, but surgical: only deactivates arrivals matching the criterion, leaves everything else untouched. Useful for e.g. excluding a known-bad distance band or knocking out residual outliers without disturbing other manual choices. Distance respects SCScheme.unit.distanceInKM like the DISTANCE column. Residual threshold is a single |time residual| cutoff in seconds (not a range), matching how outlier-killing is normally framed. The plot tabs reach the same six OriginLocatorView actions via six new signals on OriginLocatorPlot (setActiveByDistanceRequested() etc.), emitted by PlotWidget's context menu and connected in the constructor -- same pattern already used for focalMechanismCommitted()/ autoInversionRequested(). The six actions themselves moved from a plain private: section to private slots: to make them connectable. --- .../gui/datamodel/originlocatorview.cpp | 493 ++++++++++++++++++ .../gui/datamodel/originlocatorview.h | 18 + .../gui/datamodel/originlocatorview_p.h | 11 + 3 files changed, 522 insertions(+) diff --git a/libs/seiscomp/gui/datamodel/originlocatorview.cpp b/libs/seiscomp/gui/datamodel/originlocatorview.cpp index fe59f1c1..b0510036 100644 --- a/libs/seiscomp/gui/datamodel/originlocatorview.cpp +++ b/libs/seiscomp/gui/datamodel/originlocatorview.cpp @@ -73,6 +73,8 @@ #include "ui_originlocatorview_commit.h" #include "ui_originlocatorview_comment.h" +#include +#include #include #include #include @@ -1144,6 +1146,68 @@ class NodalPlaneDialog : public QDialog { }; +// Generic min/max prompt used by the arrival table's "Set active by..." +// and "Set inactive by..." actions (distance, azimuth). infoText should +// describe what happens to arrivals inside vs. outside the range. +class RangeDialog : public QDialog { + public: + RangeDialog(const QString &title, const QString &infoText, + const QString &unit, double lowerBound, double upperBound, + double defaultMin, double defaultMax, + int decimals, QWidget *parent = 0) + : QDialog(parent) { + setWindowTitle(title); + + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + + QLabel *info = new QLabel(infoText, this); + info->setWordWrap(true); + vboxLayout->addWidget(info); + + QGridLayout *gridLayout = new QGridLayout(); + + gridLayout->addWidget(new QLabel(QString("Min (%1)").arg(unit), this), 0, 0); + sbMin = new QDoubleSpinBox(this); + sbMin->setDecimals(decimals); + sbMin->setRange(lowerBound, upperBound); + sbMin->setValue(defaultMin); + sbMin->setAlignment(Qt::AlignRight); + gridLayout->addWidget(sbMin, 0, 1); + + gridLayout->addWidget(new QLabel(QString("Max (%1)").arg(unit), this), 1, 0); + sbMax = new QDoubleSpinBox(this); + sbMax->setDecimals(decimals); + sbMax->setRange(lowerBound, upperBound); + sbMax->setValue(defaultMax); + sbMax->setAlignment(Qt::AlignRight); + gridLayout->addWidget(sbMax, 1, 1); + + vboxLayout->addLayout(gridLayout); + + QHBoxLayout *hboxLayout = new QHBoxLayout(); + QSpacerItem *spacerItem = new QSpacerItem(131, 31, QSizePolicy::Expanding, QSizePolicy::Minimum); + hboxLayout->addItem(spacerItem); + + QPushButton *okButton = new QPushButton(this); + okButton->setText(QApplication::translate("", "OK", 0)); + hboxLayout->addWidget(okButton); + + QPushButton *cancelButton = new QPushButton(this); + cancelButton->setText(QApplication::translate("", "Cancel", 0)); + hboxLayout->addWidget(cancelButton); + + vboxLayout->addLayout(hboxLayout); + + QObject::connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); + QObject::connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + } + + public: + QDoubleSpinBox *sbMin; + QDoubleSpinBox *sbMax; +}; + + class PlotWidget : public OriginLocatorPlot { public: enum ShapeType { @@ -1503,6 +1567,24 @@ class PlotWidget : public OriginLocatorPlot { protected: void updateContextMenu(QMenu &menu) { + menu.addSeparator(); + + QMenu *subSetActive = menu.addMenu("Set active by..."); + QAction *actSetActiveDistance = subSetActive->addAction("Distance range..."); + actSetActiveDistance->setData(2001); + QAction *actSetActiveResidual = subSetActive->addAction("Residual threshold..."); + actSetActiveResidual->setData(2003); + QAction *actSetActiveAzimuth = subSetActive->addAction("Azimuth range..."); + actSetActiveAzimuth->setData(2005); + + QMenu *subSetInactive = menu.addMenu("Set inactive by..."); + QAction *actSetInactiveDistance = subSetInactive->addAction("Distance range..."); + actSetInactiveDistance->setData(2002); + QAction *actSetInactiveResidual = subSetInactive->addAction("Residual threshold..."); + actSetInactiveResidual->setData(2004); + QAction *actSetInactiveAzimuth = subSetInactive->addAction("Azimuth range..."); + actSetInactiveAzimuth->setData(2006); + if ( !_customDraw ) return; menu.addSeparator(); QMenu *subShowStations = menu.addMenu("Draw station names"); @@ -1536,6 +1618,17 @@ class PlotWidget : public OriginLocatorPlot { void handleContextMenuAction(QAction *action) { OriginLocatorPlot::handleContextMenuAction(action); if ( action == nullptr ) return; + + switch ( action->data().toInt() ) { + case 2001: emit setActiveByDistanceRequested(); return; + case 2002: emit setInactiveByDistanceRequested(); return; + case 2003: emit setActiveByResidualRequested(); return; + case 2004: emit setInactiveByResidualRequested(); return; + case 2005: emit setActiveByAzimuthRequested(); return; + case 2006: emit setInactiveByAzimuthRequested(); return; + default: break; + } + if ( action->data().toInt() == 1000 ) { _drawStationNames = SNM_OFF; update(); @@ -3391,6 +3484,12 @@ void OriginLocatorView::init() { this, SLOT(commitFocalMechanism(bool))); connect(SC_D.residuals, SIGNAL(autoInversionRequested()), this, SLOT(autoInvertFocalMechanism())); + connect(SC_D.residuals, SIGNAL(setActiveByDistanceRequested()), this, SLOT(setActiveByDistance())); + connect(SC_D.residuals, SIGNAL(setInactiveByDistanceRequested()), this, SLOT(setInactiveByDistance())); + connect(SC_D.residuals, SIGNAL(setActiveByAzimuthRequested()), this, SLOT(setActiveByAzimuth())); + connect(SC_D.residuals, SIGNAL(setInactiveByAzimuthRequested()), this, SLOT(setInactiveByAzimuth())); + connect(SC_D.residuals, SIGNAL(setActiveByResidualRequested()), this, SLOT(setActiveByResidual())); + connect(SC_D.residuals, SIGNAL(setInactiveByResidualRequested()), this, SLOT(setInactiveByResidual())); connect(SC_D.map, SIGNAL(arrivalChanged(int,bool)), this, SLOT(changeArrival(int,bool))); connect(SC_D.map, SIGNAL(hoverArrival(int)), this, SLOT(hoverArrival(int))); @@ -8331,6 +8430,16 @@ void OriginLocatorView::tableArrivalsContextMenuRequested(const QPoint &pos) { actionDeactivate->setEnabled(false); } + QMenu *subSetActive = menu.addMenu("Set active by..."); + QAction *actionSetActiveDistance = subSetActive->addAction("Distance range..."); + QAction *actionSetActiveResidual = subSetActive->addAction("Residual threshold..."); + QAction *actionSetActiveAzimuth = subSetActive->addAction("Azimuth range..."); + + QMenu *subSetInactive = menu.addMenu("Set inactive by..."); + QAction *actionSetInactiveDistance = subSetInactive->addAction("Distance range..."); + QAction *actionSetInactiveResidual = subSetInactive->addAction("Residual threshold..."); + QAction *actionSetInactiveAzimuth = subSetInactive->addAction("Azimuth range..."); + menu.addSeparator(); QAction *actionRename = menu.addAction("Rename selected arrivals"); @@ -8368,6 +8477,18 @@ void OriginLocatorView::tableArrivalsContextMenuRequested(const QPoint &pos) { activateSelectedArrivals(Seismology::LocatorInterface::F_BACKAZIMUTH, false); else if ( result == actionDeactivateSlow ) activateSelectedArrivals(Seismology::LocatorInterface::F_SLOWNESS, false); + else if ( result == actionSetActiveDistance ) + setActiveByDistance(); + else if ( result == actionSetInactiveDistance ) + setInactiveByDistance(); + else if ( result == actionSetActiveResidual ) + setActiveByResidual(); + else if ( result == actionSetInactiveResidual ) + setInactiveByResidual(); + else if ( result == actionSetActiveAzimuth ) + setActiveByAzimuth(); + else if ( result == actionSetInactiveAzimuth ) + setInactiveByAzimuth(); else if ( result == actionInvertSelection ) selectArrivals(InvertFilter(SC_D.ui.tableArrivals->selectionModel())); @@ -8550,6 +8671,378 @@ void OriginLocatorView::activateSelectedArrivals(Seismology::LocatorInterface::F +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setActiveByDistance() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + // Match the unit already shown in the DISTANCE column so the entered + // values mean what the analyst sees in the table. + bool distanceInKM = SCScheme.unit.distanceInKM; + QString unit = distanceInKM ? "km" : QString::fromUtf8("\xC2\xB0"); + double upperBound = distanceInKM ? 20000.0 : 180.0; + + RangeDialog dlg( + tr("Set active by distance"), + tr("Arrivals within the range are activated, arrivals outside " + "the range are deactivated."), + unit, 0.0, upperBound, + SC_D.lastArrivalDistanceMin, SC_D.lastArrivalDistanceMax, + SCScheme.precision.distance, this + ); + + if ( dlg.exec() != QDialog::Accepted ) { + return; + } + + double min = dlg.sbMin->value(); + double max = dlg.sbMax->value(); + if ( min > max ) { + std::swap(min, max); + } + + SC_D.lastArrivalDistanceMin = min; + SC_D.lastArrivalDistanceMax = max; + + double minDeg = distanceInKM ? Math::Geo::km2deg(min) : min; + double maxDeg = distanceInKM ? Math::Geo::km2deg(max) : max; + + bool changed = false; + + // Same mechanism as dragging a rectangle on the Distance plot tab + // (see residualsSelected()): RestoreRole activates/deactivates while + // preserving the arrival's time/backazimuth/slowness flags where + // possible. + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double distance; + try { + distance = arr->distance(); + } + catch ( ... ) { + continue; + } + + bool inRange = distance >= minDeg && distance <= maxDeg; + int row = static_cast(i); + + if ( inRange != SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), + inRange ? 1 : 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setInactiveByDistance() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + bool distanceInKM = SCScheme.unit.distanceInKM; + QString unit = distanceInKM ? "km" : QString::fromUtf8("\xC2\xB0"); + double upperBound = distanceInKM ? 20000.0 : 180.0; + + RangeDialog dlg( + tr("Set inactive by distance"), + tr("Arrivals within the range are deactivated. Arrivals outside " + "the range are left untouched."), + unit, 0.0, upperBound, + SC_D.lastArrivalDistanceMin, SC_D.lastArrivalDistanceMax, + SCScheme.precision.distance, this + ); + + if ( dlg.exec() != QDialog::Accepted ) { + return; + } + + double min = dlg.sbMin->value(); + double max = dlg.sbMax->value(); + if ( min > max ) { + std::swap(min, max); + } + + SC_D.lastArrivalDistanceMin = min; + SC_D.lastArrivalDistanceMax = max; + + double minDeg = distanceInKM ? Math::Geo::km2deg(min) : min; + double maxDeg = distanceInKM ? Math::Geo::km2deg(max) : max; + + bool changed = false; + + // Surgical: only touches arrivals inside the range (deactivates them); + // arrivals outside are left exactly as they were. + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double distance; + try { + distance = arr->distance(); + } + catch ( ... ) { + continue; + } + + if ( distance < minDeg || distance > maxDeg ) { + continue; + } + + int row = static_cast(i); + + if ( SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setActiveByAzimuth() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + RangeDialog dlg( + tr("Set active by azimuth"), + tr("Arrivals within the range are activated, arrivals outside " + "the range are deactivated."), + QString::fromUtf8("\xC2\xB0"), 0.0, 360.0, + SC_D.lastArrivalAzimuthMin, SC_D.lastArrivalAzimuthMax, + 1, this + ); + + if ( dlg.exec() != QDialog::Accepted ) { + return; + } + + double min = dlg.sbMin->value(); + double max = dlg.sbMax->value(); + if ( min > max ) { + std::swap(min, max); + } + + SC_D.lastArrivalAzimuthMin = min; + SC_D.lastArrivalAzimuthMax = max; + + bool changed = false; + + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double azimuth; + try { + azimuth = arr->azimuth(); + } + catch ( ... ) { + continue; + } + + bool inRange = azimuth >= min && azimuth <= max; + int row = static_cast(i); + + if ( inRange != SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), + inRange ? 1 : 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setInactiveByAzimuth() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + RangeDialog dlg( + tr("Set inactive by azimuth"), + tr("Arrivals within the range are deactivated. Arrivals outside " + "the range are left untouched."), + QString::fromUtf8("\xC2\xB0"), 0.0, 360.0, + SC_D.lastArrivalAzimuthMin, SC_D.lastArrivalAzimuthMax, + 1, this + ); + + if ( dlg.exec() != QDialog::Accepted ) { + return; + } + + double min = dlg.sbMin->value(); + double max = dlg.sbMax->value(); + if ( min > max ) { + std::swap(min, max); + } + + SC_D.lastArrivalAzimuthMin = min; + SC_D.lastArrivalAzimuthMax = max; + + bool changed = false; + + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double azimuth; + try { + azimuth = arr->azimuth(); + } + catch ( ... ) { + continue; + } + + if ( azimuth < min || azimuth > max ) { + continue; + } + + int row = static_cast(i); + + if ( SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setActiveByResidual() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + bool ok; + double threshold = QInputDialog::getDouble( + this, tr("Set active by residual"), + tr("Activate arrivals with |time residual| within this many seconds, " + "deactivate the rest:"), + SC_D.lastArrivalResidualThreshold, 0.0, 120.0, 2, &ok + ); + if ( !ok ) { + return; + } + + SC_D.lastArrivalResidualThreshold = threshold; + + bool changed = false; + + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double residual; + try { + residual = fabs(arr->timeResidual()); + } + catch ( ... ) { + continue; + } + + bool inRange = residual <= threshold; + int row = static_cast(i); + + if ( inRange != SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), + inRange ? 1 : 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + +// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +void OriginLocatorView::setInactiveByResidual() { + if ( !SC_D.currentOrigin ) return; + if ( SC_D.currentOrigin->arrivalCount() == 0 ) return; + + bool ok; + double threshold = QInputDialog::getDouble( + this, tr("Set inactive by residual"), + tr("Deactivate arrivals with |time residual| beyond this many " + "seconds. Arrivals within the threshold are left untouched:"), + SC_D.lastArrivalResidualThreshold, 0.0, 120.0, 2, &ok + ); + if ( !ok ) { + return; + } + + SC_D.lastArrivalResidualThreshold = threshold; + + bool changed = false; + + // Surgical: only deactivates outliers beyond the threshold; arrivals + // within it are left exactly as they were. + for ( size_t i = 0; i < SC_D.currentOrigin->arrivalCount(); ++i ) { + Arrival *arr = SC_D.currentOrigin->arrival(i); + + double residual; + try { + residual = fabs(arr->timeResidual()); + } + catch ( ... ) { + continue; + } + + if ( residual <= threshold ) { + continue; + } + + int row = static_cast(i); + + if ( SC_D.modelArrivals.useArrival(row) ) { + changed = true; + } + + SC_D.modelArrivals.setData(SC_D.modelArrivals.index(row, USED), 0, RestoreRole); + } + + if ( changed ) { + startBlinking(QColor(255,128,0), SC_D.ui.btnRelocate); + } +} +// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + + + // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> void OriginLocatorView::renameArrivals() { RenamePhases dlg(this); diff --git a/libs/seiscomp/gui/datamodel/originlocatorview.h b/libs/seiscomp/gui/datamodel/originlocatorview.h index 3d554693..afbf3233 100644 --- a/libs/seiscomp/gui/datamodel/originlocatorview.h +++ b/libs/seiscomp/gui/datamodel/originlocatorview.h @@ -206,6 +206,17 @@ class SC_GUI_API OriginLocatorPlot : public DiagramWidget { QPoint pos = QPoint(0, 0)); void autoInversionRequested(); + // Forwarded from the plot's own right-click menu; OriginLocatorView + // connects these to its identically named arrival table actions + // (Set active by.../Set inactive by...) so the same functionality + // is reachable from any plot tab, not just the arrival table. + void setActiveByDistanceRequested(); + void setInactiveByDistanceRequested(); + void setActiveByAzimuthRequested(); + void setInactiveByAzimuthRequested(); + void setActiveByResidualRequested(); + void setInactiveByResidualRequested(); + protected slots: virtual void linkClicked(); @@ -393,6 +404,13 @@ class SC_GUI_API OriginLocatorView : public QWidget { void changeArrivalEnableState(int,bool); void artificialOriginRequested(double lat, double lon, double depth, Seiscomp::Core::Time time); + void setActiveByDistance(); + void setInactiveByDistance(); + void setActiveByAzimuth(); + void setInactiveByAzimuth(); + void setActiveByResidual(); + void setInactiveByResidual(); + void updateBlinkState(); void locatorProfileChanged(const QString &text); diff --git a/libs/seiscomp/gui/datamodel/originlocatorview_p.h b/libs/seiscomp/gui/datamodel/originlocatorview_p.h index 20cc28c6..165e39a5 100644 --- a/libs/seiscomp/gui/datamodel/originlocatorview_p.h +++ b/libs/seiscomp/gui/datamodel/originlocatorview_p.h @@ -130,6 +130,17 @@ class OriginLocatorViewPrivate { // Last auto-inversion result (used to populate FM fields on commit) Seismology::FMInversionResult lastFMResult; + // Last values entered in the arrival table's "Set active by..." / + // "Set inactive by..." dialogs. Session-local, not persisted. + // Distance is in whatever unit the DISTANCE column currently shows + // (deg or km, see SCScheme.unit.distanceInKM); azimuth is always + // degrees; residual threshold is |time residual| in seconds. + double lastArrivalDistanceMin{0.0}; + double lastArrivalDistanceMax{10.0}; + double lastArrivalAzimuthMin{0.0}; + double lastArrivalAzimuthMax{360.0}; + double lastArrivalResidualThreshold{2.0}; + friend class OriginLocatorView; };