[mkgmap] 01/06: New upstream version 0.0.0+svn3973

Bas Couwenberg sebastic at debian.org
Sat Jul 1 12:31:49 UTC 2017


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

sebastic pushed a commit to branch master
in repository mkgmap.

commit 4a57ee4a0b661e33f9a367bfb6342bd2997fbeaa
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sat Jul 1 14:20:20 2017 +0200

    New upstream version 0.0.0+svn3973
---
 .idea/inspectionProfiles/Mapping.xml               |   6 +-
 .idea/inspectionProfiles/Project_Default.xml       |  13 +-
 .idea/inspectionProfiles/profiles_settings.xml     |   2 +-
 build.xml                                          |   1 +
 doc/options.txt                                    | 141 +++-
 doc/styles/internal-tags.txt                       |   4 +-
 doc/styles/rules-filters.txt                       |   7 +-
 doc/styles/rules.txt                               |  76 +-
 resources/help/en/options                          | 133 ++-
 resources/mkgmap-version.properties                |   4 +-
 resources/roadNameConfig.txt                       |  88 ++
 resources/sort/cp1252.txt                          |  14 +-
 resources/sort/cp1254.txt                          |   8 +-
 resources/sort/cp1258.txt                          |   4 +-
 resources/styles/default/inc/name                  |   1 +
 resources/styles/default/lines                     |  82 +-
 resources/styles/default/polygons                  |   7 +-
 resources/styles/empty/info                        |   8 +
 resources/styles/empty/version                     |   1 +
 src/uk/me/parabola/imgfmt/FileSystemParam.java     |   9 +
 src/uk/me/parabola/imgfmt/Utils.java               |  48 +-
 src/uk/me/parabola/imgfmt/app/Area.java            |  30 +-
 src/uk/me/parabola/imgfmt/app/Coord.java           | 276 +++---
 src/uk/me/parabola/imgfmt/app/Label.java           |  27 +-
 .../imgfmt/app/labelenc/AnyCharsetEncoder.java     |   5 +-
 .../parabola/imgfmt/app/labelenc/EncodedText.java  |   1 +
 src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java     |   7 +-
 src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java  |  10 +-
 src/uk/me/parabola/imgfmt/app/map/MapReader.java   |   4 +-
 .../parabola/imgfmt/app/mdr/LargeListSorter.java   | 125 +++
 src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java     |  74 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java       |  16 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java       |  51 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr19.java       |   2 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java       |  82 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java       |  51 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java       |  74 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr28.java       |   3 -
 src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java       |   6 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java       |  65 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java        |  93 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java  |  26 +
 src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java        | 333 +++++---
 src/uk/me/parabola/imgfmt/app/mdr/Mdr7Record.java  |  95 ++-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr8.java        |  10 +-
 src/uk/me/parabola/imgfmt/app/mdr/Mdr8Record.java  |   6 +-
 src/uk/me/parabola/imgfmt/app/mdr/MdrConfig.java   | 131 +++
 src/uk/me/parabola/imgfmt/app/mdr/MdrUtils.java    |  30 +-
 src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java |  60 +-
 .../parabola/imgfmt/app/net/AccessTagsAndBits.java |  73 +-
 .../me/parabola/imgfmt/app/net/AngleChecker.java   |  15 +-
 src/uk/me/parabola/imgfmt/app/net/NETFile.java     |  27 +-
 .../me/parabola/imgfmt/app/net/NETFileReader.java  |  13 +-
 src/uk/me/parabola/imgfmt/app/net/RouteNode.java   | 192 +++--
 .../me/parabola/imgfmt/app/srt/CodePosition.java   |   2 +-
 .../me/parabola/imgfmt/app/srt/DoubleSortKey.java  |  44 +
 src/uk/me/parabola/imgfmt/app/srt/SRTFile.java     |  36 +-
 src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java   |  46 +-
 src/uk/me/parabola/imgfmt/app/srt/Sort.java        | 299 +++++--
 .../me/parabola/imgfmt/app/srt/SrtFileReader.java  | 291 +++++++
 src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java  |  19 +-
 .../me/parabola/imgfmt/app/trergn/MapObject.java   |  15 +-
 src/uk/me/parabola/imgfmt/app/trergn/Polyline.java |  12 +-
 .../parabola/imgfmt/app/trergn/RGNFileReader.java  |  22 +-
 .../me/parabola/imgfmt/app/trergn/Subdivision.java |  33 +-
 .../parabola/imgfmt/app/trergn/TREFileReader.java  |  11 +-
 .../me/parabola/imgfmt/app/trergn/TREHeader.java   |  11 +-
 src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java      |  10 +-
 src/uk/me/parabola/imgfmt/sys/ImgHeader.java       |  14 +-
 src/uk/me/parabola/log/UsefulFormatter.java        |   6 +-
 .../me/parabola/mkgmap/build/LayerFilterChain.java |  13 +-
 src/uk/me/parabola/mkgmap/build/Locator.java       |  23 +-
 src/uk/me/parabola/mkgmap/build/LocatorConfig.java |  17 +-
 src/uk/me/parabola/mkgmap/build/LocatorUtil.java   |   9 +-
 src/uk/me/parabola/mkgmap/build/MapArea.java       | 585 ++++++++-----
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |  84 +-
 src/uk/me/parabola/mkgmap/build/MapSplitter.java   | 111 +--
 .../me/parabola/mkgmap/combiners/GmapiBuilder.java |  10 +-
 .../parabola/mkgmap/combiners/GmapsuppBuilder.java |  29 +-
 .../me/parabola/mkgmap/combiners/MdrBuilder.java   |   8 +-
 .../parabola/mkgmap/combiners/OverviewBuilder.java |  32 +-
 .../mkgmap/filters/LinePreparerFilter.java         |   3 +
 .../mkgmap/filters/LineSplitterFilter.java         |  81 +-
 .../mkgmap/filters/MustSplitException.java         |  38 +
 .../mkgmap/filters/PolygonSplitterBase.java        |  85 +-
 .../mkgmap/filters/PolygonSplitterFilter.java      |  57 +-
 .../mkgmap/filters/PredictFilterPoints.java        |  73 ++
 .../parabola/mkgmap/filters/ShapeMergeFilter.java  | 354 +++++---
 src/uk/me/parabola/mkgmap/general/AreaClipper.java |  20 +-
 .../mkgmap/general/LoadableMapDataSource.java      |   3 +-
 src/uk/me/parabola/mkgmap/general/MapDetails.java  |  41 +-
 src/uk/me/parabola/mkgmap/general/MapLine.java     |   7 +-
 .../me/parabola/mkgmap/general/PolygonClipper.java |  82 --
 src/uk/me/parabola/mkgmap/main/Main.java           |  13 +-
 src/uk/me/parabola/mkgmap/main/MapMaker.java       |   4 +-
 src/uk/me/parabola/mkgmap/main/StyleTester.java    | 124 +--
 src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java |   2 +-
 .../parabola/mkgmap/osmstyle/ExpressionRule.java   |   2 +-
 src/uk/me/parabola/mkgmap/osmstyle/NameFinder.java | 116 +++
 .../mkgmap/osmstyle/PrefixSuffixFilter.java        | 324 +++++++
 src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java |  35 +-
 .../parabola/mkgmap/osmstyle/RuleFileReader.java   | 314 +++++--
 src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java  | 109 ++-
 src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java  |   9 -
 .../me/parabola/mkgmap/osmstyle/StylePrinter.java  |   2 +-
 .../parabola/mkgmap/osmstyle/StyledConverter.java  |  97 ++-
 .../mkgmap/osmstyle/actions/ActionList.java        |  13 +
 .../mkgmap/osmstyle/actions/EchoAction.java        |   5 +-
 .../mkgmap/osmstyle/actions/EchoTagsAction.java    |   5 +-
 .../mkgmap/osmstyle/actions/HeightFilter.java      |   9 +-
 .../parabola/mkgmap/osmstyle/eval/AbstractOp.java  |  29 +
 .../me/parabola/mkgmap/osmstyle/eval/EqualsOp.java |   8 -
 .../mkgmap/osmstyle/eval/ExpressionReader.java     |  61 +-
 .../me/parabola/mkgmap/osmstyle/eval/LinkedOp.java |   7 +
 src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java    |   7 +
 .../mkgmap/osmstyle/housenumber/ExtNumbers.java    | 162 ++--
 .../osmstyle/housenumber/HousenumberGenerator.java |   5 +-
 src/uk/me/parabola/mkgmap/reader/MapReader.java    |  67 ++
 .../mkgmap/reader/MapperBasedMapDataSource.java    |  24 +-
 src/uk/me/parabola/mkgmap/reader/dem/Brent.java    |  99 ---
 src/uk/me/parabola/mkgmap/reader/dem/DEM.java      | 863 -------------------
 src/uk/me/parabola/mkgmap/reader/dem/HGTDEM.java   |  68 --
 .../mkgmap/reader/dem/optional/GeoTiffDEM.java     | 156 ----
 .../mkgmap/reader/osm/CoastlineFileLoader.java     |  52 +-
 src/uk/me/parabola/mkgmap/reader/osm/Element.java  |  32 +-
 .../parabola/mkgmap/reader/osm/ElementSaver.java   |  46 +-
 .../parabola/mkgmap/reader/osm/HighwayHooks.java   |  36 +-
 .../mkgmap/reader/osm/LinkDestinationHook.java     |  33 +-
 .../mkgmap/reader/osm/LoadableOsmDataSource.java   |  36 -
 .../parabola/mkgmap/reader/osm/LocationHook.java   |  23 +-
 .../mkgmap/reader/osm/MultiPolygonCutter.java      | 753 +++++++++++++++++
 .../mkgmap/reader/osm/MultiPolygonRelation.java    | 934 +++------------------
 src/uk/me/parabola/mkgmap/reader/osm/Node.java     |   1 +
 ...oastDataSource.java => OsmCoastDataSource.java} |  11 +-
 .../me/parabola/mkgmap/reader/osm/OsmHandler.java  |  93 +-
 .../mkgmap/reader/osm/OsmMapDataSource.java        | 105 ++-
 ...ataSource.java => OsmPrecompSeaDataSource.java} |  19 +-
 .../mkgmap/reader/osm/POIGeneratorHook.java        |  34 +-
 .../mkgmap/reader/osm/PrecompSeaElementSaver.java  |  30 -
 src/uk/me/parabola/mkgmap/reader/osm/Relation.java |   5 +
 .../mkgmap/reader/osm/RelationStyleHook.java       |  19 +-
 .../mkgmap/reader/osm/ResidentialHook.java         | 137 +++
 .../mkgmap/reader/osm/RestrictionRelation.java     |  43 +-
 .../parabola/mkgmap/reader/osm/SeaGenerator.java   |  57 +-
 .../mkgmap/reader/osm/SeaPolygonRelation.java      |   2 +-
 src/uk/me/parabola/mkgmap/reader/osm/TagDict.java  |  17 +
 src/uk/me/parabola/mkgmap/reader/osm/Tags.java     | 151 +---
 src/uk/me/parabola/mkgmap/reader/osm/Way.java      |   1 +
 .../reader/osm/bin/OsmBinCoastDataSource.java      |  41 -
 .../mkgmap/reader/osm/bin/OsmBinHandler.java       |  46 +-
 .../mkgmap/reader/osm/bin/OsmBinMapDataSource.java |  71 --
 .../reader/osm/bin/OsmBinPrecompSeaDataSource.java |  42 -
 .../mkgmap/reader/osm/boundary/Boundary.java       |   9 +-
 .../reader/osm/boundary/BoundaryConverter.java     |   8 +-
 .../reader/osm/boundary/BoundaryElementSaver.java  | 148 +---
 .../reader/osm/boundary/BoundaryLocationInfo.java  |  12 +-
 .../osm/boundary/BoundaryLocationPreparer.java     | 133 ++-
 .../reader/osm/boundary/BoundaryPreprocessor.java  |  59 +-
 .../reader/osm/boundary/BoundaryQuadTree.java      |  28 +-
 .../reader/osm/boundary/BoundaryRelation.java      | 230 +----
 .../mkgmap/reader/osm/boundary/BoundaryUtil.java   |  12 +-
 .../osm/boundary/LoadableBoundaryDataSource.java   |  20 -
 .../osm/boundary/O5mBinBoundaryDataSource.java     |  59 --
 .../osm/boundary/OsmBinBoundaryDataSource.java     |  59 --
 ...yDataSource.java => OsmBoundaryDataSource.java} |  10 +-
 .../mkgmap/reader/osm/o5m/O5mBinHandler.java       |  23 +-
 .../mkgmap/reader/osm/o5m/O5mBinMapDataSource.java |  57 --
 .../mkgmap/reader/osm/xml/Osm5MapDataSource.java   |  84 --
 .../{Osm5XmlHandler.java => OsmXmlHandler.java}    |  86 +-
 .../reader/overview/OverviewMapDataSource.java     |   3 +-
 .../parabola/mkgmap/reader/plugin/MapReader.java   |  90 --
 .../mkgmap/reader/polish/PolishMapDataSource.java  |  13 +-
 .../mkgmap/reader/test/ElementTestDataSource.java  |   4 +-
 .../mkgmap/sea/optional/PrecompSeaGenerator.java   |   4 +-
 src/uk/me/parabola/mkgmap/srt/SrtTextReader.java   | 102 ++-
 src/uk/me/parabola/util/Java2DConverter.java       |  60 +-
 src/uk/me/parabola/util/ShapeSplitter.java         | 441 +++++++++-
 test/func/read/ImgReadTest.java                    |   4 +-
 test/func/route/SimpleRouteTest.java               |   2 +-
 test/func/style/ScriptedStyleTest.java             |  19 +-
 test/resources/rules/if-then-1.test                |  20 +
 test/resources/rules/if-then-2.test                |  19 +
 test/resources/rules/if-then-else-1.test           |  27 +
 test/resources/rules/two-types1.test               |  20 +
 test/uk/me/parabola/imgfmt/UtilsTest.java          |  21 +-
 test/uk/me/parabola/imgfmt/app/CoordTest.java      |  98 ++-
 .../me/parabola/imgfmt/app/srt/SortExpandTest.java |   8 -
 test/uk/me/parabola/imgfmt/app/srt/SortTest.java   |   5 +-
 .../mkgmap/filters/LineSplitterFilterTest.java     | 105 +++
 .../mkgmap/filters/ShapeMergeFilterTest.java       | 153 +++-
 .../uk/me/parabola/mkgmap/general/MapLineTest.java |  16 +-
 .../uk/me/parabola/mkgmap/reader/osm/TagsTest.java |  29 +-
 test/uk/me/parabola/util/Java2DConverterTest.java  |  51 +-
 test/uk/me/parabola/util/ShapeSplitterTest.java    | 399 +++++++++
 194 files changed, 7860 insertions(+), 5805 deletions(-)

diff --git a/.idea/inspectionProfiles/Mapping.xml b/.idea/inspectionProfiles/Mapping.xml
index fbde4d8..5e24e61 100644
--- a/.idea/inspectionProfiles/Mapping.xml
+++ b/.idea/inspectionProfiles/Mapping.xml
@@ -919,6 +919,7 @@
     <inspection_tool class="ES6BindWithArrowFunction" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ES6ConvertRequireIntoImport" enabled="false" level="INFORMATION" enabled_by_default="false" />
     <inspection_tool class="ES6ConvertVarToLetConst" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ES6MissingAwait" enabled="false" level="INFORMATION" enabled_by_default="false" />
     <inspection_tool class="ES6ModulesDependencies" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="ES6UnusedImports" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ES6Validation" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -1543,6 +1544,7 @@
     <inspection_tool class="JSFileReferences" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="JSFunctionExpressionToArrowFunction" enabled="false" level="INFORMATION" enabled_by_default="false" />
     <inspection_tool class="JSHint" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
     <inspection_tool class="JSImplicitlyInternalDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="JSJQueryEfficiency" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JSLastCommaInArrayLiteral" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -1588,6 +1590,7 @@
     <inspection_tool class="JSUndefinedPropertyAssignment" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
     <inspection_tool class="JSUnfilteredForInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JSUnnecessarySemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="JSUnresolvedExtXType" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JSUnresolvedFunction" enabled="true" level="INFO" enabled_by_default="true" />
     <inspection_tool class="JSUnresolvedLibraryURL" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JSUnresolvedVariable" enabled="true" level="INFO" enabled_by_default="true" />
@@ -1781,6 +1784,7 @@
     <inspection_tool class="MethodCanBeVariableArityMethod" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="ignoreByteAndShortArrayParameters" value="true" />
       <option name="ignoreOverridingMethods" value="true" />
+      <option name="onlyReportPublicMethods" value="true" />
     </inspection_tool>
     <inspection_tool class="MethodCount" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_limit" value="20" />
@@ -2765,7 +2769,7 @@
     <inspection_tool class="TooBroadCatch" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TooBroadScope" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="m_allowConstructorAsInitializer" value="false" />
-      <option name="m_onlyLookAtBlocks" value="false" />
+      <option name="m_onlyLookAtBlocks" value="true" />
     </inspection_tool>
     <inspection_tool class="TooBroadThrows" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TrailingSpacesInProperty" enabled="false" level="WARNING" enabled_by_default="false" />
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index ea75877..5ece9ba 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -1,7 +1,6 @@
 <component name="InspectionProjectProfileManager">
-  <profile version="1.0" is_locked="false">
+  <profile version="1.0">
     <option name="myName" value="Project Default" />
-    <option name="myLocal" value="false" />
     <inspection_tool class="AbstractMethodCallInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AbstractMethodOverridesAbstractMethod" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -105,6 +104,11 @@
     <inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SameReturnValue" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
+      <option name="processCode" value="false" />
+      <option name="processLiterals" value="false" />
+      <option name="processComments" value="true" />
+    </inspection_tool>
     <inspection_tool class="StaticCollection" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="m_ignoreWeakCollections" value="false" />
     </inspection_tool>
@@ -125,6 +129,11 @@
     </inspection_tool>
     <inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="UnnecessaryUnboxing" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="WeakerAccess" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="false" />
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
+      <option name="SUGGEST_PRIVATE_FOR_INNERS" value="false" />
+    </inspection_tool>
     <inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
   </profile>
 </component>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
index 470e646..a80eb99 100644
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -1,7 +1,7 @@
 <component name="InspectionProjectProfileManager">
   <settings>
+    <option name="projectProfile" value="Mapping" />
     <option name="PROJECT_PROFILE" value="Mapping" />
-    <option name="USE_PROJECT_PROFILE" value="true" />
     <version value="1.0" />
   </settings>
 </component>
\ No newline at end of file
diff --git a/build.xml b/build.xml
index d2fd028..a3cb1a5 100644
--- a/build.xml
+++ b/build.xml
@@ -380,6 +380,7 @@
 
 		<copy todir="${dist}/examples">
 			<fileset dir="resources">
+				<include name="roadNameConfig.txt"/>
 				<include name="installer/**"/>
 				<include name="styles/default/**"/>
 				<include name="styles/noname/**"/>
diff --git a/doc/options.txt b/doc/options.txt
index 3c227b7..e2fd0b5 100644
--- a/doc/options.txt
+++ b/doc/options.txt
@@ -1,6 +1,8 @@
-= List of options = Note that option order is significant: An option only 
-applies to subsequent input files. (So if you are using splitter, you probably 
-want to put most of your options before '-c template.args'.)
+= List of options = 
+Note that option order is significant: An option only 
+applies to subsequent input files. So if you are using splitter, you probably 
+want to put most of your options before '-c template.args'.
+
 
 === General options ===
 
@@ -15,7 +17,8 @@ list of all the help topics is printed instead.
 
 ;--input-file=filename
 : 	Read input data from the give file.  This option (or just a
-filename) may be given more than once.
+filename) may be given more than once. Make sure to set all 
+wanted options before using this.
 
 ;--gmapsupp
 : 	Create a gmapsupp.img file that can be uploaded to a Garmin or
@@ -50,8 +53,9 @@ QLandkarte, MapSource or on a GPS, where it is normally shown
 below the family name. Example: --description="Germany, Denmark"
 Please note: if you use splitter.jar to build a template.args file
 and use -c template.args, then that file may contain a
-"description" that will override this option. Use "--description" in
-splitter.jar to change the description in the template.args file.
+"description" that will override this option for each single
+tile. Make sure to set the description for the gmapsupp.img after
+"-c template.args".
 <p>
 ;--country-name=name
 : 	Sets the map's country name. The default is "COUNTRY".
@@ -72,7 +76,9 @@ no abbreviated region name.
 : 	This is equivalent to --code-page=1252.
 <p>
 ;--unicode
-: 	This is equivalent to --code-page=65001. Note that only newer devices support Unicode.
+: 	This is equivalent to --code-page=65001. Note that some devices don't support 
+unicode maps produced by mkgmap.
+
 <p>
 ;--code-page=number
 :     This option enables the use of international characters. Only 8 bit
@@ -100,7 +106,7 @@ device.
 <p>
 If instead the --tdbfile option is given then the index consists
 of two files named osmmap.mdx and osmmap_mdr.img which can be used
-with mapsource. (For compatibility, you do not need the tdbfile
+with MapSource. (For compatibility, you do not need the tdbfile
 option if gmapsupp is not given).
 <p>
 If both the --gmapsupp and --tdbfile options are given alongside
@@ -122,20 +128,84 @@ tags using the style file:
   mkgmap:housenumber
   mkgmap:phone
   (mkgmap:is_in - used by location-autofill=is_in)
-
+ 
 : If the index is created from previously compiled .img files, then the
 same code page and sorting options (e.g. --code-page, --latin1) must
 be used as were used to compile the individual map tiles.
 
-;--x-split-name-index
-:     A temporary option to enable indexing each part of a street name separately.
+;--split-name-index
+:     An option to enable indexing each part of a street name separately.
 So for example if the street is "Aleksandra Gryglewskiego" then you will be able to
 search for it as both "Aleksandra" and "Gryglewskiego".  It will also increase the
 size of the index.  Useful in countries where searching for the first word in name
-is not the right thing to do.
-<p>
-Note that this option is still experimental and there may be problems. If you find
-any let us know!
+is not the right thing to do. Words following an opening bracket '(' are ignored. 
+See also option road-name-config.  
+<p>
+;--road-name-config=file
+:	This option handles the problem that some countries have road names which 
+often start or end with very similar words, e.g. in France the first word
+is very often 'Rue', often followed by a preposition like 'de la' or 'des'.
+This leads to rather long road names like 'Rue de la Concorde' where only
+the word 'Concorde' is really interesting. In the USA, you often have names
+like 'West Main Street' where only the word 'Main' is important.  
+Garmin software has some tricks to handle this problem. It allows to use
+special characters in the road labels which mark the beginning and end of 
+the important part. In combinarion with option split-name-index
+only the words in the important part are indexed.
+<p>
+:There are two different visiual effects of this option:
+::	- On the PC, when zooming out, the name 'Rue de la Concorde' is only
+rendered as 'Concorde'.
+::	- The index for road names only contains the important part of the name.
+You can search for road name Conc to find road names like 'Rue de la Concorde'.
+One problem: Search for 'Rue' will not list 'Rue de la Concorde' 
+or 'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
+Only MapSource shows a corresponding hint.
+<p>
+::	Another effect is that the index is smaller. 
+:	The option specifies the path to a file which gives the details. See 
+comments in the sample roadNameConfig.txt for further details.
+<p>
+;--mdr7-excl 
+:	This option allows to specify words which should not be in the road index.
+It was added before option road-name-config and is probably no longer needed.
+:	Example usage: --x-mdr7-excl="Road, Street, Straße, Weg"
+<p>
+;--mdr7-del
+:	Use this option if your style adds strings to the labels of roads which you
+want to see in the map but which should not appear in the result list
+of a road name / address search. The list is used like this:
+For each road label mkgmap searches the last blank. If one is found, it checks
+if the word after it appears in the given list. If so, the word is removed
+and the search is repeated. The remaining string is used to create the index.
+:	Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to a road
+label. You can use --mdr7-del="pav.,unp." to remove these apendixes from the index.
+<p>
+;--poi-excl-index
+:	By default, mkgmap indexes the following POI types with a non-empty label:
+::	- 0x00 .. 0x0f (cities, sub type 0, type <= 0xf)
+::	- 0x2axx..0x30xx (Food & Drink, Lodging, ...)
+::	- 0x28xx (no category ?)
+::	- 0x64xx .. 0x66xx (attractions)   
+:	This option allows to exclude POI types from the index. 
+The exclueded types are not indexed, but may still be searchable on a device 
+as some devices seem to ignore most of the index, e.g. an Oregon 600 with 
+firmware 5.00 only seems to use it for city search.
+If you device finds a POI name like 'Planet' when you search for 'Net'
+it doesn't use the index because the index created by mkgmap cannot help for 
+that search.
+<p> 
+:	So, this option may help when you care about the size of the index or the
+memory that is needed to calculate it.    
+The option expects a comma separated list of types or type ranges. A range is 
+given with from-type-to-type, e.g. 0x6400-0x6405. First and last type are both 
+excluded.A range can span multiple types, e.g. 0x6400-0x661f.  
+:	Examples for usage: 
+::	- Assume your style adds a POI with type 0x2800 for each addr:housenumber.
+It is not useful to index those numbers, so you can use --poi-excl-index=0x2800
+to exclude this.
+::	- For the mentioned Oregon you may use --poi-excl-index=0x2a00-0x661f
+to reduce the index size.
 <p>
 ;--bounds=directory|zipfile
 :     A directory or a zip file containing the preprocessed bounds files. 
@@ -250,6 +320,18 @@ style file.
 directory or zip file containing multiple styles. If --style-file 
 is not used, it selects one of the built-in styles. 
 <p>
+;--style-option
+: 	Provide a semicolon separated list of tags which can be used in the style.
+The intended use is to make a single style more flexible, e.g.
+you may want to use a slightly different set of rules for a map of
+a whole continent. The tags given will be prefixed with "mkgmap:option:".
+If no value is provided the default "true" is used.  
+This option allows to use rules like
+mkgmap:option:light=true & landuse=farmland {remove landuse}
+Example: -- style-option=light;routing=car
+will add the tags mkgmap:option:light=true and mkgmap:option:routing=car
+to each element before style processing happens. 
+<p>                                                            
 ;--list-styles
 : 	List the available styles. If this option is preceded by a style-file
 option then it lists the styles available within that file.
@@ -270,7 +352,7 @@ its own default. Up to 8 levels may be specified.
 : 	Get the tag that will be used to supply the name.  Useful for
 language variations.  You can supply a list and the first one
 will be used.  e.g. --name-tag-list=name:en,int_name,name
-<p>
+<p>                                                            
 ;--map-features=file
 : 	This option is ignored; use the --style-file option instead.
 <p>
@@ -293,7 +375,7 @@ Example: --family-name="OpenStreetmap mkgmap XL 2019"
 It is often just 1, which is the default.
 <p>
 ;--product-version
-: 	The version of the product. Default value is 1.
+: 	The version of the product. Default value is 100 which means version 1.00.
 <p>
 ;--series-name
 : 	This name will be displayed in MapSource in the map selection
@@ -309,10 +391,17 @@ part of the mapname in the list of the individual maps.
 ;--copyright-file=file
 : 	Specify copyright messages from a file.
 Note that the first copyright message is not displayed on a device, but is 
-shown in BaseCamp. The copyright file must include at least two lines.
+shown in BaseCamp. The copyright file must include at least two lines and
+be UTF-8 encoded. The following symbols will be substituted by mkgmap:
+$MKGMAP_VERSION$, $JAVA_VERSION$, $YEAR$, $LONG_DATE$, $SHORT_DATE$ and $TIME$.
+Time and date substitutions use the local date and time formats.
 <p>
 ;--license-file=file
-: 	Specify a file which content will be added as license. 
+: 	Specify a file which content will be added as license.
+The license file must be UTF-8 encoded.
+The following symbols will be substituted by mkgmap:
+$MKGMAP_VERSION$, $JAVA_VERSION$, $YEAR$, $LONG_DATE$, $SHORT_DATE$ and $TIME$.
+Time and date substitutions use the local date and time formats.
 All entries of all maps will be merged in the overview map.
 <p>
 === Optimization options ===
@@ -396,7 +485,8 @@ The options are translated to drive-on=left|right.
 : 	Check that roundabouts have the expected direction (clockwise
 when vehicles drive on the left). Roundabouts that are complete
 loops and have the wrong direction are reversed. Also checks
-that the roundabouts do not fork or overlap other roundabouts.
+that the roundabouts do not fork or overlap other roundabouts
+and that no more than one connecting highway joins at each node.
 <p>
 ;--check-roundabout-flares
 : 	Sanity check roundabout flare roads - warn if they don't point
@@ -585,7 +675,8 @@ generates GPX files for each polygon checked by
 the flood blocker
 
 ;--make-poi-index
-: 	Generate the POI index (not yet useful).
+:	Generate a POI index in each map tile. Probably not used by modern devices,
+but still supported.
 <p>
 ;--nsis
 : 	Write a .nsi file that can be used with the Nullsoft Scriptable Install System
@@ -670,6 +761,10 @@ TAG=VALUE or TAG=*. Blank lines and lines that start with
 a # or ; are ignored. All tag/value pairs in the OSM input are
 compared with these patterns and those that match are deleted.
 <p>
+;--ignore-fixme-values
+: 	Tells mkgmap to ignore all tags for which the value matches the pattern
+"(?i)fix[ _]?+me".	
+<p>
 ;--tdbfile
 : 	Write files that are essential to running with MapSource, a .tdb file and
 an overview map.
@@ -691,6 +786,10 @@ cover the same area, you can see through this map and see the
 lower map too.  Useful for contour line maps among other
 things.
 <p>
+;--custom
+: 	Write a different TRE header. With this option mkgmap writes the bytes
+0x170401 instead of the default 0x110301 at offset 43. Useful for marine maps.	
+<p>
 ;--hide-gmapsupp-on-pc
 : 	Set a bit in the gmapsupp.img that tells PC software that the file is
 already installed on the PC and therefore there is no need to read it 
diff --git a/doc/styles/internal-tags.txt b/doc/styles/internal-tags.txt
index 67f64e9..747d459 100644
--- a/doc/styles/internal-tags.txt
+++ b/doc/styles/internal-tags.txt
@@ -112,7 +112,8 @@ is used to assign the country location.
 | +mkgmap:admin_level9+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=9+ the element is located in | 'bounds'    
 | +mkgmap:admin_level10+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=10+ the element is located in | 'bounds'    
 | +mkgmap:admin_level11+  | Name of the +boundary=administrative+ relation/polygon with +admin_level=11+ the element is located in | 'bounds'    
-| +mkgmap:postcode+  | Name of the postal code relation/polygon the element is located in | 'bounds'    
+| +mkgmap:postcode+  | Name of the postal code relation/polygon the element is located in | 'bounds'
+| +mkgmap:residential+  | Name of the residential relation/polygon the element is located in or 'yes' if unnamed | none         
 | +mkgmap:area2poi+  | The value is +true+ if the POI is derived from a polygon | 'add-pois-to-areas'    
 | +mkgmap:line2poi+  | The value is +true+ if the POI is derived from a line | 'add-pois-to-lines'    
 | +mkgmap:line2poitype+  | The tag is set for each POI generated from a line. Possible values are: +start+, +end+, +mid+, +inner+. | 'add-pois-to-lines'    
@@ -123,6 +124,7 @@ is used to assign the country location.
 | +mkgmap:dest_hint+  | The tag is set to a reasonable destination value for the part on link roads that should contain destination information about the link | 'process-destination'    
 | +mkgmap:synthesised+  | The value is +yes+ if the way was added by the make-opposite-cycleways option | 'make-opposite-cycleways'
 | +mkgmap:mp_created+  | The value is +true+ if the way was created by the internal multi-polygon-relation handling | none
+| +mkgmap:option:<key>+  | Tag generated by the --style-option option | 'style-option' 
 |=========================================================
 
 .Other internal tags
diff --git a/doc/styles/rules-filters.txt b/doc/styles/rules-filters.txt
index 249b3ef..2424582 100644
--- a/doc/styles/rules-filters.txt
+++ b/doc/styles/rules-filters.txt
@@ -14,7 +14,7 @@ matters.
 
 | conv | `m=>ft` |
 Use for conversions between units.
-With the argument +m=>ft+ the value is converted into feet, with the
+With the argument +m\=\>ft+ the value is converted into feet, with the
 value being assumed to be in meters, unless the value includes a unit
 already.
 If any of the units are not recognised then the value is unchanged.
@@ -94,7 +94,10 @@ If there is just the one number then it is used in both cases.
 
 | height | `m=>ft` |
 This is exactly the same as the +conv+ filter, except that it prepends a special
-separation character before the value which is intended for elevations.
+separation character before the value which is intended for elevations so that 
+the Garmin software can convert it to the unit configured by the user.
+If no argument is given the default is `m=>ft`, else the target unit 
+must be ft (foot).
 
 `${ele\|height:"m=>ft"}`
 
diff --git a/doc/styles/rules.txt b/doc/styles/rules.txt
index 74e7adc..e675cb6 100644
--- a/doc/styles/rules.txt
+++ b/doc/styles/rules.txt
@@ -23,7 +23,9 @@ The three possible parts are:
 * The second part is the <<Action_block,action block>> that can be used to do things with the tags of objects that match the tests and is contained in curly braces `{...}`.
 * The third part is the <<Element_type,element type definition>> and sets
 the Garmin type and sometimes other parameters that will be used if the tests match. This part is contained
-in square brackets `[...]`.
+in square brackets `[...]`. 
+
+If you want to add two or more different map elements you can do this with repeated square brackets following one expression `[...]`  
 
 Here is an example of a rule containing all three sections:
 
@@ -827,6 +829,61 @@ will result in
 Actions in the finalize section are not persistent in terms of the +continue+ or
 +continue with_actions+ statement
 
+== Style syntax extension if then else ==
+
+To avoid the repetition of expressions you can use the following syntax: 
+if (<<Tag_tests,tests>>) then
+  i-rule(s)  
+end
+
+or
+ 
+if (<<Tag_tests,tests>>) then 
+  rule(s) 
+else 
+  e-rules(s) 
+end
+
+[NOTE]
+Rules within +if+ and +end+ must be written with an expression.
+The shortest valid expression is a pair of round brackets +()+.
+
+So, instead of 
+[source]
+----
+boundary=administrative { name '${mkgmap:boundary_name}' }
+boundary=administrative & admin_level<3 [0x1e resolution 12]
+boundary=administrative & admin_level<5 [0x1d resolution 19]
+boundary=administrative & admin_level<7 [0x1c resolution 21]
+boundary=administrative & admin_level<9 [0x1c resolution 22]
+boundary=administrative [0x1c resolution 22] 
+----
+you may write
+[source]
+----
+if (boundary=administrative) then
+	() { name '${mkgmap:boundary_name}' }
+	admin_level<3 [0x1e resolution 12]
+	admin_level<5 [0x1d resolution 19]
+	admin_level<7 [0x1c resolution 21]
+	admin_level<9 [0x1c resolution 22]
+	() [0x1c resolution 22]
+end 
+----
+
+If statements may also be nested and you can also use them in combination
+with the include statement.  
+
+[source]
+----
+if (mkgmap:option:routing=car) then
+	include "inc/car-rules";
+end 
+if (mkgmap:option:routing=bicycle) then
+	include "inc/cycle-rules";
+end 
+----
+                         
 == Troubleshooting ==
 
 For each node/way/relation, mkgmap goes through the tags exactly once in
@@ -864,6 +921,23 @@ amenity=bank [0x2f06 level 3]
 This will be explained in more detail in the following sections along
 with how to use more than one tag to make the choice.
 
+For a roundabout you may want to use the special Garmin type 0xc in combination with an overlaying way
+that shows the road importance. You can do this with two rules like this
+[source]
+----
+junction=roundabout & (highway=tertiary | highway=tertiary_link)
+    [0x0c road_class=1 road_speed=1 resolution 24 continue]
+junction=roundabout & (highway=tertiary | highway=tertiary_link) 
+    [0x10804 resolution 21] 
+----
+
+or shorter with one rule that has two type definitions 
+[source]
+----
+junction=roundabout & (highway=tertiary | highway=tertiary_link) 
+    [0x0c road_class=1 road_speed=1 resolution 24] [0x10804 resolution 21]
+----
+
 === More involved examples ===
 A few tips and tricks showing how the rules can be used to
 create almost any effect.
diff --git a/resources/help/en/options b/resources/help/en/options
index ce7346c..e3dc83f 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -14,7 +14,8 @@ General options:
 filename
 --input-file=filename
 	Read input data from the give file.  This option (or just a
-	filename) may be given more than once.
+	filename) may be given more than once. Make sure to set all 
+	wanted options before using this.
 
 --gmapsupp
 	Create a gmapsupp.img file that can be uploaded to a Garmin or
@@ -53,8 +54,9 @@ filename
 	below the family name. Example: --description="Germany, Denmark"
 	Please note: if you use splitter.jar to build a template.args file
 	and use -c template.args, then that file may contain a
-	"description" that will override this option. Use "--description" in
-	splitter.jar to change the description in the template.args file.
+	"description" that will override this option for each single
+	tile. Make sure to set the description for the gmapsupp.img after
+	"-c template.args".    
 
 --country-name=name
 	Sets the map's country name. The default is "COUNTRY".
@@ -75,7 +77,8 @@ Label options:
 	This is equivalent to --code-page=1252.
 
 --unicode
-	This is equivalent to --code-page=65001. Note that only newer devices support Unicode.
+	This is equivalent to --code-page=65001. Note that  
+	some devices don't support unicode maps produced by mkgmap.
 
 --code-page=number
 	This option enables the use of international characters. Only 8 bit
@@ -103,7 +106,7 @@ Address search options:
 
 	If instead the --tdbfile option is given then the index consists
 	of two files named osmmap.mdx and osmmap_mdr.img which can be used
-	with mapsource. (For compatibility, you do not need the tdbfile
+	with MapSource. (For compatibility, you do not need the tdbfile
 	option if gmapsupp is not given).
 
 	If both the --gmapsupp and --tdbfile options are given alongside
@@ -129,16 +132,80 @@ Address search options:
 	same code page and sorting options (e.g. --code-page, --latin1) must
 	be used as were used to compile the individual map tiles.
 
---x-split-name-index
-	A temporary option to enable indexing each part of a street name separately.
+--split-name-index
+	An option to enable indexing each part of a street name separately.
 	So for example if the street is "Aleksandra Gryglewskiego" then you will be able to
 	search for it as both "Aleksandra" and "Gryglewskiego".  It will also increase the
 	size of the index.  Useful in countries where searching for the first word in name
-	is not the right thing to do.
-
-	Note that this option is still experimental and there may be problems. If you find
-	any let us know!
-
+	is not the right thing to do. Words following an opening bracket '(' are ignored. 
+	See also option road-name-config.  
+
+--road-name-config=file
+	This option handles the problem that some countries have road names which 
+	often start or end with very similar words, e.g. in France the first word
+	is very often 'Rue', often followed by a preposition like 'de la' or 'des'.
+	This leads to rather long road names like 'Rue de la Concorde' where only
+	the word 'Concorde' is really interesting. In the USA, you often have names
+	like 'West Main Street' where only the word 'Main' is important.  
+	Garmin software has some tricks to handle this problem. It allows to use
+	special characters in the road labels which mark the beginning and end of 
+	the important part. In combinarion with option split-name-index
+	only the words in the important part are indexed.
+	 
+	There are two different visiual effects of this option:
+	- On the PC, when zooming out, the name 'Rue de la Concorde' is only
+		rendered as 'Concorde'.
+	- The index for road names only contains the important part of the name.
+		You can search for road name Conc to find road names like 'Rue de la Concorde'.
+		One problem: Search for 'Rue' will not list 'Rue de la Concorde' 
+		or 'Rue du Moulin'. It may list 'Rueben Brookins Road' if that is in the map.
+		Only MapSource shows a corresponding hint.
+		
+	Another effect is that the index is smaller. 
+	The option specifies the path to a file which gives the details. See 
+	comments in the sample roadNameConfig.txt for further details.
+ 
+--mdr7-excl 
+	This option allows to specify words which should not be in the road index.
+	It was added before option road-name-config and is probably no longer needed.
+	Example usage: --x-mdr7-excl="Road, Street, Straße, Weg"
+ 
+--mdr7-del
+	Use this option if your style adds strings to the labels of roads which you
+	want to see in the map but which should not appear in the result list
+	of a road name / address search. The list is used like this:
+	For each road label mkgmap searches the last blank. If one is found, it checks
+	if the word after it appears in the given list. If so, the word is removed
+	and the search is repeated. The remaining string is used to create the index.
+	Example: Assume your style adds surface attributes like 'pav.' or 'unp.' to a road
+	label. You can use --mdr7-del="pav.,unp." to remove these apendixes from the index.
+	  
+--poi-excl-index
+	By default, mkgmap indexes the following POI types with a non-empty label:
+	- 0x00 .. 0x0f (cities, sub type 0, type <= 0xf)
+	- 0x2axx..0x30xx (Food & Drink, Lodging, ...)
+	- 0x28xx (no category ?)
+	- 0x64xx .. 0x66xx (attractions)   
+	This option allows to exclude POI types from the index. 
+	The exclueded types are not indexed, but may still be searchable on a device 
+	as some devices seem to ignore most of the index, e.g. an Oregon 600 with 
+	firmware 5.00 only seems to use it for city search.
+	If you device finds a POI name like 'Planet' when you search for 'Net'
+	it doesn't use the index because the index created by mkgmap cannot help for 
+	that search. 
+	So, this option may help when you care about the size of the index or the
+	memory that is needed to calculate it.    
+	The option expects a comma separated list of types or type ranges. A range is 
+	given with from-type-to-type, e.g. 0x6400-0x6405. First and last type are both 
+	excluded.A range can span multiple types, e.g. 0x6400-0x661f.  
+	Examples for usage: 
+	- Assume your style adds a POI with type 0x2800 for each addr:housenumber.
+		It is not useful to index those numbers, so you can use 
+		--poi-excl-index=0x2800
+		to exclude this.
+	- For the mentioned Oregon you may use --poi-excl-index=0x2a00-0x661f
+		to reduce the index size.
+		
 --bounds=directory|zipfile
 	A directory or a zip file containing the preprocessed bounds files. 
 	Bounds files in a zip file must be located in the zip file's root directory.
@@ -248,6 +315,18 @@ Style options:
 	directory or zip file containing multiple styles. If --style-file 
 	is not used, it selects one of the built-in styles. 
 
+--style-option
+	Provide a semicolon separated list of tags which can be used in the style.
+	The intended use is to make a single style more flexible, e.g.
+	you may want to use a slightly different set of rules for a map of
+	a whole continent. The tags given will be prefixed with "mkgmap:option:".
+	If no value is provided the default "true" is used.  
+	This option allows to use rules like
+	mkgmap:option:light=true & landuse=farmland {remove landuse}
+	Example: -- style-option=light;routing=car
+	will add the tags mkgmap:option:light=true and mkgmap:option:routing=car
+	to each element before style processing happens. 
+
 --list-styles
 	List the available styles. If this option is preceded by a style-file
 	option then it lists the styles available within that file.
@@ -290,7 +369,7 @@ Product description options:
 	It is often just 1, which is the default.
 
 --product-version
-	The version of the product. Default value is 1.
+	The version of the product. Default value is 100 which means version 1.00.
 
 --series-name
 	This name will be displayed in MapSource in the map selection
@@ -306,10 +385,17 @@ Product description options:
 --copyright-file=file
 	Specify copyright messages from a file.
 	Note that the first copyright message is not displayed on a device, but is 
-	shown in BaseCamp. The copyright file must include at least two lines.
+	shown in BaseCamp. The copyright file must include at least two lines and
+	be UTF-8 encoded. The following symbols will be substituted by mkgmap:
+	$MKGMAP_VERSION$, $JAVA_VERSION$, $YEAR$, $LONG_DATE$, $SHORT_DATE$ and $TIME$.
+	Time and date substitutions use the local date and time formats.
 
 --license-file=file
-	Specify a file which content will be added as license. 
+	Specify a file which content will be added as license.
+	The license file must be UTF-8 encoded.
+	The following symbols will be substituted by mkgmap:
+	$MKGMAP_VERSION$, $JAVA_VERSION$, $YEAR$, $LONG_DATE$, $SHORT_DATE$ and $TIME$.
+	Time and date substitutions use the local date and time formats.
 	All entries of all maps will be merged in the overview map.
 
 Optimization options:
@@ -393,7 +479,8 @@ Miscellaneous options:
 	Check that roundabouts have the expected direction (clockwise
 	when vehicles drive on the left). Roundabouts that are complete
 	loops and have the wrong direction are reversed. Also checks
-	that the roundabouts do not fork or overlap other roundabouts.
+	that the roundabouts do not fork or overlap other roundabouts
+	and that no more than one connecting highway joins at each node.
 
 --check-roundabout-flares
 	Sanity check roundabout flare roads - warn if they don't point
@@ -590,8 +677,9 @@ Miscellaneous options:
 		the flood blocker
 
 --make-poi-index
-	Generate the POI index (not yet useful).
-
+	Generate a POI index in each map tile. Probably not used by modern devices,
+	but still supported.
+	
 --nsis
 	Write a .nsi file that can be used with the Nullsoft Scriptable Install System
 	(NSIS) to create a Windows Mapsource Installer.
@@ -667,6 +755,10 @@ Miscellaneous options:
 	TAG=VALUE or TAG=*. Blank lines and lines that start with
 	a # or ; are ignored. All tag/value pairs in the OSM input are
 	compared with these patterns and those that match are deleted.
+	
+--ignore-fixme-values
+	Tells mkgmap to ignore all tags for which the value matches the pattern
+	"(?i)fix[ _]?+me".	
 
 --tdbfile
 	Write files that are essential to running with MapSource, a .tdb file and
@@ -689,6 +781,11 @@ Miscellaneous options:
 	lower map too.  Useful for contour line maps among other
 	things.
 
+--custom
+	Write a different TRE header. With this option mkgmap writes the bytes
+	0x170401 instead of the default 0x110301 at offset 43. Useful for marine 
+	maps.
+	
 --hide-gmapsupp-on-pc
 	Set a bit in the gmapsupp.img that tells PC software that the file is
 	already installed on the PC and therefore there is no need to read it 
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index 4a2fc76..4263a63 100644
--- a/resources/mkgmap-version.properties
+++ b/resources/mkgmap-version.properties
@@ -1,2 +1,2 @@
-svn.version: 3741
-build.timestamp: 2016-12-26T11:25:19+0000
+svn.version: 3973
+build.timestamp: 2017-06-27T10:13:56+0100
diff --git a/resources/roadNameConfig.txt b/resources/roadNameConfig.txt
new file mode 100644
index 0000000..a201da4
--- /dev/null
+++ b/resources/roadNameConfig.txt
@@ -0,0 +1,88 @@
+# Sample configuration file for road name prefixes and suffixes for use 
+# with the --road-name-config option.
+# A road name like West Main Street is separated into a prefix 'West',
+# the important part 'Main', and the suffix 'Street'.  
+
+# The first section lists often used prefixes and suffixes for a language.
+# The next section is used to configure which languages are used for road names
+# in each country.  
+
+# Note that the order of the options doesn't matter, as well as the position 
+# in the comma separated lists. Very important is the proper usage of blanks
+# within the quotation marks as well as the spelling.
+
+##########################################################################
+# Section 1
+# prefix1:<lang> list of 1st words
+# prefix2:<lang> further words to combine with each prefix1 word, separated with a blank
+# suffix:<lang> gives list of suffix words
+
+# basque
+suffix:eu = " Kalea"
+
+# catalan
+prefix1:ca = "Avinguda", "Carrer"
+prefix2:ca = "de las ", "de los ", "de la ", "del ", "de ", "d'"
+
+# english
+prefix1:en = "East ", "North ", "South ", "West " 
+suffix:en = " Road", " Street"
+
+# french
+prefix1:fr = "Allée", "Avenue", "Boulevard", "Chemin", "Place", "Rue", "Route"
+prefix2:fr = "de la ", "du ", "de ", "des ", "d'", "de l'"
+
+# galician
+prefix1:gl = "Rua", "Avenida", "Travessa"
+prefix2:gl = "da ", "do ", "de ", "das ", "dos "
+
+# german
+suffix:de = " Straße", "-Straße", " Strasse", "-Strasse", " Weg", "-Weg"
+
+# italian
+prefix1:it = "Via", "Piazza", "Viale"
+prefix2:it = "del ", "dei ", "della ", "delle ", "di "
+
+# portugese
+prefix1:pt = "Rua", "Avenida", "Travessa"
+prefix2:pt = "da ", "do ", "de ", "das ", "dos "
+
+# spanish
+prefix1:es = "Avenida", "Calle", "Paseo"
+prefix2:es = "de las ", "de los ", "de la ", "del ", "de ", "d'", "las ", "los "
+
+##########################################################################
+# Section 2
+# Map three letter ISO country codes to list of used languages for road names.
+# It is assumed that the style sets mkgmap:country to one of these ISO codes.    
+
+lang:AND = es, ca
+lang:ARG = es
+lang:BOL = es
+lang:CAN = en, fr
+lang:CHE = de, fr, it
+lang:CHL = es
+lang:COL = es
+lang:CRI = es
+lang:CUB = es
+lang:DEU = de
+lang:DOM = es
+lang:ECU = es
+lang:ESH = es
+lang:ESP = es, gl, eu, ca
+lang:FRA = fr
+lang:GBR = en
+lang:GTM = es
+lang:GUY = en
+lang:HND = es
+lang:MEX = es
+lang:NIC = es
+lang:PAN = es
+lang:PER = es
+lang:PRI = es
+lang:PRT = pt
+lang:PRY = es
+lang:SLV = es
+lang:URI = es
+lang:USA = en
+lang:VEN = es
diff --git a/resources/sort/cp1252.txt b/resources/sort/cp1252.txt
index fdef894..2965d39 100644
--- a/resources/sort/cp1252.txt
+++ b/resources/sort/cp1252.txt
@@ -8,13 +8,13 @@ description "Western European 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
+=0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=007f=00ad,0001,0002,0003,0004,0005,0006,0007
  < 0009
  < 000a
  < 000b
  < 000c
  < 000d
- < 0020,00a0
+ < 0020,00a0,001e,001f
  < _
  < -
  < –
@@ -96,7 +96,7 @@ characters
  < 7
  < 8
  < 9
- < a,ª,A ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,Ã
+ < a,A,ª ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,à ; æ,Æ
  < b,B
  < c,C ; ç,Ç
  < d,D ; ð,Ð
@@ -111,11 +111,12 @@ characters
  < l,L
  < m,M
  < n,N ; ñ,Ñ
- < o,º,O ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø
+ < o,O,º ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø ; œ,Œ
  < p,P
  < q,Q
  < r,R
  < s,S ; š,Š
+ < ß
  < t,T
  < u,U ; ú,Ú ; ù,Ù ; û,Û ; ü,Ü
  < v,V
@@ -129,9 +130,4 @@ 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/cp1254.txt b/resources/sort/cp1254.txt
index 97ad517..6c9a3ac 100644
--- a/resources/sort/cp1254.txt
+++ b/resources/sort/cp1254.txt
@@ -92,7 +92,7 @@ characters
  < 7
  < 8
  < 9
- < a,ª,A ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,Ã
+ < a,A,ª ; á,Á ; à,À ; â, ; å,Å ; ä,Ä ; ã,à ; æ,Æ
  < b,B
  < c,C ; ç,Ç
  < d,D
@@ -108,7 +108,7 @@ characters
  < l,L
  < m,M
  < n,N ; ñ,Ñ
- < o,º,O ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø
+ < o,O,º ; ó,Ó ; ò,Ò ; ô,Ô ; ö,Ö ; õ,Õ ; ø,Ø ; œ,Œ
  < p,P
  < q,Q
  < r,R
@@ -125,9 +125,5 @@ 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/cp1258.txt b/resources/sort/cp1258.txt
index 3e770bc..955c02a 100644
--- a/resources/sort/cp1258.txt
+++ b/resources/sort/cp1258.txt
@@ -1,6 +1,6 @@
 codepage 1258
-id 18
-id 1
+id1 18
+id2 1
 description "Vietnamese sort"
 
 characters
diff --git a/resources/styles/default/inc/name b/resources/styles/default/inc/name
index e6a2a9a..419e03f 100644
--- a/resources/styles/default/inc/name
+++ b/resources/styles/default/inc/name
@@ -2,6 +2,7 @@
 # name, brand, operator, ref
 
 # delete FIXME values (they should be better used in maintenance maps)
+# better use option --ignore-fixme-values
 ref ~ '(?i)fix[ _]?+me'		{ delete ref; } 
 operator ~ '(?i)fix[ _]?+me'	{ delete operator; }
 brand ~ '(?i)fix[ _]?+me'		{ delete brand; }
diff --git a/resources/styles/default/lines b/resources/styles/default/lines
index bb534c4..7394099 100644
--- a/resources/styles/default/lines
+++ b/resources/styles/default/lines
@@ -9,8 +9,9 @@
 # for more information.
 
 addr:housenumber=* {set mkgmap:execute_finalize_rules=true}
-aeroway=runway [0x27 resolution 20]
-aeroway=taxiway [0x27 resolution 24]
+
+aeroway=runway & highway!=* & is_closed()=false {name '${ref}'} [0x27 resolution 20]
+(aeroway=taxiway | aeroway=taxilane) & highway!=* & is_closed()=false {name '${ref}'} [0x27 resolution 24]
 
 # Assign the street name for house number search
 highway=* & name=* { set mkgmap:street='${name}' }
@@ -18,6 +19,9 @@ highway=* & name=* { set mkgmap:street='${name}' }
 # Mark highways with the toll flag
 highway=* & (toll=yes|toll=true) { set mkgmap:toll=yes }
 
+# mark multipolygons as area
+highway=* & mkgmap:mp_created=true {add area=yes}
+
 # Hide proposed ways
 (highway=proposed | highway=proposal | highway=planned | highway ~ '.*proposed.*') {delete highway;delete junction}
 # Hide removed ways
@@ -43,9 +47,6 @@ highway=* & oneway=yes & (access=private|access=no)
 #highway=motorway_link & oneway!=yes & oneway!=no { echo "motorway_link lacks oneway" }
 highway=motorway|highway=motorway_link { add oneway=yes; add mkgmap:numbers=false }
 
-# Set highway names to include the reference if there is one
-highway=motorway { name '${ref|highway-symbol:hbox} ${name}' | '${ref|highway-symbol:hbox}' | '${name}' }
-
 # start of rules for process-exits and process-destination options
 # which may add info to a part of these highway=*_link roads:
 # motorway_link, trunk_link, primary_link, secondary_link, tertiary_link
@@ -67,28 +68,31 @@ mkgmap:exit_hint=true
 # use destination hint and/or exit hint to build name              
 (mkgmap:exit_hint=true | mkgmap:dest_hint=*)
   {	name '${exit_hint} ${dest_hint}' | 	'${dest_hint}' | 		'${exit_hint}' }
-# end of rules for process-exits and process-destination options	
-  
-highway=trunk {name '${ref|highway-symbol:hbox} ${name}' | '${ref|highway-symbol:hbox}' | '${name}'; addlabel '${name} (${ref})' }
-highway=primary {name '${ref|highway-symbol:box} ${name}' | '${ref|highway-symbol:box}' | '${name}'; addlabel '${name} (${ref})' }
-highway=secondary | highway=tertiary {name '${ref|highway-symbol:oval} ${name}' | '${ref|highway-symbol:oval}' | '${name}'; addlabel '${name} (${ref})' }
-highway=* {name '${name}' | '${ref}' }
+# end of rules for process-exits and process-destination options
+
+# Flag paved roads
+highway=* & (surface=asphalt | surface=paved | surface=sett | 
+    surface=concrete | surface=concrete:lanes | surface=concrete:plates | 
+    surface=paving_stones  | surface=cobblestone  | 
+   	surface=cobblestone:flattened  | surface=metal  | surface=wood) 
+{ set mkgmap:unpaved=0 }
+highway=* & tracktype=grade1 & surface!=* { set mkgmap:unpaved=0 }
 
 # Flag unpaved roads.
-highway=*
-& (surface=cobblestone | surface=compacted | surface=dirt |
-   surface=earth | surface=grass | surface=grass_paver |
-   surface=gravel | surface=grit | surface=ground | surface=mud |
-   surface=pebblestone | surface=sand | surface=unpaved |
-   mtb:scale=* |
-   tracktype ~ 'grade[2-6]' |
-   smoothness ~ '.*(bad|horrible|impassable)' |
-   sac_scale ~ '.*(mountain|alpine)_hiking' |
-   sport=via_ferrata)
-{ add mkgmap:unpaved=1 }
-(highway=bridleway | highway=path | highway=track | highway=unsurfaced)
-& surface!=* & tracktype!=* & smoothness!=* & sac_scale!=*
+highway=* & mkgmap:unpaved!=0 & (  
+    surface=* |
+	mtb:scale=* | 
+    tracktype ~ 'grade[2-6]')
 { add mkgmap:unpaved=1 }
+highway=* & (
+    mtb:scale ~ '[2-6].' |
+    sac_scale ~ '.*(mountain|alpine)_hiking' |
+    sport=via_ferrata)
+{ set mkgmap:unpaved=1 }
+(highway=bridleway | highway=path | highway=track) & mkgmap:unpaved!=0 { add mkgmap:unpaved=1 }
+(highway=unsurfaced | highway=via_ferrata) { set mkgmap:unpaved=1 }
+
+highway=* & mkgmap:unpaved!=1 & smoothness ~ '.*(bad|horrible|impassable)'  { add mkgmap:road-speed = '-2' } 
 
 # Convert generic path to most specific
 highway=footway & snowplowing!=no
@@ -106,12 +110,21 @@ leisure=track & area!=yes
 {add highway=footway; name '${ref} ${name}' | '${ref}' | '${name}' }
 
 # Roundabouts
-junction=roundabout & (highway=trunk | highway=trunk_link) [0x0c road_class=3 road_speed=2 resolution 18]
-junction=roundabout & (highway=primary | highway=primary_link) [0x0c road_class=3 road_speed=2 resolution 19]
-junction=roundabout & (highway=secondary | highway=secondary_link) [0x0c road_class=2 road_speed=2 resolution 20]
-junction=roundabout & (highway=tertiary | highway=tertiary_link) [0x0c road_class=1 road_speed=1 resolution 21]
-junction=roundabout & highway=unclassified [0x0c road_class=1 road_speed=1 resolution 21]
-junction=roundabout [0x0c road_class=0 road_speed=1 resolution 22]
+junction=roundabout & (highway=trunk | highway=trunk_link) [0x0c road_class=4 road_speed=2 resolution 24 continue]
+junction=roundabout & (highway=trunk | highway=trunk_link) [0x10801 resolution 18]
+
+junction=roundabout & (highway=primary | highway=primary_link) [0x0c road_class=3 road_speed=2 resolution 24 continue]
+junction=roundabout & (highway=primary | highway=primary_link) [0x10802 resolution 19]
+
+junction=roundabout & (highway=secondary | highway=secondary_link) [0x0c road_class=2 road_speed=2 resolution 24 continue]
+junction=roundabout & (highway=secondary | highway=secondary_link) [0x10803 resolution 20]
+
+junction=roundabout & (highway=tertiary | highway=tertiary_link) [0x0c road_class=1 road_speed=1 resolution 24 continue]
+junction=roundabout & (highway=tertiary | highway=tertiary_link) [0x10804 resolution 21]
+
+junction=roundabout & (highway=unclassified | highway=minor ) [0x0c road_class=1 road_speed=1 resolution 21]
+junction=roundabout & highway=* [0x0c road_class=0 road_speed=1 resolution 22]
+
 
 # Ways that may or may not be useable
 
@@ -207,15 +220,20 @@ include 'inc/roadspeed';
 # calculate the access rules
 include 'inc/access';
 
-#limit artificial cycleways to to resolution 24
+#limit artificial cycleways to resolution 24
 mkgmap:synthesised=yes & mkgmap:bicycle=yes { set mkgmap:highest-resolution-only = true }
 
 # don't add house numbers to unnamed or artifical bicycle ways
 mkgmap:bicycle=yes & (mkgmap:foot=no & mkgmap:car=no & mkgmap:street!=* | mkgmap:synthesised=yes) {set mkgmap:numbers=false}
 
+# Display highway shield for mayor roads if they have a ref and make them searchable by their name
+(highway=motorway | highway=trunk) & ref=* { name '${ref|highway-symbol:hbox}'; addlabel '${name}' }
+highway=primary & ref=* { name  '${ref|highway-symbol:box}'; addlabel '${name}' }
+(highway=secondary | highway=tertiary) & ref=* { name '${ref|highway-symbol:oval}'; addlabel '${name}' }
+
 name=* { name '${name}' }
+highway=* & ref=* & highway!=motorway & highway!=trunk & highway!=primary & highway!=secondary & highway!=tertiary { addlabel '${ref}' }
 
-highway=* & ref=* { addlabel '${ref}' }
 highway=* & int_ref=* { addlabel '${int_ref}' }
 highway=* & nat_ref=* { addlabel '${nat_ref}' }
 highway=* & reg_ref=* { addlabel '${reg_ref}' }
diff --git a/resources/styles/default/polygons b/resources/styles/default/polygons
index 4591fda..06245ec 100644
--- a/resources/styles/default/polygons
+++ b/resources/styles/default/polygons
@@ -17,6 +17,9 @@ include 'inc/name';
 
 aeroway=airport [0x07 resolution 20]
 aeroway=aerodrome [0x07 resolution 20]
+aeroway=runway {name '${ref}'} [0x0e resolution 20]
+(aeroway=taxiway | aeroway=taxilane) {name '${ref}'} [0x0e resolution 24]
+aeroway=heliport [0x07 resolution 20]
 aeroway=helipad [0x0e resolution 22]
 
 amenity=kindergarten [0x0a resolution 22]
@@ -55,9 +58,9 @@ place=islet & name=* [0x53 resolution 20]
 shop=* [0x08 resolution 22]
 
 # squares and plazas
-highway=pedestrian & area=yes [0x17 resolution 22]
+highway=pedestrian & (area=yes | mkgmap:mp_created=true) [0x17 resolution 22]
 # other highways that have area=yes set must be parking lots
-highway=* & area=yes [0x05 resolution 22]
+highway=* & (area=yes | mkgmap:mp_created=true) [0x05 resolution 22]
 
 historic=museum | historic=memorial [0x1e resolution 21]
 historic=archaeological_site | historic=ruins [0x1e resolution 21]
diff --git a/resources/styles/empty/info b/resources/styles/empty/info
new file mode 100644
index 0000000..0180500
--- /dev/null
+++ b/resources/styles/empty/info
@@ -0,0 +1,8 @@
+summary: Do nothing style
+
+version=1.0
+
+# A longer description of the style.
+description {
+Used when no style processing is needed.
+}
diff --git a/resources/styles/empty/version b/resources/styles/empty/version
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/resources/styles/empty/version
@@ -0,0 +1 @@
+0
diff --git a/src/uk/me/parabola/imgfmt/FileSystemParam.java b/src/uk/me/parabola/imgfmt/FileSystemParam.java
index e91d7f4..35816e7 100644
--- a/src/uk/me/parabola/imgfmt/FileSystemParam.java
+++ b/src/uk/me/parabola/imgfmt/FileSystemParam.java
@@ -26,6 +26,7 @@ public class FileSystemParam {
 	private int reservedDirectoryBlocks = 202;
 	private boolean gmapsupp;
 	private boolean hideGmapsuppOnPC;
+	private int productVersion = -1; // means unset 
 
 	public String getFilename() {
 		return filename;
@@ -84,4 +85,12 @@ public class FileSystemParam {
 	public void setHideGmapsuppOnPC(boolean hideGmapsuppOnPC) {
 		this.hideGmapsuppOnPC = hideGmapsuppOnPC;
 	}
+
+	public void setProductVersion(int version) {
+		this.productVersion = version;
+	}
+
+	public int getProductVersion() {
+		return productVersion;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/Utils.java b/src/uk/me/parabola/imgfmt/Utils.java
index b48ec4c..36c258a 100644
--- a/src/uk/me/parabola/imgfmt/Utils.java
+++ b/src/uk/me/parabola/imgfmt/Utils.java
@@ -377,11 +377,53 @@ public class Utils {
 	 * @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();
+		int latHp = co.getHighPrecLat();
+		int lonHp = co.getHighPrecLon();
 		
-		return (long)(lat30 & 0xffffffffL) << 32 | (lon30 & 0xffffffffL);
+		return (long)(latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL);
 	}
 	
+	/**
+	 * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa.
+	 * This is a form of intersection check where it is allowed that one line ends on the
+	 * other line or that the two lines overlap.
+	 * @param p1_1 first point of line 1
+	 * @param p1_2 second point of line 1
+	 * @param p2_1 first point of line 2
+	 * @param p2_2 second point of line 2
+	 * @return true if both lines intersect somewhere in the middle of each other
+	 */
+	public static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) {
+		int width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon();
+		int width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon();
+
+		int height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat();
+		int height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat();
+
+		int denominator = ((height2 * width1) - (width2 * height1));
+		if (denominator == 0) {
+			// the lines are parallel
+			// they might overlap but this is ok for this test
+			return false;
+		}
+		
+		int x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon();
+		int y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat();
+
+		double isx = (double)((width2 * y1My3) - (height2 * x1Mx3))
+				/ denominator;
+		if (isx <= 0 || isx >= 1) {
+			return false;
+		}
+		
+		double isy = (double)((width1 * y1My3) - (height1 * x1Mx3))
+				/ denominator;
+
+		if (isy <= 0 || isy >= 1) {
+			return false;
+		} 
+
+		return true;
+	}
 }
 
diff --git a/src/uk/me/parabola/imgfmt/app/Area.java b/src/uk/me/parabola/imgfmt/app/Area.java
index 99c4c5b..2c361a6 100644
--- a/src/uk/me/parabola/imgfmt/app/Area.java
+++ b/src/uk/me/parabola/imgfmt/app/Area.java
@@ -31,6 +31,7 @@ import uk.me.parabola.log.Logger;
  */
 public class Area {
 	private static final Logger log = Logger.getLogger(Area.class);
+	public final static Area PLANET = new Area(-90.0, -180.0, 90.0, 180.0);
 
 	private final int minLat;
 	private final int minLong;
@@ -190,12 +191,12 @@ public class Area {
 	 * @return true if co is inside the Area (it may touch the boundary)
 	 */
 	public final boolean contains(Coord co) {
-		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);
+		int latHp = co.getHighPrecLat();
+		int lonHp = co.getHighPrecLon();
+		return latHp  >= (minLat << Coord.DELTA_SHIFT)
+				&& latHp <= (maxLat << Coord.DELTA_SHIFT)
+				&& lonHp >= (minLong << Coord.DELTA_SHIFT)
+				&& lonHp <= (maxLong << Coord.DELTA_SHIFT);
 	}
 
 	/**
@@ -215,13 +216,13 @@ public class Area {
 	 * @return true if co is inside the Area and doesn't touch the boundary
 	 */
 	public final boolean insideBoundary(Coord co) {
-		int lat30 = co.getHighPrecLat();
-		int lon30 = co.getHighPrecLon();
+		int latHp = co.getHighPrecLat();
+		int lonHp = co.getHighPrecLon();
 		
-		return lat30  > (minLat << Coord.DELTA_SHIFT)
-				&& lat30 < (maxLat << Coord.DELTA_SHIFT)
-				&& lon30 > (minLong << Coord.DELTA_SHIFT)
-				&& lon30 < (maxLong << Coord.DELTA_SHIFT);
+		return latHp  > (minLat << Coord.DELTA_SHIFT)
+				&& latHp < (maxLat << Coord.DELTA_SHIFT)
+				&& lonHp > (minLong << Coord.DELTA_SHIFT)
+				&& lonHp < (maxLong << Coord.DELTA_SHIFT);
 	}
 	
 
@@ -315,4 +316,9 @@ public class Area {
 		coords.add(start);
 		return coords;
 	}
+
+	public Area intersect(Area other) {
+		return new Area(Math.max(minLat, other.minLat), Math.max(minLong, other.minLong),
+				Math.min(maxLat, other.maxLat), Math.min(maxLong, other.maxLong));
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java
index b49c477..a37941d 100644
--- a/src/uk/me/parabola/imgfmt/app/Coord.java
+++ b/src/uk/me/parabola/imgfmt/app/Coord.java
@@ -51,19 +51,21 @@ public class Coord implements Comparable<Coord> {
 	private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
 	private final static short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers
 	
-	public final static int HIGH_PREC_BITS = 30;
-	public final static int DELTA_SHIFT = 6;
+	private final static int HIGH_PREC_BITS = 30;
+	public final static int DELTA_SHIFT = HIGH_PREC_BITS - 24; 
+	private final static int MAX_DELTA = 1 << (DELTA_SHIFT - 2); // max delta abs value that is considered okay
+	private final static long FACTOR_HP = 1L << HIGH_PREC_BITS;
 	
-	public final static double R = 6378137.0; // Radius of earth as defined by WGS84
-	public final static double U = R * 2 * Math.PI; // circumference of earth (WGS84)
+	public final static double R = 6378137.0; // Radius of earth at equator as defined by WGS84
+	public final static double U = R * 2 * Math.PI; // circumference of earth at equator (WGS84)
+	public final static double MEAN_EARTH_RADIUS = 6371000; // earth is a flattened sphere
 	
 	private final int latitude;
 	private final int longitude;
 	private byte highwayCount; // number of highways that use this point
 	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 final byte latDelta; // delta to high precision latitude value 
+	private final byte lonDelta; // delta to high precision longitude value
 	private short approxDistanceToDisplayedCoord = -1;
 
 	/**
@@ -85,14 +87,14 @@ 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);
+		int latHighPrec = toHighPrec(latitude);
+		int lonHighPrec = toHighPrec(longitude);
+		this.latDelta = (byte) ((this.latitude << DELTA_SHIFT) - latHighPrec); 
+		this.lonDelta = (byte) ((this.longitude << DELTA_SHIFT) - lonHighPrec);
 
 		// verify math
-		assert (this.latitude << 6) - latDelta == lat30;
-		assert (this.longitude << 6) - lonDelta == lon30;
+		assert (this.latitude << DELTA_SHIFT) - latDelta == latHighPrec;
+		assert (this.longitude << DELTA_SHIFT) - lonDelta == lonHighPrec;
 	}
 	
 	private Coord (int lat, int lon, byte latDelta, byte lonDelta){
@@ -102,12 +104,18 @@ public class Coord implements Comparable<Coord> {
 		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);
+	/**
+	 * Constructor for high precision values.
+	 * @param latHighPrec latitude in high precision
+	 * @param lonHighPrec longitude in high precision
+	 * @return Coord instance
+	 */
+	public static Coord makeHighPrecCoord(int latHighPrec, int lonHighPrec){
+		int lat24 = (latHighPrec + (1 << (DELTA_SHIFT - 1))) >> DELTA_SHIFT;
+		int lon24 = (lonHighPrec + (1 << (DELTA_SHIFT - 1))) >> DELTA_SHIFT;
+		byte dLat = (byte) ((lat24 << DELTA_SHIFT) - latHighPrec);
+		byte dLon = (byte) ((lon24 << DELTA_SHIFT) - lonHighPrec);
+		return new Coord(lat24, lon24, dLat, dLon);
 	}
 	
 	/**
@@ -263,7 +271,7 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	/**
-	 * @param b true: Mark the coordinate as  via node of a restriction relation
+	 * @param b true: Mark the coordinate as via node of a restriction relation
 	 */
 	public void setViaNodeOfRestriction(boolean b) {
 		if (b) 
@@ -296,7 +304,7 @@ public class Coord implements Comparable<Coord> {
 	/** 
 	 * Get flag for {@link ShapeMergeFilter}
 	 * The value has no meaning outside of {@link ShapeMergeFilter}
-	 * @return  
+	 * @return flag value
 	 */
 	public boolean isPartOfShape2() {
 		return (flags & PART_OF_SHAPE2) != 0;
@@ -316,7 +324,7 @@ public class Coord implements Comparable<Coord> {
 	/** 
 	 * Get flag for {@link WrongAngleFixer}
 	 * The value has no meaning outside of {@link WrongAngleFixer}
-	 * @return  
+	 * @return flag value
 	 */
 	public boolean isEndOfWay() {
 		return (flags & END_OF_WAY) != 0;
@@ -454,10 +462,10 @@ public class Coord implements Comparable<Coord> {
 	 * Similar to code in JOSM
 	 */
 	public double distanceHaversine (Coord point){
-		double lat1 = int30ToRadians(getHighPrecLat());
-		double lat2 = int30ToRadians(point.getHighPrecLat());
-		double lon1 = int30ToRadians(getHighPrecLon());
-		double lon2 = int30ToRadians(point.getHighPrecLon());
+		double lat1 = hpToRadians(getHighPrecLat());
+		double lat2 = hpToRadians(point.getHighPrecLat());
+		double lon1 = hpToRadians(getHighPrecLon());
+		double lon2 = hpToRadians(point.getHighPrecLon());
 		double sinMidLat = Math.sin((lat1-lat2)/2);
 		double sinMidLon = Math.sin((lon1-lon2)/2);
 		double dRad = 2*Math.asin(Math.sqrt(sinMidLat*sinMidLat + Math.cos(lat1)*Math.cos(lat2)*sinMidLon*sinMidLon));
@@ -469,28 +477,28 @@ public class Coord implements Comparable<Coord> {
 	 * Distance to other point in metres following the shortest rhumb line.
 	 */
 	public double distanceOnRhumbLine(Coord point){
-		double lat1 = int30ToRadians(getHighPrecLat());
-		double lat2 = int30ToRadians(point.getHighPrecLat());
-		double lon1 = int30ToRadians(getHighPrecLon());
-		double lon2 = int30ToRadians(point.getHighPrecLon());
+		double lat1 = hpToRadians(getHighPrecLat());
+		double lat2 = hpToRadians(point.getHighPrecLat());
+		double lon1 = hpToRadians(getHighPrecLon());
+		double lon2 = hpToRadians(point.getHighPrecLon());
 		
-	    // see http://williams.best.vwh.net/avform.htm#Rhumb
+		// see http://williams.best.vwh.net/avform.htm#Rhumb
 
-	    double dLat = lat2 - lat1;
-	    double dLon = Math.abs(lon2 - lon1);
-	    // if dLon over 180° take shorter rhumb line across the anti-meridian:
-	    if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
+		double dLat = lat2 - lat1;
+		double dLon = Math.abs(lon2 - lon1);
+		// if dLon over 180° take shorter rhumb line across the anti-meridian:
+		if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
 
-	    // on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor'
-	    // q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it
-	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
-	    double q = Math.abs(deltaPhi) > 10e-12 ? dLat/deltaPhi : Math.cos(lat1);
+		// on Mercator projection, longitude distances shrink by latitude; q is the 'stretch factor'
+		// q becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it
+		double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
+		double q = Math.abs(deltaPhi) > 10e-12 ? dLat/deltaPhi : Math.cos(lat1);
 
-	    // distance is pythagoras on 'stretched' Mercator projection
-	    double distRad = Math.sqrt(dLat*dLat + q*q*dLon*dLon); // angular distance in radians
-	    double dist = distRad * R;
+		// distance is pythagoras on 'stretched' Mercator projection
+		double distRad = Math.sqrt(dLat*dLat + q*q*dLon*dLon); // angular distance in radians
+		double dist = distRad * R;
 
-	    return dist;
+		return dist;
 		
 	}
 
@@ -502,13 +510,13 @@ public class Coord implements Comparable<Coord> {
 	 * the rhumb line calculations. 
 	 */
 	public Coord makeBetweenPoint(Coord other, double fraction) {
-		int dLat30 = other.getHighPrecLat() - getHighPrecLat();
-		int dLon30 = other.getHighPrecLon() - getHighPrecLon();
-		if (dLon30 == 0 || Math.abs(dLat30) < 1000000 && Math.abs(dLon30) < 1000000 ){
+		int dlatHp = other.getHighPrecLat() - getHighPrecLat();
+		int dlonHp = other.getHighPrecLon() - getHighPrecLon();
+		if (dlonHp == 0 || Math.abs(dlatHp) < 1000000 && Math.abs(dlonHp) < 1000000 ){
 			// distances are rather small, we can use flat earth approximation
-			int lat30 = (int) (getHighPrecLat() + dLat30 * fraction);
-			int lon30 = (int) (getHighPrecLon() + dLon30 * fraction);
-			return makeHighPrecCoord(lat30, lon30);
+			int latHighPrec = (int) (getHighPrecLat() + dlatHp * fraction);
+			int lonHighPrec = (int) (getHighPrecLon() + dlonHp * fraction);
+			return makeHighPrecCoord(latHighPrec, lonHighPrec);
 		}
 		double brng = this.bearingToOnRhumbLine(other, true);
 		double dist = this.distance(other) * fraction;
@@ -532,10 +540,10 @@ public class Coord implements Comparable<Coord> {
 	 */
 	public double bearingToOnGreatCircle(Coord point, boolean needHighPrec) {
 		// use high precision values for this 
-		double lat1 = int30ToRadians(getHighPrecLat());
-		double lat2 = int30ToRadians(point.getHighPrecLat());
-		double lon1 = int30ToRadians(getHighPrecLon());
-		double lon2 = int30ToRadians(point.getHighPrecLon());
+		double lat1 = hpToRadians(getHighPrecLat());
+		double lat2 = hpToRadians(point.getHighPrecLat());
+		double lon1 = hpToRadians(getHighPrecLon());
+		double lon2 = hpToRadians(point.getHighPrecLon());
 
 		double dlon = lon2 - lon1;
 
@@ -553,19 +561,19 @@ public class Coord implements Comparable<Coord> {
 	 * @param needHighPrec set to true if you need a very high precision
 	 */
 	public double bearingToOnRhumbLine(Coord point, boolean needHighPrec){
-		double lat1 = int30ToRadians(this.getHighPrecLat());
-		double lat2 = int30ToRadians(point.getHighPrecLat());
-		double lon1 = int30ToRadians(this.getHighPrecLon());
-		double lon2 = int30ToRadians(point.getHighPrecLon());
+		double lat1 = hpToRadians(this.getHighPrecLat());
+		double lat2 = hpToRadians(point.getHighPrecLat());
+		double lon1 = hpToRadians(this.getHighPrecLon());
+		double lon2 = hpToRadians(point.getHighPrecLon());
 
 		double dLon = lon2-lon1;
-	    // if dLon over 180° take shorter rhumb line across the anti-meridian:
-	    if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
+		// if dLon over 180° take shorter rhumb line across the anti-meridian:
+		if (Math.abs(dLon) > Math.PI) dLon = dLon>0 ? -(2*Math.PI-dLon) : (2*Math.PI+dLon);
+
+		double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
 
-	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
-	    
-	    double brngRad = needHighPrec ? Math.atan2(dLon, deltaPhi) : Utils.atan2_approximation(dLon, deltaPhi);
-	    return brngRad * 180 / Math.PI;
+		double brngRad = needHighPrec ? Math.atan2(dLon, deltaPhi) : Utils.atan2_approximation(dLon, deltaPhi);
+		return brngRad * 180 / Math.PI;
 	}
 
 	
@@ -612,66 +620,69 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	/**
-	 * Convert latitude or longitude to 30 bits value.
+	 * Convert latitude or longitude to HIGH_PREC_BITS bits value.
 	 * This allows higher precision than the 24 bits
 	 * used in map units.
-	 * @param l The lat or long as decimal degrees.
-	 * @return An integer value with 30 bit precision.
-	 */
-	private static int toBit30(double l) {
-		double DELTA = 360.0D / (1 << 30) / 2; //Correct rounding
-		if (l > 0)
-			return (int) ((l + DELTA) * (1 << 30)/360);
-		return (int) ((l - DELTA) * (1 << 30)/360);
-		
+	 * @param degrees The latitude or longitude as decimal degrees.
+	 * @return An integer value with {@code HIGH_PREC_BITS} bit precision.
+	 */
+	private static int toHighPrec(double degrees) {
+		final double DELTA = 360.0D / FACTOR_HP / 2; // Correct rounding
+		double v = (degrees > 0) ? degrees + DELTA : degrees - DELTA;
+		return (int) (v * FACTOR_HP / 360);
 	}
 
-	/* Factor for conversion to radians using 30 bits
-	 * (Math.PI / 180) * (360.0 / (1 << 30)) 
+	/* Factor for conversion to radians using HIGH_PREC_BITS bits
+	 * (Math.PI / 180) * (360.0 / (1 << HIGH_PREC_BITS)) 
 	 */
-	final static double BIT30_RAD_FACTOR = 2 * Math.PI / (1 << 30);
+	final static double HIGH_PREC_RAD_FACTOR = 2 * Math.PI / FACTOR_HP;
 	
 	/**
 	 * Convert to radians using high precision 
-	 * @param val30 a longitude/latitude value with 30 bit precision
+	 * @param valHighPrec a longitude/latitude value with HIGH_PREC_BITS bit precision
 	 * @return an angle in radians.
 	 */
-	public static double int30ToRadians(int val30){
-		return BIT30_RAD_FACTOR * val30;
+	public static double hpToRadians(int valHighPrec){
+		return HIGH_PREC_RAD_FACTOR * valHighPrec;
 	}
 
 	/**
-	 * @return Latitude as signed 30 bit integer 
+	 * @return Latitude as signed HIGH_PREC_BITS bit integer 
 	 */
 	public int getHighPrecLat() {
-		return (latitude << 6) - latDelta;
+		return (latitude << DELTA_SHIFT) - latDelta;
 	}
 
 	/**
-	 * @return Longitude as signed 30 bit integer 
+	 * @return Longitude as signed HIGH_PREC_BITS bit integer 
 	 */
 	public int getHighPrecLon() {
-		return (longitude << 6) - lonDelta;
+		return (longitude << DELTA_SHIFT) - lonDelta;
 	}
 	
 	/**
 	 * @return latitude in degrees with highest avail. precision
 	 */
 	public double getLatDegrees(){
-		return (360.0D / (1 << 30)) * getHighPrecLat();
+		return (360.0D / FACTOR_HP) * getHighPrecLat();
 	}
 	
 	/**
 	 * @return longitude in degrees with highest avail. precision
 	 */
 	public double getLonDegrees(){
-		return (360.0D / (1 << 30)) * getHighPrecLon();
+		return (360.0D / FACTOR_HP) * getHighPrecLon();
 	}
 	
 	public Coord getDisplayedCoord(){
 		return new Coord(latitude,longitude);
 	}
 
+	/**
+	 * Check if the rounding to 24 bit resolution caused large error. If so, the point may be placed
+	 * at an alternative position. 
+	 * @return true if rounding error is large. 
+	 */
 	public boolean hasAlternativePos(){
 		if (getOnBoundary())
 			return false;
@@ -687,8 +698,8 @@ public class Coord implements Comparable<Coord> {
 		ArrayList<Coord> list = new ArrayList<>();
 		if (getOnBoundary())
 			return list; 
-		byte modLatDelta = 0;
-		byte modLonDelta = 0;
+		int modLatDelta = 0;
+		int modLonDelta = 0;
 		
 		int modLat = latitude;
 		int modLon = longitude;
@@ -700,19 +711,19 @@ public class Coord implements Comparable<Coord> {
 			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;
+		int latHighPrec = getHighPrecLat();
+		int lonHighPrec = getHighPrecLon();
+		modLatDelta = (modLat << DELTA_SHIFT) - latHighPrec;
+		modLonDelta = (modLon << DELTA_SHIFT) - lonHighPrec;
+		assert modLatDelta >= Byte.MIN_VALUE && modLatDelta <= Byte.MAX_VALUE;
+		assert modLonDelta >= Byte.MIN_VALUE && modLonDelta <= Byte.MAX_VALUE;
 		if (modLat != latitude){
 			if (modLon != longitude)
-				list.add(new Coord(modLat, modLon, modLatDelta, modLonDelta));
-			list.add(new Coord(modLat, longitude, modLatDelta, lonDelta));
+				list.add(new Coord(modLat, modLon, (byte)modLatDelta, (byte)modLonDelta));
+			list.add(new Coord(modLat, longitude, (byte)modLatDelta, lonDelta));
 		} 
 		if (modLon != longitude)
-			list.add(new Coord(latitude, modLon, latDelta, modLonDelta));
+			list.add(new Coord(latitude, modLon, latDelta, (byte)modLonDelta));
 		/* verify math
 		for(Coord co:list){
 			double d = distance(new Coord (co.getLatitude(),co.getLongitude()));
@@ -725,9 +736,9 @@ public class Coord implements Comparable<Coord> {
 	/**
 	 * @return approximate distance in cm 
 	 */
-	public short getDistToDisplayedPoint(){
-		if (approxDistanceToDisplayedCoord < 0){
-		  approxDistanceToDisplayedCoord = (short)Math.round(getDisplayedCoord().distance(this)*100);
+	public short getDistToDisplayedPoint() {
+		if (approxDistanceToDisplayedCoord < 0) {
+			approxDistanceToDisplayedCoord = (short) Math.round(getDisplayedCoord().distance(this) * 100);
 		}
 		return approxDistanceToDisplayedCoord;
 	}
@@ -740,33 +751,33 @@ public class Coord implements Comparable<Coord> {
 	 * @return a new Coord instance
 	 */
 	public Coord destOnRhumLine(double dist, double brng){
-	    double distRad = dist / R; // angular distance in radians
-		double lat1 = int30ToRadians(this.getHighPrecLat());
-		double lon1 = int30ToRadians(this.getHighPrecLon());
+		double distRad = dist / R; // angular distance in radians
+		double lat1 = hpToRadians(this.getHighPrecLat());
+		double lon1 = hpToRadians(this.getHighPrecLon());
+
+		double brngRad = Math.toRadians(brng);
 
-	    double brngRad = Math.toRadians(brng);
+		double deltaLat = distRad * Math.cos(brngRad);
 
-	    double deltaLat = distRad * Math.cos(brngRad);
+		double lat2 = lat1 + deltaLat;
+		// check for some daft bugger going past the pole, normalise latitude if so
+		if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2;
+		double lon2;
+		// catch special case: normalised value would be -8388608
+		if (this.getLongitude() == 8388608 && brng == 0)
+			lon2 = lon1;
+		else { 
+			double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
+			double q = Math.abs(deltaPhi) > 10e-12 ? deltaLat / deltaPhi : Math.cos(lat1); // E-W course becomes ill-conditioned with 0/0
 
-	    double lat2 = lat1 + deltaLat;
-	    // check for some daft bugger going past the pole, normalise latitude if so
-	    if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2;
-	    double lon2;
-	    // catch special case: normalised value would be -8388608  
-	    if (this.getLongitude() == 8388608 && brng == 0)
-	    	lon2 = lon1;
-	    else { 
-		    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
-		    double q = Math.abs(deltaPhi) > 10e-12 ? deltaLat / deltaPhi : Math.cos(lat1); // E-W course becomes ill-conditioned with 0/0
+			double deltaLon = distRad*Math.sin(brngRad)/q;
 
-		    double deltaLon = distRad*Math.sin(brngRad)/q;
+			lon2 = lon1 + deltaLon;
+
+			lon2 = (lon2 + 3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180º
+		}
 
-		    lon2 = lon1 + deltaLon;
-		    
-		    lon2 = (lon2 + 3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180º
-	    }
-	    
-	    return new Coord(Math.toDegrees(lat2), Math.toDegrees(lon2));
+		return new Coord(Math.toDegrees(lat2), Math.toDegrees(lon2));
 	}
 	
 	/**
@@ -774,7 +785,7 @@ public class Coord implements Comparable<Coord> {
 	 * defined by coords a and b.
 	 * @param a start point
 	 * @param b end point
-	 * @return perpendicular distance in m.  
+	 * @return perpendicular distance in m.
 	 */
 	public double distToLineSegment(Coord a, Coord b){
 		double ap = a.distance(this);
@@ -800,7 +811,7 @@ public class Coord implements Comparable<Coord> {
 	}
 
 	/**
-	 * Calculate distance to rhumb line segment a-b  
+	 * Calculate distance to rhumb line segment a-b.
 	 * @param a point a
 	 * @param b point b
 	 * @return distance in m
@@ -821,8 +832,8 @@ public class Coord implements Comparable<Coord> {
 			frac = 0; 
 		}
 		else {
-			// scale for longitude deltas by cosine of average latitude  
-			double scale = Math.cos(Coord.int30ToRadians((aLat + bLat + pLat) / 3) );
+			// scale for longitude deltas by cosine of average latitude
+			double scale = Math.cos(Coord.hpToRadians((aLat + bLat + pLat) / 3) );
 			double deltaLonAP = scale * (pLon - aLon);
 			deltaLon = scale * deltaLon;
 			if (deltaLon == 0 && deltaLat == 0)
@@ -842,4 +853,19 @@ public class Coord implements Comparable<Coord> {
 		return distance;
 	}
 	
+	/**
+	 * @return a new coordinate at the specified distance (metres) away along the specified bearing (degrees)
+	 * uses "Destination point given distance and bearing from start point" formula from 
+	 * http://www.movable-type.co.uk/scripts/latlong.html
+	 */
+	public Coord offset(double bearingInDegrees, double distanceInMetres) {
+		double bearing = Math.toRadians(bearingInDegrees);
+		double angularDistance = distanceInMetres / MEAN_EARTH_RADIUS;
+		double lat = Math.toRadians(getLatDegrees());
+		double lon = Math.toRadians(getLonDegrees());
+		double newLat = Math.asin(Math.sin(lat) * Math.cos(angularDistance) + Math.cos(lat) * Math.sin(angularDistance) * Math.cos(bearing));
+		double newLon = lon + Math.atan2(Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(lat), Math.cos(angularDistance) - Math.sin(lat) * Math.sin(newLat));
+		return new Coord(Math.toDegrees(newLat), Math.toDegrees(newLon));
+	}
+	
 }
diff --git a/src/uk/me/parabola/imgfmt/app/Label.java b/src/uk/me/parabola/imgfmt/app/Label.java
index a558384..1064970 100644
--- a/src/uk/me/parabola/imgfmt/app/Label.java
+++ b/src/uk/me/parabola/imgfmt/app/Label.java
@@ -18,8 +18,6 @@ package uk.me.parabola.imgfmt.app;
 
 import java.util.regex.Pattern;
 
-import uk.me.parabola.imgfmt.app.labelenc.EncodedText;
-
 /**
  * Labels are used for names of roads, points of interest etc.
  *
@@ -55,6 +53,9 @@ public class Label {
 		this.text = null;
 	}
 
+	/**
+	 * @return a value > 0 if this label is not empty. TODO: replace by isEmpty()
+	 */
 	public int getLength() {
 		if (text != null)
 			return text.length();
@@ -81,6 +82,9 @@ public class Label {
 	// two or more whitespace characters
 	private final static Pattern SQUASH_SPACES = Pattern.compile("\\s\\s+");
 
+	// DEL character 
+	public final static Pattern SQASH_DEL = Pattern.compile("[\u007f]");
+
 	public static String stripGarminCodes(String s) {
 		if(s == null)
 			return null;
@@ -97,6 +101,12 @@ public class Label {
 		return SQUASH_SPACES.matcher(s).replaceAll(" "); // replace with single space
 	}
 
+	public static String squashDel(String s) {
+		if(s == null || s.isEmpty())
+			return null;
+		return SQASH_DEL.matcher(s).replaceAll(""); // remove 0x7f=DEL character
+	}
+
 	/**
 	 * The offset of this label in the LBL file.  The first byte of this file
 	 * is zero and an offset of zero means that the label has a zero length/is
@@ -113,19 +123,6 @@ public class Label {
 	}
 
 	/**
-	 * Write this label to the given img file.
-	 *
-	 * @param writer The LBL file to write to.
-	 * @param encText The encoded version of the text for this label.
-	 */
-	public void write(ImgFileWriter writer, EncodedText encText) {
-		assert encText != null;
-
-		if (encText.getLength() > 0)
-			writer.put(encText.getCtext(), 0, encText.getLength());
-	}
-
-	/**
 	 * String version of the label, for diagnostic purposes.
 	 */
 	public String toString() {
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java b/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
index 79aa19e..33ed089 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/AnyCharsetEncoder.java
@@ -105,8 +105,9 @@ 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);
-		char[] cres = new char[res.length];
-		for (int i = 0; i < res.length; i++)
+		// no trailing zero in char array
+		char[] cres = new char[outBuf.position()];
+		for (int i = 0; i < outBuf.position(); i++)
 			cres[i] = (char) (res[i] & 0xff);
 		return new EncodedText(res, res.length, cres);
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java b/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
index a47ac67..74e7459 100644
--- a/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
+++ b/src/uk/me/parabola/imgfmt/app/labelenc/EncodedText.java
@@ -36,6 +36,7 @@ public class EncodedText {
 		this.length = len;
 		this.chars = chars;
 
+		assert chars == null || chars.length == 0 || chars[chars.length - 1] != 0 : "found trailing 0 in chars";
 		int hc = 0;
 		for (int i = 0; i < length; i++)
 			hc = 31*hc + ctext[i];
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
index 9e41aaa..b4bc0c0 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/LBLFile.java
@@ -137,7 +137,8 @@ public class LBLFile extends ImgFile {
 			labelCache.put(encodedText, l);
 
 			l.setOffset(getNextLabelOffset());
-			l.write(getWriter(), encodedText);
+			if (encodedText.getLength() > 0)
+				getWriter().put(encodedText.getCtext(), 0, encodedText.getLength());
 
 			alignForNext();
 
@@ -175,8 +176,8 @@ public class LBLFile extends ImgFile {
 		return places.createExitPOI(name, exit);
 	}
 
-	public POIIndex createPOIIndex(String name, int poiIndex, Subdivision group, int type) {
-		return places.createPOIIndex(name, poiIndex, group, type);
+	public void createPOIIndex(String name, int poiIndex, Subdivision group, int type) {
+		places.createPOIIndex(name, poiIndex, group, type);
 	}
 	
 	public Country createCountry(String name, String abbr) {
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java b/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
index 8f49081..4c0fac5 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/PlacesFile.java
@@ -37,6 +37,8 @@ import uk.me.parabola.imgfmt.app.trergn.Subdivision;
  */
 @SuppressWarnings({"unchecked", "rawtypes"})
 public class PlacesFile {
+	public static final int MIN_INDEXED_POI_TYPE = 0x29;
+	public static final int MAX_INDEXED_POI_TYPE = 0x30;
 	private final Map<String, Country> countries = new LinkedHashMap<>();
 	private final List<Country> countryList = new ArrayList<>();
 
@@ -297,14 +299,16 @@ public class PlacesFile {
 		return p;
 	}
 
-	POIIndex createPOIIndex(String name, int index, Subdivision group, int type) {
+	void createPOIIndex(String name, int index, Subdivision group, int type) {
 		assert index < 0x100 : "Too many POIS in division";
-		POIIndex pi = new POIIndex(name, (byte)index, group, (byte)type);
 		int t = type >> 8;
+		if (t < MIN_INDEXED_POI_TYPE || t > MAX_INDEXED_POI_TYPE) 
+			return;
+		
+		POIIndex pi = new POIIndex(name, (byte)index, group, (byte)type);
 		if(poiIndex[t] == null)
 			poiIndex[t] = new ArrayList<POIIndex>();
 		poiIndex[t].add(pi);
-		return pi;
 	}
 
 	void allPOIsDone() {
diff --git a/src/uk/me/parabola/imgfmt/app/map/MapReader.java b/src/uk/me/parabola/imgfmt/app/map/MapReader.java
index 1d02045..7bc1d7e 100644
--- a/src/uk/me/parabola/imgfmt/app/map/MapReader.java
+++ b/src/uk/me/parabola/imgfmt/app/map/MapReader.java
@@ -147,12 +147,12 @@ public class MapReader implements Closeable {
 	}
 
 
-	public List<Polygon> shapesForLevel(int level) {
+	public List<Polygon> shapesForLevel(int level, boolean witExtTypeData) {
 		ArrayList<Polygon> shapes = new ArrayList<Polygon>();
 
 		Subdivision[] subdivisions = treFile.subdivForLevel(level);
 		for (Subdivision div : subdivisions) {
-			List<Polygon> subdivShapes = rgnFile.shapesForSubdiv(div);
+			List<Polygon> subdivShapes = rgnFile.shapesForSubdiv(div, witExtTypeData);
 			shapes.addAll(subdivShapes);
 		}
 
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/LargeListSorter.java b/src/uk/me/parabola/imgfmt/app/mdr/LargeListSorter.java
new file mode 100644
index 0000000..1613d3c
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/mdr/LargeListSorter.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.mdr;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import uk.me.parabola.imgfmt.app.srt.Sort;
+import uk.me.parabola.imgfmt.app.srt.SortKey;
+
+/**
+ * Helper class to perform sort on possibly large lists using sort keys.
+ * The list are divided into chunks so that the peak memory usage is reduced.
+ * @author Gerd Petermann
+ *
+ * @param <T>
+ */
+public abstract class LargeListSorter<T extends NamedRecord> {
+	private final Sort sort;
+	
+	public LargeListSorter(Sort sort) {
+		this.sort = sort;
+	}
+
+	/**
+	 * Sort list in place.
+	 * @param list list of records.
+	 */
+	public void sort(List<T> list) {
+		mergeSort(0, list, 0, list.size());
+	}
+	
+	/**
+	 * A merge sort implementation which sorts large chunks using a cache for the keys 
+	 * @param depth recursion depth
+	 * @param list list to sort
+	 * @param start position of first element in list 
+	 * @param len number of elements in list 
+	 */
+	private void mergeSort(int depth, List<T> list, int start, int len) {
+		// we split if the number is very high and recursion is not too deep
+		if (len > 1_000_000 && depth < 3) {
+			mergeSort(depth+1,list, start, len / 2); // left
+			mergeSort(depth+1,list, start + len / 2, len - len / 2); // right
+			merge(list,start,len);
+		} else {
+			// sort one chunk
+//			System.out.println("sorting list of roads. positions " + start + " to " + (start + len - 1));
+			Map<String, byte[]> cache = new HashMap<>();
+			List<SortKey<T>> keys = new ArrayList<>(len);
+
+			for (int i = start; i < start + len; i++) {
+				keys.add(makeKey(list.get(i), sort, cache));
+			}
+			cache = null;
+			Collections.sort(keys);
+			
+			for (int i = 0; i < keys.size(); i++){ 
+				SortKey<T> sk = keys.get(i);
+				T r = sk.getObject();
+				list.set(start+i, r);
+			}
+			return;
+		}
+	}
+	
+	
+	private void merge(List<T> list, int start, int len) {
+//		System.out.println("merging positions " + start + " to " + (start + len - 1));
+		int pos1 = start;
+		int pos2 = start + len / 2;
+		int stop1 = start + len / 2;
+		int stop2 = start + len;
+		boolean fetch1 = true;
+		boolean fetch2 = true;
+		List<T> merged = new ArrayList<>();
+		SortKey<T> sk1 = null;
+		SortKey<T> sk2 = null;
+		while (pos1 < stop1 &&  pos2 < stop2) {
+			if (fetch1 && pos1 < stop1) {
+				sk1 = makeKey(list.get(pos1), sort, null);
+				fetch1 = false;
+			}
+			if (fetch2 && pos2 < stop2) {
+				sk2 = makeKey(list.get(pos2), sort, null);
+				fetch2 = false;
+			}
+			int d = sk1.compareTo(sk2);
+			if (d <= 0) {
+				merged.add(sk1.getObject());
+				fetch1 = true;
+				pos1++;
+			} else {
+				merged.add(sk2.getObject());
+				fetch2 = true;
+				pos2++;
+			}
+		}
+		while (pos1 < stop1) {
+			merged.add(list.get(pos1++));
+		}
+		while (pos2 < stop2) {
+			merged.add(list.get(pos2++));
+		}
+		assert merged.size() == len;
+		for (int i = 0; i < len; i++) {
+			list.set(start+i, merged.get(i));
+		}
+	}
+
+	protected abstract SortKey<T>  makeKey(T record, Sort sort, Map<String, byte[]> cache);
+}
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java b/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
index b77475e..4c3693a 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
@@ -13,6 +13,7 @@
 package uk.me.parabola.imgfmt.app.mdr;
 
 import java.util.Arrays;
+import java.util.Set;
 
 import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
 import uk.me.parabola.imgfmt.app.FileBackedImgFileWriter;
@@ -43,7 +44,7 @@ public class MDRFile extends ImgFile {
 	private final Mdr5 mdr5;
 	private final Mdr6 mdr6;
 	private final Mdr7 mdr7;
-	private final Mdr8 mdr8;
+	private final Mdr8 mdr8; // unused
 	private final Mdr9 mdr9;
 	private final Mdr10 mdr10;
 	private final Mdr11 mdr11;
@@ -68,15 +69,21 @@ public class MDRFile extends ImgFile {
 	private int currentMap;
 
 	private final boolean forDevice;
+	private final boolean isMulti;
 
 	private final MdrSection[] sections;
 	private PointerSizes sizes;
+	private Set<String> mdr7Del;
+
+	private Set<Integer> poiExclTypes; 
 
 	public MDRFile(ImgChannel chan, MdrConfig config) {
 		Sort sort = config.getSort();
 
 		forDevice = config.isForDevice();
-
+		isMulti = config.getSort().isMulti();
+		mdr7Del = config.getMdr7Del();
+		poiExclTypes = config.getPoiExclTypes();
 		mdrHeader = new MDRHeader(config.getHeaderLen());
 		mdrHeader.setSort(sort);
 		setHeader(mdrHeader);
@@ -198,7 +205,11 @@ public class MDRFile extends ImgFile {
 		int fullType = point.getType();
 		if (!MdrUtils.canBeIndexed(fullType))
 			return;
-
+		if (!poiExclTypes.isEmpty()) {
+			int t = (fullType < 0xff)  ? fullType << 8 : fullType;
+			if (poiExclTypes.contains(t))
+				return;
+		}
 		Label label = point.getLabel();
 		String name = label.getText();
 		int strOff = createString(name);
@@ -208,7 +219,7 @@ public class MDRFile extends ImgFile {
 		poi.setIsCity(isCity);
 		poi.setType(fullType);
 
-		mdr4.addType(point.getType());
+		mdr4.addType(fullType);
 	}
 
 	public void addStreet(RoadDef street, Mdr5Record mdrCity) {
@@ -221,8 +232,29 @@ public class MDRFile extends ImgFile {
 				continue;
 			
 			String name = lab.getText();
-			String cleanName = cleanUpName(name);
-			int strOff = createString(cleanName);
+			if (!mdr7Del.isEmpty()) {
+				String[] parts = name.split(" ");
+				int pos = parts.length;
+				for (int i = parts.length - 1; i >= 0; i--) {
+					if (!mdr7Del.contains(parts[i])) {
+						break;
+					}
+					pos = i;
+				}
+				if (pos == 0)
+					continue;
+				if (pos < parts.length) {
+					StringBuilder sb = new StringBuilder();
+					for (int i = 0; i + 1 < pos; i++) {
+						sb.append(parts[i]);
+						sb.append(" ");
+					}
+					sb.append(parts[pos - 1]);
+					name = sb.toString(); // XXX maybe add -intern()
+				}
+			}
+			
+			int strOff = createString(name);
 
 			// We sort on the dirty name (ie with the Garmin shield codes) although those codes do not
 			// affect the sort order. The string for mdr15 does not include the shield codes.
@@ -230,16 +262,6 @@ public class MDRFile extends ImgFile {
 		}
 	}
 
-	/**
-	 * Remove shields and other kinds of strange characters.  Perform any
-	 * rearrangement of the name to make it searchable.
-	 * @param name The street name as read from the img file.
-	 * @return The name as it will go into the index.
-	 */
-	private String cleanUpName(String name) {
-		return Label.stripGarminCodes(name);
-	}
-
 	public void write() {
 		mdr15.release();
 		
@@ -266,6 +288,7 @@ public class MDRFile extends ImgFile {
 	private void writeSections(ImgFileWriter writer) {
 		sizes = new MdrMapSection.PointerSizes(sections);
 
+		mdr7.trim();
 		// Deal with the dependencies between the sections. The order of the following
 		// statements is sometimes important.
 		mdr28.buildFromRegions(mdr13.getRegions());
@@ -287,7 +310,9 @@ public class MDRFile extends ImgFile {
 		mdr10.setNumberOfPois(mdr11.getNumberOfPois());
 		mdr12.setIndex(mdr11.getIndex());
 		mdr19.setPois(mdr11.getPois());
-		mdr17.addPois(mdr11.getPois());
+		if (forDevice & !isMulti) {
+			mdr17.addPois(mdr11.getPois());
+		}
 		mdr11.release();
 
 		if (forDevice) {
@@ -296,6 +321,8 @@ public class MDRFile extends ImgFile {
 			mdr18.setPoiTypes(mdr19.getPoiTypes());
 			mdr19.release();
 			writeSection(writer, 18, mdr18);
+		} else  {
+			mdr19.release();
 		}
 
 		writeSection(writer, 10, mdr10);
@@ -310,7 +337,9 @@ public class MDRFile extends ImgFile {
 		writeSection(writer, 5, mdr5);
 		mdr25.sortCities(mdr5.getCities());
 		mdr27.sortCities(mdr5.getCities());
-		mdr17.addCities(mdr5.getSortedCities());
+		if (forDevice & !isMulti) {
+			mdr17.addCities(mdr5.getSortedCities());
+		}
 		mdr5.release();
 		writeSection(writer, 6, mdr6);
 
@@ -322,12 +351,15 @@ public class MDRFile extends ImgFile {
 		mdr21.release();
 		
 		mdr22.buildFromStreets(mdr7.getStreets());
-		mdr8.setIndex(mdr7.getIndex());
-		mdr17.addStreets(mdr7.getSortedStreets());
+		if (forDevice & !isMulti) {
+			mdr17.addStreets(mdr7.getSortedStreets());
+		}
 
 		mdr7.release();
 		writeSection(writer, 22, mdr22);
-		mdr17.addStreetsByCountry(mdr22.getStreets());
+		if (forDevice & !isMulti) {
+			mdr17.addStreetsByCountry(mdr22.getStreets());
+		}
 		mdr22.release();
 
 		if (forDevice) {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
index 8e3c339..d5ddd7b 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr10.java
@@ -32,7 +32,7 @@ import uk.me.parabola.imgfmt.app.ImgFileWriter;
  */
 public class Mdr10 extends MdrMapSection {
 	// The maximum group number.  Note that this is 1 based, not 0 based.
-	private static final int MAX_GROUP_NUMBER = 9;
+	private static final int MAX_GROUP_NUMBER = MdrUtils.MAX_GROUP;
 
 	@SuppressWarnings({"unchecked"})
 	private List<Mdr10Record>[] poiTypes = new ArrayList[MAX_GROUP_NUMBER+1];
@@ -50,13 +50,17 @@ public class Mdr10 extends MdrMapSection {
 	public void addPoiType(Mdr11Record poi) {
 		Mdr10Record t = new Mdr10Record();
 
-		int type = poi.getType();
-		t.setSubtype(MdrUtils.getSubtypeOrTypeFromFullType(type));
-		t.setMdr11ref(poi);
+		int fullType = poi.getType();
 
-		int group = MdrUtils.getGroupForPoi(type);
+		int group = MdrUtils.getGroupForPoi(fullType);
 		if (group == 0)
 			return;
+		if (group == 1)
+			t.setSubtype(fullType);
+		else {
+			t.setSubtype(MdrUtils.getSubtypeFromFullType(fullType));
+		}
+		t.setMdr11ref(poi);
 		poiTypes[group].add(t);
 	}
 
@@ -100,7 +104,7 @@ public class Mdr10 extends MdrMapSection {
 	public Map<Integer, Integer> getGroupSizes() {
 		Map<Integer, Integer> m = new LinkedHashMap<>();
 
-		for (int i = 1; i < MAX_GROUP_NUMBER; i++) {
+		for (int i = 1; i <= MAX_GROUP_NUMBER; i++) {
 			List<Mdr10Record> poiGroup = poiTypes[i];
 			if (!poiGroup.isEmpty())
 				m.put(i, poiGroup.size());
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
index c427798..37eaa08 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr11.java
@@ -14,9 +14,12 @@
 package uk.me.parabola.imgfmt.app.mdr;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
+import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.app.srt.SortKey;
 import uk.me.parabola.imgfmt.app.trergn.Point;
 
@@ -27,7 +30,7 @@ import uk.me.parabola.imgfmt.app.trergn.Point;
  * @author Steve Ratcliffe
  */
 public class Mdr11 extends MdrMapSection {
-	private List<Mdr11Record> pois = new ArrayList<>();
+	private ArrayList<Mdr11Record> pois = new ArrayList<>();
 	private Mdr10 mdr10;
 
 	public Mdr11(MdrConfig config) {
@@ -54,14 +57,20 @@ public class Mdr11 extends MdrMapSection {
 	 * de-duplicated in the index in the same way that streets and cities are.
 	 */
 	protected void preWriteImpl() {
-		List<SortKey<Mdr11Record>> keys = MdrUtils.sortList(getConfig().getSort(), pois);
-
-		pois.clear();
-		for (SortKey<Mdr11Record> sk : keys) {
-			Mdr11Record poi = sk.getObject();
-
+		pois.trimToSize();
+		Sort sort = getConfig().getSort();
+
+		LargeListSorter<Mdr11Record> sorter = new LargeListSorter<Mdr11Record>(sort) {
+			
+			@Override
+			protected SortKey<Mdr11Record> makeKey(Mdr11Record r, Sort sort, Map<String, byte[]> cache) {
+				return sort.createSortKey(r, r.getName(), r.getMapIndex(), cache);
+			}
+		};
+//		System.out.println("sorting " + pois.size() + " pois by name"); 
+		sorter.sort(pois);
+		for (Mdr11Record poi : pois) {
 			mdr10.addPoiType(poi);
-			pois.add(poi);
 		}
 	}
 
@@ -110,8 +119,10 @@ public class Mdr11 extends MdrMapSection {
 		if (citySize > 2)
 			mdr11flags |= (citySize-2) << 2;
 
-		if (isForDevice()) 
-			mdr11flags |= 0x80;
+		if (isForDevice()) {
+			if (!getConfig().getSort().isMulti())
+				mdr11flags |= 0x80; // mdr17 sub section present (not with unicode)
+		}
 		else 
 			mdr11flags |= 0x2;
 		
@@ -121,13 +132,13 @@ public class Mdr11 extends MdrMapSection {
 	public List<Mdr8Record> getIndex() {
 		List<Mdr8Record> list = new ArrayList<>();
 		for (int number = 1; number <= pois.size(); number += 10240) {
-			String prefix = getPrefixForRecord(number);
+			char[] prefix = getPrefixForRecord(number);
 
 			// need to step back to find the first...
 			int rec = number;
 			while (rec > 1) {
-				String p = getPrefixForRecord(rec);
-				if (!p.equals(prefix)) {
+				char[] p = getPrefixForRecord(rec);
+				if (!Arrays.equals(p, prefix)) {
 					rec++;
 					break;
 				}
@@ -145,20 +156,12 @@ public class Mdr11 extends MdrMapSection {
 	/**
 	 * Get the prefix of the name at the given record.
 	 * @param number The record number.
-	 * @return The first 4 (or whatever value is set) characters of the street
-	 * name.
+	 * @return The first 4 (or whatever value is set) characters of the POI name.
 	 */
-	private String getPrefixForRecord(int number) {
+	private char[] getPrefixForRecord(int number) {
 		Mdr11Record record = pois.get(number-1);
-		int endIndex = MdrUtils.POI_INDEX_PREFIX_LEN;
 		String name = record.getName();
-		if (endIndex > name.length()) {
-			StringBuilder sb = new StringBuilder(name);
-			while (sb.length() < endIndex)
-				sb.append('\0');
-			name = sb.toString();
-		}
-		return name.substring(0, endIndex);
+		return getConfig().getSort().getPrefix(name, MdrUtils.POI_INDEX_PREFIX_LEN);
 	}
 
 	public void setMdr10(Mdr10 mdr10) {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr19.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr19.java
index 46df5dc..b134c7b 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr19.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr19.java
@@ -122,7 +122,7 @@ public class Mdr19 extends MdrSection implements HasHeaderFlags {
 	 * @return The correct value based on the contents of the section.  Zero if nothing needs to be done.
 	 */
 	public int getExtraValue() {
-		return getSizes().getSize(19) - 1;
+		return getItemSize() - 1;
 	}
 
 	public void setPois(List<Mdr11Record> pois) {
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
index a2096ca..cb8da2f 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr20.java
@@ -16,13 +16,8 @@ package uk.me.parabola.imgfmt.app.mdr;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
-
-import uk.me.parabola.imgfmt.app.srt.MultiSortKey;
-import uk.me.parabola.imgfmt.app.srt.Sort;
-import uk.me.parabola.imgfmt.app.srt.SortKey;
 
 /**
  * This is a list of streets that belong to each city.
@@ -50,71 +45,36 @@ public class Mdr20 extends Mdr2x {
 	 * Also have to set the record number of the first record in this section
 	 * on the city.
 	 *
-	 * @param inStreets The list of streets from mdr7, must have Mdr7.index set.
+	 * @param inStreets The list of streets from mdr7, must have Mdr7.index set 
+	 * @param list 
 	 */
 	public void buildFromStreets(List<Mdr7Record> inStreets) {
-		Sort sort = getConfig().getSort();
-
-		// Use a key cache because there are a large number of street names but a much smaller number
-		// of city, region and country names. Therefore we can reuse the memory needed for the keys
-		// most of the time, particularly for the country and region names.
-		Map<String, byte[]> cache = new HashMap<>();
-
-		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
-		for (Mdr7Record s : inStreets) {
-			Mdr5Record city = s.getCity();
-			if (city == null)
-				continue;
-
-			String name = city.getName();
-			if (name == null || name.isEmpty())
-				assert false;
-
-			// We are sorting the streets, but we are sorting primarily on the
-			// city name associated with the street, then on the street name.
-			SortKey<Mdr7Record> cityKey = sort.createSortKey(s, city.getName(), 0, cache);
-			SortKey<Mdr7Record> regionKey = sort.createSortKey(null, city.getRegionName(), 0, cache);
-			// The streets are already sorted, with the getIndex() method revealing the sort order
-			SortKey<Mdr7Record> countryStreetKey = sort.createSortKey(null, city.getCountryName(), s.getIndex(),
-					cache);
-
-			// Combine all together so we can sort on it.
-			SortKey<Mdr7Record> key = new MultiSortKey<>(cityKey, regionKey, countryStreetKey);
-
-			keys.add(key);
-		}
-		Collections.sort(keys);
+		ArrayList<Mdr7Record> sorted = new ArrayList<>(inStreets);
+		Collections.sort(sorted, new Comparator<Mdr7Record>() {
+			public int compare(Mdr7Record o1, Mdr7Record o2) {
+				int d = Integer.compare(o1.getCity().getMdr20SortPos(), o2.getCity().getMdr20SortPos());
+				if (d != 0)
+					return d;
+				return Integer.compare(o1.getIndex(), o2.getIndex());
+			}
+		});
 
 		Collator collator = getConfig().getSort().getCollator();
-
-		String lastName = null;
-		String lastPartialName = null;
+		collator.setStrength(Collator.SECONDARY);
+		
 		Mdr5Record lastCity = null;
+		Mdr7Record lastStreet = null;
 		int record = 0;
 		int cityRecord = 1;
-		int lastMapNumber = 0;
-
-		for (SortKey<Mdr7Record> key : keys) {
-			Mdr7Record street = key.getObject();
-
-			String name = street.getName();
-			String partialName = street.getPartialName();
+		
+		for (Mdr7Record street : sorted) {
 			Mdr5Record city = street.getCity();
-
-			boolean citySameByName = city.isSameByName(collator, lastCity);
-
-			int mapNumber = city.getMapIndex();
-
+			boolean citySameByName = lastCity != null && city.getMdr20SortPos() == lastCity.getMdr20SortPos();
+			int rr = street.checkRepeat(lastStreet, collator);
 			// Only save a single copy of each street name.
-			if (!citySameByName || mapNumber != lastMapNumber || lastName == null || lastPartialName == null
-					|| !name.equals(lastName)
-					|| !partialName.equals(lastPartialName))
-			{
+			if (!citySameByName || rr != 3) {
 				record++;
-
 				streets.add(street);
-				lastName = name;
-				lastPartialName = partialName;
 			}
 
 			// The mdr20 value changes for each new city name
@@ -128,7 +88,7 @@ public class Mdr20 extends Mdr2x {
 				city.setMdr20(cityRecord);
 				lastCity = city;
 			}
-			lastMapNumber = mapNumber;
+			lastStreet = street;
 		}
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
index 14dfd60..6492856 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr21.java
@@ -14,18 +14,15 @@ package uk.me.parabola.imgfmt.app.mdr;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
-
-import uk.me.parabola.imgfmt.app.srt.Sort;
-import uk.me.parabola.imgfmt.app.srt.SortKey;
 
 /**
  * This section contains the streets sorted by region.
  * There is no pointer from region, unlike in the case with cities.
  * 
  * @author Steve Ratcliffe
+ * @author Gerd Petermann
  */
 public class Mdr21 extends Mdr2x {
 
@@ -40,36 +37,25 @@ public class Mdr21 extends Mdr2x {
 	 * @param inStreets The list of streets from mdr7.
 	 */
 	public void buildFromStreets(List<Mdr7Record> inStreets) {
-		Sort sort = getConfig().getSort();
-
-		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
-		Map<String, byte[]> cache = new HashMap<>();
-
-		for (Mdr7Record s : inStreets) {
-			Mdr5Record city = s.getCity();
-			if (city == null) continue;
-
-			Mdr13Record region = city.getMdrRegion();
-			if (region == null) continue;
-
-			String name = region.getName();
-			if (name == null)
-				continue;
-
-			keys.add(sort.createSortKey(s, name, s.getIndex(), cache));
+		ArrayList<Mdr7Record> sorted = new ArrayList<>(inStreets.size());
+		for (Mdr7Record street : inStreets) {
+			if (street.getCity() != null && street.getCity().getMdrRegion() != null)
+				sorted.add(street);
 		}
+		Collections.sort(sorted, new Comparator<Mdr7Record>() {
+			public int compare(Mdr7Record o1, Mdr7Record o2) {
+				int d = Integer.compare(o1.getCity().getMdr21SortPos(), o2.getCity().getMdr21SortPos());
+				if (d != 0)
+					return d;
+				return Integer.compare(o1.getIndex(), o2.getIndex());
+			}
+		});
 
-		Collections.sort(keys);
 
-		String lastName = null;
-		int lastMapid = 0;
+		int lastIndex = -1;
 		int record = 0;
-		for (SortKey<Mdr7Record> key : keys) {
-			Mdr7Record street = key.getObject();
-
-			String name = street.getName();
-			int mapid = street.getMapIndex();
-			if (mapid != lastMapid || !name.equals(lastName)) {
+		for (Mdr7Record street : sorted) {
+			if (lastIndex != street.getIndex()) {
 				record++;
 				streets.add(street);
 
@@ -79,8 +65,7 @@ public class Mdr21 extends Mdr2x {
 					mdr28.setMdr21(record);
 				}
 
-				lastMapid = mapid;
-				lastName = name;
+				lastIndex = street.getIndex();
 			}
 		}
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
index afd8a35..d042bca 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr22.java
@@ -15,12 +15,8 @@ package uk.me.parabola.imgfmt.app.mdr;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
+import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
-
-import uk.me.parabola.imgfmt.app.srt.Sort;
-import uk.me.parabola.imgfmt.app.srt.SortKey;
 
 /**
  * Index of streets by country.
@@ -29,6 +25,7 @@ import uk.me.parabola.imgfmt.app.srt.SortKey;
  * cities.
  * 
  * @author Steve Ratcliffe
+ * @author Gerd Petermann
  */
 public class Mdr22 extends Mdr2x {
 
@@ -37,44 +34,36 @@ public class Mdr22 extends Mdr2x {
 	}
 
 	/**
-	 * We need to sort the streets by the name of the country. Within a city
-	 * group the streets are ordered by their own index.
+	 * We need to sort the streets by the name of the country. Within a country
+	 * the streets are ordered by their own index.
 	 *
 	 * Also have to set the record number of the first record in this section
-	 * on the city.
+	 * on the country.
 	 *
 	 * @param inStreets The list of streets from mdr7.
 	 */
 	public void buildFromStreets(List<Mdr7Record> inStreets) {
-		Sort sort = getConfig().getSort();
-
-		List<SortKey<Mdr7Record>> keys = new ArrayList<>();
-		Map<String, byte[]> cache = new HashMap<>();
-		for (Mdr7Record s : inStreets) {
-			Mdr5Record city = s.getCity();
-			if (city == null) continue;
-
-			String name = city.getMdrCountry().getName();
-			assert name != null;
-
-			// We are sorting the streets, but we are sorting primarily on the
-			// country name associated with the street.
-			// For memory use, we re-use country name part of the key.
-			keys.add(sort.createSortKey(s, name, s.getIndex(), cache));
+		ArrayList<Mdr7Record> sorted = new ArrayList<>(inStreets.size());
+		for (Mdr7Record street : inStreets) {
+			if (street.getCity() != null) {
+				assert street.getCity().getCountryName() != null;
+				sorted.add(street);
+			}
 		}
-		Collections.sort(keys);
-
-		int record = 0;
+		Collections.sort(sorted, new Comparator<Mdr7Record>() {
+			public int compare(Mdr7Record o1, Mdr7Record o2) {
+				int d = Integer.compare(o1.getCity().getMdr22SortPos(), o2.getCity().getMdr22SortPos());
+				if (d != 0)
+					return d;
+				return Integer.compare(o1.getIndex(), o2.getIndex());
+			}
+		});
 
-		String lastName = null;
-		int lastMapid = 0;
-		
-		for (SortKey<Mdr7Record> key : keys) {
-			Mdr7Record street = key.getObject();
 
-			String name = street.getName();
-			int mapid = street.getMapIndex();
-			if (mapid != lastMapid || !name.equals(lastName)) {
+		int lastIndex = -1;
+		int record = 0;
+		for (Mdr7Record street : sorted) {
+			if (street.getIndex() != lastIndex) {
 				record++;
 				streets.add(street);
 
@@ -85,10 +74,10 @@ public class Mdr22 extends Mdr2x {
 					mdr29.setMdr22(record);
 				}
 
-				lastMapid = mapid;
-				lastName = name;
+				lastIndex = street.getIndex();
 			}
 		}
+		return;
 	}
 
 	protected boolean sameGroup(Mdr7Record street1, Mdr7Record street2) {
@@ -103,9 +92,14 @@ public class Mdr22 extends Mdr2x {
 	 * Unknown flag
 	 */
 	public int getExtraValue() {
-		if (isForDevice())
-			return 0x600e;
-		else
-			return 0x11000;
+		int magic;
+		if (isForDevice()) {
+			magic = 0x0000e;
+			if (!getConfig().getSort().isMulti())
+				magic |= 0xc0000; // used to be 0x6000, maybe two different flags ? 
+		} else {
+			magic = 0x11000;
+		}
+		return magic;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr28.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr28.java
index 5d6f396..0797608 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr28.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr28.java
@@ -72,14 +72,11 @@ public class Mdr28 extends MdrSection implements HasHeaderFlags {
 		int size23 = sizes.getSize(23);
 		int size27 = sizes.getSize(27);
 
-		int idx = 0;
 		for (Mdr28Record mdr28 : index) {
 			putN(writer, size23, mdr28.getMdr23());
 			putStringOffset(writer, mdr28.getStrOffset());
 			putN(writer, size21, mdr28.getMdr21());
 			putN(writer, size27, mdr28.getMdr27());
-
-			idx++;
 		}
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
index a318329..7287918 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr29.java
@@ -110,7 +110,8 @@ public class Mdr29 extends MdrSection implements HasHeaderFlags {
 				+ sizes.getSize(5)  // NB: not 25
 				;
 		if (isForDevice()) {
-			size += numberToPointerSize(max17);
+			if (!getConfig().getSort().isMulti())
+				size += numberToPointerSize(max17);
 		} else {
 			size += sizes.getStrOffSize();
 			size += sizes.getSize(26);
@@ -136,7 +137,8 @@ public class Mdr29 extends MdrSection implements HasHeaderFlags {
 	public int getExtraValue() {
 		if (isForDevice()) {
 			int magic = 0x6; // 22 and 25
-			magic |= numberToPointerSize(max17) << 4;
+			if (!getConfig().getSort().isMulti())
+				magic |= numberToPointerSize(max17) << 4;
 			return magic; // +17, -26, -strings
 		}
 		else
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
index 7944ebe..30a428b 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr2x.java
@@ -12,6 +12,7 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -25,57 +26,61 @@ import uk.me.parabola.imgfmt.app.ImgFileWriter;
  */
 public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
 	protected List<Mdr7Record> streets = new ArrayList<>();
-
+	protected static final int HAS_LABEL = 0x02;
+	protected static final int HAS_NAME_OFFSET = 0x04;
+	
 	/**
 	 * Write out the contents of this section.
 	 *
 	 * @param writer Where to write it.
 	 */
 	public void writeSectData(ImgFileWriter writer) {
-		String lastName = null;
 		Mdr7Record prev = null;
+		Collator collator = getConfig().getSort().getCollator();
+		collator.setStrength(Collator.SECONDARY);
 
 		int size = getSizes().getStreetSizeFlagged();
-
-		boolean hasLabel = hasFlag(0x2);
-
-		String lastPartial = null;
+		int magic = getExtraValue();
+		boolean writeLabel = (magic & HAS_LABEL) != 0;  // A guess
+		boolean writeNameOffset = (magic & HAS_NAME_OFFSET) != 0;  // A guess, but less so
+		int partialInfoSize = ((magic >> 3) & 0x7);
+//		int partialBShift = ((magic >> 6) & 0xf);
+//		int partialBMask = (1 << partialBShift) - 1;
+		
 		int recordNumber = 0;
 		for (Mdr7Record street : streets) {
 			assert street.getMapIndex() == street.getCity().getMapIndex() : street.getMapIndex() + "/" + street.getCity().getMapIndex();
 			addIndexPointer(street.getMapIndex(), ++recordNumber);
 
-			int index = street.getIndex();
-
-			String name = street.getName();
-
 			int repeat = 1;
-			if (name.equals(lastName) && sameGroup(street, prev))
-				repeat = 0;
-
-			if (hasLabel) {
+			if (writeLabel) {
+				int rr = street.checkRepeat(prev, collator);
 				putMapIndex(writer, street.getMapIndex());
 				int offset = street.getLabelOffset();
+				if (rr == 3 && sameGroup(street, prev))
+					repeat = 0;
 				if (repeat != 0)
 					offset |= 0x800000;
 
-				int trailing = 0;
-				String partialName = street.getPartialName();
-				if (!partialName.equals(lastPartial)) {
-					trailing |= 1;
-					offset |= 0x800000;
-				}
-
 				writer.put3(offset);
-				writer.put(street.getOutNameOffset());
-
-				writer.put((byte) trailing);
+				if (writeNameOffset)
+					writer.put(street.getOutNameOffset());
+
+				if (partialInfoSize > 0) {
+					int trailingFlags = ((rr & 1) == 0) ? 1 : 0;
+					// trailingFlags |= s.getB() << 1;
+					// trailingFlags |= s.getS() << (1 + partialBShift);
+					putN(writer, partialInfoSize, trailingFlags);
+				}
+			} else {
+				int rr = street.checkFullRepeat(prev, collator);
+				if (rr == 2 && sameGroup(street, prev))
+					repeat = 0;
 
-				lastPartial = partialName;
-			} else
+				int index = street.getIndex();
 				putN(writer, size, (index << 1) | repeat);
+			}
 
-			lastName = name;
 			prev = street;
 		}
 	}
@@ -91,9 +96,13 @@ public abstract class Mdr2x extends MdrMapSection implements HasHeaderFlags {
 	 */
 	public int getItemSize() {
 		int size;
+		int magic = getExtraValue();
+		int partialInfoSize = ((magic >> 3) & 0x7);
+		
 		if (isForDevice()) {
 			// map-index, label, name-offset, 1byte flag
-			size = getSizes().getMapSize() + 3 + 1 + 1;
+			size = getSizes().getMapSize() + 3 + 1 + partialInfoSize;
+
 		} else {
 			size = getSizes().getStreetSizeFlagged();
 		}
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
index 652d9ba..bc9b045 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5.java
@@ -96,6 +96,94 @@ public class Mdr5 extends MdrMapSection {
 				lastCity = c;
 			}
 		}
+		// calculate positions for the different road indexes
+		calcMdr20SortPos();
+		calcMdr21SortPos();
+		calcMdr22SortPos();
+	}
+	
+	/**
+	 * Calculate a position when sorting by name, region, and country- This is used for MDR20. 
+	 */
+	private void calcMdr20SortPos() {
+		List<SortKey<Mdr5Record>> sortKeys = new ArrayList<>(allCities.size());
+		Sort sort = getConfig().getSort();
+		for (Mdr5Record m : allCities) {
+			if (m.getName() == null)
+				continue;
+
+			// Sort by city name, region name, and country name .
+			SortKey<Mdr5Record> sortKey = sort.createSortKey(m, m.getName());
+			SortKey<Mdr5Record> regionKey = sort.createSortKey(null, m.getRegionName());
+			SortKey<Mdr5Record> countryKey = sort.createSortKey(null, m.getCountryName());
+			sortKey = new MultiSortKey<>(sortKey, regionKey, countryKey);
+			sortKeys.add(sortKey);
+		}
+		Collections.sort(sortKeys);
+
+		SortKey<Mdr5Record> lastKey = null;
+		int pos = 0;
+		for (SortKey<Mdr5Record> key : sortKeys) {
+			Mdr5Record c = key.getObject();
+			if (lastKey == null || key.compareTo(lastKey) != 0)
+				pos++;
+			c.setMdr20SortPos(pos);
+			lastKey = key;
+		}
+	}
+
+	/**
+	 * Calculate a position when sorting by region- This is used for MDR21. 
+	 */
+	private void calcMdr21SortPos() {
+		List<SortKey<Mdr5Record>> sortKeys = new ArrayList<>(allCities.size());
+		Sort sort = getConfig().getSort();
+		for (Mdr5Record m : allCities) {
+			if (m.getRegionName() == null) 
+				continue;
+
+			// Sort by region name.
+			sortKeys.add(sort.createSortKey(m, m.getRegionName()));
+		}
+		Collections.sort(sortKeys);
+
+		SortKey<Mdr5Record> lastKey = null;
+		int pos = 0;
+		for (SortKey<Mdr5Record> key : sortKeys) {
+			Mdr5Record c = key.getObject();
+			if (lastKey == null || key.compareTo(lastKey) != 0)
+				pos++;
+			c.setMdr21SortPos(pos);
+			lastKey = key;
+		}
+	}
+
+	/**
+	 * Calculate a position when sorting by country- This is used for MDR22. 
+	 */
+
+	private void calcMdr22SortPos() {
+		List<SortKey<Mdr5Record>> sortKeys = new ArrayList<>(allCities.size());
+		Sort sort = getConfig().getSort();
+		for (Mdr5Record m : allCities) {
+			if (m.getCountryName() == null)
+				continue;
+
+			// Sort by country name .
+			SortKey<Mdr5Record> countryKey = sort.createSortKey(m, m.getCountryName());
+			sortKeys.add(countryKey);
+		}
+		Collections.sort(sortKeys);
+
+		SortKey<Mdr5Record> lastKey = null;
+		int pos = 0;
+		for (SortKey<Mdr5Record> key : sortKeys) {
+			Mdr5Record c = key.getObject();
+			if (lastKey == null || key.compareTo(lastKey) != 0)
+				pos++;
+			c.setMdr22SortPos(pos);
+			lastKey = key;
+		}
 	}
 
 	public void writeSectData(ImgFileWriter writer) {
@@ -175,8 +263,9 @@ public class Mdr5 extends MdrMapSection {
 	public int getExtraValue() {
 		int val = (localCitySize - 1);
 		// String offset is only included for a mapsource index.
-		if (isForDevice()) {
-			val |= 0x40; // not known, probably refers to mdr17.
+		if (isForDevice() ) {
+			if (!getConfig().getSort().isMulti())
+				val |= 0x40; // mdr17 sub section present (not with unicode)
 		} else {
 			val |= 0x04;  // region
 			val |= 0x08; // string
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
index aee7909..0d9d5e7 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr5Record.java
@@ -36,6 +36,9 @@ public class Mdr5Record extends RecordBase implements NamedRecord {
 	private Mdr14Record country;
 	private int[] mdr20;
 	private int mdr20Index;
+	private int mdr20SortPos;
+	private int mdr21SortPos;
+	private int mdr22SortPos;
 
 	public int getCityIndex() {
 		return cityIndex;
@@ -174,4 +177,27 @@ public class Mdr5Record extends RecordBase implements NamedRecord {
 	public String getCountryName() {
 		return country.getName();
 	}
+
+	public void setMdr20SortPos(int n) {
+		mdr20SortPos = n;
+	}
+
+	public int getMdr20SortPos() {
+		return mdr20SortPos;
+	}
+	public void setMdr21SortPos(int n) {
+		mdr21SortPos = n;
+	}
+
+	public int getMdr21SortPos() {
+		return mdr21SortPos;
+	}
+
+	public void setMdr22SortPos(int n) {
+		mdr22SortPos = n;
+	}
+
+	public int getMdr22SortPos() {
+		return mdr22SortPos;
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
index efa68b0..637a8e9 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7.java
@@ -12,13 +12,16 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import uk.me.parabola.imgfmt.MapFailedException;
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
-import uk.me.parabola.imgfmt.app.srt.MultiSortKey;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.app.srt.SortKey;
 
@@ -33,7 +36,7 @@ public class Mdr7 extends MdrMapSection {
 	public static final int MDR7_HAS_NAME_OFFSET = 0x20;
 	public static final int MDR7_PARTIAL_SHIFT = 6;
 	public static final int MDR7_U1 = 0x2;
-	public static final int MDR7_U2 = 0x4;
+	public static final int MDR7_HAS_MDR17 = 0x4;
 
 	private static final int MAX_NAME_OFFSET = 127;
 
@@ -41,15 +44,24 @@ public class Mdr7 extends MdrMapSection {
 	private final boolean isMulti;
 	private final boolean splitName;
 
-	private List<Mdr7Record> allStreets = new ArrayList<>();
-	private List<Mdr7Record> streets = new ArrayList<>();
+	private Set<Mdr7Record> roadsPerMap= new HashSet<>();
+	private ArrayList<Mdr7Record> allStreets = new ArrayList<>();
+	private ArrayList<Mdr7Record> streets = new ArrayList<>();
+	private int lastMaxIndex = -1;
 
-	private final int u2size = 1;
+	private int partialInfoSize;
+	private Set<String> exclNames;
+	private final Sort sort;
+	private int maxPrefixCount;
+	private int maxSuffixCount;
+	private boolean magicIsValid;
+	private int magic;
 
 	public Mdr7(MdrConfig config) {
 		setConfig(config);
-		Sort sort = config.getSort();
+		sort = config.getSort();
 		splitName = config.isSplitName();
+		exclNames = config.getMdr7Excl();
 		codepage = sort.getCodepage();
 		isMulti = sort.isMulti();
 	}
@@ -57,21 +69,27 @@ public class Mdr7 extends MdrMapSection {
 	public void addStreet(int mapId, String name, int lblOffset, int strOff, Mdr5Record mdrCity) {
 		if (name.isEmpty())
 			return;
-
-		// Find a name prefix, which is either a shield or a word ending 0x1e. We are treating
+			
+		// Find a name prefix, which is either a shield or a word ending 0x1e or 0x1c. We are treating
 		// a shield as a prefix of length one.
 		int prefix = 0;
 		if (name.charAt(0) < 7)
 			prefix = 1;
 		int sep = name.indexOf(0x1e);
-		if (sep > 0)
+		if (sep < 0)
+			sep = name.indexOf(0x1b);
+		if (sep > 0) {
 			prefix = sep + 1;
+		}
 
-		// Find a name suffix which begins with 0x1f
+		// Find a name suffix which begins with 0x1 or 0x1c
 		sep = name.indexOf(0x1f);
+		if (sep < 0)
+			sep = name.indexOf(0x1c);
 		int suffix = 0;
-		if (sep > 0)
+		if (sep > 0) {
 			suffix = sep;
+		}
 
 		// Large values can't actually work.
 		if (prefix >= MAX_NAME_OFFSET || suffix >= MAX_NAME_OFFSET)
@@ -85,7 +103,7 @@ public class Mdr7 extends MdrMapSection {
 		st.setCity(mdrCity);
 		st.setPrefixOffset((byte) prefix);
 		st.setSuffixOffset((byte) suffix);
-		allStreets.add(st);
+		storeMdr7(st);
 
 		if (!splitName)
 			return;
@@ -96,7 +114,7 @@ public class Mdr7 extends MdrMapSection {
 		int c;
 		int outOffset = 0;
 
-		int end = Math.min((suffix > 0) ? suffix : name.length() - prefix - 1, MAX_NAME_OFFSET);
+		int end = Math.min(((suffix > 0) ? suffix : name.length()) - prefix - 1, MAX_NAME_OFFSET);
 		for (int nameOffset = 0; nameOffset < end; nameOffset += Character.charCount(c)) {
 			c = name.codePointAt(prefix + nameOffset);
 
@@ -123,7 +141,9 @@ public class Mdr7 extends MdrMapSection {
 				st.setPrefixOffset((byte) prefix);
 				st.setSuffixOffset((byte) suffix);
 				//System.out.println(st.getName() + ": add partial " + st.getPartialName());
-				allStreets.add(st);
+				if (!exclNames.contains(st.getPartialName()))
+					storeMdr7(st);
+
 				start = false;
 			}
 
@@ -134,6 +154,22 @@ public class Mdr7 extends MdrMapSection {
 	}
 
 	/**
+	 * Store in array if not already done
+	 * @param st the mdr7 record
+	 */
+	private void storeMdr7(Mdr7Record st) {
+		if (lastMaxIndex != st.getMapIndex()) {
+			// we process all roads of one map tile sequentially, so we can clear the set with each new map tile
+			lastMaxIndex = st.getMapIndex();
+			roadsPerMap.clear(); 
+		}
+		
+		if (roadsPerMap.add(st)) {
+			allStreets.add(st);
+		}
+	}
+
+	/**
 	 * Return the number of bytes that the given character will consume in the output encoded
 	 * format.
 	 */
@@ -167,74 +203,173 @@ public class Mdr7 extends MdrMapSection {
 	 * as it requires a lot of heap to store the sort keys. 	  	 
 	 */
 	protected void preWriteImpl() {
-		Sort sort = getConfig().getSort();
-		List<SortKey<Mdr7Record>> sortedStreets = new ArrayList<>(allStreets.size());
-		for (Mdr7Record m : allStreets) {
-			sortedStreets.add(new MultiSortKey<>(
-					sort.createSortKey(m, m.getPartialName()),
-					sort.createSortKey(m, m.getInitialPart(), m.getMapIndex()),
-					null));
+		
+		LargeListSorter<Mdr7Record> partialSorter = new LargeListSorter<Mdr7Record>(sort) {
+			@Override
+			protected SortKey<Mdr7Record> makeKey(Mdr7Record r, Sort sort, Map<String, byte[]> cache) {
+				return sort.createSortKey(r, r.getPartialName(), 0, cache); // first sort by partial name only
+			}
+		};
+		
+		ArrayList<Mdr7Record> sorted = new ArrayList<>(allStreets);
+		allStreets.clear();
+		partialSorter.sort(sorted);
+		// list is now sorted by partial name only, we have to group by name and map index now
+		String lastPartial = null;
+		List<Mdr7Record> samePartial = new ArrayList<>();
+		Collator collator = sort.getCollator();
+		collator.setStrength(Collator.SECONDARY);
+		for (int i = 0; i < sorted.size(); i++) {
+			Mdr7Record r = sorted.get(i);
+			String partial = r.getPartialName();
+			if (lastPartial == null || collator.compare(partial, lastPartial) != 0) {
+				groupByNameAndMap(samePartial);
+				samePartial.clear();
+			}
+			samePartial.add(r);
+			lastPartial = partial;
 		}
-		Collections.sort(sortedStreets);
-
-		// De-duplicate the street names so that there is only one entry
-		// per map for the same name.
-		int recordNumber = 0;
-		Mdr7Record last = new Mdr7Record();
-		for (int i = 0; i < sortedStreets.size(); i++){ 
-			SortKey<Mdr7Record> sk = sortedStreets.get(i);
-			Mdr7Record r = sk.getObject();
-			if (r.getMapIndex() == last.getMapIndex()
-					&& r.getName().equals(last.getName())  // currently think equals is correct, not collator.compare()
-					&& r.getPartialName().equals(last.getPartialName()))
-			{
-				// This has the same name (and map number) as the previous one. Save the pointer to that one
+		groupByNameAndMap(samePartial);
+		
+		allStreets.trimToSize();
+		streets.trimToSize();
+		return;
+	}
+
+	/**
+	 * Group a list of roads with the same partial name.
+	 * @param samePartial
+	 * @param minorSorter
+	 */
+	private void groupByNameAndMap(List<Mdr7Record> samePartial) {
+		if (samePartial.isEmpty())
+			return;
+		
+		// Basecamp needs the records grouped by partial name, full name, and map index.
+		// This sometimes presents search results in the wrong order. The partial sort fields allow to
+		// tell the right order.
+		
+//		LargeListSorter<Mdr7Record> initalPartSorter = new LargeListSorter<Mdr7Record>(sort) {
+//			@Override
+//			protected SortKey<Mdr7Record> makeKey(Mdr7Record r, Sort sort, Map<String, byte[]> cache) {
+//				return sort.createSortKey(r, r.getInitialPart(), r.getMapIndex(), cache);
+//			}
+//		};
+//		LargeListSorter<Mdr7Record> suffixSorter = new LargeListSorter<Mdr7Record>(sort) {
+//			@Override
+//			protected SortKey<Mdr7Record> makeKey(Mdr7Record r, Sort sort, Map<String, byte[]> cache) {
+//				return sort.createSortKey(r, r.getSuffix(), r.getMapIndex(), cache);
+//			}
+//		};
+		LargeListSorter<Mdr7Record> fullNameSorter = new LargeListSorter<Mdr7Record>(sort) {
+			@Override
+			protected SortKey<Mdr7Record> makeKey(Mdr7Record r, Sort sort, Map<String, byte[]> cache) {
+				return sort.createSortKey(r, r.getName(), r.getMapIndex(), cache);
+			}
+		};
+		
+		
+//		List<Mdr7Record> sortedByInitial = new ArrayList<>(samePartial);
+//		List<Mdr7Record> sortedBySuffix = new ArrayList<>(samePartial);
+//		initalPartSorter.sort(sortedByInitial);
+//		suffixSorter.sort(sortedBySuffix);
+		
+//		Mdr7Record last = null;
+//		for (int i = 0; i < samePartial.size(); i++) {
+//			Mdr7Record r = samePartial.get(i);
+//			if (i > 0) {
+//				int repeat = r.checkRepeat(last, collator);
+//				int b = 0;
+//				if (repeat == 0) {
+//				} else if (repeat == 3) 
+//					b = last.getB();
+//				else if (repeat == 1) {
+//					b = last.getB() + 1;
+//				}
+//				if (b != 0) {
+//					if (b > maxPrefixCount)
+//						maxPrefixCount = b;
+//					r.setB(b);
+//				}
+//			}
+//			last = r;
+//		}
+//		suffixSorter.sort(samePartial);
+//		last = null;
+//		int s = 0;
+//		for (int i = 0; i < samePartial.size(); i++) {
+//			Mdr7Record r = samePartial.get(i);
+//			if (i > 0) {
+//				int cmp = collator.compare(last.getSuffix(), r.getSuffix());
+//				if (cmp == 0)
+//					s = last.getS();
+//				else 
+//					s = last.getS() + 1;
+//				if (s != 0) {
+//					if (s > maxSuffixCount)
+//						maxSuffixCount = s;
+//					r.setB(s);
+//				}
+//			}
+//			last = r;
+//		}
+//
+//		
+		fullNameSorter.sort(samePartial);
+		Mdr7Record last = null;
+		int recordNumber = streets.size();
+		
+		// list is now sorted by partial name, name, and map index
+//		// De-duplicate the street names so that there is only one entry
+//		// per map for the same name.
+		for (int i = 0; i < samePartial.size(); i++) {
+			Mdr7Record r = samePartial.get(i);
+			if (last != null && r.getMapIndex() == last.getMapIndex() && r.getName().equals(last.getName())) {
+				// This has the same name (and map number) as the previous one.
+				// Save the pointer to that one
 				// which is going into the file.
 				r.setIndex(recordNumber);
 			} else {
 				recordNumber++;
-				last = r;
 				r.setIndex(recordNumber);
 				streets.add(r);
 			}
-			// release memory 
-			sortedStreets.set(i, null);
+			if (r.getCity() != null)
+				allStreets.add(r);
+			last = r;
 		}
 	}
 
 	public void writeSectData(ImgFileWriter writer) {
-		String lastName = null;
-		String lastPartial = null;
 		boolean hasStrings = hasFlag(MDR7_HAS_STRING);
 		boolean hasNameOffset = hasFlag(MDR7_HAS_NAME_OFFSET);
-
+		Collator collator = sort.getCollator();
+//		int partialBShift = ((getExtraValue() >> 9) & 0xf);
+		collator.setStrength(Collator.SECONDARY); 
+		Mdr7Record last = null;
 		for (Mdr7Record s : streets) {
 			addIndexPointer(s.getMapIndex(), s.getIndex());
 
 			putMapIndex(writer, s.getMapIndex());
 			int lab = s.getLabelOffset();
-			String name = s.getName();
-			if (!name.equals(lastName)) {
+			
+			int rr = s.checkRepeat(last, collator);
+			if (rr != 3)
 				lab |= 0x800000;
-				lastName = name;
-			}
-
-			String partialName = s.getPartialName();
-			int trailingFlags = 0;
-			if (!partialName.equals(lastPartial)) {
-				trailingFlags |= 1;
-				lab |= 0x800000;  // If it is not a partial repeat, then it is not a complete repeat either
-			}
-			lastPartial = partialName;
-
+			
 			writer.put3(lab);
 			if (hasStrings)
 				putStringOffset(writer, s.getStringOffset());
 
 			if (hasNameOffset)
 				writer.put(s.getOutNameOffset());
-
-			putN(writer, u2size, trailingFlags);
+			if (partialInfoSize > 0) {
+				int trailingFlags = ((rr & 1) == 0) ? 1 : 0;
+				// trailingFlags |= s.getB() << 1;
+				// trailingFlags |= s.getS() << (1 + partialBShift);
+				putN(writer, partialInfoSize, trailingFlags);
+			}
+			last = s;
 		}
 	}
 
@@ -244,7 +379,7 @@ public class Mdr7 extends MdrMapSection {
 	 */
 	public int getItemSize() {
 		PointerSizes sizes = getSizes();
-		int size = sizes.getMapSize() + 3 + u2size;
+		int size = sizes.getMapSize() + 3 + partialInfoSize;
 		if (!isForDevice())
 			size += sizes.getStrOffSize();
 		if ((getExtraValue() & MDR7_HAS_NAME_OFFSET) != 0)
@@ -260,14 +395,26 @@ public class Mdr7 extends MdrMapSection {
 	 * Value of 3 possibly the existence of the lbl field.
 	 */
 	public int getExtraValue() {
-		int magic = MDR7_U1 | MDR7_HAS_NAME_OFFSET | (u2size << MDR7_PARTIAL_SHIFT);
-
-		if (isForDevice()) {
-			magic |= MDR7_U2;
-		} else {
-			magic |= MDR7_HAS_STRING;
+		if (!magicIsValid) {
+			int bitsPrefix = (maxPrefixCount == 0) ? 0 : Integer.numberOfTrailingZeros(Integer.highestOneBit(maxPrefixCount)) + 1;
+			int bitsSuffix = (maxSuffixCount == 0) ? 0 : Integer.numberOfTrailingZeros(Integer.highestOneBit(maxSuffixCount)) + 1;
+			int bits = bitsPrefix + bitsSuffix + 1;
+			partialInfoSize = 1 + bits / 8;
+			assert bitsSuffix <= 15;
+			magic = MDR7_U1 | MDR7_HAS_NAME_OFFSET | (partialInfoSize << MDR7_PARTIAL_SHIFT) | (bitsPrefix << 9);
+
+			if (isForDevice()) {
+				if (!getConfig().getSort().isMulti())
+					magic |= MDR7_HAS_MDR17; // mdr17 sub section present (not with unicode)
+			} else {
+				magic |= MDR7_HAS_STRING;
+			}
+			int unk2size = (magic >> 6) & 0x7;
+			int unk2split = ((magic >> 9) & 0xf);
+			assert unk2size == partialInfoSize;
+			assert unk2split == bitsPrefix;
+			magicIsValid = true;
 		}
-
 		return magic;
 	}
 
@@ -276,59 +423,21 @@ public class Mdr7 extends MdrMapSection {
 		streets = null;
 	}
 
-	/**
-	 * Must be called after the section data is written so that the streets
-	 * array is already sorted.
-	 * @return List of index records.
-	 */
-	public List<Mdr8Record> getIndex() {
-		List<Mdr8Record> list = new ArrayList<>();
-		for (int number = 1; number <= streets.size(); number += 10240) {
-			String prefix = getPrefixForRecord(number);
-
-			// need to step back to find the first...
-			int rec = number;
-			while (rec > 1) {
-				String p = getPrefixForRecord(rec);
-				if (!p.equals(prefix)) {
-					rec++;
-					break;
-				}
-				rec--;
-			}
-
-			Mdr8Record indexRecord = new Mdr8Record();
-			indexRecord.setPrefix(prefix);
-			indexRecord.setRecordNumber(rec);
-			list.add(indexRecord);
-		}
-		return list;
-	}
-
-	/**
-	 * Get the prefix of the name at the given record.
-	 * @param number The record number.
-	 * @return The first 4 (or whatever value is set) characters of the street
-	 * name.
-	 */
-	private String getPrefixForRecord(int number) {
-		Mdr7Record record = streets.get(number-1);
-		int endIndex = MdrUtils.STREET_INDEX_PREFIX_LEN;
-		String name = record.getName();
-		if (endIndex > name.length()) {
-			StringBuilder sb = new StringBuilder(name);
-			while (sb.length() < endIndex)
-				sb.append('\0');
-			name = sb.toString();
-		}
-		return name.substring(0, endIndex);
-	}
 
 	public List<Mdr7Record> getStreets() {
-		return Collections.unmodifiableList(allStreets);
+		return Collections.unmodifiableList((ArrayList<Mdr7Record>) allStreets);
 	}
 	
 	public List<Mdr7Record> getSortedStreets() {
 		return Collections.unmodifiableList(streets);
 	}
+
+	
+	/**
+	 * Free as much memory as possible.
+	 */
+	public void trim() {
+		allStreets.trimToSize();
+		roadsPerMap = new HashSet<>();
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7Record.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7Record.java
index 1de7e0a..3af8184 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr7Record.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr7Record.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
+import java.text.Collator;
+
 /**
  * Holds details of a single street.
  * @author Steve Ratcliffe
@@ -110,11 +112,102 @@ public class Mdr7Record extends RecordBase implements NamedRecord {
 			return name.substring((nameOffset & 0xff) + (prefixOffset & 0xff));
 	}
 
+	public String getSuffix() {
+		if(suffixOffset == 0)
+			return "";
+		return name.substring(suffixOffset & 0xff);
+	}
+	
+	public String getPrefix() {
+		if(prefixOffset== 0)
+			return "";
+		return name.substring(0, prefixOffset & 0xff);
+	}
+	
 	public String toString() {
 		return name + " in " + city.getName();
 	}
 
 	public String getInitialPart() {
-		return name.substring(0, (nameOffset & 0xff));
+		return name.substring(0, (nameOffset & 0xff) + (prefixOffset & 0xff));
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + labelOffset;
+		result = prime * result + nameOffset;
+		result = prime * result + outNameOffset;
+		result = prime * result + prefixOffset;
+		result = prime * result + stringOffset; // XXX probably not needed
+		result = prime * result + suffixOffset;
+		result = prime * result + getMapIndex();
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		Mdr7Record other = (Mdr7Record) obj;
+		if (labelOffset != other.labelOffset)
+			return false;
+		if (getMapIndex() != other.getMapIndex())
+			return false;
+		if (nameOffset != other.nameOffset)
+			return false;
+		if (outNameOffset != other.outNameOffset)
+			return false;
+		if (prefixOffset != other.prefixOffset)
+			return false;
+		if (stringOffset != other.stringOffset) // XXX probably not needed
+			return false;
+		if (suffixOffset != other.suffixOffset)
+			return false;
+		if (city != other.getCity()) 
+			return false;
+		return true;
+	}
+
+	/**
+	 * Calculate integer for partial + full repeat.
+	 * @param last record to compare
+	 * @param collator collator for compare  
+	 * @return 3 if full and partial repeat (Rr), 2 if only full repeat (R), 1 if partial repeat (r), 0 if no repeat 
+	 */
+	public int checkRepeat (Mdr7Record last, Collator collator) {
+		if (last == null)
+			return 0;
+		int res = 0;
+		String lastPartial = last.getPartialName();
+		String partial = getPartialName();
+		int cmp = collator.compare(lastPartial, partial);
+		if (cmp == 0)
+			res = 1;
+		res |= checkFullRepeat(last, collator);
+		return res;
+	}
+	
+	/**
+	 * Calculate integer for full repeat.
+	 * @param last record to compare
+	 * @param collator collator for compare  
+	 * @return 2 if full repeat (R), 0 else 
+	 */
+	public int checkFullRepeat (Mdr7Record last, Collator collator) {
+		if (last == null)
+			return 0;
+		int res = 0;
+		String lastName = last.getName();
+		String name = getName();
+		int cmp = collator.compare(lastName, name);
+		if (cmp == 0) 
+			res |= 2;
+		return res;
 	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr8.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr8.java
index 6113d1f..c250b72 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr8.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr8.java
@@ -12,7 +12,6 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
-import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -38,10 +37,13 @@ public class Mdr8 extends MdrSection implements HasHeaderFlags {
 	 * @param writer Where to write it.
 	 */
 	public void writeSectData(ImgFileWriter writer) {
+		if (index.size() <= 1)
+			return;
 		int size = associatedSize();
-		Charset charset = getConfig().getSort().getCharset();
 		for (Mdr8Record s : index) {
-			writer.put(s.getPrefix().getBytes(charset), 0, STRING_WIDTH);
+			for (int i = 0; i< STRING_WIDTH; i++) {
+				writer.put((byte) s.getPrefix()[i]);
+			}
 			putN(writer, size, s.getRecordNumber());
 		}
 	}
@@ -56,7 +58,7 @@ public class Mdr8 extends MdrSection implements HasHeaderFlags {
 	 * @return The number of items in the section.
 	 */
 	protected int numberOfItems() {
-		return index.size();
+		return index.size() > 1 ? 0: index.size();
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/Mdr8Record.java b/src/uk/me/parabola/imgfmt/app/mdr/Mdr8Record.java
index 0c09e67..a1f9279 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/Mdr8Record.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/Mdr8Record.java
@@ -18,14 +18,14 @@ package uk.me.parabola.imgfmt.app.mdr;
  * @author Steve Ratcliffe
  */
 public class Mdr8Record extends ConfigBase {
-	private String prefix;
+	private char[] prefix;
 	private int recordNumber;
 
-	public String getPrefix() {
+	public char[] getPrefix() {
 		return prefix;
 	}
 
-	public void setPrefix(String prefix) {
+	public void setPrefix(char[] prefix) {
 		this.prefix = prefix;
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MdrConfig.java b/src/uk/me/parabola/imgfmt/app/mdr/MdrConfig.java
index a3bcf6e..cd30718 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MdrConfig.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MdrConfig.java
@@ -13,8 +13,18 @@
 package uk.me.parabola.imgfmt.app.mdr;
 
 import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.app.srt.Sort;
+import uk.me.parabola.mkgmap.CommandArgs;
+import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
+import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.mkgmap.scan.SyntaxException;
 
 /**
  * Configuration for the MDR file.
@@ -33,6 +43,24 @@ public class MdrConfig {
 	private Sort sort;
 	private File outputDir;
 	private boolean splitName;
+	private Set<String> mdr7Excl = Collections.emptySet();
+	private Set<String> mdr7Del = Collections.emptySet();
+	private Set<Integer> poiExclTypes = Collections.emptySet();
+	
+	public MdrConfig() {
+		
+	}
+	
+	/**
+	 * Constructor which copies the values which configure the index details. 
+	 * @param base 
+	 */
+	public MdrConfig(MdrConfig base) {
+		splitName = base.isSplitName();
+		mdr7Del = base.getMdr7Del();
+		mdr7Excl = base.getMdr7Excl();
+		poiExclTypes = base.getPoiExclTypes();
+	}
 
 	/**
 	 * True if we are creating the file, rather than reading it.
@@ -96,4 +124,107 @@ public class MdrConfig {
 	public boolean isSplitName() {
 		return splitName;
 	}
+
+	public Set<String> getMdr7Excl() {
+		return Collections.unmodifiableSet(mdr7Excl);
+	}
+
+	public void setMdr7Excl(String exclList) {
+		mdr7Excl = StringToSet(exclList);
+	}
+
+	public Set<String> getMdr7Del() {
+		return Collections.unmodifiableSet(mdr7Del);
+	}
+
+	public void setMdr7Del(String delList) {
+		mdr7Del = StringToSet(delList);
+	}
+	
+	private Set<String> StringToSet (String opt) {
+		Set<String> set;
+
+		if (opt == null)
+			set = Collections.emptySet();
+		else {
+			if (opt.startsWith("'") || opt.startsWith("\""))
+				opt = opt.substring(1);
+			if (opt.endsWith("'") || opt.endsWith("\""))
+				opt = opt.substring(0, opt.length() - 1);
+			List<String> list = Arrays.asList(opt.split(","));
+			set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
+			for (String s : list) {
+				set.add(s.trim());
+			}
+		}
+		return set;
+	}
+
+	/**
+	 * Parse option --poi-excl-index .
+	 * @param opt the option string given. Expected is a comma separated list,eg
+	 * "0x2800" or "0x2800, 0x6400-0x661f" 
+	 */
+	public void setPoiExcl (String opt) {
+		if (opt == null)
+			return;
+		poiExclTypes = new TreeSet<>();
+		
+		String[] opts = opt.split(",");
+		for (String range : opts) {
+			if (range.contains("-")) {
+				String[] ranges = range.split("-");
+				if (ranges.length != 2)
+					throw new IllegalArgumentException("invalid range in option " + range);
+				genTypesForRange(poiExclTypes, ranges[0], ranges[1]);
+			} else {
+				genTypesForRange(poiExclTypes, range,range);
+			}
+		}
+	}
+
+	/**
+	 * Create all POI types for a given range.
+	 * @param set set of integers. Generated types are added to it.
+	 * @param start first type
+	 * @param stop last type (included)
+	 */
+	private void genTypesForRange(Set<Integer> set, String start, String stop) {
+		GType[] types = new GType[2];
+		String[] ranges = {start, stop};
+		boolean ok = true;
+		for (int i = 0; i < 2; i++) {
+			try {
+				types[i] = new  GType(FeatureKind.POINT, ranges[i]);
+			} catch (ExitException e) {
+				ok = false;
+			}
+			if (!ok || !GType.checkType(types[i].getFeatureKind(), types[i].getType())){
+				throw new SyntaxException("invalid type " + ranges[i] + " for " + FeatureKind.POINT + " in option " + ranges);
+			}
+		}
+		
+		if (types[0].getType() > types[1].getType()) {
+			GType gt = types[0];
+			types[0] = types[1];
+			types[1] = gt;
+		}
+		for (int i = types[0].getType(); i <= types[1].getType(); i++) {
+			if ((i & 0xff) > 0x1f)
+				i = ((i >> 8) + 1) << 8;
+			set.add(i);
+		}
+		
+	}
+
+	public Set<Integer> getPoiExclTypes() {
+		return Collections.unmodifiableSet(poiExclTypes);
+	}
+
+	public void setIndexOptions(CommandArgs args) {
+		setSplitName(args.get("split-name-index", false));
+		setMdr7Excl(args.get("mdr7-excl", null));
+		setMdr7Del(args.get("mdr7-del", null));
+		setPoiExcl(args.get("poi-excl-index", null));
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/MdrUtils.java b/src/uk/me/parabola/imgfmt/app/mdr/MdrUtils.java
index 0bc6074..c801701 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/MdrUtils.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/MdrUtils.java
@@ -26,6 +26,7 @@ public class MdrUtils {
 
 	public static final int STREET_INDEX_PREFIX_LEN = 4;
 	public static final int POI_INDEX_PREFIX_LEN = 4;
+	public static final int MAX_GROUP = 13;
 
 	/**
 	 * Get the group number for the poi.  This is the first byte of the records
@@ -36,18 +37,31 @@ public class MdrUtils {
 	 * @return The group number.  This is a number between 1 and 9 (and later
 	 * perhaps higher numbers such as 0x40, so do not assume there are no
 	 * gaps).
+	 * Group / Filed under
+	 * 1 Cities
+	 * 2 Food & Drink
+	 * 3 Lodging
+	 * 4-5 Recreation / Entertainment / Attractions
+	 * 6 Shopping
+	 * 7 Auto Services
+	 * 8 Community
+	 * 9 ?
+	 * 
 	 */
 	public static int getGroupForPoi(int fullType) {
 		// We group pois based on their type.  This may not be the final thoughts on this.
-		int type = MdrUtils.getTypeFromFullType(fullType);
+		int type = getTypeFromFullType(fullType);
 		int group = 0;
-		if (fullType < 0xf)
+		if (fullType <= 0xf)
 			group = 1;
-		else if (type >= 0x2a && type <= 0x30) {
+		else if (type >= 0x2a && type <= 0x30) { 
 			group = type - 0x28;
 		} else if (type == 0x28) {
 			group = 9;
+		} else if (type >= 0x64 && type <= 0x66) {
+			group = type - 0x59;
 		}
+		assert group >= 0 && group <= MAX_GROUP : "invalid group " + Integer.toHexString(group);
 		return group;
 	}
 
@@ -55,7 +69,7 @@ public class MdrUtils {
 		return getGroupForPoi(fullType) != 0;
 	}
 
-	private static int getTypeFromFullType(int fullType) {
+	public static int getTypeFromFullType(int fullType) {
 		if ((fullType & 0xfff00) > 0)
 			return (fullType>>8) & 0xfff;
 		else
@@ -63,12 +77,12 @@ public class MdrUtils {
 	}
 
 	/**
-	 * Gets the subtype if there is one, else the type.
+	 * Gets the subtype 
 	 * @param fullType The type in the so-called 'full' format.
-	 * @return If there is a subtype, then it is returned. Otherwise the type is returned.
+	 * @return If there is a subtype, then it is returned, else 0.
 	 */
-	public static int getSubtypeOrTypeFromFullType(int fullType) {
-		return fullType & 0xff;
+	public static int getSubtypeFromFullType(int fullType) {
+		return fullType < 0xff ? 0 : fullType & 0xff;
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java b/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
index d88c310..4e416d8 100644
--- a/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
+++ b/src/uk/me/parabola/imgfmt/app/mdr/PrefixIndex.java
@@ -12,13 +12,13 @@
  */
 package uk.me.parabola.imgfmt.app.mdr;
 
-import java.nio.charset.Charset;
 import java.text.Collator;
 import java.util.ArrayList;
 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.srt.Sort.SrtCollator;
 
 /**
  * Holds an index of name prefixes to record numbers.
@@ -58,20 +58,32 @@ public class PrefixIndex extends MdrSection {
 		// Prefixes are equal based on the primary unaccented character, so
 		// we need to use the collator to test for equality and not equals().
 		Sort sort = getConfig().getSort();
-		Collator collator = sort.getCollator();
+		Sort.SrtCollator collator = (SrtCollator) sort.getCollator();
 		collator.setStrength(Collator.PRIMARY);
 
 		String lastCountryName = null;
-		String lastPrefix = "";
+		char[] lastPrefix = "".toCharArray();
 		int inRecord = 0;  // record number of the input list
 		int outRecord = 0; // record number of the index
+		int lastMdr22SortPos = -1; 
 		for (NamedRecord r : list) {
 			inRecord++;
-
-			String prefix = getPrefix(r.getName());
-			if (collator.compare(prefix, lastPrefix) != 0) {
+			String name;
+			if (r instanceof Mdr7Record) {
+				name = ((Mdr7Record) r).getPartialName();
+				if (grouped) {
+					int mdr22SortPos = ((Mdr7Record) r).getCity().getMdr22SortPos();
+					if (mdr22SortPos != lastMdr22SortPos)
+						lastPrefix = "".toCharArray();
+					lastMdr22SortPos = mdr22SortPos; 
+				}
+			}
+			else 
+				name = r.getName();
+			char[] prefix = sort.getPrefix(name, prefixLength);
+			int cmp = collator.compareOneStrengthWithLength(prefix, lastPrefix, Collator.PRIMARY, prefixLength);
+			if (cmp > 0) {
 				outRecord++;
-
 				Mdr8Record ind = new Mdr8Record();
 				ind.setPrefix(prefix);
 				ind.setRecordNumber(inRecord);
@@ -103,9 +115,10 @@ public class PrefixIndex extends MdrSection {
 	 */
 	public void writeSectData(ImgFileWriter writer) {
 		int size = numberToPointerSize(maxIndex);
-		Charset charset = getConfig().getSort().getCharset();
 		for (Mdr8Record s : index) {
-			writer.put(s.getPrefix().getBytes(charset), 0, prefixLength);
+			for (int i = 0; i< prefixLength; i++) {
+				writer.put((byte) s.getPrefix()[i]);
+			}
 			putN(writer, size, s.getRecordNumber());
 		}
 	}
@@ -118,35 +131,6 @@ public class PrefixIndex extends MdrSection {
 		return index.size();
 	}
 
-	/**
-	 * Get the prefix of the name at the given record.
-	 * If the name is shorter than the prefix length, then it padded with nul characters.
-	 * So it can be longer than the input string.
-	 * 
-	 * @param in The name to truncate.
-	 * @return A string prefixLength characters long, consisting of the initial
-	 * prefix of name and padded with nulls if necessary to make up the length.
-	 */
-	private String getPrefix(String in) {
-		StringBuilder sb = new StringBuilder();
-		char[] chars = in.toCharArray();
-		int ci = 0;
-		for (int i = 0; i < prefixLength; i++) {
-			char c = 0;
-			while (ci < chars.length) {
-				// TODO: simplify when initial spaces are removed
-				c = chars[ci++];
-				if (ci == 1 && c== 0x20)
-					continue;
-				if (c >= 0x20)
-					break;
-			}
-			sb.append(c);
-		}
-
-		return sb.toString();
-	}
-
 	public int getPrefixLength() {
 		return prefixLength;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java b/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java
index 7d3f729..8c8ade7 100644
--- a/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java
+++ b/src/uk/me/parabola/imgfmt/app/net/AccessTagsAndBits.java
@@ -43,50 +43,55 @@ public final class AccessTagsAndBits {
 	public static final byte R_FERRY        = 0x020;
 	public static final byte R_ROUNDABOUT   = 0x040;
 
-	public final static Map<String, Byte> ACCESS_TAGS = new LinkedHashMap<String, Byte>(){{
-		put("mkgmap:foot", FOOT);
-		put("mkgmap:bicycle", BIKE);
-		put("mkgmap:car", CAR);
-		put("mkgmap:delivery", DELIVERY);
-		put("mkgmap:truck", TRUCK);
-		put("mkgmap:bus", BUS);
-		put("mkgmap:taxi", TAXI);
-		put("mkgmap:emergency", EMERGENCY);
-	}};
+	public static final Map<String, Byte> ACCESS_TAGS;
+	public static final Map<Short, Byte> ACCESS_TAGS_COMPILED;
+	static {
+		ACCESS_TAGS = new LinkedHashMap<>();
+		ACCESS_TAGS.put("mkgmap:foot", FOOT);
+		ACCESS_TAGS.put("mkgmap:bicycle", BIKE);
+		ACCESS_TAGS.put("mkgmap:car", CAR);
+		ACCESS_TAGS.put("mkgmap:delivery", DELIVERY);
+		ACCESS_TAGS.put("mkgmap:truck", TRUCK);
+		ACCESS_TAGS.put("mkgmap:bus", BUS);
+		ACCESS_TAGS.put("mkgmap:taxi", TAXI);
+		ACCESS_TAGS.put("mkgmap:emergency", EMERGENCY);
 
-	public final static Map<Short, Byte> ACCESS_TAGS_COMPILED = new LinkedHashMap<Short, Byte>(){{
-		for (Map.Entry<String, Byte> entry : ACCESS_TAGS.entrySet())
-			put(TagDict.getInstance().xlate(entry.getKey()),entry.getValue());
-	}};
+		ACCESS_TAGS_COMPILED = new LinkedHashMap<>();
+		for (Map.Entry<String, Byte> entry : ACCESS_TAGS.entrySet()) {
+			ACCESS_TAGS_COMPILED.put(TagDict.getInstance().xlate(entry.getKey()), entry.getValue());
+		}
+	};
 
-	public final static Map<String, Byte> ROUTE_TAGS = new LinkedHashMap<String, Byte>(){{
-		put("mkgmap:throughroute", R_THROUGHROUTE);
-		put("mkgmap:carpool", R_CARPOOL); 
-		put("oneway", R_ONEWAY);
-		put("mkgmap:toll", R_TOLL);
-		put("mkgmap:unpaved", R_UNPAVED);
-		put("mkgmap:ferry", R_FERRY);
-		put("junction", R_ROUNDABOUT);
-	}};
+	public static final Map<String, Byte> ROUTE_TAGS;
+	static {
+		ROUTE_TAGS = new LinkedHashMap<>();
+		ROUTE_TAGS.put("mkgmap:throughroute", R_THROUGHROUTE);
+		ROUTE_TAGS.put("mkgmap:carpool", R_CARPOOL);
+		ROUTE_TAGS.put("oneway", R_ONEWAY);
+		ROUTE_TAGS.put("mkgmap:toll", R_TOLL);
+		ROUTE_TAGS.put("mkgmap:unpaved", R_UNPAVED);
+		ROUTE_TAGS.put("mkgmap:ferry", R_FERRY);
+		ROUTE_TAGS.put("junction", R_ROUNDABOUT);
+	}
 
-	public static byte evalAccessTags(Element el){
+	public static byte evalAccessTags(Element el) {
 		byte noAccess = 0;
-		for (Map.Entry<Short,Byte> entry : ACCESS_TAGS_COMPILED.entrySet()){
+		for (Map.Entry<Short, Byte> entry : ACCESS_TAGS_COMPILED.entrySet()) {
 			if (el.tagIsLikeNo(entry.getKey()))
 				noAccess |= entry.getValue();
 		}
 		return  (byte) ~noAccess;
 	}
 
-
-	private static final short carpoolTagKey = TagDict.getInstance().xlate("mkgmap:carpool"); 
-	private static final short tollTagKey = TagDict.getInstance().xlate("mkgmap:toll"); 
-	private static final short unpavedTagKey = TagDict.getInstance().xlate("mkgmap:unpaved"); 
-	private static final short ferryTagKey = TagDict.getInstance().xlate("mkgmap:ferry"); 
-	private static final short throughrouteTagKey = TagDict.getInstance().xlate("mkgmap:throughroute"); 
-	private static final short junctionTagKey = TagDict.getInstance().xlate("junction"); 
-	private static final short onewayTagKey = TagDict.getInstance().xlate("oneway"); 
-	public static byte evalRouteTags(Element el){
+	private static final short carpoolTagKey = TagDict.getInstance().xlate("mkgmap:carpool");
+	private static final short tollTagKey = TagDict.getInstance().xlate("mkgmap:toll");
+	private static final short unpavedTagKey = TagDict.getInstance().xlate("mkgmap:unpaved");
+	private static final short ferryTagKey = TagDict.getInstance().xlate("mkgmap:ferry");
+	private static final short throughrouteTagKey = TagDict.getInstance().xlate("mkgmap:throughroute");
+	private static final short junctionTagKey = TagDict.getInstance().xlate("junction");
+	private static final short onewayTagKey = TagDict.getInstance().xlate("oneway");
+	
+	public static byte evalRouteTags(Element el) {
 		byte routeFlags = 0;
 
 		// Style has to set "yes"
diff --git a/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java b/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java
index b938058..1120ae8 100644
--- a/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java
+++ b/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java
@@ -54,6 +54,7 @@ public class AngleChecker {
 	private final int MIN_LOW_SPEED_ANGLE = 0x20;
 
 	private int mask;
+	private int mrnd;
 
 	// helper class to collect multiple arcs with (nearly) the same initial headings
 	private class ArcGroup {
@@ -95,7 +96,7 @@ public class AngleChecker {
 			while (modIH < -180)
 				modIH += 360; 
 			initialHeading = modIH;
-			imgHeading = (byte) (RouteArc.directionFromDegrees(initialHeading) & mask); 
+			imgHeading = calcEncodedBearing(initialHeading); 
 			
 			for (RouteArc arc : arcs){
 				arc.setInitialHeading(modIH);
@@ -107,6 +108,11 @@ public class AngleChecker {
 		}
 	}
 	
+	
+	private byte calcEncodedBearing (float b) {
+		return (byte) ((RouteArc.directionFromDegrees(b) + mrnd) & mask);
+	}
+	
 	public void config(EnhancedProperties props) {
 		// undocumented option - usually used for debugging only
 		ignoreSharpAngles = props.getProperty("ignore-sharp-angles", false);
@@ -129,6 +135,7 @@ public class AngleChecker {
 
 			for (RouteNode node : nodes.values()){
 				mask = 0xf0; // we assume compacted format
+				mrnd = 0x08; // rounding
 				fixSharpAngles(node, sharpAnglesCheckMask);				
 			}
 		}
@@ -393,7 +400,7 @@ public class AngleChecker {
 			}
 		}
 		for (ArcGroup ag : arcGroups){
-			ag.imgHeading = (byte) (RouteArc.directionFromDegrees(ag.initialHeading) & mask);
+			ag.imgHeading = calcEncodedBearing(ag.initialHeading);
 		}
 		return arcGroups;
 	}
@@ -401,7 +408,7 @@ public class AngleChecker {
 	/**
 	 * for log messages
 	 */
-	private String getCompassBearing (float bearing){
+	private static String getCompassBearing (float bearing){
 		float cb = (bearing + 360) % 360;
 		return Math.round(cb) + "°";
 	}
@@ -412,7 +419,7 @@ public class AngleChecker {
 	 * @param heading2
 	 * @return
 	 */
-	private int getImgAngle(byte heading1, byte heading2){
+	private static int getImgAngle(byte heading1, byte heading2){
 		int angle = heading2 - heading1;
 		if (angle < 0)
 			angle += 256;
diff --git a/src/uk/me/parabola/imgfmt/app/net/NETFile.java b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
index d871d93..0f9ab83 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NETFile.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NETFile.java
@@ -29,6 +29,7 @@ import uk.me.parabola.imgfmt.app.ImgFile;
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
 import uk.me.parabola.imgfmt.app.Label;
 import uk.me.parabola.imgfmt.app.lbl.City;
+import uk.me.parabola.imgfmt.app.srt.DoubleSortKey;
 import uk.me.parabola.imgfmt.app.srt.IntegerSortKey;
 import uk.me.parabola.imgfmt.app.srt.MultiSortKey;
 import uk.me.parabola.imgfmt.app.srt.Sort;
@@ -125,8 +126,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, 0, cache);
-
+				SortKey<LabeledRoadDef> nameKey = new IntegerSortKey<NETFile.LabeledRoadDef>(lrd, label.getOffset(), 0);
 				// If there is a city add it to the sort.
 				City city = (rd.getCities().isEmpty() ? null : rd.getCities().get(0)); // what if we more than one?
 				SortKey<LabeledRoadDef> cityKey;
@@ -176,11 +176,32 @@ public class NETFile extends ImgFile {
 
 		// Finish off the final set of duplicates.
 		addDisconnected(dupes, out);
-
+		sortByName(out);
 		return out;
 	}
 
 	/**
+	 * Sort by partial name first, then by full name.
+	 * @param roads list of labeled roads
+	 */
+	private void sortByName(List<LabeledRoadDef> roads) {
+		List<SortKey<LabeledRoadDef>> sortKeys = new ArrayList<>(roads.size());
+		Map<Label, byte[]> cachePartial = new HashMap<>();
+		Map<Label, byte[]> cacheFull = new HashMap<>();
+		// we have to use two different caches, as both use the label as key
+		for (LabeledRoadDef lrd : roads) {
+			SortKey<LabeledRoadDef> sk1 = sort.createSortKeyPartial(lrd, lrd.label, 0, cachePartial);
+			SortKey<LabeledRoadDef> sk2 = sort.createSortKey(null, lrd.label, 0, cacheFull);
+			sortKeys.add(new DoubleSortKey<>(sk1, sk2));
+		}
+		Collections.sort(sortKeys);
+		roads.clear();
+		for (SortKey<LabeledRoadDef> key : sortKeys) {
+			roads.add(key.getObject());
+		}		
+	}
+
+	/**
 	 * Take a set of roads with the same name/city etc and find sets of roads that do not
 	 * connect with each other. One of the members of each set is added to the road list.
 	 *
diff --git a/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java b/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
index e15d84d..6863f13 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NETFileReader.java
@@ -17,8 +17,10 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
 import uk.me.parabola.imgfmt.app.ImgFile;
@@ -280,19 +282,18 @@ public class NETFileReader extends ImgFile {
 		ImgFileReader reader = getReader();
 		reader.position(start);
 
-		List<Integer> offsets = new ArrayList<Integer>();
+		Set<Integer> allOffsets = new HashSet<>();
 		while (reader.position() < end) {
 			int net1 = reader.getu3();
 
 			// The offset is stored in the bottom 22 bits. The top 2 bits are an index into the list
-			// of lbl pointers in the net1 entry. Since we pick up all the labels at a particular net1
-			// entry we only need one of the offsets so pick the first one.
-			int idx = (net1 >> 22) & 0x3;
-			if (idx == 0)
-				offsets.add((net1 & 0x3fffff) << netHeader.getRoadShift());
+			// of lbl pointers in the net1 entry. 
+			allOffsets.add((net1 & 0x3fffff) << netHeader.getRoadShift());
 		}
 
 		// Sort in address order in the hope of speeding up reading.
+		List<Integer> offsets = new ArrayList<>(allOffsets);
+		allOffsets = null;
 		Collections.sort(offsets);
 		return offsets;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
index c04ad78..9d41578 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
@@ -54,9 +54,9 @@ public class RouteNode implements Comparable<RouteNode> {
 	private int offsetNod1 = -1;
 
 	// arcs from this node
-	private final List<RouteArc> arcs = new ArrayList<RouteArc>(4);
+	private final List<RouteArc> arcs = new ArrayList<>(4);
 	// restrictions at (via) this node
-	private final List<RouteRestriction> restrictions = new ArrayList<RouteRestriction>();
+	private final List<RouteRestriction> restrictions = new ArrayList<>();
 
 	private int flags;
 
@@ -212,14 +212,12 @@ public class RouteNode implements Comparable<RouteNode> {
 			for (RouteArc arc: arcs){
 				if (lastArc == null || lastArc.getIndexA() != arc.getIndexA() || lastArc.isForward() != arc.isForward()){
 					int dir = RouteArc.directionFromDegrees(arc.getInitialHeading());
-					dir = dir & 0xf0;
+					dir = (dir + 8) & 0xf0;
 					if (initialHeadings.contains(dir)){
 						useCompactDirs = false;
 						break;
 					}
 					initialHeadings.add(dir);
-				} else {
-					// 
 				}
 				lastArc = arc;
 			}
@@ -347,55 +345,151 @@ public class RouteNode implements Comparable<RouteNode> {
 	}
 
 	public void checkRoundabouts() {
-		List<RouteArc> roundaboutArcs = new ArrayList<RouteArc>();
+		List<RouteArc> roundaboutArcs = new ArrayList<>();
+		List<RouteArc> nonRoundaboutArcs = new ArrayList<>();
 		int countNonRoundaboutRoads = 0;
 		int countNonRoundaboutOtherHighways = 0;
-		RouteArc roundaboutArc = null;
-		for(RouteArc a : arcs) {
-			// ignore ways that have been synthesised by mkgmap
-			RoadDef r = a.getRoadDef();
-			if (!r.isSynthesised() && a.isDirect()){
-				if(r.isRoundabout())
-				{
-					roundaboutArcs.add(a);
-					if (roundaboutArc == null)
-						roundaboutArc = a;
-				}
-				else {
-					// ignore footpaths and ways with no access
-					byte access = r.getAccess();
-					if ((access & AccessTagsAndBits.CAR) != 0)
-						countNonRoundaboutRoads++;
-					else if ((access & (AccessTagsAndBits.BIKE | AccessTagsAndBits.BUS | AccessTagsAndBits.TAXI | AccessTagsAndBits.TRUCK)) != 0)
-						countNonRoundaboutOtherHighways++;
+		int countHighwaysInsideRoundabout = 0;
+		for(RouteArc ra : arcs) {
+			if (ra.isDirect()) {
+				// ignore ways that have been synthesised by mkgmap
+				RoadDef rd = ra.getRoadDef();
+				if (!rd.isSynthesised()) {
+					if (rd.isRoundabout())
+						roundaboutArcs.add(ra);
+					else
+						nonRoundaboutArcs.add(ra);
 				}
 			}
 		}
-			
-		if(arcs.size() > 1 && roundaboutArcs.size() == 1)
-			log.warn("Roundabout",roundaboutArc.getRoadDef(),roundaboutArc.isForward() ? "starts at" : "ends at", coord.toOSMURL());
 		if (roundaboutArcs.size() > 0) {
-			if (countNonRoundaboutRoads > 1)
-				log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to more than one road at",coord.toOSMURL());
-			else if ((countNonRoundaboutRoads == 1) && (countNonRoundaboutOtherHighways > 0))
-				log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to a road and",countNonRoundaboutOtherHighways,"other highways at",coord.toOSMURL());
-		}
-		if(roundaboutArcs.size() > 2) {
-			for(RouteArc fa : arcs) {
-				if(fa.isForward() && fa.isDirect()) {
-					RoadDef rd = fa.getRoadDef();
-					for(RouteArc fb : arcs) {
-						if(fb != fa && fb.isDirect() && 
-						   fa.getPointsHash() == fb.getPointsHash() &&
-						   ((fb.isForward() && fb.getDest() == fa.getDest()) ||
-							(!fb.isForward() && fb.getSource() == fa.getDest()))) {
-							if(!rd.messagePreviouslyIssued("roundabout forks/overlaps")) {
-								log.warn("Roundabout " + rd + " overlaps " + fb.getRoadDef() + " at " + coord.toOSMURL());
+			// get the coordinates of a box bounding the junctions of the roundabout
+			int minRoundaboutLat = coord.getHighPrecLat();
+			int maxRoundaboutLat = minRoundaboutLat;
+			int minRoundaboutLon = coord.getHighPrecLon();
+			int maxRoundaboutLon = minRoundaboutLon;
+			List<RouteNode> processedNodes = new ArrayList<>();
+			processedNodes.add(this);
+			for (RouteArc ra : roundaboutArcs) {
+				if (ra.isForward()) {
+					for (RouteArc ra1 : nonRoundaboutArcs) {
+						if ((ra1.getDirectHeading() == ra.getDirectHeading()) && (ra1.getInitialHeading() == ra.getInitialHeading()) && (ra1.getFinalHeading() == ra.getFinalHeading()) && (ra1.getLengthInMeter() == ra.getLengthInMeter())) {
+							// non roundabout highway overlaps roundabout
+							nonRoundaboutArcs.remove(ra1);
+							if(!ra.getRoadDef().messagePreviouslyIssued("roundabout forks/overlaps"))
+								log.warn("Highway",ra1.getRoadDef(), "overlaps roundabout", ra.getRoadDef(), "at",coord.toOSMURL());
+							break;
+						}						
+					}
+					RouteNode rn = ra.getDest();
+					while (rn != null && !processedNodes.contains(rn)) {
+						processedNodes.add(rn);
+						int lat = rn.coord.getHighPrecLat();
+						int lon = rn.coord.getHighPrecLon();
+						minRoundaboutLat = Math.min(minRoundaboutLat, lat);
+						maxRoundaboutLat = Math.max(maxRoundaboutLat, lat);
+						minRoundaboutLon = Math.min(minRoundaboutLon, lon);
+						maxRoundaboutLon = Math.max(maxRoundaboutLon, lon);
+						RouteNode nrn = null;
+						for (RouteArc nra : rn.arcs) {
+							if (nra.isDirect() && nra.isForward()) {
+								RoadDef nrd = nra.getRoadDef();
+								if (nrd.isRoundabout() && !nrd.isSynthesised())
+									nrn = nra.getDest();
 							}
 						}
-						else if(fa != fb && fb.isForward()) {
-							if(!rd.messagePreviouslyIssued("roundabout forks/overlaps")) {
-								log.warn("Roundabout " + rd + " forks at " + coord.toOSMURL());
+						rn = nrn;
+					}
+				}
+			}
+			if (nonRoundaboutArcs.size() > 1) {
+				// get an approximate centre of the roundabout
+				Coord roundaboutCentre =  Coord.makeHighPrecCoord((minRoundaboutLat + maxRoundaboutLat) / 2, (minRoundaboutLon + maxRoundaboutLon) / 2);
+				for (RouteArc ra : nonRoundaboutArcs) {
+					double distanceToCentre = roundaboutCentre.distance(coord);
+					RoadDef rd = ra.getRoadDef();
+					// ignore footpaths and ways with no access
+					byte access = rd.getAccess();
+					if (access != 0 && (access != AccessTagsAndBits.FOOT)) {
+						// check whether the way is inside the roundabout by seeing if the next point is nearer to the centre of the bounding box than this
+						RouteNode nextNode = ra.getSource().coord == coord ? ra.getDest() : ra.getSource();
+						Coord nextCoord = nextNode.coord;
+						for (RouteNode roundaboutNode : processedNodes) {
+							if (roundaboutNode.coord.equals(nextCoord)) {
+								// arc rejoins roundabout, so calculate another point to use half the distance away at the initial bearing
+								double heading1 = ra.getSource().coord == coord ? ra.getInitialHeading() : 180 + ra.getFinalHeading();
+								double distance = coord.distance(nextCoord) / 2;
+								Coord nextCoord1 = coord.offset(heading1, distance);
+								// now calculate a point the same distance away from the end point 180 degrees from the final bearing
+								double heading2 = ra.getSource().coord == coord ? 180 + ra.getFinalHeading() : ra.getInitialHeading();
+								Coord nextCoord2 = nextCoord.offset(heading2, distance);
+								double distanceToCentreOfNextCoord = roundaboutCentre.distance(nextCoord);
+								// use the point which has a bigger difference in distance from the centre to increase accuracy
+								if (Math.abs(distanceToCentre - roundaboutCentre.distance(nextCoord1)) >= Math.abs(distanceToCentreOfNextCoord - roundaboutCentre.distance(nextCoord2)))
+									nextCoord = nextCoord1;
+								else {
+									distanceToCentre = distanceToCentreOfNextCoord;
+									nextCoord = nextCoord2;
+								}
+								break;
+							}
+						}
+						double nextDistanceToCentre = roundaboutCentre.distance(nextCoord);
+						if (Math.abs(nextDistanceToCentre - distanceToCentre) < 2)
+							log.info("Way",rd,"unable to accurately determine whether",nextCoord.toOSMURL()," is inside roundabout");
+						if (nextDistanceToCentre < distanceToCentre)
+							countHighwaysInsideRoundabout++;
+						else {
+							if ((access & AccessTagsAndBits.CAR) != 0)
+								countNonRoundaboutRoads++;
+							else if ((access & (AccessTagsAndBits.BIKE | AccessTagsAndBits.BUS | AccessTagsAndBits.TAXI | AccessTagsAndBits.TRUCK)) != 0)
+								countNonRoundaboutOtherHighways++;
+						}
+					}
+				}
+			
+				RouteArc roundaboutArc = roundaboutArcs.get(0);
+				if (arcs.size() > 1 && roundaboutArcs.size() == 1)
+					log.warn("Roundabout",roundaboutArc.getRoadDef(),roundaboutArc.isForward() ? "starts at" : "ends at", coord.toOSMURL());
+				if (countNonRoundaboutRoads > 1)
+					log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to more than one road at",coord.toOSMURL());
+				else if (countNonRoundaboutRoads == 1) {
+					if (countNonRoundaboutOtherHighways > 0) {
+						if (countHighwaysInsideRoundabout > 0)
+							log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to a road",countNonRoundaboutOtherHighways,"other highway(s) and",countHighwaysInsideRoundabout,"highways inside the roundabout at",coord.toOSMURL());
+						else
+							log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to a road and",countNonRoundaboutOtherHighways,"other highway(s) at",coord.toOSMURL());
+					}
+					else if (countHighwaysInsideRoundabout > 0)
+						log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to a road and",countHighwaysInsideRoundabout,"highway(s) inside the roundabout at",coord.toOSMURL());
+				}
+				else if (countNonRoundaboutOtherHighways > 0) {
+					if (countHighwaysInsideRoundabout > 0)
+						log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to",countNonRoundaboutOtherHighways,"highway(s) and",countHighwaysInsideRoundabout,"inside the roundabout at",coord.toOSMURL());
+					else if (countNonRoundaboutOtherHighways > 1)
+						log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to",countNonRoundaboutOtherHighways,"highways at",coord.toOSMURL());
+				}
+				else if (countHighwaysInsideRoundabout > 1)
+					log.warn("Roundabout",roundaboutArc.getRoadDef(),"is connected to",countHighwaysInsideRoundabout,"highways inside the roundabout at",coord.toOSMURL());
+				if(roundaboutArcs.size() > 2) {
+					for(RouteArc fa : roundaboutArcs) {
+						if(fa.isForward()) {
+							RoadDef rd = fa.getRoadDef();
+							for(RouteArc fb : roundaboutArcs) {
+								if(fb != fa) { 
+									if(fa.getPointsHash() == fb.getPointsHash() &&
+									   ((fb.isForward() && fb.getDest() == fa.getDest()) ||
+										(!fb.isForward() && fb.getSource() == fa.getDest()))) {
+										if(!rd.messagePreviouslyIssued("roundabout forks/overlaps")) {
+											log.warn("Roundabout " + rd + " overlaps " + fb.getRoadDef() + " at " + coord.toOSMURL());
+										}
+									}
+									else if(fb.isForward()) {
+										if(!rd.messagePreviouslyIssued("roundabout forks/overlaps")) {
+											log.warn("Roundabout " + rd + " forks at " + coord.toOSMURL());
+										}
+									}
+								}
 							}
 						}
 					}
@@ -406,7 +500,7 @@ public class RouteNode implements Comparable<RouteNode> {
 
 	// determine "distance" between two nodes on a roundabout
 	private static int roundaboutSegmentLength(final RouteNode n1, final RouteNode n2) {
-		List<RouteNode> seen = new ArrayList<RouteNode>();
+		List<RouteNode> seen = new ArrayList<>();
 		int len = 0;
 		RouteNode n = n1;
 		boolean checkMoreLinks = true;
@@ -446,7 +540,7 @@ public class RouteNode implements Comparable<RouteNode> {
 			// follow the arc to find the first node that connects the
 			// roundabout to a non-roundabout segment
 			RouteNode nb = r.getDest();
-			List<RouteNode> seen = new ArrayList<RouteNode>();
+			List<RouteNode> seen = new ArrayList<>();
 			seen.add(this);
 
 			while (true) {
@@ -582,7 +676,7 @@ public class RouteNode implements Comparable<RouteNode> {
 
 	public void addThroughRoute(long roadIdA, long roadIdB) {
 		if(throughRoutes == null)
-			throughRoutes = new ArrayList<RouteArc[]>();
+			throughRoutes = new ArrayList<>();
 		boolean success = false;
 		for(RouteArc arc1 : arcs) {
 			if(arc1.getRoadDef().getId() == roadIdA) {
diff --git a/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java b/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
index c73ccde..03a2d87 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/CodePosition.java
@@ -19,7 +19,7 @@ import java.text.Collator;
  *
  * @author Steve Ratcliffe
  */
-class CodePosition {
+public class CodePosition {
 	private char primary;
 	private byte secondary;
 	private byte tertiary;
diff --git a/src/uk/me/parabola/imgfmt/app/srt/DoubleSortKey.java b/src/uk/me/parabola/imgfmt/app/srt/DoubleSortKey.java
new file mode 100644
index 0000000..71b9d3e
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/srt/DoubleSortKey.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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;
+
+/**
+ * Combines a pair of sort keys into one. The first is the primary sort and contains the
+ * actual object being sorted.
+ * 
+ * @author Steve Ratcliffe
+ * @author Gerd Petermann
+ */
+public class DoubleSortKey<T> implements SortKey<T> {
+	private final SortKey<T> key1;
+	private final SortKey<T> key2;
+
+	public DoubleSortKey(SortKey<T> key1, SortKey<T> key2) {
+		this.key1 = key1;
+		this.key2 = key2;
+	}
+
+	public T getObject() {
+		return key1.getObject();
+	}
+
+	public int compareTo(SortKey<T> o) {
+		DoubleSortKey<T> other = (DoubleSortKey<T>) o;
+		int res = key1.compareTo(other.key1);
+		if (res == 0) {
+			res = key2.compareTo(other.key2);
+		}
+		return res;
+	}
+}
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
index b23082b..ab2aac5 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java
@@ -30,7 +30,7 @@ import uk.me.parabola.imgfmt.fs.ImgChannel;
  */
 public class SRTFile extends ImgFile {
 
-	private final SRTHeader header;
+	private SRTHeader header;
 
 	private Sort sort;
 	private boolean isMulti;
@@ -40,15 +40,9 @@ public class SRTFile extends ImgFile {
 	private final List<Integer> srt8Starts = new ArrayList<>();
 
 	public SRTFile(ImgChannel chan) {
-		header = new SRTHeader();
-		setHeader(header);
-
 		BufferedImgFileWriter fileWriter = new BufferedImgFileWriter(chan);
 		fileWriter.setMaxSize(Long.MAX_VALUE);
 		setWriter(fileWriter);
-
-		// Position at the start of the writable area.
-		position(header.getHeaderLength());
 	}
 
 	/**
@@ -59,9 +53,13 @@ public class SRTFile extends ImgFile {
 	public void write() {
 		ImgFileWriter writer = getWriter();
 		writeDescription(writer);
-
+		// Position at the start of the writable area.
+		position(header.getHeaderLength());
 		SectionWriter subWriter = header.makeSectionWriter(writer);
-		subWriter.position(sort.isMulti()? SRTHeader.HEADER3_MULTI_LEN: SRTHeader.HEADER3_LEN);
+		int header3Len = sort.getHeader3Len();
+		if (header3Len == 0)
+			header3Len = sort.isMulti()? SRTHeader.HEADER3_MULTI_LEN: SRTHeader.HEADER3_LEN;
+		subWriter.position(header3Len);
 		writeSrt4Chars(subWriter);
 		writeSrt5Expansions(subWriter);
 		if (sort.isMulti()) {
@@ -104,13 +102,22 @@ public class SRTFile extends ImgFile {
 	}
 
 	private void writeWeights(ImgFileWriter writer, int i) {
+		int primary = sort.getPrimary(i);
+		int secondary = sort.getSecondary(i);
+		int tertiary = sort.getTertiary(i);
 		if (isMulti) {
-			writer.putChar((char) sort.getPrimary(i));
-			writer.put((byte) sort.getSecondary(i));
-			writer.put((byte) sort.getTertiary(i));
+			assert primary <= 0xffff;
+			assert secondary <= 0xff;
+			assert tertiary <= 0xff;
+			writer.putChar((char) primary);
+			writer.put((byte) secondary);
+			writer.put((byte) tertiary);
 		} else {
-			writer.put((byte) sort.getPrimary(i));
-			writer.put((byte) ((sort.getTertiary(i) << 4) | (sort.getSecondary(i) & 0xf)));
+			assert primary <= 0xff;
+			assert secondary <= 0xf;
+			assert tertiary <= 0xf;
+			writer.put((byte) primary);
+			writer.put((byte) ((tertiary << 4) | (secondary & 0xf)));
 		}
 	}
 
@@ -166,6 +173,7 @@ public class SRTFile extends ImgFile {
 
 	public void setSort(Sort sort) {
 		this.sort = sort;
+		header = new SRTHeader(sort.getHeaderLen());
 		header.setSort(sort);
 		description = sort.getDescription();
 		isMulti = sort.isMulti();
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java b/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
index 490f493..eefe2b6 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SRTHeader.java
@@ -28,34 +28,41 @@ import uk.me.parabola.imgfmt.app.SectionWriter;
  */
 public class SRTHeader extends CommonHeader {
 	// The header length we are using for the SRT file
-	private static final int HEADER_LEN = 29;
+	public static final int HEADER_LEN = 29; // newer files use 37
 	protected static final int HEADER2_LEN = 16;
 	protected static final int HEADER3_LEN = 52;
 	protected static final int HEADER3_MULTI_LEN = 92;
 
 	// The section structure of this file is somewhat different to other
 	// files, but I am still going to model it using Section.
-	private final Section header = new Section();
+	private final Section header2 = new Section();
 
-	private final Section desc = new Section(header);
+	private final Section desc = new Section(header2);
 	private final Section subheader = new Section(desc);
 	private final Section chartab = new Section((char) 3);
 	private final Section expansions = new Section(chartab, (char) 2);
 	private final Section srt8 = new Section(expansions, (char) 5);
 	private final Section srt7 = new Section(srt8, (char) 4);
+	
 
 	private Sort sort;
 
 	public SRTHeader() {
-		super(HEADER_LEN, "GARMIN SRT");
-		header.setPosition(HEADER_LEN);
-		header.setSize(16);
+		this(HEADER_LEN);
+	}
+
+	public SRTHeader(int headerLen) {
+		super(headerLen, "GARMIN SRT");
+		header2.setPosition(headerLen); 
+		header2.setSize(16);
 
 		chartab.setPosition(HEADER3_LEN);
 	}
 
 	protected void readFileHeader(ImgFileReader reader) throws ReadFailedException {
-		throw new UnsupportedOperationException("not implemented yet");
+		reader.getChar(); // expected: 1
+		header2.setPosition(reader.getInt());
+		header2.setSize(reader.getChar());
 	}
 
 	/**
@@ -63,10 +70,29 @@ public class SRTHeader extends CommonHeader {
 	 * to an area which is itself just a header.
 	 */
 	protected void writeFileHeader(ImgFileWriter writer) {
-		writer.putChar((char) 1);
+		if (getHeaderLength() <= 29) {
+			writer.putChar((char) 1);
 
-		writer.putInt(header.getPosition());
-		writer.putChar((char) header.getSize());
+			writer.putInt(header2.getPosition());
+			writer.putChar((char) header2.getSize());
+		} else {
+			// this appears in unicode maps dated 2016 and 2017
+			writer.putChar((char) 0);
+			writer.putInt(header2.getPosition());
+			writer.putChar((char) header2.getSize());
+			writer.putChar((char) 1);
+			writer.putInt(header2.getPosition());
+			writer.putChar((char) header2.getSize());
+			
+			// older maps contain this variant (note the different order)
+//			writer.putChar((char) 1);
+//			writer.putInt(header2.getPosition());
+//			writer.putChar((char) header2.getSize());
+//			writer.putChar((char) 0);
+//			writer.putInt(desc.getPosition()); 
+//			writer.putChar((char) 0x10);  
+			
+		}
 	}
 
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/srt/Sort.java b/src/uk/me/parabola/imgfmt/app/srt/Sort.java
index 8304f4b..31076dd 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/Sort.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/Sort.java
@@ -22,7 +22,7 @@ import java.nio.charset.CodingErrorAction;
 import java.text.CollationKey;
 import java.text.Collator;
 import java.util.ArrayList;
-import java.util.Iterator;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -60,7 +60,7 @@ public class Sort {
 	private String description;
 	private Charset charset;
 
-	private final Page[] pages = new Page[256];
+	private Page[] pages = new Page[256];
 
 	private final List<CodePosition> expansions = new ArrayList<>();
 	private int maxExpSize = 1;
@@ -68,6 +68,8 @@ public class Sort {
 	private CharsetEncoder encoder;
 	private boolean multi;
 	private int maxPage;
+	private int headerLen = SRTHeader.HEADER_LEN; 
+	private int header3Len = -1;
 
 	public Sort() {
 		pages[0] = new Page();
@@ -82,8 +84,39 @@ public class Sort {
 		setTertiary( ch, tertiary);
 
 		setFlags(ch, flags);
+		int numExp = (flags >> 4) & 0xf;
+		if (numExp + 1 > maxExpSize)
+			maxExpSize = numExp + 1;
 	}
 
+	public char[] encode(String s) {
+		char[] chars = null;
+		try {
+			if (isMulti()) {
+				chars = s.toCharArray();
+			} else {
+				ByteBuffer out = encoder.encode(CharBuffer.wrap(s));
+				byte[] bval = out.array();
+				chars = new char[bval.length];
+				for (int i = 0; i < bval.length; i++)
+					chars[i] = (char) (bval[i] & 0xff);
+			}
+		} catch (CharacterCodingException e) {
+		}
+		return chars;
+	}
+	
+	/**
+	 * Get the prefix of the name of the given length. 
+	 * @param name the name 
+	 * @param prefixLen the length
+	 * @return String with wanted length, possibly padded with trailing zeros.
+	 */
+	public char[] getPrefix(String name, int prefixLen) {
+		char[] chars = encode(name);
+		return Arrays.copyOf(chars, prefixLen);
+	}
+	
 	/**
 	 * Run after all sorting order points have been added.
 	 *
@@ -100,7 +133,7 @@ public class Sort {
 				continue;
 
 			for (int i = 0; i < 256; i++) {
-				if (((p.flags[i] >>> 4) & 0x3) == 0) {
+				if (((p.flags[i] >>> 4) & 0xf) == 0) {
 					if (p.getPrimary(i) != 0) {
 						byte second = p.getSecondary(i);
 						maxSecondary = Math.max(maxSecondary, second);
@@ -117,7 +150,7 @@ public class Sort {
 				continue;
 
 			for (int i = 0; i < 256; i++) {
-				if (((p.flags[i] >>> 4) & 0x3) != 0) continue;
+				if (((p.flags[i] >>> 4) & 0xf) != 0) continue;
 
 				if (p.getPrimary(i) == 0) {
 					if (p.getSecondary(i) == 0) {
@@ -165,6 +198,9 @@ public class Sort {
 	 * @return A sort key.
 	 */
 	public <T> SortKey<T> createSortKey(T object, String s, int second, Map<String, byte[]> cache) {
+		if (s.length() == 0)
+			return new SrtSortKey<>(object, ZERO_KEY, second);
+		
 		// If there is a cache then look up and return the key.
 		// This is primarily for memory management, not for speed.
 		byte[] key;
@@ -185,23 +221,7 @@ public class Sort {
 				for (int i = 0; i < bval.length; i++)
 					chars[i] = (char) (bval[i] & 0xff);
 			}
-
-			// In theory you could have a string where every character expands into maxExpSize separate characters
-			// in the key.  However if we allocate enough space to deal with the worst case, then we waste a
-			// 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[(chars.length + 1 + 2) * 4];
-			try {
-				fillCompleteKey(chars, key);
-			} catch (ArrayIndexOutOfBoundsException e) {
-				// Ok try again with the max possible key size allocated.
-				key = new byte[(chars.length+1) * 4 * maxExpSize];
-				fillCompleteKey(chars, key);
-			}
-
+			key = makeKey(chars);
 			if (cache != null)
 				cache.put(s, key);
 
@@ -223,6 +243,8 @@ public class Sort {
 	 * @return A sort key.
 	 */
 	public <T> SortKey<T> createSortKey(T object, Label label, int second, Map<Label, byte[]> cache) {
+		if (label.getLength() == 0)
+			return new SrtSortKey<>(object, ZERO_KEY, second);
 		byte[] key;
 		if (cache != null) {
 			key = cache.get(label);
@@ -231,23 +253,69 @@ public class Sort {
 		}
 
 		char[] encText = label.getEncText();
+		key = makeKey(encText);
+		if (cache != null)
+			cache.put(label, key);
 
-		// In theory you could have a string where every character expands into maxExpSize separate characters
-		// in the key.  However if we allocate enough space to deal with the worst case, then we waste a
-		// vast amount of memory. So allocate a minimal amount of space, try it and if it fails reallocate the
-		// maximum amount.
-		//
-		// We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete
-		// german map this was always enough in tests.
-		key = new byte[(encText.length + 1 + 2) * 4];
-		try {
-			fillCompleteKey(encText, key);
-		} catch (ArrayIndexOutOfBoundsException e) {
-			// Ok try again with the max possible key size allocated.
-			key = new byte[encText.length * 4 * maxExpSize + 4];
-			fillCompleteKey(encText, key);
+		return new SrtSortKey<>(object, key, second);
+	}
+
+	/**
+	 * Create a sort key based on a Label, return key for partial name if prefix / suffix is found, else key for full name.
+	 *
+	 * The label will contain the actual characters (after transliteration for example)
+	 * @param object This is saved in the sort key for later retrieval and plays no part in the sorting.
+	 * @param label The label, the actual written bytes/chars will be used as input to the sort.
+	 * @param second Secondary sort key.
+	 * @param cache A cache for the created keys. This is for saving memory so it is essential that this
+	 * is managed by the caller.
+	 * @return A sort key.
+	 */
+	public <T> SortKey<T> createSortKeyPartial(T object, Label label, int second, Map<Label, byte[]> cache) {
+		if (label.getLength() == 0)
+			return new SrtSortKey<>(object, ZERO_KEY, second);
+		byte[] key;
+		if (cache != null) {
+			key = cache.get(label);
+			if (key != null)
+				return new SrtSortKey<>(object, key, second);
 		}
 
+		char[] encText = label.getEncText();
+		int prefix = -1;
+		for (int i = 0; i < encText.length; i++) {
+			char c = encText[i];
+			if (c == 0x1e || c == 0x1b) {
+				prefix = i;
+				break;
+			}
+		}
+		int suffix = -1;
+		for (int i = 0; i < encText.length; i++) {
+			char c = encText[i];
+			if (c == 0x1f || c == 0x1c) {
+				suffix = i;
+				break;
+			}
+		}
+		
+		if (prefix > 0 || suffix > 0) {
+			int partLen;
+			if (prefix > 0 && suffix > 0)
+				partLen = suffix - prefix-1;
+			else if (prefix > 0) {
+				partLen = encText.length - (prefix + 1);
+			}
+			else {
+				partLen = suffix ;
+			}
+			// extract partial name without creating trailing zeros
+			char[] newEncText = new char[partLen];
+			System.arraycopy(encText, prefix+1, newEncText, 0, partLen); 
+			encText = newEncText;
+		}
+		 
+		key = makeKey(encText);
 		if (cache != null)
 			cache.put(label, key);
 
@@ -280,15 +348,48 @@ public class Sort {
 	}
 
 	/**
+	 * Create the key and trim it to the needed length if that saves memory.
+	 * @param chars character array
+	 * @return byte array 
+	 */
+	private byte[] makeKey(char[] chars) {
+		// 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.
+		byte[] key = new byte[(chars.length + 1 + 2) * 4];
+		int needed = 0;
+		try {
+			needed = fillCompleteKey(chars, key);
+		} catch (ArrayIndexOutOfBoundsException e) {
+			// Ok try again with the max possible key size allocated.
+			key = new byte[(chars.length+1) * 4 * maxExpSize];
+			needed = fillCompleteKey(chars, key);
+		}
+		// check if we can save bytes by copying
+		int neededBytes = needed;
+		int padding2 = 8 - (needed & 7);
+		if (padding2 != 8)
+			neededBytes += padding2;
+		if (neededBytes < key.length)
+			key = Arrays.copyOf(key, needed);
+		return key;
+	}
+ 	
+	/**
 	 * Fill in the key from the given byte string.
 	 *
 	 * @param bVal The string for which we are creating the sort key.
 	 * @param key The sort key. This will be filled in.
+	 * @return the needed number of bytes in case the buffer was large enough
 	 */
-	private void fillCompleteKey(char[] bVal, byte[] key) {
+	private int fillCompleteKey(char[] bVal, byte[] key) {
 		int start = fillKey(Collator.PRIMARY, bVal, key, 0);
 		start = fillKey(Collator.SECONDARY, bVal, key, start);
-		fillKey(Collator.TERTIARY, bVal, key, start);
+		return fillKey(Collator.TERTIARY, bVal, key, start);
 	}
 
 	/**
@@ -306,7 +407,7 @@ public class Sort {
 			if (!hasPage(c >>> 8))
 				continue;
 
-			int exp = (getFlags(c) >> 4) & 0x3;
+			int exp = (getFlags(c) >> 4) & 0xf;
 			if (exp == 0) {
 				index = writePos(type, c, outKey, index);
 			} else {
@@ -406,41 +507,6 @@ public class Sort {
 	}
 
 	/**
-	 * Add an expansion to the sort.
-	 * An expansion is a letter that sorts as if it were two separate letters.
-	 *
-	 * The case were two letters sort as if the were just one (and more complex cases) are
-	 * not supported or are unknown to us.
-	 *
-	 * @param ch The code point of this letter in the code page.
-	 * @param inFlags The initial flags, eg if it is a letter or not.
-	 * @param expansionList The letters that this letter sorts as, as code points in the codepage.
-	 */
-	public void addExpansion(int ch, int inFlags, List<Integer> expansionList) {
-		ensurePage(ch >>> 8);
-		setFlags(ch, (byte) ((inFlags & 0xf) | (((expansionList.size()-1) << 4) & 0xf0)));
-
-		// Check for repeated definitions
-		if (getPrimary(ch) != 0)
-			throw new ExitException(String.format("repeated code point %x", ch));
-
-		setPrimary(ch, (expansions.size() + 1));
-		setSecondary(ch,  0);
-		setTertiary(ch, 0);
-		maxExpSize = Math.max(maxExpSize, expansionList.size());
-
-		for (Integer b : expansionList) {
-			CodePosition cp = new CodePosition();
-			cp.setPrimary((char) getPrimary(b & 0xff));
-
-			// Currently sort without secondary or tertiary differences to the base letters.
-			cp.setSecondary((byte) getSecondary(b & 0xff));
-			cp.setTertiary((byte) getTertiary(b & 0xff));
-			expansions.add(cp);
-		}
-	}
-
-	/**
 	 * Get the expansion with the given index, one based.
 	 * @param val The one-based index number of the extension.
 	 */
@@ -520,6 +586,8 @@ public class Sort {
 	 */
 	private void ensurePage(int n) {
 		assert n == 0 || isMulti();
+		if (n > pages.length)
+			pages = Arrays.copyOf(pages, n + 1);
 		if (this.pages[n] == null) {
 			this.pages[n] = new Page();
 			if (n > maxPage)
@@ -528,6 +596,14 @@ public class Sort {
 	}
 
 	/**
+	 * Allocate space for up to n pages.
+	 * @param n
+	 */
+	public void setMaxPage(int n) {
+		pages = Arrays.copyOf(pages, n + 1);
+	}
+	
+	/**
 	 * The max page, top 8+ bits of the character that we have information on.
 	 */
 	public int getMaxPage() {
@@ -621,7 +697,7 @@ public class Sort {
 	 *
 	 * This implementation has the same effect when used for sorting as the sort keys.
 	 */
-	private class SrtCollator extends Collator {
+	public class SrtCollator extends Collator {
 		private final int codepage;
 
 		private SrtCollator(int codepage) {
@@ -629,6 +705,9 @@ public class Sort {
 		}
 
 		public int compare(String source, String target) {
+			if (source == target)
+				return 0;
+			
 			char[] chars1;
 			char[] chars2;
 			if (isMulti()) {
@@ -670,9 +749,7 @@ public class Sort {
 		 * @param char2 Bytes for the second string in the codepage encoding.
 		 * @return Comparison result -1, 0 or 1.
 		 */
-		private int compareOneStrength(char[] char1, char[] char2, int type) {
-			int res = 0;
-
+		public int compareOneStrength(char[] char1, char[] char2, int type) {
 			PositionIterator it1 = new PositionIterator(char1, type);
 			PositionIterator it2 = new PositionIterator(char2, type);
 
@@ -681,15 +758,39 @@ public class Sort {
 				int p2 = it2.next();
 
 				if (p1 < p2) {
-					res = -1;
-					break;
+					return -1;
 				} else if (p1 > p2) {
-					res = 1;
-					break;
+					return 1;
 				}
 			}
 
-			return res;
+			return 0;
+		}
+
+		/**
+		 * Compare the bytes against primary, secondary or tertiary arrays.
+		 * @param char1 Bytes for the first string in the codepage encoding.
+		 * @param char2 Bytes for the second string in the codepage encoding.
+		 * @return Comparison result -1, 0 or 1.
+		 */
+		public int compareOneStrengthWithLength(char[] char1, char[] char2, int type, int len) {
+			PositionIterator it1 = new PositionIterator(char1, type);
+			PositionIterator it2 = new PositionIterator(char2, type);
+
+			int todo = len;
+			while (it1.hasNext() || it2.hasNext()) {
+				int p1 = it1.next();
+				int p2 = it2.next();
+				if (--todo < 0)
+					return 0;
+				if (p1 < p2) {
+					return -1;
+				} else if (p1 > p2) {
+					return 1;
+				}
+			}
+
+			return 0;
 		}
 
 		public CollationKey getCollationKey(String source) {
@@ -710,7 +811,7 @@ public class Sort {
 			return codepage;
 		}
 
-		class PositionIterator implements Iterator<Integer> {
+		class PositionIterator {
 			private final char[] chars;
 			private final int len;
 			private final int type;
@@ -738,7 +839,7 @@ public class Sort {
 			 * @return The next non-ignored sort position. At the end of the string it returns
 			 * NO_ORDER.
 			 */
-			public Integer next() {
+			public int next() {
 				int next;
 				if (expPos == 0) {
 
@@ -755,7 +856,7 @@ public class Sort {
 							continue;
 						}
 
-						int nExpand = (getFlags(c) >> 4) & 0x3;
+						int nExpand = (getFlags(c) >> 4) & 0xf;
 						// Check if this is an expansion.
 						if (nExpand > 0) {
 							expStart = getPrimary(c) - 1;
@@ -778,10 +879,30 @@ public class Sort {
 
 				return next;
 			}
-
-			public void remove() {
-				throw new UnsupportedOperationException("remove not supported");
-			}
 		}
 	}
+
+	public void setExpansions(List<CodePosition> expansionList) {
+		expansions.clear();
+		expansions.addAll(expansionList);
+	}
+
+	public int getHeaderLen() {
+		return headerLen;
+	}
+
+	public void setHeaderLen(int headerLen) {
+		this.headerLen = headerLen;
+	}
+
+	public int getHeader3Len() {
+		if (header3Len < 0)
+			header3Len = isMulti() ? SRTHeader.HEADER3_MULTI_LEN : SRTHeader.HEADER3_LEN;
+		return header3Len;
+	}
+
+	public void setHeader3Len(int header3Len) {
+		this.header3Len = header3Len;
+	}
+
 }
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SrtFileReader.java b/src/uk/me/parabola/imgfmt/app/srt/SrtFileReader.java
new file mode 100644
index 0000000..bae0bc0
--- /dev/null
+++ b/src/uk/me/parabola/imgfmt/app/srt/SrtFileReader.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
+import uk.me.parabola.imgfmt.app.ImgFile;
+import uk.me.parabola.imgfmt.app.ImgFileReader;
+import uk.me.parabola.imgfmt.app.Section;
+import uk.me.parabola.imgfmt.app.labelenc.CharacterDecoder;
+import uk.me.parabola.imgfmt.app.labelenc.CodeFunctions;
+import uk.me.parabola.imgfmt.fs.DirectoryEntry;
+import uk.me.parabola.imgfmt.fs.FileSystem;
+import uk.me.parabola.imgfmt.fs.ImgChannel;
+import uk.me.parabola.imgfmt.sys.FileImgChannel;
+import uk.me.parabola.imgfmt.sys.ImgFS;
+
+/**
+ * The sort file.
+ * Work in progress.
+ * 
+
+ * @author Gerd Petermann
+ *
+ */
+public class SrtFileReader extends ImgFile {
+	private Sort sort;
+	private final CharacterDecoder decoder;
+	private SRTHeader header = new SRTHeader();
+	private Section description = new Section();
+	private Section tableHeader = new Section();
+	private Section characterTable = new Section();
+	private Section srt5 = new Section();
+	private Section srt6 = new Section();
+	private Section srt7 = new Section();
+	private Section srt8 = new Section();
+	private int countExp; 
+	private final Map<Integer, Integer> offsetToBlock = new HashMap<>();
+	
+	public SrtFileReader (ImgChannel chan) {
+		CodeFunctions funcs = CodeFunctions.createEncoderForLBL("latin1");
+		decoder = funcs.getDecoder();
+		sort = new Sort();
+		setHeader(header);
+		setReader(new BufferedImgFileReader(chan));
+		header.readHeader(getReader());
+		sort.setHeaderLen(header.getHeaderLength());
+		readSrt1();
+		readDesc();
+		readTableHeader();
+	}
+	
+	private void readTableHeader() {
+		ImgFileReader reader = getReader();
+		reader.position(tableHeader.getPosition());
+		int len = reader.getChar();
+		sort.setHeader3Len(len);
+		sort.setId1(reader.getChar());
+		sort.setId2(reader.getChar());
+		sort.setCodepage(reader.getChar());
+		if (sort.getCodepage() == 65001)
+			sort.setMulti(true);
+		reader.getInt(); //?
+		characterTable.readSectionInfo(reader, true);
+		reader.position(reader.position() + 6); // padding?
+		srt5.readSectionInfo(reader, true);
+		reader.position(reader.position() + 6); // padding?
+		if (len > 0x2c) {
+			srt6.readSectionInfo(reader, false);
+		}
+		if (len > 0x34) {
+			reader.getInt();
+			int maxCodeBlock = reader.getInt();
+			sort.setMaxPage(maxCodeBlock);
+			srt7.readSectionInfo(reader, true);
+			reader.position(reader.position() + 6); // padding?
+		}
+		if (len > 0x44) {
+			srt8.readSectionInfo(reader, true);
+		}
+		readCharacterTable();
+		if (srt7.getSize() > 0) {
+			readSrt7();
+			readSrt8();
+		}
+		readExpansions();
+	}
+
+	private void readSrt7() {
+		ImgFileReader reader = getReader();
+		reader.position(tableHeader.getPosition() + srt7.getPosition());
+
+		int block = 1;
+		for (int i = 0; i < srt7.getNumItems(); i++) {
+			int val = reader.getInt();
+			if (val >= 0)
+				offsetToBlock.put(val/srt8.getItemSize(), block);
+			block++;
+		}
+		
+	}
+
+	private void readSrt8() {
+		ImgFileReader reader = getReader();
+		reader.position(tableHeader.getPosition() + srt8.getPosition());
+		int reclen = srt8.getItemSize();
+		int block = 1;
+		for (int i = 0; i < srt8.getNumItems(); i++) {
+			Integer nblock = offsetToBlock.get(i);
+			if (nblock != null) 
+				block = nblock;
+			int flags = reader.get() & 0xff;
+			CodePosition cp = readCharPosition(reclen-1);
+			int ch = block*256 + (i % 256);
+			
+			if ((flags & 0xf0) != 0) { 
+				sort.add(ch, countExp + 1, 0, 0, flags);
+				countExp += ((flags >> 4) & 0xf) + 1;
+			}
+			else { 
+				sort.add(ch, cp.getPrimary(), cp.getSecondary(), cp.getTertiary(), flags);
+			}
+		}
+		
+	}
+
+	private void readCharacterTable() {
+		ImgFileReader reader = getReader();
+		reader.position(characterTable.getPosition());
+		int rs = characterTable.getItemSize();
+		long start = tableHeader.getPosition() + characterTable.getPosition();
+		reader.position(start);
+		for (int ch = 1; ch <= characterTable.getNumItems(); ch++) {
+			int flags = reader.get() & 0xff;
+			CodePosition cp = readCharPosition(rs-1);
+			if ((flags & 0xf0) != 0) { 
+				sort.add(ch, countExp + 1, 0, 0, flags);
+				countExp += ((flags >> 4) & 0xf) + 1;
+			}
+			else { 
+				sort.add(ch, cp.getPrimary(), cp.getSecondary(), cp.getTertiary(), flags);
+			}
+		}
+	}
+
+	/**
+	 * Read the sort position information.  The format varies depending on the posLength parameter.
+	 *
+	 * @param posLength The length of the position information (not the record length, just the
+	 * part of it that encodes the positions).
+	 */
+	private CodePosition readCharPosition(int posLength) {
+		ImgFileReader reader = getReader();
+		CodePosition cp = new CodePosition();
+		int rec;
+		if (posLength == 2) {
+			rec = reader.getChar();
+			cp.setPrimary((char) (rec & 0xff));
+			cp.setSecondary((byte) ((rec >> 8) & 0xf));
+			cp.setTertiary((byte) ((rec >> 12) & 0xf));
+			
+		} else if (posLength == 3) {
+			rec = reader.get3();
+			cp.setPrimary((char) (rec & 0xff));
+			cp.setSecondary((byte) ((rec >> 8) & 0xf));
+			cp.setTertiary((byte) ((rec >> 12) & 0xf));
+		} else if (posLength == 4) {
+			rec = reader.getInt();
+			cp.setPrimary((char) (rec & 0xffff));
+			cp.setSecondary((byte) ((rec >> 16) & 0xff));
+			cp.setTertiary((byte) ((rec >> 24) & 0xff));
+		} else {
+			throw new RuntimeException("unexpected value posLength " + posLength);
+		}
+		return cp;
+	}
+
+	private void readExpansions() {
+		ImgFileReader reader = getReader();
+		int reclen = srt5.getItemSize();
+		reader.position(tableHeader.getPosition() + srt5.getPosition());
+		List<CodePosition> expansionList = new ArrayList<>(srt5.getNumItems());
+		if (countExp != srt5.getNumItems()) {
+			throw new RuntimeException("unexpected number of expansions " + srt5.getNumItems() + " expected: " + countExp);
+		}
+		for (int i = 0; i < srt5.getNumItems(); i++) {
+			CodePosition cp = readCharPosition(reclen);
+			expansionList.add(cp);
+		}
+		sort.setExpansions(expansionList);
+	}
+
+	private void readSrt1() {
+		ImgFileReader reader = getReader();
+		reader.position(header.getHeaderLength());
+		description.readSectionInfo(reader, false);
+		tableHeader.readSectionInfo(reader, false);
+	}
+
+	private void readDesc() {
+		getReader().position(description.getPosition());
+		byte[] zString= getReader().getZString();
+		sort.setDescription(decodeToString(zString));
+	}
+	
+	private String decodeToString(byte[] zString) {
+		decoder.reset();
+		for (byte b : zString)
+			decoder.addByte(b);
+
+		return decoder.getText().getText();
+	}
+	
+	public Sort getSort() {
+		return sort;
+	}
+
+	/**
+	 * Read in a sort description text file and create a SRT from it.
+	 * @param args First arg is the text input file, the second is the name of the output file. The defaults are
+	 * in.txt and out.srt.
+	 */
+	public static void main(String[] args) throws IOException {
+		String infile = "we2.srt";
+		if (args.length > 0)
+			infile = args[0];
+
+		String outfile = "out.srt";
+		if (args.length > 1)
+			outfile = args[1];
+		try {
+			Files.delete(Paths.get(outfile, ""));
+		} catch (Exception e) {
+		}
+		ImgChannel inChannel = null;
+		FileSystem fs = null;
+		try {
+			if (infile.endsWith("srt"))
+				inChannel = new FileImgChannel(infile, "r");
+			else {
+				fs = ImgFS.openFs(infile);
+				List<DirectoryEntry> entries = fs.list();
+
+				// Find the TRE entry
+				String mapname = null;
+				for (DirectoryEntry ent : entries) {
+					if ("SRT".equals(ent.getExt())) {
+						mapname = ent.getName();
+						break;
+					}
+				}
+				inChannel = fs.open(mapname + ".SRT", "r");
+				
+				ImgChannel chan = new FileImgChannel(outfile, "rw");
+				SrtFileReader tr = new SrtFileReader(inChannel);
+				tr.close();
+				Sort sort1 = tr.getSort();
+				SRTFile sf = new SRTFile(chan);
+				sf.setSort(sort1);
+				sf.write();
+				sf.close();
+				chan.close();
+			}
+		} catch (FileNotFoundException e) {
+			System.err.println("Could not open file: " + infile);
+		} finally {
+			if (fs != null) {
+				fs.close();
+			}
+		}
+
+	}
+}
diff --git a/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java b/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
index 6969a51..159617a 100644
--- a/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
+++ b/src/uk/me/parabola/imgfmt/app/srt/SrtSortKey.java
@@ -39,17 +39,18 @@ class SrtSortKey<T> implements SortKey<T> {
 
 	public int compareTo(SortKey<T> o) {
 		SrtSortKey<T> other = (SrtSortKey<T>) o;
-		int length = Math.min(this.key.length, other.key.length);
-		for (int i = 0; i < length; i++) {
-			int k1 = this.key[i] & 0xff;
-			int k2 = other.key[i] & 0xff;
-			if (k1 < k2) {
-				return -1;
-			} else if (k1 > k2) {
-				return 1;
+		if (key != other.key) {
+			int length = Math.min(this.key.length, other.key.length);
+			for (int i = 0; i < length; i++) {
+				int k1 = this.key[i] & 0xff;
+				int k2 = other.key[i] & 0xff;
+				if (k1 < k2) {
+					return -1;
+				} else if (k1 > k2) {
+					return 1;
+				}
 			}
 		}
-
 		//if (this.key.length < other.key.length)
 		//	return -1;
 		//else if (this.key.length > other.key.length)
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java b/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
index 4152e66..87d886e 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/MapObject.java
@@ -18,8 +18,6 @@ package uk.me.parabola.imgfmt.app.trergn;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
 
 import uk.me.parabola.imgfmt.app.ImgFileWriter;
 import uk.me.parabola.imgfmt.app.Label;
@@ -42,7 +40,6 @@ public abstract class MapObject {
 
 	// The label(s) for this object
 	private Label label;
-	private List<Label> refLabels;
 
 	// The type of road etc.
 	private int type;
@@ -66,7 +63,7 @@ public abstract class MapObject {
 
 	public abstract void write(OutputStream stream) throws IOException;
 
-	int getDeltaLat() {
+	protected int getDeltaLat() {
 		return deltaLat;
 	}
 
@@ -78,12 +75,6 @@ public abstract class MapObject {
 		this.label = label;
 	}
 
-	public void addRefLabel(Label refLabel) {
-		if(refLabels == null)
-			refLabels = new ArrayList<Label>();
-		refLabels.add(refLabel);
-	}
-
 	public int getType() {
 		return type;
 	}
@@ -152,10 +143,6 @@ public abstract class MapObject {
 		return label;
 	}
 
-	public List<Label> getRefLabels() {
-		return refLabels;
-	}
-
 	protected byte[] getExtTypeExtraBytes() {
 		return (extTypeAttributes != null)? extTypeAttributes.getExtTypeExtraBytes(this) : null;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
index 6e4550d..396dcb3 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Polyline.java
@@ -58,6 +58,9 @@ public class Polyline extends MapObject {
 	// Set if it is a one-way street for example.
 	private boolean direction;
 
+	// further labels, if any
+	private List<Label> refLabels;
+
 	// The actual points that make up the line.
 	private final List<Coord> points = new ArrayList<Coord>();
 
@@ -122,7 +125,6 @@ public class Polyline extends MapObject {
 			roaddef.addOffsetTarget(file.position(),
 					FLAG_NETINFO | (loff & FLAG_EXTRABIT));
 			// also add ref label(s) if present
-			List<Label> refLabels = getRefLabels();
 			if(refLabels != null)
 				for(Label rl : refLabels)
 					roaddef.addLabel(rl);
@@ -299,4 +301,12 @@ public class Polyline extends MapObject {
 			return false;
 		return roaddef.hasHouseNumbers();
 	}
+
+	public void addRefLabel(Label refLabel) {
+		if(refLabels == null)
+			refLabels = new ArrayList<Label>();
+		refLabels.add(refLabel);
+	}
+
+	
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java b/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
index 70a9590..e0d4667 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
@@ -218,8 +218,9 @@ public class RGNFileReader extends ImgReader {
 
 	/**
 	 * Get all the polygons for a given subdivision.
+	 * @param witExtTypeData 
 	 */
-	public List<Polygon> shapesForSubdiv(Subdivision div) {
+	public List<Polygon> shapesForSubdiv(Subdivision div, boolean witExtTypeData) {
 		ArrayList<Polygon> list = new ArrayList<Polygon>();
 		if (div.hasPolygons()){
 
@@ -234,16 +235,19 @@ public class RGNFileReader extends ImgReader {
 				Polygon line = new Polygon(div);
 				readLineCommon(getReader(), div, line);
 				list.add(line);
+				line.setNumber(list.size());
 			}
 		}
-		if (div.getExtTypeAreasSize() > 0){
-			int start = rgnHeader.getExtTypeAreasOffset() + div.getExtTypeAreasOffset();
-			int end = start + div.getExtTypeAreasSize();
-			position(start);
-			while (position() < end) {
-				Polygon line = new Polygon(div);
-				readLineCommonExtType(getReader(), div, line);
-				list.add(line);
+		if (witExtTypeData) {
+			if (div.getExtTypeAreasSize() > 0){
+				int start = rgnHeader.getExtTypeAreasOffset() + div.getExtTypeAreasOffset();
+				int end = start + div.getExtTypeAreasSize();
+				position(start);
+				while (position() < end) {
+					Polygon line = new Polygon(div);
+					readLineCommonExtType(getReader(), div, line);
+					list.add(line);
+				}
 			}
 		}
 		return list;
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
index aaf455d..b9ec311 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
@@ -495,12 +495,19 @@ public class Subdivision {
 		extTypeAreasOffset = rgnFile.getExtTypeAreasSize();
 		extTypeLinesOffset = rgnFile.getExtTypeLinesSize();
 		extTypePointsOffset = rgnFile.getExtTypePointsSize();
+		assert extTypeAreasOffset >= 0;
+		assert extTypeLinesOffset >= 0;
+		assert extTypePointsOffset >= 0;
+
 	}
 
 	public void endDivision() {
 		extTypeAreasSize = rgnFile.getExtTypeAreasSize() - extTypeAreasOffset;
 		extTypeLinesSize = rgnFile.getExtTypeLinesSize() - extTypeLinesOffset;
 		extTypePointsSize = rgnFile.getExtTypePointsSize() - extTypePointsOffset;
+		assert extTypeAreasSize >= 0;
+		assert extTypeLinesSize >= 0;
+		assert extTypePointsSize >= 0;
 	}
 
 	public void writeExtTypeOffsetsRecord(ImgFileWriter file) {
@@ -531,26 +538,42 @@ public class Subdivision {
 	 * @param sdPrev the pred. sub-div or null
 	 */
 	public void readExtTypeOffsetsRecord(ImgFileReader reader,
-			Subdivision sdPrev) {
+			Subdivision sdPrev, int size) {
 		extTypeAreasOffset = reader.getInt();
 		extTypeLinesOffset = reader.getInt();
 		extTypePointsOffset = reader.getInt();
-		reader.get();
+		if (size > 12) {
+			int kinds = reader.get();
+		}
+		if (size > 13)
+			reader.get(size-13);
+		assert extTypeAreasOffset >= 0;
+		assert extTypeLinesOffset >= 0;
+		assert extTypePointsOffset >= 0;
+
 		if (sdPrev != null){
 			sdPrev.extTypeAreasSize = extTypeAreasOffset - sdPrev.extTypeAreasOffset;
 			sdPrev.extTypeLinesSize = extTypeLinesOffset - sdPrev.extTypeLinesOffset;
 			sdPrev.extTypePointsSize = extTypePointsOffset - sdPrev.extTypePointsOffset;
+			assert extTypeAreasSize >= 0;
+			assert extTypeLinesSize >= 0;
+			assert extTypePointsSize >= 0;
 		}
 	}
 	/**
 	 * Set the sizes for the extended type data. See {@link #writeLastExtTypeOffsetsRecord(ImgFileWriter)} 
 	 */
-	public void readLastExtTypeOffsetsRecord(ImgFileReader reader) {
+	public void readLastExtTypeOffsetsRecord(ImgFileReader reader, int size) {
 		extTypeAreasSize = reader.getInt() - extTypeAreasOffset;
 		extTypeLinesSize = reader.getInt() - extTypeLinesOffset;
 		extTypePointsSize = reader.getInt() - extTypePointsOffset;
-		byte test = reader.get();
-		assert test == 0;
+		assert extTypeAreasSize >= 0;
+		assert extTypeLinesSize >= 0;
+		assert extTypePointsSize >= 0;
+		if (size > 12) {
+			byte test = reader.get();
+			assert test == 0;
+		}
 	}
 	
 	/**
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java b/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
index 289fba7..bd2fdbb 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
@@ -136,7 +136,6 @@ public class TREFileReader extends ImgReader {
 		ImgFileReader reader = getReader();
 		int start = header.getExtTypeOffsetsPos();
 		int end = start + header.getExtTypeOffsetsSize();
-		int skipBytes = header.getExtTypeSectionSize() - 13;
 			
 		reader.position(start);
 		Subdivision sd = null;
@@ -149,15 +148,11 @@ public class TREFileReader extends ImgReader {
 			for (int i = 0; i < divs.length; i++) {
 				sdPrev = sd;
 				sd = divs[i];
-				sd.readExtTypeOffsetsRecord(reader, sdPrev);
-				if (skipBytes > 0)
-					reader.get(skipBytes);
+				sd.readExtTypeOffsetsRecord(reader, sdPrev, header.getExtTypeSectionSize());
 			}
 		}
-		if(sd != null) {
-			sd.readLastExtTypeOffsetsRecord(reader);
-			if (skipBytes > 0)
-				reader.get(skipBytes);
+		if(sd != null && reader.position() < end) {
+			sd.readLastExtTypeOffsetsRecord(reader, header.getExtTypeSectionSize());
 		}
 		
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java b/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
index ee71936..bff559b 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
@@ -88,6 +88,8 @@ public class TREHeader extends CommonHeader {
 
 	private int mapId;
 
+	private boolean custom;
+
 	public TREHeader() {
 		super(DEFAULT_HEADER_LEN, "GARMIN TRE");
 	}
@@ -175,8 +177,11 @@ public class TREHeader extends CommonHeader {
 		writer.put(getPoiDisplayFlags());
 
 		writer.put3(displayPriority);
-		writer.putInt(0x110301);
-
+		if (custom)
+			writer.putInt(0x170401);
+		else
+			writer.putInt(0x110301);
+		
 		writer.putChar((char) 1);
 		writer.put((byte) 0);
 
@@ -240,6 +245,8 @@ public class TREHeader extends CommonHeader {
 
 		if (props.containsKey("transparent"))
 			poiDisplayFlags |= POI_FLAG_TRANSPARENT;
+		custom = props.containsKey("custom");
+			
 	}
 	
 	/**
diff --git a/src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java b/src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java
index 666c9b3..332194c 100644
--- a/src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java
+++ b/src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java
@@ -74,16 +74,12 @@ public class MdxFile {
 	 * Write the file out to the given filename.
 	 */
 	public void write(String filename) throws IOException {
-		FileOutputStream stream = new FileOutputStream(filename);
-		FileChannel chan = stream.getChannel();
-		ByteBuffer buf = ByteBuffer.allocate(1024);
-		buf.order(ByteOrder.LITTLE_ENDIAN);
+		try (FileOutputStream stream = new FileOutputStream(filename); FileChannel chan = stream.getChannel();) {
+			ByteBuffer buf = ByteBuffer.allocate(1024);
+			buf.order(ByteOrder.LITTLE_ENDIAN);
 
-		try {
 			writeHeader(chan, buf);
 			writeBody(chan, buf);
-		} finally {
-			chan.close();
 		}
 	}
 
diff --git a/src/uk/me/parabola/imgfmt/sys/ImgHeader.java b/src/uk/me/parabola/imgfmt/sys/ImgHeader.java
index f764ffa..4746e90 100644
--- a/src/uk/me/parabola/imgfmt/sys/ImgHeader.java
+++ b/src/uk/me/parabola/imgfmt/sys/ImgHeader.java
@@ -45,6 +45,7 @@ class ImgHeader {
 
 	// Offsets into the header.
 	private static final int OFF_XOR = 0x0;
+	private static final int OFF_VERSION = 0x8;
 	private static final int OFF_UPDATE_MONTH = 0xa;
 	private static final int OFF_UPDATE_YEAR = 0xb; // +1900 for val >= 0x63, +2000 for less
 	private static final int OFF_SUPP = 0xe;		// Appears to be set for gmapsupp files
@@ -165,8 +166,17 @@ class ImgHeader {
 		setCreationTime(date);
 		setUpdateTime(date);
 		setDescription(params.getMapDescription());
-		header.put(OFF_SUPP, (byte) (fsParams.isGmapsupp() && fsParams.isHideGmapsuppOnPC() ? 1: 0));
-
+		if (fsParams.isGmapsupp()) {
+			header.put(OFF_SUPP, (byte) (fsParams.isHideGmapsuppOnPC() ? 1: 0));
+			int prodVersion = fsParams.getProductVersion();
+			if (prodVersion >= 0) {
+				// value 100 means 1.00 */
+				int major = prodVersion / 100;
+				int minor = prodVersion % 100;
+				short version = (short) (major | (minor << 8)); 
+				header.putShort(OFF_VERSION, version);
+			}
+		}
 		// Checksum is not checked.
 		header.put(OFF_CHECKSUM, (byte) 0);
 	}
diff --git a/src/uk/me/parabola/log/UsefulFormatter.java b/src/uk/me/parabola/log/UsefulFormatter.java
index 82cedb9..9282d53 100644
--- a/src/uk/me/parabola/log/UsefulFormatter.java
+++ b/src/uk/me/parabola/log/UsefulFormatter.java
@@ -32,6 +32,8 @@ import java.util.logging.LogRecord;
  */
 public class UsefulFormatter extends Formatter {
 	private boolean showTime = true;
+	private static final String lineSeparator = System.getProperty("line.separator");
+
 
 	public String format(LogRecord record) {
 		StringBuffer sb = new StringBuffer();
@@ -60,8 +62,8 @@ public class UsefulFormatter extends Formatter {
 		sb.append("): ");
 
 		sb.append(record.getMessage());
-
-		sb.append('\n');
+		
+		sb.append(lineSeparator);
 
 		Throwable t = record.getThrown();
 		if (t != null) {
diff --git a/src/uk/me/parabola/mkgmap/build/LayerFilterChain.java b/src/uk/me/parabola/mkgmap/build/LayerFilterChain.java
index a237e8a..4824a51 100644
--- a/src/uk/me/parabola/mkgmap/build/LayerFilterChain.java
+++ b/src/uk/me/parabola/mkgmap/build/LayerFilterChain.java
@@ -54,9 +54,14 @@ public class LayerFilterChain implements MapFilterChain {
 			return;
 		
 		MapFilter f = filters.get(position++);
-		f.doFilter(element, this);
-		// maintain chain position for repeated calls in the split filters 
-		position--; 
+		try {
+			f.doFilter(element, this);
+			// maintain chain position for repeated calls in the split filters 
+			position--; 
+		} catch (RuntimeException e) {
+			position--; // maintain position
+			throw e;
+		}
 	}
 
 	/**
@@ -73,7 +78,7 @@ public class LayerFilterChain implements MapFilterChain {
 	 *
 	 * @param filter Filter to added at the end of the chain.
 	 */
-	void addFilter(MapFilter filter) {
+	public void addFilter(MapFilter filter) {
 		assert config != null;
 
 		filter.init(config);
diff --git a/src/uk/me/parabola/mkgmap/build/Locator.java b/src/uk/me/parabola/mkgmap/build/Locator.java
index 076a800..48d7aff 100644
--- a/src/uk/me/parabola/mkgmap/build/Locator.java
+++ b/src/uk/me/parabola/mkgmap/build/Locator.java
@@ -18,8 +18,11 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import it.unimi.dsi.fastutil.shorts.ShortArrayList;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.MapPoint;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.util.EnhancedProperties;
 import uk.me.parabola.util.KdTree;
@@ -34,8 +37,7 @@ public class Locator {
 	private final KdTree<MapPoint> cityFinder = new KdTree<>();
 	private final List<MapPoint> placesMap  =  new ArrayList<MapPoint>();
 
-	/** Contains the tags defined by the option name-tag-list */
-	private final List<String> nameTags;
+	private final NameFinder nameFinder;
 
 	private final LocatorConfig locConfig = LocatorConfig.get();
 
@@ -48,7 +50,7 @@ public class Locator {
 	}
 	
 	public Locator(EnhancedProperties props) {
-		this.nameTags = LocatorUtil.getNameTags(props);
+		this.nameFinder = new NameFinder(props);
 		this.locationAutofill = new HashSet<String>(LocatorUtil.parseAutofillOption(props));
 	}
 	
@@ -97,10 +99,9 @@ public class Locator {
 		if (country == null) {
 			return null;
 		}
-		
 		String iso = locConfig.getCountryISOCode(country);
 		if (iso != null) {
-			String normedCountryName = locConfig.getCountryName(iso, nameTags);
+			String normedCountryName = locConfig.getCountryName(iso, nameFinder);
 			if (normedCountryName != null) {
 				log.debug("Country:",country,"ISO:",iso,"Norm:",normedCountryName);
 				return normedCountryName;
@@ -132,19 +133,17 @@ public class Locator {
 		}
 	}
 	
-	private final static String[] PREFERRED_NAME_TAGS = {"name","name:en","int_name"};
+	public final static ShortArrayList PREFERRED_NAME_TAG_KEYS = TagDict.compileTags("name","name:en","int_name");
 	
-	public String getCountryISOCode(Tags countryTags) {
-		for (String nameTag : PREFERRED_NAME_TAGS) {
-			String nameValue = countryTags.get(nameTag);
-			String isoCode = getCountryISOCode(nameValue);
+	public String getCountryISOCode(Tags tags) {
+		for (short nameTagKey : PREFERRED_NAME_TAG_KEYS) {
+			String isoCode = getCountryISOCode(tags.get(nameTagKey));
 			if (isoCode != null) {
 				return isoCode;
 			}
 		}
 
-		for (String countryStr : countryTags.getTagsWithPrefix("name:", false)
-				.values()) {
+		for (String countryStr : tags.getTagsWithPrefix("name:", false).values()) {
 			String isoCode = getCountryISOCode(countryStr);
 			if (isoCode != null) {
 				return isoCode;
diff --git a/src/uk/me/parabola/mkgmap/build/LocatorConfig.java b/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
index cbdd6ce..aec9941 100644
--- a/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
+++ b/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
@@ -16,7 +16,6 @@ import java.io.FileInputStream;
 import java.io.InputStream;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -25,6 +24,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 
 import uk.me.parabola.imgfmt.app.trergn.TREHeader;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
 import uk.me.parabola.mkgmap.reader.osm.Tags;
 
 import org.w3c.dom.Document;
@@ -287,10 +287,10 @@ public class LocatorConfig {
 	 * name tags. The first available value of the tags in the nameTags list is returned.
 	 * 
 	 * @param isoCode the three letter ISO code
-	 * @param nameTags the list of name tags 
+	 * @param nameFinder the list of name tags 
 	 * @return the full country name (<code>null</code> if unknown)
 	 */
-	public synchronized String getCountryName(String isoCode, List<String> nameTags) {
+	public synchronized String getCountryName(String isoCode, NameFinder nameFinder) {
 		Tags countryTags = countryTagMap.get(isoCode);
 		if (countryTags==null) {
 			// no tags for this country available
@@ -298,16 +298,7 @@ public class LocatorConfig {
 			return defaultCountryNames.get(isoCode);
 		}
 		
-		// search for the first available tag of the nameTags list
-		for (String nameTag : nameTags) {
-			String name = countryTags.get(nameTag);
-			if (name != null) {
-				return name;
-			}
-		}
-		
-		// last try: just the simple "name" tag
-		return countryTags.get("name");
+		return nameFinder.getName(countryTags);
 	}
 
 	public synchronized int getRegionOffset(String iso)
diff --git a/src/uk/me/parabola/mkgmap/build/LocatorUtil.java b/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
index 5392375..7523660 100644
--- a/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
+++ b/src/uk/me/parabola/mkgmap/build/LocatorUtil.java
@@ -16,7 +16,6 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Properties;
 import java.util.Set;
 import java.util.regex.Pattern;
 
@@ -24,14 +23,8 @@ import uk.me.parabola.util.EnhancedProperties;
 
 public class LocatorUtil {
 
-	private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern
-			.compile("[,\\s]+");
+	private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+");
 	
-	public static List<String> getNameTags(Properties props) {
-		String nameTagProp = props.getProperty("name-tag-list", "name");
-		return Arrays.asList(COMMA_OR_SPACE_PATTERN.split(nameTagProp));
-	}
-
 	/**
 	 * Parses the parameters of the location-autofill option. Establishes also downwards
 	 * compatibility with the old integer values of location-autofill. 
diff --git a/src/uk/me/parabola/mkgmap/build/MapArea.java b/src/uk/me/parabola/mkgmap/build/MapArea.java
index 94753dd..6c14793 100644
--- a/src/uk/me/parabola/mkgmap/build/MapArea.java
+++ b/src/uk/me/parabola/mkgmap/build/MapArea.java
@@ -21,11 +21,9 @@ import java.util.Arrays;
 import java.util.List;
 
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-
-import uk.me.parabola.imgfmt.Utils;
-import uk.me.parabola.util.Java2DConverter;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.net.RoadNetwork;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.filters.FilterConfig;
@@ -34,14 +32,14 @@ import uk.me.parabola.mkgmap.filters.LineSplitterFilter;
 import uk.me.parabola.mkgmap.filters.MapFilterChain;
 import uk.me.parabola.mkgmap.filters.PolygonSplitterFilter;
 import uk.me.parabola.mkgmap.filters.PolygonSubdivSizeSplitterFilter;
-import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+import uk.me.parabola.mkgmap.filters.PredictFilterPoints;
 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.imgfmt.app.net.RoadNetwork;
+import uk.me.parabola.util.ShapeSplitter;
 
 /**
  * A sub area of the map.  We have to divide the map up into areas to meet the
@@ -69,7 +67,7 @@ public class MapArea implements MapDataSource {
 	public static final int NUM_KINDS     = 6;
 
 	// This is the initial area.
-	private final Area bounds;
+	private Area bounds;
 
 	// Because ways may extend beyond the bounds, we keep track of the actual
 	// bounding box here.
@@ -92,7 +90,9 @@ public class MapArea implements MapDataSource {
 	private int nActiveShapes;
 
 	/** The resolution that this area is at */
-	private final int areaResolution;
+	private int areaResolution;
+	private final boolean splitPolygonsIntoArea;
+	private int splittableCount;
 
 	private Long2ObjectOpenHashMap<Coord> areasHashMap;
 
@@ -103,24 +103,31 @@ public class MapArea implements MapDataSource {
 	 *
 	 * @param src The map data source to initialise this area with.
 	 * @param resolution The resolution of this area.
+	 * @param splitPolygonsIntoArea aligns subareas as powerOf2 and splits polygons into the subareas.
 	 */
-	public MapArea(MapDataSource src, int resolution) {
-		this.areaResolution = 0;
+	public MapArea(MapDataSource src, int resolution, boolean splitPolygonsIntoArea) {
+		this.areaResolution = 0; // don't want addSize() to gather information for this MapArea
 		this.bounds = src.getBounds();
+		this.splitPolygonsIntoArea = splitPolygonsIntoArea;
 		for (MapPoint p : src.getPoints()) {
+			if (p.getMaxResolution() < resolution)
+				continue;
 			if(bounds.contains(p.getLocation()))
 				addPoint(p);
 			else
-				log.error("Point with type 0x" + Integer.toHexString(p.getType()) + " at " + p.getLocation().toOSMURL() + " is outside of the map area centred on " + bounds.getCenter().toOSMURL() + " width = " + bounds.getWidth() + " height = " + bounds.getHeight() + " resolution = " + resolution);
+				log.error("Point with type 0x" + Integer.toHexString(p.getType()) + " at " + p.getLocation().toOSMURL() +
+					  " is outside of the map area centred on " + bounds.getCenter().toOSMURL() +
+					  " width = " + bounds.getWidth() + " height = " + bounds.getHeight() + " resolution = " + areaResolution);
 		}
 		addLines(src, resolution);
 		addPolygons(src, resolution);
+		this.areaResolution = resolution;
 	}
 
 	/**
-	 * Add the polygons, making sure that they are not too big.
+	 * Add the polygons
 	 * @param src The map data.
-	 * @param resolution The resolution of this layer.
+	 * @param resolution The current resolution of the layer.
 	 */
 	private void addPolygons(MapDataSource src, final int resolution) {
 		MapFilterChain chain = new MapFilterChain() {
@@ -137,7 +144,16 @@ public class MapArea implements MapDataSource {
 		filter.init(config);
 
 		for (MapShape s : src.getShapes()) {
-			filter.doFilter(s, chain);
+			if (s.getMaxResolution() < resolution)
+				continue;
+			if (splitPolygonsIntoArea || this.bounds.isEmpty() || this.bounds.contains(s.getBounds()))
+				// if splitPolygonsIntoArea, don't want to have other splitting as well.
+				// PolygonSubdivSizeSplitterFilter is a bit drastic - filters by both size and number of points
+				// so use splitPolygonsIntoArea logic for this as well. This is fine as long as there
+				// aren't bits of the shape outside the initial area.
+				addShape(s);
+			else
+				filter.doFilter(s, chain);
 		}
 	}
 
@@ -163,6 +179,9 @@ public class MapArea implements MapDataSource {
 		config.setBounds(bounds);
 		filter.init(config);
 		for (MapLine l : src.getLines()) {
+			if (l.getMaxResolution() < resolution)
+				continue;
+// %%% ??? if not appearing at this level no need to filter
 			filter.doFilter(l, chain);
 		}
 	}
@@ -171,11 +190,13 @@ public class MapArea implements MapDataSource {
 	 * Create an map area with the given initial bounds.
 	 *
 	 * @param area The bounds for this area.
-	 * @param res The minimum resolution for this area.
+	 * @param resolution The minimum resolution for this area.
+	 * @param splitPolygonsIntoArea splits polygons into the subareas.
 	 */
-	private MapArea(Area area, int res) {
+	private MapArea(Area area, int resolution, boolean splitPolygonsIntoArea) {
 		bounds = area;
-		areaResolution = res;
+		areaResolution = resolution;
+		this.splitPolygonsIntoArea = splitPolygonsIntoArea;
 	}
 
 	/**
@@ -183,29 +204,24 @@ public class MapArea implements MapDataSource {
 	 * to the appropriate subarea.  Usually this instance would now be thrown
 	 * away and the new sub areas used instead.
 	 * <p>
-	 * if orderByDecreasingArea, the split is forced onto boundaries that can
-	 * be represented exactly with the relevant shift for the level.
-	 * This can cause the split to fail because all the lines/shapes that need
-	 * to be put at this level are here, but represented at the highest resolution
-	 * without any filtering relevant to the resolution and the logic to request
-	 * splitting considers this too much for a subDivision, even though it will
-	 * mostly will disappear when we come to write it and look meaningless -
-	 * the subDivision has been reduced to a single point at its shift level.
-	 * <p>
-	 * The lines/shapes should have been simplified much earlier in the process,
-	 * then they could appear as such in reasonably size subDivision.
-	 * The logic of levels, lines and shape placement, simplification, splitting and
-	 * other filtering, subDivision splitting etc needs a re-think and re-organisation.
+	 * This code is dealing with a lot of factors that govern the splitting, eg:
+	 *  splitPolygonsIntoArea,
+	 *  tooSmallToDivide,
+	 *  item.minResolution vs. areaResolution,
+	 *  number/size of items and the limits of a subDivision,
+	 *  items that exceed maximum subDivision on their own,
+	 *  items that extend up to 50% outside the current area,
+	 *  items bigger than this.
 	 *
 	 * @param nx The number of pieces in the x (longitude) direction.
 	 * @param ny The number of pieces in the y direction.
-	 * @param resolution The resolution of the level.
-	 * @param bounds the bounding box that is used to create the areas.  
-	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
+	 * @param bounds the bounding box that is used to create the areas.
+	 * @param tooSmallToDivide the area is small and data overflows; split into overflow areas
+	 *
 	 * @return An array of the new MapArea's or null if can't split.
 	 */
-	public MapArea[] split(int nx, int ny, int resolution, Area bounds, boolean orderByDecreasingArea) {
-		int resolutionShift = orderByDecreasingArea ? (24 - resolution) : 0;
+	public MapArea[] split(int nx, int ny, Area bounds, boolean tooSmallToDivide) {
+		int resolutionShift = MAX_RESOLUTION - areaResolution;
 		Area[] areas = bounds.split(nx, ny, resolutionShift);
 		if (areas == null) { //  Failed to split!
 			if (log.isDebugEnabled()) { // see what is here
@@ -225,102 +241,146 @@ public class MapArea implements MapDataSource {
 			}
 			return null;
 		}
+
 		MapArea[] mapAreas = new MapArea[nx * ny];
-		log.info("Splitting area " + bounds + " into " + nx + "x" + ny + " pieces at resolution " + resolution);
-		boolean useNormalSplit = true;
-		while (true){
-			List<MapArea> largeObjectAreas = new ArrayList<>();
-			for (int i = 0; i < nx * ny; i++) {
-				mapAreas[i] = new MapArea(areas[i], resolution);
-				if (log.isDebugEnabled())
-					log.debug("area before", mapAreas[i].getBounds());
-			}
+		log.info("Splitting area " + bounds + " into " + nx + "x" + ny + " pieces at resolution " + areaResolution, tooSmallToDivide);
+		List<MapArea> addedAreas = new ArrayList<>();
+		for (int i = 0; i < mapAreas.length; i++) {
+			mapAreas[i] = new MapArea(areas[i], areaResolution, splitPolygonsIntoArea);
+			if (log.isDebugEnabled())
+				log.debug("area before", mapAreas[i].getBounds());
+		}
 
-			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, xbase30, ybase30, nx, ny, dx30, dy30);
-				mapAreas[pos].addPoint(p);
-				used[pos] = true;
+		int xbaseHp = areas[0].getMinLong() << Coord.DELTA_SHIFT;
+		int ybaseHp = areas[0].getMinLat() << Coord.DELTA_SHIFT;
+		int dxHp = areas[0].getWidth() << Coord.DELTA_SHIFT;
+		int dyHp = areas[0].getHeight() << Coord.DELTA_SHIFT;
+
+		// Some of the work done by PolygonSubdivSizeSplitterFilter now done here
+		final int maxSize = Math.min((1<<24)-1, Math.max(MapSplitter.MAX_DIVISION_SIZE << (MAX_RESOLUTION - areaResolution), 0x8000));
+
+		/**
+		 * These constants control when an item (shape unless splitPolygonsIntoArea or line) is shifted into its own MapArea/SubDivision.
+		 * Generally, an item is allowed into the MapArea chosen by centre provided it is no bigger than the MapArea.
+		 * This means that there could be big items near the edges of the MapArea that stick out by almost half, so must
+		 * ensure that this doesn't cause the mapArea to exceed subDivision size limits.
+		 * When the MapArea get small, we don't want to shift lots if items into their own areas;
+		 * The *2 of LARGE_OBJECT_DIM is to keep to the same behaviour as earlier versions.
+		 */
+		final int maxWidth = Math.max(Math.min(areas[0].getWidth(), maxSize/2), LARGE_OBJECT_DIM*2);
+		final int maxHeight = Math.max(Math.min(areas[0].getHeight(), maxSize/2), LARGE_OBJECT_DIM*2);
+
+		// Now sprinkle each map element into the correct map area.
+
+		// do shapes first because want these to define the primary area
+		// and don't have a good tooSmallToDivide strategy when not splitPolygonsIntoArea.
+		if (tooSmallToDivide) {
+			distShapesIntoNewAreas(addedAreas, mapAreas[0]);
+		} else {
+			for (MapShape e : this.shapes) {
+				Area shapeBounds = e.getBounds();
+				if (splitPolygonsIntoArea || shapeBounds.getMaxDimension() > maxSize) {
+					splitIntoAreas(mapAreas, e);
+					continue;
+				}
+				int areaIndex = pickArea(mapAreas, e, xbaseHp, ybaseHp, nx, ny, dxHp, dyHp);
+				if ((shapeBounds.getHeight() > maxHeight || shapeBounds.getWidth() > maxWidth) &&
+				    !areas[areaIndex].contains(shapeBounds)) {
+					MapArea largeObjectArea = new MapArea(shapeBounds, areaResolution, true); // use splitIntoAreas to deal with overflow
+					largeObjectArea.addShape(e);
+					addedAreas.add(largeObjectArea);
+					continue;
+				}
+				mapAreas[areaIndex].addShape(e);
 			}
+		}
 
-			int maxWidth = areas[0].getWidth();
-			int maxHeight = areas[0].getHeight();
-			if (nx*ny == 1 || maxWidth < LARGE_OBJECT_DIM|| maxHeight < LARGE_OBJECT_DIM){
-				// don't separate large objects
-				maxWidth = Integer.MAX_VALUE;  
-				maxHeight = Integer.MAX_VALUE; 
+		if (tooSmallToDivide) {
+			distPointsIntoNewAreas(addedAreas, mapAreas[0]);
+		} else {
+			for (MapPoint p : this.points) {
+				int areaIndex = pickArea(mapAreas, p, xbaseHp, ybaseHp, nx, ny, dxHp, dyHp);
+				mapAreas[areaIndex].addPoint(p);
 			}
+		}
 
-			int areaIndex = 0;
+		if (tooSmallToDivide) {
+			distLinesIntoNewAreas(addedAreas, mapAreas[0]);
+		} else {
 			for (MapLine l : this.lines) {
 				// Drop any zero sized lines.
 				if (l instanceof MapRoad == false && l.getRect().height <= 0 && l.getRect().width <= 0)
 					continue;
-				if (useNormalSplit){
-					areaIndex = pickArea(mapAreas, l, xbase30, ybase30, nx, ny, dx30, dy30);
-					if (l.getBounds().getHeight() > maxHeight || l.getBounds().getWidth() > maxWidth){
-						MapArea largeObjectArea = new MapArea(l.getBounds(), resolution);
-						largeObjectArea.addLine(l);
-						largeObjectAreas.add(largeObjectArea);
-						continue;
-					}
+				Area lineBounds = l.getBounds();
+				int areaIndex = pickArea(mapAreas, l, xbaseHp, ybaseHp, nx, ny, dxHp, dyHp);
+				if ((lineBounds.getHeight() > maxHeight || lineBounds.getWidth() > maxWidth) &&
+				    !areas[areaIndex].contains(lineBounds)) {
+					MapArea largeObjectArea = new MapArea(lineBounds, areaResolution, false);
+					largeObjectArea.addLine(l);
+					addedAreas.add(largeObjectArea);
+					continue;
 				}
-				else 
-					areaIndex = ++areaIndex % mapAreas.length;
 				mapAreas[areaIndex].addLine(l);
-				used[areaIndex] = true;
 			}
+		}
+
+		if (!addedAreas.isEmpty()) {
+			// combine list and array
+			int pos = mapAreas.length;
+			mapAreas = Arrays.copyOf(mapAreas, mapAreas.length + addedAreas.size());
+			for (MapArea ma : addedAreas) {
+				if (ma.getBounds() == null) // distShapesIntoNewAreas etc didn't know how big it was going to be
+					ma.setBounds(ma.getFullBounds()); // so set to bounds of all elements
+				mapAreas[pos++] = ma;
+			}
+		}
+		return mapAreas;
+	}
 
-			for (MapShape e : this.shapes) {
-				if (orderByDecreasingArea) { // need to treat shapes consistently, regardless of useNormalSplit
-					splitIntoAreas(mapAreas, e, used);
-					continue;
+	private void distPointsIntoNewAreas(List<MapArea> addedAreas, MapArea primaryArea) {
+		MapArea extraArea = primaryArea;
+		for (MapPoint p : this.points)
+			if (p.getMinResolution() > areaResolution) // doesn't add to subDivision
+				primaryArea.addPoint(p);
+			else {
+				if (!extraArea.canAddSize(p, POINT_KIND)) {
+					extraArea = new MapArea((Area)null, areaResolution, false);
+					addedAreas.add(extraArea);
 				}
-				if (useNormalSplit){
-					areaIndex = pickArea(mapAreas, e, xbase30, ybase30, nx, ny, dx30, dy30);
-					if (e.getBounds().getHeight() > maxHeight || e.getBounds().getWidth() > maxWidth){
-						MapArea largeObjectArea = new MapArea(e.getBounds(), resolution);
-						largeObjectArea.addShape(e);
-						largeObjectAreas.add(largeObjectArea);
-						continue;
-					}
+				extraArea.addPoint(p);
+			}
+	}
+
+
+	private void distLinesIntoNewAreas(List<MapArea> addedAreas, MapArea primaryArea) {
+		MapArea extraArea = primaryArea;
+		for (MapLine l : this.lines)
+			if (l.getMinResolution() > areaResolution) // doesn't add to subDivision
+				primaryArea.addLine(l);
+			else {
+				if (!extraArea.canAddSize(l, LINE_KIND)) {
+					extraArea = new MapArea((Area)null, areaResolution, false);
+					addedAreas.add(extraArea);
 				}
-				else 
-					areaIndex = ++areaIndex % mapAreas.length;
-				mapAreas[areaIndex].addShape(e);
-				used[areaIndex] = true;
+				extraArea.addLine(l);
 			}
-			// detect special case  
-			if (useNormalSplit && mapAreas.length == 2 && bounds.getMaxDimension() < 2 * (MapSplitter.MIN_DIMENSION + 1)
-					&& used[0] != used[1]
-					&& (this.lines.size() > 1 || this.shapes.size() > 1)) {
-				/* if we get here we probably have two or more identical long ways or
-				 * big shapes with the same center point. We can safely distribute
-				 * them equally to the two areas.  
-				 */
-				useNormalSplit = false;
-				log.warn("useNormalSplit false");
-				continue;
-			} 
-			
-			if (largeObjectAreas.isEmpty() == false){
-				// combine list and array
-				int pos = mapAreas.length;
-				mapAreas = Arrays.copyOf(mapAreas, mapAreas.length + largeObjectAreas.size());
-				for (MapArea ma : largeObjectAreas)
-					mapAreas[pos++] = ma;
+	}
+
+
+	private void distShapesIntoNewAreas(List<MapArea> addedAreas, MapArea primaryArea) {
+		MapArea extraArea = primaryArea;
+		for (MapShape e : this.shapes)
+			if (e.getMinResolution() > areaResolution) // doesn't add to subDivision
+				primaryArea.addShape(e);
+			else {
+				if (!extraArea.canAddSize(e, SHAPE_KIND)) {
+					extraArea = new MapArea((Area)null, areaResolution, true); // use splitIntoAreas to deal with overflow
+					addedAreas.add(extraArea);
+				}
+				extraArea.addShape(e);
 			}
-			return mapAreas;
-		}
 	}
 
-	
 	/**
 	 * Get the full bounds of this area.  As lines and polylines are
 	 * added then may go outside of the initial area.  When this happens
@@ -356,6 +416,13 @@ public class MapArea implements MapDataSource {
 	}
 
 	/**
+	 * override the initial bounds of this area.
+	 */
+	public void setBounds(Area actualBounds) {
+		this.bounds = actualBounds;
+	}
+
+	/**
 	 * Get a list of all the points.
 	 *
 	 * @return The points.
@@ -458,21 +525,39 @@ public class MapArea implements MapDataSource {
 	}
 
 	/**
+	 * Can this area be split (in a way that reduces subDivision content)?
+	 * 
+	 * If more than one item (point/line/shape) to be shown at this resolution then yes.
+	 * Must use count of items rather than the sum of numElements that addSize() estimates
+	 * because these are generated by later filters after the contents of the subDivision
+	 * has been set.
+	 *
+	 * A single shape is considered splittable because it will end up in a splitPolygonsIntoArea
+	 * area and this can then be divided down to <= MIN_DIMENSION(=10) pixel rectangle;
+	 * so, if, somehow, the shape passed through every pixel that would be 100 points, well
+	 * below MAX_POINTS_IN_ELEMENT, MAX_RNG_SIZE, MAX_XT_SHAPE_SIZE
+	 */
+	public boolean canSplit() {
+		return splittableCount > 1;
+	}
+
+	/**
 	 * Add an estimate of the size that will be required to hold this element
 	 * if it should be displayed at the given resolution.  We also keep track
 	 * of the number of <i>active</i> elements here ie elements that will be
 	 * shown because they are at a resolution at least as great as the resolution
 	 * of the area.
 	 *
-	 * @param p The element containing the minimum resolution that it will be
+	 * @param el The element containing the minimum resolution that it will be
 	 * displayed at.
 	 * @param kind What kind of element this is KIND_POINT etc.
 	 */
-	private void addSize(MapElement p, int kind) {
+	private void addSize(MapElement el, int kind) {
 
-		int res = p.getMinResolution();
-		if (res > MAX_RESOLUTION)
+		int res = el.getMinResolution();
+		if (res > areaResolution || res > MAX_RESOLUTION)
 			return;
+		++splittableCount;
 
 		int numPoints;
 		int numElements;
@@ -480,42 +565,43 @@ public class MapArea implements MapDataSource {
 		switch (kind) {
 		case POINT_KIND:
 		case XT_POINT_KIND:
-			if(res <= areaResolution) {
-				// Points are predictably less than 9 bytes.
-				sizes[kind] += 9;
-				if(!p.hasExtendedType()) {
-					if(((MapPoint) p).isCity())
-						nActiveIndPoints++;
-					else
-						nActivePoints++;
-				}
+			// Points are predictably less than 10 bytes.
+			sizes[kind] += 9;
+			if(!el.hasExtendedType()) {
+				if(((MapPoint) el).isCity())
+					nActiveIndPoints++;
+				else
+					nActivePoints++;
 			}
 			break;
 
 		case LINE_KIND:
 		case XT_LINE_KIND:
-			if(res <= areaResolution) {
-				// Estimate the size taken by lines and shapes as a constant plus
-				// a factor based on the number of points.
-				numPoints = ((MapLine) p).getPoints().size();
-				numElements = 1 + ((numPoints - 1) / LineSplitterFilter.MAX_POINTS_IN_LINE);
-				sizes[kind] += numElements * 11 + numPoints * 4;
-				if (!p.hasExtendedType())
-					nActiveLines += numElements;
-			}
+			// Estimate the size taken by lines and shapes as a constant plus
+			// a factor based on the number of points.
+			numPoints = PredictFilterPoints.predictedMaxNumPoints(((MapLine) el).getPoints(), areaResolution,
+				// assume MapBuilder.doRoads is true. subDiv.getZoom().getLevel() == 0 is maximum resolution
+				((MapLine) el).isRoad() && areaResolution == MAX_RESOLUTION);
+			if (numPoints <= 1 && !((MapLine) el).isRoad())
+				return;
+			numElements = 1 + ((numPoints - 1) / LineSplitterFilter.MAX_POINTS_IN_LINE);
+			sizes[kind] += numElements * 11 + numPoints * 4; // very pessimistic, typically less than 2 bytes are needed for one point
+			if (!el.hasExtendedType())
+				nActiveLines += numElements;
 			break;
 
 		case SHAPE_KIND:
 		case XT_SHAPE_KIND:
-			if(res <= areaResolution) {
-				// Estimate the size taken by lines and shapes as a constant plus
-				// a factor based on the number of points.
-				numPoints = ((MapLine) p).getPoints().size();
-				numElements = 1 + ((numPoints - 1) / PolygonSplitterFilter.MAX_POINT_IN_ELEMENT);
-				sizes[kind] += numElements * 11 + numPoints * 4;
-				if (!p.hasExtendedType())
-					nActiveShapes += numElements;
-			}
+			++splittableCount; // see canSplit() above
+			// Estimate the size taken by lines and shapes as a constant plus
+			// a factor based on the number of points.
+			numPoints = PredictFilterPoints.predictedMaxNumPoints(((MapShape) el).getPoints(), areaResolution, false);
+			if (numPoints <= 3)
+				return;
+			numElements = 1 + ((numPoints - 1) / PolygonSplitterFilter.MAX_POINT_IN_ELEMENT);
+			sizes[kind] += numElements * 11 + numPoints * 4; // very pessimistic, typically less than 2 bytes are needed for one point
+			if (!el.hasExtendedType())
+				nActiveShapes += numElements;
 			break;
 
 		default:
@@ -527,6 +613,63 @@ public class MapArea implements MapDataSource {
 	}
 
 	/**
+	 * Will element fit nicely?
+	 * Limit to WANTED_MAX_AREA_SIZE which is smaller than MAX_XT_xxx_SIZE
+	 * so don't need to check down to the last detail
+	 *
+	 * @param el The element. Assume want to display it at this resolution
+	 * @param kind What kind of element this is: KIND_POINT/LINE/SHAPE.
+	 */
+	private boolean canAddSize(MapElement el, int kind) {
+
+		int numPoints;
+		int numElements;
+		int sumSize = 0;
+		for (int s : sizes)
+			sumSize += s;
+
+		switch (kind) {
+		case POINT_KIND:
+			if (getNumPoints() >= MapSplitter.MAX_NUM_POINTS)
+				return false;
+			// Points are predictably less than 10 bytes.
+			if ((sumSize + 9) > MapSplitter.WANTED_MAX_AREA_SIZE)
+				return false;
+			break;
+
+		case LINE_KIND:
+			// Estimate the size taken by lines and shapes as a constant plus
+			// a factor based on the number of points.
+			numPoints = PredictFilterPoints.predictedMaxNumPoints(((MapLine) el).getPoints(), areaResolution,
+				// assume MapBuilder.doRoads is true. subDiv.getZoom().getLevel() == 0 is maximum resolution
+				((MapLine) el).isRoad() && areaResolution == MAX_RESOLUTION);
+			if (numPoints <= 1 && !((MapLine) el).isRoad())
+				break;
+			numElements = 1 + ((numPoints - 1) / LineSplitterFilter.MAX_POINTS_IN_LINE);
+			if (getNumLines() + numElements > MapSplitter.MAX_NUM_LINES)
+				return false;
+			// very pessimistic, typically less than 2 bytes are needed for one point
+			if ((sumSize + numElements * 11 + numPoints * 4) > MapSplitter.WANTED_MAX_AREA_SIZE)
+				return false;
+			break;
+
+		case SHAPE_KIND:
+			// Estimate the size taken by lines and shapes as a constant plus
+			// a factor based on the number of points.
+			numPoints = PredictFilterPoints.predictedMaxNumPoints(((MapShape) el).getPoints(), areaResolution, false);
+			if (numPoints <= 3)
+				break;
+			numElements = 1 + ((numPoints - 1) / PolygonSplitterFilter.MAX_POINT_IN_ELEMENT);
+			// very pessimistic, typically less than 2 bytes are needed for one point
+			if ((sumSize + numElements * 11 + numPoints * 4) > MapSplitter.WANTED_MAX_AREA_SIZE)
+				return false;
+			break;
+
+		}
+		return true;
+	}
+
+	/**
 	 * Add a single point to this area.
 	 *
 	 * @param p The point to add.
@@ -587,17 +730,17 @@ public class MapArea implements MapDataSource {
 	 * @param co
 	 */
 	private void addToBounds(Coord co) {
-		int lat30 = co.getHighPrecLat();
-		int latLower  = lat30 >> Coord.DELTA_SHIFT;
-		int latUpper  = (latLower << Coord.DELTA_SHIFT) < lat30 ? latLower + 1 : latLower;
+		int latHp = co.getHighPrecLat();
+		int latLower  = latHp >> Coord.DELTA_SHIFT;
+		int latUpper  = (latLower << Coord.DELTA_SHIFT) < latHp ? latLower + 1 : latLower;
 		if (latLower < minLat)
 			minLat = latLower;
 		if (latUpper > maxLat)
 			maxLat = latUpper;
 		
-		int lon30 = co.getHighPrecLon();
-		int lonLeft = lon30 >> Coord.DELTA_SHIFT;
-		int lonRight = (lonLeft << Coord.DELTA_SHIFT) < lon30 ? lonLeft + 1 : lonLeft;
+		int lonHp = co.getHighPrecLon();
+		int lonLeft = lonHp >> Coord.DELTA_SHIFT;
+		int lonRight = (lonLeft << Coord.DELTA_SHIFT) < lonHp ? lonLeft + 1 : lonLeft;
 		if (lonLeft < minLon)
 			minLon = lonLeft;
 		if (lonRight > maxLon)
@@ -616,30 +759,30 @@ public class MapArea implements MapDataSource {
 	 *
 	 * @param areas The available areas to choose from.
 	 * @param e The map element.
-	 * @param xbase30 The 30-bit x coord at the origin
-	 * @param ybase30 The 30-bit y coord of the origin
+	 * @param xbaseHp The high-precision x coord at the origin
+	 * @param ybaseHp The high-precision y coord of the origin
 	 * @param nx number of divisions.
 	 * @param ny number of divisions in y.
-	 * @param dx30 The size of each division (x direction)
-	 * @param dy30 The size of each division (y direction)
+	 * @param dxHp The size of each division (x direction)
+	 * @param dyHp The size of each division (y direction)
 	 * @return The index to areas where the map element fits.
 	 */
 	private static int pickArea(MapArea[] areas, MapElement e,
-			int xbase30, int ybase30,
+			int xbaseHp, int ybaseHp,
 			int nx, int ny,
-			int dx30, int dy30)
+			int dxHp, int dyHp)
 	{
 		int x = e.getLocation().getHighPrecLon();
 		int y = e.getLocation().getHighPrecLat();
-		int xcell = (x - xbase30) / dx30;
-		int ycell = (y - ybase30) / dy30;
+		int xcell = (x - xbaseHp) / dxHp;
+		int ycell = (y - ybaseHp) / dyHp;
 
 		if (xcell < 0) {
-			log.info("xcell was", xcell, "x", x, "xbase", xbase30);
+			log.info("xcell was", xcell, "x", x, "xbase", xbaseHp);
 			xcell = 0;
 		}
 		if (ycell < 0) {
-			log.info("ycell was", ycell, "y", y, "ybase", ybase30);
+			log.info("ycell was", ycell, "y", y, "ybase", ybaseHp);
 			ycell = 0;
 		}
 		
@@ -658,16 +801,16 @@ public class MapArea implements MapDataSource {
 	/**
 	 * Spit the polygon into areas
 	 *
-	 * Using .intersect() here is expensive. The code should be changed to
-	 * use a simple rectangle clipping algorithm as in, say, 
-	 * util/ShapeSplitter.java
-	 *
 	 * @param areas The available areas to choose from.
 	 * @param e The map element.
 	 * @param used flag vector to say area has been added to.
 	 */
-	private void splitIntoAreas(MapArea[] areas, MapShape e, boolean[] used)
+	private void splitIntoAreas(MapArea[] areas, MapShape e)
 	{
+		if (areas.length == 1) { // this happens quite a lot
+			areas[0].addShape(e);
+			return;
+		}
 		// quick check if bbox of shape lies fully inside one of the areas
 		Area shapeBounds = e.getBounds();
 
@@ -675,91 +818,75 @@ public class MapArea implements MapDataSource {
 		// tricky problems as it might not really be fully within the area.
 		// so: pretend the shape is a touch bigger. Will get the optimisation most of the time
 		// and in the boundary cases will fall into the precise code.
-		shapeBounds = new Area(shapeBounds.getMinLat()-2,
-				       shapeBounds.getMinLong()-2,
-				       shapeBounds.getMaxLat()+2,
-				       shapeBounds.getMaxLong()+2);
+		int xtra = 2;
+		// However, if the shape is significantly larger than the error margin (ie most of it
+		// should be in this area) I don't see any problem in letting it expand a little bit out
+		// of the area.
+		// This avoids very small polygons being left in the adjacent areas, which ShapeMergeFilter
+		// notices with an debug message and then output filters probably chuck away.
+		if (Math.min(shapeBounds.getWidth(), shapeBounds.getHeight()) > 8)
+			xtra = -2; // pretend shape is smaller
+
+		shapeBounds = new Area(shapeBounds.getMinLat()-xtra,
+				       shapeBounds.getMinLong()-xtra,
+				       shapeBounds.getMaxLat()+xtra,
+				       shapeBounds.getMaxLong()+xtra);
 		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
 			if (areas[areaIndex].getBounds().contains(shapeBounds)) {
-				used[areaIndex] = true;
 				areas[areaIndex].addShape(e);
 				return;
 			}
 		}
+
 		// Shape crosses area(s), we have to split it
 
-		// Convert to a awt area
-		List<Coord> coords = e.getPoints();
-		java.awt.geom.Area area = Java2DConverter.createArea(coords);
-		// remember actual coord, so can re-use
-		int origSize = coords.size();
-		Long2ObjectOpenHashMap<Coord> shapeHashMap = new Long2ObjectOpenHashMap<>(origSize);
-		for (int i = 0; i < origSize; ++i) {
-			Coord co = coords.get(i);
-			shapeHashMap.put(Utils.coord2Long(co), co);
-		}
 		if (areasHashMap == null)
 			areasHashMap = new Long2ObjectOpenHashMap<>();
 
-		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
-			java.awt.geom.Area clipper = Java2DConverter.createBoundsArea(areas[areaIndex].getBounds());
-			clipper.intersect(area);
-			List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(clipper);
-			for (List<Coord> subShape : subShapePoints) {
-				// Use original or share newly created coords on clipped edge.
-				// NB: .intersect()/areaToShapes can output flattened shapes,
-				// normally triangles, in any orientation; check we haven't got one by calc area.
-				long signedAreaSize = 0;
-				int subSize = subShape.size();
-				int c1_highPrecLat = 0, c1_highPrecLon = 0;
-				int c2_highPrecLat, c2_highPrecLon;
-				for (int i = 0; i < subSize; ++i) {
-					Coord co = subShape.get(i);
-					c2_highPrecLat = co.getHighPrecLat();
-					c2_highPrecLon = co.getHighPrecLon();
-					if (i > 0)
-						signedAreaSize += (long)(c2_highPrecLon + c1_highPrecLon) *
-									(c1_highPrecLat - c2_highPrecLat);
-					c1_highPrecLat = c2_highPrecLat;
-					c1_highPrecLon = c2_highPrecLon;
-					long hashVal = Utils.coord2Long(co);
-					Coord replCoord = shapeHashMap.get(hashVal);
-					if (replCoord != null)
-						subShape.set(i, replCoord);
-					else { // not an original coord
-						replCoord = areasHashMap.get(hashVal);
-						if (replCoord != null)
-							subShape.set(i, replCoord);
-						else
-							areasHashMap.put(hashVal, co);
-					}
+		if (areas.length == 2) { // just divide along the line between the two areas
+			int dividingLine = 0;
+			boolean isLongitude = false;
+			boolean commonLine = true;
+			if (areas[0].getBounds().getMaxLat() == areas[1].getBounds().getMinLat()) {
+				dividingLine = areas[0].getBounds().getMaxLat();
+				isLongitude = false;
+			} else if (areas[0].getBounds().getMaxLong() == areas[1].getBounds().getMinLong()) {
+				dividingLine = areas[0].getBounds().getMaxLong();
+				isLongitude = true;
+			} else {
+				commonLine = false;
+				log.error("Split into 2 expects shared edge between the areas");
+			}
+			if (commonLine) {
+				List<List<Coord>> lessList = new ArrayList<>(), moreList = new ArrayList<>();
+				ShapeSplitter.splitShape(e.getPoints(), dividingLine << Coord.DELTA_SHIFT, isLongitude, lessList, moreList, areasHashMap);
+				for (List<Coord> subShape : lessList) {
+					MapShape s = e.copy();
+					s.setPoints(subShape);
+					s.setClipped(true);
+					areas[0].addShape(s);
 				}
-				if (Math.abs(signedAreaSize) < ShapeMergeFilter.SINGLE_POINT_AREA
-						&& areas[areaIndex].areaResolution != 24) {
-					if (log.isInfoEnabled()) {
-						log.info("splitIntoAreas creates single point shape. id", e.getOsmid(),
-								"type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
-								"points, at", subShape.get(0).toOSMURL());
-					}
-					continue;
+				for (List<Coord> subShape : moreList) {
+					MapShape s = e.copy();
+					s.setPoints(subShape);
+					s.setClipped(true);
+					areas[1].addShape(s);
 				}
+				return;
+			}
+		}
 
-				if (signedAreaSize == 0) {
-					log.warn("splitIntoAreas creates single point shape. id", e.getOsmid(),
-						 "type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
-						 "points, at", subShape.get(0).toOSMURL());
-					continue;
-				}
+		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
+			List<List<Coord>> subShapePoints = ShapeSplitter.clipToBounds(e.getPoints(), areas[areaIndex].getBounds(), areasHashMap);
+			for (List<Coord> subShape : subShapePoints) {
 				MapShape s = e.copy();
 				s.setPoints(subShape);
 				s.setClipped(true);
 				areas[areaIndex].addShape(s);
-				used[areaIndex] = true;
 			}
 		}
 	}
 
-
 	/**
 	 * @return true if this area contains any data
 	 */
@@ -768,4 +895,12 @@ public class MapArea implements MapDataSource {
 			return false;
 		return true;
 	}
+
+	@Override
+	public String toString() {
+		return "MapArea [res=" + areaResolution + ", width=" + bounds.getWidth() + ", height=" + bounds.getHeight()
+				+ ", sizes=" + Arrays.toString(sizes) + "]";
+	}
+	
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index 43be0c3..0afecb6 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -13,12 +13,12 @@
 
 package uk.me.parabola.mkgmap.build;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Set;
+import java.util.function.UnaryOperator;
 
 import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.Utils;
@@ -109,19 +110,20 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 public class MapBuilder implements Configurable {
 	private static final Logger log = Logger.getLogger(MapBuilder.class);
 	private static final int CLEAR_TOP_BITS = (32 - 15);
+	private static final LocalDateTime now = LocalDateTime.now();
 	
 	private static final int MIN_SIZE_LINE = 1;
 
-	private final java.util.Map<MapPoint,POIRecord> poimap = new HashMap<MapPoint,POIRecord>();
-	private final java.util.Map<MapPoint,City> cityMap = new HashMap<MapPoint,City>();
-	private List<String> mapInfo = new ArrayList<String>();
-	private List<String> copyrights = new ArrayList<String>();
+	private final java.util.Map<MapPoint,POIRecord> poimap = new HashMap<>();
+	private final java.util.Map<MapPoint,City> cityMap = new HashMap<>();
+	private List<String> mapInfo = new ArrayList<>();
+	private List<String> copyrights = new ArrayList<>();
 
 	private boolean doRoads;
-	private Boolean driveOnLeft;
+	private Boolean driveOnLeft; // needs to be Boolean for later test:	if (driveOnLeft == null){
 	private Locator locator;
 
-	private final java.util.Map<String, Highway> highways = new HashMap<String, Highway>();
+	private final java.util.Map<String, Highway> highways = new HashMap<>();
 
 	/** name that is used for cities which name are unknown */
 	private final static String UNKNOWN_CITY_NAME = "";
@@ -686,7 +688,7 @@ public class MapBuilder implements Configurable {
 
 		// Now the levels filled with features.
 		for (LevelInfo linfo : levels) {
-			List<SourceSubdiv> nextList = new ArrayList<SourceSubdiv>();
+			List<SourceSubdiv> nextList = new ArrayList<>();
 
 			Zoom zoom = map.createZoom(linfo.getLevel(), linfo.getBits());
 
@@ -719,7 +721,7 @@ public class MapBuilder implements Configurable {
 	 * identical when they are equal.
 	 * @param shapes the list of shapes
 	 */
-	private void prepShapesForMerge(List<MapShape> shapes) {
+	private static void prepShapesForMerge(List<MapShape> shapes) {
 		Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
 		for (MapShape s : shapes){
 			List<Coord> points = s.getPoints();
@@ -825,25 +827,24 @@ public class MapBuilder implements Configurable {
 	 */
 	private void getMapInfo() {
 		if (licenseFileName != null) {
-			File file = new File(licenseFileName);
-
+			List<String> licenseArray = new ArrayList<>();
 			try {
-				BufferedReader reader = Files.newBufferedReader(file.toPath(), Charset.forName("utf-8"));
-				String text;
-
-				// repeat until all lines is read
-				while ((text = reader.readLine()) != null) {
-					if (!text.isEmpty()) {
-						mapInfo.add(text);
-					}
-				}
-
-				reader.close();
-			} catch (FileNotFoundException e) {
-				throw new ExitException("Could not open license file " + licenseFileName);
-			} catch (IOException e) {
-				throw new ExitException("Error reading license file " + licenseFileName);
+				File file = new File(licenseFileName);
+				licenseArray = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
+			}
+			catch (Exception e) {
+				throw new ExitException("Error reading license file " + licenseFileName, e);
 			}
+			if ((licenseArray.size() > 0) && licenseArray.get(0).startsWith("\ufeff"))
+				licenseArray.set(0, licenseArray.get(0).substring(1));
+			UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION)
+					.replace("$JAVA_VERSION$", System.getProperty("java.version"))
+					.replace("$YEAR$", Integer.toString(now.getYear()))
+					.replace("$LONGDATE$", now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)))
+					.replace("$SHORTDATE$", now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)))
+					.replace("$TIME$", now.toLocalTime().toString().substring(0, 5));
+			licenseArray.replaceAll(replaceVariables);
+			mapInfo.addAll(licenseArray);
 		} else {
 			mapInfo.add("Map data (c) OpenStreetMap and its contributors");
 			mapInfo.add("http://www.openstreetmap.org/copyright");
@@ -938,8 +939,7 @@ public class MapBuilder implements Configurable {
 		// points (not 1)
 		for (MapPoint point : points) {
 			if (point.isCity() &&
-			    point.getMinResolution() <= res &&
-			    point.getMaxResolution() >= res) {
+			    point.getMinResolution() <= res) {
 				++pointIndex;
 				haveIndPoints = true;
 			}
@@ -948,8 +948,7 @@ public class MapBuilder implements Configurable {
 		for (MapPoint point : points) {
 
 			if (point.isCity() ||
-			    point.getMinResolution() > res ||
-			    point.getMaxResolution() < res)
+			    point.getMinResolution() > res)
 				continue;
 
 			String name = point.getName();
@@ -1007,8 +1006,7 @@ public class MapBuilder implements Configurable {
 			for (MapPoint point : points) {
 
 				if (!point.isCity() ||
-				    point.getMinResolution() > res ||
-				    point.getMaxResolution() < res)
+				    point.getMinResolution() > res)
 					continue;
 
 				String name = point.getName();
@@ -1090,9 +1088,8 @@ public class MapBuilder implements Configurable {
 		filters.addFilter(new LineAddFilter(div, map, doRoads));
 		
 		for (MapLine line : lines) {
-			if (line.getMinResolution() > res || line.getMaxResolution() < res)
+			if (line.getMinResolution() > res)
 				continue;
-
 			filters.startFilter(line);
 		}
 	}
@@ -1137,6 +1134,7 @@ public class MapBuilder implements Configurable {
 		preserveHorizontalAndVerticalLines(res, shapes);
 		
 		LayerFilterChain filters = new LayerFilterChain(config);
+		filters.addFilter(new PolygonSplitterFilter());
 		if (enableLineCleanFilters && (res < 24)) {
 			filters.addFilter(new RoundCoordsFilter());
 			int sizefilterVal =  getMinSizePolygonForResolution(res);
@@ -1147,16 +1145,14 @@ public class MapBuilder implements Configurable {
 			if(reducePointErrorPolygon > 0)
 				filters.addFilter(new DouglasPeuckerFilter(reducePointErrorPolygon));
 		}
-		filters.addFilter(new PolygonSplitterFilter());
-		filters.addFilter(new RemoveEmpty());
 		filters.addFilter(new RemoveObsoletePointsFilter());
+		filters.addFilter(new RemoveEmpty());
 		filters.addFilter(new LinePreparerFilter(div));
 		filters.addFilter(new ShapeAddFilter(div, map));
 
 		for (MapShape shape : shapes) {
-			if (shape.getMinResolution() > res || shape.getMaxResolution() < res)
+			if (shape.getMinResolution() > res)
 				continue;
-
 			filters.startFilter(shape);
 		}
 	}
@@ -1171,11 +1167,11 @@ public class MapBuilder implements Configurable {
 	 * @param res the current resolution
 	 * @param shapes list of shapes
 	 */
-	private void preserveHorizontalAndVerticalLines(int res, List<MapShape> shapes) {
+	private static void preserveHorizontalAndVerticalLines(int res, List<MapShape> shapes) {
 		if (res == 24)
 			return;
 		for (MapShape shape : shapes) {
-			if (shape.getMinResolution() > res || shape.getMaxResolution() < res)
+			if (shape.getMinResolution() > res)
 				continue;
 			int minLat = shape.getBounds().getMinLat();
 			int maxLat = shape.getBounds().getMaxLat();
@@ -1267,7 +1263,7 @@ public class MapBuilder implements Configurable {
 			return minSizePolygon;
 	
 		if (polygonSizeLimits == null){
-			polygonSizeLimits = new HashMap<Integer, Integer>();
+			polygonSizeLimits = new HashMap<>();
 			String[] desc = polygonSizeLimitsOpt.split("[, \\t\\n]+");
 	
 			int count = 0;
diff --git a/src/uk/me/parabola/mkgmap/build/MapSplitter.java b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
index c85d448..ba8e318 100644
--- a/src/uk/me/parabola/mkgmap/build/MapSplitter.java
+++ b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
@@ -17,7 +17,6 @@
 package uk.me.parabola.mkgmap.build;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
@@ -64,7 +63,7 @@ public class MapSplitter {
 
 	// The target number of estimated bytes for one area, smaller values
 	// result in more and typically smaller areas and larger *.img files
-	private static final int WANTED_MAX_AREA_SIZE = 0x3fff; 
+	public static final int WANTED_MAX_AREA_SIZE = 0x3fff; 
 	
 	private final Zoom zoom;
 
@@ -99,9 +98,9 @@ public class MapSplitter {
 	public MapArea[] split(boolean orderByDecreasingArea) {
 		log.debug("orig area", mapSource.getBounds());
 
-		MapArea ma = initialArea(mapSource);
+		MapArea ma = initialArea(mapSource, orderByDecreasingArea);
 		MapArea[] origArea = {ma};
-		MapArea[] areas = splitMaxSize(ma, orderByDecreasingArea);
+		MapArea[] areas = splitMaxSize(ma);
 		if (areas == null) {
 			log.warn("initial split returned null for ",ma);
 			return origArea;
@@ -111,7 +110,7 @@ public class MapSplitter {
 		// in them.  For those that do, we further split them.  This is done
 		// recursively until everything fits.
 		List<MapArea> alist = new ArrayList<>();
-		addAreasToList(areas, alist, 0, orderByDecreasingArea);
+		addAreasToList(areas, alist, 0);
 		if (alist.isEmpty()) {
 			return origArea;
 		}
@@ -122,30 +121,30 @@ public class MapSplitter {
 
 	/**
 	 * Adds map areas to a list.  If an area has too many features, then it
-	 * is split into 4 and this routine is called recursively to add the new
+	 * is split into 2 and this routine is called recursively to add the new
 	 * areas.
 	 *
 	 * @param areas The areas to add to the list (and possibly split up).
 	 * @param alist The list that will finally contain the complete list of
 	 * map areas.
-	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 */
-	private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth, boolean orderByDecreasingArea) {
-		int res = zoom.getResolution();
+	private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth) {
+		int shift = zoom.getShiftValue();
+
 		for (MapArea area : areas) {
+			if (!area.hasData())
+				continue;
 			Area bounds = area.getBounds();
 			int[] sizes = area.getEstimatedSizes();
-			if (area.hasData() == false)
-				continue;
 			if(log.isInfoEnabled()) {
 				String padding = depth + "                                                                      ";
 				log.info(padding.substring(0, (depth + 1) * 2) + 
 						 bounds.getWidth() + "x" + bounds.getHeight() +
-						 ", res = " + res +
 						 ", points = " + area.getNumPoints() + "/" + sizes[MapArea.POINT_KIND] +
 						 ", lines = " + area.getNumLines() + "/" + sizes[MapArea.LINE_KIND] +
 						 ", shapes = " + area.getNumShapes() + "/" + sizes[MapArea.SHAPE_KIND]);
 			}
+
 			boolean wantSplit = false;
 			boolean mustSplit = false;
 			if (area.getNumLines() > MAX_NUM_LINES ||
@@ -156,50 +155,51 @@ public class MapSplitter {
 				sizes[MapArea.XT_POINT_KIND] > MAX_XT_POINTS_SIZE ||
 				sizes[MapArea.XT_LINE_KIND] > MAX_XT_LINES_SIZE ||
 				sizes[MapArea.XT_SHAPE_KIND] > MAX_XT_SHAPES_SIZE)
-				mustSplit = true; // we must split
-			else if (bounds.getMaxDimension() > MIN_DIMENSION) {
+				mustSplit = true;
+			else if (bounds.getMaxDimension() > (MIN_DIMENSION << shift)) {
 				int sumSize = 0;
 				for (int s : sizes)
 					sumSize += s;
 				if (sumSize > WANTED_MAX_AREA_SIZE) {
-					if (area.getLines().size() + area.getShapes().size() >= 2) {
-						// area has more bytes than wanted, and we can split
-						log.debug("splitting area because size is larger than wanted: " + sumSize);
-						wantSplit = true;
-					}
+					// area has more bytes than wanted, and large enough to split
+					log.debug("splitting area because data size is larger than wanted:", sumSize);
+					wantSplit = true;
 				}
 			}
-			if (wantSplit || mustSplit){
-				if (bounds.getMaxDimension() > MIN_DIMENSION) {
-					if (log.isDebugEnabled())
-						log.debug("splitting area", area);
+
+			if (wantSplit || mustSplit) {
+				if (!area.canSplit()) {
+					if (mustSplit)
+						log.error("Single item predicted to exceed subdivision", area.getBounds().getCenter().toOSMURL());
+					else
+						log.info("Single item larger that WANTED_MAX_AREA_SIZE", area.getBounds().getCenter().toOSMURL());
+				} else if (bounds.getMaxDimension() > (MIN_DIMENSION << shift)) {
+					log.debug("splitting area in half", area, mustSplit, wantSplit);
 					MapArea[] sublist;
-					if(bounds.getWidth() > bounds.getHeight())
-						sublist = area.split(2, 1, res, bounds, orderByDecreasingArea);
+					if (bounds.getWidth() > bounds.getHeight())
+						sublist = area.split(2, 1, bounds, false);
 					else
-						sublist = area.split(1, 2, res, bounds, orderByDecreasingArea);
-					if (sublist == null) {
-						String msg = "SubDivision is single point at this resolution so can't split at "
-								+ area.getBounds().getCenter().toOSMURL();
-						if (wantSplit) {
-							log.info(msg + " (probably harmless)");
-						} else { 
-							log.error(msg);
-						}
-					} else {
-						addAreasToList(sublist, alist, depth + 1, orderByDecreasingArea);
+						sublist = area.split(1, 2, bounds, false);
+					if (sublist == null)
+						log.error("SubDivision split failed at", area.getBounds().getCenter().toOSMURL());
+					else {
+						addAreasToList(sublist, alist, depth + 1);
 						continue;
 					}
-				} else {
-					log.error("Area too small to split at " + area.getBounds().getCenter().toOSMURL() + " (reduce the density of points, length of lines, etc.)");
+				} else if (mustSplit) { // can't reduce size, so force more subdivisions
+					log.debug("splitting area by contents", area);
+					MapArea[] sublist = area.split(1, 1, bounds, true);
+					addAreasToList(sublist, alist, depth + 1);
+					continue;
 				}
 			}
 
-			log.debug("adding area unsplit", ",has points" + area.hasPoints());
+			log.debug("adding area unsplit: has points", area.hasPoints());
 			alist.add(area);
 		}
-	}
+	} // addAreasToList
 
+    
 	/**
 	 * Split the area into portions that have the maximum size.  There is a
 	 * maximum limit to the size of a subdivision (16 bits or about 1.4 degrees
@@ -213,18 +213,29 @@ public class MapSplitter {
 	 * If the area is already small enough then it will be returned unchanged.
 	 *
 	 * @param mapArea The area that needs to be split down.
-	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 * @return An array of map areas.  Each will be below the max size.
 	 */
-	private MapArea[] splitMaxSize(MapArea mapArea, boolean orderByDecreasingArea) {
-		Area bounds = mapArea.getFullBounds();
+	private MapArea[] splitMaxSize(MapArea mapArea) {
+		/**
+		 * mapArea.getBounds() comes from the original map source or parent/split MapArea.
+		 * mapArea.getFullBounds() is calculated from elements added to the MapArea.
+		 * Normally, mapArea.getBounds() and getFullBounds() are well defined and
+		 * getFullBounds() is a little bit bigger than bounds() because lines/shapes are allowed
+		 * to go slightly out of their area.
+		 * MapArea.split() should use getBounds() because otherwise can get primary area overlap
+		 * which conflicts with concept of orderByDecreasingArea.
+		 * Some map sources might not set getBounds().
+		 * If there are no elements, getFullBounds isEmpty.
+		*/
+		Area bounds = mapArea.getBounds(); 
+		if (bounds.isEmpty()) // ??? think this func is wrong for single point/horiz/vert/line
+			bounds = mapArea.getFullBounds(); 
+		if (bounds.isEmpty())
+			return null;
 
 		int shift = zoom.getShiftValue();
 		int width = bounds.getWidth() >> shift;
 		int height = bounds.getHeight() >> shift;
-		log.info("splitMaxSize() bounds = " + bounds + " shift = " + shift + " width = " + width + " height = " + height);
-		if (log.isDebugEnabled())
-			log.debug("shifted width", width, "shifted height", height);
 
 		// There is an absolute maximum size that a division can be.  Make sure
 		// that we are well inside that.
@@ -236,17 +247,19 @@ public class MapSplitter {
 		if (height > MAX_DIVISION_SIZE)
 			ysplit = height / MAX_DIVISION_SIZE + 1;
 
-		return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds, orderByDecreasingArea);
+		log.debug("splitMaxSize: bounds", bounds, "shift", shift, "width", width, "height", height, "xsplit", xsplit, "ysplit", ysplit);
+		return mapArea.split(xsplit, ysplit, bounds, false);
 	}
 
 	/**
 	 * The initial area contains all the features of the map.
 	 *
 	 * @param src The map data source.
+	 * @param splitPolygonsIntoArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 * @return The initial map area covering the whole area and containing
 	 * all the map features that are visible.
 	 */
-	private MapArea initialArea(MapDataSource src) {
-		return new MapArea(src, zoom.getResolution());
+	private MapArea initialArea(MapDataSource src, boolean splitPolygonsIntoArea) {
+		return new MapArea(src, zoom.getResolution(), splitPolygonsIntoArea);
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/combiners/GmapiBuilder.java b/src/uk/me/parabola/mkgmap/combiners/GmapiBuilder.java
index 6dbcd6b..1b9277e 100644
--- a/src/uk/me/parabola/mkgmap/combiners/GmapiBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/GmapiBuilder.java
@@ -135,7 +135,7 @@ public class GmapiBuilder implements Combiner {
 		}
 	}
 
-	private String nameWithoutExtension(File file) {
+	private static String nameWithoutExtension(File file) {
 		String name = file.getName();
 		int len = name.length();
 		if (len < 4)
@@ -157,7 +157,7 @@ public class GmapiBuilder implements Combiner {
 		unzipImg(srcImgName, destDir);
 	}
 
-	private void unzipImg(String srcImgName, Path destDir) throws IOException {
+	private static void unzipImg(String srcImgName, Path destDir) throws IOException {
 		FileSystem fs = ImgFS.openFs(srcImgName);
 		for (DirectoryEntry ent : fs.list()) {
 			String fullname = ent.getFullName();
@@ -173,7 +173,7 @@ public class GmapiBuilder implements Combiner {
 		}
 	}
 
-	private void copyToFile(ImgChannel f, Path dest) {
+	private static void copyToFile(ImgChannel f, Path dest) {
 		ByteBuffer buf = ByteBuffer.allocate(8 * 1024);
 		try (ByteChannel outchan = Files.newByteChannel(dest, CREATE, WRITE, TRUNCATE_EXISTING)) {
 			while (f.read(buf) > 0) {
@@ -190,7 +190,7 @@ public class GmapiBuilder implements Combiner {
 		return combinerMap.get(kind).getFilename();
 	}
 
-	private String displayName(String fullname) {
+	private static String displayName(String fullname) {
 		return fullname.trim().replace("\000", "");
 	}
 
@@ -260,7 +260,7 @@ public class GmapiBuilder implements Combiner {
 		}
 	}
 
-	private void xmlElement(XMLStreamWriter writer, String name, String value) throws XMLStreamException {
+	private static void xmlElement(XMLStreamWriter writer, String name, String value) throws XMLStreamException {
 		writer.writeCharacters(" ");
 		writer.writeStartElement(NS, name);
 		writer.writeCharacters(value);
diff --git a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
index f392e67..9cf8130 100644
--- a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
@@ -29,6 +29,7 @@ import uk.me.parabola.imgfmt.FileExistsException;
 import uk.me.parabola.imgfmt.FileNotWritableException;
 import uk.me.parabola.imgfmt.FileSystemParam;
 import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.mdr.MdrConfig;
 import uk.me.parabola.imgfmt.app.srt.SRTFile;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.fs.DirectoryEntry;
@@ -83,8 +84,9 @@ public class GmapsuppBuilder implements Combiner {
 	// There is a separate MDR and SRT file for each family id in the gmapsupp
 	private final Map<Integer, MdrBuilder> mdrBuilderMap = new LinkedHashMap<>();
 	private final Map<Integer, Sort> sortMap = new LinkedHashMap<>();
-	private boolean splitName;
+	private MdrConfig mdrConfig; // one base config for all 
 	private boolean hideGmapsuppOnPC;
+	private int productVersion;
 
 
 	public void init(CommandArgs args) {
@@ -92,8 +94,11 @@ public class GmapsuppBuilder implements Combiner {
 		mapsetName = args.get("mapset-name", "OSM map set");
 		overallDescription = args.getDescription();
 		outputDir = args.getOutputDir();
-		splitName = args.get("split-name-index", false);
 		hideGmapsuppOnPC = args.get("hide-gmapsupp-on-pc", false);
+		productVersion = args.get("product-version", 100);
+		mdrConfig = new MdrConfig();
+		mdrConfig.setIndexOptions(args);
+		
 	}
 
 	/**
@@ -110,7 +115,8 @@ public class GmapsuppBuilder implements Combiner {
 			return mdrBuilder;
 
 		mdrBuilder = new MdrBuilder();
-		mdrBuilder.initForDevice(sort, outputDir, splitName);
+		mdrBuilder.initForDevice(sort, outputDir, mdrConfig);
+		
 		mdrBuilderMap.put(familyId, mdrBuilder);
 		return mdrBuilder;
 	}
@@ -423,6 +429,7 @@ public class GmapsuppBuilder implements Combiner {
 		params.setDirectoryStartEntry(DIRECTORY_OFFSET_ENTRY);
 		params.setGmapsupp(true);
 		params.setHideGmapsuppOnPC(hideGmapsuppOnPC);
+		params.setProductVersion(productVersion);
 
 		int reserveBlocks = (int) Math.ceil(bi.reserveEntries * 512.0 / blockSize);
 		params.setReservedDirectoryBlocks(reserveBlocks);
@@ -531,15 +538,15 @@ public class GmapsuppBuilder implements Combiner {
 				}
 			}
 
-			for (int i = 0; i < sortMap.size(); i++) {
-				// These files are less than 1k
-				int sz = 1024;
-				int mdrBlocks = (sz + (bs - 1)) / bs;
-				int mdrSlots = (mdrBlocks + ENTRY_SIZE - 1) / ENTRY_SIZE;
+			for (Map.Entry<Integer, Sort> ent : sortMap.entrySet()) {
+				Sort sort = ent.getValue();
+				int sz = sort.isMulti() ? 1024 * 160 : 1024; // unicode SRT file can be much bigger
+				int srtBlocks = (sz + (bs - 1)) / bs;
+				int srtSlots = (srtBlocks + ENTRY_SIZE - 1) / ENTRY_SIZE;
 
-				totBlocks += mdrBlocks;
-				totHeaderEntries += mdrSlots;
-			}
+				totBlocks += srtBlocks;
+				totHeaderEntries += srtSlots;
+			}			
 
 			// Add for header itself, plus the first directory block.
 			totHeaderEntries += DIRECTORY_OFFSET_ENTRY + 1;
diff --git a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
index 1057341..bcf56ed 100644
--- a/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/MdrBuilder.java
@@ -111,7 +111,7 @@ public class MdrBuilder implements Combiner {
 		config.setForDevice(false);
 		config.setOutputDir(outputDir);
 		config.setSort(sort);
-		config.setSplitName(args.get("split-name-index", false));
+		config.setIndexOptions(args);
 
 		// Wrap the MDR channel with the MDRFile object
 		mdrFile = new MDRFile(mdrChan, config);
@@ -129,14 +129,14 @@ public class MdrBuilder implements Combiner {
 		}
 	}
 
-	void initForDevice(Sort sort, String outputDir, boolean splitName) {
+	void initForDevice(Sort sort, String outputDir, MdrConfig baseConfig) {
 		// Set the options that we are using for the mdr.
-		MdrConfig config = new MdrConfig();
+		MdrConfig config = new MdrConfig(baseConfig);
 		config.setHeaderLen(568);
 		config.setWritable(true);
 		config.setForDevice(true);
 		config.setSort(sort);
-		config.setSplitName(splitName);
+		
 
 		// Wrap the MDR channel with the MDRFile object
 		try {
diff --git a/src/uk/me/parabola/mkgmap/combiners/OverviewBuilder.java b/src/uk/me/parabola/mkgmap/combiners/OverviewBuilder.java
index f02b71f..fea0812 100644
--- a/src/uk/me/parabola/mkgmap/combiners/OverviewBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/OverviewBuilder.java
@@ -39,6 +39,7 @@ import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapShape;
+import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
 import uk.me.parabola.mkgmap.srt.SrtTextReader;
 
 /**
@@ -51,7 +52,7 @@ import uk.me.parabola.mkgmap.srt.SrtTextReader;
 public class OverviewBuilder implements Combiner {
 	Logger log = Logger.getLogger(OverviewBuilder.class);
 	public static final String OVERVIEW_PREFIX = "ovm_";
-	private final OverviewMap overviewSource;
+	private OverviewMapDataSource overviewSource;
 	private String areaName;
 	private String overviewMapname;
 	private String overviewMapnumber;
@@ -61,10 +62,11 @@ public class OverviewBuilder implements Combiner {
 	private List<String[]> copyrightMsgs = new ArrayList<String[]>();
 	private List<String[]> licenseInfos = new ArrayList<String[]>();
 	private LevelInfo[] wantedLevels;
+	private Area bounds;
 
 
-	public OverviewBuilder(OverviewMap overviewSource) {
-		this.overviewSource = overviewSource;
+	public OverviewBuilder() {
+		this.overviewSource = new OverviewMapDataSource();
 	}
 
 	public void init(CommandArgs args) {
@@ -86,9 +88,11 @@ public class OverviewBuilder implements Combiner {
 	}
 
 	public void onFinish() {
-		addBackground();
+		overviewSource.addBackground();
 		calcLevels();
 		writeOverviewMap();
+		bounds = overviewSource.getBounds();
+		overviewSource = null; // release memory
 	}
 
 	@Override
@@ -144,18 +148,6 @@ public class OverviewBuilder implements Combiner {
 	}
 
 	/**
-	 * Add background polygon that covers the whole area of the overview map. 
-	 */
-	private void addBackground() {
-		MapShape background = new MapShape();
-		background.setType(0x4b); // background type
-		background.setMinResolution(0); // On all levels
-		background.setPoints(overviewSource.getBounds().toCoords());
-
-		overviewSource.addShape(background);
-	}
-
-	/**
 	 * Write out the overview map.
 	 */
 	private void writeOverviewMap() {
@@ -345,7 +337,7 @@ public class OverviewBuilder implements Combiner {
 		for (int l = 1; l < levels.length; l++){
 			int min = levels[l].getLevel();
 			int res = levels[l].getResolution();
-			List<Polygon> list = mapReader.shapesForLevel(min);
+			List<Polygon> list = mapReader.shapesForLevel(min, MapReader.WITH_EXT_TYPE_DATA);
 			for (Polygon shape : list) {
 				if (log.isDebugEnabled())
 					log.debug("got polygon", shape);
@@ -399,7 +391,11 @@ public class OverviewBuilder implements Combiner {
 	}
 
 	public Area getBounds() {
-		return overviewSource.getBounds();
+		if (bounds != null)
+			return bounds;
+		if (overviewSource != null)
+			return overviewSource.getBounds();
+		return new Area(1, 1, -1, -1); // return invalid bbox
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
index 1063010..d320ec5 100644
--- a/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LinePreparerFilter.java
@@ -51,6 +51,9 @@ public class LinePreparerFilter implements MapFilter {
 		MapLine line = (MapLine) element;
 
 		int numPoints = line.getPoints().size();
+		if (line instanceof MapShape && numPoints >= PolygonSplitterFilter.MAX_POINT_IN_ELEMENT)
+			throw new MustSplitException();
+
 		boolean first = true;
 		int minPointsRequired = (element instanceof MapShape) ? 3:2;
 		if (minPointsRequired == 3 && line.getPoints().get(0).equals(line.getPoints().get(numPoints-1)))
diff --git a/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java b/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
index 31bf992..8e91f57 100644
--- a/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/LineSplitterFilter.java
@@ -28,17 +28,18 @@ import uk.me.parabola.mkgmap.general.MapShape;
 
 /**
  * A filter that ensures that a line does not exceed the allowed number of
- * points that a line can have. If the line is split, the last part
- * will have at least 50 points to avoid that too small parts are filtered later.
+ * points that a line can have. If the line is split, each part will have at
+ * least 50% of allowed number of points to avoid that too small parts are
+ * filtered later.
  *
  * @author Steve Ratcliffe
+ * @author Gerd Petermann
  */
 public class LineSplitterFilter implements MapFilter {
 	private static final Logger log = Logger.getLogger(LineSplitterFilter.class);
 	
 	// Not sure of the value, probably 255.  Say 250 here.
 	public static final int MAX_POINTS_IN_LINE = 250;
-	public static final int MIN_POINTS_IN_LINE = 50;
 
 	private int level;
 	private boolean isRoutable;
@@ -69,51 +70,43 @@ public class LineSplitterFilter implements MapFilter {
 			return;
 		}
 
+		
 		log.debug("line has too many points, splitting");
-		if(line.isRoad() && level == 0 && isRoutable) {
-			MapRoad road = ((MapRoad)line);
-			if (log.isDebugEnabled())
-				log.debug("Way " + road.getRoadDef() + " has more than "+ MAX_POINTS_IN_LINE + " points and is about to be split");
+		if(line.isRoad() && level == 0 && isRoutable && log.isDebugEnabled())  {
+			log.debug("Way " + ((MapRoad)line).getRoadDef() + " has more than "+ MAX_POINTS_IN_LINE + " points and is about to be split");
 		} 
+		
+		boolean last = false;
+		int wantedSize = (npoints < 2 * MAX_POINTS_IN_LINE) ? npoints/ 2 + 1: MAX_POINTS_IN_LINE;
+		int pos = 0;
+		while (true) {
+			if (pos == 0)
+				log.debug("saving first part");
+			else if (!last)
+				log.debug("saving next part");
+			else 
+				log.debug("saving final part");
 
-		MapLine l = line.copy();
-
-		List<Coord> coords = new ArrayList<Coord>();
-		int count = 0;
-		boolean first = true;
-		int remaining = points.size();
-		int wantedSize = (remaining < MAX_POINTS_IN_LINE + MIN_POINTS_IN_LINE) ? remaining / 2 + 10 : MAX_POINTS_IN_LINE;
-
-		for (Coord co : points) {
-			coords.add(co);
-			--remaining;
-			
-			if (++count >= wantedSize) {
-				if (first)
-					log.debug("saving first part");
-				else
-					log.debug("saving next part");
-				l.setPoints(coords);
-				if (l instanceof MapRoad){
-					((MapRoad)l).setSegmentsFollowing(true);
-				}
-				next.doFilter(l);
-
-				l = line.copy();
-				count = 0;
-				first = false;
-				coords = new ArrayList<Coord>();
-				coords.add(co);
-				// make sure that the last part has at least 50 points
-				if (remaining > MAX_POINTS_IN_LINE && remaining < MAX_POINTS_IN_LINE + MIN_POINTS_IN_LINE)
-					wantedSize = remaining / 2 + 10;
-			}
-		}
-
-		if (count != 0) {
-			log.debug("saving a final part");
-			l.setPoints(coords);
+			MapLine l = line.copy();
+			l.setPoints(new ArrayList<>(points.subList(pos, pos + wantedSize)));
+			if (wantedSize < MAX_POINTS_IN_LINE / 2)
+				log.error("size?",npoints,pos,wantedSize);
+			if (!last && line instanceof MapRoad)  
+				((MapRoad)l).setSegmentsFollowing(true);
 			next.doFilter(l);
+			
+			if (last)
+				break;
+			
+			pos += wantedSize - 1; // we start with the last point of previous part
+			int remaining = npoints - pos;
+			
+			// make sure that the last parts have enough points
+			if (remaining <= MAX_POINTS_IN_LINE) {
+				last = true;
+				wantedSize = remaining;
+			} else if (remaining < 2 * MAX_POINTS_IN_LINE)
+				wantedSize = remaining / 2 + 1;
 		}
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/filters/MustSplitException.java b/src/uk/me/parabola/mkgmap/filters/MustSplitException.java
new file mode 100644
index 0000000..3efbf19
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/filters/MustSplitException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017
+ * 
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ * 
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ * 
+ */
+package uk.me.parabola.mkgmap.filters;
+
+/**
+ * Used to exit the program.  So that System.exit need only be called
+ * in the one place, or indeed not at all.
+ *
+ * @author Gerd Petermann
+ */
+public class MustSplitException extends RuntimeException {
+
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 3534824303375821690L;
+
+	/**
+	 * Constructs a new runtime exception to signal that the object needs a split.
+	 */
+	public MustSplitException() {
+		super();
+	}
+
+}
diff --git a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
index b908c85..405c100 100644
--- a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
+++ b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
@@ -16,23 +16,25 @@
  */
 package uk.me.parabola.mkgmap.filters;
 
-import java.awt.Rectangle;
-import java.awt.geom.Area;
 import java.util.List;
+import java.util.ArrayList;
 
+import uk.me.parabola.util.ShapeSplitter;
+import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.general.MapShape;
-import uk.me.parabola.util.Java2DConverter;
 
 /**
  * @author Steve Ratcliffe
  */
 public class PolygonSplitterBase extends BaseFilter {
 	protected static final int MAX_SIZE = 0xffff; // larger value causes problem in delta encoding of lines
-	private int shift;
+	protected int shift;
+	protected int resolution;
 	
 	public void init(FilterConfig config) {
 		shift = config.getShift();
+		resolution = config.getResolution();
 	}
 	
 	/**
@@ -41,73 +43,24 @@ public class PolygonSplitterBase extends BaseFilter {
 	 * @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 area = Java2DConverter.createArea(shape.getPoints());
-
-		// Get the bounds of this polygon
-		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. 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);
+		int dividingLine = 0;
+		boolean isLongitude = false;
+		Area bounds = shape.getBounds();
+		if (bounds.getWidth() > bounds.getHeight()) {
+			isLongitude = true;
+			Area[] tmpAreas = bounds.split(2, 1, shift);
+			dividingLine = tmpAreas != null ? tmpAreas[0].getMaxLong() : (bounds.getMinLong() + bounds.getWidth() / 2);
 		} 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);
+			Area[] tmpAreas = bounds.split(1, 2, shift);
+			dividingLine = tmpAreas != null ? tmpAreas[0].getMaxLat() : (bounds.getMinLat() + bounds.getHeight() / 2);
 		}
-
-		// 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 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 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
-	 * copy for the newly created shapes.
-	 * @param area The area to be converted.
-	 * @param outputs Used to hold output shapes.
-	 */
-	private void areaToShapes(MapShape origShape, Area area, List<MapShape> outputs) {
-		List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(area);
-		
+		List<List<Coord>> subShapePoints = new ArrayList<>();
+		ShapeSplitter.splitShape(shape.getPoints(), dividingLine << Coord.DELTA_SHIFT, isLongitude, subShapePoints, subShapePoints, null);
 		for (List<Coord> subShape : subShapePoints) {
-			MapShape s = origShape.copy();
+			MapShape s = shape.copy();
 			s.setPoints(subShape);
 			outputs.add(s);
 		}
 	}
+
 }
diff --git a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java
index e5d65c2..5250eea 100644
--- a/src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java
@@ -24,25 +24,20 @@ import java.util.List;
 
 /**
  * Split polygons so that they have less than the maximum number of points.
- * This is handled by using java built in classes.  Basically I am just taking
- * the bounding box, splitting that in half and getting the intersection of
- * each half-box with the original shape.  Recurse until all are small enough.
  *
- * <p>Cutting things up may make discontiguous shapes, but this is handled by
- * the java classes (for sure) and my code (probably).
- *
- * <p>Written assuming that this is not very common, once we start doing sea
- * areas, may want to re-examine to see if we can optimize.
- * 
- * @author Steve Ratcliffe
+ * @author Gerd Petermann
  */
 public class PolygonSplitterFilter extends PolygonSplitterBase implements MapFilter {
+
 	public static final int MAX_POINT_IN_ELEMENT = 250;
 
+//	public PolygonSplitterFilter() {
+//	}
+
 	/**
-	 * Split up polygons that have more than the max allowed number of points.
-	 * Initially I shall just throw out polygons that have too many points
-	 * to see if this is causing particular problems.
+	 * This filter splits a polygon if any of the subsequent filters throws a
+	 * {@link MustSplitException}.
+	 * This will not happen often.
 	 *
 	 * @param element A map element, only polygons will be processed.
 	 * @param next	This is used to pass the possibly transformed element onward.
@@ -51,36 +46,14 @@ public class PolygonSplitterFilter extends PolygonSplitterBase implements MapFil
 		assert element instanceof MapShape;
 		MapShape shape = (MapShape) element;
 
-		int n = shape.getPoints().size();
-		if (n < MAX_POINT_IN_ELEMENT) {
-			// This is ok let it through and return.
-			next.doFilter(element);
-			return;
-		}
-
-		List<MapShape> outputs = new ArrayList<MapShape>();
-
-		// Do an initial split
-		split(shape, outputs);
-
-		// Now check that all the resulting parts are also small enough.
-		// NOTE: the end condition is changed from within the loop.
-		for (int i = 0; i < outputs.size(); i++) {
-			MapShape s = outputs.get(i);
-			if (s.getPoints().size() > MAX_POINT_IN_ELEMENT) {
-				// Not small enough, so remove it and split it again.  The resulting
-				// pieces will be placed at the end of the list and will be
-				// picked up later on.
-				outputs.set(i, null);
-				split(s, outputs);
+		try {
+			next.doFilter(shape);
+		} catch (MustSplitException e) {
+			List<MapShape> outputs = new ArrayList<MapShape>();
+			split(shape, outputs); // split in half
+			for (MapShape s : outputs) {
+				doFilter(s, next); // recurse as components could still be too big
 			}
 		}
-
-		// Now add all to the chain.
-		for (MapShape s : outputs) {
-			if (s == null)
-				continue;
-			next.doFilter(s);
-		}
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/filters/PredictFilterPoints.java b/src/uk/me/parabola/mkgmap/filters/PredictFilterPoints.java
new file mode 100644
index 0000000..18ff15c
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/filters/PredictFilterPoints.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.
+ *
+ * Author: Ticker Berkin
+ * Create date: 27-Jan-2017
+ */
+package uk.me.parabola.mkgmap.filters;
+
+import java.util.List;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.imgfmt.app.CoordNode;
+
+/**
+ * Not actually a real filter, but estimates the number of points that would be left in
+ * a polygon or line after simple RemoveObsolete/RoundCoords filtering at a given resolution
+ *
+ * Possibly could be extended to predict the effect of straight lines and spike removal
+ * but wanted it to be as simple as possible to start with.
+ *
+ * @author Ticker Berkin
+ */
+public class PredictFilterPoints {
+
+	public static int predictedMaxNumPoints(List<Coord> points, int resolution, boolean checkPreserved) {
+
+	    	// see RemoveObsoletePointsFilter, RoundCoordsFilter, ... for comments on preserved and resolution
+		// %%% checkPreserved = config.getLevel() == 0 && config.isRoutable() && line.isRoad()){
+	    
+//		final int shift = 30 - resolution; // NB getting highPrec
+//		final int half = 1 << (shift - 1); // 0.5 shifted
+//		final int mask = ~((1 << shift) - 1); // to remove fraction bits
+		final int shift = 24 - resolution; // best use same info as filters
+		int half;
+		int mask;
+		if (shift == 0) {
+			half = 0;
+			mask = ~0;
+		} else {
+			half = 1 << (shift - 1); // 0.5 shifted
+			mask = ~((1 << shift) - 1); // to remove fraction bits
+		}
+
+		int numPoints = 0;
+		int lastLat = 0, lastLon = 0;
+		for (Coord p : points) {
+//			final int lat = (p.getHighPrecLat() + half) & mask;
+//			final int lon = (p.getHighPrecLon() + half) & mask;
+			final int lat = (p.getLatitude() + half) & mask;
+			final int lon = (p.getLongitude() + half) & mask;
+			if (numPoints == 0)
+				numPoints = 1; // always have one/first point
+			else {
+				if (lat != lastLat || lon != lastLon ||
+				    (checkPreserved && (p instanceof CoordNode || p.preserved())))
+					++numPoints;
+			}
+			lastLat = lat;
+			lastLon = lon;
+		}
+		return numPoints; // true shapes will have >3 points, lines >1
+	} // predictNumNumPoints
+
+}
diff --git a/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
index e77602a..ffd8462 100644
--- a/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
@@ -12,24 +12,23 @@
  */
 package uk.me.parabola.mkgmap.filters;
 
-import it.unimi.dsi.fastutil.ints.IntArrayList;
-
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
+import java.util.Comparator;
 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 it.unimi.dsi.fastutil.ints.IntArrayList;
 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;
+import uk.me.parabola.util.Java2DConverter;
 
 
 /**
@@ -41,7 +40,7 @@ import uk.me.parabola.util.MultiHashMap;
 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)); 
+	private final static ShapeHelper DUP_SHAPE = new ShapeHelper(new ArrayList<Coord>(0)); 
 	private final boolean orderByDecreasingArea;
 
 	public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
@@ -49,135 +48,232 @@ public class ShapeMergeFilter{
 		this.orderByDecreasingArea = orderByDecreasingArea;
 	}
 
+	/**
+	 * Merge shapes that are similar and have identical points.
+	 * @param shapes list of shapes
+	 * @return list of merged shapes (might be identical to original list)
+	 */
 	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<>();
 		List<MapShape> mergedShapes = new ArrayList<>();
+		List<MapShape> usableShapes = new ArrayList<>();
 		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());
+				log.error("shape is not closed with identical points", shape.getOsmid(),
+					  shape.getPoints().get(0).toOSMURL());
 				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 ");
+			usableShapes.add(shape);
+		}
+		if (usableShapes.size() < 2) {
+			mergedShapes.addAll(usableShapes);
+			return mergedShapes;
+		}
+		
+		Comparator<MapShape> comparator = new MapShapeComparator();
+		usableShapes.sort(comparator);
+		int p1 = 0;
+		MapShape s1 = usableShapes.get(0);
+		for (int i = 1; i < usableShapes.size(); i++) {
+			if (comparator.compare(s1, usableShapes.get(i)) == 0)
 				continue;
+			mergeSimilar(usableShapes.subList(p1, i), mergedShapes);
+			s1 = usableShapes.get(i);
+			p1 = i;
+		}
+		if (p1 < usableShapes.size())
+			mergeSimilar(usableShapes.subList(p1, usableShapes.size()), mergedShapes);
+		return mergedShapes;
+	}
+	
+	/**
+	 * Merge similar shapes.
+	 * @param similar list of similar shapes
+	 * @param mergedShapes list to which the shapes are added 
+	 */
+	private void mergeSimilar(List<MapShape> similar, List<MapShape> mergedShapes) {
+		if (similar.size() == 1) {
+			mergedShapes.addAll(similar);
+			return;
+		}
+		List<ShapeHelper> list = new ArrayList<>();
+		MapShape s1 = similar.get(0);
+		for (MapShape ms : similar) {
+			ShapeHelper sh = new ShapeHelper(ms.getPoints());
+			sh.id = ms.getOsmid();
+			list.add(sh);
+		}
+		tryMerge(s1, list);
+		for (ShapeHelper sh : list) {
+			MapShape newShape = s1.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);
+		}
+	}
+
+	/**
+	 * Merge ShapeHelpers. Calls itself recursively.
+	 * @param pattern a MapShape
+	 * @param similarShapes {@link ShapeHelper} instances created from similar {@link MapShape}.
+	 * This list is modified if shapes were merged.
+	 */
+	private void tryMerge(MapShape pattern, List<ShapeHelper> similarShapes) {
+		if (similarShapes.size() <= 1)
+			return;
+
+		List<ShapeHelper> noMerge = new ArrayList<>();
+		BitSet toMerge = new BitSet(similarShapes.size());
+		
+		// abuse highway count to find identical points in different shapes
+		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::resetHighwayCount));
+		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::incHighwayCount));
+		// decrement counter for duplicated start/end node
+		similarShapes.forEach(sh -> sh.getPoints().get(0).decHighwayCount());
+		
+		// points with count > 1 are probably shared by different shapes, collect the shapes
+		IdentityHashMap<Coord, BitSet> coord2Shape = new IdentityHashMap<>();
+		BitSet[] candidates = new BitSet[similarShapes.size()];
+		
+		for (int i = 0; i < similarShapes.size(); i++) {
+			ShapeHelper sh0 = similarShapes.get(i);
+			List<Coord> sharedPoints = new ArrayList<>(); 
+			for (int j = 1; j < sh0.getPoints().size(); j++) {
+				Coord c = sh0.getPoints().get(j);
+				if (c.getHighwayCount() > 1) {
+					sharedPoints.add(c);
+				}
 			}
-			if (sameTypeList.isEmpty()){
-				Map<MapShape, List<ShapeHelper>> lowMap = new LinkedHashMap<>();
-				ArrayList<ShapeHelper> list = new ArrayList<>();
-				list.add(sh);
-				lowMap.put(shape, list);
-				topMap.add(shape.getType(),lowMap);
+			if (sharedPoints.size() == 0 || sh0.getPoints().size() - sharedPoints.size()> PolygonSplitterFilter.MAX_POINT_IN_ELEMENT) {
+				// merge will not work 
+				noMerge.add(sh0);
 				continue;
 			}
-			for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
-				boolean added = false;
-				for (MapShape ms: lowMap.keySet()){
-					if (orderByDecreasingArea && ms.getFullArea() != shape.getFullArea())
-						// must not merge areas unless derived from same thing
-						continue;
-					// 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;
+			
+			assert candidates[i] == null;
+			candidates[i] = new BitSet();
+			BitSet curr = candidates[i];
+			curr.set(i);
+			
+			toMerge.set(i);
+			for (Coord c: sharedPoints) { 
+				BitSet set = coord2Shape.get(c);
+				if (set == null) {
+					set = new BitSet();
+					coord2Shape.put(c, set);
+				} else { 
+					for (int j = set.nextSetBit(0); j >= 0; j = set.nextSetBit(j + 1)) {
+						candidates[j].set(i);
 					}
+					curr.or(set);
 				}
-				if (!added){
-					ArrayList<ShapeHelper> list = new ArrayList<>();
-					list.add(sh);
-					lowMap.put(shape, list);
-				}
+				set.set(i);
 			}
 		}
+		if (coord2Shape.isEmpty()) {
+			// nothing to do
+			return;
+		}
 		
-		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);
-					}
+		List<ShapeHelper> next = new ArrayList<>();
+		boolean merged = false;
+		BitSet done = new BitSet();
+		BitSet delayed = new BitSet();  
+
+		for (int i = toMerge.nextSetBit(0); i >= 0; i = toMerge.nextSetBit(i + 1)) {
+			if (done.get(i))
+				continue;
+			BitSet all = candidates[i];
+			if (all.cardinality() <= 1) {
+				if (!all.isEmpty())
+					delayed.set(i);
+				continue;
+			}
+			all.andNot(done);
+			
+			if (all.isEmpty())
+				continue;
+			List<ShapeHelper> result = new ArrayList<>();
+			for (int j = all.nextSetBit(0); j >= 0; j = all.nextSetBit(j + 1)) {
+				ShapeHelper sh = similarShapes.get(j);
+				int oldSize = result.size();
+				result = addWithConnectedHoles(result, sh, pattern.getType());
+				if (result.size() < oldSize + 1) {
+					merged = true;
+					log.debug("shape with id", sh.id, "was merged", (oldSize + 1 - result.size()),
+							" time(s) at resolution", resolution);
 				}
 			}
+			// XXX : not exact, there may be other combinations of shapes which can be merged
+			// e.g. merge of shape 1 + 2 may not work but 2 and 3 could still be candidates.
+			done.or(all);
+			next.addAll(result);
 		}
-		log.info("merged shapes", count, "->", mergedShapes.size(), "at resolution", resolution);
-		return mergedShapes;
+		
+		delayed.andNot(done);
+		if (!delayed.isEmpty()) {
+			for (int i = delayed.nextSetBit(0); i >= 0; i = delayed.nextSetBit(i+1)) {
+				noMerge.add(similarShapes.get(i));
+			}
+		}
+		similarShapes.clear();
+		similarShapes.addAll(noMerge);
+		
+		if (merged) 
+			tryMerge(pattern, next);
+		
+		// Maybe add final step which calls addWithConnectedHoles for all remaining shapes
+		// this will find a few more merges but is still slow for maps with lots of islands 
+		similarShapes.addAll(next);
 	}
 
+
 	/**
 	 * 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.   
+	 *  to process these shapes.
 	 * @param list list of shapes with equal type
 	 * @param toAdd new shape
+	 * @param type garmin type of pattern MapShape
 	 * @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<>(list.size()+1);
+		List<ShapeHelper> result = new ArrayList<>(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);
+		for (ShapeHelper shOld : list) {
+			ShapeHelper mergeRes = tryMerge(shOld, shNew);
 			if (mergeRes == shOld){
 				result.add(shOld);
 				continue;
 			} else if (mergeRes != null){
 				shNew = mergeRes;
 			}
-			if (shNew == dupShape){
+			if (shNew == DUP_SHAPE){
 				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)
+		if (shNew != null && shNew != DUP_SHAPE)
 			result.add(shNew);
 		if (result.size() > list.size()+1 )
 			log.error("result list size is wrong", list.size(), "->", result.size());
@@ -188,11 +284,10 @@ public class ShapeMergeFilter{
 	 * 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) {
+	private ShapeHelper tryMerge(ShapeHelper sh1, ShapeHelper sh2) {
 		
 		// both clockwise or both ccw ?
 		boolean sameDir = sh1.areaTestVal > 0 && sh2.areaTestVal > 0 || sh1.areaTestVal < 0 && sh2.areaTestVal < 0;
@@ -219,14 +314,14 @@ public class ShapeMergeFilter{
 			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;
+				return DUP_SHAPE;
 			}
 		}
 		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.isEmpty())
-				return dupShape;
+				return DUP_SHAPE;
 			if (merged.get(0) != merged.get(merged.size()-1))
 				merged = null;
 			else if (merged.size() > PolygonSplitterFilter.MAX_POINT_IN_ELEMENT){
@@ -385,69 +480,31 @@ public class ShapeMergeFilter{
 			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 static class ShapeHelper{
+	private static class ShapeHelper {
 		final private List<Coord> points;
-		long id; // TODO: remove debugging aid
+		long id;
 		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.points = other.points;
 			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);
-		}
 	}
-	public final static long SINGLE_POINT_AREA = 1L<<6 * 1L<<6;
+	
+	public final static long SINGLE_POINT_AREA = 1L << Coord.DELTA_SHIFT * 1L << Coord.DELTA_SHIFT;
 	
 	/**
 	 * Calculate the high precision area size test value.  
@@ -476,5 +533,34 @@ public class ShapeMergeFilter{
 		}
 		return signedAreaSize;
 	}
+	
+	private class MapShapeComparator implements Comparator<MapShape> {
+		@Override
+		public int compare(MapShape o1, MapShape o2) {
+			int d = Integer.compare(o1.getType(), o2.getType());
+			if (d != 0) 
+				return d;
+			d = Boolean.compare(o1.isSkipSizeFilter(), o2.isSkipSizeFilter());
+			if (d != 0)
+				return d;
+			// XXX wasClipped() is ignored here, might be needed if later filters need it  
+			if (orderByDecreasingArea) {
+				d = Long.compare(o1.getFullArea(), o2.getFullArea());
+				if (d != 0)
+					return d;
+			}
+			String n1 = o1.getName();
+			String n2 = o2.getName();
+			if (n1 == n2)
+				return 0;
+			if (n1 == null) {
+				return (n2 == null) ? 0 : 1;
+			}
+			if (n2 == null)
+				return -1;
+			
+			return n1.compareTo(n2);
+		}
+	}
 }
 
diff --git a/src/uk/me/parabola/mkgmap/general/AreaClipper.java b/src/uk/me/parabola/mkgmap/general/AreaClipper.java
index 43fba40..ec1ff5b 100644
--- a/src/uk/me/parabola/mkgmap/general/AreaClipper.java
+++ b/src/uk/me/parabola/mkgmap/general/AreaClipper.java
@@ -18,6 +18,7 @@ package uk.me.parabola.mkgmap.general;
 
 import java.util.List;
 
+import uk.me.parabola.util.ShapeSplitter;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 
@@ -25,6 +26,8 @@ import uk.me.parabola.imgfmt.app.Coord;
  * Clip objects to a bounding box.
  * 
  * TODO: migrate LineClipper and PolygonClipper into here and simplify.
+ * Actually, PolygonClipper functionality now in ShapeSplitter and it
+ * is redundant
  *
  * @author Steve Ratcliffe
  */
@@ -57,16 +60,13 @@ public class AreaClipper implements Clipper {
 			collector.addShape(shape);
 			return;
 		}
-		List<List<Coord>> list = PolygonClipper.clip(bbox, shape.getPoints());
-		if (list == null) {
-			collector.addShape(shape);
-		} else {
-			for (List<Coord> lco : list) {
-				MapShape nshape = new MapShape(shape);
-				nshape.setPoints(lco);
-				nshape.setClipped(true);
-				collector.addShape(nshape);
-			}
+		List<List<Coord>> list = ShapeSplitter.clipToBounds(shape.getPoints(), bbox, null);
+		// Should we share new points on the edge?
+		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/LoadableMapDataSource.java b/src/uk/me/parabola/mkgmap/general/LoadableMapDataSource.java
index 4261a21..09efc4c 100644
--- a/src/uk/me/parabola/mkgmap/general/LoadableMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/general/LoadableMapDataSource.java
@@ -53,10 +53,11 @@ public interface LoadableMapDataSource extends MapDataSource, Configurable {
 	 * format.
 	 *
 	 * @param name The name of the resource to be loaded.
+	 * @param addBackground set to true if 0x4b polygon is wanted
 	 * @throws FileNotFoundException When the file or resource is not found.
 	 * @throws FormatException For any kind of malformed input.
 	 */
-	public void load(String name)
+	public void load(String name, boolean addBackground)
 			throws FileNotFoundException, FormatException;
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index 337951a..8aceff5 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -21,7 +21,6 @@ import java.util.HashMap;
 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.imgfmt.app.net.GeneralRouteRestriction;
@@ -47,10 +46,10 @@ public class MapDetails implements MapCollector, MapDataSource {
 	private final List<MapShape> shapes = new ArrayList<MapShape>();
 	private final List<MapPoint> points = new ArrayList<MapPoint>();
 
-	private int minLat30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT ;
-	private int minLon30 = Utils.toMapUnit(180.0) << Coord.DELTA_SHIFT;
-	private int maxLat30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
-	private int maxLon30 = Utils.toMapUnit(-180.0) << Coord.DELTA_SHIFT;
+	private int minLatHp = Integer.MAX_VALUE;
+	private int minLonHp = Integer.MAX_VALUE;
+	private int maxLatHp = Integer.MIN_VALUE;
+	private int maxLonHp = Integer.MIN_VALUE;
 
 	// Keep lists of all items that were used.
 	private final Map<Integer, Integer> pointOverviews = new HashMap<Integer, Integer>();
@@ -141,17 +140,17 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 * @param p The coordinates of the point to add.
 	 */
 	public void addToBounds(Coord p) {
-		int lat30 = p.getHighPrecLat(); 
-		int lon30 = p.getHighPrecLon();
+		int latHp = p.getHighPrecLat(); 
+		int lonHp = p.getHighPrecLon();
 		
-		if (lat30 < minLat30)
-			minLat30 = lat30;
-		if (lat30 > maxLat30)
-			maxLat30 = lat30;
-		if (lon30 < minLon30)
-			minLon30 = lon30;
-		if (lon30 > maxLon30)
-			maxLon30 = lon30;
+		if (latHp < minLatHp)
+			minLatHp = latHp;
+		if (latHp > maxLatHp)
+			maxLatHp = latHp;
+		if (lonHp < minLonHp)
+			minLonHp = lonHp;
+		if (lonHp > maxLonHp)
+			maxLonHp = lonHp;
 	}
 
 	/**
@@ -160,13 +159,13 @@ public class MapDetails implements MapCollector, MapDataSource {
 	 * @return An area covering all the points in the map.
 	 */
 	public Area getBounds() {
-		int minLat = minLat30 >> Coord.DELTA_SHIFT;
-		int maxLat = maxLat30 >> Coord.DELTA_SHIFT;
-		int minLon = minLon30 >> Coord.DELTA_SHIFT;
-		int maxLon = maxLon30 >> Coord.DELTA_SHIFT;
-		if ((maxLat << Coord.DELTA_SHIFT) < maxLat30)
+		int minLat = minLatHp >> Coord.DELTA_SHIFT;
+		int maxLat = maxLatHp >> Coord.DELTA_SHIFT;
+		int minLon = minLonHp >> Coord.DELTA_SHIFT;
+		int maxLon = maxLonHp >> Coord.DELTA_SHIFT;
+		if ((maxLat << Coord.DELTA_SHIFT) < maxLatHp)
 			maxLat++;
-		if ((maxLon << Coord.DELTA_SHIFT) < maxLon30)
+		if ((maxLon << Coord.DELTA_SHIFT) < maxLonHp)
 			maxLon++;
 		return new Area(minLat, minLon, maxLat, maxLon);
 	}
diff --git a/src/uk/me/parabola/mkgmap/general/MapLine.java b/src/uk/me/parabola/mkgmap/general/MapLine.java
index 04531ab..67bbb5e 100644
--- a/src/uk/me/parabola/mkgmap/general/MapLine.java
+++ b/src/uk/me/parabola/mkgmap/general/MapLine.java
@@ -78,9 +78,10 @@ public class MapLine extends MapElement {
 	public void testForConsecutivePoints(List<Coord> points) {
 		Coord last = null;
 		for (Coord co : points) {
-			if (last != null && last.equals(co))
-				log.info("Line " + getName() + " has consecutive equal points at " + co.toDegreeString());
-			else {
+			if (last != null && last.equals(co)) {
+				if (log.isInfoEnabled())
+					log.info("Line", getName() , "has consecutive equal points at" , co.toDegreeString());
+			} else {
 				addToBounds(co);
 				last = co;
 			}
diff --git a/src/uk/me/parabola/mkgmap/general/PolygonClipper.java b/src/uk/me/parabola/mkgmap/general/PolygonClipper.java
deleted file mode 100644
index 36d7eb3..0000000
--- a/src/uk/me/parabola/mkgmap/general/PolygonClipper.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2008 Steve Ratcliffe
- * 
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2 as
- *  published by the Free Software Foundation.
- * 
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- * 
- * 
- * Author: Steve Ratcliffe
- * Create date: 01-Jul-2008
- */
-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;
-
-/**
- * Clip a polygon to the given bounding box.  This may result in more than
- * one polygon.
- *
- * @author Steve Ratcliffe
- */
-public class PolygonClipper {
-	/**
-	 * Clip the input polygon to the given area.
-	 * @param bbox The bounding box.
-	 * @param coords The coords of the polygon.
-	 * @return Return null if the polygon is already completely inside the
-	 * bounding box.
-	 */
-	public static List<List<Coord>> clip(Area bbox, List<Coord> coords) {
-		if (bbox == null)
-			return null;
-
-		// If all the points are inside the box then we just return null
-		// to show that nothing was done and the line can be used.  This
-		// is expected to be the normal case.
-		boolean foundOutside = false;
-		for (Coord co : coords) {
-			if (!bbox.contains(co)) {
-				foundOutside = true;
-				break;
-			}
-		}
-		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);
-
-		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/main/Main.java b/src/uk/me/parabola/mkgmap/main/Main.java
index 343a9d3..e3a07f3 100644
--- a/src/uk/me/parabola/mkgmap/main/Main.java
+++ b/src/uk/me/parabola/mkgmap/main/Main.java
@@ -55,13 +55,11 @@ import uk.me.parabola.mkgmap.combiners.MdrBuilder;
 import uk.me.parabola.mkgmap.combiners.MdxBuilder;
 import uk.me.parabola.mkgmap.combiners.NsisBuilder;
 import uk.me.parabola.mkgmap.combiners.OverviewBuilder;
-import uk.me.parabola.mkgmap.combiners.OverviewMap;
 import uk.me.parabola.mkgmap.combiners.TdbBuilder;
 import uk.me.parabola.mkgmap.osmstyle.StyleFileLoader;
 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
 import uk.me.parabola.mkgmap.reader.osm.Style;
 import uk.me.parabola.mkgmap.reader.osm.StyleInfo;
-import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
 import uk.me.parabola.mkgmap.scan.SyntaxException;
 import uk.me.parabola.mkgmap.srt.SrtTextReader;
 import uk.me.parabola.util.EnhancedProperties;
@@ -142,7 +140,13 @@ public class Main implements ArgumentProcessor {
 			++numExitExceptions;
 		} catch (ExitException e) {
 			++numExitExceptions;
-			System.err.println(e.getMessage());
+			String message = e.getMessage();
+			Throwable cause = e.getCause();
+			while (cause != null) {
+				message += "\r\n" + cause.toString();
+				cause = cause.getCause();
+			}
+			System.err.println(message);
 		}
 		
 		System.out.println("Number of ExitExceptions: " + numExitExceptions);
@@ -338,8 +342,7 @@ public class Main implements ArgumentProcessor {
 	 */
 	private void addTdbBuilder() {
 		if (!tdbBuilderAdded ){
-			OverviewMap overviewSource = new OverviewMapDataSource();
-			OverviewBuilder overviewBuilder = new OverviewBuilder(overviewSource);
+			OverviewBuilder overviewBuilder = new OverviewBuilder();
 			addCombiner("img", overviewBuilder);
 			TdbBuilder tdbBuilder = new TdbBuilder(overviewBuilder);
 			addCombiner("tdb", tdbBuilder);
diff --git a/src/uk/me/parabola/mkgmap/main/MapMaker.java b/src/uk/me/parabola/mkgmap/main/MapMaker.java
index f3309f4..82201e6 100644
--- a/src/uk/me/parabola/mkgmap/main/MapMaker.java
+++ b/src/uk/me/parabola/mkgmap/main/MapMaker.java
@@ -31,7 +31,7 @@ import uk.me.parabola.mkgmap.CommandArgs;
 import uk.me.parabola.mkgmap.build.MapBuilder;
 import uk.me.parabola.mkgmap.combiners.OverviewBuilder;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-import uk.me.parabola.mkgmap.reader.plugin.MapReader;
+import uk.me.parabola.mkgmap.reader.MapReader;
 
 /**
  * Main routine for the command line map-making utility.
@@ -151,7 +151,7 @@ public class MapMaker implements MapProcessor {
 		LoadableMapDataSource src = MapReader.createMapReader(name);
 		src.config(args.getProperties());
 		log.info("Started loading", name);
-		src.load(name);
+		src.load(name, args.getProperties().getProperty("transparent", false) == false);
 		log.info("Finished loading", name);
 		return src;
 	}
diff --git a/src/uk/me/parabola/mkgmap/main/StyleTester.java b/src/uk/me/parabola/mkgmap/main/StyleTester.java
index e2fe865..e89dba7 100644
--- a/src/uk/me/parabola/mkgmap/main/StyleTester.java
+++ b/src/uk/me/parabola/mkgmap/main/StyleTester.java
@@ -29,6 +29,7 @@ import java.util.Collections;
 import java.util.Formatter;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.Set;
 import java.util.regex.Pattern;
 
@@ -71,8 +72,8 @@ import uk.me.parabola.mkgmap.reader.osm.Style;
 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
 import uk.me.parabola.mkgmap.reader.osm.WatchableTypeResult;
 import uk.me.parabola.mkgmap.reader.osm.Way;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5XmlHandler;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5XmlHandler.SaxHandler;
+import uk.me.parabola.mkgmap.reader.osm.xml.OsmXmlHandler;
+import uk.me.parabola.mkgmap.reader.osm.xml.OsmXmlHandler.SaxHandler;
 import uk.me.parabola.mkgmap.scan.SyntaxException;
 import uk.me.parabola.mkgmap.scan.Token;
 import uk.me.parabola.mkgmap.scan.TokenScanner;
@@ -130,8 +131,6 @@ public class StyleTester implements OsmConverter {
 
 	private final OsmConverter converter;
 
-	// The file may contain a known good set of results.  They are saved here
-	private final List<String> givenResults = new ArrayList<String>();
 	private static boolean forceUseOfGiven;
 	private static boolean showMatches;
 	private static boolean print = true;
@@ -143,7 +142,7 @@ public class StyleTester implements OsmConverter {
 			converter = makeStyleConverter(stylefile, coll);
 	}
 
-	public static void main(String[] args) throws IOException {
+	public static void main(String... args) throws IOException {
 		String[] a = processOptions(args);
 		if (a.length == 1)
 			runSimpleTest(a[0]);
@@ -155,8 +154,8 @@ public class StyleTester implements OsmConverter {
 		StyleTester.out = out;
 	}
 
-	private static String[] processOptions(String[] args) {
-		List<String> a = new ArrayList<String>();
+	private static String[] processOptions(String... args) {
+		List<String> a = new ArrayList<>();
 		for (String s : args) {
 			if (s.startsWith("--reference")) {
 				System.out.println("# using reference method of calculation");
@@ -195,7 +194,7 @@ public class StyleTester implements OsmConverter {
 				EnhancedProperties props = new EnhancedProperties();
 				props.put("preserve-element-order", "1");
 				ElementSaver saver = new ElementSaver(props);
-				Osm5XmlHandler handler = new Osm5XmlHandler(props);
+				OsmXmlHandler handler = new OsmXmlHandler();
 				SaxHandler saxHandler = handler.new SaxHandler();
 				handler.setElementSaver(saver);
 				parser.parse(is, saxHandler);
@@ -227,17 +226,18 @@ public class StyleTester implements OsmConverter {
 			BufferedReader br = new BufferedReader(reader);
 			List<Way> ways = readSimpleTestFile(br);
 
-			List<MapElement> results = new ArrayList<MapElement>();
-
-			List<MapElement> strictResults = new ArrayList<MapElement>();
-
-			OsmConverter strict = new StyleTester("styletester.style", new LocalMapCollector(strictResults), true);
-			List<String> givenList = ((StyleTester) strict).givenResults;
+			List<String> givenList = readGivenResults();
+			boolean noStrict = false;
+			if (!givenList.isEmpty() && Objects.equals(givenList.get(0), "NO-STRICT")) {
+				givenList.remove(0);
+				noStrict = true;
+			}
 
-			List<String> all = new ArrayList<String>();
+			List<String> all = new ArrayList<>();
+			List<MapElement> strictResults = new ArrayList<>();
+			List<MapElement> results = new ArrayList<>();
 			for (Way w : ways) {
 				OsmConverter normal = new StyleTester("styletester.style", new LocalMapCollector(results), false);
-				strict = new StyleTester("styletester.style", new LocalMapCollector(strictResults), true);
 
 				String prefix = "WAY " + w.getId() + ": ";
 				normal.convertWay(w.copy());
@@ -246,19 +246,21 @@ public class StyleTester implements OsmConverter {
 				all.addAll(Arrays.asList(actual));
 				results.clear();
 
-				strict.convertWay(w.copy());
-				strict.end();
-				String[] expected = formatResults(prefix, strictResults);
-				strictResults.clear();
-
 				printResult(actual);
 
-				if (!Arrays.deepEquals(actual, expected)) {
-					out.println("ERROR expected result is:");
-					printResult(expected);
+				if (!noStrict) {
+					OsmConverter strict = new StyleTester("styletester.style", new LocalMapCollector(strictResults), true);
+					strict.convertWay(w.copy());
+					strict.end();
+					String[] expected = formatResults(prefix, strictResults);
+					strictResults.clear();
+
+					if (!Arrays.deepEquals(actual, expected)) {
+						out.println("ERROR expected result is:");
+						printResult(expected);
+					}
 				}
 
-				out.println();
 			}
 
 			String[] given = givenList.toArray(new String[givenList.size()]);
@@ -273,6 +275,27 @@ public class StyleTester implements OsmConverter {
 		}
 	}
 
+	private static List<String> readGivenResults() {
+		List<String> givenResults = new ArrayList<>();
+
+		//BufferedReader br = null;
+		try (StyleFileLoader fileLoader = StyleFileLoader.createStyleLoader(STYLETESTER_STYLE, null);
+			Reader reader = fileLoader.open("results");
+			BufferedReader br = new BufferedReader(reader)
+		) {
+			String line;
+			while ((line = br.readLine()) != null) {
+				line = line.trim();
+				if (line.isEmpty())
+					continue;
+				givenResults.add(line);
+			}
+		} catch (IOException e) {
+			// there are no known good results given, that is OK
+		}
+
+		return givenResults;
+	}
 
 	public void convertWay(Way way) {
 		converter.convertWay(way);
@@ -310,7 +333,7 @@ public class StyleTester implements OsmConverter {
 	 * The style does not need to include 'version' as this is added for you.
 	 */
 	private static List<Way> readSimpleTestFile(BufferedReader br) throws IOException {
-		List<Way> ways = new ArrayList<Way>();
+		List<Way> ways = new ArrayList<>();
 
 		String line;
 		while ((line = br.readLine()) != null) {
@@ -427,9 +450,7 @@ public class StyleTester implements OsmConverter {
 			field.setAccessible(true);
 			int tabA = (Integer) field.get(roadDef);
 			return tabA & 0x7;
-		} catch (NoSuchFieldException e) {
-			e.printStackTrace();
-		} catch (IllegalAccessException e) {
+		} catch (NoSuchFieldException | IllegalAccessException e) {
 			e.printStackTrace();
 		}
 		return 0;
@@ -485,8 +506,8 @@ public class StyleTester implements OsmConverter {
 		return new StyledConverter(style, coll, new EnhancedProperties());
 	}
 
-	public static void forceUseOfGiven(boolean force) {
-		forceUseOfGiven = force;
+	public static void forceUseOfGiven() {
+		forceUseOfGiven = true;
 	}
 
 	/**
@@ -509,12 +530,11 @@ public class StyleTester implements OsmConverter {
 		 * @throws FileNotFoundException If the file doesn't exist.  This can include
 		 * the version file being missing.
 		 */
-		public ReferenceStyle(String loc, String name) throws FileNotFoundException {
+		ReferenceStyle(String loc, String name) throws FileNotFoundException {
 			super(loc, name);
 			fileLoader = StyleFileLoader.createStyleLoader(loc, name);
 
 			setupReader();
-			readGivenResults();
 		}
 
 		private void setupReader() {
@@ -522,26 +542,6 @@ public class StyleTester implements OsmConverter {
 			levels = LevelInfo.createFromString(l);
 		}
 
-		private void readGivenResults() {
-			givenResults.clear();
-			BufferedReader br = null;
-			try {
-				Reader reader = fileLoader.open("results");
-				br = new BufferedReader(reader);
-				String line;
-				while ((line = br.readLine()) != null) {
-					line = line.trim();
-					if (line.isEmpty())
-						continue;
-					givenResults.add(line);
-				}
-			} catch (IOException e) {
-				// there are no known good results given, that is OK
-			} finally {
-				Utils.closeFile(br);
-			}
-		}
-
 		/**
 		 * Throws away the rules as previously read and reads again using the
 		 * SimpleRuleFileReader which does not re-order or optimise the rules
@@ -624,8 +624,8 @@ public class StyleTester implements OsmConverter {
 		 * the file, this should work.
 		 */
 		private class ReferenceRuleSet implements Rule {
-			private final List<Rule> rules = new ArrayList<Rule>();
-			int cacheId = 0;
+			private final List<Rule> rules = new ArrayList<>();
+			int cacheId;
 			
 			public void add(Rule rule) {
 				rules.add(rule);
@@ -658,7 +658,7 @@ public class StyleTester implements OsmConverter {
 					if (a.isResolved())
 						break;
 				}
-				if (showMatches && !tagsBefore.equals(el.toTagString()))
+				if (showMatches && !Objects.equals(tagsBefore, el.toTagString()))
 					out.println("# Way tags after: " + el.toTagString());
 			}
 
@@ -689,7 +689,7 @@ public class StyleTester implements OsmConverter {
 
 			@Override
 			public boolean containsExpression(String exp) {
-				if (rules == null) {
+				if (rules.isEmpty()) {
 					// this method must be called after prepare() is called so
 					// that we have rules to which the finalize rules can be applied
 					throw new IllegalStateException("First call prepare() before setting the finalize rules");
@@ -717,9 +717,9 @@ public class StyleTester implements OsmConverter {
 			private final ReferenceRuleSet rules;
 			private ReferenceRuleSet finalizeRules;
 			private TokenScanner scanner;
-			private boolean inFinalizeSection = false;
+			private boolean inFinalizeSection;
 
-			public SimpleRuleFileReader(FeatureKind kind, LevelInfo[] levels, ReferenceRuleSet rules) {
+			SimpleRuleFileReader(FeatureKind kind, LevelInfo[] levels, ReferenceRuleSet rules) {
 				this.rules = rules;
 				typeReader = new TypeReader(kind, levels);
 			}
@@ -772,7 +772,7 @@ public class StyleTester implements OsmConverter {
 				if (scanner.isEndOfFile())
 					return false;
 
-				if (inFinalizeSection == false && scanner.checkToken("<")) {
+				if (!inFinalizeSection && scanner.checkToken("<")) {
 					Token token = scanner.nextToken();
 					if (scanner.checkToken("finalize")) {
 						Token finalizeToken = scanner.nextToken();
@@ -868,7 +868,7 @@ public class StyleTester implements OsmConverter {
 				start = System.currentTimeMillis();
 			}
 			if (print) {
-				String[] strings = formatResults("", Arrays.<MapElement>asList(line));
+				String[] strings = formatResults("", Collections.singletonList(line));
 				printResult(strings);
 			}
 		}
@@ -877,7 +877,7 @@ public class StyleTester implements OsmConverter {
 
 		public void addRoad(MapRoad road) {
 			if (print) {
-				String[] strings = formatResults("", Collections.<MapElement>singletonList(road));
+				String[] strings = formatResults("", Collections.singletonList(road));
 				printResult(strings);
 			}
 		}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
index 9d6185e..a9f439a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
@@ -48,7 +48,7 @@ public class ActionRule implements Rule {
 	/** Finalize rules must not have an element type definition so the add method must never be called. */
 	private final static TypeResult finalizeTypeResult = new TypeResult() {
 		public void add(Element el, GType type) {
-			throw new UnsupportedOperationException("Finalize rules must not contain an action block.");
+			throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
 		}
 	};	
 	
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
index cfa586d..3dfcfa8 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
@@ -41,7 +41,7 @@ public class ExpressionRule implements Rule {
 	/** Finalize rules must not have an element type definition so the add method must never be called. */
 	private final static TypeResult finalizeTypeResult = new TypeResult() {
 		public void add(Element el, GType type) {
-			throw new UnsupportedOperationException("Finalize rules must not contain an action block.");
+			throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
 		}
 	};
 	
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/NameFinder.java b/src/uk/me/parabola/mkgmap/osmstyle/NameFinder.java
new file mode 100644
index 0000000..34cb52f
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/NameFinder.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.mkgmap.osmstyle;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.regex.Pattern;
+
+import it.unimi.dsi.fastutil.shorts.ShortArrayList;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
+import uk.me.parabola.mkgmap.reader.osm.Tags;
+
+/**
+ * Class to handle the option name-tag-list
+ * @author Gerd Petermann
+ *
+ */
+public class NameFinder {
+	private final ShortArrayList compiledNameTagList;
+	
+	private static final Pattern COMMA_OR_SPACE_PATTERN = Pattern.compile("[,\\s]+");
+	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
+	
+	public NameFinder(Properties props) {
+		this.compiledNameTagList = computeCompiledNameTags(props);
+	}
+
+	public static List<String> getNameTags(Properties props) {
+		String nameTagProp = props.getProperty("name-tag-list", "name");
+		return Arrays.asList(COMMA_OR_SPACE_PATTERN.split(nameTagProp));
+	}
+
+	/**
+	 * Analyse name-tag-list option.
+	 * @param props program properties
+	 * @return list of compiled tag keys or null if only name should be used.
+	 */
+	private static ShortArrayList computeCompiledNameTags(Properties props) {
+		if (props == null)
+			return null;
+		String nameTagProp = props.getProperty("name-tag-list", "name");
+		if ("name".equals(nameTagProp))
+			return null;
+		return TagDict.compileTags(COMMA_OR_SPACE_PATTERN.split(nameTagProp));
+	}
+	
+	
+	/**
+	 * Get name tag value according to name-tag-list.
+	 * @param el
+	 * @return the tag value or null if none of the name tags is set. 
+	 */
+	public String getName(Element el) {
+		if (compiledNameTagList == null)
+			return el.getTag(nameTagKey);
+
+		for (short tagKey : compiledNameTagList) {
+			String val = el.getTag(tagKey);
+			if (val != null) {
+				return val;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get name tag value according to name-tag-list.
+	 * @param tags the tags to check
+	 * @return the tag value or null if none of the name tags is set. 
+	 */
+	public String getName(Tags tags) {
+		if (compiledNameTagList == null)
+			return tags.get(nameTagKey);
+
+		for (short tagKey : compiledNameTagList) {
+			String val = tags.get(tagKey);
+			if (val != null) {
+				return val;
+			}
+		}
+		return null;
+	}
+	
+	/**
+	 * Use name-tag-list to set the name tag for the element. 
+	 * @param el the element
+	 */
+	public void setNameWithNameTagList(Element el) {
+		if (compiledNameTagList == null)
+			return;
+
+		for (short tagKey : compiledNameTagList) {
+			String val = el.getTag(tagKey);
+			if (val != null) {
+				if (tagKey != nameTagKey) {
+					// add or replace name 
+					el.addTag(nameTagKey, val);
+				}
+				break;
+			}
+		}
+		
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/PrefixSuffixFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/PrefixSuffixFilter.java
new file mode 100644
index 0000000..6a7e184
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/PrefixSuffixFilter.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.app.mdr.Mdr7;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.general.MapRoad;
+import uk.me.parabola.mkgmap.scan.TokType;
+import uk.me.parabola.mkgmap.scan.Token;
+import uk.me.parabola.mkgmap.scan.TokenScanner;
+import uk.me.parabola.util.EnhancedProperties;
+
+/**
+ * Code to add special Garmin separators 0x1b, 0x1e and 0x1f. 
+ * The separator 0x1e tells Garmin that the part of the name before that separator
+ * should not be displayed when zooming out enough. It is displayed like a blank. 
+ * The separator 0x1f tells Garmin that the part of the name after that separator 
+ * should not be displayed when zooming out enough. It is displayed like a blank. 
+ * The separator 0x1b works like 0x1e, but is not displayed at all.
+ * The separator 0x1c works like 0x1f, but is not displayed at all.
+ * See also class {@link Mdr7}. 
+ * 
+ * @author Gerd Petermann
+ *
+ */
+public class PrefixSuffixFilter {
+	private static final Logger log = Logger.getLogger(PrefixSuffixFilter.class);
+
+	private static final int MODE_PREFIX = 0;
+	private static final int MODE_SUFFIX = 1;
+	
+	private boolean enabled;
+	private final Set<String> languages = new LinkedHashSet<>();
+	private final Map<String, List<String>> langPrefixMap = new HashMap<>();
+	private final Map<String, List<String>> langSuffixMap = new HashMap<>();
+	private final Map<String, List<String>> countryLanguageMap = new HashMap<>();
+	private final Map<String, List<String>> countryPrefixMap = new HashMap<>();
+	private final Map<String, List<String>> countrySuffixMap = new HashMap<>();
+
+	private EnhancedProperties options = new EnhancedProperties();
+
+	public PrefixSuffixFilter(EnhancedProperties props) {
+		String cfgFile = props.getProperty("road-name-config",null);
+		enabled = readConfig(cfgFile);
+	}
+
+	/**
+	 * Read the configuration file for this filter.
+	 * @param cfgFile path to file
+	 * @return true if filter can be used, else false.
+	 */
+	private boolean readConfig(String cfgFile) {
+		if (cfgFile == null) 
+			return false;
+		try (InputStreamReader reader = new InputStreamReader(new FileInputStream(cfgFile), "utf-8")) {
+			readOptionFile(reader, cfgFile);
+			return true;
+		} catch (Exception e) {
+			log.error(e.getMessage());
+			log.error(this.getClass().getSimpleName() + " disabled, failed to read config file " + cfgFile);
+			return false;
+		}
+	}
+	
+	/**
+	 * 
+	 * @param r
+	 * @param filename
+	 */
+	private void readOptionFile(Reader r, String filename) {
+		BufferedReader br = new BufferedReader(r);
+		TokenScanner ts = new TokenScanner(filename, br);
+		ts.setExtraWordChars(":");
+
+		while (!ts.isEndOfFile()) {
+			Token tok = ts.nextToken();
+			if (tok.isValue("#")) {
+				ts.skipLine();
+				continue;
+			}
+
+			String key = tok.getValue();
+
+			ts.skipSpace();
+			tok = ts.peekToken();
+			
+			if (tok.getType() == TokType.SYMBOL) {
+
+				String punc = ts.nextValue();
+				String val;
+				if (punc.equals(":") || punc.equals("=")) {
+					val = ts.readLine();
+				} else {
+					ts.skipLine();
+					continue;
+				}
+				processOption(key, val);
+			} else if (key != null){
+				throw new IllegalArgumentException("don't understand line with " + key );
+			} else {
+				ts.skipLine();
+			}
+		}
+		/**
+		 * process lines starting with prefix1 or prefix2. 
+		 */
+		for (String lang : languages) {
+			String prefix1 = options.getProperty("prefix1:" + lang, null);
+			if (prefix1 == null)
+				continue;
+			String prefix2 = options.getProperty("prefix2:" + lang, null);
+			List<String> p1 = prefix1 != null ? Arrays.asList(prefix1.split(",")) : Collections.emptyList();
+			List<String> p2 = prefix2 != null ? Arrays.asList(prefix2.split(",")) : Collections.emptyList();
+			langPrefixMap.put(lang, genPrefix(p1, p2));
+		}
+	}
+
+	private void processOption(String key, String val) {
+		String[] keysParts = key.split(":");
+		String[] valParts = val.split(",");
+		if (keysParts.length < 2 || val.isEmpty() || valParts.length < 1) {
+			throw new IllegalArgumentException("don't understand " + key + " = " + val);
+		}
+		switch (keysParts[0].trim()) {
+		case "prefix1":
+		case "prefix2":
+			options.put(key, val); // store for later processing
+			break;
+		case "suffix":
+			List<String> suffixes = new ArrayList<>();
+			for (String s : valParts) {
+				suffixes.add(stripBlanksAndQuotes(s));
+			}
+			sortByLength(suffixes);
+			langSuffixMap.put(keysParts[1].trim(), suffixes);
+			break;
+		case "lang":
+			String iso = keysParts[1].trim();
+			List<String> langs = new ArrayList<>();
+			for (String lang : valParts) {
+				langs.add(lang.trim());
+			}
+			countryLanguageMap .put(iso, langs);
+			languages.addAll(langs);
+		default:
+			break;
+		}
+	}
+	
+
+	/** Create all combinations of items in prefix1 with items in prefix2 and finally prefix1 with an extra blank.  
+	 * @param prefix1 list of prefix words
+	 * @param prefix2 list of prepositions
+	 * @return all combinations
+	 */
+	private List<String> genPrefix (List<String> prefix1, List<String> prefix2) {
+		List<String> prefixes = new ArrayList<>();
+		for (String p1 : prefix1) {
+			p1 = stripBlanksAndQuotes(p1);
+			for (String p2 : prefix2) {
+				p2 = stripBlanksAndQuotes(p2);
+				prefixes.add(p1 + " " + p2);
+			}
+			prefixes.add(p1 + " ");
+		}
+		return prefixes;
+	}
+
+	/**
+	 * First remove leading and trailing blanks, next check for paired quotes
+	 * @param s the string 
+	 * @return the modified string
+	 */
+	private String stripBlanksAndQuotes(String s) {
+		s = s.trim();
+		if (s.startsWith("'") && s.endsWith("'") || s.startsWith("\"") && s.endsWith("\"")) {
+			return s.substring(1, s.length()-1);
+		}
+		return s;
+	}
+	
+	
+	/**
+	 * Modify all labels of a road. Each label is checked against country specific lists of 
+	 * well known prefixes (e.g. "Rue de la ", "Avenue des "  ) and suffixes (e.g. " Road").
+	 * If a well known prefix is found the label is modified. If the prefix ends with a blank,
+	 * that blank is replaced by 0x1e, else 0x1b is added after the prefix.
+	 * If a well known suffix is found the label is modified. If the suffix starts with a blank,
+	 * that blank is replaced by 0x1f, else 0x1c is added before the suffix.
+	 * @param road
+	 */
+	public void filter(MapRoad road) {
+		if (!enabled)
+			return;
+		String country = road.getCountry();
+		if (country == null)
+			return;
+		
+		List<String> prefixesCountry = getSearchStrings(country, MODE_PREFIX);
+		List<String> suffixesCountry = getSearchStrings(country, MODE_SUFFIX);
+		
+		// perform brute force search, seems to be fast enough
+		String[] labels = road.getLabels();
+		for (int i = 0; i < labels.length; i++) {
+			String label = labels[i];
+			if (label == null || label.length() == 0)
+				continue;
+			boolean modified = false;
+			for (String prefix : prefixesCountry) {
+				if (label.charAt(0) < 7)
+					break; // label starts with shield code
+				if (label.length() < prefix.length())
+					continue;
+				if (prefix.equalsIgnoreCase(label.substring(0, prefix.length()))) {
+					if (prefix.endsWith(" ")) {
+						label = prefix.substring(0, prefix.length() - 1) + (char) 0x1e
+								+ label.substring(prefix.length());
+					} else {
+						label = prefix + (char) 0x1b + label.substring(prefix.length());
+					}
+					modified = true;
+					break;
+				}
+			}
+			for (String suffix : suffixesCountry) {
+				int len = label.length();
+				if (len < suffix.length())
+					continue;
+				int pos = len - suffix.length();
+				if (suffix.equalsIgnoreCase(label.substring(pos, len))) {
+					if (suffix.startsWith(" "))
+						label = label.substring(0, pos) + (char) 0x1f + suffix.substring(1);
+					else 
+						label = label.substring(0, pos) + (char) 0x1c + suffix;
+					modified = true;
+					break;
+				}
+			}
+			if (modified) {
+				labels[i] = label;
+				log.debug("modified",label,country,road.getRoadDef());
+			}
+		}
+	}
+	
+	/**
+	 * Build list of prefixes or suffixes for a given country.
+	 * @param country String with 3 letter ISO code
+	 * @param mode : signals prefix or suffix
+	 * @return List with prefixes or suffixes
+	 */
+	private List<String> getSearchStrings(String country, int mode) {
+		Map<String, List<String>>  cache = (mode == MODE_PREFIX) ? countryPrefixMap : countrySuffixMap;
+		List<String> res = cache.get(country); 
+		if (res == null) {
+			// compile the list 
+			List<String> languages = countryLanguageMap.get(country);
+			if (languages == null)
+				res = Collections.emptyList();
+			else  {
+				List<List<String>> all = new ArrayList<>();
+				for (String lang : languages) {
+					List<String> prefixes = mode == MODE_PREFIX ? langPrefixMap.get(lang) : langSuffixMap.get(lang);
+					if(prefixes != null)
+						all.add(prefixes);
+				}
+				if(all.isEmpty())
+					res = Collections.emptyList();
+				else if (all.size() == 1) {
+					res = all.get(0);
+				}
+				else {
+					Set<String> allPrefixesSet = new HashSet<>();
+					for (List<String> prefOneLang : all)
+						allPrefixesSet.addAll(prefOneLang);
+					res = new ArrayList<>(allPrefixesSet);
+					sortByLength(res);
+					
+				}
+			}
+			// cache the result
+			cache.put(country, res);
+		}
+		return res;
+	}
+
+	/**
+	 * Sort by string length so that longest string comes first.
+	 * @param strings
+	 */
+	private void sortByLength(List<String> strings) {
+		strings.sort(new Comparator<String>() {
+			@Override
+			public int compare(String o1, String o2) {
+				return Integer.compare(o2.length(), o1.length());
+			}
+		});
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
index 5d7a254..b430fc1 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
@@ -14,6 +14,7 @@
 package uk.me.parabola.mkgmap.osmstyle;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -57,24 +58,22 @@ public class RoadMerger {
 	/** 
 	 * For these tags two ways need to have an equal value so that their roads can be merged.
 	 */
-	private final static Set<String> mergeTagsEqualValue = new HashSet<String>() {
-		{
-			add("mkgmap:label:1");
-			add("mkgmap:label:2");
-			add("mkgmap:label:3");
-			add("mkgmap:label:4");
-			add("mkgmap:postal_code");
-			add("mkgmap:city");
-			add("mkgmap:region");
-			add("mkgmap:country");
-			add("mkgmap:is_in");
-			add("mkgmap:skipSizeFilter");
-			add("mkgmap:synthesised");
-			add("mkgmap:highest-resolution-only");
-			add("mkgmap:flare-check");
-			add("mkgmap:numbers");
-		}
-	};
+	private final static Set<String> mergeTagsEqualValue = new HashSet<>(Arrays.asList( 
+			"mkgmap:label:1",
+			"mkgmap:label:2",
+			"mkgmap:label:3",
+			"mkgmap:label:4",
+			"mkgmap:postal_code",
+			"mkgmap:city",
+			"mkgmap:region",
+			"mkgmap:country",
+			"mkgmap:is_in",
+			"mkgmap:skipSizeFilter",
+			"mkgmap:synthesised",
+			"mkgmap:highest-resolution-only",
+			"mkgmap:flare-check",
+			"mkgmap:numbers"
+			));
 
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
index 19ac7e1..ab635b6 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
@@ -19,24 +19,36 @@ package uk.me.parabola.mkgmap.osmstyle;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.Reader;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
+import java.util.Formatter;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.LevelInfo;
+import uk.me.parabola.mkgmap.osmstyle.actions.Action;
 import uk.me.parabola.mkgmap.osmstyle.actions.ActionList;
 import uk.me.parabola.mkgmap.osmstyle.actions.ActionReader;
+import uk.me.parabola.mkgmap.osmstyle.actions.AddTagAction;
+import uk.me.parabola.mkgmap.osmstyle.actions.DeleteAction;
 import uk.me.parabola.mkgmap.osmstyle.eval.AndOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.BinaryOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.EqualsOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.ExistsOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.ExpressionReader;
 import uk.me.parabola.mkgmap.osmstyle.eval.LinkedOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.NodeType;
+import uk.me.parabola.mkgmap.osmstyle.eval.NotOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.osmstyle.eval.OrOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.ValueOp;
+import uk.me.parabola.mkgmap.osmstyle.function.GetTagFunction;
 import uk.me.parabola.mkgmap.osmstyle.function.StyleFunction;
 import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
 import uk.me.parabola.mkgmap.reader.osm.GType;
@@ -65,7 +77,10 @@ public class RuleFileReader {
 	private final boolean performChecks;
 	private final Map<Integer, List<Integer>> overlays;
 
-	private boolean inFinalizeSection = false;
+	private Deque<Op[]> ifStack = new LinkedList<>();
+	public static final String IF_PREFIX = "mkgmap:if:"; 
+
+	private boolean inFinalizeSection;
 	
 	public RuleFileReader(FeatureKind kind, LevelInfo[] levels, RuleSet rules, boolean performChecks, 
 			Map<Integer, List<Integer>> overlays) {
@@ -106,25 +121,42 @@ public class RuleFileReader {
 		// Read all the rules in the file.
 		scanner.skipSpace();
 		while (!scanner.isEndOfFile()) {
-			if (checkCommand(loader, scanner))
+			if (checkCommand(loader, scanner, expressionReader))
 				continue;
 
 			if (scanner.isEndOfFile())
 				break;
 
-			Op expr = expressionReader.readConditions();
-
+			Op expr = expressionReader.readConditions(ifStack);
 			ActionList actionList = actionReader.readActions();
-
+			checkIfStack(actionList);
+			
+			List<GType> types = new ArrayList<>();
+			while (scanner.checkToken("[")) {
+				GType type = typeReader.readType(scanner, performChecks, overlays);
+				types.add(type);
+				scanner.skipSpace();
+			};
+			
 			// If there is an action list, then we don't need a type
-			GType type = null;
-			if (scanner.checkToken("["))
-				type = typeReader.readType(scanner, performChecks, overlays);
-			else if (actionList == null)
+			if (types.isEmpty() && (actionList.isEmpty()))
 				throw new SyntaxException(scanner, "No type definition given");
 
-			saveRule(scanner, expr, actionList, type);
-			scanner.skipSpace();
+			if (types.isEmpty())
+				saveRule(scanner, expr, actionList, null);
+			
+			if (types.size() >= 2 && actionList.isModifyingTags()) {
+				throw new SyntaxException(scanner, "Combination of multiple type definitions with tag modifying action is not yet supported.");
+			}
+			for (int i = 0; i < types.size(); i++) {
+				GType type = types.get(i);
+				if (i + 1 < types.size()) {
+					type.setContinueSearch(true);
+				}
+				// No need to create a deep copy of expr
+				saveRule(scanner, expr, actionList, type);
+				actionList = new ActionList(Collections.emptyList(), Collections.emptySet());
+			}
 		}
 
 		rules.addUsedTags(expressionReader.getUsedTags());
@@ -150,84 +182,219 @@ public class RuleFileReader {
 	 * @param currentLoader The current style loader. Any included files are loaded from here, if no other
 	 * style is specified.
 	 * @param scanner The current token scanner.
+	 * @param expressionReader The current expression reader
 	 */
-	private boolean checkCommand(StyleFileLoader currentLoader, TokenScanner scanner) {
+	private boolean checkCommand(StyleFileLoader currentLoader, TokenScanner scanner, ExpressionReader expressionReader) {
 		scanner.skipSpace();
 		if (scanner.isEndOfFile())
 			return false;
 
 		if (scanner.checkToken("include")) {
-			// Consume the 'include' token and skip spaces
-			Token token = scanner.nextToken();
-			scanner.skipSpace();
+			if (readInclude(currentLoader, scanner)) return true;
 
-			// If include is being used as a keyword then it is followed by a word or a quoted word.
-			Token next = scanner.peekToken();
-			if (next.getType() == TokType.TEXT
-					|| (next.getType() == TokType.SYMBOL && (next.isValue("'") || next.isValue("\""))))
-			{
-				String filename = scanner.nextWord();
+		} else if (scanner.checkToken("if")) {
+			if (readIf(scanner, expressionReader)) return true;
 
-				StyleFileLoader loader = currentLoader;
-				scanner.skipSpace();
+		} else if (scanner.checkToken("else")) {
+			if (readElse(scanner)) return true;
 
-				// The include can be followed by an optional 'from' clause. The file is read from the given
-				// style-name in that case.
-				if (scanner.checkToken("from")) {
-					scanner.nextToken();
-					String styleName = scanner.nextWord();
-					if (styleName.equals(";"))
-						throw new SyntaxException(scanner, "No style name after 'from'");
-
-					try {
-						loader = StyleFileLoader.createStyleLoader(null, styleName);
-					} catch (FileNotFoundException e) {
-						throw new SyntaxException(scanner, "Cannot find style: " + styleName);
+		} else if (scanner.checkToken("end")) {
+			if (readEnd(scanner)) return true;
+
+		} else if (scanner.checkToken("<")) {
+			// check if it is the start label of the <finalize> section
+			if (readFinalize(scanner)) return true;
+		}
+		scanner.skipSpace();
+		return false;
+	}
+
+	private boolean readIf(TokenScanner scanner, ExpressionReader expressionReader) {
+		// Take the 'if' token
+		Token tok = scanner.nextToken();
+		scanner.skipSpace();
+
+		// If 'if'' is being used as a keyword then it is followed by a '('.
+		Token next = scanner.peekToken();
+		if (next.getType() == TokType.SYMBOL && next.isValue("(")) {
+			Op origExpr = expressionReader.readConditions();
+			scanner.validateNext("then");
+			
+			// add rule expr { set <ifVar> = true } 
+			String ifVar = getNextIfVar();
+			ArrayList<Action> actions = new ArrayList<>(1);
+			actions.add(new AddTagAction(ifVar,"true", true));
+			ActionList actionList = new ActionList(actions, Collections.singleton(ifVar+"=true"));
+			saveRule(scanner, origExpr, actionList, null);
+			// create expression (<ifVar> = true) 
+			EqualsOp safeExpr = new EqualsOp();
+			safeExpr.setFirst(new GetTagFunction(ifVar));
+			safeExpr.setSecond(new ValueOp("true"));
+			Op[] ifExpressions = {origExpr, safeExpr};  
+			ifStack.addLast(ifExpressions);
+			
+			return true;
+		} else {
+			// Wrong syntax for if statement, so push back token to allow a possible expression to be read
+			scanner.pushToken(tok);
+		}
+		return false;
+	}
+
+	private boolean readElse(TokenScanner scanner) {
+		Token tok = scanner.nextToken();
+		scanner.skipSpace();
+
+		Token next = scanner.peekToken();
+		if (next.getType() == TokType.SYMBOL && !next.isValue("(") && !next.isValue("!")) {
+			scanner.pushToken(tok);
+			return false;
+		}
+
+		Op[] ifExpressions = ifStack.removeLast();
+		for (int i = 0; i < ifExpressions.length; i++) {
+			Op op = ifExpressions[i];
+			NotOp not = new NotOp();
+			not.setFirst(op);
+			ifExpressions[i] = not;
+		}
+		ifStack.addLast(ifExpressions);
+
+		return true;
+	}
+
+	private boolean readEnd(TokenScanner scanner) {
+		Token tok = scanner.nextToken();
+		scanner.skipSpace();
+		if (ifStack.isEmpty()) {
+			scanner.pushToken(tok);
+			return false;
+		}
+
+		ifStack.removeLast();
+		return true;
+	}
+
+	/**
+	 * Check if one of the actions in the actionList would change the result of a previously read if expression.
+	 * If so, use the alternative expression with the generated tag.
+	 * @param actionList
+	 */
+	private void checkIfStack(ActionList actionList) {
+		if (actionList.isEmpty())
+			return;
+		for (Op[] ops : ifStack) {
+			if (ops[0] != ops[1]) {
+				// check if this action can change the result of the initial if expression 
+				if (possiblyChanged(ops[0], actionList)) {
+					// the result may be changed, use the generated tag for all further rules in this if / else block
+					ops[0] = ops[1];
+				} 
+			}
+		}
+	}
+
+	/**
+	 * Check if the expression depends on tags modified by an action in the action list. 
+	 * @param expr the expression to check
+	 * @param actionList the ActionList 
+	 * @return true if the value of the expression depends on one or more of the changeable tags
+	 */
+	private boolean possiblyChanged(Op expr, ActionList actionList) {
+		Set<String> evaluated = expr.getEvaluatedTagKeys();
+		if (evaluated.isEmpty())
+			return false;
+		for (String tagKey : evaluated) {
+			for (String s : actionList.getChangeableTags()) {
+				int pos = s.indexOf("=");
+				String key = pos > 0 ? s.substring(0, pos) : s;
+				if (tagKey.equals(key)) {
+					return true;
+				}
+			}
+			for (Action a : actionList.getList()) {
+				if (a instanceof DeleteAction) {
+					if (a.toString().contains(tagKey)) {
+						return true;
 					}
 				}
+			}
+		}
+		return false;
+	}
 
-				scanner.validateNext(";");
+	private boolean readInclude(StyleFileLoader currentLoader, TokenScanner scanner) {
+		// Consume the 'include' token and skip spaces
+		Token token = scanner.nextToken();
+		scanner.skipSpace();
+
+		// If include is being used as a keyword then it is followed by a word or a quoted word.
+		Token next = scanner.peekToken();
+		if (next.getType() == TokType.TEXT
+				|| (next.getType() == TokType.SYMBOL && (next.isValue("'") || next.isValue("\""))))
+		{
+			String filename = scanner.nextWord();
+
+			StyleFileLoader loader = currentLoader;
+			scanner.skipSpace();
+
+			// The include can be followed by an optional 'from' clause. The file is read from the given
+			// style-name in that case.
+			if (scanner.checkToken("from")) {
+				scanner.nextToken();
+				String styleName = scanner.nextWord();
+				if (Objects.equals(styleName, ";"))
+					throw new SyntaxException(scanner, "No style name after 'from'");
 
 				try {
-					loadFile(loader, filename);
-					return true;
+					loader = StyleFileLoader.createStyleLoader(null, styleName);
 				} catch (FileNotFoundException e) {
-					throw new SyntaxException(scanner, "Cannot open included file: " + filename);
-				} finally {
-					if (loader != currentLoader)
-						Utils.closeFile(loader);
+					throw new SyntaxException(scanner, "Cannot find style: " + styleName);
 				}
-			} else {
-				// Wrong syntax for include statement, so push back token to allow a possible expression to be read
-				scanner.pushToken(token);
 			}
-		} 
-		// check if it is the start label of the <finalize> section
-		else if (scanner.checkToken("<")) {
-			Token token = scanner.nextToken();
-			if (scanner.checkToken("finalize")) {
-				Token finalizeToken = scanner.nextToken();
-				if (scanner.checkToken(">")) {
-					if (inFinalizeSection) {
-						// there are two finalize sections which is not allowed
-						throw new SyntaxException(scanner, "There is only one finalize section allowed");
-					} else {
-						// consume the > token
-						scanner.nextToken();
-						// mark start of the finalize block
-						inFinalizeSection = true;
-						finalizeRules = new RuleSet();
-						return true;
-					}
+
+			if (scanner.checkToken(";"))
+				scanner.nextToken();
+
+			try {
+				loadFile(loader, filename);
+				return true;
+			} catch (FileNotFoundException e) {
+				throw new SyntaxException(scanner, "Cannot open included file: " + filename);
+			} finally {
+				if (loader != currentLoader)
+					Utils.closeFile(loader);
+			}
+		} else {
+			// Wrong syntax for include statement, so push back token to allow a possible expression to be read
+			scanner.pushToken(token);
+		}
+		return false;
+	}
+
+	private boolean readFinalize(TokenScanner scanner) {
+		Token token = scanner.nextToken();
+		if (scanner.checkToken("finalize")) {
+			Token finalizeToken = scanner.nextToken();
+			if (scanner.checkToken(">")) {
+				if (inFinalizeSection) {
+					// there are two finalize sections which is not allowed
+					throw new SyntaxException(scanner, "There is only one finalize section allowed");
 				} else {
-					scanner.pushToken(finalizeToken);
-					scanner.pushToken(token);
+					// consume the > token
+					scanner.nextToken();
+					// mark start of the finalize block
+					inFinalizeSection = true;
+					finalizeRules = new RuleSet();
+					return true;
 				}
 			} else {
+				scanner.pushToken(finalizeToken);
 				scanner.pushToken(token);
 			}
+		} else {
+			scanner.pushToken(token);
 		}
-		scanner.skipSpace();
 		return false;
 	}
 
@@ -564,19 +731,30 @@ public class RuleFileReader {
 			rules.add(keystring, rule, actions.getChangeableTags());
 	}
 
+	
+	private int ifCounter;
+	/**
+	 * 
+	 * @return a new tag key unique 
+	 */
+	public String getNextIfVar (){
+		return IF_PREFIX  + ++ifCounter;
+	}
+	
 	public static void main(String[] args) throws FileNotFoundException {
 		if (args.length > 0) {
 			RuleSet rs = new RuleSet();
 			RuleFileReader rr = new RuleFileReader(FeatureKind.POLYLINE,
 					LevelInfo.createFromString("0:24 1:20 2:18 3:16 4:14"), rs, false,
-					Collections.<Integer, List <Integer>>emptyMap());
+					Collections.emptyMap());
 
 			StyleFileLoader loader = new DirectoryFileLoader(
 					new File(args[0]).getAbsoluteFile().getParentFile());
 			String fname = new File(args[0]).getName();
 			rr.load(loader, fname);
 
-			System.out.println("Result: " + rs);
+
+			StylePrinter.dumpRuleSet(new Formatter(System.out), "rules", rs);
 		} else {
 			System.err.println("Usage: RuleFileReader <file>");
 		}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
index ad8086d..77b588f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleIndex.java
@@ -18,10 +18,13 @@ import java.util.BitSet;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
+import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.reader.osm.Rule;
 import uk.me.parabola.mkgmap.reader.osm.TagDict;
 
@@ -68,21 +71,21 @@ public class RuleIndex {
 	private boolean inited;
 
 	private class TagHelper{
-		// This is an index of all rules that start with EXISTS (A=*)
-		final BitSet exists;
+		// This is an index of all rules that start with EXISTS (A=*) or (A=B)
+		final BitSet checked;
 		// This is an index of all rules that start with EQUALS (A=B) 
 		Map<String, BitSet> tagVals;
 		
-		public TagHelper(BitSet exits){
-			this.exists = exits;
+		public TagHelper(BitSet checked){
+			this.checked = checked;
 		}
 
 		public void addTag(String val, BitSet value) {
 			if (tagVals == null)
 				tagVals = new HashMap<>();
-			if (exists != null){	
+			if (checked != null){	
 				BitSet merged = new BitSet();
-				merged.or(exists);
+				merged.or(checked);
 				merged.or(value);
 				tagVals.put(val, merged);
 			} else
@@ -96,8 +99,8 @@ public class RuleIndex {
 					return (BitSet) set.clone();
 				}
 			} 
-			if (exists != null)
-				return (BitSet) exists.clone();
+			if (checked != null)
+				return (BitSet) checked.clone();
 			return new BitSet();
 		}
 	}
@@ -138,7 +141,8 @@ public class RuleIndex {
 	public BitSet getRulesForTag(short tagKey, String tagVal) {
 		TagHelper th;
 		if (tagKeyArray != null){
-			if (tagKey >= 0 & tagKey < tagKeyArray.length){
+			assert tagKey > 0;
+			if (tagKey < tagKeyArray.length){
 				th = tagKeyArray[tagKey];
 			} else 
 				th = null;
@@ -158,8 +162,6 @@ public class RuleIndex {
 	public void prepare() {
 		if (inited)
 			return;
-		// This is an index of all rules that start with EXISTS (A=*)
-		Map<String, BitSet> existKeys = new HashMap<String, BitSet>();
 		// This is an index of all rules that start with EQUALS (A=B)
 		Map<String, BitSet> tagVals = new HashMap<String, BitSet>();
 		
@@ -169,6 +171,9 @@ public class RuleIndex {
 		// Maps a rule number to the tags that might be changed by that rule
 		Map<Integer, List<String>> changeTags = new HashMap<Integer, List<String>>();
 		
+		// remove unnecessary rules
+		filterRules();
+		
 		for (int i = 0; i < ruleDetails.size(); i++){
 			int ruleNumber = i;
 			RuleDetails rd = ruleDetails.get(i);
@@ -177,7 +182,6 @@ public class RuleIndex {
 
 			if (keystring.endsWith("=*")) {
 				String key = keystring.substring(0, keystring.length() - 2);
-				addNumberToMap(existKeys, key, ruleNumber);
 				addNumberToMap(tagnames, key, ruleNumber);
 			} else {
 				addNumberToMap(tagVals, keystring, ruleNumber);
@@ -208,17 +212,8 @@ public class RuleIndex {
 					// rule using the tag, no matter what the value.
 					int ind = s.indexOf('=');
 					if (ind >= 0) {
-						set = tagVals.get(s);
-
-						// Exists rules can also be triggered, so add them too.
 						String key = s.substring(0, ind);
-						BitSet set1 = existKeys.get(key);
-
-						if (set == null)
-							set = set1;
-						else if (set1 != null)
-							set.or(set1);
-
+						set = tagnames.get(key);
 					} else {
 						set = tagnames.get(s);
 					}
@@ -240,7 +235,7 @@ public class RuleIndex {
 
 						// Find every rule number set that contains the rule number that we
 						// are examining and add all the newly found rules to each such set.
-						for (Map<String, BitSet> m : Arrays.asList(existKeys, tagVals, tagnames)) {
+						for (Map<String, BitSet> m : Arrays.asList(tagVals, tagnames)) {
 							Collection<BitSet> bitSets = m.values();
 							for (BitSet bi : bitSets) {
 								if (bi.get(ruleNumber)) {
@@ -259,11 +254,8 @@ public class RuleIndex {
 		}
 
 		// compress the index: create one hash map with one entry for each key
-		int highestKey = 0;
-		for (Map.Entry<String, BitSet> entry  : existKeys.entrySet()){
+		for (Map.Entry<String, BitSet> entry  : tagnames.entrySet()){
 			Short skey = TagDict.getInstance().xlate(entry.getKey());
-			if (skey > highestKey)
-				highestKey = skey;
 			tagKeyMap.put(skey, new TagHelper(entry.getValue()));
 		}
 		for (Map.Entry<String, BitSet> entry  : tagVals.entrySet()){
@@ -272,8 +264,6 @@ public class RuleIndex {
 			if (ind >= 0) {
 				short key = TagDict.getInstance().xlate(keyString.substring(0, ind));
 				String val = keyString.substring(ind+1);
-				if (key > highestKey)
-					highestKey = key;
 				TagHelper th = tagKeyMap.get(key);
 				if (th == null){
 					th = new TagHelper(null);
@@ -282,8 +272,10 @@ public class RuleIndex {
 				th.addTag(val, entry.getValue());
 			}
 		}
-		if (highestKey > 0 && highestKey < 1024){
-			tagKeyArray = new TagHelper[highestKey+1];
+		Optional<Short> minKey = tagKeyMap.keySet().stream().min(Short::compare);
+		if (minKey.isPresent() && minKey.get() > 0){
+			Optional<Short> maxKey = tagKeyMap.keySet().stream().max(Short::compare);
+			tagKeyArray = new TagHelper[maxKey.get() + 1];
 			for (Map.Entry<Short, TagHelper> entry  : tagKeyMap.entrySet()){
 				tagKeyArray[entry.getKey()] = entry.getValue();
 			}
@@ -293,6 +285,61 @@ public class RuleIndex {
 		inited = true;
 	}
 
+	/**
+	 * Remove dead rules.
+	 * @param styleOptionTags
+	 */
+	private void filterRules() {
+		List<RuleDetails> filteredRules = new ArrayList<>(ruleDetails);
+		Set<String> usedIfVars = new HashSet<>();
+		for (RuleDetails rd : filteredRules) {
+			findIfVarUsage(rd.getRule(), usedIfVars);
+		}
+		removeUnused(filteredRules, usedIfVars);
+		ruleDetails.clear();
+		ruleDetails.addAll(filteredRules);
+	}
+
+	private void removeUnused(List<RuleDetails> filteredRules, Set<String> usedIfVars) {
+		if (usedIfVars.isEmpty())
+			return;
+		Iterator<RuleDetails> iter = filteredRules.iterator();
+		while (iter.hasNext()) {
+			RuleDetails rd = iter.next();
+			if (rd.getRule() instanceof ActionRule) {
+				ActionRule ar = (ActionRule) rd.getRule();
+				if (ar.toString().contains("set " + RuleFileReader.IF_PREFIX)) {
+					boolean needed = false;
+					for (String ifVars : usedIfVars) {
+						if (ar.toString().contains("set " + ifVars)) {
+							needed = true;
+						}
+					}
+					if (!needed)
+						iter.remove();
+				}
+			}
+		}
+	}
+
+	private void findIfVarUsage(Rule rule, Set<String> usedIfVars) {
+		if (rule == null)
+			return;
+//		if (rule.getFinalizeRule() != null)
+//			findIfVarUsage(rule.getFinalizeRule(), usedIfVars);
+		Op expr = null;
+		if (rule instanceof ExpressionRule) 
+			expr = ((ExpressionRule) rule).getOp();
+		else if (rule instanceof ActionRule)
+			expr = ((ActionRule) rule).getOp();
+		if (expr == null)
+			return;
+		for (String usedTag : expr.getEvaluatedTagKeys()) {
+			if (usedTag.startsWith(RuleFileReader.IF_PREFIX))
+				usedIfVars.add(usedTag);
+		}
+	}
+
 	private static void addNumberToMap(Map<String, BitSet> map, String key, int ruleNumber) {
 		BitSet set = map.get(key);
 		if (set == null) {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
index 1cc2448..9d0d616 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
@@ -43,7 +43,6 @@ import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.Option;
 import uk.me.parabola.mkgmap.OptionProcessor;
 import uk.me.parabola.mkgmap.Options;
-import uk.me.parabola.mkgmap.build.LocatorUtil;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LineAdder;
 import uk.me.parabola.mkgmap.general.MapLine;
@@ -97,9 +96,6 @@ public class StyleImpl implements Style {
 	// Set if this style is based on another one.
 	private final List<StyleImpl> baseStyles = new ArrayList<StyleImpl>();
 
-	// A list of tag names to be used as the element name
-	private List<String> nameTagList;
-
 	// Options from the option file that are used outside this file.
 	private final Map<String, String> generalOptions = new HashMap<String, String>();
 
@@ -139,7 +135,6 @@ public class StyleImpl implements Style {
 		location = loc;
 		fileLoader = StyleFileLoader.createStyleLoader(loc, name);
 		this.performChecks = performChecks;
-		nameTagList = LocatorUtil.getNameTags(props);
 		
 		// There must be a version file, if not then we don't create the style.
 		checkVersion();
@@ -232,10 +227,6 @@ public class StyleImpl implements Style {
 		if (s != null && s.trim().isEmpty() == false)
 			set.addAll(Arrays.asList(COMMA_OR_SPACE_PATTERN.split(s)));
 
-		// These tags are passed on the command line and so must be added
-		if (nameTagList != null)
-			set.addAll(nameTagList);
-
 		// There are a lot of tags that are used within mkgmap that 
 		InputStream is = this.getClass().getResourceAsStream("/styles/builtin-tag-list");
 		try {
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StylePrinter.java b/src/uk/me/parabola/mkgmap/osmstyle/StylePrinter.java
index 5aff590..223735d 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StylePrinter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StylePrinter.java
@@ -84,7 +84,7 @@ public class StylePrinter {
 			return ": " + str;
 	}
 
-	private void dumpRuleSet(Formatter fmt, String name, RuleSet set) {
+	static void dumpRuleSet(Formatter fmt, String name, RuleSet set) {
 		fmt.format("<<<%s>>>\n", name);
 		for (Rule rule : set) {
 			fmt.format("%s\n", rule.toString());
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 51d9651..f564713 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -16,9 +16,7 @@
  */
 package uk.me.parabola.mkgmap.osmstyle;
 
-import it.unimi.dsi.fastutil.shorts.ShortArrayList;
 import java.util.ArrayList;
-
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
@@ -29,6 +27,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.logging.Level;
 
 import uk.me.parabola.imgfmt.ExitException;
@@ -43,7 +42,6 @@ import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.MapObject;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.build.LocatorConfig;
-import uk.me.parabola.mkgmap.build.LocatorUtil;
 import uk.me.parabola.mkgmap.filters.LineSizeSplitterFilter;
 import uk.me.parabola.mkgmap.general.AreaClipper;
 import uk.me.parabola.mkgmap.general.Clipper;
@@ -69,6 +67,7 @@ import uk.me.parabola.mkgmap.reader.osm.RestrictionRelation;
 import uk.me.parabola.mkgmap.reader.osm.Rule;
 import uk.me.parabola.mkgmap.reader.osm.Style;
 import uk.me.parabola.mkgmap.reader.osm.TagDict;
+import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.util.EnhancedProperties;
@@ -85,12 +84,12 @@ public class StyledConverter implements OsmConverter {
 	private static final Logger log = Logger.getLogger(StyledConverter.class);
 	private static final Logger roadLog = Logger.getLogger(StyledConverter.class.getName()+".roads");
 
-	private final ShortArrayList nameTagList;
+	private final NameFinder nameFinder; 
 
 	private final MapCollector collector;
 
 	private Clipper clipper = Clipper.NULL_CLIPPER;
-	private Area bbox = new Area(-90.0d, -180.0d, 90.0d, 180.0d); // default is planet
+	private Area bbox = Area.PLANET;
 
 	private final List<RestrictionRelation> restrictions = new ArrayList<>();
 	private final MultiHashMap<Long, RestrictionRelation> wayRelMap = new MultiHashMap<>();
@@ -144,28 +143,25 @@ public class StyledConverter implements OsmConverter {
 	private final boolean linkPOIsToWays;
 	private final boolean mergeRoads;
 	private final boolean routable;
+	private final Tags styleOptionTags;
+	private final static String STYLE_OPTION_PREF = "mkgmap:option:";
+	private final PrefixSuffixFilter prefixSuffixFilter;
 	
-
 	private LineAdder lineAdder = new LineAdder() {
 		public void add(MapLine element) {
 			if (element instanceof MapRoad){
+				prefixSuffixFilter.filter((MapRoad) element);
 				collector.addRoad((MapRoad) element);
 			}
 			else
 				collector.addLine(element);
 		}
 	};
-
+	
 	public StyledConverter(Style style, MapCollector collector, EnhancedProperties props) {
 		this.collector = collector;
 
-		List<String> nameTags = LocatorUtil.getNameTags(props);
-		if (nameTags != null){
-			nameTagList = new ShortArrayList();
-			for(String n : nameTags)
-				nameTagList.add(TagDict.getInstance().xlate(n));
-		} else 
-			nameTagList = null;
+		nameFinder = new NameFinder(props);
 		this.style = style;
 		pointMap = new HashMap<>();
 		wayRules = style.getWayRules();
@@ -211,9 +207,51 @@ public class StyledConverter implements OsmConverter {
 		// undocumented option - usually used for debugging only
 		mergeRoads = props.getProperty("no-mergeroads", false) == false;
 		routable = props.containsKey("route");
-		
+		String styleOption= props.getProperty("style-option",null);
+		styleOptionTags = parseStyleOption(styleOption);
+		prefixSuffixFilter = new PrefixSuffixFilter(props);
 	}
 
+	/**
+	 * Handle style option parameter. Create tags which are added to each element
+	 * before style processing starts. Cross-check usage of the options with the style.
+	 * @param styleOption the user option 
+	 * @return Tags instance created from the option.
+	 */
+	private Tags parseStyleOption(String styleOption) {
+		Tags styleTags = new Tags();
+		if (styleOption != null) {
+			// expected: --style-option=car;farms=more;admin5=10
+			String[] tags = styleOption.split(";");
+			for (String t : tags) {
+				String[] pair = t.split("=");
+				String optionKey = pair[0];
+				String tagKey = STYLE_OPTION_PREF + optionKey;
+				if (!style.getUsedTags().contains(tagKey)) {
+					System.err.println("Warning: Option style-options sets tag not used in style: '" 
+							+ optionKey + "' (gives " + tagKey + ")");
+				} else {
+					String val = (pair.length == 1) ? "true" : pair[1];
+					String old = styleTags.put(tagKey, val);
+					if (old != null)
+						log.error("duplicate tag key", optionKey, "in style option", styleOption);
+				}
+			}
+		}
+		// flag options used in style but not specified in --style-option
+		if (style.getUsedTags() != null) {
+			for (String s : style.getUsedTags()) {
+				if (s != null && s.startsWith(STYLE_OPTION_PREF)) {
+					if (styleTags.get(s) == null) {
+						System.err.println("Warning: Option style-options doesn't specify '" 
+								+ s.replaceFirst(STYLE_OPTION_PREF, "") + "' (for " + s + ")");
+					}
+				}
+			}
+		}
+		return styleTags;
+	}	
+
 	/** One type result for ways to avoid recreating one for each way. */ 
 	private final WayTypeResult wayTypeResult = new WayTypeResult();
 	private class WayTypeResult implements TypeResult 
@@ -448,24 +486,20 @@ public class StyledConverter implements OsmConverter {
 	}
 	
 
-	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
 	/**
 	 * Rules to run before converting the element.
 	 */
 	private void preConvertRules(Element el) {
-		if (nameTagList == null)
-			return;
-
-		for (short tagKey : nameTagList) {
-			String val = el.getTag(tagKey);
-			if (val != null) {
-				if (tagKey != nameTagKey) {
-					// add or replace name 
-					el.addTag(nameTagKey, val);
-				}
-				break;
+		// add tags given with --style-option
+		if (styleOptionTags != null && styleOptionTags.size() > 0) {
+			Iterator<Entry<Short, String>> iter = styleOptionTags.entryShortIterator();
+			while (iter.hasNext()) {
+				Entry<Short, String> tag = iter.next();
+				el.addTag(tag.getKey(), tag.getValue());
 			}
 		}
+		
+		nameFinder.setNameWithNameTagList(el);
 	}
 
 	/**
@@ -538,7 +572,7 @@ public class StyledConverter implements OsmConverter {
 	private void removeRestrictionsWithWay(Level logLevel, Way way, String reason){
 		List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
 		for (RestrictionRelation rr : rrList){
-			if (rr.isValidWithoputWay(way.getId()) == false){
+			if (rr.isValidWithoutWay(way.getId()) == false){
 				if (log.isLoggable(logLevel)){
 					log.log(logLevel, "restriction",rr.toBrowseURL()," is ignored because referenced way",way.toBrowseURL(),reason);
 				}
@@ -721,8 +755,7 @@ public class StyledConverter implements OsmConverter {
 			if ("right".equals(driveOn) && numDriveOnRightRoads == 0 && numDriveOnLeftRoads > 0)
 				log.warn("The drive-on-left flag is NOT set used but tile contains only drive-on-left roads");
 		}		
-		if (dol == null)
-			dol = false; // should not happen
+		assert dol != null;
 		return dol;
 	}
 
@@ -1051,9 +1084,9 @@ public class StyledConverter implements OsmConverter {
 		if (dupPOI){
 			if (log.isInfoEnabled()){
 				if (FakeIdGenerator.isFakeId(node.getId()))
-					log.info("ignmoring duplicate POI with type",GType.formatType(type),mp.getName(),"for generated element with id",node.getId(),"at",mp.getLocation().toDegreeString());
+					log.info("ignoring duplicate POI with type",GType.formatType(type),mp.getName(),"for generated element with id",node.getId(),"at",mp.getLocation().toDegreeString());
 				else 
-					log.info("ignmoring duplicate POI with type",GType.formatType(type),mp.getName(),"for element",node.toBrowseURL());
+					log.info("ignoring duplicate POI with type",GType.formatType(type),mp.getName(),"for element",node.toBrowseURL());
 			}
 			return;
 		}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionList.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionList.java
index af4ae2a..1870fa9 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionList.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ActionList.java
@@ -40,4 +40,17 @@ public class ActionList {
 	public Set<String> getChangeableTags() {
 		return changeableTags;
 	}
+
+	public boolean isModifyingTags() {
+		if (!isEmpty()) {
+			if (!changeableTags.isEmpty())
+				return true;
+			for (Action a : list) {
+				if (a instanceof DeleteAction) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
index 9b454bb..c3188d7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoAction.java
@@ -32,7 +32,10 @@ public class EchoAction implements Action {
 
 	public boolean perform(Element el) {
 		String e = value.build(el, el);
-		System.err.println(el.getId() + (FakeIdGenerator.isFakeId(el.getId()) ? " (" + el.getOriginalId() + ")" : "") + ": " + e);
+		String className = el.getClass().getSimpleName();
+		if (className.equals("GeneralRelation"))
+			className = "Relation";
+		System.err.println(className + (FakeIdGenerator.isFakeId(el.getId()) ? " generated from " : " ") + el.getOriginalId() + " " + e);
 		return false;
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
index 7ee0778..cddda6b 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/EchoTagsAction.java
@@ -30,7 +30,10 @@ public class EchoTagsAction implements Action {
 
 	public boolean perform(Element el) {
 		String e = value.build(el, el);
-		System.err.println(el.getId() + (FakeIdGenerator.isFakeId(el.getId()) ? " (" + el.getOriginalId() + ")" : "") + " - " + el.toTagString()+" " + e);
+		String className = el.getClass().getSimpleName();
+		if (className.equals("GeneralRelation"))
+			className = "Relation";
+		System.err.println(className + (FakeIdGenerator.isFakeId(el.getId()) ? " generated from " : " ") + el.getOriginalId() + " " + el.toTagString() + " " + e);
 		return false;
 	}
 	
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
index 597d3a6..df7b4b4 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
@@ -14,6 +14,7 @@
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.scan.SyntaxException;
 
 /**
  * A {@code HeightFilter} transforms values into Garmin-tagged elevations.
@@ -24,9 +25,11 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
  */
 public class HeightFilter extends ConvertFilter {
 
-    public HeightFilter(String s) {
-		super(s);
-    }
+	public HeightFilter(String s) {
+		super(s == null ? "m=>ft" : s);
+		if (s != null && !(s.endsWith("ft") || s.endsWith("feet")))
+			throw new SyntaxException(String.format("height filter reqires ft (feet) as target unit: '%s'", s));
+	}
 
 	public String doFilter(String value, Element el) {
 		String s = super.doFilter(value, el);
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
index e4863d7..b3548f4 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/AbstractOp.java
@@ -16,7 +16,11 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import java.util.HashSet;
+import java.util.Set;
+
 import uk.me.parabola.imgfmt.ExitException;
+import uk.me.parabola.mkgmap.osmstyle.function.GetTagFunction;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.scan.SyntaxException;
 
@@ -142,4 +146,29 @@ public abstract class AbstractOp implements Op {
 		lastCachedId = -1;
 	}
 
+	@Override
+	public Set<String> getEvaluatedTagKeys() {
+		HashSet<String> set = new HashSet<>();
+		collectEvaluatedTags(set);
+		return set;
+	}
+
+	private void collectEvaluatedTags(HashSet<String> set) {
+		if (this instanceof GetTagFunction) {
+			set.add(getKeyValue());
+		} else if (this instanceof BinaryOp) {
+			set.addAll(getFirst().getEvaluatedTagKeys());
+			set.addAll(getSecond().getEvaluatedTagKeys());
+		} else if (this instanceof NumericOp) {
+			set.addAll(getFirst().getEvaluatedTagKeys());
+		}
+		else if (this.isType(NodeType.EXISTS) || this.isType(NodeType.NOT_EXISTS) || this.isType(NodeType.NOT)) {
+			set.addAll(getFirst().getEvaluatedTagKeys());
+		} else if (this instanceof GetTagFunction) {
+			set.add(getKeyValue());
+		} else if (this.getFirst() != null) {
+			System.err.println("Unhandled type of Op");
+		}
+			
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/EqualsOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/EqualsOp.java
index f7c6529..cdfc043 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/EqualsOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/EqualsOp.java
@@ -29,14 +29,6 @@ public class EqualsOp extends AbstractBinaryOp {
 		setType(NodeType.EQUALS);
 	}
 
-	public void setFirst(Op first) {
-		super.setFirst(first);
-	}
-
-	public void setSecond(Op second) {
-		super.setSecond(second);
-	}
-
 	public boolean eval(Element el) {
 		String s = first.value(el);
 		if (s == null)
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/ExpressionReader.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/ExpressionReader.java
index 0c068b5..5020e5a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/ExpressionReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/ExpressionReader.java
@@ -13,6 +13,8 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Stack;
@@ -52,11 +54,22 @@ public class ExpressionReader {
 	 * or by end of file.
 	 */
 	public Op readConditions() {
+		return readConditions(Collections.emptyList());
+	}
+	
+	/**
+	 * Read the conditions.  They are terminated by a '[' or '{' character
+	 * or by end of file.
+	 * @param ifStack expressions of enclosing if / else 
+	 */
+	public Op readConditions(Collection <Op[]> ifStack) {
+		boolean consumedNonBlank = false;
 		while (!scanner.isEndOfFile()) {
 			scanner.skipSpace();
-			if (scanner.checkToken("[") || scanner.checkToken("{"))
+			if (scanner.checkToken("[") || scanner.checkToken("{") || scanner.checkToken("then"))
 				break;
 
+			consumedNonBlank = true;
 			WordInfo wordInfo = scanner.nextWordWithInfo();
 			if (isOperation(wordInfo)) {
 				saveOp(wordInfo.getText());
@@ -84,9 +97,18 @@ public class ExpressionReader {
 		while (!opStack.isEmpty())
 			runOp(scanner);
 
+		if (consumedNonBlank && !ifStack.isEmpty() && stack.size() <= 1) {
+			// add expressions from enclosing if /else statements
+			Op op = null;
+			if (!stack.isEmpty())
+				op = stack.pop();
+			stack.push(appendIfExpr(op, ifStack));
+		}
+			
 		// The stack should contain one entry which is the complete tree
-		if (stack.size() != 1)
-			throw new SyntaxException(scanner, "Stack size is "+stack.size());
+		if (stack.size() != 1) {
+			throw new SyntaxException(scanner, "Stack size is "+stack.size()+" (missing or incomplete expression)");
+		}
 
 		assert stack.size() == 1;
 		Op op = stack.pop();
@@ -96,6 +118,27 @@ public class ExpressionReader {
 	}
 
 	/**
+	 * Append previously read if/else expressions.
+	 * @param expr
+	 * @param ifStack
+	 * @return
+	 */
+	private Op appendIfExpr(Op expr, Collection<Op[]> ifStack) {
+		Op result = expr;
+		for (Op[] ops : ifStack) {
+			if (result != null) {
+				AndOp and = new AndOp();
+				and.setFirst(result);
+				and.setSecond(ops[0]);
+				result = and;
+			} else 
+				result = ops[0];
+			
+		}
+		return result;
+	}
+
+	/**
 	 * Is this a token representing an operation?
 	 * @param token The string to test.
 	 * @return True if this looks like an operator.
@@ -175,10 +218,6 @@ public class ExpressionReader {
 			if (arg1.isType(VALUE) /*&& arg2.isType(VALUE)*/)
 				arg1 = new GetTagFunction(arg1.getKeyValue());
 
-			BinaryOp binaryOp = (BinaryOp) op;
-			binaryOp.setFirst(arg1);
-			binaryOp.setSecond(arg2);
-
 			// Deal with the case where you have: a & b=2.  The 'a' is a syntax error in this case.
 			if (op.isType(OR) || op.isType(AND)) {
 				if (arg1.isType(VALUE) || arg1.isType(FUNCTION))
@@ -199,6 +238,10 @@ public class ExpressionReader {
 
 			}
 
+			BinaryOp binaryOp = (BinaryOp) op;
+			binaryOp.setFirst(arg1);
+			binaryOp.setSecond(arg2);
+
 			// The combination foo=* is converted to exists(foo).
 			if (op.isType(EQUALS) && arg2.isType(VALUE) && ((ValueOp) arg2).isValue("*")) {
 				log.debug("convert to EXISTS");
@@ -220,8 +263,10 @@ public class ExpressionReader {
 		if (first == null)
 			throw new SyntaxException(scanner, "Invalid expression");
 
-		if (first.isType(FUNCTION))
+		if (first.isType(FUNCTION) && first.getKeyValue() != null) {
 			usedTags.add(first.getKeyValue());
+		}
+		
 
 		stack.push(op);
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
index 760d15f..05c987a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/LinkedOp.java
@@ -13,6 +13,8 @@
 
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import java.util.Set;
+
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
@@ -155,4 +157,9 @@ public class LinkedOp implements Op {
 	public boolean isFirstPart(){
 		return first;
 	}
+
+	@Override
+	public Set<String> getEvaluatedTagKeys() {
+		return wrapped.getEvaluatedTagKeys();
+	}
 }
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
index ffb165d..53b93b3 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/Op.java
@@ -12,6 +12,8 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.eval;
 
+import java.util.Set;
+
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
@@ -94,4 +96,9 @@ public interface Op {
 	 */
 	public int priority();
 
+	/**
+	 * @return a set with the tag keys which are evaluated, maybe empty 
+	 */
+	public Set<String> getEvaluatedTagKeys();
+
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java
index 3fafa0d..fd03db7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java
@@ -13,8 +13,6 @@
 
 package uk.me.parabola.mkgmap.osmstyle.housenumber;
 
-import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -27,6 +25,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
 
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.net.NumberStyle;
@@ -727,9 +726,6 @@ public class ExtNumbers {
 			double len1 = segmentLength * minFraction0To1; // dist to first 
 			double len2 = segmentLength * maxFraction0To1; 
 			double len3 = (1-maxFraction0To1) * segmentLength;
-			double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude()+1,c1.getLongitude()));
-			double maxDistBefore = expectedError;
-			double maxDistAfter = expectedError;
 			if (reason == SR_FIX_ERROR && worstHouse != null){
 				wantedFraction = worstHouse.getSegmentFrac();
 				if (wantedFraction < minFraction0To1 || wantedFraction > maxFraction0To1){
@@ -738,6 +734,8 @@ public class ExtNumbers {
 			}
 			boolean allowSplitBetween = true;
 			boolean forceEmpty = false;
+			List<Double> wantedFractions = new ArrayList<>();
+			
 			if (reason == SR_OPT_LEN){
 				if (log.isDebugEnabled()){
 					if (maxFraction0To1 != minFraction0To1){
@@ -748,84 +746,98 @@ public class ExtNumbers {
 				if (len2 - len1 < 10 && getHouses(Numbers.LEFT).size() <= 1 && getHouses(Numbers.RIGHT).size() <= 1){
 					// one house or two opposite houses  
 					// we try to split so that the house(s) are near the middle of one part
-					wantedFraction = wantedFraction * 2 - (wantedFraction > 0.5 ? 1 : 0);
+					wantedFraction = midFraction * 2 - (midFraction > 0.5 ? 1 : 0);
 					allowSplitBetween = false;
 				} else {
 					if (len1 > MAX_LOCATE_ERROR / 2){
 						// create empty segment at start
-						wantedFraction = minFraction0To1 * 0.999;  
+						wantedFractions.add(minFraction0To1 * 0.999);
 						forceEmpty = true;
 					} 
-					if (len3 > MAX_LOCATE_ERROR / 2 && len3 > len1){
+					if (len3 > MAX_LOCATE_ERROR / 2){
 						// create empty segment at end
-						wantedFraction = maxFraction0To1 * 1.001;
+						if (!wantedFractions.isEmpty()  && len3 > len1)
+							wantedFractions.add(0, maxFraction0To1 * 1.001);
+						else 
+							wantedFractions.add(maxFraction0To1 * 1.001);
 						forceEmpty = true;
 					}
 				}
 			}
-			double partLen = wantedFraction * segmentLength ;
-			double shorterLen = Math.min(partLen , segmentLength - partLen);
-			if (shorterLen < 10){
-				if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
-					return dupNode(midFraction, SR_FIX_ERROR);
-				double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
-				return dupNode(splitFrac, SR_OPT_LEN);
-			}
+			if (wantedFractions.isEmpty())
+				wantedFractions.add(wantedFraction);
+			
 			double usedFraction = 0;
 			double bestDist = Double.MAX_VALUE;
-			if (wantedFraction < minFraction0To1){
-				maxDistAfter = 0;
-			}
-			if (wantedFraction > maxFraction0To1){
-				maxDistBefore = 0;
-			}
-			for (;;){
-				Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
-				Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
-				boolean foundGood = false;
-				for (Entry<Double, List<Coord>> entry : candidates.entrySet()){
-					if (foundGood)
-						break;
-					bestDist = entry.getKey();
-					for (Coord candidate: entry.getValue()){
-						toAdd = candidate;
-						usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
-						if (usedFraction <= 0 || usedFraction >= 1)
-							toAdd = null;
-						else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){
-							toAdd = null;
-						} else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){
-							toAdd = null;
-						} else {
-							if (bestDist > 0.2){
-								double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
-								if (Math.abs(angle) > 3){
-									toAdd = null;
-									continue;
+			for (int w = 0; w < wantedFractions.size(); w++) {
+				wantedFraction = wantedFractions.get(w);
+				double partLen = wantedFraction * segmentLength ;
+				double shorterLen = Math.min(partLen , segmentLength - partLen);
+				if (shorterLen < 10){
+					if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
+						return dupNode(midFraction, SR_FIX_ERROR);
+					double splitFrac = len1 < len3 ? minFraction0To1 : maxFraction0To1;
+					return dupNode(splitFrac, SR_OPT_LEN);
+				}
+				double expectedError = c1.getDisplayedCoord().distance(new Coord(c1.getLatitude()+1,c1.getLongitude()));
+				double maxDistBefore = expectedError;
+				double maxDistAfter = expectedError;
+
+				if (wantedFraction < minFraction0To1){
+					maxDistAfter = 0;
+				}
+				if (wantedFraction > maxFraction0To1){
+					maxDistBefore = 0;
+				}
+				for (;;){
+					Coord wanted = c1.makeBetweenPoint(c2, wantedFraction);
+					Map<Double, List<Coord>> candidates = rasterLineNearPoint2(c1, c2, wanted, maxDistBefore, maxDistAfter);
+					boolean foundGood = false;
+					for (Entry<Double, List<Coord>> entry : candidates.entrySet()){
+						if (foundGood)
+							break;
+						bestDist = entry.getKey();
+						for (Coord candidate: entry.getValue()){
+							toAdd = candidate;
+							usedFraction = HousenumberGenerator.getFrac(c1, c2, toAdd);
+							if (usedFraction <= 0 || usedFraction >= 1)
+								toAdd = null;
+							else if (usedFraction > minFraction0To1 && wantedFraction < minFraction0To1 || usedFraction < maxFraction0To1 && wantedFraction > maxFraction0To1){
+								toAdd = null;
+							} else if (allowSplitBetween == false && usedFraction > minFraction0To1 && usedFraction < maxFraction0To1){
+								toAdd = null;
+							} else {
+								if (bestDist > 0.2){
+									double angle = Utils.getDisplayedAngle(c1, toAdd, c2);
+									if (Math.abs(angle) > 3){
+										toAdd = null;
+										continue;
+									}
 								}
+								foundGood = true;
+								break;
 							}
-							foundGood = true;
-							break;
 						}
 					}
+					if (foundGood){
+						break;
+					}
+					toAdd = null;
+					boolean tryAgain = false;
+					if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
+						maxDistBefore *= 2;
+						tryAgain = true;
+					}
+					if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
+						maxDistAfter *= 2;
+						tryAgain = true;
+					}
+					if (!tryAgain)
+						break;
 				}
-				if (foundGood){
-					break;
-				}
-				toAdd = null;
-				boolean tryAgain = false;
-				if (maxDistBefore > 0 && maxDistBefore < segmentLength * wantedFraction) {
-					maxDistBefore *= 2;
-					tryAgain = true;
-				}
-				if (maxDistAfter > 0 && maxDistAfter < segmentLength * (1 - wantedFraction)) {
-					maxDistAfter *= 2;
-					tryAgain = true;
-				}
-				if (!tryAgain)
+				if (toAdd != null)
 					break;
-			} 			
-			
+			}
 			boolean addOK = true;
 			if (toAdd == null)
 				addOK = false;
@@ -838,19 +850,17 @@ public class ExtNumbers {
 			if (!addOK){
 				if (reason == SR_FIX_ERROR && minFraction0To1 == maxFraction0To1)
 					return dupNode(midFraction, SR_FIX_ERROR);
-				if (Math.min(len1, len3) < MAX_LOCATE_ERROR ){
-					double splitFrac = -1;
-					if (reason == SR_OPT_LEN){
-						if (wantedFraction <= minFraction0To1)
-							splitFrac = minFraction0To1;
-						else if (wantedFraction >= maxFraction0To1)
-							splitFrac = maxFraction0To1;
-						if (splitFrac <= 0.5 && len1 >= MAX_LOCATE_ERROR || splitFrac > 0.5 && len3 >= MAX_LOCATE_ERROR){
-							splitFrac = -1;
-						}
+				
+				if (Math.min(len1, len3) < MAX_LOCATE_ERROR) {
+					double splitFrac = midFraction;
+					if (splitFrac <= 0.5) {
+						if (splitFrac * segmentLength > MAX_LOCATE_ERROR)
+							splitFrac = Math.max(minFraction0To1, MAX_LOCATE_ERROR / segmentLength);
+					} else {
+						if ((1-splitFrac) * segmentLength > MAX_LOCATE_ERROR)
+							splitFrac = Math.min(maxFraction0To1, (segmentLength-MAX_LOCATE_ERROR) / segmentLength);
+						
 					}
-					if (splitFrac < 0)
-						splitFrac = (minFraction0To1 != maxFraction0To1) ? midFraction : minFraction0To1;
 					return dupNode(splitFrac, SR_OPT_LEN);
 				}
 				if(reason == SR_FIX_ERROR)
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
index 753d5f2..ff99406 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
@@ -1641,10 +1641,10 @@ public class HousenumberGenerator {
 	 * @param spoint1 segment point 1
 	 * @param spoint2 segment point 2
 	 * @param point point
+	 * @param frac the fraction returned from {@code getFrac()} 
 	 * @return the distance in meter
 	 */
 	public static double distanceToSegment(Coord spoint1, Coord spoint2, Coord point, double frac) {
-
 		if (frac <= 0) {
 			return spoint1.distance(point);
 		} else if (frac >= 1) {
@@ -1652,7 +1652,6 @@ public class HousenumberGenerator {
 		} else {
 			return point.distToLineSegment(spoint1, spoint2);
 		}
-
 	}
 	
 	/**
@@ -1679,7 +1678,7 @@ public class HousenumberGenerator {
 			return 0;
 		else {
 			// scale for longitude deltas by cosine of average latitude  
-			double scale = Math.cos(Coord.int30ToRadians((aLat + bLat + pLat) / 3) );
+			double scale = Math.cos(Coord.hpToRadians((aLat + bLat + pLat) / 3) );
 			double deltaLonAP = scale * (pLon - aLon);
 			deltaLon = scale * deltaLon;
 			if (deltaLon == 0 && deltaLat == 0)
diff --git a/src/uk/me/parabola/mkgmap/reader/MapReader.java b/src/uk/me/parabola/mkgmap/reader/MapReader.java
new file mode 100644
index 0000000..1848f55
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/reader/MapReader.java
@@ -0,0 +1,67 @@
+/*
+ * 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: 02-Sep-2007
+ */
+package uk.me.parabola.mkgmap.reader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
+import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
+import uk.me.parabola.mkgmap.reader.polish.PolishMapDataSource;
+import uk.me.parabola.mkgmap.reader.test.ElementTestDataSource;
+
+/**
+ * Class to find the correct map reader to use, based on the type of the file
+ * to be read.
+ *
+ * Allows new map readers to be registered, the map readers are in charge of
+ * recognising file formats that they can deal with.
+ *
+ * @author Steve Ratcliffe
+ */
+public class MapReader {
+	private static final List<LoadableMapDataSource> loaders;
+	static {
+		loaders = new ArrayList<>();
+		loaders.add(new PolishMapDataSource());
+		loaders.add(new ElementTestDataSource());
+		loaders.add(new OsmMapDataSource()); // must be last
+	}
+
+	/**
+	 * Return a suitable map reader.  The name of the resource to be read is
+	 * passed in.  This is usually a file name, but could be something else.
+	 *
+	 * @param name The resource name to be read.
+	 * @return A LoadableMapDataSource that is capable of reading the resource.
+	 */
+	public static LoadableMapDataSource createMapReader(String name) {
+		for (LoadableMapDataSource loader : loaders) {
+			if (loader.isFileSupported(name)) {
+				try {
+					return loader.getClass().newInstance();
+				} catch (InstantiationException e) {
+					// try the next one.
+				} catch (IllegalAccessException e) {
+					// try the next one.
+				}
+			}
+		}
+
+		throw new UnsupportedOperationException("Don't know how to read " + name); 
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
index 2c4b061..3625365 100644
--- a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
@@ -22,14 +22,12 @@ 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.mkgmap.general.LoadableMapDataSource;
 import uk.me.parabola.mkgmap.general.MapDataSource;
 import uk.me.parabola.mkgmap.general.MapDetails;
 import uk.me.parabola.mkgmap.general.MapLine;
 import uk.me.parabola.mkgmap.general.MapPoint;
 import uk.me.parabola.mkgmap.general.MapShape;
 import uk.me.parabola.imgfmt.app.net.RoadNetwork;
-import uk.me.parabola.mkgmap.reader.dem.DEM;
 import uk.me.parabola.util.Configurable;
 import uk.me.parabola.util.EnhancedProperties;
 
@@ -105,23 +103,13 @@ public abstract class MapperBasedMapDataSource implements MapDataSource, Configu
 	/**
 	 * We add the background polygons if the map is not transparent.
 	 */
-	protected void addBackground() {
-		addBackground(false);
-	}
-
-	protected void addBackground(boolean mapHasPolygon4B) {
-		if (!mapHasPolygon4B && !getConfig().getProperty("transparent", false)) {
-
-			MapShape background = new MapShape();
-			background.setPoints(mapper.getBounds().toCoords());
-			background.setType(0x4b); // background type
-			background.setMinResolution(0); // On all levels
+	public void addBackground() {
+		MapShape background = new MapShape();
+		background.setPoints(mapper.getBounds().toCoords());
+		background.setType(0x4b); // background type
+		background.setMinResolution(0); // On all levels
 
-			mapper.addShape(background);
-		}
-		if (getConfig().getProperty("contours", false)) {		    
-		    DEM.createContours((LoadableMapDataSource) this, getConfig());
-		}
+		mapper.addShape(background);
 	}
 
 	public void addBoundaryLine(Area area, int type, String name) {
diff --git a/src/uk/me/parabola/mkgmap/reader/dem/Brent.java b/src/uk/me/parabola/mkgmap/reader/dem/Brent.java
deleted file mode 100644
index 19e47b6..0000000
--- a/src/uk/me/parabola/mkgmap/reader/dem/Brent.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2009 Christian Gawron
- * 
- *  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: Christian Gawron
- * Create date: 03-Jul-2009
- */
-package uk.me.parabola.mkgmap.reader.dem;
-
-/**
- * Find zero of a function using Brent's method.
- */
-public class Brent {
-    
-	public interface Function {
-		public double eval(double x);
-	}
-
-    private static final double epsilon = 3.0e-10;
-
-    public static double zero(Function f, double x1, double x2) {
-		return zero(f, x1, x2, 1e-8, 100);
-    }
-
-	public static double zero(Function f, double x1, double x2, double tol, int maxit) {
-		double a=x1, b=x2, c=x2;
-		double fa=f.eval(a), fb=f.eval(b);
-
-		if ((fa > 0.0 && fb > 0.0) || (fa < 0.0 && fb < 0.0))
-			throw new ArithmeticException("Root must be bracketed");
-
-		double fc = fb;
-		double d = 0;
-		double e = 0;
-		for (int iterations=0; iterations < maxit; iterations++) {
-			if ((fb > 0.0 && fc > 0.0) || (fb < 0.0 && fc < 0.0)) {
-				c=a;
-				fc=fa;
-				e=d=b-a;
-			}
-			if (Math.abs(fc) < Math.abs(fb)) {
-				a=b;
-				b=c;
-				c=a;
-				fa=fb;
-				fb=fc;
-				fc=fa;
-			}
-			double tolerance = 2.0 * epsilon * Math.abs(b) + 0.5 * tol;
-			double xm = 0.5 * (c - b);
-			if (Math.abs(xm) <= tolerance || fb == 0.0) return b;
-			if (Math.abs(e) >= tolerance && Math.abs(fa) > Math.abs(fb)) {
-				double s = fb / fa;
-				double p;
-				double q;
-				if (a == c) {
-					p=2.0*xm*s;
-					q=1.0-s;
-				} else {
-					q=fa/fc;
-					double r = fb / fc;
-					p=s*(2.0*xm*q*(q-r)-(b-a)*(r-1.0));
-					q=(q-1.0)*(r-1.0)*(s-1.0);
-				}
-				if (p > 0.0) q = -q;
-				p=Math.abs(p);
-				double min1 = 3.0 * xm * q - Math.abs(tolerance * q);
-				double min2 = Math.abs(e * q);
-				if (2.0*p < (min1 < min2 ? min1 : min2)) {
-					e=d;
-					d=p/q;
-				} else {
-					d=xm;
-					e=d;
-				}
-			} else {
-				d=xm;
-				e=d;
-			}
-			a=b;
-			fa=fb;
-			if (Math.abs(d) > tolerance)
-				b += d;
-			else
-				b += xm >= 0 ? tolerance : -tolerance;
-			fb=f.eval(b);
-		}
-		throw new ArithmeticException("Maximum number of iterations exceeded");
-	}
-}
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/reader/dem/DEM.java b/src/uk/me/parabola/mkgmap/reader/dem/DEM.java
deleted file mode 100644
index 75b5ab5..0000000
--- a/src/uk/me/parabola/mkgmap/reader/dem/DEM.java
+++ /dev/null
@@ -1,863 +0,0 @@
-/*
- * Copyright (C) 2009 Christian Gawron
- * 
- *  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: Christian Gawron
- * Create date: 03-Jul-2009
- */
-package uk.me.parabola.mkgmap.reader.dem;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.lang.reflect.Constructor;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import uk.me.parabola.imgfmt.ExitException;
-import uk.me.parabola.imgfmt.FileSystemParam;
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.imgfmt.Utils;
-import uk.me.parabola.imgfmt.app.Area;
-import uk.me.parabola.imgfmt.app.Coord;
-import uk.me.parabola.imgfmt.app.map.Map;
-import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.build.MapBuilder;
-import uk.me.parabola.mkgmap.general.LevelInfo;
-import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
-import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
-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;
-
-
-/**
- * Create contour lines using an algorithm similar to that described in <a
- * href="http://mapcontext.com/autocarto/proceedings/auto-carto-5/pdf/an-adaptive-grid-contouring-algorithm.pdf">An
- * Adaptive Grid Contouring Algorithm</a> by Downing and Zoraster.
- */
-public abstract class DEM {
-	private static final Logger log = Logger.getLogger(DEM.class);
-
-	private final static double epsilon = 1e-9;
-	final protected static double delta = 1.5;
-	private final static int maxPoints = 200000;
-	private final static double minDist = 15;
-	private final static double maxDist = 21;
-
-	protected static int M = 1200;
-	protected static int N = M;
-	protected static double res = 1.0 / N;
-	private static int id = -1;
-
-	protected int lat;
-	protected int lon;
-
-	protected abstract double ele(int x, int y);
-
-	protected abstract void read(int minLon, int minLat, int maxLon, int maxLat);
-
-	public static void createContours(LoadableMapDataSource mapData, EnhancedProperties config) {
-		Area bounds = mapData.getBounds();
-
-		double minLat = Utils.toDegrees(bounds.getMinLat());
-		double minLon = Utils.toDegrees(bounds.getMinLong());
-		double maxLat = Utils.toDegrees(bounds.getMaxLat());
-		double maxLon = Utils.toDegrees(bounds.getMaxLong());
-
-		System.out.printf("bounds: %f %f %f %f\n", minLat, minLon, maxLat, maxLon);
-		DEM data;
-		String demType = config.getProperty("dem-type", "SRTM");
-
-		try {
-			String dataPath;
-			Class<?> demClass ;
-			switch (demType) {
-			case "ASTER":
-				dataPath = config.getProperty("dem-path", "ASTER");
-				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.optional.GeoTiffDEM$ASTER");
-				break;
-			case "CGIAR":
-				dataPath = config.getProperty("dem-path", "CGIAR");
-				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.optional.GeoTiffDEM$CGIAR");
-				break;
-			default:
-				dataPath = config.getProperty("dem-path", "SRTM");
-				demClass = Class.forName("uk.me.parabola.mkgmap.reader.dem.HGTDEM");
-				break;
-			}
-			Constructor<DEM> constructor = (Constructor<DEM>) demClass.getConstructor(String.class,
-					Double.TYPE, Double.TYPE,
-					Double.TYPE, Double.TYPE);
-			data = constructor.newInstance(dataPath, minLat, minLon, maxLat, maxLon);
-		}
-		catch (Exception ex) {
-			throw new ExitException("failed to create DEM", ex);
-		}
-
-		Isolines lines = data.new Isolines(data, minLat, minLon, maxLat, maxLon);
-		int increment = config.getProperty("dem-increment", 10);
-
-		double minHeight = lines.getMinHeight();
-		double maxHeight = lines.getMaxHeight();
-		int maxLevels = config.getProperty("dem-maxlevels", 100);
-		while ((maxHeight - minHeight) / increment > maxLevels)
-			increment *= 2;
-
-		Style style = StyleImpl.readStyle(config);
-
-		LoadableMapDataSource dest = mapData;
-		if (config.getProperty("dem-separate-img", false)) {
-			dest = new DEMMapDataSource(mapData, config);
-		}
-
-		OsmConverter converter = new StyledConverter(style, ((MapperBasedMapDataSource) dest).getMapper(), config);
-
-		for (int level = 0; level < maxHeight; level += increment) {
-			if (level < minHeight) continue;
-
-			// create isolines
-			lines.addLevel(level);
-
-			for (Isolines.Isoline line : lines.isolines) {
-				Way way = new Way(id--, line.points);
-				way.addTag("contour", "elevation");
-				way.addTag("ele", String.format("%d", (int) line.level));
-				converter.convertWay(way);
-			}
-			lines.isolines.clear();
-		}
-
-		if (config.getProperty("dem-separate-img", false)) {
-			MapBuilder builder = new MapBuilder();
-			builder.config(config);
-
-			// Get output directory
-			String DEFAULT_DIR = ".";
-			String fileOutputDir = config.getProperty("output-dir", DEFAULT_DIR);
-			
-			// Test if directory exists
-			File outputDir = new File(fileOutputDir);
-			if (!outputDir.exists()) {
-				System.out.println("Output directory not found. Creating directory '" + fileOutputDir + "'");
-				if (!outputDir.mkdirs()) {
-					System.err.println("Unable to create output directory! Using default directory instead");
-					fileOutputDir = DEFAULT_DIR;
-				}
-			} else if (!outputDir.isDirectory()) {
-				System.err.println("The --output-dir parameter must specify a directory. The parameter is being ignored, writing to default directory instead.");
-				fileOutputDir = DEFAULT_DIR;
-			}		
-			
-			FileSystemParam params = new FileSystemParam();
-			params.setMapDescription("contour lines");
-			long mapName = Integer.valueOf(config.getProperty("mapname", "63240000"));
-			try {
-				String mapname = String.format("%08d", mapName + 10000000);
-				Map map = Map.createMap(mapname, fileOutputDir, params, mapname, SrtTextReader.sortForCodepage(1252));
-				builder.makeMap(map, dest);
-				map.close();
-			}
-			catch (Exception ex) {
-				throw new ExitException("could not open " + mapName, ex);
-			}
-		}
-	}
-
-	private int lastXi = -1;
-	private int lastYi = -1;
-
-	private final static int[][] bcInv = {
-			{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
-			{-3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1, 0, 0, 0, 0},
-			{2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
-			{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
-			{0, 0, 0, 0, -3, 0, 0, 3, 0, 0, 0, 0, -2, 0, 0, -1},
-			{0, 0, 0, 0, 2, 0, 0, -2, 0, 0, 0, 0, 1, 0, 0, 1},
-			{-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			{0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0},
-			{9, -9, 9, -9, 6, 3, -3, -6, 6, -6, -3, 3, 4, 2, 1, 2},
-			{-6, 6, -6, 6, -4, -2, 2, 4, -3, 3, 3, -3, -2, -1, -1, -2},
-			{2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
-			{0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0},
-			{-6, 6, -6, 6, -3, -3, 3, 3, -4, 4, 2, -2, -2, -2, -1, -1},
-			{4, -4, 4, -4, 2, 2, -2, -2, 2, -2, -2, 2, 1, 1, 1, 1}
-	};
-
-	private static int lastId = 1000000000;
-	private static double lastX;
-	private static double lastY;
-
-	private static final int[][] off0 = {{0, 0},
-			{0, 0},
-			{0, 1},
-			{1, 1}};
-
-	private static final int[][] off1 = {{1, 0},
-			{0, 1},
-			{1, 1},
-			{1, 0}};
-
-	private static final int[] brd = {1, 2, 4, 8};
-
-	private static final int[] rev = {2, 3, 0, 1};
-
-	private static final int[][] mov = {{0, -1},
-			{-1, 0},
-			{0, 1},
-			{1, 0}};
-
-
-	private final double[][] bc = new double[4][4];
-	private final double[] bc_y = new double[4];
-	private final double[] bc_y1 = new double[4];
-	private final double[] bc_y2 = new double[4];
-	private final double[] bc_y12 = new double[4];
-	private final double[] bc_Coeff = new double[16];
-	private final double[] bc_x = new double[16];
-
-	private void recalculateCoefficients(int xi, int yi) {
-
-		double v00 = ele(xi, yi);
-		double v0p = ele(xi, yi + 1);
-		double vpp = ele(xi + 1, yi + 1);
-		double vp0 = ele(xi + 1, yi);
-
-		double vm0 = ele(xi - 1, yi);
-		double v0m = ele(xi, yi - 1);
-		double vmp = ele(xi - 1, yi + 1);
-		double vpm = ele(xi + 1, yi - 1);
-		double vmm = ele(xi - 1, yi - 1);
-		double vmP = ele(xi + 2, yi - 1);
-		double vPm = ele(xi - 1, yi + 2);
-
-		double vP0 = ele(xi + 2, yi);
-		double v0P = ele(xi, yi + 2);
-		double vPp = ele(xi + 2, yi + 1);
-		double vpP = ele(xi + 1, yi + 2);
-		double vPP = ele(xi + 2, yi + 2);
-
-		bc_y[0] = v00;
-		bc_y[1] = vp0;
-		bc_y[2] = vpp;
-		bc_y[3] = v0p;
-
-		bc_y1[0] = (vp0 - vm0) / 2;
-		bc_y1[1] = (vP0 - v00) / 2;
-		bc_y1[2] = (vPp - v0p) / 2;
-		bc_y1[3] = (vpp - vmp) / 2;
-
-		bc_y2[0] = (v0p - v0m) / 2;
-		bc_y2[1] = (vpp - vpm) / 2;
-		bc_y2[2] = (vpP - vp0) / 2;
-		bc_y2[3] = (v0P - v00) / 2;
-
-		bc_y12[0] = (vpp - vpm - vmp + vmm) / 4;
-		bc_y12[0] = (vPp - vPm - v0p + v0m) / 4;
-		bc_y12[2] = (vPP - vP0 - v0P + v00) / 4;
-		bc_y12[0] = (vpP - vp0 - vmP + vm0) / 4;
-
-		int i;
-
-		for (i = 0; i < 4; i++) {
-			bc_x[i] = bc_y[i];
-			bc_x[i + 4] = bc_y1[i];
-			bc_x[i + 8] = bc_y2[i];
-			bc_x[i + 12] = bc_y12[i];
-		}
-
-		for (i = 0; i < 16; i++) {
-			double s = 0;
-			for (int k = 0; k < 16; k++) s += bcInv[i][k] * bc_x[k];
-			bc_Coeff[i] = s;
-		}
-
-		int l = 0;
-		for (i = 0; i < 4; i++)
-			for (int j = 0; j < 4; j++)
-				bc[i][j] = bc_Coeff[l++];
-	}
-
-	protected double gradient(double lat, double lon, double[] grad) {
-		grad[0] = 0;
-		grad[1] = 0;
-
-		double x = (lon - this.lon) / res;
-		double y = (lat - this.lat) / res;
-
-		int xi = (int) x;
-		int yi = (int) y;
-
-		if (lastXi != xi || lastYi != yi) {
-			log.debug("new Cell for interpolation: %d %d", xi, yi);
-			recalculateCoefficients(xi, yi);
-			lastXi = xi;
-			lastYi = yi;
-		}
-
-		double t = x - xi;
-		double u = y - yi;
-
-		if (xi < 0 || xi > N + 1 || yi < 0 || yi > N + 1)
-			throw new IndexOutOfBoundsException(String.format("(%f, %f)->(%d, %d)", lat, lon, xi, yi));
-
-		double val = 0;
-		for (int i = 3; i >= 0; i--) {
-			val = t * val + ((bc[i][3] * u + bc[i][2]) * u + bc[i][1]) * u + bc[i][0];
-			grad[0] = u * grad[0] + (3 * bc[3][i] * t + 2 * bc[2][i]) * t + bc[1][i];
-			grad[1] = t * grad[1] + (3 * bc[i][3] * t + 2 * bc[i][2]) * t + bc[i][1];
-		}
-
-		return val;
-	}
-
-	protected double elevation(double lat, double lon) {
-		double x = (lon - this.lon) / res;
-		double y = (lat - this.lat) / res;
-
-		int xi = (int) x;
-		int yi = (int) y;
-
-		if (lastXi != xi || lastYi != yi) {
-			log.debug("new Cell for interpolation: %d %d", xi, yi);
-			recalculateCoefficients(xi, yi);
-			lastXi = xi;
-			lastYi = yi;
-		}
-
-		double t = x - xi;
-		double u = y - yi;
-
-		if (xi < 0 || xi > N + 1 || yi < 0 || yi > N + 1)
-			throw new IndexOutOfBoundsException(String.format("(%f, %f)->(%d, %d)", lat, lon, xi, yi));
-
-		double val = 0;
-		for (int i = 3; i >= 0; i--) {
-			val = t * val + ((bc[i][3] * u + bc[i][2]) * u + bc[i][1]) * u + bc[i][0];
-		}
-
-		return val;
-	}
-
-	protected double elevation(int x, int y) {
-		if (x < 0 || x > N || y < 0 || y > N)
-			throw new IndexOutOfBoundsException(String.format("elevation: %d %d", x, y));
-		return ele(x, y);
-	}
-
-
-	class Isolines {
-		final DEM data;
-		final int minX;
-		final int maxX;
-		final int minY;
-		final int maxY;
-
-		double min;
-		double max;
-
-		final ArrayList<Isoline> isolines = new ArrayList<>();
-
-		class Isoline {
-			final int id;
-			final ArrayList<Coord> points;
-			final double level;
-
-			private Isoline(double level) {
-				this.level = level;
-				id = lastId++;
-				points = new ArrayList<>();
-			}
-
-			private class Edge implements Brent.Function {
-				final double x0;
-				final double y0;
-				final double x1;
-				final double y1;
-
-				Edge(double x0, double y0, double x1, double y1) {
-					this.x0 = x0;
-					this.y0 = y0;
-					this.x1 = x1;
-					this.y1 = y1;
-				}
-
-				public double eval(double d) {
-					return data.elevation(x0 + d * (x1 - x0), y0 + d * (y1 - y0)) - level;
-				}
-			}
-
-			private class FN implements Brent.Function {
-				double x0, y0;
-				double dx, dy;
-
-				public void setParameter(double x0, double y0, double dx, double dy) {
-					this.x0 = x0;
-					this.y0 = y0;
-					this.dx = dx;
-					this.dy = dy;
-				}
-
-				public double eval(double t) {
-					return data.elevation(y0 + t * dy, x0 + t * dx) - level;
-				}
-			}
-
-			private final FN fn = new FN();
-
-			final double[] grad = new double[2];
-			final double[] px = new double[4];
-			final double[] py = new double[4];
-			final int[] edges = new int[4];
-
-			boolean addCell(Position p, int direction) {
-				log.debug("addCell: %f %d %d %d %d", level, p.ix, p.iy, p.edge, direction);
-
-				int c = 0;
-				for (int k = 0; k < 4; k++) {
-					if (k == p.edge)
-						continue;
-
-					int x0 = p.ix + off0[k][0];
-					int y0 = p.iy + off0[k][1];
-					int x1 = p.ix + off1[k][0];
-					int y1 = p.iy + off1[k][1];
-
-					double l0 = elevation(x0, y0) - level;
-					double l1 = elevation(x1, y1) - level;
-
-					if (Math.abs(l1) < epsilon || l0 * l1 < 0) {
-						edges[c] = k;
-
-						Brent.Function f = new Edge(data.lat + y0 * DEM.res, data.lon + x0 * DEM.res, data.lat + y1 * DEM.res, data.lon + x1 * DEM.res);
-						double f0 = elevation(x0, y0) - level;
-						double delta;
-
-						if (Math.abs(1) < epsilon) {
-							delta = 1;
-						} else if (Math.abs(f0) < epsilon)
-							throw new ExitException("implementation error!");
-						else
-							delta = Brent.zero(f, epsilon, 1 - epsilon);
-
-						px[c] = data.lon + (x0 + delta * (x1 - x0)) * DEM.res;
-						py[c] = data.lat + (y0 + delta * (y1 - y0)) * DEM.res;
-						c++;
-					}
-				}
-
-
-				if (c == 1) {
-					p.edge = edges[0];
-
-					double px0 = p.x;
-					double py0 = p.y;
-					p.x = px[0];
-					p.y = py[0];
-					double px1 = p.x;
-					double py1 = p.y;
-
-					double xMin = data.lon + p.ix * DEM.res;
-					double xMax = xMin + DEM.res;
-					double yMin = data.lat + p.iy * DEM.res;
-					double yMax = yMin + DEM.res;
-
-					refineAdaptively(xMin, yMin, xMax, yMax, px0, py0, px1, py1, direction, maxDist);
-
-					addPoint(p.x, p.y, direction);
-					p.moveCell();
-					return true;
-				} else {
-					log.debug("addCellByStepping: %d", c);
-					return addCellByStepping(p, direction, c, edges, px, py);
-				}
-			}
-
-			private void refineAdaptively(double xMin, double yMin, double xMax, double yMax,
-					double x0, double y0, double x1, double y1,
-					int direction, double maxDist)
-			{
-				double dist = quickDistance(x0, y0, x1, y1);
-				if (dist > maxDist) {
-					double dx = x1 - x0;
-					double dy = y1 - y0;
-
-					double xm = x0 + 0.5 * dx;
-					double ym = y0 + 0.5 * dy;
-					double n = Math.sqrt(dx * dx + dy * dy);
-					fn.setParameter(xm, ym, -dy / n, dx / n);
-					Brent.Function f = fn;
-					double t0 = -0.05 * res;
-					double t1 = 0.05 * res;
-					double f0 = f.eval(t0);
-					double f1 = f.eval(t1);
-
-					int count = 0;
-					while (f0 * f1 > 0 && count++ < 20) {
-						if ((count & 1) > 0)
-							t0 -= 0.05 * res;
-						else
-							t1 += 0.05 * res;
-						f0 = f.eval(t0);
-						f1 = f.eval(t1);
-						log.debug("refine: %f %f %f %f", t0, t1, f0, f1);
-					}
-
-					if (f0 * f1 < 0) {
-						double t = Brent.zero(f, t0, t1);
-						xm -= t * dy;
-						ym += t * dx;
-					} else {
-						log.debug("refine failed: %f %f %f %f", t0, t1, f0, f1);
-						return;
-					}
-
-					if (xm > xMin && xm < xMax && ym > yMin && ym < yMax)
-						refineAdaptively(xMin, yMin, xMax, yMax, x0, y0, xm, ym, direction, maxDist * 1.1);
-					addPoint(xm, ym, direction);
-					if (xm > xMin && xm < xMax && ym > yMin && ym < yMax)
-						refineAdaptively(xMin, yMin, xMax, yMax, xm, ym, x1, y1, direction, maxDist * 1.1);
-				}
-			}
-
-			boolean addCellByStepping(Position p, int direction, int numEdges, int[] edges, double[] px, double[] py) {
-				log.debug("addCellByStepping: %f %d %d %d %d", level, p.ix, p.iy, p.edge, direction);
-
-				int iMin = -1;
-				double md = 5000;
-
-				for (int i = 0; i < numEdges; i++) {
-					gradient(p.y, p.x, grad);
-					double dist = quickDistance(p.x, p.y, px[i], py[i]);
-					log.debug("distance %d: %f", i, dist);
-
-					if (dist < md && (visited[p.iy * (N + 1) + p.ix] & brd[edges[i]]) == 0) {
-						md = dist;
-						iMin = i;
-					}
-				}
-
-				p.edge = edges[iMin];
-
-				double px0 = p.x;
-				double py0 = p.y;
-				p.x = px[iMin];
-				p.y = py[iMin];
-				double px1 = p.x;
-				double py1 = p.y;
-
-				double xMin = data.lon + p.ix * DEM.res;
-				double xMax = xMin + DEM.res;
-				double yMin = data.lat + p.iy * DEM.res;
-				double yMax = yMin + DEM.res;
-
-				refineAdaptively(xMin, yMin, xMax, yMax, px0, py0, px1, py1, direction, maxDist);
-
-				addPoint(p.x, p.y, direction);
-				p.moveCell();
-				return true;
-			}
-
-			private void addPoint(double x, double y, int direction) {
-				double dist = quickDistance(x, y, lastX, lastY);
-				log.debug("addPoint: %f %f %f", x, y, dist);
-
-				if (dist > minDist) {
-					if (direction > 0)
-						points.add(0, new Coord(y, x));
-					else
-						points.add(points.size(), new Coord(y, x));
-					lastX = x;
-					lastY = y;
-				}
-			}
-
-			private void close() {
-				points.add(points.size(), points.get(0));
-			}
-
-		}
-
-		public Isolines(DEM data, double minLat, double minLon, double maxLat, double maxLon) {
-			System.out.printf("init: %f %f %f %f\n", minLat, minLon, maxLat, maxLon);
-
-			this.data = data;
-			this.minX = (int) ((minLon - data.lon) / res);
-			this.minY = (int) ((minLat - data.lat) / res);
-			this.maxX = (int) ((maxLon - data.lon) / res);
-			this.maxY = (int) ((maxLat - data.lat) / res);
-
-			init();
-		}
-
-		private void init() {
-			System.out.printf("init: %d %d %d %d\n", minX, minY, maxX, maxY);
-			data.read(minX - 2, minY - 2, maxX + 2, maxY + 2);
-			// we need some overlap for bicubic interpolation
-			max = -1000;
-			min = 10000;
-			for (int i = minX; i < maxX; i++)
-				for (int j = minY; j < maxY; j++) {
-					if (data.elevation(i, j) < min) min = data.elevation(i, j);
-					if (data.elevation(i, j) > max) max = data.elevation(i, j);
-				}
-
-			log.debug("min: %f, max: %f\n", min, max);
-		}
-
-		double getMinHeight() {
-			return min;
-		}
-
-		double getMaxHeight() {
-			return max;
-		}
-
-		private class Edge implements Brent.Function {
-			final double x0, y0, x1, y1, level;
-
-			Edge(double x0, double y0, double x1, double y1, double level) {
-				this.x0 = x0;
-				this.y0 = y0;
-				this.x1 = x1;
-				this.y1 = y1;
-				this.level = level;
-			}
-
-			public double eval(double d) {
-				return data.elevation(x0 + d * (x1 - x0), y0 + d * (y1 - y0)) - level;
-			}
-		}
-
-		private class Position {
-			int ix, iy;
-			double x, y;
-			int edge;
-
-			Position(int ix, int iy, double x, double y, int edge) {
-				this.ix = ix;
-				this.iy = iy;
-				this.x = x;
-				this.y = y;
-				this.edge = edge;
-			}
-
-			Position(Position p) {
-				this.ix = p.ix;
-				this.iy = p.iy;
-				this.x = p.x;
-				this.y = p.y;
-				this.edge = p.edge;
-			}
-
-			void markEdge() {
-				log.debug("marking edge: %d %d %d %d", ix, iy, edge, brd[edge]);
-				visited[iy * (N + 1) + ix] |= brd[edge];
-			}
-
-			void moveCell() {
-				markEdge();
-				ix += mov[edge][0];
-				iy += mov[edge][1];
-				edge = rev[edge];
-				markEdge();
-			}
-		}
-
-		final byte[] visited = new byte[(N + 1) * (N + 1)];
-
-		public void addLevel(double level) {
-			if (level < min || level > max)
-				return;
-
-			System.out.printf("addLevel: %f\n", level);
-			Arrays.fill(visited, (byte) 0);
-
-			for (int y = minY; y < maxY; y++) {
-				for (int x = minX; x < maxX; x++) {
-					byte v = 0;
-					int direction;
-					// Mark the borders of the cell, represented by the four points (i, j), (i+1, j), (i, j+1), (i+1, j+1),
-					// which are intersected by the contour. The values are:
-					// 1: top
-					// 2: left
-					// 4: bottom
-					// 8: right
-					if (data.elevation(x, y) >= level) {
-						if (data.elevation(x + 1, y) < level) {
-							v |= 1;
-						}
-						if (data.elevation(x, y + 1) < level) {
-							v |= 2;
-						}
-						direction = 1;
-					} else {
-						if (data.elevation(x + 1, y) > level) {
-							v |= 1;
-						}
-						if (data.elevation(x, y + 1) > level) {
-							v |= 2;
-						}
-						direction = -1;
-					}
-
-					int k = -1;
-
-					if ((v & 1) > 0 && (visited[y * (N + 1) + x] & 1) == 0) {
-						k = 0;
-					} else if ((v & 2) > 0 && (visited[y * (N + 1) + x] & 2) == 0) {
-						k = 1;
-					}
-
-					if (k >= 0) {
-						int x0 = x + off0[k][0];
-						int y0 = y + off0[k][1];
-						int x1 = x + off1[k][0];
-						int y1 = y + off1[k][1];
-
-						try {
-							Brent.Function f = new Edge(data.lat + y0 * DEM.res, data.lon + x0 * DEM.res,
-									data.lat + y1 * DEM.res, data.lon + x1 * DEM.res,
-									level);
-							double f0 = elevation(x0, y0) - level;
-							double f1 = elevation(x1, y1) - level;
-							double delta;
-							if (Math.abs(f0) < epsilon) {
-								delta = 0;
-							} else if (Math.abs(f1) < epsilon)
-								continue;
-							else
-								delta = Brent.zero(f, 0, 1 - epsilon);
-
-							Position p = new Position(x, y, data.lon + (x0 + delta * (x1 - x0)) * DEM.res, data.lat + (y0 + delta * (y1 - y0)) * DEM.res, k);
-							p.markEdge();
-							isolines.add(traceByStepping(level, p, direction));
-						}
-						catch (RuntimeException ex) {
-							log.debug("error: %s", ex.toString());
-							ex.printStackTrace();
-							return;
-						}
-					}
-				}
-			}
-		}
-
-		private Isoline traceByStepping(double level, Position p, int direction) {
-			log.debug("traceByStepping: starting contour %f %d %d %f %f %d", level, p.ix, p.iy, p.x, p.y, p.edge);
-			int n = 0;
-			Position startP = new Position(p);
-			boolean foundEnd = false;
-
-			Isoline line = new Isoline(level);
-
-			while (true) {
-				log.debug("traceByStepping: %f %d %d %f %f %d", level, p.ix, p.iy, p.x, p.y, p.edge);
-				visited[p.iy * (N + 1) + p.ix] |= brd[p.edge];
-
-				if (n > 0 && p.ix == startP.ix && p.iy == startP.iy && quickDistance(p.x, p.y, startP.x, startP.y) < 5) {
-					log.debug("closed curve!");
-					line.close();
-					break;
-				} else if (p.ix < minX || p.iy < minY || p.ix >= maxX || p.iy >= maxY) {
-					if (foundEnd) // did we already reach one end?
-					{
-						log.debug("second border reached!");
-						break;
-					} else {
-						log.debug("border reached!");
-						foundEnd = true;
-						n = 0;
-						direction *= -1;
-						p = new Position(startP);
-						p.moveCell();
-						continue;
-					}
-				}
-				n++;
-				if (!line.addCell(p, direction) || line.points.size() > maxPoints) {
-					log.debug("ending contour");
-					isolines.add(line);
-					return line;
-				}
-			}
-
-			return line;
-		}
-
-	}
-
-	private static double quickDistance(double long1, double lat1, double long2, double lat2) {
-		double latDiff;
-		if (lat1 < lat2)
-			latDiff = lat2 - lat1;
-		else
-			latDiff = lat1 - lat2;
-		if (latDiff > 90)
-			latDiff -= 180;
-
-		double longDiff;
-		if (long1 < long2)
-			longDiff = long2 - long1;
-		else
-			longDiff = long1 - long2;
-		if (longDiff > 180)
-			longDiff -= 360;
-
-		// scale longDiff by cosine of average latitude
-		longDiff *= Math.cos(Math.PI / 180 * Math.abs((lat1 + lat2) / 2));
-
-		double distDegSq = (latDiff * latDiff) + (longDiff * longDiff);
-
-		return 40075000 * Math.sqrt(distDegSq) / 360;
-	}
-
-
-	private static class DEMMapDataSource extends MapperBasedMapDataSource implements LoadableMapDataSource {
-		final LoadableMapDataSource parent;
-		final List<String> copyright = new ArrayList<>();
-
-		DEMMapDataSource(LoadableMapDataSource parent, EnhancedProperties props) {
-			this.parent = parent;
-			config(props);
-		}
-
-		public boolean isFileSupported(String name) {
-			return false;
-		}
-
-		public void load(String name)
-				throws FileNotFoundException, FormatException
-		{
-			throw new FormatException("load not supported");
-		}
-
-		public LevelInfo[] mapLevels() {
-			return parent.mapLevels();
-		}
-
-		public LevelInfo[] overviewMapLevels() {
-			return parent.overviewMapLevels();
-		}
-
-		public String[] copyrightMessages() {
-			return copyright.toArray(new String[1]);
-		}
-
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/dem/HGTDEM.java b/src/uk/me/parabola/mkgmap/reader/dem/HGTDEM.java
deleted file mode 100644
index f153c5f..0000000
--- a/src/uk/me/parabola/mkgmap/reader/dem/HGTDEM.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2009 Christian Gawron
- * 
- *  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: Christian Gawron
- * Create date: 03-Jul-2009
- */
-package uk.me.parabola.mkgmap.reader.dem;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.Writer;
-import java.nio.MappedByteBuffer;
-
-import uk.me.parabola.imgfmt.ExitException;
-
-import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
-
-public class HGTDEM extends DEM
-{
-    private MappedByteBuffer buffer ;
-    
-    public HGTDEM(String dataPath, double minLat, double minLon, double maxLat, double maxLon)
-    {
-	this.lat = (int) minLat;
-	this.lon = (int) minLon;
-	if (maxLat > lat+1 || maxLon > lon+1)
-	    throw new ExitException("Area too large (must not span more than one SRTM file)");
-	
-	String northSouth = lat < 0 ? "S" : "N";
-	String eastWest = lon > 0 ? "E" : "W";
-	String fileName = String.format("%s/%s%02d%s%03d.hgt", dataPath, 
-					northSouth, lat < 0 ? -lat : lat, 
-					eastWest, lon < 0 ? -lon : lon);
-	try {
-	    FileInputStream is = new FileInputStream(fileName);
-	    buffer = is.getChannel().map(READ_ONLY, 0, 2*(M+1)*(M+1));
-	}
-	catch (Exception e) {
-	    throw new ExitException("failed to open " + fileName, e);
-	}
-    }
-    
-    public  void read(int minLon, int minLat, int maxLon, int maxLat)
-    {
-    }
-    
-    public double ele(int x, int y)
-    {
-	return buffer.getShort(2*((M-y)*(M+1)+x))+delta;
-    }
-    
-    public void serializeCopyRight(Writer out) throws IOException
-    {
-	out.write("  <copyright>\n");
-	out.write("  Contour lines generated from DEM data by NASA\n");
-	out.write("  </copyright>\n");
-    }
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/dem/optional/GeoTiffDEM.java b/src/uk/me/parabola/mkgmap/reader/dem/optional/GeoTiffDEM.java
deleted file mode 100644
index 61283ed..0000000
--- a/src/uk/me/parabola/mkgmap/reader/dem/optional/GeoTiffDEM.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2009 Christian Gawron
- * 
- *  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: Christian Gawron
- * Create date: 03-Jul-2009
- */
-package uk.me.parabola.mkgmap.reader.dem.optional;
-
-import java.awt.*;
-import java.awt.image.Raster;
-import java.awt.image.renderable.ParameterBlock;
-import java.io.IOException;
-import java.io.Writer;
-
-import javax.media.jai.JAI;
-import javax.media.jai.PlanarImage;
-import javax.media.jai.RenderedOp;
-
-import uk.me.parabola.imgfmt.ExitException;
-import uk.me.parabola.mkgmap.reader.dem.DEM;
-
-import com.sun.media.jai.codec.FileSeekableStream;
-import com.sun.media.jai.codec.SeekableStream;
-import com.sun.media.jai.codec.TIFFDecodeParam;
-
-public abstract class GeoTiffDEM extends DEM
-{
-    Raster raster;
-    String fileName;
-    int minLat, minLon, maxLat, maxLon;
-    PlanarImage image;
-
-    void initData()
-    {
-	
-	try {
-	    SeekableStream s = new FileSeekableStream(fileName);
-	    ParameterBlock pb = new ParameterBlock();
-	    pb.add(s);
-	    
-	    TIFFDecodeParam param = new TIFFDecodeParam();
-	    pb.add(param);
-	    
-	    RenderedOp op = JAI.create("tiff", pb);
-	    image = op.createInstance();
-	    System.out.printf("Image: %d %d %d %d\n", image.getWidth(), image.getHeight(), 
-			      image.getNumXTiles(), image.getNumYTiles());
-	}
-	catch (Exception e) {
-	    throw new ExitException("Failed to open/process " + fileName, e);
-	}
-    }
-
-    protected static class CGIAR extends GeoTiffDEM
-    {
-	public CGIAR(String dataPath, double minLat, double minLon, double maxLat, double maxLon)
-	{
-	    this.lat = ((int) (minLat/5))*5;
-	    this.lon = ((int) (minLon/5))*5;
-	    if (maxLat > lat+5 || maxLon > lon+5)
-		throw new ExitException("Area too large (must not span more than one CGIAR GeoTIFF)");
-	    
-	    int tileX = (180 + lon) / 5 + 1;
-		int tileY = (60 - lat) / 5;
-	    this.fileName = String.format("%s/srtm_%02d_%02d.tif", dataPath, tileX, tileY);
-	    System.out.printf("CGIAR GeoTIFF: %s\n", fileName);
-	    N = 6000;
-	    M = 6000;
-	    res = 5.0/M;
-	    
-	    initData();
-	}
-	
-	public void serializeCopyRight(Writer out) throws IOException
-	{
-	    out.write("  <copyright>\n");
-	    out.write("  Contour lines generated from improved SRTM data by CIAT-CSI (see http://srtm.csi.cgiar.org)\n");
-	    out.write("  </copyright>\n");
-	}
-	
-	protected void read(int minLon, int minLat, int maxLon, int maxLat)
-	{
-	    this.minLon = minLon;
-	    this.minLat = minLat;
-	    this.maxLon = maxLon;
-	    this.maxLat = maxLat;
-	    raster = image.getData(new Rectangle(minLon, 6000-maxLat-1, maxLon-minLon+1, maxLat-minLat+1));
-	    System.out.printf("read: %d %d %d %d\n", minLon, 6000-maxLat-1, maxLon-minLon+1, maxLat-minLat+1);
-	}
-
-	public double ele(int x, int y)
-	{
-		int elevation = raster.getPixel(x, 6000-y-1, (int[])null)[0];
-		return elevation+delta;
-	}
-    }
-    
-    protected static class ASTER extends GeoTiffDEM
-    {
-	
-	public ASTER(String dataPath, double minLat, double minLon, double maxLat, double maxLon)
-	{
-	    this.lat = (int) minLat;
-	    this.lon = (int) minLon;
-	    if (maxLat > lat+1 || maxLon > lon+1)
-		throw new ExitException("Area too large (must not span more than one ASTER GeoTIFF)");
-	    
-	    String northSouth = lat < 0 ? "S" : "N";
-	    String eastWest = lon > 0 ? "E" : "W";
-	    fileName = String.format("%s/ASTGTM_%s%02d%s%03d_dem.tif", dataPath, 
-				     northSouth, lat < 0 ? -lat : lat, 
-				     eastWest, lon < 0 ? -lon : lon);
-	    
-	    System.out.printf("ASTER GeoTIFF: %s\n", fileName);
-	    N = 3600;
-	    M = 3600;
-	    res = 1.0/M;
-	    initData();
-	}
-	
-	public void serializeCopyRight(Writer out) throws IOException
-	{
-	    out.write("  <copyright>\n");
-	    out.write("  Contour lines generated from DGM data by ASTER (see https://wist.echo.nasa.gov/~wist/api/imswelcome)\n");
-	    out.write("  </copyright>\n");
-	}
-        
-	protected void read(int minLon, int minLat, int maxLon, int maxLat)
-	{
-	    this.minLon = minLon;
-	    this.minLat = minLat;
-	    this.maxLon = maxLon;
-	    this.maxLat = maxLat;
-	    raster = image.getData(new Rectangle(minLon, 3601-maxLat-1, maxLon-minLon+1, maxLat-minLat+1));
-	    System.out.printf("read: %d %d %d %d\n", minLon, 3601-maxLat-1, maxLon-minLon+1, maxLat-minLat+1);
-	}
-
-	public double ele(int x, int y)
-	{
-		int elevation = raster.getPixel(x, 3601-y-1, (int[])null)[0];
-		return elevation+delta;
-	}
-    }
-
-}
-
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
index f71f1c4..a9828ef 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
@@ -29,8 +29,6 @@ import uk.me.parabola.imgfmt.FormatException;
 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.LoadableMapDataSource;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5CoastDataSource;
 import uk.me.parabola.util.EnhancedProperties;
 
 public final class CoastlineFileLoader {
@@ -38,30 +36,6 @@ public final class CoastlineFileLoader {
 	private static final Logger log = Logger
 			.getLogger(CoastlineFileLoader.class);
 
-	private static final List<Class<? extends LoadableMapDataSource>> coastFileLoaders;
-
-	static {
-		String[] sources = {
-				"uk.me.parabola.mkgmap.reader.osm.bin.OsmBinCoastDataSource",
-				// must be last as it is the default
-				"uk.me.parabola.mkgmap.reader.osm.xml.Osm5CoastDataSource", };
-
-		coastFileLoaders = new ArrayList<Class<? extends LoadableMapDataSource>>();
-
-		for (String source : sources) {
-			try {
-				@SuppressWarnings({ "unchecked" })
-				Class<? extends LoadableMapDataSource> c = (Class<? extends LoadableMapDataSource>) Class
-						.forName(source);
-				coastFileLoaders.add(c);
-			} catch (ClassNotFoundException e) {
-				// not available, try the rest
-			} catch (NoClassDefFoundError e) {
-				// not available, try the rest
-			}
-		}
-	}
-
 	private final Set<String> coastlineFiles;
 	private final Collection<CoastlineWay> coastlines = new ArrayList<CoastlineWay>();
 
@@ -97,36 +71,14 @@ public final class CoastlineFileLoader {
 
 	private OsmMapDataSource loadFromFile(String name)
 			throws FileNotFoundException, FormatException {
-		OsmMapDataSource src = createMapReader(name);
+		OsmMapDataSource src = new OsmCoastDataSource();
 		src.config(getConfig());
 		log.info("Started loading coastlines from", name);
-		src.load(name);
+		src.load(name, false);
 		log.info("Finished loading coastlines from", name);
 		return src;
 	}
 
-	public static OsmMapDataSource createMapReader(String name) {
-		for (Class<? extends LoadableMapDataSource> loader : coastFileLoaders) {
-			try {
-				LoadableMapDataSource src = loader.newInstance();
-				if (name != null && src instanceof OsmMapDataSource
-						&& src.isFileSupported(name))
-					return (OsmMapDataSource) src;
-			} catch (InstantiationException e) {
-				// try the next one.
-			} catch (IllegalAccessException e) {
-				// try the next one.
-			} catch (NoClassDefFoundError e) {
-				// try the next one
-			}
-		}
-
-		// Give up and assume it is in the XML format. If it isn't we will get
-		// an
-		// error soon enough anyway.
-		return new Osm5CoastDataSource();
-	}
-
 	private Collection<Way> loadFile(String filename)
 			throws FileNotFoundException {
 		OsmMapDataSource src = loadFromFile(filename);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
index eb5dafd..142026a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
@@ -33,8 +33,15 @@ public abstract class Element {
 	private long id;
 	private long originalId;
 
+	/**
+	 * returns a copy of the tags or a new instance if the element has no tags
+	 */
+	public Tags getCopyOfTags() {
+		return tags == null ? new Tags() : tags.copy(); 
+	}
+	
 	public int getTagCount() {
-		return (tags == null ? 0 : tags.size());
+		return tags == null ? 0 : tags.size();
 	}
 	
 	/**
@@ -55,6 +62,12 @@ public abstract class Element {
 						log.info(this.toBrowseURL(),"obsolete blanks removed from tag", key, " '" + val + "' -> '" + squashed + "'");
 					val = squashed;
 				}
+				squashed = Label.squashDel(val);
+				if (val.equals(squashed) == false) {
+					if (log.isInfoEnabled())
+						log.info(this.toBrowseURL(),"DEL character (0x7f) removed from tag", key, " '" + val + "' -> '" + squashed + "'");
+					val = squashed;
+				}
 			}
 		}
 		addTag(key, val.intern());
@@ -223,17 +236,7 @@ public abstract class Element {
 		if (tags == null)
 			return "[]";
 
-		StringBuilder sb = new StringBuilder();
-		sb.append('[');
-		for (String nameval : tags) {
-			sb.append(nameval);
-			sb.append(',');
-		}
-		if (sb.length() > 1) {
-			sb.setLength(sb.length()-1);
-		}
-		sb.append(']');
-		return sb.toString();
+		return tags.toString();
 	}
 
 	/**
@@ -249,6 +252,11 @@ public abstract class Element {
 			tags = other.tags.copy();
 	}
 
+	protected void copyIds(Element other) {
+		id = other.id;
+		originalId = other.originalId;
+	}
+
 	public String getName() {
 		return getTag("mkgmap:label:1");
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
index 8a70b1d..029f973 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/ElementSaver.java
@@ -57,12 +57,11 @@ public class ElementSaver {
 	// This is an explicitly given bounding box from the input file command line etc.
 	private Area boundingBox;
 
-	// This is a calculated bounding box, which is only available if there is
-	// no given bounding box.
-	private int minLat = Utils.toMapUnit(180.0);
-	private int minLon = Utils.toMapUnit(180.0);
-	private int maxLat = Utils.toMapUnit(-180.0);
-	private int maxLon = Utils.toMapUnit(-180.0);
+	// This is a calculated bounding box
+	private int minLat = Integer.MAX_VALUE;
+	private int minLon = Integer.MAX_VALUE;
+	private int maxLat = Integer.MIN_VALUE;
+	private int maxLon = Integer.MIN_VALUE;
 
 	// Options
 	private final boolean ignoreTurnRestrictions;
@@ -97,17 +96,15 @@ public class ElementSaver {
 	 */
 	public void addPoint(long id, Coord co) {
 		coordMap.put(id, co);
-		if (boundingBox == null) {
-			if (co.getLatitude() < minLat)
-				minLat = co.getLatitude();
-			if (co.getLatitude() > maxLat)
-				maxLat = co.getLatitude();
-
-			if (co.getLongitude() < minLon)
-				minLon = co.getLongitude();
-			if (co.getLongitude() > maxLon)
-				maxLon = co.getLongitude();
-		}
+		if (co.getLatitude() < minLat)
+			minLat = co.getLatitude();
+		if (co.getLatitude() > maxLat)
+			maxLat = co.getLatitude();
+
+		if (co.getLongitude() < minLon)
+			minLon = co.getLongitude();
+		if (co.getLongitude() > maxLon)
+			maxLon = co.getLongitude();
 	}
 
 	/**
@@ -318,15 +315,26 @@ public class ElementSaver {
 	public Area getBoundingBox() {
 		if (boundingBox != null) {
 			return boundingBox;
-		} else if (minLat == Utils.toMapUnit(180.0) && maxLat == Utils.toMapUnit(-180.0)) {
+		} else if (minLat > maxLat) {
 			return new Area(0, 0, 0, 0);
 		} else {
+			return getDataBoundingBox();
+		}
+	}
+
+	/**
+	 * Get the bounding box of all nodes. Returns null if no point was read.
+	 */
+	public Area getDataBoundingBox() {
+		if (minLat > maxLat) {
+			return null;
+		} else {
 			// 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)); 
+					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 6b0ba04..4bf0fc4 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/HighwayHooks.java
@@ -14,6 +14,7 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -41,27 +42,6 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 
 	private Node currentNodeInWay;
 
-	
-	private final Set<String> usedTags = new HashSet<String>() {
-		{
-			add("highway");
-			add("access");
-			add("barrier");
-		    add("FIXME");
-		    add("fixme");
-		    add("route");
-		    add("oneway");
-		    add("junction");
-		    add("name");
-		    add(Exit.TAG_ROAD_REF);
-		    add("ref");
-		    
-// the following two tags are only added if the cycleway options are set 
-//		    add("cycleway");
-//		    add("bicycle");
-		}
-	};
-	
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
 		if(props.getProperty("make-all-cycleways", false)) {
@@ -75,8 +55,15 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 		linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
 		currentNodeInWay = null;
 
+		return true;
+	}
+
+	@Override
+	public Set<String> getUsedTags() {
+		Set<String> usedTags = new HashSet<>(Arrays.asList("highway", "access", "barrier", "FIXME", "fixme",
+				"route", "oneway", "junction", "name", Exit.TAG_ROAD_REF, "ref"));
 		if (makeOppositeCycleways) {
-			// need the additional tags 
+			// need the additional tags
 			usedTags.add("cycleway");
 			usedTags.add("bicycle");
 			usedTags.add("oneway:bicycle");
@@ -84,11 +71,6 @@ public class HighwayHooks extends OsmReadingHooksAdaptor {
 			usedTags.add("cycleway:left");
 			usedTags.add("cycleway:right");
 		}
-		return true;
-	}
-
-	
-	public Set<String> getUsedTags() {
 		return usedTags;
 	}
 	
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
index 7430dab..1462c35 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/LinkDestinationHook.java
@@ -18,7 +18,6 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
@@ -31,7 +30,7 @@ import java.util.Set;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.build.LocatorUtil;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
 import uk.me.parabola.mkgmap.osmstyle.function.LengthFunction;
 import uk.me.parabola.util.EnhancedProperties;
 import uk.me.parabola.util.MultiHashMap;
@@ -62,7 +61,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	/** 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;
+	private NameFinder nameFinder;
 
 	/** Maps which nodes contains to which ways */ 
 	private IdentityHashMap<Coord, Set<Way>> wayNodes = new IdentityHashMap<Coord, Set<Way>>();
@@ -72,7 +71,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
-		nameTags = LocatorUtil.getNameTags(props);
+		nameFinder = new NameFinder(props);
 		processDestinations = props.containsKey("process-destination");
 		processExits = props.containsKey("process-exits");
 		return processDestinations || processExits;
@@ -215,24 +214,6 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 	}
 	
 	/**
-	 * Retrieves the name of the given element based on the name-tag-list option.
-	 * @param e an OSM element
-	 * @return the name or <code>null</code> if the element has no name
-	 */
-	private String getName(Element e) {
-		if (e.getName()!= null) {
-			return e.getName();
-		}
-		for (String nameTag : nameTags) {
-			String nameTagVal = e.getTag(nameTag);
-			if (nameTagVal != null) {
-				return nameTagVal;
-			}
-		}
-		return null;
-	}
-	
-	/**
 	 * Check all restriction relations and eventually update the relations to use
 	 * the split way if appropriate.
 	 * 
@@ -410,7 +391,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 			return false;
 		}
 		return node.getTag("ref") != null || 
-				(getName(node) != null) || 
+				(nameFinder.getName(node) != null) || 
 				node.getTag("exit_to") != null;
 	}
 	
@@ -629,8 +610,8 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 										hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
 									}
 								}
-								if (getName(exitNode) != null){
-									hintWay.addTag("mkgmap:exit_hint_name", getName(exitNode));
+								if (nameFinder.getName(exitNode) != null){
+									hintWay.addTag("mkgmap:exit_hint_name", nameFinder.getName(exitNode));
 								}
 								
 								if (log.isInfoEnabled())
@@ -755,7 +736,7 @@ public class LinkDestinationHook extends OsmReadingHooksAdaptor {
 		destinationLinkWays = null;
 		linkTypes = null;
 		saver = null;
-		nameTags = null;
+		nameFinder = null;
 	}
 
 	public Set<String> getUsedTags() {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LoadableOsmDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/LoadableOsmDataSource.java
deleted file mode 100644
index 508e6ae..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/LoadableOsmDataSource.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.reader.osm;
-
-import java.io.InputStream;
-
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-
-/**
- * 
- * @author Gerd
- *
- */
-public interface LoadableOsmDataSource extends LoadableMapDataSource{
-	/**
-	 * Load osm data from open stream.  
-	 * You would implement this interface to allow reading data from
-	 * zipped files.
-	 *
-	 * @param is the already opened stream.
-	 * @throws FormatException For any kind of malformed input.
-	 */
-	
-	void load(InputStream is) throws FormatException;
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java b/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
index ebc49dc..e3bb9d3 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/LocationHook.java
@@ -17,6 +17,7 @@ import java.util.Iterator;
 import java.util.List;
 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.reader.osm.boundary.BoundaryGrid;
@@ -95,18 +96,22 @@ public class LocationHook extends OsmReadingHooksAdaptor {
 		long t1 = System.currentTimeMillis();
 		log.info("Starting with location hook");
 
-		boundaryGrid = new BoundaryGrid(boundaryDirName, saver.getBoundingBox(), props);
-		processLocationRelevantElements();
+		Area nodesBounds = saver.getDataBoundingBox();
+		if (nodesBounds != null) {
+			Area bbox = saver.getBoundingBox();
+			// calculate the needed bounding box
+			Area searchBounds = bbox.intersect(nodesBounds);
+			boundaryGrid = new BoundaryGrid(boundaryDirName, searchBounds, props);
+			processLocationRelevantElements();
 
-		boundaryGrid = null;
-		
+			boundaryGrid = null;
+		}
 		long dt = (System.currentTimeMillis() - t1);
-		log.info("======= LocationHook Stats =====");             
-		log.info("QuadTree searches    :", cntQTSearch);             
-		log.info("unsuccesfull         :", cntNotFnd);             
-		log.info("unsuccesfull for ways:", cntwayNotFnd);             
+		log.info("======= LocationHook Stats =====");
+		log.info("QuadTree searches    :", cntQTSearch);
+		log.info("unsuccesfull         :", cntNotFnd);
+		log.info("unsuccesfull for ways:", cntwayNotFnd);
 		log.info("Location hook finished in", dt, "ms");
-
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonCutter.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonCutter.java
new file mode 100644
index 0000000..922a588
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonCutter.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.awt.geom.Area;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Queue;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation.JoinedWay;
+import uk.me.parabola.util.Java2DConverter;
+
+/**
+ * Methods to cut an MP-relation so that holes are connected with the outer way(s).
+ * Extracted from {@link MultiPolygonRelation}.
+ * 
+ * @author WanMil
+ * @author Gerd Petermann
+ *
+ */
+public class MultiPolygonCutter {
+	private static final Logger log = Logger.getLogger(MultiPolygonCutter.class);
+	private final MultiPolygonRelation rel;
+	private final Area tileArea;
+
+	/**
+	 * Create cutter for a given MP-relation and tile
+	 * @param multiPolygonRelation the MP-relation
+	 * @param tileArea the java area of the tile
+	 */
+	public MultiPolygonCutter(MultiPolygonRelation multiPolygonRelation, Area tileArea) {
+		rel = multiPolygonRelation;
+		this.tileArea = tileArea;
+	}
+
+	/**
+	 * Cut out all inner polygons from the outer polygon. This will divide the outer
+	 * polygon in several polygons.
+	 * @param multiPolygonRelation 
+	 * 
+	 * @param outerPolygon
+	 *            the outer polygon
+	 * @param innerPolygons
+	 *            a list of inner polygons
+	 * @return a list of polygons that make the outer polygon cut by the inner
+	 *         polygons
+	 */
+	public List<Way> cutOutInnerPolygons(Way outerPolygon, List<Way> innerPolygons) {
+		if (innerPolygons.isEmpty()) {
+			Way outerWay = new JoinedWay(outerPolygon);
+			if (log.isDebugEnabled()) {
+				log.debug("Way", outerPolygon.getId(), "splitted to way", outerWay.getId());
+			}
+			return Collections.singletonList(outerWay);
+		}
+
+		// use the java.awt.geom.Area class because it's a quick
+		// implementation of what's needed
+
+		// this list contains all non overlapping and singular areas
+		// of the outerPolygon
+		Queue<AreaCutData> areasToCut = new LinkedList<>();
+		Collection<Area> finishedAreas = new ArrayList<>(innerPolygons.size());
+		
+		// create a list of Area objects from the outerPolygon (clipped to the bounding box)
+		List<Area> outerAreas = createAreas(outerPolygon, true);
+		
+		// create the inner areas
+		List<Area> innerAreas = new ArrayList<>(innerPolygons.size()+2);
+		for (Way innerPolygon : innerPolygons) {
+			// don't need to clip to the bounding box because 
+			// these polygons are just used to cut out holes
+			innerAreas.addAll(createAreas(innerPolygon, false));
+		}
+
+		// initialize the cut data queue
+		if (innerAreas.isEmpty()) {
+			// this is a multipolygon without any inner areas
+			// nothing to cut
+			finishedAreas.addAll(outerAreas);
+		} else if (outerAreas.size() == 1) {
+			// there is one outer area only
+			// it is checked before that all inner areas are inside this outer area
+			AreaCutData initialCutData = new AreaCutData();
+			initialCutData.outerArea = outerAreas.get(0);
+			initialCutData.innerAreas = innerAreas;
+			areasToCut.add(initialCutData);
+		} else {
+			// multiple outer areas
+			for (Area outerArea : outerAreas) {
+				AreaCutData initialCutData = new AreaCutData();
+				initialCutData.outerArea = outerArea;
+				initialCutData.innerAreas = new ArrayList<>(innerAreas
+						.size());
+				for (Area innerArea : innerAreas) {
+					if (outerArea.getBounds2D().intersects(
+						innerArea.getBounds2D())) {
+						initialCutData.innerAreas.add(innerArea);
+					}
+				}
+				
+				if (initialCutData.innerAreas.isEmpty()) {
+					// this is either an error
+					// or the outer area has been cut into pieces on the tile bounds
+					finishedAreas.add(outerArea);
+				} else {
+					areasToCut.add(initialCutData);
+				}
+			}
+		}
+
+		while (!areasToCut.isEmpty()) {
+			AreaCutData areaCutData = areasToCut.poll();
+			CutPoint cutPoint = calcNextCutPoint(areaCutData);
+			
+			if (cutPoint == null) {
+				finishedAreas.add(areaCutData.outerArea);
+				continue;
+			}
+			
+			assert cutPoint.getNumberOfAreas() > 0 : "Number of cut areas == 0 in mp " + rel.getId();
+			
+			// cut out the holes
+			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;
+			} 
+			
+			// the inner areas of the cut point have been processed
+			// they are no longer needed
+			for (Area cutArea : cutPoint.getAreas()) {
+				ListIterator<Area> areaIter = areaCutData.innerAreas.listIterator();
+				while (areaIter.hasNext()) {
+					Area a = areaIter.next();
+					if (a == cutArea) {
+						areaIter.remove();
+						break;
+					}
+				}
+			}
+			// remove all does not seem to work. It removes more than the identical areas.
+//			areaCutData.innerAreas.removeAll(cutPoint.getAreas());
+
+			if (areaCutData.outerArea.isSingular()) {
+				// the area is singular
+				// => no further splits necessary
+				if (areaCutData.innerAreas.isEmpty()) {
+					// this area is finished and needs no further cutting
+					finishedAreas.add(areaCutData.outerArea);
+				} else {
+					// read this area to further processing
+					areasToCut.add(areaCutData);
+				}
+			} else {
+				// we need to cut the area into two halves to get singular areas
+				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 = 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));
+				} else {
+					ArrayList<Area> cuttedAreas = new ArrayList<>();
+					cuttedAreas.addAll(Java2DConverter.areaToSingularAreas(a1));
+					cuttedAreas.addAll(Java2DConverter.areaToSingularAreas(a2));
+					
+					for (Area nextOuterArea : cuttedAreas) {
+						ArrayList<Area> nextInnerAreas = null;
+						// go through all remaining inner areas and check if they
+						// must be further processed with the nextOuterArea 
+						for (Area nonProcessedInner : areaCutData.innerAreas) {
+							if (nextOuterArea.intersects(nonProcessedInner.getBounds2D())) {
+								if (nextInnerAreas == null) {
+									nextInnerAreas = new ArrayList<>();
+								}
+								nextInnerAreas.add(nonProcessedInner);
+							}
+						}
+						
+						if (nextInnerAreas == null || nextInnerAreas.isEmpty()) {
+							finishedAreas.add(nextOuterArea);
+						} else {
+							AreaCutData outCutData = new AreaCutData();
+							outCutData.outerArea = nextOuterArea;
+							outCutData.innerAreas= nextInnerAreas;
+							areasToCut.add(outCutData);
+						}
+					}
+				}
+			}
+			
+		}
+		
+		// convert the java.awt.geom.Area back to the mkgmap way
+		List<Way> cuttedOuterPolygon = new ArrayList<>(finishedAreas.size());
+		Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>();
+		for (Area area : finishedAreas) {
+			Way w = singularAreaToWay(area, rel.getOriginalId());
+			if (w != null) {
+				w.setFakeId();
+				// make sure that equal coords are changed to identical coord instances
+				// this allows merging in the ShapeMerger
+				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()) {
+					log.debug("Way", outerPolygon.getId(), "splitted to way", w.getId());
+				}
+			}
+		}
+
+		return cuttedOuterPolygon;
+	}
+	
+	private static CutPoint calcNextCutPoint(AreaCutData areaData) {
+		if (areaData.innerAreas == null || areaData.innerAreas.isEmpty()) {
+			return null;
+		}
+		
+		Rectangle2D outerBounds = areaData.outerArea.getBounds2D();
+		
+		if (areaData.innerAreas.size() == 1) {
+			// make it short if there is only one inner area
+			CutPoint cutPoint1 = new CutPoint(CoordinateAxis.LATITUDE, outerBounds);
+			cutPoint1.addArea(areaData.innerAreas.get(0));
+			CutPoint cutPoint2 = new CutPoint(CoordinateAxis.LONGITUDE, outerBounds);
+			cutPoint2.addArea(areaData.innerAreas.get(0));
+			if (cutPoint1.compareTo(cutPoint2) > 0) {
+				return cutPoint1;
+			} else {
+				return cutPoint2;
+			}
+			
+		}
+		
+		ArrayList<Area> innerStart = new ArrayList<>(areaData.innerAreas);
+		
+		// first try to cut out all polygons that intersect the boundaries of the outer polygon
+		// this has the advantage that the outer polygon need not be split into two halves
+		for (CoordinateAxis axis : CoordinateAxis.values()) {
+			CutPoint edgeCutPoint = new CutPoint(axis, outerBounds);
+
+			// 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.getStartHighPrec(anInnerStart) <= axis.getStartHighPrec(outerBounds)) {
+					// found a touching area
+					edgeCutPoint.addArea(anInnerStart);
+				} else {
+					break;
+				}
+			}
+			if (edgeCutPoint.getNumberOfAreas() > 0) {
+				// there at least one intersecting inner polygon
+				return edgeCutPoint;
+			}
+			
+			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.getStopHighPrec(anInnerStart) >= axis.getStopHighPrec(outerBounds)) {
+					// found a touching area
+					edgeCutPoint.addArea(anInnerStart);
+				} else {
+					break;
+				}
+			}
+			if (edgeCutPoint.getNumberOfAreas() > 0) {
+				// there at least one intersecting inner polygon
+				return edgeCutPoint;
+			}
+		}
+		
+		
+		ArrayList<CutPoint> bestCutPoints = new ArrayList<>(CoordinateAxis.values().length);
+		for (CoordinateAxis axis : CoordinateAxis.values()) {
+			CutPoint bestCutPoint = new CutPoint(axis, outerBounds);
+			CutPoint currentCutPoint = new CutPoint(axis, outerBounds);
+
+			Collections.sort(innerStart, (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START: COMP_LAT_START));
+
+			for (Area anInnerStart : innerStart) {
+				currentCutPoint.addArea(anInnerStart);
+
+				if (currentCutPoint.compareTo(bestCutPoint) > 0) {
+					bestCutPoint = currentCutPoint.duplicate();
+				}
+			}
+			bestCutPoints.add(bestCutPoint);
+		}
+
+		return Collections.max(bestCutPoints);
+		
+	}
+
+	/**
+	 * Create the areas that are enclosed by the way. Usually the result should
+	 * only be one area but some ways contain intersecting lines. To handle these
+	 * erroneous cases properly the method might return a list of areas.
+	 * 
+	 * @param w a closed way
+	 * @param clipBbox true if the areas should be clipped to the bounding box; false else
+	 * @return a list of enclosed ares
+	 */
+	private List<Area> createAreas(Way w, boolean clipBbox) {
+		Area area = Java2DConverter.createArea(w.getPoints());
+		if (clipBbox && !tileArea.contains(area.getBounds2D())) {
+			// the area intersects the bounding box => clip it
+			area.intersect(tileArea);
+		}
+		List<Area> areaList = Java2DConverter.areaToSingularAreas(area);
+		if (log.isDebugEnabled()) {
+			log.debug("Bbox clipped way",w.getId()+"=>",areaList.size(),"distinct area(s).");
+		}
+		return areaList;
+	}
+
+	/**
+	 * 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.
+	 * 
+	 * @param area
+	 *            the area
+	 * @param wayId
+	 *            the wayid for the new way
+	 * @return a new mkgmap way
+	 */
+	private Way singularAreaToWay(Area area, long wayId) {
+		List<Coord> points = Java2DConverter.singularAreaToPoints(area);
+		if (points == null || points.isEmpty()) {
+			if (log.isDebugEnabled()) {
+				log.debug("Empty area", wayId + ".", rel.toBrowseURL());
+			}
+			return null;
+		}
+
+		return new Way(wayId, points);
+	}
+	private static class AreaCutData {
+		Area outerArea;
+		List<Area> innerAreas;
+	}
+
+	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 startPoinHp = Integer.MAX_VALUE; // high precision map units
+		private int stopPointHp = Integer.MIN_VALUE;  // high precision map units
+		private Integer cutPointHp = null; // high precision map units
+		private final LinkedList<Area> areas;
+		private final Comparator<Area> comparator;
+		private final CoordinateAxis axis;
+		private Rectangle2D bounds;
+		private final Rectangle2D outerBounds;
+		private Double minAspectRatio;
+
+		public CutPoint(CoordinateAxis axis, Rectangle2D outerBounds) {
+			this.axis = axis;
+			this.outerBounds = outerBounds;
+			this.areas = new LinkedList<>();
+			this.comparator = (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_STOP : COMP_LAT_STOP);
+		}
+		
+		public CutPoint duplicate() {
+			CutPoint newCutPoint = new CutPoint(this.axis, this.outerBounds);
+			newCutPoint.areas.addAll(areas);
+			newCutPoint.startPoinHp = startPoinHp;
+			newCutPoint.stopPointHp = stopPointHp;
+			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 getCutPointHighPrec() % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD == 0;
+		}
+		
+		private boolean isBadCutPoint() {
+			int d1 = getCutPointHighPrec() - startPoinHp;
+			int d2 = stopPointHp - getCutPointHighPrec();
+			return Math.min(d1, d2) < CUT_POINT_CLASSIFICATION_BAD_THRESHOLD;
+		}
+		
+		private boolean isStartCut() {
+			return (startPoinHp <= axis.getStartHighPrec(outerBounds));
+		}
+		
+		private boolean isStopCut() {
+			return (stopPointHp >= axis.getStopHighPrec(outerBounds));
+		}
+		
+		/**
+		 * Calculates the point where the cut should be applied.
+		 * @return the point of cut
+		 */
+		private int getCutPointHighPrec() {
+			if (cutPointHp != null) {
+				// already calculated => just return it
+				return cutPointHp;
+			}
+			
+			if (startPoinHp == stopPointHp) {
+				// there is no choice => return the one possible point 
+				cutPointHp = startPoinHp;
+				return cutPointHp;
+			}
+			
+			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
+				cutPointHp = startPoinHp;
+				return cutPointHp;
+			}
+			
+			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
+				cutPointHp = startPoinHp;
+				return cutPointHp;
+			}
+			
+			// try to cut with a good aspect ratio so try the middle of the polygon to be cut
+			int midOuterHp = axis.getStartHighPrec(outerBounds)+(axis.getStopHighPrec(outerBounds) - axis.getStartHighPrec(outerBounds)) / 2;
+			cutPointHp = midOuterHp;
+
+			if (midOuterHp < startPoinHp) {
+				// not possible => the start point is greater than the middle so correct to the startPoint
+				cutPointHp = startPoinHp;
+				
+				if (((cutPointHp & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD) <= stopPointHp) {
+					cutPointHp = ((cutPointHp & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1)) + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD);
+				}
+				
+			} else if (midOuterHp > stopPointHp) {
+				// not possible => the stop point is smaller than the middle so correct to the stopPoint
+				cutPointHp = stopPointHp;
+
+				if ((cutPointHp & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1))  >= startPoinHp) {
+					cutPointHp = (cutPointHp & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1));
+				}
+			}
+			
+			
+			// 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 = cutPointHp % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD;
+			if (cutMod == 0) {
+				return cutPointHp;
+			}
+			
+			int cut1 = (cutMod > 0 ? cutPointHp-cutMod : cutPointHp  - CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD- cutMod);
+			if (cut1 >= startPoinHp && cut1 <= stopPointHp) {
+				cutPointHp = cut1;
+				return cutPointHp;
+			}
+			
+			int cut2 = (cutMod > 0 ? cutPointHp + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD -cutMod : cutPointHp - cutMod);
+			if (cut2 >= startPoinHp && cut2 <= stopPointHp) {
+				cutPointHp = cut2;
+				return cutPointHp;
+			}
+			
+			return cutPointHp;
+		}
+
+		public Rectangle2D getCutRectangleForArea(Area toCut, boolean firstRect) {
+			return getCutRectangleForArea(toCut.getBounds2D(), firstRect);
+		}
+		
+		public Rectangle2D getCutRectangleForArea(Rectangle2D areaRect, boolean firstRect) {
+			double cp = (double)  getCutPointHighPrec() / (1<<Coord.DELTA_SHIFT);
+			if (axis == CoordinateAxis.LONGITUDE) {
+				double newWidth = cp-areaRect.getX();
+				if (firstRect) {
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), newWidth, areaRect.getHeight()); 
+				} else {
+					return new Rectangle2D.Double(areaRect.getX()+newWidth, areaRect.getY(), areaRect.getWidth()-newWidth, areaRect.getHeight()); 
+				}
+			} else {
+				double newHeight = cp-areaRect.getY();
+				if (firstRect) {
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), areaRect.getWidth(), newHeight); 
+				} else {
+					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY()+newHeight, areaRect.getWidth(), areaRect.getHeight()-newHeight); 
+				}
+			}
+		}
+		
+		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.getStopHighPrec(areas.getFirst()) < axis.getStartHighPrec(area)) {
+				// remove the first area
+				areas.removeFirst();
+			}
+
+			areas.add(area);
+			Collections.sort(areas, comparator);
+			startPoinHp = axis.getStartHighPrec(Collections.max(areas,
+				(axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START
+						: COMP_LAT_START)));
+			stopPointHp = axis.getStopHighPrec(areas.getFirst());
+			
+			// reset the cached value => need to be recalculated the next time they are needed
+			bounds = null;
+			cutPointHp = null;
+			minAspectRatio = null;
+		}
+
+		public int getNumberOfAreas() {
+			return this.areas.size();
+		}
+
+		/**
+		 * Retrieves the minimum aspect ratio of the outer bounds after cutting.
+		 * 
+		 * @return minimum aspect ratio of outer bound after cutting
+		 */
+		public double getMinAspectRatio() {
+			if (minAspectRatio == null) {
+				// first get the left/upper cut
+				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
+				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);
+
+				// get the minimum
+				minAspectRatio = Math.min(ar1, ar2);
+			}
+			return minAspectRatio;
+		}
+		
+		public int compareTo(CutPoint o) {
+			if (this == o) {
+				return 0;
+			}
+			// prefer a cut at the boundaries
+			if (isStartCut() && o.isStartCut() == false) {
+				return 1;
+			} 
+			else if (isStartCut() == false && o.isStartCut()) {
+				return -1;
+			}
+			else if (isStopCut() && o.isStopCut() == false) {
+				return 1;
+			}
+			else if (isStopCut() == false && o.isStopCut()) {
+				return -1;
+			}
+			
+			// handle the special case that a cut has no area
+			if (getNumberOfAreas() == 0) {
+				if (o.getNumberOfAreas() == 0) {
+					return 0;
+				} else {
+					return -1;
+				}
+			} else if (o.getNumberOfAreas() == 0) {
+				return 1;
+			}
+			
+			if (isBadCutPoint() != o.isBadCutPoint()) {
+				if (isBadCutPoint()) {
+					return -1;
+				} else
+					return 1;
+			}
+			
+			double dAR = getMinAspectRatio() - o.getMinAspectRatio();
+			if (dAR != 0) {
+				return (dAR > 0 ? 1 : -1);
+			}
+			
+			if (isGoodCutPoint() != o.isGoodCutPoint()) {
+				if (isGoodCutPoint())
+					return 1;
+				else
+					return -1;
+			}
+			
+			// prefer the larger area that is split
+			double ss1 = axis.getSizeOfSide(getBounds2D());
+			double ss2 = o.axis.getSizeOfSide(o.getBounds2D());
+			if (ss1-ss2 != 0)
+				return Double.compare(ss1,ss2); 
+
+			int ndiff = getNumberOfAreas()-o.getNumberOfAreas();
+			return ndiff;
+
+		}
+
+		private Rectangle2D getBounds2D() {
+			if (bounds == null) {
+				// lazy init
+				bounds = new Rectangle2D.Double();
+				for (Area a : areas)
+					bounds.add(a.getBounds2D());
+			}
+			return bounds;
+		}
+
+		public String toString() {
+			return axis +" "+getNumberOfAreas()+" "+startPoinHp+" "+stopPointHp+" "+getCutPointHighPrec();
+		}
+	}
+
+	private static enum CoordinateAxis {
+		LATITUDE(false), LONGITUDE(true);
+
+		private CoordinateAxis(boolean useX) {
+			this.useX = useX;
+		}
+
+		private final boolean useX;
+
+		public int getStartHighPrec(Area area) {
+			return getStartHighPrec(area.getBounds2D());
+		}
+
+		public int getStartHighPrec(Rectangle2D rect) {
+			double val = (useX ? rect.getX() : rect.getY());
+			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
+		}
+
+		public int getStopHighPrec(Area area) {
+			return getStopHighPrec(area.getBounds2D());
+		}
+
+		public int getStopHighPrec(Rectangle2D rect) {
+			double val = (useX ? rect.getMaxX() : rect.getMaxY());
+			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
+		}
+		
+		public double getSizeOfSide(Rectangle2D rect) {
+			if (useX) {
+				int latHp = (int)Math.round(rect.getY() * (1<<Coord.DELTA_SHIFT));
+				Coord c1 = Coord.makeHighPrecCoord(latHp, getStartHighPrec(rect));
+				Coord c2 = Coord.makeHighPrecCoord(latHp, getStopHighPrec(rect));
+				return c1.distance(c2);
+			} else {
+				int lonHp = (int)Math.round(rect.getX() * (1<<Coord.DELTA_SHIFT));
+				Coord c1 = Coord.makeHighPrecCoord(getStartHighPrec(rect), lonHp);
+				Coord c2 = Coord.makeHighPrecCoord(getStopHighPrec(rect), lonHp);
+				return c1.distance(c2);
+			}
+		}
+	}
+	
+	private static final AreaComparator COMP_LONG_START = new AreaComparator(
+			true, CoordinateAxis.LONGITUDE);
+	private static final AreaComparator COMP_LONG_STOP = new AreaComparator(
+			false, CoordinateAxis.LONGITUDE);
+	private static final AreaComparator COMP_LAT_START = new AreaComparator(
+			true, CoordinateAxis.LATITUDE);
+	private static final AreaComparator COMP_LAT_STOP = new AreaComparator(
+			false, CoordinateAxis.LATITUDE);
+
+	private static class AreaComparator implements Comparator<Area> {
+
+		private final CoordinateAxis axis;
+		private final boolean startPoint;
+
+		public AreaComparator(boolean startPoint, CoordinateAxis axis) {
+			this.startPoint = startPoint;
+			this.axis = axis;
+		}
+
+		public int compare(Area o1, Area o2) {
+			if (o1 == o2) {
+				return 0;
+			}
+
+			if (startPoint) {
+				int cmp = axis.getStartHighPrec(o1) - axis.getStartHighPrec(o2);
+				if (cmp == 0) {
+					return axis.getStopHighPrec(o1) - axis.getStopHighPrec(o2);
+				} else {
+					return cmp;
+				}
+			} else {
+				int cmp = axis.getStopHighPrec(o1) - axis.getStopHighPrec(o2);
+				if (cmp == 0) {
+					return axis.getStartHighPrec(o1) - axis.getStartHighPrec(o2);
+				} else {
+					return cmp;
+				}
+			}
+		}
+
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
index a5c7ced..1e6d9c5 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
@@ -13,14 +13,10 @@
 
 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;
@@ -33,7 +29,6 @@ import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
@@ -67,9 +62,9 @@ public class MultiPolygonRelation extends Relation {
 	public static final String MP_CREATED_TAG = "mkgmap:mp_created";
 	
 	private final Map<Long, Way> tileWayMap;
-	private final Map<Long, String> roleMap = new HashMap<Long, String>();
+	private final Map<Long, String> roleMap = new HashMap<>();
  
-	private Map<Long, Way> mpPolygons = new LinkedHashMap<Long, Way>();
+	private Map<Long, Way> mpPolygons = new LinkedHashMap<>();
 	
 	
 	protected ArrayList<BitSet> containsMatrix;
@@ -82,8 +77,8 @@ public class MultiPolygonRelation extends Relation {
 	protected Set<Way> outerWaysForLineTagging;
 	protected Map<String, String> outerTags;
 
-	private final uk.me.parabola.imgfmt.app.Area bbox;
-	protected Area bboxArea;
+	private final uk.me.parabola.imgfmt.app.Area tileBounds;
+	private Area tileArea;
 	
 	private Coord cOfG = null;
 	
@@ -114,7 +109,9 @@ public class MultiPolygonRelation extends Relation {
 	public MultiPolygonRelation(Relation other, Map<Long, Way> wayMap,
 			uk.me.parabola.imgfmt.app.Area bbox) {
 		this.tileWayMap = wayMap;
-		this.bbox = bbox;
+		this.tileBounds = bbox;
+		// create an Area for the bbox to clip the polygons
+		tileArea = Java2DConverter.createBoundsArea(tileBounds); 
 
 		setId(other.getId());
 		copyTags(other);
@@ -183,8 +180,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @return <code>true</code> if tempWay way is (or could be) joined to
 	 *         joinWay
 	 */
-	private boolean joinWays(JoinedWay joinWay, JoinedWay tempWay,
-			boolean checkOnly) {
+	private static boolean joinWays(JoinedWay joinWay, JoinedWay tempWay, boolean checkOnly) {
 		boolean reverseTempWay = false;
 		int insIdx = -1;
 		int firstTmpIdx = 1;
@@ -227,7 +223,7 @@ public class MultiPolygonRelation extends Relation {
 			
 			if (reverseTempWay) {
 				// the remp coords need to be reversed so copy the list
-				tempCoords = new ArrayList<Coord>(tempCoords);
+				tempCoords = new ArrayList<>(tempCoords);
 				// and reverse it
 				Collections.reverse(tempCoords);
 			}
@@ -249,14 +245,14 @@ public class MultiPolygonRelation extends Relation {
 		// TODO check if the closed polygon is valid and implement a
 		// backtracking algorithm to get other combinations
 
-		ArrayList<JoinedWay> joinedWays = new ArrayList<JoinedWay>();
+		ArrayList<JoinedWay> joinedWays = new ArrayList<>();
 		if (segments == null || segments.isEmpty()) {
 			return joinedWays;
 		}
 
 		// go through all segments and categorize them to closed and unclosed
 		// list
-		ArrayList<JoinedWay> unclosedWays = new ArrayList<JoinedWay>();
+		ArrayList<JoinedWay> unclosedWays = new ArrayList<>();
 		for (Way orgSegment : segments) {
 			JoinedWay jw = new JoinedWay(orgSegment);
 			roleMap.put(jw.getId(), getRole(orgSegment));
@@ -384,7 +380,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @param wayList
 	 *            a list of ways
 	 */
-	protected void closeWays(ArrayList<JoinedWay> wayList) {
+	protected void closeWays(ArrayList<JoinedWay> wayList, double maxCloseDist) {
 		for (JoinedWay way : wayList) {
 			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
 				continue;
@@ -392,19 +388,19 @@ public class MultiPolygonRelation extends Relation {
 			Coord p1 = way.getPoints().get(0);
 			Coord p2 = way.getPoints().get(way.getPoints().size() - 1);
 
-			if (bbox.insideBoundary(p1) == false
-					&& bbox.insideBoundary(p2) == false) {
+			if (tileBounds.insideBoundary(p1) == false
+					&& tileBounds.insideBoundary(p2) == false) {
 				// both points lie outside the bbox or on the bbox
 
 				// check if both points are on the same side of the bounding box
-				if ((p1.getLatitude() <= bbox.getMinLat() && p2.getLatitude() <= bbox
+				if ((p1.getLatitude() <= tileBounds.getMinLat() && p2.getLatitude() <= tileBounds
 						.getMinLat())
-						|| (p1.getLatitude() >= bbox.getMaxLat() && p2
-								.getLatitude() >= bbox.getMaxLat())
-						|| (p1.getLongitude() <= bbox.getMinLong() && p2
-								.getLongitude() <= bbox.getMinLong())
-						|| (p1.getLongitude() >= bbox.getMaxLong() && p2
-								.getLongitude() >= bbox.getMaxLong())) {
+						|| (p1.getLatitude() >= tileBounds.getMaxLat() && p2
+								.getLatitude() >= tileBounds.getMaxLat())
+						|| (p1.getLongitude() <= tileBounds.getMinLong() && p2
+								.getLongitude() <= tileBounds.getMinLong())
+						|| (p1.getLongitude() >= tileBounds.getMaxLong() && p2
+								.getLongitude() >= tileBounds.getMaxLong())) {
 					// they are on the same side outside of the bbox
 					// so just close them without worrying about if
 					// they intersect itself because the intersection also
@@ -439,16 +435,21 @@ public class MultiPolygonRelation extends Relation {
 
 			if (!intersects) {
 				// close the polygon
-				// the new way segment does not intersect the rest of the
-				// polygon
-				if (log.isInfoEnabled()){
+				// the new way segment does not intersect the rest of the polygon
+				boolean doClose = true;
+				if (maxCloseDist > 0) {
+					// calc the distance to close
+					double closeDist = way.getPoints().get(0).distance(way.getPoints().get(way.getPoints().size()-1));
+					doClose = closeDist < maxCloseDist;
+				}
+				if (doClose) {
 					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();
+					// mark this ways as artificially closed
+					way.closeWayArtificially();
+				}
 			}
 		}
 	}
@@ -469,7 +470,7 @@ public class MultiPolygonRelation extends Relation {
 	}
 	
 	protected boolean connectUnclosedWays(List<JoinedWay> allWays) {
-		List<JoinedWay> unclosed = new ArrayList<JoinedWay>();
+		List<JoinedWay> unclosed = new ArrayList<>();
 
 		for (JoinedWay w : allWays) {
 			if (w.hasIdenticalEndPoints() == false) {
@@ -479,18 +480,18 @@ public class MultiPolygonRelation extends Relation {
 		// 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 IdentityHashMap<Coord, JoinedWay>();
+			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>();
 			
 			// check all ways for endpoints outside or on the bbox
 			for (JoinedWay w : unclosed) {
 				Coord c1 = w.getPoints().get(0);
-				if (bbox.insideBoundary(c1)==false) {
+				Coord c2 = w.getPoints().get(w.getPoints().size()-1);
+				if (tileBounds.insideBoundary(c1)==false) {
 					log.debug("Point",c1,"of way",w.getId(),"outside bbox");
 					outOfBboxPoints.put(c1, w);
 				}
 
-				Coord c2 = w.getPoints().get(w.getPoints().size()-1);
-				if (bbox.insideBoundary(c2)==false) {
+				if (tileBounds.insideBoundary(c2)==false) {
 					log.debug("Point",c2,"of way",w.getId(),"outside bbox");
 					outOfBboxPoints.put(c2, w);
 				}
@@ -501,8 +502,8 @@ public class MultiPolygonRelation extends Relation {
 				return false;
 			}
 			
-			List<ConnectionData> coordPairs = new ArrayList<ConnectionData>();
-			ArrayList<Coord> coords = new ArrayList<Coord>(outOfBboxPoints.keySet());
+			List<ConnectionData> coordPairs = new ArrayList<>();
+			ArrayList<Coord> coords = new ArrayList<>(outOfBboxPoints.keySet());
 			for (int i = 0; i < coords.size(); i++) {
 				for (int j = i + 1; j < coords.size(); j++) {
 					ConnectionData cd = new ConnectionData();
@@ -596,7 +597,7 @@ public class MultiPolygonRelation extends Relation {
 			JoinedWay tempWay = it.next();
 			if (!tempWay.hasIdenticalEndPoints()) {
 				// warn only if the way intersects the bounding box 
-				boolean inBbox = tempWay.intersects(bbox);
+				boolean inBbox = tempWay.intersects(tileBounds);
 				if (inBbox) {
 					if (firstWarn) {
 						log.warn(
@@ -633,7 +634,7 @@ public class MultiPolygonRelation extends Relation {
 			boolean remove = true;
 			// check all points
 			for (Coord c : w.getPoints()) {
-				if (bbox.contains(c)) {
+				if (tileBounds.contains(c)) {
 					// if one point is in the bounding box the way should not be removed
 					remove = false;
 					break;
@@ -642,7 +643,7 @@ public class MultiPolygonRelation extends Relation {
 
 			if (remove) {
 				// check if the polygon contains the complete bounding box
-				if (w.getBounds().contains(bboxArea.getBounds())) {
+				if (w.getBounds().contains(tileArea.getBounds())) {
 					remove = false;
 				}
 			}
@@ -712,7 +713,7 @@ public class MultiPolygonRelation extends Relation {
 
 	protected ArrayList<PolygonStatus> getPolygonStatus(BitSet outmostPolygons,
 			String defaultRole) {
-		ArrayList<PolygonStatus> polygonStatusList = new ArrayList<PolygonStatus>();
+		ArrayList<PolygonStatus> polygonStatusList = new ArrayList<>();
 		for (int polyIndex = outmostPolygons.nextSetBit(0); polyIndex >= 0; polyIndex = outmostPolygons
 				.nextSetBit(polyIndex + 1)) {
 			// polyIndex is the polygon that is not contained by any other
@@ -744,7 +745,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @return all source ways
 	 */
 	protected List<Way> getSourceWays() {
-		ArrayList<Way> allWays = new ArrayList<Way>();
+		ArrayList<Way> allWays = new ArrayList<>();
 
 		for (Map.Entry<String, Element> r_e : getElements()) {
 			if (r_e.getValue() instanceof Way) {
@@ -788,48 +789,41 @@ public class MultiPolygonRelation extends Relation {
 		}
 
 		
-		// create an Area for the bbox to clip the polygons
-		bboxArea = Java2DConverter.createBoundsArea(getBbox()); 
-
 		// join all single ways to polygons, try to close ways and remove non closed ways 
 		polygons = joinWays(allWays);
 		
-		outerWaysForLineTagging = new HashSet<Way>();
-		outerTags = new HashMap<String,String>();
+		outerWaysForLineTagging = new HashSet<>();
+		outerTags = new HashMap<>();
 		
-		closeWays(polygons);
-
-		while (connectUnclosedWays(polygons)) {
-			closeWays(polygons);
-		}
+		do {
+			closeWays(polygons, getMaxCloseDist());
+		} while (connectUnclosedWays(polygons));
 
 		removeUnclosedWays(polygons);
 
 		// now only closed ways are left => polygons only
 
 		// check if we have at least one polygon left
-		if (polygons.isEmpty()) {
-			// do nothing
-			log.info("Multipolygon " + toBrowseURL()
-					+ " does not contain a closed polygon.");
-			tagOuterWays();
-			cleanup();
-			return;
-		}
+		boolean hasPolygons = !polygons.isEmpty();
 
 		removeWaysOutsideBbox(polygons);
 
 		if (polygons.isEmpty()) {
 			// do nothing
-			log.info("Multipolygon", toBrowseURL(),
-					 "is completely outside the bounding box. It is not processed.");
+			if (log.isInfoEnabled()) {
+				if (hasPolygons)
+					log.info("Multipolygon", toBrowseURL(),
+							"is completely outside the bounding box. It is not processed.");
+				else
+					log.info("Multipolygon " + toBrowseURL() + " does not contain a closed polygon.");
+			}
 			tagOuterWays();
 			cleanup();
 			return;
 		}
 
 		// the intersectingPolygons marks all intersecting/overlapping polygons
-		intersectingPolygons = new HashSet<JoinedWay>();
+		intersectingPolygons = new HashSet<>();
 		
 		// check which polygons lie inside which other polygon 
 		createContainsMatrix(polygons);
@@ -870,7 +864,7 @@ public class MultiPolygonRelation extends Relation {
 			return;
 		}
 
-		Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<PolygonStatus>();
+		Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<>();
 		BitSet nestedOuterPolygons = new BitSet();
 		BitSet nestedInnerPolygons = new BitSet();
 
@@ -993,13 +987,13 @@ public class MultiPolygonRelation extends Relation {
 					singularOuterPolygons = Collections
 							.singletonList((Way) new JoinedWay(currentPolygon.polygon));
 				} else {
-					List<Way> innerWays = new ArrayList<Way>(holes.size());
+					List<Way> innerWays = new ArrayList<>(holes.size());
 					for (PolygonStatus polygonHoleStatus : holes) {
 						innerWays.add(polygonHoleStatus.polygon);
 					}
 
-					singularOuterPolygons = cutOutInnerPolygons(currentPolygon.polygon,
-						innerWays);
+					MultiPolygonCutter cutter = new MultiPolygonCutter(this, tileArea);
+					singularOuterPolygons = cutter.cutOutInnerPolygons(currentPolygon.polygon, innerWays);
 				}
 				
 				if (singularOuterPolygons.isEmpty()==false) {
@@ -1131,6 +1125,11 @@ public class MultiPolygonRelation extends Relation {
 		cleanup();
 	}
 	
+	protected double getMaxCloseDist() {
+		return -1; // 
+	}
+
+
 	protected void postProcessing() {
 		
 		if (isAreaSizeCalculated()) {
@@ -1183,7 +1182,7 @@ public class MultiPolygonRelation extends Relation {
 
 			boolean outOfBbox = false;
 			for (Coord c : polygon.getPoints()) {
-				if (!bbox.contains(c)) {
+				if (!tileBounds.contains(c)) {
 					outOfBbox = true;
 					oneOufOfBbox = true;
 					break;
@@ -1285,7 +1284,7 @@ public class MultiPolygonRelation extends Relation {
 		roleMap.clear();
 		containsMatrix = null;
 		polygons = null;
-		bboxArea = null;
+		tileArea = null;
 		intersectingPolygons = null;
 		outerWaysForLineTagging = null;
 		outerTags = null;
@@ -1299,341 +1298,6 @@ public class MultiPolygonRelation extends Relation {
 		largestOuterPolygon = null;
 	}
 
-	private CutPoint calcNextCutPoint(AreaCutData areaData) {
-		if (areaData.innerAreas == null || areaData.innerAreas.isEmpty()) {
-			return null;
-		}
-		
-		Rectangle2D outerBounds = areaData.outerArea.getBounds2D();
-		
-		if (areaData.innerAreas.size() == 1) {
-			// make it short if there is only one inner area
-			CutPoint cutPoint1 = new CutPoint(CoordinateAxis.LATITUDE, outerBounds);
-			cutPoint1.addArea(areaData.innerAreas.get(0));
-			CutPoint cutPoint2 = new CutPoint(CoordinateAxis.LONGITUDE, outerBounds);
-			cutPoint2.addArea(areaData.innerAreas.get(0));
-			if (cutPoint1.compareTo(cutPoint2) > 0) {
-				return cutPoint1;
-			} else {
-				return cutPoint2;
-			}
-			
-		}
-		
-		ArrayList<Area> innerStart = new ArrayList<Area>(areaData.innerAreas);
-		
-		// first try to cut out all polygons that intersect the boundaries of the outer polygon
-		// this has the advantage that the outer polygon need not be split into two halves
-		for (CoordinateAxis axis : CoordinateAxis.values()) {
-			CutPoint edgeCutPoint = new CutPoint(axis, outerBounds);
-
-			// 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.getStart30(anInnerStart) <= axis.getStart30(outerBounds)) {
-					// found a touching area
-					edgeCutPoint.addArea(anInnerStart);
-				} else {
-					break;
-				}
-			}
-			if (edgeCutPoint.getNumberOfAreas() > 0) {
-				// there at least one intersecting inner polygon
-				return edgeCutPoint;
-			}
-			
-			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.getStop30(anInnerStart) >= axis.getStop30(outerBounds)) {
-					// found a touching area
-					edgeCutPoint.addArea(anInnerStart);
-				} else {
-					break;
-				}
-			}
-			if (edgeCutPoint.getNumberOfAreas() > 0) {
-				// there at least one intersecting inner polygon
-				return edgeCutPoint;
-			}
-		}
-		
-		
-		ArrayList<CutPoint> bestCutPoints = new ArrayList<CutPoint>(CoordinateAxis.values().length);
-		for (CoordinateAxis axis : CoordinateAxis.values()) {
-			CutPoint bestCutPoint = new CutPoint(axis, outerBounds);
-			CutPoint currentCutPoint = new CutPoint(axis, outerBounds);
-
-			Collections.sort(innerStart, (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START: COMP_LAT_START));
-
-			for (Area anInnerStart : innerStart) {
-				currentCutPoint.addArea(anInnerStart);
-
-				if (currentCutPoint.compareTo(bestCutPoint) > 0) {
-					bestCutPoint = currentCutPoint.duplicate();
-				}
-			}
-			bestCutPoints.add(bestCutPoint);
-		}
-
-		return Collections.max(bestCutPoints);
-		
-	}
-
-	/**
-	 * Cut out all inner polygons from the outer polygon. This will divide the outer
-	 * polygon in several polygons.
-	 * 
-	 * @param outerPolygon
-	 *            the outer polygon
-	 * @param innerPolygons
-	 *            a list of inner polygons
-	 * @return a list of polygons that make the outer polygon cut by the inner
-	 *         polygons
-	 */
-	private List<Way> cutOutInnerPolygons(Way outerPolygon, List<Way> innerPolygons) {
-		if (innerPolygons.isEmpty()) {
-			Way outerWay = new JoinedWay(outerPolygon);
-			if (log.isDebugEnabled()) {
-				log.debug("Way", outerPolygon.getId(), "splitted to way", outerWay.getId());
-			}
-			return Collections.singletonList(outerWay);
-		}
-
-		// use the java.awt.geom.Area class because it's a quick
-		// implementation of what's needed
-
-		// this list contains all non overlapping and singular areas
-		// of the outerPolygon
-		Queue<AreaCutData> areasToCut = new LinkedList<AreaCutData>();
-		Collection<Area> finishedAreas = new ArrayList<Area>(innerPolygons.size());
-		
-		// create a list of Area objects from the outerPolygon (clipped to the bounding box)
-		List<Area> outerAreas = createAreas(outerPolygon, true);
-		
-		// create the inner areas
-		List<Area> innerAreas = new ArrayList<Area>(innerPolygons.size()+2);
-		for (Way innerPolygon : innerPolygons) {
-			// don't need to clip to the bounding box because 
-			// these polygons are just used to cut out holes
-			innerAreas.addAll(createAreas(innerPolygon, false));
-		}
-
-		// initialize the cut data queue
-		if (innerAreas.isEmpty()) {
-			// this is a multipolygon without any inner areas
-			// nothing to cut
-			finishedAreas.addAll(outerAreas);
-		} else if (outerAreas.size() == 1) {
-			// there is one outer area only
-			// it is checked before that all inner areas are inside this outer area
-			AreaCutData initialCutData = new AreaCutData();
-			initialCutData.outerArea = outerAreas.get(0);
-			initialCutData.innerAreas = innerAreas;
-			areasToCut.add(initialCutData);
-		} else {
-			// multiple outer areas
-			for (Area outerArea : outerAreas) {
-				AreaCutData initialCutData = new AreaCutData();
-				initialCutData.outerArea = outerArea;
-				initialCutData.innerAreas = new ArrayList<Area>(innerAreas
-						.size());
-				for (Area innerArea : innerAreas) {
-					if (outerArea.getBounds2D().intersects(
-						innerArea.getBounds2D())) {
-						initialCutData.innerAreas.add(innerArea);
-					}
-				}
-				
-				if (initialCutData.innerAreas.isEmpty()) {
-					// this is either an error
-					// or the outer area has been cut into pieces on the tile bounds
-					finishedAreas.add(outerArea);
-				} else {
-					areasToCut.add(initialCutData);
-				}
-			}
-		}
-
-		while (!areasToCut.isEmpty()) {
-			AreaCutData areaCutData = areasToCut.poll();
-			CutPoint cutPoint = calcNextCutPoint(areaCutData);
-			
-			if (cutPoint == null) {
-				finishedAreas.add(areaCutData.outerArea);
-				continue;
-			}
-			
-			assert cutPoint.getNumberOfAreas() > 0 : "Number of cut areas == 0 in mp "+getId();
-			
-			// cut out the holes
-			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;
-			} 
-			
-			// the inner areas of the cut point have been processed
-			// they are no longer needed
-			for (Area cutArea : cutPoint.getAreas()) {
-				ListIterator<Area> areaIter = areaCutData.innerAreas.listIterator();
-				while (areaIter.hasNext()) {
-					Area a = areaIter.next();
-					if (a == cutArea) {
-						areaIter.remove();
-						break;
-					}
-				}
-			}
-			// remove all does not seem to work. It removes more than the identical areas.
-//			areaCutData.innerAreas.removeAll(cutPoint.getAreas());
-
-			if (areaCutData.outerArea.isSingular()) {
-				// the area is singular
-				// => no further splits necessary
-				if (areaCutData.innerAreas.isEmpty()) {
-					// this area is finished and needs no further cutting
-					finishedAreas.add(areaCutData.outerArea);
-				} else {
-					// read this area to further processing
-					areasToCut.add(areaCutData);
-				}
-			} else {
-				// we need to cut the area into two halves to get singular areas
-				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 = 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));
-				} else {
-					ArrayList<Area> cuttedAreas = new ArrayList<Area>();
-					cuttedAreas.addAll(Java2DConverter.areaToSingularAreas(a1));
-					cuttedAreas.addAll(Java2DConverter.areaToSingularAreas(a2));
-					
-					for (Area nextOuterArea : cuttedAreas) {
-						ArrayList<Area> nextInnerAreas = null;
-						// go through all remaining inner areas and check if they
-						// must be further processed with the nextOuterArea 
-						for (Area nonProcessedInner : areaCutData.innerAreas) {
-							if (nextOuterArea.intersects(nonProcessedInner.getBounds2D())) {
-								if (nextInnerAreas == null) {
-									nextInnerAreas = new ArrayList<Area>();
-								}
-								nextInnerAreas.add(nonProcessedInner);
-							}
-						}
-						
-						if (nextInnerAreas == null || nextInnerAreas.isEmpty()) {
-							finishedAreas.add(nextOuterArea);
-						} else {
-							AreaCutData outCutData = new AreaCutData();
-							outCutData.outerArea = nextOuterArea;
-							outCutData.innerAreas= nextInnerAreas;
-							areasToCut.add(outCutData);
-						}
-					}
-				}
-			}
-			
-		}
-		
-		// 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, getOriginalId());
-			if (w != null) {
-				w.setFakeId();
-				// 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()) {
-					log.debug("Way", outerPolygon.getId(), "splitted to way", w.getId());
-				}
-			}
-		}
-
-		return cuttedOuterPolygon;
-	}
-
-	/**
-	 * Create the areas that are enclosed by the way. Usually the result should
-	 * only be one area but some ways contain intersecting lines. To handle these
-	 * erroneous cases properly the method might return a list of areas.
-	 * 
-	 * @param w a closed way
-	 * @param clipBbox true if the areas should be clipped to the bounding box; false else
-	 * @return a list of enclosed ares
-	 */
-	private List<Area> createAreas(Way w, boolean clipBbox) {
-		Area area = Java2DConverter.createArea(w.getPoints());
-		if (clipBbox && !bboxArea.contains(area.getBounds2D())) {
-			// the area intersects the bounding box => clip it
-			area.intersect(bboxArea);
-		}
-		List<Area> areaList = Java2DConverter.areaToSingularAreas(area);
-		if (log.isDebugEnabled()) {
-			log.debug("Bbox clipped way",w.getId()+"=>",areaList.size(),"distinct area(s).");
-		}
-		return areaList;
-	}
-
-	/**
-	 * 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.
-	 * 
-	 * @param area
-	 *            the area
-	 * @param wayId
-	 *            the wayid for the new way
-	 * @return a new mkgmap way
-	 */
-	private Way singularAreaToWay(Area area, long wayId) {
-		List<Coord> points = Java2DConverter.singularAreaToPoints(area);
-		if (points == null || points.isEmpty()) {
-			if (log.isDebugEnabled()) {
-				log.debug("Empty area", wayId + ".", toBrowseURL());
-			}
-			return null;
-		}
-
-		return new Way(wayId, points);
-	}
-
 	/**
 	 * Retrieves if the given element contains tags that may be relevant
 	 * for style processing. If it has no relevant tag it will probably be 
@@ -1695,7 +1359,7 @@ public class MultiPolygonRelation extends Relation {
 	 *            a list of polygons
 	 */
 	protected void createContainsMatrix(List<JoinedWay> polygonList) {
-		containsMatrix = new ArrayList<BitSet>();
+		containsMatrix = new ArrayList<>();
 		for (int i = 0; i < polygonList.size(); i++) {
 			containsMatrix.add(new BitSet());
 		}
@@ -1707,7 +1371,7 @@ public class MultiPolygonRelation extends Relation {
 
 		// use this matrix to check which matrix element has been
 		// calculated
-		ArrayList<BitSet> finishedMatrix = new ArrayList<BitSet>(polygonList
+		ArrayList<BitSet> finishedMatrix = new ArrayList<>(polygonList
 				.size());
 
 		for (int i = 0; i < polygonList.size(); i++) {
@@ -1853,7 +1517,7 @@ public class MultiPolygonRelation extends Relation {
 					allOnLine = false;
 					break;
 				}
-			} else if (bbox.contains(px)) {
+			} else if (tileBounds.contains(px)) {
 				// we have to check if the point is on one line of the polygon1
 				
 				if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
@@ -1869,7 +1533,7 @@ public class MultiPolygonRelation extends Relation {
 			onePointContained = false;
 			// all points of polygon2 lie on lines of polygon1
 			// => the middle of each line polygon must NOT lie outside polygon1
-			ArrayList<Coord> middlePoints2 = new ArrayList<Coord>(polygon2.getPoints().size());
+			ArrayList<Coord> middlePoints2 = new ArrayList<>(polygon2.getPoints().size());
 			Coord p1 = null;
 			for (Coord p2 : polygon2.getPoints()) {
 				if (p1 != null) {
@@ -1885,7 +1549,7 @@ public class MultiPolygonRelation extends Relation {
 					// box => polygon1 may contain polygon2
 					onePointContained = true;
 					break;
-				} else if (bbox.contains(px)) {
+				} else if (tileBounds.contains(px)) {
 					// we have to check if the point is on one line of the polygon1
 					
 					if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
@@ -1964,7 +1628,7 @@ public class MultiPolygonRelation extends Relation {
 						|| (prevLatField == 0 && prevLonField == 0);
 
 				boolean intersects = intersectionPossible
-					&& linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
+					&& Utils.linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
 				
 				if (intersects) {
 					if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
@@ -1974,10 +1638,10 @@ public class MultiPolygonRelation extends Relation {
 						// closing segment causes the intersection
 						log.info("Polygon", polygon1, "may contain polygon", polygon2,
 							". Ignoring artificial generated intersection.");
-					} else if ((!bbox.contains(p1_1))
-							|| (!bbox.contains(p1_2))
-							|| (!bbox.contains(p2_1))
-							|| (!bbox.contains(p2_2))) {
+					} else if ((!tileBounds.contains(p1_1))
+							|| (!tileBounds.contains(p1_2))
+							|| (!tileBounds.contains(p2_1))
+							|| (!tileBounds.contains(p2_2))) {
 						// at least one point is outside the bounding box
 						// we ignore the intersection because the ways may not
 						// be complete
@@ -2010,7 +1674,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @param points a list of points; all consecutive points are handled as lines
 	 * @return true if p is located on one line given by points
 	 */
-	private boolean locatedOnLine(Coord p, List<Coord> points) {
+	private static boolean locatedOnLine(Coord p, List<Coord> points) {
 		Coord cp1 = null;
 		for (Coord cp2 : points) {
 			if (p.highPrecEquals(cp2)) { 
@@ -2053,65 +1717,22 @@ public class MultiPolygonRelation extends Relation {
 	}
 
 	private boolean lineCutsBbox(Coord p1_1, Coord p1_2) {
-		Coord nw = new Coord(bbox.getMaxLat(), bbox.getMinLong());
-		Coord sw = new Coord(bbox.getMinLat(), bbox.getMinLong());
-		Coord se = new Coord(bbox.getMinLat(), bbox.getMaxLong());
-		Coord ne = new Coord(bbox.getMaxLat(), bbox.getMaxLong());
-		return linesCutEachOther(nw, sw, p1_1, p1_2)
-				|| linesCutEachOther(sw, se, p1_1, p1_2)
-				|| linesCutEachOther(se, ne, p1_1, p1_2)
-				|| linesCutEachOther(ne, nw, p1_1, p1_2);
+		Coord nw = new Coord(tileBounds.getMaxLat(), tileBounds.getMinLong());
+		Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong());
+		Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong());
+		Coord ne = new Coord(tileBounds.getMaxLat(), tileBounds.getMaxLong());
+		return Utils.linesCutEachOther(nw, sw, p1_1, p1_2)
+				|| Utils.linesCutEachOther(sw, se, p1_1, p1_2)
+				|| Utils.linesCutEachOther(se, ne, p1_1, p1_2)
+				|| Utils.linesCutEachOther(ne, nw, p1_1, p1_2);
 	}
 
-	/**
-	 * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa.
-	 * This is a form of intersection check where it is allowed that one line ends on the
-	 * other line or that the two lines overlap.
-	 * @param p1_1 first point of line 1
-	 * @param p1_2 second point of line 1
-	 * @param p2_1 first point of line 2
-	 * @param p2_2 second point of line 2
-	 * @return true if both lines intersect somewhere in the middle of each other
-	 */
-	private boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1,
-			Coord p2_2) {
-		int width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon();
-		int width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon();
-
-		int height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat();
-		int height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat();
-
-		int denominator = ((height2 * width1) - (width2 * height1));
-		if (denominator == 0) {
-			// the lines are parallel
-			// they might overlap but this is ok for this test
-			return false;
-		}
-		
-		int x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon();
-		int y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat();
-
-		double isx = (double)((width2 * y1My3) - (height2 * x1Mx3))
-				/ denominator;
-		if (isx < 0 || isx > 1) {
-			return false;
-		}
-		
-		double isy = (double)((width1 * y1My3) - (height1 * x1Mx3))
-				/ denominator;
-
-		if (isy < 0 || isy > 1) {
-			return false;
-		} 
-
-		return true;
-	}
 
 	private List<JoinedWay> getWaysFromPolygonList(BitSet selection) {
 		if (selection.isEmpty()) {
 			return Collections.emptyList();
 		}
-		List<JoinedWay> wayList = new ArrayList<JoinedWay>(selection
+		List<JoinedWay> wayList = new ArrayList<>(selection
 				.cardinality());
 		for (int i = selection.nextSetBit(0); i >= 0; i = selection.nextSetBit(i + 1)) {
 			wayList.add(polygons.get(i));
@@ -2119,7 +1740,7 @@ public class MultiPolygonRelation extends Relation {
 		return wayList;
 	}
 
-	private void logWayURLs(Level level, String preMsg, Way way) {
+	private static void logWayURLs(Level level, String preMsg, Way way) {
 		if (log.isLoggable(level)) {
 			if (way instanceof JoinedWay) {
 				if (((JoinedWay) way).getOriginalWays().isEmpty()) {
@@ -2193,7 +1814,7 @@ public class MultiPolygonRelation extends Relation {
 	protected void tagOuterWays() {
 		Map<String, String> tags;
 		if (hasStyleRelevantTags(this)) {
-			tags = new HashMap<String, String>();
+			tags = new HashMap<>();
 			for (Entry<String, String> relTag : getTagEntryIterator()) {
 				tags.put(relTag.getKey(), relTag.getValue());
 			}
@@ -2329,8 +1950,8 @@ public class MultiPolygonRelation extends Relation {
 		return mpPolygons;
 	}
 
-	protected uk.me.parabola.imgfmt.app.Area getBbox() {
-		return bbox;
+	protected uk.me.parabola.imgfmt.app.Area getTileBounds() {
+		return tileBounds;
 	}
 	
 	/**
@@ -2378,7 +1999,7 @@ public class MultiPolygonRelation extends Relation {
 		public JoinedWay(Way originalWay) {
 			super(originalWay.getOriginalId(), originalWay.getPoints());
 			setFakeId();
-			originalWays = new ArrayList<Way>();
+			originalWays = new ArrayList<>();
 			addWay(originalWay);
 
 			// we have to initialize the min/max values
@@ -2490,7 +2111,7 @@ public class MultiPolygonRelation extends Relation {
 		}
 
 		public static Map<String,String> getMergedTags(Collection<Way> ways) {
-			Map<String,String> mergedTags = new HashMap<String, String>();
+			Map<String,String> mergedTags = new HashMap<>();
 			boolean first = true;
 			for (Way way : ways) {
 				if (first) {
@@ -2508,7 +2129,7 @@ public class MultiPolygonRelation extends Relation {
 							// the tags are different
 							if (wayTagValue!= null) {
 								if (tagsToRemove == null) {
-									tagsToRemove=new ArrayList<String>();
+									tagsToRemove=new ArrayList<>();
 								}
 								tagsToRemove.add(tag.getKey());
 							}
@@ -2591,367 +2212,4 @@ public class MultiPolygonRelation extends Relation {
 			return polygon+"_"+outer;
 		}
 	}
-
-	private static class AreaCutData {
-		Area outerArea;
-		List<Area> innerAreas;
-	}
-
-	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 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 Rectangle2D bounds;
-		private final Rectangle2D outerBounds;
-		private Double minAspectRatio;
-
-		public CutPoint(CoordinateAxis axis, Rectangle2D outerBounds) {
-			this.axis = axis;
-			this.outerBounds = outerBounds;
-			this.areas = new LinkedList<Area>();
-			this.comparator = (axis == CoordinateAxis.LONGITUDE ? COMP_LONG_STOP : COMP_LAT_STOP);
-		}
-		
-		public CutPoint duplicate() {
-			CutPoint newCutPoint = new CutPoint(this.axis, this.outerBounds);
-			newCutPoint.areas.addAll(areas);
-			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 getCutPoint30() % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD == 0;
-		}
-		
-		private boolean isBadCutPoint() {
-			int d1 = getCutPoint30() - startPoint30;
-			int d2 = stopPoint30 - getCutPoint30();
-			return Math.min(d1, d2) < CUT_POINT_CLASSIFICATION_BAD_THRESHOLD;
-		}
-		
-		private boolean isStartCut() {
-			return (startPoint30 <= axis.getStart30(outerBounds));
-		}
-		
-		private boolean isStopCut() {
-			return (stopPoint30 >= axis.getStop30(outerBounds));
-		}
-		
-		/**
-		 * Calculates the point where the cut should be applied.
-		 * @return the point of cut
-		 */
-		private int getCutPoint30() {
-			if (cutPoint30 != null) {
-				// already calculated => just return it
-				return cutPoint30;
-			}
-			
-			if (startPoint30 == stopPoint30) {
-				// there is no choice => return the one possible point 
-				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
-				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
-				cutPoint30 = startPoint30;
-				return cutPoint30;
-			}
-			
-			// try to cut with a good aspect ratio so try the middle of the polygon to be cut
-			int midOuter30 = axis.getStart30(outerBounds)+(axis.getStop30(outerBounds) - axis.getStart30(outerBounds)) / 2;
-			cutPoint30 = midOuter30;
-
-			if (midOuter30 < startPoint30) {
-				// not possible => the start point is greater than the middle so correct to the startPoint
-				cutPoint30 = startPoint30;
-				
-				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 (midOuter30 > stopPoint30) {
-				// not possible => the stop point is smaller than the middle so correct to the stopPoint
-				cutPoint30 = stopPoint30;
-
-				if ((cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1))  >= startPoint30) {
-					cutPoint30 = (cutPoint30 & ~(CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD-1));
-				}
-			}
-			
-			
-			// 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 = cutPoint30 % CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD;
-			if (cutMod == 0) {
-				return cutPoint30;
-			}
-			
-			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 ? cutPoint30 + CUT_POINT_CLASSIFICATION_GOOD_THRESHOLD -cutMod : cutPoint30 - cutMod);
-			if (cut2 >= startPoint30 && cut2 <= stopPoint30) {
-				cutPoint30 = cut2;
-				return cutPoint30;
-			}
-			
-			return cutPoint30;
-		}
-
-		public Rectangle2D getCutRectangleForArea(Area toCut, boolean firstRect) {
-			return getCutRectangleForArea(toCut.getBounds2D(), firstRect);
-		}
-		
-		public Rectangle2D getCutRectangleForArea(Rectangle2D areaRect, boolean firstRect) {
-			double cp = (double)  getCutPoint30() / (1<<Coord.DELTA_SHIFT);
-			if (axis == CoordinateAxis.LONGITUDE) {
-				double newWidth = cp-areaRect.getX();
-				if (firstRect) {
-					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), newWidth, areaRect.getHeight()); 
-				} else {
-					return new Rectangle2D.Double(areaRect.getX()+newWidth, areaRect.getY(), areaRect.getWidth()-newWidth, areaRect.getHeight()); 
-				}
-			} else {
-				double newHeight = cp-areaRect.getY();
-				if (firstRect) {
-					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY(), areaRect.getWidth(), newHeight); 
-				} else {
-					return new Rectangle2D.Double(areaRect.getX(), areaRect.getY()+newHeight, areaRect.getWidth(), areaRect.getHeight()-newHeight); 
-				}
-			}
-		}
-		
-		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.getStop30(areas.getFirst()) < axis.getStart30(area)) {
-				// remove the first area
-				areas.removeFirst();
-			}
-
-			areas.add(area);
-			Collections.sort(areas, comparator);
-			startPoint30 = axis.getStart30(Collections.max(areas,
-				(axis == CoordinateAxis.LONGITUDE ? COMP_LONG_START
-						: COMP_LAT_START)));
-			stopPoint30 = axis.getStop30(areas.getFirst());
-			
-			// reset the cached value => need to be recalculated the next time they are needed
-			bounds = null;
-			cutPoint30 = null;
-			minAspectRatio = null;
-		}
-
-		public int getNumberOfAreas() {
-			return this.areas.size();
-		}
-
-		/**
-		 * Retrieves the minimum aspect ratio of the outer bounds after cutting.
-		 * 
-		 * @return minimum aspect ratio of outer bound after cutting
-		 */
-		public double getMinAspectRatio() {
-			if (minAspectRatio == null) {
-				// first get the left/upper cut
-				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
-				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);
-
-				// get the minimum
-				minAspectRatio = Math.min(ar1, ar2);
-			}
-			return minAspectRatio;
-		}
-		
-		public int compareTo(CutPoint o) {
-			if (this == o) {
-				return 0;
-			}
-			// prefer a cut at the boundaries
-			if (isStartCut() && o.isStartCut() == false) {
-				return 1;
-			} 
-			else if (isStartCut() == false && o.isStartCut()) {
-				return -1;
-			}
-			else if (isStopCut() && o.isStopCut() == false) {
-				return 1;
-			}
-			else if (isStopCut() == false && o.isStopCut()) {
-				return -1;
-			}
-			
-			// handle the special case that a cut has no area
-			if (getNumberOfAreas() == 0) {
-				if (o.getNumberOfAreas() == 0) {
-					return 0;
-				} else {
-					return -1;
-				}
-			} else if (o.getNumberOfAreas() == 0) {
-				return 1;
-			}
-			
-			if (isBadCutPoint() != o.isBadCutPoint()) {
-				if (isBadCutPoint()) {
-					return -1;
-				} else
-					return 1;
-			}
-			
-			double dAR = getMinAspectRatio() - o.getMinAspectRatio();
-			if (dAR != 0) {
-				return (dAR > 0 ? 1 : -1);
-			}
-			
-			if (isGoodCutPoint() != o.isGoodCutPoint()) {
-				if (isGoodCutPoint())
-					return 1;
-				else
-					return -1;
-			}
-			
-			// prefer the larger area that is split
-			double ss1 = axis.getSizeOfSide(getBounds2D());
-			double ss2 = o.axis.getSizeOfSide(o.getBounds2D());
-			if (ss1-ss2 != 0)
-				return Double.compare(ss1,ss2); 
-
-			int ndiff = getNumberOfAreas()-o.getNumberOfAreas();
-			return ndiff;
-
-		}
-
-		private Rectangle2D getBounds2D() {
-			if (bounds == null) {
-				// lazy init
-				bounds = new Rectangle2D.Double();
-				for (Area a : areas)
-					bounds.add(a.getBounds2D());
-			}
-			return bounds;
-		}
-
-		public String toString() {
-			return axis +" "+getNumberOfAreas()+" "+startPoint30+" "+stopPoint30+" "+getCutPoint30();
-		}
-	}
-
-	private static enum CoordinateAxis {
-		LATITUDE(false), LONGITUDE(true);
-
-		private CoordinateAxis(boolean useX) {
-			this.useX = useX;
-		}
-
-		private final boolean useX;
-
-		public int getStart30(Area area) {
-			return getStart30(area.getBounds2D());
-		}
-
-		public int getStart30(Rectangle2D rect) {
-			double val = (useX ? rect.getX() : rect.getY());
-			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
-		}
-
-		public int getStop30(Area area) {
-			return getStop30(area.getBounds2D());
-		}
-
-		public int getStop30(Rectangle2D rect) {
-			double val = (useX ? rect.getMaxX() : rect.getMaxY());
-			return (int)Math.round(val * (1<<Coord.DELTA_SHIFT));
-		}
-		
-		public double getSizeOfSide(Rectangle2D rect) {
-			if (useX) {
-				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 {
-				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);
-			}
-		}
-	}
-	
-	private static final AreaComparator COMP_LONG_START = new AreaComparator(
-			true, CoordinateAxis.LONGITUDE);
-	private static final AreaComparator COMP_LONG_STOP = new AreaComparator(
-			false, CoordinateAxis.LONGITUDE);
-	private static final AreaComparator COMP_LAT_START = new AreaComparator(
-			true, CoordinateAxis.LATITUDE);
-	private static final AreaComparator COMP_LAT_STOP = new AreaComparator(
-			false, CoordinateAxis.LATITUDE);
-
-	private static class AreaComparator implements Comparator<Area> {
-
-		private final CoordinateAxis axis;
-		private final boolean startPoint;
-
-		public AreaComparator(boolean startPoint, CoordinateAxis axis) {
-			this.startPoint = startPoint;
-			this.axis = axis;
-		}
-
-		public int compare(Area o1, Area o2) {
-			if (o1 == o2) {
-				return 0;
-			}
-
-			if (startPoint) {
-				int cmp = axis.getStart30(o1) - axis.getStart30(o2);
-				if (cmp == 0) {
-					return axis.getStop30(o1) - axis.getStop30(o2);
-				} else {
-					return cmp;
-				}
-			} else {
-				int cmp = axis.getStop30(o1) - axis.getStop30(o2);
-				if (cmp == 0) {
-					return axis.getStart30(o1) - axis.getStart30(o2);
-				} else {
-					return cmp;
-				}
-			}
-		}
-
-	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Node.java b/src/uk/me/parabola/mkgmap/reader/osm/Node.java
index 3413b08..da07fc2 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Node.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Node.java
@@ -46,6 +46,7 @@ public class Node extends Element {
 
 	public Node copy() {
 		Node dup = new Node(getId(), location);
+		dup.copyIds(this);
 		dup.copyTags(this);
 		return dup;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5CoastDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmCoastDataSource.java
similarity index 73%
rename from src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5CoastDataSource.java
rename to src/uk/me/parabola/mkgmap/reader/osm/OsmCoastDataSource.java
index 480edcc..90a6c17 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5CoastDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmCoastDataSource.java
@@ -10,22 +10,15 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  */
-package uk.me.parabola.mkgmap.reader.osm.xml;
+package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.Collections;
 import java.util.Set;
 
-import uk.me.parabola.mkgmap.reader.osm.CoastlineElementSaver;
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-
-public class Osm5CoastDataSource extends Osm5MapDataSource {
+public class OsmCoastDataSource extends OsmMapDataSource {
 
 	private static final Set<String> coastlineTags = Collections.singleton("natural");
 	
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-	
 	protected OsmReadingHooks[] getPossibleHooks() {
 		// no hooks
 		return new OsmReadingHooks[] {};
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
index f1374ec..7d55491 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmHandler.java
@@ -13,10 +13,13 @@
 
 package uk.me.parabola.mkgmap.reader.osm;
 
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Pattern;
 
+import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 
@@ -25,19 +28,25 @@ import uk.me.parabola.imgfmt.app.Coord;
  * 
  * @author Steve Ratcliffe
  */
-public class OsmHandler {
+public abstract class OsmHandler {
 	// Elements that are read are saved/further processed by these two classes.
 	protected ElementSaver saver;
 	protected OsmReadingHooks hooks;
 
-	private final Map<String, Long> fakeIdMap = new HashMap<String, Long>();
 	private Map<String,Set<String>> deletedTags;
 	private Map<String, String> usedTags;
 
+	/** Pattern for values containing fixme, fix_me etc. */
+	private static final Pattern FIXME_PATTERN = Pattern.compile("(?i)fix[ _]?+me");
+	private boolean removeFixme;
+
+	// Options
+	private boolean ignoreBounds;
+	
 	// Node references within a way
-	protected long firstNodeRef;
-	protected long lastNodeRef;
-	protected boolean missingNodeRef;
+	private long firstNodeRef;
+	private long lastNodeRef;
+	private boolean missingNodeRef;
 
 	/** 
 	 * Tag that is set to <code>true</code> if one or more tags are not loaded. 
@@ -107,8 +116,16 @@ public class OsmHandler {
 		// By returning the value stored in usedTags, instead of the key, we ensure
 		// that the same string is always used so saving some memory.
 		if (usedTags != null)
-			return usedTags.get(key);
-
+			key = usedTags.get(key);
+		
+		if (key != null && removeFixme && val.length() >= 5) {
+			// remove tags with value fixme if the key is NOT fixme
+			if ("fixme".equals(key) || "FIXME".equals(key)) {
+				// keep fixme no matter what value it has
+			} else if (FIXME_PATTERN.matcher(val).matches()) {
+				return null;
+			}
+		}
 		return key;
 	}
 
@@ -116,32 +133,13 @@ public class OsmHandler {
 	 * Actually set the bounding box.  The boundary values are given.
 	 */
 	protected void setBBox(double minlat, double minlong, double maxlat, double maxlong) {
+		if (minlat == maxlat || minlong == maxlong) {
+			return; // silently ignore bounds with dim 0
+		}
 		Area bbox = new Area(minlat, minlong, maxlat, maxlong);
 		saver.setBoundingBox(bbox);
 	}
 
-	/**
-	 * Convert an id as a string to a number. If the id is not a number, then create
-	 * a unique number instead.
-	 * @param id The id as a string. Does not have to be a numeric quantity.
-	 * @return A long id, either parsed from the input, or a unique id generated internally.
-	 */
-	protected long idVal(String id) {
-		try {
-			// attempt to parse id as a number
-			return Long.parseLong(id);
-		} catch (NumberFormatException e) {
-			// if that fails, fake a (hopefully) unique value
-			Long fakeIdVal = fakeIdMap.get(id);
-			if(fakeIdVal == null) {
-				fakeIdVal = FakeIdGenerator.makeFakeId();
-				fakeIdMap.put(id, fakeIdVal);
-			}
-			//System.out.printf("%s = 0x%016x\n", id, fakeIdVal);
-			return fakeIdVal;
-		}
-	}
-
 	public void setElementSaver(ElementSaver elementSaver) {
 		this.saver = elementSaver;
 	}
@@ -195,4 +193,41 @@ public class OsmHandler {
 			missingNodeRef = true;
 		}
 	}
+
+	/**
+	 * Enable removal of tags / value pairs where value matches the fixme pattern.
+	 * @param b true: enable the filter
+	 */
+	public void setDeleteFixmeValues(boolean b) {
+		this.removeFixme = b;
+	}
+
+	public boolean isIgnoreBounds() {
+		return ignoreBounds;
+	}
+
+	public void setIgnoreBounds(boolean ignoreBounds) {
+		this.ignoreBounds = ignoreBounds;
+	}
+	
+	/**
+	 * Determines if the file (or other resource) is supported by this map
+	 * data source.  The implementation may do this however it likes, eg
+	 * by extension or by opening up the file and reading part of it.
+	 *
+	 * @param name The file (or other resource) to check.
+	 * @return True if the OSM handler supports that file.
+	 */
+	public abstract boolean isFileSupported(String name);
+
+	/**
+	 * Load osm data from open stream.  
+	 * You would implement this interface to allow reading data from
+	 * zipped files.
+	 *
+	 * @param is the already opened stream.
+	 * @throws FormatException For any kind of malformed input.
+	 */
+	
+	public abstract void parse(InputStream is) throws FormatException; 
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
index 2ff0fe6..3b8bcdb 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
@@ -22,24 +22,33 @@ import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.UnaryOperator;
 
 import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.Version;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
 import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
 import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
+import uk.me.parabola.mkgmap.reader.osm.bin.OsmBinHandler;
+import uk.me.parabola.mkgmap.reader.osm.o5m.O5mBinHandler;
+import uk.me.parabola.mkgmap.reader.osm.xml.OsmXmlHandler;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
@@ -48,9 +57,7 @@ import uk.me.parabola.util.EnhancedProperties;
  *
  * @author Steve Ratcliffe
  */
-public abstract class OsmMapDataSource extends MapperBasedMapDataSource
-		implements LoadableMapDataSource, LoadableOsmDataSource
-{
+public class OsmMapDataSource extends MapperBasedMapDataSource implements LoadableMapDataSource {
 	private static final Logger log = Logger.getLogger(OsmMapDataSource.class);
 
 	private Style style;
@@ -64,12 +71,23 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 			new HighwayHooks(),
 			new LocationHook(),
 			new POIGeneratorHook(),
+			new ResidentialHook(),
 			new HousenumberHooks(),
 	};
 	protected OsmConverter converter;
 	private final Set<String> usedTags = new HashSet<>();
 	protected ElementSaver elementSaver;
 	protected OsmReadingHooks osmReadingHooks;
+	private static final LocalDateTime now = LocalDateTime.now();
+	
+	protected static final List<OsmHandler> handlers;
+	static {
+		handlers = new ArrayList<>();
+		handlers.add(new OsmBinHandler());
+		handlers.add(new O5mBinHandler());
+		handlers.add(new OsmXmlHandler()); // must be last
+	}
+
 
 	/**
 	 * Get the maps levels to be used for the current map.  This can be
@@ -127,9 +145,36 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	}
 	
 	@Override
-	public void load(String name) throws FileNotFoundException, FormatException {
+	public void load(String name, boolean addBackground) throws FileNotFoundException, FormatException {
 		InputStream is = Utils.openFile(name);
-		load(is);
+		parse(is, name);
+		elementSaver.finishLoading();
+
+		osmReadingHooks.end();
+		osmReadingHooks = null;
+		
+		// now convert the saved elements
+		elementSaver.convert(getConverter());
+		
+		if (addBackground)
+			addBackground();
+	}
+
+	protected void parse(InputStream is, String name) {
+		for (OsmHandler h : handlers) {
+			if (h.isFileSupported(name)) {
+				try {
+					OsmHandler handler = h.getClass().newInstance();
+					setupHandler(handler);
+					handler.parse(is);
+					break;
+				} catch (InstantiationException | IllegalAccessException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+			}
+		}
+		
 	}
 
 	/**
@@ -145,19 +190,20 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 			List<String> copyrightArray = new ArrayList<>();
 			try {
 				File file = new File(copyrightFileName);
-				BufferedReader reader = Files.newBufferedReader(file.toPath(), Charset.forName("utf-8"));
-
-				String text;
-				while ((text = reader.readLine()) != null) {
-					copyrightArray.add(text);
-				}
-
-				reader.close();
-			} catch (FileNotFoundException e) {
-				throw new ExitException("Could not open copyright file " + copyrightFileName);
-			} catch (IOException e) {
-				throw new ExitException("Error reading copyright file " + copyrightFileName);
+				copyrightArray = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8);
+			}
+			catch (Exception e) {
+				throw new ExitException("Error reading copyright file " + copyrightFileName, e);
 			}
+			if ((copyrightArray.size() > 0) && copyrightArray.get(0).startsWith("\ufeff"))
+				copyrightArray.set(0, copyrightArray.get(0).substring(1));
+			UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION)
+					.replace("$JAVA_VERSION$", System.getProperty("java.version"))
+					.replace("$YEAR$", Integer.toString(now.getYear()))
+					.replace("$LONGDATE$", now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)))
+					.replace("$SHORTDATE$", now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)))
+					.replace("$TIME$", now.toLocalTime().toString().substring(0, 5));
+			copyrightArray.replaceAll(replaceVariables);
 			String[] copyright = new String[copyrightArray.size()];
 			copyrightArray.toArray(copyright);
 			return copyright;
@@ -179,6 +225,8 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 		createElementSaver();
 		createConverter();
 		
+		handler.setIgnoreBounds(getConfig().getProperty("ignore-osm-bounds", false));
+		
 		osmReadingHooks = pluginChain(elementSaver, getConfig());
 
 		handler.setElementSaver(elementSaver);
@@ -191,6 +239,9 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 			Map<String, Set<String>> deltags = readDeleteTagsFile(deleteTagsFileName);
 			handler.setTagsToDelete(deltags);
 		}
+		if (getConfig().getProperty("ignore-fixme-values", false)) {
+			handler.setDeleteFixmeValues(true);
+		}
 	}
 	
 	protected void createElementSaver() {
@@ -206,7 +257,7 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	}
 	
 	protected OsmReadingHooks pluginChain(ElementSaver saver, EnhancedProperties props) {
-		List<OsmReadingHooks> plugins = new ArrayList<OsmReadingHooks>();
+		List<OsmReadingHooks> plugins = new ArrayList<>();
 		for (OsmReadingHooks p : getPossibleHooks()) {
 			if (p.init(saver, props)){
 				plugins.add(p);
@@ -234,10 +285,9 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 		return hooks;
 	}
 
-	private Map<String, Set<String>> readDeleteTagsFile(String fileName) {
-		Map<String, Set<String>> deletedTags = new HashMap<String,Set<String>>();
-		try {
-			BufferedReader br = new BufferedReader(new FileReader(fileName));
+	private static Map<String, Set<String>> readDeleteTagsFile(String fileName) {
+		Map<String, Set<String>> deletedTags = new HashMap<>();
+		try (BufferedReader br = new BufferedReader(new FileReader(fileName))) { 
 			String line;
 			while((line = br.readLine()) != null) {
 				line = line.trim();
@@ -251,7 +301,7 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 						} else {
 							Set<String> vals = deletedTags.get(parts[0]);
 							if (vals == null)
-								vals = new HashSet<String>();
+								vals = new HashSet<>();
 							vals.add(parts[1]);
 							deletedTags.put(parts[0], vals);
 						}
@@ -260,7 +310,6 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 					}
 				}
 			}
-			br.close();
 		}
 		catch(FileNotFoundException e) {
 			log.error("Could not open delete tags file " + fileName);
@@ -285,6 +334,7 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 		setStyle(style);
 
 		usedTags.addAll(style.getUsedTags());
+		usedTags.addAll(NameFinder.getNameTags(props));
 		converter = new StyledConverter(style, mapper, props);
 	}
 
@@ -300,4 +350,9 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	public Boolean getDriveOnLeft(){
 		return converter.getDriveOnLeft();
 	}
+
+	@Override
+	public boolean isFileSupported(String name) {
+		return true; // we always try xml reader if nothing else matched
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5PrecompSeaDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmPrecompSeaDataSource.java
similarity index 64%
rename from src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5PrecompSeaDataSource.java
rename to src/uk/me/parabola/mkgmap/reader/osm/OsmPrecompSeaDataSource.java
index e764436..f329482 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5PrecompSeaDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmPrecompSeaDataSource.java
@@ -10,22 +10,15 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
  */
-package uk.me.parabola.mkgmap.reader.osm.xml;
+package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.Collections;
 import java.util.Set;
 
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-import uk.me.parabola.mkgmap.reader.osm.PrecompSeaElementSaver;
-
-public class Osm5PrecompSeaDataSource extends Osm5MapDataSource {
-
-	private static final Set<String> coastlineTags = Collections.singleton("natural");
-	
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
+public class OsmPrecompSeaDataSource extends OsmMapDataSource {
 	
+	private static final Set<String> coastlineTags = Collections.singleton("natural");
+
 	protected OsmReadingHooks[] getPossibleHooks() {
 		// no hooks
 		return new OsmReadingHooks[] {};
@@ -34,8 +27,4 @@ public class Osm5PrecompSeaDataSource extends Osm5MapDataSource {
 	public Set<String> getUsedTags() {
 		return coastlineTags;
 	}
-
-	protected void createElementSaver() {
-		elementSaver = new PrecompSeaElementSaver(getConfig());
-	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
index d427492..427fb08 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/POIGeneratorHook.java
@@ -25,7 +25,7 @@ import java.util.Set;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.build.LocatorUtil;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
@@ -68,7 +68,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 	
 	private boolean poisToAreas = false;
 	private boolean poisToLines = false;
-	private List<String> nameTags;
+	private NameFinder nameFinder;
 	
 	/** Name of the bool tag that is set to true if a POI is created from an area */
 	public static final short AREA2POI_TAG = TagDict.getInstance().xlate("mkgmap:area2poi");
@@ -83,7 +83,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			log.info("Disable Areas2POIHook because add-pois-to-areas and add-pois-to-lines option is not set.");
 			return false;
 		}
-		nameTags = LocatorUtil.getNameTags(props);
+		nameFinder = new NameFinder(props);
 
 		this.poiPlacementTags = getPoiPlacementTags(props);
 		
@@ -157,10 +157,10 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 	}
 	
 	public void end() {
-		log.info("Areas2POIHook started");
+		log.info(getClass().getSimpleName(), "started");
 		addPOIsToWays();
 		addPOIsToMPs();
-		log.info("Areas2POIHook finished");
+		log.info(getClass().getSimpleName(), "finished");
 	}
 	
 	private int getPlacementOrder(Element elem) {
@@ -340,24 +340,6 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 		
 	}
 
-	/**
-	 * Retrieves the name of the given element based on the name-tag-list option.
-	 * @param e an OSM element
-	 * @return the name or <code>null</code> if the element has no name
-	 */
-	private String getName(Element e) {
-//		if (e.getName()!= null) {
-//			return e.getName();
-//		}
-		for (String nameTag : nameTags) {
-			String nameTagVal = e.getTag(nameTag);
-			if (nameTagVal != null) {
-				return nameTagVal;
-			}
-		}
-		return null;
-	}
-
 	private void addPOIsToMPs() {
 		int mps2POI = 0;
 		for (Relation r : saver.getRelations().values()) {
@@ -368,7 +350,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 			}
 			Node admin_centre = null;
 			Node labelPOI = null;
-			String relName = getName(r);
+			String relName = nameFinder.getName(r);
 			if (relName != null){
 				for (Entry<String, Element> pair : r.getElements()){
 					String role = pair.getKey();
@@ -378,7 +360,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 							if ("boundary".equals(r.getTag("type")) && "administrative".equals(r.getTag("boundary"))){
 								// boundary relations may have a node with role admin_centre, if yes, use the 
 								// location of it
-								String pName = getName(el);
+								String pName = nameFinder.getName(el);
 								if (relName.equals(pName)){
 									admin_centre = (Node) el;
 									if (log.isDebugEnabled())
@@ -386,7 +368,7 @@ public class POIGeneratorHook extends OsmReadingHooksAdaptor {
 								}
 							}
 						} else if ("label".equals(role)){
-							String label = getName(el);
+							String label = nameFinder.getName(el);
 							if (relName.equals(label)){
 								labelPOI = (Node) el;
 								log.debug("using label node as location for POI for rel",r.getId(),relName,"at",((Node) el).getLocation().toDegreeString());
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/PrecompSeaElementSaver.java b/src/uk/me/parabola/mkgmap/reader/osm/PrecompSeaElementSaver.java
deleted file mode 100644
index adb9ad8..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/PrecompSeaElementSaver.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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.reader.osm;
-
-import uk.me.parabola.util.EnhancedProperties;
-
-/**
- * This saver stores elements loaded from precompiled sea tiles.
- * It does not convert so the data is still available after loading.
- * @author WanMil
- */
-public class PrecompSeaElementSaver extends ElementSaver {
-
-	public PrecompSeaElementSaver(EnhancedProperties args) {
-		super(args);
-	}
-
-	public void convert(OsmConverter converter) {
-	}
-}
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Relation.java b/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
index d00ccd3..575d68b 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Relation.java
@@ -37,4 +37,9 @@ public abstract class Relation extends Element {
 	public String kind() {
 		return "relation";
 	}
+	
+	public String toString() {
+		return "RELATION: " + getId();
+	}
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
index 7932dac..603c820 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
@@ -13,10 +13,7 @@
 
 package uk.me.parabola.mkgmap.reader.osm;
 
-import java.util.List;
-
-import uk.me.parabola.mkgmap.build.LocatorUtil;
-import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
@@ -27,14 +24,14 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 
 	private Style style;
 	private ElementSaver saver;
-	List<String> nameTagList;
+	private NameFinder nameFinder;
 
 	public RelationStyleHook() {
 	}
 
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
-		nameTagList = LocatorUtil.getNameTags(props);
+		nameFinder  = new NameFinder(props);
 		return super.init(saver, props);
 	}
 
@@ -45,15 +42,7 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 	public void end() {
 		Rule relationRules = style.getRelationRules();
 		for (Relation rel : saver.getRelations().values()) {
-			if (nameTagList != null){
-				for (String t : nameTagList) {
-					String val = rel.getTag(t);
-					if (val != null) {
-						rel.addTag("name", val);
-						break;
-					}
-				}
-			}			
+			nameFinder .setNameWithNameTagList(rel);
 			relationRules.resolveType(rel, TypeResult.NULL_RESULT);
 			if (rel instanceof RestrictionRelation){
 				((RestrictionRelation) rel).eval(saver.getBoundingBox());
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/ResidentialHook.java b/src/uk/me/parabola/mkgmap/reader/osm/ResidentialHook.java
new file mode 100644
index 0000000..e2abd58
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/reader/osm/ResidentialHook.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+package uk.me.parabola.mkgmap.reader.osm;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
+import uk.me.parabola.mkgmap.reader.osm.boundary.Boundary;
+import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryQuadTree;
+import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.Java2DConverter;
+
+/**
+ * Hook to add tag mkgmap:residential to elements that lie within a landuse=residential area.
+ * @author Gerd Petermann
+ *
+ */
+public class ResidentialHook extends OsmReadingHooksAdaptor {
+	private static final Logger log = Logger.getLogger(ResidentialHook.class);
+
+	
+	private BoundaryQuadTree residentialBoundaries;
+	
+	private ElementSaver saver;
+	private NameFinder nameFinder;
+
+	public boolean init(ElementSaver saver, EnhancedProperties props) {
+		if (props.getProperty("residential-hook", true) == false)
+			return false; 
+		this.nameFinder = new NameFinder(props);
+		this.saver = saver;
+		return true;
+	}
+
+	public void end() {
+		log.info("Starting with residential hook");
+
+		long t1 = System.currentTimeMillis();
+		residentialBoundaries = buildResidentialBoundaryTree();
+		long t2 = System.currentTimeMillis();
+		log.info("Creating residential bounds took", (t2 - t1), "ms");
+		
+		// process all nodes that might be converted to a garmin node (tagcount > 0)
+		for (Node node : saver.getNodes().values()) {
+			if (node.getTagCount() > 0) {
+				if (saver.getBoundingBox().contains(node.getLocation())){
+					processElem(node);
+				}
+			}
+		}
+
+		// process  all ways that might be converted to a garmin way (tagcount > 0)
+		for (Way way : saver.getWays().values()) {
+			if (way.getTagCount() > 0) {
+				processElem(way);
+			}
+		}
+		long t3 = System.currentTimeMillis();
+		log.info("Using residential bounds took", (t3 - t2), "ms");
+		// free memory
+		residentialBoundaries = null;
+	}
+
+	private static final short landuseTagKey = TagDict.getInstance().xlate("landuse"); 
+	private static final short nameTagKey = TagDict.getInstance().xlate("name");  
+	private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
+	private static final short otherKey = TagDict.getInstance().xlate("mkgmap:other");
+	
+	private BoundaryQuadTree buildResidentialBoundaryTree() {
+		List<Boundary> residentials = new ArrayList<>();
+		Tags tags = new Tags();
+		
+		for (Way way : saver.getWays().values()) {
+			if (way.hasIdenticalEndPoints() && "residential".equals(way.getTag(landuseTagKey))) {
+				if ("polyline".equals(way.getTag(styleFilterTagKey)))
+					continue;
+				String name = nameFinder.getName(way);
+				tags.put(nameTagKey, name == null ? "yes": name);
+				Boundary b = new Boundary(Java2DConverter.createArea(way.getPoints()), tags, "w"+way.getId());
+				residentials.add(b);
+			}
+		}
+		
+		return new BoundaryQuadTree(saver.getBoundingBox(), residentials, null);
+	}
+
+	/**
+	 * Extract the location info and perform a test 
+	 * against the BoundaryQuadTree. If found, assign the tag.
+	 * @param elem A way or Node
+	 */
+	private void processElem(Element elem){
+		Tags residentialTags = null;
+
+		if (elem instanceof Node){
+			Node node = (Node) elem;
+			residentialTags = residentialBoundaries.get(node.getLocation());
+		} else if (elem instanceof Way){
+			Way way = (Way) elem;
+			// try the mid point of the way first
+			int middle = way.getPoints().size() / 2;
+			Coord loc = way.hasIdenticalEndPoints() ? way.getCofG() : way.getPoints().get(middle);
+			if (! "residential".equals(way.getTag(landuseTagKey)))
+				residentialTags = residentialBoundaries.get(loc);
+		}
+
+		if (residentialTags != null) {
+			elem.addTag("mkgmap:residential", residentialTags.get(otherKey));
+		}
+	}
+	
+	@Override
+	public Set<String> getUsedTags() {
+		Set<String> used = new HashSet<>();
+		used.add("landuse");
+		used.add("name");
+		return used;
+	}
+}
+
+
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
index 62207b0..14666eb 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RestrictionRelation.java
@@ -17,6 +17,7 @@ import java.util.ArrayList;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -46,7 +47,7 @@ public class RestrictionRelation extends Relation {
     private List<Long> toWayIds = new ArrayList<>(2);
     private List<Long> viaWayIds = new ArrayList<>(2);
     private List<Coord> viaPoints = new ArrayList<>(2);
-    private HashSet<Long> updatedViaWays = new HashSet<>(); 
+    private Map<Long, List<Coord>> updatedViaWays = new HashMap<>();
     private Coord viaCoord;
     private String restriction;
 	private byte exceptMask;
@@ -634,7 +635,7 @@ public class RestrictionRelation extends Relation {
 	 * @param wayId
 	 * @return
 	 */
-	public boolean isValidWithoputWay(long wayId) {
+	public boolean isValidWithoutWay(long wayId) {
 		assert evalWasCalled;
 		if (viaWayIds.contains(wayId))
 			return false;
@@ -661,32 +662,23 @@ public class RestrictionRelation extends Relation {
 			return;
 		if (viaWayIds.contains(way.getId()) == false)
 			return;
-		if(updatedViaWays.contains(way.getId())){
+		List<Coord> wayViaPoints = new ArrayList<>();
+		for (int i : nodeIndices) {
+			wayViaPoints.add(way.getPoints().get(i));
+		}
+		List<Coord> prevViaPoints = updatedViaWays.get(way.getId()); 
+		if(prevViaPoints != null){
 			// we may get here when the style adds multiple routable ways for the
 			// OSM way
-			if (viaPoints.size() != nodeIndices.size())
-				valid = false;
-			else {
-				Iterator<Coord> iter = viaPoints.iterator();
-				for (int pos : nodeIndices){
-					if (iter.hasNext()){
-						if (way.getPoints().get(pos).equals(iter.next()))
-							continue;
-					}
-					valid = false;
-					break;
-				}
-			}
-			if (!valid)
-				log.error(messagePrefix, "internal error: via way is updated again with different nodes");
-			else {
+			if (prevViaPoints.equals(wayViaPoints)) {
 				// already up to date
 				return;
+			} else {
+				log.error(messagePrefix, "internal error: via way is updated again with different nodes");
 			}
 		}
-		Coord first = way.getPoints().get(nodeIndices.get(0));
-		Coord last = way.getPoints().get(
-				nodeIndices.get(nodeIndices.size() - 1));
+		Coord first = wayViaPoints.get(0);
+		Coord last = wayViaPoints.get(wayViaPoints.size()-1);
 		int posFirst = -1;
 		int posLast = -1;
 		for (int i = 0; i < viaPoints.size(); i++) {
@@ -712,10 +704,7 @@ public class RestrictionRelation extends Relation {
 			valid = false;
 			return;
 		}
-		List<Coord> midPoints = new ArrayList<>();
-		for (int i = 1; i + 1 < nodeIndices.size(); i++) {
-			midPoints.add(way.getPoints().get(nodeIndices.get(i)));
-		}
+		List<Coord> midPoints = new ArrayList<>(wayViaPoints.subList(1, wayViaPoints.size()-1));
 		if (posFirst < posLast){
 			if (posLast - posFirst > 1)
 				viaPoints.subList(posFirst+1, posLast).clear();
@@ -741,6 +730,6 @@ public class RestrictionRelation extends Relation {
 			log.warn(messagePrefix,"has more than 6 via nodes, this is not supported");
 			valid = false;
 		}
-		updatedViaWays.add(way.getId());
+		updatedViaWays.put(way.getId(), wayViaPoints);
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
index ba958cd..ad86b44 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
@@ -46,9 +46,7 @@ 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.LineClipper;
-import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
 import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5PrecompSeaDataSource;
 import uk.me.parabola.util.EnhancedProperties;
 import uk.me.parabola.util.Java2DConverter;
 
@@ -116,30 +114,6 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	 */
 	private static final long seaSize = Long.MAX_VALUE-2; // sea is BIG
 
-	private static final List<Class<? extends LoadableMapDataSource>> precompSeaLoader;
-
-	static {
-		String[] sources = {
-				"uk.me.parabola.mkgmap.reader.osm.bin.OsmBinPrecompSeaDataSource",
-				// must be last as it is the default
-				"uk.me.parabola.mkgmap.reader.osm.xml.Osm5PrecompSeaDataSource", };
-
-		precompSeaLoader = new ArrayList<Class<? extends LoadableMapDataSource>>();
-
-		for (String source : sources) {
-			try {
-				@SuppressWarnings({ "unchecked" })
-				Class<? extends LoadableMapDataSource> c = (Class<? extends LoadableMapDataSource>) Class
-						.forName(source);
-				precompSeaLoader.add(c);
-			} catch (ClassNotFoundException e) {
-				// not available, try the rest
-			} catch (NoClassDefFoundError e) {
-				// not available, try the rest
-			}
-		}
-	}
-	
 	/**
 	 * Sort out options from the command line.
 	 * Returns true only if the option to generate the sea is active, so that
@@ -500,44 +474,19 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	}
 	
 	/**
-	 * Creates a reader for the given filename of the precomiled sea tile.
-	 * @param filename precompiled sea tile 
-	 * @return the reader for the tile
-	 */
-	private static OsmMapDataSource createTileReader(String filename) {
-		for (Class<? extends LoadableMapDataSource> loader : precompSeaLoader) {
-			try {
-				LoadableMapDataSource src = loader.newInstance();
-				if (filename != null && src instanceof OsmMapDataSource
-						&& src.isFileSupported(filename))
-					return (OsmMapDataSource) src;
-			} catch (InstantiationException e) {
-				// try the next one.
-			} catch (IllegalAccessException e) {
-				// try the next one.
-			} catch (NoClassDefFoundError e) {
-				// try the next one
-			}
-		}
-
-		// Give up and assume it is in the XML format. If it isn't we will get
-		// an error soon enough anyway.
-		return new Osm5PrecompSeaDataSource();
-	}
-	
-	/**
 	 * Loads the precomp sea tile with the given filename.
 	 * @param filename the filename of the precomp sea tile
 	 * @return all ways of the tile
 	 * @throws FileNotFoundException if the tile could not be found
 	 */
 	private Collection<Way> loadPrecompTile(InputStream is, String filename) {
-		OsmMapDataSource src = createTileReader(filename);
+		OsmPrecompSeaDataSource src = new OsmPrecompSeaDataSource();
 		EnhancedProperties props = new EnhancedProperties();
+		props.setProperty("style", "empty"); 
 		src.config(props);
 		log.info("Started loading coastlines from", filename);
 		try{
-			src.load(is);
+			src.parse(is, filename);
 		} catch (FormatException e) {
 			log.error("Failed to read " + filename);
 			log.error(e);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
index d6abc09..3ffc645 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
@@ -118,7 +118,7 @@ public class SeaPolygonRelation extends MultiPolygonRelation {
 
 		String baseName = GpxCreator.getGpxBaseName();
 		if (debug) {
-			GpxCreator.createAreaGpx(baseName + "bbox", getBbox());
+			GpxCreator.createAreaGpx(baseName + "bbox", getTileBounds());
 		}
 
 		// go through all polygons and check if it contains too many coords of
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
index 989b7a1..fb0054a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
@@ -14,6 +14,8 @@ package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+
+import it.unimi.dsi.fastutil.shorts.ShortArrayList;
 import uk.me.parabola.imgfmt.MapFailedException;
 
 
@@ -88,4 +90,19 @@ public class TagDict{
 	public int size(){
 		return list.size();
 	}
+	
+	/**
+	 * Return list of compiled tag keys for given array of key strings.
+	 * @param keys the keys
+	 * @return a ShortArrayList which might be empty but will not be null.
+	 */
+	public static ShortArrayList compileTags(String ...keys) {
+		ShortArrayList compiled = new ShortArrayList();
+		if (keys != null) {
+			for (String key : keys) {
+				compiled.add(getInstance().xlate(key));
+			}
+		}
+		return compiled;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
index 5378123..c8c6bf4 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Tags.java
@@ -18,7 +18,8 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.TreeSet;
 
 /**
  * Store the tags that belong to an Element.
@@ -35,7 +36,7 @@ import java.util.Map.Entry;
  *
  * @author Steve Ratcliffe
  */
-public class Tags implements Iterable<String> {
+public class Tags {
 	private static final int INIT_SIZE = 8;
 	private static final TagDict tagDict = TagDict.getInstance();  
 
@@ -177,81 +178,28 @@ public class Tags implements Iterable<String> {
 		return -1;
 	}
 
-	/**
-	 * Iterates over the tags in a special way that is used to look up in
-	 * the rules.
-	 *
-	 * If you have the tags a=b, c=d then you will get the following strings
-	 * returned: "a=b", "a=*", "c=d", "c=*".
-	 *
-	 * If you add a tag during the iteration, then it is guaranteed to
-	 * appear later in the iteration.
-	 */
-	public Iterator<String> iterator() {
-		return new Iterator<String>() {
+	public Iterator<Map.Entry<String, String>> entryIterator() {
+		return new Iterator<Map.Entry<String, String>>() {
 			private int pos;
+			private int done;
 
 			public boolean hasNext() {
-				// After every normal entry there is a wild card entry.
-				//if (doWild)
-				//	return true;
-
-				// Normal entries in the map
-				for (int i = pos; i < capacity; i++) {
-					if (values[i] != null) {
-						pos = i;
-						return true;
-					}
-				}
-
-				return false;
+				return done < size;
 			}
 
-			/**
-			 * Get the next tag as a single string.  Also returns wild card
-			 * entries.
-			 */
-			public String next() {
-				/*if (doWild) {
-					doWild = false;
-					return wild + "=*";
-				} else*/ if (pos < capacity) {
-					for (int i = pos; i < capacity; i++) {
-						if (values[i] != null) {
-							pos = i+1;
-							return (tagDict.get(keys[i]) + "=" + values[i]);
-						}
-					}
-					pos = capacity;
-				}
-
-				return null;
-			}
-
-			public void remove() {
-				throw new UnsupportedOperationException();
-			}
-		};
-	}
+			public Map.Entry<String, String> next() {
+				if (done >= size)
+					throw new NoSuchElementException();
 
-	public Iterator<Map.Entry<String, String>> entryIterator() {
-		return new Iterator<Map.Entry<String, String>>() {
-			private int pos;
-			
-			public boolean hasNext() {
-				for (int i = pos; i < capacity; i++) {
-					if (values[i] != null) {
-						pos = i;
-						return true;
+				for (; pos < capacity; pos++) {
+					if (values[pos] != null) {
+						break;
 					}
 				}
-				return false;
-			}
-
-			public Map.Entry<String, String> next() {
 				Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<>(tagDict.get(keys[pos]), values[pos]);
 
 				pos++;
+				done++;
 				return entry;
 			}
 
@@ -264,21 +212,26 @@ public class Tags implements Iterable<String> {
 	public Iterator<Map.Entry<Short, String>> entryShortIterator() {
 		return new Iterator<Map.Entry<Short, String>>() {
 			private int pos;
-			
+			private int done;
+
 			public boolean hasNext() {
-				for (int i = pos; i < capacity; i++) {
-					if (values[i] != null) {
-						pos = i;
-						return true;
-					}
-				}
-				return false;
+				return done < size;
 			}
 
 			public Map.Entry<Short, String> next() {
+				if (done >= size)
+					throw new NoSuchElementException();
+
+				for (; pos < capacity; pos++) {
+					if (values[pos] != null) {
+						break;
+					}
+				}
+
 				Map.Entry<Short, String> entry = new AbstractMap.SimpleEntry<>(keys[pos], values[pos]);
 
 				pos++;
+				done++;
 				return entry;
 			}
 
@@ -307,51 +260,15 @@ public class Tags implements Iterable<String> {
 		return map;
 	}
 	
-	public void removeAll() {
-		Arrays.fill(keys, TagDict.INVALID_TAG_VALUE);
-		Arrays.fill(values, null);
-		keySize = 0;
-		size = 0;
-	}
-	
+
 	public String toString() {
-		StringBuilder s =new StringBuilder();
-		s.append("[");
-		Iterator<Entry<String,String>> tagIter = entryIterator();
-		while (tagIter.hasNext()) {
-			Entry<String,String> tag = tagIter.next();
-			if (s.length() > 1) {
-				s.append("; ");
-			}
-			s.append(tag.getKey());
-			s.append("=");
-			s.append(tag.getValue());
-		}
-		s.append("]");
-		return s.toString();
-	}
-	
-	/**
-	 * Each tag has a position in the TagDict. This routine fills an array
-	 * so that the caller can use direct access. 
-	 * The caller has to make sure that the array is large enough to hold
-	 * the values he is looking for.  
-	 * @param tagVals
-	 * @return
-	 */
-	public int expand(short[] keyArray, String[] tagVals){
-		if (tagVals == null)
-			return 0;
-		int maxKey = tagVals.length - 1;
-		int cntTags = 0;
-		for (int i = 0; i< capacity; i++){
-			short tagKey = keys[i];
-			if (tagKey != TagDict.INVALID_TAG_VALUE && values[i] != null && tagKey <= maxKey){
-				tagVals[tagKey] = values[i];
-				keyArray[cntTags++] = tagKey;
+		// sort the tags by key to make the result predictable and easier to read
+		TreeSet<String> sorted = new TreeSet<>();
+		for (int i = 0; i < capacity; i++) {
+			if (values[i] != null) {
+				sorted.add(tagDict.get(keys[i]) + "=" + values[i]);
 			}
-
 		}
-		return cntTags;
+		return sorted.toString();
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index c782454..6d1b381 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -61,6 +61,7 @@ public class Way extends Element {
 
 	public Way copy() {
 		Way dup = new Way(getId(), points);
+		dup.copyIds(this);
 		dup.copyTags(this);
 		dup.closedInOSM = this.closedInOSM;
 		dup.complete = this.complete;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinCoastDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinCoastDataSource.java
deleted file mode 100644
index aa6120a..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinCoastDataSource.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2010, 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.reader.osm.bin;
-
-import java.util.Collections;
-import java.util.Set;
-
-import uk.me.parabola.mkgmap.reader.osm.CoastlineElementSaver;
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-
-public class OsmBinCoastDataSource extends OsmBinMapDataSource {
-	
-	private static final Set<String> coastlineTags = Collections.singleton("natural");
-
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-
-	protected OsmReadingHooks[] getPossibleHooks() {
-		// no hooks
-		return new OsmReadingHooks[] {};
-	}
-
-	protected void createElementSaver() {
-		elementSaver = new CoastlineElementSaver(getConfig());
-	}
-
-	public Set<String> getUsedTags() {
-		return coastlineTags;
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
index 2811e58..2398766 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
@@ -12,8 +12,11 @@
  */
 package uk.me.parabola.mkgmap.reader.osm.bin;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.List;
 
+import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.MapFailedException;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.reader.osm.Element;
@@ -21,10 +24,10 @@ import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
 import uk.me.parabola.mkgmap.reader.osm.Node;
 import uk.me.parabola.mkgmap.reader.osm.OsmHandler;
 import uk.me.parabola.mkgmap.reader.osm.Way;
-import uk.me.parabola.util.EnhancedProperties;
 
 import crosby.binary.BinaryParser;
 import crosby.binary.Osmformat;
+import crosby.binary.file.BlockInputStream;
 
 /**
  * Handler for Scott Crosby's binary format, based on the Google
@@ -34,18 +37,43 @@ import crosby.binary.Osmformat;
  */
 public class OsmBinHandler extends OsmHandler {
 
-	public OsmBinHandler(EnhancedProperties props) {
+	public OsmBinHandler() {
 	}
 
+	@Override
+	public boolean isFileSupported(String name) {
+		// The extension for the protobuf format is now fixed at .pbf
+		// Previously we temporarily used the .bin extension to
+		// indicate Scott's format. The .bin extension remains here for the
+		// time being, but may be removed.  Please use .pbf.
+		return name.endsWith(".pbf") || name.endsWith(".bin");
+	}
+
+	@Override
+	public void parse(InputStream is) {
+		try {
+			BinParser reader = new BinParser();
+			BlockInputStream stream = new BlockInputStream(is, reader);
+			stream.process();
+		} catch (NoClassDefFoundError e) {
+			throw new FormatException("Failed to read binary file, probably missing protobuf.jar");
+		} catch (IOException e) {
+			throw new FormatException("Failed to read binary file");
+		} 
+	}
+	
+	
 	public class BinParser extends BinaryParser {
 
 		protected void parse(Osmformat.HeaderBlock header) {
-			double multiplier = .000000001;
-			double maxLon = header.getBbox().getRight() * multiplier;
-			double minLon = header.getBbox().getLeft() * multiplier;
-			double maxLat = header.getBbox().getTop() * multiplier;
-			double minLat = header.getBbox().getBottom() * multiplier;
-
+			if (header.hasBbox()) {
+				double multiplier = .000000001;
+				double maxLon = header.getBbox().getRight() * multiplier;
+				double minLon = header.getBbox().getLeft() * multiplier;
+				double maxLat = header.getBbox().getTop() * multiplier;
+				double minLat = header.getBbox().getBottom() * multiplier;
+				setBBox(minLat, minLon, maxLat, maxLon);
+			}
 			for (String s : header.getRequiredFeaturesList()) {
 				if (s.equals("OsmSchema-V0.6"))
 					continue; // We can parse this.
@@ -56,7 +84,6 @@ public class OsmBinHandler extends OsmHandler {
 				throw new MapFailedException("File requires unknown feature: " + s);
 			}
 
-			setBBox(minLat, minLon, maxLat, maxLon);
 		}
 
 		protected void parseNodes(List<Osmformat.Node> nodes) {
@@ -221,4 +248,5 @@ public class OsmBinHandler extends OsmHandler {
 		public void complete() {
 		}
 	}
+
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinMapDataSource.java
deleted file mode 100644
index e846be1..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinMapDataSource.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2010 - 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.reader.osm.bin;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
-import uk.me.parabola.mkgmap.reader.osm.bin.OsmBinHandler.BinParser;
-
-import crosby.binary.file.BlockInputStream;
-
-/**
- * Read an OpenStreetMap data file in .osm version 0.5 format.  It is converted
- * into a generic format that the map is built from.
- * <p>The intermediate format is important as several passes are required to
- * produce the map at different zoom levels. At lower resolutions, some roads
- * will have fewer points or won't be shown at all.
- *
- * @author Steve Ratcliffe
- */
-public class OsmBinMapDataSource extends OsmMapDataSource {
-
-	public boolean isFileSupported(String name) {
-		// The extension for the protobuf format is now fixed at .pbf
-		// Previously we temporarily used the .bin extension to
-		// indicate Scott's format. The .bin extension remains here for the
-		// time being, but may be removed.  Please use .pbf.
-		return name.endsWith(".pbf") || name.endsWith(".bin");
-	}
-
-	@Override
-	public void load(InputStream is) throws FormatException {
-
-		OsmBinHandler handler = new OsmBinHandler(getConfig());
-
-		setupHandler(handler);
-
-		try {
-			BinParser reader = handler.new BinParser();
-			handler = null;
-			BlockInputStream stream = new BlockInputStream(is, reader);
-			stream.process();
-		} catch (NoClassDefFoundError e) {
-			throw new FormatException("Failed to read binary file, probably missing protobuf.jar");
-		} catch (IOException e) {
-			throw new FormatException("Failed to read binary file");
-		}
-		elementSaver.finishLoading();
-
-		osmReadingHooks.end();
-		osmReadingHooks = null;
-		
-		// now convert the saved elements
-		elementSaver.convert(getConverter());
-
-		addBackground();
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinPrecompSeaDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinPrecompSeaDataSource.java
deleted file mode 100644
index 804964a..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinPrecompSeaDataSource.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.reader.osm.bin;
-
-import java.util.Collections;
-import java.util.Set;
-
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-import uk.me.parabola.mkgmap.reader.osm.PrecompSeaElementSaver;
-
-public class OsmBinPrecompSeaDataSource extends OsmBinMapDataSource {
-	
-	private static final Set<String> coastlineTags = Collections.singleton("natural");
-
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-
-	protected OsmReadingHooks[] getPossibleHooks() {
-		// no hooks
-		return new OsmReadingHooks[] {};
-	}
-
-	public Set<String> getUsedTags() {
-		return coastlineTags;
-	}
-
-	protected void createElementSaver() {
-		elementSaver = new PrecompSeaElementSaver(getConfig());
-	}
-
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/Boundary.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/Boundary.java
index 1539ed7..ef84d1a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/Boundary.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/Boundary.java
@@ -13,8 +13,8 @@
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
 import java.awt.geom.Area;
-import java.util.Map.Entry;
 
+import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.Tags;
 
 public class Boundary  {
@@ -29,13 +29,10 @@ public class Boundary  {
 		this.id = id;
 	}
 
-	public Boundary(Area area, Iterable<Entry<String, String>> tags, String id) {
+	public Boundary(Area area, Element el, String id) {
 		this.area = new Area(area);
 		this.id = id;
-		this.tags = new Tags();
-		for (Entry<String, String> tag : tags) {
-			this.tags.put(tag.getKey(), tag.getValue());
-		}
+		this.tags = el.getCopyOfTags();
 	}
 
 	public String getId() {
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 272ff0e..8c72501 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
@@ -28,11 +28,9 @@ public class BoundaryConverter implements OsmConverter {
 	
 	@Override
 	public void convertWay(Way way) {
-		if (BoundaryElementSaver.isBoundary(way)) {
-			java.awt.geom.Area boundArea = Java2DConverter.createArea(way.getPoints());
-			Boundary boundary = new Boundary(boundArea, way.getTagEntryIterator(), "w"+way.getId());
-			saver.addBoundary(boundary);
-		}
+		java.awt.geom.Area boundArea = Java2DConverter.createArea(way.getPoints());
+		Boundary boundary = new Boundary(boundArea, way, "w"+way.getId());
+		saver.addBoundary(boundary);
 	}
 
 	@Override
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 6074a23..acc4f2a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
@@ -13,151 +13,77 @@
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Map.Entry;
 
 import uk.me.parabola.log.Logger;
-import uk.me.parabola.mkgmap.build.Locator;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.ElementSaver;
 import uk.me.parabola.mkgmap.reader.osm.Node;
 import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
-import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
- * This saver only keeps ways with <code>natural=coastline</code> tags. This is
- * used for loading of extra coastline files.
+ * This saver only keeps ways or relations with boundaries. Used to prepare the dat for the bounds file.
  * 
  * @author WanMil
  */
 public class BoundaryElementSaver extends ElementSaver {
 	private static final Logger log = Logger.getLogger(BoundaryElementSaver.class);
 
-	private final static Locator locator = new Locator();
-	
 	private final BoundarySaver saver;
+	private final BoundaryLocationPreparer preparer;
 	
 	public BoundaryElementSaver(EnhancedProperties args, BoundarySaver saver) {
 		super(args);
 		this.saver = saver;
+		preparer = new BoundaryLocationPreparer(new EnhancedProperties());
 	}
 
 	/**
 	 * Checks if the given element is an administrative boundary or a
-	 * postal code area.
+	 * postal code area (or both).
 	 * @param element an element
-	 * @return <code>true</code> administrative boundary or postal code; 
+	 * @return <code>true</code> if administrative boundary or postal code; 
 	 * <code>false</code> element cannot be used for precompiled bounds 
 	 */
-	public static boolean isBoundary(Element element) {
+	public boolean isBoundary(Element element) {
 		if (element instanceof Relation) {
 			String type = element.getTag("type");
-			
-			if ("boundary".equals(type) || "multipolygon".equals(type)) {
-				String boundaryVal = element.getTag("boundary");
-				if ("administrative".equals(boundaryVal)) {
-					// for boundary=administrative the admin_level must be set
-					if (element.getTag("admin_level") == null) {
-						return false;
-					}
-					
-					// Check for admin_level==2 if the name is known in the LocatorConfig.xml.
-					// This should abandon all non country admin_level 2 boundaries
-					if (element.getTag("admin_level").equals("2")) {
-						Tags copyTags = new Tags();
-						for (Entry<String,String> tag : element.getTagEntryIterator()) {
-							copyTags.put(tag.getKey(), tag.getValue());
-						}
-						String iso = locator.getCountryISOCode(copyTags);
-						if (iso == null) {
-							log.warn("Ignore admin_level 2 element:", element.toBrowseURL(), element.toTagString());
-							return false;
-						}
-					}
-					// and a name must be set (check only for a tag containing name
-					for (Entry<String,String> tag : element.getTagEntryIterator()) {
-						if (tag.getKey().contains("name")) {
-							return true;
-						}
-					}
-					// does not contain a name tag => do not use it
-					return false;					
-				} else if ("postal_code".equals(boundaryVal)) {
-					// perform a positive check
-					
-					// is postal_code set?
-					if (element.getTag("postal_code") != null) {
-						return true;
-					}
-					// and a name must be set (check only for a tag containing name
-					for (Entry<String,String> tag : element.getTagEntryIterator()) {
-						if (tag.getKey().contains("name")) {
-							return true;
-						}
-					}
-					// does not contain a name tag => do not use it
-					return false;						
-				} else if (element.getTag("postal_code") != null){
-					return true;
-				} else {
-					return false;
-				}
-			} else {
+			if (!"boundary".equals(type) && !"multipolygon".equals(type)) 
 				return false;
-			}
 		} else if (element instanceof Way) {
 			Way w = (Way) element;
 			// a single way must be closed
 			if (w.isClosedInOSM() == false) {
 				return false;
 			}
-			// the boundary tag must be "administrative" or "postal_code"
-			String boundaryVal = element.getTag("boundary");
-			if ("administrative".equals(boundaryVal)) {
-				// for boundary=administrative the admin_level must be set
-				if (element.getTag("admin_level") == null) {
-					return false;
-				}
-
-				// Check for admin_level==2 if the name is known in the LocatorConfig.xml.
-				// This should abandon all non country admin_level 2 boundaries
-				if (element.getTag("admin_level").equals("2")) {
-					Tags copyTags = new Tags();
-					for (Entry<String,String> tag : element.getTagEntryIterator()) {
-						copyTags.put(tag.getKey(), tag.getValue());
-					}
-					String iso = locator.getCountryISOCode(copyTags);
-					if (iso == null) {
-						log.warn("Ignore admin_level 2 element:", element.toBrowseURL(), element.toTagString());
-						return false;
-					}
-				}
-				
-				// and a name must be set (check only for a tag containing name)
-				for (Entry<String,String> tag : element.getTagEntryIterator()) {
-					if (tag.getKey().contains("name")) {
-						return true;
-					}
-				}
-				// does not contain a name tag => do not use it
-				return false;
-			} else if ( "postal_code".equals(boundaryVal)) {
-				// the name tag must be set for it
-				return element.getTag("name") != null;
-			} else if (element.getTag("postal_code") != null) {
-				// postal_code as tag
-				return true;
-			} else {
-				return false;
-			}
 		} else {
 			return false;
 		}
+		return hasRelevantTags(element);
 	}
 
+	private boolean hasRelevantTags(Element element) {
+		BoundaryLocationInfo bInfo = preparer.parseTags(element);
+		if (bInfo.getZip() != null)
+			return true;
+		if (bInfo.getAdmLevel() == BoundaryLocationPreparer.UNSET_ADMIN_LEVEL)
+			return false;
+		if (bInfo.getName() != null && !"?".equals(bInfo.getName()))
+			return true;
+		if (bInfo.getAdmLevel() >= 3 && bInfo.getAdmLevel() <= 11) {
+			// for admin_level != 2 it is enough when we find a tag key containing "name" (like int_name or name:en)  
+			for (Entry<String,String> tag : element.getTagEntryIterator()) {
+				if (tag.getKey().contains("name")) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+	
 	public void addRelation(Relation rel) {
 		if (isBoundary(rel)) {
 			BoundaryRelation bRel = (BoundaryRelation) createMultiPolyRelation(rel);
@@ -179,7 +105,6 @@ public class BoundaryElementSaver extends ElementSaver {
 	}
 
 	public void addNode(Node node) {
-		return;
 	}
 
 	public void convert(OsmConverter converter) {
@@ -187,23 +112,22 @@ public class BoundaryElementSaver extends ElementSaver {
 
 		converter.setBoundingBox(getBoundingBox());
 
-		ArrayList<Relation> relations = new ArrayList<Relation>(
-				relationMap.values());
+		ArrayList<Relation> relations = new ArrayList<>(relationMap.values());
 		relationMap = null;
-		Collections.reverse(relations);
-		for (int i = relations.size() - 1; i >= 0; i--) {
+		for (int i = 0; i < relations.size(); i++) {
 			converter.convertRelation(relations.get(i));
-			relations.remove(i);
+			relations.set(i, null);
 		}
+		relations = null;
 
-
-		for (Way w : wayMap.values())
-			converter.convertWay(w);
+		for (Way w : wayMap.values()) {
+			if (isBoundary(w)) {
+				converter.convertWay(w);
+			}
+		}
 
 		wayMap = null;
 
 		converter.end();
-
-		relationMap = null;
 	}
 }
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationInfo.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationInfo.java
index 7900a01..d7ef967 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationInfo.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationInfo.java
@@ -23,16 +23,14 @@ public class BoundaryLocationInfo  {
 	private final String zip;
 	private final String name;
 	private final int admLevel;
-	private boolean isISO;
 
-	BoundaryLocationInfo (int admLevel, String name, String zip, boolean isISO){
+	BoundaryLocationInfo (int admLevel, String name, String zip){
 		this.admLevel = admLevel;
-		if (admLevel > 0 && name == null)
-			this.name = "not_set"; // TODO: review
+		if (name == null && admLevel != BoundaryLocationPreparer.UNSET_ADMIN_LEVEL)
+			this.name = "?";
 		else 
 			this.name = name;
 		this.zip = zip;
-		this.isISO = isISO;
 	}
 	public String getZip() {
 		return zip;
@@ -45,9 +43,5 @@ public class BoundaryLocationInfo  {
 	public int getAdmLevel() {
 		return admLevel;
 	}
-
-	public boolean isISOName(){
-		return isISO;
-	}
 }
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
index c734393..414c143 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryLocationPreparer.java
@@ -12,14 +12,15 @@
  */
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.regex.Pattern;
 
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.build.Locator;
-import uk.me.parabola.mkgmap.build.LocatorUtil;
+import uk.me.parabola.mkgmap.osmstyle.NameFinder;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.TagDict;
 import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.mkgmap.reader.osm.boundary.Boundary;
 import uk.me.parabola.util.EnhancedProperties;
@@ -35,28 +36,17 @@ import uk.me.parabola.util.EnhancedProperties;
 public class BoundaryLocationPreparer {
 	private static final Logger log = Logger.getLogger(BoundaryLocationPreparer.class);
 
-	private Locator locator;
+	private final Locator locator;
 	private static final Pattern COMMA_OR_SEMICOLON_PATTERN = Pattern.compile("[,;]+");
-	// tag keys for name resolution
-	private final List<String> nameList;
-	
+	private final NameFinder nameFinder;
 
 	/**
 	 * Create a preparer. 
 	 * @param props The program properties or null. 
 	 */
 	public BoundaryLocationPreparer(EnhancedProperties props) {
-		if (props == null){
-			this.locator = null;
-			this.nameList = new ArrayList<String>();
-			for (String name: BoundaryLocationPreparer.LEVEL2_NAMES){
-				this.nameList.add(name);
-			}
-		}
-		else{
-			this.locator = new Locator(props);
-			this.nameList = LocatorUtil.getNameTags(props);
-		}
+		locator = (props != null) ? new Locator(props) : null;
+		nameFinder = new NameFinder(props);
 	}
 
 	/**
@@ -67,21 +57,26 @@ public class BoundaryLocationPreparer {
 	public BoundaryLocationInfo parseTags(Tags tags){
 		String zip = getZip(tags);
 		int admLevel = getAdminLevel(tags);
-		boolean isISO = false;
 		String name = getName(tags);
-		if (locator != null){
-			if (admLevel == 2) {
-				String isoCode = locator.addCountry(tags);
-				if (isoCode != null) {
-					isISO = true;
-					name = isoCode;
-				} else {
-					log.warn("Country name",name,"not in locator config. Country may not be assigned correctly.");
-				}
-				log.debug("Coded:",name);
+		if (admLevel == 2 && locator != null) {
+			String isoCode = locator.addCountry(tags);
+			if (isoCode != null) {
+				name = isoCode;
+			} else {
+				log.warn("Country name",name,"not in locator config. Country may not be assigned correctly.");
 			}
+			log.debug("Coded:",name);
 		}
-		return new BoundaryLocationInfo(admLevel, name, zip, isISO);
+		return new BoundaryLocationInfo(admLevel, name, zip);
+	}
+
+	/**
+	 * Extract location relevant information from tags of the element
+	 * @param el the element
+	 * @return a new BoundaryLocationInfo instance 
+	 */
+	public BoundaryLocationInfo parseTags(Element el){
+		return parseTags(el.getCopyOfTags());
 	}
 
 	/** 
@@ -100,61 +95,47 @@ public class BoundaryLocationPreparer {
 		return preparedLocationInfo;
 	}
 	
-	
-	/** 
-	 * These tags are used to retrieve the name of admin_level=2 boundaries. They need to
-	 * be handled special because their name is changed to the 3 letter ISO code using
-	 * the Locator class and the LocatorConfig.xml file. 
-	 */
-	private static final String[] LEVEL2_NAMES = new String[]{"name","name:en","int_name"};
-	
 	/**
 	 * Try to extract the name of the boundary. 
 	 * @param tags the boundary tags
 	 * @return a name or null if no usable name tag was found
 	 */
 	private String getName(Tags tags) {
-		if ("2".equals(tags.get("admin_level"))) {
-			for (String enNameTag : LEVEL2_NAMES)
-			{
-				String nameTagValue = tags.get(enNameTag);
-				if (nameTagValue == null) {
-					continue;
-				}
-
-				String[] nameParts = COMMA_OR_SEMICOLON_PATTERN.split(nameTagValue);
-				if (nameParts.length == 0) {
-					continue;
+		if ("2".equals(tags.get(admin_levelTagKey))) {
+			// admin_level=2 boundaries. They need to be handled special because their name is changed 
+			// to the 3 letter ISO code using the Locator class and the LocatorConfig.xml file. 
+			for (short nameTagKey : Locator.PREFERRED_NAME_TAG_KEYS) {
+				String nameTagValue = tags.get(nameTagKey);
+				if (nameTagValue != null) {
+					return getFirstPart(nameTagValue);
 				}
-				return nameParts[0].trim().intern();
 			}
 		}
-		
-		for (String nameTag : nameList) {
-			String nameTagValue = tags.get(nameTag);
-			if (nameTagValue == null) {
-				continue;
-			}
+		String name = nameFinder.getName(tags);
+		if (name != null)
+			return getFirstPart(name);
+		return null;
+	}
 
-			String[] nameParts = COMMA_OR_SEMICOLON_PATTERN.split(nameTagValue);
-			if (nameParts.length == 0) {
-				continue;
-			}
+	private static String getFirstPart(String name) {
+		String[] nameParts = COMMA_OR_SEMICOLON_PATTERN.split(name);
+		if (nameParts.length > 0)
 			return nameParts[0].trim().intern();
-		}
-		
 		return null;
 	}
-
+	
+	private static final short postal_codeTagKey = TagDict.getInstance().xlate("postal_code");
+	private static final short boundaryTagKey = TagDict.getInstance().xlate("boundary");
 	/**
 	 * Try to extract a zip code from the the tags of a boundary. 
 	 * @param tags the boundary tags
 	 * @return null if no zip code was found, else a String that should be a zip code. 
 	 */
 	private String getZip(Tags tags) {
-		String zip = tags.get("postal_code");
+		String zip = tags.get(postal_codeTagKey);
 		if (zip == null) {
-			if ("postal_code".equals(tags.get("boundary"))){
+			if ("postal_code".equals(tags.get(boundaryTagKey))){
+				// unlikely
 				String name = tags.get("name"); 
 				if (name == null) {
 					name = getName(tags);
@@ -171,6 +152,7 @@ public class BoundaryLocationPreparer {
 	}
 
 	public static final int UNSET_ADMIN_LEVEL = 100; // must be higher than real levels
+	private static final short admin_levelTagKey = TagDict.getInstance().xlate("admin_level");
 	/**
 	 * translate the admin_level tag to an integer. 
 	 * @param tags the boundary tags
@@ -178,19 +160,18 @@ public class BoundaryLocationPreparer {
 	 * the conversion failed. 
 	 */
 	private static int getAdminLevel(Tags tags) {
-		String level = tags.get("admin_level");
-		if (level == null) {
-			return UNSET_ADMIN_LEVEL;
-		}
-		try {
-			Integer res = Integer.valueOf(level);
-			if (res < 2 || res > 11)
-				return UNSET_ADMIN_LEVEL;
-			else
-				return res;
-		} catch (NumberFormatException nfe) {
-			return UNSET_ADMIN_LEVEL;
+		if ("administrative".equals(tags.get(boundaryTagKey))) {
+			String level = tags.get(admin_levelTagKey);
+			if (level != null) {
+				try {
+					int res = Integer.parseInt(level);
+					if (res >= 2 && res <= 11)
+						return res;
+				} catch (NumberFormatException nfe) {
+				}
+			}
 		}
+		return UNSET_ADMIN_LEVEL;
 	}
 } 
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
index 2012066..80edce2 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
@@ -14,7 +14,6 @@ package uk.me.parabola.mkgmap.reader.osm.boundary;
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Callable;
@@ -35,61 +34,7 @@ import uk.me.parabola.mkgmap.reader.osm.LocationHook;
  */
 public class BoundaryPreprocessor implements Runnable {
 	private static final Logger log = Logger.getLogger(BoundaryPreprocessor.class);
-	private static final List<Class<? extends LoadableBoundaryDataSource>> loaders;
-	static {
-		String[] sources = {
-				"uk.me.parabola.mkgmap.reader.osm.boundary.OsmBinBoundaryDataSource",
-				"uk.me.parabola.mkgmap.reader.osm.boundary.O5mBinBoundaryDataSource",
-				// must be last as it is the default
-				"uk.me.parabola.mkgmap.reader.osm.boundary.Osm5BoundaryDataSource", };
 
-		loaders = new ArrayList<>();
-
-		for (String source : sources) {
-			try {
-				@SuppressWarnings({ "unchecked" })
-				Class<? extends LoadableBoundaryDataSource> c = (Class<? extends LoadableBoundaryDataSource>) Class
-						.forName(source);
-				loaders.add(c);
-			} catch (ClassNotFoundException e) {
-				// not available, try the rest
-			} catch (NoClassDefFoundError e) {
-				// not available, try the rest
-			}
-		}
-	}
-
-	/**
-	 * Return a suitable boundary map reader. The name of the resource to be
-	 * read is passed in. This is usually a file name, but could be something
-	 * else.
-	 * 
-	 * @param name
-	 *            The resource name to be read.
-	 * @return A LoadableBoundaryDataSource that is capable of reading the
-	 *         resource.
-	 */
-	private static LoadableBoundaryDataSource createMapReader(String name) {
-		for (Class<? extends LoadableBoundaryDataSource> loader : loaders) {
-			try {
-				LoadableBoundaryDataSource src = loader.newInstance();
-				if (name != null && src.isFileSupported(name))
-					return src;
-			} catch (InstantiationException e) {
-				// try the next one.
-			} catch (IllegalAccessException e) {
-				// try the next one.
-			} catch (NoClassDefFoundError e) {
-				// try the next one
-			}
-		}
-
-		// Give up and assume it is in the XML format. If it isn't we will get
-		// an error soon enough anyway.
-		return new Osm5BoundaryDataSource();
-	}
-
-	
 	private String boundaryFilename;
 	private String outDir;
 	private ExecutorService threadPool;
@@ -145,11 +90,11 @@ public class BoundaryPreprocessor implements Runnable {
 	private boolean createRawData(){
 		File boundsDirectory = new File(outDir);
 		BoundarySaver saver = new BoundarySaver(boundsDirectory, BoundarySaver.RAW_DATA_FORMAT);
-		LoadableBoundaryDataSource dataSource = createMapReader(boundaryFilename);
+		OsmBoundaryDataSource dataSource = new OsmBoundaryDataSource();
 		dataSource.setBoundarySaver(saver);
 		log.info("Started loading", boundaryFilename);
 		try {
-			dataSource.load(boundaryFilename);
+			dataSource.load(boundaryFilename, false);
 		} catch (FileNotFoundException e) {
 			e.printStackTrace();
 			return false;
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 5c6cbbb..963fb3c 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
@@ -87,10 +87,13 @@ public class BoundaryQuadTree {
 		"mkgmap:admin_level9",
 		"mkgmap:admin_level10",
 		"mkgmap:admin_level11",
-		"mkgmap:postcode"
+		"mkgmap:postcode",
+		"mkgmap:other" // for use in residential hook
 	};
 	// 11: the position of "mkgmap:postcode" in the above array
 	public final static short POSTCODE_ONLY = 1 << 11;   
+	// 12: the position of "mkgmap:other" in the above array
+	public final static short NAME_ONLY = 1 << 12;   
 	
 	/**
 	 * Create a quadtree with the data in an open stream. 
@@ -1127,6 +1130,9 @@ public class BoundaryQuadTree {
 			if (bInfo.getAdmLevel() != BoundaryLocationPreparer.UNSET_ADMIN_LEVEL){
 				locTags.put(BoundaryQuadTree.mkgmapTagsArray[bInfo.getAdmLevel()-1], bInfo.getName());
 			}
+			if (locTags.size() == 0 && bInfo.getName() != null) {
+				locTags.put("mkgmap:other", bInfo.getName());
+			}
 			if (locationDataSrc != null && locationDataSrc.isEmpty() == false){
 				// the common format of refInfo is 
 				// 2:r19884;4:r20039;6:r998818
@@ -1296,8 +1302,9 @@ public class BoundaryQuadTree {
 							errMsg = "different " + zipKey;
 							break;
 						}
-					}
-					else{
+					} else if (testMask == NAME_ONLY) {
+						break; // happens with ResidentialHook, silently ignore it
+					} else {
 						errAdmLevel = k+1;
 						errMsg = new String ("same admin_level (" + errAdmLevel + ")");
 						break;
@@ -1351,13 +1358,13 @@ public class BoundaryQuadTree {
 			int adminLevel1 = i1.getAdmLevel();
 			int adminLevel2 = i2.getAdmLevel();
 
-			if (i1.getName() == null || i1.getName() == "?") {
+			if (i1.getName() == null || "?".equals(i1.getName())) {
 				// admin_level tag is set but no valid name available
-				adminLevel1= BoundaryLocationPreparer.UNSET_ADMIN_LEVEL;
+				adminLevel1 = BoundaryLocationPreparer.UNSET_ADMIN_LEVEL;
 			}
-			if (i2.getName() == null || i2.getName() == "?") {
+			if (i2.getName() == null || "?".equals(i2.getName())) {
 				// admin_level tag is set but no valid name available
-				adminLevel2= BoundaryLocationPreparer.UNSET_ADMIN_LEVEL;
+				adminLevel2 = BoundaryLocationPreparer.UNSET_ADMIN_LEVEL;
 			}
 			
 			if (adminLevel1 > adminLevel2)
@@ -1365,13 +1372,6 @@ public class BoundaryQuadTree {
 			if (adminLevel1 < adminLevel2)
 				return -1;
 			
-			if (i1.getAdmLevel() == 2){
-				// prefer countries that are known by the Locator
-				if (i1.isISOName() == true && i2.isISOName() == false)
-					return 1;
-				if (i1.isISOName() == false && i2.isISOName() == true)
-					return -1;
-			}
 			boolean post1set = i1.getZip() != null;
 			boolean post2set = i2.getZip() != null;
 			if (post1set && !post2set)
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 f02bd90..9a6298b 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
@@ -12,7 +12,6 @@
  */
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
-import java.awt.geom.Line2D;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
@@ -54,7 +53,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 			if (outerResultArea == null) {
 				return null;
 			}
-			boundary = new Boundary(outerResultArea, this.getTagEntryIterator(),"r"+this.getId());
+			boundary = new Boundary(outerResultArea, this, "r"+this.getId());
 			outerResultArea = null;
 		}
 		return boundary;
@@ -69,70 +68,43 @@ public class BoundaryRelation extends MultiPolygonRelation {
 	
 		List<Way> allWays = getSourceWays();
 		
-//		// check if the multipolygon itself or the non inner member ways have a tag
-//		// if not it does not make sense to process it and we could save the time
-//		boolean shouldProcess = hasStyleRelevantTags(this);
-//		if (shouldProcess == false) {
-//			for (Way w : allWays) {
-//				shouldProcess = hasStyleRelevantTags(w);
-//				if (shouldProcess) {
-//					break;
-//				}
-//			}
-//		}
-//		if (shouldProcess==false) {
-//			log.info("Do not process multipolygon",getId(),"because it has no style relevant tags.");
-//			return;
-//		}
-
-		
-		// create an Area for the bbox to clip the polygons
-		bboxArea = Java2DConverter.createBoundsArea(getBbox()); 
-
 		// join all single ways to polygons, try to close ways and remove non closed ways 
 		polygons = joinWays(allWays);
 		
-		outerWaysForLineTagging = new HashSet<Way>();
-		outerTags = new HashMap<String,String>();
+		outerWaysForLineTagging = new HashSet<>();
+		outerTags = new HashMap<>();
 		
 		removeOutOfBbox(polygons);
 
-		boolean changed = true;
-		while (changed) {
-			changed = false;
-			while (connectUnclosedWays(polygons)) {
-				changed = true;
-			}		
-			closeWays(polygons);
-		}
-		
+		do {
+			closeWays(polygons, getMaxCloseDist());
+		} while (connectUnclosedWays(polygons));
+
 		removeUnclosedWays(polygons);
-		
+
 		// now only closed ways are left => polygons only
 
 		// check if we have at least one polygon left
-		if (polygons.isEmpty()) {
-			// do nothing
-			log.info("Multipolygon " + toBrowseURL()
-					+ " does not contain a closed polygon.");
-			tagOuterWays();
-			cleanup();
-			return;
-		}
+		boolean hasPolygons = !polygons.isEmpty();
 
 		removeWaysOutsideBbox(polygons);
 
 		if (polygons.isEmpty()) {
 			// do nothing
-			log.info("Multipolygon " + toBrowseURL()
-					+ " is completely outside the bounding box. It is not processed.");
+			if (log.isInfoEnabled()) {
+				if (hasPolygons)
+					log.info("Multipolygon", toBrowseURL(),
+							"is completely outside the bounding box. It is not processed.");
+				else
+					log.info("Multipolygon " + toBrowseURL() + " does not contain a closed polygon.");
+			}
 			tagOuterWays();
 			cleanup();
 			return;
 		}
 		
 		// the intersectingPolygons marks all intersecting/overlapping polygons
-		intersectingPolygons = new HashSet<JoinedWay>();
+		intersectingPolygons = new HashSet<>();
 		
 		// check which polygons lie inside which other polygon 
 		createContainsMatrix(polygons);
@@ -298,44 +270,6 @@ public class BoundaryRelation extends MultiPolygonRelation {
 			}
 		}
 		
-		// TODO tagging of the outer ways
-		
-//		if (log.isLoggable(Level.WARNING) && 
-//				(outmostInnerPolygons.cardinality()+unfinishedPolygons.cardinality()+nestedOuterPolygons.cardinality()+nestedInnerPolygons.cardinality() >= 1)) {
-//			log.warn("Multipolygon", toBrowseURL(), "contains errors.");
-//
-//			BitSet outerUnusedPolys = new BitSet();
-//			outerUnusedPolys.or(unfinishedPolygons);
-//			outerUnusedPolys.or(outmostInnerPolygons);
-//			outerUnusedPolys.or(nestedOuterPolygons);
-//			outerUnusedPolys.or(nestedInnerPolygons);
-//			outerUnusedPolys.or(unfinishedPolygons);
-//			// use only the outer polygons
-//			outerUnusedPolys.and(outerPolygons);
-//			for (JoinedWay w : getWaysFromPolygonList(outerUnusedPolys)) {
-//				outerWaysForLineTagging.addAll(w.getOriginalWays());
-//			}
-//			
-//			runIntersectionCheck(unfinishedPolygons);
-//			runOutmostInnerPolygonCheck(outmostInnerPolygons);
-//			runNestedOuterPolygonCheck(nestedOuterPolygons);
-//			runNestedInnerPolygonCheck(nestedInnerPolygons);
-//			runWrongInnerPolygonCheck(unfinishedPolygons, innerPolygons);
-//
-//			// we have at least one polygon that could not be processed
-//			// Probably we have intersecting or overlapping polygons
-//			// one possible reason is if the relation overlaps the tile
-//			// bounds
-//			// => issue a warning
-//			List<JoinedWay> lostWays = getWaysFromPolygonList(unfinishedPolygons);
-//			for (JoinedWay w : lostWays) {
-//				log.warn("Polygon", w, "is not processed due to an unknown reason.");
-//				logWayURLs(Level.WARNING, "-", w);
-//			}
-//		}
-//
-		
-		
 		if (hasStyleRelevantTags(this)) {
 			outerTags.clear();
 			for (Entry<String,String> mpTags : getTagEntryIterator()) {
@@ -374,8 +308,9 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		postProcessing();
 		cleanup();
 	}
+
 	protected boolean connectUnclosedWays(List<JoinedWay> allWays) {
-		List<JoinedWay> unclosed = new ArrayList<JoinedWay>();
+		List<JoinedWay> unclosed = new ArrayList<>();
 
 		for (JoinedWay w : allWays) {
 			if (w.hasIdenticalEndPoints() == false) {
@@ -385,21 +320,14 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		// 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 IdentityHashMap<Coord, JoinedWay>();
+			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>();
 			
 			// check all ways for endpoints outside or on the bbox
 			for (JoinedWay w : unclosed) {
 				Coord c1 = w.getPoints().get(0);
-//				if (bbox.insideBoundary(c1)==false) {
-//					log.debug("Point",c1,"of way",w.getId(),"outside bbox");
-					outOfBboxPoints.put(c1, w);
-//				}
-
-				Coord c2 = w.getPoints().get(w.getPoints().size()-1);
-//				if (bbox.insideBoundary(c2)==false) {
-//					log.debug("Point",c2,"of way",w.getId(),"outside bbox");
-					outOfBboxPoints.put(c2, w);
-//				}
+				Coord c2 = w.getPoints().get(w.getPoints().size() - 1);
+				outOfBboxPoints.put(c1, w);
+				outOfBboxPoints.put(c2, w);
 			}
 			
 			if (outOfBboxPoints.size() < 2) {
@@ -407,8 +335,8 @@ public class BoundaryRelation extends MultiPolygonRelation {
 				return false;
 			}
 			
-			List<ConnectionData> coordPairs = new ArrayList<ConnectionData>();
-			ArrayList<Coord> coords = new ArrayList<Coord>(outOfBboxPoints.keySet());
+			List<ConnectionData> coordPairs = new ArrayList<>();
+			ArrayList<Coord> coords = new ArrayList<>(outOfBboxPoints.keySet());
 			for (int i = 0; i < coords.size(); i++) {
 				for (int j = i + 1; j < coords.size(); j++) {
 					ConnectionData cd = new ConnectionData();
@@ -417,34 +345,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 					cd.w1 = outOfBboxPoints.get(cd.c1);					
 					cd.w2 = outOfBboxPoints.get(cd.c2);					
 					
-//					if (lineCutsBbox(cd.c1, cd.c2 )) {
-//						// Check if the way can be closed with one additional point
-//						// outside the bounding box.
-//						// The additional point is combination of the coords of both endpoints.
-//						// It works if the lines from the endpoints to the additional point does
-//						// not cut the bounding box.
-//						// This can be removed when the splitter guarantees to provide logical complete
-//						// multi-polygons.
-//						Coord edgePoint1 = new Coord(cd.c1.getLatitude(), cd.c2
-//								.getLongitude());
-//						Coord edgePoint2 = new Coord(cd.c2.getLatitude(), cd.c1
-//								.getLongitude());
-//
-//						if (lineCutsBbox(cd.c1, edgePoint1) == false
-//								&& lineCutsBbox(edgePoint1, cd.c2) == false) {
-//							cd.imC = edgePoint1;
-//						} else if (lineCutsBbox(cd.c1, edgePoint2) == false
-//								&& lineCutsBbox(edgePoint2, cd.c2) == false) {
-//							cd.imC = edgePoint1;
-//						} else {
-//							// both endpoints are on opposite sides of the bounding box
-//							// automatically closing such points would create wrong polygons in most cases
-//							continue;
-//						}
-//						cd.distance = cd.c1.distance(cd.imC) + cd.imC.distance(cd.c2);
-//					} else {
-						cd.distance = cd.c1.distance(cd.c2);
-//					}
+					cd.distance = cd.c1.distance(cd.c2);
 					coordPairs.add(cd);
 				}
 			}
@@ -489,7 +390,8 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		return false;
 	}
 	
-	private double getMaxCloseDist() {
+	@Override
+	protected double getMaxCloseDist() {
 		double dist = 1000;
 		String admString= getTag("admin_level");
 		
@@ -503,79 +405,6 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		return dist;
 	}
 	
-	protected void closeWays(ArrayList<JoinedWay> wayList) {
-		for (JoinedWay way : wayList) {
-			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
-				continue;
-			}
-			Coord p1 = way.getPoints().get(0);
-			Coord p2 = way.getPoints().get(way.getPoints().size() - 1);
-
-			if (getBbox().insideBoundary(p1) == false
-					&& getBbox().insideBoundary(p2) == false) {
-				// both points lie outside the bbox or on the bbox
-
-				// check if both points are on the same side of the bounding box
-				if ((p1.getLatitude() <= getBbox().getMinLat() && p2.getLatitude() <= getBbox()
-						.getMinLat())
-						|| (p1.getLatitude() >= getBbox().getMaxLat() && p2
-								.getLatitude() >= getBbox().getMaxLat())
-						|| (p1.getLongitude() <= getBbox().getMinLong() && p2
-								.getLongitude() <= getBbox().getMinLong())
-						|| (p1.getLongitude() >= getBbox().getMaxLong() && p2
-								.getLongitude() >= getBbox().getMaxLong())) {
-					// they are on the same side outside of the bbox
-					// so just close them without worrying about if
-					// they intersect itself because the intersection also
-					// is outside the bbox
-					way.closeWayArtificially();
-					log.info("Endpoints of way", way,
-							"are both outside the bbox. Closing it directly.");
-					continue;
-				}
-			}
-			
-			Line2D closingLine = new Line2D.Float(p1.getLongitude(), p1
-					.getLatitude(), p2.getLongitude(), p2.getLatitude());
-
-			boolean intersects = false;
-			Coord lastPoint = null;
-			// don't use the first and the last point
-			// the closing line can intersect only in one point or complete.
-			// Both isn't interesting for this check
-			for (Coord thisPoint : way.getPoints().subList(1,
-					way.getPoints().size() - 1)) {
-				if (lastPoint != null) {
-					if (closingLine.intersectsLine(lastPoint.getLongitude(),
-							lastPoint.getLatitude(), thisPoint.getLongitude(),
-							thisPoint.getLatitude())) {
-						intersects = true;
-						break;
-					}
-				}
-				lastPoint = thisPoint;
-			}
-
-			if (!intersects) {
-				// close the polygon
-				// the new way segment does not intersect the rest of the
-				// polygon
-				
-				// calc the distance to close
-				double closeDist = way.getPoints().get(0).distance(way.getPoints().get(way.getPoints().size()-1));
-				
-				if (closeDist <= getMaxCloseDist()) {
-					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();
-				}
-			}
-		}
-	}
-
 	private void removeOutOfBbox(List<JoinedWay> polygons) {
 		ListIterator<JoinedWay> pIter = polygons.listIterator();
 		while (pIter.hasNext()) {
@@ -586,7 +415,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 				// 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(first) == false || getBbox().contains(last) == false) {
+				if (getTileBounds().contains(first) == false || getTileBounds().contains(last) == false) {
 					pIter.remove();
 				}
 			}
@@ -594,6 +423,7 @@ public class BoundaryRelation extends MultiPolygonRelation {
 
 	}
 
+	@Override
 	protected void cleanup() {
 		super.cleanup();
 		this.getElements().clear();
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 9cd4c10..01c4b2a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
@@ -336,11 +336,13 @@ public class BoundaryUtil {
 	 */
 	public static List<String> getRequiredBoundaryFileNames(uk.me.parabola.imgfmt.app.Area bbox) {
 		List<String> names = new ArrayList<>();
-		for (int latSplit = getSplitBegin(bbox.getMinLat()); latSplit <= BoundaryUtil
-				.getSplitBegin(bbox.getMaxLat()); latSplit += RASTER) {
-			for (int lonSplit = getSplitBegin(bbox.getMinLong()); lonSplit <= BoundaryUtil
-					.getSplitBegin(bbox.getMaxLong()); lonSplit += RASTER) {
-				names.add("bounds_"+ getKey(latSplit, lonSplit) + ".bnd");
+		if (!bbox.isEmpty()) {
+			for (int latSplit = getSplitBegin(bbox.getMinLat()); latSplit <= BoundaryUtil
+					.getSplitBegin(bbox.getMaxLat()); latSplit += RASTER) {
+				for (int lonSplit = getSplitBegin(bbox.getMinLong()); lonSplit <= BoundaryUtil
+						.getSplitBegin(bbox.getMaxLong()); lonSplit += RASTER) {
+					names.add("bounds_"+ getKey(latSplit, lonSplit) + ".bnd");
+				}
 			}
 		}
 		return names;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/LoadableBoundaryDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/LoadableBoundaryDataSource.java
deleted file mode 100644
index 88ca3da..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/LoadableBoundaryDataSource.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2006, 2011.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 or
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-package uk.me.parabola.mkgmap.reader.osm.boundary;
-
-import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-
-public interface LoadableBoundaryDataSource extends LoadableMapDataSource {
-
-	public void setBoundarySaver(BoundarySaver saver);
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/O5mBinBoundaryDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/O5mBinBoundaryDataSource.java
deleted file mode 100644
index ef61510..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/O5mBinBoundaryDataSource.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2006, 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.reader.osm.boundary;
-
-import java.util.Set;
-
-import uk.me.parabola.mkgmap.reader.osm.MultiPolygonFinishHook;
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-import uk.me.parabola.mkgmap.reader.osm.o5m.O5mBinMapDataSource;
-import uk.me.parabola.util.EnhancedProperties;
-
-public class O5mBinBoundaryDataSource 
-	extends O5mBinMapDataSource
-	implements LoadableBoundaryDataSource {
-
-	private BoundarySaver saver;
-
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-
-	protected OsmReadingHooks[] getPossibleHooks() {
-		return new OsmReadingHooks[] { new MultiPolygonFinishHook() };
-	}
-
-	protected void createElementSaver() {
-		elementSaver = new BoundaryElementSaver(getConfig(), saver);
-	}
-
-	public Set<String> getUsedTags() {
-		// return null => all tags are used
-		return null;
-	}
-
-	protected void createConverter() {
-		converter = new BoundaryConverter(saver);
-	}
-
-	private final EnhancedProperties props = new EnhancedProperties();
-
-	protected EnhancedProperties getConfig() {
-		return props;
-	}
-
-	public void setBoundarySaver(BoundarySaver saver) {
-		this.saver = saver;
-	}
-
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBinBoundaryDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBinBoundaryDataSource.java
deleted file mode 100644
index 084da76..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBinBoundaryDataSource.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2006, 2011.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 or
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-package uk.me.parabola.mkgmap.reader.osm.boundary;
-
-import java.util.Set;
-
-import uk.me.parabola.mkgmap.reader.osm.MultiPolygonFinishHook;
-import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-import uk.me.parabola.mkgmap.reader.osm.bin.OsmBinMapDataSource;
-import uk.me.parabola.util.EnhancedProperties;
-
-public class OsmBinBoundaryDataSource 
-	extends OsmBinMapDataSource 
-	implements LoadableBoundaryDataSource {
-
-	private BoundarySaver saver;
-
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-
-	protected OsmReadingHooks[] getPossibleHooks() {
-		return new OsmReadingHooks[] { new MultiPolygonFinishHook() };
-	}
-
-	protected void createElementSaver() {
-		elementSaver = new BoundaryElementSaver(getConfig(), saver);
-	}
-
-	public Set<String> getUsedTags() {
-		// return null => all tags are used
-		return null;
-	}
-
-	protected void createConverter() {
-		converter = new BoundaryConverter(saver);
-	}
-
-	private final EnhancedProperties props = new EnhancedProperties();
-
-	protected EnhancedProperties getConfig() {
-		return props;
-	}
-
-	public void setBoundarySaver(BoundarySaver saver) {
-		this.saver = saver;
-	}
-
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/Osm5BoundaryDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBoundaryDataSource.java
similarity index 83%
rename from src/uk/me/parabola/mkgmap/reader/osm/boundary/Osm5BoundaryDataSource.java
rename to src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBoundaryDataSource.java
index 7e461cd..323f28e 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/Osm5BoundaryDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/OsmBoundaryDataSource.java
@@ -15,20 +15,14 @@ package uk.me.parabola.mkgmap.reader.osm.boundary;
 import java.util.Set;
 
 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonFinishHook;
+import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
 import uk.me.parabola.mkgmap.reader.osm.OsmReadingHooks;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5MapDataSource;
 import uk.me.parabola.util.EnhancedProperties;
 
-public class Osm5BoundaryDataSource 
-	extends Osm5MapDataSource 
-	implements LoadableBoundaryDataSource {
+public class OsmBoundaryDataSource extends OsmMapDataSource {
 
 	private BoundarySaver saver;
 
-	protected void addBackground(boolean mapHasPolygon4B) {
-		// do not add a background polygon
-	}
-
 	protected OsmReadingHooks[] getPossibleHooks() {
 		return new OsmReadingHooks[] { new MultiPolygonFinishHook() };
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
index 43f38af..5601f0d 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
@@ -51,7 +51,7 @@ public class O5mBinHandler extends OsmHandler{
 	private static final String[] REL_REF_TYPES = {"node", "way", "relation", "?"};
 	private static final double FACTOR = 1d/1000000000; // used with 100*<Val>*FACTOR 
 	
-	private final BufferedInputStream fis;
+	private BufferedInputStream fis;
 	private InputStream is;
 	private ByteArrayInputStream bis;
 	
@@ -81,11 +81,22 @@ public class O5mBinHandler extends OsmHandler{
 	/**
 	 * A parser for the o5m format
 	 * @param processor A mapProcessor instance
-	 * @param stream The InputStream that contains the OSM data in o5m format 
 	 * @param skipArray An Array of longs that is used to hold information of file position of the first occurrence of 
 	 * each known 05m data type (esp. nodes, ways, and relations). 
 	 */
-	O5mBinHandler(InputStream stream) {
+	public O5mBinHandler() {
+	}
+
+	@Override
+	public boolean isFileSupported(String name) {
+		return name.endsWith(".o5m") || name.endsWith(".o5m.gz");
+	}
+
+	/**
+	 * parse the input stream
+	 */
+	@Override
+	public void parse(InputStream stream){
 		this.fis = new BufferedInputStream(stream);
 		is = fis;
 		this.cnvBuffer = new byte[4000]; // OSM data should not contain string pairs with length > 512
@@ -94,12 +105,6 @@ public class O5mBinHandler extends OsmHandler{
 		this.stringPair = new String[2];
 		this.lastRef = new long[3];
 		reset();
-	}
-
-	/**
-	 * parse the input stream
-	 */
-	public void parse(){
 		try {
 			int start = is.read();
 			++countBytes;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinMapDataSource.java
deleted file mode 100644
index 9a038de..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinMapDataSource.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2010 - 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.reader.osm.o5m;
-
-import java.io.InputStream;
-
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
-
-/**
- * Read an OpenStreetMap data file in .o5m format.  It is converted
- * into a generic format that the map is built from.
- * <p>The intermediate format is important as several passes are required to
- * produce the map at different zoom levels. At lower resolutions, some roads
- * will have fewer points or won't be shown at all.
- *
- * @author GerdP
- */
-public class O5mBinMapDataSource extends OsmMapDataSource {
-
-	@Override
-	public boolean isFileSupported(String name) {
-		return name.endsWith(".o5m");
-	}
-
-
-	@Override
-	public void load(InputStream is) throws FormatException {
-
-		O5mBinHandler handler = new O5mBinHandler(is);
-
-		setupHandler(handler);
-
-		handler.parse();
-		handler = null;
-		elementSaver.finishLoading();
-
-		osmReadingHooks.end();
-		osmReadingHooks = null;
-		
-		// now convert the saved elements
-		elementSaver.convert(getConverter());
-
-		addBackground();
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5MapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5MapDataSource.java
deleted file mode 100644
index a0f5df6..0000000
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5MapDataSource.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2010 - 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.reader.osm.xml;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import uk.me.parabola.imgfmt.FormatException;
-import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
-
-import org.xml.sax.SAXException;
-
-
-/**
- * Read an OpenStreetMap data file in .osm version 0.5 format.  It is converted
- * into a generic format that the map is built from.
- * <p>The intermediate format is important as several passes are required to
- * produce the map at different zoom levels. At lower resolutions, some roads
- * will have fewer points or won't be shown at all.
- *
- * @author Steve Ratcliffe
- */
-public class Osm5MapDataSource extends OsmMapDataSource {
-
-	@Override
-	public boolean isFileSupported(String name) {
-		// This is the default format so say supported if we get this far,
-		// this one must always be last for this reason.
-		return true;
-	}
-
-	@Override
-	public void load(InputStream is) throws FormatException {
-		try {
-			SAXParserFactory parserFactory = SAXParserFactory.newInstance();
-			parserFactory.setXIncludeAware(true);
-			parserFactory.setNamespaceAware(true);
-			SAXParser parser = parserFactory.newSAXParser();
-
-			try {
-				Osm5XmlHandler handler = new Osm5XmlHandler(getConfig());
-				Osm5XmlHandler.SaxHandler saxHandler = handler.new SaxHandler();
-
-				setupHandler(handler);
-				handler = null;
-				
-				// parse the xml file
-				parser.parse(is, saxHandler);
-
-				elementSaver.finishLoading();
-
-				osmReadingHooks.end();
-				osmReadingHooks = null;
-				
-				// now convert the saved elements
-				elementSaver.convert(getConverter());
-
-				addBackground();
-
-			} catch (IOException e) {
-				throw new FormatException("Error reading file", e);
-			}
-		} catch (SAXException e) {
-			throw new FormatException("Error parsing file", e);
-		} catch (ParserConfigurationException e) {
-			throw new FormatException("Internal error configuring xml parser", e);
-		}
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/OsmXmlHandler.java
similarity index 82%
rename from src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
rename to src/uk/me/parabola/mkgmap/reader/osm/xml/OsmXmlHandler.java
index 54b1c69..177a034 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/xml/OsmXmlHandler.java
@@ -13,15 +13,25 @@
 
 package uk.me.parabola.mkgmap.reader.osm.xml;
 
+import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
 import uk.me.parabola.mkgmap.reader.osm.Node;
 import uk.me.parabola.mkgmap.reader.osm.OsmHandler;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
 import uk.me.parabola.mkgmap.reader.osm.Way;
-import uk.me.parabola.util.EnhancedProperties;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
 
 import org.xml.sax.Attributes;
 import org.xml.sax.ContentHandler;
@@ -39,8 +49,8 @@ import org.xml.sax.helpers.DefaultHandler;
  *
  * @author Steve Ratcliffe
  */
-public class Osm5XmlHandler extends OsmHandler {
-	private static final Logger log = Logger.getLogger(Osm5XmlHandler.class);
+public class OsmXmlHandler extends OsmHandler {
+	private static final Logger log = Logger.getLogger(OsmXmlHandler.class);
 
 	// Set to the currently processing element.
 	private int mode;
@@ -52,16 +62,66 @@ public class Osm5XmlHandler extends OsmHandler {
 	private static final int MODE_RELATION = 4;
 	private static final int MODE_BOUNDS = 5;
 
-	// Options
-	private final boolean ignoreBounds;
 	// Current state.
-	protected Node currentNode;
-	protected Way currentWay;
-	protected Relation currentRelation;
-	protected long currentElementId;
+	private Node currentNode;
+	private Way currentWay;
+	private Relation currentRelation;
+	private long currentElementId;
+	private final Map<String, Long> fakeIdMap = new HashMap<String, Long>();
+
+	public OsmXmlHandler() {
+	}
+
+	@Override
+	public boolean isFileSupported(String name) {
+		// This is the default format so say supported if we get this far,
+		// this one must always be last for this reason.
+		return true;
+	}
+
+	@Override
+	public void parse(InputStream is) throws FormatException {
+		try {
+			SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+			parserFactory.setXIncludeAware(true);
+			parserFactory.setNamespaceAware(true);
+			SAXParser parser = parserFactory.newSAXParser();
+
+			try {
+				SaxHandler saxHandler = new SaxHandler();
+
+				// parse the xml file
+				parser.parse(is, saxHandler);
 
-	public Osm5XmlHandler(EnhancedProperties props) {
-		ignoreBounds = props.getProperty("ignore-osm-bounds", false);
+			} catch (IOException e) {
+				throw new FormatException("Error reading file", e);
+			}
+		} catch (SAXException e) {
+			throw new FormatException("Error parsing file", e);
+		} catch (ParserConfigurationException e) {
+			throw new FormatException("Internal error configuring xml parser", e);
+		}
+	}
+	/**
+	 * Convert an id as a string to a number. If the id is not a number, then create
+	 * a unique number instead.
+	 * @param id The id as a string. Does not have to be a numeric quantity.
+	 * @return A long id, either parsed from the input, or a unique id generated internally.
+	 */
+	private long idVal(String id) {
+		try {
+			// attempt to parse id as a number
+			return Long.parseLong(id);
+		} catch (NumberFormatException e) {
+			// if that fails, fake a (hopefully) unique value
+			Long fakeIdVal = fakeIdMap.get(id);
+			if(fakeIdVal == null) {
+				fakeIdVal = FakeIdGenerator.makeFakeId();
+				fakeIdMap.put(id, fakeIdVal);
+			}
+			//System.out.printf("%s = 0x%016x\n", id, fakeIdVal);
+			return fakeIdVal;
+		}
 	}
 
 	/**
@@ -107,14 +167,14 @@ public class Osm5XmlHandler extends OsmHandler {
 
 				} else if (qName.equals("bound")) {
 					mode = MODE_BOUND;
-					if(!ignoreBounds) {
+					if(!isIgnoreBounds()) {
 						String box = attributes.getValue("box");
 						setupBBoxFromBound(box);
 					}
 
 				} else if (qName.equals("bounds")) {
 					mode = MODE_BOUNDS;
-					if(!ignoreBounds)
+					if(!isIgnoreBounds())
 						setupBBoxFromBounds(attributes);
 				}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
index 5694877..35c58fe 100644
--- a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java
@@ -19,6 +19,7 @@ package uk.me.parabola.mkgmap.reader.overview;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.List;
+
 import uk.me.parabola.imgfmt.FormatException;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
@@ -60,7 +61,7 @@ public class OverviewMapDataSource extends MapperBasedMapDataSource
 	/*
 	 * This is never called as isFileSupported always returns false.
 	 */
-	public void load(String name) throws FileNotFoundException, FormatException {
+	public void load(String name, boolean addBackground) throws FileNotFoundException, FormatException {
 		throw new FileNotFoundException("This is not supposed to be called");
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/plugin/MapReader.java b/src/uk/me/parabola/mkgmap/reader/plugin/MapReader.java
deleted file mode 100644
index 2a0b11d..0000000
--- a/src/uk/me/parabola/mkgmap/reader/plugin/MapReader.java
+++ /dev/null
@@ -1,90 +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: 02-Sep-2007
- */
-package uk.me.parabola.mkgmap.reader.plugin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
-import uk.me.parabola.mkgmap.reader.osm.xml.Osm5MapDataSource;
-
-/**
- * Class to find the correct map reader to use, based on the type of the file
- * to be read.
- *
- * Allows new map readers to be registered, the map readers are in charge of
- * recognising file formats that they can deal with.
- *
- * @author Steve Ratcliffe
- */
-public class MapReader {
-
-	private static final List<Class<? extends LoadableMapDataSource>> loaders;
-
-	static {
-		String[] sources = {
-				"uk.me.parabola.mkgmap.reader.osm.bin.OsmBinMapDataSource",
-				"uk.me.parabola.mkgmap.reader.osm.o5m.O5mBinMapDataSource",
-				"uk.me.parabola.mkgmap.reader.polish.PolishMapDataSource",
-				"uk.me.parabola.mkgmap.reader.test.ElementTestDataSource",
-
-				// must be last as it is the default
-				"uk.me.parabola.mkgmap.reader.osm.xml.Osm5MapDataSource",
-		};
-
-		loaders = new ArrayList<Class<? extends LoadableMapDataSource>>();
-
-		for (String source : sources) {
-			try {
-				@SuppressWarnings({"unchecked"})
-				Class<? extends LoadableMapDataSource> c = (Class<? extends LoadableMapDataSource>) Class.forName(source);
-				loaders.add(c);
-			} catch (ClassNotFoundException e) {
-				// not available, try the rest
-			} catch (NoClassDefFoundError e) {
-				// not available, try the rest
-			}
-		}
-	}
-
-	/**
-	 * Return a suitable map reader.  The name of the resource to be read is
-	 * passed in.  This is usually a file name, but could be something else.
-	 *
-	 * @param name The resource name to be read.
-	 * @return A LoadableMapDataSource that is capable of reading the resource.
-	 */
-	public static LoadableMapDataSource createMapReader(String name) {
-		for (Class<? extends LoadableMapDataSource> loader : loaders) {
-			try {
-				LoadableMapDataSource src = loader.newInstance();
-				if (name != null && src.isFileSupported(name))
-					return src;
-			} catch (InstantiationException e) {
-				// try the next one.
-			} catch (IllegalAccessException e) {
-				// try the next one.
-			} catch (NoClassDefFoundError e) {
-				// try the next one
-			}
-		}
-
-		// Give up and assume it is in the XML format. If it isn't we will get an
-		// error soon enough anyway.
-		return new Osm5MapDataSource();
-	}
-}
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
index 1f77b09..cfae073 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
@@ -96,7 +96,6 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 	private int lineNo;
 
 	private boolean havePolygon4B;
-	private Boolean driveOnLeft;
 
 	// Use to decode labels if they are not in cp1252
 	private CharsetDecoder dec;
@@ -107,13 +106,8 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		return name.endsWith(".mp") || name.endsWith(".MP") || name.endsWith(".mp.gz");
 	}
 
-	/**
-	 * Load the .osm file and produce the intermediate format.
-	 *
-	 * @param name The filename to read.
-	 * @throws FileNotFoundException If the file does not exist.
-	 */
-	public void load(String name) throws FileNotFoundException, FormatException {
+	@Override
+	public void load(String name, boolean addBackground) throws FileNotFoundException, FormatException {
 		Reader reader;
 		try {
 			reader = new InputStreamReader(Utils.openFile(name), READING_CHARSET);
@@ -151,7 +145,8 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 			throw new FormatException("Reading file failed", e);
 		}
 
-		addBackground(havePolygon4B);
+		if (addBackground && !havePolygon4B)
+			addBackground();
 		coordMap = null;
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java b/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
index 7b741de..70856da 100644
--- a/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/test/ElementTestDataSource.java
@@ -45,9 +45,11 @@ public class ElementTestDataSource extends MapperBasedMapDataSource implements L
 	/**
 	 * Load a map by generating it in code.
 	 * @param name The name of the map to generate.
+	 * @param addBackground ignored
 	 * @throws FileNotFoundException If the name is not recognised.
 	 */
-	public void load(String name) throws FileNotFoundException {
+	@Override
+	public void load(String name, boolean addBackground) throws FileNotFoundException {
 		if ("test-map:all-elements".equals(name)) {
 			AllElements all = new AllElements(configProps);
 			all.load(mapper);
diff --git a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
index b4e63f5..998b7cd 100644
--- a/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaGenerator.java
@@ -143,9 +143,7 @@ public class PrecompSeaGenerator {
 	 * @return the areas of all tiles
 	 */
 	private List<uk.me.parabola.imgfmt.app.Area> getTiles() {
-		uk.me.parabola.imgfmt.app.Area earth = new uk.me.parabola.imgfmt.app.Area(
-				-90.0d, -180.0d, 90.0d, 180.0d);
-		return getTiles(earth);
+		return getTiles(uk.me.parabola.imgfmt.app.Area.PLANET);
 	}
 
 	private List<uk.me.parabola.imgfmt.app.Area> getTiles(
diff --git a/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java b/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
index e7da6a8..ee17fda 100644
--- a/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
+++ b/src/uk/me/parabola/mkgmap/srt/SrtTextReader.java
@@ -22,10 +22,15 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import uk.me.parabola.imgfmt.ExitException;
+import uk.me.parabola.imgfmt.app.srt.CodePosition;
 import uk.me.parabola.imgfmt.app.srt.SRTFile;
 import uk.me.parabola.imgfmt.app.srt.Sort;
 import uk.me.parabola.imgfmt.fs.ImgChannel;
@@ -80,6 +85,7 @@ public class SrtTextReader {
 	private static final int IN_INITIAL = 0;
 	private static final int IN_CHARACTER = 1;
 	private static final int IN_EXPAND = 2;
+	private static final boolean EXPERIMENTAL = false;
 
 	// Data that is read in, the output of the reading operation
 	private final Sort sort = new Sort();
@@ -92,6 +98,9 @@ public class SrtTextReader {
 	private int pos3;
 	private int state;
 	private String cflags = "";
+	private Map<Integer, Integer> maxSec;
+	private Map<Integer, Integer> maxTert;
+	private List<CodePosition> expansions;
 
 	public SrtTextReader(Reader r) throws IOException {
 		this("stream", r);
@@ -102,7 +111,13 @@ public class SrtTextReader {
 	}
 
 	private SrtTextReader(String filename, Reader r) throws IOException {
+		maxSec = new HashMap<>();
+		maxTert = new HashMap<>();
+		expansions = new ArrayList<>();
 		read(filename, r);
+		maxSec = null;
+		maxTert = null;
+		expansions = null;
 	}
 
 	/**
@@ -163,7 +178,7 @@ public class SrtTextReader {
 				break;
 			}
 		}
-
+		sort.setExpansions(expansions);
 		sort.finish();
 	}
 
@@ -299,7 +314,9 @@ public class SrtTextReader {
 		if (!s.equals("to"))
 			throw new SyntaxException(scanner, "Expected the word 'to' in expand command");
 
-		List<Integer> expansionList = new ArrayList<>();
+		int secondary = 0;
+		int tertiary = 0; 
+		int num = 0;
 		while (!scanner.isEndOfFile()) {
 			Token t = scanner.nextRawToken();
 			if (t.isEol())
@@ -308,10 +325,42 @@ public class SrtTextReader {
 				continue;
 
 			Code r = new Code(scanner, t.getValue()).read();
-			expansionList.add(r.getBval());
+
+			CodePosition cp = new CodePosition();
+			int b = r.getBval();
+			int primary = sort.getPrimary(b);
+			cp.setPrimary((char) primary);
+
+			// We do not want the character to sort fully equal to the expanded characters (or any other
+			// character so adjust the ordering at other strengths.  May need further tweaks.
+			if (EXPERIMENTAL) {
+				secondary = sort.getSecondary(b);
+				tertiary = sort.getTertiary(b);
+				if (num++ == 0) {
+					Integer max = maxSec.get(primary);
+					secondary += max == null ? 0 : max;
+					if (charFlags(code) == 1) {
+						max = maxTert.get(primary);
+						tertiary += max == null ? 0 : max;
+					}
+				} else {
+					secondary = 1;
+				}
+				cp.setSecondary((byte) (secondary));
+				cp.setTertiary((byte) (tertiary));
+			} else {
+				num++;
+				secondary = sort.getSecondary(b) & 0xff;
+				cp.setSecondary((byte) (secondary + 7));
+
+				tertiary = sort.getTertiary(b) & 0xff;
+				cp.setTertiary((byte) (tertiary + 2));
+			}
+			expansions.add(cp);
 		}
 
-		sort.addExpansion(code.getBval(), charFlags(code.getCval()), expansionList);
+		int flags = charFlags(code) | (num-1) << 4;
+		sort.add(code.getBval(), expansions.size() - num + 1, 0, 0, flags);
 		state = IN_INITIAL;
 	}
 
@@ -324,34 +373,51 @@ public class SrtTextReader {
 	 */
 	private void addCharacter(TokenScanner scanner, String val) {
 		Code code = new Code(scanner, val).read();
-		setSortcode(code.getBval());
+		setSortcode(code);
 	}
 
 	/**
 	 * Set the sort code for the given 8-bit character.
-	 * @param ch The same character in unicode.
+	 * @param c the code instance
 	 */
-	private void setSortcode(int ch) {
-		int flags = charFlags(ch);
+	private void setSortcode(Code c) {
+		int flags = charFlags(c);
 		if (cflags.contains("0"))
 			flags = 0;
-
-		sort.add(ch, pos1, pos2, pos3, flags);
+		sort.add(c.getBval(), pos1, pos2, pos3, flags);
 		this.cflags = "";
+
+		if (EXPERIMENTAL) {
+			Integer max = maxSec.get(pos1);
+			if (max == null)
+				max = pos2;
+			else 
+				max = Math.max(pos2, max);
+			maxSec.put(pos1, max);
+			max = maxTert.get(pos1);
+			if (max == null)
+				max = pos3;
+			else 
+				max = Math.max(pos3, max);
+			maxTert.put(pos3, max);
+		}
 	}
 
 	/**
 	 * The flags that describe the kind of character. Known ones
 	 * are letter and digit. There may be others.
-	 * @param ch The actual character (unicode).
+	 * @param c The actual code instance
 	 * @return The flags that apply to it.
 	 */
-	private int charFlags(int ch) {
+	private int charFlags(Code c) {
 		int flags = 0;
-		if (Character.isLetter(ch) && (Character.getType(ch) & Character.MODIFIER_LETTER) == 0)
-			flags = 1;
-		if (Character.isDigit(ch))
-			flags = 2;
+		if (c.getBval() <= 0xff) {
+			int ch = c.getCval();
+			if (ch == 'ª' || (Character.isLetter(ch) && (Character.getType(ch) & Character.MODIFIER_LETTER) == 0))
+				flags = 1;
+			if (Character.isDigit(ch))
+				flags = 2;
+		}
 		return flags;
 	}
 
@@ -393,6 +459,10 @@ public class SrtTextReader {
 		String outfile = "out.srt";
 		if (args.length > 1)
 			outfile = args[1];
+		try {
+			Files.delete(Paths.get(outfile, ""));
+		} catch (Exception e) {
+		}
 		ImgChannel chan = new FileImgChannel(outfile, "rw");
 		SRTFile sf = new SRTFile(chan);
 
diff --git a/src/uk/me/parabola/util/Java2DConverter.java b/src/uk/me/parabola/util/Java2DConverter.java
index 4c3a857..7ea7eaa 100644
--- a/src/uk/me/parabola/util/Java2DConverter.java
+++ b/src/uk/me/parabola/util/Java2DConverter.java
@@ -89,21 +89,21 @@ public class Java2DConverter {
 			// because we use closePath() to signal that
 			--n;
 		}
-		double lastLat = Integer.MAX_VALUE,lastLon = Integer.MAX_VALUE;
+		int 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); 
+			int latHp = co.getHighPrecLat();
+			int lonHp = co.getHighPrecLon();
+			double x = (double)lonHp / (1<<Coord.DELTA_SHIFT); 
+			double y = (double)latHp / (1<<Coord.DELTA_SHIFT); 
 			if (i == 0)
 				path.moveTo(x, y);
 			else {
-				if (lastLon != lon30 || lastLat != lat30)
+				if (lastLon != lonHp || lastLat != latHp)
 					path.lineTo(x, y);
 			}
-			lastLon = lon30;
-			lastLat = lat30;
+			lastLon = lonHp;
+			lastLat = latHp;
 		}
 		path.closePath();
 		return path;
@@ -193,26 +193,26 @@ public class Java2DConverter {
 
 		double[] res = new double[6];
 		PathIterator pit = area.getPathIterator(null);
-		int prevLat30 = Integer.MIN_VALUE;
-		int prevLong30 = Integer.MIN_VALUE;
+		int prevLatHp = Integer.MIN_VALUE;
+		int prevLongHp = Integer.MIN_VALUE;
 
 		while (!pit.isDone()) {
 			int type = pit.currentSegment(res);
 
-			int lat30 = (int)Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
-			int lon30 = (int)Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
+			int latHp = (int)Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
+			int lonHp = (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<>();
-				points.add(Coord.makeHighPrecCoord(lat30, lon30));
+				points.add(Coord.makeHighPrecCoord(latHp, lonHp));
 				break;
 			case PathIterator.SEG_LINETO:
 				assert points != null;
-				if (prevLat30 != lat30 || prevLong30 != lon30) {
-					points.add(Coord.makeHighPrecCoord(lat30, lon30));
+				if (prevLatHp != latHp || prevLongHp != lonHp) {
+					points.add(Coord.makeHighPrecCoord(latHp, lonHp));
 				}
 				break;
 			case PathIterator.SEG_CLOSE:
@@ -237,8 +237,8 @@ public class Java2DConverter {
 						+ ". This is an mkgmap error.");
 			}
 
-			prevLat30 = lat30;
-			prevLong30 = lon30;
+			prevLatHp = latHp;
+			prevLongHp = lonHp;
 
 			pit.next();
 		}
@@ -264,22 +264,22 @@ public class Java2DConverter {
 		
 		List<Coord> coords = null;
 
-		int prevLat30 = Integer.MIN_VALUE;
-		int prevLong30 = Integer.MIN_VALUE;
+		int prevLatHp = Integer.MIN_VALUE;
+		int prevLongHp = Integer.MIN_VALUE;
 
 		while (!pit.isDone()) {
 			int type = pit.currentSegment(res);
 
-			int lat30 = (int) Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
-			int lon30 = (int) Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
+			int latHp = (int) Math.round(res[1] * (1<<Coord.DELTA_SHIFT));
+			int lonHp = (int) Math.round(res[0] * (1<<Coord.DELTA_SHIFT));
 			
 			switch (type) {
 			case PathIterator.SEG_LINETO:
-				if (prevLat30 != lat30 || prevLong30 != lon30) 
-					coords.add(Coord.makeHighPrecCoord(lat30, lon30));
+				if (prevLatHp != latHp || prevLongHp != lonHp) 
+					coords.add(Coord.makeHighPrecCoord(latHp, lonHp));
 
-				prevLat30 = lat30;
-				prevLong30 = lon30;
+				prevLatHp = latHp;
+				prevLongHp = lonHp;
 				break;
 			case PathIterator.SEG_MOVETO: 
 			case PathIterator.SEG_CLOSE:
@@ -298,13 +298,13 @@ public class Java2DConverter {
 				}
 				if (type == PathIterator.SEG_MOVETO){
 					coords = new ArrayList<>();
-					coords.add(Coord.makeHighPrecCoord(lat30, lon30));
-					prevLat30 = lat30;
-					prevLong30 = lon30;
+					coords.add(Coord.makeHighPrecCoord(latHp, lonHp));
+					prevLatHp = latHp;
+					prevLongHp = lonHp;
 				} else {
 					coords = null;
-					prevLat30 = Integer.MIN_VALUE;
-					prevLong30 = Integer.MIN_VALUE;
+					prevLatHp = Integer.MIN_VALUE;
+					prevLongHp = Integer.MIN_VALUE;
 				}
 				break;
 			default:
diff --git a/src/uk/me/parabola/util/ShapeSplitter.java b/src/uk/me/parabola/util/ShapeSplitter.java
index fa882ee..6fc14f5 100644
--- a/src/uk/me/parabola/util/ShapeSplitter.java
+++ b/src/uk/me/parabola/util/ShapeSplitter.java
@@ -18,6 +18,14 @@ import java.awt.geom.PathIterator;
 import java.awt.geom.Rectangle2D;
 import java.util.Arrays;
 
+// RWB new bits
+import java.util.ArrayList;
+import java.util.List;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+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;
 
 public class ShapeSplitter {
@@ -269,4 +277,435 @@ public class ShapeSplitter {
 		return pointsToPath2D(outputList, countVals);
 	}
 
-}
+/* Dec16/Jan17. Ticker Berkin. New implementation for splitting shapes.
+
+Eventually maybe can be used instead of some of the above and in following code:
+
+done	mkgmap/build/MapArea.java
+	mkgmap/filters/PolygonSplitterBase.java
+	mkgmap/filters/ShapeMergeFilter.java
+	mkgmap/general/AreaClipper.java
+	mkgmap/general/PolygonClipper.java
+	mkgmap/reader/osm/MultiPolygonRelation.java
+Maybe not this lot:
+	mkgmap/reader/osm/boundary/BoundaryConverter.java
+	mkgmap/reader/osm/boundary/BoundaryCoverageUtil.java
+	mkgmap/reader/osm/boundary/BoundaryDiff.java
+	mkgmap/reader/osm/boundary/BoundaryElement.java
+	mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java
+	mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+	mkgmap/reader/osm/boundary/BoundaryRelation.java
+	mkgmap/reader/osm/boundary/BoundarySaver.java
+	mkgmap/reader/osm/boundary/BoundaryUtil.java
+? not sure about these
+	mkgmap/reader/osm/SeaGenerator.java
+	mkgmap/sea/optional/PrecompSeaGenerator.java
+	mkgmap/sea/optional/PrecompSeaMerger.java
+	util/ElementQuadTreeNode.java
+	util/Java2DConverter.java
+	util/QuadTreeNode.java
+*/
+
+	/**
+	 * Service routine for processLineList. Processes a nested list of holes within a shape or
+	 * list of shapes within a hole.
+	 *
+	 * Recurses to check for and handle the opposite of what has been called to process.
+	 *
+	 * @param startInx starting point in list.
+	 * @param endEnclosed point where starting line ends on dividing line.
+	 * @param addHolesToThis if not null, then called from a shape and subtract holes from it
+	 * otherwise new shapes within a hole.
+	 * @param lineInfo list of lines.
+	 * @param origList list of shapes to which we append new shapes.
+	 */
+	private static int doLines(int startInx, int endEnclosed, MergeCloseHelper addHolesToThis,
+				   List<MergeCloseHelper> lineInfo, List<List<Coord>> origList) {
+		int inx = startInx;
+		final boolean calledFromHole = addHolesToThis == null;
+		while (inx < lineInfo.size()) {
+			MergeCloseHelper thisLine = lineInfo.get(inx);
+			if (thisLine.highPoint > endEnclosed) // only do enclosed items
+				break; // simple - fully enclosed
+			if (thisLine.highPoint == endEnclosed && thisLine.highPoint == endEnclosed) // consider carefully
+				if (calledFromHole == (thisLine.areaOrHole == -1))
+					break; // stop if same type
+			inx = doLines(inx+1, thisLine.highPoint, calledFromHole ? thisLine : null, lineInfo, origList);
+			if (calledFromHole) // handling list of shapes
+				thisLine.closeAppend(origList, true);
+			else // handling list of holes
+				addHolesToThis.addHole(thisLine);
+		}
+		return inx;
+	} // doLines
+
+	/**
+	 * Service routine for splitShape. Takes list of lines and appends distinct shapes
+	 * @param lineInfo list of lines that start and end on the dividing line (or orig startPoint)
+	 * @param origList list of shapes to which we append new shapes formed from above
+	 * @param fullArea of orig polygon. used for sign and handling of last line segment
+	 */
+	private static void processLineList(List<MergeCloseHelper> lineInfo, List<List<Coord>> origList, long fullArea) {
+		if (origList == null) // never wanted this side
+			return;
+		MergeCloseHelper firstLine = lineInfo.get(0);
+		if (lineInfo.size() == 1) { // single shape that never crossed line
+			if (!firstLine.points.isEmpty()) // all on this side
+				firstLine.closeAppend(origList, false);
+			return;
+		}
+		// look at last item in list of lines
+		MergeCloseHelper lastLine = lineInfo.get(lineInfo.size()-1);
+		if (lastLine.points.isEmpty()) // will be empty if started on other side
+			lineInfo.remove(lineInfo.size()-1);
+		else { // ended up on this side and must have crossed the line
+			// so first element is really the end of the last
+			lastLine.combineFirstIntoLast(firstLine, fullArea);
+			lineInfo.remove(0);
+			firstLine = lineInfo.get(0);
+		}
+		if (lineInfo.size() == 1) { // simple poly that crossed once and back
+			firstLine.setMoreInfo(0);
+			firstLine.closeAppend(origList, true);
+			return;
+		}
+		// Above were the simple cases - probably 99% of calls.
+
+		// splitShape has generated a list of lines that start and end on the dividing line.
+		// These lines don't cross. Order them by their lowest point on the divider, but note which
+		// direction they go. The first (and last) line must define a shape. Starting with this
+		// shape; the next line up, if it is within this shape, must define a hole and
+		// so is added to the list of points for the shape. For the hole, recurse to
+		// handle any shapes enclosed. Repeat until we reach the end of the enclosing
+		// space.
+
+		final int fullAreaSign = Long.signum(fullArea);
+		// check and set any missing directions based on signs of full/area
+		boolean someDirectionsNotSet = false;
+		int areaDirection = 0;
+		String diagMsg = "";
+ 		for (MergeCloseHelper thisLine : lineInfo) {
+			thisLine.setMoreInfo(fullAreaSign);
+			if (thisLine.direction == 0)
+				someDirectionsNotSet = true;
+			else if (thisLine.areaToLine != 0) {
+				int tmpAreaDirection = thisLine.direction * Long.signum(thisLine.areaToLine);
+				if (areaDirection == 0)
+					areaDirection = tmpAreaDirection;
+				else if (areaDirection != tmpAreaDirection)
+					diagMsg += "Direction/Area conflict.";
+			}
+		}
+		if (someDirectionsNotSet) {
+			if (areaDirection == 0)
+				diagMsg += "Cant deduce direction/Area mapping.";
+			else
+				for (MergeCloseHelper thisLine : lineInfo)
+					if (thisLine.direction == 0)
+						thisLine.direction = areaDirection * Long.signum(thisLine.areaToLine);
+		}
+		if (diagMsg != "") {
+			log.warn(diagMsg, "Probably self-intersecting polygon", fullAreaSign, someDirectionsNotSet, areaDirection);
+			for (MergeCloseHelper thisLine : lineInfo) {
+				log.warn(thisLine);
+				if (log.isDebugEnabled())
+					for (Coord co : thisLine.points)
+						log.debug("line", co, co.getHighPrecLat(), co.getHighPrecLon());
+			}
+		}
+
+		lineInfo.sort(null);
+
+		int dummy = doLines(0, Integer.MAX_VALUE, null, lineInfo, origList);
+		assert dummy == lineInfo.size();
+	} // processLineList
+
+	private static List<Coord> startLine(List<MergeCloseHelper> lineInfo) {
+		MergeCloseHelper thisLine = new MergeCloseHelper();
+		lineInfo.add(thisLine);
+		return thisLine.points;
+	} // startLine
+
+	private static void openLine(List<MergeCloseHelper> lineInfo, Coord lineCoord, int lineAlong, long currentArea) {
+		MergeCloseHelper thisLine = lineInfo.get(lineInfo.size()-1);
+		thisLine.points.add(lineCoord);
+		thisLine.firstPoint = lineAlong;
+		thisLine.startingArea = currentArea;
+	} // openLine
+
+	private static List<Coord> closeLine(List<MergeCloseHelper> lineInfo, Coord lineCoord, int lineAlong, long currentArea) {
+		MergeCloseHelper thisLine = lineInfo.get(lineInfo.size()-1);
+		thisLine.points.add(lineCoord);
+		thisLine.lastPoint = lineAlong;
+		thisLine.endingArea = currentArea;
+		return startLine(lineInfo);
+	} // closeLine
+
+	/**
+	 * Helper class for splitShape. Holds information about line.
+	 * Sorts array/list of itself according to lowest point on dividing line.
+	 */
+	private static class MergeCloseHelper implements Comparable<MergeCloseHelper> {
+
+		List<Coord> points;
+		int firstPoint, lastPoint;
+		long startingArea, endingArea; // from runningArea
+		int direction;
+		int lowPoint, highPoint;
+		long areaToLine;
+		int areaOrHole; // +1/-1
+
+		MergeCloseHelper() {
+			points = new ArrayList<>();
+		} // MergeCloseHelper
+
+		public String toString() {
+			return "fp=" + firstPoint + " lp=" + lastPoint + " area=" + areaToLine + " #=" + points.size() + " " +
+				points.get(1).toOSMURL() + " " + points.get(points.size()/2).toOSMURL();
+		} // toString
+
+		void setMoreInfo(int fullAreaSign) {
+			this.direction = Integer.signum(lastPoint - firstPoint);
+			if (this.direction > 0) {
+				this.lowPoint = firstPoint;
+				this.highPoint = lastPoint;
+			} else {
+				this.lowPoint = lastPoint;
+				this.highPoint = firstPoint;
+			}
+			this.areaToLine = this.endingArea - this.startingArea; // correct if closed
+			// !!! also correct when close along the line, because would do:
+//			this.areaToLine += (long)(lastPoint + firstPoint) * (dividingLine - dividingLine);
+			this.areaOrHole = fullAreaSign * Long.signum(this.areaToLine);
+		} // setMoreInfo
+
+		void combineFirstIntoLast(MergeCloseHelper other, long fullArea) {
+			this.points.addAll(other.points);
+			this.lastPoint = other.lastPoint;
+			this.endingArea = fullArea + other.endingArea;
+		} // combineFirstIntoLast
+
+		public int compareTo(MergeCloseHelper other) {
+			int cmp = this.lowPoint - other.lowPoint;
+			if (cmp != 0)
+				return cmp;
+			// for same lowPoint, sort highPoint other way around to enclose as much as possible
+			cmp = other.highPoint - this.highPoint;
+			if (cmp != 0)
+				return cmp;
+			// case where when have same start & end
+			// return the shape as lower than the hole, to handle first
+			cmp = other.areaOrHole - this.areaOrHole;
+			if (cmp != 0)
+				return cmp;
+			log.warn("Lines hit divider at same points and have same area sign", "this:", this, "other:", other);
+			// after this, don't think anthing else possible, but, for stability
+			return this.direction - other.direction;
+		} // compareTo
+
+		void addHole(MergeCloseHelper other) {
+			if (other.areaToLine == 0)
+				return; // spike into this area. cf. closeAppend()
+			// shapes must have opposite directions.
+			if (this.direction == 0 && other.direction == 0)
+				log.warn("Direction of shape and hole indeterminate; probably self-intersecting polygon", "this:", this, "other:", other);
+			else if (this.direction != 0 && other.direction != 0 && this.direction == other.direction)
+				log.warn("Direction of shape and hole conflict; probably self-intersecting polygon", "this:", this, "other:", other);
+			else if (this.direction < 0 || other.direction > 0) {
+				this.points.addAll(other.points);
+				if (this.direction == 0)
+					this.direction = -1;
+			} else { // add at start
+				other.points.addAll(this.points);
+				this.points = other.points;
+				if (this.direction == 0)
+					this.direction = +1;
+			}
+			this.areaToLine += other.areaToLine;
+		} // addHole
+
+		/**
+		 * closes a shape and appends to list.
+		 *
+		 * If the shape starts and ends at the same point on the dividing line then
+		 * there is no need to close it. Also check for and chuck a spike, which happens
+		 * if there is a single point just across the dividing line and the two intersecting
+		 * points ended up being the same or an edge runs back on itself exactly.
+		 *
+		 * @param origList list of shapes to which we append new shapes.
+		 * @param onDividingLine if false, shape not cut so don't assume/care much about it
+		 */
+		void closeAppend(List<List<Coord>> origList, boolean onDividingLine) {
+			final Coord firstCoord = points.get(0);
+			final int lastPointInx = points.size()-1;
+			if (firstCoord.highPrecEquals(points.get(lastPointInx))) { // by chance, ends up closed
+				// There is no need to close the shape along the line, but am finding, for shapes that never crossed the
+				// dividing line, quite a few that, after splitShapes has rotating the shape by 1, have first and last
+				// points highPrecEquals but they are different objects.
+				// This means that the original first and last were the same object, but the second and last were highPrecEquals!
+				// If left like this, it might be flagged by ShapeMergeFilter.
+				// NB: if no coordPool, likely to be different closing object anyway
+				if (firstCoord != points.get(lastPointInx))
+					points.set(lastPointInx, firstCoord); // quietly replace with first point
+			} else
+				points.add(firstCoord); // close it
+			if (onDividingLine) { // otherwise just one shape untouched by chopping
+/* this is quite expensive! and drastic if there is a problem
+				assert Math.abs(this.areaToLine) == Math.abs(uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(points))
+					: "Split calcAreaSize differs";
+// this is less drastic, only ever happens after SplitShape has already detected problem
+				long stdFuncSize = uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(points);
+				if (Math.abs(this.areaToLine) != Math.abs(stdFuncSize))
+					log.warn("Split calcAreaSize differs; probably self-intersecting polygon", stdFuncSize, this);
+*/
+				if (this.areaToLine == 0)
+					return;
+			}
+			origList.add(points);
+		} // closeAppend
+
+	} // MergeCloseHelper
+
+	/**
+	 * split a shape with a line
+	 * @param coords the shape. Must be closed.
+	 * @param dividingLine the line in high precision.
+	 * @param isLongitude true if above is line of longitude, false if latitude.
+	 * @param lessList list of shapes to which we append new shapes on lower/left side of line.
+	 * @param moreList list of shapes to which we append new shapes on upper/right side of line.
+	 * @param coordPool if not null, hashmap for created coordinates. Will all be on the line.
+	 */
+	public static void splitShape(List<Coord> coords, int dividingLine, boolean isLongitude,
+				      List<List<Coord>> lessList, List<List<Coord>> moreList,
+				      Long2ObjectOpenHashMap<Coord> coordPool) {
+
+		List<MergeCloseHelper> newLess = null, newMore = null;
+		List<Coord> lessPoly = null, morePoly = null;
+		if (lessList != null) {
+			newLess = new ArrayList<>();
+			lessPoly = startLine(newLess);
+		}
+		if (moreList != null) {
+			newMore = new ArrayList<>();
+			morePoly = startLine(newMore);
+		}
+		/**
+		 * trailXxx variables are the previous coordinate information.
+		 * leadXxx            are for the current coordinate
+		 * lineXxx            are the crossing point
+		 * where Xxx is Coord : Coord
+		 *              Away  : position in plane at right angles to dividing line
+		 *              Along : position in same plane as the dividing line
+		 *              Rel   : -1/0/+1 depending on relationship of Away to dividing line
+		 */
+		Coord trailCoord = null;
+		int trailAway = 0, trailAlong = 0, trailRel = 0;
+		long runningArea = 0;
+
+		for (Coord leadCoord : coords) {
+			final int leadAway  = isLongitude ? leadCoord.getHighPrecLon() : leadCoord.getHighPrecLat();
+			final int leadAlong = isLongitude ? leadCoord.getHighPrecLat() : leadCoord.getHighPrecLon();
+			final int leadRel = Integer.signum(leadAway - dividingLine);
+			if (trailCoord != null) { // use first point as trailing (poly is closed)
+
+				Coord lineCoord = null;
+				int lineAlong = trailAlong; // initial assumption
+				if (trailRel == 0) // trailing point on line
+					lineCoord = trailCoord;
+				else if (leadRel == 0) { // leading point on line
+					lineCoord = leadCoord;
+					lineAlong = leadAlong;
+				} else if (trailRel != leadRel) { // crosses line; make intersecting coord
+					if (lineAlong != leadAlong)
+						lineAlong += Math.round((double)(dividingLine - trailAway) * (leadAlong - trailAlong)
+										  / (leadAway - trailAway));
+					lineCoord = Coord.makeHighPrecCoord(isLongitude ? lineAlong : dividingLine, isLongitude ? dividingLine : lineAlong);
+				}
+				if (lineCoord != null && coordPool != null) {
+					// Add new coords to pool. Also add existing ones if on the dividing line because there is slight
+					// chance that the intersection will coincide with an existing point and ShapeMergeFilter expects
+					// the opening/closing point to be the same object. If we see the original point first,
+					// all is good, but if other way around, it will replace an original point with the created one.
+					final long hashVal = Utils.coord2Long(lineCoord);
+					final Coord replCoord = coordPool.get(hashVal);
+					if (replCoord == null)
+						coordPool.put(hashVal, lineCoord);
+					else
+						lineCoord = replCoord;
+				}
+
+				long extraArea; // add in later to get the area to leading point
+				if (leadRel * trailRel >= 0) // doesn't cross the line
+					extraArea = (long)(trailAlong + leadAlong) * (trailAway - leadAway);
+				else { // calc as 2 points
+					runningArea += (long)(trailAlong + lineAlong) * (trailAway - dividingLine);
+					extraArea = (long)(lineAlong + leadAlong) * (dividingLine - leadAway);
+				}
+
+				if (lessList != null) {
+					if (leadRel < 0) { // this point required
+						if (trailRel >= 0) // previous not on this side, add line point
+							openLine(newLess, lineCoord, lineAlong, runningArea);
+						lessPoly.add(leadCoord);
+					} else if (trailRel < 0) // if this not reqd and prev was, add line point and start new shape
+						lessPoly = closeLine(newLess, lineCoord, lineAlong, runningArea + (leadRel == 0 ? extraArea : 0));
+				}
+
+				// identical to above except other way around
+				if (moreList != null) {
+					if (leadRel > 0) { // this point required
+						if (trailRel <= 0) // previous not on this side, add line point
+							openLine(newMore, lineCoord, lineAlong, runningArea);
+						morePoly.add(leadCoord);
+					} else if (trailRel > 0) // if this not reqd and prev was, add line point and start new shape
+						morePoly = closeLine(newMore, lineCoord, lineAlong, runningArea + (leadRel == 0 ? extraArea : 0));
+				}
+
+				runningArea += extraArea;
+			} // if not first Coord
+			trailCoord = leadCoord;
+			trailAway = leadAway;
+			trailAlong = leadAlong;
+			trailRel = leadRel;
+		} // for leadCoord
+		processLineList(newLess, lessList, runningArea);
+		processLineList(newMore, moreList, runningArea);
+	} // splitShape
+
+
+	/**
+	 * clip a shape with a rectangle
+	 *
+	 * Use above splitShape for each side; just keeping the 1/2 we want each time.
+	 *
+	 * @param coords the shape.
+	 * @param bounds the clipping area.
+	 * @param coordPool if not null, hashmap for created coordinates. Will all be on the edge.
+	 * @return list of shapes.
+	 */
+	public static List<List<Coord>> clipToBounds(List<Coord> coords, Area bounds, Long2ObjectOpenHashMap<Coord> coordPool) {
+		List<List<Coord>> newListA = new ArrayList<>();
+		int dividingLine = bounds.getMinLat() << Coord.DELTA_SHIFT;
+		splitShape(coords, dividingLine, false, null, newListA, coordPool);
+		if (newListA.isEmpty())
+			return newListA;
+		List<List<Coord>> newListB = new ArrayList<>();
+		dividingLine = bounds.getMinLong() << Coord.DELTA_SHIFT;
+		for (List<Coord> aShape : newListA)
+			splitShape(aShape, dividingLine, true, null, newListB, coordPool);
+		if (newListB.isEmpty())
+			return newListB;
+		newListA.clear();
+		dividingLine = bounds.getMaxLat() << Coord.DELTA_SHIFT;
+		for (List<Coord> aShape : newListB)
+			splitShape(aShape, dividingLine, false, newListA, null, coordPool);
+		if (newListA.isEmpty())
+			return newListA;
+		newListB.clear();
+		dividingLine = bounds.getMaxLong() << Coord.DELTA_SHIFT;
+		for (List<Coord> aShape : newListA)
+			splitShape(aShape, dividingLine, true, newListB, null, coordPool);
+		return newListB;
+	} // clipToBounds
+
+} // ShapeSplitter
diff --git a/test/func/read/ImgReadTest.java b/test/func/read/ImgReadTest.java
index e7aff34..dbe4c19 100644
--- a/test/func/read/ImgReadTest.java
+++ b/test/func/read/ImgReadTest.java
@@ -31,7 +31,7 @@ public class ImgReadTest {
 	public void testNet() throws FileNotFoundException {
 		MapReader mr = new MapReader(Utils.joinPath(Args.TEST_RESOURCE_IMG, Args.DEF_MAP_FILENAME3));
 		List<RoadDef> roads = mr.getRoads();
-
-		assertEquals("number of roads", 1355, roads.size());
+		
+		assertEquals("number of roads", 1365, roads.size());
 	}
 }
diff --git a/test/func/route/SimpleRouteTest.java b/test/func/route/SimpleRouteTest.java
index 68ec844..d1910a7 100644
--- a/test/func/route/SimpleRouteTest.java
+++ b/test/func/route/SimpleRouteTest.java
@@ -58,7 +58,7 @@ public class SimpleRouteTest extends Base {
 				count++;
 				System.out.println("TRE size " + size);
 				// Size varies depending on svn modified status
-				assertThat("TRE size", size, new RangeMatcher(1455, 2));
+				assertThat("TRE size", size, new RangeMatcher(1427, 2));
 				break;
 			case "LBL":
 				count++;
diff --git a/test/func/style/ScriptedStyleTest.java b/test/func/style/ScriptedStyleTest.java
index 4915011..92e2766 100644
--- a/test/func/style/ScriptedStyleTest.java
+++ b/test/func/style/ScriptedStyleTest.java
@@ -55,20 +55,23 @@ public class ScriptedStyleTest {
 		assertTrue(d.isDirectory());
 
 		// Only run files ending in .test
-		FilenameFilter filter = new FilenameFilter() {
-			public boolean accept(File dir, String name) {
-				if (name.endsWith(".test"))
-					return true;
-				return false;
-			}
+		FilenameFilter filter = (dir, name) -> {
+			if (name.endsWith(".test"))
+				return true;
+			return false;
 		};
 
 		int count = 0;
 		File[] files = d.listFiles(filter);
+		assert files != null;
 		for (File file : files) {
 			setup();
 			String name = file.getCanonicalPath();
-			StyleTester.runSimpleTest(name);
+			try {
+				StyleTester.runSimpleTest(name);
+			} catch (Exception e) {
+				assertFalse(name, true);
+			}
 			String result = output.toString();
 
 			// Make sure that the result does not contain an error
@@ -96,6 +99,6 @@ public class ScriptedStyleTest {
 		StyleTester.setOut(ps);
 
 		// Make sure that there is a given result set
-		StyleTester.forceUseOfGiven(true);
+		StyleTester.forceUseOfGiven();
 	}
 }
diff --git a/test/resources/rules/if-then-1.test b/test/resources/rules/if-then-1.test
new file mode 100644
index 0000000..bd83817
--- /dev/null
+++ b/test/resources/rules/if-then-1.test
@@ -0,0 +1,20 @@
+
+#
+# Test for an if where a rule modifies the if expression
+#
+
+WAY 1
+A=1
+B=1
+
+<<<lines>>>
+
+if (B=1) then
+   A=1 {set B=0}
+   C!=* {set C=1}
+end
+C=1	[0x1 resolution 24]
+ 
+<<<results>>>
+NO-STRICT
+WAY 1: Line 0x1, labels=[null, null, null, null], res=24-24 (1/1),(2/2),
diff --git a/test/resources/rules/if-then-2.test b/test/resources/rules/if-then-2.test
new file mode 100644
index 0000000..69660fc
--- /dev/null
+++ b/test/resources/rules/if-then-2.test
@@ -0,0 +1,19 @@
+
+#
+# Test for an if where a rule modifies the if expression
+#
+
+WAY 1
+A=1
+B=1
+
+<<<lines>>>
+
+if (B=1) then
+   () {set C=1}
+end
+C=1	[0x1 resolution 24]
+ 
+<<<results>>>
+NO-STRICT
+WAY 1: Line 0x1, labels=[null, null, null, null], res=24-24 (1/1),(2/2),
diff --git a/test/resources/rules/if-then-else-1.test b/test/resources/rules/if-then-else-1.test
new file mode 100644
index 0000000..840354c
--- /dev/null
+++ b/test/resources/rules/if-then-else-1.test
@@ -0,0 +1,27 @@
+
+#
+# Test for an if-else where a rule modifies the if expression
+#
+
+WAY 1
+A=1
+B=1
+
+WAY 2
+A=1
+B=2
+
+<<<lines>>>
+
+if (B=1) then
+   A=1 {set B=0}
+   C!=* {set C=1}
+else
+	A=1 [0x2 resolution 24]		
+end
+C=1	[0x1 resolution 24]
+ 
+<<<results>>>
+NO-STRICT
+WAY 1: Line 0x1, labels=[null, null, null, null], res=24-24 (1/1),(2/2),
+WAY 2: Line 0x2, labels=[null, null, null, null], res=24-24 (1/1),(2/2),
diff --git a/test/resources/rules/two-types1.test b/test/resources/rules/two-types1.test
new file mode 100644
index 0000000..4cf47ea
--- /dev/null
+++ b/test/resources/rules/two-types1.test
@@ -0,0 +1,20 @@
+
+#
+# Test for a rule with one expression and two types 
+#
+
+WAY 1
+A=1
+B=1
+
+<<<lines>>>
+
+A=1 & B=1 [0x1 resolution 24][0x10801 resolution 18]
+# short form for
+# A=1 & B=1 [0x1 resolution 24 continue]
+# A=1 & B=1 [0x10801 resolution 18]
+ 
+<<<results>>>
+NO-STRICT
+WAY 1: Line 0x1, labels=[null, null, null, null], res=24-24 (1/1),(2/2),
+WAY 1: Line 0x10801, labels=[null, null, null, null], res=18-24 (1/1),(2/2),
diff --git a/test/uk/me/parabola/imgfmt/UtilsTest.java b/test/uk/me/parabola/imgfmt/UtilsTest.java
index 76e66ed..2582594 100644
--- a/test/uk/me/parabola/imgfmt/UtilsTest.java
+++ b/test/uk/me/parabola/imgfmt/UtilsTest.java
@@ -35,22 +35,31 @@ public class UtilsTest {
 		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 latHp = -10; latHp < 10; latHp++){
+			for (int lonHp = -10; lonHp < 10; lonHp++){
 				for (int k = 0; k < 3;k++){
 					Coord co; 
 					if (k == 0)
-						co = Coord.makeHighPrecCoord(lat30, lon30); 
+						co = Coord.makeHighPrecCoord(latHp, lonHp); 
 					else if (k == 1)
-						co = Coord.makeHighPrecCoord(lat30+lowerLeft.getHighPrecLat(), lon30+lowerLeft.getHighPrecLon());
+						co = Coord.makeHighPrecCoord(latHp+lowerLeft.getHighPrecLat(), lonHp+lowerLeft.getHighPrecLon());
 					else
-						co = Coord.makeHighPrecCoord(lat30+upperRight.getHighPrecLat(), lon30+upperRight.getHighPrecLon());
+						co = Coord.makeHighPrecCoord(latHp+upperRight.getHighPrecLat(), lonHp+upperRight.getHighPrecLon());
 					long key = Utils.coord2Long(co);
 					Coord old = map.put(key, co);
 					assertTrue("key not unique", old==null);
 				}
 			}
 		}
-		
+	}
+	
+	@Test
+	public void testRoundUp() {
+		assertEquals(1, Utils.roundUp(1, 0));
+		assertEquals(-1, Utils.roundUp(-1, 0));
+		assertEquals(2, Utils.roundUp(1, 1));
+		assertEquals(0, Utils.roundUp(-1, 1));
+		assertEquals(2, Utils.roundUp(2, 1));
+		assertEquals(-2, Utils.roundUp(-2, 1));
 	}
 }
diff --git a/test/uk/me/parabola/imgfmt/app/CoordTest.java b/test/uk/me/parabola/imgfmt/app/CoordTest.java
index 52e6a10..a3848b4 100644
--- a/test/uk/me/parabola/imgfmt/app/CoordTest.java
+++ b/test/uk/me/parabola/imgfmt/app/CoordTest.java
@@ -12,9 +12,16 @@
  */
 package uk.me.parabola.imgfmt.app;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+import uk.me.parabola.mkgmap.reader.osm.Way;
 
 /**
  * Test some basic methods of the Coord class regarding distance and bearing calculations
@@ -86,4 +93,93 @@ public class CoordTest {
 		Coord russia4 = russia1.destOnRhumLine(10000, 0.0);
 		assertEquals(russia4.getLongitude(), russia1.getLongitude());
 	}
+	
+	@Test
+	public void testOffset() {
+		assertEquals(pLAX, pLAX.offset(60, 100).offset(120, 100).offset(180, 100).offset(240, 100).offset(300, 100).offset(360, 100));
+	}
+	
+	@Test
+	public void testOverflow() {
+		Coord c1 = new Coord(90.0,180.0);
+		Coord c2 = new Coord(90.0,-180.0);
+		assertFalse(c1.equals(c2));
+		assertFalse(c1.highPrecEquals(c2));
+		assertEquals(0,c1.distance(c2),0.0000001);
+		assertEquals(0x800000, c1.getLongitude()); 
+	}
+	
+	@SuppressWarnings("unused")
+	@Test 
+	public void testRounding() {
+		if (Coord.DELTA_SHIFT + 24 == Integer.SIZE) {
+			Coord c1 = new Coord(61.0000001, -23.0000001);
+			Coord c2 = new Coord(61.0000000, -23.0000001);
+			Coord c3 = new Coord(61.0000001, -23.0000000);
+			assertFalse(c1.highPrecEquals(c2));
+			assertFalse(c1.highPrecEquals(c3));
+			assertFalse(c2.highPrecEquals(c3));
+			assertTrue(c1.equals(c2));
+			assertTrue(c1.equals(c3));
+			assertTrue(c2.equals(c3));
+			assertEquals(61.0000001d,c1.getLatDegrees(),0.0000001);
+			assertEquals(61.0000000d,c2.getLatDegrees(),0.0000001);
+			assertEquals(61.0000001d,c3.getLatDegrees(),0.0000001);
+			assertEquals(-23.0000001d,c1.getLonDegrees(),0.0000001);
+			assertEquals(-23.0000001d,c2.getLonDegrees(),0.0000001);
+			assertEquals(-23.0000000d,c3.getLonDegrees(),0.0000001);
+			double lat1 = -1;
+			double lat2 = 1;
+			double lon1 = -1;
+			double lon2 = 1;
+			for (int i = 0; i < 1000; i++) {
+				Coord ct1 = new Coord(lat1,lon1);
+				Coord ct2 = new Coord(lat2,lon2);
+				assertEquals(lat1,ct1.getLatDegrees(),0.0000001);
+				assertEquals(lat2,ct2.getLatDegrees(),0.0000001);
+				assertEquals(lon1,ct1.getLonDegrees(),0.0000001);
+				assertEquals(lon2,ct2.getLonDegrees(),0.0000001);
+				lat1 = Math.nextAfter((float) lat1, 90);
+				lat2 = Math.nextAfter((float) lat2, 90);
+				lon1 = Math.nextAfter((float) lon1, 180);
+				lon2 = Math.nextAfter((float) lon2, 180);
+			}
+		}
+	}
+
+	@Test
+	public void alternativePos() {
+		Coord c1 = new Coord(61.0000001, -23.0000001);
+		List<Coord> alt1 = c1.getAlternativePositions();
+		assertEquals(1, alt1.size());
+		for (Coord c : alt1) {
+			assertTrue(c.distance(c1) < 3);
+			assertTrue(c.hasAlternativePos());
+			List<Coord> altx = c.getAlternativePositions();
+			assertFalse(altx.isEmpty());
+		}
+		Coord c2 = new Coord(61.1, -23.3);
+		List<Coord> alt2 = c2.getAlternativePositions();
+		assertEquals(3, alt2.size());
+		for (Coord c : alt2) {
+			assertTrue(c.distance(c2) < 3);
+			assertTrue(c.hasAlternativePos());
+			List<Coord> altx = c.getAlternativePositions();
+			assertFalse(altx.isEmpty());
+		}
+	}
+	
+	
+	@Test
+	public void testPlanet() throws Exception {
+		final uk.me.parabola.imgfmt.app.Area planet = uk.me.parabola.imgfmt.app.Area.PLANET;
+		long testVal = ShapeMergeFilter.calcAreaSizeTestVal(planet.toCoords());
+		double areaSizeCoords = (double) testVal / (2 * (1<<Coord.DELTA_SHIFT) * (1<<Coord.DELTA_SHIFT));
+		areaSizeCoords = Math.abs(areaSizeCoords);
+		double areaSizeBounds = (double) planet.getHeight() * planet.getWidth();
+		assertEquals(areaSizeBounds, areaSizeCoords, 0.0001);
+		boolean dir = Way.clockwise(planet.toCoords()); 
+		assertFalse(dir);
+	}	
+	
 }
diff --git a/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java b/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
index df6fa01..db5ffd3 100644
--- a/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
+++ b/test/uk/me/parabola/imgfmt/app/srt/SortExpandTest.java
@@ -50,14 +50,6 @@ public class SortExpandTest {
 		checkOrder("asrst", "asßst");
 	}
 
-	/**
-	 * Expanded letters should sort equal to what they expand to.
-	 */
-	@Test
-	public void testAgainstExpansion() {
-		assertEquals(0, compareKey("asssst", "asßst"));
-	}
-
 	@Test
 	public void testExpandSize() {
 		// make sure buffer doesn't overflow when all characters are expanded.
diff --git a/test/uk/me/parabola/imgfmt/app/srt/SortTest.java b/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
index a87e341..611ff00 100644
--- a/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
+++ b/test/uk/me/parabola/imgfmt/app/srt/SortTest.java
@@ -137,10 +137,8 @@ public class SortTest {
 	public void testExpanded() {
 		assertEquals(-1, keyCompare("æ", "Ae"));
 		assertEquals(-1, keyCompare("æ", "AE"));
-		assertEquals(0, keyCompare("æ", "ae"));
 		assertEquals(-1, keyCompare("æ", "aE"));
 		assertEquals(1, keyCompare("AE", "aE"));
-		assertEquals(1, keyCompare("Æ", "aE"));
 
 		assertEquals(-1, keyCompare("–TMO", "–uÊÑÇ"));
 		assertEquals(-1, keyCompare("–™O", "–uÊÑÇ"));
@@ -152,8 +150,7 @@ public class SortTest {
 	}
 
 	@Test
-	public void testExpandedAndIgnorable() {
-		assertEquals(0, keyCompare("æ", "ae"));
+	public void testIgnorable() {
 		assertEquals(-1, keyCompare("\u007fæ", "Ae"));
 	}
 
diff --git a/test/uk/me/parabola/mkgmap/filters/LineSplitterFilterTest.java b/test/uk/me/parabola/mkgmap/filters/LineSplitterFilterTest.java
new file mode 100644
index 0000000..4461c74
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/filters/LineSplitterFilterTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.build.LayerFilterChain;
+import uk.me.parabola.mkgmap.general.MapElement;
+import uk.me.parabola.mkgmap.general.MapLine;
+
+/**
+ * Test for the {@link LineSplitterFilter}.
+ * @author Gerd Petermann
+ *
+ */
+public class LineSplitterFilterTest {
+	
+	@Test
+	public void testSizes(){
+		List<Coord> points = new ArrayList<>();
+		points.add(new Coord(1,1));
+		for (int n = 2; n < 10 * LineSplitterFilter.MAX_POINTS_IN_LINE; n++) {
+			points.add(new Coord(n,n));
+			test(points);
+		}
+	}	
+	
+	private void test(List<Coord> points) {
+		MapLine l = new MapLine();
+		l.setPoints(points);
+		FilterConfig config = new FilterConfig() {{
+			setResolution(24);
+			setLevel(0);
+		}};
+		LayerFilterChain chain = new LayerFilterChain(config);
+		LineSplitterFilter filter = new LineSplitterFilter();
+		filter.init(config);
+		chain.addFilter(filter);
+		TestFilter testFilter = new TestFilter(l);
+		chain.addFilter(testFilter);
+		chain.doFilter(l);
+		testFilter.check();
+	}
+	
+	private class TestFilter extends BaseFilter implements MapFilter {
+		final MapLine origLine; 
+		final int origSize;
+		int count;
+		int newSize;
+		Coord lastPoint;
+		
+		TestFilter(MapLine orig) {
+			origLine = orig;
+			origSize = orig.getPoints().size();
+		}
+
+		public void check() {
+			Assert.assertEquals("final test " + origSize, origSize, newSize);
+			int neededParts = 1;
+			int rem = origSize - LineSplitterFilter.MAX_POINTS_IN_LINE;
+			if (rem > 0) { 
+				neededParts += rem / (LineSplitterFilter.MAX_POINTS_IN_LINE - 1);
+				if (rem % (LineSplitterFilter.MAX_POINTS_IN_LINE - 1) != 0)
+					++neededParts; 
+			}
+			Assert.assertEquals("too many parts " + origSize, neededParts, count);
+		}
+
+		public void doFilter(MapElement element, MapFilterChain next) {
+			MapLine line = (MapLine) element;
+			int n = line.getPoints().size();
+			count++;
+			Assert.assertTrue("too many points " + origSize, n <= LineSplitterFilter.MAX_POINTS_IN_LINE);
+			if (origLine.getPoints().size() >= LineSplitterFilter.MAX_POINTS_IN_LINE / 2)
+				Assert.assertTrue("too few points in part" + origSize + " " + n, n >= LineSplitterFilter.MAX_POINTS_IN_LINE / 2);
+			if (newSize == 0) {
+				newSize = n;
+			} else {
+				Assert.assertTrue("new part doesn't start with same point " + origSize,
+						lastPoint == line.getPoints().get(0));
+				newSize += n - 1;
+				ArrayList<Coord> points = new ArrayList<>();
+				Assert.assertTrue("parts are wrong " + origSize, points.size() <= origSize);				
+			}
+			lastPoint = line.getPoints().get(n - 1);
+		}
+	}
+	
+}
diff --git a/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
index bbc1955..a15185c 100644
--- a/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
+++ b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
@@ -29,11 +29,16 @@ 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>(){
+		/**
+		 * 
+		 */
+		private static final long serialVersionUID = 1L;
+
 		{
-			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);
+			for (int latHp = 0; latHp < 100; latHp +=5){
+				for (int lonHp = 0; lonHp < 100; lonHp += 5){
+					Coord co = Coord.makeHighPrecCoord(latHp, lonHp);
+					put(latHp*1000 + lonHp,co);
 				}
 			}
 		}
@@ -41,7 +46,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testAreaTestVal(){
-		List<Coord> points = new ArrayList<Coord>(){{
+		List<Coord> points = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(10,10));
 			add(getPoint(30,10));
 			add(getPoint(30,30));
@@ -56,7 +66,12 @@ public class ShapeMergeFilterTest {
 	 */
 	@Test
 	public void testSimpleSharingOne(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(15,10));
 			add(getPoint(30,25));
 			add(getPoint(25,30));
@@ -65,7 +80,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(15,10)); // close
 		}};
 		
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(25,30));
 			add(getPoint(30,35));
 			add(getPoint(20,40));
@@ -80,7 +100,12 @@ public class ShapeMergeFilterTest {
 	 */
 	@Test
 	public void testSimpleNonOverlapping(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(15,10));
 			add(getPoint(30,25));
 			add(getPoint(25,30));
@@ -89,7 +114,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(15,10)); // close
 		}};
 		
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(25,30));
 			add(getPoint(30,35));
 			add(getPoint(20,40));
@@ -105,7 +135,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void test3SharedPointsNonOverlapping(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(15,10));
 			add(getPoint(30,25));
 			add(getPoint(25,30));
@@ -115,7 +150,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(15,10));// close
 		}};
 		
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(25,30));
 			add(getPoint(30,35));
 			add(getPoint(20,40));
@@ -132,7 +172,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void test2SharedPointsNoEdge(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(15,10));
 			add(getPoint(30,25));
 			add(getPoint(25,30));
@@ -141,7 +186,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(15,10));// close
 		}};
 		
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(25,30));
 			add(getPoint(30,35));
 			add(getPoint(20,40));
@@ -159,7 +209,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testCloseUFormed(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			// u-formed shaped (open at top)
 			add(getPoint(15,50));
 			add(getPoint(30,50));
@@ -173,7 +228,12 @@ public class ShapeMergeFilterTest {
 		}};
 
 
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(35,50));
 			add(getPoint(35,70));
 			add(getPoint(30,70));
@@ -192,7 +252,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testFillUFormed(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			// u-formed shaped (open at top)
 			add(getPoint(15,50));
 			add(getPoint(30,50));
@@ -206,7 +271,12 @@ public class ShapeMergeFilterTest {
 		}};
 
 		
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(30,55));
 			add(getPoint(30,65));
 			add(getPoint(20,65));
@@ -222,7 +292,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testFillHole(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			// a rectangle with a hole 
 			add(getPoint(35,50));
 			add(getPoint(35,70));
@@ -237,7 +312,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(35,50));// close
 		}};
 
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(30,55));
 			add(getPoint(30,65));
 			add(getPoint(20,65));
@@ -249,7 +329,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testDuplicate(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(30,55));
 			add(getPoint(30,65));
 			add(getPoint(20,65));
@@ -263,7 +348,12 @@ public class ShapeMergeFilterTest {
 
 	@Test
 	public void testOverlap(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(30,55));
 			add(getPoint(30,65));
 			add(getPoint(20,65));
@@ -271,7 +361,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(30,55)); // close
 		}};
 
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(30,55));
 			add(getPoint(30,65));
 			add(getPoint(25,65));
@@ -288,7 +383,12 @@ public class ShapeMergeFilterTest {
 	 */
 	@Test
 	public void testTwoWShaped(){
-		List<Coord> points1 = new ArrayList<Coord>(){{
+		List<Coord> points1 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(0,5));
 			add(getPoint(35,5));
 			add(getPoint(35,20));
@@ -303,7 +403,12 @@ public class ShapeMergeFilterTest {
 			add(getPoint(0,5)); // close
 		}};
 
-		List<Coord> points2 = new ArrayList<Coord>(){{
+		List<Coord> points2 = new ArrayList<Coord>(){/**
+			 * 
+			 */
+			private static final long serialVersionUID = 1L;
+
+		{
 			add(getPoint(35,35));
 			add(getPoint(35,20));
 			add(getPoint(30,15));
diff --git a/test/uk/me/parabola/mkgmap/general/MapLineTest.java b/test/uk/me/parabola/mkgmap/general/MapLineTest.java
index 9437d90..a8cdf4e 100644
--- a/test/uk/me/parabola/mkgmap/general/MapLineTest.java
+++ b/test/uk/me/parabola/mkgmap/general/MapLineTest.java
@@ -17,6 +17,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.junit.Test;
@@ -27,17 +28,10 @@ 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));
-		}};
+		List<Coord> points1 = new ArrayList<>(
+				Arrays.asList(new Coord(30, 55), new Coord(30, 65), new Coord(20, 65), new Coord(20, 55)));
+		List<Coord> points2 = new ArrayList<>(
+				Arrays.asList(new Coord(10, 20), new Coord(30, 30), new Coord(30, 55)));
 
 		MapLine ml = new MapLine();
 		ml.setPoints(new ArrayList<>(points1));
diff --git a/test/uk/me/parabola/mkgmap/reader/osm/TagsTest.java b/test/uk/me/parabola/mkgmap/reader/osm/TagsTest.java
index ae9651f..d1fd044 100644
--- a/test/uk/me/parabola/mkgmap/reader/osm/TagsTest.java
+++ b/test/uk/me/parabola/mkgmap/reader/osm/TagsTest.java
@@ -17,7 +17,7 @@
 package uk.me.parabola.mkgmap.reader.osm;
 
 import java.util.Arrays;
-import java.util.Iterator;
+import java.util.NoSuchElementException;
 
 import static org.junit.Assert.*;
 import org.junit.Test;
@@ -106,20 +106,21 @@ public class TagsTest {
 			tags.put(ts[0], ts[1]);
 		return tags;
 	}
-
 	/**
-	 * Create an iterator over the tags.  This must be initialised to the
-	 * values in SMALL_SET.
-	 * @param tags The tags containing values from SMALL_SET.
-	 * @return An iterator that has iterated over all the tags in set.
+	 * Test removing tags.
 	 */
-	private Iterator<String> iterateOverTags(Tags tags) {
-		Iterator<String> it = tags.iterator();
-		int n = SMALL_SET.length * 2;
-		for (int i = 0; i < n; i++) {
-			assertTrue(it.hasNext());
-			assertNotNull("result should be non null", it.next());
-		}
-		return it;
+	@Test(expected = NoSuchElementException.class)
+	public void testIteratorNoNext() {
+		Tags tags = new Tags();
+		tags.entryIterator().next();
 	}
+
+	@Test
+	public void testIteratorNextWithoutHasNext() {
+		Tags tags = new Tags();
+		tags.put("test", "true");
+		assertEquals(1, tags.size());
+		assertEquals("true", tags.entryIterator().next().getValue());
+	}
+
 }
diff --git a/test/uk/me/parabola/util/Java2DConverterTest.java b/test/uk/me/parabola/util/Java2DConverterTest.java
index 46a7551..f653e88 100644
--- a/test/uk/me/parabola/util/Java2DConverterTest.java
+++ b/test/uk/me/parabola/util/Java2DConverterTest.java
@@ -12,16 +12,19 @@
  */
 package uk.me.parabola.util;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import java.awt.geom.Area;
 import java.awt.geom.Path2D;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import uk.me.parabola.imgfmt.app.Coord;
-
 import org.junit.Test;
-import static org.junit.Assert.*;
+
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 
 public class Java2DConverterTest {
 
@@ -104,4 +107,46 @@ public class Java2DConverterTest {
 		assertTrue(singularPolygon2.get(0) ==  singularPolygon2.get(singularPolygon2.size()-1));
 		assertTrue(singularPolygon3.get(0) ==  singularPolygon3.get(singularPolygon3.size()-1));
 	}
+	
+	@Test
+	public void testPolygonConversionAt180() throws Exception {
+		// various calculations near 180.0 
+		List<Coord> points1 = new ArrayList<>();
+		points1.add(new Coord(1.0,180.0));
+		points1.add(new Coord(0.0,180.0));
+		points1.add(new Coord(1.0,179.0));
+		points1.add(points1.get(0));
+		Area a1 = Java2DConverter.createArea(points1);
+		uk.me.parabola.imgfmt.app.Area bbox = Java2DConverter.createBbox(a1);
+		for (Coord co : points1)
+			assertTrue(bbox.contains(co));
+		Area awtPlanet = Java2DConverter.createBoundsArea(uk.me.parabola.imgfmt.app.Area.PLANET);
+		a1.intersect(awtPlanet);
+		assertTrue(a1.isSingular());
+		List<Coord> points2 = Java2DConverter.singularAreaToPoints(a1);
+		long testVal1 = ShapeMergeFilter.calcAreaSizeTestVal(points1);
+		long testVal2 = ShapeMergeFilter.calcAreaSizeTestVal(points2);
+		assertEquals(testVal1, testVal2);
+	}
+
+	@Test
+	public void testPolygonConversionAtMinus180() throws Exception {
+		// various calculations near 180.0 
+		List<Coord> points1 = new ArrayList<>();
+		points1.add(new Coord(-1.0,-180.0));
+		points1.add(new Coord(0.0,-180.0));
+		points1.add(new Coord(-1.0,-179.0));
+		points1.add(points1.get(0));
+		Area a1 = Java2DConverter.createArea(points1);
+		uk.me.parabola.imgfmt.app.Area bbox = Java2DConverter.createBbox(a1);
+		for (Coord co : points1)
+			assertTrue(bbox.contains(co));
+		Area awtPlanet = Java2DConverter.createBoundsArea(uk.me.parabola.imgfmt.app.Area.PLANET);
+		a1.intersect(awtPlanet);
+		assertTrue(a1.isSingular());
+		List<Coord> points2 = Java2DConverter.singularAreaToPoints(a1);
+		long testVal1 = ShapeMergeFilter.calcAreaSizeTestVal(points1);
+		long testVal2 = ShapeMergeFilter.calcAreaSizeTestVal(points2);
+		assertEquals(testVal1, testVal2);
+	}
 }
diff --git a/test/uk/me/parabola/util/ShapeSplitterTest.java b/test/uk/me/parabola/util/ShapeSplitterTest.java
new file mode 100644
index 0000000..8035157
--- /dev/null
+++ b/test/uk/me/parabola/util/ShapeSplitterTest.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2017.
+ *
+ * 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.util.ArrayList;
+//import java.util.Arrays;
+import java.util.List;
+import java.util.Collections;
+
+import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+import uk.me.parabola.mkgmap.general.MapShape;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test polygon splitting
+ * @author Ticker Berkin
+ *
+ */
+public class ShapeSplitterTest {
+
+    @Test
+    public void test1_SimpleSplit() {
+	// simple diamond shape
+	int[][] os = { {1,1}, {5,3}, {7,7}, {3,5} };
+	splitTester st = new splitTester(os);
+	
+	st.cutPosn(3, false);
+	int[][] f1 = { {1,1}, {3,2}, {3,5} };
+	st.addExpected(f1);
+	int[][] f2 = { {3,2}, {5,3}, {7,7}, {3,5} };
+	st.addExpected(f2);
+	st.runSplitShape();
+	st.runClipToBounds();
+	st.runJava2D();	
+//	st.runSuthHodg();
+
+	st.cutPosn(5, true);
+	int[][] t1 = { {1,1}, {5,3}, {6,5}, {3,5} };
+	st.addExpected(t1);
+	int[][] t2 = { {6,5}, {7,7}, {3,5} };
+	st.addExpected(t2);
+	st.runSplitShape();
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+    }
+
+    @Test
+    public void test2_cutToHole() {
+	// shape has hole with cut to get to it
+	int[][] os = { {1,1}, {3,1}, {3,3}, {2,3}, {2,4}, {4,4}, {4,3}, {3,3}, {3,1}, {5,1}, {5,5}, {1,5} };
+	splitTester st = new splitTester(os);
+
+	// cut across existing cut
+	st.cutPosn(2, true);
+	int[][] t1 = { {1,1}, {3,1}, {3,2}, {1,2} };
+	st.addExpected(t1);
+	int[][] t2 = { {3,1}, {5,1}, {5,2}, {3,2} };
+	st.addExpected(t2);
+	int[][] t3 = { {1,2}, {3,2}, {3,3}, {2,3}, {2,4}, {4,4}, {4,3}, {3,3}, {3,2}, {5,2}, {5,5}, {1,5} };
+	st.addExpected(t3);
+	st.runSplitShape();
+	st.runClipToBounds();
+//!!!	st.runJava2D();  !!! java2D can't handle this
+//	st.runSuthHodg();
+	
+	// cut along cut and through other side of hole
+	st.cutPosn(3, false);
+	int[][] f1 = { {1,1}, {3,1}, {3,3}, {2,3}, {2,4}, {3,4}, {3,5}, {1,5} };
+	st.addExpected(f1);
+	int[][] f2 = { {3,1}, {5,1}, {5,5}, {3,5}, {3,4}, {4,4}, {4,3}, {3,3} };
+	st.addExpected(f2);
+	st.runSplitShape();
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+    }
+
+    @Test
+    public void test3_cutSpiral() {
+	// Imagine shape is rope, folded and the two loose ends on inside of a spiral
+	int[][] os = {
+	    // start: lower clockwise out
+	    {7,10}, {6,10}, {6,6}, {10,6}, {10,14}, {2,14}, {2,2}, {14,2},
+	    // fold
+	    {14,14}, {13,14}, {13,3},
+	    // lower anti-clockwise in
+	    {3,3}, {3,13}, {9,13}, {9,7}, {7,7},
+	    // lower clockwise out
+	    {7,8}, {8,8}, {8,12}, {4,12}, {4,4}, {12,4}, {12,15},
+	    // fold
+	    {15,15}, {15,1},
+	    // lower anti-clockwise in :end
+	    {1,1}, {1,15}, {11,15}, {11,5}, {5,5}, {5,11}, {7,11}
+	};
+	splitTester st = new splitTester(os);
+	
+	st.cutPosn(9, true);
+	// left hand going up
+	int[][] l1 = { {1,9}, {1,1}, {15,1}, {15,9}, {14,9}, {14,2}, {2,2}, {2,9} };
+	st.addExpected(l1);
+	int[][] l2 = { {3,9}, {3,3}, {13,3}, {13,9}, {12,9}, {12,4}, {4,4}, {4,9} };
+	st.addExpected(l2);
+	int[][] l3 = { {5,9}, {5,5}, {11,5}, {11,9}, {10,9}, {10,6}, {6,6}, {6,9} };
+	st.addExpected(l3);
+	int[][] l4 = { {8,9}, {8,8}, {7,8}, {7,7}, {9,7}, {9,9} };
+	st.addExpected(l4);
+	// right hand going up
+	int[][] r1 = { {1,9}, {1,15}, {11,15}, {11,9}, {10,9}, {10,14}, {2,14}, {2,9} };
+	st.addExpected(r1);
+	int[][] r2 = { {3,9}, {3,13}, {9,13}, {9,9}, {8,9}, {8,12}, {4,12}, {4,9} };
+	st.addExpected(r2);
+	int[][] r3 = { {5,9}, {5,11}, {7,11}, {7,10}, {6,10}, {6,9} };
+	st.addExpected(r3);
+	int[][] r4 = { {12,9}, {12,15}, {15,15}, {15,9}, {14,9}, {14,14}, {13,14}, {13,9} };
+	st.addExpected(r4);
+	st.runSplitShape();
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+    }
+
+    @Test
+    public void test4_cutFlash() {
+	// a complicated shape that needs to be drawn on graph paper to understand
+	int[][] os = {
+	    {20,18}, {15,18}, {6,9}, {6,10}, {4,8}, {4,18},
+	    {1,18}, {1,1}, {20,1}, {20,10},
+	    {12,2}, {12,10}, {11,9}, {11,10}, {9,8}, {9,10}, {2,3},
+	    {2,10}, {3,11}, {3,5}, {13,15}, {13,7}, {16,10}, {16,8}, {18,10}, {18,9}, {20,11}
+	};
+	splitTester st = new splitTester(os);
+	
+	st.cutPosn(9, true);
+	// left hand going up
+	int[][] l1 = { {1,9}, {1,1}, {20,1}, {20,9}, {19,9}, {12,2}, {12,9}, {10,9}, {9,8}, {9,9}, {8,9}, {2,3}, {2,9} };
+	st.addExpected(l1);
+	int[][] l2 = { {3,9}, {3,5}, {7,9}, {5,9}, {4,8}, {4,9} };
+	st.addExpected(l2);
+	int[][] l3 = { {13,9}, {13,7}, {15,9} };
+	st.addExpected(l3);
+	int[][] l4 = { {16,9}, {16,8}, {17,9} };
+	st.addExpected(l4);
+	// right hand going up
+	int[][] r1 = { {1,9}, {1,18}, {4,18}, {4,9}, {3,9}, {3,11}, {2,10}, {2,9} };
+	st.addExpected(r1);
+	int[][] r2 = { {5,9}, {6,10}, {6,9} };
+	st.addExpected(r2);
+	int[][] r3 = { {6,9}, {15,18}, {20,18}, {20,11}, {18,9}, {18,10}, {17,9}, {16,9}, {16,10}, {15,9}, {13,9}, {13,15}, {7,9} };
+	st.addExpected(r3);
+	int[][] r4 = { {8,9}, {9,10}, {9,9} };
+	st.addExpected(r4);
+	int[][] r5 = { {10,9}, {11,10}, {11,9} };
+	st.addExpected(r5);
+	int[][] r6 = { {11,9}, {12,10}, {12,9} };
+	st.addExpected(r6);
+	int[][] r7 = { {19,9}, {20,10}, {20,9} };
+	st.addExpected(r7);
+	st.runSplitShape();
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+
+	st.clipBounds(1, 1, 20, 18); // this full area
+	st.addExpected(os); // original shape
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+
+	st.clipBounds(13, 10, 19, 16); // a solid bit
+	int[][] s1 = { {13,10}, {13,16}, {19,16}, {19,10} };
+	st.addExpected(s1);
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+	
+	st.clipBounds(9, 10, 13, 11); // a hole
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+    }
+
+    @Test
+    public void test4_clipTest() {
+	// shape to defeat naive sutherland-hodge implementation
+	int[][] os = {
+	    {2,1}, {10,1}, {10,10}, {1,10}, {1,2},
+	    {2,3},
+	    {2,5}, {4,5}, {4,6}, {2,6}, {2,9},
+	    {5,9}, {5,7}, {6,7}, {6,9}, {9,9},
+	    {9,6}, {7,6}, {7,5}, {9,5}, {9,2},
+	    {6,2}, {6,4}, {5,4}, {5,2}, {3,2}
+	};
+	splitTester st = new splitTester(os);
+	st.clipBounds(3, 3, 8, 8);
+
+	int[][] s1 = { {6,3}, {6,4}, {5,4}, {5,3} };
+	st.addExpected(s1);
+	int[][] s2 = { {7,5}, {8,5}, {8,6}, {7,6} };
+	st.addExpected(s2);
+	int[][] s3 = { {5,7}, {6,7}, {6,8}, {5,8} };
+	st.addExpected(s3);
+	int[][] s4 = { {3,5}, {4,5}, {4,6}, {3,6} };
+	st.addExpected(s4);
+	st.runClipToBounds();
+	st.runJava2D();
+//	st.runSuthHodg();
+    }
+
+
+    private class splitTester {
+
+	List<Coord> origShape;
+	long origArea;
+	
+	List<List<Coord>> expectedShapes, resultShapes;
+	long totalArea;
+
+        boolean dividingInTwo;
+	int dividingLine;
+	boolean isLongitude;
+	Area fstBounds, lstBounds;
+
+	String algorithm;
+
+	List<Coord> makeShape(int[][] lowPrecPoints) {
+	    List<Coord> points = new ArrayList<>();
+	    for (int [] intPair : lowPrecPoints)
+		points.add(new Coord(intPair[0], intPair[1]));
+	    points.add(points.get(0));
+	    return points;
+	}
+
+	splitTester(int[][] lowPrecPoints) {
+	    origShape = makeShape(lowPrecPoints);
+	    origArea = ShapeMergeFilter.calcAreaSizeTestVal(origShape);
+	}
+
+	void cutPosn(int dividingLine, boolean isLongitude) {
+	    dividingInTwo = true;
+	    this.dividingLine = dividingLine;
+	    this.isLongitude = isLongitude;
+	    expectedShapes = new ArrayList<>();
+	    totalArea = 0;
+	    // make Area bounding boxes for some of the algorithms
+	    MapShape tempShape = new MapShape();
+	    tempShape.setPoints(origShape);
+	    Area bounds = tempShape.getBounds();
+	    if (isLongitude) {
+		fstBounds = new Area(bounds.getMinLat(),
+				     bounds.getMinLong(),
+				     bounds.getMaxLat(),
+				     dividingLine);
+		lstBounds = new Area(bounds.getMinLat(),
+				     dividingLine,
+				     bounds.getMaxLat(),
+				     bounds.getMaxLong());
+	    } else {
+		fstBounds = new Area(bounds.getMinLat(),
+				     bounds.getMinLong(),
+				     dividingLine,
+				     bounds.getMaxLong());
+		lstBounds = new Area(dividingLine,
+				     bounds.getMinLong(),
+				     bounds.getMaxLat(),
+				     bounds.getMaxLong());
+	    }
+	}
+
+	void clipBounds(int minLat, int minLong, int maxLat, int maxLong) {
+	    dividingInTwo = false;
+	    expectedShapes = new ArrayList<>();
+	    totalArea = 0;
+	    fstBounds = new Area(minLat, minLong, maxLat, maxLong);
+	}
+    
+	void addExpected(int[][] lowPrecPoints) {
+	    List<Coord> another = makeShape(lowPrecPoints);
+	    totalArea += Math.abs(ShapeMergeFilter.calcAreaSizeTestVal(another));
+	    expectedShapes.add(another);
+	}
+
+	void preSplit(String algorithm) {
+	    this.algorithm = algorithm;
+	    if (dividingInTwo)
+		assertEquals(algorithm + " bits area", Math.abs(origArea), totalArea);
+	    resultShapes = null;
+	}
+		
+	void checkResults() {
+	    assertEquals(algorithm + " number of areas", expectedShapes.size(), resultShapes.size());
+	    long resTotalArea = 0;
+	    for (List<Coord> resShape : resultShapes) {
+		long resArea = ShapeMergeFilter.calcAreaSizeTestVal(resShape);
+		resTotalArea += Math.abs(resArea);
+		MapShape resTemp = new MapShape();
+		resTemp.setPoints(resShape);
+		Area resBounds = resTemp.getBounds();
+		// try and find in expected shapes
+		boolean foundIt = false;
+		for (List<Coord> expShape : expectedShapes) {
+		    long expArea = ShapeMergeFilter.calcAreaSizeTestVal(expShape);
+		    MapShape expTemp = new MapShape();
+		    expTemp.setPoints(expShape);
+		    Area expBounds = expTemp.getBounds();
+		    if (Math.abs(expArea) == Math.abs(resArea) && expBounds.equals(resBounds)) {
+			foundIt = true;
+			// attempt to check points
+			// make unclosed copy for manipulation
+			List<Coord> resPoints = new ArrayList<>(resShape);
+			resPoints.remove(resPoints.size()-1);
+			if (expArea != resArea) // if sign of areas different
+			    Collections.reverse(resPoints);
+			// rotate the list so have same first elem
+			Coord expFst = expShape.get(0);
+			int inx;
+			for (inx = 0; inx < resPoints.size(); ++inx)
+			    if (resPoints.get(inx).equals(expFst))
+				break;
+			assertNotEquals("find first point failed", resPoints.size(), inx);
+			if (inx > 0)
+			    Collections.rotate(resPoints, -inx);
+			// now step through. Maybe need to allow duplicate/extra in result set
+			// if java2D... might keeps some on the cut line
+			inx = 0;
+			for (Coord resPoint : resPoints) {
+			    assertTrue(algorithm + " point " + inx, resPoint.equals(expShape.get(inx)));
+                            ++inx;
+			}
+			break;
+		    } // if shape has correct area and bounds
+		} // for each expectedShape
+		assertTrue(algorithm + " result shape not matched", foundIt);
+	    } // for each resultShape
+	    assertEquals(algorithm + " result total area", totalArea, resTotalArea);
+	} // checkResults
+
+	void runSplitShape() {
+	    preSplit("splitShape");
+	    resultShapes = new ArrayList<>();
+	    assertTrue(algorithm + " Not applicable to clip", dividingInTwo);
+	    ShapeSplitter.splitShape(origShape, dividingLine << Coord.DELTA_SHIFT, isLongitude, resultShapes, resultShapes, null);
+	    checkResults();
+	}
+
+	void runClipToBounds() {
+	    preSplit("clipToBounds");
+	    resultShapes = ShapeSplitter.clipToBounds(origShape, fstBounds, null);
+	    if (dividingInTwo) {
+		List<List<Coord>> moreShapes = ShapeSplitter.clipToBounds(origShape, lstBounds, null);
+		resultShapes.addAll(moreShapes);
+	    }
+	    checkResults();
+	}
+
+	void runJava2D() {
+	    preSplit("java2D");
+	    java.awt.geom.Area area = Java2DConverter.createArea(origShape);
+	    java.awt.geom.Area clipper = Java2DConverter.createBoundsArea(fstBounds);
+	    clipper.intersect(area);
+	    resultShapes = Java2DConverter.areaToShapes(clipper);
+	    if (dividingInTwo) {
+		clipper = Java2DConverter.createBoundsArea(lstBounds);
+		clipper.intersect(area);
+		List<List<Coord>> moreShapes = Java2DConverter.areaToShapes(clipper);
+		resultShapes.addAll(moreShapes);
+	    }
+	    checkResults();
+	}
+
+	void runSuthHodg() {
+/*	    
+	    preSplit("suthHodg");
+
+started to look at this to see if could fit in to testing like above but gave up.
+	    Path2D.Double ShapeSplitter.clipSinglePathWithSutherlandHodgman (double[] points, int num, Rectangle2D clippingRect, Rectangle2D.Double bbox) {
+
+	    checkResults();
+*/
+	}
+
+    }
+	
+}

-- 
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