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 :
-
Préprocessing du graphe
sceneView.preprocessNavigationGraph( granularity = GraphGranularity.HIGH, includeAccessibilityInfo = true ) -
Cache de routes
PathFindingUtil.enableCaching( maxCacheSize = 100, ttl = 3600 ) -
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
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¶
- Mettre à jour la dépendance