Subversion Repositories mkgmap

Rev

Rev 1552 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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: 29-Nov-2008
 */

package uk.me.parabola.mkgmap.osmstyle;

import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import uk.me.parabola.mkgmap.general.LevelInfo;
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.TypeResult;
import uk.me.parabola.mkgmap.reader.osm.Way;

import org.junit.Test;

import static org.junit.Assert.*;


public class RuleFileReaderTest {
        /**
         * Test of a file containing a number of different rules, with varying
         * formatting and including comments.
         */

        @Test
        public void testLoad() {
                RuleSet rs = makeRuleSet("highway=footway & type=rough [0x2 level 2]\n" +
                "highway=footway | highway = path\n" +
                "  [0x3]\n# comment here\n" +
                "foo=\nbar & bar=two [0x4]\n" +
                "highway=* & oneway=true [0x6 level 1]\n" +
                "");

                Element el = new Way(1);
                el.addTag("highway", "footway");

                GType type = getFirstType(rs, el);
                assertEquals("plain footway", "[0x3 level 0]", type.toString());

                el.addTag("type", "rough");
                type = getFirstType(rs, el);
                assertEquals("rough footway", "[0x2 level 2]", type.toString());
        }

        /**
         * Test for non-standard level specification.  You can give a range
         * of levels, rather than defaulting the max end to 0.
         */

        @Test
        public void testLevel() {
                RuleSet rs = makeRuleSet(
                                "highway=primary [0x1 level 1-3]"
                );

                Element el = new Way(1);
                el.addTag("highway", "primary");

                GType type = getFirstType(rs, el);
                assertEquals("min level", 1, type.getMinLevel());
                assertEquals("max level", 3, type.getMaxLevel());
        }

        /**
         * Try out arithmetic comparisons and mixtures of 'and' and 'or'.
         */

        @Test
        public void testComplexExpressions() {
                String str = "a=b & (c=d | e=f) & x>10 [0x1]\n";
                RuleSet rs = makeRuleSet(str);

                Element el = new Way(1);
                el.addTag("a", "b");
                el.addTag("c", "d");
                el.addTag("x", "11");

                GType type = getFirstType(rs, el);
                assertEquals("expression ok", 1, type.getType());

                // fails with x less than 10
                el.addTag("x", "9");
                type = getFirstType(rs, el);
                assertNull("x too low", type);

                // also fails with x equal to 10
                el.addTag("x", "10");
                type = getFirstType(rs, el);
                assertNull("x too low", type);

                // OK with x > 10
                el.addTag("x", "100");
                el.addTag("e", "f");
                type = getFirstType(rs, el);
                assertEquals("c and e set", 1, type.getType());

                el.addTag("c", "");
                el.addTag("e", "");
                type = getFirstType(rs, el);
                assertNull("none of c and e set", type);

                el.addTag("e", "f");
                type = getFirstType(rs, el);
                assertEquals("e is set to f", 1, type.getType());
        }

        /**
         * Test based on email on the mailing list at:
         * http://www.mkgmap.org.uk/pipermail/mkgmap-dev/2009q3/003009.html
         * See that email for an explanation.
         */

        @Test
        public void testComparasons() {
                String str = "highway=null_null & layer<0  [0x01 resolution 10]\n" +
                                "highway=null_null & layer=0  [0x02 resolution 10]\n" +
                                "highway=null_null & layer>0  [0x03 resolution 10]\n" +
                                "highway=null_null & layer='-1'  [0x04 resolution 10]\n" +
                                "highway=null_null & layer='0'  [0x05 resolution 10]\n" +
                                "highway=null_null & layer='1'  [0x06 resolution 10]\n" +
                                "highway=null_null & layer='+1'  [0x07 resolution 10]\n" +
                                "highway=null_null   [0x08 resolution 10]";
                RuleSet rs = makeRuleSet(str);

                // 9902
                Element el = new Way(1);
                el.addTag("highway", "null_null");
                el.addTag("layer", "-1");

                GType type = getFirstType(rs, el);
                assertEquals("9902 layer = -1", 0x1, type.getType());

                // 9912
                el.addTag("layer", "0");
                type = getFirstType(rs, el);
                assertEquals("9912 layer = 0", 0x2, type.getType());

                // 9922
                el.deleteTag("layer");
                type = getFirstType(rs, el);
                assertEquals("9922 no layer tag", 0x8, type.getType());

                // 9932
                el.addTag("layer", "1");
                type = getFirstType(rs, el);
                assertEquals("9932 layer is 1", 0x3, type.getType());

                // 9952
                el.addTag("layer", "+1");
                assertEquals("9952 layer is +1", 0x3, type.getType());
        }

        @Test
        public void testMultipleActions() {
                String rstr = "highway=footway {add access = no; add foot = yes} [0x16 road_class=0 road_speed=0 resolution 23]";
                RuleSet rs = makeRuleSet(rstr);

                Element el = new Way(1);
                el.addTag("highway", "footway");

                getFirstType(rs, el);
                assertEquals("access set", "no", el.getTag("access"));
                assertEquals("access set", "yes", el.getTag("foot"));
        }

        /**
         * You can now have a wild card at the top level.
         */

        @Test
        public void testWildcardTop() {
                RuleSet rs = makeRuleSet("highway=* {set a=fred} [0x1]\n");

                assertNotNull("rule found", rs);
               
                Element el = new Way(1);
                el.addTag("highway", "secondary");
                GType type = getFirstType(rs, el);
                assertNotNull("can find match", type);
                assertEquals("correct type", 1, type.getType());
                assertEquals("tag set", "fred", el.getTag("a"));
        }

        /**
         * Deal with cases such as
         * (a = b | a = c) & d!=*
         * where there is no key at the top level.  This gets converted
         * to: (a=b & d!=*) | (a=c & d!= *) which can then be used.
         *
         * This is applied recursively, so you can have chains of any length.
         */

        @Test
        public void testLeftSideOr() {
                RuleSet rs = makeRuleSet("(a = b | a = c | a=d) & e!=* [0x2]" +
                                "a=c & e!=* [0x1]");

                assertNotNull("a=b chain", rs);
                assertNotNull("a=c chain", rs);
                assertNotNull("a=d chain", rs);

                // get the a=c chain and look at it more closely
                Element el = new Way(1);
                el.addTag("a", "c");
                GType type = getFirstType(rs, el);

                assertNotNull("match e not existing", type);
                assertEquals("correct type", 2, type.getType());

                el = new Way(2);
                el.addTag("a", "d");
                assertNotNull("match e not existing", type);
                assertEquals("correct type", 2, type.getType());
        }

        /**
         * You can now have a wild card at the top level, here we have & between
         * two of them.
         */

        @Test
        public void testWildcard2() {
                RuleSet rs = makeRuleSet("highway=* & z=* {set a=square} [0x1]\n");

                assertNotNull("rule found", rs);

                Element el = new Way(1);
                el.addTag("highway", "secondary");
                GType type = getFirstType(rs, el);
                assertNull("type not found with no z tag", type);

                // now add z
                el.addTag("z", "1");
                type = getFirstType(rs, el);
                assertNotNull("found match", type);
                assertEquals("correct type", 1, type.getType());
                assertEquals("tag set", "square", el.getTag("a"));
        }

        /**
         * Tests for the road classification and other parts of the GType.
         */

        @Test
        public void testGType() {
                RuleSet rs = makeRuleSet("highway=motorway " +
                                "[0x1 road_class=4 road_speed=7 default_name='motor way']\n");

                Element el = new Way(1);
                el.addTag("highway", "motorway");
                GType type = getFirstType(rs, el);

                // Check that the correct class and speed are returned.
                assertEquals("class", 4, type.getRoadClass());
                assertEquals("class", 7, type.getRoadSpeed());
                assertEquals("default name", "motor way", type.getDefaultName());
        }

        /**
         * Check for the regexp handling.
         */

        @Test
        public void testRegexp() {
                RuleSet rs = makeRuleSet("highway=* & name ~ 'blue.*' [0x2]\n");

                assertNotNull("rule found", rs);

                // Set up element with matching name
                Element el = new Way(1);
                el.addTag("highway", "secondary");
                el.addTag("name", "blue sq");
                GType type = getFirstType(rs, el);
                assertNotNull("matched regexp", type);
                assertEquals("matched type", 2, type.getType());

                // change name to one that should not match
                el.addTag("name", "yellow");
                type = getFirstType(rs, el);
                assertNull("no match for yello", type);
        }

        /**
         * This simply is to make sure that actions that affect their own
         * conditions do not hang. There are no defined semantics for this.
         */

        @Test
        public void testSelfReference() {
                RuleSet rs = makeRuleSet("iii=* { set iii=no }");
                //Rule rule = rs.getMap().get("foot=*");
                Way el = new Way(1);
                el.addTag("foot", "yes");
                el.addTag("iii", "xyz");
                getFirstType(rs, el);
        }

        /**
         * Test the not operator.
         */

        @Test
        public void testNot() {
                RuleSet rs = makeRuleSet("tunnel=yes & !(route=mtb | route=bicycle) [0x1]");
                //RuleSet rs = makeRuleSet("tunnel=yes & (route!=mtb & route!=bicycle) [0x1]");

                Way el = new Way(1);
                el.addTag("tunnel", "yes");
                el.addTag("route", "abc");
                GType type = getFirstType(rs, el);
                assertNotNull("without route mtb or bicycle", type);

                el.addTag("route", "mtb");
                type = getFirstType(rs, el);
                assertNull("with route mtb", type);
        }

        /**
         * A moderately complex set of conditions and substitutions.
         */

        @Test
        public void testMtbRules() {
                RuleSet rs = makeRuleSet(
                                "(mtb:scale=*  | mtb:scale:uphill=*) & route=mtb" +
                                                "{ name 'mtbrt${mtb:scale|def:.}${mtb:scale:uphill|def:.} ${name}' " +
                                                "       | 'mtbrt${mtb:scale|def:.}${mtb:scale:uphill|def:.}' }" +
                                                " (mtb:scale=* | mtb:scale:uphill=*) & route!=mtb " +
                                                "{ name 'mtb${mtb:scale|def:.}${mtb:scale:uphill|def:.} ${name}' " +
                                                "       | 'mtb${mtb:scale|def:.}${mtb:scale:uphill|def:.}' }"
                               
                                );

                Way el = new Way(1);
                el.addTag("route", "mtb");
                el.addTag("mtb:scale", "2");
                getFirstType(rs, el);
                assertEquals("mtbrt2.", el.getName());

                el = new Way(1);
                el.addTag("route", "mtb");
                el.addTag("mtb:scale:uphill", "3");
                getFirstType(rs, el);
                assertEquals("mtbrt.3", el.getName());

                el = new Way(1);
                el.addTag("name", "myname");
                el.addTag("route", "mtb");
                el.addTag("mtb:scale:uphill", "3");
                getFirstType(rs, el);
                assertEquals("mtbrt.3 myname", el.getName());

                el = new Way(1);
                el.addTag("mtb:scale:uphill", "3");
                getFirstType(rs, el);
                assertEquals("mtb.3", el.getName());
        }

        /**
         * Appending to an existing tag.
         */

        @Test
        public void testTagAppend() {
                RuleSet rs = makeRuleSet(
                                "highway=*{set fullname='${ref}';" +
                                                "set fullname='${fullname} ${name}';" +
                                                "set fullname='${fullname} ${name1}';" +
                                                "set fullname='${fullname} ${name2}';" +
                                                "name '${fullname}'}"
                );
               
                Way el = new Way(1);
                el.addTag("highway", "road");
                el.addTag("ref", "A1");
                el.addTag("name", "long lane");
                el.addTag("name1", "foo");
                el.addTag("name2", "bar");

                getFirstType(rs, el);
                assertEquals("appended name", "A1 long lane foo bar", el.getName());
        }

        @Test
        public void testExists() {
                RuleSet rs = makeRuleSet("highway=* & maxspeed=40 {set mcssl=40}" +
                                "highway=primary & mcssl=40 [0x2 ]" +
                                "highway=* & mcssl=40 [0x3]");
                Way el = new Way(1);
                el.addTag("ref", "A123");
                el.addTag("name", "Long Lane");
                el.addTag("highway", "primary");
                el.addTag("maxspeed", "40");

                GType type = getFirstType(rs, el);
                assertNotNull("finds the type", type);
                assertEquals("resulting type", 2, type.getType());
        }

        /**
         * Test the continue keyword.  If a type is marked with this word, then
         * further matches are performed and this might result in more types
         * being added.
         */

        @Test
        public void testContinue() {
                RuleSet rs = makeRuleSet("highway=primary [0x1 continue]" +
                                "highway=primary [0x2 continue]" +
                                "highway=primary [0x3]" +
                                "highway=primary [0x4]"
                );

                Way el = new Way(1);
                el.addTag("highway", "primary");

                final List<GType> list = new ArrayList<GType>();

                rs.resolveType(el, new TypeResult() {
                        public void add(Element el, GType type) {
                                list.add(type);
                        }
                });

                GType type = list.get(0);
                assertEquals("first type", 1, type.getType());
                assertEquals("continue search", true, type.isContinueSearch());

                assertEquals("number of result types", 3, list.size());
                assertEquals("type of first", 1, list.get(0).getType());
                assertEquals("type of second", 2, list.get(1).getType());
                assertEquals("type of third", 3, list.get(2).getType());
        }

        @Test
        public void testContinueRepeat() {
                RuleSet rs = makeRuleSet("highway=primary [0x1 continue]" +
                                "highway=primary [0x2 continue]" +
                                "highway=primary [0x3]" +
                                "highway=primary [0x4]"
                );

                Way el = new Way(1);
                el.addTag("highway", "primary");

                for (int i = 0; i < 3; i++) {
                        GType type = getFirstType(rs, el);
                        assertEquals("first type", 1, type.getType());
                        assertEquals("continue search", true, type.isContinueSearch());
                }
        }

        /**
         * The main point of this test is to ensure that all the examples compile.
         */

        @Test
        public void testComplexRegex() {
                RuleSet rs = makeRuleSet(
                                //"a~b      [0x0]" +
                                "a~b & c=d  [0x1]" +
                                                "a~b & c~d & e=f   [0x2]" +
                                                "(a~b | c~d) & e=f  [0x3]" +
                                                "(a~b | c~d) & e=f & g=h  [0x4]" +
                                                "((a~b | c~d) & e=f) & g=h [0x5]" +
                                                "e=f & g=h & (a~b | c~'d.*')  [0x6]" +
                                                "(e=f & g=h) & (a~b | c~'d.*')  [0x7]" +
                                                "a=* & b=* & c=d" +
                                                "a=* & (b=* | c=d)" +
                                                ""
                );

                Way el = new Way(1);
                el.addTag("c", "df");
                el.addTag("g", "h");
                el.addTag("e", "f");

                GType type = getFirstType(rs, el);
                assertNotNull("matches a rule", type);
        }

        @Test
        public void testTagsUsed() {
                RuleSet rs = makeRuleSet("highway=primary & surface=good [0x1]" +
                                "A=B | C=D & E~'f.*' & G!=9 & K=* & L!=* [0x2]");

                Set<String> tags = rs.getUsedTags();
                assertEquals("number of tags used", 8, tags.size());
                assertTrue("has highway", tags.contains("highway"));
                assertTrue("has surface", tags.contains("surface"));
                assertTrue("has A", tags.contains("A"));
                assertTrue("has C", tags.contains("C"));
                assertTrue("has E", tags.contains("E"));
                assertTrue("has G", tags.contains("G"));
                assertTrue("has K", tags.contains("K"));
                assertTrue("has L", tags.contains("L"));
        }

        /**
         * There is a case where a tag is only used in an action but not in any
         * expression.  If we dropped the tags it would not be available for the
         * action.  A typical example might be name.
         */

        @Test
        public void testTagsUsedInActions() {
                RuleSet rs = makeRuleSet("A=B { set t='${C}'; add t='${D} p ${E}'; name '${F} ${G}'; rename K L");

                Set<String> tags = rs.getUsedTags();
                assertTrue("has A", tags.contains("A"));
                assertTrue("has C", tags.contains("C"));
                assertTrue("has D", tags.contains("D"));
                assertTrue("has E", tags.contains("E"));
                assertTrue("has F", tags.contains("F"));
                assertTrue("has G", tags.contains("G"));
                assertTrue("has K", tags.contains("K"));
        }

        /**
         * Resolve the rule set with the given element and get the first
         * resolved type.
         */

        private GType getFirstType(Rule rs, Element el) {
                final List<GType> types = new ArrayList<GType>();
                rs.resolveType(el, new TypeResult() {
                        public void add(Element el, GType type) {
                                types.add(type);
                        }
                });
                if (types.isEmpty())
                        return null;
                else
                        return types.get(0);
        }

        /**
         * Create a rule set out of a string.  The string is processed
         * as if it were in a file and the levels spec had been set.
         */

        private RuleSet makeRuleSet(String in) {
                Reader r = new StringReader(in);

                RuleSet rs = new RuleSet();
                RuleFileReader rr = new RuleFileReader(GType.POLYLINE, LevelInfo.createFromString("0:24 1:20 2:18 3:16 4:14"), rs);
                rr.load(r, "string");
                return rs;
        }
}