[mkgmap] 02/03: Imported Upstream version 0.0.0+svn3087

Andreas Tille tille at debian.org
Thu Mar 6 17:51:50 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 a379d026844d8df85aa0a68f8c4acd623a6cd45c
Author: Andreas Tille <tille at debian.org>
Date:   Thu Mar 6 18:47:10 2014 +0100

    Imported Upstream version 0.0.0+svn3087
---
 .idea/codeStyleSettings.xml                        |    1 -
 .idea/inspectionProfiles/Mapping.xml               |   27 +-
 .idea/runConfigurations/mkgmap.xml                 |    2 +-
 doc/options.txt                                    |   10 +-
 extra/src/uk/me/parabola/util/CollationRules.java  |  372 ++++++
 resources/help/en/options                          |    5 +
 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 ++
 src/uk/me/parabola/imgfmt/Utils.java               |  100 ++
 src/uk/me/parabola/imgfmt/app/Area.java            |   73 +-
 src/uk/me/parabola/imgfmt/app/Coord.java           |  362 +++++-
 src/uk/me/parabola/imgfmt/app/CoordNode.java       |    7 +
 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/net/NETFile.java     |   27 +-
 src/uk/me/parabola/imgfmt/app/net/NODHeader.java   |   39 +-
 src/uk/me/parabola/imgfmt/app/net/RoadDef.java     |    8 +-
 src/uk/me/parabola/imgfmt/app/net/RouteArc.java    |  227 ++--
 src/uk/me/parabola/imgfmt/app/net/RouteCenter.java |   10 +-
 src/uk/me/parabola/imgfmt/app/net/RouteNode.java   |   78 +-
 .../parabola/imgfmt/app/net/RouteRestriction.java  |   19 +-
 src/uk/me/parabola/imgfmt/app/net/TableA.java      |  114 +-
 src/uk/me/parabola/imgfmt/app/net/TableC.java      |   17 +-
 src/uk/me/parabola/imgfmt/app/srt/SRTFile.java     |    2 +-
 src/uk/me/parabola/imgfmt/app/srt/Sort.java        |  348 +++--
 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/log/Logger.java                 |    4 +
 src/uk/me/parabola/mkgmap/build/MapArea.java       |   61 +-
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |   15 +-
 .../me/parabola/mkgmap/combiners/MdrBuilder.java   |   39 +-
 .../mkgmap/filters/DouglasPeuckerFilter.java       |    8 +-
 .../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 |   42 +-
 src/uk/me/parabola/mkgmap/general/MapDetails.java  |   15 +-
 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/MapShape.java    |  153 +--
 .../me/parabola/mkgmap/general/PolygonClipper.java |   23 +-
 src/uk/me/parabola/mkgmap/general/RoadNetwork.java |   79 +-
 .../me/parabola/mkgmap/main/AbstractTestMap.java   |    5 +-
 src/uk/me/parabola/mkgmap/main/MapMaker.java       |   10 +-
 .../parabola/mkgmap/osmstyle/RuleFileReader.java   |   25 +-
 .../parabola/mkgmap/osmstyle/StyledConverter.java  | 1017 ++++++++-------
 .../parabola/mkgmap/osmstyle/WrongAngleFixer.java  | 1340 ++++++++++++++++++++
 .../mkgmap/osmstyle/function/AreaSizeFunction.java |    2 +-
 .../mkgmap/osmstyle/function/IsClosedFunction.java |    3 +-
 .../osmstyle/housenumber/HousenumberGenerator.java |   29 +-
 src/uk/me/parabola/mkgmap/reader/dem/DEM.java      |   20 +-
 src/uk/me/parabola/mkgmap/reader/osm/CoordPOI.java |   27 +-
 .../parabola/mkgmap/reader/osm/ElementSaver.java   |    7 +-
 .../parabola/mkgmap/reader/osm/HighwayHooks.java   |    4 +-
 .../mkgmap/reader/osm/LinkDestinationHook.java     |   56 +-
 .../mkgmap/reader/osm/MultiPolygonRelation.java    |  334 ++---
 .../me/parabola/mkgmap/reader/osm/OsmHandler.java  |    2 +-
 .../mkgmap/reader/osm/POIGeneratorHook.java        |    6 +-
 .../mkgmap/reader/osm/RestrictionRelation.java     |   46 +-
 .../parabola/mkgmap/reader/osm/SeaGenerator.java   |  135 +-
 .../mkgmap/reader/osm/SeaPolygonRelation.java      |    1 +
 src/uk/me/parabola/mkgmap/reader/osm/Way.java      |   49 +-
 .../reader/osm/boundary/BoundaryConverter.java     |    2 +-
 .../mkgmap/reader/osm/boundary/BoundaryDiff.java   |    5 +-
 .../reader/osm/boundary/BoundaryElement.java       |    2 +-
 .../reader/osm/boundary/BoundaryElementSaver.java  |    2 +-
 .../reader/osm/boundary/BoundaryFile2Gpx.java      |    2 +-
 .../reader/osm/boundary/BoundaryPreprocessor.java  |    2 +-
 .../reader/osm/boundary/BoundaryQuadTree.java      |    4 +-
 .../reader/osm/boundary/BoundaryRelation.java      |   20 +-
 .../mkgmap/reader/osm/boundary/BoundarySaver.java  |   51 +-
 .../mkgmap/reader/osm/boundary/BoundaryUtil.java   |   30 -
 .../mkgmap/reader/polish/PolishMapDataSource.java  |   22 +-
 .../parabola/mkgmap/reader/polish/RoadHelper.java  |    2 +-
 src/uk/me/parabola/mkgmap/scan/TokenScanner.java   |   11 +-
 .../mkgmap/sea/optional/PrecompSeaGenerator.java   |   14 +-
 .../mkgmap/sea/optional/PrecompSeaMerger.java      |   26 +-
 .../mkgmap/sea/optional/PrecompSeaSaver.java       |   26 +-
 src/uk/me/parabola/mkgmap/srt/SrtTextReader.java   |  203 +--
 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/QuadTreeNode.java          |    5 +-
 test/func/SimpleTest.java                          |   14 +-
 test/func/route/SimpleRouteTest.java               |   50 +-
 test/main/SortTest.java                            |  273 ++++
 test/uk/me/parabola/imgfmt/UtilsTest.java          |   56 +
 .../imgfmt/app/labelenc/Format6EncoderTest.java    |   51 +
 .../parabola/imgfmt/app/labelenc/LabelEncTest.java |   43 +
 .../me/parabola/imgfmt/app/srt/SortExpandTest.java |   10 +-
 test/uk/me/parabola/imgfmt/app/srt/SortTest.java   |  113 +-
 .../parabola/imgfmt/app/srt/SrtCollatorTest.java   |  142 +++
 .../mkgmap/filters/ShapeMergeFilterTest.java       |  385 ++++++
 .../uk/me/parabola/mkgmap/general/MapLineTest.java |   51 +
 .../parabola/mkgmap/general/PointInShapeTest.java  |  286 -----
 .../mkgmap/osmstyle/RuleFileReaderTest.java        |   15 +-
 .../me/parabola/mkgmap/scan/TokenScannerTest.java  |   46 +
 .../me/parabola/mkgmap/srt/SrtTextReaderTest.java  |   29 +-
 .../me/parabola/mkgmap/typ/TypTextReaderTest.java  |    7 +-
 131 files changed, 7765 insertions(+), 3244 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/inspectionProfiles/Mapping.xml b/.idea/inspectionProfiles/Mapping.xml
index 3de260f..7e65c41 100644
--- a/.idea/inspectionProfiles/Mapping.xml
+++ b/.idea/inspectionProfiles/Mapping.xml
@@ -49,7 +49,7 @@
     <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="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 +60,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" />
@@ -102,7 +102,7 @@
     <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="AndroidLintProtectedPermissions" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -126,9 +126,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" />
@@ -498,7 +498,7 @@
     <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="ConvertAnnotations" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -1117,7 +1117,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" />
@@ -1807,6 +1807,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 +1929,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" />
@@ -2162,7 +2165,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 +2321,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 +2395,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" />
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..94719ee 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
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..0a460d0
--- /dev/null
+++ b/extra/src/uk/me/parabola/util/CollationRules.java
@@ -0,0 +1,372 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.util.Arrays;
+import java.util.Formatter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeSet;
+
+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();
+
+		List<Integer> blocks = Arrays.asList(0);
+		if (isUnicode)
+			blocks = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7);
+		for (int block : blocks)
+			addBlock(col, block);
+
+		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 == 65533)
+				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 {
+					if (next > 0xffff) {
+						cp.addChar(new CharPosition(ch));
+						cp.setOrder(next);
+					} else {
+						cp.addOrder(next, index);
+					}
+				}
+
+				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);
+			}
+		}
+	}
+
+	/**
+	 * 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;
+
+		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;
+		case '˜':
+			CharPosition tilde = charMap.get('~');
+			cp.first = tilde.first;
+			cp.second = tilde.second + 1;
+			cp.third = tilde.third + 1;
+			cp.nextChar = null;
+			break;
+		case '™':
+			CharPosition o = charMap.get('T');
+			cp.first = o.first;
+			cp.second = o.second;
+			cp.third = o.third;
+			cp.nextChar = charMap.get('M').copy();
+			System.out.println("TM as " + cp);
+			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;
+
+			//noinspection MalformedFormatString
+			System.out.printf("expand %c to ", cp.getUnicode());
+
+			for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
+				cp2.second &= 0xff0000;
+				cp2.third = cp2.third >= 0x8f0000 ? 0x8f0000 - 1 : 0x000000;
+				CharPosition floor = basePositionMap.ceiling(cp2);
+				if (floor == null) {
+					System.out.printf(" NF");
+					continue;
+				}
+				System.out.printf(" %s", fmtChar(floor.getUnicode()));
+			}
+			System.out.println();
+
+			for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
+				cp2.second &= 0xff0000;
+				cp2.third = cp2.third>=0x8f0000? 0x8f0000: 0x000000;
+				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;
+		}
+	}
+}
\ No newline at end of file
diff --git a/resources/help/en/options b/resources/help/en/options
index 280c28c..dd754c9 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -372,6 +372,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 +396,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
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index bc82089..19765ff 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: 3087
+build.timestamp: 2014-03-06T13:27:41+0000
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/src/uk/me/parabola/imgfmt/Utils.java b/src/uk/me/parabola/imgfmt/Utils.java
index d4bc516..d19555f 100644
--- a/src/uk/me/parabola/imgfmt/Utils.java
+++ b/src/uk/me/parabola/imgfmt/Utils.java
@@ -244,6 +244,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 +295,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/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java
index 2db395e..02dc088 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.
@@ -33,15 +35,28 @@ import uk.me.parabola.imgfmt.Utils;
  * @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;
 	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 +66,7 @@ public class Coord implements Comparable<Coord> {
 	public Coord(int latitude, int longitude) {
 		this.latitude = latitude;
 		this.longitude = longitude;
+		latDelta = lonDelta = 0;
 	}
 
 	/**
@@ -61,6 +77,43 @@ 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() {
@@ -163,8 +216,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 +234,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 +337,30 @@ 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.
 	 */
 	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;
 	}
 
 	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)
@@ -257,26 +385,28 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	public Coord makeBetweenPoint(Coord other, double fraction) {
-		return new Coord((int)(latitude + (other.latitude - latitude) * fraction),
-						 (int)(longitude + (other.longitude - longitude) * fraction));
+		int lat30 = (int) (getHighPrecLat() + (other.getHighPrecLat() - getHighPrecLat()) * fraction);
+		int lon30 = (int) (getHighPrecLon() + (other.getHighPrecLon() - getHighPrecLon()) * fraction);
+		return makeHighPrecCoord(lat30, lon30);
 	}
 
-
+	
 	// returns bearing (in degrees) from current point to another point
 	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);
-
+		// 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;
+		return Utils.atan2_approximation(y, x) * 180 / Math.PI;
 	}
-
+	
 	/**
 	 * Sort lexicographically by longitude, then latitude.
 	 *
@@ -300,23 +430,143 @@ 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);
+		else
+			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 (double) val30 * BIT30_RAD_FACTOR;
+	}
+
+	/**
+	 * @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 (double) getHighPrecLat() * (360.0D / (1 << 30));
+	}
+	
+	/**
+	 * @return longitude in degrees with highest avail. precision
+	 */
+	public double getLonDegrees(){
+		return (double) getHighPrecLon() * (360.0D / (1 << 30));
+	}
+	
+	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<Coord>();
+		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;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/CoordNode.java b/src/uk/me/parabola/imgfmt/app/CoordNode.java
index 9e9872f..706d7c4 100644
--- a/src/uk/me/parabola/imgfmt/app/CoordNode.java
+++ b/src/uk/me/parabola/imgfmt/app/CoordNode.java
@@ -40,6 +40,13 @@ public class CoordNode extends Coord {
 		preserved(true);
 	}
 
+	public CoordNode(Coord other, long id, boolean boundary){
+		super(other);
+		this.id = id;
+		setOnBoundary(boundary);
+		preserved(true);
+		
+	}
 	public long 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/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/NODHeader.java b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
index 3e41d54..c97f605 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
@@ -23,6 +23,10 @@ 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 {
@@ -34,9 +38,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();
+	private final int[] classBoundaries = new int[5];
 
     private int flags;
     private int align;
+    private int mult1;
 	private int tableARecordLen;
 
 	/** 
@@ -64,12 +71,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
@@ -87,8 +105,9 @@ public class NODHeader extends CommonHeader {
 			val |= 0x0300;
 		writer.putInt(val);
 
-		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);
@@ -133,6 +152,14 @@ public class NODHeader extends CommonHeader {
 		return boundary;
 	}
 
+	public Section getHighClassBoundary() {
+		return highClassBoundary;
+	}
+
+	public int[] getClassBoundaries() {
+		return classBoundaries;
+	}
+
 	public static void setDriveOnLeft(boolean dol) {
 		driveOnLeft.set(dol);
 	}
@@ -145,6 +172,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..ce55a30 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
@@ -170,9 +170,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 +180,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;
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
index dca1f85..d770e6c 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,66 @@ 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;
 
 	/**
-	 * 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 directLength the length of the arc in meter (A-E) 
+	 * @param curveEnabled false means don't write curve bytes 
+	 * @param pointsHash
 	 */
 	public RouteArc(RoadDef roadDef,
 					RouteNode source, RouteNode dest,
-					int initialHeading, int finalHeading,
-					double length,
+					double initialBearing, double finalBearing, double directBearing,
+					double arcLength,
+					double directLength,
 					boolean curveEnabled,
 					int pointsHash) {
 		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 = convertMeters(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.pointsHash = pointsHash;
+		int ratio = 0;
+		if (arcLength > directLength){
+			ratio = (byte) ((int)(directLength * 32 / arcLength) & 0x1f);
+			if (ratio > 26) 
+				ratio = 0;
+		}
+		if (ratio == 0 && length >= (1 << 14))
+			ratio = 0x1f;
+		lengthRatio = (byte)ratio;
+		haveCurve = curveEnabled && 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 +145,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)
@@ -180,12 +204,27 @@ public class RouteArc {
 	 
 
 	// units of 16 feet
-	final static double LENGTH_FACTOR = 3.2808 / 16;
+	final static double LENGTH_FACTOR = 1.0 / (2.391 * 2);
+	//old 3.2808 / 16;
 	private static int convertMeters(double l) {
-		return (int) (l * LENGTH_FACTOR);
+		return (int) (l * LENGTH_FACTOR + 0.5);
+
 	}
 
-	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));
@@ -210,16 +249,23 @@ public class RouteArc {
 			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 (useCompactDirs){
+			// determine if we have to write direction info
+			if (compactedDir != null)
+				writer.put(compactedDir);
+		} else 
+			writer.put((byte)(initialHeading * 256 / 360));
+		
 		if (haveCurve) {
 			int[] curvedat = encodeCurve();
 			for (int aCurvedat : curvedat)
@@ -258,81 +304,78 @@ 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 = ((int) (directHeading * 256 / 360));
+		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 ((dh & 0xfffffff8) != 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 +383,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() {
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java b/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
index 84ea542..0429482 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteCenter.java
@@ -80,13 +80,11 @@ public class RouteCenter {
 		for (RouteNode node : nodes)
 			node.write(writer);
 
-		int mult = 1 << NODHeader.DEF_ALIGN;
+		int alignment = 1 << NODHeader.DEF_ALIGN;
+		int alignMask = alignment - 1;
 
-		// 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..8cfa001 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
@@ -14,12 +14,12 @@
  */
 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.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 +44,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
 
@@ -65,7 +65,7 @@ public class RouteNode implements Comparable<RouteNode> {
 	// arcs to this node
 	private final List<RouteArc> incomingArcs = new ArrayList<RouteArc>(4);
 
-	private int flags = F_UNK_NEEDED;
+	private int flags;
 
 	private final CoordNode coord;
 	private char latOff;
@@ -98,8 +98,6 @@ public class RouteNode implements Comparable<RouteNode> {
 	}
 
 	public void addArc(RouteArc arc) {
-		if (!arcs.isEmpty())
-			arc.setNewDir();
 		arcs.add(arc);
 		int cl = arc.getRoadDef().getRoadClass();
 		if(log.isDebugEnabled())
@@ -166,6 +164,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 +174,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 = (byte)(arc.getInitialHeading() * 256 / 360);
+					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()) {
@@ -301,7 +331,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 +365,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 +376,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;
@@ -414,7 +444,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;
@@ -507,8 +537,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)
@@ -561,19 +591,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 +611,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 +626,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;
 						}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java b/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
index 3e29076..c8cefe9 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteRestriction.java
@@ -59,12 +59,20 @@ public class RouteRestriction {
 	// mask that specifies which vehicle types the restriction doesn't apply to
 	private final byte exceptMask;
 
+	private final boolean exceptPedestrian;
+	private final boolean exceptEmergency;
+
 	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;
+	
+	// additional flags that can be passed via exceptMask  
+	public final static byte EXCEPT_FOOT     = 0x08; // not written as such
+	public final static byte EXCEPT_EMERGENCY = (byte)0x80; // not written as such
+	private final static byte SPECIAL_EXCEPTION_MASK = ~(EXCEPT_FOOT|EXCEPT_EMERGENCY);
 
 	/**
 	 * Create a route restriction.
@@ -76,7 +84,9 @@ public class RouteRestriction {
 		assert from.getSource().equals(to.getSource()) : "arcs in restriction don't meet";
 		this.from = from;
 		this.to = to;
-		this.exceptMask = exceptMask;
+		this.exceptMask = (byte)(exceptMask & SPECIAL_EXCEPTION_MASK); 
+		this.exceptPedestrian = (exceptMask & EXCEPT_FOOT) != 0;
+		this.exceptEmergency  = (exceptMask & EXCEPT_EMERGENCY) != 0;
 	}
 
 	private int calcOffset(RouteNode node, int tableOffset) {
@@ -94,7 +104,12 @@ public class RouteRestriction {
 	 */
 	public void write(ImgFileWriter writer, int tableOffset) {
 		int header = HEADER;
-
+		
+		if (exceptPedestrian)
+			header |= 0x0200;
+		if (exceptEmergency)
+			header |= 0x0400;
+		
 		if(exceptMask != 0)
 			header |= 0x0800;
 
diff --git a/src/uk/me/parabola/imgfmt/app/net/TableA.java b/src/uk/me/parabola/imgfmt/app/net/TableA.java
index 90aa916..3573fed 100644
--- a/src/uk/me/parabola/imgfmt/app/net/TableA.java
+++ b/src/uk/me/parabola/imgfmt/app/net/TableA.java
@@ -39,11 +39,11 @@ public class TableA {
 	private int offset;
 
 	// arcs for paved ways
-	private final HashMap<Arc,Integer> pavedArcs = new LinkedHashMap<Arc,Integer>();
+	private final HashMap<RoadDef,Integer> pavedArcs = 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>();
 
 	private static int count;
 
@@ -55,44 +55,6 @@ 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
@@ -100,27 +62,27 @@ public class TableA {
 	 */
 	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.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);
 			}
 		}
 	}
@@ -130,24 +92,24 @@ public class TableA {
 	 */
 	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.ferry()) {
+			assert ferryArcs.containsKey(rd):
+			"Trying to read Table A index for non-registered arc: " + count + " " + rd;
+			i = 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 = 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 = unpavedArcs.get(rd);
 		}
-		assert i < 0x100 : "Table A index too large: " + narc;
+		assert i < 0x100 : "Table A index too large: " + rd;
 		return (byte) i;
 	}
 
@@ -202,30 +164,30 @@ 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: 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/TableC.java b/src/uk/me/parabola/imgfmt/app/net/TableC.java
index c36257c..00d7f2b 100644
--- a/src/uk/me/parabola/imgfmt/app/net/TableC.java
+++ b/src/uk/me/parabola/imgfmt/app/net/TableC.java
@@ -41,25 +41,18 @@ 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.numUnpavedArcs() > 0)
+			writer.put((byte)tabA.numUnpavedArcs());
+		if(tabA.numFerryArcs() > 0)
+			writer.put((byte)tabA.numFerryArcs());
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
index 510c340..f7c0d7b 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
@@ -88,7 +88,7 @@ public class SRTFile extends ImgFile {
 	}
 
 	private void writeWeights(ImgFileWriter writer, int i) {
-		writer.put(sort.getPrimary(i));
+		writer.put((byte) sort.getPrimary(i));
 		writer.put((byte) ((sort.getTertiary(i) << 4) | (sort.getSecondary(i) & 0xf)));
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/srt/Sort.java b/src/uk/me/parabola/imgfmt/app/srt/Sort.java
index 7719b44..285868f 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 Integer NO_ORDER = 0;
 
 	private int codepage;
 	private int id1; // Unknown - identifies the sort
@@ -43,36 +60,88 @@ 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;
 
+	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)
+		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.primary[i] != 0) {
+						byte second = p.secondary[i];
+						maxSecondary = Math.max(maxSecondary, second);
+						if (second != 0) {
+							maxTertiary = Math.max(maxTertiary, p.tertiary[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.primary[i] == 0) {
+					if (p.secondary[i] == 0) {
+						if (p.tertiary[i] != 0) {
+							p.tertiary[i] += maxTertiary;
+						}
+					} else {
+						p.secondary[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,12 +168,11 @@ 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);
+			ByteBuffer out = encoder.encode(CharBuffer.wrap(s));
 			byte[] bval = out.array();
 
 			// In theory you could have a string where every character expands into maxExpSize separate characters
@@ -120,25 +188,80 @@ public class Sort {
 			} catch (ArrayIndexOutOfBoundsException e) {
 				// Ok try again with the max possible key size allocated.
 				key = new byte[bval.length * 3 * maxExpSize + 3];
+				fillCompleteKey(bval, 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);
 		}
 	}
 
+	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();
+		byte[] bval = new byte[encText.length];
+		for (int i = 0; i < encText.length; i++) {
+			assert (encText[i] & 0xff00) == 0;
+			bval[i] = (byte) encText[i];
+		}
+
+		// 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[(bval.length + 1 + 2) * 3];
+		try {
+			fillCompleteKey(bval, key);
+		} catch (ArrayIndexOutOfBoundsException e) {
+			// Ok try again with the max possible key size allocated.
+			key = new byte[bval.length * 3 * maxExpSize + 3];
+			fillCompleteKey(bval, 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.
 	 *
@@ -146,9 +269,9 @@ public class Sort {
 	 * @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);
+		int start = fillKey(Collator.PRIMARY, pages[0].primary, bval, key, 0);
+		start = fillKey(Collator.SECONDARY, pages[0].secondary, bval, key, start);
+		fillKey(Collator.TERTIARY, pages[0].tertiary, bval, key, start);
 	}
 
 	/**
@@ -165,17 +288,14 @@ public class Sort {
 		for (byte inb : input) {
 			int b = inb & 0xff;
 
-			int exp = (flags[b] >> 4) & 0x3;
+			int exp = (getFlags(b) >> 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;
 			} else {
 				// now have to redirect to a list of input chars, get the list via the primary value always.
-				byte idx = primary[b];
+				int idx = getPrimary(b);
 				//List<CodePosition> list = expansions.get(idx-1);
 
 				for (int i = idx - 1; i < idx + exp; i++) {
@@ -190,20 +310,20 @@ public class Sort {
 		return index;
 	}
 
-	public byte getPrimary(int ch) {
-		return primary[ch];
+	public int getPrimary(int ch) {
+		return this.pages[ch >>> 8].primary[ch & 0xff];
 	}
 
-	public byte getSecondary(int ch) {
-		return secondary[ch];
+	public int getSecondary(int ch) {
+		return this.pages[ch >>> 8].secondary[ch & 0xff];
 	}
 
-	public byte getTertiary(int ch) {
-		return tertiary[ch];
+	public int getTertiary(int ch) {
+		return this.pages[ch >>> 8].tertiary[ch & 0xff];
 	}
 
 	public byte getFlags(int ch) {
-		return flags[ch];
+		return this.pages[ch >>> 8].flags[ch & 0xff];
 	}
 
 	public int getCodepage() {
@@ -251,16 +371,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);
 	}
@@ -286,22 +398,24 @@ public class Sort {
 	 */
 	public void addExpansion(byte bval, int inFlags, List<Byte> expansionList) {
 		int idx = bval & 0xff;
-		flags[idx] = (byte) ((inFlags & 0xf) | (((expansionList.size()-1) << 4) & 0x30));
+		setFlags(idx, (byte) ((inFlags & 0xf) | (((expansionList.size()-1) << 4) & 0x30)));
 
 		// Check for repeated definitions
-		if (primary[idx] != 0)
+		if (getPrimary(idx) != 0)
 			throw new ExitException(String.format("repeated code point %x", idx));
 
-		primary[idx] = (byte) (expansions.size() + 1);
-		secondary[idx] = 0;
-		tertiary[idx] = 0;
+		setPrimary(idx, (expansions.size() + 1));
+		setSecondary(idx,  0);
+		setTertiary(idx, 0);
 		maxExpSize = Math.max(maxExpSize, expansionList.size());
 
 		for (Byte 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((byte) 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,29 +432,6 @@ public class Sort {
 		return new SrtCollator(codepage);
 	}
 
-	/**
-	 * 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.
-	 *
-	 * 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.
-	 */
-	public static Sort defaultSort(int codepage) {
-		Sort sort = new Sort();
-		for (int i = 1; i < 256; i++) {
-			sort.add(i, i, 0, 0, 0);
-		}
-		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();
 	}
@@ -349,11 +440,57 @@ public class Sort {
 		return String.format("sort cp=%d order=%08x", codepage, getSortOrderId());
 	}
 
+	private void setPrimary(int ch, int val) {
+		this.pages[ch >>> 8].primary[ch & 0xff] = (byte) val;
+	}
+
+	private void setSecondary(int ch, int val) {
+		this.pages[ch >>> 8].secondary[ch & 0xff] = (byte) val;
+	}
+
+	private void setTertiary(int ch, int val) {
+		this.pages[ch >>> 8].tertiary[ch & 0xff] = (byte) 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;
+	}
+
+	private static class Page {
+		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];
+	}
+
 	/**
 	 * A collator that works with this sort. This should be used if you just need to compare two
 	 * 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;
@@ -375,21 +512,15 @@ public class Sort {
 			}
 
 			int strength = getStrength();
-			int res = compareOneStrength(bytes1, bytes2, primary, Collator.PRIMARY);
+			int res = compareOneStrength(bytes1, bytes2, pages[0].primary, Collator.PRIMARY);
 
 			if (res == 0 && strength != PRIMARY) {
-				res = compareOneStrength(bytes1, bytes2, secondary, Collator.SECONDARY);
+				res = compareOneStrength(bytes1, bytes2, pages[0].secondary, Collator.SECONDARY);
 				if (res == 0 && strength != SECONDARY) {
-					res = compareOneStrength(bytes1, bytes2, tertiary, Collator.TERTIARY);
+					res = compareOneStrength(bytes1, bytes2, pages[0].tertiary, Collator.TERTIARY);
 				}
 			}
 
-			if (res == 0) {
-				if (source.length() < target.length())
-					res = -1;
-				else if (source.length() > target.length())
-					res = 1;
-			}
 			return res;
 		}
 
@@ -400,17 +531,16 @@ public class Sort {
 		 * @param typePositions The strength array to use in the comparison.
 		 * @return Comparison result -1, 0 or 1.
 		 */
-		@SuppressWarnings({"AssignmentToForLoopParameter"})
 		private int compareOneStrength(byte[] bytes1, byte[] bytes2, byte[] typePositions, int type) {
 			int res = 0;
 
 			PositionIterator it1 = new PositionIterator(bytes1, typePositions, type);
 			PositionIterator it2 = new PositionIterator(bytes2, typePositions, 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 +549,7 @@ public class Sort {
 					break;
 				}
 			}
+
 			return res;
 		}
 
@@ -463,32 +594,45 @@ 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
+						byte b = bytes[(pos++ & 0xff)];
+						next = sortPositions[b & 0xff] & 0xff;
+						int nExpand = (getFlags(b & 0xff) >> 4) & 0x3;
+
+						// Check if this is an expansion.
+						if (nExpand > 0) {
+							expStart = getPrimary(b & 0xff) - 1;
+							expEnd = expStart + nExpand;
+							expPos = expStart;
+							next = expansions.get(expPos).getPosition(type) & 0xff;
+
+							if (++expPos > expEnd)
+								expPos = 0;
+						}
+					} while (next == 0);
 				} else {
-					next = expansions.get(expPos).getPosition(type);
+					next = expansions.get(expPos).getPosition(type) & 0xff;
 					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/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/build/MapArea.java b/src/uk/me/parabola/mkgmap/build/MapArea.java
index a3588c4..5647200 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,6 +33,7 @@ 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;
 
@@ -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..8f810b1 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;
@@ -133,6 +134,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 +166,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)
@@ -921,7 +926,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 +977,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;
 				}
@@ -1067,6 +1072,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());
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..2792de8 100644
--- a/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/DouglasPeuckerFilter.java
@@ -160,8 +160,14 @@ public class DouglasPeuckerFilter implements MapFilter {
 
 			// Remove the endpoint if it is the same as the start point
 			if (ab == 0 && points.get(endIndex).preserved() == false)
-				points.remove(endIndex);
+				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/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..7cc28c5
--- /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.warn("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..928b7ed 100644
--- a/src/uk/me/parabola/mkgmap/general/LineClipper.java
+++ b/src/uk/me/parabola/mkgmap/general/LineClipper.java
@@ -21,6 +21,7 @@ import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
 
 /**
  * Routine to clip a polyline to a given bounding box.
@@ -28,6 +29,7 @@ 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 {
+	private static final Logger log = Logger.getLogger(LineClipper.class);
 
 	/**
 	 * Clips a polyline by the given bounding box.  This may produce several
@@ -78,7 +80,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 +89,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 +128,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 +142,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 +174,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 +194,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 +218,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/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index 8ff6839..2abf382 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -29,6 +29,9 @@ 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 +40,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 +101,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();
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/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
index 0580b44..8aa5c49 100644
--- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
+++ b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
@@ -132,44 +132,52 @@ public class RoadNetwork {
 				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());
+					log.warn("Road " + road.getRoadDef() + " contains zero length arc at " + co.toOSMURL());
 
 
-				Coord bearingPoint = coordList.get(lastIndex + 1);
-				if(lastCoord.equals(bearingPoint)) {
+				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))) {
-							bearingPoint = coordList.get(bi);
+							forwardBearingPoint = 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)) {
+				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))) {
-							bearingPoint = coordList.get(bi);
+							reverseBearingPoint = coordList.get(bi);
 							break;
 						}
 					}
 				}
-				int reverseBearing = (int)co.bearingTo(bearingPoint);
-				int inverseReverseBearing = (int)bearingPoint.bearingTo(co);
+				
+				double forwardInitialBearing = lastCoord.bearingTo(forwardBearingPoint);
+				double forwardDirectBearing = (co == forwardBearingPoint) ? forwardInitialBearing: lastCoord.bearingTo(co); 
+
+				double reverseInitialBearing = co.bearingTo(reverseBearingPoint);
+				double reverseDirectBearing = (lastCoord == reverseBearingPoint) ? reverseInitialBearing: co.bearingTo(lastCoord); 
 
+				// TODO: maybe detect cases where bearing was already calculated above 
+				double forwardFinalBearing = reverseBearingPoint.bearingTo(co); 
+				double reverseFinalBearing = forwardBearingPoint.bearingTo(lastCoord);
+
+				double directLength = (lastIndex + 1 == index) ? arcLength : lastCoord.distance(co);
 				// Create forward arc from node1 to node2
 				RouteArc arc = new RouteArc(road.getRoadDef(),
 											node1,
 											node2,
-											forwardBearing,
-											inverseReverseBearing,
+											forwardInitialBearing,
+											forwardFinalBearing,
+											forwardDirectBearing,
 											arcLength,
+											directLength,
 											outputCurveData,
 											pointsHash);
 				arc.setForward();
@@ -179,9 +187,11 @@ public class RoadNetwork {
 				// Create the reverse arc
 				arc = new RouteArc(road.getRoadDef(),
 								   node2, node1,
-								   reverseBearing,
-								   inverseForwardBearing,
+								   reverseInitialBearing,
+								   reverseFinalBearing,
+								   reverseDirectBearing,
 								   arcLength,
+								   directLength,
 								   outputCurveData,
 								   pointsHash);
 				node2.addArc(arc);
@@ -262,19 +272,36 @@ public class RoadNetwork {
 		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();
-
+		if (fn == null  || tn == null || vn == null){
+			if (fn == null)
+				log.error("can't locate 'from' RouteNode with id", fromNode.getId());
+			if (tn == null)
+				log.error("can't locate 'to' RouteNode with id", toNode.getId());
+			if (vn == null)
+				log.error("can't locate 'via' RouteNode with id", viaNode.getId());
+			return;
+		}
 		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";
-
+		if (fa == null || ta == null){
+			if (fa == null)
+				log.error("can't locate arc from 'via' node ",viaNode.getId(),"to 'from' node",fromNode.getId());
+			if (ta == null)
+				log.error("can't locate arc from 'via' node ",viaNode.getId(),"to 'to' node",toNode.getId());
+			return;
+		}
+		if(!ta.isForward() && ta.getRoadDef().isOneway()) {
+			// the route restriction connects to the "wrong" end of a oneway
+			if ((exceptMask & RouteRestriction.EXCEPT_FOOT) != 0){
+				// pedestrians are allowed
+				log.info("restriction via "+viaNode.getId() + " to " + fromNode.getId() + " ignored because to-arc is wrong direction in oneway ");
+				return;
+			} else {
+				log.info("restriction via "+viaNode.getId() + " to " + fromNode.getId() + " added although to-arc is wrong direction in oneway, but restriction also excludes pedestrians.");
+			}
+		}
 		vn.addRestriction(new RouteRestriction(fa, ta, exceptMask));
-    }
+	}
 
 	public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) {
 		RouteNode node = nodes.get(junctionNodeId);
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/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/osmstyle/RuleFileReader.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
index ee115b0..c257328 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
@@ -471,6 +471,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 +494,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 +502,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/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 653f810..bd91fe0 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -19,12 +19,14 @@ package uk.me.parabola.mkgmap.osmstyle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+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;
 
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
@@ -32,6 +34,7 @@ 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.NODHeader;
+import uk.me.parabola.imgfmt.app.net.RouteRestriction;
 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.MapObject;
 import uk.me.parabola.log.Logger;
@@ -85,7 +88,9 @@ public class StyledConverter implements OsmConverter {
 	// 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 Map<Node, List<Way>> poiRestrictions = new LinkedHashMap<Node, List<Way>>();
+	 
 	private final List<Relation> throughRouteRelations = new ArrayList<Relation>();
 
 	/** all tags used for access restrictions */
@@ -120,8 +125,6 @@ public class StyledConverter implements OsmConverter {
 	private HashMap<Long, Way> modifiedRoads = new HashMap<Long, Way>();
 	private HashSet<Long> deletedRoads = new HashSet<Long>();
 
-	private final double minimumArcLength;
-	
 	private int nextNodeId = 1;
 	
 	private HousenumberGenerator housenumberGenerator;
@@ -137,6 +140,7 @@ 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) {
@@ -172,16 +176,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();
 	}
 
 	/** One type result for ways to avoid recreating one for each way. */ 
@@ -225,14 +225,21 @@ public class StyledConverter implements OsmConverter {
 		preConvertRules(way);
 
 		housenumberGenerator.addWay(way);
-		
+		String styleFilterTag = way.getTag("mkgmap:stylefilter");
 		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;
+		}
 		
 		wayTypeResult.setWay(way);
 		rules.resolveType(way, wayTypeResult);
@@ -515,11 +522,18 @@ public class StyledConverter implements OsmConverter {
 	public void end() {
 		setHighwayCounts();
 		findUnconnectedRoads();
+		rotateClosedWaysToFirstNode();
 		filterCoordPOI();
-		removeShortArcsByMergingNodes();
+
+		wrongAngleFixer.setBounds(bbox);
+		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 (line == null){
+				continue; // should not happen
+			}
 			if (deletedRoads.contains(line.getId())){
 				lines.set(i, null);
 				continue;
@@ -565,6 +579,9 @@ public class StyledConverter implements OsmConverter {
 		
 		housenumberGenerator.generate(lineAdder);
 		
+		createRouteRestrictionsFromPOI();
+		poiRestrictions = null;
+		
 		Collection<List<RestrictionRelation>> lists = restrictions.values();
 		for (List<RestrictionRelation> l : lists) {
 
@@ -622,6 +639,225 @@ 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 (Way way: roads){
+			if (way == null)
+				continue;
+			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;
+			// first point connects only last point, remove last
+			for (int i = 1; i < points.size();i++){
+				Coord p = points.get(i);
+				if (p.getHighwayCount() > 1){
+					p.incHighwayCount(); // this will be the new first + last point
+					points.remove(points.size()-1);
+					Coord pNew;
+					if (p0 instanceof CoordPOI){
+						pNew = new CoordPOI(p0);
+						((CoordPOI) pNew).setNode(((CoordPOI) p0).getNode());
+					}
+					else
+						pNew = new Coord(p0);
+					pNew.incHighwayCount();
+					pNew.setOnBoundary(p0.getOnBoundary());
+					points.set(0, pNew);
+					Collections.rotate(points, -i);
+					points.add(points.get(0)); // close again
+					modifiedRoads.put(way.getId(), way); 
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check if roundabout has correct direction. Set driveOnRight or
+	 * driveOnLeft is not yet set.
+	 * 
+	 * @param way
+	 */
+	private void checkRoundabout(Way way) {
+		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);
+					// 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();
+			boolean[] nodeNoAccess = getNoAccess(node);
+			byte exceptMask = 0;
+			// exclude allowed vehicles/pedestrians from restriction
+			if (nodeNoAccess[RoadNetwork.NO_BIKE] == false)
+				exceptMask |= RouteRestriction.EXCEPT_BICYCLE;
+			if (nodeNoAccess[RoadNetwork.NO_BUS] == false)
+				exceptMask |= RouteRestriction.EXCEPT_BUS;
+			if (nodeNoAccess[RoadNetwork.NO_CAR] == false)
+				exceptMask |= RouteRestriction.EXCEPT_CAR;
+			if (nodeNoAccess[RoadNetwork.NO_DELIVERY] == false)
+				exceptMask |= RouteRestriction.EXCEPT_DELIVERY;
+			if (nodeNoAccess[RoadNetwork.NO_TAXI] == false)
+				exceptMask |= RouteRestriction.EXCEPT_TAXI;
+			if (nodeNoAccess[RoadNetwork.NO_TRUCK] == false)
+				exceptMask |= RouteRestriction.EXCEPT_TRUCK;
+			if (nodeNoAccess[RoadNetwork.NO_FOOT] == false)
+				exceptMask |= RouteRestriction.EXCEPT_FOOT;
+			if (nodeNoAccess[RoadNetwork.NO_EMERGENCY] == false)
+				exceptMask |= RouteRestriction.EXCEPT_EMERGENCY;
+
+			Map<Long,CoordNode> otherNodeIds = new LinkedHashMap<Long, CoordNode>();
+			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;
+			}
+			List<CoordNode> otherNodesUniqe = new ArrayList<CoordNode>(otherNodeIds.values());
+			for (int i = 0; i < otherNodesUniqe.size(); i++){
+				CoordNode from = (CoordNode) otherNodesUniqe.get(i);
+				for (int j = i+1; j < otherNodesUniqe.size(); j++){
+					CoordNode to = (CoordNode) otherNodesUniqe.get(j);
+					if (to.getId() != viaNode.getId() && from.getId() != viaNode.getId()){
+						collector.addRestriction(from, to, viaNode, exceptMask);
+						collector.addRestriction(to, from, viaNode, exceptMask);
+						log.info("created route restriction from poi for node " + node.toBrowseURL());
+					}
+				}
+			}
+		}
+	}
+
+ 	
+	/**
 	 * 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=".."'.
@@ -641,11 +877,13 @@ public class StyledConverter implements OsmConverter {
 		
 		if(relation instanceof RestrictionRelation) {
 			RestrictionRelation rr = (RestrictionRelation)relation;
-			if(rr.isValid()) {
+			if(rr.isValid() && bbox.contains(rr.getViaCoord())) {
 				List<RestrictionRelation> lrr = restrictions.get(rr.getViaCoord());
 				if(lrr == null) {
 					lrr = new ArrayList<RestrictionRelation>();
-					restrictions.put(rr.getViaCoord(), lrr);
+					Coord via = rr.getViaCoord();
+					via.setViaNodeOfRestriction(true);
+					restrictions.put(via, lrr);
 				}
 				lrr.add(rr);
 			}
@@ -661,8 +899,9 @@ public class StyledConverter implements OsmConverter {
 		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);
@@ -701,12 +940,12 @@ 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"))
@@ -816,6 +1055,19 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
+	private boolean[] getNoAccess(Element osmElement){
+		boolean[] noAccess = new boolean[RoadNetwork.NO_MAX];
+		noAccess[RoadNetwork.NO_EMERGENCY] = osmElement.isNotBoolTag("mkgmap:emergency");
+		noAccess[RoadNetwork.NO_DELIVERY] = osmElement.isNotBoolTag("mkgmap:delivery");
+		noAccess[RoadNetwork.NO_CAR] = osmElement.isNotBoolTag("mkgmap:car");
+		noAccess[RoadNetwork.NO_BUS] = osmElement.isNotBoolTag("mkgmap:bus");
+		noAccess[RoadNetwork.NO_TAXI] = osmElement.isNotBoolTag("mkgmap:taxi");
+		noAccess[RoadNetwork.NO_FOOT] = osmElement.isNotBoolTag("mkgmap:foot");
+		noAccess[RoadNetwork.NO_BIKE] = osmElement.isNotBoolTag("mkgmap:bicycle");
+		noAccess[RoadNetwork.NO_TRUCK] = osmElement.isNotBoolTag("mkgmap:truck");
+		return noAccess;
+	}
+	 	
 	private boolean hasAccessRestriction(Element osmElement) {
 		for (String tag : ACCESS_TAGS) {
 			if (osmElement.isNotBoolTag(tag)) {
@@ -838,82 +1090,20 @@ public class StyledConverter implements OsmConverter {
 			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(way);
+		addNodesForRestrictions(way);
 
 		// 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,16 +1111,44 @@ 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() &&
+									safeToSplitWay(points, splitPos, i, points.size() - 1)) {
+								Way tail = splitWayAt(way, splitPos);
 								// recursively process tail of way
 								addRoad(tail, gt);
 							}
@@ -946,143 +1164,54 @@ public class StyledConverter implements OsmConverter {
 					}
 				}
 
-				// 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);
-							}
-						}
 					}
 				}
 			}
-		}
 
+		} 
 		// if there is a bounding box, clip the way with it
 
 		List<Way> clippedWays = null;
@@ -1144,8 +1273,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,14 +1287,19 @@ 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");
 							--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());
 							do {
@@ -1204,6 +1343,83 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
+	
+	/**
+	 * Detect the case that two nodes are connected with 
+	 * different road segments and at least one node
+	 * is the via node of an "only-xxx" restriction.
+	 * If that is the case, make sure that there is
+	 * a node between them. If not, add it or change 
+	 * an existing point to a node by incrementing the highway
+	 * count.
+	 * Without this trick, only-restrictions might be ignored.
+	 * 
+	 * @param way the road
+	 */
+	private void addNodesForRestrictions(Way way) {
+		List<Coord> points = way.getPoints();
+		// loop downwards as we may add points
+		Coord lastNode = null;
+		int lastNodePos = -1;
+		for (int i = points.size() - 1; i >= 0; i--) {
+			Coord p1 = points.get(i);
+			if (p1.getHighwayCount() > 1){
+				if (lastNode != null){
+					if (lastNode.isViaNodeOfRestriction() && p1.isViaNodeOfRestriction()
+							|| (i == 0 && lastNodePos == points.size()-1 && (lastNode.isViaNodeOfRestriction() || p1.isViaNodeOfRestriction()))){
+						boolean addNode = false;
+						Coord[] testCoords = { p1, lastNode };
+						for (int k = 0; k < 2; k++){
+							if (testCoords[k].isViaNodeOfRestriction() == false)
+								continue;
+							List<RestrictionRelation> lrr = restrictions.get(testCoords[k]);
+							Coord test = k==0 ? testCoords[1]:testCoords[0];
+							for (RestrictionRelation rr : lrr) {
+								if (rr.isOnlyXXXRestriction() == false || rr.getFromWay() == way || rr.getToWay() == way)
+									continue;
+								// check if our way shares a point with either the to or from way
+								// we have to use Coord.equals() here because some
+								// points may already be CoordNodes while others are not
+								for (Coord co: rr.getToWay().getPoints()){
+									if (co.equals(test)){
+										addNode = true;
+										break;
+									}
+								}
+								for (Coord co: rr.getFromWay().getPoints()){
+									if (co.equals(test)){
+										addNode = true;
+										break;
+									}
+								}
+							}
+						}
+						if (addNode == false)
+							continue;
+						Coord co = null;
+						if (lastNodePos-i == 1){
+							if (p1.distance(lastNode) > 0){
+								// create new point
+								co = lastNode.makeBetweenPoint(p1, 0.5);
+								co.incHighwayCount();
+								if (log.isInfoEnabled())
+									log.info("adding node in way", way.toBrowseURL(), "to have node between two via points at", co.toOSMURL());
+								points.add(lastNodePos,co);
+							}
+						} else {
+							co = points.get(i+1);
+							if (log.isInfoEnabled())
+								log.info("changing point to node in way", way.toBrowseURL(), "to have node between two via points at", co.toOSMURL());
+						}
+						if (co != null)
+							co.incHighwayCount();
+					}
+				}
+				lastNode = p1;
+				lastNodePos = i;
+			}
+		}
+	} 	
 	/**
 	 * safeToSplitWay() returns true if it is safe (no short arcs will be
 	 * created) to split a way at a given position - assumes that the
@@ -1222,12 +1438,9 @@ public class StyledConverter implements OsmConverter {
 			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 +1450,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,7 +1465,7 @@ public class StyledConverter implements OsmConverter {
 			}
 		}
 
-		return (arcLength != 0 && arcLength >= minimumArcLength);
+		return true;
 	}
 
 	private static String getDebugName(Element el) {
@@ -1372,10 +1579,30 @@ public class StyledConverter implements OsmConverter {
 				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() + "]")){
+							boolean[] nodeNoAccess = getNoAccess(cp.getNode());
+							boolean[] wayNoAccess = getNoAccess(way);
+							if (Arrays.equals(nodeNoAccess, wayNoAccess) == false){
+								List<Way> wayList = poiRestrictions.get(cp.getNode());
+								if (wayList == null){
+									wayList = new ArrayList<Way>(4);
+									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);
@@ -1435,17 +1662,8 @@ public class StyledConverter implements OsmConverter {
 			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(getNoAccess(way));
+		
 		// does the road have a carpool lane?
 		if (way.isBoolTag("mkgmap:carpool"))
 			road.setCarpoolLane();
@@ -1504,7 +1722,11 @@ public class StyledConverter implements OsmConverter {
 					}
 				}
 
-				List<RestrictionRelation> theseRestrictions = restrictions.get(coord);
+				List<RestrictionRelation> theseRestrictions;
+				if (coord.isViaNodeOfRestriction())
+					theseRestrictions = restrictions.get(coord);
+				else
+					theseRestrictions = null;
 				if(theseRestrictions != null) {
 					// this node is the location of one or more
 					// restrictions
@@ -1855,9 +2077,7 @@ public class StyledConverter implements OsmConverter {
 				continue;
 			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++) {
@@ -1865,12 +2085,35 @@ public class StyledConverter implements OsmConverter {
 						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);
+						boolean usedInThisWay = false;
+						if (node.getTag("mkgmap:road-class") != null
+								|| node.getTag("mkgmap:road-speed") != null ) {
+							if (isFootOnlyAccess(way) == false)
+								usedInThisWay = true;
+						}
+						if(hasAccessRestriction(node)){
+							// barriers etc. 
+							boolean nodeIsMoreRestrictive = false;
+							for (String tag : ACCESS_TAGS) {
+								if (node.isNotBoolTag(tag) && way.isNotBoolTag(tag) == false) {
+									nodeIsMoreRestrictive = true;
+									break;
+								}
 							}
+							if (nodeIsMoreRestrictive){
+								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()+"]";
 						}
 					}
 				} 
@@ -1884,274 +2127,6 @@ 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/WrongAngleFixer.java b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
new file mode 100644
index 0000000..91bca9f
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
@@ -0,0 +1,1340 @@
+/*
+ * Copyright (C) 2013.
+ *
+ * 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.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_DIFF_ANGLE_STRAIGHT_LINE = 3;
+	
+	private Area bbox;
+	private String gpxPath;
+	static final int MODE_ROADS = 0;
+	static final int MODE_LINES = 1;
+	private int mode = MODE_ROADS;
+	
+	public void setBounds(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<Way> roads, List<Way> lines, HashMap<Long, Way> modifiedRoads, HashSet<Long> deletedRoads, Map<Coord, 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) toRepl).isUsed()) {
+			replacement = new CoordPOI(replacement);
+			((CoordPOI) replacement).setNode(((CoordPOI) toRepl).getNode());
+		}
+		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 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;
+			} else {
+				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<Way> roads, List<Way> lines, HashMap<Long, Way> modifiedRoads, HashSet<Long> deletedRoads, Map<Coord, List<RestrictionRelation>> restrictions) {
+		// replacements maps those nodes that have been replaced to
+		// the node that replaces them
+		Map<Coord, Coord> replacements = new IdentityHashMap<Coord, Coord>();
+
+		final HashSet<Coord> changedPlaces = new HashSet<Coord>();
+		int numNodesMerged = 0; 
+		HashSet<Way> waysWithBearingErrors = new HashSet<Way>();
+		HashSet<Long> waysThatMapToOnePoint = new HashSet<>();
+		int pass = 0;
+		Way lastWay = null;
+		List<Way> ways = (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), ways);	
+			// Step 1: detect points which are parts of line segments with wrong bearings
+			lastWay = null;
+			for (int w = 0; w < ways.size(); w++) {
+				Way way = ways.get(w);
+				if (way == null || way.equals(lastWay)) 
+					continue;
+				if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+					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<Coord, CenterOfAngle>();
+			List<CenterOfAngle> centers = new ArrayList<CenterOfAngle>(); // needed for ordered processing
+			int centerId = 0;
+				
+			lastWay = null;
+			for (int w = 0; w < ways.size(); w++) {
+				Way way = ways.get(w);
+				if (way == null || way.equals(lastWay)) 
+					continue;
+				if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+					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(), way);
+							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 (mode == MODE_ROADS){
+								if (p1.getHighwayCount() >= 2 && p2.getHighwayCount() >= 2){
+									if (points.size() == 2) {
+										// way has only two points, don't merge them
+										coa1.addBadMergeCandidate(coa2);
+									}
+									if ("roundabout".equals(way.getTag("junction"))) {
+										// avoid to merge exits of roundabouts
+										coa1.addBadMergeCandidate(coa2);
+									}
+								}
+							} else {
+								if (points.size() == 2) {
+									// way has only two points, don't merge them
+									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 (int w = 0; w < ways.size(); w++) {
+				Way way = ways.get(w);
+				if (way == null || way.equals(lastWay)) 
+					continue;
+				if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+					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<CenterOfAngle>();
+				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;
+			for (int w = 0; w < ways.size(); w++){
+				Way way = ways.get(w);
+				if (way == null)
+					continue;
+				if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+					continue;
+				if (waysWithBearingErrors.contains(way) == false)
+					continue;
+				List<Coord> points = way.getPoints();
+				if (way.equals(lastWay)) {
+					if (lastWayModified){
+						points.clear();
+						points.addAll(lastWay.getPoints());
+					}
+					continue;
+				}
+				lastWay = way;
+				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 (mode == MODE_ROADS)
+							modifiedRoads.put(way.getId(), way);
+						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);
+						List<RestrictionRelation> lrr = restrictions.remove(p);
+						if (lrr != null)
+							restrictions.put(replacement,lrr);
+						for (RestrictionRelation rr: lrr)
+							rr.setViaCoord(replacement);
+						p.setViaNodeOfRestriction(false);
+					}
+					p = replacement;
+					// replace point in way
+					points.set(i, p);
+					if (p.getHighwayCount() >= 2)
+						numNodesMerged++;
+					lastWayModified = true;
+					if (mode == MODE_ROADS)
+						modifiedRoads.put(way.getId(), way);
+					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;
+					}
+				}
+			}
+		}
+		// finish: remove remaining duplicate points
+		int numWaysDeleted = 0;
+		lastWay = null;
+		boolean lastWayModified = false;
+		for (int w = 0; w < ways.size(); w++){
+			Way way = ways.get(w);
+			if (way == null)
+				continue;
+			if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+				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");
+				if (mode == MODE_LINES && waysThatMapToOnePoint.contains(way.getId()) == false)
+					log.warn("non-routable way " ,way.getId(),"was removed");
+				
+				ways.set(w, null);
+				if (mode == MODE_ROADS)
+					deletedRoads.add(way.getId());
+				++numWaysDeleted;
+				continue;
+			} 								
+			if (way.equals(lastWay)) {
+				if (lastWayModified){
+					points.clear();
+					points.addAll(lastWay.getPoints());
+				}
+				continue;
+			}
+			lastWay = way;
+			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 (Way way : lines) {
+				if (way == null)
+					continue;
+				if (modifiedRoads.containsKey(way.getId())){ 
+					// overlay line is handled later
+					continue;
+				}
+				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);
+				}
+			}
+		}
+		if (gpxPath != null) {
+			GpxCreator.createGpx(gpxPath + "solved_badAngles", bbox.toCoords(),
+					new ArrayList<Coord>(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 roads. 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 ways 
+	 * @param modifiedRoads 
+	 */
+	private void removeObsoletePoints(List<Way> ways, HashMap<Long, Way> modifiedRoads){
+		Way lastWay = null;
+		int numPointsRemoved = 0;
+		boolean lastWasModified = false;
+		List<Coord> removedInWay = new ArrayList<Coord>();
+		List<Coord> obsoletePoints = new ArrayList<Coord>();
+		List<Coord> modifiedPoints = new ArrayList<Coord>();
+		for (int w = 0; w < ways.size(); w++) {
+			Way way = ways.get(w);
+			if (way == null)
+				continue;
+			if (mode == MODE_LINES && modifiedRoads.containsKey(way.getId()))
+				continue;
+			if (way.equals(lastWay)) {
+				if (lastWasModified){
+					way.getPoints().clear();
+					way.getPoints().addAll(lastWay.getPoints());
+				}
+				continue;
+			}
+			lastWay = way;
+			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 = distToLineHeron(cm, c1, c2);
+					if (distance >= maxErrorDistance){
+						modifiedPoints.add(cm);
+						continue;
+					}
+					keepThis = false;
+				} else {
+					double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
+					if (Math.signum(displayedAngle) != Math.signum(realAngle)){
+						// 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;
+						}
+					}
+				}
+				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(), way);
+				if (gpxPath != null){
+					if (draw || "roundabout".equals(way.getTag("junction"))) {
+						GpxCreator.createGpx(gpxPath+way.getId()+"_dpmod", points,removedInWay);
+					}
+				}
+			}
+		}
+		if (gpxPath != null){
+			GpxCreator.createGpx(gpxPath + "obsolete", bbox.toCoords(),
+					new ArrayList<Coord>(obsoletePoints));
+			
+		}
+		log.info("Removed", numPointsRemoved, "obsolete points in roads"); 
+	}
+	
+	/** 
+	 * debug code
+	 * @param roads 
+	 */
+	private void printBadAngles(String name, List<Way> roads){
+		if (gpxPath ==  null)
+			return;
+		List<Way> badWays = new ArrayList<Way>();
+		Way lastWay = null;
+		List<Coord> badAngles = new ArrayList<Coord>();
+		for (int w = 0; w < roads.size(); w++) {
+			Way way = roads.get(w);
+			if (way == null)
+				continue;
+			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(way);
+		}
+		GpxCreator.createGpx(gpxPath + name, bbox.toCoords(),
+				new ArrayList<Coord>(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 (mode == MODE_LINES){
+			return p.isEndOfWay() == false;
+		}
+		if (p instanceof CoordPOI){
+			if (((CoordPOI) p).isUsed()){
+				return false;
+			}
+		}
+		if (p.getHighwayCount() >= 2 || p.getOnBoundary() || 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<CenterOfAngle>(4);
+		}
+
+		
+		@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<CenterOfAngle>(4);
+			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");
+					}
+					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
+				err = calcMaxError(replacements, currentCenter, altCenter);
+				if (err >= 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/2 && removeErr < MAX_BEARING_ERROR/2){
+					bestCenterReplacement = null;
+//					createGPX(gpxPath+id+"_rem_pref", replacements);
+				} else if (initialMaxError - bestReplErr < MAX_BEARING_ERROR/2 || bestReplErr > MAX_BEARING_ERROR/2){
+					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);
+			if (dist <= 1 || currentCenter.equals(worstNP) || (this.neighbours.size() == 3 && worstNeighbour.neighbours.size() == 3))
+				return tryMerge(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 initialMaxError max. bearing error of this centre
+		 * @param neighbour neighbour to merge 
+		 * @param replacements
+		 * @return true if merge is okay
+		 */
+		private boolean tryMerge(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);
+			if (c.getOnBoundary() && n.getOnBoundary() && c.equals(n) == false)
+				return false;
+			 if (c.isViaNodeOfRestriction() && n.isViaNodeOfRestriction())
+				 return false;
+			Coord mergePoint;
+			if (c.getOnBoundary())
+				mergePoint = c;
+			else if (n.getOnBoundary())
+				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){
+					System.out.println("still equal neighbour after merge");
+				} else { 
+					if (err >= MAX_BEARING_ERROR)
+						return false;
+					if (initialMaxError - err < MAX_BEARING_ERROR/2 && err > MAX_BEARING_ERROR/2){
+						return false; // improvement too small
+					}
+				}
+			}
+			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;
+		}
+
+
+		/**
+		 * 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<Way> ways){
+		//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<String, byte[]>();
+		for (int pass = 1; pass <= 2; pass ++){
+			IdentityHashMap<Coord, Integer> allPoints = new IdentityHashMap<Coord, Integer>();
+			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 (Way way: ways){
+					if (way == null)
+						continue;
+					for (Coord p: way.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().getEntryIteratable()) {
+									nodeOut.addTag(tagEntry.getKey(), tagEntry.getValue());
+								}
+							}
+							writer.write(nodeOut);
+						}
+					}
+				}
+				for (int w = 0; w < ways.size(); w++){
+					Way way = ways.get(w);
+					if (way == null)
+						continue;
+					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.getEntryIteratable()) {
+						wayOut.addTag(tagEntry.getKey(), tagEntry.getValue());
+					}
+					
+					if ("roundabout".equals(way.getTag("junction"))) {
+						wayOut.addTag("junction", "roundabout");
+					}
+					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 distance of point in the middle to line c1,c2 using herons formula
+	 * @param cm point in the middle
+	 * @param c1 
+	 * @param c2
+	 * @return distance in meter
+	 */
+	private static double distToLineHeron(Coord cm, Coord c1, Coord c2){
+		double ab = c1.distance(c2);
+		double ap = cm.distance(c1);
+		double bp = cm.distance(c2);
+		double abpa = (ab+ap+bp)/2;
+		double distance = 2 * Math.sqrt(abpa * (abpa-ab) * (abpa-ap) * (abpa-bp)) / ab;
+		return distance;
+		
+	}
+
+	private static double calcMaxErrorDistance(Coord p0){
+		Coord test = new Coord(p0.getLatitude(),p0.getLongitude()+1);
+		double lonErr = p0.getDisplayedCoord().distance(test) / 2;
+		test = new Coord(p0.getLatitude()+1,p0.getLongitude());
+		double latErr = p0.getDisplayedCoord().distance(test) / 2;
+		return Math.min(latErr, 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<Coord>(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 = distToLineHeron(cm, 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/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/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..86ac5aa 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
@@ -298,36 +298,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++;
 			}
 			
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/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/ElementSaver.java b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
index fedca8e..2b83163 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
@@ -314,7 +314,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/HighwayHooks.java b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
index 374a0a2..fd12585 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
@@ -121,7 +121,7 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 					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());
+						CoordPOI cp = new CoordPOI(co);
 						saver.addPoint(id, cp);
 
 						// we also have to jump through hoops to
@@ -258,6 +258,8 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 				String ref = null;
 				Way motorway = null;
 				for (Way w : motorways) {
+					// XXX: this test might fail if the exit point was removed or changed in StyledConverter
+					// as it 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..d5088a8 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,19 @@ 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;
+				if (rrel.isValid()==false)
+					// ignore invalid restrictions
+					continue;
+				restrictions.add(rrel.getFromWay().getId(), rrel);
+				restrictions.add(rrel.getToWay().getId(), rrel);
+			}
+		}
 	}
 	
 	
@@ -189,6 +206,37 @@ 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;
+		}
+		// create a copy because original list may be modified within the loop
+		for (RestrictionRelation rr : new ArrayList<>(wayRestrictions)) {
+			Coord lastPointNewWay = newWay.getPoints().get(0);
+			if (rr.getViaCoord() == lastPointNewWay) {
+				if (rr.getToWay().equals(oldWay)) {
+					log.debug("Change to-way",oldWay.getId(),"to",newWay.getId(),"for relation",rr.getId(),"at",lastPointNewWay.toOSMURL());
+					rr.setToWay(newWay);
+					restrictions.remove(oldWay.getId(), rr);
+					restrictions.add(newWay.getId(), rr);
+				} else if (rr.getFromWay().equals(oldWay)) {
+					log.debug("Change from-way",oldWay.getId(),"to",newWay.getId(),"for relation",rr.getId(),"at",lastPointNewWay.toOSMURL());
+					rr.setFromWay(newWay);
+					restrictions.remove(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 +267,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 +331,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 +358,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;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
index ee40605..777403b 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,6 +30,7 @@ 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.LinkedList;
 import java.util.List;
@@ -39,6 +44,7 @@ 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) {
@@ -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);
 		}
@@ -1747,7 +1783,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @return true if polygon1 contains polygon2
 	 */
 	private boolean contains(JoinedWay polygon1, JoinedWay polygon2) {
-		if (!polygon1.isClosed()) {
+		if (!polygon1.hasIdenticalEndPoints()) {
 			return false;
 		}
 		// check if the bounds of polygon2 are completely inside/enclosed the bounds
@@ -1756,7 +1792,7 @@ public class MultiPolygonRelation extends Relation {
 			return false;
 		}
 
-		Polygon p = Java2DConverter.createPolygon(polygon1.getPoints());
+		Polygon highPrecPolygon = Java2DConverter.createHighPrecPolygon(polygon1.getPoints());
 		// check first if one point of polygon2 is in polygon1
 
 		// ignore intersections outside the bounding box
@@ -1765,7 +1801,7 @@ public class MultiPolygonRelation extends Relation {
 		boolean onePointContained = false;
 		boolean allOnLine = true;
 		for (Coord px : polygon2.getPoints()) {
-			if (p.contains(px.getLongitude(), px.getLatitude())) {
+			if (highPrecPolygon.contains(px.getHighPrecLon(), px.getHighPrecLat())){
 				// there's one point that is in polygon1 and in the bounding
 				// box => polygon1 may contain polygon2
 				onePointContained = true;
@@ -1793,16 +1829,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 (highPrecPolygon.contains(px.getHighPrecLon(), px.getHighPrecLat())){
 					// there's one point that is in polygon1 and in the bounding
 					// box => polygon1 may contain polygon2
 					onePointContained = true;
@@ -1935,7 +1969,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 +1978,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 +2136,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 {
@@ -2268,20 +2298,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);
 	}
 
 
@@ -2520,20 +2551,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 +2574,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 +2654,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 +2733,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 +2799,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 +2809,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 +2833,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 +2891,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/POIGeneratorHook.java b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
index 96f615e..45093ce 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;
@@ -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
@@ -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++;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
index 00fdaa1..619559b 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
@@ -14,9 +14,9 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.HashSet;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
@@ -141,6 +141,7 @@ public class RestrictionRelation extends Relation {
 			}
 		}
 
+		exceptMask = RouteRestriction.EXCEPT_FOOT | RouteRestriction.EXCEPT_EMERGENCY; 
 		String except = getTag("except");
 		if(except != null) {
 			for(String e : except.split("[,;]")) { // be nice
@@ -167,10 +168,18 @@ public class RestrictionRelation extends Relation {
 		return fromWay;
 	}
 
+	public void setFromWay(Way fromWay) {
+		this.fromWay = fromWay;
+	}
+
 	public Way getToWay() {
 		return toWay;
 	}
 
+	public void setToWay(Way toWay) {
+		this.toWay = toWay;
+	}
+
 	public Coord getViaCoord() {
 		return viaCoord;
 	}
@@ -194,6 +203,12 @@ public class RestrictionRelation extends Relation {
 		this.viaNode = viaNode;
 	}
 
+	public void setViaCoord(Coord viaCoord) {
+		log.warn(messagePrefix + restriction + " 'via' coord redefined from " +
+				 this.viaCoord.toOSMURL() + " to " + viaCoord.toOSMURL());
+		this.viaCoord = viaCoord;
+	}
+
 	public void addOtherNode(CoordNode otherNode) {
 		otherNodes.add(otherNode);
 		log.debug(messagePrefix + restriction + " adding 'other' node " + otherNode.toOSMURL());
@@ -223,7 +238,7 @@ public class RestrictionRelation extends Relation {
 			List<Coord>toPoints = toWay.getPoints();
 			for(Coord fp : fromPoints) {
 				for(Coord tp : toPoints) {
-					if(fp.equals(tp)) {
+					if(fp == tp) {
 						if(viaCoord == null) {
 							viaCoord = fp;
 						}
@@ -253,16 +268,14 @@ public class RestrictionRelation extends Relation {
 
 		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)) {
+		if(e1 != v1 && e2 != v1 && e1 != v2 && e2 != v2) {
 			log.warn(messagePrefix + "'from' way " + fromWay.toBrowseURL() + " doesn't start or end at 'via' node or way");
 			result = 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)) {
+		if(e1 != v1 && e2 != v1 && e1 != v2 && e2 != v2) {
 			log.warn(messagePrefix + "'to' way " + toWay.toBrowseURL() + " doesn't start or end at 'via' node or way");
 			result = false;
 		}
@@ -271,14 +284,19 @@ public class RestrictionRelation extends Relation {
 			log.warn(messagePrefix + "sorry, 'via' ways are not supported - ignoring restriction");
 			result = false;
 		}
-
 		return result;
 	}
 
 	public void addRestriction(MapCollector collector) {
-
+		if ("no_u_turn".equals(restriction)){
+			if (toNode != null && fromNode == null)
+				fromNode = toNode;
+			else if (fromNode != null && toNode == null)
+				toNode = fromNode;
+		}
 		if(restriction == null || viaNode == null || fromNode == null || toNode == null) {
-			// restriction must have some error (reported earlier)
+			if (viaCoord != null)
+				log.warn("can't add restriction relation", this.getId(), "type", restriction);
 			return;
 		}
 
@@ -300,12 +318,12 @@ public class RestrictionRelation extends Relation {
 			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);	
+			HashSet<CoordNode> otherNodesUnique = new HashSet<CoordNode>(otherNodes);
 			for(CoordNode otherNode : otherNodesUnique) {
-				if (!otherNode.equals(fromNode) && !otherNode.equals(toNode)) {
+				if (otherNode.getId() != fromNode.getId() && otherNode.getId() != toNode.getId()) {
 					log.info(messagePrefix + restriction + "  blocks routing to node " + otherNode.toOSMURL());
 					collector.addRestriction(fromNode, otherNode, viaNode, exceptMask);
-				}
+				} 
 			}
 		}
 		else {
@@ -322,4 +340,8 @@ public class RestrictionRelation extends Relation {
 	public String toString() {
 		return "[restriction = " + restriction + ", from = " + fromWay.toBrowseURL() + ", to = " + toWay.toBrowseURL() + ", via = " + viaCoord.toOSMURL() + "]";
 	}
+	
+	public boolean isOnlyXXXRestriction(){
+		return restriction.startsWith("only");
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
index 5afce3a..aff85ef 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
@@ -22,8 +22,8 @@ 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;
@@ -158,20 +158,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 +413,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 +435,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 +451,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 +706,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 +780,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 +798,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 +825,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 +849,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 +862,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 +891,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 +978,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 +994,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 +1058,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 +1070,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 +1134,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;
@@ -1275,6 +1277,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 +1429,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 +1438,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/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index a6c5a25..716913b 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -37,8 +37,8 @@ public class Way extends Element {
 	// 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.
@@ -57,7 +57,7 @@ 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;
 		return dup;
 	}
@@ -95,13 +95,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 +178,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 +190,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() {
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..6aec6c7 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
@@ -29,7 +29,7 @@ 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()));
+			java.awt.geom.Area boundArea = Java2DConverter.createArea(way.getPoints());
 			Boundary boundary = new Boundary(boundArea, way.getEntryIteratable(), "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..2ddd65f 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
@@ -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"
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/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..8c7eae6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
@@ -668,7 +668,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 +688,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);
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..8f93151 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;
@@ -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..1a131fd 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
@@ -669,36 +669,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/polish/PolishMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
index 1ce7b8c..f3c9103 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
@@ -16,6 +16,8 @@
  */
 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;
@@ -97,7 +99,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 +149,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		}
 
 		addBackground(havePolygon4B);
+		coordMap = null;
 	}
 
 	public LevelInfo[] mapLevels() {
@@ -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());
@@ -677,7 +685,13 @@ 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() {
@@ -824,7 +838,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
                 if ("1".equals(params[i])) {
                     switch(i) {
 					case 0:
-						// Mask is not known for Emergency.
+						exceptMask |= RouteRestriction.EXCEPT_EMERGENCY;
 						break;
 					case 1:
 						exceptMask |= RouteRestriction.EXCEPT_DELIVERY;
@@ -839,7 +853,7 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 						exceptMask |= RouteRestriction.EXCEPT_TAXI;
 						break;
 					case 5:
-						// Mask is not known for Pedestrian.
+						exceptMask |= RouteRestriction.EXCEPT_FOOT;
 						break;
 					case 6:
 						exceptMask |= RouteRestriction.EXCEPT_BICYCLE;
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
index 4a2b75b..cbf6ef9 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/RoadHelper.java
@@ -136,7 +136,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);
diff --git a/src/uk/me/parabola/mkgmap/scan/TokenScanner.java b/src/uk/me/parabola/mkgmap/scan/TokenScanner.java
index 99bde8b..b6c0298 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
@@ -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,10 +178,13 @@ public class TokenScanner {
 		val.append((char) c);
 
 		TokType tt;
-		if (c == '\n') {
+		if (c == '\n' || c == '\r') {
+			while ((c = readChar()) == '\n' || c == '\r')
+				val.append(c);
+			pushback = c;
 			tt = TokType.EOL;
 		} else if (isSpace(c)) {
-			while (isSpace(c = readChar()) && c != '\n')
+			while (isSpace(c = readChar()) && (c != '\n' && c != '\r'))
 				val.append((char) c);
 
 			pushback = c;
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..8dfb28d 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,10 +111,9 @@ 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());
@@ -124,13 +123,14 @@ class PrecompSeaSaver implements Runnable {
 						}
 						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..10dacf8 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,15 +67,11 @@ 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 {
@@ -84,13 +81,10 @@ public class SrtTextReader {
 	private static final int IN_CODE = 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("#")) {
@@ -162,6 +163,8 @@ public class SrtTextReader {
 				break;
 			}
 		}
+
+		sort.finish();
 	}
 
 	/**
@@ -174,28 +177,33 @@ 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 "code":  // The old name; use characters
+			case "characters":
+				if (encoder == null)
 					throw new SyntaxException(scanner, "Missing codepage declaration before code");
 				state = IN_CODE;
 				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);
 			}
 		}
@@ -211,11 +219,13 @@ public class SrtTextReader {
 		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":
 				scanner.validateNext("=");
 				try {
 					int newPos = Integer.decode(scanner.nextWord());
@@ -225,62 +235,77 @@ public class SrtTextReader {
 				} catch (NumberFormatException e) {
 					throw new SyntaxException(scanner, "invalid integer for position");
 				}
-			} else if (val.equals("pos2")) {
+				break;
+			case "pos2":
 				scanner.validateNext("=");
 				pos2 = Integer.decode(scanner.nextWord());
-			} else if (val.equals("pos3")) {
+				break;
+			case "pos3":
 				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<Byte> 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();
-			expansionList.add(r.getBval());
+
+			Code r = new Code(scanner, t.getValue()).read();
+			expansionList.add((byte) r.getBval());
 		}
 
-		sort.addExpansion(code.getBval(), charFlags(code.getCval()), expansionList);
+		sort.addExpansion((byte) code.getBval(), charFlags(code.getCval()), expansionList);
 		state = IN_INITIAL;
 	}
 
@@ -292,35 +317,34 @@ public class SrtTextReader {
 	 * two 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 +354,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;
 	}
@@ -375,57 +402,55 @@ 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 int cval;
 		private byte bval;
-		private char cval;
 
 		public Code(TokenScanner scanner, String val) {
 			this.scanner = scanner;
 			this.val = val;
 		}
 
-		public byte getBval() {
-			return bval;
+		public int getBval() {
+			return bval & 0xff;
 		}
 
-		public char getCval() {
+		public int getCval() {
 			return cval;
 		}
 
-		public Code invoke() {
+		public Code read() {
 			try {
 				if (val.length() == 1) {
-					CharBuffer cbuf = CharBuffer.wrap(val.toCharArray());
-					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();
+					cval = Integer.parseInt(val, 16);
 				}
-			} catch (CharacterCodingException e) {
-				throw new SyntaxException(scanner, "Not a valid character (" + val + ") in codepage");
+
+				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);
+
+				// TODO: this is only for single byte charsets
+				bval = out.get();
 			} 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/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..d21d3bc 100644
--- a/test/func/SimpleTest.java
+++ b/test/func/SimpleTest.java
@@ -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/route/SimpleRouteTest.java b/test/func/route/SimpleRouteTest.java
index 35db3d2..de57d8b 100644
--- a/test/func/route/SimpleRouteTest.java
+++ b/test/func/route/SimpleRouteTest.java
@@ -52,24 +52,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(128691));
+				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")) {
+				break;
+			case "LBL":
 				count++;
-				assertEquals("LBL size", 28730, size);
-			} else if (ext.equals("NET")) {
+				assertEquals("LBL size", 28734, size);
+				break;
+			case "NET":
 				count++;
-				assertEquals("NET size", 66804, size);
-			} else if (ext.equals("NOD")) {
+				assertEquals("NET size", 66816, size);
+				break;
+			case "NOD":
 				count++;
-				assertEquals("NOD size", 186800, size);
+				assertEquals("NOD size", 149782, size);
+				break;
 			}
 		}
 		assertTrue("enough checks run", count == 5);
@@ -83,24 +89,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", 2562, size);
+				break;
 			}
 		}
 		assertTrue("enough checks run", count == 5);
diff --git a/test/main/SortTest.java b/test/main/SortTest.java
new file mode 100644
index 0000000..636e535
--- /dev/null
+++ b/test/main/SortTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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 void test() throws Exception {
+		sort = SrtTextReader.sortForCodepage(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);
+
+		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;
+			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())
+				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;
+		}
+		// Reject low characters most of the time
+		return (ch < 0x20 && rand.nextInt(100) < 95);
+	}
+
+	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) {
+			if (arg.equals("--time"))
+				sortTest.time = true;
+			else if (arg.equals("--full"))
+				sortTest.fullOutput = true;
+		}
+		sortTest.test();
+	}
+}
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/labelenc/Format6EncoderTest.java b/test/uk/me/parabola/imgfmt/app/labelenc/Format6EncoderTest.java
new file mode 100644
index 0000000..f7a642a
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/labelenc/Format6EncoderTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.After;
+import org.junit.Assert;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+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..50d6622 100644
--- a/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
+++ b/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
@@ -51,11 +51,17 @@ 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"));
+	}
+
+	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..eec8405 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Û‡²", "–"));
 	}
 
 	/**
@@ -121,52 +124,62 @@ public class SortTest {
 	}
 
 	@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"));
-
-		assertEquals(1, collator.compare("aaa", "aa"));
-		assertEquals(-1, collator.compare("aa", "aaa"));
+	public void testTertiaryPlusExpansion() {
+		assertEquals(-1, keyCompare("æ", "ªe"));
+		assertEquals(-1, keyCompare("`æ", "`ªe"));
 	}
 
+	/**
+	 * Make the internal initial buffer overflow so it has to be reallocated.
+	 */
 	@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 testKeyOverflow() {
+		assertEquals(1, keyCompare("™™™™™", "AA"));
+	}
 
-		assertEquals(1, collator.compare("aaaa", "aaa"));
-		assertEquals(-1, collator.compare("aaa", "aaaa"));
+	@Test
+	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"));
 	}
 
 	@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"));
+	}
+
+	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 +191,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..c180310
--- /dev/null
+++ b/test/uk/me/parabola/imgfmt/app/srt/SrtCollatorTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 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/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..ba027c4 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);
@@ -1023,9 +1034,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;
 	}
 	
diff --git a/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java b/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
index 3ea8c67..5c83fb4 100644
--- a/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
+++ b/test/uk/me/parabola/mkgmap/scan/TokenScannerTest.java
@@ -88,4 +88,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..183f98c 100644
--- a/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java
+++ b/test/uk/me/parabola/mkgmap/srt/SrtTextReaderTest.java
@@ -17,12 +17,14 @@ 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;
 
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotSame;
 
 public class SrtTextReaderTest {
 
@@ -31,6 +33,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 +103,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 +142,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