First commit for realz API REST service
This commit is contained in:
202
main.go
Normal file
202
main.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
_ "github.com/lib/pq" // Driver PostgreSQL
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config struct to hold all configuration for our application
|
||||
type Config struct {
|
||||
Database struct {
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Dbname string `mapstructure:"dbname"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Sslmode string `mapstructure:"sslmode"`
|
||||
} `mapstructure:"database"`
|
||||
Server struct {
|
||||
Address string `mapstructure:"address"`
|
||||
} `mapstructure:"server"`
|
||||
}
|
||||
|
||||
// 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})
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// --- Chargement de la configuration ---
|
||||
// 0. Configuration pour les variables d'environnement
|
||||
viper.SetEnvPrefix("APP") // Les variables devront commencer par "APP_" (ex: APP_SERVER_ADDRESS)
|
||||
|
||||
// 1. Fichier de configuration de base
|
||||
viper.SetConfigName("config") // config.yaml
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// Lecture du fichier de base. On ne s'arrête pas s'il n'est pas trouvé.
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
log.Fatalf("Erreur lors de la lecture du fichier de base config.yaml: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Surcharge par environnement (via la variable APP_ENV)
|
||||
if env := os.Getenv("APP_ENV"); env != "" {
|
||||
viper.SetConfigName("config." + env) // ex: config.prod.yaml
|
||||
// MergeInConfig fusionne la nouvelle configuration avec celle déjà chargée.
|
||||
// Les valeurs du nouveau fichier écrasent les anciennes.
|
||||
if err := viper.MergeInConfig(); err != nil {
|
||||
log.Printf("Avertissement : impossible de charger le fichier de configuration pour l'environnement '%s': %v", env, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2a. Indiquer à Viper de lire les variables d'environnement APRÈS avoir lu les fichiers.
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // Remplace les "." par des "_" (ex: server.address -> SERVER_ADDRESS)
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// 3. "Unmarshal" de la configuration finale dans la struct
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
log.Fatalf("Impossible de décoder la configuration dans la structure : %v", err)
|
||||
}
|
||||
// --- Fin du chargement de la configuration ---
|
||||
|
||||
// Chaîne de connexion à votre base de données PostgreSQL.
|
||||
// Construite à partir de la configuration.
|
||||
connStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%d sslmode=%s",
|
||||
config.Database.User, config.Database.Password, config.Database.Dbname,
|
||||
config.Database.Host, config.Database.Port, config.Database.Sslmode,
|
||||
)
|
||||
|
||||
// Connexion à la base de données
|
||||
db, err := sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
log.Fatal("Impossible de se connecter à la base de données:", err)
|
||||
} else {
|
||||
log.Println("Connecté à la base de données PostgreSQL")
|
||||
}
|
||||
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("Démarrage du serveur sur %s", config.Server.Address)
|
||||
router.Run(config.Server.Address)
|
||||
}
|
Reference in New Issue
Block a user