Hi

I have written a utility that can be used for demonstrating style bugs
and requests.  See the attached patch.

It allows you to create a file containing ways with tags and a set of
style rules.  You can then run this file and see what elements would
be produced.  If this is wrong or you disagree with the results you
can post the whole result along with what you wanted to happen.

It also attempts to calculate what would happen if the rules were
run in order from the top to the bottom on the way.  If the result is
different, then this is printed out as a failure after the actual
results.

Anything that depends on the order that actions are executed in is
likely to fail on trunk, so any such example will just be collected
and used to ensure that the style branch is working OK.

Anyway, here is an example of it in use.  Create a file called
example1 with the following contents.

        WAY 1
        highway=primary
        type=good
        aa=5
        name=Main Street

        <<<lines>>>
        highway=* & type=good & aa=5    {set aa=6; }

        highway=primary & aa=6 [0x26 ]
        highway=* & aa=6 [0x27 ]

Then run as (with the correct location of mkgmap.jar if different):

        java -cp dist/mkgmap.jar uk.me.parabola.mkgmap.main.StyleTester example1

This will print out the input file followed by a results section:

<<<results>>>
WAY 1:
    Line 0x27, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),
  FAIL. If rules were run strictly in order this would be:
    Line 0x26, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),

This shows that the trunk code will select the last rule, but
it should have selected the previous one.

More notes

* If more than one element is produced, as a result of using continue
  say, then they are all printed.
* Ways are created with two points (1,1) and (2,2) so you can see
  reversals
* You can have any number of WAY definitions, each of which must be
  ended with a blank line
* You can put a number after the WAY so that you can tell them apart
  in the results.

..Steve
Index: src/uk/me/parabola/mkgmap/main/StyleTester.java
===================================================================
--- src/uk/me/parabola/mkgmap/main/StyleTester.java	Sun Jan 24 12:12:14 GMT 2010
+++ src/uk/me/parabola/mkgmap/main/StyleTester.java	Sun Jan 24 12:12:14 GMT 2010
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2010.
+ *
+ * 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.main;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.net.RoadDef;
+import uk.me.parabola.mkgmap.general.LevelInfo;
+import uk.me.parabola.mkgmap.general.MapCollector;
+import uk.me.parabola.mkgmap.general.MapElement;
+import uk.me.parabola.mkgmap.general.MapLine;
+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.osmstyle.ActionRule;
+import uk.me.parabola.mkgmap.osmstyle.ExpressionRule;
+import uk.me.parabola.mkgmap.osmstyle.FixedRule;
+import uk.me.parabola.mkgmap.osmstyle.RuleSet;
+import uk.me.parabola.mkgmap.osmstyle.StyleFileLoader;
+import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
+import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
+import uk.me.parabola.mkgmap.osmstyle.TypeReader;
+import uk.me.parabola.mkgmap.osmstyle.actions.Action;
+import uk.me.parabola.mkgmap.osmstyle.actions.ActionReader;
+import uk.me.parabola.mkgmap.osmstyle.eval.ExpressionReader;
+import uk.me.parabola.mkgmap.osmstyle.eval.Op;
+import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.mkgmap.reader.osm.Rule;
+import uk.me.parabola.mkgmap.reader.osm.Style;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.mkgmap.scan.TokenScanner;
+
+
+/**
+ * Reads in a file that contains a test for the style rules.
+ *
+ * The format of the file is as follows
+ *
+ * <pre>
+ * WAY 42
+ * highway=primary
+ * oneway=reverse
+ *
+ * <<<lines>>>
+ * highway=primary [0x3 road_class=2 road_speed=2]
+ * power=line [0x29 resolution 20]
+ * </pre>
+ *
+ * You can have any number of ways, each must end with a blank line.
+ * A way will be created with two points (1,1),(2,2) (so you can see the
+ * action of oneway=reverse) and the tags that you specify.  If you give
+ * a number after WAY it will be printed on output so that if you have more
+ * than one you can tell which is which.  If the number is ommited it will
+ * default to 1.
+ *
+ * You can have as many rules as you like after the <<<lines>>> and you
+ * can include any other style files such as <<<options>>> or <<<info>>> if
+ * you like.
+ *
+ * Everything is jammed into the one file to make it easier to run against
+ * different versions.  But this will probably change when applied to the
+ * style branch.
+ *
+ * @author Steve Ratcliffe
+ */
+public class StyleTester {
+	private final List<MapElement> lines = new ArrayList<MapElement>();
+
+	private static final Pattern SPACES_PATTERN = Pattern.compile(" +");
+	private static final Pattern EQUAL_PATTERN = Pattern.compile("=");
+
+	private final List<Way> ways = new ArrayList<Way>();
+	private final PrintStream out = System.out;
+
+	public static void main(String[] args) throws IOException {
+		StyleTester sr = new StyleTester();
+		sr.test(args[0]);
+	}
+
+	/**
+	 * Run the test.  Read in the test file that describes the tags and rules
+	 * to be tested and run the rules as implemented and also with a theoretical
+	 * strict ordering.
+	 * @param file The file containing the test information.
+	 */
+	private void test(String file) {
+		try {
+			readFile(file);
+		} catch (IOException e) {
+			System.err.println("Cant open file " + file);
+			return;
+		}
+
+		try {
+			StyledConverter conv = makeStyleConverter();
+			StyledConverter strictConv = makeStrictStyleConverter();
+
+			out.println("<<<results>>>");
+			for (Way w : ways) {
+				// Actual results that you get from the code of the version of
+				// mkgmap that you are using.
+				out.printf("WAY %d:\n", w.getId());
+				conv.convertWay(w);
+				String[] actual = formatResults();
+
+				printResult(actual);
+
+				// What would happen if the rules were applied one-by-one from
+				// the top of the file downward in order.
+				strictConv.convertWay(w);
+				String[] expected = formatResults();
+				if (!Arrays.deepEquals(actual, expected)) {
+					out.println("  FAIL. If rules were run strictly in order this would be:");
+					printResult(expected);
+				}
+				out.println();
+			}
+		} catch (FileNotFoundException e) {
+			System.err.println("Cant open style: " + e);
+		}
+
+	}
+
+	/**
+	 * Print out the result obtained from {...@link #formatResults()}.
+	 * @param results An array of strings obtained from {...@link #formatResults()}.
+	 */
+	private void printResult(String[] results) {
+		for (String s : results) {
+			out.println("    " + s);
+		}
+	}
+
+	/**
+	 * Read in the test file.
+	 */
+	private void readFile(String filename) throws IOException {
+		FileReader reader = new FileReader(filename);
+		BufferedReader br = new BufferedReader(reader);
+
+		String line;
+		while ((line = br.readLine()) != null) {
+			line = line.trim();
+			if (line.toLowerCase(Locale.ENGLISH).startsWith("way")) {
+				readWayTags(br, line);
+			} else if (line.startsWith("<<<")) {
+				// read the rest of the file
+				readStyles(br, line);
+			} else if ("".equals(line)) {
+				// ignore blank lines.
+			} else if (line.startsWith("#")) {
+				// ignore comment lines
+			}
+		}
+		br.close();
+	}
+
+	/**
+	 * You can have a number of ways defined in the file.  If you give a
+	 * number after 'way' that is used as the way id so that you can identify
+	 * it in the results.
+	 *
+	 * A list of tags are read and added to the way up until a blank line.
+	 *
+	 * @param br Read from here.
+	 * @param waydef This will contain the way-id if one was given.  Otherwise
+	 * the way id will be 1.
+	 * @throws IOException If the file cannot be read.
+	 */
+	private void readWayTags(BufferedReader br, String waydef) throws IOException {
+		int id = 1;
+		String[] strings = SPACES_PATTERN.split(waydef);
+		if (strings.length > 1)
+			id = Integer.parseInt(strings[1]);
+
+		out.printf("WAY %d\n", id);
+
+		Way w = new Way(id);
+		w.addPoint(new Coord(1, 1));
+		w.addPoint(new Coord(2, 2));
+		ways.add(w);
+
+		String line;
+		while ((line = br.readLine()) != null) {
+			if (line.indexOf('=') < 0)
+				break;
+			String[] tagval = EQUAL_PATTERN.split(line, 2);
+			if (tagval.length == 2) {
+				w.addTag(tagval[0], tagval[1]);
+				out.println(line);
+			}
+		}
+		out.println();
+	}
+
+	/**
+	 * Print out the garmin elements that were produced by the rules.
+	 */
+	private String[] formatResults() {
+		String[] result = new String[lines.size()];
+		int i = 0;
+		for (MapElement el : lines) {
+			String s;
+			// So we can run against versions that do not have toString() methods
+			if (el instanceof MapRoad)
+				s = roadToString((MapRoad) el);
+			else
+				s = lineToString((MapLine) el);
+			result[i++] = s;
+		}
+		lines.clear();
+		return result;
+	}
+
+	/**
+	 * This is so we can run against versions of mkgmap that do not have
+	 * toString methods on MapLine and MapRoad.
+	 */
+	private String lineToString(MapLine el) {
+		Formatter fmt = new Formatter();
+		fmt.format("Line 0x%x, name=<%s>, ref=<%s>, res=%d-%d",
+				el.getType(), el.getName(), el.getRef(),
+				el.getMinResolution(), el.getMaxResolution());
+		if (el.isDirection())
+			fmt.format(" oneway");
+
+		fmt.format(" ");
+		for (Coord co : el.getPoints())
+			fmt.format("(%s),", co);
+
+		return fmt.toString();
+	}
+
+	/**
+	 * This is so we can run against versions of mkgmap that do not have
+	 * toString methods on MapLine and MapRoad.
+	 */
+	private String roadToString(MapRoad el) {
+		StringBuffer sb = new StringBuffer(lineToString(el));
+		sb.delete(0, 4);
+		sb.insert(0, "Road");
+		Formatter fmt = new Formatter(sb);
+		fmt.format(" road class=%d speed=%d", el.getRoadDef().getRoadClass(),
+				getRoadSpeed(el.getRoadDef()));
+		return fmt.toString();
+	}
+
+	/**
+	 * Implement a method to get the road speed from RoadDef.
+	 */
+	private int getRoadSpeed(RoadDef roadDef) {
+		try {
+			Field field = RoadDef.class.getDeclaredField("tabAInfo");
+			field.setAccessible(true);
+			int tabA = (Integer) field.get(roadDef);
+			return tabA & 0x7;
+		} catch (NoSuchFieldException e) {
+			e.printStackTrace();
+		} catch (IllegalAccessException e) {
+			e.printStackTrace();
+		}
+		return 0;
+	}
+
+	/**
+	 * Read the style definitions.  The rest of the file is just copied to
+	 * a style file named 'styletester.style' so that it can be read in the
+	 * normal manner.
+	 * @param br Read from here.
+	 * @param initLine The first line of the style definition that has already been read.
+	 * @throws IOException If writing fails.
+	 */
+	private void readStyles(BufferedReader br, String initLine) throws IOException {
+		FileWriter writer = new FileWriter("styletester.style");
+		PrintWriter pw = new PrintWriter(writer);
+		pw.println("<<<version>>>\n0");
+
+		pw.println(initLine);
+		out.println(initLine);
+
+		boolean copyToOutput = true;
+		String line;
+		while ((line = br.readLine()) != null) {
+			if (line.startsWith("<<<results>>>"))
+				copyToOutput = false;
+			if (copyToOutput)
+				out.println(line);
+			pw.println(line);
+		}
+
+		pw.close();
+	}
+
+	/**
+	 * A styled converter that should work exactly the same as the version of
+	 * mkgmap you are using.
+	 */
+	private StyledConverter makeStyleConverter() throws FileNotFoundException {
+		Style style = new StyleImpl("styletester.style", null);
+		MapCollector coll = new LocalMapCollector();
+
+		return new StyledConverter(style, coll, new Properties());
+	}
+
+	/**
+	 * A special styled converted that attempts to produce the correct theoretical
+	 * result of running the style rules in order by literally doing that.
+	 * This should produce the same result as {...@link #makeStyleConverter()} and
+	 * can be used as a test of the strict style ordering branch.
+	 */
+	private StyledConverter makeStrictStyleConverter() throws FileNotFoundException {
+		Style style = new StrictOrderingStyle("styletester.style", null);
+		MapCollector coll = new LocalMapCollector();
+
+		return new StyledConverter(style, coll, new Properties());
+	}
+
+	/**
+	 * A style that is based on StyleImpl, except that it literally implements
+	 * running each rule in the order they are defined on each element.
+	 */
+	private class StrictOrderingStyle extends StyleImpl {
+		private final StyleFileLoader fileLoader;
+
+		/**
+		 * Create a style from the given location and name.
+		 *
+		 * @param loc The location of the style. Can be null to mean just check the
+		 * classpath.
+		 * @param name The name.  Can be null if the location isn't.  If it is null
+		 * then we just check for the first version file that can be found.
+		 * @throws FileNotFoundException If the file doesn't exist.  This can include
+		 * the version file being missing.
+		 */
+		public StrictOrderingStyle(String loc, String name) throws FileNotFoundException {
+			super(loc, name);
+			fileLoader = StyleFileLoader.createStyleLoader(loc, name);
+		}
+
+		/**
+		 * Throws away the rules as previously read and reads again using the
+		 * SimpleRuleFileReader which does not re-order or optimise the rules
+		 * in any way.
+		 *
+		 * @return A simple list of rules with a resolving method that applies
+		 * each rule in turn to the element until there is match.
+		 */
+		public Rule getWayRules() {
+			StrictOrderingRuleSet r = new StrictOrderingRuleSet();
+			String l = LevelInfo.DEFAULT_LEVELS;
+			LevelInfo[] levels = LevelInfo.createFromString(l);
+
+			SimpleRuleFileReader ruleFileReader = new SimpleRuleFileReader(GType.POLYLINE, levels, r);
+			try {
+				ruleFileReader.load(fileLoader, "lines");
+			} catch (FileNotFoundException e) {
+				e.printStackTrace();
+			}
+
+			return r;
+		}
+
+		/**
+		 * Keeps each rule in an orderd list.
+		 *
+		 * Types are resolved by literally applying the rules in order to the
+		 * element.  This would be too slow to do for real in mkgmap, however
+		 * mkgmap should produce the same result as doing this.
+		 *
+		 * As long as the rules are added in the order they are encountered in
+		 * the file, this should work.
+		 */
+		private class StrictOrderingRuleSet implements Rule {
+			private final List<Rule> rules = new ArrayList<Rule>();
+			private int prevMatch = -1;
+
+			public void addAll(RuleSet lines) {
+				for (Map.Entry<String, Rule> ent : lines.entrySet()) {
+					add(ent.getValue());
+				}
+			}
+
+			public void add(Rule rule) {
+				rules.add(rule);
+			}
+
+			public GType resolveType(Element el) {
+				return resolveType(el, null);
+			}
+
+			public GType resolveType(Element el, GType pre) {
+				for (int i = 0; i < rules.size(); i++) {
+					Rule r = rules.get(i);
+					GType type = r.resolveType(el, pre);
+
+					// Because (on trunk) we get passed a copy of the way each
+					// time this is called, all the tags that were set previously
+					// have been lost and so we have to start from the beginning
+					// again and keep going if we were at the previous match
+					// point.
+					if (type != null && i != prevMatch) {
+						if (type.isFinal())
+							prevMatch = -1;
+						else
+							prevMatch = i;
+						return type;
+					}
+				}
+				prevMatch = -1;
+				return null;
+			}
+
+		}
+
+		/**
+		 * A reimplementation of RuleFileReader that does no optimisation but
+		 * just reads the rules into a list.
+		 */
+		class SimpleRuleFileReader {
+			private final TypeReader typeReader;
+
+			private final StrictOrderingRuleSet rules;
+			private TokenScanner scanner;
+
+			public SimpleRuleFileReader(int kind, LevelInfo[] levels, StrictOrderingRuleSet rules) {
+				this.rules = rules;
+				typeReader = new TypeReader(kind, levels);
+			}
+
+			/**
+			 * Read a rules file.
+			 * @param loader A file loader.
+			 * @param name The name of the file to open.
+			 * @throws FileNotFoundException If the given file does not exist.
+			 */
+			public void load(StyleFileLoader loader, String name) throws FileNotFoundException {
+				Reader r = loader.open(name);
+				load(r, name);
+			}
+
+			void load(Reader r, String name) {
+				scanner = new TokenScanner(name, r);
+				scanner.setExtraWordChars("-:");
+
+				ExpressionReader expressionReader = new ExpressionReader(scanner);
+				ActionReader actionReader = new ActionReader(scanner);
+
+				// Read all the rules in the file.
+				scanner.skipSpace();
+				while (!scanner.isEndOfFile()) {
+					Op expr = expressionReader.readConditions();
+
+					List<Action> actions = actionReader.readActions();
+
+					// If there is an action list, then we don't need a type
+					GType type = null;
+					if (scanner.checkToken("["))
+						type = typeReader.readType(scanner);
+					else if (actions == null)
+						throw new SyntaxException(scanner, "No type definition given");
+
+					saveRule(expr, actions, type);
+					scanner.skipSpace();
+				}
+			}
+
+			/**
+			 * Save the expression as a rule.
+			 */
+			private void saveRule(Op op, List<Action> actions, GType gt) {
+				Rule rule;
+				if (!actions.isEmpty())
+					rule = new ActionRule(op, actions, gt);
+				else if (op != null) {
+					rule = new ExpressionRule(op, gt);
+				} else {
+					rule = new FixedRule(gt);
+				}
+
+				//System.out.println("save rule " + null + "/" + rule);
+				rules.add(rule);
+			}
+		}
+	}
+
+	/**
+	 * A map collector that just adds any line or road we find to the end of
+	 * a list.
+	 */
+	private class LocalMapCollector implements MapCollector {
+		public void addToBounds(Coord p) { }
+
+		// could save points in the same way as lines to test them
+		public void addPoint(MapPoint point) { }
+
+		public void addLine(MapLine line) {
+			lines.add(line);
+		}
+
+		public void addShape(MapShape shape) { }
+
+		public void addRoad(MapRoad road) {
+			lines.add(road);
+		}
+	}
+}
WAY 1
highway=primary
type=good
aa=5
name=Main Street

<<<lines>>>
highway=* & type=good & aa=5    {set aa=6; }

highway=primary & aa=6 [0x26 ]
highway=* & aa=6 [0x27 ]

<<<results>>>
WAY 1:
    Line 0x27, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),
  FAIL. If rules were run strictly in order this would be:
    Line 0x26, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),

WAY 1
highway=primary
type=good
aa=5
name=Main Street

<<<lines>>>
highway=* & type=good [0x10 continue]

highway=primary [0x3 ]

<<<results>>>
WAY 1:
    Line 0x10, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),
    Line 0x3, name=<Main Street>, ref=<null>, res=24-24 (1/1),(2/2),

WAY 1
highway=primary
name=Main Street
ref=A5

WAY 2
highway=footway

<<<lines>>>
highway=primary [0x3 level 3 road_speed=1 road_class=2]
highway=footway [0x7 ]

<<<results>>>
WAY 1:
    Road 0x3, name=<Main Street>, ref=<A5>, res=18-24 (1/1),(2/2), road class=2 
speed=1

WAY 2:
    Line 0x7, name=<null>, ref=<null>, res=24-24 (1/1),(2/2),

_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to