Index: src/uk/me/parabola/util/ElementQuadTreeNode.java
===================================================================
--- src/uk/me/parabola/util/ElementQuadTreeNode.java	(revision 0)
+++ src/uk/me/parabola/util/ElementQuadTreeNode.java	(revision 0)
@@ -0,0 +1,330 @@
+package uk.me.parabola.util;
+
+import java.awt.Rectangle;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.Relation;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+
+public class ElementQuadTreeNode {
+
+	private static final Logger log = Logger.getLogger(ElementQuadTreeNode.class);
+
+	private static final int MAX_POINTS = 20;
+
+	private MultiHashMap<Coord, Element> points;
+	private final Area bounds;
+	private Area coveredBounds;
+
+	public Area getCoveredBounds() {
+		return coveredBounds;
+	}
+
+	private ElementQuadTreeNode[] children;
+
+	public static final class QuadTreePolygon {
+		private final java.awt.geom.Area javaArea;
+		private final Area bbox;
+
+		public QuadTreePolygon(java.awt.geom.Area javaArea) {
+			this.javaArea = javaArea;
+			Rectangle bboxRect = javaArea.getBounds();
+			bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
+					+ bboxRect.height, bboxRect.x + bboxRect.width);
+		}
+
+		public QuadTreePolygon(List<Coord> points) {
+			this(new java.awt.geom.Area(Java2DConverter.createPolygon(points)));
+		}
+
+		public QuadTreePolygon(Collection<List<Coord>> polygonList) {
+			this.javaArea = new java.awt.geom.Area();
+			for (List<Coord> polygon : polygonList) {
+				javaArea.add(new java.awt.geom.Area(Java2DConverter
+						.createPolygon(polygon)));
+			}
+			Rectangle bboxRect = javaArea.getBounds();
+			bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
+					+ bboxRect.height, bboxRect.x + bboxRect.width);
+		}
+
+		public Area getBbox() {
+			return bbox;
+		}
+
+		public java.awt.geom.Area getArea() {
+			return javaArea;
+		}
+	}
+
+	public ElementQuadTreeNode(Area bounds) {
+		this(bounds, Collections.<Element> emptyList());
+	}
+
+	
+	public ElementQuadTreeNode(Collection<Element> elements) {
+		this.children = null;
+
+		int minLat = Integer.MAX_VALUE;
+		int maxLat = Integer.MIN_VALUE;
+		int minLong = Integer.MAX_VALUE;
+		int maxLong = Integer.MIN_VALUE;
+
+		this.points = new MultiHashMap<Coord, Element>();
+		for (Element el : elements) {
+			Collection<Coord> coords = null;
+			if (el instanceof Relation) {
+				continue;
+			} else if (el instanceof Way) {
+				Way w = (Way) el;
+				if (w.isClosed()) {
+					coords = w.getPoints().subList(0, w.getPoints().size()-1);	
+				} else {
+					coords = w.getPoints();
+				}
+			} else if (el instanceof Node) {
+				coords = Collections.singleton(((Node) el).getLocation());
+			}
+			
+			for (Coord c : coords) {
+				if (c.getLatitude() < minLat) {
+					minLat = c.getLatitude();
+				}
+				if (c.getLatitude() > maxLat) {
+					maxLat = c.getLatitude();
+				}
+				if (c.getLongitude() < minLong) {
+					minLong = c.getLongitude();
+				}
+				if (c.getLongitude() > maxLong) {
+					maxLong = c.getLongitude();
+				}
+				points.add(c, el);
+			}
+
+		}
+		coveredBounds = new Area(minLat, minLong, maxLat, maxLong);
+		this.bounds = coveredBounds;
+		
+		if (points.size() > MAX_POINTS) {
+			split();
+		} 
+	}
+
+	public ElementQuadTreeNode(Area bounds, Collection<Element> elements) {
+		this.bounds = bounds;
+		this.children = null;
+
+		int minLat = Integer.MAX_VALUE;
+		int maxLat = Integer.MIN_VALUE;
+		int minLong = Integer.MAX_VALUE;
+		int maxLong = Integer.MIN_VALUE;
+
+		this.points = new MultiHashMap<Coord, Element>();
+		for (Element el : elements) {
+			Collection<Coord> coords = null;
+			if (el instanceof Relation) {
+				continue;
+			} else if (el instanceof Way) {
+				Way w = (Way) el;
+				if (w.isClosed()) {
+					coords = w.getPoints().subList(0, w.getPoints().size()-1);	
+				} else {
+					coords = w.getPoints();
+				}
+			} else if (el instanceof Node) {
+				coords = Collections.singleton(((Node) el).getLocation());
+			}
+			
+			for (Coord c : coords) {
+				if (c.getLatitude() < minLat) {
+					minLat = c.getLatitude();
+				}
+				if (c.getLatitude() > maxLat) {
+					maxLat = c.getLatitude();
+				}
+				if (c.getLongitude() < minLong) {
+					minLong = c.getLongitude();
+				}
+				if (c.getLongitude() > maxLong) {
+					maxLong = c.getLongitude();
+				}
+				points.add(c, el);
+			}
+
+		}
+		coveredBounds = new Area(minLat, minLong, maxLat, maxLong);
+
+		if (points.size() > MAX_POINTS) {
+			split();
+		} 
+	}
+
+	public Area getBounds() {
+		return this.bounds;
+	}
+
+	private boolean add(Coord c, Element element) {
+		if (coveredBounds == null) {
+			coveredBounds = new Area(c.getLatitude(), c.getLongitude(),
+					c.getLatitude(), c.getLongitude());
+		} else if (coveredBounds.contains(c) == false) {
+			coveredBounds = new Area(Math.min(coveredBounds.getMinLat(),
+					c.getLatitude()), Math.min(coveredBounds.getMinLong(),
+					c.getLongitude()), Math.max(coveredBounds.getMaxLat(),
+					c.getLatitude()), Math.max(coveredBounds.getMaxLong(),
+					c.getLongitude()));
+		}
+		if (isLeaf()) {
+			points.add(c,element);
+			if (points.size() > MAX_POINTS)
+				split();
+			return true;
+		} else {
+			for (ElementQuadTreeNode nodes : children) {
+				if (nodes.getBounds().contains(c)) {
+					return nodes.add(c, element);
+				}
+			}
+			return false;
+		}
+	}
+
+	public boolean add(Element c) {
+		if (c instanceof Relation) {
+			log.error("Relations are not supported by this quadtree implementation. Skipping relation "
+					+ c.toBrowseURL());
+			return false;
+		} else if (c instanceof Way) {
+			// add all points to the tree
+			Way w = (Way) c;
+			List<Coord> points;
+			if (w.isClosed()) {
+				points = w.getPoints().subList(0, w.getPoints().size()-1);	
+			} else {
+				points = w.getPoints();
+			}
+			
+			boolean allOk = true;
+			for (Coord cp : points) {
+				allOk = add(cp, c) && allOk;
+			}
+			return allOk;
+		} else if (c instanceof Node) {
+			return add(((Node) c).getLocation(), c);
+		} else {
+			log.error("Unsupported element type: "+c);
+			return false;
+		}
+	}
+
+	public Set<Element> get(Area bbox, Set<Element> resultList) {
+		if (isLeaf()) {
+			if (bbox.getMinLat() <= coveredBounds.getMinLat()
+					&& bbox.getMaxLat() >= coveredBounds.getMaxLat()
+					&& bbox.getMinLong() <= coveredBounds.getMinLong()
+					&& bbox.getMaxLong() >= coveredBounds.getMaxLong()) {
+
+				// the bounding box is contained completely in the bbox
+				// => add all points without further check
+				for (List<Element> elem : points.values())
+					resultList.addAll(elem);
+			} else {
+				// check each point
+				for (Entry<Coord, List<Element>> e : points.entrySet()) {
+					if (bbox.contains(e.getKey())) {
+						resultList.addAll(e.getValue());
+					}
+				}
+			}
+		} else {
+			for (ElementQuadTreeNode child : children) {
+				if (bbox.intersects(child.getCoveredBounds())) {
+					resultList = child.get(bbox, resultList);
+				}
+			}
+		}
+		return resultList;
+	}
+
+	public Set<Element> get(QuadTreePolygon polygon,
+			Set<Element> resultList) {
+		if (polygon.getBbox().intersects(getBounds())) {
+			if (isLeaf()) {
+				for (Entry<Coord, List<Element>> e : points.entrySet()) {
+					if (polygon.getArea().contains(e.getKey().getLongitude(),
+							e.getKey().getLatitude())) {
+						resultList.addAll(e.getValue());
+					}
+				}
+			} else {
+				for (ElementQuadTreeNode child : children) {
+					if (polygon.getBbox().intersects(child.getBounds())) {
+						java.awt.geom.Area subArea = (java.awt.geom.Area) polygon
+								.getArea().clone();
+						subArea.intersect(createArea(child.getBounds()));
+						child.get(new QuadTreePolygon(subArea), resultList);
+					}
+				}
+			}
+		}
+		return resultList;
+
+	}
+
+	private java.awt.geom.Area createArea(Area bbox) {
+		return new java.awt.geom.Area(new Rectangle(bbox.getMinLong(),
+				bbox.getMinLat(), bbox.getWidth(), bbox.getHeight()));
+	}
+
+	public boolean isLeaf() {
+		return points != null;
+	}
+
+	private void split() {
+		if (bounds.getHeight() <= 1 || bounds.getWidth() <= 1) {
+			return;
+		}
+
+		int halfLat = (bounds.getMinLat() + bounds.getMaxLat()) / 2;
+		int halfLong = (bounds.getMinLong() + bounds.getMaxLong()) / 2;
+		children = new ElementQuadTreeNode[4];
+
+		Area swBounds = new Area(bounds.getMinLat(), bounds.getMinLong(),
+				halfLat, halfLong);
+		Area nwBounds = new Area(halfLat + 1, bounds.getMinLong(),
+				bounds.getMaxLat(), halfLong);
+		Area seBounds = new Area(bounds.getMinLat(), halfLong + 1, halfLat,
+				bounds.getMaxLong());
+		Area neBounds = new Area(halfLat + 1, halfLong + 1, bounds.getMaxLat(),
+				bounds.getMaxLong());
+
+		children[0] = new ElementQuadTreeNode(swBounds);
+		children[1] = new ElementQuadTreeNode(nwBounds);
+		children[2] = new ElementQuadTreeNode(seBounds);
+		children[3] = new ElementQuadTreeNode(neBounds);
+
+		MultiHashMap<Coord, Element> copyPoints = points;
+		points = null;
+		for (Entry<Coord, List<Element>> c : copyPoints.entrySet()) {
+			for (Element el : c.getValue())
+				add(c.getKey(), el);
+		}
+	}
+
+	public void clear() {
+		this.children = null;
+		points = new MultiHashMap<Coord, Element>();
+		coveredBounds = new Area(Integer.MAX_VALUE, Integer.MAX_VALUE,
+				Integer.MIN_VALUE, Integer.MIN_VALUE);
+	}
+}
Index: src/uk/me/parabola/util/MultiHashMap.java
===================================================================
--- src/uk/me/parabola/util/MultiHashMap.java	(revision 1881)
+++ src/uk/me/parabola/util/MultiHashMap.java	(working copy)
@@ -21,7 +21,7 @@
 	* @return a list of values for the given keys or the empty list of no such
 	*         value exist.
 	*/
-	public List<V> get(K key) {
+	public List<V> get(Object key) {
 		List<V> result = super.get(key);
 		return result == null ? emptyList : result;
 	}
Index: src/uk/me/parabola/util/ElementQuadTree.java
===================================================================
--- src/uk/me/parabola/util/ElementQuadTree.java	(revision 0)
+++ src/uk/me/parabola/util/ElementQuadTree.java	(revision 0)
@@ -0,0 +1,144 @@
+package uk.me.parabola.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.util.ElementQuadTreeNode.QuadTreePolygon;
+
+public class ElementQuadTree {
+
+	private final ElementQuadTreeNode root;
+	private long itemCount;
+
+	public ElementQuadTree(Area bbox) {
+		this.root = new ElementQuadTreeNode(bbox);
+		this.itemCount = 0;
+	}
+
+	public ElementQuadTree(Collection<Element> elements) {
+		this.root = new ElementQuadTreeNode(elements);
+		this.itemCount = 0;
+	}
+	
+	public boolean addAll(Collection<Element> elements) {
+		boolean oneAdded = false;
+		for (Element element : elements) {
+			oneAdded = add(element) | oneAdded;
+		}
+		return oneAdded;
+	}
+
+	public boolean add(Element element) {
+
+		boolean added = root.add(element);
+		if (added) {
+			itemCount++;
+		}
+		return added;
+	}
+
+	public Set<Element> get(Area bbox) {
+		return root.get(bbox, new HashSet<Element>());
+	}
+
+	public Set<Element> get(java.awt.geom.Area polygon) {
+		return root.get(new QuadTreePolygon(polygon), new HashSet<Element>());
+	}
+	
+	public Set<Element> get(Collection<List<Coord>> polygons) {
+		return root.get(new QuadTreePolygon(polygons),
+				new HashSet<Element>());
+	}
+
+	public Set<Element> get(List<Coord> polygon) {
+		if (polygon.size() < 3) {
+			return new HashSet<Element>();
+		}
+		if (polygon.get(0).equals(polygon.get(polygon.size() - 1)) == false) {
+			return new HashSet<Element>();
+		}
+		return root.get(new QuadTreePolygon(polygon),
+				new HashSet<Element>());
+	}
+
+//	public Set<Element> get(List<Coord> polygon, int offset) {
+//		if (polygon.size() < 3) {
+//			return new HashSet<Element>();
+//		}
+//		if (polygon.get(0).equals(polygon.get(polygon.size() - 1)) == false) {
+//			return null;
+//		}
+//		Set<Element> points = root.get(new QuadTreePolygon(polygon),
+//				new HashSet<Element>());
+//		if (offset > 0) {
+//			for (Coord c : new ArrayList<Coord>(points.keySet())) {
+//				if (isCloseToPolygon(c, polygon, offset)) {
+//					points.remove(c);
+//				}
+//			}
+//		}
+//		return points;
+//	}
+
+	public void clear() {
+		itemCount = 0;
+		root.clear();
+	}
+
+	// TODO itemCount is not a good counter (coords or elements?)
+	private long getSize() {
+		return itemCount;
+	}
+
+	private boolean isCloseToPolygon(Coord point, List<Coord> polygon,
+			int gap) {
+		Iterator<Coord> polyIter = polygon.iterator();
+		Coord c2 = polyIter.next();
+		while (polyIter.hasNext()) {
+			Coord c1 = c2;
+			c2 = polyIter.next();
+			double dist = distanceToSegment(c1, c2, point);
+			if (dist <= gap) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Calculates the distance to the given segment in meter.
+	 * @param spoint1 segment point 1
+	 * @param spoint2 segment point 2
+	 * @param point point
+	 * @return the distance in meter
+	 */
+	private double distanceToSegment(Coord spoint1, Coord spoint2, Coord point) {
+
+		double dx = spoint2.getLongitude() - spoint1.getLongitude();
+		double dy = spoint2.getLatitude() - spoint1.getLatitude();
+
+		if ((dx == 0) && (dy == 0)) {
+			return spoint1.distance(point);
+		}
+
+		double frac = ((point.getLongitude() - spoint1.getLongitude()) * dx + (point
+				.getLatitude() - spoint1.getLatitude()) * dy)
+				/ (dx * dx + dy * dy);
+
+		if (frac < 0) {
+			return spoint1.distance(point);
+		} else if (frac > 1) {
+			return spoint2.distance(point);
+		} else {
+			return spoint1.makeBetweenPoint(spoint2, frac).distance(point);
+		}
+
+	}
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java	(working copy)
@@ -52,7 +52,9 @@
 	private Style style;
 	private final OsmReadingHooks[] POSSIBLE_HOOKS = {
 			new SeaGenerator(),
+			new MultiPolygonFinishHook(),
 			new HighwayHooks(),
+			new AddressHook(),
 	};
 	private OsmConverter converter;
 	private final Set<String> usedTags = new HashSet<String>();
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java	(working copy)
@@ -18,6 +18,7 @@
 
 import java.awt.*;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.Utils;
@@ -92,12 +93,7 @@
 	}
 
 	public void reverse() {
-		int numPoints = points.size();
-		for(int i = 0; i < numPoints/2; ++i) {
-			Coord t = points.get(i);
-			points.set(i, points.get(numPoints - 1 - i));
-			points.set(numPoints - 1 - i, t);
-		}
+		Collections.reverse(points);
 	}
 
 	public boolean isClosed() {
Index: src/uk/me/parabola/mkgmap/reader/osm/Element.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Element.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/reader/osm/Element.java	(working copy)
@@ -27,6 +27,10 @@
 	private String name;
 	private long id;
 
+	public int getTagCount() {
+		return (tags == null ? 0 : tags.size());
+	}
+	
 	/**
 	 * Add a tag to the way.  Some tags are recognised separately and saved in
 	 * separate fields.
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java	(revision 0)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonFinishHook.java	(revision 0)
@@ -0,0 +1,49 @@
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.Arrays;
+
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.EnhancedProperties;
+
+public class MultiPolygonFinishHook extends OsmReadingHooksAdaptor {
+	private static final Logger log = Logger.getLogger(MultiPolygonFinishHook.class);
+	
+	private ElementSaver saver;
+	
+	public MultiPolygonFinishHook() {
+	}
+
+	@Override
+	public boolean init(ElementSaver saver, EnhancedProperties props) {
+		this.saver = saver;
+		return true;
+	}
+
+	@Override
+	public void end() {
+		long t1 = System.currentTimeMillis();
+		log.error("Finishing multipolygons");
+		for (Way way : saver.getWays().values()) {
+			String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG);
+			if (removeTag == null) {
+				continue;
+			}
+			if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) {
+				if (log.isDebugEnabled())
+					log.debug("Remove all tags from way",way.getId(),way.toTagString());
+				way.removeAllTags();
+			} else {
+				String[] tagsToRemove = removeTag.split(";");
+				if (log.isDebugEnabled())
+					log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString());
+				for (String rTag : tagsToRemove) {
+					way.deleteTag(rTag);
+				}
+				way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG);
+			}
+		}
+		log.error("Multipolygon hook finished in "+(System.currentTimeMillis()-t1)+" ms");
+
+	}
+
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java	(revision 0)
+++ src/uk/me/parabola/mkgmap/reader/osm/AddressHook.java	(revision 0)
@@ -0,0 +1,293 @@
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.ElementQuadTree;
+import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.util.MultiHashMap;
+
+public class AddressHook extends OsmReadingHooksAdaptor {
+	private static final Logger log = Logger.getLogger(AddressHook.class);
+
+	private ElementSaver saver;
+
+	private static class BoundInfo {
+		private final String osmKey;
+		private final String mkgmapTag;
+		private final Collection<String> osmTags;
+
+		public BoundInfo(String osmKey, String mkgmapTag, String... osmTags) {
+			super();
+			this.osmKey = osmKey;
+			this.mkgmapTag = mkgmapTag;
+			this.osmTags = new ArrayList<String>(Arrays.asList(osmTags));
+		}
+
+		public String getMkgmapTag() {
+			return mkgmapTag;
+		}
+
+		public Collection<String> getOsmTags() {
+			return osmTags;
+		}
+
+		public String getOsmKey() {
+			return osmKey;
+		}
+
+	}
+
+	// private final TreeMap<Integer, BoundInfo> levelTags = new
+	// TreeMap<Integer, BoundInfo>() {
+	// {
+	// put(2, new BoundInfo(2, "mkgmap:country", "addr:country",
+	// "is_in:country"));
+	// put(3, new BoundInfo(3, "mkgmap:province", "addr:province"));
+	// put(4, new BoundInfo(4, "mkgmap:province", "addr:province"));
+	// put(5, new BoundInfo(5, "mkgmap:district", "addr:district"));
+	// put(6, new BoundInfo(6, "mkgmap:subdistrict", "addr:subdistrict"));
+	// put(7, new BoundInfo(7, "mkgmap:subdistrict", "addr:subdistrict"));
+	// put(8, new BoundInfo(8, "mkgmap:city", "addr:city","is_in:city"));
+	// put(9, new BoundInfo(9, "mkgmap:city", "addr:city","is_in:city"));
+	// put(10, new BoundInfo(10, "mkgmap:city", "addr:city","is_in:city"));
+	// }
+	// };
+	private final List<BoundInfo> levelTags = new ArrayList<BoundInfo>() {
+		{
+			add(new BoundInfo("admin_level=2", "mkgmap:country",
+					"addr:country", "is_in:country"));
+			add(new BoundInfo("admin_level=3", "mkgmap:province",
+					"addr:province"));
+			add(new BoundInfo("admin_level=4", "mkgmap:province",
+					"addr:province"));
+			add(new BoundInfo("admin_level=5", "mkgmap:district",
+					"addr:district"));
+			add(new BoundInfo("admin_level=6", "mkgmap:subdistrict",
+					"addr:subdistrict"));
+			add(new BoundInfo("admin_level=7", "mkgmap:subdistrict",
+					"addr:subdistrict"));
+			add(new BoundInfo("admin_level=8", "mkgmap:city", "addr:city",
+					"is_in:city"));
+			add(new BoundInfo("admin_level=9", "mkgmap:city", "addr:city",
+					"is_in:city"));
+			add(new BoundInfo("admin_level=10", "mkgmap:city", "addr:city",
+					"is_in:city"));
+			add(new BoundInfo("postal_code", "mkgmap:zip", "addr:postcode",
+					"postal_code"));
+		}
+	};
+
+	public boolean init(ElementSaver saver, EnhancedProperties props) {
+		this.saver = saver;
+		return true;
+	}
+
+	public void end() {
+		long t1 = System.currentTimeMillis();
+		log.info("Starting with address hook");
+		List<Element> elements = new ArrayList<Element>(saver.getWays().size()
+				+ saver.getNodes().size());
+
+		MultiHashMap<String, Way> boundaries = new MultiHashMap<String, Way>();
+
+		for (Node node : saver.getNodes().values()) {
+			if (node.getTagCount() > 0) {
+				elements.add(node);
+			}
+		}
+		for (Way way : saver.getWays().values()) {
+			if (way.getTagCount() == 0) {
+				continue;
+			}
+			if (way.isClosed()
+					&& "polyline".equals(way.getTag("mkgmap:stylefilter")) == false) {
+
+				if ("administrative".equals(way.getTag("boundary"))
+						&& way.getTag("admin_level") != null) {
+					try {
+						int adminlevel = Integer.valueOf(way
+								.getTag("admin_level"));
+						if (adminlevel >= 0 && adminlevel <= 10) {
+							boundaries.add("admin_level=" + adminlevel, way);
+						}
+					} catch (NumberFormatException nfe) {
+						log.error(nfe, nfe);
+					}
+				} else if ("postal_code".equals(way.getTag("boundary"))) {
+						boundaries.add("postal_code", way);
+				}
+			}
+
+			// in any case add it to the element list
+			elements.add(way);
+
+		}
+
+		log.info("Creating quadtree for", elements.size(), "elements");
+		ElementQuadTree quadTree = new ElementQuadTree(elements);
+		log.info("Quadtree created");
+		elements.clear();
+
+		// first add the tags to the boundaries itself
+		for (BoundInfo boundaryInfo : levelTags) {
+			String tag = boundaryInfo.getMkgmapTag();
+			log.info("Preparing", boundaryInfo.getOsmKey(), "with tag", tag);
+			List<Way> levelBoundaries = boundaries
+					.get(boundaryInfo.getOsmKey());
+			log.info(levelBoundaries.size(), "boundaries");
+			for (Way boundary : levelBoundaries) {
+				if (boundaryInfo.getOsmKey().startsWith("admin_level")) {
+					String name = getName(boundary);
+					if (name == null) {
+						continue;
+					}
+
+					boundary.addTag(tag, name);
+				} else if (boundaryInfo.getOsmKey().startsWith("postal_code")) {
+					String zip = getZip(boundary);
+					if (zip != null) {
+						boundary.addTag(tag, zip);
+					} else {
+						log.error("No zip information in "+boundary.getId()+" "+boundary.toTagString());
+					}
+				}
+			}
+		}
+
+		for (BoundInfo boundaryInfo : levelTags) {
+			String tag = boundaryInfo.getMkgmapTag();
+			log.info("Processing", boundaryInfo.getOsmKey(), "with tag", tag);
+
+			java.awt.geom.Area tileArea = Java2DConverter
+					.createBoundsArea(saver.getBoundingBox());
+
+			List<Way> levelBoundaries = boundaries
+					.get(boundaryInfo.getOsmKey());
+			log.info(levelBoundaries.size(), "boundaries");
+			for (Way boundary : levelBoundaries) {
+				String name = (boundaryInfo.getOsmKey().startsWith("postal_code") ? getZip(boundary) :  getName(boundary));
+				if (name == null) {
+					log.error("Cannot process boundary "+boundary.getId()+" "+boundary.toTagString());
+					continue;
+				}
+
+				java.awt.geom.Area boundArea = Java2DConverter
+						.createArea(boundary.getPoints());
+				if (boundaryInfo.getOsmKey().equals("admin_level=2")) {
+					tileArea.subtract(boundArea);
+				}
+
+				log.info("Checking", boundary.toTagString());
+				Set<Element> uniqueElems = quadTree.get(boundArea);
+
+				for (Element elem : uniqueElems) {
+					if (elem.getTag(tag) == null) {
+						if (log.isDebugEnabled())
+							log.debug("Add tag", tag + "=" + name, "to",
+									elem.toTagString());
+						elem.addTag(tag, name);
+					}
+				}
+			}
+
+			if (boundaryInfo.getOsmKey().equals("admin_level=2")) {
+
+				Set<Element> remainingElements = quadTree.get(tileArea);
+
+				List<Element> tagLess = new ArrayList<Element>();
+				Collection<String> osmTags = boundaryInfo.getOsmTags();
+				Map<String, Integer> counter = new HashMap<String, Integer>();
+				for (Element elem : remainingElements) {
+					String refTagValue = null;
+					for (String osmTag : osmTags) {
+						refTagValue = elem.getTag(osmTag);
+						if (refTagValue != null) {
+							break;
+						}
+					}
+					if (refTagValue != null) {
+						Integer i = counter.get(refTagValue);
+						if (i == null) {
+							counter.put(refTagValue, 1);
+						} else {
+							counter.put(refTagValue, i + 1);
+						}
+					} else {
+						tagLess.add(elem);
+					}
+				}
+
+				log.error("Tag " + osmTags + " tagless: " + tagLess.size());
+				log.error("Distribution: " + counter);
+
+				String maxTag = null;
+				int maxCount = 0;
+				for (Entry<String, Integer> tagEntry : counter.entrySet()) {
+					if (maxCount < tagEntry.getValue()) {
+						maxCount = tagEntry.getValue();
+						maxTag = tagEntry.getKey();
+					}
+				}
+
+				if (maxCount > 0) {
+					for (Element elem : remainingElements) {
+						if (elem.getTag(tag) == null)
+							elem.addTag(tag, maxTag);
+					}
+				}
+			}
+		}
+		log.error("Address hook finished in "
+				+ (System.currentTimeMillis() - t1) + " ms");
+	}
+
+	private String getName(Element element) {
+		String nameTag = element.getTag("name");
+		if (nameTag == null) {
+			return null;
+		}
+
+		String[] nameParts = nameTag.split(Pattern.quote(","));
+		if (nameParts.length == 0) {
+			return null;
+		}
+		return nameParts[0].trim().intern();
+	}
+
+	private String getZip(Element element) {
+		String zip = element.getTag("postal_code");
+		if (zip == null) {
+			String nameTag = element.getTag("name");
+			if (nameTag != null) {
+				String[] nameParts = nameTag.split(Pattern.quote(" "));
+				if (nameParts.length > 0) {
+					zip = nameParts[0].trim();
+				}
+			}
+		}
+		return zip;
+	}
+	
+	@Override
+	public Set<String> getUsedTags() {
+		Set<String> tags = new HashSet<String>();
+		tags.add("boundary");
+		tags.add("admin_level");
+		tags.add("postal_code");
+		tags.add("name");
+		return tags;
+	}
+
+}
Index: src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java	(working copy)
@@ -14,7 +14,6 @@
 
 import java.util.AbstractMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -206,7 +205,6 @@
 	
 	public void finishLoading() {
 		coordMap = null;
-		finishMultiPolygons();
 	}
 
 	/**
@@ -244,29 +242,6 @@
 		relationMap = null;
 	}
 
-
-	private void finishMultiPolygons() {
-		for (Way way : wayMap.values()) {
-			String removeTag = way.getTag(MKGMAP_REMOVE_TAG);
-			if (removeTag == null) {
-				continue;
-			}
-			if (MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) {
-				if (log.isDebugEnabled())
-					log.debug("Remove all tags from way",way.getId(),way.toTagString());
-				way.removeAllTags();
-			} else {
-				String[] tagsToRemove = removeTag.split(";");
-				if (log.isDebugEnabled())
-					log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString());
-				for (String rTag : tagsToRemove) {
-					way.deleteTag(rTag);
-				}
-				way.deleteTag(MKGMAP_REMOVE_TAG);
-			}
-		}
-	}
-
 	/**
 	 *
 	 * "soft clip" each way that crosses a boundary by adding a point
@@ -564,6 +539,10 @@
 		map.put(p, inc);
 	}
 
+	public Map<Long, Node> getNodes() {
+		return nodeMap;
+	}
+	
 	public Map<Long, Way> getWays() {
 		return wayMap;
 	}
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(working copy)
@@ -680,6 +680,25 @@
 		
 		if(city == null)
 			city = element.getTag("openGeoDB:name");
+
+		// set the results of the address hook
+		if(country == null)
+			country = element.getTag("mkgmap:country");
+
+		if(zip == null) 
+			zip = element.getTag("mkgmap:zip");
+		
+		if(city == null)
+			city = element.getTag("mkgmap:city");
+		if (city == null) 
+			city = element.getTag("mkgmap:subdistrict");
+		else if (region == null)
+			region = element.getTag("mkgmap:subdistrict");
+		
+		if (region == null)
+			region = element.getTag("mkgmap:district");
+		if (region == null)
+			region = element.getTag("mkgmap:province");
 		
 		if(city != null)
 			ms.setCity(city);
Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java	(working copy)
@@ -258,18 +258,22 @@
 			{
 				Country thisCountry;
 
-				String CountryStr = p.getCountry();
-				String RegionStr  = p.getRegion();
+				String countryStr = p.getCountry();
+				if (countryStr != null) {
+					countryStr = locator.fixCountryString(countryStr);
+					p.setCountry(countryStr);
+				}
+				String regionStr  = p.getRegion();
 
-				if(CountryStr != null)
-					thisCountry = lbl.createCountry(CountryStr, locator.getCountryCode(CountryStr));
-				else
+				if(countryStr != null) {
+					thisCountry = lbl.createCountry(countryStr, locator.getCountryCode(countryStr));
+				} else
 					thisCountry = country;
 
 				Region thisRegion;
-				if(RegionStr != null)
+				if(regionStr != null)
 				{
-					thisRegion = lbl.createRegion(thisCountry,RegionStr, null);
+					thisRegion = lbl.createRegion(thisCountry,regionStr, null);
 				}
 				else
 					thisRegion = region;
@@ -290,25 +294,29 @@
 			if(line.isRoad()) {
 				String cityName = line.getCity();
 				String cityCountryName = line.getCountry();
+				if (cityCountryName != null) {
+					cityCountryName = locator.fixCountryString(cityCountryName);
+					line.setCountry(cityCountryName);
+				}
 				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();
-					}
-				}
+//				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();
+//					}
+//				}
 
 				if(cityName != null) {
 
@@ -341,12 +349,15 @@
 			if(p.isExit()) {
 				processExit(map, (MapExitPoint)p);
 			}
-			else if(!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses)) {
+			else if (!p.isCity())
+//				if(!p.isCity() && !p.hasExtendedType() && (p.isRoadNamePOI() || poiAddresses)) 
+				{
+				
 				boolean doAutofill;
-				if(locationAutofillLevel > 0 || p.isRoadNamePOI())
+//				if(locationAutofillLevel > 0 || p.isRoadNamePOI())
 					doAutofill = true;
-				else
-					doAutofill = false;
+//				else
+//					doAutofill = false;
 				
 				
 				String countryStr = p.getCountry();
Index: src/uk/me/parabola/mkgmap/build/Locator.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/Locator.java	(revision 1881)
+++ src/uk/me/parabola/mkgmap/build/Locator.java	(working copy)
@@ -55,14 +55,15 @@
 import java.util.Collection;
 import java.util.List;
 
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapPointFastFindMap;
-import uk.me.parabola.mkgmap.general.MapPointMultiMap;
 
 public class Locator {
+	private static final Logger log = Logger.getLogger(Locator.class);
 
 	private final MapPointFastFindMap cityMap  					= new MapPointFastFindMap();
-	private final MapPointMultiMap  	fuzzyCityMap			= new MapPointMultiMap();
+//	private final MapPointMultiMap  	fuzzyCityMap			= new MapPointMultiMap();
 	private final List<MapPoint> placesMap  =  new ArrayList<MapPoint>();
 
 	private final LocatorConfig locConfig = new LocatorConfig();
@@ -74,21 +75,24 @@
 	public void addLocation(MapPoint p) 
 	{
 		resolveIsInInfo(p); // Pre-process the is_in field
-		
-		if(autoFillLevel < 1 &&  p.getCity() == null)
-		{			
-			// Without auto-fill city name is the name of the tag
-			p.setCity(p.getName());
-		}
+
+
+//		if(autoFillLevel < 1 &&  p.getCity() == null)
+//		{			
+//			// Without auto-fill city name is the name of the tag
+//			p.setCity(p.getName());
+//		}
 		
 		if(p.getCity() != null)
 		{
+			if (log.isDebugEnabled())
+			log.debug(p.getCity()+" "+p.getRegion()+" "+p.getCountry());
 			cityMap.put(p.getCity(), p);
 			
-			fuzzyCityMap.put(fuzzyDecode(p.getCity()),p);
-
-			if(p.getName() != null && !p.getCity().equals(p.getName())) // Name variants ?
-				fuzzyCityMap.put(fuzzyDecode(p.getName()),p);
+//			fuzzyCityMap.put(fuzzyDecode(p.getCity()),p);
+//
+//			if(p.getName() != null && !p.getCity().equals(p.getName())) // Name variants ?
+//				fuzzyCityMap.put(fuzzyDecode(p.getName()),p);
 		}
 		else
 		{
@@ -170,13 +174,15 @@
 			if(cityList.length > 1 &&
 				isContinent(cityList[cityList.length-1]))	// Is last a continent ?
 			{
-				// The one before continent should be the country
-				p.setCountry(fixCountryString(cityList[cityList.length-2].trim()));
-
+				if (p.getCountry() == null) {
+					// The one before continent should be the country
+					p.setCountry(fixCountryString(cityList[cityList.length-2].trim()));
+				}
+				
 				// aks the config which info to use for region info				
 				int offset = locConfig.getRegionOffset(p.getCountry()) + 1;
 
-				if(cityList.length > offset)
+				if(cityList.length > offset && p.getRegion() == null)
 					p.setRegion(cityList[cityList.length-(offset+1)].trim());
 
 			}
@@ -185,12 +191,14 @@
 
 			if(cityList.length > 1 && isContinent(cityList[0]))	// Is first a continent ?
 			{
-				// The one before continent should be the country
-				p.setCountry(fixCountryString(cityList[1].trim()));
+				if (p.getCountry() == null) {
+					// The one before continent should be the country
+					p.setCountry(fixCountryString(cityList[1].trim()));
+				}
 				
 				int offset = locConfig.getRegionOffset(p.getCountry()) + 1;
 
-				if(cityList.length > offset)
+				if(cityList.length > offset && p.getRegion() == null)
 					p.setRegion(cityList[offset].trim());
 			}	
 
@@ -208,8 +216,8 @@
 
 					int offset = locConfig.getRegionOffset(countryStr) + 1;
 
-					if(cityList.length > offset)
-		     		p.setRegion(cityList[cityList.length-(offset+1)].trim());	
+					if(cityList.length > offset && p.getRegion() == null)
+						p.setRegion(cityList[cityList.length-(offset+1)].trim());	
 				}
 			}
 		}
@@ -259,21 +267,21 @@
 			}
 	 	}
 		
-		nextCityList = fuzzyCityMap.getList(fuzzyDecode(p.getCity()));
-		
-		if(nextCityList != null)
-		{
-			for (MapPoint nextCity: nextCityList)		
-			{
-				Double dist = p.getLocation().distance(nextCity.getLocation());
-
-				if(dist < minDist)
-				{
-					minDist = dist;
-					near = nextCity;
-				}
-			}
-	 	}
+//		nextCityList = fuzzyCityMap.getList(fuzzyDecode(p.getCity()));
+//		
+//		if(nextCityList != null)
+//		{
+//			for (MapPoint nextCity: nextCityList)		
+//			{
+//				Double dist = p.getLocation().distance(nextCity.getLocation());
+//
+//				if(dist < minDist)
+//				{
+//					minDist = dist;
+//					near = nextCity;
+//				}
+//			}
+//	 	}
 		
 		if(near != null && minDist < 30000) // Wrong hit more the 30 km away ?
 			return near;
@@ -300,9 +308,9 @@
 				String biggerCityName = aCityList.trim();
 
 
-				if (fuzzy)
-					nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName));
-				else
+//				if (fuzzy)
+//					nextCityList = fuzzyCityMap.getList(fuzzyDecode(biggerCityName));
+//				else
 					nextCityList = cityMap.getList(biggerCityName);
 
 				if (nextCityList != null) {
@@ -340,7 +348,6 @@
 	}
 
 	public void resolve() {
-
 		if(autoFillLevel < 0)
 			return;			// Nothing to do if auto-fill is fully disabled
 
@@ -380,8 +387,10 @@
 
 
 					if (near != null) {
-						place.setCity(near.getCity());
-						place.setZip(near.getZip());
+						if (place.getCity()==null)
+							place.setCity(near.getCity());
+						if (place.getZip() == null)
+							place.setZip(near.getZip());
 					} else if (autoFillLevel > 1 && (runCount + 1) == maxRuns) {
 						// In the last resolve run just take info from the next known city
 						near = cityMap.findNextPoint(place);
@@ -390,7 +399,7 @@
 							// In OpenGeoDB Countries I don't want to mess up the info which city is a
 							// real independent Community in all other countries I just have to do it
 
-							if(!isOpenGeoDBCountry(near.getCountry()))
+							if(!isOpenGeoDBCountry(near.getCountry()) && place.getCity() == null)
 								place.setCity(place.getName());
 						}
 					}
@@ -420,7 +429,7 @@
 					if( place.getCity() != null)
 					{
 						cityMap.put(place.getName(),place);
-						fuzzyCityMap.put(fuzzyDecode(place.getName()),place);
+//						fuzzyCityMap.put(fuzzyDecode(place.getName()),place);
 						placesMap.set(i, null);
 					}
 					else if(autoFillLevel < 2 && (runCount + 1) == maxRuns)
@@ -442,21 +451,21 @@
 		
 	}
 
-	private String fuzzyDecode(String stringToDecode)
-	{
-
-		if(stringToDecode == null)
-			return stringToDecode;
-
-		String decodeString = stringToDecode.toUpperCase().trim();
-
-		// German umlaut resolution
-		//decodeString = decodeString.replaceAll("��","AE").replaceAll("��","UE").replaceAll("��","OE");
-		
-		//if(decodeString.equals(stringToDecode) == false)
-		//	System.out.println(stringToDecode + " -> " + decodeString);
-
-		return (decodeString);
-	}
+//	private String fuzzyDecode(String stringToDecode)
+//	{
+//
+//		if(stringToDecode == null)
+//			return stringToDecode;
+//
+//		String decodeString = stringToDecode.toUpperCase().trim();
+//
+//		// German umlaut resolution
+//		//decodeString = decodeString.replaceAll("��","AE").replaceAll("��","UE").replaceAll("��","OE");
+//		
+//		//if(decodeString.equals(stringToDecode) == false)
+//		//	System.out.println(stringToDecode + " -> " + decodeString);
+//
+//		return (decodeString);
+//	}
 }
 
