UE5.2、CesiumForUnreal实现加载GeoJson绘制单面

文章目录

  • 前言
  • 一、实现目标
  • 二、实现过程
    • 1.实现原理
    • 2.数据读取
    • 3.三角剖分
    • 3.具体代码
  • 4.蓝图测试


前言

UE5、CesiumForUnreal实现加载GeoJson绘制单面(Polygon)功能(StaticMesh方式)


一、实现目标

通过读取本地的Geojson数据,在UE中以staticMeshComponent的形式绘制出面数据,支持Editor和Runtime环境,如下图

singlePolygon

二、实现过程

1.实现原理

首先读取Geojson数据,然后进行三角剖分,最后根据顶点和索引创建StaticMesh。

2.数据读取

本文使用的是polygon转linestring格式的Geojson线状数据文件,特别需要注意,转完的数据需要去掉coordinates字段里的一组中括号。在UE中直接读取文本对其进行解析,生成坐标数组。本文数据只考虑一个feature情况,数据坐标格式为wgs84经纬度坐标。

例:{
“type”: “FeatureCollection”,
“name”: “singlePolygon”,
“crs”: { “type”: “name”, “properties”: { “name”: “urn:ogc:def:crs:OGC:1.3:CRS84” } },
“features”: [
{ “type”: “Feature”, “properties”: { “id”: 1 }, “geometry”: { “type”: “MultiLineString”, “coordinates”: [ [ 107.5955545517036, 34.322768426465544 ], [ 108.086216375377106, 34.660927250889173 ], [ 109.133845674571887, 34.448749164976306 ], [ 109.518418455288952, 33.261877996901205 ], [ 109.067540022724117, 32.552407522130054 ], [ 107.734796420583919, 32.738063347303815 ], [ 106.726950512497794, 32.930349737662347 ], [ 106.786625599160786, 33.792323211683374 ], [ 107.025325945812767, 33.938195645748472 ], [ 107.608815682073157, 34.322768426465544 ], [ 107.608815682073157, 34.322768426465544 ],[ 107.5955545517036, 34.322768426465544 ] ] } }
]
}

3.三角剖分

ue不支持直接绘制面,因此需要将面进行三角剖分。为快速的对地理控件点位进行三角剖分,直接使用Mapbox的earcut.hpp耳切算法三角剖分库。
地址:传送门
在这里插入图片描述
将其放到工程中的Source下的某个目录中,本文是放到了Developer文件夹中
在这里插入图片描述

Build.cs配置


using UnrealBuildTool;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

public class cesiumGeoJson : ModuleRules
{
	public cesiumGeoJson(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

		//PublicIncludePaths.AddRange(new string[] { Path.Combine(ModuleDirectory, "./Source/Engine/Developer") });
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" ,"CesiumRuntime","Json"});

		PrivateDependencyModuleNames.AddRange(new string[] {  });

		// Uncomment if you are using Slate UI
		// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
		
		// Uncomment if you are using online features
		// PrivateDependencyModuleNames.Add("OnlineSubsystem");

		// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
	}
}

3.具体代码

  1. AsinglePolygon_Geojson.h
// Copyright 2020-2021 CesiumGS, Inc. and Contributors

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CesiumGeoreference.h"
#include "singlePolygon_Geojson.generated.h"

UCLASS()
class CESIUMGEOJSON_API AsinglePolygon_Geojson : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AsinglePolygon_Geojson();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;


	// Current world CesiumGeoreference.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Entity | Polygon")
		ACesiumGeoreference* Georeference;

	// The selected feature index, current is only for '0', just for demo.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Entity | Polygon");
	int SelectedFeatureIndex = 0;

	// The data path, that is the relative path of ue game content.
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Entity | Polygon");
	FString GeoJsonPath;

	/**
	 * @breif Test generate polygon.
	 */
	UFUNCTION(CallInEditor, Category = "Entity | Polygon")
		void TestGeneratePolygon();

	/**
	 * @brief Get feature vertices from linestring geojson.
	 */
	void GetCoordinatesFromLineStringGeoJson(const FString& FilePath, int FeatureIndex, TArray<FVector>& Positions);

	/**
	 * @brief Build static polygon mesh component from current data.
	 */
	void BuildPolygonStaticMesh();

private:
	// Verices that crs is unreal world.
	TArray<FVector> GeometryUE;

	// Vertices that crs is geographic wgs 84, epsg 4326.
	TArray<FVector> GeometryGeo;

	// Indices of vertices.
	TArray<uint32> Indices;

};

  1. AsinglePolygon_Geojson.cpp
// Copyright 2020-2021 CesiumGS, Inc. and Contributors


#include "singlePolygon_Geojson.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Engine/Developer/earcut.hpp"
#include "array"

// Sets default values
AsinglePolygon_Geojson::AsinglePolygon_Geojson()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

}

// Called when the game starts or when spawned
void AsinglePolygon_Geojson::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void AsinglePolygon_Geojson::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AsinglePolygon_Geojson::TestGeneratePolygon()
{
	// Check file path and georeference is exist.
	if (!Georeference || GeoJsonPath.IsEmpty())
	{
		UE_LOG(LogTemp, Warning, TEXT("CesiumGeoreference or GeoJsonPath is valid, please check!"));
		return;
	}

	// Get the full path of file;
	FString FilePath = UKismetSystemLibrary::GetProjectDirectory() + GeoJsonPath;

	GetCoordinatesFromLineStringGeoJson(FilePath, 0, GeometryGeo);

	// First and last is the same point.
	GeometryGeo.Pop();

	// Triangulation
	std::vector<std::vector<std::array<double, 2>>> Polygon;
	std::vector<std::array<double, 2>> Points;
	for (FVector& Item : GeometryGeo)
	{
		std::array<double, 2> CurPoint = { Item.X, Item.Y };
		Points.push_back(CurPoint);

		// Convert coord from geo to ue.
		
		FVector PointUE = Georeference->TransformLongitudeLatitudeHeightPositionToUnreal(Item);
		GeometryUE.Push(PointUE);
	}

	// Current is just for simply polygon.
	Polygon.push_back(Points);
	std::vector<uint32_t> CalculateIndices = mapbox::earcut(Polygon);

	for (uint32_t Item : CalculateIndices)
	{
		Indices.Push(Item);
	}

	// Build static mesh.
	BuildPolygonStaticMesh();
}

void AsinglePolygon_Geojson::GetCoordinatesFromLineStringGeoJson(
	const FString& FilePath, int FeatureIndex, TArray<FVector>& Positions)
{
	// Check file exist.
	if (!FPaths::FileExists(FilePath)) {
		UE_LOG(LogTemp, Warning, TEXT("GeoJson file don't exist!"));
		return;
	}

	// Clear
	GeometryUE.Empty();
	GeometryGeo.Empty();
	Indices.Empty();

	FString FileString;
	FFileHelper::LoadFileToString(FileString, *FilePath);

	TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(FileString);
	TSharedPtr<FJsonObject> Root;

	// Check deserialize
	if (!FJsonSerializer::Deserialize(JsonReader, Root)) {
		return;
	}

	if (Root->HasField(TEXT("features"))) {
		TArray<TSharedPtr<FJsonValue>> Features = Root->GetArrayField(TEXT("features"));

		// Check feature exist
		if (Features.Num() < 1 || Features.Num() < (FeatureIndex + 1)) {
			return;
		}

		TSharedPtr<FJsonObject> Feature = Features[FeatureIndex]->AsObject();

		if (Feature->HasField(TEXT("geometry"))) {
			TSharedPtr<FJsonObject> Geometry = Feature->GetObjectField(TEXT("geometry"));

			if (Geometry->HasField(TEXT("coordinates"))) {
				TArray<TSharedPtr<FJsonValue>> Coordinates = Geometry->GetArrayField(TEXT("coordinates"));


				for (auto Item : Coordinates) {
					auto Coordinate = Item->AsArray();
					FVector Position;

					// Check coord array is 2 or 3.
					if (Coordinate.Num() == 2) {
						// If don't have z value, add target value for z.
						Position = FVector(Coordinate[0]->AsNumber(), Coordinate[1]->AsNumber(), 5000);
					}
					else if (Coordinate.Num() == 3)
					{
						Position = FVector(Coordinate[0]->AsNumber(), Coordinate[1]->AsNumber(), Coordinate[2]->AsNumber());
					}

					Positions.Emplace(Position);
				}
			}
		}
	}
}

void AsinglePolygon_Geojson::BuildPolygonStaticMesh()
{
	UStaticMeshComponent* pStaticMeshComponent = NewObject<UStaticMeshComponent>(this);
	pStaticMeshComponent->SetFlags(RF_Transient);
	pStaticMeshComponent->SetWorldLocationAndRotation(FVector(0, 0, 0), FRotator(0, 0, 0));
	UStaticMesh* pStaticMesh = NewObject<UStaticMesh>(pStaticMeshComponent);
	pStaticMesh->NeverStream = true;
	pStaticMeshComponent->SetStaticMesh(pStaticMesh);

	FStaticMeshRenderData* pRenderData = new FStaticMeshRenderData();
	pRenderData->AllocateLODResources(1);
	FStaticMeshLODResources& LODResourece = pRenderData->LODResources[0];
	TArray<FStaticMeshBuildVertex> StaticMeshBuildVertices;
	StaticMeshBuildVertices.SetNum(GeometryUE.Num());

	// Calculate bounds
	glm::dvec3 MinPosition{ std::numeric_limits<double>::max() };
	glm::dvec3 MaxPosition{ std::numeric_limits<double>::lowest() };

	// Vertices
	for (int i = 0; i < GeometryUE.Num(); i++)
	{
		FStaticMeshBuildVertex& Vertex = StaticMeshBuildVertices[i];
		Vertex.Position = FVector3f(GeometryUE[i]);
		Vertex.UVs[0] = FVector2f(0, 0);
		Vertex.TangentX = FVector3f(0, 1, 0);
		Vertex.TangentY = FVector3f(1, 0, 0);
		Vertex.TangentZ = FVector3f(0, 0, 1);

		// Calculate max and min position;
		MinPosition.x = glm::min<double>(MinPosition.x, GeometryUE[i].X);
		MinPosition.y = glm::min<double>(MinPosition.y, GeometryUE[i].Y);
		MinPosition.z = glm::min<double>(MinPosition.z, GeometryUE[i].Z);
		MaxPosition.x = glm::max<double>(MaxPosition.x, GeometryUE[i].X);
		MaxPosition.y = glm::max<double>(MaxPosition.y, GeometryUE[i].Y);
		MaxPosition.z = glm::max<double>(MaxPosition.z, GeometryUE[i].Z);
	}

	// Bounding box
	FBox BoundingBox(FVector3d(MinPosition.x, MinPosition.y, MinPosition.z), FVector3d(MaxPosition.x, MaxPosition.y, MaxPosition.z));
	BoundingBox.GetCenterAndExtents(pRenderData->Bounds.Origin, pRenderData->Bounds.BoxExtent);

	LODResourece.bHasColorVertexData = false;
	LODResourece.VertexBuffers.PositionVertexBuffer.Init(StaticMeshBuildVertices);
	LODResourece.VertexBuffers.StaticMeshVertexBuffer.Init(StaticMeshBuildVertices, 1);
	LODResourece.IndexBuffer.SetIndices(Indices, EIndexBufferStride::AutoDetect);

	LODResourece.bHasDepthOnlyIndices = false;
	LODResourece.bHasReversedIndices = false;
	LODResourece.bHasReversedDepthOnlyIndices = false;

	FStaticMeshSectionArray& Sections = LODResourece.Sections;
	FStaticMeshSection& Section = Sections.AddDefaulted_GetRef();
	Section.bEnableCollision = true;
	Section.bCastShadow = true;
	Section.NumTriangles = Indices.Num() / 3;
	Section.FirstIndex = 0;
	Section.MinVertexIndex = 0;
	Section.MaxVertexIndex = Indices.Num() - 1;

	// Add material
	UMaterialInterface* CurMaterial = LoadObject<UMaterialInterface>(nullptr, TEXT("Material'/Game/Martials/M_Polygon.M_Polygon'"));  //此处的材质需要手动在编辑器中创建,而后在c++代码中引用
	UMaterialInstanceDynamic* CurMaterialIns = UMaterialInstanceDynamic::Create(CurMaterial, nullptr);
	CurMaterialIns->AddToRoot();
	CurMaterialIns->TwoSided = true;
	FName CurMaterialSlotName = pStaticMesh->AddMaterial(CurMaterialIns);
	int32 CurMaterialIndex = pStaticMesh->GetMaterialIndex(CurMaterialSlotName);
	Section.MaterialIndex = CurMaterialIndex;

	// Todo:Build Collision

	pStaticMesh->SetRenderData(TUniquePtr<FStaticMeshRenderData>(pRenderData));
	pStaticMesh->InitResources();
	pStaticMesh->CalculateExtendedBounds();
	pRenderData->ScreenSize[0].Default = 1.0f;
	pStaticMesh->CreateBodySetup();
	pStaticMeshComponent->SetMobility(EComponentMobility::Movable);
	pStaticMeshComponent->SetupAttachment(this->RootComponent);
	pStaticMeshComponent->RegisterComponent();
}

代码中提到的材质,查看路径操作如下:
在这里插入图片描述

4.蓝图测试

  1. 基于c++类生成蓝图类,并放到世界场景中测试。
    在这里插入图片描述
  2. 在该细节面板中配置相关设置,主要是需要CesiumGeoference,用于WGS84和UE世界坐标的转换。已经Geojson数据的存放相对路径(相对于Game工程目录),不支持Geojson多feature。如下:
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/344010.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

6.go 库源码文件

目录 概述总结例子代码结构代码执行结果 结束 概述 库源码文件是不能被直接运行的源码文件&#xff0c;它仅用于存放程序实体&#xff0c;这些程序实体可以被其他代码使用&#xff08;只要遵从 Go 语言规范的话&#xff09; 那么程序实体是什么呢&#xff1f;在 Go 语言中&…

深度强化学习Task3:A2C、A3C算法

本篇博客是本人参加Datawhale组队学习第三次任务的笔记 【教程地址】 文章目录 Actor-Critic 算法提出的动机Q Actor-Critic 算法A2C 与 A3C 算法广义优势估计A3C实现建立Actor和Critic网络定义智能体定义环境训练利用JoyRL实现多进程 练习总结 Actor-Critic 算法提出的动机 蒙…

基于springboot+vue的网上租赁系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

浪花 - 查询队伍列表

一、接口设计 1. 请求参数&#xff1a;封装 TeamQuery package com.example.usercenter.model.dto;import com.example.usercenter.common.PageRequest; import lombok.Data;/*** author 乐小鑫* version 1.0* Date 2024-01-22-20:14*/ Data public class TeamQuery extends …

助力医疗数字化转型,贝锐x医百科技案例解析

在医疗数字化这个历史进程的大浪潮中&#xff0c;医药企业扮演着重要的角色&#xff0c;其重要程度恐怕仅次于医疗机构本身。同时&#xff0c;数字化转型对于医药企业的赋能作用也是十分明显的&#xff0c;尤其在营销端&#xff0c;一系列的数字化管理、数字化推广方案已经成为…

Windows下安装达梦8开发版数据库

达梦数据库属于国产主流数据库之一&#xff0c;本文记录WIndows下安装最新的达梦8数据库的过程。   达梦官网&#xff08;参考文献1&#xff09;下载开发版&#xff08;X86平台&#xff09;版安装包&#xff0c;如下图所示&#xff1a; 解压安装包后&#xff0c;其中包含ISO文…

Neo4j基本用法

Neo4j基本用法 找到电影的示例点开 跳到2&#xff0c;然后点击提供的示例代码&#xff0c;会自动复制粘贴到上方的控制台&#xff0c;然后点击执行按钮 当我们点击了执行以后&#xff0c;会得到如下的基本图模型 将指示器跳到3&#xff0c;然后点击第一个查询语句 同样地&…

虚拟机网络配置及Moba工具的使用

A、设置IP和网关 1、设置IP [roothadoop00 ~]# vi /etc/sysconfig/network-scripts/ifcfg-eth0 &#xff08;修改如下标红内容&#xff0c;没有的就添加&#xff09; DEVICEeth0 HWADDR08:00:27:BD:9D:B5 #不用改 TYPEEthernet UUID53e4e4b6-9724-43ab-9da7-68792e611031…

人类行为动作数据集大合集

最近收集了一大波关于人类行为动作的数据集&#xff0c;主要包括&#xff1a;动作识别、行为识别、活动预测、动作行为分类等数据集。废话不多说&#xff0c;接下来就给大家介绍这些数据集&#xff01;&#xff01; 1、用于自动视频编辑的视频Blooper数据集 用于自动视频编辑…

助力工业焊缝质量检测,YOLOv7【tiny/l/x】不同系列参数模型开发构建工业焊接场景下工件表面焊接缺陷检测识别分析系统

焊接是一个不陌生但是对于开发来说相对小众的场景&#xff0c;在工件表面焊接场景下常常有对工件表面缺陷智能自动化检测识别的需求&#xff0c;工业AI结合落地是一个比较有潜力的场景&#xff0c;在我们前面的博文开发实践中也有一些相关的实践&#xff0c;感兴趣的话可以自行…

python-分享篇-时钟

文章目录 代码效果 代码 Function:Python制作简易时钟 Author:Charles 微信公众号:TONOWimport turtle import datetime悬空移动 def move(distance):turtle.penup()turtle.forward(distance)turtle.pendown()创建表针turtle def createHand(name, length):turtle.reset()move(…

【开源】基于JAVA的班级考勤管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统基础支持模块2.2 班级学生教师支持模块2.3 考勤签到管理2.4 学生请假管理 三、系统设计3.1 功能设计3.1.1 系统基础支持模块3.1.2 班级学生教师档案模块3.1.3 考勤签到管理模块3.1.4 学生请假管理模块 3.2 数据库设…

C++PythonC# 三语言OpenCV从零开发(3):图像读取和显示

文章目录 相关链接前言Mat是什么读取图片CC#Python 灰度处理CCSharpPython 打印图像信息CCsharpPython 总结 相关链接 C&Python&Csharp in OpenCV 专栏 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程&#xff08;附带课程课件资料课件笔记&#xff09; …

C#winform上位机开发学习笔记11-串口助手接收数据用波形显示功能添加

1.功能描述 接收串口数据&#xff0c;并将收到的十六进制数据用坐标系的方式将数据波形展示出来 2.代码部分 步骤1&#xff1a;定义链表&#xff0c;用于数据保存 //数据结构-线性链表private List<byte> DataList new List<byte>(); 步骤2&#xff1a;定义波…

消息中间件之Kafka(一)

1.简介 高性能的消息中间件&#xff0c;在大数据的业务场景下性能比较好&#xff0c;kafka本身不维护消息位点&#xff0c;而是交由Consumer来维护&#xff0c;消息可以重复消费&#xff0c;并且内部使用了零拷贝技术&#xff0c;性能比较好 Broker持久化消息时采用了MMAP的技…

springboot集成tess4j

spring整合tess4j用于OCR识别图片&#xff0c;在windows环境识别正常&#xff0c;在liunx没有反应&#xff0c;本文用于解决部署linux问题。 整合springboot 1、引入pom文件 <dependency><groupId>net.sourceforge.tess4j</groupId><artifactId>tess…

Sentinel 新版本发布,提升配置灵活性以及可观测配套

作者&#xff1a;屿山 基本介绍 Sentinel 是阿里巴巴集团开源的&#xff0c;面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;承接了阿里巴巴近 15 年的双十一大促流量的核心场景&#xff0c;例如秒杀、冷启动、消息削峰填谷、集群流量控制、实时熔断下游不可用服…

​ElasticSearch

目录 简介 基本概念 倒排索引 FST 简介 ES是一个基于lucene构建的&#xff0c;分布式的&#xff0c;RESTful的开源全文搜索引擎。支持对各种类型的数据的索引&#xff1b;搜索速度快&#xff0c;可以提供实时的搜索服务&#xff1b;便于水平扩展&#xff0c;每秒可以处理 …

【PyTorch】使用PyTorch创建卷积神经网络并在CIFAR-10数据集上进行分类

前言 在深度学习的世界中&#xff0c;图像分类任务是一个经典的问题&#xff0c;它涉及到识别给定图像中的对象类别。CIFAR-10数据集是一个常用的基准数据集&#xff0c;包含了10个类别的60000张32x32彩色图像。在本博客中&#xff0c;我们将探讨如何使用PyTorch框架创建一个简…

app如何实现悬浮框滚动到那个模块定位到那。

如图&#xff1a; 使用uniapp内置方法 onPageScroll 获取到滚动了多少。 其实拿到屏幕滚动多少就很简单了&#xff0c;下面是思路。 tap栏切换效果代码就不贴了。直接贴如何到那个模块定位到哪&#xff0c;和点击定位到当前模块。 <view v-if"show" class&qu…