Skip to content

Blueprint support for Cesium ion Geocoder #1645

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions Source/CesiumRuntime/Private/CesiumGeocoderServiceBlueprintLibrary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include "CesiumGeocoderServiceBlueprintLibrary.h"
#include "CesiumIonServer.h"
#include "CesiumRuntime.h"

FCesiumGeocoderServiceAttribution::FCesiumGeocoderServiceAttribution(
const CesiumIonClient::GeocoderAttribution& attribution)
: Html(UTF8_TO_TCHAR(attribution.html.c_str())),
bShowOnScreen(attribution.showOnScreen) {}

FCesiumGeocoderServiceFeature::FCesiumGeocoderServiceFeature(
const CesiumIonClient::GeocoderFeature& feature) {
this->DisplayName = UTF8_TO_TCHAR(feature.displayName.c_str());

const CesiumGeospatial::GlobeRectangle rect = feature.getGlobeRectangle();
const CesiumGeospatial::Cartographic southWest = rect.getSouthwest();
const CesiumGeospatial::Cartographic northEast = rect.getNortheast();
this->GlobeRectangle = FBox(
FVector(
CesiumUtility::Math::radiansToDegrees(southWest.longitude),
CesiumUtility::Math::radiansToDegrees(southWest.latitude),
southWest.height),
FVector(
CesiumUtility::Math::radiansToDegrees(northEast.longitude),
CesiumUtility::Math::radiansToDegrees(northEast.latitude),
northEast.height));

const CesiumGeospatial::Cartographic cartographic = feature.getCartographic();
this->LongitudeLatitudeHeight = FVector(
CesiumUtility::Math::radiansToDegrees(cartographic.longitude),
CesiumUtility::Math::radiansToDegrees(cartographic.latitude),
cartographic.height);
}

UCesiumGeocoderServiceIonGeocoderAsyncAction*
UCesiumGeocoderServiceIonGeocoderAsyncAction::Geocode(
const FString& IonAccessToken,
const UCesiumIonServer* CesiumIonServer,
ECesiumIonGeocoderProviderType ProviderType,
ECesiumIonGeocoderRequestType RequestType,
const FString& Query) {
UCesiumIonServer* pServer = UCesiumIonServer::GetDefaultServer();

UCesiumGeocoderServiceIonGeocoderAsyncAction* pAction =
NewObject<UCesiumGeocoderServiceIonGeocoderAsyncAction>();
pAction->_cesiumIonServer =
IsValid(CesiumIonServer) ? CesiumIonServer : pServer;
pAction->_ionAccessToken =
IonAccessToken.IsEmpty()
? pAction->_cesiumIonServer->DefaultIonAccessToken
: IonAccessToken;
pAction->_providerType = ProviderType;
pAction->_requestType = RequestType;
pAction->_query = Query;
return pAction;
}

void UCesiumGeocoderServiceIonGeocoderAsyncAction::Activate() {
if (!IsValid(this->_cesiumIonServer)) {
this->OnGeocodeRequestComplete.Broadcast(
false,
nullptr,
TEXT("Could not find valid Cesium ion server object to use."));
return;
}
CesiumIonClient::Connection::appData(
getAsyncSystem(),
getAssetAccessor(),
TCHAR_TO_UTF8(*this->_cesiumIonServer->ApiUrl))
.thenInMainThread([this](CesiumIonClient::Response<
CesiumIonClient::ApplicationData>&& response) {
if (!response.value) {
this->OnGeocodeRequestComplete.Broadcast(
false,
nullptr,
FString::Printf(
TEXT("App data request failed, error code %s, message %s"),
UTF8_TO_TCHAR(response.errorCode.c_str()),
UTF8_TO_TCHAR(response.errorMessage.c_str())));
return;
}

CesiumIonClient::Connection connection(
getAsyncSystem(),
getAssetAccessor(),
TCHAR_TO_UTF8(*this->_ionAccessToken),
*response.value,
TCHAR_TO_UTF8(*this->_cesiumIonServer->ApiUrl));
connection
.geocode(
(CesiumIonClient::GeocoderProviderType)this->_providerType,
(CesiumIonClient::GeocoderRequestType)this->_requestType,
TCHAR_TO_UTF8(*this->_query))
.thenInMainThread([this](CesiumIonClient::Response<
CesiumIonClient::GeocoderResult>&&
response) {
if (!response.value) {
this->OnGeocodeRequestComplete.Broadcast(
false,
nullptr,
FString::Printf(
TEXT(
"Geocode request failed, error code %s, message %s"),
UTF8_TO_TCHAR(response.errorCode.c_str()),
UTF8_TO_TCHAR(response.errorMessage.c_str())));
return;
}

UCesiumGeocoderServiceResult* pResult =
NewObject<UCesiumGeocoderServiceResult>();

pResult->Attributions.Reserve(
response.value->attributions.size());
for (const CesiumIonClient::GeocoderAttribution& attr :
response.value->attributions) {
pResult->Attributions.Emplace(attr);
}

pResult->Features.Reserve(response.value->features.size());
for (CesiumIonClient::GeocoderFeature& feature :
response.value->features) {
pResult->Features.Emplace(feature);
}

this->OnGeocodeRequestComplete.Broadcast(
true,
pResult,
FString());
});
});
}
227 changes: 227 additions & 0 deletions Source/CesiumRuntime/Public/CesiumGeocoderServiceBlueprintLibrary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright 2020-2025 CesiumGS, Inc. and Contributors

#pragma once

#include "Cesium3DTileset.h"
#include "CesiumGeoreference.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Math/MathFwd.h"
#include "Misc/Optional.h"
#include "Templates/SharedPointer.h"
#include "UObject/Object.h"
#include "UObject/ObjectMacros.h"

THIRD_PARTY_INCLUDES_START
#include <CesiumIonClient/Connection.h>
THIRD_PARTY_INCLUDES_END

#include <CesiumGeospatial/Cartographic.h>
#include <CesiumGeospatial/GlobeRectangle.h>
#include <CesiumIonClient/Geocoder.h>
#include <CesiumUtility/Math.h>

#include <optional>

#include "CesiumGeocoderServiceBlueprintLibrary.generated.h"

/**
* @brief The supported providers that can be accessed through ion's geocoder
* API.
*/
UENUM(BlueprintType)
enum class ECesiumIonGeocoderProviderType : uint8 {
/**
* @brief Google geocoder, for use with Google data.
*/
Google = CesiumIonClient::GeocoderProviderType::Google,
/**
* @brief Bing geocoder, for use with Bing data.
*/
Bing = CesiumIonClient::GeocoderProviderType::Bing,
/**
* @brief Use the default geocoder as set on the server. Used when neither
* Bing or Google data is used.
*/
Default = CesiumIonClient::GeocoderProviderType::Default
};

/**
* @brief The supported types of requests to geocoding API.
*/
UENUM(BlueprintType)
enum class ECesiumIonGeocoderRequestType : uint8 {
/**
* @brief Perform a full search from a complete query.
*/
Search = CesiumIonClient::GeocoderRequestType::Search,
/**
* @brief Perform a quick search based on partial input, such as while a user
* is typing.
*
* The search results may be less accurate or exhaustive than using
* `ECesiumIonGeocoderRequestType::Search`.
*/
Autocomplete = CesiumIonClient::GeocoderRequestType::Autocomplete
};

/**
* @brief Attribution information for a query to a geocoder service.
*/
USTRUCT(BlueprintType)
struct FCesiumGeocoderServiceAttribution {
GENERATED_BODY()
public:
FCesiumGeocoderServiceAttribution() = default;
FCesiumGeocoderServiceAttribution(
const CesiumIonClient::GeocoderAttribution& attribution);

/**
* @brief An HTML string containing the necessary attribution information.
*/
UPROPERTY(BlueprintReadOnly, Category = "Cesium|Geocoder")
FString Html;

/**
* @brief If true, the credit should be visible in the main credit container.
* Otherwise, it can appear in a popover.
*/
UPROPERTY(BlueprintReadOnly, Category = "Cesium|Geocoder")
bool bShowOnScreen;
};

/**
* @brief A single feature (a location or region) obtained from a geocoder
* service.
*/
USTRUCT(BlueprintType)
struct FCesiumGeocoderServiceFeature {
GENERATED_BODY()
public:
FCesiumGeocoderServiceFeature() = default;
FCesiumGeocoderServiceFeature(
const CesiumIonClient::GeocoderFeature& feature);

/**
* @brief The position of the feature expressed as longitude in degrees (X),
* latitude in degrees (Y), and height in meters above the ellipsoid (Z).
*
* Do not confuse the ellipsoid height with a geoid height or height above
* mean sea level, which can be tens of meters higher or lower depending on
* where in the world the object is located.
*
* The height may be 0.0, indicating that the geocoder did not provide a
* height for the feature.
*
* If the geocoder service returned a bounding box for this result, this will
* return the center of the bounding box. If the geocoder service returned a
* coordinate for this result, this will return the coordinate.
*/
UPROPERTY(
BlueprintReadOnly,
Category = "Cesium|Geocoder",
meta = (AllowPrivateAccess))
FVector LongitudeLatitudeHeight;

/**
* @brief The globe rectangle that bounds the feature. The box's `Min.X` is
* the Westernmost longitude in degrees, `Min.Y` is the Southernmost latitude
* in degrees, `Max.X` is the Easternmost longitude in degrees, and `Max.Y` is
* the Northernmost latitude in degrees.
*
* If the geocoder service returned a bounding box for this result, this will
* return the bounding box. If the geocoder service returned a coordinate for
* this result, this will return a zero-width rectangle at that coordinate.
*/
UPROPERTY(
BlueprintReadOnly,
Category = "Cesium|Geocoder",
meta = (AllowPrivateAccess))
FBox GlobeRectangle;

/**
* @brief The user-friendly display name of this feature.
*/
UPROPERTY(
BlueprintReadOnly,
Category = "Cesium|Geocoder",
meta = (AllowPrivateAccess))
FString DisplayName;
};

/**
* @brief The result of making a request to a geocoder service.
*/
UCLASS(BlueprintType)
class UCesiumGeocoderServiceResult : public UObject {
GENERATED_BODY()
public:
UCesiumGeocoderServiceResult() = default;

/**
* @brief Any necessary attributions for this geocoder result.
*/
UPROPERTY(BlueprintReadOnly, Category = "Cesium|Geocoder")
TArray<FCesiumGeocoderServiceAttribution> Attributions;

/**
* @brief The features obtained from this geocoder service, if any.
*/
UPROPERTY(BlueprintReadOnly, Category = "Cesium|Geocoder")
TArray<FCesiumGeocoderServiceFeature> Features;
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
FCesiumGeocoderServiceDelegate,
bool,
Success,
UCesiumGeocoderServiceResult*,
Result,
FString,
Error);

UCLASS()
class CESIUMRUNTIME_API UCesiumGeocoderServiceIonGeocoderAsyncAction
: public UBlueprintAsyncActionBase {
GENERATED_BODY()

public:
/**
* @brief Queries the Cesium ion Geocoder service.
*
* @param IonAccessToken The access token to use for Cesium ion. This token
* must have the `geocode` scope.
* @param CesiumIonServer Information on the Cesium ion server to perform this
* request against.
* @param ProviderType The provider to obtain a geocoding result from.
* @param RequestType The type of geocoding request to make.
* @param Query The query string.
*/
UFUNCTION(
BlueprintCallable,
Category = "Cesium|Geocoder",
meta =
(BlueprintInternalUseOnly = true,
DisplayName = "Query Cesium ion Geocoder"))
static UCesiumGeocoderServiceIonGeocoderAsyncAction* Geocode(
const FString& IonAccessToken,
const UCesiumIonServer* CesiumIonServer,
ECesiumIonGeocoderProviderType ProviderType,
ECesiumIonGeocoderRequestType RequestType,
const FString& Query);

UPROPERTY(BlueprintAssignable)
FCesiumGeocoderServiceDelegate OnGeocodeRequestComplete;

virtual void Activate() override;

private:
FString _ionAccessToken;

UPROPERTY()
const UCesiumIonServer* _cesiumIonServer;

ECesiumIonGeocoderProviderType _providerType;
ECesiumIonGeocoderRequestType _requestType;
FString _query;
};
Loading