[mkgmap] 05/14: Imported Upstream version 0.0.0+svn3366

Bas Couwenberg sebastic at xs4all.nl
Sun Dec 14 15:39:46 UTC 2014


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

sebastic-guest pushed a commit to branch master
in repository mkgmap.

commit 31ade85724eb1e8d0dd4d51bc655b3916c925772
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Sun Dec 14 00:41:13 2014 +0100

    Imported Upstream version 0.0.0+svn3366
---
 .classpath                                         |  10 +-
 .idea/codeStyleSettings.xml                        |   4 +-
 .idea/inspectionProfiles/Mapping.xml               |  71 +++++-
 .project                                           |  28 ++-
 .settings/org.apache.ivyde.eclipse.prefs           |   2 +
 .settings/org.eclipse.jdt.core.prefs               | 100 ++++++++
 doc/options.txt                                    |  19 +-
 doc/styles/rules-filters.txt                       |  32 ++-
 doc/styles/rules.txt                               |  10 +-
 ivy.xml                                            |   3 +
 ivysettings.xml                                    |   2 +-
 resources/LocatorConfig.xml                        | 148 ++++++------
 resources/help/en/options                          |  19 +-
 resources/mkgmap-version.properties                |   4 +-
 resources/styles/default/inc/address               |  22 +-
 resources/styles/default/inc/water_lines           |   1 +
 resources/styles/default/points                    |  14 +-
 .../parabola/imgfmt/app/BufferedImgFileReader.java |  11 +-
 src/uk/me/parabola/imgfmt/app/Coord.java           |  27 ++-
 .../me/parabola/imgfmt/app/lbl/LBLFileReader.java  |  15 +-
 src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java   |  39 +--
 src/uk/me/parabola/imgfmt/app/net/NODFile.java     |   4 +
 src/uk/me/parabola/imgfmt/app/net/NODHeader.java   |  17 +-
 src/uk/me/parabola/imgfmt/app/net/RoadDef.java     |  19 +-
 .../me/parabola/imgfmt/app/trergn/Subdivision.java |  22 +-
 src/uk/me/parabola/imgfmt/app/trergn/TREFile.java  |   4 +
 .../parabola/imgfmt/app/trergn/TREFileReader.java  |   6 +-
 .../me/parabola/imgfmt/app/trergn/TREHeader.java   |  10 +-
 src/uk/me/parabola/mkgmap/CommandArgsReader.java   |  17 +-
 src/uk/me/parabola/mkgmap/build/LocatorConfig.java |  23 +-
 src/uk/me/parabola/mkgmap/build/MapArea.java       |  98 +++++---
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |  40 +--
 src/uk/me/parabola/mkgmap/build/MapSplitter.java   |  29 ++-
 .../parabola/mkgmap/combiners/GmapsuppBuilder.java |  11 +-
 src/uk/me/parabola/mkgmap/main/StyleTester.java    |   9 +
 src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java |  14 +-
 .../parabola/mkgmap/osmstyle/ExpressionRule.java   |  16 +-
 .../parabola/mkgmap/osmstyle/RuleFileReader.java   |   2 +-
 src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java    |  14 ++
 src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java  |   8 +
 .../parabola/mkgmap/osmstyle/StyledConverter.java  | 158 +++++++++---
 src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java |   4 +-
 .../parabola/mkgmap/osmstyle/WrongAngleFixer.java  |   2 +-
 .../mkgmap/osmstyle/actions/AddLabelAction.java    |   2 +-
 .../mkgmap/osmstyle/actions/ConvertFilter.java     |  44 +++-
 .../mkgmap/osmstyle/actions/CountryISOFilter.java  |  38 +++
 .../mkgmap/osmstyle/actions/HeightFilter.java      |   2 +-
 .../mkgmap/osmstyle/actions/NameAction.java        |   2 +-
 .../mkgmap/osmstyle/actions/SubstringFilter.java   |   2 +-
 .../mkgmap/osmstyle/actions/ValueBuilder.java      |   8 +-
 .../mkgmap/osmstyle/eval/UnitConversions.java      | 168 +++++++++++--
 .../osmstyle/housenumber/HousenumberGenerator.java | 146 ++++++++++-
 .../mkgmap/reader/MapperBasedMapDataSource.java    |  13 +
 .../mkgmap/reader/osm/CoastlineFileLoader.java     |   2 -
 src/uk/me/parabola/mkgmap/reader/osm/Element.java  |   2 +-
 src/uk/me/parabola/mkgmap/reader/osm/GType.java    |  23 +-
 .../mkgmap/reader/osm/MultiPolygonRelation.java    |  11 +
 .../parabola/mkgmap/reader/osm/OsmConverter.java   |   5 +
 .../mkgmap/reader/osm/OsmMapDataSource.java        |  15 +-
 .../mkgmap/reader/osm/RelationStyleHook.java       |   7 +-
 src/uk/me/parabola/mkgmap/reader/osm/Rule.java     |   1 +
 .../parabola/mkgmap/reader/osm/SeaGenerator.java   |   3 -
 src/uk/me/parabola/mkgmap/reader/osm/Style.java    |   5 +
 src/uk/me/parabola/mkgmap/reader/osm/TagDict.java  |  15 +-
 .../reader/osm/boundary/BoundaryConverter.java     |   5 +
 .../mkgmap/reader/osm/boundary/BoundaryDiff.java   |  24 +-
 .../reader/osm/boundary/BoundaryPreprocessor.java  |  16 +-
 .../reader/osm/boundary/BoundaryQuadTree.java      | 269 ++++++++++++---------
 .../mkgmap/reader/osm/boundary/BoundarySaver.java  | 164 +++++--------
 .../mkgmap/reader/osm/boundary/BoundaryUtil.java   | 252 ++++++++++++-------
 .../mkgmap/reader/polish/PolishMapDataSource.java  |   7 +
 src/uk/me/parabola/util/Java2DConverter.java       |  39 ++-
 src/uk/me/parabola/util/ShapeSplitter.java         | 260 ++++++++++++++++++++
 test/func/route/SimpleRouteTest.java               |   2 +-
 test/uk/me/parabola/imgfmt/app/CoordTest.java      |  15 ++
 .../mkgmap/osmstyle/actions/ConvertFilterTest.java | 151 ++++++++++++
 .../osmstyle/actions/CountryISOFilterTest.java     |  55 +++++
 .../mkgmap/osmstyle/actions/ValueBuilderTest.java  |  14 ++
 78 files changed, 2144 insertions(+), 751 deletions(-)

diff --git a/.classpath b/.classpath
index cb68169..a9544e7 100644
--- a/.classpath
+++ b/.classpath
@@ -1,14 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
 	<classpathentry kind="src" path="resources"/>
-	<classpathentry kind="src" path="src"/>
+	<classpathentry excluding="uk/me/parabola/mkgmap/reader/dem/optional/" kind="src" path="src"/>
 	<classpathentry kind="src" path="test"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/junit"/>
-	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/resource"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
-	<classpathentry kind="lib" path="lib/compile/protobuf-java-2.4.1.jar"/>
-	<classpathentry kind="lib" path="lib/compile/osmpbf-1.1.1-754a33af.jar"/>
-	<classpathentry kind="lib" path="lib/compile/fastutil-6.5.2-mkg.1.jar"/>
+	<classpathentry kind="con" path="org.apache.ivyde.eclipse.cpcontainer.IVYDE_CONTAINER/?project=mkgmap&ivyXmlPath=ivy.xml&confs=*"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
index d96c3e2..34644f1 100644
--- a/.idea/codeStyleSettings.xml
+++ b/.idea/codeStyleSettings.xml
@@ -118,7 +118,6 @@
           <option name="METHOD_BRACE_STYLE" value="5" />
           <option name="INDENT_CASE_FROM_SWITCH" value="false" />
           <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
-          <option name="WRAP_COMMENTS" value="true" />
           <option name="PARENT_SETTINGS_INSTALLED" value="true" />
           <indentOptions>
             <option name="USE_TAB_CHARACTER" value="true" />
@@ -176,5 +175,4 @@
     </option>
     <option name="USE_PER_PROJECT_SETTINGS" value="true" />
   </component>
-</project>
-
+</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Mapping.xml b/.idea/inspectionProfiles/Mapping.xml
index c999530..bd0f371 100644
--- a/.idea/inspectionProfiles/Mapping.xml
+++ b/.idea/inspectionProfiles/Mapping.xml
@@ -26,9 +26,13 @@
     <inspection_tool class="AndroidDomInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidElementNotAllowed" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintAaptCrash" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintAccidentalOctal" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAdapterViewChildren" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintAddJavascriptInterface" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAllowBackup" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAlwaysShowAction" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintAppCompatMethod" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintAppCompatResource" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintAssert" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintBackButton" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintButtonCase" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -37,7 +41,9 @@
     <inspection_tool class="AndroidLintByteOrderMark" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintCommitPrefEdits" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintContentDescription" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintCustomViewStyleable" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintCutPasteId" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintDeprecated" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintDeviceAdmin" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintDisableBaselineAlignment" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintDrawAllocation" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -49,13 +55,17 @@
     <inspection_tool class="AndroidLintEasterEgg" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintEnforceUTF8" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintExportedContentProvider" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintExportedPreferenceActivity" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintExportedReceiver" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintExportedService" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintExtraText" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintExtraTranslation" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintGetInstance" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGifUsage" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGradleCompatible" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleCompatiblePlugin" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGradleDependency" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintGradleDeprecated" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGradleDynamicVersion" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintGradleGetter" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintGradleIdeError" enabled="false" level="ERROR" enabled_by_default="true" />
@@ -79,6 +89,7 @@
     <inspection_tool class="AndroidLintIconNoDpi" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintIconXmlAndPng" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintIllegalResourceRef" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintImpliedQuantity" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInOrMmUsage" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintIncludeLayoutParam" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintInconsistentArrays" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -91,6 +102,7 @@
     <inspection_tool class="AndroidLintLabelFor" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintLibraryCustomView" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintLocalSuppress" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintLocaleFolder" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintMangledCRLF" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintManifestOrder" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintMenuTitle" enabled="false" level="WARNING" enabled_by_default="true" />
@@ -107,6 +119,7 @@
     <inspection_tool class="AndroidLintNestedScrolling" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintNestedWeights" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintNewApi" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintNewerVersionAvailable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintNfcTechWhitespace" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintNotSibling" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintObsoleteLayoutParam" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -114,7 +127,9 @@
     <inspection_tool class="AndroidLintOrientation" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintOverdraw" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintOverride" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintOverrideAbstract" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintPackagedPrivateKey" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintParcelCreator" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintPrivateResource" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintProguard" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintProguardSplit" enabled="false" level="WARNING" enabled_by_default="true" />
@@ -124,10 +139,12 @@
     <inspection_tool class="AndroidLintPxUsage" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintReferenceType" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRegistered" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintRelativeOverlap" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRequiredSize" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintResAuto" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintResourceAsColor" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintResourceCycle" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintResourceName" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintRtlCompat" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintRtlEnabled" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintRtlHardcoded" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -139,6 +156,7 @@
     <inspection_tool class="AndroidLintServiceCast" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSetJavaScriptEnabled" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintShowToast" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintSignatureOrSystemPermissions" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSmallSp" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSpUsage" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSparseArray" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -147,6 +165,7 @@
     <inspection_tool class="AndroidLintStringFormatCount" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintStringFormatInvalid" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintStringFormatMatches" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintStringShouldBeInt" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintStyleCycle" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="AndroidLintSuspicious0dp" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintSuspiciousImport" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -175,10 +194,13 @@
     <inspection_tool class="AndroidLintUselessLeaf" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUselessParent" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AndroidLintUsesMinSdkAttributes" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintValidFragment" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="AndroidLintViewConstructor" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintViewHolder" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWebViewLayout" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWorldReadableFiles" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWorldWriteableFiles" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AndroidLintWrongCall" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWrongCase" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWrongFolder" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="AndroidLintWrongViewCast" enabled="false" level="ERROR" enabled_by_default="false" />
@@ -242,6 +264,7 @@
     </inspection_tool>
     <inspection_tool class="AssignmentToForLoopParameterJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AssignmentToFunctionParameterJS" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="AssignmentToLambdaParameter" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="AssignmentToMethodParameter" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreTransformationOfOriginalParameter" value="false" />
     </inspection_tool>
@@ -284,7 +307,10 @@
     <inspection_tool class="BeforeClassOrAfterClassIsPublicStaticVoidNoArg" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="BeforeOrAfterIsPublicVoidNoArg" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="BigDecimalEquals" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="BigDecimalLegacyMethod" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="BigDecimalMethodWithoutRoundingCalled" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="BindingAnnotationWithoutInject" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="BlockMarkerComments" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="BlockStatementJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="BooleanConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="BooleanMethodIsAlwaysInverted" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -343,6 +369,7 @@
     <inspection_tool class="CdiManagedBeanInconsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CdiNormalScopeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CdiObservesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="CdiScopeInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CdiSpecializesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CdiStereotypeInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CdiStereotypeRestrictionsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -373,6 +400,7 @@
     <inspection_tool class="CheckXmlFileWithXercesValidator" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CheckedExceptionClass" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ClashingGetters" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ClashingTraitMethods" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ClassComplexity" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_limit" value="80" />
     </inspection_tool>
@@ -419,6 +447,7 @@
     <inspection_tool class="ClassWithMultipleLoggers" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="loggerNamesString" value="java.util.logging.Logger,org.slf4j.Logger,org.apache.commons.logging.Log,org.apache.log4j.Logger" />
     </inspection_tool>
+    <inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ClassWithTooManyDependencies" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="limit" value="10" />
     </inspection_tool>
@@ -519,6 +548,7 @@
     <inspection_tool class="ContinueStatementJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ContinueStatementWithLabel" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ContinueStatementWithLabelJS" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Contract" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ControlFlowStatementWithoutBraces" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="Convert2Diamond" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="Convert2Lambda" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -537,10 +567,13 @@
     <inspection_tool class="CssInvalidCharsetRule" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidElement" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssInvalidElementInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="CssInvalidFunction" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssInvalidHtmlTagReference" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidHtmlTagReferenceInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidImport" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="CssInvalidImportInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="CssInvalidMediaFeature" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="CssInvalidPropertyValue" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssInvalidPseudoSelector" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssInvalidShorthandPropertyValue" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="CssMissingSemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -610,15 +643,21 @@
     <inspection_tool class="DivideByZeroJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DjangoBrokenLineCommentInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DjangoCloseTagInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="DjangoIncompatibleInspection" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="DjangoOrmInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DjangoRelationInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DjangoUnresolvedFilterInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DjangoUnresolvedLoadInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DjangoUnresolvedStaticReferenceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DjangoUnresolvedTagInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="DjangoUnresolvedTemplateReferenceInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="DjangoUnresolvedUrlInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+    <inspection_tool class="DjangoUrlArgumentsInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="Django_close_tag" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DocumentWriteJS" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DollarSignInName" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="DontUsePairConstructor" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="DoubleBraceInitialization" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="DoubleCheckedLocking" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreOnVolatileVariables" value="false" />
     </inspection_tool>
@@ -724,7 +763,9 @@
     <inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="EqualsUsesNonFinalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="EqualsWhichDoesntCheckParameterClass" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="EqualsWithItself" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ErrorRethrown" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Eslint" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="ExceptionCaughtLocallyJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="ExceptionFromCatchWhichDoesntWrap" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreGetMessage" value="false" />
@@ -835,6 +876,7 @@
     <inspection_tool class="GWTStyleCheck" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="Geronimo" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="GherkinBrokenTableInspection" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="GherkinMisplacedBackground" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="GjsLint" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="GlassFish" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="Glassfish" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -1290,8 +1332,11 @@
     <inspection_tool class="JSValidateJSDoc" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JSValidateJSON" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JSValidateTypes" enabled="true" level="INFO" enabled_by_default="true" />
+    <inspection_tool class="JSXLanguageLevel" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="JUnit3MethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JUnit3StyleTestMethodInJUnit4Class" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JUnit4AnnotatedMethodInJUnit3TestCase" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="JUnit4MethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JUnitAbstractTestClassNamingConvention" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_regex" value="[A-Z][A-Za-z\d]*TestCase" />
       <option name="m_minLength" value="12" />
@@ -1362,8 +1407,10 @@
     <inspection_tool class="JpaQlInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JpaQueryApiInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JpdlModelInspection" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="Jscs" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="JsfJamExtendsClassInconsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JsfManagedBeansInconsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="JsonStandardCompliance" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JspAbsolutePathInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="JspDirectiveInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="JspPropertiesInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -1538,6 +1585,7 @@
       <option name="checkDivision" value="false" />
     </inspection_tool>
     <inspection_tool class="NakedNotify" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="NativeMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="NativeMethods" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="NegatedConditional" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="m_ignoreNegatedNullComparison" value="true" />
@@ -1732,6 +1780,7 @@
       <option name="m_maxLength" value="32" />
     </inspection_tool>
     <inspection_tool class="ParameterOfConcreteClass" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ParameterTypePreventsOverriding" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ParameterizedParametersStaticCollection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ParametersPerConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ParametersPerFunctionJS" enabled="false" level="WARNING" enabled_by_default="false">
@@ -1797,10 +1846,12 @@
     <inspection_tool class="PointlessBooleanExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PointlessIndexOfComparison" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="PointlessNullCheck" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="PostfixTemplateDescriptionNotFound" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PrimitiveArrayArgumentToVariableArgMethod" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PrivateMemberAccessBetweenOuterAndInnerClass" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ProblematicVarargsMethodOverride" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ProblematicWhitespace" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="PropertyValueSetToItself" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ProtectedField" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ProtectedInnerClass" enabled="false" level="WARNING" enabled_by_default="false">
       <option name="ignoreEnums" value="false" />
@@ -1913,13 +1964,7 @@
     <inspection_tool class="PyUnboundLocalVariableInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="PyUnnecessaryBackslashInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="PyUnreachableCodeInspection" enabled="true" level="WARNING" enabled_by_default="true" />
-    <inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false">
-      <option name="ignoredIdentifiers">
-        <value>
-          <list size="0" />
-        </value>
-      </option>
-    </inspection_tool>
+    <inspection_tool class="PyUnresolvedReferencesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="PyUnusedLocalInspection" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="ignoreTupleUnpacking" value="true" />
       <option name="ignoreLambdaParameters" value="true" />
@@ -2017,6 +2062,7 @@
       <option name="ignorePrivateMethods" value="true" />
     </inspection_tool>
     <inspection_tool class="ReturnOfDateField" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ReturnOfInnerClass" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ReturnThis" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ReuseOfLocalVariable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ReuseOfLocalVariableJS" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2069,6 +2115,7 @@
       <option name="ignoreAnonymousInnerClasses" value="false" />
       <option name="superClassString" value="java.awt.Component" />
     </inspection_tool>
+    <inspection_tool class="SerializableStoresNonSerializable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SerializableWithUnconstructableAncestor" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ServerEndpointInconsistencyInspection" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="ServletWithoutMappingInspection" enabled="true" level="ERROR" enabled_by_default="true" />
@@ -2112,8 +2159,10 @@
     <inspection_tool class="SpringBeanInstantiationInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringBeanLookupMethodInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringBeanNameConventionInspection" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="SpringComponentScan" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringContextConfigurationInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringElInspection" enabled="false" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="SpringElStaticFieldInjectionInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringFacetCodeInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringFacetInspection" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="checkTestFiles" value="false" />
@@ -2121,6 +2170,7 @@
     <inspection_tool class="SpringFacetProgrammaticInspection" enabled="false" level="WEAK WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringFactoryMethodInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringHandlersSchemasHighlighting" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="SpringInactiveProfileHighlightingInspection" enabled="false" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringIncorrectResourceTypeInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringInjectionValueConsistencyInspection" enabled="true" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SpringInjectionValueStyleInspection" enabled="true" level="WARNING" enabled_by_default="true" />
@@ -2149,6 +2199,7 @@
     <inspection_tool class="SpringTransactionalComponentInspection" enabled="false" level="ERROR" enabled_by_default="false" />
     <inspection_tool class="SpringWebServiceAnnotationsInconsistencyInspection" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="SpringWebServicesConfigurationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="SpringWebSocketConfigurationInspection" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="SqlAddNotNullColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlAmbiguousColumnInspection" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SqlAutoIncrementDuplicateInspection" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2248,6 +2299,7 @@
     <inspection_tool class="SuperClassHasFrequentlyUsedInheritors" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SuppressionAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SuspiciousArrayCast" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SuspiciousGetterSetter" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="SuspiciousMethodCalls" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="REPORT_CONVERTIBLE_METHOD_CALLS" value="true" />
@@ -2311,6 +2363,7 @@
       <option name="assertionMethods" value="org.junit.Assert,assert.*|fail.*,junit.framework.Assert,assert.*|fail.*,org.mockito.Mockito,verify.*" />
       <option name="assertKeywordIsAssertion" value="false" />
     </inspection_tool>
+    <inspection_tool class="TestNGMethodNamingConvention" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TestOnlyProblems" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="TextLabelInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TextLabelInSwitchStatementJS" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2343,6 +2396,8 @@
       <option name="m_limit" value="3" />
     </inspection_tool>
     <inspection_tool class="ThrowsRuntimeException" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ThymeleafMessagesResolveInspection" enabled="false" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="ThymeleafVariablesResolveInspection" enabled="false" level="ERROR" enabled_by_default="true" />
     <inspection_tool class="TimeToString" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="ToArrayCallWithZeroLengthArrayArgument" enabled="true" level="WARNING" enabled_by_default="true" />
     <inspection_tool class="TodoComment" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -2526,6 +2581,7 @@
     <inspection_tool class="UnusedProperty" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UnusedReturnValue" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UpperCaseFieldNameNotConstant" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UseCouple" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseJBColor" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfAWTPeerClass" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfAnotherObjectsPrivateField" enabled="false" level="WARNING" enabled_by_default="false">
@@ -2534,6 +2590,7 @@
     </inspection_tool>
     <inspection_tool class="UseOfJDBCDriverClass" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="UseOfObsoleteDateTimeApi" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfProcessBuilder" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfPropertiesAsHashtable" enabled="false" level="WARNING" enabled_by_default="false" />
     <inspection_tool class="UseOfSunClasses" enabled="false" level="WARNING" enabled_by_default="false" />
diff --git a/.project b/.project
index 8bf15cf..e19a7be 100644
--- a/.project
+++ b/.project
@@ -1,16 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <projectDescription>
-    <name>mkgmap</name>
-    <comment />
-    <projects />
-    <buildSpec>
-        <buildCommand>
-            <name>org.eclipse.jdt.core.javabuilder</name>
-            <arguments />
-        </buildCommand>
-    </buildSpec>
-    <natures>
-        <nature>org.eclipse.jdt.core.javanature</nature>
-    </natures>
+	<name>mkgmap</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.apache.ivyde.eclipse.ivynature</nature>
+	</natures>
 </projectDescription>
-
diff --git a/.settings/org.apache.ivyde.eclipse.prefs b/.settings/org.apache.ivyde.eclipse.prefs
new file mode 100644
index 0000000..a95f744
--- /dev/null
+++ b/.settings/org.apache.ivyde.eclipse.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+org.apache.ivyde.eclipse.standaloneretrieve=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><setuplist/>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..7078bf8
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,100 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/doc/options.txt b/doc/options.txt
index 7aeeb73..78086de 100644
--- a/doc/options.txt
+++ b/doc/options.txt
@@ -350,13 +350,22 @@ will ask you to try a value for this option.
 ;--route
 : 	Create maps that support routing.
 
+;--drive-on=left|right|detect|detect,left|detect,right
+: 	Explicitly specify which side of the road vehicles are
+expected to drive on. 
+If the first option is detect, the program tries 
+to find out the proper flag. If that detection
+fails, the second value is used (or right if none is given).
+With OSM data as input, the detection tries to find out  
+the country each road is in and compares the number
+of drive-on-left roads with the rest.
+Use the --bounds option to make sure that the detection 
+finds the correct country. 
+	
 ;--drive-on-left
 ;--drive-on-right
-: 	Explicitly specify which side of the road vehicles are
-expected to drive on. If neither of these options are
-specified, it is assumed that vehicles drive on the right
-unless --check-roundabouts is specified and the first
-roundabout processed is clockwise.
+: 	Deprecated: Use drive-on instead.
+The options are translated to drive-on=left|right. 
 
 ;--check-roundabouts
 : 	Check that roundabouts have the expected direction (clockwise
diff --git a/doc/styles/rules-filters.txt b/doc/styles/rules-filters.txt
index c46e9e9..6bf7314 100644
--- a/doc/styles/rules-filters.txt
+++ b/doc/styles/rules-filters.txt
@@ -12,11 +12,22 @@ matters.
 
 `${oneway\|def:no}`
 
-| conv | `factor` |
-Use for conversions between units. The only supported version is from meters to feet
-number. It is multiplied by the argument.
+| conv | `m=>ft` |
+Use for conversions between units.
+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.
 
-`${height\|conv:m=>ft}`
+`${height\|conv:"m=>ft"}`
+
+So if +height+ is 10, then the result is 33, and if +height+ is 10ft,
+then the result is 10, as it is already in feet.
+
+The possible units are:
+
+* Length: m, km, ft (feet), mi (miles).
+* Speed: mph; km/h (or kmh, kmph), knots
 
 | subst | `from=>to` +
 `from~>to`|
@@ -81,12 +92,21 @@ The second is the maximum length of references that do not contain numbers.
 If there is just the one number then it is used in both cases.
 
 | height | `m=>ft` |
-This is the same as the +conv+ filter, except that it prepends a special
+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.
-As with +conv+ the only supported conversion currently is from meters to feet.
 
 `${ele\|height:"m=>ft"}`
 
+| country-ISO |  |
+Use to normalize country names to the 3 character ISO 1366 code.
+The filter has no arguments. It uses the list in LocatorConfig.xml.
+Possible arguments are country names, or ISO codes in 2 or 3 characters,
+for example "Deutschland", "Germany", "Bundesrepublik Deutschland", or "DE" 
+will all return "DEU", also different cases like "GERMANY" or "   germany " 
+will work.
+ 
+If the value is not found in the list, then the value is unchanged.
+
 | not-equal | `tag` |
 Used to check for duplicate tags. If the value of this tag is equal to
 the value of the tag named as the argument to +not-equal+, then value
diff --git a/doc/styles/rules.txt b/doc/styles/rules.txt
index 0795106..db7f725 100644
--- a/doc/styles/rules.txt
+++ b/doc/styles/rules.txt
@@ -535,18 +535,16 @@ by the filter name, then a colon ":" and an argument. If there is more than
 one argument required then they are usually separated by colons too, but
 that is not a rule.
 
-[source]
- ${tagname|filter:arg1:arg2}
++${tagname|filter:arg1:arg2}+
 
 You can apply as many filter expressions to a substitution as you like.
 
-[source]
- ${tagname|filter1:arg|filter2:arg}
+
++${tagname|filter1:arg|filter2:arg}+
 
 If the argument contains spaces or symbols it should be quoted.
 
-[source]
- ${tagname|filter1:"arg with spaces"}
++${tagname|filter1:"arg with spaces"}+
 
 For backward compatibility, most cases where you have spaces or symbols
 do not actually need to be quoted, however we would recommend that you
diff --git a/ivy.xml b/ivy.xml
index d6f9fc1..06dd7a4 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -64,6 +64,9 @@
 		<dependency org="javax.media.jai" name="com.springsource.javax.media.jai.core" rev="1.1.3"
 					conf="optional->default(*),compile(*),master(*)"/>
 
+		<dependency org="uk.org.mkgmap" name="splitter" rev="412"
+					conf="optional->*"/>
+
 		<dependency org="org.geotools" name="gt-api" rev="2.7.5" conf="optional->default,compile">
 			<!-- A broken version of this is in the main repo which causes problems. Since we already depend on it, just ignore it here -->
 			<exclude module="jai_core"/>
diff --git a/ivysettings.xml b/ivysettings.xml
index ee4c8ab..c20b124 100644
--- a/ivysettings.xml
+++ b/ivysettings.xml
@@ -20,7 +20,7 @@
 
 		</chain>
 
-		<ibiblio name="geotools-resolver" m2compatible="true" root="http://repo.opengeo.org/" />
+		<ibiblio name="geotools-resolver" m2compatible="true" root="http://download.osgeo.org/webdav/geotools/" />
 
 		<url name="spring-resolver" >
 			<ivy pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
diff --git a/resources/LocatorConfig.xml b/resources/LocatorConfig.xml
index ffec3f9..d2e74ba 100644
--- a/resources/LocatorConfig.xml
+++ b/resources/LocatorConfig.xml
@@ -33,7 +33,7 @@
 		<variant>AO</variant>
 		<variant>AGO</variant>
 	</country>
-	<country name="Anguilla" abr="AIA">
+	<country name="Anguilla" abr="AIA" driveOnLeft="true">
 		<variant>AI</variant>
 		<variant>AIA</variant>
 	</country>
@@ -41,7 +41,7 @@
 		<variant>AQ</variant>
 		<variant>ATA</variant>
 	</country>
-	<country name="Antigua and Barbuda" abr="ATG">
+	<country name="Antigua and Barbuda" abr="ATG" driveOnLeft="true">
 		<variant>AG</variant>
 		<variant>ATG</variant>
 	</country>
@@ -57,7 +57,7 @@
 		<variant>AW</variant>
 		<variant>ABW</variant>
 	</country>
-	<country name="Australia" abr="AUS">
+	<country name="Australia" abr="AUS" driveOnLeft="true">
 		<variant>AU</variant>
 		<variant>AUS</variant>
 	</country>
@@ -74,7 +74,7 @@
 		<variant>AZE</variant>
 		<variant>Azərbaycan</variant>
 	</country>
-	<country name="Bahamas" abr="BHS">
+	<country name="Bahamas" abr="BHS" driveOnLeft="true">
 		<variant>BS</variant>
 		<variant>BHS</variant>
 		<variant>The Bahamas</variant>
@@ -84,12 +84,12 @@
 		<variant>BHR</variant>
 		<variant>الإسلامية</variant>
 	</country>
-	<country name="Bangladesh" abr="BGD">
+	<country name="Bangladesh" abr="BGD" driveOnLeft="true">
 		<variant>BD</variant>
 		<variant>BGD</variant>
 		<variant>বাংলাদেশ</variant>
 	</country>
-	<country name="Barbados" abr="BRB">
+	<country name="Barbados" abr="BRB" driveOnLeft="true">
 		<variant>BB</variant>
 		<variant>BRB</variant>
 	</country>
@@ -117,11 +117,11 @@
 		<variant>BJ</variant>
 		<variant>BEN</variant>
 	</country>
-	<country name="Bermuda" abr="BMU">
+	<country name="Bermuda" abr="BMU" driveOnLeft="true">
 		<variant>BM</variant>
 		<variant>BMU</variant>
 	</country>
-	<country name="Bhutan" abr="BTN">
+	<country name="Bhutan" abr="BTN" driveOnLeft="true">
 		<variant>BT</variant>
 		<variant>BTN</variant>
 	</country>
@@ -139,7 +139,7 @@
 		<variant>BIH</variant>
 		<variant>Bosnia and Herzegovina</variant>
 	</country>
-	<country name="Botswana" abr="BWA">
+	<country name="Botswana" abr="BWA" driveOnLeft="true">
 		<variant>BW</variant>
 		<variant>BWA</variant>
 	</country>
@@ -155,7 +155,7 @@
 		<variant>IO</variant>
 		<variant>IOT</variant>
 	</country>
-	<country name="Brunei Darussalam" abr="BRN">
+	<country name="Brunei Darussalam" abr="BRN"  driveOnLeft="true">
 		<variant>BN</variant>
 		<variant>BRN</variant>
 		<variant>برني دارالسلام‎</variant>
@@ -193,7 +193,7 @@
 		<variant>CPV</variant>
 		<variant>Cabo Verde</variant>
 	</country>
-	<country name="Cayman Islands" abr="CYM">
+	<country name="Cayman Islands" abr="CYM" driveOnLeft="true">
 		<variant>KY</variant>
 		<variant>CYM</variant>
 	</country>
@@ -216,11 +216,11 @@
 		<variant>CHN</variant>
 		<variant>People's Republic of China</variant>
 	</country>
-	<country name="Christmas Island" abr="CXR">
+	<country name="Christmas Island" abr="CXR" driveOnLeft="true">
 		<variant>CX</variant>
 		<variant>CXR</variant>
 	</country>
-	<country name="Cocos (Keeling) Islands" abr="CCK">
+	<country name="Cocos (Keeling) Islands" abr="CCK" driveOnLeft="true">
 		<variant>CC</variant>
 		<variant>CCK</variant>
 	</country>
@@ -244,7 +244,7 @@
 		<variant>COD</variant>
 		<variant>Congo-Kinshasa</variant>
 	</country>
-	<country name="Cook Islands" abr="COK">
+	<country name="Cook Islands" abr="COK" driveOnLeft="true">
 		<variant>CK</variant>
 		<variant>COK</variant>
 	</country>
@@ -269,7 +269,7 @@
 		<variant>CW</variant>
 		<variant>CUW</variant>
 	</country>
-	<country name="Cyprus" abr="CYP">
+	<country name="Cyprus" abr="CYP" driveOnLeft="true">
 		<variant>CY</variant>
 		<variant>CYP</variant>
 		<variant>Κυπριακή Δημοκρατία</variant>
@@ -289,7 +289,7 @@
 		<variant>DJ</variant>
 		<variant>DJI</variant>
 	</country>
-	<country name="Dominica" abr="DMA">
+	<country name="Dominica" abr="DMA" driveOnLeft="true">
 		<variant>DM</variant>
 		<variant>DMA</variant>
 	</country>
@@ -330,7 +330,7 @@
 		<variant>ET</variant>
 		<variant>ETH</variant>
 	</country>
-	<country name="Falkland Islands (Malvinas)" abr="FLK">
+	<country name="Falkland Islands (Malvinas)" abr="FLK" driveOnLeft="true">
 		<variant>FK</variant>
 		<variant>FLK</variant>
 	</country>
@@ -339,7 +339,7 @@
 		<variant>FRO</variant>
 		<variant>Føroyar</variant>
 	</country>
-	<country name="Fiji" abr="FJI">
+	<country name="Fiji" abr="FJI" driveOnLeft="true">
 		<variant>FJ</variant>
 		<variant>FJI</variant>
 		<variant>VITI</variant>
@@ -406,7 +406,7 @@
 		<variant>GL</variant>
 		<variant>GRL</variant>
 	</country>
-	<country name="Grenada" abr="GRD">
+	<country name="Grenada" abr="GRD" driveOnLeft="true">
 		<variant>GD</variant>
 		<variant>GRD</variant>
 	</country>
@@ -423,7 +423,7 @@
 		<variant>GT</variant>
 		<variant>GTM</variant>
 	</country>
-	<country name="Guernsey" abr="GGY">
+	<country name="Guernsey" abr="GGY" driveOnLeft="true">
 		<variant>GG</variant>
 		<variant>GGY</variant>
 	</country>
@@ -436,7 +436,7 @@
 		<variant>GNB</variant>
 		<variant>Guiné-Bissau</variant>
 	</country>
-	<country name="Guyana" abr="GUY">
+	<country name="Guyana" abr="GUY" driveOnLeft="true">
 		<variant>GY</variant>
 		<variant>GUY</variant>
 	</country>
@@ -460,7 +460,7 @@
 		<variant>HN</variant>
 		<variant>HND</variant>
 	</country>
-	<country name="Hong Kong" abr="HKG">
+	<country name="Hong Kong" abr="HKG" driveOnLeft="true">
 		<variant>HK</variant>
 		<variant>HKG</variant>
 	</country>
@@ -476,11 +476,11 @@
 		<variant>ICE</variant>
 		<variant>ISL</variant>
 	</country>
-	<country name="India" abr="IND">
+	<country name="India" abr="IND" driveOnLeft="true">
 		<variant>IN</variant>
 		<variant>IND</variant>
 	</country>
-	<country name="Indonesia" abr="IDN" streetBeforeHousenumber="true">
+	<country name="Indonesia" abr="IDN" streetBeforeHousenumber="true" driveOnLeft="true">
 		<variant>ID</variant>
 		<variant>IDN</variant>
 	</country>
@@ -495,13 +495,13 @@
 		<variant>IRQ</variant>
 		<variant>جمهورية العراق</variant>
 	</country>
-	<country name="Ireland" abr="IRL" regionOffset="3">
+	<country name="Ireland" abr="IRL" regionOffset="3" driveOnLeft="true">
 		<variant>Republic of Ireland</variant>
 		<variant>Éire</variant>
 		<variant>IE</variant>
 		<variant>IRL</variant>
 	</country>
-	<country name="Isle of Man" abr="IMN">
+	<country name="Isle of Man" abr="IMN" driveOnLeft="true">
 		<variant>IM</variant>
 		<variant>IMN</variant>
 	</country>
@@ -515,16 +515,16 @@
 		<variant>IT</variant>
 		<variant>ITA</variant>
 	</country>
-	<country name="Jamaica" abr="JAM">
+	<country name="Jamaica" abr="JAM" driveOnLeft="true">
 		<variant>JM</variant>
 		<variant>JAM</variant>
 	</country>
-	<country name="Japan" abr="JPN">
+	<country name="Japan" abr="JPN" driveOnLeft="true">
 		<variant>JP</variant>
 		<variant>JPN</variant>
 		<variant>日本</variant>
 	</country>
-	<country name="Jersey" abr="JEY">
+	<country name="Jersey" abr="JEY" driveOnLeft="true">
 		<variant>JE</variant>
 		<variant>JEY</variant>
 	</country>
@@ -537,11 +537,11 @@
 		<variant>KAZ</variant>
 		<variant>Казахстан</variant>
 	</country>
-	<country name="Kenya" abr="KEN">
+	<country name="Kenya" abr="KEN" driveOnLeft="true">
 		<variant>KE</variant>
 		<variant>KEN</variant>
 	</country>
-	<country name="Kiribati" abr="KIR">
+	<country name="Kiribati" abr="KIR" driveOnLeft="true">
 		<variant>KI</variant>
 		<variant>KIR</variant>
 	</country>
@@ -581,7 +581,7 @@
 		<variant>LBN</variant>
 		<variant>الجمهورية اللبنانية</variant>
 	</country>
-	<country name="Lesotho" abr="LSO">
+	<country name="Lesotho" abr="LSO" driveOnLeft="true">
 		<variant>LS</variant>
 		<variant>LSO</variant>
 		<variant>Kingdom of Lesotho</variant>
@@ -612,7 +612,7 @@
 		<variant>Groussherzogtum Lëtzebuerg</variant>
 		<variant>Grand Duchy of Luxembourg</variant>
 	</country>
-	<country name="Macao" abr="MAC">
+	<country name="Macao" abr="MAC" driveOnLeft="true">
 		<variant>MO</variant>
 		<variant>MAC</variant>
 	</country>
@@ -627,16 +627,16 @@
 		<variant>MDG</variant>
 		<variant>Madagasikara</variant>
 	</country>
-	<country name="Malawi" abr="MWI">
+	<country name="Malawi" abr="MWI" driveOnLeft="true">
 		<variant>MW</variant>
 		<variant>MWI</variant>
 		<variant>Republic of Malawi</variant>
 	</country>
-	<country name="Malaysia" abr="MYS">
+	<country name="Malaysia" abr="MYS" driveOnLeft="true">
 		<variant>MY</variant>
 		<variant>MYS</variant>
 	</country>
-	<country name="Maldives" abr="MDV">
+	<country name="Maldives" abr="MDV" driveOnLeft="true">
 		<variant>MV</variant>
 		<variant>MDV</variant>
 		<variant>ދިވެހިރާއްޖޭގެ ޖުމުހޫރިއްޔާ</variant>
@@ -645,7 +645,7 @@
 		<variant>ML</variant>
 		<variant>MLI</variant>
 	</country>
-	<country name="Malta" abr="MLT">
+	<country name="Malta" abr="MLT" driveOnLeft="true">
 		<variant>MT</variant>
 		<variant>MLT</variant>
 	</country>
@@ -662,7 +662,7 @@
 		<variant>MR</variant>
 		<variant>MRT</variant>
 	</country>
-	<country name="Mauritius" abr="MUS">
+	<country name="Mauritius" abr="MUS" driveOnLeft="true">
 		<variant>MU</variant>
 		<variant>MUS</variant>
 	</country>
@@ -698,7 +698,7 @@
 		<variant>ME</variant>
 		<variant>MNE</variant>
 	</country>
-	<country name="Montserrat" abr="MSR">
+	<country name="Montserrat" abr="MSR"  driveOnLeft="true">
 		<variant>MS</variant>
 		<variant>MSR</variant>
 	</country>
@@ -706,7 +706,7 @@
 		<variant>MA</variant>
 		<variant>MAR</variant>
 	</country>
-	<country name="Mozambique" abr="MOZ">
+	<country name="Mozambique" abr="MOZ" driveOnLeft="true">
 		<variant>MZ</variant>
 		<variant>MOZ</variant>
 		<variant>Moçambique</variant>
@@ -715,16 +715,16 @@
 		<variant>MM</variant>
 		<variant>MMR</variant>
 	</country>
-	<country name="Namibia" abr="NAM">
+	<country name="Namibia" abr="NAM" driveOnLeft="true">
 		<variant>NA</variant>
 		<variant>NAM</variant>
 	</country>
-	<country name="Nauru" abr="NRU">
+	<country name="Nauru" abr="NRU" driveOnLeft="true">
 		<variant>NR</variant>
 		<variant>NRU</variant>
 		<variant>NAOERO</variant>
 	</country>
-	<country name="Nepal" abr="NPL">
+	<country name="Nepal" abr="NPL" driveOnLeft="true">
 		<variant>NP</variant>
 		<variant>NPL</variant>
 		<variant>नेपाल</variant>
@@ -739,7 +739,7 @@
 		<variant>NC</variant>
 		<variant>NCL</variant>
 	</country>
-	<country name="New Zealand" abr="NZL">
+	<country name="New Zealand" abr="NZL" driveOnLeft="true">
 		<variant>NZ</variant>
 		<variant>NZL</variant>
 		<variant>AOTEAROA NEW ZEALAND</variant>
@@ -756,11 +756,11 @@
 		<variant>NG</variant>
 		<variant>NGA</variant>
 	</country>
-	<country name="Niue" abr="NIU">
+	<country name="Niue" abr="NIU" driveOnLeft="true">
 		<variant>NU</variant>
 		<variant>NIU</variant>
 	</country>
-	<country name="Norfolk Island" abr="NFK">
+	<country name="Norfolk Island" abr="NFK" driveOnLeft="true">
 		<variant>NF</variant>
 		<variant>NFK</variant>
 	</country>
@@ -780,7 +780,7 @@
 		<variant>OMN</variant>
 		<variant>‏سلطنة عمان‎</variant>
 	</country>
-	<country name="Pakistan" abr="PAK">
+	<country name="Pakistan" abr="PAK" driveOnLeft="true">
 		<variant>PK</variant>
 		<variant>PAK</variant>
 		<variant>‏پاکستان‎</variant>
@@ -800,7 +800,7 @@
 		<variant>PAN</variant>
 		<variant>Panamá</variant>
 	</country>
-	<country name="Papua New Guinea" abr="PNG">
+	<country name="Papua New Guinea" abr="PNG" driveOnLeft="true">
 		<variant>PG</variant>
 		<variant>PNG</variant>
 		<variant>PAPUA NIUGINI</variant>
@@ -817,7 +817,7 @@
 		<variant>PH</variant>
 		<variant>PHL</variant>
 	</country>
-	<country name="Pitcairn" abr="PCN">
+	<country name="Pitcairn" abr="PCN" driveOnLeft="true">
 		<variant>PN</variant>
 		<variant>PCN</variant>
 	</country>
@@ -866,19 +866,19 @@
 		<variant>BLM</variant>
 		<variant>Saint-Barthélémy</variant>
 	</country>
-	<country name="Saint Helena" abr="SHN">
+	<country name="Saint Helena" abr="SHN" driveOnLeft="true">
 		<variant>Ascension</variant>
 		<variant>Tristan da Cunha</variant>
 		<variant>SH</variant>
 		<variant>SHN</variant>
 	</country>
-	<country name="Saint Kitts and Nevis" abr="KNA">
+	<country name="Saint Kitts and Nevis" abr="KNA" driveOnLeft="true">
 		<variant>Saint Kitts</variant>
 		<variant>Nevis</variant>
 		<variant>KN</variant>
 		<variant>KNA</variant>
 	</country>
-	<country name="Saint Lucia" abr="LCA">
+	<country name="Saint Lucia" abr="LCA" driveOnLeft="true">
 		<variant>LC</variant>
 		<variant>LCA</variant>
 	</country>
@@ -892,13 +892,13 @@
 		<variant>PM</variant>
 		<variant>SPM</variant>
 	</country>
-	<country name="Saint Vincent and the Grenadines" abr="VCT">
+	<country name="Saint Vincent and the Grenadines" abr="VCT" driveOnLeft="true">
 		<variant>Saint Vincent</variant>
 		<variant>Grenadines</variant>
 		<variant>VC</variant>
 		<variant>VCT</variant>
 	</country>
-	<country name="Samoa" abr="WSM">
+	<country name="Samoa" abr="WSM" driveOnLeft="true">
 		<variant>WS</variant>
 		<variant>WSM</variant>
 	</country>
@@ -928,7 +928,7 @@
 		<variant>Serbia</variant>
 		<variant>Republic of Serbia</variant>
 	</country>
-	<country name="Seychelles" abr="SYC">
+	<country name="Seychelles" abr="SYC" driveOnLeft="true">
 		<variant>SC</variant>
 		<variant>SYC</variant>
 		<variant>SESEL</variant>
@@ -937,7 +937,7 @@
 		<variant>SL</variant>
 		<variant>SLE</variant>
 	</country>
-	<country name="Singapore" abr="SGP">
+	<country name="Singapore" abr="SGP" driveOnLeft="true">
 		<variant>SG</variant>
 		<variant>SGP</variant>
 		<variant>SINGAPURA</variant>
@@ -956,7 +956,7 @@
 		<variant>SVN</variant>
 		<variant>Slovenia</variant>
 	</country>
-	<country name="Solomon Islands" abr="SLB">
+	<country name="Solomon Islands" abr="SLB" driveOnLeft="true">
 		<variant>SB</variant>
 		<variant>SLB</variant>
 	</country>
@@ -965,7 +965,7 @@
 		<variant>SOM</variant>
 		<variant>Soomaaliya</variant>
 	</country>
-	<country name="South Africa" abr="ZAF">
+	<country name="South Africa" abr="ZAF" driveOnLeft="true">
 		<variant>ZA</variant>
 		<variant>ZAF</variant>
 	</country>
@@ -980,7 +980,7 @@
 		<variant>ES</variant>
 		<variant>ESP</variant>
 	</country>
-	<country name="Sri Lanka" abr="LKA">
+	<country name="Sri Lanka" abr="LKA" driveOnLeft="true">
 		<variant>LK</variant>
 		<variant>LKA</variant>
 		<variant>ශ්‍රී ලංකා</variant>
@@ -995,7 +995,7 @@
 		<variant>SDN</variant>
 		<variant>جمهورية السودان</variant>
 	</country>
-	<country name="Suriname" abr="SUR">
+	<country name="Suriname" abr="SUR" driveOnLeft="true">
 		<variant>SR</variant>
 		<variant>SUR</variant>
 	</country>
@@ -1005,7 +1005,7 @@
 		<variant>SJ</variant>
 		<variant>SJM</variant>
 	</country>
-	<country name="Swaziland" abr="SWZ">
+	<country name="Swaziland" abr="SWZ" driveOnLeft="true">
 		<variant>SZ</variant>
 		<variant>SWZ</variant>
 		<variant>Swatini</variant>
@@ -1037,17 +1037,17 @@
 		<variant>TJK</variant>
 		<variant>Тоҷикистон</variant>
 	</country>
-	<country name="Tanzania" abr="TZA">
+	<country name="Tanzania" abr="TZA" driveOnLeft="true">
 		<variant>United Republic of Tanzania</variant>
 		<variant>TZ</variant>
 		<variant>TZA</variant>
 	</country>
-	<country name="Thailand" abr="THA">
+	<country name="Thailand" abr="THA" driveOnLeft="true">
 		<variant>TH</variant>
 		<variant>THA</variant>
 		<variant>ราชอาณาจักรไทย</variant>
 	</country>
-	<country name="Timor-Leste" abr="TLS">
+	<country name="Timor-Leste" abr="TLS" driveOnLeft="true">
 		<variant>TL</variant>
 		<variant>TLS</variant>
 		<variant>Timór Loro Sa'e</variant>
@@ -1058,7 +1058,7 @@
 		<variant>TGO</variant>
 		<variant>République Togolaise</variant>
 	</country>
-	<country name="Tokelau" abr="TKL">
+	<country name="Tokelau" abr="TKL" driveOnLeft="true">
 		<variant>TK</variant>
 		<variant>TKL</variant>
 	</country>
@@ -1066,7 +1066,7 @@
 		<variant>TO</variant>
 		<variant>TON</variant>
 	</country>
-	<country name="Trinidad and Tobago" abr="TTO">
+	<country name="Trinidad and Tobago" abr="TTO" driveOnLeft="true">
 		<variant>Trinidad</variant>
 		<variant>Tobago</variant>
 		<variant>TT</variant>
@@ -1086,15 +1086,15 @@
 		<variant>TKM</variant>
 		<variant>Türkmenistan</variant>
 	</country>
-	<country name="Turks and Caicos Islands" abr="TCA">
+	<country name="Turks and Caicos Islands" abr="TCA" driveOnLeft="true">
 		<variant>TC</variant>
 		<variant>TCA</variant>
 	</country>
-	<country name="Tuvalu" abr="TUV">
+	<country name="Tuvalu" abr="TUV" driveOnLeft="true">
 		<variant>TV</variant>
 		<variant>TUV</variant>
 	</country>
-	<country name="Uganda" abr="UGA">
+	<country name="Uganda" abr="UGA" driveOnLeft="true">
 		<variant>UG</variant>
 		<variant>UGA</variant>
 	</country>
@@ -1109,7 +1109,7 @@
 		<variant>ARE</variant>
 		<variant>الإمارات العربيّة المتّحدة</variant>
 	</country>
-	<country name="United Kingdom" abr="GBR">
+	<country name="United Kingdom" abr="GBR" driveOnLeft="true">
 		<variant>England</variant>
 		<variant>Scotland</variant>
 		<variant>Wales</variant>
@@ -1153,12 +1153,12 @@
 		<variant>VNM</variant>
 		<variant>Việt Nam</variant>
 	</country>
-	<country name="Virgin Islands, British" abr="VGB">
+	<country name="Virgin Islands, British" abr="VGB" driveOnLeft="true">
 		<variant>VG</variant>
 		<variant>VGB</variant>
 		<variant>British Virgin Islands</variant>
 	</country>
-	<country name="Virgin Islands, U.S." abr="VIR">
+	<country name="Virgin Islands, U.S." abr="VIR" driveOnLeft="true">
 		<variant>VI</variant>
 		<variant>VIR</variant>
 	</country>
@@ -1177,11 +1177,11 @@
 		<variant>YEM</variant>
 		<variant>‏اليمن‎</variant>
 	</country>
-	<country name="Zambia" abr="ZMB">
+	<country name="Zambia" abr="ZMB" driveOnLeft="true">
 		<variant>ZM</variant>
 		<variant>ZMB</variant>
 	</country>
-	<country name="Zimbabwe" abr="ZWE">
+	<country name="Zimbabwe" abr="ZWE" driveOnLeft="true">
 		<variant>ZW</variant>
 		<variant>ZWE</variant>
 	</country>
diff --git a/resources/help/en/options b/resources/help/en/options
index 47dcf1d..e8ac20f 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -347,13 +347,22 @@ Miscellaneous options:
 --route
 	Create maps that support routing.
 
+--drive-on=left|right|detect|detect,left|detect,right
+	Explicitly specify which side of the road vehicles are
+	expected to drive on. 
+	If the first option is detect, the program tries 
+	to find out the proper flag. If that detection
+	fails, the second value is used (or right if none is given).
+	With OSM data as input, the detection tries to find out  
+	the country each road is in and compares the number
+	of drive-on-left roads with the rest.
+	Use the --bounds option to make sure that the detection 
+	finds the correct country. 
+	
 --drive-on-left
 --drive-on-right
-	Explicitly specify which side of the road vehicles are
-	expected to drive on. If neither of these options are
-	specified, it is assumed that vehicles drive on the right
-	unless --check-roundabouts is specified and the first
-	roundabout processed is clockwise.
+	Deprecated: Use drive-on instead.
+	The options are translated to drive-on=left|right. 
 
 --check-roundabouts
 	Check that roundabouts have the expected direction (clockwise
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index f1389b0..9f108d2 100644
--- a/resources/mkgmap-version.properties
+++ b/resources/mkgmap-version.properties
@@ -1,2 +1,2 @@
-svn.version: 3333
-build.timestamp: 2014-08-08T07:32:50+0100
+svn.version: 3366
+build.timestamp: 2014-12-08T07:00:53+0000
diff --git a/resources/styles/default/inc/address b/resources/styles/default/inc/address
index 7c2b3bc..f01b26e 100644
--- a/resources/styles/default/inc/address
+++ b/resources/styles/default/inc/address
@@ -5,8 +5,8 @@
 
 # first set the country code
 mkgmap:country!=* & mkgmap:admin_level2=* { set mkgmap:country='${mkgmap:admin_level2}' }
-mkgmap:country!=* & addr:country=* { set mkgmap:country='${addr:country}' }
-mkgmap:country!=* & is_in:country=* { set mkgmap:country='${is_in:country}' }
+mkgmap:country!=* & addr:country=* { set mkgmap:country='${addr:country|country-ISO:}' }
+mkgmap:country!=* & is_in:country=* { set mkgmap:country='${is_in:country|country-ISO:}' }
 
 # country specific rules first
 
@@ -31,7 +31,7 @@ mkgmap:country=AUT & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='
 mkgmap:country=AUT & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:Gemeinde |subst:Stadt }' }
 
 # Poland = POL
-mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10}' }
+#After recent changes in OSM-Poland we don't use level 10 - all cities and villages are in level 8
 mkgmap:country=POL & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
 mkgmap:country=POL & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4|subst:województwo =>}' }
 
@@ -59,7 +59,21 @@ mkgmap:country=CHE & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='$
  
 # Canada
 mkgmap:country=CAN & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4}' }
-mkgmap:country=CAN & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+mkgmap:country=CAN & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:City of }' }
+
+# United States
+mkgmap:country=USA & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4}' }
+# New York City has different admin levels than the rest of the US.
+# https://wiki.openstreetmap.org/wiki/United_States_admin_level
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='New York County' { set mkgmap:city='New York' }
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='Bronx County' { set mkgmap:city='Bronx' }
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='Kings County' { set mkgmap:city='Brooklyn' }
+# Queens uses neighborhoods for city in postal addresses
+# http://en.wikipedia.org/wiki/List_of_Queens_neighborhoods
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='Queens County' & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8}' }
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='Queens County' { set mkgmap:city='Queens' }
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level5='New York City' & mkgmap:admin_level6='Richmond County' { set mkgmap:city='Staten Island' }
+mkgmap:country=USA & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:City of }' }
 
 # Ecuador = ECU
 mkgmap:country=ECU & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4}' }
diff --git a/resources/styles/default/inc/water_lines b/resources/styles/default/inc/water_lines
index 908f028..e829a8a 100644
--- a/resources/styles/default/inc/water_lines
+++ b/resources/styles/default/inc/water_lines
@@ -1,5 +1,6 @@
 natural=coastline [0x15 resolution 12]
 
+route=ferry & (motorcar=no | motor_vehicle=no) {add mkgmap:ferry=1} [0x1b road_class=0 road_speed=0 resolution 23]
 route=ferry {add mkgmap:ferry=1} [0x1b road_class=3 road_speed=0 resolution 19]
 
 waterway=canal [0x1f resolution 21]
diff --git a/resources/styles/default/points b/resources/styles/default/points
index eb88a5c..e571d63 100644
--- a/resources/styles/default/points
+++ b/resources/styles/default/points
@@ -42,21 +42,21 @@ barrier=kissing_gate | barrier=stile | barrier=block
 internet_access=yes { name 'Internet ${name}' | 'Internet' } [0x2f12 resolution 24 continue]
 internet_access=* & internet_access!=no & internet_access!=yes { name 'Internet(${internet_access}) ${name|def:}' } [0x2f12 resolution 24 continue]
 
-(highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
+(public_transport=platform | highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
 & (ref=* | route_ref=*) {
   set ref='${ref|def:}(${route_ref})';
 }
-(highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
+(public_transport=platform | highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
 & lit=yes & (shelter=yes | covered=yes)
 {
   set ref='${ref|def:}*';
 }
-(highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
+(public_transport=platform | highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
 & lit!=yes & (shelter=yes | covered=yes)
 {
   set ref='${ref|def:}+';
 }
-(highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
+(public_transport=platform | highway=bus_stop | railway=tram_stop | railway=halt | railway=station)
 & (shelter=no | covered=no)
 {
   set ref='${ref|def:}-';
@@ -190,8 +190,6 @@ landuse=village_green & name=* [0x2c06 resolution 24]
 healthcare=hospital | amenity=hospital [0x3002 resolution 22]
 healthcare=* | amenity=dentist | amenity=doctors [0x3002 resolution 24]
 
-highway=bus_stop [0x2f17 resolution 24]
-
 highway=motorway_junction [0x2100 resolution 24]
 
 highway=services & mkgmap:area2poi!=true [0x210f resolution 24 default_name 'Services']
@@ -224,9 +222,9 @@ natural=peak {name '${name|def:}${ele|height:m=>ft|def:}' } [0x6616 resolution 2
 natural=rock [0x6614 resolution 24]
 natural=volcano [0x2c0c resolution 24]
 
-railway=halt [0x2f08 resolution 23]
 railway=station [0x2f08 resolution 22]
-railway=tram_stop [0x2f17 resolution 24]
+( public_transport=platform & rail=yes ) | railway=halt [0x2f08 resolution 23]
+public_transport=platform | highway=bus_stop | railway=tram_stop [0x2f17 resolution 24]
 
 shop=bakers [0x2e02 resolution 24]
 shop=bakery [0x2e02 resolution 24]
diff --git a/src/uk/me/parabola/imgfmt/app/BufferedImgFileReader.java b/src/uk/me/parabola/imgfmt/app/BufferedImgFileReader.java
index 0a1776c..2a9b295 100644
--- a/src/uk/me/parabola/imgfmt/app/BufferedImgFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/BufferedImgFileReader.java
@@ -201,11 +201,7 @@ public class BufferedImgFileReader implements ImgFileReader {
 
 		int ch = firstChar & 0xff;
 		do {
-			if (str11.length() == 0) {
-				// Not found
-				if (ch < 0x80)
-					return "";
-			}
+			assert !(str11.length() == 0 && (ch & 0x80) == 0);
 
 			if ((ch & 0x80) != 0)
 				--term;
@@ -215,9 +211,8 @@ public class BufferedImgFileReader implements ImgFileReader {
 		} while (term != 0);
 
 		// Remove any trailing delimiters
-		int idx;
-		if ((idx = str11.lastIndexOf("A")) >= 0)
-			str11.setLength(idx);
+		while (str11.length() > 0 && str11.charAt(str11.length()-1) == 'A')
+			str11.setLength(str11.length()-1);
 
 		// Convert in-line delimiters to the char delimiter
 		int len = str11.length();
diff --git a/src/uk/me/parabola/imgfmt/app/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java
index 5daa21a..c1486e7 100644
--- a/src/uk/me/parabola/imgfmt/app/Coord.java
+++ b/src/uk/me/parabola/imgfmt/app/Coord.java
@@ -458,7 +458,7 @@ public class Coord implements Comparable<Coord> {
 	public Coord makeBetweenPoint(Coord other, double fraction) {
 		int dLat30 = other.getHighPrecLat() - getHighPrecLat();
 		int dLon30 = other.getHighPrecLon() - getHighPrecLon();
-		if ((Math.abs(dLat30) < 1000000 && Math.abs(dLon30) < 1000000 )){
+		if (dLon30 == 0 || Math.abs(dLat30) < 1000000 && Math.abs(dLon30) < 1000000 ){
 			// distances are rather small, we can use flat earth approximation
 			int lat30 = (int) (getHighPrecLat() + dLat30 * fraction);
 			int lon30 = (int) (getHighPrecLon() + dLon30 * fraction);
@@ -705,16 +705,21 @@ public class Coord implements Comparable<Coord> {
 	    double lat2 = lat1 + deltaLat;
 	    // check for some daft bugger going past the pole, normalise latitude if so
 	    if (Math.abs(lat2) > Math.PI/2) lat2 = lat2>0 ? Math.PI-lat2 : -Math.PI-lat2;
-
-	    double deltaPhi = Math.log(Math.tan(lat2/2+Math.PI/4)/Math.tan(lat1/2+Math.PI/4));
-	    double q = Math.abs(deltaPhi) > 10e-12 ? deltaLat / deltaPhi : Math.cos(lat1); // E-W course becomes ill-conditioned with 0/0
-
-	    double deltaLon = distRad*Math.sin(brngRad)/q;
-
-	    double lon2 = lon1 + deltaLon;
-
-	    lon2 = (lon2 + 3*Math.PI) % (2*Math.PI) - Math.PI; // normalise to -180..+180º
-
+	    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;
+
+		    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));
 	}
 	
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java b/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
index 0523291..fd58cd5 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/LBLFileReader.java
@@ -386,14 +386,13 @@ public class LBLFileReader extends ImgFile {
 
 			if (hasStreetNum) {
 				byte b = reader.get();
-				String num = reader.getBase11str(b, '-');
-				if (num.isEmpty()) {
+				if ((b & 0x80) == 0) {
 					int mpoffset = (b << 16) & 0xff0000;
 					mpoffset |= reader.getChar() & 0xffff;
 
-					poi.setComplexPhoneNumber(fetchLabel(mpoffset));
+					poi.setComplexStreetNumber(fetchLabel(mpoffset));
 				} else {
-					poi.setSimpleStreetNumber(num);
+					poi.setSimpleStreetNumber(reader.getBase11str(b, '-'));
 				}
 			}
 
@@ -425,16 +424,14 @@ public class LBLFileReader extends ImgFile {
 			
 			if (hasPhone) {
 				byte b = reader.get();
-				String num = reader.getBase11str(b, '-');
-				if (num.isEmpty()) {
+				if ((b & 0x80) == 0) {
 					// Yes this is a bit strange it is a byte followed by a char
 					int mpoffset = (b << 16) & 0xff0000;
 					mpoffset |= reader.getChar() & 0xffff;
 
-					Label label = fetchLabel(mpoffset);
-					poi.setComplexPhoneNumber(label);
+					poi.setComplexPhoneNumber(fetchLabel(mpoffset));
 				} else {
-					poi.setSimplePhoneNumber(num);
+					poi.setSimplePhoneNumber(reader.getBase11str(b, '-'));
 				}
 			}
 
diff --git a/src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java b/src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java
index a44e945..e87ca70 100644
--- a/src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java
+++ b/src/uk/me/parabola/imgfmt/app/lbl/POIRecord.java
@@ -295,27 +295,6 @@ public class POIRecord {
 	}
 
 	/**
-	 * Address abbreviations.
-	 */
-	//static class AddrAbbr {
-	//	private final char code;
-	//	private final String value;
-	//
-	//	AddrAbbr(char code, String value) {
-	//		this.code = code;
-	//		this.value = value;
-	//	}
-	//
-	//	public String toString() {
-	//		return value;
-	//	}
-	//
-	//	public char getCode() {
-	//		return code;
-	//	}
-	//}
-
-	/**
 	 * Street and Phone numbers can be stored in two different ways in the poi record
 	 * Simple Number that only contain digits are coded in base 11 coding.
 	 * This helper class tries to code the given number. If the number contains other
@@ -330,7 +309,7 @@ public class POIRecord {
 		 * Encode a string as base 11.
 		 * @param str The input string.
 		 * @return If the string is not all numeric (or A) then false is returned
-		 * and the string will be encoded as a label instead.
+		 * and this object is invalid.
 		 */
 		public boolean set(String str) {
 
@@ -393,10 +372,18 @@ public class POIRecord {
 			return encodedSize;
 		}
 
-		private int decodeChar(char ch)
-		{
-			return (ch - '0');
+		/**
+		 * Convert the characters '0' to '9' and '-' to a number 0-10 (base 11).
+		 * @param ch The character to convert.
+		 * @return A number between 0 and 10 or -1 if the character is not valid.
+		 */
+		private int decodeChar(char ch) {
+			if (ch == '-')
+				return 10;
+			else if (ch >= '0' && ch <= '9')
+				return (ch - '0');
+			else
+				return -1;
 		}
-
 	}	
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/NODFile.java b/src/uk/me/parabola/imgfmt/app/net/NODFile.java
index a684dff..6649368 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NODFile.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NODFile.java
@@ -186,4 +186,8 @@ public class NODFile extends ImgFile {
 		this.roads = roads;
 		this.boundary = boundary;
 	}
+
+	public void setDriveOnLeft(boolean dol) {
+		nodHeader.setDriveOnLeft(dol);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/net/NODHeader.java b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
index 59744b1..65a57a8 100644
--- a/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/net/NODHeader.java
@@ -47,16 +47,7 @@ public class NODHeader extends CommonHeader {
     private int align;
     private int mult1;
 	private int tableARecordLen;
-
-	/** 
-	 * The driveOnLeft flag is set via a static method. Using a ThreadLocal
-	 * ensures thread safety when using more than one thread.
-	 */
-	private static final ThreadLocal<Boolean> driveOnLeft = new ThreadLocal<Boolean>() {
-		protected Boolean initialValue() {
-			return Boolean.FALSE;
-		}
-	};
+	private boolean driveOnLeft;
 
 	public NODHeader() {
 		super(HEADER_LEN, "GARMIN NOD");
@@ -115,7 +106,7 @@ public class NODHeader extends CommonHeader {
 		assert Integer.bitCount(DISTANCE_MULT) == 1;
 		assert DISTANCE_MULT_SHIFT < 8;
 		flags |= DISTANCE_MULT_SHIFT << 5;
-		if(driveOnLeft.get())
+		if(driveOnLeft)
 			flags |= 0x0100;
 		
 		writer.putInt(flags);
@@ -191,8 +182,8 @@ public class NODHeader extends CommonHeader {
 		return classBoundaries;
 	}
 
-	public static void setDriveOnLeft(boolean dol) {
-		driveOnLeft.set(dol);
+	public void setDriveOnLeft(boolean dol) {
+		driveOnLeft = dol;
 	}
 
     public int getFlags() {
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
index 3b55592..95e5a4f 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
@@ -53,7 +53,7 @@ import uk.me.parabola.log.Logger;
  * @author Robert Vollmert
  */
 
-public class RoadDef implements Comparable<RoadDef> {
+public class RoadDef {
 	private static final Logger log = Logger.getLogger(RoadDef.class);
 
 	public static final int NET_FLAG_NODINFO  = 0x40;
@@ -689,23 +689,6 @@ public class RoadDef implements Comparable<RoadDef> {
 		netFlags |= NET_FLAG_ADDRINFO;
 	}
 
-	public int compareTo(RoadDef other) {
-		// sort by city name - this is used to group together
-		// roads that have been split into segments
-		if(other == this)
-			return 0;
-
-		// TODO: look at what this is doing...
-		if(city != null && other.city != null)
-			return city.getName().compareTo(other.city.getName());
-		if (hashCode() == other.hashCode())
-			return 0;
-		else if (hashCode() < other.hashCode())
-			return -1;
-		else
-			return 0;
-	}
-
 	public City getCity() {
 		return city;
 	}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
index d8ee51d..aaf455d 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
@@ -85,7 +85,7 @@ public class Subdivision {
 	// Set if this is the last one.
 	private boolean last;
 
-	private final List<Subdivision> divisions = new ArrayList<Subdivision>();
+	private final List<Subdivision> divisions = new ArrayList<>();
 
 	private int extTypeAreasOffset;
 	private int extTypeLinesOffset;
@@ -299,9 +299,8 @@ public class Subdivision {
 				} else {
 					maxSetIdx = 2;
 				}
-			} else {
-				maxSetIdx = 3;
 			}
+
 			String[] refs = Arrays.copyOfRange(labels, 1, maxSetIdx+1);
 			if(refs.length == 1) {
 				// don't bother to add a single ref that looks the
@@ -545,7 +544,6 @@ public class Subdivision {
 	}
 	/**
 	 * Set the sizes for the extended type data. See {@link #writeLastExtTypeOffsetsRecord(ImgFileWriter)} 
-	 * @param reader
 	 */
 	public void readLastExtTypeOffsetsRecord(ImgFileReader reader) {
 		extTypeAreasSize = reader.getInt() - extTypeAreasOffset;
@@ -640,4 +638,20 @@ public class Subdivision {
 	public Coord getCenter(){
 		return new Coord(getLatitude(),getLongitude());
 	}
+
+	/**
+	 * Get the unshifted width of the subdivision.
+	 * @return The true (unshifted) width.
+	 */
+	public int getWidth() {
+		return width << getShift();
+	}
+
+	/**
+	 * Get the unshifted height of the subdivision.
+	 * @return The true (unshifted) height.
+	 */
+	public int getHeight() {
+		return height << getShift();
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/TREFile.java b/src/uk/me/parabola/imgfmt/app/trergn/TREFile.java
index 589f018..8542279 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/TREFile.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/TREFile.java
@@ -361,4 +361,8 @@ public class TREFile extends ImgFile implements Configurable {
 	public void addPoiDisplayFlags(byte b) {
 		header.addPoiDisplayFlags(b);
 	}
+
+	public void setDriveOnLeft(boolean b) {
+		header.setDriveOnLeft(b);
+	}
 }
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java b/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
index e849f64..11eb272 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
@@ -112,7 +112,7 @@ public class TREFileReader extends ImgReader {
 				int endRgnOffset = reader.getu3();
 
 				SubdivData subdivData = new SubdivData(flags,
-						lat, lon, width, height,
+						lat, lon, 2*width, 2*height,
 						lastRgnOffset, endRgnOffset);
 
 				Subdivision subdiv = Subdivision.readSubdivision(mapLevels[count], subdivData);
@@ -127,7 +127,7 @@ public class TREFileReader extends ImgReader {
 	}
 	
 	/**
-	 * Read the extended type info for the sub divisions. Corresponds to {@link #TREFile.writeExtTypeOffsetsRecords()}.
+	 * Read the extended type info for the sub divisions. Corresponds to {@link TREFile#writeExtTypeOffsetsRecords()}.
 	 */
 	private void readExtTypeOffsetsRecords() {
 		ImgFileReader reader = getReader();
@@ -230,4 +230,4 @@ public class TREFileReader extends ImgReader {
 		}
 		return msgs.toArray(new String[msgs.size()]);
 	}
-}
\ No newline at end of file
+}
diff --git a/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java b/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
index e3d3fc1..ee71936 100644
--- a/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
+++ b/src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
@@ -240,9 +240,6 @@ public class TREHeader extends CommonHeader {
 
 		if (props.containsKey("transparent"))
 			poiDisplayFlags |= POI_FLAG_TRANSPARENT;
-
-		if (props.containsKey("drive-on-left"))
-			poiDisplayFlags |= POI_FLAG_DRIVE_ON_LEFT;
 	}
 	
 	/**
@@ -260,7 +257,12 @@ public class TREHeader extends CommonHeader {
 	public void setMapId(int id) {
 		mapId = id;
 	}
-	
+
+	public void setDriveOnLeft(boolean dol) {
+		if (dol)
+			this.poiDisplayFlags |= POI_FLAG_DRIVE_ON_LEFT; 
+	}
+
 	public void addPoiDisplayFlags(byte poiDisplayFlags) {
 		this.poiDisplayFlags |= poiDisplayFlags;
 	}	
diff --git a/src/uk/me/parabola/mkgmap/CommandArgsReader.java b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
index b4ba2e9..9d5fd60 100644
--- a/src/uk/me/parabola/mkgmap/CommandArgsReader.java
+++ b/src/uk/me/parabola/mkgmap/CommandArgsReader.java
@@ -119,7 +119,7 @@ public class CommandArgsReader {
 		// If there is more than one filename argument we inform of this fact
 		// via a fake option.
 		proc.processOption("number-of-files", String.valueOf(arglist.getFilenameCount()));
-
+		
 		// Now process the arguments in order.
 		for (ArgType a : arglist) {
 			a.processArg();
@@ -145,6 +145,20 @@ public class CommandArgsReader {
 	 */
 	private void addOption(String optval) {
 		CommandOption opt = new CommandOption(new Option(optval));
+		boolean legacyOptionDetected = false; 
+		// translate legacy options drive-on-left and drive-on-right
+		String option = opt.getOption();
+		if ("drive-on-left".equals(option)){
+			opt = new CommandOption(new Option("drive-on=left"));
+			legacyOptionDetected = true;
+		} 
+		if ("drive-on-right".equals(option)){
+			opt = new CommandOption(new Option("drive-on=right"));
+			legacyOptionDetected = true;
+		} 
+		if (legacyOptionDetected){
+			System.err.println("Option " + option + " is deprecated. Will use " + opt.getOption() + "=" + opt.getValue() + " as replacement for " + optval);
+		}
 		addOption(opt);
 	}
 
@@ -267,6 +281,7 @@ public class CommandArgsReader {
 					fmt.format("%8.8s", mapname);
 				}
 				args.setProperty("mapname", fmt.toString());
+				fmt.close();
 			} catch (NumberFormatException e) {
 				// If the name is not a number then we just leave it alone...
 			}
diff --git a/src/uk/me/parabola/mkgmap/build/LocatorConfig.java b/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
index d29de30..c754f33 100644
--- a/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
+++ b/src/uk/me/parabola/mkgmap/build/LocatorConfig.java
@@ -35,13 +35,15 @@ public class LocatorConfig {
 	private static final Logger log = Logger.getLogger(LocatorConfig.class);
 
 	/** maps country name (in all variants) to the 3 letter ISO code */
-	private final Map<String,String>  isoMap = new HashMap<String,String>();
+	private final Map<String,String>  isoMap = new HashMap<>();
 	/** maps the ISO code to the offset of the region in the is_in tag */
-	private final Map<String,Integer>  regOffsetMap = new HashMap<String,Integer>();
+	private final Map<String,Integer>  regOffsetMap = new HashMap<>();
 	/** maps the ISO code to the POI display flag */
-	private final Map<String,Integer>  poiDispFlagMap = new HashMap<String,Integer>();
+	private final Map<String,Integer>  poiDispFlagMap = new HashMap<>();
+	/** maps the ISO code to the drive-on-left flag */
+	private final Map<String,Boolean>  driveOnLeftFlagMap = new HashMap<>();
 	/** contains the names of all continents */
-	private final Set<String> continents = new HashSet<String>();
+	private final Set<String> continents = new HashSet<>();
 
 	/** maps ISO => default country name */
 	private final Map<String, String> defaultCountryNames = new HashMap<String, String>();
@@ -153,6 +155,10 @@ public class LocatorConfig {
 								if (poiDispTag != 0x0 && iso != null) {
 									setPoiDispTag(iso, poiDispTag);
 								}
+								Node driveOnLeft = attr.getNamedItem("driveOnLeft");
+								if (driveOnLeft != null && "true".equals(driveOnLeft.getNodeValue())){
+									driveOnLeftFlagMap.put(iso, true);
+								}
 							}
 
 							if (iso != null) {
@@ -339,5 +345,14 @@ public class LocatorConfig {
 		String s = continent.toUpperCase().trim();
 		return continents.contains(s);
 	}		
+
+	public synchronized boolean getDriveOnLeftFlag(String iso)
+	{
+		if (iso == null)
+			return false;
+		if (driveOnLeftFlagMap.containsKey(iso))
+			return true;
+		return false;
+	}
 }
 
diff --git a/src/uk/me/parabola/mkgmap/build/MapArea.java b/src/uk/me/parabola/mkgmap/build/MapArea.java
index 650b4bd..df899f4 100644
--- a/src/uk/me/parabola/mkgmap/build/MapArea.java
+++ b/src/uk/me/parabola/mkgmap/build/MapArea.java
@@ -17,6 +17,7 @@
 package uk.me.parabola.mkgmap.build;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
@@ -52,7 +53,8 @@ public class MapArea implements MapDataSource {
 
 	private static final int INITIAL_CAPACITY = 100;
 	private static final int MAX_RESOLUTION = 24;
-
+	private static final int LARGE_OBJECT_DIM = 8192;
+	
 	public static final int POINT_KIND    = 0;
 	public static final int LINE_KIND     = 1;
 	public static final int SHAPE_KIND    = 2;
@@ -72,9 +74,9 @@ public class MapArea implements MapDataSource {
 	private int maxLon = Integer.MIN_VALUE;
 
 	// The contents of the area.
-	private final List<MapPoint> points = new ArrayList<MapPoint>(INITIAL_CAPACITY);
-	private final List<MapLine> lines = new ArrayList<MapLine>(INITIAL_CAPACITY);
-	private final List<MapShape> shapes = new ArrayList<MapShape>(INITIAL_CAPACITY);
+	private final List<MapPoint> points = new ArrayList<>(INITIAL_CAPACITY);
+	private final List<MapLine> lines = new ArrayList<>(INITIAL_CAPACITY);
+	private final List<MapShape> shapes = new ArrayList<>(INITIAL_CAPACITY);
 
 	// amount of space required for the contents
 	private final int[] sizes = new int[NUM_KINDS];
@@ -98,8 +100,6 @@ public class MapArea implements MapDataSource {
 	public MapArea(MapDataSource src, int resolution) {
 		this.areaResolution = 0;
 		this.bounds = src.getBounds();
-		addToBounds(bounds);
-
 		for (MapPoint p : src.getPoints()) {
 			if(bounds.contains(p.getLocation()))
 				addPoint(p);
@@ -119,9 +119,7 @@ public class MapArea implements MapDataSource {
 		MapFilterChain chain = new MapFilterChain() {
 			public void doFilter(MapElement element) {
 				MapShape shape = (MapShape) element;
-				shapes.add(shape);
-				addToBounds(shape.getBounds());
-				addSize(element, shape.hasExtendedType()? XT_SHAPE_KIND : SHAPE_KIND);
+				addShape(shape);
 			}
 		};
 
@@ -148,9 +146,7 @@ public class MapArea implements MapDataSource {
 		MapFilterChain chain = new MapFilterChain() {
 			public void doFilter(MapElement element) {
 				MapLine line = (MapLine) element;
-				lines.add(line);
-				addToBounds(line.getBounds());
-				addSize(element, line.hasExtendedType()? XT_LINE_KIND : LINE_KIND);
+				addLine(line);
 			}
 		};
 
@@ -173,7 +169,6 @@ public class MapArea implements MapDataSource {
 	private MapArea(Area area, int res) {
 		bounds = area;
 		areaResolution = res;
-		addToBounds(area);
 	}
 
 	/**
@@ -193,6 +188,7 @@ public class MapArea implements MapDataSource {
 		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())
@@ -212,14 +208,28 @@ public class MapArea implements MapDataSource {
 				used[pos] = true;
 			}
 
-			
+			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; 
+			}
+
 			int areaIndex = 0;
 			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)
+				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;
+					}
+				}
 				else 
 					areaIndex = ++areaIndex % mapAreas.length;
 				mapAreas[areaIndex].addLine(l);
@@ -227,8 +237,15 @@ public class MapArea implements MapDataSource {
 			}
 
 			for (MapShape e : this.shapes) {
-				if (useNormalSplit)
+				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;
+					}
+				}
 				else 
 					areaIndex = ++areaIndex % mapAreas.length;
 				mapAreas[areaIndex].addShape(e);
@@ -245,6 +262,14 @@ public class MapArea implements MapDataSource {
 				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;
+			}
 			return mapAreas;
 		}
 	}
@@ -462,7 +487,7 @@ public class MapArea implements MapDataSource {
 	 */
 	private void addPoint(MapPoint p) {
 		points.add(p);
-		addToBounds(p.getLocation());
+		addToBounds(p.getLocation()); 
 		addSize(p, p.hasExtendedType()? XT_POINT_KIND : POINT_KIND);
 	}
 
@@ -511,18 +536,26 @@ public class MapArea implements MapDataSource {
 			maxLon = l;
 	}
 
+	/**
+	 * Add to bounds considering high precision values. 
+	 * @param co
+	 */
 	private void addToBounds(Coord co) {
-		int l = co.getLatitude();
-		if (l < minLat)
-			minLat = l;
-		if (l > maxLat)
-			maxLat = l;
-
-		l = co.getLongitude();
-		if (l < minLon)
-			minLon = l;
-		if (l > maxLon)
-			maxLon = l;
+		int lat30 = co.getHighPrecLat();
+		int latLower  = lat30 >> Coord.DELTA_SHIFT;
+		int latUpper  = (latLower << Coord.DELTA_SHIFT) < lat30 ? 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;
+		if (lonLeft < minLon)
+			minLon = lonLeft;
+		if (lonRight > maxLon)
+			maxLon = lonRight;
 	}
 
 	
@@ -575,4 +608,13 @@ public class MapArea implements MapDataSource {
 		}
 		return xcell * ny + ycell;
 	}
+
+	/**
+	 * @return true if this area contains any data
+	 */
+	public boolean hasData() {
+		if (points.isEmpty() && lines.isEmpty() && shapes.isEmpty())
+			return false;
+		return true;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index 54ad0b5..49a9aaf 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -110,7 +110,7 @@ public class MapBuilder implements Configurable {
 	private List<String> copyrights = new ArrayList<String>();
 
 	private boolean doRoads;
-
+	private Boolean driveOnLeft;
 	private Locator locator;
 
 	private final java.util.Map<String, Highway> highways = new HashMap<String, Highway>();
@@ -180,6 +180,11 @@ public class MapBuilder implements Configurable {
 		
 		locator = new Locator(props);
 		locator.setDefaultCountry(countryName, countryAbbr);
+		String driveOn = props.getProperty("drive-on",null);
+		if ("left".equals(driveOn))
+			driveOnLeft = true;
+		if ("right".equals(driveOn))
+			driveOnLeft = false;
 	}
 
 	/**
@@ -214,6 +219,16 @@ public class MapBuilder implements Configurable {
 		processOverviews(map, src);
 		processInfo(map, src);
 		makeMapAreas(map, src);
+		 
+		if (driveOnLeft == null){
+			// check if source gives info about driving side
+			if (src instanceof MapperBasedMapDataSource){
+				driveOnLeft = ((MapperBasedMapDataSource) src).getDriveOnLeft();
+			}
+		}
+		if (driveOnLeft == null)
+			driveOnLeft = false;
+		treFile.setDriveOnLeft(driveOnLeft);
 
 		treFile.setLastRgnPos(rgnFile.position() - RGNHeader.HEADER_LEN);
 
@@ -229,6 +244,7 @@ public class MapBuilder implements Configurable {
 			NODFile nodFile = map.getNodFile();
 			if (nodFile != null) {
 				nodFile.setNetwork(network.getCenters(), network.getRoadDefs(), network.getBoundary());
+				nodFile.setDriveOnLeft(driveOnLeft);
 				nodFile.write();
 			}
 			netFile.write(lblFile.numCities(), lblFile.numZips());
@@ -524,22 +540,16 @@ public class MapBuilder implements Configurable {
 					r.setStreetName(streetName);			  
 				}
 
-				if(p.getHouseNumber() != null)
-				{
-					if(!r.setSimpleStreetNumber(p.getHouseNumber()))
-					{
-						Label streetNumber = lbl.newLabel(p.getHouseNumber());
-						r.setComplexStreetNumber(streetNumber);
-					}
+				String houseNumber = p.getHouseNumber();
+				if (houseNumber != null && !houseNumber.isEmpty()) {
+					if(!r.setSimpleStreetNumber(houseNumber))
+						r.setComplexStreetNumber(lbl.newLabel(houseNumber));
 				}
 
-				if(p.getPhone() != null)
-				{
-					if(!r.setSimplePhoneNumber(p.getPhone()))
-					{
-						Label phoneNumber = lbl.newLabel(p.getPhone());
-						r.setComplexPhoneNumber(phoneNumber);
-					}
+				String phone = p.getPhone();
+				if (phone != null && !phone.isEmpty()) {
+					if(!r.setSimplePhoneNumber(phone))
+						r.setComplexPhoneNumber(lbl.newLabel(phone));
 				}	
 		  	
 				poimap.put(p, r);
diff --git a/src/uk/me/parabola/mkgmap/build/MapSplitter.java b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
index e787319..b3884d5 100644
--- a/src/uk/me/parabola/mkgmap/build/MapSplitter.java
+++ b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
@@ -59,8 +59,12 @@ public class MapSplitter {
 	public static final int MAX_XT_LINES_SIZE  = 0xff00;
 	public static final int MAX_XT_SHAPES_SIZE = 0xff00;
 	
-	public static final int MIN_DIMENSION = 10; // just a reasonable value 
+	public static final int MIN_DIMENSION = 10; // just a reasonable value
 
+	// 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; 
+	
 	private final Zoom zoom;
 
 	/**
@@ -99,7 +103,7 @@ public class MapSplitter {
 		// Now step through each area and see if any have too many map features
 		// in them.  For those that do, we further split them.  This is done
 		// recursively until everything fits.
-		List<MapArea> alist = new ArrayList<MapArea>();
+		List<MapArea> alist = new ArrayList<>();
 		addAreasToList(areas, alist, 0);
 
 		MapArea[] results = new MapArea[alist.size()];
@@ -120,6 +124,8 @@ public class MapSplitter {
 		for (MapArea area : areas) {
 			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) + 
@@ -129,7 +135,8 @@ public class MapSplitter {
 						 ", lines = " + area.getNumLines() + "/" + sizes[MapArea.LINE_KIND] +
 						 ", shapes = " + area.getNumShapes() + "/" + sizes[MapArea.SHAPE_KIND]);
 			}
-
+			boolean doSplit = false;
+			
 			if (area.getNumLines() > MAX_NUM_LINES ||
 				area.getNumPoints() > MAX_NUM_POINTS ||
 				(sizes[MapArea.POINT_KIND] +
@@ -137,7 +144,21 @@ public class MapSplitter {
 				 sizes[MapArea.SHAPE_KIND]) > MAX_RGN_SIZE ||
 				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) {
+				sizes[MapArea.XT_SHAPE_KIND] > MAX_XT_SHAPES_SIZE)
+				doSplit = true; // we must split
+			else if (bounds.getMaxDimension() > MIN_DIMENSION) {
+				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);
+						doSplit = true;
+					}
+				}
+			}
+			if (doSplit){
 				if (bounds.getMaxDimension() > MIN_DIMENSION) {
 					if (log.isDebugEnabled())
 						log.debug("splitting area", area);
diff --git a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
index 645643f..f173a2c 100644
--- a/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
+++ b/src/uk/me/parabola/mkgmap/combiners/GmapsuppBuilder.java
@@ -536,19 +536,18 @@ public class GmapsuppBuilder implements Combiner {
 				totHeaderEntries += mdrSlots;
 			}
 
-			// There are 2 entries for the header itself.
-			totHeaderEntries += 2;
+			// Add for header itself, plus the first directory block.
+			totHeaderEntries += DIRECTORY_OFFSET_ENTRY + 1;
 			int totHeaderBlocks = totHeaderEntries * 512 / bs;
 
 			log.info("total blocks for", bs, "is", totHeaderBlocks, "based on slots=", totHeaderEntries);
 
-			int reserveEntries = DIRECTORY_OFFSET_ENTRY + 1 + totHeaderEntries;
-			if (totBlocks + reserveEntries < 0xfffe && totHeaderBlocks <= ENTRY_SIZE) {
-				return new BlockInfo(bs, reserveEntries);
+			if (totBlocks + totHeaderEntries < 0xfffe && totHeaderBlocks <= ENTRY_SIZE) {
+				return new BlockInfo(bs, totHeaderEntries);
 			}
 		}
 
-		throw new IllegalArgumentException("hmm");
+		throw new IllegalArgumentException("Could not select a suitable block size. Try to reduce the number of splits.");
 	}
 
 	public void setCreateIndex(boolean create) {
diff --git a/src/uk/me/parabola/mkgmap/main/StyleTester.java b/src/uk/me/parabola/mkgmap/main/StyleTester.java
index 41f2ee1..cf49a2f 100644
--- a/src/uk/me/parabola/mkgmap/main/StyleTester.java
+++ b/src/uk/me/parabola/mkgmap/main/StyleTester.java
@@ -294,6 +294,10 @@ public class StyleTester implements OsmConverter {
 		converter.end();
 	}
 
+	@Override
+	public Boolean getDriveOnLeft() {
+		return null; // unknown
+	}
 
 	private static void printResult(String[] results) {
 		for (String s : results) {
@@ -670,6 +674,11 @@ public class StyleTester implements OsmConverter {
 					rule.setFinalizeRule(finalizeRule);
 				}
 			}
+
+			@Override
+			public void printStats(String header) {
+				// TODO Auto-generated method stub
+			}
 		}
 
 		/**
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
index 4eaa7bb..8151c1a 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ActionRule.java
@@ -18,6 +18,7 @@ package uk.me.parabola.mkgmap.osmstyle;
 
 import java.util.List;
 
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.osmstyle.actions.Action;
 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.reader.osm.Element;
@@ -36,10 +37,13 @@ import uk.me.parabola.mkgmap.reader.osm.TypeResult;
  * @author Steve Ratcliffe
  */
 public class ActionRule implements Rule {
+	private static final Logger statsLog = Logger.getLogger(ActionRule.class.getPackage().getName()+".stats");
 	private Op expression;
 	private final List<Action> actions;
 	private final GType type;
 	private Rule finalizeRule;
+	private long numEval; // count how often the expression was evaluated 
+	private long numTrue; // count how often the evaluation returned true
 
 	/** Finalize rules must not have an element type definition so the add method must never be called. */
 	private final static TypeResult finalizeTypeResult = new TypeResult() {
@@ -66,9 +70,10 @@ public class ActionRule implements Rule {
 	public int resolveType(int cacheId, Element el, TypeResult result) {
 		Element element = el;
 		if (expression != null) {
+			numEval++;
 			if (!expression.eval(cacheId, element))
 				return cacheId;
-				
+			numTrue++;
 			// If this is a continue and we are not to propagate the effects
 			// of the action on the element to further rules, then make
 			// a copy of the element so that the original is unsullied.
@@ -108,8 +113,10 @@ public class ActionRule implements Rule {
 	public void resolveType(Element el, TypeResult result) {
 		Element element = el;
 		if (expression != null) {
+			numEval++;
 			if (!expression.eval(element))
 				return;
+			numTrue++;
 			// If this is a continue and we are not to propagate the effects
 			// of the action on the element to further rules, then make
 			// a copy of the element so that the original is unsullied.
@@ -168,4 +175,9 @@ public class ActionRule implements Rule {
 		this.expression = expression;
 	}
 	
+	@Override
+	public void printStats(String header) {
+		if (statsLog.isInfoEnabled())
+			statsLog.info(header,"stats (rule/evals/true)", this.toString() + "/" + numEval + "/" + numTrue);
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
index 8c934ad..7df93f7 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/ExpressionRule.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.osmstyle;
 
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.GType;
@@ -29,9 +30,13 @@ import uk.me.parabola.mkgmap.reader.osm.TypeResult;
  * @author Steve Ratcliffe
  */
 public class ExpressionRule implements Rule {
+	private static final Logger statsLog = Logger.getLogger(ExpressionRule.class.getPackage().getName()+".stats");
+
 	private Op expression;
 	private final GType gtype;
 	private Rule finalizeRule;
+	private long numEval; // count how often the expression was evaluated 
+	private long numTrue; // count how often the evaluation returned true
 
 	/** Finalize rules must not have an element type definition so the add method must never be called. */
 	private final static TypeResult finalizeTypeResult = new TypeResult() {
@@ -47,7 +52,9 @@ public class ExpressionRule implements Rule {
 
 	
 	public void resolveType(Element el, TypeResult result) {
+		numEval++;
 		if (expression.eval(el)) {
+			numTrue++;
 			// expression matches
 			if (finalizeRule != null) {
 				if (gtype.isContinueSearch()) {
@@ -63,7 +70,9 @@ public class ExpressionRule implements Rule {
 	}
 
 	public int resolveType(int cacheId, Element el, TypeResult result) {
+		numEval++;
 		if (expression.eval(cacheId, el)){
+			numTrue++;
 			if (finalizeRule != null) {
 				if (gtype.isContinueSearch()) {
 					el = el.copy();
@@ -93,5 +102,10 @@ public class ExpressionRule implements Rule {
 	public void setOp(Op expression){
 		this.expression = expression;
 	}
-	
+
+	@Override
+	public void printStats(String header) {
+		if (statsLog.isInfoEnabled())
+			statsLog.info(header,"stats (rule/evals/true)", this.toString() + "/" + numEval + "/" + numTrue);
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
index f9459a1..1d69c56 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
@@ -243,7 +243,7 @@ public class RuleFileReader {
 	 * from the expression.
 	 */
 	private void saveRule(TokenScanner scanner, Op op, ActionList actions, GType gt) {
-		log.info("EXP", op, ", type=", gt);
+		log.debug("EXP", op, ", type=", gt);
 
 		// check if the type definition is allowed
 		if (inFinalizeSection && gt != null)
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
index b54cb6c..77a1f1f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/RuleSet.java
@@ -46,6 +46,7 @@ import uk.me.parabola.mkgmap.reader.osm.WatchableTypeResult;
 public class RuleSet implements Rule, Iterable<Rule> {
 	private static final Logger log = Logger.getLogger(RuleSet.class);
 	private Rule[] rules;
+	private Rule finalizeRule;
 
 	// identifies cached values 
 	int cacheId;
@@ -259,5 +260,18 @@ public class RuleSet implements Rule, Iterable<Rule> {
 			rule.setFinalizeRule(finalizeRule);
 		
 		compiled = false;
+		this.finalizeRule = finalizeRule;  
 	}
+
+	@Override
+	public void printStats(String header) {
+		if (rules == null)
+		  return;
+		for (Rule rule : rules){ 
+			rule.printStats(header);
+		}
+		if (finalizeRule != null)
+			finalizeRule.printStats(header);
+	}
+
 } 
\ No newline at end of file
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
index f88691a..1cc2448 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyleImpl.java
@@ -568,6 +568,14 @@ public class StyleImpl implements Style {
 		}
 		return style;
 	}
+
+	@Override
+	public void reportStats() {
+		relations.printStats("relations");
+		nodes.printStats("points");
+		lines.printStats("lines");
+		polygons.printStats("polygons");
+	}
 	
 	public static void main(String[] args) throws FileNotFoundException {
 		String file = args[0];
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index f51fa56..bbc6093 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -31,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
 
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
@@ -38,10 +39,10 @@ import uk.me.parabola.imgfmt.app.Exit;
 import uk.me.parabola.imgfmt.app.Label;
 import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
 import uk.me.parabola.imgfmt.app.net.GeneralRouteRestriction;
-import uk.me.parabola.imgfmt.app.net.NODHeader;
 import uk.me.parabola.imgfmt.app.trergn.ExtTypeAttributes;
 import uk.me.parabola.imgfmt.app.trergn.MapObject;
 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;
@@ -124,9 +125,15 @@ public class StyledConverter implements OsmConverter {
 	private final Rule nodeRules;
 	private final Rule lineRules;
 	private final Rule polygonRules;
-
-	private boolean driveOnLeft;
-	private boolean driveOnRight;
+	private Style style;
+
+	private String driveOn;
+	private Boolean driveOnLeft;
+	private int numDriveOnLeftRoads;
+	private int numDriveOnRightRoads;
+	private int numDriveOnSideUnknown;
+	private int numRoads;
+	
 	private final boolean checkRoundabouts;
 	private int reportDeadEnds; 
 	private final boolean linkPOIsToWays;
@@ -153,22 +160,44 @@ public class StyledConverter implements OsmConverter {
 				nameTagList.add(TagDict.getInstance().xlate(n));
 		} else 
 			nameTagList = null;
-
+		this.style = style;
 		wayRules = style.getWayRules();
 		nodeRules = style.getNodeRules();
 		lineRules = style.getLineRules();
 		polygonRules = style.getPolygonRules();
 		
 		housenumberGenerator = new HousenumberGenerator(props);
-
-		driveOnLeft = props.getProperty("drive-on-left") != null;
-		// check if the setDriveOnLeft flag should be ignored 
-		// (this is the case if precompiled sea is loaded)
-		if (props.getProperty("ignore-drive-on-left") == null)
-			// do not ignore the flag => initialize it
-			NODHeader.setDriveOnLeft(driveOnLeft);
-		driveOnRight = props.getProperty("drive-on-right") != null;
-		checkRoundabouts = props.getProperty("check-roundabouts") != null;
+		
+		driveOn = props.getProperty("drive-on", null);
+		if (driveOn == null){
+			// support legacy options --drive-on-left and --drive-on-right
+			boolean dol = props.getProperty("drive-on-left", false);
+			boolean dor = props.getProperty("drive-on-right", false);
+			if (dol && dor)
+				throw new ExitException("options drive-on-left and drive-on-right and mutually exclusive");
+			if (dol)
+				driveOn = "left";
+			if (dor)
+				driveOn = "right";
+		}
+		if (driveOn == null)
+			driveOn = "detect,right";
+		switch (driveOn) {
+		case "left":
+			driveOnLeft = true; 
+			break;
+		case "right":
+			driveOnLeft = false; 
+			break;
+		case "detect":
+		case "detect,left":
+		case "detect,right":
+			break;
+		default:
+			throw new ExitException("invalid parameters for option drive-on:"+driveOn);
+		}
+		
+		checkRoundabouts = props.getProperty("check-roundabouts",false);
 		reportDeadEnds = props.getProperty("report-dead-ends", 1);  
 		
 		LineAdder overlayAdder = style.getOverlays(lineAdder);
@@ -332,8 +361,19 @@ public class StyledConverter implements OsmConverter {
 		cw.setReversed(wasReversed);
 		if (cw.isRoad()){
 			roads.add(cw);
-			if (wasReversed && cw.isRoundabout())
-				log.warn("Roundabout", way.getId(), "has reverse oneway tag (" + way.getPoints().get(0).toOSMURL() + ")");
+			numRoads++;
+			String country = way.getTag(countryTagKey);
+			if (country != null) {
+				if (LocatorConfig.get().getDriveOnLeftFlag(country))
+					numDriveOnLeftRoads++;
+				else
+					numDriveOnRightRoads++;
+			} else
+				numDriveOnSideUnknown++;
+			if (cw.isRoundabout()) {
+				if (wasReversed)
+					log.warn("Roundabout", way.getId(), "has reverse oneway tag (" + way.getPoints().get(0).toOSMURL() + ")");
+			}
 			lastRoadId = way.getId();
 		}
 		else 
@@ -504,6 +544,9 @@ public class StyledConverter implements OsmConverter {
 	}
 	
 	public void end() {
+		style.reportStats();
+		driveOnLeft = calcDrivingSide();
+		
 		setHighwayCounts();
 		findUnconnectedRoads();
 		rotateClosedWaysToFirstNode();
@@ -533,7 +576,8 @@ public class StyledConverter implements OsmConverter {
 		}
 		for (Long wayId: deletedRoads){
 			if (wayRelMap.containsKey(wayId)){
-				log.error("internal error: was that is used in valid restriction relation was removed, id:",wayId);
+				// may happen e.g. when very short way is leading to nowhere 
+				log.warn("Way that is used in valid restriction relation was removed, id:",wayId);
 			}
 		}
 		deletedRoads = null;
@@ -558,7 +602,6 @@ public class StyledConverter implements OsmConverter {
 			if (cw.isValid())
 				addRoad(cw);
 		}
-		
 		housenumberGenerator.generate(lineAdder);
 		
 		createRouteRestrictionsFromPOI();
@@ -569,6 +612,12 @@ public class StyledConverter implements OsmConverter {
 		}
 		roads = null;
 
+		// at this point the check-roundabout option might have changed the driveOn value 
+//		if ("left".equals(driveOn) && !ignoreDriveOn){
+//			NODHeader.setDriveOnLeft(true);
+//			TREHeader.setDriveOnLeft(true);
+//		}
+
 		for(Relation relation : throughRouteRelations) {
 			Node node = null;
 			Way w1 = null;
@@ -615,6 +664,49 @@ public class StyledConverter implements OsmConverter {
 		nodeIdMap = null;
 		throughRouteRelations.clear();
 		restrictions.clear();
+		
+	}
+
+	/**
+	 * Check the counters and verify the driveOn value to calculate
+	 * the drive on left flag. 
+	 */
+	private Boolean calcDrivingSide() {
+		Boolean dol = null;
+		log.info("Found", numRoads, "roads",
+				numDriveOnLeftRoads, "in drive-on-left country,",
+				numDriveOnRightRoads, "in drive-on-right country, and",
+				numDriveOnSideUnknown, " with unknwon country");
+		if (numDriveOnLeftRoads> 0 &&  numDriveOnRightRoads > 0)
+			log.error("Attention: Tile contains both drive-on-left (" + numDriveOnLeftRoads + 
+					") and drive-on-right roads (" + numDriveOnRightRoads + ")");
+		if (driveOn.startsWith("detect")) {
+			if (numDriveOnSideUnknown > numRoads * 0.05){
+				// warn if more than 5% of the roads are in unknown area
+				log.warn("Found", numDriveOnSideUnknown, "roads with unknown country and driving side");
+			}
+			if (numDriveOnLeftRoads > numDriveOnRightRoads + numDriveOnSideUnknown) {
+				dol = true;
+			} else if (numDriveOnRightRoads > numDriveOnLeftRoads + numDriveOnSideUnknown) {
+				dol = false;
+			} else {
+				if (driveOn.endsWith("left"))
+					dol = true;
+				else 
+					dol = false;
+			}
+			log.info("detected value for driving on left flag is:",dol);			
+		} else {
+			driveOnLeft = ("left".equals(driveOn));
+			// warn if user given flag is obviously wrong
+			if ("left".equals(driveOn) && numDriveOnLeftRoads == 0 && numDriveOnRightRoads > 0)
+				log.warn("The drive-on-left flag is set but tile contains only drive-on-right roads");
+			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
+		return dol;
 	}
 
 	/**
@@ -704,32 +796,14 @@ public class StyledConverter implements OsmConverter {
 				boolean clockwise = dir > 0;
 				if (points.get(0) == points.get(points.size() - 1)) {
 					// roundabout is a loop
-					if (!driveOnLeft && !driveOnRight) {
-						if (clockwise) {
-							log.info("Roundabout "
-									+ way.getId()
-									+ " is clockwise so assuming vehicles should drive on left side of road ("
-									+ centre.toOSMURL() + ")");
-							driveOnLeft = true;
-							NODHeader.setDriveOnLeft(true);
-						} else {
-							log.info("Roundabout "
-									+ way.getId()
-									+ " is anti-clockwise so assuming vehicles should drive on right side of road ("
-									+ centre.toOSMURL() + ")");
-							driveOnRight = true;
-						}
-					}
-					if (driveOnLeft && !clockwise || driveOnRight
-							&& clockwise) {
+					if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
 						log.warn("Roundabout "
 								+ way.getId()
 								+ " direction is wrong - reversing it (see "
 								+ centre.toOSMURL() + ")");
 						way.reverse();
 					}
-				} else if (driveOnLeft && !clockwise || driveOnRight
-						&& clockwise) {
+				} else if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
 					// roundabout is a line
 					log.warn("Roundabout segment " + way.getId()
 							+ " direction looks wrong (see "
@@ -926,7 +1000,7 @@ public class StyledConverter implements OsmConverter {
 			}
 			else {
 				mp = new MapPoint();
-				log.warn("Motorway exit", node.getName(), "(" + node.getLocation().toOSMURL() + ") has no motorway! (either make the exit share a node with the motorway or specify the motorway ref with a", Exit.TAG_ROAD_REF, "tag)");
+				log.warn("Motorway exit", node.getName(), "(" + node.toBrowseURL() + ") has no motorway! (either make the exit share a node with the motorway or specify the motorway ref with a", Exit.TAG_ROAD_REF, "tag)");
 			}
 		}
 		else {
@@ -1685,8 +1759,8 @@ public class StyledConverter implements OsmConverter {
 				}
 			}
 		}
-		
 	}
+
 	
 	/**
 	 * Increment the highway counter for each coord of each road.
@@ -1932,5 +2006,9 @@ public class StyledConverter implements OsmConverter {
 		}
 	}
 
+	public Boolean getDriveOnLeft(){
+		assert roads == null : "getDriveOnLeft() should be called after end()";
+		return driveOnLeft;
+	}
 }
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
index 544b151..03afc2f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
@@ -124,7 +124,7 @@ public class TypeReader {
 				}
 				if (kind == FeatureKind.POLYLINE && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){ 
 					if (GType.isSpecialRoutableLineType(usedType)){
-						if (gt.isRoad() == false){
+						if (gt.hasRoadAttribute() == false){
 							String msg = "Warning: routable type " + type  + " is used for non-routable line with level 0. This may break routing. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber();
 							if (fromOverlays)
 								msg += typeOverlaidMsg;
@@ -142,7 +142,7 @@ public class TypeReader {
 						foundRoutableType = true;
 				}
 			}
-			if (gt.isRoad() && foundRoutableType == false && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){
+			if (gt.hasRoadAttribute() && foundRoutableType == false && gt.getMinLevel() == 0 && gt.getMaxLevel() >= 0){
 				String msg = "Warning: non-routable type " + type  + " is used in combination with road_class/road_speed. Line will not be routable. Style file "+ ts.getFileName() + ", line " + ts.getLinenumber();
 				if (fromOverlays)
 					msg += ". Type is overlaid, but not with a routable type";
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
index a4ce874..7e22452 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/WrongAngleFixer.java
@@ -1396,7 +1396,7 @@ public class WrongAngleFixer {
 			}
 			modifiedPoints.add(cm);
 		}
-		if (modifiedPoints.get(0) != modifiedPoints.get(modifiedPoints.size()-1))
+		if (modifiedPoints.size() > 1 && modifiedPoints.get(0) != modifiedPoints.get(modifiedPoints.size()-1))
 			modifiedPoints.add(modifiedPoints.get(0));
 		return modifiedPoints;
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
index 0a9b357..d7adc34 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/AddLabelAction.java
@@ -65,7 +65,7 @@ public class AddLabelAction extends ValueBuildedAction {
 			sb.append(vb);
 			sb.append(" | ");
 		}
-		sb.setLength(sb.length() - 1);
+		sb.setLength(sb.length() - 3); 
 		return sb.toString();
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilter.java
index 64ce549..b972859 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilter.java
@@ -16,33 +16,55 @@
  */
 package uk.me.parabola.mkgmap.osmstyle.actions;
 
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import uk.me.parabola.mkgmap.osmstyle.eval.UnitConversions;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
  * Convert a numeric quantity from one set of units to another.
  *
- * TODO: this will change a lot it is just here for backward compatibility
- * at present.
  * @author Steve Ratcliffe
  */
 public class ConvertFilter extends ValueFilter {
-	private final double factor;
+	private static final Pattern UNIT_RE = Pattern.compile("\\s*([\\d.]+)\\s*([\\w/]*)\\s*");
+
+	private final UnitConversions units;
 
 	public ConvertFilter(String arg) {
-		factor = UnitConversions.convertFactor(arg);
+		units = UnitConversions.createConversion(arg);
 	}
 
 	protected String doFilter(String value, Element el) {
-		if (value == null) return null;
-		
-		try {
-			double d = Double.parseDouble(value);
+		if (value == null || !units.isValid())
+			return value;
 
-			double res = d * factor;
-			res = Math.round(res);
-			return String.valueOf((int) res);
+		String number = value;
+		Double factor = units.getDefaultFactor();
+
+		// If this is not a pure number, then extract the number part and the unit part
+		// and convert based on the found values.  There are also various possible error
+		// cases.
+		if (!Character.isDigit(value.charAt(value.length() - 1))) {
+			// Extract number and unit string
+			Matcher matcher = UNIT_RE.matcher(value);
+			if (matcher.matches()) {
+				number = matcher.group(1);
+				String source = matcher.group(2);
+				factor = units.convertFactor(source);
+				if (factor == null)
+					return value;
+			} else {
+				return value;
+			}
+		}
+
+		try {
+			double d = Double.parseDouble(number);
+			return String.valueOf(Math.round(d * factor));
 		} catch (NumberFormatException e) {
+			// Turns out it wasn't a pure number, just return the value unchanged.
 			return value;
 		}
 	}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilter.java
new file mode 100644
index 0000000..7ba94f1
--- /dev/null
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilter.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+package uk.me.parabola.mkgmap.osmstyle.actions;
+
+import uk.me.parabola.mkgmap.build.LocatorConfig;
+import uk.me.parabola.mkgmap.reader.osm.Element;
+
+/**
+ * Convert a string containing a country name or ISO string to the 3 character ISO string.
+ * Samples: Deutschland->DEU, UK->GBR
+ *
+ * @author GerdP
+ */
+public class CountryISOFilter extends ValueFilter {
+
+	public CountryISOFilter() {
+	}
+
+	protected String doFilter(String value, Element el) {
+		if (value == null)
+			return value;
+		String s = LocatorConfig.get().getCountryISOCode(value);
+		if (s != null)
+			return s;
+		else 
+			return value;
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
index 33b2180..597d3a6 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/HeightFilter.java
@@ -16,7 +16,7 @@ package uk.me.parabola.mkgmap.osmstyle.actions;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 
 /**
- * A <code>HeightFilter</code> transforms values into Garmin-tagged elevations.
+ * A {@code HeightFilter} transforms values into Garmin-tagged elevations.
  *
  * @author Toby Speight
  *
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
index f72c879..1707cb8 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/NameAction.java
@@ -59,7 +59,7 @@ public class NameAction extends ValueBuildedAction {
 			sb.append(vb);
 			sb.append(" | ");
 		}
-		sb.setLength(sb.length() - 1);
+		sb.setLength(sb.length() - 3);
 		return sb.toString();
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstringFilter.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstringFilter.java
index 3710a81..fd88bbc 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstringFilter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/SubstringFilter.java
@@ -50,7 +50,7 @@ public class SubstringFilter extends ValueFilter {
 				args = 0;
 			}
 		} catch (NumberFormatException e) {
-			throw new ExitException("Not valid numbers in style substring command: " + arg);
+			throw new ExitException(String.format("Numbers not valid in style substring command: '%s'", arg));
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
index c0aad6d..b942153 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilder.java
@@ -24,6 +24,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.scan.SyntaxException;
 
 /**
  * Build a value that can have tag values substituted in it.
@@ -37,7 +38,7 @@ public class ValueBuilder {
 			Pattern.compile("[ \t]*([^: \\t|]+:'[^']+')[ \t]*"),
 
 			// This must be last
-			Pattern.compile("([ \t]*[^: \\t|]+:[^|]*)"),
+			Pattern.compile("[ \t]*([^: \\t|]+:[^|]*)"),
 	};
 
 	private final List<ValueItem> items = new ArrayList<>();
@@ -224,6 +225,11 @@ public class ValueBuilder {
 		case "part":
 			item.addFilter(new PartFilter(arg));
 			break;
+		case "country-ISO":
+			item.addFilter(new CountryISOFilter());
+			break;
+		default:
+			throw new SyntaxException(String.format("Unknown filter '%s'", cmd));
 		}
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/eval/UnitConversions.java b/src/uk/me/parabola/mkgmap/osmstyle/eval/UnitConversions.java
index 51737d4..a1836af 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/eval/UnitConversions.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/eval/UnitConversions.java
@@ -18,38 +18,166 @@ package uk.me.parabola.mkgmap.osmstyle.eval;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import uk.me.parabola.mkgmap.scan.SyntaxException;
 
 /**
  * Converting quantities from one unit to another.
  *
- * TODO: this will probably change a lot.
- *
  * @author Steve Ratcliffe
  */
 public class UnitConversions {
-	private static final Map<String, Double> conversions = new HashMap<String, Double>();
+	private static final Pattern CODE_RE = Pattern.compile("(.*)=>(.*)");
+
+	private static final Map<UnitType, Map<String, Double>> CONVERSIONS = new HashMap<>();
+
+	private static final Map<String, Double> LENGTH_FACTORS = new HashMap<>();
+	private static final Map<String, Double> SPEED_FACTORS = new HashMap<>();
 
-	// Initially we are just supporting the existing case for contour
-	// lines where we convert to feet.
 	static {
-		Map<String, Double> m = conversions;
-		m.put("m=>ft", 3.2808399);
+		Map<String, Double> m = LENGTH_FACTORS;
+		m.put("m", 1.0);
+		m.put("km", 1000.0);
+		m.put("ft", 0.3048);
+		m.put("mi", 1_609.344);
+		CONVERSIONS.put(UnitType.LENGTH, LENGTH_FACTORS);
+
+		m = SPEED_FACTORS;
+		m.put("kmh", 1.0);
+		m.put("km/h", 1.0);
+		m.put("kmph", 1.0);
+		m.put("mph", 1.60934);
+		m.put("knots", 1.852);
+		CONVERSIONS.put(UnitType.SPEED, SPEED_FACTORS);
+	}
+
+	/** The type of unit, speed, length etc. */
+	private final UnitType unitType;
+	/** The target unit */
+	private final String target;
+	/** The factor to convert from the default source unit to the target */
+	private final double defaultFactor;
+
+	/**
+	 * Create a converter between the units given in the {@code code} argument.
+	 *
+	 * This will be something like m=>ft to convert from meters to feet. If the input is a plain
+	 * value such as 10, then the input is in meters and it is converted to feet.  If the
+	 * input already has a unit, eg 10ft, then the conversion will be from that unit to
+	 * the target unit. So in this case, since the input is already in feet, then result is 10.
+	 *
+	 * @param code A specifier from=>to.
+	 */
+	public static UnitConversions createConversion(String code) {
+		Matcher matcher = CODE_RE.matcher(code);
+		if (!matcher.matches())
+			throw new SyntaxException(String.format("Unrecognised unit conversion: '%s'", code));
+
+		String source = matcher.group(1);
+		String target = matcher.group(2);
+
+		return new UnitConversions(source, target);
+	}
+
+	private UnitConversions(String source, String target) {
+		this.target = target;
+
+		UnitType type = getType(source);
+		if (type != null && type == getType(target)) {
+			unitType = type;
+			defaultFactor = getConversion(source);
+		} else {
+			unitType = null;
+			defaultFactor = 1;
+		}
+	}
+
+	/**
+	 * The conversion factor; multiply by this value to convert to the target unit.
+	 * @param source If this is not null, then use this as the source unit.
+	 * @return The factor to multiply the value by to convert from the source to target units.
+	 */
+	public Double convertFactor(String source) {
+		// The value has no unit, so use the default one. We already know the conversion factor for the
+		// default source unit, so just return it.
+		if (source == null)
+			return defaultFactor;
+
+		if (unitType == null)
+			return null;
+
+		return getConversion(source);
+	}
+
+	public double convertFrom(String source) {
+		assert source != null && unitType != null;
+
+		if (CONVERSIONS.get(unitType).containsKey(source))
+			return getConversion(source);
+		else
+			return 0;
+	}
+
+	public boolean isValid() {
+		return unitType != null;
 	}
-	//
-	//private double factor;
-	//
-	//public double convert(double in) {
-	//	return in * factor;
-	//}
 
 	/**
-	 * Get the conversion factor for the given conversion.
-	 * @param code A string such as 'm=>ft' which would mean meters
-	 * to feet.
-	 * @return The factor required to convert the first to the second.
+	 * Find the unit type that corresponds to the unit abbreviation.
+	 *
+	 * For example for km, this would be LENGTH.
+	 * @param source A unit specifier.
+	 * @return The type of unit.
 	 */
-	public static double convertFactor(String code) {
-		Double f = conversions.get(code);
-		return (f == null)?1 :f;
+	private static UnitType getType(String source) {
+		for (UnitType t : UnitType.values()) {
+			Map<String, Double> map = CONVERSIONS.get(t);
+			for (String unit : map.keySet()) {
+				if (unit.equals(source))
+					return t;
+			}
+		}
+		return null;
+	}
+
+	private double getFactor(String unit) {
+		assert isValid();
+
+		Double d = CONVERSIONS.get(unitType).get(unit);
+		return d == null? 0: d;
+	}
+
+	private Double getConversion(String source) {
+		Double in = getInFactor(unitType, source);
+		if (in == null)
+			return null;
+		double out = getOutFactor(unitType, target);
+
+		return in * out;
+	}
+
+	private static Double getInFactor(UnitType type, String source) {
+		if (source.isEmpty())
+			return 1.0;
+
+		Map<String, Double> map = CONVERSIONS.get(type);
+		assert map != null;
+
+		return map.get(source);
+	}
+
+	private static double getOutFactor(UnitType type, String target) {
+		return 1.0 / getInFactor(type, target);
+	}
+
+	public double getDefaultFactor() {
+		return defaultFactor;
+	}
+
+	public static enum UnitType {
+		LENGTH,
+		SPEED,
 	}
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
index 5ebb99f..13c7aa2 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java
@@ -17,9 +17,9 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
-
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.CoordNode;
 import uk.me.parabola.imgfmt.app.net.NumberStyle;
@@ -28,6 +28,7 @@ import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.general.LineAdder;
 import uk.me.parabola.mkgmap.general.MapRoad;
 import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.Node;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
 import uk.me.parabola.mkgmap.reader.osm.Way;
@@ -86,6 +87,9 @@ public class HousenumberGenerator {
 			String streetname = getStreetname(n);
 			if (streetname != null) {
 				houseNumbers.add(streetname, n);
+			} else {
+				if (log.isDebugEnabled())
+					log.debug(n.toBrowseURL()," ignored, doesn't contain a street name.");
 			}
 		}
 	}
@@ -102,6 +106,13 @@ public class HousenumberGenerator {
 			String streetname = getStreetname(w);
 			if (streetname != null) {
 				houseNumbers.add(streetname, w);
+			} else {
+				if (log.isDebugEnabled()){
+					if (FakeIdGenerator.isFakeId(w.getId()))
+						log.debug("mp-created way ignored, doesn't contain a street name. Tags:",w.toTagString());
+					else 
+						log.debug(w.toBrowseURL()," ignored, doesn't contain a street name.");
+				}
 			}
 		}
 	}
@@ -115,7 +126,6 @@ public class HousenumberGenerator {
 	public void addRoad(Way osmRoad, MapRoad road) {
 		roads.add(road);
 		if (numbersEnabled) {
-			// first try to get the streetname from mkgmap:streetname
 			String name = getStreetname(osmRoad); 
 			if (name != null) {
 				if (log.isDebugEnabled())
@@ -125,10 +135,125 @@ public class HousenumberGenerator {
 		} 
 	}
 	
+	/**
+	 * Evaluate type=associatedStreet relations.
+	 */
 	public void addRelation(Relation r) {
-		// TODO 
+		if (numbersEnabled == false) 
+			return;
+		String relType = r.getTag("type");
+		// the wiki says that we should also evaluate type=street
+		if ("associatedStreet".equals(relType) || "street".equals(relType)){
+			List<Element> houses= new ArrayList<>();
+			List<Element> streets = new ArrayList<>();
+			for (Map.Entry<String, Element> member : r.getElements()) {
+				if (member.getValue() instanceof Node) {
+					Node node = (Node) member.getValue();
+					houses.add(node);
+				} else if (member.getValue() instanceof Way) {
+					Way w = (Way) member.getValue();
+					String role = member.getKey();
+					switch (role) {
+					case "house":
+					case "addr:houselink":
+					case "address":
+						houses.add(w);
+						break;
+					case "street":
+						streets.add(w);
+						break;
+					case "":
+						if (w.getTag("highway") != null){
+							streets.add(w);
+							continue;
+						}
+						String buildingTag = w.getTag("building");
+						if (buildingTag != null)
+							houses.add(w);
+						else 
+							log.warn("Relation",r.toBrowseURL(),": role of member",w.toBrowseURL(),"unclear");
+						break;
+					default:
+						break;
+					}
+				}
+			}
+			if (houses.isEmpty()){
+				if ("associatedStreet".equals(relType))
+					log.warn("Relation",r.toBrowseURL(),": ignored, found no houses");
+				return;
+			}
+			String streetName = r.getTag("name");
+			String streetNameFromRoads = null;
+			boolean nameFromStreetsIsUnclear = false;
+			if (streets.isEmpty() == false) {
+				for (Element street : streets) {
+					String roadName = street.getTag("name");
+					if (roadName == null)
+						continue;
+					if (streetNameFromRoads == null)
+						streetNameFromRoads = roadName;
+					else if (streetNameFromRoads.equals(roadName) == false)
+						nameFromStreetsIsUnclear = true;
+				}
+			}
+			if (streetName == null){
+				if (nameFromStreetsIsUnclear == false)
+					streetName = streetNameFromRoads;
+				else {
+					log.warn("Relation",r.toBrowseURL(),": ignored, street name is not clear.");
+					return;
+				}
+
+			} else {
+				if (streetNameFromRoads != null){
+					if (nameFromStreetsIsUnclear == false && streetName.equals(streetNameFromRoads) == false){
+						log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the way, not that of the relation.");
+						streetName = streetNameFromRoads;
+					} 
+					else if (nameFromStreetsIsUnclear == true){
+						log.warn("Relation",r.toBrowseURL(),": street name is not clear, using the name from the relation.");
+					}
+				} 
+			}
+			int countOK = 0;
+			if (streetName != null && streetName.isEmpty() == false){
+				for (Element house : houses) {
+					if (addStreetTagFromRel(r, house, streetName) )
+						countOK++;
+				}
+			}
+			if (countOK > 0)
+				log.info("Relation",r.toBrowseURL(),": added tag mkgmap:street=",streetName,"to",countOK,"of",houses.size(),"house members");
+			else 
+				log.info("Relation",r.toBrowseURL(),": ignored, the house members all have a addr:street or mkgmap:street tag");
+		}
 	}
 	
+	/**
+	 * Add the tag mkgmap:street=streetName to the element of the 
+	 * relation if it does not already have a street name tag.
+	 */
+	private boolean addStreetTagFromRel(Relation r, Element house, String streetName){
+		String addrStreet = getStreetname(house);
+		if (addrStreet == null){
+			house.addTag("mkgmap:street", streetName);
+			if (log.isDebugEnabled())
+				log.debug("Relation",r.toBrowseURL(),": adding tag mkgmap:street=" + streetName, "to house",house.toBrowseURL());
+			return true;
+		}
+		else if (addrStreet.equals(streetName) == false){
+			if (house.getTag("mkgmap:street") != null){
+				log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing mkgmap:street tag for house",house.toBrowseURL(),"the house seems to be member of another type=associatedStreet relation");
+				house.deleteTag("mkgmap:street");
+			}
+			else 
+				log.warn("Relation",r.toBrowseURL(),": street name from relation doesn't match existing name for house",house.toBrowseURL());
+		}
+		return false;
+	}
+	
+	
 	public void generate(LineAdder adder) {
 		if (numbersEnabled) {
 			for (Entry<String, List<Element>> numbers : houseNumbers.entrySet()) {
@@ -195,9 +320,20 @@ public class HousenumberGenerator {
 	private static void match(String streetname, List<Element> elements, List<MapRoad> roads) {
 		List<HousenumberMatch> numbersList = new ArrayList<HousenumberMatch>(
 				elements.size());
-		for (Element node : elements) {
+		for (Element element : elements) {
 			try {
-				numbersList.add(new HousenumberMatch(node));
+				HousenumberMatch match = new HousenumberMatch(element);
+				if (match.getLocation() == null) {
+					// there has been a report that indicates match.getLocation() == null
+					// could not reproduce so far but catching it here with some additional
+					// information. (WanMil)
+					log.error("OSM element seems to have no point.");
+					log.error("Element: "+element.toBrowseURL()+" " +element);
+					log.error("Please report on the mkgmap mailing list.");
+					log.error("Continue creating the map. This should be possible without a problem.");
+				} else {
+					numbersList.add(match);
+				}
 			} catch (IllegalArgumentException exp) {
 				log.debug(exp);
 			}
diff --git a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
index 40d9946..2c4b061 100644
--- a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
@@ -42,6 +42,7 @@ import uk.me.parabola.util.EnhancedProperties;
 public abstract class MapperBasedMapDataSource implements MapDataSource, Configurable {
 	protected final MapDetails mapper = new MapDetails();
 	private EnhancedProperties configProps;
+	private boolean driveOnLeft;
 
 	/**
 	 * Get the area that this map covers. Delegates to the map collector.
@@ -138,4 +139,16 @@ public abstract class MapperBasedMapDataSource implements MapDataSource, Configu
 		boundary.setPoints(coords);
 		mapper.addLine(boundary);
 	}
+	
+	/**
+	 * @return true/false if source contains info about driving side, else null
+	 */
+	public Boolean getDriveOnLeft(){
+		return driveOnLeft;
+	}
+	
+	protected void setDriveOnLeft(boolean b) {
+		driveOnLeft = b;
+	}
+
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
index 6892f4f..6b393f5 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/CoastlineFileLoader.java
@@ -73,8 +73,6 @@ public final class CoastlineFileLoader {
 	private CoastlineFileLoader() {
 		this.coastlineFiles = new HashSet<String>();
 		this.coastConfig = new EnhancedProperties();
-		// disable drive-on-left handling
-		this.coastConfig.setProperty("ignore-drive-on-left", "true");
 	}
 
 	private static final CoastlineFileLoader loader = new CoastlineFileLoader();
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
index e6a77d0..d4c610a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java
@@ -251,7 +251,7 @@ public abstract class Element {
 	}
 
 	public String toBrowseURL() {
-		return "http://www.openstreetmap.org/browse/" + kind() + "/" + id;
+		return "http://www.openstreetmap.org/" + kind() + "/" + id;
 	}
 
 	public Element copy() {
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/GType.java b/src/uk/me/parabola/mkgmap/reader/osm/GType.java
index 5e10ae4..d851a88 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/GType.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/GType.java
@@ -45,7 +45,8 @@ public class GType {
 	private int roadClass;
 	private int roadSpeed;
 
-	private boolean road;
+	private boolean hasRoadAttribute;
+	private boolean levelsWereFixed = false;
 
 	/** If this is set, then we look for further types after this one is matched */
 	private boolean continueSearch;
@@ -133,6 +134,7 @@ public class GType {
 			if (info.getBits() <= maxResolution)
 				minLevel = info.getLevel();
 		}
+		levelsWereFixed = true;
 	}
 
 	public String toString() {
@@ -151,7 +153,7 @@ public class GType {
 			else
 				fmt.format(" level %d-%d", minLevel, maxLevel);
 		}
-		if (road)
+		if (hasRoadAttribute)
 			fmt.format(" road_class=%d road_speed=%d", roadClass, roadSpeed);
 		
 		if (continueSearch)
@@ -179,7 +181,7 @@ public class GType {
 	public void setRoadClass(int roadClass) {
 		// road class might also be set for nodes used by the link-pois-to-ways option
 		if (getFeatureKind() == FeatureKind.POLYLINE)
-			road = true;
+			hasRoadAttribute = true;
 		this.roadClass = roadClass;
 	}
 
@@ -190,12 +192,21 @@ public class GType {
 	public void setRoadSpeed(int roadSpeed) {
 		// road speed might also be set for nodes used by the link-pois-to-ways option
 		if (getFeatureKind() == FeatureKind.POLYLINE)
-			road = true;
+			hasRoadAttribute = true;
 		this.roadSpeed = roadSpeed;
 	}
 
+	public boolean hasRoadAttribute() {
+		return hasRoadAttribute;
+	}
+
+	/**
+	 * @return true if the object has valid attributes to be used as a routable way 
+	 */
 	public boolean isRoad() {
-		return road;
+		if (!levelsWereFixed)
+			log.error("internal: isRoad() called before fixLevels()");
+		return hasRoadAttribute && minLevel == 0;
 	}
 
 	public boolean isContinueSearch() {
@@ -217,7 +228,7 @@ public class GType {
 	/**
 	 * 
 	 * @param type the type value
-	 * @return true if the type is can be used for routable lines
+	 * @return true if the type can be used for routable lines
 	 */
 	public static boolean isRoutableLineType(int type){
 		return type >= 0x01 && type <= 0x3f;
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
index 44ced84..03365f6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
@@ -725,6 +725,17 @@ public class MultiPolygonRelation extends Relation {
 			} 
 			polygonStatusList.add(new PolygonStatus("outer".equals(role), polyIndex, polygon));
 		}
+		// sort by role and then by number of points, this improves performance
+		// in the routines which add the polygons to areas
+		if (polygonStatusList.size() > 2){
+			Collections.sort(polygonStatusList, new Comparator<PolygonStatus>() {
+				public int compare(PolygonStatus o1, PolygonStatus o2) {
+					if (o1.outer != o2.outer)
+						return (o1.outer) ? -1 : 1;
+					return o1.polygon.getPoints().size() - o2.polygon.getPoints().size();
+				}
+			});
+		}
 		return polygonStatusList;
 	}
 
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmConverter.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmConverter.java
index 3354bbf..3d32ba6 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmConverter.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmConverter.java
@@ -72,4 +72,9 @@ public interface OsmConverter {
 	 * Called when all conversion has been done.
 	 */
 	public void end();
+	
+	/**
+	 * @return true/false if source contains info about driving side, else null
+	 */
+	public Boolean getDriveOnLeft();
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
index c09a21f..ad83575 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/OsmMapDataSource.java
@@ -150,13 +150,13 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	 */
 	protected void setupHandler(OsmHandler handler) {
 		createElementSaver();
+		createConverter();
+		
 		osmReadingHooks = pluginChain(elementSaver, getConfig());
 
 		handler.setElementSaver(elementSaver);
 		handler.setHooks(osmReadingHooks);
 
-		createConverter();
-
 		handler.setUsedTags(getUsedTags());
 
 		String deleteTagsFileName = getConfig().getProperty("delete-tags-file");
@@ -180,10 +180,12 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	
 	protected OsmReadingHooks pluginChain(ElementSaver saver, EnhancedProperties props) {
 		List<OsmReadingHooks> plugins = new ArrayList<OsmReadingHooks>();
-
 		for (OsmReadingHooks p : getPossibleHooks()) {
-			if (p.init(saver, props))
+			if (p.init(saver, props)){
 				plugins.add(p);
+				if (p instanceof RelationStyleHook)
+					((RelationStyleHook) p).setStyle(style);
+			}
 		}
 
 		OsmReadingHooks hooks;
@@ -266,4 +268,9 @@ public abstract class OsmMapDataSource extends MapperBasedMapDataSource
 	public Set<String> getUsedTags() {
 		return usedTags;
 	}
+	
+	@Override
+	public Boolean getDriveOnLeft(){
+		return converter.getDriveOnLeft();
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
index 76138ae..7932dac 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/RelationStyleHook.java
@@ -35,10 +35,13 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 	public boolean init(ElementSaver saver, EnhancedProperties props) {
 		this.saver = saver;
 		nameTagList = LocatorUtil.getNameTags(props);
-		style = StyleImpl.readStyle(props);
 		return super.init(saver, props);
 	}
 
+	public void setStyle(Style style){
+		this.style = style;
+	}
+	
 	public void end() {
 		Rule relationRules = style.getRelationRules();
 		for (Relation rel : saver.getRelations().values()) {
@@ -57,8 +60,6 @@ public class RelationStyleHook extends OsmReadingHooksAdaptor {
 			}
 		}
 		super.end();
-		
-		style = null;
 	}
 
 	
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
index 1488946..0a0dd62 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Rule.java
@@ -53,5 +53,6 @@ public interface Rule {
 	 */
 	public void setFinalizeRule(Rule finalizeRule);
 	
+	public void printStats(String header);
 	
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
index a8bd725..c27e221 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
@@ -511,9 +511,6 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	private Collection<Way> loadPrecompTile(InputStream is, String filename) {
 		OsmMapDataSource src = createTileReader(filename);
 		EnhancedProperties props = new EnhancedProperties();
-		// set a flag that the StyledConverter which is created by the 
-		// OsmMapDataSource does not set the drive-on-left flag
-		props.setProperty("ignore-drive-on-left", "true");
 		src.config(props);
 		log.info("Started loading coastlines from", filename);
 		try{
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Style.java b/src/uk/me/parabola/mkgmap/reader/osm/Style.java
index a974e80..f5956d2 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Style.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Style.java
@@ -74,4 +74,9 @@ public interface Style {
 	 * Get the tags that are used by this style.
 	 */
 	public Set<String> getUsedTags();
+	
+	/**
+	 * Report statistics for rule expressions. 
+	 */
+	public void reportStats();
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
index 3a358a6..989b7a1 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/TagDict.java
@@ -23,9 +23,9 @@ import uk.me.parabola.imgfmt.MapFailedException;
  *
  */
 public class TagDict{
-	private static TagDict instance = null;
-	private HashMap<String,Short>  map;
-	private ArrayList<String>  list;
+	private final static TagDict INSTANCE = new TagDict();
+	private final HashMap<String,Short>  map = new HashMap<>();
+	private final ArrayList<String>  list = new ArrayList<>();
 
 	public static final short INVALID_TAG_VALUE = 0;
 
@@ -33,8 +33,6 @@ public class TagDict{
 	 * create an empty dictionary
 	 */
 	private TagDict() {
-		map = new HashMap<>();
-		list = new ArrayList<>();
 		map.put("invalid tag", INVALID_TAG_VALUE);
 		list.add("invalid tag");
 	}
@@ -43,11 +41,8 @@ public class TagDict{
 	 * give access to the singleton instance  
 	 * @return
 	 */
-	public static synchronized TagDict getInstance() {
-		if (instance == null) {
-			instance = new TagDict();
-		}
-		return instance;
+	public static TagDict getInstance() {
+		return INSTANCE;
 	}		
 	
 	/**
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 0aad1d0..272ff0e 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryConverter.java
@@ -57,4 +57,9 @@ public class BoundaryConverter implements OsmConverter {
 	public void end() {
 	}
 
+	@Override
+	public Boolean getDriveOnLeft(){
+		return null; // unknown
+	}
+	
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
index 0459bf7..7a55fa2 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryDiff.java
@@ -56,16 +56,15 @@ public class BoundaryDiff {
 	 * or a single *.bnd file
 	 * @return
 	 */
-	private List<String> getBoundsFiles(String dirName) {
+	private static List<String> getBoundsFiles(String dirName) {
 		File dir = new File(dirName);
 		System.out.println(dirName);
 		if (dir.isFile() && dir.getName().endsWith(".bnd")) {
-			List<String> boundaryFiles = new ArrayList<String>();
+			List<String> boundaryFiles = new ArrayList<>();
 			boundaryFiles.add(dir.getName());
 			return boundaryFiles;
-		} else {
-			return BoundaryUtil.getBoundaryDirContent(dirName);
 		}
+		return BoundaryUtil.getBoundaryDirContent(dirName);
 	}
 
 	/**
@@ -85,8 +84,8 @@ public class BoundaryDiff {
 		Collections.sort(b1);
 		Collections.sort(b2);
 
-		Queue<String> bounds1 = new LinkedList<String>(b1);
-		Queue<String> bounds2 = new LinkedList<String>(b2);
+		Queue<String> bounds1 = new LinkedList<>(b1);
+		Queue<String> bounds2 = new LinkedList<>(b2);
 		b1 = null;
 		b2 = null;
 
@@ -153,7 +152,7 @@ public class BoundaryDiff {
 	 * @param value the tag value
 	 * @return a new Area (which might be empty) 
 	 */
-	private Area loadArea(String dirName, String fileName, String tag, String value) {
+	private static Area loadArea(String dirName, String fileName, String tag, String value) {
 		String dir = dirName;
 		String bndFileName = fileName;
 		if (dir.endsWith(".bnd")){
@@ -190,7 +189,7 @@ public class BoundaryDiff {
 	 * @param tagKey used to build the gpx file name
 	 * @param tagValue used to build the gpx file name
 	 */
-	private void saveArea(Area a, String subDirName, String tagKey, String tagValue) {
+	private static void saveArea(Area a, String subDirName, String tagKey, String tagValue) {
 
 		String gpxBasename = "gpx/diff/" + subDirName + "/"
 				+ tagKey + "=" + tagValue + "/";
@@ -240,17 +239,17 @@ public class BoundaryDiff {
 			printUsage();
 		}
 
-		List<Entry<String,String>> tags = new ArrayList<Entry<String,String>>();
+		List<Entry<String,String>> tags = new ArrayList<>();
 		
 		if (args.length > 2) {
 			for (int i = 2; i < args.length; i++) {
 				final String[] parts = args[i].split(Pattern.quote("="));
-				tags.add(new AbstractMap.SimpleImmutableEntry<String, String>(
+				tags.add(new AbstractMap.SimpleImmutableEntry<>(
 						parts[0], parts[1]));
 			}
 		} else {
 			for (int adminlevel = 2; adminlevel <= 11; adminlevel++) {
-			tags.add(new AbstractMap.SimpleImmutableEntry<String, String>(
+			tags.add(new AbstractMap.SimpleImmutableEntry<>(
 					"admin_level", String.valueOf(adminlevel)));
 			}
 		}
@@ -258,8 +257,7 @@ public class BoundaryDiff {
 			
 		int processors = Runtime.getRuntime().availableProcessors();
 		ExecutorService excSvc = Executors.newFixedThreadPool(processors);
-		ExecutorCompletionService<String> executor = new ExecutorCompletionService<String>(
-				excSvc);
+		ExecutorCompletionService<String> executor = new ExecutorCompletionService<>(excSvc);
 
 		
 		for (final Entry<String, String> tag : tags) {
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 66aa84d..2012066 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryPreprocessor.java
@@ -43,7 +43,7 @@ public class BoundaryPreprocessor implements Runnable {
 				// must be last as it is the default
 				"uk.me.parabola.mkgmap.reader.osm.boundary.Osm5BoundaryDataSource", };
 
-		loaders = new ArrayList<Class<? extends LoadableBoundaryDataSource>>();
+		loaders = new ArrayList<>();
 
 		for (String source : sources) {
 			try {
@@ -93,7 +93,7 @@ public class BoundaryPreprocessor implements Runnable {
 	private String boundaryFilename;
 	private String outDir;
 	private ExecutorService threadPool;
-	private final BlockingQueue<Future<Object>> remainingTasks = new LinkedBlockingQueue<Future<Object>>();
+	private final BlockingQueue<Future<Object>> remainingTasks = new LinkedBlockingQueue<>();
 
 	/**
 	 * constructor for stand-alone usage (workout only)
@@ -215,14 +215,13 @@ public class BoundaryPreprocessor implements Runnable {
 		if (threadPool == null) {
 			// only one thread available for the preparer
 			// so execute the task directly
-			FutureTask<V> future = new FutureTask<V>(worker);
+			FutureTask<V> future = new FutureTask<>(worker);
 			future.run();
 			return future;
-		} else {
-			Future<Object> task = threadPool.submit((Callable<Object>) worker);
-			remainingTasks.add(task);
-			return (Future<V>) task;
 		}
+		Future<Object> task = threadPool.submit((Callable<Object>) worker);
+		remainingTasks.add(task);
+		return (Future<V>) task;
 	}
 
 	/**
@@ -275,8 +274,7 @@ public class BoundaryPreprocessor implements Runnable {
 			long dt = System.currentTimeMillis() - t1;
 			log.info("splitting", boundsFilename, "took", dt, "ms");
 			if (bqt != null){
-				File outDir = new File(boundsDir);
-				BoundarySaver saver = new BoundarySaver(outDir, BoundarySaver.QUADTREE_DATA_FORMAT);
+				BoundarySaver saver = new BoundarySaver(new File(boundsDir), BoundarySaver.QUADTREE_DATA_FORMAT);
 				saver.setCreateEmptyFiles(false);
 
 				saver.saveQuadTree(bqt, boundsFilename); 		
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 61b0662..5c6cbbb 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryQuadTree.java
@@ -13,8 +13,10 @@
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
 import java.awt.Rectangle;
+import java.awt.Shape;
 import java.awt.geom.Area;
 import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
 import java.awt.geom.Rectangle2D;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
@@ -40,6 +42,7 @@ import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.util.EnhancedProperties;
 import uk.me.parabola.util.GpxCreator;
 import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.util.ShapeSplitter;
 
 /**
  * A quadtree implementation to handle areas formed by boundaries.
@@ -54,10 +57,13 @@ public class BoundaryQuadTree {
 	// debugging  aid 
 	private static final String DEBUG_TREEPATH = "?";
 	private static final boolean DO_ALL_TESTS = false;
-
+	
+	private static final boolean DO_CLIP = true;
+	private static final boolean DO_NOT_CLIP = false;
+	
 	// maps the "normal" tags of the boundaries that are saved in this tree to
 	// the boundaryId
-	private final HashMap<String, Tags> boundaryTags = new LinkedHashMap<String,Tags>();
+	private final HashMap<String, Tags> boundaryTags = new LinkedHashMap<>();
 	// maps the location relevant info to the boundaryId 
 	private final HashMap<String, BoundaryLocationInfo> preparedLocationInfo;
 	// property controlled preparer
@@ -67,7 +73,7 @@ public class BoundaryQuadTree {
 	// the bounding box of the quadtree
 	private final Rectangle bbox;
 	private final String bbox_key; 
-
+	
 	// tags that can be returned in the get method
 	public final static String[] mkgmapTagsArray =  {
 		"mkgmap:admin_level1",
@@ -97,7 +103,7 @@ public class BoundaryQuadTree {
 			uk.me.parabola.imgfmt.app.Area fileBbox,
 			uk.me.parabola.imgfmt.app.Area searchBbox, EnhancedProperties props)
 			throws IOException {
-		preparedLocationInfo = new LinkedHashMap<String, BoundaryLocationInfo> ();
+		preparedLocationInfo = new LinkedHashMap<> ();
 		preparer = new BoundaryLocationPreparer(props);
 		assert fileBbox != null: "parameter fileBbox must not be null";
 		this.bbox = new Rectangle(fileBbox.getMinLong(), fileBbox.getMinLat(),
@@ -133,7 +139,7 @@ public class BoundaryQuadTree {
 			return;
 		
 
-		HashMap<String,Boundary> bMap = new HashMap<String,Boundary>();
+		HashMap<String,Boundary> bMap = new HashMap<>();
 		for (Boundary b: boundaries){
 			bMap.put(b.getId(), b);
 			boundaryTags.put(b.getId(), b.getTags());
@@ -141,7 +147,7 @@ public class BoundaryQuadTree {
 		sortBoundaryTagsMap();
 		// add the boundaries in a specific order
 		for (String id: boundaryTags.keySet()){
-			root.add (bMap.get(id).getArea(), id, null);
+			root.add (bMap.get(id).getArea(), id, null, DO_NOT_CLIP);
 		}
 		bMap = null;
 		root.split("_");
@@ -180,7 +186,7 @@ public class BoundaryQuadTree {
 	 * AdminLevelCollator and then reversed.  
 	 */
 	public Map<String, Tags> getTagsMap() {
-		return new LinkedHashMap<String, Tags>(boundaryTags);
+		return new LinkedHashMap<>(boundaryTags);
 	}
 	
 	/**
@@ -192,7 +198,7 @@ public class BoundaryQuadTree {
 	 * @return A HashMap mapping BoundaryIds to a List with all area parts  
 	 */
 	public Map<String, List<Area>> getAreas(){
-		Map<String, List<Area>> areas = new HashMap<String, List<Area>>();
+		Map<String, List<Area>> areas = new HashMap<>();
 		root.getAreas(areas, "_", null);
 		return areas;
 	}
@@ -261,10 +267,10 @@ public class BoundaryQuadTree {
 	 */
 	private void sortBoundaryTagsMap(){
 		// make sure that the merged LinkedHashMap is sorted as mergeBoundaries() needs it
-		ArrayList<String> ids = new ArrayList<String>(boundaryTags.keySet());
+		ArrayList<String> ids = new ArrayList<>(boundaryTags.keySet());
 		Collections.sort(ids, new AdminLevelCollator());
 		Collections.reverse(ids);
-		HashMap<String,Tags> tmp = new LinkedHashMap<String,Tags>(boundaryTags);
+		HashMap<String,Tags> tmp = new LinkedHashMap<>(boundaryTags);
 		boundaryTags.clear();
 		for (String id: ids){
 			boundaryTags.put(id,tmp.get(id));
@@ -282,7 +288,7 @@ public class BoundaryQuadTree {
 	 * @param id the boundaryId
 	 * @throws IOException
 	 */
-	private void writeBoundaryTags(OutputStream stream, Tags tags, String id) throws IOException{
+	private static void writeBoundaryTags(OutputStream stream, Tags tags, String id) throws IOException{
 		DataOutputStream dOutStream = new DataOutputStream(stream);
 		dOutStream.writeUTF("TAGS");
 		dOutStream.writeUTF(id);
@@ -308,12 +314,12 @@ public class BoundaryQuadTree {
 	/**
 	 * Read a stream in QUADTREE_DATA_FORMAT 
 	 * @param inpStream the already opened DataInputStream
-	 * @param bbox a bounding box. Areas not intersecting the bbox are 
+	 * @param searchBBox a bounding box. Areas not intersecting the bbox are 
 	 * ignored. 
 	 * @throws IOException
 	 */
 	private void readStreamQuadTreeFormat(DataInputStream inpStream,
-			uk.me.parabola.imgfmt.app.Area bbox) throws IOException{
+			uk.me.parabola.imgfmt.app.Area searchBBox) throws IOException{
 		boolean isFirstArea = true;
 		try {
 			while (true) {
@@ -338,13 +344,15 @@ public class BoundaryQuadTree {
 					int minLong = inpStream.readInt();
 					int maxLat = inpStream.readInt();
 					int maxLong = inpStream.readInt();
-					log.debug("Next boundary. Lat min:",minLat,"max:",maxLat,"Long min:",minLong,"max:",maxLong);
+					if (log.isDebugEnabled()){
+						log.debug("Next boundary. Lat min:",minLat,"max:",maxLat,"Long min:",minLong,"max:",maxLong);
+					}
 					uk.me.parabola.imgfmt.app.Area rBbox = new uk.me.parabola.imgfmt.app.Area(
 							minLat, minLong, maxLat, maxLong);
 					int bSize = inpStream.readInt();
 					log.debug("Size:",bSize);
 
-					if ( bbox == null || bbox.intersects(rBbox)) {
+					if ( searchBBox == null || searchBBox.intersects(rBbox)) {
 						log.debug("Bbox intersects. Load the boundary");
 						String treePath = inpStream.readUTF();
 						String id = inpStream.readUTF();
@@ -464,7 +472,7 @@ public class BoundaryQuadTree {
 				int lat = co.getLatitude();
 				for (NodeElem nodeElem: nodes){
 					if (nodeElem.tagMask > 0){	
-						if (nodeElem.area.contains(lon,lat)){
+						if (nodeElem.getArea().contains(lon,lat)){
 							String res = new String (nodeElem.boundaryId);
 							if (nodeElem.locationDataSrc != null)
 								res += ";" + nodeElem.locationDataSrc;
@@ -499,7 +507,7 @@ public class BoundaryQuadTree {
 				int lat = co.getLatitude();
 				for (NodeElem nodeElem: nodes){
 					if (nodeElem.tagMask > 0){	
-						if (nodeElem.area.contains(lon,lat)){
+						if (nodeElem.getArea().contains(lon,lat)){
 							return nodeElem.locTags;
 						}
 					}
@@ -549,8 +557,8 @@ public class BoundaryQuadTree {
 			boolean ok = true;
 			for (int i=0; i< nodes.size()-1; i++){
 				for (int j=i+1; j < nodes.size(); j++){
-					Area a = new Area (nodes.get(i).area);
-					a.intersect(nodes.get(j).area);
+					Area a = new Area (nodes.get(i).getArea());
+					a.intersect(nodes.get(j).getArea());
 					
 					if (a.isEmpty())
 						continue;
@@ -592,7 +600,7 @@ public class BoundaryQuadTree {
 			Node node = this;
 			String path = treePath;
 			while(path.isEmpty() == false){
-				int idx = Integer.valueOf(path.substring(0, 1));
+				int idx = Integer.parseInt(path.substring(0, 1));
 				path = path.substring(1);
 				if (node.childs == null)
 					node.allocChilds();
@@ -600,7 +608,7 @@ public class BoundaryQuadTree {
 			}
 			
 			if (node.nodes == null){
-				node.nodes = new ArrayList<NodeElem>();
+				node.nodes = new ArrayList<>();
 			}
 			NodeElem nodeElem = new NodeElem(boundaryId, area, refs);
 			assert (area.getBounds2D().getWidth() == 0 || area.getBounds2D().getHeight() == 0 || this.bbox.intersects(area.getBounds2D())) : "boundary bbox doesn't fit into quadtree "+ bbox + " " + area.getBounds2D(); 
@@ -608,40 +616,29 @@ public class BoundaryQuadTree {
 		}
 
 		/**
-		 * Add an area and the related tags to the tree. 
-		 * @param area the part of the boundary area that should be added to the tree.    
-		 * @param locTags the location relevant tags from the boundary 
+		 * Add a shape and the related tags to the tree. 
+		 * @param shape the part of the boundary area that should be added to the tree.    
 		 * @param boundaryId id of the originating boundary
+		 * @param refs A string containing boundaryIds and admin-level info
+		 * of all boundaries with lower admin levels that share the same area. 
+		 * @param clipOption true: clip the shape with the bounding box of the nodeElem, 
+		 * false: use shape without clipping 
 		 */
-		private void add(Area area, String boundaryId, String refs){
-			if (!isLeaf){
-				// should not happen
-				for (int i = 0; i < 4; i++){
-					childs[i].add(area, boundaryId, refs);
-				}
-				return;
-			}
-			// only add areas that intersect with this part of the tree
-			if (area.intersects(this.bbox) == false)
-				return;
-			Area a;
-			if (area.contains(bbox))
-				a = new Area(this.bbox); // quadtree bbox lies entirely in area
-			else {
-				a = new Area(area);
-				Area bboxArea = new Area(this.bbox);
-				// check if area lies entirely in quadtree bbox
-				if (bboxArea.contains(area.getBounds2D()) == false){
-					// worst case: area and bbox partly intersect
-					a.intersect(bboxArea); 
-				}
-			}
-			if (a.isEmpty() == false){
-				if (nodes == null)
-					nodes = new ArrayList<NodeElem>();
-				NodeElem nodeElem = new NodeElem(boundaryId, a, refs);
-				nodes.add(nodeElem);
+		private void add(Shape shape, String boundaryId, String refs, boolean clipOption){
+			assert isLeaf;
+			Path2D.Double path;
+			if (clipOption){
+				path = ShapeSplitter.clipShape (shape, bbox);
+				// only add areas that intersect with this part of the tree
+				if (path == null)
+					return;
 			}
+			else
+				path = new Path2D.Double(shape);
+			if (nodes == null)
+				nodes = new ArrayList<>();
+			NodeElem nodeElem = new NodeElem(boundaryId, path, refs);
+			nodes.add(nodeElem);
 		}
 
 		/**
@@ -658,7 +655,7 @@ public class BoundaryQuadTree {
 			else{
 				// (sub) tree is different, rebuild it as combination of 
 				// both trees.
-				HashMap<String,List<Area>> areas = new HashMap<String, List<Area>>();
+				HashMap<String,List<Area>> areas = new HashMap<>();
 				this.getAreas(areas, treePath,null);
 				other.getAreas(areas, treePath,null);
 				isLeaf = true;
@@ -673,7 +670,7 @@ public class BoundaryQuadTree {
 					for (Area area : aList){
 						path.append(area, false);
 					}
-					add(new Area(path), id, null);
+					add(new Area(path), id, null, DO_NOT_CLIP);
 				}
 				split(treePath);
 			}
@@ -686,16 +683,19 @@ public class BoundaryQuadTree {
 		 * @return a new Area instance (might be empty)
 		 */
 		private Area getCoveredArea(Integer admLevel, String treePath){
-			HashMap<String,List<Area>> areas = new HashMap<String, List<Area>>();
+			HashMap<String,List<Area>> areas = new HashMap<>();
 			this.getAreas(areas, treePath, admLevel);
-			Path2D.Double path = new Path2D.Double();
-			for (Entry <String, List<Area>> entry : areas.entrySet()){
-				for (Area area: entry.getValue()){
-					path.append(area, false);
+			if (areas.isEmpty() == false){
+				Path2D.Double path = new Path2D.Double(PathIterator.WIND_NON_ZERO, 1024 * 1024);
+				for (Entry <String, List<Area>> entry : areas.entrySet()){
+					for (Area area: entry.getValue()){
+						path.append(area, false);
+					}
 				}
+				Area combinedArea = new Area(path);
+				return combinedArea;
 			}
-			Area combinedArea = new Area(path);
-			return combinedArea;
+			return new Area();
 		}
 		
 		/**
@@ -722,9 +722,9 @@ public class BoundaryQuadTree {
 				if (testMask != null && (nodeElem.tagMask & testMask) == 0)
 					continue;
 				List<Area> aList = areas.get(id);
-				Area a = new Area(nodeElem.area);
+				Area a = new Area(nodeElem.getArea());
 				if (aList == null){
-					aList = new ArrayList<Area>(4);
+					aList = new ArrayList<>(4);
 					areas.put(id, aList);
 				}
 				aList.add(a);
@@ -742,9 +742,9 @@ public class BoundaryQuadTree {
 						}
 						id = relParts[1];
 						aList = areas.get(id);
-						a = new Area(nodeElem.area);
+						a = new Area(nodeElem.getArea());
 						if (aList == null){
-							aList = new ArrayList<Area>(4);
+							aList = new ArrayList<>(4);
 							areas.put(id, aList);
 						}
 						aList.add(a);
@@ -768,13 +768,20 @@ public class BoundaryQuadTree {
 				printNodes("start", treePath);
 			}
 			long t1 = System.currentTimeMillis();
+			if (DEBUG){
+				if (treePath.equals(DEBUG_TREEPATH) || DEBUG_TREEPATH.equals("all")){
+					for (NodeElem nodeElem: nodes){
+						nodeElem.saveGPX("start",treePath);
+					}			
+				}
+			}
 			
 			mergeEqualIds();
 			mergeLastRectangles();
 			if (DEBUG)
 				printNodes("prep", treePath);
 
-			List<NodeElem> reworked = new ArrayList<NodeElem>();
+			List<NodeElem> reworked = new ArrayList<>();
 
 			// detect intersection of areas, merge tag info
 			for (int i=0; i < nodes.size(); i++){
@@ -790,32 +797,32 @@ public class BoundaryQuadTree {
 					if (toAdd.isValid() == false)
 						break;
 					NodeElem currElem = reworked.get(j);
-					if (currElem.srcPos == i || currElem.area.isEmpty())
+					if (currElem.srcPos == i || currElem.getArea().isEmpty())
 						continue;
 
-					Rectangle2D rCurr = currElem.area.getBounds2D();
+					Rectangle2D rCurr = currElem.getArea().getBounds2D();
 
-					Rectangle2D rAdd = rCurr.createIntersection(toAdd.area.getBounds2D());
+					Rectangle2D rAdd = rCurr.createIntersection(toAdd.getArea().getBounds2D());
 					if (rAdd.isEmpty()){
 						continue; 
 					}
 					// the bounding boxes intersect, so we have to find out if the areas also intersect
-					Area toAddxCurr = new Area(currElem.area);
-					toAddxCurr.intersect(toAdd.area);
+					Area toAddxCurr = new Area(currElem.getArea());
+					toAddxCurr.intersect(toAdd.getArea());
 										
 					if (!isWritable(toAddxCurr)){
 						continue; // empty or only too small fragments 
 					}
 					
-					Area toAddMinusCurr = new Area(toAdd.area);
-					toAddMinusCurr.subtract(currElem.area);
+					Area toAddMinusCurr = new Area(toAdd.getArea());
+					toAddMinusCurr.subtract(currElem.getArea());
 
 					if (toAddMinusCurr.isEmpty()){
 						// toadd is fully covered by curr
 						if (toAdd.tagMask == POSTCODE_ONLY){
 							// if we get here, toAdd has only zip code that is already known 
 							// in larger or equal area of currElem
-							toAdd.area.reset(); // ignore this
+							toAdd.getArea().reset(); // ignore this
 							break;
 						}
 					}
@@ -833,11 +840,11 @@ public class BoundaryQuadTree {
 						log.warn(chkMsg);
 					}
 					
-					Area currMinusToAdd = new Area(currElem.area);
-					currMinusToAdd.subtract(toAdd.area);
+					Area currMinusToAdd = new Area(currElem.getArea());
+					currMinusToAdd.subtract(toAdd.getArea());
 					
 					// remove intersection part from toAdd
-					toAdd.area = toAddMinusCurr;
+					toAdd.setArea(toAddMinusCurr);
 					if (!isWritable(currMinusToAdd)){
 					    // curr is fully covered by toAdd 
 						if (toAdd.tagMask != POSTCODE_ONLY){
@@ -854,7 +861,7 @@ public class BoundaryQuadTree {
 					}
 
 					// remove intersection part also from curr 
-					currElem.area = currMinusToAdd;
+					currElem.setArea(currMinusToAdd);
 					
 					if (toAdd.tagMask != POSTCODE_ONLY){
 						// combine tag info in intersection
@@ -871,8 +878,9 @@ public class BoundaryQuadTree {
 			removeEmptyAreas(treePath);
 
 			long dt = System.currentTimeMillis()-t1;
-			if (dt  > 1000)
-				log.info(bbox_key, ": merge required long time:", dt, "ms");
+			if (dt  > 1000){
+				log.info(bbox_key, " : makeDistinct required long time:", dt, "ms");
+			}
 			if (DEBUG)
 				printNodes("end", treePath);
 
@@ -891,7 +899,7 @@ public class BoundaryQuadTree {
 			int start = nodes.size()-1;
 			for (int i = start; i > 0; i--){
 				if (nodes.get(i).boundaryId.equals(nodes.get(i-1).boundaryId)){
-					nodes.get(i-1).area.add(nodes.get(i).area);
+					nodes.get(i-1).getArea().add(nodes.get(i).getArea());
 					nodes.remove(i);
 				}
 			}
@@ -912,10 +920,10 @@ public class BoundaryQuadTree {
 				NodeElem lastNode = nodes.get(nodes.size()-1);
 				NodeElem prevNode = nodes.get(nodes.size()-2);
 				// don't merge admin_level tags into zip-code only boundary
-				if (prevNode.tagMask != POSTCODE_ONLY && lastNode.area.isRectangular() && prevNode.area.isRectangular()){
+				if (prevNode.tagMask != POSTCODE_ONLY && lastNode.getArea().isRectangular() && prevNode.getArea().isRectangular()){
 					// two areas are rectangles, it is likely that they are equal to the bounding box
 					// In this case we add the tags to the existing area instead of creating a new one
-					if (prevNode.area.equals(lastNode.area)){
+					if (prevNode.getArea().equals(lastNode.getArea())){
 						prevNode.addLocInfo(lastNode);
 						nodes.remove(nodes.size()-1);
 						done = false;
@@ -936,12 +944,12 @@ public class BoundaryQuadTree {
 				NodeElem chkRemove = nodes.get(j);
 				if (chkRemove.isValid() == false)
 					removeThis = true;
-				else if (this.bbox.intersects(chkRemove.area.getBounds2D()) == false){
+				else if (this.bbox.intersects(chkRemove.getArea().getBounds2D()) == false){
 					// we might get here because of errors in java.awt.geom.Area
 					// sometimes, Area.subtract() seems to produce an area which 
 					// lies outside of original areas
 					removeThis = true;
-				}else if (!isWritable(chkRemove.area)){
+				}else if (!isWritable(chkRemove.getArea())){
 					removeThis = true;
 				}
 				if (removeThis){
@@ -949,7 +957,7 @@ public class BoundaryQuadTree {
 				}
 			}			 		
 		}
-
+		
 		/**
 		 * allocate 4 childs with bounding boxes that have 1/4 of the 
 		 * size of the parent.  
@@ -991,11 +999,13 @@ public class BoundaryQuadTree {
 					return ;
 				}
 
-				mergeLastRectangles();
+//				mergeLastRectangles();
 				allocChilds();
-				for (int i = 0; i < 4; i++){
-					for (NodeElem nodeElem: nodes){
-						childs[i].add(nodeElem.area, nodeElem.boundaryId, nodeElem.locationDataSrc);
+				for (NodeElem nodeElem: nodes){
+					Rectangle shapeBBox = nodeElem.shape.getBounds();
+					for (int i = 0; i < 4; i++){
+						if (childs[i].bbox.intersects(shapeBBox))
+							childs[i].add(nodeElem.shape, nodeElem.boundaryId, nodeElem.locationDataSrc, DO_CLIP);
 					}
 				}
 				// return memory to GC
@@ -1011,6 +1021,8 @@ public class BoundaryQuadTree {
 	private class NodeElem{
 		// the intersections of the boundaries with the bounding box of this node
 		private Area area;
+		
+		private Shape shape; // for temp. use when splitting
 		// location relevant tags of boundaries that intersect with the bounding box of this node
 		private Tags locTags;
 
@@ -1023,10 +1035,10 @@ public class BoundaryQuadTree {
 		private int srcPos;
 
 		/**
-		 * Create a node elem. 
+		 * Create a node element. 
 		 * @param boundaryId The boundary Id
 		 * @param area the (part of the) boundary area stored in this node
-		 * @param refs A string containing boundaryIds and admin-level infos
+		 * @param refs A string containing boundaryIds and admin level info
 		 * of all boundaries with lower admin levels that share the same area. 
 		 */
 		NodeElem (String boundaryId, Area area, String refs){
@@ -1038,7 +1050,22 @@ public class BoundaryQuadTree {
 		}
 
 		/**
-		 * Create a new node elem as a partly copy of an existing 
+		 * Create a node element. 
+		 * @param boundaryId The boundary Id
+		 * @param shape the (part of the) boundary area stored in this node
+		 * @param refs A string containing boundaryIds and admin level info
+		 * of all boundaries with lower admin levels that share the same area. 
+		 */
+		NodeElem (String boundaryId, Shape shape, String refs){
+			srcPos = -1;
+			this.boundaryId = boundaryId;
+			this.shape = shape;
+			this.locationDataSrc = refs;
+			calcLocTags();
+		}
+
+		/**
+		 * Create a new node element as a partly copy of an existing 
 		 * NodeElem and a new area. 
 		 * @param other the existing NodeElem instance
 		 * @param area the new area 
@@ -1060,8 +1087,11 @@ public class BoundaryQuadTree {
 		 * the tags should be ignored.
 		 */
 		private boolean isValid(){
-			if (tagMask == 0 || area == null || area.isEmpty() 
-					|| area.getBounds2D().getWidth() <= BoundaryUtil.MIN_DIMENSION && area.getBounds2D().getHeight() <= BoundaryUtil.MIN_DIMENSION)
+			if (tagMask == 0)
+				return false;
+			Area checkArea = getArea();
+			if (checkArea == null || checkArea.isEmpty()
+					|| checkArea.getBounds2D().getWidth() <= BoundaryUtil.MIN_DIMENSION && checkArea.getBounds2D().getHeight() <= BoundaryUtil.MIN_DIMENSION)
 				return false;
 			return true;
 		}
@@ -1159,28 +1189,27 @@ public class BoundaryQuadTree {
 		 */
 		private void save(OutputStream stream, String treePath) throws IOException{
 			ByteArrayOutputStream oneItemStream = new ByteArrayOutputStream();
-			DataOutputStream dos = new DataOutputStream(oneItemStream);
-			String id = this.boundaryId;
-			dos.writeUTF(treePath.substring(1));
-			dos.writeUTF(id);
-			if (this.locationDataSrc == null)
-				dos.writeUTF("");
-			else 
-				dos.writeUTF(this.locationDataSrc);
-			BoundarySaver.writeArea(dos, this.area);
-			dos.close();
-
+			try(DataOutputStream dos = new DataOutputStream(oneItemStream)){
+				String id = this.boundaryId;
+				dos.writeUTF(treePath.substring(1));
+				dos.writeUTF(id);
+				if (this.locationDataSrc == null)
+					dos.writeUTF("");
+				else 
+					dos.writeUTF(this.locationDataSrc);
+				BoundarySaver.writeArea(dos, this.getArea());
+			}
 			// now start to write into the real stream
 
 			// first write the bounding box so that is possible to skip the
 			// complete entry
-			uk.me.parabola.imgfmt.app.Area bbox = Java2DConverter.createBbox(this.area);
+			uk.me.parabola.imgfmt.app.Area outBBox = Java2DConverter.createBbox(this.getArea());
 			DataOutputStream dOutStream = new DataOutputStream(stream);
 			dOutStream.writeUTF("AREA");
-			dOutStream.writeInt(bbox.getMinLat());
-			dOutStream.writeInt(bbox.getMinLong());
-			dOutStream.writeInt(bbox.getMaxLat());
-			dOutStream.writeInt(bbox.getMaxLong());
+			dOutStream.writeInt(outBBox.getMinLat());
+			dOutStream.writeInt(outBBox.getMinLong());
+			dOutStream.writeInt(outBBox.getMaxLat());
+			dOutStream.writeInt(outBBox.getMaxLong());
 
 			// write the size of the boundary block so that it is possible to
 			// skip it
@@ -1192,7 +1221,22 @@ public class BoundaryQuadTree {
 			dOutStream.write(data);
 			dOutStream.flush();
 		}
-		
+
+
+		private Area getArea(){
+			if (shape != null){
+				area = new Area(shape);
+				shape = null;
+			}
+			return area;
+		}
+
+		private void setArea(Area area) {
+			this.area = area;
+			this.shape = null;
+		}
+
+
 		/**
 		 * calculate a handy short value that represents the available location tags
 		 * @return a bit mask, a bit with value 1 means the corresponding entry in {@link locationTagNames } 
@@ -1288,6 +1332,7 @@ public class BoundaryQuadTree {
 		}
 	}
 
+	
 	/***
 	 * Used to sort BoundaryLocationInfo. Input are boundaryIds.
 	 * @author gerd
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
index 6c30d6e..e2d4a14 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundarySaver.java
@@ -12,10 +12,11 @@
  */
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
-import java.awt.Rectangle;
-import java.awt.geom.Area;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntStack;
+
+import java.awt.Shape;
 import java.awt.geom.PathIterator;
-import java.awt.geom.Rectangle2D;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
@@ -37,6 +38,7 @@ import java.util.regex.Pattern;
 
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.mkgmap.Version;
+import uk.me.parabola.mkgmap.reader.osm.Tags;
 import uk.me.parabola.util.Java2DConverter;
 
 public class BoundarySaver {
@@ -86,7 +88,7 @@ public class BoundarySaver {
 	}
 
 	private int lastAccessNo = 0;
-	private final List<StreamInfo> openStreams = new ArrayList<StreamInfo>();
+	private final List<StreamInfo> openStreams = new ArrayList<>();
 	/** keeps the open streams */
 	private final Map<String, StreamInfo> streams;
 	private boolean createEmptyFiles = false;
@@ -98,8 +100,8 @@ public class BoundarySaver {
 			System.exit(-1);
 		}
 		this.dataFormat = mode;
-		this.streams = new HashMap<String, StreamInfo>();
-		this.writtenFileNames = new HashSet<String>();
+		this.streams = new HashMap<>();
+		this.writtenFileNames = new HashSet<>();
 	}
 
 	/**
@@ -129,14 +131,14 @@ public class BoundarySaver {
 	}
 
 	public void addBoundary(Boundary boundary) {
-		Map<String, Area> splitBounds = splitArea(boundary.getArea());
-		for (Entry<String, Area> split : splitBounds.entrySet()) {
-			saveToFile(split.getKey(),
-					new Boundary(split.getValue(), boundary.getTags(), boundary
-							.getId()));
+		Map<String, Shape> splitBounds = BoundaryUtil.rasterArea(boundary.getArea());
+		for (Entry<String, Shape> split : splitBounds.entrySet()) {
+			saveToFile(split.getKey(), split.getValue(), boundary.getTags(),
+					boundary.getId());
 		}
 	}
 
+
 	public HashSet<String> end() {
 		if (isCreateEmptyFiles() && getBbox() != null) {
 			// a bounding box is set => fill the gaps with empty files
@@ -197,69 +199,6 @@ public class BoundarySaver {
 		log.debug("Remaining", openStreams.size(), "open streams.");
 	}
 
-	private Map<String, Area> splitArea(Area areaToSplit) {
-		return splitArea(areaToSplit, new HashMap<String, Area>(), null);
-	}
-	
-	/**
-	 * Split a given area into the raster tiles. 
-	 * @param areaToSplit the area
-	 * @param splits a map the splitted tiles are added to
-	 * @return the map with the splitted tiles
-	 */
-	private Map<String, Area> splitArea(Area areaToSplit, Map<String, Area> splits, Rectangle knownBbox) {
-		if (areaToSplit.isEmpty())
-			return splits;
-		Rectangle2D areaBounds;
-		
-		if (knownBbox != null){
-			// within recursion: use the calculated rectangle, not the area,
-			// as the latter might contain a spike that "looks" out of the bbox
-			// and can cause a stack overflow
-			areaBounds = knownBbox.getBounds2D();
-		}
-		else 
-			areaBounds = areaToSplit.getBounds2D();
-
-		// use high precision bounds with later rounding to avoid some little rounding
-		// errors (49999.99999999 instead of 50000.0)
-		int sMinLong = BoundaryUtil.getSplitBegin((int)Math.round(areaBounds.getMinX()));
-		int sMinLat = BoundaryUtil.getSplitBegin((int)Math.round(areaBounds.getMinY()));
-		int sMaxLong = BoundaryUtil.getSplitEnd((int)Math.round(areaBounds.getMaxX()));
-		int sMaxLat = BoundaryUtil.getSplitEnd((int)Math.round(areaBounds.getMaxY()));
-		
-		int dLon = sMaxLong- sMinLong;
-		int dLat = sMaxLat - sMinLat;
-		if (dLon > BoundaryUtil.RASTER || dLat > BoundaryUtil.RASTER){ 
-			// split into two halves
-			Rectangle r1,r2;
-			int middle; 
-			if (dLon > dLat) {
-				middle = BoundaryUtil.getSplitEnd(sMinLong+dLon/2);
-				r1 = new Rectangle(sMinLong, sMinLat, middle-sMinLong, dLat);
-				r2 = new Rectangle(middle, sMinLat, sMaxLong-middle, dLat);
-			} else {
-				middle = BoundaryUtil.getSplitEnd(sMinLat+dLat/2);
-				r1 = new Rectangle(sMinLong, sMinLat, dLon, middle-sMinLat);
-				r2 = new Rectangle(sMinLong, middle, dLon, sMaxLat-middle);
-			}
-			Area a = new Area(r1);
-			// intersect with the both halves
-			// and split both halves recursively
-			a.intersect(areaToSplit);
-			splitArea(a, splits, r1);
-			
-			a = new Area(r2);
-			a.intersect(areaToSplit);
-			splitArea(a, splits, r2);
-		} else {
-			// the area fully fits into one raster tile
-			splits.put(BoundaryUtil.getKey(sMinLat, sMinLong), areaToSplit);
-		}
-		return splits;
-
-	}
-
 	private void openStream(StreamInfo streamInfo, boolean newFile) {
 		if (streamInfo.file.getParentFile().exists() == false
 				&& streamInfo.file.getParentFile() != null)
@@ -274,8 +213,8 @@ public class BoundarySaver {
 
 				String[] keyParts = streamInfo.boundsKey.split(Pattern
 						.quote("_"));
-				int lat = Integer.valueOf(keyParts[0]);
-				int lon = Integer.valueOf(keyParts[1]);
+				int lat = Integer.parseInt(keyParts[0]);
+				int lon = Integer.parseInt(keyParts[1]);
 				if (lat < minLat) {
 					minLat = lat;
 					log.debug("New min Lat:", minLat);
@@ -339,11 +278,11 @@ public class BoundarySaver {
 		// write the header part 2
 		// write it first to a byte array to be able to calculate the length of the header
 		ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
-		DataOutputStream headerDataStream = new DataOutputStream(headerStream);
-		headerDataStream.writeUTF(dataFormat);
-		headerDataStream.writeInt(CURRENT_RECORD_ID);
-		headerDataStream.writeUTF(Version.VERSION);
-		headerDataStream.close();
+		try(DataOutputStream headerDataStream = new DataOutputStream(headerStream)){
+			headerDataStream.writeUTF(dataFormat);
+			headerDataStream.writeInt(CURRENT_RECORD_ID);
+			headerDataStream.writeUTF(Version.VERSION);
+		}
 		
 		byte[] header2 = headerStream.toByteArray();
 		// write the length of the header part 2 so that it is possible to add
@@ -353,25 +292,38 @@ public class BoundarySaver {
 		dos.flush();
 	}
 
-	private void saveToFile(String filekey, Boundary boundary) {
+	/**
+	 * Save the elements that build a boundary with a given key 
+	 * that identifies the lower left corner of the raster.
+	 * @param filekey the string that identifies the lower left corner
+	 * @param shape the shape that describes the area of the boundary
+	 * @param tags the tags of the boundary
+	 * @param id the boundary id
+	 */
+	private void saveToFile(String filekey, Shape shape, Tags tags, String id) {
 		try {
 			StreamInfo streamInfo = getStream(filekey);
 			if (streamInfo != null && streamInfo.isOpen()) {
-				writeRawFormat(streamInfo.stream, boundary);
+				writeRawFormat(streamInfo.stream, shape, tags, id);
 			}
 		} catch (Exception exp) {
 			log.error("Cannot write boundary: " + exp, exp);
 		}
 
 		tidyStreams();
+		
 	}
+	
 
 	/**
-	 * Write a boundary to a given stream. 
+	 * Save the elements of a boundary to a stream.
 	 * @param stream the already opened OutputStream
-	 * @param boundary the boundary 
+	 * @param shape the shape that describes the area of the boundary
+	 * @param tags the tags of the boundary
+	 * @param id the boundary id
 	 */
-	private void writeRawFormat(OutputStream stream, Boundary boundary) {
+	private void writeRawFormat(OutputStream stream, Shape shape, Tags tags,
+			String id) {
 		ByteArrayOutputStream oneItemStream = new ByteArrayOutputStream();
 		DataOutputStream dos = new DataOutputStream(oneItemStream);
 		if (dataFormat == QUADTREE_DATA_FORMAT) {
@@ -379,14 +331,13 @@ public class BoundarySaver {
 			System.exit(1);
 		}
 		try {
-			dos.writeUTF(boundary.getId());
+			dos.writeUTF(id);
 			
 			// write the tags
-			int noOfTags = boundary.getTags().size();
+			int noOfTags = tags.size();
 			dos.writeInt(noOfTags);
 
-			Iterator<Entry<String, String>> tagIter = boundary.getTags()
-					.entryIterator();
+			Iterator<Entry<String, String>> tagIter = tags.entryIterator();
 			while (tagIter.hasNext()) {
 				Entry<String, String> tag = tagIter.next();
 				dos.writeUTF(tag.getKey());
@@ -394,23 +345,24 @@ public class BoundarySaver {
 				noOfTags--;
 			}
 			assert noOfTags == 0 : "Remaining tags: " + noOfTags + " size: "
-					+ boundary.getTags().size() + " "
-					+ boundary.getTags().toString();
+					+ tags.size() + " "
+					+ tags.toString();
 
-			writeArea(dos,boundary.getArea());
+			//writeArea(dos,boundary.getArea());
+			writeArea(dos, shape);
 			dos.close();
 
 			// now start to write into the real stream 
 
 			// first write the bounding box so that is possible to skip the
 			// complete entry
-			uk.me.parabola.imgfmt.app.Area bbox = Java2DConverter
-					.createBbox(boundary.getArea());
+			uk.me.parabola.imgfmt.app.Area outBBox = Java2DConverter
+					.createBbox(shape);
 			DataOutputStream dOutStream = new DataOutputStream(stream);
-			dOutStream.writeInt(bbox.getMinLat());
-			dOutStream.writeInt(bbox.getMinLong());
-			dOutStream.writeInt(bbox.getMaxLat());
-			dOutStream.writeInt(bbox.getMaxLong());
+			dOutStream.writeInt(outBBox.getMinLat());
+			dOutStream.writeInt(outBBox.getMinLong());
+			dOutStream.writeInt(outBBox.getMaxLat());
+			dOutStream.writeInt(outBBox.getMaxLong());
 
 			// write the size of the boundary block so that it is possible to
 			// skip it
@@ -426,8 +378,9 @@ public class BoundarySaver {
 			log.error(exp.toString());
 		}
 
+		
 	}
-
+	
 	/**
 	 * Write area to stream with Double precision. The coordinates
 	 * are saved as varying length doubles with delta coding. 
@@ -435,10 +388,11 @@ public class BoundarySaver {
 	 * @param area the area (can be non-singular)
 	 * @throws IOException
 	 */
-	public static void writeArea(DataOutputStream dos, Area area) throws IOException{
+	public static void writeArea(DataOutputStream dos, Shape area) throws IOException{
 		double[] res = new double[6];
 		double[] lastRes = new double[2];
-		List<Integer> pairs = new LinkedList<Integer>();
+		
+		IntArrayList pairs = new IntArrayList();
 		// step 1: count parts
 		PathIterator pit = area.getPathIterator(null);
 		int prevType = -1;
@@ -458,7 +412,7 @@ public class BoundarySaver {
 		// 2nd pass: write the data
 		pit = area.getPathIterator(null);
 		prevType = -1;
-		
+		int pairsPos = 0;
 		dos.writeInt(pit.getWindingRule());
 		while (!pit.isDone()) {
 			int type = pit.currentSegment(res);
@@ -467,7 +421,7 @@ public class BoundarySaver {
 			switch (type) {
 			case PathIterator.SEG_LINETO:
 				if (prevType != type){
-					len = pairs.remove(0);
+					len = pairs.getInt(pairsPos++);
 					dos.writeInt(len);
 				}
 				// no break
@@ -570,6 +524,6 @@ public class BoundarySaver {
 		
 		buffer[numBytes-1] &= 0x7f;
 		dos.write(buffer, 0, numBytes);
-	}  
+	}
 
 }
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 d4f1609..43da6ed 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
@@ -12,9 +12,12 @@
  */
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
+import java.awt.Rectangle;
+import java.awt.Shape;
 import java.awt.geom.Area;
 import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
@@ -24,6 +27,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Enumeration;
@@ -44,6 +48,7 @@ import uk.me.parabola.mkgmap.reader.osm.Way;
 import uk.me.parabola.util.EnhancedProperties;
 import uk.me.parabola.util.Java2DConverter;
 import uk.me.parabola.util.MultiHashMap;
+import uk.me.parabola.util.ShapeSplitter;
 
 public class BoundaryUtil {
 	private static final Logger log = Logger.getLogger(BoundaryUtil.class);
@@ -75,7 +80,7 @@ public class BoundaryUtil {
 				return Collections.emptyList();
 			}
 
-			List<BoundaryElement> bElements = new ArrayList<BoundaryElement>();
+			List<BoundaryElement> bElements = new ArrayList<>();
 			for (List<Coord> singleElement : areaElements) {
 				if (singleElement.size() <= 3) {
 					// need at least 4 items to describe a polygon
@@ -140,53 +145,49 @@ public class BoundaryUtil {
 	public static Map<String,BoundaryQuadTree> loadQuadTrees (String boundaryDirName, 
 			List<String> boundaryFileNames, 
 			uk.me.parabola.imgfmt.app.Area searchBbox, EnhancedProperties props){
-		Map<String,BoundaryQuadTree>  trees = new HashMap<String,BoundaryQuadTree>();
+		Map<String,BoundaryQuadTree>  trees = new HashMap<>();
 		File boundaryDir = new File(boundaryDirName);
 		BoundaryQuadTree bqt;
-			if (boundaryDir.isDirectory()){
-				for (String boundaryFileName: boundaryFileNames){
-					log.info("loading boundary file:", boundaryFileName);
-					// no support for nested directories
-					File boundaryFile = new File(boundaryDir, boundaryFileName);
-					try {
-						if (boundaryFile.exists()){
-							InputStream stream = new FileInputStream(boundaryFile);
-							bqt = BoundaryUtil.loadQuadTreeFromStream(stream, boundaryFileName, searchBbox, props);
-							if (bqt != null)
-								trees.put(boundaryFileName,bqt);
-						}
+		if (boundaryDir.isDirectory()){
+			for (String boundaryFileName: boundaryFileNames){
+				log.info("loading boundary file:", boundaryFileName);
+				// no support for nested directories
+				File boundaryFile = new File(boundaryDir, boundaryFileName);
+				if (boundaryFile.exists()){
+					try(InputStream stream = new FileInputStream(boundaryFile)){
+						bqt = BoundaryUtil.loadQuadTreeFromStream(stream, boundaryFileName, searchBbox, props);
+						if (bqt != null)
+							trees.put(boundaryFileName,bqt);
 					} catch (IOException exp) {
 						log.error("Cannot load boundary file " +  boundaryFileName + "." + exp);
 					}
-					
-				}
-			} else if (boundaryDirName.endsWith(".zip")) {
-				String  currentFileName = "";
-				try{
-					ZipFile zipFile = new ZipFile(boundaryDir);
-
-					for (String boundaryFileName : boundaryFileNames){
-						log.info("loading boundary file:", boundaryFileName);
-						currentFileName = boundaryFileName;
-						// direct access  
-						ZipEntry entry = zipFile.getEntry(boundaryFileName);
-						if (entry != null){ 
-							bqt = BoundaryUtil.loadQuadTreeFromStream(zipFile.getInputStream(entry), 
-									boundaryFileName, searchBbox, props);
+				}					
+			}
+		} else if (boundaryDirName.endsWith(".zip")) {
+			String  currentFileName = "";
+			try(ZipFile zipFile = new ZipFile(boundaryDir)){
+				for (String boundaryFileName : boundaryFileNames){
+					log.info("loading boundary file:", boundaryFileName);
+					currentFileName = boundaryFileName;
+					// direct access  
+					ZipEntry entry = zipFile.getEntry(boundaryFileName);
+					if (entry != null){ 
+						try(InputStream stream = zipFile.getInputStream(entry)){
+							bqt = BoundaryUtil.loadQuadTreeFromStream(stream, boundaryFileName, searchBbox, props);
 							if (bqt != null)
 								trees.put(boundaryFileName,bqt);
 						}
 					}
-					zipFile.close();
-				} catch (IOException exp) {
-					log.error("Cannot load boundary file " + currentFileName + "." + exp);
 				}
-			} else{ 
-				log.error("Cannot read " + boundaryDirName);
+			} catch (IOException exp) {
+				log.error("Cannot load boundary file " + currentFileName + "." + exp);
 			}
+		} else{ 
+			log.error("Cannot read " + boundaryDirName);
+		}
 		return trees;
 	}
-	
+
 	
 	/**
 	 * read path iterator info from stream and create Area. 
@@ -197,7 +198,7 @@ public class BoundaryUtil {
 	 */
 	public static Area readAreaAsPath(DataInputStream inpStream) throws IOException{
 		double[] res = new double[2];
-		Path2D.Double path = new Path2D.Double();
+		Path2D.Double path = new Path2D.Double(PathIterator.WIND_NON_ZERO, 1024);
 		int windingRule = inpStream.readInt();
 		path.setWindingRule(windingRule);
 		int type = inpStream.readInt(); 
@@ -282,7 +283,7 @@ public class BoundaryUtil {
 	private static List<Boundary> readStreamRawFormat(
 			DataInputStream inpStream, String fname,
 			uk.me.parabola.imgfmt.app.Area bbox) throws IOException			{
-		List<Boundary> boundaryList = new ArrayList<Boundary>();
+		List<Boundary> boundaryList = new ArrayList<>();
 
 		try {
 			while (true) {
@@ -290,7 +291,8 @@ public class BoundaryUtil {
 				int minLong = inpStream.readInt();
 				int maxLat = inpStream.readInt();
 				int maxLong = inpStream.readInt();
-				log.debug("Next boundary. Lat min:",minLat,"max:",maxLat,"Long min:",minLong,"max:",maxLong);
+				if (log.isDebugEnabled())
+					log.debug("Next boundary. Lat min:",minLat,"max:",maxLat,"Long min:",minLong,"max:",maxLong);
 				uk.me.parabola.imgfmt.app.Area rBbox = new uk.me.parabola.imgfmt.app.Area(
 						minLat, minLong, maxLat, maxLong);
 				int bSize = inpStream.readInt();
@@ -332,11 +334,11 @@ public class BoundaryUtil {
 	 * @return a List with the names
 	 */
 	public static List<String> getRequiredBoundaryFileNames(uk.me.parabola.imgfmt.app.Area bbox) {
-		List<String> names = new ArrayList<String>();
-		for (int latSplit = BoundaryUtil.getSplitBegin(bbox.getMinLat()); latSplit <= BoundaryUtil
-				.getSplitBegin(bbox.getMaxLat()); latSplit += BoundaryUtil.RASTER) {
-			for (int lonSplit = BoundaryUtil.getSplitBegin(bbox.getMinLong()); lonSplit <= BoundaryUtil
-					.getSplitBegin(bbox.getMaxLong()); lonSplit += BoundaryUtil.RASTER) {
+		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");
 			}
 		}
@@ -350,7 +352,7 @@ public class BoundaryUtil {
 	 * @return the available *.bnd files in dirName.
 	 */
 	public static List<String> getBoundaryDirContent(String dirName) {
-		List<String> names = new ArrayList<String>();
+		List<String> names = new ArrayList<>();
 		File boundaryDir = new File(dirName);
 		if (!boundaryDir.exists())
 			log.error("boundary directory/zip does not exist: " + dirName);
@@ -364,8 +366,7 @@ public class BoundaryUtil {
 				}
 			}
 			else if (boundaryDir.getName().endsWith(".zip")){
-				try {
-					ZipFile zipFile = new ZipFile(boundaryDir);
+				try (ZipFile zipFile = new ZipFile(boundaryDir)){
 					Enumeration<? extends ZipEntry> entries = zipFile.entries();
 					boolean isFlat = true;
 					while(entries.hasMoreElements()) {
@@ -376,7 +377,6 @@ public class BoundaryUtil {
 						if (entry.getName().endsWith(".bnd"))
 							names.add(entry.getName());
 					}
-					zipFile.close();
 					if (!isFlat){
 						log.error("boundary zip file contains directories. Files in directories will be ignored." + dirName);			
 					}
@@ -428,8 +428,8 @@ public class BoundaryUtil {
 		filename = filename.substring(0,filename.length()-4);
 		String[] fParts = filename.split(Pattern.quote("_"));
 		
-		int lat = Integer.valueOf(fParts[1]);
-		int lon = Integer.valueOf(fParts[2]);
+		int lat = Integer.parseInt(fParts[1]);
+		int lon = Integer.parseInt(fParts[2]);
 		
 		return new uk.me.parabola.imgfmt.app.Area(lat, lon, lat+RASTER, lon+RASTER);
 	}
@@ -450,11 +450,8 @@ public class BoundaryUtil {
 			uk.me.parabola.imgfmt.app.Area searchBbox, 
 			EnhancedProperties props)throws IOException{
 		BoundaryQuadTree bqt = null;
-		uk.me.parabola.imgfmt.app.Area qtBbox = BoundaryUtil.getBbox(fname);
-		try {
-			DataInputStream inpStream = new DataInputStream(
-					new BufferedInputStream(stream, 1024 * 1024));
-
+		uk.me.parabola.imgfmt.app.Area qtBbox = getBbox(fname);
+		try (DataInputStream inpStream = new DataInputStream(new BufferedInputStream(stream, 1024 * 1024))){
 			try {
 				// 1st read the mkgmap release the boundary file is created by
 				String mkgmapRel = "?";
@@ -472,9 +469,8 @@ public class BoundaryUtil {
 					int nBytes = inpStream.read(header, bytesRead, headerLength-bytesRead);
 					if (nBytes<0) {
 						throw new IOException("Cannot read header with size "+headerLength);
-					} else {
-						bytesRead += nBytes;
 					}
+					bytesRead += nBytes;
 				}
 					
 				ByteArrayInputStream rawHeaderStream = new ByteArrayInputStream(header);
@@ -513,11 +509,7 @@ public class BoundaryUtil {
 			catch (FormatException exp) {
 				log.error("Failed to read boundary file " + fname + " " + exp.getMessage());
 			} 
-			inpStream.close();
-		} finally {
-			if (stream != null)
-				stream.close();
-		}
+		} 
 		return bqt;
 	}
 	
@@ -527,9 +519,9 @@ public class BoundaryUtil {
 	 * @return the boundary list with postal code areas merged
 	 */
 	private static List<Boundary> mergePostalCodes(List<Boundary> boundaries) {
-		List<Boundary> mergedList = new ArrayList<Boundary>(boundaries.size());
+		List<Boundary> mergedList = new ArrayList<>(boundaries.size());
 		
-		MultiHashMap<String, Boundary> equalPostalCodes = new MultiHashMap<String, Boundary>();
+		MultiHashMap<String, Boundary> equalPostalCodes = new MultiHashMap<>();
 		for (Boundary boundary : boundaries) {
 			String postalCode = getPostalCode(boundary.getTags());
 			if (postalCode == null) {
@@ -595,26 +587,21 @@ public class BoundaryUtil {
 			
 			if ("boundary".equals(type) || "multipolygon".equals(type)) {
 				String boundaryVal = b.getTags().get("boundary");
-				if ("administrative".equals(boundaryVal)) {
-					// for boundary=administrative the admin_level must be set
-					if (b.getTags().get("admin_level") == null) {
-						return false;
-					}
-					// and a name must be set (check only for a tag containing name
-					Iterator<Entry<String,String>> tagIterator = b.getTags().entryIterator();
-					while (tagIterator.hasNext()) {
-						Entry<String,String> tag  = tagIterator.next();
-						if (tag.getKey().contains("name")) {
-							return true;
-						}
-					}
-					// does not contain a name tag => do not use it
-					return false;					
-				}  else {
+				if ("administrative".equals(boundaryVal) == false) 
+					return false;
+				// for boundary=administrative the admin_level must be set
+				if (b.getTags().get("admin_level") == null) {
 					return false;
 				}
-			} else {
-				return false;
+				// and a name must be set (check only for a tag containing name
+				Iterator<Entry<String,String>> tagIterator = b.getTags().entryIterator();
+				while (tagIterator.hasNext()) {
+					Entry<String,String> tag  = tagIterator.next();
+					if (tag.getKey().contains("name")) {
+						return true;
+					}
+				}
+				// does not contain a name tag => do not use it
 			}
 		} else if (b.getId().startsWith("w")) {
 			// the boundary tag must be "administrative" or "postal_code"
@@ -633,13 +620,9 @@ public class BoundaryUtil {
 					}
 				}
 				// does not contain a name tag => do not use it
-				return false;
-			} else {
-				return false;
 			}
-		} else {
-			return false;
-		}
+		} 
+		return false;
 	}
 	
 	
@@ -719,4 +702,99 @@ public class BoundaryUtil {
 		return Double.longBitsToDouble(res);
 	}
 
+	/**
+	 * Raster a given area. This is the non-recursive public method.
+	 * @param areaToSplit the area
+	 * @return a map with the divided shapes 
+	 */
+	public static Map<String, Shape> rasterArea(Area areaToSplit) {
+		return rasterShape(areaToSplit, new HashMap<String, Shape>());
+	}
+
+	/**
+	 * Raster a given shape. This method calls itself recursively.
+	 * @param shapeToSplit the shape
+	 * @param splits a map that will contain the resulting shapes
+	 * @return a reference to the map 
+	 */
+	private static Map<String, Shape> rasterShape(Shape shapeToSplit, Map<String, Shape> splits) {
+		double minX = Double.POSITIVE_INFINITY,minY = Double.POSITIVE_INFINITY, 
+				maxX = Double.NEGATIVE_INFINITY,maxY = Double.NEGATIVE_INFINITY;
+		PathIterator pit = shapeToSplit.getPathIterator(null);
+		double[] points = new double[512];
+		double[] res = new double[6];
+		int num = 0;
+		while (!pit.isDone()) {
+			int type = pit.currentSegment(res);
+			double x = res[0];
+			double y = res[1];
+			if (x < minX) minX = x;
+			if (x > maxX) maxX = x;
+			if (y < minY) minY = y;
+			if (y > maxY) maxY = y;
+			switch (type) {
+			case PathIterator.SEG_LINETO:
+			case PathIterator.SEG_MOVETO:
+				if (num  + 2 >= points.length) {
+					points = Arrays.copyOf(points, points.length * 2);
+				}
+				points[num++] = x;
+				points[num++] = y;
+				break;
+			case PathIterator.SEG_CLOSE:
+				int sMinLong = getSplitBegin((int)Math.round(minX));
+				int sMinLat = getSplitBegin((int)Math.round(minY));
+				int sMaxLong = getSplitEnd((int)Math.round(maxX));
+				int sMaxLat = getSplitEnd((int)Math.round(maxY));
+	
+				int dLon = sMaxLong- sMinLong;
+				int dLat = sMaxLat - sMinLat;
+				Rectangle2D.Double bbox = new Rectangle2D.Double(minX,minY,maxX-minX,maxY-minY);
+				if (dLon > RASTER || dLat > RASTER) {
+					// split into two halves
+					Rectangle clip1,clip2;
+					if (dLon > dLat) {
+						int midLon = getSplitEnd(sMinLong+dLon/2);
+						clip1 = new Rectangle(sMinLong, sMinLat, midLon-sMinLong, dLat);
+						clip2 = new Rectangle(midLon, sMinLat, sMaxLong-midLon, dLat);
+					} else {
+						int midLat = getSplitEnd(sMinLat+dLat/2);
+						clip1 = new Rectangle(sMinLong, sMinLat, dLon, midLat-sMinLat);
+						clip2 = new Rectangle(sMinLong, midLat, dLon, sMaxLat-midLat);
+					}
+	
+					// intersect with the both halves
+					// and split both halves recursively
+					Path2D.Double clippedPath = ShapeSplitter.clipSinglePathWithSutherlandHodgman (points, num, clip1, bbox);
+					if (clippedPath != null)
+						rasterShape(clippedPath, splits);
+					clippedPath = ShapeSplitter.clipSinglePathWithSutherlandHodgman (points, num, clip2, bbox);
+					if (clippedPath != null)
+						rasterShape(clippedPath, splits);
+				} 
+				else {
+					String key = getKey(sMinLat, sMinLong);
+					// no need to split, path fits into one tile
+					Path2D.Double segment = ShapeSplitter.pointsToPath2D(points, num);
+					if (segment != null){
+						Path2D.Double path = (Path2D.Double) splits.get(key);
+						if (path == null)
+							splits.put(key, segment);
+						else 
+							path.append(segment, false);
+					}
+				}
+				num = 0;
+				minX = minY = Double.POSITIVE_INFINITY; 
+				maxX = maxY = Double.NEGATIVE_INFINITY;
+				break;
+			default:
+				log.error("Unsupported path iterator type " + type
+						+ ". This is an mkgmap error.");
+			}
+	
+			pit.next();
 		}
+		return splits;
+	}
+}
diff --git a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
index 4c3b803..a001070 100644
--- a/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/polish/PolishMapDataSource.java
@@ -96,6 +96,7 @@ 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;
@@ -667,6 +668,12 @@ public class PolishMapDataSource extends MapperBasedMapDataSource implements Loa
 		} else if (name.equals("CodePage")) {
 			dec = Charset.forName("cp" + value).newDecoder();
 			dec.onUnmappableCharacter(CodingErrorAction.REPLACE);
+		} else if (name.endsWith("LeftSideTraffic")){
+			if ("Y".equals(value)){
+				setDriveOnLeft(true);
+			} else if ("N".equals(value)){ 
+				setDriveOnLeft(false);
+			}
 		}
 	}
 
diff --git a/src/uk/me/parabola/util/Java2DConverter.java b/src/uk/me/parabola/util/Java2DConverter.java
index 5879ff3..4c3a857 100644
--- a/src/uk/me/parabola/util/Java2DConverter.java
+++ b/src/uk/me/parabola/util/Java2DConverter.java
@@ -14,6 +14,7 @@ package uk.me.parabola.util;
 
 import java.awt.Polygon;
 import java.awt.Rectangle;
+import java.awt.Shape;
 import java.awt.geom.Area;
 import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
@@ -47,16 +48,17 @@ public class Java2DConverter {
 	}
 
 	/**
-	 * Converts the bounding box of a Java2D {@link Area} object to an mkgmap
+	 * Converts the bounding box of a Java2D {@link Shape} object to an mkgmap
 	 * {@link uk.me.parabola.imgfmt.app.Area} object.
 	 * 
-	 * @param area a Java2D area
+	 * @param shape a Java2D Shape (Area, Path2D, ...)
 	 * @return the bounding box
 	 */
-	public static uk.me.parabola.imgfmt.app.Area createBbox(Area area) {
-		Rectangle areaBounds = area.getBounds();
+	public static uk.me.parabola.imgfmt.app.Area createBbox(Shape shape) {
+		Rectangle areaBounds = shape.getBounds();
 		return new uk.me.parabola.imgfmt.app.Area(areaBounds.y,areaBounds.x,(int) areaBounds.getMaxY(),(int) areaBounds.getMaxX());
 	}
+	
 
 	/**
 	 * Creates a Java2D {@link Area} object from a polygon given as a list of
@@ -66,10 +68,22 @@ public class Java2DConverter {
 	 * @return the converted Java2D area
 	 */
 	public static Area createArea(List<Coord> polygonPoints) {
-		if (polygonPoints.size()<3)
-			return new Area();
-		Path2D path = new Path2D.Double();
+		return new Area(createPath2D(polygonPoints));
+	}
+	
+	/**
+	 * Creates a Java2D {@link Path2D} object from a polygon given as a list of
+	 * {@link Coord} objects. This list should describe a closed polygon.
+	 * 
+	 * @param polygonPoints a list of points that describe a closed polygon
+	 * @return the converted Java2D path
+	 */
+	public static Path2D createPath2D (List<Coord> polygonPoints) {
 		int n = polygonPoints.size();
+		if (n < 3)
+			return new Path2D.Double();
+		
+		Path2D path = new Path2D.Double(PathIterator.WIND_NON_ZERO, n);
 		if (polygonPoints.get(0).highPrecEquals(polygonPoints.get(n-1))){
 			// if first and last point are high-prec-equal, ignore last point 
 			// because we use closePath() to signal that
@@ -92,8 +106,7 @@ public class Java2DConverter {
 			lastLat = lat30;
 		}
 		path.closePath();
-		return new Area(path);
-		
+		return path;
 	}
 
 	public static Polygon createHighPrecPolygon(List<Coord> points) {
@@ -120,7 +133,7 @@ public class Java2DConverter {
 		} else if (area.isSingular()) {
 			return Collections.singletonList(area);
 		} else {
-			List<Area> singularAreas = new ArrayList<Area>();
+			List<Area> singularAreas = new ArrayList<>();
 
 			// all ways in the area MUST define outer areas
 			// it is not possible that one of the areas define an inner segment
@@ -193,7 +206,7 @@ public class Java2DConverter {
 			case PathIterator.SEG_MOVETO:
 				if (points != null)
 					log.error("area not singular");
-				points = new ArrayList<Coord>();
+				points = new ArrayList<>();
 				points.add(Coord.makeHighPrecCoord(lat30, lon30));
 				break;
 			case PathIterator.SEG_LINETO:
@@ -244,7 +257,7 @@ public class Java2DConverter {
 	 * @return a list of closed polygons
 	 */
 	public static List<List<Coord>> areaToShapes(java.awt.geom.Area area) {
-		List<List<Coord>> outputs = new ArrayList<List<Coord>>(4);
+		List<List<Coord>> outputs = new ArrayList<>(4);
 
 		double[] res = new double[6];
 		PathIterator pit = area.getPathIterator(null);
@@ -284,7 +297,7 @@ public class Java2DConverter {
 					}
 				}
 				if (type == PathIterator.SEG_MOVETO){
-					coords = new ArrayList<Coord>();
+					coords = new ArrayList<>();
 					coords.add(Coord.makeHighPrecCoord(lat30, lon30));
 					prevLat30 = lat30;
 					prevLong30 = lon30;
diff --git a/src/uk/me/parabola/util/ShapeSplitter.java b/src/uk/me/parabola/util/ShapeSplitter.java
new file mode 100644
index 0000000..1f2bc6e
--- /dev/null
+++ b/src/uk/me/parabola/util/ShapeSplitter.java
@@ -0,0 +1,260 @@
+package uk.me.parabola.util;
+
+import java.awt.Shape;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Rectangle2D;
+import java.util.Arrays;
+
+import uk.me.parabola.log.Logger;
+
+public class ShapeSplitter {
+	private static final Logger log = Logger.getLogger(ShapeSplitter.class);
+	private static final int LEFT = 0;
+	private static final int TOP = 1;
+	private static final int RIGHT = 2;
+	private static final int BOTTOM= 3;
+
+	
+	/**
+	 * Clip a given shape with a given rectangle. 
+	 * @param shape the subject shape to clip
+	 * @param clippingRect the clipping rectangle
+	 * @return the intersection of the shape and the rectangle 
+	 * or null if they don't intersect. 
+	 * The intersection may contain dangling edges. 
+	 */
+	public static Path2D.Double clipShape (Shape shape, Rectangle2D clippingRect) {
+		double minX = Double.POSITIVE_INFINITY,minY = Double.POSITIVE_INFINITY, 
+				maxX = Double.NEGATIVE_INFINITY,maxY = Double.NEGATIVE_INFINITY;
+		PathIterator pit = shape.getPathIterator(null);
+		double[] points = new double[512];
+		int num = 0;
+		Path2D.Double result = null;
+		double[] res = new double[6];
+		while (!pit.isDone()) {
+			int type = pit.currentSegment(res);
+			double x = res[0];
+			double y = res[1];
+			if (x < minX) minX = x;
+			if (x > maxX) maxX = x;
+			if (y < minY) minY = y;
+			if (y > maxY) maxY = y;
+			switch (type) {
+			case PathIterator.SEG_LINETO:
+			case PathIterator.SEG_MOVETO:
+				if (num  + 2 >= points.length) {
+					points = Arrays.copyOf(points, points.length * 2);
+				}
+				points[num++] = x;
+				points[num++] = y;
+				break;
+			case PathIterator.SEG_CLOSE:
+				Path2D.Double segment = null;
+				if (clippingRect.contains(minX, minY) == false || clippingRect.contains(maxX,maxY) == false){
+					Rectangle2D.Double bbox = new Rectangle2D.Double(minX,minY,maxX-minX,maxY-minY);
+					segment = clipSinglePathWithSutherlandHodgman (points, num, clippingRect, bbox);
+				} else 
+					segment = pointsToPath2D(points, num);
+				if (segment != null){
+					if (result == null)
+						result = segment;
+					else 
+						result.append(segment, false);
+				}
+				num = 0;
+				minX = minY = Double.POSITIVE_INFINITY; 
+				maxX = maxY = Double.NEGATIVE_INFINITY;
+				break;
+			default:
+				log.error("Unsupported path iterator type " + type
+						+ ". This is an mkgmap error.");
+			}
+	
+			pit.next();
+		}
+		return result;
+	}
+
+	/**
+	 * Convert a list of longitude+latitude values to a Path2D.Double
+	 * @param points the pairs
+	 * @return the path or null if the path describes a point or line. 
+	 */
+	public static Path2D.Double pointsToPath2D(double[] points, int num) {
+		if (num < 2)
+			return null;
+		if (points[0] == points[num-2] && points[1] == points[num-1])
+			num -= 2;
+		if (num < 6)
+			return null;
+		Path2D.Double path = new Path2D.Double(Path2D.WIND_NON_ZERO, num / 2 + 2);
+		double lastX = points[0], lastY = points[1];
+		path.moveTo(lastX, lastY);
+		int numOut = 1;
+		for (int i = 2; i < num; ){
+			double x = points[i++], y = points[i++];
+			if (x != lastX || y != lastY){
+				path.lineTo(x,y);
+				lastX = x; lastY = y;
+				++numOut;
+			}
+		}
+		if (numOut < 3)
+			return null;
+		path.closePath();
+		return path;
+	}
+
+	//    The Sutherland�Hodgman algorithm Pseudo-Code: 	 
+	//	  List outputList = subjectPolygon;
+	//	  for (Edge clipEdge in clipPolygon) do
+	//	     List inputList = outputList;
+	//	     outputList.clear();
+	//	     Point S = inputList.last;
+	//	     for (Point E in inputList) do
+	//	        if (E inside clipEdge) then
+	//	           if (S not inside clipEdge) then
+	//	              outputList.add(ComputeIntersection(S,E,clipEdge));
+	//	           end if
+	//	           outputList.add(E);
+	//	        else if (S inside clipEdge) then
+	//	           outputList.add(ComputeIntersection(S,E,clipEdge));
+	//	        end if
+	//	        S = E;
+	//	     done
+	//	  done
+
+	/**
+	 * Clip a single path with a given rectangle using the Sutherland-Hodgman algorithm. This is much faster compared to
+	 * the area.intersect method, but may create dangling edges. 
+	 * @param points	a list of longitude+latitude pairs  
+	 * @param num the nnumber of valid values in points 
+	 * @param clippingRect the clipping rectangle 
+	 * @param bbox the bounding box of the path 
+	 * @return the clipped path as a Path2D.Double or null if the result is empty  
+	 */
+	public static Path2D.Double clipSinglePathWithSutherlandHodgman (double[] points, int num, Rectangle2D clippingRect, Rectangle2D.Double bbox) {
+		if (num <= 2 || bbox.intersects(clippingRect) == false){
+			return null;
+		}
+		
+		int countVals = num;
+		if (points[0] == points[num-2] && points[1] == points[num-1]){
+			countVals -= 2;
+		}
+		double[] outputList = points;
+		double[] input;
+		
+		double leftX = clippingRect.getMinX();
+		double rightX = clippingRect.getMaxX();
+		double lowerY = clippingRect.getMinY();
+		double upperY = clippingRect.getMaxY();
+		boolean eIsIn = false, sIsIn = false;
+		for (int side = LEFT; side <= BOTTOM; side ++){
+			if (countVals < 6)
+				return null; // ignore point or line 
+			
+			boolean skipTestForThisSide;
+			switch(side){
+			case LEFT: skipTestForThisSide = (bbox.getMinX() >= leftX); break;
+			case TOP: skipTestForThisSide = (bbox.getMaxY()  < upperY); break;
+			case RIGHT: skipTestForThisSide = (bbox.getMaxX()  < rightX); break;
+			default: skipTestForThisSide = (bbox.getMinY() >= lowerY); 
+			}
+			if (skipTestForThisSide)
+				continue;
+			
+			input = outputList;
+			outputList = new double[countVals + 16];
+			double sLon = 0,sLat = 0;
+			double pLon = 0,pLat = 0; // intersection
+			int posIn = countVals - 2; 
+			int posOut = 0;
+			for (int i = 0; i < countVals+2; i+=2){
+				if (posIn >=  countVals)
+					posIn = 0;
+				double eLon = input[posIn++];
+				double eLat = input[posIn++];
+				switch (side){
+				case LEFT: eIsIn =  (eLon >= leftX); break;
+				case TOP: eIsIn =  (eLat < upperY); break;
+				case RIGHT: eIsIn =  (eLon < rightX); break;
+				default: eIsIn =  (eLat >= lowerY); 
+				}
+				if (i > 0){
+					if (eIsIn != sIsIn){
+						// compute intersection
+						double slope;
+						if (eLon != sLon)
+							slope = (eLat - sLat) / (eLon-sLon);
+						else slope = 1;
+	
+						switch (side){
+						case LEFT: 
+							pLon = leftX;
+							pLat = slope *(leftX-sLon) + sLat;
+							break;
+						case RIGHT: 
+							pLon = rightX;
+							pLat = slope *(rightX-sLon) + sLat; 
+							break;
+	
+						case TOP: 
+							if (eLon != sLon)
+								pLon =  sLon + (upperY - sLat) / slope;
+							else 
+								pLon =  sLon;
+							pLat = upperY;
+							break;
+						default: // BOTTOM
+							if (eLon != sLon)
+								pLon =  sLon + (lowerY - sLat) / slope;
+							else 
+								pLon =  sLon;
+							pLat = lowerY;
+							break;
+	
+						}
+					}
+					int toAdd = 0;
+					if (eIsIn){
+						if (!sIsIn){
+							toAdd += 2;
+						}
+						toAdd += 2;
+					}
+					else {
+						if (sIsIn){
+							toAdd += 2;
+						}
+					}
+					if (posOut + toAdd >= outputList.length) {
+						// unlikely
+						outputList = Arrays.copyOf(outputList, outputList.length * 2);
+					}
+					if (eIsIn){
+						if (!sIsIn){
+							outputList[posOut++] = pLon;
+							outputList[posOut++] = pLat;
+						}
+						outputList[posOut++] = eLon;
+						outputList[posOut++] = eLat;
+					}
+					else {
+						if (sIsIn){
+							outputList[posOut++] = pLon;
+							outputList[posOut++] = pLat;
+						}
+					}
+				}
+				// S = E
+				sLon = eLon; sLat = eLat;
+				sIsIn = eIsIn;
+			}
+			countVals = posOut;
+		}
+		return pointsToPath2D(outputList, countVals);
+	}
+
+}
diff --git a/test/func/route/SimpleRouteTest.java b/test/func/route/SimpleRouteTest.java
index 8fae3b4..c9df2ba 100644
--- a/test/func/route/SimpleRouteTest.java
+++ b/test/func/route/SimpleRouteTest.java
@@ -63,7 +63,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(1554, 2));
+				assertThat("TRE size", size, new RangeMatcher(1454, 2));
 				break;
 			case "LBL":
 				count++;
diff --git a/test/uk/me/parabola/imgfmt/app/CoordTest.java b/test/uk/me/parabola/imgfmt/app/CoordTest.java
index 6b5d1f3..52e6a10 100644
--- a/test/uk/me/parabola/imgfmt/app/CoordTest.java
+++ b/test/uk/me/parabola/imgfmt/app/CoordTest.java
@@ -30,6 +30,8 @@ public class CoordTest {
 	Coord p1_11 = new Coord (1.0, 11.0);
 	Coord p60_10 = new Coord (60.0, 10.0);
 	Coord p61_11 = new Coord (61.0, 11.0);
+	Coord russia1 = new Coord(3069580,8388608);
+	Coord russia2 = new Coord(3105677,8388608);
 	
 	/**
 	 */
@@ -71,4 +73,17 @@ public class CoordTest {
 		assertEquals(124100, p60_10.distanceHaversine(p61_11), 100);
 	}
 
+	@Test 
+	public void testMakeBetweenAt180(){
+		Coord russia3 = russia1.makeBetweenPoint(russia2, 0.5);
+		assertEquals(russia3.getLongitude(), russia1.getLongitude());
+	}
+	
+	@Test 
+	public void destOnRhumLineAt180(){
+		Coord russia3 = russia1.destOnRhumLine(1, 0.0);
+		assertEquals(russia3.getLongitude(), russia1.getLongitude());
+		Coord russia4 = russia1.destOnRhumLine(10000, 0.0);
+		assertEquals(russia4.getLongitude(), russia1.getLongitude());
+	}
 }
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilterTest.java b/test/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilterTest.java
new file mode 100644
index 0000000..ce2d077
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/osmstyle/actions/ConvertFilterTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+package uk.me.parabola.mkgmap.osmstyle.actions;
+
+import java.util.Arrays;
+import java.util.List;
+
+import uk.me.parabola.mkgmap.reader.osm.Element;
+import uk.me.parabola.mkgmap.reader.osm.Way;
+import uk.me.parabola.mkgmap.scan.SyntaxException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ConvertFilterTest {
+	private final List<Data> simpleTests = Arrays.asList(
+			new Data("kmh=>mph", "100", "62"),
+			new Data("km/h=>mph", "100", "62"),
+			new Data("mph=>km/h", "60", "97"),
+			new Data("m=>ft", "10", "33"),
+			new Data("km=>ft", "10", "32808"),
+			new Data("ft=>m", "100", "30"),
+			new Data("mi=>km", "100", "161"),
+			new Data("knots=>mph", "20", "23")
+	);
+
+	/** This is not used by this filter, so no need to create a new one for each test */
+	private final Element el = new Way(1);
+
+	/**
+	 * Just test a whole bunch of different conversions.
+	 */
+	@Test
+	public void testConversions() {
+		for (Data data : simpleTests) {
+			ConvertFilter f = new ConvertFilter(data.conv);
+			String result = f.doFilter(data.input, el);
+			assertEquals("Simple test for conversion " + data.conv, data.output, result);
+		}
+	}
+
+	/**
+	 * If there is a unit on the input value, and that is the same as the default, then the conversion
+	 * should be between the units as stated.
+	 *
+	 * Separate test, since there is likely to be a different code path involved.
+	 */
+	@Test
+	public void testConvertWithUnitSameAsDefault() {
+		ConvertFilter f = new ConvertFilter("m=>ft");
+		assertEquals("328", f.doFilter("100m", el));
+	}
+
+	/**
+	 * If the value has a unit which is the same as the target unit, then the result will be the
+	 * input value (without the unit).
+	 */
+	@Test
+	public void testConvertWIthUnitSameAsTarget() {
+		ConvertFilter f = new ConvertFilter("m=>ft");
+		assertEquals("100", f.doFilter("100ft", el));
+	}
+
+	/**
+	 * Test the case where the input string has a unit specified that is neither the source nor the
+	 * target string in the conversion specifier.
+	 */
+	@Test
+	public void testConvertWithDifferentUnit() {
+		ConvertFilter f = new ConvertFilter("km=>ft");
+		assertEquals("33", f.doFilter("10m", el));
+	}
+
+	@Test
+	public void testConvertNumberWithSpaces() {
+		ConvertFilter f = new ConvertFilter("m=>ft");
+		String s = f.doFilter(" 10 ", el);
+		assertEquals("33", s);
+	}
+
+	@Test
+	public void testConvertWithSpaces() {
+		ConvertFilter f = new ConvertFilter("km/h=>mph");
+		String s = f.doFilter(" 10 km/h ", el);
+		assertEquals("6", s);
+	}
+
+	@Test(expected = SyntaxException.class)
+	public void testUnrecognisable() {
+		ConvertFilter f = new ConvertFilter("fjdkfjdk");
+	}
+
+	@Test
+	public void testBadConversion() {
+		ConvertFilter f = new ConvertFilter("kk=>ft");
+
+		String in = "10m";
+		assertEquals(in, f.doFilter(in, el));
+	}
+
+	@Test
+	public void testValueNotNumber() {
+		ConvertFilter f = new ConvertFilter("km=>m");
+
+		String in = "x10m";
+		assertEquals(in, f.doFilter(in, el));
+	}
+
+	@Test
+	public void testUnknownUnit() {
+		ConvertFilter f = new ConvertFilter("m=>ft");
+		String in = "10abc";
+		String s = f.doFilter(in, el);
+		assertEquals(in, s);
+	}
+
+	/**
+	 * Converting between a distance and a speed for example.
+	 */
+	@Test
+	public void testIncompatibleConversion() {
+		ConvertFilter f = new ConvertFilter("m=>mph");
+		String in = "10m";
+		String s = f.doFilter(in, el);
+		assertEquals(in, s);
+	}
+
+	class Data {
+		private final String conv;
+		private final String input;
+		private final String output;
+
+		Data(String conv, String input, String output) {
+			this.conv = conv;
+			this.input = input;
+			this.output = output;
+		}
+	}
+}
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilterTest.java b/test/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilterTest.java
new file mode 100644
index 0000000..2db6159
--- /dev/null
+++ b/test/uk/me/parabola/mkgmap/osmstyle/actions/CountryISOFilterTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 or
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+/* Create date: 28-Nov-2014 */
+package uk.me.parabola.mkgmap.osmstyle.actions;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * 
+ * @author GerdP
+ *
+ */
+public class CountryISOFilterTest {
+	/**
+	 * Test different inputs for the country-ISO filter
+	 */
+	@Test
+	public void testDoFilter() {
+		CountryISOFilter filter = new CountryISOFilter();
+		String s;
+		s = filter.doFilter("Germany", null);
+		assertEquals("Germany", "DEU", s);
+		s = filter.doFilter("Deutschland", null);
+		assertEquals("Deutschland", "DEU", s);
+		s = filter.doFilter("United Kingdom", null);
+		assertEquals("United Kingdom", "GBR", s);
+		s = filter.doFilter("UNITED KINGDOM", null);
+		assertEquals("UNITED KINGDOM", "GBR", s);
+		s = filter.doFilter("united kingdom", null);
+		assertEquals("united kingdom", "GBR", s);
+		s = filter.doFilter("UK", null);
+		assertEquals("UK", "GBR", s);
+		s = filter.doFilter("xyz", null);
+		assertEquals("xyz", "xyz", s);
+		s = filter.doFilter("Ελλάδα", null);
+		assertEquals("Ελλάδα", "GRC", s);
+		s = filter.doFilter("  germany ", null);
+		assertEquals("  germany ", "DEU", s);
+
+	
+	}
+
+}
diff --git a/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java b/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java
index 52de042..8a30ca6 100644
--- a/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java
+++ b/test/uk/me/parabola/mkgmap/osmstyle/actions/ValueBuilderTest.java
@@ -130,6 +130,20 @@ public class ValueBuilderTest {
 		assertEquals("substitutions in name", "x|y w|w", s);
 	}
 
+	/**
+	 * Test that you can use a space before the pipe with the old unquoted syntax.
+	 */
+	@Test
+	public void testSpacedArgsOldSyntax() {
+		ValueBuilder vb = new ValueBuilder("{ name '${rcnname | substring:1:14}' }");
+		Element el = new Way(1);
+
+		el.addTag("rcnname", "1234567890123456789");
+
+		String s = vb.build(el, null);
+		assertEquals("value is trimmed", "{ name '2345678901234' }", s);
+	}
+
 	@Test
 	public void testQuotedSplitLines() {
 		String value =

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