First lib version
This commit is contained in:
commit
0683d721cb
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
python
|
||||
cmd
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git-enter-code.ru/moxitech/simulation-go
|
||||
|
||||
go 1.22.7
|
||||
|
||||
require github.com/dhconnelly/rtreego v1.0.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/dhconnelly/rtreego v1.0.0 h1:1+V1STGw+zwx7jpvH/fwbeC5w5gZfn+XinARU45oRek=
|
||||
github.com/dhconnelly/rtreego v1.0.0/go.mod h1:SDozu0Fjy17XH1svEXJgdYq8Tah6Zjfa/4Q33Z80+KM=
|
378
pkg/simulation.go
Normal file
378
pkg/simulation.go
Normal file
@ -0,0 +1,378 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
|
||||
vector "moxitech/drone/simulation-go/pkg/vector"
|
||||
"moxitech/drone/simulation-go/utils"
|
||||
|
||||
"github.com/dhconnelly/rtreego" // R-Tree для пространственного индекса
|
||||
)
|
||||
|
||||
type UAV struct {
|
||||
Path map[int][]float64
|
||||
Speed *vector.Vector3
|
||||
Freq float64 // Частота передачи
|
||||
Radius float64 // Радиус покрытия в километрах
|
||||
}
|
||||
|
||||
type CGS struct {
|
||||
Position *vector.Vector3
|
||||
Radius float64 // Радиус покрытия в километрах
|
||||
Frequency float64 // Частота передачи в МГц
|
||||
}
|
||||
|
||||
type Simulation struct {
|
||||
elevationDefault float64
|
||||
distanceDefault float64
|
||||
uavs map[int]*UAV
|
||||
cgs map[int]*CGS
|
||||
heights map[string]float64 // высоты в формате "lat,lon": высота
|
||||
idGen func() int
|
||||
timeline []int
|
||||
mutex sync.Mutex
|
||||
rtree *rtreego.Rtree
|
||||
results []SimulationResult
|
||||
}
|
||||
|
||||
type SimulationResult struct {
|
||||
Time int `json:"time"`
|
||||
UAVs map[int]UAVResult `json:"uavs"`
|
||||
CGSs map[int]CGSResult `json:"cgs"`
|
||||
}
|
||||
|
||||
type UAVResult struct {
|
||||
Position *vector.Vector3 `json:"position"`
|
||||
Freq float64 `json:"frequency"`
|
||||
Radius float64 `json:"radius"`
|
||||
DataRate float64 `json:"data_rate"`
|
||||
}
|
||||
|
||||
type CGSResult struct {
|
||||
Position *vector.Vector3 `json:"position"`
|
||||
Radius float64 `json:"radius"`
|
||||
Frequency float64 `json:"frequency"`
|
||||
}
|
||||
|
||||
func NewSimulation() *Simulation {
|
||||
return &Simulation{
|
||||
elevationDefault: 85.0 * (math.Pi / 180), // 15 degrees in radians
|
||||
distanceDefault: 1000.0, // km
|
||||
uavs: make(map[int]*UAV),
|
||||
cgs: make(map[int]*CGS),
|
||||
heights: make(map[string]float64),
|
||||
idGen: utils.GetIDGenerator(),
|
||||
rtree: rtreego.NewTree(2, 25, 50), // Инициализация R-Tree
|
||||
results: []SimulationResult{},
|
||||
}
|
||||
}
|
||||
|
||||
// Adding CGS (ground station) with coverage radius and frequency
|
||||
func (s *Simulation) AddCGS(pos []float64, radius float64, frequency float64) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
cgsID := s.idGen()
|
||||
cgsVector := new(vector.Vector3).SetOn(0, pos[0], pos[1])
|
||||
s.cgs[cgsID] = &CGS{
|
||||
Position: cgsVector,
|
||||
Radius: radius,
|
||||
Frequency: frequency,
|
||||
}
|
||||
s.rtree.Insert(cgsVector) // Добавление в R-Tree
|
||||
}
|
||||
|
||||
// Adding UAV with its path and coverage radius
|
||||
func (s *Simulation) AddUAV(path map[int][]float64, speed []float64, radius float64) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
uavID := s.idGen()
|
||||
speedVector := &vector.Vector3{X: speed[0], Y: speed[1], Z: speed[2]}
|
||||
freq := 5030 + rand.Float64()*(5090-5030) // Генерация частоты в диапазоне 5030-5090 МГц
|
||||
|
||||
s.uavs[uavID] = &UAV{
|
||||
Path: path,
|
||||
Speed: speedVector,
|
||||
Freq: freq,
|
||||
Radius: radius,
|
||||
}
|
||||
|
||||
for _, p := range path {
|
||||
pos := new(vector.Vector3).SetOn(p[0], p[1], p[2])
|
||||
s.rtree.Insert(pos) // Добавление позиций в R-Tree
|
||||
}
|
||||
}
|
||||
|
||||
// Removing UAV
|
||||
func (s *Simulation) RemoveUAV(id int) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
delete(s.uavs, id)
|
||||
}
|
||||
|
||||
func (s *Simulation) GetPosition(uav *UAV, time int) *vector.Vector3 {
|
||||
// Determine known time slices
|
||||
keys := make([]int, 0)
|
||||
for k := range uav.Path {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
if _, exists := uav.Path[time]; exists {
|
||||
return new(vector.Vector3).SetOn(uav.Path[time][0], uav.Path[time][1], uav.Path[time][2])
|
||||
}
|
||||
|
||||
// If out of known range, use speed to estimate position
|
||||
if time < keys[0] || time > keys[len(keys)-1] {
|
||||
initialTime := keys[len(keys)-1]
|
||||
initialPosition := &vector.Vector3{
|
||||
X: uav.Path[initialTime][1],
|
||||
Y: uav.Path[initialTime][2],
|
||||
Z: uav.Path[initialTime][0],
|
||||
}
|
||||
deltaTime := float64(time - initialTime)
|
||||
speedComponent := uav.Speed.Scale(deltaTime)
|
||||
return initialPosition.Add(speedComponent)
|
||||
}
|
||||
|
||||
// Otherwise interpolate
|
||||
keys = append(keys, time)
|
||||
keys = utils.Sorted(keys)
|
||||
idx := utils.IndexOf(keys, time)
|
||||
temp := new(vector.Vector3)
|
||||
temp.RerpVectors(
|
||||
&vector.Vector3{X: uav.Path[keys[idx-1]][1], Y: uav.Path[keys[idx-1]][2], Z: uav.Path[keys[idx-1]][0]},
|
||||
&vector.Vector3{X: uav.Path[keys[idx+1]][1], Y: uav.Path[keys[idx+1]][2], Z: uav.Path[keys[idx+1]][0]},
|
||||
float64(time-keys[idx-1])/float64(keys[idx+1]-keys[idx-1]),
|
||||
)
|
||||
return temp.Copy()
|
||||
}
|
||||
|
||||
// Setting timeline configuration
|
||||
func (s *Simulation) SetTimeline(conf []int) {
|
||||
s.timeline = make([]int, 0)
|
||||
for i := conf[0]; i < conf[1]; i += conf[2] {
|
||||
s.timeline = append(s.timeline, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculating Great Circle Distance (to account for Earth's curvature)
|
||||
func GreatCircleDistance(v1, v2 *vector.Vector3) float64 {
|
||||
lat1, lon1 := v1.Y*(math.Pi/180), v1.X*(math.Pi/180)
|
||||
lat2, lon2 := v2.Y*(math.Pi/180), v2.X*(math.Pi/180)
|
||||
dlat := lat2 - lat1
|
||||
dlon := lon2 - lon1
|
||||
a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(lat1)*math.Cos(lat2)*math.Sin(dlon/2)*math.Sin(dlon/2)
|
||||
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||||
R := 6371.0 // Радиус Земли в км
|
||||
return R * c
|
||||
}
|
||||
|
||||
// FreeSpacePathLoss Calculating Free Space Path Loss (FSPL)
|
||||
func FreeSpacePathLoss(distance float64, frequency float64) float64 {
|
||||
return 20*math.Log10(distance*1e3) + 20*math.Log10(frequency) - 20*math.Log10(3e8/frequency) + 32.44
|
||||
}
|
||||
|
||||
// Determining Line of Sight (LoS)
|
||||
// Function to represent Line of Sight (LoS) check considering terrain and Fresnel zones
|
||||
func (s *Simulation) HasLineOfSight(v1, v2 *vector.Vector3) bool {
|
||||
// Расчет расстояния и средней точки между v1 и v2
|
||||
distance := GreatCircleDistance(v1, v2)
|
||||
midpoint := &vector.Vector3{
|
||||
X: (v1.X + v2.X) / 2,
|
||||
Y: (v1.Y + v2.Y) / 2,
|
||||
Z: (v1.Z + v2.Z) / 2,
|
||||
}
|
||||
|
||||
// Проверка высоты на средней точке для оценки наличия препятствий
|
||||
key := fmt.Sprintf("%.6f,%.6f", midpoint.X, midpoint.Y)
|
||||
terrainHeight, exists := s.heights[key]
|
||||
if exists && midpoint.Z < terrainHeight {
|
||||
return false
|
||||
}
|
||||
|
||||
// Проверка первой зоны Френеля
|
||||
fresnelRadius := FresnelZoneRadius(distance, v1.Length(), v2.Length())
|
||||
if midpoint.Z < terrainHeight+fresnelRadius {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Calculate the radius of the first Fresnel zone
|
||||
func FresnelZoneRadius(distance, height1, height2 float64) float64 {
|
||||
wavelength := 3e8 / 5.06e9 // Средняя частота 5060 МГц
|
||||
return math.Sqrt((wavelength * distance) / (height1 + height2))
|
||||
}
|
||||
|
||||
// Calculating connectivity between UAVs and CGSs
|
||||
func (s *Simulation) CalculateConnectivity() {
|
||||
for _, uav := range s.uavs {
|
||||
for time := range s.timeline {
|
||||
uavPosition := s.GetPosition(uav, time)
|
||||
if uavPosition == nil {
|
||||
continue
|
||||
}
|
||||
for _, cgs := range s.cgs {
|
||||
distance := GreatCircleDistance(uavPosition, cgs.Position)
|
||||
if distance <= uav.Radius && distance <= cgs.Radius && s.HasLineOfSight(uavPosition, cgs.Position) {
|
||||
fspl := FreeSpacePathLoss(distance, uav.Freq*1e6) // Используем частоту UAV в МГц
|
||||
snr := 100.0 / (fspl + 1) // Примерный SNR для расчетов
|
||||
modulation := s.CalculateModulation(distance)
|
||||
dataRate := s.CalculateDataRate(modulation, snr)
|
||||
|
||||
fmt.Printf("\u0412ремя %d: UAV на позиции (%.2f, %.2f, %.2f) может подключиться к CGS на позиции (%.2f, %.2f, %.2f) с потерями %.2f дБ, модуляция: %s, частота CGS: %.2f МГц, скорость передачи: %.2f Mbps\n",
|
||||
time, uavPosition.X, uavPosition.Y, uavPosition.Z,
|
||||
cgs.Position.X, cgs.Position.Y, cgs.Position.Z, fspl, modulation, cgs.Frequency, dataRate)
|
||||
}
|
||||
}
|
||||
s.results = append(s.results, s.CollectResult(time))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate Modulation Scheme Based on Distance
|
||||
func (s *Simulation) CalculateModulation(distance float64) string {
|
||||
if distance < 500 {
|
||||
return "64-QAM"
|
||||
} else if distance < 1000 {
|
||||
return "16-QAM"
|
||||
} else if distance < 2000 {
|
||||
return "QPSK"
|
||||
}
|
||||
return "BPSK"
|
||||
}
|
||||
func (s *Simulation) CalculateDataRate(modulation string, snr float64) float64 {
|
||||
var spectralEfficiency float64
|
||||
switch modulation {
|
||||
case "64-QAM":
|
||||
spectralEfficiency = 6.0
|
||||
case "16-QAM":
|
||||
spectralEfficiency = 4.0
|
||||
case "QPSK":
|
||||
spectralEfficiency = 2.0
|
||||
case "BPSK":
|
||||
spectralEfficiency = 1.0
|
||||
default:
|
||||
spectralEfficiency = 0.5
|
||||
}
|
||||
|
||||
// Подсчет пропускной способности (в Mbps)
|
||||
bandwidth := 20.0 // Пропускная способность канала в МГц
|
||||
dataRate := bandwidth * spectralEfficiency * math.Log2(1+snr)
|
||||
return dataRate
|
||||
}
|
||||
|
||||
// CollectResult collects the current state of the simulation at a given time
|
||||
func (s *Simulation) CollectResult(time int) SimulationResult {
|
||||
uavsResult := make(map[int]UAVResult)
|
||||
for id, uav := range s.uavs {
|
||||
position := s.GetPosition(uav, time)
|
||||
|
||||
if position != nil {
|
||||
uavsResult[id] = UAVResult{
|
||||
Position: position,
|
||||
Freq: uav.Freq,
|
||||
Radius: uav.Radius,
|
||||
DataRate: s.CalculateDataRate(s.CalculateModulation(GreatCircleDistance(position, s.cgs[1].Position)),
|
||||
100.0/(FreeSpacePathLoss(GreatCircleDistance(position, s.cgs[1].Position), uav.Freq*1e6)+1)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cgsResult := make(map[int]CGSResult)
|
||||
for id, cgs := range s.cgs {
|
||||
cgsResult[id] = CGSResult{
|
||||
Position: cgs.Position,
|
||||
Radius: cgs.Radius,
|
||||
Frequency: cgs.Frequency,
|
||||
}
|
||||
}
|
||||
|
||||
return SimulationResult{
|
||||
Time: time,
|
||||
UAVs: uavsResult,
|
||||
CGSs: cgsResult,
|
||||
}
|
||||
}
|
||||
|
||||
// GetResultsJSON returns the results of the simulation as a JSON string
|
||||
func (s *Simulation) GetResultsJSON() (string, error) {
|
||||
jsonData, err := json.MarshalIndent(s.results, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// Test function to create example data and run the simulation
|
||||
func TestSimulation() []byte {
|
||||
sim := NewSimulation()
|
||||
|
||||
// Add CGSs (Ground Stations)
|
||||
sim.AddCGS([]float64{55.7558, 37.6176}, 100, 5060)
|
||||
sim.AddCGS([]float64{56.7558, 37.7176}, 80, 5080)
|
||||
sim.AddCGS([]float64{34.0522, -118.2437}, 120, 5035)
|
||||
|
||||
// Add UAVs (drones) with paths and speed
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1000, 55.7558, 37.6176},
|
||||
10: {1100, 56.0, 38.0},
|
||||
20: {2200, 256.5, 78.5},
|
||||
}, []float64{10, 0.1, 0.1}, 100)
|
||||
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1050, 55.7558, 37.6176},
|
||||
10: {1100, 56.0, 38.0},
|
||||
20: {1200, 56.5, 38.5},
|
||||
}, []float64{100, 0.1, 0.1}, 50)
|
||||
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1010, 55.7558, 37.6176},
|
||||
10: {1100, 56.0, 38.0},
|
||||
20: {1200, 56.5, 38.5},
|
||||
}, []float64{10, 0.1, 0.1}, 100)
|
||||
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1500, 40.7128, -74.0060},
|
||||
10: {1600, 41.0, -73.5},
|
||||
20: {1700, 41.5, -73.0},
|
||||
}, []float64{12, 0.15, 0.1}, 100)
|
||||
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1400, 34.0522, -118.2437},
|
||||
10: {1500, 34.5, -118.0},
|
||||
20: {1600, 35.0, -117.5},
|
||||
}, []float64{15, 0.2, 0.1}, 100)
|
||||
|
||||
sim.AddUAV(map[int][]float64{
|
||||
0: {1300, 48.8566, 2.3522},
|
||||
10: {1350, 49.0, 2.5},
|
||||
20: {1400, 49.5, 3.0},
|
||||
}, []float64{8, 0.1, 0.05}, 100)
|
||||
|
||||
// Set timeline configuration [start, end, step]
|
||||
sim.SetTimeline([]int{0, 50, 1})
|
||||
|
||||
// Run simulation and print results
|
||||
fmt.Println("Результаты симуляции:")
|
||||
for _, t := range sim.timeline {
|
||||
fmt.Printf("\nВремя %d секунд:\n", t)
|
||||
for uavID, uav := range sim.uavs {
|
||||
position := sim.GetPosition(uav, t)
|
||||
if position != nil {
|
||||
fmt.Printf("UAV %d на позиции (%.2f, %.2f, %.2f)\n", uavID, position.X, position.Y, position.Z)
|
||||
}
|
||||
}
|
||||
sim.CalculateConnectivity()
|
||||
}
|
||||
result, err := json.Marshal(sim.results)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
67
pkg/vector/vector.go
Normal file
67
pkg/vector/vector.go
Normal file
@ -0,0 +1,67 @@
|
||||
package vector
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/dhconnelly/rtreego"
|
||||
)
|
||||
|
||||
type Vector3 struct {
|
||||
X, Y, Z float64
|
||||
}
|
||||
|
||||
func (v *Vector3) SetOn(z, x, y float64) *Vector3 {
|
||||
v.Z = z
|
||||
v.X = x
|
||||
v.Y = y
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *Vector3) Copy() *Vector3 {
|
||||
return &Vector3{v.X, v.Y, v.Z}
|
||||
}
|
||||
|
||||
func (v *Vector3) Subtract(other *Vector3) *Vector3 {
|
||||
return &Vector3{
|
||||
X: v.X - other.X,
|
||||
Y: v.Y - other.Y,
|
||||
Z: v.Z - other.Z,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Vector3) Length() float64 {
|
||||
return math.Sqrt(v.X*v.X + v.Y*v.Y + v.Z*v.Z)
|
||||
}
|
||||
|
||||
func (v *Vector3) RerpVectors(v1, v2 *Vector3, ratio float64) {
|
||||
v.X = v1.X + ratio*(v2.X-v1.X)
|
||||
v.Y = v1.Y + ratio*(v2.Y-v1.Y)
|
||||
v.Z = v1.Z + ratio*(v2.Z-v1.Z)
|
||||
}
|
||||
|
||||
func (v *Vector3) AngleTo(other *Vector3) float64 {
|
||||
cosAlpha := (v.X*other.X + v.Y*other.Y + v.Z*other.Z) / (v.Length() * other.Length())
|
||||
return math.Acos(cosAlpha)
|
||||
}
|
||||
|
||||
// Implementing rtreego.Spatial interface for Vector3
|
||||
func (v *Vector3) Bounds() *rtreego.Rect {
|
||||
point := rtreego.Point{v.X, v.Y}
|
||||
rect, _ := rtreego.NewRect(point, []float64{0.001, 0.001})
|
||||
return rect
|
||||
}
|
||||
func (v *Vector3) Scale(factor float64) *Vector3 {
|
||||
return &Vector3{
|
||||
X: v.X * factor,
|
||||
Y: v.Y * factor,
|
||||
Z: v.Z * factor,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Vector3) Add(other *Vector3) *Vector3 {
|
||||
return &Vector3{
|
||||
X: v.X + other.X,
|
||||
Y: v.Y + other.Y,
|
||||
Z: v.Z + other.Z,
|
||||
}
|
||||
}
|
37
utils/helpers.go
Normal file
37
utils/helpers.go
Normal file
@ -0,0 +1,37 @@
|
||||
package utils
|
||||
|
||||
import "sort"
|
||||
|
||||
// Автоинкрементер ID
|
||||
func GetIDGenerator() func() int {
|
||||
id := 0
|
||||
return func() int {
|
||||
id++
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function сортирует срез массивов
|
||||
func Sorted(values []int) []int {
|
||||
sort.Ints(values)
|
||||
return values
|
||||
}
|
||||
|
||||
// Helper function ищет индекс по значению в слайсе
|
||||
func IndexOf(values []int, target int) int {
|
||||
for i, v := range values {
|
||||
if v == target {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func FindIndex(slice []int, predicate func(int) bool) int {
|
||||
for i, v := range slice {
|
||||
if predicate(v) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
Loading…
Reference in New Issue
Block a user