diff --git a/resources/help/en/options b/resources/help/en/options
index c236850..6358704 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -101,6 +101,13 @@ Product description options:
 --overview-mapname
 
 Misc options:
+--max-jobs[=number]
+	When number is specified, allow that number of maps to be
+	processed concurrently. If number is not specified, the limit
+	is set equal to the number of CPU cores. If this option is not
+	given at all, the limit is 1 (i.e. the maps are processed
+	sequentially).
+
 --block-size=''number''
 	Changes the block size that is used in the generated map.
 	There is no general reason why you would want to do this.
diff --git a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
index 4f4b400..9bc6b83 100644
--- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
+++ b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
@@ -17,7 +17,7 @@
 package uk.me.parabola.mkgmap.general;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -51,7 +51,7 @@ public class RoadNetwork {
 	public static final int NO_TRUCK = 7;
 	public static final int NO_MAX = 8;
 
-	private final Map<Long, RouteNode> nodes = new HashMap<Long, RouteNode>();
+	private final Map<Long, RouteNode> nodes = new LinkedHashMap<Long, RouteNode>();
 
 	// boundary nodes
 	// a node should be in here iff the nodes boundary flag is set
diff --git a/src/uk/me/parabola/mkgmap/main/Main.java b/src/uk/me/parabola/mkgmap/main/Main.java
index 85a43e4..9fe41df 100644
--- a/src/uk/me/parabola/mkgmap/main/Main.java
+++ b/src/uk/me/parabola/mkgmap/main/Main.java
@@ -25,10 +25,16 @@ import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
 import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.ArgumentProcessor;
@@ -61,13 +67,15 @@ public class Main implements ArgumentProcessor {
 	// Final .img file combiners.
 	private final List<Combiner> combiners = new ArrayList<Combiner>();
 
-	// The filenames that will be used in pass2.
-	private final List<String> filenames = new ArrayList<String>();
-
 	private final Map<String, MapProcessor> processMap = new HashMap<String, MapProcessor>();
 	private String styleFile = "classpath:styles";
 	private boolean verbose;
 
+	public List<Future<String>> futures = new LinkedList<Future<String>>();
+	public ExecutorService threadPool = null;
+	// default number of threads
+	public int maxJobs = 1;
+
 	/**
 	 * The main program to make or combine maps.  We now use a two pass process,
 	 * first going through the arguments and make any maps and collect names
@@ -143,14 +151,25 @@ public class Main implements ArgumentProcessor {
 	 * @param args The command arguments.
 	 * @param filename The filename to process.
 	 */
-	public void processFilename(CommandArgs args, String filename) {
-		String ext = extractExtension(filename);
+	public void processFilename(final CommandArgs args, final String filename) {
+		final String ext = extractExtension(filename);
 		log.debug("file", filename, ", extension is", ext);
 
-		MapProcessor mp = mapMaker(ext);
-		String output = mp.makeMap(args, filename);
-		log.debug("adding output name", output);
-		filenames.add(output);
+		final MapProcessor mp = mapMaker(ext);
+
+		if(threadPool == null) {
+			log.info("Creating thread pool with " + maxJobs + " threads");
+			threadPool = Executors.newFixedThreadPool(maxJobs);
+		}
+
+		log.info("Submitting job " + filename);
+		futures.add(threadPool.submit(new Callable<String>() {
+				public String call() {
+					String output = mp.makeMap(args, filename);
+					log.debug("adding output name", output);
+					return output;
+				}
+			}));
 	}
 
 	private MapProcessor mapMaker(String ext) {
@@ -184,6 +203,15 @@ public class Main implements ArgumentProcessor {
 			verbose = true;
 		} else if (opt.equals("list-styles")) {
 			listStyles();
+		} else if (opt.equals("max-jobs")) {
+			if(val.length() > 0)
+				maxJobs = Integer.parseInt(val);
+			else
+				maxJobs = Runtime.getRuntime().availableProcessors();
+			if(maxJobs < 1) {
+				log.warn("max-jobs has to be at least 1");
+				maxJobs = 1;
+			}
 		} else if (opt.equals("version")) {
 			System.err.println(Version.VERSION);
 			System.exit(0);
@@ -249,9 +277,31 @@ public class Main implements ArgumentProcessor {
 	}
 
 	public void endOptions(CommandArgs args) {
+
+		List<String> filenames = new ArrayList<String>();
+
+		if(threadPool != null) {
+			threadPool.shutdown();
+			while(!futures.isEmpty()) {
+				try {
+					// don't call get() until a job has finished
+					if(futures.get(0).isDone())
+						filenames.add(futures.remove(0).get());
+					else
+						Thread.sleep(10);
+				}
+				catch(Exception e) {
+					log.error("" + e);
+					e.printStackTrace(System.err);
+				}
+			}
+		}
+
 		if (combiners.isEmpty())
 			return;
 
+		log.info("Combining maps");
+
 		// Get them all set up.
 		for (Combiner c : combiners)
 			c.init(args);
@@ -261,6 +311,7 @@ public class Main implements ArgumentProcessor {
 			if (file == null)
 				continue;
 			try {
+				log.info("  " + file);
 				FileInfo mapReader = FileInfo.getFileInfo(file);
 				for (Combiner c : combiners) {
 					c.onMapEnd(mapReader);
diff --git a/src/uk/me/parabola/mkgmap/main/MapMaker.java b/src/uk/me/parabola/mkgmap/main/MapMaker.java
index 7dfe5b9..2941819 100644
--- a/src/uk/me/parabola/mkgmap/main/MapMaker.java
+++ b/src/uk/me/parabola/mkgmap/main/MapMaker.java
@@ -18,6 +18,7 @@ package uk.me.parabola.mkgmap.main;
 
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -39,6 +40,7 @@ import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapPointFastFindMap;
 import uk.me.parabola.mkgmap.general.MapShape;
 import uk.me.parabola.mkgmap.reader.plugin.MapReader;
+import uk.me.parabola.util.Sortable;
 
 /**
  * Main routine for the command line map-making utility.
@@ -79,6 +81,7 @@ public class MapMaker implements MapProcessor {
 		params.setBlockSize(args.getBlockSize());
 		params.setMapDescription(args.getDescription());
 
+		log.info("Started making", args.getMapname(), "(" + args.getDescription() + ")");
 		try {
 			Map map = Map.createMap(args.getMapname(), params);
 			setOptions(map, args);
@@ -132,9 +135,14 @@ public class MapMaker implements MapProcessor {
 	private LoadableMapDataSource loadFromFile(CommandArgs args, String name) throws
 			FileNotFoundException, FormatException
 	{
-		LoadableMapDataSource src = MapReader.createMapReader(name);
-		src.config(args.getProperties());
-		src.load(name);
+		LoadableMapDataSource src;
+		// work around non-reentrancy of GType priority stuff
+		// by serialising the map reading
+		synchronized(MapMaker.class) {
+			src = MapReader.createMapReader(name);
+			src.config(args.getProperties());
+			src.load(name);
+		}
 		return src;
 	}
 
@@ -208,10 +216,22 @@ public class MapMaker implements MapProcessor {
 			}
 
 			// generate a POI for each named road
+
+			// sort by name and coordinate of first point so that
+			// the order is always the same for the same input
+			List<Sortable<String, MapLine>> rnpRoads = new ArrayList<Sortable<String, MapLine>>();
 			for(List<MapLine> lr : findConnectedRoadsWithSameName(namedRoads)) {
 				// connected roads are not ordered so just use first in list
-				src.getPoints().add(makeRoadNamePOI(lr.get(0), rnpt));
+				MapLine r = lr.get(0);
+				String key = r.getName();
+				List<Coord> points = r.getPoints();
+				if(points.size() > 0)
+					key += "_" + points.get(0);
+				rnpRoads.add(new Sortable<String, MapLine>(key, r));
 			}
+			Collections.sort(rnpRoads);
+			for(Sortable<String, MapLine> sr : rnpRoads)
+				src.getPoints().add(makeRoadNamePOI(sr.getValue(), rnpt));
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Relation.java b/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
index 1cd59c6..24548ea 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
@@ -1,7 +1,7 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -11,7 +11,7 @@ import java.util.Map;
  * @author Rene_A
  */
 public abstract class Relation extends Element {
-	private final Map<Element, String> roles = new HashMap<Element, String>();
+	private final Map<Element, String> roles = new LinkedHashMap<Element, String>();
 	private final List<Element> elements = new ArrayList<Element>();
 
 	/** 
