mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-08-24 16:32:26 -04:00
Map: Add PFD, first person view and path smoothing. Only send changes via CZML.
This commit is contained in:
parent
29f7d534e5
commit
ff3b3f4ef5
@ -50,6 +50,15 @@ void CesiumInterface::setView(float latitude, float longitude, float altitude)
|
||||
send(obj);
|
||||
}
|
||||
|
||||
void CesiumInterface::setViewFirstPerson(bool firstPerson)
|
||||
{
|
||||
QJsonObject obj {
|
||||
{"command", "setViewFirstPerson"},
|
||||
{"firstPerson", firstPerson}
|
||||
};
|
||||
send(obj);
|
||||
}
|
||||
|
||||
// Play glTF model animation for the map item with the specified name
|
||||
void CesiumInterface::playAnimation(const QString &name, Animation *animation)
|
||||
{
|
||||
|
@ -56,6 +56,7 @@ public:
|
||||
CesiumInterface(const MapSettings *settings, QObject *parent = nullptr);
|
||||
void setHomeView(float latitude, float longitude, float angle=1.0f);
|
||||
void setView(float latitude, float longitude, float altitude=60000);
|
||||
void setViewFirstPerson(bool firstPerson);
|
||||
void playAnimation(const QString &name, Animation *animation);
|
||||
void setDateTime(QDateTime dateTime);
|
||||
void getDateTime();
|
||||
@ -69,6 +70,7 @@ public:
|
||||
void setHDR(bool enabled);
|
||||
void setFog(bool enabled);
|
||||
void showFPS(bool show);
|
||||
void showPFD(bool show);
|
||||
void showMUF(bool show);
|
||||
void showfoF2(bool show);
|
||||
void showMagDec(bool show);
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "util/coordinates.h"
|
||||
|
||||
// FIXME: Cesium now has some additional options: CLAMP_TO_TERRAIN, RELATIVE_TO_TERRAIN, CLAMP_TO_3D_TILE, RELATIVE_TO_3D_TILE
|
||||
// CLIP_TO_GROUND is our own addition
|
||||
const QStringList CZML::m_heightReferences = {"NONE", "CLAMP_TO_GROUND", "RELATIVE_TO_GROUND", "CLIP_TO_GROUND"};
|
||||
|
||||
CZML::CZML(const MapSettings *settings) :
|
||||
@ -48,7 +50,8 @@ bool CZML::filter(const MapItem *mapItem) const
|
||||
|
||||
QJsonObject CZML::init()
|
||||
{
|
||||
QString start = QDateTime::currentDateTimeUtc().toString(Qt::ISODate);
|
||||
// Start a few seconds in the past, to allow for data to be received
|
||||
QString start = QDateTime::currentDateTimeUtc().addSecs(-4).toString(Qt::ISODate);
|
||||
QString stop = QDateTime::currentDateTimeUtc().addSecs(60*60).toString(Qt::ISODate);
|
||||
QString interval = QString("%1/%2").arg(start).arg(stop);
|
||||
|
||||
@ -241,6 +244,48 @@ QJsonObject CZML::update(PolylineMapItem *mapItem)
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void insertConstantProperty(QJsonObject& properties, const QString& name, const QString& value)
|
||||
{
|
||||
properties.insert(name, value);
|
||||
}
|
||||
|
||||
static void insertProperty(QJsonObject& properties, const QString& name, const QString& dateTime, float value)
|
||||
{
|
||||
if (!std::isnan(value))
|
||||
{
|
||||
QJsonArray ar;
|
||||
ar.push_back(dateTime);
|
||||
ar.push_back(value);
|
||||
QJsonObject obj {
|
||||
{"number", ar},
|
||||
{"backwardExtrapolationType", "HOLD"},
|
||||
{"backwardExtrapolationDuration", 30},
|
||||
{"forwardExtrapolationType", "HOLD"},
|
||||
{"forwardExtrapolationDuration", 30},
|
||||
};
|
||||
properties.insert(name, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// SampledProperties are interpolated
|
||||
// Need to use intervals to avoid interpolation
|
||||
// See: https://sandcastle.cesium.com/?src=CZML%20Custom%20Properties.html&label=All
|
||||
static void insertProperty0(QJsonObject& properties, const QString& name, const QString& dateTime, float value)
|
||||
{
|
||||
if (!std::isnan(value))
|
||||
{
|
||||
QJsonObject obj {
|
||||
{"interval", dateTime + "/3000"}, // Year 3000
|
||||
{"number", value}
|
||||
};
|
||||
|
||||
QJsonArray array {
|
||||
obj
|
||||
};
|
||||
properties.insert(name, array);
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{
|
||||
(void) isTarget;
|
||||
@ -274,8 +319,9 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
|
||||
// Keep a hash of the time we first saw each item
|
||||
bool existingId = m_ids.contains(id);
|
||||
State& state = m_ids[id];
|
||||
if (!existingId) {
|
||||
m_ids.insert(id, dt);
|
||||
state.m_firstSeenDateTime = dt;
|
||||
}
|
||||
|
||||
bool removeObj = false;
|
||||
@ -318,14 +364,49 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool useDateTime = !fixedPosition && mapItem->m_positionDateTime.isValid();
|
||||
|
||||
// Update positions that have been recalculated with interpolation
|
||||
if (!mapItem->m_interpolatedCoords.isEmpty())
|
||||
{
|
||||
for (int i = 0; i < mapItem->m_interpolatedCoords.size(); i++)
|
||||
{
|
||||
if (useDateTime) {
|
||||
coords.push_back(mapItem->m_interpolatedDateTimes[i]->toString(Qt::ISODateWithMs));
|
||||
}
|
||||
coords.push_back(mapItem->m_interpolatedCoords[i]->longitude());
|
||||
coords.push_back(mapItem->m_interpolatedCoords[i]->latitude());
|
||||
coords.push_back(mapItem->m_interpolatedCoords[i]->altitude());
|
||||
}
|
||||
mapItem->m_interpolatedCoords.clear();
|
||||
mapItem->m_interpolatedDateTimes.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only send latest position, to reduce processing
|
||||
if (!fixedPosition && mapItem->m_positionDateTime.isValid()) {
|
||||
if (!mapItem->m_takenTrackPositionExtrapolated.isEmpty() && mapItem->m_takenTrackPositionExtrapolated.back())
|
||||
{
|
||||
if (useDateTime) {
|
||||
coords.push_back(mapItem->m_takenTrackDateTimes.back()->toString(Qt::ISODateWithMs));
|
||||
}
|
||||
coords.push_back(mapItem->m_takenTrackCoords.back()->longitude());
|
||||
coords.push_back(mapItem->m_takenTrackCoords.back()->latitude());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (useDateTime) {
|
||||
coords.push_back(mapItem->m_positionDateTime.toString(Qt::ISODateWithMs));
|
||||
}
|
||||
coords.push_back(mapItem->m_longitude);
|
||||
coords.push_back(mapItem->m_latitude);
|
||||
coords.push_back(mapItem->m_altitude);
|
||||
}
|
||||
if (!mapItem->m_takenTrackAltitudeExtrapolated.isEmpty() && mapItem->m_takenTrackAltitudeExtrapolated.back()) {
|
||||
coords.push_back(mapItem->m_takenTrackCoords.back()->altitude());
|
||||
} else {
|
||||
coords.push_back(mapItem->m_altitude + mapItem->m_modelAltitudeOffset); // See nodeTransformations comment below, as to why we use this here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -343,6 +424,7 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{
|
||||
// Need 2 different positions to enable extrapolation, otherwise entity may not appear
|
||||
bool hasMoved = m_hasMoved.contains(id);
|
||||
|
||||
if (!hasMoved && m_lastPosition.contains(id) && (m_lastPosition.value(id) != coords))
|
||||
{
|
||||
hasMoved = true;
|
||||
@ -351,12 +433,16 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
if (hasMoved && (mapItem->m_itemSettings->m_extrapolate > 0))
|
||||
{
|
||||
position.insert("forwardExtrapolationType", "EXTRAPOLATE");
|
||||
//position.insert("forwardExtrapolationType", "LINEAR_EXTRAPOLATE"); // LAGRANGE is poor for extrapolation
|
||||
//position.insert("forwardExtrapolationType", "HOLD"); // Keeps at last position
|
||||
position.insert("forwardExtrapolationDuration", mapItem->m_itemSettings->m_extrapolate);
|
||||
// Use linear interpolation for now - other two can go crazy with aircraft on the ground
|
||||
// To calc acceleration, we need to use non-linear interpolation.
|
||||
position.insert("interpolationAlgorithm", "LINEAR");
|
||||
position.insert("interpolationDegree", 2);
|
||||
//position.insert("interpolationAlgorithm", "HERMITE");
|
||||
//position.insert("interpolationDegree", "2");
|
||||
//position.insert("interpolationDegree", 2);
|
||||
//position.insert("interpolationAlgorithm", "LAGRANGE");
|
||||
//position.insert("interpolationDegree", "5");
|
||||
//position.insert("interpolationDegree", 5); // crazy interpolation for LAGRANGE
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -365,11 +451,11 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolation goes wrong at end points
|
||||
// Interpolation goes wrong at end points. FIXME: Check if still true
|
||||
//position.insert("interpolationAlgorithm", "LAGRANGE");
|
||||
//position.insert("interpolationDegree", "5");
|
||||
//position.insert("interpolationDegree", 5);
|
||||
//position.insert("interpolationAlgorithm", "HERMITE");
|
||||
//position.insert("interpolationDegree", "2");
|
||||
//position.insert("interpolationDegree", 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -398,8 +484,257 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{"forwardExtrapolationType", "NONE"}
|
||||
};
|
||||
|
||||
if (!removeObj)
|
||||
{
|
||||
if (mapItem->m_aircraftState)
|
||||
{
|
||||
QJsonObject properties;
|
||||
QDateTime aircraftStateDateTime = mapItem->m_positionDateTime;
|
||||
QString dateTime = aircraftStateDateTime.toString(Qt::ISODateWithMs);
|
||||
bool aircraftStateDateTimeChanged = aircraftStateDateTime != state.m_aircraftStateDateTime;
|
||||
QString aircraftCallsign = mapItem->m_aircraftState->m_callsign;
|
||||
QString aircraftType = mapItem->m_aircraftState->m_aircraftType;
|
||||
QString aircraftIndicatedAirspeedDateTime = mapItem->m_aircraftState->m_indicatedAirspeedDateTime;
|
||||
float aircraftIndicatedAirspeed = mapItem->m_aircraftState->m_indicatedAirspeed;
|
||||
float aircraftTrueAirspeed = mapItem->m_aircraftState->m_trueAirspeed;
|
||||
float aircraftGroundspeed = mapItem->m_aircraftState->m_groundspeed;
|
||||
QString aircraftAltitudeDateTime = mapItem->m_aircraftState->m_altitudeDateTime;
|
||||
float aircraftAltitude = mapItem->m_aircraftState->m_altitude;
|
||||
int aircraftOnSurface = mapItem->m_aircraftState->m_onSurface;
|
||||
float aircraftMach = mapItem->m_aircraftState->m_mach;
|
||||
float aircraftQNH = mapItem->m_aircraftState->m_qnh;
|
||||
float aircraftVerticalSpeed = mapItem->m_aircraftState->m_verticalSpeed;
|
||||
float aircraftHeading = mapItem->m_aircraftState->m_heading;
|
||||
float aircraftTrack = mapItem->m_aircraftState->m_track;
|
||||
float aircraftRoll = mapItem->m_roll;
|
||||
float aircraftSelectedAltitude = mapItem->m_aircraftState->m_selectedAltitude;
|
||||
float aircraftSelectedHeading = mapItem->m_aircraftState->m_selectedHeading;
|
||||
int aircraftAutopilot = mapItem->m_aircraftState->m_autopilot;
|
||||
MapAircraftState::VerticalMode aircraftVerticalMode = mapItem->m_aircraftState->m_verticalMode;
|
||||
MapAircraftState::LateralMode aircraftLateralMode = mapItem->m_aircraftState->m_lateralMode;
|
||||
MapAircraftState::TCASMode aircraftTCASMode = mapItem->m_aircraftState->m_tcasMode;
|
||||
float aircraftWindSpeed = mapItem->m_aircraftState->m_windSpeed;
|
||||
float aircraftWindDirection = mapItem->m_aircraftState->m_windDirection;
|
||||
float aircraftStaticAirTemperature = mapItem->m_aircraftState->m_staticAirTemperature;
|
||||
|
||||
if ( (!existingId && !aircraftCallsign.isEmpty())
|
||||
|| (aircraftCallsign != state.m_aircraftState.m_callsign)
|
||||
)
|
||||
{
|
||||
insertConstantProperty(properties, "pfdCallsign", aircraftCallsign);
|
||||
state.m_aircraftState.m_callsign = aircraftCallsign;
|
||||
}
|
||||
if ( (!existingId && !aircraftType.isEmpty())
|
||||
|| (aircraftType != state.m_aircraftState.m_aircraftType)
|
||||
)
|
||||
{
|
||||
insertConstantProperty(properties, "pfdAircraftType", aircraftType);
|
||||
state.m_aircraftState.m_aircraftType = aircraftType;
|
||||
}
|
||||
if ( (!existingId && !aircraftIndicatedAirspeedDateTime.isEmpty())
|
||||
|| (aircraftIndicatedAirspeedDateTime != state.m_aircraftState.m_indicatedAirspeedDateTime)
|
||||
|| (aircraftIndicatedAirspeed != state.m_aircraftState.m_indicatedAirspeed)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdIndicatedAirspeed", aircraftIndicatedAirspeedDateTime, aircraftIndicatedAirspeed);
|
||||
state.m_aircraftState.m_indicatedAirspeedDateTime = aircraftIndicatedAirspeedDateTime;
|
||||
state.m_aircraftState.m_indicatedAirspeed = aircraftIndicatedAirspeed;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftTrueAirspeed != state.m_aircraftState.m_trueAirspeed)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdTrueAirspeed", dateTime, aircraftTrueAirspeed);
|
||||
state.m_aircraftState.m_trueAirspeed = aircraftTrueAirspeed;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftGroundspeed != state.m_aircraftState.m_groundspeed)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdGroundspeed", dateTime, aircraftGroundspeed);
|
||||
state.m_aircraftState.m_groundspeed = aircraftGroundspeed;
|
||||
}
|
||||
if ( (!existingId && !aircraftAltitudeDateTime.isEmpty())
|
||||
|| (aircraftAltitudeDateTime != state.m_aircraftState.m_altitudeDateTime)
|
||||
|| (aircraftAltitude != state.m_aircraftState.m_altitude)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdAltitude", aircraftAltitudeDateTime, aircraftAltitude);
|
||||
state.m_aircraftState.m_altitudeDateTime = aircraftAltitudeDateTime;
|
||||
state.m_aircraftState.m_altitude = aircraftAltitude;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftOnSurface != state.m_aircraftState.m_onSurface)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdOnSurface", dateTime, aircraftOnSurface);
|
||||
state.m_aircraftState.m_onSurface = aircraftOnSurface;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftMach != state.m_aircraftState.m_mach)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdMach", dateTime, aircraftMach);
|
||||
state.m_aircraftState.m_mach = aircraftMach;
|
||||
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftQNH != state.m_aircraftState.m_qnh)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdQNH", dateTime, aircraftQNH);
|
||||
state.m_aircraftState.m_qnh = aircraftQNH;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftVerticalSpeed != state.m_aircraftState.m_verticalSpeed)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdVerticalSpeed", dateTime, aircraftVerticalSpeed);
|
||||
state.m_aircraftState.m_verticalSpeed = aircraftVerticalSpeed;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftHeading != state.m_aircraftState.m_heading)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdHeading", dateTime, aircraftHeading);
|
||||
state.m_aircraftState.m_heading = aircraftHeading;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftTrack != state.m_aircraftState.m_track)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdTrack", dateTime, aircraftTrack);
|
||||
state.m_aircraftState.m_track = aircraftTrack;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftRoll != state.m_aircraftRoll)
|
||||
)
|
||||
{
|
||||
insertProperty(properties, "pfdRoll", dateTime, aircraftRoll);
|
||||
state.m_aircraftRoll = aircraftRoll;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftSelectedAltitude != state.m_aircraftState.m_selectedAltitude)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdSelectedAltitude", dateTime, aircraftSelectedAltitude);
|
||||
state.m_aircraftState.m_selectedAltitude = aircraftSelectedAltitude;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftSelectedHeading != state.m_aircraftState.m_selectedHeading)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdSelectedHeading", dateTime, aircraftSelectedHeading);
|
||||
state.m_aircraftState.m_selectedHeading = aircraftSelectedHeading;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftAutopilot != state.m_aircraftState.m_autopilot)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdAutopilot", dateTime, aircraftAutopilot);
|
||||
state.m_aircraftState.m_autopilot = aircraftAutopilot;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftVerticalMode != state.m_aircraftState.m_verticalMode)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdVerticalMode", dateTime, aircraftVerticalMode);
|
||||
state.m_aircraftState.m_verticalMode = aircraftVerticalMode;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftLateralMode != state.m_aircraftState.m_lateralMode)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdLateralMode", dateTime, aircraftLateralMode);
|
||||
state.m_aircraftState.m_lateralMode = aircraftLateralMode;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftTCASMode != state.m_aircraftState.m_tcasMode)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdTCASMode", dateTime, aircraftTCASMode);
|
||||
state.m_aircraftState.m_tcasMode = aircraftTCASMode;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftWindSpeed != state.m_aircraftState.m_windSpeed)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdWindSpeed", dateTime, aircraftWindSpeed);
|
||||
state.m_aircraftState.m_windSpeed = aircraftWindSpeed;
|
||||
}
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftWindDirection != state.m_aircraftState.m_windDirection)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdWindDirection", dateTime, aircraftWindDirection);
|
||||
state.m_aircraftState.m_windDirection = aircraftWindDirection;
|
||||
}
|
||||
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
|| (aircraftStaticAirTemperature != state.m_aircraftState.m_staticAirTemperature)
|
||||
)
|
||||
{
|
||||
insertProperty0(properties, "pfdStaticAirTemperature", dateTime, aircraftStaticAirTemperature);
|
||||
state.m_aircraftState.m_staticAirTemperature = aircraftStaticAirTemperature;
|
||||
}
|
||||
|
||||
//QJsonObject speedProperty {
|
||||
// {"velocityReference", "#position"},
|
||||
//};
|
||||
//properties.insert("pfdSpeed", speedProperty);
|
||||
|
||||
if ( !existingId
|
||||
|| aircraftStateDateTimeChanged
|
||||
)
|
||||
{
|
||||
state.m_aircraftStateDateTime = aircraftStateDateTime;
|
||||
}
|
||||
|
||||
if (properties.size() > 0) {
|
||||
obj.insert("properties", properties);
|
||||
}
|
||||
}
|
||||
|
||||
obj.insert("position", position);
|
||||
if (!fixedPosition)
|
||||
{
|
||||
if (mapItem->m_useHeadingPitchRoll) {
|
||||
obj.insert("orientation", orientation);
|
||||
} else {
|
||||
obj.insert("orientation", orientationPosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Point
|
||||
QColor pointColor = QColor::fromRgba(mapItem->m_itemSettings->m_3DPointColor);
|
||||
quint32 pointColorInt = mapItem->m_itemSettings->m_3DPointColor;
|
||||
int pointAltitudeReference = mapItem->m_altitudeReference;
|
||||
bool pointShow = mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DPoint;
|
||||
|
||||
if ( !existingId
|
||||
|| (pointColorInt != state.m_pointColorInt)
|
||||
|| (pointAltitudeReference != state.m_pointAltitudeReference)
|
||||
|| (pointShow != state.m_pointShow)
|
||||
)
|
||||
{
|
||||
QColor pointColor = QColor::fromRgba(pointColorInt);
|
||||
QJsonArray pointRGBA {
|
||||
pointColor.red(), pointColor.green(), pointColor.blue(), pointColor.alpha()
|
||||
};
|
||||
@ -409,68 +744,32 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
QJsonObject point {
|
||||
{"pixelSize", 8},
|
||||
{"color", pointColorObj},
|
||||
{"heightReference", heightReferences[mapItem->m_altitudeReference]},
|
||||
{"show", mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DPoint}
|
||||
{"heightReference", heightReferences[pointAltitudeReference]},
|
||||
{"show", pointShow}
|
||||
};
|
||||
obj.insert("point", point);
|
||||
|
||||
// Model
|
||||
QJsonArray node0Cartesian {
|
||||
{0.0, mapItem->m_modelAltitudeOffset, 0.0}
|
||||
};
|
||||
QJsonObject node0Translation {
|
||||
{"cartesian", node0Cartesian}
|
||||
};
|
||||
QJsonObject node0Transform {
|
||||
{"translation", node0Translation}
|
||||
};
|
||||
QJsonObject nodeTransforms {
|
||||
{"node0", node0Transform},
|
||||
};
|
||||
QJsonObject model {
|
||||
{"gltf", m_settings->m_modelURL + mapItem->m_model},
|
||||
{"incrementallyLoadTextures", false}, // Aircraft will flash as they appear without textures if this is the default of true
|
||||
{"heightReference", heightReferences[mapItem->m_altitudeReference]},
|
||||
{"runAnimations", false},
|
||||
{"show", mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DModel},
|
||||
{"minimumPixelSize", mapItem->m_itemSettings->m_3DModelMinPixelSize},
|
||||
{"maximumScale", 20000} // Stop it getting too big when zoomed really far out
|
||||
};
|
||||
if (mapItem->m_modelAltitudeOffset != 0.0) {
|
||||
model.insert("nodeTransformations", nodeTransforms);
|
||||
state.m_pointColorInt = pointColorInt;
|
||||
state.m_pointAltitudeReference = pointAltitudeReference;
|
||||
state.m_pointShow = pointShow;
|
||||
}
|
||||
|
||||
// Path
|
||||
QColor pathColor = QColor::fromRgba(mapItem->m_itemSettings->m_3DTrackColor);
|
||||
QJsonArray pathColorRGBA {
|
||||
pathColor.red(), pathColor.green(), pathColor.blue(), pathColor.alpha()
|
||||
};
|
||||
QJsonObject pathColorObj {
|
||||
{"rgba", pathColorRGBA}
|
||||
};
|
||||
// Paths can't be clamped to ground, so AIS paths can be underground if terrain is used
|
||||
// See: https://github.com/CesiumGS/cesium/issues/7133
|
||||
QJsonObject pathSolidColorMaterial {
|
||||
{"color", pathColorObj}
|
||||
};
|
||||
QJsonObject pathMaterial {
|
||||
{"solidColor", pathSolidColorMaterial}
|
||||
};
|
||||
bool showPath = mapItem->m_itemSettings->m_enabled
|
||||
&& mapItem->m_itemSettings->m_display3DTrack
|
||||
&& ( m_settings->m_displayAllGroundTracks
|
||||
|| (m_settings->m_displaySelectedGroundTracks && isSelected));
|
||||
QJsonObject path {
|
||||
// We want full paths for sat tracker, so leadTime and trailTime should be 0
|
||||
// Should be configurable.. 6000=100mins ~> 1 orbit for LEO
|
||||
//{"leadTime", "6000"},
|
||||
//{"trailTime", "6000"},
|
||||
{"width", "3"},
|
||||
{"material", pathMaterial},
|
||||
{"show", showPath}
|
||||
};
|
||||
|
||||
// Label
|
||||
|
||||
float labelAltitudeOffset = mapItem->m_labelAltitudeOffset;
|
||||
QString labelText = mapItem->m_label;
|
||||
bool labelShow = m_settings->m_displayNames && mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DLabel;
|
||||
float labelScale = mapItem->m_itemSettings->m_3DLabelScale;
|
||||
int labelAltitudeReference = mapItem->m_altitudeReference;
|
||||
|
||||
if ( !existingId
|
||||
|| (labelAltitudeOffset != state.m_lableAltitudeOffset)
|
||||
|| (labelText != state.m_labelText)
|
||||
|| (labelShow != state.m_labelShow)
|
||||
|| (labelScale != state.m_labelScale)
|
||||
|| (labelAltitudeReference != state.m_labelAltitudeReference)
|
||||
)
|
||||
{
|
||||
// Prevent labels from being too cluttered when zoomed out
|
||||
// FIXME: These values should come from mapItem or mapItemSettings
|
||||
float displayDistanceMax = std::numeric_limits<float>::max();
|
||||
@ -499,7 +798,7 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{"cartesian2", labelPixelOffsetArray}
|
||||
};
|
||||
QJsonArray labelEyeOffsetArray {
|
||||
0, mapItem->m_labelAltitudeOffset, 0 // Position above the object, dependent on the height of the model
|
||||
0, labelAltitudeOffset, 0 // Position above the object, dependent on the height of the model
|
||||
};
|
||||
QJsonObject labelEyeOffset {
|
||||
{"cartesian", labelEyeOffsetArray}
|
||||
@ -513,23 +812,101 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
QJsonObject labelDistanceDisplayCondition {
|
||||
{"distanceDisplayCondition", labelDisplayDistance}
|
||||
};
|
||||
QString labelText = mapItem->m_label;
|
||||
|
||||
labelText.replace("<br>", "\n");
|
||||
QJsonObject label {
|
||||
{"text", labelText},
|
||||
{"show", m_settings->m_displayNames && mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DLabel},
|
||||
{"scale", mapItem->m_itemSettings->m_3DLabelScale},
|
||||
//{"text", labelText},
|
||||
{"show", labelShow},
|
||||
{"scale", labelScale},
|
||||
{"pixelOffset", labelPixelOffset},
|
||||
{"pixelOffsetScaleByDistance", labelPixelOffsetScaleObject},
|
||||
{"eyeOffset", labelEyeOffset},
|
||||
{"verticalOrigin", "BASELINE"},
|
||||
{"horizontalOrigin", "LEFT"},
|
||||
{"heightReference", heightReferences[mapItem->m_altitudeReference]},
|
||||
{"heightReference", heightReferences[labelAltitudeReference]},
|
||||
{"style", "FILL_AND_OUTLINE"},
|
||||
};
|
||||
if (!mapItem->m_labelDateTime.isValid())
|
||||
{
|
||||
label.insert("text", labelText);
|
||||
}
|
||||
else
|
||||
{
|
||||
QString interval = mapItem->m_labelDateTime.toString(Qt::ISODateWithMs) + "/2999-12-31";
|
||||
QJsonObject labelInterval {
|
||||
{"interval", interval},
|
||||
{"string", labelText}
|
||||
};
|
||||
QJsonArray labelIntervalArray {
|
||||
labelInterval
|
||||
};
|
||||
label.insert("text", labelIntervalArray);
|
||||
}
|
||||
if (displayDistanceMax != std::numeric_limits<float>::max()) {
|
||||
label.insert("distanceDisplayCondition", labelDistanceDisplayCondition);
|
||||
}
|
||||
|
||||
obj.insert("label", label);
|
||||
|
||||
state.m_lableAltitudeOffset = labelAltitudeOffset;
|
||||
state.m_labelText = labelText;
|
||||
state.m_labelShow = labelShow;
|
||||
state.m_labelScale = labelScale;
|
||||
state.m_labelAltitudeReference = labelAltitudeReference;
|
||||
}
|
||||
|
||||
if (!mapItem->m_model.isEmpty())
|
||||
{
|
||||
// Model
|
||||
QString modelGLTF = m_settings->m_modelURL + mapItem->m_model;
|
||||
int modelAltitudeReference = mapItem->m_altitudeReference;
|
||||
bool modelShow = mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DModel;
|
||||
int modelMinimumPixelSize = mapItem->m_itemSettings->m_3DModelMinPixelSize;
|
||||
|
||||
if ( !existingId
|
||||
|| (modelGLTF != state.m_modelGLTF)
|
||||
|| (modelAltitudeReference != state.m_modelAltitudeReference)
|
||||
|| (modelShow != state.m_modelShow)
|
||||
|| (modelMinimumPixelSize != state.m_modelMinimumPixelSize)
|
||||
)
|
||||
{
|
||||
QJsonObject model {
|
||||
{"gltf", modelGLTF},
|
||||
//{"incrementallyLoadTextures", false}, // Aircraft will flash as they appear without textures if this is the default of true
|
||||
{"heightReference", heightReferences[modelAltitudeReference]},
|
||||
{"runAnimations", false},
|
||||
{"show", modelShow},
|
||||
{"minimumPixelSize", modelMinimumPixelSize},
|
||||
{"maximumScale", 20000} // Stop it getting too big when zoomed really far out
|
||||
};
|
||||
// Using nodeTransformations stops animations from running.
|
||||
// See: https://github.com/CesiumGS/cesium/issues/11566
|
||||
/*QJsonArray node0Cartesian {
|
||||
{0.0, mapItem->m_modelAltitudeOffset, 0.0}
|
||||
};
|
||||
QJsonObject node0Translation {
|
||||
{"cartesian", node0Cartesian}
|
||||
};
|
||||
QJsonObject node0Transform {
|
||||
{"translation", node0Translation}
|
||||
};
|
||||
QJsonObject nodeTransforms {
|
||||
{"node0", node0Transform},
|
||||
};
|
||||
if (mapItem->m_modelAltitudeOffset != 0.0) {
|
||||
model.insert("nodeTransformations", nodeTransforms);
|
||||
}*/
|
||||
|
||||
obj.insert("model", model);
|
||||
|
||||
state.m_modelGLTF = modelGLTF;
|
||||
state.m_modelAltitudeReference = modelAltitudeReference;
|
||||
state.m_modelShow = modelShow;
|
||||
state.m_modelMinimumPixelSize = modelMinimumPixelSize;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use billboard for APRS as we don't currently have 3D objects
|
||||
QString imageURL = mapItem->m_image;
|
||||
if (imageURL.startsWith("qrc://")) {
|
||||
@ -541,27 +918,64 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{"verticalOrigin", "BOTTOM"} // To stop it being cut in half when zoomed out
|
||||
};
|
||||
|
||||
if (!removeObj)
|
||||
{
|
||||
obj.insert("position", position);
|
||||
if (!fixedPosition)
|
||||
{
|
||||
if (mapItem->m_useHeadingPitchRoll) {
|
||||
obj.insert("orientation", orientation);
|
||||
} else {
|
||||
obj.insert("orientation", orientationPosition);
|
||||
}
|
||||
}
|
||||
obj.insert("point", point);
|
||||
if (!mapItem->m_model.isEmpty()) {
|
||||
obj.insert("model", model);
|
||||
} else {
|
||||
obj.insert("billboard", billboard);
|
||||
}
|
||||
obj.insert("label", label);
|
||||
obj.insert("description", mapItem->m_text);
|
||||
if (!fixedPosition) {
|
||||
|
||||
// Description
|
||||
QString description = mapItem->m_text;
|
||||
if ( !existingId
|
||||
|| (description != state.m_description)
|
||||
)
|
||||
{
|
||||
obj.insert("description", description);
|
||||
state.m_description = description;
|
||||
}
|
||||
|
||||
// Path
|
||||
if (!fixedPosition)
|
||||
{
|
||||
quint32 pathColorInt = mapItem->m_itemSettings->m_3DTrackColor;
|
||||
bool pathShow = mapItem->m_itemSettings->m_enabled
|
||||
&& mapItem->m_itemSettings->m_display3DTrack
|
||||
&& ( m_settings->m_displayAllGroundTracks
|
||||
|| (m_settings->m_displaySelectedGroundTracks && isSelected));
|
||||
|
||||
if ( !existingId
|
||||
|| (pathColorInt != state.m_pathColorInt)
|
||||
|| (pathShow != state.m_pathShow)
|
||||
)
|
||||
{
|
||||
QColor pathColor = QColor::fromRgba(pathColorInt);
|
||||
QJsonArray pathColorRGBA {
|
||||
pathColor.red(), pathColor.green(), pathColor.blue(), pathColor.alpha()
|
||||
};
|
||||
QJsonObject pathColorObj {
|
||||
{"rgba", pathColorRGBA}
|
||||
};
|
||||
// Paths can't be clamped to ground, so AIS paths can be underground if terrain is used
|
||||
// See: https://github.com/CesiumGS/cesium/issues/7133
|
||||
QJsonObject pathSolidColorMaterial {
|
||||
{"color", pathColorObj}
|
||||
};
|
||||
QJsonObject pathMaterial {
|
||||
{"solidColor", pathSolidColorMaterial}
|
||||
};
|
||||
QJsonObject path {
|
||||
// We want full paths for sat tracker, so leadTime and trailTime should be 0
|
||||
// Should be configurable.. 6000=100mins ~> 1 orbit for LEO
|
||||
//{"leadTime", "6000"},
|
||||
//{"trailTime", "6000"},
|
||||
{"width", "3"},
|
||||
{"material", pathMaterial},
|
||||
{"show", pathShow},
|
||||
{"resolution", 1}
|
||||
};
|
||||
|
||||
obj.insert("path", path);
|
||||
|
||||
state.m_pathColorInt = pathColorInt;
|
||||
state.m_pathShow = pathShow;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fixedPosition)
|
||||
@ -577,7 +991,7 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{
|
||||
if (mapItem->m_availableUntil.isValid())
|
||||
{
|
||||
QString period = QString("%1/%2").arg(m_ids[id]).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
|
||||
QString period = QString("%1/%2").arg(state.m_firstSeenDateTime).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
|
||||
obj.insert("availability", period);
|
||||
}
|
||||
}
|
||||
@ -586,7 +1000,7 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
{
|
||||
if (mapItem->m_availableUntil.isValid())
|
||||
{
|
||||
QString period = QString("%1/%2").arg(m_ids[id]).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
|
||||
QString period = QString("%1/%2").arg(state.m_firstSeenDateTime).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
|
||||
obj.insert("availability", period);
|
||||
}
|
||||
}
|
||||
@ -599,13 +1013,24 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
||||
}
|
||||
|
||||
// Use our own clipping routine, due to
|
||||
// https://github.com/CesiumGS/cesium/issues/4049
|
||||
if (mapItem->m_altitudeReference == 3) {
|
||||
obj.insert("altitudeReference", "CLIP_TO_GROUND");
|
||||
}
|
||||
// https://github.com/CesiumGS/cesium/issues/4049 - Has now been fixed
|
||||
//if (mapItem->m_altitudeReference == 3) {
|
||||
// obj.insert("altitudeReference", "CLIP_TO_GROUND");
|
||||
//}
|
||||
|
||||
//qDebug() << obj;
|
||||
|
||||
/*if (id == "400b00")
|
||||
{
|
||||
QJsonDocument doc(obj);
|
||||
if (!m_file.isOpen())
|
||||
{
|
||||
m_file.setFileName(QString("%1.czml").arg(id));
|
||||
m_file.open(QIODeviceBase::WriteOnly);
|
||||
}
|
||||
m_file.write(doc.toJson());
|
||||
m_file.write(",\n");
|
||||
}*/
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -24,10 +24,13 @@
|
||||
#define INCLUDE_FEATURE_CZML_H_
|
||||
|
||||
#include <QHash>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
#include "mapaircraftstate.h"
|
||||
|
||||
struct MapSettings;
|
||||
class MapItem;
|
||||
class ObjectMapItem;
|
||||
@ -36,13 +39,59 @@ class PolylineMapItem;
|
||||
|
||||
class CZML
|
||||
{
|
||||
// Record previous state of object, so we only send changes in state
|
||||
struct State {
|
||||
QString m_firstSeenDateTime;
|
||||
|
||||
QString m_modelGLTF;
|
||||
int m_modelAltitudeReference;
|
||||
bool m_modelShow;
|
||||
int m_modelMinimumPixelSize;
|
||||
|
||||
quint32 m_pointColorInt;
|
||||
int m_pointAltitudeReference;
|
||||
bool m_pointShow;
|
||||
|
||||
float m_lableAltitudeOffset;
|
||||
QString m_labelText;
|
||||
bool m_labelShow;
|
||||
float m_labelScale;
|
||||
int m_labelAltitudeReference;
|
||||
|
||||
quint32 m_pathColorInt;
|
||||
bool m_pathShow;
|
||||
|
||||
QString m_description;
|
||||
|
||||
QDateTime m_aircraftStateDateTime;
|
||||
MapAircraftState m_aircraftState;
|
||||
float m_aircraftRoll;
|
||||
|
||||
State() :
|
||||
m_modelAltitudeReference(0),
|
||||
m_modelShow(false),
|
||||
m_modelMinimumPixelSize(0),
|
||||
m_pointColorInt(0),
|
||||
m_pointAltitudeReference(0),
|
||||
m_pointShow(false),
|
||||
m_lableAltitudeOffset(0.0),
|
||||
m_labelShow(false),
|
||||
m_labelScale(0.0),
|
||||
m_labelAltitudeReference(0),
|
||||
m_pathColorInt(0),
|
||||
m_pathShow(false)
|
||||
{ }
|
||||
};
|
||||
|
||||
private:
|
||||
const MapSettings *m_settings;
|
||||
QHash<QString, QString> m_ids;
|
||||
QHash<QString, State> m_ids;
|
||||
QHash<QString, QJsonArray> m_lastPosition;
|
||||
QHash<QString, bool> m_hasMoved;
|
||||
QGeoCoordinate m_position;
|
||||
static const QStringList m_heightReferences;
|
||||
QFile m_file;
|
||||
QFile m_csvFile;
|
||||
|
||||
public:
|
||||
CZML(const MapSettings *settings);
|
||||
|
BIN
plugins/feature/map/icons/firstperson.png
Normal file
BIN
plugins/feature/map/icons/firstperson.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 342 B |
BIN
plugins/feature/map/icons/pfd.png
Normal file
BIN
plugins/feature/map/icons/pfd.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 377 B |
BIN
plugins/feature/map/icons/thirdperson.png
Normal file
BIN
plugins/feature/map/icons/thirdperson.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 303 B |
1463
plugins/feature/map/map/cockpit.js
Normal file
1463
plugins/feature/map/map/cockpit.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
<script src="/Cesium/Cesium.js"></script>
|
||||
<style>
|
||||
@import url(/Cesium/Widgets/widgets.css);
|
||||
|
||||
html,
|
||||
body,
|
||||
#cesiumContainer {
|
||||
@ -15,14 +16,13 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||
</head>
|
||||
<body style="margin:0;padding:0">
|
||||
<div id="cesiumContainer"></div>
|
||||
<script src="grid.js"></script>
|
||||
<script>
|
||||
|
||||
// See: https://community.cesium.com/t/how-to-run-an-animation-for-an-entity-model/16932
|
||||
function getActiveAnimations(viewer, entity) {
|
||||
@ -48,7 +48,7 @@
|
||||
startOffset: command.startOffset,
|
||||
reverse: command.reverse,
|
||||
loop: command.loop ? Cesium.ModelAnimationLoop.REPEAT : Cesium.ModelAnimationLoop.NONE,
|
||||
multiplier: command.multiplier,
|
||||
multiplier: 0.2 // command.multiplier,
|
||||
};
|
||||
options.startTime = Cesium.JulianDate.fromIso8601(command.startDateTime);
|
||||
// https://github.com/CesiumGS/cesium/issues/10048
|
||||
@ -60,7 +60,7 @@
|
||||
if (command.duration != 0) {
|
||||
options.stopTime = Cesium.JulianDate.addSeconds(options.startTime, command.duration, new Cesium.JulianDate());
|
||||
}
|
||||
animations.add(options);
|
||||
const anim = animations.add(options);
|
||||
} catch (e) {
|
||||
// Note we get TypeError instead of DeveloperError, if running minified version of Cesium
|
||||
if ((e instanceof Cesium.DeveloperError) || (e instanceof TypeError)) {
|
||||
@ -127,7 +127,7 @@
|
||||
loop: anim.loop,
|
||||
multiplier: anim.multiplier,
|
||||
startTime: anim.startTime,
|
||||
stopTime: Cesium.JulianDate.fromIso8601(command.startDateTime)
|
||||
stopTime: Cesium.JulianDate.fromIso8601(command.startDateTime) // FIXME: Should this be stopDateTime?
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -148,8 +148,7 @@
|
||||
}
|
||||
|
||||
// Polygons (such as for airspaces) should be prioritized behind other entities
|
||||
function pickEntityPrioritized(e)
|
||||
{
|
||||
function pickEntityPrioritized(e) {
|
||||
var picked = viewer.scene.drillPick(e.position);
|
||||
if (Cesium.defined(picked)) {
|
||||
var firstPolygon = null;
|
||||
@ -171,6 +170,24 @@
|
||||
viewer.selectedEntity = pickEntityPrioritized(e);
|
||||
}
|
||||
|
||||
function pickAndTrack(e) {
|
||||
const entity = pickEntityPrioritized(e);
|
||||
if (Cesium.defined(entity)) {
|
||||
if (viewFirstPerson) {
|
||||
setFirstPersonView(entity);
|
||||
} else {
|
||||
if (Cesium.Property.getValueOrUndefined(entity.position, viewer.clock.currentTime)) {
|
||||
setThirdPersonView(entity);
|
||||
} else {
|
||||
viewer.zoomTo(entity);
|
||||
}
|
||||
}
|
||||
} else if (Cesium.defined(viewer.trackedEntity)) {
|
||||
//viewer.trackedEntity = undefined;
|
||||
setThirdPersonView(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
function showCoords(e) {
|
||||
if (viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) {
|
||||
var cartesian = viewer.camera.pickEllipsoid(e.position);
|
||||
@ -189,8 +206,9 @@
|
||||
const ray = viewer.camera.getPickRay(e.position);
|
||||
const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
|
||||
var cartographic = Cesium.Cartographic.fromCartesian(cartesian);
|
||||
var promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]);
|
||||
Cesium.when(promise, function(updatedPositions) {
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [cartographic]),
|
||||
).then((updatedPositions) => {
|
||||
longitudeString = Cesium.Math.toDegrees(cartographic.longitude).toFixed(6);
|
||||
latitudeString = Cesium.Math.toDegrees(cartographic.latitude).toFixed(6);
|
||||
heightString = updatedPositions[0].height.toFixed(1);
|
||||
@ -201,8 +219,6 @@
|
||||
`Lon: ${` ${longitudeString}`}\u00B0` +
|
||||
`\nLat: ${` ${latitudeString}`}\u00B0` +
|
||||
`\nAlt: ${` ${heightString}`}m`;
|
||||
}, function() {
|
||||
console.log(`Terrain doesn't support sampleTerrainMostDetailed`);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -213,9 +229,14 @@
|
||||
}
|
||||
|
||||
Cesium.Ion.defaultAccessToken = '$CESIUM_ION_API_KEY$';
|
||||
if ('$ARCGIS_API_KEY$' != '') {
|
||||
Cesium.ArcGisMapService.defaultAccessToken = '$ARCGIS_API_KEY$';
|
||||
}
|
||||
|
||||
// Start time is set via CZML::init()
|
||||
const viewer = new Cesium.Viewer('cesiumContainer', {
|
||||
terrainProvider: Cesium.createWorldTerrain(),
|
||||
baseLayer: false,
|
||||
terrainProvider: Cesium.createWorldTerrainAsync(),
|
||||
animation: true,
|
||||
shouldAnimate: true,
|
||||
timeline: true,
|
||||
@ -225,19 +246,32 @@
|
||||
navigationInstructionsInitiallyVisible: false,
|
||||
terrainProviderViewModels: [] // User should adjust terrain via dialog, so depthTestAgainstTerrain doesn't get set
|
||||
});
|
||||
//viewer.scene.debugShowFramesPerSecond = true; // FIXME: Embedded Chrome only runs at 60fps
|
||||
viewer.scene.globe.depthTestAgainstTerrain = false; // So labels/points aren't clipped by terrain (this prevents pickPosition from working)
|
||||
viewer.scene.globe.tileCacheSize = 5000; // FIXME: Embedded Chrome is slower at loading from cache
|
||||
viewer.scene.moon.onlySunLighting = false; // Moon can be just a black dot if default of true
|
||||
viewer.screenSpaceEventHandler.setInputAction(pickEntity, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||
viewer.screenSpaceEventHandler.setInputAction(pickAndTrack, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
|
||||
viewer.screenSpaceEventHandler.setInputAction(showCoords, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK, Cesium.KeyboardEventModifier.SHIFT);
|
||||
viewer.screenSpaceEventHandler.setInputAction(hideCoords, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
|
||||
|
||||
viewer.useBrowserRecommendedResolution = false; // Improves label quality when false, as drawn at higher res
|
||||
|
||||
viewer.infoBox.frame.setAttribute('sandbox', 'allow-same-origin allow-popups allow-forms allow-scripts allow-top-navigation');
|
||||
viewer.infoBox.frame.src = "about:blank"; // Force reload so new attributes are applied
|
||||
|
||||
viewer.infoBox.viewModel.cameraClicked.removeEventListener(Cesium.Viewer.prototype._onInfoBoxCameraClicked, viewer); // Override info box camera button being clicked
|
||||
viewer.infoBox.viewModel.cameraClicked.addEventListener(infoBoxCameraClicked, viewer);
|
||||
|
||||
var pfdTimer = undefined;
|
||||
var pfdRadioAltTimer = undefined;
|
||||
|
||||
var buildings = undefined;
|
||||
const images = new Map();
|
||||
|
||||
var mufGeoJSONStream = null;
|
||||
var foF2GeoJSONStream = null;
|
||||
var wmmGeoJSONStream = null;
|
||||
|
||||
const positionMarker = viewer.entities.add({
|
||||
id: 'Position marker',
|
||||
@ -284,6 +318,18 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
// Generate HTML for WMM contour info box from properties in GeoJSON
|
||||
function describeWMM(properties, nameProperty) {
|
||||
let html = "";
|
||||
if (properties.hasOwnProperty("Contour")) {
|
||||
const value = properties["Contour"];
|
||||
if (Cesium.defined(value)) {
|
||||
html = `<p>Magnetic declination: ${value} degrees`;
|
||||
}
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
// Use CZML to stream data from Map plugin to Cesium
|
||||
var czmlStream = new Cesium.CzmlDataSource();
|
||||
|
||||
@ -293,6 +339,72 @@
|
||||
viewer.scene.light.direction = Cesium.Cartesian3.clone(scene.camera.directionWC, viewer.scene.light.direction);
|
||||
}
|
||||
|
||||
var velocityVectorProperty = undefined;
|
||||
const velocityVector = new Cesium.Cartesian3();
|
||||
|
||||
var viewFirstPerson = false;
|
||||
var firstPersonEntity;
|
||||
var firstPersonOffset = 0.0;
|
||||
var cameraSavedPositionValid = false;
|
||||
var cameraSavedPosition;
|
||||
var cameraSavedHeading;
|
||||
var cameraSavedPitch;
|
||||
var cameraSavedRoll;
|
||||
var cameraSavedTransform;
|
||||
var cameraInitPos = false;
|
||||
|
||||
// First person camera
|
||||
function cameraFirstPerson(scene, time) {
|
||||
const entity = firstPersonEntity;
|
||||
if (!Cesium.defined(entity)) {
|
||||
return;
|
||||
}
|
||||
const position = entity.position.getValue(time);
|
||||
if (!Cesium.defined(position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform;
|
||||
if (!Cesium.defined(entity.orientation)) {
|
||||
transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
|
||||
} else {
|
||||
const orientation = entity.orientation.getValue(time);
|
||||
if (!Cesium.defined(orientation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
transform = Cesium.Matrix4.fromRotationTranslation(
|
||||
Cesium.Matrix3.fromQuaternion(orientation),
|
||||
position,
|
||||
);
|
||||
}
|
||||
|
||||
const camera = viewer.camera;
|
||||
|
||||
if (cameraInitPos) {
|
||||
camera.position = new Cesium.Cartesian3(firstPersonOffset, 0.0, 0.0);
|
||||
camera.direction = new Cesium.Cartesian3(1.0, 0.0, 0.0);
|
||||
camera.up = new Cesium.Cartesian3(0.0, 0.0, 1.0);
|
||||
camera.right = new Cesium.Cartesian3(0.0, -1.0, 0.0);
|
||||
cameraInitPos = false;
|
||||
}
|
||||
|
||||
|
||||
// Save camera state
|
||||
const offset = Cesium.Cartesian3.clone(camera.position);
|
||||
const direction = Cesium.Cartesian3.clone(camera.direction);
|
||||
const up = Cesium.Cartesian3.clone(camera.up);
|
||||
|
||||
// Set camera to be in model's reference frame.
|
||||
camera.lookAtTransform(transform);
|
||||
|
||||
// Reset the camera state to the saved state so it appears fixed in the model's frame.
|
||||
Cesium.Cartesian3.clone(offset, camera.position);
|
||||
Cesium.Cartesian3.clone(direction, camera.direction);
|
||||
Cesium.Cartesian3.clone(up, camera.up);
|
||||
Cesium.Cartesian3.cross(direction, up, camera.right);
|
||||
}
|
||||
|
||||
// Image overlays
|
||||
|
||||
function dataCallback(interval, index) {
|
||||
@ -321,6 +433,7 @@
|
||||
// See https://wiki.earthdata.nasa.gov/display/GIBS/GIBS+API+for+Developers#GIBSAPIforDevelopers-OGCWebMapService(WMS)
|
||||
var gibsProvider = new Cesium.WebMapTileServiceImageryProvider({
|
||||
url: "https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/MODIS_Terra_CorrectedReflectance_TrueColor/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpg",
|
||||
layer: '', // FIXME
|
||||
style: "default",
|
||||
tileMatrixSetID: "250m",
|
||||
format: "image/jpeg",
|
||||
@ -339,6 +452,11 @@
|
||||
var cloudProvider = new Cesium.UrlTemplateImageryProvider({
|
||||
url: "https://tilecache.rainviewer.com/v2/satellite/0000000000/256/{z}/{x}/{y}/0/0_0.png"
|
||||
});
|
||||
var auroraProvider = new Cesium.SingleTileImageryProvider({
|
||||
url: "aurora.png",
|
||||
tileWidth: 360,
|
||||
tileHeight: 181
|
||||
});
|
||||
|
||||
var gibsLayer = new Cesium.ImageryLayer(gibsProvider);
|
||||
gibsLayer.show = false;
|
||||
@ -355,13 +473,17 @@
|
||||
const railwaysLayer = new Cesium.ImageryLayer(railwaysProvider);
|
||||
railwaysLayer.show = false;
|
||||
viewer.imageryLayers.add(railwaysLayer);
|
||||
var auroraLayer = new Cesium.ImageryLayer(auroraProvider);
|
||||
auroraLayer.show = false;
|
||||
viewer.imageryLayers.add(auroraLayer);
|
||||
|
||||
const layers = new Map([
|
||||
["nasaGlobalImagery", gibsLayer],
|
||||
["clouds", cloudLayer],
|
||||
["rain", rainLayer],
|
||||
["seaMarks", seaMarksLayer],
|
||||
["railways", railwaysLayer]
|
||||
["railways", railwaysLayer],
|
||||
["aurora", auroraLayer]
|
||||
]);
|
||||
|
||||
function downloadBlob(filename, blob) {
|
||||
@ -440,13 +562,22 @@
|
||||
heading: 0,
|
||||
},
|
||||
});
|
||||
} else if (command.command == "setViewFirstPerson") {
|
||||
if (command.firstPerson) {
|
||||
// Use first person view from entity
|
||||
viewFirstPerson = true;
|
||||
setFirstPersonView(viewer.trackedEntity);
|
||||
} else {
|
||||
viewFirstPerson = false;
|
||||
setThirdPersonView(firstPersonEntity);
|
||||
}
|
||||
} else if (command.command == "playAnimation") {
|
||||
// Play model animation
|
||||
if (command.stop) {
|
||||
//console.log(`stopping animation ${command.animation} for ${command.id}`);
|
||||
stopAnimation(viewer, command);
|
||||
} else {
|
||||
//console.log(`playing animation ${command.animation} for ${command.id}`);
|
||||
//console.log(`playing animation ${command.animation} for ${command.id} command` + JSON.stringify(command));
|
||||
playAnimation(viewer, command, 30);
|
||||
}
|
||||
} else if (command.command == "setDateTime") {
|
||||
@ -465,15 +596,35 @@
|
||||
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider();
|
||||
}
|
||||
} else if (command.provider == "Cesium World Terrain") {
|
||||
viewer.terrainProvider = Cesium.createWorldTerrain();
|
||||
viewer.scene.setTerrain(
|
||||
Cesium.Terrain.fromWorldTerrain({
|
||||
requestWaterMask: command.water,
|
||||
requestVertexNormals: command.terrainLighting
|
||||
})
|
||||
);
|
||||
} else if (command.provider == "CesiumTerrainProvider") {
|
||||
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
|
||||
url: command.url
|
||||
});
|
||||
viewer.scene.setTerrain(
|
||||
new Cesium.Terrain(
|
||||
Cesium.CesiumTerrainProvider.fromUrl(
|
||||
command.url,
|
||||
{
|
||||
requestWaterMask: command.water,
|
||||
requestVertexNormals: command.terrainLighting
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
} else if (command.provider == "ArcGISTiledElevationTerrainProvider") {
|
||||
viewer.terrainProvider = new Cesium.ArcGISTiledElevationTerrainProvider({
|
||||
url: command.url
|
||||
});
|
||||
viewer.scene.setTerrain(
|
||||
new Cesium.Terrain(
|
||||
Cesium.ArcGISTiledElevationTerrainProvider.fromUrl(
|
||||
command.url,
|
||||
{
|
||||
requestWaterMask: command.water,
|
||||
requestVertexNormals: command.terrainLighting
|
||||
})
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log(`Unknown terrain ${command.terrain}`);
|
||||
}
|
||||
@ -486,16 +637,21 @@
|
||||
}
|
||||
} else {
|
||||
if (buildings === undefined) {
|
||||
buildings = viewer.scene.primitives.add(Cesium.createOsmBuildings());
|
||||
Promise.resolve(
|
||||
Cesium.createOsmBuildingsAsync()
|
||||
).then((osmBuildingsTileset) => {
|
||||
buildings = viewer.scene.primitives.add(osmBuildingsTileset);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (command.command == "setSunLight") {
|
||||
// Enable illumination of the globe from the direction of the Sun or camera
|
||||
} else if (command.command == "setLighting") {
|
||||
viewer.scene.globe.enableLighting = command.useSunLight;
|
||||
viewer.scene.globe.nightFadeOutDistance = 0.0;
|
||||
//viewer.scene.globe.nightFadeOutDistance = 0.0; // FIXME: Can't be nearly 0. Causes terrain above horizon to be blacked out in 1.129
|
||||
// Currently Cesium only supports a single light source, either Sun or Directional
|
||||
if (!command.useSunLight) {
|
||||
viewer.scene.light = new Cesium.DirectionalLight({
|
||||
direction: new Cesium.Cartesian3(1, 0, 0)
|
||||
direction: new Cesium.Cartesian3(1, 0, 0),
|
||||
intensity: command.cameraLightIntensity
|
||||
});
|
||||
viewer.scene.preRender.addEventListener(cameraLight);
|
||||
} else {
|
||||
@ -509,10 +665,40 @@
|
||||
viewer.scene.postUpdate.removeEventListener(icrf);
|
||||
}
|
||||
} else if (command.command == "setAntiAliasing") {
|
||||
if (command.antiAliasing == "FXAA") {
|
||||
viewer.scene.postProcessStages.fxaa.enabled = true;
|
||||
viewer.scene.postProcessStages.fxaa.enabled = command.fxaa;
|
||||
viewer.scene.msaaSamples = command.msaa;
|
||||
} else if (command.command == "setHDR") {
|
||||
if (command.hdr) {
|
||||
viewer.scene.highDynamicRange = true;
|
||||
} else {
|
||||
viewer.scene.postProcessStages.fxaa.enabled = false;
|
||||
viewer.scene.highDynamicRange = false;
|
||||
}
|
||||
} else if (command.command == "setFog") {
|
||||
if (command.fog) {
|
||||
viewer.scene.fog.enabled = true;
|
||||
} else {
|
||||
viewer.scene.fog.enabled = false;
|
||||
}
|
||||
} else if (command.command == "showFPS") {
|
||||
if (command.show) {
|
||||
viewer.scene.debugShowFramesPerSecond = true;
|
||||
} else {
|
||||
viewer.scene.debugShowFramesPerSecond = false;
|
||||
}
|
||||
} else if (command.command == "showPFD") {
|
||||
const pfdCanvas = document.getElementById("pfdCanvas");
|
||||
if (command.show == true) {
|
||||
if (pfdTimer === undefined) {
|
||||
pfdTimer = setInterval(updatePFD, 10);
|
||||
pfdRadioAltTimer = setInterval(updateRadioAlt, 250);
|
||||
}
|
||||
canvas.removeAttribute("hidden");
|
||||
} else {
|
||||
canvas.setAttribute("hidden", "hidden");
|
||||
clearInterval(pfdTimer);
|
||||
clearInterval(pfdRadioAltTimer);
|
||||
pfdTimer = undefined;
|
||||
pfdRadioAltTimer = undefined;
|
||||
}
|
||||
} else if (command.command == "showMUF") {
|
||||
if (command.show == true) {
|
||||
@ -550,6 +736,42 @@
|
||||
viewer.dataSources.remove(foF2GeoJSONStream, true);
|
||||
foF2GeoJSONStream = null;
|
||||
}
|
||||
} else if (command.command == "showMagneticDeclination") {
|
||||
if (command.show == true) {
|
||||
viewer.dataSources.add(
|
||||
Cesium.GeoJsonDataSource.load(
|
||||
"/map/data/wmm.geojson",
|
||||
{ describe: describeWMM }
|
||||
)
|
||||
).then(function (dataSource) {
|
||||
if (wmmGeoJSONStream != null) {
|
||||
viewer.dataSources.remove(wmmGeoJSONStream, true);
|
||||
wmmGeoJSONStream = null;
|
||||
}
|
||||
wmmGeoJSONStream = dataSource;
|
||||
});
|
||||
} else {
|
||||
viewer.dataSources.remove(wmmGeoJSONStream, true);
|
||||
wmmGeoJSONStream = null;
|
||||
}
|
||||
} else if (command.command == "showMaidenheadGrid") {
|
||||
showGrid(command.show);
|
||||
|
||||
} else if (command.command == "setDefaultImagery") {
|
||||
// For indexes, see pacakges/widgets/Source/BaseLayerPicker/createDefaultImageryProviderViewModels.js
|
||||
if (command.imagery == "Bing Maps Aerial") {
|
||||
viewer.baseLayerPicker.viewModel.selectedImagery = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[0];
|
||||
} else if (command.imagery == "ArcGIS world imagery") {
|
||||
viewer.baseLayerPicker.viewModel.selectedImagery = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[3];
|
||||
} else if (command.imagery == "Ersi world ocean") {
|
||||
viewer.baseLayerPicker.viewModel.selectedImagery = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[5];
|
||||
} else if (command.imagery == "Sentinel-2") {
|
||||
viewer.baseLayerPicker.viewModel.selectedImagery = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[11];
|
||||
} else if (command.imagery == "Earth at night") {
|
||||
viewer.baseLayerPicker.viewModel.selectedImagery = viewer.baseLayerPicker.viewModel.imageryProviderViewModels[13];
|
||||
} else {
|
||||
console.log(`Unknown imagery ${command.imagery}`);
|
||||
}
|
||||
} else if (command.command == "showLayer") {
|
||||
layers.get(command.layer).show = command.show;
|
||||
} else if (command.command == "setLayerSettings") {
|
||||
@ -585,6 +807,7 @@
|
||||
|
||||
gibsProvider = new Cesium.WebMapTileServiceImageryProvider({
|
||||
url: command.url,
|
||||
layer: '', // FIXME
|
||||
style: "default",
|
||||
tileMatrixSetID: command.tileMatrixSet,
|
||||
format: command.format,
|
||||
@ -619,13 +842,24 @@
|
||||
rainLayer.show = command.show;
|
||||
viewer.imageryLayers.add(rainLayer);
|
||||
layers.set(command.layer, rainLayer);
|
||||
} else if (command.layer == "aurora") {
|
||||
viewer.imageryLayers.remove(auroraLayer, true);
|
||||
auroraProvider = new Cesium.SingleTileImageryProvider({
|
||||
url: "aurora.png",
|
||||
tileWidth: 360,
|
||||
tileHeight: 181
|
||||
});
|
||||
auroraLayer = new Cesium.ImageryLayer(auroraProvider);
|
||||
auroraLayer.show = command.show;
|
||||
viewer.imageryLayers.add(auroraLayer);
|
||||
layers.set(command.layer, auroraLayer);
|
||||
} else {
|
||||
console.log("Unknown layer: " + command.layer);
|
||||
}
|
||||
} else if (command.command == "updateImage") {
|
||||
|
||||
// Textures on entities can flash white when changed: https://github.com/CesiumGS/cesium/issues/1640
|
||||
// so we use a primitive instead of an entity
|
||||
// so we use a primitive instead of an entity - FIXME: No longer working
|
||||
// Can't modify geometry of primitives, so need to create a new primitive each time
|
||||
// Material needs to be set as translucent in order to allow camera to zoom through it
|
||||
var oldImage = images.get(command.name);
|
||||
@ -652,8 +886,12 @@
|
||||
}));
|
||||
images.set(command.name, image);
|
||||
if (oldImage !== undefined) {
|
||||
image.readyPromise.then(function(prim) {
|
||||
const removeListener = viewer.scene.postRender.addEventListener(() => {
|
||||
if (!image.ready) {
|
||||
return;
|
||||
}
|
||||
viewer.scene.primitives.remove(oldImage);
|
||||
removeListener();
|
||||
});
|
||||
}
|
||||
} else if (command.command == "removeImage") {
|
||||
@ -670,7 +908,7 @@
|
||||
} else if (command.command == "removeAllCZMLEntities") {
|
||||
czmlStream.entities.removeAll();
|
||||
} else if (command.command == "czml") {
|
||||
// Implement CLIP_TO_GROUND, to work around https://github.com/CesiumGS/cesium/issues/4049
|
||||
// Implement CLIP_TO_GROUND, to work around https://github.com/CesiumGS/cesium/issues/4049 - Now fixed, so this may be obsolete
|
||||
if (command.hasOwnProperty('altitudeReference') && command.hasOwnProperty('position') && command.position.hasOwnProperty('cartographicDegrees')) {
|
||||
var size = command.position.cartographicDegrees.length;
|
||||
if ((size == 3) || (size == 4)) {
|
||||
@ -694,8 +932,9 @@
|
||||
}
|
||||
czmlStream.process(command);
|
||||
} else {
|
||||
var promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [position]);
|
||||
Cesium.when(promise, function (updatedPositions) {
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [position]),
|
||||
).then((updatedPositions) => {
|
||||
if (height < updatedPositions[0].height) {
|
||||
if (size == 3) {
|
||||
command.position.cartographicDegrees[2] = updatedPositions[0].height;
|
||||
@ -704,13 +943,10 @@
|
||||
}
|
||||
}
|
||||
czmlStream.process(command);
|
||||
}, function () {
|
||||
console.log(`Terrain doesn't support sampleTerrainMostDetailed`);
|
||||
czmlStream.process(command);
|
||||
});
|
||||
};
|
||||
} else {
|
||||
console.log(`Can't currently use altitudeReference when more than one position`);
|
||||
console.log(`Can't currently use altitudeReference when more than one position`, command.position);
|
||||
czmlStream.process(command);
|
||||
}
|
||||
} else if ((command.hasOwnProperty('polygon') && command.polygon.hasOwnProperty('altitudeReference'))
|
||||
@ -739,8 +975,9 @@
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
positions[i] = Cesium.Cartographic.fromDegrees(prim.positions.cartographicDegrees[i * 3 + 0], prim.positions.cartographicDegrees[i * 3 + 1]);
|
||||
}
|
||||
var promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, positions);
|
||||
Cesium.when(promise, function (updatedPositions) {
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, positions),
|
||||
).then((updatedPositions) => {
|
||||
if (clampToGround) {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
prim.positions.cartographicDegrees[i * 3 + 2] = updatedPositions[i].height;
|
||||
@ -753,9 +990,6 @@
|
||||
}
|
||||
}
|
||||
czmlStream.process(command);
|
||||
}, function () {
|
||||
console.log(`Terrain doesn't support sampleTerrainMostDetailed`);
|
||||
czmlStream.process(command);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -784,9 +1018,97 @@
|
||||
}
|
||||
};
|
||||
|
||||
function setFirstPersonView(entity) {
|
||||
if (!Cesium.defined(entity)) {
|
||||
viewer.scene.postUpdate.removeEventListener(cameraFirstPerson);
|
||||
return;
|
||||
}
|
||||
|
||||
const camera = viewer.camera;
|
||||
|
||||
// Save current camera position/orientation so we can restore it, when going back to third person
|
||||
/*
|
||||
cameraSavedPosition = camera.positionWC.clone(cameraSavedPosition);
|
||||
cameraSavedHeading = camera.heading;
|
||||
cameraSavedPitch = camera.pitch;
|
||||
cameraSavedRoll = camera.roll;
|
||||
cameraSavedTransform = camera.transform.clone(cameraSavedTransform);
|
||||
cameraSavedPositionValid = true;
|
||||
console.log("******* SAVED POSITION", cameraSavedPosition, cameraSavedHeading, cameraSavedPitch, cameraSavedRoll, cameraSavedTransform);*/
|
||||
|
||||
viewer.trackedEntity = entity; // So infobox camera icon indicates we're trackingd
|
||||
viewer.cesiumWidget._needTrackedEntityUpdate = false; // Prevent camera from zooming to it a bit later, and overwriting the position we set
|
||||
|
||||
firstPersonEntity = entity;
|
||||
|
||||
// Get size of model, so we can position camera at front of it, rather than in the middle
|
||||
if (Cesium.defined(firstPersonEntity) && Cesium.defined(firstPersonEntity.id)) {
|
||||
var primitives = viewer.scene.primitives;
|
||||
var length = primitives.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var primitive = primitives.get(i);
|
||||
if (primitive.id === firstPersonEntity && primitive instanceof Cesium.Model && primitive.ready) {
|
||||
firstPersonOffset = primitive.boundingSphere.radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cameraInitPos = true;
|
||||
|
||||
viewer.scene.postUpdate.addEventListener(cameraFirstPerson);
|
||||
}
|
||||
|
||||
function setThirdPersonView(entity) {
|
||||
viewer.trackedEntity = undefined; // If we're switching from first to third, ensure trackedEntity changes, so camera switches to it
|
||||
viewer.trackedEntity = entity;
|
||||
viewer.scene.postUpdate.removeEventListener(cameraFirstPerson);
|
||||
firstPersonEntity = undefined;
|
||||
/*if (cameraSavedPositionValid && !Cesium.defined(entity)) {
|
||||
console.log("******* SAVED POSITION RESTORED", cameraSavedPosition, cameraSavedHeading, cameraSavedPitch, cameraSavedRoll, cameraSavedTransform);
|
||||
const camera = viewer.camera;
|
||||
camera.setView({
|
||||
destination: cameraSavedPosition,
|
||||
orientation: {
|
||||
heading: cameraSavedHeading,
|
||||
pitch: cameraSavedPitch,
|
||||
roll: cameraSavedRoll
|
||||
},
|
||||
endTransform: cameraSavedTransform
|
||||
});
|
||||
//camera.transform = Cesium.Matrix4.clone(cameraSavedTransform, camera.transform);
|
||||
cameraSavedPositionValid = false;
|
||||
} else {
|
||||
console.log("******* SAVED POSITION NOT RESTORED");
|
||||
}*/
|
||||
}
|
||||
|
||||
function infoBoxCameraClicked(infoBoxViewModel) {
|
||||
if (infoBoxViewModel.isCameraTracking && viewer.trackedEntity === viewer.selectedEntity) {
|
||||
if (viewFirstPerson === true) {
|
||||
setThirdPersonView(undefined);
|
||||
} else {
|
||||
viewer.trackedEntity = undefined;
|
||||
}
|
||||
} else {
|
||||
const selectedEntity = viewer.selectedEntity;
|
||||
if (viewFirstPerson === true) {
|
||||
setFirstPersonView(selectedEntity);
|
||||
} else {
|
||||
const position = selectedEntity.position;
|
||||
if (Cesium.defined(position)) {
|
||||
setThirdPersonView(selectedEntity);
|
||||
} else {
|
||||
viewer.zoomTo(viewer.selectedEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewer.selectedEntityChanged.addEventListener(function (selectedEntity) {
|
||||
if (Cesium.defined(selectedEntity) && Cesium.defined(selectedEntity.id)) {
|
||||
socket.send(JSON.stringify({ event: "selected", id: selectedEntity.id }));
|
||||
// Calculate it's velocity for PFD
|
||||
velocityVectorProperty = new Cesium.VelocityVectorProperty(selectedEntity.position, false);
|
||||
} else {
|
||||
socket.send(JSON.stringify({ event: "selected" }));
|
||||
}
|
||||
@ -822,6 +1144,24 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use WASD keys to move camera
|
||||
document.addEventListener('keydown', function (event) {
|
||||
var amount = 0.5;
|
||||
if (event.key == 'w') {
|
||||
viewer.camera.moveUp(amount);
|
||||
} else if (event.key == 's') {
|
||||
viewer.camera.moveDown(amount);
|
||||
} else if (event.key == 'a') {
|
||||
viewer.camera.moveLeft(amount);
|
||||
} else if (event.key == 'd') {
|
||||
viewer.camera.moveRight(amount);
|
||||
} else if (event.key == 'q') {
|
||||
viewer.camera.moveForward(amount);
|
||||
} else if (event.key == 'e') {
|
||||
viewer.camera.moveBackward(amount);
|
||||
}
|
||||
});
|
||||
|
||||
Cesium.knockout.getObservable(viewer.clockViewModel, 'shouldAnimate').subscribe(function (isAnimating) {
|
||||
reportClock();
|
||||
});
|
||||
@ -843,6 +1183,166 @@
|
||||
|
||||
</script>
|
||||
</div>
|
||||
<canvas id="pfdCanvas" width="1000" height="1000" style="border:1px solid #000000;" hidden>
|
||||
Browser does not support canvas.
|
||||
</canvas>
|
||||
<style>
|
||||
#pfdCanvas {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
<script src="cockpit.js"></script>
|
||||
<script>
|
||||
|
||||
// Position PFD in centre at bottom
|
||||
const pfdCanvas = document.getElementById("pfdCanvas");
|
||||
pfdCanvas.style.left = ((window.innerWidth / 2) - 250).toString() + "px";
|
||||
pfdCanvas.style.top = (window.innerHeight - 500 - 30).toString() + "px";
|
||||
if (pfdCanvas.style.width > window.innerWidth) {
|
||||
pfdCanvas.style.width = Math.max(window.innerWidth, 250);
|
||||
}
|
||||
if (pfdCanvas.style.height > window.innerHeight) {
|
||||
pfdCanvas.style.height = Math.max(window.innerHeight, 250);
|
||||
}
|
||||
|
||||
function getPropertyValue(entity, propertyName) {
|
||||
const property = entity.properties[propertyName];
|
||||
if (Cesium.defined(property)) {
|
||||
return property.getValue(viewer.clock.currentTime);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Only valid for TimeIntervalCollection properties
|
||||
function getTimerIntervalPropertyValueAt(entity, propertyName, time) {
|
||||
var value = undefined;
|
||||
const property = entity.properties[propertyName];
|
||||
|
||||
if (Cesium.defined(property)) {
|
||||
value = property.getValue(time);
|
||||
if (!Cesium.defined(value)) {
|
||||
value = property.intervals.get(0).data; // Get first available value
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Only valid for SampledProperty properties
|
||||
function getSampledPropertyValueAt(entity, propertyName, time) {
|
||||
var value = undefined;
|
||||
const property = entity.properties[propertyName];
|
||||
|
||||
if (Cesium.defined(property)) {
|
||||
value = property.getValue(time);
|
||||
if (!Cesium.defined(value)) {
|
||||
value = property.getValue(property.getSample(0)); // Get first available value
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
var pfdEntity;
|
||||
var pfd60SecsAgo = new Cesium.JulianDate();
|
||||
var pfdPrevClock;
|
||||
var pfdRadioAltitude;
|
||||
var pfdRadioAltitudeEntity;
|
||||
var pfdRadioAltPosition;
|
||||
|
||||
function updatePFD() {
|
||||
// Display PFD for last selected aircraft
|
||||
const entity = viewer.selectedEntity;
|
||||
if (Cesium.defined(entity) && Cesium.defined(entity.properties) && ((entity.properties.hasProperty("pfdAltitude") || entity.properties.hasProperty("pfdOnSurface")))) {
|
||||
if (entity !== pfdEntity) {
|
||||
pfdRadioAltitude = undefined;
|
||||
}
|
||||
pfdEntity = entity;
|
||||
}
|
||||
if (Cesium.defined(pfdEntity)) {
|
||||
var callsign;
|
||||
var aircraftType;
|
||||
if (pfdEntity.properties.hasProperty("pfdCallsign")) {
|
||||
callsign = pfdEntity.properties["pfdCallsign"].getValue();
|
||||
} else {
|
||||
callsign = "";
|
||||
}
|
||||
if (pfdEntity.properties.hasProperty("pfdAircraftType")) {
|
||||
aircraftType = pfdEntity.properties["pfdAircraftType"].getValue();
|
||||
} else {
|
||||
aircraftType = "";
|
||||
}
|
||||
pfd60SecsAgo = Cesium.JulianDate.addSeconds(viewer.clock.currentTime, -60, pfd60SecsAgo);
|
||||
const onSurface = pfdEntity.properties["pfdOnSurface"].getValue(viewer.clock.currentTime);
|
||||
const wasOnSurface60SecsAgo = getTimerIntervalPropertyValueAt(pfdEntity, "pfdOnSurface", pfd60SecsAgo);
|
||||
const indicatedAirspeed = getPropertyValue(pfdEntity, "pfdIndicatedAirspeed");
|
||||
const trueAirspeed = getPropertyValue(pfdEntity, "pfdTrueAirspeed");
|
||||
const groundspeed = getPropertyValue(pfdEntity, "pfdGroundspeed");
|
||||
const mach = getPropertyValue(pfdEntity, "pfdMach");
|
||||
const altitude = getPropertyValue(pfdEntity, "pfdAltitude");
|
||||
var runwayAltitudeEstimate = undefined;
|
||||
if ((onSurface === 0) && (wasOnSurface60SecsAgo > 0)) {
|
||||
runwayAltitudeEstimate = getSampledPropertyValueAt(pfdEntity, "pfdAltitude", pfd60SecsAgo);
|
||||
}
|
||||
const qnh = getPropertyValue(pfdEntity, "pfdQNH");
|
||||
const verticalSpeed = getPropertyValue(pfdEntity, "pfdVerticalSpeed");
|
||||
const heading = getPropertyValue(pfdEntity, "pfdHeading");
|
||||
const track = getPropertyValue(pfdEntity, "pfdTrack");
|
||||
const roll = getPropertyValue(pfdEntity, "pfdRoll");
|
||||
const selectedAltitude = getPropertyValue(pfdEntity, "pfdSelectedAltitude");
|
||||
const selectedHeading = getPropertyValue(pfdEntity, "pfdSelectedHeading");
|
||||
const autopilot = getPropertyValue(pfdEntity, "pfdAutopilot");
|
||||
const verticalMode = getPropertyValue(pfdEntity, "pfdVerticalMode");
|
||||
const lateralMode = getPropertyValue(pfdEntity, "pfdLateralMode");
|
||||
const tcasMode = getPropertyValue(pfdEntity, "pfdTCASMode");
|
||||
const windSpeed = getPropertyValue(pfdEntity, "pfdWindSpeed");
|
||||
const windDirection = getPropertyValue(pfdEntity, "pfdWindDirection");
|
||||
const staticAirTemperature = getPropertyValue(pfdEntity, "pfdStaticAirTemperature");
|
||||
|
||||
velocityVectorProperty.getValue(viewer.clock.currentTime, velocityVector);
|
||||
const modelSpeedMps = Cesium.Cartesian3.magnitude(velocityVector);
|
||||
const modelSpeedKnots = Math.round(modelSpeedMps * 1.944);
|
||||
|
||||
// Is the clock moving forwards
|
||||
const forward = pfdPrevClock === undefined ? true : Cesium.JulianDate.compare(viewer.clock.currentTime, pfdPrevClock) > 0;
|
||||
|
||||
setPFDData(forward, pfdEntity.id, callsign, aircraftType, onSurface, wasOnSurface60SecsAgo, runwayAltitudeEstimate,
|
||||
modelSpeedKnots, indicatedAirspeed, trueAirspeed, groundspeed, mach, altitude, pfdRadioAltitude, qnh, verticalSpeed, heading, track, roll,
|
||||
selectedAltitude, selectedHeading, autopilot, verticalMode, lateralMode, tcasMode,
|
||||
windSpeed, windDirection, staticAirTemperature
|
||||
);
|
||||
}
|
||||
drawPFD();
|
||||
pfdPrevClock = Cesium.JulianDate.clone(viewer.clock.currentTime, pfdPrevClock);
|
||||
}
|
||||
|
||||
function convertToNearest10Foot(metres) {
|
||||
return Math.round((metres * 3.28084) / 10.0) * 10.0;
|
||||
}
|
||||
|
||||
function updateRadioAlt() {
|
||||
if (Cesium.defined(pfdEntity)) {
|
||||
pfdRadioAltitudeEntity = pfdEntity;
|
||||
pfdRadioAltPosition = pfdRadioAltitudeEntity.position.getValue(viewer.clock.currentTime, pfdRadioAltPosition);
|
||||
if (Cesium.defined(pfdRadioAltPosition)) {
|
||||
if (viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) {
|
||||
pfdRadioAltitude = convertToNearest10Foot(Cesium.Cartographic.fromCartesian(pfdRadioAltPosition).height);
|
||||
} else {
|
||||
Promise.resolve(
|
||||
Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [pfdRadioAltPosition]),
|
||||
).then((updatedPositions) => {
|
||||
if (pfdRadioAltitudeEntity === pfdEntity) {
|
||||
pfdRadioAltitude = convertToNearest10Foot(Cesium.Cartographic.fromCartesian(updatedPositions[0]).height);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
61
plugins/feature/map/mapaircraftstate.h
Normal file
61
plugins/feature/map/mapaircraftstate.h
Normal file
@ -0,0 +1,61 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2025 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as 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 General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_FEATURE_MAPAIRCRAFTSTATE_H_
|
||||
#define INCLUDE_FEATURE_MAPAIRCRAFTSTATE_H_
|
||||
|
||||
struct MapAircraftState {
|
||||
QString m_callsign;
|
||||
QString m_aircraftType;
|
||||
int m_onSurface; // -1 for unknown
|
||||
float m_indicatedAirspeed; // NaN for unknown
|
||||
QString m_indicatedAirspeedDateTime;
|
||||
float m_trueAirspeed;
|
||||
float m_groundspeed;
|
||||
float m_mach;
|
||||
float m_altitude;
|
||||
QString m_altitudeDateTime;
|
||||
float m_qnh;
|
||||
float m_verticalSpeed;
|
||||
float m_heading;
|
||||
float m_track;
|
||||
float m_selectedAltitude;
|
||||
float m_selectedHeading;
|
||||
int m_autopilot; // -1 for unknown
|
||||
enum VerticalMode {
|
||||
UNKNOWN_VERTICAL_MODE,
|
||||
VNAV,
|
||||
ALT_HOLD,
|
||||
GS
|
||||
} m_verticalMode;
|
||||
enum LateralMode {
|
||||
UNKNOWN_LATERAL_MODE,
|
||||
LNAV,
|
||||
LOC
|
||||
} m_lateralMode;
|
||||
enum TCASMode {
|
||||
UNKNOWN_TCAS_MODE,
|
||||
TCAS_OFF,
|
||||
TA,
|
||||
TA_RA
|
||||
} m_tcasMode;
|
||||
float m_windSpeed;
|
||||
float m_windDirection;
|
||||
float m_staticAirTemperature;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPAIRCRAFTSTATE_H_
|
@ -1929,7 +1929,6 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
||||
ui->web->load(QUrl(QString("http://127.0.0.1:%1/map/map/map3d.html").arg(m_webPort)));
|
||||
//ui->web->load(QUrl(QString("http://webglreport.com/")));
|
||||
//ui->web->load(QUrl(QString("https://sandcastle.cesium.com/")));
|
||||
//ui->web->load(QUrl("chrome://gpu/"));
|
||||
ui->web->show();
|
||||
}
|
||||
else if (!m_settings.m_map3DEnabled && (m_cesium != nullptr))
|
||||
@ -1948,9 +1947,11 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
||||
m_cesium->setCameraReferenceFrame(m_settings.m_eciCamera);
|
||||
m_cesium->setAntiAliasing(m_settings.m_fxaa, m_settings.m_msaa);
|
||||
m_cesium->getDateTime();
|
||||
m_cesium->setViewFirstPerson(m_settings.m_viewFirstPerson);
|
||||
m_cesium->setHDR(m_settings.m_hdr);
|
||||
m_cesium->setFog(m_settings.m_fog);
|
||||
m_cesium->showFPS(m_settings.m_fps);
|
||||
m_cesium->showPFD(m_settings.m_displayPFD);
|
||||
m_cesium->showMUF(m_settings.m_displayMUF);
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
m_cesium->showMagDec(m_settings.m_displayMagDec);
|
||||
@ -1968,10 +1969,13 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
||||
m_polylineMapModel.allUpdated();
|
||||
}
|
||||
MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations");
|
||||
if (m_giro)
|
||||
{
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (ionosondeItemSettings) {
|
||||
m_giro->getDataPeriodically(ionosondeItemSettings->m_enabled ? 2 : 0);
|
||||
}
|
||||
}
|
||||
if (m_aurora) {
|
||||
m_aurora->getDataPeriodically(m_settings.m_displayAurora ? 30 : 0);
|
||||
}
|
||||
@ -2084,6 +2088,8 @@ void MapGUI::displaySettings()
|
||||
setTitle(m_settings.m_title);
|
||||
blockApplySettings(true);
|
||||
ui->displayNames->setChecked(m_settings.m_displayNames);
|
||||
ui->viewFirstPerson->setChecked(m_settings.m_viewFirstPerson);
|
||||
ui->displayPFD->setChecked(m_settings.m_displayPFD);
|
||||
ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks);
|
||||
ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks);
|
||||
ui->displayRain->setChecked(m_settings.m_displayRain);
|
||||
@ -2202,6 +2208,24 @@ void MapGUI::on_maidenhead_clicked()
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MapGUI::on_viewFirstPerson_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_viewFirstPerson = checked;
|
||||
if (m_cesium) {
|
||||
m_cesium->setViewFirstPerson(checked);
|
||||
}
|
||||
applySetting("viewFirstPerson");
|
||||
}
|
||||
|
||||
void MapGUI::on_displayPFD_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_displayPFD = checked;
|
||||
if (m_cesium) {
|
||||
m_cesium->showPFD(checked);
|
||||
}
|
||||
applySetting("viewFirstPerson");
|
||||
}
|
||||
|
||||
void MapGUI::on_displayNames_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_displayNames = checked;
|
||||
@ -2415,10 +2439,10 @@ void MapGUI::on_displayMUF_clicked(bool checked)
|
||||
m_displayMUF->setChecked(checked);
|
||||
}
|
||||
m_settings.m_displayMUF = checked;
|
||||
// Only call show if disabling, so we don't get two updates
|
||||
// (as getMUFPeriodically results in a call to showMUF when the data is available)
|
||||
if (m_giro) {
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (m_cesium && !m_settings.m_displayMUF) {
|
||||
}
|
||||
if (m_cesium) {
|
||||
m_cesium->showMUF(m_settings.m_displayMUF);
|
||||
}
|
||||
applySetting("displayMUF");
|
||||
@ -2433,8 +2457,10 @@ void MapGUI::on_displayfoF2_clicked(bool checked)
|
||||
m_displayfoF2->setChecked(checked);
|
||||
}
|
||||
m_settings.m_displayfoF2 = checked;
|
||||
if (m_giro) {
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (m_cesium && !m_settings.m_displayfoF2) {
|
||||
}
|
||||
if (m_cesium) {
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
}
|
||||
applySetting("displayfoF2");
|
||||
@ -3009,6 +3035,8 @@ void MapGUI::preferenceChanged(int elementType)
|
||||
void MapGUI::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->displayNames, &ButtonSwitch::clicked, this, &MapGUI::on_displayNames_clicked);
|
||||
QObject::connect(ui->viewFirstPerson, &ButtonSwitch::clicked, this, &MapGUI::on_viewFirstPerson_clicked);
|
||||
QObject::connect(ui->displayPFD, &ButtonSwitch::clicked, this, &MapGUI::on_displayPFD_clicked);
|
||||
QObject::connect(ui->displayAllGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displayAllGroundTracks_clicked);
|
||||
QObject::connect(ui->displaySelectedGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displaySelectedGroundTracks_clicked);
|
||||
QObject::connect(ui->displayRain, &ButtonSwitch::clicked, this, &MapGUI::on_displayRain_clicked);
|
||||
|
@ -326,6 +326,8 @@ private slots:
|
||||
void onMenuDialogCalled(const QPoint &p);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void handleInputMessages();
|
||||
void on_viewFirstPerson_clicked(bool checked=false);
|
||||
void on_displayPFD_clicked(bool checked=false);
|
||||
void on_displayNames_clicked(bool checked=false);
|
||||
void on_displayAllGroundTracks_clicked(bool checked=false);
|
||||
void on_displaySelectedGroundTracks_clicked(bool checked=false);
|
||||
|
@ -29,7 +29,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
<enum>Qt::FocusPolicy::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Map</string>
|
||||
@ -39,7 +39,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1221</width>
|
||||
<width>1251</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -166,7 +166,7 @@
|
||||
<string>IBP</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/ibp.png</normaloff>:/map/icons/ibp.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
@ -180,7 +180,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/clock.png</normaloff>:/map/icons/clock.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
@ -191,11 +191,11 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/layers.png</normaloff>:/map/icons/layers.png</iconset>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::ToolButtonPopupMode::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -205,10 +205,10 @@
|
||||
<string>Display satellite infra-red (clouds)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/cloud.png</normaloff>:/map/icons/cloud.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -225,10 +225,10 @@
|
||||
<string>Display weather radar (rain/snow)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/precipitation.png</normaloff>:/map/icons/precipitation.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -245,10 +245,10 @@
|
||||
<string>Display sea marks</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/anchor.png</normaloff>:/map/icons/anchor.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -265,10 +265,10 @@
|
||||
<string>Display railways</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/railway.png</normaloff>:/map/icons/railway.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -285,10 +285,10 @@
|
||||
<string>Display MUF (Maximum Usable Frequency) contours (3D only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/muf.png</normaloff>:/map/icons/muf.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -305,10 +305,10 @@
|
||||
<string>Display foF2 (F2 layer critical frequency) contours (3D only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/fof2.png</normaloff>:/map/icons/fof2.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -365,10 +365,10 @@
|
||||
<string>Display NASA GIBS data</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/earthsat.png</normaloff>:/map/icons/earthsat.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -430,7 +430,7 @@
|
||||
<string>Display names</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
@ -444,13 +444,54 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="viewFirstPerson">
|
||||
<property name="toolTip">
|
||||
<string>First person / third person view on 3D map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/thirdperson.png</normaloff>
|
||||
<normalon>:/map/icons/firstperson.png</normalon>:/map/icons/thirdperson.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="displayPFD">
|
||||
<property name="toolTip">
|
||||
<string>Display aircraft PFD (Primary Flight Display) on 3D map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/pfd.png</normaloff>:/map/icons/pfd.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="displaySelectedGroundTracks">
|
||||
<property name="toolTip">
|
||||
<string>Display ground tracks for selected item</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
@ -470,10 +511,10 @@
|
||||
<string>Display all ground tracks</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/groundtracks.png</normaloff>:/map/icons/groundtracks.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
@ -573,7 +614,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QQuickWidget" name="map">
|
||||
<property name="sizePolicy">
|
||||
@ -592,7 +633,7 @@
|
||||
<string>Map</string>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QQuickWidget::SizeRootObjectToView</enum>
|
||||
<enum>QQuickWidget::ResizeMode::SizeRootObjectToView</enum>
|
||||
</property>
|
||||
<property name="source">
|
||||
<url>
|
||||
@ -655,7 +696,7 @@
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
<include location="icons.qrc"/>
|
||||
<include location="mapicons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -15,6 +15,7 @@
|
||||
<file>icons/waypoints.png</file>
|
||||
<file>icons/earthsat.png</file>
|
||||
<file>icons/aurora.png</file>
|
||||
<file>icons/pfd.png</file>
|
||||
<file>icons/compass.png</file>
|
||||
<file>icons/grid.png</file>
|
||||
<file>icons/thirdperson.png</file>
|
||||
|
@ -34,6 +34,11 @@ void MapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
} else {
|
||||
m_label = "";
|
||||
}
|
||||
if (mapItem->getLabelDateTime()) {
|
||||
m_labelDateTime = QDateTime::fromString(*mapItem->getLabelDateTime(), Qt::ISODateWithMs);
|
||||
} else {
|
||||
m_labelDateTime = QDateTime();
|
||||
}
|
||||
m_latitude = mapItem->getLatitude();
|
||||
m_longitude = mapItem->getLongitude();
|
||||
m_altitude = mapItem->getAltitude();
|
||||
@ -47,6 +52,8 @@ QGeoCoordinate MapItem::getCoordinates()
|
||||
return coords;
|
||||
}
|
||||
|
||||
WhittakerEilers ObjectMapItem::m_filter;
|
||||
|
||||
void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
{
|
||||
MapItem::update(mapItem);
|
||||
@ -55,6 +62,11 @@ void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
} else {
|
||||
m_positionDateTime = QDateTime();
|
||||
}
|
||||
if (mapItem->getAltitudeDateTime()) {
|
||||
m_altitudeDateTime = QDateTime::fromString(*mapItem->getAltitudeDateTime(), Qt::ISODateWithMs);
|
||||
} else {
|
||||
m_altitudeDateTime = QDateTime();
|
||||
}
|
||||
m_useHeadingPitchRoll = mapItem->getOrientation() == 1;
|
||||
m_heading = mapItem->getHeading();
|
||||
m_pitch = mapItem->getPitch();
|
||||
@ -79,6 +91,14 @@ void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
}
|
||||
m_labelAltitudeOffset = mapItem->getLabelAltitudeOffset();
|
||||
m_modelAltitudeOffset = mapItem->getModelAltitudeOffset();
|
||||
// FIXME: See nodeTransformations comment in czml.cpp
|
||||
// We can't use nodeTransformations, so adjust altitude instead
|
||||
if (m_modelAltitudeOffset != 0)
|
||||
{
|
||||
m_labelAltitudeOffset -= m_modelAltitudeOffset;
|
||||
m_altitude += m_modelAltitudeOffset;
|
||||
m_modelAltitudeOffset = 0;
|
||||
}
|
||||
m_altitudeReference = mapItem->getAltitudeReference();
|
||||
m_fixedPosition = mapItem->getFixedPosition();
|
||||
QList<SWGSDRangel::SWGMapAnimation *> *animations = mapItem->getAnimations();
|
||||
@ -91,7 +111,7 @@ void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
findFrequencies();
|
||||
if (!m_fixedPosition)
|
||||
{
|
||||
updateTrack(mapItem->getTrack());
|
||||
updateTrack(mapItem->getTrack(), m_itemSettings);
|
||||
updatePredictedTrack(mapItem->getPredictedTrack());
|
||||
}
|
||||
if (mapItem->getAvailableFrom()) {
|
||||
@ -104,6 +124,47 @@ void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
} else {
|
||||
m_availableUntil = QDateTime();
|
||||
}
|
||||
if (mapItem->getAircraftState()) {
|
||||
if (!m_aircraftState) {
|
||||
m_aircraftState = new MapAircraftState();
|
||||
}
|
||||
SWGSDRangel::SWGMapAircraftState *as = mapItem->getAircraftState();
|
||||
if (as->getCallsign()) {
|
||||
m_aircraftState->m_callsign = *as->getCallsign();
|
||||
}
|
||||
if (as->getAircraftType()) {
|
||||
m_aircraftState->m_aircraftType = *as->getAircraftType();
|
||||
}
|
||||
m_aircraftState->m_onSurface = as->getOnSurface();
|
||||
m_aircraftState->m_indicatedAirspeed = as->getAirspeed();
|
||||
if (as->getAirspeedDateTime()) {
|
||||
m_aircraftState->m_indicatedAirspeedDateTime = *as->getAirspeedDateTime();
|
||||
} else {
|
||||
m_aircraftState->m_indicatedAirspeedDateTime = QString();
|
||||
}
|
||||
m_aircraftState->m_trueAirspeed = as->getTrueAirspeed();
|
||||
m_aircraftState->m_groundspeed = as->getGroundspeed();
|
||||
m_aircraftState->m_mach = as->getMach();
|
||||
m_aircraftState->m_altitude = as->getAltitude();
|
||||
if (as->getAltitudeDateTime()) {
|
||||
m_aircraftState->m_altitudeDateTime = *as->getAltitudeDateTime();
|
||||
} else {
|
||||
m_aircraftState->m_altitudeDateTime = QString();
|
||||
}
|
||||
m_aircraftState->m_qnh = as->getQnh();
|
||||
m_aircraftState->m_verticalSpeed = as->getVerticalSpeed();
|
||||
m_aircraftState->m_heading = as->getHeading();
|
||||
m_aircraftState->m_track = as->getTrack();
|
||||
m_aircraftState->m_selectedAltitude = as->getSelectedAltitude();
|
||||
m_aircraftState->m_selectedHeading = as->getSelectedHeading();
|
||||
m_aircraftState->m_autopilot = as->getAutopilot();
|
||||
m_aircraftState->m_verticalMode = (MapAircraftState::VerticalMode) as->getVerticalMode();
|
||||
m_aircraftState->m_lateralMode = (MapAircraftState::LateralMode) as->getLateralMode();
|
||||
m_aircraftState->m_tcasMode = (MapAircraftState::TCASMode) as->getTcasMode();
|
||||
m_aircraftState->m_windSpeed = as->getWindSpeed();
|
||||
m_aircraftState->m_windDirection = as->getWindDirection();
|
||||
m_aircraftState->m_staticAirTemperature = as->getStaticAirTemperature();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
@ -225,7 +286,145 @@ void ObjectMapItem::findFrequencies()
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
void ObjectMapItem::extrapolatePosition(QGeoCoordinate *c, const QDateTime& dateTime)
|
||||
{
|
||||
int p1;
|
||||
int p2;
|
||||
|
||||
// Find last two non extrapolated position
|
||||
for (p2 = m_takenTrackPositionExtrapolated.size() - 1 ; p2 >= 0; p2--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p2]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (p1 = p2 - 1 ; p1 >= 0; p1--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p1 < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
qint64 t1 = m_takenTrackDateTimes[p1]->msecsTo(*m_takenTrackDateTimes[p2]);
|
||||
qint64 t2 = m_takenTrackDateTimes[p2]->msecsTo(dateTime);
|
||||
|
||||
double latV = (m_takenTrackCoords[p2]->latitude() - m_takenTrackCoords[p1]->latitude()) / t1;
|
||||
double lonV = (m_takenTrackCoords[p2]->longitude() - m_takenTrackCoords[p1]->longitude()) / t1;
|
||||
|
||||
double newLat = m_takenTrackCoords[p2]->latitude() + latV * t2;
|
||||
double newLon = m_takenTrackCoords[p2]->longitude() + lonV * t2;
|
||||
|
||||
c->setLatitude(newLat);
|
||||
c->setLongitude(newLon);
|
||||
}
|
||||
|
||||
void ObjectMapItem::extrapolateAltitude(QGeoCoordinate *c, const QDateTime& dateTime)
|
||||
{
|
||||
int p1;
|
||||
int p2;
|
||||
|
||||
// Find last two non extrapolated position
|
||||
for (p2 = m_takenTrackPositionExtrapolated.size() - 1 ; p2 >= 0; p2--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p2]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (p1 = p2 - 1 ; p1 >= 0; p1--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p1 < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
qint64 t1 = m_takenTrackDateTimes[p1]->msecsTo(*m_takenTrackDateTimes[p2]);
|
||||
qint64 t2 = m_takenTrackDateTimes[p2]->msecsTo(dateTime);
|
||||
|
||||
double vertV = (m_takenTrackCoords[p2]->altitude() - m_takenTrackCoords[p1]->altitude()) / t1;
|
||||
|
||||
double newAlt = m_takenTrackCoords[p2]->latitude() + vertV * t2;
|
||||
|
||||
c->setAltitude(newAlt);
|
||||
}
|
||||
|
||||
void ObjectMapItem::interpolatePosition(int p2, const float p3Latitude, const float p3Longitude, const QDateTime &p3DateTime)
|
||||
{
|
||||
// p1 last non extrapolated position
|
||||
// p2 interpolated position
|
||||
// p3 current non extrapolated position
|
||||
|
||||
// Find last non extrapolated position
|
||||
int p1;
|
||||
|
||||
for (p1 = p2 - 1; p1 >= 0; p1--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p1 < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 t1 = m_takenTrackDateTimes[p1]->msecsTo(p3DateTime);
|
||||
qint64 t2 = m_takenTrackDateTimes[p1]->msecsTo(*m_takenTrackDateTimes[p2]);
|
||||
|
||||
double latV = (p3Latitude - m_takenTrackCoords[p1]->latitude()) / t1;
|
||||
double lonV = (p3Longitude - m_takenTrackCoords[p1]->longitude()) / t1;
|
||||
|
||||
double newLat = m_takenTrackCoords[p1]->latitude() + latV * t2;
|
||||
double newLon = m_takenTrackCoords[p1]->longitude() + lonV * t2;
|
||||
|
||||
m_takenTrackCoords[p2]->setLatitude(newLat);
|
||||
m_takenTrackCoords[p2]->setLongitude(newLon);
|
||||
|
||||
m_interpolatedCoords.append(m_takenTrackCoords[p2]);
|
||||
m_interpolatedDateTimes.append(m_takenTrackDateTimes[p2]);
|
||||
}
|
||||
|
||||
void ObjectMapItem::interpolateAltitude(int p2, const float p3Altitude, const QDateTime &p3DateTime)
|
||||
{
|
||||
// p1 last non extrapolated position
|
||||
// p2 interpolated position
|
||||
// p3 current non extrapolated position
|
||||
|
||||
// Find last non extrapolated position
|
||||
int p1;
|
||||
|
||||
for (p1 = p2 - 1; p1 >= 0; p1--)
|
||||
{
|
||||
if (!m_takenTrackPositionExtrapolated[p1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p1 < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 t1 = m_takenTrackDateTimes[p1]->msecsTo(p3DateTime);
|
||||
qint64 t2 = m_takenTrackDateTimes[p1]->msecsTo(*m_takenTrackDateTimes[p2]);
|
||||
|
||||
double vertV = (p3Altitude - m_takenTrackCoords[p1]->altitude()) / t1;
|
||||
|
||||
double newAlt = m_takenTrackCoords[p1]->altitude() + vertV * t2;
|
||||
|
||||
m_takenTrackCoords[p2]->setAltitude(newAlt);
|
||||
|
||||
m_interpolatedCoords.append(m_takenTrackCoords[p2]);
|
||||
m_interpolatedDateTimes.append(m_takenTrackDateTimes[p2]);
|
||||
}
|
||||
|
||||
void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track, MapSettings::MapItemSettings *itemSettings)
|
||||
{
|
||||
if (track != nullptr)
|
||||
{
|
||||
@ -233,6 +432,8 @@ void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
m_takenTrackCoords.clear();
|
||||
qDeleteAll(m_takenTrackDateTimes);
|
||||
m_takenTrackDateTimes.clear();
|
||||
m_takenTrackPositionExtrapolated.clear();
|
||||
m_takenTrackAltitudeExtrapolated.clear();
|
||||
m_takenTrack.clear();
|
||||
m_takenTrack1.clear();
|
||||
m_takenTrack2.clear();
|
||||
@ -243,6 +444,8 @@ void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
QDateTime *d = new QDateTime(QDateTime::fromString(*p->getDateTime(), Qt::ISODate));
|
||||
m_takenTrackCoords.push_back(c);
|
||||
m_takenTrackDateTimes.push_back(d);
|
||||
m_takenTrackPositionExtrapolated.push_back(false);
|
||||
m_takenTrackAltitudeExtrapolated.push_back(false);
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
}
|
||||
@ -253,28 +456,188 @@ void ObjectMapItem::updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
{
|
||||
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
|
||||
m_takenTrackCoords.push_back(c);
|
||||
if (m_positionDateTime.isValid()) {
|
||||
if (m_altitudeDateTime.isValid()) {
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(m_altitudeDateTime));
|
||||
} else if (m_positionDateTime.isValid()) {
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(m_positionDateTime));
|
||||
} else {
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(QDateTime::currentDateTime()));
|
||||
}
|
||||
m_takenTrackPositionExtrapolated.push_back(false);
|
||||
m_takenTrackAltitudeExtrapolated.push_back(false);
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
else
|
||||
{
|
||||
QGeoCoordinate *prev = m_takenTrackCoords.last();
|
||||
// For Whittaker-Eilers filtering, we need to make sure we don't have 2 data with the same time
|
||||
// so we just update the last item if the prev time is the same
|
||||
// To reduce size of list for stationary items, we only store two items with same position
|
||||
// We store two, rather than one, so that we have the times this position was arrived at and left
|
||||
|
||||
const bool interpolate = false;
|
||||
|
||||
QGeoCoordinate *prev1 = m_takenTrackCoords.last();
|
||||
bool samePos1 = (prev1->latitude() == m_latitude) && (prev1->longitude() == m_longitude) && (prev1->altitude() == m_altitude);
|
||||
QGeoCoordinate *prev2 = m_takenTrackCoords.size() > 1 ? m_takenTrackCoords[m_takenTrackCoords.size() - 2] : nullptr;
|
||||
bool samePos2 = prev2 && samePos1 ? (prev2->latitude() == m_latitude) && (prev2->longitude() == m_longitude) && (prev2->altitude() == m_altitude) : false;
|
||||
|
||||
QDateTime *prevDateTime = m_takenTrackDateTimes.last();
|
||||
if ((prev->latitude() != m_latitude) || (prev->longitude() != m_longitude)
|
||||
|| (prev->altitude() != m_altitude) || (*prevDateTime != m_positionDateTime))
|
||||
|
||||
QGeoCoordinate c(m_latitude, m_longitude, m_altitude);
|
||||
int prevSize = m_takenTrackPositionExtrapolated.size();
|
||||
|
||||
if (m_altitudeDateTime.isValid() && m_positionDateTime.isValid() && (m_altitudeDateTime > m_positionDateTime))
|
||||
{
|
||||
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
|
||||
m_takenTrackCoords.push_back(c);
|
||||
if (m_positionDateTime.isValid()) {
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(m_positionDateTime));
|
||||
} else {
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(QDateTime::currentDateTime()));
|
||||
if (interpolate)
|
||||
{
|
||||
for (int i = m_takenTrackAltitudeExtrapolated.size() - 1; (i >= 0) && m_takenTrackAltitudeExtrapolated[i]; i--)
|
||||
{
|
||||
interpolateAltitude(i, m_altitude, m_altitudeDateTime);
|
||||
m_takenTrackAltitudeExtrapolated[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePos2)
|
||||
{
|
||||
*m_takenTrackDateTimes.last() = m_altitudeDateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
extrapolatePosition(&c, m_altitudeDateTime);
|
||||
if (m_altitudeDateTime == *prevDateTime)
|
||||
{
|
||||
m_takenTrackPositionExtrapolated[m_takenTrackPositionExtrapolated.size() - 1] = true;
|
||||
*m_takenTrackCoords.last() = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(m_altitudeDateTime));
|
||||
m_takenTrackPositionExtrapolated.push_back(true);
|
||||
m_takenTrackAltitudeExtrapolated.push_back(false);
|
||||
m_takenTrackCoords.push_back(new QGeoCoordinate(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_positionDateTime.isValid())
|
||||
{
|
||||
if (interpolate)
|
||||
{
|
||||
for (int i = m_takenTrackPositionExtrapolated.size() - 1; (i >= 0) && m_takenTrackPositionExtrapolated[i]; i--)
|
||||
{
|
||||
interpolatePosition(i, m_latitude, m_longitude, m_positionDateTime);
|
||||
m_takenTrackPositionExtrapolated[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_positionDateTime > *m_takenTrackDateTimes.last())
|
||||
{
|
||||
if (samePos2)
|
||||
{
|
||||
*m_takenTrackDateTimes.last() = m_positionDateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool extrapolateAlt = m_altitudeDateTime.isValid() && (m_positionDateTime > m_altitudeDateTime);
|
||||
if (extrapolateAlt) {
|
||||
extrapolateAltitude(&c, m_positionDateTime);
|
||||
}
|
||||
if (m_positionDateTime == *prevDateTime)
|
||||
{
|
||||
m_takenTrackAltitudeExtrapolated[m_takenTrackPositionExtrapolated.size() - 1] = extrapolateAlt;
|
||||
*m_takenTrackCoords.last() = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(m_positionDateTime));
|
||||
m_takenTrackPositionExtrapolated.push_back(false);
|
||||
m_takenTrackAltitudeExtrapolated.push_back(extrapolateAlt);
|
||||
m_takenTrackCoords.push_back(new QGeoCoordinate(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//qDebug() << "m_positionDateTime matches last datetime" << samePos1 << samePos2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_takenTrackDateTimes.push_back(new QDateTime(QDateTime::currentDateTime()));
|
||||
m_takenTrackPositionExtrapolated.push_back(false);
|
||||
m_takenTrackAltitudeExtrapolated.push_back(false);
|
||||
//m_takenTrackCoords.push_back(c);
|
||||
m_takenTrackCoords.push_back(new QGeoCoordinate(c));
|
||||
}
|
||||
//m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
m_takenTrack.push_back(QVariant::fromValue(c));
|
||||
|
||||
if (m_takenTrackDateTimes.size() >= 2) {
|
||||
if (*m_takenTrackDateTimes[m_takenTrackDateTimes.size() - 1] < *m_takenTrackDateTimes[m_takenTrackDateTimes.size() - 2]) {
|
||||
qDebug() << "Out of order";
|
||||
}
|
||||
}
|
||||
|
||||
if ((m_takenTrackPositionExtrapolated.size() > 0) && (prevSize != m_takenTrackPositionExtrapolated.size()))
|
||||
{
|
||||
const int filterLen = itemSettings->m_smoothingWindow;
|
||||
if ((filterLen > 0) && (m_takenTrackCoords.size() >= filterLen) && (m_takenTrackCoords.size() % (filterLen/2)) == 0)
|
||||
{
|
||||
// Filter last filterLen coords
|
||||
QVector<double> x(filterLen);
|
||||
QVector<double> y1(filterLen);
|
||||
QVector<double> y2(filterLen);
|
||||
QVector<double> y3(filterLen);
|
||||
QVector<double> w1(filterLen);
|
||||
QVector<double> w3(filterLen);
|
||||
|
||||
//qDebug() << "Filter from" << (m_takenTrackCoords.size() - (filterLen - 0)) << "to" << (m_takenTrackCoords.size() - (filterLen - (filterLen - 1)));
|
||||
for (int i = 0; i < filterLen; i++)
|
||||
{
|
||||
int idx = m_takenTrackCoords.size() - (filterLen - i);
|
||||
x[i] = (m_takenTrackDateTimes[idx]->toMSecsSinceEpoch() - m_takenTrackDateTimes[0]->toMSecsSinceEpoch()) / 1000.0;
|
||||
y1[i] = m_takenTrackCoords[idx]->latitude();
|
||||
y2[i] = m_takenTrackCoords[idx]->longitude();
|
||||
y3[i] = m_takenTrackCoords[idx]->altitude();
|
||||
if (i < (filterLen / 4))
|
||||
{
|
||||
w1[i] = 10.0; // Try to avoid discontinuities between windows
|
||||
w3[i] = 10.0;
|
||||
}
|
||||
else if (i == filterLen - 1)
|
||||
{
|
||||
w1[i] = 1.0;
|
||||
w3[i] = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
w1[i] = m_takenTrackPositionExtrapolated[idx] ? 0.0 : 1.0;
|
||||
w3[i] = m_takenTrackAltitudeExtrapolated[idx] ? 0.0 : 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
const double lambda = itemSettings->m_smoothingLambda;
|
||||
m_filter.filter(x.data(), y1.data(), w1.data(), filterLen, lambda);
|
||||
m_filter.filter(x.data(), y2.data(), w1.data(), filterLen, lambda);
|
||||
m_filter.filter(x.data(), y3.data(), w3.data(), filterLen, lambda);
|
||||
|
||||
for (int i = 0; i < filterLen; i++)
|
||||
{
|
||||
int idx = m_takenTrackCoords.size() - (filterLen - i);
|
||||
m_takenTrackCoords[idx]->setLatitude(y1[i]);
|
||||
m_takenTrackCoords[idx]->setLongitude(y2[i]);
|
||||
m_takenTrackCoords[idx]->setAltitude(y3[i]);
|
||||
m_takenTrackPositionExtrapolated[idx] = false;
|
||||
m_takenTrackAltitudeExtrapolated[idx] = false;
|
||||
|
||||
m_interpolatedCoords.append(m_takenTrackCoords[idx]);
|
||||
m_interpolatedDateTimes.append(m_takenTrackDateTimes[idx]);
|
||||
}
|
||||
|
||||
// Update current position
|
||||
m_latitude = m_takenTrackCoords[filterLen-1]->latitude();
|
||||
m_longitude = m_takenTrackCoords[filterLen-1]->longitude();
|
||||
m_altitude = m_takenTrackCoords[filterLen-1]->altitude();
|
||||
}
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
#include "mapsettings.h"
|
||||
#include "cesiuminterface.h"
|
||||
#include "util/whittakereilers.h"
|
||||
#include "mapaircraftstate.h"
|
||||
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
@ -58,6 +60,7 @@ protected:
|
||||
|
||||
QString m_name; // Unique id
|
||||
QString m_label;
|
||||
QDateTime m_labelDateTime; // Date & time from which this label is valid from (for 3D map). Invalid date/time is forever
|
||||
float m_latitude; // Position for label
|
||||
float m_longitude;
|
||||
float m_altitude; // In metres
|
||||
@ -65,13 +68,13 @@ protected:
|
||||
QDateTime m_availableUntil; // Date & time this item is visible until (for 3D map). Invalid date/time is forever
|
||||
};
|
||||
|
||||
|
||||
// Information required about each item displayed on the map
|
||||
class ObjectMapItem : public MapItem {
|
||||
|
||||
public:
|
||||
ObjectMapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem) :
|
||||
MapItem(sourcePipe, group, itemSettings, mapItem)
|
||||
MapItem(sourcePipe, group, itemSettings, mapItem),
|
||||
m_aircraftState(nullptr)
|
||||
{
|
||||
update(mapItem);
|
||||
}
|
||||
@ -79,12 +82,17 @@ public:
|
||||
|
||||
protected:
|
||||
void findFrequencies();
|
||||
void updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
|
||||
void updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track, MapSettings::MapItemSettings *itemSettings);
|
||||
void updatePredictedTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track);
|
||||
void extrapolatePosition(QGeoCoordinate *c, const QDateTime& dateTime);
|
||||
void extrapolateAltitude(QGeoCoordinate *c, const QDateTime& dateTime);
|
||||
void interpolatePosition(int i, const float latitude, const float longitude, const QDateTime &dateTime);
|
||||
void interpolateAltitude(int i, const float altitude, const QDateTime &dateTime);
|
||||
|
||||
friend ObjectMapModel;
|
||||
friend CZML;
|
||||
QDateTime m_positionDateTime;
|
||||
QDateTime m_altitudeDateTime;
|
||||
bool m_useHeadingPitchRoll;
|
||||
float m_heading;
|
||||
float m_pitch;
|
||||
@ -107,6 +115,8 @@ protected:
|
||||
QGeoCoordinate m_predictedEnd2;
|
||||
QList<QGeoCoordinate *> m_takenTrackCoords;
|
||||
QList<QDateTime *> m_takenTrackDateTimes;
|
||||
QList<bool> m_takenTrackPositionExtrapolated;
|
||||
QList<bool> m_takenTrackAltitudeExtrapolated;
|
||||
QVariantList m_takenTrack; // Line showing where the object has been
|
||||
QVariantList m_takenTrack1;
|
||||
QVariantList m_takenTrack2;
|
||||
@ -114,6 +124,8 @@ protected:
|
||||
QGeoCoordinate m_takenStart2;
|
||||
QGeoCoordinate m_takenEnd1;
|
||||
QGeoCoordinate m_takenEnd2;
|
||||
QList<QGeoCoordinate *> m_interpolatedCoords;
|
||||
QList<QDateTime *> m_interpolatedDateTimes;
|
||||
|
||||
// For 3D map
|
||||
QString m_model;
|
||||
@ -121,6 +133,9 @@ protected:
|
||||
float m_labelAltitudeOffset;
|
||||
float m_modelAltitudeOffset;
|
||||
QList<CesiumInterface::Animation *> m_animations;
|
||||
MapAircraftState *m_aircraftState;
|
||||
|
||||
static WhittakerEilers m_filter; // For smoothing/interpolating position
|
||||
};
|
||||
|
||||
class PolygonMapItem : public MapItem {
|
||||
|
@ -269,7 +269,6 @@ QByteArray MapSettings::serialize() const
|
||||
s.writeBool(29, m_sunLightEnabled);
|
||||
s.writeBool(30, m_eciCamera);
|
||||
s.writeString(31, m_cesiumIonAPIKey);
|
||||
s.writeString(32, m_antiAliasing);
|
||||
s.writeS32(33, m_workspaceIndex);
|
||||
s.writeBlob(34, m_geometryBytes);
|
||||
|
||||
@ -292,6 +291,9 @@ QByteArray MapSettings::serialize() const
|
||||
s.writeBool(48, m_displayMaidenheadGrid);
|
||||
s.writeString(49, m_defaultImagery);
|
||||
s.writeString(50, m_arcGISAPIKey);
|
||||
s.writeBool(51, m_displayPFD);
|
||||
s.writeBool(52, m_viewFirstPerson);
|
||||
|
||||
s.writeBool(53, m_terrainLighting);
|
||||
s.writeBool(54, m_water);
|
||||
s.writeBool(55, m_hdr);
|
||||
@ -299,6 +301,7 @@ QByteArray MapSettings::serialize() const
|
||||
s.writeBool(57, m_fps);
|
||||
s.writeBool(58, m_fxaa);
|
||||
s.writeS32(59, m_msaa);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
@ -369,7 +372,6 @@ bool MapSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(29, &m_sunLightEnabled, true);
|
||||
d.readBool(30, &m_eciCamera, false);
|
||||
d.readString(31, &m_cesiumIonAPIKey, "");
|
||||
d.readString(32, &m_antiAliasing, "None");
|
||||
d.readS32(33, &m_workspaceIndex, 0);
|
||||
d.readBlob(34, &m_geometryBytes);
|
||||
|
||||
@ -391,6 +393,9 @@ bool MapSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(48, &m_displayMaidenheadGrid, false);
|
||||
d.readString(49, &m_defaultImagery, "Sentinel-2");
|
||||
d.readString(50, &m_arcGISAPIKey, "");
|
||||
d.readBool(51, &m_displayPFD, false);
|
||||
d.readBool(52, &m_viewFirstPerson, false);
|
||||
|
||||
d.readBool(53, &m_terrainLighting, true);
|
||||
d.readBool(54, &m_water, false);
|
||||
d.readBool(55, &m_hdr, true);
|
||||
@ -398,6 +403,7 @@ bool MapSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(57, &m_fps, false);
|
||||
d.readBool(58, &m_fxaa, false);
|
||||
d.readS32(59, &m_msaa, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -451,6 +457,8 @@ void MapSettings::MapItemSettings::resetToDefaults()
|
||||
m_filterName = "";
|
||||
m_filterDistance = 0;
|
||||
m_extrapolate = 60;
|
||||
m_smoothingWindow = 0;
|
||||
m_smoothingLambda = 100;
|
||||
}
|
||||
|
||||
QByteArray MapSettings::MapItemSettings::serialize() const
|
||||
@ -475,6 +483,8 @@ QByteArray MapSettings::MapItemSettings::serialize() const
|
||||
s.writeString(16, m_filterName);
|
||||
s.writeS32(17, m_filterDistance);
|
||||
s.writeS32(18, m_extrapolate);
|
||||
s.writeS32(19, m_smoothingWindow);
|
||||
s.writeFloat(20, m_smoothingLambda);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -511,6 +521,16 @@ bool MapSettings::MapItemSettings::deserialize(const QByteArray& data)
|
||||
d.readS32(18, &m_extrapolate, 60);
|
||||
m_filterNameRE.setPattern(m_filterName);
|
||||
m_filterNameRE.optimize();
|
||||
if (m_group == "ADSBDemod")
|
||||
{
|
||||
d.readS32(19, &m_smoothingWindow, 10);
|
||||
d.readFloat(20, &m_smoothingLambda, 100);
|
||||
}
|
||||
else
|
||||
{
|
||||
d.readS32(19, &m_smoothingWindow, 0);
|
||||
d.readFloat(20, &m_smoothingLambda, 100);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -703,6 +723,12 @@ void MapSettings::applySettings(const QStringList& settingsKeys, const MapSettin
|
||||
if (settingsKeys.contains("arcGISAPIKey")) {
|
||||
m_arcGISAPIKey = settings.m_arcGISAPIKey;
|
||||
}
|
||||
if (settingsKeys.contains("displayPFD")) {
|
||||
m_displayPFD = settings.m_displayPFD;
|
||||
}
|
||||
if (settingsKeys.contains("viewFirstPerson")) {
|
||||
m_viewFirstPerson = settings.m_viewFirstPerson;
|
||||
}
|
||||
if (settingsKeys.contains("workspaceIndex")) {
|
||||
m_workspaceIndex = settings.m_workspaceIndex;
|
||||
}
|
||||
@ -847,6 +873,12 @@ QString MapSettings::getDebugString(const QStringList& settingsKeys, bool force)
|
||||
if (settingsKeys.contains("arcGISAPIKey") || force) {
|
||||
ostr << " m_arcGISAPIKey: " << m_arcGISAPIKey.toStdString();
|
||||
}
|
||||
if (settingsKeys.contains("displayPFD") || force) {
|
||||
ostr << " m_displayPFD: " << m_displayPFD;
|
||||
}
|
||||
if (settingsKeys.contains("viewFirstPerson") || force) {
|
||||
ostr << " m_viewFirstPerson: " << m_viewFirstPerson;
|
||||
}
|
||||
if (settingsKeys.contains("workspaceIndex") || force) {
|
||||
ostr << " m_workspaceIndex: " << m_workspaceIndex;
|
||||
}
|
||||
|
@ -52,6 +52,8 @@ struct MapSettings
|
||||
QRegularExpression m_filterNameRE;
|
||||
int m_filterDistance; // Filter items > this distance in metres away from My Position. <= 0 don't filter
|
||||
int m_extrapolate; // Extrapolate duration in seconds on 3D map
|
||||
int m_smoothingWindow; // Window size (numer of points) to smooth over. 0 for no smoothing
|
||||
float m_smoothingLambda; // Lambda parameter for WhittakerEilers, controls how much smoothing to apply
|
||||
|
||||
MapItemSettings(const QString& group, bool enabled, const QColor color, bool display2DTrack=true, bool display3DPoint=true, int minZoom=11, int modelMinPixelSize=0);
|
||||
MapItemSettings(const QByteArray& data);
|
||||
@ -120,6 +122,8 @@ struct MapSettings
|
||||
bool m_displayAurora;
|
||||
bool m_displayMagDec;
|
||||
bool m_displayMaidenheadGrid;
|
||||
bool m_displayPFD;
|
||||
bool m_viewFirstPerson;
|
||||
QString m_defaultImagery;
|
||||
|
||||
QString m_checkWXAPIKey; //!< checkwxapi.com API key
|
||||
|
@ -53,10 +53,20 @@ MapItemSettingsGUI::MapItemSettingsGUI(QTableWidget *table, int row, MapSettings
|
||||
m_filterDistance->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
m_filterDistance->setSpecialValueText(" ");
|
||||
m_filterDistance->setCorrectionMode(QAbstractSpinBox::CorrectToNearestValue);
|
||||
m_smoothingWindow = new QSpinBox(table);
|
||||
m_smoothingWindow->setRange(0, 1000);
|
||||
m_smoothingWindow->setValue(settings->m_smoothingWindow);
|
||||
m_smoothingWindow->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
m_smoothingLambda = new QDoubleSpinBox(table);
|
||||
m_smoothingLambda->setRange(0, 1e9);
|
||||
m_smoothingLambda->setValue(settings->m_smoothingLambda);
|
||||
m_smoothingLambda->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_2D_MIN_ZOOM, m_minZoom);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_3D_MIN_PIXELS, m_minPixels);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_3D_LABEL_SCALE, m_labelScale);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_FILTER_DISTANCE, m_filterDistance);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_SMOOTHING_WINDOW, m_smoothingWindow);
|
||||
table->setCellWidget(row, MapSettingsDialog::COL_SMOOTHING_LAMBDA, m_smoothingLambda);
|
||||
}
|
||||
|
||||
MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
|
||||
@ -142,6 +152,12 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
|
||||
item = new QTableWidgetItem(itemSettings->m_filterName);
|
||||
ui->mapItemSettings->setItem(row, COL_FILTER_NAME, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
ui->mapItemSettings->setItem(row, COL_SMOOTHING_WINDOW, item);
|
||||
|
||||
item = new QTableWidgetItem();
|
||||
ui->mapItemSettings->setItem(row, COL_SMOOTHING_LAMBDA, item);
|
||||
|
||||
MapItemSettingsGUI *gui = new MapItemSettingsGUI(ui->mapItemSettings, row, itemSettings);
|
||||
m_mapItemSettingsGUIs.append(gui);
|
||||
}
|
||||
@ -170,6 +186,8 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
|
||||
ui->mapItemSettings->hideColumn(COL_3D_POINT);
|
||||
ui->mapItemSettings->hideColumn(COL_3D_TRACK);
|
||||
ui->mapItemSettings->hideColumn(COL_3D_LABEL_SCALE);
|
||||
ui->mapItemSettings->hideColumn(COL_SMOOTHING_WINDOW);
|
||||
ui->mapItemSettings->hideColumn(COL_SMOOTHING_LAMBDA);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -203,7 +221,6 @@ void MapSettingsDialog::accept()
|
||||
m_settings->m_mapBoxAPIKey = mapBoxAPIKey;
|
||||
m_settings->m_osmURL = osmURL;
|
||||
m_settings->m_mapBoxStyles = mapBoxStyles;
|
||||
m_settings->m_cesiumIonAPIKey = cesiumIonAPIKey;
|
||||
m_map2DSettingsChanged = true;
|
||||
}
|
||||
else
|
||||
@ -223,7 +240,6 @@ void MapSettingsDialog::accept()
|
||||
{
|
||||
m_map3DSettingsChanged = false;
|
||||
}
|
||||
|
||||
if (m_settings->m_map2DEnabled != ui->map2DEnabled->isChecked())
|
||||
{
|
||||
m_settings->m_map2DEnabled = ui->map2DEnabled->isChecked();
|
||||
@ -323,6 +339,8 @@ void MapSettingsDialog::accept()
|
||||
itemSettings->m_filterNameRE.setPattern(itemSettings->m_filterName);
|
||||
itemSettings->m_filterNameRE.optimize();
|
||||
itemSettings->m_filterDistance = gui->m_filterDistance->value() * 1000;
|
||||
itemSettings->m_smoothingWindow = gui->m_smoothingWindow->value();
|
||||
itemSettings->m_smoothingLambda = gui->m_smoothingLambda->value();
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
@ -359,6 +377,8 @@ void MapSettingsDialog::on_map3DEnabled_clicked(bool checked)
|
||||
ui->mapItemSettings->showColumn(COL_3D_POINT);
|
||||
ui->mapItemSettings->showColumn(COL_3D_TRACK);
|
||||
ui->mapItemSettings->showColumn(COL_3D_LABEL_SCALE);
|
||||
ui->mapItemSettings->showColumn(COL_SMOOTHING_WINDOW);
|
||||
ui->mapItemSettings->showColumn(COL_SMOOTHING_LAMBDA);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -368,6 +388,8 @@ void MapSettingsDialog::on_map3DEnabled_clicked(bool checked)
|
||||
ui->mapItemSettings->hideColumn(COL_3D_POINT);
|
||||
ui->mapItemSettings->hideColumn(COL_3D_TRACK);
|
||||
ui->mapItemSettings->hideColumn(COL_3D_LABEL_SCALE);
|
||||
ui->mapItemSettings->hideColumn(COL_SMOOTHING_WINDOW);
|
||||
ui->mapItemSettings->hideColumn(COL_SMOOTHING_LAMBDA);
|
||||
}
|
||||
ui->terrain->setEnabled(checked);
|
||||
ui->buildings->setEnabled(checked);
|
||||
|
@ -48,6 +48,8 @@ public:
|
||||
QSpinBox *m_minPixels;
|
||||
QDoubleSpinBox *m_labelScale;
|
||||
QSpinBox *m_filterDistance;
|
||||
QSpinBox *m_smoothingWindow;
|
||||
QDoubleSpinBox *m_smoothingLambda;
|
||||
};
|
||||
|
||||
class MapSettingsDialog : public QDialog {
|
||||
@ -70,7 +72,9 @@ public:
|
||||
COL_3D_TRACK,
|
||||
COL_3D_LABEL_SCALE,
|
||||
COL_FILTER_NAME,
|
||||
COL_FILTER_DISTANCE
|
||||
COL_FILTER_DISTANCE,
|
||||
COL_SMOOTHING_WINDOW,
|
||||
COL_SMOOTHING_LAMBDA
|
||||
};
|
||||
|
||||
public:
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1267</width>
|
||||
<height>648</height>
|
||||
<height>775</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -153,13 +153,52 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="defaultImageryLabel">
|
||||
<property name="text">
|
||||
<string>Default imagery</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="defaultImagery">
|
||||
<property name="toolTip">
|
||||
<string>Default imagery (Note there is a quota on Bing Maps Ariel usage)</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>ArcGIS world imagery</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Bing Maps Aerial</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Sentinel-2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Earth at night</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ersi world ocean</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="terrainLabel">
|
||||
<property name="text">
|
||||
<string>Terrain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="terrain">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -183,14 +222,48 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="terrainLightingLabel">
|
||||
<property name="text">
|
||||
<string>Terrain lighting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="terrainLighting">
|
||||
<property name="toolTip">
|
||||
<string>Enable terrain lighting</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="waterLabel">
|
||||
<property name="text">
|
||||
<string>Water effects</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="water">
|
||||
<property name="toolTip">
|
||||
<string>Enable water effects such as waves</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="buildingsLabel">
|
||||
<property name="text">
|
||||
<string>Buildings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="buildings">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -204,14 +277,14 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="sunLightEnabledLabel">
|
||||
<property name="text">
|
||||
<string>Lighting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="sunLightEnabled">
|
||||
<property name="toolTip">
|
||||
<string>Whether lighting is from the Sun or Camera</string>
|
||||
@ -228,14 +301,71 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="lightIntensityLabel">
|
||||
<property name="text">
|
||||
<string>Camera light Intensity</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QDoubleSpinBox" name="lightIntensity">
|
||||
<property name="toolTip">
|
||||
<string>Intensity of camera light</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>3.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="hdrLabel">
|
||||
<property name="text">
|
||||
<string>HDR</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="hdr">
|
||||
<property name="toolTip">
|
||||
<string>High dynamic range</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="fogLabel">
|
||||
<property name="text">
|
||||
<string>Fog</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="fog">
|
||||
<property name="toolTip">
|
||||
<string>Enable fog effect</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="eciCameraLabel">
|
||||
<property name="text">
|
||||
<string>Camera reference frame</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QComboBox" name="eciCamera">
|
||||
<property name="toolTip">
|
||||
<string>Selects camera reference frame. For ECEF the camera rotates with the Earth. For ECI, the camera position is fixed relative to the stars and the Earth's rotation will be visible.</string>
|
||||
@ -252,28 +382,77 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="antiAliasingLabel">
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="fpsLabel">
|
||||
<property name="text">
|
||||
<string>Anti-aliasing</string>
|
||||
<string>Display FPS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="antiAliasing">
|
||||
<item row="14" column="1">
|
||||
<widget class="QCheckBox" name="fps">
|
||||
<property name="toolTip">
|
||||
<string>Set anti-aliasing to use. This can remove jagged pixels on the edge of 3D models.</string>
|
||||
<string>Display frames per second (FPS)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="msaaLabel">
|
||||
<property name="text">
|
||||
<string>MSAA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QComboBox" name="msaa">
|
||||
<property name="toolTip">
|
||||
<string>Multisample Anti-Aliasing</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
<string>Off</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>16</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="fxaaLabel">
|
||||
<property name="text">
|
||||
<string>FXAA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="fxaa">
|
||||
<property name="toolTip">
|
||||
<string>Fast Approximate Anti-aliasing</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -282,7 +461,7 @@
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -313,7 +492,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/controltower.png</normaloff>:/map/icons/controltower.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
@ -327,7 +506,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/vor.png</normaloff>:/map/icons/vor.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
@ -341,7 +520,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<iconset resource="mapicons.qrc">
|
||||
<normaloff>:/map/icons/waypoints.png</normaloff>:/map/icons/waypoints.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
@ -349,7 +528,7 @@
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@ -371,7 +550,7 @@
|
||||
<item>
|
||||
<widget class="QTableWidget" name="mapItemSettings">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
<enum>QAbstractItemView::SelectionMode::NoSelection</enum>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
@ -468,6 +647,22 @@
|
||||
<string>Filter objects further than this distance in km away from My Position</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Smoothing Window</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>How many coordinates to apply smoothing filter to. Set to 0 for no smoothing.</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Smoothing Lambda</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Smoothing parameter. Higher values result in more smoothing.</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -563,20 +758,34 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="checkWXAPIKeyLabel">
|
||||
<property name="text">
|
||||
<string>CheckWX API key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="checkWXAPIKey">
|
||||
<property name="toolTip">
|
||||
<string>checkwxapi.com API key for accessing airport weather (METARs)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="arcGISAPIKeyLabel">
|
||||
<property name="text">
|
||||
<string>ArcGIS API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="arcGISAPIKey">
|
||||
<property name="toolTip">
|
||||
<string>Enter an ArcGIS API Key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -590,10 +799,10 @@
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -609,7 +818,7 @@
|
||||
<tabstop>buildings</tabstop>
|
||||
<tabstop>sunLightEnabled</tabstop>
|
||||
<tabstop>eciCamera</tabstop>
|
||||
<tabstop>antiAliasing</tabstop>
|
||||
<tabstop>msaa</tabstop>
|
||||
<tabstop>downloadModels</tabstop>
|
||||
<tabstop>thunderforestAPIKey</tabstop>
|
||||
<tabstop>maptilerAPIKey</tabstop>
|
||||
@ -617,7 +826,7 @@
|
||||
<tabstop>cesiumIonAPIKey</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="icons.qrc"/>
|
||||
<include location="mapicons.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
|
Loading…
x
Reference in New Issue
Block a user