diff --git a/README.md b/README.md index e995cf7e9..d7c4efb33 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ GCS project uses [APX Shared Library](https://github.com/uavos/apx-lib) submodul - [GStreamer](https://gstreamer.freedesktop.org) - used for video streaming by some plugins; - [SDL2](https://www.libsdl.org) - used for joystick interface by some plugins; - [Sparkle](https://sparkle-project.org/) - required for mac auto updates; +- [GDAL](https://gdal.org) - required for elevation map files processing; - [AppImageUpdate](https://github.com/AppImage/AppImageUpdate) - required for linux build, see installation in [Dockerfile](https://github.com/uavos/apx-gcs/blob/main/Dockerfile); ### CMAKE build diff --git a/src/Plugins/MapView/CMakeLists.txt b/src/Plugins/MapView/CMakeLists.txt index 435b1f8c0..576125f64 100644 --- a/src/Plugins/MapView/CMakeLists.txt +++ b/src/Plugins/MapView/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory("Location") add_subdirectory("MissionPlanner") add_subdirectory("Sites") add_subdirectory("KmlOverlay") +add_subdirectory("ElevationMap") diff --git a/src/Plugins/MapView/ElevationMap/CMakeLists.txt b/src/Plugins/MapView/ElevationMap/CMakeLists.txt new file mode 100644 index 000000000..ab98da75e --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/CMakeLists.txt @@ -0,0 +1,18 @@ +apx_plugin(QML_NO_PREFIX DEPENDS lib.ApxGcs QT Location Positioning) + +if(APPLE) + find_package(GDAL CONFIG) + if(NOT GDAL_FOUND) + find_package(GDAL REQUIRED) + endif() + target_link_libraries(${MODULE} PRIVATE GDAL::GDAL) +elseif(UNIX AND NOT APPLE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(GDAL REQUIRED gdal) + target_include_directories(${MODULE} PRIVATE ${GDAL_INCLUDE_DIRS}) + target_link_directories(${MODULE} PRIVATE ${GDAL_LIBRARY_DIRS}) + target_link_libraries(${MODULE} PRIVATE ${GDAL_LIBRARIES}) + +elseif(WIN32) + message(WARNING "Not implemented") +endif() diff --git a/src/Plugins/MapView/ElevationMap/ElevationDB.cpp b/src/Plugins/MapView/ElevationMap/ElevationDB.cpp new file mode 100644 index 000000000..f4ac5002a --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/ElevationDB.cpp @@ -0,0 +1,261 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "ElevationDB.h" +#include + +#include + +#include "cpl_minixml.h" +#include "cpl_string.h" +#include "gdal.h" +#include "gdal_version.h" +#include "ogr_spatialref.h" +#include +#include + +OfflineElevationDB::OfflineElevationDB(QString &path) + : m_dbPath(path) +{} + +double OfflineElevationDB::getElevationASTER(double latitude, double longitude) +{ + double elevation{NAN}; + auto fileName = createASTERFileName(latitude, longitude); + if (!QFile::exists(fileName)) { + return elevation; + } + + elevation = getElevationFromGeoFile(fileName, latitude, longitude); + return elevation; +} + +QString OfflineElevationDB::createASTERFileName(double latitude, double longitude) +{ + int lat = fabs(static_cast(latitude)); + int lon = fabs(static_cast(longitude)); + auto fileName = QString("ASTGTMV003_%1%2%3%4_dem.tif") + .arg((latitude >= 0) ? 'N' : 'S') + .arg((latitude >= 0 ? lat : ++lat), 2, 10, QChar('0')) + .arg((longitude >= 0) ? 'E' : 'W') + .arg((longitude >= 0 ? lon : ++lon), 3, 10, QChar('0')); + + auto path = QString("%1/%2").arg(m_dbPath).arg(fileName); + return path; +} + +double OfflineElevationDB::getElevation(double latitude, double longitude) +{ + return getElevationASTER(latitude, longitude); +} + +double OfflineElevationDB::getElevationFromGeoFile(QString fileName, + double latitude, + double longitude) +{ + char *srcSRS = nullptr; + std::vector anBandList; + double elevation{NAN}; + int nOverview = -1; + + GDALAllRegister(); + CPLFree(srcSRS); + srcSRS = SanitizeSRS("WGS84"); + + if (!srcSRS) + return NAN; + + auto src = fileName.toStdString(); + auto srcFilename = src.c_str(); + + // Open source file + GDALDatasetH hSrcDS = GDALOpenEx(srcFilename, + GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, + nullptr, + nullptr, + nullptr); + + if (hSrcDS == nullptr) + return NAN; + + // Setup coordinate transformation, if required + OGRSpatialReferenceH hSrcSRS = nullptr; + OGRCoordinateTransformationH hCT = nullptr; + if (srcSRS != nullptr && !EQUAL(srcSRS, "-geoloc")) { + hSrcSRS = OSRNewSpatialReference(srcSRS); + OSRSetAxisMappingStrategy(hSrcSRS, OAMS_TRADITIONAL_GIS_ORDER); + auto hTrgSRS = GDALGetSpatialRef(hSrcDS); + if (!hTrgSRS) + return NAN; + + hCT = OCTNewCoordinateTransformation(hSrcSRS, hTrgSRS); + if (hCT == nullptr) + return NAN; + } + + // If no bands were requested, we will query them all + if (anBandList.empty()) { + for (int i = 0; i < GDALGetRasterCount(hSrcDS); i++) + anBandList.push_back(i + 1); + } + + // Turn the location into a pixel and line location. + bool inputAvailable = true; + double dfGeoX = longitude; + double dfGeoY = latitude; + + while (inputAvailable) { + int iPixel; + int iLine; + + if (hCT) { + if (!OCTTransform(hCT, 1, &dfGeoX, &dfGeoY, nullptr)) + return NAN; + } + + if (srcSRS != nullptr) { + double adfGeoTransform[6] = {}; + if (GDALGetGeoTransform(hSrcDS, adfGeoTransform) != CE_None) { + apxMsgW() << tr("Error %1. Cannot get geotransform.").arg(CPLE_AppDefined); + return NAN; + } + + double adfInvGeoTransform[6] = {}; + if (!GDALInvGeoTransform(adfGeoTransform, adfInvGeoTransform)) { + apxMsgW() << tr("Error %1. Cannot invert geotransform.").arg(CPLE_AppDefined); + return NAN; + } + + iPixel = static_cast(floor(adfInvGeoTransform[0] + adfInvGeoTransform[1] * dfGeoX + + adfInvGeoTransform[2] * dfGeoY)); + iLine = static_cast(floor(adfInvGeoTransform[3] + adfInvGeoTransform[4] * dfGeoX + + adfInvGeoTransform[5] * dfGeoY)); + } else { + iPixel = static_cast(floor(dfGeoX)); + iLine = static_cast(floor(dfGeoY)); + } + + CPLString osLine; + bool bPixelReport = true; + + if (iPixel < 0 || iLine < 0 || iPixel >= GDALGetRasterXSize(hSrcDS) + || iLine >= GDALGetRasterYSize(hSrcDS)) { + apxMsgW() << tr("Location is off this file! No further details to report."); + bPixelReport = false; + } + + // Process each band + for (int i = 0; bPixelReport && i < static_cast(anBandList.size()); i++) { + GDALRasterBandH hBand = GDALGetRasterBand(hSrcDS, anBandList[i]); + int iPixelToQuery = iPixel; + int iLineToQuery = iLine; + + if (nOverview >= 0 && hBand != nullptr) { + GDALRasterBandH hOvrBand = GDALGetOverview(hBand, nOverview); + if (hOvrBand != nullptr) { + auto nOvrXSize = GDALGetRasterBandXSize(hOvrBand); + auto nOvrYSize = GDALGetRasterBandYSize(hOvrBand); + iPixelToQuery = static_cast( + 0.5 + 1.0 * iPixel / GDALGetRasterXSize(hSrcDS) * nOvrXSize); + iLineToQuery = static_cast( + 0.5 + 1.0 * iLine / GDALGetRasterYSize(hSrcDS) * nOvrYSize); + if (iPixelToQuery >= nOvrXSize) + iPixelToQuery = nOvrXSize - 1; + if (iLineToQuery >= nOvrYSize) + iLineToQuery = nOvrYSize - 1; + } else { + apxMsgW() << tr("Error %1. Cannot get overview %2 of band %3") + .arg(CPLE_AppDefined) + .arg(nOverview + 1) + .arg(anBandList[i]); + } + hBand = hOvrBand; + } + + if (hBand == nullptr) + continue; + + double adfPixel[2] = {0, 0}; + const bool bIsComplex = CPL_TO_BOOL(GDALDataTypeIsComplex(GDALGetRasterDataType(hBand))); + + if (GDALRasterIO(hBand, + GF_Read, + iPixelToQuery, + iLineToQuery, + 1, + 1, + adfPixel, + 1, + 1, + bIsComplex ? GDT_CFloat64 : GDT_Float64, + 0, + 0) + == CE_None) { + CPLString osValue; + + if (bIsComplex) + osValue.Printf("%.15g+%.15gi", adfPixel[0], adfPixel[1]); + else + osValue.Printf("%.15g", adfPixel[0]); + + elevation = std::stod(osValue.c_str()); + } + } + + if ((latitude != NAN && longitude != NAN) + || (fscanf(stdin, "%lf %lf", &dfGeoX, &dfGeoY) != 2)) { + inputAvailable = false; + } + } + + // Cleanup + if (hCT) { + OSRDestroySpatialReference(hSrcSRS); + OCTDestroyCoordinateTransformation(hCT); + } + + GDALClose(hSrcDS); + GDALDumpOpenDatasets(stderr); + GDALDestroyDriverManager(); + CPLFree(srcSRS); + + return elevation; +} + +char *OfflineElevationDB::SanitizeSRS(const char *userInput) +{ + OGRSpatialReferenceH hSRS = OSRNewSpatialReference(nullptr); + char *result = nullptr; + + if (OSRSetFromUserInput(hSRS, userInput) == OGRERR_NONE) + OSRExportToWkt(hSRS, &result); + else { + apxMsgW() << tr("Error %1. Translating source or target SRS failed: %2") + .arg(CPLE_AppDefined) + .arg(userInput); + return nullptr; + } + + OSRDestroySpatialReference(hSRS); + + return result; +} diff --git a/src/Plugins/MapView/ElevationMap/ElevationDB.h b/src/Plugins/MapView/ElevationMap/ElevationDB.h new file mode 100644 index 000000000..2fd8f626b --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/ElevationDB.h @@ -0,0 +1,52 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include + +class AbstractElevationDB : public QObject +{ + Q_OBJECT + +public: + AbstractElevationDB() = default; + virtual double getElevation(double latitude, double longitude) = 0; +}; + +class OfflineElevationDB : public AbstractElevationDB +{ + Q_OBJECT + +public: + OfflineElevationDB(QString &path); + double getElevation(double latitude, double longitude) override; + double getElevationASTER(double latitude, + double longitude); // Return NaN if the elevation is undefined + +private: + QString m_dbPath; + + QString createASTERFileName(double latitude, double longitude); + double getElevationFromGeoFile(QString fileName, double latitude, double longitude); + char *SanitizeSRS(const char *userInput); +}; diff --git a/src/Plugins/MapView/ElevationMap/ElevationMap.cpp b/src/Plugins/MapView/ElevationMap/ElevationMap.cpp new file mode 100644 index 000000000..ad16f51d5 --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/ElevationMap.cpp @@ -0,0 +1,77 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "ElevationMap.h" +#include + +ElevationMap::ElevationMap(Fact *parent) + : Fact(parent, + QString(PLUGIN_NAME).toLower(), + tr("Elevation Map"), + tr("Terrain elevation map"), + Group | FlatModel, + "elevation-rise") +{ + m_elevation = qQNaN(); + auto path = AppDirs::db().absolutePath() + "/Elevation"; + + f_use = new Fact(this, "use", tr("Use elevation map"), "", Fact::Bool, "check"); + f_use->setValue(true); + + f_path = new Fact(this, "open", tr("Path"), tr("Elevation files path"), Text, "import"); + f_path->setValue(path); + connect(f_path, &Fact::valueChanged, this, &ElevationMap::createElevationDatabase); + + createElevationDatabase(); + // qml = loadQml("qrc:/ElevationPlugin.qml"); +} + +double ElevationMap::elevation() const +{ + return m_elevation; +} + +void ElevationMap::setElevation(double v) +{ + if (m_elevation == v) + return; + + m_elevation = v; + emit elevationChanged(); +} + +void ElevationMap::setElevationByCoordinate(const QGeoCoordinate &v) +{ + auto elevation = m_elevationDB->getElevation(v.latitude(), v.longitude()); + setElevation(elevation); +} + +double ElevationMap::getElevationByCoordinate(const QGeoCoordinate &v) +{ + return m_elevationDB->getElevation(v.latitude(), v.longitude()); +} + +void ElevationMap::createElevationDatabase() +{ + auto path = f_path->value().toString(); + m_elevationDB = std::make_shared(path); +} diff --git a/src/Plugins/MapView/ElevationMap/ElevationMap.h b/src/Plugins/MapView/ElevationMap/ElevationMap.h new file mode 100644 index 000000000..6b5d9102f --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/ElevationMap.h @@ -0,0 +1,66 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "ElevationDB.h" +#include + +#include + +// #include +#include +#include + +class ElevationMap : public Fact +{ + Q_OBJECT + Q_PROPERTY(double elevation READ elevation WRITE setElevation NOTIFY elevationChanged) + // Q_PROPERTY(QGeoCoordinate clickCoordinate READ clickCoordinate WRITE setClickCoordinate NOTIFY + // clickCoordinateChanged) + +public: + explicit ElevationMap(Fact *parent = nullptr); + + Fact *f_use; + Fact *f_path; + + double elevation() const; + void setElevation(double v); + // QGeoCoordinate clickCoordinate() const; + // void setClickCoordinate(const QGeoCoordinate &v); + + Q_INVOKABLE void setElevationByCoordinate(const QGeoCoordinate &v); + Q_INVOKABLE double getElevationByCoordinate(const QGeoCoordinate &v); + +protected: + double m_elevation; + // QGeoCoordinate m_clickCoordinate; + +private: + std::shared_ptr m_elevationDB; + + void createElevationDatabase(); + // QObject *qml; + +signals: + void elevationChanged(); +}; diff --git a/src/Plugins/MapView/ElevationMap/ElevationPlugin.h b/src/Plugins/MapView/ElevationMap/ElevationPlugin.h new file mode 100644 index 000000000..94e157d81 --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/ElevationPlugin.h @@ -0,0 +1,37 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "ElevationMap.h" +#include +#include + +class ElevationPlugin : public PluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "com.uavos.gcs.PluginInterface/1.0") + Q_INTERFACES(PluginInterface) +public: + int flags() override { return Feature | Map; } + QObject *createControl() override { return new ElevationMap(); } + QStringList depends() override { return QStringList() << "Location"; } +}; diff --git a/src/Plugins/MapView/ElevationMap/README.md b/src/Plugins/MapView/ElevationMap/README.md new file mode 100644 index 000000000..767024310 --- /dev/null +++ b/src/Plugins/MapView/ElevationMap/README.md @@ -0,0 +1,7 @@ +--- +page: plugins +--- + +# Elevation + +Map plugin to determine the elevation of the terrain above sea level. diff --git a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Common/MapObject.qml b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Common/MapObject.qml index bae428f10..bcb003ff9 100755 --- a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Common/MapObject.qml +++ b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Common/MapObject.qml @@ -30,6 +30,7 @@ MapQuickItem { //to be used inside MapComponent only property bool interactive: visibleOnMap property bool draggable: true property bool shadow: true + property bool alarmed: false property int implicitZ: 0 @@ -149,6 +150,24 @@ MapQuickItem { //to be used inside MapComponent only Item { width: textItem.width height: textItem.height + Loader { + anchors.centerIn: textItem + active: alarmed + // asynchronous: true + sourceComponent: Component { + Rectangle { + id: alarm + width: textItem.width*textItem.scale+10 + height: textItem.height*textItem.scale+10 + antialiasing: true + border.width: 2 + opacity: 0.8 + border.color: "#FF0000" + radius: shadow?height/10:0 + color: "#FF0000" + } + } + } Loader { anchors.centerIn: textItem active: selected diff --git a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Controls/MapInfo.qml b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Controls/MapInfo.qml index 1ebba813e..e21b81dbd 100755 --- a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Controls/MapInfo.qml +++ b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Controls/MapInfo.qml @@ -183,4 +183,39 @@ RowLayout { } } + Item { + id: elevationItem + visible: apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value + property var elevation: visible ? apx.tools.elevationmap.elevation : NaN + property var color: isNaN(elevation) ? "#f00" : "#fff" + + Layout.alignment: Qt.AlignVCenter + implicitHeight: control.size + implicitWidth: Math.max(elevationIcon.width+elevationText.implicitWidth, height*4) + + MaterialIcon { + id: elevationIcon + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + name: "elevation-rise" + color: elevationItem.color + size: height + } + Text { + id: elevationText + anchors.left: elevationIcon.right + anchors.top: parent.top + anchors.bottom: parent.bottom + verticalAlignment: Text.AlignVCenter + font: apx.font_narrow(Style.fontSize) + color: elevationItem.color + text: isNaN(elevationItem.elevation) ? "NO" : elevationItem.elevation + "m" + } + ToolTipArea { + text: qsTr("Point elevation above sea level") + cursorShape: Qt.PointingHandCursor + } + } + } diff --git a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/MapControl/MapBase.qml b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/MapControl/MapBase.qml index 978a038dc..7eeb17460 100755 --- a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/MapControl/MapBase.qml +++ b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/MapControl/MapBase.qml @@ -292,6 +292,9 @@ Map { lastX = mouse.x lastY = mouse.y } + + if(apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value) + apx.tools.elevationmap.setElevationByCoordinate(toCoordinate(Qt.point(mouse.x, mouse.y))); } onClicked: (mouse) => { diff --git a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Mission/WaypointItem.qml b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Mission/WaypointItem.qml index a339f5a2c..c180f2fa9 100755 --- a/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Mission/WaypointItem.qml +++ b/src/Plugins/MapView/MissionPlanner/qml/Apx/Map/Mission/WaypointItem.qml @@ -30,8 +30,8 @@ import APX.Mission as APX MissionObject { id: waypointItem - color: visibleOnMap?Style.cWaypoint:"yellow" - textColor: "black" + color: visibleOnMap?(alarmed?"#ffdead":Style.cWaypoint):"yellow" + textColor: alarmed?"#ff0000":"black" fact: modelData implicitZ: 50 @@ -62,6 +62,32 @@ MissionObject { property bool showDetails: interacting || active || f_distance===0 || (map.metersToPixelsFactor*f_distance)>150 + // Unsafe AGL alarm + property var alarmOn: apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value + property var coordinate: fact?fact.coordinate:0 + property var altitude: fact?fact.child("altitude").value:0 + property var homeHmsl: mandala.est.ref.hmsl.value + property var agl: fact?fact.child("agl").value:0 + property var elevation: NaN + + onCoordinateChanged: alarmed = alarm() + onAltitudeChanged: alarmed = alarm() + onHomeHmslChanged: alarmed = alarm() + onAlarmOnChanged: alarmed = alarm() + + function alarm() + { + if(!fact) + return false + if(!alarmOn) + return false + elevation = apx.tools.elevationmap.getElevationByCoordinate(coordinate) + if(isNaN(elevation)) + return false + agl = homeHmsl + altitude - elevation + return agl < fact.unsafeAgl + } + //property bool pathVisibleOnMap: true //property Item pathItem //visible: mission.visible && (visibleOnMap || pathVisibleOnMap) diff --git a/src/Plugins/MapView/Sites/SiteEdit.cpp b/src/Plugins/MapView/Sites/SiteEdit.cpp index 8fbeb6adc..87bab7f24 100644 --- a/src/Plugins/MapView/Sites/SiteEdit.cpp +++ b/src/Plugins/MapView/Sites/SiteEdit.cpp @@ -32,12 +32,12 @@ SiteEdit::SiteEdit(Fact *parent, , blockUpdateItemData(false) { f_title = new Fact(this, "sname", tr("Site name"), tr("Label for geographic area"), Text); - f_latitude = new Fact(this, "latitude", tr("Latitude"), tr("Global postition latitude"), Float); + f_latitude = new Fact(this, "latitude", tr("Latitude"), tr("Global position latitude"), Float); f_latitude->setUnits("lat"); f_longitude = new Fact(this, "longitude", tr("Longitude"), - tr("Global postition longitude"), + tr("Global position longitude"), Float); f_longitude->setUnits("lon"); diff --git a/src/lib/ApxGcs/Mission/MissionItem.cpp b/src/lib/ApxGcs/Mission/MissionItem.cpp index 6d1260882..3e5306ed3 100644 --- a/src/lib/ApxGcs/Mission/MissionItem.cpp +++ b/src/lib/ApxGcs/Mission/MissionItem.cpp @@ -52,13 +52,13 @@ MissionItem::MissionItem(MissionGroup *parent, f_latitude = new MissionField(this, "lat", tr("Latitude"), - tr("Global postition latitude"), + tr("Global position latitude"), Float); f_latitude->setUnits("lat"); f_longitude = new MissionField(this, "lon", tr("Longitude"), - tr("Global postition longitude"), + tr("Global position longitude"), Float); f_longitude->setUnits("lon"); diff --git a/src/lib/ApxGcs/Mission/Poi.cpp b/src/lib/ApxGcs/Mission/Poi.cpp index ba219356d..bb930028b 100644 --- a/src/lib/ApxGcs/Mission/Poi.cpp +++ b/src/lib/ApxGcs/Mission/Poi.cpp @@ -31,6 +31,7 @@ Poi::Poi(MissionGroup *parent) f_hmsl = new MissionField(this, "hmsl", tr("HMSL"), tr("Object of interest altitude MSL"), Int); f_hmsl->setUnits("m"); f_hmsl->setEnumStrings(QStringList() << "ground"); + f_hmsl->setOpt("extrainfo", "ExtraInfoElevation.qml"); f_radius = new MissionField(this, "radius", tr("Radius"), tr("Loiter radius"), Int); f_radius->setUnits("m"); diff --git a/src/lib/ApxGcs/Mission/Runway.cpp b/src/lib/ApxGcs/Mission/Runway.cpp index ae2135ed3..b42a84089 100644 --- a/src/lib/ApxGcs/Mission/Runway.cpp +++ b/src/lib/ApxGcs/Mission/Runway.cpp @@ -48,6 +48,7 @@ Runway::Runway(MissionGroup *parent) Int); f_hmsl->setUnits("m"); f_hmsl->setEnumStrings(QStringList() << "default"); + f_hmsl->setOpt("extrainfo", "ExtraInfoElevation.qml"); f_dN = new MissionField(this, "dN", tr("Delta North"), tr("Runway direction point (north)"), Int); f_dN->setUnits("m"); diff --git a/src/lib/ApxGcs/Mission/Waypoint.cpp b/src/lib/ApxGcs/Mission/Waypoint.cpp index e012c2b71..41f79f137 100644 --- a/src/lib/ApxGcs/Mission/Waypoint.cpp +++ b/src/lib/ApxGcs/Mission/Waypoint.cpp @@ -29,14 +29,28 @@ Waypoint::Waypoint(MissionGroup *parent) , m_bearing(0) , m_reachable(false) , m_warning(false) + , m_chosen(ALT) { - f_altitude = new MissionField(this, "altitude", tr("Altitude"), tr("Altitude above ground"), Int); + f_altitude = new MissionField(this, "altitude", tr("Altitude"), tr("Altitude above home"), Int); f_altitude->setUnits("m"); + f_altitude->setOpt("extrainfo", "ExtraInfoAltitude.qml"); + connect(f_altitude, &Fact::triggered, this, [this]() { this->setChosen(ALT); }); + + f_agl = new MissionField(this, "agl", tr("AGL"), tr("Altitude above ground level"), Int); + f_agl->setUnits("m"); + f_agl->setDefaultValue(0); + f_agl->setOpt("extrainfo", "ExtraInfoAgl.qml"); + connect(f_agl, &Fact::triggered, this, [this]() { this->setChosen(AGL); }); + + f_amsl = new MissionField(this, "amsl", tr("AMSL"), tr("Altitude above sea level"), Int); + f_amsl->setUnits("m"); + f_amsl->setDefaultValue(0); + f_amsl->setOpt("extrainfo", "ExtraInfoAmsl.qml"); + connect(f_amsl, &Fact::triggered, this, [this]() { this->setChosen(AMSL); }); f_type = new MissionField(this, "type", tr("Type"), tr("Maneuver type"), Enum); f_type->setEnumStrings(QStringList() << "direct" << "track"); - //actions f_actions = new WaypointActions(this); @@ -212,3 +226,21 @@ void Waypoint::setWarning(bool v) m_warning = v; emit warningChanged(); } + +Waypoint::ChosenFact Waypoint::chosen() const +{ + return m_chosen; +} + +void Waypoint::setChosen(ChosenFact v) +{ + if (m_chosen == v) + return; + m_chosen = v; + emit chosenChanged(); +} + +int Waypoint::unsafeAgl() const +{ + return UNSAFE_AGL; +} diff --git a/src/lib/ApxGcs/Mission/Waypoint.h b/src/lib/ApxGcs/Mission/Waypoint.h index 4cb9423eb..7e2dc416f 100644 --- a/src/lib/ApxGcs/Mission/Waypoint.h +++ b/src/lib/ApxGcs/Mission/Waypoint.h @@ -33,11 +33,22 @@ class Waypoint : public MissionItem Q_PROPERTY(bool reachable READ reachable WRITE setReachable NOTIFY reachableChanged) Q_PROPERTY(bool warning READ warning WRITE setWarning NOTIFY warningChanged) + Q_PROPERTY(ChosenFact chosen READ chosen WRITE setChosen NOTIFY chosenChanged) + Q_PROPERTY(int unsafeAgl READ unsafeAgl CONSTANT) public: + enum ChosenFact { + ALT = 0, + AGL, + AMSL, + }; + Q_ENUM(ChosenFact) + explicit Waypoint(MissionGroup *parent); Fact *f_altitude; + Fact *f_agl; + Fact *f_amsl; Fact *f_type; WaypointActions *f_actions; @@ -61,11 +72,19 @@ private slots: bool warning() const; void setWarning(bool v); + ChosenFact chosen() const; + void setChosen(ChosenFact v); + + int unsafeAgl() const; + protected: + static const int UNSAFE_AGL = 100; // Suggested by the CEO + ChosenFact m_chosen; bool m_reachable; bool m_warning; signals: void reachableChanged(); void warningChanged(); + void chosenChanged(); }; diff --git a/src/lib/ApxGcs/Telemetry/TelemetryPlayer.cpp b/src/lib/ApxGcs/Telemetry/TelemetryPlayer.cpp index 561e6caa4..184918728 100644 --- a/src/lib/ApxGcs/Telemetry/TelemetryPlayer.cpp +++ b/src/lib/ApxGcs/Telemetry/TelemetryPlayer.cpp @@ -49,7 +49,7 @@ TelemetryPlayer::TelemetryPlayer(Telemetry *telemetry, Fact *parent) &TelemetryPlayer::setCacheId); connect(this, &Fact::activeChanged, this, &TelemetryPlayer::updateActive); - f_time = new Fact(this, "time", tr("Time"), tr("Current postition"), Int); + f_time = new Fact(this, "time", tr("Time"), tr("Current position"), Int); f_time->setMin((quint64) 0); f_time->setUnits("ms"); connect(f_time, &Fact::valueChanged, this, &TelemetryPlayer::updateTime); diff --git a/src/main/qml/Apx/Common/Button/ButtonBase.qml b/src/main/qml/Apx/Common/Button/ButtonBase.qml index fabe9b1c1..838ac8951 100644 --- a/src/main/qml/Apx/Common/Button/ButtonBase.qml +++ b/src/main/qml/Apx/Common/Button/ButtonBase.qml @@ -49,7 +49,7 @@ Button { // geometry - Material.roundedScale: height/32 + Material.roundedScale: Material.ExtraSmallScale padding: height/32 spacing: height/20 diff --git a/src/main/qml/Apx/Common/FactButton/EditorIntWithFeet.qml b/src/main/qml/Apx/Common/FactButton/EditorIntWithFeet.qml new file mode 100644 index 000000000..c67ff680e --- /dev/null +++ b/src/main/qml/Apx/Common/FactButton/EditorIntWithFeet.qml @@ -0,0 +1,234 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +import Apx.Common + +SpinBox { + id: editor + property var opts: fact.opts + property bool isFeet: !fact.parentFact.isFeet ? false : true + + hoverEnabled: true + + from: (typeof fact.min!=='undefined')?fact.min*div:-1000000000 + to: (typeof fact.max!=='undefined')?fact.max*div:1000000000 + + property real div: 1 + + value: !isFeet ? fact.value * div : opts.ft * div + + readonly property real precision: fact.precision + + font: apx.font_narrow(factButton.valueSize) + + property int wgrow: implicitWidth + onImplicitWidthChanged: { + if(implicitWidth0?fact.increment*div:1 + + stepSize: defaultStepSize * stepMult + + up.onPressedChanged: start() + down.onPressedChanged: start() + + property int elapsed: 0 + property real startTime: 0 + function start(inc) + { + if(activeFocus) + factButton.forceActiveFocus() + + if(up.pressed || down.pressed) + startTime = new Date().getTime() + else{ + startTime = 0 + elapsed = 0 + } + } + property int stepMult: + elapsed>6 + ? 100 + : elapsed>2 + ? 10 + : 1 + + contentItem: Item{ + implicitWidth: isFeet ? textInputFt.width : textInput.width + + TextInput { + id: textInputFt + visible: isFeet + validator: IntValidator{bottom: editor.from; top: editor.to} + + anchors.centerIn: parent + font: editor.font + + color: activeFocus?Material.color(Material.Yellow):Material.primaryTextColor + text: opts.ft + " ft" + + activeFocusOnTab: true + + width: Math.max(contentWidth, height*3) + + selectByMouse: true + onEditingFinished: { + opts.ft = text + fact.opts = opts + fact.setValue(ft2m(text)) + console.log("onEditingFinished - isFeet", text, "-", fact.opts.ft) + factButton.forceActiveFocus(); + } + onActiveFocusChanged: { + if(activeFocus){ + text=opts.ft + selectAll(); + }else{ + text=Qt.binding(function(){return opts.ft + " ft"}) + } + } + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + Rectangle { + z: parent.z-1 + visible: fact.enabled + anchors.centerIn: parent + width: parent.width+Style.spacing*2 + height: parent.height + radius: height/10 + color: "#000" + border.width: 0 + opacity: 0.3 + } + } + TextInput { + id: textInput + visible: !textInputFt.visible + + anchors.centerIn: parent + font: editor.font + + color: activeFocus?Material.color(Material.Yellow):Material.primaryTextColor + text: fact.text + activeFocusOnTab: true + + width: Math.max(contentWidth, height*3) + + selectByMouse: true + onEditingFinished: { + fact.setValue(text) + opts.ft = m2ft(fact.value) + fact.opts = opts; + console.log("onEditingFinished - isFeet", text, "-", fact.opts.ft) + + factButton.forceActiveFocus(); + } + onActiveFocusChanged: { + if(activeFocus){ + text=fact.editorText() + selectAll(); + }else{ + text=Qt.binding(function(){return fact.text}) + } + } + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + Rectangle { + z: parent.z-1 + visible: fact.enabled + anchors.centerIn: parent + width: parent.width+Style.spacing*2 + height: parent.height + radius: height/10 + color: "#000" + border.width: 0 + opacity: 0.3 + } + } + } + + padding: 0 + spacing: 0 + topPadding: 0 + bottomPadding: 0 + baselineOffset: 0 + + background: Item { + implicitWidth: editor.height*3 + } + + leftPadding: 0 + rightPadding: 0 + leftInset: 0 + rightInset: 0 + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + contentItem.implicitWidth + + height * 2 + Style.spacing*4) + + onValueModified: { + var v=value + v -= v % stepSize + v /= div + + console.log("2. onValueModified - ", v) + + if(!isFeet) { + fact.setValue(v) + opts.ft = m2ft(v) + fact.opts = opts + } else { + opts.ft = v + fact.opts = opts + fact.setValue(ft2m(v)) + } + + value=Qt.binding(function(){return Math.round(!isFeet ? fact.value*div : opts.ft*div)}) + + console.log("3. onValueModified - ", value) + + // accelerate + elapsed = startTime>0?(new Date().getTime()-startTime)/1000:0 + } + + ToolTip { + parent: editor + visible: hovered + font: parent.font + implicitHeight: parent.height + topPadding: (implicitHeight-implicitContentHeight)/2 + text: isFeet ? textInput.text : textInputFt.text + x: parent.x + parent.implicitWidth + 7*ui.scale + y: parent.y + background: Rectangle { + color: "#404040" + border.color: "#d3d3d3" + radius: height/10 + } + onVisibleChanged: console.log(height,"-",implicitHeight,"-",implicitBackgroundHeight,"-",implicitContentHeight,) + } +} diff --git a/src/main/qml/Apx/Common/FactButton/ExtraInfoAgl.qml b/src/main/qml/Apx/Common/FactButton/ExtraInfoAgl.qml new file mode 100644 index 000000000..400187297 --- /dev/null +++ b/src/main/qml/Apx/Common/FactButton/ExtraInfoAgl.qml @@ -0,0 +1,84 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +import Apx.Common +import APX.Mission + +Item { + id: item + visible: apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value + property var map: apx.tools.elevationmap + property var altitude: fact.parentFact.child("altitude").value + property var homeHmsl: mandala.est.ref.hmsl.value + property var coordinate: fact.parentFact.coordinate + property var elevation: NaN + property var color: isNaN(elevation) ? "#dc143c" : "#32cd32" + property bool chosen: fact.parentFact.chosen == Waypoint.AGL + + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + implicitHeight: parent.height + implicitWidth: Math.max(icon.width+text.implicitWidth, height*4) + + MaterialIcon { + id: icon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + name: "elevation-rise" + color: item.color + size: text.contentHeight + verticalAlignment: Text.AlignVCenter + } + Text { + id: text + anchors.left: icon.right + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + font: apx.font_narrow(Style.fontSize) + color: item.color + text: isNaN(item.elevation) ? "NO" : item.elevation + "m" + } + + onVisibleChanged: fact.parentFact.chosen = Waypoint.ALT + onChosenChanged: _editor.enabled = chosen + onAltitudeChanged: aglProcessing() + + Component.onCompleted: { + _editor.enabled = chosen + if(visible) + elevation = map.getElevationByCoordinate(coordinate) + aglProcessing() + } + + function aglProcessing() + { + if(isNaN(elevation)) { + fact.value = 0 + return + } + fact.value = homeHmsl + altitude - elevation; + factButton.color = fact.value < fact.parentFact.unsafeAgl ? Material.color(Material.Red) : action_color() + } +} diff --git a/src/main/qml/Apx/Common/FactButton/ExtraInfoAltitude.qml b/src/main/qml/Apx/Common/FactButton/ExtraInfoAltitude.qml new file mode 100644 index 000000000..2cd13dd0e --- /dev/null +++ b/src/main/qml/Apx/Common/FactButton/ExtraInfoAltitude.qml @@ -0,0 +1,97 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +import Apx.Common +import APX.Mission + +Item { + id: item + visible: apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value + + property var map: apx.tools.elevationmap + property var agl: fact.parentFact.child("agl").value + property var amsl: fact.parentFact.child("amsl").value + property var coordinate: fact.parentFact.coordinate + property var homeHmsl: mandala.est.ref.hmsl.value + property var color: "#dcdcdc" + property var elevation: NaN + property bool chosen: fact.parentFact.chosen == Waypoint.ALT + + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + implicitHeight: parent.height + implicitWidth: Math.max(icon.width+text.implicitWidth, height*4) + + MaterialIcon { + id: icon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + name: "home-map-marker" + color: item.color + size: text.contentHeight + verticalAlignment: Text.AlignVCenter + } + Text { + id: text + anchors.left: icon.right + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + font: apx.font_narrow(Style.fontSize) + color: item.color + text: homeHmsl + "m" + } + + onChosenChanged: _editor.enabled = chosen + onAglChanged: altitudeProcessing() + onAmslChanged: altitudeProcessing() + onHomeHmslChanged: altitudeProcessing() + + Component.onCompleted: { + _editor.enabled = chosen + if(visible) + elevation = map.getElevationByCoordinate(coordinate) + + // Proposal to ON/OFF AGL and HTML when elevation map is disabled + if(!visible){ + fact.parentFact.child("agl").visible = false + fact.parentFact.child("amsl").visible = false + } else { + fact.parentFact.child("agl").visible = true + fact.parentFact.child("amsl").visible = true + } + } + + function altitudeProcessing() + { + if(isNaN(elevation)) + return + if(chosen) + return + if (fact.parentFact.chosen == Waypoint.AGL) + fact.value = elevation + agl - homeHmsl + if (fact.parentFact.chosen == Waypoint.AMSL) + fact.value = amsl - homeHmsl + } +} diff --git a/src/main/qml/Apx/Common/FactButton/ExtraInfoAmsl.qml b/src/main/qml/Apx/Common/FactButton/ExtraInfoAmsl.qml new file mode 100644 index 000000000..bc86cc6a7 --- /dev/null +++ b/src/main/qml/Apx/Common/FactButton/ExtraInfoAmsl.qml @@ -0,0 +1,50 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +import Apx.Common +import APX.Mission + +Item { + id: item + property var homeHmsl: mandala.est.ref.hmsl.value + property var altitude: fact.parentFact.child("altitude").value + property bool chosen: fact.parentFact.chosen == Waypoint.AMSL + + onChosenChanged: _editor.enabled = chosen + onVisibleChanged: fact.parentFact.chosen = Waypoint.ALT + onAltitudeChanged: altitudeProcessing() + + Component.onCompleted: { + _editor.enabled = chosen + fact.value = altitude + homeHmsl + } + + function altitudeProcessing() + { + if(chosen) + return + fact.value = altitude + homeHmsl + } +} diff --git a/src/main/qml/Apx/Common/FactButton/ExtraInfoElevation.qml b/src/main/qml/Apx/Common/FactButton/ExtraInfoElevation.qml new file mode 100644 index 000000000..7fc3c6d30 --- /dev/null +++ b/src/main/qml/Apx/Common/FactButton/ExtraInfoElevation.qml @@ -0,0 +1,60 @@ +/* + * APX Autopilot project + * + * Copyright (c) 2003-2020, Aliaksei Stratsilatau + * All rights reserved + * + * This file is part of APX Ground Control. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +import Apx.Common + +Item { + id: item + visible: apx.settings.application.plugins.elevationmap.value && apx.tools.elevationmap.use.value + property var map: apx.tools.elevationmap + property var coordinate: fact.parentFact.coordinate + property var elevation: NaN + property var color: isNaN(elevation) ? "#dc143c" : "#32cd32" + + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + implicitHeight: parent.height + implicitWidth: Math.max(icon.width+text.implicitWidth, height*4) + + MaterialIcon { + id: icon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + name: "elevation-rise" + color: item.color + size: text.contentHeight + verticalAlignment: Text.AlignVCenter + } + Text { + id: text + anchors.left: icon.right + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + font: apx.font_narrow(Style.fontSize) + color: item.color + text: isNaN(item.elevation) ? "NO" : item.elevation + "m" + } + Component.onCompleted: elevation = map.getElevationByCoordinate(coordinate) +} diff --git a/src/main/qml/Apx/Common/FactButton/FactButton.qml b/src/main/qml/Apx/Common/FactButton/FactButton.qml index 3414318d4..d206a2127 100644 --- a/src/main/qml/Apx/Common/FactButton/FactButton.qml +++ b/src/main/qml/Apx/Common/FactButton/FactButton.qml @@ -221,6 +221,7 @@ ActionButton { // value Item { + id: _data anchors.right: _next.left anchors.top: parent.top anchors.bottom: parent.bottom @@ -253,6 +254,23 @@ ActionButton { } } + // extrainfo + Item { + id: _extrainfo + property var mrg: 10 + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: _data.left + anchors.rightMargin: mrg + + Loader { + active: !fact.opts.extrainfo ? false : true + anchors.fill: parent + // Material.accent: Material.color(Material.Green) + source: active?getExtrainfoSource():"" + onLoaded: _extrainfo.mrg = item.implicitWidth + 10*Style.scale + } + } } } @@ -310,6 +328,17 @@ ActionButton { return "Editor"+qml+".qml" } + function getExtrainfoSource() + { + if(!fact) + return "" + + if(fact.opts.extrainfo) + return fact.opts.extrainfo + + return "" + } + function openDialog(name) { if(!fact)return diff --git a/translations/by.ts b/translations/by.ts index 4da620ef0..bf8afe700 100644 --- a/translations/by.ts +++ b/translations/by.ts @@ -2125,7 +2125,7 @@ Шырата - Global postition latitude + Global position latitude @@ -2133,7 +2133,7 @@ Даўгата - Global postition longitude + Global position longitude @@ -3828,7 +3828,7 @@ socat -d -d pty,raw,echo=0 pty,raw,echo=0 Шырата - Global postition latitude + Global position latitude @@ -3836,7 +3836,7 @@ socat -d -d pty,raw,echo=0 pty,raw,echo=0 Даўгата - Global postition longitude + Global position longitude @@ -4158,7 +4158,7 @@ socat -d -d pty,raw,echo=0 pty,raw,echo=0 - Current postition + Current position