Moin,

finally I got my eclipse environment running and was able to build
mkgmap from source. So the next step was my first extension of mkgmap.

Until now a single OSM element was converted into a single garmin
element, i.e. the first one in the style file with the matching expression.

I have extended the style definition by two commands (stop and
continue), to allow the
generation of multiple elements.

A classic conversion rule has a syntax like:

amenity=restaurant [0x2a00 resolution 20]

With my extension this is equal to

amenity=restaurant [0x2a00 resolution 20 stop]

and has the meaning: If this rule is used, no further rules will be
applied to the element.

If this line is changed now to

amenity=restaurant [0x2a00 resolution 20 continue]

mkgmap will convert a matching OSM element into a garmin Element type
0x2a00, but afterwards it will check, whether another rule is matching
for the same osm element. So this will allow, that for the same item
additionally a

tourism=hotel [0x2b01 resolution 20]

rule might be applied.

My modification is working on points as well as on lines or polygons,
i.e. it will also help with barrier=fence problem.

There are two problems with my modification:

1. The POI generation for an area will not work, with multiple
conversion rules.

2. I do not know, how to provide a proper patch for my modification, so
I just attached the modified java-files (based on mkgmap-r1087).

I hope you can incorporate the modification into your mkgmap and give it
a try. Without any change to the style, the modification should not
change anything. As a try, you could add the continue command to all
point rules and all barrier rules.

What do you think about it?

Gruss
Torsten
/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 15-Nov-2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import java.util.List;

import uk.me.parabola.mkgmap.osmstyle.actions.Action;
import uk.me.parabola.mkgmap.osmstyle.eval.Op;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;

/**
 * An action rule modifies the tags on the incoming element.
 *
 * It can also have an expression, and does not need to have a Type.  If
 * there is no type then the resolve method always returns false.  The tags
 * on the element may have been modified however.
 *
 * @author Steve Ratcliffe
 */
public class ActionRule implements Rule {
	private final Op expression;
	private final List<Action> actions;
	private final GType type;

	public ActionRule(Op expression, List<Action> actions, GType type) {
		assert actions != null;
		this.expression = expression;
		this.actions = actions;
		this.type = type;
	}

	public ActionRule(Op expression, List<Action> actions) {
		assert actions != null;
		this.expression = expression;
		this.actions = actions;
		this.type = null;
	}

	public GType resolveType(Element el, GType pre) {
		if (expression == null || expression.eval(el)) {
			for (Action a : actions)
				a.perform(el);

			return type;
		}
		return null;
	}

	public String toString() {
		StringBuilder fmt = new StringBuilder();
		if (expression != null)
			fmt.append(expression);

		fmt.append("\n\t{");
		for (Action a : actions)
			fmt.append(a);
		fmt.append("}\n");

		if (type != null) {
			fmt.append('\t');
			fmt.append(type);
		}

		return fmt.toString();
	}
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 07-Nov-2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import uk.me.parabola.mkgmap.osmstyle.eval.Op;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;

/**
 * A rule that contains a condition.  If the condition is matched by the
 * element then the held gtype is returned.
 * 
 * @author Steve Ratcliffe
 */
public class ExpressionRule implements Rule {
	private final Op exression;
	private final GType gtype;

	public ExpressionRule(Op exression, GType gtype) {
		this.exression = exression;
		this.gtype = gtype;
	}

	public GType resolveType(Element el, GType pre) {
		if (exression.eval(el))
			return gtype;

		return null;
	}

	public String toString() {
		return exression.toString() + ' ' + gtype;
	}
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 07-Nov-2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;

/**
 * A rule that always returns the same gtype.
 *
 * @author Steve Ratcliffe
 */
public class FixedRule implements Rule {
	private final GType gtype;

	public FixedRule(GType gtype) {
		this.gtype = gtype;
	}

	public GType resolveType(Element el, GType pre) {
		return gtype;
	}

	public String toString() {
		return gtype.toString();
	}
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: Apr 25, 2008
 */
package uk.me.parabola.mkgmap.reader.osm;

import java.util.Formatter;

import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.LevelInfo;

/**
 * Holds the garmin type of an element and all the information that
 * will be needed to represent it on the map.  So we have a range of
 * resolutions at which it will be present.
 *
 * @author Steve Ratcliffe
 */
public class GType {
	private static final Logger log = Logger.getLogger(GType.class);

	public static final int POINT = 1;
	public static final int POLYLINE = 2;
	public static final int POLYGON = 3;

	private static int nextPriority = 1;
	private static final int PRIORITY_PUSH = 100000;

	private final int featureKind;
	private final int type;

	private int minResolution = 24;
	private int maxResolution = 24;

	private int maxLevel = -1;
	private int minLevel;

	private String defaultName;

	// road class and speed will be set on roads.
	private int roadClass;
	private int roadSpeed;

	private final int priority;

	private boolean road;

	// control flag, whether this element defines
	// the final conversion, or whether we shall search
	// for further matching elements
	private boolean FinalElement = true;

	public GType(int featureKind, String type) {
		priority = nextPriority();
		this.featureKind = featureKind;
		try {
			this.type = Integer.decode(type);
		} catch (NumberFormatException e) {
			log.error("not numeric " + type);
			throw new ExitException("non-numeric type in map-features file");
		}
	}

	private static int nextPriority() {
		return nextPriority++;
	}

	public GType(int featureKind, String type, String subtype) {
		priority = nextPriority();

		this.featureKind = featureKind;
		try {
			this.type = (Integer.decode(type) << 8) + Integer.decode(subtype);
		} catch (NumberFormatException e) {
			log.error("not numeric " + type + ' ' + subtype);
			throw new ExitException("non-numeric type in map-features file");
		}
	}

	public int getFeatureKind() {
		return featureKind;
	}

	public int getType() {
		return type;
	}

	public int getMinResolution() {
		return minResolution;
	}

	public void setMinResolution(int minResolution) {
		this.minResolution = minResolution;
	}

	public int getMaxResolution() {
		return maxResolution;
	}

	public void setMaxResolution(int maxResolution) {
		this.maxResolution = maxResolution;
	}

	public String getDefaultName() {
		return defaultName;
	}

	public void setDefaultName(String defaultName) {
		this.defaultName = defaultName;
	}

	/**
	 * Is the priority of this type better than that of other?
	 * Lower priorities are better and win out.
	 */
	public boolean isBetterPriority(GType other) {
		return this.priority < other.priority;
	}

	/**
	 * Set minLevel and maxLevel based on the resolution values set and
	 * the given levels info.  We do this because we used to work only
	 * on resolution, but we want to move more towards working with
	 * levels.
	 */
	public void fixLevels(LevelInfo[] levels) {
		for (LevelInfo info : levels) {
			if (info.getBits() <= minResolution)
				maxLevel = info.getLevel();
			if (info.getBits() <= maxResolution)
				minLevel = info.getLevel();
		}
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		Formatter fmt = new Formatter(sb);
		sb.append('[');
		fmt.format("%#x", type);
		if (maxLevel == -1) {
			if (maxResolution == 24)
				fmt.format(" resolution %d", minResolution);
			else
				fmt.format(" resolution %d-%d", maxResolution, minResolution);
		} else {
			if (minLevel == 0)
				fmt.format(" level %d", maxLevel);
			else
				fmt.format(" level %d-%d", minLevel, maxLevel);
		}
		sb.append(']');
		return sb.toString();
	}

	public int getMinLevel() {
		return minLevel;
	}

	public int getMaxLevel() {
		return maxLevel;
	}

	public int getRoadClass() {
		return roadClass;
	}

	public void setRoadClass(int roadClass) {
		road = true;
		this.roadClass = roadClass;
	}

	public int getRoadSpeed() {
		return roadSpeed;
	}

	public void setRoadSpeed(int roadSpeed) {
		road = true;
		this.roadSpeed = roadSpeed;
	}

	public boolean isRoad() {
		return road;
	}

	public void setFinal() {
		FinalElement = true;
	}

	public void setContinue() {
		FinalElement = false;
	}

	public boolean isFinal() {
		return FinalElement;
	}

	public static void push() {
		nextPriority += PRIORITY_PUSH;
	}

	public static void pop() {
		nextPriority -= PRIORITY_PUSH;
	}
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 07-Nov-2008
 */
package uk.me.parabola.mkgmap.reader.osm;

/**
 * A rule takes an element and returns the correct garmin type for it.
 * Immplementations can be simple or complex as needed.
 *
 * @author Steve Ratcliffe
 */
public interface Rule {

	/**
	 * Given the element return the garmin type that should be used to
	 * represent it.
	 *
	 * @param el The element as read from an OSM xml file in 'tag' format.
	 * @param pre The previous garmin type generated from the element.
	 * @return Enough information to represent this as a garmin type.
	 */
	public GType resolveType(Element el, GType pre);
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 08-Nov-2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import java.util.Formatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;

/**
 * A group of rules.  Basically just a map of a tag=value strings that is used
 * as an index and the rule that applies for that tag,value pair.
 *
 * The main purpose is to separate out the code to add a new rule
 * which is moderately complex.
 *
 * @author Steve Ratcliffe
 */
public class RuleSet implements Rule {
	private final Map<String, Rule> rules = new LinkedHashMap<String, Rule>();

	public void add(String s, Rule rule) {
		Rule existingRule = rules.get(s);
		if (existingRule == null) {
			rules.put(s, rule);
		} else {
			if (existingRule instanceof SequenceRule) {
				((SequenceRule) existingRule).add(rule);
			} else {
				// There was already a single rule there.  Create a sequence
				// rule and add the existing and the new rule to it.
				SequenceRule sr = new SequenceRule();
				sr.add(existingRule);
				sr.add(rule);
				rules.put(s, sr);
			}
		}
	}

	public Map<String, Rule> getMap() {
		return rules;
	}

	public Set<Map.Entry<String,Rule>> entrySet() {
		return rules.entrySet();
	}

	public GType resolveType(Element el, GType pre) {
		GType foundType = null;
		for (String tagKey : el) {
			Rule rule = rules.get(tagKey);
			if (rule != null) {
				GType type = rule.resolveType(el, pre);
				if (type != null) {
					if ((foundType == null || type.isBetterPriority(foundType)) && (pre == null || pre.isBetterPriority(type))) {
						foundType = type;
					}
				}
			}
		}
		return foundType;
	}

	public void addAll(RuleSet rs) {
		for (Map.Entry<String, Rule> ent : rs.entrySet())
			add(ent.getKey(), ent.getValue());
	}

	/**
	 * Format the rule set.  Warning: this doesn't produce a valid input
	 * rule file.
	 */
	public String toString() {
		Formatter fmt = new Formatter();
		for (Map.Entry<String, Rule> ent: rules.entrySet()) {
			String first = ent.getKey();
			Rule r = ent.getValue();
			if (r instanceof FixedRule)
				fmt.format("%s %s\n", first, r);
			else
				fmt.format("%s & %s\n", first, r);
		}
		return fmt.toString();
	}
}

/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 07-Nov-2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;

import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Rule;

/**
 * This holds a list of rules.  Each of them is tried out in order and
 * the first one that matches is the result of this rule.
 *
 * @author Steve Ratcliffe
 */
public class SequenceRule implements Rule, Iterable<Rule> {
	private final List<Rule> ruleList = new ArrayList<Rule>();

	public GType resolveType(Element el, GType pre) {
		for (Rule r : ruleList) {
			GType type = r.resolveType(el, pre);
			if (type != null)
				return type;
		}
		return null;
	}

	/**
	 * Add a rule to this sequence.  We do a quick check for impossible
	 * situations: if a FixedRule is added, then any rule added afterwards
	 * would never be called (because a fixed rule always returns an answer).
	 */
	public void add(Rule rule) {
		//if (blocked && !(rule instanceof FixedRule))
		//	System.err.println("Warning: Unreachable rule (" + rule + "), more general rules should be later in the file");
		
		ruleList.add(rule);
		//boolean blocked;
		//if (rule instanceof FixedRule)
		//	blocked = true;
	}

	public Iterator<Rule> iterator() {
		return ruleList.listIterator();
	}

	public String toString() {
		Formatter fmt = new Formatter(new StringBuilder());
		fmt.format("(");
		for (Rule r : ruleList) {
			fmt.format("%s | ", r);
		}
		fmt.format(")");
		return fmt.toString();
	}
}

/*
 * Copyright (C) 2007 Steve Ratcliffe
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: Feb 17, 2008
 */
package uk.me.parabola.mkgmap.osmstyle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.Exit;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.AreaClipper;
import uk.me.parabola.mkgmap.general.Clipper;
import uk.me.parabola.mkgmap.general.LineAdder;
import uk.me.parabola.mkgmap.general.LineClipper;
import uk.me.parabola.mkgmap.general.MapCollector;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapExitPoint;
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.general.RoadNetwork;
import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.Node;
import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
import uk.me.parabola.mkgmap.reader.osm.Relation;
import uk.me.parabola.mkgmap.reader.osm.RestrictionRelation;
import uk.me.parabola.mkgmap.reader.osm.Rule;
import uk.me.parabola.mkgmap.reader.osm.Style;
import uk.me.parabola.mkgmap.reader.osm.Way;

/**
 * Convert from OSM to the mkgmap intermediate format using a style.
 * A style is a collection of files that describe the mappings to be used
 * when converting.
 *
 * @author Steve Ratcliffe
 */
public class StyledConverter implements OsmConverter {
	private static final Logger log = Logger.getLogger(StyledConverter.class);

	private final String[] nameTagList;

	private final MapCollector collector;

	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
	private final Map<Coord, List<RestrictionRelation>> restrictions = new IdentityHashMap<Coord, List<RestrictionRelation>>();

	// originalWay associates Ways that have been created due to
	// splitting or clipping with the Ways that they were derived
	// from
	private final Map<Way, Way> originalWay = new HashMap<Way, Way>();

	// limit arc lengths to what can currently be handled by RouteArc
	private final int MAX_ARC_LENGTH = 75000;

	private final int MAX_POINTS_IN_WAY = 200;

	private final int MAX_NODES_IN_WAY = 16;

	private final double MIN_DISTANCE_BETWEEN_NODES = 5.5;

	// nodeIdMap maps a Coord into a nodeId
	private final Map<Coord, Integer> nodeIdMap = new IdentityHashMap<Coord, Integer>();
	private int nextNodeId = 1;
	
	private final Rule wayRules;
	private final Rule nodeRules;
	private final Rule relationRules;

	private boolean ignoreMaxspeeds;

	class AccessMapping {
		private final String type;
		private final int index;
		AccessMapping(String type, int index) {
			this.type = type;
			this.index = index;
		}
	}

	private final AccessMapping[] accessMap = {
		new AccessMapping("access",     RoadNetwork.NO_MAX), // must be first in list
		new AccessMapping("bicycle",    RoadNetwork.NO_BIKE),
		new AccessMapping("foot",       RoadNetwork.NO_FOOT),
		new AccessMapping("hgv",        RoadNetwork.NO_TRUCK),
		new AccessMapping("motorcar",   RoadNetwork.NO_CAR),
		new AccessMapping("motorcycle", RoadNetwork.NO_CAR),
		new AccessMapping("psv",        RoadNetwork.NO_BUS),
		new AccessMapping("taxi",       RoadNetwork.NO_TAXI),
		new AccessMapping("emergency",  RoadNetwork.NO_EMERGENCY),
		new AccessMapping("delivery",   RoadNetwork.NO_DELIVERY),
		new AccessMapping("goods",      RoadNetwork.NO_DELIVERY),
	};

	private LineAdder lineAdder = new LineAdder() {
		public void add(MapLine element) {
			if (element instanceof MapRoad)
				collector.addRoad((MapRoad) element);
			else
				collector.addLine(element);
		}
	};

	public StyledConverter(Style style, MapCollector collector, Properties props) {
		this.collector = collector;

		nameTagList = style.getNameTagList();
		wayRules = style.getWayRules();
		nodeRules = style.getNodeRules();
		relationRules = style.getRelationRules();
		ignoreMaxspeeds = props.getProperty("ignore-maxspeeds") != null;

		LineAdder overlayAdder = style.getOverlays(lineAdder);
		if (overlayAdder != null)
			lineAdder = overlayAdder;
	}

	/**
	 * This takes the way and works out what kind of map feature it is and makes
	 * the relevant call to the mapper callback.
	 * <p>
	 * As a few examples we might want to check for the 'highway' tag, work out
	 * if it is an area of a park etc.
	 *
	 * @param way The OSM way.
	 */
	public void convertWay(Way way) {
		if (way.getPoints().size() < 2)
			return;

		preConvertRules(way);

		GType foundType = null;

		do {
			foundType = wayRules.resolveType(way, foundType);
			if (foundType == null)
				return;

			postConvertRules(way, foundType);

			if (foundType.getFeatureKind() == GType.POLYLINE) {
				if(foundType.isRoad())
					addRoad(way, foundType);
				else
					addLine(way, foundType);
			}
			else
				addShape(way, foundType);
		} while (!foundType.isFinal());
	}

	/**
	 * Takes a node (that has its own identity) and converts it from the OSM
	 * type to the Garmin map type.
	 *
	 * @param node The node to convert.
	 */
	public void convertNode(Node node) {
		preConvertRules(node);

		GType foundType = null;

		do {
			foundType = nodeRules.resolveType(node, foundType);
			if (foundType == null)
				return;

			postConvertRules(node, foundType);

			addPoint(node, foundType);
		} while (!foundType.isFinal());
	}

	/**
	 * Rules to run before converting the element.
	 */
	private void preConvertRules(Element el) {
		if (nameTagList == null)
			return;

		for (String t : nameTagList) {
			String val = el.getTag(t);
			if (val != null) {
				el.addTag("name", val);
				break;
			}
		}
	}

	/**
	 * Built in rules to run after converting the element.
	 */
	private void postConvertRules(Element el, GType type) {
		// Set the name from the 'name' tag or failing that from
		// the default_name.
		el.setName(el.getTag("name"));
		if (el.getName() == null)
			el.setName(type.getDefaultName());
	}

	/**
	 * Set the bounding box for this map.  This should be set before any other
	 * elements are converted if you want to use it. All elements that are added
	 * are clipped to this box, new points are added as needed at the boundry.
	 *
	 * If a node or a way falls completely outside the boundry then it would be
	 * ommited.  This would not normally happen in the way this option is typically
	 * used however.
	 *
	 * @param bbox The bounding area.
	 */
	public void setBoundingBox(Area bbox) {
		this.clipper = new AreaClipper(bbox);
		this.bbox = bbox;
	}

	/**
	 * Run the rules for this relation.  As this is not an end object, then
	 * the only useful rules are action rules that set tags on the contained
	 * ways or nodes.  Every rule should probably start with 'type=".."'.
	 *
	 * @param relation The relation to convert.
	 */
	public void convertRelation(Relation relation) {
		// Relations never resolve to a GType and so we ignore the return
		// value.
		relationRules.resolveType(relation, null);

		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);
				}
				lrr.add(rr);
			}
		}
	}

	private void addLine(Way way, GType gt) {
		MapLine line = new MapLine();
		elementSetup(line, gt, way);
		line.setPoints(way.getPoints());

		if (way.isBoolTag("oneway"))
			line.setDirection(true);

		clipper.clipLine(line, lineAdder);
	}

	private void addShape(Way way, GType gt) {
		MapShape shape = new MapShape();
		elementSetup(shape, gt, way);
		shape.setPoints(way.getPoints());

		clipper.clipShape(shape, collector);
		
		GType pointType = nodeRules.resolveType(way, null);
		
		if(pointType != null)
			shape.setPoiType(pointType.getType());
	}

	private void addPoint(Node node, GType gt) {
		if (!clipper.contains(node.getLocation()))
			return;

		// to handle exit points we use a subclass of MapPoint
		// to carry some extra info (a reference to the
		// motorway associated with the exit)
		MapPoint mp;
		int type = gt.getType();
		if(type >= 0x2000 && type < 0x2800) {
			String ref = node.getTag(Exit.TAG_ROAD_REF);
			String id = node.getTag("osm:id");
			if(ref != null) {
				String to = node.getTag(Exit.TAG_TO);
				MapExitPoint mep = new MapExitPoint(ref, to);
				String fd = node.getTag(Exit.TAG_FACILITY);
				if(fd != null)
					mep.setFacilityDescription(fd);
				if(id != null)
					mep.setOSMId(id);
				mp = mep;
			}
			else {
				mp = new MapPoint();
				log.warn("Motorway exit " + node.getName() + " (OSM id " + id + ") located at " + node.getLocation().toDegreeString() + " has no motorway! (either make the exit share a node with the motorway or specify the motorway ref with a " + Exit.TAG_ROAD_REF + " tag)");
			}
		}
		else {
			mp = new MapPoint();
		}
		elementSetup(mp, gt, node);
		mp.setLocation(node.getLocation());

		collector.addPoint(mp);
	}

	private String combineRefs(Element element) {
		String ref = element.getTag("ref");
		String int_ref = element.getTag("int_ref");
		if(int_ref != null) {
			if(ref == null)
				ref = int_ref;
			else
				ref += ";" + int_ref;
		}
		String nat_ref = element.getTag("nat_ref");
		if(nat_ref != null) {
			if(ref == null)
				ref = nat_ref;
			else
				ref += ";" + nat_ref;
		}
		String reg_ref = element.getTag("reg_ref");
		if(reg_ref != null) {
			if(ref == null)
				ref = reg_ref;
			else
				ref += ";" + reg_ref;
		}

		return ref;
	}

	private void elementSetup(MapElement ms, GType gt, Element element) {
		String name = element.getName();
		String refs = combineRefs(element);
		if(name == null && refs != null) {
			// use first ref as name
			name = refs.split(";")[0].trim();
		}
		if(name != null)
			ms.setName(name);
		if(refs != null)
			ms.setRef(refs);
		ms.setType(gt.getType());
		ms.setMinResolution(gt.getMinResolution());
		ms.setMaxResolution(gt.getMaxResolution());
		
		// Now try to get some address info for POIs
		
		String city         = element.getTag("addr:city");
		String zip          = element.getTag("addr:postcode");
		String street 	    = element.getTag("addr:street");
		String houseNumber  = element.getTag("addr:housenumber");
		String phone        = element.getTag("phone");
		String isIn         = element.getTag("is_in");
		String country      = element.getTag("is_in:country");
		String region       = element.getTag("is_in:county");
		
		if(country != null)
			country = element.getTag("addr:country");

		if(zip == null)
		  zip = element.getTag("openGeoDB:postal_codes");
		
		if(city == null)
		  city = element.getTag("openGeoDB:sort_name");
		
		if(city != null)
		  ms.setCity(city);
		  
		if(zip != null)
		  ms.setZip(zip);
		  
		if(street != null)
		  ms.setStreet(street);		  

		if(houseNumber != null)
		  ms.setHouseNumber(houseNumber);
		  
		if(isIn != null)
		  ms.setIsIn(isIn);		  
			
		if(phone != null)
		  ms.setPhone(phone);	

		if(country != null)
		  ms.setCountry(country);	

		if(region != null)
		  ms.setRegion(region);			
	}

	void addRoad(Way way, GType gt) {

		if("roundabout".equals(way.getTag("junction"))) {
			String frigFactorTag = way.getTag("mkgmap:frig_roundabout");
			if(frigFactorTag != null) {
				// do special roundabout frigging to make gps
				// routing prompt use the correct exit number
				double frigFactor = 0.25; // default
				try {
					frigFactor = Double.parseDouble(frigFactorTag);
				}
				catch (NumberFormatException nfe) {
					// relax, tag was probably not a number anyway
				}
				frigRoundabout(way, frigFactor);
			}
		}

		// if there is a bounding box, clip the way with it

		List<Way> clippedWays = null;

		if(bbox != null) {
			List<List<Coord>> lineSegs = LineClipper.clip(bbox, way.getPoints());
			boundaryCoords = new HashSet<Coord>();

			if (lineSegs != null) {

				clippedWays = new ArrayList<Way>();

				for (List<Coord> lco : lineSegs) {
					Way nWay = new Way(way.getId());
					nWay.setName(way.getName());
					nWay.copyTags(way);
					for(Coord co : lco) {
						nWay.addPoint(co);
						if(co.getHighwayCount() == 0) {
							boundaryCoords.add(co);
							co.incHighwayCount();
						}
					}
					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);
				}
			}
		}

		if(clippedWays != null) {
			for(Way cw : clippedWays) {
				while(cw.getPoints().size() > MAX_POINTS_IN_WAY) {
					Way tail = splitWayAt(cw, MAX_POINTS_IN_WAY - 1);
					addRoadAfterSplittingLoops(cw, gt);
					cw = tail;
				}
				addRoadAfterSplittingLoops(cw, gt);
			}
		}
		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);
				way = tail;
			}
			addRoadAfterSplittingLoops(way, gt);
		}
	}

	void addRoadAfterSplittingLoops(Way way, GType gt) {

		// check if the way is a loop or intersects with itself

		boolean wayWasSplit = true; // aka rescan required

		while(wayWasSplit) {
			List<Coord> wayPoints = way.getPoints();
			int numPointsInWay = wayPoints.size();

			wayWasSplit = false; // assume way won't be split

			// check each point in the way to see if it is the same
			// point as a following point in the way (actually the
			// same object not just the same coordinates)
			for(int p1I = 0; !wayWasSplit && p1I < (numPointsInWay - 1); p1I++) {
				Coord p1 = wayPoints.get(p1I);
				for(int p2I = p1I + 1; !wayWasSplit && p2I < numPointsInWay; p2I++) {
					if(p1 == wayPoints.get(p2I)) {
						// way is a loop or intersects itself 
						// attempt to split it into two ways

						// start at point before intersection point
						// check that splitting there will not produce
						// a zero length arc - if it does try the
						// previous point(s)
						int splitI = p2I - 1;
						while(splitI > p1I && p1.equals(wayPoints.get(splitI)))
							--splitI;

						if(splitI == p1I) {
							log.warn("Looped way " + getDebugName(way) + " has zero length arc - deleting node[" + p2I + "] to remove it");
							wayPoints.remove(p2I);
							// next point to inspect has same index
							--p2I;
							// but number of points has reduced
							--numPointsInWay;
						}
						else {
							// split the way before the second point
							log.info("Splitting looped way " + getDebugName(way) + " at node[" + splitI + "] - it has " + (numPointsInWay - splitI - 1 ) + " following segment(s).");
							Way loopTail = splitWayAt(way, splitI);
							// recursively check (shortened) head for
							// more loops
							addRoadAfterSplittingLoops(way, gt);
							// now process the tail of the way
							way = loopTail;
							wayWasSplit = true;
						}
					}
				}
			}

			if(!wayWasSplit) {
				// no split required so make road from way
				addRoadWithoutLoops(way, gt);
			}
		}
	}

	String getDebugName(Way way) {
		String name = way.getName();
		if(name == null)
			name = way.getTag("ref");
		if(name == null)
			name = "";
		else
			name += " ";
		return name + "(OSM id " + way.getId() + ")";
	}

	void addRoadWithoutLoops(Way way, GType gt) {
		List<Integer> nodeIndices = new ArrayList<Integer>();
		List<Coord> points = way.getPoints();
		Way trailingWay = null;
		String debugWayName = getDebugName(way);

		// make sure the way has nodes at each end
		points.get(0).incHighwayCount();
		points.get(points.size() - 1).incHighwayCount();

		// collect the Way's nodes and also split the way if any
		// inter-node arc length becomes excessive
		double arcLength = 0;
		for(int i = 0; i < points.size(); ++i) {
			Coord p = points.get(i);

			// check if we should split the way at this point to limit
			// the arc length between nodes
			if((i + 1) < points.size()) {
				double d = p.distance(points.get(i + 1));
				if(d > MAX_ARC_LENGTH) {
					double fraction = 0.99 * MAX_ARC_LENGTH / d;
					Coord extrap = p.makeBetweenPoint(points.get(i + 1), fraction);
					extrap.incHighwayCount();
					points.add(i + 1, extrap);
					double newD = p.distance(extrap);
					log.warn("Way " + debugWayName + " contains a segment that is " + (int)d + "m long so I am adding a point to reduce its length to " + (int)newD + "m");
					d = newD;
				}

				if((arcLength + d) > MAX_ARC_LENGTH) {
					assert i > 0;
					trailingWay = splitWayAt(way, i);
					// this will have truncated the current Way's
					// points so the loop will now terminate
					log.warn("Splitting way " + debugWayName + " at " + points.get(i).toDegreeString() + " to limit arc length to " + (long)arcLength + "m");
				}
				else {
					if(p.getHighwayCount() > 1)
						// point is a node so zero arc length
						arcLength = 0;

					arcLength += d;
				}
			}

			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++);
				}
				nodeIndices.add(i);

				if((i + 1) < points.size() &&
				   nodeIndices.size() == MAX_NODES_IN_WAY) {
					// this isn't the last point in the way so split
					// it here to avoid exceeding the max nodes in way
					// limit
					trailingWay = splitWayAt(way, i);
					// this will have truncated the current Way's
					// points so the loop will now terminate
					log.info("Splitting way " + debugWayName + " at " + points.get(i).toDegreeString() + " as it has at least " + MAX_NODES_IN_WAY + " nodes");
				}
			}
		}

		MapLine line = new MapLine();
		elementSetup(line, gt, way);
		line.setPoints(points);

		MapRoad road = new MapRoad(way.getId(), line);

		// set road parameters.
		road.setRoadClass(gt.getRoadClass());
		if (way.isBoolTag("oneway")) {
			road.setDirection(true);
			road.setOneway();
		}

		int speedIdx = -1;
		if(!ignoreMaxspeeds) {
			// maxspeed attribute overrides default for road type
			String maxSpeed = way.getTag("maxspeed");
			if(maxSpeed != null) {
				speedIdx = getSpeedIdx(maxSpeed);
				log.info(debugWayName + " maxspeed=" + maxSpeed + ", speedIndex=" + speedIdx);
			}
		}

		road.setSpeed(speedIdx >= 0? speedIdx : gt.getRoadSpeed());

		boolean[] noAccess = new boolean[RoadNetwork.NO_MAX];
		String highwayType = way.getTag("highway");
		if(highwayType == null) {
			// it's a routable way but not a highway (e.g. a ferry)
			// use the value of the route tag as the highwayType for
			// the purpose of testing for access restrictions
			highwayType = way.getTag("route");
		}

		for (AccessMapping anAccessMap : accessMap) {
			int index = anAccessMap.index;
			String type = anAccessMap.type;
			String accessTagValue = way.getTag(type);
			if (accessTagValue == null)
				continue;
			if (accessExplicitlyDenied(accessTagValue)) {
				if (index == RoadNetwork.NO_MAX) {
					// everything is denied access
					for (int j = 1; j < accessMap.length; ++j)
						noAccess[accessMap[j].index] = true;
				} else {
					// just the specific vehicle class is denied
					// access
					noAccess[index] = true;
				}
				log.info(type + " is not allowed in " + highwayType + " " + debugWayName);
			} else if (accessExplicitlyAllowed(accessTagValue)) {
				if (index == RoadNetwork.NO_MAX) {
					// everything is allowed access
					for (int j = 1; j < accessMap.length; ++j)
						noAccess[accessMap[j].index] = false;
				} else {
					// just the specific vehicle class is allowed
					// access
					noAccess[index] = false;
				}
				log.info(type + " is allowed in " + highwayType + " " + debugWayName);
			}
			else if (accessTagValue.equalsIgnoreCase("destination")) {
				if (type.equals("motorcar") ||
				    type.equals("motorcycle")) {
					road.setNoThroughRouting();
				} else if (type.equals("access")) {
					log.warn("access=destination only affects routing for cars in " + highwayType + " " + debugWayName);
					road.setNoThroughRouting();
				} else {
					log.warn(type + "=destination ignored in " + highwayType + " " + debugWayName);
				}
			} else if (accessTagValue.equalsIgnoreCase("unknown")) {
				// implicitly allow access
			} else {
				log.warn("Ignoring unsupported access tag value " + type + "=" + accessTagValue + " in " + highwayType + " " + debugWayName);
			}
		}

		road.setAccess(noAccess);

		if(way.isBoolTag("toll"))
			road.setToll();

		Way origWay = originalWay.get(way);
		if(origWay == null)
			origWay = way;

		int numNodes = nodeIndices.size();
		road.setNumNodes(numNodes);

		if(numNodes > 0) {
			// replace Coords that are nodes with CoordNodes
			boolean hasInternalNodes = false;
			CoordNode lastCoordNode = null;
			List<RestrictionRelation> lastRestrictions = null;
			for(int i = 0; i < numNodes; ++i) {
				int n = nodeIndices.get(i);
				if(n > 0 && n < points.size() - 1)
					hasInternalNodes = true;
				Coord coord = points.get(n);
				Integer nodeId = nodeIdMap.get(coord);
				boolean boundary = boundaryCoords.contains(coord);
				if(boundary) {
					log.info("Way " + debugWayName + "'s point #" + n + " at " + points.get(n).toDegreeString() + " is a boundary node");
				}

				CoordNode thisCoordNode = new CoordNode(coord.getLatitude(), coord.getLongitude(), nodeId, boundary);
				points.set(n, thisCoordNode);

				// see if this node plays a role in any turn
				// restrictions

				if(lastRestrictions != null) {
					// the previous node was the location of one or
					// more restrictions
					for(RestrictionRelation rr : lastRestrictions) {
						if(rr.getToWay().equals(origWay)) {
							rr.setToNode(thisCoordNode);
						}
						else if(rr.getFromWay().equals(origWay)) {
							rr.setFromNode(thisCoordNode);
						}
						else {
							rr.addOtherNode(thisCoordNode);
						}
					}
				}

				List<RestrictionRelation> theseRestrictions = restrictions.get(coord);
				if(theseRestrictions != null) {
					// this node is the location of one or more
					// restrictions
					for(RestrictionRelation rr : theseRestrictions) {
						rr.setViaNode(thisCoordNode);
						if(rr.getToWay().equals(origWay)) {
							if(lastCoordNode != null)
								rr.setToNode(lastCoordNode);
						}
						else if(rr.getFromWay().equals(origWay)) {
							if(lastCoordNode != null)
								rr.setFromNode(lastCoordNode);
						}
						else if(lastCoordNode != null) {
							rr.addOtherNode(lastCoordNode);
						}
					}
				}

				lastRestrictions = theseRestrictions;
				lastCoordNode = thisCoordNode;
			}

			road.setStartsWithNode(nodeIndices.get(0) == 0);
			road.setInternalNodes(hasInternalNodes);
		}

		lineAdder.add(road);

		if(trailingWay != null)
			addRoadWithoutLoops(trailingWay, gt);
	}

	// split a Way at the specified point and return the new Way (the
	// original Way is truncated)

	Way splitWayAt(Way way, int index) {
		Way trailingWay = new Way(way.getId());
		List<Coord> wayPoints = way.getPoints();
		int numPointsInWay = wayPoints.size();

		for(int i = index; i < numPointsInWay; ++i)
			trailingWay.addPoint(wayPoints.get(i));

		// ensure split point becomes a node
		wayPoints.get(index).incHighwayCount();

		// copy the way's name and tags to the new way
		trailingWay.setName(way.getName());
		trailingWay.copyTags(way);

		// remove the points after the split from the original way
		// it's probably more efficient to remove from the end first
		for(int i = numPointsInWay - 1; i > index; --i)
			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);

		return trailingWay;
	}

	// function to add points between adjacent nodes in a roundabout
	// to make gps use correct exit number in routing instructions
	void frigRoundabout(Way way, double frigFactor) {
		List<Coord> wayPoints = way.getPoints();
		int origNumPoints = wayPoints.size();

		if(origNumPoints < 3) {
			// forget it!
			return;
		}

		int[] highWayCounts = new int[origNumPoints];
		int middleLat = 0;
		int middleLon = 0;
		highWayCounts[0] = wayPoints.get(0).getHighwayCount();
		for(int i = 1; i < origNumPoints; ++i) {
			Coord p = wayPoints.get(i);
			middleLat += p.getLatitude();
			middleLon += p.getLongitude();
			highWayCounts[i] = p.getHighwayCount();
		}
		middleLat /= origNumPoints - 1;
		middleLon /= origNumPoints - 1;
		Coord middleCoord = new Coord(middleLat, middleLon);

		// account for fact that roundabout joins itself
		--highWayCounts[0];
		--highWayCounts[origNumPoints - 1];

		for(int i = origNumPoints - 2; i >= 0; --i) {
			Coord p1 = wayPoints.get(i);
			Coord p2 = wayPoints.get(i + 1);
			if(highWayCounts[i] > 1 && highWayCounts[i + 1] > 1) {
				// both points will be nodes so insert a new point
				// between them that (approximately) falls on the
				// roundabout's perimeter
				int newLat = (p1.getLatitude() + p2.getLatitude()) / 2;
				int newLon = (p1.getLongitude() + p2.getLongitude()) / 2;
				// new point has to be "outside" of existing line
				// joining p1 and p2 - how far outside is determined
				// by the ratio of the distance between p1 and p2
				// compared to the distance of p1 from the "middle" of
				// the roundabout (aka, the approx radius of the
				// roundabout) - the higher the value of frigFactor,
				// the further out the point will be
				double scale = 1 + frigFactor * p1.distance(p2) / p1.distance(middleCoord);
				newLat = (int)((newLat - middleLat) * scale) + middleLat;
				newLon = (int)((newLon - middleLon) * scale) + middleLon;
				Coord newPoint = new Coord(newLat, newLon);
				double d1 = p1.distance(newPoint);
				double d2 = p2.distance(newPoint);
				double maxDistance = 100;
				if(d1 >= MIN_DISTANCE_BETWEEN_NODES && d1 <= maxDistance &&
				   d2 >= MIN_DISTANCE_BETWEEN_NODES && d2 <= maxDistance) {
				    newPoint.incHighwayCount();
				    wayPoints.add(i + 1, newPoint);
				}
			}
		}
	}

	int getSpeedIdx(String tag)
	{
		double kmh;
		double factor = 1.0;
		
		String speedTag = tag.toLowerCase().trim();
		
		if(speedTag.matches(".*mph")) // Check if it is a limit in mph
		{
			speedTag = speedTag.replaceFirst("mph", "");
			factor = 1.61;
		}
		else
			speedTag = speedTag.replaceFirst("kmh", "");  // get rid of kmh just in case
		
		try {
			kmh = Integer.parseInt(speedTag) * factor;
		}
		catch (Exception e)
		{
			return -1;
		}
		
		if(kmh > 110)
			return 7;
		if(kmh > 90)
			return 6;
		if(kmh > 80)
			return 5;
		if(kmh > 60)
			return 4;
		if(kmh > 40)
			return 3;
		if(kmh > 20)
			return 2;
		if(kmh > 10)
			return 1;
		else
			return 0;	    

	}

	protected boolean accessExplicitlyAllowed(String val) {
		if (val == null)
			return false;

		return (val.equalsIgnoreCase("yes") ||
			val.equalsIgnoreCase("designated") ||
			val.equalsIgnoreCase("permissive"));
	}

	protected boolean accessExplicitlyDenied(String val) {
		if (val == null)
			return false;

		return (val.equalsIgnoreCase("no") ||
			val.equalsIgnoreCase("private"));
	}
}

package uk.me.parabola.mkgmap.osmstyle;

import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.LevelInfo;
import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.scan.TokType;
import uk.me.parabola.mkgmap.scan.Token;
import uk.me.parabola.mkgmap.scan.TokenScanner;

/**
 * Read a type description from a style file.
 */
public class TypeReader {
	private static final Logger log = Logger.getLogger(TypeReader.class);

	private final int kind;
	private final LevelInfo[] levels;

	public TypeReader(int kind, LevelInfo[] levels) {
		this.kind = kind;
		this.levels = levels;
	}

	public GType readType(TokenScanner ts) {
		// We should have a '[' to start with
		Token t = ts.nextToken();
		if (t == null || t.getType() == TokType.EOF)
			throw new SyntaxException(ts, "No garmin type information given");

		if (!t.getValue().equals("[")) {
			throw new SyntaxException(ts, "No type definition");
		}

		ts.skipSpace();
		String type = ts.nextValue();
		if (!Character.isDigit(type.charAt(0)))
			throw new SyntaxException(ts, "Garmin type number must be first.  Saw '" + type + '\'');

		log.debug("gtype", type);
		GType gt = new GType(kind, type);

		while (!ts.isEndOfFile()) {
			ts.skipSpace();
			String w = ts.nextValue();
			if (w.equals("]"))
				break;

			if (w.equals("level")) {
				setLevel(ts, gt);
			} else if (w.equals("resolution")) {
				setResolution(ts, gt);
			} else if (w.equals("default_name")) {
				gt.setDefaultName(nextValue(ts));
			} else if (w.equals("road_class")) {
				gt.setRoadClass(nextIntValue(ts));
			} else if (w.equals("road_speed")) {
				gt.setRoadSpeed(nextIntValue(ts));
			} else if (w.equals("copy")) {
				// reserved word.  not currently used
			} else if (w.equals("continue")) {
				gt.setContinue();
			} else if (w.equals("stop")) {
				gt.setFinal();
			} else {
				throw new SyntaxException(ts, "Unrecognised type command '" + w + '\'');
			}
		}

		gt.fixLevels(levels);
		return gt;
	}

	private int nextIntValue(TokenScanner ts) {
		if (ts.checkToken("="))
			ts.nextToken();
		try {
			return ts.nextInt();
		} catch (NumberFormatException e) {
			throw new SyntaxException(ts, "Expecting numeric value");
		}
	}

	/**
	 * Get the value in a 'name=value' pair.
	 */
	private String nextValue(TokenScanner ts) {
		if (ts.checkToken("="))
			ts.nextToken();
		return ts.nextWord();
	}

	private void setResolution(TokenScanner ts, GType gt) {
		String str = ts.nextWord();
		log.debug("res word value", str);
		try {
			if (str.indexOf('-') >= 0) {
				String[] minmax = str.split("-", 2);
				gt.setMaxResolution(Integer.parseInt(minmax[0]));
				gt.setMinResolution(Integer.parseInt(minmax[1]));
			} else {
				gt.setMinResolution(Integer.parseInt(str));
			}
		} catch (NumberFormatException e) {
			gt.setMinResolution(24);
		}
	}

	private void setLevel(TokenScanner ts, GType gt) {
		String str = ts.nextWord();
		try {
			if (str.indexOf('-') >= 0) {
				String[] minmax = str.split("-", 2);
				gt.setMaxResolution(toResolution(Integer.parseInt(minmax[0])));
				gt.setMinResolution(toResolution(Integer.parseInt(minmax[1])));
			} else {
				gt.setMinResolution(toResolution(Integer.parseInt(str)));
			}
		} catch (NumberFormatException e) {
			gt.setMinResolution(24);
		}
	}

	private int toResolution(int level) {
		int max = levels.length - 1;
		if (level > max)
			throw new SyntaxException("Level number too large, max=" + max);

		return levels[max - level].getBits();
	}
}

_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to