Aller au contenu

Version 0.3.2 Alpha

Date de sortie : 30 juillet 2024
Statut : Version alpha
Build : 0.3.2


Vue d'ensemble

La version 0.3.2 représente une étape majeure pour MINE - Indoor Navigation Engine, en introduisant des capacités avancées de calcul d'itinéraires permettant le guidage en temps réel et la navigation pas-à-pas en intérieur. Cette version fait passer MINE d'un simple outil de visualisation de carte à une solution de navigation complète.

Version alpha stable

Notre alpha la plus stable à ce jour : fortes améliorations de performance et aucune anomalie critique connue. Prête pour des déploiements pilotes et des tests approfondis.


Points forts

  • Moteur de calcul d'itinéraires


    Algorithme A* avancé avec évitement d'obstacles et support multi-étages

  • Performances optimisées


    Calcul d'itinéraire ultra-rapide avec consommation minimale de ressources

  • UI de navigation


    Ensemble complet de composants UI pour le guidage pas-à-pas

  • Système extensible


    Architecture de plugins pour algorithmes de routage personnalisés


Nouvelles fonctionnalités

Utilitaires de calcul d'itinéraires

Suite complète d'utilitaires pour calculer, manipuler et optimiser les itinéraires.

Capacités clés :

  • 🎯 Implémentation A* — Calcul de plus court chemin efficace
  • 🚧 Détection d'obstacles — Évite automatiquement les zones bloquées
  • 🏢 Routage multi-étages — Navigation fluide entre niveaux
  • Itinéraires accessibles — Parcours alternatifs pour l'accessibilité
  • 🔄 Recalcul dynamique — Ajustement de route en temps réel
  • 📏 Calcul de distance — Estimations précises distance/temps
  • 🎨 Visualisation — Tracé animé des chemins

Exemple d'utilisation :

import com.machinestalk.indoornavigationengine.util.PathFindingUtil
import com.machinestalk.indoornavigationengine.models.Location
import com.machinestalk.indoornavigationengine.models.Route

class NavigationController(private val sceneView: MineSceneView) {

    private val pathFinder = PathFindingUtil(sceneView)

    fun calculateRoute(
        startLocation: Location,
        destinationLocation: Location,
        options: RouteOptions = RouteOptions()
    ): Route? {
        return pathFinder.findPath(
            start = startLocation,
            end = destinationLocation,
            options = options
        )
    }

    fun calculateAccessibleRoute(
        start: Location,
        destination: Location
    ): Route? {
        return pathFinder.findPath(
            start = start,
            end = destination,
            options = RouteOptions(
                accessibleOnly = true,
                avoidStairs = true,
                preferElevators = true
            )
        )
    }

    fun getRouteDistance(route: Route): Float {
        return pathFinder.calculateDistance(route)
    }

    fun getEstimatedTime(route: Route, walkingSpeed: Float = 1.4f): Int {
        val distance = getRouteDistance(route)
        return (distance / walkingSpeed).toInt() // secondes
    }
}
import com.machinestalk.indoornavigationengine.util.PathFindingUtil;
import com.machinestalk.indoornavigationengine.models.Location;
import com.machinestalk.indoornavigationengine.models.Route;
import com.machinestalk.indoornavigationengine.models.RouteOptions;

public class NavigationController {

    private PathFindingUtil pathFinder;

    public NavigationController(MineSceneView sceneView) {
        this.pathFinder = new PathFindingUtil(sceneView);
    }

    public Route calculateRoute(
        Location start,
        Location destination,
        RouteOptions options
    ) {
        return pathFinder.findPath(start, destination, options);
    }

    public Route calculateAccessibleRoute(Location start, Location destination) {
        RouteOptions options = new RouteOptions.Builder()
            .setAccessibleOnly(true)
            .setAvoidStairs(true)
            .setPreferElevators(true)
            .build();

        return pathFinder.findPath(start, destination, options);
    }

    public float getRouteDistance(Route route) {
        return pathFinder.calculateDistance(route);
    }

    public int getEstimatedTime(Route route, float walkingSpeed) {
        float distance = getRouteDistance(route);
        return (int) (distance / walkingSpeed); // secondes
    }
}

Options de route :

Option Type Défaut Description
accessibleOnly Boolean false N'utiliser que des chemins accessibles PMR
avoidStairs Boolean false Préférer rampes et ascenseurs
preferElevators Boolean false Prioriser les ascenseurs
maxDistance Float null Distance maximale acceptable
avoidAreas List [] Zones à éviter lors du routage

Algorithme de calcul d'itinéraires

Implémentation A* optimisée pour la navigation intérieure avec heuristiques avancées.

Fonctionnalités de l'algorithme :

  • 🧮 Recherche A* optimisée — Exploration efficace avec heuristiques intelligentes
  • 🎯 Support multi-cibles — Recherche vers plusieurs destinations en parallèle
  • 🔄 Recherche bidirectionnelle — Calcul plus rapide sur longues distances
  • 📊 Fonctions de coût — Pondérations personnalisables selon préférences
  • 🌳 Optimisation du graphe — Navmeshes pré-calculés pour la perf
  • 💾 Cache d'itinéraires — Mise en cache intelligente des routes fréquentes

Implémentation technique :

import com.machinestalk.indoornavigationengine.pathfinding.PathFindingAlgorithm
import com.machinestalk.indoornavigationengine.pathfinding.AStarConfig

// Configurer l'algorithme A*
val algorithmConfig = AStarConfig(
    heuristicWeight = 1.2f,
    maxNodesExplored = 10000,
    allowDiagonalMovement = true,
    diagonalCost = 1.414f,
    enablePathSmoothing = true,
    timeout = 5000
)

val algorithm = PathFindingAlgorithm.AStar(algorithmConfig)

// Fonction de coût personnalisée
algorithm.setCostFunction { from, to ->
    val baseCost = calculateDistance(from, to)
    val stairPenalty = if (to.type == NodeType.STAIRS) 2.0f else 0f
    val lightingBonus = if (to.lighting > 0.7f) -0.5f else 0f
    baseCost + stairPenalty + lightingBonus
}

val path = algorithm.findPath(
    startNode = startLocation.toNode(),
    endNode = destinationLocation.toNode(),
    navigationGraph = sceneView.getNavigationGraph()
)
import com.machinestalk.indoornavigationengine.pathfinding.PathFindingAlgorithm;
import com.machinestalk.indoornavigationengine.pathfinding.AStarConfig;

AStarConfig config = new AStarConfig.Builder()
    .setHeuristicWeight(1.2f)
    .setMaxNodesExplored(10000)
    .setAllowDiagonalMovement(true)
    .setDiagonalCost(1.414f)
    .setEnablePathSmoothing(true)
    .setTimeout(5000)
    .build();

PathFindingAlgorithm algorithm = PathFindingAlgorithm.createAStar(config);

algorithm.setCostFunction((from, to) -> {
    float baseCost = calculateDistance(from, to);
    float stairPenalty = to.getType() == NodeType.STAIRS ? 2.0f : 0f;
    float lightingBonus = to.getLighting() > 0.7f ? -0.5f : 0f;
    return baseCost + stairPenalty + lightingBonus;
});

Path path = algorithm.findPath(
    startLocation.toNode(),
    destinationLocation.toNode(),
    sceneView.getNavigationGraph()
);

Benchmarks de performance :

Scénario Distance Nœuds Temps Mémoire
Même étage, court 20m 45 8ms 2KB
Même étage, long 150m 320 35ms 12KB
Multi-étage 200m 580 65ms 25KB
Labyrinthe complexe 100m 1200 120ms 45KB

Composants UI de navigation

Ensemble complet de composants UI prêts à l'emploi pour afficher instructions et informations d'itinéraire.

Composants disponibles :

Panneau d'instructions

import com.machinestalk.indoornavigationengine.ui.NavigationInstructionPanel

val instructionPanel = NavigationInstructionPanel(this).apply {
    setPosition(NavigationInstructionPanel.Position.TOP_CENTER)
    setBackgroundColor(Color.parseColor("#2196F3"))
    setTextColor(Color.WHITE)

    showInstruction(
        action = NavigationAction.TURN_LEFT,
        distance = 25f,
        description = "Tournez à gauche à la zone restauration"
    )
    showNextInstruction(
        action = NavigationAction.GO_STRAIGHT,
        distance = 50f
    )
    setOnInstructionClickListener {
        showDetailedDirections()
    }
}

sceneView.addUIComponent(instructionPanel)

Barre de progression d'itinéraire

import com.machinestalk.indoornavigationengine.ui.RouteProgressBar

val progressBar = RouteProgressBar(this).apply {
    setPosition(RouteProgressBar.Position.BOTTOM)
    setTotalDistance(250f)
    setRemainingDistance(180f)
    setEstimatedTime(180)
    setProgressColor(Color.parseColor("#4CAF50"))
    setBackgroundColor(Color.parseColor("#E0E0E0"))
    setHeight(8.dp)
    enableAutoUpdate = true
}

sceneView.addUIComponent(progressBar)

Carte d'aperçu d'itinéraire

import com.machinestalk.indoornavigationengine.ui.RoutePreviewCard

val routeCard = RoutePreviewCard(this).apply {
    setDestination("Coffee Shop - Étage 2")
    setDistance(185f)
    setEstimatedTime(135)
    setFloorChanges(1)

    addAlternativeRoute(
        name = "Chemin plus court",
        distance = 165f,
        time = 120,
        note = "Par les escaliers"
    )

    addAlternativeRoute(
        name = "Chemin accessible",
        distance = 210f,
        time = 155,
        note = "Accès ascenseur"
    )

    setOnRouteSelectedListener { routeIndex ->
        startNavigation(routes[routeIndex])
    }
}

sceneView.addUIComponent(routeCard)

Liste d'étapes pas-à-pas

import com.machinestalk.indoornavigationengine.ui.DirectionListView

val directionList = DirectionListView(this).apply {
    route.instructions.forEach { instruction ->
        addDirection(
            icon = getInstructionIcon(instruction.action),
            text = instruction.description,
            distance = instruction.distance
        )
    }
    setCurrentStep(0)
    setOnStepSelectedListener { stepIndex ->
        previewStep(route.instructions[stepIndex])
    }
}

showBottomSheet(directionList)

Personnalisation UI :

NavigationUIConfig.apply {
    primaryColor = Color.parseColor("#2196F3")
    accentColor = Color.parseColor("#FF5722")
    textColor = Color.parseColor("#212121")
    fontFamily = ResourcesCompat.getFont(context, R.font.custom_font)
    titleTextSize = 18.sp
    bodyTextSize = 14.sp
    defaultPadding = 16.dp
    componentMargin = 8.dp
    animationDuration = 300
    enableTransitions = true
}

Algorithmes personnalisés

Étendez le moteur avec vos propres algorithmes de routage pour des cas spécifiques.

Architecture plugin :

import com.machinestalk.indoornavigationengine.pathfinding.CustomPathFinder
import com.machinestalk.indoornavigationengine.pathfinding.PathFinderPlugin

class ShortestTimePathFinder : PathFinderPlugin {
    override val name = "Shortest Time Algorithm"
    override val version = "1.0.0"

    override fun findPath(
        start: Node,
        end: Node,
        graph: NavigationGraph,
        options: RouteOptions
    ): Path? {
        val openSet = PriorityQueue<Node>(compareBy { it.estimatedTime })
        val closedSet = mutableSetOf<Node>()
        openSet.add(start)
        while (openSet.isNotEmpty()) {
            val current = openSet.poll()
            if (current == end) return reconstructPath(current)
            closedSet.add(current)
            graph.getNeighbors(current).forEach { neighbor ->
                if (neighbor in closedSet) return@forEach
                val travelTime = calculateTravelTime(current, neighbor)
                val newTime = current.totalTime + travelTime
                if (newTime < neighbor.totalTime) {
                    neighbor.totalTime = newTime
                    neighbor.parent = current
                    if (neighbor !in openSet) openSet.add(neighbor)
                }
            }
        }
        return null
    }

    private fun calculateTravelTime(from: Node, to: Node): Float {
        val distance = from.distanceTo(to)
        val speed = when (to.type) {
            NodeType.WALKWAY -> 1.4f
            NodeType.STAIRS -> 0.6f
            NodeType.ESCALATOR -> 0.8f
            NodeType.ELEVATOR -> 0.5f
            else -> 1.0f
        }
        return distance / speed
    }
}

val customAlgorithm = ShortestTimePathFinder()
PathFindingUtil.registerAlgorithm(customAlgorithm)
val route = pathFinder.findPath(
    start = startLocation,
    end = destinationLocation,
    algorithm = customAlgorithm
)
import com.machinestalk.indoornavigationengine.pathfinding.PathFinderPlugin;

public class ShortestTimePathFinder implements PathFinderPlugin {
    @Override public String getName() { return "Shortest Time Algorithm"; }
    @Override public String getVersion() { return "1.0.0"; }
    @Override public Path findPath(
        Node start,
        Node end,
        NavigationGraph graph,
        RouteOptions options
    ) {
        PriorityQueue<Node> openSet = new PriorityQueue<>(
            Comparator.comparing(Node::getEstimatedTime)
        );
        Set<Node> closedSet = new HashSet<>();
        openSet.add(start);
        while (!openSet.isEmpty()) {
            Node current = openSet.poll();
            if (current.equals(end)) return reconstructPath(current);
            closedSet.add(current);
            for (Node neighbor : graph.getNeighbors(current)) {
                if (closedSet.contains(neighbor)) continue;
                float travelTime = calculateTravelTime(current, neighbor);
                float newTime = current.getTotalTime() + travelTime;
                if (newTime < neighbor.getTotalTime()) {
                    neighbor.setTotalTime(newTime);
                    neighbor.setParent(current);
                    if (!openSet.contains(neighbor)) openSet.add(neighbor);
                }
            }
        }
        return null;
    }
}

Sélection d'algorithme :

val algorithms = PathFindingUtil.getAvailableAlgorithms()
algorithms.forEach { algorithm ->
    println("${algorithm.name} v${algorithm.version}")
}

val route = pathFinder.findPath(
    start = startLocation,
    end = destinationLocation,
    algorithmName = "Shortest Time Algorithm"
)

Optimisations de performance

Améliorations majeures sur la vitesse de calcul et l'utilisation des ressources.

Techniques :

  1. Préprocessing du graphe

    sceneView.preprocessNavigationGraph(
        granularity = GraphGranularity.HIGH,
        includeAccessibilityInfo = true
    )
    

  2. Cache de routes

    PathFindingUtil.enableCaching(
        maxCacheSize = 100,
        ttl = 3600
    )
    

  3. Mises à jour incrémentales

    navigationGraph.updateRegion(
        bounds = changedArea,
        incremental = true
    )
    

Gains mesurés :

Métrique v0.2.1 v0.3.2 Gain
Calcul d'itinéraire 180ms 65ms ⬇️ 64%
Mémoire 45MB 28MB ⬇️ 38%
Chargement du graphe 2.1s 0.8s ⬇️ 62%
Taux de cache N/A 78% ⬆️ Nouveau
CPU 35% 18% ⬇️ 49%

Corrections de bugs

Stabilité de l'algorithme

Problème : - Crashs occasionnels sur cartes complexes - Boucles infinies dans certains plans circulaires - Fuites mémoire sur longues sessions - Routes incorrectes sur graphes non connectés

Cause racine : - Validation insuffisante de connectivité - Conditions d'arrêt manquantes - Nettoyage incomplet des structures temporaires - Cas limites dans les transitions multi-étages

Résolution :

fun validateNavigationGraph(graph: NavigationGraph): ValidationResult {
    val issues = mutableListOf<String>()
    if (!graph.isFullyConnected()) issues.add("Graphe non entièrement connecté")
    graph.nodes.forEach { node ->
        if (node.neighbors.isEmpty() && node.type != NodeType.DESTINATION) {
            issues.add("Nœud isolé détecté: ${node.id}")
        }
    }
    graph.edges.forEach { edge ->
        if (edge.cost < 0) issues.add("Coût négatif: ${edge.id}")
    }
    return if (issues.isEmpty()) ValidationResult.Valid else ValidationResult.Invalid(issues)
}

val pathResult = pathFinder.findPath(
    start = start,
    end = end,
    config = AStarConfig(
        maxNodesExplored = 10000,
        timeout = 5000,
        enableSafetyChecks = true
    )
)

Impact : - 99,8% de succès de calcul (vs 94%) - Zéro crash lié au pathfinding en test - Mémoire -38%


Affichage UI de navigation

Problème : - Texte coupé sur petits écrans - Barre de progression non mise à jour en temps réel - Icônes parfois manquantes - Scintillement pendant les mises à jour

Résolution : - Taille de texte responsive - Intégration temps réel de la position pour la progression - Préchargement/cache des icônes - Pipeline de rendu optimisé pour supprimer le flicker

Avant :

instructionPanel.updateInstruction(newInstruction)
progressBar.setProgress(newProgress)

Après :

sceneView.batchUIUpdate {
    instructionPanel.updateInstruction(newInstruction)
    progressBar.setProgress(newProgress)
}

Gains techniques : - Latence UI -75% - Scintillement éliminé (double buffering) - Rendu texte amélioré - Transitions animées plus fluides


Parsing JSON de configuration

Problème : Fichiers JSON vides/malformés provoquaient crash ou valeurs par défaut incorrectes.

Résolution :

fun loadPathFindingConfig(fileName: String): PathFindingConfig {
    return try {
        val json = JsonUtil.LoadJsonFromAsset(context, fileName)
        if (json.isNullOrEmpty()) {
            Log.w("PathFinding", "Fichier vide, utilisation des valeurs par défaut")
            return PathFindingConfig.createDefault()
        }
        val config = parsePathFindingConfig(json)
        if (!config.isValid()) {
            Log.w("PathFinding", "Config invalide, valeurs par défaut")
            return PathFindingConfig.createDefault()
        }
        config
    } catch (e: JsonSyntaxException) {
        Log.e("PathFinding", "Erreur JSON : ${e.message}")
        PathFindingConfig.createDefault()
    } catch (e: Exception) {
        Log.e("PathFinding", "Échec de chargement : ${e.message}")
        PathFindingConfig.createDefault()
    }
}

fun PathFindingConfig.Companion.createDefault() = PathFindingConfig(
    algorithm = "A*",
    heuristicWeight = 1.0f,
    allowDiagonalMovement = true,
    enableCaching = true,
    maxNodesExplored = 10000
)

Sécurités supplémentaires : - Validation de schéma - Logs détaillés avec numéros de ligne - Fallback gracieux - Validation avant usage


Problèmes connus

Aucun problème critique connu

La version 0.3.2 ne présente aucun problème critique ou majeur connu. Tous les bugs signalés précédemment sont résolus et les tests étendus n'ont rien révélé de significatif.

Couverture de test :

  • ✅ Routage sur 50+ configurations de cartes
  • ✅ Navigation sur 20+ modèles d'appareils
  • ✅ Stress tests multi-étages complexes
  • ✅ Tests de fuite mémoire sur >8h
  • ✅ Tests de performance sur appareils bas de gamme
  • ✅ Tests UI sur toutes tailles d'écran

Signaler un problème


Mise à niveau depuis v0.2.1

Changements incompatibles

Petits changements d'API

Quelques ajustements mineurs peuvent nécessiter des mises à jour de code.

Constructeur PathFinder

Avant (v0.2.1) :

val pathFinder = PathFinder(context)

Après (v0.3.2) :

val pathFinder = PathFindingUtil(sceneView)
// Requiert désormais MineSceneView au lieu du Context

Options de route

Avant :

val route = pathFinder.findPath(start, end, accessible = true)

Après :

val options = RouteOptions(accessibleOnly = true)
val route = pathFinder.findPath(start, end, options)

Étapes de migration

  1. Mettre à jour la dépendance