Index: doc/options.txt
===================================================================
--- doc/options.txt	(revision 4397)
+++ doc/options.txt	(working copy)
@@ -677,18 +677,18 @@
 matches or the taglist is empty, the centre of the polygon is used.
 It is possible to define wildcards for tag values like entrance=*.
 <p>Default: entrance=main;entrance=yes;building=entrance
-;--is-in-landuse=value[,value...]
-:     Tells mkgmap to calculate a tag for each given landuse area. 
+;--is-in-hook=tag[,tag...]
+:     Tells mkgmap to calculate a tag for each area with the given key=value tag. 
 Allows to identify eg. buildings within a landuse=residential or ways 
 within a landuse=cemetery. If an element is within the given landuse area,
-the information is stored in special tags with prefix mkmap:lu:.
-Example: If you specify --is-in-landuse=cemetery and an element is within a 
+the information is stored in special tags with prefix mkmap:is-in:.
+Example: If you specify --is-in-hook=landuse=cemetery and an element is within a 
 landuse=cemetery polygon mkgmap adds the tag
-mkgmap:lu:cemetery=x where x is either "yes" or the name of the cemetery.
-The program builds a spatial index for each listed landuse value to be able to compute
-this information before the style rules in lines and points are applied.
-The default for this option is residential. To disable the processing use
---no-is-in-landuse or an empty list of values.
+mkgmap:is-in:landuse-cemetery=x where x is either "yes" or the name of the cemetery.
+The program builds a spatial index for each listed tag to be able to compute
+this information before the style rules for nodes and ways are applied.
+The default for this option is landuse=residential. To disable the processing use
+--no-is-in-hook or an empty list of tags.
 If the style uses the tag mkgmap:residential which was set by earlier versions
 of mkgmap a warning is printed and the old tag is generated.
 <p>
Index: doc/styles/internal-tags.txt
===================================================================
--- doc/styles/internal-tags.txt	(revision 4397)
+++ doc/styles/internal-tags.txt	(working copy)
@@ -116,7 +116,7 @@
 | +mkgmap:admin_level10+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=10+ the element is located in | 'bounds'    
 | +mkgmap:admin_level11+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=11+ the element is located in | 'bounds'    
 | +mkgmap:postcode+  | Name of the postal code relation/polygon the element is located in | 'bounds'
-| +mkgmap:residential+  | See mkgmap:lu: prefix | none         
+| +mkgmap:residential+  | See mkgmap:is-in: prefix | none         
 | +mkgmap:area2poi+  | The value is +true+ if the POI is derived from a polygon | 'add-pois-to-areas'    
 | +mkgmap:line2poi+  | The value is +true+ if the POI is derived from a line | 'add-pois-to-lines'    
 | +mkgmap:line2poitype+  | The tag is set for each POI generated from a line. Possible values are: +start+, +end+, +mid+, +inner+. | 'add-pois-to-lines'    
@@ -129,7 +129,7 @@
 | +mkgmap:synthesised+  | The value is +yes+ if the way was added by the make-opposite-cycleways option | 'make-opposite-cycleways'
 | +mkgmap:mp_created+  | The value is +true+ if the way was created by the internal multi-polygon-relation handling | none
 | +mkgmap:option:<key>+  | Tag generated by the --style-option option | 'style-option'
-| +mkgmap:lu:<landuse>+  | Tag generated by the --is-in-landuse option, e.g. mkgmap:lu:residential=yes | 'is-in-landuse'
+| +mkgmap:is:<tag key>-<tag value>+  | Tag generated by the --is-in-hook option, e.g. mkgmap:is-in:landuse-residential=yes | 'is-in-hook'
  
 |=========================================================
 
Index: resources/help/en/options
===================================================================
--- resources/help/en/options	(revision 4397)
+++ resources/help/en/options	(working copy)
@@ -687,18 +687,18 @@
 	It is possible to define wildcards for tag values like entrance=*.
 	Default: entrance=main;entrance=yes;building=entrance
 
---is-in-landuse=value[,value...]
-	Tells mkgmap to calculate a tag for each given landuse area. 
+--is-in-hook=tag[,tag...]
+	Tells mkgmap to calculate a tag for each area with the given key=value tag. 
 	Allows to identify eg. buildings within a landuse=residential or ways 
 	within a landuse=cemetery. If an element is within the given landuse area,
-	the information is stored in special tags with prefix mkmap:lu:.
-	Example: If you specify --is-in-landuse=cemetery and an element is within a 
-	landuse=cemetery polygon mkgmap adds the tag
-	mkgmap:lu:cemetery=x where x is either "yes" or the name of the cemetery.
-	The program builds a spatial index for each listed landuse value to be able to compute
-	this information before the style rules in lines and points are applied.
-	The default for this option is residential. To disable the processing use
-	--no-is-in-landuse or an empty list of values.
+	the information is stored in special tags with prefix mkmap:is-in:.
+	Example: If you specify --is-in-hook=landuse=cemetery and an element is within
+	a landuse=cemetery polygon mkgmap adds the tag
+	mkgmap:is-in:landuse-cemetery=x where x is either "yes" or the name of the cemetery.
+	The program builds a spatial index for each listed tag to be able to compute
+	this information before the style rules for nodes and ways are applied.
+	The default for this option is landuse=residential. To disable the processing use
+	--no-is-in-hook or an empty list of tags.
 	If the style uses the tag mkgmap:residential which was set by earlier versions
 	of mkgmap a warning is printed and the old tag is generated.
 
Index: src/uk/me/parabola/mkgmap/CommandArgsReader.java
===================================================================
--- src/uk/me/parabola/mkgmap/CommandArgsReader.java	(revision 4397)
+++ src/uk/me/parabola/mkgmap/CommandArgsReader.java	(working copy)
@@ -63,7 +63,7 @@
 		add(new CommandOption("overview-mapnumber", "63240000"));
 		add(new CommandOption("poi-address", ""));
 		add(new CommandOption("merge-lines", ""));
-		add(new CommandOption("is-in-landuse", "residential"));
+		add(new CommandOption("is-in-hook", "landuse=residential"));
 	}
 
 	/**
Index: src/uk/me/parabola/mkgmap/reader/osm/IsInHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/IsInHook.java	(revision 4397)
+++ src/uk/me/parabola/mkgmap/reader/osm/IsInHook.java	(working copy)
@@ -21,6 +21,7 @@
 import java.util.Map.Entry;
 import java.util.Set;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.osmstyle.NameFinder;
@@ -30,34 +31,45 @@
 import uk.me.parabola.util.Java2DConverter;
 
 /**
- * Hook to add information about given landuse areas to an element. If the element lies within the given area xxx
- * a tag mkgmap:lu:xxx is added. Example:
- * tag mkgmap:lu:cemetery is added to elements that lie within a landuse=cemetery area.
+ * Hook to add information about given landuse areas to an element. If the element lies within the given area with tag k=v
+ * a tag mkgmap:is-in::k-v is added. Example:
+ * tag mkgmap:is-in:landuse-cemetery is added to elements that lie within a landuse=cemetery area.
  * Special case: Legacy support for old ResidentialHook
- * If style uses mkgmap:residential instead of mkgmap:lu:residential we add this tag.
+ * If style uses mkgmap:residential instead of mkgmap:is-in:landuse-residential we add this tag.
  * 
  * @author Gerd Petermann
  *
  */
-public class LanduseHook implements OsmReadingHooks {
-	private static final Logger log = Logger.getLogger(LanduseHook.class);
+public class IsInHook implements OsmReadingHooks {
+	private static final Logger log = Logger.getLogger(IsInHook.class);
 
-	private static final String LANDUSE_PREFIX = "mkgmap:lu:";
+	private static final String IS_IN_PREFIX = "mkgmap:is-in:";
 	private Map<String, BoundaryQuadTree> landuseTreeMap = new LinkedHashMap<>();
 	private String[] wantedTrees;
 	private boolean legacyStyle;
 	private ElementSaver saver;
 	private NameFinder nameFinder;
+	Set<String> used = new HashSet<>();
 
 	@Override
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
-		String opt = props.getProperty("is-in-landuse");
+		String opt = props.getProperty("is-in-hook");
 		if (!props.getProperty("residential-hook", true)) {
-			System.err.println("Found undocumented option residential-hook=false, is now ignored, please see option --is-in-landuse");
+			System.err.println("Found undocumented option residential-hook=false, is now ignored, please see option --is-in-hook");
 		}
 		wantedTrees = getCommaSeparatedTrimmedList(opt);
 		if (wantedTrees.length == 0) 
 			return false; // hook is disabled
+		for (String tag : wantedTrees) {
+			String[] kv = tag.split("=");
+			if (kv.length != 2) {
+				throw new ExitException("invalid value in is-in-hook parameter:'"  + tag + "' is not a key=value pair");
+			}
+			if ("*".equals(kv[1])) {
+				throw new ExitException("invalid value in is-in-hook parameter:'"  + tag + "' *  is not allowed as tag value");
+			}
+			used.add(kv[0].trim());
+		}
 		
 		legacyStyle = props.getProperty(OsmMapDataSource.ADD_MKGMAP_RESIDENTIAL, false);
 		this.nameFinder = new NameFinder(props);
@@ -90,8 +102,8 @@
 	public void end() {
 		log.info("Starting with landuse hook");
 		long t1 = System.currentTimeMillis();
-		for (String landuse: wantedTrees) {
-			landuseTreeMap.put(landuse, buildInsideBoundaryTree(landuseTagKey, landuse));
+		for (String tag: wantedTrees) {
+			landuseTreeMap.put(tag, buildInsideBoundaryTree(tag));
 		}
 		long t2 = System.currentTimeMillis();
 		log.info("Creating landuse bounds took", (t2 - t1), "ms");
@@ -115,12 +127,14 @@
 		landuseTreeMap.clear();
 	}
 
-	private static final short landuseTagKey = TagDict.getInstance().xlate("landuse"); 
 	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
 	private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
 	private static final short otherKey = TagDict.getInstance().xlate("mkgmap:other");
 	
-	private BoundaryQuadTree buildInsideBoundaryTree(short tagKey, String val) {
+	private BoundaryQuadTree buildInsideBoundaryTree(String tag) {
+		String[] kv = tag.split("=");
+		short tagKey = TagDict.getInstance().xlate(kv[0].trim());
+		String val = kv[1].trim();
 		List<Boundary> rings = new ArrayList<>();
 		Tags tags = new Tags();
 
@@ -134,7 +148,7 @@
 				rings.add(b);
 			}
 		}
-		log.info("adding ", rings.size(), "areas for", val, "to spatial index");
+		log.info("adding ", rings.size(), "areas for tag", tag, "to spatial index");
 		return new BoundaryQuadTree(saver.getBoundingBox(), rings, null);
 	}
 
@@ -146,7 +160,10 @@
 	private void processElem(Element elem) {
 		for (Entry<String, BoundaryQuadTree> entry : landuseTreeMap.entrySet()) {
 			BoundaryQuadTree tree = entry.getValue();
-			String tagVal = entry.getKey();
+			String tag = entry.getKey();
+			String[] kv = tag.split("=");
+			String tagKey = kv[0];
+			String tagVal = kv[1];
 			Tags isinTags = null;
 			if (elem instanceof Node) {
 				Node node = (Node) elem;
@@ -156,16 +173,23 @@
 				// try the mid point of the way first
 				int middle = way.getPoints().size() / 2;
 				Coord loc = way.hasIdenticalEndPoints() ? way.getCofG() : way.getPoints().get(middle);
-				if (!tagVal.equals(way.getTag(landuseTagKey)))
+				if (!tagVal.equals(way.getTag(tagKey)))
 					isinTags = tree.get(loc);
+				if (isinTags != null && way.hasIdenticalEndPoints()) {
+					// make sure that all nodes of a closed way are inside or on the boundary
+					for (Coord c : way.getPoints()) {
+						if (tree.get(c) == null) {
+							isinTags = null;
+							break;
+						}
+					}
+				}
 			}
 
 			if (isinTags != null) {
-				String prefix = LANDUSE_PREFIX;
-				if (legacyStyle && "residential".equals(tagVal)) {
-					prefix = "mkgmap:";
-				}
-				elem.addTag(prefix + tagVal, isinTags.get(otherKey));
+				String prefixed = legacyStyle && "landuse=residential".equals(tag) ? "mkgmap:residential"
+						: IS_IN_PREFIX + tag.replace('=', '-');
+				elem.addTag(prefixed, isinTags.get(otherKey));
 			}
 		}
 	}
@@ -172,8 +196,6 @@
 	
 	@Override
 	public Set<String> getUsedTags() {
-		Set<String> used = new HashSet<>();
-		used.add("landuse");
 		used.add("name");
 		return used;
 	}
Index: src/uk/me/parabola/mkgmap/reader/osm/LanduseHook.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/LanduseHook.java	(revision 4397)
+++ src/uk/me/parabola/mkgmap/reader/osm/LanduseHook.java	(nonexistent)
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2017.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 or
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-
-package uk.me.parabola.mkgmap.reader.osm;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.osmstyle.NameFinder;
-import uk.me.parabola.mkgmap.reader.osm.boundary.Boundary;
-import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryQuadTree;
-import uk.me.parabola.util.EnhancedProperties;
-import uk.me.parabola.util.Java2DConverter;
-
-/**
- * Hook to add information about given landuse areas to an element. If the element lies within the given area xxx
- * a tag mkgmap:lu:xxx is added. Example:
- * tag mkgmap:lu:cemetery is added to elements that lie within a landuse=cemetery area.
- * Special case: Legacy support for old ResidentialHook
- * If style uses mkgmap:residential instead of mkgmap:lu:residential we add this tag.
- * 
- * @author Gerd Petermann
- *
- */
-public class LanduseHook implements OsmReadingHooks {
-	private static final Logger log = Logger.getLogger(LanduseHook.class);
-
-	private static final String LANDUSE_PREFIX = "mkgmap:lu:";
-	private Map<String, BoundaryQuadTree> landuseTreeMap = new LinkedHashMap<>();
-	private String[] wantedTrees;
-	private boolean legacyStyle;
-	private ElementSaver saver;
-	private NameFinder nameFinder;
-
-	@Override
-	public boolean init(ElementSaver saver, EnhancedProperties props) {
-		String opt = props.getProperty("is-in-landuse");
-		if (!props.getProperty("residential-hook", true)) {
-			System.err.println("Found undocumented option residential-hook=false, is now ignored, please see option --is-in-landuse");
-		}
-		wantedTrees = getCommaSeparatedTrimmedList(opt);
-		if (wantedTrees.length == 0) 
-			return false; // hook is disabled
-		
-		legacyStyle = props.getProperty(OsmMapDataSource.ADD_MKGMAP_RESIDENTIAL, false);
-		this.nameFinder = new NameFinder(props);
-		this.saver = saver;
-		return true;
-	}
-	
-	/**
-	 * Compute list of values from a comma separated list, remove enclosing hyphens and blanks
-	 * @param s the string to parse
-	 * @return array of strings, length = 0 if s is null or empty
-	 */
-	private static String[] getCommaSeparatedTrimmedList(String s) {
-		String[] list = {};
-		if (s != null) {
-			if (s.startsWith("'") || s.startsWith("\""))
-				s = s.substring(1);
-			if (s.endsWith("'") || s.endsWith("\""))
-				s = s.substring(0, s.length() - 1);
-			list = s.split(",");
-			for (int i = 0; i < list.length; i++) {
-				list[i] = list[i].trim();
-			}
-		}
-		return list;
-	}
-
-
-	@Override
-	public void end() {
-		log.info("Starting with landuse hook");
-		long t1 = System.currentTimeMillis();
-		for (String landuse: wantedTrees) {
-			landuseTreeMap.put(landuse, buildInsideBoundaryTree(landuseTagKey, landuse));
-		}
-		long t2 = System.currentTimeMillis();
-		log.info("Creating landuse bounds took", (t2 - t1), "ms");
-		
-		// process all nodes that might be converted to a garmin node (tag count > 0)
-		for (Node node : saver.getNodes().values()) {
-			if (node.getTagCount() > 0 && saver.getBoundingBox().contains(node.getLocation())) {
-				processElem(node);
-			}
-		}
-
-		// process  all ways that might be converted to a garmin way (tag count > 0)
-		for (Way way : saver.getWays().values()) {
-			if (way.getTagCount() > 0) {
-				processElem(way);
-			}
-		}
-		long t3 = System.currentTimeMillis();
-		log.info("Using landuse hook took", (t3 - t2), "ms");
-		// free memory
-		landuseTreeMap.clear();
-	}
-
-	private static final short landuseTagKey = TagDict.getInstance().xlate("landuse"); 
-	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
-	private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
-	private static final short otherKey = TagDict.getInstance().xlate("mkgmap:other");
-	
-	private BoundaryQuadTree buildInsideBoundaryTree(short tagKey, String val) {
-		List<Boundary> rings = new ArrayList<>();
-		Tags tags = new Tags();
-
-		for (Way way : saver.getWays().values()) {
-			if (way.hasIdenticalEndPoints() && val.equals(way.getTag(tagKey))) {
-				if ("polyline".equals(way.getTag(styleFilterTagKey)))
-					continue;
-				String name = nameFinder.getName(way);
-				tags.put(nameTagKey, name == null ? "yes" : name);
-				Boundary b = new Boundary(Java2DConverter.createArea(way.getPoints()), tags, "w" + way.getId());
-				rings.add(b);
-			}
-		}
-		log.info("adding ", rings.size(), "areas for", val, "to spatial index");
-		return new BoundaryQuadTree(saver.getBoundingBox(), rings, null);
-	}
-
-	/**
-	 * Extract the location info and perform a test 
-	 * against the BoundaryQuadTree. If found, assign the tag.
-	 * @param elem A way or Node
-	 */
-	private void processElem(Element elem) {
-		for (Entry<String, BoundaryQuadTree> entry : landuseTreeMap.entrySet()) {
-			BoundaryQuadTree tree = entry.getValue();
-			String tagVal = entry.getKey();
-			Tags isinTags = null;
-			if (elem instanceof Node) {
-				Node node = (Node) elem;
-				isinTags = tree.get(node.getLocation());
-			} else if (elem instanceof Way) {
-				Way way = (Way) elem;
-				// try the mid point of the way first
-				int middle = way.getPoints().size() / 2;
-				Coord loc = way.hasIdenticalEndPoints() ? way.getCofG() : way.getPoints().get(middle);
-				if (!tagVal.equals(way.getTag(landuseTagKey)))
-					isinTags = tree.get(loc);
-			}
-
-			if (isinTags != null) {
-				String prefix = LANDUSE_PREFIX;
-				if (legacyStyle && "residential".equals(tagVal)) {
-					prefix = "mkgmap:";
-				}
-				elem.addTag(prefix + tagVal, isinTags.get(otherKey));
-			}
-		}
-	}
-	
-	@Override
-	public Set<String> getUsedTags() {
-		Set<String> used = new HashSet<>();
-		used.add("landuse");
-		used.add("name");
-		return used;
-	}
-}
Index: src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java	(revision 4397)
+++ src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java	(working copy)
@@ -72,7 +72,7 @@
 			new HighwayHooks(),
 			new LocationHook(),
 			new POIGeneratorHook(),
-			new LanduseHook(),
+			new IsInHook(),
 			new HousenumberHooks(),
 	};
 	protected OsmConverter converter;
@@ -337,7 +337,7 @@
 		setStyle(StyleImpl.readStyle(props));
 
 		if (style.getUsedTags().contains("mkgmap:residential")) {
-			System.err.println("Found mkgmap:residential in style, please replace by mkgmap:lu:residential");
+			System.err.println("Found mkgmap:residential in style, please replace by mkgmap:is-in:landuse-residential");
 			props.put(ADD_MKGMAP_RESIDENTIAL, "yes");
 		}
 		usedTags.addAll(style.getUsedTags());
