-
Notifications
You must be signed in to change notification settings - Fork 297
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/dms-editing' into globe-anchor-ui
- Loading branch information
Showing
7 changed files
with
695 additions
and
121 deletions.
There are no files selected for viewing
293 changes: 293 additions & 0 deletions
293
Source/CesiumEditor/Private/CesiumDegreesMinutesSecondsEditor.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
80
Source/CesiumEditor/Private/CesiumDegreesMinutesSecondsEditor.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; |
Oops, something went wrong.