diff --git a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
index f2b6125..d7d5571 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
@@ -125,7 +125,7 @@ public class LBLFile extends ImgFile {
 	 * @param text The text of the label, it will be uppercased.
 	 * @return A reference to the created label.
 	 */
-	public Label newLabel(String text) {
+	public synchronized Label newLabel(String text) {
 		EncodedText etext = textEncoder.encodeText(text);
 		Label l = labelCache.get(text);
 		if (l == null) {
@@ -139,43 +139,43 @@ public class LBLFile extends ImgFile {
 		return l;
 	}
 
-	public POIRecord createPOI(String name) {
+	public synchronized POIRecord createPOI(String name) {
 		return places.createPOI(name);
 	}
 
-	public POIRecord createExitPOI(String name, Exit exit) {
+	public synchronized POIRecord createExitPOI(String name, Exit exit) {
 		return places.createExitPOI(name, exit);
 	}
 
-	public POIIndex createPOIIndex(String name, int poiIndex, Subdivision group, int type) {
+	public synchronized POIIndex createPOIIndex(String name, int poiIndex, Subdivision group, int type) {
 		return places.createPOIIndex(name, poiIndex, group, type);
 	}
 	
-	public Country createCountry(String name, String abbr) {
+	public synchronized Country createCountry(String name, String abbr) {
 		return places.createCountry(name, abbr);
 	}
 	
-	public Region createRegion(Country country, String region, String abbr) {
+	public synchronized Region createRegion(Country country, String region, String abbr) {
 	    return places.createRegion(country, region, abbr);
 	}
 	
-	public City createCity(Region region, String city, boolean unique) {
+	public synchronized City createCity(Region region, String city, boolean unique) {
 		return places.createCity(region, city, unique);
 	}
 
-	public City createCity(Country country, String city, boolean unique) {
+	public synchronized City createCity(Country country, String city, boolean unique) {
 		return places.createCity(country, city, unique);
 	}
 
-	public Zip createZip(String code) {
+	public synchronized Zip createZip(String code) {
 		return places.createZip(code);
 	}
 
-	public Highway createHighway(Region region, String name) {
+	public synchronized Highway createHighway(Region region, String name) {
 		return places.createHighway(region, name);
 	}
 
-	public ExitFacility createExitFacility(int type, char direction, int facilities, String description, boolean last) {
+	public synchronized ExitFacility createExitFacility(int type, char direction, int facilities, String description, boolean last) {
 		return places.createExitFacility(type, direction, facilities, description, last);
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polygon.java b/src/uk/me/parabola/imgfmt/app/trergn/Polygon.java
index 5e7b582..06baba6 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polygon.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polygon.java
@@ -24,5 +24,7 @@ package uk.me.parabola.imgfmt.app.trergn;
 public class Polygon extends Polyline {
 	public Polygon(Subdivision div) {
 		super(div);
+		// inhibit Polyline from quering the division for an index number
+		number = -1;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
index f36fcca..d80eb29 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
@@ -48,7 +48,7 @@ public class Polyline extends MapObject {
 	private static final int FLAG_DIR = 0x40;
 	private static final int FLAG_2BYTE_LEN = 0x80;
 
-	private int number;
+	protected int number;
 
 	// Reference to NET section, if any
 	private RoadDef roaddef;
@@ -98,40 +98,47 @@ public class Polyline extends MapObject {
 		if (blen >= 0x100)
 			b1 |= FLAG_2BYTE_LEN;
 
-		file.put(b1);
-
-		// The label, contains a couple of flags within it.
-		int loff = getLabel().getOffset();
-		if (w.isExtraBit())
-			loff |= FLAG_EXTRABIT;
-
-		// If this is a road, then we need to save the offset of the label
-		// so that we can change it to the index in the net section
-		if (roaddef != null) {
-			roaddef.addLabel(getLabel());
-			roaddef.addOffsetTarget(file.position(),
-					FLAG_NETINFO | (loff & FLAG_EXTRABIT));
-			// also add ref label(s) if present
-			List<Label> refLabels = getRefLabels();
-			if(refLabels != null)
-				for(Label rl : refLabels)
-					roaddef.addLabel(rl);
-		}
+		synchronized(file) {
+			file.put(b1);
+
+			// The label, contains a couple of flags within it.
+			int loff = getLabel().getOffset();
+			if (w.isExtraBit())
+				loff |= FLAG_EXTRABIT;
+
+			// If this is a road, then we need to save the offset of the label
+			// so that we can change it to the index in the net section
+			if (roaddef != null) {
+				roaddef.addLabel(getLabel());
+				roaddef.addOffsetTarget(file.position(),
+										FLAG_NETINFO | (loff & FLAG_EXTRABIT));
+				// also add ref label(s) if present
+				List<Label> refLabels = getRefLabels();
+				if(refLabels != null)
+					for(Label rl : refLabels)
+						roaddef.addLabel(rl);
+			}
 
-		file.put3(loff);
+			file.put3(loff);
 
-		// The delta of the longitude from the subdivision centre point
-		// note that this has already been calculated.
-		file.putChar((char) getDeltaLong());
-		file.putChar((char) getDeltaLat());
-		log.debug("out center", getDeltaLat(), getDeltaLong());
+			// The delta of the longitude from the subdivision centre point
+			// note that this has already been calculated.
+			file.putChar((char) getDeltaLong());
+			file.putChar((char) getDeltaLat());
+			log.debug("out center", getDeltaLat(), getDeltaLong());
 
-		if (blen < 0x100)
-			file.put((byte) (blen & 0xff));
-		else
-			file.putChar((char) (blen & 0xffff));
+			if (blen < 0x100)
+				file.put((byte) (blen & 0xff));
+			else
+				file.putChar((char) (blen & 0xffff));
 
-		file.put(bw.getBytes(), 0, blen+1);
+			file.put(bw.getBytes(), 0, blen+1);
+			
+			if(number == 0) {
+				// this is a line (not a shape)
+				number = getSubdiv().getNextPolylineNumber();
+			}
+		}
 	}
 
 	public void addCoord(Coord co) {
@@ -166,11 +173,8 @@ public class Polyline extends MapObject {
 		this.roaddef = rd;
 	}
 
-	void setNumber(int n) {
-		number = n;
-	}
-
 	public int getNumber() {
+		assert number > 0 : "Attempt to retrieve unset Polyline number";
 		return number;
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
index 081ff7b..edaa87b 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
@@ -225,11 +225,13 @@ public class Subdivision {
 			}
 		}
 
-		numPolylines++;
-		pl.setNumber(numPolylines);
 		return pl;
 	}
 
+	public int getNextPolylineNumber() {
+		return ++numPolylines;
+	}
+
 	public Polygon createPolygon(String name) {
 		assert hasPolygons;
 		Label label = lblFile.newLabel(name);
diff --git a/src/uk/me/parabola/mkgmap/build/Locator.java b/src/uk/me/parabola/mkgmap/build/Locator.java
index 1341772..dadca57 100644
--- a/src/uk/me/parabola/mkgmap/build/Locator.java
+++ b/src/uk/me/parabola/mkgmap/build/Locator.java
@@ -231,9 +231,11 @@ public class Locator {
 		MapPoint nextPoint;
 	
 		nextPoint = cityMap.findNextPoint(p);
-		
-		totalFinds++;
-		totalTime += ((System.nanoTime() - startTime) / 1e9);
+	
+		synchronized(this) {
+			totalFinds++;
+			totalTime += ((System.nanoTime() - startTime) / 1e9);
+		}
 		return nextPoint;
 	}
 	
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index 2b3ee17..0b4b90e 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -20,8 +20,11 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 
+import java.util.concurrent.Future;
+
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.Label;
 import uk.me.parabola.imgfmt.app.Exit;
@@ -69,6 +72,7 @@ import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapRoad;
 import uk.me.parabola.mkgmap.general.MapShape;
 import uk.me.parabola.mkgmap.general.RoadNetwork;
+import uk.me.parabola.mkgmap.main.Main;
 import uk.me.parabola.util.Configurable;
 import uk.me.parabola.util.EnhancedProperties;
 
@@ -104,7 +108,7 @@ public class MapBuilder implements Configurable {
 	private String regionAbbr;
 	private int		locationAutofillLevel;
 	private boolean	poiAddresses = true;
-	private int		poiDisplayFlags;
+	private Integer poiDisplayFlags = null;
 	private boolean sortRoads = true;
 	private static final double FILTER_DISTANCE = 2.6;
 
@@ -162,20 +166,28 @@ public class MapBuilder implements Configurable {
 		if(regionName != null)
 			region = lblFile.createRegion(country, regionName, regionAbbr);
 
+		log.info("Processing cities");
 		processCities(map, src);
+		log.info("Processing POIs");
 		processPOIs(map, src);
 		//preProcessRoads(map, src);
+		log.info("Processing overviews");
 		processOverviews(map, src);
+		log.info("Processing info");
 		processInfo(map, src);
+		log.info("Making map areas");
 		makeMapAreas(map, src);
 		//processRoads(map, src);
 		//postProcessRoads(map, src);
 
 		treFile.setLastRgnPos(rgnFile.position() - RGNHeader.HEADER_LEN);
 
+		log.info("Writing RGN file");
 		rgnFile.write();
+		log.info("Writing TRE file");
 		treFile.write();
 		treFile.writePost();
+		log.info("Writing LBL file");
 		lblFile.write();
 		lblFile.writePost();
 
@@ -185,8 +197,10 @@ public class MapBuilder implements Configurable {
 			NODFile nodFile = map.getNodFile();
 			if (nodFile != null) {
 				nodFile.setNetwork(network.getCenters(), network.getRoadDefs(), network.getBoundary());
+				log.info("Writing NOD file");
 				nodFile.write();
 			}
+			log.info("Writing NET file");
 			netFile.write(lblFile.numCities(), lblFile.numZips());
 
 			if (nodFile != null) {
@@ -194,6 +208,7 @@ public class MapBuilder implements Configurable {
 			}
 			netFile.writePost(rgnFile.getWriter(), sortRoads);
 		}
+		log.info("Done making map");
 	}
 
 	/**
@@ -207,7 +222,7 @@ public class MapBuilder implements Configurable {
 	 * @param src The map data.
 	 */
 	private void processCities(Map map, MapDataSource src) {
-		LBLFile lbl = map.getLblFile();
+		final LBLFile lbl = map.getLblFile();
 
 		locator.setDefaultCountry(countryName, countryAbbr);
 		
@@ -252,194 +267,245 @@ public class MapBuilder implements Configurable {
 			}
 		}
 
-		MapPoint tempPoint = new MapPoint();
-
-		for (MapLine line : src.getLines()) {
-			if(line.isRoad()) {
-				String cityName = line.getCity();
-				String cityCountryName = line.getCountry();
-				String cityRegionName  = line.getRegion();
-				String zipStr = line.getZip();
-
-				if(cityName == null) {
-					// Get name of next city if untagged
-
-					tempPoint.setLocation(line.getLocation());
-					MapPoint nextCity = locator.findNextPoint(tempPoint);
-
-					if(nextCity != null) {
-						cityName = nextCity.getCity();
-						if(cityCountryName == null)
-							cityCountryName = nextCity.getCountry();
-						if(cityRegionName == null)
-							cityRegionName = nextCity.getRegion();
-						if(zipStr == null)
-							zipStr = nextCity.getZip();
-					}
-				}
+		class ProcessCitiesWorker implements Runnable {
+			List<MapLine> lines;
 
-				if(cityName != null) {
+			public ProcessCitiesWorker(List<MapLine> lines) {
+				this.lines = lines;
+			}
 
-					Country cc = (cityCountryName == null)? country : lbl.createCountry(cityCountryName, locator.getCountryCode(cityCountryName));
+			public void run() {
+				MapPoint tempPoint = new MapPoint();
+				for(MapLine line : lines) {
+					if(line.isRoad()) {
+						String cityName = line.getCity();
+						String cityCountryName = line.getCountry();
+						String cityRegionName  = line.getRegion();
+						String zipStr = line.getZip();
+
+						if(cityName == null) {
+							// Get name of next city if untagged
+
+							tempPoint.setLocation(line.getLocation());
+							MapPoint nextCity = locator.findNextPoint(tempPoint);
+
+							if(nextCity != null) {
+								cityName = nextCity.getCity();
+								if(cityCountryName == null)
+									cityCountryName = nextCity.getCountry();
+								if(cityRegionName == null)
+									cityRegionName = nextCity.getRegion();
+								if(zipStr == null)
+									zipStr = nextCity.getZip();
+							}
+						}
 
-					Region cr = (cityRegionName == null)? region : lbl.createRegion(cc, cityRegionName, null);
+						if(cityName != null) {
+							Country cc = (cityCountryName == null)? country : lbl.createCountry(cityCountryName, locator.getCountryCode(cityCountryName));
+							Region cr = (cityRegionName == null)? region : lbl.createRegion(cc, cityRegionName, null);
 
-					if(cr != null) {
-						((MapRoad)line).setRoadCity(lbl.createCity(cr, cityName, false));
-					}
-					else {
-						((MapRoad)line).setRoadCity(lbl.createCity(cc, cityName, false));
+							if(cr != null) {
+								((MapRoad)line).setRoadCity(lbl.createCity(cr, cityName, false));
+							}
+							else {
+								((MapRoad)line).setRoadCity(lbl.createCity(cc, cityName, false));
+							}
+						}
+
+						if(zipStr != null) {
+							((MapRoad)line).setRoadZip(lbl.createZip(zipStr));
+						}
 					}
 				}
+			}
+		}
 
-				if(zipStr != null) {
-					((MapRoad)line).setRoadZip(lbl.createZip(zipStr));
-				}
+		List<MapLine> lines = src.getLines();
+
+		final int maxChunks = Main.numThreads * 2;
+		List<Future<?>> futures = new LinkedList<Future<?>>();
 
+		int chunkSize = (lines.size() + maxChunks - 1) / maxChunks;
+		if(chunkSize < 8)
+			chunkSize = 8;
+		if(Main.numThreads == 1 || chunkSize >= lines.size() || Main.preserveElementOrder) {
+			futures.add(Main.threadPool.submit(new ProcessCitiesWorker(lines)));
+		}
+		else {
+			for(int base = 0; base < lines.size(); base += chunkSize) {
+				int limit = base + chunkSize;
+				if(limit > lines.size())
+					limit = lines.size();
+				futures.add(Main.threadPool.submit(new ProcessCitiesWorker(lines.subList(base, limit))));
+			}
+		}
+		// wait for all the work to be completed
+		while(!futures.isEmpty()) {
+			try {
+				futures.remove(0).get();
+			}
+			catch(Exception e) {
+				log.error("" + e);
 			}
 		}
 	}
 
-	private void processPOIs(Map map, MapDataSource src) {
+	private void processPOIs(final Map map, MapDataSource src) {
 
-		LBLFile lbl = map.getLblFile();
-		boolean checkedForPoiDispFlag = false;
-		boolean doAutofill;
+		final LBLFile lbl = map.getLblFile();
 
-		for (MapPoint p : src.getPoints()) {
-			if(p.isExit()) {
-				processExit(map, (MapExitPoint)p);
+		class ProcessPOIsWorker implements Runnable {
+			List<MapPoint> points;
+
+			public ProcessPOIsWorker(List<MapPoint> points) {
+				this.points = points;
 			}
-			else if(!p.isCity() && (p.isRoadNamePOI() || poiAddresses)) {
-				if(locationAutofillLevel > 0 || p.isRoadNamePOI())
-					doAutofill = true;
-				else
-					doAutofill = false;
-				
+
+			public void run() {
+				for(MapPoint p : points) {
+					if(p.isExit()) {
+						processExit(map, (MapExitPoint)p);
+					}
+					else if(!p.isCity() && (p.isRoadNamePOI() || poiAddresses)) {
+						boolean doAutofill;
+						if(locationAutofillLevel > 0 || p.isRoadNamePOI())
+							doAutofill = true;
+						else
+							doAutofill = false;
 				
-				String CountryStr = p.getCountry();
-				String RegionStr  = p.getRegion();		
-				String ZipStr     = p.getZip();
-				String CityStr    = p.getCity();
-				boolean guessed   = false;
+						String CountryStr = p.getCountry();
+						String RegionStr  = p.getRegion();		
+						String ZipStr     = p.getZip();
+						String CityStr    = p.getCity();
+						boolean guessed   = false;
 
-				if(CityStr != null || ZipStr != null ||RegionStr != null || CountryStr != null)
+						if(CityStr != null || ZipStr != null ||RegionStr != null || CountryStr != null)
 
-				if(CountryStr != null)
-					CountryStr = locator.fixCountryString(CountryStr);	
+							if(CountryStr != null)
+								CountryStr = locator.fixCountryString(CountryStr);	
 
-				if(CountryStr == null || RegionStr == null || (ZipStr == null && CityStr == null))
-				{
-						MapPoint nextCity = locator.findByCityName(p);
+						if(CountryStr == null || RegionStr == null || (ZipStr == null && CityStr == null)) {
+								MapPoint nextCity = locator.findByCityName(p);
 						
-						if(doAutofill && nextCity == null)
-							nextCity = locator.findNextPoint(p);
-
-						if(nextCity != null)
-						{
-							guessed = true;
-
-							if (CountryStr == null)	CountryStr = nextCity.getCountry();
-							if (RegionStr == null)  RegionStr  = nextCity.getRegion();
-
-							if(doAutofill)
-							{
-								if(ZipStr == null)
-								{
-									String CityZipStr = nextCity.getZip();
+								if(doAutofill && nextCity == null)
+									nextCity = locator.findNextPoint(p);
+
+								if(nextCity != null) {
+									guessed = true;
+										
+									if (CountryStr == null)	CountryStr = nextCity.getCountry();
+									if (RegionStr == null)  RegionStr  = nextCity.getRegion();
+
+									if(doAutofill) {
+										if(ZipStr == null) {
+											String CityZipStr = nextCity.getZip();
 									
-									// Ignore list of Zips seperated by ;
+											// Ignore list of Zips seperated by ;
 									
-									if(CityZipStr != null && CityZipStr.indexOf(',') < 0)
-										ZipStr = CityZipStr; 
+											if(CityZipStr != null && CityZipStr.indexOf(',') < 0)
+												ZipStr = CityZipStr; 
+										}
+										if(CityStr == null) CityStr = nextCity.getCity();								
+									}
 								}
-								
-								if(CityStr == null) CityStr = nextCity.getCity();								
-							}
-						
 						}
-				}
 				
-	
-				if(CountryStr != null && !checkedForPoiDispFlag)
-				{
-					// Different countries require different address notation
+						if(CountryStr != null && poiDisplayFlags == null) {
+							// Different countries require different address notation
 
-					poiDisplayFlags = locator.getPOIDispFlag(CountryStr);
-					checkedForPoiDispFlag = true;
-				}
+							poiDisplayFlags = locator.getPOIDispFlag(CountryStr);
+						}
 
+						if(p.isRoadNamePOI() && CityStr != null) {
+							// If it is road POI add city name and street name into address info
+							p.setStreet(p.getName());
+							p.setName(p.getName() + "/" + CityStr);
+						}
 
-				if(p.isRoadNamePOI() && CityStr != null)					
-				{
-						// If it is road POI add city name and street name into address info
-						p.setStreet(p.getName());
-						p.setName(p.getName() + "/" + CityStr);
-				}
+						POIRecord r = lbl.createPOI(p.getName());	
 
-				POIRecord r = lbl.createPOI(p.getName());	
+						if(CityStr != null)	{
+							Country thisCountry;
+							Region 	thisRegion;
+							City 	city;
 
-				if(CityStr != null)
-				{
-					Country thisCountry;
-					Region 	thisRegion;
-					City 		city;
-
-					if(CountryStr != null)
-						thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr));
-					else
-						thisCountry = country;
+							if(CountryStr != null)
+								thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr));
+							else
+								thisCountry = country;
 					
-					if(RegionStr != null)
-						thisRegion = lbl.createRegion(thisCountry,RegionStr, null);
-					else
-						thisRegion = region;
-
-					if(thisRegion != null)
-						city = lbl.createCity(thisRegion, CityStr, false);
-					else
-						city = lbl.createCity(thisCountry, CityStr, false);
-
-	  			r.setCity(city);
+							if(RegionStr != null)
+								thisRegion = lbl.createRegion(thisCountry,RegionStr, null);
+							else
+								thisRegion = region;
+
+							if(thisRegion != null)
+								city = lbl.createCity(thisRegion, CityStr, false);
+							else
+								city = lbl.createCity(thisCountry, CityStr, false);
+							r.setCity(city);
+						}
 
-				}
+						if (ZipStr != null) {
+							Zip zip = lbl.createZip(ZipStr);
+							r.setZipIndex(zip.getIndex());
+						}
 
-				if (ZipStr != null)
-				{
-					Zip zip = lbl.createZip(ZipStr);
-					r.setZipIndex(zip.getIndex());
-				}
+						if(p.getStreet() != null) {
+							Label streetName = lbl.newLabel(p.getStreet());
+							r.setStreetName(streetName);			  
+						}
+						else if (guessed && locationAutofillLevel > 0) {
+							Label streetName = lbl.newLabel("FIX MY ADDRESS");
+							r.setStreetName(streetName);		
+						}
 
-				if(p.getStreet() != null)
-				{
-					Label streetName = lbl.newLabel(p.getStreet());
-					r.setStreetName(streetName);			  
-				}
-				else if (guessed && locationAutofillLevel > 0)
-				{
-					Label streetName = lbl.newLabel("FIX MY ADDRESS");
-					r.setStreetName(streetName);		
-				}
+						if(p.getHouseNumber() != null) {
+							if(!r.setSimpleStreetNumber(p.getHouseNumber())) {
+								Label streetNumber = lbl.newLabel(p.getHouseNumber());
+								r.setComplexStreetNumber(streetNumber);
+							}
+						}
 
-				if(p.getHouseNumber() != null)
-				{
-					if(!r.setSimpleStreetNumber(p.getHouseNumber()))
-					{
-						Label streetNumber = lbl.newLabel(p.getHouseNumber());
-						r.setComplexStreetNumber(streetNumber);
+						if(p.getPhone() != null) {
+							if(!r.setSimplePhoneNumber(p.getPhone())) {
+								Label phoneNumber = lbl.newLabel(p.getPhone());
+								r.setComplexPhoneNumber(phoneNumber);
+							}
+						}
+						synchronized(poimap) {
+							poimap.put(p, r);
+						}
 					}
 				}
+			}
+		}
 
-				if(p.getPhone() != null)
-				{
-					if(!r.setSimplePhoneNumber(p.getPhone()))
-					{
-						Label phoneNumber = lbl.newLabel(p.getPhone());
-						r.setComplexPhoneNumber(phoneNumber);
-					}
-				}	
-		  	
-				poimap.put(p, r);
+		List<MapPoint> points = src.getPoints();
+		final int maxChunks = Main.numThreads * 2;
+		List<Future<?>> futures = new LinkedList<Future<?>>();
+
+		int chunkSize = (points.size() + maxChunks - 1) / maxChunks;
+		if(chunkSize < 8)
+			chunkSize = 8;
+		if(Main.numThreads == 1 || chunkSize >= points.size() || Main.preserveElementOrder) {
+			futures.add(Main.threadPool.submit(new ProcessPOIsWorker(points)));
+		}
+		else {
+			for(int base = 0; base < points.size(); base += chunkSize) {
+				int limit = base + chunkSize;
+				if(limit > points.size())
+					limit = points.size();
+				futures.add(Main.threadPool.submit(new ProcessPOIsWorker(points.subList(base, limit))));
+			}
+		}
+		// wait for all the work to be completed
+		while(!futures.isEmpty()) {
+			try {
+				futures.remove(0).get();
+			}
+			catch(Exception e) {
+				log.error("" + e);
 			}
 		}
 
@@ -454,9 +520,12 @@ public class MapBuilder implements Configurable {
 		String ref = mep.getMotorwayRef();
 		String OSMId = mep.getOSMId();
 		if(ref != null) {
-			Highway hw = highways.get(ref);
-			if(hw == null)
-				hw = makeHighway(map, ref);
+			Highway hw = null;
+			synchronized(this) {
+				hw = highways.get(ref);
+				if(hw == null)
+					hw = makeHighway(map, ref);
+			}
 			if(hw == null) {
 			    log.warn("Can't create exit " + mep.getName() + " (OSM id " + OSMId + ") on unknown highway " + ref);
 			    return;
@@ -496,7 +565,9 @@ public class MapBuilder implements Configurable {
 				Label ed = lbl.newLabel(exitTo);
 				exit.setDescription(ed);
 			}
-			poimap.put(mep, r);
+			synchronized(poimap) {
+				poimap.put(mep, r);
+			}
 			// FIXME - set bottom bits of
 			// type to reflect facilities available?
 		}
@@ -552,6 +623,7 @@ public class MapBuilder implements Configurable {
 			List<SourceSubdiv> nextList = new ArrayList<SourceSubdiv>();
 
 			Zoom zoom = map.createZoom(linfo.getLevel(), linfo.getBits());
+			log.info("  Level " + zoom.getLevel());
 
 			for (SourceSubdiv srcDivPair : srcList) {
 
@@ -666,7 +738,7 @@ public class MapBuilder implements Configurable {
 		// The bounds of the map.
 		map.setBounds(src.getBounds());
 
-		if(poiDisplayFlags != 0)							// POI requested alterate address notation
+		if(poiDisplayFlags != null && poiDisplayFlags != 0)	// POI requested alterate address notation
 			map.setPoiDisplayFlags(poiDisplayFlags);
 
 		// You can add anything here.
@@ -810,26 +882,67 @@ public class MapBuilder implements Configurable {
 	 * @param div	The subdivision that the lines belong to.
 	 * @param lines The lines to be added.
 	 */
-	private void processLines(Map map, Subdivision div, List<MapLine> lines)
+	private void processLines(final Map map, final Subdivision div, List<MapLine> lines)
 	{
 		div.startLines();  // Signal that we are beginning to draw the lines.
 
-		int res = div.getResolution();
+		if(lines.size() > 0) {
+			final int res = div.getResolution();
 
-		FilterConfig config = new FilterConfig();
-		config.setResolution(res);
+			final FilterConfig config = new FilterConfig();
+			config.setResolution(res);
 
-		LayerFilterChain filters = new LayerFilterChain(config);
-		filters.addFilter(new DouglasPeuckerFilter(FILTER_DISTANCE));
-		filters.addFilter(new LineSplitterFilter());
-		filters.addFilter(new RemoveEmpty());
-		filters.addFilter(new LineAddFilter(div, map, doRoads));
-		
-		for (MapLine line : lines) {
-			if (line.getMinResolution() > res || line.getMaxResolution() < res)
-				continue;
+			class ProcessLinesWorker implements Runnable {
+				List<MapLine> lines;
+
+				public ProcessLinesWorker(List<MapLine> lines) {
+					this.lines = lines;
+				}
+
+				public void run() {
+					LayerFilterChain filters = new LayerFilterChain(config);
+					filters.addFilter(new DouglasPeuckerFilter(FILTER_DISTANCE));
+					filters.addFilter(new LineSplitterFilter());
+					filters.addFilter(new RemoveEmpty());
+					filters.addFilter(new LineAddFilter(div, map, doRoads));
+					for(MapLine line : lines) {
+						if (line.getMinResolution() <= res &&
+							line.getMaxResolution() >= res)
+							filters.startFilter(line);
+					}
+				}
+			}
+
+			final int maxChunks = Main.numThreads * 2;
+			List<Future<?>> futures = new LinkedList<Future<?>>();
 
-			filters.startFilter(line);
+			int chunkSize = (lines.size() + maxChunks - 1) / maxChunks;
+			if(chunkSize < 8)
+				chunkSize = 8;
+
+			if(Main.numThreads == 1 || chunkSize >= lines.size() || Main.preserveElementOrder) {
+				futures.add(Main.threadPool.submit(new ProcessLinesWorker(lines)));
+			}
+			else {
+				for(int base = 0; base < lines.size(); base += chunkSize) {
+					int limit = base + chunkSize;
+					if(limit > lines.size())
+						limit = lines.size();
+					futures.add(Main.threadPool.submit(new ProcessLinesWorker(lines.subList(base, limit))));
+				}
+			}
+
+			//			log.info("" + lines.size() + " lines, " + chunkSize + " lines per chunk, " + futures.size() + " jobs");
+
+			// wait for all the work to be completed
+			while(!futures.isEmpty()) {
+				try {
+					futures.remove(0).get();
+				}
+				catch(Exception e) {
+					System.err.println("" + e);
+				}
+			}
 		}
 	}
 
@@ -844,27 +957,70 @@ public class MapBuilder implements Configurable {
 	 * @param div	The subdivision that the polygons belong to.
 	 * @param shapes The polygons to be added.
 	 */
-	private void processShapes(Map map, Subdivision div, List<MapShape> shapes)
+	private void processShapes(final Map map, final Subdivision div, List<MapShape> shapes)
 	{
 		div.startShapes();  // Signal that we are beginning to draw the shapes.
 
-		int res = div.getResolution();
+		if(shapes.size() > 0) {
+			final int res = div.getResolution();
 
-		FilterConfig config = new FilterConfig();
-		config.setResolution(res);
-		LayerFilterChain filters = new LayerFilterChain(config);
-		//DouglasPeucker behaves at the moment not really optimal at low zooms, but acceptable.
-		//Is there an similar algorithm for polygons?
-		filters.addFilter(new DouglasPeuckerFilter(FILTER_DISTANCE));
-		filters.addFilter(new PolygonSplitterFilter());
-		filters.addFilter(new RemoveEmpty());
-		filters.addFilter(new ShapeAddFilter(div, map));
-
-		for (MapShape shape : shapes) {
-			if (shape.getMinResolution() > res || shape.getMaxResolution() < res)
-				continue;
+			final FilterConfig config = new FilterConfig();
+			config.setResolution(res);
+
+			class ProcessShapesWorker implements Runnable {
+				List<MapShape> shapes;
+
+				public ProcessShapesWorker(List<MapShape> shapes) {
+					this.shapes = shapes;
+				}
 
-			filters.startFilter(shape);
+				public void run() {
+
+					LayerFilterChain filters = new LayerFilterChain(config);
+					//DouglasPeucker behaves at the moment not really optimal at low zooms, but acceptable.
+					//Is there an similar algorithm for polygons?
+					filters.addFilter(new DouglasPeuckerFilter(FILTER_DISTANCE));
+					filters.addFilter(new PolygonSplitterFilter());
+					filters.addFilter(new RemoveEmpty());
+					filters.addFilter(new ShapeAddFilter(div, map));
+					for(MapShape shape : shapes) {
+						if (shape.getMinResolution() <= res &&
+							shape.getMaxResolution() >= res)
+							filters.startFilter(shape);
+					}
+				}
+			}
+
+			final int maxChunks = Main.numThreads * 2;
+			List<Future<?>> futures = new LinkedList<Future<?>>();
+
+			int chunkSize = (shapes.size() + maxChunks - 1) / maxChunks;
+			if(chunkSize < 8)
+				chunkSize = 8;
+
+			if(Main.numThreads == 1 || chunkSize >= shapes.size() || Main.preserveElementOrder) {
+				futures.add(Main.threadPool.submit(new ProcessShapesWorker(shapes)));
+			}
+			else {
+				for(int base = 0; base < shapes.size(); base += chunkSize) {
+					int limit = base + chunkSize;
+					if(limit > shapes.size())
+						limit = shapes.size();
+					futures.add(Main.threadPool.submit(new ProcessShapesWorker(shapes.subList(base, limit))));
+				}
+			}
+
+			//			log.info("" + shapes.size() + " shapes, " + chunkSize + " shapes per chunk, " + futures.size() + " jobs");
+
+			// wait for all the work to be completed
+			while(!futures.isEmpty()) {
+				try {
+					futures.remove(0).get();
+				}
+				catch(Exception e) {
+					System.err.println("" + e);
+				}
+			}
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index ce297a8..42eca41 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -56,7 +56,7 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 *
 	 * @param point Point to add.
 	 */
-	public void addPoint(MapPoint point) {
+	public synchronized void addPoint(MapPoint point) {
 		updateOverview(pointOverviews, point.getType(), point.getMinResolution());
 
 		points.add(point);
@@ -67,7 +67,7 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 *
 	 * @param line The line information.
 	 */
-	public void addLine(MapLine line) {
+	public synchronized void addLine(MapLine line) {
 		assert !(line instanceof MapShape);
 		if (line.getPoints().isEmpty())
 			return;
@@ -85,7 +85,7 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 *
 	 * @param shape The polygon to add.
 	 */
-	public void addShape(MapShape shape) {
+	public synchronized void addShape(MapShape shape) {
 		if (shape.getPoints().isEmpty())
 			return;
 
@@ -107,7 +107,7 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 *
 	 * @param p The coordinates of the point to add.
 	 */
-	public void addToBounds(Coord p) {
+	public synchronized void addToBounds(Coord p) {
 		int lat = p.getLatitude();
 		int lon = p.getLongitude();
 		if (lat < minLat)
diff --git a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
index 9bfc321..859b4ad 100644
--- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
+++ b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
@@ -63,7 +63,9 @@ public class RoadNetwork {
 
 	public void addRoad(MapRoad road) {
 		//mapRoads.add(road);
-		roadDefs.add(road.getRoadDef()); //XXX
+		synchronized(roadDefs) {
+			roadDefs.add(road.getRoadDef()); //XXX
+		}
 
 		CoordNode lastCoord = null;
 		int lastIndex = 0;
@@ -123,7 +125,7 @@ public class RoadNetwork {
 		road.getRoadDef().setLength(roadLength);
 	}
 
-	private RouteNode getNode(long id, Coord coord) {
+	private synchronized RouteNode getNode(long id, Coord coord) {
 		RouteNode node = nodes.get(id);
 		if (node == null) {
 			node = new RouteNode(coord);
diff --git a/src/uk/me/parabola/mkgmap/main/Main.java b/src/uk/me/parabola/mkgmap/main/Main.java
index 6af2120..ca3ae83 100644
--- a/src/uk/me/parabola/mkgmap/main/Main.java
+++ b/src/uk/me/parabola/mkgmap/main/Main.java
@@ -29,6 +29,9 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+
 import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.ArgumentProcessor;
@@ -67,6 +70,10 @@ public class Main implements ArgumentProcessor {
 	private String styleFile = "classpath:styles";
 	private boolean verbose;
 
+	public static ExecutorService threadPool = null;
+	public static int numThreads = Runtime.getRuntime().availableProcessors();
+	public static boolean preserveElementOrder = false;
+
 	/**
 	 * 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
@@ -121,6 +128,7 @@ public class Main implements ArgumentProcessor {
 	}
 
 	public void startOptions() {
+
 		MapProcessor saver = new NameSaver();
 		processMap.put("img", saver);
 
@@ -146,6 +154,11 @@ public class Main implements ArgumentProcessor {
 		String ext = extractExtension(filename);
 		log.debug("file", filename, ", extension is", ext);
 
+		if(threadPool == null) {
+			log.info("Creating thread pool with " + numThreads + " threads");
+			threadPool = Executors.newFixedThreadPool(numThreads);
+		}
+
 		MapProcessor mp = mapMaker(ext);
 		String output = mp.makeMap(args, filename);
 		log.debug("adding output name", output);
@@ -183,6 +196,10 @@ public class Main implements ArgumentProcessor {
 			verbose = true;
 		} else if (opt.equals("list-styles")) {
 			listStyles();
+		} else if (opt.equals("num-threads")) {
+			numThreads = Integer.parseInt(val);
+		} else if (opt.equals("preserve-element-order")) {
+			preserveElementOrder = true;
 		} else if (opt.equals("version")) {
 			System.err.println(Version.VERSION);
 			System.exit(0);
@@ -273,6 +290,9 @@ public class Main implements ArgumentProcessor {
 		for (Combiner c : combiners) {
 			c.onFinish();
 		}
+
+		if(threadPool != null)
+			threadPool.shutdown();
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 126ac61..ece80b7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -66,7 +66,6 @@ public class StyledConverter implements OsmConverter {
 
 	private Clipper clipper = Clipper.NULL_CLIPPER;
 	private Area bbox;
-	private Set<Coord> boundaryCoords = new HashSet<Coord>();
 
 	// restrictions associates lists of turn restrictions with the
 	// Coord corresponding to the restrictions' 'via' node
@@ -248,12 +247,14 @@ public class StyledConverter implements OsmConverter {
 		if(relation instanceof RestrictionRelation) {
 			RestrictionRelation rr = (RestrictionRelation)relation;
 			if(rr.isValid()) {
-				List<RestrictionRelation> lrr = restrictions.get(rr.getViaCoord());
-				if(lrr == null) {
-					lrr = new ArrayList<RestrictionRelation>();
-					restrictions.put(rr.getViaCoord(), lrr);
+				synchronized(restrictions) {
+					List<RestrictionRelation> lrr = restrictions.get(rr.getViaCoord());
+					if(lrr == null) {
+						lrr = new ArrayList<RestrictionRelation>();
+						restrictions.put(rr.getViaCoord(), lrr);
+					}
+					lrr.add(rr);
 				}
-				lrr.add(rr);
 			}
 		}
 	}
@@ -427,6 +428,8 @@ public class StyledConverter implements OsmConverter {
 
 		List<Way> clippedWays = null;
 
+		Set<Coord> boundaryCoords = null;
+
 		if(bbox != null) {
 			List<List<Coord>> lineSegs = LineClipper.clip(bbox, way.getPoints());
 			boundaryCoords = new HashSet<Coord>();
@@ -449,10 +452,12 @@ public class StyledConverter implements OsmConverter {
 					clippedWays.add(nWay);
 					// associate the original Way
 					// to the new Way
-					Way origWay = originalWay.get(way);
-					if(origWay == null)
-						origWay = way;
-					originalWay.put(nWay, origWay);
+					synchronized(originalWay) {
+						Way origWay = originalWay.get(way);
+						if(origWay == null)
+							origWay = way;
+						originalWay.put(nWay, origWay);
+					}
 				}
 			}
 		}
@@ -461,24 +466,24 @@ public class StyledConverter implements OsmConverter {
 			for(Way cw : clippedWays) {
 				while(cw.getPoints().size() > MAX_POINTS_IN_WAY) {
 					Way tail = splitWayAt(cw, MAX_POINTS_IN_WAY - 1);
-					addRoadAfterSplittingLoops(cw, gt);
+					addRoadAfterSplittingLoops(cw, gt, boundaryCoords);
 					cw = tail;
 				}
-				addRoadAfterSplittingLoops(cw, gt);
+				addRoadAfterSplittingLoops(cw, gt, boundaryCoords);
 			}
 		}
 		else {
 			// no bounding box or way was not clipped
 			while(way.getPoints().size() > MAX_POINTS_IN_WAY) {
 				Way tail = splitWayAt(way, MAX_POINTS_IN_WAY - 1);
-				addRoadAfterSplittingLoops(way, gt);
+				addRoadAfterSplittingLoops(way, gt, boundaryCoords);
 				way = tail;
 			}
-			addRoadAfterSplittingLoops(way, gt);
+			addRoadAfterSplittingLoops(way, gt, boundaryCoords);
 		}
 	}
 
-	void addRoadAfterSplittingLoops(Way way, GType gt) {
+	void addRoadAfterSplittingLoops(Way way, GType gt, Set<Coord> boundaryCoords) {
 
 		// check if the way is a loop or intersects with itself
 
@@ -511,7 +516,7 @@ public class StyledConverter implements OsmConverter {
 							log.info("Split way at " + wayPoints.get(splitI).toDegreeString() + " - it has " + (numPointsInWay - splitI - 1 ) + " following segments.");
 							Way loopTail = splitWayAt(way, splitI);
 							// way before split has now been verified
-							addRoadWithoutLoops(way, gt);
+							addRoadWithoutLoops(way, gt, boundaryCoords);
 							// now repeat for the tail of the way
 							way = loopTail;
 							wayWasSplit = true;
@@ -522,7 +527,7 @@ public class StyledConverter implements OsmConverter {
 
 			if(!wayWasSplit) {
 				// no split required so make road from way
-				addRoadWithoutLoops(way, gt);
+				addRoadWithoutLoops(way, gt, boundaryCoords);
 			}
 		}
 	}
@@ -538,7 +543,7 @@ public class StyledConverter implements OsmConverter {
 		return name + "(OSM id " + way.getId() + ")";
 	}
 
-	void addRoadWithoutLoops(Way way, GType gt) {
+	void addRoadWithoutLoops(Way way, GType gt, Set<Coord> boundaryCoords) {
 		List<Integer> nodeIndices = new ArrayList<Integer>();
 		List<Coord> points = way.getPoints();
 		Way trailingWay = null;
@@ -579,10 +584,13 @@ public class StyledConverter implements OsmConverter {
 
 			if(p.getHighwayCount() > 1) {
 				// this point is a node connecting highways
-				Integer nodeId = nodeIdMap.get(p);
-				if(nodeId == null) {
-					// assign a node id
-					nodeIdMap.put(p, nextNodeId++);
+				Integer nodeId;
+				synchronized(nodeIdMap) {
+					nodeId = nodeIdMap.get(p);
+					if(nodeId == null) {
+						// assign a node id
+						nodeIdMap.put(p, nextNodeId++);
+					}
 				}
 				nodeIndices.add(i);
 
@@ -603,7 +611,10 @@ public class StyledConverter implements OsmConverter {
 		elementSetup(line, gt, way);
 		line.setPoints(points);
 
-		MapRoad road = new MapRoad(roadId++, line);
+		MapRoad road;
+		synchronized(this) {
+			road = new MapRoad(roadId++, line);
+		}
 
 		// set road parameters.
 		road.setRoadClass(gt.getRoadClass());
@@ -690,9 +701,12 @@ public class StyledConverter implements OsmConverter {
 			road.setRef(ref);
 		}
 
-		Way origWay = originalWay.get(way);
-		if(origWay == null)
-			origWay = way;
+		Way origWay;
+		synchronized(originalWay) {
+			origWay = originalWay.get(way);
+			if(origWay == null)
+				origWay = way;
+		}
 
 		int numNodes = nodeIndices.size();
 		road.setNumNodes(numNodes);
@@ -707,8 +721,13 @@ public class StyledConverter implements OsmConverter {
 				if(n > 0 && n < points.size() - 1)
 					hasInternalNodes = true;
 				Coord coord = points.get(n);
-				Integer nodeId = nodeIdMap.get(coord);
-				boolean boundary = boundaryCoords.contains(coord);
+				Integer nodeId;
+				synchronized(nodeIdMap) {
+					nodeId = nodeIdMap.get(coord);
+				}
+				boolean boundary = false;
+				if(boundaryCoords != null && boundaryCoords.contains(coord))
+					boundary = true;
 				if(boundary) {
 					log.info("Way " + debugWayName + "'s point #" + n + " at " + points.get(n).toDegreeString() + " is a boundary node");
 				}
@@ -735,7 +754,10 @@ public class StyledConverter implements OsmConverter {
 					}
 				}
 
-				List<RestrictionRelation> theseRestrictions = restrictions.get(coord);
+				List<RestrictionRelation> theseRestrictions;
+				synchronized(restrictions) {
+					theseRestrictions = restrictions.get(coord);
+				}
 				if(theseRestrictions != null) {
 					// this node is the location of one or more
 					// restrictions
@@ -766,7 +788,7 @@ public class StyledConverter implements OsmConverter {
 		lineAdder.add(road);
 
 		if(trailingWay != null)
-			addRoadWithoutLoops(trailingWay, gt);
+			addRoadWithoutLoops(trailingWay, gt, boundaryCoords);
 	}
 
 	// split a Way at the specified point and return the new Way (the
@@ -793,10 +815,12 @@ public class StyledConverter implements OsmConverter {
 			wayPoints.remove(i);
 
 		// associate the original Way to the new Way
-		Way origWay = originalWay.get(way);
-		if(origWay == null)
-			origWay = way;
-		originalWay.put(trailingWay, origWay);
+		synchronized(originalWay) {
+			Way origWay = originalWay.get(way);
+			if(origWay == null)
+				origWay = way;
+			originalWay.put(trailingWay, origWay);
+		}
 
 		return trailingWay;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
index 39ade95..9893a05 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
@@ -19,15 +19,19 @@ package uk.me.parabola.mkgmap.reader.osm.xml;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import java.util.concurrent.Future;
+
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.Exit;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.MapDetails;
 import uk.me.parabola.mkgmap.general.RoadNetwork;
+import uk.me.parabola.mkgmap.main.Main;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
@@ -90,7 +94,7 @@ class Osm5XmlHandler extends DefaultHandler {
 		routing = props.containsKey("route");
 		frigRoundabouts = props.getProperty("frig-roundabouts");
 		ignoreTurnRestrictions = props.getProperty("ignore-turn-restrictions", false);
-		if (props.getProperty("preserve-element-order", false)) {
+		if (Main.preserveElementOrder) {
 			nodeMap = new LinkedHashMap<Long, Node>(5000);
 			wayMap = new LinkedHashMap<Long, Way>(5000);
 			relationMap = new LinkedHashMap<Long, Relation>();
@@ -358,8 +362,53 @@ class Osm5XmlHandler extends DefaultHandler {
 
 		nodeMap = null;
 
-		for (Way w: wayMap.values())
-			converter.convertWay(w);
+		if(Main.preserveElementOrder) {
+			for (Way w : wayMap.values())
+				converter.convertWay(w);
+		}
+		else if(!wayMap.isEmpty()) {
+			class ConvertWayWorker implements Runnable {
+				List<Way> work;
+
+				public ConvertWayWorker(List<Way> work) {
+					this.work = work;
+				}
+
+				public void run() {
+					for(Way w : work)
+						converter.convertWay(w);
+				}
+			}
+
+			List<Way> ways = new ArrayList<Way>(wayMap.values());
+
+			final int maxChunks = Main.numThreads * 2;
+			List<Future<?>> futures = new LinkedList<Future<?>>();
+
+			int chunkSize = (ways.size() + maxChunks - 1) / maxChunks;
+			if(chunkSize < 8)
+				chunkSize = 8;
+			if(Main.numThreads == 1 || chunkSize >= ways.size()) {
+				futures.add(Main.threadPool.submit(new ConvertWayWorker(ways)));
+			}
+			else {
+				for(int base = 0; base < ways.size(); base += chunkSize) {
+					int limit = base + chunkSize;
+					if(limit > ways.size())
+						limit = ways.size();
+					futures.add(Main.threadPool.submit(new ConvertWayWorker(ways.subList(base, limit))));
+				}
+			}
+			// wait for all the work to be completed
+			while(!futures.isEmpty()) {
+				try {
+					futures.remove(0).get();
+				}
+				catch(Exception e) {
+					log.error("" + e);
+				}
+			}
+		}
 
 		wayMap = null;
 
