[mkgmap] 03/04: Imported Upstream version 3333

Andreas Tille tille at debian.org
Wed Aug 13 20:21:28 UTC 2014


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository mkgmap.

commit 98bd598c677fab18d9f9994ddf9ae6261bc45f0d
Author: Andreas Tille <tille at debian.org>
Date:   Wed Aug 13 22:19:02 2014 +0200

    Imported Upstream version 3333
---
 .idea/codeStyleSettings.xml                        |     1 -
 .idea/copyright/profiles_settings.xml              |     4 +-
 .idea/inspectionProfiles/Mapping.xml               |    72 +-
 .idea/runConfigurations/mkgmap.xml                 |     2 +-
 doc/options.txt                                    |    34 +-
 doc/resources/asciidoc/local-docbook45.conf        |    28 +-
 doc/resources/docbook-xsl/fo.xsl                   |    22 +-
 doc/resources/make.param                           |    12 +-
 doc/styles/Makefile                                |     8 +-
 doc/styles/about.txt                               |     2 +-
 doc/styles/creating.txt                            |    58 +-
 doc/styles/internal-tags.txt                       |   135 +
 doc/styles/main.txt                                |     2 +-
 doc/styles/rules-filters.txt                       |    32 +-
 doc/styles/rules.txt                               |   257 +-
 doc/styles/style-manual.txt                        |     2 +-
 doc/typ-compiler.txt                               |    18 +-
 extra/src/uk/me/parabola/util/CollationRules.java  |   437 +
 ivy.xml                                            |    10 +-
 mkgmap.iml                                         |    34 +-
 resources/chars/ascii/rowff.trans                  |     2 +-
 resources/chars/latin1/rowff.trans                 |     2 +-
 resources/help/en/options                          |    24 +-
 resources/installer/installer_template.nsi         |    11 +-
 resources/mkgmap-version.properties                |     4 +-
 resources/sort/README                              |    56 +
 resources/sort/cp0.txt                             |    81 +
 resources/sort/cp1250.txt                          |   200 +-
 resources/sort/cp1251.txt                          |   312 +-
 resources/sort/cp1252.txt                          |   275 +-
 resources/sort/cp1253.txt                          |   142 +
 resources/sort/cp1254.txt                          |   133 +
 resources/sort/cp1255.txt                          |   158 +
 resources/sort/cp1256.txt                          |   282 +-
 resources/sort/cp1257.txt                          |   129 +
 resources/sort/cp1258.txt                          |   134 +
 resources/sort/cp65001.txt                         | 11135 +++++++++++++++++++
 resources/styles/default/inc/access                |    92 +-
 resources/styles/default/inc/access_country        |    22 +
 resources/styles/default/inc/address               |    22 +-
 resources/styles/default/inc/name                  |    11 +
 resources/styles/default/lines                     |    16 +-
 resources/styles/default/points                    |     4 +-
 resources/styles/default/polygons                  |    10 +-
 resources/styles/default/relations                 |     2 +-
 scripts/download/buildwatch                        |    19 -
 scripts/download/mkdoc                             |     3 +-
 scripts/download/mksnap                            |    12 +-
 scripts/download/mksnapbranches                    |     3 +-
 scripts/download/mksnapindex                       |   110 -
 scripts/download/nightly                           |    11 +
 scripts/download/post-commit-svn-redis             |     8 +
 scripts/download/pre-commit-crlf                   |    22 +
 scripts/download/skel-bot                          |    45 -
 scripts/download/skel-top                          |    47 -
 src/uk/me/parabola/imgfmt/MapFailedException.java  |    19 +-
 src/uk/me/parabola/imgfmt/Utils.java               |   101 +
 src/uk/me/parabola/imgfmt/app/Area.java            |    73 +-
 .../parabola/imgfmt/app/BufferedImgFileWriter.java |     4 +-
 src/uk/me/parabola/imgfmt/app/Coord.java           |   589 +-
 src/uk/me/parabola/imgfmt/app/CoordNode.java       |    13 +-
 src/uk/me/parabola/imgfmt/app/Label.java           |    45 +-
 .../imgfmt/app/labelenc/AnyCharsetEncoder.java     |     9 +-
 .../parabola/imgfmt/app/labelenc/BaseEncoder.java  |     4 +-
 .../imgfmt/app/labelenc/CodeFunctions.java         |    22 +-
 .../parabola/imgfmt/app/labelenc/EncodedText.java  |    26 +-
 .../imgfmt/app/labelenc/Format6Encoder.java        |     8 +-
 .../imgfmt/app/labelenc/Simple8Encoder.java        |    30 -
 .../parabola/imgfmt/app/labelenc/Utf8Encoder.java  |    11 +-
 src/uk/me/parabola/imgfmt/app/lbl/City.java        |     4 +
 src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java     |     4 +-
 .../me/parabola/imgfmt/app/lbl/LBLFileReader.java  |    19 +-
 src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java  |    46 +-
 src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java     |    14 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr1.java        |    20 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java       |    13 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr10Record.java |     4 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java       |    10 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java       |    23 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java       |     8 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java       |    10 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java       |     6 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java       |    25 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java        |    22 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java  |    16 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr6.java        |     6 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java        |    10 +-
 .../me/parabola/imgfmt/app/mdr/MdrMapSection.java  |    23 +
 src/uk/me/parabola/imgfmt/app/mdr/MdrSection.java  |     9 -
 src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java |    26 +-
 src/uk/me/parabola/imgfmt/app/mdr/RecordBase.java  |     1 +
 .../parabola/imgfmt/app/net/AccessTagsAndBits.java |   117 +
 .../imgfmt/app/net/GeneralRouteRestriction.java    |   112 +
 src/uk/me/parabola/imgfmt/app/net/NETFile.java     |    27 +-
 src/uk/me/parabola/imgfmt/app/net/NOD1Part.java    |    56 +-
 src/uk/me/parabola/imgfmt/app/net/NODFile.java     |    44 +-
 src/uk/me/parabola/imgfmt/app/net/NODHeader.java   |    80 +-
 src/uk/me/parabola/imgfmt/app/net/RoadDef.java     |   108 +-
 src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java |   709 ++
 src/uk/me/parabola/imgfmt/app/net/RouteArc.java    |   292 +-
 src/uk/me/parabola/imgfmt/app/net/RouteCenter.java |    53 +-
 src/uk/me/parabola/imgfmt/app/net/RouteNode.java   |   434 +-
 .../parabola/imgfmt/app/net/RouteRestriction.java  |   153 +-
 src/uk/me/parabola/imgfmt/app/net/TableA.java      |   142 +-
 src/uk/me/parabola/imgfmt/app/net/TableB.java      |     1 -
 src/uk/me/parabola/imgfmt/app/net/TableC.java      |    21 +-
 .../me/parabola/imgfmt/app/srt/CodePosition.java   |    12 +-
 src/uk/me/parabola/imgfmt/app/srt/SRTFile.java     |    75 +-
 src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java   |    63 +-
 src/uk/me/parabola/imgfmt/app/srt/Sort.java        |   575 +-
 src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java  |    14 +-
 .../parabola/imgfmt/app/trergn/LinePreparer.java   |    53 +-
 src/uk/me/parabola/imgfmt/app/trergn/Polyline.java |     4 +-
 .../parabola/imgfmt/app/trergn/RGNFileReader.java  |     4 +-
 .../me/parabola/imgfmt/app/trergn/Subdivision.java |    11 +-
 src/uk/me/parabola/imgfmt/sys/Directory.java       |     6 +-
 src/uk/me/parabola/log/Logger.java                 |     4 +
 src/uk/me/parabola/mkgmap/CommandArgsReader.java   |    20 +-
 src/uk/me/parabola/mkgmap/build/MapArea.java       |    63 +-
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |    34 +-
 .../parabola/mkgmap/combiners/GmapsuppBuilder.java |     2 +-
 .../me/parabola/mkgmap/combiners/MdrBuilder.java   |    39 +-
 .../mkgmap/filters/DouglasPeuckerFilter.java       |    77 +-
 .../parabola/mkgmap/filters/LineMergeFilter.java   |    16 +-
 .../mkgmap/filters/LinePreparerFilter.java         |    68 +-
 .../mkgmap/filters/LineSizeSplitterFilter.java     |     4 +-
 .../mkgmap/filters/LineSplitterFilter.java         |     3 +-
 .../mkgmap/filters/PolygonSplitterBase.java        |    47 +-
 .../PreserveHorizontalAndVerticalLinesFilter.java  |     6 -
 .../mkgmap/filters/RemoveObsoletePointsFilter.java |   103 +-
 .../parabola/mkgmap/filters/ShapeMergeFilter.java  |   468 +
 .../parabola/mkgmap/filters/SmoothingFilter.java   |     2 +-
 src/uk/me/parabola/mkgmap/general/AreaClipper.java |     9 +
 src/uk/me/parabola/mkgmap/general/LevelInfo.java   |     9 +-
 src/uk/me/parabola/mkgmap/general/LineClipper.java |    41 +-
 .../me/parabola/mkgmap/general/MapCollector.java   |     6 +-
 .../me/parabola/mkgmap/general/MapDataSource.java  |     1 +
 src/uk/me/parabola/mkgmap/general/MapDetails.java  |    28 +-
 src/uk/me/parabola/mkgmap/general/MapLine.java     |    24 +-
 .../mkgmap/general/MapPointFastFindMap.java        |   202 -
 .../me/parabola/mkgmap/general/MapPointKdTree.java |     4 +-
 .../parabola/mkgmap/general/MapPointMultiMap.java  |    59 -
 src/uk/me/parabola/mkgmap/general/MapRoad.java     |     3 +-
 src/uk/me/parabola/mkgmap/general/MapShape.java    |   153 +-
 .../me/parabola/mkgmap/general/PolygonClipper.java |    23 +-
 src/uk/me/parabola/mkgmap/general/RoadNetwork.java |   285 -
 .../me/parabola/mkgmap/main/AbstractTestMap.java   |     5 +-
 src/uk/me/parabola/mkgmap/main/Main.java           |    52 +-
 src/uk/me/parabola/mkgmap/main/MapMaker.java       |    10 +-
 src/uk/me/parabola/mkgmap/main/StyleTester.java    |    35 +-
 src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java |    55 +-
 .../mkgmap/osmstyle/CombinedStyleFileLoader.java   |     2 +
 .../me/parabola/mkgmap/osmstyle/ConvertedWay.java  |   285 +
 .../mkgmap/osmstyle/DirectoryFileLoader.java       |    17 +-
 .../parabola/mkgmap/osmstyle/ExpressionRule.java   |    28 +-
 .../me/parabola/mkgmap/osmstyle/JarFileLoader.java |    12 +-
 src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java |   655 +-
 .../parabola/mkgmap/osmstyle/RuleFileReader.java   |    45 +-
 src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java  |   177 +-
 src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java    |   118 +-
 src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java  |     2 +-
 .../parabola/mkgmap/osmstyle/StyledConverter.java  |  1693 ++-
 src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java |    49 +-
 .../parabola/mkgmap/osmstyle/WrongAngleFixer.java  |  1403 +++
 .../parabola/mkgmap/osmstyle/actions/Action.java   |     8 +-
 .../mkgmap/osmstyle/actions/ActionReader.java      |    13 +-
 .../mkgmap/osmstyle/actions/AddAccessAction.java   |    17 +-
 .../mkgmap/osmstyle/actions/AddLabelAction.java    |    12 +-
 .../mkgmap/osmstyle/actions/AddTagAction.java      |    19 +-
 .../mkgmap/osmstyle/actions/DeleteAction.java      |    12 +-
 .../osmstyle/actions/DeleteAllTagsAction.java      |     3 +-
 .../mkgmap/osmstyle/actions/EchoAction.java        |     3 +-
 .../mkgmap/osmstyle/actions/EchoTagsAction.java    |     3 +-
 .../osmstyle/actions/HighwaySymbolFilter.java      |    11 +-
 .../mkgmap/osmstyle/actions/NameAction.java        |    15 +-
 .../mkgmap/osmstyle/actions/NotEqualFilter.java    |     7 +-
 .../mkgmap/osmstyle/actions/RenameAction.java      |    13 +-
 .../mkgmap/osmstyle/actions/SubAction.java         |     3 +-
 .../osmstyle/actions/SubstitutionFilter.java       |    14 +-
 .../mkgmap/osmstyle/actions/ValueBuilder.java      |    73 +-
 .../mkgmap/osmstyle/actions/ValueItem.java         |     7 +-
 .../parabola/mkgmap/osmstyle/eval/AbstractOp.java  |    22 +
 src/uk/me/parabola/mkgmap/osmstyle/eval/AndOp.java |    18 +
 .../me/parabola/mkgmap/osmstyle/eval/LinkedOp.java |    40 +-
 src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java    |     9 +
 src/uk/me/parabola/mkgmap/osmstyle/eval/OrOp.java  |    15 +
 .../mkgmap/osmstyle/function/AreaSizeFunction.java |     2 +-
 .../mkgmap/osmstyle/function/CachedFunction.java   |     9 +-
 .../mkgmap/osmstyle/function/GetTagFunction.java   |     6 +-
 .../mkgmap/osmstyle/function/IsClosedFunction.java |     3 +-
 .../osmstyle/housenumber/HousenumberGenerator.java |    81 +-
 .../osmstyle/housenumber/HousenumberMatch.java     |    14 +-
 .../mkgmap/reader/MapperBasedMapDataSource.java    |     2 +-
 src/uk/me/parabola/mkgmap/reader/dem/DEM.java      |    20 +-
 .../mkgmap/reader/osm/CoastlineFileLoader.java     |     2 +-
 src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java |    27 +-
 src/uk/me/parabola/mkgmap/reader/osm/Element.java  |   116 +-
 .../parabola/mkgmap/reader/osm/ElementSaver.java   |    26 +-
 .../mkgmap/reader/osm/FakeIdGenerator.java         |     6 +-
 src/uk/me/parabola/mkgmap/reader/osm/GType.java    |    57 +-
 .../parabola/mkgmap/reader/osm/HighwayHooks.java   |   196 +-
 .../mkgmap/reader/osm/LinkDestinationHook.java     |    71 +-
 .../parabola/mkgmap/reader/osm/LocationHook.java   |     4 +-
 .../mkgmap/reader/osm/MultiPolygonRelation.java    |   397 +-
 .../me/parabola/mkgmap/reader/osm/OsmHandler.java  |     2 +-
 .../mkgmap/reader/osm/OsmMapDataSource.java        |     2 +-
 .../mkgmap/reader/osm/POIGeneratorHook.java        |    29 +-
 .../mkgmap/reader/osm/RelationStyleHook.java       |    19 +-
 .../mkgmap/reader/osm/RestrictionRelation.java     |   805 +-
 .../me/parabola/mkgmap/reader/osm/RoutingHook.java |     7 +
 src/uk/me/parabola/mkgmap/reader/osm/Rule.java     |    14 +
 .../parabola/mkgmap/reader/osm/SeaGenerator.java   |   150 +-
 .../mkgmap/reader/osm/SeaPolygonRelation.java      |     1 +
 src/uk/me/parabola/mkgmap/reader/osm/TagDict.java  |    96 +
 src/uk/me/parabola/mkgmap/reader/osm/Tags.java     |   123 +-
 src/uk/me/parabola/mkgmap/reader/osm/Way.java      |    77 +-
 .../reader/osm/boundary/BoundaryConverter.java     |     4 +-
 .../mkgmap/reader/osm/boundary/BoundaryDiff.java   |     5 +-
 .../reader/osm/boundary/BoundaryElement.java       |     2 +-
 .../reader/osm/boundary/BoundaryElementSaver.java  |    12 +-
 .../reader/osm/boundary/BoundaryFile2Gpx.java      |     2 +-
 .../osm/boundary/BoundaryLocationPreparer.java     |     8 +-
 .../reader/osm/boundary/BoundaryPreprocessor.java  |     2 +-
 .../reader/osm/boundary/BoundaryQuadTree.java      |    10 +-
 .../reader/osm/boundary/BoundaryRelation.java      |    26 +-
 .../mkgmap/reader/osm/boundary/BoundarySaver.java  |    51 +-
 .../mkgmap/reader/osm/boundary/BoundaryUtil.java   |    57 +-
 .../reader/overview/OverviewMapDataSource.java     |    11 +-
 .../mkgmap/reader/polish/PolishMapDataSource.java  |    56 +-
 .../reader/polish/PolishTurnRestriction.java       |    23 +-
 .../mkgmap/reader/polish/RestrictionHelper.java    |    30 +-
 .../parabola/mkgmap/reader/polish/RoadHelper.java  |    49 +-
 src/uk/me/parabola/mkgmap/scan/TokenScanner.java   |    15 +-
 .../mkgmap/sea/optional/PrecompSeaGenerator.java   |    14 +-
 .../mkgmap/sea/optional/PrecompSeaMerger.java      |    26 +-
 .../mkgmap/sea/optional/PrecompSeaSaver.java       |    28 +-
 src/uk/me/parabola/mkgmap/srt/SrtTextReader.java   |   234 +-
 src/uk/me/parabola/util/ElementQuadTreeNode.java   |     5 +-
 src/uk/me/parabola/util/GpxCreator.java            |   131 +-
 src/uk/me/parabola/util/Java2DConverter.java       |   240 +-
 src/uk/me/parabola/util/MultiHashMap.java          |    12 +-
 src/uk/me/parabola/util/MultiIdentityHashMap.java  |    12 +-
 src/uk/me/parabola/util/QuadTree.java              |    34 +-
 src/uk/me/parabola/util/QuadTreeNode.java          |     5 +-
 test/func/SimpleTest.java                          |    20 +-
 test/func/StructureTest.java                       |     2 +-
 test/func/files/GmapsuppTest.java                  |    28 +-
 test/func/files/TdbTest.java                       |     4 +-
 test/func/lib/TestDataSource.java                  |     2 +-
 test/func/lib/TestUtils.java                       |     2 +-
 test/func/route/SimpleRouteTest.java               |    55 +-
 test/func/sources/TestSourceTest.java              |     4 +-
 test/main/NumberRangeTest.java                     |     1 -
 test/main/SortTest.java                            |   315 +
 test/resources/rules/quoted_var.test               |    14 +
 test/uk/me/parabola/imgfmt/UtilsTest.java          |    56 +
 test/uk/me/parabola/imgfmt/app/CoordTest.java      |    74 +
 .../imgfmt/app/labelenc/Format6EncoderTest.java    |    46 +
 .../parabola/imgfmt/app/labelenc/LabelEncTest.java |    43 +
 .../me/parabola/imgfmt/app/srt/SortExpandTest.java |    16 +-
 test/uk/me/parabola/imgfmt/app/srt/SortTest.java   |   131 +-
 .../parabola/imgfmt/app/srt/SrtCollatorTest.java   |   147 +
 .../imgfmt/app/srt/UnicodeCollatorTest.java        |    47 +
 .../me/parabola/imgfmt/app/srt/UnicodeKeyTest.java |    72 +
 .../mkgmap/filters/ShapeMergeFilterTest.java       |   385 +
 .../uk/me/parabola/mkgmap/general/MapLineTest.java |    51 +
 .../parabola/mkgmap/general/PointInShapeTest.java  |   286 -
 .../mkgmap/osmstyle/RuleFileReaderTest.java        |   112 +-
 .../me/parabola/mkgmap/osmstyle/StyleImplTest.java |     6 +-
 .../mkgmap/osmstyle/StyledConverterTest.java       |     8 +-
 .../osmstyle/actions/AddAccessActionTest.java      |    16 +-
 .../mkgmap/osmstyle/actions/ValueBuilderTest.java  |   193 +
 .../me/parabola/mkgmap/reader/osm/ElementTest.java |    28 +-
 .../mkgmap/reader/osm/RestrictionRelationTest.java |   230 +
 .../me/parabola/mkgmap/scan/TokenScannerTest.java  |    60 +
 .../me/parabola/mkgmap/srt/SrtTextReaderTest.java  |    28 +-
 .../me/parabola/mkgmap/typ/TypTextReaderTest.java  |     7 +-
 277 files changed, 25671 insertions(+), 6217 deletions(-)

diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
index c3eb785..d96c3e2 100644
--- a/.idea/codeStyleSettings.xml
+++ b/.idea/codeStyleSettings.xml
@@ -75,7 +75,6 @@
             <package name="" withSubpackages="true" static="true" />
           </value>
         </option>
-        <option name="RIGHT_MARGIN" value="100" />
         <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
         <option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
         <option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
index 7e2edc5..ee1cdbd 100644
--- a/.idea/copyright/profiles_settings.xml
+++ b/.idea/copyright/profiles_settings.xml
@@ -1,5 +1,3 @@
 <component name="CopyrightManager">
-  <settings default="gpl">
-    <module2copyright />
-  </settings>
+  <settings default="gpl" />
 </component>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Mapping.xml b/.idea/inspectionProfiles/Mapping.xml
index 3de260f..c999530 100644
--- a/.idea/inspectionProfiles/Mapping.xml
+++ b/.idea/inspectionProfiles/Mapping.xml
@@ -5,6 +5,7 @@
     <inspection_tool class="AbsoluteAlignmentInUserInterface" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AbstractBeanReferencesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AbstractClassExtendsConcreteClass" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AbstractClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AbstractClassNeverImplemented" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AbstractClassWithOnlyOneDirectInheritor" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AbstractClassWithoutAbstractMethods" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -24,13 +25,16 @@
     <inspection_tool class="AmbiguousMethodCall" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidDomInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidElementNotAllowed" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintAaptCrash" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAdapterViewChildren" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintAllowBackup" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAlwaysShowAction" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintAssert" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintBackButton" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintButtonCase" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintButtonOrder" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintButtonStyle" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintByteOrderMark" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintCommitPrefEdits" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintContentDescription" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintCutPasteId" enabled="false" level="WARNING" enabled_by_default="true" />
@@ -49,7 +53,14 @@
     <inspection_tool class="AndroidLintExportedService" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintExtraText" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintExtraTranslation" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintGifUsage" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintGifUsage" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleCompatible" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleDependency" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleDynamicVersion" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintGradleGetter" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleIdeError" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleOverrides" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradlePath" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGrantAllUris" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintGridLayout" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintHardcodedDebugMode" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -60,7 +71,7 @@
     <inspection_tool class="AndroidLintIconDuplicates" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintIconDuplicatesConfig" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintIconExpectedSize" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintIconExtension" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintIconExtension" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintIconLauncherShape" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintIconLocation" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintIconMissingDensityFolder" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -69,9 +80,11 @@
     <inspection_tool class="AndroidLintIconXmlAndPng" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintIllegalResourceRef" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInOrMmUsage" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintIncludeLayoutParam" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInconsistentArrays" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintInconsistentLayout" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInefficientWeight" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintInflateParams" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInlinedApi" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInnerclassSeparator" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInvalidId" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -94,6 +107,7 @@
     <inspection_tool class="AndroidLintNestedScrolling" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintNestedWeights" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintNewApi" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintNfcTechWhitespace" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintNotSibling" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintObsoleteLayoutParam" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintOldTargetApi" enabled="false" level="WARNING" enabled_by_default="true" />
@@ -102,18 +116,22 @@
     <inspection_tool class="AndroidLintOverride" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintPackagedPrivateKey" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintPrivateResource" enabled="false" level="ERROR" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintProguard" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintProguard" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintProguardSplit" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintProguardSplitConfig" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintPropertyEscape" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintProtectedPermissions" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintPxUsage" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintReferenceType" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRegistered" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRequiredSize" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintResAuto" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintResourceAsColor" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintResourceCycle" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRtlCompat" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintRtlEnabled" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintRtlHardcoded" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintRtlSymmetry" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintScrollViewCount" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintScrollViewSize" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintSdCardPath" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -126,9 +144,9 @@
     <inspection_tool class="AndroidLintSparseArray" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintStateListReachable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintStopShip" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintStringFormatCount" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintStringFormatInvalid" enabled="false" level="ERROR" enabled_by_default="false" />
-    <inspection_tool class="AndroidLintStringFormatMatches" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintStringFormatCount" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintStringFormatInvalid" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintStringFormatMatches" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintStyleCycle" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintSuspicious0dp" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSuspiciousImport" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -146,6 +164,7 @@
     <inspection_tool class="AndroidLintUnknownId" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUnknownIdInLayout" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUnlocalizedSms" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintUnusedAttribute" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintUnusedIds" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUnusedQuantity" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintUnusedResources" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -156,6 +175,8 @@
     <inspection_tool class="AndroidLintUselessLeaf" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUselessParent" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUsesMinSdkAttributes" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintViewHolder" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintWebViewLayout" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWorldReadableFiles" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWorldWriteableFiles" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintWrongCase" enabled="false" level="WARNING" enabled_by_default="true" />
@@ -491,6 +512,7 @@
       <option name="m_limit" value="5" />
     </inspection_tool>
     <inspection_tool class="ContextComponentScanInconsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="ContextJavaBeanUnresolvedMethodsInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ContinueOrBreakFromFinallyBlock" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ContinueOrBreakFromFinallyBlockJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ContinueStatement" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -498,9 +520,10 @@
     <inspection_tool class="ContinueStatementWithLabel" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ContinueStatementWithLabelJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ControlFlowStatementWithoutBraces" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="Convert2Diamond" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Convert2Diamond" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="Convert2Lambda" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="Convert2MethodRef" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Convert2streamapi" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="ConvertAnnotations" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ConvertJavadoc" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ConvertOldAnnotations" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -509,6 +532,7 @@
     <inspection_tool class="CriteriaApiResolveInspection" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="CssConvertColorToHexInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="CssConvertColorToRgbInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssFloatPxLength" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidElement" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -527,6 +551,7 @@
     <inspection_tool class="CssOptimizeSimilarProperties" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="CssOptimizeSimilarPropertiesInspection" enabled="true" level="INFO" enabled_by_default="true" />
     <inspection_tool class="CssOverwrittenProperties" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="CssRedundantUnit" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssRgbFunction" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssRgbFunctionInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssShorthandPropertyValue" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -621,12 +646,14 @@
     <inspection_tool class="DuplicateThrows" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DuplicatedBeanNamesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="DuplicatedBlockNamesInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="Duplicates" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DynamicRegexReplaceableByCompiledPattern" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DynamicallyGeneratedCodeJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ELDeferredExpressionsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ELMethodSignatureInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ELSpecValidationInJSP" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ELValidationInJSP" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="ES6Validation" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="EjbClassBasicInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="EjbClassWarningsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="EjbDomInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -1094,6 +1121,7 @@
     <inspection_tool class="ImplicitCallToSuper" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_ignoreForObjectSubclasses" value="false" />
     </inspection_tool>
+    <inspection_tool class="ImplicitDefaultCharsetUsage" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ImplicitNumericConversion" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreWideningConversions" value="false" />
       <option name="ignoreCharConversions" value="false" />
@@ -1117,7 +1145,7 @@
     <inspection_tool class="IncorrectOnMessageMethodsInspection" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="IncrementDecrementResultUsedJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="IncrementDecrementUsedAsExpression" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="IndexOfReplaceableByContains" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="IndexOfReplaceableByContains" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="IndexZeroUsage" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="InfiniteLoopJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="InfiniteLoopStatement" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -1173,6 +1201,7 @@
     </inspection_tool>
     <inspection_tool class="IntentionDescriptionNotFoundInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="InterceptionAnnotationWithoutRuntimeRetention" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="InterfaceNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_regex" value="[A-Z][A-Za-z\d]*" />
       <option name="m_minLength" value="8" />
@@ -1342,6 +1371,7 @@
     <inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="LabeledStatement" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="LabeledStatementJS" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="LambdaParameterHidingMemberVariable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="LanguageMismatch" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="CHECK_NON_ANNOTATED_REFERENCES" value="true" />
     </inspection_tool>
@@ -1364,8 +1394,6 @@
     <inspection_tool class="LocalCanBeFinal" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="REPORT_VARIABLES" value="true" />
       <option name="REPORT_PARAMETERS" value="true" />
-      <option name="REPORT_CATCH_PARAMETERS" value="true" />
-      <option name="REPORT_FOREACH_PARAMETERS" value="true" />
     </inspection_tool>
     <inspection_tool class="LocalVariableHidingMemberVariable" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_ignoreInvisibleFields" value="true" />
@@ -1419,7 +1447,6 @@
     <inspection_tool class="MethodCallInLoopCondition" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="MethodCanBeVariableArityMethod" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreByteAndShortArrayParameters" value="true" />
-      <option name="ignoreOverridingMethods" value="false" />
     </inspection_tool>
     <inspection_tool class="MethodCount" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_limit" value="20" />
@@ -1807,6 +1834,7 @@
     <inspection_tool class="PyAbstractClassInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PyArgumentEqualDefaultInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="PyArgumentListInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="PyAssignmentToLoopOrWithParameterInspection" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="PyAttributeOutsideInitInspection" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PyBroadExceptionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -1928,7 +1956,9 @@
     <inspection_tool class="RecordStoreResource" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="RedundantArrayCreation" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="RedundantCast" enabled="true" level="WARNING" enabled_by_default="true" />
-    <inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="onlyWarnOnNull" value="true" />
+    </inspection_tool>
     <inspection_tool class="RedundantImplements" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreSerializable" value="false" />
       <option name="ignoreCloneable" value="false" />
@@ -1963,6 +1993,7 @@
     </inspection_tool>
     <inspection_tool class="RequiredBeanTypeInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="ReservedWordUsedAsNameJS" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="ResourceType" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="RestRoleInspection" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoredRoles">
         <value>
@@ -2045,6 +2076,7 @@
     <inspection_tool class="SetReplaceableByEnumSet" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SetupCallsSuperSetup" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SetupIsPublicVoidNoArg" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SharedThreadLocalRandom" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ShiftOutOfRange" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ShiftOutOfRangeJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SignalWithoutCorrespondingAwait" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2073,6 +2105,7 @@
     </inspection_tool>
     <inspection_tool class="SpringAopErrorsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringAopWarningsInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="SpringBeanAttributesInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringBeanAutowiringInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringBeanConstructorArgInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringBeanDepedencyCheckInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -2080,18 +2113,24 @@
     <inspection_tool class="SpringBeanLookupMethodInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringBeanNameConventionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringContextConfigurationInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="SpringElInspection" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="SpringFacetCodeInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringFacetInspection" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="checkTestFiles" value="false" />
     </inspection_tool>
+    <inspection_tool class="SpringFacetProgrammaticInspection" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringFactoryMethodInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="SpringHandlersSchemasHighlighting" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringIncorrectResourceTypeInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringInjectionValueConsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringInjectionValueStyleInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringIntegrationModelErrorInspection" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="SpringIntegrationModelInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SpringJavaAutowiredMembersInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringJavaAutowiringInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringJavaConfigExternalBeansErrorInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringJavaConfigInconsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="SpringMVCInitBinder" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringMVCViewInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringMessageDispatcherWebXmlInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringModelInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -2106,6 +2145,7 @@
     <inspection_tool class="SpringScopesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringSecurityElementsInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="SpringSecurityFiltersConfiguredInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SpringStaticMembersAutowiringInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringTransactionalComponentInspection" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="SpringWebServiceAnnotationsInconsistencyInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringWebServicesConfigurationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -2122,6 +2162,7 @@
     <inspection_tool class="SqlIdentifierInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlInsertValuesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlNoDataSourceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SqlNullComparisonInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SqlPostgresqlSelectFromProcedureInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlResolveInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlShouldBeInGroupByInspection" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2162,7 +2203,7 @@
     <inspection_tool class="StringBufferField" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="StringBufferMustHaveInitialCapacity" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="StringBufferReplaceableByString" enabled="true" level="WARNING" enabled_by_default="true" />
-    <inspection_tool class="StringBufferReplaceableByStringBuilder" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="StringBufferReplaceableByStringBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="StringCompareTo" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="StringConcatenation" enabled="false" level="WARNING" enabled_by_default="false">
@@ -2318,7 +2359,7 @@
     <inspection_tool class="TrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TrivialIfJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TrivialStringConcatenation" enabled="false" level="WARNING" enabled_by_default="false" />
-    <inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="TryFinallyCanBeTryWithResources" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TryWithIdenticalCatches" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TypeCustomizer" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TypeMayBeWeakened" enabled="false" level="WARNING" enabled_by_default="false">
@@ -2392,7 +2433,7 @@
     <inspection_tool class="UnnecessaryFinalOnParameter" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="onlyWarnOnAbstractMethods" value="false" />
     </inspection_tool>
-    <inspection_tool class="UnnecessaryFullyQualifiedName" enabled="true" level="WARNING" enabled_by_default="true">
+    <inspection_tool class="UnnecessaryFullyQualifiedName" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_ignoreJavadoc" value="false" />
     </inspection_tool>
     <inspection_tool class="UnnecessaryInheritDoc" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2496,6 +2537,7 @@
     <inspection_tool class="UseOfProcessBuilder" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfPropertiesAsHashtable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfSunClasses" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UseVirtualFileEquals" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="UtilSchemaInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="UtilityClass" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignorableAnnotations">
diff --git a/.idea/runConfigurations/mkgmap.xml b/.idea/runConfigurations/mkgmap.xml
index 5410003..f264625 100644
--- a/.idea/runConfigurations/mkgmap.xml
+++ b/.idea/runConfigurations/mkgmap.xml
@@ -3,7 +3,7 @@
     <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
     <option name="MAIN_CLASS_NAME" value="uk.me.parabola.mkgmap.main.Main" />
     <option name="VM_PARAMETERS" value="-ea -Xmx1000m" />
-    <option name="PROGRAM_PARAMETERS" value="--code-page=1251 freizeit.txt" />
+    <option name="PROGRAM_PARAMETERS" value="test.osm" />
     <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
     <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
     <option name="ALTERNATIVE_JRE_PATH" value="" />
diff --git a/doc/options.txt b/doc/options.txt
index b1336fe..7aeeb73 100644
--- a/doc/options.txt
+++ b/doc/options.txt
@@ -378,6 +378,10 @@ number of legitimate roads that are flagged as flare road
 problems. Default value is 0 (disabled) because it's not a
 completely reliable heuristic.
 
+;--ignore-maxspeeds
+:	Now ignored, former usage: 	
+When reading OSM files, ignore any "maxspeed" tags.
+
 ;--ignore-builtin-relations
 : 	When reading OSM files, skip the built-in processing of
 relations. This speeds up the processing non-routable map
@@ -393,15 +397,17 @@ With this option selected generate-sea sometimes works better,
 but routing across tiles will not work.
 
 ;--preserve-element-order
-: 	Process the map elements (nodes, ways, relations) in the order
+:  Process the map elements (nodes, ways, relations) in the order
 in which they appear in the OSM input. Without this option,
 the order in which the elements are processed is not defined.
 
 ;--remove-short-arcs[=MinLength]
-: 	Merge nodes to remove short arcs that can cause routing
+:  now ignored, former explanation:
+Merge nodes to remove short arcs that can cause routing
 problems. If MinLength is specified (in metres), arcs shorter
 than that length will be removed. If a length is not
 specified, only zero-length arcs will be removed.
+ 
 
 ;--adjust-turn-headings[=BITMASK]
 : 	Where possible, ensure that turns off to side roads change
@@ -556,7 +562,8 @@ the flood blocker
 (NSIS) to create a Windows Mapsource Installer.
 
 ;--make-all-cycleways
-: 	Turn on all of the options that make cycleways.
+: 	Deprecated, use --make-opposite-cycleways instead. Former meaning:
+    Turn on all of the options that make cycleways.
 
 ;--make-opposite-cycleways
 : 	Some oneway streets allow bicycle traffic in the reverse
@@ -564,23 +571,30 @@ direction and this option makes a way with the same points as
 the original that allows bicycle traffic (in both directions).
 
 ;--make-cycleways
-: 	Some streets have a separate cycleway track/lane just for
+:   Now ignored, former meaning:   	
+    Some streets have a separate cycleway track/lane just for
 bicycle traffic and this option makes a way with the same
 points as the original that allows bicycle traffic. Also,
 bicycle traffic is prohibited from using the original way
 (unless that way's bicycle access has been defined).
 
 ;--link-pois-to-ways
-:	This option copies some specific attributes of a POI to a 
-small part of the way the POI is located on. This can be used
+:This option may copy some specific attributes of a POI 
+to a small part of the way the POI is located on. This can be used
 to let barriers block a way or to lower the calculated speed
 around traffic signals.
-POIs with the tags highway=* (e.g. highway=traffic_signals)  
+POIs with the tags highway=* (e.g. highway=traffic_signals)
 or barrier=* (e.g. barrier=cycle_barrier) are supported.
 The style developer must add at least one of the access tags
-(mkgmap:foot, mkgmap:car etc.), mkgmap:road-speed and/or 
-mkgmap:road-class to the POI. These tags are copied to a small
-part of the way around the POI. 
+(mkgmap:foot, mkgmap:car etc.), mkgmap:road-speed and/or
+mkgmap:road-class to the POI.
+The access tags are ignored if they have no effect for the way,
+else a route restriction is added at the POI so that only
+allowed vehicles are routed through it.
+The tags mkgmap:road-speed and/or mkgmap:road-class are
+applied to a small part of the way around the POI, typically
+to the next junction or a length of ~25 m. The tags
+are ignored for pedestrian-only ways.
 
 ;--process-destination
 : 	Splits all motorway_link and trunk_link ways tagged with 
diff --git a/doc/resources/asciidoc/local-docbook45.conf b/doc/resources/asciidoc/local-docbook45.conf
index 661e605..1a5661f 100644
--- a/doc/resources/asciidoc/local-docbook45.conf
+++ b/doc/resources/asciidoc/local-docbook45.conf
@@ -1,3 +1,29 @@
-
 [+docinfo]
 {pubdate#}<pubdate>{pubdate}</pubdate>
+
+
+ifdef::basebackend-docbook[]
+ifeval::["{source-highlighter}"=="pygments"]
+[source-filter-style]
+source-style=template="source-highlight-block",presubs=(),postsubs=("callouts",),posattrs=("style","language","src_numbered","src_tab"),filter="pygmentize -l {language} -f xslfo {pygments-style?-Ostyle={pygments-style}} {args=}"
+
+#########################
+# Source paragraph styles
+#########################
+[paradef-default]
+template::[source-filter-style]
+
+[paradef-literal]
+template::[source-filter-style]
+
+#########################
+# Source block styles
+#########################
+[blockdef-open]
+template::[source-filter-style]
+
+[blockdef-listing]
+template::[source-filter-style]
+
+endif::[]
+endif::basebackend-docbook[]
diff --git a/doc/resources/docbook-xsl/fo.xsl b/doc/resources/docbook-xsl/fo.xsl
index 4b2717e..b7e0f1f 100644
--- a/doc/resources/docbook-xsl/fo.xsl
+++ b/doc/resources/docbook-xsl/fo.xsl
@@ -135,11 +135,21 @@
 <xsl:attribute-set name="shade.verbatim.style">
   <xsl:attribute name="background-color">
     <xsl:choose>
-      <xsl:when test="self::programlisting|self::screen">#E0E0E0</xsl:when>
+      <xsl:when test="self::programlisting|self::screen">#f8f8f8</xsl:when>
       <xsl:otherwise>inherit</xsl:otherwise>
     </xsl:choose>
   </xsl:attribute>
 </xsl:attribute-set>
+<xsl:attribute-set name="verbatim.properties">
+  <xsl:attribute name="border-left-style">solid</xsl:attribute>
+  <xsl:attribute name="border-left-width">2pt</xsl:attribute>
+  <xsl:attribute name="border-left-color">#CC7777</xsl:attribute>
+  <xsl:attribute name="margin-left">1pt</xsl:attribute>
+  <xsl:attribute name="margin-right">0pt</xsl:attribute>
+  <xsl:attribute name="padding-left">4pt</xsl:attribute>
+  <xsl:attribute name="padding-top">2pt</xsl:attribute>
+  <xsl:attribute name="padding-bottom">2pt</xsl:attribute>
+</xsl:attribute-set>
 
 <xsl:attribute-set name="component.title.properties">
   <xsl:attribute name="font-size">16pt</xsl:attribute>
@@ -167,4 +177,14 @@
   <xsl:attribute name="keep-together.within-column">auto</xsl:attribute>
 </xsl:attribute-set>
 
+<!-- Give links a colour -->
+<xsl:attribute-set name="xref.properties">
+	<xsl:attribute name="color">#0000dd</xsl:attribute>
+</xsl:attribute-set>
+
+<xsl:template match="fo:inline|fo:block">
+	<xsl:copy-of select=".">
+	</xsl:copy-of>
+</xsl:template>
+
 </xsl:stylesheet>
diff --git a/doc/resources/make.param b/doc/resources/make.param
index d2523f6..6766772 100644
--- a/doc/resources/make.param
+++ b/doc/resources/make.param
@@ -1,4 +1,3 @@
-
 ASCIIDOC=asciidoc
 A2X=a2x
 
@@ -8,8 +7,13 @@ RES=../resources
 XSL=$(RES)/docbook-xsl/fo.xsl
 ICONSDIR=$(RES)/common-images/icons
 
-HTML_ATTRS=--conf-file=../resources/asciidoc/local-missing-blockdef.conf -a data-uri
+HTML_ATTRS=--conf-file=../resources/asciidoc/local-missing-blockdef.conf -a data-uri \
+	-a source-highlighter=pygments
+
 PDF_OPTS=--icons --icons-dir=$(ICONSDIR) \
-	--asciidoc-opts='--conf-file=../resources/asciidoc/local-docbook45.conf' \
-	-a pubdate="$$(date +'%d %B %Y')"
+	-a pubdate="$$(date +'%d %B %Y')" \
+	--asciidoc-opts='--conf-file=../resources/asciidoc/local-docbook45.conf'
 
+ifdef PDF_HIGHLIGHT
+PDF_OPTS += -a source-highlighter=pygments -a pygments-style=mkgmap 
+endif
diff --git a/doc/styles/Makefile b/doc/styles/Makefile
index c2d23be..a91a673 100644
--- a/doc/styles/Makefile
+++ b/doc/styles/Makefile
@@ -1,9 +1,9 @@
-
 include ../resources/make.param
 
 
-SOURCES= about.txt creating.txt design.txt files.txt rules.txt rules-filters.txt \
-	style-manual.txt $(XSL) 
+SOURCES= about.txt creating.txt design.txt files.txt rules.txt internal-tags.txt \
+		 rules-filters.txt style-manual.txt $(XSL) 
+
 STYLE_MAIN=style-manual.txt
 TARGET_PDF=style-manual.pdf
 
@@ -19,7 +19,7 @@ style-manual.html: $(SOURCES)
 	$(ASCIIDOC) $(HTML_ATTRS) -a icons -a iconsdir=$(ICONSDIR) -b html5 $(STYLE_MAIN)
 
 $(TARGET_PDF): $(SOURCES)
-	$(A2X) --xsl-file=$(XSL) --fop $(PDF_OPTS) $(STYLE_MAIN)
+	$(A2X) -L --xsl-file=$(XSL) --fop $(PDF_OPTS) $(STYLE_MAIN)
 
 style-manual.epub: $(SOURCES)
 	mkdir -p build
diff --git a/doc/styles/about.txt b/doc/styles/about.txt
index 136cc39..1a91858 100644
--- a/doc/styles/about.txt
+++ b/doc/styles/about.txt
@@ -2,7 +2,7 @@
 = About =
 == Licence ==
 This manual is released under the
-link:http://creativecommons.org/licenses/by-sa/2.0/[Creative Commons Attribution-ShareAlike 2.0 license].
+http://creativecommons.org/licenses/by-sa/2.0/[Creative Commons Attribution-ShareAlike 2.0 license].
 It makes use of some material that was added to the OSM Wiki which is
 release under the same licence.
 
diff --git a/doc/styles/creating.txt b/doc/styles/creating.txt
index 06d687c..cc0412f 100644
--- a/doc/styles/creating.txt
+++ b/doc/styles/creating.txt
@@ -1,3 +1,5 @@
+// Set default source language
+:language: mkgmap
 
 = Creating a style =
 
@@ -17,14 +19,14 @@ The `--check-styles` option verfies that your style uses type values which can p
 
 The following rules are verified:
 
-. If a type is ≥ 0x0100 (means it has more than one byte), the rightmost byte
+. If a type is >= 0x0100 (means it has more than one byte), the rightmost byte
 must be between 0x00 and 0x1f, so e.g. 0x011f is ok, 0x0120 is not.
-. If a type is ≥ 0x010000, it is an extended type, which can be used for points, lines, and polygons.
-. If the type is not extended, it must be ≥ 0x0100 for a point, ≤ 0x3f for a line, and ≤ 0x7f
-for a polygon. 
-. The polygon type 0x4a is reserved for the overview map. 
+. If a type is >= 0x010000, it is an extended type, which can be used for points, lines, and polygons.
+. If the type is not extended, it must be >= 0x0100 for a point, < 0x3f for a line, and < 0x7f for a polygon. 
+. The polygon type 0x4a is reserved for the overview map.
 . It is known that the usage of routable types for non-routable lines in resolution 24 can cause
 routing problems (e.g. address search doesn't work). The check will flag rules that assign a routable type for a line in resolution 24 without giving road_class or road_speed. A routable type is between 0x01 and 0x13 or one of: 0x1a, 0x1b, 0x16.
+. If road_class or road_speed is given in combination with a non-routable type, the rule is flagged.  
 
 [[style-packaging]]
 == Making a style package ==
@@ -79,6 +81,7 @@ file separated by lines that contain the file name in triple angled
 brackets.
 
 .Single file archive
+[source]
 -------
 <<<version>>>
 0
@@ -97,48 +100,16 @@ This file can be easily created in its entirity in a text editor, but you
 can also convert between the files-in-a-directory format and the
 single-file format using the following command:
 
+[source,sh]
 -----
- # (to be typed all on one line)
- java -cp mkgmap.jar uk.me.parabola.mkgmap.osmstyle.StyleImpl
-      mystyle > mystyle.style
+# (to be typed all on one line)
+java -cp mkgmap.jar uk.me.parabola.mkgmap.osmstyle.StyleImpl
+     mystyle > mystyle.style
 -----
 
 To convert back then supply the file as the argument, rather than the
 directory.
 
-////
-== Examples ==
-
-Examples of mkgmap conversion style rules.
-
-=== Alternatives ===
-Two or more osm tags resolve to the same element in the garmin map.
-----
-highway=footway | highway=path [0x16 level 1]
-----
-
-This is exactly the same as writing the two lines:
-----
-highway=footway [0x16 level 1]
-highway=path [0x16 level 1]
-----
-
-=== White space ===
-Space and new lines don't matter except to separate words that have to be
-separate.
-You can add as much space and as many newlines to the rule to make
-it look good.  Or remove them depending on taste.  For example the following two rules
-are exactly the same:
-----
-highway = primary 
-   | highway=primary_link
-      [
- 	0x2
-        level 3
-      ]
-
-highway=primary|highway=primary_link[0x2 level 3]
-----
 
 === The Garmin Map ===
 Each Garmin map may contain several separate maps which are prepared at different 'levels' of detail, the most appropriate of these is displayed depending on the zoom selected by the user. 
@@ -181,9 +152,11 @@ This means that the map will have three levels.  Level 0 in the map will corresp
 resolution 22 (between scales of 500m and 200m) and so on. The lowest level needs to include at least an object, therefore the default lowest level of 16 will create a broken map, if your osm input file has no information at zoom level 16 or lower included.
 
 ==== Watch out with levels when building topographical maps ====
+
 According to the principle that a map is never allowed to have an empty layer, if you have two input files for mkgmap, you have to specify --levels for each input file. This is especially important when one of the input files consists exclusively of contour lines. Take the following command as example on how to create such a map. (Attention the line wrap is only here for the wiki, this has to be one command in cmd.exe or terminal)
+[source,bat]
 -----
- java -jar mkgmap.jar --style-file=D:\path\to\mkgmap\resources\styles\style_name\ 
+java -jar mkgmap.jar --style-file=D:\path\to\mkgmap\resources\styles\style_name \
  --levels=0:24,1:22,2:20,3:18,4:16,5:14,6:12,7:10 data.osm 
  --levels=0:24,1:22,2:20 srtm.osm
 -----
@@ -194,4 +167,3 @@ There are 2 alternatives to circumvent having to assign different levels on comp
  b) Merge your osm files (either by script or in text editor (text editor may crash though on opening huge .osm files), and then use the lowest resulting level.
 Concluding the easiest is to include dummy objects at lowest level. (it should be thought about mkgmap doing this by default). The lower your lowest level the later the basemap will exchange your osm map.
 Your lowest level object is the defined by the object with the lowest level (as defined in your style) actually present in your osm input file.
-////
diff --git a/doc/styles/internal-tags.txt b/doc/styles/internal-tags.txt
new file mode 100644
index 0000000..d2abd11
--- /dev/null
+++ b/doc/styles/internal-tags.txt
@@ -0,0 +1,135 @@
+:language: mkgmap
+
+== mkgmap internal tags ==
+There are lots of tags prefixed with +mkgmap:+. Some of them need to be set in the
+style file to set specific attributes of the Garmin map elements, e.g.  
+access restrictions, labels, attributes required for address search etc.
+Others are added to the OSM elements by mkgmap so that they can be evaluted in
+the style files to change the processing. 
+ 
+=== Tags evaluated by mkgmap ===
+These tags need to be set within the style file to set specific attributes of 
+the Garmin map elements.
+
+[source]
+----
+  highway=* & (bicycle=no | bicycle=private) { set mkgmap:bicycle='no' }
+----
+This rule defines that the road cannot be used by bicycles.
+
+// see [[Mkgmap/help/Tags]]
+
+.Tags for routable roads
+[options="header"]
+|=========================================================
+| Attribute   | mkgmap tag     | Example | Notes
+| Labels      | +mkgmap:label:1+ + 
++mkgmap:label:2+ + 
++mkgmap:label:3+ + 
++mkgmap:label:4+ | Eastern Avenue +
+A112 | Usually only the first label is displayed. On some units the second label of roads is displayed as routing instruction. All labels are used for address search. 
+| Country     | +mkgmap:country+ | GBR | Three letter ISO code, e.g. for GBR United Kingdom
+| Region      | +mkgmap:region+  | London Borough of Waltham Forest | The regions name. Useful if there are multiple cities with the same name.
+| City        | +mkgmap:city+    | London |
+| Street      | +mkgmap:street+  | High Road Leyton | This value is used by house number search to match the +addr:street+ tag of an OSM element with house number to the corresponding road. It must be set so that house number search is working.
+| Zipcode     | +mkgmap:postal_code+ |  E10 5NA |
+| Access restrictions | +mkgmap:foot+ +
++mkgmap:bicycle+ +
++mkgmap:car+ +
++mkgmap:taxi+ +
++mkgmap:truck+ +
++mkgmap:bus+ +
++mkgmap:emergency+ +
++mkgmap:delivery+  | no | These tags are evaluated for routable lines (roads) only. By default access for a specific vehicle type is allowed. Only in case the value of the tag is _no_ access is blocked for the given type.  
+| Throughroute | +mkgmap:throughroute+   | no | If this tag is set to _no_ routing is allowed on this road only if the start or end point lies on the road.
+| Carpool lane | +mkgmap:carpool+   | yes | If this tag is set to _yes_ the road is marked to have a carpool lane. This does not seem to work on all units.
+| Toll road | +mkgmap:toll+   | yes | If this tag is set to _yes_ the road can be used only when paying a specific toll.
+| Unpaved | +mkgmap:unpaved+   | yes | If this tag is set to _yes_ the road is marked to be unpaved. Some units can avoid unpaved roads.
+| Ferry | +mkgmap:ferry+   | yes | If this tag is set to _yes_ the line is marked to be a ferry line. Some units can avoid ferry lines.
+| Road speed | +mkgmap:road-speed-class+   | 2 | A value between 0 and 7. Overrides the +road_speed+ definition in the element type definition if this tag is set.
+| Road speed modifier | +mkgmap:road-speed+   | +1 | Modifies the road speed class by the given value. In case the value is prefixed with + or - the road speed class is modified. In case the value does not start with + or - the road speed class value of the element type definition is overriden. 
+| Road speed limiters | +mkgmap:road-speed-min+ +
+ +mkgmap:road-speed-max+  | 5 | Defines the minimum/maximum road speed class. This can be used to limit the modification of the road speed class (+mkgmap:road-speed+). 
+| Road class | +mkgmap:road-class+   | -1 | Modifies the road class defined in the element type definition. In case the value is prefixed with + or - the road class is modified. In case the value does not start with + or - the road class value of the element type definition is overriden. 
+| Road class limiters | +mkgmap:road-class-min+ +
+ +mkgmap:road-class-max+  | 2 | Defines the minimum/maximum road class. This can be used to limit the modification of the road class (+mkgmap:road-class+). 
+|=========================================================
+
+.Tags that control the treatment of roads
+[options="header"]
+|=========================================================
+| Tag | Description     | Required mkgmap option
+| +mkgmap:way-has-pois+  | +true+ for ways that have at least one point with a tag +access=\*+, +barrier=\*+, or +highway=*+ | 'link-pois-to-ways'
+| +mkgmap:dead-end-check+  | Set to +false+ to disable the dead end check for a specific way | 'report-dead-ends'
+| +mkgmap:flare-check+  | Set to +true+ to force the flare check for a specific way, set to +false+ to disable it | 'check-roundabout-flares'
+| +mkgmap:dir-check+  | Set to +false+ to tell mkgmap to ignore the way when checking roundabouts for clockwise direction | 'check-roundabouts'
+| +mkgmap:no-dir-check+  | Set to +true+ to tell mkgmap to ignore the way when checking roundabouts for clockwise direction | 'check-roundabouts'
+    
+|=========================================================
+
+ 
+.POI address tags
+[options="header"]
+|=========================================================
+| Attribute   | mkgmap tag     | Example | Notes
+| Name        | +mkgmap:label:1+ +
++mkgmap:label:2+ +
++mkgmap:label:3+ +
++mkgmap:label:4+ | Pizza Express | Names of the POI
+| Country     | +mkgmap:country+ | GBR | Three letter ISO code, e.g. for GBR United Kingdom
+| Region      | +mkgmap:region+  | Nottinghamshire | The regions name. Useful if there are multiple cities with the same name.
+| City        | +mkgmap:city+    | Nottingham |
+| Street      | +mkgmap:street+  | King Street |
+| Housenumber | +mkgmap:housenumber+ | 20 |
+| Zipcode     | +mkgmap:postal_code+ | NG1 2AS |
+| Phone       | +mkgmap:phone+   | +44 115 999999 | Phone number in any format
+|=========================================================
+
+TIP: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3[Wikipedia] has a list of all ISO 3166-1 alpha 3 codes
+ 
+=== Tags added by mkgmap ===
+Some tags are added by mkgmap to indicate some property calculated by mkgmap.
+
+[source]
+----
+ mkgmap:admin_level2=* { add mkgmap:country='${mkgmap:admin_level2}' }
+---- 
+The tag +mkgmap:admin_level2+ is added to each OSM element if the 'bounds' option is set. In the rule above it 
+is used to assign the country location.
+
+.Tags added by mkgmap
+[options="header"]
+|=========================================================
+| Tag | Description     | Required mkgmap option
+| +mkgmap:admin_level2+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=2+ the element is located in | 'bounds'    
+| +mkgmap:admin_level3+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=3+ the element is located in | 'bounds'    
+| +mkgmap:admin_level4+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=4+ the element is located in | 'bounds'    
+| +mkgmap:admin_level5+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=5+ the element is located in | 'bounds'    
+| +mkgmap:admin_level6+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=6+ the element is located in | 'bounds'    
+| +mkgmap:admin_level7+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=7+ the element is located in | 'bounds'    
+| +mkgmap:admin_level8+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=8+ the element is located in | 'bounds'    
+| +mkgmap:admin_level9+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=9+ the element is located in | 'bounds'    
+| +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:area2poi+  | The value is +true+ if the POI is derived from a polygon | 'add-poi-to-areas'    
+| +mkgmap:line2poi+  | The value is +true+ if the POI is derived from a line | 'add-poi-to-lines'    
+| +mkgmap:line2poitype+  | The tag is set for each POI generated from a line. Possible values are: +start+, +end+, +mid+, +inner+. | 'add-poi-to-lines'    
+| +mkgmap:exit_hint+  | +true+ for the part on link roads that should contain information about the exit | 'process-exits'    
+| +mkgmap:exit_hint_name+  | The +name+ tag value of the links exit node | 'process-exits'    
+| +mkgmap:exit_hint_ref+  | The +ref+ tag value of the links exit node | 'process-exits'    
+| +mkgmap:exit_hint_exit_to+  | The +exit_to+ tag value of the links exit node | 'process-exits'    
+| +mkgmap:dest_hint+  | +true+ for the part on link roads that should contain destination information about the link | 'process-destination'    
+| +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
+|=========================================================
+
+.Other internal tags
+[options="header"]
+|=========================================================
+| Tag | Description     
+| +mkgmap:skipSizeFilter+  | If set to +true+ the line or polygon will pass the size filter, no matter what size it has    
+| +mkgmap:highest-resolution-only+  | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.    
+|=========================================================
+
+
diff --git a/doc/styles/main.txt b/doc/styles/main.txt
index 33ca269..1aed8c7 100644
--- a/doc/styles/main.txt
+++ b/doc/styles/main.txt
@@ -38,7 +38,7 @@ starting with a Polish format file, there is no style involved as the
 garmin types are already fully specified in the input file.
 
 For general information about the Open Street Map project see the
-link:http://wiki.openstreetmap.org[Open Street Map wiki].
+http://wiki.openstreetmap.org[Open Street Map wiki].
 
 
 :leveloffset: 1
diff --git a/doc/styles/rules-filters.txt b/doc/styles/rules-filters.txt
index 6703336..c46e9e9 100644
--- a/doc/styles/rules-filters.txt
+++ b/doc/styles/rules-filters.txt
@@ -28,11 +28,11 @@ regular expressions in the +from+ attribute.
 
 Example, if name ="Queen Street" 
 
-`${name\|subst:Queen=>}` returns " Street" 
+`${name\|subst:"Queen=>"}` returns " Street"
 
-`${name\|subst:Queen=>King}` returns "King Street" 
+`${name\|subst:"Queen=>King"}` returns "King Street"
 
-`${name\|subst:.*\s~>}` returns "Street" 
+`${name\|subst:".*\s~>"}` returns "Street"
 
 | part | `separator operator partnumber` |
 Split a value in parts and returns one or more part(s) of it. If +partnumber+ is negative, the part returned is counted from the end of the split
@@ -45,24 +45,24 @@ If the operator is `<` or `>` the correspondent number of parts before or after
 
 Example: if the value is "Aa#Bb#Cc#Dd#Ee"
 
-`${name\|part:#:1}`  returns Aa
+`${name\|part:"#:1"}`  returns Aa
 
-`${name\|part:#:-1}` returns Ee
+`${name\|part:"#:-1"}` returns Ee
 
-`${name\|part:#:2}`  returns Bb
+`${name\|part:"#:2"}`  returns Bb
 
-`${name\|part:#:-2}` returns Dd
+`${name\|part:"#:-2"}` returns Dd
 
-`${name\|part:#>1}`  returns Bb#Cc#Dd#Ee#
+`${name\|part:"#>1"}`  returns Bb#Cc#Dd#Ee#
 
-`${name\|part:#<5}`  returns Aa#Bb#Cc#Dd#
+`${name\|part:"#<5"}`  returns Aa#Bb#Cc#Dd#
 
-`${name\|part:#<-1}` returns Aa#Bb#Cc#Dd#
+`${name\|part:"#<-1"}` returns Aa#Bb#Cc#Dd#
 
 This can be especially useful for tags like ref, exit_to and destination or to switch words, 
 example if value is "word1 word2 ... wordN-1 wordN"
 
-`${name\|part: :-1}, ${name\|part: <-1}` returns "wordN, word1 word2 ... wordN-1 "
+`${name\|part:" :-1"}, ${name\|part:" <-1"}` returns "wordN, word1 word2 ... wordN-1 "
 
 | highway-symbol | `symbol:max-num:max-alpha` |
 Prepares the value as a highway reference such as "A21" "I-80" and so
@@ -71,7 +71,7 @@ A code is added to the front of the string so that a highway shield is
 displayed, spaces are removed and the text is truncated so as not to overflow the
 symbol.
 
-`${ref\|highway-symbol:box:4:8}`
+`${ref\|highway-symbol:"box:4:8"}`
 
 See below for a list of the +highway-symbol+ values.
 
@@ -85,7 +85,7 @@ This is the same as the +conv+ filter, except that it prepends a special
 separation character before the value which is intended for elevations.
 As with +conv+ the only supported conversion currently is from meters to feet.
 
-`${ele\|height:m=>ft}`
+`${ele\|height:"m=>ft"}`
 
 | not-equal | `tag` |
 Used to check for duplicate tags. If the value of this tag is equal to
@@ -128,9 +128,3 @@ The actual symbol will depend on the device that it is displayed on.
 | oval    | image:img/sym-oval.png[]          | Box for smaller roads
 |====
 
-
-////
-| prefix | symbol code |
-hello
-| `${ref\|prefix:box}`
-////
diff --git a/doc/styles/rules.txt b/doc/styles/rules.txt
index 2fcc460..0795106 100644
--- a/doc/styles/rules.txt
+++ b/doc/styles/rules.txt
@@ -1,3 +1,5 @@
+// Set default source language
+:language: mkgmap
 
 [[RULES]]
 = Style rules =
@@ -25,6 +27,7 @@ in square brackets `[...]`.
 
 Here is an example of a rule containing all three sections:
 
+[source]
 ----
 natural=cliff { name '${name} cliff' | 'cliff' } [0x10501 resolution 22]
 ----
@@ -39,6 +42,7 @@ lines and add extra spaces wherever you like if it helps to make them
 easier to read.
 
 .Example with lots of extra space and newlines
+[source]
 ----
 natural=cliff
 	{
@@ -53,9 +57,8 @@ natural=cliff
 ----
 
 .Example with all unneeded spaces removed
-----
-natural=cliff{name'${name} cliff'|"cliff"}[0x10501 resolution 22]
-----
+[source]
+  natural=cliff{name'${name} cliff'|"cliff"}[0x10501 resolution 22]
 
 === Tag and text values ===
 Tag names and vales are often single words consisting of letters and
@@ -68,6 +71,7 @@ double quotes (+"+).
 If your text contains a quote then you must use the other kind
 of quote around the value.
 
+[source]
 ----
 highway=primary
 "highway"="primary"  # quotes not needed, but do no harm
@@ -80,14 +84,14 @@ name="Ten O'Clock Tavern"  # Double quotes used because text contains single quo
 The most common test is that a particular OSM tag has a given value.
 So for example if we have
 
-----
+[source]
 highway=motorway
-----
 
 This means that we look up the highway tag in the OSM input file and if it exists and has the value
 'motorway' then this test has matched.
 
 You can also compare numeric quantities:
+[source]
 ----
 population > 10000
 lanes >= 2
@@ -98,9 +102,9 @@ Respectively, these mean: a population greater than ten thousand, a road with at
 and a population less than one million.
 
 You may also use regular expressions:
-----
+[source]
 ele ~ '\d*00'
-----
+
 This checks whether ele is a multiple of 100.
 
 === Allowed operations
@@ -133,8 +137,8 @@ As above, for less than or equal, greater than and greater than or equal.
 
 |tag ~ REGEX| This is true when the value of the tag matches the given
 regular expression.
-The link::http://docs.oracle.com/javase/1.4.2/docs/api/java/util/regex/Pattern.html[Java
-regular expression] syntax is recognised.
+The http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html[Java regular expression]
+syntax is recognised.
 For example `name ~ '.*[Ll]ane'` would match every name that ended in
 'Lane' or 'lane'.
 
@@ -154,6 +158,7 @@ For example, say you want to take roads that are tagged both as
 just +highway=unclassified+.  In this type of case, you might create two
 separate rules as follows:
 
+[source]
 ----
 highway=unclassified & lanes>2 [0x06]
 highway=unclassified [0x05] 
@@ -176,9 +181,8 @@ complex rules.
 
 You can also combine alternatives into the one rule using a logical or, represented
 with a pipe (|) symbol.  For example
-----
+[source]
 highway=footway | highway=path [0x07]
-----
 
 This means if the road has either the *highway=footway* tag or the
 *highway=path* tags (or both), then the condition matches and mkgmap would
@@ -189,9 +193,8 @@ to two separate rules internally when mkgmap runs.
 You are not limited to two tests for a given rule... you can combine and group tests
 in almost whatever way you like.
 So for a slightly forced example the following would be possible:
-----
+[source]
 place=town & (population > 1000000 | capital=true) | place=city
-----
 
 This would match if there was a +place+ tag which had the value +town+
 and either the population was over a million or it was tagged a capital,
@@ -208,24 +211,27 @@ Sometimes you may want to compare the values of two tags, rather than
 the value of one tag with a fixed value.
 Use a dollar sign to indicate that you want the tag value.
 
+[source]
 ----
 # If you had the following tags:
 # name=Fford-y-Mor
 # name:en=Terrace Road
 # name:cy=Fford-y-Mor
 
-name = $name:cy {...}  # this would match
-name = $name:en {...}  # and this would not
+name = $name:cy { }  # this would match
+name = $name:en { }  # and this would not
 ----
 
 This tests if the value of the +name+ tag is the same as the welsh name
 tag (+name:cy+) 
 
 It is worth noting that the normal case
+[source]
 ----
 highway=primary
 ----
 is exactly the same as
+[source]
 ----
 $highway=primary
 ----
@@ -277,9 +283,8 @@ some element ids are changed and some have a faked id > 4611686018427387904.
 |====
 
 The following rule matches for all service ways longer than 50m.
------
-highway=service & length()>50 
------
+[source]
+highway=service & length()>50
 
 [[Action_block]]
 == Action block ==
@@ -305,9 +310,8 @@ For example, motorways are one way by default so we need to add the
 device. But there are some stretches of motorway that are one-way and
 these will be tagged as +oneway=no+. If we used +set+ then that tagging
 would be lost, so we use +add+.
-----
+[source]
  highway=motorway { add oneway=yes }
-----
 
 The other use is in in relations with the 'apply' command.
 
@@ -315,9 +319,8 @@ All the same you can set any tag you want, it might be useful so you can match
 on it elsewhere in the rules.
 
 You can also use substitutions.
-----
-{add name='${ele}'; add name='${ref}';}
-----
+[source]
+  {add name='${ele}'; add name='${ref}';}
 
 These two commands would set the 'name' tag to the value of the 'ele' tag if it exists, or to the value of the 'ref' tag if that exists.
 
@@ -325,9 +328,8 @@ You can also give a list of alternative expressions separated with a
 vertical bar in the same way as on the name command.  The first
 one that is fully defined will be used.
 
-----
-{add key123 = '${name:en}' | '${name}'; } 
-----
+[source]
+  {add key123 = '${name:en}' | '${name}'; }
 
 If 'key123' is not set it will set 'key123' to the value of the 'name:en' tag if it exists and to the 'name' tag if not.
 
@@ -337,15 +339,13 @@ tag, replacing any existing value it had.
 
 === delete ===
 The delete command deletes a tag.
-----
+[source]
  { delete key123 }
-----
 
 === deletealltags ===
 The deletealltags command deletes all tags. Usually this stops all further processing of the element.
-----
+[source]
  { deletealltags }
-----
 
 === addlabel ===
 Each item in the Garmin map can have up to four labels. Usually only the first label is displayed.
@@ -355,9 +355,8 @@ by setting the tags +mkgmap:label:n+ where n is a number between 1 and 4.
 
 The addlabel command assigns the first empty +mkgmap:label:n+ tag with the given value.
 
-----
+[source]
  {addlabel '${name} (${ref})' | '${ref}' | '${name}'}
-----
 
 If both the +name+ and +ref+ tags are are set, then the first alternative would be
 completed and the resulting label might be _Main St (A1)_.
@@ -376,6 +375,7 @@ you view the map.
 This sets the first label of the element but only if it is not already set.
 This is a helper action. The same effect can be produced with different notations
 as it is shown in the following example where all three lines have the same effect.
+[source]
 ----
  {name '${name} (${ref})' | '${ref}' | '${name}'}
  {add mkgmap:label:1='${name} (${ref})' | '${ref}' | '${name}'}
@@ -386,10 +386,12 @@ as it is shown in the following example where all three lines have the same effe
 The "addaccess" action sets all unset mkgmap access restriction tags to the given value.
 This is a helper action to avoid long action blocks.
 
+[source]
 ----
 { addaccess 'no' }
 ----
 is the same as
+[source]
 ----
 { 
   add mkgmap:foot=no; 
@@ -407,10 +409,12 @@ is the same as
 The "setaccess" action sets all mkgmap access restriction tags to the given value no matter if
 they already have a value or not. This is a helper action to avoid long action blocks.
 
+[source]
 ----
 { setaccess 'no' }
 ----
 is the same as
+[source]
 ----
 { 
   set mkgmap:foot=no; 
@@ -431,6 +435,7 @@ relation have any special tags to indicate that they form part of that
 bus route, and you want to be able to tell from looking at the map which
 buses go where.  You can write a rule in the *relations file* such as:
 
+[source]
 ----
 type=route & route=bus {
 	apply {
@@ -451,6 +456,7 @@ The substitution `$(route_ref)` (with parenthesis, rather than curly
 brackets) can be used for accessing the value of the tag on
 the actually processed *member* of the relation, e.g.
 
+[source]
 ----
 type=route & route=bus {
 	apply {
@@ -463,6 +469,7 @@ type=route & route=bus {
 The "apply" action can be limited to members with a special role by adding
 _role=rolevalue_ after the _apply_ keyword.
 
+[source]
 ----
 type=route & route=bus {
 	apply role=forward {
@@ -481,16 +488,14 @@ one way streets.
 === echo ===
 The echo action prints the element id plus a text to standard error. This can be
 used for quality checks and debugging purposes.
-----
-highway=motorway_link & oneway!=* { echo "motorway_link without oneway tag" }
-----
+[source]
+  highway=motorway_link & oneway!=* { echo "motorway_link without oneway tag" }
 
 === echotags ===
 The echotags action prints the element id, all tags and values plus a text to standard error. 
 This can be used for style debugging purposes.
-----
-highway=living_street { echotags "This is a living_street" }
-----
+[source]
+  highway=living_street { echotags "This is a living_street" }
 
 [[VARS]]
 == Variables
@@ -501,9 +506,8 @@ surrounded by curly braces like so `${name}`.
 The most obvious use for variables is in setting the name of the element.
 You are able to use any combination of tags to make the name from.
 Here we name a fuel station by its brand and the name in brackets following.
-----
-amenity=fuel { name '${brand} (${operator})' } [ 0x2f01 ]
-----
+[source]
+  amenity=fuel { name '${brand} (${operator})' } [ 0x2f01 ]
 
 If the operator tag was not set, then the name would not be set because *all*
 substitutions in a string must exist for the result to be valid.
@@ -511,6 +515,7 @@ This is why the "name" command takes a list of possibilities, if operator
 was simply replaced with a blank, then you would have an empty pair of brackets.
 So you would fix the previous rule by adding another name option.
 
+[source]
 ----
  amenity=fuel
     { name '${brand} (${operator})' | '${brand}' }
@@ -530,142 +535,27 @@ by the filter name, then a colon ":" and an argument. If there is more than
 one argument required then they are usually separated by colons too, but
 that is not a rule.
 
+[source]
  ${tagname|filter:arg1:arg2}
 
 You can apply as many filter expressions to a substitution as you like.
 
+[source]
  ${tagname|filter1:arg|filter2:arg}
 
-include::rules-filters.txt[]
+If the argument contains spaces or symbols it should be quoted.
 
-== mkgmap internal tags ==
-There are lots of tags prefixed with +mkgmap:+. Some of them need to be set in the
-style file to set specific attributes of the Garmin map elements, e.g.  
-access restrictions, labels, attributes required for address search etc.
-Others are added to the OSM elements by mkgmap so that they can be evaluted in
-the style files to change the processing. 
- 
-=== Tags evaluated by mkgmap ===
-These tags need to be set within the style file to set specific attributes of 
-the Garmin map elements.
+[source]
+ ${tagname|filter1:"arg with spaces"}
 
-----
-  highway=* & (bicycle=no | bicycle=private) { set mkgmap:bicycle='no' }
-----
-This rule defines that the road cannot be used by bicycles.
-
-// see [[Mkgmap/help/Tags]]
-
-.Tags for routable roads
-[options="header"]
-|=========================================================
-| Attribute   | mkgmap tag     | Example | Notes
-| Labels      | +mkgmap:label:1+ + 
-+mkgmap:label:2+ + 
-+mkgmap:label:3+ + 
-+mkgmap:label:4+ | Eastern Avenue +
-A112 | Usually only the first label is displayed. On some units the second label of roads is displayed as routing instruction. All labels are used for address search. 
-| Country     | +mkgmap:country+ | GBR | Three letter ISO code, e.g. for GBR United Kingdom
-| Region      | +mkgmap:region+  | London Borough of Waltham Forest | The regions name. Useful if there are multiple cities with the same name.
-| City        | +mkgmap:city+    | London |
-| Street      | +mkgmap:street+  | High Road Leyton | This value is used by house number search to match the +addr:street+ tag of an OSM element with house number to the corresponding road. It must be set so that house number search is working.
-| Zipcode     | +mkgmap:postal_code+ |  E10 5NA |
-| Access restrictions | +mkgmap:foot+ +
-+mkgmap:bicycle+ +
-+mkgmap:car+ +
-+mkgmap:taxi+ +
-+mkgmap:truck+ +
-+mkgmap:bus+ +
-+mkgmap:emergency+ +
-+mkgmap:delivery+  | no | These tags are evaluated for routable lines (roads) only. By default access for a specific vehicle type is allowed. Only in case the value of the tag is _no_ access is blocked for the given type.  
-| Throughroute | +mkgmap:throughroute+   | no | If this tag is set to _no_ routing is allowed on this road only if the start or end point lies on the road.
-| Carpool lane | +mkgmap:carpool+   | yes | If this tag is set to _yes_ the road is marked to have a carpool lane. This does not seem to work on all units.
-| Toll road | +mkgmap:toll+   | yes | If this tag is set to _yes_ the road can be used only when paying a specific toll.
-| Unpaved | +mkgmap:unpaved+   | yes | If this tag is set to _yes_ the road is marked to be unpaved. Some units can avoid unpaved roads.
-| Ferry | +mkgmap:ferry+   | yes | If this tag is set to _yes_ the line is marked to be a ferry line. Some units can avoid ferry lines.
-| Road speed | +mkgmap:road-speed-class+   | 2 | A value between 0 and 7. Overrides the +road_speed+ definition in the element type definition if this tag is set.
-| Road speed modifier | +mkgmap:road-speed+   | +1 | Modifies the road speed class by the given value. In case the value is prefixed with + or - the road speed class is modified. In case the value does not start with + or - the road speed class value of the element type definition is overriden. 
-| Road speed limiters | +mkgmap:road-speed-min+ +
- +mkgmap:road-speed-max+  | 5 | Defines the minimum/maximum road speed class. This can be used to limit the modification of the road speed class (+mkgmap:road-speed+). 
-| Road class | +mkgmap:road-class+   | -1 | Modifies the road class defined in the element type definition. In case the value is prefixed with + or - the road class is modified. In case the value does not start with + or - the road class value of the element type definition is overriden. 
-| Road class limiters | +mkgmap:road-class-min+ +
- +mkgmap:road-class-max+  | 2 | Defines the minimum/maximum road class. This can be used to limit the modification of the road class (+mkgmap:road-class+). 
-|=========================================================
-
-.Tags that control the treatment of roads
-[options="header"]
-|=========================================================
-| Tag | Description     | Required mkgmap option
-| +mkgmap:way-has-pois+  | +true+ for ways that have at least one point with a tag +access=\*+, +barrier=\*+, or +highway=*+ | 'link-pois-to-ways'
-| +mkgmap:dead-end-check+  | Set to +false+ to disable the dead end check for a specific way | 'report-dead-ends'
-| +mkgmap:flare-check+  | Set to +true+ to force the flare check for a specific way, set to +false+ to disable it | 'check-roundabout-flares'
-| +mkgmap:dir-check+  | Set to +false+ to tell mkgmap to ignore the way when checking roundabouts for clockwise direction | 'check-roundabouts'
-| +mkgmap:no-dir-check+  | Set to +true+ to tell mkgmap to ignore the way when checking roundabouts for clockwise direction | 'check-roundabouts'
-    
-|=========================================================
+For backward compatibility, most cases where you have spaces or symbols
+do not actually need to be quoted, however we would recommend that you
+do for clarity.  If you need a pipe symbol or a closing curly backet,
+then you must use quotes.
 
- 
-.POI address tags
-[options="header"]
-|=========================================================
-| Attribute   | mkgmap tag     | Example | Notes
-| Name        | +mkgmap:label:1+ +
-+mkgmap:label:2+ +
-+mkgmap:label:3+ +
-+mkgmap:label:4+ | Pizza Express | Names of the POI
-| Country     | +mkgmap:country+ | GBR | Three letter ISO code, e.g. for GBR United Kingdom
-| Region      | +mkgmap:region+  | Nottinghamshire | The regions name. Useful if there are multiple cities with the same name.
-| City        | +mkgmap:city+    | Nottingham |
-| Street      | +mkgmap:street+  | King Street |
-| Housenumber | +mkgmap:housenumber+ | 20 |
-| Zipcode     | +mkgmap:postal_code+ | NG1 2AS |
-| Phone       | +mkgmap:phone+   | +44 115 999999 | Phone number in any format
-|=========================================================
-
-TIP: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3[Wikipedia] has a list of all ISO 3166-1 alpha 3 codes
- 
-=== Tags added by mkgmap ===
-Some tags are added by mkgmap to indicate some property calculated by mkgmap.
+include::rules-filters.txt[]
 
-----
- mkgmap:admin_level2=* { add mkgmap:country='${mkgmap:admin_level2}' }
----- 
-The tag +mkgmap:admin_level2+ is added to each OSM element if the 'bounds' option is set. In the rule above it 
-is used to assign the country location.
-
-.Tags added by mkgmap
-[options="header"]
-|=========================================================
-| Tag | Description     | Required mkgmap option
-| +mkgmap:admin_level2+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=2+ the element is located in | 'bounds'    
-| +mkgmap:admin_level3+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=3+ the element is located in | 'bounds'    
-| +mkgmap:admin_level4+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=4+ the element is located in | 'bounds'    
-| +mkgmap:admin_level5+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=5+ the element is located in | 'bounds'    
-| +mkgmap:admin_level6+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=6+ the element is located in | 'bounds'    
-| +mkgmap:admin_level7+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=7+ the element is located in | 'bounds'    
-| +mkgmap:admin_level8+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=8+ the element is located in | 'bounds'    
-| +mkgmap:admin_level9+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=9+ the element is located in | 'bounds'    
-| +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:area2poi+  | The value is +true+ if the POI is derived from a polygon | 'add-poi-to-areas'    
-| +mkgmap:line2poi+  | The value is +true+ if the POI is derived from a line | 'add-poi-to-lines'    
-| +mkgmap:line2poitype+  | The tag is set for each POI generated from a line. Possible values are: +start+, +end+, +mid+, +inner+. | 'add-poi-to-lines'    
-| +mkgmap:exit_hint+  | +true+ for the part on link roads that should contain information about the exit | 'process-exits'    
-| +mkgmap:exit_hint_name+  | The +name+ tag value of the links exit node | 'process-exits'    
-| +mkgmap:exit_hint_ref+  | The +ref+ tag value of the links exit node | 'process-exits'    
-| +mkgmap:exit_hint_exit_to+  | The +exit_to+ tag value of the links exit node | 'process-exits'    
-| +mkgmap:dest_hint+  | +true+ for the part on link roads that should contain destination information about the link | 'process-destination'    
-| +mkgmap:synthesised+  | The value is +yes+ if the way was added by the make-cycleways option | 'make-cycleways' or 'make-opposite-cycleways'
-| +mkgmap:mp_created+  | The value is +true+ if the way was created by the internal multi-polygon-relation handling | none
-|=========================================================
-
-.Other internal tags
-[options="header"]
-|=========================================================
-| Tag | Description     
-| +mkgmap:skipSizeFilter+  | If set to +true+ the line or polygon will pass the size filter, no matter what size it has    
-|=========================================================
+include::internal-tags.txt[]
 
 [[Element_type]]
 == Element type definition ==
@@ -685,6 +575,7 @@ given then it defaults to 0 and so the specified feature will only appear
 at the most detailed level.
 
 In the following example, we set highways to appear from zoom level 4 down to zoom level 0:
+[source]
 -----
 highway=motorway [0x01 level 4]
 -----
@@ -703,6 +594,7 @@ which POI's can be shown at which resolutions.
 You can also give a range (e.g. 1-3) and the map will then contain the
 object only between the specified levels.
 
+[source]
 ----
 highway=motorway [0x01 level 3-5]
 -----
@@ -747,6 +639,7 @@ levels for example it will only show up in the lower one.
 Just as with levels, you can specify a range of resolutions at which an
 object should appear. Here is an example.
 
+[source]
 ----
 highway=residential [0x06 resolution 16-22 continue]
 highway=residential [0x07 resolution 23-24]
@@ -850,9 +743,10 @@ that the action block of this rule is also applied, when this element is
 checked for additional conversions. 
 
 .Example of a full element type definition
+[source]
 ----
 [0x2 road_class=3 road_speed=5 level 2 
-default_name 'example street' continue with_actions]
+     default_name 'example street' continue with_actions]
 ----
 
 == Including files ==
@@ -860,6 +754,7 @@ Its often convenient to split a file into smaller parts or to use the
 same rules in two different files.  In these cases you can include
 one rule file within another.
 
+[source]
  include "inc/common";
 
 Here some common rules have been included in a rule file from a directory
@@ -882,6 +777,7 @@ It is also possible to include a file from another style.
 To do this you simply add +from stylename+ to the end of the include
 statement.
 
+[source]
  include "points" from default;
 
 That will include the +points+ file from the default style. This might be
@@ -904,15 +800,18 @@ Two elements tagged with
 
 using the lines file
 
- highway=motorway    [0x01 road_class=4 road_speed=7 resolution 15]
- highway=service     [0x07 road_class=0 road_speed=1 resolution 24]
+[source]
+----
+highway=motorway    [0x01 road_class=4 road_speed=7 resolution 15]
+highway=service     [0x07 road_class=0 road_speed=1 resolution 24]
 
- <finalize>
- highway=*        { name '${name} (${ref})' | '${name}' | '${ref}' }
- highway=motorway { add bicycle=no; add foot=no }
- bicycle=*        { add mkgmap:bicycle='${bicycle}' } 
- foot=*           { add mkgmap:foot='${foot}' } 
- access=*         { addaccess '${access}' }
+<finalize>
+highway=*        { name '${name} (${ref})' | '${name}' | '${ref}' }
+highway=motorway { add bicycle=no; add foot=no }
+bicycle=*        { add mkgmap:bicycle='${bicycle}' } 
+foot=*           { add mkgmap:foot='${foot}' } 
+access=*         { addaccess '${access}' }
+----
 
 will result in
 
@@ -953,11 +852,13 @@ The following are some examples of style rules, with explanations of what they d
 In the majority of cases everything is very simple. Say you want roads that are tagged as *highway=motorway* to have the Garmin type 0x01 ("motorway") and for it to appear up until the zoom level 3.
 
 Then you would write the following rule.
+[source]
 ----
 highway=motorway [0x01 level 3]
 ----
 
 Nodes that have an id and a subid are referenced by concatenating both ids.
+[source]
 ----
 amenity=bank [0x2f06 level 3]
 ----
@@ -971,6 +872,7 @@ create almost any effect.
 
 .Internet cafes 
 ====
+[source]
  amenity=cafe & internet_access=wlan {name '${name} (wifi)'} [0x2a14 resolution 23]
 ====
 Checks to see if an OSM object has both the amenity=cafe and internet_access=wlan key/tag pairs.
@@ -979,6 +881,7 @@ The Garmin object used will be 0x2a14 and the object will only appear at resolut
 
 .Guideposts
 ====
+[source]
  information=guidepost
      { name '${name} - ${operator} - ${description} '
        | '${name} - ${description}'
@@ -1006,6 +909,7 @@ The Garmin object used will be 0x4c02 and will only appear at resolutions 23 and
 
 .Car sales rooms
 ====
+[source]
  shop=car {name '${name} (${operator})' | '${name}' |'${operator}'} [0x2f07 resolution 23]
 
 If name="Alice's Car Salesroom" and operator=Nissan, the Garmin object
@@ -1018,6 +922,7 @@ This is a trick to get opening hours to show up in the postcode field of
 a POI. Tricks like this can enhance the map for certain uses, but of
 course may prevent the proper use of the postcode field.
 
+[source]
  opening_hours=* {set addr:postcode = '${addr:postcode} open ${opening_hours}'
     | 'open ${opening_hours}'}
 
diff --git a/doc/styles/style-manual.txt b/doc/styles/style-manual.txt
index db4d50f..af38129 100644
--- a/doc/styles/style-manual.txt
+++ b/doc/styles/style-manual.txt
@@ -48,7 +48,7 @@ starting with a Polish format file, there is no style involved as the
 garmin types are already fully specified in the input file.
 
 For general information about the OpenStreetMap project see the
-link:http://wiki.openstreetmap.org[OpenStreetMap wiki].
+http://wiki.openstreetmap.org[OpenStreetMap wiki].
 
 
 :leveloffset: 1
diff --git a/doc/typ-compiler.txt b/doc/typ-compiler.txt
index ef86de3..7886f3a 100644
--- a/doc/typ-compiler.txt
+++ b/doc/typ-compiler.txt
@@ -16,6 +16,8 @@ These produce file formats that differ from each other and have variations to th
 : The product code within a family, usually just left as one.
 ;CodePage=#
 : The code page to use for writing the labels.
+
+Example:
  [_id]
  FID=1299
  ProductCode=1
@@ -102,7 +104,11 @@ These options will work in all the element sections [_point], [_line] and [_poly
 : The colour used when the GPS unit is displaying night colours.
 
 == XPM format ==
-The XPM format is used is a somewhat modified form. You can find out more about elsewhere as it is widely used.  This is a brief summary of what it all means based on the following example:
+The XPM format is a fairly widely used format for small image icons, so you
+can read about it [http://en.wikipedia.org/wiki/X_PixMap elsewhere].
+You would normally use some tool to create the image and copy it in,
+but it is possible to create them by hand.
+This is a brief summary of what it all means based on the following example:
  Xpm="10 5 3 1"
  "r  c #ff0000"
  "g  c #00ff00"
@@ -114,7 +120,9 @@ The XPM format is used is a somewhat modified form. You can find out more about
  "rbbrrrrrrr"
  "rrrrrrrrrr"
 
-Working from the top, the first line means that the pixmap has a width of 10 and a height of 5. There are 3 different colours and each colour is represented by one character in the lines to follow.
+Working from the top, the first line consists of four numbers that mean
+in order: the pixmap has a width of 10 and a height of 5; there are 3
+different colours and each colour is represented by 1 character.
 
 There then follows the three lines giving the colours to use. The first character(s) are a short name for the colour, in this case there is one character (r, g, b) because the last field in the first line was 1. Next is the letter 'c' which can be ignored, and the follows the normal RGB representation of the colour. In this case I have chosen red to be represented by the letter r, g for green and b for blue, but you can use any characters or colours you choose.  A space is allowed, and i [...]
 
@@ -152,7 +160,7 @@ It must also contain an Xpm tag. This uses a modified form of the XPM format, se
  "aaaaaaaaaaabbbbbbbbbbbaaaaaaaaaa"
  ; ... 32 rows in total
 
-* If you want to have different colours for the day and night modes, then use 4 colours. As before the second and fourth colours can be 'none' to indicate that the background is transparent for the day and/or night colour respectively. In the example the night colour has a transparent background and the day version does not. When you draw the pixmap you only use the day colours, the device will automatically switch to the alternate colours when in night mode. It is traditional to '3' and [...]
+* If you want to have different colours for the day and night modes, then use 4 colours. As before the second and fourth colours can be 'none' to indicate that the background is transparent for the day and/or night colour respectively. In the example the night colour has a transparent background and the day version does not. When you draw the pixmap you only use the day colours, the device will automatically switch to the alternate colours when in night mode. It is traditional to use '3' [...]
  Xpm="32 32 4 1"
  "a   c #778899"
  "b   c #221133"
@@ -178,7 +186,7 @@ A line section can contain any of the common tags. It can also have the followin
 
 As with polygons there is an Xpm tag too and it can specify that solid colours should be used or that there is a bitmap.
 
-If there is a bitmap then it is always has a width of 32, its height will be the width of the line (yes that sound confusing the first time you read it, it just means that the line is written horizontally in the pixmap). The colours work in exactly the same way as they do for polygons, so see the examples there for the different possibilities with day/nitght and transparent colours. An example with a pixmap, which shows a line that will have a thickness of 3. 
+If there is a bitmap then it is always has a width of 32, its height will be the width of the line (yes that sound confusing the first time you read it, it just means that the line is written horizontally in the pixmap). The colours work in exactly the same way as they do for polygons, so see the examples there for the different possibilities with day/night and transparent colours. An example with a pixmap, which shows a line that will have a thickness of 3. 
  Xpm="32 3 4 1"
  "a  c #550088"
  ".  c #889988"
@@ -296,7 +304,7 @@ long as there are the correct number altogether.
  "#990088 #990088 #990067"
  " ... and so on for 100 different values ..."
 
-The spaces can be ommitted for the most compact representation.
+The spaces can be omitted for the most compact representation.
 It is also possible to have a transparent pixel with this format, but there is currently not a way to represent this.
 
 == Icons ==
diff --git a/extra/src/uk/me/parabola/util/CollationRules.java b/extra/src/uk/me/parabola/util/CollationRules.java
new file mode 100644
index 0000000..3550a41
--- /dev/null
+++ b/extra/src/uk/me/parabola/util/CollationRules.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.util;
+
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.ibm.icu.text.CollationElementIterator;
+import com.ibm.icu.text.Collator;
+import com.ibm.icu.text.RuleBasedCollator;
+
+//import java.text.CollationElementIterator;
+//import java.text.Collator;
+//import java.text.RuleBasedCollator;
+
+/**
+ * Create a set of rules for a given code page.
+ *
+ * Should be usable, perhaps with a few tweaks.
+ * Works with unicode too, need to choose which blocks to take for unicode.
+ *
+ * @author Steve Ratcliffe
+ */
+public class CollationRules {
+
+	private CharsetDecoder decoder;
+	private final NavigableSet<CharPosition> positionMap = new TreeSet<>();
+	private final NavigableSet<CharPosition> basePositionMap = new TreeSet<>();
+	private final Map<Character, CharPosition> charMap = new HashMap<>();
+	private boolean isUnicode;
+	private Charset charset;
+
+	public static void main(String[] args) {
+		String charsetName = args[0];
+		CollationRules main = new CollationRules();
+		main.go(charsetName);
+	}
+
+	private void go(String charsetName) {
+		RuleBasedCollator col = (RuleBasedCollator) Collator.getInstance();
+
+		charset = Charset.forName(charsetName);
+		if (charsetName.equalsIgnoreCase("utf-8"))
+			isUnicode = true;
+		decoder = charset.newDecoder();
+
+		if (isUnicode)
+			addUnicode();
+		else
+			addBlock(col, 0);
+
+		printCharMap();
+		printExpansions();
+	}
+
+	private void addBlock(RuleBasedCollator col, int block) {
+		for (int i = 0; i < 0x100; i++) {
+			int ch = (block << 8) + i;
+			String testString = getString(ch);
+			char conv = testString.charAt(0);
+			if (Character.getType(conv) == Character.UNASSIGNED || conv == 0xfffd)
+				continue;
+			CollationElementIterator it = col.getCollationElementIterator(testString);
+
+			System.out.printf("# %s ", fmtChar(testString.charAt(0)));
+			int next;
+			int index = 0;
+			CharPosition cp = new CharPosition(0);
+			while ((next = it.next()) != CollationElementIterator.NULLORDER) {
+				if (index == 0) {
+					cp = new CharPosition(ch);
+					cp.setOrder(next);
+				} else {
+					assert index < 3;
+					if ((next & 0xffff0000) == 0) {
+						cp.addOrder(next, index);
+					} else {
+						cp.addChar(new CharPosition(ch));
+						cp.setOrder(next);
+					}
+				}
+
+				index++;
+			}
+			System.out.printf(" %s %d", cp, Character.getType(cp.getUnicode()));
+			System.out.println();
+
+			tweak(cp);
+			if (ch > 0)
+				positionMap.add(cp);
+			if (cp.nextChar == null) {
+				basePositionMap.add(cp);
+				charMap.put(conv, cp);
+			}
+		}
+	}
+
+	private void addUnicode() {
+		Pattern pat = Pattern.compile("([0-9A-F]{4,5}) ? ; \\[[.*](.*)\\] #.*");
+		try (FileReader r = new FileReader("allkeys.txt")) {
+			try (BufferedReader br = new BufferedReader(r)) {
+				String line;
+				while ((line = br.readLine()) != null) {
+					Matcher matcher = pat.matcher(line);
+					if (matcher.matches()) {
+						String weights = matcher.group(2);
+						int ch = Integer.parseInt(matcher.group(1), 16);
+						if (ch > 0xffff)
+							continue;
+
+						System.out.printf("# %04x %s ", ch, fmtChar(ch));
+
+						String[] split = weights.split("]\\[[.*]");
+
+						int index = 0;
+						CharPosition cp = new CharPosition(0);
+
+						for (String s : split) {
+							String[] ws = s.split("\\.");
+							int next = Integer.parseInt(ws[0], 16) << 16
+									| ((Integer.parseInt(ws[1], 16) << 8) & 0xff00)
+									| ((Integer.parseInt(ws[2], 16)) & 0xff);
+
+							if (index == 0) {
+								cp = new CharPosition(ch);
+								cp.setOrder(next);
+							} else {
+								if ((next & 0xffff0000) == 0) {
+									cp.addOrder(next, index);
+								} else {
+									cp.addChar(new CharPosition(ch));
+									cp.setOrder(next);
+								}
+							}
+							index++;
+						}
+
+						System.out.printf(" %s %d\n", cp, Character.getType(cp.getUnicode()));
+
+						tweak(cp);
+						if (ch > 0)
+							positionMap.add(cp);
+						if (cp.nextChar == null) {
+							basePositionMap.add(cp);
+							charMap.put((char) ch, cp);
+						}
+					} else {
+						System.out.println("# NOMATCH: " + line);
+					}
+				}
+
+			}
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Fix up a few characters that we always want to be in well known places.
+	 *
+	 * @param cp The position to change.
+	 */
+	private void tweak(CharPosition cp) {
+		if (cp.val < 8)
+			cp.third = cp.val + 7;
+
+		if (!isUnicode) {
+			switch (cp.getUnicode()) {
+			case '¼':
+				cp.nextChar = charMap.get('/').copy();
+				cp.nextChar.nextChar = charMap.get('4');
+				break;
+			case '½':
+				cp.nextChar = charMap.get('/').copy();
+				cp.nextChar.nextChar = charMap.get('2');
+				break;
+			case '¾':
+				cp.nextChar = charMap.get('/').copy();
+				cp.nextChar.nextChar = charMap.get('4');
+				break;
+			}
+		}
+
+		switch (cp.getUnicode()) {
+		case '˜':
+			CharPosition tilde = charMap.get('~');
+			cp.first = tilde.first;
+			cp.second = tilde.second + 1;
+			cp.third = tilde.third + 1;
+			cp.nextChar = null;
+			break;
+		}
+	}
+
+	private String getString(int i) {
+		if (isUnicode)
+			return new String(new char[]{(char) i});
+		else {
+			byte[] b = {(byte) i};
+			return new String(b, 0, 1, charset);
+		}
+	}
+
+	private void printCharMap() {
+
+		Formatter chars = new Formatter();
+		chars.format("\n");
+
+		CharPosition last = new CharPosition(0);
+		last.first = 0;
+		for (CharPosition cp : positionMap) {
+			if (cp.isExpansion())
+				continue;
+
+			if (cp.first != last.first) {
+				chars.format("\n < ");
+			} else if (cp.second != last.second) {
+				chars.format(" ; ");
+			} else if (cp.third != last.third) {
+				chars.format(",");
+			} else {
+				chars.format("=");
+			}
+			last = cp;
+			int uni = toUnicode(cp.val);
+			chars.format("%s", fmtChar(uni));
+		}
+
+		System.out.println(chars);
+	}
+
+	private void printExpansions() {
+		for (CharPosition cp : positionMap) {
+			if (!cp.isExpansion())
+				continue;
+
+			Formatter fmt = new Formatter();
+
+			//noinspection MalformedFormatString
+			fmt.format("expand %c to", cp.getUnicode());
+
+			boolean ok = true;
+			for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
+				cp2.second = 0x50000;
+				int top = (cp2.third >> 16) & 0xff;
+				cp2.third = (top == 0x9e || top == 0xa2 || top == 0x2b) ? 0x9b0000 : 0;
+
+				CharPosition floor = basePositionMap.ceiling(cp2);
+				if (floor == null || floor.getUnicode() == 0xfffd) {
+					fmt.format(" NF");
+					ok = false;
+				} else {
+					fmt.format(" %s", fmtChar(floor.getUnicode()));
+				}
+			}
+
+			System.out.println((ok ? "" : "# ") + fmt.toString());
+
+			// Print comments to help find problems.
+			for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
+				CharPosition floor = basePositionMap.ceiling(cp2);
+				if (floor == null) {
+					System.out.println("#FIX: NF ref=" + cp2);
+				} else {
+					System.out.println("#floor is " + fmtChar(toUnicode(floor.val)) + ", " + floor + ", ref is " + cp2);
+				}
+			}
+		}
+	}
+
+	private String fmtChar(int val) {
+		boolean asChar = true;
+		switch (val) {
+		case '<':
+		case ';':
+		case ',':
+		case '=':
+		case '#':
+			asChar = false;
+			break;
+		default:
+
+			switch (Character.getType(val)) {
+			case Character.UNASSIGNED:
+			case Character.NON_SPACING_MARK:
+			case Character.FORMAT:
+			case Character.CONTROL:
+			case Character.SPACE_SEPARATOR:
+			case Character.LINE_SEPARATOR:
+			case Character.PARAGRAPH_SEPARATOR:
+				asChar = false;
+			}
+		}
+
+		if (asChar) {
+			//noinspection MalformedFormatString
+			return String.format("%c", val);
+		} else {
+			return String.format("%04x", val);
+		}
+	}
+
+	private int toUnicode(int c) {
+		if (isUnicode)
+			return c;
+		ByteBuffer b = ByteBuffer.allocate(1);
+		b.put((byte) c);
+		b.flip();
+		try {
+			CharBuffer chars = decoder.decode(b);
+			return chars.charAt(0);
+		} catch (CharacterCodingException e) {
+			return '?';
+		}
+	}
+
+
+	class CharPosition implements Comparable<CharPosition> {
+		private final int val;
+		private int first;
+		private int second;
+		private int third;
+		private CharPosition nextChar;
+
+		public CharPosition(int charValue) {
+			this.val = charValue;
+		}
+
+		public int compareTo(CharPosition other) {
+			if (other.first == first)
+				return compareSecond(other);
+			else if (first < other.first)
+				return -1;
+			else
+				return 1;
+		}
+
+		private int compareSecond(CharPosition c2) {
+			if (c2.second == second)
+				return compareThird(c2);
+			else if (second < c2.second)
+				return -1;
+			else
+				return 1;
+		}
+
+		private int compareThird(CharPosition c2) {
+			if (third == c2.third)
+				return new Integer(val).compareTo(c2.val);
+			else if (third < c2.third)
+				return -1;
+			else
+				return 1;
+		}
+
+		public String toString() {
+			Formatter fmt = new Formatter();
+			toString(fmt);
+
+			return fmt.toString();
+		}
+
+		private void toString(Formatter fmt) {
+			fmt.format("[%04x %02x %02x]", first, second, third);
+			if (nextChar != null)
+				nextChar.toString(fmt);
+		}
+
+		public void setOrder(int next) {
+			if (nextChar != null) {
+				nextChar.setOrder(next);
+				return;
+			}
+			first = (next >> 16) & 0xffff;
+			second = (next << 8) & 0xff0000;
+			third = (next << 16) & 0xff0000;
+		}
+
+		public void addOrder(int next, int count) {
+			assert ((next >>> 16) & 0xffff) == 0;
+			if (this.nextChar != null) {
+				this.nextChar.addOrder(next, count);
+				return;
+			}
+			second += ((next >> 8) & 0xff) << (2-count)*8;
+			third += ((next) & 0xff) << (2-count)*8;
+		}
+
+		public boolean isExpansion() {
+			return nextChar != null;
+		}
+
+		public void addChar(CharPosition pos) {
+			if (nextChar != null) {
+				nextChar.addChar(pos);
+				return;
+			}
+			nextChar = pos;
+		}
+
+		public int getUnicode() {
+			return toUnicode(val);
+		}
+
+		public CharPosition copy() {
+			CharPosition cp = new CharPosition(this.val);
+			cp.first = this.first;
+			cp.second = this.second;
+			cp.third = this.third;
+			return cp;
+		}
+	}
+}
diff --git a/ivy.xml b/ivy.xml
index ccf5442..d6f9fc1 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -17,20 +17,20 @@
 	</publications>
 	<dependencies>
 		<dependency org="com.google.protobuf" name="protobuf-java"
-			    rev="2.4.1"
+			    rev="2.5.0"
 			    conf="compile->compile(*),master(*)" />
 
 		<dependency org="crosby" name="osmpbf"
-			    rev="1.1.1-754a33af"
+			    rev="1.3.3"
 			    conf="compile->compile(*),master(*)" />
 
 		<dependency org="it.unimi.dsi" name="fastutil"
-				rev="6.5.2-mkg.1"
+				rev="6.5.15-mkg.1b"
 				conf="compile->default(*)"
 				/>
 
 		<dependency org="junit" name="junit"
-			    rev="4.5"
+			    rev="4.11"
 			    conf="test->runtime(*),master(*)" />
 
 		<dependency org="innig" name="macker"
@@ -53,7 +53,7 @@
 			    rev="b9"
 			    conf="macker->compile(*),master(*)" />
 
-		<dependency org="com.ibm.icu" name="icu4j" rev="4.8"
+		<dependency org="com.ibm.icu" name="icu4j" rev="53.1"
 								conf="optional->master"/>
 
 		<dependency org="org.apache.ant" name="ant" rev="1.8.2"
diff --git a/mkgmap.iml b/mkgmap.iml
index 07417c8..b36fc90 100644
--- a/mkgmap.iml
+++ b/mkgmap.iml
@@ -41,7 +41,6 @@
         <CLASSES>
           <root url="jar://$USER_HOME$/.ivy2/cache/com.google.protobuf/protobuf-java/jars/protobuf-java-2.4.1.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/crosby/osmpbf/jars/osmpbf-1.1.1-754a33af.jar!/" />
-          <root url="jar://$USER_HOME$/.ivy2/cache/junit/junit/jars/junit-4.5.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/com.ibm.icu/icu4j/jars/icu4j-4.8.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/org.apache.ant/ant/jars/ant-1.8.2.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/javax.media.jai/com.springsource.javax.media.jai.codec/jars/com.springsource.javax.media.jai.codec-1.1.3.jar!/" />
@@ -67,9 +66,38 @@
           <root url="jar://$USER_HOME$/.ivy2/cache/it.unimi.dsi/fastutil/jars/fastutil-6.5.2-mkg.1.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-shapefile/jars/gt-shapefile-2.7.5.jar!/" />
           <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-data/jars/gt-data-2.7.5.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/junit/junit/jars/junit-4.11.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.hamcrest/hamcrest-core/jars/hamcrest-core-1.3.jar!/" />
         </CLASSES>
-        <JAVADOC />
-        <SOURCES />
+        <JAVADOC>
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.google.protobuf/protobuf-java/javadocs/protobuf-java-2.4.1-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/junit/junit/javadocs/junit-4.11-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.hamcrest/hamcrest-core/javadocs/hamcrest-core-1.3-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.ibm.icu/icu4j/javadocs/icu4j-4.8-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.vividsolutions/jts/javadocs/jts-1.11-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/commons-pool/commons-pool/javadocs/commons-pool-1.5.4-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/net.java.dev.jsr-275/jsr-275/javadocs/jsr-275-1.0-beta-2-javadoc.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/commons-lang/commons-lang/javadocs/commons-lang-1.0.1-javadoc.jar!/" />
+        </JAVADOC>
+        <SOURCES>
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.google.protobuf/protobuf-java/sources/protobuf-java-2.4.1-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/junit/junit/sources/junit-4.11-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.hamcrest/hamcrest-core/sources/hamcrest-core-1.3-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.ibm.icu/icu4j/sources/icu4j-4.8-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-api/sources/gt-api-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/com.vividsolutions/jts/sources/jts-1.11-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-referencing/sources/gt-referencing-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/commons-pool/commons-pool/sources/commons-pool-1.5.4-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-metadata/sources/gt-metadata-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-opengis/sources/gt-opengis-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/net.java.dev.jsr-275/jsr-275/sources/jsr-275-1.0-beta-2-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-cql/sources/gt-cql-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-main/sources/gt-main-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/jdom/jdom/sources/jdom-1.0-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-shapefile/sources/gt-shapefile-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/org.geotools/gt-data/sources/gt-data-2.7.5-sources.jar!/" />
+          <root url="jar://$USER_HOME$/.ivy2/cache/bcel/bcel/sources/bcel-5.1-sources.jar!/" />
+        </SOURCES>
       </library>
     </orderEntry>
   </component>
diff --git a/resources/chars/ascii/rowff.trans b/resources/chars/ascii/rowff.trans
index 6c87bc4..d55d4f2 100644
--- a/resources/chars/ascii/rowff.trans
+++ b/resources/chars/ascii/rowff.trans
@@ -256,6 +256,6 @@ U+fff9 {            # Character 
 U+fffa |            # Character 
 U+fffb }            # Character 
 U+fffc ?            # Character 
-U+fffd ?            # Character �
+U+fffd ?            # Character ? (replacement character)
 U+fffe ?            # Character ￾
 U+ffff ?            # Character ￿
diff --git a/resources/chars/latin1/rowff.trans b/resources/chars/latin1/rowff.trans
index 43709e7..2a23edb 100644
--- a/resources/chars/latin1/rowff.trans
+++ b/resources/chars/latin1/rowff.trans
@@ -259,6 +259,6 @@ U+fff9 ?            # Character 
 U+fffa ?            # Character 
 U+fffb ?            # Character 
 U+fffc ?            # Character 
-U+fffd ?            # Character �
+U+fffd ?            # Character ? (replacement character)
 U+fffe ?            # Character ￾
 U+ffff ?            # Character ￿
diff --git a/resources/help/en/options b/resources/help/en/options
index 280c28c..47dcf1d 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -70,6 +70,9 @@ Label options:
 --latin1
 	This is equivalent to --code-page=1252.
 
+--unicode
+	This is equivalent to --code-page=65001. Note that only newer devices support unicode.
+
 --code-page=number
     This option enables the use of international characters. Only 8 bit
     character sets are supported and so you have to specify which code page
@@ -372,6 +375,10 @@ Miscellaneous options:
 	problems. Default value is 0 (disabled) because it's not a
 	completely reliable heuristic.
 
+--ignore-maxspeeds
+	Now ignored, former usage:
+	When reading OSM files, ignore any "maxspeed" tags.
+
 --ignore-builtin-relations
 	When reading OSM files, skip the built-in processing of
 	relations. This speeds up the processing non-routable map
@@ -392,6 +399,7 @@ Miscellaneous options:
 	the order in which the elements are processed is not defined.
 
 --remove-short-arcs[=MinLength]
+  	Now ignored, former usage:	
 	Merge nodes to remove short arcs that can cause routing
 	problems. If MinLength is specified (in metres), arcs shorter
 	than that length will be removed. If a length is not
@@ -544,6 +552,7 @@ Miscellaneous options:
 	(NSIS) to create a Windows Mapsource Installer.
 
 --make-all-cycleways
+  Deprecated, use --make-opposite-cycleways instead. Former meaning: 
 	Turn on all of the options that make cycleways.
 
 --make-opposite-cycleways
@@ -552,6 +561,7 @@ Miscellaneous options:
 	the original that allows bicycle traffic (in both directions).
 
 --make-cycleways
+  Now ignored, former meaning:
 	Some streets have a separate cycleway track/lane just for
 	bicycle traffic and this option makes a way with the same
 	points as the original that allows bicycle traffic. Also,
@@ -559,16 +569,22 @@ Miscellaneous options:
 	(unless that way's bicycle access has been defined).
 
 --link-pois-to-ways
-    This option copies some specific attributes of a POI to a 
-    small part of the way the POI is located on. This can be used
+    This option may copy some specific attributes of a POI 
+    to a small part of the way the POI is located on. This can be used
     to let barriers block a way or to lower the calculated speed
     around traffic signals.
     POIs with the tags highway=* (e.g. highway=traffic_signals)  
     or barrier=* (e.g. barrier=cycle_barrier) are supported.
     The style developer must add at least one of the access tags
     (mkgmap:foot, mkgmap:car etc.), mkgmap:road-speed and/or 
-    mkgmap:road-class to the POI. These tags are copied to a small
-    part of the way around the POI. 
+    mkgmap:road-class to the POI. 
+    The access tags are ignored if they have no effect for the way, 
+    else a route restriction is added at the POI so that only 
+    allowed vehicles are routed through it. 
+    The tags mkgmap:road-speed and/or mkgmap:road-class are 
+    applied to a small part of the way around the POI, typically
+    to the next junction or a length of ~25 m. The tags
+    are ignored for pedestrian-only ways.      
 
 --process-destination
 	Splits all motorway_link and trunk_link ways tagged with 
diff --git a/resources/installer/installer_template.nsi b/resources/installer/installer_template.nsi
index 7e7caab..6de4b09 100644
--- a/resources/installer/installer_template.nsi
+++ b/resources/installer/installer_template.nsi
@@ -44,6 +44,13 @@ Function .onInit
 FunctionEnd
 
 Function myGUIInit
+  ;Read $INSTDIR from the registry
+  ClearErrors
+  ReadRegStr $INSTDIR HKLM "SOFTWARE\Garmin\MapSource\Families\${REG_KEY}\${PRODUCT_ID}" "LOC"
+  IfErrors +2
+  StrCmp $INSTDIR "" 0 +2
+  StrCpy $INSTDIR "${DEFAULT_DIR}"
+  
   ; Uninstall before installing (code from http://nsis.sourceforge.net/Auto-uninstall_old_before_installing_new )
   ReadRegStr $R0 HKLM \
   "Software\Microsoft\Windows\CurrentVersion\Uninstall\${REG_KEY}" "UninstallString"
@@ -56,7 +63,7 @@ Function myGUIInit
   ;Run the uninstaller
   uninst:
   ClearErrors
-  ExecWait '"$R0" _?=$INSTDIR' ;Do not copy the uninstaller to a temp file
+  ExecWait '"$R0" /S ' ;Do not copy the uninstaller to a temp file
  
   IfErrors no_remove_uninstaller done
     ;You can either use Delete /REBOOTOK in the uninstaller or add some code
@@ -68,7 +75,7 @@ Function myGUIInit
   Goto done
  
   silent:
-  ExecWait '"$R0" /S _?=$INSTDIR' ;Do not copy the uninstaller to a temp file
+  ExecWait '"$R0" /S ' ;Do not copy the uninstaller to a temp file
  
   done:
  
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index bc82089..f1389b0 100644
--- a/resources/mkgmap-version.properties
+++ b/resources/mkgmap-version.properties
@@ -1,2 +1,2 @@
-svn.version: 2981
-build.timestamp: 2014-01-23T11:59:50+0000
+svn.version: 3333
+build.timestamp: 2014-08-08T07:32:50+0100
diff --git a/resources/sort/README b/resources/sort/README
new file mode 100644
index 0000000..c4bf96a
--- /dev/null
+++ b/resources/sort/README
@@ -0,0 +1,56 @@
+There are generic sort descriptions for various code pages.
+
+You could write one for a particular language.
+
+
+An ordering of characters for a given code page.
+Characters are represented either as themselves (in unicode) or
+as two or more hex digits of the unicode representation.
+
+There are three ordering strengths represented in this file.
+
+These are Primary (different letters), secondary (different
+accents), tertiary (different case).
+See the java documentation for the Collator class for some more
+discussion of the strength concept and examples.
+
+Note that primary differences always determine the order even if
+they are later in the word than secondary differences.
+ie A B comes after A-acute A, even though A-acute sorts after A.
+
+The word 'code' starts the ordering section.
+
+Primary differences are represented by the '<' separator.
+Characters with secondary differences are separated by semicolons
+and characters with tertiary differences are separated by commas.
+
+The code section ends if the word 'expansion' is seen.
+This introduces a character that should sort as though it is
+two (or more) separate characters.
+
+
+ID values
+---------
+
+I believe that these are arbitary identifiers.  Here is a registry of
+values we are using.  If you make a variation on a code-page
+sort-order then give it a different id2 value.
+
+code-page  id1  id2
+
+1250       12   1
+1251        8   1
+1252        7   2
+1253       13   1
+1254       14   1
+1255       15   1
+1256       16   1
+1257       17   1
+1258       18   1
+874        11   1
+932         9   1
+936         5   1
+949        10   1
+
+65001      19   4
+0          0    0
diff --git a/resources/sort/cp0.txt b/resources/sort/cp0.txt
new file mode 100644
index 0000000..21ad72b
--- /dev/null
+++ b/resources/sort/cp0.txt
@@ -0,0 +1,81 @@
+codepage 0
+id1 0
+id2 1
+description "ASCII 7-bit sort"
+
+characters
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020
+ < _
+ < -
+ < 002c
+ < 003b
+ < :
+ < !
+ < ?
+ < .
+ < '
+ < "
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < `
+ < ^
+ < +
+ < 003c
+ < 003d
+ < >
+ < |
+ < ~
+ < $
+ < 0
+ < 1
+ < 2
+ < 3
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A
+ < b,B
+ < c,C
+ < d,D
+ < e,E
+ < f,F
+ < g,G
+ < h,H
+ < i,I
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N
+ < o,O
+ < p,P
+ < q,Q
+ < r,R
+ < s,S
+ < t,T
+ < u,U
+ < v,V
+ < w,W
+ < x,X
+ < y,Y
+ < z,Z
diff --git a/resources/sort/cp1250.txt b/resources/sort/cp1250.txt
index 699502e..c157300 100644
--- a/resources/sort/cp1250.txt
+++ b/resources/sort/cp1250.txt
@@ -3,86 +3,120 @@ id1 12
 id2 1
 description "Central European sort"
 
-code pos2=0 01, 02, 03, 04, 05, 06, 07
-
-code pos=1 20 < a0 < 09 < 0a < 0b < 0c < 0d < ! < " < 23 < $ < %
-code & < ( < ) < * < , < . < / < : < ; < ? < @ < [
-code \ < ] < ^ < _ < ` < { < | < } < ~ < a6 < a8 < '
-code b8 < a1 < a2
-
-code ff
-code b2
-code bd
-code 91
-code 92
-code 82
-code 93
-code 94
-code 84
-code 8B
-code 9B
-code a4
-code 80
-code +
-code 3c
-code =
-code >
-code b1
-code ab
-code bb
-code d7
-code f7
-code a7
-code a9
-code ac
-code ae
-code b0
-code b5
-code b6
-code b7
-code 86
-code 87
-code 95
-code 89
-
-code 0
-code 1
-code 2
-code 3
-code 4
-code 5
-code 6
-code 7
-code 8
-code 9
-
-code a,A; ä,Ä; á,Á; â,Â; ă,Ă; ą,Ą
-code b,B
-code c,C; ć,Ć; č,Č; ç,Ç
-code d,D; ď,Ď; đ,Đ
-code e,E; ë,Ë; é,É; ě,Ě; ę,Ę
-code f,F
-code g,G
-code h,H
-code i,I; í,Í; î,Î
-code j,J
-code k,K
-code l,L; ĺ,Ĺ; ľ,Ľ; ł,Ł
-code m,M
-code n,N; ń,Ń; ň,Ň
-code o,O; ö,Ö; ó,Ó; ô,Ô; ő,Ő
-code p,P
-code q,Q
-code r,R; ŕ,Ŕ
-code s,S; ś,Ś; š,Š; ş,Ş
-code t,T; ť,Ť; ţ,Ţ
-code 99
-code u,U; ü,Ü; ú,Ú; ů,Ů; ű,Ű
-code v,V
-code w,W
-code x,X
-code y,Y; ý,Ý
-code z,Z; ż,Ż; ź,Ź; ž,Ž
-
-expand ß to s s
-expand 85 to . . .
+characters
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ?
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ´
+ < ^
+ < ¨
+ < ¸
+ < §
+ < ¶
+ < ©
+ < ®
+ < ˇ
+ < °
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~
+ < ¤
+ < $
+ < €
+ < 0
+ < 1
+ < 2
+ < 3
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A ; á,Á ; ă,Ă ; â, ; ä,Ä ; ą,Ą
+ < b,B
+ < c,C ; ć,Ć ; č,Č ; ç,Ç
+ < d,D ; ď,Ď ; đ,Đ
+ < e,E ; é,É ; ě,Ě ; ë,Ë ; ę,Ę
+ < f,F
+ < g,G
+ < h,H
+ < i,I ; í,Í ; î,Î
+ < j,J
+ < k,K
+ < l,L ; ĺ,Ĺ ; ľ,Ľ ; ł,Ł
+ < m,M
+ < n,N ; ń,Ń ; ň,Ň
+ < o,O ; ó,Ó ; ô,Ô ; ö,Ö ; ő,Ő
+ < p,P
+ < q,Q
+ < r,R ; ŕ,Ŕ ; ř,Ř
+ < s,S ; ś,Ś ; š,Š ; ş,Ş
+ < t,T ; ť,Ť ; ţ,Ţ
+ < u,U ; ú,Ú ; ů,Ů ; ü,Ü ; ű,Ű
+ < v,V
+ < w,W
+ < x,X
+ < y,Y ; ý,Ý
+ < z,Z ; ź,Ź ; ž,Ž ; ż,Ż
+ < µ
+expand … to  . . .
+expand ˘ to  ¨ 0020
+expand ˙ to  ¨ `
+expand ˝ to  ¸ `
+expand ˛ to  § 0020
+expand ß to  s s
+expand ™ to  T M
diff --git a/resources/sort/cp1251.txt b/resources/sort/cp1251.txt
index 2c2a391..bcaa8a3 100644
--- a/resources/sort/cp1251.txt
+++ b/resources/sort/cp1251.txt
@@ -1,160 +1,158 @@
 codepage 1251
 id1 8
 id2 1
-description "Cyrillic Sort"
-code 01
-code 02
-code 03
-code 04
-code 05
-code 06
-code 07
-code 08
-code 09, 0a, 0b, 0c, 0d, 20, a0
-code 0e
-code 0f
-code 10
-code 11
-code 12
-code 13
-code 14
-code 15
-code 16
-code 17
-code 18
-code 19
-code 1a
-code 1b
-code 1c
-code 1d
-code 1e
-code 1f
-code !
-code "
-code “
-code ”
-code «
-code »
-code „
-code '
-code ‘
-code ’
-code ‚
-code 2c
-code 3b
-code :
-code .
-code …
-code $
-code ¤
-code €
-code %
-code ‰
-code &
-code 23
-code (
-code )
-code ‹, 3c
-code ›, 3e
-code [
-code ]
-code {
-code }
-code *
-code +
-code -, –, —
-code ±
-code =
-code ad
-code /
-code \
-code ?
-code @
-code ^
-code ~
-code ¬
-code _
-code `
-code |
-code 7f
-code †
-code ‡
-# code 98
-code ¦
-code §
-code ™
-code ©
-code ®
-code °
-code ¶
-code •
-code ·
-code №
-code 0
-code 1
-code 2
-code 3
-code 4
-code 5
-code 6
-code 7
-code 8
-code 9
-code a, A
-code b, B
-code c, C
-code d, D
-code e, E
-code f, F
-code g, G
-code h, H
-code i, I; і, І; ї, Ї
-code j, J; ј, Ј
-code k, K
-code l, L
-code m, M
-code n, N
-code o, O
-code p, P
-code q, Q
-code r, R
-code s, S; ѕ, Ѕ
-code t, T
-code u, U
-code v, V
-code w, W
-code x, X
-code y, Y
-code z, Z
-code а, А
-code б, Б
-code в, В
-code г, Г; ґ, Ґ; ћ, Ћ; ѓ, Ѓ
-code д, Д; ђ, Ђ
-code е, Е; ё, Ё; є, Є
-code ж, Ж
-code з, З
-code и, И
-code й, Й
-code к, К; ќ, Ќ
-code л, Л; љ, Љ
-code м, М; µ
-code н, Н; њ, Њ
-code о, О
-code п, П
-code р, Р
-code с, С
-code т, Т
-code у, У; ў, Ў
-code ф, Ф
-code х, Х
-code ц, Ц
-code ч, Ч; џ, Џ
-code ш, Ш
-code щ, Щ
-code ъ, Ъ
-code ы, Ы
-code ь, Ь
-code э, Э
-code ю, Ю
-code я, Я
+description "Cyrillic sort"
+
+characters
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ?
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ^
+ < §
+ < ¶
+ < ©
+ < ®
+ < °
+ < +
+ < ±
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~
+ < ¤
+ < $
+ < €
+ < 0
+ < 1
+ < 2
+ < 3
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A
+ < b,B
+ < c,C
+ < d,D
+ < e,E
+ < f,F
+ < g,G
+ < h,H
+ < i,I
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N
+ < o,O
+ < p,P
+ < q,Q
+ < r,R
+ < s,S
+ < t,T
+ < u,U
+ < v,V
+ < w,W
+ < x,X
+ < y,Y
+ < z,Z
+ < µ
+ < а,А
+ < б,Б
+ < в,В
+ < г,Г ; ґ,Ґ
+ < д,Д
+ < ђ,Ђ
+ < ѓ,Ѓ
+ < е,Е ; ё,Ё
+ < є,Є
+ < ж,Ж
+ < з,З
+ < ѕ,Ѕ
+ < и,И
+ < і,І
+ < ї,Ї
+ < й,Й
+ < ј,Ј
+ < к,К
+ < л,Л
+ < љ,Љ
+ < м,М
+ < н,Н
+ < њ,Њ
+ < о,О
+ < п,П
+ < р,Р
+ < с,С
+ < т,Т
+ < ћ,Ћ
+ < ќ,Ќ
+ < у,У
+ < ў,Ў
+ < ф,Ф
+ < х,Х
+ < ц,Ц
+ < ч,Ч
+ < џ,Џ
+ < ш,Ш
+ < щ,Щ
+ < ъ,Ъ
+ < ы,Ы
+ < ь,Ь
+ < э,Э
+ < ю,Ю
+ < я,Я
+
+expand … to  . . .
+expand № to  N o
+expand ™ to  T M
diff --git a/resources/sort/cp1252.txt b/resources/sort/cp1252.txt
index ac08df1..fdef894 100644
--- a/resources/sort/cp1252.txt
+++ b/resources/sort/cp1252.txt
@@ -1,154 +1,137 @@
-#
-# An ordering of characters for a given code page.
-# Characters are represented either as themselves (in unicode) or
-# as two hex digits in the target codepage.
-# Characters later, reading left to right from top to bottom, sort
-# after those that are earlier in the file.
-#
-# There are three ordering strengths represented in this file.
-#
-# These are Primary (different letters), secondary (different
-# accents), tertiary (different case).
-# See the java documentation for the Collator class for some more
-# discussion of the strength concept and examples.
-#
-# Note that primary differences always determine the order even if
-# they are later in the word than secondary differences.
-# ie A B comes after A-acute A, even though A-acute sorts after A.
-#
-# In this file Primary differences are represented by lines begining
-# with the keyword 'code'.  All the letters following are the same
-# letter ignoring case and accents.
-# Characters with secondary differences are separated by semicolons
-# and characters with tertiary differences are separated by commas.
-#
-# You can also separate different letters with '<' instead of starting a new
-# 'code' line.
-#
-# You can split lines after a semi-colon or comma, but otherwise a new
-# line ends the 'code' command.
 
 
 # This must be first before any 'code' lines.
 codepage 1252
 id1 7
 id2 2
-description "Western European Sort"
+description "Western European sort"
 
-code pos2=0 pos3=8 01, 02, 03, 04, 05, 06, 07
-code flags=w ¼
-code 20,a0,1e,1f; _ ;b4;`;^;a8;98;b8;af
-code pos2=1 ad
-code -
-code 96
-code 97
-code 2c
-code 3b
-code :
-code !
-code ¡
-code ?
-code ¿
-code .
-code ·
-code '
-code 91
-code 92
-code 82
-code 8b
-code 9b
-code "
-code 93
-code 94
-code 84
-code «
-code »
-code (
-code )
-code [
-code ]
-code {
-code }
-code §
-code ¶
-code ©
-code ®
-code @
-code *
-code /
-code \
-code &
-code 23
-code %
-code 89
-code 86
-code 87
-code 95
-code ¤
-code ¢
-code $
-code £
-code ¥
-code °
-code +
-code ±
-code ÷
-code 88
-code 3c
-code =
-code >
-code ¬
-code |
-code ¦
-code ~
-code 0
-code 1,¹
-code 2,²
-code 3,³
-code 4
-code 5
-code 6
-code 7
-code 8
-code 9
-code a,A,,ª; á,Á; à,À; â,Â; å,Å; ä,Ä; ã,Ã
-code b,B
-code c,C; ç,Ç
-code d,D;;ð,Ð
-code e,E; é,É; è,È;ê,Ê;ë,Ë
-code f,F
-code 83
-code g,G
-code h,H
-code i,I;í,Í;ì,Ì;î,Î;ï,Ï
-code j,J
-code k,K
-code l,L
-code m,M
-code n,N;ñ,Ñ
-code o,O,,º;;ó,Ó;ò,Ò;ô,Ô;ö,Ö;õ,Õ;;ø,Ø
-code p,P
-code q,Q
-code r,R
-code s,S;;; 9a,8a
-code t,T,,99
-code u,U;ú,Ú;ù,Ù;û,Û;ü,Ü
-code v,V
-code w,W
-code x,X
-code y,Y;ý,Ý;ÿ,9f
-code z,Z
-code þ,Þ
-code flags=0 µ
-
-expand æ to a e
-expand Æ to A E
-
-expand ß to s s
-expand 85 to . . .
-expand 9c to o e
-expand 8c to O E
-expand ½ to 1 / 2
-expand ¼ to 1 / 4
-expand ¾ to 3 / 4
+characters
 
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ¡
+ < ?
+ < ¿
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < §
+ < ¶
+ < @
+ < *
+ < /
+ < \
+ < &
+ < 0023
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ´
+ < ^
+ < ¯
+ < ¨
+ < ¸
+ < ˆ
+ < °
+ < ©
+ < ®
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~ ; ˜
+ < ¤
+ < ¢
+ < $
+ < £
+ < ¥
+ < €
+ < 0
+ < 1,¹
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,ª,A ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,Ã
+ < b,B
+ < c,C ; ç,Ç
+ < d,D ; ð,Ð
+ < e,E ; é,É ; è,È ; ê,Ê ; ë,Ë
+ < f,F
+ < ƒ
+ < g,G
+ < h,H
+ < i,I ; í,Í ; ì,Ì ; î,Î ; ï,Ï
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N ; ñ,Ñ
+ < o,º,O ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø
+ < p,P
+ < q,Q
+ < r,R
+ < s,S ; š,Š
+ < t,T
+ < u,U ; ú,Ú ; ù,Ù ; û,Û ; ü,Ü
+ < v,V
+ < w,W
+ < x,X
+ < y,Y ; ý,Ý ; ÿ,Ÿ
+ < z,Z ; ž,Ž
+ < þ,Þ
+ < µ
+expand … to  . . .
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand æ to  a e
+expand Æ to  A E
+expand œ to  o e
+expand Πto  O E
+expand ß to  s s
+expand ™ to  T M
diff --git a/resources/sort/cp1253.txt b/resources/sort/cp1253.txt
new file mode 100644
index 0000000..8edc3f0
--- /dev/null
+++ b/resources/sort/cp1253.txt
@@ -0,0 +1,142 @@
+codepage 1253
+id1 13
+id2 1
+description "Greek sort"
+
+characters
+
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < ―
+ < 002c
+ < 003b
+ < :
+ < !
+ < ?
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ΄
+ < ^
+ < ¨ ; ΅
+ < §
+ < ¶
+ < ©
+ < ®
+ < °
+ < +
+ < ±
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~
+ < ¤
+ < $
+ < £
+ < ¥
+ < €
+ < 0
+ < 1
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A
+ < b,B
+ < c,C
+ < d,D
+ < e,E
+ < f,F
+ < ƒ
+ < g,G
+ < h,H
+ < i,I
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N
+ < o,O
+ < p,P
+ < q,Q
+ < r,R
+ < s,S
+ < t,T
+ < u,U
+ < v,V
+ < w,W
+ < x,X
+ < y,Y
+ < z,Z
+ < α,Α ; ά,Ά
+ < β,Β
+ < γ,Γ
+ < δ,Δ
+ < ε,Ε ; έ,Έ
+ < ζ,Ζ
+ < η,Η ; ή,Ή
+ < θ,Θ
+ < ι,Ι ; ί,Ί ; ϊ,Ϊ ; ΐ
+ < κ,Κ
+ < λ,Λ
+ < μ,µ,Μ
+ < ν,Ν
+ < ξ,Ξ
+ < ο,Ο ; ό,Ό
+ < π,Π
+ < ρ,Ρ
+ < σ,ς,Σ
+ < τ,Τ
+ < υ,Υ ; ύ,Ύ ; ϋ,Ϋ ; ΰ
+ < φ,Φ
+ < χ,Χ
+ < ψ,Ψ
+ < ω,Ω ; ώ,Ώ
+expand … to  . . .
+expand ½ to  1 / 2
+expand ™ to  T M
diff --git a/resources/sort/cp1254.txt b/resources/sort/cp1254.txt
new file mode 100644
index 0000000..97ad517
--- /dev/null
+++ b/resources/sort/cp1254.txt
@@ -0,0 +1,133 @@
+codepage 1254
+id1 14
+id2 1
+description "Turkish sort"
+
+characters
+= 0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ¡
+ < ?
+ < ¿
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ´
+ < ^
+ < ¯
+ < ¨
+ < ¸
+ < §
+ < ¶
+ < ©
+ < ®
+ < ˆ
+ < °
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~ ; ˜
+ < ¤
+ < ¢
+ < $
+ < £
+ < ¥
+ < €
+ < 0
+ < 1,¹
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,ª,A ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,Ã
+ < b,B
+ < c,C ; ç,Ç
+ < d,D
+ < e,E ; é,É ; è,È ; ê,Ê ; ë,Ë
+ < f,F
+ < ƒ
+ < g,G ; ğ,Ğ
+ < h,H
+ < i,I ; í,Í ; ì,Ì ; î,Î ; ï,Ï ; İ
+ < ı
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N ; ñ,Ñ
+ < o,º,O ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø
+ < p,P
+ < q,Q
+ < r,R
+ < s,S ; š,Š ; ş,Ş
+ < t,T
+ < u,U ; ú,Ú ; ù,Ù ; û,Û ; ü,Ü
+ < v,V
+ < w,W
+ < x,X
+ < y,Y ; ÿ,Ÿ
+ < z,Z
+ < µ
+expand … to  . . .
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand æ to  a e
+expand Æ to  A E
+expand œ to  o e
+expand Πto  O E
+expand ß to  s s
+expand ™ to  T M
diff --git a/resources/sort/cp1255.txt b/resources/sort/cp1255.txt
new file mode 100644
index 0000000..c0a2b72
--- /dev/null
+++ b/resources/sort/cp1255.txt
@@ -0,0 +1,158 @@
+codepage 1255
+id1 15
+id2 1
+description "Hebrew sort"
+
+characters
+
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad=05bd=200e=200f,0001,0002,0003,0004,0005,0006,0007 ; 05b0 ; 05b1 ; 05b2 ; 05b3 ; 05b4 ; 05b5 ; 05b6 ; 05b7 ; 05b8 ; 05b9 ; 05bb ; 05c2 ; 05c1 ; 05bc ; 05bf
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ¡
+ < ?
+ < ¿
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < ־
+ < ׀
+ < ׃
+ < ׳
+ < ״
+ < `
+ < ´
+ < ^
+ < ¯
+ < ¨
+ < ¸
+ < §
+ < ¶
+ < ©
+ < ®
+ < ˆ
+ < °
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~ ; ˜
+ < ¢
+ < $
+ < £
+ < ¥
+ < ₪
+ < €
+ < 0
+ < 1,¹
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A
+ < b,B
+ < c,C
+ < d,D
+ < e,E
+ < f,F
+ < ƒ
+ < g,G
+ < h,H
+ < i,I
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N
+ < o,O
+ < p,P
+ < q,Q
+ < r,R
+ < s,S
+ < t,T
+ < u,U
+ < v,V
+ < w,W
+ < x,X
+ < y,Y
+ < z,Z
+ < µ
+ < א
+ < ב
+ < ג
+ < ד
+ < ה
+ < ו
+ < ז
+ < ח
+ < ט
+ < י
+ < כ,ך
+ < ל
+ < מ,ם
+ < נ,ן
+ < ס
+ < ע
+ < פ,ף
+ < צ,ץ
+ < ק
+ < ר
+ < ש
+ < ת
+expand … to  . . .
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand ™ to  T M
+expand װ to  ו ו
+expand ױ to  ו י
+expand ײ to  י י
diff --git a/resources/sort/cp1256.txt b/resources/sort/cp1256.txt
index b80f704..e2e8aee 100644
--- a/resources/sort/cp1256.txt
+++ b/resources/sort/cp1256.txt
@@ -2,114 +2,180 @@
 codepage 1256
 id1 9
 id2 1
-description "Arabic"
+description "Arabic sort"
 
-code pos2=0 pos3=8 01, 02, 03, 04, 05, 06, 07
+characters
 
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=200c=200d=00ad=ـ=200e=200f,0001,0002,0003,0004,0005,0006,0007 ; 064b ; 064c ; 064d ; 064e ; 064f ; 0650 ; 0651 ; 0652
+< 0009
+< 000a
+< 000b
+< 000c
+< 000d
+< 0020,00a0
+< _
+< -
+< –
+< —
+< 002c
+< ،
+< 003b
+< ؛
+< :
+< !
+< ?
+< ؟
+< .
+< ·
+< '
+< ‘
+< ’
+< ‚
+< ‹
+< ›
+< "
+< “
+< ”
+< „
+< «
+< »
+< (
+< )
+< [
+< ]
+< {
+< }
+< @
+< *
+< /
+< \
+< &
+< #
+< %
+< ‰
+< †
+< ‡
+< •
+< `
+< ´
+< ^
+< ¯
+< ¨
+< ¸
+< §
+< ¶
+< ©
+< ®
+< ˆ
+< °
+< +
+< ±
+< ÷
+< ×
+< 003c
+< 003d
+< >
+< ¬
+< |
+< ¦
+< ~
+< ¤
+< ¢
+< $
+< £
+< ¥
+< €
+< 0
+< 1,¹
+< 2,²
+< 3,³
+< 4
+< 5
+< 6
+< 7
+< 8
+< 9
+< a,A ; à ; â
+< b,B
+< c,C ; ç
+< d,D
+< e,E ; é ; è ; ê ; ë
+< f,F
+< ƒ
+< g,G
+< h,H
+< i,I ; î ; ï
+< j,J
+< k,K
+< l,L
+< m,M
+< n,N
+< o,O ; ô
+< p,P
+< q,Q
+< r,R
+< s,S
+< t,T
+< u,U ; ù ; û ; ü
+< v,V
+< w,W
+< x,X
+< y,Y
+< z,Z
+< µ
+< ء
+< آ
+< أ
+< ؤ
+< إ
+< ئ
+< ا
+< ب
+< پ
+< ة
+< ت
+< ث
+< ٹ
+< ج
+< چ
+< ح
+< خ
+< د
+< ذ
+< ڈ
+< ر
+< ز
+< ڑ
+< ژ
+< س
+< ش
+< ص
+< ض
+< ط
+< ظ
+< ع
+< غ
+< ف
+< ق
+< ك
+< ک
+< گ
+< ل
+< م
+< ن
+< ں
+< ه
+< ھ
+< ہ
+< و
+< ى
+< ي
+< ے
 
-code 85
-code 20 < a0 < 09 < 0a < 0b < 0c < 0d
-code !
-code "
-code 23
-code $
-code %
-code &
-code (
-code )
-code *
-code ,
-code .
-code /
-code :
-code ;
-code ?
-code @
-code [
-code \
-code ]
-code ^ 88
-code `
-code {
-code |
-code }
-code ~
-code a6
-code a8
-code af
-code b4
-code b8
-code a1
-code ba
-code bf
-code 91
-code 92
-code 82
-code 93
-code 94
-code 84
-code 8b
-code 9b
-code _
-code <
-code =
-code >
-code ±
-code ab
-code bb
-code d7
-code f7
-code a2
-code a3
-code a4
-code a5
-code a7
-code a9 < ac < ae < b0 < b5 < b6 < b7 < 86 < 87 < 95 < 85 < 89
-code 80
-code 0 < bc < bd < be 
-code 1,¹
-code 2,²
-code 3,³
-code 4
-code 5
-code 6
-code 7
-code 8
-code 9
-code a,A; e0; e2
-code b,B
-code c,C; e7
-code d,D
-code e,E; e9; e8; ea; eb
-code f,F; 83
-code g,G
-code h,H
-code i,I; ee; ef
-code j,J
-code k,K
-code l,L
-code m,M
-code n,N
-code o,O; f4
-code 9c,8c
-code p,P
-code q,Q
-code r,R
-code s,S
-code t,T
-code 99
-code u,U; f9; fb; fc
-code v,V
-code w,W
-code x,X
-code y,Y
-code z,Z
-code c1; c4; c6
-code c7; c2; c3; c5
-code c8 < 81
-code c9, ca
-code cb < cc < 8d < cd < ce < cf < d0 < d1 < d2 < 8e < d3 < d4
-code d5 < d6 < d8 < d9 < da < db < dd < de < df < 90 < e1 < e3
-code e4 < e5 < e6
-code ec; ed
-code 8a < 8f < 9a < 98 < 9f < aa < c0 < ff < f8
+expand … to  . . .
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand œ to  o e
+expand Πto  O E
+expand ™ to  T M
diff --git a/resources/sort/cp1257.txt b/resources/sort/cp1257.txt
new file mode 100644
index 0000000..5f89be7
--- /dev/null
+++ b/resources/sort/cp1257.txt
@@ -0,0 +1,129 @@
+codepage 1257
+id1 17
+id2 1
+description "Latin Baltic sort"
+
+characters
+
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ?
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ´
+ < ^
+ < ¯
+ < ¨
+ < ¸
+ < §
+ < ¶
+ < ©
+ < ®
+ < ˇ
+ < °
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~
+ < ¤
+ < ¢
+ < $
+ < £
+ < €
+ < 0
+ < 1,¹
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,A ; å,Å ; ä,Ä ; ą,Ą ; ā,Ā
+ < b,B
+ < c,C ; ć,Ć ; č,Č
+ < d,D
+ < e,E ; é,É ; ė,Ė ; ę,Ę ; ē,Ē
+ < f,F
+ < g,G ; ģ,Ģ
+ < h,H
+ < i,I ; į,Į ; ī,Ī
+ < j,J
+ < k,K ; ķ,Ķ
+ < l,L ; ļ,Ļ ; ł,Ł
+ < m,M
+ < n,N ; ń,Ń ; ņ,Ņ
+ < o,O ; ó,Ó ; ö,Ö ; õ,Õ ; ø,Ø ; ō,Ō
+ < p,P
+ < q,Q
+ < r,R ; ŗ,Ŗ
+ < s,S ; ś,Ś ; š,Š
+ < t,T
+ < u,U ; ü,Ü ; ų,Ų ; ū,Ū
+ < v,V
+ < w,W
+ < x,X
+ < y,Y
+ < z,Z ; ź,Ź ; ž,Ž ; ż,Ż
+ < µ
+expand … to  . . .
+expand ˙ to  ¨ `
+expand ˛ to  § 0020
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand æ to  a e
+expand Æ to  A E
+expand ß to  s s
+expand ™ to  T M
diff --git a/resources/sort/cp1258.txt b/resources/sort/cp1258.txt
new file mode 100644
index 0000000..3e770bc
--- /dev/null
+++ b/resources/sort/cp1258.txt
@@ -0,0 +1,134 @@
+codepage 1258
+id 18
+id 1
+description "Vietnamese sort"
+
+characters
+
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad,0001,0002,0003,0004,0005,0006,0007 ; 0301 ; 0300 ; 0303 ; 0309 ; 0323
+ < 0009
+ < 000a
+ < 000b
+ < 000c
+ < 000d
+ < 0020,00a0
+ < _
+ < -
+ < –
+ < —
+ < 002c
+ < 003b
+ < :
+ < !
+ < ¡
+ < ?
+ < ¿
+ < .
+ < ·
+ < '
+ < ‘
+ < ’
+ < ‚
+ < ‹
+ < ›
+ < "
+ < “
+ < ”
+ < „
+ < «
+ < »
+ < (
+ < )
+ < [
+ < ]
+ < {
+ < }
+ < @
+ < *
+ < /
+ < \
+ < &
+ < #
+ < %
+ < ‰
+ < †
+ < ‡
+ < •
+ < `
+ < ´
+ < ^
+ < ¯
+ < ¨
+ < ¸
+ < §
+ < ¶
+ < ©
+ < ®
+ < ˆ
+ < °
+ < +
+ < ±
+ < ÷
+ < ×
+ < 003c
+ < 003d
+ < >
+ < ¬
+ < |
+ < ¦
+ < ~ ; ˜
+ < ¤
+ < ¢
+ < $
+ < £
+ < ¥
+ < ₫
+ < €
+ < 0
+ < 1,¹
+ < 2,²
+ < 3,³
+ < 4
+ < 5
+ < 6
+ < 7
+ < 8
+ < 9
+ < a,ª,A ; á,Á ; à,À ; ă,Ă ; â, ; å,Å ; ä,Ä
+ < b,B
+ < c,C ; ç,Ç
+ < d,D ; đ,Đ
+ < e,E ; é,É ; è,È ; ê,Ê ; ë,Ë
+ < f,F
+ < ƒ
+ < g,G
+ < h,H
+ < i,I ; í,Í ; î,Î ; ï,Ï
+ < j,J
+ < k,K
+ < l,L
+ < m,M
+ < n,N ; ñ,Ñ
+ < o,º,O ; ó,Ó ; ô,Ô ; ö,Ö ; ø,Ø ; ơ,Ơ
+ < p,P
+ < q,Q
+ < r,R
+ < s,S
+ < t,T
+ < u,U ; ú,Ú ; ù,Ù ; û,Û ; ü,Ü ; ư,Ư
+ < v,V
+ < w,W
+ < x,X
+ < y,Y ; ÿ,Ÿ
+ < z,Z
+ < µ
+expand … to  . . .
+expand ¼ to  1 / 4
+expand ½ to  1 / 2
+expand ¾ to  3 / 4
+expand æ to  a e
+expand Æ to  A E
+expand œ to  o e
+expand Πto  O E
+expand ß to  s s
+expand ™ to  T M
diff --git a/resources/sort/cp65001.txt b/resources/sort/cp65001.txt
new file mode 100644
index 0000000..e5f1bab
--- /dev/null
+++ b/resources/sort/cp65001.txt
@@ -0,0 +1,11135 @@
+codepage 65001
+id1 19
+id2 4
+description "Unicode sort"
+multi
+
+characters
+
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=0080=0081=0082=0083=0084=0086=0087=0088=0089=008a=008b=008c=008d=008e=008f=0090=0091=0092=0093=0094=0095=0096=0097=0098=0099=009a=009b=009c=009d=009e=009f=00ad=034f=҈=҉=0591=0592=0593=0594=0595=0596=0597=0598=0599=059a=059b=059c=059d=059e=059f=05a0=05a1=05a2=05a3=05a4=05a5=05a6=05a7=05a8=05a9=05aa=05ab=05ac=05ad=05ae=05af=05bd=05c4=05c5=0600=0601=0602=0603=0604=0610=0611=0612=0613=0614=06 [...]
+< 0009
+< 000a
+< 000b
+< 000c
+< 000d
+< 0085
+< 2028
+< 2029
+< 0020,3000,1680=2000=2001=2002=2003=2004=2005=2006=2008=2009=200a=205f,00a0=2007=202f
+< ‾,﹉=﹊=﹋=﹌
+< _,_,﹍=﹎=﹏,︳=︴
+< ‗
+< -,-,﹣
+< ֊
+< ᐀
+< ᭠
+< ᠆
+< ᠇
+< ‐,‑
+< ‒
+< –,︲
+< —,﹘,︱
+< ―
+< 2e3a
+< 2e3b
+< ⁓
+< ⸗
+< 〜
+< 〰
+< ゠
+< ・,・
+< 002c,,,﹐,︐
+< 2e34
+< 2e32
+< ՝
+< ،
+< ؍
+< ٫
+< ٬
+< ߸
+< ᠂
+< ᠈
+< ꓾
+< ꘍
+< ꛵
+< 、,﹑,、,︑
+< ﹅
+< ﹆
+< 003b=;,;,﹔,︔
+< ؛
+< ⁏
+< 2e35
+< ꛶
+< :,:,﹕,︓
+< ։
+< ؞
+< ܃
+< ܄
+< ܅
+< ܆
+< ܇
+< ܈
+< ࠰
+< ࠱
+< ࠲
+< ࠳
+< ࠴
+< ࠵
+< ࠶
+< ࠷
+< ࠸
+< ࠹
+< ࠺
+< ࠻
+< ࠼
+< ࠽
+< ࠾
+< ፡
+< ፣
+< ፤
+< ፥
+< ፦
+< ᠄
+< ᠅
+< ༔
+< ៖
+< ᭝
+< ꧇
+< ᛫
+< ᛬
+< ᛭
+< ꛴
+< !,!,﹗,︕
+< ¡
+< ՜
+< ߹
+< ᥄
+< ?,?,﹖,︖
+< ¿
+< ⸮
+< ՞
+< ؟
+< ܉
+< ፧
+< ᥅
+< ⳺
+< ⳻
+< ꘏
+< ꛷
+< aaf1
+< ‽
+< ⸘
+< .,.,․,﹒
+< ᠁
+< ۔
+< ܁
+< ܂
+< ።
+< ᠃
+< ᠉
+< ᙮
+< ᭜
+< ⳹
+< ⳾
+< ⸰
+< ꓿
+< ꘎
+< ꛳
+< 。,。,︒
+< ·=·
+< ⸱
+< 2e33
+< ।
+< ॥
+< ꣎
+< ꣏
+< ᰻
+< ᰼
+< ꡶
+< ꡷
+< ᜵
+< ᜶
+< ꤯
+< ၊
+< ။
+< ។
+< ៕
+< ᪨
+< ᪩
+< ᪪
+< ᪫
+< ᭞
+< ᭟
+< ꧈
+< ꧉
+< ꩝
+< ꩞
+< ꩟
+< aaf0
+< ꯫
+< ᱾
+< ᱿
+< ܀
+< ߷
+< ჻
+< ፠
+< ፨
+< ᨞
+< ᨟
+< ᭚
+< ᭛
+< ꧁
+< ꧂
+< ꧃
+< ꧄
+< ꧅
+< ꧆
+< ꧊
+< ꧋
+< ꧌
+< ꧍
+< ꛲
+< ꥟
+< ⁕
+< ⁖
+< ⁘
+< ⁙
+< ⁚
+< ⁛
+< ⁜
+< ⁝
+< ⁞
+< ⸪
+< ⸫
+< ⸬
+< ⸭
+< ⳼
+< ⳿
+< ⸙
+< ','
+< ‘
+< ’
+< ‚
+< ‛
+< ‹
+< ›
+< ","
+< “
+< ”
+< „
+< ‟
+< 〝
+< 〞
+< 〟
+< «
+< »
+< (,(,﹙,⁽,₍,︵
+< ),),﹚,⁾,₎,︶
+< [,[,﹇
+< ],],﹈
+< {,{,﹛,︷
+< },},﹜,︸
+< ༺
+< ༻
+< ༼
+< ༽
+< ᚛
+< ᚜
+< ⁅
+< ⁆
+< ⌈
+< ⌉
+< ⌊
+< ⌋
+< ⧼
+< ⧽
+< ⦃
+< ⦄
+< ⦅,⦅
+< ⦆,⦆
+< ⦇
+< ⦈
+< ⦉
+< ⦊
+< ⦋
+< ⦌
+< ⦍
+< ⦎
+< ⦏
+< ⦐
+< ⦑
+< ⦒
+< ⦓
+< ⦔
+< ⦕
+< ⦖
+< ⦗
+< ⦘
+< ⟅
+< ⟆
+< ⟦
+< ⟧
+< ⟨
+< ⟩
+< ⟪
+< ⟫
+< ⟬
+< ⟭
+< ⟮
+< ⟯
+< ❨
+< ❩
+< ❪
+< ❫
+< ❬
+< ❭
+< ❮
+< ❯
+< ❰
+< ❱
+< ❲
+< ❳
+< ❴
+< ❵
+< ⸂
+< ⸃
+< ⸄
+< ⸅
+< ⸉
+< ⸊
+< ⸌
+< ⸍
+< ⸜
+< ⸝
+< ⸠
+< ⸡
+< ⸢
+< ⸣
+< ⸤
+< ⸥
+< ⸦
+< ⸧
+< ⸨
+< ⸩
+< 〈=〈,︿
+< 〉=〉,﹀
+< 《,︽
+< 》,︾
+< 「,「,﹁
+< 」,」,﹂
+< 『,﹃
+< 』,﹄
+< 【,︻
+< 】,︼
+< 〔,﹝,︹
+< 〕,﹞,︺
+< 〖,︗
+< 〗,︘
+< 〘
+< 〙
+< 〚
+< 〛
+< ﴾
+< ﴿
+< ‖
+< ⧘
+< ⧙
+< ⧚
+< ⧛
+< §
+< 2e39
+< ¶
+< ⁋
+< @,@,﹫
+< *,*,﹡
+< ⁎
+< ⁑
+< ٭
+< ꙳
+< /,/
+< \,\,﹨
+< &,&,﹠
+< ⁊
+< 0023,#,﹟
+< %,%,﹪
+< ٪
+< ‰
+< ؉
+< ‱
+< ؊
+< †
+< ‡
+< 2e36
+< 2e37
+< 2e38
+< •
+< ‣
+< ‧
+< ⁃
+< ⁌
+< ⁍
+< ′
+< ‵
+< 〃
+< 〽
+< ‸
+< ※
+< ‿
+< ⁔
+< ⁀
+< ⁐
+< ⁁
+< ⁂
+< ⸀
+< ⸁
+< ⸆
+< ⸇
+< ⸈
+< ⸋
+< ⸎
+< ⸏
+< ⸐
+< ⸑
+< ⸒
+< ⸓
+< ⸔
+< ⸕
+< ⸖
+< ⸚
+< ⸛
+< ⸞
+< ⸟
+< ꙾
+< ՚
+< ՛
+< ՟
+< ־
+< ׀
+< ׃
+< ׆
+< ׳
+< ״
+< ܊
+< ܋
+< ܌
+< ܍
+< ࡞
+< ᠀
+< ॰
+< ꣸
+< ꣹
+< ꣺
+< 0af0
+< ෴
+< ๏
+< ๚
+< ๛
+< ꫞
+< ꫟
+< ༄
+< ༅
+< ༆
+< ༇
+< ༈
+< ༉
+< ༊
+< ࿐
+< ࿑
+< ་,༌
+< །
+< ༎
+< ༏
+< ༐
+< ༑
+< ༒
+< ྅
+< ࿒
+< ࿓
+< ࿔
+< ࿙
+< ࿚
+< ᰽
+< ᰾
+< ᰿
+< ၌
+< ၍
+< ၎
+< ၏
+< ៘
+< ៙
+< ៚
+< ᪠
+< ᪡
+< ᪢
+< ᪣
+< ᪤
+< ᪥
+< ᪦
+< ᪬
+< ᪭
+< ᙭
+< 1cc0
+< 1cc1
+< 1cc2
+< 1cc3
+< 1cc4
+< 1cc5
+< 1cc6
+< 1cc7
+< ⵰
+< ꡴
+< ꡵
+< ᯼
+< ᯽
+< ᯾
+< ᯿
+< ꤮
+< ꧞
+< ꧟
+< ꩜
+< `=`,`
+< ´=΄=´
+< ^,^
+< ¯, ̄
+< ˘
+< ˙
+< ¨ ; ΅=΅ ; ῭ ; ῁
+< ˚
+< ˝
+< ¸
+< ˛
+< ᾽=᾿ ; ῎ ; ῍ ; ῏
+< ῾ ; ῞ ; ῝ ; ῟
+< ῀
+< ゛
+< ゜
+< ʹ=ʹ
+< ͵
+< ʺ
+< ˂
+< ˃
+< ˄
+< ˅
+< ˆ
+< ˇ
+< ˈ
+< ˉ
+< ˊ
+< ˋ
+< ˌ
+< ˍ
+< ˎ
+< ˏ
+< ˒
+< ˓
+< ˔
+< ˕
+< ˖
+< ˗
+< ˞
+< ˟
+< ˥
+< ˦
+< ˧
+< ˨
+< ˩
+< ˪
+< ˫
+< ˬ
+< ˭
+< ˯
+< ˰
+< ˱
+< ˲
+< ˳
+< ˴
+< ˵
+< ˶
+< ˷
+< ˸
+< ˹
+< ˺
+< ˻
+< ˼
+< ˽
+< ˾
+< ˿
+< ᎐
+< ᎑
+< ᎒
+< ᎓
+< ᎔
+< ᎕
+< ᎖
+< ᎗
+< ᎘
+< ᎙
+< ꜀
+< ꜁
+< ꜂
+< ꜃
+< ꜄
+< ꜅
+< ꜆
+< ꜇
+< ꜈
+< ꜉
+< ꜊
+< ꜋
+< ꜌
+< ꜍
+< ꜎
+< ꜏
+< ꜐
+< ꜑
+< ꜒
+< ꜓
+< ꜔
+< ꜕
+< ꜖
+< ꜗ
+< ꜘ
+< ꜙ
+< ꜚ
+< ꜛ
+< ꜜ
+< ꜝ
+< ꜞ
+< ꜟ
+< ꜠
+< ꜡
+< ꞈ
+< ꞉
+< ꞊
+< °
+< ҂
+< ؈
+< ؎
+< ؏
+< ۞
+< ۩
+< ﷽
+< ﮲
+< ﮳
+< ﮴
+< ﮵
+< ﮶
+< ﮷
+< ﮸
+< ﮹
+< ﮺
+< ﮻
+< ﮼
+< ﮽
+< ﮾
+< ﮿
+< ﯀
+< ﯁
+< ߶
+< ৺
+< ୰
+< ௳
+< ௴
+< ௵
+< ௶
+< ௷
+< ௸
+< ௺
+< ౿
+< ൹
+< ꠨
+< ꠩
+< ꠪
+< ꠫
+< ꠶
+< ꠷
+< ꠹
+< ༁
+< ༂
+< ༃
+< ༓
+< ༕
+< ༖
+< ༗
+< ༚
+< ༛
+< ༜
+< ༝
+< ༞
+< ༟
+< ༴
+< ༶
+< ༸
+< ྾
+< ྿
+< ࿀
+< ࿁
+< ࿂
+< ࿃
+< ࿄
+< ࿅
+< ࿇
+< ࿈
+< ࿉
+< ࿊
+< ࿋
+< ࿌
+< ࿎
+< ࿏
+< ࿕
+< ࿖
+< ࿗
+< ࿘
+< ᥀
+< ႞
+< ႟
+< ꩷
+< ꩸
+< ꩹
+< ᧠
+< ᧡
+< ᧢
+< ᧣
+< ᧤
+< ᧥
+< ᧦
+< ᧧
+< ᧨
+< ᧩
+< ᧪
+< ᧫
+< ᧬
+< ᧭
+< ᧮
+< ᧯
+< ᧰
+< ᧱
+< ᧲
+< ᧳
+< ᧴
+< ᧵
+< ᧶
+< ᧷
+< ᧸
+< ᧹
+< ᧺
+< ᧻
+< ᧼
+< ᧽
+< ᧾
+< ᧿
+< ᭡
+< ᭢
+< ᭣
+< ᭤
+< ᭥
+< ᭦
+< ᭧
+< ᭨
+< ᭩
+< ᭪
+< ᭴
+< ᭵
+< ᭶
+< ᭷
+< ᭸
+< ᭹
+< ᭺
+< ᭻
+< ᭼
+< ©
+< ®
+< ℄
+< ℈
+< ℔
+< ℗
+< ℘
+< ℞
+< ℟
+< ℣
+< ℥
+< ℧
+< ℩
+< ℮
+< ℺
+< ⅁
+< ⅂
+< ⅃
+< ⅄
+< ⅊
+< ⅌
+< ⅏
+< ←,← ; ↚
+< →,→ ; ↛
+< ↑,↑
+< ↓,↓
+< ↔ ; ↮
+< ↕
+< ↖
+< ↗
+< ↘
+< ↙
+< ↜
+< ↝
+< ↞
+< ↟
+< ↠
+< ↡
+< ↢
+< ↣
+< ↤
+< ↥
+< ↦
+< ↧
+< ↨
+< ↩
+< ↪
+< ↫
+< ↬
+< ↭
+< ↯
+< ↰
+< ↱
+< ↲
+< ↳
+< ↴
+< ↵
+< ↶
+< ↷
+< ↸
+< ↹
+< ↺
+< ↻
+< ↼
+< ↽
+< ↾
+< ↿
+< ⇀
+< ⇁
+< ⇂
+< ⇃
+< ⇄
+< ⇅
+< ⇆
+< ⇇
+< ⇈
+< ⇉
+< ⇊
+< ⇋
+< ⇌
+< ⇐ ; ⇍
+< ⇑
+< ⇒ ; ⇏
+< ⇓
+< ⇔ ; ⇎
+< ⇕
+< ⇖
+< ⇗
+< ⇘
+< ⇙
+< ⇚
+< ⇛
+< ⇜
+< ⇝
+< ⇞
+< ⇟
+< ⇠
+< ⇡
+< ⇢
+< ⇣
+< ⇤
+< ⇥
+< ⇦
+< ⇧
+< ⇨
+< ⇩
+< ⇪
+< ⇫
+< ⇬
+< ⇭
+< ⇮
+< ⇯
+< ⇰
+< ⇱
+< ⇲
+< ⇳
+< ⇴
+< ⇵
+< ⇶
+< ⇷
+< ⇸
+< ⇹
+< ⇺
+< ⇻
+< ⇼
+< ⇽
+< ⇾
+< ⇿
+< ∀
+< ∁
+< ∂
+< ∃ ; ∄
+< ∅
+< ∆
+< ∇
+< ∈ ; ∉
+< ∊
+< ∋ ; ∌
+< ∍
+< ϶
+< ∎
+< ∏
+< ∐
+< ∑,⅀
+< +,+,﬩,﹢,⁺,₊
+< ±
+< ÷
+< ×
+< 003c,<,﹤ ; ≮
+< 003d,=,﹦,⁼,₌ ; ≠
+< >,>,﹥ ; ≯
+< ¬,¬
+< |,|
+< ¦,¦
+< ~,~ ; ˜
+< −,⁻,₋
+< ⁒
+< ∓
+< ∔
+< ∕
+< ⁄
+< ∖
+< ∗
+< ∘
+< ∙
+< √
+< ∛
+< ؆
+< ∜
+< ؇
+< ∝
+< ∞
+< ∟
+< ∠
+< ∡
+< ∢
+< ∣ ; ∤
+< ∥ ; ∦
+< ∧
+< ∨
+< ∩
+< ∪
+< ∫
+< ∮
+< ∱
+< ∲
+< ∳
+< ∴
+< ∵
+< ∶
+< ∷
+< ∸
+< ∹
+< ∺
+< ∻
+< ∼ ; ≁
+< ∽
+< ∾
+< ∿
+< ≀
+< ≂
+< ≃ ; ≄
+< ≅ ; ≇
+< ≆
+< ≈ ; ≉
+< ≊
+< ≋
+< ≌
+< ≍ ; ≭
+< ≎
+< ≏
+< ≐
+< ≑
+< ≒
+< ≓
+< ≔
+< ≕
+< ≖
+< ≗
+< ≘
+< ≙
+< ≚
+< ≛
+< ≜
+< ≝
+< ≞
+< ≟
+< ≡ ; ≢
+< ≣
+< ≤ ; ≰
+< ≥ ; ≱
+< ≦
+< ≧
+< ≨
+< ≩
+< ≪
+< ≫
+< ≬
+< ≲ ; ≴
+< ≳ ; ≵
+< ≶ ; ≸
+< ≷ ; ≹
+< ≺ ; ⊀
+< ≻ ; ⊁
+< ≼ ; ⋠
+< ≽ ; ⋡
+< ≾
+< ≿
+< ⊂ ; ⊄
+< ⊃ ; ⊅
+< ⊆ ; ⊈
+< ⊇ ; ⊉
+< ⊊
+< ⊋
+< ⊌
+< ⊍
+< ⊎
+< ⊏
+< ⊐
+< ⊑ ; ⋢
+< ⊒ ; ⋣
+< ⊓
+< ⊔
+< ⊕
+< ⊖
+< ⊗
+< ⊘
+< ⊙
+< ⊚
+< ⊛
+< ⊜
+< ⊝
+< ⊞
+< ⊟
+< ⊠
+< ⊡
+< ⊢ ; ⊬
+< ⊣
+< ⊤
+< ⊥
+< ⊦
+< ⊧
+< ⊨ ; ⊭
+< ⊩ ; ⊮
+< ⊪
+< ⊫ ; ⊯
+< ⊰
+< ⊱
+< ⊲ ; ⋪
+< ⊳ ; ⋫
+< ⊴ ; ⋬
+< ⊵ ; ⋭
+< ⊶
+< ⊷
+< ⊸
+< ⊹
+< ⊺
+< ⊻
+< ⊼
+< ⅋
+< ⊽
+< ⊾
+< ⊿
+< ⋀
+< ⋁
+< ⋂
+< ⋃
+< ⋄
+< ⋅
+< ⋆
+< ⋇
+< ⋈
+< ⋉
+< ⋊
+< ⋋
+< ⋌
+< ⋍
+< ⋎
+< ⋏
+< ⋐
+< ⋑
+< ⋒
+< ⋓
+< ⋔
+< ⋕
+< ⋖
+< ⋗
+< ⋘
+< ⋙
+< ⋚
+< ⋛
+< ⋜
+< ⋝
+< ⋞
+< ⋟
+< ⋤
+< ⋥
+< ⋦
+< ⋧
+< ⋨
+< ⋩
+< ⋮
+< ⋯
+< ⋰
+< ⋱
+< ⋲
+< ⋳
+< ⋴
+< ⋵
+< ⋶
+< ⋷
+< ⋸
+< ⋹
+< ⋺
+< ⋻
+< ⋼
+< ⋽
+< ⋾
+< ⋿
+< ⌀
+< ⌁
+< ⌂
+< ⌃
+< ⌄
+< ⌅
+< ⌆
+< ⌇
+< ⌌
+< ⌍
+< ⌎
+< ⌏
+< ⌐
+< ⌑
+< ⌒
+< ⌓
+< ⌔
+< ⌕
+< ⌖
+< ⌗
+< ⌘
+< ⌙
+< ⌚
+< ⌛
+< ⌜
+< ⌝
+< ⌞
+< ⌟
+< ⌠
+< ⌡
+< ⌢
+< ⌣
+< ⌤
+< ⌥
+< ⌦
+< ⌧
+< ⌨
+< ⌫
+< ⌬
+< ⌭
+< ⌮
+< ⌯
+< ⌰
+< ⌱
+< ⌲
+< ⌳
+< ⌴
+< ⌵
+< ⌶
+< ⌷
+< ⌸
+< ⌹
+< ⌺
+< ⌻
+< ⌼
+< ⌽
+< ⌾
+< ⌿
+< ⍀
+< ⍁
+< ⍂
+< ⍃
+< ⍄
+< ⍅
+< ⍆
+< ⍇
+< ⍈
+< ⍉
+< ⍊
+< ⍋
+< ⍌
+< ⍍
+< ⍎
+< ⍏
+< ⍐
+< ⍑
+< ⍒
+< ⍓
+< ⍔
+< ⍕
+< ⍖
+< ⍗
+< ⍘
+< ⍙
+< ⍚
+< ⍛
+< ⍜
+< ⍝
+< ⍞
+< ⍟
+< ⍠
+< ⍡
+< ⍢
+< ⍣
+< ⍤
+< ⍥
+< ⍦
+< ⍧
+< ⍨
+< ⍩
+< ⍪
+< ⍫
+< ⍬
+< ⍭
+< ⍮
+< ⍯
+< ⍰
+< ⍱
+< ⍲
+< ⍳
+< ⍴
+< ⍵
+< ⍶
+< ⍷
+< ⍸
+< ⍹
+< ⍺
+< ⍻
+< ⍼
+< ⍽
+< ⍾
+< ⍿
+< ⎀
+< ⎁
+< ⎂
+< ⎃
+< ⎄
+< ⎅
+< ⎆
+< ⎇
+< ⎈
+< ⎉
+< ⎊
+< ⎋
+< ⎌
+< ⎍
+< ⎎
+< ⎏
+< ⎐
+< ⎑
+< ⎒
+< ⎓
+< ⎔
+< ⎕
+< ⎖
+< ⎗
+< ⎘
+< ⎙
+< ⎚
+< ⎛
+< ⎜
+< ⎝
+< ⎞
+< ⎟
+< ⎠
+< ⎡
+< ⎢
+< ⎣
+< ⎤
+< ⎥
+< ⎦
+< ⎧
+< ⎨
+< ⎩
+< ⎪
+< ⎫
+< ⎬
+< ⎭
+< ⎮
+< ⎯
+< ⎰
+< ⎱
+< ⎲
+< ⎳
+< ⎴
+< ⎵
+< ⎶
+< ⎷
+< ⎸
+< ⎹
+< ⎺
+< ⎻
+< ⎼
+< ⎽
+< ⎾
+< ⎿
+< ⏀
+< ⏁
+< ⏂
+< ⏃
+< ⏄
+< ⏅
+< ⏆
+< ⏇
+< ⏈
+< ⏉
+< ⏊
+< ⏋
+< ⏌
+< ⏍
+< ⏎
+< ⏏
+< ⏐
+< ⏑
+< ⏒
+< ⏓
+< ⏔
+< ⏕
+< ⏖
+< ⏗
+< ⏘
+< ⏙
+< ⏚
+< ⏛
+< ⏜
+< ⏝
+< ⏞
+< ⏟
+< ⏠
+< ⏡
+< ⏢
+< ⏣
+< ⏤
+< ⏥
+< ⏦
+< ⏧
+< ⏨
+< ⏩
+< ⏪
+< ⏫
+< ⏬
+< ⏭
+< ⏮
+< ⏯
+< ⏰
+< ⏱
+< ⏲
+< ⏳
+< ␀
+< ␁
+< ␂
+< ␃
+< ␄
+< ␅
+< ␆
+< ␇
+< ␈
+< ␉
+< ␊
+< ␋
+< ␌
+< ␍
+< ␎
+< ␏
+< ␐
+< ␑
+< ␒
+< ␓
+< ␔
+< ␕
+< ␖
+< ␗
+< ␘
+< ␙
+< ␚
+< ␛
+< ␜
+< ␝
+< ␞
+< ␟
+< ␠
+< ␡
+< ␢
+< ␣
+< ␤
+< ␥
+< ␦
+< ⑀
+< ⑁
+< ⑂
+< ⑃
+< ⑄
+< ⑅
+< ⑆
+< ⑇
+< ⑈
+< ⑉
+< ⑊
+< ─
+< ━
+< │,│
+< ┃
+< ┄
+< ┅
+< ┆
+< ┇
+< ┈
+< ┉
+< ┊
+< ┋
+< ┌
+< ┍
+< ┎
+< ┏
+< ┐
+< ┑
+< ┒
+< ┓
+< └
+< ┕
+< ┖
+< ┗
+< ┘
+< ┙
+< ┚
+< ┛
+< ├
+< ┝
+< ┞
+< ┟
+< ┠
+< ┡
+< ┢
+< ┣
+< ┤
+< ┥
+< ┦
+< ┧
+< ┨
+< ┩
+< ┪
+< ┫
+< ┬
+< ┭
+< ┮
+< ┯
+< ┰
+< ┱
+< ┲
+< ┳
+< ┴
+< ┵
+< ┶
+< ┷
+< ┸
+< ┹
+< ┺
+< ┻
+< ┼
+< ┽
+< ┾
+< ┿
+< ╀
+< ╁
+< ╂
+< ╃
+< ╄
+< ╅
+< ╆
+< ╇
+< ╈
+< ╉
+< ╊
+< ╋
+< ╌
+< ╍
+< ╎
+< ╏
+< ═
+< ║
+< ╒
+< ╓
+< ╔
+< ╕
+< ╖
+< ╗
+< ╘
+< ╙
+< ╚
+< ╛
+< ╜
+< ╝
+< ╞
+< ╟
+< ╠
+< ╡
+< ╢
+< ╣
+< ╤
+< ╥
+< ╦
+< ╧
+< ╨
+< ╩
+< ╪
+< ╫
+< ╬
+< ╭
+< ╮
+< ╯
+< ╰
+< ╱
+< ╲
+< ╳
+< ╴
+< ╵
+< ╶
+< ╷
+< ╸
+< ╹
+< ╺
+< ╻
+< ╼
+< ╽
+< ╾
+< ╿
+< ▀
+< ▁
+< ▂
+< ▃
+< ▄
+< ▅
+< ▆
+< ▇
+< █
+< ▉
+< ▊
+< ▋
+< ▌
+< ▍
+< ▎
+< ▏
+< ▐
+< ░
+< ▒
+< ▓
+< ▔
+< ▕
+< ▖
+< ▗
+< ▘
+< ▙
+< ▚
+< ▛
+< ▜
+< ▝
+< ▞
+< ▟
+< ■,■
+< □
+< ▢
+< ▣
+< ▤
+< ▥
+< ▦
+< ▧
+< ▨
+< ▩
+< ▪
+< ▫
+< ▬
+< ▭
+< ▮
+< ▯
+< ▰
+< ▱
+< ▲
+< △
+< ▴
+< ▵
+< ▶
+< ▷
+< ▸
+< ▹
+< ►
+< ▻
+< ▼
+< ▽
+< ▾
+< ▿
+< ◀
+< ◁
+< ◂
+< ◃
+< ◄
+< ◅
+< ◆
+< ◇
+< ◈
+< ◉
+< ◊
+< ○,○
+< ◌
+< ◍
+< ◎
+< ●
+< ◐
+< ◑
+< ◒
+< ◓
+< ◔
+< ◕
+< ◖
+< ◗
+< ◘
+< ◙
+< ◚
+< ◛
+< ◜
+< ◝
+< ◞
+< ◟
+< ◠
+< ◡
+< ◢
+< ◣
+< ◤
+< ◥
+< ◦
+< ◧
+< ◨
+< ◩
+< ◪
+< ◫
+< ◬
+< ◭
+< ◮
+< ◯
+< ◰
+< ◱
+< ◲
+< ◳
+< ◴
+< ◵
+< ◶
+< ◷
+< ◸
+< ◹
+< ◺
+< ◻
+< ◼
+< ◽
+< ◾
+< ◿
+< ☀
+< ☁
+< ☂
+< ☃
+< ☄
+< ★
+< ☆
+< ☇
+< ☈
+< ☉
+< ☊
+< ☋
+< ☌
+< ☍
+< ☎
+< ☏
+< ☐
+< ☑
+< ☒
+< ☓
+< ☔
+< ☕
+< ☖
+< ☗
+< ☘
+< ☙
+< ☚
+< ☛
+< ☜
+< ☝
+< ☞
+< ☟
+< ☠
+< ☡
+< ☢
+< ☣
+< ☤
+< ☥
+< ☦
+< ☧
+< ☨
+< ☩
+< ☪
+< ☫
+< ☬
+< ☭
+< ☮
+< ☯
+< ☸
+< ☹
+< ☺
+< ☻
+< ☼
+< ☽
+< ☾
+< ☿
+< ♀
+< ♁
+< ♂
+< ♃
+< ♄
+< ♅
+< ♆
+< ♇
+< ♈
+< ♉
+< ♊
+< ♋
+< ♌
+< ♍
+< ♎
+< ♏
+< ♐
+< ♑
+< ♒
+< ♓
+< ♔
+< ♕
+< ♖
+< ♗
+< ♘
+< ♙
+< ♚
+< ♛
+< ♜
+< ♝
+< ♞
+< ♟
+< ♠
+< ♡
+< ♢
+< ♣
+< ♤
+< ♥
+< ♦
+< ♧
+< ♨
+< ♩
+< ♪
+< ♫
+< ♬
+< ♰
+< ♱
+< ♲
+< ♳
+< ♴
+< ♵
+< ♶
+< ♷
+< ♸
+< ♹
+< ♺
+< ♻
+< ♼
+< ♽
+< ♾
+< ♿
+< ⚀
+< ⚁
+< ⚂
+< ⚃
+< ⚄
+< ⚅
+< ⚆
+< ⚇
+< ⚈
+< ⚉
+< ⚐
+< ⚑
+< ⚒
+< ⚓
+< ⚔
+< ⚕
+< ⚖
+< ⚗
+< ⚘
+< ⚙
+< ⚚
+< ⚛
+< ⚜
+< ⚝
+< ⚞
+< ⚟
+< ⚠
+< ⚡
+< ⚢
+< ⚣
+< ⚤
+< ⚥
+< ⚦
+< ⚧
+< ⚨
+< ⚩
+< ⚪
+< ⚫
+< ⚬
+< ⚭
+< ⚮
+< ⚯
+< ⚰
+< ⚱
+< ⚲
+< ⚳
+< ⚴
+< ⚵
+< ⚶
+< ⚷
+< ⚸
+< ⚹
+< ⚺
+< ⚻
+< ⚼
+< ⚽
+< ⚾
+< ⚿
+< ⛀
+< ⛁
+< ⛂
+< ⛃
+< ⛄
+< ⛅
+< ⛆
+< ⛇
+< ⛈
+< ⛉
+< ⛊
+< ⛋
+< ⛌
+< ⛍
+< ⛎
+< ⛏
+< ⛐
+< ⛑
+< ⛒
+< ⛓
+< ⛔
+< ⛕
+< ⛖
+< ⛗
+< ⛘
+< ⛙
+< ⛚
+< ⛛
+< ⛜
+< ⛝
+< ⛞
+< ⛟
+< ⛠
+< ⛡
+< ⛢
+< ⛣
+< ⛤
+< ⛥
+< ⛦
+< ⛧
+< ⛨
+< ⛩
+< ⛪
+< ⛫
+< ⛬
+< ⛭
+< ⛮
+< ⛯
+< ⛰
+< ⛱
+< ⛲
+< ⛳
+< ⛴
+< ⛵
+< ⛶
+< ⛷
+< ⛸
+< ⛹
+< ⛺
+< ⛻
+< ⛼
+< ⛽
+< ⛾
+< ⛿
+< ✁
+< ✂
+< ✃
+< ✄
+< ✅
+< ✆
+< ✇
+< ✈
+< ✉
+< ✊
+< ✋
+< ✌
+< ✍
+< ✎
+< ✏
+< ✐
+< ✑
+< ✒
+< ✓
+< ✔
+< ✕
+< ✖
+< ✗
+< ✘
+< ✙
+< ✚
+< ✛
+< ✜
+< ✝
+< ✞
+< ✟
+< ✠
+< ✡
+< ✢
+< ✣
+< ✤
+< ✥
+< ✦
+< ✧
+< ✨
+< ✩
+< ✪
+< ✫
+< ✬
+< ✭
+< ✮
+< ✯
+< ✰
+< ✱
+< ✲
+< ✳
+< ✴
+< ✵
+< ✶
+< ✷
+< ✸
+< ✹
+< ✺
+< ✻
+< ✼
+< ✽
+< ✾
+< ✿
+< ❀
+< ❁
+< ❂
+< ❃
+< ❄
+< ❅
+< ❆
+< ❇
+< ❈
+< ❉
+< ❊
+< ❋
+< ❌
+< ❍
+< ❎
+< ❏
+< ❐
+< ❑
+< ❒
+< ❓
+< ❔
+< ❕
+< ❖
+< ❗
+< ❘
+< ❙
+< ❚
+< ❛
+< ❜
+< ❝
+< ❞
+< ❟
+< ❠
+< ❡
+< ❢
+< ❣
+< ❤
+< ❥
+< ❦
+< ❧
+< ➔
+< ➕
+< ➖
+< ➗
+< ➘
+< ➙
+< ➚
+< ➛
+< ➜
+< ➝
+< ➞
+< ➟
+< ➠
+< ➡
+< ➢
+< ➣
+< ➤
+< ➥
+< ➦
+< ➧
+< ➨
+< ➩
+< ➪
+< ➫
+< ➬
+< ➭
+< ➮
+< ➯
+< ➰
+< ➱
+< ➲
+< ➳
+< ➴
+< ➵
+< ➶
+< ➷
+< ➸
+< ➹
+< ➺
+< ➻
+< ➼
+< ➽
+< ➾
+< ➿
+< ⟀
+< ⟁
+< ⟂
+< ⟃
+< ⟄
+< ⟇
+< ⟈
+< ⟉
+< ⟊
+< 27cb
+< ⟌
+< 27cd
+< ⟎
+< ⟏
+< ⟐
+< ⟑
+< ⟒
+< ⟓
+< ⟔
+< ⟕
+< ⟖
+< ⟗
+< ⟘
+< ⟙
+< ⟚
+< ⟛
+< ⟜
+< ⟝
+< ⟞
+< ⟟
+< ⟠
+< ⟡
+< ⟢
+< ⟣
+< ⟤
+< ⟥
+< ⟰
+< ⟱
+< ⟲
+< ⟳
+< ⟴
+< ⟵
+< ⟶
+< ⟷
+< ⟸
+< ⟹
+< ⟺
+< ⟻
+< ⟼
+< ⟽
+< ⟾
+< ⟿
+< ⤀
+< ⤁
+< ⤂
+< ⤃
+< ⤄
+< ⤅
+< ⤆
+< ⤇
+< ⤈
+< ⤉
+< ⤊
+< ⤋
+< ⤌
+< ⤍
+< ⤎
+< ⤏
+< ⤐
+< ⤑
+< ⤒
+< ⤓
+< ⤔
+< ⤕
+< ⤖
+< ⤗
+< ⤘
+< ⤙
+< ⤚
+< ⤛
+< ⤜
+< ⤝
+< ⤞
+< ⤟
+< ⤠
+< ⤡
+< ⤢
+< ⤣
+< ⤤
+< ⤥
+< ⤦
+< ⤧
+< ⤨
+< ⤩
+< ⤪
+< ⤫
+< ⤬
+< ⤭
+< ⤮
+< ⤯
+< ⤰
+< ⤱
+< ⤲
+< ⤳
+< ⤴
+< ⤵
+< ⤶
+< ⤷
+< ⤸
+< ⤹
+< ⤺
+< ⤻
+< ⤼
+< ⤽
+< ⤾
+< ⤿
+< ⥀
+< ⥁
+< ⥂
+< ⥃
+< ⥄
+< ⥅
+< ⥆
+< ⥇
+< ⥈
+< ⥉
+< ⥊
+< ⥋
+< ⥌
+< ⥍
+< ⥎
+< ⥏
+< ⥐
+< ⥑
+< ⥒
+< ⥓
+< ⥔
+< ⥕
+< ⥖
+< ⥗
+< ⥘
+< ⥙
+< ⥚
+< ⥛
+< ⥜
+< ⥝
+< ⥞
+< ⥟
+< ⥠
+< ⥡
+< ⥢
+< ⥣
+< ⥤
+< ⥥
+< ⥦
+< ⥧
+< ⥨
+< ⥩
+< ⥪
+< ⥫
+< ⥬
+< ⥭
+< ⥮
+< ⥯
+< ⥰
+< ⥱
+< ⥲
+< ⥳
+< ⥴
+< ⥵
+< ⥶
+< ⥷
+< ⥸
+< ⥹
+< ⥺
+< ⥻
+< ⥼
+< ⥽
+< ⥾
+< ⥿
+< ⦀
+< ⦁
+< ⦂
+< ⦙
+< ⦚
+< ⦛
+< ⦜
+< ⦝
+< ⦞
+< ⦟
+< ⦠
+< ⦡
+< ⦢
+< ⦣
+< ⦤
+< ⦥
+< ⦦
+< ⦧
+< ⦨
+< ⦩
+< ⦪
+< ⦫
+< ⦬
+< ⦭
+< ⦮
+< ⦯
+< ⦰
+< ⦱
+< ⦲
+< ⦳
+< ⦴
+< ⦵
+< ⦶
+< ⦷
+< ⦸
+< ⦹
+< ⦺
+< ⦻
+< ⦼
+< ⦽
+< ⦾
+< ⦿
+< ⧀
+< ⧁
+< ⧂
+< ⧃
+< ⧄
+< ⧅
+< ⧆
+< ⧇
+< ⧈
+< ⧉
+< ⧊
+< ⧋
+< ⧌
+< ⧍
+< ⧎
+< ⧏
+< ⧐
+< ⧑
+< ⧒
+< ⧓
+< ⧔
+< ⧕
+< ⧖
+< ⧗
+< ⧜
+< ⧝
+< ⧞
+< ⧟
+< ⧠
+< ⧡
+< ⧢
+< ⧣
+< ⧤
+< ⧥
+< ⧦
+< ⧧
+< ⧨
+< ⧩
+< ⧪
+< ⧫
+< ⧬
+< ⧭
+< ⧮
+< ⧯
+< ⧰
+< ⧱
+< ⧲
+< ⧳
+< ⧴
+< ⧵
+< ⧶
+< ⧷
+< ⧸
+< ⧹
+< ⧺
+< ⧻
+< ⧾
+< ⧿
+< ⨀
+< ⨁
+< ⨂
+< ⨃
+< ⨄
+< ⨅
+< ⨆
+< ⨇
+< ⨈
+< ⨉
+< ⨊
+< ⨋
+< ⨍
+< ⨎
+< ⨏
+< ⨐
+< ⨑
+< ⨒
+< ⨓
+< ⨔
+< ⨕
+< ⨖
+< ⨗
+< ⨘
+< ⨙
+< ⨚
+< ⨛
+< ⨜
+< ⨝
+< ⨞
+< ⨟
+< ⨠
+< ⨡
+< ⨢
+< ⨣
+< ⨤
+< ⨥
+< ⨦
+< ⨧
+< ⨨
+< ⨩
+< ⨪
+< ⨫
+< ⨬
+< ⨭
+< ⨮
+< ⨯
+< ⨰
+< ⨱
+< ⨲
+< ⨳
+< ⨴
+< ⨵
+< ⨶
+< ⨷
+< ⨸
+< ⨹
+< ⨺
+< ⨻
+< ⨼
+< ⨽
+< ⨾
+< ⨿
+< ⩀
+< ⩁
+< ⩂
+< ⩃
+< ⩄
+< ⩅
+< ⩆
+< ⩇
+< ⩈
+< ⩉
+< ⩊
+< ⩋
+< ⩌
+< ⩍
+< ⩎
+< ⩏
+< ⩐
+< ⩑
+< ⩒
+< ⩓
+< ⩔
+< ⩕
+< ⩖
+< ⩗
+< ⩘
+< ⩙
+< ⩚
+< ⩛
+< ⩜
+< ⩝
+< ⩞
+< ⩟
+< ⩠
+< ⩡
+< ⩢
+< ⩣
+< ⩤
+< ⩥
+< ⩦
+< ⩧
+< ⩨
+< ⩩
+< ⩪
+< ⩫
+< ⩬
+< ⩭
+< ⩮
+< ⩯
+< ⩰
+< ⩱
+< ⩲
+< ⩳
+< ⩷
+< ⩸
+< ⩹
+< ⩺
+< ⩻
+< ⩼
+< ⩽
+< ⩾
+< ⩿
+< ⪀
+< ⪁
+< ⪂
+< ⪃
+< ⪄
+< ⪅
+< ⪆
+< ⪇
+< ⪈
+< ⪉
+< ⪊
+< ⪋
+< ⪌
+< ⪍
+< ⪎
+< ⪏
+< ⪐
+< ⪑
+< ⪒
+< ⪓
+< ⪔
+< ⪕
+< ⪖
+< ⪗
+< ⪘
+< ⪙
+< ⪚
+< ⪛
+< ⪜
+< ⪝
+< ⪞
+< ⪟
+< ⪠
+< ⪡
+< ⪢
+< ⪣
+< ⪤
+< ⪥
+< ⪦
+< ⪧
+< ⪨
+< ⪩
+< ⪪
+< ⪫
+< ⪬
+< ⪭
+< ⪮
+< ⪯
+< ⪰
+< ⪱
+< ⪲
+< ⪳
+< ⪴
+< ⪵
+< ⪶
+< ⪷
+< ⪸
+< ⪹
+< ⪺
+< ⪻
+< ⪼
+< ⪽
+< ⪾
+< ⪿
+< ⫀
+< ⫁
+< ⫂
+< ⫃
+< ⫄
+< ⫅
+< ⫆
+< ⫇
+< ⫈
+< ⫉
+< ⫊
+< ⫋
+< ⫌
+< ⫍
+< ⫎
+< ⫏
+< ⫐
+< ⫑
+< ⫒
+< ⫓
+< ⫔
+< ⫕
+< ⫖
+< ⫗
+< ⫘
+< ⫙
+< ⫚
+< ⫛
+< ⫝ ; ⫝̸
+< ⫞
+< ⫟
+< ⫠
+< ⫡
+< ⫢
+< ⫣
+< ⫤
+< ⫥
+< ⫦
+< ⫧
+< ⫨
+< ⫩
+< ⫪
+< ⫫
+< ⫬
+< ⫭
+< ⫮
+< ⫯
+< ⫰
+< ⫱
+< ⫲
+< ⫳
+< ⫴
+< ⫵
+< ⫶
+< ⫷
+< ⫸
+< ⫹
+< ⫺
+< ⫻
+< ⫼
+< ⫽
+< ⫾
+< ⫿
+< ⬀
+< ⬁
+< ⬂
+< ⬃
+< ⬄
+< ⬅
+< ⬆
+< ⬇
+< ⬈
+< ⬉
+< ⬊
+< ⬋
+< ⬌
+< ⬍
+< ⬎
+< ⬏
+< ⬐
+< ⬑
+< ⬒
+< ⬓
+< ⬔
+< ⬕
+< ⬖
+< ⬗
+< ⬘
+< ⬙
+< ⬚
+< ⬛
+< ⬜
+< ⬝
+< ⬞
+< ⬟
+< ⬠
+< ⬡
+< ⬢
+< ⬣
+< ⬤
+< ⬥
+< ⬦
+< ⬧
+< ⬨
+< ⬩
+< ⬪
+< ⬫
+< ⬬
+< ⬭
+< ⬮
+< ⬯
+< ⬰
+< ⬱
+< ⬲
+< ⬳
+< ⬴
+< ⬵
+< ⬶
+< ⬷
+< ⬸
+< ⬹
+< ⬺
+< ⬻
+< ⬼
+< ⬽
+< ⬾
+< ⬿
+< ⭀
+< ⭁
+< ⭂
+< ⭃
+< ⭄
+< ⭅
+< ⭆
+< ⭇
+< ⭈
+< ⭉
+< ⭊
+< ⭋
+< ⭌
+< ⭐
+< ⭑
+< ⭒
+< ⭓
+< ⭔
+< ⭕
+< ⭖
+< ⭗
+< ⭘
+< ⭙
+< ⳥
+< ⳦
+< ⳧
+< ⳨
+< ⳩
+< ⳪
+< ⠀
+< ⠁
+< ⠂
+< ⠃
+< ⠄
+< ⠅
+< ⠆
+< ⠇
+< ⠈
+< ⠉
+< ⠊
+< ⠋
+< ⠌
+< ⠍
+< ⠎
+< ⠏
+< ⠐
+< ⠑
+< ⠒
+< ⠓
+< ⠔
+< ⠕
+< ⠖
+< ⠗
+< ⠘
+< ⠙
+< ⠚
+< ⠛
+< ⠜
+< ⠝
+< ⠞
+< ⠟
+< ⠠
+< ⠡
+< ⠢
+< ⠣
+< ⠤
+< ⠥
+< ⠦
+< ⠧
+< ⠨
+< ⠩
+< ⠪
+< ⠫
+< ⠬
+< ⠭
+< ⠮
+< ⠯
+< ⠰
+< ⠱
+< ⠲
+< ⠳
+< ⠴
+< ⠵
+< ⠶
+< ⠷
+< ⠸
+< ⠹
+< ⠺
+< ⠻
+< ⠼
+< ⠽
+< ⠾
+< ⠿
+< ⡀
+< ⡁
+< ⡂
+< ⡃
+< ⡄
+< ⡅
+< ⡆
+< ⡇
+< ⡈
+< ⡉
+< ⡊
+< ⡋
+< ⡌
+< ⡍
+< ⡎
+< ⡏
+< ⡐
+< ⡑
+< ⡒
+< ⡓
+< ⡔
+< ⡕
+< ⡖
+< ⡗
+< ⡘
+< ⡙
+< ⡚
+< ⡛
+< ⡜
+< ⡝
+< ⡞
+< ⡟
+< ⡠
+< ⡡
+< ⡢
+< ⡣
+< ⡤
+< ⡥
+< ⡦
+< ⡧
+< ⡨
+< ⡩
+< ⡪
+< ⡫
+< ⡬
+< ⡭
+< ⡮
+< ⡯
+< ⡰
+< ⡱
+< ⡲
+< ⡳
+< ⡴
+< ⡵
+< ⡶
+< ⡷
+< ⡸
+< ⡹
+< ⡺
+< ⡻
+< ⡼
+< ⡽
+< ⡾
+< ⡿
+< ⢀
+< ⢁
+< ⢂
+< ⢃
+< ⢄
+< ⢅
+< ⢆
+< ⢇
+< ⢈
+< ⢉
+< ⢊
+< ⢋
+< ⢌
+< ⢍
+< ⢎
+< ⢏
+< ⢐
+< ⢑
+< ⢒
+< ⢓
+< ⢔
+< ⢕
+< ⢖
+< ⢗
+< ⢘
+< ⢙
+< ⢚
+< ⢛
+< ⢜
+< ⢝
+< ⢞
+< ⢟
+< ⢠
+< ⢡
+< ⢢
+< ⢣
+< ⢤
+< ⢥
+< ⢦
+< ⢧
+< ⢨
+< ⢩
+< ⢪
+< ⢫
+< ⢬
+< ⢭
+< ⢮
+< ⢯
+< ⢰
+< ⢱
+< ⢲
+< ⢳
+< ⢴
+< ⢵
+< ⢶
+< ⢷
+< ⢸
+< ⢹
+< ⢺
+< ⢻
+< ⢼
+< ⢽
+< ⢾
+< ⢿
+< ⣀
+< ⣁
+< ⣂
+< ⣃
+< ⣄
+< ⣅
+< ⣆
+< ⣇
+< ⣈
+< ⣉
+< ⣊
+< ⣋
+< ⣌
+< ⣍
+< ⣎
+< ⣏
+< ⣐
+< ⣑
+< ⣒
+< ⣓
+< ⣔
+< ⣕
+< ⣖
+< ⣗
+< ⣘
+< ⣙
+< ⣚
+< ⣛
+< ⣜
+< ⣝
+< ⣞
+< ⣟
+< ⣠
+< ⣡
+< ⣢
+< ⣣
+< ⣤
+< ⣥
+< ⣦
+< ⣧
+< ⣨
+< ⣩
+< ⣪
+< ⣫
+< ⣬
+< ⣭
+< ⣮
+< ⣯
+< ⣰
+< ⣱
+< ⣲
+< ⣳
+< ⣴
+< ⣵
+< ⣶
+< ⣷
+< ⣸
+< ⣹
+< ⣺
+< ⣻
+< ⣼
+< ⣽
+< ⣾
+< ⣿
+< ⚊
+< ⚋
+< ⚌
+< ⚍
+< ⚎
+< ⚏
+< ☰
+< ☱
+< ☲
+< ☳
+< ☴
+< ☵
+< ☶
+< ☷
+< ䷀
+< ䷁
+< ䷂
+< ䷃
+< ䷄
+< ䷅
+< ䷆
+< ䷇
+< ䷈
+< ䷉
+< ䷊
+< ䷋
+< ䷌
+< ䷍
+< ䷎
+< ䷏
+< ䷐
+< ䷑
+< ䷒
+< ䷓
+< ䷔
+< ䷕
+< ䷖
+< ䷗
+< ䷘
+< ䷙
+< ䷚
+< ䷛
+< ䷜
+< ䷝
+< ䷞
+< ䷟
+< ䷠
+< ䷡
+< ䷢
+< ䷣
+< ䷤
+< ䷥
+< ䷦
+< ䷧
+< ䷨
+< ䷩
+< ䷪
+< ䷫
+< ䷬
+< ䷭
+< ䷮
+< ䷯
+< ䷰
+< ䷱
+< ䷲
+< ䷳
+< ䷴
+< ䷵
+< ䷶
+< ䷷
+< ䷸
+< ䷹
+< ䷺
+< ䷻
+< ䷼
+< ䷽
+< ䷾
+< ䷿
+< ꒐
+< ꒑
+< ꒒
+< ꒓
+< ꒔
+< ꒕
+< ꒖
+< ꒗
+< ꒘
+< ꒙
+< ꒚
+< ꒛
+< ꒜
+< ꒝
+< ꒞
+< ꒟
+< ꒠
+< ꒡
+< ꒢
+< ꒣
+< ꒤
+< ꒥
+< ꒦
+< ꒧
+< ꒨
+< ꒩
+< ꒪
+< ꒫
+< ꒬
+< ꒭
+< ꒮
+< ꒯
+< ꒰
+< ꒱
+< ꒲
+< ꒳
+< ꒴
+< ꒵
+< ꒶
+< ꒷
+< ꒸
+< ꒹
+< ꒺
+< ꒻
+< ꒼
+< ꒽
+< ꒾
+< ꒿
+< ꓀
+< ꓁
+< ꓂
+< ꓃
+< ꓄
+< ꓅
+< ꓆
+< ♭
+< ♮
+< ♯
+< ⿰
+< ⿱
+< ⿲
+< ⿳
+< ⿴
+< ⿵
+< ⿶
+< ⿷
+< ⿸
+< ⿹
+< ⿺
+< ⿻
+< ㇀
+< ㇁
+< ㇂
+< ㇃
+< ㇄
+< ㇅
+< ㇆
+< ㇇
+< ㇈
+< ㇉
+< ㇊
+< ㇋
+< ㇌
+< ㇍
+< ㇎
+< ㇏
+< ㇐
+< ㇑
+< ㇒
+< ㇓
+< ㇔
+< ㇕
+< ㇖
+< ㇗
+< ㇘
+< ㇙
+< ㇚
+< ㇛
+< ㇜
+< ㇝
+< ㇞
+< ㇟
+< ㇠
+< ㇡
+< ㇢
+< ㇣
+< 〄
+< 〒,〶
+< 〓
+< 〠
+< 〷
+< 〾
+< 〿
+< ㆐
+< ㆑
+< ㉿
+< 
+< ৴
+< ৵
+< ৶
+< ৷
+< ৸
+< ৹
+< ୲
+< ୳
+< ୴
+< ୵
+< ୶
+< ୷
+< ꠰
+< ꠱
+< ꠲
+< ꠳
+< ꠴
+< ꠵
+< ௰
+< ௱
+< ௲
+< ൰
+< ൱
+< ൲
+< ൳
+< ൴
+< ൵
+< ፲
+< ፳
+< ፴
+< ፵
+< ፶
+< ፷
+< ፸
+< ፹
+< ፺
+< ፻
+< ፼
+< ↀ
+< ↁ
+< ↂ
+< ↆ
+< ↇ
+< ↈ
+< ⳽
+< ː
+< ˑ
+< ॱ
+< ๆ
+< ໆ
+< ៗ
+< ᪧ
+< ꧏ
+< ꩰ
+< ꫝ
+< aaf3
+< aaf4
+< 々
+< 〻
+< 〱 ; 〲
+< 〳 ; 〴
+< 〵
+< ゝ ; ゞ
+< ー,ー
+< ヽ ; ヾ
+< ¤
+< ¢,¢
+< $,$,﹩
+< £,£
+< ¥,¥
+< 058f
+< ؋
+< ৲
+< ৳
+< ৻
+< ૱
+< ꠸
+< ௹
+< ฿
+< ៛
+< ₠
+< ₡
+< ₢
+< ₣
+< ₤
+< ₥
+< ₦
+< ₧
+< ₩,₩
+< ₪
+< ₫
+< €
+< ₭
+< ₮
+< ₯
+< ₰
+< ₱
+< ₲
+< ₳
+< ₴
+< ₵
+< ₶
+< ₷
+< ₸
+< ₹
+< 20ba
+< 0=٠=۰=߀=०=০=੦=૦=୦=௦=౦=౸=೦=൦=๐=໐=༠=၀=႐=០=៰=᠐=᥆=᧐=᪀=᪐=᭐=᮰=᱀=᱐=〇=꘠=꣐=꤀=꧐=꩐=꯰,0,༳,⓪=⓿,⁰,₀
+< 1=١=۱=߁=१=১=੧=૧=୧=௧=౧=౹=౼=೧=൧=๑=໑=༡=၁=႑=፩=១=៱=᠑=᥇=᧑=᧚=᪁=᪑=᭑=᮱=᱁=᱑=〡=꘡=꣑=꤁=꧑=꩑=꯱,1,༪,①=⓵=❶=➀=➊,¹,₁
+< 2=٢=۲=߂=२=২=੨=૨=୨=௨=౨=౺=౽=೨=൨=๒=໒=༢=၂=႒=፪=២=៲=᠒=᥈=᧒=᪂=᪒=᭒=᮲=᱂=᱒=〢=꘢=꣒=꤂=꧒=꩒=꯲,2,༫,②=⓶=❷=➁=➋,²,₂
+< 3=٣=۳=߃=३=৩=੩=૩=୩=௩=౩=౻=౾=೩=൩=๓=໓=༣=၃=႓=፫=៣=៳=᠓=᥉=᧓=᪃=᪓=᭓=᮳=᱃=᱓=〣=꘣=꣓=꤃=꧓=꩓=꯳,3,༬,③=⓷=❸=➂=➌,³,₃
+< 4=٤=۴=߄=४=৪=੪=૪=୪=௪=౪=೪=൪=๔=໔=༤=၄=႔=፬=៤=៴=᠔=᥊=᧔=᪄=᪔=᭔=᮴=᱄=᱔=〤=꘤=꣔=꤄=꧔=꩔=꯴,4,༭,④=⓸=❹=➃=➍,⁴,₄
+< 5=٥=۵=߅=५=৫=੫=૫=୫=௫=౫=೫=൫=๕=໕=༥=၅=႕=፭=៥=៵=᠕=᥋=᧕=᪅=᪕=᭕=᮵=᱅=᱕=〥=꘥=꣕=꤅=꧕=꩕=꯵,5,༮,⑤=⓹=❺=➄=➎,⁵,₅
+< 6=٦=۶=߆=६=৬=੬=૬=୬=௬=౬=೬=൬=๖=໖=༦=၆=႖=፮=៦=៶=᠖=᥌=᧖=᪆=᪖=᭖=᮶=᱆=᱖=ↅ=〦=꘦=꣖=꤆=꧖=꩖=꯶,6,༯,⑥=⓺=❻=➅=➏,⁶,₆
+< 7=٧=۷=߇=७=৭=੭=૭=୭=௭=౭=೭=൭=๗=໗=༧=၇=႗=፯=៧=៷=᠗=᥍=᧗=᪇=᪗=᭗=᮷=᱇=᱗=〧=꘧=꣗=꤇=꧗=꩗=꯷,7,༰,⑦=⓻=❼=➆=➐,⁷,₇
+< 8=٨=۸=߈=८=৮=੮=૮=୮=௮=౮=೮=൮=๘=໘=༨=၈=႘=፰=៨=៸=᠘=᥎=᧘=᪈=᪘=᭘=᮸=᱈=᱘=〨=꘨=꣘=꤈=꧘=꩘=꯸,8,༱,⑧=⓼=❽=➇=➑,⁸,₈
+< 9=٩=۹=߉=९=৯=੯=૯=୯=௯=౯=೯=൯=๙=໙=༩=၉=႙=፱=៩=៹=᠙=᥏=᧙=᪉=᪙=᭙=᮹=᱉=᱙=〩=꘩=꣙=꤉=꧙=꩙=꯹,9,༲,⑨=⓽=❾=➈=➒,⁹,₉
+< a,a,0363,ⓐ,A,A,Ⓐ,ª=ᵃ,ₐ,ᴬ ; á,Á ; à,À ; ă,Ă ; ắ,Ắ ; ằ,Ằ ; ẵ,Ẵ ; ẳ,Ẳ ; â, ; ấ,Ấ ; ầ,Ầ ; ẫ,Ẫ ; ẩ,Ẩ ; ǎ,Ǎ ; å,Å=Å ; ǻ,Ǻ ; ä,Ä ; ǟ,Ǟ ; ã,à ; ȧ,Ȧ ; ǡ,Ǡ ; ą,Ą ; ā,Ā ; ả,Ả ; ȁ,Ȁ ; ȃ,Ȃ ; ạ,Ạ ; ặ,Ặ ; ậ,Ậ ; ḁ,Ḁ ; 1dd3
+< ᴀ
+< ⱥ,Ⱥ
+< ᶏ
+< ᴁ
+< ᴂ,ᵆ
+< ɐ,Ɐ,ᵄ
+< ɑ,Ɑ,ᵅ
+< ᶐ
+< ɒ,Ɒ,ᶛ
+< b,b,ⓑ,B,B,ℬ,Ⓑ,ᵇ,ᴮ ; ḃ,Ḃ ; ḅ,Ḅ ; ḇ,Ḇ
+< ʙ
+< ƀ,Ƀ
+< ᴯ
+< ᴃ
+< ᵬ
+< ᶀ
+< ɓ,Ɓ
+< ƃ,Ƃ
+< c,c,0368=ⅽ,ⓒ,C,C,Ⅽ,ℂ=ℭ,Ⓒ,ᶜ ; ć,Ć ; ĉ,Ĉ ; č,Č ; ċ,Ċ ; ç,1dd7,Ç ; ḉ,Ḉ
+< ᴄ
+< ȼ,Ȼ
+< a793,a792
+< ƈ,Ƈ
+< ɕ,ᶝ
+< ↄ,Ↄ
+< ꜿ,Ꜿ
+< d,d,0369=ⅾ,ⅆ,ⓓ,D,D,Ⅾ,ⅅ,Ⓓ,ᵈ,ᴰ ; ď,Ď ; ḋ,Ḋ ; ḑ,Ḑ ; đ,Đ ; ḍ,Ḍ ; ḓ,Ḓ ; ḏ,Ḏ ; ð=1dd9,Ð,ᶞ ; 1dd8=ꝺ,Ꝺ
+< ᴅ
+< ᴆ
+< ᵭ
+< ᶁ
+< ɖ,Ɖ
+< ɗ,Ɗ
+< ᶑ
+< ƌ,Ƌ
+< ȡ
+< ꝱ
+< ẟ
+< e,e,0364,ℯ=ⅇ,ⓔ,E,E,ℰ,Ⓔ,ᵉ,ₑ,ᴱ ; é,É ; è,È ; ĕ,Ĕ ; ê,Ê ; ế,Ế ; ề,Ề ; ễ,Ễ ; ể,Ể ; ě,Ě ; ë,Ë ; ẽ,Ẽ ; ė,Ė ; ȩ,Ȩ ; ḝ,Ḝ ; ę,Ę ; ē,Ē ; ḗ,Ḗ ; ḕ,Ḕ ; ẻ,Ẻ ; ȅ,Ȅ ; ȇ,Ȇ ; ẹ,Ẹ ; ệ,Ệ ; ḙ,Ḙ ; ḛ,Ḛ
+< ᴇ
+< ɇ,Ɇ
+< ᶒ
+< ⱸ
+< ǝ,Ǝ,ᴲ
+< ⱻ
+< ə,Ə,ᵊ,ₔ
+< ᶕ
+< ɛ,Ɛ,ℇ,ᵋ
+< ᶓ
+< ɘ
+< ɚ
+< ɜ,ᶟ
+< ᶔ
+< ᴈ,ᵌ
+< ɝ
+< ɞ
+< ʚ
+< ɤ
+< f,f,ⓕ,F,F,ℱ,Ⓕ,ᶠ ; ḟ,Ḟ ; ꝼ,Ꝼ
+< ꜰ
+< ᵮ
+< ᶂ
+< ƒ,Ƒ
+< ⅎ,Ⅎ
+< ꟻ
+< g,g,1dda,ℊ,ⓖ,G,G,Ⓖ,ᵍ,ᴳ ; ǵ,Ǵ ; ğ,Ğ ; ĝ,Ĝ ; ǧ,Ǧ ; ġ,Ġ ; ģ,Ģ ; ḡ,Ḡ ; ꞡ,Ꞡ ; ᵹ,Ᵹ
+< ɡ,ᶢ
+< ɢ,1ddb
+< ǥ,Ǥ
+< ᶃ
+< ɠ,Ɠ
+< ʛ
+< ᵷ
+< ꝿ,Ꝿ
+< ɣ,Ɣ,ˠ
+< ƣ,Ƣ
+< h,h,036a,ℎ,ⓗ,H,H,ℋ=ℌ=ℍ,Ⓗ,ʰ,ₕ,ᴴ ; ĥ,Ĥ ; ȟ,Ȟ ; ḧ,Ḧ ; ḣ,Ḣ ; ḩ,Ḩ ; ħ=ℏ,Ħ,a7f8 ; ḥ,Ḥ ; ḫ,Ḫ ; ẖ
+< ʜ
+< ƕ,Ƕ
+< ɦ,a7aa,ʱ
+< ⱨ,Ⱨ
+< ⱶ,Ⱶ
+< ꜧ,Ꜧ
+< ɧ
+< ʻ
+< ʽ
+< i,i,0365=ⅰ,ℹ=ⅈ,ⓘ,I,I,Ⅰ,ℐ=ℑ,Ⓘ,ⁱ,ᵢ,ᴵ ; í,Í ; ì,Ì ; ĭ,Ĭ ; î,Î ; ǐ,Ǐ ; ï,Ï ; ḯ,Ḯ ; ĩ,Ĩ ; İ ; į,Į ; ī,Ī ; ỉ,Ỉ ; ȉ,Ȉ ; ȋ,Ȋ ; ị,Ị ; ḭ,Ḭ
+< ı
+< ɪ,ᶦ
+< ꟾ
+< ᴉ,ᵎ
+< ɨ,Ɨ,ᶤ
+< ᵻ,ᶧ
+< ᶖ
+< ɩ,Ɩ,ᶥ
+< ᵼ
+< j,j,ⅉ,ⓙ,J,J,Ⓙ,ʲ,ⱼ,ᴶ ; ĵ,Ĵ ; ǰ
+< ȷ
+< ᴊ
+< ɉ,Ɉ
+< ʝ,ᶨ
+< ɟ,ᶡ
+< ʄ
+< k,k,1ddc,ⓚ,K=K,K,Ⓚ,ᵏ,ₖ,ᴷ ; ḱ,Ḱ ; ǩ,Ǩ ; ķ,Ķ ; ꞣ,Ꞣ ; ḳ,Ḳ ; ḵ,Ḵ
+< ᴋ
+< ᶄ
+< ƙ,Ƙ
+< ⱪ,Ⱪ
+< ꝁ,Ꝁ
+< ꝃ,Ꝃ
+< ꝅ,Ꝅ
+< ʞ
+< l,l,1ddd=ⅼ,ℓ,ⓛ,L,L,Ⅼ,ℒ,Ⓛ,ˡ,ₗ,ᴸ ; ĺ,Ĺ ; ľ,Ľ ; ļ,Ļ ; ł,Ł ; ḷ,Ḷ ; ḹ,Ḹ ; ḽ,Ḽ ; ḻ,Ḻ ; ŀ,Ŀ
+< ʟ,1dde,ᶫ
+< ꝇ,Ꝇ
+< ᴌ
+< ꝉ,Ꝉ
+< ƚ,Ƚ
+< ⱡ,Ⱡ
+< ɫ,Ɫ
+< ɬ
+< ᶅ,ᶪ
+< ɭ,ᶩ
+< ꞎ
+< ȴ
+< ꝲ
+< ɮ
+< ꞁ,Ꞁ
+< ƛ
+< ʎ
+< m,m,036b=ⅿ,ⓜ,M,M,Ⅿ,ℳ,Ⓜ,ᵐ,ₘ,ᴹ ; ḿ,Ḿ ; ṁ,Ṁ ; ṃ,Ṃ
+< ᴍ,1ddf
+< ᵯ
+< ᶆ
+< ɱ,Ɱ,ᶬ
+< ꟽ
+< ꟿ
+< ꝳ
+< n,n,1de0,ⓝ,N,N,ℕ,Ⓝ,ⁿ,ₙ,ᴺ ; ń,Ń ; ǹ,Ǹ ; ň,Ň ; ñ,Ñ ; ṅ,Ṅ ; ņ,Ņ ; ꞥ,Ꞥ ; ṇ,Ṇ ; ṋ,Ṋ ; ṉ,Ṉ
+< ɴ,1de1,ᶰ
+< ᴻ
+< ᴎ
+< ᵰ
+< ɲ,Ɲ,ᶮ
+< ƞ,Ƞ
+< ꞑ,Ꞑ
+< ᶇ
+< ɳ,ᶯ
+< ȵ
+< ꝴ
+< ŋ,Ŋ,ᵑ
+< o,o,0366,ℴ,ⓞ,O,O,Ⓞ,º=ᵒ,ₒ,ᴼ ; ó,Ó ; ò,Ò ; ŏ,Ŏ ; ô,Ô ; ố,Ố ; ồ,Ồ ; ỗ,Ỗ ; ổ,Ổ ; ǒ,Ǒ ; ö,Ö ; ȫ,Ȫ ; ő,Ő ; õ,Õ ; ṍ,Ṍ ; ṏ,Ṏ ; ȭ,Ȭ ; ȯ,Ȯ ; ȱ,Ȱ ; ø,Ø ; ǿ,Ǿ ; ǫ,Ǫ ; ǭ,Ǭ ; ō,Ō ; ṓ,Ṓ ; ṑ,Ṑ ; ỏ,Ỏ ; ȍ,Ȍ ; ȏ,Ȏ ; ơ,Ơ ; ớ,Ớ ; ờ,Ờ ; ỡ,Ỡ ; ở,Ở ; ợ,Ợ ; ọ,Ọ ; ộ,Ộ
+< ᴏ
+< ᴑ
+< ɶ
+< ᴔ
+< ᴓ
+< ɔ,Ɔ,ᵓ
+< ᴐ
+< ᴒ
+< ᶗ
+< ꝍ,Ꝍ
+< ᴖ,ᵔ
+< ᴗ,ᵕ
+< ⱺ
+< ɵ,Ɵ,ᶱ
+< ꝋ,Ꝋ
+< ɷ
+< ȣ,Ȣ,ᴽ
+< ᴕ
+< p,p,ⓟ,P,P,ℙ,Ⓟ,ᵖ,ₚ,ᴾ ; ṕ,Ṕ ; ṗ,Ṗ
+< ᴘ
+< ᵽ,Ᵽ
+< ꝑ,Ꝑ
+< ᵱ
+< ᶈ
+< ƥ,Ƥ
+< ꝓ,Ꝓ
+< ꝕ,Ꝕ
+< ꟼ
+< ɸ,ᶲ
+< ⱷ
+< q,q,ⓠ,Q,Q,ℚ,Ⓠ
+< ꝗ,Ꝗ
+< ꝙ,Ꝙ
+< ʠ
+< ɋ,Ɋ
+< ĸ
+< r,r,036c=1dca,ⓡ,R,R,ℛ=ℜ=ℝ,Ⓡ,ʳ,ᵣ,ᴿ ; ŕ,Ŕ ; ř,Ř ; ṙ,Ṙ ; ŗ,Ŗ ; ꞧ,Ꞧ ; ȑ,Ȑ ; ȓ,Ȓ ; ṛ,Ṛ ; ṝ,Ṝ ; ṟ,Ṟ ; ꞃ,Ꞃ
+< ʀ,1de2,Ʀ
+< ꝛ,1de3,Ꝛ
+< ᴙ
+< ɍ,Ɍ
+< ᵲ
+< ɹ,ʴ
+< ᴚ
+< ɺ
+< ᶉ
+< ɻ,ʵ
+< ⱹ
+< ɼ
+< ɽ,Ɽ
+< ɾ
+< ᵳ
+< ɿ
+< ʁ,ʶ
+< ꝵ
+< ꝶ
+< ꝝ,Ꝝ
+< s,s,1de4,ⓢ,S,S,Ⓢ,ˢ,ₛ ; ś,Ś ; ṥ,Ṥ ; ŝ,Ŝ ; š,Š ; ṧ,Ṧ ; ṡ,Ṡ ; ş,Ş ; ꞩ,Ꞩ ; ṣ,Ṣ ; ṩ,Ṩ ; ș,Ș ; ſ=1de5=ꞅ,Ꞅ ; ẛ
+< ꜱ
+< ᵴ
+< ᶊ
+< ʂ,ᶳ
+< ȿ,Ȿ
+< ẜ
+< ẝ
+< ʃ,Ʃ,ᶴ
+< ᶋ
+< ƪ
+< ʅ
+< ᶘ
+< ʆ
+< t,t,036d,ⓣ,T,T,Ⓣ,ᵗ,ₜ,ᵀ ; ť,Ť ; ẗ ; ṫ,Ṫ ; ţ,Ţ ; ṭ,Ṭ ; ț,Ț ; ṱ,Ṱ ; ṯ,Ṯ ; ꞇ,Ꞇ
+< ᴛ
+< ŧ,Ŧ
+< ⱦ,Ⱦ
+< ᵵ
+< ƫ,ᶵ
+< ƭ,Ƭ
+< ʈ,Ʈ
+< ȶ
+< ꝷ
+< ʇ
+< u,u,0367,ⓤ,U,U,Ⓤ,ᵘ,ᵤ,ᵁ ; ú,Ú ; ù,Ù ; ŭ,Ŭ ; û,Û ; ǔ,Ǔ ; ů,Ů ; ü,Ü ; ǘ,Ǘ ; ǜ,Ǜ ; ǚ,Ǚ ; ǖ,Ǖ ; ű,Ű ; ũ,Ũ ; ṹ,Ṹ ; ų,Ų ; ū,Ū ; ṻ,Ṻ ; ủ,Ủ ; ȕ,Ȕ ; ȗ,Ȗ ; ư,Ư ; ứ,Ứ ; ừ,Ừ ; ữ,Ữ ; ử,Ử ; ự,Ự ; ụ,Ụ ; ṳ,Ṳ ; ṷ,Ṷ ; ṵ,Ṵ
+< ᴜ,ᶸ
+< ᴝ,ᵙ
+< ᴞ
+< ᵫ
+< ʉ,Ʉ,ᶶ
+< ᵾ
+< ᶙ
+< ɥ,Ɥ,ᶣ
+< ʮ
+< ʯ
+< ɯ,Ɯ,ᵚ
+< ꟺ
+< ᴟ
+< ɰ,ᶭ
+< ʊ,Ʊ,ᶷ
+< ᵿ
+< v,v,036e=ⅴ,ⓥ,V,V,Ⅴ,Ⓥ,ᵛ,ᵥ,ⱽ ; ṽ,Ṽ ; ṿ,Ṿ
+< ᴠ
+< ꝟ,Ꝟ
+< ᶌ
+< ʋ,Ʋ,ᶹ
+< ⱱ
+< ⱴ
+< ỽ,Ỽ
+< ʌ,Ʌ,ᶺ
+< w,w,ⓦ,W,W,Ⓦ,ʷ,ᵂ ; ẃ,Ẃ ; ẁ,Ẁ ; ŵ,Ŵ ; ẘ ; ẅ,Ẅ ; ẇ,Ẇ ; ẉ,Ẉ
+< ᴡ
+< ⱳ,Ⱳ
+< ʍ
+< x,x,036f=ⅹ,ⓧ,X,X,Ⅹ,Ⓧ,ˣ,ₓ ; ẍ,Ẍ ; ẋ,Ẋ
+< ᶍ
+< y,y,ⓨ,Y,Y,Ⓨ,ʸ ; ý,Ý ; ỳ,Ỳ ; ŷ,Ŷ ; ẙ ; ÿ,Ÿ ; ỹ,Ỹ ; ẏ,Ẏ ; ȳ,Ȳ ; ỷ,Ỷ ; ỵ,Ỵ
+< ʏ
+< ɏ,Ɏ
+< ƴ,Ƴ
+< ỿ,Ỿ
+< ȝ,Ȝ
+< z,z,1de6,ⓩ,Z,Z,ℤ=ℨ,Ⓩ,ᶻ ; ź,Ź ; ẑ,Ẑ ; ž,Ž ; ż,Ż ; ẓ,Ẓ ; ẕ,Ẕ
+< ᴢ
+< ƶ,Ƶ
+< ᵶ
+< ᶎ
+< ȥ,Ȥ
+< ʐ,ᶼ
+< ʑ,ᶽ
+< ɀ,Ɀ
+< ⱬ,Ⱬ
+< ꝣ,Ꝣ
+< ʒ,Ʒ,ᶾ ; ǯ,Ǯ
+< ᴣ
+< ƹ,Ƹ
+< ᶚ
+< ƺ
+< ʓ
+< þ,Þ
+< ꝥ,Ꝥ
+< ꝧ,Ꝧ
+< ƿ,Ƿ
+< ꝩ,Ꝩ
+< ꝫ,Ꝫ
+< ꝭ,Ꝭ
+< ꝯ,1dd2,Ꝯ,ꝰ
+< ꝸ
+< ƻ
+< ꜫ,Ꜫ
+< ꜭ,Ꜭ
+< ꜯ,Ꜯ
+< ƨ,Ƨ
+< ƽ,Ƽ
+< ƅ,Ƅ
+< ʔ
+< ɂ,Ɂ
+< ˀ
+< ʼ
+< ˮ
+< ʾ
+< ꜣ,Ꜣ
+< ꞌ,Ꞌ
+< ʕ,ˤ
+< ʿ
+< ˁ
+< ᴤ
+< ᴥ,ᵜ
+< ꜥ,Ꜥ
+< ʡ
+< ʢ
+< ʖ
+< ǀ
+< ǁ
+< ǂ
+< ǃ
+< ʗ
+< ʘ
+< ʬ
+< ʭ
+< α,Α ; ἀ,Ἀ ; ἄ,Ἄ ; ἂ,Ἂ ; ἆ,Ἆ ; ᾀ,ᾈ ; ἁ,Ἁ ; ἅ,Ἅ ; ἃ,Ἃ ; ἇ,Ἇ ; ᾁ,ᾉ ; ά=ά,Ά=Ά ; ᾴ ; ὰ,Ὰ ; ᾲ ; ᾰ,Ᾰ ; ᾶ ; ᾷ ; ᾱ,Ᾱ ; ᾳ,ᾼ ; ᾄ,ᾌ ; ᾂ,ᾊ ; ᾆ,ᾎ ; ᾅ,ᾍ ; ᾃ,ᾋ ; ᾇ,ᾏ
+< β,ϐ,Β,ᵝ,ᵦ
+< γ,ℽ,Γ,ℾ,ᵞ,ᵧ
+< ᴦ
+< δ,Δ,ᵟ
+< ε,ϵ,Ε ; ἐ,Ἐ ; ἔ,Ἔ ; ἒ,Ἒ ; ἑ,Ἑ ; ἕ,Ἕ ; ἓ,Ἓ ; έ=έ,Έ=Έ ; ὲ,Ὲ
+< ϝ,Ϝ
+< ͷ,Ͷ
+< ϛ,Ϛ
+< ζ,Ζ
+< ͱ,Ͱ
+< η,Η ; ἠ,Ἠ ; ἤ,Ἤ ; ἢ,Ἢ ; ἦ,Ἦ ; ᾐ,ᾘ ; ἡ,Ἡ ; ἥ,Ἥ ; ἣ,Ἣ ; ἧ,Ἧ ; ᾑ,ᾙ ; ή=ή,Ή=Ή ; ῄ ; ὴ,Ὴ ; ῂ ; ῆ ; ῇ ; ῃ,ῌ ; ᾔ,ᾜ ; ᾒ,ᾚ ; ᾖ,ᾞ ; ᾕ,ᾝ ; ᾓ,ᾛ ; ᾗ,ᾟ
+< θ,ϑ,Θ,ϴ,ᶿ
+< ι=ι,ͺ,Ι ; ἰ,Ἰ ; ἴ,Ἴ ; ἲ,Ἲ ; ἶ,Ἶ ; ἱ,Ἱ ; ἵ,Ἵ ; ἳ,Ἳ ; ἷ,Ἷ ; ί=ί,Ί=Ί ; ὶ,Ὶ ; ῐ,Ῐ ; ῖ ; ϊ,Ϊ ; ΐ=ΐ ; ῒ ; ῗ ; ῑ,Ῑ
+< ϳ
+< κ,ϰ,Κ
+< λ,Λ
+< ᴧ
+< μ,µ,Μ
+< ν,Ν
+< ξ,Ξ
+< ο,Ο ; ὀ,Ὀ ; ὄ,Ὄ ; ὂ,Ὂ ; ὁ,Ὁ ; ὅ,Ὅ ; ὃ,Ὃ ; ό=ό,Ό=Ό ; ὸ,Ὸ
+< π,ϖ,ℼ,Π,ℿ
+< ᴨ
+< ϻ,Ϻ
+< ϟ,Ϟ
+< ϙ,Ϙ
+< ρ,ϱ,Ρ,ᵨ ; ῤ ; ῥ,Ῥ
+< ᴩ
+< ϼ
+< σ,ϲ,Σ,Ϲ,ς
+< ͼ,Ͼ
+< ͻ,Ͻ
+< ͽ,Ͽ
+< τ,Τ
+< υ,Υ,ϒ ; ὐ ; ὔ ; ὒ ; ὖ ; ὑ,Ὑ ; ὕ,Ὕ ; ὓ,Ὓ ; ὗ,Ὗ ; ύ=ύ,Ύ=Ύ,ϓ ; ὺ,Ὺ ; ῠ,Ῠ ; ῦ ; ϋ,Ϋ,ϔ ; ΰ=ΰ ; ῢ ; ῧ ; ῡ,Ῡ
+< φ,ϕ,Φ,ᵠ,ᵩ
+< χ,Χ,ᵡ,ᵪ
+< ψ,Ψ
+< ᴪ
+< ω,Ω=Ω ; ὠ,Ὠ ; ὤ,Ὤ ; ὢ,Ὢ ; ὦ,Ὦ ; ᾠ,ᾨ ; ὡ,Ὡ ; ὥ,Ὥ ; ὣ,Ὣ ; ὧ,Ὧ ; ᾡ,ᾩ ; ώ=ώ,Ώ=Ώ ; ῴ ; ὼ,Ὼ ; ῲ ; ῶ ; ῷ ; ῳ,ῼ ; ᾤ,ᾬ ; ᾢ,ᾪ ; ᾦ,ᾮ ; ᾥ,ᾭ ; ᾣ,ᾫ ; ᾧ,ᾯ
+< ϡ,Ϡ
+< ͳ,Ͳ
+< ϸ,Ϸ
+< ⲁ,Ⲁ
+< ⲃ,Ⲃ
+< ⲅ,Ⲅ
+< ⲇ,Ⲇ
+< ⲉ,Ⲉ
+< ⲷ,Ⲷ
+< ⲋ,Ⲋ
+< ⲍ,Ⲍ
+< ⲏ,Ⲏ
+< ⲑ,Ⲑ
+< ⲓ,Ⲓ
+< ⲕ,Ⲕ
+< ⲹ,Ⲹ
+< ⲗ,Ⲗ
+< ⲙ,Ⲙ
+< ⲛ,Ⲛ
+< ⲻ,Ⲻ
+< ⲽ,Ⲽ
+< ⲝ,Ⲝ
+< ⲟ,Ⲟ
+< ⲡ,Ⲡ
+< ⲣ,Ⲣ
+< ⲥ,Ⲥ
+< ⲧ,Ⲧ
+< ⲩ,Ⲩ
+< ⲫ,Ⲫ
+< ⲭ,Ⲭ
+< ⲯ,Ⲯ
+< ⲱ,Ⲱ
+< ⲿ,Ⲿ
+< ⳁ,Ⳁ
+< ϣ,Ϣ
+< ⳬ,Ⳬ
+< ⳃ,Ⳃ
+< ⳅ,Ⳅ
+< ⳇ,Ⳇ
+< ϥ,Ϥ
+< ϧ,Ϧ
+< 2cf3,2cf2
+< ⳉ,Ⳉ
+< ϩ,Ϩ
+< ⳋ,Ⳋ
+< ⳍ,Ⳍ
+< ⳏ,Ⳏ
+< ⳑ,Ⳑ
+< ⳓ,Ⳓ
+< ⳕ,Ⳕ
+< ϫ,Ϫ
+< ⳮ,Ⳮ
+< ⳗ,Ⳗ
+< ϭ,Ϭ
+< ⳙ,Ⳙ
+< ⳛ,Ⳛ
+< ⳝ,Ⳝ
+< ϯ,Ϯ
+< ⲳ,Ⲳ
+< ⲵ,Ⲵ
+< ⳟ,Ⳟ
+< ⳡ,Ⳡ
+< ⳣ,Ⳣ
+< а,2df6,А
+< ӑ,Ӑ
+< ӓ,Ӓ
+< ә,Ә
+< ӛ,Ӛ
+< ӕ,Ӕ
+< б,2de0,Б
+< в,2de1,В
+< г,2de2,Г ; ґ,Ґ
+< ғ,Ғ
+< ӻ,Ӻ
+< ҕ,Ҕ
+< ӷ,Ӷ
+< д,2de3,Д
+< ԁ,Ԁ
+< ꚁ,Ꚁ
+< ђ,Ђ
+< ꙣ,Ꙣ
+< ԃ,Ԃ
+< ѓ,Ѓ
+< ҙ,Ҙ
+< е,2df7,Е ; ѐ,Ѐ ; ё,Ё
+< ӗ,Ӗ
+< є,a674,Є
+< ж,2de4,Ж ; ӂ,Ӂ
+< ꚅ,Ꚅ
+< ӝ,Ӝ
+< җ,Җ
+< з,2de5,З
+< ꙁ,Ꙁ
+< ԅ,Ԅ
+< ԑ,Ԑ
+< ӟ,Ӟ
+< ꙃ,Ꙃ
+< ѕ,Ѕ
+< ꙅ,Ꙅ
+< ӡ,Ӡ
+< ꚉ,Ꚉ
+< ԇ,Ԇ
+< ꚃ,Ꚃ
+< и,a675,И ; ѝ,Ѝ ; ӣ,Ӣ
+< ҋ,Ҋ
+< ӥ,Ӥ
+< і,І
+< ꙇ,Ꙇ
+< ї,a676,Ї
+< й,Й
+< ј,Ј
+< ꙉ,2df8,Ꙉ
+< к,2de6,К
+< қ,Қ
+< ӄ,Ӄ
+< ҡ,Ҡ
+< ҟ,Ҟ
+< ҝ,Ҝ
+< ԟ,Ԟ
+< ԛ,Ԛ
+< л,2de7,Л
+< ᴫ
+< ӆ,Ӆ
+< ԓ,Ԓ
+< ԡ,Ԡ
+< љ,Љ
+< ꙥ,Ꙥ
+< ԉ,Ԉ
+< ԕ,Ԕ
+< м,2de8,М
+< ӎ,Ӎ
+< ꙧ,Ꙧ
+< н,2de9,Н,ᵸ
+< ӊ,Ӊ
+< ң,Ң
+< ӈ,Ӈ
+< ԣ,Ԣ
+< ҥ,Ҥ
+< њ,Њ
+< ԋ,Ԋ
+< о,2dea=ꙩ=ꙫ=ꙭ=ꙮ,О,Ꙩ=Ꙫ=Ꙭ
+< ӧ,Ӧ
+< ө,Ө
+< ӫ,Ӫ
+< п,2deb,П
+< ԥ,Ԥ
+< ҧ,Ҧ
+< ҁ,Ҁ
+< р,2dec,Р
+< ҏ,Ҏ
+< ԗ,Ԗ
+< с,2ded,С
+< ԍ,Ԍ
+< ҫ,Ҫ
+< т,2dee,Т
+< ꚍ,Ꚍ
+< ԏ,Ԏ
+< ҭ,Ҭ
+< ꚋ,Ꚋ
+< ћ,Ћ
+< ќ,Ќ
+< у,a677,У ; ӯ,Ӯ
+< ў,Ў
+< ӱ,Ӱ
+< ӳ,Ӳ
+< ү,Ү
+< ұ,Ұ
+< ꙋ,2df9,Ꙋ
+< ѹ,Ѹ
+< ф,Ф
+< х,2def,Х
+< ӽ,Ӽ
+< ӿ,Ӿ
+< ҳ,Ҳ
+< һ,Һ
+< ԧ,Ԧ
+< ꚕ,Ꚕ
+< ѡ,a67b,Ѡ
+< ѿ,Ѿ
+< ꙍ,Ꙍ
+< ѽ,Ѽ
+< ѻ,Ѻ
+< ц,2df0,Ц
+< ꙡ,Ꙡ
+< ꚏ,Ꚏ
+< ҵ,Ҵ
+< ꚑ,Ꚑ
+< ч,2df1,Ч
+< ꚓ,Ꚓ
+< ӵ,Ӵ
+< ҷ,Ҷ
+< ӌ,Ӌ
+< ҹ,Ҹ
+< ꚇ,Ꚇ
+< ҽ,Ҽ
+< ҿ,Ҿ
+< џ,Џ
+< ш,2df2,Ш
+< ꚗ,Ꚗ
+< щ,2df3,Щ
+< ꙏ,Ꙏ
+< ⸯ
+< ꙿ
+< ъ,a678,Ъ
+< ꙑ,Ꙑ
+< ы,a679,Ы
+< ӹ,Ӹ
+< ь,a67a,Ь
+< ҍ,Ҍ
+< ѣ,2dfa,Ѣ
+< ꙓ,Ꙓ
+< э,Э
+< ӭ,Ӭ
+< ю,2dfb,Ю
+< ꙕ,Ꙕ
+< ꙗ,2dfc,Ꙗ
+< я,Я
+< ԙ,Ԙ
+< ѥ,a69f,Ѥ
+< ѧ,2dfd,Ѧ
+< ꙙ,Ꙙ
+< ѫ,2dfe,Ѫ
+< ꙛ,Ꙛ
+< ѩ,Ѩ
+< ꙝ,Ꙝ
+< ѭ,2dff,Ѭ
+< ѯ,Ѯ
+< ѱ,Ѱ
+< ѳ,2df4,Ѳ
+< ѵ,Ѵ
+< ѷ,Ѷ
+< ꙟ,Ꙟ
+< ҩ,Ҩ
+< ԝ,Ԝ
+< ӏ,Ӏ
+< ⰰ,Ⰰ
+< ⰱ,Ⰱ
+< ⰲ,Ⰲ
+< ⰳ,Ⰳ
+< ⰴ,Ⰴ
+< ⰵ,Ⰵ
+< ⰶ,Ⰶ
+< ⰷ,Ⰷ
+< ⰸ,Ⰸ
+< ⰹ,Ⰹ
+< ⰺ,Ⰺ
+< ⰻ,Ⰻ
+< ⰼ,Ⰼ
+< ⰽ,Ⰽ
+< ⰾ,Ⰾ
+< ⰿ,Ⰿ
+< ⱀ,Ⱀ
+< ⱁ,Ⱁ
+< ⱂ,Ⱂ
+< ⱃ,Ⱃ
+< ⱄ,Ⱄ
+< ⱅ,Ⱅ
+< ⱆ,Ⱆ
+< ⱇ,Ⱇ
+< ⱈ,Ⱈ
+< ⱉ,Ⱉ
+< ⱊ,Ⱊ
+< ⱋ,Ⱋ
+< ⱌ,Ⱌ
+< ⱍ,Ⱍ
+< ⱎ,Ⱎ
+< ⱏ,Ⱏ
+< ⱐ,Ⱐ
+< ⱑ,Ⱑ
+< ⱒ,Ⱒ
+< ⱓ,Ⱓ
+< ⱔ,Ⱔ
+< ⱕ,Ⱕ
+< ⱖ,Ⱖ
+< ⱗ,Ⱗ
+< ⱘ,Ⱘ
+< ⱙ,Ⱙ
+< ⱚ,Ⱚ
+< ⱛ,Ⱛ
+< ⱜ,Ⱜ
+< ⱝ,Ⱝ
+< ⱞ,Ⱞ
+< ა
+< ⴀ,Ⴀ
+< ბ
+< ⴁ,Ⴁ
+< გ
+< ⴂ,Ⴂ
+< დ
+< ⴃ,Ⴃ
+< ე
+< ⴄ,Ⴄ
+< ვ
+< ⴅ,Ⴅ
+< ზ
+< ⴆ,Ⴆ
+< ჱ
+< ⴡ,Ⴡ
+< თ
+< ⴇ,Ⴇ
+< ი
+< ⴈ,Ⴈ
+< კ
+< ⴉ,Ⴉ
+< ლ
+< ⴊ,Ⴊ
+< მ
+< ⴋ,Ⴋ
+< ნ,ჼ
+< ⴌ,Ⴌ
+< ჲ
+< ⴢ,Ⴢ
+< ო
+< ⴍ,Ⴍ
+< პ
+< ⴎ,Ⴎ
+< ჟ
+< ⴏ,Ⴏ
+< რ
+< ⴐ,Ⴐ
+< ს
+< ⴑ,Ⴑ
+< ტ
+< ⴒ,Ⴒ
+< ჳ
+< ⴣ,Ⴣ
+< უ
+< ⴓ,Ⴓ
+< ფ
+< ⴔ,Ⴔ
+< ქ
+< ⴕ,Ⴕ
+< ღ
+< ⴖ,Ⴖ
+< ყ
+< ⴗ,Ⴗ
+< შ
+< ⴘ,Ⴘ
+< ჩ
+< ⴙ,Ⴙ
+< ც
+< ⴚ,Ⴚ
+< ძ
+< ⴛ,Ⴛ
+< წ
+< ⴜ,Ⴜ
+< ჭ
+< ⴝ,Ⴝ
+< ხ
+< ⴞ,Ⴞ
+< ჴ
+< ⴤ,Ⴤ
+< ჯ
+< ⴟ,Ⴟ
+< ჰ
+< ⴠ,Ⴠ
+< ჵ
+< ⴥ,Ⴥ
+< ჶ
+< ჷ
+< 2d27,10c7
+< ჸ
+< ჹ
+< ჺ
+< 10fd
+< 2d2d,10cd
+< 10fe
+< 10ff
+< ա,Ա
+< բ,Բ
+< գ,Գ
+< դ,Դ
+< ե,Ե
+< զ,Զ
+< է,Է
+< ը,Ը
+< թ,Թ
+< ժ,Ժ
+< ի,Ի
+< լ,Լ
+< խ,Խ
+< ծ,Ծ
+< կ,Կ
+< հ,Հ
+< ձ,Ձ
+< ղ,Ղ
+< ճ,Ճ
+< մ,Մ
+< յ,Յ
+< ն,Ն
+< շ,Շ
+< ո,Ո
+< չ,Չ
+< պ,Պ
+< ջ,Ջ
+< ռ,Ռ
+< ս,Ս
+< վ,Վ
+< տ,Տ
+< ր,Ր
+< ց,Ց
+< ւ,Ւ
+< փ,Փ
+< ք,Ք
+< օ,Օ
+< ֆ,Ֆ
+< ՙ
+< א,ℵ,ﬡ ; אַ ; אָ ; אּ
+< ב,ℶ ; בּ ; בֿ
+< ג,ℷ ; גּ
+< ד,ℸ,ﬢ ; דּ
+< ה,ﬣ ; הּ
+< ו ; וֹ ; וּ
+< ז ; זּ
+< ח
+< ט ; טּ
+< י ; יִ ; יּ
+< כ,ﬤ,ך ; כּ,ךּ ; כֿ
+< ל,ﬥ ; לּ
+< מ,ﬦ,ם ; מּ
+< נ,ן ; נּ
+< ס ; סּ
+< ע,ﬠ
+< פ,ף ; פּ,ףּ ; פֿ
+< צ,ץ ; צּ
+< ק ; קּ
+< ר,ﬧ ; רּ
+< ש ; שׂ ; שׁ ; שּ ; שּׂ ; שּׁ
+< ת,ﬨ ; תּ
+< ࠀ
+< ࠁ
+< ࠂ
+< ࠃ
+< ࠄ
+< ࠅ
+< ࠆ
+< ࠇ
+< ࠈ
+< ࠉ
+< ࠊ
+< ࠋ
+< ࠌ
+< ࠍ
+< ࠎ
+< ࠏ
+< ࠐ
+< ࠑ
+< ࠒ
+< ࠓ
+< ࠔ
+< ࠕ
+< 0816
+< 0817
+< ࠚ
+< 081b
+< ء,ٴ,ﺀ ; ۽
+< آ,ﺂ,ﺁ
+< أ,ﺄ,ﺃ
+< ٲ
+< ٱ,ﭑ,ﭐ
+< ؤ,ﺆ,ﺅ
+< إ,ﺈ,ﺇ
+< ٳ
+< ݳ
+< ݴ
+< ئ,ﺋ,ﺌ,ﺊ,ﺉ
+< 08a8
+< 08a9
+< 08ac
+< ا,ﺎ,ﺍ ; ﴼ,ﴽ
+< ٮ
+< ب,ﺑ,ﺒ,ﺐ,ﺏ
+< ٻ,ﭔ,ﭕ,ﭓ,ﭒ
+< پ,ﭘ,ﭙ,ﭗ,ﭖ
+< ڀ,ﭜ,ﭝ,ﭛ,ﭚ
+< ݐ
+< ݑ
+< ݒ
+< ݓ
+< ݔ
+< ݕ
+< 08a0
+< ݖ
+< ة,ﺔ,ﺓ
+< ت,ﺗ,ﺘ,ﺖ,ﺕ
+< ث,ﺛ,ﺜ,ﺚ,ﺙ
+< ٹ,ﭨ,ﭩ,ﭧ,ﭦ
+< ٺ,ﭠ,ﭡ,ﭟ,ﭞ
+< ټ
+< ٽ
+< ٿ,ﭤ,ﭥ,ﭣ,ﭢ
+< ج,ﺟ,ﺠ,ﺞ,ﺝ
+< ڃ,ﭸ,ﭹ,ﭷ,ﭶ
+< ڄ,ﭴ,ﭵ,ﭳ,ﭲ
+< چ,ﭼ,ﭽ,ﭻ,ﭺ
+< ڿ
+< ڇ,ﮀ,ﮁ,ﭿ,ﭾ
+< 08a2
+< ح,ﺣ,ﺤ,ﺢ,ﺡ
+< خ,ﺧ,ﺨ,ﺦ,ﺥ
+< ځ
+< ڂ
+< څ
+< ݗ
+< ݘ
+< ݮ
+< ݯ
+< ݲ
+< ݼ
+< د,ﺪ,ﺩ
+< ذ,ﺬ,ﺫ ; ﱛ
+< ڈ,ﮉ,ﮈ
+< ډ
+< ڊ
+< ڋ
+< ڌ,ﮅ,ﮄ
+< ڍ,ﮃ,ﮂ
+< ڎ,ﮇ,ﮆ
+< ڏ
+< ڐ
+< ۮ
+< ݙ
+< ݚ
+< ر,ﺮ,ﺭ ; ﱜ
+< ز,ﺰ,ﺯ
+< ڑ,ﮍ,ﮌ
+< ڒ
+< ړ
+< ڔ
+< ڕ
+< ږ
+< ڗ
+< ژ,ﮋ,ﮊ
+< ڙ
+< ۯ
+< ݛ
+< ݫ
+< ݬ
+< ݱ
+< 08aa
+< س,ﺳ,ﺴ,ﺲ,ﺱ
+< ش,ﺷ,ﺸ,ﺶ,ﺵ
+< ښ
+< ڛ
+< ڜ
+< ۺ
+< ݜ
+< ݭ
+< ݰ
+< ݽ
+< ݾ
+< ص,ﺻ,ﺼ,ﺺ,ﺹ
+< ض,ﺿ,ﻀ,ﺾ,ﺽ
+< ڝ
+< ڞ
+< ۻ
+< ط,ﻃ,ﻄ,ﻂ,ﻁ
+< ظ,ﻇ,ﻈ,ﻆ,ﻅ
+< ڟ
+< 08a3
+< ع,ﻋ,ﻌ,ﻊ,ﻉ
+< غ,ﻏ,ﻐ,ﻎ,ﻍ
+< ڠ
+< ۼ
+< ݝ
+< ݞ
+< ݟ
+< ف,ﻓ,ﻔ,ﻒ,ﻑ
+< ڡ
+< ڢ
+< ڣ
+< ڤ,ﭬ,ﭭ,ﭫ,ﭪ
+< 08a4
+< ڥ
+< ڦ,ﭰ,ﭱ,ﭯ,ﭮ
+< ݠ
+< ݡ
+< ٯ
+< ق,ﻗ,ﻘ,ﻖ,ﻕ
+< ڧ
+< ڨ
+< 08a5
+< ك,ﻛ,ﻜ,ﻚ,ﻙ
+< ک,ﮐ,ﮑ,ﮏ,ﮎ
+< ڪ
+< ګ
+< ڬ
+< ݿ
+< ڭ,ﯕ,ﯖ,ﯔ,ﯓ
+< ڮ
+< گ,ﮔ,ﮕ,ﮓ,ﮒ
+< ڰ
+< ڱ,ﮜ,ﮝ,ﮛ,ﮚ
+< ڲ
+< ڳ,ﮘ,ﮙ,ﮗ,ﮖ
+< ڴ
+< ݢ
+< ػ
+< ؼ
+< ݣ
+< ݤ
+< ل,ﻟ,ﻠ,ﻞ,ﻝ
+< ڵ
+< ڶ
+< ڷ
+< ڸ
+< ݪ
+< 08a6
+< م,ﻣ,ﻤ,ﻢ,ﻡ ; ۾
+< ݥ
+< ݦ
+< 08a7
+< ن,ﻧ,ﻨ,ﻦ,ﻥ
+< ں,ﮟ,ﮞ
+< ڻ,ﮢ,ﮣ,ﮡ,ﮠ
+< ڼ
+< ڽ
+< ڹ
+< ݧ
+< ݨ
+< ݩ
+< ه,ﻫ,ﻬ,ﻪ,ﻩ ; ﳙ
+< ھ,ﮬ,ﮭ,ﮫ,ﮪ
+< ہ,ﮨ,ﮩ,ﮧ,ﮦ ; ۂ
+< ۃ
+< ۿ
+< ە ; ۀ,ﮥ,ﮤ
+< و,ۥ,ﻮ,ﻭ
+< ۄ
+< ۅ,ﯡ,ﯠ
+< ۆ,ﯚ,ﯙ
+< ۇ,ﯘ,ﯗ
+< ۈ,ﯜ,ﯛ
+< ۉ,ﯣ,ﯢ
+< ۊ
+< ۋ,ﯟ,ﯞ
+< ۏ
+< ݸ
+< ݹ
+< 08ab
+< ى,ﯨ,ﯩ,ﻰ,ﻯ ; ﲐ,ﱝ
+< ي,ۦ,ﻳ,ﻴ,ﻲ,ﻱ
+< ی,ﯾ,ﯿ,ﯽ,ﯼ
+< ۍ
+< ێ
+< ې,ﯦ,ﯧ,ﯥ,ﯤ
+< ۑ
+< ؽ
+< ؾ
+< ؿ
+< ؠ
+< ݵ
+< ݶ
+< ݷ
+< ے,ﮯ,ﮮ ; ۓ,ﮱ,ﮰ
+< ݺ
+< ݻ
+< ܐ
+< ܒ ; ܭ
+< ܓ ; ܔ ; ܮ
+< ܖ
+< ܕ ; ܯ
+< ܗ
+< ܘ
+< ܙ
+< ݍ
+< ܚ
+< ܛ ; ܜ
+< ܝ
+< ܞ
+< ܟ
+< ݎ
+< ܠ
+< ܡ
+< ܢ
+< ܣ,ܤ
+< ܥ
+< ܦ ; ܧ
+< ݏ
+< ܨ
+< ܩ
+< ܪ
+< ܫ
+< ܬ
+< ࡀ
+< ࡁ
+< ࡂ
+< ࡃ
+< ࡄ
+< ࡅ
+< ࡆ
+< ࡇ
+< ࡈ
+< ࡉ
+< ࡊ
+< ࡋ
+< ࡌ
+< ࡍ
+< ࡎ
+< ࡏ
+< ࡐ
+< ࡑ
+< ࡒ
+< ࡓ
+< ࡔ
+< ࡕ
+< ࡖ
+< ࡗ
+< ࡘ
+< ހ
+< ޙ
+< ޚ
+< ށ
+< ނ
+< ރ
+< ޜ
+< ބ
+< ޅ
+< ކ
+< އ
+< ޢ
+< ޣ
+< ވ
+< ޥ
+< މ
+< ފ
+< ދ
+< ޛ
+< ތ
+< ޘ
+< ޠ
+< ޡ
+< ލ
+< ގ
+< ޤ
+< ޏ
+< ސ
+< ޝ
+< ޞ
+< ޟ
+< ޑ
+< ޒ
+< ޓ
+< ޔ
+< ޕ
+< ޖ
+< ޗ
+< ޱ
+< 07a6
+< 07a7
+< 07a8
+< 07a9
+< 07aa
+< 07ab
+< 07ac
+< 07ad
+< 07ae
+< 07af
+< 07b0
+< ߊ
+< ߋ
+< ߌ
+< ߍ
+< ߎ
+< ߏ
+< ߐ
+< ߑ
+< ߒ
+< ߓ
+< ߔ
+< ߕ
+< ߖ ; ߨ
+< ߗ ; ߩ
+< ߘ
+< ߙ ; ߪ
+< ߚ
+< ߛ
+< ߜ
+< ߝ
+< ߞ
+< ߟ
+< ߠ
+< ߡ
+< ߢ
+< ߣ
+< ߤ
+< ߥ
+< ߦ
+< ߧ
+< ߴ
+< ߵ
+< ⴰ
+< ⴱ
+< ⴲ
+< ⴳ
+< ⴴ
+< ⴵ
+< ⴶ
+< ⴷ
+< ⴸ
+< ⴹ
+< ⴺ
+< ⴻ
+< 2d66
+< ⴼ
+< ⴽ
+< ⴾ
+< ⴿ
+< ⵀ
+< ⵁ
+< ⵂ
+< ⵃ
+< ⵄ
+< ⵅ
+< ⵆ
+< ⵇ
+< ⵈ
+< ⵉ
+< ⵊ
+< ⵋ
+< ⵌ
+< ⵍ
+< ⵎ
+< ⵏ
+< ⵐ
+< ⵑ
+< ⵒ
+< ⵓ
+< 2d67
+< ⵔ
+< ⵕ
+< ⵖ
+< ⵗ
+< ⵘ
+< ⵙ
+< ⵚ
+< ⵛ
+< ⵜ
+< ⵝ
+< ⵞ
+< ⵟ
+< ⵠ
+< ⵡ
+< ⵢ
+< ⵣ
+< ⵤ
+< ⵥ
+< ⵯ
+< ሀ
+< ሁ
+< ሂ
+< ሃ
+< ሄ
+< ህ
+< ሆ
+< ሇ
+< ለ
+< ሉ
+< ሊ
+< ላ
+< ሌ
+< ል
+< ሎ
+< ሏ
+< ⶀ
+< ሐ
+< ሑ
+< ሒ
+< ሓ
+< ሔ
+< ሕ
+< ሖ
+< ሗ
+< መ
+< ሙ
+< ሚ
+< ማ
+< ሜ
+< ም
+< ሞ
+< ሟ
+< ᎀ
+< ᎁ
+< ᎂ
+< ᎃ
+< ⶁ
+< ሠ
+< ሡ
+< ሢ
+< ሣ
+< ሤ
+< ሥ
+< ሦ
+< ሧ
+< ረ
+< ሩ
+< ሪ
+< ራ
+< ሬ
+< ር
+< ሮ
+< ሯ
+< ⶂ
+< ሰ
+< ሱ
+< ሲ
+< ሳ
+< ሴ
+< ስ
+< ሶ
+< ሷ
+< ⶃ
+< ꬁ
+< ꬂ
+< ꬃ
+< ꬄ
+< ꬅ
+< ꬆ
+< ሸ
+< ሹ
+< ሺ
+< ሻ
+< ሼ
+< ሽ
+< ሾ
+< ሿ
+< ⶄ
+< ቀ
+< ቁ
+< ቂ
+< ቃ
+< ቄ
+< ቅ
+< ቆ
+< ቇ
+< ቈ
+< ቊ
+< ቋ
+< ቌ
+< ቍ
+< ቐ
+< ቑ
+< ቒ
+< ቓ
+< ቔ
+< ቕ
+< ቖ
+< ቘ
+< ቚ
+< ቛ
+< ቜ
+< ቝ
+< በ
+< ቡ
+< ቢ
+< ባ
+< ቤ
+< ብ
+< ቦ
+< ቧ
+< ᎄ
+< ᎅ
+< ᎆ
+< ᎇ
+< ⶅ
+< ቨ
+< ቩ
+< ቪ
+< ቫ
+< ቬ
+< ቭ
+< ቮ
+< ቯ
+< ተ
+< ቱ
+< ቲ
+< ታ
+< ቴ
+< ት
+< ቶ
+< ቷ
+< ⶆ
+< ቸ
+< ቹ
+< ቺ
+< ቻ
+< ቼ
+< ች
+< ቾ
+< ቿ
+< ⶇ
+< ኀ
+< ኁ
+< ኂ
+< ኃ
+< ኄ
+< ኅ
+< ኆ
+< ኇ
+< ኈ
+< ኊ
+< ኋ
+< ኌ
+< ኍ
+< ነ
+< ኑ
+< ኒ
+< ና
+< ኔ
+< ን
+< ኖ
+< ኗ
+< ⶈ
+< ኘ
+< ኙ
+< ኚ
+< ኛ
+< ኜ
+< ኝ
+< ኞ
+< ኟ
+< ⶉ
+< አ
+< ኡ
+< ኢ
+< ኣ
+< ኤ
+< እ
+< ኦ
+< ኧ
+< ⶊ
+< ከ
+< ኩ
+< ኪ
+< ካ
+< ኬ
+< ክ
+< ኮ
+< ኯ
+< ኰ
+< ኲ
+< ኳ
+< ኴ
+< ኵ
+< ኸ
+< ኹ
+< ኺ
+< ኻ
+< ኼ
+< ኽ
+< ኾ
+< ዀ
+< ዂ
+< ዃ
+< ዄ
+< ዅ
+< ወ
+< ዉ
+< ዊ
+< ዋ
+< ዌ
+< ው
+< ዎ
+< ዏ
+< ዐ
+< ዑ
+< ዒ
+< ዓ
+< ዔ
+< ዕ
+< ዖ
+< ዘ
+< ዙ
+< ዚ
+< ዛ
+< ዜ
+< ዝ
+< ዞ
+< ዟ
+< ⶋ
+< ꬑ
+< ꬒ
+< ꬓ
+< ꬔ
+< ꬕ
+< ꬖ
+< ዠ
+< ዡ
+< ዢ
+< ዣ
+< ዤ
+< ዥ
+< ዦ
+< ዧ
+< የ
+< ዩ
+< ዪ
+< ያ
+< ዬ
+< ይ
+< ዮ
+< ዯ
+< ደ
+< ዱ
+< ዲ
+< ዳ
+< ዴ
+< ድ
+< ዶ
+< ዷ
+< ⶌ
+< ꬉ
+< ꬊ
+< ꬋ
+< ꬌ
+< ꬍ
+< ꬎ
+< ዸ
+< ዹ
+< ዺ
+< ዻ
+< ዼ
+< ዽ
+< ዾ
+< ዿ
+< ⶍ
+< ጀ
+< ጁ
+< ጂ
+< ጃ
+< ጄ
+< ጅ
+< ጆ
+< ጇ
+< ⶎ
+< ገ
+< ጉ
+< ጊ
+< ጋ
+< ጌ
+< ግ
+< ጎ
+< ጏ
+< ጐ
+< ጒ
+< ጓ
+< ጔ
+< ጕ
+< ጘ
+< ጙ
+< ጚ
+< ጛ
+< ጜ
+< ጝ
+< ጞ
+< ጟ
+< ⶓ
+< ⶔ
+< ⶕ
+< ⶖ
+< ጠ
+< ጡ
+< ጢ
+< ጣ
+< ጤ
+< ጥ
+< ጦ
+< ጧ
+< ⶏ
+< ጨ
+< ጩ
+< ጪ
+< ጫ
+< ጬ
+< ጭ
+< ጮ
+< ጯ
+< ⶐ
+< ꬠ
+< ꬡ
+< ꬢ
+< ꬣ
+< ꬤ
+< ꬥ
+< ꬦ
+< ጰ
+< ጱ
+< ጲ
+< ጳ
+< ጴ
+< ጵ
+< ጶ
+< ጷ
+< ⶑ
+< ጸ
+< ጹ
+< ጺ
+< ጻ
+< ጼ
+< ጽ
+< ጾ
+< ጿ
+< ꬨ
+< ꬩ
+< ꬪ
+< ꬫ
+< ꬬ
+< ꬭ
+< ꬮ
+< ፀ
+< ፁ
+< ፂ
+< ፃ
+< ፄ
+< ፅ
+< ፆ
+< ፇ
+< ፈ
+< ፉ
+< ፊ
+< ፋ
+< ፌ
+< ፍ
+< ፎ
+< ፏ
+< ᎈ
+< ᎉ
+< ᎊ
+< ᎋ
+< ፐ
+< ፑ
+< ፒ
+< ፓ
+< ፔ
+< ፕ
+< ፖ
+< ፗ
+< ᎌ
+< ᎍ
+< ᎎ
+< ᎏ
+< ⶒ
+< ፘ
+< ፙ
+< ፚ
+< ⶠ
+< ⶡ
+< ⶢ
+< ⶣ
+< ⶤ
+< ⶥ
+< ⶦ
+< ⶨ
+< ⶩ
+< ⶪ
+< ⶫ
+< ⶬ
+< ⶭ
+< ⶮ
+< ⶰ
+< ⶱ
+< ⶲ
+< ⶳ
+< ⶴ
+< ⶵ
+< ⶶ
+< ⶸ
+< ⶹ
+< ⶺ
+< ⶻ
+< ⶼ
+< ⶽ
+< ⶾ
+< ⷀ
+< ⷁ
+< ⷂ
+< ⷃ
+< ⷄ
+< ⷅ
+< ⷆ
+< ⷈ
+< ⷉ
+< ⷊ
+< ⷋ
+< ⷌ
+< ⷍ
+< ⷎ
+< ⷐ
+< ⷑ
+< ⷒ
+< ⷓ
+< ⷔ
+< ⷕ
+< ⷖ
+< ⷘ
+< ⷙ
+< ⷚ
+< ⷛ
+< ⷜ
+< ⷝ
+< ⷞ
+< ॐ
+< ॲ
+< ऄ
+< अ
+< आ
+< ॳ
+< ॴ
+< ॵ
+< ॶ
+< ॷ
+< इ
+< ई
+< उ
+< ऊ
+< ऋ
+< ॠ
+< ऌ
+< ॡ
+< ऍ
+< ऎ
+< ए
+< ऐ
+< ऑ
+< ऒ
+< ओ
+< औ
+< क ; क़
+< ख ; ख़
+< ग ; ग़
+< ॻ
+< घ
+< ङ
+< च
+< छ
+< ज ; ज़
+< ॹ
+< ॼ
+< झ
+< ञ
+< ट
+< ठ
+< ड ; ड़
+< ॾ
+< ढ ; ढ़
+< ण
+< त
+< थ
+< द
+< ध
+< न ; ऩ
+< प
+< फ ; फ़
+< ब
+< ॿ
+< भ
+< म
+< य ; य़
+< ॺ
+< र ; ऱ
+< ल
+< ळ ; ऴ
+< व
+< श
+< ष
+< स
+< ह
+< ऽ
+< ॽ
+< ᳩ,ᳪ=ᳫ=ᳬ=ᳮ=ᳯ=ᳰ=ᳱ
+< 1cf5
+< 1cf6
+< ꣲ,ꣳ=ꣴ=ꣵ=ꣶ=ꣷ
+< ꣻ
+< ा
+< 093a
+< ऻ
+< ॏ
+< 0956
+< 0957
+< ि
+< ी
+< 0941
+< 0942
+< 0943
+< 0944
+< 0962
+< 0963
+< 0945
+< 0955
+< 0946
+< 0947
+< ॎ
+< 0948
+< ॉ
+< ॊ
+< ो
+< ौ
+< 094d
+< অ
+< আ
+< ই
+< ঈ
+< উ
+< ঊ
+< ঋ
+< ৠ
+< ঌ
+< ৡ
+< এ
+< ঐ
+< ও
+< ঔ
+< ক
+< খ
+< গ
+< ঘ
+< ঙ
+< চ
+< ছ
+< জ
+< ঝ
+< ঞ
+< ট
+< ঠ
+< ড ; ড়
+< ঢ ; ঢ়
+< ণ
+< ত
+< থ
+< দ
+< ধ
+< ন
+< প
+< ফ
+< ব
+< ভ
+< ম
+< য ; য়
+< র
+< ৰ
+< ল
+< ৱ
+< শ
+< ষ
+< স
+< হ
+< ঽ
+< া
+< ি
+< ী
+< 09c1
+< 09c2
+< 09c3
+< 09c4
+< 09e2
+< 09e3
+< ে
+< ৈ
+< ো
+< ৌ
+< 09cd
+< ৗ
+< ੴ
+< ੳ
+< ਉ
+< ਊ
+< ਓ
+< ਅ
+< ਆ
+< ਐ
+< ਔ
+< ੲ
+< ਇ
+< ਈ
+< ਏ
+< ਸ ; ਸ਼
+< ਹ
+< 0a51
+< ਕ
+< ਖ ; ਖ਼
+< ਗ ; ਗ਼
+< ਘ
+< ਙ
+< ਚ
+< ਛ
+< ਜ ; ਜ਼
+< ਝ
+< ਞ
+< ਟ
+< ਠ
+< ਡ
+< ਢ
+< ਣ
+< ਤ
+< ਥ
+< ਦ
+< ਧ
+< ਨ
+< ਪ
+< ਫ ; ਫ਼
+< ਬ
+< ਭ
+< ਮ
+< ਯ
+< 0a75
+< ਰ
+< ਲ ; ਲ਼
+< ਵ
+< ੜ
+< ਾ
+< ਿ
+< ੀ
+< 0a41
+< 0a42
+< 0a47
+< 0a48
+< 0a4b
+< 0a4c
+< 0a4d
+< ૐ
+< અ
+< આ
+< ઇ
+< ઈ
+< ઉ
+< ઊ
+< ઋ
+< ૠ
+< ઌ
+< ૡ
+< ઍ
+< એ
+< ઐ
+< ઑ
+< ઓ
+< ઔ
+< ક
+< ખ
+< ગ
+< ઘ
+< ઙ
+< ચ
+< છ
+< જ
+< ઝ
+< ઞ
+< ટ
+< ઠ
+< ડ
+< ઢ
+< ણ
+< ત
+< થ
+< દ
+< ધ
+< ન
+< પ
+< ફ
+< બ
+< ભ
+< મ
+< ય
+< ર
+< લ
+< વ
+< શ
+< ષ
+< સ
+< હ
+< ળ
+< ઽ
+< ા
+< િ
+< ી
+< 0ac1
+< 0ac2
+< 0ac3
+< 0ac4
+< 0ae2
+< 0ae3
+< 0ac5
+< 0ac7
+< 0ac8
+< ૉ
+< ો
+< ૌ
+< 0acd
+< ଅ
+< ଆ
+< ଇ
+< ଈ
+< ଉ
+< ଊ
+< ଋ
+< ୠ
+< ଌ
+< ୡ
+< ଏ
+< ଐ
+< ଓ
+< ଔ
+< କ
+< ଖ
+< ଗ
+< ଘ
+< ଙ
+< ଚ
+< ଛ
+< ଜ
+< ଝ
+< ଞ
+< ଟ
+< ଠ
+< ଡ ; ଡ଼
+< ଢ ; ଢ଼
+< ଣ
+< ତ
+< ଥ
+< ଦ
+< ଧ
+< ନ
+< ପ
+< ଫ
+< ବ
+< ଭ
+< ମ
+< ଯ
+< ୟ
+< ର
+< ଲ
+< ଳ
+< ଵ
+< ୱ
+< ଶ
+< ଷ
+< ସ
+< ହ
+< ଽ
+< ା
+< 0b3f
+< ୀ
+< 0b41
+< 0b42
+< 0b43
+< 0b44
+< 0b62
+< 0b63
+< େ
+< ୈ
+< ୋ
+< ୌ
+< 0b4d
+< 0b56
+< ୗ
+< ௐ
+< அ
+< ஆ
+< இ
+< ஈ
+< உ
+< ஊ
+< எ
+< ஏ
+< ஐ
+< ஒ
+< ஓ
+< ஔ
+< ஃ
+< க
+< ங
+< ச
+< ஞ
+< ட
+< ண
+< த
+< ந
+< ப
+< ம
+< ய
+< ர
+< ல
+< வ
+< ழ
+< ள
+< ற
+< ன
+< ஜ
+< ஶ
+< ஷ
+< ஸ
+< ஹ
+< ா
+< ி
+< 0bc0
+< ு
+< ூ
+< ெ
+< ே
+< ை
+< ொ
+< ோ
+< ௌ
+< 0bcd
+< ௗ
+< అ
+< ఆ
+< ఇ
+< ఈ
+< ఉ
+< ఊ
+< ఋ
+< ౠ
+< ఌ
+< ౡ
+< ఎ
+< ఏ
+< ఐ
+< ఒ
+< ఓ
+< ఔ
+< క
+< ఖ
+< గ
+< ఘ
+< ఙ
+< చ
+< ౘ
+< ఛ
+< జ
+< ౙ
+< ఝ
+< ఞ
+< ట
+< ఠ
+< డ
+< ఢ
+< ణ
+< త
+< థ
+< ద
+< ధ
+< న
+< ప
+< ఫ
+< బ
+< భ
+< మ
+< య
+< ర
+< ఱ
+< ల
+< వ
+< శ
+< ష
+< స
+< హ
+< ళ
+< ఽ
+< 0c3e
+< 0c3f
+< 0c40
+< ు
+< ూ
+< ృ
+< ౄ
+< 0c62
+< 0c63
+< 0c46
+< 0c47
+< 0c48
+< 0c4a
+< 0c4b
+< 0c4c
+< 0c4d
+< 0c55
+< 0c56
+< ಅ
+< ಆ
+< ಇ
+< ಈ
+< ಉ
+< ಊ
+< ಋ
+< ೠ
+< ಌ
+< ೡ
+< ಎ
+< ಏ
+< ಐ
+< ಒ
+< ಓ
+< ಔ
+< ಕ
+< ಖ
+< ಗ
+< ಘ
+< ಙ
+< ಚ
+< ಛ
+< ಜ
+< ಝ
+< ಞ
+< ಟ
+< ಠ
+< ಡ
+< ಢ
+< ಣ
+< ತ
+< ಥ
+< ದ
+< ಧ
+< ನ
+< ಪ
+< ಫ
+< ಬ
+< ಭ
+< ಮ
+< ಯ
+< ರ
+< ಱ
+< ಲ
+< ವ
+< ಶ
+< ಷ
+< ಸ
+< ಹ
+< ಳ
+< ೞ
+< ಽ
+< ೱ
+< ೲ
+< ಾ
+< 0cbf
+< ೀ
+< ು
+< ೂ
+< ೃ
+< ೄ
+< 0ce2
+< 0ce3
+< 0cc6
+< ೇ
+< ೈ
+< ೊ
+< ೋ
+< 0ccc
+< 0ccd
+< ೕ
+< ೖ
+< അ
+< ആ
+< ഇ
+< ഈ
+< ഉ
+< ഊ
+< ഋ
+< ൠ
+< ഌ
+< ൡ
+< എ
+< ഏ
+< ഐ
+< ഒ
+< ഓ
+< ഔ
+< ക
+< ഖ
+< ഗ
+< ഘ
+< ങ
+< ച
+< ഛ
+< ജ
+< ഝ
+< ഞ
+< ട
+< ഠ
+< ഡ
+< ഢ
+< ണ
+< ത
+< ഥ
+< ദ
+< ധ
+< ന
+< ഩ
+< പ
+< ഫ
+< ബ
+< ഭ
+< മ
+< യ
+< ര
+< ല
+< വ
+< ശ
+< ഷ
+< സ
+< ഹ
+< ള
+< ഴ
+< റ
+< ഺ
+< ഽ
+< ാ
+< ി
+< ീ
+< 0d41
+< 0d42
+< 0d43
+< 0d44
+< 0d62
+< 0d63
+< െ
+< േ
+< ൈ
+< ൊ
+< ോ
+< ൌ
+< ൗ
+< 0d4d
+< අ
+< ආ
+< ඇ
+< ඈ
+< ඉ
+< ඊ
+< උ
+< ඌ
+< ඍ
+< ඎ
+< ඏ
+< ඐ
+< එ
+< ඒ
+< ඓ
+< ඔ
+< ඕ
+< ඖ
+< ක
+< ඛ
+< ග
+< ඝ
+< ඞ
+< ඟ
+< ච
+< ඡ
+< ජ
+< ඣ
+< ඤ
+< ඥ
+< ඦ
+< ට
+< ඨ
+< ඩ
+< ඪ
+< ණ
+< ඬ
+< ත
+< ථ
+< ද
+< ධ
+< න
+< ඳ
+< ප
+< ඵ
+< බ
+< භ
+< ම
+< ඹ
+< ය
+< ර
+< ල
+< ව
+< ශ
+< ෂ
+< ස
+< හ
+< ළ
+< ෆ
+< ා
+< ැ
+< ෑ
+< 0dd2
+< 0dd3
+< 0dd4
+< 0dd6
+< ෘ
+< ෲ
+< ෟ
+< ෳ
+< ෙ
+< ේ
+< ෛ
+< ො
+< ෝ
+< ෞ
+< 0dca
+< aaf2
+< ꯀ
+< ꯁ
+< ꯂ
+< ꯃ
+< ꯄ
+< ꯅ
+< ꯆ
+< ꯇ
+< ꯈ
+< ꯉ
+< ꯊ
+< ꯋ
+< ꯌ
+< ꯍ
+< ꯎ
+< ꯏ
+< ꯐ
+< ꯑ
+< ꯒ
+< ꯓ
+< ꯔ
+< ꯕ
+< ꯖ
+< ꯗ
+< ꯘ
+< ꯙ
+< ꯚ
+< aae0
+< aae1
+< aae2
+< aae3
+< aae4
+< aae5
+< aae6
+< aae7
+< aae8
+< aae9
+< aaea
+< ꯣ
+< ꯤ
+< abe5
+< ꯦ
+< ꯧ
+< abe8
+< ꯩ
+< ꯪ
+< aaeb
+< aaec
+< aaed
+< aaee
+< aaef
+< aaf5
+< ꯛ
+< ꯜ
+< ꯝ
+< ꯞ
+< ꯟ
+< ꯠ
+< ꯡ
+< ꯢ
+< abed
+< aaf6
+< ꠀ
+< ꠁ
+< a802
+< ꠃ
+< ꠄ
+< ꠅ
+< a806
+< ꠇ
+< ꠈ
+< ꠉ
+< ꠊ
+< ꠌ
+< ꠍ
+< ꠎ
+< ꠏ
+< ꠐ
+< ꠑ
+< ꠒ
+< ꠓ
+< ꠔ
+< ꠕ
+< ꠖ
+< ꠗ
+< ꠘ
+< ꠙ
+< ꠚ
+< ꠛ
+< ꠜ
+< ꠝ
+< ꠞ
+< ꠟ
+< ꠠ
+< ꠡ
+< ꠢ
+< ꠣ
+< ꠤ
+< a825
+< a826
+< ꠧ
+< ꢂ
+< ꢃ
+< ꢄ
+< ꢅ
+< ꢆ
+< ꢇ
+< ꢈ
+< ꢉ
+< ꢊ
+< ꢋ
+< ꢌ
+< ꢍ
+< ꢎ
+< ꢏ
+< ꢐ
+< ꢑ
+< ꢒ
+< ꢓ
+< ꢔ
+< ꢕ
+< ꢖ
+< ꢗ
+< ꢘ
+< ꢙ
+< ꢚ
+< ꢛ
+< ꢜ
+< ꢝ
+< ꢞ
+< ꢟ
+< ꢠ
+< ꢡ
+< ꢢ
+< ꢣ
+< ꢤ
+< ꢥ
+< ꢦ
+< ꢧ
+< ꢨ
+< ꢩ
+< ꢪ
+< ꢫ
+< ꢬ
+< ꢭ
+< ꢮ
+< ꢯ
+< ꢰ
+< ꢱ
+< ꢲ
+< ꢳ
+< ꢴ
+< ꢵ
+< ꢶ
+< ꢷ
+< ꢸ
+< ꢹ
+< ꢺ
+< ꢻ
+< ꢼ
+< ꢽ
+< ꢾ
+< ꢿ
+< ꣀ
+< ꣁ
+< ꣂ
+< ꣃ
+< a8c4
+< ᮃ,1bba
+< ᮄ
+< ᮅ
+< ᮆ
+< ᮇ
+< ᮈ
+< ᮉ
+< ᮊ,1bbe
+< ᮮ
+< ᮋ
+< ᮌ
+< ᮍ
+< ᮎ
+< ᮏ
+< ᮐ
+< ᮑ
+< ᮒ
+< ᮓ
+< ᮔ
+< ᮕ
+< ᮖ
+< ᮗ
+< ᮘ
+< 1bbd
+< ᮙ,1bbf
+< 1bac
+< ᮚ
+< ᮡ
+< ᮛ
+< 1ba2
+< 1bbb
+< ᮜ
+< 1ba3
+< 1bbc
+< ᮝ
+< 1bad
+< ᮞ
+< ᮟ
+< ᮯ
+< ᮠ
+< 1ba4
+< 1ba5
+< ᮦ
+< ᮧ
+< 1ba8
+< 1ba9
+< ᮪
+< 1bab
+< ก
+< ข
+< ฃ
+< ค
+< ฅ
+< ฆ
+< ง
+< จ
+< ฉ
+< ช
+< ซ
+< ฌ
+< ญ
+< ฎ
+< ฏ
+< ฐ
+< ฑ
+< ฒ
+< ณ
+< ด
+< ต
+< ถ
+< ท
+< ธ
+< น
+< บ
+< ป
+< ผ
+< ฝ
+< พ
+< ฟ
+< ภ
+< ม
+< ย
+< ร
+< ฤ
+< ล
+< ฦ
+< ว
+< ศ
+< ษ
+< ส
+< ห
+< ฬ
+< อ
+< ฮ
+< ฯ
+< ะ
+< 0e31
+< า
+< ำ
+< 0e34
+< 0e35
+< 0e36
+< 0e37
+< 0e38
+< 0e39
+< 0e3a
+< เ
+< แ
+< โ
+< ใ
+< ไ
+< ๅ
+< 0ede
+< ກ
+< ຂ
+< ຄ
+< ງ
+< ຈ
+< ສ
+< ຊ
+< 0edf
+< ຍ
+< ດ
+< ຕ
+< ຖ
+< ທ
+< ນ
+< ບ
+< ປ
+< ຜ
+< ຝ
+< ພ
+< ຟ
+< ມ
+< ຢ
+< ຣ
+< ລ
+< ວ
+< ຫ
+< ອ
+< ຮ
+< ຯ
+< ະ
+< 0eb1
+< າ
+< ຳ
+< 0eb4
+< 0eb5
+< 0eb6
+< 0eb7
+< 0eb8
+< 0eb9
+< 0ebb
+< 0ebc
+< ຽ
+< ເ
+< ແ
+< ໂ
+< ໃ
+< ໄ
+< ꪀ
+< ꪁ
+< ꪂ
+< ꪃ
+< ꪄ
+< ꪅ
+< ꪆ
+< ꪇ
+< ꪈ
+< ꪉ
+< ꪊ
+< ꪋ
+< ꪌ
+< ꪍ
+< ꪎ
+< ꪏ
+< ꪐ
+< ꪑ
+< ꪒ
+< ꪓ
+< ꪔ
+< ꪕ
+< ꪖ
+< ꪗ
+< ꪘ
+< ꪙ
+< ꪚ
+< ꪛ
+< ꪜ
+< ꪝ
+< ꪞ
+< ꪟ
+< ꪠ
+< ꪡ
+< ꪢ
+< ꪣ
+< ꪤ
+< ꪥ
+< ꪦ
+< ꪧ
+< ꪨ
+< ꪩ
+< ꪪ
+< ꪫ
+< ꪬ
+< ꪭ
+< ꪮ
+< ꪯ
+< aab0
+< ꪱ
+< aab2
+< aab3
+< aab4
+< ꪵ
+< ꪶ
+< aab7
+< aab8
+< ꪹ
+< ꪺ
+< ꪻ
+< ꪼ
+< ꪽ
+< aabe
+< ꫀ
+< ꫂ
+< ꫛ
+< ꫜ
+< ཀ
+< 0f90
+< ཫ
+< ཁ
+< 0f91
+< ག
+< 0f92
+< ང
+< 0f94
+< ཅ
+< 0f95
+< ཆ
+< 0f96
+< ཇ
+< 0f97
+< ཉ
+< 0f99
+< ཊ
+< 0f9a
+< ཋ
+< 0f9b
+< ཌ
+< 0f9c
+< ཎ
+< 0f9e
+< ཏ
+< 0f9f
+< ཐ
+< 0fa0
+< ད
+< 0fa1
+< ན
+< 0fa3
+< པ
+< 0fa4
+< ཕ
+< 0fa5
+< བ
+< 0fa6
+< མ
+< 0fa8
+< ཙ
+< 0fa9
+< ཚ
+< 0faa
+< ཛ
+< 0fab
+< ཝ
+< 0fad ; 0fba
+< ཞ
+< 0fae
+< ཟ
+< 0faf
+< འ
+< 0fb0
+< ཡ
+< 0fb1 ; 0fbb
+< ར ; ཪ
+< 0fb2 ; 0fbc
+< ཬ
+< ལ
+< 0fb3
+< ཤ
+< 0fb4
+< ཥ
+< 0fb5
+< ས
+< 0fb6
+< ཧ
+< 0fb7
+< ཨ
+< 0fb8
+< ྈ
+< 0f8d
+< ྉ
+< 0f8e
+< ྌ
+< 0f8f
+< ྊ
+< ྋ
+< 0f71
+< 0f72
+< 0f73
+< 0f80
+< 0f81
+< 0f74
+< 0f75
+< 0f76
+< 0f77
+< 0f78
+< 0f79
+< 0f7a
+< 0f7b
+< 0f7c
+< 0f7d
+< 0f84
+< ᰀ
+< ᰁ
+< ᰂ
+< ᰃ
+< ᰄ
+< ᰅ
+< ᰆ
+< ᰇ
+< ᰈ
+< ᰉ
+< ᱍ
+< ᱎ
+< ᱏ
+< ᰊ
+< ᰋ
+< ᰌ
+< ᰍ
+< ᰎ
+< ᰏ
+< ᰐ
+< ᰑ
+< ᰒ
+< ᰓ
+< ᰔ
+< ᰕ
+< ᰖ
+< ᰗ
+< ᰘ
+< ᰙ
+< ᰚ
+< ᰤ
+< ᰛ
+< ᰥ
+< ᰜ
+< ᰝ
+< ᰞ
+< ᰟ
+< ᰠ
+< ᰡ
+< ᰢ
+< ᰣ
+< 1c36
+< ᰦ
+< ᰧ
+< ᰨ
+< ᰩ
+< ᰪ
+< ᰫ
+< 1c2c
+< 1c2d
+< 1c2e
+< 1c2f
+< 1c30
+< 1c31
+< 1c32
+< 1c33
+< ᰴ
+< ᰵ
+< ꡀ
+< ꡁ
+< ꡂ
+< ꡃ
+< ꡄ
+< ꡅ
+< ꡆ
+< ꡇ
+< ꡩ
+< ꡪ
+< ꡫ
+< ꡬ
+< ꡈ
+< ꡉ
+< ꡊ
+< ꡋ
+< ꡌ
+< ꡍ
+< ꡎ
+< ꡏ
+< ꡐ
+< ꡑ
+< ꡒ
+< ꡓ
+< ꡧ
+< ꡔ
+< ꡕ
+< ꡖ
+< ꡗ
+< ꡨ
+< ꡭ
+< ꡘ
+< ꡱ
+< ꡲ
+< ꡙ
+< ꡚ
+< ꡮ
+< ꡛ
+< ꡜ
+< ꡯ
+< ꡰ
+< ꡝ
+< ꡢ
+< ꡣ
+< ꡤ
+< ꡥ
+< ꡞ
+< ꡟ
+< ꡠ
+< ꡡ
+< ꡦ
+< ꡳ
+< ᤀ
+< ᤁ
+< ᤂ
+< ᤃ
+< ᤄ
+< ᤅ
+< ᤆ
+< ᤇ
+< ᤈ
+< ᤉ
+< ᤊ
+< ᤋ
+< ᤌ
+< ᤍ
+< ᤎ
+< ᤏ
+< ᤐ
+< ᤑ
+< ᤒ
+< ᤓ
+< ᤔ
+< ᤕ
+< ᤖ
+< ᤗ
+< ᤘ
+< ᤙ
+< ᤚ
+< ᤛ
+< ᤜ
+< 1920
+< 1921
+< 1922
+< ᤣ
+< ᤤ
+< ᤥ
+< ᤦ
+< 1927
+< 1928
+< ᤩ
+< ᤪ
+< ᤫ
+< ᤰ
+< ᤱ
+< 1932
+< ᤳ
+< ᤴ
+< ᤵ
+< ᤶ
+< ᤷ
+< ᤸ
+< ᜀ
+< ᜁ
+< ᜂ
+< ᜃ
+< ᜄ
+< ᜅ
+< ᜆ
+< ᜇ
+< ᜈ
+< ᜉ
+< ᜊ
+< ᜋ
+< ᜌ
+< ᜎ
+< ᜏ
+< ᜐ
+< ᜑ
+< 1712
+< 1713
+< 1714
+< ᜠ
+< ᜡ
+< ᜢ
+< ᜣ
+< ᜤ
+< ᜥ
+< ᜦ
+< ᜧ
+< ᜨ
+< ᜩ
+< ᜪ
+< ᜫ
+< ᜬ
+< ᜭ
+< ᜮ
+< ᜯ
+< ᜰ
+< ᜱ
+< 1732
+< 1733
+< 1734
+< ᝀ
+< ᝁ
+< ᝂ
+< ᝃ
+< ᝄ
+< ᝅ
+< ᝆ
+< ᝇ
+< ᝈ
+< ᝉ
+< ᝊ
+< ᝋ
+< ᝌ
+< ᝍ
+< ᝎ
+< ᝏ
+< ᝐ
+< ᝑ
+< 1752
+< 1753
+< ᝠ
+< ᝡ
+< ᝢ
+< ᝣ
+< ᝤ
+< ᝥ
+< ᝦ
+< ᝧ
+< ᝨ
+< ᝩ
+< ᝪ
+< ᝫ
+< ᝬ
+< ᝮ
+< ᝯ
+< ᝰ
+< 1772
+< 1773
+< ᨀ
+< ᨁ
+< ᨂ
+< ᨃ
+< ᨄ
+< ᨅ
+< ᨆ
+< ᨇ
+< ᨈ
+< ᨉ
+< ᨊ
+< ᨋ
+< ᨌ
+< ᨍ
+< ᨎ
+< ᨏ
+< ᨐ
+< ᨑ
+< ᨒ
+< ᨓ
+< ᨔ
+< ᨕ
+< ᨖ
+< 1a17
+< 1a18
+< ᨙ
+< ᨚ
+< ᨛ
+< ᯀ,ᯁ
+< ᯂ,ᯃ=ᯄ
+< ᯅ,ᯆ
+< ᯇ,ᯈ
+< ᯉ,ᯊ
+< ᯋ,ᯌ=ᯍ
+< ᯎ,ᯏ
+< ᯐ
+< ᯑ
+< ᯒ,ᯓ
+< ᯔ,ᯕ
+< ᯖ,ᯗ
+< ᯘ,ᯙ=ᯚ
+< ᯛ,ᯜ
+< ᯝ
+< ᯞ,ᯟ
+< ᯠ
+< ᯡ
+< ᯢ
+< ᯣ
+< ᯤ
+< ᯥ
+< ᯧ,1be8
+< 1be9
+< ᯪ,ᯫ
+< ᯬ,1bed
+< ᯮ,1bef
+< 1bf0
+< 1bf1
+< ᯲
+< ᯳
+< ꤰ
+< ꤱ
+< ꤲ
+< ꤳ
+< ꤴ
+< ꤵ
+< ꤶ
+< ꤷ
+< ꤸ
+< ꤹ
+< ꤺ
+< ꤻ
+< ꤼ
+< ꤽ
+< ꤾ
+< ꤿ
+< ꥀ
+< ꥁ
+< ꥂ
+< ꥃ
+< ꥄ
+< ꥅ
+< ꥆ
+< a947
+< a948
+< a949
+< a94a
+< a94b
+< a94c
+< a94d
+< a94e
+< a94f
+< a950
+< a951
+< ꥒ
+< ꥓
+< ꤊ
+< ꤋ
+< ꤌ
+< ꤍ
+< ꤎ
+< ꤏ
+< ꤐ
+< ꤑ
+< ꤒ
+< ꤓ
+< ꤔ
+< ꤕ
+< ꤖ
+< ꤗ
+< ꤘ
+< ꤙ
+< ꤚ
+< ꤛ
+< ꤜ
+< ꤝ
+< ꤞ
+< ꤟ
+< ꤠ
+< ꤡ
+< ꤢ
+< ꤣ
+< ꤤ
+< ꤥ
+< a926
+< a927
+< a928
+< a929
+< a92a
+< က
+< ၵ
+< ခ
+< ၶ
+< ဂ
+< ၷ
+< ꩠ
+< ဃ
+< င
+< ၚ
+< စ
+< ၸ
+< ꩡ
+< ဆ
+< ꩢ
+< ဇ
+< ꩣ
+< ၹ
+< ꩲ
+< ဈ
+< ၛ
+< ꩤ
+< ၡ
+< ဉ
+< ၺ
+< ꩥ
+< ည
+< ဋ
+< ꩦ
+< ဌ
+< ꩧ
+< ဍ
+< ꩨ
+< ဎ
+< ꩩ
+< ဏ
+< ၮ
+< တ
+< ထ
+< ဒ
+< ၻ
+< ဓ
+< ꩪ
+< န
+< ၼ
+< ꩫ
+< 105e
+< ပ
+< ဖ
+< ၽ
+< ၾ
+< ꩯ
+< ႎ
+< ဗ
+< ၿ
+< ဘ
+< မ
+< 105f
+< ယ
+< ျ
+< ရ
+< ꩳ
+< ꩺ
+< ြ
+< လ
+< 1060
+< ဝ
+< 103d
+< 1082
+< ႀ
+< ၐ
+< ၑ
+< ၥ
+< သ
+< ꩬ
+< ဟ
+< ႁ
+< ꩭ
+< 103e
+< ꩮ
+< ꩱ
+< ဠ
+< ၜ
+< ၝ
+< ၯ
+< ၰ
+< ၦ
+< အ
+< ဢ
+< ဣ
+< ဤ
+< ဥ
+< ဦ
+< ၒ
+< ၓ
+< ၔ
+< ၕ
+< ဧ
+< ဨ
+< ဩ
+< ဪ
+< ာ,ါ
+< ႃ
+< 1072
+< ႜ
+< 102d
+< 1071
+< 102e
+< 1033
+< 102f
+< 1073
+< 1074
+< 1030
+< ၖ
+< ၗ
+< 1058
+< 1059
+< ေ
+< ႄ
+< 1035
+< 1085
+< 1032
+< 109d
+< 1034
+< ၢ
+< ၧ
+< ၨ
+< 1086
+< 1039
+< 103a
+< ၣ
+< ၤ
+< ၩ
+< ၪ
+< ၫ
+< ၬ
+< ၭ
+< ႇ
+< ႋ
+< ႈ
+< ႌ
+< ႉ
+< ႊ
+< ႏ
+< ႚ
+< ႛ
+< ꩻ
+< ꩴ
+< ꩵ
+< ꩶ
+< ក
+< ខ
+< គ
+< ឃ
+< ង
+< ច
+< ឆ
+< ជ
+< ឈ
+< ញ
+< ដ
+< ឋ
+< ឌ
+< ឍ
+< ណ
+< ត
+< ថ
+< ទ
+< ធ
+< ន
+< ប
+< ផ
+< ព
+< ភ
+< ម
+< យ
+< រ
+< ល
+< វ
+< ឝ
+< ឞ
+< ស
+< ហ
+< ឡ
+< អ
+< ៜ
+< ឣ
+< ឤ
+< ឥ
+< ឦ
+< ឧ
+< ឨ
+< ឩ
+< ឪ
+< ឫ
+< ឬ
+< ឭ
+< ឮ
+< ឯ
+< ឰ
+< ឱ
+< ឲ
+< ឳ
+< ា
+< 17b7
+< 17b8
+< 17b9
+< 17ba
+< 17bb
+< 17bc
+< 17bd
+< ើ
+< ឿ
+< ៀ
+< េ
+< ែ
+< ៃ
+< ោ
+< ៅ
+< 17d2
+< ᥐ
+< ᥑ
+< ᥒ
+< ᥓ
+< ᥔ
+< ᥕ
+< ᥖ
+< ᥗ
+< ᥘ
+< ᥙ
+< ᥚ
+< ᥛ
+< ᥜ
+< ᥝ
+< ᥞ
+< ᥟ
+< ᥠ
+< ᥡ
+< ᥢ
+< ᥣ
+< ᥤ
+< ᥥ
+< ᥦ
+< ᥧ
+< ᥨ
+< ᥩ
+< ᥪ
+< ᥫ
+< ᥬ
+< ᥭ
+< ᥰ
+< ᥱ
+< ᥲ
+< ᥳ
+< ᥴ
+< ᦀ
+< ᦁ
+< ᦂ
+< ᦃ
+< ᦄ
+< ᦅ
+< ᦆ
+< ᦇ
+< ᦈ
+< ᦉ
+< ᦊ
+< ᦋ
+< ᦌ
+< ᦍ
+< ᦎ
+< ᦏ
+< ᦐ
+< ᦑ
+< ᦒ
+< ᦓ
+< ᦔ
+< ᦕ
+< ᦖ
+< ᦗ
+< ᦘ
+< ᦙ
+< ᦚ
+< ᦛ
+< ᦜ
+< ᦝ
+< ᦞ
+< ᦟ
+< ᦠ
+< ᦡ
+< ᦢ
+< ᦣ
+< ᦤ
+< ᦥ
+< ᦦ
+< ᦧ
+< ᦨ
+< ᦩ
+< ᦪ
+< ᦫ
+< ᦰ
+< ᦱ
+< ᦲ
+< ᦳ
+< ᦴ
+< ᦵ
+< ᦶ
+< ᦷ
+< ᦸ
+< ᦹ
+< ᦺ
+< ᦻ
+< ᦼ
+< ᦽ
+< ᦾ
+< ᦿ
+< ᧀ
+< ᧁ
+< ᧂ
+< ᧃ
+< ᧄ
+< ᧅ
+< ᧆ
+< ᧇ
+< ᧈ
+< ᧉ
+< ᨠ
+< ᨡ
+< ᨢ
+< ᨣ
+< ᨤ
+< ᨥ
+< ᨦ,1a58=1a59
+< ᨧ
+< ᨨ
+< ᨩ
+< ᨪ
+< ᨫ
+< ᨬ
+< ᨭ
+< ᨮ
+< ᨯ
+< ᨰ
+< ᨱ
+< ᨲ
+< ᨳ
+< ᨴ
+< ᨵ
+< ᨶ
+< ᨷ
+< ᨸ
+< ᨹ
+< ᨺ
+< ᨻ,1a5a=1a5b
+< ᨼ
+< ᨽ
+< ᨾ
+< ᨿ
+< ᩀ
+< ᩁ
+< ᩂ
+< ᩃ
+< ᩄ
+< ᩅ
+< ᩆ
+< ᩇ
+< ᩈ
+< ᩉ
+< ᩊ
+< ᩋ
+< ᩌ
+< ᩓ
+< 1a6b
+< ᩕ
+< 1a56
+< ᩗ
+< 1a5c
+< 1a5d
+< 1a5e
+< ᩍ
+< ᩎ
+< ᩏ
+< ᩐ
+< ᩑ
+< ᩒ
+< ᩡ
+< 1a6c
+< 1a62
+< ᩣ,ᩤ
+< 1a65
+< 1a66
+< 1a67
+< 1a68
+< 1a69
+< 1a6a
+< ᩮ
+< ᩯ
+< 1a73
+< ᩰ
+< ᩱ
+< ᩲ
+< ᩭ
+< 1a60
+< ꨀ
+< ꨁ
+< ꨂ
+< ꨃ
+< ꨄ
+< ꨅ
+< ꨆ
+< ꨇ
+< ꨈ
+< ꨉ
+< ꨊ
+< ꨋ
+< ꨌ
+< ꨍ
+< ꨎ
+< ꨏ
+< ꨐ
+< ꨑ
+< ꨒ
+< ꨓ
+< ꨔ
+< ꨕ
+< ꨖ
+< ꨗ
+< ꨘ
+< ꨙ
+< ꨚ
+< ꨛ
+< ꨜ
+< ꨝ
+< ꨞ
+< ꨟ
+< ꨠ
+< ꨡ
+< ꨢ
+< ꨣ
+< ꨤ
+< ꨥ
+< ꨦ
+< ꨧ
+< ꨨ
+< ꨳ
+< ꨴ
+< aa35
+< aa36
+< aa29
+< aa2a
+< aa2b
+< aa2c
+< aa2d
+< aa2e
+< ꨯ
+< ꨰ
+< aa31
+< aa32
+< ꩀ
+< ꩁ
+< ꩂ
+< aa43
+< ꩄ
+< ꩅ
+< ꩆ
+< ꩇ
+< ꩈ
+< ꩉ
+< ꩊ
+< ꩋ
+< aa4c
+< ꩍ
+< ᬅ
+< ᬆ
+< ᬇ
+< ᬈ
+< ᬉ
+< ᬊ
+< ᬋ
+< ᬌ
+< ᬍ
+< ᬎ
+< ᬏ
+< ᬐ
+< ᬑ
+< ᬒ
+< ᬓ
+< ᭅ
+< ᭆ
+< ᬔ
+< ᬕ
+< ᬖ
+< ᬗ
+< ᬘ
+< ᬙ
+< ᬚ
+< ᬛ
+< ᬜ
+< ᬝ
+< ᬞ
+< ᬟ
+< ᬠ
+< ᬡ
+< ᬢ
+< ᭇ
+< ᬣ
+< ᬤ
+< ᬥ
+< ᬦ
+< ᬧ
+< ᭈ
+< ᬨ
+< ᬩ
+< ᬪ
+< ᬫ
+< ᬬ
+< ᬭ
+< ᬮ
+< ᬯ
+< ᭉ
+< ᬰ
+< ᬱ
+< ᬲ
+< ᭊ
+< ᭋ
+< ᬳ
+< ᬵ
+< 1b36
+< 1b37
+< 1b38
+< 1b39
+< 1b3a
+< ᬻ
+< 1b3c
+< ᬽ
+< ᬾ
+< ᬿ
+< ᭀ
+< ᭁ
+< 1b42
+< ᭃ
+< ᭄
+< ꦄ
+< ꦅ
+< ꦆ
+< ꦇ
+< ꦈ
+< ꦉ
+< ꦊ
+< ꦋ
+< ꦌ
+< ꦍ
+< ꦎ
+< ꦏ
+< ꦐ
+< ꦑ
+< ꦒ
+< ꦓ
+< ꦔ
+< ꦕ
+< ꦖ
+< ꦗ
+< ꦘ
+< ꦙ
+< ꦚ
+< ꦛ
+< ꦜ
+< ꦝ
+< ꦞ
+< ꦟ
+< ꦠ
+< ꦡ
+< ꦢ
+< ꦣ
+< ꦤ
+< ꦥ
+< ꦦ
+< ꦧ
+< ꦨ
+< ꦩ
+< ꦪ
+< ꦾ
+< ꦫ,ꦬ
+< ꦿ
+< ꦭ
+< ꦮ
+< ꦯ
+< ꦰ
+< ꦱ
+< ꦲ
+< ꦴ
+< a9bc
+< a9b6
+< a9b7
+< a9b8
+< a9b9
+< ꦽ
+< ꦺ
+< ꦻ
+< ꦵ
+< ꧀
+< ᢀ
+< ᢁ
+< ᢂ
+< ᢃ
+< ᢄ
+< ᢅ
+< ᢆ
+< ᡃ
+< ᠠ
+< ᢇ
+< ᠡ
+< ᡄ
+< ᡝ
+< ᠢ
+< ᡅ
+< ᡞ
+< ᡳ
+< ᢈ
+< ᡟ
+< ᠣ
+< ᡆ
+< ᠤ
+< ᡇ
+< ᡡ
+< ᠥ
+< ᡈ
+< ᠦ
+< ᡉ
+< ᡠ
+< ᠧ
+< ᠨ
+< ᠩ
+< ᡊ
+< ᡢ
+< ᢊ
+< ᢛ
+< ᠪ
+< ᡋ
+< ᠫ
+< ᡌ
+< ᡦ
+< ᠬ
+< ᡍ
+< ᠭ
+< ᡎ
+< ᡤ
+< ᢚ
+< ᡥ
+< ᠮ
+< ᡏ
+< ᠯ
+< ᠰ
+< ᠱ
+< ᡧ
+< ᢜ
+< ᢝ
+< ᢢ
+< ᢤ
+< ᢥ
+< ᠲ
+< ᡐ
+< ᡨ
+< ᠳ
+< ᡑ
+< ᡩ
+< ᠴ
+< ᡒ
+< ᡱ
+< ᡜ
+< ᢋ
+< ᠵ
+< ᡓ
+< ᡪ
+< ᡷ
+< ᠶ
+< ᡕ
+< ᡲ
+< ᠷ
+< ᡵ
+< ᠸ
+< ᡖ
+< ᠹ
+< ᡫ
+< ᡶ
+< ᠺ
+< ᡗ
+< ᡣ
+< ᡴ
+< ᢉ
+< ᠻ
+< ᠼ
+< ᡔ
+< ᡮ
+< ᠽ
+< ᡯ
+< ᡘ
+< ᡬ
+< ᠾ
+< ᡙ
+< ᡭ
+< ᠿ
+< ᡀ
+< ᡁ
+< ᡂ
+< ᡚ
+< ᡛ
+< ᡰ
+< ᢌ
+< ᢞ
+< ᢍ
+< ᢎ
+< ᢟ
+< ᢏ
+< ᢐ
+< ᢘ
+< ᢠ
+< ᢑ
+< ᢡ
+< ᢒ
+< ᢓ
+< ᢨ
+< ᢔ
+< ᢣ
+< ᢕ
+< ᢙ
+< ᢖ
+< ᢗ
+< ᢦ
+< ᢧ
+< ᢪ
+< 18a9
+< ᱚ
+< ᱛ
+< ᱜ
+< ᱝ
+< ᱞ
+< ᱟ
+< ᱠ
+< ᱡ
+< ᱢ
+< ᱣ
+< ᱤ
+< ᱥ
+< ᱦ
+< ᱧ
+< ᱨ
+< ᱩ
+< ᱪ
+< ᱫ
+< ᱬ
+< ᱭ
+< ᱮ
+< ᱯ
+< ᱰ
+< ᱱ
+< ᱲ
+< ᱳ
+< ᱴ
+< ᱵ
+< ᱶ
+< ᱷ
+< ᱸ
+< ᱹ
+< ᱺ
+< ᱻ
+< ᱼ
+< ᱽ
+< Ꭰ
+< Ꭱ
+< Ꭲ
+< Ꭳ
+< Ꭴ
+< Ꭵ
+< Ꭶ
+< Ꭷ
+< Ꭸ
+< Ꭹ
+< Ꭺ
+< Ꭻ
+< Ꭼ
+< Ꭽ
+< Ꭾ
+< Ꭿ
+< Ꮀ
+< Ꮁ
+< Ꮂ
+< Ꮃ
+< Ꮄ
+< Ꮅ
+< Ꮆ
+< Ꮇ
+< Ꮈ
+< Ꮉ
+< Ꮊ
+< Ꮋ
+< Ꮌ
+< Ꮍ
+< Ꮎ
+< Ꮏ
+< Ꮐ
+< Ꮑ
+< Ꮒ
+< Ꮓ
+< Ꮔ
+< Ꮕ
+< Ꮖ
+< Ꮗ
+< Ꮘ
+< Ꮙ
+< Ꮚ
+< Ꮛ
+< Ꮜ
+< Ꮝ
+< Ꮞ
+< Ꮟ
+< Ꮠ
+< Ꮡ
+< Ꮢ
+< Ꮣ
+< Ꮤ
+< Ꮥ
+< Ꮦ
+< Ꮧ
+< Ꮨ
+< Ꮩ
+< Ꮪ
+< Ꮫ
+< Ꮬ
+< Ꮭ
+< Ꮮ
+< Ꮯ
+< Ꮰ
+< Ꮱ
+< Ꮲ
+< Ꮳ
+< Ꮴ
+< Ꮵ
+< Ꮶ
+< Ꮷ
+< Ꮸ
+< Ꮹ
+< Ꮺ
+< Ꮻ
+< Ꮼ
+< Ꮽ
+< Ꮾ
+< Ꮿ
+< Ᏸ
+< Ᏹ
+< Ᏺ
+< Ᏻ
+< Ᏼ
+< ᐁ
+< ᐂ
+< ᐃ
+< ᐄ
+< ᐅ
+< ᐆ
+< ᐇ
+< ᐈ
+< ᐉ
+< ᐊ
+< ᐋ
+< ᐌ
+< ᐍ
+< ᐎ
+< ᐏ
+< ᐐ
+< ᐑ
+< ᐒ
+< ᐓ
+< ᐔ
+< ᐕ
+< ᐖ
+< ᐗ
+< ᐘ
+< ᐙ
+< ᐚ
+< ᐛ
+< ᐜ
+< ᐝ
+< ᐞ
+< ᐟ
+< ᐠ
+< ᐡ
+< ᐢ
+< ᐣ
+< ᐤ
+< ᐥ
+< ᐦ
+< ᐧ
+< ᐨ
+< ᐩ
+< ᐪ
+< ᐫ
+< ᐬ
+< ᐭ
+< ᐮ
+< ᐯ
+< ᐰ
+< ᐱ
+< ᐲ
+< ᐳ
+< ᐴ
+< ᐵ
+< ᐶ
+< ᐷ
+< ᐸ
+< ᐹ
+< ᐺ
+< ᐻ
+< ᐼ
+< ᐽ
+< ᐾ
+< ᐿ
+< ᑀ
+< ᑁ
+< ᑂ
+< ᑃ
+< ᑄ
+< ᑅ
+< ᑆ
+< ᑇ
+< ᑈ
+< ᑉ
+< ᑊ
+< ᑋ
+< ᑌ
+< ᑍ
+< ᑎ
+< ᑏ
+< ᑐ
+< ᑑ
+< ᑒ
+< ᑓ
+< ᑔ
+< ᑕ
+< ᑖ
+< ᑗ
+< ᑘ
+< ᑙ
+< ᑚ
+< ᑛ
+< ᑜ
+< ᑝ
+< ᑞ
+< ᑟ
+< ᑠ
+< ᑡ
+< ᑢ
+< ᑣ
+< ᑤ
+< ᑥ
+< ᑦ
+< ᑧ
+< ᑨ
+< ᑩ
+< ᑪ
+< ᑫ
+< ᑬ
+< ᑭ
+< ᑮ
+< ᑯ
+< ᑰ
+< ᑱ
+< ᑲ
+< ᑳ
+< ᑴ
+< ᑵ
+< ᑶ
+< ᑷ
+< ᑸ
+< ᑹ
+< ᑺ
+< ᑻ
+< ᑼ
+< ᑽ
+< ᑾ
+< ᑿ
+< ᒀ
+< ᒁ
+< ᒂ
+< ᒃ
+< ᒄ
+< ᒅ
+< ᒆ
+< ᒇ
+< ᒈ
+< ᒉ
+< ᒊ
+< ᒋ
+< ᒌ
+< ᒍ
+< ᒎ
+< ᒏ
+< ᒐ
+< ᒑ
+< ᒒ
+< ᒓ
+< ᒔ
+< ᒕ
+< ᒖ
+< ᒗ
+< ᒘ
+< ᒙ
+< ᒚ
+< ᒛ
+< ᒜ
+< ᒝ
+< ᒞ
+< ᒟ
+< ᒠ
+< ᒡ
+< ᒢ
+< ᒣ
+< ᒤ
+< ᒥ
+< ᒦ
+< ᒧ
+< ᒨ
+< ᒩ
+< ᒪ
+< ᒫ
+< ᒬ
+< ᒭ
+< ᒮ
+< ᒯ
+< ᒰ
+< ᒱ
+< ᒲ
+< ᒳ
+< ᒴ
+< ᒵ
+< ᒶ
+< ᒷ
+< ᒸ
+< ᒹ
+< ᒺ
+< ᒻ
+< ᒼ
+< ᒽ
+< ᒾ
+< ᒿ
+< ᓀ
+< ᓁ
+< ᓂ
+< ᓃ
+< ᓄ
+< ᓅ
+< ᓆ
+< ᓇ
+< ᓈ
+< ᓉ
+< ᓊ
+< ᓋ
+< ᓌ
+< ᓍ
+< ᓎ
+< ᓏ
+< ᓐ
+< ᓑ
+< ᓒ
+< ᓓ
+< ᓔ
+< ᓕ
+< ᓖ
+< ᓗ
+< ᓘ
+< ᓙ
+< ᓚ
+< ᓛ
+< ᓜ
+< ᓝ
+< ᓞ
+< ᓟ
+< ᓠ
+< ᓡ
+< ᓢ
+< ᓣ
+< ᓤ
+< ᓥ
+< ᓦ
+< ᓧ
+< ᓨ
+< ᓩ
+< ᓪ
+< ᓫ
+< ᓬ
+< ᓭ
+< ᓮ
+< ᓯ
+< ᓰ
+< ᓱ
+< ᓲ
+< ᓳ
+< ᓴ
+< ᓵ
+< ᓶ
+< ᓷ
+< ᓸ
+< ᓹ
+< ᓺ
+< ᓻ
+< ᓼ
+< ᓽ
+< ᓾ
+< ᓿ
+< ᔀ
+< ᔁ
+< ᔂ
+< ᔃ
+< ᔄ
+< ᔅ
+< ᔆ
+< ᔇ
+< ᔈ
+< ᔉ
+< ᔊ
+< ᔋ
+< ᔌ
+< ᔍ
+< ᔎ
+< ᔏ
+< ᔐ
+< ᔑ
+< ᔒ
+< ᔓ
+< ᔔ
+< ᔕ
+< ᔖ
+< ᔗ
+< ᔘ
+< ᔙ
+< ᔚ
+< ᔛ
+< ᔜ
+< ᔝ
+< ᔞ
+< ᔟ
+< ᔠ
+< ᔡ
+< ᔢ
+< ᔣ
+< ᔤ
+< ᔥ
+< ᔦ
+< ᔧ
+< ᔨ
+< ᔩ
+< ᔪ
+< ᔫ
+< ᔬ
+< ᔭ
+< ᔮ
+< ᔯ
+< ᔰ
+< ᔱ
+< ᔲ
+< ᔳ
+< ᔴ
+< ᔵ
+< ᔶ
+< ᔷ
+< ᔸ
+< ᔹ
+< ᔺ
+< ᔻ
+< ᔼ
+< ᔽ
+< ᔾ
+< ᔿ
+< ᕀ
+< ᕁ
+< ᕂ
+< ᕃ
+< ᕄ
+< ᕅ
+< ᕆ
+< ᕇ
+< ᕈ
+< ᕉ
+< ᕊ
+< ᕋ
+< ᕌ
+< ᕍ
+< ᕎ
+< ᕏ
+< ᕐ
+< ᕑ
+< ᕒ
+< ᕓ
+< ᕔ
+< ᕕ
+< ᕖ
+< ᕗ
+< ᕘ
+< ᕙ
+< ᕚ
+< ᕛ
+< ᕜ
+< ᕝ
+< ᕞ
+< ᕟ
+< ᕠ
+< ᕡ
+< ᕢ
+< ᕣ
+< ᕤ
+< ᕥ
+< ᕦ
+< ᕧ
+< ᕨ
+< ᕩ
+< ᕪ
+< ᕫ
+< ᕬ
+< ᕭ
+< ᕮ
+< ᕯ
+< ᕰ
+< ᕱ
+< ᕲ
+< ᕳ
+< ᕴ
+< ᕵ
+< ᕶ
+< ᕷ
+< ᕸ
+< ᕹ
+< ᕺ
+< ᕻ
+< ᕽ
+< ᙯ
+< ᕾ
+< ᕿ
+< ᖀ
+< ᖁ
+< ᖂ
+< ᖃ
+< ᖄ
+< ᖅ
+< ᖆ
+< ᖇ
+< ᖈ
+< ᖉ
+< ᖊ
+< ᖋ
+< ᖌ
+< ᖍ
+< ᙰ
+< ᖎ
+< ᖏ
+< ᖐ
+< ᖑ
+< ᖒ
+< ᖓ
+< ᖔ
+< ᖕ
+< ᙱ
+< ᙲ
+< ᙳ
+< ᙴ
+< ᙵ
+< ᙶ
+< ᖖ
+< ᖗ
+< ᖘ
+< ᖙ
+< ᖚ
+< ᖛ
+< ᖜ
+< ᖝ
+< ᖞ
+< ᖟ
+< ᖠ
+< ᖡ
+< ᖢ
+< ᖣ
+< ᖤ
+< ᖥ
+< ᖦ
+< ᕼ
+< ᖧ
+< ᖨ
+< ᖩ
+< ᖪ
+< ᖫ
+< ᖬ
+< ᖭ
+< ᖮ
+< ᖯ
+< ᖰ
+< ᖱ
+< ᖲ
+< ᖳ
+< ᖴ
+< ᖵ
+< ᖶ
+< ᖷ
+< ᖸ
+< ᖹ
+< ᖺ
+< ᖻ
+< ᖼ
+< ᖽ
+< ᖾ
+< ᖿ
+< ᗀ
+< ᗁ
+< ᗂ
+< ᗃ
+< ᗄ
+< ᗅ
+< ᗆ
+< ᗇ
+< ᗈ
+< ᗉ
+< ᗊ
+< ᗋ
+< ᗌ
+< ᗍ
+< ᗎ
+< ᗏ
+< ᗐ
+< ᗑ
+< ᗒ
+< ᗓ
+< ᗔ
+< ᗕ
+< ᗖ
+< ᗗ
+< ᗘ
+< ᗙ
+< ᗚ
+< ᗛ
+< ᗜ
+< ᗝ
+< ᗞ
+< ᗟ
+< ᗠ
+< ᗡ
+< ᗢ
+< ᗣ
+< ᗤ
+< ᗥ
+< ᗦ
+< ᗧ
+< ᗨ
+< ᗩ
+< ᗪ
+< ᗫ
+< ᗬ
+< ᗭ
+< ᗮ
+< ᗯ
+< ᗰ
+< ᗱ
+< ᗲ
+< ᗳ
+< ᗴ
+< ᗵ
+< ᗶ
+< ᗷ
+< ᗸ
+< ᗹ
+< ᗺ
+< ᗻ
+< ᗼ
+< ᗽ
+< ᗾ
+< ᗿ
+< ᘀ
+< ᘁ
+< ᘂ
+< ᘃ
+< ᘄ
+< ᘅ
+< ᘆ
+< ᘇ
+< ᘈ
+< ᘉ
+< ᘊ
+< ᘋ
+< ᘌ
+< ᘍ
+< ᘎ
+< ᘏ
+< ᘐ
+< ᘑ
+< ᘒ
+< ᘓ
+< ᘔ
+< ᘕ
+< ᘖ
+< ᘗ
+< ᘘ
+< ᘙ
+< ᘚ
+< ᘛ
+< ᘜ
+< ᘝ
+< ᘞ
+< ᘟ
+< ᘠ
+< ᘡ
+< ᘢ
+< ᘣ
+< ᘤ
+< ᘥ
+< ᘦ
+< ᘧ
+< ᘨ
+< ᘩ
+< ᘪ
+< ᘫ
+< ᘬ
+< ᘭ
+< ᘮ
+< ᘯ
+< ᘰ
+< ᘱ
+< ᘲ
+< ᘳ
+< ᘴ
+< ᘵ
+< ᘶ
+< ᘷ
+< ᘸ
+< ᘹ
+< ᘺ
+< ᘻ
+< ᘼ
+< ᘽ
+< ᘾ
+< ᘿ
+< ᙀ
+< ᙁ
+< ᙂ
+< ᙃ
+< ᙄ
+< ᙅ
+< ᙆ
+< ᙇ
+< ᙈ
+< ᙉ
+< ᙊ
+< ᙋ
+< ᙌ
+< ᙍ
+< ᙎ
+< ᙏ
+< ᙐ
+< ᙑ
+< ᙒ
+< ᙓ
+< ᙔ
+< ᙕ
+< ᙖ
+< ᙗ
+< ᙘ
+< ᙙ
+< ᙚ
+< ᙛ
+< ᙜ
+< ᙝ
+< ᙞ
+< ᙟ
+< ᙠ
+< ᙡ
+< ᙢ
+< ᙣ
+< ᙤ
+< ᙥ
+< ᙦ
+< ᙧ
+< ᙨ
+< ᙩ
+< ᙪ
+< ᙫ
+< ᙬ
+< ᙷ
+< ᙸ
+< ᙹ
+< ᙺ
+< ᙻ
+< ᙼ
+< ᙽ
+< ᙾ
+< ᙿ
+< ᢰ
+< ᢱ
+< ᢲ
+< ᢳ
+< ᢴ
+< ᢵ
+< ᢶ
+< ᢷ
+< ᢸ
+< ᢹ
+< ᢺ
+< ᢻ
+< ᢼ
+< ᢽ
+< ᢾ
+< ᢿ
+< ᣀ
+< ᣁ
+< ᣂ
+< ᣃ
+< ᣄ
+< ᣅ
+< ᣆ
+< ᣇ
+< ᣈ
+< ᣉ
+< ᣊ
+< ᣋ
+< ᣌ
+< ᣍ
+< ᣎ
+< ᣏ
+< ᣐ
+< ᣑ
+< ᣒ
+< ᣓ
+< ᣔ
+< ᣕ
+< ᣖ
+< ᣗ
+< ᣘ
+< ᣙ
+< ᣚ
+< ᣛ
+< ᣜ
+< ᣝ
+< ᣞ
+< ᣟ
+< ᣠ
+< ᣡ
+< ᣢ
+< ᣣ
+< ᣤ
+< ᣥ
+< ᣦ
+< ᣧ
+< ᣨ
+< ᣩ
+< ᣪ
+< ᣫ
+< ᣬ
+< ᣭ
+< ᣮ
+< ᣯ
+< ᣰ
+< ᣱ
+< ᣲ
+< ᣳ
+< ᣴ
+< ᣵ
+< ᚁ
+< ᚂ
+< ᚃ
+< ᚄ
+< ᚅ
+< ᚆ
+< ᚇ
+< ᚈ
+< ᚉ
+< ᚊ
+< ᚋ
+< ᚌ
+< ᚍ
+< ᚎ
+< ᚏ
+< ᚐ
+< ᚑ
+< ᚒ
+< ᚓ
+< ᚔ
+< ᚕ
+< ᚖ
+< ᚗ
+< ᚘ
+< ᚙ
+< ᚚ
+< ᚠ ; ᚡ
+< ᚢ ; ᚤ ; ᚥ
+< ᚦ ; ᚧ
+< ᚨ ; ᚩ ; ᚬ ; ᚭ ; ᚮ
+< ᚯ
+< ᚰ
+< ᚱ
+< ᚲ ; ᚳ ; ᚴ ; ᚵ ; ᚶ
+< ᚷ
+< ᚹ ; ᛩ
+< ᚺ ; ᚻ ; ᚼ ; ᚽ
+< ᚾ ; ᚿ ; ᛀ
+< ᛁ ; ᛂ
+< ᛃ ; ᛄ
+< ᛅ ; ᛆ
+< ᛇ
+< ᛈ ; ᛕ
+< ᛉ
+< ᛊ,ᛎ ; ᛋ ; ᛪ ; ᛌ ; ᛍ
+< ᛏ ; ᛐ ; ᛑ
+< ᛒ ; ᛓ ; ᛔ
+< ᛖ
+< ᛗ ; ᛘ ; ᛙ
+< ᛚ ; ᛛ
+< ᛜ ; ᛝ
+< ᛞ
+< ᛟ
+< ᚪ
+< ᚫ
+< ᚣ
+< ᛠ
+< ᛣ
+< ᚸ
+< ᛤ
+< ᛡ
+< ᛢ
+< ᛥ
+< ᛦ ; ᛧ ; ᛨ
+< ꔀ
+< ꔁ
+< ꔂ
+< ꔃ
+< ꔄ
+< ꔅ
+< ꔆ
+< ꔇ
+< ꔈ
+< ꔉ
+< ꔊ
+< ꔋ
+< ꔌ
+< ꔍ
+< ꔎ
+< ꔏ
+< ꔐ
+< ꔑ
+< ꔒ
+< ꔓ
+< ꔔ
+< ꔕ
+< ꔖ
+< ꔗ
+< ꔘ
+< ꔙ
+< ꔚ
+< ꔛ
+< ꔜ
+< ꔝ
+< ꔞ
+< ꔟ
+< ꔠ
+< ꔡ
+< ꔢ
+< ꔣ
+< ꔤ
+< ꔥ
+< ꔦ
+< ꔧ
+< ꔨ
+< ꔩ
+< ꔪ
+< ꔫ
+< ꔬ
+< ꔭ
+< ꔮ
+< ꔯ
+< ꔰ
+< ꔱ
+< ꔲ
+< ꔳ
+< ꔴ
+< ꔵ
+< ꔶ
+< ꔷ
+< ꔸ
+< ꔹ
+< ꔺ
+< ꔻ
+< ꔼ
+< ꔽ
+< ꔾ
+< ꔿ
+< ꕀ
+< ꕁ
+< ꕂ
+< ꕃ
+< ꕄ
+< ꕅ
+< ꕆ
+< ꕇ
+< ꕈ
+< ꕉ
+< ꕊ
+< ꕋ
+< ꕌ
+< ꕍ
+< ꕎ
+< ꕏ
+< ꕐ
+< ꕑ
+< ꕒ
+< ꕓ
+< ꕔ
+< ꕕ
+< ꕖ
+< ꕗ
+< ꕘ,ꘐ
+< ꕙ
+< ꕚ
+< ꕛ
+< ꕜ
+< ꕝ
+< ꕞ
+< ꕟ
+< ꕠ
+< ꕡ
+< ꕢ
+< ꕣ
+< ꕤ
+< ꕥ
+< ꕦ
+< ꕧ
+< ꕨ
+< ꕩ
+< ꕪ,ꘑ
+< ꕫ
+< ꕬ
+< ꕭ
+< ꕮ,ꘪ
+< ꕯ
+< ꕰ
+< ꕱ
+< ꕲ
+< ꕳ
+< ꕴ
+< ꕵ
+< ꕶ
+< ꕷ
+< ꕸ
+< ꕹ
+< ꕺ
+< ꕻ
+< ꕼ
+< ꕽ
+< ꕾ
+< ꕿ
+< ꖀ
+< ꖁ
+< ꖂ
+< ꖃ
+< ꖄ
+< ꖅ
+< ꖆ
+< ꖇ,ꘒ
+< ꖈ
+< ꖉ
+< ꖊ
+< ꖋ
+< ꖌ
+< ꖍ
+< ꖎ
+< ꖏ
+< ꖐ
+< ꖑ
+< ꖒ
+< ꖓ
+< ꖔ
+< ꖕ
+< ꖖ
+< ꖗ
+< ꖘ
+< ꖙ
+< ꖚ
+< ꖛ
+< ꖜ
+< ꖝ
+< ꖞ
+< ꖟ
+< ꖠ
+< ꖡ
+< ꖢ
+< ꖣ
+< ꖤ
+< ꖥ
+< ꖦ
+< ꖧ
+< ꖨ
+< ꖩ
+< ꖪ
+< ꖫ
+< ꖬ
+< ꖭ
+< ꖮ
+< ꖯ
+< ꖰ
+< ꖱ
+< ꖲ
+< ꖳ
+< ꖴ
+< ꖵ
+< ꖶ
+< ꖷ
+< ꖸ
+< ꖹ
+< ꖺ
+< ꖻ
+< ꖼ
+< ꖽ
+< ꖾ
+< ꖿ
+< ꗀ
+< ꗁ
+< ꗂ
+< ꗃ
+< ꗄ
+< ꗅ
+< ꗆ
+< ꗇ
+< ꗈ
+< ꗉ
+< ꗊ
+< ꗋ
+< ꗌ
+< ꗍ
+< ꗎ
+< ꗏ
+< ꗐ
+< ꗑ,ꘫ
+< ꗒ
+< ꗓ
+< ꗔ
+< ꗕ
+< ꗖ
+< ꗗ
+< ꗘ
+< ꗙ
+< ꗚ
+< ꗛ
+< ꗜ
+< ꗝ
+< ꗞ
+< ꗟ
+< ꗠ
+< ꗡ
+< ꗢ
+< ꗣ
+< ꗤ
+< ꗥ
+< ꗦ
+< ꗧ
+< ꗨ
+< ꗩ
+< ꗪ
+< ꗫ
+< ꗬ
+< ꗭ
+< ꗮ
+< ꗯ
+< ꗰ
+< ꗱ
+< ꗲ
+< ꗳ
+< ꗴ
+< ꗵ
+< ꗶ
+< ꗷ
+< ꗸ
+< ꗹ
+< ꗺ
+< ꗻ
+< ꗼ
+< ꗽ
+< ꗾ
+< ꗿ
+< ꘀ
+< ꘁ
+< ꘂ
+< ꘃ
+< ꘄ
+< ꘅ
+< ꘆ
+< ꘇ
+< ꘈ
+< ꘉ
+< ꘊ
+< ꘋ
+< ꘌ
+< ꚠ
+< ꚡ
+< ꚢ
+< ꚣ
+< ꚤ
+< ꚥ
+< ꚦ
+< ꚧ
+< ꚨ
+< ꚩ
+< ꚪ
+< ꚫ
+< ꚬ
+< ꚭ
+< ꚮ
+< ꚯ
+< ꚰ
+< ꚱ
+< ꚲ
+< ꚳ
+< ꚴ
+< ꚵ
+< ꚶ
+< ꚷ
+< ꚸ
+< ꚹ
+< ꚺ
+< ꚻ
+< ꚼ
+< ꚽ
+< ꚾ
+< ꚿ
+< ꛀ
+< ꛁ
+< ꛂ
+< ꛃ
+< ꛄ
+< ꛅ
+< ꛆ
+< ꛇ
+< ꛈ
+< ꛉ
+< ꛊ
+< ꛋ
+< ꛌ
+< ꛍ
+< ꛎ
+< ꛏ
+< ꛐ
+< ꛑ
+< ꛒ
+< ꛓ
+< ꛔ
+< ꛕ
+< ꛖ
+< ꛗ
+< ꛘ
+< ꛙ
+< ꛚ
+< ꛛ
+< ꛜ
+< ꛝ
+< ꛞ
+< ꛟ
+< ꛠ
+< ꛡ
+< ꛢ
+< ꛣ
+< ꛤ
+< ꛥ
+< ꛦ
+< ꛧ
+< ꛨ
+< ꛩ
+< ꛪ
+< ꛫ
+< ꛬ
+< ꛭ
+< ꛮ
+< ꛯ
+< ᄀ,ㄱ,㉠,ᄀ
+< ᄁ,ㄲ,ᄁ
+< ᄂ,ㄴ,㉡,ᄂ
+< ᄃ,ㄷ,㉢,ᄃ
+< ᄄ,ㄸ,ᄄ
+< ᄅ,ㄹ,㉣,ᄅ
+< ᄆ,ㅁ,㉤,ᄆ
+< ᄇ,ㅂ,㉥,ᄇ
+< ᄈ,ㅃ,ᄈ
+< ᄉ,ㅅ,㉦,ᄉ
+< ᄊ,ㅆ,ᄊ
+< ᄋ,ㅇ,㉧,ᄋ
+< ᄌ,ㅈ,㉨,ᄌ
+< ᄍ,ㅉ,ᄍ
+< ᄎ,ㅊ,㉩,ᄎ
+< ᄏ,ㅋ,㉪,ᄏ
+< ᄐ,ㅌ,㉫,ᄐ
+< ᄑ,ㅍ,㉬,ᄑ
+< ᄒ,ㅎ,㉭,ᄒ
+< ᄓ
+< ᄔ,ㅥ
+< ᄕ,ㅦ
+< ᄖ
+< ᄗ
+< ᄘ
+< ᄙ
+< ᄚ,ㅀ,ᄚ
+< ᄛ
+< ᄜ,ㅮ
+< ᄝ,ㅱ
+< ᄞ,ㅲ
+< ᄟ
+< ᄠ,ㅳ
+< ᄡ,ㅄ,ᄡ
+< ᄢ,ㅴ
+< ᄣ,ㅵ
+< ᄤ
+< ᄥ
+< ᄦ
+< ᄧ,ㅶ
+< ᄨ
+< ᄩ,ㅷ
+< ᄪ
+< ᄫ,ㅸ
+< ᄬ,ㅹ
+< ᄭ,ㅺ
+< ᄮ,ㅻ
+< ᄯ,ㅼ
+< ᄰ
+< ᄱ
+< ᄲ,ㅽ
+< ᄳ
+< ᄴ
+< ᄵ
+< ᄶ,ㅾ
+< ᄷ
+< ᄸ
+< ᄹ
+< ᄺ
+< ᄻ
+< ᄼ
+< ᄽ
+< ᄾ
+< ᄿ
+< ᅀ,ㅿ
+< ᅁ
+< ᅂ
+< ᅃ
+< ᅄ
+< ᅅ
+< ᅆ
+< ᅇ,ㆀ
+< ᅈ
+< ᅉ
+< ᅊ
+< ᅋ
+< ᅌ,ㆁ
+< ᅍ
+< ᅎ
+< ᅏ
+< ᅐ
+< ᅑ
+< ᅒ
+< ᅓ
+< ᅔ
+< ᅕ
+< ᅖ
+< ᅗ,ㆄ
+< ᅘ,ㆅ
+< ᅙ,ㆆ
+< ᅚ
+< ᅛ
+< ᅜ
+< ᅝ
+< ᅞ
+< ꥠ
+< ꥡ
+< ꥢ
+< ꥣ
+< ꥤ
+< ꥥ
+< ꥦ
+< ꥧ
+< ꥨ
+< ꥩ
+< ꥪ
+< ꥫ
+< ꥬ
+< ꥭ
+< ꥮ
+< ꥯ
+< ꥰ
+< ꥱ
+< ꥲ
+< ꥳ
+< ꥴ
+< ꥵ
+< ꥶ
+< ꥷ
+< ꥸ
+< ꥹ
+< ꥺ
+< ꥻ
+< ꥼ
+< ᅟ
+< ᅠ,ㅤ,ᅠ
+< ᅡ,ㅏ,ᅡ
+< ᅢ,ㅐ,ᅢ
+< ᅣ,ㅑ,ᅣ
+< ᅤ,ㅒ,ᅤ
+< ᅥ,ㅓ,ᅥ
+< ᅦ,ㅔ,ᅦ
+< ᅧ,ㅕ,ᅧ
+< ᅨ,ㅖ,ᅨ
+< ᅩ,ㅗ,ᅩ
+< ᅪ,ㅘ,ᅪ
+< ᅫ,ㅙ,ᅫ
+< ᅬ,ㅚ,ᅬ
+< ᅭ,ㅛ,ᅭ
+< ᅮ,ㅜ,ᅮ
+< ᅯ,ㅝ,ᅯ
+< ᅰ,ㅞ,ᅰ
+< ᅱ,ㅟ,ᅱ
+< ᅲ,ㅠ,ᅲ
+< ᅳ,ㅡ,ᅳ
+< ᅴ,ㅢ,ᅴ
+< ᅵ,ㅣ,ᅵ
+< ᅶ
+< ᅷ
+< ᅸ
+< ᅹ
+< ᅺ
+< ᅻ
+< ᅼ
+< ᅽ
+< ᅾ
+< ᅿ
+< ᆀ
+< ᆁ
+< ᆂ
+< ᆃ
+< ᆄ,ㆇ
+< ᆅ,ㆈ
+< ᆆ
+< ᆇ
+< ᆈ,ㆉ
+< ᆉ
+< ᆊ
+< ᆋ
+< ᆌ
+< ᆍ
+< ᆎ
+< ᆏ
+< ᆐ
+< ᆑ,ㆊ
+< ᆒ,ㆋ
+< ᆓ
+< ᆔ,ㆌ
+< ᆕ
+< ᆖ
+< ᆗ
+< ᆘ
+< ᆙ
+< ᆚ
+< ᆛ
+< ᆜ
+< ᆝ
+< ᆞ,ㆍ
+< ᆟ
+< ᆠ
+< ᆡ,ㆎ
+< ᆢ
+< ᆣ
+< ᆤ
+< ᆥ
+< ᆦ
+< ᆧ
+< ힰ
+< ힱ
+< ힲ
+< ힳ
+< ힴ
+< ힵ
+< ힶ
+< ힷ
+< ힸ
+< ힹ
+< ힺ
+< ힻ
+< ힼ
+< ힽ
+< ힾ
+< ힿ
+< ퟀ
+< ퟁ
+< ퟂ
+< ퟃ
+< ퟄ
+< ퟅ
+< ퟆ
+< ᆨ
+< ᆩ
+< ᆪ,ㄳ,ᆪ
+< ᆫ
+< ᆬ,ㄵ,ᆬ
+< ᆭ,ㄶ,ᆭ
+< ᆮ
+< ᆯ
+< ᆰ,ㄺ,ᆰ
+< ᆱ,ㄻ,ᆱ
+< ᆲ,ㄼ,ᆲ
+< ᆳ,ㄽ,ᆳ
+< ᆴ,ㄾ,ᆴ
+< ᆵ,ㄿ,ᆵ
+< ᆶ
+< ᆷ
+< ᆸ
+< ᆹ
+< ᆺ
+< ᆻ
+< ᆼ
+< ᆽ
+< ᆾ
+< ᆿ
+< ᇀ
+< ᇁ
+< ᇂ
+< ᇃ
+< ᇄ
+< ᇅ
+< ᇆ
+< ᇇ,ㅧ
+< ᇈ,ㅨ
+< ᇉ
+< ᇊ
+< ᇋ
+< ᇌ,ㅩ
+< ᇍ
+< ᇎ,ㅪ
+< ᇏ
+< ᇐ
+< ᇑ
+< ᇒ
+< ᇓ,ㅫ
+< ᇔ
+< ᇕ
+< ᇖ
+< ᇗ,ㅬ
+< ᇘ
+< ᇙ,ㅭ
+< ᇚ
+< ᇛ
+< ᇜ
+< ᇝ,ㅯ
+< ᇞ
+< ᇟ,ㅰ
+< ᇠ
+< ᇡ
+< ᇢ
+< ᇣ
+< ᇤ
+< ᇥ
+< ᇦ
+< ᇧ
+< ᇨ
+< ᇩ
+< ᇪ
+< ᇫ
+< ᇬ
+< ᇭ
+< ᇮ
+< ᇯ
+< ᇰ
+< ᇱ,ㆂ
+< ᇲ,ㆃ
+< ᇳ
+< ᇴ
+< ᇵ
+< ᇶ
+< ᇷ
+< ᇸ
+< ᇹ
+< ᇺ
+< ᇻ
+< ᇼ
+< ᇽ
+< ᇾ
+< ᇿ
+< ퟋ
+< ퟌ
+< ퟍ
+< ퟎ
+< ퟏ
+< ퟐ
+< ퟑ
+< ퟒ
+< ퟓ
+< ퟔ
+< ퟕ
+< ퟖ
+< ퟗ
+< ퟘ
+< ퟙ
+< ퟚ
+< ퟛ
+< ퟜ
+< ퟝ
+< ퟞ
+< ퟟ
+< ퟠ
+< ퟡ
+< ퟢ
+< ퟣ
+< ퟤ
+< ퟥ
+< ퟦ
+< ퟧ
+< ퟨ
+< ퟩ
+< ퟪ
+< ퟫ
+< ퟬ
+< ퟭ
+< ퟮ
+< ퟯ
+< ퟰ
+< ퟱ
+< ퟲ
+< ퟳ
+< ퟴ
+< ퟵ
+< ퟶ
+< ퟷ
+< ퟸ
+< ퟹ
+< ퟺ
+< ퟻ
+< ぁ,あ,ァ,ァ,ア,ア,㋐
+< ぃ,い,ィ,ィ,イ,イ,㋑
+< ぅ,う,ゥ,ゥ,ウ,ウ,㋒ ; ゔ,ヴ
+< ぇ,え,ェ,ェ,エ,エ,㋓
+< ぉ,お,ォ,ォ,オ,オ,㋔
+< ゕ,か,ヵ,カ,カ,㋕ ; が,ガ
+< き,キ,キ,㋖ ; ぎ,ギ
+< く,ㇰ,ク,ク,㋗ ; ぐ,グ
+< ゖ,け,ヶ,ケ,ケ,㋘ ; げ,ゲ
+< こ,コ,コ,㋙ ; ご,ゴ
+< さ,サ,サ,㋚ ; ざ,ザ
+< し,ㇱ,シ,シ,㋛ ; じ,ジ
+< す,ㇲ,ス,ス,㋜ ; ず,ズ
+< せ,セ,セ,㋝ ; ぜ,ゼ
+< そ,ソ,ソ,㋞ ; ぞ,ゾ
+< た,タ,タ,㋟ ; だ,ダ
+< ち,チ,チ,㋠ ; ぢ,ヂ
+< っ,つ,ッ,ッ,ツ,ツ,㋡ ; づ,ヅ
+< て,テ,テ,㋢ ; で,デ
+< と,ㇳ,ト,ト,㋣ ; ど,ド
+< な,ナ,ナ,㋤
+< に,ニ,ニ,㋥
+< ぬ,ㇴ,ヌ,ヌ,㋦
+< ね,ネ,ネ,㋧
+< の,ノ,ノ,㋨
+< は,ㇵ,ハ,ハ,㋩ ; ば,バ ; ぱ,パ
+< ひ,ㇶ,ヒ,ヒ,㋪ ; び,ビ ; ぴ,ピ
+< ふ,ㇷ,フ,フ,㋫ ; ぶ,ブ ; ぷ,プ
+< へ,ㇸ,ヘ,ヘ,㋬ ; べ,ベ ; ぺ,ペ
+< ほ,ㇹ,ホ,ホ,㋭ ; ぼ,ボ ; ぽ,ポ
+< ま,マ,マ,㋮
+< み,ミ,ミ,㋯
+< む,ㇺ,ム,ム,㋰
+< め,メ,メ,㋱
+< も,モ,モ,㋲
+< ゃ,や,ャ,ャ,ヤ,ヤ,㋳
+< ゅ,ゆ,ュ,ュ,ユ,ユ,㋴
+< ょ,よ,ョ,ョ,ヨ,ヨ,㋵
+< ら,ㇻ,ラ,ラ,㋶
+< り,ㇼ,リ,リ,㋷
+< る,ㇽ,ル,ル,㋸
+< れ,ㇾ,レ,レ,㋹
+< ろ,ㇿ,ロ,ロ,㋺
+< ゎ,わ,ヮ,ワ,ワ,㋻ ; ヷ
+< ゐ,ヰ,㋼ ; ヸ
+< ゑ,ヱ,㋽ ; ヹ
+< を,ヲ,ヲ,㋾ ; ヺ
+< ん,ン,ン
+< ㄅ ; ㆠ
+< ㄆ,ㆴ
+< ㄇ
+< ㄈ
+< ㄪ
+< ㄉ
+< ㄊ,ㆵ
+< ㄋ
+< ㄌ
+< ㄍ ; ㆣ
+< ㄎ,ㆶ
+< ㄫ
+< ㆭ
+< ㄏ,ㆷ
+< ㄐ ; ㆢ
+< ㄑ
+< ㄒ
+< ㄬ
+< ㄓ
+< ㄔ
+< ㄕ
+< ㄖ
+< ㄗ ; ㆡ
+< ㄘ
+< ㄙ
+< ㆸ
+< ㆹ
+< ㆺ
+< ㄚ ; ㆩ
+< ㄛ ; ㆧ
+< ㆦ
+< ㄜ
+< ㄝ
+< ㆤ ; ㆥ
+< ㄞ ; ㆮ
+< ㄟ
+< ㄠ ; ㆯ
+< ㄡ
+< ㄢ
+< ㄣ
+< ㄤ
+< ㆲ
+< ㄥ
+< ㆰ
+< ㆱ
+< ㆬ
+< ㄦ
+< ㄧ ; ㆪ,ㆳ
+< ㄨ ; ㆫ ; ㆨ
+< ㄩ
+< ㄭ
+< ꀀ
+< ꀁ
+< ꀂ
+< ꀃ
+< ꀄ
+< ꀅ
+< ꀆ
+< ꀇ
+< ꀈ
+< ꀉ
+< ꀊ
+< ꀋ
+< ꀌ
+< ꀍ
+< ꀎ
+< ꀏ
+< ꀐ
+< ꀑ
+< ꀒ
+< ꀓ
+< ꀔ
+< ꀕ
+< ꀖ
+< ꀗ
+< ꀘ
+< ꀙ
+< ꀚ
+< ꀛ
+< ꀜ
+< ꀝ
+< ꀞ
+< ꀟ
+< ꀠ
+< ꀡ
+< ꀢ
+< ꀣ
+< ꀤ
+< ꀥ
+< ꀦ
+< ꀧ
+< ꀨ
+< ꀩ
+< ꀪ
+< ꀫ
+< ꀬ
+< ꀭ
+< ꀮ
+< ꀯ
+< ꀰ
+< ꀱ
+< ꀲ
+< ꀳ
+< ꀴ
+< ꀵ
+< ꀶ
+< ꀷ
+< ꀸ
+< ꀹ
+< ꀺ
+< ꀻ
+< ꀼ
+< ꀽ
+< ꀾ
+< ꀿ
+< ꁀ
+< ꁁ
+< ꁂ
+< ꁃ
+< ꁄ
+< ꁅ
+< ꁆ
+< ꁇ
+< ꁈ
+< ꁉ
+< ꁊ
+< ꁋ
+< ꁌ
+< ꁍ
+< ꁎ
+< ꁏ
+< ꁐ
+< ꁑ
+< ꁒ
+< ꁓ
+< ꁔ
+< ꁕ
+< ꁖ
+< ꁗ
+< ꁘ
+< ꁙ
+< ꁚ
+< ꁛ
+< ꁜ
+< ꁝ
+< ꁞ
+< ꁟ
+< ꁠ
+< ꁡ
+< ꁢ
+< ꁣ
+< ꁤ
+< ꁥ
+< ꁦ
+< ꁧ
+< ꁨ
+< ꁩ
+< ꁪ
+< ꁫ
+< ꁬ
+< ꁭ
+< ꁮ
+< ꁯ
+< ꁰ
+< ꁱ
+< ꁲ
+< ꁳ
+< ꁴ
+< ꁵ
+< ꁶ
+< ꁷ
+< ꁸ
+< ꁹ
+< ꁺ
+< ꁻ
+< ꁼ
+< ꁽ
+< ꁾ
+< ꁿ
+< ꂀ
+< ꂁ
+< ꂂ
+< ꂃ
+< ꂄ
+< ꂅ
+< ꂆ
+< ꂇ
+< ꂈ
+< ꂉ
+< ꂊ
+< ꂋ
+< ꂌ
+< ꂍ
+< ꂎ
+< ꂏ
+< ꂐ
+< ꂑ
+< ꂒ
+< ꂓ
+< ꂔ
+< ꂕ
+< ꂖ
+< ꂗ
+< ꂘ
+< ꂙ
+< ꂚ
+< ꂛ
+< ꂜ
+< ꂝ
+< ꂞ
+< ꂟ
+< ꂠ
+< ꂡ
+< ꂢ
+< ꂣ
+< ꂤ
+< ꂥ
+< ꂦ
+< ꂧ
+< ꂨ
+< ꂩ
+< ꂪ
+< ꂫ
+< ꂬ
+< ꂭ
+< ꂮ
+< ꂯ
+< ꂰ
+< ꂱ
+< ꂲ
+< ꂳ
+< ꂴ
+< ꂵ
+< ꂶ
+< ꂷ
+< ꂸ
+< ꂹ
+< ꂺ
+< ꂻ
+< ꂼ
+< ꂽ
+< ꂾ
+< ꂿ
+< ꃀ
+< ꃁ
+< ꃂ
+< ꃃ
+< ꃄ
+< ꃅ
+< ꃆ
+< ꃇ
+< ꃈ
+< ꃉ
+< ꃊ
+< ꃋ
+< ꃌ
+< ꃍ
+< ꃎ
+< ꃏ
+< ꃐ
+< ꃑ
+< ꃒ
+< ꃓ
+< ꃔ
+< ꃕ
+< ꃖ
+< ꃗ
+< ꃘ
+< ꃙ
+< ꃚ
+< ꃛ
+< ꃜ
+< ꃝ
+< ꃞ
+< ꃟ
+< ꃠ
+< ꃡ
+< ꃢ
+< ꃣ
+< ꃤ
+< ꃥ
+< ꃦ
+< ꃧ
+< ꃨ
+< ꃩ
+< ꃪ
+< ꃫ
+< ꃬ
+< ꃭ
+< ꃮ
+< ꃯ
+< ꃰ
+< ꃱ
+< ꃲ
+< ꃳ
+< ꃴ
+< ꃵ
+< ꃶ
+< ꃷ
+< ꃸ
+< ꃹ
+< ꃺ
+< ꃻ
+< ꃼ
+< ꃽ
+< ꃾ
+< ꃿ
+< ꄀ
+< ꄁ
+< ꄂ
+< ꄃ
+< ꄄ
+< ꄅ
+< ꄆ
+< ꄇ
+< ꄈ
+< ꄉ
+< ꄊ
+< ꄋ
+< ꄌ
+< ꄍ
+< ꄎ
+< ꄏ
+< ꄐ
+< ꄑ
+< ꄒ
+< ꄓ
+< ꄔ
+< ꄕ
+< ꄖ
+< ꄗ
+< ꄘ
+< ꄙ
+< ꄚ
+< ꄛ
+< ꄜ
+< ꄝ
+< ꄞ
+< ꄟ
+< ꄠ
+< ꄡ
+< ꄢ
+< ꄣ
+< ꄤ
+< ꄥ
+< ꄦ
+< ꄧ
+< ꄨ
+< ꄩ
+< ꄪ
+< ꄫ
+< ꄬ
+< ꄭ
+< ꄮ
+< ꄯ
+< ꄰ
+< ꄱ
+< ꄲ
+< ꄳ
+< ꄴ
+< ꄵ
+< ꄶ
+< ꄷ
+< ꄸ
+< ꄹ
+< ꄺ
+< ꄻ
+< ꄼ
+< ꄽ
+< ꄾ
+< ꄿ
+< ꅀ
+< ꅁ
+< ꅂ
+< ꅃ
+< ꅄ
+< ꅅ
+< ꅆ
+< ꅇ
+< ꅈ
+< ꅉ
+< ꅊ
+< ꅋ
+< ꅌ
+< ꅍ
+< ꅎ
+< ꅏ
+< ꅐ
+< ꅑ
+< ꅒ
+< ꅓ
+< ꅔ
+< ꅕ
+< ꅖ
+< ꅗ
+< ꅘ
+< ꅙ
+< ꅚ
+< ꅛ
+< ꅜ
+< ꅝ
+< ꅞ
+< ꅟ
+< ꅠ
+< ꅡ
+< ꅢ
+< ꅣ
+< ꅤ
+< ꅥ
+< ꅦ
+< ꅧ
+< ꅨ
+< ꅩ
+< ꅪ
+< ꅫ
+< ꅬ
+< ꅭ
+< ꅮ
+< ꅯ
+< ꅰ
+< ꅱ
+< ꅲ
+< ꅳ
+< ꅴ
+< ꅵ
+< ꅶ
+< ꅷ
+< ꅸ
+< ꅹ
+< ꅺ
+< ꅻ
+< ꅼ
+< ꅽ
+< ꅾ
+< ꅿ
+< ꆀ
+< ꆁ
+< ꆂ
+< ꆃ
+< ꆄ
+< ꆅ
+< ꆆ
+< ꆇ
+< ꆈ
+< ꆉ
+< ꆊ
+< ꆋ
+< ꆌ
+< ꆍ
+< ꆎ
+< ꆏ
+< ꆐ
+< ꆑ
+< ꆒ
+< ꆓ
+< ꆔ
+< ꆕ
+< ꆖ
+< ꆗ
+< ꆘ
+< ꆙ
+< ꆚ
+< ꆛ
+< ꆜ
+< ꆝ
+< ꆞ
+< ꆟ
+< ꆠ
+< ꆡ
+< ꆢ
+< ꆣ
+< ꆤ
+< ꆥ
+< ꆦ
+< ꆧ
+< ꆨ
+< ꆩ
+< ꆪ
+< ꆫ
+< ꆬ
+< ꆭ
+< ꆮ
+< ꆯ
+< ꆰ
+< ꆱ
+< ꆲ
+< ꆳ
+< ꆴ
+< ꆵ
+< ꆶ
+< ꆷ
+< ꆸ
+< ꆹ
+< ꆺ
+< ꆻ
+< ꆼ
+< ꆽ
+< ꆾ
+< ꆿ
+< ꇀ
+< ꇁ
+< ꇂ
+< ꇃ
+< ꇄ
+< ꇅ
+< ꇆ
+< ꇇ
+< ꇈ
+< ꇉ
+< ꇊ
+< ꇋ
+< ꇌ
+< ꇍ
+< ꇎ
+< ꇏ
+< ꇐ
+< ꇑ
+< ꇒ
+< ꇓ
+< ꇔ
+< ꇕ
+< ꇖ
+< ꇗ
+< ꇘ
+< ꇙ
+< ꇚ
+< ꇛ
+< ꇜ
+< ꇝ
+< ꇞ
+< ꇟ
+< ꇠ
+< ꇡ
+< ꇢ
+< ꇣ
+< ꇤ
+< ꇥ
+< ꇦ
+< ꇧ
+< ꇨ
+< ꇩ
+< ꇪ
+< ꇫ
+< ꇬ
+< ꇭ
+< ꇮ
+< ꇯ
+< ꇰ
+< ꇱ
+< ꇲ
+< ꇳ
+< ꇴ
+< ꇵ
+< ꇶ
+< ꇷ
+< ꇸ
+< ꇹ
+< ꇺ
+< ꇻ
+< ꇼ
+< ꇽ
+< ꇾ
+< ꇿ
+< ꈀ
+< ꈁ
+< ꈂ
+< ꈃ
+< ꈄ
+< ꈅ
+< ꈆ
+< ꈇ
+< ꈈ
+< ꈉ
+< ꈊ
+< ꈋ
+< ꈌ
+< ꈍ
+< ꈎ
+< ꈏ
+< ꈐ
+< ꈑ
+< ꈒ
+< ꈓ
+< ꈔ
+< ꈕ
+< ꈖ
+< ꈗ
+< ꈘ
+< ꈙ
+< ꈚ
+< ꈛ
+< ꈜ
+< ꈝ
+< ꈞ
+< ꈟ
+< ꈠ
+< ꈡ
+< ꈢ
+< ꈣ
+< ꈤ
+< ꈥ
+< ꈦ
+< ꈧ
+< ꈨ
+< ꈩ
+< ꈪ
+< ꈫ
+< ꈬ
+< ꈭ
+< ꈮ
+< ꈯ
+< ꈰ
+< ꈱ
+< ꈲ
+< ꈳ
+< ꈴ
+< ꈵ
+< ꈶ
+< ꈷ
+< ꈸ
+< ꈹ
+< ꈺ
+< ꈻ
+< ꈼ
+< ꈽ
+< ꈾ
+< ꈿ
+< ꉀ
+< ꉁ
+< ꉂ
+< ꉃ
+< ꉄ
+< ꉅ
+< ꉆ
+< ꉇ
+< ꉈ
+< ꉉ
+< ꉊ
+< ꉋ
+< ꉌ
+< ꉍ
+< ꉎ
+< ꉏ
+< ꉐ
+< ꉑ
+< ꉒ
+< ꉓ
+< ꉔ
+< ꉕ
+< ꉖ
+< ꉗ
+< ꉘ
+< ꉙ
+< ꉚ
+< ꉛ
+< ꉜ
+< ꉝ
+< ꉞ
+< ꉟ
+< ꉠ
+< ꉡ
+< ꉢ
+< ꉣ
+< ꉤ
+< ꉥ
+< ꉦ
+< ꉧ
+< ꉨ
+< ꉩ
+< ꉪ
+< ꉫ
+< ꉬ
+< ꉭ
+< ꉮ
+< ꉯ
+< ꉰ
+< ꉱ
+< ꉲ
+< ꉳ
+< ꉴ
+< ꉵ
+< ꉶ
+< ꉷ
+< ꉸ
+< ꉹ
+< ꉺ
+< ꉻ
+< ꉼ
+< ꉽ
+< ꉾ
+< ꉿ
+< ꊀ
+< ꊁ
+< ꊂ
+< ꊃ
+< ꊄ
+< ꊅ
+< ꊆ
+< ꊇ
+< ꊈ
+< ꊉ
+< ꊊ
+< ꊋ
+< ꊌ
+< ꊍ
+< ꊎ
+< ꊏ
+< ꊐ
+< ꊑ
+< ꊒ
+< ꊓ
+< ꊔ
+< ꊕ
+< ꊖ
+< ꊗ
+< ꊘ
+< ꊙ
+< ꊚ
+< ꊛ
+< ꊜ
+< ꊝ
+< ꊞ
+< ꊟ
+< ꊠ
+< ꊡ
+< ꊢ
+< ꊣ
+< ꊤ
+< ꊥ
+< ꊦ
+< ꊧ
+< ꊨ
+< ꊩ
+< ꊪ
+< ꊫ
+< ꊬ
+< ꊭ
+< ꊮ
+< ꊯ
+< ꊰ
+< ꊱ
+< ꊲ
+< ꊳ
+< ꊴ
+< ꊵ
+< ꊶ
+< ꊷ
+< ꊸ
+< ꊹ
+< ꊺ
+< ꊻ
+< ꊼ
+< ꊽ
+< ꊾ
+< ꊿ
+< ꋀ
+< ꋁ
+< ꋂ
+< ꋃ
+< ꋄ
+< ꋅ
+< ꋆ
+< ꋇ
+< ꋈ
+< ꋉ
+< ꋊ
+< ꋋ
+< ꋌ
+< ꋍ
+< ꋎ
+< ꋏ
+< ꋐ
+< ꋑ
+< ꋒ
+< ꋓ
+< ꋔ
+< ꋕ
+< ꋖ
+< ꋗ
+< ꋘ
+< ꋙ
+< ꋚ
+< ꋛ
+< ꋜ
+< ꋝ
+< ꋞ
+< ꋟ
+< ꋠ
+< ꋡ
+< ꋢ
+< ꋣ
+< ꋤ
+< ꋥ
+< ꋦ
+< ꋧ
+< ꋨ
+< ꋩ
+< ꋪ
+< ꋫ
+< ꋬ
+< ꋭ
+< ꋮ
+< ꋯ
+< ꋰ
+< ꋱ
+< ꋲ
+< ꋳ
+< ꋴ
+< ꋵ
+< ꋶ
+< ꋷ
+< ꋸ
+< ꋹ
+< ꋺ
+< ꋻ
+< ꋼ
+< ꋽ
+< ꋾ
+< ꋿ
+< ꌀ
+< ꌁ
+< ꌂ
+< ꌃ
+< ꌄ
+< ꌅ
+< ꌆ
+< ꌇ
+< ꌈ
+< ꌉ
+< ꌊ
+< ꌋ
+< ꌌ
+< ꌍ
+< ꌎ
+< ꌏ
+< ꌐ
+< ꌑ
+< ꌒ
+< ꌓ
+< ꌔ
+< ꌕ
+< ꌖ
+< ꌗ
+< ꌘ
+< ꌙ
+< ꌚ
+< ꌛ
+< ꌜ
+< ꌝ
+< ꌞ
+< ꌟ
+< ꌠ
+< ꌡ
+< ꌢ
+< ꌣ
+< ꌤ
+< ꌥ
+< ꌦ
+< ꌧ
+< ꌨ
+< ꌩ
+< ꌪ
+< ꌫ
+< ꌬ
+< ꌭ
+< ꌮ
+< ꌯ
+< ꌰ
+< ꌱ
+< ꌲ
+< ꌳ
+< ꌴ
+< ꌵ
+< ꌶ
+< ꌷ
+< ꌸ
+< ꌹ
+< ꌺ
+< ꌻ
+< ꌼ
+< ꌽ
+< ꌾ
+< ꌿ
+< ꍀ
+< ꍁ
+< ꍂ
+< ꍃ
+< ꍄ
+< ꍅ
+< ꍆ
+< ꍇ
+< ꍈ
+< ꍉ
+< ꍊ
+< ꍋ
+< ꍌ
+< ꍍ
+< ꍎ
+< ꍏ
+< ꍐ
+< ꍑ
+< ꍒ
+< ꍓ
+< ꍔ
+< ꍕ
+< ꍖ
+< ꍗ
+< ꍘ
+< ꍙ
+< ꍚ
+< ꍛ
+< ꍜ
+< ꍝ
+< ꍞ
+< ꍟ
+< ꍠ
+< ꍡ
+< ꍢ
+< ꍣ
+< ꍤ
+< ꍥ
+< ꍦ
+< ꍧ
+< ꍨ
+< ꍩ
+< ꍪ
+< ꍫ
+< ꍬ
+< ꍭ
+< ꍮ
+< ꍯ
+< ꍰ
+< ꍱ
+< ꍲ
+< ꍳ
+< ꍴ
+< ꍵ
+< ꍶ
+< ꍷ
+< ꍸ
+< ꍹ
+< ꍺ
+< ꍻ
+< ꍼ
+< ꍽ
+< ꍾ
+< ꍿ
+< ꎀ
+< ꎁ
+< ꎂ
+< ꎃ
+< ꎄ
+< ꎅ
+< ꎆ
+< ꎇ
+< ꎈ
+< ꎉ
+< ꎊ
+< ꎋ
+< ꎌ
+< ꎍ
+< ꎎ
+< ꎏ
+< ꎐ
+< ꎑ
+< ꎒ
+< ꎓ
+< ꎔ
+< ꎕ
+< ꎖ
+< ꎗ
+< ꎘ
+< ꎙ
+< ꎚ
+< ꎛ
+< ꎜ
+< ꎝ
+< ꎞ
+< ꎟ
+< ꎠ
+< ꎡ
+< ꎢ
+< ꎣ
+< ꎤ
+< ꎥ
+< ꎦ
+< ꎧ
+< ꎨ
+< ꎩ
+< ꎪ
+< ꎫ
+< ꎬ
+< ꎭ
+< ꎮ
+< ꎯ
+< ꎰ
+< ꎱ
+< ꎲ
+< ꎳ
+< ꎴ
+< ꎵ
+< ꎶ
+< ꎷ
+< ꎸ
+< ꎹ
+< ꎺ
+< ꎻ
+< ꎼ
+< ꎽ
+< ꎾ
+< ꎿ
+< ꏀ
+< ꏁ
+< ꏂ
+< ꏃ
+< ꏄ
+< ꏅ
+< ꏆ
+< ꏇ
+< ꏈ
+< ꏉ
+< ꏊ
+< ꏋ
+< ꏌ
+< ꏍ
+< ꏎ
+< ꏏ
+< ꏐ
+< ꏑ
+< ꏒ
+< ꏓ
+< ꏔ
+< ꏕ
+< ꏖ
+< ꏗ
+< ꏘ
+< ꏙ
+< ꏚ
+< ꏛ
+< ꏜ
+< ꏝ
+< ꏞ
+< ꏟ
+< ꏠ
+< ꏡ
+< ꏢ
+< ꏣ
+< ꏤ
+< ꏥ
+< ꏦ
+< ꏧ
+< ꏨ
+< ꏩ
+< ꏪ
+< ꏫ
+< ꏬ
+< ꏭ
+< ꏮ
+< ꏯ
+< ꏰ
+< ꏱ
+< ꏲ
+< ꏳ
+< ꏴ
+< ꏵ
+< ꏶ
+< ꏷ
+< ꏸ
+< ꏹ
+< ꏺ
+< ꏻ
+< ꏼ
+< ꏽ
+< ꏾ
+< ꏿ
+< ꐀ
+< ꐁ
+< ꐂ
+< ꐃ
+< ꐄ
+< ꐅ
+< ꐆ
+< ꐇ
+< ꐈ
+< ꐉ
+< ꐊ
+< ꐋ
+< ꐌ
+< ꐍ
+< ꐎ
+< ꐏ
+< ꐐ
+< ꐑ
+< ꐒ
+< ꐓ
+< ꐔ
+< ꐕ
+< ꐖ
+< ꐗ
+< ꐘ
+< ꐙ
+< ꐚ
+< ꐛ
+< ꐜ
+< ꐝ
+< ꐞ
+< ꐟ
+< ꐠ
+< ꐡ
+< ꐢ
+< ꐣ
+< ꐤ
+< ꐥ
+< ꐦ
+< ꐧ
+< ꐨ
+< ꐩ
+< ꐪ
+< ꐫ
+< ꐬ
+< ꐭ
+< ꐮ
+< ꐯ
+< ꐰ
+< ꐱ
+< ꐲ
+< ꐳ
+< ꐴ
+< ꐵ
+< ꐶ
+< ꐷ
+< ꐸ
+< ꐹ
+< ꐺ
+< ꐻ
+< ꐼ
+< ꐽ
+< ꐾ
+< ꐿ
+< ꑀ
+< ꑁ
+< ꑂ
+< ꑃ
+< ꑄ
+< ꑅ
+< ꑆ
+< ꑇ
+< ꑈ
+< ꑉ
+< ꑊ
+< ꑋ
+< ꑌ
+< ꑍ
+< ꑎ
+< ꑏ
+< ꑐ
+< ꑑ
+< ꑒ
+< ꑓ
+< ꑔ
+< ꑕ
+< ꑖ
+< ꑗ
+< ꑘ
+< ꑙ
+< ꑚ
+< ꑛ
+< ꑜ
+< ꑝ
+< ꑞ
+< ꑟ
+< ꑠ
+< ꑡ
+< ꑢ
+< ꑣ
+< ꑤ
+< ꑥ
+< ꑦ
+< ꑧ
+< ꑨ
+< ꑩ
+< ꑪ
+< ꑫ
+< ꑬ
+< ꑭ
+< ꑮ
+< ꑯ
+< ꑰ
+< ꑱ
+< ꑲ
+< ꑳ
+< ꑴ
+< ꑵ
+< ꑶ
+< ꑷ
+< ꑸ
+< ꑹ
+< ꑺ
+< ꑻ
+< ꑼ
+< ꑽ
+< ꑾ
+< ꑿ
+< ꒀ
+< ꒁ
+< ꒂ
+< ꒃ
+< ꒄ
+< ꒅ
+< ꒆ
+< ꒇ
+< ꒈ
+< ꒉ
+< ꒊ
+< ꒋ
+< ꒌ
+< ꓸ
+< ꓹ
+< ꓺ
+< ꓻ
+< ꓽ
+< ꓼ
+< ꓐ
+< ꓑ
+< ꓒ
+< ꓓ
+< ꓔ
+< ꓕ
+< ꓖ
+< ꓗ
+< ꓘ
+< ꓙ
+< ꓚ
+< ꓛ
+< ꓜ
+< ꓝ
+< ꓞ
+< ꓟ
+< ꓠ
+< ꓡ
+< ꓢ
+< ꓣ
+< ꓤ
+< ꓥ
+< ꓦ
+< ꓧ
+< ꓨ
+< ꓩ
+< ꓫ
+< ꓭ
+< ꓪ
+< ꓬ
+< ꓮ
+< ꓯ
+< ꓰ
+< ꓱ
+< ꓲ
+< ꓳ
+< ꓴ
+< ꓵ
+< ꓶ
+< ꓷ
+expand ⩴ to : : 003d
+expand ‼ to ! !
+expand ⁉ to ! ?
+expand ⁇ to ? ?
+expand ⁈ to ? !
+expand ‥ to . .
+expand … to . . .
+expand ︙ to . . .
+expand ︰ to . .
+expand ⑴ to ( 1 )
+expand ⑵ to ( 2 )
+expand ⑶ to ( 3 )
+expand ⑷ to ( 4 )
+expand ⑸ to ( 5 )
+expand ⑹ to ( 6 )
+expand ⑺ to ( 7 )
+expand ⑻ to ( 8 )
+expand ⑼ to ( 9 )
+expand ⑽ to ( 1 0 )
+expand ⑾ to ( 1 1 )
+expand ⑿ to ( 1 2 )
+expand ⒀ to ( 1 3 )
+expand ⒁ to ( 1 4 )
+expand ⒂ to ( 1 5 )
+expand ⒃ to ( 1 6 )
+expand ⒄ to ( 1 7 )
+expand ⒅ to ( 1 8 )
+expand ⒆ to ( 1 9 )
+expand ⒇ to ( 2 0 )
+expand ⒜ to ( a )
+expand ⒝ to ( b )
+expand ⒞ to ( c )
+expand ⒟ to ( d )
+expand ⒠ to ( e )
+expand ⒡ to ( f )
+expand ⒢ to ( g )
+expand ⒣ to ( h )
+expand ⒤ to ( i )
+expand ⒥ to ( j )
+expand ⒦ to ( k )
+expand ⒧ to ( l )
+expand ⒨ to ( m )
+expand ⒩ to ( n )
+expand ⒪ to ( o )
+expand ⒫ to ( p )
+expand ⒬ to ( q )
+expand ⒭ to ( r )
+expand ⒮ to ( s )
+expand ⒯ to ( t )
+expand ⒰ to ( u )
+expand ⒱ to ( v )
+expand ⒲ to ( w )
+expand ⒳ to ( x )
+expand ⒴ to ( y )
+expand ⒵ to ( z )
+expand ㈀ to ( ᄀ )
+expand ㈁ to ( ᄂ )
+expand ㈂ to ( ᄃ )
+expand ㈃ to ( ᄅ )
+expand ㈄ to ( ᄆ )
+expand ㈅ to ( ᄇ )
+expand ㈆ to ( ᄉ )
+expand ㈇ to ( ᄋ )
+expand ㈈ to ( ᄌ )
+expand ㈉ to ( ᄎ )
+expand ㈊ to ( ᄏ )
+expand ㈋ to ( ᄐ )
+expand ㈌ to ( ᄑ )
+expand ㈍ to ( ᄒ )
+expand ㈎ to ( ᄀ ᅡ )
+expand ㈏ to ( ᄂ ᅡ )
+expand ㈐ to ( ᄃ ᅡ )
+expand ㈑ to ( ᄅ ᅡ )
+expand ㈒ to ( ᄆ ᅡ )
+expand ㈓ to ( ᄇ ᅡ )
+expand ㈔ to ( ᄉ ᅡ )
+expand ㈕ to ( ᄋ ᅡ )
+expand ㈖ to ( ᄌ ᅡ )
+expand ㈗ to ( ᄎ ᅡ )
+expand ㈘ to ( ᄏ ᅡ )
+expand ㈙ to ( ᄐ ᅡ )
+expand ㈚ to ( ᄑ ᅡ )
+expand ㈛ to ( ᄒ ᅡ )
+expand ㈜ to ( ᄌ ᅮ )
+expand ㈝ to ( ᄋ ᅩ ᄌ ᅥ ᆫ )
+expand ㈞ to ( ᄋ ᅩ ᄒ ᅮ )
+expand ″ to ′ ′
+expand ‴ to ′ ′ ′
+expand ⁗ to ′ ′ ′ ′
+expand ‶ to ‵ ‵
+expand ‷ to ‵ ‵ ‵
+expand ℃ to ° C
+expand ℉ to ° F
+expand ⩵ to 003d 003d
+expand ⩶ to 003d 003d 003d
+expand ∬ to ∫ ∫
+expand ∭ to ∫ ∫ ∫
+expand ⨌ to ∫ ∫ ∫ ∫
+expand ∯ to ∮ ∮
+expand ∰ to ∮ ∮ ∮
+expand ↉ to 0 ⁄ 3
+expand ⒈ to 1 .
+expand ⒑ to 1 0 .
+expand ⒒ to 1 1 .
+expand ⒓ to 1 2 .
+expand ⒔ to 1 3 .
+expand ⒕ to 1 4 .
+expand ⒖ to 1 5 .
+expand ⒗ to 1 6 .
+expand ⒘ to 1 7 .
+expand ⒙ to 1 8 .
+expand ⒚ to 1 9 .
+expand ⑩ to 1 0
+expand ⑪ to 1 1
+expand ⑫ to 1 2
+expand ⑬ to 1 3
+expand ⑭ to 1 4
+expand ⑮ to 1 5
+expand ⑯ to 1 6
+expand ⑰ to 1 7
+expand ⑱ to 1 8
+expand ⑲ to 1 9
+expand ⓫ to 1 1
+expand ⓬ to 1 2
+expand ⓭ to 1 3
+expand ⓮ to 1 4
+expand ⓯ to 1 5
+expand ⓰ to 1 6
+expand ⓱ to 1 7
+expand ⓲ to 1 8
+expand ⓳ to 1 9
+expand ⓾ to 1 0
+expand ❿ to 1 0
+expand ➉ to 1 0
+expand ➓ to 1 0
+expand ㉈ to 1 0
+expand ¼ to 1 ⁄ 4
+expand ½ to 1 ⁄ 2
+expand ⅐ to 1 ⁄ 7
+expand ⅑ to 1 ⁄ 9
+expand ⅒ to 1 ⁄ 1 0
+expand ⅓ to 1 ⁄ 3
+expand ⅕ to 1 ⁄ 5
+expand ⅙ to 1 ⁄ 6
+expand ⅛ to 1 ⁄ 8
+expand ⅟ to 1 ⁄
+expand ⒉ to 2 .
+expand ⒛ to 2 0 .
+expand ⑳ to 2 0
+expand ⓴ to 2 0
+expand ㉉ to 2 0
+expand ㉑ to 2 1
+expand ㉒ to 2 2
+expand ㉓ to 2 3
+expand ㉔ to 2 4
+expand ㉕ to 2 5
+expand ㉖ to 2 6
+expand ㉗ to 2 7
+expand ㉘ to 2 8
+expand ㉙ to 2 9
+expand ⅔ to 2 ⁄ 3
+expand ⅖ to 2 ⁄ 5
+expand ⒊ to 3 .
+expand ㉊ to 3 0
+expand ㉚ to 3 0
+expand ㉛ to 3 1
+expand ㉜ to 3 2
+expand ㉝ to 3 3
+expand ㉞ to 3 4
+expand ㉟ to 3 5
+expand ㊱ to 3 6
+expand ㊲ to 3 7
+expand ㊳ to 3 8
+expand ㊴ to 3 9
+expand ¾ to 3 ⁄ 4
+expand ⅗ to 3 ⁄ 5
+expand ⅜ to 3 ⁄ 8
+expand ⒋ to 4 .
+expand ㉋ to 4 0
+expand ㊵ to 4 0
+expand ㊶ to 4 1
+expand ㊷ to 4 2
+expand ㊸ to 4 3
+expand ㊹ to 4 4
+expand ㊺ to 4 5
+expand ㊻ to 4 6
+expand ㊼ to 4 7
+expand ㊽ to 4 8
+expand ㊾ to 4 9
+expand ⅘ to 4 ⁄ 5
+expand ⒌ to 5 .
+expand ㉌ to 5 0
+expand ㊿ to 5 0
+expand ⅚ to 5 ⁄ 6
+expand ⅝ to 5 ⁄ 8
+expand ⒍ to 6 .
+expand ㉍ to 6 0
+expand ⒎ to 7 .
+expand ㉎ to 7 0
+expand ⅞ to 7 ⁄ 8
+expand ⒏ to 8 .
+expand ㉏ to 8 0
+expand ⒐ to 9 .
+expand ᷕ to a o
+expand ᷖ to a v
+expand ẚ to a ʾ
+expand ℀ to a / c
+expand ℁ to a / s
+expand ꜳ to a a
+expand ꜵ to a o
+expand ꜷ to a u
+expand ꜹ to a v
+expand ꜽ to a y
+expand ⅍ to A / S
+expand Ꜳ to A A
+expand Ꜵ to A O
+expand Ꜷ to A U
+expand Ꜹ to A V
+expand Ꜽ to A Y
+expand ㏂ to a . m .
+expand ㍳ to A U
+expand ㏟ to A ∕ m
+expand æ to a e
+expand ǣ to a e
+expand ǽ to a e
+expand ᷔ to a e
+expand ꜻ to a v
+expand Æ to A E
+expand Ǣ to A E
+expand Ǽ to A E
+expand Ꜻ to A V
+expand ᴭ to a e
+expand ㍴ to b a r
+expand ㏃ to B q
+expand ℅ to c / o
+expand ℆ to c / u
+expand ㎈ to c a l
+expand ㎝ to c m
+expand ㎠ to c m 2
+expand ㎤ to c m 3
+expand ㏄ to c c
+expand ㏅ to c d
+expand ㏆ to C ∕ k g
+expand ㏇ to C o .
+expand dž to d z
+expand dz to d z
+expand ȸ to d b
+expand ʣ to d z
+expand ʤ to d ʒ
+expand ʥ to d ʑ
+expand DŽ to D Z
+expand Dž to D z
+expand DZ to D Z
+expand Dz to D z
+expand ㍲ to d a
+expand ㍷ to d m
+expand ㍸ to d m 2
+expand ㍹ to d m 3
+expand ㎗ to d l
+expand ㏈ to d B
+expand ㋍ to e r g
+expand ㋎ to e V
+expand ʩ to f ŋ
+expand ff to f f
+expand fi to f i
+expand fl to f l
+expand ffi to f f i
+expand ffl to f f l
+expand ℻ to F A X
+expand ㎙ to f m
+expand ㏿ to g a l
+expand ㎇ to G B
+expand ㎓ to G H z
+expand ㎬ to G P a
+expand ㏉ to G y
+expand ㍱ to h P a
+expand ㏊ to h a
+expand ㋌ to H g
+expand ㎐ to H z
+expand ㏋ to H P
+expand ij to i j
+expand ⅱ to i i
+expand ⅲ to i i i
+expand ⅳ to i v
+expand ⅸ to i x
+expand IJ to I J
+expand Ⅱ to I I
+expand Ⅲ to I I I
+expand Ⅳ to I V
+expand Ⅸ to I X
+expand ㏌ to i n
+expand ㍺ to I U
+expand ㎄ to k A
+expand ㎉ to k c a l
+expand ㎏ to k g
+expand ㎑ to k H z
+expand ㎘ to k l
+expand ㎞ to k m
+expand ㎢ to k m 2
+expand ㎦ to k m 3
+expand ㎪ to k P a
+expand ㎸ to k V
+expand ㎾ to k W
+expand ㏀ to k Ω
+expand ㏏ to k t
+expand ㎅ to K B
+expand ㏍ to K K
+expand ㏎ to K M
+expand lj to l j
+expand ʪ to l s
+expand ʫ to l z
+expand ỻ to l l
+expand LJ to L J
+expand Lj to L j
+expand Ỻ to L L
+expand ㏐ to l m
+expand ㏑ to l n
+expand ㏒ to l o g
+expand ㏓ to l x
+expand ㋏ to L T D
+expand ㎃ to m A
+expand ㎎ to m g
+expand ㎖ to m l
+expand ㎜ to m m
+expand ㎟ to m m 2
+expand ㎡ to m 2
+expand ㎣ to m m 3
+expand ㎥ to m 3
+expand ㎧ to m ∕ s
+expand ㎨ to m ∕ s 2
+expand ㎳ to m s
+expand ㎷ to m V
+expand ㎽ to m W
+expand ㏔ to m b
+expand ㏕ to m i l
+expand ㏖ to m o l
+expand ㎆ to M B
+expand ㎒ to M H z
+expand ㎫ to M P a
+expand ㎹ to M V
+expand ㎿ to M W
+expand ㏁ to M Ω
+expand nj to n j
+expand NJ to N J
+expand Nj to N j
+expand № to N o
+expand ㎁ to n A
+expand ㎋ to n F
+expand ㎚ to n m
+expand ㎱ to n s
+expand ㎵ to n V
+expand ㎻ to n W
+expand ꝏ to o o
+expand Ꝏ to O O
+expand ㍵ to o V
+expand œ to o e
+expand Πto O E
+expand ꟹ to o e
+expand ㍶ to p c
+expand ㎀ to p A
+expand ㎊ to p F
+expand ㎰ to p s
+expand ㎴ to p V
+expand ㎺ to p W
+expand ㏘ to p . m .
+expand ㉐ to P T E
+expand ㎩ to P a
+expand ㏗ to P H
+expand ㏙ to P P M
+expand ㏚ to P R
+expand ȹ to q p
+expand ₨ to R s
+expand ㎭ to r a d
+expand ㎮ to r a d ∕ s
+expand ㎯ to r a d ∕ s 2
+expand st to s t
+expand ℠ to s m
+expand ㏛ to s r
+expand ㏜ to S v
+expand ß to s s
+expand ẞ to S S
+expand ſt to s t
+expand ƾ to t s
+expand ʦ to t s
+expand ʧ to t ʃ
+expand ʨ to t ɕ
+expand ꜩ to t z
+expand ™ to T M
+expand ℡ to T E L
+expand Ꜩ to T z
+expand ㎔ to T H z
+expand ᵺ to t h
+expand ⅵ to v i
+expand ⅶ to v i i
+expand ⅷ to v i i i
+expand ꝡ to v y
+expand Ⅵ to V I
+expand Ⅶ to V I I
+expand Ⅷ to V I I I
+expand Ꝡ to V Y
+expand ㏞ to V ∕ m
+expand ㏝ to W b
+expand ⅺ to x i
+expand ⅻ to x i i
+expand Ⅺ to X I
+expand Ⅻ to X I I
+expand ƍ to z w
+expand ʼn to ʼ n
+expand ϗ to κ α ι
+expand Ϗ to Κ α ι
+expand ㎂ to μ A
+expand ㎌ to μ F
+expand ㎍ to μ g
+expand ㎕ to μ l
+expand ㎛ to μ m
+expand ㎲ to μ s
+expand ㎶ to μ V
+expand ㎼ to μ W
+expand ⳤ to ⲕ ⲁ ⲓ
+expand ⷵ to с т
+expand և to ե ւ
+expand ﬓ to մ ն
+expand ﬔ to մ ե
+expand ﬕ to մ ի
+expand ﬗ to մ խ
+expand ﬖ to վ ն
+expand ﭏ to א ל
+expand װ to ו ו
+expand ױ to ו י
+expand ײ to י י
+expand ײַ to י י
+expand ﯸ to ئ ې
+expand ﯻ to ئ ى
+expand ﲗ to ئ ج
+expand ﲘ to ئ ح
+expand ﲙ to ئ خ
+expand ﲚ to ئ م
+expand ﲛ to ئ ه
+expand ﳟ to ئ م
+expand ﳠ to ئ ه
+expand ﯫ to ئ ا
+expand ﯭ to ئ ە
+expand ﯯ to ئ و
+expand ﯱ to ئ ۇ
+expand ﯳ to ئ ۆ
+expand ﯵ to ئ ۈ
+expand ﯷ to ئ ې
+expand ﯺ to ئ ى
+expand ﱤ to ئ ر
+expand ﱥ to ئ ز
+expand ﱦ to ئ م
+expand ﱧ to ئ ن
+expand ﱨ to ئ ى
+expand ﱩ to ئ ي
+expand ﯪ to ئ ا
+expand ﯬ to ئ ە
+expand ﯮ to ئ و
+expand ﯰ to ئ ۇ
+expand ﯲ to ئ ۆ
+expand ﯴ to ئ ۈ
+expand ﯶ to ئ ې
+expand ﯹ to ئ ى
+expand ﰀ to ئ ج
+expand ﰁ to ئ ح
+expand ﰂ to ئ م
+expand ﰃ to ئ ى
+expand ﰄ to ئ ي
+expand ٵ to ا ء
+expand ﷲ to ا ل ل ه
+expand ﷳ to ا ك ب ر
+expand ﲜ to ب ج
+expand ﲝ to ب ح
+expand ﲞ to ب خ
+expand ﲟ to ب م
+expand ﲠ to ب ه
+expand ﳡ to ب م
+expand ﳢ to ب ه
+expand ﱪ to ب ر
+expand ﱫ to ب ز
+expand ﱬ to ب م
+expand ﱭ to ب ن
+expand ﱮ to ب ى
+expand ﱯ to ب ي
+expand ﶞ to ب خ ي
+expand ﷂ to ب ح ي
+expand ﰅ to ب ج
+expand ﰆ to ب ح
+expand ﰇ to ب خ
+expand ﰈ to ب م
+expand ﰉ to ب ى
+expand ﰊ to ب ي
+expand ﲡ to ت ج
+expand ﲢ to ت ح
+expand ﲣ to ت خ
+expand ﲤ to ت م
+expand ﲥ to ت ه
+expand ﵐ to ت ج م
+expand ﵒ to ت ح ج
+expand ﵓ to ت ح م
+expand ﵔ to ت خ م
+expand ﵕ to ت م ج
+expand ﵖ to ت م ح
+expand ﵗ to ت م خ
+expand ﳣ to ت م
+expand ﳤ to ت ه
+expand ﱰ to ت ر
+expand ﱱ to ت ز
+expand ﱲ to ت م
+expand ﱳ to ت ن
+expand ﱴ to ت ى
+expand ﱵ to ت ي
+expand ﵑ to ت ح ج
+expand ﶟ to ت ج ي
+expand ﶠ to ت ج ى
+expand ﶡ to ت خ ي
+expand ﶢ to ت خ ى
+expand ﶣ to ت م ي
+expand ﶤ to ت م ى
+expand ﰋ to ت ج
+expand ﰌ to ت ح
+expand ﰍ to ت خ
+expand ﰎ to ت م
+expand ﰏ to ت ى
+expand ﰐ to ت ي
+expand ﲦ to ث م
+expand ﳥ to ث م
+expand ﳦ to ث ه
+expand ﱶ to ث ر
+expand ﱷ to ث ز
+expand ﱸ to ث م
+expand ﱹ to ث ن
+expand ﱺ to ث ى
+expand ﱻ to ث ي
+expand ﰑ to ث ج
+expand ﰒ to ث م
+expand ﰓ to ث ى
+expand ﰔ to ث ي
+expand ﲧ to ج ح
+expand ﲨ to ج م
+expand ﵙ to ج م ح
+expand ﴝ to ج ى
+expand ﴞ to ج ي
+expand ﵘ to ج م ح
+expand ﶥ to ج م ي
+expand ﶦ to ج ح ى
+expand ﶧ to ج م ى
+expand ﶾ to ج ح ي
+expand ﰕ to ج ح
+expand ﰖ to ج م
+expand ﴁ to ج ى
+expand ﴂ to ج ي
+expand ﷻ to ج ل 0020 ج ل ا ل ه
+expand ﲩ to ح ج
+expand ﲪ to ح م
+expand ﴛ to ح ى
+expand ﴜ to ح ي
+expand ﵚ to ح م ي
+expand ﵛ to ح م ى
+expand ﶿ to ح ج ي
+expand ﰗ to ح ج
+expand ﰘ to ح م
+expand ﳿ to ح ى
+expand ﴀ to ح ي
+expand ﲫ to خ ج
+expand ﲬ to خ م
+expand ﴟ to خ ى
+expand ﴠ to خ ي
+expand ﰙ to خ ج
+expand ﰚ to خ ح
+expand ﰛ to خ م
+expand ﴃ to خ ى
+expand ﴄ to خ ي
+expand ﷶ to ر س و ل
+expand ﷼ to ر ی ا ل
+expand ﲭ to س ج
+expand ﲮ to س ح
+expand ﲯ to س خ
+expand ﲰ to س م
+expand ﴱ to س ه
+expand ﵜ to س ح ج
+expand ﵝ to س ج ح
+expand ﵠ to س م ح
+expand ﵡ to س م ج
+expand ﵣ to س م م
+expand ﳧ to س م
+expand ﳨ to س ه
+expand ﴴ to س ج
+expand ﴵ to س ح
+expand ﴶ to س خ
+expand ﴗ to س ى
+expand ﴘ to س ي
+expand ﴪ to س ر
+expand ﵞ to س ج ى
+expand ﵟ to س م ح
+expand ﵢ to س م م
+expand ﶨ to س خ ى
+expand ﷆ to س خ ي
+expand ﰜ to س ج
+expand ﰝ to س ح
+expand ﰞ to س خ
+expand ﰟ to س م
+expand ﳻ to س ى
+expand ﳼ to س ي
+expand ﴎ to س ر
+expand ﴭ to ش ج
+expand ﴮ to ش ح
+expand ﴯ to ش خ
+expand ﴰ to ش م
+expand ﴲ to ش ه
+expand ﵨ to ش ح م
+expand ﵫ to ش م خ
+expand ﵭ to ش م م
+expand ﳩ to ش م
+expand ﳪ to ش ه
+expand ﴷ to ش ج
+expand ﴸ to ش ح
+expand ﴹ to ش خ
+expand ﴙ to ش ى
+expand ﴚ to ش ي
+expand ﴥ to ش ج
+expand ﴦ to ش ح
+expand ﴧ to ش خ
+expand ﴨ to ش م
+expand ﴩ to ش ر
+expand ﵧ to ش ح م
+expand ﵩ to ش ج ي
+expand ﵪ to ش م خ
+expand ﵬ to ش م م
+expand ﶪ to ش ح ي
+expand ﳽ to ش ى
+expand ﳾ to ش ي
+expand ﴉ to ش ج
+expand ﴊ to ش ح
+expand ﴋ to ش خ
+expand ﴌ to ش م
+expand ﴍ to ش ر
+expand ﲱ to ص ح
+expand ﲲ to ص خ
+expand ﲳ to ص م
+expand ﵥ to ص ح ح
+expand ﷅ to ص م م
+expand ﴡ to ص ى
+expand ﴢ to ص ي
+expand ﴫ to ص ر
+expand ﵤ to ص ح ح
+expand ﵦ to ص م م
+expand ﶩ to ص ح ي
+expand ﰠ to ص ح
+expand ﰡ to ص م
+expand ﴅ to ص ى
+expand ﴆ to ص ي
+expand ﴏ to ص ر
+expand ﷰ to ص ل ے
+expand ﷵ to ص ل ع م
+expand ﷹ to ص ل ى
+expand ﷺ to ص ل ى 0020 ا ل ل ه 0020 ع ل ي ه 0020 و س ل م
+expand ﲴ to ض ج
+expand ﲵ to ض ح
+expand ﲶ to ض خ
+expand ﲷ to ض م
+expand ﵰ to ض خ م
+expand ﴣ to ض ى
+expand ﴤ to ض ي
+expand ﴬ to ض ر
+expand ﵮ to ض ح ى
+expand ﵯ to ض خ م
+expand ﶫ to ض ح ي
+expand ﰢ to ض ج
+expand ﰣ to ض ح
+expand ﰤ to ض خ
+expand ﰥ to ض م
+expand ﴇ to ض ى
+expand ﴈ to ض ي
+expand ﴐ to ض ر
+expand ﲸ to ط ح
+expand ﴳ to ط م
+expand ﵲ to ط م ح
+expand ﵳ to ط م م
+expand ﴺ to ط م
+expand ﴑ to ط ى
+expand ﴒ to ط ي
+expand ﵱ to ط م ح
+expand ﵴ to ط م ي
+expand ﰦ to ط ح
+expand ﰧ to ط م
+expand ﳵ to ط ى
+expand ﳶ to ط ي
+expand ﲹ to ظ م
+expand ﴻ to ظ م
+expand ﰨ to ظ م
+expand ﲺ to ع ج
+expand ﲻ to ع م
+expand ﵷ to ع م م
+expand ﷄ to ع ج م
+expand ﴓ to ع ى
+expand ﴔ to ع ي
+expand ﵵ to ع ج م
+expand ﵶ to ع م م
+expand ﵸ to ع م ى
+expand ﶶ to ع م ي
+expand ﰩ to ع ج
+expand ﰪ to ع م
+expand ﳷ to ع ى
+expand ﳸ to ع ي
+expand ﷷ to ع ل ي ه
+expand ﲼ to غ ج
+expand ﲽ to غ م
+expand ﴕ to غ ى
+expand ﴖ to غ ي
+expand ﵹ to غ م م
+expand ﵺ to غ م ي
+expand ﵻ to غ م ى
+expand ﰫ to غ ج
+expand ﰬ to غ م
+expand ﳹ to غ ى
+expand ﳺ to غ ي
+expand ﲾ to ف ج
+expand ﲿ to ف ح
+expand ﳀ to ف خ
+expand ﳁ to ف م
+expand ﵽ to ف خ م
+expand ﱼ to ف ى
+expand ﱽ to ف ي
+expand ﵼ to ف خ م
+expand ﷁ to ف م ي
+expand ﰭ to ف ج
+expand ﰮ to ف ح
+expand ﰯ to ف خ
+expand ﰰ to ف م
+expand ﰱ to ف ى
+expand ﰲ to ف ي
+expand ﳂ to ق ح
+expand ﳃ to ق م
+expand ﶴ to ق م ح
+expand ﱾ to ق ى
+expand ﱿ to ق ي
+expand ﵾ to ق م ح
+expand ﵿ to ق م م
+expand ﶲ to ق م ي
+expand ﰳ to ق ح
+expand ﰴ to ق م
+expand ﰵ to ق ى
+expand ﰶ to ق ي
+expand ﷱ to ق ل ے
+expand ﳄ to ك ج
+expand ﳅ to ك ح
+expand ﳆ to ك خ
+expand ﳇ to ك ل
+expand ﳈ to ك م
+expand ﷃ to ك م م
+expand ﳫ to ك ل
+expand ﳬ to ك م
+expand ﲀ to ك ا
+expand ﲁ to ك ل
+expand ﲂ to ك م
+expand ﲃ to ك ى
+expand ﲄ to ك ي
+expand ﶷ to ك م ي
+expand ﶻ to ك م م
+expand ﰷ to ك ا
+expand ﰸ to ك ج
+expand ﰹ to ك ح
+expand ﰺ to ك خ
+expand ﰻ to ك ل
+expand ﰼ to ك م
+expand ﰽ to ك ى
+expand ﰾ to ك ي
+expand ﳉ to ل ج
+expand ﳊ to ل ح
+expand ﳋ to ل خ
+expand ﳌ to ل م
+expand ﳍ to ل ه
+expand ﶃ to ل ج ج
+expand ﶆ to ل خ م
+expand ﶈ to ل م ح
+expand ﶵ to ل ح م
+expand ﶺ to ل ج م
+expand ﳭ to ل م
+expand ﲅ to ل م
+expand ﲆ to ل ى
+expand ﲇ to ل ي
+expand ﶀ to ل ح م
+expand ﶁ to ل ح ي
+expand ﶂ to ل ح ى
+expand ﶄ to ل ج ج
+expand ﶅ to ل خ م
+expand ﶇ to ل م ح
+expand ﶬ to ل ج ي
+expand ﶭ to ل م ي
+expand ﶼ to ل ج م
+expand ﻶ to ل آ
+expand ﻸ to ل أ
+expand ﻺ to ل إ
+expand ﻼ to ل ا
+expand ﰿ to ل ج
+expand ﱀ to ل ح
+expand ﱁ to ل خ
+expand ﱂ to ل م
+expand ﱃ to ل ى
+expand ﱄ to ل ي
+expand ﻵ to ل آ
+expand ﻷ to ل أ
+expand ﻹ to ل إ
+expand ﻻ to ل ا
+expand ﳎ to م ج
+expand ﳏ to م ح
+expand ﳐ to م خ
+expand ﳑ to م م
+expand ﶉ to م ح ج
+expand ﶊ to م ح م
+expand ﶌ to م ج ح
+expand ﶍ to م ج م
+expand ﶎ to م خ ج
+expand ﶏ to م خ م
+expand ﶒ to م ج خ
+expand ﲈ to م ا
+expand ﲉ to م م
+expand ﶋ to م ح ي
+expand ﶱ to م م ي
+expand ﶹ to م خ ي
+expand ﷀ to م ج ي
+expand ﱅ to م ج
+expand ﱆ to م ح
+expand ﱇ to م خ
+expand ﱈ to م م
+expand ﱉ to م ى
+expand ﱊ to م ي
+expand ﷴ to م ح م د
+expand ﳒ to ن ج
+expand ﳓ to ن ح
+expand ﳔ to ن خ
+expand ﳕ to ن م
+expand ﳖ to ن ه
+expand ﶕ to ن ح م
+expand ﶘ to ن ج م
+expand ﶸ to ن ج ح
+expand ﳮ to ن م
+expand ﳯ to ن ه
+expand ﲊ to ن ر
+expand ﲋ to ن ز
+expand ﲌ to ن م
+expand ﲍ to ن ن
+expand ﲎ to ن ى
+expand ﲏ to ن ي
+expand ﶖ to ن ح ى
+expand ﶗ to ن ج م
+expand ﶙ to ن ج ى
+expand ﶚ to ن م ي
+expand ﶛ to ن م ى
+expand ﶳ to ن ح ي
+expand ﶽ to ن ج ح
+expand ﷇ to ن ج ي
+expand ﱋ to ن ج
+expand ﱌ to ن ح
+expand ﱍ to ن خ
+expand ﱎ to ن م
+expand ﱏ to ن ى
+expand ﱐ to ن ي
+expand ﳗ to ه ج
+expand ﳘ to ه م
+expand ﶓ to ه م ج
+expand ﶔ to ه م م
+expand ﱑ to ه ج
+expand ﱒ to ه م
+expand ﱓ to ه ى
+expand ﱔ to ه ي
+expand ٶ to و ء
+expand ﷸ to و س ل م
+expand ٷ to ۇ ء
+expand ﯝ to ۇ ء
+expand ٸ to ي ء
+expand ﳚ to ي ج
+expand ﳛ to ي ح
+expand ﳜ to ي خ
+expand ﳝ to ي م
+expand ﳞ to ي ه
+expand ﶝ to ي م م
+expand ﳰ to ي م
+expand ﳱ to ي ه
+expand ﲑ to ي ر
+expand ﲒ to ي ز
+expand ﲓ to ي م
+expand ﲔ to ي ن
+expand ﲕ to ي ى
+expand ﲖ to ي ي
+expand ﶜ to ي م م
+expand ﶮ to ي ح ي
+expand ﶯ to ي ج ي
+expand ﶰ to ي م ي
+expand ﱕ to ي ج
+expand ﱖ to ي ح
+expand ﱗ to ي خ
+expand ﱘ to ي م
+expand ﱙ to ي ى
+expand ﱚ to ي ي
+expand ৎ to ত 09cd
+expand ൿ to ക 0d4d
+expand ൺ to ണ 0d4d
+expand ൻ to ന 0d4d
+expand ൎ to ര 0d4d
+expand ർ to ര 0d4d
+expand ൽ to ല 0d4d
+expand ൾ to ള 0d4d
+expand ໜ to ຫ ນ
+expand ໝ to ຫ ມ
+expand ཀྵ to ཀ 0fb5
+expand ྐྵ to 0f90 0fb5
+expand གྷ to ག 0fb7
+expand ྒྷ to 0f92 0fb7
+expand ཌྷ to ཌ 0fb7
+expand ྜྷ to 0f9c 0fb7
+expand དྷ to ད 0fb7
+expand ྡྷ to 0fa1 0fb7
+expand བྷ to བ 0fb7
+expand ྦྷ to 0fa6 0fb7
+expand ཛྷ to ཛ 0fb7
+expand ྫྷ to 0fab 0fb7
+expand ༀ to ཨ 0f7c
+expand ဿ to သ 1039 သ
+expand ᧞ to ᦜ ᦶ
+expand ᧟ to ᦜ ᦶ ᧁ
+expand ᩔ to ᩆ 1a60 ᩆ
+expand ᛰ to ᚦ ᚦ
+expand ᛮ to ᛅ ᛚ
+expand ᛯ to ᛗ ᛗ
+expand ꘓ to ꔌ ꘋ
+expand ꘔ to ꔞ ꘋ
+expand ꘕ to ꔳ ꘋ
+expand ꘖ to ꕇ ꘌ
+expand ꘗ to ꕒ ꘋ
+expand ꘘ to ꕘ ꘌ
+expand ꘙ to ꕚ ꘌ
+expand ꘚ to ꕠ ꘋ
+expand ꘛ to ꖅ ꘋ
+expand ꘜ to ꖴ ꘋ
+expand ꘝ to ꗋ ꘋ
+expand ꘞ to ꗑ ꘌ
+expand ꘟ to ꗘ ꘋ
+expand ㉮ to ᄀ ᅡ
+expand ㉯ to ᄂ ᅡ
+expand ㉰ to ᄃ ᅡ
+expand ㉱ to ᄅ ᅡ
+expand ㉲ to ᄆ ᅡ
+expand ㉳ to ᄇ ᅡ
+expand ㉴ to ᄉ ᅡ
+expand ㉵ to ᄋ ᅡ
+expand ㉾ to ᄋ ᅮ
+expand ㉶ to ᄌ ᅡ
+expand ㉽ to ᄌ ᅮ ᄋ ᅴ
+expand ㉷ to ᄎ ᅡ
+expand ㉼ to ᄎ ᅡ ᆷ ᄀ ᅩ
+expand ㉸ to ᄏ ᅡ
+expand ㉹ to ᄐ ᅡ
+expand ㉺ to ᄑ ᅡ
+expand ㉻ to ᄒ ᅡ
+expand ㌀ to ぁ は ー と
+expand ㌁ to ぁ る ふ ぁ
+expand ㌂ to ぁ ん へ ぁ
+expand ㌃ to ぁ ー る
+expand ㌄ to ぃ に ん ゖ
+expand ㌅ to ぃ ん ち
+expand ㌆ to ぅ ぉ ん
+expand ㌇ to ぇ す く ー と
+expand ㌈ to ぇ ー ゕ ー
+expand ㌉ to ぉ ん す
+expand ㌊ to ぉ ー む
+expand ㌋ to ゕ ぃ り
+expand ㌌ to ゕ ら っ と
+expand ㌍ to ゕ ろ り ー
+expand ㌎ to ゕ ろ ん
+expand ㌏ to ゕ ん ま
+expand ㌒ to き ゅ り ー
+expand ㌔ to き ろ
+expand ㌕ to き ろ く ら む
+expand ㌖ to き ろ め ー と る
+expand ㌗ to き ろ ゎ っ と
+expand ㌐ to き ゕ
+expand ㌑ to き に ー
+expand ㌓ to き る ち ー
+expand ㌚ to く る せ ぃ ろ
+expand ㌛ to く ろ ー ね
+expand ㌘ to く ら む
+expand ㌙ to く ら む と ん
+expand ㌜ to ゖ ー す
+expand ヿ to こ と
+expand ㌝ to こ る な
+expand ㌞ to こ ー ほ
+expand ㌟ to さ ぃ く る
+expand ㌠ to さ ん ち ー む
+expand 〆 to し め
+expand ㌡ to し り ん ゖ
+expand ㌢ to せ ん ち
+expand ㌣ to せ ん と
+expand ㌤ to た ー す
+expand ㌥ to て し
+expand ㌧ to と ん
+expand ㌦ to と る
+expand ㌨ to な の
+expand ㌩ to の っ と
+expand ㌪ to は ぃ っ
+expand ㌭ to は ー れ る
+expand ㌫ to は ー せ ん と
+expand ㌬ to は ー っ
+expand ㌱ to ひ る
+expand ㌮ to ひ ぁ す と る
+expand ㌯ to ひ く る
+expand ㌰ to ひ こ
+expand ㌲ to ふ ぁ ら っ と
+expand ㌳ to ふ ぃ ー と
+expand ㌵ to ふ ら ん
+expand ㌴ to ふ っ し ぇ る
+expand ㌶ to へ く た ー る
+expand ㌹ to へ る っ
+expand ㌼ to へ ー た
+expand ㌷ to へ そ
+expand ㌸ to へ に ひ
+expand ㌺ to へ ん す
+expand ㌻ to へ ー す
+expand ㌿ to ほ ん
+expand ㍁ to ほ ー る
+expand ㍂ to ほ ー ん
+expand ㌾ to ほ る と
+expand ㌽ to ほ ぃ ん と
+expand ㍀ to ほ ん な
+expand 〼 to ま す
+expand ㍃ to ま ぃ く ろ
+expand ㍄ to ま ぃ る
+expand ㍅ to ま っ は
+expand ㍆ to ま る く
+expand ㍇ to ま ん し ょ ん
+expand ㍈ to み く ろ ん
+expand ㍉ to み り
+expand ㍊ to み り は ー る
+expand ㍋ to め ゕ
+expand ㍌ to め ゕ と ん
+expand ㍍ to め ー と る
+expand ㍎ to ゃ ー と
+expand ㍏ to ゃ ー る
+expand ㍐ to ゅ ぁ ん
+expand ゟ to ょ り
+expand ㍑ to り っ と る
+expand ㍒ to り ら
+expand ㍓ to る ひ ー
+expand ㍔ to る ー ふ る
+expand ㍕ to れ む
+expand ㍖ to れ ん と こ ん
+expand ㍗ to ゎ っ と
diff --git a/resources/styles/default/inc/access b/resources/styles/default/inc/access
index 277df4a..7023296 100644
--- a/resources/styles/default/inc/access
+++ b/resources/styles/default/inc/access
@@ -10,63 +10,65 @@
 # mkgmap:delivery
 # A class is not allowed to use the way if its tag is set to 'no'
 
-highway=steps                           { add foot=yes; add access=no }
-highway=* & motorroad=yes               { add bicycle=no; add foot=no }
-highway=motorway|highway=motorway_link  { add bicycle=no; add foot=no }
-highway=pedestrian & area!=yes          { add foot=yes; add access=no } 
-highway=path                            { add foot=yes; add bicycle=yes; add access=no } 
-highway=bridleway                       { add access=no }
-highway=cycleway                        { add bicycle=yes; add access=no }
-highway=footway                         { add foot=yes; add access=no }
-railway=platform                        { add foot=yes; add access=no }
+# First set access settings with high priority.
+#
+# In case a way is tagged with 
+#    highway=motorway;access=yes 
+# we assume that foot and bikes are not allowed anyhow.
 
+highway=* & motorroad=yes                  { add bicycle=no; add foot=no; }
+highway=motorway | highway=motorway_link   { add bicycle=no; add foot=no; }
 
+access=agricultural  { set access=no; add foot=yes }
+
+# Now fill the access tree (http://wiki.openstreetmap.org/wiki/Key:access) 
+# This is required so that subsequent rules do not add a tag (bicycle) which is already defined by a higher tag (vehicle) 
+access=*             { add foot='${access}'; add vehicle='${access}'; }
+vehicle=*            { add bicycle='${vehicle}'; add motor_vehicle='${vehicle}'; }
+motor_vehicle=*      { add motorcar='${motor_vehicle}'; add goods='${motor_vehicle}'; add hgv='${motor_vehicle}'; add psv='${motor_vehicle}'; add emergency='${motor_vehicle}'; }
+psv=*                { add taxi='${psv}'; add bus='${psv}'; }
+
+
+# Include country specific access rules that are derived from the highway type
+include 'inc/access_country';
+
+# Add common access rules that are derived from the highway type
+highway=steps                              { add foot=yes; add access=no }
+highway=pedestrian & area!=yes             { add foot=yes; add access=no } 
+highway=path                               { add foot=yes; add bicycle=yes; add access=no } 
+highway=bridleway                          { add access=no }
+highway=cycleway                           { add bicycle=yes; add access=no }
+highway=footway                            { add foot=yes; add access=no }
+railway=platform                           { add foot=yes; add access=no }
+
+# throughroute cannot be handled differently for different vehicle types
+# Therefore we have to choose one vehicle type - and the winner is: motorcar
 motorcar=destination   { set mkgmap:throughroute=no; set motorcar=yes } 
-motorcycle=destination { set mkgmap:throughroute=no; set motorcycle=yes } 
 access=destination     { set mkgmap:throughroute=no; set access=yes }
-bicycle=destination    { set bicycle=yes }
-foot=destination       { set foot=yes }                  
-hgv=destination        { delete hgv }                  
-psv=destination        { delete psv }                  
-taxi=destination       { delete taxi }                  
-emergency=destination  { delete emergency }                  
-delivery=destination   { delete delivery }                  
-goods=destination      { delete goods }                  
 
-bicycle=private       | bicycle=agricultural       { set bicycle=no }
+# Normalize all values that indicate a restriction to 'no'
 foot=private          | foot=agricultural          { set foot=no }
-hgv=private           | hgv=agricultural           { set hgv=no }
+bicycle=private       | bicycle=agricultural       { set bicycle=no }
 motorcar=private      | motorcar=agricultural      { set motorcar=no }
-psv=private           | psv=agricultural           { set psv=no }
+goods=private         | goods=agricultural         { set goods=no }
+hgv=private           | hgv=agricultural           { set hgv=no }
+bus=private           | bus=agricultural           { set bus=no }
 taxi=private          | taxi=agricultural          { set taxi=no }
 emergency=private     | emergency=agricultural     { set emergency=no }
-delivery=private      | delivery=agricultural      { set delivery=no }
-goods=private         | goods=agricultural         { set goods=no }
-motorcycle=private    | motorcycle=agricultural    { set motorcycle=no }
-motor_vehicle=private | motor_vehicle=agricultural { set motor_vehicle=no }
-vehicle=private       | vehicle=agricultural       { set vehicle=no }
-access=private                                     { set access=no }
-access=agricultural                                { set access=no; add foot=yes }
 
-# set (override) specific restrictions                        
+# Copy the OSM access tags to the mkgmap internal tags
+foot=*       { set mkgmap:foot='${foot}' }
 bicycle=*    { set mkgmap:bicycle='${bicycle}' }
-foot=*       { set mkgmap:foot='${foot}' }                  
-hgv=*        { set mkgmap:truck='${hgv}' }                  
 motorcar=*   { set mkgmap:car='${motorcar}' }
-psv=*        { set mkgmap:bus='${psv}' }                  
-taxi=*       { set mkgmap:taxi='${taxi}' }                  
-emergency=*  { set mkgmap:emergency='${emergency}' }                  
-delivery=*   { set mkgmap:delivery='${delivery}' }                  
-goods=*      { set mkgmap:delivery='${goods}' }                  
-
-# Translate motor_vehicle and vehicle access rules.
-motorcycle=*    { add mkgmap:car='${motorcycle}' }                  
-motor_vehicle=* { add mkgmap:car='${motor_vehicle}' }
-vehicle=*       { add mkgmap:car='${vehicle}'; add mkgmap:bicycle='${vehicle}' }
+goods=*      { set mkgmap:delivery='${goods}' }
+hgv=*        { set mkgmap:truck='${hgv}' }
+bus=*        { set mkgmap:bus='${bus}' }
+taxi=*       { set mkgmap:taxi='${taxi}' }
+emergency=*  { set mkgmap:emergency='${emergency}' }
 
-# the access tag defines all restrictions
+# The access tag defines all restrictions that are not already set
 access=* { addaccess '${access}' }
 
-
-# check for carpool lane 
-(carpool=yes | carpool=designated | carpool=permissive | carpool=official)     { set mkgmap:carpool=yes }
+# Check for carpool lane (they are not really supported yet so these lines are commented)
+# hov=* { add carpool='${hov}' }
+# (carpool=yes | carpool=designated | carpool=permissive | carpool=official)     { set mkgmap:carpool=yes }
diff --git a/resources/styles/default/inc/access_country b/resources/styles/default/inc/access_country
new file mode 100644
index 0000000..964eb59
--- /dev/null
+++ b/resources/styles/default/inc/access_country
@@ -0,0 +1,22 @@
+# This file contains country specific access restrictions
+# If your country is missing please post your country specific access rules
+# to the mkgmap developers list.
+
+# Belgium (BEL)
+
+highway=trunk & mkgmap:country=BEL	{ add bicycle=no; add foot=no }
+highway=cycleway & mkgmap:country=BEL	{ add foot=yes }
+highway=bridleway & mkgmap:country=BEL	{ add foot=yes }
+
+# The Netherlands (NLD)
+
+highway=trunk & mkgmap:country=NLD	{ add bicycle=no; add foot=no }
+highway=cycleway & mkgmap:country=NLD	{ add foot=yes }
+highway=bridleway & mkgmap:country=NLD	{ add foot=yes }
+
+# Spain (ESP)
+
+highway=trunk & mkgmap:country=ESP	{ add bicycle=yes; add foot=yes }
+highway=bridleway & mkgmap:country=ESP	{ add bicycle=yes; add foot=yes }
+
+
diff --git a/resources/styles/default/inc/address b/resources/styles/default/inc/address
index f1bb3a0..7c2b3bc 100644
--- a/resources/styles/default/inc/address
+++ b/resources/styles/default/inc/address
@@ -30,6 +30,11 @@ mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='
 mkgmap:country=AUT & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10|subst:Gemeinde |subst:Stadt }' }
 mkgmap:country=AUT & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:Gemeinde |subst:Stadt }' }
 
+# Poland = POL
+mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10}' }
+mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+mkgmap:country=POL & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4|subst:województwo =>}' }
+
 # other european countries
 mkgmap:country=BEL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=CZE & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
@@ -44,8 +49,6 @@ mkgmap:country=ISL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='$
 mkgmap:country=ITA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=LUX & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=NOR & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
-mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10}' }
-mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=PRT & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
 mkgmap:country=PRT & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=SVN & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10}' }
@@ -64,6 +67,21 @@ mkgmap:country=ECU & mkgmap:city!=* & mkgmap:admin_level6=* { set mkgmap:city='$
 mkgmap:country=ECU & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
 mkgmap:country=ECU & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 
+# South Africa = ZAF
+mkgmap:country=ZAF & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4}' }
+mkgmap:country=ZAF & mkgmap:city!=* & mkgmap:admin_level6=* { set mkgmap:city='${mkgmap:admin_level6}' }
+mkgmap:country=ZAF & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
+mkgmap:country=ZAF & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+
+# Slovakia
+mkgmap:country=SVK & mkgmap:city!=* & mkgmap:admin_level6=* { set mkgmap:city='${mkgmap:admin_level6}' }
+mkgmap:country=SVK & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9}' }
+mkgmap:country=SVK & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+
+# Greece
+mkgmap:country=GRE & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7}' }
+mkgmap:country=GRE & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+
 # common rules for all the rest of countries
 mkgmap:region!=* & mkgmap:admin_level6=* { set mkgmap:region='${mkgmap:admin_level6}' } 
 mkgmap:region!=* & mkgmap:admin_level5=* { set mkgmap:region='${mkgmap:admin_level5}' } 
diff --git a/resources/styles/default/inc/name b/resources/styles/default/inc/name
index 91b5f09..f3e9d98 100644
--- a/resources/styles/default/inc/name
+++ b/resources/styles/default/inc/name
@@ -1,6 +1,17 @@
 # Rules for naming objects, based on the following tags:
 # name, brand, operator, ref
 
+# delete FIXME values (they should be better used in maintenance maps)
+ref=FIXME | ref=fixme           { delete ref; }
+operator=FIXME | operator=fixme { delete operator; }
+brand=FIXME | brand=fixme       { delete brand; }
+name=FIXME | name=fixme         { delete name; }
+
+# delete duplicate names
+operator=${brand} { delete operator; }
+operator=${name}  { delete operator; }
+brand=${name}     { delete brand; }
+
 # None of operator, brand given
 ref=* & (operator!=* & brand!=*) { name '${ref} ${name}' | '${ref}' }
 
diff --git a/resources/styles/default/lines b/resources/styles/default/lines
index bd2c239..c43a29c 100644
--- a/resources/styles/default/lines
+++ b/resources/styles/default/lines
@@ -87,8 +87,6 @@ highway=path & snowplowing!=no
 {set highway=cycleway; set bicycle=yes}
 highway=path & (horse=designated|horse=permissive|horse=official)
 {set highway=bridleway; set horse=yes}
-highway=path
-{set highway=footway}
 
 leisure=track & area!=yes
 {add highway=footway; name '${name} (${sport})' | '${name}'}
@@ -96,10 +94,10 @@ leisure=track & area!=yes
 {add highway=footway; name '${ref} ${name}' | '${ref}' | '${name}' }
 
 # Roundabouts
-junction=roundabout & highway=trunk [0x0c road_class=3 road_speed=2 resolution 18]
-junction=roundabout & highway=primary [0x0c road_class=3 road_speed=2 resolution 19]
-junction=roundabout & highway=secondary [0x0c road_class=2 road_speed=2 resolution 20]
-junction=roundabout & highway=tertiary [0x0c road_class=1 road_speed=1 resolution 21]
+junction=roundabout & (highway=trunk | highway=trunk_link) [0x0c road_class=3 road_speed=2 resolution 18]
+junction=roundabout & (highway=primary | highway=primary_link) [0x0c road_class=3 road_speed=2 resolution 19]
+junction=roundabout & (highway=secondary | highway=secondary_link) [0x0c road_class=2 road_speed=2 resolution 20]
+junction=roundabout & (highway=tertiary | highway=tertiary_link) [0x0c road_class=1 road_speed=1 resolution 21]
 junction=roundabout & highway=unclassified [0x0c road_class=1 road_speed=1 resolution 21]
 junction=roundabout [0x0c road_class=0 road_speed=1 resolution 22]
 
@@ -167,7 +165,8 @@ railway=abandoned [0x0a road_class=0 road_speed=1 resolution 22]
 railway=platform [0x16 road_class=0 road_speed=0 resolution 23]
 railway=* & !(tunnel=yes) [0x14 resolution 22]
 
-man_made=cable|(man_made=* & man_made ~ '.*pipe.*')
+(man_made=cable|(man_made=* & man_made ~ '.*pipe.*')) & area!=yes &
+tunnel!=yes & location != underground
 {name '${name} (${operator})' | '${name}' | '${operator}' }
 [0x28 resolution 23]
 
@@ -194,6 +193,9 @@ include 'inc/roadspeed';
 # calculate the access rules
 include 'inc/access';
 
+#limit artificial cycleways to to resolution 24
+mkgmap:synthesised=yes & mkgmap:bicycle=yes { set mkgmap:highest-resolution-only = true }
+
 name=* { name '${name}' }
 
 highway=* & ref=* { addlabel '${ref}' }
diff --git a/resources/styles/default/points b/resources/styles/default/points
index c7ed57b..eb88a5c 100644
--- a/resources/styles/default/points
+++ b/resources/styles/default/points
@@ -133,7 +133,7 @@ amenity=fuel [0x2f01 resolution 24]
 amenity=kindergarten [0x2c05 resolution 24]
 amenity=library [0x2c03 resolution 24]
 amenity=nightclub [0x2d02 resolution 24]
-amenity=nursing_home [0x2b04 resolution 24]
+amenity=nursing_home [0x2f14 resolution 24]
 amenity=parking [0x2f0b resolution 24 default_name 'Parking']
 amenity=pharmacy [0x2e05 resolution 24]
 amenity=place_of_worship [0x2c0b resolution 24]
@@ -213,7 +213,7 @@ leisure=recreation_ground [0x2c08 resolution 24]
 leisure=sports_center | leisure=sports_centre { name '${name} (${sport})' | '${name}' } [0x2d0a resolution 24]
 leisure=stadium { name '${name} (${sport})' | '${name}' } [0x2c08 resolution 24]
 leisure=track { name '${name} (${sport})' | '${name}' } [0x2c08 resolution 24]
-leisure=water_park [0x2b04 resolution 24]
+leisure=water_park [0x2d09 resolution 24]
 
 man_made=tower|landmark=chimney [0x6411 resolution 24]
 
diff --git a/resources/styles/default/polygons b/resources/styles/default/polygons
index edb9a28..d25da79 100644
--- a/resources/styles/default/polygons
+++ b/resources/styles/default/polygons
@@ -61,14 +61,16 @@ historic=museum | historic=memorial [0x1e resolution 21]
 historic=archaeological_site | historic=ruins [0x1e resolution 21]
 
 # building tag should be last
-(building=* | amenity=*) & area!=no [0x13 resolution 24]
-tourism=* & area!=no & waterway!=* [0x13 resolution 24]
+(building=* | amenity=*) & area!=no & amenity!=grave_yard [0x13 resolution 24]
+tourism=* & area!=no & waterway!=* [0x1f resolution 24]
 # man_made can be used on areas or lines
 man_made=* & area!=no
 & (man_made!=door & man_made!=embankment & man_made!=breakwater
    & man_made!=cable_line & man_made!=cutline & man_made!=cutting
-   & man_made!=levee & man_made!=trench)
+   & man_made!=levee & man_made!=trench
+   & man_made!=groyne & man_made!=reinforced_slope)
 [0x13 resolution 24]
+
 man_made=* & area=yes
 [0x13 resolution 24]
 
@@ -78,4 +80,4 @@ include 'inc/landuse_polygons';
 <finalize>
 # The finalizer section is executed for each element when a rule with an element type matches
 
-name=* { name '${name}' }
\ No newline at end of file
+name=* { name '${name}' }
diff --git a/resources/styles/default/relations b/resources/styles/default/relations
index f3d0418..cbc587c 100644
--- a/resources/styles/default/relations
+++ b/resources/styles/default/relations
@@ -24,7 +24,7 @@
 }
 
 # European E-Road network
-route=road & network=e-road { apply { add ref='${ref}'; add int_ref='${int_ref'}; add network=e-road }
+route=road & network=e-road { apply { add ref='${ref}'; add int_ref='${int_ref}'; add network='e-road' } }
 
 # Public transportation routes.
 # We could want to sort the matching relations by ref first.
diff --git a/scripts/download/buildwatch b/scripts/download/buildwatch
deleted file mode 100755
index 7511e8a..0000000
--- a/scripts/download/buildwatch
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-BIN=/home/steve/scripts/mkgmap
-
-while :
-do
-	redis-cli blpop svn:branch-build-trigger 0
-	if [ "$?" != "0" ]; then
-		echo redis connection failed
-		sleep 60
-		continue
-	fi
-
-	(
-	$BIN/mksnapbranches mkgmap
-	$BIN/mksnapbranches splitter
-	) | mail -s 'Branch builds' steve at parabola.me.uk
-	sleep 120
-done
diff --git a/scripts/download/mkdoc b/scripts/download/mkdoc
index e8ca1ed..5a39fa2 100755
--- a/scripts/download/mkdoc
+++ b/scripts/download/mkdoc
@@ -15,8 +15,7 @@ fi
 
 for f in *.txt
 do
-	export PYTHONPATH=$HOME/www/mkgmap.org.uk
-	python -m wiki.main -t text $f > ../dist/doc/$f
+	mwtext -t text $f > ../dist/doc/$f
 done
 
 DOC=~/www/web/mkgmap/content/doc
diff --git a/scripts/download/mksnap b/scripts/download/mksnap
index ea1123b..3993e1c 100755
--- a/scripts/download/mksnap
+++ b/scripts/download/mksnap
@@ -1,10 +1,11 @@
 #!/bin/bash
 
 PROGDIR=$(dirname $(readlink -f $0))
-ANT_JAVAC_TARGET="-Dant.build.javac.target=1.6 -Dant.build.javac.source=1.6"
+#ANT_JAVAC_TARGET="-Dant.build.javac.target=1.6 -Dant.build.javac.source=1.6"
+#ANT_JAVAC_TARGET="-Dant.build.javac.target=1.7 -Dant.build.javac.source=1.7"
 
 export JAVA_HOME=/opt/java
-export PATH=$JAVA_HOME/bin:/opt/jars/apache-ant-1.8.2/bin:/opt/jars/apache-ant-1.7.1/bin:/usr/bin:/bin:/usr/local/bin:$PROGDIR
+export PATH="$JAVA_HOME/bin:$VIRTUAL_ENV/bin:/bin:/usr/bin:/usr/local/bin:$PROGDIR"
 export LANG=en_GB.UTF-8
 
 source mksnapfuncs
@@ -80,7 +81,7 @@ if [ $BRANCH != 'trunk' ]; then
 fi
 
 if [ ! -f $TARGET ]; then
-	echo Rebuilding
+	echo Rebuilding $TARGET
 	cd /usr/tmp
 
 	BUILD_DIR=/usr/tmp/build-mkgmap/$RELNAME
@@ -105,13 +106,14 @@ if [ ! -f $TARGET ]; then
 	(cd $(dirname $BUILD_DIR); zip -r $TARGET_SRC_ZIP $RELNAME) > /dev/null
 
 	if [ "$BUILD_DIST" = 1 ]; then
-		echo BUILDING TARGET
 		ant $ANT_JAVAC_TARGET -Dhave.version=1 $BUILD_TARGETS || {
 			redis-cli lpush svn:build "failed:$PRODUCT:$BRANCH:$VERSION"
+			rm -f $TARGET_SRC_ZIP $TARGET_SRC
 			exit 1
 		}
 
-		if [ -x $PROGDIR/mkdoc ]; then
+		CURRENT=$(redis-cli hget svn:current:$PRODUCT version)
+		if [ -x $PROGDIR/mkdoc -a $VERSION -gt $CURRENT ]; then
 			$PROGDIR/mkdoc $PRODUCT
 		fi
 
diff --git a/scripts/download/mksnapbranches b/scripts/download/mksnapbranches
index b1e14f7..550dcb8 100755
--- a/scripts/download/mksnapbranches
+++ b/scripts/download/mksnapbranches
@@ -3,7 +3,8 @@
 PROGDIR=$(dirname $(readlink -f $0))
 
 export JAVA_HOME=/opt/java
-PATH=$JAVA_HOME/bin:/opt/jars/apache-ant-1.7.1/bin:/usr/bin:/bin:/usr/local/bin:$PROGDIR
+export PATH="$JAVA_HOME/bin:$VIRTUAL_ENV/bin:/bin:/usr/bin:/usr/local/bin:$PROGDIR"
+export LANG=en_GB.UTF-8
 
 typeset -i VERSION
 typeset -i MIN_VERSION
diff --git a/scripts/download/mksnapindex b/scripts/download/mksnapindex
deleted file mode 100755
index 254e327..0000000
--- a/scripts/download/mksnapindex
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/bin/bash
-
-PROGDIR=$(dirname $(readlink -f $0))
-
-while getopts "r:d:" c
-do
-	case $c in
-	d) SNAPDIR=$OPTARG;;
-	esac
-done
-shift $[OPTIND-1]
-
-PRODUCT=$1
-if [ "$PRODUCT" = "" ]; then
-	echo Usage: "mksnap [-d dir] <product>"
-	exit
-fi
-
-SNAPDIR=${SNAPDIR:-$HOME/www/mkgmap.org.uk/docroot/download}
-
-print_product_line() {
-	file=$1
-	set $file $(ls -Lhtl $file| sed 's/  */ /' | sed 's/ \+/ /g' | cut -d ' ' -f5-9)
-
-	case $file in
-	*-src.tar.gz)
-		return;;
-	*latest*)
-		fileref=$(ls -l $file | cut -d' ' -f11)
-		;;
-	*)
-		fileref=$file
-		;;
-	esac
-
-	name=${file%.tar.gz}
-	refbase=${fileref%.tar.gz}
-
-	if [ -f $file ]; then
-		echo '<tr>'
-		echo "<td>$name</td>"
-		case $name in
-		*.jar)
-			echo "<td> <a href='$fileref'>[.jar]</a> "
-			;;
-		*)
-			echo "<td> <a href='$fileref'>[.tar.gz]</a> "
-			echo "<a href='$refbase.zip'>[.zip]</a> "
-			echo "<a href='${refbase}-src.tar.gz'>[src]</a> </td>"
-			;;
-		esac
-		echo "<td> </td>"
-		echo "<td>$3 $4</td>"
-		echo "<td>$5</td>"
-		echo "<td> </td>"
-		echo "<td>$2</td>"
-		echo '</tr>'
-	fi
-}
-
-cd $SNAPDIR
-
-sed -e "s/%(PRODUCT)s/$PRODUCT/" $PROGDIR/skel-top > $PRODUCT.html
-
-(
-#print_product_line $PRODUCT-latest.tar.gz
-for file in $(ls -t)
-do
-	case $file in
-	$PRODUCT-r*tar.gz)
-		print_product_line $file
-		;;
-	*)
-		;;
-	esac
-done
-) >> $PRODUCT.html
-
-if [ $(echo *.jar | wc -c) -gt 6 ] ; then
-	echo '</table><h1>Branch builds</h1>
-	<p>These jar files are latest builds of recent development branches.
-	They are useful if you want to quickly test a branch without having
-	obtain and build it.  The source code is available via subversion.
-	<p>As this is an automatic process, some of the branches might be
-	already merged with the main line, or abandoned and of course they
-	may not work.
-	<table>
-	' >> $PRODUCT.html
-
-	for file in $(ls -t)
-	do
-		case $file in
-		$PRODUCT-*jar)
-			print_product_line $file $(ls -Ltl $file| sed 's/  */ /' | sed 's/ \+/ /g' | cut -d ' ' -f6-9,11-)
-			;;
-		*)
-			;;
-		esac
-	done >> $PRODUCT.html
-else
-	echo '<tr><td></td></tr>' >> $PRODUCT.html
-fi
-
-if [ $PRODUCT = mkgmap ]; then
-	PRODUCT_AD=9104507573
-else
-	PRODUCT_AD=0835769470
-fi
-
-sed -e "s/%(PRODUCT_AD)s/$PRODUCT_AD/" $PROGDIR/skel-bot >> $PRODUCT.html
diff --git a/scripts/download/nightly b/scripts/download/nightly
new file mode 100755
index 0000000..91e8465
--- /dev/null
+++ b/scripts/download/nightly
@@ -0,0 +1,11 @@
+
+CMDS='prune:mkgmap prune:splitter
+current:mkgmap current:splitter
+build:mkgmap:trunk: build:splitter:trunk:
+stats
+'
+
+for cmd in $CMDS
+do
+	redis-cli lpush svn:build $cmd
+done > /dev/null
diff --git a/scripts/download/post-commit-svn-redis b/scripts/download/post-commit-svn-redis
new file mode 100755
index 0000000..374f0db
--- /dev/null
+++ b/scripts/download/post-commit-svn-redis
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+REPOS="$1"
+REV="$2"
+
+PROJECT=${REPOS##*/}
+
+redis-cli lpush "svn:actions" "commit:$PROJECT:$REV"
diff --git a/scripts/download/pre-commit-crlf b/scripts/download/pre-commit-crlf
new file mode 100755
index 0000000..e548139
--- /dev/null
+++ b/scripts/download/pre-commit-crlf
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+REPOS="$1"
+TXN="$2"
+
+TMP=$(mktemp)
+trap "rm -f $TMP" 0
+
+# Make sure that the log message contains some text.
+SVNLOOK=/usr/bin/svnlook
+
+$SVNLOOK changed -t "$TXN" "$REPOS" | while read kind file
+do
+		$SVNLOOK -t "$TXN" cat "$REPOS" "$file" > $TMP
+		file $TMP | grep -q 'CRLF line'
+		if [ $? = 0 ]; then
+				echo "File '$file' contains CRLF line endings" >&2
+				exit 2
+		fi
+	done
+exit $?
+
diff --git a/scripts/download/skel-bot b/scripts/download/skel-bot
deleted file mode 100644
index 5fb9f1f..0000000
--- a/scripts/download/skel-bot
+++ /dev/null
@@ -1,45 +0,0 @@
-</table>
-</div>
-</div>
-
-<div id="rightbar">
-<div id="right-inner">
-
-	<div class="text-links">
-	<script type="text/javascript"><!--
-	google_ad_client = "ca-pub-0485692618255663";
-	/* mkgmap files */
-	google_ad_slot = "%(PRODUCT_AD)s";
-	google_ad_width = 160;
-	google_ad_height = 600;
-	//-->
-	</script>
-	<script type="text/javascript"
-					src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
-	</script>
-	</div>
-
-
-<div style="margin-top: 50px">
-	<a href="http://www.jetbrains.com/idea/" style="position:
-            relative;display:block; width:127px; height:37px; border:0;
-            margin:0;padding:0;text-decoration:none;text-indent:0;"><span
-			style="margin: 0;padding: 0;position: absolute;top: 0;left: 33px;font-size:
-            10px;cursor:pointer;  background-image:none;border:0;color: #acc4f9;
-            font-family: trebuchet ms,arial,sans-serif;font-weight: normal;">Developed
-				with</span><img src="http://www.jetbrains.com/idea/opensource/img/all/banners/idea125x37_blue.gif"
-												alt="The best Java IDE" border="0"/></a>
-</div>
-
-</div>
-</div>
-
-</div>
-
-<div id="footer">
-	Copyright © 2012 Steve Ratcliffe
-</div>
-
-
-</body>
-</html>
diff --git a/scripts/download/skel-top b/scripts/download/skel-top
deleted file mode 100644
index e3148da..0000000
--- a/scripts/download/skel-top
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-	<title> mkgmap </title>
-	<link rel="stylesheet" type="text/css" href="/static/css/bundle.css">
-	<script src="/static/js/bundle.js" type="text/javascript"></script>
-	<link rel="alternate" type="application/atom+xml" title="mkgmap - Atom" href="http://www.mkgmap.org.uk/feed/atom">
-	<link rel="alternate" type="application/rss+xml" title="mkgmap - Rss" href="http://www.mkgmap.org.uk/feed/rss">
-	
-</head>
-<body>
-<div id="heading">
-	<img src="/static/img/logo.png" alt="logo">
-	<img src="/static/img/separator.png" alt="separator">
-</div>
-
-<div id="page">
-
-<div id="sidenav">
-
-<ul>
-<li><a href="/">Home</a></li>
-<li><a href="/news/">News</a></li>
-<li><a href="/doc/index.html">Documentation</a></li>
-<li class="active"><a href="/download/mkgmap.html">Download</a></li>
-<li class="active subnav"><a href="/download/mkgmap.html">mkgmap</a></li>
-<li class="active subnav"><a href="/download/splitter.html">splitter</a></li>
-<li><a href="/develop.html">Development</a></li>
-<li> <hr> </li>
-</ul>
-
-</div>
-
-<div id="maincontent" >
-<div id="main-inner">
-	
-
-<h1>Download %(PRODUCT)s</h1>
-<p>
-These snapshots are created automatically each night when there are changes.
-Each version can be downloaded in tar or zip format as well as its source code.
-The dates are the time that the version was created and can be used to
-judge the freshness of the release, you would normally download the top
-one unless you know it has a problem.
-<table>
-<col span="2">
-<col width="20px">
diff --git a/src/uk/me/parabola/imgfmt/MapFailedException.java b/src/uk/me/parabola/imgfmt/MapFailedException.java
index abea490..28f757f 100644
--- a/src/uk/me/parabola/imgfmt/MapFailedException.java
+++ b/src/uk/me/parabola/imgfmt/MapFailedException.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.imgfmt;
 
+import uk.me.parabola.log.Logger;
+
 /**
  * Used for cases where the current map has failed to compile, but the error
  * is expected to be specific to the map (eg it is too big etc).  When this
@@ -24,7 +26,7 @@ package uk.me.parabola.imgfmt;
  * @author Steve Ratcliffe
  */
 public class MapFailedException extends RuntimeException {
-
+	private static final Logger log = Logger.getLogger(MapFailedException.class);
 
 	/**
 	 * Constructs a new runtime exception with the specified detail message.
@@ -36,6 +38,7 @@ public class MapFailedException extends RuntimeException {
 	 */
 	public MapFailedException(String message) {
 		super(message);
+		log(message);
 	}
 
 	/**
@@ -54,5 +57,19 @@ public class MapFailedException extends RuntimeException {
 	 */
 	public MapFailedException(String message, Throwable cause) {
 		super(message, cause);
+		log(message);
+	}
+	
+	private static void log(String message){
+		String thrownBy = "";
+		try{
+			StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
+			int callerPosInStack = 3; 
+			String[] caller = stackTraceElements[callerPosInStack].getClassName().split("\\.");
+			thrownBy = "(thrown in " + caller[caller.length-1]+ "." +stackTraceElements[callerPosInStack].getMethodName() + "()) ";
+		} catch(Exception e){
+			log.info(e);
+		}
+		log.error(thrownBy + message);
 	}
 }
\ No newline at end of file
diff --git a/src/uk/me/parabola/imgfmt/Utils.java b/src/uk/me/parabola/imgfmt/Utils.java
index d4bc516..b48ec4c 100644
--- a/src/uk/me/parabola/imgfmt/Utils.java
+++ b/src/uk/me/parabola/imgfmt/Utils.java
@@ -228,6 +228,7 @@ public class Utils {
 	
 	/**
 	 * Calculates the angle between the two segments (c1,c2),(c2,c3).
+	 * It is assumed that the segments are rhumb lines, not great circle paths.
 	 * @param c1 first point
 	 * @param c2 second point
 	 * @param c3 third point
@@ -244,6 +245,19 @@ public class Utils {
 		
 		return angle;
 	}
+	
+	/**
+	 * Calculates the angle between the two segments (c1,c2),(c2,c3)
+	 * using the coords in map units.
+	 * @param c1 first point
+	 * @param c2 second point
+	 * @param c3 third point
+	 * @return angle between the two segments in degree [-180;180]
+	 */
+	public static double getDisplayedAngle(Coord c1, Coord c2, Coord c3) {
+		return getAngle(c1.getDisplayedCoord(), c2.getDisplayedCoord(), c3.getDisplayedCoord());
+	}
+
 	public final static int NOT_STRAIGHT = 0;
 	public final static int STRAIGHT_SPIKE = 1;
 	public final static int STRICTLY_STRAIGHT = 2;
@@ -282,5 +296,92 @@ public class Utils {
 		return NOT_STRAIGHT;
 		
 	}
+
+	/**
+	 * Checks if the two segments (c1,c2),(c2,c3) form a straight line
+	 * using high precision coordinates.
+	 * @param c1 first point
+	 * @param c2 second point
+	 * @param c3 third point
+	 * @return NOT_STRAIGHT, STRAIGHT_SPIKE or STRICTLY_STRAIGHT 
+	 */
+	public static int isHighPrecStraight(Coord c1, Coord c2, Coord c3) {
+		if (c1.highPrecEquals(c3))
+			return STRAIGHT_SPIKE;
+		long area;
+		long c1Lat = c1.getHighPrecLat();
+		long c2Lat = c2.getHighPrecLat();
+		long c3Lat = c3.getHighPrecLat();
+		long c1Lon = c1.getHighPrecLon();
+		long c2Lon = c2.getHighPrecLon();
+		long c3Lon = c3.getHighPrecLon();
+		// calculate the area that is enclosed by the three points
+		// (as if a closing line is drawn from c3 back to c1)
+		area = c1Lon * c2Lat - c2Lon * c1Lat;
+		area += c2Lon * c3Lat - c3Lon * c2Lat;
+		area += c3Lon * c1Lat - c1Lon * c3Lat;
+		if (area == 0){
+			// area is empty-> points lie on a straight line
+			long delta1 = c1Lat - c2Lat;
+			long delta2 = c2Lat - c3Lat;
+			if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
+				return STRAIGHT_SPIKE;
+			delta1 = c1Lon - c2Lon;
+			delta2 = c2Lon - c3Lon;
+			if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
+				return STRAIGHT_SPIKE;
+			return STRICTLY_STRAIGHT;
+		}
+		// line is not straight
+		return NOT_STRAIGHT;
+		
+	}
+
+	/**
+	 * approximate atan2, much faster than Math.atan2()
+	 * Based on a 50-year old arctan approximation due to Hastings
+	 */
+	private final static double PI_BY_2 = Math.PI / 2;
+	// |error| < 0.005
+	public static double atan2_approximation( double y, double x )
+	{
+		if ( x == 0.0f )
+		{
+			if ( y > 0.0f ) return PI_BY_2 ;
+			if ( y == 0.0f ) return 0.0f;
+			return -PI_BY_2 ;
+		}
+		double atan;
+		double z = y/x;
+		if ( Math.abs( z ) < 1.0f )
+		{
+			atan = z/(1.0f + 0.28f*z*z);
+			if ( x < 0.0f )
+			{
+				if ( y < 0.0f ) return atan - Math.PI;
+				return atan + Math.PI;
+			}
+		}
+		else
+		{
+			atan = PI_BY_2  - z/(z*z + 0.28f);
+			if ( y < 0.0f ) return atan - Math.PI;
+		}
+		return atan;
+	}
+	
+	/**
+	 * calculate a long value for the latitude and longitude of a coord
+	 * in high precision. 
+	 * @param co
+	 * @return a long that can be used as a key in HashMaps 
+	 */
+	public static long coord2Long(Coord co){
+		int lat30 = co.getHighPrecLat();
+		int lon30 = co.getHighPrecLon();
+		
+		return (long)(lat30 & 0xffffffffL) << 32 | (lon30 & 0xffffffffL);
+	}
+	
 }
 
diff --git a/src/uk/me/parabola/imgfmt/app/Area.java b/src/uk/me/parabola/imgfmt/app/Area.java
index 7dbf8d6..2bc6ac0 100644
--- a/src/uk/me/parabola/imgfmt/app/Area.java
+++ b/src/uk/me/parabola/imgfmt/app/Area.java
@@ -91,7 +91,7 @@ public class Area {
 	}
 
 	public Coord getCenter() {
-		return new Coord((minLat + maxLat)/2, (minLong + maxLong)/2);
+		return new Coord((minLat + maxLat)/2, (minLong + maxLong)/2);// high prec not needed
 	}
 
 	public String toString() {
@@ -153,26 +153,66 @@ public class Area {
 		return Math.max(getWidth(), getHeight());
 	}
 
+	/**
+	 * 
+	 * @param co a coord
+	 * @return true if co is inside the Area (it may touch the boundary)
+	 */
 	public final boolean contains(Coord co) {
-		// return true if co is inside the Area (it may touch the
-		// boundary)
-		return co.getLatitude() >= minLat
-				&& co.getLatitude() <= maxLat
-				&& co.getLongitude() >= minLong
-				&& co.getLongitude() <= maxLong;
+		int lat30 = co.getHighPrecLat();
+		int lon30 = co.getHighPrecLon();
+		return lat30  >= (minLat << Coord.DELTA_SHIFT)
+				&& lat30 <= (maxLat << Coord.DELTA_SHIFT)
+				&& lon30 >= (minLong << Coord.DELTA_SHIFT)
+				&& lon30 <= (maxLong << Coord.DELTA_SHIFT);
+	}
+
+	/**
+	 * 
+	 * @param other an area
+	 * @return true if the other area is inside the Area (it may touch the boundary)
+	 */
+	public final boolean contains(Area other) {
+		return other.getMinLat() >= minLat
+				&& other.getMaxLat() <= maxLat
+				&& other.getMinLong() >= minLong
+				&& other.getMaxLong() <= maxLong;
 	}
 
+	/**
+	 * @param co a coord
+	 * @return true if co is inside the Area and doesn't touch the boundary
+	 */
 	public final boolean insideBoundary(Coord co) {
-		// return true if co is inside the Area and doesn't touch the
-		// boundary
-		return co.getLatitude() > minLat
-				&& co.getLatitude() < maxLat
-				&& co.getLongitude() > minLong
-				&& co.getLongitude() < maxLong;
+		int lat30 = co.getHighPrecLat();
+		int lon30 = co.getHighPrecLon();
+		
+		return lat30  > (minLat << Coord.DELTA_SHIFT)
+				&& lat30 < (maxLat << Coord.DELTA_SHIFT)
+				&& lon30 > (minLong << Coord.DELTA_SHIFT)
+				&& lon30 < (maxLong << Coord.DELTA_SHIFT);
 	}
+	
 
+	
+	/**
+	 * 
+	 * @param other an area
+	 * @return true if the other area is inside the Area and doesn't touch the boundary 
+	 */
+	public final boolean insideBoundary(Area other) {
+		return other.getMinLat() > minLat
+				&& other.getMaxLat() < maxLat
+				&& other.getMinLong() > minLong
+				&& other.getMaxLong() < maxLong;
+	}
+
+
+	/**
+	 * @param co
+	 * @return true if co is on the boundary
+	 */
 	public final boolean onBoundary(Coord co) {
-		// return true if co is on the boundary
 		return contains(co) && !insideBoundary(co);
 	}
 	
@@ -193,6 +233,11 @@ public class Area {
 		return minLat >= maxLat || minLong >= maxLong;
 	}
 
+	/**
+	 * 	
+	 * @param coords a list of coord instances
+	 * @return false if any of the coords lies on or outside of this area
+	 */
 	public boolean allInsideBoundary(List<Coord> coords) {
 		for (Coord co : coords) {
 			if (!insideBoundary(co))
diff --git a/src/uk/me/parabola/imgfmt/app/BufferedImgFileWriter.java b/src/uk/me/parabola/imgfmt/app/BufferedImgFileWriter.java
index d94d33a..3c0c642 100644
--- a/src/uk/me/parabola/imgfmt/app/BufferedImgFileWriter.java
+++ b/src/uk/me/parabola/imgfmt/app/BufferedImgFileWriter.java
@@ -195,8 +195,8 @@ public class BufferedImgFileWriter implements ImgFileWriter {
 				// Previous message was confusing people, although it is difficult to come
 				// up with something that is strictly true in all situations.
 				throw new MapFailedException(
-						"There is not enough room in a single garmin map for all the input data\n" +
-								"   The .osm file should be split into smaller pieces first.");
+						"There is not enough room in a single garmin map for all the input data." +
+								" The .osm file should be split into smaller pieces first.");
 			}
 			ByteBuffer newb = ByteBuffer.allocate(bufferSize);
 			newb.order(ByteOrder.LITTLE_ENDIAN);
diff --git a/src/uk/me/parabola/imgfmt/app/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java
index 2db395e..5daa21a 100644
--- a/src/uk/me/parabola/imgfmt/app/Coord.java
+++ b/src/uk/me/parabola/imgfmt/app/Coord.java
@@ -16,10 +16,12 @@
  */
 package uk.me.parabola.imgfmt.app;
 
-import java.util.Formatter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Locale;
 
 import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 
 /**
  * A point coordinate in unshifted map-units.
@@ -29,19 +31,38 @@ import uk.me.parabola.imgfmt.Utils;
  *
  * You can create one of these with lat/long by calling the constructor with
  * double args.
+ * 
+ * See also http://www.movable-type.co.uk/scripts/latlong.html
  *
  * @author Steve Ratcliffe
  */
 public class Coord implements Comparable<Coord> {
-	private final static byte ON_BOUNDARY_MASK = 0x01; // bit in flags is true if point lies on a boundary
-	private final static byte PRESERVED_MASK = 0x02; // bit in flags is true if point should not be filtered out
-	private final static byte REPLACED_MASK = 0x04;  // bit in flags is true if point was replaced 
-	private final static byte TREAT_AS_NODE_MASK = 0x08; // bit in flags is true if point should be treated as a node
-	private final static byte FIXME_NODE_MASK = 0x10; // bit in flags is true if a node with this coords has a fixme tag
+	private final static short ON_BOUNDARY_MASK = 0x0001; // bit in flags is true if point lies on a boundary
+	private final static short PRESERVED_MASK = 0x0002; // bit in flags is true if point should not be filtered out
+	private final static short REPLACED_MASK = 0x0004;  // bit in flags is true if point was replaced 
+	private final static short TREAT_AS_NODE_MASK = 0x0008; // bit in flags is true if point should be treated as a node 
+	private final static short FIXME_NODE_MASK = 0x0010; // bit in flags is true if a node with this coords has a fixme tag
+	private final static short REMOVE_MASK = 0x0020; // bit in flags is true if this point should be removed
+	private final static short VIA_NODE_MASK = 0x0040; // bit in flags is true if a node with this coords is the via node of a RestrictionRelation
+	
+	private final static short PART_OF_BAD_ANGLE = 0x0080; // bit in flags is true if point should be treated as a node
+	private final static short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger
+	private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
+	
+	public final static int HIGH_PREC_BITS = 30;
+	public final static int DELTA_SHIFT = 6;
+	
+	public final static double R = 6378137.0; // Radius of earth as defined by WGS84
+	public final static double U = R * 2 * Math.PI; // circumference of earth (WGS84)
+	
 	private final int latitude;
 	private final int longitude;
 	private byte highwayCount; // number of highways that use this point
-	private byte flags; // further attributes
+	private short flags; // further attributes
+	private final byte latDelta; // delta to 30 bit lat value 
+	private final byte lonDelta; // delta to 30 bit lon value
+	private final static byte MAX_DELTA = 16; // max delta abs value that is considered okay
+	private short approxDistanceToDisplayedCoord = -1;
 
 	/**
 	 * Construct from co-ordinates that are already in map-units.
@@ -51,6 +72,7 @@ public class Coord implements Comparable<Coord> {
 	public Coord(int latitude, int longitude) {
 		this.latitude = latitude;
 		this.longitude = longitude;
+		latDelta = lonDelta = 0;
 	}
 
 	/**
@@ -61,6 +83,42 @@ public class Coord implements Comparable<Coord> {
 	public Coord(double latitude, double longitude) {
 		this.latitude = Utils.toMapUnit(latitude);
 		this.longitude = Utils.toMapUnit(longitude);
+		int lat30 = toBit30(latitude);
+		int lon30 = toBit30(longitude);
+		this.latDelta = (byte) ((this.latitude << 6) - lat30); 
+		this.lonDelta = (byte) ((this.longitude << 6) - lon30);
+		
+		// verify math
+		assert (this.latitude << 6) - latDelta == lat30;
+		assert (this.longitude << 6) - lonDelta == lon30;
+	}
+	
+	private Coord (int lat, int lon, byte latDelta, byte lonDelta){
+		this.latitude = lat;
+		this.longitude = lon;
+		this.latDelta = latDelta;
+		this.lonDelta = lonDelta;
+	}
+	
+	public static Coord makeHighPrecCoord(int lat30, int lon30){
+		int lat24 = (lat30 + (1 << 5)) >> 6;  
+		int lon24 = (lon30 + (1 << 5)) >> 6;
+		byte dLat = (byte) ((lat24 << 6) - lat30);
+		byte dLon = (byte) ((lon24 << 6) - lon30);
+		return new Coord(lat24,lon24,dLat,dLon);
+	}
+	
+	/**
+	 * Construct from other coord instance, copies 
+	 * the lat/lon values in high precision
+	 * @param other
+	 */
+	public Coord(Coord other) {
+		this.latitude = other.latitude;
+		this.longitude = other.longitude;
+		this.latDelta = other.latDelta;
+		this.lonDelta = other.lonDelta;
+		this.approxDistanceToDisplayedCoord = other.approxDistanceToDisplayedCoord;
 	}
 
 	public int getLatitude() {
@@ -71,7 +129,10 @@ public class Coord implements Comparable<Coord> {
 		return longitude;
 	}
 
-	public long getId() {
+	/**
+	 * @return the route node id
+	 */
+	public int getId() {
 		return 0;
 	}
 
@@ -163,8 +224,8 @@ public class Coord implements Comparable<Coord> {
 			this.flags |= TREAT_AS_NODE_MASK;
 		else 
 			this.flags &= ~TREAT_AS_NODE_MASK; 
-	}
-
+	} 
+	
 	/**
 	 * Does this coordinate belong to a node with a fixme tag?
 	 * Note that the value is set after evaluating the points style. 
@@ -181,6 +242,95 @@ public class Coord implements Comparable<Coord> {
 			this.flags &= ~FIXME_NODE_MASK; 
 	}
 	
+	public boolean isToRemove() {
+		return (flags & REMOVE_MASK) != 0;
+	}
+	
+	public void setRemove(boolean b) {
+		if (b) 
+			this.flags |= REMOVE_MASK;
+		else 
+			this.flags &= ~REMOVE_MASK; 
+	}
+	
+	/**
+	 * @return true if this coordinate belong to a via node of a restriction relation
+	 */
+	public boolean isViaNodeOfRestriction() {
+		return (flags & VIA_NODE_MASK) != 0;
+	}
+
+	/**
+	 * @param b true: Mark the coordinate as  via node of a restriction relation
+	 */
+	public void setViaNodeOfRestriction(boolean b) {
+		if (b) 
+			this.flags |= VIA_NODE_MASK;
+		else 
+			this.flags &= ~VIA_NODE_MASK; 
+	}
+	
+	/** 
+	 * Should this Coord be treated by the removeWrongAngle method=
+	 * The value has no meaning outside of StyledConverter.
+	 * @return true if this coord is part of a line that has a big bearing error. 
+	 */
+	public boolean isPartOfBadAngle() {
+		return (flags & PART_OF_BAD_ANGLE) != 0;
+	}
+
+	/**
+	 * Mark the Coord to be part of a line which has a big bearing
+	 * error because of the rounding to map units. 
+	 * @param b true or false
+	 */
+	public void setPartOfBadAngle(boolean b) {
+		if (b) 
+			this.flags |= PART_OF_BAD_ANGLE;
+		else 
+			this.flags &= ~PART_OF_BAD_ANGLE; 
+	}
+
+	/** 
+	 * Get flag for {@link ShapeMergeFilter}
+	 * The value has no meaning outside of {@link ShapeMergeFilter}
+	 * @return  
+	 */
+	public boolean isPartOfShape2() {
+		return (flags & PART_OF_SHAPE2) != 0;
+	}
+
+	/**
+	 * Set or unset flag for {@link ShapeMergeFilter} 
+	 * @param b true or false
+	 */
+	public void setPartOfShape2(boolean b) {
+		if (b) 
+			this.flags |= PART_OF_SHAPE2;
+		else 
+			this.flags &= ~PART_OF_SHAPE2; 
+	}
+
+	/** 
+	 * Get flag for {@link WrongAngleFixer}
+	 * The value has no meaning outside of {@link WrongAngleFixer}
+	 * @return  
+	 */
+	public boolean isEndOfWay() {
+		return (flags & END_OF_WAY) != 0;
+	}
+
+	/**
+	 * Set or unset flag for {@link WrongAngleFixer} 
+	 * @param b true or false
+	 */
+	public void setEndOfWay(boolean b) {
+		if (b) 
+			this.flags |= END_OF_WAY;
+		else 
+			this.flags &= ~END_OF_WAY; 
+	}
+
 	public int hashCode() {
 		// Use a factor for latitude to span over the whole integer range:
 		// max lat: 4194304
@@ -195,44 +345,39 @@ public class Coord implements Comparable<Coord> {
 		Coord other = (Coord) obj;
 		return latitude == other.latitude && longitude == other.longitude;
 	}
+	
+	public boolean highPrecEquals(Coord other) {
+		if (other == null)
+			return false;
+		if (this == other)
+			return true;
+		return getHighPrecLat() == other.getHighPrecLat() && getHighPrecLon() == other.getHighPrecLon(); 
+	} 
 
 	/**
-	 * Distance to other point in meters.
+	 * Distance to other point in metres, using
+	 * "flat earth approximation" or rhumb-line algo
 	 */
 	public double distance(Coord other) {
-		return quickDistance(other);
-	}
-
-	protected double slowDistance(Coord other) {
-		if (equals(other))
-			return 0;
-
-		double lat1 = Utils.toRadians(latitude);
-		double lat2 = Utils.toRadians(other.getLatitude());
-		double lon1 = Utils.toRadians(getLongitude());
-		double lon2 = Utils.toRadians(other.getLongitude());
-
-		double R = 6371000; // meters
-
-		// cosine of great circle angle between points
-		double cangle = Math.sin(lat1)*Math.sin(lat2) +
-			        Math.cos(lat1)*Math.cos(lat2) * Math.cos(lon2-lon1);
-
-		return Math.acos(cangle) * R;
-  	}
-
-	public double quickDistance(Coord other){
-		return 40075000 * Math.sqrt(distanceInDegreesSquared(other)) / 360;
+		double d1 = U / 360 * Math.sqrt(distanceInDegreesSquared(other));
+		if (d1 < 10000)
+			return d1; // error is below 0.01 m
+		// for long distances, use more complex algorithm 
+		return distanceOnRhumbLine(other);
 	}
 
+	/**
+	 * Square of distance to other point in metres, using
+	 * "flat earth approximation" 
+	 */
 	public double distanceInDegreesSquared(Coord other) {
-		if (equals(other))
+		if (this == other || highPrecEquals(other))
 			return 0;
-
-		double lat1 = Utils.toDegrees(getLatitude());
-		double lat2 = Utils.toDegrees(other.getLatitude());
-		double long1 = Utils.toDegrees(getLongitude());
-		double long2 = Utils.toDegrees(other.getLongitude());
+		
+		double lat1 = getLatDegrees();
+		double lat2 = other.getLatDegrees();
+		double long1 = getLonDegrees();
+		double long2 = other.getLonDegrees();
 				
 		double latDiff;
 		if (lat1 < lat2)
@@ -255,39 +400,141 @@ public class Coord implements Comparable<Coord> {
 
 		return (latDiff * latDiff) + (longDiff * longDiff);
 	}
+	
+	/**
+	 * Distance to other point in metres following a great circle path, without 
+	 * flat earth approximation, slower but better with large 
+	 * distances and big deltas in lat AND lon. 
+	 * Similar to code in JOSM
+	 */
+	public double distanceHaversine (Coord point){
+		double lat1 = int30ToRadians(getHighPrecLat());
+		double lat2 = int30ToRadians(point.getHighPrecLat());
+		double lon1 = int30ToRadians(getHighPrecLon());
+		double lon2 = int30ToRadians(point.getHighPrecLon());
+		double sinMidLat = Math.sin((lat1-lat2)/2);
+		double sinMidLon = Math.sin((lon1-lon2)/2);
+		double dRad = 2*Math.asin(Math.sqrt(sinMidLat*sinMidLat + Math.cos(lat1)*Math.cos(lat2)*sinMidLon*sinMidLon));
+		double distance= dRad * R;
+		return distance;
+	}
 
-	public Coord makeBetweenPoint(Coord other, double fraction) {
-		return new Coord((int)(latitude + (other.latitude - latitude) * fraction),
-						 (int)(longitude + (other.longitude - longitude) * fraction));
+	/**
+	 * Distance to other point in metres following the shortest rhumb line.
+	 */
+	public double distanceOnRhumbLine(Coord point){
+		double lat1 = int30ToRadians(getHighPrecLat());
+		double lat2 = int30ToRadians(point.getHighPrecLat());
+		double lon1 = int30ToRadians(getHighPrecLon());
+		double lon2 = int30ToRadians(point.getHighPrecLon());
+		
+	    // see http://williams.best.vwh.net/avform.htm#Rhumb
+
+	    double dLat = lat2 - lat1;
+	    double dLon = Math.abs(lon2 - lon1);
+	    // if dLon over 180° take shorter rhumb line across the anti-meridian:
+	    if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
+
+	    // on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor'
+	    // q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it
+	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
+	    double q = Math.abs(deltaPhi) > 10e-12 ? dLat/deltaPhi : Math.cos(lat1);
+
+	    // distance is pythagoras on 'stretched' Mercator projection
+	    double distRad = Math.sqrt(dLat*dLat + q*q*dLon*dLon); // angular distance in radians
+	    double dist = distRad * R;
+
+	    return dist;
+		
 	}
 
+	/**
+	 * Calculate point on the line this->other. If d is the distance between this and other,
+	 * the point is {@code fraction * d} metres from this.
+	 * For small distances between this and other we use a flat earth approximation,
+	 * for large distances this could result in errors of many metres, so we use 
+	 * the rhumb line calculations. 
+	 */
+	public Coord makeBetweenPoint(Coord other, double fraction) {
+		int dLat30 = other.getHighPrecLat() - getHighPrecLat();
+		int dLon30 = other.getHighPrecLon() - getHighPrecLon();
+		if ((Math.abs(dLat30) < 1000000 && Math.abs(dLon30) < 1000000 )){
+			// distances are rather small, we can use flat earth approximation
+			int lat30 = (int) (getHighPrecLat() + dLat30 * fraction);
+			int lon30 = (int) (getHighPrecLon() + dLon30 * fraction);
+			return makeHighPrecCoord(lat30, lon30);
+		}
+		double brng = this.bearingToOnRhumbLine(other, true);
+		double dist = this.distance(other) * fraction;
+		return this.destOnRhumLine(dist, brng);
+	}
 
-	// returns bearing (in degrees) from current point to another point
+	
+	/**
+	 * returns bearing (in degrees) from current point to another point
+	 * following a rhumb line
+	 */
 	public double bearingTo(Coord point) {
-		double lat1 = Utils.toRadians(latitude);
-		double lat2 = Utils.toRadians(point.latitude);
-		double lon1 = Utils.toRadians(longitude);
-		double lon2 = Utils.toRadians(point.longitude);
+		return bearingToOnRhumbLine(point, false);
+	}
+
+	/**
+	 * returns bearing (in degrees) from current point to another point
+	 * following a great circle path
+	 * @param point the other point
+	 * @param needHighPrec set to true if you need a very high precision
+	 */
+	public double bearingToOnGreatCircle(Coord point, boolean needHighPrec) {
+		// use high precision values for this 
+		double lat1 = int30ToRadians(getHighPrecLat());
+		double lat2 = int30ToRadians(point.getHighPrecLat());
+		double lon1 = int30ToRadians(getHighPrecLon());
+		double lon2 = int30ToRadians(point.getHighPrecLon());
 
 		double dlon = lon2 - lon1;
 
 		double y = Math.sin(dlon) * Math.cos(lat2);
 		double x = Math.cos(lat1)*Math.sin(lat2) -
-			Math.sin(lat1)*Math.cos(lat2)*Math.cos(dlon);
-		return Math.atan2(y, x) * 180 / Math.PI;
+				Math.sin(lat1)*Math.cos(lat2)*Math.cos(dlon);
+		double brngRad = needHighPrec ? Math.atan2(y, x) : Utils.atan2_approximation(y, x);
+		return brngRad * 180 / Math.PI;
+	}
+
+	/**
+	 * returns bearing (in degrees) from current point to another point
+	 * following shortest rhumb line
+	 * @param point the other point
+	 * @param needHighPrec set to true if you need a very high precision
+	 */
+	public double bearingToOnRhumbLine(Coord point, boolean needHighPrec){
+		double lat1 = int30ToRadians(this.getHighPrecLat());
+		double lat2 = int30ToRadians(point.getHighPrecLat());
+		double lon1 = int30ToRadians(this.getHighPrecLon());
+		double lon2 = int30ToRadians(point.getHighPrecLon());
+
+		double dLon = lon2-lon1;
+	    // if dLon over 180° take shorter rhumb line across the anti-meridian:
+	    if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
+
+	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
+	    
+	    double brngRad = needHighPrec ? Math.atan2(dLon, deltaPhi) : Utils.atan2_approximation(dLon, deltaPhi);
+	    return brngRad * 180 / Math.PI;
 	}
 
+	
 	/**
 	 * Sort lexicographically by longitude, then latitude.
 	 *
 	 * This ordering is used for sorting entries in NOD3.
 	 */
 	public int compareTo(Coord other) {
-		if (longitude == other.getLongitude())
-			if (latitude == other.getLatitude()) return 0;
-			else return latitude > other.getLatitude() ? 1 : -1;
-		else
-			return longitude > other.getLongitude()? 1: -1;
+		if (longitude == other.getLongitude()) {
+			if (latitude == other.getLatitude())
+				return 0;
+			return latitude > other.getLatitude() ? 1 : -1;
+		}
+		return longitude > other.getLongitude() ? 1 : -1;
 	}			
 
 	/**
@@ -300,23 +547,237 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	public String toDegreeString() {
-		Formatter fmt = new Formatter();
-		return fmt.format("%.5f/%.5f",
-			Utils.toDegrees(latitude),
-			Utils.toDegrees(longitude)).toString();
+		return String.format(Locale.ENGLISH, "%.6f/%.6f",
+			getLatDegrees(),
+			getLonDegrees());
 	}
 
 	protected String toOSMURL(int zoom) {
 		return ("http://www.openstreetmap.org/?mlat=" +
-			new Formatter(Locale.ENGLISH).format("%.5f", Utils.toDegrees(latitude)) +
-			"&mlon=" +
-			new Formatter(Locale.ENGLISH).format("%.5f", Utils.toDegrees(longitude)) +
-			"&zoom=" +
-			zoom);
+				String.format(Locale.ENGLISH, "%.6f", getLatDegrees()) +
+				"&mlon=" +
+				String.format(Locale.ENGLISH, "%.6f", getLonDegrees()) +
+				"&zoom=" +
+				zoom);
 	}
 
 	public String toOSMURL() {
 		return toOSMURL(17);
 	}
 
+	/**
+	 * Convert latitude or longitude to 30 bits value.
+	 * This allows higher precision than the 24 bits
+	 * used in map units.
+	 * @param l The lat or long as decimal degrees.
+	 * @return An integer value with 30 bit precision.
+	 */
+	private static int toBit30(double l) {
+		double DELTA = 360.0D / (1 << 30) / 2; //Correct rounding
+		if (l > 0)
+			return (int) ((l + DELTA) * (1 << 30)/360);
+		return (int) ((l - DELTA) * (1 << 30)/360);
+		
+	}
+
+	/* Factor for conversion to radians using 30 bits
+	 * (Math.PI / 180) * (360.0 / (1 << 30)) 
+	 */
+	final static double BIT30_RAD_FACTOR = 2 * Math.PI / (1 << 30);
+	
+	/**
+	 * Convert to radians using high precision 
+	 * @param val30 a longitude/latitude value with 30 bit precision
+	 * @return an angle in radians.
+	 */
+	private static double int30ToRadians(int val30){
+		return BIT30_RAD_FACTOR * val30;
+	}
+
+	/**
+	 * @return Latitude as signed 30 bit integer 
+	 */
+	public int getHighPrecLat() {
+		return (latitude << 6) - latDelta;
+	}
+
+	/**
+	 * @return Longitude as signed 30 bit integer 
+	 */
+	public int getHighPrecLon() {
+		return (longitude << 6) - lonDelta;
+	}
+	
+	/**
+	 * @return latitude in degrees with highest avail. precision
+	 */
+	public double getLatDegrees(){
+		return (360.0D / (1 << 30)) * getHighPrecLat();
+	}
+	
+	/**
+	 * @return longitude in degrees with highest avail. precision
+	 */
+	public double getLonDegrees(){
+		return (360.0D / (1 << 30)) * getHighPrecLon();
+	}
+	
+	public Coord getDisplayedCoord(){
+		return new Coord(latitude,longitude);
+	}
+
+	public boolean hasAlternativePos(){
+		if (getOnBoundary())
+			return false;
+		return (Math.abs(latDelta) > MAX_DELTA || Math.abs(lonDelta) > MAX_DELTA);
+	}
+	/**
+	 * Calculate up to three points with equal 
+	 * high precision coordinate, but
+	 * different map unit coordinates. 
+	 * @return a list of Coord instances, is empty if alternative positions are too far
+	 */
+	public List<Coord> getAlternativePositions(){
+		ArrayList<Coord> list = new ArrayList<>();
+		if (getOnBoundary())
+			return list; 
+		byte modLatDelta = 0;
+		byte modLonDelta = 0;
+		
+		int modLat = latitude;
+		int modLon = longitude;
+		if (latDelta > MAX_DELTA)
+			modLat--;
+		else if (latDelta < -MAX_DELTA)
+			modLat++;
+		if (lonDelta > MAX_DELTA)
+			modLon--;
+		else if (lonDelta < -MAX_DELTA)
+			modLon++;
+		int lat30 = getHighPrecLat();
+		int lon30 = getHighPrecLon();
+		modLatDelta = (byte) ((modLat<<6) - lat30);
+		modLonDelta = (byte) ((modLon<<6) - lon30);
+		assert modLatDelta >= -63 && modLatDelta <= 63;
+		assert modLonDelta >= -63 && modLonDelta <= 63;
+		if (modLat != latitude){
+			if (modLon != longitude)
+				list.add(new Coord(modLat, modLon, modLatDelta, modLonDelta));
+			list.add(new Coord(modLat, longitude, modLatDelta, lonDelta));
+		} 
+		if (modLon != longitude)
+			list.add(new Coord(latitude, modLon, latDelta, modLonDelta));
+		/* verify math
+		for(Coord co:list){
+			double d = distance(new Coord (co.getLatitude(),co.getLongitude()));
+			assert d < 3.0;
+		}
+		*/
+		return list;
+	}
+	
+	/**
+	 * @return approximate distance in cm 
+	 */
+	public short getDistToDisplayedPoint(){
+		if (approxDistanceToDisplayedCoord < 0){
+		  approxDistanceToDisplayedCoord = (short)Math.round(getDisplayedCoord().distance(this)*100);
+		}
+		return approxDistanceToDisplayedCoord;
+	}
+	
+	/**
+	 * Get the coord that is {@code dist} metre away travelling with course
+	 * {@code brng} on a rhumb-line.
+	 * @param dist distance in m
+	 * @param brng bearing in degrees
+	 * @return a new Coord instance
+	 */
+	public Coord destOnRhumLine(double dist, double brng){
+	    double distRad = dist / R; // angular distance in radians
+		double lat1 = int30ToRadians(this.getHighPrecLat());
+		double lon1 = int30ToRadians(this.getHighPrecLon());
+
+	    double brngRad = Math.toRadians(brng);
+
+	    double deltaLat = distRad * Math.cos(brngRad);
+
+	    double lat2 = lat1 + deltaLat;
+	    // check for some daft bugger going past the pole, normalise latitude if so
+	    if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2;
+
+	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
+	    double q = Math.abs(deltaPhi) > 10e-12 ? deltaLat / deltaPhi : Math.cos(lat1); // E-W course becomes ill-conditioned with 0/0
+
+	    double deltaLon = distRad*Math.sin(brngRad)/q;
+
+	    double lon2 = lon1 + deltaLon;
+
+	    lon2 = (lon2 + 3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180º
+
+	    return new Coord(Math.toDegrees(lat2), Math.toDegrees(lon2));
+	}
+	
+	/**
+	 * Calculate the distance in metres to the rhumb line
+	 * defined by coords a and b.
+	 * @param a start point
+	 * @param b end point
+	 * @return perpendicular distance in m.  
+	 */
+	public double distToLineSegment(Coord a, Coord b){
+		double ap = a.distance(this);
+		double ab = a.distance(b);
+		double bp = b.distance(this);
+		double abpa = (ab+ap+bp)/2;
+		double dx = abpa-ab;
+		double dist;
+		if (dx < 0){
+			// simple calculation using Herons formula will fail
+			// calculate x, the point on line a-b which is as far away from a as this point
+			double b_ab = a.bearingToOnRhumbLine(b, true);
+			Coord x = a.destOnRhumLine(ap, b_ab);
+			// this dist between these two points is not exactly 
+			// the perpendicul distance, but close enough
+			dist = x.distance(this);
+		}
+		else 
+			dist = 2 * Math.sqrt(abpa * (abpa-ab) * (abpa-ap) * (abpa-bp)) / ab;
+		return dist;
+	}
+
+	/**
+	 * Calculate distance to rhumb line segment a-b  
+	 * @param a point a
+	 * @param b point b
+	 * @return distance in m
+	 */
+	public double shortestDistToLineSegment(Coord a, Coord b){
+		int aLon = a.getHighPrecLon();
+		int bLon = b.getHighPrecLon();
+		int pLon = this.getHighPrecLon();
+		int aLat = a.getHighPrecLat();
+		int bLat = b.getHighPrecLat();
+		int pLat = this.getHighPrecLat();
+		
+		double deltaLon = bLon - aLon;
+		double deltaLat = bLat - aLat;
+
+		double frac;
+		if (deltaLon == 0 && deltaLat == 0) 
+			frac = 0;
+		else 
+			frac = ((pLon - aLon) * deltaLon + (pLat - aLat) * deltaLat) / (deltaLon * deltaLon + deltaLat * deltaLat);
+
+		double distance;
+		if (frac <= 0) {
+			distance = a.distance(this);
+		} else if (frac >= 1) {
+			distance = b.distance(this);
+		} else {
+			distance = this.distToLineSegment(a, b);
+		}
+		return distance;
+	}
+	
 }
diff --git a/src/uk/me/parabola/imgfmt/app/CoordNode.java b/src/uk/me/parabola/imgfmt/app/CoordNode.java
index 9e9872f..d73dfb0 100644
--- a/src/uk/me/parabola/imgfmt/app/CoordNode.java
+++ b/src/uk/me/parabola/imgfmt/app/CoordNode.java
@@ -23,7 +23,7 @@ package uk.me.parabola.imgfmt.app;
  * @author Steve Ratcliffe
  */
 public class CoordNode extends Coord {
-	private final long id;
+	private final int id;
 
 	/**
 	 * Construct from co-ordinates that are already in map-units.
@@ -33,14 +33,21 @@ public class CoordNode extends Coord {
 	 * @param id The ID of this routing node.
 	 * @param boundary This is a routing node on the boundary.
 	 */
-	public CoordNode(int latitude, int longitude, long id, boolean boundary) {
+	public CoordNode(int latitude, int longitude, int id, boolean boundary) {
 		super(latitude, longitude);
 		this.id = id;
 		setOnBoundary(boundary);
 		preserved(true);
 	}
 
-	public long getId() {
+	public CoordNode(Coord other, int id, boolean boundary){
+		super(other);
+		this.id = id;
+		setOnBoundary(boundary);
+		preserved(true);
+		
+	}
+	public int getId() {
 		return id;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/Label.java b/src/uk/me/parabola/imgfmt/app/Label.java
index ad93db3..a558384 100644
--- a/src/uk/me/parabola/imgfmt/app/Label.java
+++ b/src/uk/me/parabola/imgfmt/app/Label.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006 Steve Ratcliffe
+ * Copyright (C) 2006,2014 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
@@ -31,30 +31,47 @@ import uk.me.parabola.imgfmt.app.labelenc.EncodedText;
  * 2. An 8 bit format.  This seems to be a fairly straightforward latin-1 like
  * encoding with no tricks to reduce the amount of space required.
  *
+ * 3. A multi-byte format. For unicode, cp932 etc.
+ *
  * @author Steve Ratcliffe
  */
-public class Label implements Comparable<Label> {
+public class Label {
+	public static final Label NULL_LABEL = new Label("");
+	public static final Label NULL_OUT_LABEL = new Label(new char[0]);
 
 	private final String text;
+	private final char[] encText;
 
 	// The offset in to the data section.
 	private int offset;
 
 	public Label(String text) {
 		this.text = text;
+		this.encText = null;
+	}
+
+	public Label(char[] encText) {
+		this.encText = encText;
+		this.text = null;
 	}
 
 	public int getLength() {
-		if (text == null)
-			return 0;
-		else
+		if (text != null)
 			return text.length();
+		if (encText != null)
+			return encText.length;
+		return 0;
 	}
 
 	public String getText() {
+		assert text != null;
 		return text;
 	}
 
+	public char[] getEncText() {
+		return encText;
+	}
+
 	// highway shields and "thin" separators
 	public final static Pattern SHIELDS = Pattern.compile("[\u0001-\u0006\u001b-\u001c]");
 
@@ -88,10 +105,7 @@ public class Label implements Comparable<Label> {
 	 * @return The offset within the LBL file of this string.
 	 */
 	public int getOffset() {
-		if (text == null || text.isEmpty())
-			return 0;
-		else
-			return offset;
+		return offset;
 	}
 
 	public void setOffset(int offset) {
@@ -115,7 +129,7 @@ public class Label implements Comparable<Label> {
 	 * String version of the label, for diagnostic purposes.
 	 */
 	public String toString() {
-		return "[" + offset + "]" + text;
+		return text != null ? text : "[" + offset + "]";
 	}
 
 	public boolean equals(Object o) {
@@ -123,20 +137,9 @@ public class Label implements Comparable<Label> {
 		if (o == null || getClass() != o.getClass()) return false;
 
 		return offset == ((Label) o).offset;
-
 	}
 
 	public int hashCode() {
 		return offset;
 	}
-
-	/**
-	 * Note: this class has a natural ordering that is inconsistent with equals.
-	 * (But perhaps it shouldn't?)
-	 */
-	public int compareTo(Label other) {
-		if(this == other)
-			return 0;
-		return text.compareToIgnoreCase(other.text);
-	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
index c7e0165..79aa19e 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Steve Ratcliffe
+ * Copyright (C) 2007,2014 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
@@ -48,7 +48,7 @@ public class AnyCharsetEncoder extends BaseEncoder implements CharacterEncoder {
 	}
 
 	public EncodedText encodeText(String text) {
-		if (text == null)
+		if (text == null || text.isEmpty())
 			return NO_TEXT;
 
 		if (!isCharsetSupported())
@@ -105,7 +105,10 @@ public class AnyCharsetEncoder extends BaseEncoder implements CharacterEncoder {
 		// We need it to be null terminated but also to trim any extra memory from the allocated
 		// buffer.
 		byte[] res = Arrays.copyOf(outBuf.array(), outBuf.position() + 1);
-		return new EncodedText(res, res.length);
+		char[] cres = new char[res.length];
+		for (int i = 0; i < res.length; i++)
+			cres[i] = (char) (res[i] & 0xff);
+		return new EncodedText(res, res.length, cres);
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/BaseEncoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/BaseEncoder.java
index fd06368..d64c29e 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/BaseEncoder.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/BaseEncoder.java
@@ -30,7 +30,7 @@ import uk.me.parabola.log.Logger;
 public class BaseEncoder {
 	private static final Logger log = Logger.getLogger(BaseEncoder.class);
 
-	protected static final EncodedText NO_TEXT = new EncodedText(null, 0);
+	public static final EncodedText NO_TEXT = new EncodedText(null, 0, null);
 
 	private boolean charsetSupported = true;
 
@@ -62,7 +62,7 @@ public class BaseEncoder {
 		for (char c : in)
 			out[off++] = (byte) (c & 0xff);
 
-		return new EncodedText(out, out.length);
+		return new EncodedText(out, out.length, in);
 	}
 
 	public boolean isUpperCase() {
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/CodeFunctions.java b/src/uk/me/parabola/imgfmt/app/labelenc/CodeFunctions.java
index 9d4c916..24996ec 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/CodeFunctions.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/CodeFunctions.java
@@ -72,33 +72,39 @@ public class CodeFunctions {
 	 */
 	public static CodeFunctions createEncoderForLBL(String charset) {
 		CodeFunctions funcs = new CodeFunctions();
-		if ("ascii".equals(charset)) {
+		switch (charset) {
+		case "ascii":
 			funcs.setEncodingType(ENCODING_FORMAT6);
 			funcs.setEncoder(new Format6Encoder());
 			funcs.setDecoder(new Format6Decoder());
-		} else if ("cp1252".equals(charset) || "latin1".equals(charset)) {
+			break;
+		case "cp1252":
+		case "latin1":
 			funcs.setEncodingType(ENCODING_FORMAT9);
 			funcs.setEncoder(new AnyCharsetEncoder("cp1252", new TableTransliterator("latin1")));
 			funcs.setDecoder(new AnyCharsetDecoder("cp1252"));
 			funcs.setCodepage(1252);
-		} else if ("cp65001".equals(charset) || "unicode".equals(charset)) {
+			break;
+		case "cp65001":
+		case "unicode":
 			funcs.setEncodingType(ENCODING_FORMAT10);
 			funcs.setEncoder(new Utf8Encoder());
 			funcs.setDecoder(new Utf8Decoder());
 			funcs.setCodepage(65001);
-		} else if ("simple8".equals(charset)) {
-			funcs.setEncodingType(ENCODING_FORMAT9);
-			funcs.setEncoder(new Simple8Encoder());
-		} else if ("cp932".equals(charset) || "ms932".equals(charset)) {
+			break;
+		case "cp932":
+		case "ms932":
 			funcs.setEncodingType(ENCODING_FORMAT10);
 			funcs.setEncoder(new AnyCharsetEncoder("ms932", new SparseTransliterator("nomacron")));
 			funcs.setDecoder(new AnyCharsetDecoder("ms932"));
 			funcs.setCodepage(932);
-		} else {
+			break;
+		default:
 			funcs.setEncodingType(ENCODING_FORMAT9);
 			funcs.setDecoder(new AnyCharsetDecoder(charset));
 			funcs.setEncoder(new AnyCharsetEncoder(charset, new TableTransliterator("ascii")));
 			funcs.setCodepage(guessCodepage(charset));
+			break;
 		}
 
 		return funcs;
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java b/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
index 0beb5a5..a47ac67 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Steve Ratcliffe
+ * Copyright (C) 2007,2014 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
@@ -16,8 +16,6 @@
  */
 package uk.me.parabola.imgfmt.app.labelenc;
 
-import java.util.Arrays;
-
 /**
  * Holds the bytes and length of an encoded character string used in a label.
  * The length of the byte array may be longer than the part that is actually
@@ -28,12 +26,20 @@ import java.util.Arrays;
  * @author Steve Ratcliffe
  */
 public class EncodedText {
+	private final int hashCode;
 	private final byte[] ctext;
 	private final int length;
+	private final char[] chars;
 
-	public EncodedText(byte[] buf, int len) {
+	public EncodedText(byte[] buf, int len, char[] chars) {
 		this.ctext = buf;
 		this.length = len;
+		this.chars = chars;
+
+		int hc = 0;
+		for (int i = 0; i < length; i++)
+			hc = 31*hc + ctext[i];
+		hashCode = hc;
 	}
 
 	public byte[] getCtext() {
@@ -44,6 +50,10 @@ public class EncodedText {
 		return length;
 	}
 
+	public char[] getChars() {
+		return chars;
+	}
+
 	public boolean equals(Object o) {
 		if (this == o) return true;
 		if (o == null || getClass() != o.getClass()) return false;
@@ -51,14 +61,14 @@ public class EncodedText {
 		EncodedText that = (EncodedText) o;
 
 		if (length != that.length) return false;
-		if (!Arrays.equals(ctext, that.ctext)) return false;
+		for (int i = 0; i < length; i++)
+			if (ctext[i] != that.ctext[i])
+				return false;
 
 		return true;
 	}
 
 	public int hashCode() {
-		int result = Arrays.hashCode(ctext);
-		result = 31 * result + length;
-		return result;
+		return hashCode;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/Format6Encoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/Format6Encoder.java
index 92b575f..b85b095 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/Format6Encoder.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/Format6Encoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Steve Ratcliffe
+ * Copyright (C) 2007,2014 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
@@ -58,7 +58,7 @@ public class Format6Encoder extends BaseEncoder implements CharacterEncoder {
 	 * some escape sequences will be present.
 	 */
 	public EncodedText encodeText(String text) {
-		if (text == null)
+		if (text == null || text.isEmpty())
 			return NO_TEXT;
 
 		String s = transliterator.transliterate(text).toUpperCase(Locale.ENGLISH);
@@ -94,7 +94,8 @@ public class Format6Encoder extends BaseEncoder implements CharacterEncoder {
 
 		int len = ((off - 1) * 6) / 8 + 1;
 
-		return new EncodedText(buf, len);
+		char[] chars = s.toCharArray();
+		return new EncodedText(buf, len, chars);
 	}
 
 	/**
@@ -147,5 +148,4 @@ public class Format6Encoder extends BaseEncoder implements CharacterEncoder {
 
 		return buf;
 	}
-
 }
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/Simple8Encoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/Simple8Encoder.java
deleted file mode 100644
index fa45e1d..0000000
--- a/src/uk/me/parabola/imgfmt/app/labelenc/Simple8Encoder.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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: 14-Jan-2007
- */
-package uk.me.parabola.imgfmt.app.labelenc;
-
-/**
- * An encoder that just takes the lower 8 bits of the char and uses that
- * without any character set conversion.  Useful for testing mainly (only?).
- *
- * @author Steve Ratcliffe
- */
-public class Simple8Encoder extends BaseEncoder implements CharacterEncoder {
-
-	public EncodedText encodeText(String text) {
-		return simpleEncode(text);
-	}
-}
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/Utf8Encoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/Utf8Encoder.java
index 53e9aaf..0ce0592 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/Utf8Encoder.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/Utf8Encoder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Steve Ratcliffe
+ * Copyright (C) 2007,2014 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
@@ -20,15 +20,14 @@ import java.io.UnsupportedEncodingException;
 import java.util.Locale;
 
 /**
- * Encoder for labels in utf-8, note that I am not actually sure that this
- * is in fact used anywhere.
+ * Encoder for labels in utf-8.
  * 
  * @author Steve Ratcliffe
  */
 public class Utf8Encoder extends BaseEncoder implements CharacterEncoder {
 	
 	public EncodedText encodeText(String text) {
-		if (text == null)
+		if (text == null || text.isEmpty())
 			return NO_TEXT;
 
 		String uctext;
@@ -43,11 +42,11 @@ public class Utf8Encoder extends BaseEncoder implements CharacterEncoder {
 			byte[] res = new byte[buf.length + 1];
 			System.arraycopy(buf, 0, res, 0, buf.length);
 			res[buf.length] = 0;
-			et = new EncodedText(res, res.length);
+			et = new EncodedText(res, res.length, uctext.toCharArray());
 		} catch (UnsupportedEncodingException e) {
 			// As utf-8 must be supported, this can't happen
 			byte[] buf = uctext.getBytes();
-			et = new EncodedText(buf, buf.length);
+			et = new EncodedText(buf, buf.length, uctext.toCharArray());
 		}
 		return et;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/City.java b/src/uk/me/parabola/imgfmt/app/lbl/City.java
index c48babd..7b9ddd4 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/City.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/City.java
@@ -161,4 +161,8 @@ public class City {
 	public int getCountryNumber() {
 		return country != null ? country.getIndex() : 0;
 	}
+
+	public Label getLabel() {
+		return label;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
index d486b63..22c415a 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
@@ -76,6 +76,7 @@ public class LBLFile extends ImgFile {
 
 		places.init(this, lblHeader.getPlaceHeader());
 		places.setSort(sort);
+		labelCache.put(BaseEncoder.NO_TEXT, Label.NULL_OUT_LABEL);
 	}
 
 	public void write() {
@@ -129,9 +130,10 @@ public class LBLFile extends ImgFile {
 	 */
 	public Label newLabel(String text) {
 		EncodedText encodedText = textEncoder.encodeText(text);
+
 		Label l = labelCache.get(encodedText);
 		if (l == null) {
-			l = new Label(text);
+			l = new Label(encodedText.getChars());
 			labelCache.put(encodedText, l);
 
 			l.setOffset(getNextLabelOffset());
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java b/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
index ba2d9e7..0523291 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
@@ -28,6 +28,8 @@ import uk.me.parabola.imgfmt.app.labelenc.DecodedText;
 import uk.me.parabola.imgfmt.app.trergn.Subdivision;
 import uk.me.parabola.imgfmt.fs.ImgChannel;
 
+import static uk.me.parabola.imgfmt.app.Label.NULL_LABEL;
+
 /**
  * The file that holds all the labels for the map.
  *
@@ -41,18 +43,17 @@ import uk.me.parabola.imgfmt.fs.ImgChannel;
  * @author Steve Ratcliffe
  */
 public class LBLFileReader extends ImgFile {
-	private static final Label NULL_LABEL = new Label("");
 
 	private CharacterDecoder textDecoder = CodeFunctions.getDefaultDecoder();
 
 	private final LBLHeader header = new LBLHeader();
 
-	private final Map<Integer, Label> labels = new HashMap<Integer, Label>();
-	private final Map<Integer, POIRecord> pois = new HashMap<Integer, POIRecord>();
-	private final List<Country> countries = new ArrayList<Country>();
-	private final List<Region> regions = new ArrayList<Region>();
-	private final Map<Integer, Zip> zips = new HashMap<Integer, Zip>();
-	private final List<City> cities = new ArrayList<City>();
+	private final Map<Integer, Label> labels = new HashMap<>();
+	private final Map<Integer, POIRecord> pois = new HashMap<>();
+	private final List<Country> countries = new ArrayList<>();
+	private final List<Region> regions = new ArrayList<>();
+	private final Map<Integer, Zip> zips = new HashMap<>();
+	private final List<City> cities = new ArrayList<>();
 
 	public LBLFileReader(ImgChannel chan) {
 		setHeader(header);
@@ -107,7 +108,7 @@ public class LBLFileReader extends ImgFile {
 	}
 	
 	public List<Zip> getZips() {
-		return new ArrayList<Zip>(zips.values());
+		return new ArrayList<>(zips.values());
 	}
 
 	/**
@@ -521,7 +522,7 @@ public class LBLFileReader extends ImgFile {
 	}
 
 	public Map<Integer, String> getLabels() {
-		Map<Integer, String> m = new HashMap<Integer, String>();
+		Map<Integer, String> m = new HashMap<>();
 		for (Map.Entry<Integer, Label> ent : labels.entrySet()) {
 			m.put(ent.getKey(), ent.getValue().getText());
 		}
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java b/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
index 245382a..fab831f 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
@@ -35,23 +35,23 @@ import uk.me.parabola.imgfmt.app.trergn.Subdivision;
  * This is really part of the LBLFile.  We split out all the parts of the file
  * that are to do with location to here.
  */
- at SuppressWarnings({"RawUseOfParameterizedType"})
+ at SuppressWarnings({"RawUseOfParameterizedType", "unchecked", "rawtypes"})
 public class PlacesFile {
-	private final Map<String, Country> countries = new LinkedHashMap<String, Country>();
-	private final List<Country> countryList = new ArrayList<Country>();
+	private final Map<String, Country> countries = new LinkedHashMap<>();
+	private final List<Country> countryList = new ArrayList<>();
 
-	private final Map<String, Region> regions = new LinkedHashMap<String, Region>();
-	private final List<Region> regionList = new ArrayList<Region>();
+	private final Map<String, Region> regions = new LinkedHashMap<>();
+	private final List<Region> regionList = new ArrayList<>();
 
-	private final Map<String, City> cities = new LinkedHashMap<String, City>();
-	private final List<City> cityList = new ArrayList<City>();
+	private final Map<String, City> cities = new LinkedHashMap<>();
+	private final List<City> cityList = new ArrayList<>();
 
-	private final Map<String, Zip> postalCodes = new LinkedHashMap<String, Zip>();
-	private final List<Zip> zipList = new ArrayList<Zip>();
+	private final Map<String, Zip> postalCodes = new LinkedHashMap<>();
+	private final List<Zip> zipList = new ArrayList<>();
 
-	private final List<Highway> highways = new ArrayList<Highway>();
-	private final List<ExitFacility> exitFacilities = new ArrayList<ExitFacility>();
-	private final List<POIRecord> pois = new ArrayList<POIRecord>();
+	private final List<Highway> highways = new ArrayList<>();
+	private final List<ExitFacility> exitFacilities = new ArrayList<>();
+	private final List<POIRecord> pois = new ArrayList<>();
 	private final List[] poiIndex = new ArrayList[256];
 
 	private LBLFile lblFile;
@@ -91,7 +91,7 @@ public class PlacesFile {
 		for (List<POIIndex> pil : poiIndex) {
 			if(pil != null) {
 				// sort entries by POI name
-				List<SortKey<POIIndex>> sorted = new ArrayList<SortKey<POIIndex>>();
+				List<SortKey<POIIndex>> sorted = new ArrayList<>();
 				for (POIIndex index : pil) {
 					SortKey<POIIndex> sortKey = sort.createSortKey(index, index.getName());
 					sorted.add(sortKey);
@@ -178,7 +178,7 @@ public class PlacesFile {
 	}
 
 	City createCity(Country country, String name, boolean unique) {
-		
+
 		String uniqueCityName = name.toUpperCase() + "_C" + country.getLabel().getOffset();
 		
 		// if unique is true, make sure that the name really is unique
@@ -333,9 +333,9 @@ public class PlacesFile {
 	 * But why not?
 	 */
 	private void sortCountries() {
-		List<SortKey<Country>> keys = new ArrayList<SortKey<Country>>();
+		List<SortKey<Country>> keys = new ArrayList<>();
 		for (Country c : countries.values()) {
-			SortKey<Country> key = sort.createSortKey(c, c.getLabel().getText());
+			SortKey<Country> key = sort.createSortKey(c, c.getLabel());
 			keys.add(key);
 		}
 		Collections.sort(keys);
@@ -353,9 +353,9 @@ public class PlacesFile {
 	 * Sort the regions by the defined sort.
 	 */
 	private void sortRegions() {
-		List<SortKey<Region>> keys = new ArrayList<SortKey<Region>>();
+		List<SortKey<Region>> keys = new ArrayList<>();
 		for (Region r : regionList) {
-			SortKey<Region> key = sort.createSortKey(r, r.getLabel().getText(), r.getCountry().getIndex());
+			SortKey<Region> key = sort.createSortKey(r, r.getLabel(), r.getCountry().getIndex());
 			keys.add(key);
 		}
 		Collections.sort(keys);
@@ -373,10 +373,10 @@ public class PlacesFile {
 	 * Sort the cities by the defined sort.
 	 */
 	private void sortCities() {
-		List<SortKey<City>> keys = new ArrayList<SortKey<City>>();
+		List<SortKey<City>> keys = new ArrayList<>();
 		for (City c : cityList) {
-			SortKey<City> sortKey = sort.createSortKey(c, c.getName());
-			sortKey = new CombinedSortKey<City>(sortKey, c.getRegionNumber(), c.getCountryNumber());
+			SortKey<City> sortKey = sort.createSortKey(c, c.getLabel());
+			sortKey = new CombinedSortKey<>(sortKey, c.getRegionNumber(), c.getCountryNumber());
 			keys.add(sortKey);
 		}
 		Collections.sort(keys);
@@ -391,9 +391,9 @@ public class PlacesFile {
 	}
 
 	private void sortZips() {
-		List<SortKey<Zip>> keys = new ArrayList<SortKey<Zip>>();
+		List<SortKey<Zip>> keys = new ArrayList<>();
 		for (Zip c : postalCodes.values()) {
-			SortKey<Zip> sortKey = sort.createSortKey(c, c.getLabel().getText());
+			SortKey<Zip> sortKey = sort.createSortKey(c, c.getLabel());
 			keys.add(sortKey);
 		}
 		Collections.sort(keys);
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java b/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
index 4dbd295..3640b05 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
@@ -139,7 +139,7 @@ public class MDRFile extends ImgFile {
 	 */
 	public void addMap(int mapName, int codePage) {
 		currentMap++;
-		mdr1.addMap(mapName);
+		mdr1.addMap(mapName, currentMap);
 		Sort sort = mdrHeader.getSort();
 
 		if (sort.getCodepage() != codePage)
@@ -151,7 +151,7 @@ public class MDRFile extends ImgFile {
 
 		String name = country.getLabel().getText();
 		record.setMapIndex(currentMap);
-		record.setCountryIndex((int) country.getIndex());
+		record.setCountryIndex(country.getIndex());
 		record.setLblOffset(country.getLabel().getOffset());
 		record.setName(name);
 		record.setStrOff(createString(name));
@@ -243,11 +243,6 @@ public class MDRFile extends ImgFile {
 	public void write() {
 		mdr15.release();
 		
-		for (MdrSection s : sections) {
-			if (s != null)
-				s.finish();
-		}
-
 		ImgFileWriter writer = getWriter();
 		writeSections(writer);
 
@@ -271,6 +266,8 @@ public class MDRFile extends ImgFile {
 	private void writeSections(ImgFileWriter writer) {
 		sizes = new MdrMapSection.PointerSizes(sections);
 
+		mdr1.finish();
+
 		// Deal with the dependencies between the sections. The order of the following
 		// statements is sometimes important.
 		mdr28.buildFromRegions(mdr13.getRegions());
@@ -373,7 +370,7 @@ public class MDRFile extends ImgFile {
 	private void writeSection(ImgFileWriter writer, int sectionNumber, MdrSection section) {
 
 		// Some sections are just not written in the device config
-		if (forDevice && Arrays.asList(12, 13, 14, 15, 21, 23, 26, 27, 28).contains(sectionNumber))
+		if (forDevice && Arrays.asList(13, 14, 15, 21, 23, 26, 27, 28).contains(sectionNumber))
 			return;
 
 		section.setSizes(sizes);
@@ -386,6 +383,7 @@ public class MDRFile extends ImgFile {
 			MdrMapSection mapSection = (MdrMapSection) section;
 			mapSection.setMapIndex(mdr1);
 			mapSection.initIndex(sectionNumber);
+			mapSection.relabelMaps(mdr1);
 		}
 
 		if (section instanceof HasHeaderFlags)
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr1.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr1.java
index 9ab0c9a..e2090b4 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr1.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr1.java
@@ -45,7 +45,8 @@ import uk.me.parabola.imgfmt.app.ImgFileWriter;
  * @author Steve Ratcliffe
  */
 public class Mdr1 extends MdrSection implements HasHeaderFlags {
-	private final List<Mdr1Record> maps = new ArrayList<Mdr1Record>();
+	private final List<Mdr1Record> maps = new ArrayList<>();
+	private int[] mapping;
 
 	public Mdr1(MdrConfig config) {
 		setConfig(config);
@@ -55,9 +56,12 @@ public class Mdr1 extends MdrSection implements HasHeaderFlags {
 	 * Add a map.  Create an MDR1 record for it and also allocate its reverse
 	 * index if this is not for a device.
 	 * @param mapNumber The map index number.
+	 * @param index The map index.
 	 */
-	public void addMap(int mapNumber) {
+	public void addMap(int mapNumber, int index) {
+		assert index > 0;
 		Mdr1Record rec = new Mdr1Record(mapNumber, getConfig());
+		rec.setMapIndex(index);
 		maps.add(rec);
 
 		if (!isForDevice()) {
@@ -80,6 +84,14 @@ public class Mdr1 extends MdrSection implements HasHeaderFlags {
 					return 1;
 			}
 		});
+
+		// Used to renumber all the existing (pre-sorted) map index numbers.
+		mapping = new int[maps.size()];
+		int count = 1;
+		for (Mdr1Record r : maps) {
+			mapping[r.getMapIndex()-1] = count;
+			count++;
+		}
 	}
 
 	public void writeSubSections(ImgFileWriter writer) {
@@ -154,4 +166,8 @@ public class Mdr1 extends MdrSection implements HasHeaderFlags {
 			magic |= 1;
 		return magic;
 	}
+
+	public int sortedMapIndex(int n) {
+		return mapping[n-1];
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
index 4f3fec3..2dbf684 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
@@ -43,7 +43,7 @@ public class Mdr10 extends MdrMapSection {
 		setConfig(config);
 
 		for (int i = 1; i <= MAX_GROUP_NUMBER; i++) {
-			poiTypes[i] = new ArrayList<Mdr10Record>();
+			poiTypes[i] = new ArrayList<>();
 		}
 	}
 
@@ -98,7 +98,7 @@ public class Mdr10 extends MdrMapSection {
 	 * number of entries in that group.
 	 */
 	public Map<Integer, Integer> getGroupSizes() {
-		Map<Integer, Integer> m = new LinkedHashMap<Integer, Integer>();
+		Map<Integer, Integer> m = new LinkedHashMap<>();
 
 		for (int i = 1; i < MAX_GROUP_NUMBER; i++) {
 			List<Mdr10Record> poiGroup = poiTypes[i];
@@ -132,4 +132,13 @@ public class Mdr10 extends MdrMapSection {
 		// Nothing to do here
 		return 0;
 	}
+
+	/**
+	 * Nothing to do for this section.
+	 *
+	 * Although this section has a subsection by map index in mdr1, its record does not contain the
+	 * map index and so nothing needs to be re-written here.  The map index is contained in its mdr11ref.
+	 */
+	public void relabelMaps(Mdr1 maps) {
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10Record.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10Record.java
index 0303bb7..bdea735 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10Record.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10Record.java
@@ -24,7 +24,7 @@ package uk.me.parabola.imgfmt.app.mdr;
  *
  * @author Steve Ratcliffe
  */
-public class Mdr10Record extends RecordBase implements Comparable<Mdr10Record> {
+public class Mdr10Record implements Comparable<Mdr10Record> {
 	private int subtype;
 	private Mdr11Record mdr11ref;
 
@@ -52,4 +52,4 @@ public class Mdr10Record extends RecordBase implements Comparable<Mdr10Record> {
 	public void setSubtype(int subtype) {
 		this.subtype = subtype;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
index 7ccb7d7..ab505a9 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
@@ -27,7 +27,7 @@ import uk.me.parabola.imgfmt.app.trergn.Point;
  * @author Steve Ratcliffe
  */
 public class Mdr11 extends MdrMapSection {
-	private List<Mdr11Record> pois = new ArrayList<Mdr11Record>();
+	private List<Mdr11Record> pois = new ArrayList<>();
 	private Mdr10 mdr10;
 
 	public Mdr11(MdrConfig config) {
@@ -119,7 +119,7 @@ public class Mdr11 extends MdrMapSection {
 	}
 
 	public List<Mdr8Record> getIndex() {
-		List<Mdr8Record> list = new ArrayList<Mdr8Record>();
+		List<Mdr8Record> list = new ArrayList<>();
 		for (int number = 1; number <= pois.size(); number += 10240) {
 			String prefix = getPrefixForRecord(number);
 
@@ -171,6 +171,10 @@ public class Mdr11 extends MdrMapSection {
 	}
 
 	public List<Mdr11Record> getPois() {
-		return new ArrayList<Mdr11Record>(pois);
+		return new ArrayList<>(pois);
+	}
+
+	public void relabelMaps(Mdr1 maps) {
+		relabel(maps, pois);
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
index 076bcf5..3791d31 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
@@ -13,6 +13,7 @@
 
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -57,9 +58,9 @@ public class Mdr20 extends Mdr2x {
 		// Use a key cache because there are a large number of street names but a much smaller number
 		// of city, region and country names. Therefore we can reuse the memory needed for the keys
 		// most of the time, particularly for the country and region names.
-		Map<String, byte[]> cache = new HashMap<String, byte[]>();
+		Map<String, byte[]> cache = new HashMap<>();
 
-		List<SortKey<Mdr7Record>> keys = new ArrayList<SortKey<Mdr7Record>>();
+		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
 		for (Mdr7Record s : inStreets) {
 			Mdr5Record city = s.getCity();
 			if (city == null)
@@ -78,29 +79,32 @@ public class Mdr20 extends Mdr2x {
 					cache);
 
 			// Combine all together so we can sort on it.
-			SortKey<Mdr7Record> key = new MultiSortKey<Mdr7Record>(cityKey, regionKey, countryStreetKey);
+			SortKey<Mdr7Record> key = new MultiSortKey<>(cityKey, regionKey, countryStreetKey);
 
 			keys.add(key);
 		}
 		Collections.sort(keys);
 
+		Collator collator = getConfig().getSort().getCollator();
+
 		String lastName = null;
 		Mdr5Record lastCity = null;
 		int record = 0;
 		int cityRecord = 0;
 		int lastMapNumber = 0;
+
 		for (SortKey<Mdr7Record> key : keys) {
 			Mdr7Record street = key.getObject();
 
 			String name = street.getName();
 			Mdr5Record city = street.getCity();
 
-			boolean citySameByName = city.isSameByName(lastCity);
+			boolean citySameByName = city.isSameByName(collator, lastCity);
 
 			int mapNumber = city.getMapIndex();
 
 			// Only save a single copy of each street name.
-			if (!name.equals(lastName) || !citySameByName || mapNumber != lastMapNumber) {
+			if (!citySameByName || mapNumber != lastMapNumber || lastName == null || collator.compare(name, lastName) != 0) {
 				record++;
 
 				streets.add(street);
@@ -121,6 +125,15 @@ public class Mdr20 extends Mdr2x {
 	}
 
 	/**
+	 * Two streets are in the same group if they have the same mdr20 id.
+	 */
+	protected boolean sameGroup(Mdr7Record street1, Mdr7Record street2) {
+		if (street2 != null && street1.getCity().getMdr20() == street2.getCity().getMdr20())
+			return true;
+		return false;
+	}
+
+	/**
 	 * Unknown.
 	 */
 	public int getExtraValue() {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
index 8c86983..14dfd60 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
@@ -42,8 +42,8 @@ public class Mdr21 extends Mdr2x {
 	public void buildFromStreets(List<Mdr7Record> inStreets) {
 		Sort sort = getConfig().getSort();
 
-		List<SortKey<Mdr7Record>> keys = new ArrayList<SortKey<Mdr7Record>>();
-		Map<String, byte[]> cache = new HashMap<String, byte[]>();
+		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
+		Map<String, byte[]> cache = new HashMap<>();
 
 		for (Mdr7Record s : inStreets) {
 			Mdr5Record city = s.getCity();
@@ -85,6 +85,10 @@ public class Mdr21 extends Mdr2x {
 		}
 	}
 
+	protected boolean sameGroup(Mdr7Record street1, Mdr7Record street2) {
+		return true;
+	}
+
 	/**
 	 * Not known what these flags signify.
 	 */
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
index f218f3b..379d3b4 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
@@ -48,8 +48,8 @@ public class Mdr22 extends Mdr2x {
 	public void buildFromStreets(List<Mdr7Record> inStreets) {
 		Sort sort = getConfig().getSort();
 
-		List<SortKey<Mdr7Record>> keys = new ArrayList<SortKey<Mdr7Record>>();
-		Map<String, byte[]> cache = new HashMap<String, byte[]>();
+		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
+		Map<String, byte[]> cache = new HashMap<>();
 		for (Mdr7Record s : inStreets) {
 			Mdr5Record city = s.getCity();
 			if (city == null) continue;
@@ -91,6 +91,10 @@ public class Mdr22 extends Mdr2x {
 		}
 	}
 
+	protected boolean sameGroup(Mdr7Record street1, Mdr7Record street2) {
+		return true;
+	}
+
 	public List<Mdr7Record> getStreets() {
 		return Collections.unmodifiableList(streets);
 	}
@@ -100,7 +104,7 @@ public class Mdr22 extends Mdr2x {
 	 */
 	public int getExtraValue() {
 		if (isForDevice())
-			return 0xc000a;
+			return 0x600a;
 		else
 			return 0x11000;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
index 7a506db..a318329 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
@@ -26,7 +26,7 @@ import uk.me.parabola.imgfmt.app.srt.SortKey;
  * @author Steve Ratcliffe
  */
 public class Mdr29 extends MdrSection implements HasHeaderFlags {
-	private final List<Mdr29Record> index = new ArrayList<Mdr29Record>();
+	private final List<Mdr29Record> index = new ArrayList<>();
 	private int max17;
 
 	public Mdr29(MdrConfig config) {
@@ -80,7 +80,7 @@ public class Mdr29 extends MdrSection implements HasHeaderFlags {
 		PointerSizes sizes = getSizes();
 		int size24 = sizes.getSize(24);
 		int size22 = sizes.getSize(22);
-		int size25 = sizes.getSize(25);
+		int size25 = sizes.getSize(5);  // NB appears to be size of 5 (cities), not 25 (cities with country).
 		int size26 = has26? sizes.getSize(26): 0;
 		int size17 = numberToPointerSize(max17);
 		for (Mdr29Record record : index) {
@@ -107,7 +107,7 @@ public class Mdr29 extends MdrSection implements HasHeaderFlags {
 		PointerSizes sizes = getSizes();
 		int size = sizes.getSize(24)
 				+ sizes.getSize(22)
-				+ sizes.getSize(25)
+				+ sizes.getSize(5)  // NB: not 25
 				;
 		if (isForDevice()) {
 			size += numberToPointerSize(max17);
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
index 965b260..bade6d8 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
@@ -25,7 +25,7 @@ import uk.me.parabola.imgfmt.app.Label;
  * @author Steve Ratcliffe
  */
 public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
-	protected List<Mdr7Record> streets = new ArrayList<Mdr7Record>();
+	protected List<Mdr7Record> streets = new ArrayList<>();
 
 	/**
 	 * Write out the contents of this section.
@@ -34,6 +34,7 @@ public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
 	 */
 	public void writeSectData(ImgFileWriter writer) {
 		String lastName = null;
+		Mdr7Record prev = null;
 
 		int size = getSizes().getStreetSizeFlagged();
 
@@ -47,11 +48,10 @@ public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
 			String name = Label.stripGarminCodes(street.getName());
 			
 			int flag = 1;
-			if (name.equals(lastName)) {
+			if (name.equals(lastName) && sameGroup(street, prev))
 				flag = 0;
-			} else {
-				lastName = name;
-			}
+			lastName = name;
+			prev = street;
 
 			if (hasLabel) {
 				putMapIndex(writer, street.getMapIndex());
@@ -98,4 +98,19 @@ public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
 	protected void releaseMemory() {
 		streets = null;
 	}
+
+	/**
+	 * These sections are divided into groups based on city, region or country. This routine is
+	 * implemented to return true if the two streets are in the same group.
+	 *
+	 * It is not clear if this is needed for region or country.
+	 * @param street1 The first street.
+	 * @param street2 The street to compare against.
+	 * @return True if the streets are in the same group (city, region etc).
+	 */
+	protected abstract boolean sameGroup(Mdr7Record street1, Mdr7Record street2);
+
+	public void relabelMaps(Mdr1 maps) {
+		// Nothing to do, since all streets are re-labeled in their own section.
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
index 7503359..2186d96 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
@@ -12,6 +12,7 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -30,8 +31,8 @@ import uk.me.parabola.imgfmt.app.srt.SortKey;
  * @author Steve Ratcliffe
  */
 public class Mdr5 extends MdrMapSection {
-	private List<Mdr5Record> allCities = new ArrayList<Mdr5Record>();
-	private List<Mdr5Record> cities = new ArrayList<Mdr5Record>();
+	private List<Mdr5Record> allCities = new ArrayList<>();
+	private List<Mdr5Record> cities = new ArrayList<>();
 	private int maxCityIndex;
 	private int localCitySize;
 
@@ -52,7 +53,7 @@ public class Mdr5 extends MdrMapSection {
 	public void preWriteImpl() {
 		localCitySize = numberToPointerSize(maxCityIndex + 1);
 
-		List<SortKey<Mdr5Record>> sortKeys = new ArrayList<SortKey<Mdr5Record>>(allCities.size());
+		List<SortKey<Mdr5Record>> sortKeys = new ArrayList<>(allCities.size());
 		Sort sort = getConfig().getSort();
 		for (Mdr5Record m : allCities) {
 			if (m.getName() == null)
@@ -62,11 +63,13 @@ public class Mdr5 extends MdrMapSection {
 			SortKey<Mdr5Record> sortKey = sort.createSortKey(m, m.getName());
 			SortKey<Mdr5Record> regionKey = sort.createSortKey(null, m.getRegionName());
 			SortKey<Mdr5Record> countryKey = sort.createSortKey(null, m.getCountryName(), m.getMapIndex());
-			sortKey = new MultiSortKey<Mdr5Record>(sortKey, regionKey, countryKey);
+			sortKey = new MultiSortKey<>(sortKey, regionKey, countryKey);
 			sortKeys.add(sortKey);
 		}
 		Collections.sort(sortKeys);
 
+		Collator collator = getConfig().getSort().getCollator();
+
 		int count = 0;
 		Mdr5Record lastCity = null;
 
@@ -79,11 +82,11 @@ public class Mdr5 extends MdrMapSection {
 			Mdr5Record c = key.getObject();
 			c.setMdr20set(mdr20s);
 
-			if (!c.isSameByName(lastCity))
+			if (!c.isSameByName(collator, lastCity))
 				mdr20count++;
 			c.setMdr20Index(mdr20count);
 
-			if (c.isSameByMapAndName(lastCity)) {
+			if (c.isSameByMapAndName(collator, lastCity)) {
 				c.setGlobalCityIndex(count);
 			} else {
 				count++;
@@ -100,6 +103,7 @@ public class Mdr5 extends MdrMapSection {
 		Mdr5Record lastCity = null;
 		boolean hasString = hasFlag(0x8);
 		boolean hasRegion = hasFlag(0x4);
+		Collator collator = getConfig().getSort().getCollator();
 		for (Mdr5Record city : cities) {
 			int gci = city.getGlobalCityIndex();
 			addIndexPointer(city.getMapIndex(), gci);
@@ -111,7 +115,7 @@ public class Mdr5 extends MdrMapSection {
 			int region = city.getRegionIndex();
 
 			// Set the no-repeat flag if the name/region is different
-			if (!city.isSameByName(lastCity)) {
+			if (!city.isSameByName(collator, lastCity)) {
 				flag = 0x800000;
 				lastCity = city;
 			}
@@ -198,4 +202,8 @@ public class Mdr5 extends MdrMapSection {
 	public List<Mdr5Record> getSortedCities() {
 		return Collections.unmodifiableList(cities);
 	}
+
+	public void relabelMaps(Mdr1 maps) {
+		relabel(maps, cities);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
index 702cc49..aee7909 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
+
 /**
  * Holds information about a city that will make its way into mdr 5.
  * This class is used in several places as the information has to be gathered
@@ -135,25 +137,27 @@ public class Mdr5Record extends RecordBase implements NamedRecord {
 	 * Is this the same city, by the rules segregating the cities in mdr5 and 20.
 	 * @return True if in the same tile and has the same name for city/region/country.
 	 */
-	public boolean isSameByMapAndName(Mdr5Record other) {
+	public boolean isSameByMapAndName(Collator collator, Mdr5Record other) {
 		if (other == null)
 			return false;
 
-		return getMapIndex() == other.getMapIndex() && isSameByName(other);
+		return getMapIndex() == other.getMapIndex() && isSameByName(collator, other);
 	}
 
 	/**
 	 * Same city by the name of the city/region/country combination.
+	 *
+	 * @param collator Used to sort names.
 	 * @param other The other city to compare with.
 	 * @return True if is the same city, maybe in a different tile.
 	 */
-	public boolean isSameByName(Mdr5Record other) {
+	public boolean isSameByName(Collator collator, Mdr5Record other) {
 		if (other == null)
 			return false;
 
-		return getName().equals(other.getName())
-				&& getRegionName().equals(other.getRegionName())
-				&& getCountryName().equals(other.getCountryName());
+		return collator.compare(getName(), other.getName()) == 0
+				&& collator.compare(getRegionName(), other.getRegionName()) == 0
+				&& collator.compare(getCountryName(), other.getCountryName()) == 0;
 	}
 
 	public String toString() {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr6.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr6.java
index 5005432..d9398ec 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr6.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr6.java
@@ -29,7 +29,7 @@ import uk.me.parabola.imgfmt.app.srt.SortKey;
  */
 public class Mdr6 extends MdrMapSection {
 
-	private final List<Mdr6Record> zips = new ArrayList<Mdr6Record>();
+	private final List<Mdr6Record> zips = new ArrayList<>();
 
 
 	public Mdr6(MdrConfig config) {
@@ -88,4 +88,8 @@ public class Mdr6 extends MdrMapSection {
 	public int getExtraValue() {
 		return  ((getSizes().getZipSize()-1)&0x03) | (isForDevice() ? 0 : 0x04);
 	}
+
+	public void relabelMaps(Mdr1 maps) {
+		relabel(maps, zips);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
index b59936e..6e43a30 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
@@ -27,8 +27,8 @@ import uk.me.parabola.imgfmt.app.srt.SortKey;
  * @author Steve Ratcliffe
  */
 public class Mdr7 extends MdrMapSection {
-	private List<Mdr7Record> allStreets = new ArrayList<Mdr7Record>();
-	private List<Mdr7Record> streets = new ArrayList<Mdr7Record>();
+	private List<Mdr7Record> allStreets = new ArrayList<>();
+	private List<Mdr7Record> streets = new ArrayList<>();
 
 	public Mdr7(MdrConfig config) {
 		setConfig(config);
@@ -134,7 +134,7 @@ public class Mdr7 extends MdrMapSection {
 	 * @return List of index records.
 	 */
 	public List<Mdr8Record> getIndex() {
-		List<Mdr8Record> list = new ArrayList<Mdr8Record>();
+		List<Mdr8Record> list = new ArrayList<>();
 		for (int number = 1; number <= streets.size(); number += 10240) {
 			String prefix = getPrefixForRecord(number);
 
@@ -183,4 +183,8 @@ public class Mdr7 extends MdrMapSection {
 	public List<Mdr7Record> getSortedStreets() {
 		return Collections.unmodifiableList(streets);
 	}
+
+	public void relabelMaps(Mdr1 maps) {
+		relabel(maps, allStreets);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MdrMapSection.java b/src/uk/me/parabola/imgfmt/app/mdr/MdrMapSection.java
index 14ab889..cc5a330 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MdrMapSection.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MdrMapSection.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.util.List;
+
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
 
 /**
@@ -69,4 +71,25 @@ public abstract class MdrMapSection extends MdrSection implements HasHeaderFlags
 	protected boolean hasFlag(int val) {
 		return (getExtraValue() & val) != 0;
 	}
+
+	public abstract void relabelMaps(Mdr1 maps);
+
+	/**
+	 * Relabel every map-index in the given set of records.
+	 *
+	 * The maps must be in sorted order, but because of the incremental way that we build the
+	 * index, this isn't known until the end.  So we get rewrite the mapIndex from the
+	 * initial to the final ordering.
+	 *
+	 * @param maps The final ordering of the maps.
+	 * @param records The set of records to be relabeled.
+	 * @param <T> The type of the record. Must be a subclass of RecordBase.
+	 */
+	protected <T extends RecordBase> void relabel(Mdr1 maps, List<T> records) {
+		for (T r : records) {
+			int n = r.getMapIndex();
+			int newIndex = maps.sortedMapIndex(n);
+			r.setMapIndex(newIndex);
+		}
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MdrSection.java b/src/uk/me/parabola/imgfmt/app/mdr/MdrSection.java
index 1d0b652..a851293 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MdrSection.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MdrSection.java
@@ -117,15 +117,6 @@ public abstract class MdrSection extends ConfigBase {
 	}
 
 	/**
-	 * This is called after all the sections are read in but before any section is written.
-	 *
-	 * This is now pretty much redundant and could be replaced with direct calls for sections
-	 * that need it.
-	 */
-	public void finish() {
-	}
-
-	/**
 	 * Called before the section is written and before the actual size of the section
 	 * is required.
 	 *
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java b/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
index 7cf0218..d88c310 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
@@ -18,7 +18,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
-import uk.me.parabola.imgfmt.app.Label;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 
 /**
@@ -33,7 +32,7 @@ public class PrefixIndex extends MdrSection {
 	private int maxIndex;
 
 	// We use mdr8record for all similar indexes.
-	private final List<Mdr8Record> index = new ArrayList<Mdr8Record>();
+	private final List<Mdr8Record> index = new ArrayList<>();
 
 	/**
 	 * Sets the config and the prefix length for this index.
@@ -129,14 +128,23 @@ public class PrefixIndex extends MdrSection {
 	 * prefix of name and padded with nulls if necessary to make up the length.
 	 */
 	private String getPrefix(String in) {
-		String name = Label.stripGarminCodes(in);
-		if (prefixLength > name.length()) {
-			StringBuilder sb = new StringBuilder(name);
-			while (sb.length() < prefixLength)
-				sb.append('\0');
-			return sb.toString();
+		StringBuilder sb = new StringBuilder();
+		char[] chars = in.toCharArray();
+		int ci = 0;
+		for (int i = 0; i < prefixLength; i++) {
+			char c = 0;
+			while (ci < chars.length) {
+				// TODO: simplify when initial spaces are removed
+				c = chars[ci++];
+				if (ci == 1 && c== 0x20)
+					continue;
+				if (c >= 0x20)
+					break;
+			}
+			sb.append(c);
 		}
-		return name.substring(0, prefixLength);
+
+		return sb.toString();
 	}
 
 	public int getPrefixLength() {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/RecordBase.java b/src/uk/me/parabola/imgfmt/app/mdr/RecordBase.java
index df3a23a..c345042 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/RecordBase.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/RecordBase.java
@@ -26,6 +26,7 @@ public abstract class RecordBase {
 	}
 
 	public void setMapIndex(int mapIndex) {
+		assert mapIndex > 0;
 		this.mapIndex = (short) mapIndex;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java b/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java
new file mode 100644
index 0000000..7d3f729
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.net;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
+
+/**
+ * mkgmap internal representation of (vehicle) access.
+ * @author GerdP
+ *
+ */
+public final class AccessTagsAndBits {
+	// constants for vehicle class
+	public static final byte FOOT 	   = 0x01;
+	public static final byte BIKE      = 0x02;
+	public static final byte CAR       = 0x04;
+	public static final byte DELIVERY  = 0x08;
+
+	public static final byte TRUCK     = 0x10;
+	public static final byte BUS       = 0x20;
+	public static final byte TAXI      = 0x40;
+	public static final byte EMERGENCY = (byte) 0x80;
+
+	// other routing attributes
+	public static final byte R_THROUGHROUTE	= 0x001; // note: 1 means throughroute is allowed
+	public static final byte R_CARPOOL      = 0x002; 	
+	public static final byte R_ONEWAY       = 0x004;
+	public static final byte R_TOLL		    = 0x008;
+	public static final byte R_UNPAVED      = 0x010;
+	public static final byte R_FERRY        = 0x020;
+	public static final byte R_ROUNDABOUT   = 0x040;
+
+	public final static Map<String, Byte> ACCESS_TAGS = new LinkedHashMap<String, Byte>(){{
+		put("mkgmap:foot", FOOT);
+		put("mkgmap:bicycle", BIKE);
+		put("mkgmap:car", CAR);
+		put("mkgmap:delivery", DELIVERY);
+		put("mkgmap:truck", TRUCK);
+		put("mkgmap:bus", BUS);
+		put("mkgmap:taxi", TAXI);
+		put("mkgmap:emergency", EMERGENCY);
+	}};
+
+	public final static Map<Short, Byte> ACCESS_TAGS_COMPILED = new LinkedHashMap<Short, Byte>(){{
+		for (Map.Entry<String, Byte> entry : ACCESS_TAGS.entrySet())
+			put(TagDict.getInstance().xlate(entry.getKey()),entry.getValue());
+	}};
+
+	public final static Map<String, Byte> ROUTE_TAGS = new LinkedHashMap<String, Byte>(){{
+		put("mkgmap:throughroute", R_THROUGHROUTE);
+		put("mkgmap:carpool", R_CARPOOL); 
+		put("oneway", R_ONEWAY);
+		put("mkgmap:toll", R_TOLL);
+		put("mkgmap:unpaved", R_UNPAVED);
+		put("mkgmap:ferry", R_FERRY);
+		put("junction", R_ROUNDABOUT);
+	}};
+
+	public static byte evalAccessTags(Element el){
+		byte noAccess = 0;
+		for (Map.Entry<Short,Byte> entry : ACCESS_TAGS_COMPILED.entrySet()){
+			if (el.tagIsLikeNo(entry.getKey()))
+				noAccess |= entry.getValue();
+		}
+		return  (byte) ~noAccess;
+	}
+
+
+	private static final short carpoolTagKey = TagDict.getInstance().xlate("mkgmap:carpool"); 
+	private static final short tollTagKey = TagDict.getInstance().xlate("mkgmap:toll"); 
+	private static final short unpavedTagKey = TagDict.getInstance().xlate("mkgmap:unpaved"); 
+	private static final short ferryTagKey = TagDict.getInstance().xlate("mkgmap:ferry"); 
+	private static final short throughrouteTagKey = TagDict.getInstance().xlate("mkgmap:throughroute"); 
+	private static final short junctionTagKey = TagDict.getInstance().xlate("junction"); 
+	private static final short onewayTagKey = TagDict.getInstance().xlate("oneway"); 
+	public static byte evalRouteTags(Element el){
+		byte routeFlags = 0;
+
+		// Style has to set "yes"
+		if (el.tagIsLikeYes(carpoolTagKey))
+			routeFlags |= R_CARPOOL;
+		if (el.tagIsLikeYes(tollTagKey))
+			routeFlags |= R_TOLL;
+		if (el.tagIsLikeYes(unpavedTagKey))
+			routeFlags |= R_UNPAVED;
+		if (el.tagIsLikeYes(ferryTagKey))
+			routeFlags |= R_FERRY;
+
+		// Style has to set "no" 
+		if (el.tagIsLikeNo(throughrouteTagKey))
+			routeFlags &= ~R_THROUGHROUTE;
+		else 
+			routeFlags |= R_THROUGHROUTE;
+
+		// tags without the mkgmap: prefix
+		if ("roundabout".equals(el.getTag(junctionTagKey))) 
+			routeFlags |= R_ROUNDABOUT;
+		if (el.tagIsLikeYes(onewayTagKey))
+			routeFlags |= R_ONEWAY;
+
+		return routeFlags;
+	}
+
+}
diff --git a/src/uk/me/parabola/imgfmt/app/net/GeneralRouteRestriction.java b/src/uk/me/parabola/imgfmt/app/net/GeneralRouteRestriction.java
new file mode 100644
index 0000000..987e00e
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/net/GeneralRouteRestriction.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014
+ * 
+ *  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.
+ * 
+ */
+package uk.me.parabola.imgfmt.app.net;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.me.parabola.imgfmt.app.CoordNode;
+
+/**
+ * A class to collect the data related to routing restrictions
+ * like only-left-turn or no-right-turn
+ * @author GerdP
+ *
+ */
+public class GeneralRouteRestriction {
+	public enum RestrType {TYPE_ONLY , 
+		TYPE_NOT, 
+		TYPE_NO_TROUGH // for elements like barriers, gates, etc.
+	}
+
+	private final byte exceptionMask;
+	private final RestrType type;
+	private final String sourceDesc;
+	
+	private long fromWayId, toWayId;
+	private CoordNode fromNode, toNode;
+	private List<Long> viaWayIds = new ArrayList<>();
+	private List<CoordNode> viaNodes = new ArrayList<>();
+	private char dirIndicator; // s(traight),l(eft),r(ight),u, ? for unknown 
+
+	public GeneralRouteRestriction(String type, byte exceptionMask, String sourceDesc) {
+		if ("not".equals(type))
+			this.type = RestrType.TYPE_NOT;
+		else if ("only".equals(type))
+			this.type = RestrType.TYPE_ONLY;
+		else if ("no_through".equals(type))
+			this.type = RestrType.TYPE_NO_TROUGH;
+		else 
+			throw new IllegalArgumentException("invalid type " + type);
+		this.exceptionMask = exceptionMask;
+		this.sourceDesc = sourceDesc;
+		this.setDirIndicator('?');
+	}
+	
+	public long getFromWayId() {
+		return fromWayId;
+	}
+	public void setFromWayId(long fromWayId) {
+		this.fromWayId = fromWayId;
+	}
+	public long getToWayId() {
+		return toWayId;
+	}
+	public void setToWayId(long toWayId) {
+		this.toWayId = toWayId;
+	}
+	public byte getExceptionMask() {
+		return exceptionMask;
+	}
+	public RestrType getType() {
+		return type;
+	}
+	public CoordNode getFromNode() {
+		return fromNode;
+	}
+	public void setFromNode(CoordNode fromNode) {
+		this.fromNode = fromNode;
+	}
+	public CoordNode getToNode() {
+		return toNode;
+	}
+	public void setToNode(CoordNode toNode) {
+		this.toNode = toNode;
+	}
+
+	public List<Long> getViaWayIds() {
+		return viaWayIds;
+	}
+
+	public void setViaWayIds(List<Long> viaWayIds) {
+		this.viaWayIds = new ArrayList<Long>(viaWayIds);
+	}
+	public List<CoordNode> getViaNodes() {
+		return viaNodes;
+	}
+	public void setViaNodes(List<CoordNode> viaNodes){
+		this.viaNodes = new ArrayList<>(viaNodes);
+	}
+	public String getSourceDesc(){
+		return sourceDesc;
+	}
+
+	public char getDirIndicator() {
+		return dirIndicator;
+	}
+
+	public void setDirIndicator(char dirIndicator) {
+		this.dirIndicator = dirIndicator;
+	}
+}
diff --git a/src/uk/me/parabola/imgfmt/app/net/NETFile.java b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
index 53c422b..eed440b 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NETFile.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
@@ -113,8 +113,8 @@ public class NETFile extends ImgFile {
 	 * @return A sorted list of road labels that identify all the different roads.
 	 */
 	private List<LabeledRoadDef> sortRoads() {
-		List<SortKey<LabeledRoadDef>> sortKeys = new ArrayList<SortKey<LabeledRoadDef>>(roads.size());
-		Map<String, byte[]> cache = new HashMap<String, byte[]>();
+		List<SortKey<LabeledRoadDef>> sortKeys = new ArrayList<>(roads.size());
+		Map<Label, byte[]> cache = new HashMap<>();
 
 		for (RoadDef rd : roads) {
 			Label[] labels = rd.getLabels();
@@ -125,7 +125,7 @@ public class NETFile extends ImgFile {
 
 				// Sort by name, city, region/country and subdivision number.
 				LabeledRoadDef lrd = new LabeledRoadDef(label, rd);
-				SortKey<LabeledRoadDef> nameKey = sort.createSortKey(lrd, label.getText(), 0, cache);
+				SortKey<LabeledRoadDef> nameKey = sort.createSortKey(lrd, label, 0, cache);
 
 				// If there is a city add it to the sort.
 				City city = rd.getCity();
@@ -133,12 +133,13 @@ public class NETFile extends ImgFile {
 				if (city != null) {
 					int region = city.getRegionNumber();
 					int country = city.getCountryNumber();
-					cityKey = sort.createSortKey(null, city.getName(), (region & 0xffff) << 16 | (country & 0xffff), cache);
+					cityKey = sort.createSortKey(null, city.getLabel(), (region & 0xffff) << 16 | (country & 0xffff),
+							cache);
 				} else {
-					cityKey = sort.createSortKey(null, "", 0, cache);
+					cityKey = sort.createSortKey(null, Label.NULL_OUT_LABEL, 0, cache);
 				}
 
-				SortKey<LabeledRoadDef> sortKey = new MultiSortKey<LabeledRoadDef>(nameKey, cityKey,
+				SortKey<LabeledRoadDef> sortKey = new MultiSortKey<>(nameKey, cityKey,
 						new IntegerSortKey<LabeledRoadDef>(null, rd.getStartSubdivNumber(), 0));
 				sortKeys.add(sortKey);
 			}
@@ -146,18 +147,18 @@ public class NETFile extends ImgFile {
 
 		Collections.sort(sortKeys);
 
-		List<LabeledRoadDef> out = new ArrayList<LabeledRoadDef>(sortKeys.size());
+		List<LabeledRoadDef> out = new ArrayList<>(sortKeys.size());
 
-		String lastName = null;
+		Label lastName = null;
 		City lastCity = null;
-		List<LabeledRoadDef> dupes = new ArrayList<LabeledRoadDef>();
+		List<LabeledRoadDef> dupes = new ArrayList<>();
 
 		// Since they are sorted we can easily remove the duplicates.
 		// The duplicates are saved to the dupes list.
 		for (SortKey<LabeledRoadDef> key : sortKeys) {
 			LabeledRoadDef lrd = key.getObject();
 
-			String name = lrd.label.getText();
+			Label name = lrd.label;
 			RoadDef road = lrd.roadDef;
 			City city = road.getCity();
 
@@ -165,7 +166,7 @@ public class NETFile extends ImgFile {
 
 				// process any previously collected duplicate road names and reset.
 				addDisconnected(dupes, out);
-				dupes = new ArrayList<LabeledRoadDef>();
+				dupes = new ArrayList<>();
 
 				lastName = name;
 				lastCity = city;
@@ -266,12 +267,12 @@ public class NETFile extends ImgFile {
 		});
 
 		int lastDiv = 0;
-		List<LabeledRoadDef> dupes = new ArrayList<LabeledRoadDef>();
+		List<LabeledRoadDef> dupes = new ArrayList<>();
 		for (LabeledRoadDef lrd : in) {
 			int sd = lrd.roadDef.getStartSubdivNumber();
 			if (sd != lastDiv) {
 				addDisconnectedSmall(dupes, out);
-				dupes = new ArrayList<LabeledRoadDef>();
+				dupes = new ArrayList<>();
 				lastDiv = sd;
 			}
 			dupes.add(lrd);
diff --git a/src/uk/me/parabola/imgfmt/app/net/NOD1Part.java b/src/uk/me/parabola/imgfmt/app/net/NOD1Part.java
index 729d761..f4e4d38 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NOD1Part.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NOD1Part.java
@@ -17,8 +17,12 @@
 package uk.me.parabola.imgfmt.app.net;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
@@ -181,9 +185,9 @@ public class NOD1Part {
 	// The area that actually has nodes.
 	private final BBox bboxActual = new BBox();
 
-	private final List<RouteNode> nodes = new ArrayList<RouteNode>();
-	private final TableA tabA = new TableA();
-	private final TableB tabB = new TableB();
+	private List<RouteNode> nodes = new ArrayList<RouteNode>();
+	private TableA tabA = new TableA();
+	private Map<RouteNode,RouteNode> destNodes = new LinkedHashMap<RouteNode, RouteNode>();
 
 	/**
 	 * Create an unbounded NOD1Part.
@@ -224,11 +228,34 @@ public class NOD1Part {
 		for (RouteArc arc : node.arcsIteration()) {
 			tabA.addArc(arc);
 			RouteNode dest = arc.getDest();
-			if (bbox != null && !bbox.contains(dest.getCoord())) {
+			if (arc.isInternal() == false){
+				destNodes.put(dest, dest);
+			}
+			else if (bbox != null && !bbox.contains(dest.getCoord()) || dest.getGroup() != node.getGroup()) {
 				arc.setInternal(false);
-				tabB.addNode(dest);
+				destNodes.put(dest, dest);
+			}
+		}
+		
+		for (RouteRestriction rr: node.getRestrictions()){
+			List<RouteArc> arcs = rr.getArcs();
+			if (arcs.size() >= 3){
+				for (int i = 0; i < arcs.size(); i++){
+					RouteArc arc = arcs.get(i);
+					if (arc.getSource() != node){
+						tabA.addArc(arc);
+						RouteNode dest = arc.getDest();
+						if (arc.isInternal() == false)
+							destNodes.put(dest, dest);
+						else if (bbox != null && !bbox.contains(dest.getCoord()) || dest.getGroup() != node.getGroup()) {
+							arc.setInternal(false);
+							destNodes.put(dest, dest);
+						} 
+					}
+				}
 			}
 		}
+		
 		nodesSize += node.boundSize();
 	}
 
@@ -269,14 +296,17 @@ public class NOD1Part {
 
 		for (int i = 0; i < split.length; i++)
 			parts[i] = new NOD1Part(split[i]);
-
+		
+		
 		for (RouteNode node : nodes) {
 			int i = 0;
 			while (!split[i].contains(node.getCoord()))
 				i++;
 			parts[i].addNode(node);
 		}
-
+		this.tabA = null;
+		this.destNodes = null;
+		this.nodes = null;
 		for (NOD1Part part : parts)
 			if(!part.bboxActual.empty)
 				centers.addAll(part.subdivideHelper(depth + 1));
@@ -285,10 +315,10 @@ public class NOD1Part {
 	}
 
 	private boolean satisfiesConstraints() {
-		log.debug("constraints:", bboxActual, tabA.size(), tabB.size(), nodesSize);
+		log.debug("constraints:", bboxActual, tabA.size(), destNodes.size(), nodesSize);
 		return bboxActual.getMaxDimension() < MAX_SIZE
 			&& tabA.size() < MAX_TABA
-			&& tabB.size() < MAX_TABB
+			&& destNodes.size() < MAX_TABB
 			&& nodesSize < MAX_NODES_SIZE;
 	}
 
@@ -299,6 +329,14 @@ public class NOD1Part {
 	 * be a legal RouteCenter.
 	 */
 	private RouteCenter toRouteCenter() {
+		Collections.sort(nodes, new Comparator<RouteNode>() {
+			public int compare(RouteNode n1, RouteNode n2) {
+				return n1.getCoord().compareTo(n2.getCoord());
+			}
+		});
+		TableB tabB = new TableB();
+		for (RouteNode rn : destNodes.keySet())
+			tabB.addNode(rn);
 		return new RouteCenter(bboxActual.toArea(), nodes, tabA, tabB);
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/NODFile.java b/src/uk/me/parabola/imgfmt/app/net/NODFile.java
index ed930da..a684dff 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NODFile.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NODFile.java
@@ -72,6 +72,7 @@ public class NODFile extends ImgFile {
 		writeNodes();
 		writeRoadData();
 		writeBoundary();
+		writeHighClassBoundary();
 	}
 
 	public void writePost() {
@@ -96,8 +97,14 @@ public class NODFile extends ImgFile {
 		Section section = nodHeader.getNodeSection();
 		writer = new SectionWriter(writer, section);
 
-		for (RouteCenter cp : centers)
-			cp.write(writer);
+		int[] classBoundaries = nodHeader.getClassBoundaries();
+		for (RouteCenter cp : centers){
+			cp.write(writer, classBoundaries);
+		}
+		for (int i = 4; i >= 0; --i){
+			if (classBoundaries[i] > writer.position())
+				classBoundaries[i] = writer.position();
+		}
 		nodHeader.setNodeSize(writer.position());
 		log.debug("the nod offset", Integer.toHexString(getWriter().position()));
 		Section.close(writer);
@@ -136,13 +143,44 @@ public class NODFile extends ImgFile {
 		for (RouteNode node : boundary) {
 			if(debug)
 				log.debug("wrting nod3", writer.position());
-			node.writeNod3(writer);
+			node.writeNod3OrNod4(writer);
 		}
 		if(debug)
 			log.debug("ending nod3", writer.position());
 		nodHeader.setBoundarySize(writer.position());
 	}
 
+	/**
+	 * Write the high class boundary node table (NOD4).
+	 * Like NOD3, but contains only nodes on roads with class > 0
+	 */
+	private void writeHighClassBoundary() {
+		log.info("writeBoundary");
+
+//		Collections.sort(boundary); // already sorted for NOD3
+
+		Section section = nodHeader.getHighClassBoundary();
+		int pos = section.getPosition();
+		pos = (pos + 0x200) & ~0x1ff; // align on 0x200  
+		int numBytesToWrite = pos - section.getPosition();
+		for (int i = 0; i < numBytesToWrite; i++)
+			getWriter().put((byte)0); 
+		section.setPosition(pos);
+		ImgFileWriter writer = new SectionWriter(getWriter(), section);
+		
+		boolean debug = log.isDebugEnabled();
+		for (RouteNode node : boundary) {
+			if (node.getNodeClass() == 0)
+				continue;
+			if(debug)
+				log.debug("wrting nod4", writer.position());
+			node.writeNod3OrNod4(writer);
+		}
+		if(debug)
+			log.debug("ending nod4", writer.position());
+		nodHeader.setHighClassBoundarySize(writer.position());
+	}
+
 	public void setNetwork(List<RouteCenter> centers, List<RoadDef> roads, List<RouteNode> boundary) {
 		this.centers = centers;
 		this.roads = roads;
diff --git a/src/uk/me/parabola/imgfmt/app/net/NODHeader.java b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
index 3e41d54..59744b1 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
@@ -16,6 +16,8 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import java.util.Arrays;
+
 import uk.me.parabola.imgfmt.ReadFailedException;
 import uk.me.parabola.imgfmt.app.CommonHeader;
 import uk.me.parabola.imgfmt.app.ImgFileReader;
@@ -23,10 +25,14 @@ import uk.me.parabola.imgfmt.app.ImgFileWriter;
 import uk.me.parabola.imgfmt.app.Section;
 
 /**
+ * Header information for the NOD file.
+ *
+ * This is a routing network for the map.
+ *
  * @author Steve Ratcliffe
  */
 public class NODHeader extends CommonHeader {
-	public static final int HEADER_LEN = 63;
+	public static final int HEADER_LEN = 127;
 
 	static final char DEF_ALIGN = 6;
 	private static final char BOUNDARY_ITEM_SIZE = 9;
@@ -34,9 +40,12 @@ public class NODHeader extends CommonHeader {
 	private final Section nodes = new Section();
 	private final Section roads = new Section(nodes);
 	private final Section boundary = new Section(roads, BOUNDARY_ITEM_SIZE);
+	private final Section highClassBoundary = new Section(boundary);
+	private final int[] classBoundaries = new int[5];
 
     private int flags;
     private int align;
+    private int mult1;
 	private int tableARecordLen;
 
 	/** 
@@ -51,6 +60,7 @@ public class NODHeader extends CommonHeader {
 
 	public NODHeader() {
 		super(HEADER_LEN, "GARMIN NOD");
+		Arrays.fill(classBoundaries, Integer.MAX_VALUE);
 	}
 
 	/**
@@ -64,12 +74,23 @@ public class NODHeader extends CommonHeader {
         nodes.readSectionInfo(reader, false);
         flags = reader.getChar();
         reader.getChar();
-        align = reader.getChar();
+        align = reader.get();
+        mult1 = reader.get();
         tableARecordLen = reader.getChar();
         roads.readSectionInfo(reader, false);
         reader.getInt();
         boundary.readSectionInfo(reader, true);
-    }
+		reader.getInt();
+		if (getHeaderLength() > 0x3f) {
+			highClassBoundary.readSectionInfo(reader, false);
+
+			classBoundaries[0] = reader.getInt();
+			classBoundaries[1] = classBoundaries[0] + reader.getInt();
+			classBoundaries[2] = classBoundaries[1] + reader.getInt();
+			classBoundaries[3] = classBoundaries[2] + reader.getInt();
+			classBoundaries[4] = classBoundaries[3] + reader.getInt();
+		}
+	}
 
 	/**
 	 * Write the rest of the header.  It is guaranteed that the writer will be set
@@ -77,26 +98,52 @@ public class NODHeader extends CommonHeader {
 	 *
 	 * @param writer The header is written here.
 	 */
+	
+	// multiplier shift for road + arc length values, the smaller the shift the higher the precision and NOD size 
+	// as it has an influence on the number of bits needed to encode a length
+	final static int DISTANCE_MULT_SHIFT = 1; // 0..7  1 seems to be a good compromise
+	final static int DISTANCE_MULT = 1 << DISTANCE_MULT_SHIFT;
 	protected void writeFileHeader(ImgFileWriter writer) {
 		nodes.setPosition(HEADER_LEN);
 		nodes.writeSectionInfo(writer);
 
-		// now sets 0x02 (enable turn restrictions?)
-		int val = 0x27;
+		// 0x0001 always set, meaning ?
+		// 0x0002 (enable turn restrictions)
+		// 0x001c meaning ?
+		// 0x00E0 distance multiplier, effects predicted travel time
+		int flags = 0x0207;
+		assert Integer.bitCount(DISTANCE_MULT) == 1;
+		assert DISTANCE_MULT_SHIFT < 8;
+		flags |= DISTANCE_MULT_SHIFT << 5;
 		if(driveOnLeft.get())
-			val |= 0x0300;
-		writer.putInt(val);
+			flags |= 0x0100;
+		
+		writer.putInt(flags);
 
-		char align = DEF_ALIGN;
-		writer.putChar(align);
+		byte align = DEF_ALIGN;
+		writer.put(align);
+		writer.put((byte) 0); // pointer multiplier
 		writer.putChar((char) 5);
 
 		roads.writeSectionInfo(writer);
 		writer.putInt(0);
 
 		boundary.writeSectionInfo(writer);
+		// new fields for header length > 0x3f
+		writer.putInt(2); // no other value spotted, meaning ?
+		highClassBoundary.writeSectionInfo(writer);
+		writer.putInt(classBoundaries[0]);
+		for (int i = 1; i < classBoundaries.length; i++){
+			writer.putInt(classBoundaries[i] - classBoundaries[i-1]);
+		}
 	}
 
+	private static final double UNIT_TO_METER = 2.4;
+	public static int metersToRaw(double m) {
+		double d = m / (DISTANCE_MULT * UNIT_TO_METER);
+		return (int) Math.round(d);
+	}
+	
     public int getNodeStart() {
         return nodes.getPosition();
     }
@@ -133,6 +180,17 @@ public class NODHeader extends CommonHeader {
 		return boundary;
 	}
 
+	public void setHighClassBoundarySize(int size) {
+		highClassBoundary.setSize(size);
+	}
+	public Section getHighClassBoundary() {
+		return highClassBoundary;
+	}
+
+	public int[] getClassBoundaries() {
+		return classBoundaries;
+	}
+
 	public static void setDriveOnLeft(boolean dol) {
 		driveOnLeft.set(dol);
 	}
@@ -145,6 +203,10 @@ public class NODHeader extends CommonHeader {
         return align;
     }
 
+	public int getMult1() {
+		return mult1;
+	}
+
 	public int getTableARecordLen() {
 		return tableARecordLen;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
index 505369d..3b55592 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
@@ -62,31 +62,27 @@ public class RoadDef implements Comparable<RoadDef> {
 	private static final int NET_FLAG_ONEWAY   = 0x02;
 
 	private static final int NOD2_FLAG_UNK        = 0x01;
-	private static final int NOD2_FLAG_EXTRA_DATA = 0x80;
+//	private static final int NOD2_FLAG_EXTRA_DATA = 0x80; just documentation
 
 	// first byte of Table A info in NOD 1
 	private static final int TABA_FLAG_TOLL = 0x80;
-	private static final int TABA_MASK_CLASS = 0x70;
+//	private static final int TABA_MASK_CLASS = 0x70; just documentation
 	private static final int TABA_FLAG_ONEWAY = 0x08;
-	private static final int TABA_MASK_SPEED = 0x07;
+//	private static final int TABA_MASK_SPEED = 0x07; just documentation
 
 	private static final int TABAACCESS_FLAG_CARPOOL = 0x0008;
 	private static final int TABAACCESS_FLAG_NOTHROUGHROUTE = 0x0080;
 	
-	// second byte: access flags - order must correspond to constants
-	// in RoadNetwork 
-	// bits 0x08, 0x80 are set separately
-	private static final int[] ACCESS = {
-		0x8000, // emergency (net pointer bit 31)
-		0x4000, // delivery (net pointer bit 30)
-		0x0001, // car
-		0x0002, // bus
-		0x0004, // taxi
-		0x0010, // foot
-		0x0020, // bike
-		0x0040, // truck
-	};
-
+	// second byte: access flags, bits 0x08, 0x80 are set separately 
+	private static final int TABAACCESS_FLAG_NO_EMERGENCY = 0x8000;
+	private static final int TABAACCESS_FLAG_NO_DELIVERY  = 0x4000;
+	private static final int TABAACCESS_FLAG_NO_CAR     = 0x0001;
+	private static final int TABAACCESS_FLAG_NO_BUS     = 0x0002;
+	private static final int TABAACCESS_FLAG_NO_TAXI    = 0x0004;
+	private static final int TABAACCESS_FLAG_NO_FOOT    = 0x0010;
+	private static final int TABAACCESS_FLAG_NO_BIKE    = 0x0020;
+	private static final int TABAACCESS_FLAG_NO_TRUCK   = 0x0040;
+	
 	// the offset in Nod2 of our Nod2 record
 	private int offsetNod2;
 
@@ -98,6 +94,9 @@ public class RoadDef implements Comparable<RoadDef> {
 	 */
 	private int netFlags = NET_FLAG_UNK1;
 
+	// the allowed vehicles in mkgmap internal format
+	private byte mkgmapAccess; 
+	
 	// The road length units may be affected by other flags in the header as
 	// there is doubt as to the formula.
 	private int roadLength;
@@ -108,7 +107,7 @@ public class RoadDef implements Comparable<RoadDef> {
 	private final Label[] labels = new Label[MAX_LABELS];
 	private int numlabels;
 
-	private final SortedMap<Integer,List<RoadIndex>> roadIndexes = new TreeMap<Integer,List<RoadIndex>>();
+	private final SortedMap<Integer,List<RoadIndex>> roadIndexes = new TreeMap<>();
 
 	private City city;
 	private Zip zip;
@@ -120,7 +119,7 @@ public class RoadDef implements Comparable<RoadDef> {
 	private boolean flareCheck;
 	private Set<String> messageIssued;
 
-	private final List<Offset> rgnOffsets = new ArrayList<Offset>(4);
+	private final List<Offset> rgnOffsets = new ArrayList<>();
 
 	/*
 	 * Everything that's relevant for writing out Nod 2.
@@ -170,9 +169,9 @@ public class RoadDef implements Comparable<RoadDef> {
 	public String toString() {
 		// assumes id is an OSM id
 		String browseURL = "http://www.openstreetmap.org/browse/way/" + id;
-		if(getName() != null)
-			return "(" + getName() + ", " + browseURL + ")";
-		else
+		//if(getName() != null)
+		//	return "(" + getName() + ", " + browseURL + ")";
+		//else
 			return "(" + browseURL + ")";
 	}
 
@@ -180,7 +179,7 @@ public class RoadDef implements Comparable<RoadDef> {
 		if (name != null)
 			return name;
 		if (labels[0] != null)
-			return labels[0].getText();
+			return labels[0].toString();
 		return null;
 	}
 
@@ -211,9 +210,8 @@ public class RoadDef implements Comparable<RoadDef> {
 		}
 
 		writeLabels(writer);
-		if (numbers != null) { // TODO combine if
-			if (numbers.getSwapped())
-				netFlags |= 0x20; // swapped default; left=even, right=odd
+		if (numbers != null && numbers.getSwapped()) {
+			netFlags |= 0x20; // swapped default; left=even, right=odd
 		}
 		writer.put((byte) netFlags);
 		writer.put3(roadLength);
@@ -352,13 +350,11 @@ public class RoadDef implements Comparable<RoadDef> {
 		int level = pl.getSubdiv().getZoom().getLevel();
 		List<RoadIndex> l = roadIndexes.get(level);
 		if (l == null) {
-			l = new ArrayList<RoadIndex>(4);
+			l = new ArrayList<>();
 			roadIndexes.put(level, l);
 		}
-		int s = l.size();
 		l.add(new RoadIndex(pl));
 
-		// XXX needs to be the lowest level, which might not always be zero in the future
 		if (level == 0) {
 			nodeCount += pl.getNodeCount();
 		}
@@ -396,9 +392,8 @@ public class RoadDef implements Comparable<RoadDef> {
 	/**
 	 * Set the road length (in meters).
 	 */
-	public void setLength(double l) {
-		// XXX: this is from test.display.NetDisplay, possibly varies
-		roadLength = (int) l / 2;
+	public void setLength(double lenInMeter) {
+		roadLength = NODHeader.metersToRaw(lenInMeter);
 	}
 
 	public boolean hasHouseNumbers() {
@@ -481,6 +476,10 @@ public class RoadDef implements Comparable<RoadDef> {
 		this.node = node;
 	}
 
+	public RouteNode getNode(){
+		return node;
+	}
+	
 	private boolean hasNodInfo() {
 		return (netFlags & NET_FLAG_NODINFO) != 0;
 	}
@@ -586,13 +585,43 @@ public class RoadDef implements Comparable<RoadDef> {
 		tabAAccess |= TABAACCESS_FLAG_NOTHROUGHROUTE;
 	}
 
-	public void setAccess(boolean[] access) {
-		assert access.length <= ACCESS.length;
-		for (int i = 0; i < access.length; i++)
-			if (access[i])
-				tabAAccess |= ACCESS[i];
+	/**
+	 * @return allowed vehicles in mkgmap format  
+	 */
+	public byte getAccess() {
+		return mkgmapAccess;
 	}
 
+	/**
+	 * Set allowed vehicles
+	 * @param mkgmapAccess bit mask in mkgmap format
+	 */
+	public void setAccess(byte mkgmapAccess) {
+		this.mkgmapAccess = mkgmapAccess;
+		// translate internal format to that used in TableA
+		//clear the corresponding bits
+		tabAAccess &= ~(0xc077);
+		if (mkgmapAccess == (byte) 0xff)
+			return; // all vehicles allowed
+
+		if ((mkgmapAccess & AccessTagsAndBits.FOOT) == 0)
+			tabAAccess |= TABAACCESS_FLAG_NO_FOOT; 
+		if ((mkgmapAccess & AccessTagsAndBits.BIKE) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_BIKE;
+		if ((mkgmapAccess & AccessTagsAndBits.CAR) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_CAR;
+		if ((mkgmapAccess & AccessTagsAndBits.DELIVERY) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_DELIVERY;
+		if ((mkgmapAccess & AccessTagsAndBits.TRUCK) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_TRUCK;
+		if ((mkgmapAccess & AccessTagsAndBits.BUS) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_BUS;
+		if ((mkgmapAccess & AccessTagsAndBits.TAXI) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_TAXI;
+		if ((mkgmapAccess & AccessTagsAndBits.EMERGENCY) == 0)
+			tabAAccess |=TABAACCESS_FLAG_NO_EMERGENCY;
+	}
+	
 	public int getTabAInfo() {
 		return tabAInfo;
 	}
@@ -610,7 +639,7 @@ public class RoadDef implements Comparable<RoadDef> {
 	// road class that goes in various places (really?)
 	public void setRoadClass(int roadClass) {
 		assert roadClass < 0x08;
-
+		
 		/* for RouteArcs to get as their "destination class" */
 		this.roadClass = roadClass;
 
@@ -731,9 +760,10 @@ public class RoadDef implements Comparable<RoadDef> {
 
 	public boolean messagePreviouslyIssued(String key) {
 		if(messageIssued == null)
-			messageIssued = new HashSet<String>();
+			messageIssued = new HashSet<>();
 		boolean previouslyIssued = messageIssued.contains(key);
 		messageIssued.add(key);
 		return previouslyIssued;
 	}
+
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java
new file mode 100644
index 0000000..1b2bf9d
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java
@@ -0,0 +1,709 @@
+/*
+ * 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: 13-Jul-2008
+ */
+package uk.me.parabola.imgfmt.app.net;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.EnhancedProperties;
+
+/**
+ * This holds the road network.  That is all the roads and the nodes
+ * that connect them together.
+ * 
+ * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance / bearing calculations</a>
+ * @author Steve Ratcliffe
+ */
+public class RoadNetwork {
+	private static final Logger log = Logger.getLogger(RoadNetwork.class);
+
+	private final static int MAX_RESTRICTIONS_ARCS = 7;
+	private final Map<Integer, RouteNode> nodes = new LinkedHashMap<>();
+
+	// boundary nodes
+	// a node should be in here if the nodes boundary flag is set
+	private final List<RouteNode> boundary = new ArrayList<>();
+	private final List<RoadDef> roadDefs = new ArrayList<>();
+	private List<RouteCenter> centers = new ArrayList<>();
+	private int adjustTurnHeadings ;
+	private boolean checkRoundabouts;
+	private boolean checkRoundaboutFlares;
+	private int maxFlareLengthRatio ;
+	private boolean reportSimilarArcs;
+
+	public void config(EnhancedProperties props) {
+		String ath = props.getProperty("adjust-turn-headings");
+		if(ath != null) {
+			if(ath.length() > 0)
+				adjustTurnHeadings = Integer.decode(ath);
+			else
+				adjustTurnHeadings = RouteNode.ATH_DEFAULT_MASK;
+		}
+		checkRoundabouts = props.getProperty("check-roundabouts", false);
+		checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false);
+		maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0);
+
+		reportSimilarArcs = props.getProperty("report-similar-arcs", false);
+	}
+
+	public void addRoad(RoadDef roadDef, List<Coord> coordList) {
+		roadDefs.add(roadDef);
+
+		CoordNode lastCoord = null;
+		int lastIndex = 0;
+		double roadLength = 0;
+		double arcLength = 0;
+		int pointsHash = 0;
+
+		int npoints = coordList.size();
+		for (int index = 0; index < npoints; index++) {
+			Coord co = coordList.get(index);
+
+			if (index > 0) {
+				double d = co.distance(coordList.get(index-1));
+				arcLength += d;
+				roadLength += d;
+			}
+
+			int id = co.getId();
+
+			pointsHash += co.hashCode();
+
+			if (id == 0)
+				// not a routing node
+				continue;
+
+			// The next coord determines the heading
+			// If this is the not the first node, then create an arc from
+			// the previous node to this one (and back again).
+			if (lastCoord != null) {
+				int lastId = lastCoord.getId();
+				if(log.isDebugEnabled()) {
+					log.debug("lastId = " + lastId + " curId = " + id);
+					log.debug("from " + lastCoord.toDegreeString() 
+							  + " to " + co.toDegreeString());
+					log.debug("arclength=" + arcLength + " roadlength=" + roadLength);
+				}
+
+				RouteNode node1 = getOrAddNode(lastId, lastCoord);
+				RouteNode node2 = getOrAddNode(id, co);
+
+				if(node1 == node2)
+					log.error("Road " + roadDef + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken");
+				else if(arcLength == 0)
+					log.warn("Road " + roadDef + " contains zero length arc at " + co.toOSMURL());
+
+				Coord forwardBearingPoint = coordList.get(lastIndex + 1);
+				if(lastCoord.equals(forwardBearingPoint)) {
+					// bearing point is too close to last node to be
+					// useful - try some more points
+					for(int bi = lastIndex + 2; bi <= index; ++bi) {
+						if(!lastCoord.equals(coordList.get(bi))) {
+							forwardBearingPoint = coordList.get(bi);
+							break;
+						}
+					}
+				}
+				Coord reverseBearingPoint = coordList.get(index - 1);
+				if(co.equals(reverseBearingPoint)) {
+					// bearing point is too close to this node to be
+					// useful - try some more points
+					for(int bi = index - 2; bi > lastIndex; --bi) {
+						if(!co.equals(coordList.get(bi))) {
+							reverseBearingPoint = coordList.get(bi);
+							break;
+						}
+					}
+				}
+				
+				double forwardInitialBearing = lastCoord.bearingTo(forwardBearingPoint);
+				double forwardDirectBearing = (co == forwardBearingPoint) ? forwardInitialBearing: lastCoord.bearingTo(co); 
+
+				double reverseInitialBearing = co.bearingTo(reverseBearingPoint);
+				double directLength = (lastIndex + 1 == index) ? arcLength : lastCoord.distance(co);
+				double reverseFinalBearing, forwardFinalBearing, reverseDirectBearing;
+				if (directLength > 0){
+					// bearing on rhumb line is a constant, so we can simply revert
+					reverseDirectBearing = (forwardDirectBearing <= 0) ? 180 + forwardDirectBearing: -(180 - forwardDirectBearing) % 180.0;
+					forwardFinalBearing = (reverseInitialBearing <= 0) ? 180 + reverseInitialBearing : -(180 - reverseInitialBearing) % 180.0;
+					reverseFinalBearing = (forwardInitialBearing <= 0) ? 180 + forwardInitialBearing : -(180 - forwardInitialBearing) % 180.0;
+				}
+				else {
+					reverseDirectBearing = 0;
+					forwardFinalBearing = 0;
+					reverseFinalBearing = 0;
+				}
+				// Create forward arc from node1 to node2
+				RouteArc arc = new RouteArc(roadDef,
+											node1,
+											node2,
+											forwardInitialBearing,
+											forwardFinalBearing,
+											forwardDirectBearing,
+											arcLength,
+											arcLength,
+											directLength,
+											pointsHash);
+				arc.setForward();
+				node1.addArc(arc);
+				
+				// Create the reverse arc
+				RouteArc reverseArc = new RouteArc(roadDef,
+								   node2, node1,
+								   reverseInitialBearing,
+								   reverseFinalBearing,
+								   reverseDirectBearing,
+								   arcLength,
+								   arcLength,
+								   directLength,
+								   pointsHash);
+				node2.addArc(reverseArc);
+				// link the two arcs
+				arc.setReverseArc(reverseArc);
+				reverseArc.setReverseArc(arc);
+			} else {
+				// This is the first node in the road
+				roadDef.setNode(getOrAddNode(id, co));
+			}
+
+			lastCoord = (CoordNode) co;
+			lastIndex = index;
+			arcLength = 0;
+			pointsHash = co.hashCode();
+		}
+		roadDef.setLength(roadLength);
+	}
+
+	private RouteNode getOrAddNode(int id, Coord coord) {
+		RouteNode node = nodes.get(id);
+		if (node == null) {
+			node = new RouteNode(coord);
+			nodes.put(id, node);
+			if (node.isBoundary())
+				boundary.add(node);
+		}
+		return node;
+	}
+
+	public List<RoadDef> getRoadDefs() {
+		return roadDefs;
+	}
+
+	/**
+	 * Split the network into RouteCenters.
+	 *
+	 * The resulting centers must satisfy several constraints,
+	 * documented in NOD1Part.
+	 */
+	private void splitCenters() {
+		if (nodes.isEmpty())
+			return;
+		assert centers.isEmpty() : "already subdivided into centers";
+
+		// sort nodes by NodeGroup 
+		List<RouteNode> nodeList = new ArrayList<>(nodes.values());
+		nodes.clear(); // return to GC
+		for (int group = 0; group <= 4; group++){
+			NOD1Part nod1 = new NOD1Part();
+			int n = 0;
+			for (RouteNode node : nodeList) {
+				if (node.getGroup() != group)
+					continue;
+				if(!node.isBoundary()) {
+					if(checkRoundabouts)
+						node.checkRoundabouts();
+					if(checkRoundaboutFlares)
+						node.checkRoundaboutFlares(maxFlareLengthRatio);
+					if(reportSimilarArcs)
+						node.reportSimilarArcs();
+				}
+				if(adjustTurnHeadings != 0)
+					node.tweezeArcs(adjustTurnHeadings);
+				nod1.addNode(node);
+				n++;
+			}
+			if (n > 0)
+				centers.addAll(nod1.subdivide());
+		}
+	}
+
+	public List<RouteCenter> getCenters() {
+		if (centers.isEmpty()){
+			addArcsToMajorRoads();
+			splitCenters();
+		}
+		return centers;
+	}
+
+	/**
+	 * add indirect arcs for each road class (in descending order)
+	 */
+	private void addArcsToMajorRoads() {
+		long t1 = System.currentTimeMillis();
+		
+		for (RoadDef rd: roadDefs){
+			if (rd.getRoadClass() >= 1)
+				rd.getNode().addArcsToMajorRoads(rd);
+		}
+		log.info(" added major road arcs in " + (System.currentTimeMillis() - t1) + " ms");
+	}
+
+	/**
+	 * Get the list of nodes on the boundary of the network.
+	 *
+	 * Currently empty.
+	 */
+	public List<RouteNode> getBoundary() {
+		return boundary;
+	}
+
+	/**
+	 * One restriction forbids to travel a specific combination of arcs.
+	 * We know two kinds: 3 nodes with two arcs and one via node or 4 nodes with 3 arcs
+	 * and two via nodes. Maybe more nodes are possible, but we don't know for sure how
+	 * to write them (2014-04-02). 
+	 * Depending on the data in grr we create one or more such restrictions.
+	 * A restriction with 4 (or more) nodes is added to each via node.     
+	 * 
+	 * The OSM restriction gives a from way id and a to way id and one or more 
+	 * via nodes. It is possible that the to-way is a loop, so we have to identify
+	 * the correct arc. 
+	 * @param grr the object that holds the details about the route restriction
+	 */
+	public int addRestriction(GeneralRouteRestriction grr) {
+		if (grr.getType() == GeneralRouteRestriction.RestrType.TYPE_NO_TROUGH)
+			return addNoThroughRoute(grr);
+		String sourceDesc = grr.getSourceDesc();
+		
+		List<RouteNode> viaNodes = new ArrayList<>();
+		for (CoordNode via : grr.getViaNodes()){
+			RouteNode vn = nodes.get(via.getId());
+			if (vn == null){
+				log.error(sourceDesc, "can't locate 'via' RouteNode with id", via.getId());
+				return 0;
+			}
+			viaNodes.add(vn);
+		}
+		
+		int firstViaId = grr.getViaNodes().get(0).getId();
+		int lastViaId = grr.getViaNodes().get(grr.getViaNodes().size()-1).getId();
+		RouteNode firstViaNode = nodes.get(firstViaId);
+		RouteNode lastViaNode = nodes.get(lastViaId);
+		List<List<RouteArc>> viaArcsList = new ArrayList<>();
+		if (grr.getViaNodes().size() != grr.getViaWayIds().size() + 1){
+			log.error(sourceDesc, "internal error: number of via nodes and via ways doesn't fit");
+			return 0;
+		}
+		for (int i = 1; i < grr.getViaNodes().size(); i++){
+			RouteNode vn = viaNodes.get(i-1);
+			Long viaWayId = grr.getViaWayIds().get(i-1);
+			List<RouteArc> viaArcs = vn.getDirectArcsTo(viaNodes.get(i), viaWayId);
+			if (viaArcs.isEmpty()){
+				log.error(sourceDesc, "can't locate arc from 'via' node at",vn.getCoord().toOSMURL(),"to next 'via' node on way",viaWayId);
+				return 0;
+			}
+			viaArcsList.add(viaArcs);
+		}
+		
+		// determine the from node and arc(s)
+		int fromId = 0;
+		RouteNode fn = null;
+		if (grr.getFromNode() != null){
+			fromId = grr.getFromNode().getId();
+			// polish input data provides id
+			fn = nodes.get(fromId);
+			if (fn == null ){
+				log.error(sourceDesc, "can't locate 'from' RouteNode with id", fromId);
+				return 0; 
+			}
+		} else {
+			List<RouteArc> possibleFromArcs = firstViaNode.getDirectArcsOnWay(grr.getFromWayId());
+			for (RouteArc arc : possibleFromArcs){
+				if (fn == null)
+					fn = arc.getDest();
+				else if (fn != arc.getDest()){
+					log.warn(sourceDesc, "found different 'from' arcs for way",grr.getFromWayId(),"restriction is ignored");
+					return 0;
+				}
+			}
+			if (fn == null){
+				log.warn(sourceDesc, "can't locate 'from' RouteNode for 'from' way", grr.getFromWayId());
+				return 0;
+			} 
+			fromId = fn.getCoord().getId();
+		}
+		List<RouteArc> fromArcs = fn.getDirectArcsTo(firstViaNode, grr.getFromWayId()); 
+		if (fromArcs.isEmpty()){
+			log.error(sourceDesc, "can't locate arc from 'from' node ",fromId,"to 'via' node",firstViaId,"on way",grr.getFromWayId());
+			return 0;
+		}
+		
+		// a bit more complex: determine the to-node and arc(s) 
+		RouteNode uTurnNode = (viaNodes.size() > 1) ? viaNodes.get(viaNodes.size()-2): fn;
+		long uTurnWay = (viaNodes.size() > 1) ? grr.getViaWayIds().get(grr.getViaWayIds().size()-1) : grr.getFromWayId(); 
+		
+		RouteNode tn = null;
+		int toId = 0; 
+		List<RouteArc> toArcs = new ArrayList<>();
+		if (grr.getToNode() != null){ 
+			// polish input data provides id
+			toId = grr.getToNode().getId();
+			tn = nodes.get(toId);
+			if (tn == null ){
+				log.error(sourceDesc, "can't locate 'to' RouteNode with id", toId);
+				return 0; 
+			}
+		} else {
+			// we can have multiple arcs between last via node and to node. The 
+			// arcs can be on the same OSM way or on different OSM ways.
+			// We can have multiple arcs with different RoadDef objects that refer to the same 
+			// OSM way id. The direction indicator tells us what arc is probably meant.
+			List<RouteArc> possibleToArcs = lastViaNode.getDirectArcsOnWay(grr.getToWayId());
+			RouteArc fromArc = fromArcs.get(0);
+
+			boolean ignoreAngle = false;
+			if (fromArc.getLengthInMeter() <= 0.0001)
+				ignoreAngle = true;
+			if (grr.getDirIndicator() == '?')
+				ignoreAngle = true;
+			log.info(sourceDesc, "found", possibleToArcs.size(), "candidates for to-arc");
+
+			// group the available arcs by angle 
+			Map<Integer, List<RouteArc>> angleMap = new TreeMap<>();
+			for (RouteArc arc : possibleToArcs){
+				if (arc.getLengthInMeter() <= 0.0001)
+					ignoreAngle = true;
+				Integer angle = Math.round(getAngle(fromArc, arc));
+				List<RouteArc> list = angleMap.get(angle);
+				if (list == null){
+					list = new ArrayList<>();
+					angleMap.put(angle, list);
+				}
+				list.add(arc);
+			}
+
+			// find the group that fits best 
+			Iterator<Entry<Integer, List<RouteArc>>> iter = angleMap.entrySet().iterator();
+			Integer bestAngle = null;
+			while (iter.hasNext()){
+				Entry<Integer, List<RouteArc>> entry = iter.next();
+				if (ignoreAngle || matchDirectionInfo(entry.getKey(), grr.getDirIndicator()) ){
+					if (bestAngle == null)
+						bestAngle = entry.getKey();
+					else {
+						bestAngle = getBetterAngle(bestAngle, entry.getKey(), grr.getDirIndicator());
+					}
+				}
+			}
+			
+			if (bestAngle == null){
+				log.warn(sourceDesc,"the angle of the from and to way don't match the restriction");
+				return 0;
+			} 
+			toArcs = angleMap.get(bestAngle);
+		}
+		if (toArcs.isEmpty()){
+			log.error(sourceDesc, "can't locate arc from 'via' node ",lastViaId,"to 'to' node",toId,"on way",grr.getToWayId());
+			return 0;
+		}
+		
+		List<RouteArc> badArcs = new ArrayList<>();
+		
+		if (grr.getType() == GeneralRouteRestriction.RestrType.TYPE_NOT){
+			for (RouteArc toArc: toArcs){
+				badArcs.add(toArc);
+			}
+		}
+		else if (grr.getType() == GeneralRouteRestriction.RestrType.TYPE_ONLY){
+			// this is the inverse logic, grr gives the allowed path, we have to find the others
+			int uTurns = 0;
+			
+			for (RouteArc badArc : lastViaNode.arcsIteration()){
+				if (!badArc.isDirect() || toArcs.contains(badArc))
+					continue;
+				if (badArc.getDest() == uTurnNode && badArc.getRoadDef().getId() == uTurnWay){
+					// ignore u-turn
+					++uTurns;
+					continue;
+				}
+				badArcs.add(badArc);
+			}
+			if (badArcs.isEmpty()){
+				if (uTurns > 0)
+					log.warn(sourceDesc, "restriction ignored because it forbids only u-turn");
+				else
+					log.warn(sourceDesc, "restriction ignored because it has no effect");
+				return 0;
+			}
+		}
+		
+		// create all possible paths for which the restriction applies 
+		List<List<RouteArc>> arcLists = new ArrayList<>();
+		arcLists.add(fromArcs);
+		arcLists.addAll(viaArcsList);
+		arcLists.add(badArcs);
+		if (arcLists.size() > MAX_RESTRICTIONS_ARCS){
+			log.warn(sourceDesc, "has more than", MAX_RESTRICTIONS_ARCS, "arcs, this is not supported");
+			return 0;
+		}
+		
+		// remove arcs which cannot be travelled by the vehicles listed in the restriction
+		for (int i = 0; i < arcLists.size(); i++){
+			List<RouteArc> arcs =  arcLists.get(i);
+			int countNoEffect = 0;
+			int countOneway= 0;
+			for (int j = arcs.size()-1; j >= 0; --j){
+				RouteArc arc = arcs.get(j);
+				if (isUsable(arc.getRoadDef().getAccess(), grr.getExceptionMask()) == false){
+					countNoEffect++;
+					arcs.remove(j);
+				}
+				else if (arc.getRoadDef().isOneway()){
+					if (!arc.isForward()){
+						countOneway++;
+						arcs.remove(j);
+					}
+				}
+			}
+			String arcType = null;
+			if (arcs.isEmpty()){
+				if (i == 0)
+					arcType = "from way is";
+				else if (i == arcLists.size()-1){
+					if (grr.getType() == GeneralRouteRestriction.RestrType.TYPE_ONLY)
+						arcType = "all possible other ways are";
+					else 
+						arcType = "to way is";
+				}
+				else 
+					arcType = "via way is";
+				String reason;
+				if (countNoEffect > 0 & countOneway > 0)
+					reason = "wrong direction in oneway or not accessible for restricted vehicles";
+				else if (countNoEffect > 0)
+					reason = "not accessible for restricted vehicles";
+				else 
+					reason = "wrong direction in oneway";
+				log.warn(sourceDesc, "restriction ignored because",arcType,reason);
+				return 0;
+			}
+		}
+		if (viaNodes.contains(fn)){
+			log.warn(sourceDesc, "restriction not written because from node appears also as via node");
+			return 0;
+		}
+		// determine all possible combinations of arcs. In most cases,
+		// this will be 0 or one, but if the style creates multiple roads for one
+		// OSM way, this can be a larger number
+		int numCombis = 1;
+		int [] indexes = new int[arcLists.size()];
+		for (int i = 0; i < indexes.length; i++){
+			List<RouteArc> arcs =  arcLists.get(i);
+			numCombis *= arcs.size();
+		}
+		List<RouteArc> path = new ArrayList<>();
+		int added = 0;
+		for (int i = 0; i < numCombis; i++){
+			for (RouteNode vn : viaNodes){
+				path.clear();
+				boolean viaNodeFound = false;
+				byte pathNoAccessMask = 0;
+				for (int j = 0; j < indexes.length; j++){
+					RouteArc arc = arcLists.get(j).get(indexes[j]);
+					if (arc.getDest() == vn || viaNodeFound == false){
+						arc = arc.getReverseArc();
+					}
+					if (arc.getSource() == vn)
+						viaNodeFound = true;
+					if (arc.getDest() == vn){
+						if (added > 0)
+							log.error(sourceDesc, "restriction incompletely written because dest in arc is via node");
+						else 
+							log.warn(sourceDesc, "restriction not written because dest in arc is via node");
+						return added;
+					}
+					pathNoAccessMask |= ~arc.getRoadDef().getAccess();
+					path.add(arc);
+				}
+				byte pathAccessMask = (byte)~pathNoAccessMask;
+				if (isUsable(pathAccessMask, grr.getExceptionMask())){
+					vn.addRestriction(new RouteRestriction(vn, path, grr.getExceptionMask()));
+					++added;
+				} 
+			}
+			// get next combination of arcs
+			++indexes[indexes.length-1];
+			for (int j = indexes.length-1; j > 0; --j){
+				if (indexes[j] >= arcLists.get(j).size()){
+					indexes[j] = 0;
+					indexes[j-1]++;
+				}
+			}
+		}
+
+		// double check
+		if (indexes[0] != arcLists.get(0).size())
+			log.error(sourceDesc, " failed to generate all possible paths");
+		log.info(sourceDesc, "added",added,"route restriction(s) to img file");
+		return added;
+	}
+
+	/**
+	 * Compare the disallowed vehicles for the path with the exceptions from the restriction
+	 * @param roadNoAccess
+	 * @param exceptionMask
+	 * @return
+	 */
+	private static boolean isUsable(byte roadAccess, byte exceptionMask) {
+		if ((roadAccess & (byte) ~exceptionMask) == 0)
+			return false; // no allowed vehicle is concerned by this restriction
+		return true;
+	}
+
+	private int addNoThroughRoute(GeneralRouteRestriction grr) {
+		assert grr.getViaNodes() != null;
+		assert grr.getViaNodes().size() == 1;
+		int viaId = grr.getViaNodes().get(0).getId();
+		RouteNode vn = nodes.get(viaId);
+		if (vn == null){
+			log.error(grr.getSourceDesc(), "can't locate 'via' RouteNode with id", viaId);
+			return 0;
+		}
+		int added = 0;
+		
+		for (RouteArc out: vn.arcsIteration()){
+			if (!out.isDirect())
+				continue;
+			for (RouteArc in: vn.arcsIteration()){
+				if (!in.isDirect() || in == out || in.getDest() == out.getDest())
+					continue;
+				byte pathAccessMask = (byte) (out.getRoadDef().getAccess() & in.getRoadDef().getAccess());
+				if (isUsable(pathAccessMask, grr.getExceptionMask())){
+					vn.addRestriction(new RouteRestriction(vn, Arrays.asList(in,out), grr.getExceptionMask()));
+					added++;
+				} else {
+					if (log.isDebugEnabled())
+						log.debug(grr.getSourceDesc(),"ignored no-through-route",in,"to",out);
+				}
+			}
+		}
+		return added;
+	}
+	
+	public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) {
+		RouteNode node = nodes.get(junctionNodeId);
+		assert node != null :  "Can't find node with id " + junctionNodeId;
+
+		node.addThroughRoute(roadIdA, roadIdB);
+	}
+	
+	/**
+	 * Calculate the "angle" between to arcs. The arcs may not be connected.
+	 * We do this by "virtually" moving the toArc so that its source 
+	 * node lies on the destination node of the from arc.
+	 * This makes only sense if move is not over a large distance, we assume that this 
+	 * is the case as via ways should be short. 
+	 * @param fromArc arc with from node as source and first via node as destination
+	 * @param toArc arc with last via node as source
+	 * @return angle at in degree [-180;180]
+	 */
+	private static float getAngle(RouteArc fromArc, RouteArc toArc){
+		// note that the values do not depend on the isForward() attribute
+		float headingFrom = fromArc.getFinalHeading();
+		float headingTo = toArc.getInitialHeading();
+		float angle = headingTo - headingFrom;
+		while(angle > 180)
+			angle -= 360;
+		while(angle < -180)
+			angle += 360;
+		return angle;
+	}
+	
+	/**
+	 * Find the angle that comes closer to the direction indicated.
+	 * 
+	 * @param angle1 1st angle -180:180 degrees
+	 * @param angle2 2nd angle -180:180 degrees
+	 * @param dirIndicator l:left, r:right, u:u_turn, s: straight_on
+	 * @return
+	 */
+	private static Integer getBetterAngle (Integer angle1, Integer angle2, char dirIndicator){
+		switch (dirIndicator){
+		case 'l':
+			if (Math.abs(-90-angle2) < Math.abs(-90-angle1))
+				return angle2; // closer to -90
+			break;
+		case 'r':
+			if (Math.abs(90-angle2) < Math.abs(90-angle1))
+				return angle2; // closer to 90
+			break;
+		case 'u': 
+			double d1 = (angle1 < 0 ) ? -180-angle1 : 180-angle1; 
+			double d2 = (angle2 < 0 ) ? -180-angle2 : 180-angle2; 
+			if (Math.abs(d2) < Math.abs(d1))
+				return angle2; // closer to -180
+			break;
+		case 's': 
+			if (Math.abs(angle2) < Math.abs(angle1))
+				return angle2; // closer to 0
+			break;
+		}
+		
+		return angle1;
+	}
+	
+	/**
+	 * Check if angle is in the range indicated by the direction
+	 * @param angle the angle -180:180 degrees
+	 * @param dirIndicator l:left, r:right, u:u_turn, s: straight_on 
+	 * @return
+	 */
+	private static boolean matchDirectionInfo (float angle, char dirIndicator){
+		switch (dirIndicator){
+		case 'l':
+			if (angle < -3 && angle > - 177)
+				return true;
+			break;
+		case 'r':
+			if (angle > 3 && angle < 177)
+				return true;
+			break;
+		case 'u':
+			if (angle < -87 || angle > 93)
+				return true;
+			break;
+		case 's':
+			if (angle > -87 && angle < 87)
+				return true;
+			break;
+		case '?':
+			return true;
+		}
+		return false;
+	}
+	
+	
+}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
index dca1f85..5f0f320 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
@@ -30,7 +30,7 @@ public class RouteArc {
 	private static final Logger log = Logger.getLogger(RouteArc.class);
 	
 	// Flags A
-	private static final int FLAG_NEWDIR = 0x80;
+	private static final int FLAG_HASNET = 0x80;
 	private static final int FLAG_FORWARD = 0x40;
 	private static final int MASK_DESTCLASS = 0x7;
 	public static final int MASK_CURVE_LEN = 0x38;
@@ -41,8 +41,10 @@ public class RouteArc {
 
 	private int offset;
 
-	private int initialHeading; // degrees
-	private final int finalHeading; // degrees
+	// heading / bearing: 
+	private float initialHeading; // degrees (A-> B in an arc ABCD) 
+	private final float finalHeading; // degrees (C-> D in an arc ABCD)
+	private final float directHeading; // degrees (A-> D in an arc ABCD)
 
 	private final RoadDef roadDef;
 
@@ -58,44 +60,80 @@ public class RouteArc {
 	private byte flagA;
 	private byte flagB;
 
-	private boolean haveCurve;
-	private int length;
+	private final boolean haveCurve;
+	private final int length;
+	private final byte lengthRatio;
 	private final int pointsHash;
-	private final boolean curveEnabled;
+	private boolean isForward;
+	private final float lengthInMeter;
+
+	private boolean isDirect;
+
+	// this field may limit the arcs destination class
+	private byte maxDestClass = -1;
+	
+	// pointer to the reverse arc if this is a direct arc
+	private RouteArc reverseArc;
 
 	/**
-	 * Create a new arc.
-	 *
+	 * Create a new arc. An arc can contain multiple points (eg. A->B->C->D->E)
 	 * @param roadDef The road that this arc segment is part of.
-	 * @param source The source node.
-	 * @param dest The destination node.
-	 * @param initialHeading The initial heading (signed degrees)
+	 * @param source The source node. (A)
+	 * @param dest The destination node (E).
+	 * @param initialBearing The initial heading (signed degrees) (A->B)
+	 * @param finalBearing The final heading (signed degrees) (D->E)
+	 * @param directBearing The direct heading (signed degrees) (A->E)
+	 * @param arcLength the length of the arc in meter (A->B->C->D->E)
+	 * @param pathLength the length of the arc in meter (summed length for additional arcs)
+	 * @param directLength the length of the arc in meter (A-E) 
+	 * @param pointsHash
 	 */
 	public RouteArc(RoadDef roadDef,
 					RouteNode source, RouteNode dest,
-					int initialHeading, int finalHeading,
-					double length,
-					boolean curveEnabled,
+					double initialBearing, double finalBearing, double directBearing,
+					double arcLength,
+					double pathLength,
+					double directLength,
 					int pointsHash) {
+		this.isDirect = true; // unless changed
 		this.roadDef = roadDef;
 		this.source = source;
 		this.dest = dest;
-		this.initialHeading = initialHeading;
-		this.finalHeading = finalHeading;
-		this.length = convertMeters(length);
-		this.curveEnabled = curveEnabled;
+		this.initialHeading = (float) initialBearing;
+		this.finalHeading = (float) finalBearing;
+		this.directHeading = (directBearing < 180) ? (float) directBearing : -180.0f;
+		int len = NODHeader.metersToRaw(arcLength);
+		if (len >= (1 << 22)) {
+			log.error("Way " + roadDef.getName() + " (id " + roadDef.getId() + ") contains an arc whose length (" + len + " units) is too big to be encoded, using length",((1 << 22) - 1));
+			len = (1 << 22) - 1;
+		}
+		this.length = len;
+		this.lengthInMeter = (float) arcLength;
 		this.pointsHash = pointsHash;
+		// calculate length ratio between way length on road and direct distance between end points
+		int ratio = 0;
+		int pathEncoded = NODHeader.metersToRaw(pathLength);
+		if (pathEncoded >= 2){
+			int directEncoded = NODHeader.metersToRaw(directLength);
+			if (pathEncoded > directEncoded){
+				ratio = (int) Math.min(31, Math.round(32.0d * directLength/ pathLength));
+			}
+		}
+		if (ratio == 0 && len >= (1 << 14))
+			ratio = 0x1f; // force encoding of curve info for very long arcs
+		lengthRatio = (byte)ratio;
+		haveCurve = lengthRatio > 0;
 	}
 
-	public int getInitialHeading() {
+	public float getInitialHeading() {
 		return initialHeading;
 	}
 
-	public void setInitialHeading(int ih) {
+	public void setInitialHeading(float ih) {
 		initialHeading = ih;
 	}
 
-	public int getFinalHeading() {
+	public float getFinalHeading() {
 		return finalHeading;
 	}
 
@@ -121,7 +159,7 @@ public class RouteArc {
 	public int boundSize() {
 
 		int[] lendat = encodeLength();
-
+		//TODO: take into account that initialHeading and indexA are not always written
 		// 1 (flagA) + 1-2 (offset) + 1 (indexA) + 1 (initialHeading)
 		int size = 5 + lendat.length;
 		if(haveCurve)
@@ -174,52 +212,79 @@ public class RouteArc {
 	 *
 	 * Required for writing restrictions (Table C).
 	 */
-	public byte getIndexB() {
-		return indexB;
+	public int getIndexB() {
+		return indexB & 0xff;
 	}
 	 
-
-	// units of 16 feet
-	final static double LENGTH_FACTOR = 3.2808 / 16;
-	private static int convertMeters(double l) {
-		return (int) (l * LENGTH_FACTOR);
+	public int getArcDestClass(){
+		return Math.min(getRoadDef().getRoadClass(), dest.getGroup());
+	}
+	
+	public float getLengthInMeter(){
+		return lengthInMeter;
+	}
+	
+	public static byte directionFromDegrees(double dir) {
+		return (byte) Math.round(dir * 256.0 / 360) ;
 	}
 
-	public void write(ImgFileWriter writer) {
+	public void write(ImgFileWriter writer, RouteArc lastArc, boolean useCompactDirs, Byte compactedDir) {
+		boolean first = lastArc == null;
+		if (first){
+			if (useCompactDirs)
+				flagA |= FLAG_HASNET; // first arc: the 1 tells us that initial directions are in compacted format
+		} else {
+			if (lastArc.getRoadDef() != this.getRoadDef())
+				flagA |= FLAG_HASNET; // not first arc: the 1 tells us that we have to read table A 
+			
+		}
+
+		if (isForward)
+			flagA |= FLAG_FORWARD;
+		
 		offset = writer.position();
 		if(log.isDebugEnabled())
 			log.debug("writing arc at", offset, ", flagA=", Integer.toHexString(flagA));
 
 		// fetch destination class -- will have been set correctly by now
-		setDestinationClass(dest.getNodeClass());
+		setDestinationClass(getArcDestClass());
 
 		// determine how to write length and curve bit
 		int[] lendat = encodeLength();
 
 		writer.put(flagA);
-
 		if (isInternal()) {
 			// space for 14 bit node offset, written in writeSecond.
 			writer.put(flagB);
 			writer.put((byte) 0);
 		} else {
-			if(indexB >= 0x3f) {
+			if(indexB < 0 || indexB >= 0x3f) {
 				writer.put((byte) (flagB | 0x3f));
 				writer.put(indexB);
 			}
 			else
 				writer.put((byte) (flagB | indexB));
 		}
-
-		writer.put(indexA);
+		
+		 // only write out the local net index if it is the first arc or else if newDir is set.
+		if (first || lastArc.indexA != this.indexA)
+			writer.put(indexA);
 
 		if(log.isDebugEnabled())
 			log.debug("writing length", length);
 		for (int aLendat : lendat)
 			writer.put((byte) aLendat);
 
-		writer.put((byte)(256 * initialHeading / 360));
-
+		if (first || lastArc.indexA != this.indexA || lastArc.isForward != isForward){
+			if (useCompactDirs){
+				// determine if we have to write direction info
+				if (compactedDir != null)
+					writer.put(compactedDir);
+			} else 
+				writer.put(directionFromDegrees(initialHeading));
+		} else {
+//			System.out.println("skipped writing of initial dir");
+		}
 		if (haveCurve) {
 			int[] curvedat = encodeCurve();
 			for (int aCurvedat : curvedat)
@@ -258,81 +323,76 @@ public class RouteArc {
 	 * There's even more different encodings supposedly.
 	 */
 	private int[] encodeLength() {
-
-		// update haveCurve
-		haveCurve = (curveEnabled && finalHeading != initialHeading);
-
-		if (length >= (1 << 22)) {
-			log.error("Way " + roadDef.getName() + " (id " + roadDef.getId() + ") contains an arc whose length (" + length + " units) is too big to be encoded so the way might not be routable");
-			length = (1 << 22) - 1;
-		}
-
-		// clear existing bits in case length or final heading have
-		// been changed
-		flagA &= ~0x38;
 		int[] lendat;
-		if(length < 0x200) {
-			// 9 bit length optional curve
+		if(length < 0x300 || (length < 0x400 && haveCurve == false)) {
+			// 10 bit length optional curve
+			// clear bits 
+			flagA &= ~0x38;
+			
 			if(haveCurve)
 				flagA |= 0x20;
-			flagA |= (length >> 5) & 0x08; // top bit of length
+			flagA |= (length >> 5) & 0x18; // top two bits of length (at least one must be zero)
 			lendat = new int[1];		   // one byte of data
 			lendat[0] = length;			   // bottom 8 bits of length
-		} 
-		else if(length >= (1 << 14)) {
-			// 22 bit length with curve
-			flagA |= 0x38;
-			lendat = new int[3];		   // three bytes of data
-			lendat[0] = 0xC0 | (length & 0x3f); // 0x80 set, 0x40 set, 6 low bits of length
-			lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
-			lendat[2] = (length >> 14) & 0xff; // 8 more bits of length
-		}
-
-		else if(haveCurve) {
-			// 15 bit length with curve
-			flagA |= 0x38;				 // all three bits set
-			lendat = new int[2];		 // two bytes of data
-			lendat[0] = (length & 0x7f); // 0x80 not set, 7 low bits of length
-			lendat[1] = (length >> 7) & 0xff; // 8 more bits of length
-		}
-		else {
-			// 14 bit length no curve
+			assert (flagA & 0x38) != 0x38;
+		} else {
 			flagA |= 0x38;		 // all three bits set
-			lendat = new int[2]; // two bytes of data
-			lendat[0] = 0x80 | (length & 0x3f); // 0x80 set, 0x40 not set, 6 low bits of length
-			lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
+			if(length >= (1 << 14)) {
+				// 22 bit length with curve
+				lendat = new int[3];		   // three bytes of data
+				lendat[0] = 0xC0 | (length & 0x3f); // 0x80 set, 0x40 set, 6 low bits of length
+				lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
+				lendat[2] = (length >> 14) & 0xff; // 8 more bits of length
+			}
+			else if(haveCurve) {
+				// 15 bit length with curve
+				lendat = new int[2];		 // two bytes of data
+				lendat[0] = (length & 0x7f); // 0x80 not set, 7 low bits of length
+				lendat[1] = (length >> 7) & 0xff; // 8 more bits of length
+			}
+			else {
+				// 14 bit length no curve
+				lendat = new int[2]; // two bytes of data
+				lendat[0] = 0x80 | (length & 0x3f); // 0x80 set, 0x40 not set, 6 low bits of length
+				lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
+			}
 		}
-
 		return lendat;
 	}
 
 	/**
 	 * Encode the curve data into a sequence of bytes.
-	 *
-	 * 1 or 2 bytes show up in practice, but they're not at
-	 * all well understood yet.
+	 * Curve data contains a ratio between arc length and direct distance
+	 * and the direct bearing. This is typically encode in one byte, 
+	 * for extreme ratios two bytes are used.
 	 */
 	private int[] encodeCurve() {
-		// most examples of curve data are a single byte that encodes
-		// the final heading of the arc. The bits appear to be
-		// reorganised into the order 21076543 (i.e. the top 5 bits
-		// are shifted down to the bottom).  Unfortunately, it's not
-		// that simple because sometimes the curve is encoded using 2
-		// bytes. The presence of the 2nd byte is indicated by the top
-		// 3 bits of the first byte all being zero. As the encoding of
-		// the 2-byte variant is not yet understood, for the moment,
-		// if the resulting value would have the top 3 bits all zero,
-		// we set what we hope is the LSB so that it becomes valid
-		// 1-byte curve data
-		int heading = 256 * finalHeading / 360;
-		int encodedHeading = ((heading & 0xf8) >> 3) | ((heading & 0x07) << 5);
-		if((encodedHeading & 0xe0) == 0) {
-			// hack - set a bit (hopefully, the LSB) to force 1-byte
-			// encoding
-			encodedHeading |= 0x20;
+		assert lengthRatio != 0;
+		int[] curveData;
+		int dh = directionFromDegrees(directHeading);
+		if (lengthRatio >= 1 && lengthRatio <= 17) {
+			// two byte curve data neeeded
+			curveData = new int[2];
+			curveData[0] = lengthRatio;
+			curveData[1] = dh;
+			
+		} else {
+			// use compacted form
+			int compactedRatio = lengthRatio / 2 - 8;
+			assert compactedRatio > 0 && compactedRatio < 8;
+			curveData = new int[1];
+			curveData[0] = (compactedRatio << 5) | ((dh >> 3) & 0x1f);
+			
+			/* check math:
+			int dhx = curveData[0] & 0x1f;
+			int decodedDirectHeading = (dhx <16) ?  dhx << 3 : -(256 - (dhx<<3));
+			if ((byte) (dh & 0xfffffff8) != (byte) decodedDirectHeading)
+				log.error("failed to encode direct heading", directHeading, dh, decodedDirectHeading);
+			int ratio = (curveData[0] & 0xe0) >> 5;
+			if (ratio != compactedRatio)
+				log.error("failed to encode length ratio", lengthRatio, compactedRatio, ratio);
+			 */
 		}
-		int[] curveData = new int[1];
-		curveData[0] = encodedHeading;
 		return curveData;
 	}
 
@@ -340,16 +400,12 @@ public class RouteArc {
 		return roadDef;
 	}
 
-	public void setNewDir() {
-		flagA |= FLAG_NEWDIR;
-	}
-
 	public void setForward() {
-		flagA |= FLAG_FORWARD;
+		isForward = true;
 	}
 
 	public boolean isForward() {
-		return (flagA & FLAG_FORWARD) != 0;
+		return isForward;
 	}
 
 	public void setLast() {
@@ -361,4 +417,34 @@ public class RouteArc {
 			log.debug("setting destination class", destinationClass);
 		flagA |= (destinationClass & MASK_DESTCLASS);
 	}
+
+	public void setIndirect() {
+		this.isDirect = false;
+		
+	}
+
+	public boolean isDirect() {
+		return isDirect;
+	}
+
+	public void setMaxDestClass(int destClass) {
+		if (this.maxDestClass < 0)
+			this.maxDestClass = (byte) destClass;
+		else if (destClass < maxDestClass)
+			maxDestClass = (byte)destClass;
+	}
+
+	@Override
+	public String toString() {
+		return "RouteArc [" + (isForward ? "->":"<-") + (isDirect ? "direct":"indirect")
+				+ roadDef +  " " + dest + "]";
+	}
+
+	public RouteArc getReverseArc() {
+		return reverseArc;
+	}
+
+	public void setReverseArc(RouteArc reverseArc) {
+		this.reverseArc = reverseArc;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java b/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
index 84ea542..9aa52cc 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
@@ -52,41 +52,68 @@ public class RouteCenter {
 		log.info("new RouteCenter at " + centralPoint.toDegreeString() +
 				 ", nodes: " + nodes.size()	+ " tabA: " + tabA.size() +
 				 " tabB: " + tabB.size());
+	}
 
-		// update lat/lon offsets; update arcs with table indices; populate tabC
+	/**
+	 * update arcs with table indices; populate tabC
+	 */
+	private void updateOffsets(){
 		for (RouteNode node : nodes) {
 			node.setOffsets(centralPoint);
 			for (RouteArc arc : node.arcsIteration()) {
 				arc.setIndexA(tabA.getIndex(arc));
+				arc.setInternal(nodes.contains(arc.getDest()));
 				if (!arc.isInternal())
 					arc.setIndexB(tabB.getIndex(arc.getDest()));
 			}
-			for (RouteRestriction restr : node.getRestrictions())
+
+			for (RouteRestriction restr : node.getRestrictions()){
+				if (restr.getArcs().size() >= 3){
+					// only restrictions with more than 2 arcs can contain further arcs 
+					for (RouteArc arc : restr.getArcs()){
+						if (arc.getSource() == node)
+							continue;
+						arc.setIndexA(tabA.getIndex(arc));
+						arc.setInternal(nodes.contains(arc.getDest()));
+						if (!arc.isInternal())
+							arc.setIndexB(tabB.getIndex(arc.getDest()));
+					}
+				}
 				restr.setOffsetC(tabC.addRestriction(restr));
+			}
 		}
 		// update size of tabC offsets, now that tabC has been populated
 		tabC.propagateSizeBytes();
 	}
-
+	
 	/**
 	 * Write a route center.
 	 *
 	 * writer.position() is relative to the start of NOD 1.
 	 * Space for Table A is reserved but not written. See writeTableA.
 	 */
-	public void write(ImgFileWriter writer) {
+	public void write(ImgFileWriter writer, int[] classBoundaries) {
 		assert !nodes.isEmpty(): "RouteCenter without nodes";
-
-		for (RouteNode node : nodes)
+		updateOffsets();
+		int centerPos = writer.position();
+		for (RouteNode node : nodes){
 			node.write(writer);
+			int group = node.getGroup();
+			if (group == 0)
+				continue;
+			if (centerPos < classBoundaries[group-1]){
+				// update positions (loop is used because style might not use all classes  
+				for (int i = group-1; i >= 0; i--){
+					if (centerPos < classBoundaries[i] )
+						classBoundaries[i] = centerPos;
+				}
+			}
+		}
+		int alignment = 1 << NODHeader.DEF_ALIGN;
+		int alignMask = alignment - 1;
 
-		int mult = 1 << NODHeader.DEF_ALIGN;
-
-		// Get the position of the tables, and position there.
-		int roundpos = (writer.position() + mult - 1) 
-					>> NODHeader.DEF_ALIGN
-					<< NODHeader.DEF_ALIGN;
-		int tablesOffset = roundpos + mult;
+		// Calculate the position of the tables.
+		int tablesOffset = (writer.position() + alignment) & ~alignMask;
 		log.debug("write table a at offset", Integer.toHexString(tablesOffset));
 
 		// Go back and fill in all the table offsets
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
index 23370de..026d7e2 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
@@ -14,12 +14,13 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import it.unimi.dsi.fastutil.ints.IntArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
@@ -44,11 +45,11 @@ public class RouteNode implements Comparable<RouteNode> {
 	 */
 
 	// Values for the first flag byte at offset 1
+	private static final int MAX_DEST_CLASS_MASK = 0x07;
 	private static final int F_BOUNDARY = 0x08;
 	private static final int F_RESTRICTIONS = 0x10;
 	private static final int F_LARGE_OFFSETS = 0x20;
 	private static final int F_ARCS = 0x40;
-	private static final int F_UNK_NEEDED = 0x04; // XXX
 	// only used internally in mkgmap
 	private static final int F_DISCARDED = 0x100; // node has been discarded
 
@@ -61,21 +62,21 @@ public class RouteNode implements Comparable<RouteNode> {
 	// arcs from this node
 	private final List<RouteArc> arcs = new ArrayList<RouteArc>(4);
 	// restrictions at (via) this node
-	private final List<RouteRestriction> restrictions = new ArrayList<RouteRestriction>(4);
-	// arcs to this node
-	private final List<RouteArc> incomingArcs = new ArrayList<RouteArc>(4);
+	private final List<RouteRestriction> restrictions = new ArrayList<RouteRestriction>();
 
-	private int flags = F_UNK_NEEDED;
+	private int flags;
 
 	private final CoordNode coord;
 	private char latOff;
 	private char lonOff;
 	private List<RouteArc[]> throughRoutes;
 
-	// this is for setting destination class on arcs
-	// we're taking the maximum of roads this node is
-	// on for now -- unsure of precise mechanic
-	private int nodeClass;
+	// contains the maximum of roads this node is on, written with the flags
+	// field. It is also used for the calculation of the destination class on
+	// arcs.
+	private byte nodeClass;
+	
+	private byte nodeGroup = -1;
 
 	public RouteNode(Coord coord) {
 		this.coord = (CoordNode) coord;
@@ -98,10 +99,8 @@ public class RouteNode implements Comparable<RouteNode> {
 	}
 
 	public void addArc(RouteArc arc) {
-		if (!arcs.isEmpty())
-			arc.setNewDir();
 		arcs.add(arc);
-		int cl = arc.getRoadDef().getRoadClass();
+		byte cl = (byte) arc.getRoadDef().getRoadClass();
 		if(log.isDebugEnabled())
 			log.debug("adding arc", arc.getRoadDef(), cl);
 		if (cl > nodeClass)
@@ -109,23 +108,60 @@ public class RouteNode implements Comparable<RouteNode> {
 		flags |= F_ARCS;
 	}
 
-	public void addIncomingArc(RouteArc arc) {
-		incomingArcs.add(arc);
-	}
-
 	public void addRestriction(RouteRestriction restr) {
 		restrictions.add(restr);
 		flags |= F_RESTRICTIONS;
 	}
 
-	public RouteArc getArcTo(RouteNode otherNode) {
-		for(RouteArc a : arcs)
-			if(a.getDest() == otherNode)
-				return a;
+	/**
+	 * get all direct arcs to the given node and the given way id
+	 * @param otherNode
+	 * @param roadId
+	 * @return
+	 */
+	public List<RouteArc> getDirectArcsTo(RouteNode otherNode, long roadId) {
+		List<RouteArc> result = new ArrayList<>();
+		for(RouteArc a : arcs){
+			if(a.isDirect() && a.getDest() == otherNode){
+				if(a.getRoadDef().getId() == roadId)
+					result.add(a);
+			}
+		}
+		return result;
+	}
 
+	/**
+	 * get all direct arcs on a given way id  
+	 * @param roadId
+	 * @return
+	 */
+	public List<RouteArc> getDirectArcsOnWay(long roadId) {
+		List<RouteArc> result = new ArrayList<>();
+		for(RouteArc a : arcs){
+			if(a.isDirect()){
+				if(a.getRoadDef().getId() == roadId)
+					result.add(a);
+			}
+		}
+		return result;
+	}
+	
+	/**
+	 * Find arc to given node on given road. 
+	 * @param otherNode
+	 * @param roadDef
+	 * @return
+	 */
+	public RouteArc getDirectArcTo(RouteNode otherNode, RoadDef roadDef) {
+		for(RouteArc a : arcs){
+			if(a.isDirect() && a.getDest() == otherNode){
+				if(a.getRoadDef()== roadDef)
+					return a;
+			}
+		}
 		return null;
 	}
-
+	
 	/**
 	 * Provide an upper bound to the size (in bytes) that
 	 * writing this node will take.
@@ -166,6 +202,7 @@ public class RouteNode implements Comparable<RouteNode> {
 		assert (flags & F_DISCARDED) == 0 : "attempt to write discarded node";
 
 		writer.put((byte) 0);  // will be overwritten later
+		flags |= (nodeClass & MAX_DEST_CLASS_MASK); // max. road class of any outgoing road
 		writer.put((byte) flags);
 
 		if (haveLargeOffsets()) {
@@ -175,9 +212,40 @@ public class RouteNode implements Comparable<RouteNode> {
 		}
 
 		if (!arcs.isEmpty()) {
+			boolean useCompactDirs = true;
+			IntArrayList initialHeadings = new IntArrayList(arcs.size()+1);
+			RouteArc lastArc = null;
+			for (RouteArc arc: arcs){
+				if (lastArc == null || lastArc.getIndexA() != arc.getIndexA() || lastArc.isForward() != arc.isForward()){
+					int dir = RouteArc.directionFromDegrees(arc.getInitialHeading());
+					dir = dir & 0xf0;
+					if (initialHeadings.contains(dir)){
+						useCompactDirs = false;
+						break;
+					}
+					initialHeadings.add(dir);
+				} else {
+					// 
+				}
+				lastArc = arc;
+			}
+			initialHeadings.add(0); // add dummy 0 so that we don't have to check for existence
 			arcs.get(arcs.size() - 1).setLast();
-			for (RouteArc arc : arcs)
-				arc.write(writer);
+			lastArc = null;
+			
+			int index = 0;
+			for (RouteArc arc: arcs){
+				Byte compactedDir = null;
+				if (useCompactDirs){
+					if (lastArc == null || lastArc.getIndexA() != arc.getIndexA() || lastArc.isForward() != arc.isForward()){
+						if (index % 2 == 0)
+							compactedDir = (byte) ((initialHeadings.get(index) >> 4) | initialHeadings.getInt(index+1));
+						index++;
+					}
+				}
+				arc.write(writer, lastArc, useCompactDirs, compactedDir);
+				lastArc = arc;
+			}
 		}
 
 		if (!restrictions.isEmpty()) {
@@ -188,13 +256,13 @@ public class RouteNode implements Comparable<RouteNode> {
 	}
 
 	/**
-	 * Writes a nod3 entry.
+	 * Writes a nod3 /nod4 entry.
 	 */
-	public void writeNod3(ImgFileWriter writer) {
+	public void writeNod3OrNod4(ImgFileWriter writer) {
 		assert isBoundary() : "trying to write nod3 for non-boundary node";
 
 		writer.put3(coord.getLongitude());
-		writer.put3(coord.getLatitude() + 0x800000); // + 180 degrees
+		writer.put3(coord.getLatitude()); 
 		writer.put3(offsetNod1);
 	}
 
@@ -301,7 +369,7 @@ public class RouteNode implements Comparable<RouteNode> {
 					if(labb != null && labb.getOffset() != 0) {
 						bothArcsNamed = true;
 						if(laba.equals(labb)) {
-							// the roads have the same name
+							// the roads have the same label
 							if(rda.isLinkRoad() == rdb.isLinkRoad()) {
 								// if both are a link road or both are
 								// not a link road, consider them the
@@ -335,7 +403,7 @@ public class RouteNode implements Comparable<RouteNode> {
 		return false;
 	}
 
-	private static boolean rightTurnRequired(int inHeading, int outHeading, int sideHeading) {
+	private static boolean rightTurnRequired(float inHeading, float outHeading, float otherHeading) {
 		// given the headings of the incoming, outgoing and side
 		// roads, decide whether a side road is to the left or the
 		// right of the main road
@@ -346,13 +414,13 @@ public class RouteNode implements Comparable<RouteNode> {
 		while(outHeading > 180)
 			outHeading -= 360;
 
-		sideHeading -= inHeading;
-		while(sideHeading < -180)
-			sideHeading += 360;
-		while(sideHeading > 180)
-			sideHeading -= 360;
+		otherHeading -= inHeading;
+		while(otherHeading < -180)
+			otherHeading += 360;
+		while(otherHeading > 180)
+			otherHeading -= 360;
 
-		return sideHeading > outHeading;
+		return otherHeading > outHeading;
 	}
 
 	private static final int ATH_OUTGOING = 1;
@@ -391,7 +459,11 @@ public class RouteNode implements Comparable<RouteNode> {
 			List<RouteArc> outgoingArcs = new ArrayList<RouteArc>();
 
 			// sort incoming arcs by decreasing class/speed
-			List<RouteArc> inArcs = new ArrayList<RouteArc>(incomingArcs);
+			List<RouteArc> inArcs = new ArrayList<RouteArc>();
+			for (RouteArc arc : arcs){
+				if (arc.isDirect())
+					inArcs.add(arc.getReverseArc());
+			}
 
 			Collections.sort(inArcs, new Comparator<RouteArc>() {
 					public int compare(RouteArc ra1, RouteArc ra2) {
@@ -414,7 +486,7 @@ public class RouteNode implements Comparable<RouteNode> {
 					continue;
 				}
 
-				int inHeading = inArc.getFinalHeading();
+				float inHeading = inArc.getFinalHeading();
 				// determine the outgoing arc that is likely to be the
 				// same road as the incoming arc
 				RouteArc outArc = null;
@@ -434,6 +506,8 @@ public class RouteNode implements Comparable<RouteNode> {
 					// next, if oa has the same RoadDef as inArc, it's
 					// definitely the same road
 					for(RouteArc oa : arcs) {
+						if (oa.isDirect() == false)
+							continue;
 						if(oa.getDest() != inArc.getSource()) {
 							// this arc is not going to the same node as
 							// inArc came from
@@ -450,6 +524,8 @@ public class RouteNode implements Comparable<RouteNode> {
 					// possiblySameRoad() to see if the roads' id or
 					// labels (names/refs) match
 					for(RouteArc oa : arcs) {
+						if (oa.isDirect() == false)
+							continue;
 						if(oa.getDest() != inArc.getSource()) {
 							// this arc is not going to the same node as
 							// inArc came from
@@ -462,41 +538,6 @@ public class RouteNode implements Comparable<RouteNode> {
 					}
 				}
 
-				if(false && outArc == null) {
-					// last ditch attempt to find the outgoing arc -
-					// try and find a single arc that has the same
-					// road class and speed as the incoming arc
-					int inArcClass = inArc.getRoadDef().getRoadClass();
-					int inArcSpeed = inArc.getRoadDef().getRoadSpeed();
-					for(RouteArc oa : arcs) {
-						if(oa.getDest() != inArc.getSource() &&
-						   oa.getRoadDef().getRoadClass() == inArcClass &&
-						   oa.getRoadDef().getRoadSpeed() == inArcSpeed) {
-							if(outArc != null) {
-								// multiple arcs have the same road
-								// class/speed as the incoming arc so
-								// don't use any of them as the
-								// outgoing arc
-								outArc = null;
-								break;
-							}
-							// oa has the same class/speed as inArc,
-							// now check that oa is not part of
-							// another road by matching names rather
-							// than class/speed because they could be
-							// different
-							boolean paired = false;
-							for(RouteArc z : arcs)
-								if(z != oa && possiblySameRoad(z, oa))
-									paired = true;
-							if(!paired)
-								outArc = oa;
-						}
-					}
-					if(outArc != null)
-						log.info("Matched outgoing arc " + outArc.getRoadDef() + " to " + inRoadDef + " using road class (" + inArcClass + ") and speed (" + inArcSpeed + ") at " + coord.toOSMURL()); 
-				}
-
 				// if we did not find the outgoing arc, give up with
 				// this incoming arc
 				if(outArc == null) {
@@ -507,8 +548,8 @@ public class RouteNode implements Comparable<RouteNode> {
 				// remember that this arc is an outgoing arc
 				outgoingArcs.add(outArc);
 
-				int outHeading = outArc.getInitialHeading();
-				int mainHeadingDelta = outHeading - inHeading;
+				float outHeading = outArc.getInitialHeading();
+				float mainHeadingDelta = outHeading - inHeading;
 				while(mainHeadingDelta > 180)
 					mainHeadingDelta -= 360;
 				while(mainHeadingDelta < -180)
@@ -523,6 +564,8 @@ public class RouteNode implements Comparable<RouteNode> {
 				}
 
 				for(RouteArc otherArc : arcs) {
+					if (otherArc.isDirect() == false)
+						continue;
 
 					// for each other arc leaving this node, tweeze
 					// its heading if its heading change from the
@@ -561,19 +604,19 @@ public class RouteNode implements Comparable<RouteNode> {
 						continue;
 					}
 
-					int otherHeading = otherArc.getInitialHeading();
-					int outToOtherDelta = otherHeading - outHeading;
+					float otherHeading = otherArc.getInitialHeading();
+					float outToOtherDelta = otherHeading - outHeading;
 					while(outToOtherDelta > 180)
 						outToOtherDelta -= 360;
 					while(outToOtherDelta < -180)
 						outToOtherDelta += 360;
-					int inToOtherDelta = otherHeading - inHeading;
+					float inToOtherDelta = otherHeading - inHeading;
 					while(inToOtherDelta > 180)
 						inToOtherDelta -= 360;
 					while(inToOtherDelta < -180)
 						inToOtherDelta += 360;
 
-					int newHeading = otherHeading;
+					float newHeading = otherHeading;
 					if(rightTurnRequired(inHeading, outHeading, otherHeading)) {
 						// side road to the right
 						if((mask & ATH_OUTGOING) != 0 &&
@@ -581,7 +624,7 @@ public class RouteNode implements Comparable<RouteNode> {
 							newHeading = outHeading + MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
 						if((mask & ATH_INCOMING) != 0 &&
 						   Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
-							int nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
+							float nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
 							if(nh > newHeading)
 								newHeading = nh;
 						}
@@ -596,7 +639,7 @@ public class RouteNode implements Comparable<RouteNode> {
 							newHeading = outHeading - MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
 						if((mask & ATH_INCOMING) != 0 &&
 						   Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
-							int nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
+							float nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
 							if(nh < newHeading)
 								newHeading = nh;
 						}
@@ -604,7 +647,7 @@ public class RouteNode implements Comparable<RouteNode> {
 						if(newHeading < -180)
 							newHeading += 360;
 					}
-					if(newHeading != otherHeading) {
+					if(Math.abs(newHeading - otherHeading) > 0.0000001) {
 						otherArc.setInitialHeading(newHeading);
 						log.info("Adjusting turn heading from " + otherHeading + " to " + newHeading + " at junction of " + inRoadDef + " and " + otherArc.getRoadDef() + " at " + coord.toOSMURL());
 					}
@@ -619,7 +662,7 @@ public class RouteNode implements Comparable<RouteNode> {
 
 		for(RouteArc a : arcs) {
 			// ignore ways that have been synthesised by mkgmap
-			if(!a.getRoadDef().isSynthesised() &&
+			if(!a.getRoadDef().isSynthesised() && a.isDirect() && 
 			   a.getRoadDef().isRoundabout()) {
 				roundaboutArcs.add(a);
 			}
@@ -634,10 +677,10 @@ public class RouteNode implements Comparable<RouteNode> {
 
 		if(roundaboutArcs.size() > 2) {
 			for(RouteArc fa : arcs) {
-				if(fa.isForward()) {
+				if(fa.isForward() && fa.isDirect()) {
 					RoadDef rd = fa.getRoadDef();
 					for(RouteArc fb : arcs) {
-						if(fb != fa &&
+						if(fb != fa && fb.isDirect() && 
 						   fa.getPointsHash() == fb.getPointsHash() &&
 						   ((fb.isForward() && fb.getDest() == fa.getDest()) ||
 							(!fb.isForward() && fb.getSource() == fa.getDest()))) {
@@ -657,7 +700,7 @@ public class RouteNode implements Comparable<RouteNode> {
 	}
 
 	// determine "distance" between two nodes on a roundabout
-	private int roundaboutSegmentLength(final RouteNode n1, final RouteNode n2) {
+	private static int roundaboutSegmentLength(final RouteNode n1, final RouteNode n2) {
 		List<RouteNode> seen = new ArrayList<RouteNode>();
 		int len = 0;
 		RouteNode n = n1;
@@ -692,7 +735,7 @@ public class RouteNode implements Comparable<RouteNode> {
 		for(RouteArc r : arcs) {
 			// see if node has a forward arc that is part of a
 			// roundabout
-			if(!r.isForward() || !r.getRoadDef().isRoundabout() || r.getRoadDef().isSynthesised())
+			if(!r.isForward() || !r.isDirect() || !r.getRoadDef().isRoundabout() || r.getRoadDef().isSynthesised())
 				continue;
 
 			// follow the arc to find the first node that connects the
@@ -715,6 +758,8 @@ public class RouteNode implements Comparable<RouteNode> {
 				boolean connectsToNonRoundaboutSegment = false;
 				RouteArc nextRoundaboutArc = null;
 				for (RouteArc nba : nb.arcs) {
+					if (nba.isDirect() == false)
+						continue;
 					if (!nba.getRoadDef().isSynthesised()) {
 						if (nba.getRoadDef().isRoundabout()) {
 							if (nba.isForward())
@@ -747,11 +792,11 @@ public class RouteNode implements Comparable<RouteNode> {
 			// triangular "flare" connected to both ends of the
 			// roundabout segment
 			for(RouteArc fa : arcs) {
-				if(!fa.getRoadDef().doFlareCheck())
+				if(!fa.isDirect() || !fa.getRoadDef().doFlareCheck())
 					continue;
 
 				for(RouteArc fb : nb.arcs) {
-					if(!fb.getRoadDef().doFlareCheck())
+					if(!fb.isDirect() || !fb.getRoadDef().doFlareCheck())
 						continue;
 					if(fa.getDest() == fb.getDest()) {
 						// found the 3rd point of the triangle that
@@ -798,7 +843,7 @@ public class RouteNode implements Comparable<RouteNode> {
 							// check that the flare road arcs are not
 							// part of a longer way
 							for(RouteArc a : fa.getDest().arcs) {
-								if(a.getDest() != this && a.getDest() != nb) {
+								if(a.isDirect() && a.getDest() != this && a.getDest() != nb) {
 									if(a.getRoadDef() == fa.getRoadDef())
 										log.warn("Outgoing roundabout flare road " + fb.getRoadDef() + " does not finish at flare? " + fa.getDest().coord.toOSMURL());
 									else if(a.getRoadDef() == fb.getRoadDef())
@@ -830,6 +875,27 @@ public class RouteNode implements Comparable<RouteNode> {
 		if(throughRoutes == null)
 			throughRoutes = new ArrayList<RouteArc[]>();
 		boolean success = false;
+		for(RouteArc arc1 : arcs) {
+			if(arc1.getRoadDef().getId() == roadIdA) {
+				for(RouteArc arc2 : arcs) {
+					if(arc2.getRoadDef().getId() == roadIdB) {
+						throughRoutes.add(new RouteArc[] { arc1.getReverseArc(), arc2 });
+						success = true;
+						break;
+					}
+				}
+			}
+			else if(arc1.getRoadDef().getId() == roadIdB) {
+				for(RouteArc arc2 : arcs) {
+					if(arc2.getRoadDef().getId() == roadIdA) {
+						throughRoutes.add(new RouteArc[] { arc1.getReverseArc(), arc2 });
+						success = true;
+						break;
+					}
+				}
+			}
+		}
+		/*
 		for(RouteArc arc1 : incomingArcs) {
 			if(arc1.getRoadDef().getId() == roadIdA) {
 				for(RouteArc arc2 : arcs) {
@@ -850,9 +916,199 @@ public class RouteNode implements Comparable<RouteNode> {
 				}
 			}
 		}
+		*/
 		if(success)
 			log.info("Added through route between ways " + roadIdA + " and " + roadIdB + " at " + coord.toOSMURL());
 		else
 			log.warn("Failed to add through route between ways " + roadIdA + " and " + roadIdB + " at " + coord.toOSMURL() + " - perhaps they don't meet here?");
 	}
+
+	/**
+	 * For each arc on the road, check if we can add indirect arcs to 
+	 * other nodes of the same road. This is done if the other node
+	 * lies on a different road with a higher road class than the
+	 * highest other road of the target node of the arc. We do this
+	 * for both forward and reverse arcs. Multiple indirect arcs
+	 * may be added for each Node. An indirect arc will 
+	 * always point to a higher road than the previous arc.
+	 * The length and direct bearing of the additional arc is measured 
+	 * from the target node of the preceding arc to the new target node.
+	 * The initial bearing doesn't really matter as it is not written
+	 * for indirect arcs.  
+	 * @param road
+	 * @param maxRoadClass
+	 */
+	public void addArcsToMajorRoads(RoadDef road){
+		assert road.getNode() == this;
+		RouteNode current = this;
+		// the nodes of this road
+		List<RouteNode> nodes = new ArrayList<>();
+		// the forward arcs of this road
+		List<RouteArc> forwardArcs = new ArrayList<>();
+		// will contain the highest other road of each node 
+		IntArrayList forwardArcPositions = new IntArrayList();
+		List<RouteArc> reverseArcs = new ArrayList<>();
+		IntArrayList reverseArcPositions = new IntArrayList();
+
+		// collect the nodes of the road and remember the arcs between them
+		nodes.add(current);
+		while (current != null){
+			RouteNode next = null;
+			for (int i = 0; i < current.arcs.size(); i++){
+				RouteArc arc = current.arcs.get(i);
+				if (arc.getRoadDef() == road){
+					if (arc.isDirect()){
+						if (arc.isForward()){
+							next = arc.getDest();
+							nodes.add(next);
+							forwardArcs.add(arc);
+							forwardArcPositions.add(i);
+						} else {
+							reverseArcPositions.add(i);
+							reverseArcs.add(arc);
+						}
+					}
+				} 
+			}
+			current = next;
+		}
+		
+		if (nodes.size() < 3)
+			return;
+//		System.out.println(road + " " + nodes.size() + " " + forwardArcs.size());
+		ArrayList<RouteArc> newArcs = new ArrayList<>(); 
+		IntArrayList arcPositions = forwardArcPositions;
+		List<RouteArc> roadArcs = forwardArcs;
+		for (int dir = 0; dir < 2; dir++){
+			// forward arcs first
+			for (int i = 0; i + 2 < nodes.size(); i++){
+				RouteNode sourceNode = nodes.get(i); // original source node of direct arc
+				RouteNode stepNode = nodes.get(i+1); 
+				RouteArc arcToStepNode = roadArcs.get(i);
+				assert arcToStepNode.getDest() == stepNode;
+				int currentClass = arcToStepNode.getArcDestClass();
+				int finalClass = road.getRoadClass();
+				if (finalClass <= currentClass)
+					continue;
+				newArcs.clear();
+				double partialArcLength = 0;
+				double pathLength = arcToStepNode.getLengthInMeter();
+				for (int j = i+2; j < nodes.size(); j++){
+					RouteArc arcToDest = roadArcs.get(j-1);
+					partialArcLength += arcToDest.getLengthInMeter();
+					pathLength += arcToDest.getLengthInMeter();
+					int cl = nodes.get(j).getGroup();
+					if (cl > currentClass){
+						if (cl > finalClass)
+							cl = finalClass;
+						currentClass = cl;
+						// create indirect arc from node i+1 to node j
+						RouteNode destNode = nodes.get(j);
+						Coord c1 = sourceNode.getCoord();
+						Coord c2 = destNode.getCoord();
+						RouteArc newArc = new RouteArc(road, 
+								sourceNode, 
+								destNode, 
+								roadArcs.get(i).getInitialHeading(), // not used
+								arcToDest.getFinalHeading(),  // not used
+								c1.bearingTo(c2),
+								partialArcLength, // from stepNode to destNode on road
+								pathLength, // from sourceNode to destNode on road
+								c1.distance(c2), 
+								c1.hashCode() + c2.hashCode());
+						if (arcToStepNode.isDirect())
+							arcToStepNode.setMaxDestClass(0);
+						else 
+							newArc.setMaxDestClass(cl);
+						if (dir == 0)
+							newArc.setForward();
+						newArc.setIndirect();
+						newArcs.add(newArc);
+						arcToStepNode = newArc;
+						stepNode = destNode;
+						
+						partialArcLength = 0;
+						if (cl >= finalClass)
+							break;
+					}
+				}
+				if (newArcs.isEmpty() == false){
+					int directArcPos =  arcPositions.getInt(i);
+					assert nodes.get(i).arcs.get(directArcPos).isDirect();
+					assert nodes.get(i).arcs.get(directArcPos).getRoadDef() == newArcs.get(0).getRoadDef();
+					assert nodes.get(i).arcs.get(directArcPos).isForward() == newArcs.get(0).isForward();
+					nodes.get(i).arcs.addAll(directArcPos + 1, newArcs);
+					if (dir == 0 && i > 0){
+						// check if the inserted arcs change the position of the direct reverse arc
+						int reverseArcPos = reverseArcPositions.get(i-1); // i-1 because first node doesn't have reverse arc 
+						if (directArcPos < reverseArcPos)
+							reverseArcPositions.set(i - 1, reverseArcPos + newArcs.size());
+							
+					}
+				}
+			}
+			if (dir > 0)
+				break;
+			// reverse the arrays for the other direction
+			Collections.reverse(reverseArcs);
+			Collections.reverse(reverseArcPositions);
+			Collections.reverse(nodes);
+			arcPositions = reverseArcPositions;
+			roadArcs = reverseArcs;
+		}
+	}
+
+	/**
+	 * Find the class group of the node. Rules:
+	 * 1. Find the highest class which is used more than once.
+	 * 2. Otherwise: use the class if the only one, or else the n-1 class.
+     * (eg: if [1,] then use 1, if [1,2,] then use 1, if [1,2,3,] then 	use 2.
+ 	 * 
+	 * @return the class group
+	 */
+	public int getGroup() {
+		if (nodeGroup < 0){
+			HashSet<RoadDef> roads = new HashSet<>();
+			for (RouteArc arc: arcs){
+				roads.add(arc.getRoadDef());
+			}
+			int[] classes = new int[5];
+			int numClasses = 0;
+			// find highest class that is used more than once
+			for (RoadDef road: roads){
+				int cl = road.getRoadClass();
+				int n = ++classes[cl];
+				if (n == 1)
+					numClasses++;
+				else if (n > 1 && cl > nodeGroup)
+					nodeGroup = (byte) cl;
+			}
+			if (nodeGroup >= 0)
+				return nodeGroup;
+			if (numClasses == 1)
+				nodeGroup = nodeClass; // only one class
+			else {
+				// find n-1 class 
+				int n = 0;
+				for (int cl = 4; cl >= 0; cl--){
+					if (classes[cl] > 0){
+						if (n == 1){
+							nodeGroup = (byte) cl;
+							break;
+						}
+						n++;
+					}
+				}
+			}
+		}
+		return nodeGroup;
+	}
+
+	public List<RouteArc> getArcs() {
+		return arcs;
+	}
+
+	public int hashCode(){
+		return getCoord().getId();
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java b/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
index 3e29076..96ca3c5 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
@@ -14,14 +14,20 @@
  */
 package uk.me.parabola.imgfmt.app.net;
 
+import java.util.ArrayList;
+
+import java.util.List;
+
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
+import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
+
 
 /**
  * A restriction in the routing graph.
  *
- * There may eventually be several types of these at which point
- * we might consider splitting them into several classes. For the
- * moment, just simple from-to-via restrictions.
+ * A routing restriction has two or more arcs.
+ * The first arc is the "from" arc, the last is the "to" arc,
+ *  and other arc is a "via" arc.
  *
  * A from-to-via restriction says you can't go along arc "to"
  * if you came to node to.getSource() == from.getSource()
@@ -34,21 +40,18 @@ import uk.me.parabola.imgfmt.app.ImgFileWriter;
 public class RouteRestriction {
 	//private static final Logger log = Logger.getLogger(RouteRestriction.class);
 
-	// size in bytes
-	private static final int SIZE = 11;
-
 	// first three bytes of the header -- might specify the type of restriction
 	// and when it is active
-	private static final int HEADER = 0x004005;
+	private static final byte RESTRICTION_TYPE = 0x05; // 0x07 spotted, meaning?
 
 	// To specify that a node is given by a relative offset instead
 	// of an entry to Table B.
 	private static final int F_INTERNAL = 0x8000;
 
 	// the arcs
-	private final RouteArc from;
-	private final RouteArc to;
+	private final List<RouteArc> arcs;
 
+	private final RouteNode viaNode; 
 	// offset in Table C
 	private byte offsetSize;
 	private int offsetC;
@@ -58,27 +61,73 @@ public class RouteRestriction {
 
 	// mask that specifies which vehicle types the restriction doesn't apply to
 	private final byte exceptMask;
+	private final byte flags; // meaning of bits 0x01 and 0x10 are not clear 
+
+	private final static byte F_EXCEPT_FOOT      = 0x02;
+	private final static byte F_EXCEPT_EMERGENCY = 0x04;
+	private final static byte F_MORE_EXCEPTIONS  = 0x08;
+	
+	private final static byte EXCEPT_CAR      = 0x01;
+	private final static byte EXCEPT_BUS      = 0x02;
+	private final static byte EXCEPT_TAXI     = 0x04;
+	private final static byte EXCEPT_DELIVERY = 0x10;
+	private final static byte EXCEPT_BICYCLE  = 0x20;
+	private final static byte EXCEPT_TRUCK    = 0x40;
+	
+	/**
+	 * 
+	 * @param viaNode the node to which this restriction is related
+	 * @param traffArcs the arcs that describe the "forbidden" path
+	 * @param mkgmapExceptMask the exception mask in the mkgmap format
+	 */
+	public RouteRestriction(RouteNode viaNode, List<RouteArc> traffArcs, byte mkgmapExceptMask) {
+		this.viaNode = viaNode;
+		this.arcs = new ArrayList<>(traffArcs);
+		for (int i = 0; i < arcs.size(); i++){
+			RouteArc arc = arcs.get(i);
+			assert arc.getDest() != viaNode;
+		}
+		byte flags = 0;
+		
+		if ((mkgmapExceptMask & FOOT) != 0)
+			flags |= F_EXCEPT_FOOT;
+		if ((mkgmapExceptMask & EMERGENCY) != 0)
+			flags |= F_EXCEPT_EMERGENCY;
+		
+		exceptMask = translateExceptMask(mkgmapExceptMask); 
+		if(exceptMask != 0)
+			flags |= F_MORE_EXCEPTIONS;
 
-	public final static byte EXCEPT_CAR      = 0x01;
-	public final static byte EXCEPT_BUS      = 0x02;
-	public final static byte EXCEPT_TAXI     = 0x04;
-	public final static byte EXCEPT_DELIVERY = 0x10;
-	public final static byte EXCEPT_BICYCLE  = 0x20;
-	public final static byte EXCEPT_TRUCK    = 0x40;
+		int numArcs = arcs.size();
+		assert numArcs < 8;
+		flags |= ((numArcs) << 5);
+		this.flags = flags;
+	}
 
+	
 	/**
-	 * Create a route restriction.
-	 *
-	 * @param from The inverse arc of "from" arc.
-	 * @param to The "to" arc.
+	 * Translate the mkgmap internal representation of vehicles to the one used in the img format
+	 * @param mkgmapExceptMask
+	 * @return
 	 */
-	public RouteRestriction(RouteArc from, RouteArc to, byte exceptMask) {
-		assert from.getSource().equals(to.getSource()) : "arcs in restriction don't meet";
-		this.from = from;
-		this.to = to;
-		this.exceptMask = exceptMask;
+	private byte translateExceptMask(byte mkgmapExceptMask) {
+		byte mask = 0;
+		if ((mkgmapExceptMask & CAR) != 0)
+			mask |= EXCEPT_CAR;
+		if ((mkgmapExceptMask & BUS) != 0)
+			mask |= EXCEPT_BUS;
+		if ((mkgmapExceptMask & TAXI) != 0)
+			mask |= EXCEPT_TAXI;
+		if ((mkgmapExceptMask & DELIVERY) != 0)
+			mask |= EXCEPT_DELIVERY;
+		if ((mkgmapExceptMask & BIKE) != 0)
+			mask |= EXCEPT_BICYCLE;
+		if ((mkgmapExceptMask & TRUCK) != 0)
+			mask |= EXCEPT_TRUCK;
+		return mask;
 	}
 
+
 	private int calcOffset(RouteNode node, int tableOffset) {
 		int offset = tableOffset - node.getOffsetNod1();
 		assert offset >= 0 : "node behind start of tables";
@@ -86,40 +135,55 @@ public class RouteRestriction {
 		return offset | F_INTERNAL;
 	}
 
+	public List<RouteArc> getArcs(){
+		return arcs;
+	}
+	
 	/**
-	 * Writes a Table C entry.
+	 * Writes a Table C entry with 3 or more nodes.
 	 *
 	 * @param writer The writer.
 	 * @param tableOffset The offset in NOD 1 of the tables area.
+	 * 
 	 */
 	public void write(ImgFileWriter writer, int tableOffset) {
-		int header = HEADER;
+		writer.put(RESTRICTION_TYPE); 
 
-		if(exceptMask != 0)
-			header |= 0x0800;
-
-		writer.put3(header);
+		writer.put(flags);
+		writer.put((byte)0); // meaning ?
 
 		if(exceptMask != 0)
 			writer.put(exceptMask);
 
-		int[] offsets = new int[3];
-
-		if (from.isInternal())
-			offsets[0] = calcOffset(from.getDest(), tableOffset);
-		else
-			offsets[0] = from.getIndexB();
-		offsets[1] = calcOffset(to.getSource(), tableOffset);
-		if (to.isInternal())
-			offsets[2] = calcOffset(to.getDest(), tableOffset);
-		else
-			offsets[2] = to.getIndexB();
+		int numArcs = arcs.size();
+		int[] offsets = new int[numArcs+1];
+		int pos = 0;
+		boolean viaWritten = false;
+		for (int i = 0; i < numArcs; i++){
+			RouteArc arc = arcs.get(i);
+			// the arcs must have a specific order and direction
+			// first arc: dest is from node , last arc: dest is to node
+			// if there only two arcs, both will have the via node as source node.
+			// For more n via nodes, the order is like this: 
+			// from <- via(1) <- via(2) <- ... <- this via node -> via( n-1) -> via(n) -> to
+			if (arc.isInternal())
+				offsets[pos++] = calcOffset(arc.getDest(), tableOffset);
+			else 
+				offsets[pos++] = arc.getIndexB();
+			if (arc.getSource() == viaNode){
+				// there will be two nodes with source node = viaNode, but we write the source only once
+				if (!viaWritten){
+					offsets[pos++] = calcOffset(viaNode, tableOffset);
+					viaWritten = true;
+				}
+			}
+		}
 
 		for (int offset : offsets)
 			writer.putChar((char) offset);
 
-		writer.put(from.getIndexA());
-		writer.put(to.getIndexA());
+		for (RouteArc arc: arcs)
+			writer.put(arc.getIndexA());
 	}
 
 	/**
@@ -145,9 +209,10 @@ public class RouteRestriction {
 	 * Size in bytes of the Table C entry.
 	 */
 	public int getSize() {
-		int size = SIZE;
+		int size = 3; // header length
 		if(exceptMask != 0)
 			++size;
+		size += arcs.size() + (arcs.size()+1) * 2; 
 		return size;
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/net/TableA.java b/src/uk/me/parabola/imgfmt/app/net/TableA.java
index 90aa916..ba47401 100644
--- a/src/uk/me/parabola/imgfmt/app/net/TableA.java
+++ b/src/uk/me/parabola/imgfmt/app/net/TableA.java
@@ -38,12 +38,14 @@ public class TableA {
 	// This table's start position relative to the start of NOD 1
 	private int offset;
 
-	// arcs for paved ways
-	private final HashMap<Arc,Integer> pavedArcs = new LinkedHashMap<Arc,Integer>();
+	// arcs for roundabouts
+	private final HashMap<RoadDef,Integer> roundaboutArcs = new LinkedHashMap<RoadDef,Integer>();
 	// arcs for unpaved ways
-	private final HashMap<Arc,Integer> unpavedArcs = new LinkedHashMap<Arc,Integer>();
+	private final HashMap<RoadDef,Integer> unpavedArcs = new LinkedHashMap<RoadDef,Integer>();
 	// arcs for ferry ways
-	private final HashMap<Arc,Integer> ferryArcs = new LinkedHashMap<Arc,Integer>();
+	private final HashMap<RoadDef,Integer> ferryArcs = new LinkedHashMap<RoadDef,Integer>();
+	// arcs for paved ways
+	private final HashMap<RoadDef,Integer> pavedArcs = new LinkedHashMap<RoadDef,Integer>();
 
 	private static int count;
 
@@ -55,99 +57,74 @@ public class TableA {
 	}
 
 	/**
-	 * Internal class tracking all the data a Table A entry needs.
-	 * Basically a "forward arc".
-	 */
-	private class Arc {
-		final RouteNode first; final RouteNode second;
-		final RoadDef roadDef;
-
-		Arc(RouteArc arc) {
-			if (arc.isForward()) {
-				first = arc.getSource();
-				second = arc.getDest();
-			} else {
-				first = arc.getDest();
-				second = arc.getSource();
-			}
-			roadDef = arc.getRoadDef();
-		}
-
-		public boolean equals(Object obj) {
-			if (this == obj) return true;
-			if (obj == null || getClass() != obj.getClass()) return false;
-
-			Arc arc = (Arc) obj;
-			return first.equals(arc.first)
-				&& second.equals(arc.second)
-				&& roadDef.equals(arc.roadDef);
-		}
-
-		public int hashCode() {
-			return first.hashCode() + 2*second.hashCode() + roadDef.hashCode();
-		}
-
-		public String toString() {
-			return "" + first + "->" + second + " (" + roadDef + ")";
-		}
-	}
-
-	/**
 	 * Add an arc to the table if not present and set its index.
 	 *
 	 * The value may overflow while it isn't certain that
-	 * the table fulfills the size constraint.
+	 * the table fulfils the size constraint.
 	 */
 	public void addArc(RouteArc arc) {
 		assert !frozen : "trying to add arc to Table A after it has been frozen";
-		Arc narc = new Arc(arc);
 		int i;
-		if(arc.getRoadDef().ferry()) {
-			if (!ferryArcs.containsKey(narc)) {
+		RoadDef rd = arc.getRoadDef();
+		if(rd.isRoundabout()) {
+			if (!roundaboutArcs.containsKey(rd)) {
+				i = roundaboutArcs.size();
+				roundaboutArcs.put(rd, i);
+				log.debug("added roundabout arc", count, rd, i);
+			}
+		}
+		else if(rd.ferry()) {
+			if (!ferryArcs.containsKey(rd)) {
 				i = ferryArcs.size();
-				ferryArcs.put(narc, i);
-				log.debug("added ferry arc", count, narc, i);
+				ferryArcs.put(rd, i);
+				log.debug("added ferry arc", count, rd, i);
 			}
 		}
-		else if(arc.getRoadDef().paved()) {
-			if (!pavedArcs.containsKey(narc)) {
+		else if(rd.paved()) {
+			if (!pavedArcs.containsKey(rd)) {
 				i = pavedArcs.size();
-				pavedArcs.put(narc, i);
-				log.debug("added paved arc", count, narc, i);
+				pavedArcs.put(rd, i);
+				log.debug("added paved arc", count, rd, i);
 			}
 		}
 		else {
-			if (!unpavedArcs.containsKey(narc)) {
+			if (!unpavedArcs.containsKey(rd)) {
 				i = unpavedArcs.size();
-				unpavedArcs.put(narc, i);
-				log.debug("added unpaved arc", count, narc, i);
+				unpavedArcs.put(rd, i);
+				log.debug("added unpaved arc", count, rd, i);
 			}
 		}
 	}
 
 	/**
 	 * Retrieve an arc's index.
+	 * Order in table A: roundabouts, unpaved, ferry, paved 
 	 */
 	public byte getIndex(RouteArc arc) {
 		frozen = true;			// don't allow any more arcs to be added
-		Arc narc = new Arc(arc);
 		int i;
-		if(arc.getRoadDef().ferry()) {
-			assert ferryArcs.containsKey(narc):
-			"Trying to read Table A index for non-registered arc: " + count + " " + narc;
-			i = unpavedArcs.size() + ferryArcs.get(narc);
+		RoadDef rd = arc.getRoadDef();
+		if(rd.isRoundabout()) {
+			assert roundaboutArcs.containsKey(rd):
+			"Trying to read Table A index for non-registered arc: " + count + " " + rd;
+			i = roundaboutArcs.get(rd);
+		}
+		else if(rd.ferry()) {
+			assert ferryArcs.containsKey(rd):
+			"Trying to read Table A index for non-registered arc: " + count + " " + rd;
+			i = roundaboutArcs.size() + unpavedArcs.size() + ferryArcs.get(rd);
 		}
-		else if(arc.getRoadDef().paved()) {
-			assert pavedArcs.containsKey(narc):
-			"Trying to read Table A index for non-registered arc: " + count + " " + narc;
-			i = unpavedArcs.size() + ferryArcs.size() + pavedArcs.get(narc);
+		else if(rd.paved()) {
+			assert pavedArcs.containsKey(rd):
+			"Trying to read Table A index for non-registered arc: " + count + " " + rd;
+			i = roundaboutArcs.size() + unpavedArcs.size() + ferryArcs.size() + pavedArcs.get(rd);
 		}
 		else {
-			assert unpavedArcs.containsKey(narc):
-			"Trying to read Table A index for non-registered arc: " + count + " " + narc;
-			i = unpavedArcs.get(narc);
+			assert unpavedArcs.containsKey(rd):
+			"Trying to read Table A index for non-registered arc: " + count + " " + rd;
+			i = roundaboutArcs.size() + unpavedArcs.get(rd);
 		}
-		assert i < 0x100 : "Table A index too large: " + narc;
+		assert i < 0x100 : "Table A index too large: " + rd;
 		return (byte) i;
 	}
 
@@ -159,7 +136,11 @@ public class TableA {
 	 * the network.
 	 */
 	public int size() {
-		return ferryArcs.size() + unpavedArcs.size() + pavedArcs.size();
+		return roundaboutArcs.size() + unpavedArcs.size() + ferryArcs.size() + pavedArcs.size();
+	}
+
+	public int numRoundaboutArcs() {
+		return roundaboutArcs.size();
 	}
 
 	public int numUnpavedArcs() {
@@ -202,30 +183,33 @@ public class TableA {
 	public void writePost(ImgFileWriter writer) {
 		writer.position(offset);
 		// unpaved arcs first
-		for (Arc arc : unpavedArcs.keySet()) {
-			writePost(writer, arc);
+		for (RoadDef rd: roundaboutArcs.keySet()) {
+			writePost(writer, rd);
+		}
+		for (RoadDef rd: unpavedArcs.keySet()) {
+			writePost(writer, rd);
 		}
 		// followed by the ferry arcs
-		for (Arc arc : ferryArcs.keySet()) {
-			writePost(writer, arc);
+		for (RoadDef rd : ferryArcs.keySet()) {
+			writePost(writer, rd);
 		}
 		// followed by the paved arcs
-		for (Arc arc : pavedArcs.keySet()) {
-			writePost(writer, arc);
+		for (RoadDef rd : pavedArcs.keySet()) {
+			writePost(writer, rd);
 		}
 	}
 
-	public void writePost(ImgFileWriter writer, Arc arc) {
+	public void writePost(ImgFileWriter writer, RoadDef rd) {
 		// write the table A entries.  Consists of a pointer to net
 		// followed by 2 bytes of class and speed flags and road restrictions.
-		int pos = arc.roadDef.getOffsetNet1();
-		int access = arc.roadDef.getTabAAccess();
+		int pos = rd.getOffsetNet1();
+		int access = rd.getTabAAccess();
 		// top bits of access go into net1 offset
 		final int ACCESS_TOP_BITS = 0xc000;
 		pos |= (access & ACCESS_TOP_BITS) << 8;
 		access &= ~ACCESS_TOP_BITS;
 		writer.put3(pos);
-		writer.put((byte) arc.roadDef.getTabAInfo());
+		writer.put((byte) rd.getTabAInfo());
 		writer.put((byte) access);
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/TableB.java b/src/uk/me/parabola/imgfmt/app/net/TableB.java
index 23f63c6..4eb74b9 100644
--- a/src/uk/me/parabola/imgfmt/app/net/TableB.java
+++ b/src/uk/me/parabola/imgfmt/app/net/TableB.java
@@ -17,7 +17,6 @@
 package uk.me.parabola.imgfmt.app.net;
 
 import java.util.ArrayList;
-
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
 
 /**
diff --git a/src/uk/me/parabola/imgfmt/app/net/TableC.java b/src/uk/me/parabola/imgfmt/app/net/TableC.java
index c36257c..055e31d 100644
--- a/src/uk/me/parabola/imgfmt/app/net/TableC.java
+++ b/src/uk/me/parabola/imgfmt/app/net/TableC.java
@@ -41,25 +41,20 @@ public class TableC {
 	 * Write the table including size field.
 	 */
 	public void write(ImgFileWriter writer, int tablesOffset) {
-		if (restrictions.isEmpty()) {
-			if(tabA.numUnpavedArcs() > 0)
-				writer.put((byte)tabA.numUnpavedArcs());
-			if(tabA.numFerryArcs() > 0)
-				writer.put((byte)tabA.numFerryArcs());
-			writer.put((byte) 0);
-		}
-		else {
+		if (!restrictions.isEmpty()) {
 			if (size < 0x100)
 				writer.put((byte) size);
 			else
 				writer.putChar((char) size);
 			for (RouteRestriction restr : restrictions)
 				restr.write(writer, tablesOffset);
-			if(tabA.numUnpavedArcs() > 0)
-				writer.put((byte)tabA.numUnpavedArcs());
-			if(tabA.numFerryArcs() > 0)
-				writer.put((byte)tabA.numFerryArcs());
 		}
+		if(tabA.numRoundaboutArcs() > 0)
+			writer.put((byte)tabA.numRoundaboutArcs());
+		if(tabA.numUnpavedArcs() > 0)
+			writer.put((byte)tabA.numUnpavedArcs());
+		if(tabA.numFerryArcs() > 0)
+			writer.put((byte)tabA.numFerryArcs());
 	}
 
 	/**
@@ -107,6 +102,8 @@ public class TableC {
 			if(size > 0xff)
 				++format;
 		}
+		if(tabA.numRoundaboutArcs() > 0)
+			format |= 0x04;
 		if(tabA.numUnpavedArcs() > 0)
 			format |= 0x08;
 		if(tabA.numFerryArcs() > 0)
diff --git a/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java b/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
index 3b075a5..c73ccde 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
@@ -20,11 +20,11 @@ import java.text.Collator;
  * @author Steve Ratcliffe
  */
 class CodePosition {
-	private byte primary;
+	private char primary;
 	private byte secondary;
 	private byte tertiary;
 
-	public byte getPrimary() {
+	public char getPrimary() {
 		return primary;
 	}
 
@@ -42,20 +42,20 @@ class CodePosition {
 	 * @param type The strength, Collator.PRIMARY, SECONDARY etc.
 	 * @return The collation position at the given strength.
 	 */
-	public byte getPosition(int type) {
+	public int getPosition(int type) {
 		switch (type) {
 		case Collator.PRIMARY:
 			return primary;
 		case Collator.SECONDARY:
-			return secondary;
+			return secondary & 0xff;
 		case Collator.TERTIARY:
-			return tertiary;
+			return tertiary & 0xff;
 		default:
 			return 0;
 		}
 	}
 
-	public void setPrimary(byte primary) {
+	public void setPrimary(char primary) {
 		this.primary = primary;
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
index 510c340..b23082b 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
@@ -13,6 +13,8 @@
 package uk.me.parabola.imgfmt.app.srt;
 
 import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
 
 import uk.me.parabola.imgfmt.app.BufferedImgFileWriter;
 import uk.me.parabola.imgfmt.app.ImgFile;
@@ -31,9 +33,12 @@ public class SRTFile extends ImgFile {
 	private final SRTHeader header;
 
 	private Sort sort;
+	private boolean isMulti;
 	
 	private String description;
 
+	private final List<Integer> srt8Starts = new ArrayList<>();
+
 	public SRTFile(ImgChannel chan) {
 		header = new SRTHeader();
 		setHeader(header);
@@ -56,9 +61,15 @@ public class SRTFile extends ImgFile {
 		writeDescription(writer);
 
 		SectionWriter subWriter = header.makeSectionWriter(writer);
-		subWriter.position(SRTHeader.HEADER3_LEN);
-		writeCharacterTable(subWriter);
-		writeExpansions(subWriter);
+		subWriter.position(sort.isMulti()? SRTHeader.HEADER3_MULTI_LEN: SRTHeader.HEADER3_LEN);
+		writeSrt4Chars(subWriter);
+		writeSrt5Expansions(subWriter);
+		if (sort.isMulti()) {
+			for (int i = 0; i <= sort.getMaxPage(); i++)
+				srt8Starts.add(-1);
+			writeSrt8(subWriter);
+			writeSrt7(subWriter);
+		}
 		subWriter.close();
 
 		// Header 2 is just after the real header
@@ -79,7 +90,12 @@ public class SRTFile extends ImgFile {
 		header.endDescription(writer.position());
 	}
 
-	private void writeCharacterTable(ImgFileWriter writer) {
+	/**
+	 * Write SRT4 the character table.
+	 *
+	 * @param writer The img file writer.
+	 */
+	private void writeSrt4Chars(ImgFileWriter writer) {
 		for (int i = 1; i < 256; i++) {
 			writer.put(sort.getFlags(i));
 			writeWeights(writer, i);
@@ -88,33 +104,70 @@ public class SRTFile extends ImgFile {
 	}
 
 	private void writeWeights(ImgFileWriter writer, int i) {
-		writer.put(sort.getPrimary(i));
-		writer.put((byte) ((sort.getTertiary(i) << 4) | (sort.getSecondary(i) & 0xf)));
+		if (isMulti) {
+			writer.putChar((char) sort.getPrimary(i));
+			writer.put((byte) sort.getSecondary(i));
+			writer.put((byte) sort.getTertiary(i));
+		} else {
+			writer.put((byte) sort.getPrimary(i));
+			writer.put((byte) ((sort.getTertiary(i) << 4) | (sort.getSecondary(i) & 0xf)));
+		}
 	}
 
 	/**
+	 * Write SRT5, the expansion table.
+	 *
 	 * Write out the expansion table. This is referenced from the character table, when
 	 * the top nibble of the type is set via the primary position value.
 	 */
-	private void writeExpansions(ImgFileWriter writer) {
+	private void writeSrt5Expansions(ImgFileWriter writer) {
 
 		int size = sort.getExpansionSize();
 		for (int j = 1; j <= size; j++) {
 			CodePosition b = sort.getExpansion(j);
-			writer.put(b.getPrimary());
-			writer.put((byte) ((b.getTertiary() << 4) | (b.getSecondary() & 0xf)));
+			if (isMulti) {
+				writer.putChar(b.getPrimary());
+				writer.put(b.getSecondary());
+				writer.put(b.getTertiary());
+			} else {
+				writer.put((byte) b.getPrimary());
+				writer.put((byte) ((b.getTertiary() << 4) | (b.getSecondary() & 0xf)));
+			}
 		}
 
 		header.endTab2(writer.position());
 	}
 
-	public void setDescription(String description) {
-		this.description = description;
+	private void writeSrt7(SectionWriter writer) {
+		assert sort.isMulti();
+		for (int i = 1; i <= sort.getMaxPage(); i++) {
+			writer.putInt(srt8Starts.get(i));
+		}
+		header.endSrt7(writer.position());
+	}
+
+	private void writeSrt8(SectionWriter writer) {
+		assert sort.isMulti();
+
+		int offset = 0;
+		for (int p = 1; p <= sort.getMaxPage(); p++) {
+			if (sort.hasPage(p)) {
+				srt8Starts.set(p, offset);
+				for (int j = 0; j < 256; j++) {
+					int ch = p * 256 + j;
+					writer.put(sort.getFlags(ch));
+					writeWeights(writer, ch);
+					offset += 5;
+				}
+			}
+		}
+		header.endSrt8(writer.position());
 	}
 
 	public void setSort(Sort sort) {
 		this.sort = sort;
 		header.setSort(sort);
 		description = sort.getDescription();
+		isMulti = sort.isMulti();
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java b/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
index 5b106d9..490f493 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
@@ -31,15 +31,18 @@ public class SRTHeader extends CommonHeader {
 	private static final int HEADER_LEN = 29;
 	protected static final int HEADER2_LEN = 16;
 	protected static final int HEADER3_LEN = 52;
+	protected static final int HEADER3_MULTI_LEN = 92;
 
 	// The section structure of this file is somewhat different to other
 	// files, but I am still going to model it using Section.
 	private final Section header = new Section();
-	//private final Section pointers = new Section(header);
+
 	private final Section desc = new Section(header);
 	private final Section subheader = new Section(desc);
 	private final Section chartab = new Section((char) 3);
-	private final Section tab2 = new Section(chartab, (char) 2);
+	private final Section expansions = new Section(chartab, (char) 2);
+	private final Section srt8 = new Section(expansions, (char) 5);
+	private final Section srt7 = new Section(srt8, (char) 4);
 
 	private Sort sort;
 
@@ -64,15 +67,10 @@ public class SRTHeader extends CommonHeader {
 
 		writer.putInt(header.getPosition());
 		writer.putChar((char) header.getSize());
-
-		//writeHeader2(writer);
-
-		//writeDescription(writer);
-		//writeHeader3(writer);
 	}
 
 	/**
-	 * Section writer to write the character and tab2 sections. These two sections are embedded within another
+	 * Section writer to write the character and expansions sections. These two sections are embedded within another
 	 * section and their offsets are relative to that section.
 	 * @param writer The real underlying writer.
 	 * @return A new writer where offsets are relative to the start of the sub-header section.
@@ -97,24 +95,51 @@ public class SRTHeader extends CommonHeader {
 	 * @param writer Header is written here.
 	 */
 	protected void writeHeader3(ImgFileWriter writer) {
-		writer.putChar((char) HEADER3_LEN);
+		if (sort.isMulti()) {
+			writer.putChar((char) HEADER3_MULTI_LEN);
+		} else {
+			writer.putChar((char) HEADER3_LEN);
+		}
+
 		writer.putChar((char) sort.getId1());
 		writer.putChar((char) sort.getId2());
 		writer.putChar((char) sort.getCodepage());
-		writer.putInt(0x2002);
+		if (sort.isMulti())
+			writer.putInt(0x6f02);
+		else
+			writer.putInt(0x2002);
 
 		chartab.writeSectionInfo(writer, true, true);
-
 		writer.putChar((char) 0);
-		tab2.writeSectionInfo(writer, true, true);
 
+		expansions.writeSectionInfo(writer, true, true);
 		writer.putChar((char) 0);
-		writer.putInt(0x34);
+
+		// SRT6 A repeat pointer to the single byte character table
+		writer.putInt(chartab.getPosition());
 		writer.putInt(0);
+
+		if (sort.isMulti()) {
+			writer.putInt(1);
+			writer.putInt(sort.getMaxPage());  // max block in srt7
+
+			srt7.writeSectionInfo(writer, true);
+			writer.putChar((char) 0);
+			writer.putInt(0);
+			srt8.writeSectionInfo(writer, true);
+
+			writer.putChar((char) 0);
+			writer.putInt(0);
+		}
 	}
 
 	public void setSort(Sort sort) {
 		this.sort = sort;
+		if (sort.isMulti()) {
+			chartab.setPosition(HEADER3_MULTI_LEN);
+			chartab.setItemSize((char) 5);
+			expansions.setItemSize((char) 4);
+		}
 	}
 
 	/** Called after the description has been written to record the position. */
@@ -128,9 +153,17 @@ public class SRTHeader extends CommonHeader {
 		chartab.setSize(position - chartab.getPosition());
 	}
 
-	/** Called after the tab2 has been written to record the position. */
+	/** Called after the expansions has been written to record the position. */
 	public void endTab2(int postition) {
 		subheader.setSize(postition - subheader.getPosition());
-		tab2.setSize(postition - tab2.getPosition());
+		expansions.setSize(postition - expansions.getPosition());
+	}
+
+	public void endSrt8(int position) {
+		srt8.setSize(position - srt8.getPosition());
+	}
+
+	public void endSrt7(int position) {
+		srt7.setSize(position - srt7.getPosition());
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/srt/Sort.java b/src/uk/me/parabola/imgfmt/app/srt/Sort.java
index 7719b44..8304f4b 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/Sort.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/Sort.java
@@ -27,14 +27,31 @@ import java.util.List;
 import java.util.Map;
 
 import uk.me.parabola.imgfmt.ExitException;
+import uk.me.parabola.imgfmt.app.Label;
 
 /**
  * Represents the sorting positions for all the characters in a codepage.
+ *
+ * A map contains a file that determines how the characters are to be sorted. So we
+ * have to have to be able to create such a file and sort with exactly the same rules
+ * as is contained in it.
+ *
+ * What about the java {@link java.text.RuleBasedCollator}? It turns out that it is possible to
+ * make it work in the way we need it to, although it doesn't help with creating the srt file.
+ * Also it is significantly slower than this implementation, so this one is staying. I also
+ * found that sorting with the sort keys and the collator gave different results in some
+ * cases. This implementation does not.
+ *
+ * Be careful when benchmarking. With small lists (< 10000 entries) repeated runs cause some
+ * pretty aggressive optimisation to kick in. This tends to favour this implementation which has
+ * much tighter loops that the java7 or ICU implementations, but this may not be realised with
+ * real workloads.
+ *
  * @author Steve Ratcliffe
  */
 public class Sort {
-
-	private static final byte[] ZERO_KEY = new byte[3];
+	private static final byte[] ZERO_KEY = new byte[4];
+	private static final Integer NO_ORDER = 0;
 
 	private int codepage;
 	private int id1; // Unknown - identifies the sort
@@ -43,36 +60,91 @@ public class Sort {
 	private String description;
 	private Charset charset;
 
-	private final byte[] primary = new byte[256];
-	private final byte[] secondary = new byte[256];
-	private final byte[] tertiary = new byte[256];
-	private final byte[] flags = new byte[256];
+	private final Page[] pages = new Page[256];
 
-	private final List<CodePosition> expansions = new ArrayList<CodePosition>();
+	private final List<CodePosition> expansions = new ArrayList<>();
 	private int maxExpSize = 1;
 
 	private CharsetEncoder encoder;
+	private boolean multi;
+	private int maxPage;
+
+	public Sort() {
+		pages[0] = new Page();
+	}
 
 	public void add(int ch, int primary, int secondary, int tertiary, int flags) {
-		if (this.primary[ch & 0xff] != 0)
+		ensurePage(ch >>> 8);
+		if (getPrimary(ch) != 0)
 			throw new ExitException(String.format("Repeated primary index 0x%x", ch & 0xff));
-		this.primary[ch & 0xff] = (byte) primary;
-		this.secondary[ch & 0xff] = (byte) secondary;
-		this.tertiary[ch & 0xff] = (byte) tertiary;
+		setPrimary (ch, primary);
+		setSecondary(ch, secondary);
+		setTertiary( ch, tertiary);
 
-		this.flags[ch & 0xff] = (byte) flags;
+		setFlags(ch, flags);
+	}
+
+	/**
+	 * Run after all sorting order points have been added.
+	 *
+	 * Make sure that all tertiary values of secondary ignorable are greater
+	 * than any normal tertiary value.
+	 *
+	 * And the same for secondaries on primary ignorable.
+	 */
+	public void finish() {
+		int maxSecondary = 0;
+		int maxTertiary = 0;
+		for (Page p : pages) {
+			if (p == null)
+				continue;
+
+			for (int i = 0; i < 256; i++) {
+				if (((p.flags[i] >>> 4) & 0x3) == 0) {
+					if (p.getPrimary(i) != 0) {
+						byte second = p.getSecondary(i);
+						maxSecondary = Math.max(maxSecondary, second);
+						if (second != 0) {
+							maxTertiary = Math.max(maxTertiary, p.getTertiary(i));
+						}
+					}
+				}
+			}
+		}
+
+		for (Page p : pages) {
+			if (p == null)
+				continue;
+
+			for (int i = 0; i < 256; i++) {
+				if (((p.flags[i] >>> 4) & 0x3) != 0) continue;
+
+				if (p.getPrimary(i) == 0) {
+					if (p.getSecondary(i) == 0) {
+						if (p.getTertiary(i) != 0) {
+							p.setTertiary(i, p.getTertiary(i) + maxTertiary);
+						}
+					} else {
+						p.setSecondary(i, p.getSecondary(i) + maxSecondary);
+					}
+				}
+			}
+		}
 	}
 
 	/**
 	 * Return a table indexed by a character value in the target codepage, that gives the complete sort
 	 * position of the character.
+	 *
+	 * This is only used for testing.
+	 *
 	 * @return A table of sort positions.
 	 */
 	public char[] getSortPositions() {
 		char[] tab = new char[256];
 
 		for (int i = 1; i < 256; i++) {
-			tab[i] = (char) (((primary[i] << 8) & 0xff00) | ((secondary[i] << 4) & 0xf0) | (tertiary[i] & 0xf));
+			tab[i] = (char) (((getPrimary(i) << 8) & 0xff00) | ((getSecondary(i) << 4) & 0xf0) | (getTertiary(i) & 0xf));
 		}
 
 		return tab;
@@ -99,13 +171,20 @@ public class Sort {
 		if (cache != null) {
 			key = cache.get(s);
 			if (key != null)
-				return new SrtSortKey<T>(object, key, second);
+				return new SrtSortKey<>(object, key, second);
 		}
 
-		CharBuffer inb = CharBuffer.wrap(s);
 		try {
-			ByteBuffer out = encoder.encode(inb);
-			byte[] bval = out.array();
+			char[] chars;
+			if (isMulti()) {
+				chars = s.toCharArray();
+			} else {
+				ByteBuffer out = encoder.encode(CharBuffer.wrap(s));
+				byte[] bval = out.array();
+				chars = new char[bval.length];
+				for (int i = 0; i < bval.length; i++)
+					chars[i] = (char) (bval[i] & 0xff);
+			}
 
 			// In theory you could have a string where every character expands into maxExpSize separate characters
 			// in the key.  However if we allocate enough space to deal with the worst case, then we waste a
@@ -114,96 +193,157 @@ public class Sort {
 			//
 			// We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete
 			// german map this was always enough in tests.
-			key = new byte[(bval.length + 1 + 2) * 3];
+			key = new byte[(chars.length + 1 + 2) * 4];
 			try {
-				fillCompleteKey(bval, key);
+				fillCompleteKey(chars, key);
 			} catch (ArrayIndexOutOfBoundsException e) {
 				// Ok try again with the max possible key size allocated.
-				key = new byte[bval.length * 3 * maxExpSize + 3];
+				key = new byte[(chars.length+1) * 4 * maxExpSize];
+				fillCompleteKey(chars, key);
 			}
 
 			if (cache != null)
 				cache.put(s, key);
 
-			return new SrtSortKey<T>(object, key, second);
+			return new SrtSortKey<>(object, key, second);
 		} catch (CharacterCodingException e) {
-			return new SrtSortKey<T>(object, ZERO_KEY);
+			return new SrtSortKey<>(object, ZERO_KEY);
+		}
+	}
+
+	/**
+	 * Create a sort key based on a Label.
+	 *
+	 * The label will contain the actual characters (after transliteration for example)
+	 * @param object This is saved in the sort key for later retrieval and plays no part in the sorting.
+	 * @param label The label, the actual written bytes/chars will be used as input to the sort.
+	 * @param second Secondary sort key.
+	 * @param cache A cache for the created keys. This is for saving memory so it is essential that this
+	 * is managed by the caller.
+	 * @return A sort key.
+	 */
+	public <T> SortKey<T> createSortKey(T object, Label label, int second, Map<Label, byte[]> cache) {
+		byte[] key;
+		if (cache != null) {
+			key = cache.get(label);
+			if (key != null)
+				return new SrtSortKey<>(object, key, second);
+		}
+
+		char[] encText = label.getEncText();
+
+		// In theory you could have a string where every character expands into maxExpSize separate characters
+		// in the key.  However if we allocate enough space to deal with the worst case, then we waste a
+		// vast amount of memory. So allocate a minimal amount of space, try it and if it fails reallocate the
+		// maximum amount.
+		//
+		// We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete
+		// german map this was always enough in tests.
+		key = new byte[(encText.length + 1 + 2) * 4];
+		try {
+			fillCompleteKey(encText, key);
+		} catch (ArrayIndexOutOfBoundsException e) {
+			// Ok try again with the max possible key size allocated.
+			key = new byte[encText.length * 4 * maxExpSize + 4];
+			fillCompleteKey(encText, key);
 		}
+
+		if (cache != null)
+			cache.put(label, key);
+
+		return new SrtSortKey<>(object, key, second);
 	}
 
+	/**
+	 * Convenient version of create sort key method.
+	 * @see #createSortKey(Object, String, int, Map)
+	 */
 	public <T> SortKey<T> createSortKey(T object, String s, int second) {
 		return createSortKey(object, s, second, null);
 	}
 
+	/**
+	 * Convenient version of create sort key method.
+	 *
+	 * @see #createSortKey(Object, String, int, Map)
+	 */
 	public <T> SortKey<T> createSortKey(T object, String s) {
 		return createSortKey(object, s, 0, null);
 	}
 
+	public <T> SortKey<T> createSortKey(T object, Label label) {
+		return createSortKey(object, label, 0, null);
+	}
+
+	public <T> SortKey<T> createSortKey(T object, Label label, int second) {
+		return createSortKey(object, label, second, null);
+	}
+
 	/**
 	 * Fill in the key from the given byte string.
 	 *
-	 * @param bval The string for which we are creating the sort key.
+	 * @param bVal The string for which we are creating the sort key.
 	 * @param key The sort key. This will be filled in.
 	 */
-	private void fillCompleteKey(byte[] bval, byte[] key) {
-		int start = fillKey(Collator.PRIMARY, primary, bval, key, 0);
-		start = fillKey(Collator.SECONDARY, secondary, bval, key, start);
-		fillKey(Collator.TERTIARY, tertiary, bval, key, start);
+	private void fillCompleteKey(char[] bVal, byte[] key) {
+		int start = fillKey(Collator.PRIMARY, bVal, key, 0);
+		start = fillKey(Collator.SECONDARY, bVal, key, start);
+		fillKey(Collator.TERTIARY, bVal, key, start);
 	}
 
 	/**
 	 * Fill in the output key for a given strength.
 	 *
-	 * @param sortPositions An array giving the sort position for each of the 256 characters.
 	 * @param input The input string in a particular 8 bit codepage.
 	 * @param outKey The output sort key.
 	 * @param start The index into the output key to start at.
 	 * @return The next position in the output key.
 	 */
-	private int fillKey(int type, byte[] sortPositions, byte[] input, byte[] outKey, int start) {
+	private int fillKey(int type, char[] input, byte[] outKey, int start) {
 		int index = start;
-		for (byte inb : input) {
-			int b = inb & 0xff;
+		for (char c : input) {
+
+			if (!hasPage(c >>> 8))
+				continue;
 
-			int exp = (flags[b] >> 4) & 0x3;
+			int exp = (getFlags(c) >> 4) & 0x3;
 			if (exp == 0) {
-				// I am guessing that a sort position of 0 means that the character is ignorable at this
-				// strength. In other words it is as if it is not present in the string.  This appears to
-				// be true for shield symbols, but perhaps not for other kinds of control characters.
-				byte pos = sortPositions[b];
-				if (pos != 0)
-					outKey[index++] = pos;
+				index = writePos(type, c, outKey, index);
 			} else {
 				// now have to redirect to a list of input chars, get the list via the primary value always.
-				byte idx = primary[b];
-				//List<CodePosition> list = expansions.get(idx-1);
-
+				int idx = getPrimary(c);
 				for (int i = idx - 1; i < idx + exp; i++) {
-					byte pos = expansions.get(i).getPosition(type);
-					if (pos != 0)
-						outKey[index++] = pos;
+					int pos = expansions.get(i).getPosition(type);
+					if (pos != 0) {
+						if (type == Collator.PRIMARY)
+							outKey[index++] = (byte) ((pos >>> 8) & 0xff);
+						outKey[index++] = (byte) pos;
+					}
 				}
 			}
 		}
 
+		if (type == Collator.PRIMARY)
+			outKey[index++] = '\0';
 		outKey[index++] = '\0';
 		return index;
 	}
 
-	public byte getPrimary(int ch) {
-		return primary[ch];
+	public int getPrimary(int ch) {
+		return this.pages[ch >>> 8].getPrimary(ch);
 	}
 
-	public byte getSecondary(int ch) {
-		return secondary[ch];
+	public int getSecondary(int ch) {
+		return this.pages[ch >>> 8].getSecondary(ch);
 	}
 
-	public byte getTertiary(int ch) {
-		return tertiary[ch];
+	public int getTertiary(int ch) {
+		return this.pages[ch >>> 8].getTertiary(ch);
 	}
 
 	public byte getFlags(int ch) {
-		return flags[ch];
+		assert ch >= 0;
+		return this.pages[ch >>> 8].flags[ch & 0xff];
 	}
 
 	public int getCodepage() {
@@ -251,16 +391,8 @@ public class Sort {
 
 	public void setCodepage(int codepage) {
 		this.codepage = codepage;
-		if (codepage == 0)
-			charset = Charset.forName("cp1252");
-		else if (codepage == 65001)
-			charset = Charset.forName("UTF-8");
-		else if (codepage == 932)
-			// Java uses "ms932" for code page 932
-			// (Windows-31J, Shift-JIS + MS extensions)
-			charset = Charset.forName("ms932");
-		else
-			charset = Charset.forName("cp" + codepage);
+		charset = charsetFromCodepage(codepage);
+
 		encoder = charset.newEncoder();
 		encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
 	}
@@ -280,28 +412,30 @@ public class Sort {
 	 * The case were two letters sort as if the were just one (and more complex cases) are
 	 * not supported or are unknown to us.
 	 *
-	 * @param bval The code point of this letter in the code page.
+	 * @param ch The code point of this letter in the code page.
 	 * @param inFlags The initial flags, eg if it is a letter or not.
 	 * @param expansionList The letters that this letter sorts as, as code points in the codepage.
 	 */
-	public void addExpansion(byte bval, int inFlags, List<Byte> expansionList) {
-		int idx = bval & 0xff;
-		flags[idx] = (byte) ((inFlags & 0xf) | (((expansionList.size()-1) << 4) & 0x30));
+	public void addExpansion(int ch, int inFlags, List<Integer> expansionList) {
+		ensurePage(ch >>> 8);
+		setFlags(ch, (byte) ((inFlags & 0xf) | (((expansionList.size()-1) << 4) & 0xf0)));
 
 		// Check for repeated definitions
-		if (primary[idx] != 0)
-			throw new ExitException(String.format("repeated code point %x", idx));
+		if (getPrimary(ch) != 0)
+			throw new ExitException(String.format("repeated code point %x", ch));
 
-		primary[idx] = (byte) (expansions.size() + 1);
-		secondary[idx] = 0;
-		tertiary[idx] = 0;
+		setPrimary(ch, (expansions.size() + 1));
+		setSecondary(ch,  0);
+		setTertiary(ch, 0);
 		maxExpSize = Math.max(maxExpSize, expansionList.size());
 
-		for (Byte b : expansionList) {
+		for (Integer b : expansionList) {
 			CodePosition cp = new CodePosition();
-			cp.setPrimary(primary[b & 0xff]);
-			cp.setSecondary(secondary[b & 0xff]);
-			cp.setTertiary((byte) (tertiary[b & 0xff] + 2));
+			cp.setPrimary((char) getPrimary(b & 0xff));
+
+			// Currently sort without secondary or tertiary differences to the base letters.
+			cp.setSecondary((byte) getSecondary(b & 0xff));
+			cp.setTertiary((byte) getTertiary(b & 0xff));
 			expansions.add(cp);
 		}
 	}
@@ -318,35 +452,165 @@ public class Sort {
 		return new SrtCollator(codepage);
 	}
 
+	public int getExpansionSize() {
+		return expansions.size();
+	}
+
+	public String toString() {
+		return String.format("sort cp=%d order=%08x", codepage, getSortOrderId());
+	}
+
+	private void setPrimary(int ch, int val) {
+		this.pages[ch >>> 8].setPrimary(ch, val);
+	}
+
+	private void setSecondary(int ch, int val) {
+		this.pages[ch >>> 8].setSecondary(ch, val);
+	}
+
+	private void setTertiary(int ch, int val) {
+		this.pages[ch >>> 8].setTertiary(ch, val);
+	}
+
+	private void setFlags(int ch, int val) {
+		this.pages[ch >>> 8].flags[ch & 0xff] = (byte) val;
+	}
+
+	public static Charset charsetFromCodepage(int codepage) {
+		Charset charset;
+		switch (codepage) {
+		case 0:
+			charset = Charset.forName("ascii");
+			break;
+		case 65001:
+			charset = Charset.forName("UTF-8");
+			break;
+		case 932:
+			// Java uses "ms932" for code page 932
+			// (Windows-31J, Shift-JIS + MS extensions)
+			charset = Charset.forName("ms932");
+			break;
+		default:
+			charset = Charset.forName("cp" + codepage);
+			break;
+		}
+		return charset;
+	}
+
+	public void setMulti(boolean multi) {
+		this.multi = multi;
+	}
+
+	public boolean isMulti() {
+		return multi;
+	}
+
+	public int getPos(int type, int ch) {
+		return pages[ch >>> 8].getPos(type, ch);
+	}
+
+	public int writePos(int type, int ch, byte[] outkey, int start) {
+		return pages[ch >>> 8].writePos(type, ch, outkey, start);
+	}
+
 	/**
-	 * Create a default sort that simply sorts by the values of the characters.
-	 * It has to pretend to be associated with a particular code page, otherwise
-	 * it will not be recognised at all.
+	 * Ensure that the given page exists in the page array.
 	 *
-	 * This is not likely to be very useful. You need to create a sort description for your language
-	 * to make things work properly.
-	 *
-	 * @return A default sort.
-	 * @param codepage The code page that we are pretending to be.
+	 * @param n The page index.
 	 */
-	public static Sort defaultSort(int codepage) {
-		Sort sort = new Sort();
-		for (int i = 1; i < 256; i++) {
-			sort.add(i, i, 0, 0, 0);
+	private void ensurePage(int n) {
+		assert n == 0 || isMulti();
+		if (this.pages[n] == null) {
+			this.pages[n] = new Page();
+			if (n > maxPage)
+				maxPage = n;
 		}
-		sort.charset = Charset.forName("ascii");
-		sort.encoder = sort.charset.newEncoder();
-		sort.setDescription("Default sort");
-		sort.setCodepage(codepage);
-		return sort;
 	}
 
-	public int getExpansionSize() {
-		return expansions.size();
+	/**
+	 * The max page, top 8+ bits of the character that we have information on.
+	 */
+	public int getMaxPage() {
+		return maxPage;
 	}
 
-	public String toString() {
-		return String.format("sort cp=%d order=%08x", codepage, getSortOrderId());
+	/**
+	 * @return True if there is at least one character with the given page/block number.
+	 */
+	public boolean hasPage(int p) {
+		return pages[p] != null;
+	}
+
+	/**
+	 * Holds the sort positions of a 256 character block.
+	 */
+	private static class Page {
+		private final char[] primary = new char[256];
+		private final byte[] secondary = new byte[256];
+		private final byte[] tertiary = new byte[256];
+		private final byte[] flags = new byte[256];
+
+		char getPrimary(int ch) {
+			return primary[ch & 0xff];
+		}
+
+		void setPrimary(int ch, int val) {
+			primary[ch & 0xff] = (char) val;
+		}
+
+		byte getSecondary(int ch) {
+			return secondary[ch & 0xff];
+		}
+
+		void setSecondary(int ch, int val) {
+			secondary[ch & 0xff] = (byte) val;
+		}
+
+		byte getTertiary(int ch) {
+			return tertiary[ch & 0xff];
+		}
+
+		void setTertiary(int ch, int val) {
+			tertiary[ch & 0xff] = (byte) val;
+		}
+
+		/**
+		 * Get the sort position data for a given strength for a character.
+		 * @param type The collation strength PRIMARY, SECONDARY etc.
+		 * @param ch The character.
+		 * @return The sorting weight for the given character.
+		 */
+		public int getPos(int type, int ch) {
+			switch (type) {
+			case Collator.PRIMARY:
+				return getPrimary(ch) & 0xffff;
+			case Collator.SECONDARY:
+				return getSecondary(ch) & 0xff;
+			case Collator.TERTIARY:
+				return getTertiary(ch) & 0xff;
+			default:
+				assert false : "bad collation type passed";
+				return 0;
+			}
+		}
+
+		/**
+		 * Write a sort position for a given character to a sort key.
+		 * @param strength The sort strength type.
+		 * @param ch The character.
+		 * @param outKey The output key.
+		 * @param start The offset into outKey, the new position is written here.
+		 * @return The new start offset, after the key information has been written.
+		 */
+		public int writePos(int strength, int ch, byte[] outKey, int start) {
+			int pos = getPos(strength, ch);
+			if (pos != 0) {
+				if (strength == Collator.PRIMARY)
+					outKey[start++] = (byte) ((pos >> 8) & 0xff); // for 2 byte charsets
+				outKey[start++] = (byte) (pos & 0xff);
+			}
+			return start;
+		}
 	}
 
 	/**
@@ -354,6 +618,8 @@ public class Sort {
 	 * strings against each other once.
 	 *
 	 * The sort key is better when the comparison must be done several times as in a sort operation.
+	 *
+	 * This implementation has the same effect when used for sorting as the sort keys.
 	 */
 	private class SrtCollator extends Collator {
 		private final int codepage;
@@ -363,54 +629,57 @@ public class Sort {
 		}
 
 		public int compare(String source, String target) {
-			CharBuffer in1 = CharBuffer.wrap(source);
-			CharBuffer in2 = CharBuffer.wrap(target);
-			byte[] bytes1;
-			byte[] bytes2;
-			try {
-				bytes1 = encoder.encode(in1).array();
-				bytes2 = encoder.encode(in2).array();
-			} catch (CharacterCodingException e) {
-				throw new ExitException("character encoding failed unexpectedly", e);
+			char[] chars1;
+			char[] chars2;
+			if (isMulti()) {
+				chars1 = source.toCharArray();
+				chars2 = target.toCharArray();
+			} else {
+				CharBuffer in1 = CharBuffer.wrap(source);
+				CharBuffer in2 = CharBuffer.wrap(target);
+				try {
+					byte[] bytes1 = encoder.encode(in1).array();
+					byte[] bytes2 = encoder.encode(in2).array();
+					chars1 = new char[bytes1.length];
+					for (int i = 0; i < bytes1.length; i++)
+						chars1[i] = (char) (bytes1[i] & 0xff);
+					chars2 = new char[bytes2.length];
+					for (int i = 0; i < bytes2.length; i++)
+						chars2[i] = (char) (bytes2[i] & 0xff);
+				} catch (CharacterCodingException e) {
+					throw new ExitException("character encoding failed unexpectedly", e);
+				}
 			}
 
 			int strength = getStrength();
-			int res = compareOneStrength(bytes1, bytes2, primary, Collator.PRIMARY);
+			int res = compareOneStrength(chars1, chars2, Collator.PRIMARY);
 
 			if (res == 0 && strength != PRIMARY) {
-				res = compareOneStrength(bytes1, bytes2, secondary, Collator.SECONDARY);
+				res = compareOneStrength(chars1, chars2, Collator.SECONDARY);
 				if (res == 0 && strength != SECONDARY) {
-					res = compareOneStrength(bytes1, bytes2, tertiary, Collator.TERTIARY);
+					res = compareOneStrength(chars1, chars2, Collator.TERTIARY);
 				}
 			}
 
-			if (res == 0) {
-				if (source.length() < target.length())
-					res = -1;
-				else if (source.length() > target.length())
-					res = 1;
-			}
 			return res;
 		}
 
 		/**
 		 * Compare the bytes against primary, secondary or tertiary arrays.
-		 * @param bytes1 Bytes for the first string in the codepage encoding.
-		 * @param bytes2 Bytes for the second string in the codepage encoding.
-		 * @param typePositions The strength array to use in the comparison.
+		 * @param char1 Bytes for the first string in the codepage encoding.
+		 * @param char2 Bytes for the second string in the codepage encoding.
 		 * @return Comparison result -1, 0 or 1.
 		 */
-		@SuppressWarnings({"AssignmentToForLoopParameter"})
-		private int compareOneStrength(byte[] bytes1, byte[] bytes2, byte[] typePositions, int type) {
+		private int compareOneStrength(char[] char1, char[] char2, int type) {
 			int res = 0;
 
-			PositionIterator it1 = new PositionIterator(bytes1, typePositions, type);
-			PositionIterator it2 = new PositionIterator(bytes2, typePositions, type);
+			PositionIterator it1 = new PositionIterator(char1, type);
+			PositionIterator it2 = new PositionIterator(char2, type);
 
-			while (it1.hasNext() && it2.hasNext()) {
+			while (it1.hasNext() || it2.hasNext()) {
 				int p1 = it1.next();
 				int p2 = it2.next();
-				
+
 				if (p1 < p2) {
 					res = -1;
 					break;
@@ -419,6 +688,7 @@ public class Sort {
 					break;
 				}
 			}
+
 			return res;
 		}
 
@@ -441,8 +711,7 @@ public class Sort {
 		}
 
 		class PositionIterator implements Iterator<Integer> {
-			private final byte[] bytes;
-			private final byte[] sortPositions;
+			private final char[] chars;
 			private final int len;
 			private final int type;
 
@@ -452,10 +721,9 @@ public class Sort {
 			private int expEnd;
 			private int expPos;
 
-			PositionIterator(byte[] bytes, byte[] sortPositions, int type) {
-				this.bytes = bytes;
-				this.sortPositions = sortPositions;
-				this.len = bytes.length;
+			PositionIterator(char[] chars, int type) {
+				this.chars = chars;
+				this.len = chars.length;
 				this.type = type;
 			}
 
@@ -463,32 +731,51 @@ public class Sort {
 				return pos < len || expPos != 0;
 			}
 
+			/**
+			 * Get the next sort order value for the input string. Does not ever return values
+			 * that are ignorable. Returns NO_ORDER at (and beyond) the end of the string, this
+			 * value sorts less than any other and so makes shorter strings sort first.
+			 * @return The next non-ignored sort position. At the end of the string it returns
+			 * NO_ORDER.
+			 */
 			public Integer next() {
 				int next;
 				if (expPos == 0) {
-					int in = pos++ & 0xff;
-					byte b = bytes[in];
-					int n = (flags[b & 0xff] >> 4) & 0x3;
-					if (n > 0) {
-						expStart = primary[b & 0xff] - 1;
-						expEnd = expStart + n;
-						expPos = expStart;
-						next = expansions.get(expPos).getPosition(type);
-
-						if (++expPos > expEnd)
-							expPos = 0;
 
-					} else {
-						for (next = sortPositions[bytes[in] & 0xff]; next == 0 && pos < len; ) {
-							next = sortPositions[bytes[pos++ & 0xff] & 0xff];
+					do {
+						if (pos >= len) {
+							next = NO_ORDER;
+							break;
 						}
-					}
+
+						// Get the first non-ignorable at this level
+						int c = chars[(pos++ & 0xff)];
+						if (!hasPage(c >>> 8)) {
+							next = 0;
+							continue;
+						}
+
+						int nExpand = (getFlags(c) >> 4) & 0x3;
+						// Check if this is an expansion.
+						if (nExpand > 0) {
+							expStart = getPrimary(c) - 1;
+							expEnd = expStart + nExpand;
+							expPos = expStart;
+							next = expansions.get(expPos).getPosition(type);
+
+							if (++expPos > expEnd)
+								expPos = 0;
+						} else {
+							next = getPos(type, c);
+						}
+
+					} while (next == 0);
 				} else {
 					next = expansions.get(expPos).getPosition(type);
 					if (++expPos > expEnd)
 						expPos = 0;
-
 				}
+
 				return next;
 			}
 
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java b/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
index 632efda..6969a51 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
@@ -13,6 +13,8 @@
 
 package uk.me.parabola.imgfmt.app.srt;
 
+import java.util.Arrays;
+
 /**
  * Sort key created from a Srt {@link Sort} object that allows strings to be compared according to that sorting
  * scheme.
@@ -48,10 +50,10 @@ class SrtSortKey<T> implements SortKey<T> {
 			}
 		}
 
-		if (this.key.length < other.key.length)
-			return -1;
-		else if (this.key.length > other.key.length)
-			return 1;
+		//if (this.key.length < other.key.length)
+		//	return -1;
+		//else if (this.key.length > other.key.length)
+		//	return 1;
 
 		if (second == other.second)
 			return 0;
@@ -64,4 +66,8 @@ class SrtSortKey<T> implements SortKey<T> {
 	public T getObject() {
 		return orig;
 	}
+
+	public String toString() {
+		return String.format("%s,%d", Arrays.toString(key), second);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java b/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
index 5bf334e..4319412 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/LinePreparer.java
@@ -25,7 +25,7 @@ import uk.me.parabola.log.Logger;
  * This class holds all of the calculations needed to encode a line into
  * the garmin format.
  */
-class LinePreparer {
+public class LinePreparer {
 	private static final Logger log = Logger.getLogger(LinePreparer.class);
 
 	// These are our inputs.
@@ -121,16 +121,17 @@ class LinePreparer {
 		for (int i = 0; i < deltas.length; i+=2) {
 			int dx = deltas[i];
 			int dy = deltas[i + 1];
-			if (dx == 0 && dy == 0)
-				continue;
-			
+			if (dx == 0 && dy == 0){
+				if (extraBit && nodes[i/2+1] == false && i+2 != deltas.length) // don't skip CoordNode
+					continue;
+			}
 			++numPointsEncoded;
 
 			if (log.isDebugEnabled())
 				log.debug("x delta", dx, "~", xbits);
 			assert dx >> xbits == 0 || dx >> xbits == -1;
 			if (xSameSign) {
-				bw.putn(abs(dx), xbits);
+				bw.putn(Math.abs(dx), xbits);
 			} else {
 				// catch inadvertent output of "magic" value that has
 				// sign bit set but other bits all 0
@@ -143,7 +144,7 @@ class LinePreparer {
 				log.debug("y delta", dy, ybits);
 			assert dy >> ybits == 0 || dy >> ybits == -1;
 			if (ySameSign) {
-				bw.putn(abs(dy), ybits);
+				bw.putn(Math.abs(dy), ybits);
 			} else {
 				// catch inadvertent output of "magic" value that has
 				// sign bit set but other bits all 0
@@ -208,9 +209,8 @@ class LinePreparer {
 		boolean yDiffSign = false; // The lat values have different sign
 		int xSign = 0;  // If all the same sign, then this 1 or -1 depending on +ve or -ve
 		int ySign = 0;  // As above for lat.
-		int xBits = 0;  // Number of bits needed for long
-		int yBits = 0;  // Number of bits needed for lat.
-
+		int minDx = Integer.MAX_VALUE, maxDx = 0;
+		int minDy = Integer.MAX_VALUE, maxDy = 0;
 		// index of first point in a series of identical coords (after shift)
 		int firstsame = 0;
 		for (int i = 0; i < numPointsToUse; i++) {
@@ -238,7 +238,7 @@ class LinePreparer {
 			lastLong = lon;
 			lastLat = lat;
 
-			if (dx != 0 || dy != 0)
+			if (dx != 0 || dy != 0 || (extraBit && co.getId() != 0))
 				firstsame = i;
 
 			/*
@@ -294,19 +294,23 @@ class LinePreparer {
 				}
 			}
 
-			// Find the maximum number of bits required to hold the value.
-			int nbits = bitsNeeded(dx);
-			if (nbits > xBits)
-				xBits = nbits;
-
-			nbits = bitsNeeded(dy);
-			if (nbits > yBits)
-				yBits = nbits;
-
+			// find largest delta values
+			if (dx < minDx)
+				minDx = dx;
+			if (dx > maxDx)
+				maxDx = dx;
+			if (dy < minDy)
+				minDy = dy;
+			if (dy > maxDy)
+				maxDy = dy;
+			
 			// Save the deltas
 			deltas[2*(i-1)] = dx;
 			deltas[2*(i-1) + 1] = dy;
 		}
+		// Find the maximum number of bits required to hold the delta values.
+		int xBits = Math.max(bitsNeeded(minDx), bitsNeeded(maxDx)); 
+		int yBits = Math.max(bitsNeeded(minDy), bitsNeeded(maxDy));
 
 		// Now we need to know the 'base' number of bits used to represent
 		// the value.  In decoding you start with that number and add various
@@ -358,8 +362,8 @@ class LinePreparer {
 	 * @param val The number for bit counting.
 	 * @return The number of bits required.
 	 */
-	private int bitsNeeded(int val) {
-		int n = abs(val);
+	public static int bitsNeeded(int val) {
+		int n = Math.abs(val);
 
 		int count = val < 0? 1: 0;
 		while (n != 0) {
@@ -369,13 +373,6 @@ class LinePreparer {
 		return count;
 	}
 
-	private int abs(int val) {
-		if (val < 0)
-			return -val;
-		else
-			return val;
-	}
-
 	public boolean isExtraBit() {
 		return extraBit;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
index 76df145..8b6c2d4 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
@@ -83,7 +83,7 @@ public class Polyline extends MapObject {
 		catch (AssertionError ae) {
 			log.error("Problem writing line (" + getClass() + ") of type 0x" + Integer.toHexString(getType()) + " containing " + points.size() + " points and starting at " + points.get(0).toOSMURL());
 			log.error("  Subdivision shift is " + getSubdiv().getShift() +
-					  " and its centre is at " + new Coord(getSubdiv().getLatitude(), getSubdiv().getLongitude()).toOSMURL());
+					  " and its centre is at " + getSubdiv().getCenter().toOSMURL());
 			log.error("  " + ae.getMessage());
 			if(roaddef != null)
 				log.error("  Way is " + roaddef);
@@ -164,7 +164,7 @@ public class Polyline extends MapObject {
 		catch (AssertionError ae) {
 			log.error("Problem writing line (" + getClass() + ") of type 0x" + Integer.toHexString(getType()) + " containing " + points.size() + " points and starting at " + points.get(0).toOSMURL());
 			log.error("  Subdivision shift is " + getSubdiv().getShift() +
-					  " and its centre is at " + new Coord(getSubdiv().getLatitude(), getSubdiv().getLongitude()).toOSMURL());
+					  " and its centre is at " + getSubdiv().getCenter().toOSMURL());
 			log.error("  " + ae.getMessage());
 			if(roaddef != null)
 				log.error("  Way is " + roaddef);
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java b/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
index b181e02..70a9590 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
@@ -482,10 +482,8 @@ public class RGNFileReader extends ImgReader {
 			line.addCoord(coord);
 		}
 		if (line instanceof Polygon){
-			int numPoints = line.getPoints().size();
 			// make sure that polygon is closed
-			if (line.getPoints().get(0).equals(line.getPoints().get(numPoints-1)) == false)
-				line.addCoord(line.getPoints().get(0));
+			line.addCoord(line.getPoints().get(0));
 		}
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
index 7443469..d8ee51d 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
@@ -124,12 +124,12 @@ public class Subdivision {
 		h = ((h + 1)/2 + mask) >> shift;
 		
 		if (w > 0x7fff) {
-			log.warn("Subdivision width is " + w + " at " + new Coord(latitude, longitude));
+			log.warn("Subdivision width is " + w + " at " + getCenter());
 			w = 0x7fff;
 		}
 
 		if (h > 0xffff) {
-			log.warn("Subdivision height is " + h + " at " + new Coord(latitude, longitude));
+			log.warn("Subdivision height is " + h + " at " + getCenter());
 			h = 0xffff;
 		}
 
@@ -435,7 +435,7 @@ public class Subdivision {
 	}
 
 	public String toString() {
-		return "Sub" + zoomLevel + '(' + new Coord(latitude, longitude).toOSMURL() + ')';
+		return "Sub" + zoomLevel + '(' + getCenter().toOSMURL() + ')';
 	}
 	/**
 	 * Get a type that shows if this area has lines, points etc.
@@ -636,5 +636,8 @@ public class Subdivision {
 		return (val >> shift);
 	}
 
-	
+
+	public Coord getCenter(){
+		return new Coord(getLatitude(),getLongitude());
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/sys/Directory.java b/src/uk/me/parabola/imgfmt/sys/Directory.java
index 4404bd1..7764940 100644
--- a/src/uk/me/parabola/imgfmt/sys/Directory.java
+++ b/src/uk/me/parabola/imgfmt/sys/Directory.java
@@ -156,8 +156,8 @@ class Directory {
 
 		// Get the number of blocks required for the directory entry representing the header.
 		// First calculate the number of blocks required for the directory entries.
-		int headerBlocks = (int) Math.ceil((startEntry + 1.0 + headerEntries) * Dirent.ENTRY_SIZE / blockSize);
-		int forHeader = (headerBlocks + Dirent.ENTRY_SIZE - 1)/Dirent.ENTRY_SIZE;
+		int headerBlocks = (int) Math.ceil((startEntry + 1.0 + headerEntries) * DirectoryEntry.ENTRY_SIZE / blockSize);
+		int forHeader = (headerBlocks + DirectoryEntry.ENTRY_SIZE - 1)/DirectoryEntry.ENTRY_SIZE;
 
 		log.debug("header blocks needed", forHeader);
 
@@ -166,7 +166,7 @@ class Directory {
 		assert forHeader == 1;
 
 		// Write the blocks that will will contain the header blocks.
-		chan.position(dirPosition + (long) forHeader * Dirent.ENTRY_SIZE);
+		chan.position(dirPosition + (long) forHeader * DirectoryEntry.ENTRY_SIZE);
 
 		for (DirectoryEntry dir : entries.values()) {
 			Dirent ent = (Dirent) dir;
diff --git a/src/uk/me/parabola/log/Logger.java b/src/uk/me/parabola/log/Logger.java
index b468730..ea69c14 100644
--- a/src/uk/me/parabola/log/Logger.java
+++ b/src/uk/me/parabola/log/Logger.java
@@ -184,6 +184,10 @@ public class Logger {
 		log.severe(tagMessage(o == null? "null" : o.toString()));
 	}
 
+	public void error(Object ... olist) {
+			arrayFormat(Level.SEVERE, olist);
+	}
+	
 	public void error(Object o, Throwable e) {
 		log.log(Level.SEVERE, tagMessage(o == null? "null" : o.toString()), e);
 	}
diff --git a/src/uk/me/parabola/mkgmap/CommandArgsReader.java b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
index f21bc54..b4ba2e9 100644
--- a/src/uk/me/parabola/mkgmap/CommandArgsReader.java
+++ b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
@@ -159,9 +159,7 @@ public class CommandArgsReader {
 		String value = opt.getValue();
 
 		if (validOptions != null && !validOptions.contains(option) && !opt.isExperimental()) {
-			Formatter f = new Formatter();
-			f.format("Invalid option: '%s'", option);
-			throw new ExitException(f.toString());
+			throw new ExitException(String.format("Invalid option: '%s'", option));
 		}
 
 		log.debug("adding option", option, value);
@@ -170,15 +168,23 @@ public class CommandArgsReader {
 		if (option.equals("mapname"))
 			mapnameWasSet = true;
 
-		if (option.equals("input-file")) {
+		switch (option) {
+		case "input-file":
 			log.debug("adding filename", value);
 			add(new Filename(value));
-		} else if (option.equals("read-config")) {
+			break;
+		case "read-config":
 			readConfigFile(value);
-		} else if (option.equals("latin1")) {
+			break;
+		case "latin1":
 			add(new CommandOption("code-page", "1252"));
-		} else {
+			break;
+		case "unicode":
+			add(new CommandOption("code-page", "65001"));
+			break;
+		default:
 			add(opt);
+			break;
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/build/MapArea.java b/src/uk/me/parabola/mkgmap/build/MapArea.java
index a3588c4..650b4bd 100644
--- a/src/uk/me/parabola/mkgmap/build/MapArea.java
+++ b/src/uk/me/parabola/mkgmap/build/MapArea.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.filters.FilterConfig;
@@ -32,8 +33,9 @@ import uk.me.parabola.mkgmap.general.MapDataSource;
 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.general.RoadNetwork;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 
 /**
  * A sub area of the map.  We have to divide the map up into areas to meet the
@@ -197,15 +199,15 @@ public class MapArea implements MapDataSource {
 					log.debug("area before", mapAreas[i].getBounds());
 			}
 
-			int xbase = areas[0].getMinLong();
-			int ybase = areas[0].getMinLat();
-			int dx = areas[0].getWidth();
-			int dy = areas[0].getHeight();
+			int xbase30 = areas[0].getMinLong() << Coord.DELTA_SHIFT;
+			int ybase30 = areas[0].getMinLat() << Coord.DELTA_SHIFT;
+			int dx30 = areas[0].getWidth() << Coord.DELTA_SHIFT;
+			int dy30 = areas[0].getHeight() << Coord.DELTA_SHIFT;
 			
 			boolean[] used = new boolean[nx * ny];
 			// Now sprinkle each map element into the correct map area.
 			for (MapPoint p : this.points) {
-				int pos = pickArea(mapAreas, p, xbase, ybase, nx, ny, dx, dy);
+				int pos = pickArea(mapAreas, p, xbase30, ybase30, nx, ny, dx30, dy30);
 				mapAreas[pos].addPoint(p);
 				used[pos] = true;
 			}
@@ -214,10 +216,10 @@ public class MapArea implements MapDataSource {
 			int areaIndex = 0;
 			for (MapLine l : this.lines) {
 				// Drop any zero sized lines.
-				if (l.getBounds().getMaxDimension() <= 0)
+				if (l instanceof MapRoad == false && l.getRect().height <= 0 && l.getRect().width <= 0)
 					continue;
 				if (useNormalSplit)
-					areaIndex = pickArea(mapAreas, l, xbase, ybase, nx, ny, dx, dy);
+					areaIndex = pickArea(mapAreas, l, xbase30, ybase30, nx, ny, dx30, dy30);
 				else 
 					areaIndex = ++areaIndex % mapAreas.length;
 				mapAreas[areaIndex].addLine(l);
@@ -226,7 +228,7 @@ public class MapArea implements MapDataSource {
 
 			for (MapShape e : this.shapes) {
 				if (useNormalSplit)
-					areaIndex = pickArea(mapAreas, e, xbase, ybase, nx, ny, dx, dy);
+					areaIndex = pickArea(mapAreas, e, xbase30, ybase30, nx, ny, dx30, dy30);
 				else 
 					areaIndex = ++areaIndex % mapAreas.length;
 				mapAreas[areaIndex].addShape(e);
@@ -460,7 +462,7 @@ public class MapArea implements MapDataSource {
 	 */
 	private void addPoint(MapPoint p) {
 		points.add(p);
-		addToBounds(p.getBounds());
+		addToBounds(p.getLocation());
 		addSize(p, p.hasExtendedType()? XT_POINT_KIND : POINT_KIND);
 	}
 
@@ -509,6 +511,22 @@ public class MapArea implements MapDataSource {
 			maxLon = l;
 	}
 
+	private void addToBounds(Coord co) {
+		int l = co.getLatitude();
+		if (l < minLat)
+			minLat = l;
+		if (l > maxLat)
+			maxLat = l;
+
+		l = co.getLongitude();
+		if (l < minLon)
+			minLon = l;
+		if (l > maxLon)
+			maxLon = l;
+	}
+
+	
+	
 	/**
 	 * Out of all the available areas, it picks the one that the map element
 	 * should be placed into.
@@ -519,31 +537,30 @@ public class MapArea implements MapDataSource {
 	 *
 	 * @param areas The available areas to choose from.
 	 * @param e The map element.
-	 * @param xbase The x coord at the origin
-	 * @param ybase The y coord of the origin
+	 * @param xbase30 The 30-bit x coord at the origin
+	 * @param ybase30 The 30-bit y coord of the origin
 	 * @param nx number of divisions.
 	 * @param ny number of divisions in y.
-	 * @param dx The size of each division (x direction)
-	 * @param dy The size of each division (y direction)
+	 * @param dx30 The size of each division (x direction)
+	 * @param dy30 The size of each division (y direction)
 	 * @return The index to areas where the map element fits.
 	 */
 	private int pickArea(MapArea[] areas, MapElement e,
-			int xbase, int ybase,
+			int xbase30, int ybase30,
 			int nx, int ny,
-			int dx, int dy)
+			int dx30, int dy30)
 	{
-		int x = e.getLocation().getLongitude();
-		int y = e.getLocation().getLatitude();
-
-		int xcell = (x - xbase) / dx;
-		int ycell = (y - ybase) / dy;
+		int x = e.getLocation().getHighPrecLon();
+		int y = e.getLocation().getHighPrecLat();
+		int xcell = (x - xbase30) / dx30;
+		int ycell = (y - ybase30) / dy30;
 
 		if (xcell < 0) {
-			log.info("xcell was", xcell, "x", x, "xbase", xbase);
+			log.info("xcell was", xcell, "x", x, "xbase", xbase30);
 			xcell = 0;
 		}
 		if (ycell < 0) {
-			log.info("ycell was", ycell, "y", y, "ybase", ybase);
+			log.info("ycell was", ycell, "y", y, "ybase", ybase30);
 			ycell = 0;
 		}
 		
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index c86884a..54ad0b5 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -71,6 +71,7 @@ import uk.me.parabola.mkgmap.filters.PreserveHorizontalAndVerticalLinesFilter;
 import uk.me.parabola.mkgmap.filters.RemoveEmpty;
 import uk.me.parabola.mkgmap.filters.RemoveObsoletePointsFilter;
 import uk.me.parabola.mkgmap.filters.RoundCoordsFilter;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 import uk.me.parabola.mkgmap.filters.SizeFilter;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
@@ -81,9 +82,8 @@ 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.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
-import uk.me.parabola.mkgmap.reader.osm.GType;
 import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
 import uk.me.parabola.util.Configurable;
 import uk.me.parabola.util.EnhancedProperties;
@@ -110,7 +110,6 @@ public class MapBuilder implements Configurable {
 	private List<String> copyrights = new ArrayList<String>();
 
 	private boolean doRoads;
-	private boolean routingErrorMsgPrinted;
 
 	private Locator locator;
 
@@ -133,6 +132,7 @@ public class MapBuilder implements Configurable {
 	private double reducePointError;
 	private double reducePointErrorPolygon;
 	private boolean mergeLines;
+	private boolean mergeShapes;
 
 	private boolean	poiAddresses;
 	private int		poiDisplayFlags;
@@ -164,6 +164,9 @@ public class MapBuilder implements Configurable {
 			reducePointErrorPolygon = reducePointError;
 		mergeLines = props.containsKey("merge-lines");
 
+		// undocumented option - usually used for debugging only
+		mergeShapes = props.getProperty("no-mergeshapes", false) == false;
+
 		makePOIIndex = props.getProperty("make-poi-index", false);
 
 		if(props.getProperty("poi-address") != null)
@@ -698,7 +701,7 @@ public class MapBuilder implements Configurable {
 	 * @param zoom The zoom level.
 	 * @return The new top level subdivision.
 	 */
-	private Subdivision makeTopArea(MapDataSource src, Map map, Zoom zoom) {
+	private static Subdivision makeTopArea(MapDataSource src, Map map, Zoom zoom) {
 		Subdivision topdiv = map.topLevelSubdivision(src.getBounds(), zoom);
 		topdiv.setLast(true);
 		return topdiv;
@@ -921,7 +924,7 @@ public class MapBuilder implements Configurable {
 			catch (AssertionError ae) {
 				log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL());
 				log.error("  Subdivision shift is " + div.getShift() +
-						  " and its centre is at " + new Coord(div.getLatitude(), div.getLongitude()).toOSMURL());
+						  " and its centre is at " + div.getCenter().toOSMURL());
 				log.error("  " + ae.getMessage());
 				continue;
 			}
@@ -972,7 +975,7 @@ public class MapBuilder implements Configurable {
 				catch (AssertionError ae) {
 					log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL());
 					log.error("  Subdivision shift is " + div.getShift() +
-							  " and its centre is at " + new Coord(div.getLatitude(), div.getLongitude()).toOSMURL());
+							  " and its centre is at " + div.getCenter().toOSMURL());
 					log.error("  " + ae.getMessage());
 					continue;
 				}
@@ -1022,7 +1025,7 @@ public class MapBuilder implements Configurable {
 		// Maybe more efficient if merging before creating subdivisions.
 		if (mergeLines) {
 			LineMergeFilter merger = new LineMergeFilter();
-			lines = merger.merge(lines);
+			lines = merger.merge(lines, res);
 		}
 		LayerFilterChain filters = new LayerFilterChain(config);
 		if (enableLineCleanFilters && (res < 24)) {
@@ -1067,6 +1070,12 @@ public class MapBuilder implements Configurable {
 		config.setLevel(div.getZoom().getLevel());
 		config.setRoutable(doRoads);
 		
+		if (mergeShapes){
+			ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res);
+			List<MapShape> mergedShapes = shapeMergeFilter.merge(shapes);
+			shapes = mergedShapes;
+		}
+		
 		LayerFilterChain filters = new LayerFilterChain(config);
 		if (enableLineCleanFilters && (res < 24)) {
 			filters.addFilter(new PreserveHorizontalAndVerticalLinesFilter());
@@ -1117,7 +1126,7 @@ public class MapBuilder implements Configurable {
 	 * @return The largest number of bits where we can still represent the
 	 *         whole map.
 	 */
-	private int getMaxBits(MapDataSource src) {
+	private static int getMaxBits(MapDataSource src) {
 		int topshift = Integer.numberOfLeadingZeros(src.getBounds().getMaxDimension());
 		int minShift = Math.max(CLEAR_TOP_BITS - topshift, 0);
 		return 24 - minShift;
@@ -1246,15 +1255,6 @@ public class MapBuilder implements Configurable {
 						pl.setLastSegment(false);
 					
 					roaddef.addPolylineRef(pl);
-				} else if (routingErrorMsgPrinted == false){
-					if (div.getZoom().getLevel() == 0 && GType.isRoutableLineType(line.getType())){
-						Coord start = line.getPoints().get(0);
-						log.error("Non-routable way with routable type " + GType.formatType(line.getType()) + " starting at " +
-								start.toOSMURL() + 
-								" is used for a routable map. This leads to routing errors. Try --check-styles to check the style.");
-						
-						routingErrorMsgPrinted = true;
-					}
 				}
 			}
 			map.addMapObject(pl);
diff --git a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
index e7e490b..645643f 100644
--- a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
@@ -542,7 +542,7 @@ public class GmapsuppBuilder implements Combiner {
 
 			log.info("total blocks for", bs, "is", totHeaderBlocks, "based on slots=", totHeaderEntries);
 
-			int reserveEntries = (int) Math.ceil(DIRECTORY_OFFSET_ENTRY + 1 + totHeaderEntries);
+			int reserveEntries = DIRECTORY_OFFSET_ENTRY + 1 + totHeaderEntries;
 			if (totBlocks + reserveEntries < 0xfffe && totHeaderBlocks <= ENTRY_SIZE) {
 				return new BlockInfo(bs, reserveEntries);
 			}
diff --git a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
index 1007fe1..a00a6f7 100644
--- a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
@@ -16,8 +16,6 @@ import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Deque;
@@ -64,7 +62,7 @@ public class MdrBuilder implements Combiner {
 	private MDRFile mdrFile;
 
 	// Push things onto this stack to have them closed in the reverse order.
-	private final Deque<Closeable> toClose = new ArrayDeque<Closeable>();
+	private final Deque<Closeable> toClose = new ArrayDeque<>();
 
 	// We write to a temporary file name, and then rename once all is OK.
 	private File tmpName;
@@ -104,7 +102,7 @@ public class MdrBuilder implements Combiner {
 		}
 
 		// Create the sort description
-		Sort sort = createSort(args.getCodePage());
+		Sort sort = SrtTextReader.sortForCodepage(args.getCodePage());
 
 		// Set the options that we are using for the mdr.
 		MdrConfig config = new MdrConfig();
@@ -151,31 +149,6 @@ public class MdrBuilder implements Combiner {
 	}
 
 	/**
-	 * Create the sort description for the mdr.  This is converted into a SRT which is included
-	 * in the mdr.img and also it is used to actually sort the text items within the file itself.
-	 *
-	 * We simply use the code page to locate a sorting description, we could have several for the same
-	 * code page for different countries for example.
-	 *
-	 * @param codepage The code page which is used to find a suitable sort description.
-	 * @return A sort description object.
-	 */
-	private Sort createSort(int codepage) {
-		String name = "sort/cp" + codepage + ".txt";
-		InputStream is = getClass().getClassLoader().getResourceAsStream(name);
-		if (is == null) {
-			return Sort.defaultSort(codepage);
-		}
-		try {
-			InputStreamReader r = new InputStreamReader(is, "utf-8");
-			SrtTextReader sr = new SrtTextReader(r);
-			return sr.getSort();
-		} catch (IOException e) {
-			return Sort.defaultSort(codepage);
-		}
-	}
-
-	/**
 	 * Adds a new map to the file.  We need to read in the img file and
 	 * extract all the information that can be indexed from it.
 	 *
@@ -212,7 +185,7 @@ public class MdrBuilder implements Combiner {
 	}
 
 	private Map<Integer, Mdr14Record> addCountries(MapReader mr) {
-		Map<Integer, Mdr14Record> countryMap = new HashMap<Integer, Mdr14Record>();
+		Map<Integer, Mdr14Record> countryMap = new HashMap<>();
 		List<Country> countries = mr.getCountries();
 		for (Country c : countries) {
 			if (c != null) {
@@ -224,7 +197,7 @@ public class MdrBuilder implements Combiner {
 	}
 
 	private Map<Integer, Mdr13Record> addRegions(MapReader mr, AreaMaps maps) {
-		Map<Integer, Mdr13Record> regionMap = new HashMap<Integer, Mdr13Record>();
+		Map<Integer, Mdr13Record> regionMap = new HashMap<>();
 
 		List<Region> regions = mr.getRegions();
 		for (Region region : regions) {
@@ -245,7 +218,7 @@ public class MdrBuilder implements Combiner {
 	private List<Mdr5Record> fetchCities(MapReader mr, AreaMaps maps) {
 		Map<Integer, Mdr5Record> cityMap = maps.cities;
 
-		List<Mdr5Record> cityList = new ArrayList<Mdr5Record>();
+		List<Mdr5Record> cityList = new ArrayList<>();
 		List<City> cities = mr.getCities();
 		for (City c : cities) {
 			int regionCountryNumber = c.getRegionCountryNumber();
@@ -402,7 +375,7 @@ public class MdrBuilder implements Combiner {
 	 * sufficient to link them all up.
 	 */
 	class AreaMaps {
-		private final Map<Integer, Mdr5Record> cities = new HashMap<Integer, Mdr5Record>();
+		private final Map<Integer, Mdr5Record> cities = new HashMap<>();
 		private Map<Integer, Mdr13Record> regions;
 		private Map<Integer, Mdr14Record> countries;
 		private List<Mdr5Record> cityList;
diff --git a/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java b/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java
index a9cac6f..55cf626 100644
--- a/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java
@@ -65,14 +65,8 @@ public class DouglasPeuckerFilter implements MapFilter {
 		List<Coord> points = line.getPoints();
 
 		// Create a new list to rewrite the points into. Don't alter the original one
-		List<Coord> coords = new ArrayList<Coord>(points.size());
+		List<Coord> coords = new ArrayList<>(points.size());
 		coords.addAll(points);
-
-//#if (Node version)
-//Don't touch Coords, which are nodes.
-//So points at crossings will not be moved
-		// For now simplify all points, which are not nodes
-		// and no start and no end point
 		// Loop runs downwards, as the list length gets modified while running
 		int endIndex = coords.size()-1;
 		if (level == 0 || line instanceof MapShape){
@@ -91,18 +85,13 @@ public class DouglasPeuckerFilter implements MapFilter {
 		}
 		// Simplify the rest
 		douglasPeucker(coords, 0, endIndex, maxErrorDistance);
-
-//#else Straight version
-//Do the douglasPeucker on the whole line. 
-//Deletes more points, but may lead to incorrect display of crossings at given high error distances
-/*		
-		douglasPeucker(coords, 0, n, maxErrorDistance);
-	*/	
-//#endif
-		MapLine newline = line.copy();
-
-		newline.setPoints(coords);
-		next.doFilter(newline);
+		if (coords.size() == points.size())
+			next.doFilter(line); // nothing changed, no need to copy 
+		else {
+			MapLine newline = line.copy();
+			newline.setPoints(coords);
+			next.doFilter(newline);
+		}
 	}
 
 	/**
@@ -124,30 +113,16 @@ public class DouglasPeuckerFilter implements MapFilter {
 
 		Coord a = points.get(startIndex);
 		Coord b = points.get(endIndex);
-		double ab = a.distance(b);
-
-		if (ab == 0) { // Start- and endpoint are the same
-			// Find point with highest distance to start- and endpoint
-			for (int i = endIndex-1; i > startIndex; i--) {
-				Coord p = points.get(i);
-				double distance = p.distance(a);
-				if (distance > maxDistance) {
-					maxDistance = distance;
-					maxIndex = i;
-				}
-			}
-		} else {
-			// Find point with highest distance to line between start- and endpoint by using herons formula.
-			for(int i = endIndex-1; i > startIndex; i--) {
-				Coord p = points.get(i);
-				double ap = p.distance(a);
-				double bp = p.distance(b);
-				double abpa = (ab+ap+bp)/2;
-				double distance = 2 * Math.sqrt(abpa * (abpa-ab) * (abpa-ap) * (abpa-bp)) / ab;
-				if (distance > maxDistance) {
-					maxDistance = distance;
-					maxIndex = i;
-				}
+		
+
+		// Find point with highest distance to line between start- and end-point.
+		// handle also closed or nearly closed lines and spikes on straight lines
+		for(int i = endIndex-1; i > startIndex; i--) {
+			Coord p = points.get(i);
+			double distance = p.shortestDistToLineSegment(a, b);
+			if (distance > maxDistance) {
+				maxDistance = distance;
+				maxIndex = i;
 			}
 		}
 		if (maxDistance > allowedError) {
@@ -157,16 +132,20 @@ public class DouglasPeuckerFilter implements MapFilter {
 		}
 		else {
 			// All points in tolerance, delete all of them.
-
-			// Remove the endpoint if it is the same as the start point
-			if (ab == 0 && points.get(endIndex).preserved() == false)
-				points.remove(endIndex);
-
+			// Remove the end-point if it is the same as the start point
+			if (a.highPrecEquals(b) && points.get(endIndex).preserved() == false)
+				endIndex++;
+
+			if (endIndex - startIndex > 4){
+				// faster than many repeated remove actions
+				points.subList(startIndex+1, endIndex).clear();  
+				return;
+			}
+			
 			// Remove the points in between
 			for (int i = endIndex - 1; i > startIndex; i--) {
 				points.remove(i);
 			}
 		}
 	}
-
 }
diff --git a/src/uk/me/parabola/mkgmap/filters/LineMergeFilter.java b/src/uk/me/parabola/mkgmap/filters/LineMergeFilter.java
index ccb6f6b..6c8fbe3 100644
--- a/src/uk/me/parabola/mkgmap/filters/LineMergeFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LineMergeFilter.java
@@ -29,9 +29,9 @@ public class LineMergeFilter{
 		// Merges the points in the second one
 		List<Coord> points1 = line1.getPoints();
 		List<Coord> points2 = line2.getPoints();
-		startPoints.remove(points1.get(0), line1);
-		endPoints.remove(points1.get(points1.size()-1), line1);
-		startPoints.remove(points2.get(0), line2);
+		startPoints.removeMapping(points1.get(0), line1);
+		endPoints.removeMapping(points1.get(points1.size() - 1), line1);
+		startPoints.removeMapping(points2.get(0), line2);
 		startPoints.add(points1.get(0), line2);
 		line2.insertPointsAtStart(points1);
 		linesMerged.remove(line1);
@@ -40,7 +40,7 @@ public class LineMergeFilter{
 	private void addPointsAtStart(MapLine line, List<Coord> additionalPoints) {
 		log.info("merged lines before " + line.getName());
 		List<Coord> points = line.getPoints();
-		startPoints.remove(points.get(0), line);
+		startPoints.removeMapping(points.get(0), line);
 		line.insertPointsAtStart(additionalPoints);
 		startPoints.add(points.get(0), line);
 	}
@@ -48,14 +48,18 @@ public class LineMergeFilter{
 	private void addPointsAtEnd(MapLine line, List<Coord> additionalPoints) {
 		log.info("merged lines after " + line.getName());
 		List<Coord> points = line.getPoints();
-		endPoints.remove(points.get(points.size()-1), line);
+		endPoints.removeMapping(points.get(points.size() - 1), line);
 		line.insertPointsAtEnd(additionalPoints);
 		endPoints.add(points.get(points.size()-1), line);
 	}
 
-	public List<MapLine> merge(List<MapLine> lines) {
+	//TODO: This routine has a side effect: it modifies some of the MapLine instances
+	// instead of creating copies. It seems that this has no bad effect, but it is not clean
+	public List<MapLine> merge(List<MapLine> lines, int res) {
 		linesMerged = new ArrayList<MapLine>(lines.size());	//better use LinkedList??
 		for (MapLine line : lines) {
+			if (line.getMinResolution() > res || line.getMaxResolution() < res)
+				continue;
 			
 			if (line.isRoad()){
 				linesMerged.add(line);
diff --git a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
index 65bdf15..f2c60f9 100644
--- a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
@@ -13,7 +13,11 @@
  
 package uk.me.parabola.mkgmap.filters;
 
+import java.util.Collections;
+import java.util.List;
+
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.trergn.LinePreparer;
 import uk.me.parabola.imgfmt.app.trergn.Subdivision;
 import uk.me.parabola.mkgmap.general.MapElement;
 import uk.me.parabola.mkgmap.general.MapLine;
@@ -21,7 +25,8 @@ import uk.me.parabola.mkgmap.general.MapShape;
 
 /**
  * This filter does more or less the same calculations as LinePreparer.calcDeltas   
- * It rejects lines that have not enough different points
+ * It rejects lines that have not enough different points, and it optimises
+ * shapes so that they require fewer bits in the img file.
  * @author GerdP
  *
  */
@@ -40,7 +45,7 @@ public class LinePreparerFilter implements MapFilter {
 
 	/**
 	 * @param element A map element that will be a line or a polygon.
-	 * @param next This is used to pass the (unchanged) element onward.
+	 * @param next This is used to pass the element onward.
 	 */
 	public void doFilter(MapElement element, MapFilterChain next) {
 		MapLine line = (MapLine) element;
@@ -54,6 +59,11 @@ public class LinePreparerFilter implements MapFilter {
 		int lastLat = 0;
 		int lastLong = 0;
 		int numPointsEncoded = 1;
+		// fields to keep track of the largest delta values  
+		int[] maxBits = {0,0};
+		int[] maxBits2nd = {0,0};
+		int[] maxBitsPos = {0,0};
+		
 		for (int i = 0; i < numPoints; i++) {
 			Coord co = line.getPoints().get(i);
 
@@ -76,16 +86,62 @@ public class LinePreparerFilter implements MapFilter {
 			lastLong = lon;
 			lastLat = lat;
 			if (dx == 0 && dy == 0){
-				continue;
+				if(!line.isRoad() || co.getId() == 0)
+					continue;
 			}
-				
 			++numPointsEncoded;
-			if (numPointsEncoded >= minPointsRequired)
+			if (numPointsEncoded >= minPointsRequired && element instanceof MapShape == false)
 				break;
+			// find out largest and 2nd largest delta for both dx and dy
+			for (int k = 0; k < 2; k++){
+				int nBits = LinePreparer.bitsNeeded((k==0) ? dx:dy);
+				if (nBits > maxBits2nd[k]){
+					if (nBits > maxBits[k]){
+						maxBits2nd[k] = maxBits[k];
+						maxBits[k] = nBits;
+						maxBitsPos[k] = i;
+					} 
+					else
+						maxBits2nd[k] = nBits;
+				}
+			}
+			
 		}		
 		if(numPointsEncoded < minPointsRequired)
 			return;
-		
+		if (minPointsRequired >= 3){
+			// check if we can optimise shape by rotating
+			// so that the line segment that requires the highest number of bits 
+			// is not encoded and thus fewer bits 
+			// are required for all points
+			// TODO: maybe add additional points to further reduce max. delta values
+			// or reverse order if largest delta is negative
+			int maxReduction = 0;
+			int rotation = 0;
+			
+			for (int k = 0; k < 2; k++){
+				int delta = maxBits[k] - maxBits2nd[k]; 
+				// prefer largest delta, then smallest rotation
+				if (delta > maxReduction || delta == maxReduction && rotation > maxBitsPos[k]){
+					maxReduction = delta;
+					rotation = maxBitsPos[k];
+				} 
+			}
+			/*
+			int savedBits = (numPoints-1 * maxReduction);
+			if (savedBits > 100){
+				System.out.println("rotation of shape saves " + savedBits + " bits");
+			}
+			*/
+			if (rotation != 0){
+				List<Coord> points = line.getPoints();
+				if (minPointsRequired == 4)
+					points.remove(numPoints-1);
+				Collections.rotate(points, -rotation);
+				if (minPointsRequired == 4)
+					points.add(points.get(0));
+			}
+		}
 		next.doFilter(element);
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java b/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
index 4851a45..3d7b879 100644
--- a/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LineSizeSplitterFilter.java
@@ -181,9 +181,7 @@ public class LineSizeSplitterFilter implements MapFilter {
 			int width = Math.abs( p1.getLongitude() - p2.getLongitude());
 			int height = Math.abs( p1.getLatitude() - p2.getLatitude());
 			if (width > maxSize || height > maxSize){
-				int midLon = (p1.getLongitude() + p2.getLongitude())/2;
-				int midLat = (p1.getLatitude() + p2.getLatitude())/2;
-				testedCoords.add(posToTest+1, new Coord(midLat,midLon));
+				testedCoords.add(posToTest+1, p1.makeBetweenPoint(p2, 0.5));
 				++posToTest;
 			}
 			else
diff --git a/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java b/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
index 42cee8f..31bf992 100644
--- a/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
@@ -72,7 +72,8 @@ public class LineSplitterFilter implements MapFilter {
 		log.debug("line has too many points, splitting");
 		if(line.isRoad() && level == 0 && isRoutable) {
 			MapRoad road = ((MapRoad)line);
-			log.debug("Way " + road.getRoadDef() + " has more than "+ MAX_POINTS_IN_LINE + " points and is about to be split");
+			if (log.isDebugEnabled())
+				log.debug("Way " + road.getRoadDef() + " has more than "+ MAX_POINTS_IN_LINE + " points and is about to be split");
 		} 
 
 		MapLine l = line.copy();
diff --git a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
index b261aa6..4a19c52 100644
--- a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
+++ b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
@@ -16,7 +16,7 @@
  */
 package uk.me.parabola.mkgmap.filters;
 
-import java.awt.*;
+import java.awt.Rectangle;
 import java.awt.geom.Area;
 import java.util.List;
 
@@ -29,32 +29,53 @@ import uk.me.parabola.util.Java2DConverter;
  */
 public class PolygonSplitterBase extends BaseFilter {
 	protected static final int MAX_SIZE = 0x7fff;
-
+	private int shift;
+	
+	public void init(FilterConfig config) {
+		shift = config.getShift();
+	}
+	
 	/**
 	 * Split the given shape and place the resulting shapes in the outputs list.
 	 * @param shape The original shape (that is too big).
 	 * @param outputs The output list.
 	 */
 	protected void split(MapShape shape, List<MapShape> outputs) {
-
+		// TODO: use different algo which will keep track of holes which are
+		// connected with the outer polygon
+		
 		// Convert to a awt area
-		Area a1 = Java2DConverter.createArea(shape.getPoints());
+		Area area = Java2DConverter.createArea(shape.getPoints());
 
 		// Get the bounds of this polygon
-		Rectangle bounds = a1.getBounds();
+		Rectangle bounds = area.getBounds();
 
 		if (bounds.isEmpty())
 			return;  // Drop it
+		
+		int half = 1 << (shift - 1);	// 0.5 shifted
+		int mask = ~((1 << shift) - 1); // to remove fraction bits
 
-		// Cut the bounding box into two rectangles
+		// Cut the bounding box into two rectangles. The position of the common
+		// line is rounded to the current resolution.
 		Rectangle r1;
 		Rectangle r2;
 		if (bounds.width > bounds.height) {
 			int halfWidth = bounds.width / 2;
+			if (shift != 0){
+				halfWidth = (halfWidth + half) & mask;
+				if (halfWidth == 0 || halfWidth == bounds.width)
+					halfWidth = bounds.width / 2;
+			}
 			r1 = new Rectangle(bounds.x, bounds.y, halfWidth, bounds.height);
 			r2 = new Rectangle(bounds.x + halfWidth, bounds.y, bounds.width - halfWidth, bounds.height);
 		} else {
 			int halfHeight = bounds.height / 2;
+			if (shift != 0){
+				halfHeight = (halfHeight + half) & mask;
+				if (halfHeight== 0 || halfHeight == bounds.height)
+					halfHeight = bounds.height / 2;
+			}
 			r1 = new Rectangle(bounds.x, bounds.y, bounds.width, halfHeight);
 			r2 = new Rectangle(bounds.x, bounds.y + halfHeight, bounds.width, bounds.height - halfHeight);
 		}
@@ -62,17 +83,17 @@ public class PolygonSplitterBase extends BaseFilter {
 		// Now find the intersection of these two boxes with the original
 		// polygon.  This will make two new areas, and each area will be one
 		// (or more) polygons.
-		Area a2 = (Area) a1.clone();
-		a1.intersect(new Area(r1));
-		a2.intersect(new Area(r2));
-
-		areaToShapes(shape, a1, outputs);
-		areaToShapes(shape, a2, outputs);
+		Area clipper = new Area(r1);
+		clipper.intersect(area);
+		areaToShapes(shape, clipper, outputs);
+		clipper = new Area(r2);
+		clipper.intersect(area);
+		areaToShapes(shape, clipper, outputs);
 	}
 
 	/**
 	 * Convert the area back into {@link MapShape}s.  It is possible that the
-	 * area is multiple discontiguous polygons, so you may append more than one
+	 * area is multiple discontinuous polygons, so you may append more than one
 	 * shape to the output list.
 	 *
 	 * @param origShape The original shape, this is only used as a prototype to
diff --git a/src/uk/me/parabola/mkgmap/filters/PreserveHorizontalAndVerticalLinesFilter.java b/src/uk/me/parabola/mkgmap/filters/PreserveHorizontalAndVerticalLinesFilter.java
index 11e747c..e778aa7 100644
--- a/src/uk/me/parabola/mkgmap/filters/PreserveHorizontalAndVerticalLinesFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/PreserveHorizontalAndVerticalLinesFilter.java
@@ -55,12 +55,6 @@ public class PreserveHorizontalAndVerticalLinesFilter implements MapFilter {
 				}
 				prev = last;
 			}
-			// if the way has the same point at each end, make sure
-			// that if either is preserved, they both are
-			if(first.equals(last) && first.preserved() != last.preserved()) {
-				first.preserved(true);
-				last.preserved(true);
-			}
 		}
 
 		next.doFilter(line);
diff --git a/src/uk/me/parabola/mkgmap/filters/RemoveObsoletePointsFilter.java b/src/uk/me/parabola/mkgmap/filters/RemoveObsoletePointsFilter.java
index 183d201..ba2d583 100644
--- a/src/uk/me/parabola/mkgmap/filters/RemoveObsoletePointsFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/RemoveObsoletePointsFilter.java
@@ -43,55 +43,78 @@ public class RemoveObsoletePointsFilter implements MapFilter {
 	 */
 	public void doFilter(MapElement element, MapFilterChain next) {
 		MapLine line = (MapLine) element;
-		int numPoints = line.getPoints().size();
+		List<Coord> points = line.getPoints();
+		int numPoints = points.size();
 		if (numPoints <= 1){
 			return;
 		}
-		
+		int requiredPoints = (line instanceof MapShape ) ? 4:2; 
 		List<Coord> newPoints = new ArrayList<Coord>(numPoints);
-		
-		Coord lastP = line.getPoints().get(0);
-		newPoints.add(lastP);
-		for(int i = 1; i < numPoints; i++) {
-			Coord newP = line.getPoints().get(i);
-			int last = newPoints.size()-1;
-			lastP = newPoints.get(last);
-			if (lastP.equals(newP)){
-				// only add the new point if it has different
-				// coordinates to the last point or is preserved
-				if (checkPreserved && line.isRoad()){
-					if (newP.preserved() == false)
-						continue;
-					else if (lastP.preserved() == false){
-						newPoints.set(last, newP); // replace last
-					}
-				} 
-				continue;
-			}
-			if (newPoints.size() > 1) {
-				switch (Utils.isStraight(newPoints.get(last-1), lastP, newP)){
-				case Utils.STRICTLY_STRAIGHT:
-					if (checkPreserved && lastP.preserved() && line.isRoad()){
-						// keep it
-					} else {
-						log.debug("found three consecutive points on strictly straight line");
-						newPoints.set(last, newP);
+		while (true){
+			boolean removedSpike = false;
+			numPoints = points.size();
+			
+
+			Coord lastP = points.get(0);
+			newPoints.add(lastP);
+			for(int i = 1; i < numPoints; i++) {
+				Coord newP = points.get(i);
+				int last = newPoints.size()-1;
+				lastP = newPoints.get(last);
+				if (lastP.equals(newP)){
+					// only add the new point if it has different
+					// coordinates to the last point or is preserved
+					if (checkPreserved && line.isRoad()){
+						if (newP.preserved() == false)
+							continue;
+						else if (lastP.preserved() == false){
+							newPoints.set(last, newP); // replace last
+						} 
+					} else  
 						continue;
+				}
+				if (newPoints.size() > 1) {
+					switch (Utils.isStraight(newPoints.get(last-1), lastP, newP)){
+					case Utils.STRICTLY_STRAIGHT:
+						if (checkPreserved && lastP.preserved() && line.isRoad()){
+							// keep it
+						} else {
+							log.debug("found three consecutive points on strictly straight line");
+							newPoints.set(last, newP);
+							continue;
+						}
+						break;
+					case Utils.STRAIGHT_SPIKE:
+						if (line instanceof MapShape){
+							log.debug("removing spike");
+							newPoints.remove(last);
+							removedSpike = true;
+							if (newPoints.get(last-1).equals(newP))
+								continue;
+						}
+						break;
+					default:
+						break;
 					}
-					break;
-				case Utils.STRAIGHT_SPIKE:
-					if (line instanceof MapShape){
-						log.debug("removing spike");
-						newPoints.remove(last);
-					}
-					break;
-				default:
-					break;
 				}
-			}
 
-			newPoints.add(newP);
+				newPoints.add(newP);
+			}
+			if (!removedSpike || newPoints.size() < requiredPoints)
+				break;
+			points = newPoints;
+			newPoints = new ArrayList<Coord>(points.size());
 		}
+		if (line instanceof MapShape && newPoints.size() > 3){
+			// check special case: shape starts with spike
+			if (Utils.isStraight(newPoints.get(0), newPoints.get(1), newPoints.get(newPoints.size()-2)) == Utils.STRICTLY_STRAIGHT){
+				newPoints.remove(0);
+				newPoints.set(newPoints.size()-1, newPoints.get(0));
+				if (newPoints.get(newPoints.size()-2).equals(newPoints.get(newPoints.size()-1)))
+					newPoints.remove(newPoints.size()-1);
+			}
+		}
+		
 		if (newPoints.size() != line.getPoints().size()){
 			if (line instanceof MapShape && newPoints.size() <= 3 || newPoints.size() <= 1)
 				return;
diff --git a/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
new file mode 100644
index 0000000..caeda7f
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.filters;
+
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.MapShape;
+import uk.me.parabola.mkgmap.osmstyle.WrongAngleFixer;
+import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
+import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.util.MultiHashMap;
+
+
+/**
+ * Merge shapes with same Garmin type and similar attributes if they have common 
+ * points. This reduces the number of shapes as well as the number of points.
+ * @author GerdP
+ *
+ */
+public class ShapeMergeFilter{
+	private static final Logger log = Logger.getLogger(ShapeMergeFilter.class);
+	private final int resolution;
+	private final ShapeHelper dupShape = new ShapeHelper(new ArrayList<Coord>(0)); 
+
+	public ShapeMergeFilter(int resolution) {
+		this.resolution = resolution;
+	}
+
+	public List<MapShape> merge(List<MapShape> shapes) {
+		if (shapes.size() <= 1)
+			return shapes;
+		int count = 0;
+		MultiHashMap<Integer, Map<MapShape, List<ShapeHelper>>> topMap = new MultiHashMap<Integer, Map<MapShape,List<ShapeHelper>>>();
+		List<MapShape> mergedShapes = new ArrayList<MapShape>();
+		for (MapShape shape: shapes) {
+			if (shape.getMinResolution() > resolution || shape.getMaxResolution() < resolution)
+				continue;
+			count++;
+			if (shape.getPoints().get(0) != shape.getPoints().get(shape.getPoints().size()-1)){
+				// should not happen here
+				log.error("shape is not closed with identical points", shape.getOsmid());
+				mergedShapes.add(shape);
+				continue;
+			}
+			List<Map<MapShape, List<ShapeHelper>>> sameTypeList = topMap.get(shape.getType());
+			ShapeHelper sh = new ShapeHelper(shape.getPoints());
+			sh.id = shape.getOsmid();
+			if (sh.areaTestVal == 0){
+				// should not happen here
+				log.error("ignoring shape with id", sh.id, "and type",
+						GType.formatType(shape.getType()), "at resolution", resolution + ", it", 
+						(shape.wasClipped() ?   "was clipped to" : "has"), 
+						shape.getPoints().size(), "points and has an empty area ");
+				continue;
+			}
+			if (sameTypeList.isEmpty()){
+				Map<MapShape, List<ShapeHelper>> lowMap = new LinkedHashMap<MapShape, List<ShapeHelper>>();
+				ArrayList<ShapeHelper> list = new ArrayList<ShapeHelper>();
+				list.add(sh);
+				lowMap.put(shape, list);
+				topMap.add(shape.getType(),lowMap);
+				continue;
+			}
+			for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
+				boolean added = false;
+				for (MapShape ms: lowMap.keySet()){
+					// we do not use isSimilar() here, as it compares minRes and maxRes as well
+					String s1 = ms.getName();
+					String s2 = shape.getName();
+					if (s1 == s2 || s1 != null && s1.equals(s2)){
+						List<ShapeHelper> list = lowMap.get(ms);
+						int oldSize = list.size();
+						list = addWithConnectedHoles(list, sh, ms.getType());
+						lowMap.put(ms, list);
+						if (list.size() < oldSize+1){
+							log.debug("shape with id", sh.id, "was merged", (oldSize+1 - list.size()), " time(s) at resolution", resolution);
+						}
+						added = true;
+						break;
+					}
+				}
+				if (!added){
+					ArrayList<ShapeHelper> list = new ArrayList<ShapeHelper>();
+					list.add(sh);
+					lowMap.put(shape, list);
+				}
+			}
+		}
+		
+		for (List<Map<MapShape, List<ShapeHelper>>> sameTypeList : topMap.values()){
+			for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
+				Iterator<Entry<MapShape, List<ShapeHelper>>> iter = lowMap.entrySet().iterator();
+				while (iter.hasNext()){
+					Entry<MapShape, List<ShapeHelper>> item = iter.next();
+					MapShape ms = item.getKey();
+					List<ShapeHelper> shapeHelpers = item.getValue();
+					for (ShapeHelper sh:shapeHelpers){
+						MapShape newShape = ms.copy();
+						
+						assert sh.getPoints().get(0) == sh.getPoints().get(sh.getPoints().size()-1);
+						if (sh.id == 0){
+							// this shape is the result of a merge
+							List<Coord> optimizedPoints = WrongAngleFixer.fixAnglesInShape(sh.getPoints());
+							if (optimizedPoints.isEmpty())
+								continue;
+							newShape.setPoints(optimizedPoints);
+							newShape.setOsmid(FakeIdGenerator.makeFakeId());
+						} else {
+							newShape.setPoints(sh.getPoints());
+							newShape.setOsmid(sh.id);
+						}
+						
+						mergedShapes.add(newShape);
+					}
+				}
+			}
+		}
+		log.info("merged shapes", count, "->", mergedShapes.size(), "at resolution", resolution);
+		return mergedShapes;
+	}
+
+	/**
+	 * Try to merge a shape with one or more of the shapes in the list.
+	 *  If it cannot be merged, it is added to the list.
+	 *  Holes in shapes are connected with the outer lines,
+	 *  so no following routine must use {@link Java2DConverter}
+	 *  to process these shapes.   
+	 * @param list list of shapes with equal type
+	 * @param toAdd new shape
+	 * @return new list of shapes, this might contain fewer (merged) elements
+	 */
+	private List<ShapeHelper> addWithConnectedHoles(List<ShapeHelper> list,
+			final ShapeHelper toAdd, final int type) {
+		assert toAdd.getPoints().size() > 3;
+		List<ShapeHelper> result = new ArrayList<ShapeHelper>(list.size()+1);
+		ShapeHelper shNew = new ShapeHelper(toAdd);
+		for (ShapeHelper shOld:list){
+			if (shOld.getBounds().intersects(shNew.getBounds()) == false){
+				result.add(shOld);
+				continue;
+			}
+			ShapeHelper mergeRes = tryMerge(shOld, shNew, type);
+			if (mergeRes == shOld){
+				result.add(shOld);
+				continue;
+			} else if (mergeRes != null){
+				shNew = mergeRes;
+			}
+			if (shNew == dupShape){
+				log.warn("ignoring duplicate shape with id", toAdd.id, "at",  toAdd.getPoints().get(0).toOSMURL(), "with type", GType.formatType(type), "for resolution", resolution);
+				return list; // nothing to do
+			}
+		}
+		if (shNew != null && shNew != dupShape)
+			result.add(shNew);
+		if (result.size() > list.size()+1 )
+			log.error("result list size is wrong", list.size(), "->", result.size());
+		return result;
+	}
+
+	/**
+	 * Find out if two shapes have common points. If yes, merge them.
+	 * @param sh1 1st shape1
+	 * @param sh2 2st shape2
+	 * @param type Garmin type (used for log messages)
+	 * @return merged shape or 1st shape if no common point found or {@code dupShape} 
+	 * if both shapes describe the same area. 
+	 */
+	private ShapeHelper tryMerge(ShapeHelper sh1, ShapeHelper sh2, int type) {
+		
+		// both clockwise or both ccw ?
+		boolean sameDir = sh1.areaTestVal > 0 && sh2.areaTestVal > 0 || sh1.areaTestVal < 0 && sh2.areaTestVal < 0;
+		
+		List<Coord> points1, points2;
+		if (sh2.getPoints().size()> sh1.getPoints().size()){
+			points1 = sh2.getPoints();
+			points2 = sh1.getPoints();
+		} else {
+			points1 = sh1.getPoints();
+			points2 = sh2.getPoints();
+		}
+		// find all coords that are common in the two shapes 
+		IntArrayList sh1PositionsToCheck = new IntArrayList();
+		IntArrayList sh2PositionsToCheck = new IntArrayList();
+
+		findCommonCoords(points1, points2, sh1PositionsToCheck, sh2PositionsToCheck); 		
+		if (sh1PositionsToCheck.isEmpty()){
+			return sh1;
+		}
+		if (sh2PositionsToCheck.size() + 1 >= points2.size()){
+			// all points are identical, might be a duplicate
+			// or a piece that fills a hole 
+			if (points1.size() == points2.size() && Math.abs(sh1.areaTestVal) == Math.abs(sh2.areaTestVal)){ 
+				// it is a duplicate, we can ignore it
+				// XXX this might fail if one of the shapes is self intersecting
+				return dupShape;
+			}
+		}
+		List<Coord> merged = null; 
+		if (points1.size() + points2.size() - 2*sh1PositionsToCheck.size() < PolygonSplitterFilter.MAX_POINT_IN_ELEMENT){
+			merged = mergeLongestSequence(points1, points2, sh1PositionsToCheck, sh2PositionsToCheck, sameDir);
+			if (merged.get(0) != merged.get(merged.size()-1))
+				merged = null;
+			else if (merged.size() > PolygonSplitterFilter.MAX_POINT_IN_ELEMENT){
+				// don't merge because merged polygon would be split again
+				log.info("merge rejected: merged shape has too many points " + merged.size());
+				merged = null;
+			}
+		}
+		ShapeHelper shm = null;
+		if (merged != null){
+			shm = new ShapeHelper(merged);
+			if (Math.abs(shm.areaTestVal) != Math.abs(sh1.areaTestVal) + Math.abs(sh2.areaTestVal)){
+				log.warn("merging shapes skipped for shapes near", points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), 
+						"(maybe overlapping shapes?)");
+				merged = null;
+				shm = null;
+			} else {
+				if (log.isInfoEnabled()){
+					log.info("merge of shapes near",points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), 
+							"reduces number of points from",(points1.size()+points2.size()),
+							"to",merged.size());
+				}
+			}
+		}
+		if (shm != null)
+			return shm;
+		if (merged == null)
+			return sh1;
+		return null;
+	}
+
+	/**
+	 * Find the common Coord instances and save their positions for both shapes.
+	 * @param s1 shape 1
+	 * @param s2 shape 2
+	 * @param s1PositionsToCheck will contain common positions in shape 1   
+	 * @param s2PositionsToCheck will contain common positions in shape 2
+	 */
+	private void findCommonCoords(List<Coord> s1, List<Coord> s2,
+			IntArrayList s1PositionsToCheck,
+			IntArrayList s2PositionsToCheck) {
+		Map<Coord, Integer> s2PosMap = new IdentityHashMap<>(s2.size() - 1);
+		
+		for (int i = 0; i+1 < s1.size(); i++){
+		    Coord co = s1.get(i);
+		    co.setPartOfShape2(false);
+		}
+		for (int i = 0; i+1 < s2.size(); i++){
+		    Coord co = s2.get(i);
+		    co.setPartOfShape2(true);
+		    s2PosMap.put(co, i); 
+		}
+		
+		int start = 0;
+		while(start < s1.size()){
+			Coord co = s1.get(start);
+			if (!co.isPartOfShape2())
+				break;
+			start++;
+		}
+		int pos = start+1;
+		int tested = 0;
+		while(true){
+			if (pos+1 >= s1.size())
+				pos = 0;
+			Coord co = s1.get(pos);
+			if (++tested >= s1.size())
+				break;
+			if (co.isPartOfShape2()){
+				s1PositionsToCheck.add(pos);
+				Integer posInSh2 = s2PosMap.get(co);
+				assert posInSh2 != null;
+				s2PositionsToCheck.add(posInSh2);
+			}
+			pos++;
+		}
+		return;
+	} 	
+	
+	/**
+	 * Finds the longest sequence of common points in two shapes.
+	 * @param points1 list of Coord instances that describes the 1st shape 
+	 * @param points2 list of Coord instances that describes the 2nd shape
+	 * @param sh1PositionsToCheck positions in the 1st shape that are common
+	 * @param sh2PositionsToCheck positions in the 2nd shape that are common
+	 * @param sameDir true if both shapes are clockwise or both are ccw
+	 * @return the merged shape or null if no points are common.
+	 */
+	private List<Coord> mergeLongestSequence(List<Coord> points1, List<Coord> points2, IntArrayList sh1PositionsToCheck,
+			IntArrayList sh2PositionsToCheck, boolean sameDir) {
+		if (sh1PositionsToCheck.isEmpty())
+			return null;
+		int s1Size = points1.size(); 
+		int s2Size = points2.size();
+		int longestSequence = 0;
+		int startOfLongestSequence = 0;
+		int length = 0;
+		int start = -1;
+		int n1 = sh1PositionsToCheck.size();
+		
+		assert sh2PositionsToCheck.size() == n1;
+		boolean inSequence = false;
+		for (int i = 0; i+1 < n1; i++){
+			int pred1 = sh1PositionsToCheck.getInt(i);
+			int succ1 = sh1PositionsToCheck.getInt(i+1);
+			if (Math.abs(succ1-pred1) == 1 || pred1+2 == s1Size && succ1 == 0 || succ1+2 == s1Size && pred1 == 0 ){
+				// found sequence in s1
+				int pred2 = sh2PositionsToCheck.getInt(i);
+				int succ2 = sh2PositionsToCheck.getInt(i+1);
+				if (Math.abs(succ2-pred2) == 1 || pred2+2 == s2Size && succ2 == 0 || succ2+2 == s2Size && pred2 == 0 ){
+					// found common sequence
+					if (start < 0)
+						start = i;
+					inSequence = true;
+					length++; 
+				} else {
+					inSequence = false;
+				}
+			} else {
+				inSequence = false;
+			}
+			if (!inSequence){
+				if (length > longestSequence){
+					longestSequence = length;
+					startOfLongestSequence = start;
+				}
+				length = 0;
+				start = -1;
+			}
+		}
+		if (length > longestSequence){
+			longestSequence = length;
+			startOfLongestSequence = start;
+		}
+		// now merge the shapes. The longest sequence of common points is removed.
+		// The remaining points are connected in the direction of the 1st shape.
+		List<Coord> merged = new ArrayList<Coord>(s1Size + s2Size - 2*longestSequence -1);
+		int s1Pos = sh1PositionsToCheck.getInt(startOfLongestSequence+longestSequence);
+		for (int i = 0; i < s1Size - longestSequence - 1; i++){
+			merged.add(points1.get(s1Pos));
+			s1Pos++;
+			if (s1Pos+1 >= s1Size)
+				s1Pos = 0;
+		}
+		int s2Pos = sh2PositionsToCheck.getInt(startOfLongestSequence);
+		int s2Step = sameDir ? 1:-1;
+		for (int i = 0; i < s2Size - longestSequence; i++){
+			merged.add(points2.get(s2Pos));
+			s2Pos += s2Step;
+			if (s2Pos < 0) 
+				s2Pos = s2Size-2;
+			else if (s2Pos+1 >= s2Size)
+				s2Pos = 0;
+		}
+//		if (merged.get(0).equals(new Coord(2438126,342573))){
+//			GpxCreator.createGpx("e:/ld/s1", points1);
+//			GpxCreator.createGpx("e:/ld/s2", points2);
+//			GpxCreator.createGpx("e:/ld/merged", merged);
+//			long dd = 4;
+//		}
+		return merged;
+	}
+ 	
+	private class ShapeHelper{
+		final private List<Coord> points;
+		long id; // TODO: remove debugging aid
+		long areaTestVal;
+		private final Area bounds;
+
+		public ShapeHelper(List<Coord> merged) {
+			this.points = merged;
+			areaTestVal = calcAreaSizeTestVal(points);
+			bounds = prep();
+		}
+
+		public ShapeHelper(ShapeHelper other) {
+			this.points = new ArrayList<>(other.getPoints());
+			this.areaTestVal = other.areaTestVal;
+			this.id = other.id;
+			this.bounds = new Area(other.getBounds().getMinLat(), 
+					other.getBounds().getMinLong(), 
+					other.getBounds().getMaxLat(), 
+					other.getBounds().getMaxLong());
+		}
+
+		public List<Coord> getPoints() {
+//			return Collections.unmodifiableList(points); // too slow, use only while testing
+			return points;
+		}
+		
+		public Area getBounds(){
+			return bounds;
+		}
+		/**
+		 * Calculates a unitless number that gives a value for the size
+		 * of the area and the direction (clockwise/ccw)
+		 * 
+		 */
+		Area prep() {
+			int minLat = Integer.MAX_VALUE;
+			int maxLat = Integer.MIN_VALUE;
+			int minLon = Integer.MAX_VALUE;
+			int maxLon = Integer.MIN_VALUE;
+			for (Coord co: points) {
+				if (co.getLatitude() > maxLat)
+					maxLat = co.getLatitude();
+				if (co.getLatitude() < minLat)
+					minLat = co.getLatitude();
+				if (co.getLongitude() > maxLon)
+					maxLon = co.getLongitude();
+				if (co.getLongitude() < minLon)
+					minLon = co.getLongitude();
+			}
+			return new Area(minLat, minLon, maxLat, maxLon);
+		}
+	}
+	private final static long smallArea = 1L<<6 * 1L<<6;
+	
+	/**
+	 * Calculate the high precision area size test value.  
+	 * @param points
+	 * @return area size in high precision map units * 2.
+	 * The value is >= 0 if the shape is clockwise, else < 0   
+	 */
+	public static long calcAreaSizeTestVal(List<Coord> points){
+		if (points.size() < 4)
+			return 0; // straight line cannot enclose an area
+		if (points.get(0).highPrecEquals(points.get(points.size()-1)) == false){
+			log.error("shape is not closed");
+			return 0;
+		}
+		Iterator<Coord> polyIter = points.iterator();
+		Coord c2 = polyIter.next();
+		long signedAreaSize = 0;
+		while (polyIter.hasNext()) {
+			Coord c1 = c2;
+			c2 = polyIter.next();
+			signedAreaSize += (long) (c2.getHighPrecLon() + c1.getHighPrecLon())
+					* (c1.getHighPrecLat() - c2.getHighPrecLat());
+		}
+		if (Math.abs(signedAreaSize) < smallArea){
+			log.debug("very small shape near", points.get(0).toOSMURL(), "signed area in high prec map units:", signedAreaSize );
+		}
+		return signedAreaSize;
+	}
+}
+
diff --git a/src/uk/me/parabola/mkgmap/filters/SmoothingFilter.java b/src/uk/me/parabola/mkgmap/filters/SmoothingFilter.java
index 45b2d1c..a90fb1d 100644
--- a/src/uk/me/parabola/mkgmap/filters/SmoothingFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/SmoothingFilter.java
@@ -155,7 +155,7 @@ public class SmoothingFilter implements MapFilter {
 
 		public Coord getAverageCoord() {
 			assert count > 0;
-			return new Coord(avlat / count, avlon / count);
+			return new Coord(avlat / count, avlon / count); // TODO high prec?
 		}
 
 		public void add(Coord co) {
diff --git a/src/uk/me/parabola/mkgmap/general/AreaClipper.java b/src/uk/me/parabola/mkgmap/general/AreaClipper.java
index b9bad47..43fba40 100644
--- a/src/uk/me/parabola/mkgmap/general/AreaClipper.java
+++ b/src/uk/me/parabola/mkgmap/general/AreaClipper.java
@@ -36,6 +36,10 @@ public class AreaClipper implements Clipper {
 	}
 
 	public void clipLine(MapLine line, LineAdder collector) {
+		if (bbox == null || bbox.insideBoundary(line.getBounds())){
+			collector.add(line);
+			return;
+		}
 		List<List<Coord>> list = LineClipper.clip(bbox, line.getPoints());
 		if (list == null) {
 			collector.add(line);
@@ -49,6 +53,10 @@ public class AreaClipper implements Clipper {
 	}
 
 	public void clipShape(MapShape shape, MapCollector collector) {
+		if (bbox == null || bbox.contains(shape.getBounds())){
+			collector.addShape(shape);
+			return;
+		}
 		List<List<Coord>> list = PolygonClipper.clip(bbox, shape.getPoints());
 		if (list == null) {
 			collector.addShape(shape);
@@ -56,6 +64,7 @@ public class AreaClipper implements Clipper {
 			for (List<Coord> lco : list) {
 				MapShape nshape = new MapShape(shape);
 				nshape.setPoints(lco);
+				nshape.setClipped(true);
 				collector.addShape(nshape);
 			}
 		}
diff --git a/src/uk/me/parabola/mkgmap/general/LevelInfo.java b/src/uk/me/parabola/mkgmap/general/LevelInfo.java
index 9f2cadb..6ea3d0e 100644
--- a/src/uk/me/parabola/mkgmap/general/LevelInfo.java
+++ b/src/uk/me/parabola/mkgmap/general/LevelInfo.java
@@ -62,16 +62,19 @@ public class LevelInfo implements Comparable<LevelInfo> {
 		for (String s : desc) {
 			String[] keyVal = s.split("[=:]");
 			if (keyVal == null || keyVal.length < 2) {
-				System.err.println("incorrect level specification " + levelSpec);
-				continue;
+				throw new ExitException("Error: incorrect level specification " + levelSpec);
 			}
 
 			try {
 				int key = Integer.parseInt(keyVal[0]);
+				if (key < 0 || key > 16)
+					throw new ExitException("Error: Level value out of range 0-16: " + s);
 				int value = Integer.parseInt(keyVal[1]);
+				if (value <= 0 || value > 24)
+					throw new ExitException("Error: Resolution value out of range 0-24: " + s);
 				levels[count] = new LevelInfo(key, value);
 			} catch (NumberFormatException e) {
-				System.err.println("Levels specification not all numbers " + keyVal[count]);
+				throw new ExitException("Error: Levels specification not all numbers: " + levelSpec + " check " + s);
 			}
 			count++;
 		}
diff --git a/src/uk/me/parabola/mkgmap/general/LineClipper.java b/src/uk/me/parabola/mkgmap/general/LineClipper.java
index a5f029d..f0d82ec 100644
--- a/src/uk/me/parabola/mkgmap/general/LineClipper.java
+++ b/src/uk/me/parabola/mkgmap/general/LineClipper.java
@@ -28,7 +28,6 @@ import uk.me.parabola.imgfmt.app.Coord;
  * @see <a href="http://www.skytopia.com/project/articles/compsci/clipping.html">A very clear explaination of the Liang-Barsky algorithm</a>
  */
 public class LineClipper {
-
 	/**
 	 * Clips a polyline by the given bounding box.  This may produce several
 	 * separate lines if the line meanders in and out of the box.
@@ -78,7 +77,7 @@ public class LineClipper {
 		// lines from it.
 		for (int i = 0; i <= coords.size() - 2; i++) {
 			Coord[] pair = {coords.get(i), coords.get(i+1)};
-			if (pair[0].equals(pair[1])) {
+			if (pair[0].highPrecEquals(pair[1])) {
 				continue;
 			}
 			Coord[] clippedPair = clip(a, pair);
@@ -87,12 +86,12 @@ public class LineClipper {
 		
 		// in case the coords build a closed way the first and the last clipped line 
 		// might have to be joined
-		if (seg.ret.size() >= 2 && coords.get(0).equals(coords.get(coords.size()-1))) {
+		if (seg.ret.size() >= 2 && coords.get(0) == coords.get(coords.size()-1)) {
 			List<Coord> firstSeg = seg.ret.get(0);
 			List<Coord> lastSeg = seg.ret.get(seg.ret.size()-1);
 			// compare the first point of the first segment with the last point of 
 			// the last segment
-			if (firstSeg.get(0).equals(lastSeg.get(lastSeg.size()-1))) {
+			if (firstSeg.get(0).equals(lastSeg.get(lastSeg.size()-1))) { //TODO : equal, ident or highPrecEqual? 
 				// they are the same so the two segments should be joined
 				lastSeg.addAll(firstSeg.subList(1, firstSeg.size()));
 				seg.ret.remove(0);
@@ -126,12 +125,13 @@ public class LineClipper {
 		if (a.insideBoundary(ends[0]) && a.insideBoundary(ends[1])) {
 			return (nullIfInside ? null : ends);
 		}
-		
-		int x0 = ends[0].getLongitude();
-		int y0 = ends[0].getLatitude();
+		Coord lowerLeft = new Coord(a.getMinLat(),a.getMinLong());
+		Coord upperRight = new Coord(a.getMaxLat(),a.getMaxLong());
+		int x0 = ends[0].getHighPrecLon();
+		int y0 = ends[0].getHighPrecLat();
 
-		int x1 = ends[1].getLongitude();
-		int y1 = ends[1].getLatitude();
+		int x1 = ends[1].getHighPrecLon();
+		int y1 = ends[1].getHighPrecLat();
 
 		int dx = x1 - x0;
 		int dy = y1 - y0;
@@ -139,22 +139,22 @@ public class LineClipper {
 		double[] t = {0, 1};
 
 		int p = -dx;
-		int q = -(a.getMinLong() - x0);
+		int q = -(lowerLeft.getHighPrecLon() - x0);
 		boolean scrap = checkSide(t, p, q);
 		if (scrap) return null;
 
 		p = dx;
-		q = a.getMaxLong() - x0;
+		q = upperRight.getHighPrecLon() - x0;
 		scrap = checkSide(t, p, q);
 		if (scrap) return null;
 
 		p = -dy;
-		q = -(a.getMinLat() - y0);
+		q = -(lowerLeft.getHighPrecLat() - y0);
 		scrap = checkSide(t, p, q);
 		if (scrap) return null;
 
 		p = dy;
-		q = a.getMaxLat() - y0;
+		q = upperRight.getHighPrecLat() - y0;
 		scrap = checkSide(t, p, q);
 		if (scrap) return null;
 
@@ -171,11 +171,10 @@ public class LineClipper {
 			// line requires clipping so create a new end point and if
 			// its position (in map coordinates) is different from the
 			// original point, use the new point as a boundary node
-			Coord new0 = new Coord(calcCoord(y0, dy, t[0]), calcCoord(x0, dx, t[0]));
-
+			Coord new0 = Coord.makeHighPrecCoord(calcCoord(y0, dy, t[0]), calcCoord(x0, dx, t[0]));
 			// check the maths worked out
 			assert a.onBoundary(new0) : "New boundary point at " + new0.toString() + " not on boundary of [" + a.getMinLat() + ", " + a.getMinLong() + ", " + a.getMaxLat() + ", " + a.getMaxLong() + "]";
-			if(!new0.equals(orig0))
+			if(!new0.highPrecEquals(orig0))
 				ends[0] = new0;
 			ends[0].setOnBoundary(true);
 		}
@@ -192,11 +191,11 @@ public class LineClipper {
 			// line requires clipping so create a new end point and if
 			// its position (in map coordinates) is different from the
 			// original point, use the new point as a boundary node
-			Coord new1 = new Coord(calcCoord(y0, dy, t[1]), calcCoord(x0, dx, t[1]));
-
+			Coord new1 = Coord.makeHighPrecCoord(calcCoord(y0, dy, t[1]), calcCoord(x0, dx, t[1])); 
+			
 			// check the maths worked out
 			assert a.onBoundary(new1) : "New boundary point at " + new1.toString() + " not on boundary of [" + a.getMinLat() + ", " + a.getMinLong() + ", " + a.getMaxLat() + ", " + a.getMaxLong() + "]";
-			if(!new1.equals(orig1))
+			if(!new1.highPrecEquals(orig1))
 				ends[1] = new1;
 			ends[1].setOnBoundary(true);
 		}
@@ -216,8 +215,8 @@ public class LineClipper {
 		// are equal could catch the situation where although t[0] and
 		// t[1] differ, the coordinates come out the same for both
 		// points
-
-		if(t[0] >= t[1] || ends[0].equals(ends[1]))
+		
+		if(t[0] >= t[1] || ends[0].highPrecEquals(ends[1]))
 			return null;
 
 		return ends;
diff --git a/src/uk/me/parabola/mkgmap/general/MapCollector.java b/src/uk/me/parabola/mkgmap/general/MapCollector.java
index 176f667..322523a 100644
--- a/src/uk/me/parabola/mkgmap/general/MapCollector.java
+++ b/src/uk/me/parabola/mkgmap/general/MapCollector.java
@@ -17,7 +17,7 @@
 package uk.me.parabola.mkgmap.general;
 
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 
 
 /**
@@ -75,11 +75,11 @@ public interface MapCollector {
 	 * no left turn.
 	 * @param exceptMask For exceptions eg. no-left-turn except for buses.
 	 */
-	public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask);
+	public int addRestriction(GeneralRouteRestriction grr);
 
 	/**
 	 * Add a through route to the map. 
 	 *
 	 */
-	public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB);
+	public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB);
 }
diff --git a/src/uk/me/parabola/mkgmap/general/MapDataSource.java b/src/uk/me/parabola/mkgmap/general/MapDataSource.java
index 2fe5984..9ff9af5 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDataSource.java
@@ -19,6 +19,7 @@ package uk.me.parabola.mkgmap.general;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 
 /**
diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index 8ff6839..3b69f3f 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -24,11 +24,15 @@ import java.util.Map;
 import uk.me.parabola.imgfmt.Utils;
 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.net.GeneralRouteRestriction;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 import uk.me.parabola.imgfmt.app.trergn.PointOverview;
 import uk.me.parabola.imgfmt.app.trergn.PolygonOverview;
 import uk.me.parabola.imgfmt.app.trergn.PolylineOverview;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+import uk.me.parabola.mkgmap.reader.osm.GType;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
@@ -37,6 +41,8 @@ import uk.me.parabola.util.EnhancedProperties;
  * @author Steve Ratcliffe
  */
 public class MapDetails implements MapCollector, MapDataSource {
+	private static final Logger log = Logger.getLogger(MapDetails.class);
+	
 	private final List<MapLine> lines = new ArrayList<MapLine>();
 	private final List<MapShape> shapes = new ArrayList<MapShape>();
 	private final List<MapPoint> points = new ArrayList<MapPoint>();
@@ -96,9 +102,15 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 * @param shape The polygon to add.
 	 */
 	public void addShape(MapShape shape) {
-		if (shape.getPoints().isEmpty())
+		if (shape.getPoints().size() < 4)
 			return;
-
+		if (ShapeMergeFilter.calcAreaSizeTestVal(shape.getPoints()) == 0){
+			log.info("ignoring shape with id", shape.getOsmid(), "and type",
+					GType.formatType(shape.getType()) + ", it", 
+					(shape.wasClipped() ?   "was clipped to" : "has"), 
+					shape.getPoints().size(), "points and has an empty area ");
+			return;
+		}
 		int type;
 		if(shape.hasExtendedType())
 			type = shape.getType();
@@ -111,15 +123,15 @@ public class MapDetails implements MapCollector, MapDataSource {
 	}
 
 	public void addRoad(MapRoad road) {
-		roadNetwork.addRoad(road);
+		roadNetwork.addRoad(road.getRoadDef(), road.getPoints());
 		addLine(road);
 	}
 
-	public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
-		roadNetwork.addRestriction(fromNode, toNode, viaNode, exceptMask);
+	public int addRestriction(GeneralRouteRestriction grr) {
+		return roadNetwork.addRestriction(grr);
 	}
 
-	public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
+	public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) {
 		roadNetwork.addThroughRoute(junctionNodeId, roadIdA, roadIdB);
 	}
 
@@ -199,7 +211,7 @@ public class MapDetails implements MapCollector, MapDataSource {
 		return ovlist;
 	}
 
-	private void updateOverview(Map<Integer, Integer> overviews, int type, int minResolution) {
+	private static void updateOverview(Map<Integer, Integer> overviews, int type, int minResolution) {
 		Integer prev = overviews.get(type);
 		if (prev == null || minResolution < prev)
 			overviews.put(type, minResolution);
diff --git a/src/uk/me/parabola/mkgmap/general/MapLine.java b/src/uk/me/parabola/mkgmap/general/MapLine.java
index 72dff38..2dd2485 100644
--- a/src/uk/me/parabola/mkgmap/general/MapLine.java
+++ b/src/uk/me/parabola/mkgmap/general/MapLine.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.general;
 
+import java.awt.Rectangle;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
@@ -34,6 +35,7 @@ public class MapLine extends MapElement {
 	private List<Coord> points;
 	private boolean direction; // set if direction is important.
 	private boolean skipSizeFilter;
+	private boolean wasClipped;
 	private int minLat = Integer.MAX_VALUE;
 	private int minLong = Integer.MAX_VALUE;
 	private int maxLat = Integer.MIN_VALUE;
@@ -71,7 +73,7 @@ public class MapLine extends MapElement {
 		Coord last = null;
 		for (Coord co : points) {
 			if (last != null && last.equals(co))
-				log.info("Line " + getName() + " has consecutive identical points at " + co.toDegreeString() + " (discarding)");
+				log.info("Line " + getName() + " has consecutive equal points at " + co.toDegreeString());
 			else {
 				addToBounds(co);
 				last = co;
@@ -80,10 +82,10 @@ public class MapLine extends MapElement {
 	}
 
 	public void insertPointsAtStart(List<Coord> additionalPoints) {
+		assert points.get(0).equals(additionalPoints.get(additionalPoints.size()-1));
 		testForConsecutivePoints(additionalPoints);
 		points.get(0).preserved(true);
-		points.addAll(0, additionalPoints);
-		points.remove(additionalPoints.size()-1);	//End node exists now twice
+		points.addAll(0, additionalPoints.subList(0, additionalPoints.size()-1));
 	}
 
 	public void insertPointsAtEnd(List<Coord> additionalPoints) {
@@ -114,6 +116,14 @@ public class MapLine extends MapElement {
 	}
 
 
+	public boolean wasClipped() {
+		return wasClipped;
+	}
+
+	public void setClipped(boolean wasClipped) {
+		this.wasClipped = wasClipped;
+	}
+
 	/**
 	 * Get the mid-point of the bounding box for this element.  This is as good
 	 * an indication of 'where the element is' as any.  Previously we just
@@ -123,7 +133,7 @@ public class MapLine extends MapElement {
 	 * @return The mid-point of the bounding box.
 	 */
 	public Coord getLocation() {
-		return new Coord((minLat + maxLat) / 2, (minLong + maxLong) / 2);
+		return new Coord((minLat + maxLat) / 2, (minLong + maxLong) / 2);// high prec not needed
 	}
 
 	/**
@@ -154,4 +164,10 @@ public class MapLine extends MapElement {
 		return new Area(minLat, minLong, maxLat, maxLong);
 	}
 
+	/**
+	 * @return bounding box that can have 0 height or width
+	 */
+	public Rectangle getRect(){
+		return new Rectangle(minLong, minLat, maxLong-minLong, maxLat-minLat);
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java b/src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java
deleted file mode 100644
index 061b417..0000000
--- a/src/uk/me/parabola/mkgmap/general/MapPointFastFindMap.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2009 Bernhard Heibler
- * 
- *  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.
- * 
- * 	This is multi-map to store city information for the Address Locator
- *  tt provides also a fast tile based nearest point search function
- *
- *
- * Author: Bernhard Heibler
- * Create date: 02-Jan-2009
- */
-
-package uk.me.parabola.mkgmap.general;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import uk.me.parabola.imgfmt.app.Coord;
-
-public class MapPointFastFindMap{
-
-	private final Map<String, ArrayList<MapPoint>> map  = new HashMap<String, ArrayList<MapPoint>>();
-	private final Map<Long,ArrayList<MapPoint>> posMap = new HashMap<Long,ArrayList<MapPoint>>();
-	private final ArrayList<MapPoint> points  =  new ArrayList<MapPoint>();
-
-	private static final  long POS_HASH_DIV = 8000;  	// the smaller -> more tiles 
-	private static final  long POS_HASH_MUL = 10000;	// multiplier for latitude to create hash
-
-	public MapPoint put(String name, MapPoint p)
-	{
-		ArrayList<MapPoint> list;
-		
-		if(name != null)
-		{
-			list = map.get(name);
-		
-			if(list == null){
-
-				list = new ArrayList<MapPoint>();
-				list.add(p);		
-				map.put(name, list);
-			}
-			else
-				list.add(p);
-			
-			points.add(p);
-		}
-		
-		long posHash  =  getPosHashVal(p.getLocation().getLatitude(), p.getLocation().getLongitude());
-	
-		list = posMap.get(posHash);
-		
-		if(list == null)
-		{
-			list = new ArrayList<MapPoint>();
-			list.add(p);		
-			posMap.put(posHash, list);
-		}
-		else
-			list.add(p);
-		
-		return p;
-	}
-
-	public Collection<MapPoint> getList(String name)
-	{
-		return map.get(name);
-	}
-	   
-	public long size()
-	{
-		return points.size();
-	}
-
-
-	public MapPoint findNextPoint(MapPoint p)
-	{
-		/* tile based search 
-			
-		to prevent expensive linear search over all points we put the points
-		into tiles. We just search the tiles the point is in linear and the 
-		surrounding tiles. If we don't find a point we have to search further
-		around the central tile
-
-		*/
-
-		MapPoint nextPoint = null;
-		
-		if(posMap.size() < 1)  // No point in list
-		   return nextPoint;
-		
-		long centLatitIdx = p.getLocation().getLatitude()  / POS_HASH_DIV ;
-		long centLongiIdx = p.getLocation().getLongitude() / POS_HASH_DIV ;
-		long delta = 1;
-
-		double minDist = Double.MAX_VALUE;
-		do
-		{
-			// in the first step we only check our tile and the tiles surrounding us
-			
-			for(long latitIdx = centLatitIdx - delta; latitIdx <= centLatitIdx + delta; latitIdx++)
-		    for(long longiIdx = centLongiIdx - delta; longiIdx <= centLongiIdx + delta; longiIdx++)
-		    {
-		    	if(delta < 2 
-						|| latitIdx == centLatitIdx - delta 
-						|| latitIdx == centLatitIdx + delta
-						|| longiIdx == centLongiIdx - delta
-						|| longiIdx == centLongiIdx + delta)
-					{
-
-						long posHash = latitIdx * POS_HASH_MUL + longiIdx;
-
-						ArrayList<MapPoint> list = posMap.get(posHash);
-
-						if(list != null)
-						{
-			    
-							for (MapPoint actPoint: list)
-							{
-								double distance =  actPoint.getLocation().distanceInDegreesSquared(p.getLocation());
-
-								if(distance < minDist)
-								{
-									nextPoint = actPoint;
-									minDist = distance;
-									
-								}
-							}
-						}
-					}
-			}
-			delta ++; // We have to look in tiles fairer away
-		}
-		while(nextPoint == null); 
-	 
-		return nextPoint;
-	}
-	
-	public MapPoint findPointInShape(MapShape shape, int pointType, String poiName) {
-		List<Coord>	points = shape.getPoints();
-		MapPoint nextPoint = null;
-				
-		if(posMap.size() < 1)  // No point in list
-			return nextPoint;
-
-		long lastHashValue = -1;
-		for (Coord point : points) {
-			long posHash = getPosHashVal(point.getLatitude(), point.getLongitude());
-
-			if (posHash == lastHashValue) // Have we already checked this tile ?
-				continue;
-
-			lastHashValue = posHash;
-
-			ArrayList<MapPoint> list = posMap.get(posHash);
-
-			if (list != null) {
-				for (MapPoint actPoint : list) {
-					boolean checkThisPoint = false;
-					
-					if (pointType == 0 || actPoint.getType() == pointType)
-						checkThisPoint = true;
-					
-					if(MapPoint.isCityType(pointType) && actPoint.isCity()	&& 
-							actPoint.getName() != null && poiName != null)
-					{
-						// Check for city name pois in that shape
-						// Since the types might not be exactly the same we
-						// check for all places pois with the same name
-						
-						checkThisPoint = actPoint.getName().equalsIgnoreCase(poiName);
-					}
-							
-					if (checkThisPoint && shape.contains(actPoint.getLocation()))
-						return actPoint;
-				}
-			}
-		}
-	
-		return null;		
-	}
-	
-	private long getPosHashVal(long lat, long lon)
-	{
-		long latitIdx  =  lat /  POS_HASH_DIV ;
-		long longiIdx  =  lon /  POS_HASH_DIV ; 
-		
-		return latitIdx * POS_HASH_MUL + longiIdx;
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java b/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java
index 010761c..bd96cd3 100644
--- a/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java
+++ b/src/uk/me/parabola/mkgmap/general/MapPointKdTree.java
@@ -153,9 +153,9 @@ public class MapPointKdTree {
 		// do we have to search the other part of the tree?
 		Coord test;
 		if (useLongitude)
-			test = new Coord(p.getLocation().getLatitude(),tree.point.getLocation().getLongitude());
+			test = Coord.makeHighPrecCoord(p.getLocation().getHighPrecLat(), tree.point.getLocation().getHighPrecLon());
 		else
-			test = new Coord(tree.point.getLocation().getLatitude(),p.getLocation().getLongitude());
+			test = Coord.makeHighPrecCoord(tree.point.getLocation().getHighPrecLat(), p.getLocation().getHighPrecLon());
 		if (test.distanceInDegreesSquared(p.getLocation()) < minDist){
 			if (continueWithLeft) 
 				nextPoint = findNextPoint(p, tree.left, !useLongitude);
diff --git a/src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java b/src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java
deleted file mode 100644
index 497e9c1..0000000
--- a/src/uk/me/parabola/mkgmap/general/MapPointMultiMap.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2009 Bernhard Heibler
- * 
- *  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.
- * 
- * 	This is multimap to store city information for the Address Locator
- *
- *
- * Author: Bernhard Heibler
- * Create date: 02-Jan-2009
- */
-
-package uk.me.parabola.mkgmap.general;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-public class MapPointMultiMap {
-
-	private final Map<String,ArrayList<MapPoint>> map  = new HashMap<String, ArrayList<MapPoint>>();
-
-	public MapPoint put(String name, MapPoint p) {
-		ArrayList<MapPoint> list = map.get(name);
-		
-		if(list == null){
-
-		   list = new ArrayList<MapPoint>();
-		   list.add(p);		
-		   map.put(name, list);
-		}
-		else
-		   list.add(p);
-		
-		return p;
-	}
-
-	public MapPoint get(String name) {
-		ArrayList<MapPoint> list = map.get(name);
-		
-		if(list != null)		
-			return list.get(0);
-		else
-			return null;
-	}
-	   
-	public Collection<MapPoint> getList(String name)
-	{
-		return map.get(name);
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/general/MapRoad.java b/src/uk/me/parabola/mkgmap/general/MapRoad.java
index ed67756..ed870bc 100644
--- a/src/uk/me/parabola/mkgmap/general/MapRoad.java
+++ b/src/uk/me/parabola/mkgmap/general/MapRoad.java
@@ -89,8 +89,7 @@ public class MapRoad extends MapLine {
 		roadDef.setSynthesised(s);
 	}
 
-	// XXX: currently passing PolishMapSource-internal format
-	public void setAccess(boolean[] access) {
+	public void setAccess(byte access) {
 		roadDef.setAccess(access);
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/general/MapShape.java b/src/uk/me/parabola/mkgmap/general/MapShape.java
index 670e43a..54c462f 100644
--- a/src/uk/me/parabola/mkgmap/general/MapShape.java
+++ b/src/uk/me/parabola/mkgmap/general/MapShape.java
@@ -15,11 +15,6 @@
  */
 package uk.me.parabola.mkgmap.general;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import uk.me.parabola.imgfmt.app.Coord;
-
 /**
  * A shape or polygon is just the same as a line really as far as I can tell.
  * There are some things that you cannot do with them semantically.
@@ -27,12 +22,16 @@ import uk.me.parabola.imgfmt.app.Coord;
  * @author Steve Ratcliffe.
  */
 public class MapShape extends MapLine {// So top code can link objects from here
-
+	private long osmid; //TODO: remove debug aid
 	public MapShape() {
+		osmid = 0;
+	}
+	public MapShape(long id) {
+		osmid = id;
 	}
-
 	MapShape(MapShape s) {
 		super(s);
+		this.osmid = s.osmid;
 	}
 
 	public MapShape copy() {
@@ -43,144 +42,12 @@ public class MapShape extends MapLine {// So top code can link objects from here
 		throw new IllegalArgumentException(
 				"can't set a direction on a polygon");
 	}
-	
-	/**
-	 * Checks if a point is contained within this shape. Points on the
-	 * edge of the shape are considered inside.
-	 * 
-	 * @param co point to check
-	 * @return true if point is in shape, false otherwise
-	 */
-	public boolean contains(Coord co) {
-		return contains(this.getPoints(), co, true);
-	}
-	
-	/**
-	 * Checks if a point is contained within a shape.
-	 * 
-	 * @param points points that define the shape
-	 * @param target point to check
-	 * @param onLineIsInside if a point on the line should be considered inside the shape
-	 * @return true if point is contained within the shape, false if the target point is outside the shape
-	 */
-	private static boolean contains(List<Coord> points, Coord target, boolean onLineIsInside) {
-		// implementation of the Ray casting algorithm as described here:
-		// http://en.wikipedia.org/wiki/Point_in_polygon
-		// with inspiration from:
-		// http://www.visibone.com/inpoly/
-		if (points.size() < 3)
-			return false;
-
-		// complete the shape if we're dealing with a MapShape that is not closed
-		Coord start = points.get(0);
-		Coord end = points.get(points.size() - 1);
-		if (!start.equals(end)) {
-			// make copy of the shape's geometry 
-			List<Coord> pointsTemp = new ArrayList<Coord>(points.size() + 1);
-			for (Coord coord : points) {
-				pointsTemp.add(new Coord(coord.getLatitude(), coord.getLongitude()));
-			}
-			pointsTemp.add(new Coord(start.getLatitude(), start.getLongitude()));
-			points = pointsTemp;
-		}
-
-		int xtarget = target.getLatitude();
-		int ytarget = target.getLongitude();
 
-		boolean inside = false;for (int i = 0; i < points.size() - 1; i++) {
-
-			// apply transformation points to change target point to (0,0)
-			int x0 = points.get(i).getLatitude() - xtarget;
-			int y0 = points.get(i).getLongitude() - ytarget;
-			int x1 = points.get(i+1).getLatitude() - xtarget;
-			int y1 = points.get(i+1).getLongitude() - ytarget;
-
-			// ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily
-			if (x0 > x1) {
-				int xtemp = x0;
-				int ytemp = y0;
-				x0 = x1;
-				y0 = y1;
-				x1 = xtemp;
-				y1 = ytemp;
-			}
-
-			// use (0,0) as target because points already transformed
-			if (isPointOnLine(x0, y0, x1, y1, 0, 0))
-				return onLineIsInside;
-
-			// explanation of if statement 
-			//
-			// (x0 < 0 && x1 >= 0):
-			// are the x values between the y axis? only include points from the right
-			// with this check so that corners aren't counted twice 
-			// 
-			// (y0 * (x1 - x0) > (y1 - y0) * x0):
-			// from y  = mx + b: 
-			//   => b = y0 ((y1 - y0) / (x1 - x0)) * x0
-			// for intersection,    b > 0
-			// from y = mx + b,     b = y - mx
-			//                  =>  y - mx > 0
-			//                  => y0 - ((y1 - y0) / (x1 - x0)) * x0 > 0
-			//                  => y0 > ((y1 - y0) / (x1 - x0)) * x0
-			// from 'if (x0 > x1)',  x1 >= x0
-			//                  => x1 - x0 >=0
-			//                  => y0 * (x1 - x0) > (y1 - y0) * x0
-			if ((x0 < 0 && x1 >= 0) && (y0 * (x1 - x0)) > ((y1 - y0) * x0))
-				inside = !inside;
-		}
-
-		return inside;
+	public void setOsmid(long osmid) {
+		this.osmid = osmid;
 	}
 
-	/**
-	 * Checks if a point is on a line.
-	 * 
-	 * @param x0 x value of first point in line
-	 * @param y0 y value of first point in line
-	 * @param x1 x value of second point in line
-	 * @param y1 y value of second point in line
-	 * @param xt x value of target point
-	 * @param yt y value of target point
-	 * @return return true if point is on the line, false if the point isn't on the line
-	 */
-	private static boolean isPointOnLine(int x0, int y0, int x1, int y1, int xt, int yt) {
-		// this implementation avoids using doubles
-		// apply transformation points to change target point to (0,0)
-		x0 -= xt;
-		y0 -= yt;
-		x1 -= xt;
-		y1 -= yt;
-
-		// ensure that x0 is smaller than x1 so that we can just check to see if the line intersects the y axis easily
-		if (x0 > x1) {
-			int xtemp = x0;
-			int ytemp = y0;
-			x0 = x1;
-			y0 = y1;
-			x1 = xtemp;
-			y1 = ytemp;
-		}
-
-		// if a point is on the edge of shape (on a line), it's considered outside the shape
-		// special case if line is on y-axis
-		if (x0 == 0 && x1 == 0) {
-			// ensure that y0 is smaller than y1 so that we can just check if the line intersects the x axis
-			if (y0 > y1) {
-				int ytemp = y0;
-				y0 = y1;
-				y1 = ytemp;
-			}
-			// test to see if we have a vertical line touches x-axis
-			if (y0 <= 0 && y1 >= 0)
-				return true;
-			// checks if point is on the line, see comments in contain() for derivation of similar 
-			// formula - left as an exercise to the reader ;)
-		} else if ((x0 <= 0 && x1 >= 0) && (y0 * (x1 - x0)) == ((y1 - y0) * x0)) {
-			return true;
-		}
-		return false;
+	public long getOsmid() {
+		return osmid;
 	}
-					
-					
 }
diff --git a/src/uk/me/parabola/mkgmap/general/PolygonClipper.java b/src/uk/me/parabola/mkgmap/general/PolygonClipper.java
index 060d7dd..36d7eb3 100644
--- a/src/uk/me/parabola/mkgmap/general/PolygonClipper.java
+++ b/src/uk/me/parabola/mkgmap/general/PolygonClipper.java
@@ -16,8 +16,11 @@
  */
 package uk.me.parabola.mkgmap.general;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
 import java.util.List;
 
+import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.util.Java2DConverter;
@@ -29,7 +32,6 @@ import uk.me.parabola.util.Java2DConverter;
  * @author Steve Ratcliffe
  */
 public class PolygonClipper {
-
 	/**
 	 * Clip the input polygon to the given area.
 	 * @param bbox The bounding box.
@@ -53,13 +55,28 @@ public class PolygonClipper {
 		}
 		if (!foundOutside)
 			return null;
-
+		Long2ObjectOpenHashMap<Coord> map = new Long2ObjectOpenHashMap<>(coords.size());
+		for (int i = 1; i < coords.size(); i++){
+			Coord co = coords.get(i);
+			map.put(Utils.coord2Long(co), co);
+		}
 		java.awt.geom.Area bbarea = Java2DConverter.createBoundsArea(bbox); 
 		java.awt.geom.Area shape = Java2DConverter.createArea(coords);
 
 		shape.intersect(bbarea);
 
-		return Java2DConverter.areaToShapes(shape);
+		List<List<Coord>> shapes = Java2DConverter.areaToShapes(shape);
+		for (List<Coord> sh: shapes){
+			for (int i = 0; i < sh.size(); i++){
+				Coord co = sh.get(i);
+				Coord origCoord = map.get(Utils.coord2Long(co));
+				if (origCoord != null)
+					sh.set(i, origCoord);
+				else 
+					map.put(Utils.coord2Long(co), co);
+			}
+		}
+		return shapes;
 	}
 
 }
diff --git a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
deleted file mode 100644
index 0580b44..0000000
--- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * 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: 13-Jul-2008
- */
-package uk.me.parabola.mkgmap.general;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.CoordNode;
-import uk.me.parabola.imgfmt.app.net.NOD1Part;
-import uk.me.parabola.imgfmt.app.net.RoadDef;
-import uk.me.parabola.imgfmt.app.net.RouteArc;
-import uk.me.parabola.imgfmt.app.net.RouteCenter;
-import uk.me.parabola.imgfmt.app.net.RouteNode;
-import uk.me.parabola.imgfmt.app.net.RouteRestriction;
-import uk.me.parabola.log.Logger;
-import uk.me.parabola.util.EnhancedProperties;
-
-/**
- * This holds the road network.  That is all the roads and the nodes
- * that connect them together.
- * 
- * @see <a href="http://www.movable-type.co.uk/scripts/latlong.html">Distance / bearing calculations</a>
- * @author Steve Ratcliffe
- */
-public class RoadNetwork {
-	private static final Logger log = Logger.getLogger(RoadNetwork.class);
-
-	public static final int NO_EMERGENCY = 0;
-	public static final int NO_DELIVERY = 1;
-	public static final int NO_CAR = 2;
-	public static final int NO_BUS = 3;
-	public static final int NO_TAXI = 4;
-	public static final int NO_FOOT = 5;
-	public static final int NO_BIKE = 6;
-	public static final int NO_TRUCK = 7;
-	public static final int NO_MAX = 8;
-
-	private final Map<Long, RouteNode> nodes = new LinkedHashMap<Long, RouteNode>();
-
-	// boundary nodes
-	// a node should be in here iff the nodes boundary flag is set
-	private final List<RouteNode> boundary = new ArrayList<RouteNode>();
-	//private final List<MapRoad> mapRoads = new ArrayList<MapRoad>();
-
-	private final List<RoadDef> roadDefs = new ArrayList<RoadDef>();
-	private List<RouteCenter> centers = new ArrayList<RouteCenter>();
-	private int adjustTurnHeadings ;
-	private boolean checkRoundabouts;
-	private boolean checkRoundaboutFlares;
-	private int maxFlareLengthRatio ;
-	private boolean reportSimilarArcs;
-	private boolean outputCurveData;
-
-	public void config(EnhancedProperties props) {
-		String ath = props.getProperty("adjust-turn-headings");
-		if(ath != null) {
-			if(ath.length() > 0)
-				adjustTurnHeadings = Integer.decode(ath);
-			else
-				adjustTurnHeadings = RouteNode.ATH_DEFAULT_MASK;
-		}
-		checkRoundabouts = props.getProperty("check-roundabouts", false);
-		checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false);
-		maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0);
-
-		reportSimilarArcs = props.getProperty("report-similar-arcs", false);
-
-		outputCurveData = !props.getProperty("no-arc-curves", false);
-	}
-
-	public void addRoad(MapRoad road) {
-		//mapRoads.add(road);
-		roadDefs.add(road.getRoadDef()); //XXX
-
-		CoordNode lastCoord = null;
-		int lastIndex = 0;
-		double roadLength = 0;
-		double arcLength = 0;
-		int pointsHash = 0;
-
-		List<Coord> coordList = road.getPoints();
-		int npoints = coordList.size();
-		for (int index = 0; index < npoints; index++) {
-			Coord co = coordList.get(index);
-
-			if (index > 0) {
-				double d = co.distance(coordList.get(index-1));
-				arcLength += d;
-				roadLength += d;
-			}
-
-			long id = co.getId();
-
-			pointsHash += co.hashCode();
-
-			if (id == 0)
-				// not a routing node
-				continue;
-
-			// The next coord determines the heading
-			// If this is the not the first node, then create an arc from
-			// the previous node to this one (and back again).
-			if (lastCoord != null) {
-				long lastId = lastCoord.getId();
-				if(log.isDebugEnabled()) {
-					log.debug("lastId = " + lastId + " curId = " + id);
-					log.debug("from " + lastCoord.toDegreeString() 
-							  + " to " + co.toDegreeString());
-					log.debug("arclength=" + arcLength + " roadlength=" + roadLength);
-				}
-
-				RouteNode node1 = getNode(lastId, lastCoord);
-				RouteNode node2 = getNode(id, co);
-
-				if(node1 == node2)
-					log.error("Road " + road.getRoadDef() + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken");
-				else if(arcLength == 0)
-					log.error("Road " + road.getRoadDef() + " contains zero length arc at " + co.toOSMURL());
-
-
-				Coord bearingPoint = coordList.get(lastIndex + 1);
-				if(lastCoord.equals(bearingPoint)) {
-					// bearing point is too close to last node to be
-					// useful - try some more points
-					for(int bi = lastIndex + 2; bi <= index; ++bi) {
-						if(!lastCoord.equals(coordList.get(bi))) {
-							bearingPoint = coordList.get(bi);
-							break;
-						}
-					}
-				}
-				int forwardBearing = (int)lastCoord.bearingTo(bearingPoint);
-				int inverseForwardBearing = (int)bearingPoint.bearingTo(lastCoord);
-
-				bearingPoint = coordList.get(index - 1);
-				if(co.equals(bearingPoint)) {
-					// bearing point is too close to this node to be
-					// useful - try some more points
-					for(int bi = index - 2; bi > lastIndex; --bi) {
-						if(!co.equals(coordList.get(bi))) {
-							bearingPoint = coordList.get(bi);
-							break;
-						}
-					}
-				}
-				int reverseBearing = (int)co.bearingTo(bearingPoint);
-				int inverseReverseBearing = (int)bearingPoint.bearingTo(co);
-
-				// Create forward arc from node1 to node2
-				RouteArc arc = new RouteArc(road.getRoadDef(),
-											node1,
-											node2,
-											forwardBearing,
-											inverseReverseBearing,
-											arcLength,
-											outputCurveData,
-											pointsHash);
-				arc.setForward();
-				node1.addArc(arc);
-				node2.addIncomingArc(arc);
-
-				// Create the reverse arc
-				arc = new RouteArc(road.getRoadDef(),
-								   node2, node1,
-								   reverseBearing,
-								   inverseForwardBearing,
-								   arcLength,
-								   outputCurveData,
-								   pointsHash);
-				node2.addArc(arc);
-				node1.addIncomingArc(arc);
-			} else {
-				// This is the first node in the road
-				road.getRoadDef().setNode(getNode(id, co));
-			}
-
-			lastCoord = (CoordNode) co;
-			lastIndex = index;
-			arcLength = 0;
-			pointsHash = co.hashCode();
-		}
-		road.getRoadDef().setLength(roadLength);
-	}
-
-	private RouteNode getNode(long id, Coord coord) {
-		RouteNode node = nodes.get(id);
-		if (node == null) {
-			node = new RouteNode(coord);
-			nodes.put(id, node);
-			if (node.isBoundary())
-				boundary.add(node);
-		}
-		return node;
-	}
-
-	public List<RoadDef> getRoadDefs() {
-		return roadDefs;
-	}
-
-	/**
-	 * Split the network into RouteCenters.
-	 *
-	 * The resulting centers must satisfy several constraints,
-	 * documented in NOD1Part.
-	 */
-	private void splitCenters() {
-		if (nodes.isEmpty())
-			return;
-		assert centers.isEmpty() : "already subdivided into centers";
-
-		NOD1Part nod1 = new NOD1Part();
-
-		for (RouteNode node : nodes.values()) {
-			if(!node.isBoundary()) {
-				if(checkRoundabouts)
-					node.checkRoundabouts();
-				if(checkRoundaboutFlares)
-					node.checkRoundaboutFlares(maxFlareLengthRatio);
-				if(reportSimilarArcs)
-					node.reportSimilarArcs();
-			}
-			if(adjustTurnHeadings != 0)
-				node.tweezeArcs(adjustTurnHeadings);
-			nod1.addNode(node);
-		}
-		centers = nod1.subdivide();
-	}
-
-	public List<RouteCenter> getCenters() {
-		if (centers.isEmpty())
-			splitCenters();
-		return centers;
-	}
-
-	/**
-	 * Get the list of nodes on the boundary of the network.
-	 *
-	 * Currently empty.
-	 */
-	public List<RouteNode> getBoundary() {
-		return boundary;
-	}
-
-	public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
-		RouteNode fn = nodes.get(fromNode.getId());
-		RouteNode tn = nodes.get(toNode.getId());
-		RouteNode vn = nodes.get(viaNode.getId());
-
-		assert fn != null : "can't locate 'from' RouteNode with id " + fromNode.getId();
-		assert tn != null : "can't locate 'to' RouteNode with id " + toNode.getId();
-		assert vn != null : "can't locate 'via' RouteNode with id " + viaNode.getId();
-
-		RouteArc fa = vn.getArcTo(fn); // inverse arc gets used
-		RouteArc ta = vn.getArcTo(tn);
-
-		assert fa != null : "can't locate arc from 'via' node to 'from' node";
-		assert ta != null : "can't locate arc from 'via' node to 'to' node";
-
-		vn.addRestriction(new RouteRestriction(fa, ta, exceptMask));
-    }
-
-	public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
-		RouteNode node = nodes.get(junctionNodeId);
-		assert node != null :  "Can't find node with id " + junctionNodeId;
-
-		node.addThroughRoute(roadIdA, roadIdB);
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/main/AbstractTestMap.java b/src/uk/me/parabola/mkgmap/main/AbstractTestMap.java
index 87b030e..44be50d 100644
--- a/src/uk/me/parabola/mkgmap/main/AbstractTestMap.java
+++ b/src/uk/me/parabola/mkgmap/main/AbstractTestMap.java
@@ -22,10 +22,10 @@ import uk.me.parabola.imgfmt.FileNotWritableException;
 import uk.me.parabola.imgfmt.FileSystemParam;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.map.Map;
-import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.app.trergn.Subdivision;
 import uk.me.parabola.imgfmt.app.trergn.Zoom;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
 
 /**
  * Common code used for the test maps.  The test maps are programmatically
@@ -55,7 +55,7 @@ public abstract class AbstractTestMap {
 
 		Map map;
 		try {
-			map = Map.createMap("32860003", ".", params, "32860003", Sort.defaultSort(1252));
+			map = Map.createMap("32860003", ".", params, "32860003", SrtTextReader.sortForCodepage(1252));
 		} catch (FileExistsException e) {
 			throw new ExitException("File exists already", e);
 		} catch (FileNotWritableException e) {
@@ -95,5 +95,4 @@ public abstract class AbstractTestMap {
 	}
 
 	protected abstract void drawTestMap(Map map, Subdivision div, double lat, double lng);
-
 }
diff --git a/src/uk/me/parabola/mkgmap/main/Main.java b/src/uk/me/parabola/mkgmap/main/Main.java
index c487f6b..7f9a865 100644
--- a/src/uk/me/parabola/mkgmap/main/Main.java
+++ b/src/uk/me/parabola/mkgmap/main/Main.java
@@ -91,6 +91,22 @@ public class Main implements ArgumentProcessor {
 	// used for messages in listStyles and checkStyles
 	private String searchedStyleName;
 
+	private volatile int programRC = 0;
+
+	/**
+	 * Used for unit tests
+	 * @param args
+	 */
+	public static void mainNoSystemExit(String[] args) {
+		Main.mainStart(args);
+	}
+	
+	public static void main(String[] args) {
+		int rc = Main.mainStart(args);
+		if (rc != 0)
+			System.exit(1);
+	}
+	
 	/**
 	 * The main program to make or combine maps.  We now use a two pass process,
 	 * first going through the arguments and make any maps and collect names
@@ -98,36 +114,53 @@ public class Main implements ArgumentProcessor {
 	 *
 	 * @param args The command line arguments.
 	 */
-	public static void main(String[] args) {
+	private static int mainStart(String[] args) {
 		long start = System.currentTimeMillis();
 		System.out.println("Time started: " + new Date());
 		// We need at least one argument.
 		if (args.length < 1) {
 			printUsage();
 			printHelp(System.err, getLang(), "options");
-			return;
+			return 0;
 		}
 
 		Main mm = new Main();
 
+		int numExitExceptions = 0;
 		try {
 			// Read the command line arguments and process each filename found.
 			CommandArgsReader commandArgs = new CommandArgsReader(mm);
 			commandArgs.setValidOptions(getValidOptions(System.err));
 			commandArgs.readArgs(args);
 		} catch (MapFailedException e) {
-			System.err.println(e.getMessage());
+			System.err.println(e.getMessage()); // should not happen
 		} catch (ExitException e) {
+			++numExitExceptions;
 			System.err.println(e.getMessage());
 		}
+		
+		System.out.println("Number of ExitExceptions: " + numExitExceptions);
+		
 		System.out.println("Time finished: " + new Date());
-		System.out.println("Total time taken: " + (System.currentTimeMillis() - start) + "ms"); 
+		System.out.println("Total time taken: " + (System.currentTimeMillis() - start) + "ms");
+		if (numExitExceptions > 0 || mm.getProgramRC() != 0){
+			return 1;
+		}
+		return 0;
 	}
 	
 	private static void printUsage (){
 		System.err.println("Usage: mkgmap [options...] <file.osm>");
 	}
 
+	private void setProgramRC(int rc){
+		programRC = rc;
+	}
+
+	private int getProgramRC(){
+		return programRC;
+	}
+	
 	/**
 	 * Grab the options help file and print it.
 	 * @param err The output print stream to write to.
@@ -420,7 +453,9 @@ public class Main implements ArgumentProcessor {
 
 
 		List<FilenameTask> filenames = new ArrayList<FilenameTask>();
-
+		
+		int numMapFailedExceptions = 0;
+		
 		if (threadPool != null) {
 			threadPool.shutdown();
 			while (!futures.isEmpty()) {
@@ -451,7 +486,9 @@ public class Main implements ArgumentProcessor {
 				} catch (ExitException ee) {
 					throw ee;
 				} catch (MapFailedException mfe) {
-					System.err.println(mfe.getMessage());
+//					System.err.println(mfe.getMessage()); // already printed via log
+					numMapFailedExceptions++;
+					setProgramRC(-1);
 				} catch (Throwable t) {
 					t.printStackTrace();
 					if (!args.getProperties().getProperty("keep-going", false)) {
@@ -460,6 +497,7 @@ public class Main implements ArgumentProcessor {
 				}
 			}
 		}
+		System.out.println("Number of MapFailedExceptions: " + numMapFailedExceptions);
 
 		if (combiners.isEmpty())
 			return;
@@ -574,7 +612,7 @@ public class Main implements ArgumentProcessor {
 	 * @param filename The original filename.
 	 * @return The file extension.
 	 */
-	private String extractExtension(String filename) {
+	private static String extractExtension(String filename) {
 		String[] parts = filename.toLowerCase(Locale.ENGLISH).split("\\.");
 		List<String> ignore = Arrays.asList("gz", "bz2", "bz");
 
diff --git a/src/uk/me/parabola/mkgmap/main/MapMaker.java b/src/uk/me/parabola/mkgmap/main/MapMaker.java
index f56f79a..567ebef 100644
--- a/src/uk/me/parabola/mkgmap/main/MapMaker.java
+++ b/src/uk/me/parabola/mkgmap/main/MapMaker.java
@@ -224,7 +224,7 @@ public class MapMaker implements MapProcessor {
 		if(r1 != r2) {
 			for(Coord c1 : r1.getPoints()) {
 				for(Coord c2 : r2.getPoints()) {
-					if(c1 == c2 || c1.equals(c2))
+					if(c1 == c2 || c1.highPrecEquals(c2))
 						return true;
 				}
 			}
@@ -309,13 +309,13 @@ public class MapMaker implements MapProcessor {
 		List<Coord> points = road.getPoints();
 		int numPoints = points.size();
 		Coord coord;
+		// XXX Why not always use an existing point close to
+		// numpoints/2 ?
 		if ((numPoints & 1) == 0) {
 			int i2 = numPoints / 2;
 			int i1 = i2 - 1;
-			coord = new Coord((points.get(i1).getLatitude() +
-					   points.get(i2).getLatitude()) / 2,
-					  (points.get(i1).getLongitude() +
-					   points.get(i2).getLongitude()) / 2);
+			coord = points.get(i1).makeBetweenPoint(points.get(i2), 0.5);
+			
 		} else {
 			coord = points.get(numPoints / 2);
 		}
diff --git a/src/uk/me/parabola/mkgmap/main/StyleTester.java b/src/uk/me/parabola/mkgmap/main/StyleTester.java
index ac655c8..41f2ee1 100644
--- a/src/uk/me/parabola/mkgmap/main/StyleTester.java
+++ b/src/uk/me/parabola/mkgmap/main/StyleTester.java
@@ -40,7 +40,7 @@ import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.Utils;
 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.net.GeneralRouteRestriction;
 import uk.me.parabola.imgfmt.app.net.RoadDef;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.MapCollector;
@@ -621,6 +621,7 @@ public class StyleTester implements OsmConverter {
 		 */
 		private class ReferenceRuleSet implements Rule {
 			private final List<Rule> rules = new ArrayList<Rule>();
+			int cacheId = 0;
 			
 			public void add(Rule rule) {
 				rules.add(rule);
@@ -633,7 +634,7 @@ public class StyleTester implements OsmConverter {
 			}
 
 			public void resolveType(Element el, TypeResult result) {
-				String tagsBefore = wayTags(el);
+				String tagsBefore = el.toTagString();
 				if (showMatches) {
 					out.println("# Tags before: " + tagsBefore);
 				}
@@ -641,8 +642,8 @@ public class StyleTester implements OsmConverter {
 				// Start by literally running through the rules in order.
 				for (Rule rule : rules) {
 					a.reset();
-					rule.resolveType(el, a);
-
+					cacheId = rule.resolveType(cacheId, el, a);
+					
 					if (showMatches) {
 						if (a.isFound()) {
 							out.println("# Matched: " + rule);
@@ -653,19 +654,17 @@ public class StyleTester implements OsmConverter {
 					if (a.isResolved())
 						break;
 				}
-				if (showMatches && !tagsBefore.equals(wayTags(el)))
-					out.println("# Way tags after: " + wayTags(el));
+				if (showMatches && !tagsBefore.equals(el.toTagString()))
+					out.println("# Way tags after: " + el.toTagString());
 			}
 
-			private String wayTags(Element el) {
-				StringBuilder sb = new StringBuilder();
-				for (String t : el) {
-					sb.append(t);
-					sb.append(",");
-				}
-				return sb.toString();
+			@Override
+			public int resolveType(int cacheId, Element el, TypeResult result) {
+				resolveType(el, result);
+				return cacheId;
 			}
 
+
 			public void setFinalizeRule(Rule finalizeRule) {
 				for (Rule rule : rules) {
 					rule.setFinalizeRule(finalizeRule);
@@ -808,10 +807,11 @@ public class StyleTester implements OsmConverter {
 			lines.add(road);
 		}
 
-		public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
+		public int addRestriction(GeneralRouteRestriction grr) {
+			return 0;
 		}
 
-		public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
+		public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) {
 		}
 	}
 
@@ -850,10 +850,11 @@ public class StyleTester implements OsmConverter {
 			}
 		}
 
-		public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
+		public int addRestriction(GeneralRouteRestriction grr) {
+			return 0;
 		}
 
-		public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
+		public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) {
 		}
 
 		public long getStart() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
index 35d377a..4eaa7bb 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
@@ -36,7 +36,7 @@ import uk.me.parabola.mkgmap.reader.osm.TypeResult;
  * @author Steve Ratcliffe
  */
 public class ActionRule implements Rule {
-	private final Op expression;
+	private Op expression;
 	private final List<Action> actions;
 	private final GType type;
 	private Rule finalizeRule;
@@ -62,6 +62,49 @@ public class ActionRule implements Rule {
 		this.type = null;
 	}
 	
+	
+	public int resolveType(int cacheId, Element el, TypeResult result) {
+		Element element = el;
+		if (expression != null) {
+			if (!expression.eval(cacheId, element))
+				return cacheId;
+				
+			// If this is a continue and we are not to propagate the effects
+			// of the action on the element to further rules, then make
+			// a copy of the element so that the original is unsullied.
+			//
+			// There is another reason we need to copy: since there will be
+			if (type != null && !type.isPropogateActions() && !(element instanceof Relation)) {
+				element = element.copy();
+			}
+		}
+
+		// an action will be performed, so we may have to invalidate the cache
+		boolean invalidate_cache = false;
+		for (Action a : actions){
+			if (a.perform(element)){
+				invalidate_cache = true;
+			}
+		}
+		if (invalidate_cache)
+			cacheId++;
+		
+		if (type != null && finalizeRule != null) {
+			if (el == element && type.isContinueSearch())
+				// if there is a continue statement changes performed in 
+				// the finalize block must not be persistent
+				element = element.copy();
+			// there is a type so first execute the finalize rules
+			if (type.getDefaultName() != null)
+				element.addTag("mkgmap:default_name", type.getDefaultName());
+			cacheId = finalizeRule.resolveType(cacheId, element, finalizeTypeResult);
+		}
+		
+		result.add(element, type);
+		return cacheId;
+	}
+	
+	
 	public void resolveType(Element el, TypeResult result) {
 		Element element = el;
 		if (expression != null) {
@@ -115,4 +158,14 @@ public class ActionRule implements Rule {
 	public void setFinalizeRule(Rule finalizeRule) {
 		this.finalizeRule = finalizeRule;
 	}
+
+
+	public Op getOp(){
+		return expression;
+	}
+	
+	public void setOp(Op expression){
+		this.expression = expression;
+	}
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/CombinedStyleFileLoader.java b/src/uk/me/parabola/mkgmap/osmstyle/CombinedStyleFileLoader.java
index ef8edd6..9911323 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/CombinedStyleFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/CombinedStyleFileLoader.java
@@ -190,6 +190,7 @@ public class CombinedStyleFileLoader extends StyleFileLoader {
 				writer.close();
 			}
 		}
+		loader.close();
 	}
 
 	private static void convertToFile(File file, PrintStream out) throws IOException {
@@ -212,6 +213,7 @@ public class CombinedStyleFileLoader extends StyleFileLoader {
 				String line;
 				while ((line = r.readLine()) != null)
 					out.println(line);
+				r.close();
 			} else {
 				convertToFile(out, entry.listFiles(new NoHiddenFilter()), entry.getName());
 			}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ConvertedWay.java b/src/uk/me/parabola/mkgmap/osmstyle/ConvertedWay.java
new file mode 100644
index 0000000..523e902
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ConvertedWay.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.osmstyle;
+
+import java.util.List;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.trergn.MapObject;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
+
+/**
+ * Class that is used to connect an OSM way with the attributes of GType
+ * after Style processing.
+ * 
+ * @author GerdP
+ *
+ */
+public class ConvertedWay {
+	private static final Logger log = Logger.getLogger(ConvertedWay.class);
+	private final int index;
+	private final Way way;				// with tags after Style processing
+	private final GType gt;
+
+	private byte roadClass;			// 0-4
+	private byte roadSpeed;			// 0-7
+	private byte mkgmapAccess; 		// bit mask, see ACCESS_TAGS 
+	private final byte routeFlags;	// bit mask, see ROUTING_TAGS
+	private boolean isRoad;
+	private boolean reversed;		// points were reversed
+	private boolean overlay;		// this is a non-routable overlay line that for a road 
+	
+
+	public ConvertedWay(int index, Way way, GType type) {
+		this.index = index;
+		this.way = way;
+		this.gt = type;
+		// note that the gt.getType() may not be a routable type when overlays are used
+		if (type.isRoad() && MapObject.hasExtendedType(gt.getType()) == false) {
+			this.roadClass = (byte) gt.getRoadClass();
+			this.roadSpeed = (byte) gt.getRoadSpeed();
+			recalcRoadClass(way);
+			recalcRoadSpeed(way);
+			mkgmapAccess = evalAccessTags(way);
+			routeFlags = evalRouteTags(way);
+			isRoad = true;
+		} else {
+			roadClass = 0;
+			roadSpeed = 0;
+			mkgmapAccess = 0;
+			routeFlags = 0;
+			isRoad = false;
+		}
+	}
+	
+	public ConvertedWay(ConvertedWay other, Way way){
+		this.way = way;
+		// copy all other attributes
+		this.index = other.index;
+		this.gt = other.gt;
+		this.roadClass = other.roadClass;
+		this.roadSpeed = other.roadSpeed;
+		this.mkgmapAccess = other.mkgmapAccess;
+		this.routeFlags = other.routeFlags;
+	}
+	
+	public int getIndex(){
+		return index;
+	}
+	
+	public GType getGType(){
+		return gt;
+	}
+
+	public Way getWay() {
+		return way;
+	}
+	
+	public byte getAccess(){
+		return mkgmapAccess;
+	}
+	
+	public byte getRoadClass(){
+		return roadClass;
+	}
+
+	public byte getRoadSpeed(){
+		return roadSpeed;
+	}
+	
+	public byte getRouteFlags(){
+		return routeFlags;
+	}
+	
+
+	/**
+	 * Recalculates the road class based on the tags
+	 * <ul>
+	 * <li>{@code mkgmap:road-class}</li>
+	 * <li>{@code mkgmap:road-class-min}</li>
+	 * <li>{@code mkgmap:road-class-max}</li>
+	 * </ul>
+	 * The road class is changed if the tags modify its road class. 
+	 * 
+	 * @param el an element 
+	 * @return {@code true} the road class has been changed, else {@code false} 
+	 */
+	private final static short roadClassTagKey = TagDict.getInstance().xlate("mkgmap:road-class");
+	public boolean recalcRoadClass(Element el) {
+		// save the original road class value
+		byte oldRoadClass = roadClass;
+		
+		String val = el.getTag(roadClassTagKey);
+		if (val != null) {
+			if (val.startsWith("-")) {
+				roadClass -= Byte.decode(val.substring(1));
+			} else if (val.startsWith("+")) {
+				roadClass += Byte.decode(val.substring(1));
+			} else {
+				roadClass = Byte.decode(val);
+			}
+			val = el.getTag("mkgmap:road-class-max");
+			byte roadClassMax = 4;
+			if (val != null)
+				roadClassMax = Byte.decode(val);
+			val = el.getTag("mkgmap:road-class-min");
+
+			byte roadClassMin = 0;
+			if (val != null)
+				roadClassMin = Byte.decode(val);
+			if (roadClass > roadClassMax)
+				roadClass = roadClassMax;
+			else if (roadClass < roadClassMin)
+				roadClass = roadClassMin;
+
+		}
+		return (roadClass != oldRoadClass);
+	}
+	
+	/**
+	 * Recalculates the road speed 
+	 * <ul>
+	 * <li>{@code mkgmap:road-speed-class}</li>
+	 * <li>{@code mkgmap:road-speed}</li>
+	 * <li>{@code mkgmap:road-speed-min}</li>
+	 * <li>{@code mkgmap:road-speed-max}</li>
+	 * </ul>
+	 * 
+	 * @param el an element 
+	 * @return {@code true} the road speed has been changed, else {@code false} 
+	 */
+	private final static short roadSpeedTagKey = TagDict.getInstance().xlate("mkgmap:road-speed");
+	private final static short roadSpeedClassTagKey = TagDict.getInstance().xlate("mkgmap:road-speed-class");
+	public boolean recalcRoadSpeed(Element el) {
+		// save the original road speed value
+		byte oldRoadSpeed = roadSpeed;
+		
+		// check if the road speed is modified
+		String roadSpeedOverride = el.getTag(roadSpeedClassTagKey);
+		if (roadSpeedOverride != null) {
+			try {
+				byte rs = Byte.decode(roadSpeedOverride);
+				if (rs >= 0 && rs <= 7) {
+					// override the road speed class
+					roadSpeed = rs;
+				} else {
+					log.error(el.getDebugName()
+							+ " road classification mkgmap:road-speed-class="
+							+ roadSpeedOverride + " must be in [0;7]");
+				}
+			} catch (Exception exp) {
+				log.error(el.getDebugName()
+						+ " road classification mkgmap:road-speed-class="
+						+ roadSpeedOverride + " must be in [0;7]");
+			}
+		}
+		
+		// check if the road speed should be modified more
+		String val = el.getTag(roadSpeedTagKey);
+		if(val != null) {
+			if(val.startsWith("-")) {
+				roadSpeed -= Byte.decode(val.substring(1));
+			}
+			else if(val.startsWith("+")) {
+				roadSpeed += Byte.decode(val.substring(1));
+			}
+			else {
+				roadSpeed = Byte.decode(val);
+			}
+			val = el.getTag("mkgmap:road-speed-max");
+			byte roadSpeedMax = 7;
+			if(val != null)
+				roadSpeedMax = Byte.decode(val);
+			val = el.getTag("mkgmap:road-speed-min");
+
+			byte roadSpeedMin = 0;
+			if(val != null)
+				roadSpeedMin = Byte.decode(val);
+			if(roadSpeed > roadSpeedMax)
+				roadSpeed = roadSpeedMax;
+			else if(roadSpeed < roadSpeedMin)
+				roadSpeed = roadSpeedMin;
+		}
+		return (oldRoadSpeed != roadSpeed);
+	}
+
+	public List<Coord> getPoints(){
+		return way.getPoints();
+	}
+
+	public boolean isValid() {
+		if (way == null)
+			return false;
+		if (way.getPoints() == null || way.getPoints().size()<2)
+			return false;
+		return true;
+	}
+	
+	public String toString(){
+		return getGType() + " " + getWay().getId() + " " + getWay().toTagString();
+
+	}
+	
+	public boolean isOneway(){
+		return (routeFlags & R_ONEWAY) != 0;
+	}
+
+	public boolean isRoundabout(){
+		return (routeFlags & R_ROUNDABOUT) != 0;
+	}
+	public boolean isToll(){
+		return (routeFlags & R_TOLL) != 0; 
+	}
+
+	public boolean isUnpaved(){
+		return (routeFlags & R_UNPAVED) != 0; 
+	}
+
+	public boolean isFerry(){
+		return (routeFlags & R_FERRY) != 0; 
+	}
+
+	public boolean isCarpool(){
+		return (routeFlags & R_CARPOOL) != 0; 
+	}
+
+	public boolean isThroughroute(){
+		return (routeFlags & R_THROUGHROUTE) != 0; 
+	}
+	
+	public boolean isRoad(){
+		return isRoad;
+	}
+
+	public boolean isReversed() {
+		return reversed;
+	}
+
+	public void setReversed(boolean reversed) {
+		this.reversed = reversed;
+	}
+
+	public void setOverlay(boolean b) {
+		this.overlay = b;
+	}
+
+	public boolean isOverlay() {
+		return overlay;
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/DirectoryFileLoader.java b/src/uk/me/parabola/mkgmap/osmstyle/DirectoryFileLoader.java
index 7f32c94..2ecdc2d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/DirectoryFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/DirectoryFileLoader.java
@@ -18,11 +18,11 @@ package uk.me.parabola.mkgmap.osmstyle;
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStreamReader;
-import java.io.FileInputStream;
-import java.io.UnsupportedEncodingException;
 import java.io.Reader;
+import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -61,6 +61,7 @@ public class DirectoryFileLoader extends StyleFileLoader {
 			r = new InputStreamReader(new FileInputStream(file), "UTF-8");
         } catch (UnsupportedEncodingException uee) {
             System.out.println("DirectoryFileLoader: Encoding UTF-8 not supported");
+            r = new InputStreamReader(new FileInputStream(file));
         }
 
 		return new BufferedReader(r);
@@ -77,10 +78,14 @@ public class DirectoryFileLoader extends StyleFileLoader {
 		List<String> res = new ArrayList<String>();
 
 		File[] allFiles = dir.listFiles();
-		for (File file : allFiles) {
-			log.debug("dir loader", file);
-			if (file.isDirectory()) {
-				res.add(file.getName());
+		if (allFiles != null) {
+			for (File file : allFiles) {
+				log.debug("dir loader", file);
+				if (file.isDirectory()) {
+					File style = new File(file, "version");
+					if (style.exists())
+						res.add(file.getName());
+				}
 			}
 		}
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
index 49a4c6e..8c934ad 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
@@ -29,7 +29,7 @@ import uk.me.parabola.mkgmap.reader.osm.TypeResult;
  * @author Steve Ratcliffe
  */
 public class ExpressionRule implements Rule {
-	private final Op expression;
+	private Op expression;
 	private final GType gtype;
 	private Rule finalizeRule;
 
@@ -45,6 +45,7 @@ public class ExpressionRule implements Rule {
 		this.gtype = gtype;
 	}
 
+	
 	public void resolveType(Element el, TypeResult result) {
 		if (expression.eval(el)) {
 			// expression matches
@@ -61,6 +62,22 @@ public class ExpressionRule implements Rule {
 		}
 	}
 
+	public int resolveType(int cacheId, Element el, TypeResult result) {
+		if (expression.eval(cacheId, el)){
+			if (finalizeRule != null) {
+				if (gtype.isContinueSearch()) {
+					el = el.copy();
+				}
+				// run the finalize rules
+				if (gtype.getDefaultName() != null)
+					el.addTag("mkgmap:default_name", gtype.getDefaultName());
+				cacheId = finalizeRule.resolveType(cacheId, el, finalizeTypeResult);
+			}
+			result.add(el, gtype);
+		}
+		return cacheId;
+	}
+
 	public String toString() {
 		return expression.toString() + ' ' + gtype;
 	}
@@ -68,4 +85,13 @@ public class ExpressionRule implements Rule {
 	public void setFinalizeRule(Rule finalizeRule) {
 		this.finalizeRule = finalizeRule;
 	}
+	
+	public Op getOp(){
+		return expression;
+	}
+
+	public void setOp(Op expression){
+		this.expression = expression;
+	}
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/JarFileLoader.java b/src/uk/me/parabola/mkgmap/osmstyle/JarFileLoader.java
index d4aacef..1cceac2 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/JarFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/JarFileLoader.java
@@ -16,12 +16,13 @@
  */
 package uk.me.parabola.mkgmap.osmstyle;
 
-import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.io.UnsupportedEncodingException;
 import java.net.JarURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -130,7 +131,14 @@ public class JarFileLoader extends StyleFileLoader {
 		} catch (IOException e) {
 			throw new FileNotFoundException("Could not open " + filename);
 		}
-		return new InputStreamReader(new BufferedInputStream(stream));
+		Reader reader = null;
+		try {
+			reader = new InputStreamReader(stream, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			System.out.println("JarFileLoader: Encoding UTF-8 not supported");
+			reader = new InputStreamReader(stream);
+		}
+		return new BufferedReader(reader);
 	}
 
 	public void close() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
index 766115f..9c2581a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
@@ -20,10 +20,12 @@ import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.GType;
@@ -45,349 +47,64 @@ public class RoadMerger {
 	private static final double MAX_MERGE_ANGLE = 130d;
 	
 	/** maps which coord of a way(id) are restricted - they should not be merged */
-	private final MultiIdentityHashMap<Coord, Long> restrictions;
-	/** Contains a list of all roads (GType + Way) */
-	private final List<Road> roads;
+	private final MultiIdentityHashMap<Coord, Long> restrictions = new MultiIdentityHashMap<>();
 
 	/** maps the start point of a road to its road definition */
-	private final MultiIdentityHashMap<Coord, Road> startPoints = new MultiIdentityHashMap<Coord, Road>();
+	private final MultiIdentityHashMap<Coord, ConvertedWay> startPoints = new MultiIdentityHashMap<>();
 	/** maps the end point of a road to its road definition */
-	private final MultiIdentityHashMap<Coord, Road> endPoints = new MultiIdentityHashMap<Coord, Road>();
+	private final MultiIdentityHashMap<Coord, ConvertedWay> endPoints = new MultiIdentityHashMap<>();
 
-	/**
-	 * Helper class to keep the Way and the GType object of a road. 
-	 * Also provides methods that are able to decide if two roads can 
-	 * be merged.
-	 * 
-	 * @author WanMil
+	/** 
+	 * For these tags two ways need to have an equal value so that their roads can be merged.
 	 */
-	private static class Road {
-		/** gives the index of the original position in the way/road list */
-		private final int index;
-		private final Way way;
-		private final GType gtype;
-
-		/** 
-		 * For these tags two ways need to return the same value for {@link Way#isNotBoolTag(String)} 
-		 * so that their roads can be merged.
-		 */
-		private final static Set<String> mergeTagsNotBool = new HashSet<String>() {
-			{
-				add("mkgmap:emergency");
-				add("mkgmap:delivery");
-				add("mkgmap:car");
-				add("mkgmap:bus");
-				add("mkgmap:taxi");
-				add("mkgmap:foot");
-				add("mkgmap:bicycle");
-				add("mkgmap:truck");
-				add("mkgmap:throughroute");
-			}
-		};
-
-		/** 
-		 * For these tags two ways need to return the same value for {@link Way#isBoolTag(String)} 
-		 * so that their roads can be merged.
-		 */
-		private final static Set<String> mergeTagsBool = new HashSet<String>() {
-			{
-				add("mkgmap:carpool");
-				add("mkgmap:toll");
-				add("mkgmap:unpaved");
-				add("mkgmap:ferry");
-			}
-		};
-		
-		/** 
-		 * For these tags two ways need to have an equal value so that their roads can be merged.
-		 */
-		private final static Set<String> mergeTagsEqualValue = new HashSet<String>() {
-			{
-				add("mkgmap:label:1");
-				add("mkgmap:label:2");
-				add("mkgmap:label:3");
-				add("mkgmap:label:4");
-				add("mkgmap:postal_code");
-				add("mkgmap:city");
-				add("mkgmap:region");
-				add("mkgmap:country");
-				add("mkgmap:is_in");
-				add("mkgmap:skipSizeFilter");
-				add("junction");
-				add("mkgmap:synthesised");
-				add("mkgmap:flare-check");
-			}
-		};
-
-		public Road(int index, Way way, GType gtype) {
-			this.index = index;
-			this.way = way;
-			this.gtype = gtype;
-		}
-
-		/**
-		 * Checks if the given {@code otherRoad} can be merged with this road at 
-		 * the given {@code mergePoint}.
-		 * @param mergePoint the coord where this road and otherRoad might be merged
-		 * @param otherRoad another road instance
-		 * @return {@code true} this road can be merged with {@code otherRoad};
-		 * 	{@code false} the roads cannot be merged at {@code mergePoint}
-		 */
-		public boolean isMergable(Coord mergePoint, Road otherRoad) {
-			// first check if this road starts or stops at the mergePoint
-			Coord cStart = way.getPoints().get(0);
-			Coord cEnd = way.getPoints().get(way.getPoints().size() - 1);
-			if (cStart != mergePoint && cEnd != mergePoint) {
-				// it doesn't => roads not mergeable at mergePoint
-				return false;
-			}
-
-			// do the same check for the otherRoad
-			Coord cOtherStart = otherRoad.getWay().getPoints().get(0);
-			Coord cOtherEnd = otherRoad.getWay().getPoints()
-					.get(otherRoad.getWay().getPoints().size() - 1);
-			if (cOtherStart != mergePoint && cOtherEnd != mergePoint) {
-				// otherRoad does not start or stop at mergePoint =>
-				// roads not mergeable at mergePoint
-				return false;
-			}
-
-			// check if merging would create a closed way - which should not
-			// be done (why? WanMil)
-			if (cStart == cOtherEnd) {
-				return false;
-			}
-			
-			// check if the GType objects are the same
-			if (isGTypeMergable(otherRoad.getGtype()) == false) {
-				return false;
-			}
-			
-			// checks if the tag values of both ways match so that the ways
-			// can be merged
-			if (isWayMergable(mergePoint, otherRoad.getWay()) == false) {
-				return false;
-			}
-
-			return true;
-		}
-
-		/**
-		 * Checks if the given GType can be merged with the GType of this road.
-		 * @param otherGType the GType of the other road
-		 * @return {@code true} both GType objects can be merged; {@code false} GType 
-		 *   objects do not match and must not be merged
-		 */
-		private boolean isGTypeMergable(GType otherGType) {
-			// log.info("Gtype1",gtype);
-			// log.info("Gtype2",otherGType);
-			
-			// check all fields of the GType objects for equality
-			
-			if (gtype.getType() != otherGType.getType()) {
-				return false;
-			}
-			if (gtype.getMinResolution() != otherGType.getMinResolution()) {
-				return false;
-			}
-			if (gtype.getMaxResolution() != otherGType.getMaxResolution()) {
-				return false;
-			}
-			if (gtype.getMinLevel() != otherGType.getMinLevel()) {
-				return false;
-			}
-			if (gtype.getMaxLevel() != otherGType.getMaxLevel()) {
-				return false;
-			}
-			if (gtype.getRoadClass() != otherGType.getRoadClass()){
-				return false;
-			}
-			if (gtype.getRoadSpeed() != otherGType.getRoadSpeed()){
-				return false;
-			}
-// default name is applied before the RoadMerger starts
-// so they needn't be equal 
-//			if (stringEquals(gtype.getDefaultName(),
-//					otherGType.getDefaultName()) == false) {
-//				return false;
-//			}
-			
-			// log.info("Matches");
-			return true;
-		}
-
-		/**
-		 * Checks if the tag values of the {@link Way} objects of both roads 
-		 * match so that both roads can be merged. 
-		 * @param mergePoint the coord where both roads should be merged
-		 * @param otherWay the way of the road to merge
-		 * @return {@code true} tag values match so that both roads might be merged;
-		 *  {@code false} tag values differ so that road must not be merged
-		 */
-		private boolean isWayMergable(Coord mergePoint, Way otherWay) {
-
-			// oneway must not only be checked for equal tag values
-			// but also for correct direction of both ways
-			
-			// first map the different oneway values
-			String thisOneway = getWay().getTag("oneway");
-			// map oneway value for the other way
-			String otherOneway = otherWay.getTag("oneway");
-
-			if (stringEquals(thisOneway, otherOneway) == false) {
-				// the oneway tags differ => cannot merge
-				// (It might be possible to reverse the direction of one way
-				// but this might be implemented later)
-				log.debug("oneway does not match", way.getId(), "("
-						+ thisOneway + ")", otherWay.getId(), "(" + otherOneway
-						+ ")");
-				return false;
-				
-			} else if ("yes".equals(thisOneway)) {
-				// the oneway tags match and both ways are oneway
-				// now check if both ways have the same direction
-				
-				boolean thisStart = (getWay().getPoints().get(0) == mergePoint);
-				boolean otherStart = (otherWay.getPoints().get(0) == mergePoint);
-				
-				if (thisStart == otherStart) {
-					// both ways are oneway but they have a different direction
-					log.warn("oneway with different direction", way.getId(),
-							otherWay.getId());
-					return false;
-				}
-			}
-			// oneway matches
-
-			// now check the other tag lists
-			
-			// first: tags that need to have an equal value
-			for (String tagname : mergeTagsEqualValue) {
-				String thisTag = getWay().getTag(tagname);
-				String otherTag = otherWay.getTag(tagname);
-				if (stringEquals(thisTag, otherTag) == false) {
-					log.debug(tagname, "does not match", way.getId(), "("
-							+ thisTag + ")", otherWay.getId(), "(" + otherTag
-							+ ")");
-					// log.warn(way.getId(), way.toTagString());
-					// log.warn(otherWay.getId(), otherWay.toTagString());
-					return false;
-				}
-			}
-
-			// second: tags for which only the NotBool value must be equal 
-			for (String tagname : mergeTagsNotBool) {
-				boolean thisNo = getWay().isNotBoolTag(tagname);
-				boolean otherNo = otherWay.isNotBoolTag(tagname);
-				if (thisNo != otherNo) {
-					log.debug(tagname, "does not match", way.getId(), "("
-							+ getWay().getTag(tagname) + ")", otherWay.getId(),
-							"(" + otherWay.getTag(tagname) + ")");
-					return false;
-				}
-			}
-
-			// third: tags for which only the bool value must be equal 
-			for (String tagname : mergeTagsBool) {
-				boolean thisYes = getWay().isBoolTag(tagname);
-				boolean otherYes = otherWay.isBoolTag(tagname);
-				if (thisYes != otherYes) {
-					log.debug(tagname, "does not match", way.getId(), "("
-							+ getWay().getTag(tagname) + ")", otherWay.getId(),
-							"(" + otherWay.getTag(tagname) + ")");
-					return false;
-				}
-			}			
-			
-			// Check the angle of the two ways
-			Coord c1;
-			if (getWay().getPoints().get(0) == mergePoint) {
-				c1 = getWay().getPoints().get(1);
-			} else {
-				c1 = getWay().getPoints().get(getWay().getPoints().size() - 2);
-			}
-			Coord cOther;
-			if (otherWay.getPoints().get(0) == mergePoint) {
-				cOther = otherWay.getPoints().get(1);
-			} else {
-				cOther = otherWay.getPoints().get(
-						otherWay.getPoints().size() - 2);
-			}
-
-			double angle = Math.abs(Utils.getAngle(c1, mergePoint, cOther));
-			if (angle > MAX_MERGE_ANGLE) {
-				// The angle exceeds the limit => do not merge
-				// Don't know if this is really required or not. 
-				// But the number of merges which do not succeed due to this
-				// restriction is quite low and there have been requests
-				// for this: http://www.mkgmap.org.uk/pipermail/mkgmap-dev/2013q3/018649.html
-				
-				log.info("Do not merge ways",getWay().getId(),"and",otherWay.getId(),"because they span a too big angle",angle,"°");
-				return false;
-			}
-
-			return true;
-		}
-
-		public Way getWay() {
-			return way;
-		}
-
-		public GType getGtype() {
-			return gtype;
-		}
-
-		/**
-		 * Checks if two strings are equal ({@code null} supported).
-		 * @param s1 first string ({@code null} allowed)
-		 * @param s2 second string ({@code null} allowed)
-		 * @return {@code true} both strings are equal or both {@code null}; {@code false} both strings are not equal
-		 */
-		private boolean stringEquals(String s1, String s2) {
-			if (s1 == null) {
-				return s2 == null;
-			} else {
-				return s1.equals(s2);
-			}
+	private final static Set<String> mergeTagsEqualValue = new HashSet<String>() {
+		{
+			add("mkgmap:label:1");
+			add("mkgmap:label:2");
+			add("mkgmap:label:3");
+			add("mkgmap:label:4");
+			add("mkgmap:postal_code");
+			add("mkgmap:city");
+			add("mkgmap:region");
+			add("mkgmap:country");
+			add("mkgmap:is_in");
+			add("mkgmap:skipSizeFilter");
+			add("mkgmap:synthesised");
+			add("mkgmap:highest-resolution-only");
+			add("mkgmap:flare-check");
 		}
+	};
 
-		public String toString() {
-			return gtype + " " + way.getId() + " " + way.toTagString();
-		}
 
-		public final int getIndex() {
-			return index;
-		}
+	/**
+	 * Checks if two strings are equal ({@code null} supported).
+	 * @param s1 first string ({@code null} allowed)
+	 * @param s2 second string ({@code null} allowed)
+	 * @return {@code true} both strings are equal or both {@code null}; {@code false} both strings are not equal
+	 */
+	private static boolean stringEquals(String s1, String s2) {
+		if (s1 == null) 
+			return s2 == null;
+		return s1.equals(s2);
 	}
 
-	public RoadMerger(List<Way> ways, List<GType> gtypes,
-			Map<Coord, List<RestrictionRelation>> restrictions,
-			List<Relation> throughRouteRelations) {
-		assert ways.size() == gtypes.size();
-
-		this.roads = new ArrayList<Road>(ways.size());
 
-		for (int i = 0; i < ways.size(); i++) {
-			if (ways.get(i) != null)
-				roads.add(new Road(i, ways.get(i), gtypes.get(i)));
-		}
-
-		this.restrictions = new MultiIdentityHashMap<Coord, Long>();
-		workoutRestrictionRelations(restrictions);
-		workoutThroughRoutes(throughRouteRelations);
-	}
-
-	private void workoutRestrictionRelations(Map<Coord, List<RestrictionRelation>> restrictionRels) {
-		for (List<RestrictionRelation> rels : restrictionRels.values()) {
-			for (RestrictionRelation rel : rels) {
-				if (rel.getViaCoord() == null) {
-					continue;
-				}
-				if (rel.getFromWay() != null) {
-					restrictions.add(rel.getViaCoord(), rel.getFromWay().getId());
-				}
-				if (rel.getToWay() != null) {
-					restrictions.add(rel.getViaCoord(), rel.getToWay().getId());
+	/**
+	 * We must not merge roads at via points of restriction relations
+	 * if the way is referenced in the restriction.
+	 * @param restrictionRels
+	 */
+	private void workoutRestrictionRelations(List<RestrictionRelation> restrictionRels) {
+		for (RestrictionRelation rel : restrictionRels) {
+			Set<Long> restrictionWayIds = rel.getWayIds();
+			for (Coord via: rel.getViaCoords()){
+				HashSet<ConvertedWay> roadAtVia = new HashSet<>();
+				roadAtVia.addAll(startPoints.get(via));
+				roadAtVia.addAll(endPoints.get(via));
+				for (ConvertedWay r: roadAtVia){
+					long wayId = r.getWay().getId();
+					if (restrictionWayIds.contains(wayId))
+						restrictions.add(via, wayId);
 				}
 			}
 		}
@@ -436,6 +153,8 @@ public class RoadMerger {
 	}
 
 	private boolean hasRestriction(Coord c, Way w) {
+		if (w.isViaWay())
+			return true;
 		List<Long> wayRestrictions = restrictions.get(c);
 		return wayRestrictions.contains(w.getId());
 	}
@@ -449,7 +168,7 @@ public class RoadMerger {
 	 * @param road1 first road (will keep the merged road)
 	 * @param road2 second road
 	 */
-	private void mergeRoads(Road road1, Road road2) {
+	private void mergeRoads(ConvertedWay road1, ConvertedWay road2) {
 		// Removes the second line,
 		// Merges the points in the first one
 		List<Coord> points1 = road1.getWay().getPoints();
@@ -458,9 +177,9 @@ public class RoadMerger {
 		Coord mergePoint = points2.get(0);
 		Coord endPoint= points2.get(points2.size()-1);
 		
-		startPoints.remove(mergePoint, road2);
-		endPoints.remove(endPoint, road2);
-		endPoints.remove(mergePoint, road1);
+		startPoints.removeMapping(mergePoint, road2);
+		endPoints.removeMapping(endPoint, road2);
+		endPoints.removeMapping(mergePoint, road1);
 
 		points1.addAll(points2.subList(1, points2.size()));
 		endPoints.add(endPoint, road1);
@@ -491,25 +210,32 @@ public class RoadMerger {
 	 * @param resultingWays list for the merged (and not mergeable) ways
 	 * @param resultingGTypes list for the merged (and not mergeable) GTypes
 	 */
-	public void merge(List<Way> resultingWays, List<GType> resultingGTypes) {
+	public List<ConvertedWay>  merge(List<ConvertedWay> convertedWays,
+			List<RestrictionRelation> restrictions,
+			List<Relation> throughRouteRelations) {
+		List<ConvertedWay> result = new ArrayList<>();
+		List<ConvertedWay> roadsToMerge = new ArrayList<>(convertedWays.size());
+		for (int i = 0; i < convertedWays.size(); i++) {
+			ConvertedWay cw = convertedWays.get(i);
+			if (cw.isValid() && cw.isRoad())
+				roadsToMerge.add(cw);
+		}
 
-		int noRoadsBeforeMerge = this.roads.size();
+		int noRoadsBeforeMerge = roadsToMerge.size();
 		int noMerges = 0;
-		List<Road> roadsToMerge = new ArrayList<Road>(this.roads);
-		this.roads.clear();
 		
 		List<Coord> mergePoints = new ArrayList<>();
 
 		// first add all roads with their start and end points to the
 		// start/endpoint lists
-		for (Road road : roadsToMerge) {
+		for (ConvertedWay road : roadsToMerge) {
 			List<Coord> points = road.getWay().getPoints();
 			Coord start = points.get(0);
 			Coord end = points.get(points.size() - 1);
 
 			if (start == end) {
 				// do not merge closed roads
-				roads.add(road);
+				result.add(road);
 				continue;
 			}
 
@@ -518,6 +244,8 @@ public class RoadMerger {
 			startPoints.add(start, road);
 			endPoints.add(end, road);
 		}
+		workoutRestrictionRelations(restrictions);
+		workoutThroughRoutes(throughRouteRelations);
 
 		// a set of all points where no more merging is possible
 		Set<Coord> mergeCompletedPoints = Collections.newSetFromMap(new IdentityHashMap<Coord, Boolean>());
@@ -531,9 +259,9 @@ public class RoadMerger {
 			}
 			
 			// get all road that start with the merge point
-			List<Road> startRoads = startPoints.get(mergePoint);
+			List<ConvertedWay> startRoads = startPoints.get(mergePoint);
 			// get all roads that end with the merge point
-			List<Road> endRoads = endPoints.get(mergePoint);
+			List<ConvertedWay> endRoads = endPoints.get(mergePoint);
 			
 			if (endRoads.isEmpty() || startRoads.isEmpty()) {
 				// this might happen if another merge operation changed endPoints and/or startPoints
@@ -543,10 +271,10 @@ public class RoadMerger {
 			
 			// go through all combinations and test which combination is the best
 			double bestAngle = Double.MAX_VALUE;
-			Road mergeRoad1 = null;
-			Road mergeRoad2 = null;
+			ConvertedWay mergeRoad1 = null;
+			ConvertedWay mergeRoad2 = null;
 			
-			for (Road road1 : endRoads) {
+			for (ConvertedWay road1 : endRoads) {
 				// check if the road has a restriction at the merge point
 				// which does not allow us to merge the road at this point
 				if (hasRestriction(mergePoint, road1.getWay())) {
@@ -556,7 +284,7 @@ public class RoadMerger {
 				List<Coord> points1 = road1.getWay().getPoints();
 				
 				// go through all candidates to merge
-				for (Road road2 : startRoads) {
+				for (ConvertedWay road2 : startRoads) {
 					if (hasRestriction(mergePoint, road2.getWay())) {
 						continue;
 					}
@@ -571,7 +299,7 @@ public class RoadMerger {
 					}
 					
 					// check if both roads can be merged
-					if (road1.isMergable(mergePoint, road2)) {
+					if (isMergeable(mergePoint, road1, road2)){
 						// yes they might be merged
 						// calculate the angle between them 
 						// if there is more then one road to merge the one with the lowest angle is merged 
@@ -599,29 +327,236 @@ public class RoadMerger {
 		}
 
 		// copy all merged roads to the roads list
-		for (List<Road> mergedRoads : endPoints.values()) {
-			this.roads.addAll(mergedRoads);
+		for (List<ConvertedWay> mergedRoads : endPoints.values()) {
+			result.addAll(mergedRoads);
 		}
 
 		// sort the roads to ensure that the order of roads is constant for two runs
-		Collections.sort(this.roads, new Comparator<Road>() {
-			public int compare(Road o1, Road o2) {
+		Collections.sort(result, new Comparator<ConvertedWay>() {
+			public int compare(ConvertedWay o1, ConvertedWay o2) {
 				return Integer.compare(o1.getIndex(), o2.getIndex());
 			}
 		});
 		
-		// copy the roads to the resulting lists
-		for (Road r : roads) {
-			resultingWays.add(r.getWay());
-			resultingGTypes.add(r.getGtype());
-		}
-		
 		// print out some statistics
-		int noRoadsAfterMerge = this.roads.size();
+		int noRoadsAfterMerge = result.size();
 		log.info("Roads before/after merge:", noRoadsBeforeMerge, "/",
 				noRoadsAfterMerge);
 		int percentage = (int) Math.round((noRoadsBeforeMerge - noRoadsAfterMerge) * 100.0d
 						/ noRoadsBeforeMerge);
 		log.info("Road network reduced by", percentage, "%",noMerges,"merges");
+		return result;
+	}
+
+	/**
+	 * Checks if the given {@code otherRoad} can be merged with this road at 
+	 * the given {@code mergePoint}.
+	 * @param mergePoint the coord where this road and otherRoad might be merged
+	 * @param road1 1st road instance
+	 * @param road2 2nd road instance
+	 * @return {@code true} road1 can be merged with {@code road2};
+	 * 	{@code false} the roads cannot be merged at {@code mergePoint}
+	 */
+	private static boolean isMergeable(Coord mergePoint, ConvertedWay road1, ConvertedWay road2) {
+		// check if basic road attributes match
+		if (road1.getRoadClass() != road2.getRoadClass())
+			return false;
+		if (road1.getRoadSpeed() != road2.getRoadSpeed())
+			return false;
+		Way way1 = road1.getWay();
+		Way way2 = road2.getWay();
+
+		if (road1.getAccess() != road2.getAccess()) {
+			if (log.isDebugEnabled()) {
+				reportFirstDifferentTag(way1, way2, road1.getAccess(),
+						road2.getAccess(), AccessTagsAndBits.ACCESS_TAGS);
+			}
+			return false;
+		}
+		if (road1.getRouteFlags() != road2.getRouteFlags()) {
+			if (log.isDebugEnabled()) {
+				reportFirstDifferentTag(way1, way2, road1.getRouteFlags(),
+						road2.getRouteFlags(), AccessTagsAndBits.ROUTE_TAGS);
+			}
+			return false;
+		}
+
+		// now check if this road starts or stops at the mergePoint
+		Coord cStart = road1.getWay().getPoints().get(0);
+		Coord cEnd = road1.getWay().getPoints().get(road1.getWay().getPoints().size() - 1);
+		if (cStart != mergePoint && cEnd != mergePoint) {
+			// it doesn't => roads not mergeable at mergePoint
+			return false;
+		}
+
+		// do the same check for the otherRoad
+		Coord cOtherStart = way2.getPoints().get(0);
+		Coord cOtherEnd = way2.getPoints()
+				.get(way2.getPoints().size() - 1);
+		if (cOtherStart != mergePoint && cOtherEnd != mergePoint) {
+			// otherRoad does not start or stop at mergePoint =>
+			// roads not mergeable at mergePoint
+			return false;
+		}
+
+		// check if merging would create a closed way - which should not
+		// be done (why? WanMil)
+		if (cStart == cOtherEnd) {
+			return false;
+		}
+
+		// check if certain fields in the GType objects are the same
+		if (isGTypeMergeable(road1.getGType(), road2.getGType()) == false) {
+			return false;
+		}
+
+		if (road1.isOneway()){
+			assert road2.isOneway();
+			// oneway must not only be checked for equality
+			// but also for correct direction of both ways
+			if ((cStart == mergePoint) == (cOtherStart == mergePoint)) {
+				// both ways are oneway but they have a different direction
+				log.warn("oneway with different direction", way1.getId(),way2.getId());
+				return false;
+			}
+		}
+		
+		// checks if the tag values of both ways match so that the ways
+		// can be merged
+		if (isWayMergeable(mergePoint, way1, way2) == false) 
+			return false;
+		
+
+		// check if the angle between the two ways is not too sharp
+		if (isAngleOK(mergePoint, way1, way2) == false) 
+			return false;
+
+		return true;
+	}
+
+
+	/**
+	 * For logging purposes. Print first tag with different meaning.
+	 * @param way1 1st way
+	 * @param way2 2nd way
+	 * @param flags1 the bit mask for 1st way 
+	 * @param flags2 the bit mask for 2nd way
+	 * @param tagMaskMap the map that explains the meaning of the bit masks
+	 */
+	private static void reportFirstDifferentTag(Way way1, Way way2, byte flags1,
+			byte flags2, Map<String, Byte> tagMaskMap) {
+		for (Entry<String, Byte> entry : tagMaskMap.entrySet()){
+			byte mask = entry.getValue();
+			if ((flags1 & mask) != (flags2 & mask)){
+				String tagKey = entry.getKey();
+				log.debug(entry.getKey(), "does not match", way1.getId(), "("
+						+ way1.getTag(tagKey) + ")", 
+						way2.getId(), "(" + way2.getTag(tagKey) + ")");
+				return; // report only first mismatch 
+			}
+		}
+	}
+
+
+	/**
+	 * Checks if two GType objects can be merged. Not all fields are compared.
+	 * @param type1 the 1st GType 
+	 * @param type2 the 2nd GType 
+	 * @return {@code true} both GType objects can be merged; {@code false} GType 
+	 *   objects do not match and must not be merged
+	 */
+	private static boolean isGTypeMergeable(GType type1, GType type2) {
+		if (type1.getType() != type2.getType()) {
+			return false;
+		}
+		if (type1.getMinResolution() != type2.getMinResolution()) {
+			return false;
+		}
+		if (type1.getMaxResolution() != type2.getMaxResolution()) {
+			return false;
+		}
+		if (type1.getMinLevel() != type2.getMinLevel()) {
+			return false;
+		}
+		if (type1.getMaxLevel() != type2.getMaxLevel()) {
+			return false;
+		}
+		// roadClass and roadSpeed are taken from the ConvertedWay objects 
+		//
+		//default name is applied before the RoadMerger starts
+		//so they needn't be equal 
+		//		if (stringEquals(gtype.getDefaultName(),
+		//				otherGType.getDefaultName()) == false) {
+		//			return false;
+		//		}
+
+		// log.info("Matches");
+		return true;
+	}
+
+	/**
+	 * Checks if the tag values of the {@link Way} objects of both roads 
+	 * match so that both roads can be merged. 
+	 * @param mergePoint the coord where both roads should be merged
+	 * @param way1 1st way
+	 * @param way2 2nd way
+	 * @return {@code true} tag values match so that both roads might be merged;
+	 *  {@code false} tag values differ so that road must not be merged
+	 */
+	private static boolean isWayMergeable(Coord mergePoint, Way way1, Way way2) {
+		// tags that need to have an equal value
+		for (String tagname : mergeTagsEqualValue) {
+			String tag1 = way1.getTag(tagname);
+			String tag2 = way2.getTag(tagname);
+			if (stringEquals(tag1, tag2) == false) {
+				if (log.isDebugEnabled()){
+					log.debug(tagname, "does not match", way1.getId(), "("
+							+ tag1 + ")", way2.getId(), "(" + tag2
+							+ ")");
+				}
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Checks if the angle between the two {@link Way} objects of both roads 
+	 * is not too sharp so that both roads can be merged. 
+	 * @param mergePoint the coord where both roads should be merged
+	 * @param way1 1st way
+	 * @param way2 2nd way
+	 * @return {@code true} angle is okay, roads might be merged;
+	 *  {@code false} angle is so sharp that roads must not be merged
+	 */
+	private static boolean isAngleOK(Coord mergePoint, Way way1, Way way2) {
+		// Check the angle of the two ways
+		Coord cOnWay1;
+		if (way1.getPoints().get(0) == mergePoint) {
+			cOnWay1 = way1.getPoints().get(1);
+		} else {
+			cOnWay1 = way1.getPoints().get(way1.getPoints().size() - 2);
+		}
+		Coord cOnWay2;
+		if (way2.getPoints().get(0) == mergePoint) {
+			cOnWay2 = way2.getPoints().get(1);
+		} else {
+			cOnWay2 = way2.getPoints().get(
+					way2.getPoints().size() - 2);
+		}
+
+		double angle = Math.abs(Utils.getAngle(cOnWay1, mergePoint, cOnWay2));
+		if (angle > MAX_MERGE_ANGLE) {
+			// The angle exceeds the limit => do not merge
+			// Don't know if this is really required or not. 
+			// But the number of merges which do not succeed due to this
+			// restriction is quite low and there have been requests
+			// for this: http://www.mkgmap.org.uk/pipermail/mkgmap-dev/2013q3/018649.html
+
+			log.info("Do not merge ways",way1.getId(),"and",way2.getId(),"because they span a too big angle",angle,"°");
+			return false;
+		}
+
+		return true;
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
index ee115b0..f9459a1 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
@@ -278,9 +278,21 @@ public class RuleFileReader {
 			rearrangeExpression(op.getSecond());
 
 			swapForSelectivity((BinaryOp) op);
+
+			// Rearrange ((A&B)&C) to (A&(B&C)).
+			while (op.getFirst().isType(AND)) {
+				Op aAndB = op.getFirst();
+				Op c = op.getSecond();
+				op.setFirst(aAndB.getFirst()); // A
+
+				aAndB.setFirst(aAndB.getSecond());
+				((BinaryOp) aAndB).setSecond(c);  // a-and-b is now b-and-c
+				((BinaryOp) op).setSecond(aAndB);
+			}
+
 			Op op1 = op.getFirst();
 			Op op2 = op.getSecond();
-			
+
 			// If the first term is an EQUALS or EXISTS then this subtree is
 			// already solved and we need to do no more.
 			if (isSolved(op1)) {
@@ -424,9 +436,11 @@ public class RuleFileReader {
 			return 10;
 
 		case AND:
-		case OR:
 			return Math.min(selectivity(op.getFirst()), selectivity(op.getSecond()));
-		
+
+		case OR:
+			return Math.max(selectivity(op.getFirst()), selectivity(op.getSecond()));
+
 		default:
 			return 1000;
 		}
@@ -471,6 +485,14 @@ public class RuleFileReader {
 				keystring = first.getFirst().getKeyValue() + "=*";
 			} else if (first.isType(NOT_EXISTS)) {
 				throw new SyntaxException(scanner, "Cannot start rule with tag!=*");
+			} else if (first.getFirst() != null &&
+					first.getFirst().getType() == FUNCTION
+					&& ((StyleFunction) first.getFirst()).isIndexable())
+			{
+				// Extract the initial key and add an exists clause at the beginning
+				AndOp aop = combineWithExists(new ValueOp(first.getFirst().getKeyValue()), op);
+				optimiseAndSaveBinaryOp(scanner, aop, actions, gt);
+				return;
 			} else {
 				throw new SyntaxException(scanner, "Invalid rule expression: " + op);
 			}
@@ -486,12 +508,7 @@ public class RuleFileReader {
 
 			// We can make every other binary op work by converting to AND(EXISTS, op), as long as it does
 			// not involve an un-indexable function.
-			Op existsOp = new ExistsOp();
-			existsOp.setFirst(first);
-
-			AndOp andOp = new AndOp();
-			andOp.setFirst(existsOp);
-			andOp.setSecond(op);
+			AndOp andOp = combineWithExists(first, op);
 			optimiseAndSaveBinaryOp(scanner, andOp, actions, gt);
 			return;
 		}
@@ -499,6 +516,16 @@ public class RuleFileReader {
 		createAndSaveRule(keystring, op, actions, gt);
 	}
 
+	private AndOp combineWithExists(Op first, BinaryOp op) {
+		Op existsOp = new ExistsOp();
+		existsOp.setFirst(first);
+
+		AndOp andOp = new AndOp();
+		andOp.setFirst(existsOp);
+		andOp.setSecond(op);
+		return andOp;
+	}
+
 	private void saveRestOfOr(TokenScanner scanner, ActionList actions, GType gt, Op second, LinkedOp op1) {
 		if (second.isType(OR)) {
 			LinkedOp nl = LinkedOp.create(second.getFirst(), false);
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
index 30a803f..ad8086d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import java.util.Set;
 
 import uk.me.parabola.mkgmap.reader.osm.Rule;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * An index to reduce the number of rules that have to be executed.
@@ -61,18 +62,46 @@ import uk.me.parabola.mkgmap.reader.osm.Rule;
 public class RuleIndex {
 	private final List<RuleDetails> ruleDetails = new ArrayList<RuleDetails>();
 
-	// This is an index of all rules that start with EQUALS (A=B)
-	private final Map<String, BitSet> existKeys = new HashMap<String, BitSet>();
-	// This is an index of all rules that start with EXISTS (A=*)
-	private final Map<String, BitSet> tagVals = new HashMap<String, BitSet>();
-	// This is an index of all rules by the tag name (A).
-	private final Map<String, BitSet> tagnames = new HashMap<String, BitSet>();
-
-	// Maps a rule number to the tags that might be changed by that rule
-	private final Map<Integer, List<String>> changeTags = new HashMap<Integer, List<String>>();
+	private final Map<Short, TagHelper> tagKeyMap = new HashMap<>();
+	private TagHelper[] tagKeyArray = null;
 
 	private boolean inited;
 
+	private class TagHelper{
+		// This is an index of all rules that start with EXISTS (A=*)
+		final BitSet exists;
+		// This is an index of all rules that start with EQUALS (A=B) 
+		Map<String, BitSet> tagVals;
+		
+		public TagHelper(BitSet exits){
+			this.exists = exits;
+		}
+
+		public void addTag(String val, BitSet value) {
+			if (tagVals == null)
+				tagVals = new HashMap<>();
+			if (exists != null){	
+				BitSet merged = new BitSet();
+				merged.or(exists);
+				merged.or(value);
+				tagVals.put(val, merged);
+			} else
+				tagVals.put(val, value);
+		}
+
+		public BitSet getBitSet(String tagVal) {
+			if (tagVals != null){
+				BitSet set = tagVals.get(tagVal);
+				if (set != null){
+					return (BitSet) set.clone();
+				}
+			} 
+			if (exists != null)
+				return (BitSet) exists.clone();
+			return new BitSet();
+		}
+	}
+	
 	/**
 	 * Save the rule and maintains several lists related to it from the other
 	 * information that is supplied.
@@ -82,24 +111,6 @@ public class RuleIndex {
 	 */
 	public void addRuleToIndex(RuleDetails rd) {
 		assert !inited;
-		int ruleNumber = ruleDetails.size();
-		String keystring = rd.getKeystring();
-		Set<String> changeableTags = rd.getChangingTags();
-
-		if (keystring.endsWith("=*")) {
-			String key = keystring.substring(0, keystring.length() - 2);
-			addExists(key, ruleNumber);
-			addUnknowns(key, ruleNumber);
-		} else {
-			addKeyVal(keystring, ruleNumber);
-			int ind = keystring.indexOf('=');
-			if (ind >= 0) {
-				String key = keystring.substring(0, ind);
-				addUnknowns(key, ruleNumber);
-			}
-		}
-
-		addChangables(changeableTags, ruleNumber);
 		ruleDetails.add(rd);
 	}
 
@@ -124,27 +135,61 @@ public class RuleIndex {
 	 * @return A BitSet of rules numbers.
 	 * If there are no rules then null will be returned.
 	 */
-	public BitSet getRulesForTag(String tagval) {
-		BitSet set = tagVals.get(tagval);
-
-		// Need to also look up all rules that might match highway=*
-		int i = tagval.indexOf('=');
-		String s2 = tagval.substring(0, i);
-		BitSet set2 = existKeys.get(s2);
-		
-		BitSet res = new BitSet();
-		if (set != null)
-			res.or(set);
-		if (set2 != null) 
-			res.or(set2);
-		return res;
+	public BitSet getRulesForTag(short tagKey, String tagVal) {
+		TagHelper th;
+		if (tagKeyArray != null){
+			if (tagKey >= 0 & tagKey < tagKeyArray.length){
+				th = tagKeyArray[tagKey];
+			} else 
+				th = null;
+		} else {
+			th = tagKeyMap.get(tagKey);
+		}
+		if (th == null)
+			return new BitSet();
+		return th.getBitSet(tagVal);
 	}
 
+	
 	/**
 	 * Prepare the index for use.  This involves merging in all the possible
 	 * rules that could be run as a result of actions changing tags.
 	 */
 	public void prepare() {
+		if (inited)
+			return;
+		// This is an index of all rules that start with EXISTS (A=*)
+		Map<String, BitSet> existKeys = new HashMap<String, BitSet>();
+		// This is an index of all rules that start with EQUALS (A=B)
+		Map<String, BitSet> tagVals = new HashMap<String, BitSet>();
+		
+		// This is an index of all rules by the tag name (A).
+		Map<String, BitSet> tagnames = new HashMap<String, BitSet>();
+
+		// Maps a rule number to the tags that might be changed by that rule
+		Map<Integer, List<String>> changeTags = new HashMap<Integer, List<String>>();
+		
+		for (int i = 0; i < ruleDetails.size(); i++){
+			int ruleNumber = i;
+			RuleDetails rd = ruleDetails.get(i);
+			String keystring = rd.getKeystring();
+			Set<String> changeableTags = rd.getChangingTags();
+
+			if (keystring.endsWith("=*")) {
+				String key = keystring.substring(0, keystring.length() - 2);
+				addNumberToMap(existKeys, key, ruleNumber);
+				addNumberToMap(tagnames, key, ruleNumber);
+			} else {
+				addNumberToMap(tagVals, keystring, ruleNumber);
+				int ind = keystring.indexOf('=');
+				if (ind >= 0) {
+					String key = keystring.substring(0, ind);
+					addNumberToMap(tagnames, key, ruleNumber);
+				} 
+			}
+			addChangables(changeTags, changeableTags, ruleNumber);
+		}
+		
 		for (Map.Entry<Integer, List<String>> ent : changeTags.entrySet()) {
 			int ruleNumber = ent.getKey();
 			List<String> changeTagList = ent.getValue();
@@ -213,23 +258,42 @@ public class RuleIndex {
 			} while (!newChanged.isEmpty());
 		}
 
+		// compress the index: create one hash map with one entry for each key
+		int highestKey = 0;
+		for (Map.Entry<String, BitSet> entry  : existKeys.entrySet()){
+			Short skey = TagDict.getInstance().xlate(entry.getKey());
+			if (skey > highestKey)
+				highestKey = skey;
+			tagKeyMap.put(skey, new TagHelper(entry.getValue()));
+		}
+		for (Map.Entry<String, BitSet> entry  : tagVals.entrySet()){
+			String keyString = entry.getKey();
+			int ind = keyString.indexOf('=');
+			if (ind >= 0) {
+				short key = TagDict.getInstance().xlate(keyString.substring(0, ind));
+				String val = keyString.substring(ind+1);
+				if (key > highestKey)
+					highestKey = key;
+				TagHelper th = tagKeyMap.get(key);
+				if (th == null){
+					th = new TagHelper(null);
+					tagKeyMap.put(key, th);
+				} 
+				th.addTag(val, entry.getValue());
+			}
+		}
+		if (highestKey > 0 && highestKey < 1024){
+			tagKeyArray = new TagHelper[highestKey+1];
+			for (Map.Entry<Short, TagHelper> entry  : tagKeyMap.entrySet()){
+				tagKeyArray[entry.getKey()] = entry.getValue();
+			}
+			tagKeyMap.clear();
+		}
+			
 		inited = true;
 	}
 
-	private void addExists(String keystring, int ruleNumber) {
-		addNumberToMap(existKeys, keystring, ruleNumber);
-	}
-
-	private void addKeyVal(String keystring, int ruleNumber) {
-		addNumberToMap(tagVals, keystring, ruleNumber);
-
-	}
-
-	private void addUnknowns(String keystring, int ruleNumber) {
-		addNumberToMap(tagnames, keystring, ruleNumber);
-	}
-
-	private void addNumberToMap(Map<String, BitSet> map, String key, int ruleNumber) {
+	private static void addNumberToMap(Map<String, BitSet> map, String key, int ruleNumber) {
 		BitSet set = map.get(key);
 		if (set == null) {
 			set = new BitSet();
@@ -241,11 +305,12 @@ public class RuleIndex {
 	/**
 	 * For each rule number, we maintain a list of tags that might be
 	 * changed by that rule.
+	 * @param changeTags 
 	 * @param changeableTags The tags that might be changed if the rule is
 	 * matched.
 	 * @param ruleNumber The rule number.
 	 */
-	private void addChangables(Set<String> changeableTags, int ruleNumber) {
+	private static void addChangables(Map<Integer, List<String>> changeTags, Set<String> changeableTags, int ruleNumber) {
 		List<String> tags = changeTags.get(ruleNumber);
 		if (tags == null) {
 			tags = new ArrayList<String>();
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
index b816035..b54cb6c 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
@@ -19,9 +19,18 @@ package uk.me.parabola.mkgmap.osmstyle;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map.Entry;
 import java.util.Set;
+
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.osmstyle.eval.AbstractBinaryOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.AbstractOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.LinkedBinaryOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.LinkedOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Rule;
 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
@@ -35,11 +44,22 @@ import uk.me.parabola.mkgmap.reader.osm.WatchableTypeResult;
  * @author Steve Ratcliffe
  */
 public class RuleSet implements Rule, Iterable<Rule> {
+	private static final Logger log = Logger.getLogger(RuleSet.class);
 	private Rule[] rules;
 
+	// identifies cached values 
+	int cacheId;
+	boolean compiled = false;
+
 	private RuleIndex index = new RuleIndex();
 	private final Set<String> usedTags = new HashSet<String>();
-
+	
+	@Override
+	public void resolveType(Element el, TypeResult result) {
+		cacheId = resolveType(cacheId, el, result);
+	}
+	
+	
 	/**
 	 * Resolve the type for this element by running the rules in order.
 	 *
@@ -52,23 +72,27 @@ public class RuleSet implements Rule, Iterable<Rule> {
 	 * one type may be saved here.  If there are no matches then nothing will
 	 * be saved.
 	 */
-	public void resolveType(Element el, TypeResult result) {
+	public int resolveType(int cacheId, Element el, TypeResult result) {
 		WatchableTypeResult a = new WatchableTypeResult(result);
-
+		if (!compiled || cacheId == Integer.MAX_VALUE)
+			compile();
+		// new element, invalidate all caches
+		cacheId++;
+		
 		// Get all the rules that could match from the index.  
 		BitSet candidates = new BitSet();
-		for (String tag : el) {
-			BitSet rules = index.getRulesForTag(tag);
-			if (rules != null)
+		for (Entry<Short, String> tagEntry : el.getFastTagEntryIterator()) {
+			BitSet rules = index.getRulesForTag(tagEntry.getKey(), tagEntry.getValue());
+			if (rules != null && !rules.isEmpty() )
 				candidates.or(rules);
 		}
-
 		for (int i = candidates.nextSetBit(0); i >= 0; i = candidates.nextSetBit(i + 1)) {			
 			a.reset();
-			rules[i].resolveType(el, a);
+			cacheId = rules[i].resolveType(cacheId, el, a);
 			if (a.isResolved())
-				return;
+				return cacheId;
 		}
+		return cacheId;
 	}
 
 	public Iterator<Rule> iterator() {
@@ -86,6 +110,7 @@ public class RuleSet implements Rule, Iterable<Rule> {
 	 * will be either a plain tag name A, or with a value A=B.
 	 */
 	public void add(String keystring, Rule rule, Set<String> changeableTags) {
+		compiled = false;
 		index.addRuleToIndex(new RuleDetails(keystring, rule, changeableTags));
 	}
 
@@ -133,6 +158,7 @@ public class RuleSet implements Rule, Iterable<Rule> {
 		//		   + rs.getUsedTags());
 		addUsedTags(rs.usedTags);
 		//System.out.println("Result: " + getUsedTags().toString());
+		compiled = false;
 	}
 
 	/**
@@ -142,6 +168,7 @@ public class RuleSet implements Rule, Iterable<Rule> {
 	public void prepare() {
 		index.prepare();
 		rules = index.getRules();
+		compile();
 	}
 
 	public Set<String> getUsedTags() {
@@ -152,6 +179,76 @@ public class RuleSet implements Rule, Iterable<Rule> {
 		this.usedTags.addAll(usedTags);
 	}
 
+	/**
+	 * Compile the rules and reset caches. Detect common sub-expressions and
+	 * make sure that all rules use the same instance of these common
+	 * sub-expressions.
+	 */
+	private void compile(){
+		HashMap<String, Op> tests = new HashMap<String, Op>();
+
+		for (Rule rule:rules){
+			Op op;
+			if (rule instanceof ExpressionRule)
+				op = ((ExpressionRule)rule).getOp();
+			else if (rule instanceof ActionRule)
+				op = ((ActionRule) rule).getOp();
+			else {
+				log.error("unexpected rule instance");
+				continue;
+			}
+			if (op instanceof AbstractBinaryOp){
+				AbstractBinaryOp binOp = (AbstractBinaryOp) op;
+				binOp.setFirst(compileOp(tests, binOp.getFirst()));
+				binOp.setSecond(compileOp(tests, binOp.getSecond()));
+				op = compileOp(tests, binOp);
+			} else if (op instanceof AbstractOp){
+				op = compileOp(tests, op);
+			} else if (op instanceof LinkedBinaryOp){
+				((LinkedBinaryOp) op).setFirst(compileOp(tests, ((LinkedBinaryOp) op).getFirst()));
+				((LinkedBinaryOp) op).setSecond(compileOp(tests, ((LinkedBinaryOp) op).getSecond()));
+			} else if (op instanceof LinkedOp) {
+				Op wrappedOp = compileOp(tests, ((LinkedOp) op).getFirst());
+				op.setFirst(wrappedOp);
+			} else {
+				log.error("unexpected op instance");
+				continue;
+			}
+			if (rule instanceof ExpressionRule)
+				((ExpressionRule)rule).setOp(op);
+			else if (rule instanceof ActionRule)
+				((ActionRule) rule).setOp(op);
+			else {
+				log.error("unexpected rule instance");
+				continue;
+			}
+		}
+		cacheId = 0;
+		compiled = true;
+	}
+	
+	private Op compileOp(HashMap<String, Op> tests, Op op){
+		if (op instanceof AbstractBinaryOp){
+			AbstractBinaryOp binOp = (AbstractBinaryOp) op;
+			binOp.setFirst(compileOp(tests, binOp.getFirst()));
+			binOp.setSecond(compileOp(tests, binOp.getSecond()));
+		}
+		if (op instanceof LinkedOp){
+			// LinkedOp is referenced by other OPs, don't replace it 
+			return op;
+		}
+		String test = op.toString();
+		Op commonOp = tests.get(test);
+		if (commonOp == null){
+			if (op instanceof AbstractOp)
+				((AbstractOp)op).resetCache();
+			tests.put(test, op);
+			commonOp = op;
+		}
+		
+		return commonOp;
+	}
+	
 	public void setFinalizeRule(Rule finalizeRule) {
 		if (rules == null) {
 			// this method must be called after prepare() is called so
@@ -161,5 +258,6 @@ public class RuleSet implements Rule, Iterable<Rule> {
 		for (Rule rule : rules) 
 			rule.setFinalizeRule(finalizeRule);
 		
+		compiled = false;
 	}
-}
+} 
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
index 3d45a16..f88691a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
@@ -237,7 +237,7 @@ public class StyleImpl implements Style {
 			set.addAll(nameTagList);
 
 		// There are a lot of tags that are used within mkgmap that 
-		InputStream is = getClass().getResourceAsStream("/styles/builtin-tag-list");
+		InputStream is = this.getClass().getResourceAsStream("/styles/builtin-tag-list");
 		try {
 			if (is != null) {
 				BufferedReader br = new BufferedReader(new InputStreamReader(is));
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 653f810..f51fa56 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -16,12 +16,17 @@
  */
 package uk.me.parabola.mkgmap.osmstyle;
 
+import it.unimi.dsi.fastutil.shorts.ShortArrayList;
 import java.util.ArrayList;
+
 import java.util.Arrays;
-import java.util.Collection;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
@@ -31,6 +36,8 @@ 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.imgfmt.app.Label;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 import uk.me.parabola.imgfmt.app.net.NODHeader;
 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.MapObject;
@@ -48,7 +55,6 @@ 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.osmstyle.housenumber.HousenumberGenerator;
 import uk.me.parabola.mkgmap.reader.osm.CoordPOI;
 import uk.me.parabola.mkgmap.reader.osm.Element;
@@ -60,9 +66,11 @@ 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.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.MultiHashMap;
 
 /**
  * Convert from OSM to the mkgmap intermediate format using a style.
@@ -75,30 +83,20 @@ public class StyledConverter implements OsmConverter {
 	private static final Logger log = Logger.getLogger(StyledConverter.class);
 	private static final Logger roadLog = Logger.getLogger(StyledConverter.class.getName()+".roads");
 
-	private final List<String> nameTagList;
+	private final ShortArrayList nameTagList;
 
 	private final MapCollector collector;
 
 	private Clipper clipper = Clipper.NULL_CLIPPER;
 	private Area bbox = new Area(-90.0d, -180.0d, 90.0d, 180.0d); // default is planet
 
-	// 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>>();
-
-	private final List<Relation> throughRouteRelations = new ArrayList<Relation>();
-
-	/** all tags used for access restrictions */
-	public final static List<String> ACCESS_TAGS = Arrays.asList(
-			"mkgmap:bicycle", 
-			"mkgmap:foot", 
-			"mkgmap:truck", 
-			"mkgmap:car",
-			"mkgmap:bus", 
-			"mkgmap:taxi",
-			"mkgmap:emergency", 
-			"mkgmap:delivery");
+	private final List<RestrictionRelation> restrictions = new ArrayList<>();
+	private final MultiHashMap<Long, RestrictionRelation> wayRelMap = new MultiHashMap<>();
 	
+	private Map<Node, List<Way>> poiRestrictions = new LinkedHashMap<>();
+	 
+	private final List<Relation> throughRouteRelations = new ArrayList<>();
+
 	// limit line length to avoid problems with portions of really
 	// long lines being assigned to the wrong subdivision
 	private static final int MAX_LINE_LENGTH = 40000;
@@ -109,19 +107,15 @@ public class StyledConverter implements OsmConverter {
 	private static final int MAX_NODES_IN_WAY = 64; // possibly could be increased
 
 	// nodeIdMap maps a Coord into a CoordNode
-	private IdentityHashMap<Coord, CoordNode> nodeIdMap = new IdentityHashMap<Coord, CoordNode>();
+	private IdentityHashMap<Coord, CoordNode> nodeIdMap = new IdentityHashMap<>();
 
 	public final static String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids";
 	
-	private List<Way> roads = new ArrayList<Way>();
-	private List<GType> roadTypes = new ArrayList<GType>();
-	private List<Way> lines = new ArrayList<Way>();
-	private List<GType> lineTypes = new ArrayList<GType>();
-	private HashMap<Long, Way> modifiedRoads = new HashMap<Long, Way>();
-	private HashSet<Long> deletedRoads = new HashSet<Long>();
-
-	private final double minimumArcLength;
-	
+	private List<ConvertedWay> roads = new ArrayList<>();
+	private List<ConvertedWay> lines = new ArrayList<>();
+	private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>();
+	private HashSet<Long> deletedRoads = new HashSet<>();
+
 	private int nextNodeId = 1;
 	
 	private HousenumberGenerator housenumberGenerator;
@@ -137,11 +131,13 @@ public class StyledConverter implements OsmConverter {
 	private int reportDeadEnds; 
 	private final boolean linkPOIsToWays;
 	private final boolean mergeRoads;
+	private WrongAngleFixer wrongAngleFixer;
 
 	private LineAdder lineAdder = new LineAdder() {
 		public void add(MapLine element) {
-			if (element instanceof MapRoad)
+			if (element instanceof MapRoad){
 				collector.addRoad((MapRoad) element);
+			}
 			else
 				collector.addLine(element);
 		}
@@ -150,7 +146,13 @@ public class StyledConverter implements OsmConverter {
 	public StyledConverter(Style style, MapCollector collector, EnhancedProperties props) {
 		this.collector = collector;
 
-		nameTagList = LocatorUtil.getNameTags(props);
+		List<String> nameTags = LocatorUtil.getNameTags(props);
+		if (nameTags != null){
+			nameTagList = new ShortArrayList();
+			for(String n : nameTags)
+				nameTagList.add(TagDict.getInstance().xlate(n));
+		} else 
+			nameTagList = null;
 
 		wayRules = style.getWayRules();
 		nodeRules = style.getNodeRules();
@@ -172,16 +174,12 @@ public class StyledConverter implements OsmConverter {
 		LineAdder overlayAdder = style.getOverlays(lineAdder);
 		if (overlayAdder != null)
 			lineAdder = overlayAdder;
-		String rsa = props.getProperty("remove-short-arcs", "0");
-		minimumArcLength = (!rsa.isEmpty())? Double.parseDouble(rsa) : 0.0;
-		if (minimumArcLength > 0){
-			System.err.println("Warning: remove-short-arcs=" + rsa + " overrides default 0." +
-					" This is no longer recommended for a routable map.");
-		}
 		linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
 		
 		// undocumented option - usually used for debugging only
 		mergeRoads = props.getProperty("no-mergeroads", false) == false;
+
+		wrongAngleFixer = new WrongAngleFixer(bbox);
 	}
 
 	/** One type result for ways to avoid recreating one for each way. */ 
@@ -189,21 +187,36 @@ public class StyledConverter implements OsmConverter {
 	private class WayTypeResult implements TypeResult 
 	{
 		private Way way;
+		/** flag if the rule was fired */
+		private boolean matched;
+		
 		public void setWay(Way way) {
 			this.way = way;
+			this.matched = false;
 		}
 		
 		public void add(Element el, GType type) {
+			this.matched = true;
 			if (type.isContinueSearch()) {
 				// If not already copied, do so now
 				if (el == way) 
 					el = way.copy();
 			}
 			postConvertRules(el, type);
+			housenumberGenerator.addWay((Way)el);
 			addConvertedWay((Way) el, type);
 		}
+
+		/**
+		 * Retrieves if a rule of the style matched and the way is converted.
+		 * @return {@code true} way is converted; {@code false} way is not converted
+		 */
+		public boolean isMatched() {
+			return matched;
+		}
 	}
 	
+	
 	/**
 	 * This takes the way and works out what kind of map feature it is and makes
 	 * the relevant call to the mapper callback.
@@ -213,80 +226,134 @@ public class StyledConverter implements OsmConverter {
 	 *
 	 * @param way The OSM way.
 	 */
+	private final static short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
+	private final static short makeCycleWayTagKey = TagDict.getInstance().xlate("mkgmap:make-cycle-way");
+	private long lastRoadId = 0; 
+	private int lineCacheId = 0;
+	private BitSet routingWarningWasPrinted = new BitSet();
 	public void convertWay(final Way way) {
-		if (way.getPoints().size() < 2)
-			return;
-		
-		if (way.getTagCount() == 0) {
-			// no tags => nothing to convert
-			return;
+		if (way.getPoints().size() < 2 || way.getTagCount() == 0){
+			// no tags or no points => nothing to convert
+			removeRestrictionsWithWay(Level.WARNING, way, "is ignored");
 		}
-
 		preConvertRules(way);
 
-		housenumberGenerator.addWay(way);
-		
+		String styleFilterTag = way.getTag(styleFilterTagKey);
 		Rule rules;
-		if ("polyline".equals(way.getTag("mkgmap:stylefilter")))
+		if ("polyline".equals(styleFilterTag))
 			rules = lineRules;
-		else if ("polygon".equals(way.getTag("mkgmap:stylefilter")))
+		else if ("polygon".equals(styleFilterTag))
 			rules = polygonRules;
-		else
-			rules = wayRules;
-		
+		else {
+			if (way.isClosedInOSM() && !way.isComplete() && !way.hasIdenticalEndPoints())
+				way.getPoints().add(way.getPoints().get(0));
+			
+			if (way.hasIdenticalEndPoints() == false || way.getPoints().size() < 4)
+				rules = lineRules;
+			else
+				rules = wayRules;
+		}
+
+		Way cycleWay = null;
+		String cycleWayTag = way.getTag(makeCycleWayTagKey);
+		if ("yes".equals(cycleWayTag)){
+			way.deleteTag("mkgmap:make-cycle-way");
+			cycleWay = makeCycleWay(way);
+			way.addTag("bicycle", "no"); // make sure that bicycles are using the added bicycle way 
+		}
 		wayTypeResult.setWay(way);
-		rules.resolveType(way, wayTypeResult);
+		lineCacheId = rules.resolveType(lineCacheId, way, wayTypeResult);
+		if (wayTypeResult.isMatched() == false) {
+			// no match found but we have to keep it for house number processing
+			housenumberGenerator.addWay(way);
+		}
+		if (cycleWay != null){
+			wayTypeResult.setWay(cycleWay);
+			lineCacheId = rules.resolveType(lineCacheId, cycleWay, wayTypeResult);
+			if (wayTypeResult.isMatched() == false) {
+				// no match found but we have to keep it for house number processing
+				housenumberGenerator.addWay(cycleWay);
+			}
+		}
+		if (lastRoadId != way.getId()){
+			// this way was not added to the roads list
+			removeRestrictionsWithWay(Level.WARNING, way, "is not routable");
+		} else {
+			// way was added as road, check if we also have non-routable lines for the way
+			// which have to be skipped by WrongAngleFixer
+			for (int i = lines.size()-1; i >= 0; --i){
+				ConvertedWay cw = lines.get(i); 
+				if (cw.getWay().getId() == way.getId()){
+					cw.setOverlay(true);
+					int lineType = cw.getGType().getType();
+					if (GType.isSpecialRoutableLineType(lineType) && cw.getGType().getMinLevel() == 0){
+						if (!routingWarningWasPrinted.get(lineType)){
+							log.error("routable type", GType.formatType(cw.getGType().getType()),
+							"is used with a non-routable way which was also added as a routable way. This leads to routing errors.",
+							"Try --check-styles to check the style.");
+							routingWarningWasPrinted.set(lineType);
+						}
+					}
+				}
+				else 
+					break;
+			}
+		}
 	}
 
-
+	private int lineIndex = 0;
+	private final static short onewayTagKey = TagDict.getInstance().xlate("oneway"); 
 	private void addConvertedWay(Way way, GType foundType) {
+		if (foundType.getFeatureKind() == FeatureKind.POLYGON){ 
+			addShape(way, foundType);
+			return;
+		}
 		
-		if (foundType.getFeatureKind() == FeatureKind.POLYLINE) {
-			GType type = new GType(foundType);
-
-			String oneWay = way.getTag("oneway");
+		boolean wasReversed = false;
+		String oneWay = way.getTag(onewayTagKey);
+		if (oneWay != null){
 			if("-1".equals(oneWay) || "reverse".equals(oneWay)) {
 				// it's a oneway street in the reverse direction
 				// so reverse the order of the nodes and change
 				// the oneway tag to "yes"
 				way.reverse();
-				way.addTag("oneway", "yes");
-				if (type.isRoad() && "roundabout".equals(way.getTag("junction")))
-					log.warn("Roundabout", way.getId(), "has reverse oneway tag (" + way.getPoints().get(0).toOSMURL() + ")");
+				wasReversed = true;
+				way.addTag(onewayTagKey, "yes");
 			}
 
-			if (way.isBoolTag("oneway")) {
-				way.addTag("oneway", "yes");
-				if (type.isRoad() && checkFixmeCoords(way) )
+			if (way.tagIsLikeYes(onewayTagKey)) {
+				way.addTag(onewayTagKey, "yes");
+				if (foundType.isRoad() && checkFixmeCoords(way) )
 					way.addTag("mkgmap:dead-end-check", "false");
 			} else 
-				way.deleteTag("oneway");
-			
-			if(foundType.isRoad() &&
-			   !MapObject.hasExtendedType(foundType.getType())){
-				recalcRoadClass(way, type);
-				recalcRoadSpeed(way, type);
-		    	roads.add(way);
-		    	roadTypes.add(type);
-		    }
-		    else {
-		    	lines.add(way);
-		    	lineTypes.add(type);
-		    }
+				way.deleteTag(onewayTagKey);
 		}
-		else
-			addShape(way, foundType);
+		ConvertedWay cw = new ConvertedWay(lineIndex++, way, foundType);
+		cw.setReversed(wasReversed);
+		if (cw.isRoad()){
+			roads.add(cw);
+			if (wasReversed && cw.isRoundabout())
+				log.warn("Roundabout", way.getId(), "has reverse oneway tag (" + way.getPoints().get(0).toOSMURL() + ")");
+			lastRoadId = way.getId();
+		}
+		else 
+			lines.add(cw);
 	}
 
 	/** One type result for nodes to avoid recreating one for each node. */ 
 	private NodeTypeResult nodeTypeResult = new NodeTypeResult();
 	private class NodeTypeResult implements TypeResult {
 		private Node node;
+		/** flag if the rule was fired */
+		private boolean matched;
+		
 		public void setNode(Node node) {
 			this.node = node;
+			this.matched = false;
 		}
 		
 		public void add(Element el, GType type) {
+			this.matched = true;
 			if (type.isContinueSearch()) {
 				// If not already copied, do so now
 				if (el == node) 
@@ -294,8 +361,17 @@ public class StyledConverter implements OsmConverter {
 			}
 			
 			postConvertRules(el, type);
+			housenumberGenerator.addNode((Node)el);
 			addPoint((Node) el, type);
 		}
+
+		/**
+		 * Retrieves if a rule of the style matched and the node is converted.
+		 * @return {@code true} node is converted; {@code false} node is not converted
+		 */
+		public boolean isMatched() {
+			return matched;
+		}
 	}
 
 	/**
@@ -312,13 +388,16 @@ public class StyledConverter implements OsmConverter {
 
 		preConvertRules(node);
 
-		housenumberGenerator.addNode(node);
-		
 		nodeTypeResult.setNode(node);
 		nodeRules.resolveType(node, nodeTypeResult);
+		if (nodeTypeResult.isMatched() == false) {
+			// no match found but we have to keep it for house number processing
+			housenumberGenerator.addNode(node);
+		}
 	}
 	
 
+	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
 	/**
 	 * Rules to run before converting the element.
 	 */
@@ -326,148 +405,46 @@ public class StyledConverter implements OsmConverter {
 		if (nameTagList == null)
 			return;
 
-		for (String t : nameTagList) {
-			String val = el.getTag(t);
+		for (short tagKey : nameTagList) {
+			String val = el.getTag(tagKey);
 			if (val != null) {
-				el.addTag("name", val);
+				if (tagKey != nameTagKey) {
+					// add or replace name 
+					el.addTag(nameTagKey, val);
+				}
 				break;
 			}
 		}
 	}
 
 	/**
-	 * Recalculates the road class defined in the given {@link GType} object based on the tags
-	 * <ul>
-	 * <li>{@code mkgmap:road-class}</li>
-	 * <li>{@code mkgmap:road-class-min}</li>
-	 * <li>{@code mkgmap:road-class-max}</li>
-	 * </ul>
-	 * The road class of the {@link GType} object is changed if the tags modify its road class. 
-	 * 
-	 * @param el an element 
-	 * @param type a GType instance with the current road class.
-	 * @return {@code true} the road class of {@code type} has been changed; 
-	 *    {@code false} the road class of of {@code type} has not been changed
+	 * Construct a cycleway that has the same points as an existing way.  Used for separate
+	 * cycle lanes.
+	 * @param way The original way.
+	 * @return The new way, which will have the same points and have suitable cycle tags.
 	 */
-	private boolean recalcRoadClass(Element el, GType type) {
-		// retrieve the original road class value
-		int roadClass = type.getRoadClass();
-		
-		// check if the road class is modified
-		String val = el.getTag("mkgmap:road-class");
-		if (val != null) {
-			if (val.startsWith("-")) {
-				roadClass -= Integer.decode(val.substring(1));
-			} else if (val.startsWith("+")) {
-				roadClass += Integer.decode(val.substring(1));
-			} else {
-				roadClass = Integer.decode(val);
-			}
-			val = el.getTag("mkgmap:road-class-max");
-			int roadClassMax = 4;
-			if (val != null)
-				roadClassMax = Integer.decode(val);
-			val = el.getTag("mkgmap:road-class-min");
-
-			int roadClassMin = 0;
-			if (val != null)
-				roadClassMin = Integer.decode(val);
-			if (roadClass > roadClassMax)
-				roadClass = roadClassMax;
-			else if (roadClass < roadClassMin)
-				roadClass = roadClassMin;
+	private static Way makeCycleWay(Way way) {
+		Way cycleWay = new Way(way.getId(), way.getPoints());
+		cycleWay.copyTags(way);
 
-		}
-		
-		if (roadClass == type.getRoadClass()) {
-			// no change of road class
-			return false;
-		} else {
-			// change the road class
-			type.setRoadClass(roadClass);
-			return true;
-		}
-	}
-	
-	/**
-	 * Recalculates the road speed defined in the given {@link GType} object based on the tags
-	 * <ul>
-	 * <li>{@code mkgmap:road-speed-class}</li>
-	 * <li>{@code mkgmap:road-speed}</li>
-	 * <li>{@code mkgmap:road-speed-min}</li>
-	 * <li>{@code mkgmap:road-speed-max}</li>
-	 * </ul>
-	 * The road speed of the {@link GType} object is changed if the tags modify its road speed. 
-	 * 
-	 * @param el an element 
-	 * @param type a GType instance with the current road speed.
-	 * @return {@code true} the road speed of {@code type} has been changed; 
-	 *    {@code false} the road speed of of {@code type} has not been changed
-	 */
-	private boolean recalcRoadSpeed(Element el, GType type) {
-		// retrieve the original road speed value
-		int roadSpeed = type.getRoadSpeed();
-		
-		// check if the road speed defined in the GType object is overridden
-		String roadSpeedOverride = el.getTag("mkgmap:road-speed-class");
-		if (roadSpeedOverride != null) {
-			try {
-				int rs = Integer.decode(roadSpeedOverride);
-				if (rs >= 0 && rs <= 7) {
-					// override the road speed class
-					roadSpeed = rs;
-				} else {
-					log.error(getDebugName(el)
-							+ " road classification mkgmap:road-speed-class="
-							+ roadSpeedOverride + " must be in [0;7]");
-				}
-			} catch (Exception exp) {
-				log.error(getDebugName(el)
-						+ " road classification mkgmap:road-speed-class="
-						+ roadSpeedOverride + " must be in [0;7]");
-			}
-		}
-		
-		// check if the road speed should be modified more
-		String val = el.getTag("mkgmap:road-speed");
-		if(val != null) {
-			if(val.startsWith("-")) {
-				roadSpeed -= Integer.decode(val.substring(1));
-			}
-			else if(val.startsWith("+")) {
-				roadSpeed += Integer.decode(val.substring(1));
-			}
-			else {
-				roadSpeed = Integer.decode(val);
-			}
-			val = el.getTag("mkgmap:road-speed-max");
-			int roadSpeedMax = 7;
-			if(val != null)
-				roadSpeedMax = Integer.decode(val);
-			val = el.getTag("mkgmap:road-speed-min");
-
-			int roadSpeedMin = 0;
-			if(val != null)
-				roadSpeedMin = Integer.decode(val);
-			if(roadSpeed > roadSpeedMax)
-				roadSpeed = roadSpeedMax;
-			else if(roadSpeed < roadSpeedMin)
-				roadSpeed = roadSpeedMin;
-		}
-		
-		if (roadSpeed == type.getRoadSpeed()) {
-			// road speed is not changed
-			return false;
-		} else {
-			type.setRoadSpeed(roadSpeed);
-			return true;
-		}
+		String name = way.getTag("name");
+		if(name != null)
+			name += " (cycleway)";
+		else
+			name = "cycleway";
+		cycleWay.addTag("name", name);
+		cycleWay.addTag("access", "no");
+		cycleWay.addTag("bicycle", "yes");
+		cycleWay.addTag("foot", "no");
+		cycleWay.addTag("mkgmap:synthesised", "yes");
+		cycleWay.addTag(onewayTagKey, "no");
+		return cycleWay;
 	}
 	
 	/**
 	 * Built in rules to run after converting the element.
 	 */
-	private void postConvertRules(Element el, GType type) {
+	private static void postConvertRules(Element el, GType type) {
 		// Set the default_name if no name is set
 		if (type.getDefaultName() != null && el.getName() == null)
 			el.addTag("mkgmap:label:1", type.getDefaultName());
@@ -494,7 +471,26 @@ public class StyledConverter implements OsmConverter {
 	}
 
 	/**
-	 * Merges roads with identical attributes (gtype, OSM tags) to reduce the size of the 
+	 * Remove all restriction relations that are invalid if the way will not appear
+	 * in the NOD file.
+	 * @param logLevel 
+	 * @param way the way that was removed
+	 * @param reason explanation for the removal
+	 */
+	private void removeRestrictionsWithWay(Level logLevel, Way way, String reason){
+		List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
+		for (RestrictionRelation rr : rrList){
+			if (rr.isValidWithoputWay(way.getId()) == false){
+				if (log.isLoggable(logLevel)){
+					log.log(logLevel, "restriction",rr.toBrowseURL()," is ignored because referenced way",way.toBrowseURL(),reason);
+				}
+				restrictions.remove(rr);
+			}
+		}
+	}
+
+	/**
+	 * Merges roads with identical attributes (GType, OSM tags) to reduce the size of the 
 	 * road network.
 	 */
 	private void mergeRoads() {
@@ -503,75 +499,75 @@ public class StyledConverter implements OsmConverter {
 			return;
 		}
 		
-		// instantiate the RoadMerger - the roads and roadTypes lists are copied
-		RoadMerger merger = new RoadMerger(roads, roadTypes, restrictions, throughRouteRelations);
-		// clear the lists
-		roads.clear();
-		roadTypes.clear();
-		// merge the roads and copy the results to the roads and roadTypes list
-		merger.merge(roads, roadTypes);
+		RoadMerger merger = new RoadMerger();
+		roads = merger.merge(roads, restrictions, throughRouteRelations);
 	}
 	
 	public void end() {
 		setHighwayCounts();
 		findUnconnectedRoads();
+		rotateClosedWaysToFirstNode();
 		filterCoordPOI();
-		removeShortArcsByMergingNodes();
+
+		wrongAngleFixer.optimizeWays(roads, lines, modifiedRoads, deletedRoads, restrictions);
+
 		// make sure that copies of modified roads have equal points 
-		for (int i = 0; i < lines.size(); i++){
-			Way line = lines.get(i);
-			if (deletedRoads.contains(line.getId())){
-				lines.set(i, null);
+		for (ConvertedWay line : lines){
+			if (!line.isValid())
+				continue; 
+			Way way = line.getWay();
+			if (deletedRoads.contains(way.getId())){
+				line.getPoints().clear();
 				continue;
 			}
-			Way modWay = modifiedRoads.get(line.getId());
+			if (!line.isOverlay())
+				continue;
+			ConvertedWay modWay = modifiedRoads.get(way.getId());
 			if (modWay != null){
 				List<Coord> points = line.getPoints();
 				points.clear();
 				points.addAll(modWay.getPoints());
+				if (modWay.isReversed() != line.isReversed())
+					Collections.reverse(points);
+			} 
+		}
+		for (Long wayId: deletedRoads){
+			if (wayRelMap.containsKey(wayId)){
+				log.error("internal error: was that is used in valid restriction relation was removed, id:",wayId);
 			}
 		}
 		deletedRoads = null;
 		modifiedRoads = null;
 
 		mergeRoads();
-
+		
 		resetHighwayCounts();
 		setHighwayCounts();
 		
-		for (int i = 0; i < lines.size(); i++){
-			Way line = lines.get(i);
-			if (line == null)
-				continue;
-			GType gt = lineTypes.get(i);
-			addLine(line, gt);
+		for (ConvertedWay cw : lines){
+			if (cw.isValid())
+				addLine(cw.getWay(), cw.getGType());
 		}
 		lines = null;
-		lineTypes = null;
 		if (roadLog.isInfoEnabled()) {
 			roadLog.info("Flags: oneway,no-emergency, no-delivery, no-throughroute, no-truck, no-bike, no-foot, carpool, no-taxi, no-bus, no-car");
-			roadLog.info(String.format("%19s %4s %11s %s", "Road-OSM-Id","Type","Flags", "Labels"));
+			roadLog.info(String.format("%19s %4s %11s %6s %s", "Road-OSM-Id","Type","Flags", "Points", "Labels"));
 		}
 		// add the roads after the other lines
-		for (int i = 0; i < roads.size(); i++){
-			Way road = roads.get(i);
-			if (road == null)
-				continue;
-			GType gt = roadTypes.get(i);
-			addRoad(road, gt);
+		for (ConvertedWay cw : roads){
+			if (cw.isValid())
+				addRoad(cw);
 		}
-		roads = null;
-		roadTypes = null;
 		
 		housenumberGenerator.generate(lineAdder);
 		
-		Collection<List<RestrictionRelation>> lists = restrictions.values();
-		for (List<RestrictionRelation> l : lists) {
-
-			for (RestrictionRelation rr : l) {
-				rr.addRestriction(collector);
-			}
+		createRouteRestrictionsFromPOI();
+		poiRestrictions = null;
+		
+		for (RestrictionRelation rr : restrictions) {
+			rr.addRestriction(collector, nodeIdMap);
 		}
+		roads = null;
 
 		for(Relation relation : throughRouteRelations) {
 			Node node = null;
@@ -622,6 +618,198 @@ public class StyledConverter implements OsmConverter {
 	}
 
 	/**
+	 * Try to make sure that closed ways start with a point that is 
+	 * also part of another road. This reduces the number of nodes
+	 * a little bit.
+	 * 
+	 */
+	private void rotateClosedWaysToFirstNode() {
+		for (ConvertedWay cw: roads){
+			if (!cw.isValid())
+				continue;
+			Way way = cw.getWay();
+			List<Coord> points = way.getPoints();
+			if (points.size() < 3)
+				continue;
+			if (points.get(0) != points.get(points.size()-1))
+				continue;
+			// this is a closed way 
+			Coord p0 = points.get(0);
+			if (p0.getHighwayCount() > 2)
+				continue;
+			
+			for (int i = 1; i < points.size() - 1;i++){
+				Coord p = points.get(i);
+				if (p.getHighwayCount() > 1){
+					p.incHighwayCount(); // this will be the new first + last point
+					// first point connects only last point, remove last
+					points.remove(points.size()-1);
+					p0.decHighwayCount();
+					Collections.rotate(points, -i);
+					points.add(points.get(0)); // close again
+					modifiedRoads.put(way.getId(), cw); 
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check if roundabout has correct direction. Set driveOnRight or
+	 * driveOnLeft is not yet set.
+	 * 
+	 */
+	private void checkRoundabout(ConvertedWay cw) {
+		if (cw.isRoundabout() == false)
+			return;
+		Way way = cw.getWay();
+		List<Coord> points = way.getPoints();
+		// if roundabout checking is enabled and roundabout has at
+		// least 3 points and it has not been marked as "don't
+		// check", check its direction
+		if (checkRoundabouts && points.size() > 2
+				&& !way.tagIsLikeYes("mkgmap:no-dir-check")
+				&& !way.tagIsLikeNo("mkgmap:dir-check")) {
+			Coord centre = way.getCofG();
+			int dir = 0;
+			// check every third segment
+			for (int i = 0; (i + 1) < points.size(); i += 3) {
+				Coord pi = points.get(i);
+				Coord pi1 = points.get(i + 1);
+				// TODO: check if high prec coords allow to use smaller
+				// distance
+				// don't check segments that are very short
+				if (pi.distance(centre) > 2.5 && pi.distance(pi1) > 2.5) {
+					// determine bearing from segment that starts with
+					// point i to centre of roundabout
+					double a = pi.bearingTo(pi1);
+					double b = pi.bearingTo(centre) - a;
+					while (b > 180)
+						b -= 360;
+					while (b < -180)
+						b += 360;
+					// if bearing to centre is between 15 and 165
+					// degrees consider it trustworthy
+					if (b >= 15 && b < 165)
+						++dir;
+					else if (b <= -15 && b > -165)
+						--dir;
+				}
+			}
+			if (dir == 0)
+				log.info("Roundabout segment " + way.getId()
+						+ " direction unknown (see "
+						+ points.get(0).toOSMURL() + ")");
+			else {
+				boolean clockwise = dir > 0;
+				if (points.get(0) == points.get(points.size() - 1)) {
+					// roundabout is a loop
+					if (!driveOnLeft && !driveOnRight) {
+						if (clockwise) {
+							log.info("Roundabout "
+									+ way.getId()
+									+ " is clockwise so assuming vehicles should drive on left side of road ("
+									+ centre.toOSMURL() + ")");
+							driveOnLeft = true;
+							NODHeader.setDriveOnLeft(true);
+						} else {
+							log.info("Roundabout "
+									+ way.getId()
+									+ " is anti-clockwise so assuming vehicles should drive on right side of road ("
+									+ centre.toOSMURL() + ")");
+							driveOnRight = true;
+						}
+					}
+					if (driveOnLeft && !clockwise || driveOnRight
+							&& clockwise) {
+						log.warn("Roundabout "
+								+ way.getId()
+								+ " direction is wrong - reversing it (see "
+								+ centre.toOSMURL() + ")");
+						way.reverse();
+					}
+				} else if (driveOnLeft && !clockwise || driveOnRight
+						&& clockwise) {
+					// roundabout is a line
+					log.warn("Roundabout segment " + way.getId()
+							+ " direction looks wrong (see "
+							+ points.get(0).toOSMURL() + ")");
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * If POI changes access restrictions (e.g. bollards), create corresponding
+	 * route restrictions so that only allowed vehicles/pedestrians are routed
+	 * through this point.
+	 */
+	private void createRouteRestrictionsFromPOI() {
+		Iterator<Map.Entry<Node, List<Way>>> iter = poiRestrictions.entrySet().iterator();
+		while (iter.hasNext()){
+			Map.Entry<Node, List<Way>> entry = iter.next();
+			Node node = entry.getKey();
+			Coord p = node.getLocation();
+			// list of ways that are connected to the poi
+			List<Way> wayList = entry.getValue();
+
+			byte exceptMask = AccessTagsAndBits.evalAccessTags(node);
+			Map<Integer,CoordNode> otherNodeIds = new LinkedHashMap<>();
+			CoordNode viaNode = null;
+			boolean viaIsUnique = true;
+			for (Way way : wayList) {
+				CoordNode lastNode = null;
+				for (Coord co: way.getPoints()){
+					// not 100% fail safe: points may have been replaced before
+					if (co instanceof CoordNode == false)
+						continue;
+					CoordNode cn = (CoordNode) co;
+					if (p.highPrecEquals(cn)){
+						if (viaNode == null)
+							viaNode = cn;
+						else if (viaNode != cn){
+							log.error("Found multiple points with equal coords as CoordPOI at " + p.toOSMURL());
+							// if we ever get here we can add code to identify the exact node 
+							viaIsUnique = false;
+						}
+						if (lastNode != null)
+							otherNodeIds.put(lastNode.getId(),lastNode);
+					} else {
+						if (p.highPrecEquals(lastNode))
+							otherNodeIds.put(cn.getId(),cn);
+					}
+					lastNode = cn;
+				}
+			}
+			if (viaNode == null){
+				log.error("Did not find CoordPOI node at " + p.toOSMURL() + " in ways " + wayList);
+				continue;
+			}
+			if (viaIsUnique == false){
+				log.error("Found multiple points with equal coords as CoordPOI at " + p.toOSMURL());
+				continue;
+			}
+			if (otherNodeIds.size() < 2){
+				log.info("Access restriction in POI node " + node.toBrowseURL() + " was ignored, has no effect on any connected way");
+				continue;
+			}
+			GeneralRouteRestriction rr = new GeneralRouteRestriction("no_through", exceptMask, "CoordPOI at " + p.toOSMURL());
+			rr.setViaNodes(Arrays.asList(viaNode));
+			int added = collector.addRestriction(rr);
+			if (added == 0){
+				log.info("Access restriction in POI node " + node.toBrowseURL() + " was ignored, has no effect on any connected way");
+			} else { 
+				log.info("Access restriction in POI node", node.toBrowseURL(), "was translated to",added,"route restriction(s)");
+			}
+			if (wayList.size() > 1 && added > 2){
+				log.warn("Access restriction in POI node", node.toBrowseURL(), "affects routing on multiple ways");
+			}
+		}
+	}
+
+ 	
+	/**
 	 * 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=".."'.
@@ -638,39 +826,40 @@ public class StyledConverter implements OsmConverter {
 
 		// relation rules are not applied here because they are applied
 		// earlier by the RelationStyleHook
-		
 		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);
+				restrictions.add(rr);
+				for (long id : rr.getWayIds())
+					wayRelMap.add(id, rr);
 			}
 		}
 		else if("through_route".equals(relation.getTag("type"))) {
 			throughRouteRelations.add(relation);
 		}
 	}
-
+	
 	private void addLine(Way way, GType gt) {
+		addLine(way, gt, -1);
+	}
+	
+	private void addLine(Way way, GType gt, int replType) {
 		List<Coord> wayPoints = way.getPoints();
-		List<Coord> points = new ArrayList<Coord>(wayPoints.size());
+		List<Coord> points = new ArrayList<>(wayPoints.size());
 		double lineLength = 0;
 		Coord lastP = null;
 		for (Coord p : wayPoints) {
-			if (lastP != null && p.equals(lastP))
+			if (p.highPrecEquals(lastP))
 				continue;
+			
 			points.add(p);
 			if(lastP != null) {
 				lineLength += p.distance(lastP);
 				if(lineLength >= MAX_LINE_LENGTH) {
 					if (log.isInfoEnabled())
 						log.info("Splitting line", way.toBrowseURL(), "at", p.toOSMURL(), "to limit its length to", (long)lineLength + "m");
-					addLine(way, gt, points);
-					points = new ArrayList<Coord>(wayPoints.size() - points.size() + 1);
+					addLine(way, gt, replType, points);
+					points = new ArrayList<>(wayPoints.size() - points.size() + 1);
 					points.add(p);
 					lineLength = 0;
 				}
@@ -679,19 +868,19 @@ public class StyledConverter implements OsmConverter {
 		}
 
 		if(points.size() > 1)
-			addLine(way, gt, points);
+			addLine(way, gt, replType, points);
 	}
 
-	private void addLine(Way way, GType gt, List<Coord> points) {
+	private void addLine(Way way, GType gt, int replType, List<Coord> points) {
 		MapLine line = new MapLine();
 		elementSetup(line, gt, way);
+		if (replType >= 0)
+			line.setType(replType);
 		line.setPoints(points);
 
 		
-		if (way.isBoolTag("oneway"))
+		if (way.tagIsLikeYes(onewayTagKey))
 			line.setDirection(true);
-		if (way.isBoolTag("mkgmap:skipSizeFilter"))
-			line.setSkipSizeFilter(true);
 
 		clipper.clipLine(line, lineAdder);
 	}
@@ -701,16 +890,14 @@ public class StyledConverter implements OsmConverter {
 		// the tile and some distance around it.  Therefore a way that is closed in reality may not be closed
 		// as we see it in its incomplete state.
 		//
-		// Here isClosed means that it is really closed in OSM, and therefore it is safe to clip the line
-		// segment to the tile boundaries.
-		if (!way.isClosed())
+		if (!way.hasIdenticalEndPoints() && way.hasEqualEndPoints())
+			log.error("shape is not closed with identical points " + way.getId());
+		if (!way.hasIdenticalEndPoints())
 			return;
-
-		final MapShape shape = new MapShape();
+		// TODO: split self intersecting polygons?
+		final MapShape shape = new MapShape(way.getId());
 		elementSetup(shape, gt, way);
 		shape.setPoints(way.getPoints());
-		if (way.isBoolTag("mkgmap:skipSizeFilter"))
-			shape.setSkipSizeFilter(true);
 
 		clipper.clipShape(shape, collector);
 	}
@@ -751,11 +938,29 @@ public class StyledConverter implements OsmConverter {
 		collector.addPoint(mp);
 	}
 
-	private void elementSetup(MapElement ms, GType gt, Element element) {
+	private static final short[] labelTagKeys = {
+		TagDict.getInstance().xlate("mkgmap:label:1"),
+		TagDict.getInstance().xlate("mkgmap:label:2"),
+		TagDict.getInstance().xlate("mkgmap:label:3"),
+		TagDict.getInstance().xlate("mkgmap:label:4"),
+	};
+	private static final short highResOnlyTagKey = TagDict.getInstance().xlate("mkgmap:highest-resolution-only");
+	private static final short skipSizeFilterTagKey = TagDict.getInstance().xlate("mkgmap:skipSizeFilter");
+
+	private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country");
+	private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region");
+	private static final short cityTagKey = TagDict.getInstance().xlate("mkgmap:city");
+	private static final short postal_codeTagKey = TagDict.getInstance().xlate("mkgmap:postal_code");
+	private static final short streetTagKey = TagDict.getInstance().xlate("mkgmap:street");
+	private static final short housenumberTagKey = TagDict.getInstance().xlate("mkgmap:housenumber");
+	private static final short phoneTagKey = TagDict.getInstance().xlate("mkgmap:phone");
+	private static final short is_inTagKey = TagDict.getInstance().xlate("mkgmap:is_in");
+	
+	private static void elementSetup(MapElement ms, GType gt, Element element) {
 		String[] labels = new String[4];
 		int noLabels = 0;
-		for (int labelNo = 1; labelNo <= 4; labelNo++) {
-			String label1 = element.getTag("mkgmap:label:"+labelNo);
+		for (int labelNo = 0; labelNo < 4; labelNo++) {
+			String label1 = element.getTag(labelTagKeys[labelNo]);
 			String label = Label.squashSpaces(label1);
 			if (label != null) {
 				labels[noLabels] = label;
@@ -769,17 +974,25 @@ public class StyledConverter implements OsmConverter {
 		ms.setType(gt.getType());
 		ms.setMinResolution(gt.getMinResolution());
 		ms.setMaxResolution(gt.getMaxResolution());
+
+		if (element.tagIsLikeYes(highResOnlyTagKey)){
+			ms.setMinResolution(ms.getMaxResolution());
+		}
+		
+		if (ms instanceof MapLine && element.tagIsLikeYes(skipSizeFilterTagKey)){
+			((MapLine)ms).setSkipSizeFilter(true);
+		}
 		
 		// Now try to get some address info for POIs
 		
-		String country      = element.getTag("mkgmap:country");
-		String region       = element.getTag("mkgmap:region");
-		String city         = element.getTag("mkgmap:city");
-		String zip          = element.getTag("mkgmap:postal_code");
-		String street 	    = element.getTag("mkgmap:street");
-		String houseNumber  = element.getTag("mkgmap:housenumber");
-		String phone        = element.getTag("mkgmap:phone");
-		String isIn         = element.getTag("mkgmap:is_in");
+		String country      = element.getTag(countryTagKey);
+		String region       = element.getTag(regionTagKey);
+		String city         = element.getTag(cityTagKey);
+		String zip          = element.getTag(postal_codeTagKey);
+		String street 	    = element.getTag(streetTagKey);
+		String houseNumber  = element.getTag(housenumberTagKey);
+		String phone        = element.getTag(phoneTagKey);
+		String isIn         = element.getTag(is_inTagKey);
 
 		if(country != null)
 			ms.setCountry(country);
@@ -816,104 +1029,33 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
-	private boolean hasAccessRestriction(Element osmElement) {
-		for (String tag : ACCESS_TAGS) {
-			if (osmElement.isNotBoolTag(tag)) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
 	/**
 	 * Add a way to the road network. May call itself recursively and
 	 * might truncate the way if splitting is required. 
 	 * @param way the way
 	 * @param gt the type assigned by the style
 	 */
-	private void addRoad(Way way, GType gtParm) {
-		GType gt = new GType(gtParm);
+	private void addRoad(ConvertedWay cw) {
+		Way way = cw.getWay();
 		if (way.getPoints().size() < 2){
 			log.warn("road has < 2 points",way.getId(),"(discarding)");
 			return;
 		}
 
-		if("roundabout".equals(way.getTag("junction"))) {
-			List<Coord> points = way.getPoints();
-			// if roundabout checking is enabled and roundabout has at
-			// least 3 points and it has not been marked as "don't
-			// check", check its direction
-			if(checkRoundabouts &&
-			   way.getPoints().size() > 2 &&
-			   !way.isBoolTag("mkgmap:no-dir-check") &&
-			   !way.isNotBoolTag("mkgmap:dir-check")) {
-				Coord centre = way.getCofG();
-				int dir = 0;
-				// check every third segment
-				for(int i = 0; (i + 1) < points.size(); i += 3) {
-					Coord pi = points.get(i);
-					Coord pi1 = points.get(i + 1);
-					// don't check segments that are very short
-					if(pi.quickDistance(centre) > 2.5 &&
-					   pi.quickDistance(pi1) > 2.5) {
-						// determine bearing from segment that starts with
-						// point i to centre of roundabout
-						double a = pi.bearingTo(pi1);
-						double b = pi.bearingTo(centre) - a;
-						while(b > 180)
-							b -= 360;
-						while(b < -180)
-							b += 360;
-						// if bearing to centre is between 15 and 165
-						// degrees consider it trustworthy
-						if(b >= 15 && b < 165)
-							++dir;
-						else if(b <= -15 && b > -165)
-							--dir;
-					}
-				}
-				if (dir == 0)
-					log.info("Roundabout segment", way.getId(), "direction unknown (see", points.get(0).toOSMURL() + ")");
-				else {
-					boolean clockwise = dir > 0;
-					if (points.get(0) == points.get(points.size() - 1)) {
-						// roundabout is a loop
-						if (!driveOnLeft && !driveOnRight) {
-							if (clockwise) {
-								if (log.isInfoEnabled())
-									log.info("Roundabout", way.getId(), "is clockwise so assuming vehicles should drive on left side of road (" + centre.toOSMURL() + ")");
-								driveOnLeft = true;
-								NODHeader.setDriveOnLeft(true);
-							} else {
-								if (log.isInfoEnabled())
-									log.info("Roundabout", way.getId(), "is anti-clockwise so assuming vehicles should drive on right side of road (" + centre.toOSMURL() + ")");
-								driveOnRight = true;
-							}
-						}
-						if (driveOnLeft && !clockwise ||
-								driveOnRight && clockwise)
-						{
-							log.warn("Roundabout", way.getId(), "direction is wrong - reversing it (see", centre.toOSMURL() + ")");
-							way.reverse();
-						}
-					} else if (driveOnLeft && !clockwise ||
-							driveOnRight && clockwise)
-					{
-						// roundabout is a line
-						log.warn("Roundabout segment", way.getId(), "direction looks wrong (see", points.get(0).toOSMURL() + ")");
-					}
-				}
-			}
-		}
+		
+		checkRoundabout(cw);
 
 		// process any Coords that have a POI associated with them
+		final double stubSegmentLength = 25; // metres
 		String wayPOI = way.getTag(WAY_POI_NODE_IDS);
 		if (wayPOI != null) {
 			List<Coord> points = way.getPoints();
-
+			
 			// look for POIs that modify the way's road class or speed
-			// this could be e.g. highway=traffic_signals that reduces the
-			// road speed to cause a short increase of traveling time
+			// or contain access restrictions
+			// This could be e.g. highway=traffic_signals that reduces the
+			// road speed to cause a short increase of travelling time
+			// or a barrier
 			for(int i = 0; i < points.size(); ++i) {
 				Coord p = points.get(i);
 				if (p instanceof CoordPOI && ((CoordPOI) p).isUsed()) {
@@ -921,168 +1063,107 @@ public class StyledConverter implements OsmConverter {
 					Node node = cp.getNode();
 					if (wayPOI.contains("["+node.getId()+"]")){
 						log.debug("POI",node.getId(),"changes way",way.getId());
+
+						// make sure that we create nodes for all POI that 
+						// are converted to RouteRestrictions
+						if(p.getHighwayCount() < 2 && cp.getConvertToViaInRouteRestriction() && (i != 0 && i != points.size()-1))
+							p.incHighwayCount();
+						
 						String roadClass = node.getTag("mkgmap:road-class");
 						String roadSpeed = node.getTag("mkgmap:road-speed");
 						if(roadClass != null || roadSpeed != null) {
-							// if the way has more than one point
-							// following this one, split the way at the
-							// next point to limit the size of the
-							// affected region
-							if((i + 2) < points.size() &&
-									safeToSplitWay(points, i + 1, i, points.size() - 1)) {
-								Way tail = splitWayAt(way, i + 1);
+							// find good split point after POI
+							Coord splitPoint;
+							double segmentLength = 0;
+							int splitPos = i+1;
+							while( splitPos+1 < points.size()){
+								splitPoint = points.get(splitPos);
+								segmentLength += splitPoint.distance(points.get(splitPos - 1));
+								if (splitPoint.getHighwayCount() > 1
+										|| segmentLength > stubSegmentLength - 5)
+									break;
+								splitPos++;
+							}
+							if (segmentLength > stubSegmentLength + 10){
+								// insert a new point after the POI to
+								// make a short stub segment
+								splitPoint = points.get(splitPos);
+								Coord prev = points.get(splitPos-1);
+								double dist = splitPoint.distance(prev);
+								double neededLength = stubSegmentLength - (segmentLength - dist);
+								
+								splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist);
+								double newDist = splitPoint.distance(prev);
+								segmentLength += newDist - dist;
+								splitPoint.incHighwayCount();
+								points.add(splitPos, splitPoint);
+							}
+							if((splitPos + 1) < points.size() && way.isViaWay() == false &&
+									safeToSplitWay(points, splitPos, i, points.size() - 1)) {
+								Way tail = splitWayAt(way, splitPos);
 								// recursively process tail of way
-								addRoad(tail, gt);
+								addRoad(new ConvertedWay(cw, tail));
 							}
-							boolean classChanged = recalcRoadClass(node, gt);
+							boolean classChanged = cw.recalcRoadClass(node);
 							if (classChanged && log.isInfoEnabled()){
-								log.info("POI changing road class of", way.toBrowseURL(), "to", gt.getRoadClass(), "at", points.get(0).toOSMURL()); 								
+								log.info("POI changing road class of", way.toBrowseURL(), "to", cw.getRoadClass(), "at", points.get(0).toOSMURL()); 								
 							}
-							boolean speedChanged = recalcRoadSpeed(node, gt);
+							boolean speedChanged = cw.recalcRoadSpeed(node);
 							if (speedChanged && log.isInfoEnabled()){
-								log.info("POI changing road speed of", way.toBrowseURL(), "to", gt.getRoadSpeed(), "at" , points.get(0).toOSMURL());
+								log.info("POI changing road speed of", way.toBrowseURL(), "to", cw.getRoadSpeed(), "at" , points.get(0).toOSMURL());
 							}
 						}
 					}
 				}
 
-				// if this isn't the first (or last) point in the way
+				// if this isn't the last point in the way
 				// and the next point modifies the way's speed/class,
 				// split the way at this point to limit the size of
 				// the affected region
-				if (i > 0 && (i + 1) < points.size()
+				if (i + 1 < points.size()
 						&& points.get(i + 1) instanceof CoordPOI) {
 					CoordPOI cp = (CoordPOI) points.get(i + 1);
 					Node node = cp.getNode();
 					if (cp.isUsed() && wayPOI.contains("["+node.getId()+"]")){
 						if (node.getTag("mkgmap:road-class") != null
 								|| node.getTag("mkgmap:road-speed") != null) {
-							if (safeToSplitWay(points, i, i - 1,
-									points.size() - 1)) {
-								Way tail = splitWayAt(way, i);
-								// recursively process tail of way
-								addRoad(tail, gt);
+							// find good split point before POI
+							double segmentLength = 0;
+							int splitPos = i;
+							Coord splitPoint;
+							while( splitPos >= 0){
+								splitPoint = points.get(splitPos);
+								segmentLength += splitPoint.distance(points.get(splitPos + 1));
+								if (splitPoint.getHighwayCount() >= 2
+										|| segmentLength > stubSegmentLength - 5)
+									break;
+								--splitPos;			
 							}
-						}
-					}
-				}
-			}
-
-			// now look for POIs that have an access restriction defined -
-			// if they do, copy the access permissions to the way -
-			// what we want to achieve is modifying the way's access
-			// permissions where it passes through the POI without
-			// affecting the rest of the way too much - to that end we
-			// split the way before and after the POI - if necessary,
-			// extra points are inserted before and after the POI to
-			// limit the size of the affected region
-
-			final double stubSegmentLength = 25; // metres
-			for(int i = 0; i < points.size(); ++i) {
-				Coord p = points.get(i);
-				// check if this POI modifies access and if so, split
-				// the way at the following point (if any) and then
-				// copy its access restrictions to the way
-				if (p instanceof CoordPOI && ((CoordPOI) p).isUsed()) {
-					CoordPOI cp = (CoordPOI) p;
-					Node node = cp.getNode();
-					if (hasAccessRestriction(node) && wayPOI.contains("["+node.getId()+"]")){
-						// if this or the next point are not the last
-						// points in the way, split at the next point
-						// taking care not to produce a short arc
-						if((i + 1) < points.size()) {
-							Coord p1 = points.get(i + 1);
-							// check if the next point is further away
-							// than we would like
-							double dist = p.distance(p1);
-							if(dist >= (2 * stubSegmentLength)) {
-								// insert a new point after the POI to
+							if (segmentLength > stubSegmentLength + 10){
+								// insert a new point before the POI to
 								// make a short stub segment
-								p1 = p.makeBetweenPoint(p1, stubSegmentLength / dist);
-								p1.incHighwayCount();
-								points.add(i + 1, p1);
+								splitPoint = points.get(splitPos);
+								Coord prev = points.get(splitPos+1);
+								double dist = splitPoint.distance(prev);
+								double neededLength = stubSegmentLength - (segmentLength - dist);
+								splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist);
+								segmentLength += splitPoint.distance(prev) - dist;
+								splitPoint.incHighwayCount();
+								splitPos++;
+								points.add(splitPos, splitPoint);
 							}
-
-							// now split the way at the next point to
-							// limit the region that has restricted
-							// access
-							if((i+2 < points.size() && 
-									safeToSplitWay(points, i+1, 0, points.size()-1))) {
-								Way tail = splitWayAt(way, i + 1);
+							if(splitPos > 0 &&
+									safeToSplitWay(points, splitPos, 0, points.size()-1)) {
+								Way tail = splitWayAt(way, splitPos);
 								// recursively process tail of way
-								addRoad(tail, gt);
-							}
-						}
-
-						// make the POI a node so that the region with
-						// restricted access is split into two as far
-						// as routing is concerned - this should stop
-						// routing across the POI when the start point
-						// is within the restricted region and the
-						// destination point is outside of the
-						// restricted region on the other side of the
-						// POI
-
-						// however, this still doesn't stop routing
-						// across the POI when both the start and end
-						// points are either side of the POI and both
-						// are in the restricted region
-						if (p.getHighwayCount() < 2){
-							if (i == 0|| i == points.size()-1 ||
-									safeToSplitWay(points, i, 0, points.size()-1))
-								p.incHighwayCount();
-							else {
-								points.set(i,new Coord(p.getLatitude(),p.getLongitude()));
-								points.get(i).incHighwayCount();
-							}
-						}
-
-						// copy all of the POI's access restrictions
-						// to the way segment
-						for (String accessTag : ACCESS_TAGS) {
-							if(node.isNotBoolTag(accessTag))
-								way.addTag(accessTag, "no");
-							
-						}
-					}
-				}
-
-				// check if the next point modifies access and if so,
-				// split the way either here or at a new point that's
-				// closer to the POI taking care not to introduce a
-				// short arc
-				if((i + 1) < points.size()) {
-					Coord p1 = points.get(i + 1);
-					if (p1 instanceof CoordPOI && ((CoordPOI) p1).isUsed()) {
-						CoordPOI cp = (CoordPOI) p1;
-						Node node = cp.getNode();
-						if (hasAccessRestriction(node) && wayPOI.contains("["+node.getId()+"]")){
-							// check if this point is further away
-							// from the POI than we would like
-							double dist = p.distance(p1);
-							if(dist >= (2 * stubSegmentLength)) {
-								// insert a new point to make a short
-								// stub segment
-								p1 = p1.makeBetweenPoint(p, stubSegmentLength / dist);
-								p1.incHighwayCount();
-								points.add(i + 1, p1);
-								continue;
-							}
-
-							// now split the way here if it is not the
-							// first point in the way
-							if(i > 0 &&
-							   safeToSplitWay(points, i, 0, points.size() - 1)) {
-								Way tail = splitWayAt(way, i);
-								// recursively process tail of road
-								addRoad(tail, gt);
+								addRoad(new ConvertedWay(cw, tail));
 							}
 						}
 					}
 				}
 			}
-		}
 
+		} 
 		// if there is a bounding box, clip the way with it
 
 		List<Way> clippedWays = null;
@@ -1091,8 +1172,10 @@ public class StyledConverter implements OsmConverter {
 			List<List<Coord>> lineSegs = LineClipper.clip(bbox, way.getPoints());
 
 			if (lineSegs != null) {
-
-				clippedWays = new ArrayList<Way>();
+				if (lineSegs.isEmpty()){
+					removeRestrictionsWithWay(Level.WARNING, way, "ends on tile boundary, restriction is ignored");
+				}
+				clippedWays = new ArrayList<>();
 
 				for (List<Coord> lco : lineSegs) {
 					Way nWay = new Way(way.getId());
@@ -1111,18 +1194,18 @@ public class StyledConverter implements OsmConverter {
 		}
 
 		if(clippedWays != null) {
-			for(Way cw : clippedWays) {
-				addRoadAfterSplittingLoops(cw, gt);
+			for(Way clippedWay : clippedWays) {
+				addRoadAfterSplittingLoops(new ConvertedWay(cw, clippedWay));
 			}
 		}
 		else {
 			// no bounding box or way was not clipped
-			addRoadAfterSplittingLoops(way, gt);
+			addRoadAfterSplittingLoops(cw);
 		}
 	}
 
-	private void addRoadAfterSplittingLoops(Way way, final GType gtParm) {
-		GType gt = new GType(gtParm);
+	private void addRoadAfterSplittingLoops(ConvertedWay cw) {
+		Way way = cw.getWay();
 		// make sure the way has nodes at each end
 		way.getPoints().get(0).incHighwayCount();
 		way.getPoints().get(way.getPoints().size() - 1).incHighwayCount();
@@ -1144,8 +1227,13 @@ public class StyledConverter implements OsmConverter {
 				Coord p1 = wayPoints.get(p1I);
 				if (p1.getHighwayCount() < 2)
 					continue;
+				int niceSplitPos = -1;
 				for(int p2I = p1I + 1; !wayWasSplit && p2I < numPointsInWay; p2I++) {
-					if(p1 == wayPoints.get(p2I)) {
+					Coord p2 = wayPoints.get(p2I);
+					if (p1 != p2){
+						if (p2.getHighwayCount() > 1)
+							niceSplitPos = p2I;
+					} else {
 						// way is a loop or intersects itself 
 						// attempt to split it into two ways
 
@@ -1153,16 +1241,21 @@ public class StyledConverter implements OsmConverter {
 						// check that splitting there will not produce
 						// a zero length arc - if it does try the
 						// previous point(s)
-						int splitI = p2I - 1;
+						int splitI;
+						if (niceSplitPos >= 0 && safeToSplitWay(wayPoints, niceSplitPos, p1I, p2I))
+							// prefer to split at a point that is going to be a node anyway
+							splitI = niceSplitPos;
+						else {
+							splitI = p2I - 1;
 						while(splitI > p1I &&
 							  !safeToSplitWay(wayPoints, splitI, p1I, p2I)) {
 							if (log.isInfoEnabled())
-								log.info("Looped way", getDebugName(way), "can't safely split at point[" + splitI + "], trying the preceeding point");
+								log.info("Looped way", way.getDebugName(), "can't safely split at point[" + splitI + "], trying the preceeding point");
 							--splitI;
 						}
-
+						}
 						if(splitI == p1I) {
-							log.warn("Splitting looped way", getDebugName(way), "would make a zero length arc, so it will have to be pruned at", wayPoints.get(p2I).toOSMURL());
+							log.warn("Splitting looped way", way.getDebugName(), "would make a zero length arc, so it will have to be pruned at", wayPoints.get(p2I).toOSMURL());
 							do {
 								log.warn("  Pruning point[" + p2I + "]");
 								wayPoints.remove(p2I);
@@ -1184,12 +1277,16 @@ public class StyledConverter implements OsmConverter {
 						else {
 							// split the way before the second point
 							if (log.isInfoEnabled())
-								log.info("Splitting looped way", getDebugName(way), "at", wayPoints.get(splitI).toOSMURL(), "- it has", (numPointsInWay - splitI - 1 ), "following segment(s).");
+								log.info("Splitting looped way", way.getDebugName(), "at", wayPoints.get(splitI).toOSMURL(), "- it has", (numPointsInWay - splitI - 1 ), "following segment(s).");
 							Way loopTail = splitWayAt(way, splitI);
+							
+							ConvertedWay next = new ConvertedWay(cw, loopTail);
+							
 							// recursively check (shortened) head for
 							// more loops
-							addRoadAfterSplittingLoops(way, gt);
+							addRoadAfterSplittingLoops(cw);
 							// now process the tail of the way
+							cw = next;
 							way = loopTail;
 							wayWasSplit = true;
 						}
@@ -1199,11 +1296,12 @@ public class StyledConverter implements OsmConverter {
 
 			if(!wayWasSplit) {
 				// no split required so make road from way
-				addRoadWithoutLoops(way, gt);
+				addRoadWithoutLoops(cw);
 			}
 		}
 	}
 
+	
 	/**
 	 * safeToSplitWay() returns true if it is safe (no short arcs will be
 	 * created) to split a way at a given position - assumes that the
@@ -1215,19 +1313,16 @@ public class StyledConverter implements OsmConverter {
 	 * @param ceiling upper limit of points to test (inclusive)
 	 * @return true if is OK to split as pos
 	 */
-	private boolean safeToSplitWay(List<Coord> points, int pos, int floor, int ceiling) {
+	private static boolean safeToSplitWay(List<Coord> points, int pos, int floor, int ceiling) {
 		Coord candidate = points.get(pos);
 		// avoid running off the ends of the list
 		if(floor < 0)
 			floor = 0;
 		if(ceiling >= points.size())
 			ceiling = points.size() - 1;
-		double arcLength = 0;
-		Coord prev = candidate;
 		// test points after pos
 		for(int i = pos + 1; i <= ceiling; ++i) {
 			Coord p = points.get(i);
-			arcLength += p.distance(prev);
 			if(i == ceiling || p.getHighwayCount() > 1) {
 				// point is going to be a node
 				if(candidate.equals(p)) {
@@ -1237,16 +1332,10 @@ public class StyledConverter implements OsmConverter {
 				// no need to test further
 				break;
 			}
-			prev = p;
 		}
-		if (arcLength == 0 || arcLength < minimumArcLength)
-			return false;
-		prev = candidate;
-		arcLength = 0;
 		// test points before pos
 		for(int i = pos - 1; i >= floor; --i) {
 			Coord p = points.get(i);
-			arcLength += p.distance(prev);
 			if(i == floor || p.getHighwayCount() > 1) {
 				// point is going to be a node
 				if(candidate.equals(p)) {
@@ -1258,25 +1347,16 @@ public class StyledConverter implements OsmConverter {
 			}
 		}
 
-		return (arcLength != 0 && arcLength >= minimumArcLength);
-	}
-
-	private static String getDebugName(Element el) {
-		String name = el.getName();
-		if(name == null)
-			name = el.getTag("ref");
-		if(name == null)
-			name = "";
-		else
-			name += " ";
-		return name + "(OSM id " + el.getId() + ")";
+		return true;
 	}
 
-	private void addRoadWithoutLoops(Way way, GType gt) {
-		List<Integer> nodeIndices = new ArrayList<Integer>();
+	private void addRoadWithoutLoops(ConvertedWay cw) {
+		Way way = cw.getWay();
+		GType gt = cw.getGType();
+		List<Integer> nodeIndices = new ArrayList<>();
 		List<Coord> points = way.getPoints();
 		Way trailingWay = null;
-		String debugWayName = getDebugName(way);
+		String debugWayName = way.getDebugName();
 
 		// collect the Way's nodes and also split the way if any
 		// inter-node arc length becomes excessive
@@ -1309,7 +1389,7 @@ public class StyledConverter implements OsmConverter {
 		}
 
 		WayBBox wayBBox = new WayBBox();
-
+		
 		for(int i = 0; i < points.size(); ++i) {
 			Coord p = points.get(i);
 
@@ -1366,16 +1446,34 @@ public class StyledConverter implements OsmConverter {
 					arcLength += d;
 				}
 			}
-
 			if(p.getHighwayCount() > 1) {
 				// this point is a node connecting highways
 				CoordNode coordNode = nodeIdMap.get(p);
 				if(coordNode == null) {
 					// assign a node id
-					coordNode = new CoordNode(p.getLatitude(), p.getLongitude(), nextNodeId++, p.getOnBoundary());
+					coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary());
 					nodeIdMap.put(p, coordNode);
 				}
-
+				
+				if (p instanceof CoordPOI){
+					// check if this poi should be converted to a route restriction
+					CoordPOI cp = (CoordPOI) p;
+					if (cp.getConvertToViaInRouteRestriction()){
+						String wayPOI = way.getTag(WAY_POI_NODE_IDS);
+						if (wayPOI != null && wayPOI.contains("[" + cp.getNode().getId() + "]")){
+							byte nodeAccess = AccessTagsAndBits.evalAccessTags(cp.getNode());
+							if (nodeAccess != cw.getAccess()){
+								List<Way> wayList = poiRestrictions.get(cp.getNode());
+								if (wayList == null){
+									wayList = new ArrayList<>();
+									poiRestrictions.put(cp.getNode(), wayList);
+								}
+								wayList.add(way);
+							}
+						}
+					}
+				}
+				
 				// add this index to node Indexes (should not already be there)
 				assert !nodeIndices.contains(i) : debugWayName + " has multiple nodes for point " + i + " new node is " + p.toOSMURL();
 				nodeIndices.add(i);
@@ -1396,28 +1494,26 @@ public class StyledConverter implements OsmConverter {
 		}
 
 		MapLine line = new MapLine();
-		elementSetup(line, gt, way);
+		elementSetup(line, cw.getGType(), way);
 		line.setPoints(points);
-		
 		MapRoad road = new MapRoad(way.getId(), line);
-		if (way.isBoolTag("mkgmap:skipSizeFilter"))
-			road.setSkipSizeFilter(true);
 
 		boolean doFlareCheck = true;
-		if("roundabout".equals(way.getTag("junction"))) {
+		
+		if (cw.isRoundabout()){
 			road.setRoundabout(true);
 			doFlareCheck = false;
 		}
 
-		if(way.isBoolTag("mkgmap:synthesised")) {
+		if(way.tagIsLikeYes("mkgmap:synthesised")) {
 			road.setSynthesised(true);
 			doFlareCheck = false;
 		}
 
-		if(way.isNotBoolTag("mkgmap:flare-check")) {
+		if(way.tagIsLikeNo("mkgmap:flare-check")) {
 			doFlareCheck = false;
 		}
-		else if(way.isBoolTag("mkgmap:flare-check")) {
+		else if(way.tagIsLikeYes("mkgmap:flare-check")) {
 			doFlareCheck = true;
 		}
 		road.doFlareCheck(doFlareCheck);
@@ -1427,51 +1523,45 @@ public class StyledConverter implements OsmConverter {
 		// set road parameters 
 
 		// copy road class and road speed
-		road.setRoadClass(gt.getRoadClass());
-		road.setSpeed(gt.getRoadSpeed());
+		road.setRoadClass(cw.getRoadClass());
+		road.setSpeed(cw.getRoadSpeed());
 		
-		if (way.isBoolTag("oneway")) {
+		if (cw.isOneway()) {
 			road.setDirection(true);
 			road.setOneway();
 		}
 
-		boolean[] noAccess = new boolean[RoadNetwork.NO_MAX];
-		noAccess[RoadNetwork.NO_EMERGENCY] = way.isNotBoolTag("mkgmap:emergency");
-		noAccess[RoadNetwork.NO_DELIVERY] = way.isNotBoolTag("mkgmap:delivery");
-		noAccess[RoadNetwork.NO_CAR] = way.isNotBoolTag("mkgmap:car");
-		noAccess[RoadNetwork.NO_BUS] = way.isNotBoolTag("mkgmap:bus");
-		noAccess[RoadNetwork.NO_TAXI] = way.isNotBoolTag("mkgmap:taxi");
-		noAccess[RoadNetwork.NO_FOOT] = way.isNotBoolTag("mkgmap:foot");
-		noAccess[RoadNetwork.NO_BIKE] = way.isNotBoolTag("mkgmap:bicycle");
-		noAccess[RoadNetwork.NO_TRUCK] = way.isNotBoolTag("mkgmap:truck");
-		road.setAccess(noAccess);
-
+		road.setAccess(cw.getAccess());
+		
 		// does the road have a carpool lane?
-		if (way.isBoolTag("mkgmap:carpool"))
+		if (cw.isCarpool())
 			road.setCarpoolLane();
 
-		if (way.isNotBoolTag("mkgmap:throughroute")) 
+		if (cw.isThroughroute() == false)
 			road.setNoThroughRouting();
 
-		if(way.isBoolTag("mkgmap:toll"))
+		if(cw.isToll())
 			road.setToll();
 
 		// by default, ways are paved
-		if(way.isBoolTag("mkgmap:unpaved"))
+		if(cw.isUnpaved())
 			road.paved(false);
 
 		// by default, way's are not ferry routes
-		if(way.isBoolTag("mkgmap:ferry"))
+		if(cw.isFerry())
 			road.ferry(true);
 
 		int numNodes = nodeIndices.size();
+		if (way.isViaWay() && numNodes > 2){
+			List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
+			for (RestrictionRelation rr : rrList){
+				rr.updateViaWay(way, nodeIndices);
+			}
+		}
 		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)
@@ -1484,48 +1574,6 @@ public class StyledConverter implements OsmConverter {
 					log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node");
 				}
 				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().getId() == way.getId()) {
-							rr.setToNode(thisCoordNode);
-						}
-						else if(rr.getFromWay().getId() == way.getId()) {
-							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().getId() == way.getId()) {
-							if(lastCoordNode != null)
-								rr.setToNode(lastCoordNode);
-						}
-						else if(rr.getFromWay().getId() == way.getId()) {
-							if(lastCoordNode != null)
-								rr.setFromNode(lastCoordNode);
-						}
-						else if(lastCoordNode != null) {
-							rr.addOtherNode(lastCoordNode);
-						}
-					}
-				}
-
-				lastRestrictions = theseRestrictions;
-				lastCoordNode = thisCoordNode;
 			}
 
 			road.setStartsWithNode(nodeIndices.get(0) == 0);
@@ -1536,11 +1584,10 @@ public class StyledConverter implements OsmConverter {
 			// shift the bits so that they have the correct position
 			int cmpAccess = (road.getRoadDef().getTabAAccess() & 0xff) + ((road.getRoadDef().getTabAAccess() & 0xc000) >> 6);
 			if (road.isDirection()) {
-				
 				cmpAccess |= 1<<10;
 			}
 			String access = String.format("%11s",Integer.toBinaryString(cmpAccess)).replace(' ', '0');
-			roadLog.info(String.format("%19d 0x%-2x %11s %s", way.getId(), road.getType(), access, Arrays.toString(road.getLabels())));
+			roadLog.info(String.format("%19d 0x%-2x %11s %6d %s", way.getId(), road.getType(), access, road.getPoints().size(),Arrays.toString(road.getLabels())));
 		}
 		
 		// add the road to the housenumber generator
@@ -1548,7 +1595,7 @@ public class StyledConverter implements OsmConverter {
 		housenumberGenerator.addRoad(way, road);
 
 		if(trailingWay != null)
-			addRoadWithoutLoops(trailingWay, gt);
+			addRoadWithoutLoops(new ConvertedWay(cw, trailingWay));
 	}
 
 	/**
@@ -1556,7 +1603,7 @@ public class StyledConverter implements OsmConverter {
 	 * @param way the way to check 
 	 * @return true if fixme flag was found
 	 */
-	private boolean checkFixmeCoords(Way way) {
+	private static boolean checkFixmeCoords(Way way) {
 		if (way.getPoints().get(0).isFixme())
 			return true;
 		if (way.getPoints().get(way.getPoints().size()-1).isFixme())
@@ -1571,7 +1618,10 @@ public class StyledConverter implements OsmConverter {
 	 * @param index the split position. 
 	 * @return the trailing part of the way
 	 */
-	private Way splitWayAt(Way way, int index) {
+	private static Way splitWayAt(Way way, int index) {
+		if (way.isViaWay()){
+			log.warn("via way of restriction is split, restriction will be ignored",way);
+		}
 		Way trailingWay = new Way(way.getId());
 		List<Coord> wayPoints = way.getPoints();
 		int numPointsInWay = wayPoints.size();
@@ -1589,37 +1639,10 @@ public class StyledConverter implements OsmConverter {
 		// it's probably more efficient to remove from the end first
 		for(int i = numPointsInWay - 1; i > index; --i)
 			wayPoints.remove(i);
-
+		
 		return trailingWay;
 	}
 
-	protected boolean accessExplicitlyAllowed(String val) {
-		if (val == null)
-			return false;
-
-		return (val.equalsIgnoreCase("yes") ||
-			val.equalsIgnoreCase("designated") ||
-			val.equalsIgnoreCase("permissive") ||
-			val.equalsIgnoreCase("official"));
-	}
-
-	private boolean isFootOnlyAccess(Way way){
-
-		// foot must be allowed
-		if (way.isNotBoolTag("mkgmap:foot")) {
-			return false;
-		}
-		// check if bike, truck, car, bus, taxi and emergency are not allowed
-		// not sure about delivery - but check if also
-		// carpool and throughroute can be ignored (I think so...)
-		for (String accessTag : Arrays.asList("mkgmap:bicycle","mkgmap:truck","mkgmap:car","mkgmap:bus","mkgmap:taxi","mkgmap:emergency","mkgmap:delivery")) 
-		{
-			if (way.isNotBoolTag(accessTag) == false) {
-				return false;
-			}
-		}
-		return true;
-	}
 
 	/**
 	 * Increment the highway counter for each coord of each road.
@@ -1628,11 +1651,11 @@ public class StyledConverter implements OsmConverter {
 	private void setHighwayCounts(){
 		log.info("Maintaining highway counters");
 		long lastId = 0;
-		List<Way> dupIdHighways = new ArrayList<Way>();
-		for (Way way :roads){
-			if (way == null)
+		List<Way> dupIdHighways = new ArrayList<>();
+		for (ConvertedWay cw :roads){
+			if (!cw.isValid())
 				continue;
-			
+			Way way = cw.getWay();
 			if (way.getId() == lastId) {
 				log.debug("Road with identical id:", way.getId());
 				dupIdHighways.add(way);
@@ -1645,7 +1668,8 @@ public class StyledConverter implements OsmConverter {
 			}
 		}
 		
-		// go through all duplicated highways and increase the highway counter of all crossroads 
+		// go through all duplicated highways and increase the highway counter of all crossroads
+		
 		for (Way way : dupIdHighways) {
 			List<Coord> points = way.getPoints();
 			// increase the highway counter of the first and last point
@@ -1661,6 +1685,7 @@ public class StyledConverter implements OsmConverter {
 				}
 			}
 		}
+		
 	}
 	
 	/**
@@ -1670,10 +1695,10 @@ public class StyledConverter implements OsmConverter {
 	private void resetHighwayCounts(){
 		log.info("Resetting highway counters");
 		long lastId = 0;
-		for (Way way :roads){
-			if (way == null)
+		for (ConvertedWay cw :roads){
+			if (!cw.isValid())
 				continue;
-			
+			Way way = cw.getWay();
 			if (way.getId() == lastId) {
 				continue;
 			}
@@ -1690,14 +1715,15 @@ public class StyledConverter implements OsmConverter {
 	 * If such a road has the mkgmap:set_unconnected_type tag, add it as line, not as a road. 
 	 */
 	private void findUnconnectedRoads(){
-		Map<Coord, HashSet<Way>> connectors = new IdentityHashMap<Coord, HashSet<Way>>(roads.size()*2);
+		Map<Coord, HashSet<Way>> connectors = new IdentityHashMap<>(roads.size()*2);
 		
 		// for dead-end-check only: will contain ways with loops (also simply closed ways)
-		HashSet<Way> selfConnectors = new HashSet<Way>();
+		HashSet<Way> selfConnectors = new HashSet<>();
 		
 		// collect nodes that might connect roads
 		long lastId = 0;
-		for (Way way :roads){
+		for (ConvertedWay cw :roads){
+			Way way = cw.getWay();
 			if (way.getId() == lastId)
 				continue;
 			lastId = way.getId();
@@ -1705,7 +1731,7 @@ public class StyledConverter implements OsmConverter {
 				if (p.getHighwayCount() > 1){
 					HashSet<Way> ways = connectors.get(p);
 					if (ways == null){
-						ways = new HashSet<Way>(4);
+						ways = new HashSet<>();
 						connectors.put(p, ways);
 					}
 					boolean wasNew = ways.add(way);
@@ -1716,11 +1742,16 @@ public class StyledConverter implements OsmConverter {
 		}
 		
 		// find roads that are not connected
-		for (int i = 0; i < roads.size(); i++){
-			Way way = roads.get(i);
+		// count downwards because we are removing elements
+		Iterator<ConvertedWay> iter = roads.iterator();
+		while(iter.hasNext()){
+			ConvertedWay cw = iter.next();
+			if (!cw.isValid())
+				continue;
+			Way way = cw.getWay();
 			if(reportDeadEnds > 0){
 				// report dead ends of oneway roads 
-				if (way.isBoolTag("oneway") && !way.isNotBoolTag("mkgmap:dead-end-check")) {
+				if (cw.isOneway() && !way.tagIsLikeNo("mkgmap:dead-end-check")) {
 					List<Coord> points = way.getPoints();
 					int[] pointsToCheck = {0, points.size()-1};
 					if (points.get(pointsToCheck[0]) == points.get(pointsToCheck[1]))
@@ -1772,7 +1803,7 @@ public class StyledConverter implements OsmConverter {
 								List<Coord> otherPoints = connectedWay.getPoints();
 								Coord otherFirst = otherPoints.get(0);
 								Coord otherLast = otherPoints.get(otherPoints.size()-1);
-								if (otherFirst == otherLast || connectedWay.isBoolTag("oneway") == false)
+								if (otherFirst == otherLast || connectedWay.tagIsLikeYes(onewayTagKey) == false)
 									isDeadEnd = false;  
 								else {
 									Coord pOther;
@@ -1794,8 +1825,8 @@ public class StyledConverter implements OsmConverter {
 					}
 				}
 			}
-  			String check_type = way.getTag("mkgmap:set_unconnected_type");
-			if (check_type != null){
+  			String replType = way.getTag("mkgmap:set_unconnected_type");
+			if (replType != null){
 				boolean isConnected = false;
 				boolean onBoundary = false;
 				for (Coord p:way.getPoints()){
@@ -1813,29 +1844,27 @@ public class StyledConverter implements OsmConverter {
 					if (onBoundary){
 						log.info("road not connected to other roads but is on boundary:", way.toBrowseURL());
 					} else {
-						if ("none".equals(check_type))
+						if ("none".equals(replType))
 							log.info("road not connected to other roads, is ignored:", way.toBrowseURL());
 						else {
-							int type = -1;
+							int typeNoConnection = -1;
 							try{
-								type = Integer.decode(check_type);
-								if (GType.isRoutableLineType(type)){
-									type = -1;
-									log.error("type value in mkgmap:set_unconnected_type should not be a routable type:" + check_type);
+								typeNoConnection = Integer.decode(replType);
+								if (GType.isRoutableLineType(typeNoConnection)){
+									typeNoConnection = -1;
+									log.error("type value in mkgmap:set_unconnected_type should not be a routable type:" + replType);
 								}
 							} catch (NumberFormatException e){
-								log.warn("invalid type value in mkgmap:set_unconnected_type:", check_type);
+								log.warn("invalid type value in mkgmap:set_unconnected_type:", replType);
 							}
-							if (type != -1 ){
-								log.info("road not connected to other roads, added as line with type", check_type + ":", way.toBrowseURL());
-								GType gt = new GType(roadTypes.get(i), check_type); 
-								addLine(way, gt);
+							if (typeNoConnection != -1 ){
+								log.info("road not connected to other roads, added as line with type", replType + ":", way.toBrowseURL());
+								addLine(way, cw.getGType(), typeNoConnection);
 							} else {
 								log.warn("road not connected to other roads, but replacement type is invalid. Dropped:", way.toBrowseURL());
 							}
 						}
-						roads.set(i, null);
-						roadTypes.set(i, null);
+						iter.remove();
 					}
 				}
 			}
@@ -1850,27 +1879,45 @@ public class StyledConverter implements OsmConverter {
 		if (!linkPOIsToWays)
 			return;
 		log.info("translating CoordPOI");
-		for (Way way : roads) {
-			if (way == null)
+		for (ConvertedWay cw: roads) {
+			if (!cw.isValid())
 				continue;
+			Way way = cw.getWay();
 			if ("true".equals(way.getTag("mkgmap:way-has-pois"))) {
 				String wayPOI = "";
-				boolean isFootWay = isFootOnlyAccess(way); 
-				if (!isFootWay){
-					// check if the way is for pedestrians only 
-					List<Coord> points = way.getPoints();
-					int numPoints = points.size();
-					for (int i = 0;i < numPoints; i++) {
-						Coord p = points.get(i);
-						if (p instanceof CoordPOI){
-							CoordPOI cp = (CoordPOI) p;
-							Node node = cp.getNode();
-							if(hasAccessRestriction(node) || 
-									node.getTag("mkgmap:road-class") != null ||
-									node.getTag("mkgmap:road-speed") != null){
-								wayPOI += "["+ node.getId()+"]"; 
-								cp.setUsed(true);
-							}
+
+				List<Coord> points = way.getPoints();
+				int numPoints = points.size();
+				for (int i = 0;i < numPoints; i++) {
+					Coord p = points.get(i);
+					if (p instanceof CoordPOI){
+						CoordPOI cp = (CoordPOI) p;
+						Node node = cp.getNode();
+						boolean usedInThisWay = false;
+						byte wayAccess = cw.getAccess();
+						if (node.getTag("mkgmap:road-class") != null
+								|| node.getTag("mkgmap:road-speed") != null ) {
+							if (wayAccess != AccessTagsAndBits.FOOT)
+								usedInThisWay = true;
+						}
+						byte nodeAccess = AccessTagsAndBits.evalAccessTags(node);
+						if(nodeAccess != (byte)0xff){
+							// barriers etc. 
+							if ((wayAccess & nodeAccess) != wayAccess){
+								// node is more restrictive
+								if (p.getHighwayCount() >= 2 || (i != 0 && i != numPoints-1)){
+									usedInThisWay = true;
+									cp.setConvertToViaInRouteRestriction(true);
+								}
+								else {
+									log.info("POI node", node.getId(), "with access restriction is ignored, it is not connected to other routable ways");
+								}
+							} else 
+								log.info("Access restriction in POI node", node.toBrowseURL(), "was ignored for way", way.toBrowseURL());
+						}
+						if (usedInThisWay){
+							cp.setUsed(true);
+							wayPOI += "["+ node.getId()+"]";
 						}
 					}
 				} 
@@ -1885,273 +1932,5 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
-	/**
-	 * Routing nodes must not be too close together as this 
-	 * causes routing errors. We try to merge these nodes here.
-	 */
-	private void removeShortArcsByMergingNodes() {
-		log.info("Removing short arcs (min arc length =", minimumArcLength + "m)");
-		log.info("Removing short arcs - marking points as node-alike and removing obsolete points");
-		for (Way way : roads) {
-			if (way == null)
-				continue;
-			List<Coord> points = way.getPoints();
-			int numPoints = points.size();
-			if (numPoints >= 2) {
-				// all end points should be treated as nodes
-				points.get(0).setTreatAsNode(true);
-				points.get(numPoints - 1).setTreatAsNode(true);
-				// non-end points have 2 arcs but ignore points that
-				// are only in a single way
-				Coord prev = points.get(numPoints - 1);
-				for (int i = numPoints - 2; i >= 0; --i) {
-					Coord p = points.get(i);
-					// if this point is a CoordPOI it may become a
-					// node later even if it isn't actually a connection
-					// between roads at this time - so for the purposes
-					// of short arc removal, consider it to be a node
-					// if it is on a boundary it will become a node later
-					if (p.getHighwayCount() > 1 || p instanceof CoordPOI || p.getOnBoundary())
-						p.setTreatAsNode(true);
-					// remove equal points
-					if (p.equals(prev)) {
-						int removePos = -1;
-						if (prev.isTreatAsNode() == false){
-							removePos = i+1;
-							prev = p;
-						}
-						else if (p.isTreatAsNode() == false){
-							removePos = i;
-						}
-						if (removePos >= 0){
-							points.remove(removePos);
-							if (log.isInfoEnabled())
-								log.info("Way", way.toBrowseURL(), "has consecutive equal points at node numbers",i+1, "and",i+2,"(discarding",removePos+1,")");						
-							modifiedRoads.put(way.getId(), way);
-						}
-					} else {
-						prev = p;
-					}
-				}
-			}
-		}
-
-		// replacements maps those nodes that have been replaced to
-		// the node that replaces them
-		Map<Coord, Coord> replacements = new IdentityHashMap<Coord, Coord>();
-		Map<Way, Way> complainedAbout = new HashMap<Way, Way>();
-		boolean anotherPassRequired = true;
-		int pass = 0;
-		int numWaysDeleted = 0;
-		int numNodesMerged = 0;
-
-		while (anotherPassRequired && pass < 10) {
-			anotherPassRequired = false;
-			log.info("Removing short arcs - PASS", ++pass);
-			for (int w = 0; w < roads.size(); w++){
-				Way way = roads.get(w);
-				if (way == null)
-					continue;
-				List<Coord> points = way.getPoints();
-				if (points.size() < 2) {
-					if (log.isInfoEnabled())
-						log.info("  Way", way.getTag("name"), "(" + way.toBrowseURL() + ") has less than 2 points - deleting it");
-					roads.set(w, null);
-					deletedRoads.add(way.getId());
-					++numWaysDeleted;
-					continue;
-				}
-				// scan through the way's points looking for nodes and
-				// check to see that the nodes are not too close to
-				// each other
-				int previousNodeIndex = 0; // first point will be a node
-				Coord previousPoint = points.get(0);
-				double arcLength = 0;
-
-				for (int i = 0; i < points.size(); ++i) {
-					Coord p = points.get(i);
-
-					// check if this point is to be replaced because
-					// it was previously merged into another point
-					if (p.isReplaced()){
-						Coord replacement = null;
-						Coord r = p;
-						while ((r = replacements.get(r)) != null) {
-							replacement = r;
-						}
-
-						if (replacement != null) {
-							assert !p.getOnBoundary() : "Boundary node replaced";
-							if (p instanceof CoordPOI){
-								CoordPOI cp = (CoordPOI) p;
-								Node node = cp.getNode(); 
-								if (cp.isUsed() && way != null && way.getId() != 0) {
-									String wayPOI = way.getTag(WAY_POI_NODE_IDS);
-									if (wayPOI != null && wayPOI.contains("["+node.getId()+"]")){
-										if (replacement instanceof CoordPOI){
-											Node rNode = ((CoordPOI) replacement).getNode();
-											if (wayPOI.contains("["+ rNode.getId() + "]"))
-												log.warn("CoordPOI", node.getId(), "replaced by CoordPOI",rNode.getId(), "in way",  way.toBrowseURL());
-											else
-												log.warn("CoordPOI", node.getId(), "replaced by ignored CoordPOI",rNode.getId(), "in way",  way.toBrowseURL());
-										}
-										else 
-											log.warn("CoordPOI", node.getId(),"replaced by simple coord in way", way.toBrowseURL());
-									}
-								}
-							}
-							p = replacement;
-							p.incHighwayCount();
-							// replace point in way
-							points.set(i, p);
-							if (i == 0)
-								previousPoint = p;
-							modifiedRoads.put(way.getId(), way);
-						}
-					}
-					if (i == 0) {
-						// nothing more to do with this point
-						continue;
-					}
-
-					// this is not the first point in the way
-					if (p == previousPoint) {
-						if (log.isInfoEnabled())
-							log.info("  Way", way.getTag("name"), "(" + way.toBrowseURL() + ") has consecutive identical points at", p.toOSMURL(), "- deleting the second point");
-						points.remove(i);
-						// hack alert! rewind the loop index
-						--i;
-						modifiedRoads.put(way.getId(), way);
-						anotherPassRequired = true;
-						continue;
-					}
-
-					if (minimumArcLength > 0){
-						// we have to calculate the length of the arc
-						arcLength += p.distance(previousPoint);
-					}
-					else {
-						// if the points are not equal, the arc length is > 0
-						if (!p.equals(previousPoint)){
-							arcLength = 1; // just a value > 0	
-						}
-					}
-					previousPoint = p;
-
-					// do we treat this point as a node ?
-					if (!p.isTreatAsNode()) {
-						// it's not a node so go on to next point
-						continue;
-					}
-					Coord previousNode = points.get(previousNodeIndex);
-					if (p == previousNode) {
-						// this node is the same point object as the
-						// previous node - leave it for now and it
-						// will be handled later by the road loop
-						// splitter
-						previousNodeIndex = i;
-						arcLength = 0;
-						continue;
-					}
-
-					boolean mergeNodes = false;
-
-					if (p.equals(previousNode)) {
-						// nodes have identical coordinates and are
-						// candidates for being merged
-
-						// however, to avoid trashing unclosed loops
-						// (e.g. contours) we only want to merge the
-						// nodes when the length of the arc between
-						// the nodes is small
-
-						if(arcLength == 0 || arcLength < minimumArcLength)
-							mergeNodes = true;
-						else if(complainedAbout.get(way) == null) {
-							if (log.isInfoEnabled())
-								log.info("  Way", way.getTag("name"), "(" + way.toBrowseURL() + ") has unmerged co-located nodes at", p.toOSMURL(), "- they are joined by a", (int)(arcLength * 10) / 10.0 + "m arc");
-							complainedAbout.put(way, way);
-						}
-					}
-					else if(minimumArcLength > 0 && minimumArcLength > arcLength) {
-						// nodes have different coordinates but the
-						// arc length is less than minArcLength so
-						// they will be merged
-						mergeNodes = true;
-					}
-
-					if (!mergeNodes) {
-						// keep this node and go look at the next point
-						previousNodeIndex = i;
-						arcLength = 0;
-						continue;
-					}
-
-					if (previousNode.getOnBoundary() && p.getOnBoundary()) {
-						if (p.equals(previousNode)) {
-							// the previous node has identical
-							// coordinates to the current node so it
-							// can be replaced but to avoid the
-							// assertion above we need to forget that
-							// it is on the boundary
-							previousNode.setOnBoundary(false);
-						} else {
-							// both the previous node and this node
-							// are on the boundary and they don't have
-							// identical coordinates
-							if(complainedAbout.get(way) == null) {
-								if (log.isLoggable(Level.WARNING))
-									log.warn("  Way", way.getTag("name"), "(" + way.toBrowseURL() + ") has short arc (" + String.format("%.2f", arcLength) + "m) at", p.toOSMURL(), "- but it can't be removed because both ends of the arc are boundary nodes!");
-								complainedAbout.put(way, way);
-							}
-							break; // give up with this way
-						}
-					}
-
-					// reset arc length
-					arcLength = 0;
-
-					// do the merge
-					++numNodesMerged;
-					if (p.getOnBoundary()) {
-						// current point is a boundary node so we need
-						// to merge the previous node into this node
-						replacements.put(previousNode, p);
-						previousNode.setReplaced(true);
-						p.setTreatAsNode(true);
-						// remove the preceding point(s) back to and
-						// including the previous node
-						for(int j = i - 1; j >= previousNodeIndex; --j) {
-							points.remove(j);
-						}
-					} else {
-						// current point is not on a boundary so merge
-						// this node into the previous one
-						replacements.put(p, previousNode);
-						p.setReplaced(true);
-						previousNode.setTreatAsNode(true);
-						// reset previous point to be the previous
-						// node
-						previousPoint = previousNode;
-						// remove the point(s) back to the previous
-						// node
-						for (int j = i; j > previousNodeIndex; --j) {
-							points.remove(j);
-						}
-					}
-
-					// hack alert! rewind the loop index
-					i = previousNodeIndex;
-					modifiedRoads.put(way.getId(), way);
-					anotherPassRequired = true;
-				}
-			}
-		}
-		if (anotherPassRequired)
-			log.error("Removing short arcs - didn't finish in " + pass + " passes, giving up!");
-		else
-			log.info("Removing short arcs - finished in", pass, "passes (", numNodesMerged, "nodes merged,", numWaysDeleted, "ways deleted)");
-	}
-	
 }
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
index 72c96b0..544b151 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
@@ -93,6 +93,12 @@ public class TypeReader {
 		}
 
 		gt.fixLevels(levels);
+		if ("lines".equals(ts.getFileName())){
+			if(gt.getRoadClass() < 0 || gt.getRoadClass() > 4)
+				log.error("road class value", gt.getRoadClass(), "not in the range 0-4 in style file lines, line " + ts.getLinenumber());
+			if(gt.getRoadSpeed() < 0 || gt.getRoadSpeed() > 7)
+				log.error("road speed value ", gt.getRoadSpeed(), "not in the range 0-7 in style file lines, line " + ts.getLinenumber());
+		}
 		if (performChecks){
 			boolean fromOverlays = false;
 			List<Integer> usedTypes = null;
@@ -106,6 +112,7 @@ public class TypeReader {
 			}
 			if (usedTypes == null)
 				usedTypes = Arrays.asList(gt.getType());
+			boolean foundRoutableType = false;
 			for (int i = 0; i < usedTypes.size(); i++){
 				int usedType = usedTypes.get(i);
 				String typeOverlaidMsg = ". Type is overlaid with " + GType.formatType(usedType);
@@ -115,27 +122,39 @@ public class TypeReader {
 						msg += typeOverlaidMsg;
 					System.out.println(msg);
 				}
-				if (kind == FeatureKind.POLYLINE && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0 && GType.isRoutableLineType(usedType)){
-					if (gt.isRoad() == false){
-						String msg = "Warning: routable type " + type  + " is used for non-routable line with level 0. This may break routing. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber();
-						if (fromOverlays)
-							msg += typeOverlaidMsg;
-						System.out.println(msg);
-					}
-					else if (i > 0){
-						System.out.println("Warning: routable type " + type + " is used for non-routable line with level 0. " +
-								"This may break routing. Style file " + ts.getFileName() + ", line " + ts.getLinenumber() + 
-								typeOverlaidMsg +
-								" which is used for adding the non-routable copy of the way.");
+				if (kind == FeatureKind.POLYLINE && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){ 
+					if (GType.isSpecialRoutableLineType(usedType)){
+						if (gt.isRoad() == false){
+							String msg = "Warning: routable type " + type  + " is used for non-routable line with level 0. This may break routing. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber();
+							if (fromOverlays)
+								msg += typeOverlaidMsg;
+							System.out.println(msg);
+						}
+						else if (i > 0){
+							System.out.println("Warning: routable type " + type + " is used for non-routable line with level 0. " +
+									"This may break routing. Style file " + ts.getFileName() + ", line " + ts.getLinenumber() + 
+									typeOverlaidMsg +
+									" which is used for adding the non-routable copy of the way.");
+						}
 					}
 				}
+				if (kind == FeatureKind.POLYLINE && GType.isRoutableLineType(usedType)){
+						foundRoutableType = true;
+				}
 			}
+			if (gt.isRoad() && foundRoutableType == false && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){
+				String msg = "Warning: non-routable type " + type  + " is used in combination with road_class/road_speed. Line will not be routable. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber();
+				if (fromOverlays)
+					msg += ". Type is overlaid, but not with a routable type";
+				System.out.println(msg);
+			}
+
 		}
 		
 		return gt;
 	}
 
-	private int nextIntValue(TokenScanner ts) {
+	private static int nextIntValue(TokenScanner ts) {
 		if (ts.checkToken("="))
 			ts.nextToken();
 		try {
@@ -148,7 +167,7 @@ public class TypeReader {
 	/**
 	 * Get the value in a 'name=value' pair.
 	 */
-	private String nextValue(TokenScanner ts) {
+	private static String nextValue(TokenScanner ts) {
 		if (ts.checkToken("="))
 			ts.nextToken();
 		return ts.nextWord();
@@ -158,7 +177,7 @@ public class TypeReader {
 	 * A resolution can be just a single number, in which case that is the
 	 * min resolution and the max defaults to 24.  Or a min to max range.
 	 */
-	private void setResolution(TokenScanner ts, GType gt) {
+	private static void setResolution(TokenScanner ts, GType gt) {
 		String str = ts.nextWord();
 		log.debug("res word value", str);
 		try {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
new file mode 100644
index 0000000..a4ce874
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
@@ -0,0 +1,1403 @@
+/*
+ * Copyright (C) 2013-2014.
+ *
+ * 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.osmstyle;
+
+//import java.io.File;
+//import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.reader.osm.CoordPOI;
+import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.RestrictionRelation;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+//import uk.me.parabola.splitter.O5mMapWriter;
+import uk.me.parabola.util.GpxCreator;
+
+/**
+ * We are rounding coordinates with double precision to map units with a
+ * precision of < 2m. This hasn't a big visible effect for single points,
+ * but wherever points are connected with lines the lines may show
+ * heavy zig-zagging while the original lines were almost straight.
+ * This happens when one of the points was rounded to one direction
+ * and the next point was rounded to the opposite direction.
+ * The effect is esp. visible with parallel roads, rails, and buildings,
+ * but also in small roundabouts.
+ * The methods in this class try to fix these wrong bearings by
+ * moving or removing points.
+ *         
+ * @author GerdP
+ *
+ */
+public class WrongAngleFixer {
+	private static final Logger log = Logger.getLogger(WrongAngleFixer.class);
+
+	static private final double MAX_BEARING_ERROR = 15;
+	static private final double MAX_BEARING_ERROR_HALF = MAX_BEARING_ERROR / 2;
+	static private final double MAX_DIFF_ANGLE_STRAIGHT_LINE = 3;
+	
+	private final Area bbox;
+	private final String gpxPath = null;
+	static final int MODE_ROADS = 0;
+	static final int MODE_LINES = 1;
+	private int mode = MODE_ROADS;
+	
+	public WrongAngleFixer(Area bbox) {
+		this.bbox = bbox;
+	}
+
+	/**
+	 * Find wrong angles caused by rounding to map units. Try to fix them by
+	 * moving, removing or merging points. 
+	 * When done, remove obsolete points.  
+	 * @param roads list of roads, elements might be set to null by this method
+	 * @param lines list of non-routable ways  
+	 * @param modifiedRoads Will be enlarged by all roads modified in this method 
+	 * @param deletedRoads Will be enlarged by all roads in roads that were set to null by this method 
+	 * @param restrictions Map with restriction relations 
+	 */
+	public void optimizeWays(List<ConvertedWay> roads, List<ConvertedWay> lines, HashMap<Long, ConvertedWay> modifiedRoads, HashSet<Long> deletedRoads, List<RestrictionRelation> restrictions ) {
+		printBadAngles("bad_angles_start", roads);
+		writeOSM("roads_orig", roads);
+		writeOSM("lines_orig", lines);
+		removeWrongAngles(roads, lines, modifiedRoads, deletedRoads, restrictions);
+		writeOSM("roads_post_rem_wrong_angles", roads);
+		removeObsoletePoints(roads, modifiedRoads);
+		writeOSM("roads_post_rem_obsolete_points", roads);
+		printBadAngles("bad_angles_finish", roads);
+		this.mode = MODE_LINES;
+		writeOSM("lines_after_roads", lines);
+		removeWrongAngles(null, lines, modifiedRoads, null, restrictions);
+		writeOSM("lines_post_rem_wrong_angles", lines);
+		removeObsoletePoints(lines, modifiedRoads);
+		writeOSM("lines_final", lines);
+	}	
+	
+	private void replaceCoord(Coord toRepl, Coord replacement, Map<Coord, Coord> replacements) {
+		assert toRepl != replacement;
+		if (toRepl.getOnBoundary()){
+			if (replacement.equals(toRepl) == false){
+				log.error("boundary node is replaced by node with non-equal coordinates at", toRepl.toOSMURL());
+				assert false : "boundary node is replaced" ;
+			}
+			replacement.setOnBoundary(true);
+		}
+		toRepl.setReplaced(true);
+		if (toRepl instanceof CoordPOI) {
+			CoordPOI cp = (CoordPOI) toRepl;
+			if (cp.isUsed()){
+				replacement = new CoordPOI(replacement);
+				((CoordPOI) replacement).setNode(cp.getNode());
+				((CoordPOI) replacement).setUsed(true);
+				((CoordPOI) replacement).setConvertToViaInRouteRestriction(cp.getConvertToViaInRouteRestriction());
+				if (replacement.highPrecEquals(cp.getNode().getLocation()) == false){
+					log.error("CoordPOI node is replaced with non-equal coordinates at", toRepl.toOSMURL());
+				}
+			}
+		}
+		if (toRepl.isViaNodeOfRestriction())
+			replacement.setViaNodeOfRestriction(true);
+		replacements.put(toRepl, replacement);
+		while (toRepl.getHighwayCount() > replacement.getHighwayCount())
+			replacement.incHighwayCount();
+		if (mode == MODE_LINES && toRepl.isEndOfWay() ){
+			replacement.setEndOfWay(true);
+		}
+	}
+	
+	/**
+	 * Common code to handle replacements of points in ways. Checks for special 
+	 * cases regarding CoordPOI.
+	 * 
+	 * @param p point to replace
+	 * @param way way that contains p
+	 * @param replacements the Map containing the replaced points
+	 * @return the replacement
+	 */
+	private static Coord getReplacement(Coord p, Way way,
+			Map<Coord, Coord> replacements) {
+		// check if this point is to be replaced because
+		// it was previously merged into another point
+		if (p.isReplaced()) {
+			Coord replacement = null;
+			Coord r = p;
+			while ((r = replacements.get(r)) != null) {
+				replacement = r;
+			}
+
+			if (replacement != null) {
+				if (p instanceof CoordPOI) {
+					CoordPOI cp = (CoordPOI) p;
+					Node node = cp.getNode();
+					if (cp.isUsed() && way != null && way.getId() != 0) {
+						String wayPOI = way.getTag(StyledConverter.WAY_POI_NODE_IDS);
+						if (wayPOI != null && wayPOI.contains("["+node.getId()+"]")){
+							if (replacement instanceof CoordPOI) {
+								Node rNode = ((CoordPOI) replacement).getNode();
+								if (rNode.getId() != node.getId()) {
+									if (wayPOI.contains("["+ rNode.getId() + "]")){
+										log.warn("CoordPOI", node.getId(),
+												"replaced by CoordPOI",
+												rNode.getId(), "in way",
+												way.toBrowseURL());
+									}
+									else
+										log.warn("CoordPOI", node.getId(),
+												"replaced by ignored CoordPOI",
+												rNode.getId(), "in way",
+												way.toBrowseURL());
+								}
+							} else
+								log.warn("CoordPOI", node.getId(),
+										"replaced by simple coord in way",
+										way.toBrowseURL());
+						}
+					}
+				}
+				return replacement;
+			} 
+			log.error("replacement not found for point " + p.toOSMURL());
+			
+		}
+		return p;
+	}
+
+	/**
+	 * Find wrong angles caused by rounding to map units. Try to fix them by
+	 * moving, removing or merging points.
+	 * @param roads list with routable ways or null, if lines should be optimized
+	 * @param lines list with non-routable ways
+	 * @param modifiedRoads map of modified routable ways (modified by this routine)
+	 * @param deletedRoads set of ids of deleted routable ways (modified by this routine)
+	 * @param restrictions Map with restriction relations. The restriction relations may be modified by this routine 
+	 */
+	private void removeWrongAngles(List<ConvertedWay> roads, List<ConvertedWay> lines, HashMap<Long, ConvertedWay> modifiedRoads, HashSet<Long> deletedRoads, List<RestrictionRelation> restrictions) {
+		// replacements maps those nodes that have been replaced to
+		// the node that replaces them
+		Map<Coord, Coord> replacements = new IdentityHashMap<>();
+
+		final HashSet<Coord> changedPlaces = new HashSet<>();
+		int numNodesMerged = 0; 
+		HashSet<Way> waysWithBearingErrors = new HashSet<>();
+		HashSet<Long> waysThatMapToOnePoint = new HashSet<>();
+		int pass = 0;
+		Way lastWay = null;
+		List<ConvertedWay> convertedWays = (roads != null) ? roads: lines;
+		
+		boolean anotherPassRequired = true;
+		while (anotherPassRequired && pass < 20) {
+			anotherPassRequired = false;
+			log.info("Removing wrong angles - PASS " + ++pass);
+			writeOSM(((mode==MODE_LINES) ? "lines_pass_" + pass:"roads_pass_" + pass), convertedWays);	
+			// Step 1: detect points which are parts of line segments with wrong bearings
+			lastWay = null;
+			for (ConvertedWay cw : convertedWays) {
+				if (!cw.isValid() || cw.isOverlay()) 
+					continue;
+				Way way = cw.getWay();
+				if (way.equals(lastWay)) 
+					continue;
+				if (pass != 1 && waysWithBearingErrors.contains(way) == false)
+					continue;
+				lastWay = way;
+				List<Coord> points = way.getPoints();
+				
+				// scan through the way's points looking for line segments with big 
+				// bearing errors 
+				Coord prev = null;
+				if (points.get(0) == points.get(points.size()-1) && points.size() >= 2)
+					prev = points.get(points.size()-2);
+				boolean hasNonEqualPoints = false;
+				for (int i = 0; i < points.size(); ++i) {
+					Coord p = points.get(i);
+					if (pass == 1)
+						p.setRemove(false);
+					p = getReplacement(p, way, replacements);
+					if (i == 0 || i == points.size()-1){
+						p.setEndOfWay(true);
+					}
+					
+					if (prev != null) {
+						if (pass == 1 && p.equals(prev) == false)
+							hasNonEqualPoints = true;
+						double err = calcBearingError(p,prev);
+						if (err >= MAX_BEARING_ERROR){
+							// bearing error is big
+							p.setPartOfBadAngle(true);
+							prev.setPartOfBadAngle(true);
+						}
+					}
+					prev = p;
+				}
+				if (pass == 1 && hasNonEqualPoints == false){
+					waysThatMapToOnePoint.add(way.getId());
+					log.info("all points of way",way.toBrowseURL(),"are rounded to equal map units" );
+				}
+			}
+			// Step 2: collect the line segments that are connected to critical points
+			IdentityHashMap<Coord, CenterOfAngle> centerMap = new IdentityHashMap<>();
+			List<CenterOfAngle> centers = new ArrayList<>(); // needed for ordered processing
+			int centerId = 0;
+				
+			lastWay = null;
+			for (ConvertedWay cw : convertedWays) {
+				if (!cw.isValid() || cw.isOverlay()) 
+					continue;
+				Way way = cw.getWay();
+				if (way.equals(lastWay)) 
+					continue;
+				if (pass != 1 && waysWithBearingErrors.contains(way) == false)
+					continue;
+				lastWay = way;
+
+				boolean wayHasSpecialPoints = false;
+				List<Coord> points = way.getPoints();
+				// scan through the way's points looking for line segments with big 
+				// bearing errors
+				Coord prev = null;
+				if (points.get(0) == points.get(points.size()-1) && points.size() >= 2)
+					prev = points.get(points.size()-2);
+				for (int i = 0; i < points.size(); ++i) {
+					Coord p = points.get(i);
+					if (prev != null) {
+						if (p == prev){
+							points.remove(i);
+							--i;
+							if (mode == MODE_ROADS)
+								modifiedRoads.put(way.getId(), cw);
+							continue;
+						}
+						if (p.isPartOfBadAngle() || prev.isPartOfBadAngle()) {
+							wayHasSpecialPoints = true;
+							// save both points with their neighbour
+							Coord p1 = prev;
+							Coord p2 = p;
+							CenterOfAngle coa1 = centerMap.get(p);
+							if (coa1 == null) {
+								coa1 = new CenterOfAngle(p, centerId++);
+								centerMap.put(p, coa1);
+								centers.add(coa1);
+							}
+							CenterOfAngle coa2 = centerMap.get(prev);
+							if (coa2 == null) {
+								coa2 = new CenterOfAngle(prev, centerId++);
+								centerMap.put(prev, coa2);
+								centers.add(coa2);
+							}
+							coa1.addNeighbour(coa2);
+							coa2.addNeighbour(coa1);
+							if (points.size() == 2) {
+								// way has only two points, don't merge them
+								coa1.addBadMergeCandidate(coa2);
+							}
+							if (mode == MODE_ROADS){
+								if (p1.getHighwayCount() >= 2 && p2.getHighwayCount() >= 2){
+									if (cw.isRoundabout()) {
+										// avoid to merge exits of roundabouts
+										coa1.addBadMergeCandidate(coa2);
+									}
+								}
+							}
+						}
+					}
+					prev = p;
+				}
+				if (pass == 1 && wayHasSpecialPoints)
+					waysWithBearingErrors.add(way);
+			}
+			// Step 3: Update list of ways with bearing errors or points next to them 
+			lastWay = null;
+			for (ConvertedWay cw : convertedWays) {
+				if (!cw.isValid() || cw.isOverlay())
+					continue;
+				Way way = cw.getWay();
+				if (way.equals(lastWay)) 
+					continue;
+				lastWay = way;
+				if (waysWithBearingErrors.contains(way))
+					continue;
+				List<Coord> points = way.getPoints();
+				// scan through the way's points looking for line segments with big 
+				// bearing errors 
+				for (Coord p: points) {
+					if (p.getHighwayCount() < 2)
+						continue;
+					if (centerMap.containsKey(p)){
+						waysWithBearingErrors.add(way);
+						break;
+					}
+				}
+			}
+			log.info("pass " + pass + ": analysing " + centers.size() + " points with bearing problems.");
+			centerMap = null; // Return to GC 
+			// Step 4: try to correct the errors
+			List<CenterOfAngle> checkAgainList = null;
+			boolean tryMerge = false;
+			while (true){
+				checkAgainList = new ArrayList<>();
+				for (CenterOfAngle coa : centers) {
+					coa.center.setPartOfBadAngle(false); // reset flag for next pass
+					if (coa.getCurrentLocation(replacements) == null)
+						continue; // removed center
+					if (coa.isOK(replacements) == false) {
+						boolean changed = coa.tryChange(replacements, tryMerge);
+						if (changed){
+							if (gpxPath != null)
+								changedPlaces.add(coa.center);
+							continue;
+						}
+						checkAgainList.add(coa);
+					}
+				}
+				if (tryMerge)
+					break; // leave when 2nd pass finished
+				tryMerge = true;
+				centers = checkAgainList;
+			}
+			
+			// Step 5: apply the calculated corrections to the ways
+			lastWay = null;
+			boolean lastWayModified = false;
+			ConvertedWay lastConvertedWay = null;
+			for (ConvertedWay cw : convertedWays) {
+				if (!cw.isValid() || cw.isOverlay())
+					continue;
+				Way way = cw.getWay();
+				if (waysWithBearingErrors.contains(way) == false)
+					continue;
+				List<Coord> points = way.getPoints();
+				if (way.equals(lastWay)) {
+					if (lastWayModified){
+						points.clear();
+						points.addAll(lastWay.getPoints());
+						if (cw.isReversed() != lastConvertedWay.isReversed())
+							Collections.reverse(points);
+					}
+					continue;
+				}
+				lastWay = way;
+				lastConvertedWay = cw;
+				lastWayModified = false;
+				// loop backwards because we may delete points
+				for (int i = points.size() - 1; i >= 0; i--) {
+					Coord p = points.get(i);
+					if (p.isToRemove()) {
+						points.remove(i);
+						anotherPassRequired = true;
+						lastWayModified = true;
+						if (i > 0 && i < points.size()) {
+							// special case: handle micro loop
+							if (points.get(i - 1) == points.get(i))
+								points.remove(i);
+							}
+						continue;
+					}
+					// check if this point is to be replaced because
+					// it was previously moved
+					Coord replacement = getReplacement(p, way, replacements);
+					if (p == replacement) 
+						continue;
+					
+					if (p.isViaNodeOfRestriction()){
+						// make sure that we find the restriction with the new coord instance
+						replacement.setViaNodeOfRestriction(true);
+						p.setViaNodeOfRestriction(false);
+					}
+					p = replacement;
+					// replace point in way
+					points.set(i, p);
+					if (p.getHighwayCount() >= 2)
+						numNodesMerged++;
+					lastWayModified = true;
+					if (i + 1 < points.size() && points.get(i + 1) == p) {
+						points.remove(i);
+						anotherPassRequired = true;
+					}
+					if (i -1 >= 0 && points.get(i-1) == p){
+						points.remove(i);
+						anotherPassRequired = true;
+					}
+				}
+				if (lastWayModified && mode == MODE_ROADS){
+					modifiedRoads.put(way.getId(), cw);
+				}
+			}
+		}
+		// finish: remove remaining duplicate points
+		int numWaysDeleted = 0;
+		lastWay = null;
+		boolean lastWayModified = false;
+		ConvertedWay lastConvertedWay = null;
+		for (ConvertedWay cw : convertedWays) {
+			if (cw.isOverlay())
+				continue;
+			Way way = cw.getWay();
+			
+			List<Coord> points = way.getPoints();
+			if (points.size() < 2) {
+				if (log.isInfoEnabled())
+					log.info("  Way " + way.getTag("name") + " (" + way.toBrowseURL() + ") has less than 2 points - deleting it");
+				if (mode == MODE_LINES && waysThatMapToOnePoint.contains(way.getId()) == false)
+					log.warn("non-routable way " ,way.getId(),"was removed");
+				
+				if (mode == MODE_ROADS)
+					deletedRoads.add(way.getId());
+				++numWaysDeleted;
+				continue;
+			}
+			if (way.equals(lastWay)) {
+				if (lastWayModified){
+					points.clear();
+					points.addAll(lastWay.getPoints());
+					if (cw.isReversed() != lastConvertedWay.isReversed())
+						Collections.reverse(points);
+					
+				}
+				continue;
+			}
+			lastWay = way;
+			lastConvertedWay = cw;
+			lastWayModified = false;
+			Coord prev = points.get(points.size() - 1);
+			// loop backwards because we may delete points
+			for (int i = points.size() - 2; i >= 0; i--) {
+				Coord p = points.get(i);
+				if (p == prev){
+					points.remove(i);
+					lastWayModified = true;
+				}
+//				if (p.equals(prev) && (p.getHighwayCount() < 2 || prev.getHighwayCount() < 2)){
+//					// not an error, but should not happen
+//					log.warn("way " + way.getId() + " still has consecutive equal points at " + p.toOSMURL()); 
+//				}
+				prev = p;
+			}
+		}
+		if (mode == MODE_ROADS){
+			// treat special case: non-routable ways may be connected to moved
+			// points in roads
+			for (ConvertedWay cw : lines) {
+				if (!cw.isValid() || cw.isOverlay())
+					continue;
+				Way way = cw.getWay();
+				List<Coord> points = way.getPoints();
+				int n = points.size();
+				boolean hasReplacedPoints = false;
+				for (int i = 0; i < n; i++) {
+					Coord p = points.get(i);
+					if (p.isReplaced()) {
+						hasReplacedPoints = true;
+						points.set(i, getReplacement(p, null, replacements));
+					}
+				}
+				if (hasReplacedPoints && gpxPath != null) {
+					GpxCreator.createGpx(gpxPath + way.getId()
+							+ "_mod_non_routable", points);
+				}
+			}
+		
+			for (RestrictionRelation rr: restrictions){
+				for (Coord p: rr.getViaCoords()){ 
+					Coord replacement = getReplacement(p, null, replacements);
+					if (p != replacement){
+						rr.replaceViaCoord(p, replacement);
+					}
+				}
+			}
+		}
+		
+		if (gpxPath != null) {
+			GpxCreator.createGpx(gpxPath + "solved_badAngles", bbox.toCoords(),
+					new ArrayList<>(changedPlaces));
+		}
+		if (anotherPassRequired)
+			log.error("Removing wrong angles - didn't finish in " + pass + " passes, giving up!");
+		else
+			log.info("Removing wrong angles - finished in", pass, "passes (", numNodesMerged, "nodes merged,", numWaysDeleted, "ways deleted)"); 		
+	}
+
+	
+	/** 
+	 * remove obsolete points in ways. Obsolete are points which are
+	 * very close to 180 degrees angles in the real line or wrong points. 
+	 * Wrong points are those that produce wrong angles, so that  
+	 * removing them reduces the error.
+	 * @param convertedWays 
+	 * @param modifiedRoads 
+	 */
+	private void removeObsoletePoints(List<ConvertedWay> convertedWays, HashMap<Long, ConvertedWay> modifiedRoads){
+		ConvertedWay lastConvertedWay = null;
+		int numPointsRemoved = 0;
+		boolean lastWasModified = false;
+		List<Coord> removedInWay = new ArrayList<>();
+		List<Coord> obsoletePoints = new ArrayList<>();
+		List<Coord> modifiedPoints = new ArrayList<>();
+		
+		for (ConvertedWay cw : convertedWays) {
+			if (!cw.isValid() || cw.isOverlay())
+				continue;
+			Way way = cw.getWay();
+			if (lastConvertedWay != null && way.equals(lastConvertedWay.getWay())) {
+				if (lastWasModified){
+					List<Coord> points = way.getPoints();
+					points.clear();
+					points.addAll(lastConvertedWay.getPoints());
+					if (cw.isReversed() != lastConvertedWay.isReversed())
+						Collections.reverse(points);
+				}
+				continue;
+			}
+			lastConvertedWay = cw;
+			lastWasModified = false;
+			List<Coord> points = way.getPoints();
+			modifiedPoints.clear();
+			double maxErrorDistance = calcMaxErrorDistance(points.get(0));
+			boolean draw = false;
+			removedInWay.clear();
+			modifiedPoints.add(points.get(0));
+
+			// scan through the way's points looking for points which are
+			// on almost straight line and therefore obsolete
+			for (int i = 1; i+1 < points.size(); i++) {
+				Coord cm = points.get(i);
+				if (allowedToRemove(cm) == false){
+					modifiedPoints.add(cm);
+					continue;
+				}
+				Coord c1 = modifiedPoints.get(modifiedPoints.size()-1);
+				Coord c2 = points.get(i+1);
+				if (c1 == c2){
+					// loop, handled by split routine
+					modifiedPoints.add(cm);
+					continue; 
+				}
+				
+				boolean keepThis = true;
+				double realAngle = Utils.getAngle(c1, cm, c2);
+				if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE){ 
+					double distance = cm.distToLineSegment(c1, c2);
+					if (distance >= maxErrorDistance){
+						modifiedPoints.add(cm);
+						continue;
+					}
+					keepThis = false;
+				} else {
+					double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
+					if (displayedAngle < 0 && realAngle > 0 || displayedAngle > 0 && realAngle < 0){
+						// straight line is closer to real angle 
+						keepThis = false;
+					} else if (Math.abs(displayedAngle) < 1){ 
+						// displayed line is nearly straight
+						if (c1.getHighwayCount() < 2 && c2.getHighwayCount() < 2){
+							// we can remove the point
+							keepThis = false;
+						}
+					} else if (Math.abs(realAngle-displayedAngle) > 2 * Math.abs(realAngle) && Math.abs(realAngle) < MAX_BEARING_ERROR_HALF){
+						// displayed angle is much sharper than wanted, straight line is closer to real angle
+						keepThis = false;
+					}
+				}
+				if (keepThis){
+					modifiedPoints.add(cm);
+					continue;
+				}
+				if (log.isDebugEnabled())
+					log.debug("removing obsolete point on almost straight segment in way ",way.toBrowseURL(),"at",cm.toOSMURL());
+				if (gpxPath != null){
+					obsoletePoints.add(cm);
+					removedInWay.add(cm);
+				}
+				numPointsRemoved++;
+				lastWasModified = true;
+				
+			}
+			if (lastWasModified){
+				modifiedPoints.add(points.get(points.size()-1));
+				points.clear();
+				points.addAll(modifiedPoints);
+				if (mode == MODE_ROADS)
+					modifiedRoads.put(way.getId(), cw);
+				if (gpxPath != null){
+					if (draw || cw.isRoundabout()) {
+						GpxCreator.createGpx(gpxPath+way.getId()+"_dpmod", points,removedInWay);
+					}
+				}
+			}
+		}
+		if (gpxPath != null){
+			GpxCreator.createGpx(gpxPath + "obsolete", bbox.toCoords(),
+					new ArrayList<>(obsoletePoints));
+			
+		}
+		log.info("Removed", numPointsRemoved, "obsolete points in lines"); 
+	}
+	
+	/** 
+	 * debug code
+	 * @param roads 
+	 */
+	private void printBadAngles(String name, List<ConvertedWay> roads){
+		if (gpxPath ==  null)
+			return;
+		List<ConvertedWay> badWays = new ArrayList<>();
+		Way lastWay = null;
+		List<Coord> badAngles = new ArrayList<>();
+		for (int w = 0; w < roads.size(); w++) {
+			ConvertedWay cw = roads.get(w);
+			if (!cw.isValid())
+				continue;
+			Way way = cw.getWay();
+			if (way.equals(lastWay)) {
+				continue;
+			}
+			boolean hasBadAngles = false;
+			lastWay = way;
+			List<Coord> points = way.getPoints();
+			// scan through the way's points looking for points which are
+			// on almost straight line and therefore obsolete
+			for (int i = points.size() - 2; i >= 1; --i) {
+				Coord cm = points.get(i);
+				Coord c1 = points.get(i-1);
+				Coord c2 = points.get(i+1);
+				if (c1 == c2){
+					// loop, handled by split routine
+					continue; 
+				}
+				double realAngle = Utils.getAngle(c1, cm, c2);
+				double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
+				if (Math.abs(displayedAngle-realAngle) > 30){
+					badAngles.add(cm);
+					hasBadAngles = true;
+					//						badAngles.addAll(cm.getAlternativePositions());
+				}
+
+			}
+			if (points.size() > 2){
+				Coord p0 = points.get(0);
+				Coord plast = points.get(points.size()-1);
+				if (p0 == plast){
+					Coord cm = points.get(0);
+					Coord c1 = points.get(points.size()-2);
+					Coord c2 = points.get(1);
+					if (c1 == c2){
+						// loop, handled by split routine
+						continue; 
+					}
+					double realAngle = Utils.getAngle(c1, cm, c2);
+					double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
+					if (Math.abs(displayedAngle-realAngle) > 30){
+						badAngles.add(cm);
+						hasBadAngles = true;
+						//						badAngles.addAll(cm.getAlternativePositions());
+					}
+				}
+			}
+			if (hasBadAngles)
+				badWays.add(cw);
+		}
+		GpxCreator.createGpx(gpxPath + name, bbox.toCoords(),
+				new ArrayList<>(badAngles));
+		writeOSM(name, badWays);
+	}
+	
+	/**
+	 * Check if the point can safely be removed from a road. 
+	 * @param p
+	 * @return true if remove is okay
+	 */
+	private boolean allowedToRemove(Coord p){
+		if (p.getOnBoundary())
+			return false;
+		if (mode == MODE_LINES && p.isEndOfWay())
+			return false;
+		if (p instanceof CoordPOI){
+			if (((CoordPOI) p).isUsed()){
+				return false;
+			}
+		}
+		if (p.getHighwayCount() >= 2 || p.isViaNodeOfRestriction()) {
+			return false;
+		}
+		return true;
+	}
+	
+	/**
+	 * helper class
+	 */
+	private class CenterOfAngle {
+		final Coord center;
+		final List<CenterOfAngle> neighbours;
+		final int id; // debugging aid
+		boolean wasMerged;
+		
+		List<CenterOfAngle> badMergeCandidates;
+		
+		public CenterOfAngle(Coord center, int id) {
+			this.center = center;
+			assert center.isReplaced() == false;
+			this.id = id;
+			neighbours = new ArrayList<>();
+		}
+
+		
+		@Override
+		public String toString() {
+			return "CenterOfAngle [id=" + id + ", wasMerged=" + wasMerged + ", num Neighbours="+neighbours.size()+"]";
+		}
+
+
+		@Override
+		public int hashCode() {
+			return center.hashCode();
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if (this == obj)
+				return true;
+			if (obj == null)
+				return false;
+			return center == ((CenterOfAngle) obj).center;
+		}
+
+		/**
+		 * returns current center position or null if removed
+		 * @param replacements
+		 * @return
+		 */
+		public Coord getCurrentLocation(Map<Coord, Coord> replacements){
+			Coord c = getReplacement(center, null, replacements); 
+			if (c.isToRemove())
+				return null;
+			return c; 
+		}
+		
+		/**
+		 * Add neighbour which should not be merged
+		 * @param other
+		 */
+		public void addBadMergeCandidate(CenterOfAngle other) {
+			if (badMergeCandidates == null)
+				badMergeCandidates = new ArrayList<>();
+			badMergeCandidates.add(other);
+		}
+
+		public void addNeighbour(CenterOfAngle other) {
+			if (this == other){
+				log.error("neighbour is equal" );
+			}
+			boolean isNew = true;
+			// we want only different Coord instances here
+			for (CenterOfAngle neighbour : neighbours) {
+				if (neighbour == other) {
+					isNew = false;
+					break;
+				}
+			}
+			if (isNew)
+				neighbours.add(other);
+		}
+
+		/**
+		 * 
+		 * @param replacements
+		 * @return false if this needs changes 
+		 */
+		public boolean isOK(Map<Coord, Coord> replacements) {
+			Coord c = getCurrentLocation (replacements);
+			if (c == null)
+				return true; // removed center: nothing to do
+			for (CenterOfAngle neighbour : neighbours) {
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					continue; // skip removed neighbours
+				double err = calcBearingError(c, n);
+				if (err >= MAX_BEARING_ERROR)
+					return false;
+			}
+			return true;
+		}
+		
+		/**
+		 * Try whether a move or remove or merge of this centre
+		 * fixes bearing problems.
+		 * @param replacements
+		 * @param tryAlsoMerge true means merge is allowed
+		 * @return true if something was changed
+		 */
+		public boolean tryChange(Map<Coord, Coord> replacements, boolean tryAlsoMerge) {
+			if (wasMerged ) {
+				return false;
+			}
+			Coord currentCenter = getCurrentLocation(replacements);
+			if (currentCenter == null)
+				return false; // cannot modify removed centre  
+			CenterOfAngle worstNeighbour = null;
+			Coord worstNP = null;
+			double initialMaxError = 0;
+			double initialSumErr = 0;
+			for (CenterOfAngle neighbour : neighbours) {
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					return false; // neighbour was removed
+				if (currentCenter.highPrecEquals(n)){
+					if (currentCenter == n){
+						log.error(id + ": bad neighbour " + neighbour.id + " zero distance");
+					}
+					if (badMergeCandidates != null && badMergeCandidates.contains(neighbour )
+							|| neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) {
+						//not allowed to merge
+					} else {
+						replaceCoord(currentCenter, n, replacements);
+						neighbour.wasMerged = wasMerged = true;
+						return true;
+					}
+				}
+				double err = calcBearingError(currentCenter, n);
+				if (err != Double.MAX_VALUE)
+					initialSumErr += err;
+				if (err > initialMaxError){
+					initialMaxError = err;
+					worstNeighbour = neighbour;
+					worstNP = n;
+				}
+			}
+			if (initialMaxError < MAX_BEARING_ERROR)
+				return false;
+			double removeErr = calcRemoveError(replacements);
+			if (removeErr == 0){
+//				createGPX(gpxPath+id+"_rem_0", replacements);
+				currentCenter.setRemove(true);
+				return true;
+			}
+			if (initialMaxError == Double.MAX_VALUE)
+				initialSumErr = initialMaxError;
+			double bestReplErr = initialMaxError; 
+			Coord bestCenterReplacement = null;
+			List<Coord> altPositions = currentCenter.getAlternativePositions();
+			for (Coord altCenter : altPositions){
+				double err = calcBearingError(altCenter, worstNP);
+				if (err >= bestReplErr)
+					continue;
+				// alt. position is improvement, check all neighbours
+				double errMax = calcMaxError(replacements, currentCenter, altCenter);
+				if (errMax >= initialMaxError)
+					continue;
+				bestReplErr = err;
+				bestCenterReplacement = altCenter;
+			}
+			Coord bestNeighbourReplacement = null;
+			if (worstNP.hasAlternativePos()){
+				for (Coord altCenter : altPositions){
+					replaceCoord(currentCenter, altCenter, replacements);
+					for (Coord altN: worstNP.getAlternativePositions()){
+						double err = calcBearingError(altCenter, altN);
+						if (err >= bestReplErr)
+							continue;
+						double errNeighbour = worstNeighbour.calcMaxError(replacements, worstNP, altN);
+						if (errNeighbour >= bestReplErr)
+							continue;
+						bestReplErr = err;
+						bestCenterReplacement = altCenter;
+						bestNeighbourReplacement = altN;
+					}
+					replacements.remove(currentCenter);
+					currentCenter.setReplaced(false);
+				}
+			}
+			
+			if (bestReplErr < MAX_BEARING_ERROR){
+//				String msg = "_good"; 
+				if (removeErr < bestReplErr && initialMaxError - removeErr >= MAX_BEARING_ERROR_HALF && removeErr < MAX_BEARING_ERROR_HALF){
+					bestCenterReplacement = null;
+//					createGPX(gpxPath+id+"_rem_pref", replacements);
+				} else if (initialMaxError - bestReplErr < MAX_BEARING_ERROR_HALF || bestReplErr > MAX_BEARING_ERROR_HALF){
+//					msg = "_rather_good";
+				}
+				if (bestCenterReplacement != null){
+					replaceCoord(currentCenter, bestCenterReplacement, replacements);
+					if (bestNeighbourReplacement != null)
+						replaceCoord(worstNP, bestNeighbourReplacement, replacements);
+					double modifiedSumErr = calcSumOfErrors(replacements);
+					if (modifiedSumErr < initialSumErr){
+//						if ("_good".equals(msg) == false)
+//							createGPX(gpxPath+id+msg, replacements);
+						if (bestNeighbourReplacement != null){
+//							worstNeighbour.createGPX(gpxPath+worstNeighbour.id+msg+"_n", replacements);
+						}
+						return true;
+					}
+					// revert changes
+//					System.out.println("ignoring possible improvement at center " + id + " " + initialMaxError + " -> " + bestReplErr + " " + initialSumErr + " --> " + modifiedSumErr);
+//					createGPX(gpxPath+id+"_reverted_"+msg, replacements);
+					replacements.remove(currentCenter);
+					currentCenter.setReplaced(false);
+					replacements.remove(worstNP);
+					worstNP.setReplaced(false);
+//					createGPX(gpxPath+id+"_as_is", replacements);
+					bestCenterReplacement = null;
+				}
+			}
+			if (removeErr < MAX_BEARING_ERROR){
+//				createGPX(gpxPath+id+"_rem", replacements);
+				currentCenter.setRemove(true);
+				return true;
+			}
+			if (!tryAlsoMerge)
+				return false;
+			
+			double dist = currentCenter.distance(worstNP);
+			double maxDist = calcMaxErrorDistance(currentCenter) * 2;
+			boolean forceMerge = dist < maxDist || currentCenter.equals(worstNP);
+			if (forceMerge || this.neighbours.size() == 3 && worstNeighbour.neighbours.size() == 3)
+				return tryMerge(forceMerge, initialMaxError, worstNeighbour, replacements);
+			if (bestCenterReplacement != null){
+				double replImprovement = initialMaxError - bestReplErr;
+				if (replImprovement < MAX_BEARING_ERROR)
+					return false;
+				replaceCoord(currentCenter, bestCenterReplacement, replacements);
+				if (bestNeighbourReplacement != null){
+					replaceCoord(worstNP, bestNeighbourReplacement, replacements);
+				}
+				double modifiedSumErr = calcSumOfErrors(replacements);
+				if (modifiedSumErr < initialSumErr){
+//					System.out.println("ignoring possible improvement at center " + id + " " + initialMaxError + " -> " + bestReplErr + " " + initialSumErr + " --> " + modifiedSumErr);
+//					createGPX(gpxPath+id+"_possible", replacements);
+				}
+				replacements.remove(currentCenter);
+				currentCenter.setReplaced(false);
+				if (bestNeighbourReplacement != null){
+					replacements.remove(worstNP);
+					worstNP.setReplaced(false);
+				}
+				if (modifiedSumErr < initialSumErr){
+//					createGPX(gpxPath+id+"_as_is", replacements);
+				}
+			}
+			return false;
+		}
+
+		/**
+		 * Calculate error when two centres are merged. If they are not equal 
+		 * and the error is too big, nothing is changed and false is returned. 
+		 * 
+		 * @param forceMerge true: skip straight line check
+		 * @param initialMaxError max. bearing error of this centre
+		 * @param neighbour neighbour to merge 
+		 * @param replacements
+		 * @return true if merge is okay
+		 */
+		private boolean tryMerge(boolean forceMerge, double initialMaxError, CenterOfAngle neighbour, Map<Coord, Coord> replacements) {
+			if (badMergeCandidates != null && badMergeCandidates.contains(neighbour )
+					|| neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) {
+				return false; // not allowed to merge
+			}
+			Coord c = getCurrentLocation(replacements);
+			Coord n = neighbour.getCurrentLocation(replacements);
+			// check special cases: don't merge if
+			// 1) both points are via nodes 
+			// 2) both nodes are boundary nodes with non-equal coords
+			// 3) on point is via node and the other is a boundary node, the result could be that the restriction is ignored 
+			if (c.getOnBoundary()){
+				if (n.isViaNodeOfRestriction() || n.getOnBoundary() && c.equals(n) == false)
+					return false; 
+			}
+			if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary()))
+				 return false;
+			if (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary()))
+				return false;
+			if (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary()))
+				return false;
+
+			Coord mergePoint;
+			if (c.getOnBoundary() || c instanceof CoordPOI)
+				mergePoint = c;
+			else if (n.getOnBoundary() || n instanceof CoordPOI)
+				mergePoint = n;
+			else if (c.equals(n))
+				mergePoint = c;
+			else 
+				mergePoint = c.makeBetweenPoint(n, 0.5);
+			double err = 0;
+			if (c.equals(n) == false){
+				err = calcMergeErr(neighbour, mergePoint, replacements);
+				if (err == Double.MAX_VALUE && initialMaxError == Double.MAX_VALUE){
+					log.warn("still equal neighbour after merge",c.toOSMURL());
+				} else { 
+					if (err >= MAX_BEARING_ERROR)
+						return false;
+					if (initialMaxError - err < MAX_BEARING_ERROR_HALF && err > MAX_BEARING_ERROR_HALF){
+						return false; // improvement too small
+					}
+				}
+			}
+			if (!forceMerge){
+				// merge only if the merged line is part of a (nearly) straight line going through both centres,
+				if (!checkNearlyStraight(c.bearingTo(n), neighbour, replacements)
+						|| !neighbour.checkNearlyStraight(n.bearingTo(c), this, replacements)) {
+//					createGPX(gpxPath + "no_more_merge_" + id, replacements);
+//					neighbour.createGPX(gpxPath + "no_more_merge_" + neighbour.id, replacements);
+//					System.out.println("no_merge at " + mergePoint.toDegreeString() + " " + mergePoint.toOSMURL() + " at " + id);
+					return false;
+				}
+			}
+			int hwc = c.getHighwayCount() + n.getHighwayCount() - 1;
+			for (int i = 0; i < hwc; i++)
+				mergePoint.incHighwayCount();
+			if (c != mergePoint)
+				replaceCoord(c, mergePoint, replacements);
+			if (n != mergePoint){
+				replaceCoord(n, mergePoint, replacements);
+			}
+//			createGPX(gpxPath+id+"_merged", replacements);
+//			neighbour.createGPX(gpxPath+neighbour.id+"_merged_w_"+id, replacements);
+			neighbour.wasMerged = wasMerged = true;
+			return true;
+		}
+
+		/**
+		 * Try to find a line that builds a nearly straight line
+		 * with the connection to an other centre. 
+		 * @param bearing bearing of the connection to the other centre
+		 * @param other the other centre
+		 * @param replacements 
+		 * @return true if a nearly straight line exists
+		 */
+		private boolean checkNearlyStraight(double bearing, CenterOfAngle other,
+				Map<Coord, Coord> replacements) {
+			Coord c = getCurrentLocation(replacements);
+			for (CenterOfAngle neighbour : neighbours) {
+				if (neighbour == other) 
+					continue;
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					continue;
+				double bearing2 = c.bearingTo(n);
+				double angle = bearing2 - (bearing - 180);
+				while(angle > 180)
+					angle -= 360;
+				while(angle < -180)
+					angle += 360;
+				if (Math.abs(angle) < 10) // tolerate small angle
+					return true;
+			}
+			return false;
+		}
+
+		/**
+		 * Calculate max. error of this merged with other centres. 
+		 * @param other the other centre
+		 * @param mergePoint the point which should be used as a new centre for both
+		 * @param replacements
+		 * @return the error
+		 */
+		private double calcMergeErr(CenterOfAngle other, Coord mergePoint, Map<Coord, Coord> replacements) {
+			double maxErr = 0;
+			for (CenterOfAngle neighbour : neighbours) {
+				if (neighbour == other) 
+					continue;
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n != null){
+					double err = calcBearingError(mergePoint, n);
+					if (err > maxErr)
+						maxErr = err;
+				}
+			}
+			for (CenterOfAngle othersNeighbour : other.neighbours) {
+				if (othersNeighbour == this) 
+					continue;
+				Coord n = othersNeighbour.getCurrentLocation(replacements);
+				if (n != null){
+					double err = calcBearingError(mergePoint, n);
+					if (err > maxErr)
+						maxErr = err;
+				}
+			}
+			return maxErr;
+		}
+
+		/**
+		 * Calculate max. bearing error of centre point to all neighbours.
+		 * @param replacements
+		 * @param toRepl if centre or a neighbour center is identical to this, use replacement instead
+		 * @param replacement see toRepl
+		 * @return error [0..180] or Double.MAX_VALUE in case of equal points
+		 */
+		private double calcMaxError(Map<Coord, Coord> replacements,
+				Coord toRepl, Coord replacement) {
+			double maxErr = 0;
+			Coord c = getCurrentLocation(replacements);
+			for (CenterOfAngle neighbour : neighbours) {
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					continue; // neighbour was removed
+				double err;
+				if (c == toRepl)
+					err = calcBearingError(replacement, n);
+				else if (n == toRepl)
+					err = calcBearingError(c, replacement);
+				else 
+					err = calcBearingError(c, n);
+				if (err == Double.MAX_VALUE)
+					return err;
+				if (err > maxErr)
+					maxErr = err;
+			}
+			return maxErr;
+		}
+
+		/**
+		 * Calculate sum of errors for a centre.
+		 * @param replacements
+		 * @return
+		 */
+		private double calcSumOfErrors(Map<Coord, Coord> replacements) {
+			double SumErr = 0;
+			Coord c = getCurrentLocation(replacements);
+			for (CenterOfAngle neighbour : neighbours) {
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					continue; // skip removed neighbour
+				double err = calcBearingError(c, n);
+				if (err == Double.MAX_VALUE)
+					return err;
+				SumErr += err;
+			}
+			return SumErr;
+		}
+
+		/**
+		 * Calculate error for a removed centre.
+		 * @param replacements
+		 * @return Double.MAX_VALUE if centre must not be deleted, else [0..180]
+		 */
+		private double calcRemoveError(Map<Coord, Coord> replacements) {
+			if (allowedToRemove(center) == false)
+				return Double.MAX_VALUE;
+			Coord c = getCurrentLocation(replacements);
+			if (neighbours.size() > 2)
+				return Double.MAX_VALUE;
+			Coord[] outerPoints = new Coord[neighbours.size()];
+			
+			for (int i = 0; i < neighbours.size(); i++) {
+				CenterOfAngle neighbour = neighbours.get(i);
+				Coord n = neighbour.getCurrentLocation(replacements);
+				if (n == null)
+					return Double.MAX_VALUE;
+				if (c.equals(n)){
+					if (c.getDistToDisplayedPoint() < n.getDistToDisplayedPoint())
+						return 0;
+				}
+				outerPoints[i] = n;
+			}
+			if (neighbours.size() < 2)
+				return Double.MAX_VALUE;
+			if (c.getDistToDisplayedPoint() < Math.max(outerPoints[0].getDistToDisplayedPoint(), outerPoints[1].getDistToDisplayedPoint()))
+				return Double.MAX_VALUE;
+			double dsplAngle = Utils.getDisplayedAngle(outerPoints[0], c, outerPoints[1]);
+			if (Math.abs( dsplAngle ) < 3)
+				return Double.MAX_VALUE;
+			double realAngle = Utils.getAngle(outerPoints[0], c, outerPoints[1]);
+			double err = Math.abs(realAngle) / 2;
+			return err;
+		}
+
+		// TODO: remove this debugging aid
+		private void createGPX(String gpxName, Map<Coord, Coord> replacements) {
+			if (gpxName == null || gpxPath == null)
+				return;
+			if (gpxName.isEmpty())
+				gpxName = gpxPath + id + "_no_info";
+			// print lines after change
+			Coord c = getReplacement(center, null, replacements);
+			List<Coord> alternatives = c.getAlternativePositions();
+			for (int i = 0; i < neighbours.size(); i++) {
+				CenterOfAngle n = neighbours.get(i);
+				Coord nc = getReplacement(n.center, null, replacements);
+				if (nc == null)
+					continue; // skip removed neighbour
+				if (i == 0 && alternatives.isEmpty() == false) {
+					GpxCreator.createGpx(gpxName + "_" + i,
+							Arrays.asList(c, nc), alternatives);
+				} else
+					GpxCreator.createGpx(gpxName + "_" + i,
+							Arrays.asList(c, nc));
+			}
+			if (neighbours.isEmpty())
+				GpxCreator.createGpx(gpxName + "_empty", Arrays.asList(c, c),
+						alternatives);
+		}
+
+	}
+	
+	
+	private void writeOSM(String name, List<ConvertedWay> convertedWays){
+		//TODO: comment or remove
+		/*
+		if (gpxPath == null)
+			return;
+		File outDir = new File(gpxPath + "/.");
+		if (outDir.getParentFile() != null) {
+			outDir.getParentFile().mkdirs();
+		} 		
+		Map<String,byte[]> dummyMap = new HashMap<>();
+		for (int pass = 1; pass <= 2; pass ++){
+			IdentityHashMap<Coord, Integer> allPoints = new IdentityHashMap<>();
+			uk.me.parabola.splitter.Area bounds = new uk.me.parabola.splitter.Area(
+					bbox.getMinLat(),bbox.getMinLong(),bbox.getMaxLat(),bbox.getMaxLong());
+
+			
+			O5mMapWriter writer = new O5mMapWriter(bounds, outDir, 0, 0, dummyMap, dummyMap);
+			writer.initForWrite();
+			Integer nodeId;
+			try {
+
+				for (ConvertedWay cw: convertedWays){
+					if (cw == null)
+						continue;
+					for (Coord p: cw.getPoints()){
+						nodeId = allPoints.get(p);
+						if (nodeId == null){
+							nodeId = allPoints.size();
+							allPoints.put(p, nodeId);
+							uk.me.parabola.splitter.Node nodeOut = new  uk.me.parabola.splitter.Node();				
+							if (pass == 1)
+								nodeOut.set(nodeId+1000000000L, p.getLatDegrees(), p.getLonDegrees()); // high prec
+							else 
+								nodeOut.set(nodeId+1000000000L, Utils.toDegrees(p.getLatitude()), Utils.toDegrees(p.getLongitude()));
+							if (p instanceof CoordPOI){
+								for (Map.Entry<String, String> tagEntry : ((CoordPOI) p).getNode().getTagEntryIterator()) {
+									nodeOut.addTag(tagEntry.getKey(), tagEntry.getValue());
+								}
+							}
+							writer.write(nodeOut);
+						}
+					}
+				}
+				for (int w = 0; w < convertedWays.size(); w++){
+					ConvertedWay cw = convertedWays.get(w);
+					if (cw == null)
+						continue;
+					Way way = cw.getWay();
+					uk.me.parabola.splitter.Way wayOut = new uk.me.parabola.splitter.Way();
+					for (Coord p: way.getPoints()){
+						nodeId = allPoints.get(p);
+						assert nodeId != null;
+						wayOut.addRef(nodeId+1000000000L);
+					}
+					for (Map.Entry<String, String> tagEntry : way.getTagEntryIterator()) {
+						wayOut.addTag(tagEntry.getKey(), tagEntry.getValue());
+					}
+					
+					wayOut.setId(way.getId());
+					
+					writer.write(wayOut);
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			writer.finishWrite();
+			File f = new File(outDir.getAbsoluteFile() , "00000000.o5m");
+			File ren = new File(outDir.getAbsoluteFile() , name+((pass==1) ? "_hp":"_mu") + ".o5m");
+			if (ren.exists())
+				ren.delete();
+			f.renameTo(ren);
+		}
+		*/
+	}
+	
+	 
+	private static double calcBearingError(Coord p1, Coord p2){
+		if (p1.equals(p2) || p1.highPrecEquals(p2)) {
+			return Double.MAX_VALUE;
+		}
+		double realBearing = p1.bearingTo(p2);
+		double displayedBearing = p1.getDisplayedCoord().bearingTo(p2.getDisplayedCoord());
+		double err = displayedBearing - realBearing;
+		while(err > 180)
+			err -= 360;
+		while(err < -180)
+			err += 360;
+		return Math.abs(err);
+	}
+
+
+	/**
+	 * Calculate the rounding error tolerance for a given point.
+	 * The latitude error may be max higher. Maybe this should be 
+	 * @param p0
+	 * @return
+	 */
+	private static double calcMaxErrorDistance(Coord p0){
+		Coord test = new Coord(p0.getLatitude(),p0.getLongitude()+1);
+		double lonErr = p0.getDisplayedCoord().distance(test) / 2;
+		return lonErr;
+	}
+
+	/**
+	 * Remove obsolete points on straight lines and spikes
+	 * and some wrong angles caused by rounding errors.  
+	 * TODO: optimise by moving 
+	 * @param points list of coordinates that form a shape
+	 * @return reduced list 
+	 */
+	public static List<Coord> fixAnglesInShape(List<Coord> points) {
+		List<Coord> modifiedPoints = new ArrayList<>(points.size());
+		double maxErrorDistance = calcMaxErrorDistance(points.get(0));
+		
+		int n = points.size();
+		// scan through the way's points looking for points which are
+		// on almost straight line and therefore obsolete
+		for (int i = 0; i+1 < points.size(); i++) {
+			Coord c1;
+			if (modifiedPoints.size() > 0)
+				c1 = modifiedPoints.get(modifiedPoints.size()-1);
+			else {
+				c1 = (i > 0) ? points.get(i-1):points.get(n-2);
+			}
+			Coord cm = points.get(i);
+			if (cm.highPrecEquals(c1)){
+				if (modifiedPoints.size() > 1){
+					modifiedPoints.remove(modifiedPoints.size()-1);
+					c1 = modifiedPoints.get(modifiedPoints.size()-1); // might be part of spike
+				} else {
+					continue;
+				}
+			}
+			Coord c2 = points.get(i+1);
+			int straightTest = Utils.isHighPrecStraight(c1, cm, c2);
+			if (straightTest == Utils.STRICTLY_STRAIGHT || straightTest == Utils.STRAIGHT_SPIKE){
+				continue;
+			}
+			double realAngle = Utils.getAngle(c1, cm, c2);
+			if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE){ 
+				double distance = cm.distToLineSegment(c1, c2);
+				if (distance < maxErrorDistance)
+					continue;
+			}
+			modifiedPoints.add(cm);
+		}
+		if (modifiedPoints.get(0) != modifiedPoints.get(modifiedPoints.size()-1))
+			modifiedPoints.add(modifiedPoints.get(0));
+		return modifiedPoints;
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/Action.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/Action.java
index 85afc9b..0109b5c 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/Action.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/Action.java
@@ -26,7 +26,13 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
 public interface Action {
 
 	/**
+	 * 
+	 * 
+	 */
+	/**
 	 * Perform the action on the element.
+	 * @param el
+	 * @return true if one or more tags of the element were changed.
 	 */
-	public void perform(Element el);
+	public boolean perform(Element el);
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionReader.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionReader.java
index 09b2659..0ccfcb5 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionReader.java
@@ -17,14 +17,15 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import java.util.ArrayList;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
 import uk.me.parabola.mkgmap.scan.SyntaxException;
 import uk.me.parabola.mkgmap.scan.Token;
 import uk.me.parabola.mkgmap.scan.TokenScanner;
+import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
 
 /**
  * Read an action block.  This is contained within braces and contains
@@ -35,15 +36,15 @@ import uk.me.parabola.mkgmap.scan.TokenScanner;
 public class ActionReader {
 	private final TokenScanner scanner;
 
-	private final Set<String> usedTags = new HashSet<String>();
+	private final Set<String> usedTags = new HashSet<>();
 
 	public ActionReader(TokenScanner scanner) {
 		this.scanner = scanner;
 	}
 
 	public ActionList readActions() {
-		List<Action> actions = new ArrayList<Action>();
-		Set<String> changeableTags = new HashSet<String>();
+		List<Action> actions = new ArrayList<>();
+		Set<String> changeableTags = new HashSet<>();
 		scanner.skipSpace();
 		if (!scanner.checkToken("{"))
 			return new ActionList(actions, changeableTags);
@@ -209,10 +210,10 @@ public class ActionReader {
 			// If the value contains a variable, then we do not know what the
 			// value will be.  Otherwise save the full tag=value
 			if (val.contains("$")) {
-				for (String accessTag : StyledConverter.ACCESS_TAGS)
+				for (String accessTag : ACCESS_TAGS.keySet())
 					changeableTags.add(accessTag);
 			} else {
-				for (String accessTag : StyledConverter.ACCESS_TAGS)
+				for (String accessTag : ACCESS_TAGS.keySet())
 					changeableTags.add(accessTag + "=" + val);
 			}
 			if (scanner.checkToken("|"))
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessAction.java
index 58fa53c..c6df7c7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessAction.java
@@ -14,8 +14,8 @@ package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import java.util.List;
 
-import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
 
 /**
  * Add one value to all mkgmap access tags, optionally changing them if they already exist.  
@@ -39,7 +39,7 @@ public class AddAccessAction extends ValueBuildedAction {
 		add(value);
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		// 1st build the value
 		Element tags = valueTags!=null? valueTags: el;
 		String accessValue = null;
@@ -50,11 +50,12 @@ public class AddAccessAction extends ValueBuildedAction {
 			}
 		}
 		if (accessValue == null) {
-			return;
+			return false;
 		}
-		for (String accessTag : StyledConverter.ACCESS_TAGS) {
+		for (Short accessTag : ACCESS_TAGS_COMPILED.keySet()) {
 			setTag(el, accessTag, accessValue);
 		}
+		return true;
 	}
 	
 	/**
@@ -62,15 +63,15 @@ public class AddAccessAction extends ValueBuildedAction {
 	 * is {@code true} the tag is always set. Otherwise the tag
 	 * is set only if it does not already exist.
 	 * @param el OSM element
-	 * @param tag the tag name
+	 * @param tagKey the compiled tag key 
 	 * @param value the value to be set
 	 */
-	private void setTag(Element el, String tag, String value) {
-		String tv = el.getTag(tag);
+	private void setTag(Element el, Short tagKey, String value) {
+		String tv = el.getTag(tagKey);
 		if (tv != null && !modify)
 			return;
 
-		el.addTag(tag, value);
+		el.addTag(tagKey, value);
 	}
 
 	public void setValueTags(Element valueTags) {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
index f267f8e..0a9b357 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
@@ -21,7 +21,7 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * We have a list of possible substitutions.
  */
 public class AddLabelAction extends ValueBuildedAction {
-
+	
 	/**
 	 * Search for the first matching pattern and set the first unset element label
 	 * to it.
@@ -29,8 +29,9 @@ public class AddLabelAction extends ValueBuildedAction {
 	 * If all four labels are already set, then nothing is done.
 	 *
 	 * @param el The element on which a label may be set.
+	 * @return 
 	 */
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		for (int index = 1; index <=4; index++) {
 			// find the first unset label and set it
 			if (el.getTag("mkgmap:label:"+index) == null) {
@@ -42,18 +43,19 @@ public class AddLabelAction extends ValueBuildedAction {
 							if (s.equals(el.getTag("mkgmap:label:"+n))) {
 								// value is equal to a previous label
 								// do not use it
-								return;
+								return false;
 							}
 						}
 						
 						// set the label
 						el.addTag("mkgmap:label:"+index, s);
-						return;
+						return true;
 					}
 				}
-				return;
+				return false;
 			}
 		}
+		return false;
 	}
 
 	public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddTagAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddTagAction.java
index 969c51c..f000a31 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddTagAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddTagAction.java
@@ -19,6 +19,7 @@ package uk.me.parabola.mkgmap.osmstyle.actions;
 import java.util.List;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Add a tag, optionally changing it if it already exists.  The value that
@@ -29,6 +30,7 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
 public class AddTagAction extends ValueBuildedAction {
 	private final boolean modify;
 	private final String tag;
+	private final short tagKey;
 
 	// The tags used to build the value.
 	private Element valueTags;
@@ -41,23 +43,26 @@ public class AddTagAction extends ValueBuildedAction {
 	public AddTagAction(String tag, String value, boolean modify) {
 		this.modify = modify;
 		this.tag = tag;
+		this.tagKey = TagDict.getInstance().xlate(tag);
 		add(value);
 	}
 
-	public void perform(Element el) {
-		String tv = el.getTag(tag);
-		if (tv != null && !modify)
-			return;
-
+	public boolean perform(Element el) {
+		if (!modify){
+			String tv = el.getTag(tagKey);
+			if (tv != null)
+				return false;
+		}
 		Element tags = valueTags!=null? valueTags: el;
 
 		for (ValueBuilder value : getValueBuilder()) {
 			String newval = value.build(tags, el);
 			if (newval != null) {
-				el.addTag(tag, newval);
-				break;
+				el.addTag(tagKey, newval);
+				return true;
 			}
 		}
+		return false;
 	}
 
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAction.java
index faac87f..20d8271 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAction.java
@@ -17,6 +17,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Deletes a tag.
@@ -24,17 +25,18 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * @author Steve Ratcliffe
  */
 public class DeleteAction implements Action {
-	private final String tag;
+	private final short tag;
 
 	public DeleteAction(String tag) {
-		this.tag = tag;
+		this.tag = TagDict.getInstance().xlate(tag);
 	}
 
-	public void perform(Element el) {
-		el.deleteTag(tag);
+	public boolean perform(Element el) {
+		String oldVal = el.deleteTag(tag);
+		return oldVal != null;
 	}
 
 	public String toString() {
-		return "delete " + tag + ";";
+		return "delete " + TagDict.getInstance().get(tag) + ";";
 	}
 }
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAllTagsAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAllTagsAction.java
index b446640..519a44e 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAllTagsAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/DeleteAllTagsAction.java
@@ -30,9 +30,10 @@ public class DeleteAllTagsAction implements Action {
 		this.noTagElement = new Node(0, new Coord(0,0));
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		// remove all tags by copying the tags from a no tag element
 		el.copyTags(noTagElement);
+		return true;
 	}
 
 	public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
index 062953b..e095be2 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
@@ -29,8 +29,9 @@ public class EchoAction implements Action {
 		this.value = new ValueBuilder(str, false);
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		String e = value.build(el, el);
 		System.err.println(el.getId() + ": " + e);
+		return false;
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
index 99650a2..6850f49 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
@@ -27,9 +27,10 @@ public class EchoTagsAction implements Action {
 		this.value = new ValueBuilder(str, false);
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		String e = value.build(el, el);
 		System.err.println(el.getId() + " - " + el.toTagString()+" " + e);
+		return false;
 	}
 	
 	public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/HighwaySymbolFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/HighwaySymbolFilter.java
index a63bf1f..c0e92cc 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/HighwaySymbolFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/HighwaySymbolFilter.java
@@ -15,6 +15,8 @@ package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
@@ -34,6 +36,8 @@ public class HighwaySymbolFilter extends ValueFilter {
 	private int maxAlphaNum = MAX_REF_LENGTH; // Max. length for alphanumeric (e.g., 'A67')
 	private int maxAlpha = MAX_REF_LENGTH; // Max. length for alpha only signs (e.g., 'QEW')
 
+	private static final Pattern spacePattern = Pattern.compile(" ", Pattern.LITERAL);
+	private static final Pattern semicolonPattern = Pattern.compile(";", Pattern.LITERAL);
 	static {
 		//symbols.put("ele", "\u001f"); // name.height separator
 
@@ -89,10 +93,11 @@ public class HighwaySymbolFilter extends ValueFilter {
 
 
 		// Nuke all spaces
-		String shieldText = value.replace(" ", "");
-
+//		String shieldText = value.replace(" ", "");
+		String shieldText = spacePattern.matcher(value).replaceAll(Matcher.quoteReplacement(""));
 		// Also replace ";" with "/", to change B3;B4 to B3/B4
-		shieldText = shieldText.replace(";", "/");
+		//shieldText = shieldText.replace(";", "/");
+		shieldText = semicolonPattern.matcher(shieldText).replaceAll(Matcher.quoteReplacement("/"));
 		
 		// Check if value is alphanumeric
 		boolean isAlphaNum = false;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
index ce3aa2a..f72c879 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
@@ -17,6 +17,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Set the name on the given element.  The tags of the element may be
@@ -27,7 +28,7 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * @author Steve Ratcliffe
  */
 public class NameAction extends ValueBuildedAction {
-
+	private final short label1TagKey = TagDict.getInstance().xlate("mkgmap:label:1"); 
 	/**
 	 * search for the first matching name pattern and set the element name
 	 * to it.
@@ -35,18 +36,20 @@ public class NameAction extends ValueBuildedAction {
 	 * If the element name is already set, then nothing is done.
 	 *
 	 * @param el The element on which the name may be set.
+	 * @return 
 	 */
-	public void perform(Element el) {
-		if (el.getTag("mkgmap:label:1") != null)
-			return;
+	public boolean perform(Element el) {
+		if (el.getTag(label1TagKey) != null)
+			return false;
 		
 		for (ValueBuilder vb : getValueBuilder()) {
 			String s = vb.build(el, el);
 			if (s != null) {
-				el.addTag("mkgmap:label:1", s);
-				break;
+				el.addTag(label1TagKey, s);
+				return true;
 			}
 		}
+		return false;
 	}
 
 	public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/NotEqualFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/NotEqualFilter.java
index a7eaee9..4eb60dc 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/NotEqualFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/NotEqualFilter.java
@@ -14,6 +14,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * This can be used to filter out redundant values.
@@ -25,18 +26,18 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  */
 public class NotEqualFilter extends ValueFilter {
 
-	private final String tagName; 
+	private final short tagKey; 
 
 	public NotEqualFilter(String s) {
 
-		tagName = s;
+		tagKey = TagDict.getInstance().xlate(s);
 
 	}
 
 	public String doFilter(String value, Element el) {
 		if (value == null) return value;
 
-		String tagValue = el.getTag(tagName);
+		String tagValue = el.getTag(tagKey);
 
 		if (tagValue == null)
 			return value;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/RenameAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/RenameAction.java
index c31115e..e59f08a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/RenameAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/RenameAction.java
@@ -17,6 +17,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Renames a tag.  Specifically takes the value of the 'from' tag, sets
@@ -24,19 +25,21 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * @author Steve Ratcliffe
  */
 public class RenameAction implements Action {
-	private final String from;
-	private final String to;
+	private final short from;
+	private final short to;
 
 	public RenameAction(String from, String to) {
-		this.from = from;
-		this.to = to;
+		this.from = TagDict.getInstance().xlate(from);
+		this.to = TagDict.getInstance().xlate(to);
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		String fromval = el.getTag(from);
 		if (fromval != null) {
 			el.addTag(to, fromval);
 			el.deleteTag(from);
+			return true;
 		}
+		return false;
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubAction.java
index 83c834b..e067524 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubAction.java
@@ -43,9 +43,10 @@ public class SubAction implements Action {
 		this.once = once;
 	}
 
-	public void perform(Element el) {
+	public boolean perform(Element el) {
 		if (el instanceof Relation)
 			performOnSubElements((Relation) el);
+		return true; // probably false, but relation may contain itself in complex recursive structures 
 	}
 
 	private void performOnSubElements(Relation rel) {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstitutionFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstitutionFilter.java
index 3344bfa..78e6634 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstitutionFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstitutionFilter.java
@@ -12,6 +12,9 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
@@ -27,6 +30,7 @@ public class SubstitutionFilter extends ValueFilter {
 	private final String from;
 	private final String to;
 	private boolean isRegexp = false;
+	private final Pattern pattern;
 
 	public SubstitutionFilter(String arg) {
 		int i = arg.indexOf("=>");
@@ -43,14 +47,16 @@ public class SubstitutionFilter extends ValueFilter {
 			from = arg;
 			to = "";
 		}
+		if (isRegexp)
+			pattern = Pattern.compile(from);
+		else
+			pattern = Pattern.compile(from, Pattern.LITERAL);
 	}
 
 	public String doFilter(String value, Element el) {
 		if (value == null) return null;
-		if (from == null || to == null)
-			// can't happen!
-			return value;
+		return pattern.matcher(value).replaceAll(isRegexp ? to : Matcher.quoteReplacement(to));
 		// replaceAll expects a regexp as 1st argument
-		return (isRegexp ? value.replaceAll(from, to) : value.replace(from, to) );
+//				return (isRegexp ? value.replaceAll(from, to) : value.replace(from, to) );
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
index 5c0e516..c0aad6d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
@@ -32,8 +32,15 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * @author Toby Speight
  */
 public class ValueBuilder {
+	private static final Pattern[] FILTER_ARG_PATTERNS = {
+			Pattern.compile("[ \t]*([^: \\t|]+:\"[^\"]+\")[ \t]*"),
+			Pattern.compile("[ \t]*([^: \\t|]+:'[^']+')[ \t]*"),
 
-	private final List<ValueItem> items = new ArrayList<ValueItem>();
+			// This must be last
+			Pattern.compile("([ \t]*[^: \\t|]+:[^|]*)"),
+	};
+
+	private final List<ValueItem> items = new ArrayList<>();
 	private final boolean completeCheck;
 	
 	public ValueBuilder(String pattern) {
@@ -149,11 +156,32 @@ public class ValueBuilder {
 	private void addTagValue(String tagname, boolean is_local) {
 		ValueItem item = new ValueItem();
 		if (tagname.contains("|")) {
-			String[] parts = tagname.split("\\|");
+			String[] parts = tagname.split("[ \t]*\\|", 2);
 			assert parts.length > 1;
+
 			item.setTagname(parts[0], is_local);
-			for (int i = 1; i < parts.length; i++)
-				addFilter(item, parts[i]);
+
+			String s = parts[1];
+
+			int start = 0;
+			int end = s.length();
+			while (start < end) {
+				Matcher matcher = null;
+				for (Pattern p : FILTER_ARG_PATTERNS) {
+					matcher = p.matcher(s);
+					matcher.region(start, end);
+					if (matcher.lookingAt())
+						break;
+				}
+
+				if (matcher != null && matcher.lookingAt()) {
+					start = matcher.end() + 1;
+					addFilter(item, matcher.group(1));
+				} else {
+					assert false;
+					start = end;
+				}
+			}
 		} else {
 			item.setTagname(tagname, is_local);
 		}
@@ -161,30 +189,41 @@ public class ValueBuilder {
 	}
 
 	private void addFilter(ValueItem item, String expr) {
-		Pattern pattern = Pattern.compile("([^:]+):(.*)");
-		//pattern.
+		Pattern pattern = Pattern.compile("([^:]+):[\"']?(.*?)[\"']?", Pattern.DOTALL);
+
 		Matcher matcher = pattern.matcher(expr);
-		matcher.find();
+		matcher.matches();
 		String cmd = matcher.group(1);
 		String arg = matcher.group(2);
-		if (cmd.equals("def")) {
+
+		switch (cmd) {
+		case "def":
 			item.addFilter(new DefaultFilter(arg));
-		} else if (cmd.equals("conv")) {
+			break;
+		case "conv":
 			item.addFilter(new ConvertFilter(arg));
-		} else if (cmd.equals("subst")) {
+			break;
+		case "subst":
 			item.addFilter(new SubstitutionFilter(arg));
-		} else if (cmd.equals("prefix")) {
+			break;
+		case "prefix":
 			item.addFilter(new PrependFilter(arg));
-		} else if (cmd.equals("highway-symbol")) {
+			break;
+		case "highway-symbol":
 			item.addFilter(new HighwaySymbolFilter(arg));
-		} else if (cmd.equals("height")) {
+			break;
+		case "height":
 			item.addFilter(new HeightFilter(arg));
-		} else if (cmd.equals("not-equal")) {
+			break;
+		case "not-equal":
 			item.addFilter(new NotEqualFilter(arg));
-		} else if (cmd.equals("substring")) {
+			break;
+		case "substring":
 			item.addFilter(new SubstringFilter(arg));
-		}  else if (cmd.equals("part")) {
+			break;
+		case "part":
 			item.addFilter(new PartFilter(arg));
+			break;
 		}
 	}
 
@@ -198,7 +237,7 @@ public class ValueBuilder {
 	}
 
 	public Set<String> getUsedTags() {
-		Set<String> set = new HashSet<String>();
+		Set<String> set = new HashSet<>();
 		for (ValueItem v : items) {
 			String tagname = v.getTagname();
 			if (tagname != null)
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueItem.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueItem.java
index b1f0ab5..3299598 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueItem.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueItem.java
@@ -17,6 +17,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Part of a substitution string.  This can represent a constant
@@ -26,9 +27,10 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  */
 public class ValueItem {
 	private String tagname;
-	private boolean tagname_is_local;
+	private short tagKey;
 	private ValueFilter filter;
 	private String value;
+	private boolean tagname_is_local;
 
 	public ValueItem() {
 	}
@@ -43,7 +45,7 @@ public class ValueItem {
 
 		if (tagname != null) {
 			Element e = tagname_is_local ? local_el : el;
-			String tagval = e.getTag(tagname);
+			String tagval = e.getTag(tagKey);
 			if (filter != null)
 				value = filter.filter(tagval,el);
 			else
@@ -67,6 +69,7 @@ public class ValueItem {
 	public void setTagname(String tagname, boolean local) {
 		this.tagname = tagname;
 		this.tagname_is_local = local;
+		this.tagKey = TagDict.getInstance().xlate(tagname);
 	}
 
 	public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
index 58ded00..e4863d7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.scan.SyntaxException;
 
@@ -28,6 +29,8 @@ public abstract class AbstractOp implements Op {
 	
 	protected Op first;
 	private NodeType type;
+	protected boolean lastRes;
+	protected int lastCachedId = -1;
 
 	public static Op createOp(String value) {
 		char c = value.charAt(0);
@@ -71,6 +74,19 @@ public abstract class AbstractOp implements Op {
 		return op;
 	}
 
+	public boolean eval(int cacheId, Element el){
+		if (lastCachedId != cacheId){
+			if (lastCachedId > cacheId){
+				throw new ExitException("fatal error: cache id invalid");
+			}
+			lastRes = eval(el);
+			lastCachedId = cacheId;
+		}
+		return lastRes;
+			
+	}
+	
+	
 	/**
 	 * Does this operation have a higher priority that the other one?
 	 * @param other The other operation.
@@ -85,6 +101,7 @@ public abstract class AbstractOp implements Op {
 
 	public void setFirst(Op first) {
 		this.first = first;
+		lastCachedId = -1;
 	}
 
 	/**
@@ -120,4 +137,9 @@ public abstract class AbstractOp implements Op {
 	public boolean isType(NodeType value) {
 		return type == value;
 	}
+	
+	public void resetCache(){
+		lastCachedId = -1;
+	}
+
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/AndOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/AndOp.java
index 63b034a..cc4f211 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/AndOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/AndOp.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
@@ -34,7 +35,24 @@ public class AndOp extends AbstractBinaryOp {
 		return getFirst().eval(el) && getSecond().eval(el);
 	}
 
+	public boolean eval(int cacheId, Element el){
+		if (lastCachedId != cacheId){
+			if (lastCachedId > cacheId){
+				throw new ExitException("fatal error: cache id invalid");
+			}
+			lastRes = getFirst().eval(cacheId, el);
+			if (lastRes == true)
+				lastRes = getSecond().eval(cacheId, el);
+			lastCachedId = cacheId;
+		}
+		//else System.out.println("cached: " + cacheId + " " + toString());
+		return lastRes;
+	}
+
+
+	
 	public int priority() {
 		return 5;
 	}
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
index b52752a..760d15f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
@@ -58,22 +58,30 @@ public class LinkedOp implements Op {
 		return b;
 	}
 
+	public boolean eval(int cacheId, Element el){
+		if (el == current)
+			return false;
+
+		boolean b = wrapped.eval(cacheId, el);
+		if (link != null && b)
+			link.setMatched(el);
+		return b;
+	}
+	
 	public String toString() {
-		if (first) {
-			StringBuilder sb = new StringBuilder();
-			sb.append('(');
-			sb.append(wrapped);
-			LinkedOp l = link;
-			while (l != null) {
-				sb.append(" | ");
-				sb.append(l.wrapped);
-				l = l.link;
-			}
-			sb.append(')');
-			return sb.toString();
-		} else {
-			return "# Part of the previous OR expression.";
+		StringBuilder sb = new StringBuilder();
+		if (!first)
+			sb.append("# Part of the previous OR expression: ");
+		sb.append('(');
+		sb.append(wrapped);
+		LinkedOp l = link;
+		while (l != null) {
+			sb.append(" | ");
+			sb.append(l.wrapped);
+			l = l.link;
 		}
+		sb.append(')');
+		return sb.toString();
 	}
 
 	public int priority() {
@@ -143,4 +151,8 @@ public class LinkedOp implements Op {
 			return new LinkedOp(op, first);
 		}
 	}
+	
+	public boolean isFirstPart(){
+		return first;
+	}
 }
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
index c7be904..ffb165d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
@@ -31,6 +31,15 @@ public interface Op {
 	 * @return True if the expression is true for the given element.
 	 */
 	public boolean eval(Element el);
+	
+	/**
+	 * Evaluate the expression using a cache.
+	 * @param cacheId used to recognise an invalid cache
+	 * @param el The OSM element to be tested.
+	 * @return True if the expression is true for the given element.
+	 */
+	public boolean eval(int cacheId, Element el);
+
 
 	/**
 	 * Does this operation have a higher priority that the other one?
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/OrOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/OrOp.java
index 1d66d45..1d979d7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/OrOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/OrOp.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
@@ -33,6 +34,20 @@ public class OrOp extends AbstractBinaryOp {
 		return getFirst().eval(el) || getSecond().eval(el);
 	}
 
+	public boolean eval(int cacheId, Element el){
+		if (lastCachedId != cacheId){
+			if (lastCachedId > cacheId){
+				throw new ExitException("fatal error: cache id invalid");
+			}
+			lastRes = getFirst().eval(cacheId, el);
+			if (lastRes == false)
+				lastRes = getSecond().eval(cacheId, el);
+			lastCachedId = cacheId;
+		}
+		//else System.out.println("cached: " + cacheId + " " + toString());
+		return lastRes;
+	}
+
 	public int priority() {
 		return 3;
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java b/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
index 6964eda..5fdef4f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
@@ -24,7 +24,7 @@ public class AreaSizeFunction extends CachedFunction {
 		if (el instanceof Way) {
 			Way w = (Way)el;
 			// a non closed way has size 0
-			if (w.isClosed() == false) {
+			if (w.hasEqualEndPoints() == false) {
 				return "0";
 			}
 			return nf.format(MultiPolygonRelation.calcAreaSize(((Way) el).getPoints()));
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/function/CachedFunction.java b/src/uk/me/parabola/mkgmap/osmstyle/function/CachedFunction.java
index 2f730da..c9344e4 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/function/CachedFunction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/function/CachedFunction.java
@@ -16,6 +16,7 @@ package uk.me.parabola.mkgmap.osmstyle.function;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Node;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 
 /**
@@ -24,7 +25,7 @@ import uk.me.parabola.mkgmap.reader.osm.Way;
  * @author WanMil
  */
 public abstract class CachedFunction extends StyleFunction {
-
+	short cacheKey = TagDict.INVALID_TAG_VALUE; 
 	public CachedFunction(String value) {
 		super(value);
 	}
@@ -46,9 +47,11 @@ public abstract class CachedFunction extends StyleFunction {
 		}
 		
 		if (isCached()) {
+			if (cacheKey == TagDict.INVALID_TAG_VALUE)
+				cacheKey = TagDict.getInstance().xlate(getCacheTag());
 			// if caching is supported check if the value has already
 			// been calculated
-			String cachedValue = el.getTag(getCacheTag());
+			String cachedValue = el.getTag(cacheKey);
 			if (cachedValue != null) {
 				return cachedValue;
 			}
@@ -59,7 +62,7 @@ public abstract class CachedFunction extends StyleFunction {
 		
 		if (functionResult != null && isCached()) {
 			// if caching is supported save the value for later usage
-			el.addTag(getCacheTag(), functionResult);
+			el.addTag(cacheKey, functionResult);
 		}
 
 		return functionResult;
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/function/GetTagFunction.java b/src/uk/me/parabola/mkgmap/osmstyle/function/GetTagFunction.java
index 0d6cbb1..cf91362 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/function/GetTagFunction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/function/GetTagFunction.java
@@ -13,6 +13,7 @@
 package uk.me.parabola.mkgmap.osmstyle.function;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
 /**
  * Get the value of a tag from the element.
@@ -23,13 +24,14 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  * @author Steve Ratcliffe
  */
 public class GetTagFunction extends StyleFunction {
-
+	short tagKey;
 	public GetTagFunction(String value) {
 		super(value);
+		tagKey = TagDict.getInstance().xlate(value);
 	}
 
 	public String value(Element el) {
-		return el.getTag(getKeyValue());
+		return el.getTag(tagKey);
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/function/IsClosedFunction.java b/src/uk/me/parabola/mkgmap/osmstyle/function/IsClosedFunction.java
index 2d76b72..5e6aefa 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/function/IsClosedFunction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/function/IsClosedFunction.java
@@ -45,7 +45,8 @@ public class IsClosedFunction extends StyleFunction {
 	
 	public String value(Element el) {
 		if (el instanceof Way) {
-			return String.valueOf(((Way)el).isClosed());
+			Way w = (Way) el;
+			return String.valueOf((w.getPoints().size() > 2 && w.hasIdenticalEndPoints()));
 		}
 		return null;
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
index 148438d..5ebb99f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
@@ -19,11 +19,9 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Map.Entry;
 import java.util.Properties;
-import java.util.regex.Pattern;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
-import uk.me.parabola.imgfmt.app.Label;
 import uk.me.parabola.imgfmt.app.net.NumberStyle;
 import uk.me.parabola.imgfmt.app.net.Numbers;
 import uk.me.parabola.log.Logger;
@@ -68,10 +66,10 @@ public class HousenumberGenerator {
 	 * @param e an OSM element
 	 * @return the street name (or {@code null} if no street name set)
 	 */
-	private String getStreetname(Element e) {
-		String streetname = stripStreetName(e.getTag("mkgmap:street"));
+	private static String getStreetname(Element e) {
+		String streetname = e.getTag("mkgmap:street");
 		if (streetname == null) {
-			streetname = stripStreetName(e.getTag("addr:street"));
+			streetname = e.getTag("addr:street");
 		}	
 		return streetname;
 	}
@@ -108,40 +106,6 @@ public class HousenumberGenerator {
 		}
 	}
 	
-	private static final Pattern BRACKETS = Pattern.compile("\\(.*\\)"); 
-	
-	/**
-	 * Removes the shield from the given string. This is the shield character 
-	 * including the following part until the first whitespace. Additionally all
-	 * text within brackets is removed. 
-	 * @param s a label
-	 * @return the label without shield ({@code null} if stripped label is empty)
-	 */
-	public static String stripStreetName(String s) {
-		if (s == null || s.isEmpty())
-			return null;
-		
-		if (Label.SHIELDS.matcher(s.substring(0, 1)).matches()) {
-			int whitespaceIndex = s.indexOf(' ', 1);
-			if (whitespaceIndex < 0) {
-				return null;
-			} else {
-				s = s.substring(whitespaceIndex);
-			}
-		}
-		s = BRACKETS.matcher(s).replaceAll(""); // remove text in brackets
-		s = Label.squashSpaces(s);
-		if (s == null) {
-			return null;
-		}
-		s = s.trim();
-		if (s.isEmpty()) {
-			return null;
-		}
-		return s;
-		
-	}
-
 	
 	/**
 	 * Adds a road to be processed by the house number generator.
@@ -228,7 +192,7 @@ public class HousenumberGenerator {
 	 * @param elements a list of OSM elements belonging to this street name
 	 * @param roads a list of roads with the given street name
 	 */
-	private void match(String streetname, List<Element> elements, List<MapRoad> roads) {
+	private static void match(String streetname, List<Element> elements, List<MapRoad> roads) {
 		List<HousenumberMatch> numbersList = new ArrayList<HousenumberMatch>(
 				elements.size());
 		for (Element node : elements) {
@@ -298,36 +262,43 @@ public class HousenumberGenerator {
 			log.info("Numbers:",roadX.getValue());
 			
 			int n = 0;
-			int lastRoutableNodeIndex = -1;
+			int nodeIndex = 0;
+			int lastRoutableNodeIndex = 0;
 			for (Coord p : r.getPoints()) {
-				if (n== 0) {
+				if (n == 0) {
 					assert p instanceof CoordNode; 
 				}
-				
-				if (p instanceof CoordNode == false) {
+
+				// An ordinary point in the road.
+				if (p.getId() == 0) {
 					n++;
 					continue;
 				}
-				
-				if (lastRoutableNodeIndex < 0) {
-					lastRoutableNodeIndex=n;
+
+				// The first time round, this is guaranteed to be a CoordNode
+				if (n == 0) {
+					nodeIndex++;
 					n++;
 					continue;
 				}
 
+				// Now we have a CoordNode and it is not the first one.
 				Numbers numbers = new Numbers();
 				numbers.setNodeNumber(0);
 				numbers.setRnodNumber(lastRoutableNodeIndex);
 			
 				applyNumbers(numbers,leftNumbers,n,true);
 				applyNumbers(numbers,rightNumbers,n,false);
-				log.info("Left: ",numbers.getLeftNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getLeftStart(),"End:",numbers.getLeftEnd(), "Remaining: "+leftNumbers);
-				log.info("Right:",numbers.getRightNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getRightStart(),"End:",numbers.getRightEnd(), "Remaining: "+rightNumbers);
+
+				if (log.isInfoEnabled()) {
+					log.info("Left: ",numbers.getLeftNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getLeftStart(),"End:",numbers.getLeftEnd(), "Remaining: "+leftNumbers);
+					log.info("Right:",numbers.getRightNumberStyle(),numbers.getRnodNumber(),"Start:",numbers.getRightStart(),"End:",numbers.getRightEnd(), "Remaining: "+rightNumbers);
+				}
 				
 				numbersListing.add(numbers);
 				
-				lastRoutableNodeIndex=n;
-				
+				lastRoutableNodeIndex = nodeIndex;
+				nodeIndex++;
 				n++;
 			}
 			
@@ -342,7 +313,7 @@ public class HousenumberGenerator {
 	 * @param maxSegment the highest segment number to use
 	 * @param left {@code true} the left side of the street; {@code false} the right side of the street
 	 */
-	private void applyNumbers(Numbers numbers, List<HousenumberMatch> housenumbers, int maxSegment, boolean left) {
+	private static void applyNumbers(Numbers numbers, List<HousenumberMatch> housenumbers, int maxSegment, boolean left) {
 		NumberStyle style = NumberStyle.NONE;
 
 		if (housenumbers.isEmpty() == false) {
@@ -401,7 +372,7 @@ public class HousenumberGenerator {
 	 * @param point the point to check
 	 * @return {@code true} point lies on the left side; {@code false} point lies on the right side
 	 */
-	private boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
+	private static boolean isLeft(Coord spoint1, Coord spoint2, Coord point) {
 		
 		boolean left =  ((spoint2.getLongitude() - spoint1.getLongitude())
 				* (point.getLatitude() - spoint1.getLatitude()) - (spoint2.getLatitude() - spoint1
@@ -417,7 +388,7 @@ public class HousenumberGenerator {
 	 * @param point point
 	 * @return the distance in meter
 	 */
-	private double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
+	private static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
 
 		if (frac <= 0) {
 			return spoint1.distance(point);
@@ -436,7 +407,7 @@ public class HousenumberGenerator {
 	 * @param point point
 	 * @return the fraction
 	 */
-	private double getFrac(Coord spoint1, Coord spoint2, Coord point) {
+	private static double getFrac(Coord spoint1, Coord spoint2, Coord point) {
 
 		double dx = spoint2.getLongitude() - spoint1.getLongitude();
 		double dy = spoint2.getLatitude() - spoint1.getLatitude();
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
index 56a609c..7422b2d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberMatch.java
@@ -20,6 +20,7 @@ import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.general.MapRoad;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Node;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 
 /**
@@ -63,14 +64,13 @@ public class HousenumberMatch {
 	 * @param e an OSM element
 	 * @return the house number (or {@code null} if no house number set)
 	 */
+	private static final short housenumberTagKey1 =  TagDict.getInstance().xlate("mkgmap:housenumber");
+	private static final short housenumberTagKey2 =  TagDict.getInstance().xlate("addr:housenumber");
 	public static String getHousenumber(Element e) {
-		if (e.getTag("mkgmap:housenumber") != null) {
-			return e.getTag("mkgmap:housenumber");
-		}
-		if (e.getTag("addr:housenumber") != null) {
-			return e.getTag("addr:housenumber");
-		}	
-		return null;
+		String res = e.getTag(housenumberTagKey1); 
+		if (res != null)
+			return res;
+		return e.getTag(housenumberTagKey2);
 	}
 	
 	/**
diff --git a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
index b1f72b5..40d9946 100644
--- a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
@@ -28,7 +28,7 @@ import uk.me.parabola.mkgmap.general.MapDetails;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapShape;
-import uk.me.parabola.mkgmap.general.RoadNetwork;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.mkgmap.reader.dem.DEM;
 import uk.me.parabola.util.Configurable;
 import uk.me.parabola.util.EnhancedProperties;
diff --git a/src/uk/me/parabola/mkgmap/reader/dem/DEM.java b/src/uk/me/parabola/mkgmap/reader/dem/DEM.java
index 71bc07b..cb948db 100644
--- a/src/uk/me/parabola/mkgmap/reader/dem/DEM.java
+++ b/src/uk/me/parabola/mkgmap/reader/dem/DEM.java
@@ -30,7 +30,6 @@ import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.map.Map;
-import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.build.MapBuilder;
 import uk.me.parabola.mkgmap.general.LevelInfo;
@@ -41,6 +40,7 @@ import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
 import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
 import uk.me.parabola.mkgmap.reader.osm.Style;
 import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
 import uk.me.parabola.util.EnhancedProperties;
 
 
@@ -85,15 +85,19 @@ public abstract class DEM {
 		try {
 			String dataPath;
 			Class demClass;
-			if (demType.equals("ASTER")) {
+			switch (demType) {
+			case "ASTER":
 				dataPath = config.getProperty("dem-path", "ASTER");
 				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.optional.GeoTiffDEM$ASTER");
-			} else if (demType.equals("CGIAR")) {
+				break;
+			case "CGIAR":
 				dataPath = config.getProperty("dem-path", "CGIAR");
 				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.optional.GeoTiffDEM$CGIAR");
-			} else {
+				break;
+			default:
 				dataPath = config.getProperty("dem-path", "SRTM");
 				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.HGTDEM");
+				break;
 			}
 			Constructor<DEM> constructor = demClass.getConstructor(String.class,
 					Double.TYPE, Double.TYPE,
@@ -163,7 +167,7 @@ public abstract class DEM {
 			long mapName = Integer.valueOf(config.getProperty("mapname", "63240000"));
 			try {
 				String mapname = String.format("%08d", mapName + 10000000);
-				Map map = Map.createMap(mapname, fileOutputDir, params, mapname, Sort.defaultSort(1252));
+				Map map = Map.createMap(mapname, fileOutputDir, params, mapname, SrtTextReader.sortForCodepage(1252));
 				builder.makeMap(map, dest);
 				map.close();
 			}
@@ -367,7 +371,7 @@ public abstract class DEM {
 		double min;
 		double max;
 
-		final ArrayList<Isoline> isolines = new ArrayList<Isoline>();
+		final ArrayList<Isoline> isolines = new ArrayList<>();
 
 		class Isoline {
 			final int id;
@@ -377,7 +381,7 @@ public abstract class DEM {
 			private Isoline(double level) {
 				this.level = level;
 				id = lastId++;
-				points = new ArrayList<Coord>();
+				points = new ArrayList<>();
 			}
 
 			private class Edge implements Brent.Function {
@@ -826,7 +830,7 @@ public abstract class DEM {
 
 	private static class DEMMapDataSource extends MapperBasedMapDataSource implements LoadableMapDataSource {
 		final LoadableMapDataSource parent;
-		final List<String> copyright = new ArrayList<String>();
+		final List<String> copyright = new ArrayList<>();
 
 		DEMMapDataSource(LoadableMapDataSource parent, EnhancedProperties props) {
 			this.parent = parent;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
index 15b068f..6892f4f 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
@@ -257,7 +257,7 @@ public final class CoastlineFileLoader {
 		}
 
 		@Override
-		public Iterable<Entry<String, String>> getEntryIteratable() {
+		public Iterable<Entry<String, String>> getTagEntryIterator() {
 			return Collections.singletonMap("natural", "coastline").entrySet();
 		}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java b/src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java
index 914ae38..2a80ce9 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java
@@ -26,15 +26,15 @@ import uk.me.parabola.imgfmt.app.Coord;
 public class CoordPOI extends Coord {
 	private Node node;
 	private boolean used;
+	private boolean convertToViaInRouteRestriction;
 
 	/**
-	 * Construct from co-ordinates that are already in map-units.
-	 *
-	 * @param latitude The latitude in map units.
-	 * @param longitude The longitude in map units.
+	 * Construct from other coord instance, copies the lat/lon values in high precision,
+	 * nothing else.
+ 
 	 */
-	public CoordPOI(int latitude, int longitude) {
-		super(latitude, longitude);
+	public CoordPOI(Coord co) {
+		super(co);
 	}
 
 	public Node getNode() {
@@ -51,4 +51,19 @@ public class CoordPOI extends Coord {
 	public boolean isUsed() {
 		return used;
 	}
+
+	/** 
+	 * @param b true means: Convert the access restriction coded in the node to a via
+	 * node in an route restriction. 
+	 */
+	public void setConvertToViaInRouteRestriction(boolean b) {
+		this.convertToViaInRouteRestriction = b;
+	}
+	
+	/**
+	 * @return true if the node should be converted 
+	 */
+	public boolean getConvertToViaInRouteRestriction(){
+		return convertToViaInRouteRestriction;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
index 0a1a800..e6a77d0 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
@@ -22,7 +22,7 @@ import java.util.Map;
 /**
  * Superclass of the node, segment and way OSM elements.
  */
-public abstract class Element implements Iterable<String> {
+public abstract class Element {
 	private Tags tags;
 	private long id;
 
@@ -43,26 +43,53 @@ public abstract class Element implements Iterable<String> {
 		tags.put(key, val);
 	}
 
+	/**
+	 * Add a tag to the way.  Some tags are recognised separately and saved in
+	 * separate fields.
+	 *
+	 * @param tagKey The tag id created by TagDict
+	 * @param val Its value.
+	 */
+	public void addTag(short tagKey, String val) {
+		if (tags == null)
+			tags = new Tags();
+		tags.put(tagKey, val);
+	}
+
 	public String getTag(String key) {
 		if (tags == null)
 			return null;
 		return tags.get(key);
 	}
+	public String getTag(short tagKey) {
+		if (tags == null)
+			return null;
+		return tags.get(tagKey);
+	}
+
 
-	public void deleteTag(String tagname) {
+	public String deleteTag(String tagname) {
+		String old = null;
 		if(tags != null) {
-			tags.remove(tagname);
+			old = tags.remove(tagname);
 			if (tags.size() == 0) {
 				tags = null;
 			}
+			
 		}
+		return old;
 	}
 
-	public Iterator<String> iterator() {
-		if (tags == null) 
-			return Collections.<String>emptyList().iterator();
-
-		return tags.iterator();
+	public String deleteTag(short tagKey) {
+		String old = null;
+		if(tags != null) {
+			old = tags.remove(tagKey);
+			if (tags.size() == 0) {
+				tags = null;
+			}
+			
+		}
+		return old;
 	}
 
 	/**
@@ -76,8 +103,23 @@ public abstract class Element implements Iterable<String> {
 	 * @param s tag name
 	 * @return <code>true</code> if the tag value is a boolean tag with a "positive" value
 	 */
-	public boolean isBoolTag(String s) {
-		String val = getTag(s);
+	public boolean tagIsLikeYes(String s) {
+		return tagIsLikeYes(TagDict.getInstance().xlate(s));
+	}
+
+	/**
+	 * Retrieves if the given tag has a "positive" boolean value which means its value is
+	 * one of
+	 * <ul>
+	 * <li><code>true</code></li>
+	 * <li><code>yes</code></li>
+	 * <li><code>1</code></li>
+	 * </ul>
+	 * @param tagKey tag id returned by TagDict
+	 * @return <code>true</code> if the tag value is a boolean tag with a "positive" value
+	 */
+	public boolean tagIsLikeYes(short tagKey) {
+		String val = getTag(tagKey);
 		if (val == null)
 			return false;
 
@@ -98,8 +140,23 @@ public abstract class Element implements Iterable<String> {
 	 * @param s tag name
 	 * @return <code>true</code> if the tag value is a boolean tag with a "negative" value
 	 */
-	public boolean isNotBoolTag(String s) {
-		String val = getTag(s);
+	public boolean tagIsLikeNo(String s) {
+		return tagIsLikeNo(TagDict.getInstance().xlate(s));
+	}
+	
+	/**
+	 * Retrieves if the given tag has a "negative" boolean value which means its value is
+	 * one of
+	 * <ul>
+	 * <li><code>false</code></li>
+	 * <li><code>no</code></li>
+	 * <li><code>0</code></li>
+	 * </ul>
+	 * @param tagKey tag id returned by TagDict
+	 * @return <code>true</code> if the tag value is a boolean tag with a "negative" value
+	 */
+	public boolean tagIsLikeNo(short tagKey) {
+		String val = getTag(tagKey);
 		if (val == null)
 			return false;
 
@@ -162,13 +219,29 @@ public abstract class Element implements Iterable<String> {
 		tags = null;
 	}
 
-	public Iterable<Map.Entry<String, String>> getEntryIteratable() {
+	/**
+	 * @return a Map iterator for the key + value pairs  
+	 */
+	
+	public Iterable<Map.Entry<String, String>> getTagEntryIterator() {
 		return new Iterable<Map.Entry<String, String>>() {
 			public Iterator<Map.Entry<String, String>> iterator() {
 				if (tags == null)
-					return Collections.<String, String>emptyMap().entrySet().iterator();
-				else
-					return tags.entryIterator();
+					return Collections.emptyIterator();
+				return tags.entryIterator();
+			}
+		};
+	}
+
+	/**
+	 * @return a Map iterator for the key + value pairs  
+	 */
+	public Iterable<Map.Entry<Short, String>> getFastTagEntryIterator() {
+		return new Iterable<Map.Entry<Short, String>>() {
+			public Iterator<Map.Entry<Short, String>> iterator() {
+				if (tags == null)
+					return Collections.emptyIterator();
+				return tags.entryShortIterator();
 			}
 		};
 	}
@@ -185,4 +258,15 @@ public abstract class Element implements Iterable<String> {
 		// Can be implemented in subclasses
 		throw new UnsupportedOperationException("unsupported element copy");
 	}
+	
+	public String getDebugName() {
+		String name = getName();
+		if(name == null)
+			name = getTag("ref");
+		if(name == null)
+			name = "";
+		else
+			name += " ";
+		return name + "(OSM id " + getId() + ")";
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
index fedca8e..1517d32 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
@@ -128,6 +128,15 @@ public class ElementSaver {
 	 */
 	public void addWay(Way way) {
 		wayMap.put(way.getId(), way);
+		/*
+		Way old = wayMap.put(way.getId(), way);
+		if (old != null){
+			if (old == way)
+				log.error("way",way.toBrowseURL(),"was added again");
+			else 
+				log.error("duplicate way",way.toBrowseURL(),"replaces previous way");
+		}
+		*/
 	}
 
 	/**
@@ -141,11 +150,11 @@ public class ElementSaver {
 			if (type == null) {
 			} else if ("multipolygon".equals(type) || "boundary".equals(type)) {
 				rel = createMultiPolyRelation(rel); 
-			} else if("restriction".equals(type)) {
+			} else if("restriction".equals(type) || type.startsWith("restriction:")) {
 				if (ignoreTurnRestrictions)
 					rel = null;
-				else if (rel.getTag("restriction") == null)
-					log.warn("ignoring unspecified restriction " + rel.toBrowseURL());
+				else if (rel.getTag("restriction") == null && rel.getTagsWithPrefix("restriction:", false).isEmpty())
+					log.warn("ignoring unspecified/unsupported restriction " + rel.toBrowseURL());
 				else
 					rel = new RestrictionRelation(rel);
 			}
@@ -219,9 +228,11 @@ public class ElementSaver {
 		for (Relation r : relationMap.values())
 			converter.convertRelation(r);
 
+		short fixmeTagKey = TagDict.getInstance().xlate("fixme"); 
+		short fixmeTagKey2 = TagDict.getInstance().xlate("FIXME"); 
 		for (Node n : nodeMap.values()){
 			converter.convertNode(n);
-			if (n.getTag("fixme") != null || n.getTag("FIXME") != null){
+			if (n.getTag(fixmeTagKey) != null || n.getTag(fixmeTagKey2) != null){
 				n.getLocation().setFixme(true);
 			}
 		}
@@ -314,7 +325,12 @@ public class ElementSaver {
 		} else if (minLat == Utils.toMapUnit(180.0) && maxLat == Utils.toMapUnit(-180.0)) {
 			return new Area(0, 0, 0, 0);
 		} else {
-			return new Area(minLat, minLon, maxLat, maxLon);
+			// calculate an area that is slightly larger so that high precision coordinates
+			// are safely within the bbox.
+			return new Area(Math.max(Utils.toMapUnit(-90.0), minLat-1), 
+					Math.max(Utils.toMapUnit(-180.0), minLon-1),
+					Math.min(Utils.toMapUnit(90.0), maxLat+1),
+					Math.min(Utils.toMapUnit(180.0), maxLon+1)); 
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java
index 2ea1a7a..e67ead6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java
@@ -39,7 +39,11 @@ public class FakeIdGenerator {
 	 * @return a unique id
 	 */
 	public static long makeFakeId() {
-		return fakeId.incrementAndGet();
+		long id = fakeId.incrementAndGet(); 
+//				if (4611686018427394038L == id){
+//					long dd = 4;
+//				}
+		return id;
 	}
 
 	public static boolean isFakeId(long id) {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/GType.java b/src/uk/me/parabola/mkgmap/reader/osm/GType.java
index d515446..5e10ae4 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/GType.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/GType.java
@@ -88,46 +88,6 @@ public class GType {
 		}
 	}
 
-	public GType(GType other) {
-		this.continueSearch = other.continueSearch;
-		this.defaultName = other.defaultName;
-		this.featureKind = other.featureKind;
-		this.maxLevel = other.maxLevel;
-		this.maxResolution = other.maxResolution;
-		this.minLevel = other.minLevel;
-		this.minResolution = other.minResolution;
-		this.propogateActionsOnContinue = other.propogateActionsOnContinue;
-		this.road = other.road;
-		this.roadClass = other.roadClass;
-		this.roadSpeed = other.roadSpeed;
-		this.type = other.type;
-	}
-	
-	/**
-	 * Copy all attributes and replace type to a non-routable one.
-	 * @param other
-	 * @param nonRoutableType
-	 */
-	public GType(GType other, String nonRoutableType) {
-		assert other.featureKind == FeatureKind.POLYLINE;
-		
-		this.continueSearch = other.continueSearch;
-		this.defaultName = other.defaultName;
-		this.featureKind = other.featureKind;
-		this.maxLevel = other.maxLevel;
-		this.maxResolution = other.maxResolution;
-		this.minLevel = other.minLevel;
-		this.minResolution = other.minResolution;
-		this.propogateActionsOnContinue = other.propogateActionsOnContinue;
-		this.road = false;
-		try {
-			this.type = Integer.decode(nonRoutableType);
-		} catch (NumberFormatException e) {
-			log.error("not numeric " + nonRoutableType);
-			throw new ExitException("non-numeric type in style file");
-		}
-	}
-
 	public FeatureKind getFeatureKind() {
 		return featureKind;
 	}
@@ -199,7 +159,9 @@ public class GType {
 		if (propogateActionsOnContinue)
 			fmt.format(" propagate");
 		sb.append(']');
-		return sb.toString();
+		String res = sb.toString();
+		fmt.close();
+		return res;
 	}
 
 	public int getMinLevel() {
@@ -255,10 +217,19 @@ public class GType {
 	/**
 	 * 
 	 * @param type the type value
-	 * @return true if the type is known as routable.
+	 * @return true if the type is can be used for routable lines
 	 */
 	public static boolean isRoutableLineType(int type){
-		return type >= 0x01 && type <= 0x13 || type == 0x1a || type == 0x1b || type == 0x16;
+		return type >= 0x01 && type <= 0x3f;
+	}
+	/**
+	 *  
+	 * @param type the type value
+	 * @return true if the type is known as routable in Garmin maps. These are 
+	 * known to cause routing errors if used for non-routable lines. 
+	 */
+	public static boolean isSpecialRoutableLineType(int type){
+		return type >= 0x01 && type <= 0x13 || type == 0x16 || type == 0x1b; 
 	}
 	
 	/**
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
index 374a0a2..a1b4ca8 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
@@ -32,13 +32,10 @@ import uk.me.parabola.util.EnhancedProperties;
 public class HighwayHooks extends OsmReadingHooksAdaptor {
 	private static final Logger log = Logger.getLogger(HighwayHooks.class);
 
-	private static final long CYCLEWAY_ID_OFFSET = 0x10000000;
-
-	private final List<Way> motorways = new ArrayList<Way>();
-	private final List<Node> exits = new ArrayList<Node>();
+	private final List<Way> motorways = new ArrayList<>();
+	private final List<Node> exits = new ArrayList<>();
 
 	private boolean makeOppositeCycleways;
-	private boolean makeCycleways;
 	private ElementSaver saver;
 	private boolean linkPOIsToWays;
 
@@ -68,19 +65,24 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
 		if(props.getProperty("make-all-cycleways", false)) {
-			makeOppositeCycleways = makeCycleways = true;
+			log.error("option make-all-cycleways is deprecated, please use make-opposite-cycleways");
+			makeOppositeCycleways = true;
 		}
 		else {
 			makeOppositeCycleways = props.getProperty("make-opposite-cycleways", false);
-			makeCycleways = props.getProperty("make-cycleways", false);
 		}
+		
 		linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
 		currentNodeInWay = null;
 
-		if (makeCycleways || makeOppositeCycleways) {
-			// need the additional two tags 
+		if (makeOppositeCycleways) {
+			// need the additional tags 
 			usedTags.add("cycleway");
 			usedTags.add("bicycle");
+			usedTags.add("oneway:bicycle");
+			usedTags.add("bicycle:oneway");
+			usedTags.add("cycleway:left");
+			usedTags.add("cycleway:right");
 		}
 		
 		// add addr:street and addr:housenumber if housenumber search is enabled
@@ -106,98 +108,89 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 	}
 
 	public void onCoordAddedToWay(Way way, long id, Coord co) {
+		if (!linkPOIsToWays)
+			return;
+			
 		currentNodeInWay = saver.getNode(id);
-
-		if (linkPOIsToWays) {
-			// if this Coord is also a POI, replace it with an
-			// equivalent CoordPOI that contains a reference to
-			// the POI's Node so we can access the POI's tags
-			if (!(co instanceof CoordPOI) && currentNodeInWay != null) {
-				// for now, only do this for nodes that have
-				// certain tags otherwise we will end up creating
-				// a CoordPOI for every node in the way
-				final String[] coordPOITags = { "access", "barrier", "highway" };
-				for (String cpt : coordPOITags) {
-					if (currentNodeInWay.getTag(cpt) != null) {
-						// the POI has one of the approved tags so
-						// replace the Coord with a CoordPOI
-						CoordPOI cp = new CoordPOI(co.getLatitude(), co.getLongitude());
-						saver.addPoint(id, cp);
-
-						// we also have to jump through hoops to
-						// make a new version of Node because we
-						// can't replace the Coord that defines
-						// its location
-						Node newNode = new Node(id, cp);
-						newNode.copyTags(currentNodeInWay);
-						saver.addNode(newNode);
-						// tell the CoordPOI what node it's
-						// associated with
-						cp.setNode(newNode);
-						co = cp;
-						// if original node is in exits, replace it
-						if (exits.remove(currentNodeInWay))
-							exits.add(newNode);
-						currentNodeInWay = newNode;
-						break;
-					}
+		// if this Coord is also a POI, replace it with an
+		// equivalent CoordPOI that contains a reference to
+		// the POI's Node so we can access the POI's tags
+		if (!(co instanceof CoordPOI) && currentNodeInWay != null) {
+			// for now, only do this for nodes that have
+			// certain tags otherwise we will end up creating
+			// a CoordPOI for every node in the way
+			final String[] coordPOITags = { "barrier", "highway" };
+			for (String cpt : coordPOITags) {
+				if (currentNodeInWay.getTag(cpt) != null) {
+					// the POI has one of the approved tags so
+					// replace the Coord with a CoordPOI
+					CoordPOI cp = new CoordPOI(co);
+					saver.addPoint(id, cp);
+
+					// we also have to jump through hoops to
+					// make a new version of Node because we
+					// can't replace the Coord that defines
+					// its location
+					Node newNode = new Node(id, cp);
+					newNode.copyTags(currentNodeInWay);
+					saver.addNode(newNode);
+					// tell the CoordPOI what node it's
+					// associated with
+					cp.setNode(newNode);
+					co = cp;
+					// if original node is in exits, replace it
+					if (exits.remove(currentNodeInWay))
+						exits.add(newNode);
+					currentNodeInWay = newNode;
+					break;
 				}
 			}
+		}
 
-			if (co instanceof CoordPOI) {
-				// flag this Way as having a CoordPOI so it
-				// will be processed later
-				way.addTag("mkgmap:way-has-pois", "true");
-				if (log.isInfoEnabled())
-					log.info("Linking POI", currentNodeInWay.toBrowseURL(), "to way at", co.toOSMURL());
-			}
+		if (co instanceof CoordPOI) {
+			// flag this Way as having a CoordPOI so it
+			// will be processed later
+			way.addTag("mkgmap:way-has-pois", "true");
+			if (log.isInfoEnabled())
+				log.info("Linking POI", currentNodeInWay.toBrowseURL(), "to way at", co.toOSMURL());
 		}
 	}
 
 	public void onAddWay(Way way) {
 		String highway = way.getTag("highway");
 		if (highway != null || "ferry".equals(way.getTag("route"))) {
-			boolean oneway = way.isBoolTag("oneway");
-
 			// if the way is a roundabout but isn't already
 			// flagged as "oneway", flag it here
 			if ("roundabout".equals(way.getTag("junction"))) {
 				if (way.getTag("oneway") == null) {
 					way.addTag("oneway", "yes");
 				}
-
 			}
 
-			String cycleway = way.getTag("cycleway");
-			if (makeOppositeCycleways && cycleway != null && !"cycleway".equals(highway) && oneway &&
-			   ("opposite".equals(cycleway) ||
-				"opposite_lane".equals(cycleway) ||
-				"opposite_track".equals(cycleway)))
-			{
-				// what we have here is a oneway street
-				// that allows bicycle traffic in both
-				// directions -- to enable bicycle routing
-				// in the reverse direction, we synthesise
-				// a cycleway that has the same points as
-				// the original way
-				Way cycleWay = makeCycleWay(way);
-				cycleWay.addTag("oneway", "no");
-
-			} else if (makeCycleways && cycleway != null && !"cycleway".equals(highway) &&
-					("track".equals(cycleway) ||
-					 "lane".equals(cycleway) ||
-					 "both".equals(cycleway) ||
-					 "left".equals(cycleway) ||
-					 "right".equals(cycleway)))
-			{
-				// what we have here is a highway with a
-				// separate track for cycles -- to enable
-				// bicycle routing, we synthesise a cycleway
-				// that has the same points as the original
-				// way
-				makeCycleWay(way);
-				if (way.getTag("bicycle") == null)
-					way.addTag("bicycle", "no");
+			if (makeOppositeCycleways && !"cycleway".equals(highway)){
+				String onewayTag = way.getTag("oneway");
+				boolean oneway = way.tagIsLikeYes("oneway");
+				if (!oneway & onewayTag != null && ("-1".equals(onewayTag) || "reverse".equals(onewayTag)))
+					oneway = true;
+				if (oneway){
+					String cycleway = way.getTag("cycleway");
+					boolean addCycleWay = false;
+					// we have a oneway street, check if it allows bicycles to travel in opposite direction
+					if ("no".equals(way.getTag("oneway:bicycle")) || "no".equals(way.getTag("bicycle:oneway"))){
+						addCycleWay = true;
+					}
+					else if (cycleway != null && ("opposite".equals(cycleway) || "opposite_lane".equals(cycleway) || "opposite_track".equals(cycleway))){
+						addCycleWay = true;	
+					}
+					else if ("opposite_lane".equals(way.getTag("cycleway:left")) || "opposite_lane".equals(way.getTag("cycleway:right"))){
+						addCycleWay = true;
+					}
+					else if ("opposite_track".equals(way.getTag("cycleway:left")) || "opposite_track".equals(way.getTag("cycleway:right"))){
+						addCycleWay = true;
+					}
+					if (addCycleWay)
+						way.addTag("mkgmap:make-cycle-way", "yes");
+				} 
 			}
 		}
 
@@ -205,42 +198,6 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 			motorways.add(way);
 	}
 
-	/**
-	 * Construct a cycleway that has the same points as an existing way.  Used for separate
-	 * cycle lanes.
-	 * @param way The original way.
-	 * @return The new way, which will have the same points and have suitable cycle tags.
-	 */
-	private Way makeCycleWay(Way way) {
-		long cycleWayId = way.getId() + CYCLEWAY_ID_OFFSET;
-		Way cycleWay = new Way(cycleWayId);
-		saver.addWay(cycleWay);
-
-		// this reverses the direction of the way but
-		// that isn't really necessary as the cycleway
-		// isn't tagged as oneway
-		List<Coord> points = way.getPoints();
-		//for (int i = points.size() - 1; i >= 0; --i)
-		//	cycleWay.addPoint(points.get(i));
-		for (Coord point : points)
-			cycleWay.addPoint(point);
-		
-		cycleWay.copyTags(way);
-
-		String name = way.getTag("name");
-		if(name != null)
-			name += " (cycleway)";
-		else
-			name = "cycleway";
-		cycleWay.addTag("name", name);
-		cycleWay.addTag("access", "no");
-		cycleWay.addTag("bicycle", "yes");
-		cycleWay.addTag("foot", "no");
-		cycleWay.addTag("mkgmap:synthesised", "yes");
-
-		return cycleWay;
-	}
-
 	public void end() {
 		finishExits();
 		exits.clear();
@@ -258,6 +215,7 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 				String ref = null;
 				Way motorway = null;
 				for (Way w : motorways) {
+					// uses an implicit call of Coord.equals()
 					if (w.getPoints().contains(e.getLocation())) {
 						motorway = w;
 						ref = w.getTag("ref");
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
index 3352de7..83f0f2e 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
@@ -32,6 +32,7 @@ import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.build.LocatorUtil;
 import uk.me.parabola.mkgmap.osmstyle.function.LengthFunction;
 import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.MultiHashMap;
 
 /**
  * Copies the destination tag from motorway_link and trunk_link ways to the 
@@ -52,6 +53,9 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	private HashSet<String> tagValues = new HashSet<String>(Arrays.asList(
 			"motorway_link", "trunk_link"));
 
+	/** Map way ids to its restriction relations so that the relations can easily be updated when the way is split. */
+	private MultiHashMap<Long, RestrictionRelation> restrictions = new MultiHashMap<>();
+	
 	private List<String> nameTags;
 
 	/** Maps which nodes contains to which ways */ 
@@ -136,6 +140,16 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 				}
 			}
 		}
+		
+		// get all restriction relations
+		// eventually they must be modified if one of its ways is split
+		for (Relation rel : saver.getRelations().values()) {
+			if (rel instanceof RestrictionRelation) {
+				RestrictionRelation rrel = (RestrictionRelation) rel;
+				for (Long wayId : rrel.getWayIds())
+					restrictions.add(wayId, rrel);
+			}
+		}
 	}
 	
 	
@@ -189,6 +203,43 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	}
 	
 	/**
+	 * Check all restriction relations and eventually update the relations to use
+	 * the split way if appropriate.
+	 * 
+	 * @param oldWay the original way
+	 * @param newWay the split part of the old way
+	 */
+	private void changeWayIdInRelations(Way oldWay, Way newWay) {
+		List<RestrictionRelation> wayRestrictions = restrictions.get(oldWay.getId());
+		if (wayRestrictions.isEmpty()) {
+			return;
+		}
+		if (oldWay.isViaWay())
+			newWay.setViaWay(true);
+		// create a copy because original list may be modified within the loop
+		for (RestrictionRelation rr : new ArrayList<>(wayRestrictions)) {
+			Coord lastPointNewWay = newWay.getPoints().get(0);
+			List<Coord> viaCoords = rr.getViaCoords();
+			for (Coord via : viaCoords){
+				if (via == lastPointNewWay) {
+					if (rr.isToWay(oldWay.getId())) {
+						log.debug("Change to-way",oldWay.getId(),"to",newWay.getId(),"for relation",rr.getId(),"at",lastPointNewWay.toOSMURL());
+						rr.replaceWay(oldWay.getId(), newWay.getId());
+						restrictions.removeMapping(oldWay.getId(), rr);
+						restrictions.add(newWay.getId(), rr);
+						
+					} else if (rr.isFromWay(oldWay.getId())){
+						log.debug("Change from-way",oldWay.getId(),"to",newWay.getId(),"for relation",rr.getId(),"at",lastPointNewWay.toOSMURL());
+						rr.replaceWay(oldWay.getId(), newWay.getId());
+						restrictions.removeMapping(oldWay.getId(), rr);
+						restrictions.add(newWay.getId(), rr);
+					} 
+				}
+			}
+		}
+	}
+	
+	/**
 	 * Cuts off at least minLength meter of the given way and returns the cut off way tagged
 	 * identical to the given way.   
 	 * @param w the way to be cut 
@@ -219,6 +270,9 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 
 				registerPointsOfWay(precedingWay);
 
+				// check and update relations so that they use the new way if appropriate
+				changeWayIdInRelations(w, precedingWay);
+				
 				log.debug("Cut way", w, "at existing point 1. New way:",
 						precedingWay);
 
@@ -280,6 +334,9 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 				removePointsFromWay(w, 0, i);
 				registerPointsOfWay(precedingWay);
 
+				// check and update relations so that they use the new way if appropriate
+				changeWayIdInRelations(w, precedingWay);
+
 				// return the split way
 				return precedingWay;			
 			} 		
@@ -304,7 +361,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 				if (dLat == 0 && dLon == 0) {
 					continue;
 				}
-				neighbours.add(new Coord(c.getLatitude()+dLat, c.getLongitude()+dLon));
+				neighbours.add(new Coord(c.getLatitude()+dLat, c.getLongitude()+dLon));//TODO: move to Coord class?
 			}
 		}
 		return neighbours;
@@ -474,11 +531,14 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 					// use link ways only
 					for (Way w : exitWays) {
 						destinationLinkWays.remove(w.getId());
-						
 						if (isNotOneway(w)) {
 							log.warn("Ignore way",w,"because it is not oneway");
 							continue;
 						}
+						if (w.isViaWay()){
+							log.warn("Ignore way",w,"because it is a via way in a restriction  relation");
+							continue;
+						}
 						
 						String highwayLinkTag = w.getTag("highway");
 						if (highwayLinkTag.endsWith("_link")) {
@@ -541,11 +601,14 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 			while (destinationLinkWays.isEmpty() == false) {
 				Way w = destinationLinkWays.values().iterator().next();
 				destinationLinkWays.remove(w.getId());
-
 				if (isNotOneway(w)) {
 					log.warn("Ignore way",w,"because it is not oneway");
 					continue;
 				}
+				if (w.isViaWay()){
+					log.warn("Ignore way",w,"because it is a via way in a restriction  relation");
+					continue;
+				}
 				
 				String highwayLinkTag = w.getTag("highway");
 				if (highwayLinkTag.endsWith("_link")) {
@@ -679,7 +742,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	 * @return <code>true</code> way is oneway
 	 */
 	private boolean isOnewayInDirection(Way w) {
-		if (w.isBoolTag("oneway")) {
+		if (w.tagIsLikeYes("oneway")) {
 			return true;
 		}
 		
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java b/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
index ecaa459..abef120 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
@@ -202,9 +202,9 @@ public class LocationHook extends OsmReadingHooksAdaptor {
 		}
 		else{
 			// tag the element with all tags referenced by the boundary
-			Iterator<Entry<String,String>> tagIter = tags.entryIterator();
+			Iterator<Entry<Short,String>> tagIter = tags.entryShortIterator();
 			while (tagIter.hasNext()) {
-				Entry<String,String> tag = tagIter.next();
+				Entry<Short,String> tag = tagIter.next();
 				if (elem.getTag(tag.getKey()) == null){
 					elem.addTag(tag.getKey(),tag.getValue());
 				}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
index ee40605..44ced84 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
@@ -13,10 +13,14 @@
 
 package uk.me.parabola.mkgmap.reader.osm;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
 import java.awt.Polygon;
 import java.awt.Rectangle;
 import java.awt.geom.Area;
 import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.util.ArrayList;
@@ -26,7 +30,9 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -37,8 +43,8 @@ import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.logging.Level;
-import java.util.LinkedHashMap;
 
+import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.util.Java2DConverter;
@@ -51,8 +57,7 @@ import uk.me.parabola.util.Java2DConverter;
  * @author WanMil
  */
 public class MultiPolygonRelation extends Relation {
-	private static final Logger log = Logger
-			.getLogger(MultiPolygonRelation.class);
+	private static final Logger log = Logger.getLogger(MultiPolygonRelation.class);
 
 	public static final String STYLE_FILTER_TAG = "mkgmap:stylefilter";
 	public static final String STYLE_FILTER_LINE = "polyline";
@@ -124,8 +129,12 @@ public class MultiPolygonRelation extends Relation {
 			if (log.isDebugEnabled()) {
 				log.debug(" ", role, el.toBrowseURL(), el.toTagString());
 			}
-			addElement(role, el);
-			roleMap.put(el.getId(), role);
+			if (roleMap.containsKey(el.getId()) )
+				log.warn("repeated member with id ", el.getId(), "in multipolygon relation",this.getId(),"is ignored");
+			else {
+				addElement(role, el);
+				roleMap.put(el.getId(), role);
+			}
 		}
 	}
 	
@@ -253,12 +262,13 @@ public class MultiPolygonRelation extends Relation {
 			roleMap.put(jw.getId(), getRole(orgSegment));
 			if (orgSegment.isClosed()) {
 				if (orgSegment.isComplete() == false) {
-					// the way is complete in planet but some points are missing in this tile
+					// the way is closed in planet but some points are missing in this tile
 					// we can close it artificially
 					if (log.isDebugEnabled())
 						log.debug("Close incomplete but closed polygon:",orgSegment);
 					jw.closeWayArtificially();
 				}
+				assert 	jw.hasIdenticalEndPoints() : "way is not closed";
 				joinedWays.add(jw);
 			} else {
 				unclosedWays.add(jw);
@@ -270,7 +280,7 @@ public class MultiPolygonRelation extends Relation {
 
 			// check if the current way is already closed or if it is the last
 			// way
-			if (joinWay.isClosed() || unclosedWays.isEmpty()) {
+			if (joinWay.hasIdenticalEndPoints() || unclosedWays.isEmpty()) {
 				joinedWays.add(joinWay);
 				continue;
 			}
@@ -289,7 +299,7 @@ public class MultiPolygonRelation extends Relation {
 			// joinWay to the beginning of the list
 			// (not optimal but understandable - can be optimized later)
 			for (JoinedWay tempWay : unclosedWays) {
-				if (tempWay.isClosed()) {
+				if (tempWay.hasIdenticalEndPoints()) {
 					continue;
 				}
 
@@ -348,7 +358,7 @@ public class MultiPolygonRelation extends Relation {
 			}
 
 			if (joined) {
-				if (joinWay.isClosed()) {
+				if (joinWay.hasIdenticalEndPoints()) {
 					// it's closed => don't process it again
 					joinedWays.add(joinWay);
 				} else if (unclosedWays.isEmpty()) {
@@ -376,7 +386,7 @@ public class MultiPolygonRelation extends Relation {
 	 */
 	protected void closeWays(ArrayList<JoinedWay> wayList) {
 		for (JoinedWay way : wayList) {
-			if (way.isClosed() || way.getPoints().size() < 3) {
+			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
 				continue;
 			}
 			Coord p1 = way.getPoints().get(0);
@@ -431,10 +441,12 @@ public class MultiPolygonRelation extends Relation {
 				// close the polygon
 				// the new way segment does not intersect the rest of the
 				// polygon
-				log.info("Closing way", way);
-				log.info("from", way.getPoints().get(0).toOSMURL());
-				log.info("to", way.getPoints().get(way.getPoints().size() - 1)
-						.toOSMURL());
+				if (log.isInfoEnabled()){
+					log.info("Closing way", way);
+					log.info("from", way.getPoints().get(0).toOSMURL());
+					log.info("to", way.getPoints().get(way.getPoints().size() - 1)
+							.toOSMURL());
+				} 
 				// mark this ways as artificially closed
 				way.closeWayArtificially();
 			}
@@ -460,14 +472,14 @@ public class MultiPolygonRelation extends Relation {
 		List<JoinedWay> unclosed = new ArrayList<JoinedWay>();
 
 		for (JoinedWay w : allWays) {
-			if (w.isClosed() == false) {
+			if (w.hasIdenticalEndPoints() == false) {
 				unclosed.add(w);
 			}
 		}
 		// try to connect ways lying outside or on the bbox
 		if (unclosed.size() >= 2) {
 			log.debug("Checking",unclosed.size(),"unclosed ways for connections outside the bbox");
-			Map<Coord, JoinedWay> outOfBboxPoints = new HashMap<Coord, JoinedWay>();
+			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<Coord, JoinedWay>();
 			
 			// check all ways for endpoints outside or on the bbox
 			for (JoinedWay w : unclosed) {
@@ -552,10 +564,10 @@ public class MultiPolygonRelation extends Relation {
 				} else {
 					log.debug("Connect", minCon.w1, "with", minCon.w2);
 
-					if (minCon.w1.getPoints().get(0).equals(minCon.c1)) {
+					if (minCon.w1.getPoints().get(0) == minCon.c1) {
 						Collections.reverse(minCon.w1.getPoints());
 					}
-					if (minCon.w2.getPoints().get(0).equals(minCon.c2) == false) {
+					if (minCon.w2.getPoints().get(0) != minCon.c2) {
 						Collections.reverse(minCon.w2.getPoints());
 					}
 
@@ -572,7 +584,7 @@ public class MultiPolygonRelation extends Relation {
 	
 	/**
 	 * Removes all ways non closed ways from the given list (
-	 * <code>{@link Way#isClosed()} == false</code>)
+	 * <code>{@link Way#hasIdenticalEndPoints()} == false</code>)
 	 * 
 	 * @param wayList
 	 *            list of ways
@@ -582,7 +594,7 @@ public class MultiPolygonRelation extends Relation {
 		boolean firstWarn = true;
 		while (it.hasNext()) {
 			JoinedWay tempWay = it.next();
-			if (!tempWay.isClosed()) {
+			if (!tempWay.hasIdenticalEndPoints()) {
 				// warn only if the way intersects the bounding box 
 				boolean inBbox = tempWay.intersects(bbox);
 				if (inBbox) {
@@ -1005,7 +1017,7 @@ public class MultiPolygonRelation extends Relation {
 
 						// all cut polygons have the same tags - copy them from the first polygon
 						Way outerWay = singularOuterPolygons.get(0);
-						for (Entry<String, String> tag : outerWay.getEntryIteratable()) {
+						for (Entry<String, String> tag : outerWay.getTagEntryIterator()) {
 							outerTags.put(tag.getKey(), tag.getValue());
 						}
 						outmostPolygonProcessing = false;
@@ -1276,7 +1288,7 @@ public class MultiPolygonRelation extends Relation {
 			return null;
 		}
 		
-		Rectangle outerBounds = areaData.outerArea.getBounds();
+		Rectangle2D outerBounds = areaData.outerArea.getBounds2D();
 		
 		if (areaData.innerAreas.size() == 1) {
 			// make it short if there is only one inner area
@@ -1302,7 +1314,7 @@ public class MultiPolygonRelation extends Relation {
 			// go through the inner polygon list and use all polygons that intersect the outer polygons bbox at the start
 			Collections.sort(innerStart, (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START: COMP_LAT_START));
 			for (Area anInnerStart : innerStart) {
-				if (axis.getStart(anInnerStart) <= axis.getStart(outerBounds)) {
+				if (axis.getStart30(anInnerStart) <= axis.getStart30(outerBounds)) {
 					// found a touching area
 					edgeCutPoint.addArea(anInnerStart);
 				} else {
@@ -1317,7 +1329,7 @@ public class MultiPolygonRelation extends Relation {
 			Collections.sort(innerStart, (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_STOP: COMP_LAT_STOP));
 			// go through the inner polygon list and use all polygons that intersect the outer polygons bbox at the stop
 			for (Area anInnerStart : innerStart) {
-				if (axis.getStop(anInnerStart) >= axis.getStop(outerBounds)) {
+				if (axis.getStop30(anInnerStart) >= axis.getStop30(outerBounds)) {
 					// found a touching area
 					edgeCutPoint.addArea(anInnerStart);
 				} else {
@@ -1411,8 +1423,8 @@ public class MultiPolygonRelation extends Relation {
 				initialCutData.innerAreas = new ArrayList<Area>(innerAreas
 						.size());
 				for (Area innerArea : innerAreas) {
-					if (outerArea.getBounds().intersects(
-						innerArea.getBounds())) {
+					if (outerArea.getBounds2D().intersects(
+						innerArea.getBounds2D())) {
 						initialCutData.innerAreas.add(innerArea);
 					}
 				}
@@ -1439,10 +1451,18 @@ public class MultiPolygonRelation extends Relation {
 			assert cutPoint.getNumberOfAreas() > 0 : "Number of cut areas == 0 in mp "+getId();
 			
 			// cut out the holes
-			for (Area cutArea : cutPoint.getAreas()) {
-				areaCutData.outerArea.subtract(cutArea);
+			if (cutPoint.getAreas().size() == 1)
+				areaCutData.outerArea.subtract(cutPoint.getAreas().get(0));
+			else {
+				// first combine the areas that should be subtracted
+				Path2D.Double path = new Path2D.Double();
+				for (Area cutArea : cutPoint.getAreas()) {
+					path.append(cutArea, false);
+				}
+				Area combinedCutAreas = new Area(path);
+				areaCutData.outerArea.subtract(combinedCutAreas);
 			}
-			
+				
 			if (areaCutData.outerArea.isEmpty()) {
 				// this outer area space can be abandoned
 				continue;
@@ -1475,17 +1495,16 @@ public class MultiPolygonRelation extends Relation {
 				}
 			} else {
 				// we need to cut the area into two halves to get singular areas
-				Rectangle r1 = cutPoint.getCutRectangleForArea(areaCutData.outerArea, true);
-				Rectangle r2 = cutPoint.getCutRectangleForArea(areaCutData.outerArea, false);
+				Rectangle2D r1 = cutPoint.getCutRectangleForArea(areaCutData.outerArea, true);
+				Rectangle2D r2 = cutPoint.getCutRectangleForArea(areaCutData.outerArea, false);
 
 				// Now find the intersection of these two boxes with the
 				// original polygon. This will make two new areas, and each
 				// area will be one (or more) polygons.
-				Area a1 = areaCutData.outerArea;
-				Area a2 = (Area) a1.clone();
-				a1.intersect(new Area(r1));
-				a2.intersect(new Area(r2));
-
+				Area a1 = new Area(r1); 
+				Area a2 = new Area(r2);
+				a1.intersect(areaCutData.outerArea);
+				a2.intersect(areaCutData.outerArea);
 				if (areaCutData.innerAreas.isEmpty()) {
 					finishedAreas.addAll(Java2DConverter.areaToSingularAreas(a1));
 					finishedAreas.addAll(Java2DConverter.areaToSingularAreas(a2));
@@ -1523,9 +1542,26 @@ public class MultiPolygonRelation extends Relation {
 		
 		// convert the java.awt.geom.Area back to the mkgmap way
 		List<Way> cuttedOuterPolygon = new ArrayList<Way>(finishedAreas.size());
+		Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>();
 		for (Area area : finishedAreas) {
 			Way w = singularAreaToWay(area, FakeIdGenerator.makeFakeId());
 			if (w != null) {
+				// make sure that equal coords are changed to identical coord instances
+				// this allows merging in the ShapeMerger
+				// TODO: maybe better merge here?
+				
+				int n = w.getPoints().size();
+				for (int i = 0; i < n; i++){
+					Coord p = w.getPoints().get(i);
+					long key = Utils.coord2Long(p);
+					Coord replacement = commonCoordMap.get(key);
+					if (replacement == null)
+						commonCoordMap.put(key, p);
+					else {
+						assert p.highPrecEquals(replacement);
+						w.getPoints().set(i, replacement);
+					}
+				}
 				w.copyTags(outerPolygon);
 				cuttedOuterPolygon.add(w);
 				if (log.isDebugEnabled()) {
@@ -1548,7 +1584,7 @@ public class MultiPolygonRelation extends Relation {
 	 */
 	private List<Area> createAreas(Way w, boolean clipBbox) {
 		Area area = Java2DConverter.createArea(w.getPoints());
-		if (clipBbox && !bboxArea.contains(area.getBounds())) {
+		if (clipBbox && !bboxArea.contains(area.getBounds2D())) {
 			// the area intersects the bounding box => clip it
 			area.intersect(bboxArea);
 		}
@@ -1593,12 +1629,12 @@ public class MultiPolygonRelation extends Relation {
 		if (element instanceof MultiPolygonRelation) {
 			// in case it is a multipolygon the TAGS_INCOMPLETE_TAG declares
 			// that the mp has additional tags removed by the file loader
-			if (element.isBoolTag(OsmHandler.TAGS_INCOMPLETE_TAG)) {
+			if (element.tagIsLikeYes(OsmHandler.TAGS_INCOMPLETE_TAG)) {
 				return true;
 			}
 		}
 		
-		for (Map.Entry<String, String> tagEntry : element.getEntryIteratable()) {
+		for (Map.Entry<String, String> tagEntry : element.getTagEntryIterator()) {
 			String tagName = tagEntry.getKey();
 			// all tags are style relevant
 			// except: type (for relations), mkgmap:* 
@@ -1668,6 +1704,10 @@ public class MultiPolygonRelation extends Relation {
 			JoinedWay potentialOuterPolygon = polygonList.get(rowIndex);
 			BitSet containsColumns = containsMatrix.get(rowIndex);
 			BitSet finishedCol = finishedMatrix.get(rowIndex);
+			
+			// the polygon need to be created only sometimes
+			// so use a lazy creation to improve performance
+			WayAndLazyPolygon lazyPotOuterPolygon = new WayAndLazyPolygon(potentialOuterPolygon);
 
 			// get all non calculated columns of the matrix
 			for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0
@@ -1679,9 +1719,8 @@ public class MultiPolygonRelation extends Relation {
 				if (potentialOuterPolygon.getBounds().intersects(
 						innerPolygon.getBounds()))
 				{
-					boolean contains = contains(potentialOuterPolygon,
-							innerPolygon);
-
+					boolean contains = contains(lazyPotOuterPolygon, innerPolygon);
+					
 					if (contains) {
 						containsColumns.set(colIndex);
 
@@ -1728,6 +1767,31 @@ public class MultiPolygonRelation extends Relation {
 		}
 	}
 
+	
+	/**
+	 * This is a helper class that creates a high precision polygon for a way 
+	 * on request only.
+	 */
+	private static class WayAndLazyPolygon {
+		private final JoinedWay way;
+		private Polygon polygon;
+		
+		public WayAndLazyPolygon(JoinedWay way) {
+			this.way = way;
+		}
+
+		public final JoinedWay getWay() {
+			return this.way;
+		}
+
+		public final Polygon getPolygon() {
+			if (this.polygon == null) {
+				this.polygon = Java2DConverter.createHighPrecPolygon(this.way.getPoints());
+			}
+			return this.polygon;
+		}
+	}
+	
 	/**
 	 * Checks if the polygon with polygonIndex1 contains the polygon with polygonIndex2.
 	 * 
@@ -1746,17 +1810,16 @@ public class MultiPolygonRelation extends Relation {
 	 *            a 2nd closed way
 	 * @return true if polygon1 contains polygon2
 	 */
-	private boolean contains(JoinedWay polygon1, JoinedWay polygon2) {
-		if (!polygon1.isClosed()) {
+	private boolean contains(WayAndLazyPolygon polygon1, JoinedWay polygon2) {
+		if (!polygon1.getWay().hasIdenticalEndPoints()) {
 			return false;
 		}
 		// check if the bounds of polygon2 are completely inside/enclosed the bounds
 		// of polygon1
-		if (!polygon1.getBounds().contains(polygon2.getBounds())) {
+		if (!polygon1.getWay().getBounds().contains(polygon2.getBounds())) {
 			return false;
 		}
 
-		Polygon p = Java2DConverter.createPolygon(polygon1.getPoints());
 		// check first if one point of polygon2 is in polygon1
 
 		// ignore intersections outside the bounding box
@@ -1765,18 +1828,18 @@ public class MultiPolygonRelation extends Relation {
 		boolean onePointContained = false;
 		boolean allOnLine = true;
 		for (Coord px : polygon2.getPoints()) {
-			if (p.contains(px.getLongitude(), px.getLatitude())) {
+			if (polygon1.getPolygon().contains(px.getHighPrecLon(), px.getHighPrecLat())){
 				// there's one point that is in polygon1 and in the bounding
 				// box => polygon1 may contain polygon2
 				onePointContained = true;
-				if (!locatedOnLine(px, polygon1.getPoints())) {
+				if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
 					allOnLine = false;
 					break;
 				}
 			} else if (bbox.contains(px)) {
 				// we have to check if the point is on one line of the polygon1
 				
-				if (!locatedOnLine(px, polygon1.getPoints())) {
+				if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
 					// there's one point that is not in polygon1 but inside the
 					// bounding box => polygon1 does not contain polygon2
 					//allOnLine = false;
@@ -1793,16 +1856,14 @@ public class MultiPolygonRelation extends Relation {
 			Coord p1 = null;
 			for (Coord p2 : polygon2.getPoints()) {
 				if (p1 != null) {
-					int mLat = p1.getLatitude()+(int)Math.round((p2.getLatitude()-p1.getLatitude())/2d);
-					int mLong = p1.getLongitude()+(int)Math.round((p2.getLongitude()-p1.getLongitude())/2d);
-					Coord pm = new Coord(mLat, mLong);
+					Coord pm = p1.makeBetweenPoint(p2, 0.5);
 					middlePoints2.add(pm);
 				}
 				p1 = p2;
 			}
 			
 			for (Coord px : middlePoints2) {
-				if (p.contains(px.getLongitude(), px.getLatitude())) {
+				if (polygon1.getPolygon().contains(px.getHighPrecLon(), px.getHighPrecLat())){
 					// there's one point that is in polygon1 and in the bounding
 					// box => polygon1 may contain polygon2
 					onePointContained = true;
@@ -1810,7 +1871,7 @@ public class MultiPolygonRelation extends Relation {
 				} else if (bbox.contains(px)) {
 					// we have to check if the point is on one line of the polygon1
 					
-					if (!locatedOnLine(px, polygon1.getPoints())) {
+					if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
 						// there's one point that is not in polygon1 but inside the
 						// bounding box => polygon1 does not contain polygon2
 						return false;
@@ -1824,7 +1885,7 @@ public class MultiPolygonRelation extends Relation {
 			return false;
 		}
 		
-		Iterator<Coord> it1 = polygon1.getPoints().iterator();
+		Iterator<Coord> it1 = polygon1.getWay().getPoints().iterator();
 		Coord p1_1 = it1.next();
 
 		while (it1.hasNext()) {
@@ -1889,7 +1950,7 @@ public class MultiPolygonRelation extends Relation {
 					&& linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
 				
 				if (intersects) {
-					if ((polygon1.isClosedArtificially() && !it1.hasNext())
+					if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
 							|| (polygon2.isClosedArtificially() && !it2.hasNext())) {
 						// don't care about this intersection
 						// one of the polygons is closed by this mp code and the
@@ -1910,7 +1971,7 @@ public class MultiPolygonRelation extends Relation {
 						// store them in the intersection polygons set
 						// the error message will be printed out in the end of
 						// the mp handling
-						intersectingPolygons.add(polygon1);
+						intersectingPolygons.add(polygon1.getWay());
 						intersectingPolygons.add(polygon2);
 						return false;
 					}
@@ -1935,7 +1996,7 @@ public class MultiPolygonRelation extends Relation {
 	private boolean locatedOnLine(Coord p, List<Coord> points) {
 		Coord cp1 = null;
 		for (Coord cp2 : points) {
-			if (p.equals(cp2)) {
+			if (p.highPrecEquals(cp2)) { 
 				return true;
 			}
 
@@ -1944,27 +2005,23 @@ public class MultiPolygonRelation extends Relation {
 					// first init
 					continue;
 				}
-
-				if (p.getLongitude() < Math.min(cp1.getLongitude(), cp2
-						.getLongitude())) {
+				
+				if (p.getHighPrecLon() < Math.min(cp1.getHighPrecLon(), cp2.getHighPrecLon())) {
 					continue;
 				}
-				if (p.getLongitude() > Math.max(cp1.getLongitude(), cp2
-						.getLongitude())) {
+				if (p.getHighPrecLon() > Math.max(cp1.getHighPrecLon(), cp2.getHighPrecLon())) {
 					continue;
 				}
-				if (p.getLatitude() < Math.min(cp1.getLatitude(), cp2
-						.getLatitude())) {
+				if (p.getHighPrecLat() < Math.min(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
 					continue;
 				}
-				if (p.getLatitude() > Math.max(cp1.getLatitude(), cp2
-						.getLatitude())) {
+				if (p.getHighPrecLat() > Math.max(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
 					continue;
 				}
 
-				double dist = Line2D.ptSegDistSq(cp1.getLongitude(), cp1
-						.getLatitude(), cp2.getLongitude(), cp2.getLatitude(),
-					p.getLongitude(), p.getLatitude());
+				double dist = Line2D.ptSegDistSq(cp1.getHighPrecLon(), cp1.getHighPrecLat(),
+						cp2.getHighPrecLon(), cp2.getHighPrecLat(),
+						p.getHighPrecLon(), p.getHighPrecLat());
 
 				if (dist <= OVERLAP_TOLERANCE_DISTANCE) {
 					log.debug("Point", p, "is located on line between", cp1, "and",
@@ -2106,8 +2163,8 @@ public class MultiPolygonRelation extends Relation {
 		for (Way orgWay : fakeWay.getOriginalWays()) {
 			log.log(logLevel, " Way",orgWay.getId(),"is composed of other artificial ways. Details:");
 			log.log(logLevel, "  Start:",orgWay.getPoints().get(0).toOSMURL());
-			if (orgWay.isClosed()) {
-				// the way is closed so start==end - log the point in the middle of the way
+			if (orgWay.hasEqualEndPoints()) {
+				// the way is closed so start and end are equal - log the point in the middle of the way
 				int mid = orgWay.getPoints().size()/2;
 				log.log(logLevel, "  Mid:  ",orgWay.getPoints().get(mid).toOSMURL());
 			} else {
@@ -2120,7 +2177,7 @@ public class MultiPolygonRelation extends Relation {
 		Map<String, String> tags;
 		if (hasStyleRelevantTags(this)) {
 			tags = new HashMap<String, String>();
-			for (Entry<String, String> relTag : getEntryIteratable()) {
+			for (Entry<String, String> relTag : getTagEntryIterator()) {
 				tags.put(relTag.getKey(), relTag.getValue());
 			}
 		} else {
@@ -2162,7 +2219,7 @@ public class MultiPolygonRelation extends Relation {
 	 *            a joined way
 	 */
 	private void removeTagsInOrgWays(Element tagElement, JoinedWay way) {
-		for (Entry<String, String> tag : tagElement.getEntryIteratable()) {
+		for (Entry<String, String> tag : tagElement.getTagEntryIterator()) {
 			removeTagInOrgWays(way, tag.getKey(), tag.getValue());
 		}
 	}
@@ -2268,20 +2325,21 @@ public class MultiPolygonRelation extends Relation {
 	 * @return the size of the area (unitless)
 	 */
 	public static double calcAreaSize(List<Coord> polygon) {
-		if (polygon.size() < 4 || polygon.get(0).equals(polygon.get(polygon.size()-1)) == false) {
-			return 0;
+		if (polygon.size() < 4 || polygon.get(0) != polygon.get(polygon.size()-1)) {
+			return 0; // line or not closed
 		}
-		double area = 0;
+		long area = 0;
 		Iterator<Coord> polyIter = polygon.iterator();
 		Coord c2 = polyIter.next();
 		while (polyIter.hasNext()) {
 			Coord c1 = c2;
 			c2 = polyIter.next();
-			area += (double) (c2.getLongitude() + c1.getLongitude())
-					* (c1.getLatitude() - c2.getLatitude());
+			area += (long) (c2.getHighPrecLon() + c1.getHighPrecLon())
+					* (c1.getHighPrecLat() - c2.getHighPrecLat());
 		}
-		area /= 2.0d;
-		return Math.abs(area);
+		//  convert from high prec to value in map units
+		double areaSize = (double) area / (2 * (1<<Coord.DELTA_SHIFT) * (1<<Coord.DELTA_SHIFT));  
+		return Math.abs(areaSize);
 	}
 
 
@@ -2418,7 +2476,7 @@ public class MultiPolygonRelation extends Relation {
 			for (Way way : ways) {
 				if (first) {
 					// the tags of the first way are copied completely 
-					for (Map.Entry<String, String> tag : way.getEntryIteratable()) {
+					for (Map.Entry<String, String> tag : way.getTagEntryIterator()) {
 						mergedTags.put(tag.getKey(), tag.getValue());
 					}
 					first = false;
@@ -2520,20 +2578,20 @@ public class MultiPolygonRelation extends Relation {
 		List<Area> innerAreas;
 	}
 
-	private static final int CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD = 1<<11;
-	private static final int CUT_POINT_CLASSIFICATION_BAD_THRESHOLD = 1<<8;
+	private static final int CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD = 1<<(11 + Coord.DELTA_SHIFT);
+	private static final int CUT_POINT_CLASSIFICATION_BAD_THRESHOLD = 1<< (8 + Coord.DELTA_SHIFT);
 	private static class CutPoint implements Comparable<CutPoint>{
-		private int startPoint = Integer.MAX_VALUE;
-		private int stopPoint = Integer.MIN_VALUE;
-		private Integer cutPoint = null;
+		private int startPoint30 = Integer.MAX_VALUE; // 30 bits precision map units
+		private int stopPoint30 = Integer.MIN_VALUE;  // 30 bits precision map units
+		private Integer cutPoint30 = null; // 30 bits precision map units
 		private final LinkedList<Area> areas;
 		private final Comparator<Area> comparator;
 		private final CoordinateAxis axis;
-		private Rectangle bounds;
-		private final Rectangle outerBounds;
+		private Rectangle2D bounds;
+		private final Rectangle2D outerBounds;
 		private Double minAspectRatio;
 
-		public CutPoint(CoordinateAxis axis, Rectangle outerBounds) {
+		public CutPoint(CoordinateAxis axis, Rectangle2D outerBounds) {
 			this.axis = axis;
 			this.outerBounds = outerBounds;
 			this.areas = new LinkedList<Area>();
@@ -2543,79 +2601,79 @@ public class MultiPolygonRelation extends Relation {
 		public CutPoint duplicate() {
 			CutPoint newCutPoint = new CutPoint(this.axis, this.outerBounds);
 			newCutPoint.areas.addAll(areas);
-			newCutPoint.startPoint = startPoint;
-			newCutPoint.stopPoint = stopPoint;
+			newCutPoint.startPoint30 = startPoint30;
+			newCutPoint.stopPoint30 = stopPoint30;
 			return newCutPoint;
 		}
 
 		private boolean isGoodCutPoint() {
 			// It is better if the cutting line is on a multiple of 2048. 
 			// Otherwise MapSource and QLandkarteGT paints gaps between the cuts
-			return getCutPoint() % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD == 0;
+			return getCutPoint30() % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD == 0;
 		}
 		
 		private boolean isBadCutPoint() {
-			int d1 = getCutPoint() - startPoint;
-			int d2 = stopPoint - getCutPoint();
+			int d1 = getCutPoint30() - startPoint30;
+			int d2 = stopPoint30 - getCutPoint30();
 			return Math.min(d1, d2) < CUT_POINT_CLASSIFICATION_BAD_THRESHOLD;
 		}
 		
 		private boolean isStartCut() {
-			return (startPoint <= axis.getStart(outerBounds));
+			return (startPoint30 <= axis.getStart30(outerBounds));
 		}
 		
 		private boolean isStopCut() {
-			return (stopPoint >= axis.getStop(outerBounds));
+			return (stopPoint30 >= axis.getStop30(outerBounds));
 		}
 		
 		/**
 		 * Calculates the point where the cut should be applied.
 		 * @return the point of cut
 		 */
-		public int getCutPoint() {
-			if (cutPoint != null) {
+		private int getCutPoint30() {
+			if (cutPoint30 != null) {
 				// already calculated => just return it
-				return cutPoint;
+				return cutPoint30;
 			}
 			
-			if (startPoint == stopPoint) {
+			if (startPoint30 == stopPoint30) {
 				// there is no choice => return the one possible point 
-				cutPoint = startPoint;
-				return cutPoint;
+				cutPoint30 = startPoint30;
+				return cutPoint30;
 			}
 			
 			if (isStartCut()) {
 				// the polygons can be cut out at the start of the sector
 				// thats good because the big polygon need not to be cut into two halves
-				cutPoint = startPoint;
-				return cutPoint;
+				cutPoint30 = startPoint30;
+				return cutPoint30;
 			}
 			
 			if (isStopCut()) {
 				// the polygons can be cut out at the end of the sector
 				// thats good because the big polygon need not to be cut into two halves
-				cutPoint = startPoint;
-				return cutPoint;
+				cutPoint30 = startPoint30;
+				return cutPoint30;
 			}
 			
 			// try to cut with a good aspect ratio so try the middle of the polygon to be cut
-			int midOuter = axis.getStart(outerBounds)+(axis.getStop(outerBounds) - axis.getStart(outerBounds)) / 2;
-			cutPoint = midOuter;
+			int midOuter30 = axis.getStart30(outerBounds)+(axis.getStop30(outerBounds) - axis.getStart30(outerBounds)) / 2;
+			cutPoint30 = midOuter30;
 
-			if (midOuter < startPoint) {
+			if (midOuter30 < startPoint30) {
 				// not possible => the start point is greater than the middle so correct to the startPoint
-				cutPoint = startPoint;
+				cutPoint30 = startPoint30;
 				
-				if (((cutPoint & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD) <= stopPoint) {
-					cutPoint = ((cutPoint & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD);
+				if (((cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD) <= stopPoint30) {
+					cutPoint30 = ((cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD);
 				}
 				
-			} else if (midOuter > stopPoint) {
+			} else if (midOuter30 > stopPoint30) {
 				// not possible => the stop point is smaller than the middle so correct to the stopPoint
-				cutPoint = stopPoint;
+				cutPoint30 = stopPoint30;
 
-				if ((cutPoint & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1))  >= startPoint) {
-					cutPoint = (cutPoint & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1));
+				if ((cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1))  >= startPoint30) {
+					cutPoint30 = (cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1));
 				}
 			}
 			
@@ -2623,69 +2681,70 @@ public class MultiPolygonRelation extends Relation {
 			// try to find a cut point that is a multiple of 2048 to 
 			// avoid that gaps are painted by MapSource and QLandkarteGT
 			// between the cutting lines
-			int cutMod = cutPoint % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD;
+			int cutMod = cutPoint30 % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD;
 			if (cutMod == 0) {
-				return cutPoint;
+				return cutPoint30;
 			}
 			
-			int cut1 = (cutMod > 0 ? cutPoint-cutMod : cutPoint  - CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD- cutMod);
-			if (cut1 >= startPoint && cut1 <= stopPoint) {
-				cutPoint = cut1;
-				return cutPoint;
+			int cut1 = (cutMod > 0 ? cutPoint30-cutMod : cutPoint30  - CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD- cutMod);
+			if (cut1 >= startPoint30 && cut1 <= stopPoint30) {
+				cutPoint30 = cut1;
+				return cutPoint30;
 			}
 			
-			int cut2 = (cutMod > 0 ? cutPoint + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD -cutMod : cutPoint - cutMod);
-			if (cut2 >= startPoint && cut2 <= stopPoint) {
-				cutPoint = cut2;
-				return cutPoint;
+			int cut2 = (cutMod > 0 ? cutPoint30 + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD -cutMod : cutPoint30 - cutMod);
+			if (cut2 >= startPoint30 && cut2 <= stopPoint30) {
+				cutPoint30 = cut2;
+				return cutPoint30;
 			}
 			
-			return cutPoint;
+			return cutPoint30;
 		}
 
-		public Rectangle getCutRectangleForArea(Area toCut, boolean firstRect) {
-			return getCutRectangleForArea(toCut.getBounds(), firstRect);
+		public Rectangle2D getCutRectangleForArea(Area toCut, boolean firstRect) {
+			return getCutRectangleForArea(toCut.getBounds2D(), firstRect);
 		}
 		
-		public Rectangle getCutRectangleForArea(Rectangle areaRect, boolean firstRect) {
+		public Rectangle2D getCutRectangleForArea(Rectangle2D areaRect, boolean firstRect) {
+			double cp = (double)  getCutPoint30() / (1<<Coord.DELTA_SHIFT);
 			if (axis == CoordinateAxis.LONGITUDE) {
-				int newWidth = getCutPoint()-areaRect.x;
+				double newWidth = cp-areaRect.getX();
 				if (firstRect) {
-					return new Rectangle(areaRect.x, areaRect.y, newWidth, areaRect.height); 
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), newWidth, areaRect.getHeight()); 
 				} else {
-					return new Rectangle(areaRect.x+newWidth, areaRect.y, areaRect.width-newWidth, areaRect.height); 
+					return new Rectangle2D.Double(areaRect.getX()+newWidth, areaRect.getY(), areaRect.getWidth()-newWidth, areaRect.getHeight()); 
 				}
 			} else {
-				int newHeight = getCutPoint()-areaRect.y;
+				double newHeight = cp-areaRect.getY();
 				if (firstRect) {
-					return new Rectangle(areaRect.x, areaRect.y, areaRect.width, newHeight); 
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), areaRect.getWidth(), newHeight); 
 				} else {
-					return new Rectangle(areaRect.x, areaRect.y+newHeight, areaRect.width, areaRect.height-newHeight); 
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY()+newHeight, areaRect.getWidth(), areaRect.getHeight()-newHeight); 
 				}
 			}
 		}
 		
-		public Collection<Area> getAreas() {
+		public List<Area> getAreas() {
 			return areas;
 		}
 
 		public void addArea(Area area) {
 			// remove all areas that do not overlap with the new area
-			while (!areas.isEmpty() && axis.getStop(areas.getFirst()) < axis.getStart(area)) {
+			while (!areas.isEmpty() && axis.getStop30(areas.getFirst()) < axis.getStart30(area)) {
 				// remove the first area
 				areas.removeFirst();
 			}
 
 			areas.add(area);
 			Collections.sort(areas, comparator);
-			startPoint = axis.getStart(Collections.max(areas,
+			startPoint30 = axis.getStart30(Collections.max(areas,
 				(axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START
 						: COMP_LAT_START)));
-			stopPoint = axis.getStop(areas.getFirst());
+			stopPoint30 = axis.getStop30(areas.getFirst());
 			
 			// reset the cached value => need to be recalculated the next time they are needed
 			bounds = null;
-			cutPoint = null;
+			cutPoint30 = null;
 			minAspectRatio = null;
 		}
 
@@ -2701,13 +2760,13 @@ public class MultiPolygonRelation extends Relation {
 		public double getMinAspectRatio() {
 			if (minAspectRatio == null) {
 				// first get the left/upper cut
-				Rectangle r1 = getCutRectangleForArea(outerBounds, true);
+				Rectangle2D r1 = getCutRectangleForArea(outerBounds, true);
 				double s1_1 = CoordinateAxis.LATITUDE.getSizeOfSide(r1);
 				double s1_2 = CoordinateAxis.LONGITUDE.getSizeOfSide(r1);
 				double ar1 = Math.min(s1_1, s1_2) / Math.max(s1_1, s1_2);
 
 				// second get the right/lower cut
-				Rectangle r2 = getCutRectangleForArea(outerBounds, false);
+				Rectangle2D r2 = getCutRectangleForArea(outerBounds, false);
 				double s2_1 = CoordinateAxis.LATITUDE.getSizeOfSide(r2);
 				double s2_2 = CoordinateAxis.LONGITUDE.getSizeOfSide(r2);
 				double ar2 = Math.min(s2_1, s2_2) / Math.max(s2_1, s2_2);
@@ -2767,8 +2826,8 @@ public class MultiPolygonRelation extends Relation {
 			}
 			
 			// prefer the larger area that is split
-			double ss1 = axis.getSizeOfSide(getBounds());
-			double ss2 = o.axis.getSizeOfSide(o.getBounds());
+			double ss1 = axis.getSizeOfSide(getBounds2D());
+			double ss2 = o.axis.getSizeOfSide(o.getBounds2D());
 			if (ss1-ss2 != 0)
 				return Double.compare(ss1,ss2); 
 
@@ -2777,18 +2836,18 @@ public class MultiPolygonRelation extends Relation {
 
 		}
 
-		private Rectangle getBounds() {
+		private Rectangle2D getBounds2D() {
 			if (bounds == null) {
 				// lazy init
-				bounds = new Rectangle();
+				bounds = new Rectangle2D.Double();
 				for (Area a : areas)
-					bounds.add(a.getBounds());
+					bounds.add(a.getBounds2D());
 			}
 			return bounds;
 		}
 
 		public String toString() {
-			return axis +" "+getNumberOfAreas()+" "+startPoint+" "+stopPoint+" "+getCutPoint();
+			return axis +" "+getNumberOfAreas()+" "+startPoint30+" "+stopPoint30+" "+getCutPoint30();
 		}
 	}
 
@@ -2801,30 +2860,34 @@ public class MultiPolygonRelation extends Relation {
 
 		private final boolean useX;
 
-		public int getStart(Area area) {
-			return getStart(area.getBounds());
+		public int getStart30(Area area) {
+			return getStart30(area.getBounds2D());
 		}
 
-		public int getStart(Rectangle rect) {
-			return (useX ? rect.x : rect.y);
+		public int getStart30(Rectangle2D rect) {
+			double val = (useX ? rect.getX() : rect.getY());
+			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
 		}
 
-		public int getStop(Area area) {
-			return getStop(area.getBounds());
+		public int getStop30(Area area) {
+			return getStop30(area.getBounds2D());
 		}
 
-		public int getStop(Rectangle rect) {
-			return (useX ? rect.x + rect.width : rect.y + rect.height);
+		public int getStop30(Rectangle2D rect) {
+			double val = (useX ? rect.getMaxX() : rect.getMaxY());
+			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
 		}
 		
-		public double getSizeOfSide(Rectangle rect) {
+		public double getSizeOfSide(Rectangle2D rect) {
 			if (useX) {
-				Coord c1 = new Coord(rect.y, getStart(rect));
-				Coord c2 = new Coord(rect.y, getStop(rect));
+				int lat30 = (int)Math.round(rect.getY() * (1<<Coord.DELTA_SHIFT));
+				Coord c1 = Coord.makeHighPrecCoord(lat30, getStart30(rect));
+				Coord c2 = Coord.makeHighPrecCoord(lat30, getStop30(rect));
 				return c1.distance(c2);
 			} else {
-				Coord c1 = new Coord(getStart(rect), rect.x );
-				Coord c2 = new Coord(getStop(rect), rect.x );
+				int lon30 = (int)Math.round(rect.getX() * (1<<Coord.DELTA_SHIFT));
+				Coord c1 = Coord.makeHighPrecCoord(getStart30(rect), lon30);
+				Coord c2 = Coord.makeHighPrecCoord(getStop30(rect), lon30);
 				return c1.distance(c2);
 			}
 		}
@@ -2855,16 +2918,16 @@ public class MultiPolygonRelation extends Relation {
 			}
 
 			if (startPoint) {
-				int cmp = axis.getStart(o1) - axis.getStart(o2);
+				int cmp = axis.getStart30(o1) - axis.getStart30(o2);
 				if (cmp == 0) {
-					return axis.getStop(o1) - axis.getStop(o2);
+					return axis.getStop30(o1) - axis.getStop30(o2);
 				} else {
 					return cmp;
 				}
 			} else {
-				int cmp = axis.getStop(o1) - axis.getStop(o2);
+				int cmp = axis.getStop30(o1) - axis.getStop30(o2);
 				if (cmp == 0) {
-					return axis.getStart(o1) - axis.getStart(o2);
+					return axis.getStart30(o1) - axis.getStart30(o2);
 				} else {
 					return cmp;
 				}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
index 5ee23d5..f1374ec 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
@@ -169,7 +169,7 @@ public class OsmHandler {
 	 * @param way The way that was read.
 	 */
 	protected void endWay(Way way) {
-		way.setClosed(firstNodeRef == lastNodeRef);
+		way.setClosedInOSM(firstNodeRef == lastNodeRef);
 		way.setComplete(!missingNodeRef);
 
 		saver.addWay(way);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
index b78f4a3..c09a21f 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
@@ -53,12 +53,12 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	private final OsmReadingHooks[] POSSIBLE_HOOKS = {
 			new SeaGenerator(),
 			new MultiPolygonFinishHook(),
+			new RelationStyleHook(), 
 			new LinkDestinationHook(),
 			new UnusedElementsRemoverHook(),
 			new RoutingHook(),
 			new HighwayHooks(),
 			new LocationHook(),
-			new RelationStyleHook(),
 			new POIGeneratorHook(),
 	};
 	protected OsmConverter converter;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
index 96f615e..22c7427 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
@@ -16,8 +16,8 @@ package uk.me.parabola.mkgmap.reader.osm;
 import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -69,9 +69,9 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 	private boolean poisToLines = false;
 	
 	/** Name of the bool tag that is set to true if a POI is created from an area */
-	public static final String AREA2POI_TAG = "mkgmap:area2poi";
-	public static final String LINE2POI_TAG = "mkgmap:line2poi";
-	public static final String LINE2POI_TYPE_TAG  = "mkgmap:line2poitype";
+	public static final short AREA2POI_TAG = TagDict.getInstance().xlate("mkgmap:area2poi");
+	public static final short LINE2POI_TAG = TagDict.getInstance().xlate("mkgmap:line2poi");
+	public static final short LINE2POI_TYPE_TAG  = TagDict.getInstance().xlate("mkgmap:line2poitype");
 	
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		poisToAreas = props.containsKey("add-pois-to-areas");
@@ -175,7 +175,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 	}
 	
 	private void addPOIsToWays() {
-		Map<Coord, Integer> labelCoords = new HashMap<Coord, Integer>(); 
+		Map<Coord, Integer> labelCoords = new IdentityHashMap<Coord, Integer>(); 
 		
 		// save all coords with one of the placement tags to a map
 		// so that ways use this coord as its labeling point
@@ -202,7 +202,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			}
 
 			// do not add POIs for polygons created by multipolygon processing
-			if (w.isBoolTag(MultiPolygonRelation.MP_CREATED_TAG)) {
+			if (w.tagIsLikeYes(MultiPolygonRelation.MP_CREATED_TAG)) {
 				if (log.isDebugEnabled())
 					log.debug("MP processed: Do not create POI for", w.toTagString());
 				continue;
@@ -210,7 +210,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			
 			
 			// check if it is an area
-			if (w.isClosed()) {
+			if (w.hasIdenticalEndPoints()) {
 				if (poisToAreas) {
 					addPOItoPolygon(w, labelCoords);
 					ways2POI++;
@@ -264,6 +264,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 		saver.addNode(poi);
 	}
 	
+	
 	private int addPOItoLine(Way line) {
 		Node startNode = createPOI(line, line.getPoints().get(0), LINE2POI_TAG);
 		startNode.addTag(LINE2POI_TYPE_TAG,"start");
@@ -274,9 +275,13 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 		saver.addNode(endNode);
 
 		int noPOIs = 2;
-		
+		Coord lastPoint = line.getPoints().get(0);
 		if (line.getPoints().size() > 2) {
 			for (Coord inPoint : line.getPoints().subList(1, line.getPoints().size()-1)) {
+				if (inPoint.equals(lastPoint)){
+					continue;
+				}
+				lastPoint = inPoint;
 				Node innerNode = createPOI(line, inPoint, LINE2POI_TAG);
 				innerNode.addTag(LINE2POI_TYPE_TAG,"inner");
 				saver.addNode(innerNode);
@@ -284,7 +289,6 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			}
 		}
 		
-		
 		// calculate the middle of the line
 		Coord prevC = null;
 		double sumDist = 0.0;
@@ -309,23 +313,22 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			} 
 			remMidDist -= nextDist;
 		}
-
+		
 		if (midPoint != null) {
 			Node midNode = createPOI(line, midPoint, LINE2POI_TAG);
 			midNode.addTag(LINE2POI_TYPE_TAG,"mid");
 			saver.addNode(midNode);
 			noPOIs++;
 		}
-
 		return noPOIs;
 
 	}
 
-	private Node createPOI(Element source, Coord poiCoord, String poiTypeTag) {
+	private static Node createPOI(Element source, Coord poiCoord, short poiTypeTagKey) {
 		Node poi = new Node(FakeIdGenerator.makeFakeId(), poiCoord);
 		poi.copyTags(source);
 		poi.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG);
-		poi.addTag(poiTypeTag, "true");
+		poi.addTag(poiTypeTagKey, "true");
 		if (log.isDebugEnabled()) {
 			log.debug("Create POI",poi.toTagString(),"from",source.getId(),source.toTagString());
 		}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
index c642dab..76138ae 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
@@ -13,6 +13,9 @@
 
 package uk.me.parabola.mkgmap.reader.osm;
 
+import java.util.List;
+
+import uk.me.parabola.mkgmap.build.LocatorUtil;
 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
 import uk.me.parabola.util.EnhancedProperties;
 
@@ -24,12 +27,14 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 
 	private Style style;
 	private ElementSaver saver;
-	
+	List<String> nameTagList;
+
 	public RelationStyleHook() {
 	}
 
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
+		nameTagList = LocatorUtil.getNameTags(props);
 		style = StyleImpl.readStyle(props);
 		return super.init(saver, props);
 	}
@@ -37,7 +42,19 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 	public void end() {
 		Rule relationRules = style.getRelationRules();
 		for (Relation rel : saver.getRelations().values()) {
+			if (nameTagList != null){
+				for (String t : nameTagList) {
+					String val = rel.getTag(t);
+					if (val != null) {
+						rel.addTag("name", val);
+						break;
+					}
+				}
+			}			
 			relationRules.resolveType(rel, TypeResult.NULL_RESULT);
+			if (rel instanceof RestrictionRelation){
+				((RestrictionRelation) rel).eval(saver.getBoundingBox());
+			}
 		}
 		super.end();
 		
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
index 00fdaa1..4e7b6fe 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
@@ -14,38 +14,57 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
 
+import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
-import uk.me.parabola.imgfmt.app.net.RouteRestriction;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.MapCollector;
 
+
 /**
  * Representation of an OSM turn restriction
  *
- * @author Mark Burton
+ * @author Mark Burton, GerdP
  */
 public class RestrictionRelation extends Relation {
-
     private static final Logger log = Logger.getLogger(RestrictionRelation.class);
 
-    private Way fromWay;
-    private Way toWay;
-    private Way viaWay;
+    private List<Long> fromWayIds = new ArrayList<>(2);
+    private List<Long> toWayIds = new ArrayList<>(2);
+    private List<Long> viaWayIds = new ArrayList<>(2);
+    private List<Coord> viaPoints = new ArrayList<>(2);
     private Coord viaCoord;
-    private final String restriction;
-
-    private CoordNode fromNode;
-    private CoordNode toNode;
-    private CoordNode viaNode;
-    private final List<CoordNode> otherNodes = new ArrayList<CoordNode>();
+    private String restriction;
 	private byte exceptMask;
+	private char dirIndicator; 
     private String messagePrefix;
-
+    private boolean valid;
+    private boolean evalWasCalled;
+    
+	// These tags are not loaded by default but if they exist issue a warning
+	private final static String[] unsupportedTags = { "day_on", "day_off", "hour_on", "hour_off" };
+	
+	private final static byte DEFAULT_EXCEPT_MASK = AccessTagsAndBits.FOOT  | AccessTagsAndBits.EMERGENCY;
+	
+	private final static List<String> supportedRestrictions = Arrays.asList(
+		"no_right_turn", "no_left_turn", "no_u_turn", "no_straight_on", 
+		"only_right_turn", "only_left_turn", "only_straight_on", 
+		"no_entry", "no_exit"	     
+	);
+	
 	/**
 	 * Create an instance based on an existing relation.  We need to do
 	 * this because the type of the relation is not known until after all
@@ -53,263 +72,499 @@ public class RestrictionRelation extends Relation {
 	 * @param other The relation to base this one on.
 	 */
 	public RestrictionRelation(Relation other) {
-
 		setId(other.getId());
-
-		final String browseURL = toBrowseURL();
-
-		messagePrefix = "Turn restriction " + browseURL + " ";
-
+		messagePrefix = "Turn restriction " + toBrowseURL();
+		copyTags(other);
 		for (Map.Entry<String, Element> pair : other.getElements()) {
+			addElement(pair.getKey(), pair.getValue());
+		}
+	}
+	
+	/**
+	 * The evaluation should happen after style processing.
+	 * Normally this is called from the {@link RelationStyleHook}
+	 * Performs also diverse plausibility checks.
+	 * @param bbox tile boundary
+	 */
+	public void eval(Area bbox){
+		if (evalWasCalled){
+			log.error(messagePrefix,"internal error: eval() was already called");
+			fromWayIds.clear();
+			toWayIds.clear();
+			viaWayIds.clear();
+		}
+		evalWasCalled = true;
+		if (getTag("type") == null){
+			// style removed the tag
+			log.info(messagePrefix, "type tag was removed, relation is ignored");				
+			valid = false;
+			return;
+		}
+		
+	    List<Way> fromWays = new ArrayList<>();
+	    List<Way> toWays = new ArrayList<>();
+	    List<Way> viaWays = new ArrayList<>();
+		final String browseURL = toBrowseURL();
+		valid = true;
+		// find out what kind of restriction we have and to which vehicles it applies
+		exceptMask = DEFAULT_EXCEPT_MASK;
+		String specifc_type = getTag("restriction");
+		int count_unknown = 0;
+		Map<String, String> vehicles = getTagsWithPrefix("restriction:", true);
+		if (vehicles.isEmpty() == false){
+			exceptMask = (byte) 0xff;
+			Iterator<Entry<String, String>> iter = vehicles.entrySet().iterator();
+			while (iter.hasNext()){
+				Map.Entry<String, String> entry = iter.next();
+				String vehicle = entry.getKey();
+				if (setExceptMask(vehicle, false) == false)
+					count_unknown++;
+				if (specifc_type == null)
+					specifc_type = entry.getValue();
+				else if (specifc_type.equals(entry.getValue()) == false){
+					log.warn(messagePrefix, "is invalid, it specifies different kinds of turns");
+					valid = false;
+					break;
+				}
+			}
+			if (valid && vehicles.size() == count_unknown){
+				log.warn(messagePrefix, "no supported vehicle in turn restriction");				
+				valid = false;
+				return;
+			}
+		}
+		if (specifc_type == null){
+			// style removed the tag
+			log.info(messagePrefix, "no valid restriction tag found");				
+			valid = false;
+			return;
+		}
+		restriction = specifc_type.trim();
+		
+		messagePrefix = "Turn restriction (" + restriction + ") " + browseURL;
+		if (supportedRestrictions.contains(restriction) == false){
+			log.warn(messagePrefix, "ignoring unsupported restriction type '" + restriction + "'");
+			valid = false;
+			return;
+		}
+		
+		String dirInfo = "";
+		if (restriction.contains("left"))
+			dirInfo += "l";
+		if (restriction.contains("right"))
+			dirInfo += "r";
+		if (restriction.contains("straight"))
+			dirInfo += "s";
+		if (restriction.endsWith("u_turn"))
+			dirInfo += "u";
+		if (dirInfo.length() > 1){
+			log.warn(messagePrefix, "ignoring unsupported restriction type '" + restriction + "'");
+			valid = false;
+			return;
+		} else if (dirInfo.length() == 1){
+			dirIndicator = dirInfo.charAt(0);
+		} else 
+			dirIndicator = '?';
+		
+		String type = getTag("type");
+		if (type.startsWith("restriction:")){
+			exceptMask = (byte) 0xff;
+			String vehicle = type.substring("restriction:".length());
+			if (setExceptMask(vehicle, false) == false) {
+				log.warn(messagePrefix, "ignoring unsupported '" + vehicle + "' in turn restriction");
+				valid = false;
+				return;
+			}
+		}
+		
+		String except = getTag("except");
+		if(except != null) {
+			for(String vehicle : except.split("[,;]")) { // be nice
+				vehicle = vehicle.trim();
+				setExceptMask(vehicle, true);
+			}
+		}
+		
+		for (String unsupportedTag : unsupportedTags) {
+			if (getTag(unsupportedTag) != null) {
+				log.warn(messagePrefix, "ignoring unsupported '" + unsupportedTag + "' tag");
+			}
+		}
+		
+		// evaluate members
+		for (Map.Entry<String, Element> pair : getElements()) {
 			String role = pair.getKey();
 			Element el = pair.getValue();
-			addElement(role, el);
 
 			Coord location = null;
 
 			if(viaCoord != null)
 				location = viaCoord;
-			else if(fromWay != null && !fromWay.getPoints().isEmpty())
-				location = fromWay.getPoints().get(0);
-			else if(toWay != null && !toWay.getPoints().isEmpty())
-				location = toWay.getPoints().get(0);
+			else if(!fromWays.isEmpty() && !fromWays.get(0).getPoints().isEmpty())
+				location = fromWays.get(0).getPoints().get(0);
+			else if(!toWays.isEmpty() && !toWays.get(0).getPoints().isEmpty())
+				location = toWays.get(0).getPoints().get(0);
+			else if(!viaWays.isEmpty() && !viaWays.get(0).getPoints().isEmpty())
+				location = viaWays.get(0).getPoints().get(0);
 
 			if(location != null)
-				messagePrefix = "Turn restriction " + browseURL + " (at " + location.toOSMURL() + ") ";
+				messagePrefix = "Turn restriction (" + restriction + ") " + browseURL + " (at " + location.toOSMURL() + ")";
 
 			if("to".equals(role)) {
-				if(toWay != null) {
-					log.warn(messagePrefix + "has extra 'to' member " + el.toBrowseURL());
-				}
-				else if(!(el instanceof Way)) {
-					log.warn(messagePrefix + "'to' member " + el.toBrowseURL() + " is not a way but it should be");
+				if(!(el instanceof Way)) {
+					log.warn(messagePrefix, "'to' member", el.toBrowseURL(), "is not a way but it should be");
 				}
 				else if(((Way)el).getPoints().isEmpty()) {
-					log.warn(messagePrefix + "ignoring empty 'to' way " + el.toBrowseURL());
+					log.warn(messagePrefix, "ignoring empty 'to' way", el.toBrowseURL());
 				}
 				else
-					toWay = (Way)el;
+					toWays.add((Way)el);
 			}
 			else if("from".equals(role)) {
-				if(fromWay != null) {
-					log.warn(messagePrefix + "has extra 'from' member " + el.toBrowseURL());
-				}
-				else if(!(el instanceof Way)) {
-					log.warn(messagePrefix + "'from' member " + el.toBrowseURL() + " is not a way but it should be");
+				if(!(el instanceof Way)) {
+					log.warn(messagePrefix, "'from' member", el.toBrowseURL(), "is not a way but it should be");
 				}
 				else if(((Way)el).getPoints().isEmpty()) {
-					log.warn(messagePrefix + "ignoring empty 'from' way " + el.toBrowseURL());
+					log.warn(messagePrefix, "ignoring empty 'from' way", el.toBrowseURL());
 				}
 				else
-					fromWay = (Way)el;
+					fromWays.add((Way)el);
 			}
 			else if("via".equals(role)) {
-				if(viaCoord != null || viaWay != null) {
-					log.warn(messagePrefix + "has extra 'via' member " + el.toBrowseURL());
-				}
-				else if(el instanceof Node) {
-					viaCoord = ((Node)el).getLocation();
+				if(el instanceof Node) {
+					if (viaCoord != null){
+						log.warn(messagePrefix, "has extra 'via' node", el.toBrowseURL());
+						valid = false;						
+					} else
+						viaCoord = ((Node)el).getLocation();
 				}
 				else if(el instanceof Way) {
-					viaWay = (Way)el;
+					if (viaCoord != null){
+						log.warn(messagePrefix, "has extra 'via' way", el.toBrowseURL());
+						valid = false;						
+					} else
+						viaWays.add((Way)el);
 				}
 				else {
-					log.warn(messagePrefix + "'via' member " + el.toBrowseURL() + " is not a node or way");
+					log.warn(messagePrefix, "'via' member", el.toBrowseURL(), "is not a node or way");
 				}
 			}
 			else if("location_hint".equals(role)) {
 				// relax - we don't care about this
 			}
 			else {
-				log.warn(messagePrefix + "unknown member role '" + role + "'");
+				log.warn(messagePrefix, "unknown member role '" + role + "'");
 			}
 		}
 
-		copyTags(other);
-
-		restriction = getTag("restriction");
-
-		// These tags are not loaded by default but if they exist issue a warning
-		String[] unsupportedTags = {
-		    "day_on",
-		    "day_off",
-		    "hour_on",
-		    "hour_off" };
-		for (String unsupportedTag : unsupportedTags) {
-			if (getTag(unsupportedTag) != null) {
-				log.warn(messagePrefix + "ignoring unsupported '" + unsupportedTag + "' tag");
+		
+		if (!valid)
+			return;
+		
+		if ("no_entry".equals(restriction) == false){
+			if (fromWays.size() > 1){
+				log.warn(messagePrefix, "multiple 'from' members are only accepted for no_entry restrictions");
+				valid = false;
+				return;
 			}
 		}
-
-		String except = getTag("except");
-		if(except != null) {
-			for(String e : except.split("[,;]")) { // be nice
-				e = e.trim();
-				if(e.equals("motorcar") || e.equals("motorcycle"))
-					exceptMask |= RouteRestriction.EXCEPT_CAR;
-				else if(e.equals("psv") || e.equals("bus"))
-					exceptMask |= RouteRestriction.EXCEPT_BUS;
-				else if(e.equals("taxi"))
-					exceptMask |= RouteRestriction.EXCEPT_TAXI;
-				else if(e.equals("delivery") || e.equals("goods"))
-					exceptMask |= RouteRestriction.EXCEPT_DELIVERY;
-				else if(e.equals("bicycle"))
-					exceptMask |= RouteRestriction.EXCEPT_BICYCLE;
-				else if(e.equals("hgv") || e.equals("truck"))
-					exceptMask |= RouteRestriction.EXCEPT_TRUCK;
-				else
-					log.warn(messagePrefix + "ignoring unsupported vehicle class '" + e + "' in turn restriction exception");
+		if ("no_exit".equals(restriction) == false){
+			if (toWays.size() > 1){
+				log.warn(messagePrefix, "multiple 'to' members are only accepted for no_exit restrictions");
+				valid = false;
+				return;
 			}
-		}
-	}
-
-	public Way getFromWay() {
-		return fromWay;
-	}
-
-	public Way getToWay() {
-		return toWay;
-	}
-
-	public Coord getViaCoord() {
-		return viaCoord;
-	}
-
-	public void setFromNode(CoordNode fromNode) {
-		this.fromNode = fromNode;
-		log.debug(messagePrefix + restriction + " 'from' node is " + fromNode.toOSMURL());
-	}
-
-	public void setToNode(CoordNode toNode) {
-		this.toNode = toNode;
-		log.debug(messagePrefix + restriction + " 'to' node is " + toNode.toOSMURL());
-	}
-
-	public void setViaNode(CoordNode viaNode) {
-		if(this.viaNode == null)
-			log.debug(messagePrefix + restriction + " 'via' node is " + viaNode.toOSMURL());
-		else if(!this.viaNode.equals(viaNode))
-			log.warn(messagePrefix + restriction + " 'via' node redefined from " +
-					 this.viaNode.toOSMURL() + " to " + viaNode.toOSMURL());
-		this.viaNode = viaNode;
-	}
-
-	public void addOtherNode(CoordNode otherNode) {
-		otherNodes.add(otherNode);
-		log.debug(messagePrefix + restriction + " adding 'other' node " + otherNode.toOSMURL());
-	}
-
-	public boolean isValid() {
-		boolean result = true;
-
-		if(restriction == null) {
-			log.warn(messagePrefix + "lacks 'restriction' tag (e.g. no_left_turn)");
-			result = false;
-		}
-
-		if(fromWay == null) {
-			log.warn(messagePrefix + "lacks 'from' way");
-		}
-
-		if(toWay == null) {
-			log.warn(messagePrefix + "lacks 'to' way");
-		}
-
-		if(fromWay == null || toWay == null)
-			return false;
-
-		if(viaCoord == null && viaWay == null) {
+		} 
+		if (viaWays.isEmpty() && viaCoord == null && fromWays.size() == 1 && toWays.size() == 1){
+			Way fromWay = fromWays.get(0);
+			Way toWay = toWays.get(0);
 			List<Coord>fromPoints = fromWay.getPoints();
 			List<Coord>toPoints = toWay.getPoints();
+			int countSame = 0;
 			for(Coord fp : fromPoints) {
 				for(Coord tp : toPoints) {
-					if(fp.equals(tp)) {
-						if(viaCoord == null) {
-							viaCoord = fp;
-						}
-						else {
-							log.warn(messagePrefix + "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways connect in more than one place");
-							return false;
-						}
+					if(fp == tp){
+						countSame++;
+						viaCoord = fp;
 					}
 				}
 			}
-
-			if(viaCoord == null) {
-				log.warn(messagePrefix + "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways don't connect");
-				return false;
+			if (countSame > 1){
+				log.warn(messagePrefix, "lacks 'via' node and way and the 'from' (", fromWay.toBrowseURL(), ") and 'to' (", toWay.toBrowseURL(), ") ways connect in more than one place");
+				valid = false;
+			} else if (viaCoord == null){
+				log.warn(messagePrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways don't connect");
+				valid = false;
+			} else {
+				if (fromPoints.get(0) != viaCoord && fromPoints.get(fromPoints.size()-1) != viaCoord ||
+						toPoints.get(0) != viaCoord && toPoints.get(toPoints.size()-1) != viaCoord){
+					log.warn(messagePrefix, "lacks 'via' node and the 'from' (" + fromWay.toBrowseURL() + ") and 'to' (" + toWay.toBrowseURL() + ") ways don't connect at an end point");
+					valid = false;
+				} else
+					log.warn(messagePrefix, "lacks 'via' node (guessing it should be at", viaCoord.toOSMURL() + ", why don't you add it to the OSM data?)");				
 			}
-
-			log.warn(messagePrefix + "lacks 'via' node (guessing it should be at " + viaCoord.toOSMURL() + ", why don't you add it to the OSM data?)");
 		}
 
-		Coord v1 = viaCoord;
-		Coord v2 = null;
-
-		if(viaWay != null) {
-			v1 = viaWay.getPoints().get(0);
-			v2 = viaWay.getPoints().get(viaWay.getPoints().size() - 1);
+		if(fromWays.isEmpty() ) {
+			log.warn(messagePrefix, "lacks 'from' way");
+			valid = false;
 		}
 
-		Coord e1 = fromWay.getPoints().get(0);
-		Coord e2 = fromWay.getPoints().get(fromWay.getPoints().size() - 1);
-		if(!e1.equals(v1) && !e2.equals(v1) &&
-		   !e1.equals(v2) && !e2.equals(v2)) {
-			log.warn(messagePrefix + "'from' way " + fromWay.toBrowseURL() + " doesn't start or end at 'via' node or way");
-			result = false;
+		if(toWays.isEmpty()) {
+			log.warn(messagePrefix, "lacks 'to' way");
+			valid = false;
 		}
 
-		e1 = toWay.getPoints().get(0);
-		e2 = toWay.getPoints().get(toWay.getPoints().size() - 1);
-		if(!e1.equals(v1) && !e2.equals(v1) &&
-		   !e1.equals(v2) && !e2.equals(v2)) {
-			log.warn(messagePrefix + "'to' way " + toWay.toBrowseURL() + " doesn't start or end at 'via' node or way");
-			result = false;
+		if ((fromWays.size() > 1 || toWays.size() > 1) && viaWays.isEmpty() == false){
+			log.warn(messagePrefix, "'via' way(s) are not supported with multiple 'from' or 'to' ways");
+			valid = false;
 		}
-
-		if (result && viaWay != null) {
-			log.warn(messagePrefix + "sorry, 'via' ways are not supported - ignoring restriction");
-			result = false;
+		if (toWays.size() == 1 && fromWays.size() == 1 && viaWays.isEmpty()){
+			if ("no_u_turn".equals(restriction) && fromWays.get(0).equals(toWays.get(0))){
+				log.warn(messagePrefix,"no_u_turn with equal 'from' and 'to' way and via node is ignored");
+				valid = false;
+			}
 		}
+		if (!valid)
+			return;
+		for (List<Way> ways : Arrays.asList(fromWays,viaWays,toWays)){
+			for (Way way : ways){
+				if (way.getPoints().size() < 2){
+					log.warn(messagePrefix,"way",way.toBrowseURL(),"has less than 2 points, restriction is ignored");
+					valid = false;
+				} else {
+					if (way.getPoints().get(0) == way.getPoints().get(way.getPoints().size()-1)){
+						if (ways == toWays && dirIndicator != '?')
+							continue; // we try to determine the correct part in RoadNetwork 
+						log.warn(messagePrefix, "way", way.toBrowseURL(), "starts and ends at same node, don't know which one to use");
+						valid = false;
+					}
+				}
+			}
+		}
+		if (!valid)
+			return;
+		if (viaPoints.isEmpty() == false)
+			viaCoord = viaPoints.get(0);
+		
+		if(viaCoord == null && viaWays.isEmpty()) {
+			valid = false;
+			return;
+		}
+		
+		viaPoints.clear();
+		Coord v1 = viaCoord;
+		Coord v2 = viaCoord;
+		if (viaWays.isEmpty() == false){
+			v1 = viaWays.get(0).getPoints().get(0);
+			v2 = viaWays.get(0).getPoints().get(viaWays.get(0).getPoints().size()-1);
+		}
+		// check if all from ways are connected at the given via point or with the given via ways
+		for (Way fromWay : fromWays){
+			Coord e1 = fromWay.getPoints().get(0);
+			Coord e2 = fromWay.getPoints().get(fromWay.getPoints().size() - 1);
+			if (e1 == v1 || e2 == v1)
+				viaCoord = v1;
+			else if (e1 == v2 || e2 == v2)
+				viaCoord = v2;
+			else {
+				log.warn(messagePrefix, "'from' way", fromWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
+				valid = false;
+			} 
+		}
+		if (!valid)
+			return;
+		viaPoints.add(viaCoord);
+		// check if via ways are connected in the given order
+		for (int i = 0; i < viaWays.size();i++){
+			Way way = viaWays.get(i);
+			Coord v = viaPoints.get(viaPoints.size()-1);
+			if (way.getPoints().get(0) == v)
+				v2 = way.getPoints().get(way.getPoints().size()-1);
+			else if (way.getPoints().get(way.getPoints().size()-1) == v)
+				v2 = way.getPoints().get(0);
+			else {
+				log.warn(messagePrefix, "'via' way", way.toBrowseURL(), "doesn't start or end at",v.toDegreeString());
+				valid = false;
+			}
+			viaPoints.add(v2);
+		}
+		
+		// check if all via points are inside the bounding box
+		int countInside = 0;
+		for (Coord via: viaPoints){
+			if(bbox.contains(via))
+				++countInside;
+		}
+		if (countInside == 0)
+			valid = false;
+		else if (countInside > 0 && countInside < viaPoints.size()){
+			log.warn(messagePrefix,"via way crosses tile boundary. Don't know how to save that, ignoring it");
+			valid = false;
+		}
+		
+		if (!valid)
+			return;
+		// check if all to ways are connected to via point or last via way
+		Coord lastVia = viaPoints.get(viaPoints.size()-1);
+		for (Way toWay : toWays){
+			Coord e1 = toWay.getPoints().get(0);
+			Coord e2 = toWay.getPoints().get(toWay.getPoints().size() - 1);
+			if(e1 != lastVia && e2 != lastVia) {
+				log.warn(messagePrefix, "'to' way", toWay.toBrowseURL(), "doesn't start or end at 'via' node or way");
+				valid = false;
+			} 
+		}
+		if (valid && !viaWays.isEmpty() && restriction.startsWith("only")){
+			log.warn(messagePrefix, "check: 'via' way(s) are used in",restriction,"restriction");
+		}
+		if (valid){
+			// make sure that via way(s) don't appear in the from or to lists 
+			for (Way w: viaWays){
+				if (fromWays.contains(w)){
+					log.warn(messagePrefix, "'via' way",w.toBrowseURL(),"appears also as 'from' way");
+					valid = false;
+				}
+				if (toWays.contains(w)){
+					log.warn(messagePrefix, "'via' way",w.toBrowseURL(),"appears also as 'to' way");
+					valid = false;
+				}
+			}
+		}
+		if (valid){
+			for (Way w: fromWays)
+				fromWayIds.add(w.getId());
+			for (Way w: toWays)
+				toWayIds.add(w.getId());
+			for (Way w: viaWays){
+				w.setViaWay(true);
+				viaWayIds.add(w.getId());
+			}
+			for (Coord v: viaPoints)
+				v.setViaNodeOfRestriction(true);
+		}
+	}
 
-		return result;
+	/** 
+	 * Match the vehicle type in a restriction with the mkgmap type
+	 * and modify the exceptMask 
+	 * @param vehicle
+	 * @param b true: restriction should not apply for vehicle, false: restriction should apply  
+	 * @return true if vehicle has a matching flag in the garmin format
+	 */
+	private boolean setExceptMask(String vehicle, boolean b){
+		byte flag = 0;
+		if (vehicle == null)
+			return false;
+		// inverted 
+		if(vehicle.equals("vehicle"))
+			flag = (byte) ~(DEFAULT_EXCEPT_MASK); 
+		else if(vehicle.equals("motor_vehicle"))
+			flag = (byte) ~(AccessTagsAndBits.BIKE | DEFAULT_EXCEPT_MASK);
+		// normal
+		else if(vehicle.equals("psv"))
+			flag = (byte) (AccessTagsAndBits.TAXI | AccessTagsAndBits.BUS);
+		else if(vehicle.equals("bicycle"))
+			flag = AccessTagsAndBits.BIKE;
+		else if(vehicle.equals("motorcar"))
+			flag = AccessTagsAndBits.CAR;
+		else if(vehicle.equals("bus"))
+			flag = AccessTagsAndBits.BUS;
+		else if(vehicle.equals("taxi"))
+			flag = AccessTagsAndBits.TAXI;
+		else if(vehicle.equals("goods"))
+			flag = AccessTagsAndBits.DELIVERY;
+		else if(vehicle.equals("hgv") || vehicle.equals("truck"))
+			flag = AccessTagsAndBits.TRUCK;
+		else if(vehicle.equals("emergency"))
+			flag = AccessTagsAndBits.EMERGENCY;
+		else if(vehicle.equals("foot"))
+			flag = AccessTagsAndBits.FOOT;
+		if (flag == 0){
+			log.warn(messagePrefix, "ignoring unsupported vehicle class '" + vehicle + "' in turn restriction");
+			return false;
+		} 
+		
+		if (b)
+			exceptMask |= flag;
+		else 
+			exceptMask &= ~flag;
+		return true;			
+	}
+	
+	public boolean isFromWay(long wayId) {
+		return fromWayIds.contains(wayId);
 	}
 
-	public void addRestriction(MapCollector collector) {
+	public boolean isToWay(long wayId) {
+		return toWayIds.contains(wayId);
+	}
 
-		if(restriction == null || viaNode == null || fromNode == null || toNode == null) {
-			// restriction must have some error (reported earlier)
+	public void replaceViaCoord(Coord oldP, Coord newP) {
+		for (int i = 0; i < viaPoints.size(); i++){
+			if (viaPoints.get(i) == oldP){
+				viaPoints.set(i, newP);
+				if (log.isDebugEnabled()){
+					log.debug(messagePrefix, restriction, "'via' coord redefined from",
+							oldP.toOSMURL(), "to", newP.toOSMURL());
+				}
+				return;
+			}
+		}
+	}
+	
+	public void addRestriction(MapCollector collector, IdentityHashMap<Coord, CoordNode> nodeIdMap) {
+		if (!valid)
 			return;
+	    List<CoordNode> viaNodes = new ArrayList<>();
+		for (Coord v: viaPoints){
+			CoordNode vn = nodeIdMap.get(v);
+			if (vn == null){
+				log.error(messagePrefix,"via node is not a routing node");
+				return;
+			}
+			viaNodes.add(vn);
 		}
 
-		if(restriction.equals("no_left_turn") ||
-		   restriction.equals("no_right_turn") ||
-		   restriction.equals("no_straight_on") ||
-		   restriction.equals("no_u_turn") ||
-		   restriction.startsWith("no_turn")) {
-			collector.addRestriction(fromNode, toNode, viaNode, exceptMask);
-			if(restriction.startsWith("no_turn"))
-				log.warn(messagePrefix + "has bad type '" + restriction + "' it should be of the form no_X_turn rather than no_turn_X - I added the restriction anyway - blocks routing to way " + toWay.toBrowseURL());
-			else
-				log.info(messagePrefix + restriction + " added - blocks routing to way " + toWay.toBrowseURL());
-		}
-		else if(restriction.equals("only_left_turn") ||
-				restriction.equals("only_right_turn") ||
-				restriction.startsWith("only_straight") ||
-				restriction.startsWith("only_turn")) {
-			if(restriction.startsWith("only_turn"))
-				log.warn(messagePrefix + "has bad type '" + restriction + "' it should be of the form only_X_turn rather than only_turn_X - I added the restriction anyway - allows routing to way " + toWay.toBrowseURL());
-			log.info(messagePrefix + restriction + " added - allows routing to way " + toWay.toBrowseURL());
-			HashSet<CoordNode> otherNodesUnique = new HashSet<CoordNode>(otherNodes);	
-			for(CoordNode otherNode : otherNodesUnique) {
-				if (!otherNode.equals(fromNode) && !otherNode.equals(toNode)) {
-					log.info(messagePrefix + restriction + "  blocks routing to node " + otherNode.toOSMURL());
-					collector.addRestriction(fromNode, otherNode, viaNode, exceptMask);
+		if (viaNodes.size() > 6){
+			log.warn(messagePrefix,"has more than 6 via nodes, this is not supported");
+			return;
+		}
+		if(restriction == null){
+			log.error("internal error: can't add valid restriction relation", this.getId(), "type", restriction);
+			return;
+		}
+		int addedRestrictions = 0;
+		GeneralRouteRestriction grr;
+		if(restriction.startsWith("no_")){
+			for (long fromWayId : fromWayIds){
+				for (long toWayId : toWayIds){
+					grr = new GeneralRouteRestriction("not", exceptMask, messagePrefix);
+					grr.setFromWayId(fromWayId);
+					grr.setToWayId(toWayId);
+					grr.setViaNodes(viaNodes);
+					grr.setViaWayIds(viaWayIds);
+					grr.setDirIndicator(dirIndicator);
+					addedRestrictions += collector.addRestriction(grr);
 				}
 			}
+			if (log.isInfoEnabled())
+				log.info(messagePrefix, restriction, "translated to",addedRestrictions,"img file restrictions");
+		}
+		else if(restriction.startsWith("only_")){
+			grr = new GeneralRouteRestriction("only", exceptMask, messagePrefix);
+			grr.setFromWayId(fromWayIds.get(0));
+			grr.setToWayId(toWayIds.get(0));
+			grr.setViaNodes(viaNodes);
+			grr.setViaWayIds(viaWayIds);
+			grr.setDirIndicator(dirIndicator);
+			int numAdded = collector.addRestriction(grr);
+			if (numAdded > 0)
+				log.info(messagePrefix, restriction, "added - allows routing to way", toWayIds.get(0));
+
 		}
 		else {
-			log.warn(messagePrefix + "has unsupported type '" + restriction + "'");
+			log.error("mkgmap internal error: unknown restriction", restriction);
 		}
 	}
 
@@ -320,6 +575,148 @@ public class RestrictionRelation extends Relation {
 	}
 
 	public String toString() {
-		return "[restriction = " + restriction + ", from = " + fromWay.toBrowseURL() + ", to = " + toWay.toBrowseURL() + ", via = " + viaCoord.toOSMURL() + "]";
+		String s = "[restriction id = " + getId() + "(" + restriction + ")";
+		if (!fromWayIds.isEmpty() && !toWayIds.isEmpty() && viaCoord != null )
+			s += ", from = " + fromWayIds.get(0) + ", to = " + toWayIds.get(0) + ", via = " + viaCoord.toOSMURL() + "]";
+		else 
+			s += "]";
+		return s;
+	}
+	
+	/**
+	 * @return true if restriction is usable
+	 */
+	public boolean isValid() {
+		assert evalWasCalled;
+		return valid;
+	}
+
+	public List<Coord> getViaCoords() {
+		assert evalWasCalled;
+		return viaPoints;
+	}
+
+	/**
+	 * 
+	 * @return a Set with the OSM IDs of all ways used in the restriction 
+	 */
+	public Set<Long> getWayIds(){
+		assert evalWasCalled;
+		Set<Long> wayIds = new HashSet<>();
+		wayIds.addAll(fromWayIds);
+		wayIds.addAll(viaWayIds);
+		wayIds.addAll(toWayIds);
+		return wayIds;
+	}
+	
+	public byte getExceptMask(){
+		assert evalWasCalled;
+		return exceptMask;
+	}
+	
+	/**
+	 * Replace 
+	 * @param oldWayId
+	 * @param newWayId
+	 * @return
+	 */
+	public boolean replaceWay(long oldWayId, long newWayId) {
+		assert evalWasCalled;
+		boolean matched = false;
+		for (List<Long> ways: Arrays.asList(fromWayIds, viaWayIds, toWayIds)){
+			for (int i = 0; i < ways.size(); i++){
+				if (ways.get(i) == oldWayId){
+					ways.set(i, newWayId);
+					matched = true;
+				}
+			}
+		}
+		return matched;
+	}
+
+	/**
+	 * check if restriction is still valid if the way with the given id is not in the map
+	 * @param wayId
+	 * @return
+	 */
+	public boolean isValidWithoputWay(long wayId) {
+		assert evalWasCalled;
+		if (viaWayIds.contains(wayId))
+			return false;
+		fromWayIds.remove(wayId);
+		if (fromWayIds.isEmpty())
+			return false;
+		// else it must be a no_entry restriction which still is valid
+
+		toWayIds.remove(wayId);
+		if (toWayIds.isEmpty())
+			return false;
+		// else it must be a no_exit restriction which still is valid
+		return true;
+	}
+
+	/**
+	 * A via way may be connected to other ways between the end points.
+	 * We have to create a complete path for that. 
+	 * @param way
+	 * @param nodeIndices
+	 */
+	public void updateViaWay(Way way, List<Integer> nodeIndices) {
+		if (!valid)
+			return;
+		if (viaWayIds.contains(way.getId()) == false)
+			return;
+		Coord first = way.getPoints().get(nodeIndices.get(0));
+		Coord last = way.getPoints().get(
+				nodeIndices.get(nodeIndices.size() - 1));
+		int posFirst = -1;
+		for (int i = 0; i < viaPoints.size(); i++) {
+			if (first == viaPoints.get(i)) {
+				posFirst = i;
+				break;
+			}
+		}
+		int posLast = -1;
+		for (int i = 0; i < viaPoints.size(); i++) {
+			if (last == viaPoints.get(i)) {
+				posLast = i;
+				break;
+			}
+		}
+		if (posFirst < 0  || posLast < 0){
+			log.error(messagePrefix, "internal error: via way doesn't contain expected points");
+			valid = false;
+			return;
+		}
+		
+		List<Coord> midPoints = new ArrayList<>();
+		for (int i = 1; i + 1 < nodeIndices.size(); i++) {
+			midPoints.add(way.getPoints().get(nodeIndices.get(i)));
+		}
+		if (posFirst < posLast){
+			if (posLast - posFirst > 1)
+				viaPoints.subList(posFirst+1, posLast).clear();
+			viaPoints.addAll(posFirst + 1, midPoints);
+		}
+		else {
+			if (posFirst - posLast > 1)
+				viaPoints.subList(posLast + 1, posFirst).clear();
+			Collections.reverse(midPoints);
+			viaPoints.addAll(posLast + 1, midPoints);
+		}
+		
+		int wayPos = viaWayIds.indexOf(way.getId());
+		while(viaWayIds.size() > wayPos + 1 && viaWayIds.get(wayPos+1) == way.getId())
+			viaWayIds.remove(wayPos);
+		for (int i = 0; i < midPoints.size(); i++){
+			viaWayIds.add(wayPos+1, way.getId());
+		}
+		if (viaPoints.size() != viaWayIds.size()+1){
+			log.error("internal error: number of via points and via ways no longer fits");
+			valid = false;
+		} else if (viaPoints.size() > 6){
+			log.warn(messagePrefix,"has more than 6 via nodes, this is not supported");
+			valid = false;
+		} 
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RoutingHook.java b/src/uk/me/parabola/mkgmap/reader/osm/RoutingHook.java
index db837fe..3ea4d61 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RoutingHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RoutingHook.java
@@ -34,6 +34,13 @@ public class RoutingHook extends OsmReadingHooksAdaptor {
 		usedTags = new HashSet<String>();
 		usedTags.add("except");
 		usedTags.add("restriction");
+		usedTags.add("restriction:foot");
+		usedTags.add("restriction:hgv");
+		usedTags.add("restriction:motorcar");
+		usedTags.add("restriction:vehicle");
+		usedTags.add("restriction:motor_vehicle");
+		usedTags.add("restriction:bicycle");
+		usedTags.add("restriction:bus");
 	}
 
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
index 552ea92..1488946 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
@@ -34,10 +34,24 @@ public interface Rule {
 	public void resolveType(Element el, TypeResult result);
 	
 	/**
+	 * 
+	 * Given the element return the garmin type that should be used to
+	 * represent it.
+	 *
+	 * @param cacheId
+	 * @param el The element as read from an OSM xml file in 'tag' format.
+	 * @param result The resolved Garmin type that will go into the map.
+	 * @return
+	 */
+	public int resolveType(int cacheId, Element el, TypeResult result);
+	
+	/**
 	 * Sets the finalize rules that are executed when 
 	 * an element type definition matches.
 	 * 
 	 * @param finalizeRule finalize rule(s)
 	 */
 	public void setFinalizeRule(Rule finalizeRule);
+	
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
index 5afce3a..a8bd725 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
@@ -22,12 +22,13 @@ import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NavigableMap;
 import java.util.NavigableSet;
 import java.util.Set;
@@ -158,20 +159,21 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 							}
 						} else if (precompSea.endsWith(".zip")){
 							zipFile = new ZipFile(precompSeaDir);
-							internalPath = "sea";
-							ZipEntry entry = zipFile.getEntry(internalPath);
-							if (entry == null)
-								internalPath = "";
-							else 
-								internalPath = internalPath + "/";
-							entry = zipFile.getEntry(internalPath + indexFileName);
+							internalPath = "sea/";
+							ZipEntry entry = zipFile.getEntry(internalPath + indexFileName);
 							if (entry == null){
 								indexFileName = "index.txt";
 								entry = zipFile.getEntry(internalPath + indexFileName);
 							}
+							if (entry == null){
+								internalPath = "";
+								indexFileName = "index.txt.gz";
+								entry = zipFile.getEntry(internalPath + indexFileName);
+							}
 							if (entry != null){
 								indexStream = zipFile.getInputStream(entry);
-							}
+							} else 
+								log.error("Don't know how to read " + precompSeaDir);
 						} else {
 							log.error("Don't know how to read " + precompSeaDir);
 						}
@@ -412,17 +414,13 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 		String natural = way.getTag("natural");
 		if(natural != null) {
 			if("coastline".equals(natural)) {
-				way.deleteTag("natural");
-				if (coastlineFilenames == null && precompSeaDir == null)
-					shoreline.add(way);
-				
-				if (precompSeaDir != null) {
-					// add a copy of this way to be able to draw the coastline which is not possible with precompiled sea
-					Way coastlineWay = new Way(FakeIdGenerator.makeFakeId(), way.getPoints());
-					coastlineWay.addTag("natural", "coastline");
-					// tag that this way is used as line only
-					coastlineWay.addTag(MultiPolygonRelation.STYLE_FILTER_TAG, MultiPolygonRelation.STYLE_FILTER_LINE);
-					saver.addWay(coastlineWay);
+				if (precompSeaDir != null)
+					splitCoastLineToLineAndShape(way, natural);
+				else {
+					if (coastlineFilenames == null){
+						way.deleteTag("natural");
+						shoreline.add(way);
+					}
 				}
 			} else if (natural.contains(";")) {
 				// cope with compound tag value
@@ -438,19 +436,15 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 				}
 
 				if(foundCoastline) {
-					way.deleteTag("natural");
-					if(others != null)
-						way.addTag("natural", others);
-					if (coastlineFilenames == null && precompSeaDir == null)
-						shoreline.add(way);
-					
-					if (precompSeaDir != null) {
-						// add a copy of this way to be able to draw the coastline which is not possible with precompiled sea
-						Way coastlineWay = new Way(FakeIdGenerator.makeFakeId(), way.getPoints());
-						coastlineWay.addTag("natural", "coastline");
-						// tag that this way is used as line only
-						coastlineWay.addTag(MultiPolygonRelation.STYLE_FILTER_TAG, MultiPolygonRelation.STYLE_FILTER_LINE);
-						saver.addWay(coastlineWay);
+					if (precompSeaDir != null)
+						splitCoastLineToLineAndShape(way, natural);
+					else { 
+						if (coastlineFilenames == null){
+							way.deleteTag("natural");
+							if(others != null)
+								way.addTag("natural", others);
+							shoreline.add(way);
+						}
 					}
 				}
 			}
@@ -458,6 +452,31 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	}
 
 	/**
+	 * With precompiled sea, we don't want to process all natural=coastline
+	 * ways as shapes without additional processing.   
+	 * This should avoid duplicate shapes for islands that are also in the 
+	 * precompiled data. 
+	 * @param way the OSM way with tag key natural 
+	 * @param naturalVal the tag value
+	 */
+	private void splitCoastLineToLineAndShape(Way way, String naturalVal){
+		if (precompSeaDir == null)
+			return;
+		if (way.hasIdenticalEndPoints()){
+			// add a copy of this way to be able to draw it as a shape
+			Way shapeWay = new Way(FakeIdGenerator.makeFakeId(), way.getPoints());
+			// change the tag so that only special rules looking for it are firing
+			shapeWay.deleteTag("natural"); 
+			shapeWay.addTag("mkgmap:removed_natural",naturalVal); 
+			// tag that this way so that it is used as shape only
+			shapeWay.addTag(MultiPolygonRelation.STYLE_FILTER_TAG, MultiPolygonRelation.STYLE_FILTER_POLYGON);
+			saver.addWay(shapeWay);		
+		}
+		// make sure that the original (unchanged) way is not processed as a shape
+		way.addTag(MultiPolygonRelation.STYLE_FILTER_TAG, MultiPolygonRelation.STYLE_FILTER_LINE);
+	}
+	
+	/**
 	 * Creates a reader for the given filename of the precomiled sea tile.
 	 * @param filename precompiled sea tile 
 	 * @return the reader for the tile
@@ -688,12 +707,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			
 			Area bounds = saver.getBoundingBox();
 			// first add the complete bounding box as sea
-			Way sea = new Way(FakeIdGenerator.makeFakeId());
-			sea.addPoint(new Coord(bounds.getMinLat(), bounds.getMinLong()));
-			sea.addPoint(new Coord(bounds.getMinLat(), bounds.getMaxLong()));
-			sea.addPoint(new Coord(bounds.getMaxLat(), bounds.getMaxLong()));
-			sea.addPoint(new Coord(bounds.getMaxLat(), bounds.getMinLong()));
-			sea.addPoint(new Coord(bounds.getMinLat(), bounds.getMinLong()));
+			Way sea = new Way(FakeIdGenerator.makeFakeId(),bounds.toCoords());
 			sea.addTag("natural", "sea");
 			
 			for (Way w : landWays) {
@@ -767,10 +781,10 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	 */
 	public static ArrayList<Way> joinWays(Collection<Way> segments) {
 		ArrayList<Way> joined = new ArrayList<Way>((int)Math.ceil(segments.size()*0.5));
-		Map<Coord, Way> beginMap = new HashMap<Coord, Way>();
+		Map<Coord, Way> beginMap = new IdentityHashMap<Coord, Way>();
 
 		for (Way w : segments) {
-			if (w.isClosed()) {
+			if (w.hasIdenticalEndPoints()) {
 				joined.add(w);
 			} else if (w.getPoints() != null && w.getPoints().size() > 1){
 				List<Coord> points = w.getPoints();
@@ -785,7 +799,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 		while (merged > 0) {
 			merged = 0;
 			for (Way w1 : beginMap.values()) {
-				if (w1.isClosed()) {
+				if (w1.hasIdenticalEndPoints()) {
 					// this should not happen
 					log.error("joinWays2: Way "+w1+" is closed but contained in the begin map");
 					joined.add(w1);
@@ -812,7 +826,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 					beginMap.remove(points2.get(0));
 					merged++;
 					
-					if (wm.isClosed()) {
+					if (wm.hasIdenticalEndPoints()) {
 						joined.add(wm);
 						beginMap.remove(wm.getPoints().get(0));
 					}
@@ -836,7 +850,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			return;
 		}
 
-		Area seaBounds = saver.getBoundingBox();
+		final Area seaBounds = saver.getBoundingBox();
 		if (coastlineFilenames == null) {
 			log.info("Shorelines before join", shoreline.size());
 			shoreline = joinWays(shoreline);
@@ -849,7 +863,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 		int closedS = 0;
 		int unclosedS = 0;
 		for (Way w : shoreline) {
-			if (w.isClosed()) {
+			if (w.hasIdenticalEndPoints()) {
 				closedS++;
 			} else {
 				unclosedS++;
@@ -878,12 +892,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			// match the land colour on the tiles that do contain
 			// some sea
 			long landId = FakeIdGenerator.makeFakeId();
-			Way land = new Way(landId);
-			land.addPoint(nw);
-			land.addPoint(sw);
-			land.addPoint(se);
-			land.addPoint(ne);
-			land.addPoint(nw);
+			Way land = new Way(landId, seaBounds.toCoords());
 			land.addTag(landTag[0], landTag[1]);
 			// no matter if the multipolygon option is used it is
 			// only necessary to create a land polygon
@@ -970,8 +979,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 					se.getLongitude() + 1));
 			sea.addPoint(new Coord(ne.getLatitude() - 1,
 					ne.getLongitude() + 1));
-			sea.addPoint(new Coord(nw.getLatitude() - 1,
-					nw.getLongitude() - 1));
+			sea.addPoint(sea.getPoints().get(0)); // close shape
 			sea.addTag("natural", "sea");
 
 			log.info("sea: ", sea);
@@ -987,12 +995,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			// background colour will match the land colour on the
 			// tiles that do contain some sea
 			long landId = FakeIdGenerator.makeFakeId();
-			Way land = new Way(landId);
-			land.addPoint(nw);
-			land.addPoint(sw);
-			land.addPoint(se);
-			land.addPoint(ne);
-			land.addPoint(nw);
+			Way land = new Way(landId, seaBounds.toCoords());
 			land.addTag(landTag[0], landTag[1]);
 			saver.addWay(land);
 			if (generateSeaUsingMP) {
@@ -1056,7 +1059,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 		Iterator<Way> it = shoreline.iterator();
 		while (it.hasNext()) {
 			Way w = it.next();
-			if (w.isClosed()) {
+			if (w.hasIdenticalEndPoints()) {
 				log.info("adding island", w);
 				islands.add(w);
 				it.remove();
@@ -1068,7 +1071,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 		it = shoreline.iterator();
 		while (it.hasNext()) {
 			Way w = it.next();
-			if (w.isClosed()) {
+			if (w.hasIdenticalEndPoints()) {
 				log.debug("island after concatenating");
 				islands.add(w);
 				it.remove();
@@ -1132,8 +1135,8 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 				hit = hNext;
 			} while (!hits.isEmpty() && !hit.equals(hFirst));
 
-			if (!w.isClosed())
-				w.getPoints().add(w.getPoints().get(0));
+			if (!w.hasIdenticalEndPoints())
+				w.addPoint(w.getPoints().get(0)); // close shape
 			log.info("adding non-island landmass, hits.size()=" + hits.size());
 			islands.add(w);
 			shorelineReachesBoundary = true;
@@ -1157,9 +1160,10 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 				Way w1 = new Way(FakeIdGenerator.makeFakeId());
 				w1.getPoints().addAll(w.getPoints());
 				// only copy the name tags
-				for(String tag : w)
-					if(tag.equals("name") || tag.endsWith(":name"))
-						w1.addTag(tag, w.getTag(tag));
+				for (Entry<String, String> tagEntry : w.getTagEntryIterator()){
+					if(tagEntry.getKey().equals("name") || tagEntry.getKey().contains("name"))
+						w1.addTag(tagEntry.getKey(), tagEntry.getValue());
+				}
 				w = w1;
 			}
 
@@ -1235,9 +1239,10 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 						Way w1 = new Way(FakeIdGenerator.makeFakeId());
 						w1.getPoints().addAll(w.getPoints());
 						// only copy the name tags
-						for(String tag : w)
-							if(tag.equals("name") || tag.endsWith(":name"))
-								w1.addTag(tag, w.getTag(tag));
+						for (Entry<String, String> tagEntry : w.getTagEntryIterator()){
+							if(tagEntry.getKey().equals("name") || tagEntry.getKey().contains("name"))
+								w1.addTag(tagEntry.getKey(), tagEntry.getValue());
+						}
 						w = w1;
 					}
 					w.addTag(landTag[0], landTag[1]);
@@ -1275,6 +1280,9 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 					// show the coastline even though we can't produce
 					// a polygon for the land
 					w.addTag("natural", "coastline");
+					if (w.hasIdenticalEndPoints() == false){
+						log.error("adding sea shape that is not really closed");
+					}
 					saver.addWay(w);
 				}
 			} else {
@@ -1424,7 +1432,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			while (changed) {
 				changed = false;
 				for (Way w1 : ways) {
-					if(w1.isClosed())
+					if(w1.hasIdenticalEndPoints())
 						continue;
 					List<Coord> points1 = w1.getPoints();
 					Coord w1e = points1.get(points1.size() - 1);
@@ -1433,7 +1441,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 					Way nearest = null;
 					double smallestGap = Double.MAX_VALUE;
 					for (Way w2 : ways) {
-						if(w1 == w2 || w2.isClosed())
+						if(w1 == w2 || w2.hasIdenticalEndPoints())
 							continue;
 						List<Coord> points2 = w2.getPoints();
 						Coord w2s = points2.get(0);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
index d1d8a95..d6abc09 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
@@ -56,6 +56,7 @@ public class SeaPolygonRelation extends MultiPolygonRelation {
 		addTag("type", "mkgmap:seapolygon");
 	}
 
+	@Override
 	protected boolean isAreaSizeCalculated() {
 		return false;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
new file mode 100644
index 0000000..3a358a6
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.HashMap;
+import uk.me.parabola.imgfmt.MapFailedException;
+
+
+/**
+ * A dictionary for tag names. Allows to translate a tag name to a unique Short value.
+ * @author GerdP
+ *
+ */
+public class TagDict{
+	private static TagDict instance = null;
+	private HashMap<String,Short>  map;
+	private ArrayList<String>  list;
+
+	public static final short INVALID_TAG_VALUE = 0;
+
+	/**
+	 * create an empty dictionary
+	 */
+	private TagDict() {
+		map = new HashMap<>();
+		list = new ArrayList<>();
+		map.put("invalid tag", INVALID_TAG_VALUE);
+		list.add("invalid tag");
+	}
+	
+	/** 
+	 * give access to the singleton instance  
+	 * @return
+	 */
+	public static synchronized TagDict getInstance() {
+		if (instance == null) {
+			instance = new TagDict();
+		}
+		return instance;
+	}		
+	
+	/**
+	 * translate a tag name to a short value
+	 * @param keyString the tag name
+	 * @return a Short > 0 that can be used to retrieve
+	 * the tag name with the get() method
+	 */
+	public synchronized  short xlate (String keyString){
+		Short tagKey = map.get(keyString);
+		if (tagKey == null) {
+			short size = (short) list.size();
+			if (size == Short.MAX_VALUE){
+				// very unlikely, typically we have a few hundred tag names
+				throw new MapFailedException("Fatal: Too many different tags in style");
+			}
+			String s = keyString;
+			map.put(s, size);
+			//System.out.println(""+x + ":" +   s);
+			list.add(s);
+			return size;
+		}
+		return tagKey.shortValue();
+	}
+
+	/**
+	 * get the tagName for a tagKey. The caller has
+	 * to make sure that the key is valid.
+	 * @param key the tagKey (returned by xlate()
+	 * @return the tagName
+	 */
+	public String get(short key){
+		if (key == INVALID_TAG_VALUE) return null;
+			
+		return list.get(key);
+	}
+	
+	/**
+	 * The size of the dictionary. The highest known tagKey is 
+	 * size() - 1. 
+	 * @return 
+	 */
+	public int size(){
+		return list.size();
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
index 21627ab..5378123 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
@@ -37,23 +37,34 @@ import java.util.Map.Entry;
  */
 public class Tags implements Iterable<String> {
 	private static final int INIT_SIZE = 8;
+	private static final TagDict tagDict = TagDict.getInstance();  
 
 	private short keySize;
 	private short capacity;
 	
 	private short size;
 
-	private String[] keys;
+	private short[] keys;
 	private String[] values;
 
 	public Tags() {
-		keys = new String[INIT_SIZE];
+		keys = new short[INIT_SIZE];
 		values = new String[INIT_SIZE];
 		capacity = INIT_SIZE;
 	}
 
 	public String get(String key) {
+		short k = tagDict.xlate(key);
+		int ind = keyPos(k);
+		if (ind < 0)
+			return null;
+
+		return values[ind];
+	}
+	
+	public String get(short key) {
 		int ind = keyPos(key);
+		
 		if (ind < 0)
 			return null;
 
@@ -70,6 +81,11 @@ public class Tags implements Iterable<String> {
 
 	public String put(String key, String value) {
 		assert key != null : "key is null";
+		short dictIdx = tagDict.xlate(key);
+		return put(dictIdx,value);
+	}
+
+	public String put(short key, String value) {
 		assert value != null : "value is null";
 		ensureSpace();
 		int ind = keyPos(key);
@@ -87,7 +103,7 @@ public class Tags implements Iterable<String> {
 		return old;
 	}
 
-	public String remove(String key) {
+	public String remove(short key) {
 		int k = keyPos(key);
 
 		if (k >= 0 && values[k] != null) {
@@ -101,6 +117,11 @@ public class Tags implements Iterable<String> {
 		return null;
 	}
 	
+	public String remove(String key) {
+		short kd = tagDict.xlate(key);
+		return remove(kd); 
+	}
+	
 	/**
 	 * Make a deep copy of this object.
 	 * @return A copy of this object.
@@ -119,31 +140,35 @@ public class Tags implements Iterable<String> {
 	private void ensureSpace() {
 		while (keySize + 1 >= capacity) {
 			short ncap = (short) (capacity*2);
-			String[] okey = keys;
+			short[] okey = keys;
 			String[] oval = values;
-			keys = new String[ncap];
+			keys = new short[ncap];
 			values = new String[ncap];
 			capacity = ncap;
 			keySize = 0;
 			size = 0;
 			for (int i = 0; i < okey.length; i++) {
-				String k = okey[i];
+				short k = okey[i];
 				String v = oval[i]; // null if tag has been removed
-				if (k != null && v != null)
-					put(k, v);
+				if (k != TagDict.INVALID_TAG_VALUE && v != null){
+					//put(keyDict.get(k), v);
+					int ind = keyPos(k);
+					keys[ind] = k;
+					values[ind] = v;
+					++keySize;
+					++size;
+				}
 			}
 		}
 		assert keySize < capacity;
 	}
 
-	private int keyPos(String key) {
-		int h = key.hashCode();
-		int k = h & (capacity - 1);
+	private int keyPos(short key) {
+		int k = key & (capacity - 1);
 
 		int i = k;
 		do {
-			String fk = keys[i];
-			if (fk == null || fk.equals(key))
+			if (keys[i] == TagDict.INVALID_TAG_VALUE || keys[i] == key)
 				return i;
 			i++;
 			if (i >= capacity)
@@ -194,7 +219,7 @@ public class Tags implements Iterable<String> {
 					for (int i = pos; i < capacity; i++) {
 						if (values[i] != null) {
 							pos = i+1;
-							return (keys[i] + "=" + values[i]);
+							return (tagDict.get(keys[i]) + "=" + values[i]);
 						}
 					}
 					pos = capacity;
@@ -224,7 +249,34 @@ public class Tags implements Iterable<String> {
 			}
 
 			public Map.Entry<String, String> next() {
-				Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<String, String>(keys[pos], values[pos]);
+				Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<>(tagDict.get(keys[pos]), values[pos]);
+
+				pos++;
+				return entry;
+			}
+
+			public void remove() {
+				throw new UnsupportedOperationException();
+			}
+		};
+	}
+
+	public Iterator<Map.Entry<Short, String>> entryShortIterator() {
+		return new Iterator<Map.Entry<Short, String>>() {
+			private int pos;
+			
+			public boolean hasNext() {
+				for (int i = pos; i < capacity; i++) {
+					if (values[i] != null) {
+						pos = i;
+						return true;
+					}
+				}
+				return false;
+			}
+
+			public Map.Entry<Short, String> next() {
+				Map.Entry<Short, String> entry = new AbstractMap.SimpleEntry<>(keys[pos], values[pos]);
 
 				pos++;
 				return entry;
@@ -237,15 +289,18 @@ public class Tags implements Iterable<String> {
 	}
 
 	public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) {
-		Map<String, String> map = new HashMap<String, String>();
+		Map<String, String> map = new HashMap<>();
 
 		int prefixLen = prefix.length();
 		for(int i = 0; i < capacity; ++i) {
-			if(keys[i] != null && keys[i].startsWith(prefix)) {
+			if (keys[i] != 0){
+				String k = tagDict.get(keys[i]);
+				if(k != null && k.startsWith(prefix)) {
 				if(removePrefix)
-					map.put(keys[i].substring(prefixLen), values[i]);
+						map.put(k.substring(prefixLen), values[i]);
 				else
-					map.put(keys[i], values[i]);
+						map.put(k, values[i]);
+				}
 			}
 		}
 
@@ -253,10 +308,8 @@ public class Tags implements Iterable<String> {
 	}
 	
 	public void removeAll() {
-		for (int i = 0; i < capacity; i++){
-			keys[i] = null;
-			values[i] = null;
-		}
+		Arrays.fill(keys, TagDict.INVALID_TAG_VALUE);
+		Arrays.fill(values, null);
 		keySize = 0;
 		size = 0;
 	}
@@ -277,4 +330,28 @@ public class Tags implements Iterable<String> {
 		s.append("]");
 		return s.toString();
 	}
+	
+	/**
+	 * Each tag has a position in the TagDict. This routine fills an array
+	 * so that the caller can use direct access. 
+	 * The caller has to make sure that the array is large enough to hold
+	 * the values he is looking for.  
+	 * @param tagVals
+	 * @return
+	 */
+	public int expand(short[] keyArray, String[] tagVals){
+		if (tagVals == null)
+			return 0;
+		int maxKey = tagVals.length - 1;
+		int cntTags = 0;
+		for (int i = 0; i< capacity; i++){
+			short tagKey = keys[i];
+			if (tagKey != TagDict.INVALID_TAG_VALUE && values[i] != null && tagKey <= maxKey){
+				tagVals[tagKey] = values[i];
+				keyArray[cntTags++] = tagKey;
+			}
+
+		}
+		return cntTags;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index a6c5a25..8ad9aa4 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
 
 /**
  * Represent a OSM way in the 0.5 api.  A way consists of an ordered list of
@@ -31,18 +32,20 @@ import uk.me.parabola.imgfmt.app.Coord;
  * @author Steve Ratcliffe
  */
 public class Way extends Element {
-
+	private static final Logger log = Logger.getLogger(Way.class);
 	private final List<Coord> points;
 
 	// This will be set if a way is read from an OSM file and the first node is the same node as the last
 	// one in the way. This can be set to true even if there are missing nodes and so the nodes that we
 	// have do not form a closed loop.
-	// Note: this is not always set, you must use isClosed()
-	private boolean closed;
+	// Note: this is not always set
+	private boolean closedInOSM;
 
 	// This is set to false if, we know that there are nodes missing from this way.
 	// If you set this to false, then you *must* also set closed to the correct value.
 	private boolean complete  = true;
+	
+	private boolean isViaWay;
 
 	public Way(long id) {
 		points = new ArrayList<Coord>(5);
@@ -57,8 +60,9 @@ public class Way extends Element {
 	public Way copy() {
 		Way dup = new Way(getId(), points);
 		dup.copyTags(this);
-		dup.closed = this.closed;
+		dup.closedInOSM = this.closedInOSM;
 		dup.complete = this.complete;
+		dup.isViaWay = this.isViaWay;
 		return dup;
 	}
 
@@ -95,13 +99,40 @@ public class Way extends Element {
 	 */
 	public boolean isClosed() {
 		if (!isComplete())
-			return closed;
+			return closedInOSM;
+
+		return !points.isEmpty() && hasIdenticalEndPoints();
+	}
+
+	/**
+	 * 
+	 * @return true if the way is really closed in OSM,
+	 * false if the way was created by mkgmap or read from polish
+	 * input file (*.mp). 
+	 * 
+	 */
+	public boolean isClosedInOSM() {
+		return closedInOSM;
+	}
+
+	/**
+	 *  
+	 * @return Returns true if the first point in the way is identical to the last.
+	 */
+	public boolean hasIdenticalEndPoints() {
+		return !points.isEmpty() && points.get(0) == points.get(points.size()-1);
+	}
 
+	/**
+	 *  
+	 * @return Returns true if the first point in the way is identical to the last.
+	 */
+	public boolean hasEqualEndPoints() {
 		return !points.isEmpty() && points.get(0).equals(points.get(points.size()-1));
 	}
 
-	public void setClosed(boolean closed) {
-		this.closed = closed;
+	public void setClosedInOSM(boolean closed) {
+		this.closedInOSM = closed;
 	}
 
 	public boolean isComplete() {
@@ -151,6 +182,10 @@ public class Way extends Element {
 		return getId() == ((Way) o).getId();
 	}
 
+	/**
+	 * calculate weighted centre of way, using high precision
+	 * @return
+	 */
 	public Coord getCofG() {
 		int numPoints = points.size();
 		if(numPoints < 1)
@@ -159,10 +194,10 @@ public class Way extends Element {
 		double lat = 0;
 		double lon = 0;
 		for(Coord p : points) {
-			lat += (double)p.getLatitude()/numPoints;
-			lon += (double)p.getLongitude()/numPoints;
+			lat += (double)p.getHighPrecLat()/numPoints;
+			lon += (double)p.getHighPrecLon()/numPoints;
 		}
-		return new Coord((int)Math.round(lat), (int)Math.round(lon));
+		return Coord.makeHighPrecCoord((int)Math.round(lat), (int)Math.round(lon));
 	}
 
 	public String kind() {
@@ -173,15 +208,19 @@ public class Way extends Element {
 	// direction
 	public static boolean clockwise(List<Coord> points) {
 
+		
 		if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1)))
 			return false;
-
+		if (points.get(0).highPrecEquals(points.get(points.size() - 1)) == false){
+			log.error("Way.clockwise was called for way that is not closed in high precision");
+		}
+		
 		long area = 0;
 		Coord p1 = points.get(0);
 		for(int i = 1; i < points.size(); ++i) {
 			Coord p2 = points.get(i);
-			area += ((long)p1.getLongitude() * p2.getLatitude() - 
-					 (long)p2.getLongitude() * p1.getLatitude());
+			area += ((long)p1.getHighPrecLon() * p2.getHighPrecLat() - 
+					 (long)p2.getHighPrecLon() * p1.getHighPrecLat());
 			p1 = p2;
 		}
 
@@ -196,10 +235,18 @@ public class Way extends Element {
 	public boolean containsPointsOf(Way other) {
 		Polygon thisPoly = new Polygon();
 		for(Coord p : points)
-			thisPoly.addPoint(p.getLongitude(), p.getLatitude());
+			thisPoly.addPoint(p.getHighPrecLon(), p.getHighPrecLat());
 		for(Coord p : other.points)
-			if(!thisPoly.contains(p.getLongitude(), p.getLatitude()))
+			if(!thisPoly.contains(p.getHighPrecLon(), p.getHighPrecLat()))
 				return false;
 		return true;
 	}
+
+	public boolean isViaWay() {
+		return isViaWay;
+	}
+
+	public void setViaWay(boolean isViaWay) {
+		this.isViaWay = isViaWay;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
index 7140346..0aad1d0 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
@@ -29,8 +29,8 @@ public class BoundaryConverter implements OsmConverter {
 	@Override
 	public void convertWay(Way way) {
 		if (BoundaryElementSaver.isBoundary(way)) {
-			java.awt.geom.Area boundArea = new java.awt.geom.Area(Java2DConverter.createArea(way.getPoints()));
-			Boundary boundary = new Boundary(boundArea, way.getEntryIteratable(), "w"+way.getId());
+			java.awt.geom.Area boundArea = Java2DConverter.createArea(way.getPoints());
+			Boundary boundary = new Boundary(boundArea, way.getTagEntryIterator(), "w"+way.getId());
 			saver.addBoundary(boundary);
 		}
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
index b485f90..0459bf7 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
@@ -170,17 +170,16 @@ public class BoundaryDiff {
 			return (bqt.getCoveredArea(Integer.valueOf(value)));
 		Map<String, Tags> bTags = bqt.getTagsMap();
 		Map<String, List<Area>> areas = bqt.getAreas();
-		Area a = new Area();
 		Path2D.Double path = new Path2D.Double();
 		for (Entry<String, Tags> entry: bTags.entrySet()){
 			if (value.equals(entry.getValue().get(tag))){
 				List<Area> aList = areas.get(entry.getKey());
 				for (Area area : aList){
-					BoundaryUtil.addToPath(path, area);
+					path.append(area, false);
 				}
 			}
 		}
-		a = new Area(path);
+		Area a = new Area(path);
 		return a;
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElement.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElement.java
index a968e7a..b56ee7c 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElement.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElement.java
@@ -31,7 +31,7 @@ public class BoundaryElement  {
 
 	public Area getArea() {
 		if (area == null) {
-			area = new Area(Java2DConverter.createArea(points));
+			area = Java2DConverter.createArea(points);
 		}
 		return area;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
index 0e59f60..6074a23 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
@@ -68,7 +68,7 @@ public class BoundaryElementSaver extends ElementSaver {
 					// This should abandon all non country admin_level 2 boundaries
 					if (element.getTag("admin_level").equals("2")) {
 						Tags copyTags = new Tags();
-						for (Entry<String,String> tag : element.getEntryIteratable()) {
+						for (Entry<String,String> tag : element.getTagEntryIterator()) {
 							copyTags.put(tag.getKey(), tag.getValue());
 						}
 						String iso = locator.getCountryISOCode(copyTags);
@@ -78,7 +78,7 @@ public class BoundaryElementSaver extends ElementSaver {
 						}
 					}
 					// and a name must be set (check only for a tag containing name
-					for (Entry<String,String> tag : element.getEntryIteratable()) {
+					for (Entry<String,String> tag : element.getTagEntryIterator()) {
 						if (tag.getKey().contains("name")) {
 							return true;
 						}
@@ -93,7 +93,7 @@ public class BoundaryElementSaver extends ElementSaver {
 						return true;
 					}
 					// and a name must be set (check only for a tag containing name
-					for (Entry<String,String> tag : element.getEntryIteratable()) {
+					for (Entry<String,String> tag : element.getTagEntryIterator()) {
 						if (tag.getKey().contains("name")) {
 							return true;
 						}
@@ -111,7 +111,7 @@ public class BoundaryElementSaver extends ElementSaver {
 		} else if (element instanceof Way) {
 			Way w = (Way) element;
 			// a single way must be closed
-			if (w.isClosed() == false) {
+			if (w.isClosedInOSM() == false) {
 				return false;
 			}
 			// the boundary tag must be "administrative" or "postal_code"
@@ -126,7 +126,7 @@ public class BoundaryElementSaver extends ElementSaver {
 				// This should abandon all non country admin_level 2 boundaries
 				if (element.getTag("admin_level").equals("2")) {
 					Tags copyTags = new Tags();
-					for (Entry<String,String> tag : element.getEntryIteratable()) {
+					for (Entry<String,String> tag : element.getTagEntryIterator()) {
 						copyTags.put(tag.getKey(), tag.getValue());
 					}
 					String iso = locator.getCountryISOCode(copyTags);
@@ -137,7 +137,7 @@ public class BoundaryElementSaver extends ElementSaver {
 				}
 				
 				// and a name must be set (check only for a tag containing name)
-				for (Entry<String,String> tag : element.getEntryIteratable()) {
+				for (Entry<String,String> tag : element.getTagEntryIterator()) {
 					if (tag.getKey().contains("name")) {
 						return true;
 					}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java
index 0de2a04..7fc0db6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java
@@ -124,7 +124,7 @@ public class BoundaryFile2Gpx {
 				Path2D.Double path = new Path2D.Double();
 				List<Area> aList = areas.get(bId);
 				for (Area area : aList){
-					BoundaryUtil.addToPath(path, area);
+					path.append(area, false);
 				}
 				int i = 0;
 				List<BoundaryElement> bElements = BoundaryUtil.splitToElements(new Area(path),bId);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
index b001fb3..c734393 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
@@ -92,8 +92,10 @@ public class BoundaryLocationPreparer {
 	public HashMap<String, BoundaryLocationInfo> getPreparedLocationInfo(
 			List<Boundary> boundaries) {
 		HashMap<String, BoundaryLocationInfo> preparedLocationInfo = new HashMap<String, BoundaryLocationInfo> ();
-		for (Boundary b :boundaries){
-			preparedLocationInfo.put(b.getId(), parseTags(b.getTags())); 
+		if (boundaries != null){
+			for (Boundary b :boundaries){
+				preparedLocationInfo.put(b.getId(), parseTags(b.getTags())); 
+			}
 		}
 		return preparedLocationInfo;
 	}
@@ -175,7 +177,7 @@ public class BoundaryLocationPreparer {
 	 * @return the admin_level value. The value is UNSET_ADMIN_LEVEL if 
 	 * the conversion failed. 
 	 */
-	private int getAdminLevel(Tags tags) {
+	private static int getAdminLevel(Tags tags) {
 		String level = tags.get("admin_level");
 		if (level == null) {
 			return UNSET_ADMIN_LEVEL;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
index 9a60da8..66aa84d 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
@@ -188,7 +188,7 @@ public class BoundaryPreprocessor implements Runnable {
 			System.err.println(exp);
 			exp.printStackTrace();
 		}
-		
+		System.out.println("Bnd files converted in " +  (System.currentTimeMillis()-t1) + " ms");
 		log.info("Bnd files converted in", (System.currentTimeMillis()-t1), "ms");
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
index 552310a..61b0662 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
@@ -355,6 +355,9 @@ public class BoundaryQuadTree {
 						
 						if (area != null && area.isEmpty() == false)
 							root.add(area, refs, id, treePath);
+						else {
+							log.warn(refs,id,treePath,"invalid or empty or too small area");
+						}
 					} else {
 						log.debug("Bbox does not intersect. Skip",bSize);
 						inpStream.skipBytes(bSize);
@@ -668,7 +671,7 @@ public class BoundaryQuadTree {
 						continue;
 					Path2D.Double path = new Path2D.Double();
 					for (Area area : aList){
-						BoundaryUtil.addToPath(path, area);
+						path.append(area, false);
 					}
 					add(new Area(path), id, null);
 				}
@@ -688,7 +691,7 @@ public class BoundaryQuadTree {
 			Path2D.Double path = new Path2D.Double();
 			for (Entry <String, List<Area>> entry : areas.entrySet()){
 				for (Area area: entry.getValue()){
-					BoundaryUtil.addToPath(path,area);
+					path.append(area, false);
 				}
 			}
 			Area combinedArea = new Area(path);
@@ -1057,7 +1060,8 @@ public class BoundaryQuadTree {
 		 * the tags should be ignored.
 		 */
 		private boolean isValid(){
-			if (tagMask == 0 || area == null || area.isEmpty())
+			if (tagMask == 0 || area == null || area.isEmpty() 
+					|| area.getBounds2D().getWidth() <= BoundaryUtil.MIN_DIMENSION && area.getBounds2D().getHeight() <= BoundaryUtil.MIN_DIMENSION)
 				return false;
 			return true;
 		}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
index 1b2bf08..f02bd90 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
@@ -19,6 +19,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -53,7 +54,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 			if (outerResultArea == null) {
 				return null;
 			}
-			boundary = new Boundary(outerResultArea, this.getEntryIteratable(),"r"+this.getId());
+			boundary = new Boundary(outerResultArea, this.getTagEntryIterator(),"r"+this.getId());
 			outerResultArea = null;
 		}
 		return boundary;
@@ -279,7 +280,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 
 				for (Way outerWay : currentPolygon.polygon.getOriginalWays()) {
 					if (outmostPolygonProcessing) {
-						for (Entry<String, String> tag : outerWay.getEntryIteratable()) {
+						for (Entry<String, String> tag : outerWay.getTagEntryIterator()) {
 							outerTags.put(tag.getKey(), tag.getValue());
 						}
 						outmostPolygonProcessing = false;
@@ -337,7 +338,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		
 		if (hasStyleRelevantTags(this)) {
 			outerTags.clear();
-			for (Entry<String,String> mpTags : getEntryIteratable()) {
+			for (Entry<String,String> mpTags : getTagEntryIterator()) {
 				if ("type".equals(mpTags.getKey())==false) {
 					outerTags.put(mpTags.getKey(), mpTags.getValue());
 				}
@@ -377,14 +378,14 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		List<JoinedWay> unclosed = new ArrayList<JoinedWay>();
 
 		for (JoinedWay w : allWays) {
-			if (w.isClosed() == false) {
+			if (w.hasIdenticalEndPoints() == false) {
 				unclosed.add(w);
 			}
 		}
 		// try to connect ways lying outside or on the bbox
 		if (unclosed.size() >= 2) {
 			log.debug("Checking",unclosed.size(),"unclosed ways for connections outside the bbox");
-			Map<Coord, JoinedWay> outOfBboxPoints = new HashMap<Coord, JoinedWay>();
+			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<Coord, JoinedWay>();
 			
 			// check all ways for endpoints outside or on the bbox
 			for (JoinedWay w : unclosed) {
@@ -470,11 +471,10 @@ public class BoundaryRelation extends MultiPolygonRelation {
 						minCon.w1.closeWayArtificially();
 					} else {
 						log.debug("Connect", minCon.w1, "with", minCon.w2);
-
-						if (minCon.w1.getPoints().get(0).equals(minCon.c1)) {
+						if (minCon.w1.getPoints().get(0) == minCon.c1) {
 							Collections.reverse(minCon.w1.getPoints());
 						}
-						if (minCon.w2.getPoints().get(0).equals(minCon.c2) == false) {
+						if (minCon.w2.getPoints().get(0) != minCon.c2) {
 							Collections.reverse(minCon.w2.getPoints());
 						}
 
@@ -505,7 +505,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 	
 	protected void closeWays(ArrayList<JoinedWay> wayList) {
 		for (JoinedWay way : wayList) {
-			if (way.isClosed() || way.getPoints().size() < 3) {
+			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
 				continue;
 			}
 			Coord p1 = way.getPoints().get(0);
@@ -580,13 +580,13 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		ListIterator<JoinedWay> pIter = polygons.listIterator();
 		while (pIter.hasNext()) {
 			JoinedWay w = pIter.next();
-			if (w.isClosed() == false) {
+			Coord first = w.getPoints().get(0);
+			Coord last =  w.getPoints().get(w.getPoints().size() - 1);
+			if (first != last) {
 				// the way is not closed
 				// check if one of start/endpoint is out of the bounding box
 				// in this case it is too risky to close it
-				if (getBbox().contains(w.getPoints().get(0)) == false
-						|| getBbox().contains(
-								w.getPoints().get(w.getPoints().size() - 1)) == false) {
+				if (getBbox().contains(first) == false || getBbox().contains(last) == false) {
 					pIter.remove();
 				}
 			}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
index 265be81..6c30d6e 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
@@ -198,7 +198,7 @@ public class BoundarySaver {
 	}
 
 	private Map<String, Area> splitArea(Area areaToSplit) {
-		return splitArea(areaToSplit, new HashMap<String, Area>());
+		return splitArea(areaToSplit, new HashMap<String, Area>(), null);
 	}
 	
 	/**
@@ -207,45 +207,51 @@ public class BoundarySaver {
 	 * @param splits a map the splitted tiles are added to
 	 * @return the map with the splitted tiles
 	 */
-	private Map<String, Area> splitArea(Area areaToSplit, Map<String, Area> splits) {
+	private Map<String, Area> splitArea(Area areaToSplit, Map<String, Area> splits, Rectangle knownBbox) {
 		if (areaToSplit.isEmpty())
 			return splits;
+		Rectangle2D areaBounds;
 		
+		if (knownBbox != null){
+			// within recursion: use the calculated rectangle, not the area,
+			// as the latter might contain a spike that "looks" out of the bbox
+			// and can cause a stack overflow
+			areaBounds = knownBbox.getBounds2D();
+		}
+		else 
+			areaBounds = areaToSplit.getBounds2D();
+
 		// use high precision bounds with later rounding to avoid some little rounding
 		// errors (49999.99999999 instead of 50000.0)
-		Rectangle2D areaBounds = areaToSplit.getBounds2D();
 		int sMinLong = BoundaryUtil.getSplitBegin((int)Math.round(areaBounds.getMinX()));
 		int sMinLat = BoundaryUtil.getSplitBegin((int)Math.round(areaBounds.getMinY()));
 		int sMaxLong = BoundaryUtil.getSplitEnd((int)Math.round(areaBounds.getMaxX()));
 		int sMaxLat = BoundaryUtil.getSplitEnd((int)Math.round(areaBounds.getMaxY()));
-
+		
 		int dLon = sMaxLong- sMinLong;
 		int dLat = sMaxLat - sMinLat;
-		if (dLon > BoundaryUtil.RASTER || dLat > BoundaryUtil.RASTER) {
+		if (dLon > BoundaryUtil.RASTER || dLat > BoundaryUtil.RASTER){ 
 			// split into two halves
-			Area a1;
-			Area a2;
+			Rectangle r1,r2;
+			int middle; 
 			if (dLon > dLat) {
-				int midLon = BoundaryUtil.getSplitEnd(sMinLong+dLon/2);
-				a1 = new Area(new Rectangle(sMinLong, sMinLat, midLon-sMinLong, dLat));
-				a2 = new Area(new Rectangle(midLon, sMinLat, sMaxLong-midLon, dLat));
+				middle = BoundaryUtil.getSplitEnd(sMinLong+dLon/2);
+				r1 = new Rectangle(sMinLong, sMinLat, middle-sMinLong, dLat);
+				r2 = new Rectangle(middle, sMinLat, sMaxLong-middle, dLat);
 			} else {
-				int midLat = BoundaryUtil.getSplitEnd(sMinLat+dLat/2);
-				a1 = new Area(new Rectangle(sMinLong, sMinLat, dLon, midLat-sMinLat));
-				a2 = new Area(new Rectangle(sMinLong, midLat, dLon, sMaxLat-midLat));
+				middle = BoundaryUtil.getSplitEnd(sMinLat+dLat/2);
+				r1 = new Rectangle(sMinLong, sMinLat, dLon, middle-sMinLat);
+				r2 = new Rectangle(sMinLong, middle, dLon, sMaxLat-middle);
 			}
-
+			Area a = new Area(r1);
 			// intersect with the both halves
 			// and split both halves recursively
-
-			a1.intersect(areaToSplit);
-			splitArea(a1, splits);
-			// a1 is no longer needed => GC
-			a1 = null;
-			
-			a2.intersect(areaToSplit);
-			splitArea(a2, splits);
+			a.intersect(areaToSplit);
+			splitArea(a, splits, r1);
 			
+			a = new Area(r2);
+			a.intersect(areaToSplit);
+			splitArea(a, splits, r2);
 		} else {
 			// the area fully fits into one raster tile
 			splits.put(BoundaryUtil.getKey(sMinLat, sMinLong), areaToSplit);
@@ -465,6 +471,7 @@ public class BoundarySaver {
 					dos.writeInt(len);
 				}
 				// no break
+				//$FALL-THROUGH$
 			case PathIterator.SEG_MOVETO: 
 				len--;
 				for (int i = 0; i < 2; i++){
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
index 98a8bae..d4f1609 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
@@ -51,7 +51,7 @@ public class BoundaryUtil {
 	private static final int UNKNOWN_DATA_FORMAT = 0;
 	private static final int RAW_DATA_FORMAT_V1 = 2;
 	private static final int QUADTREE_DATA_FORMAT_V1 = 3;
-	
+	public static final double MIN_DIMENSION = 0.0000001;
 	/**
 	 * Calculate the polygons that describe the area.
 	 * @param area the Area instance
@@ -201,6 +201,8 @@ public class BoundaryUtil {
 		int windingRule = inpStream.readInt();
 		path.setWindingRule(windingRule);
 		int type = inpStream.readInt(); 
+		double minX = Double.MAX_VALUE,maxX = Double.MIN_VALUE;
+		double minY = Double.MAX_VALUE,maxY = Double.MIN_VALUE;
 		while (type >= 0) {
 			switch (type) {
 			case PathIterator.SEG_LINETO:
@@ -213,6 +215,14 @@ public class BoundaryUtil {
 						else
 							res[ii] = res[ii] + delta;
 					}
+					if (res[0] < minX)
+						minX = res[0];
+					if (res[0] > maxX)
+						maxX = res[0];
+					if (res[1] < minY)
+						minY = res[1];
+					if (res[1] > maxY)
+						maxY = res[1];
 					path.lineTo(res[0],res[1]);
 					--len;
 				}
@@ -225,6 +235,14 @@ public class BoundaryUtil {
 					else
 						res[ii] = res[ii] + delta;
 				}
+				if (res[0] < minX)
+					minX = res[0];
+				if (res[0] > maxX)
+					maxX = res[0];
+				if (res[1] < minY)
+					minY = res[1];
+				if (res[1] > maxY)
+					maxY = res[1];
 				path.moveTo(res[0],res[1]);
 				break;
 			case PathIterator.SEG_CLOSE:
@@ -242,7 +260,12 @@ public class BoundaryUtil {
 			log.error("Final type value != -1: " + type);
 		}
 		else{
-			return new Area(path);
+			if (maxX - minX >= MIN_DIMENSION || maxY - minY >= MIN_DIMENSION)
+				return new Area(path);
+			else {
+				// ignore micro area caused by rounding errors in awt area routines
+			}
+				
 		}
 		return null;
 	}
@@ -669,36 +692,6 @@ public class BoundaryUtil {
 	}
 
 	/**
-	 * Add the path of an area to an existing path. 
-	 * @param path
-	 * @param area
-	 */
-	public static void addToPath (Path2D.Double path, Area area){
-		PathIterator pit = area.getPathIterator(null);
-		double[] res = new double[6];
-		path.setWindingRule(pit.getWindingRule());
-		while (!pit.isDone()) {
-			int type = pit.currentSegment(res);
-			switch (type) {
-			case PathIterator.SEG_LINETO:
-				path.lineTo(res[0],res[1]);
-				break;
-			case PathIterator.SEG_MOVETO: 
-				path.moveTo(res[0],res[1]);
-				break;
-			case PathIterator.SEG_CLOSE:
-				path.closePath();
-				break;
-			default:
-				log.error("Unsupported path iterator type " + type
-						+ ". This is an mkgmap error.");
-			}
-
-			pit.next();
-		}
-	}
-
-	/**
 	 * read a varying length double. See BoundarySaver.writeVarDouble().
 	 * @param inp the already opened DataInputStream
 	 * @return the extracted double value
diff --git a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
index 0091875..5694877 100644
--- a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
@@ -21,7 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.combiners.OverviewMap;
 import uk.me.parabola.mkgmap.general.LevelInfo;
@@ -155,11 +155,12 @@ public class OverviewMapDataSource extends MapperBasedMapDataSource
 		addLine(road);
 	}
 
-	public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) {
-		getRoadNetwork().addRestriction(fromNode, toNode, viaNode, exceptMask);
+	public int addRestriction(GeneralRouteRestriction grr) {
+		log.error("This is not supposed to be called");
+		return 0;
 	}
 
-	public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
-		getRoadNetwork().addThroughRoute(junctionNodeId, roadIdA, roadIdB);
+	public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) {
+		log.error("This is not supposed to be called");
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
index 1ce7b8c..4c3b803 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
@@ -16,6 +16,9 @@
  */
 package uk.me.parabola.mkgmap.reader.polish;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
+
 import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -36,7 +39,7 @@ import java.util.Map;
 import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.net.RouteRestriction;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
@@ -97,7 +100,8 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 	// Use to decode labels if they are not in cp1252
 	private CharsetDecoder dec;
 
-    public boolean isFileSupported(String name) {
+	Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
+	public boolean isFileSupported(String name) {
 		// Supported if the extension is .mp
 		return name.endsWith(".mp") || name.endsWith(".MP") || name.endsWith(".mp.gz");
 	}
@@ -146,6 +150,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		}
 
 		addBackground(havePolygon4B);
+		coordMap = null;
 	}
 
 	public LevelInfo[] mapLevels() {
@@ -165,7 +170,6 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 	
 	@Override
 	public LevelInfo[] overviewMapLevels() {
-		// TODO Auto-generated method stub
 		return null;
 	}
 
@@ -240,14 +244,14 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 						polyline.setExtTypeAttributes(makeExtTypeAttributes());
 					final int maxPointsInLine = LineSplitterFilter.MAX_POINTS_IN_LINE;
 					if(points.size() > maxPointsInLine) {
-						List<Coord> segPoints = new ArrayList<Coord>(maxPointsInLine);
+						List<Coord> segPoints = new ArrayList<>(maxPointsInLine);
 						for(Coord p : points) {
 							segPoints.add(p);
 							if(segPoints.size() == maxPointsInLine) {
 								MapLine seg = polyline.copy();
 								seg.setPoints(segPoints);
 								mapper.addLine(seg);
-								segPoints = new ArrayList<Coord>(maxPointsInLine);
+								segPoints = new ArrayList<>(maxPointsInLine);
 								segPoints.add(p);
 							}
 						}
@@ -265,6 +269,10 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 			break;
 		case S_POLYGON:
 			if (points != null) {
+				if (points.get(0) != points.get(points.size()-1)){
+					// not closed, close it
+					points.add(points.get(0));  
+				}
 				shape.setPoints(points);
 				if(extraAttributes != null && shape.hasExtendedType())
 					shape.setExtTypeAttributes(makeExtTypeAttributes());
@@ -357,7 +365,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		}
 		else {
 			if(extraAttributes == null)
-				extraAttributes = new HashMap<String, String>();
+				extraAttributes = new HashMap<>();
 			extraAttributes.put(name, value);
 		}
 	}
@@ -402,14 +410,14 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 			roadHelper.addNumbers(value);
 		} else {
 			if (extraAttributes == null)
-				extraAttributes = new HashMap<String, String>();
+				extraAttributes = new HashMap<>();
 			extraAttributes.put(name, value);
 		}
 	}
 
 	private List<Coord> coordsFromString(String value) {
 		String[] ords = value.split("\\) *, *\\(");
-		List<Coord> points = new ArrayList<Coord>();
+		List<Coord> points = new ArrayList<>();
 
 		for (String s : ords) {
 			Coord co = makeCoord(s);
@@ -467,7 +475,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		}
 		else {
 			if(extraAttributes == null)
-				extraAttributes = new HashMap<String, String>();
+				extraAttributes = new HashMap<>();
 			extraAttributes.put(name, value);
 		}
 	}
@@ -677,11 +685,17 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 
 		Double f1 = Double.valueOf(fields[i]);
 		Double f2 = Double.valueOf(fields[i+1]);
-		return new Coord(f1, f2);
+		Coord co = new Coord(f1, f2);
+		long key = Utils.coord2Long(co);
+		Coord co2 = coordMap.get(key);
+		if (co2 != null)
+			return co2;
+		coordMap.put(key, co);
+		return co;
 	}
 
 	private ExtTypeAttributes makeExtTypeAttributes() {
-		Map<String, String> eta = new HashMap<String, String>();
+		Map<String, String> eta = new HashMap<>();
 		int colour = 0;
 		int style = 0;
 
@@ -776,8 +790,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
                             [END-RESTRICT]
                          */
                         restriction.setValid(false);
-                        log.info("Restrictions composed\n" +
-                                "from 3 roads are not yet supported\n");
+                        log.info("Restrictions composed from 3 or more roads are not yet supported");
                     }
                 } else if (name.equals("TraffRoads")) {
                     String[] traffRoads = value.split(",");
@@ -815,6 +828,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
      * will NOT apply for Delivery and Car.
      *
      * @param value Tag value
+     * @return the exceptMask in mkgmap internal format
      */
     private byte getRestrictionExceptionMask(String value) {
         String[] params = value.split(",");
@@ -824,28 +838,28 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
                 if ("1".equals(params[i])) {
                     switch(i) {
 					case 0:
-						// Mask is not known for Emergency.
+						exceptMask |= AccessTagsAndBits.EMERGENCY;
 						break;
 					case 1:
-						exceptMask |= RouteRestriction.EXCEPT_DELIVERY;
+						exceptMask |= AccessTagsAndBits.DELIVERY;
 						break;
 					case 2:
-						exceptMask |= RouteRestriction.EXCEPT_CAR;
+						exceptMask |= AccessTagsAndBits.CAR;
 						break;
 					case 3:
-						exceptMask |= RouteRestriction.EXCEPT_BUS;
+						exceptMask |= AccessTagsAndBits.BUS;
 						break;
 					case 4:
-						exceptMask |= RouteRestriction.EXCEPT_TAXI;
+						exceptMask |= AccessTagsAndBits.TAXI;
 						break;
 					case 5:
-						// Mask is not known for Pedestrian.
+						exceptMask |= AccessTagsAndBits.FOOT;
 						break;
 					case 6:
-						exceptMask |= RouteRestriction.EXCEPT_BICYCLE;
+						exceptMask |= AccessTagsAndBits.BIKE;
 						break;
 					case 7:
-						exceptMask |= RouteRestriction.EXCEPT_TRUCK;
+						exceptMask |= AccessTagsAndBits.TRUCK;
 						break;
                     }
                 }
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/PolishTurnRestriction.java b/src/uk/me/parabola/mkgmap/reader/polish/PolishTurnRestriction.java
index 6fd32ef..fbdb980 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/PolishTurnRestriction.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/PolishTurnRestriction.java
@@ -20,8 +20,10 @@ public class PolishTurnRestriction {
     private long nodId;
     private long toNodId;
     private long fromNodId;
+    private long viaNodId;
     private long roadIdA;
     private long roadIdB;
+    private long roadIdC;
     private byte exceptMask;
 
 
@@ -60,7 +62,15 @@ public class PolishTurnRestriction {
         this.fromNodId = fromNodId;
     }
 
-    public long getRoadIdA() {
+    public long getViaNodId() {
+		return viaNodId;
+	}
+
+	public void setViaNodId(long viaNodId) {
+		this.viaNodId = viaNodId;
+	}
+
+	public long getRoadIdA() {
         return roadIdA;
     }
 
@@ -76,7 +86,15 @@ public class PolishTurnRestriction {
         this.roadIdB = roadIdB;
     }
 
-    public byte getExceptMask() {
+	public long getRoadIdC() {
+		return roadIdC;
+	}
+
+	public void setRoadIdC(long roadIdC) {
+		this.roadIdC = roadIdC;
+	}
+
+	public byte getExceptMask() {
         return exceptMask;
     }
 
@@ -88,4 +106,5 @@ public class PolishTurnRestriction {
     public String toString() {
         return "TurnRestriction[FromNodId=" + fromNodId + ", ViaNodId=" + nodId + ", ToNodId=" + toNodId + "]";
     }
+
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/RestrictionHelper.java b/src/uk/me/parabola/mkgmap/reader/polish/RestrictionHelper.java
index fe643ab..672bed5 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/RestrictionHelper.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/RestrictionHelper.java
@@ -13,10 +13,11 @@
 package uk.me.parabola.mkgmap.reader.polish;
 
 import uk.me.parabola.imgfmt.app.CoordNode;
-import uk.me.parabola.log.Logger;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 import uk.me.parabola.mkgmap.general.MapDetails;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -30,7 +31,6 @@ import java.util.Map;
  * @author Supun Jayathilake
  */
 public class RestrictionHelper {
-    private static final Logger log = Logger.getLogger(RestrictionHelper.class);
 
     // Holds all collected restrictions.
     private final List<PolishTurnRestriction> allRestrictions = new ArrayList<PolishTurnRestriction>();
@@ -38,18 +38,20 @@ public class RestrictionHelper {
     public void processAndAddRestrictions(RoadHelper roadHelper, MapDetails mapper) {
         Map<Long, CoordNode> allNodes = roadHelper.getNodeCoords();
 
-		for (PolishTurnRestriction tr : allRestrictions) {
-            if (tr.isValid()) { // Process only the restrictions marked as valid.
-				CoordNode from = allNodes.get(tr.getFromNodId());
-				CoordNode to = allNodes.get(tr.getToNodId());
-				CoordNode via = allNodes.get(tr.getNodId());
-
-				if (from != null && to != null && via != null) {            // All nodes participating in the
-                    mapper.addRestriction(from, to, via, tr.getExceptMask()); // restriction should be part of the map
-                } else {
-                    log.error("");
-                }
-            }
+        for (PolishTurnRestriction tr : allRestrictions) {
+        	GeneralRouteRestriction grr = new GeneralRouteRestriction("not", tr.getExceptMask(),Long.toString(tr.getNodId()));
+        	grr.setFromNode(allNodes.get(tr.getFromNodId()));
+        	grr.setFromWayId(tr.getRoadIdA());
+        	grr.setToNode(allNodes.get(tr.getToNodId()));
+        	if (tr.getViaNodId() != 0){
+        		grr.setViaNodes(Arrays.asList(allNodes.get(tr.getNodId()),allNodes.get(tr.getViaNodId())));
+        		grr.setViaWayIds(Arrays.asList(tr.getRoadIdB()));
+        		grr.setToWayId(tr.getRoadIdC());
+        	} else {
+        		grr.setViaNodes(Arrays.asList(allNodes.get(tr.getNodId())));
+        		grr.setToWayId(tr.getRoadIdB());
+        	}
+        	mapper.addRestriction(grr); // restriction should be part of the map
         }
     }
 
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
index 4a2b75b..73bb9b6 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
@@ -24,6 +24,7 @@ import java.util.Map;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
 import uk.me.parabola.imgfmt.app.net.NumberStyle;
 import uk.me.parabola.imgfmt.app.net.Numbers;
 import uk.me.parabola.log.Logger;
@@ -41,16 +42,14 @@ import uk.me.parabola.mkgmap.general.MapRoad;
 class RoadHelper {
 	private static final Logger log = Logger.getLogger(RoadHelper.class);
 
-	private static final int NUM_ACCESS = 8;
-
 	// routing node store, persistent over resets
-	private final Map<Long, CoordNode> nodeCoords = new HashMap<Long, CoordNode>();
+	private final Map<Long, CoordNode> nodeCoords = new HashMap<>();
 
 	// Next node number to use for nodes constructed for house numbers. Persists over reset.
 	private long houseNumberNodeNumber = 16000000;
 
 	private int roadId;
-	private final List<NodeIndex> nodes = new ArrayList<NodeIndex>();
+	private final List<NodeIndex> nodes = new ArrayList<>();
 
 	private int speed;
 	private int roadClass;
@@ -58,7 +57,7 @@ class RoadHelper {
 	private boolean oneway;
 	private boolean toll;
 
-	private boolean[] access;
+	private byte mkgmapAccess;
 	private List<Numbers> numbers;
 
 	public RoadHelper() {
@@ -73,7 +72,6 @@ class RoadHelper {
 		roadClass = 0;
 		oneway = false;
 		toll = false;
-		access = new boolean[NUM_ACCESS];
 		numbers = null;
 	}
 
@@ -86,14 +84,41 @@ class RoadHelper {
 		nodes.add(new NodeIndex(f));
 	}
 
+	/**
+	 * @param param cgpsmapper manual:
+	 * RouteParam=speed,road_class,one_way,toll,
+	 * denied_emergency,denied_delivery,denied_car,denied_bus,denied_taxi,denied_pedestrain,denied_bicycle,denied_truck
+	 */
 	public void setParam(String param) {
 		String[] f = param.split(",");
 		speed = Integer.parseInt(f[0]);
+		if (speed < 0)
+			speed = 0;
+		if (speed > 7)
+			speed = 7;
 		roadClass = Integer.parseInt(f[1]);
+		if (roadClass < 0)
+			roadClass = 0;
+		if (roadClass > 4)
+			roadClass = 4;
 		oneway = Integer.parseInt(f[2]) > 0;
 		toll = Integer.parseInt(f[3]) > 0;
-		for (int j = 0; j < f.length - 4; j++)
-			access[j] = Integer.parseInt(f[4+j]) > 0;
+		byte noAccess = 0;
+		for (int j = 0; j < f.length - 4; j++){
+			if (Integer.parseInt(f[4+j]) == 0)
+				continue;
+			switch (j){
+			case 0: noAccess |= AccessTagsAndBits.EMERGENCY; break; 
+			case 1: noAccess |= AccessTagsAndBits.DELIVERY; break; 
+			case 2: noAccess |= AccessTagsAndBits.CAR; break; 
+			case 3: noAccess |= AccessTagsAndBits.BUS; break; 
+			case 4: noAccess |= AccessTagsAndBits.TAXI; break; 
+			case 5: noAccess |= AccessTagsAndBits.FOOT; break; 
+			case 6: noAccess |= AccessTagsAndBits.BIKE; break; 
+			case 7: noAccess |= AccessTagsAndBits.TRUCK; break; 
+			}
+		}
+		mkgmapAccess = (byte) ~noAccess; // we store the allowed vehicles
 	}
 
 	public MapRoad makeRoad(MapLine l) {
@@ -111,7 +136,7 @@ class RoadHelper {
 			road.setOneway();
 		if (toll)
 			road.setToll();
-		road.setAccess(access);
+		road.setAccess(mkgmapAccess);
 
 		if (numbers != null && !numbers.isEmpty()) {
 			convertNodesForHouseNumbers();
@@ -136,7 +161,7 @@ class RoadHelper {
 			if (id == 0) {
 				CoordNode node = nodeCoords.get((long) ni.nodeId);
 				if (node == null) {
-					node = new CoordNode(coord.getLatitude(), coord.getLongitude(), ni.nodeId, ni.boundary);
+					node = new CoordNode(coord, ni.nodeId, ni.boundary);
 					nodeCoords.put((long) ni.nodeId, node);
 				}
 				points.set(n, node);
@@ -225,7 +250,7 @@ class RoadHelper {
 
 	public void addNumbers(String value) {
 		if (numbers == null)
-			numbers = new ArrayList<Numbers>();
+			numbers = new ArrayList<>();
 		Numbers num = new Numbers(value);
 		if (num.getLeftNumberStyle() != NumberStyle.NONE || num.getRightNumberStyle() != NumberStyle.NONE)
 			numbers.add(num);
@@ -245,7 +270,7 @@ class RoadHelper {
 			if (f.length > 2)
 				boundary = Integer.parseInt(f[2]) > 0;
 			if (log.isDebugEnabled())
-				log.debug("ind=%d, node=%d, bound=%b\n", index, nodeId, boundary);
+				log.debug("ind=" + index + "node=" + nodeId + "bound=" + boundary);
 		}
 
 		public String toString() {
diff --git a/src/uk/me/parabola/mkgmap/scan/TokenScanner.java b/src/uk/me/parabola/mkgmap/scan/TokenScanner.java
index 99bde8b..5aab035 100644
--- a/src/uk/me/parabola/mkgmap/scan/TokenScanner.java
+++ b/src/uk/me/parabola/mkgmap/scan/TokenScanner.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 Steve Ratcliffe
+ * Copyright (C) 2008,2014 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
@@ -37,7 +37,7 @@ public class TokenScanner {
 	private final String fileName;
 	private int linenumber;
 
-	private final LinkedList<Token> tokens = new LinkedList<Token>();
+	private final LinkedList<Token> tokens = new LinkedList<>();
 
 	private boolean bol = true;
 
@@ -108,6 +108,7 @@ public class TokenScanner {
 	}
 
 	public boolean isEndOfFile() {
+		ensureTok();
 		if (tokens.isEmpty()) {
 			return isEOF;
 		} else {
@@ -121,7 +122,6 @@ public class TokenScanner {
 	 */
 	public void skipSpace() {
 		while (!isEndOfFile()) {
-			ensureTok();
 			if (tokens.peek().isValue(commentChar)) {
 				skipLine();
 				continue;
@@ -178,7 +178,12 @@ public class TokenScanner {
 		val.append((char) c);
 
 		TokType tt;
-		if (c == '\n') {
+		if (c == '\r') {
+			c = readChar();
+			if (c != '\n')
+				pushback = c;
+			tt = TokType.EOL;
+		} else if (c == '\n') {
 			tt = TokType.EOL;
 		} else if (isSpace(c)) {
 			while (isSpace(c = readChar()) && c != '\n')
@@ -230,6 +235,8 @@ public class TokenScanner {
 
 		try {
 			c = reader.read();
+			if (c == 0xfffd)
+				throw new SyntaxException(this, "Bad character in input, file probably not in utf-8");
 		} catch (IOException e) {
 			isEOF = true;
 			c = -1;
diff --git a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
index c6a2387..2d52945 100644
--- a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
@@ -15,7 +15,6 @@ package uk.me.parabola.mkgmap.sea.optional;
 
 import java.awt.Rectangle;
 import java.awt.geom.Area;
-import java.awt.geom.Path2D;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -31,10 +30,11 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
-import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.SeaGenerator;
 import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.util.Java2DConverter;
 
 import org.geotools.data.DataStore;
 import org.geotools.data.DataStoreFinder;
@@ -202,13 +202,11 @@ public class PrecompSeaGenerator {
 	 */
 	private Area convertToArea(Geometry geometry) {
 		Coordinate[] c = geometry.getCoordinates();
-		Path2D.Double path = new Path2D.Double();
-		path.moveTo(Utils.toMapUnit(c[0].x), Utils.toMapUnit(c[0].y));
-		for (int n = 1; n < c.length; n++) {
-			path.lineTo(Utils.toMapUnit(c[n].x), Utils.toMapUnit(c[n].y));
+		List<Coord> points = new ArrayList<>(c.length);
+		for (int n = 0; n < c.length; n++) {
+			points.add(new Coord(c[n].y, c[n].x));
 		}
-		path.closePath();
-		return new Area(path);
+		return Java2DConverter.createArea(points);
 	}
 
 	
diff --git a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java
index eff4131..2d02a9a 100644
--- a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java
+++ b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java
@@ -14,6 +14,7 @@
 package uk.me.parabola.mkgmap.sea.optional;
 
 import java.awt.geom.Area;
+import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
@@ -50,8 +51,8 @@ class PrecompSeaMerger implements Runnable {
 		public final Rectangle2D bounds;
 		public final BlockingQueue<Area> toMerge;
 		public final AtomicBoolean ready = new AtomicBoolean(false);
-		public Area tmpLandArea = new Area();
-		public final Area landArea = new Area();
+		public Path2D.Double tmpLandPath = new Path2D.Double();
+		public Area landArea = new Area();
 		private final String key;
 
 		public MergeData(Rectangle2D bounds, String key) {
@@ -100,6 +101,7 @@ class PrecompSeaMerger implements Runnable {
 		for (List<Coord> points : pointLists) {
 			Way w = new Way(FakeIdGenerator.makeFakeId(), points);
 			w.addTag("natural", naturalTag);
+			w.setClosedInOSM(true);
 			ways.add(w);
 		}
 		return ways;
@@ -116,20 +118,23 @@ class PrecompSeaMerger implements Runnable {
 		while (merge != null) {
 			Area landClipped = new Area(mergeData.bounds);
 			landClipped.intersect(merge);
-			mergeData.tmpLandArea.add(landClipped);
+			mergeData.tmpLandPath.append(landClipped, false);
 			merges++;
+			
 			if (merges % 500 == 0) {
 				// store each 500 polygons into a temporary area
 				// and merge them after that. That seems to be quicker
 				// than adding lots of very small areas to a highly 
 				// scattered area 
-				mergeData.landArea.add(mergeData.tmpLandArea);
-				mergeData.tmpLandArea = new Area();
+				Area tmpLandArea = new Area(mergeData.tmpLandPath);
+				mergeData.landArea.add(tmpLandArea);
+				mergeData.tmpLandPath.reset();
 			}
 
 			if (merges % 500 == 0) {
 				break;
 			}
+			
 			merge = mergeData.toMerge.poll();
 		}
 
@@ -139,9 +144,11 @@ class PrecompSeaMerger implements Runnable {
 			service.execute(this);
 			return;
 		}
-
-		mergeData.landArea.add(mergeData.tmpLandArea);
-		mergeData.tmpLandArea = null;
+		if (mergeData.landArea.isEmpty())
+			mergeData.landArea = new Area(mergeData.tmpLandPath);
+		else
+			mergeData.landArea.add(new Area(mergeData.tmpLandPath));
+		mergeData.tmpLandPath = null;
 
 		// post processing //
 		
@@ -165,7 +172,8 @@ class PrecompSeaMerger implements Runnable {
 			seaWay.addPoint(new Coord(90.0d, -180.0d));
 			seaWay.addPoint(new Coord(90.0d, 180.0d));
 			seaWay.addPoint(new Coord(-90.0d, 180.0d));
-			seaWay.addPoint(new Coord(-90.0d, -180.0d));
+			seaWay.addPoint(seaWay.getPoints().get(0)); // close shape
+			seaWay.setClosedInOSM(true);
 			landWays.put(seaWay.getId(), seaWay);
 
 			Relation rel = new GeneralRelation(FakeIdGenerator.makeFakeId());
diff --git a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaSaver.java b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaSaver.java
index 98e82c3..0270b9e 100644
--- a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaSaver.java
+++ b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaSaver.java
@@ -13,11 +13,12 @@
 
 package uk.me.parabola.mkgmap.sea.optional;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -33,7 +34,6 @@ import java.util.zip.GZIPOutputStream;
 
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.SeaGenerator;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.splitter.BinaryMapWriter;
@@ -111,26 +111,26 @@ class PrecompSeaSaver implements Runnable {
 
 					OSMWriter writer = createWriter(id, tileData.getKey());
 
-					Map<Coord, Long> coordIds = new HashMap<Coord, Long>();
-
-					List<uk.me.parabola.splitter.Way> pbfWays = new ArrayList<uk.me.parabola.splitter.Way>();
-
+					Long2ObjectOpenHashMap<Long> coordIds = new Long2ObjectOpenHashMap<>();
+					Map<Long,uk.me.parabola.splitter.Way> pbfWays = new TreeMap<Long, uk.me.parabola.splitter.Way>();
+					long maxNodeId = 1;
 					for (Way w : tileData.getValue()) {
 						uk.me.parabola.splitter.Way pbfWay = new uk.me.parabola.splitter.Way();
 						pbfWay.set(w.getId());
 						for (Entry<String, String> tag : w
-								.getEntryIteratable()) {
+								.getTagEntryIterator()) {
 							pbfWay.addTag(tag.getKey(), tag.getValue());
 						}
 						for (Coord c : w.getPoints()) {
 							Node n = new Node();
-							Long nodeId = coordIds.get(c);
+							long key = Utils.coord2Long(c);
+							Long nodeId = coordIds.get(key);
 							if (nodeId == null) {
-								nodeId = FakeIdGenerator.makeFakeId();
-								coordIds.put(c, nodeId);
+								nodeId = new Long(maxNodeId++);
+								coordIds.put(key, nodeId);
 								n.set(nodeId,
-										Utils.toDegrees(c.getLatitude()),
-										Utils.toDegrees(c.getLongitude()));
+										c.getLatDegrees(),
+										c.getLonDegrees());
 								try {
 									writer.write(n);
 								} catch (IOException exp) {
@@ -139,9 +139,9 @@ class PrecompSeaSaver implements Runnable {
 							}
 							pbfWay.addRef(nodeId);
 						}
-						pbfWays.add(pbfWay);
+						pbfWays.put(pbfWay.getId(),pbfWay);
 					}
-					for (uk.me.parabola.splitter.Way pbfWay : pbfWays) {
+					for (uk.me.parabola.splitter.Way pbfWay : pbfWays.values()) {
 						try {
 							writer.write(pbfWay);
 						} catch (IOException exp) {
diff --git a/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java b/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
index f29b0f4..e7da6a8 100644
--- a/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
+++ b/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
@@ -21,13 +21,11 @@ import java.io.Reader;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
 import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CodingErrorAction;
 import java.util.ArrayList;
 import java.util.List;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.app.srt.SRTFile;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.fs.ImgChannel;
@@ -53,12 +51,15 @@ import uk.me.parabola.mkgmap.scan.TokenScanner;
  * Secondary difference - different accents (eg a and a-acute)
  * Tertiary difference - different case (eg a and A)
  *
- * Primary differences are represented by an new 'code' line, or alternatively by the less-than separator.
+ * The sort order section begins with the word 'code'.
+ *
+ * Primary differences are represented by the less-than separator.
  * Secondary differences are represented by the semi-colon separator.
  * Tertiary differences are represented by the comma separator.
  *
- * Characters are represented by a two digit hex number that is the code point in the target code page. Alternatively
- * you can write the characters as themselves in <emphasis>unicode (utf-8)</emphasis> (the whole file must be in utf-8).
+ * Characters are represented in <emphasis>unicode (utf-8)</emphasis> (the whole file must be in utf-8).
+ * Or alternatively you can use a four hex-digit number. A few special punctuation characters must
+ * be written that way to prevent them being mistaken for separators.
  *
  * Example
  * <pre>
@@ -66,31 +67,24 @@ import uk.me.parabola.mkgmap.scan.TokenScanner;
  * codepage 1252
  * description "Example sort"
  * code a, A; â Â
- * code b, B
+ * < b, B
  * # Last two lines could be written:
  * # code a, A; â, Â < b, B
  * </pre>
  *
- * NOTE: as we always use upper case in an img file, the upper-lower case sorting differences are untested
- * and based on guess work. In particular you might expect that upper-case sorts before lowercase, but we
- * have the opposite.
- *
  * @author Steve Ratcliffe
  */
 public class SrtTextReader {
 
 	// States
 	private static final int IN_INITIAL = 0;
-	private static final int IN_CODE = 1;
+	private static final int IN_CHARACTER = 1;
 	private static final int IN_EXPAND = 2;
 
-	private int codepage;
-
 	// Data that is read in, the output of the reading operation
 	private final Sort sort = new Sort();
 
 	private CharsetEncoder encoder;
-	private CharsetDecoder decoder;
 
 	// Used during parsing.
 	private int pos1;
@@ -112,20 +106,27 @@ public class SrtTextReader {
 	}
 
 	/**
-	 * Find and read in the sort description for the given codepage.
+	 * Find and read in the default sort description for the given codepage.
 	 */
 	public static Sort sortForCodepage(int codepage) {
 		String name = "sort/cp" + codepage + ".txt";
 		InputStream is = Sort.class.getClassLoader().getResourceAsStream(name);
-		if (is == null)
-			return Sort.defaultSort(codepage);
+		if (is == null) {
+			if (codepage == 1252)
+				throw new ExitException("No sort description for code-page 1252 available");
+
+			Sort defaultSort = SrtTextReader.sortForCodepage(1252);
+			defaultSort.setCodepage(codepage);
+			defaultSort.setDescription("Default sort");
+			return defaultSort;
+		}
 
 		try {
 			InputStreamReader r = new InputStreamReader(is, "utf-8");
 			SrtTextReader sr = new SrtTextReader(r);
 			return sr.getSort();
 		} catch (IOException e) {
-			return Sort.defaultSort(codepage);
+			return SrtTextReader.sortForCodepage(codepage);
 		}
 	}
 
@@ -142,7 +143,7 @@ public class SrtTextReader {
 		resetPos();
 		state = IN_INITIAL;
 		while (!scanner.isEndOfFile()) {
-			Token tok = scanner.nextRawToken();
+			Token tok = scanner.nextToken();
 
 			// We deal with whole line comments here
 			if (tok.isValue("#")) {
@@ -154,14 +155,16 @@ public class SrtTextReader {
 			case IN_INITIAL:
 				initialState(scanner, tok);
 				break;
-			case IN_CODE:
-				codeState(scanner, tok);
+			case IN_CHARACTER:
+				characterState(scanner, tok);
 				break;
 			case IN_EXPAND:
 				expandState(scanner, tok);
 				break;
 			}
 		}
+
+		sort.finish();
 	}
 
 	/**
@@ -174,48 +177,61 @@ public class SrtTextReader {
 		String val = tok.getValue();
 		TokType type = tok.getType();
 		if (type == TokType.TEXT) {
-			if (val.equals("codepage")) {
-				codepage = scanner.nextInt();
+			switch (val) {
+			case "codepage":
+				int codepage = scanner.nextInt();
 				sort.setCodepage(codepage);
-				Charset charset = Charset.forName("cp" + codepage);
-				encoder = charset.newEncoder();
-				decoder = charset.newDecoder();
-				decoder.onMalformedInput(CodingErrorAction.REPORT);
-			} else if (val.equals("description")) {
+				encoder = sort.getCharset().newEncoder();
+				break;
+			case "description":
 				sort.setDescription(scanner.nextWord());
-			} else if (val.equals("id1")) {
+				break;
+			case "id1":
 				sort.setId1(scanner.nextInt());
-			} else if (val.equals("id2")) {
+				break;
+			case "id2":
 				sort.setId2(scanner.nextInt());
-			} else if (val.equals("code")) {
-				if (codepage == 0)
+				break;
+			case "multi":
+				sort.setMulti(true);
+				break;
+
+			case "code":  // The old name; use characters
+			case "characters":
+				if (encoder == null)
 					throw new SyntaxException(scanner, "Missing codepage declaration before code");
-				state = IN_CODE;
+				state = IN_CHARACTER;
 				scanner.skipSpace();
-			} else if (val.equals("expand")) {
+				break;
+			case "expand":
 				state = IN_EXPAND;
 				scanner.skipSpace();
-			} else {
+				break;
+			default:
 				throw new SyntaxException(scanner, "Unrecognised command " + val);
 			}
 		}
 	}
 
 	/**
-	 * Inside a code block that describes a set of characters that all sort
-	 * at the same major position.
+	 * Block consisting of characters and relations between them.
+	 *
+	 * The sort order is derived from this.
+	 *
 	 * @param scanner The scanner for more tokens.
 	 * @param tok The current token to process.
 	 */
-	private void codeState(TokenScanner scanner, Token tok) {
+	private void characterState(TokenScanner scanner, Token tok) {
 		String val = tok.getValue();
 		TokType type = tok.getType();
 		if (type == TokType.TEXT) {
-			if (val.equals("flags")) {
+			switch (val) {
+			case "flags":
 				scanner.validateNext("=");
 				cflags = scanner.nextWord();
 				// TODO not yet
-			} else if (val.equals("pos")) {
+				break;
+			case "pos": // Used to set the actual sort position value, not used any more
 				scanner.validateNext("=");
 				try {
 					int newPos = Integer.decode(scanner.nextWord());
@@ -225,58 +241,73 @@ public class SrtTextReader {
 				} catch (NumberFormatException e) {
 					throw new SyntaxException(scanner, "invalid integer for position");
 				}
-			} else if (val.equals("pos2")) {
+				break;
+			case "pos2": // Used to set the actual sort position value, not used any more
 				scanner.validateNext("=");
 				pos2 = Integer.decode(scanner.nextWord());
-			} else if (val.equals("pos3")) {
+				break;
+			case "pos3": // Used to set the actual sort position value, not used any more
 				scanner.validateNext("=");
 				pos3 = Integer.decode(scanner.nextWord());
-			} else if (val.length() == 1 || val.length() == 2) {
+				break;
+			case "code":  // the old name, use 'characters'
+			case "characters":
+				advancePos();
+				break;
+			case "expand":
+				//scanner.pushToken(tok);
+				state = IN_EXPAND;
+				break;
+			default:
 				addCharacter(scanner, val);
-			} else {
-				throw new SyntaxException(scanner, "Unexpected word " + val);
+				break;
 			}
 		} else if (type == TokType.SYMBOL) {
-			if (val.equals(",")) {
+			switch (val) {
+			case "=":
+				break;
+			case ",":
 				pos3++;
-			} else if (val.equals(";")) {
+				break;
+			case ";":
 				pos3 = 1;
 				pos2++;
-			} else if (val.equals("<")) {
+				break;
+			case "<":
 				advancePos();
-			} else {
+				break;
+			default:
 				addCharacter(scanner, val);
+				break;
 			}
 
-		} else if (type == TokType.EOL) {
-			state = IN_INITIAL;
-			advancePos();
 		}
 	}
 
 	/**
-	 * Within an 'expand' command. The whole command is read before
-	 * return, they can not span lines.
+	 * Within an 'expand' command. The whole command is read before return, they can not span
+	 * lines.
+	 *
 	 * @param tok The first token after the keyword.
 	 */
 	private void expandState(TokenScanner scanner, Token tok) {
 		String val = tok.getValue();
 
-		Code code = new Code(scanner, val).invoke();
+		Code code = new Code(scanner, val).read();
 
 		String s = scanner.nextValue();
 		if (!s.equals("to"))
 			throw new SyntaxException(scanner, "Expected the word 'to' in expand command");
 
-		List<Byte> expansionList = new ArrayList<Byte>();
+		List<Integer> expansionList = new ArrayList<>();
 		while (!scanner.isEndOfFile()) {
 			Token t = scanner.nextRawToken();
 			if (t.isEol())
 				break;
 			if (t.isWhiteSpace())
 				continue;
-			
-			Code r = new Code(scanner, t.getValue()).invoke();
+
+			Code r = new Code(scanner, t.getValue()).read();
 			expansionList.add(r.getBval());
 		}
 
@@ -289,38 +320,37 @@ public class SrtTextReader {
 	 * @param scanner Input scanner, for line number information.
 	 * @param val A single character string containing the character to be added. This will
 	 * be either a single character which is the unicode representation of the character, or
-	 * two characters which is the hex representation of the code point in the target codepage.
+	 * two or more characters which is the hex representation of the code point in the target codepage.
 	 */
 	private void addCharacter(TokenScanner scanner, String val) {
-		Code code = new Code(scanner, val).invoke();
-		setSortcode(code.getBval(), code.getCval());
+		Code code = new Code(scanner, val).read();
+		setSortcode(code.getBval());
 	}
 
 	/**
 	 * Set the sort code for the given 8-bit character.
-	 * @param b The 8-bit character in the character set of the codepage.
-	 * @param cval The same character in unicode.
+	 * @param ch The same character in unicode.
 	 */
-	private void setSortcode(byte b, char cval) {
-		int flags = charFlags(cval);
+	private void setSortcode(int ch) {
+		int flags = charFlags(ch);
 		if (cflags.contains("0"))
 			flags = 0;
 
-		sort.add(b, pos1, pos2, pos3, flags);
+		sort.add(ch, pos1, pos2, pos3, flags);
 		this.cflags = "";
 	}
 
 	/**
 	 * The flags that describe the kind of character. Known ones
 	 * are letter and digit. There may be others.
-	 * @param cval The actual character (unicode).
+	 * @param ch The actual character (unicode).
 	 * @return The flags that apply to it.
 	 */
-	private int charFlags(char cval) {
+	private int charFlags(int ch) {
 		int flags = 0;
-		if (Character.isLetter(cval) && (Character.getType(cval) & Character.MODIFIER_LETTER) == 0)
+		if (Character.isLetter(ch) && (Character.getType(ch) & Character.MODIFIER_LETTER) == 0)
 			flags = 1;
-		if (Character.isDigit(cval))
+		if (Character.isDigit(ch))
 			flags = 2;
 		return flags;
 	}
@@ -330,15 +360,18 @@ public class SrtTextReader {
 	 */
 	private void resetPos() {
 		pos1 = 0;
-		pos2 = 1;
-		pos3 = 1;
+		pos2 = 0;
+		pos3 = 0;
 	}
 
 	/**
 	 * Advance the major position value, resetting the minor position variables.
 	 */
 	private void advancePos() {
-		pos1 += pos2;
+		if (pos1 == 0)
+			pos1 = 1;
+		else
+			pos1 += pos2;
 		pos2 = 1;
 		pos3 = 1;
 	}
@@ -366,7 +399,6 @@ public class SrtTextReader {
 		SrtTextReader tr = new SrtTextReader(infile);
 		Sort sort1 = tr.getSort();
 		sf.setSort(sort1);
-		sf.setDescription(sort1.getDescription());
 		sf.write();
 		sf.close();
 		chan.close();
@@ -375,57 +407,71 @@ public class SrtTextReader {
 	/**
 	 * Helper to represent a code read from the file.
 	 *
-	 * You can write it in unicode, or as a two digit hex number.
+	 * You can write it in unicode, or as a hex number.
 	 * We work out what you wrote, and return both the code point in
 	 * the codepage and the unicode character form of the letter.
 	 */
 	private class Code {
 		private final TokenScanner scanner;
 		private final String val;
-		private byte bval;
-		private char cval;
+		private int cval;
+		private int bval;
 
 		public Code(TokenScanner scanner, String val) {
 			this.scanner = scanner;
 			this.val = val;
 		}
 
-		public byte getBval() {
+		/**
+		 * Get the character encoded in the code-page encoding.
+		 *
+		 * It will be one byte for the format-9 code pages cp1252 etc.
+		 * @return A character encoded in the code-page.
+		 */
+		public int getBval() {
 			return bval;
 		}
 
-		public char getCval() {
+		/**
+		 * Get the character in unicode.
+		 *
+		 * It will in general be a 2 byte value.
+		 *
+		 * @return The character expressed in unicode.
+		 */
+		public int getCval() {
 			return cval;
 		}
 
-		public Code invoke() {
+		public Code read() {
 			try {
 				if (val.length() == 1) {
-					CharBuffer cbuf = CharBuffer.wrap(val.toCharArray());
+					cval = val.charAt(0);
+				} else {
+					cval = Integer.parseInt(val, 16);
+				}
+
+				if (sort.isMulti()) {
+					bval = cval;
+				} else {
+					CharBuffer cbuf = CharBuffer.wrap(new char[] {(char) cval});
 					ByteBuffer out = encoder.encode(cbuf);
 					if (out.remaining() > 1)
 						throw new SyntaxException(scanner, "more than one character resulted from conversion of " + val);
 
-					bval = out.get();
-					cval = val.charAt(0);
-				} else {
-					bval = (byte) Integer.parseInt(val, 16);
-					ByteBuffer bin = ByteBuffer.allocate(1);
-					bin.put(bval);
-					bin.flip();
-					CharBuffer out = decoder.decode(bin);
-					cval = out.get();
+					bval = out.get() & 0xff;
 				}
-			} catch (CharacterCodingException e) {
-				throw new SyntaxException(scanner, "Not a valid character (" + val + ") in codepage");
+
 			} catch (NumberFormatException e) {
 				throw new SyntaxException(scanner, "Not a valid hex number " + val);
+			} catch (CharacterCodingException e) {
+				throw new SyntaxException(scanner, "Character not valid in character set '" + val + "'");
 			}
 			return this;
 		}
 
 		public String toString() {
-			return String.format("%02x: %c (0x%x)", bval, cval, (int) cval);
+			return String.format("%x", cval);
 		}
 	}
 }
diff --git a/src/uk/me/parabola/util/ElementQuadTreeNode.java b/src/uk/me/parabola/util/ElementQuadTreeNode.java
index a379e67..adcf6f1 100644
--- a/src/uk/me/parabola/util/ElementQuadTreeNode.java
+++ b/src/uk/me/parabola/util/ElementQuadTreeNode.java
@@ -67,14 +67,13 @@ public final class ElementQuadTreeNode {
 		}
 
 		public ElementQuadTreePolygon(List<Coord> points) {
-			this(new java.awt.geom.Area(Java2DConverter.createPolygon(points)));
+			this(Java2DConverter.createArea(points));
 		}
 
 		public ElementQuadTreePolygon(Collection<List<Coord>> polygonList) {
 			this.javaArea = new java.awt.geom.Area();
 			for (List<Coord> polygon : polygonList) {
-				javaArea.add(new java.awt.geom.Area(Java2DConverter
-						.createPolygon(polygon)));
+				javaArea.add(Java2DConverter.createArea(polygon));
 			}
 			Rectangle bboxRect = javaArea.getBounds();
 			bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
diff --git a/src/uk/me/parabola/util/GpxCreator.java b/src/uk/me/parabola/util/GpxCreator.java
index dbd7b0b..afcd005 100644
--- a/src/uk/me/parabola/util/GpxCreator.java
+++ b/src/uk/me/parabola/util/GpxCreator.java
@@ -3,7 +3,6 @@ package uk.me.parabola.util;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -40,6 +39,13 @@ public class GpxCreator {
 	private static void addWptPoint(PrintWriter pw, int latitude, int longitude) {
 		addGpxPoint(pw, "wpt", latitude, longitude);
 	}
+	private static void addTrkPoint(PrintWriter pw, Coord co) {
+		addGpxPoint(pw, "trkpt", co);
+	}
+
+	private static void addWptPoint(PrintWriter pw, Coord co) {
+		addGpxPoint(pw, "wpt", co);
+	}
 
 	private static void addGpxPoint(PrintWriter pw, String type, int latitude,
 			int longitude) {
@@ -52,15 +58,18 @@ public class GpxCreator {
 		pw.print("\"/>");
 	}
 
+	private static void addGpxPoint(PrintWriter pw, String type, Coord co) {
+		pw.print("<");
+		pw.print(type);
+		pw.print(" lat=\"");
+		pw.print(co.getLatDegrees());
+		pw.print("\" lon=\"");
+		pw.print(co.getLonDegrees());
+		pw.print("\"/>");
+	}
+
 	public static void createAreaGpx(String name, Area bbox) {
-		List<Coord> points = new ArrayList<Coord>(5);
-		points.add(new Coord(bbox.getMinLat(), bbox.getMinLong()));
-		points.add(new Coord(bbox.getMaxLat(), bbox.getMinLong()));
-		points.add(new Coord(bbox.getMaxLat(), bbox.getMaxLong()));
-		points.add(new Coord(bbox.getMinLat(), bbox.getMaxLong()));
-		points.add(new Coord(bbox.getMinLat(), bbox.getMinLong()));
-
-		GpxCreator.createGpx(name, points);
+		GpxCreator.createGpx(name, bbox.toCoords());
 	}
 	
 	/**
@@ -76,62 +85,78 @@ public class GpxCreator {
 	}
 
 	public static void createGpx(String name, List<Coord> points) {
-		try {
-			File f = new File(name);
-			if (f.getParentFile() != null) {
-				f.getParentFile().mkdirs();
-			}
-			PrintWriter pw = new PrintWriter(new FileWriter(name + ".gpx"));
-			pw.print("<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:gpxx=\"http://www.garmin.com/xmlschemas/GpxExtensions/v3\" ");
-			pw.print("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v1\" version=\"1.1\" ");
-			pw.print("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd\"> ");
+		for (int i = 0; i < 2; i++){
+			String fname = name + (i==0 ? "_mu":"_hp");
+			try {
+				File f = new File(fname);
+				if (f.getParentFile() != null) {
+					f.getParentFile().mkdirs();
+				}
+				PrintWriter pw = new PrintWriter(new FileWriter(fname + ".gpx"));
+				pw.print("<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"mkgmap\" ");
+				pw.print("version=\"1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
+				pw.print("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\"> ");
 
-			pw.print("<trk><name>");
-			pw.print(name);
-			pw.print("</name><trkseg>");
+				pw.print("<trk><name>");
+				pw.print(fname);
+				pw.print("</name><trkseg>");
 
-			for (Coord c : points) {
-				addTrkPoint(pw, c.getLatitude(), c.getLongitude());
+				for (Coord c : points) {
+					if (i == 0)
+						addTrkPoint(pw, c.getLatitude(), c.getLongitude());
+					else 
+						addTrkPoint(pw, c);
+				}
+				pw.print("</trkseg></trk>");
+				pw.print("</gpx>");
+				pw.close();
+			} catch (Exception exp) {
+				// only for debugging so just log
+				log.warn("Could not create gpx file ", fname);
 			}
-			pw.print("</trkseg></trk></gpx>");
-			pw.close();
-		} catch (Exception exp) {
-			// only for debugging so just log
-			log.warn("Could not create gpx file ", name);
 		}
 	}
 
 	public static void createGpx(String name, List<Coord> polygonpoints,
 			List<Coord> singlePoints) {
-		try {
-			File f = new File(name);
-			f.getParentFile().mkdirs();
-			PrintWriter pw = new PrintWriter(new FileWriter(name + ".gpx"));
-			pw.print("<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:gpxx=\"http://www.garmin.com/xmlschemas/GpxExtensions/v3\" ");
-			pw.print("xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v1\" version=\"1.1\" ");
-			pw.print("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd\"> ");
-
-			if (singlePoints != null) {
-				for (Coord c : singlePoints) {
-					addWptPoint(pw, c.getLatitude(), c.getLongitude());
+		for (int i = 0; i < 2; i++){
+			String fname = name + (i==0 ? "_mu":"_hp");
+			try {
+				File f = new File(fname);
+				f.getParentFile().mkdirs();
+				PrintWriter pw = new PrintWriter(new FileWriter(fname + ".gpx"));
+				pw.print("<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" creator=\"mkgmap\" ");
+				pw.print("version=\"1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ");
+				pw.print("xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\"> ");
+
+				if (singlePoints != null) {
+					for (Coord c : singlePoints) {
+						if (i == 0)
+							addWptPoint(pw, c.getLatitude(), c.getLongitude());
+						else 
+							addWptPoint(pw, c);
+					}
 				}
-			}
 
-			if (polygonpoints != null && polygonpoints.isEmpty() == false) {
-				pw.print("<trk><name>");
-				pw.print(name);
-				pw.print("</name><trkseg>");
-
-				for (Coord c : polygonpoints) {
-					addTrkPoint(pw, c.getLatitude(), c.getLongitude());
+				if (polygonpoints != null && polygonpoints.isEmpty() == false) {
+					pw.print("<trk><name>");
+					pw.print(fname);
+					pw.print("</name><trkseg>");
+
+					for (Coord c : polygonpoints) {
+						if (i == 0)
+							addTrkPoint(pw, c.getLatitude(), c.getLongitude());
+						else 
+							addTrkPoint(pw, c);
+					}
+					pw.print("</trkseg></trk>");
 				}
-				pw.print("</trkseg></trk>");
+				pw.print("</gpx>");
+				pw.close();
+			} catch (Exception exp) {
+				// only for debugging so just log
+				log.warn("Could not create gpx file ", fname);
 			}
-			pw.print("</gpx>");
-			pw.close();
-		} catch (Exception exp) {
-			// only for debugging so just log
-			log.warn("Could not create gpx file ", name);
 		}
 	}
 }
diff --git a/src/uk/me/parabola/util/Java2DConverter.java b/src/uk/me/parabola/util/Java2DConverter.java
index b4113ea..5879ff3 100644
--- a/src/uk/me/parabola/util/Java2DConverter.java
+++ b/src/uk/me/parabola/util/Java2DConverter.java
@@ -15,6 +15,7 @@ package uk.me.parabola.util;
 import java.awt.Polygon;
 import java.awt.Rectangle;
 import java.awt.geom.Area;
+import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -22,7 +23,6 @@ import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.reader.osm.Way;
 
 /**
  * This is a tool class that provides static methods to convert between mkgmap
@@ -43,9 +43,7 @@ public class Java2DConverter {
 	 * @return the converted Java2D area
 	 */
 	public static Area createBoundsArea(uk.me.parabola.imgfmt.app.Area bbox) {
-		return new Area(new Rectangle(bbox.getMinLong(), bbox.getMinLat(),
-				bbox.getMaxLong() - bbox.getMinLong(), bbox.getMaxLat()
-						- bbox.getMinLat()));
+		return createArea(bbox.toCoords());
 	}
 
 	/**
@@ -57,9 +55,7 @@ public class Java2DConverter {
 	 */
 	public static uk.me.parabola.imgfmt.app.Area createBbox(Area area) {
 		Rectangle areaBounds = area.getBounds();
-		return new uk.me.parabola.imgfmt.app.Area(areaBounds.y, areaBounds.x,
-				areaBounds.y + areaBounds.height, areaBounds.x
-						+ areaBounds.width);
+		return new uk.me.parabola.imgfmt.app.Area(areaBounds.y,areaBounds.x,(int) areaBounds.getMaxY(),(int) areaBounds.getMaxX());
 	}
 
 	/**
@@ -70,31 +66,55 @@ public class Java2DConverter {
 	 * @return the converted Java2D area
 	 */
 	public static Area createArea(List<Coord> polygonPoints) {
-		return new Area(createPolygon(polygonPoints));
+		if (polygonPoints.size()<3)
+			return new Area();
+		Path2D path = new Path2D.Double();
+		int n = polygonPoints.size();
+		if (polygonPoints.get(0).highPrecEquals(polygonPoints.get(n-1))){
+			// if first and last point are high-prec-equal, ignore last point 
+			// because we use closePath() to signal that
+			--n;
+		}
+		double lastLat = Integer.MAX_VALUE,lastLon = Integer.MAX_VALUE;
+		for (int i = 0; i < n; i++){
+			Coord co = polygonPoints.get(i);
+			int lat30 = co.getHighPrecLat();
+			int lon30 = co.getHighPrecLon();
+			double x = (double)lon30 / (1<<Coord.DELTA_SHIFT); 
+			double y = (double)lat30 / (1<<Coord.DELTA_SHIFT); 
+			if (i == 0)
+				path.moveTo(x, y);
+			else {
+				if (lastLon != lon30 || lastLat != lat30)
+					path.lineTo(x, y);
+			}
+			lastLon = lon30;
+			lastLat = lat30;
+		}
+		path.closePath();
+		return new Area(path);
+		
 	}
 
-	/**
-	 * Create a polygon from a list of points.
-	 * 
-	 * @param points list of points
-	 * @return the polygon
-	 */
-	public static Polygon createPolygon(List<Coord> points) {
+	public static Polygon createHighPrecPolygon(List<Coord> points) {
 		Polygon polygon = new Polygon();
 		for (Coord co : points) {
-			polygon.addPoint(co.getLongitude(), co.getLatitude());
+			polygon.addPoint(co.getHighPrecLon(), co.getHighPrecLat());
 		}
 		return polygon;
 	}
 
+	public static List<Area> areaToSingularAreas(Area area) {
+		return areaToSingularAreas(0, area);
+	}
 	/**
 	 * Convert an area that may contains multiple areas to a list of singular
-	 * areas
+	 * areas keeping the highest possible precision.
 	 * 
 	 * @param area an area
 	 * @return list of singular areas
 	 */
-	public static List<Area> areaToSingularAreas(Area area) {
+	private static List<Area> areaToSingularAreas(int depth, Area area) {
 		if (area.isEmpty()) {
 			return Collections.emptyList();
 		} else if (area.isSingular()) {
@@ -105,51 +125,39 @@ public class Java2DConverter {
 			// all ways in the area MUST define outer areas
 			// it is not possible that one of the areas define an inner segment
 
-			float[] res = new float[6];
+			double[] res = new double[6];
 			PathIterator pit = area.getPathIterator(null);
-			int prevLat = Integer.MIN_VALUE;
-			int prevLong = Integer.MIN_VALUE;
-
-			Polygon p = null;
+			Path2D path = null;
 			while (!pit.isDone()) {
 				int type = pit.currentSegment(res);
-				int lat = Math.round(res[1]);
-				int lon = Math.round(res[0]);
+				double lat = res[1];
+				double lon = res[0];
 
 				switch (type) {
 				case PathIterator.SEG_LINETO:
-					if (prevLat != lat || prevLong != lon) {
-						p.addPoint(lon, lat);
-					}
-					prevLat = lat;
-					prevLong = lon;
+					path.lineTo(lon, lat);
 					break;
 				case PathIterator.SEG_CLOSE:
-					p.addPoint(p.xpoints[0], p.ypoints[0]);
-					Area a = new Area(p);
+					path.closePath();
+					Area a = new Area(path);
 					if (!a.isEmpty()) {
-						singularAreas.add(a);
+						if (depth < 10 && !a.isSingular()){
+							// should not happen, but it does. Error in Area code?
+							singularAreas.addAll(areaToSingularAreas(depth+1,a));
+						}
+						else 
+							singularAreas.add(a);
 					}
-					p = null;
+					path = null;
 					break;
 				case PathIterator.SEG_MOVETO:
-					if (p != null) {
-						Area a2 = new Area(p);
-						if (!a2.isEmpty()) {
-							singularAreas.add(a2);
-						}
-					}
-					p = new Polygon();
-					p.addPoint(lon, lat);
+					path = new Path2D.Double();
+					path.moveTo(lon, lat);
 					break;
 				default:
 					log.error("Unsupported path iterator type " + type
 							+ ". This is an mkgmap error.");
 				}
-
-				prevLat = lat;
-				prevLong = lon;
-
 				pit.next();
 			}
 			return singularAreas;
@@ -158,7 +166,7 @@ public class Java2DConverter {
 
 	/**
 	 * Convert an area to an mkgmap way. The caller must ensure that the area is
-	 * singular. Otherwise only the first part of the area is converted.
+	 * singular. Otherwise only the first non-empty part of the area is converted.
 	 * 
 	 * @param area the area
 	 * @return a new mkgmap way
@@ -170,46 +178,54 @@ public class Java2DConverter {
 
 		List<Coord> points = null;
 
-		float[] res = new float[6];
+		double[] res = new double[6];
 		PathIterator pit = area.getPathIterator(null);
-		int prevLat = Integer.MIN_VALUE;
-		int prevLong = Integer.MIN_VALUE;
+		int prevLat30 = Integer.MIN_VALUE;
+		int prevLong30 = Integer.MIN_VALUE;
 
 		while (!pit.isDone()) {
 			int type = pit.currentSegment(res);
 
-			int lat = Math.round(res[1]);
-			int lon = Math.round(res[0]);
+			int lat30 = (int)Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
+			int lon30 = (int)Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
 
 			switch (type) {
 			case PathIterator.SEG_MOVETO:
 				if (points != null)
 					log.error("area not singular");
 				points = new ArrayList<Coord>();
-				points.add(new Coord(lat, lon));
+				points.add(Coord.makeHighPrecCoord(lat30, lon30));
 				break;
 			case PathIterator.SEG_LINETO:
 				assert points != null;
-				if (prevLat != lat || prevLong != lon) {
-					points.add(new Coord(lat, lon));
+				if (prevLat30 != lat30 || prevLong30 != lon30) {
+					points.add(Coord.makeHighPrecCoord(lat30, lon30));
 				}
 				break;
 			case PathIterator.SEG_CLOSE:
 				assert points != null;
-				if (points.get(0).equals(points.get(points.size() - 1))) { 
-					// replace equal last with closing point
-					points.set(points.size() - 1, points.get(0)); 
+				if (points.size() < 3)
+					points = null; 
+				else {
+					if (points.get(0).highPrecEquals(points.get(points.size() - 1))) { 
+						// replace equal last with closing point
+						points.set(points.size() - 1, points.get(0)); 
+					}
+					else
+						points.add(points.get(0)); // add closing point
+					if (points.size() < 4)
+						points = null;
+					else
+						return points;
 				}
-				else
-					points.add(points.get(0)); // add closing point
-				return points;
+				break;
 			default:
 				log.error("Unsupported path iterator type " + type
 						+ ". This is an mkgmap error.");
 			}
 
-			prevLat = lat;
-			prevLong = lon;
+			prevLat30 = lat30;
+			prevLong30 = lon30;
 
 			pit.next();
 		}
@@ -224,48 +240,39 @@ public class Java2DConverter {
 	 * holes in the polygon have counterclockwise order. 
 	 * 
 	 * @param area The area to be converted.
+	 * @param useHighPrec false: round coordinates to map units
 	 * @return a list of closed polygons
 	 */
 	public static List<List<Coord>> areaToShapes(java.awt.geom.Area area) {
 		List<List<Coord>> outputs = new ArrayList<List<Coord>>(4);
 
-		float[] res = new float[6];
+		double[] res = new double[6];
 		PathIterator pit = area.getPathIterator(null);
 		
-		// store float precision coords to check if the direction (cw/ccw)
-		// of a polygon changes due to conversion to int precision 
-		List<Float> floatLat = null;
-		List<Float>	floatLon = null;
-
 		List<Coord> coords = null;
 
-		int iPrevLat = Integer.MIN_VALUE;
-		int iPrevLong = Integer.MIN_VALUE;
+		int prevLat30 = Integer.MIN_VALUE;
+		int prevLong30 = Integer.MIN_VALUE;
 
 		while (!pit.isDone()) {
 			int type = pit.currentSegment(res);
 
-			float fLat = res[1];
-			float fLon = res[0];
-			int iLat = Math.round(fLat);
-			int iLon = Math.round(fLon);
+			int lat30 = (int) Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
+			int lon30 = (int) Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
 			
 			switch (type) {
 			case PathIterator.SEG_LINETO:
-				floatLat.add(fLat);
-				floatLon.add(fLon);
-
-				if (iPrevLat != iLat || iPrevLong != iLon) 
-					coords.add(new Coord(iLat,iLon));
+				if (prevLat30 != lat30 || prevLong30 != lon30) 
+					coords.add(Coord.makeHighPrecCoord(lat30, lon30));
 
-				iPrevLat = iLat;
-				iPrevLong = iLon;
+				prevLat30 = lat30;
+				prevLong30 = lon30;
 				break;
 			case PathIterator.SEG_MOVETO: 
 			case PathIterator.SEG_CLOSE:
 				if ((type == PathIterator.SEG_MOVETO && coords != null) || type == PathIterator.SEG_CLOSE) {
 					if (coords.size() > 2){
-						if (coords.get(0).equals(coords.get(coords.size() - 1))){ 
+						if (coords.get(0).highPrecEquals(coords.get(coords.size() - 1))){ 
 							// replace equal last with closing point
 							coords.set(coords.size() - 1, coords.get(0)); 
 						}
@@ -273,71 +280,18 @@ public class Java2DConverter {
 							coords.add(coords.get(0)); // add closing point
 					}
 					if (coords.size() > 3){
-						// use float values to verify area size calculations with higher precision
-						if (floatLat.size() > 2) {
-							if (floatLat.get(0).equals(floatLat.get(floatLat.size() - 1)) == false
-									|| floatLon.get(0).equals(floatLon.get(floatLon.size() - 1)) == false){ 
-								floatLat.add(floatLat.get(0));
-								floatLon.add(floatLon.get(0));
-							}
-						}
-
-						// calculate area size with float values 
-						double realAreaSize = 0;
-						float pf1Lat = floatLat.get(0);
-						float pf1Lon = floatLon.get(0);
-						for(int i = 1; i < floatLat.size(); i++) {
-							float pf2Lat = floatLat.get(i);
-							float pf2Lon = floatLon.get(i);
-							realAreaSize += ((double)pf1Lon * pf2Lat - 
-									(double)pf2Lon * pf1Lat);
-							pf1Lat = pf2Lat;
-							pf1Lon = pf2Lon;
-						}
-						
-					
-						// Check if the polygon with float precision has the same direction
-						// than the polygon with int precision. If not reverse the int precision
-						// polygon. Its direction has changed artificially by the int conversion.
-						boolean floatPrecClockwise = (realAreaSize <= 0);
-						if (Way.clockwise(coords) != floatPrecClockwise) {
-							
-							if (log.isInfoEnabled()) {
-								log.info("Converting area to int precision changes direction. Will correct that.");
-								StringBuilder sb = new StringBuilder("[");
-								for (int i = 0; i < floatLat.size(); i++) {
-									if (i > 0) {
-										sb.append(", ");
-									}
-									sb.append(floatLat.get(i));
-									sb.append("/");
-									sb.append(floatLon.get(i));
-								}
-								sb.append("]");
-								log.info("Float area: ", sb);
-								log.info("Int area: ", coords);
-							}
-							
-							Collections.reverse(coords);
-						}
 						outputs.add(coords);
 					}
 				}
 				if (type == PathIterator.SEG_MOVETO){
-					floatLat= new ArrayList<Float>();
-					floatLon= new ArrayList<Float>();
-					floatLat.add(fLat);
-					floatLon.add(fLon);
 					coords = new ArrayList<Coord>();
-					coords.add(new Coord(iLat,iLon));
-					iPrevLat = iLat;
-					iPrevLong = iLon;
+					coords.add(Coord.makeHighPrecCoord(lat30, lon30));
+					prevLat30 = lat30;
+					prevLong30 = lon30;
 				} else {
-					floatLat= null;
-					floatLon= null;
 					coords = null;
-					iPrevLat = Integer.MIN_VALUE;
-					iPrevLong = Integer.MIN_VALUE;
+					prevLat30 = Integer.MIN_VALUE;
+					prevLong30 = Integer.MIN_VALUE;
 				}
 				break;
 			default:
@@ -350,4 +304,6 @@ public class Java2DConverter {
 
 		return outputs;
 	}
-}
+
+
+} 
\ No newline at end of file
diff --git a/src/uk/me/parabola/util/MultiHashMap.java b/src/uk/me/parabola/util/MultiHashMap.java
index d870cdf..ee1b523 100644
--- a/src/uk/me/parabola/util/MultiHashMap.java
+++ b/src/uk/me/parabola/util/MultiHashMap.java
@@ -40,10 +40,8 @@ public class MultiHashMap<K,V> extends HashMap<K,List<V>> {
 	}
 
 
-	public V add(K key, V value )
-	{
-	    
-	    List<V> values = super.get(key);
+	public V add(K key, V value ) {
+		List<V> values = super.get(key);
 	    if (values == null ) {
 	        values = new LinkedList<V>();
 	        super.put( key, values );
@@ -54,13 +52,11 @@ public class MultiHashMap<K,V> extends HashMap<K,List<V>> {
 	    return ( results ? value : null );
 	}
 
-	public V remove(K key, V value )
-	{
-	    
+	public V removeMapping(K key, V value) {
 	    List<V> values = super.get(key);
 	    if (values == null )
 			return null;
-	
+
 	    values.remove(value);
 		
 		if (values.isEmpty())
diff --git a/src/uk/me/parabola/util/MultiIdentityHashMap.java b/src/uk/me/parabola/util/MultiIdentityHashMap.java
index 47b176d..bc0d605 100644
--- a/src/uk/me/parabola/util/MultiIdentityHashMap.java
+++ b/src/uk/me/parabola/util/MultiIdentityHashMap.java
@@ -40,10 +40,8 @@ public class MultiIdentityHashMap<K,V> extends IdentityHashMap<K,List<V>> {
 	}
 
 
-	public V add(K key, V value )
-	{
-	    
-	    List<V> values = super.get(key);
+	public V add(K key, V value ) {
+		List<V> values = super.get(key);
 	    if (values == null ) {
 	        values = new LinkedList<V>();
 	        super.put( key, values );
@@ -54,10 +52,8 @@ public class MultiIdentityHashMap<K,V> extends IdentityHashMap<K,List<V>> {
 	    return ( results ? value : null );
 	}
 
-	public V remove(K key, V value )
-	{
-	    
-	    List<V> values = super.get(key);
+	public V removeMapping(K key, V value) {
+		List<V> values = super.get(key);
 	    if (values == null )
 			return null;
 	
diff --git a/src/uk/me/parabola/util/QuadTree.java b/src/uk/me/parabola/util/QuadTree.java
index 954dd64..b1e32c2 100644
--- a/src/uk/me/parabola/util/QuadTree.java
+++ b/src/uk/me/parabola/util/QuadTree.java
@@ -80,48 +80,18 @@ public class QuadTree {
 		return itemCount;
 	}
 
-	private boolean isCloseToPolygon(Coord point, List<Coord> polygon,
+	private static boolean isCloseToPolygon(Coord point, List<Coord> polygon,
 			int gap) {
 		Iterator<Coord> polyIter = polygon.iterator();
 		Coord c2 = polyIter.next();
 		while (polyIter.hasNext()) {
 			Coord c1 = c2;
 			c2 = polyIter.next();
-			double dist = distanceToSegment(c1, c2, point);
+			double dist = point.shortestDistToLineSegment(c1, c2);
 			if (dist <= gap) {
 				return true;
 			}
 		}
 		return false;
 	}
-
-	/**
-	 * Calculates the distance to the given segment in meter.
-	 * @param spoint1 segment point 1
-	 * @param spoint2 segment point 2
-	 * @param point point
-	 * @return the distance in meter
-	 */
-	private double distanceToSegment(Coord spoint1, Coord spoint2, Coord point) {
-
-		double dx = spoint2.getLongitude() - spoint1.getLongitude();
-		double dy = spoint2.getLatitude() - spoint1.getLatitude();
-
-		if ((dx == 0) && (dy == 0)) {
-			return spoint1.distance(point);
-		}
-
-		double frac = ((point.getLongitude() - spoint1.getLongitude()) * dx + (point
-				.getLatitude() - spoint1.getLatitude()) * dy)
-				/ (dx * dx + dy * dy);
-
-		if (frac < 0) {
-			return spoint1.distance(point);
-		} else if (frac > 1) {
-			return spoint2.distance(point);
-		} else {
-			return spoint1.makeBetweenPoint(spoint2, frac).distance(point);
-		}
-
-	}
 }
diff --git a/src/uk/me/parabola/util/QuadTreeNode.java b/src/uk/me/parabola/util/QuadTreeNode.java
index b494ebc..1ce8d0d 100644
--- a/src/uk/me/parabola/util/QuadTreeNode.java
+++ b/src/uk/me/parabola/util/QuadTreeNode.java
@@ -36,14 +36,13 @@ public class QuadTreeNode {
 		}
 
 		public QuadTreePolygon(List<Coord> points) {
-			this(new java.awt.geom.Area(Java2DConverter.createPolygon(points)));
+			this(Java2DConverter.createArea(points));
 		}
 
 		public QuadTreePolygon(Collection<List<Coord>> polygonList) {
 			this.javaArea = new java.awt.geom.Area();
 			for (List<Coord> polygon : polygonList) {
-				javaArea.add(new java.awt.geom.Area(Java2DConverter
-						.createPolygon(polygon)));
+				javaArea.add(Java2DConverter.createArea(polygon));
 			}
 			Rectangle bboxRect = javaArea.getBounds();
 			bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y
diff --git a/test/func/SimpleTest.java b/test/func/SimpleTest.java
index 7c6bc56..f93a24d 100644
--- a/test/func/SimpleTest.java
+++ b/test/func/SimpleTest.java
@@ -53,7 +53,7 @@ public class SimpleTest extends Base {
 	@Test
 	public void testBasic() throws FileNotFoundException {
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--preserve-element-order",
 				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz"
@@ -77,7 +77,7 @@ public class SimpleTest extends Base {
 
 	@Test
 	public void testNoSuchFile() {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				"no-such-file-xyz.osm",
 		});
 		assertFalse("no file generated", new File(Args.DEF_MAP_FILENAME).exists());
@@ -85,7 +85,7 @@ public class SimpleTest extends Base {
 
 	@Test
 	public void testPolish() throws FileNotFoundException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				Args.TEST_RESOURCE_MP + "test1.mp"
 		});
@@ -99,18 +99,22 @@ public class SimpleTest extends Base {
 			String ext = ent.getExt();
 
 			int size = ent.getSize();
-			if (ext.equals("RGN")) {
+			switch (ext) {
+			case "RGN":
 				count++;
 				System.out.println("RGN size " + size);
-				assertThat("RGN size", size, new RangeMatcher(2756));
-			} else if (ext.equals("TRE")) {
+				assertThat("RGN size", size, new RangeMatcher(2702));
+				break;
+			case "TRE":
 				count++;
 				System.out.println("TRE size " + size);
 				// Size varies depending on svn modified status
 				assertThat("TRE size", size, new RangeMatcher(769, 2));
-			} else if (ext.equals("LBL")) {
+				break;
+			case "LBL":
 				count++;
-				assertEquals("LBL size", 985, size);
+				assertEquals("LBL size", 989, size);
+				break;
 			}
 		}
 		assertTrue("enough checks run", count >= 3);
diff --git a/test/func/StructureTest.java b/test/func/StructureTest.java
index 523b632..713c3d3 100644
--- a/test/func/StructureTest.java
+++ b/test/func/StructureTest.java
@@ -72,7 +72,7 @@ public class StructureTest {
 	public static void init() throws FileNotFoundException {
 		TestUtils.deleteOutputFiles();
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz"
 		});
diff --git a/test/func/files/GmapsuppTest.java b/test/func/files/GmapsuppTest.java
index 7d1a609..6870cee 100644
--- a/test/func/files/GmapsuppTest.java
+++ b/test/func/files/GmapsuppTest.java
@@ -47,7 +47,7 @@ public class GmapsuppTest extends Base {
 		File f = new File(GMAPSUPP_IMG);
 		assertFalse("does not pre-exist", f.exists());
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				Args.TEST_RESOURCE_IMG + "63240001.img",
@@ -72,7 +72,7 @@ public class GmapsuppTest extends Base {
 	 */
 	@Test
 	public void testMpsFile() throws IOException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--family-id=150",
@@ -108,7 +108,7 @@ public class GmapsuppTest extends Base {
 	@Test
 	public void testCombiningSupps() throws IOException {
 		TestUtils.registerFile("g1.img", "g2.img");
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--family-id=150",
@@ -122,7 +122,7 @@ public class GmapsuppTest extends Base {
 		File f = new File("gmapsupp.img");
 		f.renameTo(new File("g1.img"));
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--family-id=152",
@@ -134,7 +134,7 @@ public class GmapsuppTest extends Base {
 		});
 		f.renameTo(new File("g2.img"));
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"g1.img",
@@ -173,7 +173,7 @@ public class GmapsuppTest extends Base {
 	 */
 	@Test
 	public void testDifferentFamilies() throws IOException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 
@@ -212,7 +212,7 @@ public class GmapsuppTest extends Base {
 	 */
 	@Test
 	public void testProductBlocks() throws IOException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 
@@ -259,7 +259,7 @@ public class GmapsuppTest extends Base {
 	 */
 	@Test
 	public void testProductWithSeveralMaps() throws IOException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 						Args.TEST_STYLE_ARG,
 						"--gmapsupp",
 
@@ -279,7 +279,7 @@ public class GmapsuppTest extends Base {
 	@Test
 	public void testWithIndex() throws IOException {
 		new File("osmmap_mdr.img").delete();
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--index",
@@ -315,7 +315,7 @@ public class GmapsuppTest extends Base {
 	public void testWithTwoIndexes() throws IOException {
 		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--index",
@@ -355,7 +355,7 @@ public class GmapsuppTest extends Base {
 	public void testTwoFamilyIndex() throws IOException {
 		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--index",
@@ -407,14 +407,14 @@ public class GmapsuppTest extends Base {
 	public void testImplicitCodePageIndex() throws IOException {
 		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--code-page=1256",
 
 				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz",
 		});
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--gmapsupp",
 				"--index",
@@ -442,7 +442,7 @@ public class GmapsuppTest extends Base {
 	public void testWarningOnMismatchedCodePages() throws IOException {
 		TestUtils.registerFile("osmmap.img");
 
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--route",
 				"--code-page=1256",
diff --git a/test/func/files/TdbTest.java b/test/func/files/TdbTest.java
index e881f44..8b60ccf 100644
--- a/test/func/files/TdbTest.java
+++ b/test/func/files/TdbTest.java
@@ -36,7 +36,7 @@ public class TdbTest extends Base {
 	 */
 	@Test
 	public void testBasic() throws IOException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--tdbfile",
 				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz",
@@ -58,7 +58,7 @@ public class TdbTest extends Base {
 	public void testOptions() {
 		int thisMapname = 11112222;
 		TestUtils.registerFile(thisMapname + ".img", thisMapname + ".tdb");
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
 				"--tdbfile",
 				"--overview-mapname=" + thisMapname,
diff --git a/test/func/lib/TestDataSource.java b/test/func/lib/TestDataSource.java
index 74047f2..8d7bcec 100644
--- a/test/func/lib/TestDataSource.java
+++ b/test/func/lib/TestDataSource.java
@@ -19,7 +19,7 @@ import uk.me.parabola.mkgmap.general.MapDataSource;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapShape;
-import uk.me.parabola.mkgmap.general.RoadNetwork;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 
diff --git a/test/func/lib/TestUtils.java b/test/func/lib/TestUtils.java
index ecf141f..717873f 100644
--- a/test/func/lib/TestUtils.java
+++ b/test/func/lib/TestUtils.java
@@ -119,7 +119,7 @@ public class TestUtils {
 		try {
 			System.setOut(out);
 			System.setErr(err);
-			Main.main(args.toArray(new String[args.size()]));
+			Main.mainNoSystemExit(args.toArray(new String[args.size()]));
 		} finally {
 			out.close();
 			err.close();
diff --git a/test/func/route/SimpleRouteTest.java b/test/func/route/SimpleRouteTest.java
index 35db3d2..8fae3b4 100644
--- a/test/func/route/SimpleRouteTest.java
+++ b/test/func/route/SimpleRouteTest.java
@@ -36,8 +36,9 @@ public class SimpleRouteTest extends Base {
 	 */
 	@Test
 	public void testSize() throws FileNotFoundException {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				Args.TEST_STYLE_ARG,
+				"--preserve-element-order",
 				"--route",
 				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz",
 				Args.TEST_RESOURCE_MP + "test1.mp"
@@ -52,24 +53,30 @@ public class SimpleRouteTest extends Base {
 			String ext = ent.getExt();
 
 			int size = ent.getSize();
-			if (ext.equals("RGN")) {
+			switch (ext) {
+			case "RGN":
 				count++;
 				System.out.println("RGN size " + size);
-				assertThat("RGN size", size, new RangeMatcher(130140));
-			} else if (ext.equals("TRE")) {
+				assertThat("RGN size", size, new RangeMatcher(128717));
+				break;
+			case "TRE":
 				count++;
 				System.out.println("TRE size " + size);
 				// Size varies depending on svn modified status
-				assertThat("TRE size", size, new RangeMatcher(1478, 2));
-			} else if (ext.equals("LBL")) {
+				assertThat("TRE size", size, new RangeMatcher(1554, 2));
+				break;
+			case "LBL":
 				count++;
-				assertEquals("LBL size", 28730, size);
-			} else if (ext.equals("NET")) {
+				assertEquals("LBL size", 28744, size);
+				break;
+			case "NET":
 				count++;
-				assertEquals("NET size", 66804, size);
-			} else if (ext.equals("NOD")) {
+				assertEquals("NET size", 66851, size);
+				break;
+			case "NOD":
 				count++;
-				assertEquals("NOD size", 186800, size);
+				assertEquals("NOD size", 170201, size);
+				break;
 			}
 		}
 		assertTrue("enough checks run", count == 5);
@@ -83,24 +90,30 @@ public class SimpleRouteTest extends Base {
 			String ext = ent.getExt();
 
 			int size = ent.getSize();
-			if (ext.equals("RGN")) {
+			switch (ext) {
+			case "RGN":
 				count++;
-        System.out.println("RGN size " + size);
-				assertThat("RGN size", size, new RangeMatcher(2780));
-			} else if (ext.equals("TRE")) {
+				System.out.println("RGN size " + size);
+				assertThat("RGN size", size, new RangeMatcher(2726));
+				break;
+			case "TRE":
 				count++;
-        System.out.println("TRE size " + size);
+				System.out.println("TRE size " + size);
 				// Size varies depending on svn modified status
 				assertThat("TRE size", size, new RangeMatcher(769, 2));
-			} else if (ext.equals("LBL")) {
+				break;
+			case "LBL":
 				count++;
-				assertEquals("LBL size", 985, size);
-			} else if (ext.equals("NET")) {
+				assertEquals("LBL size", 989, size);
+				break;
+			case "NET":
 				count++;
 				assertEquals("NET size", 1280, size);
-			} else if (ext.equals("NOD")) {
+				break;
+			case "NOD":
 				count++;
-				assertEquals("NOD size", 3114, size);
+				assertEquals("NOD size", 3584, size);
+				break;
 			}
 		}
 		assertTrue("enough checks run", count == 5);
diff --git a/test/func/sources/TestSourceTest.java b/test/func/sources/TestSourceTest.java
index 14db6cf..8576c19 100644
--- a/test/func/sources/TestSourceTest.java
+++ b/test/func/sources/TestSourceTest.java
@@ -33,7 +33,7 @@ public class TestSourceTest extends Base {
 	@Test
 	public void testAllElements() {
 		checkNoStdFile();
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				"test-map:all-elements"
 		});
 		checkStdFile();
@@ -44,7 +44,7 @@ public class TestSourceTest extends Base {
 	 */
 	@Test
 	public void testAllPoints() {
-		Main.main(new String[]{
+		Main.mainNoSystemExit(new String[]{
 				"test-map:test-points"
 		});
 		checkStdFile();
diff --git a/test/main/NumberRangeTest.java b/test/main/NumberRangeTest.java
index 3685fe5..819458e 100644
--- a/test/main/NumberRangeTest.java
+++ b/test/main/NumberRangeTest.java
@@ -20,7 +20,6 @@
 package main;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 
diff --git a/test/main/SortTest.java b/test/main/SortTest.java
new file mode 100644
index 0000000..41cb6a4
--- /dev/null
+++ b/test/main/SortTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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 main;
+
+import java.nio.charset.Charset;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.text.ParseException;
+import java.text.RuleBasedCollator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import uk.me.parabola.imgfmt.app.srt.Sort;
+import uk.me.parabola.imgfmt.app.srt.SortKey;
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
+
+/**
+ * Test to compare sorting results and timings between sort keys and collator.
+ *
+ * Also have tested against java7 RuleBasedCollator and the ICU one.
+ *
+ * In general our implementation is fastest by a long way; key based sort 3 times faster, collation
+ * based sort even more so.  The java collator does not result in the same sort as using sort keys.
+ *
+ * I also tried out the ICU collation with mixed results. Could not get the correct desired results with
+ * it.  It was not faster than our implementation for a 1252 cp sort.
+ */
+public class SortTest {
+
+	private static final int LIST_SIZE = 500000;
+	private Sort sort;
+	private boolean time;
+	private boolean fullOutput;
+	private boolean quiet;
+	private boolean unicode;
+
+	private void test() throws Exception {
+		sort = SrtTextReader.sortForCodepage(unicode? 65001: 1252);
+
+		//testPairs();
+
+		Charset charset = sort.getCharset();
+
+		Random rand = new Random(21909278L);
+
+		List<String> list = createList(rand, charset);
+
+		if (time) {
+			// Run a few times without output, to warm up
+			compareLists(sortWithKeys(list), sortWithKeys(list));
+			compareLists(sortWithCollator(list), sortWithCollator(list));
+			compareLists(sortWithJavaKeys(list), sortWithJavaKeys(list));
+			compareLists(sortWithJavaCollator(list), sortWithJavaCollator(list));
+			// re-create the list to make sure it wasn't too optimised to the data
+			list = createList(rand, charset);
+		}
+
+		System.out.println("Compare key sort and collator sort");
+		int n = compareLists(sortWithKeys(list), sortWithCollator(list));
+		System.out.println("N errors " + n);
+
+		if (!unicode) {
+			System.out.println("Compare our sort with java sort");
+			n = compareLists(sortWithKeys(list), sortWithJavaKeys(list));
+			System.out.println("N errors " + n);
+		}
+
+		if (time) {
+			System.out.println("Compare java keys with java collator");
+			n = compareLists(sortWithJavaKeys(list), sortWithJavaCollator(list));
+			System.out.println("N errors " + n);
+		}
+	}
+
+	private List<String> createList(Random rand, Charset charset) {
+		List<String> list = new ArrayList<>();
+
+		for (int n = 0; n < LIST_SIZE; n++) {
+			int len = rand.nextInt(6)+1;
+			if (len < 2)
+				len = rand.nextInt(5) + 2;
+
+			if (unicode) {
+				char[] c = new char[len];
+				for (int i = 0; i < len; i++) {
+					int ch;
+					do {
+						if (rand.nextInt(10) > 6)
+							ch = rand.nextInt(6 * 256);
+						else
+							ch = rand.nextInt(256);
+					} while (reject(rand, ch));
+
+					c[i] = (char) ch;
+				}
+				list.add(new String(c));
+			} else {
+				byte[] b = new byte[len];
+				for (int i = 0; i < len; i++) {
+
+					int ch;
+					do {
+						ch = rand.nextInt(256);
+						// reject unassigned. Also low chars most of the time
+					} while (reject(rand, ch));
+
+					b[i] = (byte) ch;
+				}
+				list.add(new String(b, charset));
+			}
+		}
+
+		list = Collections.unmodifiableList(list);
+		return list;
+	}
+
+	private int compareLists(List<String> r1, List<String> r2) {
+		int count = 0;
+		for (int i = 0; i < LIST_SIZE; i++) {
+			String s1 = r1.get(i);
+			String s2 = r2.get(i);
+			String mark = "";
+			if (!s1.equals(s2)) {
+				mark = "*";
+				count++;
+			}
+
+			if (fullOutput || (!mark.isEmpty() && !quiet))
+				System.out.printf("%6d |%-10s |%-10s %s\n", i, s1, s2, mark);
+		}
+		return count;
+	}
+
+	private boolean reject(Random rand, int ch) {
+		switch (ch) {
+		case 0:
+		case ' ':
+		case '\n':case '\r':
+		case 0x81:case 0x8d:case 0x8f:
+		case 0x90:case 0x9d:
+			return true;
+		}
+		switch (Character.getType(ch)) {
+		case Character.UNASSIGNED:
+			return true;
+		case Character.CONTROL:
+			return true;
+		}
+
+		// Reject low characters most of the time
+		if (ch < 0x20 && rand.nextInt(100) < 95)
+			return true;
+		if (ch > 255 && rand.nextInt(100) > 99)
+			return true;
+		return false;
+	}
+
+	private List<String> sortWithKeys(List<String> list) {
+		long start = System.currentTimeMillis();
+		List<SortKey<String>> keys = new ArrayList<>();
+		for (String s : list) {
+			SortKey<String> key = sort.createSortKey(s, s);
+			keys.add(key);
+		}
+		Collections.sort(keys);
+
+		long end = System.currentTimeMillis();
+
+		List<String> ret = new ArrayList<>();
+
+		for (SortKey<String> key : keys) {
+			ret.add(key.getObject());
+		}
+		System.out.println("time keys: " + (end-start) + "ms");
+		return ret;
+	}
+
+	private List<String> sortWithCollator(List<String> list) {
+		long start = System.currentTimeMillis();
+		List<String> ret = new ArrayList<>(list);
+		Collections.sort(ret, sort.getCollator());
+		System.out.println("time coll: " + (System.currentTimeMillis() - start) + "ms");
+		return ret;
+	}
+
+	private List<String> sortWithJavaKeys(List<String> list) {
+
+		long start = System.currentTimeMillis();
+		List<CollationKey> keys = new ArrayList<>();
+		Collator jcol;
+		try {
+			jcol = new RuleBasedCollator(getRules(false));
+		} catch (ParseException e) {
+			e.printStackTrace();
+			return null;
+		}
+		for (String s : list) {
+			CollationKey key = jcol.getCollationKey(s);
+			keys.add(key);
+		}
+		Collections.sort(keys);
+
+		long end = System.currentTimeMillis();
+
+		List<String> ret = new ArrayList<>();
+		for (CollationKey key : keys) {
+			ret.add(key.getSourceString());
+		}
+		System.out.println("time J keys: " + (end - start) + "ms");
+		return ret;
+	}
+
+	private List<String> sortWithJavaCollator(List<String> list) {
+
+		long start = System.currentTimeMillis();
+
+		List<String> out = new ArrayList<>(list);
+		Collator jcol;
+		try {
+			jcol = new RuleBasedCollator(getRules(false));
+			jcol.setStrength(Collator.TERTIARY);
+		} catch (ParseException e) {
+			e.printStackTrace();
+			return null;
+		}
+
+		Collections.sort(out, jcol);
+
+		System.out.println("time J collator: " + (System.currentTimeMillis() - start) + "ms");
+		return out;
+	}
+
+	private String getRules(boolean forICU) {
+		return "='\u0008'='\u000e'='\u000f'='\u0010'='\u0011'='\u0012'='\u0013'='\u0014'='\u0015'='\u0016'"
+				+ "='\u0017' ='\u0018' = '\u0019' ='\u001a' ='\u001b'= '\u001c' ='\u001d'= '\u001e'= '\u001f' "
+				+ "='\u007f' ='\u00ad'"
+				+ ", '\u0001', '\u0002', '\u0003', '\u0004' ,'\u0005' ,'\u0006', '\u0007'"
+				+ "< '\u0009' < '\n' < '\u000b' < '\u000c' < '\r' < '\u0020','\u00a0'"
+				+ "< '_' < '-' < '–' < '—' < '\u002c' < '\u003b' < ':' < '!' < '¡' < '?' < '¿'"
+				+ "< '.' < '·' "
+				+ ((forICU)? "< \\' ": "< ''' ")
+				+ "< '‘' < '’' < '‚' < '‹' < '›' < '“' < '”' < '„' < '«' < '»' "
+				+ " < '\"' "
+				+ "< '“' < '”' < '„' < '«'< '»' < '(' < ')' "
+				+ "< '[' < ']' < '{' < '}' < '§' < '¶' < '@' < '*' < '/' < '\\' < '&' < '#' < '%'"
+				+ "< '‰' < '†' < '‡' < '•' < '`' < '´' < '^' < '¯' < '¨' < '¸' < 'ˆ' < '°' < '©' < '®'"
+				+ "< '+' < '±' < '÷' < '×' < '\u003c' < '\u003d' < '>' < '¬' < '|' < '¦' < '~' ; '˜' <  '¤'"
+				+ "< '¢' < '$' < '£' < '¥' < '€' < 0 < 1,¹ < 2,² < 3,³ < 4 < 5 < 6 < 7 < 8 < 9"
+				+ "< a,ª,A ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,Ã"
+				+ "< b,B"
+				+ "< c,C ; ç,Ç"
+				+ "< d,D ; ð,Ð"
+				+ "< e,E ; é,É ; è,È ; ê,Ê ; ë,Ë"
+				+ "< f,F"
+				+ "< ƒ"
+				+ "< g,G"
+				+ "< h,H"
+				+ "< i,I ; í,Í ; ì,Ì ; î,Î ; ï,Ï"
+				+ "< j,J"
+				+ "< k,K"
+				+ "< l,L"
+				+ "< m,M"
+				+ "< n,N ; ñ,Ñ"
+				+ "< o,º,O ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø"
+				+ "< p,P"
+				+ "< q,Q"
+				+ "< r,R"
+				+ "< s,S ; š,Š"
+				+ "< t,T"
+				+ "< u,U ; ú,Ú ; ù,Ù ; û,Û ; ü,Ü"
+				+ "< v,V"
+				+ "< w,W"
+				+ "< x,X"
+				+ "< y,Y ; ý,Ý ; ÿ,Ÿ"
+				+ "< z,Z ; ž,Ž"
+				+ "< þ,Þ"
+				+ "< µ"
+				+ "&'1/4'=¼  &'1/2'=½  &'3/4'=¾"
+				+ "&ae = æ &AE = Æ &ss = ß &OE= Œ  &oe= œ  &TM = ™  &'...' = … "
+				;
+	}
+
+	public static void main(String[] args) throws Exception {
+		SortTest sortTest = new SortTest();
+		for (String arg : args) {
+			switch (arg) {
+			case "--time":
+				sortTest.time = true;
+				break;
+			case "--full":
+				sortTest.fullOutput = true;
+				break;
+			case "--quiet":
+				sortTest.quiet = true;
+				break;
+			case "--unicode":
+				sortTest.unicode = true;
+				break;
+			}
+		}
+		sortTest.test();
+	}
+}
diff --git a/test/resources/rules/quoted_var.test b/test/resources/rules/quoted_var.test
new file mode 100644
index 0000000..0d808b1
--- /dev/null
+++ b/test/resources/rules/quoted_var.test
@@ -0,0 +1,14 @@
+#
+# Test variable substitution with filters that have quoted arguments.
+#
+
+WAY
+highway=primary
+name=Doctor Who
+
+<<<lines>>>
+
+highway=primary {name '${name|subst:"^(Doctor|Dokter) ~>Dr "}' } [0x1]
+
+<<<results>>>
+WAY 1: Line 0x1, labels=[Dr Who, null, null, null], res=24-24 (1/1),(2/2),
diff --git a/test/uk/me/parabola/imgfmt/UtilsTest.java b/test/uk/me/parabola/imgfmt/UtilsTest.java
new file mode 100644
index 0000000..76e66ed
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/UtilsTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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: 30-Nov-2008
+ */
+package uk.me.parabola.imgfmt;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+
+import org.junit.Test;
+
+import uk.me.parabola.imgfmt.app.Coord;
+
+
+public class UtilsTest {
+
+	/**
+	 * Very simple test that the coord2Long method is working.
+	 */
+	@Test
+	public void testCoord2Long() {
+		HashMap<Long,Coord> map = new HashMap<>();
+		Coord lowerLeft = new Coord(-89.0,-179.0); 
+		Coord upperRight = new Coord(89.0,179.0); 
+		for (int lat30 = -10; lat30 < 10; lat30++){
+			for (int lon30 = -10; lon30 < 10; lon30++){
+				for (int k = 0; k < 3;k++){
+					Coord co; 
+					if (k == 0)
+						co = Coord.makeHighPrecCoord(lat30, lon30); 
+					else if (k == 1)
+						co = Coord.makeHighPrecCoord(lat30+lowerLeft.getHighPrecLat(), lon30+lowerLeft.getHighPrecLon());
+					else
+						co = Coord.makeHighPrecCoord(lat30+upperRight.getHighPrecLat(), lon30+upperRight.getHighPrecLon());
+					long key = Utils.coord2Long(co);
+					Coord old = map.put(key, co);
+					assertTrue("key not unique", old==null);
+				}
+			}
+		}
+		
+	}
+}
diff --git a/test/uk/me/parabola/imgfmt/app/CoordTest.java b/test/uk/me/parabola/imgfmt/app/CoordTest.java
new file mode 100644
index 0000000..6b5d1f3
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/CoordTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test some basic methods of the Coord class regarding distance and bearing calculations
+ * @author GerdP
+ *
+ */
+public class CoordTest {
+	Coord pLAX = new Coord(33.95, -118.4);
+	Coord pJFK = new Coord(40.6333333333, -73.7833333333333333);
+	Coord pD = new Coord(34.5, -116.5);
+	Coord p0_10 = new Coord (0.0, 10.0);
+	Coord p1_10 = new Coord (1.0, 10.0);
+	Coord p1_11 = new Coord (1.0, 11.0);
+	Coord p60_10 = new Coord (60.0, 10.0);
+	Coord p61_11 = new Coord (61.0, 11.0);
+	
+	/**
+	 */
+	@Test
+	public void testBearingGC() {
+		assertEquals(65.892222, pLAX.bearingToOnGreatCircle(pJFK, true), 0.1);
+		assertEquals(0.0, p0_10.bearingToOnGreatCircle(p1_10, true), 0.001);
+		assertEquals(89.991388, p1_10.bearingToOnGreatCircle(p1_11, true), 0.001);
+		assertEquals(44.99555, p0_10.bearingToOnGreatCircle(p1_11, true), 0.001);
+	}
+	@Test
+	public void testBearingRhumb() {
+		assertEquals(79.32388, pLAX.bearingToOnRhumbLine(pJFK, true), 0.1);
+		assertEquals(0, p0_10.bearingToOnRhumbLine(p1_10, true), 0.001);
+		assertEquals(90, p1_10.bearingToOnRhumbLine(p1_11, true), 0.001);
+		assertEquals(44.99861, p0_10.bearingToOnRhumbLine(p1_11, true), 0.001);
+		assertEquals(26.214722, p60_10.bearingToOnRhumbLine(p61_11, true), 0.001);
+	}
+	
+	@Test
+	public void testDistanceRhumb() {
+		// http://www.movable-type.co.uk/scripts/latlong.html says 4011 km  for R=6371 km
+		assertEquals(4011000 * Coord.R / 6371000, pLAX.distanceOnRhumbLine(pJFK), 1000);
+		assertEquals(4011000 * Coord.R / 6371000, pJFK.distanceOnRhumbLine(pLAX), 1000);
+		assertEquals(Coord.U/360, p1_10.distanceOnRhumbLine(p1_11), 20);
+		assertEquals(Coord.U/360, p0_10.distanceOnRhumbLine(p1_10), 20);
+		assertEquals(157200*Coord.R / 6371000, p0_10.distanceOnRhumbLine(p1_11), 200);
+		assertEquals(123900*Coord.R / 6371000, p60_10.distanceOnRhumbLine(p61_11), 200);
+	}
+	
+	@Test
+	public void testDistanceGC() {
+		// http://www.movable-type.co.uk/scripts/latlong.html says 3973 km  for R=6371 km
+		assertEquals(3973000 * Coord.R / 6371000, pLAX.distanceHaversine(pJFK), 1000);
+		assertEquals(3973000 * Coord.R / 6371000, pJFK.distanceHaversine(pLAX), 1000);
+		assertEquals(111300, p1_10.distanceHaversine(p1_11), 100);
+		assertEquals(111300, p0_10.distanceHaversine(p1_10), 100); 
+		assertEquals(157400, p0_10.distanceHaversine(p1_11), 100);
+		assertEquals(124100, p60_10.distanceHaversine(p61_11), 100);
+	}
+
+}
diff --git a/test/uk/me/parabola/imgfmt/app/labelenc/Format6EncoderTest.java b/test/uk/me/parabola/imgfmt/app/labelenc/Format6EncoderTest.java
new file mode 100644
index 0000000..dd4b71a
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/labelenc/Format6EncoderTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.labelenc;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class Format6EncoderTest {
+	/**
+	 * Note that this is essentially a special case. We need a zero length input to map to the first
+	 * empty entry in the table.
+	 */
+	@Test
+	public void testEmptyGivesZeroResult() {
+		Format6Encoder fmt = new Format6Encoder();
+
+		EncodedText enc = fmt.encodeText("");
+		assertEquals(0, enc.getLength());
+	}
+
+	@Test
+	public void testEmptyGivesNullChars() {
+		Format6Encoder fmt = new Format6Encoder();
+
+		EncodedText enc = fmt.encodeText("");
+		assertNull(enc.getChars());
+	}
+
+	@Test
+	public void testEmptyGivesNullCtext() {
+		Format6Encoder fmt = new Format6Encoder();
+
+		EncodedText enc = fmt.encodeText("");
+		assertNull(enc.getCtext());
+	}
+}
diff --git a/test/uk/me/parabola/imgfmt/app/labelenc/LabelEncTest.java b/test/uk/me/parabola/imgfmt/app/labelenc/LabelEncTest.java
new file mode 100644
index 0000000..ad26dab
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/labelenc/LabelEncTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.labelenc;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class LabelEncTest {
+
+	private static final char[] EMPTY_CHARS = new char[0];
+	private static final byte[] EMPTY_BYTES = new byte[0];
+
+	@Test
+	public void testHashForNull() {
+		EncodedText enc = new EncodedText(null, 0, null);
+		assertEquals(0, enc.hashCode());
+	}
+
+	@Test
+	public void testHashForEmpty() {
+		EncodedText enc = new EncodedText(EMPTY_BYTES, 0, EMPTY_CHARS);
+		assertEquals(0, enc.hashCode());
+	}
+
+	@Test
+	public void testEmptyEqualsNull() {
+		EncodedText e1 = new EncodedText(null, 0, null);
+		EncodedText e2 = new EncodedText(EMPTY_BYTES, 0, EMPTY_CHARS);
+
+		assertEquals(e1, e2);
+	}
+}
diff --git a/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java b/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
index 5029507..df6fa01 100644
--- a/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
+++ b/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
@@ -51,11 +51,23 @@ public class SortExpandTest {
 	}
 
 	/**
-	 * Expanded letters should sort just after what they expand to.
+	 * Expanded letters should sort equal to what they expand to.
 	 */
 	@Test
 	public void testAgainstExpansion() {
-		checkOrder("asssst", "asßst");
+		assertEquals(0, compareKey("asssst", "asßst"));
+	}
+
+	@Test
+	public void testExpandSize() {
+		// make sure buffer doesn't overflow when all characters are expanded.
+		assertEquals(0, compareKey("……………………", "……………………"));
+	}
+
+	private int compareKey(String s1, String s2) {
+		SortKey<Object> key1 = sort.createSortKey(null, s1);
+		SortKey<Object> key2 = sort.createSortKey(null, s2);
+		return key1.compareTo(key2);
 	}
 
 	@Test
diff --git a/test/uk/me/parabola/imgfmt/app/srt/SortTest.java b/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
index 3e8acb6..a87e341 100644
--- a/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
+++ b/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
@@ -13,8 +13,6 @@
 
 package uk.me.parabola.imgfmt.app.srt;
 
-import java.io.Reader;
-import java.io.StringReader;
 import java.text.Collator;
 
 import uk.me.parabola.mkgmap.srt.SrtTextReader;
@@ -30,11 +28,8 @@ public class SortTest {
 
 	@Before
 	public void setUp() throws Exception {
-		Reader r = new StringReader("codepage 1252\n" +
-				"code 01\n" +
-				"code a, A; â, Â < b, B;\n");
-		SrtTextReader srr = new SrtTextReader(r);
-		sort = srr.getSort();
+		sort = SrtTextReader.sortForCodepage(1252);
+
 		collator = sort.getCollator();
 		collator.setStrength(Collator.TERTIARY);
 	}
@@ -59,35 +54,43 @@ public class SortTest {
 
 	@Test
 	public void testPrimaryDifference() {
-		checkOrder("AAA", "AAB");
+		checkOrdered("AAA", "AAB");
 	}
 
 	@Test
 	public void testSecondaryDifferences() {
-		checkOrder("AAA", "AÂA");
+		checkOrdered("AAA", "AÂA");
 	}
 
 	@Test
 	public void testTertiaryDifferences() {
-		checkOrder("AAa", "AAA");
+		checkOrdered("AAa", "AAA");
 	}
 
 	@Test
 	public void testPrimaryOverridesSecondary() {
-		checkOrder("AAAA", "ÂAAA");
-		checkOrder("ÂAAA", "AAAB");
+		checkOrdered("AAAA", "ÂAAA");
+		checkOrdered("ÂAAA", "AAAB");
 	}
 
 	@Test
 	public void testSecondaryOverridesTertiary() {
-		checkOrder("aaa", "Aaa");
-		checkOrder("Aaa", "aâa");
-		checkOrder("Aaa", "aÂa");
+		checkOrdered("aaa", "Aaa");
+		checkOrdered("Aaa", "aâa");
+		checkOrdered("Aaa", "aÂa");
 	}
 
 	@Test
 	public void testSecondarySort() {
-		checkOrder(1, 24);
+		checkOrdered(1, 24);
+	}
+
+	@Test
+	public void testLengths() {
+		assertEquals(-1, keyCompare("-Û", "-ü:X"));
+		assertEquals(-1, keyCompare("-ü:X", "-Û$"));
+		assertEquals(-1, keyCompare("–", "–X"));
+		assertEquals(1, keyCompare("–TÛ‡²", "–"));
 	}
 
 	/**
@@ -103,70 +106,84 @@ public class SortTest {
 		SortKey<Object> k1 = sort.createSortKey(null, s);
 		SortKey<Object> k2 = sort.createSortKey(null, "aa");
 
-		int res = k1.compareTo(k2);
-		assertTrue(res != 0);
+		assertTrue(k1.compareTo(k2) != 0);
 
-		res = k2.compareTo(k1);
-		assertTrue(res != 0);
+		assertTrue(k2.compareTo(k1) != 0);
 
 		// not equal to an empty string.
 		k2 = sort.createSortKey(null, "");
-		res = k1.compareTo(k2);
-		assertTrue(res != 0);
+		assertTrue(k1.compareTo(k2) != 0);
 
 		// character is replaced with '?'
 		k2 = sort.createSortKey(null, "a?b");
-		res = k1.compareTo(k2);
-		assertEquals(0, res);
+		assertEquals(0, k1.compareTo(k2));
 	}
 
 	@Test
-	public void testCollatorPrimary() {
-		Collator collator = sort.getCollator();
-		collator.setStrength(Collator.PRIMARY);
-		assertEquals(0, collator.compare("aa", "aa"));
-		assertEquals(0, collator.compare("aa", "âa"));
-		assertEquals(0, collator.compare("Aa", "aA"));
-		assertEquals(1, collator.compare("ab", "âa"));
+	public void testTertiaryPlusExpansion() {
+		assertEquals(-1, keyCompare("æ", "ªe"));
+		assertEquals(-1, keyCompare("`æ", "`ªe"));
+	}
 
-		assertEquals(1, collator.compare("aaa", "aa"));
-		assertEquals(-1, collator.compare("aa", "aaa"));
+	/**
+	 * Make the internal initial buffer overflow so it has to be reallocated.
+	 */
+	@Test
+	public void testKeyOverflow() {
+		assertEquals(1, keyCompare("™™™™™", "AA"));
 	}
 
 	@Test
-	public void testCollatorSecondary() {
-		Collator collator = sort.getCollator();
-		collator.setStrength(Collator.SECONDARY);
-		assertEquals(0, collator.compare("aa", "aa"));
-		assertEquals(0, collator.compare("aA", "aa"));
-		assertEquals(-1, collator.compare("aa", "âa"));
-		assertEquals(0, collator.compare("âa", "âa"));
-		assertEquals(1, collator.compare("ab", "âa"));
+	public void testExpanded() {
+		assertEquals(-1, keyCompare("æ", "Ae"));
+		assertEquals(-1, keyCompare("æ", "AE"));
+		assertEquals(0, keyCompare("æ", "ae"));
+		assertEquals(-1, keyCompare("æ", "aE"));
+		assertEquals(1, keyCompare("AE", "aE"));
+		assertEquals(1, keyCompare("Æ", "aE"));
 
-		assertEquals(1, collator.compare("aaaa", "aaa"));
-		assertEquals(-1, collator.compare("aaa", "aaaa"));
+		assertEquals(-1, keyCompare("–TMO", "–uÊÑÇ"));
+		assertEquals(-1, keyCompare("–™O", "–uÊÑÇ"));
 	}
 
 	@Test
-	public void testCollatorTertiary() {
-		Collator collator = sort.getCollator();
-		collator.setStrength(Collator.TERTIARY);
-		assertEquals(0, collator.compare("aa", "aa"));
-		assertEquals(1, collator.compare("aA", "aa"));
-		assertEquals(-1, collator.compare("aaa", "âaa"));
-		assertEquals(0, collator.compare("âaa", "âaa"));
-		assertEquals(1, collator.compare("ab", "âa"));
+	public void testExpand2() {
+		assertEquals(1, keyCompare("™ð", "tMÐ"));
+	}
 
-		assertEquals(1, collator.compare("AAA", "AA"));
-		assertEquals(-1, collator.compare("AA", "AAA"));
+	@Test
+	public void testExpandedAndIgnorable() {
+		assertEquals(0, keyCompare("æ", "ae"));
+		assertEquals(-1, keyCompare("\u007fæ", "Ae"));
 	}
 
 	@Test
 	public void testIgnorableCharacters() {
-		checkOrder("aa", "\004aa");
+		assertEquals(0, keyCompare("aaa", "a\u0008aa"));
+
+		assertEquals(-1, keyCompare("\u007f", "(T"));
+	}
+
+	@Test
+	public void testSecondaryIgnorable() {
+		assertEquals(1, keyCompare("\u0001A", "A\u0008"));
+	}
+
+	@Test
+	public void testSpaces() {
+		assertEquals(1, keyCompare("øþõ Ñ", "õþO"));
+	}
+
+	private int keyCompare(String s1, String s2) {
+		SortKey<Object> k1 = sort.createSortKey(null, s1);
+		SortKey<Object> k2 = sort.createSortKey(null, s2);
+		System.out.println("K1: " + k1);
+		System.out.println("K2: " + k2);
+
+		return k1.compareTo(k2);
 	}
 
-	private void checkOrder(int i1, int i2) {
+	private void checkOrdered(int i1, int i2) {
 		String s = "aaa";
 		SortKey<Object> k1 = sort.createSortKey(null, s, i1);
 		SortKey<Object> k2 = sort.createSortKey(null, s, i2);
@@ -178,11 +195,13 @@ public class SortTest {
 	 * @param s First string.
 	 * @param s1 Second string.
 	 */
-	private void checkOrder(String s, String s1) {
+	private void checkOrdered(String s, String s1) {
 		SortKey<Object> k1 = sort.createSortKey(null, s);
 		SortKey<Object> k2 = sort.createSortKey(null, s1);
 
 		assertEquals(1, k2.compareTo(k1));
+		assertEquals(-1, k1.compareTo(k2));
 		assertEquals(-1, collator.compare(s, s1));
+		assertEquals(1, collator.compare(s1, s));
 	}
 }
diff --git a/test/uk/me/parabola/imgfmt/app/srt/SrtCollatorTest.java b/test/uk/me/parabola/imgfmt/app/srt/SrtCollatorTest.java
new file mode 100644
index 0000000..e1e4ac7
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/srt/SrtCollatorTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.srt;
+
+import java.text.Collator;
+
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SrtCollatorTest {
+
+	private Collator collator;
+
+	@Before
+	public void setUp() {
+		Sort sort = SrtTextReader.sortForCodepage(1252);
+		collator = sort.getCollator();
+	}
+
+	/**
+	 * Test primary strength comparisons.
+	 */
+	@Test
+	public void testPrimary() {
+		collator.setStrength(Collator.PRIMARY);
+		assertEquals("prim: different case", 0, collator.compare("AabBb", "aabbb"));
+		assertEquals("prim: different case", 0, collator.compare("aabBb", "aabbb"));
+		assertEquals("prim: different length", -1, collator.compare("AabB", "aabbb"));
+		assertEquals("prim: different letter", -1, collator.compare("aaac", "aaad"));
+		assertEquals("prim: different letter", 1, collator.compare("aaae", "aaad"));
+		assertEquals(0, collator.compare("aaaa", "aaaa"));
+		assertEquals(0, collator.compare("aáÄâ", "aaaa"));
+	}
+
+	@Test
+	public void testSecondary() {
+		collator.setStrength(Collator.SECONDARY);
+		assertEquals(0, collator.compare("AabBb", "aabbb"));
+		assertEquals(0, collator.compare("aabBb", "aabBb"));
+		assertEquals(0, collator.compare("aabbB", "aabBb"));
+		assertEquals(1, collator.compare("aáÄâ", "aaaa"));
+		assertEquals("prim len diff", -1, collator.compare("aáÄâ", "aaaaa"));
+		assertEquals(-1, collator.compare("aáÄâa", "aaaab"));
+	}
+
+	@Test
+	public void testTertiary() {
+		collator.setStrength(Collator.TERTIARY);
+		assertEquals("prim: different case", 1, collator.compare("AabBb", "aabbb"));
+		assertEquals(1, collator.compare("AabBb", "aabbb"));
+		assertEquals(0, collator.compare("aabBb", "aabBb"));
+		assertEquals(-1, collator.compare("aabbB", "aabBb"));
+		assertEquals(-1, collator.compare("aAbb", "aabbb"));
+		assertEquals(1, collator.compare("t", "a"));
+		assertEquals(1, collator.compare("ß", "a"));
+		assertEquals(-1, collator.compare("ESA", "Eß"));
+		assertEquals(-1, collator.compare(":.e", "\u007fæ"));
+		assertEquals(-1, collator.compare(";œ", ";Œ"));
+		assertEquals(-1, collator.compare("œ;", "Œ;"));
+	}
+
+	/**
+	 * Test that ignorable characters do not affect the result in otherwise identical strings.
+	 */
+	@Test
+	public void testIgnoreable() throws Exception {
+		assertEquals("ignorable at beginning", 0, collator.compare("\u0008fred", "fred"));
+		assertEquals("ignorable at end", 0, collator.compare("fred\u0008", "fred"));
+		assertEquals("ignorable in middle", 0, collator.compare("fr\u0008ed", "fred"));
+		assertEquals(1, collator.compare("\u0001A", "A\u0008"));
+
+		collator.setStrength(Collator.PRIMARY);
+		assertEquals("prim: different case", 0, collator.compare("AabBb\u0008", "aabbb"));
+	}
+
+	@Test
+	public void testSecondaryIgnorable() {
+		assertEquals(-1, collator.compare("A", "A\u0001"));
+	}
+
+	@Test
+	public void testLengths() {
+		assertEquals(-1, collator.compare("-Û", "-ü:X"));
+		assertEquals(-1, collator.compare("-Û", "-Û$"));
+		assertEquals(-1, collator.compare("-ü:X", "-Û$"));
+		assertEquals(-1, collator.compare("–", "–X"));
+		assertEquals(1, collator.compare("–TÛ‡²", "–"));
+	}
+
+	@Test
+	public void testSpaces() {
+		assertEquals(1, collator.compare("øþõ Ñ", "õþO"));
+	}
+
+	/**
+	 * Test using the java collator, to experiment. Note that our implementation is not
+	 * meant to be identical to the java one.
+	 */
+	@Test
+	public void testJavaRules() {
+		Collator collator = Collator.getInstance();
+
+		// Testing ignorable
+		assertEquals(0, collator.compare("\u0001fred", "fred"));
+		assertEquals(0, collator.compare("fre\u0001d", "fred"));
+
+		collator.setStrength(Collator.PRIMARY);
+		assertEquals("prim: different case", 0, collator.compare("AabBb", "aabbb"));
+		assertEquals("prim: different case", 0, collator.compare("aabBb", "aabbb"));
+		assertEquals("prim: different length", -1, collator.compare("AabB", "aabbb"));
+		assertEquals("prim: different letter", -1, collator.compare("aaac", "aaad"));
+		assertEquals("prim: different letter", 1, collator.compare("aaae", "aaad"));
+		assertEquals(0, collator.compare("aaaa", "aaaa"));
+		assertEquals(0, collator.compare("aáÄâ", "aaaa"));
+
+		collator.setStrength(Collator.SECONDARY);
+		assertEquals(0, collator.compare("AabBb", "aabbb"));
+		assertEquals(0, collator.compare("aabBb", "aabBb"));
+		assertEquals(0, collator.compare("aabbB", "aabBb"));
+		assertEquals(1, collator.compare("aáÄâ", "aaaa"));
+		assertEquals("prim len diff", -1, collator.compare("aáÄâ", "aaaaa"));
+		assertEquals(-1, collator.compare("aáÄâa", "aaaab"));
+
+		collator.setStrength(Collator.TERTIARY);
+		assertEquals("prim: different case", 1, collator.compare("AabBb", "aabbb"));
+		assertEquals(1, collator.compare("AabBb", "aabbb"));
+		assertEquals(0, collator.compare("aabBb", "aabBb"));
+		assertEquals(-1, collator.compare("aabbB", "aabBb"));
+		assertEquals(-1, collator.compare("aAbb", "aabbb"));
+		assertEquals(1, collator.compare("t", "a"));
+	}
+
+}
diff --git a/test/uk/me/parabola/imgfmt/app/srt/UnicodeCollatorTest.java b/test/uk/me/parabola/imgfmt/app/srt/UnicodeCollatorTest.java
new file mode 100644
index 0000000..67d29df
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/srt/UnicodeCollatorTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.srt;
+
+import java.text.Collator;
+
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class UnicodeCollatorTest {
+
+	private Collator collator;
+
+	@Before
+	public void setUp() throws Exception {
+		Sort sort = SrtTextReader.sortForCodepage(65001);
+
+		collator = sort.getCollator();
+		collator.setStrength(Collator.TERTIARY);
+	}
+
+	@Test
+	public void testSimpleLessThan() {
+		assertEquals(-1, collator.compare("G", "Ò"));
+		assertEquals(-1, collator.compare("G", "Γ"));
+	}
+
+	@Test
+	public void testExpand() {
+		assertEquals(-1, collator.compare("!", "ß"));
+		assertEquals(-1, collator.compare("A:", "Ǣ"));
+	}
+}
diff --git a/test/uk/me/parabola/imgfmt/app/srt/UnicodeKeyTest.java b/test/uk/me/parabola/imgfmt/app/srt/UnicodeKeyTest.java
new file mode 100644
index 0000000..2c8e70d
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/srt/UnicodeKeyTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.imgfmt.app.srt;
+
+import java.text.Collator;
+
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class UnicodeKeyTest {
+
+	private Sort sort;
+
+	@Before
+	public void setUp() throws Exception {
+		sort = SrtTextReader.sortForCodepage(65001);
+
+		Collator collator = sort.getCollator();
+		collator.setStrength(Collator.TERTIARY);
+	}
+
+	@Test
+	public void testUnicodePresent() {
+		String description = sort.getDescription();
+		assertTrue(description.contains("Unicode"));
+		assertFalse(description.contains("Default"));
+	}
+
+	@Test
+	public void testEquals() {
+		String abc = "ABC\u0234\u1023";
+		SortKey<Object> key1 = sort.createSortKey(null, abc);
+		SortKey<Object> key2 = sort.createSortKey(null, abc);
+
+		assertEquals(0, key1.compareTo(key2));
+	}
+
+	@Test
+	public void testSimpleLessThan() {
+		assertEquals(-1, keyCompare("G", "Ò"));
+		assertEquals(-1, keyCompare("G", "Γ"));
+	}
+
+	@Test
+	public void testExpand() {
+		assertEquals(-1, keyCompare("!", "ß"));
+		assertEquals(-1, keyCompare("A:", "Ǣ"));
+	}
+
+	private int keyCompare(String s1, String s2) {
+		SortKey<Object> k1 = sort.createSortKey(null, s1);
+		SortKey<Object> k2 = sort.createSortKey(null, s2);
+		System.out.println("K1: " + k1);
+		System.out.println("K2: " + k2);
+
+		return k1.compareTo(k2);
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
new file mode 100644
index 0000000..636d52e
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.filters;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.general.MapShape;
+//import uk.me.parabola.util.GpxCreator;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+
+public class ShapeMergeFilterTest {
+	// create one Coord instance for each point in a small test grid 
+	private static final HashMap<Integer,Coord> map = new HashMap<Integer,Coord>(){
+		{
+			for (int lat30 = 0; lat30 < 100; lat30 +=5){
+				for (int lon30 = 0; lon30 < 100; lon30 += 5){
+					Coord co = Coord.makeHighPrecCoord(lat30, lon30);
+					put(lat30*1000 + lon30,co);
+				}
+			}
+		}
+	};
+
+	@Test
+	public void testAreaTestVal(){
+		List<Coord> points = new ArrayList<Coord>(){{
+			add(getPoint(10,10));
+			add(getPoint(30,10));
+			add(getPoint(30,30));
+			add(getPoint(10,30));
+			add(getPoint(10,10)); // close
+			
+		}};
+		assertEquals(2 * (20 * 20),ShapeMergeFilter.calcAreaSizeTestVal(points));
+	}	
+	/**
+	 * two simple shapes, sharing one point
+	 */
+	@Test
+	public void testSimpleSharingOne(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(15,10));
+			add(getPoint(30,25));
+			add(getPoint(25,30));
+			add(getPoint(10,30));
+			add(getPoint(5,20));
+			add(getPoint(15,10)); // close
+		}};
+		
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(25,30));
+			add(getPoint(30,35));
+			add(getPoint(20,40));
+			add(getPoint(15,35));
+			add(getPoint(25,30));
+		}};
+		testVariants("simple shapes sharing one point", points1, points2,1,10);
+	}
+	
+	/**
+	 * two simple shapes, sharing one edge 
+	 */
+	@Test
+	public void testSimpleNonOverlapping(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(15,10));
+			add(getPoint(30,25));
+			add(getPoint(25,30));
+			add(getPoint(15,35));
+			add(getPoint(5,20));
+			add(getPoint(15,10)); // close
+		}};
+		
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(25,30));
+			add(getPoint(30,35));
+			add(getPoint(20,40));
+			add(getPoint(15,35));
+			add(getPoint(25,30));
+		}};
+		testVariants("simple shapes", points1, points2,1,8);
+	}
+
+	/**
+	 * two simple shapes, sharing three consecutive points 
+	 */
+
+	@Test
+	public void test3SharedPointsNonOverlapping(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(15,10));
+			add(getPoint(30,25));
+			add(getPoint(25,30));
+			add(getPoint(20,35)); 
+			add(getPoint(15,35));
+			add(getPoint(5,20));
+			add(getPoint(15,10));// close
+		}};
+		
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(25,30));
+			add(getPoint(30,35));
+			add(getPoint(20,40));
+			add(getPoint(15,35));
+			add(getPoint(20,35));
+			add(getPoint(25,30));// close
+		}};
+		testVariants("test 3 consecutive shared points", points1, points2, 1, 8);
+	}
+	
+	/**
+	 * two simple shapes, sharing three consecutive points 
+	 */
+
+	@Test
+	public void test2SharedPointsNoEdge(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(15,10));
+			add(getPoint(30,25));
+			add(getPoint(25,30));
+			add(getPoint(15,35));
+			add(getPoint(5,20));
+			add(getPoint(15,10));// close
+		}};
+		
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(25,30));
+			add(getPoint(30,35));
+			add(getPoint(20,40));
+			add(getPoint(15,35));
+			add(getPoint(20,35));
+			add(getPoint(25,30));// close
+		}};
+		testVariants("test 2 non-consecutive shared points", points1, points2, 1, 11);
+	}
+	
+	/**
+	 * one u-formed shape, the other closes it to a rectangular shape with a hole
+	 * They are sharing 4 points. 
+	 */
+
+	@Test
+	public void testCloseUFormed(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			// u-formed shaped (open at top)
+			add(getPoint(15,50));
+			add(getPoint(30,50));
+			add(getPoint(30,55));
+			add(getPoint(20,55)); 
+			add(getPoint(20,65));
+			add(getPoint(30,65));
+			add(getPoint(30,70));
+			add(getPoint(15,70));
+			add(getPoint(15,50));// close
+		}};
+
+
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(35,50));
+			add(getPoint(35,70));
+			add(getPoint(30,70));
+			add(getPoint(30,65));
+			add(getPoint(30,55));
+			add(getPoint(30,50));
+			add(getPoint(35,50)); // close
+		}};
+		
+		testVariants("test close U formed shape", points1, points2, 1, 11);
+	}
+	
+	/**
+	 * one u-formed shape, the fits into the u and shares all points
+	 */
+
+	@Test
+	public void testFillUFormed(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			// u-formed shaped (open at top)
+			add(getPoint(15,50));
+			add(getPoint(30,50));
+			add(getPoint(30,55));
+			add(getPoint(20,55)); 
+			add(getPoint(20,65));
+			add(getPoint(30,65));
+			add(getPoint(30,70));
+			add(getPoint(15,70));
+			add(getPoint(15,50)); // close
+		}};
+
+		
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(30,55));
+			add(getPoint(30,65));
+			add(getPoint(20,65));
+			add(getPoint(20,55));
+			add(getPoint(30,55)); // close
+		}};
+		testVariants("test fill U-formed shape", points1, points2, 1, 5);
+	}
+	
+	/**
+	 * one u-formed shape, the fits into the u and shares all points
+	 */
+
+	@Test
+	public void testFillHole(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			// a rectangle with a hole 
+			add(getPoint(35,50));
+			add(getPoint(35,70));
+			add(getPoint(15,70));
+			add(getPoint(15,50));
+			add(getPoint(30,50));
+			add(getPoint(30,55));
+			add(getPoint(20,55)); 
+			add(getPoint(20,65));
+			add(getPoint(30,65));
+			add(getPoint(30,50));
+			add(getPoint(35,50));// close
+		}};
+
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(30,55));
+			add(getPoint(30,65));
+			add(getPoint(20,65));
+			add(getPoint(20,55));
+			add(getPoint(30,55)); // close
+		}};
+		testVariants("test-fill-hole", points1, points2, 1, 6); // expect 8 points if spike is not removed  
+	}
+
+	@Test
+	public void testDuplicate(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(30,55));
+			add(getPoint(30,65));
+			add(getPoint(20,65));
+			add(getPoint(20,55));
+			add(getPoint(30,55)); // close
+		}};
+		List<Coord> points2 = new ArrayList<Coord>(points1);
+		
+		testVariants("test duplicate", points1, points2, 1, 5);
+	}
+
+	@Test
+	public void testOverlap(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(30,55));
+			add(getPoint(30,65));
+			add(getPoint(20,65));
+			add(getPoint(20,55));
+			add(getPoint(30,55)); // close
+		}};
+
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(30,55));
+			add(getPoint(30,65));
+			add(getPoint(25,65));
+			add(getPoint(25,55));
+			add(getPoint(30,55)); // close
+		}};
+		// no merge expected
+		testVariants("test overlap", points1, points2, 2, 5);
+	}
+
+	/*
+	 * shapes are connected at multiple edges like two 
+	 * w-formed shapes.
+	 */
+	@Test
+	public void testTwoWShaped(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(getPoint(0,5));
+			add(getPoint(35,5));
+			add(getPoint(35,20));
+			add(getPoint(30,15));
+			add(getPoint(25,20));
+			add(getPoint(25,10));
+			add(getPoint(15,10));
+			add(getPoint(15,20));
+			add(getPoint(10,15));
+			add(getPoint(5,20));
+			add(getPoint(0,20));
+			add(getPoint(0,5)); // close
+		}};
+
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(getPoint(35,35));
+			add(getPoint(35,20));
+			add(getPoint(30,15));
+			add(getPoint(25,20));
+			add(getPoint(25,25));
+			add(getPoint(15,25));
+			add(getPoint(15,20));
+			add(getPoint(10,15));
+			add(getPoint(5,20));
+			add(getPoint(0,20));
+			add(getPoint(5,35));
+			add(getPoint(35,35)); // close
+		}};
+		
+		// wanted: merge that removes at least the longer shared sequence
+		testVariants("test two w-shaped", points1, points2, 1, 16);
+	}
+
+	/**
+	 * Test all variants regarding clockwise/ccw direction and positions of the points 
+	 * in the list and the order of shapes. 
+	 * @param list1
+	 * @param list2
+	 */
+	void testVariants(String msg, List<Coord> list1, List<Coord> list2, int expectedNumShapes, int expectedNumPoints){
+		MapShape s1 = new MapShape(1);
+		MapShape s2 = new MapShape(2);
+		s1.setMinResolution(22);
+		s2.setMinResolution(22);
+		for (int i = 0; i < 4; i++){
+			for (int j = 0; j < list1.size(); j++){
+				List<Coord> points1 = new ArrayList<>(list1);
+				if ((i & 1) != 0)
+					Collections.reverse(points1);
+				points1.remove(points1.size()-1);
+				Collections.rotate(points1, j);
+				points1.add(points1.get(0));
+				s1.setPoints(points1);
+				for (int k = 0; k < list2.size(); k++){
+					List<Coord> points2 = new ArrayList<>(list2);
+					if ((i & 2) != 0)
+						Collections.reverse(points2);
+					points2.remove(points2.size()-1);
+					Collections.rotate(points2, k);
+					points2.add(points2.get(0));
+					s2.setPoints(points2);
+					
+					for (int l = 0; l < 2; l++){
+						String testId = msg+" i="+i+",j="+j+",k="+k+",l="+l;
+						if (l == 0)
+							testOneVariant(testId, s1, s2, expectedNumShapes,expectedNumPoints);
+						else 
+							testOneVariant(testId, s2, s1, expectedNumShapes,expectedNumPoints);
+					}
+				}
+			}
+		}
+		return;
+	}
+	
+	void testOneVariant(String testId, MapShape s1, MapShape s2, int expectedNumShapes, int expectedNumPoints){
+		ShapeMergeFilter smf = new ShapeMergeFilter(24);
+		List<MapShape> res = smf.merge(Arrays.asList(s1,s2));
+		assertTrue(testId, res != null);
+		assertEquals(testId,expectedNumShapes, res.size() );
+//		if (res.get(0).getPoints().size() != expectedNumPoints){
+//			GpxCreator.createGpx("e:/ld/s1", s1.getPoints());
+//			GpxCreator.createGpx("e:/ld/s2", s2.getPoints());
+//			GpxCreator.createGpx("e:/ld/res", res.get(0).getPoints());
+//		}
+		assertEquals(testId, expectedNumPoints, res.get(0).getPoints().size());
+		// TODO: test shape size
+	}
+	Coord getPoint(int lat, int lon){
+		Coord co = map.get(lat*1000+lon);
+		assert co != null;
+		return co;
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/general/MapLineTest.java b/test/uk/me/parabola/mkgmap/general/MapLineTest.java
new file mode 100644
index 0000000..9437d90
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/general/MapLineTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012.
+ *
+ * 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.general;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import uk.me.parabola.imgfmt.app.Coord;
+
+public class MapLineTest {
+
+	@Test
+	public void TestInsertPointsAtStart(){
+		List<Coord> points1 = new ArrayList<Coord>(){{
+			add(new Coord(30,55));
+			add(new Coord(30,65));
+			add(new Coord(20,65));
+			add(new Coord(20,55));
+		}};
+		List<Coord> points2 = new ArrayList<Coord>(){{
+			add(new Coord(10,20));
+			add(new Coord(30,30));
+			add(new Coord(30,55));
+		}};
+
+		MapLine ml = new MapLine();
+		ml.setPoints(new ArrayList<>(points1));
+		assertEquals(points1.size(), ml.getPoints().size());
+		ml.insertPointsAtStart(points2);
+		assertEquals(6, ml.getPoints().size());
+		assertTrue(ml.getPoints().get(0).equals(new Coord(10,20)));
+		assertTrue(ml.getPoints().get(2).equals(new Coord(30,55)));
+		assertTrue(ml.getPoints().get(5).equals(new Coord(20,55)));
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/general/PointInShapeTest.java b/test/uk/me/parabola/mkgmap/general/PointInShapeTest.java
deleted file mode 100644
index aeb3fca..0000000
--- a/test/uk/me/parabola/mkgmap/general/PointInShapeTest.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/**
- * 
- */
-package uk.me.parabola.mkgmap.general;
-
-
-import java.util.Arrays;
-import java.util.List;
-
-import uk.me.parabola.imgfmt.app.Coord;
-
-import static org.junit.Assert.*;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * @author ben
- */
-public class PointInShapeTest {
-
-	private MapShape square;
-	private MapShape triangle;
-	private MapShape line;
-	private final int squareSize = 4;
-
-	/**
-	 * @throws Exception
-	 */
-	@Before
-	public void setUp() throws Exception {
-		// Square
-		List<Coord> points = Arrays.asList(
-				new Coord(0, 0),
-				new Coord(0, squareSize),
-				new Coord(squareSize, squareSize),
-				new Coord(squareSize, 0),
-				new Coord(0,0) 
-				);
-		square = new MapShape();
-		square.setPoints(points);
-		
-		// Triangle
-		points = Arrays.asList(
-				new Coord(0,0),
-				new Coord(4,4),
-				new Coord(8,0),
-				new Coord(0,0) 
-				);
-		triangle = new MapShape();
-		triangle.setPoints(points);
-		
-		// Line
-		points = Arrays.asList(
-				new Coord(2,5),
-				new Coord(12,1)
-				);
-		line = new MapShape();
-		line.setPoints(points);
-	}
-
-	@Test
-	public void testLinePointsInsideSquare() {
-		
-		// inside square, 1 unit from corners
-		List<Coord> points = Arrays.asList(
-				new Coord(1, squareSize/2),
-				new Coord(squareSize/2, squareSize - 1),
-				new Coord(squareSize - 1, squareSize/2),
-				new Coord(squareSize/2, 1) 
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside square",	
-					square.contains(coord));
-		}
-		
-		// on the line
-		points = Arrays.asList(
-				new Coord(0, squareSize/2),
-				new Coord(squareSize/2, squareSize),
-				new Coord(squareSize, squareSize/2),
-				new Coord(squareSize/2, 0) 
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square",	
-					square.contains(coord));
-		}
-	}
-	
-	@Test
-	public void testLinePointsOutsideSquare() {
-		
-		// outside square, 1 unit from line
-		List<Coord> points = Arrays.asList(
-				new Coord(-1, squareSize/2),
-				new Coord(squareSize/2, squareSize + 1),
-				new Coord(squareSize + 1, squareSize/2),
-				new Coord(squareSize/2, -1) 
-				);
-		for (Coord coord : points) {
-			assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside square",	
-					square.contains(coord));
-		}
-	}
-	
-	@Test
-	public void testCornerPointsInsideSquare() {
-		// corner points
-		for (Coord cornerpoint : square.getPoints()) {
-			Coord co = new Coord(cornerpoint.getLatitude(), cornerpoint.getLongitude());
-			assertTrue("corner point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside square",
-					square.contains(co));
-		}
-		
-		// sub shape
-		for (Coord cornerpoint : square.getPoints()) {
-			int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1;
-			int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1;
-			int x = cornerpoint.getLatitude() + xadd;
-			int y = cornerpoint.getLongitude() + yadd;
-			Coord co = new Coord(x, y);
-			assertTrue("point (" + x + ", " + y + ") should be inside square", square.contains(co));
-		}
-		
-		// tests above / below corner points, on the outside edge
-		for (Coord cornerpoint : square.getPoints()) {
-			int xadd = cornerpoint.getLatitude() > 0 ? -1 : 1;
-			int x = cornerpoint.getLatitude() + xadd;
-			int y = cornerpoint.getLongitude();
-			Coord co = new Coord(x, y);
-			assertTrue("point (" + x + ", " + y + ") should be outside square",	square.contains(co));
-		}
-		
-		// tests to the right / left side of corner points, on square edge
-		for (Coord cornerpoint : square.getPoints()) {
-			int yadd = cornerpoint.getLongitude() > 0 ? -1 : 1;
-			int x = cornerpoint.getLatitude();
-			int y = cornerpoint.getLongitude() + yadd;
-			Coord co = new Coord(x, y);
-			assertTrue("point (" + x + ", " + y + ") should be outside square", square.contains(co));
-		}
-	}
-	
-	@Test
-	public void testCornerPointsOutsideSquare() {
-		
-		// tests above / below corner points, outside square
-		for (Coord cornerpoint : square.getPoints()) {
-			int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1;
-			int x = cornerpoint.getLatitude();
-			int y = cornerpoint.getLongitude() + yadd;
-			Coord co = new Coord(x, y);
-			assertFalse("point (" + x + ", " + y + ") should be outside square",	square.contains(co));
-		}
-		
-		// tests to the right / left side of corner points, outside square
-		for (Coord cornerpoint : square.getPoints()) {
-			int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1;
-			int x = cornerpoint.getLatitude() + xadd;
-			int y = cornerpoint.getLongitude();
-			Coord co = new Coord(x, y);
-			assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co));
-		}
-		
-		// super shape
-		for (Coord cornerpoint : square.getPoints()) {
-			int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1;
-			int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1;
-			int x = cornerpoint.getLatitude() + xadd;
-			int y = cornerpoint.getLongitude() + yadd;
-			Coord co = new Coord(x, y);
-			assertFalse("point (" + x + ", " + y + ") should be outside square", square.contains(co));
-		}
-	}
-	
-	
-	@Test
-	public void testLinePointsInsideTriangle() {
-		// inside triangle, above / below lines
-		List<Coord> points = Arrays.asList(
-				new Coord(2,1),
-				new Coord(6,1),
-				new Coord(4,1)
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle",	
-					triangle.contains(coord));
-		}
-		
-		// on lines
-		points = Arrays.asList(
-				new Coord(2,2),
-				new Coord(6,2),
-				new Coord(4,0)
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle",	
-					triangle.contains(coord));
-		}
-	}
-	
-	@Test
-	public void testLinePointsOutsideTriangle() {
-		// outside triangle, above / below lines
-		List<Coord> points = Arrays.asList(
-				new Coord(2,3),
-				new Coord(6,3),
-				new Coord(4,-1)
-				);
-		for (Coord coord : points) {
-			assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle",	
-					triangle.contains(coord));
-		}
-	}
-	
-	@Test
-	public void testCornerPointsInsideTriangle() {
-		// corner points
-		for (Coord cornerpoint : triangle.getPoints()) {
-			assertTrue("point (" + cornerpoint.getLatitude() + ", " + cornerpoint.getLongitude() + ") should be outside triangle",
-					triangle.contains(cornerpoint));
-		}
-		
-		// sub shape
-		List<Coord> points = Arrays.asList(
-				new Coord(2,1),
-				new Coord(4,3),
-				new Coord(6,1)
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be inside triangle", 
-					triangle.contains(coord));
-		}
-		
-		// beside points, on edge
-		points = Arrays.asList(
-				new Coord(1,0),
-				new Coord(7,0)
-				);
-		for (Coord coord : points) {
-			assertTrue("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle",	
-					triangle.contains(coord));
-		}
-	}
-	
-	@Test
-	public void testCornerPointsOutsideTriangle() {
-		// above points
-		for (Coord coord : triangle.getPoints()) {
-			Coord co = new Coord(coord.getLatitude(), coord.getLongitude() + 1);
-			assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside triangle",	
-					triangle.contains(co));
-		}
-		
-		// outside triangle, beside / below lines
-		List<Coord> points = Arrays.asList(
-				new Coord(-1,0),
-				new Coord(0,-1),
-				new Coord(3,4),
-				new Coord(5,4),
-				new Coord(9,0),
-				new Coord(8,-1)
-				);
-		for (Coord coord : points) {
-			assertFalse("point (" + coord.getLatitude() + ", " + coord.getLongitude() + ") should be outside triangle",	
-					triangle.contains(coord));
-		}
-		
-		// super shape
-		for (Coord cornerpoint : triangle.getPoints()) {
-			int xadd = cornerpoint.getLatitude() > 0 ? 1 : -1;
-			int yadd = cornerpoint.getLongitude() > 0 ? 1 : -1;
-			int x = cornerpoint.getLatitude() + xadd;
-			int y = cornerpoint.getLongitude() + yadd;
-			Coord co = new Coord(x, y);
-			assertFalse("point (" + x + ", " + y + ") should be outside triangle", triangle.contains(co));
-		}
-	}
-	
-	@Test
-	public void testLine() {
-		// midpoint
-		Coord co = new Coord(7,3);
-		assertFalse("point (" + co.getLatitude() + ", " + co.getLongitude() + ") should be outside line",
-				line.contains(co));
-	}
-}
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java b/test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java
index 18e6492..c9423b6 100644
--- a/test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java
+++ b/test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java
@@ -356,6 +356,17 @@ public class RuleFileReaderTest {
 	}
 
 	@Test
+	public void testNEAtTopWithRE() {
+		RuleSet rs = makeRuleSet("a != 'fred' &  a ~ '.*' [0x2]");
+		Element el = new Way(1);
+		el.addTag("a", "tom");
+
+		GType type = getFirstType(rs, el);
+		assertNotNull(type);
+		assertEquals(2, type.getType());
+	}
+
+	@Test
 	public void testNumberOpAtTop() {
 		RuleSet rs = makeRuleSet("QUOTA > 10 [0x1] QUOTA < 6 [0x2]");
 		Element el = new Way(1);
@@ -367,6 +378,88 @@ public class RuleFileReaderTest {
 	}
 
 	/**
+	 * Failure of the optimiser to promote the correct term to the front.
+	 * Example from mailing list.
+	 */
+	@Test
+	public void testOptimizeWithOr() {
+		String s = "highway ~ '(secondary|tertiary|unclassified|residential|minor|living_street|service)' " +
+				"& oneway=* " +
+				"& (cycleway=opposite | cycleway=opposite_lane | cycleway=opposite_track )" +
+				"[0x2 ]";
+		RuleSet rs = makeRuleSet(s);
+
+		Element el = new Way(1);
+		el.addTag("highway", "tertiary");
+		el.addTag("oneway", "1");
+		el.addTag("cycleway", "opposite_track");
+
+		GType type = getFirstType(rs, el);
+		assertNotNull(type);
+		assertEquals(2, type.getType());
+
+		el.addTag("cycleway", "fred");
+		type = getFirstType(rs, el);
+		assertNull(type);
+
+		el.addTag("cycleway", "opposite");
+		type = getFirstType(rs, el);
+		assertNotNull(type);
+
+		el.addTag("cycleway", "opposite_lane");
+		type = getFirstType(rs, el);
+		assertNotNull(type);
+
+		el.addTag("highway", "fred");
+		type = getFirstType(rs, el);
+		assertNull(type);
+	}
+
+	/**
+	 * Test is a simplified version of a rule in the floodblocker style.
+	 */
+	@Test
+	public void testOptimizeWithOr2() {
+		String s = "highway=*" +
+				"& tunnel!=*" +
+				"& (layer!=* | layer=0)" +
+				" [0x02]\n"
+				;
+		RuleSet rs = makeRuleSet(s);
+		Element el = new Way(1);
+
+		el.addTag("highway", "primary");
+		GType type = getFirstType(rs, el);
+		assertNotNull(type);
+		assertEquals(2, type.getType());
+
+		el.addTag("layer", "0");
+		type = getFirstType(rs, el);
+		assertNotNull(type);
+		assertEquals(2, type.getType());
+
+		el.addTag("layer", "1");
+		type = getFirstType(rs, el);
+		assertNull(type);
+	}
+
+	@Test
+	public void testOptimizeWithOr3() throws Exception {
+		String s = "highway=* &  bridge!=* & " +
+				"   (mtb:scale>0 | mtb:scale='0+' | tracktype ~ 'grade[2-6]' |" +
+				"   sac_scale ~ '.*(mountain|alpine)_hiking' |" +
+				"   sport=via_ferrata) [0x3]";
+
+		RuleSet rs = makeRuleSet(s);
+
+		Element el = new Way(1);
+		el.addTag("highway", "primary");
+		el.addTag("mtb:scale", "0+");
+		GType type = getFirstType(rs, el);
+		assertNotNull(type);
+	}
+
+	/**
 	 * This simply is to make sure that actions that affect their own
 	 * conditions do not hang. There are no defined semantics for this.
 	 */
@@ -585,7 +678,7 @@ public class RuleFileReaderTest {
 		Way el = new Way(1);
 		el.addTag("highway", "primary");
 
-		final List<GType> list = new ArrayList<GType>();
+		final List<GType> list = new ArrayList<>();
 
 		rs.resolveType(el, new TypeResult() {
 			public void add(Element el, GType type) {
@@ -854,15 +947,12 @@ public class RuleFileReaderTest {
 		assertNotNull(type);
 	}
 
-	@Test
+	@Test(expected = SyntaxException.class)
 	public void testFunctionWithParameters() {
 		// a parameter in a function is not allowed yet
-		try {
-			// this should throw a SyntaxException
-			makeRuleSet("A=B & length(a) > 91 [0x5]");
-			assertTrue("Function with parameters are not allowed", false);
-		} catch (SyntaxException exp) {
-		}
+		// this should throw a SyntaxException
+		makeRuleSet("A=B & length(a) > 91 [0x5]");
+		assertTrue("Function with parameters are not allowed", false);
 	}
 	
 	@Test
@@ -1023,9 +1113,9 @@ public class RuleFileReaderTest {
 		el.addPoint(new Coord(2000,2000));
 		el.addPoint(new Coord(2000,1000));
 		if (closed)
-			el.addPoint(new Coord(1000,1000));
+			el.addPoint(el.getPoints().get(0));
 		el.setComplete(complete);
-		el.setClosed(true);
+		el.setClosedInOSM(true);
 		return el;
 	}
 	
@@ -1036,7 +1126,7 @@ public class RuleFileReaderTest {
 	 * resolved type.
 	 */
 	private GType getFirstType(Rule rs, Element el) {
-		final List<GType> types = new ArrayList<GType>();
+		final List<GType> types = new ArrayList<>();
 		rs.resolveType(el, new TypeResult() {
 			public void add(Element el, GType type) {
 				types.add(type);
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/StyleImplTest.java b/test/uk/me/parabola/mkgmap/osmstyle/StyleImplTest.java
index a5a1025..33728ea 100644
--- a/test/uk/me/parabola/mkgmap/osmstyle/StyleImplTest.java
+++ b/test/uk/me/parabola/mkgmap/osmstyle/StyleImplTest.java
@@ -65,8 +65,8 @@ public class StyleImplTest {
 	 */
 	@Test(expected = FileNotFoundException.class)
 	public void testBadStyleName() throws FileNotFoundException {
-		//noinspection UnusedDeclaration
 		Style style = new StyleImpl(STYLE_LOC, "no-such-style");
+		if (style != null) style= null; // pseudo use the value to calm down FindBugs
 	}
 
 	/**
@@ -77,8 +77,8 @@ public class StyleImplTest {
 	 */
 	@Test(expected = FileNotFoundException.class)
 	public void testBadStyleFileOnClasspath() throws FileNotFoundException {
-		//noinspection UnusedDeclaration
 		Style style = new StyleImpl("classpath:no-such-place", "default");
+		if (style != null) style= null; // pseudo use the value to calm down FindBugs
 	}
 
 	/**
@@ -90,8 +90,8 @@ public class StyleImplTest {
 	 */
 	@Test(expected = FileNotFoundException.class)
 	public void testBadStyleFileOnFilesystem() throws FileNotFoundException {
-		//noinspection UnusedDeclaration
 		Style style = new StyleImpl("/no-such-place/hopefully", "default");
+		if (style != null) style= null; // pseudo use the value to calm down FindBugs
 	}
 
 	private void printStyle(StyleImpl in) {
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/StyledConverterTest.java b/test/uk/me/parabola/mkgmap/osmstyle/StyledConverterTest.java
index 9c40cf1..9890ff9 100644
--- a/test/uk/me/parabola/mkgmap/osmstyle/StyledConverterTest.java
+++ b/test/uk/me/parabola/mkgmap/osmstyle/StyledConverterTest.java
@@ -24,7 +24,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.CoordNode;
+import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
 import uk.me.parabola.mkgmap.general.MapCollector;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
@@ -265,9 +265,11 @@ public class StyledConverterTest {
 				lines.add(road);
 			}
 
-			public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) { }
+			public int addRestriction(GeneralRouteRestriction grr) {
+				return 0;
+			}
 
-			public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { }
+			public void addThroughRoute(int junctionNodeId, long roadIdA, long roadIdB) { }
 		};
 
 		return new StyledConverter(style, coll, new EnhancedProperties());
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessActionTest.java b/test/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessActionTest.java
index 7fb8f72..379478b 100644
--- a/test/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessActionTest.java
+++ b/test/uk/me/parabola/mkgmap/osmstyle/actions/AddAccessActionTest.java
@@ -12,9 +12,9 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
-import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Way;
+import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
 
 import org.junit.Test;
 
@@ -34,7 +34,7 @@ public class AddAccessActionTest {
 		Action act = new AddAccessAction(value, false);
 		Element el = stdElement();
 		act.perform(el);
-		for (String accessTag : StyledConverter.ACCESS_TAGS) {
+		for (String accessTag : ACCESS_TAGS.keySet()) {
 			assertSame("a not changed", value, el.getTag(accessTag));
 		}
 	}
@@ -49,7 +49,7 @@ public class AddAccessActionTest {
 		Element el = stdElement();
 		act.perform(el);
 
-		for (String accessTag : StyledConverter.ACCESS_TAGS) {
+		for (String accessTag : ACCESS_TAGS.keySet()) {
 			assertEquals("subst access", ACCESSVAL, el.getTag(accessTag));
 		}
 	}
@@ -78,7 +78,7 @@ public class AddAccessActionTest {
 		Element el = stdElement();
 		el.addTag("mkgmap:bicycle", "yes");
 		act.perform(el);
-		for (String accessTag : StyledConverter.ACCESS_TAGS) {
+		for (String accessTag : ACCESS_TAGS.keySet()) {
 			if ("mkgmap:bicycle".equals(accessTag))
 				assertEquals("no overwrite", "yes", el.getTag(accessTag));
 			else
@@ -96,7 +96,7 @@ public class AddAccessActionTest {
 		Element el = stdElement();
 		el.addTag("mkgmap:bicycle", "yes");
 		act.perform(el);
-		for (String accessTag : StyledConverter.ACCESS_TAGS) {
+		for (String accessTag : ACCESS_TAGS.keySet()) {
 			assertEquals("no overwrite", "no", el.getTag(accessTag));
 		}
 	}
@@ -116,7 +116,7 @@ public class AddAccessActionTest {
 		Element el = stdElement();
 		act.perform(el);
 
-		for (String accessTag : StyledConverter.ACCESS_TAGS) 
+		for (String accessTag : ACCESS_TAGS.keySet()) 
 			assertNull(accessTag+"a not set", el.getTag(accessTag));
 	}
 
@@ -133,7 +133,7 @@ public class AddAccessActionTest {
 		el.addTag("hello", "hello");
 		act.perform(el);
 
-		for (String accessTag : StyledConverter.ACCESS_TAGS) 
+		for (String accessTag : ACCESS_TAGS.keySet()) 
 			assertEquals(accessTag+" is set", ACCESSVAL, el.getTag(accessTag));
 	}
 
@@ -150,7 +150,7 @@ public class AddAccessActionTest {
 		el.addTag("world", "world");
 		act.perform(el);
 
-		for (String accessTag : StyledConverter.ACCESS_TAGS) 
+		for (String accessTag : ACCESS_TAGS.keySet()) 
 			assertEquals(accessTag+" is set", ACCESSVAL, el.getTag(accessTag));
 	}
 
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java b/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java
new file mode 100644
index 0000000..52de042
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * 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.osmstyle.actions;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Test substitutions when building values with ValueBuilder.
+ */
+public class ValueBuilderTest {
+	@Test
+	public void testVariable() {
+		ValueBuilder vb = new ValueBuilder("${name} road");
+
+		Element el = new Way(1);
+		el.addTag("name", "abc abc");
+
+		String s = vb.build(el, null);
+		assertEquals("abc abc road", s);
+	}
+
+	@Test
+	public void testSimpleSubst() {
+		ValueBuilder vb = new ValueBuilder("init ${name|subst:abc=>xyz} final");
+
+		Element el = new Way(1);
+		el.addTag("name", "abc road abc");
+
+		String s = vb.build(el, null);
+		assertEquals("init xyz road xyz final", s);
+	}
+
+	@Test
+	public void testMultiSubst() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:abc=>xyz|subst:def=>www|def:unset}");
+
+		Element el = new Way(1);
+
+		// No tags set, so default value will be applied.
+		String s = vb.build(el, null);
+		assertEquals("name not set, so default is applied", "unset", s);
+
+		// Name tag is set, so substitutions are made
+		el.addTag("name", "abc def");
+		s = vb.build(el, null);
+		assertEquals("substitutions in name", "xyz www", s);
+	}
+
+	@Test
+	public void testSubstWithSpace() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:abc=>x y z }!");
+
+		Element el = new Way(1);
+		el.addTag("name", "Tabc");
+
+		String s = vb.build(el, null);
+		assertEquals("Tx y z !", s);
+	}
+
+	@Test
+	public void testQuotedArg() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:'abc=>x y z '}!");
+
+		Element el = new Way(1);
+		el.addTag("name", "Tabc");
+
+		String s = vb.build(el, null);
+		assertEquals("Tx y z !", s);
+	}
+
+	@Test
+	public void testDQuotedArg() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:\"abc=>x y z \"}!");
+
+		Element el = new Way(1);
+		el.addTag("name", "Tabc");
+
+		String s = vb.build(el, null);
+		assertEquals("Tx y z !", s);
+	}
+
+	@Test
+	public void testQuotedArgs() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:'abc=>x|y'|subst:'defg=>w|w\"w'|def:'unset string' }");
+
+		Element el = new Way(1);
+
+		// No tags set, so default value will be applied.
+		String s = vb.build(el, null);
+		assertEquals("name not set, so default is applied", "unset string", s);
+
+		// Name tag is set, so substitutions are made
+		el.addTag("name", "abc defg");
+		s = vb.build(el, null);
+		assertEquals("substitutions in name", "x|y w|w\"w", s);
+	}
+
+	@Test
+	public void testSpacedQuotedArgs() {
+		ValueBuilder vb = new ValueBuilder("${name | subst:'abc=>x|y' | subst:'defg=>w|w' | def:'unset string' }");
+		Element el = new Way(1);
+
+		// No tags set, so default value will be applied.
+		String s = vb.build(el, null);
+		assertEquals("name not set, so default is applied", "unset string", s);
+
+		// Name tag is set, so substitutions are made
+		el.addTag("name", "abc defg");
+		s = vb.build(el, null);
+		assertEquals("substitutions in name", "x|y w|w", s);
+	}
+
+	@Test
+	public void testQuotedSplitLines() {
+		String value =
+				"${cs:phone|subst:^00~>+|subst:[-\n" +
+				"()]~>|subst:^0~>+353|subst:^+3530~>+353}";
+		ValueBuilder vb = new ValueBuilder(value);
+
+		Element el = new Way(1);
+		el.addTag("mkgmap:country", "IRL");
+		el.addTag("cs:phone", "00(22)5554-444");
+
+		String s = vb.build(el, null);
+
+		assertEquals("+225554444", s);
+	}
+
+	@Test
+	public void testExample() {
+		ValueBuilder vb = new ValueBuilder("${name|subst:'^(Doctor|Dokter) ~>Dr '}");
+
+		Element el = new Way(1);
+		el.addTag("name", "Doctor Who");
+
+		String s = vb.build(el, null);
+		assertEquals("Dr Who", s);
+	}
+
+	@Test
+	public void testEmptyArg() {
+		ValueBuilder vb = new ValueBuilder("${name|def:}");
+
+		Element el = new Way(1);
+
+		String s = vb.build(el, null);
+		assertEquals("", s);
+	}
+
+	@Test
+	public void testEmptyQuotedArg() {
+		ValueBuilder vb = new ValueBuilder("${name|def:''}");
+
+		Element el = new Way(1);
+
+		String s = vb.build(el, null);
+		assertEquals("", s);
+	}
+
+	@Test
+	public void testUsedTags() {
+		ValueBuilder vb = new ValueBuilder("${name}");
+
+		Element el = new Way(1);
+		el.addTag("name", "fred");
+		el.addTag("highway", "primary");
+		vb.build(el, null);
+
+		Set<String> exp = new HashSet<>();
+		exp.add("name");
+		assertEquals(exp, vb.getUsedTags());
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java b/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java
index cff6916..b5ce727 100644
--- a/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java
+++ b/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java
@@ -17,8 +17,6 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -29,30 +27,6 @@ import static org.junit.Assert.*;
 
 
 public class ElementTest {
-	/*
-	 * Test the iterator.
-	 */
-	@Test
-	public void testIterator() {
-		Element el = new Way(1);
-
-		el.addTag("a", "1");
-		el.addTag("b", "2");
-		el.addTag("c", "3");
-
-		Collection<String> l = new ArrayList<String>();
-		for (String s : el) {
-			l.add(s);
-		}
-		assertEquals("list size", 3, l.size());
-
-		Object[] observeds = l.toArray();
-		Arrays.sort(observeds);
-		String[] res = {"a=1", "b=2", "c=3"};
-		Arrays.sort(res);
-		assertArrayEquals("list includes wildcards", res, observeds);
-	}
-
 	@Test
 	public void testEntryIterator() {
 		Element el = new Way(1);
@@ -64,7 +38,7 @@ public class ElementTest {
 		List<String> keys = new ArrayList<String>();
 		List<String> values = new ArrayList<String>();
 
-		for (Map.Entry<String, String> ent : el.getEntryIteratable()) {
+		for (Map.Entry<String, String> ent : el.getTagEntryIterator()) {
 			keys.add(ent.getKey());
 			values.add(ent.getValue());
 		}
diff --git a/test/uk/me/parabola/mkgmap/reader/osm/RestrictionRelationTest.java b/test/uk/me/parabola/mkgmap/reader/osm/RestrictionRelationTest.java
new file mode 100644
index 0000000..a98fd09
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/reader/osm/RestrictionRelationTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014
+ * 
+ *  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.
+ * 
+ */ 
+
+package uk.me.parabola.mkgmap.reader.osm;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
+
+/**
+ * Test evaluation of restriction relations
+ * @author GerdP
+ *
+ */
+public class RestrictionRelationTest {
+	private final static byte DEFAULT_EXCEPTION =  AccessTagsAndBits.FOOT  | AccessTagsAndBits.EMERGENCY;
+	
+	@Test
+	public void basicTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_left_turn");
+		gr.addTag("except","bicycle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+	
+	@Test
+	public void footTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:foot","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(~AccessTagsAndBits.FOOT , rr.getExceptMask());
+	}
+
+	@Test
+	public void footAndBikeTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:foot","no_left_turn");
+		gr.addTag("restriction:bicycle","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(~(AccessTagsAndBits.FOOT |  AccessTagsAndBits.BIKE), rr.getExceptMask());
+	}
+
+	@Test
+	public void psvTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_left_turn");
+		gr.addTag("except","psv");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BUS | AccessTagsAndBits.TAXI | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void multipleExeptTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_left_turn");
+		gr.addTag("except","psv;bicycle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BUS | AccessTagsAndBits.TAXI | AccessTagsAndBits.BIKE | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void multipleExeptTestWithUnknown() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_left_turn");
+		gr.addTag("except","psv;xyz;bicycle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BUS | AccessTagsAndBits.TAXI | AccessTagsAndBits.BIKE | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void multipleExcplicitTestWithUnknown() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:motorcar","no_left_turn");
+		gr.addTag("restriction:hgv","no_left_turn");
+		gr.addTag("restriction:xyz","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(~(AccessTagsAndBits.CAR |AccessTagsAndBits.TRUCK)  , rr.getExceptMask());
+	}
+
+	@Test
+	public void excplicitTestWithUnknown() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:xyz","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertFalse(rr.isValid());
+	}
+
+	@Test
+	public void motor_vehicleTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:motor_vehicle","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void motor_vehicleTest2() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_left_turn");
+		gr.addTag("type", "restriction:motor_vehicle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void motor_vehicleExceptCarTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("except","motorcar");
+		gr.addTag("restriction:motor_vehicle","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | AccessTagsAndBits.CAR | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void motor_vehicleExceptCarTest2() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("except","motorcar");
+		gr.addTag("restriction", "no_left_turn");
+		gr.addTag("type", "restriction:motor_vehicle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | AccessTagsAndBits.CAR | DEFAULT_EXCEPTION, rr.getExceptMask());
+	}
+
+	@Test
+	public void noEmergencyTest1() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:emergency","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(~AccessTagsAndBits.EMERGENCY, rr.getExceptMask());
+	}
+
+	@Test
+	public void noEmergencyTest2() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:motor_vehicle","no_left_turn");
+		gr.addTag("restriction:emergency","no_left_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(AccessTagsAndBits.BIKE | AccessTagsAndBits.FOOT, rr.getExceptMask());
+	}
+
+	@Test
+	public void mixedDirectionsTest() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:car","no_left_turn");
+		gr.addTag("restriction:truck","no_u_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertFalse(rr.isValid());
+	}
+
+	@Test
+	public void ignoreMotorcycleTest1() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction:motorcycle","no_u_turn");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertFalse(rr.isValid());
+	}
+	
+	@Test
+	public void ignoreMotorcycleTest2() {
+		GeneralRelation gr = createRelation();
+		gr.addTag("restriction","no_u_turn");
+		gr.addTag("except","motorcycle");
+		RestrictionRelation rr = new RestrictionRelation(gr);
+		rr.eval(new Area(-100,-100,1000,1000));
+		assertTrue(rr.isValid());
+		assertEquals(DEFAULT_EXCEPTION, rr.getExceptMask());		
+	}
+	
+	private static GeneralRelation createRelation(){
+		GeneralRelation gr = new GeneralRelation(1);
+		gr.addTag("type", "restriction");
+		Way fromWay = new Way(1);
+		Way toWay = new Way(2);
+		Coord viaCoord = new Coord(100,100);
+		Node viaNode = new Node(1, viaCoord);
+		fromWay.addPoint(new Coord(0,0));
+		fromWay.addPoint(viaCoord);
+		toWay.addPoint(new Coord(120,200));
+		toWay.addPoint(viaCoord);
+		gr.addElement("from", fromWay);
+		gr.addElement("to", toWay);
+		gr.addElement("via", viaNode);
+		return gr;
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java b/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
index 3ea8c67..d72366f 100644
--- a/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
+++ b/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
@@ -64,6 +64,20 @@ public class TokenScannerTest {
 		assertEquals(2, ts.getLinenumber());
 	}
 
+	@Test
+	public void testLinenumberBlankLines() throws Exception {
+		String s = "hello world\n\nnext tokens\n";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		ts.readLine();
+
+		// still on first line
+		assertEquals(1, ts.getLinenumber());
+
+		// now next line, which is line three in the file as blank line is skipped
+		ts.nextValue();
+		assertEquals(3, ts.getLinenumber());
+	}
+
 	/**
 	 * This is a misfeature of skipSpace, but relied on everywhere.
 	 */
@@ -88,4 +102,50 @@ public class TokenScannerTest {
 		assertEquals("hello", ts.nextValue());
 		assertEquals("#", ts.nextValue());
 	}
+
+	@Test
+	public void testNextWithNoEOL() {
+		String s = "hello";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		Token token = ts.nextToken();
+		assertEquals("hello", token.getValue());
+	}
+
+	@Test
+	public void testReadLineWithNoEOL() {
+		String s = "hello";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		String line = ts.readLine();
+		assertEquals("hello", line);
+	}
+
+	@Test
+	public void testReadLineWithCR() {
+		String s = "hello\rworld\r";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		String line = ts.readLine();
+		assertEquals("hello", line);
+		line = ts.readLine();
+		assertEquals("world", line);
+	}
+
+	@Test
+	public void testReadLineReturnsEmptyIfNothing() {
+		String s = "";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		String line = ts.readLine();
+		assertEquals("", line);
+	}
+
+	@Test
+	public void testCRLFIsOneLineEnding() {
+		String s = "fred\r\n";
+		TokenScanner ts = new TokenScanner("", new StringReader(s));
+		Token token = ts.nextRawToken();
+		assertEquals(TokType.TEXT, token.getType());
+		token = ts.nextRawToken();
+		assertEquals(TokType.EOL, token.getType());
+		token = ts.nextRawToken();
+		assertEquals(TokType.EOF, token.getType());
+	}
 }
diff --git a/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java b/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java
index 354155c..a9b74fb 100644
--- a/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java
+++ b/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java
@@ -17,6 +17,7 @@ import java.io.CharArrayReader;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
+import java.nio.charset.Charset;
 
 import uk.me.parabola.imgfmt.app.srt.Sort;
 
@@ -31,6 +32,8 @@ public class SrtTextReaderTest {
 			"codepage 1252\n" +
 			"code 01, 02, 03\n";
 
+	private static final Charset charset = Charset.forName("cp1252");
+
 	/**
 	 * Test for a simple case of two letters that have the same major and minor
 	 * sort codes.
@@ -99,15 +102,30 @@ public class SrtTextReaderTest {
 	}
 
 	/**
-	 * Check that 88 is not a letter in 1252.
+	 * We can have any unicode character in the file.
+	 */
+	@Test
+	public void testUnicodeChars() throws IOException {
+		char[] sortcodes = getSortcodes("< :\n< ›\n");
+		assertEquals(1, major(sortcodes[':']));
+		int b = getByteInCodePage("›");
+		assertEquals(2, major(sortcodes[b & 0xff]));
+	}
+
+	/**
+	 * Check character that is not a letter in 1252.
 	 * @throws Exception
 	 */
 	@Test
 	public void testNotLetter() throws Exception {
-		Sort sort = getSort("code 88");
-		byte flags = sort.getFlags(0x88);
+		Sort sort = getSort("code 00a8");
+		byte flags = sort.getFlags(0xa8);
 
 		assertEquals(0, flags);
+
+		sort = getSort("code 0041");
+		flags = sort.getFlags(0x41);
+		assertNotSame(0, flags);
 	}
 
 	@Test
@@ -123,6 +141,10 @@ public class SrtTextReaderTest {
 		return sort.getSortPositions();
 	}
 
+	private byte getByteInCodePage(String ch) {
+		return ch.getBytes(charset)[0];
+	}
+
 	private Sort getSort(String text) throws IOException {
 		String s = BASE + text + "\n";
 
diff --git a/test/uk/me/parabola/mkgmap/typ/TypTextReaderTest.java b/test/uk/me/parabola/mkgmap/typ/TypTextReaderTest.java
index d43bfbf..ad800bb 100644
--- a/test/uk/me/parabola/mkgmap/typ/TypTextReaderTest.java
+++ b/test/uk/me/parabola/mkgmap/typ/TypTextReaderTest.java
@@ -13,7 +13,6 @@
 package uk.me.parabola.mkgmap.typ;
 
 import java.io.BufferedReader;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
@@ -25,7 +24,6 @@ import java.nio.channels.FileChannel;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
-import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.app.typ.ShapeStacking;
 import uk.me.parabola.imgfmt.app.typ.TYPFile;
 import uk.me.parabola.imgfmt.app.typ.TypData;
@@ -34,6 +32,7 @@ import uk.me.parabola.imgfmt.app.typ.TypParam;
 import uk.me.parabola.imgfmt.app.typ.TypPoint;
 import uk.me.parabola.imgfmt.app.typ.TypPolygon;
 import uk.me.parabola.imgfmt.sys.FileImgChannel;
+import uk.me.parabola.mkgmap.srt.SrtTextReader;
 
 import func.lib.ArrayImgWriter;
 import func.lib.TestUtils;
@@ -229,8 +228,6 @@ public class TypTextReaderTest {
 			OutputStream os = new FileOutputStream("hello");
 			os.write(w.getBytes());
 			os.close();
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
 		} catch (IOException e) {
 			e.printStackTrace();
 		}
@@ -319,7 +316,7 @@ public class TypTextReaderTest {
 		TypTextReader tr = new TypTextReader();
 		tr.read("string", r);
 		if (tr.getData().getSort() == null)
-			tr.getData().setSort(Sort.defaultSort(1252));
+			tr.getData().setSort(SrtTextReader.sortForCodepage(1252));
 		return tr;
 	}
 }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mkgmap.git



More information about the Pkg-grass-devel mailing list