Index: test/uk/me/parabola/imgfmt/app/net/EqualityTestCase.java
===================================================================
--- test/uk/me/parabola/imgfmt/app/net/EqualityTestCase.java	(revision 0)
+++ test/uk/me/parabola/imgfmt/app/net/EqualityTestCase.java	(working copy)
@@ -0,0 +1,255 @@
+/* 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+package uk.me.parabola.imgfmt.app.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+ 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+ 
+import org.junit.Test;
+ 
+/**
+ * This base class is for testing that an object properly overrides {@link Object#equals} and {@link Object#hashCode}.
+ * 
+ * <p>
+ * Here are the important contract requirements for the two methods, as documented in the javadoc documentation for
+ * <code>java.lang.Object</code>:
+ * </p>
+ * <ol>
+ * 
+ * <li>The <code>hashCode</code> method must return the same integer value every time it is invoked on the same object
+ * during the entire execution of a Java application or applet. It need not return the same value for different runs of
+ * an application or applet. The Java 2 platform (Java 2) documentation further allows the <code>hashCode</code> value
+ * to change if the information used in the <code>equals</code> method changes.<br>
+ * <br>
+ * </li>
+ * <li>If two objects are equal according to the <code>equals</code> method, they must return the same value from
+ * <code>hashCode</code>.<br>
+ * <br>
+ * </li>
+ * 
+ * <li>The <code>equals</code> method is reflexive, which means that an object is equal to itself:
+ * <code>x.equals(x)</code> should return true.<br>
+ * <br>
+ * </li>
+ * <li>The <code>equals</code> method is symmetric: If <code>x.equals(y)</code> returns true, then
+ * <code>y.equals(x)</code> should return true also.<br>
+ * <br>
+ * </li>
+ * 
+ * <li>The <code>equals</code> method is transitive: If <code>x.equals(y)</code> returns true and
+ * <code>y.equals(z)</code> returns true, then <code>x.equals(z)</code> should return true.<br>
+ * <br>
+ * </li>
+ * <li>The <code>equals</code> method is consistent. <code>x.equals(y)</code> should consistently return either true or
+ * false. The Java 2 javadoc clarifies that the result of <code>x.equals(y)</code> can change if the information used in
+ * the equals comparisons change.<br>
+ * <br>
+ * </li>
+ * 
+ * <li>Finally, <code>x.equals(null)</code> should return false.</li>
+ * </ol>
+ * 
+ * Additionally, if this class implements {@link Comparable}, {@link Serializable}, or {@link Cloneable}, then the
+ * object will be checked to see if these methods provide reasonable behavior.
+ * 
+ * You must implement two factory methods: getA, getB.
+ * 
+ * If the object under test implements Comparable then a < b (< c)
+ * 
+ * @author Brian Egge
+ */
+public abstract class EqualityTestCase<T> {
+ 
+    /**
+     * 
+     * @return a new instance of an Object which should equate to other objects created by getA, but be not equals to
+     *         objects created by getB
+     */
+    protected abstract T getA() throws Exception;
+ 
+    /**
+     * 
+     * @return a new instance of an Object which should equate to other objects created by getB, but be not equals to
+     *         objects created by getA
+     */
+    protected abstract T getB() throws Exception;
+ 
+    /**
+     * @return an object which is not equal to either A or B. This method can optionally be implemented.
+     */
+    protected T getC() throws Exception {
+        return null;
+    }
+ 
+    @Test
+    public void testEquals() throws Exception {
+        assertFalse(getA().equals(null));
+        assertFalse(getB().equals(null));
+        assertFalse(getA().equals(new Object()));
+        assertFalse(getB().equals(new Object()));
+        // The equals method is symmetric: If x.equals(y) returns true, then y.equals(x) should return true also.
+        assertTrue(getA().equals(getA()));
+        assertTrue(getB().equals(getB()));
+        assertFalse(getA().equals(getB()));
+        assertFalse(getB().equals(getA()));
+        if (getC() != null) {
+            assertFalse(getC().equals(null));
+            assertFalse(getC().equals(new Object()));
+            assertTrue(getC().equals(getC()));
+            assertFalse(getA().equals(getC()));
+            assertFalse(getC().equals(getA()));
+            assertFalse(getB().equals(getC()));
+            assertFalse(getC().equals(getB()));
+        }
+ 
+        for (Iterator<T> iterator = getObjects().iterator(); iterator.hasNext();) {
+            Object o = iterator.next();
+            // The equals method is reflexive, which means that an object is equal to itself: x.equals(x) should return
+            // true.
+            assertTrue(o.equals(o));
+            // x.equals(null) should return false.
+            assertFalse(o.equals(null));
+        }
+    }
+ 
+    @Test
+    public void testHashCode() throws Exception {
+        assertEquals("hashCodes differ for two A objects", getA().hashCode(), getA().hashCode());
+        assertEquals(getB().hashCode(), getB().hashCode());
+        assertFalse("hashCode is either not implmented correctly or is very bad\n"
+                + "Both A and B returned a hashCode of " + getA().hashCode(), getA().hashCode() == getB().hashCode());
+        if (getC() != null) {
+            assertEquals(getC().hashCode(), getC().hashCode());
+            assertFalse("hashCode is either not implmented correctly or is very bad\n"
+                    + "Both A and C returned a hashCode of " + getA().hashCode(), getA().hashCode() == getC()
+                    .hashCode());
+            assertFalse("hashCode is either not implmented correctly or is very bad\n"
+                    + "Both B and C returned a hashCode of " + getB().hashCode(), getB().hashCode() == getC()
+                    .hashCode());
+        }
+ 
+        Set<T> set = new HashSet<T>();
+        set.add(getA());
+ 
+        assertTrue(set.contains(getA()));
+        assertFalse(set.contains(getB()));
+        assertFalse(set.contains(getC()));
+    }
+ 
+    @Test
+    @SuppressWarnings("unchecked")
+    // Comparable<T> is checked only at runtime
+    public void testComparable() throws Exception {
+        if (getA() instanceof Comparable) {
+            assertEquals(0, ((Comparable<T>) getA()).compareTo(getA()));
+            assertEquals(0, ((Comparable<T>) getB()).compareTo(getB()));
+            assertEquals("Item B should compare equally with another item B", 0,
+                    ((Comparable<T>) getB()).compareTo(getB()));
+ 
+            assertTrue("Item B should be less than Item A", ((Comparable<T>) getA()).compareTo(getB()) < 0);
+            assertTrue(((Comparable<T>) getB()).compareTo(getA()) > 0);
+            if (getC() != null) {
+                assertEquals(0, ((Comparable<T>) getC()).compareTo(getC()));
+                assertTrue("Item C should be less than Item A", ((Comparable<T>) getA()).compareTo(getC()) < 0);
+                assertTrue("Item C should be less than Item B", ((Comparable<T>) getB()).compareTo(getC()) < 0);
+                assertTrue(((Comparable<T>) getC()).compareTo(getB()) > 0);
+            }
+        }
+    }
+ 
+    @Test
+    public void testSerializable() throws Exception {
+        if (getA() instanceof Serializable) {
+            for (T o : getObjects()) {
+                assertTrue(o.equals(serialize(o)));
+            }
+        }
+    }
+ 
+    private Object serialize(Object value) throws IOException, ClassNotFoundException {
+        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
+        ObjectOutputStream outputStream = new ObjectOutputStream(outputBuffer);
+        outputStream.writeObject(value);
+        outputStream.flush();
+        byte[] bytes = outputBuffer.toByteArray();
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
+        return objectInputStream.readObject();
+    }
+ 
+    /**
+     * If you must implement {@link java.lang.Cloneable}, please do it correctly! The clone interface doesn't require
+     * you to make 'clone' a public method, but this what one generally expects for an object which implements
+     * {@link java.lang.Cloneable}
+     * 
+     * @throws NoSuchMethodException
+     * @throws SecurityException
+     * @throws InvocationTargetException
+     * @throws IllegalAccessException
+     * @throws IllegalArgumentException
+     */
+    @Test
+    public void testClone() throws Exception, IllegalAccessException, InvocationTargetException {
+        if (getA() instanceof Cloneable) {
+            for (T o : getObjects()) {
+                Method clone = o.getClass().getMethod("clone", new Class[] {});
+                Object cloned = clone.invoke(o, new Object[] {});
+                assertNotSame(o, cloned);
+                assertEquals(o, cloned);
+                assertEquals(o.getClass(), cloned.getClass());
+            }
+        }
+    }
+ 
+    /**
+     * toString doesn't have to be implemented. There's not a lot of checks we can do to make sure this is a 'good'
+     * toString, other than to make sure the code works and doesn't return <code>null</code>.
+     */
+    @Test
+    public void testToString() throws Exception {
+        for (Iterator<T> iterator = getObjects().iterator(); iterator.hasNext();) {
+            Object o = iterator.next();
+            assertNotNull(o.toString());
+        }
+    }
+ 
+    /**
+     * This will either return a list of one or two objects depending on if getC() is implemented.
+     * 
+     * @return a list of test objects.
+     */
+    private List<T> getObjects() throws Exception {
+        List<T> list = new ArrayList<T>(3);
+        list.add(getA());
+        list.add(getB());
+        if (getC() != null) {
+            list.add(getC());
+        }
+        return list;
+    }
+}
\ No newline at end of file
Index: test/uk/me/parabola/imgfmt/app/net/RoadDefTest.java
===================================================================
--- test/uk/me/parabola/imgfmt/app/net/RoadDefTest.java	(revision 3342)
+++ test/uk/me/parabola/imgfmt/app/net/RoadDefTest.java	(working copy)
@@ -17,12 +17,12 @@
 import java.util.Random;
 
 import uk.me.parabola.imgfmt.app.BitWriter;
+import static org.junit.Assert.*;
 
-import static org.junit.Assert.*;
 import org.junit.Test;
 
 
-public class RoadDefTest {
+public class RoadDefTest extends EqualityTestCase<RoadDef> {
 
 	/**
 	 * Compares the result of the loop in writeNod2 with using a BitWriter.
@@ -63,4 +63,15 @@
 
 		assertArrayEquals("Loop against bitwriter", bwResult, loopResult);
 	}
+
+	@Override
+	protected RoadDef getA() throws Exception {
+		return new RoadDef(10176016, "Danbury Road");
+	}
+
+	@Override
+	protected RoadDef getB() throws Exception {
+		return new RoadDef(43021098,"Main Street");
+	}
+
 }
Index: src/uk/me/parabola/imgfmt/app/net/RoadDef.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/net/RoadDef.java	(revision 3342)
+++ src/uk/me/parabola/imgfmt/app/net/RoadDef.java	(working copy)
@@ -692,20 +692,55 @@
 	public int compareTo(RoadDef other) {
 		// sort by city name - this is used to group together
 		// roads that have been split into segments
+		int c = 0;
 		if(other == this)
-			return 0;
-
-		// TODO: look at what this is doing...
+			return c;
 		if(city != null && other.city != null)
-			return city.getName().compareTo(other.city.getName());
-		if (hashCode() == other.hashCode())
-			return 0;
-		else if (hashCode() < other.hashCode())
-			return -1;
-		else
-			return 0;
+			c = city.getName().compareTo(other.city.getName());
+		if (c != 0)
+			return c;
+		if (name != null && other.name != null)
+			c = name.compareTo(other.name);
+		if (c != 0)
+			return c;
+		c = Long.compare(this.id, other.id);
+		return c;
 	}
 
+	/* (non-Javadoc)
+	 * @see java.lang.Object#hashCode()
+	 */
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + (int) (id ^ (id >>> 32));
+		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		return result;
+	}
+
+	/* (non-Javadoc)
+	 * @see java.lang.Object#equals(java.lang.Object)
+	 */
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		RoadDef other = (RoadDef) obj;
+		if (id != other.id)
+			return false;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		return true;
+	}
+
 	public City getCity() {
 		return city;
 	}
Index: test/func/route/SimpleRouteTest.java
===================================================================
--- test/func/route/SimpleRouteTest.java	(revision 3342)
+++ test/func/route/SimpleRouteTest.java	(working copy)
@@ -75,7 +75,7 @@
 				break;
 			case "NOD":
 				count++;
-				assertEquals("NOD size", 170201, size);
+				assertEquals("NOD size", 169689, size);
 				break;
 			}
 		}
