Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dms-editing' into globe-anchor-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
kring committed Aug 14, 2023
2 parents 9240ab8 + e8f6ca7 commit d0cb137
Show file tree
Hide file tree
Showing 7 changed files with 695 additions and 121 deletions.
293 changes: 293 additions & 0 deletions Source/CesiumEditor/Private/CesiumDegreesMinutesSecondsEditor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// Copyright 2020-2021 CesiumGS, Inc. and Contributors

#include "CesiumDegreesMinutesSecondsEditor.h"
#include "CesiumEditor.h"

#include "PropertyCustomizationHelpers.h"
#include "PropertyEditing.h"

#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Input/STextComboBox.h"
#include "Widgets/Text/STextBlock.h"

#include <glm/glm.hpp>

namespace {

/**
* @brief A structure describing cartographic coordinates in
* the DMS (Degree-Minute-Second) representation.
*/
struct DMS {

/**
* @brief The degrees.
*
* This is usually a value in [0,90] (for latitude) or
* in [0,180] (for longitude), although explicit
* clamping is not guaranteed.
*/
int32_t d;

/**
* @brief The minutes.
*
* This is a value in [0,60).
*/
int32_t m;

/**
* @brief The seconds.
*
* This is a value in [0,60).
*/
double s;

/**
* @brief Whether the coordinate is negative.
*
* When the coordinate is negative, it represents a latitude south
* of the equator, or a longitude west of the prime meridian.
*/
bool negative;
};

/**
* @brief Converts the given decimal degrees to a DMS representation.
*
* @param decimalDegrees The decimal degrees
* @return The DMS representation.
*/
DMS decimalDegreesToDms(double decimalDegrees) {
// Roughly based on
// https://en.wikiversity.org/wiki/Geographic_coordinate_conversion
// Section "Conversion_from_Decimal_Degree_to_DMS"
bool negative = decimalDegrees < 0;
double dd = negative ? -decimalDegrees : decimalDegrees;
double d = glm::floor(dd);
double min = (dd - d) * 60;
double m = glm::floor(min);
double sec = (min - m) * 60;
double s = sec;
if (s >= 60) {
m++;
s -= 60;
}
if (m == 60) {
d++;
m = 0;
}
return DMS{static_cast<int32_t>(d), static_cast<int32_t>(m), s, negative};
}

/**
* @brief Converts the given DMS into decimal degrees.
*
* @param dms The DMS
* @return The decimal degrees
*/
double dmsToDecimalDegrees(const DMS& dms) {
double dd = dms.d + (dms.m / 60.0) + (dms.s / 3600.0);
if (dms.negative) {
return -dd;
}
return dd;
}

} // namespace

CesiumDegreesMinutesSecondsEditor::CesiumDegreesMinutesSecondsEditor(
TSharedPtr<class IPropertyHandle> InputDecimalDegreesHandle,
bool InputIsLongitude) {
this->DecimalDegreesHandle = InputDecimalDegreesHandle;
this->IsLongitude = InputIsLongitude;
}

void CesiumDegreesMinutesSecondsEditor::PopulateRow(IDetailPropertyRow& Row) {

// The default editing component for the property:
// A SpinBox for the decimal degrees
DecimalDegreesSpinBox =
SNew(SSpinBox<double>)
.MinSliderValue(IsLongitude ? -180 : -90)
.MaxSliderValue(IsLongitude ? 180 : 90)
.OnValueChanged(
this,
&CesiumDegreesMinutesSecondsEditor::SetDecimalDegreesOnProperty)
.Value(
this,
&CesiumDegreesMinutesSecondsEditor::
GetDecimalDegreesFromProperty);

// Editing components for the DMS representation:
// Spin boxes for degrees, minutes and seconds
DegreesSpinBox =
SNew(SSpinBox<int32>)
.MinSliderValue(0)
.MaxSliderValue(IsLongitude ? 179 : 89)
.OnValueChanged(this, &CesiumDegreesMinutesSecondsEditor::SetDegrees)
.Value(this, &CesiumDegreesMinutesSecondsEditor::GetDegrees);

MinutesSpinBox =
SNew(SSpinBox<int32>)
.MinSliderValue(0)
.MaxSliderValue(59)
.OnValueChanged(this, &CesiumDegreesMinutesSecondsEditor::SetMinutes)
.Value(this, &CesiumDegreesMinutesSecondsEditor::GetMinutes);

SecondsSpinBox =
SNew(SSpinBox<double>)
.MinSliderValue(0)
.MaxSliderValue(59.999999)
.OnValueChanged(this, &CesiumDegreesMinutesSecondsEditor::SetSeconds)
.Value(this, &CesiumDegreesMinutesSecondsEditor::GetSeconds);

// The combo box for selecting "Eeast" or "West",
// or "North" or "South", respectively.
if (IsLongitude) {
PositiveIndicator = MakeShareable(new FString(TEXT("E")));
NegativeIndicator = MakeShareable(new FString(TEXT("W")));
} else {
PositiveIndicator = MakeShareable(new FString(TEXT("N")));
NegativeIndicator = MakeShareable(new FString(TEXT("S")));
}
SignComboBoxItems.Add(NegativeIndicator);
SignComboBoxItems.Emplace(PositiveIndicator);
SignComboBox = SNew(STextComboBox)
.OptionsSource(&SignComboBoxItems)
.OnSelectionChanged(
this,
&CesiumDegreesMinutesSecondsEditor::SignChanged);
SignComboBox->SetSelectedItem(
GetDecimalDegreesFromProperty() < 0 ? NegativeIndicator
: PositiveIndicator);

const float hPad = 3.0;
const float vPad = 2.0;
// clang-format off
Row.CustomWidget().NameContent()
[
DecimalDegreesHandle->CreatePropertyNameWidget()
]
.ValueContent().HAlign(EHorizontalAlignment::HAlign_Fill)
[
SNew( SVerticalBox )
+ SVerticalBox::Slot().Padding(0.0f, vPad)
[
DecimalDegreesSpinBox.ToSharedRef()
]
+ SVerticalBox::Slot().Padding(0.0f, vPad)
[
SNew( SHorizontalBox )
+ SHorizontalBox::Slot().FillWidth(1.0)
[
DegreesSpinBox.ToSharedRef()
]
+ SHorizontalBox::Slot().AutoWidth().Padding(hPad, 0.0f)
[
// The 'degrees' symbol
SNew(STextBlock).Text(FText::FromString(TEXT("\u00B0")))
]
+ SHorizontalBox::Slot().FillWidth(1.0)
[
MinutesSpinBox.ToSharedRef()
]
+ SHorizontalBox::Slot().AutoWidth().Padding(hPad, 0.0f)
[
// The 'minutes' symbol
SNew(STextBlock).Text(FText::FromString(TEXT("\u2032")))
]
+ SHorizontalBox::Slot().FillWidth(1.0)
[
SecondsSpinBox.ToSharedRef()
]
+ SHorizontalBox::Slot().AutoWidth().Padding(hPad, 0.0f)
[
// The 'seconds' symbol
SNew(STextBlock).Text(FText::FromString(TEXT("\u2033")))
]
+ SHorizontalBox::Slot().AutoWidth()
[
SignComboBox.ToSharedRef()
]
]
];
// clang-format on
}

double
CesiumDegreesMinutesSecondsEditor::GetDecimalDegreesFromProperty() const {
double decimalDegrees;
FPropertyAccess::Result AccessResult =
DecimalDegreesHandle->GetValue(decimalDegrees);
if (AccessResult == FPropertyAccess::Success) {
return decimalDegrees;
}
// This should never be the case, if the actual property
// was a double property, so this warning indicates a
// developer error:
UE_LOG(LogCesiumEditor, Error, TEXT("GetDecimalDegreesFromProperty FAILED"));
assert(false);
return decimalDegrees;
}

void CesiumDegreesMinutesSecondsEditor::SetDecimalDegreesOnProperty(
double NewValue) {
DecimalDegreesHandle->SetValue(NewValue);
SignComboBox->SetSelectedItem(
NewValue < 0 ? NegativeIndicator : PositiveIndicator);
}

int32 CesiumDegreesMinutesSecondsEditor::GetDegrees() const {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
return static_cast<int32>(dms.d);
}
void CesiumDegreesMinutesSecondsEditor::SetDegrees(int32 NewValue) {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
dms.d = NewValue;
double newDecimalDegreesValue = dmsToDecimalDegrees(dms);
SetDecimalDegreesOnProperty(newDecimalDegreesValue);
}

int32 CesiumDegreesMinutesSecondsEditor::GetMinutes() const {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
return static_cast<int32>(dms.m);
}
void CesiumDegreesMinutesSecondsEditor::SetMinutes(int32 NewValue) {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
dms.m = NewValue;
double newDecimalDegreesValue = dmsToDecimalDegrees(dms);
SetDecimalDegreesOnProperty(newDecimalDegreesValue);
}

double CesiumDegreesMinutesSecondsEditor::GetSeconds() const {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
return dms.s;
}
void CesiumDegreesMinutesSecondsEditor::SetSeconds(double NewValue) {
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
dms.s = NewValue;
double newDecimalDegreesValue = dmsToDecimalDegrees(dms);
SetDecimalDegreesOnProperty(newDecimalDegreesValue);
}

void CesiumDegreesMinutesSecondsEditor::SignChanged(
TSharedPtr<FString> StringItem,
ESelectInfo::Type SelectInfo) {

bool negative = false;
if (StringItem.IsValid()) {
negative = (StringItem == NegativeIndicator);
}
double decimalDegrees = GetDecimalDegreesFromProperty();
DMS dms = decimalDegreesToDms(decimalDegrees);
dms.negative = negative;
double newDecimalDegreesValue = dmsToDecimalDegrees(dms);
SetDecimalDegreesOnProperty(newDecimalDegreesValue);
}
80 changes: 80 additions & 0 deletions Source/CesiumEditor/Private/CesiumDegreesMinutesSecondsEditor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2020-2021 CesiumGS, Inc. and Contributors

#pragma once

#include "IDetailCustomization.h"
#include "IDetailPropertyRow.h"
#include "Types/SlateEnums.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Input/STextComboBox.h"

/**
* A class that allows configuring a Details View row that shows
* a latitude or longitude.
*
* Assuming that the property was given as a (double) property
* that contains the decimal degrees, the Details View row
* that is created with this class shows the value additionally
* in a DMS (Degree-Minutes-Seconds) view.
*
* See FCesiumGeoreferenceCustomization::CustomizeDetails for
* an example of how to use this class.
*/
class CesiumDegreesMinutesSecondsEditor
: public TSharedFromThis<CesiumDegreesMinutesSecondsEditor> {

public:
/**
* Creates a new instance.
*
* The given property handle must be a handle to a 'double'
* property!
*
* @param InputDecimalDegreesHandle The property hande for the
* decimal degrees property
* @param InputIsLongitude Whether the edited property is a
* longitude (as opposed to a latitude) property
*/
CesiumDegreesMinutesSecondsEditor(
TSharedPtr<class IPropertyHandle> InputDecimalDegreesHandle,
bool InputIsLongitude);

/**
* Populates the given Details View row with the default
* editor (a SSpinBox for the value), as well as the
* spin boxes and dropdowns for the DMS editing.
*
* @param Row The Details View row
*/
void PopulateRow(IDetailPropertyRow& Row);

private:
TSharedPtr<class IPropertyHandle> DecimalDegreesHandle;
bool IsLongitude;

TSharedPtr<SSpinBox<double>> DecimalDegreesSpinBox;
TSharedPtr<SSpinBox<int32>> DegreesSpinBox;
TSharedPtr<SSpinBox<int32>> MinutesSpinBox;
TSharedPtr<SSpinBox<double>> SecondsSpinBox;

TSharedPtr<FString> NegativeIndicator;
TSharedPtr<FString> PositiveIndicator;

TArray<TSharedPtr<FString>> SignComboBoxItems;
TSharedPtr<STextComboBox> SignComboBox;

double GetDecimalDegreesFromProperty() const;
void SetDecimalDegreesOnProperty(double NewValue);

int32 GetDegrees() const;
void SetDegrees(int32 NewValue);

int32 GetMinutes() const;
void SetMinutes(int32 NewValue);

double GetSeconds() const;
void SetSeconds(double NewValue);

void
SignChanged(TSharedPtr<FString> StringItem, ESelectInfo::Type SelectInfo);
};
Loading

0 comments on commit d0cb137

Please sign in to comment.