commit 65c81b992144fa90e4193dbe52c4669114fc92a8 Author: Mikael Capelle Date: Mon Jan 29 12:35:24 2018 +0100 Initial commit. diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..f7d8560 --- /dev/null +++ b/.classpath @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..9a8e431 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + be-graphes-base + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/FORMAT b/FORMAT new file mode 100644 index 0000000..3cd3022 --- /dev/null +++ b/FORMAT @@ -0,0 +1,61 @@ +=== Format des fichiers .map === + + - Version du document (= version du format) : 4 + + - Sauf mention contraire, les entiers sont codés en big endian (compatible DataOutputStream). + + [No d'octets] = signification + + [0-3] = Magic number 0xbacaff (doit se trouver au début du fichier) + [4-7] = Version du format + [8-11] = Identifiant de carte + [12-15] = Numéro de zone + [16-19] = Nombre de descripteurs dans ce fichier + [20-23] = Nombre de noeuds dans ce fichier + + [24-..] = + * Tous les noeuds, les uns après les autres, en commençant par le numéro 0. Voir le format d'un noeud. + * Puis un octet à 255. + + * Puis, tous les descripteurs, les uns après les autres, en commençant par le numéro 0. + Voir le format des descripteurs. + * Puis un octet à 254. + + * Puis, toutes les routes sortantes (routes sortantes du premier noeud, puis celles du deuxième noeud, etc. ) + * Puis un octet à 253. + + (fin du fichier) + + +=== Format des noeuds === + + [0-3] = longitude sur 32 bits (à diviser par 1E6) + [4-7] = latitude sur 32 bits (à diviser par 1E6) + [8] = Nombre de routes sortantes sur 8 bits + + +=== Format des routes sortantes (taille variable car dépend du nombre de segments) === + + [0] = Numéro de zone du noeud destination (8 bits) + [1-3] = Numéro du noeud destination, dans la zone donnée (24 bits, big endian) + [4-6] = Numéro de descripteur (24 bits) + [7-8] = Longueur de l'arête (16 bits), en mètres, prenant en compte tous les segments. + [9-10] = Nombre de segments (16 bits), éventuellement 0. + [11-...] = Segments + + +=== Format des segments === + + [0-1] = Delta de longitude, sur 16 bits signés (à diviser par 2.0E5) + [2-3] = Delta de latitude, sur 16 bits signés (à diviser par 2.0E5) + +=== Format des descripteurs (la taille est variable, car elle dépend du nom du chemin) === + + [0] = Un caractère indiquant le type de chemin (voir dans Descripteur.java) + [1] + .bit 7 = sens unique + .bits 0-6 = vitesse max en km/h à multiplier par 5. + + [2-] = Nom du chemin, de type String-UTF8 (les deux premiers octets donnent la longueur de la chaîne) + + diff --git a/FORMAT_PATH b/FORMAT_PATH new file mode 100644 index 0000000..dbc0615 --- /dev/null +++ b/FORMAT_PATH @@ -0,0 +1,21 @@ +=== Format des fichiers .path === + + - Version du document (= version du format) : 1 + + - Sauf mention contraire, les entiers sont codés en big endian (compatible DataOutputStream). + + [No d'octets] = signification + + [0-3] = Magic number 0xdecafe (doit se trouver au début du fichier) + [4-7] = Version du format + [8-11] = Identifiant de carte + [12-15] = Nombre de noeuds dans le chemin + [16-19] = Identifiant du premier noeud (8 bits zone + 24 bits numéro noeud) + [20-23] = Identifiant du dernier noeud (8 bits zone + 24 bits numéro noeud) + + [24-27] = Identifiant du premier noeud (encore) + [28-31] = Identifiant du deuxième noeud + [32-35] = Identifiant du troisième noeud + etc. + [derniers octets] = Identifiant du dernier noeud + diff --git a/src/main/org/insa/algo/AbstractAlgorithm.java b/src/main/org/insa/algo/AbstractAlgorithm.java new file mode 100644 index 0000000..cdfb46c --- /dev/null +++ b/src/main/org/insa/algo/AbstractAlgorithm.java @@ -0,0 +1,59 @@ +package org.insa.algo ; + +import java.io.* ; + +public abstract class AbstractAlgorithm { + + protected PrintStream output; + protected AbstractInstance instance; + protected AbstractSolution solution; + + /** + * + * @param instance + * @param logOutput + */ + protected AbstractAlgorithm(AbstractInstance instance, PrintStream logOutput) { + this.instance = instance; + this.output = logOutput; + this.solution = null; + } + + /** + * Update the current solution. + * + * @param solution New solution, or null to unset the current solution. + * + */ + protected void updateLastSolution(AbstractSolution solution) { + this.solution = solution; + } + + /** + * @return Instance corresponding to this algorithm. + */ + public AbstractInstance getInstance() { return instance; } + + /** + * @return Last solution, or null if no solution was stored. + */ + public AbstractSolution getLastSolution() { return solution; } + + /** + * Run the algorithm and update the current solution. + * + * @return true if a feasible solution was found (even non-optimal). + */ + public boolean run() { + this.solution = this.doRun(); + return this.solution != null && this.solution.isFeasible(); + } + + /** + * Abstract method that should be implemented by child class. + * + * @return A solution, if one was found, or null. + */ + protected abstract AbstractSolution doRun(); + +} diff --git a/src/main/org/insa/algo/AbstractInstance.java b/src/main/org/insa/algo/AbstractInstance.java new file mode 100644 index 0000000..344ad82 --- /dev/null +++ b/src/main/org/insa/algo/AbstractInstance.java @@ -0,0 +1,20 @@ +package org.insa.algo; + +import org.insa.graph.Graph; + +public abstract class AbstractInstance { + + protected Graph graph; + + /** + * Create a new abstract instance with the given graph. + * + * @param graph + */ + protected AbstractInstance(Graph graph) { + this.graph = graph; + } + + public Graph getGraph() { return graph; } + +} diff --git a/src/main/org/insa/algo/AbstractSolution.java b/src/main/org/insa/algo/AbstractSolution.java new file mode 100644 index 0000000..87bddc2 --- /dev/null +++ b/src/main/org/insa/algo/AbstractSolution.java @@ -0,0 +1,67 @@ +package org.insa.algo; + +import java.time.Duration; + +public abstract class AbstractSolution { + + /** + * Possible status for a solution. + * + */ + public enum Status { + UNKNOWN, + INFEASIBLE, + FEASIBLE, + OPTIMAL, + }; + + // Status of the solution. + Status status; + + // Solving time for the solution + Duration solvingTime; + + // Original instance of the solution + AbstractInstance instance; + + /** + * Create a new abstract solution with unknown status. + * + * @param instance + */ + protected AbstractSolution(AbstractInstance instance) { + this.instance = instance; + this.solvingTime = Duration.ZERO; + this.status = Status.UNKNOWN; + } + + protected AbstractSolution(AbstractInstance instance, + Duration solvingTime, Status status) { + this.instance = instance; + this.solvingTime = solvingTime; + this.status = status; + } + + /** + * @return Original instance for this solution. + */ + public AbstractInstance getInstance() { return instance; } + + /** + * @return Status of this solution. + */ + public Status getStatus() { return status; } + + /** + * @return Solving time of this solution. + */ + public Duration getSolvingTime() { return solvingTime; } + + /** + * @return true if the solution is feasible or optimal. + */ + public boolean isFeasible() { + return status == Status.FEASIBLE || status == Status.OPTIMAL; + } + +} diff --git a/src/main/org/insa/algo/connectivity/ConnectivityAlgorithm.java b/src/main/org/insa/algo/connectivity/ConnectivityAlgorithm.java new file mode 100644 index 0000000..ecd34aa --- /dev/null +++ b/src/main/org/insa/algo/connectivity/ConnectivityAlgorithm.java @@ -0,0 +1,30 @@ +package org.insa.algo.connectivity ; + +import java.io.* ; + +import org.insa.algo.AbstractAlgorithm; +import org.insa.algo.AbstractSolution; + +public class ConnectivityAlgorithm extends AbstractAlgorithm { + + /** + * + * @param instance + * @param logOutput + */ + public ConnectivityAlgorithm(ConnectivityInstance instance, PrintStream logOutput) { + super(instance, logOutput); + } + + /** + * {@inheritDoc} + */ + @Override + protected AbstractSolution doRun() { + ConnectivityInstance instance = (ConnectivityInstance)getInstance(); + ConnectivitySolution solution = null; + // TODO: + return solution; + } + +} diff --git a/src/main/org/insa/algo/connectivity/ConnectivityInstance.java b/src/main/org/insa/algo/connectivity/ConnectivityInstance.java new file mode 100644 index 0000000..987872a --- /dev/null +++ b/src/main/org/insa/algo/connectivity/ConnectivityInstance.java @@ -0,0 +1,16 @@ +package org.insa.algo.connectivity; + +import org.insa.algo.AbstractInstance; +import org.insa.graph.Graph; + +public class ConnectivityInstance extends AbstractInstance { + + /** + * + * @param graph + */ + public ConnectivityInstance(Graph graph) { + super(graph); + } + +} diff --git a/src/main/org/insa/algo/connectivity/ConnectivitySolution.java b/src/main/org/insa/algo/connectivity/ConnectivitySolution.java new file mode 100644 index 0000000..114e6a4 --- /dev/null +++ b/src/main/org/insa/algo/connectivity/ConnectivitySolution.java @@ -0,0 +1,20 @@ +package org.insa.algo.connectivity; + +import java.time.Duration; + +import org.insa.algo.AbstractSolution; + +public class ConnectivitySolution extends AbstractSolution { + + protected ConnectivitySolution(ConnectivityInstance instance) { + super(instance); + } + + protected ConnectivitySolution(ConnectivityInstance instance, + Duration solvingTime, Status status) { + super(instance, solvingTime, status); + + // TODO: + } + +} diff --git a/src/main/org/insa/base/Couleur.java b/src/main/org/insa/base/Couleur.java new file mode 100644 index 0000000..e137851 --- /dev/null +++ b/src/main/org/insa/base/Couleur.java @@ -0,0 +1,58 @@ +package org.insa.base ; + +/** + * Choix des couleurs pour l'affichage. + */ + +import java.awt.* ; + +import org.insa.drawing.Drawing; + +public class Couleur { + + static final Color autoroute = Color.red ; + static final Color bigroute = new Color(255, 105, 0) ; + static final Color tiroute = new Color(255, 234, 0) ; + static final Color cote = Color.blue ; + + public static void set(Drawing d, char type) { + + // Voir le fichier Descripteur.java pour le type des routes. + switch (type) { + case 'a': + d.setWidth(2) ; + d.setColor(Color.red) ; + break ; + + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + d.setWidth(1) ; + d.setColor(bigroute) ; + break ; + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + d.setWidth(1) ; + d.setColor(tiroute) ; + break ; + + case 'z': + d.setWidth(4) ; + d.setColor(cote) ; + break ; + + default: + d.setWidth(1) ; + d.setColor(Color.black) ; + } + } +} diff --git a/src/main/org/insa/base/Openfile.java b/src/main/org/insa/base/Openfile.java new file mode 100644 index 0000000..4f2e374 --- /dev/null +++ b/src/main/org/insa/base/Openfile.java @@ -0,0 +1,115 @@ +package org.insa.base ; + +import java.io.* ; +import java.util.zip.* ; + +/* Ne lisez pas cette classe. Lancez javadoc et lisez la doc generee plutot. */ + +/** + * La classe Openfile permet de lire les fichiers contenant les cartes : + * + * + */ +public class Openfile { + + // Le programme examine chaque dossier dans l'ordre jusqu'a trouver celui qui contient la carte voulue + private static final String[] datadirs = + { // NE MODIFIEZ PAS CELUI-CI + // car il permet de tester en etant a l'INSA. + "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps", + + // Celui-ci pour les chemins + "/home/commetud/3eme Annee MIC/Graphes-et-Algorithmes/", + + // On cherche aussi dans le sous-repertoire local "Maps" (s'il existe) + "Maps", + + // et dans le repertoire courant (Unix uniquement) + ".", + + // Si vous utilisez votre propre dossier pour les donnees, mettez-le ici. + "/home/votrepropredossier/a/vous", + } ; + + // Extension testees. Garder l'extension vide dans la liste. + private static final String[] extensions = { ".map", ".gz", ".map.gz", ".path", ".path.gz", "" } ; + + /** + * Ouvre le fichier indiqué et renvoie un DataInputStream sur ce fichier. + * Le fichier ne sera pas ferme avant la fin de l'application. + * @param filename Nom du fichier a ouvrir (sans chemin) + */ + public static DataInputStream open (String filename) { + + if (!filename.equals (new File(filename).getName())) { + System.out.println("Le nom du fichier ne doit pas contenir un chemin (ni absolu, ni relatif).") ; + System.out.println("Il doit juste contenir le nom du fichier contenant la carte.") ; + System.out.println("Si vous voulez utiliser un dossier specifique, configurez base/Openfile.java") ; + System.exit(1) ; + } + + boolean trouve = false ; + InputStream fileinput = null ; + String fname = null ; + String fullpath = null ; + + for (int extn = 0 ; !trouve && extn < extensions.length ; extn++) { + fname = filename + extensions[extn] ; + for (int index = 0 ; !trouve && index < datadirs.length ; index++) { + fullpath = datadirs[index] + File.separator + fname ; + File file = new File(fullpath) ; + if (file.canRead()) { + trouve = true ; + try { + fileinput = new FileInputStream(file) ; + } catch (IOException e) { + e.printStackTrace() ; + System.exit(1) ; + } + } + } + } + + if (!trouve) { + // Pas trouve + System.out.println("Impossible de trouver le fichier " + filename) ; + System.out.println(" pourtant j'ai cherche dans les dossiers : ") ; + int existepas = 0 ; + for (int i = 0 ; i < datadirs.length ; i++) { + System.out.println(" - " + datadirs[i]) ; + if (!new File(datadirs[i]).isDirectory()) { + switch (existepas) { + case 0: System.out.println(" (Ce dossier n'existe pas d'ailleurs)") ; break; + case 1: System.out.println(" (Ce dossier n'existe pas non plus)") ; break; + default: System.out.println(" (Celui-la non plus)") ; break; + } + existepas++ ; + } + System.out.println() ; + } + System.exit(1) ; + } + + System.out.println("Fichier utilisee : " + fullpath) ; + System.out.println() ; + + if (fname.endsWith(".gz")) { + // The file is gzipped. + try { + fileinput = new GZIPInputStream(fileinput) ; + } catch (IOException e) { + e.printStackTrace() ; + System.exit(1) ; + } + } + else { + fileinput = new BufferedInputStream(fileinput) ; + } + + return new DataInputStream(fileinput) ; + } + +} diff --git a/src/main/org/insa/base/Readarg.java b/src/main/org/insa/base/Readarg.java new file mode 100644 index 0000000..00c2723 --- /dev/null +++ b/src/main/org/insa/base/Readarg.java @@ -0,0 +1,86 @@ +package org.insa.base ; + +import java.io.* ; + +/* Ne lisez pas cette classe. Lancez javadoc et lisez la doc generee plutot. */ + +/** + * La classe Readarg facilite la lecture de donnees depuis le clavier ou depuis la ligne de commande. + * + */ +public class Readarg { + + private final String[] args ; + private int next ; + + // Le Java est le langage prefere des Shadoks. + private final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + public Readarg(String[] argz) { + this.args = argz ; + this.next = 0 ; + } + + /** + * Obtient une chaine, ou bien depuis la ligne de commande, ou depuis l'entree standard. + * @param msg Message affiche avant de demander la chaine + */ + public String lireString (String msg) { + + String resultat = "" ; + + System.out.print(msg) ; + + if (this.next >= this.args.length) { + try { + resultat = br.readLine () ; + } catch (Exception e) { + System.err.println ("Erreur de lecture de l'entree standard.") ; + System.exit(1) ; + } + } + else { + resultat = this.args[this.next] ; + this.next++ ; + System.out.println (resultat) ; + } + + return resultat ; + } + + + /** + * Obtient un entier, ou bien depuis la ligne de commande, ou depuis l'entree standard. + * @param msg Message affiche avant de demander l'entier + */ + public int lireInt (String msg) { + String lu = lireString (msg) ; + int result = 0 ; + try { + result = Integer.parseInt(lu) ; + } + catch (Exception e) { + System.err.println ("Un entier est attendu mais je lis " + lu) ; + System.exit(1) ; + } + return result ; + } + + /** + * Obtient un float, ou bien depuis la ligne de commande, ou depuis l'entree standard. + * @param msg Message affiche avant de demander le float. + */ + public float lireFloat (String msg) { + String lu = lireString (msg) ; + float result = 0 ; + try { + result = Float.parseFloat(lu) ; + } + catch (Exception e) { + System.err.println ("Un reel est attendu mais je lis " + lu) ; + System.exit(1) ; + } + + return result ; + } +} diff --git a/src/main/org/insa/base/Utils.java b/src/main/org/insa/base/Utils.java new file mode 100644 index 0000000..f4c77e9 --- /dev/null +++ b/src/main/org/insa/base/Utils.java @@ -0,0 +1,57 @@ +package org.insa.base ; + +import java.io.* ; + +import org.insa.drawing.Drawing; + +/** + * Fonctions accessoires dont vous n'avez pas a vous servir directement. + */ +public class Utils { + + + // Calibrer la sortie graphique en fonction de la carte + // Vous pouvez modifier les coordonnees pour ameliorer le rendu. + public static void calibrer(String nomCarte, Drawing dessin) { + + if (nomCarte.startsWith("insa")) { + // L'INSA + dessin.setBB (1.462, 1.473, 43.567, 43.5744) ; + } + else if (nomCarte.startsWith("paris")) { + // Ile de la Cité, Paris + dessin.setBB (2.329, 2.372, 48.839, 48.867) ; + } + else if (nomCarte.startsWith("mayot")) { + // Mayotte + dessin.setBB (44.5, 45.5, -13.25, -12.25) ; + } + else if (nomCarte.startsWith("reuni")) { + // La Réunion + dessin.setBB (55.0, 56.0, -21.5, -20.5) ; + } + else if (nomCarte.startsWith("midip")) { + dessin.setBB (-0.6, 3.8, 42.2, 45.3) ; + } + else if (nomCarte.startsWith("franc")) { + dessin.setBB (-5.2, 10.0, 41.0, 51.5) ; + } + else if (nomCarte.startsWith("pfranc")) { + dessin.setBB (-5.2, 10.0, 41.0, 51.5) ; + } + else if (nomCarte.startsWith("morbihan")) { + dessin.setBB (-3.53, -2.452, 47.27, 47.665) ; + } + else if (nomCarte.startsWith("newzealand")) { + dessin.setBB (153.415, 179.912, -47.931, -33.980) ; + } + else if (nomCarte.startsWith("fract") || nomCarte.startsWith("carr")) { + dessin.setBB (-0.05, 1.05, -0.05, 1.05) ; + } + else { + dessin.setBB (-20.0, 50.0, 20.0, 70.0) ; + } + } + + +} diff --git a/src/main/org/insa/drawing/Drawing.java b/src/main/org/insa/drawing/Drawing.java new file mode 100644 index 0000000..c3d2685 --- /dev/null +++ b/src/main/org/insa/drawing/Drawing.java @@ -0,0 +1,83 @@ +package org.insa.drawing ; + +/** + * Classe abstraite pour dessiner a l'ecran. + * Deux implementations : une sous-classe DessinVisible qui dessine vraiment a l'ecran + * et une sous-classe DessinInvisible qui ne dessine rien (pour ne pas ralentir les tests avec l'affichage). + */ + +import java.awt.* ; + +public interface Drawing { + + /** + * Enable auto-repaint mode - When this mode is enable, call to + * drawing function will automatically repaint the drawing, which + * may be very slow in some case. + * + * @param autoRepaint Use true to enable auto-repaint, false to disable. + * + */ + public void setAutoRepaint(boolean autoRepaint); + + /** + * Repaint the drawing. + * + */ + public void repaint(); + + /** + * Set the pencil width. + * + * @param width Width for the pencil. + * + */ + public void setWidth(int width); + + /** + * Set the pencil color. + * + * param color Color for the pencil. + * + */ + public void setColor(Color col); + + /** + * Indique les bornes de la fenetre graphique. + * Le calcul des coordonnees en pixel se fera automatiquement + * a l'appel des methodes drawLine et autres. + * + * @param long1 longitude du bord gauche + * @param long2 longitude du bord droit + * @param lat1 latitude du bord bas + * @param lat2 latitude du bord haut + * + */ + public void setBB(double long1, double long2, double lat1, double lat2); + + /** + * Trace un segment. + * @param long1 longitude du premier point + * @param lat1 latitude du premier point + * @param long2 longitude du second point + * @param lat2 latitude du second point + */ + public void drawLine(float long1, float lat1, float long2, float lat2); + + /** + * Trace un point. + * @param lon longitude du point + * @param lat latitude du point + * @param width grosseur du point + */ + public void drawPoint(float lon, float lat, int width); + + /** + * Ecrit du texte a la position indiquee. + * @param lon longitude du point ou positionner le texte. + * @param lat latitude du point ou positionner le texte. + * @param txt le texte a ecrire. + */ + public void putText(float lon, float lat, String txt); + +} diff --git a/src/main/org/insa/drawing/DrawingInvisible.java b/src/main/org/insa/drawing/DrawingInvisible.java new file mode 100644 index 0000000..ea572fd --- /dev/null +++ b/src/main/org/insa/drawing/DrawingInvisible.java @@ -0,0 +1,38 @@ +package org.insa.drawing; + +import java.awt.Color; + +/** + * Cette implementation de la classe Dessin ne produit pas d'affichage, + * ce qui accelere l'execution (utile pour ne pas ralentir les tests). + */ + +public class DrawingInvisible implements Drawing { + + public DrawingInvisible () { } + + @Override + public void setWidth(int width) { } + + @Override + public void setColor(Color col) { } + + @Override + public void setBB(double long1, double long2, double lat1, double lat2) { } + + @Override + public void drawLine(float long1, float lat1, float long2, float lat2) { } + + @Override + public void drawPoint(float lon, float lat, int width) { } + + @Override + public void putText(float lon, float lat, String txt) { } + + @Override + public void setAutoRepaint(boolean autoRepaint) { } + + @Override + public void repaint() { } + +} diff --git a/src/main/org/insa/drawing/DrawingVisible.java b/src/main/org/insa/drawing/DrawingVisible.java new file mode 100644 index 0000000..99aca86 --- /dev/null +++ b/src/main/org/insa/drawing/DrawingVisible.java @@ -0,0 +1,184 @@ +package org.insa.drawing; + +import java.awt.*; +import java.awt.image.*; + +/** + * Cette implementation de la classe Dessin produit vraiment un affichage + * (au contraire de la classe DessinInvisible). + */ + +public class DrawingVisible extends Canvas implements Drawing { + + /** + * + */ + private static final long serialVersionUID = 96779785877771827L; + + private final Graphics2D gr; + + private float long1; + private float long2; + private float lat1; + private float lat2; + private final float width; + private final float height; + + private boolean bb_is_set ; + + private Image image; + private ZoomAndPanListener zoomAndPanListener; + + public boolean autoRepaint = true; + + /** + * Cree et affiche une nouvelle fenetre de dessin. + */ + public DrawingVisible (int largeur, int hauteur) { + super(); + + this.zoomAndPanListener = new ZoomAndPanListener(this, 0, ZoomAndPanListener.DEFAULT_MAX_ZOOM_LEVEL, 1.2); + this.addMouseListener(zoomAndPanListener); + this.addMouseMotionListener(zoomAndPanListener); + this.addMouseWheelListener(zoomAndPanListener); + + BufferedImage img = new BufferedImage (largeur, hauteur, BufferedImage.TYPE_3BYTE_BGR); + + this.image = img; + this.gr = img.createGraphics(); + + this.zoomAndPanListener.setCoordTransform(this.gr.getTransform()); + + this.bb_is_set = false; + + this.width = largeur; + this.height = hauteur; + + this.long1 = (float)0.0; + this.long2 = (float)largeur; + this.lat1 = (float)0.0; + this.lat2 = (float)hauteur; + + this.setColor(Color.white); + gr.fillRect(0,0, largeur, hauteur); + this.repaint(); + + } + + @Override + public void paint(Graphics g1) { + Graphics2D g = (Graphics2D)g1; + g.setTransform(zoomAndPanListener.getCoordTransform()); + g.drawImage(image, 0, 0, this); + } + + + @Override + public Dimension getPreferredSize() { + Dimension size = new Dimension(0, 0); + + if (image != null) { + int w = image.getWidth(null); + int h = image.getHeight(null); + size = new Dimension(w > 0 ? w : 0, h > 0 ? h : 0); + } + return size; + } + + @Override + public void setAutoRepaint(boolean autoRepaint) { + this.autoRepaint = autoRepaint; + } + + protected void doAutoPaint() { + if (autoRepaint) { + this.repaint(); + } + } + + public void setWidth (int width) { + this.gr.setStroke(new BasicStroke(width)); + } + + public void setColor (Color col) { + this.gr.setColor (col); + } + + public void setBB (double long1, double long2, double lat1, double lat2) { + + if (long1 > long2 || lat1 > lat2) { + throw new Error("DessinVisible.setBB : mauvaises coordonnees."); + } + + /* Adapte la BB en fonction de la taille du dessin, pour préserver le ratio largeur/hauteur */ + double deltalong = long2 - long1 ; + double deltalat = lat2 - lat1 ; + double ratiobb = deltalong / deltalat ; + double ratiogr = width / height ; + + /* On ne peut qu'agrandir la BB, pour ne rien perdre. + * Si le ratiobb est trop petit, il faut agrandir deltalong + * s'il est trop grand, il faut agrandir deltalat. */ + if (ratiobb < ratiogr) { + /* De combien faut-il agrandir ? */ + double delta = (ratiogr - ratiobb) * deltalat ; + + this.long1 = (float)(long1 - 0.5*delta) ; + this.long2 = (float)(long2 + 0.5*delta) ; + this.lat1 = (float)lat1 ; + this.lat2 = (float)lat2 ; + } + else { + double delta = (deltalong / ratiogr) - deltalat ; + + this.long1 = (float)long1 ; + this.long2 = (float)long2 ; + this.lat1 = (float)(lat1 - 0.5*delta); + this.lat2 = (float)(lat2 + 0.5*delta); + } + + this.bb_is_set = true ; + } + + private int projx(float lon) { + return (int)(width * (lon - this.long1) / (this.long2 - this.long1)) ; + } + + private int projy(float lat) { + return (int)(height * (1 - (lat - this.lat1) / (this.lat2 - this.lat1))) ; + } + + private void checkBB() { + if (!this.bb_is_set) { + throw new Error("Classe DessinVisible : vous devez invoquer la methode setBB avant de dessiner.") ; + } + } + + public void drawLine (float long1, float lat1, float long2, float lat2) { + this.checkBB() ; + int x1 = this.projx(long1) ; + int x2 = this.projx(long2) ; + int y1 = this.projy(lat1) ; + int y2 = this.projy(lat2) ; + + gr.drawLine(x1, y1, x2, y2) ; + this.doAutoPaint(); + } + + public void drawPoint (float lon, float lat, int width) { + this.checkBB() ; + int x = this.projx(lon) - width / 2 ; + int y = this.projy(lat) - width / 2 ; + gr.fillOval (x, y, width, width) ; + this.doAutoPaint(); + } + + public void putText (float lon, float lat, String txt) { + this.checkBB() ; + int x = this.projx(lon) ; + int y = this.projy(lat) ; + gr.drawString (txt, x, y) ; + this.doAutoPaint(); + } + +} diff --git a/src/main/org/insa/drawing/ZoomAndPanListener.java b/src/main/org/insa/drawing/ZoomAndPanListener.java new file mode 100644 index 0000000..9827265 --- /dev/null +++ b/src/main/org/insa/drawing/ZoomAndPanListener.java @@ -0,0 +1,135 @@ +package org.insa.drawing; +import java.awt.*; +import java.awt.event.*; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; + +public class ZoomAndPanListener implements MouseListener, MouseMotionListener, MouseWheelListener { + public static final int DEFAULT_MIN_ZOOM_LEVEL = -20; + public static final int DEFAULT_MAX_ZOOM_LEVEL = 10; + public static final double DEFAULT_ZOOM_MULTIPLICATION_FACTOR = 1.2; + + private Component targetComponent; + + private int zoomLevel = 0; + private int minZoomLevel = DEFAULT_MIN_ZOOM_LEVEL; + private int maxZoomLevel = DEFAULT_MAX_ZOOM_LEVEL; + private double zoomMultiplicationFactor = DEFAULT_ZOOM_MULTIPLICATION_FACTOR; + + private Point dragStartScreen; + private Point dragEndScreen; + private AffineTransform coordTransform = new AffineTransform(); + + public ZoomAndPanListener(Component targetComponent) { + this.targetComponent = targetComponent; + } + + public ZoomAndPanListener(Component targetComponent, int minZoomLevel, int maxZoomLevel, double zoomMultiplicationFactor) { + this.targetComponent = targetComponent; + this.minZoomLevel = minZoomLevel; + this.maxZoomLevel = maxZoomLevel; + this.zoomMultiplicationFactor = zoomMultiplicationFactor; + } + + + public void mouseClicked(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + dragStartScreen = e.getPoint(); + dragEndScreen = null; + } + + public void mouseReleased(MouseEvent e) { +// moveCamera(e); + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mouseMoved(MouseEvent e) { + } + + public void mouseDragged(MouseEvent e) { + moveCamera(e); + } + + public void mouseWheelMoved(MouseWheelEvent e) { + zoomCamera(e); + } + + private void moveCamera(MouseEvent e) { + try { + dragEndScreen = e.getPoint(); + Point2D.Float dragStart = transformPoint(dragStartScreen); + Point2D.Float dragEnd = transformPoint(dragEndScreen); + double dx = dragEnd.getX() - dragStart.getX(); + double dy = dragEnd.getY() - dragStart.getY(); + coordTransform.translate(dx, dy); + dragStartScreen = dragEndScreen; + dragEndScreen = null; + targetComponent.repaint(); + } catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } + + private void zoomCamera(MouseWheelEvent e) { + try { + int wheelRotation = e.getWheelRotation(); + Point p = e.getPoint(); + if (wheelRotation > 0) { + if (zoomLevel < maxZoomLevel) { + zoomLevel++; + Point2D p1 = transformPoint(p); + coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + } else { + if (zoomLevel > minZoomLevel) { + zoomLevel--; + Point2D p1 = transformPoint(p); + coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor); + Point2D p2 = transformPoint(p); + coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY()); + targetComponent.repaint(); + } + } + } catch (NoninvertibleTransformException ex) { + ex.printStackTrace(); + } + } + + private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException { + + AffineTransform inverse = coordTransform.createInverse(); + + Point2D.Float p2 = new Point2D.Float(); + inverse.transform(p1, p2); + return p2; + } + + public int getZoomLevel() { + return zoomLevel; + } + + + public void setZoomLevel(int zoomLevel) { + this.zoomLevel = zoomLevel; + } + + + public AffineTransform getCoordTransform() { + return coordTransform; + } + + public void setCoordTransform(AffineTransform coordTransform) { + this.coordTransform = coordTransform; + } +} \ No newline at end of file diff --git a/src/main/org/insa/graph/Arc.java b/src/main/org/insa/graph/Arc.java new file mode 100644 index 0000000..197db53 --- /dev/null +++ b/src/main/org/insa/graph/Arc.java @@ -0,0 +1,81 @@ +package org.insa.graph; + +import java.util.ArrayList; + +public class Arc { + + // Destination node. + private Node dest; + + // Length of the road (in meters). + private int length; + + // Road information. + RoadInformation info; + + // Segments. + ArrayList points; + + /** + * @param dest + * @param length + * @param roadInformation + * @param points + */ + public Arc(Node dest, int length, RoadInformation roadInformation) { + this.dest = dest; + this.length = length; + this.info = roadInformation; + this.points = new ArrayList(); + } + + /** + * @param dest + * @param length + * @param roadInformation + * @param points + */ + public Arc(Node dest, int length, RoadInformation roadInformation, ArrayList points) { + this.dest = dest; + this.length = length; + this.info = roadInformation; + this.points = points; + } + + /** + * @return Destination node of this arc. + */ + public Node getDest() { + return dest; + } + + /** + * @return Length of this arc, in meters. + */ + public int getLength() { + return length; + } + + /** + * @return Minimum time required to travel this arc, in seconds. + */ + public float getMinimumTravelTime() { + return getLength() * 3600f / (info.getMaximumSpeed() * 1000f); + } + + /** + * @return Road information for this arc. + */ + public RoadInformation getInfo() { + return info; + } + + /** + * @return Points representing segments of this arc. This function may return an empty + * ArrayList if the segments are stored in the reversed arc (for two-ways road). + */ + public ArrayList getPoints() { + return points; + } + +} diff --git a/src/main/org/insa/graph/Graph.java b/src/main/org/insa/graph/Graph.java new file mode 100644 index 0000000..b95a9a8 --- /dev/null +++ b/src/main/org/insa/graph/Graph.java @@ -0,0 +1,28 @@ +package org.insa.graph ; + +import java.util.ArrayList; + +public class Graph { + + // Map identifier. + private int mapId; + + // Nodes of the graph. + private ArrayList nodes; + + public Graph(int mapId, ArrayList nodes) { + this.mapId = mapId; + this.nodes = nodes; + } + + /** + * @return Nodes of this graph. + */ + public ArrayList getNodes() { return nodes; } + + /** + * @return Map ID of this graph. + */ + public int getMapId() { return mapId; } + +} diff --git a/src/main/org/insa/graph/Node.java b/src/main/org/insa/graph/Node.java new file mode 100644 index 0000000..07a4eb0 --- /dev/null +++ b/src/main/org/insa/graph/Node.java @@ -0,0 +1,52 @@ +package org.insa.graph; + +import java.util.ArrayList; + +public class Node { + + // ID of the node. + private int id; + + // Point of this graph. + private Point point; + + // Successors. + private ArrayList successors; + + /** + * Create a new Node corresponding to the given Point with + * an empty list of successors. + * + * @param point + */ + public Node(int id, Point point) { + this.id = id; + this.point = point; + this.successors = new ArrayList(); + } + + /** + * Add a successor to this node. + * + * @param arc Arc to the successor. + */ + public void addSuccessor(Arc arc) { + successors.add(arc); + } + + /** + * @return ID of this node. + */ + public int getId() { return id; } + + /** + * @return List of successors of this node. + */ + public ArrayList getSuccessors() { return successors; } + + /** + * @return Point of this node. + */ + public Point getPoint() { return point; } + +} diff --git a/src/main/org/insa/graph/Point.java b/src/main/org/insa/graph/Point.java new file mode 100644 index 0000000..162c147 --- /dev/null +++ b/src/main/org/insa/graph/Point.java @@ -0,0 +1,62 @@ +package org.insa.graph; + +/** + * Class representing a point on Earth. + * + */ +public class Point { + + // Earth radius, in meters; + private static final double EARTH_RADIUS = 6378137.0 ; + + /** + * Compute the distance between the two given points. + * + * @param long1 + * @param lat1 + * @param long2 + * @param lat2 + * @return + */ + public static double distance(Point p1, Point p2) { + double sinLat = Math.sin(Math.toRadians(p1.getLatitude()))*Math.sin(Math.toRadians(p2.getLatitude())); + double cosLat = Math.cos(Math.toRadians(p1.getLatitude()))*Math.cos(Math.toRadians(p2.getLatitude())); + double cosLong = Math.cos(Math.toRadians(p2.getLongitude() - p1.getLongitude())); + return EARTH_RADIUS * Math.acos(sinLat+cosLat*cosLong); + } + + // Longitude and latitude of the point. + private float longitude, latitude; + + /** + * + * @param longitude Longitude of the point, in degrees. + * @param latitude Latitude of the point, in degrees. + */ + public Point(float longitude, float latitude) { + this.longitude = longitude; + this.latitude = latitude; + } + + /** + * @return Longitude of this point (in degrees). + */ + public float getLongitude() { return longitude; } + + /** + * @return Latitude of this point (in degrees). + */ + public float getLatitude() { return latitude; } + + /** + * Compute the distance from this point to the given point + * + * @param target Target point. + * + * @return Distane between this point and the target point, in meters. + */ + public double distanceTo(Point target) { + return distance(this, target); + } + +} diff --git a/src/main/org/insa/graph/RoadInformation.java b/src/main/org/insa/graph/RoadInformation.java new file mode 100644 index 0000000..536bd68 --- /dev/null +++ b/src/main/org/insa/graph/RoadInformation.java @@ -0,0 +1,85 @@ +package org.insa.graph ; + +/** + * Class containing information for road that may be shared + * by multiple arcs. + * + */ +public class RoadInformation { + + /** + * Road type. + */ + public enum RoadType { + MOTORWAY, + TRUNK, + PRIMARY, + SECONDARY, + MOTORWAY_LINK, + TRUNK_LINK, + PRIMARY_LINK, + SECONDARY_LINK, + TERTIARY, + RESIDENTIAL, + UNCLASSIFIED, + ROAD, + LIVING_STREET, + SERVICE, + ROUNDABOUT, + COASTLINE + } + + // Type of the road (see above). + private RoadType type; + + // One way road? + private boolean oneway; + + // Max speed in kilometers per hour. + private int maxSpeed; + + // Name of the road. + private String name; + + public RoadInformation(RoadType roadType, boolean isOneWay, int maxSpeed, String name) { + this.type = roadType; + this.oneway = isOneWay; + this.maxSpeed = maxSpeed; + this.name = name; + } + + /** + * @return Type of the road. + */ + public RoadType getType() { return type; } + + /** + * @return true if this is a one-way road. + */ + public boolean isOneWay() { return oneway; } + + /** + * @return Maximum speed for this road (in km/h). + */ + public int getMaximumSpeed() { return maxSpeed; } + + /** + * @return Name of the road. + */ + public String getName() { return name; } + + @Override + public String toString() { + String typeAsString = "road"; + if (getType() == RoadType.COASTLINE) { + typeAsString = "coast"; + } + if (getType() == RoadType.MOTORWAY) { + typeAsString = "highway"; + } + return typeAsString + " : " + getName() + + " " + (isOneWay() ? " (oneway) " : "") + + maxSpeed + " km/h (max.)"; + } + +} diff --git a/src/main/org/insa/graph/io/AbstractGraphReader.java b/src/main/org/insa/graph/io/AbstractGraphReader.java new file mode 100644 index 0000000..5fc4b6f --- /dev/null +++ b/src/main/org/insa/graph/io/AbstractGraphReader.java @@ -0,0 +1,16 @@ +package org.insa.graph.io; + +import org.insa.graph.Graph; + +public interface AbstractGraphReader { + + /** + * Read a graph an returns it. + * + * @return Graph. + * @throws Exception + * + */ + public Graph read() throws Exception; + +} diff --git a/src/main/org/insa/graph/io/AbstractPathReader.java b/src/main/org/insa/graph/io/AbstractPathReader.java new file mode 100644 index 0000000..296e5d4 --- /dev/null +++ b/src/main/org/insa/graph/io/AbstractPathReader.java @@ -0,0 +1,18 @@ +package org.insa.graph.io; + +import org.insa.graph.Graph; +import org.insa.graph.Path; + +public interface AbstractPathReader { + + /** + * Read a path of the given graph and returns it. + * + * @param graph Graph of the path. + * + * @return A new path. + * @throws Exception + */ + public Path readPath(Graph graph) throws Exception; + +} diff --git a/src/main/org/insa/graph/io/BadFormatException.java b/src/main/org/insa/graph/io/BadFormatException.java new file mode 100644 index 0000000..da90f55 --- /dev/null +++ b/src/main/org/insa/graph/io/BadFormatException.java @@ -0,0 +1,21 @@ +package org.insa.graph.io; + +import java.io.IOException; + +public class BadFormatException extends IOException { + + /** + * + */ + private static final long serialVersionUID = -5455552814725826052L; + + /** + * + * @param actualVersion + * @param expectedVersion + */ + public BadFormatException() { + super(); + } + +} diff --git a/src/main/org/insa/graph/io/BadMagicNumberException.java b/src/main/org/insa/graph/io/BadMagicNumberException.java new file mode 100644 index 0000000..e3b81f3 --- /dev/null +++ b/src/main/org/insa/graph/io/BadMagicNumberException.java @@ -0,0 +1,36 @@ +package org.insa.graph.io; + +import java.io.IOException; + +public class BadMagicNumberException extends IOException { + + /** + * + */ + private static final long serialVersionUID = -2176603967548838864L; + + // Actual and expected magic numbers. + private int actualNumber, expectedNumber; + + /** + * + * @param actualVersion + * @param expectedVersion + */ + public BadMagicNumberException(int actualNumber, int expectedNumber) { + super(); + this.actualNumber = actualNumber; + this.expectedNumber = expectedNumber; + } + + /** + * + */ + public int getActualMagicNumber() { return actualNumber; } + + /** + * + */ + public int getExpectedMagicNumber() { return expectedNumber; } + +} diff --git a/src/main/org/insa/graph/io/BadVersionException.java b/src/main/org/insa/graph/io/BadVersionException.java new file mode 100644 index 0000000..df9d99e --- /dev/null +++ b/src/main/org/insa/graph/io/BadVersionException.java @@ -0,0 +1,35 @@ +package org.insa.graph.io; + +import java.io.IOException; + +public class BadVersionException extends IOException { + + /** + * + */ + private static final long serialVersionUID = 7776317018302386042L; + + // Actual and expected version.. + private int actualVersion, expectedVersion; + + /** + * + * @param actualVersion + * @param expectedVersion + */ + public BadVersionException(int actualVersion, int expectedVersion) { + super(); + this.actualVersion = actualVersion; + this.expectedVersion = expectedVersion; + } + + /** + * + */ + public int getActualVersion() { return actualVersion; } + + /** + * + */ + public int getExpectedVersion() { return expectedVersion; } +} diff --git a/src/main/org/insa/graph/io/BinaryGraphReader.java b/src/main/org/insa/graph/io/BinaryGraphReader.java new file mode 100644 index 0000000..e328344 --- /dev/null +++ b/src/main/org/insa/graph/io/BinaryGraphReader.java @@ -0,0 +1,176 @@ +package org.insa.graph.io; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; + +import org.insa.graph.Arc; +import org.insa.graph.Graph; +import org.insa.graph.Node; +import org.insa.graph.Point; +import org.insa.graph.RoadInformation; +import org.insa.graph.RoadInformation.RoadType; + +public class BinaryGraphReader extends BinaryReader implements AbstractGraphReader { + + // Map version and magic number targeted for this reader. + private static final int VERSION = 4; + private static final int MAGIC_NUMBER = 0xbacaff; + + /** + * Convert a character to its corresponding road type. + * + * @param ch Character to convert. + * + * @return Road type corresponding to ch. + * + * @see http://wiki.openstreetmap.org/wiki/Highway_tag_usage. + */ + public static RoadType toRoadType(char ch) { + switch (ch) { + case 'a': return RoadType.MOTORWAY; + case 'b': return RoadType.TRUNK; + case 'c': return RoadType.PRIMARY; + case 'd': return RoadType.SECONDARY; + case 'e': return RoadType.MOTORWAY_LINK; + case 'f': return RoadType.TRUNK_LINK; + case 'g': return RoadType.PRIMARY_LINK; + case 'h': return RoadType.SECONDARY_LINK; + case 'i': return RoadType.TERTIARY; + case 'j': return RoadType.RESIDENTIAL; + case 'k': return RoadType.UNCLASSIFIED; + case 'l': return RoadType.ROAD; + case 'm': return RoadType.LIVING_STREET; + case 'n': return RoadType.SERVICE; + case 'o': return RoadType.ROUNDABOUT; + case 'z': return RoadType.COASTLINE; + } + return RoadType.UNCLASSIFIED; + } + + /** + * Create a new BinaryGraphReader using the given DataInputStream. + * + * @param dis + */ + public BinaryGraphReader(DataInputStream dis) { + super(MAGIC_NUMBER, VERSION, dis); + } + + @Override + public Graph read() throws IOException { + + // Read and check magic number and file version. + checkMagicNumberOrThrow(dis.readInt()); + checkVersionOrThrow(dis.readInt()); + + // Read map id. + int mapId = dis.readInt(); + + // Read zone. + int graphZone = dis.readInt(); + + // Number of descriptors and nodes. + int nbDesc = dis.readInt(); + int nbNodes = dis.readInt(); + + // Number of successors for each nodes. + int[] nbSuccessors = new int[nbNodes]; + + // Construct an array list with initial capacity of nbNodes. + ArrayList nodes = new ArrayList(nbNodes); + + // Read nodes. + for (int node = 0; node < nbNodes; ++node) { + float longitude = ((float)dis.readInt ()) / 1E6f; + float latitude = ((float)dis.readInt ()) / 1E6f; + nbSuccessors[node] = dis.readUnsignedByte(); + nodes.add(new Node(node, new Point(longitude, latitude))); + } + + // Check format. + checkByteOrThrow(255); + + // Read descriptors. + RoadInformation[] descs = new RoadInformation[nbDesc]; + + // Read + for (int descr = 0; descr < nbDesc; ++descr) { + descs[descr] = readRoadInformation(); + } + + // Check format. + checkByteOrThrow(254); + + // Read successors and convert to arcs. + for (int node = 0; node < nbNodes; ++node) { + for (int succ = 0; succ < nbSuccessors[node]; ++succ) { + + // Read destination zone. + int destZone = dis.readUnsignedByte(); + + // Read target node number. + int destNode = this.read24bits(); + + // Read information number. + int descrNum = this.read24bits(); + + // Length of the arc. + int length = dis.readUnsignedShort(); + + // Number of segments. + int nbSegments = dis.readUnsignedShort(); + + // Chain of points corresponding to the segments. + ArrayList points = new ArrayList(nbSegments + 2); + points.add(nodes.get(node).getPoint()); + + for (int seg = 0; seg < nbSegments; ++seg) { + Point lastPoint = points.get(points.size() - 1); + + float dlon = (dis.readShort()) / 2.0e5f; + float dlat = (dis.readShort()) / 2.0e5f; + + points.add(new Point(lastPoint.getLongitude() + dlon, + lastPoint.getLatitude() + dlat)); + } + + points.add(nodes.get(destNode).getPoint()); + + if (graphZone == destZone) { + + RoadInformation info = descs[descrNum]; + Node orig = nodes.get(node); + Node dest = nodes.get(destNode); + + // Add successor to initial arc. + orig.addSuccessor(new Arc(dest, length, info, points)); + + // And reverse arc if its a two-way road. + if (!info.isOneWay()) { + // Add without segments. + dest.addSuccessor(new Arc(orig, length, info)); + } + + } + } + } + + // Check format. + checkByteOrThrow(253); + + return new Graph(mapId, nodes); + } + + /** + * Read the next road information from the stream. + * + * @throws IOException + */ + private RoadInformation readRoadInformation() throws IOException { + char type = (char)dis.readUnsignedByte(); + int x = dis.readUnsignedByte() ; + return new RoadInformation(toRoadType(type), (x & 0x80) > 0, (x & 0x7F) * 5, dis.readUTF()); + } + +} diff --git a/src/main/org/insa/graph/io/BinaryPathReader.java b/src/main/org/insa/graph/io/BinaryPathReader.java new file mode 100644 index 0000000..735092c --- /dev/null +++ b/src/main/org/insa/graph/io/BinaryPathReader.java @@ -0,0 +1,70 @@ +package org.insa.graph.io; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; + +import org.insa.graph.Graph; +import org.insa.graph.Node; +import org.insa.graph.Path; + +public class BinaryPathReader extends BinaryReader implements AbstractPathReader { + + // Map version and magic number targeted for this reader. + private static final int VERSION = 1; + private static final int MAGIC_NUMBER = 0xdecafe; + + public BinaryPathReader(DataInputStream dis) { + super(MAGIC_NUMBER, VERSION, dis); + } + + @Override + public Path readPath(Graph graph) throws Exception { + + // Read and check magic number and version. + checkMagicNumberOrThrow(dis.readInt()); + checkVersionOrThrow(dis.readInt()); + + // Read map ID and check against graph. + int mapId = dis.readInt(); + + if (mapId != graph.getMapId()) { + throw new MapMismatchException(mapId, graph.getMapId()); + } + + // Number of nodes in the path (without first and last). + int nbNodes = dis.readInt(); + + ArrayList nodes = new ArrayList(nbNodes + 2); + + // Read first node + nodes.add(readNode(graph)); + + // Read last node + Node lastNode = readNode(graph); + + // Read intermediate nodes: + for (int node = 0; node < nbNodes; ++node) { + nodes.add(readNode(graph)); + } + + // Add last node + nodes.add(lastNode); + + return new Path(graph, nodes); + } + + /** + * Read a node from the stream and returns id. + * + * @return + * @throws IOException + */ + protected Node readNode(Graph graph) throws IOException { + // Discard zone. + dis.readUnsignedByte(); + + return graph.getNodes().get(read24bits()); + } + +} diff --git a/src/main/org/insa/graph/io/BinaryReader.java b/src/main/org/insa/graph/io/BinaryReader.java new file mode 100644 index 0000000..33a7400 --- /dev/null +++ b/src/main/org/insa/graph/io/BinaryReader.java @@ -0,0 +1,67 @@ +package org.insa.graph.io; + +import java.io.DataInputStream; +import java.io.IOException; + +public abstract class BinaryReader { + + // Map version and magic number targeted for this reader. + private int version; + private int magicNumber; + + // InputStream + protected DataInputStream dis; + + protected BinaryReader(int magicNumber, int version, DataInputStream dis) { + this.magicNumber = magicNumber; + this.version = version; + this.dis = dis; + } + + /** + * @param version + * @throws BadVersionException + */ + public void checkVersionOrThrow(int version) throws BadVersionException { + if (this.version != version) { + throw new BadVersionException(version, this.version); + } + } + + /** + * @param magicNumber + * @throws BadMagicNumberException + */ + public void checkMagicNumberOrThrow(int magicNumber) throws BadMagicNumberException { + if (this.magicNumber != magicNumber) { + throw new BadMagicNumberException(magicNumber, this.magicNumber); + } + } + + /** + * Check if the next byte in the input stream correspond to the + * given byte. This function consumes the next byte in the input + * stream. + * + * @param i Byte to check against. + * + * @throws IOException + */ + public void checkByteOrThrow(int i) throws IOException { + if (dis.readUnsignedByte() != i) { + throw new BadFormatException(); + } + } + + /** + * Read 24 bits from the stream and return the corresponding integer value. + * + * @return Integer value read from the next 24 bits of the stream. + * + * @throws IOException + */ + protected int read24bits() throws IOException { + int x = dis.readUnsignedShort() ; + return (x << 8) | dis.readUnsignedByte() ; + } +} diff --git a/src/main/org/insa/graph/io/MapMismatchException.java b/src/main/org/insa/graph/io/MapMismatchException.java new file mode 100644 index 0000000..10b0fd2 --- /dev/null +++ b/src/main/org/insa/graph/io/MapMismatchException.java @@ -0,0 +1,36 @@ +package org.insa.graph.io; + +public class MapMismatchException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 3076730078387819138L; + // Actual and expected magic numbers. + private int actualMapId, expectedMapId; + + /** + * + * @param actualVersion + * @param expectedVersion + */ + public MapMismatchException(int actualMapId, int expectedMapId) { + super(); + this.actualMapId = actualMapId; + this.expectedMapId = expectedMapId; + } + + /** + * @return + */ + public int getActualMapId() { + return actualMapId; + } + + /** + * @return + */ + public int getExpectedMapId() { + return expectedMapId; + } +} diff --git a/src/main/org/insa/utility/BinaryHeap.java b/src/main/org/insa/utility/BinaryHeap.java new file mode 100644 index 0000000..3e8a6ed --- /dev/null +++ b/src/main/org/insa/utility/BinaryHeap.java @@ -0,0 +1,252 @@ +// +// ******************PUBLIC OPERATIONS********************* +// void insert( x ) --> Insert x +// Comparable deleteMin( )--> Return and remove smallest item +// Comparable findMin( ) --> Return smallest item +// boolean isEmpty( ) --> Return true if empty; else false +// ******************ERRORS******************************** +// Throws RuntimeException for findMin and deleteMin when empty + +package org.insa.utility; + +import java.util.* ; + +/** + * Implements a binary heap. + * Note that all "matching" is based on the compareTo method. + * @author Mark Allen Weiss + * @author DLB + */ +public class BinaryHeap> { + + private int currentSize; // Number of elements in heap + + // Java genericity does not work with arrays. + // We have to use an ArrayList + private ArrayList array; // The heap array + + /** + * Construct the binary heap. + */ + public BinaryHeap() { + this.currentSize = 0; + this.array = new ArrayList() ; + } + + // Constructor used for debug. + public BinaryHeap(BinaryHeap heap) { + this.currentSize = heap.currentSize ; + this.array = new ArrayList(heap.array) ; + } + + // Sets an element in the array + private void arraySet(int index, E value) { + if (index == this.array.size()) { + this.array.add(value) ; + } + else { + this.array.set(index, value) ; + } + } + + /** + * Test if the heap is logically empty. + * @return true if empty, false otherwise. + */ + public boolean isEmpty() { return this.currentSize == 0; } + + /** + * Returns size. + * @return current size. + */ + public int size() { return this.currentSize; } + + + /** + * Returns index of parent. + */ + private int index_parent(int index) { + return (index - 1) / 2 ; + } + + /** + * Returns index of left child. + */ + private int index_left(int index) { + return index * 2 + 1 ; + } + + /** + * Insert into the heap. + * @param x the item to insert. + */ + public void insert(E x) { + int index = this.currentSize++ ; + this.arraySet(index, x) ; + this.percolateUp(index) ; + } + + /** + * Internal method to percolate up in the heap. + * @param index the index at which the percolate begins. + */ + private void percolateUp(int index) { + E x = this.array.get(index) ; + + for( ; index > 0 && x.compareTo(this.array.get(index_parent(index)) ) < 0; index = index_parent(index) ) { + E moving_val = this.array.get(index_parent(index)) ; + this.arraySet(index, moving_val) ; + } + + this.arraySet(index, x) ; + } + + /** + * Internal method to percolate down in the heap. + * @param index the index at which the percolate begins. + */ + private void percolateDown(int index) { + int ileft = index_left(index) ; + int iright = ileft + 1 ; + + if (ileft < this.currentSize) { + E current = this.array.get(index) ; + E left = this.array.get(ileft) ; + boolean hasRight = iright < this.currentSize ; + E right = (hasRight)?this.array.get(iright):null ; + + if (!hasRight || left.compareTo(right) < 0) { + // Left is smaller + if (left.compareTo(current) < 0) { + this.arraySet(index, left) ; + this.arraySet(ileft, current) ; + this.percolateDown( ileft ) ; + } + } + else { + // Right is smaller + if (right.compareTo(current) < 0) { + this.arraySet(index, right) ; + this.arraySet(iright, current) ; + this.percolateDown( iright ) ; + } + } + } + } + + /** + * Find the smallest item in the heap. + * @return the smallest item. + * @throws Exception if empty. + */ + public E findMin( ) { + if( isEmpty() ) + throw new RuntimeException( "Empty binary heap" ); + return this.array.get(0); + } + + /** + * Remove the smallest item from the heap. + * @return the smallest item. + * @throws Exception if empty. + */ + public E deleteMin( ) { + E minItem = findMin( ); + E lastItem = this.array.get(--this.currentSize) ; + this.arraySet(0, lastItem) ; + this.percolateDown( 0 ); + return minItem; + } + + /** + * Prints the heap + */ + public void print() { + System.out.println() ; + System.out.println("======== HEAP (size = " + this.currentSize + ") ========") ; + System.out.println() ; + + for (int i = 0 ; i < this.currentSize ; i++) { + System.out.println(this.array.get(i).toString()) ; + } + + System.out.println() ; + System.out.println("-------- End of heap --------") ; + System.out.println() ; + } + + /** + * Prints the elements of the heap according to their respective order. + */ + public void printSorted() { + + BinaryHeap copy = new BinaryHeap(this) ; + + System.out.println() ; + System.out.println("======== Sorted HEAP (size = " + this.currentSize + ") ========") ; + System.out.println() ; + + while (!copy.isEmpty()) { + System.out.println(copy.deleteMin()) ; + } + + System.out.println() ; + System.out.println("-------- End of heap --------") ; + System.out.println() ; + } + + + + // Test program : compare with the reference implementation PriorityQueue. + public static void main(String [] args) { + BinaryHeap heap = new BinaryHeap() ; + PriorityQueue queue = new PriorityQueue() ; + + int count = 0 ; + int blocksize = 10000 ; + + System.out.println("Interrupt to stop the test.") ; + + while (true) { + + // Insert up to blocksize elements + int nb_insert = (int)(Math.random() * (blocksize + 1)) ; + + for (int i = 0 ; i < nb_insert ; i++) { + Integer obj = new Integer(i) ; + heap.insert(obj) ; + queue.add(obj) ; + } + + // Remove up to blocksize elements + int nb_remove = (int)(Math.random() * blocksize * 1.1) ; + + if (nb_remove > queue.size()) { + nb_remove = queue.size() ; + } + + for (int i = 0 ; i < nb_remove ; i++) { + + int removed1 = queue.poll().intValue() ; + int removed2 = heap.deleteMin().intValue() ; + + if (removed1 != removed2) { + System.out.println("Ouch : expected " + removed1 + " .. but got " + removed2) ; + System.exit(1) ; + } + } + + if (heap.size() != queue.size()) { + System.out.println("Ouch : heap size = " + heap.size() + " queue size = " + queue.size() ) ; + System.exit(1) ; + } + + count += nb_remove ; + + if (count > 1000000) { + System.out.println("" + count + " items successfully compared. Heap size : " + heap.size()) ; + count = 0 ; + } + } + } +} diff --git a/src/test/org/insa/utility/BinaryHeapTest.java b/src/test/org/insa/utility/BinaryHeapTest.java new file mode 100644 index 0000000..88abc19 --- /dev/null +++ b/src/test/org/insa/utility/BinaryHeapTest.java @@ -0,0 +1,68 @@ +package org.insa.utility; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class BinaryHeapTest { + + private BinaryHeap rangeHeap1; + + static IntStream dataRange1() { + return IntStream.range(0, 20); + } + + @BeforeAll + static void initAll() { + + } + + @BeforeEach + void init() { + // Create the range heap + this.rangeHeap1 = new BinaryHeap(); + dataRange1().forEach((int x) -> rangeHeap1.insert(x)); + } + + @Test + void testInsert() { + BinaryHeap heap = new BinaryHeap(); + int size = 0; + for (int x: dataRange1().toArray()) { + heap.insert(x); + size += 1; + assertEquals(heap.size(), size); + } + } + + @Test + void testDeleteMin() { + int[] range1 = dataRange1().toArray(); + int size = range1.length; + assertEquals(rangeHeap1.size(), size); + for (int x: range1) { + assertEquals(rangeHeap1.deleteMin().intValue(), x); + size -= 1; + assertEquals(rangeHeap1.size(), size); + } + } + + @AfterEach + void tearDown() { + } + + @AfterAll + static void tearDownAll() { + } + +}