feat(refactor): moving configuration and api to proper packages

This commit is contained in:
2025-10-13 11:07:53 +02:00
parent be9f68c38c
commit fde01b4cef
2 changed files with 98 additions and 67 deletions

166
cmd/api/realz.go Normal file
View File

@@ -0,0 +1,166 @@
package api
import (
"database/sql"
"fmt"
"log"
"net/http"
"forge.cadoles.com/LaCanne/realZ/internal/config"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq" // Driver PostgreSQL
)
// GpsCoord représente les coordonnées GPS reçues en entrée.
// Les noms de champs commencent par une majuscule pour être exportables
// et donc accessibles par le décodeur JSON de Gin.
type GpsCoord struct {
Lat float64 `json:"lat" binding:"required"`
Lon float64 `json:"lon" binding:"required"`
SrcProj int `json:"srcProj" binding:"required"`
DstProj int `json:"dstProj" binding:"required"`
}
// GpsCoordWithZ représente les coordonnées GPS et l'altitude reçues pour le calcul de différence.
type GpsCoordWithZ struct {
Lat float64 `json:"lat" binding:"required"`
Lon float64 `json:"lon" binding:"required"`
Z float64 `json:"z" binding:"required"`
SrcProj int `json:"srcProj" binding:"required"`
DstProj int `json:"dstProj" binding:"required"`
}
// AltitudeResponse représente la réponse JSON retournée par l'API.
type AltitudeResponse struct {
Z float64 `json:"z"`
}
// CorrectedAltitudeResponse représente la réponse JSON pour l'altitude corrigée.
type CorrectedAltitudeResponse struct {
CorrectedZ float64 `json:"corrected_z"`
}
// queryAltitude interroge la base de données pour obtenir l'altitude réelle pour des coordonnées données.
// C'est une fonction utilitaire pour éviter la duplication de code.
func queryOrthometricCorrection(db *sql.DB, lat, lon float64, srcProj, dstProj int) (float64, error) {
var altitude float64
query := `
SELECT ST_Value(rast, ST_Transform(ST_SetSRID(ST_MakePoint($2, $1), $3::integer), $4::integer)) AS pixel_value
FROM raf20lamber93
WHERE ST_Intersects(rast, ST_Transform(ST_SetSRID(ST_MakePoint($2, $1), $3::integer), $4::integer));
`
// On exécute la requête. QueryRow est idéal car nous attendons au plus une ligne.
err := db.QueryRow(query, lat, lon, srcProj, dstProj).Scan(&altitude)
if err != nil {
// Si aucune ligne n'est trouvée, ST_Value renvoie NULL, ce qui cause une erreur au Scan.
// On retourne l'erreur pour qu'elle soit gérée par la fonction appelante.
return 0, err
}
return altitude, nil
}
// getRealZ est le handler pour notre route. Il prend une connexion à la BDD en paramètre.
func getOrthometricCorrection(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var input GpsCoord
// On valide et on lie le JSON d'entrée à notre struct GpsCoord.
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Paramètres invalides: " + err.Error()})
return
}
// On appelle notre fonction utilitaire pour obtenir l'altitude.
altitude, err := queryOrthometricCorrection(db, input.Lat, input.Lon, input.SrcProj, input.DstProj)
if err != nil {
// Si aucune ligne n'est trouvée, ST_Value renvoie NULL, ce qui cause une erreur au Scan.
// On peut considérer cette erreur comme un "non trouvé".
// Pour une gestion plus fine, il faudrait vérifier le type d'erreur exact.
log.Printf("Erreur de la base de données ou aucune valeur trouvée: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Aucune donnée d'altitude trouvée pour ces coordonnées."})
return
}
c.IndentedJSON(http.StatusOK, AltitudeResponse{Z: altitude})
}
}
// getCorrectedAltitude est le handler pour la nouvelle route.
// Il prend une altitude mesurée et retourne l'altitude corrigée.
func getCorrectedAltitude(db *sql.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var input GpsCoordWithZ
// On valide et on lie le JSON d'entrée à notre struct GpsCoordWithZ.
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Paramètres invalides: " + err.Error()})
return
}
// On appelle notre fonction utilitaire pour obtenir la correction orthométrique (N).
correction, err := queryOrthometricCorrection(db, input.Lat, input.Lon, input.SrcProj, input.DstProj)
if err != nil {
log.Printf("Erreur de la base de données ou aucune valeur trouvée: %v", err)
c.JSON(http.StatusNotFound, gin.H{"error": "Aucune donnée de correction trouvée pour ces coordonnées."})
return
}
// On calcule l'altitude corrigée (H = h - N).
// input.Z est l'altitude ellipsoïdale (h).
// correction est l'ondulation du géoïde (N).
correctedAltitude := input.Z - correction
c.IndentedJSON(http.StatusOK, CorrectedAltitudeResponse{CorrectedZ: correctedAltitude})
}
}
// initDB initializes and returns a database connection.
func initDB(cfg *config.DatabaseConfig) (*sql.DB, error) {
connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%d sslmode=%s",
cfg.User, cfg.Password, cfg.Dbname,
cfg.Host, cfg.Port, cfg.Sslmode,
)
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, fmt.Errorf("could not open database connection: %w", err)
}
if err = db.Ping(); err != nil {
return nil, fmt.Errorf("could not ping database: %w", err)
}
log.Println("Connected to PostgreSQL database")
return db, nil
}
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("Error loading configuration: %v", err)
}
db, err := initDB(&cfg.Database)
if err != nil {
log.Fatalf("Could not initialize database: %v", err)
}
defer db.Close()
router := gin.Default()
// Servir les fichiers statiques (index.html, etc.) depuis le répertoire courant.
router.StaticFS("/", http.Dir("."))
// La route est maintenant un POST pour recevoir un corps de requête JSON.
router.POST("/getorthocorrec", getOrthometricCorrection(db))
// Nouvelle route pour obtenir l'altitude corrigée.
router.POST("/getcorrectedaltitude", getCorrectedAltitude(db))
log.Printf("Starting server on %s", cfg.Server.Address)
if err := router.Run(cfg.Server.Address); err != nil {
log.Fatalf("Failed to run server: %v", err)
}
}