Skip to content

Commit

Permalink
feat: basic satellite tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
SharkEzz committed Dec 29, 2021
1 parent c8e3b1a commit 6f8369c
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 3 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SatTrack

Modern satellite tracking solution

## TODO

- [ ] Custom validation messages
2 changes: 1 addition & 1 deletion database/models/Satellite.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "gorm.io/gorm"

type TLE struct {
gorm.Model
CatNBR string
CatNBR int
Name string
Line1 string
Line2 string
Expand Down
6 changes: 6 additions & 0 deletions dto/Error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dto

type Error struct {
Status uint
Message string
}
19 changes: 19 additions & 0 deletions dto/Tracking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dto

import (
"time"

"github.com/SharkEzz/sgp4"
)

type TrackingRequest struct {
CatNBR int `validate:"required"`
Lat float64 `validate:"required,latitude"`
Lng float64 `validate:"required,longitude"`
Alt float64 `validate:"required"`
}

type TrackingResponse struct {
sgp4.Observation
GeneratedAt time.Time
}
51 changes: 51 additions & 0 deletions handlers/TrackingHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package handlers

import (
"net/http"
"time"

"github.com/SharkEzz/sattrack/dto"
"github.com/SharkEzz/sattrack/services"
"github.com/SharkEzz/sgp4"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)

func HandlePostTracking(c *fiber.Ctx, db *gorm.DB, validator *validator.Validate) error {
requestData := &dto.TrackingRequest{}
c.BodyParser(requestData)
err := validator.Struct(requestData)
if err != nil {
c.Response().SetStatusCode(http.StatusBadRequest)
return c.JSON(dto.Error{
Status: http.StatusBadRequest,
Message: err.Error(),
})
}

tle, err := services.GetTLEFromDatabase(requestData.CatNBR, db)
if err != nil {
c.Response().SetStatusCode(http.StatusNotFound)
return c.JSON(dto.Error{
Status: http.StatusNotFound,
Message: "Satellite not found",
})
}

sgp4, err := sgp4.NewSGP4(tle)
if err != nil {
c.Response().SetStatusCode(http.StatusInternalServerError)
return c.JSON(dto.Error{
Status: http.StatusInternalServerError,
Message: "Error while initializing SGP4, please try again later",
})
}

observation := sgp4.ObservationFromLocation(requestData.Lat, requestData.Lng, requestData.Alt)

return c.JSON(dto.TrackingResponse{
Observation: observation,
GeneratedAt: time.Now(),
})
}
20 changes: 18 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package main

import (
"flag"

"github.com/SharkEzz/sattrack/database"
"github.com/SharkEzz/sattrack/database/models"
"github.com/SharkEzz/sattrack/handlers"
"github.com/SharkEzz/sattrack/services"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)

var (
shouldUpdate = flag.Bool("update", false, "Use this flag to update all the TLE")
)

func main() {
flag.Parse()
db := database.Init("database/local.db")
db.AutoMigrate(&models.TLE{})

validator := validator.New()

if *shouldUpdate {
services.UpdateDatabase(db)
}

app := fiber.New()

app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("oui")
app.Post("/tracking", func(c *fiber.Ctx) error {
return handlers.HandlePostTracking(c, db, validator)
})

app.Listen(":8000")
Expand Down
76 changes: 76 additions & 0 deletions services/TLEService.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package services

import (
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/SharkEzz/sattrack/database/models"
"github.com/SharkEzz/sgp4"
"gorm.io/gorm"
)

// CelesTrack active satellites list
const activeListUrl string = "https://celestrak.com/NORAD/elements/active.txt"

// Fetch all active satellite from CelesTrack and update the satellites table
func UpdateDatabase(db *gorm.DB) error {
fmt.Println("Updating TLE database")
fmt.Println("Deleting record from `tles` table")
db.Exec("DELETE FROM tles")
fmt.Println("Querying new TLE list")
res, err := http.Get(activeListUrl)
if err != nil {
return err
}

content, _ := io.ReadAll(res.Body)
contentStr := strings.TrimSpace(string(content))
contentArr := strings.Split(string(contentStr), "\n")

if len(contentArr)%3 != 0 {
return errors.New("invalid TLE count")
}

fmt.Println("Inserting new records")
start := time.Now()
satellites := []models.TLE{}
for i := 0; i < len(contentArr); i += 3 {
tle, err := sgp4.NewTLE(contentArr[i][:24], contentArr[i+1][:69], contentArr[i+2][:69])
if err != nil {
// TODO: error handling
continue
}
satellites = append(satellites, models.TLE{
Name: tle.Name(),
CatNBR: tle.NoradNumber(),
Line1: tle.Line1(),
Line2: tle.Line2(),
})
}
db.Create(&satellites)

fmt.Printf("Inserted %v TLE in %s\n", len(contentArr)/3, time.Since(start))

return nil
}

// Get a TLE from the database by it's NORAD catalog number
func GetTLEFromDatabase(catNbr int, db *gorm.DB) (*sgp4.TLE, error) {
dbTLE := new(models.TLE)
db.Where("cat_nbr = ?", catNbr).First(&dbTLE)

if dbTLE == nil {
return nil, errors.New("satellite not found")
}

tle, err := sgp4.NewTLE(dbTLE.Name, dbTLE.Line1, dbTLE.Line2)
if err != nil {
return nil, err
}

return tle, nil
}

0 comments on commit 6f8369c

Please sign in to comment.